diff --git a/constants/fokabotCommands.py b/constants/fokabotCommands.py index 5dfe3c6..8e6c911 100644 --- a/constants/fokabotCommands.py +++ b/constants/fokabotCommands.py @@ -481,7 +481,7 @@ def tillerinoLast(fro, chan, message): FROM scores LEFT JOIN beatmaps ON beatmaps.beatmap_md5=scores.beatmap_md5 LEFT JOIN users ON users.id = scores.userid - WHERE users.username = ? + WHERE users.username = %s ORDER BY scores.time DESC LIMIT 1""", [fro]) if data == None: diff --git a/events/loginEvent.py b/events/loginEvent.py index 6069526..a3bb3b6 100644 --- a/events/loginEvent.py +++ b/events/loginEvent.py @@ -11,23 +11,22 @@ from helpers import generalFunctions from events import channelJoinEvent import sys import traceback +from helpers import requestHelper -def handle(flaskRequest): +def handle(tornadoRequest): # Data to return responseTokenString = "ayy" responseData = bytes() # Get IP from flask request - requestIP = flaskRequest.headers.get('X-Real-IP') - if requestIP == None: - requestIP = flaskRequest.remote_addr + requestIP = tornadoRequest.getRequestIP() # Console output print("> Accepting connection from {}...".format(requestIP)) # 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 - loginData = str(flaskRequest.data)[2:-3].split("\\n") + loginData = str(tornadoRequest.request.body)[2:-3].split("\\n") # Process login print("> Processing login request for {}...".format(loginData[0])) diff --git a/handlers/mainHandler.py b/handlers/mainHandler.py new file mode 100644 index 0000000..197b62f --- /dev/null +++ b/handlers/mainHandler.py @@ -0,0 +1,215 @@ +import datetime +import gzip +from helpers import requestHelper +from objects import glob +from helpers import consoleHelper +from constants import bcolors +from constants import exceptions +from constants import packetIDs +from helpers import packetHelper +from constants import serverPackets +from events import sendPublicMessageEvent +from events import sendPrivateMessageEvent +from events import channelJoinEvent +from events import channelPartEvent +from events import changeActionEvent +from events import cantSpectateEvent +from events import startSpectatingEvent +from events import stopSpectatingEvent +from events import spectateFramesEvent +from events import friendAddEvent +from events import friendRemoveEvent +from events import logoutEvent +from events import loginEvent +from events import setAwayMessageEvent +from events import joinLobbyEvent +from events import createMatchEvent +from events import partLobbyEvent +from events import changeSlotEvent +from events import joinMatchEvent +from events import partMatchEvent +from events import changeMatchSettingsEvent +from events import changeMatchPasswordEvent +from events import changeMatchModsEvent +from events import matchReadyEvent +from events import matchLockEvent +from events import matchStartEvent +from events import matchPlayerLoadEvent +from events import matchSkipEvent +from events import matchFramesEvent +from events import matchCompleteEvent +from events import matchNoBeatmapEvent +from events import matchHasBeatmapEvent +from events import matchTransferHostEvent +from events import matchFailedEvent +from events import matchInviteEvent +from events import matchChangeTeamEvent +from helpers import discordBotHelper +import sys +import traceback + +class handler(requestHelper.asyncRequestHandler): + def asyncPost(self): + try: + # Track time if needed + if glob.requestTime == True: + # Start time + st = datetime.datetime.now() + + # Client's token string and request data + requestTokenString = self.request.headers.get('osu-token') + requestData = self.request.body + + # Server's token string and request data + responseTokenString = "ayy" + responseData = bytes() + + if requestTokenString == None: + # No token, first request. Handle login. + responseTokenString, responseData = loginEvent.handle(self) + else: + try: + # This is not the first packet, send response based on client's request + # Packet start position, used to read stacked packets + pos = 0 + + # Make sure the token exists + if requestTokenString not in glob.tokens.tokens: + raise exceptions.tokenNotFoundException() + + # Token exists, get its object + userToken = glob.tokens.tokens[requestTokenString] + + # Keep reading packets until everything has been read + while pos < len(requestData): + # Get packet from stack starting from new packet + leftData = requestData[pos:] + + # Get packet ID, data length and data + packetID = packetHelper.readPacketID(leftData) + dataLength = packetHelper.readPacketLength(leftData) + packetData = requestData[pos:(pos+dataLength+7)] + + # Console output if needed + if glob.conf.config["server"]["outputpackets"] == True and packetID != 4: + consoleHelper.printColored("Incoming packet ({})({}):".format(requestTokenString, userToken.username), bcolors.GREEN) + consoleHelper.printColored("Packet code: {}\nPacket length: {}\nSingle packet data: {}\n".format(str(packetID), str(dataLength), str(packetData)), bcolors.YELLOW) + + # Event handler + def handleEvent(ev): + def wrapper(): + ev.handle(userToken, packetData) + return wrapper + + eventHandler = { + # TODO: Rename packets and events + # TODO: Host check for multi + packetIDs.client_sendPublicMessage: handleEvent(sendPublicMessageEvent), + packetIDs.client_sendPrivateMessage: handleEvent(sendPrivateMessageEvent), + packetIDs.client_setAwayMessage: handleEvent(setAwayMessageEvent), + packetIDs.client_channelJoin: handleEvent(channelJoinEvent), + packetIDs.client_channelPart: handleEvent(channelPartEvent), + packetIDs.client_changeAction: handleEvent(changeActionEvent), + packetIDs.client_startSpectating: handleEvent(startSpectatingEvent), + packetIDs.client_stopSpectating: handleEvent(stopSpectatingEvent), + packetIDs.client_cantSpectate: handleEvent(cantSpectateEvent), + packetIDs.client_spectateFrames: handleEvent(spectateFramesEvent), + packetIDs.client_friendAdd: handleEvent(friendAddEvent), + packetIDs.client_friendRemove: handleEvent(friendRemoveEvent), + packetIDs.client_logout: handleEvent(logoutEvent), + packetIDs.client_joinLobby: handleEvent(joinLobbyEvent), + packetIDs.client_partLobby: handleEvent(partLobbyEvent), + 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_invite: handleEvent(matchInviteEvent), + packetIDs.client_matchChangeTeam: handleEvent(matchChangeTeamEvent) + } + + if packetID != 4: + if packetID in eventHandler: + eventHandler[packetID]() + else: + consoleHelper.printColored("[!] Unknown packet id from {} ({})".format(requestTokenString, packetID), bcolors.RED) + + # Update pos so we can read the next stacked packet + # +7 because we add packet ID bytes, unused byte and data length bytes + pos += dataLength+7 + + # Token queue built, send it + responseTokenString = userToken.token + responseData = userToken.queue + userToken.resetQueue() + + # Update ping time for timeout + userToken.updatePingTime() + except exceptions.tokenNotFoundException: + # Token not found. Disconnect that user + responseData = serverPackets.loginError() + responseData += serverPackets.notification("Whoops! Something went wrong, please login again.") + consoleHelper.printColored("[!] Received packet from unknown token ({}).".format(requestTokenString), bcolors.RED) + consoleHelper.printColored("> {} have been disconnected (invalid token)".format(requestTokenString), bcolors.YELLOW) + + if glob.requestTime == True: + # End time + et = datetime.datetime.now() + + # Total time: + tt = float((et.microsecond-st.microsecond)/1000) + consoleHelper.printColored("Request time: {}ms".format(tt), bcolors.PINK) + + # Send server's response to client + # We don't use token object because we might not have a token (failed login) + self.add_header("cho-token", responseTokenString) + self.add_header("cho-protocol", "19") + self.add_header("Keep-Alive", "timeout=5, max=100") + self.add_header("Connection", "keep-alive") + self.add_header("Content-Type", "text/html; charset=UTF-8") + self.add_header("Vary", "Accept-Encoding") + self.add_header("Content-Encoding", "gzip") + self.write(gzip.compress(responseData, 6)) + self.finish() + except: + msg = "**asyncppytornadovroom error** *(aka test server, ignore this)*\nUnhandled exception in mainHandler:\n```\n{}\n{}\n```".format(sys.exc_info(), traceback.format_exc()) + consoleHelper.printColored("[!] {}".format(msg), bcolors.RED) + discordBotHelper.sendConfidential(msg) + + def asyncGet(self): + html = "MA MAURO ESISTE?
"
+		html += "           _                 __
" + html += " (_) / /
" + html += " ______ __ ____ ____ / /____
" + html += " / ___/ / _ \\/ _ \\/ / _ \\
" + html += " / / / / /_) / /_) / / ____/
" + html += "/__/ /__/ .___/ .___/__/ \\_____/
" + html += " / / / /
" + html += " /__/ /__/
" + html += "PYTHON > ALL VERSION

" + html += "
" + html += " .. o .
" + html += " o.o o . o
" + html += " oo...
" + html += " __[]__
" + html += " phwr--> _\\:D/_/o_o_o_|__ u wot m8
" + html += " \\\"\"\"\"\"\"\"\"\"\"\"\"\"\"/
" + html += " \\ . .. .. . /
" + html += "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
" + html += "

reverse engineering a protocol impossible to reverse engineer since always
we are actually reverse engineering bancho successfully. for the third time.
" + self.write(html) + self.finish() diff --git a/helpers/configHelper.py b/helpers/configHelper.py index e355704..fba87e1 100644 --- a/helpers/configHelper.py +++ b/helpers/configHelper.py @@ -46,10 +46,9 @@ class config: self.config.get("db","username") self.config.get("db","password") self.config.get("db","database") - self.config.get("db","pingtime") + self.config.get("db","workers") - self.config.get("server","server") - self.config.get("server","host") + self.config.get("server","threads") self.config.get("server","port") self.config.get("server","localizeusers") self.config.get("server","outputpackets") @@ -57,12 +56,6 @@ class config: self.config.get("server","timeouttime") self.config.get("server","timeoutlooptime") - if self.config["server"]["server"] == "flask": - # Flask only config - self.config.get("flask","threaded") - self.config.get("flask","debug") - self.config.get("flask","logger") - self.config.get("discord","enable") self.config.get("discord","boturl") @@ -85,11 +78,10 @@ class config: self.config.set("db", "username", "root") self.config.set("db", "password", "") self.config.set("db", "database", "ripple") - self.config.set("db", "pingtime", "600") + self.config.set("db", "workers", "4") self.config.add_section("server") - self.config.set("server", "server", "tornado") - self.config.set("server", "host", "0.0.0.0") + self.config.set("server", "threads", "16") self.config.set("server", "port", "5001") self.config.set("server", "localizeusers", "1") self.config.set("server", "outputpackets", "0") @@ -97,18 +89,13 @@ class config: self.config.set("server", "timeoutlooptime", "100") self.config.set("server", "timeouttime", "100") - self.config.add_section("flask") - self.config.set("flask", "threaded", "1") - self.config.set("flask", "debug", "0") - self.config.set("flask", "logger", "0") - self.config.add_section("ci") self.config.set("ci", "key", "changeme") self.config.add_section("discord") self.config.set("discord", "enable", "False") self.config.set("discord", "boturl", "") - + # Write ini to file and close self.config.write(f) f.close() diff --git a/helpers/databaseHelperNew.py b/helpers/databaseHelperNew.py new file mode 100644 index 0000000..4c794e4 --- /dev/null +++ b/helpers/databaseHelperNew.py @@ -0,0 +1,118 @@ +import MySQLdb +import threading + +class mysqlWorker: + """ + Instance of a pettirosso meme + """ + def __init__(self, wid, host, username, password, database): + """ + Create a pettirosso meme (mysql worker) + + wid -- worker id + host -- hostname + username -- MySQL username + password -- MySQL password + database -- MySQL database name + """ + self.wid = wid + self.connection = MySQLdb.connect(host, username, password, database) + self.connection.autocommit(True) + self.ready = True + self.lock = threading.Lock() + +class db: + """ + A MySQL db connection with multiple workers + """ + + def __init__(self, host, username, password, database, workers): + """ + Create MySQL workers aka pettirossi meme + + host -- hostname + username -- MySQL username + password -- MySQL password + database -- MySQL database name + workers -- Number of workers to spawn + """ + #self.lock = threading.Lock() + #self.connection = MySQLdb.connect(host, username, password, database) + + self.workers = [] + self.lastWorker = 0 + self.workersNumber = workers + for i in range(0,self.workersNumber): + print("> Spawning MySQL pettirosso meme {}".format(i)) + self.workers.append(mysqlWorker(i, host, username, password, database)) + + def getWorker(self): + """ + Return a worker object (round-robin way) + + return -- worker object + """ + if self.lastWorker >= self.workersNumber-1: + self.lastWorker = 0 + else: + self.lastWorker += 1 + #print("Using worker {}".format(self.lastWorker)) + return self.workers[self.lastWorker] + + def execute(self, query, params = ()): + """ + Executes a query + + query -- Query to execute. You can bind parameters with %s + params -- Parameters list. First element replaces first %s and so on. Optional. + """ + # Get a worker and acquire its lock + worker = self.getWorker() + worker.lock.acquire() + + try: + # Create cursor, execute query and commit + cursor = worker.connection.cursor(MySQLdb.cursors.DictCursor) + cursor.execute(query, params) + return cursor.lastrowid + finally: + # Close the cursor and release worker's lock + if cursor: + cursor.close() + worker.lock.release() + + def fetch(self, query, params = (), all = False): + """ + Fetch a single value from db that matches given query + + query -- Query to execute. You can bind parameters with %s + params -- Parameters list. First element replaces first %s and so on. Optional. + all -- Fetch one or all values. Used internally. Use fetchAll if you want to fetch all values. + """ + # Get a worker and acquire its lock + worker = self.getWorker() + worker.lock.acquire() + + try: + # Create cursor, execute the query and fetch one/all result(s) + cursor = worker.connection.cursor(MySQLdb.cursors.DictCursor) + cursor.execute(query, params) + if all == True: + return cursor.fetchall() + else: + return cursor.fetchone() + finally: + # Close the cursor and release worker's lock + if cursor: + cursor.close() + worker.lock.release() + + def fetchAll(self, query, params = ()): + """ + Fetch all values from db that matche given query. + Calls self.fetch with all = True. + + query -- Query to execute. You can bind parameters with %s + params -- Parameters list. First element replaces first %s and so on. Optional. + """ + return self.fetch(query, params, True) diff --git a/helpers/requestHelper.py b/helpers/requestHelper.py new file mode 100644 index 0000000..436030b --- /dev/null +++ b/helpers/requestHelper.py @@ -0,0 +1,72 @@ +import tornado +import tornado.web +import tornado.gen +from tornado.ioloop import IOLoop +from objects import glob + +class asyncRequestHandler(tornado.web.RequestHandler): + """ + Tornado asynchronous request handler + create a class that extends this one (requestHelper.asyncRequestHandler) + 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): + yield tornado.gen.Task(runBackground, (self.asyncGet, tuple(args), dict(kwargs))) + + @tornado.web.asynchronous + @tornado.gen.engine + def post(self, *args, **kwargs): + yield tornado.gen.Task(runBackground, (self.asyncPost, tuple(args), dict(kwargs))) + + 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-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) + + +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))) diff --git a/helpers/responseHelper.py b/helpers/responseHelper.py deleted file mode 100644 index a2fbd8f..0000000 --- a/helpers/responseHelper.py +++ /dev/null @@ -1,47 +0,0 @@ -import flask -import gzip - -def generateResponse(token, data = None): - """ - Return a flask response with required headers for osu! client, token and gzip compressed data - - token -- user token - data -- plain response body - return -- flask response - """ - - resp = flask.Response(gzip.compress(data, 6)) - resp.headers['cho-token'] = token - resp.headers['cho-protocol'] = '19' - resp.headers['Keep-Alive'] = 'timeout=5, max=100' - resp.headers['Connection'] = 'keep-alive' - resp.headers['Content-Type'] = 'text/html; charset=UTF-8' - resp.headers['Vary'] = 'Accept-Encoding' - resp.headers['Content-Encoding'] = 'gzip' - return resp - - -def HTMLResponse(): - """Return HTML bancho meme response""" - - html = "MA MAURO ESISTE?
"
-	html += "           _                 __
" - html += " (_) / /
" - html += " ______ __ ____ ____ / /____
" - html += " / ___/ / _ \\/ _ \\/ / _ \\
" - html += " / / / / /_) / /_) / / ____/
" - html += "/__/ /__/ .___/ .___/__/ \\_____/
" - html += " / / / /
" - html += " /__/ /__/
" - html += "PYTHON > ALL VERSION

" - html += "
" - html += " .. o .
" - html += " o.o o . o
" - html += " oo...
" - html += " __[]__
" - html += " phwr--> _\\:D/_/o_o_o_|__ u wot m8
" - html += " \\\"\"\"\"\"\"\"\"\"\"\"\"\"\"/
" - html += " \\ . .. .. . /
" - html += "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
" - html += "

reverse engineering a protocol impossible to reverse engineer since always
we are actually reverse engineering bancho successfully. for the third time.
" - return html diff --git a/helpers/userHelper.py b/helpers/userHelper.py index e48ea38..4abdcf4 100644 --- a/helpers/userHelper.py +++ b/helpers/userHelper.py @@ -13,7 +13,7 @@ def getID(username): """ # Get user ID from db - userID = glob.db.fetch("SELECT id FROM users WHERE username = ?", [username]) + userID = glob.db.fetch("SELECT id FROM users WHERE username = %s", [username]) # Make sure the query returned something if userID == None: @@ -34,7 +34,7 @@ def checkLogin(userID, password): """ # Get password data - passwordData = glob.db.fetch("SELECT password_md5, salt, password_version FROM users WHERE id = ?", [userID]) + passwordData = glob.db.fetch("SELECT password_md5, salt, password_version FROM users WHERE id = %s", [userID]) # Make sure the query returned something if passwordData == None: @@ -48,7 +48,7 @@ def checkLogin(userID, password): ok = passwordHelper.checkOldPassword(password, passwordData["salt"], passwordData["password_md5"]) if not ok: return False newpass = passwordHelper.genBcrypt(password) - glob.db.execute("UPDATE users SET password_md5=?, salt='', password_version='2' WHERE id = ?", [newpass, userID]) + glob.db.execute("UPDATE users SET password_md5=%s, salt='', password_version='2' WHERE id = %s", [newpass, userID]) def exists(userID): @@ -59,7 +59,7 @@ def exists(userID): return -- bool """ - result = glob.db.fetch("SELECT id FROM users WHERE id = ?", [userID]) + result = glob.db.fetch("SELECT id FROM users WHERE id = %s", [userID]) if result == None: return False else: @@ -74,7 +74,7 @@ def getAllowed(userID): return -- allowed int """ - return glob.db.fetch("SELECT allowed FROM users WHERE id = ?", [userID])["allowed"] + return glob.db.fetch("SELECT allowed FROM users WHERE id = %s", [userID])["allowed"] def getRankPrivileges(userID): @@ -83,7 +83,7 @@ def getRankPrivileges(userID): If you want to get that rank, user getUserGameRank instead """ - return glob.db.fetch("SELECT rank FROM users WHERE id = ?", [userID])["rank"] + return glob.db.fetch("SELECT rank FROM users WHERE id = %s", [userID])["rank"] def getSilenceEnd(userID): @@ -95,7 +95,7 @@ def getSilenceEnd(userID): return -- UNIX time """ - return glob.db.fetch("SELECT silence_end FROM users WHERE id = ?", [userID])["silence_end"] + return glob.db.fetch("SELECT silence_end FROM users WHERE id = %s", [userID])["silence_end"] def silence(userID, silenceEndTime, silenceReason): @@ -108,7 +108,7 @@ def silence(userID, silenceEndTime, silenceReason): silenceReason -- Silence reason shown on website """ - glob.db.execute("UPDATE users SET silence_end = ?, silence_reason = ? WHERE id = ?", [silenceEndTime, silenceReason, userID]) + glob.db.execute("UPDATE users SET silence_end = %s, silence_reason = %s WHERE id = %s", [silenceEndTime, silenceReason, userID]) def getRankedScore(userID, gameMode): """ @@ -120,7 +120,7 @@ def getRankedScore(userID, gameMode): """ modeForDB = gameModes.getGameModeForDB(gameMode) - return glob.db.fetch("SELECT ranked_score_"+modeForDB+" FROM users_stats WHERE id = ?", [userID])["ranked_score_"+modeForDB] + return glob.db.fetch("SELECT ranked_score_"+modeForDB+" FROM users_stats WHERE id = %s", [userID])["ranked_score_"+modeForDB] def getTotalScore(userID, gameMode): @@ -133,7 +133,7 @@ def getTotalScore(userID, gameMode): """ modeForDB = gameModes.getGameModeForDB(gameMode) - return glob.db.fetch("SELECT total_score_"+modeForDB+" FROM users_stats WHERE id = ?", [userID])["total_score_"+modeForDB] + return glob.db.fetch("SELECT total_score_"+modeForDB+" FROM users_stats WHERE id = %s", [userID])["total_score_"+modeForDB] def getAccuracy(userID, gameMode): @@ -146,7 +146,7 @@ def getAccuracy(userID, gameMode): """ modeForDB = gameModes.getGameModeForDB(gameMode) - return glob.db.fetch("SELECT avg_accuracy_"+modeForDB+" FROM users_stats WHERE id = ?", [userID])["avg_accuracy_"+modeForDB] + return glob.db.fetch("SELECT avg_accuracy_"+modeForDB+" FROM users_stats WHERE id = %s", [userID])["avg_accuracy_"+modeForDB] def getGameRank(userID, gameMode): @@ -159,7 +159,7 @@ def getGameRank(userID, gameMode): """ modeForDB = gameModes.getGameModeForDB(gameMode) - result = glob.db.fetch("SELECT position FROM leaderboard_"+modeForDB+" WHERE user = ?", [userID]) + result = glob.db.fetch("SELECT position FROM leaderboard_"+modeForDB+" WHERE user = %s", [userID]) if result == None: return 0 else: @@ -176,7 +176,7 @@ def getPlaycount(userID, gameMode): """ modeForDB = gameModes.getGameModeForDB(gameMode) - return glob.db.fetch("SELECT playcount_"+modeForDB+" FROM users_stats WHERE id = ?", [userID])["playcount_"+modeForDB] + return glob.db.fetch("SELECT playcount_"+modeForDB+" FROM users_stats WHERE id = %s", [userID])["playcount_"+modeForDB] def getUsername(userID): @@ -187,7 +187,7 @@ def getUsername(userID): return -- username """ - return glob.db.fetch("SELECT username FROM users WHERE id = ?", [userID])["username"] + return glob.db.fetch("SELECT username FROM users WHERE id = %s", [userID])["username"] def getFriendList(userID): @@ -199,7 +199,7 @@ def getFriendList(userID): """ # Get friends from db - friends = glob.db.fetchAll("SELECT user2 FROM users_relationships WHERE user1 = ?", [userID]) + friends = glob.db.fetchAll("SELECT user2 FROM users_relationships WHERE user1 = %s", [userID]) if friends == None or len(friends) == 0: # We have no friends, return 0 list @@ -225,11 +225,11 @@ def addFriend(userID, friendID): return # check user isn't already a friend of ours - if glob.db.fetch("SELECT id FROM users_relationships WHERE user1 = ? AND user2 = ?", [userID, friendID]) != None: + if glob.db.fetch("SELECT id FROM users_relationships WHERE user1 = %s AND user2 = %s", [userID, friendID]) != None: return # Set new value - glob.db.execute("INSERT INTO users_relationships (user1, user2) VALUES (?, ?)", [userID, friendID]) + glob.db.execute("INSERT INTO users_relationships (user1, user2) VALUES (%s, %s)", [userID, friendID]) def removeFriend(userID, friendID): @@ -242,7 +242,7 @@ def removeFriend(userID, friendID): # Delete user relationship. We don't need to check if the relationship was there, because who gives a shit, # if they were not friends and they don't want to be anymore, be it. ¯\_(ツ)_/¯ - glob.db.execute("DELETE FROM users_relationships WHERE user1 = ? AND user2 = ?", [userID, friendID]) + glob.db.execute("DELETE FROM users_relationships WHERE user1 = %s AND user2 = %s", [userID, friendID]) def getCountry(userID): @@ -255,7 +255,7 @@ def getCountry(userID): return -- country code (two letters) """ - return glob.db.fetch("SELECT country FROM users_stats WHERE id = ?", [userID])["country"] + return glob.db.fetch("SELECT country FROM users_stats WHERE id = %s", [userID])["country"] def getPP(userID, gameMode): """ @@ -266,7 +266,7 @@ def getPP(userID, gameMode): """ modeForDB = gameModes.getGameModeForDB(gameMode) - return glob.db.fetch("SELECT pp_{} FROM users_stats WHERE id = ?".format(modeForDB), [userID])["pp_{}".format(modeForDB)] + return glob.db.fetch("SELECT pp_{} FROM users_stats WHERE id = %s".format(modeForDB), [userID])["pp_{}".format(modeForDB)] def setAllowed(userID, allowed): """ @@ -275,7 +275,7 @@ def setAllowed(userID, allowed): userID -- user allowed -- allowed status. 1: normal, 0: banned """ - glob.db.execute("UPDATE users SET allowed = ? WHERE id = ?", [allowed, userID]) + glob.db.execute("UPDATE users SET allowed = %s WHERE id = %s", [allowed, userID]) def setCountry(userID, country): """ @@ -284,7 +284,7 @@ def setCountry(userID, country): userID -- userID country -- country letters """ - glob.db.execute("UPDATE users_stats SET country = ? WHERE id = ?", [country, userID]) + glob.db.execute("UPDATE users_stats SET country = %s WHERE id = %s", [country, userID]) def getShowCountry(userID): """ @@ -293,7 +293,7 @@ def getShowCountry(userID): userID -- userID return -- True if country is shown, False if it's hidden """ - country = glob.db.fetch("SELECT show_country FROM users_stats WHERE id = ?", [userID]) + country = glob.db.fetch("SELECT show_country FROM users_stats WHERE id = %s", [userID]) if country == None: return False return generalFunctions.stringToBool(country) @@ -303,5 +303,5 @@ def IPLog(userID, ip): Botnet the user (log his ip for multiaccount detection) """ - glob.db.execute("""INSERT INTO ip_user (userid, ip, occurencies) VALUES (?, ?, '1') + glob.db.execute("""INSERT INTO ip_user (userid, ip, occurencies) VALUES (%s, %s, '1') ON DUPLICATE KEY UPDATE occurencies = occurencies + 1""", [userID, ip]) diff --git a/objects/banchoConfig.py b/objects/banchoConfig.py index 36e1fea..4294b59 100644 --- a/objects/banchoConfig.py +++ b/objects/banchoConfig.py @@ -39,4 +39,4 @@ class banchoConfig: """ self.config["banchoMaintenance"] = __maintenance - glob.db.execute("UPDATE bancho_settings SET value_int = ? WHERE name = 'bancho_maintenance'", [int(__maintenance)]) + glob.db.execute("UPDATE bancho_settings SET value_int = %s WHERE name = 'bancho_maintenance'", [int(__maintenance)]) diff --git a/objects/glob.py b/objects/glob.py index 8467823..17fc34d 100644 --- a/objects/glob.py +++ b/objects/glob.py @@ -14,3 +14,5 @@ channels = channelList.channelList() matches = matchList.matchList() memes = True restarting = False +pool = None +requestTime = False diff --git a/pep.py b/pep.py index fb9165e..caf1992 100644 --- a/pep.py +++ b/pep.py @@ -1,81 +1,33 @@ """Hello, pep.py here, ex-owner of ripple and prime minister of Ripwot.""" -import logging import sys -import flask -import datetime import os +from multiprocessing.pool import ThreadPool -# Tornado server -from tornado.wsgi import WSGIContainer -from tornado.httpserver import HTTPServer -from tornado.ioloop import IOLoop +# Tornado +import tornado.ioloop +import tornado.web +import tornado.httpserver +import tornado.gen # pep.py files from constants import bcolors -from constants import packetIDs -from constants import serverPackets from helpers import configHelper from helpers import discordBotHelper -from constants import exceptions from objects import glob from objects import fokabot from objects import banchoConfig - -from events import sendPublicMessageEvent -from events import sendPrivateMessageEvent -from events import channelJoinEvent -from events import channelPartEvent -from events import changeActionEvent -from events import cantSpectateEvent -from events import startSpectatingEvent -from events import stopSpectatingEvent -from events import spectateFramesEvent -from events import friendAddEvent -from events import friendRemoveEvent -from events import logoutEvent -from events import loginEvent -from events import setAwayMessageEvent -from events import joinLobbyEvent -from events import createMatchEvent -from events import partLobbyEvent -from events import changeSlotEvent -from events import joinMatchEvent -from events import partMatchEvent -from events import changeMatchSettingsEvent -from events import changeMatchPasswordEvent -from events import changeMatchModsEvent -from events import matchReadyEvent -from events import matchLockEvent -from events import matchStartEvent -from events import matchPlayerLoadEvent -from events import matchSkipEvent -from events import matchFramesEvent -from events import matchCompleteEvent -from events import matchNoBeatmapEvent -from events import matchHasBeatmapEvent -from events import matchTransferHostEvent -from events import matchFailedEvent -from events import matchInviteEvent -from events import matchChangeTeamEvent - -# pep.py helpers -from helpers import packetHelper +from handlers import mainHandler from helpers import consoleHelper -from helpers import databaseHelper -from helpers import responseHelper +from helpers import databaseHelperNew from helpers import generalFunctions -from helpers import systemHelper -from flask import request - -# Create flask instance -app = flask.Flask(__name__) - -# Get flask logger -flaskLogger = logging.getLogger("werkzeug") +def make_app(): + return tornado.web.Application([ + (r"/", mainHandler.handler) + ]) # Ci trigger -@app.route("/ci-trigger") +'''@app.route("/ci-trigger") @app.route("/api/ci-trigger") def ciTrigger(): # Ci restart trigger @@ -133,135 +85,10 @@ def apiOnlineUsers(): def banchoServer(): if flask.request.method == 'POST': - # Track time if needed - if serverOutputRequestTime == True: - # Start time - st = datetime.datetime.now() - # Client's token string and request data - requestTokenString = flask.request.headers.get('osu-token') - requestData = flask.request.data - - # Server's token string and request data - responseTokenString = "ayy" - responseData = bytes() - - if requestTokenString == None: - # No token, first request. Handle login. - responseTokenString, responseData = loginEvent.handle(flask.request) - else: - try: - # This is not the first packet, send response based on client's request - # Packet start position, used to read stacked packets - pos = 0 - - # Make sure the token exists - if requestTokenString not in glob.tokens.tokens: - raise exceptions.tokenNotFoundException() - - # Token exists, get its object - userToken = glob.tokens.tokens[requestTokenString] - - # Keep reading packets until everything has been read - while pos < len(requestData): - # Get packet from stack starting from new packet - leftData = requestData[pos:] - - # Get packet ID, data length and data - packetID = packetHelper.readPacketID(leftData) - dataLength = packetHelper.readPacketLength(leftData) - packetData = requestData[pos:(pos+dataLength+7)] - - # Console output if needed - if serverOutputPackets == True and packetID != 4: - consoleHelper.printColored("Incoming packet ({})({}):".format(requestTokenString, userToken.username), bcolors.GREEN) - consoleHelper.printColored("Packet code: {}\nPacket length: {}\nSingle packet data: {}\n".format(str(packetID), str(dataLength), str(packetData)), bcolors.YELLOW) - - # Event handler - def handleEvent(ev): - def wrapper(): - ev.handle(userToken, packetData) - return wrapper - - eventHandler = { - # TODO: Rename packets and events - # TODO: Host check for multi - packetIDs.client_sendPublicMessage: handleEvent(sendPublicMessageEvent), - packetIDs.client_sendPrivateMessage: handleEvent(sendPrivateMessageEvent), - packetIDs.client_setAwayMessage: handleEvent(setAwayMessageEvent), - packetIDs.client_channelJoin: handleEvent(channelJoinEvent), - packetIDs.client_channelPart: handleEvent(channelPartEvent), - packetIDs.client_changeAction: handleEvent(changeActionEvent), - packetIDs.client_startSpectating: handleEvent(startSpectatingEvent), - packetIDs.client_stopSpectating: handleEvent(stopSpectatingEvent), - packetIDs.client_cantSpectate: handleEvent(cantSpectateEvent), - packetIDs.client_spectateFrames: handleEvent(spectateFramesEvent), - packetIDs.client_friendAdd: handleEvent(friendAddEvent), - packetIDs.client_friendRemove: handleEvent(friendRemoveEvent), - packetIDs.client_logout: handleEvent(logoutEvent), - packetIDs.client_joinLobby: handleEvent(joinLobbyEvent), - packetIDs.client_partLobby: handleEvent(partLobbyEvent), - 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_invite: handleEvent(matchInviteEvent), - packetIDs.client_matchChangeTeam: handleEvent(matchChangeTeamEvent) - } - - if packetID != 4: - if packetID in eventHandler: - eventHandler[packetID]() - else: - consoleHelper.printColored("[!] Unknown packet id from {} ({})".format(requestTokenString, packetID), bcolors.RED) - - # Update pos so we can read the next stacked packet - # +7 because we add packet ID bytes, unused byte and data length bytes - pos += dataLength+7 - - # Token queue built, send it - responseTokenString = userToken.token - responseData = userToken.queue - userToken.resetQueue() - - # Update ping time for timeout - userToken.updatePingTime() - except exceptions.tokenNotFoundException: - # Token not found. Disconnect that user - responseData = serverPackets.loginError() - responseData += serverPackets.notification("Whoops! Something went wrong, please login again.") - consoleHelper.printColored("[!] Received packet from unknown token ({}).".format(requestTokenString), bcolors.RED) - consoleHelper.printColored("> {} have been disconnected (invalid token)".format(requestTokenString), bcolors.YELLOW) - - if serverOutputRequestTime == True: - # End time - et = datetime.datetime.now() - - # Total time: - tt = float((et.microsecond-st.microsecond)/1000) - consoleHelper.printColored("Request time: {}ms".format(tt), bcolors.PINK) - - # Send server's response to client - # We don't use token object because we might not have a token (failed login) - return responseHelper.generateResponse(responseTokenString, responseData) else: # Not a POST request, send html page - return responseHelper.HTMLResponse() + return responseHelper.HTMLResponse()''' if __name__ == "__main__": @@ -291,8 +118,8 @@ if __name__ == "__main__": # Connect to db try: - consoleHelper.printNoNl("> Connecting to MySQL db... ") - glob.db = databaseHelper.db(glob.conf.config["db"]["host"], glob.conf.config["db"]["username"], glob.conf.config["db"]["password"], glob.conf.config["db"]["database"], int(glob.conf.config["db"]["pingtime"])) + print("> Connecting to MySQL db... ") + glob.db = databaseHelperNew.db(glob.conf.config["db"]["host"], glob.conf.config["db"]["username"], glob.conf.config["db"]["password"], glob.conf.config["db"]["database"], int(glob.conf.config["db"]["workers"])) consoleHelper.printDone() except: # Exception while connecting to db @@ -300,6 +127,15 @@ if __name__ == "__main__": consoleHelper.printColored("[!] Error while connection to database. Please check your config.ini and run the server again", bcolors.RED) raise + # Create threads pool + try: + consoleHelper.printNoNl("> Creating threads pool... ") + glob.pool = ThreadPool(int(glob.conf.config["server"]["threads"])) + consoleHelper.printDone() + except: + consoleHelper.printError() + consoleHelper.printColored("[!] Error while creating threads pool. Please check your config.ini and run the server again", bcolors.RED) + # Load bancho_settings try: consoleHelper.printNoNl("> Loading bancho settings from DB... ") @@ -344,41 +180,14 @@ if __name__ == "__main__": consoleHelper.printColored("[!] Warning! users localization is disabled!", bcolors.YELLOW) # Get server parameters from config.ini - serverName = glob.conf.config["server"]["server"] - serverHost = glob.conf.config["server"]["host"] serverPort = int(glob.conf.config["server"]["port"]) - serverOutputPackets = generalFunctions.stringToBool(glob.conf.config["server"]["outputpackets"]) - serverOutputRequestTime = generalFunctions.stringToBool(glob.conf.config["server"]["outputrequesttime"]) + glob.requestTime = generalFunctions.stringToBool(glob.conf.config["server"]["outputrequesttime"]) - # Send server start message + # Server start message and console output discordBotHelper.sendConfidential("w00t p00t! (pep.py started)") + consoleHelper.printColored("> Tornado listening for clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN) - # Run server sanic way - if serverName == "tornado": - # Tornado server - consoleHelper.printColored("> Tornado listening for clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN) - webServer = HTTPServer(WSGIContainer(app)) - webServer.listen(serverPort) - IOLoop.instance().start() - elif serverName == "flask": - # Flask server - # Get flask settings - flaskThreaded = generalFunctions.stringToBool(glob.conf.config["flask"]["threaded"]) - flaskDebug = generalFunctions.stringToBool(glob.conf.config["flask"]["debug"]) - flaskLoggerStatus = not generalFunctions.stringToBool(glob.conf.config["flask"]["logger"]) - - # Set flask debug mode and logger - app.debug = flaskDebug - flaskLogger.disabled = flaskLoggerStatus - - # Console output - if flaskDebug == False: - consoleHelper.printColored("> Flask listening for clients on {}.{}...".format(serverHost, serverPort), bcolors.GREEN) - else: - consoleHelper.printColored("> Flask "+bcolors.YELLOW+"(debug mode)"+bcolors.ENDC+" listening for clients on {}:{}...".format(serverHost, serverPort), bcolors.GREEN) - - # Run flask server - app.run(host=serverHost, port=serverPort, threaded=flaskThreaded) - else: - print(bcolors.RED+"[!] Unknown server. Please set the server key in config.ini to "+bcolors.ENDC+bcolors.YELLOW+"tornado"+bcolors.ENDC+bcolors.RED+" or "+bcolors.ENDC+bcolors.YELLOW+"flask"+bcolors.ENDC) - sys.exit() + # Start tornado + app = tornado.httpserver.HTTPServer(make_app()) + app.listen(serverPort) + tornado.ioloop.IOLoop.instance().start()