def anticheat_boot(clientName, banNotif): log.info("Account ID {} tried to use {}!".format( userID, clientName)) def webhook_time(restrict=False): webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]") webhook.set_title( title="Catched some cheater Account ID {}".format(userID)) if restrict: webhook.set_desc( f' tried to use {clientName} and got restricted!') else: webhook.set_desc(f' tried to use {clientName}!') log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() if responseToken.admin: responseToken.enqueue( serverPackets.notification( "Kamu ngapain pake {}? stress.".format(clientName))) elif userUtils.isRestricted(userID): responseToken.enqueue(serverPackets.notification(banNotif)) #if glob.conf.config["discord"]["enable"] == True: webhook_time() else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) #if glob.conf.config["discord"]["enable"] == True: webhook_time(restrict=True) raise exceptions.loginCheatClientsException() pass
def mp_invite(): userID = userUtils.getID(fro) if not can_user_touch_lobby(get_match_id_from_channel(chan), userID, True): return False if len(message) < 2: raise exceptions.invalidArgumentsException( "Wrong syntax: !mp invite <username>") username = message[1].strip() if not username: raise exceptions.invalidArgumentsException( "Please provide a username") userID = userUtils.getIDSafe(username) if userID is None: raise exceptions.userNotFoundException("No such user") token = glob.tokens.getTokenFromUserID(userID, ignoreIRC=True) if token is None: raise exceptions.invalidUserException( "That user is not connected to bancho right now.") _match = glob.matches.matches[get_match_id_from_channel(chan)] _match.invite(999, userID) token.enqueue( serverPackets.notification( "Please accept the invite you've just received from {} to " "enter your tourney match.".format(glob.BOT_NAME))) return "An invite to this match has been sent to {}".format(username)
def report(fro, chan, message): msg = "" try: # TODO: Rate limit # Get username, report reason and report info target, reason, additionalInfo = message[0], message[1], message[2] target = chat.fixUsernameForBancho(target) # Make sure the target is not foka if target == glob.BOT_NAME: raise exceptions.invalidUserException() # Make sure the user exists targetID = userUtils.getID(target) if targetID == 0: raise exceptions.userNotFoundException() # Make sure that the user has specified additional info if report reason is 'Other' if reason.lower() == "other" and not additionalInfo: raise exceptions.missingReportInfoException() # Get the token if possible chatlog = "" token = glob.tokens.getTokenFromUsername(userUtils.safeUsername(target), safe=True) if token is not None: chatlog = token.getMessagesBufferString() # Everything is fine, submit report glob.db.execute( "INSERT INTO reports (id, from_uid, to_uid, reason, chatlog, time, assigned) VALUES (NULL, %s, %s, %s, %s, %s, 0)", [userUtils.getID(fro), targetID, "{reason} - ingame {info}".format(reason=reason, info="({})".format( additionalInfo) if additionalInfo is not None else ""), chatlog, int(time.time())]) msg = "You've reported {target} for {reason}{info}. A Community Manager will check your report as soon as possible. Every !report message you may see in chat wasn't sent to anyone, so nobody in chat, but admins, know about your report. Thank you for reporting!".format( target=target, reason=reason, info="" if additionalInfo is None else " (" + additionalInfo + ")") adminMsg = "{user} has reported {target} for {reason} ({info})".format(user=fro, target=target, reason=reason, info=additionalInfo) # Log report in #admin and on discord chat.sendMessage(glob.BOT_NAME, "#admin", adminMsg) log.warning(adminMsg, discord="cm") except exceptions.invalidUserException: msg = "Hello, {} here! You can't report me. I won't forget what you've tried to do. Watch out.".format( glob.BOT_NAME) except exceptions.invalidArgumentsException: msg = "Invalid report command syntax. To report an user, click on it and select 'Report user'." except exceptions.userNotFoundException: msg = "The user you've tried to report doesn't exist." except exceptions.missingReportInfoException: msg = "Please specify the reason of your report." except: raise finally: if msg != "": token = glob.tokens.getTokenFromUsername(fro) if token is not None: if token.irc: chat.sendMessage(glob.BOT_NAME, fro, msg) else: token.enqueue(serverPackets.notification(msg)) return False
def handle(self, data): data = super().parseData(data) if data is None: return targetToken = glob.tokens.getTokenFromUserID(data["userID"]) if targetToken != None: targetToken.enqueue(serverPackets.notification(data["message"]))
def usersTimeoutCheckLoop(self): """ Start timed out users disconnect loop. This function will be called every `checkTime` seconds and so on, forever. CALL THIS FUNCTION ONLY ONCE! :return: """ log.debug("Checking timed out clients") timedOutTokens = [] # timed out users timeoutLimit = int(time.time()) - 100 for key, value in self.tokens.items(): # Check timeout (fokabot is ignored) if value.pingTime < timeoutLimit and value.userID != 999 and value.irc == False and value.tournament == False: # That user has timed out, add to disconnected tokens # We can't delete it while iterating or items() throws an error timedOutTokens.append(key) # Delete timed out users from self.tokens # i is token string (dictionary key) for i in timedOutTokens: log.debug("{} timed out!!".format(self.tokens[i].username)) self.tokens[i].enqueue( serverPackets.notification( "Your connection to the server timed out.")) logoutEvent.handle(self.tokens[i], None) del timedOutTokens # Schedule a new check (endless loop) threading.Timer(100, self.usersTimeoutCheckLoop).start()
def scheduleShutdown(sendRestartTime, restart, message="", delay=20): """ Schedule a server shutdown/restart :param sendRestartTime: time (seconds) to wait before sending server restart packets to every client :param restart: if True, server will restart. if False, server will shudown :param message: if set, send that message to every client to warn about the shutdown/restart :param delay: additional restart delay in seconds. Default: 20 :return: """ # Console output log.info("Pep.py will {} in {} seconds!".format( "restart" if restart else "shutdown", sendRestartTime + delay)) log.info("Sending server restart packets in {} seconds...".format( sendRestartTime)) # Send notification if set if message != "": glob.streams.broadcast("main", serverPackets.notification(message)) # Schedule server restart packet threading.Timer( sendRestartTime, glob.streams.broadcast, ["main", serverPackets.banchoRestart(delay * 2 * 1000)]).start() glob.restarting = True # Restart/shutdown if restart: action = restartServer else: action = shutdownServer # Schedule actual server shutdown/restart some seconds after server restart packet, so everyone gets it threading.Timer(sendRestartTime + delay, action).start()
def systemMaintenance(fro, chan, message): # Turn on/off bancho maintenance maintenance = True # Get on/off if len(message) >= 2: if message[1] == "off": maintenance = False # Set new maintenance value in bancho_settings table glob.banchoConf.setMaintenance(maintenance) if maintenance: # We have turned on maintenance mode # Users that will be disconnected who = [] # Disconnect everyone but mod/admins with glob.tokens: for _, value in glob.tokens.tokens.items(): if not value.admin: who.append(value.userID) glob.streams.broadcast("main", serverPackets.notification("Our bancho server is in maintenance mode. Please try to login again later.")) glob.tokens.multipleEnqueue(serverPackets.loginError(), who) msg = "The server is now in maintenance mode!" else: # We have turned off maintenance mode # Send message if we have turned off maintenance mode msg = "The server is no longer in maintenance mode!" # Chat output return msg
def asyncGet(self): statusCode = 400 data = {"message": "unknown error"} try: # Check arguments if not requestsManager.checkArguments(self.request.arguments, ["k", "to", "code"]): raise exceptions.invalidArgumentsException() # Check ci key key = self.get_argument("k") if key is None or key != glob.conf.config["server"]["cikey"]: raise exceptions.invalidArgumentsException() targetID = self.get_argument('to') if not targetID.isdigit() and int(targetID) < 2: raise exceptions.invalidArgumentsException() code = self.get_argument('code') if (code[0] == '_' and code[:1] == code[-1:]) or (code[0] == '_' and code[0] == code[1] and code[:2] == code[-2:]): raise exceptions.invalidArgumentsException() if code not in dir(specialErrorMessages): raise exceptions.invalidArgumentsException() force = False if 'force' in self.request.arguments: force = self.get_argument( 'force').lower() in '1 true yes'.split() msg = getattr(specialErrorMessages, code) for t in glob.tokens.getTokenFromUserID(targetID, ignoreIRC=True, _all=True): # Only send notice flag once per token if not force: if code in t.noticeFlags: continue t.noticeFlags.append(code) t.enqueue(serverPackets.notification(msg)) # Status code and message statusCode = 200 data["message"] = "ok" except exceptions.invalidArgumentsException: statusCode = 400 data["message"] = "invalid parameters" finally: # Add status code to data data["status"] = statusCode # Send response self.write(json.dumps(data)) self.set_status(statusCode)
def alertUser(fro, chan, message): target = message[0].lower() targetToken = glob.tokens.getTokenFromUsername( userUtils.safeUsername(target), safe=True) if targetToken is not None: targetToken.enqueue(serverPackets.notification(' '.join(message[1:]))) return False else: return "User offline."
def handle(self, userID): userID = super().parseData(userID) if userID is None: return targetToken = glob.tokens.getTokenFromUserID(userID) if targetToken is not None: targetToken.enqueue(serverPackets.notification("Bye-bye! See ya!")) targetToken.enqueue(serverPackets.userSupporterGMT(True, False, False)) targetToken.enqueue(serverPackets.userSupporterGMT(False, True, False)) targetToken.enqueue(serverPackets.kill())
def kick(self, message="You have been kicked from the server. Please login again.", reason="kick"): """ Kick this user from the server :param message: Notification message to send to this user. Default: "You have been kicked from the server. Please login again." :param reason: Kick reason, used in logs. Default: "kick" :return: """ # Send packet to target log.info("{} has been disconnected. ({})".format(self.username, reason)) if message != "": self.enqueue(serverPackets.notification(message)) self.enqueue(serverPackets.loginFailed()) # Logout event logoutEvent.handle(self, deleteToken=self.irc)
def joinMatch(self, matchID): """ Set match to matchID, join match stream and channel :param matchID: new match ID :return: """ # Make sure the match exists if matchID not in glob.matches.matches: return # Match exists, get object match = glob.matches.matches[matchID] # Stop spectating self.stopSpectating() # Leave other matches if self.matchID > -1 and self.matchID != matchID: self.leaveMatch() # Try to join match joined = match.userJoin(self) if not joined: self.enqueue(serverPackets.matchJoinFail()) return # Set matchID, join stream, channel and send packet self.matchID = matchID self.joinStream(match.streamName) chat.joinChannel(token=self, channel="{}_{}".format( chatChannels.MULTIPLAYER_PREFIX, self.matchID), force=True) self.enqueue(serverPackets.matchJoinSuccess(matchID)) if match.isTourney: # Alert the user if we have just joined a tourney match self.enqueue( serverPackets.notification( "You are now in a tournament match.")) # If an user joins, then the ready status of the match changes and # maybe not all users are ready. match.sendReadyStatus()
def mpInvite(): if len(message) < 2: raise exceptions.invalidArgumentsException( "Wrong syntax: !mp invite <username>") username = message[1] userID = userUtils.getIDSafe(username) if userID is None: raise exceptions.userNotFoundException("No such user") token = glob.tokens.getTokenFromUserID(userID, ignoreIRC=True) if token is None: raise exceptions.invalidUserException( "That user is not connected to bancho right now.") _match = glob.matches.matches[getMatchIDFromChannel(chan)] _match.invite(999, userID) token.enqueue( serverPackets.notification( "Please accept the invite you've just received from FokaBot to " "enter your tourney match.")) return "An invite to this match has been sent to {}".format(username)
def usersTimeoutCheckLoop(self): """ Start timed out users disconnect loop. This function will be called every `checkTime` seconds and so on, forever. CALL THIS FUNCTION ONLY ONCE! :return: """ try: log.debug("Checking timed out clients") exceptions = [] timedOutTokens = [] # timed out users timeoutLimit = int(time.time()) - 100 for key, value in self.tokens.items(): # Check timeout (fokabot is ignored) #print("UserID {} Always Online: {}".format(value.userID, aobaHelper.getAlwaysOnline(value.userID))) if aobaHelper.getAlwaysOnline(value.userID) == False: if value.pingTime < timeoutLimit and not value.irc and not value.tournament: # That user has timed out, add to disconnected tokens # We can't delete it while iterating or items() throws an error timedOutTokens.append(key) # Delete timed out users from self.tokens # i is token string (dictionary key) for i in timedOutTokens: log.debug("{} timed out!!".format(self.tokens[i].username)) self.tokens[i].enqueue( serverPackets.notification( "Your connection to the server timed out.")) try: logoutEvent.handle(self.tokens[i], None) except Exception as e: exceptions.append(e) log.error( "Something wrong happened while disconnecting a timed out client. Reporting to Sentry " "when the loop ends.") del timedOutTokens # Re-raise exceptions if needed if exceptions: raise periodicLoopException(exceptions) finally: # Schedule a new check (endless loop) threading.Timer(100, self.usersTimeoutCheckLoop).start()
def invite(response, token, sender, channel, message): if len(message) < 1: raise exceptions.invalidArgumentsException( "Wrong syntax: !mp invite <username>") username = message[0].strip() if not username: raise exceptions.invalidArgumentsException( "Please provide a username") userID = lookupUser(username) token = glob.tokens.getTokenFromUserID(userID, ignoreIRC=True) if token is None: raise exceptions.invalidUserException( "That user is not connected to bancho right now.") _match = getCurrentMatch(channel) _match.invite(1, userID, force=True) token.enqueue( serverPackets.notification( "Please accept the invite you've just received from {} to " "enter your tourney match.".format(glob.BOT_NAME))) response.setContent(token, "Invited {} to the game.".format(username)) response.listen_in = [token.token]
def joinMatch(self, matchID): """ Set match to matchID, join match stream and channel :param matchID: new match ID :return: """ # Make sure the match exists if matchID not in glob.matches.matches: return # Match exists, get object match = glob.matches.matches[matchID] # Stop spectating self.stopSpectating() # Leave other matches if self.matchID > -1 and self.matchID != matchID: self.leaveMatch() # Try to join match joined = match.userJoin(self) if not joined: self.enqueue(serverPackets.matchJoinFail()) return # Set matchID, join stream, channel and send packet self.matchID = matchID self.joinStream(match.streamName) chat.joinChannel(token=self, channel="#multi_{}".format(self.matchID)) self.enqueue(serverPackets.matchJoinSuccess(matchID)) # Alert the user if we have just joined a tourney match if match.isTourney: self.enqueue( serverPackets.notification( "You are now in a tournament match."))
from logger import log from common.ripple import userUtils from constants import exceptions from constants import serverPackets from helpers import chatHelper as chat from helpers import geo_helper from objects import glob from datetime import datetime from helpers.realistik_stuff import Timer from objects import glob import random from helpers.user_helper import verify_password from helpers.geo_helper import get_full UNFREEZE_NOTIF = serverPackets.notification( "Thank you for providing a liveplay! You have proven your legitemacy and have subsequently been unfrozen. Have fun playing RealistikOsu!" ) FREEZE_RES_NOTIF = serverPackets.notification( "Your window for liveplay sumbission has expired! Your account has been restricted as per our cheating policy. Please contact staff for more information on what can be done. This can be done via the RealistikCentral Discord server." ) def handle(tornadoRequest): # I wanna benchmark! t = Timer() t.start() # Data to return responseToken = None responseTokenString = "" responseData = bytearray()
def alert(fro, chan, message): glob.streams.broadcast("main", serverPackets.notification(' '.join(message[:]))) return False
def instantRestart(fro, chan, message): glob.streams.broadcast( "main", serverPackets.notification("We are restarting Bancho. Be right back!")) systemHelper.scheduleShutdown(0, True, delay=5) return False
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.autopiloting = False userToken.relaxing = True userToken.ScoreV2 = False userToken.lastMod = userToken.currentMod userToken.currentMod = 'Relax' 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.currentMod != userToken.lastMod: userToken.enqueue( serverPackets.notification("You're playing with Relax")) # autopiloten elif bool(packetData["actionMods"] & 8192) == True: userToken.autopiloting = True userToken.relaxing = False userToken.ScoreV2 = False userToken.lastMod = userToken.currentMod userToken.currentMod = 'Autopilot' if userToken.actionID in (0, 1, 14): UserText = packetData["actionText"] + "on Autopilot" else: UserText = packetData["actionText"] + " on Autopilot" userToken.actionText = UserText userToken.updateCachedStats() if userToken.currentMod != userToken.lastMod: userToken.enqueue( serverPackets.notification("You're playing with Autopilot")) # score v2 elif bool(packetData["actionMods"] & 536870912) == True: userToken.autopiloting = False userToken.relaxing = False userToken.ScoreV2 = True userToken.lastMod = userToken.currentMod userToken.currentMod = 'ScoreV2' if userToken.actionID in (0, 1, 14): UserText = packetData["actionText"] + "on ScoreV2" else: UserText = packetData["actionText"] + " on ScoreV2" userToken.actionText = UserText userToken.updateCachedStats() if userToken.currentMod != userToken.lastMod: userToken.enqueue( serverPackets.notification("You're playing with ScoreV2")) else: userToken.relaxing = False userToken.autopiloting = False userToken.ScoreV2 = False userToken.lastMod = userToken.currentMod userToken.currentMod = 'Regular' UserText = packetData["actionText"] userToken.actionText = UserText userToken.updateCachedStats() if userToken.currentMod != userToken.lastMod: userToken.enqueue( serverPackets.notification("You're playing in regular mode")) 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 != 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
def handle(tornadoRequest): # Data to return responseToken = None responseTokenString = "ayy" responseData = bytes() # Get IP from tornado request requestIP = tornadoRequest.getRequestIP() # Avoid exceptions clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"] osuVersion = "unknown" # Split POST body so we can get username/password/hardware data # 2:-3 thing is because requestData has some escape stuff that we don't need loginData = str(tornadoRequest.request.body)[2:-3].split("\\n") try: # Make sure loginData is valid if len(loginData) < 3: raise exceptions.invalidArgumentsException() # Get HWID, MAC address and more # Structure (new line = "|", already split) # [0] osu! version # [1] plain mac addressed, separated by "." # [2] mac addresses hash set # [3] unique ID # [4] disk ID splitData = loginData[2].split("|") osuVersion = splitData[0] timeOffset = int(splitData[1]) clientData = splitData[3].split(":")[:5] if len(clientData) < 4: raise exceptions.forceUpdateException() # Try to get the ID from username username = str(loginData[0]) userID = userUtils.getID(username) if not userID: # Invalid username raise exceptions.loginFailedException() if not userUtils.checkLogin(userID, loginData[1]): # Invalid password raise exceptions.loginFailedException() # Make sure we are not banned or locked priv = userUtils.getPrivileges(userID) if userUtils.isBanned(userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginBannedException() if userUtils.isLocked(userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginLockedException() # 2FA check if userUtils.check2FA(userID, requestIP): log.warning("Need 2FA check for user {}.".format(loginData[0])) raise exceptions.need2FAException() # No login errors! # Verify this user (if pending activation) firstLogin = False if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware(userID): if userUtils.verifyUser(userID, clientData): # Valid account log.info("Account {} verified successfully!".format(userID)) glob.verifiedCache[str(userID)] = 1 firstLogin = True else: # Multiaccount detected log.info("Account {} NOT verified!".format(userID)) glob.verifiedCache[str(userID)] = 0 raise exceptions.loginBannedException() # Save HWID in db for multiaccount detection hwAllowed = userUtils.logHardware(userID, clientData, firstLogin) # This is false only if HWID is empty # if HWID is banned, we get restricted so there's no # need to deny bancho access if not hwAllowed: raise exceptions.haxException() # Log user IP userUtils.logIP(userID, requestIP) # Delete old tokens for that user and generate a new one isTournament = "tourney" in osuVersion if not isTournament: glob.tokens.deleteOldTokens(userID) responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset, tournament=isTournament) responseTokenString = responseToken.token # Check restricted mode (and eventually send message) responseToken.checkRestricted() userFlags = userUtils.getUserFlags(userID) if userFlags > 0: # Pending public bans. Such as chargebacks, etc. flagReason = userUtils.getFlagReason(userID) if userFlags-int(time.time()) < 0: responseToken.enqueue(serverPackets.notification("Your account has been automatically restricted due to a pending restriction not being having been dealt with.\n\nReason: {}".format(flagReason))) userUtils.restrict(userID) userUtils.setUserFlags(userID, 0) log.cmyui("{} has been automatically restricted due to not dealing with pending restriction. Reason: {}.".format(username, flagReason), discord="cm") log.rap(userID, "has been restricted due to a pending restriction. Reason: {}.".format(flagReason)) else: if "charge" in flagReason: responseToken.enqueue(serverPackets.notification("Your account has been flagged with an automatic restriction.\n\nIt will occur at {time} if not dealt with.\n" "Reason: {reason}\n\nTo avoid being restricted for this behaviour, you can cancel or revert your chargeback before your restriction date.".format(time=datetime.utcfromtimestamp(int(userFlags)).strftime('%Y-%m-%d %H:%M:%S'), reason=flagReason))) elif "live" in flagReason: responseToken.enqueue(serverPackets.notification("Your account has been flagged with an automatic restriction.\n\nIt will occur at {time} if not dealt with.\n" "Reason: {reason}\n\nThis means you are required to submit a liveplay to avoid this. This only happens in cases when we are confident in foul play; and are offering you this opportunity as a final stance to prove your legitimacy, against all the odds.".format(time=datetime.utcfromtimestamp(int(userFlags)).strftime('%Y-%m-%d %H:%M:%S'), reason=flagReason))) else: responseToken.enqueue(serverPackets.notification("Your account has been flagged with an automatic restriction.\n\nIt will occur at {time} if not dealt with.\n" "Reason: {reason}\n\nYou have until the restriction to deal with the issue.".format(time=datetime.utcfromtimestamp(int(userFlags)).strftime('%Y-%m-%d %H:%M:%S'), reason=flagReason))) # Send message if premium / donor expires soon # ok spaghetti code time if responseToken.privileges & privileges.USER_DONOR: donorType = 'premium' if responseToken.privileges & privileges.USER_PREMIUM else 'donor' expireDate = userUtils.getDonorExpire(responseToken.userID) if expireDate-int(time.time()) < 0: userUtils.setPrivileges(userID, 3) log.cmyui("{}'s donation perks have been removed as their time has run out.".format(username), discord="cm") log.rap(userID, "User's donor perks have been removed as their time has run out.") responseToken.enqueue(serverPackets.notification("Your {donorType} tag has expired! Thank you so much for the support, it really means everything to us. If you wish to keep supporting Akatsuki and you don't want to lose your {donorType} privileges, you can donate again by clicking on 'Support us' on Akatsuki's website.".format(donorType=donorType))) elif expireDate-int(time.time()) <= 86400*3: expireDays = round((expireDate-int(time.time()))/86400) expireIn = "{} days".format(expireDays) if expireDays > 1 else "less than 24 hours" responseToken.enqueue(serverPackets.notification("Your {donorType} tag expires in {expireIn}! When your {donorType} tag expires, you won't have any of the {donorType} privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Akatsuki and you don't want to lose your {donorType} privileges, you can donate again by clicking on 'Support us' on Akatsuki's website.".format(donorType=donorType, expireIn=expireIn))) """ Akatsuki does not use 2fa! we suck! if userUtils.deprecateTelegram2Fa(userID): responseToken.enqueue(serverPackets.notification("As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://akatsuki.pw. Thank you for your patience.")) """ # Set silence end UNIX time in token responseToken.silenceEndTime = userUtils.getSilenceEnd(userID) # Get only silence remaining seconds silenceSeconds = responseToken.getSilenceSecondsLeft() # Get supporter/GMT userGMT = False userSupporter = True userTournament = False if responseToken.admin: userGMT = True if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0: userTournament = True # Server restarting check if glob.restarting: raise exceptions.banchoRestartingException() # Send login notification before maintenance message if glob.banchoConf.config["loginNotification"] != "": responseToken.enqueue(serverPackets.notification(glob.banchoConf.config["loginNotification"])) # Maintenance check if glob.banchoConf.config["banchoMaintenance"]: if not userGMT: # We are not mod/admin, delete token, send notification and logout glob.tokens.deleteToken(responseTokenString) raise exceptions.banchoMaintenanceException() else: # We are mod/admin, send warning notification and continue responseToken.enqueue(serverPackets.notification("Akatsuki is currently in maintenance mode. Only admins have full access to the server.\nType '!system maintenance off' in chat to turn off maintenance mode.")) # Send all needed login packets responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds)) responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue(serverPackets.userSupporterGMT(userSupporter, userGMT, userTournament)) responseToken.enqueue(serverPackets.userPanel(userID, True)) responseToken.enqueue(serverPackets.userStats(userID, True)) # Channel info end (before starting!?! wtf bancho?) responseToken.enqueue(serverPackets.channelInfoEnd()) # Default opened channels # TODO: Configurable default channels chat.joinChannel(token=responseToken, channel="#osu") chat.joinChannel(token=responseToken, channel="#announce") #Akatsuki extra channels chat.joinChannel(token=responseToken, channel="#nowranked") chat.joinChannel(token=responseToken, channel="#request") # Join admin channel if we are an admin if responseToken.admin or responseToken.privileges & privileges.USER_PREMIUM: chat.joinChannel(token=responseToken, channel="#admin") # Output channels info for key, value in glob.channels.channels.items(): if value.publicRead and not value.hidden: responseToken.enqueue(serverPackets.channelInfo(key)) # Send friends list responseToken.enqueue(serverPackets.friendList(userID)) # Send main menu icon if glob.banchoConf.config["menuIcon"] != "": responseToken.enqueue(serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) # Send online users' panels with glob.tokens: for _, token in glob.tokens.tokens.items(): if not token.restricted: responseToken.enqueue(serverPackets.userPanel(token.userID)) # Get location and country from ip.zxq.co or database. If the user is a donor, then yee if glob.localize and (firstLogin == True or responseToken.privileges & privileges.USER_DONOR <= 0): # Get location and country from IP latitude, longitude = locationHelper.getLocation(requestIP) countryLetters = locationHelper.getCountry(requestIP) country = countryHelper.getCountryID(countryLetters) else: # Set location to 0,0 and get country from db log.warning("Location skipped") latitude = 0 longitude = 0 countryLetters = "XX" country = countryHelper.getCountryID(userUtils.getCountry(userID)) # Set location and country responseToken.setLocation(latitude, longitude) responseToken.country = country # Set country in db if user has no country (first bancho login) if userUtils.getCountry(userID) == "XX": userUtils.setCountry(userID, countryLetters) # Send to everyone our userpanel if we are not restricted or tournament if not responseToken.restricted: glob.streams.broadcast("main", serverPackets.userPanel(userID)) # Set reponse data to right value and reset our queue responseData = responseToken.queue responseToken.resetQueue() except exceptions.loginFailedException: # Login failed error packet # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() except exceptions.invalidArgumentsException: # Invalid POST data # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() responseData += serverPackets.notification("We see what you're doing..") log.cmyui("User {} has triggered invalidArgumentsException in loginEvent.py".format(userID), discord="cm") except exceptions.loginBannedException: # Login banned error packet responseData += serverPackets.loginBanned() except exceptions.loginLockedException: # Login banned error packet responseData += serverPackets.loginLocked() except exceptions.banchoMaintenanceException: # Bancho is in maintenance mode responseData = bytes() if responseToken is not None: responseData = responseToken.queue responseData += serverPackets.notification("Akatsuki is currently in maintenance mode. Please try to login again later.") responseData += serverPackets.loginFailed() except exceptions.banchoRestartingException: # Bancho is restarting responseData += serverPackets.notification("Akatsuki is restarting. Try again in a few minutes.") responseData += serverPackets.loginFailed() except exceptions.need2FAException: # User tried to log in from unknown IP responseData += serverPackets.needVerification() except exceptions.haxException: # Using oldoldold client, we don't have client data. Force update. # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.forceUpdate() responseData += serverPackets.notification("Custom clients of ANY kind are NOT PERMITTED on Akatsuki. Please login using the current osu! client.") log.cmyui("User {} has logged in with a VERY old client".format(userID), discord="cm") except: log.error("Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc())) finally: # Console and discord log if len(loginData) < 3: log.info("Invalid bancho login request from **{}** (insufficient POST data)".format(requestIP), "bunker") # Return token string and data return responseTokenString, responseData
def 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] # osu! version timeOffset = int(splitData[1]) # timezone showCity = int(splitData[2]) # allow to show city clientData = splitData[3].split(":")[:5] # security hash blockNonFriendPM = int(splitData[4]) # allow PM if len(clientData) < 4: raise exceptions.forceUpdateException() # Try to get the ID from username username = str(loginData[0]) userID = userUtils.getID(username) if not userID: # Invalid username raise exceptions.loginFailedException() if not userUtils.checkLogin(userID, loginData[1]): # Invalid password raise exceptions.loginFailedException() # Make sure we are not banned or locked priv = userUtils.getPrivileges(userID) if userUtils.isBanned( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginBannedException() if userUtils.isLocked( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginLockedException() # 2FA check if userUtils.check2FA(userID, requestIP): log.warning("Need 2FA check for user {}".format(loginData[0])) raise exceptions.need2FAException() # No login errors! # Verify this user (if pending activation) firstLogin = False if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware( userID): if userUtils.verifyUser(userID, clientData): # Valid account log.info("Account ID {} verified successfully!".format(userID)) glob.verifiedCache[str(userID)] = 1 firstLogin = True else: # Multiaccount detected log.info("Account ID {} NOT verified!".format(userID)) glob.verifiedCache[str(userID)] = 0 raise exceptions.loginBannedException() # Save HWID in db for multiaccount detection hwAllowed = userUtils.logHardware(userID, clientData, firstLogin) # This is false only if HWID is empty # if HWID is banned, we get restricted so there's no # need to deny bancho access if not hwAllowed: raise exceptions.haxException() # Log user IP userUtils.logIP(userID, requestIP) # Log user osuver kotrikhelper.setUserLastOsuVer(userID, osuVersion) # Delete old tokens for that user and generate a new one isTournament = "tourney" in osuVersion numericVersion = re.sub(r'[^0-9.]', '', osuVersion) if not isTournament: glob.tokens.deleteOldTokens(userID) if numericVersion < glob.conf.config["server"]["osuminver"]: raise exceptions.forceUpdateException() responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset, tournament=isTournament) responseTokenString = responseToken.token # Check restricted mode (and eventually send message) responseToken.checkRestricted() # Send message if donor expires soon if responseToken.privileges & privileges.USER_DONOR > 0: expireDate = userUtils.getDonorExpire(responseToken.userID) if expireDate - int(time.time()) <= 86400 * 3: expireDays = round((expireDate - int(time.time())) / 86400) expireIn = "{} days".format( expireDays) if expireDays > 1 else "less than 24 hours" responseToken.enqueue( serverPackets.notification( "Your donor tag expires in {}! When your donor tag expires, you won't have any of the donor privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Ripple and you don't want to lose your donor privileges, you can donate again by clicking on 'Support us' on Ripple's website." .format(expireIn))) # Deprecate telegram 2fa and send alert if userUtils.deprecateTelegram2Fa(userID): responseToken.enqueue( serverPackets.notification( "As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://ripple.moe. Thank you for your patience." )) # Set silence end UNIX time in token responseToken.silenceEndTime = userUtils.getSilenceEnd(userID) # Get only silence remaining seconds silenceSeconds = responseToken.getSilenceSecondsLeft() # Get supporter/GMT userGMT = False if not userUtils.isRestricted(userID): userSupporter = True else: userSupporter = False userTournament = False if responseToken.admin: userGMT = True if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0: userTournament = True # Server restarting check if glob.restarting: raise exceptions.banchoRestartingException() """ if userUtils.checkIfFlagged(userID): responseToken.enqueue(serverPackets.notification("Staff suspect you of cheat! You have 5 days to make a full pc startup liveplay, or you will get restricted and you'll have to wait a month to appeal!")) """ # Check If today is 4/20 (Peppy Day) if today == peppyday: if glob.conf.extra["mode"]["peppyday"]: responseToken.enqueue( serverPackets.notification( "Everyone on today will have peppy as their profile picture! Have fun on peppy day" )) # Send login notification before maintenance message if glob.banchoConf.config["loginNotification"] != "": responseToken.enqueue( serverPackets.notification( glob.banchoConf.config["loginNotification"])) # Maintenance check if glob.banchoConf.config["banchoMaintenance"]: if not userGMT: # We are not mod/admin, delete token, send notification and logout glob.tokens.deleteToken(responseTokenString) raise exceptions.banchoMaintenanceException() else: # We are mod/admin, send warning notification and continue responseToken.enqueue( serverPackets.notification( "Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode." )) # BAN CUSTOM CHEAT CLIENTS # 0Ainu = First Ainu build # b20190326.2 = Ainu build 2 (MPGH PAGE 10) # b20190401.22f56c084ba339eefd9c7ca4335e246f80 = Ainu Aoba's Birthday Build # b20190906.1 = Unknown Ainu build? (unreleased, I think) # b20191223.3 = Unknown Ainu build? (Taken from most users osuver in cookiezi.pw) # b20190226.2 = hqOsu (hq-af) if glob.conf.extra["mode"]["anticheat"]: # Ainu Client 2020 update if tornadoRequest.request.headers.get("ainu") == "happy": log.info( "Account ID {} tried to use Ainu (Cheat) Client 2020!". format(userID)) if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "You're banned because you're currently using Ainu Client... Happy New Year 2020 and Enjoy your restriction :)" )) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]" ) webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to use Ainu (Cheat) Client 2020! AGAIN!!!". format(username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) aobaHelper.Webhook.post() else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]" ) webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to use Ainu (Cheat) Client 2020 and got restricted!" .format(username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() raise exceptions.loginCheatClientsException() # Ainu Client 2019 elif aobaHelper.getOsuVer(userID) in [ "0Ainu", "b20190326.2", "b20190401.22f56c084ba339eefd9c7ca4335e246f80", "b20190906.1", "b20191223.3" ]: log.info( "Account ID {} tried to use Ainu (Cheat) Client!".format( userID)) if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "You're banned because you're currently using Ainu Client. Enjoy your restriction :)" )) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]" ) webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to use Ainu (Cheat) Client! AGAIN!!!".format( username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]" ) webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to use Ainu (Cheat) Client and got restricted!" .format(username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() raise exceptions.loginCheatClientsException() # hqOsu elif aobaHelper.getOsuVer(userID) == "b20190226.2": log.info("Account ID {} tried to use hqOsu!".format(userID)) if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "Trying to use hqOsu in here? Well... No, sorry. We don't allow cheats here. Go play https://cookiezi.pw or others cheat server." )) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]" ) webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to use hqOsu! AGAIN!!!".format(username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="Man... this is worst player. [ Login Gate AC ]" ) webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to use hqOsu and got restricted!".format( username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() raise exceptions.loginCheatClientsException() # Send all needed login packets responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds)) responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue( serverPackets.userSupporterGMT(userSupporter, userGMT, userTournament)) responseToken.enqueue(serverPackets.userPanel(userID, True)) responseToken.enqueue(serverPackets.userStats(userID, True)) # Channel info end (before starting!?! wtf bancho?) responseToken.enqueue(serverPackets.channelInfoEnd()) # Default opened channels # TODO: Configurable default channels chat.joinChannel(token=responseToken, channel="#osu") chat.joinChannel(token=responseToken, channel="#announce") # Join admin channel if we are an admin if responseToken.admin: chat.joinChannel(token=responseToken, channel="#admin") # Output channels info for key, value in glob.channels.channels.items(): if value.publicRead and not value.hidden: responseToken.enqueue(serverPackets.channelInfo(key)) # Send friends list responseToken.enqueue(serverPackets.friendList(userID)) # Send main menu icon if glob.banchoConf.config["menuIcon"] != "": responseToken.enqueue( serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) # Send online users' panels with glob.tokens: for _, token in glob.tokens.tokens.items(): if not token.restricted: responseToken.enqueue(serverPackets.userPanel( token.userID)) # Get location and country from ip.zxq.co or database if glob.localize: # Get location and country from IP latitude, longitude = locationHelper.getLocation(requestIP) if userID == 1000: latitude, longitude = 34.676143, 133.938883 countryLetters = locationHelper.getCountry(requestIP) country = countryHelper.getCountryID(countryLetters) else: # Set location to 0,0 and get country from db log.warning("Location skipped") latitude = 0 longitude = 0 countryLetters = "XX" country = countryHelper.getCountryID(userUtils.getCountry(userID)) # Set location and country responseToken.setLocation(latitude, longitude) responseToken.country = country # Set country in db if user has no country (first bancho login) if userUtils.getCountry(userID) == "XX": userUtils.setCountry(userID, countryLetters) # Send to everyone our userpanel if we are not restricted or tournament if not responseToken.restricted: glob.streams.broadcast("main", serverPackets.userPanel(userID)) # Set reponse data to right value and reset our queue responseData = responseToken.queue responseToken.resetQueue() except exceptions.loginFailedException: # Login failed error packet # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() except exceptions.invalidArgumentsException: # Invalid POST data # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() responseData += serverPackets.notification( "I see what you're doing...") except exceptions.loginBannedException: # Login banned error packet responseData += serverPackets.loginBanned() except exceptions.loginLockedException: # Login banned error packet responseData += serverPackets.loginLocked() except exceptions.loginCheatClientsException: # Banned for logging in with cheats responseData += serverPackets.loginCheats() except exceptions.banchoMaintenanceException: # Bancho is in maintenance mode responseData = bytes() if responseToken is not None: responseData = responseToken.queue responseData += serverPackets.notification( "Our bancho server is in maintenance mode. Please try to login again later." ) responseData += serverPackets.loginFailed() except exceptions.banchoRestartingException: # Bancho is restarting responseData += serverPackets.notification( "Bancho is restarting. Try again in a few minutes.") responseData += serverPackets.loginFailed() except exceptions.need2FAException: # User tried to log in from unknown IP responseData += serverPackets.needVerification() except exceptions.haxException: # Uh... responseData += serverPackets.notification("Your HWID is banned.") responseData += serverPackets.loginFailed() except exceptions.forceUpdateException: # This happens when you: # - Using older build than config set # - Using oldoldold client, we don't have client data. Force update. # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.forceUpdate() except: log.error("Unknown error!\n```\n{}\n{}```".format( sys.exc_info(), traceback.format_exc())) finally: # Console and discord log if len(loginData) < 3: log.info( "Invalid bancho login request from **{}** (insufficient POST data)" .format(requestIP), "bunker") # Return token string and data return responseTokenString, responseData
def 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 relax status changed if packetData["actionMods"] & 128 != userToken.relax: userToken.relax = packetData["actionMods"] & 128 notifs = userUtils.checkAkatsukiNotifications(userID) if packetData["actionMods"] & 128: if not notifs: userToken.enqueue( serverPackets.notification("You've switched to Relax!")) userToken.rxupdateCachedStats() else: if not notifs: userToken.enqueue( serverPackets.notification("You've switched to Regular!")) userToken.updateCachedStats() # 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"]): if packetData["actionMods"] & 128: userToken.rxupdateCachedStats() else: userToken.updateCachedStats() # Update cached stats if we've changed gamemode if userToken.gameMode != packetData["gameMode"]: userToken.gameMode = packetData["gameMode"] if packetData["actionMods"] & 128: userToken.rxupdateCachedStats() else: userToken.updateCachedStats() # Always update action id, text, md5 and beatmapID userToken.actionID = packetData["actionID"] userToken.actionText = packetData[ "actionText"] + " +" + scoreUtils.readableMods( int(packetData["actionMods"])) 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 handle(tornadoRequest): # I wanna benchmark! t = Timer() t.start() # Data to return responseToken = None responseTokenString = "" responseData = bytearray() # 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: log.error("Login error (invalid login data)!") 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]) safe_username = username.rstrip().replace(" ", "_").lower() # Set stuff from single query rather than many userUtils calls. user_db = glob.db.fetch( "SELECT id, privileges, silence_end, donor_expire, frozen, " "firstloginafterfrozen, freezedate FROM users " "WHERE username_safe = %s LIMIT 1", (safe_username, )) if not user_db: # Invalid username log.error(f"Login failed for user {username} (user not found)!") responseData += serverPackets.notification( "RealistikOsu: This user does not exist!") raise exceptions.loginFailedException() userID = user_db["id"] priv = int(user_db["privileges"]) silence_end = int(user_db["silence_end"]) donor_expire = int(user_db["donor_expire"]) if not verify_password(userID, loginData[1]): # Invalid password log.error(f"Login failed for user {username} (invalid password)!") responseData += serverPackets.notification( "RealistikOsu: Invalid password!") raise exceptions.loginFailedException() # Make sure we are not banned or locked if (not priv & 3 > 0) and ( not priv & privileges.USER_PENDING_VERIFICATION): log.error(f"Login failed for user {username} (user is banned)!") raise exceptions.loginBannedException() # No login errors! # Verify this user (if pending activation) firstLogin = False if priv & privileges.USER_PENDING_VERIFICATION or not userUtils.hasVerifiedHardware( userID): if userUtils.verifyUser(userID, clientData): # Valid account log.info(f"Account {userID} verified successfully!") glob.verifiedCache[str(userID)] = 1 firstLogin = True else: # Multiaccount detected log.info(f"Account {userID} NOT verified!") 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 glob.db.execute("UPDATE users SET osuver = %s WHERE id = %s LIMIT 1", [osuVersion, userID]) # 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) # Cache this for less db queries user_restricted = (priv & privileges.USER_NORMAL ) and not (priv & privileges.USER_PUBLIC) if user_restricted: responseToken.setRestricted() #responseToken.checkRestricted() # Check if frozen frozen = user_db["frozen"] present = datetime.now() readabledate = datetime.utcfromtimestamp( user_db["freezedate"]).strftime('%d-%m-%Y %H:%M:%S') date2 = datetime.utcfromtimestamp( user_db["freezedate"]).strftime('%d/%m/%Y') date3 = present.strftime('%d/%m/%Y') passed = date2 < date3 if frozen and not passed: responseToken.enqueue( serverPackets.notification( f"The RealistikOsu staff team has found you suspicious and would like to request a liveplay. You have until {readabledate} (UTC) to provide a liveplay to the staff team. This can be done via the RealistikCentral Discord server. Failure to provide a valid liveplay will result in your account being automatically restricted." )) elif frozen and passed: responseToken.enqueue(FREEZE_RES_NOTIF) userUtils.restrict(responseToken.userID) #we thank unfrozen people if not frozen and user_db["firstloginafterfrozen"]: responseToken.enqueue(UNFREEZE_NOTIF) glob.db.execute( f"UPDATE users SET firstloginafterfrozen = 0 WHERE id = {userID}" ) # Send message if donor expires soon if responseToken.privileges & privileges.USER_DONOR: if donor_expire - int(time.time()) <= 86400 * 3: expireDays = round((donor_expire - int(time.time())) / 86400) expireIn = "{} days".format( expireDays) if expireDays > 1 else "less than 24 hours" responseToken.enqueue( serverPackets.notification( "Your supporter status expires in {}! Following this, you will lose your supporter privileges (such as the further profile customisation options, name changes or profile wipes) and will not be able to access supporter features. If you wish to keep supporting RealistikOsu and you don't want to lose your donor privileges, you can donate again by clicking on 'Donate' on our website." .format(expireIn))) # Get only silence remaining seconds responseToken.silenceEndTime = silence_end silenceSeconds = responseToken.getSilenceSecondsLeft() # Get supporter/GMT userGMT = False userSupporter = not user_restricted userTournament = False userGMT = responseToken.admin userTournament = bool(responseToken.privileges & privileges.USER_TOURNAMENT_STAFF) # Server restarting check if glob.restarting: raise exceptions.banchoRestartingException() # 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) # TODO: Rewrite this mess # Ainu Client 2020 update if tornadoRequest.request.headers.get("ainu"): log.info(f"Account {userID} tried to use Ainu Client 2020!") if user_restricted: responseToken.enqueue( serverPackets.notification( "Note: AINU CLIENT IS DETECTED EVERYWHERE... ITS CREATORS LITERALLY ADDED A WAY TO EASILY DETECT." )) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) userUtils.appendNotes( userID, "User restricted on login for Ainu Client 2020.") raise exceptions.loginCheatClientsException() # Ainu Client 2019 elif osuVersion in ("0Ainu", "b20190326.2", "b20190401.22f56c084ba339eefd9c7ca4335e246f80", "b20191223.3"): log.info(f"Account {userID} tried to use Ainu Client!") if user_restricted: responseToken.enqueue( serverPackets.notification( "Note: AINU CLIENT IS DETECTED EVERYWHERE...")) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) userUtils.appendNotes( userID, "User restricted on login for Ainu Client 2019 (or older)." ) raise exceptions.loginCheatClientsException() # hqOsu elif osuVersion == "b20190226.2": log.info(f"Account {userID} tried to use hqOsu!") if user_restricted: responseToken.enqueue(serverPackets.notification("Comedian.")) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) userUtils.appendNotes( userID, "User restricted on login for HQOsu (normal).") raise exceptions.loginCheatClientsException() #hqosu legacy elif osuVersion == "b20190716.5": log.info(f"Account {userID} tried to use hqOsu legacy!") if user_restricted: responseToken.enqueue(serverPackets.notification("Comedian.")) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) userUtils.appendNotes( userID, "User restricted on login for HQOsu (legacy).") raise exceptions.loginCheatClientsException() # Budget Hacked client. elif osuVersion.startswith("skoot"): if user_restricted: responseToken.enqueue(serverPackets.notification("Comedian.")) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) userUtils.appendNotes(userID, "Wack 2016 Scooter client.") raise exceptions.loginCheatClientsException() # Blanket cover for most retard clients, force update. elif osuVersion[0] != "b": glob.tokens.deleteToken(userID) raise exceptions.haxException() # Send all needed login packets responseToken.enqueue( bytearray(serverPackets.silenceEndTime( silenceSeconds)) + # Fast addition serverPackets.userID(userID) + serverPackets.protocolVersion() + serverPackets.userSupporterGMT( userSupporter, userGMT, userTournament) + serverPackets.userPanel(userID, True) + serverPackets.userStats(userID, True) + 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)) # Localise the user based off IP. # Get location and country from IP latitude, longitude, countryLetters = get_full(requestIP) country = geo_helper.getCountryID(countryLetters) # 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)) #creating notification t.end() t_str = t.time_str() online_users = len(glob.tokens.tokens) # Wylie has his own quote he gets to enjoy only himself lmfao. UPDATE: Electro gets it too. if userID in (4674, 3277): quote = "I lost an S because I saw her lewd" # Ced also gets his own AS HE DOESNT WANT TO CHECK FAST SPEED. elif userID == 1002: quote = "juSt Do iT" # Me and relesto are getting one as well lmao. UPDATE: Sky and Aochi gets it too lmao. elif userID in (1000, 1180, 3452, 4812): quote = ( f"Hello I'm RealistikBot! The server's official bot to assist you, " "if you want to know what I can do just type !help") else: quote = random.choice(glob.banchoConf.config['Quotes']) notif = f"""- Online Users: {online_users}\n- {quote}""" if responseToken.admin: notif += f"\n- Elapsed: {t_str}!" responseToken.enqueue(serverPackets.notification(notif)) log.info(f"Authentication attempt took {t_str}!") # Set reponse data to right value and reset our queue responseData = responseToken.fetch_queue() 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 have eyes y'know?") 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.fetch_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("What...") 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, bytes(responseData)
def handle(tornadoRequest): # Data to return responseToken = None responseTokenString = "ayy" responseData = bytes() # Get IP from tornado request requestIP = tornadoRequest.getRequestIP() # Avoid exceptions clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"] osuVersion = "unknown" # Split POST body so we can get username/password/hardware data # 2:-3 thing is because requestData has some escape stuff that we don't need loginData = str(tornadoRequest.request.body)[2:-3].split("\\n") try: # Make sure loginData is valid #if len(loginData) < 3: #raise exceptions.invalidArgumentsException() # Get HWID, MAC address and more # Structure (new line = "|", already split) # [0] osu! version # [1] plain mac addressed, separated by "." # [2] mac addresses hash set # [3] unique ID # [4] disk ID splitData = loginData[2].split("|") osuVersion = splitData[0] timeOffset = int(splitData[1]) clientData = splitData[3].split(":")[:5] if len(clientData) < 4: raise exceptions.forceUpdateException() # Try to get the ID from username username = str(loginData[0]) userID = userUtils.getID(username) if not userID: # Invalid username raise exceptions.loginFailedException() if not userUtils.checkLogin(userID, loginData[1]): # Invalid password raise exceptions.loginFailedException() # Make sure we are not banned or locked priv = userUtils.getPrivileges(userID) if userUtils.isBanned( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginBannedException() if userUtils.isLocked( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginLockedException() # 2FA check if userUtils.check2FA(userID, requestIP): if userID == 1000: #make sakuru do a different check because he plays over LAN if glob.db.fetch( f"SELECT 2fabypassip FROM users WHERE id = 1000" ) == requestIP: pass log.warning("Need 2FA check for user {}".format(loginData[0])) raise exceptions.need2FAException() # No login errors! # Verify this user (if pending activation) firstLogin = False if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware( userID): # Log user IP userUtils.logIP(userID, requestIP) if glob.db.fetch( f"SELECT ip FROM ip_blacklist WHERE ip = '{requestIP}'" ): #requestIP comes from tornado, so this isn't an sql injection vulnerability afaik glob.tokens.deleteToken(userID) userUtils.restrict(userID) return if userUtils.verifyUser(userID, clientData): # Valid account log.info("Account {} verified successfully!".format(userID)) glob.verifiedCache[str(userID)] = 1 firstLogin = True else: # Multiaccount detected log.info("Account {} NOT verified!".format(userID)) glob.verifiedCache[str(userID)] = 0 raise exceptions.loginBannedException() # Save HWID in db for multiaccount detection hwAllowed = userUtils.logHardware(userID, clientData, firstLogin) # This is false only if HWID is empty # if HWID is banned, we get restricted so there's no # need to deny bancho access if not hwAllowed: raise exceptions.haxException() # Log user osuver kotrikhelper.setUserLastOsuVer(userID, osuVersion) # Delete old tokens for that user and generate a new one isTournament = "tourney" in osuVersion if not isTournament: glob.tokens.deleteOldTokens(userID) responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset, tournament=isTournament) responseTokenString = responseToken.token # Check restricted mode (and eventually send message) responseToken.checkRestricted() # Check if frozen IsFrozen = glob.db.fetch( f"SELECT frozen, firstloginafterfrozen, freezedate FROM users WHERE id = {userID} LIMIT 1" ) #ok kids, dont ever use formats in sql queries. here i can do it as the userID comes from a trusted source (this being pep.py itself) so it wont leave me susceptable to sql injection frozen = bool(IsFrozen["frozen"]) present = datetime.now() readabledate = datetime.utcfromtimestamp( IsFrozen["freezedate"]).strftime('%d-%m-%Y %H:%M:%S') date2 = datetime.utcfromtimestamp( IsFrozen["freezedate"]).strftime('%d/%m/%Y') date3 = present.strftime('%d/%m/%Y') passed = date2 < date3 if frozen and passed == False: responseToken.enqueue( serverPackets.notification( f"The osuHOW staff team has found you suspicious and would like to request a liveplay. You have until {readabledate} (UTC) to provide a liveplay to the staff team. This can be done via the osuHOW Discord server. Failure to provide a valid liveplay will result in your account being automatically restricted." )) elif frozen and passed == True: responseToken.enqueue( serverPackets.notification( "Your window for liveplay sumbission has expired! Your account has been restricted as per our cheating policy. Please contact staff for more information on what can be done. This can be done via the osuHOW Discord server." )) userUtils.restrict(responseToken.userID) #we thank unfrozen people first = IsFrozen["firstloginafterfrozen"] if not frozen and first: responseToken.enqueue( serverPackets.notification( "Thank you for providing a liveplay! You have proven your legitemacy and have subsequently been unfrozen." )) glob.db.execute( f"UPDATE users SET firstloginafterfrozen = 0 WHERE id = {userID}" ) # Deprecate telegram 2fa and send alert #if userUtils.deprecateTelegram2Fa(userID): # responseToken.enqueue(serverPackets.notification("As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://ripple.moe. Thank you for your patience.")) # Set silence end UNIX time in token responseToken.silenceEndTime = userUtils.getSilenceEnd(userID) # Get only silence remaining seconds silenceSeconds = responseToken.getSilenceSecondsLeft() # Get supporter/GMT userGMT = False if not userUtils.isRestricted(userID): userSupporter = True else: userSupporter = False userTournament = False if responseToken.admin: userGMT = True if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0: userTournament = True # Server restarting check if glob.restarting: raise exceptions.banchoRestartingException() # Send login notification before maintenance message #if glob.banchoConf.config["loginNotification"] != "": #creating notification OnlineUsers = int( glob.redis.get("ripple:online_users").decode("utf-8")) Notif = f"""- Online Users: {OnlineUsers} - {random.choice(glob.banchoConf.config['Quotes'])}""" responseToken.enqueue(serverPackets.notification(Notif)) # Maintenance check if glob.banchoConf.config["banchoMaintenance"]: if not userGMT: # We are not mod/admin, delete token, send notification and logout glob.tokens.deleteToken(responseTokenString) raise exceptions.banchoMaintenanceException() else: # We are mod/admin, send warning notification and continue responseToken.enqueue( serverPackets.notification( "Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode." )) # BAN CUSTOM CHEAT CLIENTS # 0Ainu = First Ainu build # b20190326.2 = Ainu build 2 (MPGH PAGE 10) # b20190401.22f56c084ba339eefd9c7ca4335e246f80 = Ainu Aoba's Birthday Build # b20191223.3 = Unknown Ainu build? (Taken from most users osuver in cookiezi.pw) # b20190226.2 = hqOsu (hq-af) if glob.conf.extra["mode"]["anticheat"]: # Ainu Client 2020 update if tornadoRequest.request.headers.get("ainu") == "happy": log.info(f"Account {userID} tried to use Ainu Client 2020!") if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "Ainu client... Really? Welp enjoy your ban!")) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) raise exceptions.loginCheatClientsException() # Ainu Client 2019, elif aobaHelper.getOsuVer(userID) in [ "0Ainu", "b20190401.22f56c084ba339eefd9c7ca4335e246f80" ]: log.info(f"Account {userID} tried to use 0Ainu Client!") if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "Ainu client... Really? Welp enjoy your ban!")) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) raise exceptions.loginCheatClientsException() elif aobaHelper.getOsuVer(userID) in [ "b20190326.2", "b20191223.3" ]: log.info(f"Account {userID} tried to use 1Ainu Client!") # hqOsu elif aobaHelper.getOsuVer(userID) == "b20190226.2": log.info(f"Account {userID} is maybe using hqosu") #hqosu legacy elif aobaHelper.getOsuVer(userID) == "b20190716.5": log.info(f"Account {userID} is maybe using hqosu legacy") elif tornadoRequest.request.headers.get( "a") == "@_@_@_@_@_@_@_@___@_@_@_@___@_@___@": log.info("Account ID {} tried to use secret!".format(userID)) if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "You're banned because you're currently using some darkness secret that no one has..." )) return else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) #if glob.conf.config["discord"]["enable"] == True: webhook = aobaHelper.Webhook( glob.conf.config["discord"]["anticheat"], color=0xadd8e6, footer="@_@_@_@_@_@_@_@___@_@_@_@___@_@___@") webhook.set_title( title="Catched some cheater Account ID {}".format( userID)) webhook.set_desc( "{} tried to @_@_@_@_@_@_@_@___@_@_@_@___@_@___@ and got restricted!" .format(username)) log.info("Sent to webhook {} DONE!!".format( glob.conf.config["discord"]["enable"])) webhook.post() raise exceptions.loginCheatClientsException() elif osuVersion.startswith("skoot"): log.info(f"Account {userID} tried to skooooot!!!!") if userUtils.isRestricted(userID): responseToken.enqueue( serverPackets.notification( "oyoyoyoyoyoyoy no skooooooooting allowed here bud" )) else: glob.tokens.deleteToken(userID) userUtils.restrict(userID) raise exceptions.loginCheatClientsException() elif osuVersion[0] != "b": glob.tokens.deleteToken(userID) raise exceptions.haxException() # Send all needed login packets responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds)) responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue( serverPackets.userSupporterGMT(userSupporter, userGMT, userTournament)) responseToken.enqueue(serverPackets.userPanel(userID, True)) responseToken.enqueue(serverPackets.userStats(userID, True)) # Channel info end (before starting!?! wtf bancho?) responseToken.enqueue(serverPackets.channelInfoEnd()) # Default opened channels # TODO: Configurable default channels chat.joinChannel(token=responseToken, channel="#osu") chat.joinChannel(token=responseToken, channel="#announce") # Join admin channel if we are an admin if responseToken.admin: chat.joinChannel(token=responseToken, channel="#admin") # Output channels info for key, value in glob.channels.channels.items(): if value.publicRead and not value.hidden: responseToken.enqueue(serverPackets.channelInfo(key)) # Send friends list responseToken.enqueue(serverPackets.friendList(userID)) # Send main menu icon if glob.banchoConf.config["menuIcon"] != "": responseToken.enqueue( serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) # Send online users' panels with glob.tokens: for _, token in glob.tokens.tokens.items(): if not token.restricted: responseToken.enqueue(serverPackets.userPanel( token.userID)) # Get location and country from ip.zxq.co or database if glob.localize: # Get location and country from IP latitude, longitude = locationHelper.getLocation(requestIP) countryLetters = locationHelper.getCountry(requestIP) country = countryHelper.getCountryID(countryLetters) else: # Set location to 0,0 and get country from db log.warning("Location skipped") latitude = 0 longitude = 0 countryLetters = "XX" country = countryHelper.getCountryID(userUtils.getCountry(userID)) # Set location and country responseToken.setLocation(latitude, longitude) responseToken.country = country # Set country in db if user has no country (first bancho login) if userUtils.getCountry(userID) == "XX": userUtils.setCountry(userID, countryLetters) # Send to everyone our userpanel if we are not restricted or tournament if not responseToken.restricted: glob.streams.broadcast("main", serverPackets.userPanel(userID)) # Set reponse data to right value and reset our queue responseData = responseToken.queue responseToken.resetQueue() except exceptions.loginFailedException: # Login failed error packet # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() except exceptions.invalidArgumentsException: # Invalid POST data # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() responseData += serverPackets.notification( "I see what you're doing...") except exceptions.loginBannedException: # Login banned error packet responseData += serverPackets.loginBanned() except exceptions.loginLockedException: # Login banned error packet responseData += serverPackets.loginLocked() except exceptions.loginCheatClientsException: # Banned for logging in with cheats responseData += serverPackets.loginBanned() except exceptions.banchoMaintenanceException: # Bancho is in maintenance mode responseData = bytes() if responseToken is not None: responseData = responseToken.queue responseData += serverPackets.notification( "Our bancho server is in maintenance mode. Please try to login again later." ) responseData += serverPackets.loginFailed() except exceptions.banchoRestartingException: # Bancho is restarting responseData += serverPackets.notification( "Bancho is restarting. Try again in a few minutes.") responseData += serverPackets.loginFailed() except exceptions.need2FAException: # User tried to log in from unknown IP responseData += serverPackets.needVerification() except exceptions.haxException: # Using oldoldold client, we don't have client data. Force update. # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.forceUpdate() except: log.error("Unknown error!\n```\n{}\n{}```".format( sys.exc_info(), traceback.format_exc())) finally: # Console and discord log if len(loginData) < 3: log.info( "Invalid bancho login request from **{}** (insufficient POST data)" .format(requestIP), "bunker") # Return token string and data return responseTokenString, responseData
def 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(tornadoRequest): # Data to return responseToken = None responseTokenString = "ayy" responseData = bytes() # Get IP from tornado request requestIP = tornadoRequest.getRequestIP() # Avoid exceptions clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"] osuVersion = "unknown" # Split POST body so we can get username/password/hardware data # 2:-3 thing is because requestData has some escape stuff that we don't need loginData = str(tornadoRequest.request.body)[2:-3].split("\\n") try: # Make sure loginData is valid if len(loginData) < 3: raise exceptions.invalidArgumentsException() # Get HWID, MAC address and more # Structure (new line = "|", already split) # [0] osu! version # [1] plain mac addressed, separated by "." # [2] mac addresses hash set # [3] unique ID # [4] disk ID splitData = loginData[2].split("|") osuVersion = splitData[0] timeOffset = int(splitData[1]) clientData = splitData[3].split(":")[:5] if len(clientData) < 4: raise exceptions.forceUpdateException() # Try to get the ID from username username = str(loginData[0]) userID = userUtils.getID(username) if not userID: # Invalid username raise exceptions.loginFailedException() if not userUtils.checkLogin(userID, loginData[1]): # Invalid password raise exceptions.loginFailedException() # Make sure we are not banned or locked priv = userUtils.getPrivileges(userID) if userUtils.isBanned( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginBannedException() if userUtils.isLocked( userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: raise exceptions.loginLockedException() # 2FA check if userUtils.check2FA(userID, requestIP): log.warning("Need 2FA check for user {}".format(loginData[0])) raise exceptions.need2FAException() # No login errors! # Verify this user (if pending activation) firstLogin = False if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware( userID): if userUtils.verifyUser(userID, clientData): # Valid account log.info("Account {} verified successfully!".format(userID)) glob.verifiedCache[str(userID)] = 1 firstLogin = True else: # Multiaccount detected log.info("Account {} NOT verified!".format(userID)) glob.verifiedCache[str(userID)] = 0 raise exceptions.loginBannedException() # Save HWID in db for multiaccount detection hwAllowed = userUtils.logHardware(userID, clientData, firstLogin) # This is false only if HWID is empty # if HWID is banned, we get restricted so there's no # need to deny bancho access if not hwAllowed: raise exceptions.haxException() # Log user IP userUtils.logIP(userID, requestIP) # Delete old tokens for that user and generate a new one isTournament = "tourney" in osuVersion if not isTournament: glob.tokens.deleteOldTokens(userID) responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset, tournament=isTournament) responseTokenString = responseToken.token # Check restricted mode (and eventually send message) responseToken.checkRestricted() # Send message if donor expires soon if responseToken.privileges & privileges.USER_DONOR > 0: expireDate = userUtils.getDonorExpire(responseToken.userID) if expireDate - int(time.time()) <= 86400 * 3: expireDays = round((expireDate - int(time.time())) / 86400) expireIn = "{} days".format( expireDays) if expireDays > 1 else "less than 24 hours" responseToken.enqueue( serverPackets.notification( "Your donor tag expires in {}! When your donor tag expires, you won't have any of the donor privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Ripple and you don't want to lose your donor privileges, you can donate again by clicking on 'Support us' on Ripple's website." .format(expireIn))) # Deprecate telegram 2fa and send alert if userUtils.deprecateTelegram2Fa(userID): responseToken.enqueue( serverPackets.notification( "As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://ripple.moe. Thank you for your patience." )) # Set silence end UNIX time in token responseToken.silenceEndTime = userUtils.getSilenceEnd(userID) # Get only silence remaining seconds silenceSeconds = responseToken.getSilenceSecondsLeft() # Get supporter/GMT userGMT = False userSupporter = True userTournament = False if responseToken.admin: userGMT = True if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0: userTournament = True # Server restarting check if glob.restarting: raise exceptions.banchoRestartingException() # Send login notification before maintenance message if glob.banchoConf.config["loginNotification"] != "": responseToken.enqueue( serverPackets.notification( glob.banchoConf.config["loginNotification"])) # Maintenance check if glob.banchoConf.config["banchoMaintenance"]: if not userGMT: # We are not mod/admin, delete token, send notification and logout glob.tokens.deleteToken(responseTokenString) raise exceptions.banchoMaintenanceException() else: # We are mod/admin, send warning notification and continue responseToken.enqueue( serverPackets.notification( "Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode." )) # Send all needed login packets responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds)) responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue( serverPackets.userSupporterGMT(userSupporter, userGMT, userTournament)) responseToken.enqueue(serverPackets.userPanel(userID, True)) responseToken.enqueue(serverPackets.userStats(userID, True)) # Channel info end (before starting!?! wtf bancho?) responseToken.enqueue(serverPackets.channelInfoEnd()) # Default opened channels # TODO: Configurable default channels chat.joinChannel(token=responseToken, channel="#osu") chat.joinChannel(token=responseToken, channel="#announce") # Join admin channel if we are an admin if responseToken.admin: chat.joinChannel(token=responseToken, channel="#admin") # Output channels info for key, value in glob.channels.channels.items(): if value.publicRead and not value.hidden: responseToken.enqueue(serverPackets.channelInfo(key)) # Send friends list responseToken.enqueue(serverPackets.friendList(userID)) # Send main menu icon if glob.banchoConf.config["menuIcon"] != "": responseToken.enqueue( serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) # Send online users' panels with glob.tokens: for _, token in glob.tokens.tokens.items(): if not token.restricted: responseToken.enqueue(serverPackets.userPanel( token.userID)) # Get location and country from ip.zxq.co or database if glob.localize: # Get location and country from IP latitude, longitude = locationHelper.getLocation(requestIP) countryLetters = locationHelper.getCountry(requestIP) country = countryHelper.getCountryID(countryLetters) else: # Set location to 0,0 and get country from db log.warning("Location skipped") latitude = 0 longitude = 0 countryLetters = "XX" country = countryHelper.getCountryID(userUtils.getCountry(userID)) # Set location and country responseToken.setLocation(latitude, longitude) responseToken.country = country # Set country in db if user has no country (first bancho login) if userUtils.getCountry(userID) == "XX": userUtils.setCountry(userID, countryLetters) # Send to everyone our userpanel if we are not restricted or tournament if not responseToken.restricted: glob.streams.broadcast("main", serverPackets.userPanel(userID)) # Set reponse data to right value and reset our queue responseData = responseToken.queue responseToken.resetQueue() except exceptions.loginFailedException: # Login failed error packet # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() except exceptions.invalidArgumentsException: # Invalid POST data # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.loginFailed() responseData += serverPackets.notification( "I see what you're doing...") except exceptions.loginBannedException: # Login banned error packet responseData += serverPackets.loginBanned() except exceptions.loginLockedException: # Login banned error packet responseData += serverPackets.loginLocked() except exceptions.banchoMaintenanceException: # Bancho is in maintenance mode responseData = bytes() if responseToken is not None: responseData = responseToken.queue responseData += serverPackets.notification( "Our bancho server is in maintenance mode. Please try to login again later." ) responseData += serverPackets.loginFailed() except exceptions.banchoRestartingException: # Bancho is restarting responseData += serverPackets.notification( "Bancho is restarting. Try again in a few minutes.") responseData += serverPackets.loginFailed() except exceptions.need2FAException: # User tried to log in from unknown IP responseData += serverPackets.needVerification() except exceptions.haxException: # Using oldoldold client, we don't have client data. Force update. # (we don't use enqueue because we don't have a token since login has failed) responseData += serverPackets.forceUpdate() responseData += serverPackets.notification( "Hory shitto, your client is TOO old! Nice prehistory! Please turn update it from the settings!" ) except: log.error("Unknown error!\n```\n{}\n{}```".format( sys.exc_info(), traceback.format_exc())) finally: # Console and discord log if len(loginData) < 3: log.info( "Invalid bancho login request from **{}** (insufficient POST data)" .format(requestIP), "bunker") # Return token string and data return responseTokenString, responseData
def alert(fro, chan, message): msg = ' '.join(message[:]).strip() if not msg: return False glob.streams.broadcast("main", serverPackets.notification(msg)) return False