.BANCHO. Merge branch tornado-gevent into master

This commit is contained in:
Nyo 2016-08-23 20:35:47 +02:00
commit de47c46d25
12 changed files with 506 additions and 378 deletions

View File

@ -10,14 +10,14 @@ This is Ripple's bancho server. It handles:
## Requirements ## Requirements
- Python 3.5 - Python 3.5
- MySQLdb (`mysqlclient` or `mysql-python`) - MySQLdb (`mysqlclient` or `mysql-python`)
- Bottle - Tornado
- Gevent - Gevent
- Bcrypt - Bcrypt
## How to set up pep.py ## How to set up pep.py
First of all, install all the dependencies First of all, install all the dependencies
``` ```
$ pip install mysqlclient bottle gevent bcrypt $ pip install mysqlclient tornado gevent bcrypt
``` ```
then, run pep.py once to create the default config file and edit it then, run pep.py once to create the default config file and edit it
``` ```

View File

@ -586,7 +586,6 @@ def tillerinoLast(fro, chan, message):
stars = oppaiData["stars"] stars = oppaiData["stars"]
msg += " | {0:.2f} stars".format(stars) msg += " | {0:.2f} stars".format(stars)
return msg return msg
except Exception as a: except Exception as a:
log.error(a) log.error(a)

View File

@ -2,22 +2,27 @@ from helpers import userHelper
from constants import serverPackets from constants import serverPackets
from constants import exceptions from constants import exceptions
from objects import glob from objects import glob
from helpers import consoleHelper
from constants import bcolors
from helpers import locationHelper from helpers import locationHelper
from helpers import countryHelper from helpers import countryHelper
import time
from helpers import generalFunctions
import sys import sys
import traceback import traceback
from helpers import requestHelper
from helpers import discordBotHelper
from helpers import logHelper as log from helpers import logHelper as log
from helpers import chatHelper as chat from helpers import chatHelper as chat
from constants import privileges from constants import privileges
from helpers import requestHelper
def handle(bottleRequest): def handle(tornadoRequest):
# Data to return # Data to return
responseTokenString = "ayy" responseTokenString = "ayy"
responseData = bytes() responseData = bytes()
# Get IP from tornado request # Get IP from tornado request
requestIP = requestHelper.getRequestIP(bottleRequest) requestIP = tornadoRequest.getRequestIP()
# Avoid exceptions # Avoid exceptions
clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"] clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"]
@ -25,8 +30,7 @@ def handle(bottleRequest):
# Split POST body so we can get username/password/hardware data # Split POST body so we can get username/password/hardware data
# 2:-3 thing is because requestData has some escape stuff that we don't need # 2:-3 thing is because requestData has some escape stuff that we don't need
postBody = bottleRequest.body.read() loginData = str(tornadoRequest.request.body)[2:-3].split("\\n")
loginData = str(postBody)[2:-3].split("\\n")
try: try:
# If true, print error to console # If true, print error to console
err = False err = False

View File

@ -1,37 +1,39 @@
from helpers import requestHelper
from constants import exceptions from constants import exceptions
import json import json
from objects import glob from objects import glob
from helpers import chatHelper from helpers import chatHelper
import bottle from helpers import logHelper as log
@bottle.route("/api/v1/fokabotMessage") class handler(requestHelper.asyncRequestHandler):
def GETApiFokabotMessage(): def asyncGet(self):
statusCode = 400
data = {"message": "unknown error"}
try:
# Check arguments
if "k" not in bottle.request.query or "to" not in bottle.request.query or "msg" not in bottle.request.query:
raise exceptions.invalidArgumentsException()
# Check ci key
key = bottle.request.query["k"]
if key is None or key != glob.conf.config["server"]["cikey"]:
raise exceptions.invalidArgumentsException()
# Send chat message
chatHelper.sendMessage("FokaBot", bottle.request.query["to"], bottle.request.query["msg"])
# Status code and message
statusCode = 200
data["message"] = "ok"
except exceptions.invalidArgumentsException:
statusCode = 400 statusCode = 400
data["message"] = "invalid parameters" data = {"message": "unknown error"}
finally: try:
# Add status code to data # Check arguments
data["status"] = statusCode if requestHelper.checkArguments(self.request.arguments, ["k", "to", "msg"]) == False:
raise exceptions.invalidArgumentsException()
# Send response # Check ci key
bottle.response.status = statusCode key = self.get_argument("k")
bottle.response.add_header("Content-Type", "application/json") if key is None or key != glob.conf.config["server"]["cikey"]:
yield json.dumps(data) raise exceptions.invalidArgumentsException()
log.info("API REQUEST FOR FOKABOT MESSAGE AAAAAAA")
chatHelper.sendMessage("FokaBot", self.get_argument("to"), self.get_argument("msg"))
# Status code and message
statusCode = 200
data["message"] = "ok"
except exceptions.invalidArgumentsException:
statusCode = 400
data["message"] = "invalid parameters"
finally:
# Add status code to data
data["status"] = statusCode
# Send response
#self.clear()
self.write(json.dumps(data))
self.set_status(statusCode)
#self.finish(json.dumps(data))

View File

@ -1,39 +1,48 @@
from helpers import requestHelper
from constants import exceptions from constants import exceptions
import json import json
from objects import glob from objects import glob
import bottle
@bottle.route("/api/v1/isOnline") class handler(requestHelper.asyncRequestHandler):
def GETApiIsOnline(): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}
try: try:
# Check arguments # Check arguments
if "u" not in bottle.request.query and "id" not in bottle.request.query: if "u" not in self.request.arguments and "id" not in self.request.arguments:
raise exceptions.invalidArgumentsException()
# Get online staus
if "u" in bottle.request.query:
username = bottle.request.query["u"]
data["result"] = True if glob.tokens.getTokenFromUsername(username) != None else False
else:
try:
userID = int(bottle.request.query["id"])
data["result"] = True if glob.tokens.getTokenFromUserID(userID) != None else False
except:
raise exceptions.invalidArgumentsException() raise exceptions.invalidArgumentsException()
# Status code and message # Get online staus
statusCode = 200 username = None
data["message"] = "ok" userID = None
except exceptions.invalidArgumentsException: if "u" in self.request.arguments:
statusCode = 400 username = self.get_argument("u")
data["message"] = "missing required arguments" else:
finally: try:
# Add status code to data userID = int(self.get_argument("id"))
data["status"] = statusCode except:
raise exceptions.invalidArgumentsException()
# Send response if username == None and userID == None:
bottle.response.status = statusCode data["result"] = False
bottle.response.add_header("Content-Type", "application/json") else:
yield json.dumps(data) if username != None:
data["result"] = True if glob.tokens.getTokenFromUsername(username) != None else False
else:
data["result"] = True if glob.tokens.getTokenFromUserID(userID) != None else False
# Status code and message
statusCode = 200
data["message"] = "ok"
except exceptions.invalidArgumentsException:
statusCode = 400
data["message"] = "missing required arguments"
finally:
# Add status code to data
data["status"] = statusCode
# Send response
#self.clear()
self.write(json.dumps(data))
self.set_status(statusCode)
#self.finish(json.dumps(data))

View File

@ -1,23 +1,24 @@
from helpers import requestHelper
import json import json
from objects import glob from objects import glob
import bottle
@bottle.route("/api/v1/onlineUsers") class handler(requestHelper.asyncRequestHandler):
def GETApiOnlineUsers(): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}
try: try:
# Get online users count # Get online users count
data["result"] = len(glob.tokens.tokens) data["result"] = len(glob.tokens.tokens)
# Status code and message # Status code and message
statusCode = 200 statusCode = 200
data["message"] = "ok" data["message"] = "ok"
finally: finally:
# Add status code to data # Add status code to data
data["status"] = statusCode data["status"] = statusCode
# Send response # Send response
bottle.response.status = statusCode #self.clear()
bottle.response.add_header("Content-Type", "application/json") self.write(json.dumps(data))
yield json.dumps(data) self.set_status(statusCode)
#self.finish(json.dumps(data))

View File

@ -1,23 +1,24 @@
from helpers import requestHelper
import json import json
from objects import glob from objects import glob
import bottle
@bottle.route("/api/v1/serverStatus") class handler(requestHelper.asyncRequestHandler):
def GETApiServerStatus(): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}
try: try:
# Get online users count # Get online users count
data["result"] = -1 if glob.restarting == True else 1 data["result"] = -1 if glob.restarting == True else 1
# Status code and message # Status code and message
statusCode = 200 statusCode = 200
data["message"] = "ok" data["message"] = "ok"
finally: finally:
# Add status code to data # Add status code to data
data["status"] = statusCode data["status"] = statusCode
# Send response # Send response
bottle.response.status = statusCode #self.clear()
bottle.response.add_header("Content-Type", "application/json") self.write(json.dumps(data))
yield json.dumps(data) self.set_status(statusCode)
#self.finish(json.dumps(data))

View File

@ -1,48 +1,46 @@
from helpers import requestHelper
from helpers import logHelper as log
import json import json
from objects import glob from objects import glob
from constants import exceptions from constants import exceptions
import bottle
@bottle.route("/api/v1/verifiedStatus") class handler(requestHelper.asyncRequestHandler):
def GETApiVerifiedStatus(): def asyncGet(self):
statusCode = 400
data = {"message": "unknown error"}
try:
# Check arguments
if "u" not in bottle.request.query:
raise exceptions.invalidArgumentsException()
# Get userID and its verified cache thing
# -1: Not in cache
# 0: Not verified (multiacc)
# 1: Verified
userID = bottle.request.query["u"]
callback = None
if "callback" in bottle.request.query:
callback = bottle.request.query["callback"]
data["result"] = -1 if userID not in glob.verifiedCache else glob.verifiedCache[userID]
# Status code and message
statusCode = 200
data["message"] = "ok"
except exceptions.invalidArgumentsException:
statusCode = 400 statusCode = 400
data["message"] = "missing required arguments" data = {"message": "unknown error"}
finally: try:
# Add status code to data # Check arguments
data["status"] = statusCode if requestHelper.checkArguments(self.request.arguments, ["u"]) == False:
raise exceptions.invalidArgumentsException()
# Send response # Get userID and its verified cache thing
bottle.response.add_header("Access-Control-Allow-Origin", "*") # -1: Not in cache
bottle.response.add_header("Content-Type", "application/json") # 0: Not verified (multiacc)
# 1: Verified
userID = self.get_argument("u")
data["result"] = -1 if userID not in glob.verifiedCache else glob.verifiedCache[userID]
# jquery meme # Status code and message
output = "" statusCode = 200
if callback != None: data["message"] = "ok"
output += callback+"(" except exceptions.invalidArgumentsException:
output += json.dumps(data) statusCode = 400
if callback != None: data["message"] = "missing required arguments"
output += ")" finally:
# Add status code to data
data["status"] = statusCode
bottle.response.status = statusCode # Send response
yield output self.add_header("Access-Control-Allow-Origin", "*")
self.add_header("Content-Type", "application/json")
# jquery meme
output = ""
if "callback" in self.request.arguments:
output += self.get_argument("callback")+"("
output += json.dumps(data)
if "callback" in self.request.arguments:
output += ")"
self.write(output)
self.set_status(statusCode)

View File

@ -1,38 +1,39 @@
from helpers import requestHelper
from constants import exceptions from constants import exceptions
import json import json
from objects import glob from objects import glob
from helpers import systemHelper from helpers import systemHelper
from helpers import logHelper as log from helpers import logHelper as log
import bottle
@bottle.route("/api/v1/ciTrigger") class handler(requestHelper.asyncRequestHandler):
def GETCiTrigger(): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}
try: try:
# Check arguments # Check arguments
if "k" not in bottle.request.query: if requestHelper.checkArguments(self.request.arguments, ["k"]) == False:
raise exceptions.invalidArgumentsException() raise exceptions.invalidArgumentsException()
# Check ci key # Check ci key
key = bottle.request.query["k"] key = self.get_argument("k")
if key != glob.conf.config["server"]["cikey"]: if key is None or key != glob.conf.config["server"]["cikey"]:
raise exceptions.invalidArgumentsException() raise exceptions.invalidArgumentsException()
log.info("Ci event triggered!!") log.info("Ci event triggered!!")
systemHelper.scheduleShutdown(5, False, "A new Bancho update is available and the server will be restarted in 5 seconds. Thank you for your patience.") systemHelper.scheduleShutdown(5, False, "A new Bancho update is available and the server will be restarted in 5 seconds. Thank you for your patience.")
# Status code and message # Status code and message
statusCode = 200 statusCode = 200
data["message"] = "ok" data["message"] = "ok"
except exceptions.invalidArgumentsException: except exceptions.invalidArgumentsException:
statusCode = 403 statusCode = 400
data["message"] = "invalid ci key" data["message"] = "invalid ci key"
finally: finally:
# Add status code to data # Add status code to data
data["status"] = statusCode data["status"] = statusCode
# Send response # Send response
bottle.response.status = statusCode #self.clear()
bottle.response.add_header("Content-Type", "application/json") self.write(json.dumps(data))
yield json.dumps(data) self.set_status(statusCode)
#self.finish(json.dumps(data))

View File

@ -1,11 +1,10 @@
import bottle
import datetime import datetime
import gzip import gzip
from helpers import requestHelper
from objects import glob from objects import glob
from helpers import packetHelper
from helpers import logHelper as log
from constants import exceptions from constants import exceptions
from constants import packetIDs from constants import packetIDs
from helpers import packetHelper
from constants import serverPackets from constants import serverPackets
from events import sendPublicMessageEvent from events import sendPublicMessageEvent
from events import sendPrivateMessageEvent from events import sendPrivateMessageEvent
@ -47,199 +46,219 @@ from events import userStatsRequestEvent
from events import requestStatusUpdateEvent from events import requestStatusUpdateEvent
from events import userPanelRequestEvent from events import userPanelRequestEvent
@bottle.route("/", method="POST") # Exception tracking
def POSTMain(): import tornado.web
# Track time if needed import tornado.gen
if glob.outputRequestTime == True: import sys
# Start time import traceback
st = datetime.datetime.now() from raven.contrib.tornado import SentryMixin
from helpers import logHelper as log
# Client's token string and request data class handler(SentryMixin, requestHelper.asyncRequestHandler):
#requestTokenString = bottle.request.headers.get("osu-token") @tornado.web.asynchronous
requestTokenString = bottle.request.get_header("osu-token") @tornado.gen.engine
requestData = bottle.request.body.read() def asyncPost(self):
# Server's token string and request data
responseTokenString = "ayy"
responseData = bytes()
if requestTokenString == None:
# No token, first request. Handle login.
responseTokenString, responseData = loginEvent.handle(bottle.request)
else:
userToken = None # default value
try: try:
# This is not the first packet, send response based on client's request # Track time if needed
# Packet start position, used to read stacked packets if glob.outputRequestTime == True:
pos = 0 # Start time
st = datetime.datetime.now()
# Make sure the token exists # Client's token string and request data
if requestTokenString not in glob.tokens.tokens: requestTokenString = self.request.headers.get("osu-token")
raise exceptions.tokenNotFoundException() requestData = self.request.body
# Token exists, get its object and lock it # Server's token string and request data
userToken = glob.tokens.tokens[requestTokenString] responseTokenString = "ayy"
userToken.lock.acquire() responseData = bytes()
# Keep reading packets until everything has been read if requestTokenString == None:
while pos < len(requestData): # No token, first request. Handle login.
# Get packet from stack starting from new packet responseTokenString, responseData = loginEvent.handle(self)
leftData = requestData[pos:] else:
userToken = None # default value
try:
# This is not the first packet, send response based on client's request
# Packet start position, used to read stacked packets
pos = 0
# Get packet ID, data length and data # Make sure the token exists
packetID = packetHelper.readPacketID(leftData) if requestTokenString not in glob.tokens.tokens:
dataLength = packetHelper.readPacketLength(leftData) raise exceptions.tokenNotFoundException()
packetData = requestData[pos:(pos+dataLength+7)]
# Console output if needed # Token exists, get its object and lock it
if glob.outputPackets == True and packetID != 4: userToken = glob.tokens.tokens[requestTokenString]
log.debug("Incoming packet ({})({}):\n\nPacket code: {}\nPacket length: {}\nSingle packet data: {}\n".format(requestTokenString, userToken.username, str(packetID), str(dataLength), str(packetData))) userToken.lock.acquire()
# Event handler # Keep reading packets until everything has been read
def handleEvent(ev): while pos < len(requestData):
def wrapper(): # Get packet from stack starting from new packet
ev.handle(userToken, packetData) leftData = requestData[pos:]
return wrapper
eventHandler = { # Get packet ID, data length and data
# TODO: Rename packets and events packetID = packetHelper.readPacketID(leftData)
# TODO: Host check for multi dataLength = packetHelper.readPacketLength(leftData)
packetIDs.client_changeAction: handleEvent(changeActionEvent), packetData = requestData[pos:(pos+dataLength+7)]
packetIDs.client_logout: handleEvent(logoutEvent),
packetIDs.client_friendAdd: handleEvent(friendAddEvent),
packetIDs.client_friendRemove: handleEvent(friendRemoveEvent),
packetIDs.client_userStatsRequest: handleEvent(userStatsRequestEvent),
packetIDs.client_requestStatusUpdate: handleEvent(requestStatusUpdateEvent),
packetIDs.client_userPanelRequest: handleEvent(userPanelRequestEvent),
packetIDs.client_channelJoin: handleEvent(channelJoinEvent), # Console output if needed
packetIDs.client_channelPart: handleEvent(channelPartEvent), if glob.outputPackets == True and packetID != 4:
packetIDs.client_sendPublicMessage: handleEvent(sendPublicMessageEvent), log.debug("Incoming packet ({})({}):\n\nPacket code: {}\nPacket length: {}\nSingle packet data: {}\n".format(requestTokenString, userToken.username, str(packetID), str(dataLength), str(packetData)))
packetIDs.client_sendPrivateMessage: handleEvent(sendPrivateMessageEvent),
packetIDs.client_setAwayMessage: handleEvent(setAwayMessageEvent),
packetIDs.client_startSpectating: handleEvent(startSpectatingEvent), # Event handler
packetIDs.client_stopSpectating: handleEvent(stopSpectatingEvent), def handleEvent(ev):
packetIDs.client_cantSpectate: handleEvent(cantSpectateEvent), def wrapper():
packetIDs.client_spectateFrames: handleEvent(spectateFramesEvent), ev.handle(userToken, packetData)
return wrapper
packetIDs.client_joinLobby: handleEvent(joinLobbyEvent), eventHandler = {
packetIDs.client_partLobby: handleEvent(partLobbyEvent), # TODO: Rename packets and events
packetIDs.client_createMatch: handleEvent(createMatchEvent), # TODO: Host check for multi
packetIDs.client_joinMatch: handleEvent(joinMatchEvent), packetIDs.client_changeAction: handleEvent(changeActionEvent),
packetIDs.client_partMatch: handleEvent(partMatchEvent), packetIDs.client_logout: handleEvent(logoutEvent),
packetIDs.client_matchChangeSlot: handleEvent(changeSlotEvent), packetIDs.client_friendAdd: handleEvent(friendAddEvent),
packetIDs.client_matchChangeSettings: handleEvent(changeMatchSettingsEvent), packetIDs.client_friendRemove: handleEvent(friendRemoveEvent),
packetIDs.client_matchChangePassword: handleEvent(changeMatchPasswordEvent), packetIDs.client_userStatsRequest: handleEvent(userStatsRequestEvent),
packetIDs.client_matchChangeMods: handleEvent(changeMatchModsEvent), packetIDs.client_requestStatusUpdate: handleEvent(requestStatusUpdateEvent),
packetIDs.client_matchReady: handleEvent(matchReadyEvent), packetIDs.client_userPanelRequest: handleEvent(userPanelRequestEvent),
packetIDs.client_matchNotReady: handleEvent(matchReadyEvent),
packetIDs.client_matchLock: handleEvent(matchLockEvent),
packetIDs.client_matchStart: handleEvent(matchStartEvent),
packetIDs.client_matchLoadComplete: handleEvent(matchPlayerLoadEvent),
packetIDs.client_matchSkipRequest: handleEvent(matchSkipEvent),
packetIDs.client_matchScoreUpdate: handleEvent(matchFramesEvent),
packetIDs.client_matchComplete: handleEvent(matchCompleteEvent),
packetIDs.client_matchNoBeatmap: handleEvent(matchNoBeatmapEvent),
packetIDs.client_matchHasBeatmap: handleEvent(matchHasBeatmapEvent),
packetIDs.client_matchTransferHost: handleEvent(matchTransferHostEvent),
packetIDs.client_matchFailed: handleEvent(matchFailedEvent),
packetIDs.client_matchChangeTeam: handleEvent(matchChangeTeamEvent),
packetIDs.client_invite: handleEvent(matchInviteEvent),
}
# Packets processed if in restricted mode. packetIDs.client_channelJoin: handleEvent(channelJoinEvent),
# All other packets will be ignored if the user is in restricted mode packetIDs.client_channelPart: handleEvent(channelPartEvent),
packetsRestricted = [ packetIDs.client_sendPublicMessage: handleEvent(sendPublicMessageEvent),
packetIDs.client_logout, packetIDs.client_sendPrivateMessage: handleEvent(sendPrivateMessageEvent),
packetIDs.client_userStatsRequest, packetIDs.client_setAwayMessage: handleEvent(setAwayMessageEvent),
packetIDs.client_requestStatusUpdate,
packetIDs.client_userPanelRequest,
packetIDs.client_changeAction,
packetIDs.client_channelJoin,
packetIDs.client_channelPart,
]
# Process/ignore packet packetIDs.client_startSpectating: handleEvent(startSpectatingEvent),
if packetID != 4: packetIDs.client_stopSpectating: handleEvent(stopSpectatingEvent),
if packetID in eventHandler: packetIDs.client_cantSpectate: handleEvent(cantSpectateEvent),
if userToken.restricted == False or (userToken.restricted == True and packetID in packetsRestricted): packetIDs.client_spectateFrames: handleEvent(spectateFramesEvent),
eventHandler[packetID]()
else:
log.warning("Ignored packet id from {} ({}) (user is restricted)".format(requestTokenString, packetID))
else:
log.warning("Unknown packet id from {} ({})".format(requestTokenString, packetID))
# Update pos so we can read the next stacked packet packetIDs.client_joinLobby: handleEvent(joinLobbyEvent),
# +7 because we add packet ID bytes, unused byte and data length bytes packetIDs.client_partLobby: handleEvent(partLobbyEvent),
pos += dataLength+7 packetIDs.client_createMatch: handleEvent(createMatchEvent),
packetIDs.client_joinMatch: handleEvent(joinMatchEvent),
packetIDs.client_partMatch: handleEvent(partMatchEvent),
packetIDs.client_matchChangeSlot: handleEvent(changeSlotEvent),
packetIDs.client_matchChangeSettings: handleEvent(changeMatchSettingsEvent),
packetIDs.client_matchChangePassword: handleEvent(changeMatchPasswordEvent),
packetIDs.client_matchChangeMods: handleEvent(changeMatchModsEvent),
packetIDs.client_matchReady: handleEvent(matchReadyEvent),
packetIDs.client_matchNotReady: handleEvent(matchReadyEvent),
packetIDs.client_matchLock: handleEvent(matchLockEvent),
packetIDs.client_matchStart: handleEvent(matchStartEvent),
packetIDs.client_matchLoadComplete: handleEvent(matchPlayerLoadEvent),
packetIDs.client_matchSkipRequest: handleEvent(matchSkipEvent),
packetIDs.client_matchScoreUpdate: handleEvent(matchFramesEvent),
packetIDs.client_matchComplete: handleEvent(matchCompleteEvent),
packetIDs.client_matchNoBeatmap: handleEvent(matchNoBeatmapEvent),
packetIDs.client_matchHasBeatmap: handleEvent(matchHasBeatmapEvent),
packetIDs.client_matchTransferHost: handleEvent(matchTransferHostEvent),
packetIDs.client_matchFailed: handleEvent(matchFailedEvent),
packetIDs.client_matchChangeTeam: handleEvent(matchChangeTeamEvent),
packetIDs.client_invite: handleEvent(matchInviteEvent),
}
# Token queue built, send it # Packets processed if in restricted mode.
responseTokenString = userToken.token # All other packets will be ignored if the user is in restricted mode
responseData = userToken.queue packetsRestricted = [
userToken.resetQueue() packetIDs.client_logout,
packetIDs.client_userStatsRequest,
packetIDs.client_requestStatusUpdate,
packetIDs.client_userPanelRequest,
packetIDs.client_changeAction,
packetIDs.client_channelJoin,
packetIDs.client_channelPart,
]
# Update ping time for timeout # Process/ignore packet
userToken.updatePingTime() if packetID != 4:
except exceptions.tokenNotFoundException: if packetID in eventHandler:
# Token not found. Disconnect that user if userToken.restricted == False or (userToken.restricted == True and packetID in packetsRestricted):
responseData = serverPackets.loginError() eventHandler[packetID]()
responseData += serverPackets.notification("Whoops! Something went wrong, please login again.") else:
log.warning("Received packet from unknown token ({}).".format(requestTokenString)) log.warning("Ignored packet id from {} ({}) (user is restricted)".format(requestTokenString, packetID))
log.info("{} has been disconnected (invalid token)".format(requestTokenString)) else:
finally: log.warning("Unknown packet id from {} ({})".format(requestTokenString, packetID))
# Unlock token
if userToken != None:
userToken.lock.release()
if glob.outputRequestTime == True: # Update pos so we can read the next stacked packet
# End time # +7 because we add packet ID bytes, unused byte and data length bytes
et = datetime.datetime.now() pos += dataLength+7
# Total time: # Token queue built, send it
tt = float((et.microsecond-st.microsecond)/1000) responseTokenString = userToken.token
log.debug("Request time: {}ms".format(tt)) responseData = userToken.queue
userToken.resetQueue()
# Send server's response to client # Update ping time for timeout
# We don't use token object because we might not have a token (failed login) userToken.updatePingTime()
if glob.gzip == True: except exceptions.tokenNotFoundException:
# First, write the gzipped response # Token not found. Disconnect that user
responseData = gzip.compress(responseData, int(glob.conf.config["server"]["gziplevel"])) responseData = serverPackets.loginError()
responseData += serverPackets.notification("Whoops! Something went wrong, please login again.")
log.warning("Received packet from unknown token ({}).".format(requestTokenString))
log.info("{} has been disconnected (invalid token)".format(requestTokenString))
finally:
# Unlock token
if userToken != None:
userToken.lock.release()
# Then, add gzip headers if glob.outputRequestTime == True:
bottle.response.add_header("Vary", "Accept-Encoding") # End time
bottle.response.add_header("Content-Encoding", "gzip") et = datetime.datetime.now()
else:
# First, write the response
responseData = responseData
# Add all the headers AFTER the response has been written # Total time:
bottle.response.status = 200 tt = float((et.microsecond-st.microsecond)/1000)
bottle.response.add_header("cho-token", responseTokenString) log.debug("Request time: {}ms".format(tt))
bottle.response.add_header("cho-protocol", "19")
bottle.response.add_header("Content-Type", "text/html; charset=UTF-8")
yield responseData
@bottle.route("/", method="GET") # Send server's response to client
def GETMain(): # We don't use token object because we might not have a token (failed login)
html = "<html><head><title>MA MAURO ESISTE?</title><style type='text/css'>body{width:30%}</style></head><body><pre>" if glob.gzip == True:
html += " _ __<br>" # First, write the gzipped response
html += " (_) / /<br>" self.write(gzip.compress(responseData, int(glob.conf.config["server"]["gziplevel"])))
html += " ______ __ ____ ____ / /____<br>"
html += " / ___/ / _ \\/ _ \\/ / _ \\<br>" # Then, add gzip headers
html += " / / / / /_) / /_) / / ____/<br>" self.add_header("Vary", "Accept-Encoding")
html += "/__/ /__/ .___/ .___/__/ \\_____/<br>" self.add_header("Content-Encoding", "gzip")
html += " / / / /<br>" else:
html += " /__/ /__/<br>" # First, write the response
html += "<b>PYTHON > ALL VERSION</b><br><br>" self.write(responseData)
html += "<marquee style='white-space:pre;'><br>"
html += " .. o .<br>" # Add all the headers AFTER the response has been written
html += " o.o o . o<br>" self.set_status(200)
html += " oo...<br>" self.add_header("cho-token", responseTokenString)
html += " __[]__<br>" self.add_header("cho-protocol", "19")
html += " phwr--> _\\:D/_/o_o_o_|__ <span style=\"font-family: 'Comic Sans MS'; font-size: 8pt;\">u wot m8</span><br>" #self.add_header("Keep-Alive", "timeout=5, max=100")
html += " \\\"\"\"\"\"\"\"\"\"\"\"\"\"\"/<br>" #self.add_header("Connection", "keep-alive")
html += " \\ . .. .. . /<br>" self.add_header("Content-Type", "text/html; charset=UTF-8")
html += "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<br>" except:
html += "</marquee><br><strike>reverse engineering a protocol impossible to reverse engineer since always</strike><br>we are actually reverse engineering bancho successfully. for the third time.<br><br><i>&copy; Ripple team, 2016</i></pre></body></html>" log.error("Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc()))
yield html if glob.sentry:
yield tornado.gen.Task(self.captureException, exc_info=True)
#finally:
# self.finish()
@tornado.web.asynchronous
@tornado.gen.engine
def asyncGet(self):
html = "<html><head><title>MA MAURO ESISTE?</title><style type='text/css'>body{width:30%}</style></head><body><pre>"
html += " _ __<br>"
html += " (_) / /<br>"
html += " ______ __ ____ ____ / /____<br>"
html += " / ___/ / _ \\/ _ \\/ / _ \\<br>"
html += " / / / / /_) / /_) / / ____/<br>"
html += "/__/ /__/ .___/ .___/__/ \\_____/<br>"
html += " / / / /<br>"
html += " /__/ /__/<br>"
html += "<b>PYTHON > ALL VERSION</b><br><br>"
html += "<marquee style='white-space:pre;'><br>"
html += " .. o .<br>"
html += " o.o o . o<br>"
html += " oo...<br>"
html += " __[]__<br>"
html += " phwr--> _\\:D/_/o_o_o_|__ <span style=\"font-family: 'Comic Sans MS'; font-size: 8pt;\">u wot m8</span><br>"
html += " \\\"\"\"\"\"\"\"\"\"\"\"\"\"\"/<br>"
html += " \\ . .. .. . /<br>"
html += "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<br>"
html += "</marquee><br><strike>reverse engineering a protocol impossible to reverse engineer since always</strike><br>we are actually reverse engineering bancho successfully. for the third time.<br><br><i>&copy; Ripple team, 2016</i></pre></body></html>"
self.write(html)
#yield tornado.gen.Task(self.captureMessage, "test")
#self.finish()

View File

@ -1,7 +1,89 @@
import tornado
import tornado.web
import tornado.gen
from tornado.ioloop import IOLoop
from objects import glob from objects import glob
from raven.contrib.tornado import SentryMixin
from raven.contrib.tornado import AsyncSentryClient
import gevent
def getRequestIP(bottleRequest): class asyncRequestHandler(tornado.web.RequestHandler):
realIP = bottleRequest.headers.get("X-Forwarded-For") if glob.cloudflare == True else bottleRequest.headers.get("X-Real-IP") """
if realIP != None: Tornado asynchronous request handler
return realIP create a class that extends this one (requestHelper.asyncRequestHandler)
return bottleRequest.environ.get("REMOTE_ADDR") use asyncGet() and asyncPost() instad of get() and post().
Done. I'm not kidding.
"""
@tornado.web.asynchronous
@tornado.gen.engine
def get(self, *args, **kwargs):
try:
yield tornado.gen.Task(runBackground, (self.asyncGet, tuple(args), dict(kwargs)))
except Exception as e:
yield tornado.gen.Task(self.captureException, exc_info=True)
finally:
if not self._finished:
self.finish()
@tornado.web.asynchronous
@tornado.gen.engine
def post(self, *args, **kwargs):
try:
yield tornado.gen.Task(runBackground, (self.asyncPost, tuple(args), dict(kwargs)))
except Exception as e:
yield tornado.gen.Task(self.captureException, exc_info=True)
finally:
if not self._finished:
self.finish()
def asyncGet(self, *args, **kwargs):
self.send_error(405)
self.finish()
def asyncPost(self, *args, **kwargs):
self.send_error(405)
self.finish()
def getRequestIP(self):
realIP = self.request.headers.get("X-Forwarded-For") if glob.cloudflare == True else self.request.headers.get("X-Real-IP")
if realIP != None:
return realIP
return self.request.remote_ip
def runBackground(data, callback):
"""
Run a function in the background.
Used to handle multiple requests at the same time
"""
func, args, kwargs = data
def _callback(result):
IOLoop.instance().add_callback(lambda: callback(result))
#glob.pool.apply_async(func, args, kwargs, _callback)
g = gevent.Greenlet(func, *args, **kwargs)
g.link(_callback)
g.start()
def checkArguments(arguments, requiredArguments):
"""
Check that every requiredArguments elements are in arguments
arguments -- full argument list, from tornado
requiredArguments -- required arguments list es: ["u", "ha"]
handler -- handler string name to print in exception. Optional
return -- True if all arguments are passed, none if not
"""
for i in requiredArguments:
if i not in arguments:
return False
return True
def printArguments(t):
"""
Print passed arguments, for debug purposes
t -- tornado object (self)
"""
print("ARGS::")
for i in t.request.arguments:
print ("{}={}".format(i, t.get_argument(i)))

54
pep.py
View File

@ -3,11 +3,17 @@ import sys
import os import os
import threading import threading
# Bottle # Tornado
import bottle import tornado.ioloop
import tornado.web
import tornado.httpserver
import tornado.gen
from gevent import monkey as brit_monkey from gevent import monkey as brit_monkey
brit_monkey.patch_all() brit_monkey.patch_all()
# Raven
from raven.contrib.tornado import AsyncSentryClient
# pep.py files # pep.py files
from constants import bcolors from constants import bcolors
from helpers import configHelper from helpers import configHelper
@ -20,13 +26,27 @@ from helpers import databaseHelperNew
from helpers import generalFunctions from helpers import generalFunctions
from helpers import logHelper as log from helpers import logHelper as log
# Raven from handlers import mainHandler
from raven import Client from handlers import apiIsOnlineHandler
from raven.contrib.bottle import Sentry from handlers import apiOnlineUsersHandler
from handlers import apiServerStatusHandler
from handlers import ciTriggerHandler
from handlers import apiVerifiedStatusHandler
from handlers import apiFokabotMessageHandler
# IRC
from irc import ircserver from irc import ircserver
def make_app():
return tornado.web.Application([
(r"/", mainHandler.handler),
(r"/api/v1/isOnline", apiIsOnlineHandler.handler),
(r"/api/v1/onlineUsers", apiOnlineUsersHandler.handler),
(r"/api/v1/serverStatus", apiServerStatusHandler.handler),
(r"/api/v1/ciTrigger", ciTriggerHandler.handler),
(r"/api/v1/verifiedStatus", apiVerifiedStatusHandler.handler),
(r"/api/v1/fokabotMessage", apiFokabotMessageHandler.handler)
])
if __name__ == "__main__": if __name__ == "__main__":
# Server start # Server start
consoleHelper.printServerStartHeader(True) consoleHelper.printServerStartHeader(True)
@ -140,22 +160,13 @@ if __name__ == "__main__":
consoleHelper.printColored("[!] Warning! Server running in debug mode!", bcolors.YELLOW) consoleHelper.printColored("[!] Warning! Server running in debug mode!", bcolors.YELLOW)
# Make app # Make app
app = bottle.app() application = make_app()
app.catchall = False
from handlers import mainHandler
from handlers import apiIsOnlineHandler
from handlers import apiOnlineUsersHandler
from handlers import apiServerStatusHandler
from handlers import ciTriggerHandler
from handlers import apiVerifiedStatusHandler
from handlers import apiFokabotMessageHandler
# Set up sentry # Set up sentry
try: try:
glob.sentry = generalFunctions.stringToBool(glob.conf.config["sentry"]["enable"]) glob.sentry = generalFunctions.stringToBool(glob.conf.config["sentry"]["enable"])
if glob.sentry == True: if glob.sentry == True:
client = Client(glob.conf.config["sentry"]["banchodns"], release=glob.VERSION) application.sentry_client = AsyncSentryClient(glob.conf.config["sentry"]["banchodns"], release=glob.VERSION)
app = Sentry(app, client)
else: else:
consoleHelper.printColored("[!] Warning! Sentry logging is disabled!", bcolors.YELLOW) consoleHelper.printColored("[!] Warning! Sentry logging is disabled!", bcolors.YELLOW)
except: except:
@ -185,8 +196,9 @@ if __name__ == "__main__":
consoleHelper.printColored("[!] Invalid server port! Please check your config.ini and run the server again", bcolors.RED) consoleHelper.printColored("[!] Invalid server port! Please check your config.ini and run the server again", bcolors.RED)
# Server start message and console output # Server start message and console output
log.logMessage("Server started!", discord=True, stdout=False) log.logMessage("Server started!", discord=True, of="info.txt", stdout=False)
consoleHelper.printColored("> Bottle listening for HTTP(s) clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN) consoleHelper.printColored("> Tornado listening for HTTP(s) clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN)
# Start bottle # Start tornado
bottle.run(app=app, host="0.0.0.0", port=serverPort, server="gevent") application.listen(serverPort)
tornado.ioloop.IOLoop.instance().start()