def connect(self): try: self.sio.connect(self.sioServer) log.info('socketio server connected: {}'.format(self.sio.sid)) except Exception as err: log.warning('socketio connect failed, err: {}'.format(err))
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 handle(userToken, packetData): # Get usertoken data userID = userToken.userID username = userToken.username # Make sure we are not banned #if userUtils.isBanned(userID): # userToken.enqueue(serverPackets.loginBanned()) # return # Send restricted message if needed #if userToken.restricted: # userToken.checkRestricted(True) # Change action packet packetData = clientPackets.userActionChange(packetData) # If we are not in spectate status but we're spectating someone, stop spectating ''' if userToken.spectating != 0 and userToken.actionID != actions.WATCHING and userToken.actionID != actions.IDLE and userToken.actionID != actions.AFK: userToken.stopSpectating() # If we are not in multiplayer but we are in a match, part match if userToken.matchID != -1 and userToken.actionID != actions.MULTIPLAYING and userToken.actionID != actions.MULTIPLAYER and userToken.actionID != actions.AFK: userToken.partMatch() ''' # Update cached stats if our pp changed if we've just submitted a score or we've changed gameMode #if (userToken.actionID == actions.PLAYING or userToken.actionID == actions.MULTIPLAYING) or (userToken.pp != userUtils.getPP(userID, userToken.gameMode)) or (userToken.gameMode != packetData["gameMode"]): # Update cached stats if we've changed gamemode if userToken.gameMode != packetData["gameMode"]: userToken.gameMode = packetData["gameMode"] userToken.updateCachedStats() # Always update action id, text, md5 and beatmapID userToken.actionID = packetData["actionID"] #userToken.actionID = packetData["actionText"] userToken.actionMd5 = packetData["actionMd5"] userToken.actionMods = packetData["actionMods"] userToken.beatmapID = packetData["beatmapID"] if bool(packetData["actionMods"] & 128) == True: userToken.relaxing = True userToken.autopiloting = False if userToken.actionID in (0, 1, 14): UserText = packetData["actionText"] + "on Relax" else: UserText = packetData["actionText"] + " on Relax" userToken.actionText = UserText userToken.updateCachedStats() """ if userToken.relaxAnnounce == False: userToken.relaxAnnounce = True userToken.enqueue(serverPackets.notification("You're playing with Relax, we've changed the leaderboard to Relax.")) """ #autopiloten elif bool(packetData["actionMods"] & 8192) == True: userToken.autopiloting = True userToken.relaxing = False if userToken.actionID in (0, 1, 14): UserText = packetData["actionText"] + "on Autopilot" else: UserText = packetData["actionText"] + " on Autopilot" userToken.actionText = UserText userToken.updateCachedStats() else: userToken.relaxing = False userToken.autopiloting = False UserText = packetData["actionText"] userToken.actionText = UserText userToken.updateCachedStats() if userToken.relaxAnnounce == True: userToken.relaxAnnounce = False userToken.enqueue( serverPackets.notification( "You've disabled relax. We've changed back to the Regular leaderboard." )) glob.db.execute("UPDATE users_stats SET current_status = %s WHERE id = %s", [UserText, userID]) # Enqueue our new user panel and stats to us and our spectators recipients = [userToken] if len(userToken.spectators) > 0: for i in userToken.spectators: if i in glob.tokens.tokens: recipients.append(glob.tokens.tokens[i]) for i in recipients: if i is not None: # Force our own packet force = True if i == userToken else False i.enqueue(serverPackets.userPanel(userID, force)) i.enqueue(serverPackets.userStats(userID, force)) # Console output log.info("{} changed action: {} [{}][{}][{}]".format( username, str(userToken.actionID), userToken.actionText, userToken.actionMd5, userToken.beatmapID))
def sendMessage(sender="", target="", message="", token=None, toIRC=True): """ Send a message to osu!bancho and IRC server :param sender: sender username. Optional. token can be used instead :param target: receiver channel (if starts with #) or username :param message: text of the message :param token: sender token object. Optional. sender can be used instead :param toIRC: if True, send the message to IRC. If False, send it to Bancho only. Default: True :return: 0 if joined or other IRC code in case of error. Needed only on IRC-side """ reencode = lambda s: s.encode('latin-1').decode('utf-8') try: #tokenString = "" # Get token object if not passed if token is None: token = glob.tokens.getTokenFromUsername(sender) if token is None: raise exceptions.userNotFoundException() else: # token object alredy passed, get its string and its username (sender) sender = token.username #tokenString = token.token # Make sure this is not a tournament client # if token.tournament: # raise exceptions.userTournamentException() # Make sure the user is not in restricted mode if token.restricted: raise exceptions.userRestrictedException() # Make sure the user is not silenced if token.isSilenced(): raise exceptions.userSilencedException() if target in chatChannels.RESERVED_CHANNELS: sendMessage(glob.BOT_NAME, token.username, "\x01ACTION gaplok kamu\x01") token.enqueue(serverPackets.notification("You have been gaplok'd")) return 403 # Bancho style. isAPI = sender == glob.BOT_NAME and message.startswith( "\x02YOHANE ") and message.endswith("\x02") isSPub = target.startswith( f"{chatChannels.SPECTATOR_PREFIX}_") or target.startswith( f"{chatChannels.MULTIPLAYER_PREFIX}_") or target in ( chatChannels.SPECTATOR_MASK, chatChannels.MULTIPLAYER_MASK) forBot = target.lower() == glob.BOT_NAME.lower() botCommand = (target[0] == '#' and message[0] == '!') or forBot if isAPI: message = message[8:-1] if botCommand and not isAPI: redirectBot = not (token.admin or isSPub or target.lower() == chatChannels.SPECIAL_CHANNEL) if target[0] == '#' or forBot: pass if redirectBot: target = glob.BOT_NAME log.info( f'Redirect {redirectBot} ({token.admin}/{isSPub}) -> {target}({forBot}) -> {message}' ) if message.lower().split()[0] in ('!help', '!report'): target = glob.BOT_NAME # Determine internal name if needed # (toclient is used clientwise for #multiplayer and #spectator channels) target, toClient = channelMasking(token, target, userID=token.userID) if target is None and toClient is None: return 0 isChannel = target.startswith("#") # Make sure the message is valid if not message.strip(): raise exceptions.invalidArgumentsException() # Truncate message if > 2048 characters message = message[:2048] + "..." if len(message) > 2048 else message # Check for word filters message = glob.chatFilters.filterMessage(message) # Build packet bytes packet = serverPackets.sendMessage(token.username, toClient, message) # Send the message to IRC if glob.irc and toIRC: messageSplitInLines = reencode(message).split("\n") for line in messageSplitInLines: if line == messageSplitInLines[:1] and line == "": continue glob.ircServer.banchoMessage(sender, target, line) # Spam protection (ignore the bot) if token.userID > 1 and not token.admin: token.spamProtection() # File and discord logs (public chat only) eligibleLogging = target.startswith("#") and not ( message.startswith("\x01ACTION is playing") and target.startswith(f"{chatChannels.SPECTATOR_PREFIX}_")) # this one is to mark "!" usage, as those are thrown directly to the bot. eligibleLogging = eligibleLogging or (target == glob.BOT_NAME and not forBot) if eligibleLogging: log.chat("{sender} @ {target}: {message}".format( sender=token.username, target=target, message=reencode(message))) glob.schiavo.sendChatlog( "**{sender} @ {target}:** {message}".format( sender=token.username, target=target, message=reencode(message))) # Send the message if isChannel: # CHANNEL # Make sure the channel exists if target not in glob.channels.channels: raise exceptions.channelUnknownException() # Make sure the channel is not in moderated mode if glob.channels.channels[target].moderated and not token.admin: raise exceptions.channelModeratedException() # Make sure we are in the channel if target not in token.joinedChannels: # I'm too lazy to put and test the correct IRC error code here... # but IRC is not strict at all so who cares raise exceptions.channelNoPermissionsException() # Make sure we have write permissions if not (token.admin or glob.channels.channels[target].publicWrite): raise exceptions.channelNoPermissionsException() # Add message in buffer token.addMessageInBuffer(target, message) # Everything seems fine, build recipients list and send packet glob.streams.broadcast("chat/{}".format(target), packet, but=[token.token]) else: # USER # Make sure recipient user is connected recipientToken = glob.tokens.getTokenFromUsername(target) if recipientToken is None: raise exceptions.userNotFoundException() # Make sure the recipient is not a tournament client if recipientToken.tournament: raise exceptions.userTournamentException() if recipientToken.ignoreDM: targetFriends = userUtils.getFriendList(recipientToken.userID) if sender != glob.BOT_NAME and token.userID not in targetFriends and not token.admin: packet = serverPackets.userDeniedMessage( token.username, toClient, message) token.enqueue(packet) raise exceptions.userBlockedPrivateException() # Make sure the recipient is not restricted or we are the bot if recipientToken.restricted and sender.lower( ) != glob.BOT_NAME.lower(): raise exceptions.userRestrictedException() # TODO: Make sure the recipient has not disabled PMs for non-friends or he's our friend # Away check if recipientToken.awayCheck(token.userID): sendMessage( target, sender, "\x01ACTION is away: {}\x01".format( recipientToken.awayMessage)) # Check message templates (mods/admins only) if message in messageTemplates.templates and token.admin: sendMessage(sender, target, messageTemplates.templates[message]) # Everything seems fine, send packet recipientToken.enqueue(packet) # Bot must not react to their own message. if sender == glob.BOT_NAME and not isAPI: return 0 # Some bot message if (isChannel or target == glob.BOT_NAME): if botCommand or isAPI: msgOffset = 0 if forBot else 1 da10Message = fokabot.fokabotCommands(token, token.username, target, message[msgOffset:]) else: da10Message = fokabot.fokabotResponse(token, token.username, target, message) if da10Message: sendMessage(glob.BOT_NAME, target if isChannel else sender, da10Message) return 0 except exceptions.userSilencedException: token.enqueue( serverPackets.silenceEndTime(token.getSilenceSecondsLeft())) log.warning("{} tried to send a message during silence".format( token.username)) return 404 except exceptions.channelModeratedException: log.warning( "{} tried to send a message to a channel that is in moderated mode ({})" .format(token.username, target)) return 404 except exceptions.channelUnknownException: log.warning( "{} tried to send a message to an unknown channel ({})".format( token.username, target)) return 403 except exceptions.channelNoPermissionsException: log.warning( "{} tried to send a message to channel {}, but they have no write permissions" .format(token.username, target)) return 404 except exceptions.userRestrictedException: log.warning( "{} tried to send a message {}, but the recipient is in restricted mode" .format(token.username, target)) return 404 except exceptions.userTournamentException: log.warning( "{} tried to send a message {}, but the recipient is a tournament client" .format(token.username, target)) return 404 except exceptions.userNotFoundException: log.warning("User not connected to IRC/Bancho") return 401 except exceptions.userBlockedPrivateException: return 404 except exceptions.invalidArgumentsException: log.warning("{} tried to send an invalid message to {}".format( token.username, target)) return 404
consoleHelper.printColored( "[!] Error while starting Datadog client! Please check your config.ini and run the server again", bcolors.RED) # IRC start message and console output glob.irc = generalUtils.stringToBool(glob.conf.config["irc"]["enable"]) if glob.irc: # IRC port ircPort = 0 try: ircPort = int(glob.conf.config["irc"]["port"]) except ValueError: consoleHelper.printColored( "[!] Invalid IRC port! Please check your config.ini and run the server again", bcolors.RED) log.info("IRC server started!") consoleHelper.printColored( "> IRC server listening on 127.0.0.1:{}...".format(ircPort), bcolors.GREEN) threading.Thread( target=lambda: ircserver.main(port=ircPort)).start() else: consoleHelper.printColored("[!] Warning! IRC server is disabled!", bcolors.YELLOW) # Server port serverPort = 0 try: serverPort = int(glob.conf.config["server"]["port"]) except ValueError: consoleHelper.printColored(
def __init__(self, matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID, isTourney=False): """ Create a new match object :param matchID: match progressive identifier :param matchName: match name, string :param matchPassword: match md5 password. Leave empty for no password :param beatmapID: beatmap ID :param beatmapName: beatmap name, string :param beatmapMD5: beatmap md5 hash, string :param gameMode: game mode ID. See gameModes.py :param hostUserID: user id of the host """ self.matchID = matchID self.streamName = "multi/{}".format(self.matchID) self.playingStreamName = "{}/playing".format(self.streamName) self.inProgress = False self.mods = 0 self.matchName = matchName self.matchPassword = matchPassword self.beatmapID = beatmapID self.beatmapName = beatmapName self.beatmapMD5 = beatmapMD5 self.hostUserID = hostUserID self.refs = [] self.tourneyHost = -1 self.gameMode = gameMode self.matchScoringType = matchScoringTypes.SCORE # default values self.matchTeamType = matchTeamTypes.HEAD_TO_HEAD # default value self.matchModMode = matchModModes.NORMAL # default value self.seed = 0 self.matchDataCache = bytes() self.isTourney = isTourney self.isLocked = False # if True, users can't change slots/teams. Used in tourney matches self.isStarting = False self._lock = threading.Lock() self.createTime = int(time.time()) self.games = [] # Create all slots and reset them self.slots = [] for _ in range(0, 16): self.slots.append(slot()) # Create streams glob.streams.add(self.streamName) glob.streams.add(self.playingStreamName) # Create #multiplayer channel glob.channels.addHiddenChannel("#multi_{}".format(self.matchID)) log.info("MPROOM{}: {} match created!".format( self.matchID, "Tourney" if self.isTourney else "Normal"))
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="what a bitch [ 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="what a bitch [ 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="what a bitch [ 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="what a bitch [ 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 other cheating server." )) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="what a bitch [ 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="What a bitch [ 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) 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 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 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")
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 start(self): """ Start IRC server main loop :return: """ # Sentry sentryClient = None if glob.sentry: sentryClient = raven.Client(glob.conf.config["sentry"]["ircdns"]) serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: serversocket.bind(("0.0.0.0", self.port)) except socket.error as e: log.error("[IRC] Could not bind port {}:{}".format(self.port, e)) sys.exit(1) serversocket.listen(5) lastAliveCheck = time.time() # Main server loop while True: try: (iwtd, owtd, ewtd) = select.select( [serversocket] + [x.socket for x in self.clients.values()], [ x.socket for x in self.clients.values() if x.writeBufferSize() > 0 ], [], 1) # Handle incoming connections for x in iwtd: if x in self.clients: self.clients[x].readSocket() else: (conn, addr) = x.accept() try: self.clients[conn] = Client(self, conn) log.info( "[IRC] Accepted connection from {}:{}".format( addr[0], addr[1])) except socket.error: try: conn.close() except: pass # Handle outgoing connections for x in owtd: if x in self.clients: # client may have been disconnected self.clients[x].writeSocket() # Make sure all IRC clients are still connected now = time.time() if lastAliveCheck + 10 < now: for client in list(self.clients.values()): client.checkAlive() lastAliveCheck = now except: log.error("[IRC] Unknown error!\n```\n{}\n{}```".format( sys.exc_info(), traceback.format_exc())) if glob.sentry and sentryClient is not None: sentryClient.captureException()
def handle(userToken, packetData): # Read new settings packetData = clientPackets.changeMatchSettings(packetData) # Get match ID matchID = userToken.matchID # Make sure the match exists if matchID not in glob.matches.matches: return # Host check with glob.matches.matches[matchID] as match: if userToken.userID != match.hostUserID: return # Some dank memes easter egg memeTitles = [ "OWC 2020", "AC is a duck", "Dank memes", "1337ms Ping", "Iscriviti a Xenotoze", "...e i marò?", "Superman dies", "The brace is on fire", "print_foot()", "#FREEZEBARKEZ", "osu!thailand devs are actually cats", "Thank Mr Shaural", "NEVER GIVE UP", "T I E D W I T H U N I T E D", "HIGHEST HDHR LOBBY OF ALL TIME", "This is gasoline and I set myself on fire", "Everyone is cheating apparently", "Kurwa mac", "TATOE", "This is not your drama landfill.", "I like cheese", "AOBA IS NOT A CAT HE IS A DO(N)G", "YAMI NI NOMARE YO" ] # Set match name match.matchName = packetData["matchName"] if packetData["matchName"] != "meme" else random.choice(memeTitles) # Update match settings # NOBODY SHOULD INTRUDE A SINGLE PLAYER MATCH AFTER ALL (tempfix) inProgress = bool(packetData["inProgress"]) slotTaken = [slot for slot in match.slots if slot.status & slotStatuses.OCCUPIED] slotPlay = [slot for slot in slotTaken if slot.status & slotStatuses.PLAYING] # log.info("MPROOM{}: ProgPkt({}) SlotPlay({} left)".format(match.matchID,inProgress,len(slotPlay))) if len(slotTaken) <= 1: match.inProgress = False else: match.inProgress = len(slotPlay) > 0 and inProgress if packetData["matchPassword"] != "": match.matchPassword = generalUtils.stringMd5(packetData["matchPassword"]) else: match.matchPassword = "" match.beatmapName = packetData["beatmapName"] match.beatmapID = packetData["beatmapID"] match.hostUserID = packetData["hostUserID"] match.gameMode = packetData["gameMode"] oldBeatmapMD5 = match.beatmapMD5 oldMods = match.mods oldMatchTeamType = match.matchTeamType match.mods = packetData["mods"] match.beatmapMD5 = packetData["beatmapMD5"] match.matchScoringType = packetData["scoringType"] match.matchTeamType = packetData["teamType"] match.matchModMode = packetData["multiSpecial"] # Reset ready if needed if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5: match.resetReady() # Reset mods if needed if match.matchModMode & matchModModes.FREE_MOD: # TODO: Implement a FREEMOD barrier configuration # which works as follows: # - turn off all FM mods on main mods # - turn only FM mods on personal/player's free mods # match.mods = 0 if match.freeModHandler == matchFreeModTypes.datenshi: for i, slot in zip(range(len(match.slots)), match.slots): slot.mods = packetData['slot{}Mods'.format(i)] else: match.mods = match.mods & ~mods.FM for i, slot in zip(range(len(match.slots)), match.slots): #if match.hostUserID == 3: # log.info("MPROOM{} - Slot {} Mods {}".format(match.matchID, i + 1, slot.mods)) slot.mods = packetData['slot{}Mods'.format(i)] & mods.FM else: # Synchronize mods if it's no FM for i, slot in zip(range(len(match.slots)), match.slots): slot.mods = match.mods match.seed = packetData['seed'] # Initialize teams if team type changed if match.matchTeamType != oldMatchTeamType: match.initializeTeams() # Force no freemods if tag coop # if match.matchTeamType == matchTeamTypes.TAG_COOP or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS: # match.matchModMode = matchModModes.NORMAL # Send updated settings match.sendUpdates() # Console output log.info("MPROOM{}: Updated room settings".format(match.matchID))
def handleDisconnect(self): self.sio.disconnect() log.info('socketio server disconnect!') log.info('try to reconnecting...') self.connect() log.info('socketio server reconnected!')
def autorankCheck(beatmap, recursive=True): # No autorank check for frozen maps if beatmap.rankedStatusFrozen not in (0, 3): return # No autorank check for already ranked beatmap # Only handle this if the map is ranked by autorank. if beatmap.rankedStatusFrozen != 3 and beatmap.rankedStatus >= 2: return # Only check if the map is autorankable. if not autorankFlagOK(beatmap.beatmapID): return # Check recursiveness if recursive: beatmap_id_query = glob.db.fetchAll( 'select beatmap_md5, beatmap_id from beatmaps where beatmapset_id = %s', [beatmap.beatmapSetID]) for beatmap_id_data in beatmap_id_query: # Skip self. if int(beatmap_id_data['beatmap_id']) == beatmap.beatmapID: continue siblingmap = bm.beatmap(None, beatmap.beatmapSetID) siblingmap.setData(beatmap_id_data['beatmap_md5'], beatmap.beatmapSetID, True) # Not checking maps with non updated date. #if recursive: #log.info(f"Checking {beatmap.artist} - {beatmap.title} for autorank eligiblity.") # log.info(f"Checking {beatmap.songName} for autorank eligiblity.") obtainDateTime = lambda t: datetime.datetime.strptime( t, "%Y-%m-%d %H:%M:%S") obtainUnixClock = lambda t: int(time.mktime(t.timetuple())) if beatmap.updateDate == 0: log.info(f"Updating {beatmap.fileMD5} data") data = osuapiHelper.osuApiRequest('get_beatmaps', 'h={}'.format(beatmap.fileMD5)) if not data: return dateTouch = obtainDateTime(data['last_update']) beatmap.updateDate = obtainUnixClock(dateTouch) else: dateTouch = datetime.datetime.fromtimestamp(beatmap.updateDate) dateNow = datetime.datetime.today() dateQualify = dateTouch + datetime.timedelta(days=GRAVEYARD_DAYS - QUALIFIED_DAYS) dateRanked = dateTouch + datetime.timedelta(days=GRAVEYARD_DAYS) forLove = autorankFlagForLove(beatmap.beatmapID) rankStatus = beatmap.rankedStatus needWipe = False if dateNow >= dateRanked: needWipe = rankStatus == rankedStatuses.QUALIFIED if forLove: log.debug(f"Considering {beatmap.fileMD5} to be loved") beatmap.rankedStatus = rankedStatuses.LOVED else: log.debug(f"Considering {beatmap.fileMD5} on ranking") beatmap.rankedStatus = rankedStatuses.RANKED beatmap.rankedStatusFrozen = 3 elif dateNow >= dateQualify and not forLove: log.debug(f"Considering {beatmap.fileMD5} for qualified") beatmap.rankedStatus = rankedStatuses.QUALIFIED beatmap.rankedStatusFrozen = 3 else: needWipe = rankStatus >= rankedStatuses.RANKED beatmap.rankedStatus = rankedStatuses.PENDING beatmap.rankedStatusFrozen = 0 if rankStatus != beatmap.rankedStatus: dbBeatmap = glob.db.fetch( 'select ranked_status_freezed as f from beatmaps where beatmap_md5 = %s', [beatmap.fileMD5]) oldFreeze = 0 if dbBeatmap is not None: oldFreeze = dbBeatmap['f'] glob.db.execute( 'update beatmaps set ranked_status_freezed = 0 where beatmap_md5 = %s', [beatmap.fileMD5]) if oldFreeze != beatmap.rankedStatusFrozen and recursive: autorankAnnounce(beatmap) glob.db.execute( 'update beatmaps set rankedby = %s where beatmap_md5 = %s', [autorankUserID(beatmap.beatmapID), beatmap.fileMD5]) if needWipe: log.info(f"Wiping {beatmap.fileMD5} leaderboard") beatmap.clearLeaderboard() 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 ) == True and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginBannedException() if userUtils.isLocked( userID ) == True 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 userUtils.hasVerifiedHardware( userID) == False: 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))) # 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") chat.joinChannel(token=responseToken, channel="#nowranked") # 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 == True and value.hidden == False: 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 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( "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 asyncGet(self): statusCode = 400 data = {"message": "unknown error"} try: # Check arguments if not requestsManager.checkArguments(self.request.arguments, ["b"]): raise exceptions.invalidArgumentsException(MODULE_NAME) # Get beatmap ID and make sure it's a valid number beatmapID = self.get_argument("b") if not beatmapID.isdigit(): raise exceptions.invalidArgumentsException(MODULE_NAME) # Get mods if "m" in self.request.arguments: modsEnum = self.get_argument("m") if not modsEnum.isdigit(): raise exceptions.invalidArgumentsException(MODULE_NAME) modsEnum = int(modsEnum) else: modsEnum = 0 # Get game mode if "g" in self.request.arguments: gameMode = self.get_argument("g") if not gameMode.isdigit(): raise exceptions.invalidArgumentsException(MODULE_NAME) gameMode = int(gameMode) else: gameMode = 0 # Get acc if "a" in self.request.arguments: accuracy = self.get_argument("a") try: accuracy = float(accuracy) except ValueError: raise exceptions.invalidArgumentsException(MODULE_NAME) else: accuracy = -1.0 # Print message log.info("Requested pp for beatmap {}".format(beatmapID)) """ Peppy >:( # Get beatmap md5 from osuapi # TODO: Move this to beatmap object osuapiData = osuapiHelper.osuApiRequest("get_beatmaps", "b={}".format(beatmapID)) if osuapiData is None or "file_md5" not in osuapiData or "beatmapset_id" not in osuapiData: raise exceptions.invalidBeatmapException(MODULE_NAME) beatmapMd5 = osuapiData["file_md5"] beatmapSetID = osuapiData["beatmapset_id"] """ dbData = glob.db.fetch( "SELECT beatmap_md5, beatmapset_id FROM beatmaps WHERE beatmap_id = {}" .format(beatmapID)) if dbData is None: raise exceptions.invalidBeatmapException(MODULE_NAME) beatmapMd5 = dbData["beatmap_md5"] beatmapSetID = dbData["beatmapset_id"] # Create beatmap object bmap = beatmap.beatmap(beatmapMd5, beatmapSetID) # Check beatmap length if bmap.hitLength < 0: raise exceptions.beatmapTooLongException(MODULE_NAME) returnPP = [] if gameMode == gameModes.STD and bmap.starsStd == 0: # Mode Specific beatmap, auto detect game mode if bmap.starsTaiko > 0: gameMode = gameModes.TAIKO if bmap.starsCtb > 0: gameMode = gameModes.CTB if bmap.starsMania > 0: gameMode = gameModes.MANIA # Calculate pp if gameMode == gameModes.STD or gameMode == gameModes.TAIKO: # Std pp if accuracy < 0 and modsEnum == 0: # Generic acc # Get cached pp values cachedPP = bmap.getCachedTillerinoPP() if cachedPP != [0, 0, 0, 0]: log.debug("Got cached pp.") returnPP = cachedPP else: log.debug( "Cached pp not found. Calculating pp with oppai..." ) # Cached pp not found, calculate them oppai = rippoppai.oppai(bmap, mods=modsEnum, tillerino=True) returnPP = oppai.pp bmap.starsStd = oppai.stars # Cache values in DB log.debug("Saving cached pp...") if type(returnPP) == list and len(returnPP) == 4: bmap.saveCachedTillerinoPP(returnPP) else: # Specific accuracy, calculate # Create oppai instance log.debug( "Specific request ({}%/{}). Calculating pp with oppai..." .format(accuracy, modsEnum)) oppai = rippoppai.oppai(bmap, mods=modsEnum, tillerino=True) bmap.starsStd = oppai.stars if accuracy > 0: returnPP.append(calculatePPFromAcc(oppai, accuracy)) else: returnPP = oppai.pp else: raise exceptions.unsupportedGameModeException() # Data to return data = { "song_name": bmap.songName, "pp": [round(x, 2) for x in returnPP] if type(returnPP) == list else returnPP, "length": bmap.hitLength, "stars": bmap.starsStd, "ar": bmap.AR, "bpm": bmap.bpm, } # Set status code and message statusCode = 200 data["message"] = "ok" except exceptions.invalidArgumentsException: # Set error and message statusCode = 400 data["message"] = "missing required arguments" except exceptions.invalidBeatmapException: statusCode = 400 data["message"] = "beatmap not found" except exceptions.beatmapTooLongException: statusCode = 400 data["message"] = "requested beatmap is too long" except exceptions.unsupportedGameModeException: statusCode = 400 data["message"] = "Unsupported gamemode" finally: # Add status code to data data["status"] = statusCode # Debug output log.debug(str(data)) # Send response #self.clear() self.write(json.dumps(data)) self.set_header("Content-Type", "application/json") self.set_status(statusCode)
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 osu_scores.*, phpbb_users.username AS uname FROM osu_scores LEFT JOIN phpbb_users ON osu_scores.user_id = phpbb_users.user_id WHERE osu_scores.score_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 allPlayersCompleted(self): """ Cleanup match stuff and send match end packet to everyone :return: """ # Collect some info about the match that just ended to send to the api infoToSend = { "id": self.matchID, "name": self.matchName, "beatmap_id": self.beatmapID, "mods": self.mods, "game_mode": self.gameMode, "host_id": self.hostUserID, "host_user_name": userUtils.getUsername(self.hostUserID), "game_type": self.matchTeamType, "game_score_condition": self.matchScoringType, "game_mod_mode": self.matchModMode, "scores": {} } # Add score info for each player for i in range(0, 16): if self.slots[i].user is not None and self.slots[ i].status == slotStatuses.PLAYING: infoToSend["scores"][glob.tokens.tokens[ self.slots[i].user].userID] = { "score": self.slots[i].score, "mods": self.slots[i].mods, "failed": self.slots[i].failed, "pass": self.slots[i].passed, "team": self.slots[i].team, "username": userUtils.getUsername(glob.tokens.tokens[ self.slots[i].user].userID) # vinse shit ;3 } # Send the info to the api glob.redis.publish("api:mp_complete_match", json.dumps(infoToSend)) self.games.append(json.dumps(infoToSend)) # Reset inProgress self.inProgress = False # Reset slots self.resetSlots() # Send match update self.sendUpdates() # Send match complete glob.streams.broadcast(self.streamName, serverPackets.matchComplete()) # Destroy playing stream glob.streams.dispose(self.playingStreamName) glob.streams.remove(self.playingStreamName) # Console output log.info("MPROOM{}: Match completed".format(self.matchID)) # If this is a tournament match, then we send a notification in the chat # saying that the match has completed. chanName = "#multi_{}".format(self.matchID) if self.isTourney and (chanName in glob.channels.channels): chat.sendMessage(glob.BOT_NAME, chanName, "Match has just finished.")
def asyncGet(self): statusCode = 400 data = {"message": "unknown error"} try: # Check arguments if not requestsManager.checkArguments(self.request.arguments, ["b"]): raise exceptions.invalidArgumentsException(MODULE_NAME) # Get beatmap ID and make sure it's a valid number beatmapID = self.get_argument("b") if not beatmapID.isdigit(): raise exceptions.invalidArgumentsException(MODULE_NAME) # Get mods if "m" in self.request.arguments: modsEnum = self.get_argument("m") if not modsEnum.isdigit(): raise exceptions.invalidArgumentsException(MODULE_NAME) modsEnum = int(modsEnum) else: modsEnum = 0 # Get game mode if "g" in self.request.arguments: gameMode = self.get_argument("g") if not gameMode.isdigit(): raise exceptions.invalidArgumentsException(MODULE_NAME) gameMode = int(gameMode) else: gameMode = 0 # Get acc if "a" in self.request.arguments: accuracy = self.get_argument("a") try: accuracy = float(accuracy) except ValueError: raise exceptions.invalidArgumentsException(MODULE_NAME) else: accuracy = None # Print message log.info("Requested pp for beatmap {}".format(beatmapID)) # Get beatmap md5 from osuapi # TODO: Move this to beatmap object osuapiData = osuapiHelper.osuApiRequest("get_beatmaps", "b={}".format(beatmapID)) if osuapiData is None or "file_md5" not in osuapiData or "beatmapset_id" not in osuapiData: raise exceptions.invalidBeatmapException(MODULE_NAME) beatmapMd5 = osuapiData["file_md5"] beatmapSetID = osuapiData["beatmapset_id"] # Create beatmap object bmap = beatmap.beatmap(beatmapMd5, beatmapSetID) # Check beatmap length if bmap.hitLength > 900: raise exceptions.beatmapTooLongException(MODULE_NAME) if gameMode == gameModes.STD and bmap.starsStd == 0: # Mode Specific beatmap, auto detect game mode if bmap.starsTaiko > 0: gameMode = gameModes.TAIKO if bmap.starsCtb > 0: gameMode = gameModes.CTB if bmap.starsMania > 0: gameMode = gameModes.MANIA # Calculate pp if gameMode in (gameModes.STD, gameModes.TAIKO): # osu!standard and osu!taiko oppai = ez.Ez(bmap, mods_=modsEnum, tillerino=accuracy is None, acc=accuracy, gameMode=gameMode) bmap.starsStd = oppai.stars returnPP = oppai.pp elif gameMode == gameModes.CTB: # osu!catch ciccio = cicciobello.Cicciobello(bmap, mods_=modsEnum, tillerino=accuracy is None, accuracy=accuracy) bmap.starsStd = ciccio.stars returnPP = ciccio.pp else: raise exceptions.unsupportedGameModeException() # Data to return data = { "song_name": bmap.songName, "pp": [x for x in returnPP] if type(returnPP) is list else returnPP, "game_mode": gameMode, "length": bmap.hitLength, "stars": bmap.starsStd, "ar": bmap.AR, "bpm": bmap.bpm, } # Set status code and message statusCode = 200 data["message"] = "ok" except exceptions.invalidArgumentsException: # Set error and message statusCode = 400 data["message"] = "missing required arguments" except exceptions.invalidBeatmapException: statusCode = 400 data["message"] = "beatmap not found" except exceptions.beatmapTooLongException: statusCode = 400 data["message"] = "requested beatmap is too long" except exceptions.unsupportedGameModeException: statusCode = 400 data["message"] = "Unsupported gamemode" finally: # Add status code to data data["status"] = statusCode # Debug output log.debug(str(data)) # Send response self.write(json.dumps(data)) self.set_header("Content-Type", "application/json") self.set_status(statusCode)
def partChannel(userID=0, channel="", token=None, toIRC=True, kick=False, force=False): """ Part a channel :param userID: user ID of the user that parts the channel. Optional. token can be used instead. :param token: user token object of user that parts the channel. Optional. userID can be used instead. :param channel: channel name :param toIRC: if True, send this channel join event to IRC. Must be true if joining from bancho. Optional. Default: True :param kick: if True, channel tab will be closed on client. Used when leaving lobby. Optional. Default: False :param force: whether to allow game clients to part #spect_ and #multi_ channels :return: 0 if joined or other IRC code in case of error. Needed only on IRC-side """ try: # Make sure the client is not drunk and sends partChannel when closing a PM tab if not channel.startswith("#"): return # Get token if not defined if token is None: token = glob.tokens.getTokenFromUserID(userID) # Make sure the token exists if token is None: raise exceptions.userNotFoundException() else: token = token # Determine internal/client name if needed # (toclient is used clientwise for #multiplayer and #spectator channels) channel, channelClient = channelMasking(token, channel, userID=userID) if channel is None and channelClient is None: return 0 # Make sure the channel exists if channel not in glob.channels.channels: raise exceptions.channelUnknownException() # Make sure a game client is not trying to join a #multi_ or #spect_ channel manually channelObject = glob.channels.channels[channel] if channelObject.isSpecial and not token.irc and not force: raise exceptions.channelUnknownException() # Make sure the user is in the channel if channel not in token.joinedChannels: raise exceptions.userNotInChannelException() # Part channel (token-side and channel-side) token.partChannel(channelObject) # Delete temporary channel if everyone left if "chat/{}".format(channelObject.name) in glob.streams.streams: if channelObject.temp and len(glob.streams.streams[ "chat/{}".format(channelObject.name)].clients) - 1 == 0: glob.channels.removeChannel(channelObject.name) # Force close tab if needed # NOTE: Maybe always needed, will check later if kick: token.enqueue(serverPackets.channelKicked(channelClient)) # IRC part if glob.irc and toIRC: glob.ircServer.banchoPartChannel(token.username, channel) # Console output log.info("{} parted channel {} ({})".format(token.username, channel, channelClient)) # Return IRC code return 0 except exceptions.channelUnknownException: log.warning("{} attempted to part an unknown channel ({})".format( token.username, channel)) return 403 except exceptions.userNotInChannelException: log.warning( "{} attempted to part {}, but he's not in that channel".format( token.username, channel)) return 442 except exceptions.userNotFoundException: log.warning("User not connected to IRC/Bancho") return 442 # idk
def allPlayersCompleted(self): """ Cleanup match stuff and send match end packet to everyone :return: """ # Collect some info about the match that just ended to send to the api infoToSend = { "id": self.matchID, "name": self.matchName, "beatmap_id": self.beatmapID, "mods": self.mods, "game_mode": self.gameMode, "scores": {} } # Add score info for each player for i in range(0,16): if self.slots[i].user is not None and self.slots[i].status == slotStatuses.PLAYING: infoToSend["scores"][glob.tokens.tokens[self.slots[i].user].userID] = { "score": self.slots[i].score, "mods": self.slots[i].mods, "failed": self.slots[i].failed, "pass": self.slots[i].passed, "team": self.slots[i].team } # Send the info to the api glob.redis.publish("api:mp_complete_match", json.dumps(infoToSend)) # Reset inProgress self.inProgress = False # Reset slots self.resetSlots() # Send match update self.sendUpdates() # Send match complete glob.streams.broadcast(self.streamName, serverPackets.matchComplete()) # Destroy playing stream glob.streams.dispose(self.playingStreamName) glob.streams.remove(self.playingStreamName) # Console output log.info("MPROOM{}: Match completed".format(self.matchID)) # Set vinse id if needed chanName = "#multi_{}".format(self.matchID) if self.vinseID is None: self.vinseID = (int(time.time()) // (60 * 15)) << 32 | self.matchID chat.sendMessage(glob.BOT_NAME, chanName, "Match history available [{} here]".format( "https://multi.bigtu.vip/match/{}".format(self.vinseID) )) if not self.bloodcatAlert: chat.sendMessage( glob.BOT_NAME, chanName, "and uh... in case you're playing unranked or broken maps " "that are now available through ripple's osu!direct, you can " "type '!bloodcat' in the chat to get a download link for the " "currently selected map from Bloodcat! If osu!direct is not working, " "You can still use '!beatconnect' as a mirror too! " ) self.bloodcatAlert = True # If this is a tournament match, then we send a notification in the chat # saying that the match has completed. if self.isTourney and (chanName in glob.channels.channels): chat.sendMessage(glob.BOT_NAME, chanName, "Match has just finished.")
def joinChannel(userID=0, channel="", token=None, toIRC=True, force=False): """ Join a channel :param userID: user ID of the user that joins the channel. Optional. token can be used instead. :param token: user token object of user that joins the channel. Optional. userID can be used instead. :param channel: channel name :param toIRC: if True, send this channel join event to IRC. Must be true if joining from bancho. Default: True :param force: whether to allow game clients to join #spect_ and #multi_ channels :return: 0 if joined or other IRC code in case of error. Needed only on IRC-side """ try: # Get token if not defined if token is None: token = glob.tokens.getTokenFromUserID(userID) # Make sure the token exists if token is None: raise exceptions.userNotFoundException else: token = token # Normal channel, do check stuff # Make sure the channel exists if channel in chatChannels.RESERVED_CHANNELS: sendMessage(glob.BOT_NAME, token.username, "\x01ACTION gaplok kamu\x01") token.enqueue(serverPackets.notification("You have been gaplok'd")) return 403 if channel not in glob.channels.channels: raise exceptions.channelUnknownException() # Make sure a game client is not trying to join a #multi_ or #spect_ channel manually channelObject = glob.channels.channels[channel] if channelObject.isSpecial and not token.irc and not force and not token.admin: raise exceptions.channelUnknownException() # Add the channel to our joined channel token.joinChannel(channelObject) # Send channel joined (IRC) if glob.irc and not toIRC: glob.ircServer.banchoJoinChannel(token.username, channel) # Console output log.info("{} joined channel {}".format(token.username, channel)) # IRC code return return 0 except exceptions.channelNoPermissionsException: log.warning( "{} attempted to join channel {}, but they have no read permissions" .format(token.username, channel)) return 403 except exceptions.channelUnknownException: log.warning("{} attempted to join an unknown channel ({})".format( token.username, channel)) return 403 except exceptions.userAlreadyInChannelException: log.warning("User {} already in channel {}".format( token.username, channel)) return 403 except exceptions.userNotFoundException: log.warning("User not connected to IRC/Bancho") return 403 # idk
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) # 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() # 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 perks are about to run out in less than 24 hours! All perks associated with your donor perks will be gone within the next 24 hours. If you don't want this, you may want to buy donor again! However, if you are a staff member, ignore this message!" .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() # 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) if glob.conf.extra["mode"]["anticheat"]: # Ainu Client 2020 update if tornadoRequest.request.headers.get("ainu") == "happy": log.info( "Account {} tried to use Ainu Client 2020!".format(userID)) if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification("f**k off thanks")) else: glob.tokens.deleteToken(userID) userUtils.ban(userID) raise exceptions.loginCheatClientsException() # Ainu Client 2019 elif aobaHelper.getOsuVer(userID) in [ "0Ainu", "b20190326.2", "b20190401.22f56c084ba339eefd9c7ca4335e246f80", "b20191223.3" ]: log.info("Account {} tried to use Ainu Client!".format(userID)) if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification("f**k off thanks")) else: glob.tokens.deleteToken(userID) userUtils.ban(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") 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( "Update your client! We aren't in 1990 mate.") 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(userToken, packetData): # Read new settings packetData = clientPackets.changeMatchSettings(packetData) # Get match ID matchID = userToken.matchID # Make sure the match exists if matchID not in glob.matches.matches: return # Host check with glob.matches.matches[matchID] as match: if userToken.userID != match.hostUserID: return # Some dank memes easter egg memeTitles = [ "OWC 2020", "AC is a duck", "Dank memes", "1337ms Ping", "Iscriviti a Xenotoze", "...e i marò?", "Superman dies", "The brace is on fire", "print_foot()", "#FREEZEBARKEZ", "osu!thailand devs are actually cats", "Thank Mr Shaural", "NEVER GIVE UP", "T I E D W I T H U N I T E D", "HIGHEST HDHR LOBBY OF ALL TIME", "This is gasoline and I set myself on fire", "Everyone is cheating apparently", "Kurwa mac", "TATOE", "This is not your drama landfill.", "I like cheese", "AOBA IS NOT A CAT HE IS A DO(N)G", "Datingu startuato" ] # Set match name match.matchName = packetData["matchName"] if packetData[ "matchName"] != "meme" else random.choice(memeTitles) # Update match settings match.inProgress = packetData["inProgress"] if packetData["matchPassword"] != "": match.matchPassword = generalUtils.stringMd5( packetData["matchPassword"]) else: match.matchPassword = "" match.beatmapName = packetData["beatmapName"] match.beatmapID = packetData["beatmapID"] match.hostUserID = packetData["hostUserID"] match.gameMode = packetData["gameMode"] oldBeatmapMD5 = match.beatmapMD5 oldMods = match.mods oldMatchTeamType = match.matchTeamType match.mods = packetData["mods"] match.beatmapMD5 = packetData["beatmapMD5"] match.matchScoringType = packetData["scoringType"] match.matchTeamType = packetData["teamType"] match.matchModMode = packetData["freeMods"] # Reset ready if needed if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5: match.resetReady() # Reset mods if needed if match.matchModMode == matchModModes.NORMAL: # Reset slot mods if not freeMods match.resetMods() else: # Reset match mods if freemod match.mods = 0 # Initialize teams if team type changed if match.matchTeamType != oldMatchTeamType: match.initializeTeams() # Force no freemods if tag coop if match.matchTeamType == matchTeamTypes.TAG_COOP or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS: match.matchModMode = matchModModes.NORMAL # Send updated settings match.sendUpdates() # Console output log.info("MPROOM{}: Updated room settings".format(match.matchID))
def asyncGet(self): statusCode = 400 data = {"message": "unknown error"} try: # Check arguments if not requestsManager.checkArguments(self.request.arguments, ["b"]): raise exceptions.invalidArgumentsException(MODULE_NAME) # Get beatmap ID and make sure it's a valid number beatmapID = self.get_argument("b") if not beatmapID.isdigit(): raise exceptions.invalidArgumentsException(MODULE_NAME) # Get mods if "m" in self.request.arguments: modsEnum = self.get_argument("m") if not modsEnum.isdigit(): raise exceptions.invalidArgumentsException(MODULE_NAME) modsEnum = int(modsEnum) else: modsEnum = 0 # Get game mode if "g" in self.request.arguments: gameMode = self.get_argument("g") if not gameMode.isdigit(): raise exceptions.invalidArgumentsException(MODULE_NAME) gameMode = int(gameMode) else: gameMode = 0 # Get acc if "a" in self.request.arguments: accuracy = self.get_argument("a") try: accuracy = float(accuracy) if math.isnan(accuracy): accuracy = -1.0 except ValueError: raise exceptions.invalidArgumentsException(MODULE_NAME) else: accuracy = -1.0 if "x" in self.request.arguments: misses = self.get_argument("x") try: misses = int(misses) except ValueError: raise exceptions.invalidArgumentsException(MODULE_NAME) else: misses = 0 if "c" in self.request.arguments: combo = self.get_argument("c") if not combo.isdigit(): raise exceptions.invalidArgumentsException(MODULE_NAME) combo = int(combo) else: combo = 0 # Print message log.info("Requested pp for beatmap {}".format(beatmapID)) # Get beatmap md5 from osuapi # TODO: Move this to beatmap object """osuapiData = osuapiHelper.osuApiRequest("get_beatmaps", "b={}".format(beatmapID)) if osuapiData is None or "file_md5" not in osuapiData or "beatmapset_id" not in osuapiData: raise exceptions.invalidBeatmapException(MODULE_NAME) beatmapMd5 = osuapiData["file_md5"] beatmapSetID = osuapiData["beatmapset_id"]""" dbData = glob.db.fetch("SELECT beatmap_md5, beatmapset_id, mode FROM beatmaps WHERE beatmap_id = {}".format(beatmapID)) if dbData is None: raise exceptions.invalidBeatmapException(MODULE_NAME) beatmapMd5 = dbData["beatmap_md5"] beatmapSetID = dbData["beatmapset_id"] # Create beatmap object bmap = beatmap.beatmap(beatmapMd5, beatmapSetID) stars = 0 # Check beatmap length if bmap.hitLength > 900: raise exceptions.beatmapTooLongException(MODULE_NAME) returnPP = [] if gameMode == gameModes.STD and bmap.starsStd == 0: gameMode = dbData["mode"] if(gameMode == 2): raise exceptions.unsupportedGameModeException if accuracy > 100 or combo > bmap.maxCombo or misses < 0 or math.isnan(accuracy): raise exceptions.invalidArgumentsException(MODULE_NAME) # Calculate pp if gameMode != 2: # Std pp if accuracy < 0 and combo == 0 and misses == 0 and gameMode == dbData["mode"]: # Generic acc # Get cached pp values cachedPP = bmap.getCachedTillerinoPP() if(modsEnum == 0 and cachedPP != [0,0,0,0]): log.debug("Got cached pp.") returnPP = cachedPP stars = bmap.starsStd else: log.debug("Cached pp not found. Calculating pp with oppai...") # Cached pp not found, calculate them if(gameMode == 1 or gameMode == 0): oppai = rippoppai.oppai(bmap, mods=modsEnum, tillerino=True, stars=True) returnPP = oppai.pp stars = oppai.stars else: xeno = omppcPy.piano(bmap, mods=modsEnum, tillerino=True, stars=True) returnPP = xeno.pp stars = xeno.stars # Cache values in DB log.debug("Saving cached pp...") if(modsEnum == 0 and type(returnPP) != int and len(returnPP) == 4): bmap.saveCachedTillerinoPP(returnPP) else: # Specific accuracy, calculate # Create oppai instance if(gameMode == 3): log.info("Specific request ({}%/{}). Calculating pp with omppc...".format(accuracy, modsEnum)) xeno = omppcPy.piano(bmap, acc=accuracy, mods=modsEnum, tillerino=False, stars=True) returnPP.append(xeno.pp) stars = xeno.stars else: if(gameMode != 0 and gameMode != 1): raise exceptions.unsupportedGameModeException log.debug("Specific request ({}%/{}). Calculating pp with oppai...".format(accuracy, modsEnum)) oppai = rippoppai.oppai(bmap, mods=modsEnum, tillerino=False, stars=True) stars = oppai.stars if accuracy > 0: oppai.acc = accuracy if combo > 0: oppai.combo = combo if misses > 0: oppai.misses = misses oppai.getPP() returnPP.append(oppai.pp) else: raise exceptions.unsupportedGameModeException # Data to return data = { "song_name": bmap.songName, "pp": returnPP, "length": round(bmap.hitLength*0.75) if modsEnum & mods.DOUBLETIME > 0 else bmap.hitLength if modsEnum & mods.HALFTIME < 1 else round(bmap.hitLength*1.50), "stars": stars, "ar": round(min(10, bmap.AR * 1.4),1) if modsEnum & mods.HARDROCK > 0 else bmap.AR if modsEnum & mods.EASY < 1 else round(max(0, bmap.AR / 2),1), "od": round(min(10, bmap.OD * 1.4),1) if modsEnum & mods.HARDROCK > 0 else bmap.OD if modsEnum & mods.EASY < 1 else round(max(0, bmap.OD / 2),1), "bpm": round(bmap.bpm*1.5) if modsEnum & mods.DOUBLETIME > 0 else bmap.bpm if modsEnum & mods.HALFTIME < 1 else round(bmap.bpm*0.75), } # Set status code and message statusCode = 200 data["message"] = "ok" except exceptions.invalidArgumentsException: # Set error and message statusCode = 400 data["message"] = "missing required arguments" except exceptions.invalidBeatmapException: statusCode = 400 data["message"] = "beatmap not found" except exceptions.beatmapTooLongException: statusCode = 400 data["message"] = "requested beatmap is too long" except exceptions.unsupportedGameModeException: statusCode = 400 data["message"] = "Unsupported gamemode" finally: # Add status code to data data["status"] = statusCode # Debug output log.debug(str(data)) # Send response #self.clear() self.write(json.dumps(data)) self.set_header("Content-Type", "application/json") self.set_status(statusCode)
def verifyUser(userID, hashes): """ Activate `userID`'s account. :param userID: user id :param hashes: Peppy's botnet (client data) 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 :return: True if verified successfully, else False (multiaccount) """ # Check for valid hash set for i in hashes[2:5]: if i == "": log.warning("Invalid hash set ({}) for user {} while verifying the account".format(str(hashes), userID), "bunk") return False # Get username username = getUsername(userID) # Make sure there are no other accounts activated with this exact mac/unique id/hwid if hashes[2] == "b4ec3c4334a0249dae95c284ec5983df" or hashes[4] == "ffae06fb022871fe9beb58b005c5e21d": # Running under wine, check only by uniqueid log.info("{user} ({userID}) ha triggerato Sannino:\n**Full data:** {hashes}\n**Usual wine mac address hash:** b4ec3c4334a0249dae95c284ec5983df\n**Usual wine disk id:** ffae06fb022871fe9beb58b005c5e21d".format(user=username, userID=userID, hashes=hashes), "bunker") log.debug("Veryfing with Linux/Mac hardware") match = glob.db.fetchAll("SELECT userid FROM hw_user WHERE unique_id = %(uid)s AND userid != %(userid)s AND activated = 1 LIMIT 1", { "uid": hashes[3], "userid": userID }) else: # Running under windows, full check log.debug("Veryfing with Windows hardware") match = glob.db.fetchAll("SELECT userid FROM hw_user WHERE mac = %(mac)s AND unique_id = %(uid)s AND disk_id = %(diskid)s AND userid != %(userid)s AND activated = 1 LIMIT 1", { "mac": hashes[2], "uid": hashes[3], "diskid": hashes[4], "userid": userID }) if match: # This is a multiaccount, restrict other account and ban this account # Get original userID and username (lowest ID) originalUserID = match[0]["userid"] originalUsername = getUsername(originalUserID) # Ban this user and append notes ban(userID) # this removes the USER_PENDING_VERIFICATION flag too appendNotes(userID, "{}'s multiaccount ({}), found HWID match while verifying account ({})".format(originalUsername, originalUserID, hashes[2:5])) appendNotes(originalUserID, "Has created multiaccount {} ({})".format(username, userID)) # Restrict the original restrict(originalUserID) # Discord message log.warning("User **{originalUsername}** ({originalUserID}) has been restricted because he has created multiaccount **{username}** ({userID}). The multiaccount has been banned.".format( originalUsername=originalUsername, originalUserID=originalUserID, username=username, userID=userID ), "cm") # Disallow login return False else: # No matches found, set USER_PUBLIC and USER_NORMAL flags and reset USER_PENDING_VERIFICATION flag resetPendingFlag(userID) #log.info("User **{}** ({}) has verified his account with hash set _{}_".format(username, userID, hashes[2:5]), "cm") # Allow login return True
def handle(userToken, packetData): # Get usertoken data userID = userToken.userID username = userToken.username # Make sure we are not banned #if userUtils.isBanned(userID): # userToken.enqueue(serverPackets.loginBanned()) # return # Send restricted message if needed #if userToken.restricted: # userToken.checkRestricted(True) # Change action packet packetData = clientPackets.userActionChange(packetData) # If we are not in spectate status but we're spectating someone, stop spectating ''' if userToken.spectating != 0 and userToken.actionID != actions.WATCHING and userToken.actionID != actions.IDLE and userToken.actionID != actions.AFK: userToken.stopSpectating() # If we are not in multiplayer but we are in a match, part match if userToken.matchID != -1 and userToken.actionID != actions.MULTIPLAYING and userToken.actionID != actions.MULTIPLAYER and userToken.actionID != actions.AFK: userToken.partMatch() ''' # Update cached stats if our pp changed if we've just submitted a score or we've changed gameMode #if (userToken.actionID == actions.PLAYING or userToken.actionID == actions.MULTIPLAYING) or (userToken.pp != userUtils.getPP(userID, userToken.gameMode)) or (userToken.gameMode != packetData["gameMode"]): # Update cached stats if we've changed gamemode #will delete this userToken.autopilot = packetData['actionMods'] & 8192 userToken.relax = packetData['actionMods'] & 128 if packetData['actionMods'] & 128: userToken.enqueue(serverPackets.notification('You switched to relax!')) if userToken.actionID in (0, 1, 14): userToken.actionText = packetData["actionText"] + " on Relax" else: userToken.actionText = packetData["actionText"] + " on Relax" userToken.updateCachedStatsRx() elif packetData['actionMods'] & 8192: userToken.enqueue( serverPackets.notification('You switched to autopilot!')) if userToken.actionID in (0, 1, 14): userToken.actionText = packetData["actionText"] + " on Autopilot" else: userToken.actionText = packetData["actionText"] + " on Autopilot" userToken.updateCachedStatsAp() else: userToken.enqueue( serverPackets.notification('You switched to vanilla!')) userToken.actionText = packetData["actionText"] userToken.updateCachedStats() if userToken.gameMode != packetData["gameMode"]: userToken.gameMode = packetData["gameMode"] userToken.updateCachedStats() # Always update action id, text, md5 and beatmapID userToken.actionID = packetData["actionID"] userToken.actionMd5 = packetData["actionMd5"] userToken.actionMods = packetData["actionMods"] userToken.beatmapID = packetData["beatmapID"] # Enqueue our new user panel and stats to us and our spectators recipients = [userToken] if len(userToken.spectators) > 0: for i in userToken.spectators: if i in glob.tokens.tokens: recipients.append(glob.tokens.tokens[i]) for i in recipients: if i is not None: # Force our own packet force = True if i == userToken else False i.enqueue(serverPackets.userPanel(userID, force)) i.enqueue(serverPackets.userStats(userID, force)) # Console output log.info("{} changed action: {} [{}][{}][{}]".format( username, str(userToken.actionID), userToken.actionText, userToken.actionMd5, userToken.beatmapID))
def _addComment(self): username = self.get_argument("u") target = self.get_argument("target", default=None) mode = int(self.get_argument("m", default=0)) specialFormat = self.get_argument("f", default=None) userID = userUtils.getID(username) # Technically useless if userID < 0: return # Get beatmap/set/score ids try: beatmapID = int(self.get_argument("b", default=0)) beatmapSetID = int(self.get_argument("s", default=0)) scoreID = int(self.get_argument("r", default=0)) except ValueError: raise exceptions.invalidArgumentsException(MODULE_NAME) # Add a comment, removing all illegal characters and trimming after 128 characters comment = self.get_argument("comment").replace("\r", "").replace( "\t", "").replace("\n", "")[:128] try: time_ = int(self.get_argument("starttime")) except ValueError: raise exceptions.invalidArgumentsException(MODULE_NAME) # Type of comment who = "normal" if target == "replay" and glob.db.fetch( "SELECT COUNT(*) AS c FROM scores WHERE id = %s AND userid = %s AND completed = 3", (scoreID, userID))["c"] > 0: # From player, on their score who = "player" elif userUtils.isInAnyPrivilegeGroup( userID, ("developer", "community manager", "bat")): # From BAT/Admin who = "admin" elif userUtils.isInPrivilegeGroup(userID, "donor"): # Supporter who = "donor" if target == "song": # Set comment if beatmapSetID <= 0: return value = beatmapSetID column = "beatmapset_id" elif target == "map": # Beatmap comment if beatmapID <= 0: return value = beatmapID column = "beatmap_id" elif target == "replay": # Score comment if scoreID <= 0: return value = scoreID column = "score_id" else: # Invalid target return # Make sure the user hasn't submitted another comment on the same map/set/song in a 5 seconds range if glob.db.fetch( "SELECT COUNT(*) AS c FROM comments WHERE user_id = %s AND {} = %s AND `time` BETWEEN %s AND %s" .format(column), (userID, value, time_ - 5000, time_ + 5000))["c"] > 0: return # Store the comment glob.db.execute( "INSERT INTO comments ({}, user_id, mode, comment, `time`, who, special_format) " "VALUES (%s, %s, %s, %s, %s, %s, %s)".format(column), (value, userID, mode, comment, time_, who, specialFormat)) log.info("Submitted {} ({}) comment, mode {}, user {}: '{}'".format( column, value, mode, userID, comment))
def asyncPost(self): try: if not requestsManager.checkArguments(self.request.arguments, [ "user[username]", "user[user_email]", "user[password]", "check" ]): return self.write("what are you doing here?") username = self.get_argument("user[username]") email = self.get_argument("user[user_email]") password = self.get_argument("user[password]") # Raw password accountCreated = self.get_argument("check") if accountCreated == "1": return self.write( '{"form_error":{"user":{"check":["Account already created."]}}}' ) emailCheck = glob.db.fetch("SELECT 1 FROM users WHERE email = %s", [email]) usernameCheck = glob.db.fetch( "SELECT 1 FROM users WHERE username = %s", [username]) if emailCheck != None: return self.write( '{"form_error":{"user":{"user_email":["Email address already used."]}}}' ) if usernameCheck != None or username.lower() in [ "peppy", "rrtyui", "cookiezi", "azer", "loctav", "banchobot", "happystick", "doomsday", "sharingan33", "andrea", "cptnxn", "reimu-desu", "hvick225", "_index", "my aim sucks", "kynan", "rafis", "sayonara-bye", "thelewa", "wubwoofwolf", "millhioref", "tom94", "tillerino", "clsw", "spectator", "exgon", "axarious", "angelsim", "recia", "nara", "emperorpenguin83", "bikko", "xilver", "vettel", "kuu01", "_yu68", "tasuke912", "dusk", "ttobas", "velperk", "jakads", "jhlee0133", "abcdullah", "yuko-", "entozer", "hdhr", "ekoro", "snowwhite", "osuplayer111", "musty", "nero", "elysion", "ztrot", "koreapenguin", "fort", "asphyxia", "niko", "shigetora" ]: return self.write( '{"form_error":{"user":{"username":["Username already used or it is forbidden."]}}}' ) if len(password) < 8 or len(password) > 32: return self.write( '{"form_error":{"user":{"password":["Password too short or long! (Password length must be more than 8 and less than 32)"]}}}' ) if "_" in username and " " in username: self.write( '{"form_error":{"user":{"username":["An username can not contain both underscores and spaces."]}}}' ) userID = int( glob.db.execute( "INSERT INTO users(username, username_safe, password_md5, salt, email, register_datetime, privileges, password_version) VALUES (%s, %s, %s, '', %s, %s, 1048576, 2)", [ username, userUtils.safeUsername(username), passwordUtils.genBcrypt( hashlib.md5(password.encode('utf-8')).hexdigest()), email, int(time.time()) ])) glob.db.execute( "INSERT INTO users_stats(id, username, user_color, user_style, ranked_score_std, playcount_std, total_score_std, ranked_score_taiko, playcount_taiko, total_score_taiko, ranked_score_ctb, playcount_ctb, total_score_ctb, ranked_score_mania, playcount_mania, total_score_mania) VALUES (%s, %s, 'black', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)", [userID, username]) glob.db.execute( "INSERT INTO rx_stats(id, username, user_color, user_style, ranked_score_std, playcount_std, total_score_std, ranked_score_taiko, playcount_taiko, total_score_taiko, ranked_score_ctb, playcount_ctb, total_score_ctb, ranked_score_mania, playcount_mania, total_score_mania) VALUES (%s, %s, 'black', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)", [userID, username]) log.info( "{} created their account using ingame registration.".format( username)) except Exception as e: log.error(e)