def asyncGet(self): try: args = {} try: # Check user auth because of sneaky people if not requestsManager.checkArguments(self.request.arguments, ["u", "h"]): raise exceptions.invalidArgumentsException(MODULE_NAME) username = self.get_argument("u") password = self.get_argument("h") ip = self.getRequestIP() userID = userUtils.getID(username) if not userUtils.checkLogin(userID, password): raise exceptions.loginFailedException( MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, username, ip) except ValueError: raise exceptions.invalidArgumentsException(MODULE_NAME) # Pass all arguments otherwise it doesn't work for key, _ in self.request.arguments.items(): args[key] = self.get_argument(key) response = requests.get("{}/web/osu-search-set.php?{}".format( glob.conf.config["beatmapserver"]["domain"], urlencode(args))) self.write(response.text) except Exception as e: log.error("search failed: {}".format(e)) self.write("")
def asyncGet(self): try: # Get request ip ip = self.getRequestIP() # Check arguments if not requestsManager.checkArguments(self.request.arguments, ["c", "u", "h"]): raise exceptions.invalidArgumentsException(MODULE_NAME) # Get arguments username = self.get_argument("u") password = self.get_argument("h") replayID = self.get_argument("c") # Login check userID = userUtils.getID(username) if userID == 0: raise exceptions.loginFailedException(MODULE_NAME, userID) if not userUtils.checkLogin(userID, password, ip): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, username, ip) # Get user ID replayData = glob.db.fetch( "SELECT scores.*, users.username AS uname FROM scores LEFT JOIN users ON scores.userid = users.id WHERE scores.id = %s", [replayID]) if replayData == None: replayData = glob.db.fetch( "SELECT scores_relax.*, users.username AS uname FROM scores_relax LEFT JOIN users ON scores_relax.userid = users.id WHERE scores_relax.id = %s", [replayID]) fileName = "{}_relax/replay_{}.osr".format( glob.conf.config["server"]["replayspath"], replayID) else: fileName = "{}/replay_{}.osr".format( glob.conf.config["server"]["replayspath"], replayID) # Increment 'replays watched by others' if needed if replayData is not None: if username != replayData["uname"]: userUtils.incrementReplaysWatched(replayData["userid"], replayData["play_mode"]) # Serve replay log.info("Serving replay_{}.osr".format(replayID)) if os.path.isfile(fileName): with open(fileName, "rb") as f: fileContent = f.read() self.write(fileContent) else: log.warning("Replay {} doesn't exist".format(replayID)) self.write("") except exceptions.invalidArgumentsException: pass except exceptions.need2FAException: pass except exceptions.loginFailedException: pass
def asyncPost(self): try: # Required arguments check if not requestsManager.checkArguments(self.request.arguments, ("u", "p", "a")): raise exceptions.invalidArgumentsException(MODULE_NAME) # Get arguments username = self.get_argument("u") password = self.get_argument("p") action = self.get_argument("a").strip().lower() # IP for session check ip = self.getRequestIP() # Login and ban check userID = userUtils.getID(username) if userID == 0: raise exceptions.loginFailedException(MODULE_NAME, userID) if not userUtils.checkLogin(userID, password, ip): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, userID, ip) if userUtils.isBanned(userID): raise exceptions.userBannedException(MODULE_NAME, username) # Action (depends on 'action' parameter, not on HTTP method) if action == "get": self.write(self._getComments()) elif action == "post": self._addComment() except (exceptions.loginFailedException, exceptions.need2FAException, exceptions.userBannedException): self.write("error: no")
def asyncPost(self): try: if glob.debug: requestsManager.printArguments(self) # Make sure screenshot file was passed if "ss" not in self.request.files: raise exceptions.invalidArgumentsException(MODULE_NAME) # Check user auth because of sneaky people if not requestsManager.checkArguments(self.request.arguments, ["u", "p"]): raise exceptions.invalidArgumentsException(MODULE_NAME) username = self.get_argument("u") password = self.get_argument("p") ip = self.getRequestIP() userID = userUtils.getID(username) if not userUtils.checkLogin(userID, password): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, username, ip) # Rate limit if glob.redis.get("lets:screenshot:{}".format(userID)) is not None: self.write("no") return glob.redis.set("lets:screenshot:{}".format(userID), 1, 60) # Get a random screenshot id found = False screenshotID = "" while not found: screenshotID = generalUtils.randomString(8) if not os.path.isfile( ".data/screenshots/{}.png".format(screenshotID)): found = True glob.db.execute( "INSERT INTO screenshots (userid, ssid, sstime) VALUES (%s, %s, %s)", [userID, screenshotID, int(time.time())]) # Write screenshot file to .data folder with open(".data/screenshots/{}.png".format(screenshotID), "wb") as f: f.write(self.request.files["ss"][0]["body"]) # Output log.info("New screenshot ({})".format(screenshotID)) # Return screenshot link self.write("{}/ss/{}.png".format( glob.conf.config["server"]["serverurl"], screenshotID)) except exceptions.need2FAException: pass except exceptions.invalidArgumentsException: pass except exceptions.loginFailedException: pass
def asyncGet(self): try: # Get request ip ip = self.getRequestIP() # Argument check if not requestsManager.checkArguments(self.request.arguments, ["u", "h"]): raise exceptions.invalidArgumentsException(self.MODULE_NAME) # Get user ID username = self.get_argument("u") userID = userUtils.getID(username) if userID is None: raise exceptions.loginFailedException(self.MODULE_NAME, username) # Check login log.info("{} ({}) wants to connect".format(username, userID)) if not userUtils.checkLogin(userID, self.get_argument("h"), ip): raise exceptions.loginFailedException(self.MODULE_NAME, username) # Ban check if userUtils.isBanned(userID): raise exceptions.userBannedException(self.MODULE_NAME, username) # Lock check if userUtils.isLocked(userID): raise exceptions.userLockedException(self.MODULE_NAME, username) # 2FA check if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(self.MODULE_NAME, username, ip) # Update latest activity userUtils.updateLatestActivity(userID) # Get country and output it country = glob.db.fetch( "SELECT country FROM users_stats WHERE id = %s", [userID])["country"] self.write(country) except exceptions.invalidArgumentsException: pass except exceptions.loginFailedException: self.write("error: pass\n") except exceptions.userBannedException: pass except exceptions.userLockedException: pass except exceptions.need2FAException: self.write("error: verify\n")
def asyncGet(self): try: # Get request ip ip = self.getRequestIP() # Check arguments if not requestsManager.checkArguments(self.request.arguments, ["c", "u", "h"]): raise exceptions.invalidArgumentsException(MODULE_NAME) # Get arguments username = self.get_argument("u") password = self.get_argument("h") replayID = self.get_argument("c") isRelaxing = False if int(replayID) < 500000000: isRelaxing = True # Login check userID = userUtils.getID(username) if userID == 0: raise exceptions.loginFailedException(MODULE_NAME, userID) if not userUtils.checkLogin(userID, password, ip): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, username, ip) replayData = glob.db.fetch("SELECT scores{relax}.*, users.username AS uname FROM scores{relax} LEFT JOIN users ON scores{relax}.userid = users.id WHERE scores{relax}.id = %s".format(relax="_relax" if isRelaxing else ""), [replayID]) # Increment 'replays watched by others' if needed if replayData is not None: if username != replayData["uname"]: userUtils.incrementReplaysWatched(replayData["userid"], replayData["play_mode"], replayData["mods"]) log.info("Serving replay_{}.osr".format(replayID)) fileName = ".data/replays/replay_{}.osr".format(replayID) if os.path.isfile(fileName): with open(fileName, "rb") as f: fileContent = f.read() self.write(fileContent) else: self.write("") log.warning("Replay {} doesn't exist.".format(replayID)) except exceptions.invalidArgumentsException: pass except exceptions.need2FAException: pass except exceptions.loginFailedException: pass
def asyncGet(self): try: args = {} try: # Check user auth because of sneaky people if not requestsManager.checkArguments(self.request.arguments, ["u", "h"]): raise exceptions.invalidArgumentsException(MODULE_NAME) username = self.get_argument("u") password = self.get_argument("h") ip = self.getRequestIP() userID = userUtils.getID(username) if not userUtils.checkLogin(userID, password): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, username, ip) # Get arguments gameMode = self.get_argument("m", None) if gameMode is not None: gameMode = int(gameMode) try: if gameMode < 0 or gameMode > 3: gameMode = None except: gameMode = None rankedStatus = self.get_argument("r", None) if rankedStatus is not None: rankedStatus = int(rankedStatus) query = self.get_argument("q", "") page = int(self.get_argument("p", "0")) if query.lower() in ["newest", "top rated", "most played"]: query = "" except ValueError: raise exceptions.invalidArgumentsException(MODULE_NAME) # Pass all arguments otherwise it doesn't work for key, _ in self.request.arguments.items(): args[key] = self.get_argument(key) # Get data from cheesegull API log.info("{} has requested osu!direct search: {}".format(username, query if query != "" else "index")) response = requests.get("http://ripple.moe/web/osu-search.php?{}".format(urlencode(args))) self.write(response.text) except Exception as e: log.error("search failed: {}".format(e)) self.write("")
def asyncPost(self): try: if glob.debug: requestsManager.printArguments(self) # Check user auth because of sneaky people if not requestsManager.checkArguments(self.request.arguments, ["u", "h", "action"]): raise exceptions.invalidArgumentsException(MODULE_NAME) username = self.get_argument("u") password = self.get_argument("h") action = self.get_argument("action") ip = self.getRequestIP() userID = userUtils.getID(username) if not userUtils.checkLogin(userID, password): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, username, ip) if action != "submit": log.debug("{} didn't send anything like \"submit\" to the server".format(username)) self.write("ok") return content = self.get_argument("content") try: contentDict = json.loads(content) session.setUserSession(userID, contentDict) log.debug("Good, We got {} from {}".format(content, username)) self.write("ok") except: self.write("Not yet") return except exceptions.need2FAException: pass except exceptions.invalidArgumentsException: pass except exceptions.loginFailedException: pass
def asyncPost(self): try: if glob.debug: requestsManager.printArguments(self) # Check user auth because of sneaky people if not requestsManager.checkArguments(self.request.arguments, ["u", "h", "action"]): raise exceptions.invalidArgumentsException(MODULE_NAME) username = self.get_argument("u") password = self.get_argument("h") action = self.get_argument("action") ip = self.getRequestIP() userID = userUtils.getID(username) if not userUtils.checkLogin(userID, password): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, username, ip) if action != "submit": self.write("Yield") return content = self.get_argument("content") try: contentDict = json.loads(content) kotrikhelper.setUserSession(userID, contentDict) self.write("Yield") except: self.write("Not Yet") return except exceptions.need2FAException: pass except exceptions.invalidArgumentsException: pass except exceptions.loginFailedException: pass
def handle(tornadoRequest): # Data to return responseToken = None responseTokenString = "ayy" responseData = bytes() # Get IP from tornado request requestIP = tornadoRequest.getRequestIP() # Avoid exceptions clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"] osuVersion = "unknown" # 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(tornadoRequest.request.body)[2:-3].split("\\n") try: # Make sure loginData is valid if len(loginData) < 3: raise exceptions.invalidArgumentsException() # Get HWID, MAC address and more # Structure (new line = "|", already split) # [0] osu! version # [1] plain mac addressed, separated by "." # [2] mac addresses hash set # [3] unique ID # [4] disk ID splitData = loginData[2].split("|") osuVersion = splitData[0] timeOffset = int(splitData[1]) clientData = splitData[3].split(":")[:5] if len(clientData) < 4: raise exceptions.forceUpdateException() # Try to get the ID from username username = str(loginData[0]) userID = userUtils.getID(username) if not userID: # Invalid username raise exceptions.loginFailedException() if not userUtils.checkLogin(userID, loginData[1]): # Invalid password raise exceptions.loginFailedException() # Make sure we are not banned or locked priv = userUtils.getPrivileges(userID) if userUtils.isBanned( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginBannedException() if userUtils.isLocked( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginLockedException() # 2FA check if userUtils.check2FA(userID, requestIP): log.warning("Need 2FA check for user {}".format(loginData[0])) raise exceptions.need2FAException() # No login errors! # Verify this user (if pending activation) firstLogin = False if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware( userID): if userUtils.verifyUser(userID, clientData): # Valid account log.info("Account {} verified successfully!".format(userID)) glob.verifiedCache[str(userID)] = 1 firstLogin = True else: # Multiaccount detected log.info("Account {} NOT verified!".format(userID)) glob.verifiedCache[str(userID)] = 0 raise exceptions.loginBannedException() # Save HWID in db for multiaccount detection hwAllowed = userUtils.logHardware(userID, clientData, firstLogin) # This is false only if HWID is empty # if HWID is banned, we get restricted so there's no # need to deny bancho access if not hwAllowed: raise exceptions.haxException() # Log user IP userUtils.logIP(userID, requestIP) # Delete old tokens for that user and generate a new one isTournament = "tourney" in osuVersion if not isTournament: glob.tokens.deleteOldTokens(userID) responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset, tournament=isTournament) responseTokenString = responseToken.token # Check restricted mode (and eventually send message) responseToken.checkRestricted() # Send message if donor expires soon if responseToken.privileges & privileges.USER_DONOR > 0: expireDate = userUtils.getDonorExpire(responseToken.userID) if expireDate - int(time.time()) <= 86400 * 3: expireDays = round((expireDate - int(time.time())) / 86400) expireIn = "{} days".format( expireDays) if expireDays > 1 else "less than 24 hours" responseToken.enqueue( serverPackets.notification( "Your donor tag expires in {}! When your donor tag expires, you won't have any of the donor privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Ripple and you don't want to lose your donor privileges, you can donate again by clicking on 'Support us' on Ripple's website." .format(expireIn))) # Deprecate telegram 2fa and send alert if userUtils.deprecateTelegram2Fa(userID): responseToken.enqueue( serverPackets.notification( "As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://ripple.moe. Thank you for your patience." )) # Set silence end UNIX time in token responseToken.silenceEndTime = userUtils.getSilenceEnd(userID) # Get only silence remaining seconds silenceSeconds = responseToken.getSilenceSecondsLeft() # Get supporter/GMT userGMT = False userSupporter = True userTournament = False if responseToken.admin: userGMT = True if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0: userTournament = True # Server restarting check if glob.restarting: raise exceptions.banchoRestartingException() # Send login notification before maintenance message if glob.banchoConf.config["loginNotification"] != "": responseToken.enqueue( serverPackets.notification( glob.banchoConf.config["loginNotification"])) # Maintenance check if glob.banchoConf.config["banchoMaintenance"]: if not userGMT: # We are not mod/admin, delete token, send notification and logout glob.tokens.deleteToken(responseTokenString) raise exceptions.banchoMaintenanceException() else: # We are mod/admin, send warning notification and continue responseToken.enqueue( serverPackets.notification( "Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode." )) # Send all needed login packets responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds)) responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue( serverPackets.userSupporterGMT(userSupporter, userGMT, userTournament)) responseToken.enqueue(serverPackets.userPanel(userID, True)) responseToken.enqueue(serverPackets.userStats(userID, True)) # Channel info end (before starting!?! wtf bancho?) responseToken.enqueue(serverPackets.channelInfoEnd()) # Default opened channels # TODO: Configurable default channels chat.joinChannel(token=responseToken, channel="#osu") chat.joinChannel(token=responseToken, channel="#announce") # Join admin channel if we are an admin if responseToken.admin: chat.joinChannel(token=responseToken, channel="#admin") # Output channels info for key, value in glob.channels.channels.items(): if value.publicRead and not value.hidden: responseToken.enqueue(serverPackets.channelInfo(key)) # Send friends list responseToken.enqueue(serverPackets.friendList(userID)) # Send main menu icon if glob.banchoConf.config["menuIcon"] != "": responseToken.enqueue( serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) # Send online users' panels with glob.tokens: for _, token in glob.tokens.tokens.items(): if not token.restricted: responseToken.enqueue(serverPackets.userPanel( token.userID)) # Get location and country from ip.zxq.co or database if glob.localize: # Get location and country from IP latitude, longitude = locationHelper.getLocation(requestIP) countryLetters = locationHelper.getCountry(requestIP) country = countryHelper.getCountryID(countryLetters) else: # Set location to 0,0 and get country from db log.warning("Location skipped") latitude = 0 longitude = 0 countryLetters = "XX" country = countryHelper.getCountryID(userUtils.getCountry(userID)) # Set location and country responseToken.setLocation(latitude, longitude) responseToken.country = country # Set country in db if user has no country (first bancho login) if userUtils.getCountry(userID) == "XX": userUtils.setCountry(userID, countryLetters) # Send to everyone our userpanel if we are not restricted or tournament if not responseToken.restricted: glob.streams.broadcast("main", serverPackets.userPanel(userID)) # Set reponse data to right value and reset our queue responseData = responseToken.queue responseToken.resetQueue() except exceptions.loginFailedException: # Login failed error packet # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() except exceptions.invalidArgumentsException: # Invalid POST data # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() responseData += serverPackets.notification( "I see what you're doing...") except exceptions.loginBannedException: # Login banned error packet responseData += serverPackets.loginBanned() except exceptions.loginLockedException: # Login banned error packet responseData += serverPackets.loginLocked() except exceptions.banchoMaintenanceException: # Bancho is in maintenance mode responseData = bytes() if responseToken is not None: responseData = responseToken.queue responseData += serverPackets.notification( "Our bancho server is in maintenance mode. Please try to login again later." ) responseData += serverPackets.loginFailed() except exceptions.banchoRestartingException: # Bancho is restarting responseData += serverPackets.notification( "Bancho is restarting. Try again in a few minutes.") responseData += serverPackets.loginFailed() except exceptions.need2FAException: # User tried to log in from unknown IP responseData += serverPackets.needVerification() except exceptions.haxException: # Using oldoldold client, we don't have client data. Force update. # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.forceUpdate() responseData += serverPackets.notification( "Hory shitto, your client is TOO old! Nice prehistory! Please turn update it from the settings!" ) except: log.error("Unknown error!\n```\n{}\n{}```".format( sys.exc_info(), traceback.format_exc())) finally: # Console and discord log if len(loginData) < 3: log.info( "Invalid bancho login request from **{}** (insufficient POST data)" .format(requestIP), "bunker") # Return token string and data return responseTokenString, responseData
def handle(tornadoRequest): # Data to return responseToken = None responseTokenString = "ayy" responseData = bytes() # Get IP from tornado request requestIP = tornadoRequest.getRequestIP() # Avoid exceptions clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"] osuVersion = "unknown" # 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(tornadoRequest.request.body)[2:-3].split("\\n") try: # Make sure loginData is valid #if len(loginData) < 3: #raise exceptions.invalidArgumentsException() # Get HWID, MAC address and more # Structure (new line = "|", already split) # [0] osu! version # [1] plain mac addressed, separated by "." # [2] mac addresses hash set # [3] unique ID # [4] disk ID splitData = loginData[2].split("|") osuVersion = splitData[0] timeOffset = int(splitData[1]) clientData = splitData[3].split(":")[:5] if len(clientData) < 4: raise exceptions.forceUpdateException() # Try to get the ID from username username = str(loginData[0]) userID = userUtils.getID(username) if not userID: # Invalid username raise exceptions.loginFailedException() if not userUtils.checkLogin(userID, loginData[1]): # Invalid password raise exceptions.loginFailedException() # Make sure we are not banned or locked priv = userUtils.getPrivileges(userID) if userUtils.isBanned( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginBannedException() if userUtils.isLocked( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginLockedException() # 2FA check if userUtils.check2FA(userID, requestIP): if userID == 1000: #make sakuru do a different check because he plays over LAN if glob.db.fetch( f"SELECT 2fabypassip FROM users WHERE id = 1000" ) == requestIP: pass log.warning("Need 2FA check for user {}".format(loginData[0])) raise exceptions.need2FAException() # No login errors! # Verify this user (if pending activation) firstLogin = False if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware( userID): # Log user IP userUtils.logIP(userID, requestIP) if glob.db.fetch( f"SELECT ip FROM ip_blacklist WHERE ip = '{requestIP}'" ): #requestIP comes from tornado, so this isn't an sql injection vulnerability afaik glob.tokens.deleteToken(userID) userUtils.restrict(userID) return if userUtils.verifyUser(userID, clientData): # Valid account log.info("Account {} verified successfully!".format(userID)) glob.verifiedCache[str(userID)] = 1 firstLogin = True else: # Multiaccount detected log.info("Account {} NOT verified!".format(userID)) glob.verifiedCache[str(userID)] = 0 raise exceptions.loginBannedException() # Save HWID in db for multiaccount detection hwAllowed = userUtils.logHardware(userID, clientData, firstLogin) # This is false only if HWID is empty # if HWID is banned, we get restricted so there's no # need to deny bancho access if not hwAllowed: raise exceptions.haxException() # Log user osuver kotrikhelper.setUserLastOsuVer(userID, osuVersion) # Delete old tokens for that user and generate a new one isTournament = "tourney" in osuVersion if not isTournament: glob.tokens.deleteOldTokens(userID) responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset, tournament=isTournament) responseTokenString = responseToken.token # Check restricted mode (and eventually send message) responseToken.checkRestricted() # Check if frozen IsFrozen = glob.db.fetch( f"SELECT frozen, firstloginafterfrozen, freezedate FROM users WHERE id = {userID} LIMIT 1" ) #ok kids, dont ever use formats in sql queries. here i can do it as the userID comes from a trusted source (this being pep.py itself) so it wont leave me susceptable to sql injection frozen = bool(IsFrozen["frozen"]) present = datetime.now() readabledate = datetime.utcfromtimestamp( IsFrozen["freezedate"]).strftime('%d-%m-%Y %H:%M:%S') date2 = datetime.utcfromtimestamp( IsFrozen["freezedate"]).strftime('%d/%m/%Y') date3 = present.strftime('%d/%m/%Y') passed = date2 < date3 if frozen and passed == False: responseToken.enqueue( serverPackets.notification( f"The osuHOW staff team has found you suspicious and would like to request a liveplay. You have until {readabledate} (UTC) to provide a liveplay to the staff team. This can be done via the osuHOW Discord server. Failure to provide a valid liveplay will result in your account being automatically restricted." )) elif frozen and passed == True: responseToken.enqueue( serverPackets.notification( "Your window for liveplay sumbission has expired! Your account has been restricted as per our cheating policy. Please contact staff for more information on what can be done. This can be done via the osuHOW Discord server." )) userUtils.restrict(responseToken.userID) #we thank unfrozen people first = IsFrozen["firstloginafterfrozen"] if not frozen and first: responseToken.enqueue( serverPackets.notification( "Thank you for providing a liveplay! You have proven your legitemacy and have subsequently been unfrozen." )) glob.db.execute( f"UPDATE users SET firstloginafterfrozen = 0 WHERE id = {userID}" ) # Deprecate telegram 2fa and send alert #if userUtils.deprecateTelegram2Fa(userID): # responseToken.enqueue(serverPackets.notification("As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://ripple.moe. Thank you for your patience.")) # Set silence end UNIX time in token responseToken.silenceEndTime = userUtils.getSilenceEnd(userID) # Get only silence remaining seconds silenceSeconds = responseToken.getSilenceSecondsLeft() # Get supporter/GMT userGMT = False if not userUtils.isRestricted(userID): userSupporter = True else: userSupporter = False userTournament = False if responseToken.admin: userGMT = True if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0: userTournament = True # Server restarting check if glob.restarting: raise exceptions.banchoRestartingException() # Send login notification before maintenance message #if glob.banchoConf.config["loginNotification"] != "": #creating notification OnlineUsers = int( glob.redis.get("ripple:online_users").decode("utf-8")) Notif = f"""- Online Users: {OnlineUsers} - {random.choice(glob.banchoConf.config['Quotes'])}""" responseToken.enqueue(serverPackets.notification(Notif)) # Maintenance check if glob.banchoConf.config["banchoMaintenance"]: if not userGMT: # We are not mod/admin, delete token, send notification and logout glob.tokens.deleteToken(responseTokenString) raise exceptions.banchoMaintenanceException() else: # We are mod/admin, send warning notification and continue responseToken.enqueue( serverPackets.notification( "Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode." )) # BAN CUSTOM CHEAT CLIENTS # 0Ainu = First Ainu build # b20190326.2 = Ainu build 2 (MPGH PAGE 10) # b20190401.22f56c084ba339eefd9c7ca4335e246f80 = Ainu Aoba's Birthday Build # b20191223.3 = Unknown Ainu build? (Taken from most users osuver in cookiezi.pw) # b20190226.2 = hqOsu (hq-af) if glob.conf.extra["mode"]["anticheat"]: # Ainu Client 2020 update if tornadoRequest.request.headers.get("ainu") == "happy": log.info(f"Account {userID} tried to use Ainu Client 2020!") if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "Ainu client... Really? Welp enjoy your ban!")) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) raise exceptions.loginCheatClientsException() # Ainu Client 2019, elif aobaHelper.getOsuVer(userID) in [ "0Ainu", "b20190401.22f56c084ba339eefd9c7ca4335e246f80" ]: log.info(f"Account {userID} tried to use 0Ainu Client!") if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "Ainu client... Really? Welp enjoy your ban!")) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) raise exceptions.loginCheatClientsException() elif aobaHelper.getOsuVer(userID) in [ "b20190326.2", "b20191223.3" ]: log.info(f"Account {userID} tried to use 1Ainu Client!") # hqOsu elif aobaHelper.getOsuVer(userID) == "b20190226.2": log.info(f"Account {userID} is maybe using hqosu") #hqosu legacy elif aobaHelper.getOsuVer(userID) == "b20190716.5": log.info(f"Account {userID} is maybe using hqosu legacy") elif tornadoRequest.request.headers.get( "a") == "@_@_@_@_@_@_@_@___@_@_@_@___@_@___@": log.info("Account ID {} tried to use secret!".format(userID)) if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "You're banned because you're currently using some darkness secret that no one has..." )) return else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="@_@_@_@_@_@_@_@___@_@_@_@___@_@___@") webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to @_@_@_@_@_@_@_@___@_@_@_@___@_@___@ and got restricted!" .format(username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() raise exceptions.loginCheatClientsException() elif osuVersion.startswith("skoot"): log.info(f"Account {userID} tried to skooooot!!!!") if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "oyoyoyoyoyoyoy no skooooooooting allowed here bud" )) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) raise exceptions.loginCheatClientsException() elif osuVersion[0] != "b": glob.tokens.deleteToken(userID) raise exceptions.haxException() # Send all needed login packets responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds)) responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue( serverPackets.userSupporterGMT(userSupporter, userGMT, userTournament)) responseToken.enqueue(serverPackets.userPanel(userID, True)) responseToken.enqueue(serverPackets.userStats(userID, True)) # Channel info end (before starting!?! wtf bancho?) responseToken.enqueue(serverPackets.channelInfoEnd()) # Default opened channels # TODO: Configurable default channels chat.joinChannel(token=responseToken, channel="#osu") chat.joinChannel(token=responseToken, channel="#announce") # Join admin channel if we are an admin if responseToken.admin: chat.joinChannel(token=responseToken, channel="#admin") # Output channels info for key, value in glob.channels.channels.items(): if value.publicRead and not value.hidden: responseToken.enqueue(serverPackets.channelInfo(key)) # Send friends list responseToken.enqueue(serverPackets.friendList(userID)) # Send main menu icon if glob.banchoConf.config["menuIcon"] != "": responseToken.enqueue( serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) # Send online users' panels with glob.tokens: for _, token in glob.tokens.tokens.items(): if not token.restricted: responseToken.enqueue(serverPackets.userPanel( token.userID)) # Get location and country from ip.zxq.co or database if glob.localize: # Get location and country from IP latitude, longitude = locationHelper.getLocation(requestIP) countryLetters = locationHelper.getCountry(requestIP) country = countryHelper.getCountryID(countryLetters) else: # Set location to 0,0 and get country from db log.warning("Location skipped") latitude = 0 longitude = 0 countryLetters = "XX" country = countryHelper.getCountryID(userUtils.getCountry(userID)) # Set location and country responseToken.setLocation(latitude, longitude) responseToken.country = country # Set country in db if user has no country (first bancho login) if userUtils.getCountry(userID) == "XX": userUtils.setCountry(userID, countryLetters) # Send to everyone our userpanel if we are not restricted or tournament if not responseToken.restricted: glob.streams.broadcast("main", serverPackets.userPanel(userID)) # Set reponse data to right value and reset our queue responseData = responseToken.queue responseToken.resetQueue() except exceptions.loginFailedException: # Login failed error packet # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() except exceptions.invalidArgumentsException: # Invalid POST data # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() responseData += serverPackets.notification( "I see what you're doing...") except exceptions.loginBannedException: # Login banned error packet responseData += serverPackets.loginBanned() except exceptions.loginLockedException: # Login banned error packet responseData += serverPackets.loginLocked() except exceptions.loginCheatClientsException: # Banned for logging in with cheats responseData += serverPackets.loginBanned() except exceptions.banchoMaintenanceException: # Bancho is in maintenance mode responseData = bytes() if responseToken is not None: responseData = responseToken.queue responseData += serverPackets.notification( "Our bancho server is in maintenance mode. Please try to login again later." ) responseData += serverPackets.loginFailed() except exceptions.banchoRestartingException: # Bancho is restarting responseData += serverPackets.notification( "Bancho is restarting. Try again in a few minutes.") responseData += serverPackets.loginFailed() except exceptions.need2FAException: # User tried to log in from unknown IP responseData += serverPackets.needVerification() except exceptions.haxException: # Using oldoldold client, we don't have client data. Force update. # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.forceUpdate() except: log.error("Unknown error!\n```\n{}\n{}```".format( sys.exc_info(), traceback.format_exc())) finally: # Console and discord log if len(loginData) < 3: log.info( "Invalid bancho login request from **{}** (insufficient POST data)" .format(requestIP), "bunker") # Return token string and data return responseTokenString, responseData
def asyncPost(self): try: if glob.debug: requestsManager.printArguments(self) # Make sure screenshot file was passed if "ss" not in self.request.files: raise exceptions.invalidArgumentsException(MODULE_NAME) # Check user auth because of sneaky people if not requestsManager.checkArguments(self.request.arguments, ["u", "p"]): raise exceptions.invalidArgumentsException(MODULE_NAME) username = self.get_argument("u") password = self.get_argument("p") ip = self.getRequestIP() userID = userUtils.getID(username) if not userUtils.checkLogin(userID, password): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, username, ip) # Rate limit if glob.redis.get("lets:screenshot:{}".format(userID)) is not None: self.write("no") return glob.redis.set("lets:screenshot:{}".format(userID), 1, 60) # Get a random screenshot id found = False screenshotID = "" while not found: screenshotID = generalUtils.randomString(8) if not os.path.isfile(".data/screenshots/{}.png".format(screenshotID)): found = True # Write screenshot file to .data folder with open(".data/screenshots/{}.png".format(screenshotID), "wb") as f: f.write(self.request.files["ss"][0]["body"]) # Add Akatsuki's watermark base_screenshot = Image.open('.data/screenshots/{}.png'.format(screenshotID)) watermark = Image.open('constants/watermark.png') width, height = base_screenshot.size position = (width - 330, height - 200) transparent = Image.new('RGBA', (width, height), (0,0,0,0)) transparent.paste(base_screenshot, (0,0)) transparent.paste(watermark, position, mask=watermark) transparent.show() transparent.save('.data/screenshots/{}.png'.format(screenshotID)) # Output log.info("New screenshot ({})".format(screenshotID)) # Return screenshot link self.write("{}/ss/{}.png".format(glob.conf.config["server"]["servername"], screenshotID)) except exceptions.need2FAException: pass except exceptions.invalidArgumentsException: pass except exceptions.loginFailedException: pass
def asyncGet(self): try: # insert ripple unfunny roblox word here. very unfunny # Get request ip ip = self.getRequestIP() # Check arguments if not requestsManager.checkArguments(self.request.arguments, ["c", "u", "h"]): raise exceptions.invalidArgumentsException(MODULE_NAME) # Get arguments username = self.get_argument("u") password = self.get_argument("h") replayID = self.get_argument("c") # Login check userID = userUtils.getID(username) if userID == 0: raise exceptions.loginFailedException(MODULE_NAME, userID) if not userUtils.checkLogin(userID, password, ip): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, username, ip) # Get user ID replayMode = 'NM' modeData = { 'NM': (modeSwitches.rp_folder[0], 'VANILLA', modeSwitches.score[0]), 'RL': (modeSwitches.rp_folder[1], 'RELAX', modeSwitches.score[1]), 'V2': (modeSwitches.rp_folder[2], 'SCOREv2', modeSwitches.score[2]) } if features.MASTER_SCORE_TABLE: replayData = glob.db.fetch("SELECT s.*, users.username AS uname FROM {} as s LEFT JOIN users ON s.userid = users.id WHERE s.id = %s".format('scores_master'), [replayID]) if replayData is not None: replayMode = list(modeData.keys())[replayData['special_mode']] else: userStat = glob.db.fetch('select current_status as c from user_config where id = %s', [userID]) if userStat['c'].endswith('on V2'): replayMode = 'V2' elif userStat['c'].endswith('on Relax'): replayMode = 'RL' pass else: userStat = glob.db.fetch('select current_status as c from user_config where id = %s', [userID]) if userStat['c'].endswith('on V2'): replayMode = 'V2' elif userStat['c'].endswith('on Relax'): replayMode = 'RL' replayData = glob.db.fetch("SELECT s.*, users.username AS uname FROM {} as s LEFT JOIN users ON s.userid = users.id WHERE s.id = %s".format(modeData[replayMode][2]), [replayID]) if replayData is not None: fileName = "{}{}/replay_{}.osr".format(glob.conf.config["server"]["replayspath"], modeData[replayMode][0], replayID) Play = modeData[replayMode][1] else: log.warning("Replay {} ({}) doesn't exist".format(replayID, replayMode)) self.write("") return # Increment 'replays watched by others' if needed if replayData is not None: if username != replayData["uname"]: userUtils.incrementReplaysWatched(replayData["userid"], replayData["play_mode"]) # Serve replay log.info("[{}] Serving replay_{}.osr".format(Play, replayID)) if os.path.isfile(fileName): with open(fileName, "rb") as f: fileContent = f.read() self.write(fileContent) else: log.warning("Replay {} doesn't exist".format(replayID)) self.write("") except exceptions.invalidArgumentsException: pass except exceptions.need2FAException: pass except exceptions.loginFailedException: pass
def asyncPost(self): try: if glob.conf["DEBUG"]: requestsManager.printArguments(self) # Make sure screenshot file was passed if "ss" not in self.request.files: raise exceptions.invalidArgumentsException(MODULE_NAME) # Check user auth because of sneaky people if not requestsManager.checkArguments(self.request.arguments, ["u", "p"]): raise exceptions.invalidArgumentsException(MODULE_NAME) username = self.get_argument("u") password = self.get_argument("p") ip = self.getRequestIP() userID = userUtils.getID(username) if not userUtils.checkLogin(userID, password): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, username, ip) # Rate limit if glob.redis.get("lets:screenshot:{}".format(userID)) is not None: self.write("no") return glob.redis.set("lets:screenshot:{}".format(userID), 1, 60) # Get a random screenshot id hasS3 = bool(glob.conf["S3_SCREENSHOTS_BUCKET"]) \ and bool(glob.conf["S3_SCREENSHOTS_REGION"]) \ and bool(glob.conf["S3_SCREENSHOTS_ENDPOINT_URL"]) found = False screenshotID = "" while not found: screenshotID = generalUtils.randomString(8) if hasS3: try: glob.threadScope.s3Screenshots.head_object( Bucket=glob.conf["S3_SCREENSHOTS_BUCKET"], Key=f"{screenshotID}.jpg") found = False except botocore.errorfactory.ClientError: found = True else: found = not os.path.isfile("{}/{}.jpg".format( glob.conf["SCREENSHOTS_FOLDER"], screenshotID)) # Output log.info("New screenshot ({})".format(screenshotID)) # Write screenshot file to .data folder if hasS3: with io.BytesIO(self.request.files["ss"][0]["body"]) as f: glob.threadScope.s3Screenshots.upload_fileobj( f, glob.conf["S3_SCREENSHOTS_BUCKET"], f"{screenshotID}.jpg", ExtraArgs={"ACL": "public-read"}) else: with open( "{}/{}.jpg".format(glob.conf["SCREENSHOTS_FOLDER"], screenshotID), "wb") as f: f.write(self.request.files["ss"][0]["body"]) # Return screenshot link self.write("{}/ss/{}.jpg".format(glob.conf["SERVER_URL"], screenshotID)) except exceptions.need2FAException: pass except exceptions.invalidArgumentsException: pass except exceptions.loginFailedException: pass
def asyncGet(self): try: # OOF UsingRelax = False UsingAuto = False # Get request ip ip = self.getRequestIP() # Check arguments if not requestsManager.checkArguments(self.request.arguments, ["c", "u", "h"]): raise exceptions.invalidArgumentsException(MODULE_NAME) # Get arguments username = self.get_argument("u") password = self.get_argument("h") replayID = self.get_argument("c") # Login check userID = userUtils.getID(username) if userID == 0: raise exceptions.loginFailedException(MODULE_NAME, userID) if not userUtils.checkLogin(userID, password, ip): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, username, ip) # Get user ID current_gamemode_redis = "lets:user_current_gamemode:{}".format( userID) cgamemode = int(glob.redis.get(current_gamemode_redis)) replayData = glob.db.fetch( "SELECT scores.*, users.username AS uname FROM scores LEFT JOIN users ON scores.userid = users.id WHERE scores.id = %s", [replayID]) if cgamemode == 3: log.debug("autopilot") UsingRelax = False usingAuto = True fileName = "{}_ap/replay_{}.osr".format( glob.conf.config["server"]["replayspath"], replayID) if cgamemode == 2: log.debug("relax") fileName = "{}_relax/replay_{}.osr".format( glob.conf.config["server"]["replayspath"], replayID) UsingRelax = True UsingAuto = False if cgamemode == 1: log.debug("std") UsingRelax = False UsingAuto = False fileName = "{}/replay_{}.osr".format( glob.conf.config["server"]["replayspath"], replayID) # Increment 'replays watched by others' if needed if replayData is not None: if username != replayData["uname"]: userUtils.incrementReplaysWatched(replayData["userid"], replayData["play_mode"]) Play = "VANILLA" if UsingRelax: Play = "RELAX" if UsingAuto: Play = "AUTOPILOT" # Serve replay log.info("[{}] Serving replay_{}.osr".format(Play, replayID)) if os.path.isfile(fileName): with open(fileName, "rb") as f: fileContent = f.read() self.write(fileContent) else: log.info("Replay {} doesn't exist".format(replayID)) self.write("") except exceptions.invalidArgumentsException: pass except exceptions.need2FAException: pass except exceptions.loginFailedException: pass
def handle(tornadoRequest): # Data to return responseToken = None responseTokenString = "ayy" responseData = bytes() # Get IP from tornado request requestIP = tornadoRequest.getRequestIP() # Avoid exceptions clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"] osuVersion = "unknown" # 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(tornadoRequest.request.body)[2:-3].split("\\n") try: # Make sure loginData is valid if len(loginData) < 3: raise exceptions.invalidArgumentsException() # Get HWID, MAC address and more # Structure (new line = "|", already split) # [0] osu! version # [1] plain mac addressed, separated by "." # [2] mac addresses hash set # [3] unique ID # [4] disk ID splitData = loginData[2].split("|") osuVersion = splitData[0] timeOffset = int(splitData[1]) clientData = splitData[3].split(":")[:5] if len(clientData) < 4: raise exceptions.forceUpdateException() # Try to get the ID from username username = str(loginData[0]) userID = userUtils.getID(username) if not userID: # Invalid username raise exceptions.loginFailedException() if not userUtils.checkLogin(userID, loginData[1]): # Invalid password raise exceptions.loginFailedException() # Make sure we are not banned or locked priv = userUtils.getPrivileges(userID) if userUtils.isBanned(userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginBannedException() if userUtils.isLocked(userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginLockedException() # 2FA check if userUtils.check2FA(userID, requestIP): log.warning("Need 2FA check for user {}.".format(loginData[0])) raise exceptions.need2FAException() # No login errors! # Verify this user (if pending activation) firstLogin = False if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware(userID): if userUtils.verifyUser(userID, clientData): # Valid account log.info("Account {} verified successfully!".format(userID)) glob.verifiedCache[str(userID)] = 1 firstLogin = True else: # Multiaccount detected log.info("Account {} NOT verified!".format(userID)) glob.verifiedCache[str(userID)] = 0 raise exceptions.loginBannedException() # Save HWID in db for multiaccount detection hwAllowed = userUtils.logHardware(userID, clientData, firstLogin) # This is false only if HWID is empty # if HWID is banned, we get restricted so there's no # need to deny bancho access if not hwAllowed: raise exceptions.haxException() # Log user IP userUtils.logIP(userID, requestIP) # Delete old tokens for that user and generate a new one isTournament = "tourney" in osuVersion if not isTournament: glob.tokens.deleteOldTokens(userID) responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset, tournament=isTournament) responseTokenString = responseToken.token # Check restricted mode (and eventually send message) responseToken.checkRestricted() userFlags = userUtils.getUserFlags(userID) if userFlags > 0: # Pending public bans. Such as chargebacks, etc. flagReason = userUtils.getFlagReason(userID) if userFlags-int(time.time()) < 0: responseToken.enqueue(serverPackets.notification("Your account has been automatically restricted due to a pending restriction not being having been dealt with.\n\nReason: {}".format(flagReason))) userUtils.restrict(userID) userUtils.setUserFlags(userID, 0) log.cmyui("{} has been automatically restricted due to not dealing with pending restriction. Reason: {}.".format(username, flagReason), discord="cm") log.rap(userID, "has been restricted due to a pending restriction. Reason: {}.".format(flagReason)) else: if "charge" in flagReason: responseToken.enqueue(serverPackets.notification("Your account has been flagged with an automatic restriction.\n\nIt will occur at {time} if not dealt with.\n" "Reason: {reason}\n\nTo avoid being restricted for this behaviour, you can cancel or revert your chargeback before your restriction date.".format(time=datetime.utcfromtimestamp(int(userFlags)).strftime('%Y-%m-%d %H:%M:%S'), reason=flagReason))) elif "live" in flagReason: responseToken.enqueue(serverPackets.notification("Your account has been flagged with an automatic restriction.\n\nIt will occur at {time} if not dealt with.\n" "Reason: {reason}\n\nThis means you are required to submit a liveplay to avoid this. This only happens in cases when we are confident in foul play; and are offering you this opportunity as a final stance to prove your legitimacy, against all the odds.".format(time=datetime.utcfromtimestamp(int(userFlags)).strftime('%Y-%m-%d %H:%M:%S'), reason=flagReason))) else: responseToken.enqueue(serverPackets.notification("Your account has been flagged with an automatic restriction.\n\nIt will occur at {time} if not dealt with.\n" "Reason: {reason}\n\nYou have until the restriction to deal with the issue.".format(time=datetime.utcfromtimestamp(int(userFlags)).strftime('%Y-%m-%d %H:%M:%S'), reason=flagReason))) # Send message if premium / donor expires soon # ok spaghetti code time if responseToken.privileges & privileges.USER_DONOR: donorType = 'premium' if responseToken.privileges & privileges.USER_PREMIUM else 'donor' expireDate = userUtils.getDonorExpire(responseToken.userID) if expireDate-int(time.time()) < 0: userUtils.setPrivileges(userID, 3) log.cmyui("{}'s donation perks have been removed as their time has run out.".format(username), discord="cm") log.rap(userID, "User's donor perks have been removed as their time has run out.") responseToken.enqueue(serverPackets.notification("Your {donorType} tag has expired! Thank you so much for the support, it really means everything to us. If you wish to keep supporting Akatsuki and you don't want to lose your {donorType} privileges, you can donate again by clicking on 'Support us' on Akatsuki's website.".format(donorType=donorType))) elif expireDate-int(time.time()) <= 86400*3: expireDays = round((expireDate-int(time.time()))/86400) expireIn = "{} days".format(expireDays) if expireDays > 1 else "less than 24 hours" responseToken.enqueue(serverPackets.notification("Your {donorType} tag expires in {expireIn}! When your {donorType} tag expires, you won't have any of the {donorType} privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Akatsuki and you don't want to lose your {donorType} privileges, you can donate again by clicking on 'Support us' on Akatsuki's website.".format(donorType=donorType, expireIn=expireIn))) """ Akatsuki does not use 2fa! we suck! if userUtils.deprecateTelegram2Fa(userID): responseToken.enqueue(serverPackets.notification("As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://akatsuki.pw. Thank you for your patience.")) """ # Set silence end UNIX time in token responseToken.silenceEndTime = userUtils.getSilenceEnd(userID) # Get only silence remaining seconds silenceSeconds = responseToken.getSilenceSecondsLeft() # Get supporter/GMT userGMT = False userSupporter = True userTournament = False if responseToken.admin: userGMT = True if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0: userTournament = True # Server restarting check if glob.restarting: raise exceptions.banchoRestartingException() # Send login notification before maintenance message if glob.banchoConf.config["loginNotification"] != "": responseToken.enqueue(serverPackets.notification(glob.banchoConf.config["loginNotification"])) # Maintenance check if glob.banchoConf.config["banchoMaintenance"]: if not userGMT: # We are not mod/admin, delete token, send notification and logout glob.tokens.deleteToken(responseTokenString) raise exceptions.banchoMaintenanceException() else: # We are mod/admin, send warning notification and continue responseToken.enqueue(serverPackets.notification("Akatsuki is currently in maintenance mode. Only admins have full access to the server.\nType '!system maintenance off' in chat to turn off maintenance mode.")) # Send all needed login packets responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds)) responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue(serverPackets.userSupporterGMT(userSupporter, userGMT, userTournament)) responseToken.enqueue(serverPackets.userPanel(userID, True)) responseToken.enqueue(serverPackets.userStats(userID, True)) # Channel info end (before starting!?! wtf bancho?) responseToken.enqueue(serverPackets.channelInfoEnd()) # Default opened channels # TODO: Configurable default channels chat.joinChannel(token=responseToken, channel="#osu") chat.joinChannel(token=responseToken, channel="#announce") #Akatsuki extra channels chat.joinChannel(token=responseToken, channel="#nowranked") chat.joinChannel(token=responseToken, channel="#request") # Join admin channel if we are an admin if responseToken.admin or responseToken.privileges & privileges.USER_PREMIUM: chat.joinChannel(token=responseToken, channel="#admin") # Output channels info for key, value in glob.channels.channels.items(): if value.publicRead and not value.hidden: responseToken.enqueue(serverPackets.channelInfo(key)) # Send friends list responseToken.enqueue(serverPackets.friendList(userID)) # Send main menu icon if glob.banchoConf.config["menuIcon"] != "": responseToken.enqueue(serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) # Send online users' panels with glob.tokens: for _, token in glob.tokens.tokens.items(): if not token.restricted: responseToken.enqueue(serverPackets.userPanel(token.userID)) # Get location and country from ip.zxq.co or database. If the user is a donor, then yee if glob.localize and (firstLogin == True or responseToken.privileges & privileges.USER_DONOR <= 0): # Get location and country from IP latitude, longitude = locationHelper.getLocation(requestIP) countryLetters = locationHelper.getCountry(requestIP) country = countryHelper.getCountryID(countryLetters) else: # Set location to 0,0 and get country from db log.warning("Location skipped") latitude = 0 longitude = 0 countryLetters = "XX" country = countryHelper.getCountryID(userUtils.getCountry(userID)) # Set location and country responseToken.setLocation(latitude, longitude) responseToken.country = country # Set country in db if user has no country (first bancho login) if userUtils.getCountry(userID) == "XX": userUtils.setCountry(userID, countryLetters) # Send to everyone our userpanel if we are not restricted or tournament if not responseToken.restricted: glob.streams.broadcast("main", serverPackets.userPanel(userID)) # Set reponse data to right value and reset our queue responseData = responseToken.queue responseToken.resetQueue() except exceptions.loginFailedException: # Login failed error packet # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() except exceptions.invalidArgumentsException: # Invalid POST data # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() responseData += serverPackets.notification("We see what you're doing..") log.cmyui("User {} has triggered invalidArgumentsException in loginEvent.py".format(userID), discord="cm") except exceptions.loginBannedException: # Login banned error packet responseData += serverPackets.loginBanned() except exceptions.loginLockedException: # Login banned error packet responseData += serverPackets.loginLocked() except exceptions.banchoMaintenanceException: # Bancho is in maintenance mode responseData = bytes() if responseToken is not None: responseData = responseToken.queue responseData += serverPackets.notification("Akatsuki is currently in maintenance mode. Please try to login again later.") responseData += serverPackets.loginFailed() except exceptions.banchoRestartingException: # Bancho is restarting responseData += serverPackets.notification("Akatsuki is restarting. Try again in a few minutes.") responseData += serverPackets.loginFailed() except exceptions.need2FAException: # User tried to log in from unknown IP responseData += serverPackets.needVerification() except exceptions.haxException: # Using oldoldold client, we don't have client data. Force update. # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.forceUpdate() responseData += serverPackets.notification("Custom clients of ANY kind are NOT PERMITTED on Akatsuki. Please login using the current osu! client.") log.cmyui("User {} has logged in with a VERY old client".format(userID), discord="cm") except: log.error("Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc())) finally: # Console and discord log if len(loginData) < 3: log.info("Invalid bancho login request from **{}** (insufficient POST data)".format(requestIP), "bunker") # Return token string and data return responseTokenString, responseData
def asyncPost(self): try: if glob.debug: requestsManager.printArguments(self) # Make sure screenshot file was passed if "ss" not in self.request.files: raise exceptions.invalidArgumentsException(MODULE_NAME) # Check user auth because of sneaky people if not requestsManager.checkArguments(self.request.arguments, ["u", "p"]): raise exceptions.invalidArgumentsException(MODULE_NAME) username = self.get_argument("u") password = self.get_argument("p") ip = self.getRequestIP() userID = userUtils.getID(username) if not userUtils.checkLogin(userID, password): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, username, ip) # Rate limit if glob.redis.get("lets:screenshot:{}".format(userID)) is not None: self.write("no") return glob.redis.set("lets:screenshot:{}".format(userID), 1, 60) #check if user folder exist screenshotID = generalUtils.randomString(8) if not os.path.isdir("{}/{}/".format( glob.conf.config["server"]["storagepath"], userID)): os.mkdir("{}/{}/".format( glob.conf.config["server"]["storagepath"], userID)) # Get a random screenshot id found = False screenshotID = "" while not found: screenshotID = generalUtils.randomString(8) if not os.path.isfile("{}/{}/{}.jpg".format( glob.conf.config["server"]["storagepath"], userID, screenshotID)): found = True # Write screenshot file to screenshots folder with open( "{}/{}/{}.jpg".format( glob.conf.config["server"]["storagepath"], userID, screenshotID), "wb") as f: f.write(self.request.files["ss"][0]["body"]) # Output log.info("New screenshot ({})".format(screenshotID)) # Return screenshot link self.write("https://storage.aeris-dev.pw/get/{}/{}.jpg".format( userID, screenshotID)) except exceptions.need2FAException: pass except exceptions.invalidArgumentsException: pass except exceptions.loginFailedException: pass
def handle(tornadoRequest): # Data to return responseToken = None responseTokenString = "ayy" responseData = bytes() # Get IP from tornado request requestIP = tornadoRequest.getRequestIP() # Avoid exceptions clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"] osuVersion = "unknown" # 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(tornadoRequest.request.body)[2:-3].split("\\n") try: # Make sure loginData is valid if len(loginData) < 3: raise exceptions.invalidArgumentsException() # Get HWID, MAC address and more # Structure (new line = "|", already split) # [0] osu! version # [1] plain mac addressed, separated by "." # [2] mac addresses hash set # [3] unique ID # [4] disk ID splitData = loginData[2].split("|") osuVersion = splitData[0] # osu! version timeOffset = int(splitData[1]) # timezone showCity = int(splitData[2]) # allow to show city clientData = splitData[3].split(":")[:5] # security hash blockNonFriendPM = int(splitData[4]) # allow PM if len(clientData) < 4: raise exceptions.forceUpdateException() # Try to get the ID from username username = str(loginData[0]) userID = userUtils.getID(username) if not userID: # Invalid username raise exceptions.loginFailedException() if not userUtils.checkLogin(userID, loginData[1]): # Invalid password raise exceptions.loginFailedException() # Make sure we are not banned or locked priv = userUtils.getPrivileges(userID) if userUtils.isBanned( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginBannedException() if userUtils.isLocked( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginLockedException() # 2FA check if userUtils.check2FA(userID, requestIP): log.warning("Need 2FA check for user {}".format(loginData[0])) raise exceptions.need2FAException() # No login errors! # Verify this user (if pending activation) firstLogin = False if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware( userID): if userUtils.verifyUser(userID, clientData): # Valid account log.info("Account ID {} verified successfully!".format(userID)) glob.verifiedCache[str(userID)] = 1 firstLogin = True else: # Multiaccount detected log.info("Account ID {} NOT verified!".format(userID)) glob.verifiedCache[str(userID)] = 0 raise exceptions.loginBannedException() # Save HWID in db for multiaccount detection hwAllowed = userUtils.logHardware(userID, clientData, firstLogin) # This is false only if HWID is empty # if HWID is banned, we get restricted so there's no # need to deny bancho access if not hwAllowed: raise exceptions.haxException() # Log user IP userUtils.logIP(userID, requestIP) # Log user osuver kotrikhelper.setUserLastOsuVer(userID, osuVersion) # Delete old tokens for that user and generate a new one isTournament = "tourney" in osuVersion numericVersion = re.sub(r'[^0-9.]', '', osuVersion) if not isTournament: glob.tokens.deleteOldTokens(userID) if numericVersion < glob.conf.config["server"]["osuminver"]: raise exceptions.forceUpdateException() responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset, tournament=isTournament) responseTokenString = responseToken.token # Check restricted mode (and eventually send message) responseToken.checkRestricted() # Send message if donor expires soon if responseToken.privileges & privileges.USER_DONOR > 0: expireDate = userUtils.getDonorExpire(responseToken.userID) if expireDate - int(time.time()) <= 86400 * 3: expireDays = round((expireDate - int(time.time())) / 86400) expireIn = "{} days".format( expireDays) if expireDays > 1 else "less than 24 hours" responseToken.enqueue( serverPackets.notification( "Your donor tag expires in {}! When your donor tag expires, you won't have any of the donor privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Ripple and you don't want to lose your donor privileges, you can donate again by clicking on 'Support us' on Ripple's website." .format(expireIn))) # Deprecate telegram 2fa and send alert if userUtils.deprecateTelegram2Fa(userID): responseToken.enqueue( serverPackets.notification( "As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://ripple.moe. Thank you for your patience." )) # Set silence end UNIX time in token responseToken.silenceEndTime = userUtils.getSilenceEnd(userID) # Get only silence remaining seconds silenceSeconds = responseToken.getSilenceSecondsLeft() # Get supporter/GMT userGMT = False if not userUtils.isRestricted(userID): userSupporter = True else: userSupporter = False userTournament = False if responseToken.admin: userGMT = True if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0: userTournament = True # Server restarting check if glob.restarting: raise exceptions.banchoRestartingException() """ if userUtils.checkIfFlagged(userID): responseToken.enqueue(serverPackets.notification("Staff suspect you of cheat! You have 5 days to make a full pc startup liveplay, or you will get restricted and you'll have to wait a month to appeal!")) """ # Check If today is 4/20 (Peppy Day) if today == peppyday: if glob.conf.extra["mode"]["peppyday"]: responseToken.enqueue( serverPackets.notification( "Everyone on today will have peppy as their profile picture! Have fun on peppy day" )) # Send login notification before maintenance message if glob.banchoConf.config["loginNotification"] != "": responseToken.enqueue( serverPackets.notification( glob.banchoConf.config["loginNotification"])) # Maintenance check if glob.banchoConf.config["banchoMaintenance"]: if not userGMT: # We are not mod/admin, delete token, send notification and logout glob.tokens.deleteToken(responseTokenString) raise exceptions.banchoMaintenanceException() else: # We are mod/admin, send warning notification and continue responseToken.enqueue( serverPackets.notification( "Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode." )) # BAN CUSTOM CHEAT CLIENTS # 0Ainu = First Ainu build # b20190326.2 = Ainu build 2 (MPGH PAGE 10) # b20190401.22f56c084ba339eefd9c7ca4335e246f80 = Ainu Aoba's Birthday Build # b20190906.1 = Unknown Ainu build? (unreleased, I think) # b20191223.3 = Unknown Ainu build? (Taken from most users osuver in cookiezi.pw) # b20190226.2 = hqOsu (hq-af) if glob.conf.extra["mode"]["anticheat"]: # Ainu Client 2020 update if tornadoRequest.request.headers.get("ainu") == "happy": log.info( "Account ID {} tried to use Ainu (Cheat) Client 2020!". format(userID)) if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "You're banned because you're currently using Ainu Client... Happy New Year 2020 and Enjoy your restriction :)" )) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]" ) webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to use Ainu (Cheat) Client 2020! AGAIN!!!". format(username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) aobaHelper.Webhook.post() else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]" ) webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to use Ainu (Cheat) Client 2020 and got restricted!" .format(username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() raise exceptions.loginCheatClientsException() # Ainu Client 2019 elif aobaHelper.getOsuVer(userID) in [ "0Ainu", "b20190326.2", "b20190401.22f56c084ba339eefd9c7ca4335e246f80", "b20190906.1", "b20191223.3" ]: log.info( "Account ID {} tried to use Ainu (Cheat) Client!".format( userID)) if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "You're banned because you're currently using Ainu Client. Enjoy your restriction :)" )) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]" ) webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to use Ainu (Cheat) Client! AGAIN!!!".format( username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]" ) webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to use Ainu (Cheat) Client and got restricted!" .format(username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() raise exceptions.loginCheatClientsException() # hqOsu elif aobaHelper.getOsuVer(userID) == "b20190226.2": log.info("Account ID {} tried to use hqOsu!".format(userID)) if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "Trying to use hqOsu in here? Well... No, sorry. We don't allow cheats here. Go play https://cookiezi.pw or others cheat server." )) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]" ) webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to use hqOsu! AGAIN!!!".format(username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]" ) webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to use hqOsu and got restricted!".format( username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() raise exceptions.loginCheatClientsException() # Send all needed login packets responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds)) responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue( serverPackets.userSupporterGMT(userSupporter, userGMT, userTournament)) responseToken.enqueue(serverPackets.userPanel(userID, True)) responseToken.enqueue(serverPackets.userStats(userID, True)) # Channel info end (before starting!?! wtf bancho?) responseToken.enqueue(serverPackets.channelInfoEnd()) # Default opened channels # TODO: Configurable default channels chat.joinChannel(token=responseToken, channel="#osu") chat.joinChannel(token=responseToken, channel="#announce") # Join admin channel if we are an admin if responseToken.admin: chat.joinChannel(token=responseToken, channel="#admin") # Output channels info for key, value in glob.channels.channels.items(): if value.publicRead and not value.hidden: responseToken.enqueue(serverPackets.channelInfo(key)) # Send friends list responseToken.enqueue(serverPackets.friendList(userID)) # Send main menu icon if glob.banchoConf.config["menuIcon"] != "": responseToken.enqueue( serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) # Send online users' panels with glob.tokens: for _, token in glob.tokens.tokens.items(): if not token.restricted: responseToken.enqueue(serverPackets.userPanel( token.userID)) # Get location and country from ip.zxq.co or database if glob.localize: # Get location and country from IP latitude, longitude = locationHelper.getLocation(requestIP) if userID == 1000: latitude, longitude = 34.676143, 133.938883 countryLetters = locationHelper.getCountry(requestIP) country = countryHelper.getCountryID(countryLetters) else: # Set location to 0,0 and get country from db log.warning("Location skipped") latitude = 0 longitude = 0 countryLetters = "XX" country = countryHelper.getCountryID(userUtils.getCountry(userID)) # Set location and country responseToken.setLocation(latitude, longitude) responseToken.country = country # Set country in db if user has no country (first bancho login) if userUtils.getCountry(userID) == "XX": userUtils.setCountry(userID, countryLetters) # Send to everyone our userpanel if we are not restricted or tournament if not responseToken.restricted: glob.streams.broadcast("main", serverPackets.userPanel(userID)) # Set reponse data to right value and reset our queue responseData = responseToken.queue responseToken.resetQueue() except exceptions.loginFailedException: # Login failed error packet # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() except exceptions.invalidArgumentsException: # Invalid POST data # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() responseData += serverPackets.notification( "I see what you're doing...") except exceptions.loginBannedException: # Login banned error packet responseData += serverPackets.loginBanned() except exceptions.loginLockedException: # Login banned error packet responseData += serverPackets.loginLocked() except exceptions.loginCheatClientsException: # Banned for logging in with cheats responseData += serverPackets.loginCheats() except exceptions.banchoMaintenanceException: # Bancho is in maintenance mode responseData = bytes() if responseToken is not None: responseData = responseToken.queue responseData += serverPackets.notification( "Our bancho server is in maintenance mode. Please try to login again later." ) responseData += serverPackets.loginFailed() except exceptions.banchoRestartingException: # Bancho is restarting responseData += serverPackets.notification( "Bancho is restarting. Try again in a few minutes.") responseData += serverPackets.loginFailed() except exceptions.need2FAException: # User tried to log in from unknown IP responseData += serverPackets.needVerification() except exceptions.haxException: # Uh... responseData += serverPackets.notification("Your HWID is banned.") responseData += serverPackets.loginFailed() except exceptions.forceUpdateException: # This happens when you: # - Using older build than config set # - Using oldoldold client, we don't have client data. Force update. # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.forceUpdate() except: log.error("Unknown error!\n```\n{}\n{}```".format( sys.exc_info(), traceback.format_exc())) finally: # Console and discord log if len(loginData) < 3: log.info( "Invalid bancho login request from **{}** (insufficient POST data)" .format(requestIP), "bunker") # Return token string and data return responseTokenString, responseData
def asyncGet(self): try: # Get request ip ip = self.getRequestIP() # Check arguments if not requestsManager.checkArguments(self.request.arguments, ["c", "u", "h"]): raise exceptions.invalidArgumentsException(self.MODULE_NAME) # Get arguments username = self.get_argument("u") password = self.get_argument("h") replayID = self.get_argument("c") # Login check userID = userUtils.getID(username) if userID == 0: raise exceptions.loginFailedException(self.MODULE_NAME, userID) if not userUtils.checkLogin(userID, password, ip): raise exceptions.loginFailedException(self.MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(self.MODULE_NAME, username, ip) # Get user ID replayData = glob.db.fetch( "SELECT scores.*, users.username AS uname FROM scores LEFT JOIN users ON scores.userid = users.id WHERE scores.id = %s", [replayID]) # Increment 'replays watched by others' if needed if replayData is not None: if username != replayData["uname"]: userUtils.incrementReplaysWatched(replayData["userid"], replayData["play_mode"]) # Serve replay log.info("Serving replay_{}.osr".format(replayID)) r = "" replayID = int(replayID) try: r = replayHelper.getRawReplayS3(replayID) except timeout_decorator.TimeoutError: log.warning("S3 timed out") sentry.captureMessage("S3 timeout while fetching replay.") glob.stats["replay_download_failures"].labels( type="raw_s3_timeout").inc() except FileNotFoundError: log.warning("Replay {} doesn't exist".format(replayID)) except: glob.stats["replay_download_failures"].labels( type="raw_other").inc() raise finally: self.write(r) except exceptions.invalidArgumentsException: pass except exceptions.need2FAException: pass except exceptions.loginFailedException: pass
def asyncGet(self): output = "" try: if not requestsManager.checkArguments(self.request.arguments, ["c", "u", "p"]): raise exceptions.invalidArgumentsException(MODULE_NAME) ip = self.getRequestIP() username = self.get_argument("u").strip() password = self.get_argument("p").strip() user_id = userUtils.getID(username) checksum = self.get_argument("c").strip() if not user_id: raise exceptions.loginFailedException(MODULE_NAME, user_id) if not userUtils.checkLogin(user_id, password, ip): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(user_id, ip): raise exceptions.need2FAException(MODULE_NAME, user_id, ip) ranked = glob.db.fetch( "SELECT ranked FROM beatmaps WHERE beatmap_md5 = %s LIMIT 1", (checksum, )) if ranked is None: output = "no exist" return if ranked["ranked"] < rankedStatuses.RANKED: output = "not ranked" return rating = glob.db.fetch( "SELECT rating FROM beatmaps WHERE beatmap_md5 = %s LIMIT 1", (checksum, )) has_voted = glob.db.fetch( "SELECT id FROM beatmaps_rating WHERE user_id = %s AND beatmap_md5 = %s LIMIT 1", (user_id, checksum)) if has_voted is not None: output = "alreadyvoted\n{rating['rating']:.2}" return vote = self.get_argument("v", default=None) if vote is None: output = "ok" return try: vote = int(vote) except ValueError: raise exceptions.invalidArgumentsException(MODULE_NAME) if vote < 0 or vote > 10: output = "out of range" return glob.db.execute( "REPLACE INTO beatmaps_rating (beatmap_md5, user_id, rating) VALUES (%s, %s, %s)", (checksum, user_id, vote)) glob.db.execute( "UPDATE beatmaps SET rating = (SELECT SUM(rating)/COUNT(rating) FROM beatmaps_rating " "WHERE beatmap_md5 = %(md5)s) WHERE beatmap_md5 = %(md5)s LIMIT 1", {"md5": checksum}) rating = glob.db.fetch( "SELECT rating FROM beatmaps WHERE beatmap_md5 = %s LIMIT 1", (checksum, )) output = "{rating['rating']:.2}" except exceptions.loginFailedException: output = "auth failed" except exceptions.invalidArgumentsException: output = "no" finally: self.write(output)
def handle(tornadoRequest): # Data to return responseToken = None responseTokenString = "ayy" responseData = bytes() # Get IP from tornado request requestIP = tornadoRequest.getRequestIP() # Avoid exceptions clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"] osuVersion = "unknown" # 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(tornadoRequest.request.body)[2:-3].split("\\n") try: # Make sure loginData is valid if len(loginData) < 3: raise exceptions.invalidArgumentsException() # Get HWID, MAC address and more # Structure (new line = "|", already split) # [0] osu! version # [1] plain mac addressed, separated by "." # [2] mac addresses hash set # [3] unique ID # [4] disk ID splitData = loginData[2].split("|") osuVersion = splitData[0] timeOffset = int(splitData[1]) clientData = splitData[3].split(":")[:5] if len(clientData) < 4: raise exceptions.forceUpdateException() if len(splitData) > 4: ignoreDM = bool(int(splitData[4])) else: ignoreDM = False # Try to get the ID from username username = str(loginData[0]) userID = userUtils.getID(username) if not userID: # Invalid username raise exceptions.loginFailedException() if not userUtils.checkLogin(userID, loginData[1]): # Invalid password raise exceptions.loginFailedException() if not eligible.tryLogin(userID): raise exceptions.loginFailedException() # Make sure we are not banned or locked priv = userUtils.getPrivileges(userID) if userUtils.isBanned(userID) and not ( priv & privileges.USER_PENDING_VERIFICATION): raise exceptions.loginBannedException() if userUtils.isLocked(userID) and not ( priv & privileges.USER_PENDING_VERIFICATION): raise exceptions.loginLockedException() # 2FA check if userUtils.check2FA(userID, requestIP): log.warning("Need 2FA check for user {}".format(loginData[0])) raise exceptions.need2FAException() # No login errors! # Verify this user (if pending activation) firstLogin = False if priv & privileges.USER_PENDING_VERIFICATION or not userUtils.hasVerifiedHardware( userID): if userUtils.verifyUser(userID, clientData): # Valid account log.info("Account ID {} verified successfully!".format(userID)) glob.verifiedCache[str(userID)] = 1 firstLogin = True else: # Multiaccount detected log.info("Account ID {} NOT verified!".format(userID)) glob.verifiedCache[str(userID)] = 0 raise exceptions.loginBannedException() # Save HWID in db for multiaccount detection hwAllowed = userUtils.logHardware(userID, clientData, firstLogin) # This is false only if HWID is empty # if HWID is banned, we get restricted so there's no # need to deny bancho access if not hwAllowed: raise exceptions.haxException() # Log user IP userUtils.logIP(userID, requestIP) # Log user osuver kotrikhelper.setUserLastOsuVer(userID, osuVersion) # Delete old tokens for that user and generate a new one isTournament = "tourney" in osuVersion if not isTournament: glob.tokens.deleteOldTokens(userID) responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset, tournament=isTournament, ignoreDM=ignoreDM) responseTokenString = responseToken.token # Check restricted mode (and eventually send message) responseToken.checkRestricted() # Send message if donor expires soon # Silence the noise for perma-donor mode. if (not responseToken.privileges & privileges.ADMIN_MANAGE_BEATMAPS ) and responseToken.privileges & privileges.USER_DONOR: expireDate = userUtils.getDonorExpire(responseToken.userID) if expireDate - int(time.time()) <= 86400 * 3: expireDays = round((expireDate - int(time.time())) / 86400) expireIn = "{} days".format( expireDays) if expireDays > 1 else "less than 24 hours" responseToken.enqueue( serverPackets.notification( "Your donor tag expires in {}! When your donor tag expires, you won't have any of the donor privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Datenshi and you don't want to lose your donor privileges, you can donate again by clicking on 'Support us' on Datenshi's website." .format(expireIn))) # Deprecate telegram 2fa and send alert if userUtils.deprecateTelegram2Fa(userID): responseToken.enqueue( serverPackets.notification( "As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://ripple.moe. Thank you for your patience." )) # Set silence end UNIX time in token responseToken.silenceEndTime = userUtils.getSilenceEnd(userID) # Get only silence remaining seconds silenceSeconds = responseToken.getSilenceSecondsLeft() # Get supporter/GMT userGMT = False if not userUtils.isRestricted(userID): userSupporter = True else: userSupporter = False userTournament = False if responseToken.admin: userGMT = True if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF: userTournament = True # Server restarting check if glob.restarting: raise exceptions.banchoRestartingException() def lildemon_list_over_pp(): a = 'VANILLA RELAX'.split() b = 'std taiko ctb mania'.split() c = [] for d in range(len(a)): m = [userUtils.getUserStats, userUtils.getUserStatsRx][d] for e in range(len(b)): j = userUtils.noPPLimit(userID, e, d) if d == 1 and e == 3: continue f, g, h, i = userUtils.obtainPPLimit(userID, e, relax=(d == 1), modded=False) k = j & 2 l = m(userID, e) if l['pp'] >= i and not k: c.append((a[d], b[e], l['pp'], i)) return c # Little demon lildemon_overpp = lildemon_list_over_pp() if lildemon_overpp: responseToken.enqueue( serverPackets.notification( "Hi fellow little demon! Did you forgot to submit yourself to the guild? I'm afraid you may not be able to advance with it." )) responseToken.enqueue( serverPackets.notification( "To consider further little demon training, here I jotted down some of your achievements!\n\n{}" .format("\n".join("{}:{} {:d}/{:d}".format(*ldop) for ldop in lildemon_overpp)))) # Send login notification before maintenance message if glob.banchoConf.config["loginNotification"] != "": responseToken.enqueue( serverPackets.notification( glob.banchoConf.config["loginNotification"])) # Maintenance check if glob.banchoConf.config["banchoMaintenance"]: if not userGMT: # We are not mod/admin, delete token, send notification and logout glob.tokens.deleteToken(responseTokenString) raise exceptions.banchoMaintenanceException() else: # We are mod/admin, send warning notification and continue responseToken.enqueue( serverPackets.notification( "Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode." )) # BAN CUSTOM CHEAT CLIENTS # 0Ainu = First Ainu build # b20190326.2 = Ainu build 2 (MPGH PAGE 10) # b20190401.22f56c084ba339eefd9c7ca4335e246f80 = Ainu Aoba's Birthday Build # b20191223.3 = Unknown Ainu build? (Taken from most users osuver in cookiezi.pw) # b20190226.2 = hqOsu (hq-af) def anticheat_boot(clientName, banNotif): log.info("Account ID {} tried to use {}!".format( userID, clientName)) def webhook_time(restrict=False): webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]") webhook.set_title( title="Catched some cheater Account ID {}".format(userID)) if restrict: webhook.set_desc( f' tried to use {clientName} and got restricted!') else: webhook.set_desc(f' tried to use {clientName}!') log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() if responseToken.admin: responseToken.enqueue( serverPackets.notification( "Kamu ngapain pake {}? stress.".format(clientName))) elif userUtils.isRestricted(userID): responseToken.enqueue(serverPackets.notification(banNotif)) #if glob.conf.config["discord"]["enable"] == True: webhook_time() else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) #if glob.conf.config["discord"]["enable"] == True: webhook_time(restrict=True) raise exceptions.loginCheatClientsException() pass if glob.conf.extra["mode"]["anticheat"]: # Ainu Client 2020 update if tornadoRequest.request.headers.get("ainu") == "happy": anticheat_boot( 'Ainu Client 2020', "You're banned because you're currently using Ainu Client... Happy New Year 2020 and Enjoy your restriction :)" ) # Ainu Client 2019 elif aobaHelper.getOsuVer(userID) in [ "0Ainu", "b20190326.2", "b20190401.22f56c084ba339eefd9c7ca4335e246f80", "b20191223.3" ]: anticheat_boot( 'Ainu Client', "You're banned because you're currently using Ainu Client. Enjoy your restriction :)" ) # hqOsu elif aobaHelper.getOsuVer(userID) == "b20190226.2": anticheat_boot( 'hqOsu', "Trying to use hqOsu in here? Well... No, sorry. We don't allow cheats here. Go play https://cookiezi.pw or others cheat server." ) # Send all needed login packets responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds)) responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue( serverPackets.userSupporterGMT(userSupporter, userGMT, userTournament)) responseToken.enqueue(serverPackets.userPanel(userID, True)) responseToken.enqueue(serverPackets.userStats(userID, True)) # Channel info end (before starting!?! wtf bancho?) responseToken.enqueue(serverPackets.channelInfoEnd()) # Default opened channels if True: for channelAutoJoin in glob.db.fetchAll( 'SELECT bcc.privilege_bit AS pb, bc.name AS cn FROM bancho_client_channels as bcc JOIN bancho_channels AS bc ON bcc.channel_id = bc.id' ): if responseToken.admin or responseToken.privileges & ( 1 << channelAutoJoin['pb']): chat.joinChannel(token=responseToken, channel=channelAutoJoin['cn']) else: chat.joinChannel(token=responseToken, channel="#osu") chat.joinChannel(token=responseToken, channel="#announce") chat.joinChannel(token=responseToken, channel="#ranked-now") # Join admin channel if we are an admin if responseToken.admin or responseToken.privileges & privileges.ADMIN_MANAGE_BEATMAPS: chat.joinChannel(token=responseToken, channel="#admin") # Output channels info for key, value in glob.channels.channels.items(): if responseToken.admin or (value.publicRead and not value.hidden): responseToken.enqueue(serverPackets.channelInfo(key)) # Send friends list responseToken.enqueue(serverPackets.friendList(userID)) # Send main menu icon if glob.banchoConf.config["menuIcon"] != "": responseToken.enqueue( serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) # Send online users' panels with glob.tokens: for _, token in glob.tokens.tokens.items(): if not token.restricted: responseToken.enqueue(serverPackets.userPanel( token.userID)) # Get location and country from ip.zxq.co or database if glob.localize: # Get location and country from IP latitude, longitude = locationHelper.getLocation(requestIP) countryLetters = locationHelper.getCountry(requestIP) country = countryHelper.getCountryID(countryLetters) else: # Set location to 0,0 and get country from db log.warning("Location skipped") latitude = 0 longitude = 0 countryLetters = "XX" country = countryHelper.getCountryID(userUtils.getCountry(userID)) # Set location and country responseToken.setLocation(latitude, longitude) responseToken.country = country # Set country in db if user has no country (first bancho login) if userUtils.getCountry(userID) == "XX": userUtils.setCountry(userID, countryLetters) # Send to everyone our userpanel if we are not restricted or tournament if not responseToken.restricted: glob.streams.broadcast("main", serverPackets.userPanel(userID)) # Set reponse data to right value and reset our queue responseData = responseToken.queue responseToken.resetQueue() except exceptions.loginFailedException: # Login failed error packet # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() except exceptions.invalidArgumentsException: # Invalid POST data # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() responseData += serverPackets.notification( "I see what you're doing...") except exceptions.loginBannedException: # Login banned error packet responseData += serverPackets.loginBanned() except exceptions.loginLockedException: # Login banned error packet responseData += serverPackets.loginLocked() except exceptions.loginCheatClientsException: # Banned for logging in with cheats responseData += serverPackets.loginCheats() except exceptions.banchoMaintenanceException: # Bancho is in maintenance mode responseData = bytes() if responseToken is not None: responseData = responseToken.queue responseData += serverPackets.notification( "Our bancho server is in maintenance mode. Please try to login again later." ) responseData += serverPackets.loginFailed() except exceptions.banchoRestartingException: # Bancho is restarting responseData += serverPackets.notification( "Bancho is restarting. Try again in a few minutes.") responseData += serverPackets.loginFailed() except exceptions.need2FAException: # User tried to log in from unknown IP responseData += serverPackets.needVerification() except exceptions.haxException: # Using oldoldold client, we don't have client data. Force update. # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.forceUpdate() responseData += serverPackets.notification( "Hory shitto, your client is TOO old! Nice prehistory! Please update the client from settings!" ) except: log.error("Unknown error!\n```\n{}\n{}```".format( sys.exc_info(), traceback.format_exc())) finally: # Console and discord log if len(loginData) < 3: log.info( "Invalid bancho login request from **{}** (insufficient POST data)" .format(requestIP), "bunker") # Return token string and data return responseTokenString, responseData
def handle(tornadoRequest): country = userID = osuVersion = requestIP = "" atitude = longitude = 0 clientData = [] # Data to return responseToken = None responseTokenString = "ayy" responseData = bytes() def saveLoginRecord(status, note=""): userUtils.saveLoginRecord(userID, osuVersion, requestIP, status, countryHelper.getCountryLetters(country), latitude, longitude, clientData=clientData, note=note) # Get IP from tornado request requestIP = tornadoRequest.getRequestIP() # Avoid exceptions clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"] osuVersion = "unknown" # Split POST body so we can get username/password/hardware data loginData = tornadoRequest.request.body.decode('utf-8')[:-1].split('\n') try: # Make sure loginData is valid if len(loginData) < 3: raise exceptions.invalidArgumentsException() # Get HWID, MAC address and more # Structure (new line = "|", already split) # [0] osu! version # [1] plain mac addressed, separated by "." # [2] mac addresses hash set # [3] unique ID # [4] disk ID splitData = loginData[2].split("|") osuVersion = splitData[0] osuVersionID = "".join( filter(str.isdigit, (osuVersion.split(".") or [""])[0])) or 0 timeOffset = int(splitData[1]) clientData = splitData[3].split(":")[:5] # old client? if len(clientData) < 4: raise exceptions.forceUpdateException() # self client? selfClient = len([ i for i in glob.conf.config["client"]["buildnames"].replace( " ", "").split(",") if i and i in osuVersion ]) > 0 # smaller than minversion: refuse login if selfClient and osuVersionID < glob.conf.config["client"][ "minversion"]: raise exceptions.forceUpdateException() # Try to get the ID from username username = str(loginData[0]) userID = userUtils.getID(username) if not userID: # Invalid username raise exceptions.loginFailedException() if not userUtils.checkLogin(userID, loginData[1]): # Invalid password raise exceptions.loginFailedException() # Make sure we are not banned or locked priv = userUtils.getPrivileges(userID) if userUtils.isBanned( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginBannedException() if userUtils.isLocked( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginLockedException() # 2FA check if userUtils.check2FA(userID, requestIP): log.warning("Need 2FA check for user {}".format(loginData[0])) raise exceptions.need2FAException() # No login errors! # Verify this user (if pending activation) firstLogin = False if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware( userID): if userUtils.verifyUser(userID, clientData): # Valid account log.info("Account {} verified successfully!".format(userID)) glob.verifiedCache[str(userID)] = 1 firstLogin = True else: # Multiaccount detected log.info("Account {} NOT verified!".format(userID)) glob.verifiedCache[str(userID)] = 0 raise exceptions.loginBannedException() # Save HWID in db for multiaccount detection hwAllowed = userUtils.logHardware(userID, clientData, firstLogin) # This is false only if HWID is empty # if HWID is banned, we get restricted so there's no # need to deny bancho access if not hwAllowed: raise exceptions.haxException() # Log user IP userUtils.logIP(userID, requestIP) # Log user osuver kotrikhelper.setUserLastOsuVer(userID, osuVersion) log.info("User {}({}) login, client ver: {}, ip: {}".format( username, userID, osuVersion, requestIP)) # Delete old tokens for that user and generate a new one isTournament = "tourney" in osuVersion if not isTournament: glob.tokens.deleteOldTokens(userID) responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset, tournament=isTournament) responseTokenString = responseToken.token # Check restricted mode (and eventually send message) responseToken.checkRestricted() # Send message if donor expires soon if responseToken.privileges & privileges.USER_DONOR > 0: responseToken.enqueue(serverPackets.notification("欢迎您,高贵的撒泼特")) #expireDate = userUtils.getDonorExpire(responseToken.userID) #if expireDate-int(time.time()) <= 86400*3: # expireDays = round((expireDate-int(time.time()))/86400) # expireIn = "{} days".format(expireDays) if expireDays > 1 else "less than 24 hours" # responseToken.enqueue(serverPackets.notification("Your donor tag expires in {}! When your donor tag expires, you won't have any of the donor privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Ripple and you don't want to lose your donor privileges, you can donate again by clicking on 'Support us' on Ripple's website.".format(expireIn))) # Deprecate telegram 2fa and send alert if userUtils.deprecateTelegram2Fa(userID): responseToken.enqueue( serverPackets.notification( "As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://ripple.moe. Thank you for your patience." )) # If the client version used is lower than stable, but still greater than minversion: tip if selfClient and osuVersionID < glob.conf.config["client"][ "stableversion"]: responseToken.enqueue( serverPackets.notification( "客户端有更新!请到osu!Kafuu官网:https://old.kafuu.pro 或 官方群(955377404)下载并使用最新客户端。\n不过您可以继续使用此客户端,直到它过期(可能很快)。所以请您最好尽快升级。" )) # Set silence end UNIX time in token responseToken.silenceEndTime = userUtils.getSilenceEnd(userID) # Get only silence remaining seconds silenceSeconds = responseToken.getSilenceSecondsLeft() # Get supporter/GMT userGMT = False if not userUtils.isRestricted(userID): userSupporter = True else: userSupporter = False userTournament = False if responseToken.admin: userGMT = True if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0: userTournament = True # Server restarting check if glob.restarting: raise exceptions.banchoRestartingException() # Send login notification before maintenance message loginNotification = glob.banchoConf.config["loginNotification"] #creating notification OnlineUsers = int( glob.redis.get("ripple:online_users").decode("utf-8")) Notif = "- Online Users: {}\n- {}".format( OnlineUsers, loginNotification ) # - {random.choice(glob.banchoConf.config['Quotes'])} responseToken.enqueue(serverPackets.notification(Notif)) # Maintenance check if glob.banchoConf.config["banchoMaintenance"]: if not userGMT: # We are not mod/admin, delete token, send notification and logout glob.tokens.deleteToken(responseTokenString) raise exceptions.banchoMaintenanceException() else: # We are mod/admin, send warning notification and continue responseToken.enqueue( serverPackets.notification( "Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode." )) # BAN CUSTOM CHEAT CLIENTS # 0Ainu = First Ainu build # b20190326.2 = Ainu build 2 (MPGH PAGE 10) # b20190401.22f56c084ba339eefd9c7ca4335e246f80 = Ainu Aoba's Birthday Build # b20191223.3 = Unknown Ainu build? (Taken from most users osuver in cookiezi.pw) # b20190226.2 = hqOsu (hq-af) if True: # Ainu Client 2020 update if tornadoRequest.request.headers.get("ainu") == "happy": log.info(f"Account {userID} tried to use Ainu Client 2020!") if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "Ainu client... Really? Welp enjoy your ban! -Realistik" )) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) raise exceptions.loginCheatClientsException() # Ainu Client 2019 elif aobaHelper.getOsuVer(userID) in [ "0Ainu", "b20190326.2", "b20190401.22f56c084ba339eefd9c7ca4335e246f80", "b20191223.3" ]: log.info(f"Account {userID} tried to use Ainu Client!") if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "Ainu client... Really? Welp enjoy your ban! -Realistik" )) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) raise exceptions.loginCheatClientsException() # hqOsu elif aobaHelper.getOsuVer(userID) == "b20190226.2": log.info(f"Account {userID} tried to use hqOsu!") if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "Trying to use hqOsu in here? Well... No, sorry. We don't allow cheats here. Go play on Aminosu." )) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) raise exceptions.loginCheatClientsException() #hqosu legacy elif aobaHelper.getOsuVer(userID) == "b20190716.5": log.info(f"Account {userID} tried to use hqOsu legacy!") if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "Trying to play with HQOsu Legacy? Cute...")) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) raise exceptions.loginCheatClientsException() # Send all needed login packets responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds)) responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue( serverPackets.userSupporterGMT(userSupporter, userGMT, userTournament)) responseToken.enqueue(serverPackets.userPanel(userID, True)) responseToken.enqueue(serverPackets.userStats(userID, True)) # Channel info end (before starting!?! wtf bancho?) responseToken.enqueue(serverPackets.channelInfoEnd()) # Default opened channels # TODO: Configurable default channels chat.joinChannel(token=responseToken, channel="#osu") chat.joinChannel(token=responseToken, channel="#announce") # Join admin channel if we are an admin if responseToken.admin: chat.joinChannel(token=responseToken, channel="#admin") # Output channels info for key, value in glob.channels.channels.items(): if value.publicRead and not value.hidden: responseToken.enqueue(serverPackets.channelInfo(key)) # Send friends list responseToken.enqueue(serverPackets.friendList(userID)) # Send main menu icon if glob.banchoConf.config["menuIcon"] != "": responseToken.enqueue( serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) # Send online users' panels with glob.tokens: for _, token in glob.tokens.tokens.items(): if not token.restricted: responseToken.enqueue(serverPackets.userPanel( token.userID)) # Get location and country from ip.zxq.co or database if glob.localize: # Get location and country from IP latitude, longitude = locationHelper.getLocation(requestIP) countryLetters = locationHelper.getCountry(requestIP) country = countryHelper.getCountryID(countryLetters) else: # Set location to 0,0 and get country from db log.warning("Location skipped") countryLetters = "XX" country = countryHelper.getCountryID(userUtils.getCountry(userID)) saveLoginRecord("success") # Set location and country responseToken.setLocation(latitude, longitude) responseToken.country = country # Set country in db if user has no country (first bancho login) if userUtils.getCountry(userID) == "XX": userUtils.setCountry(userID, countryLetters) # Send to everyone our userpanel if we are not restricted or tournament if not responseToken.restricted: glob.streams.broadcast("main", serverPackets.userPanel(userID)) # Set reponse data to right value and reset our queue responseData = responseToken.queue responseToken.resetQueue() except exceptions.loginFailedException: # Login failed error packet # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() saveLoginRecord("failed", note="Error packet") except exceptions.invalidArgumentsException: # Invalid POST data # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() responseData += serverPackets.notification( "I see what you're doing...") saveLoginRecord("failed", note="Invalid POST data") except exceptions.loginBannedException: # Login banned error packet responseData += serverPackets.loginBanned() saveLoginRecord("failed", note="Banned") except exceptions.loginLockedException: # Login banned error packet responseData += serverPackets.loginLocked() saveLoginRecord("failed", note="Locked") except exceptions.loginCheatClientsException: # Banned for logging in with cheats responseData += serverPackets.loginCheats() saveLoginRecord("failed", note="Logging with cheats") except exceptions.banchoMaintenanceException: # Bancho is in maintenance mode responseData = bytes() if responseToken != None: responseData = responseToken.queue responseData += serverPackets.notification( "Our bancho server is in maintenance mode. Please try to login again later." ) responseData += serverPackets.loginFailed() saveLoginRecord("failed", note="Bancho is in maintenance mode") except exceptions.banchoRestartingException: # Bancho is restarting responseData += serverPackets.notification( "Bancho is restarting. Try again in a few minutes.") responseData += serverPackets.loginFailed() saveLoginRecord("failed", note="Bancho is restarting") except exceptions.need2FAException: # User tried to log in from unknown IP responseData += serverPackets.needVerification() saveLoginRecord("failed", note="Need 2FA") except exceptions.forceUpdateException: # Using oldoldold client, we don't have client data. Force update. # (we don't use enqueue because we don't have a token since login has failed) # responseData += serverPackets.forceUpdate() responseData += serverPackets.notification( "您当前所使用的客户端({})太旧了,请到osu!Kafuu官网:https://old.kafuu.pro 或 官方群(955377404)下载并使用最新客户端登录。" .format(osuVersion)) saveLoginRecord("failed", note="Too old client: {}".format(osuVersion)) except exceptions.haxException: responseData += serverPackets.notification("what") responseData += serverPackets.loginFailed() saveLoginRecord("failed", note="Not HWinfo") except: log.error("Unknown error!\n```\n{}\n{}```".format( sys.exc_info(), traceback.format_exc())) saveLoginRecord("failed", note="unknown error") finally: # Console and discord log if len(loginData) < 3: log.info( "Invalid bancho login request from **{}** (insufficient POST data)" .format(requestIP), "bunker") saveLoginRecord("failed", note="insufficient POST data") # Return token string and data print(responseData) return responseTokenString, responseData
def asyncGet(self): try: # Get request ip ip = self.getRequestIP() # Argument check if not requestsManager.checkArguments(self.request.arguments, ["u", "h"]): raise exceptions.invalidArgumentsException(MODULE_NAME) # Get user ID username = self.get_argument("u") userID = userUtils.getID(username) if userID is None: self.write("error: pass\n") return # Check login log.info("{} ({}) wants to connect".format(username, userID)) if not userUtils.checkLogin(userID, self.get_argument("h"), ip): self.write("error: pass\n") return # Ban check if userUtils.isBanned(userID): return # Lock check if userUtils.isLocked(userID): return # 2FA check if userUtils.check2FA(userID, ip): self.write("error: verify\n") # Update latest activity userUtils.updateLatestActivity(userID) if "x" in self.request.arguments: if len(self.get_argument("x")) > 4: ''' When "x" is found in the arguments, it means two things, 1. "Monitor" has just been triggered (desktop screenshot """"""anticheat"""""") 2. Files named "LL" (used by *a certain cheat website* for login data) have been found on the users computer. This should *NEVER* happen, but just incase it does, i'll send a notification to the discord. ''' webhook = Webhook( glob.conf.config["discord"] ["ahook"], #send shit to discord hq color=0xc32c74, footer="stupid anticheat") if glob.conf.config["discord"]["enable"]: webhook.set_title( title=f"Catched some cheater {username} ({userID})" ) webhook.set_desc( f'They just tried to send bancho_monitor and they have LL files!' ) webhook.set_footer(text="peppycode anticheat") webhook.post() # Get country and output it country = glob.db.fetch( "SELECT country FROM users_stats WHERE id = %s", [userID])["country"] self.write(country) except exceptions.invalidArgumentsException: pass except exceptions.loginFailedException: self.write("error: pass\n") except exceptions.userBannedException: pass except exceptions.userLockedException: pass except exceptions.need2FAException: self.write("error: verify\n")