Exemple #1
0
 def connect(self):
     try:
         self.sio.connect(self.sioServer)
         log.info('socketio server connected: {}'.format(self.sio.sid))
     except Exception as err:
         log.warning('socketio connect failed, err: {}'.format(err))
Exemple #2
0
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
Exemple #3
0
def handle(userToken, packetData):
    # Get usertoken data
    userID = userToken.userID
    username = userToken.username

    # Make sure we are not banned
    #if userUtils.isBanned(userID):
    #	userToken.enqueue(serverPackets.loginBanned())
    #	return

    # Send restricted message if needed
    #if userToken.restricted:
    #	userToken.checkRestricted(True)

    # Change action packet
    packetData = clientPackets.userActionChange(packetData)

    # If we are not in spectate status but we're spectating someone, stop spectating
    '''
if userToken.spectating != 0 and userToken.actionID != actions.WATCHING and userToken.actionID != actions.IDLE and userToken.actionID != actions.AFK:
	userToken.stopSpectating()

# If we are not in multiplayer but we are in a match, part match
if userToken.matchID != -1 and userToken.actionID != actions.MULTIPLAYING and userToken.actionID != actions.MULTIPLAYER and userToken.actionID != actions.AFK:
	userToken.partMatch()
		'''

    # Update cached stats if our pp changed if we've just submitted a score or we've changed gameMode
    #if (userToken.actionID == actions.PLAYING or userToken.actionID == actions.MULTIPLAYING) or (userToken.pp != userUtils.getPP(userID, userToken.gameMode)) or (userToken.gameMode != packetData["gameMode"]):

    # Update cached stats if we've changed gamemode
    if userToken.gameMode != packetData["gameMode"]:
        userToken.gameMode = packetData["gameMode"]
        userToken.updateCachedStats()

    # Always update action id, text, md5 and beatmapID
    userToken.actionID = packetData["actionID"]
    #userToken.actionID = packetData["actionText"]
    userToken.actionMd5 = packetData["actionMd5"]
    userToken.actionMods = packetData["actionMods"]
    userToken.beatmapID = packetData["beatmapID"]

    if bool(packetData["actionMods"] & 128) == True:
        userToken.relaxing = True
        userToken.autopiloting = False
        if userToken.actionID in (0, 1, 14):
            UserText = packetData["actionText"] + "on Relax"
        else:
            UserText = packetData["actionText"] + " on Relax"
        userToken.actionText = UserText
        userToken.updateCachedStats()
        """
		if userToken.relaxAnnounce == False:
			userToken.relaxAnnounce = True
			userToken.enqueue(serverPackets.notification("You're playing with Relax, we've changed the leaderboard to Relax."))
		"""
    #autopiloten
    elif bool(packetData["actionMods"] & 8192) == True:
        userToken.autopiloting = True
        userToken.relaxing = False
        if userToken.actionID in (0, 1, 14):
            UserText = packetData["actionText"] + "on Autopilot"
        else:
            UserText = packetData["actionText"] + " on Autopilot"
        userToken.actionText = UserText
        userToken.updateCachedStats()
    else:
        userToken.relaxing = False
        userToken.autopiloting = False
        UserText = packetData["actionText"]
        userToken.actionText = UserText
        userToken.updateCachedStats()

        if userToken.relaxAnnounce == True:
            userToken.relaxAnnounce = False
            userToken.enqueue(
                serverPackets.notification(
                    "You've disabled relax. We've changed back to the Regular leaderboard."
                ))

    glob.db.execute("UPDATE users_stats SET current_status = %s WHERE id = %s",
                    [UserText, userID])
    # Enqueue our new user panel and stats to us and our spectators
    recipients = [userToken]
    if len(userToken.spectators) > 0:
        for i in userToken.spectators:
            if i in glob.tokens.tokens:
                recipients.append(glob.tokens.tokens[i])

    for i in recipients:
        if i is not None:
            # Force our own packet
            force = True if i == userToken else False
            i.enqueue(serverPackets.userPanel(userID, force))
            i.enqueue(serverPackets.userStats(userID, force))

    # Console output
    log.info("{} changed action: {} [{}][{}][{}]".format(
        username, str(userToken.actionID), userToken.actionText,
        userToken.actionMd5, userToken.beatmapID))
Exemple #4
0
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
Exemple #5
0
            consoleHelper.printColored(
                "[!] Error while starting Datadog client! Please check your config.ini and run the server again",
                bcolors.RED)

        # IRC start message and console output
        glob.irc = generalUtils.stringToBool(glob.conf.config["irc"]["enable"])
        if glob.irc:
            # IRC port
            ircPort = 0
            try:
                ircPort = int(glob.conf.config["irc"]["port"])
            except ValueError:
                consoleHelper.printColored(
                    "[!] Invalid IRC port! Please check your config.ini and run the server again",
                    bcolors.RED)
            log.info("IRC server started!")
            consoleHelper.printColored(
                "> IRC server listening on 127.0.0.1:{}...".format(ircPort),
                bcolors.GREEN)
            threading.Thread(
                target=lambda: ircserver.main(port=ircPort)).start()
        else:
            consoleHelper.printColored("[!] Warning! IRC server is disabled!",
                                       bcolors.YELLOW)

        # Server port
        serverPort = 0
        try:
            serverPort = int(glob.conf.config["server"]["port"])
        except ValueError:
            consoleHelper.printColored(
Exemple #6
0
    def __init__(self,
                 matchID,
                 matchName,
                 matchPassword,
                 beatmapID,
                 beatmapName,
                 beatmapMD5,
                 gameMode,
                 hostUserID,
                 isTourney=False):
        """
		Create a new match object

		:param matchID: match progressive identifier
		:param matchName: match name, string
		:param matchPassword: match md5 password. Leave empty for no password
		:param beatmapID: beatmap ID
		:param beatmapName: beatmap name, string
		:param beatmapMD5: beatmap md5 hash, string
		:param gameMode: game mode ID. See gameModes.py
		:param hostUserID: user id of the host
		"""
        self.matchID = matchID
        self.streamName = "multi/{}".format(self.matchID)
        self.playingStreamName = "{}/playing".format(self.streamName)
        self.inProgress = False
        self.mods = 0
        self.matchName = matchName
        self.matchPassword = matchPassword
        self.beatmapID = beatmapID
        self.beatmapName = beatmapName
        self.beatmapMD5 = beatmapMD5
        self.hostUserID = hostUserID
        self.refs = []
        self.tourneyHost = -1

        self.gameMode = gameMode
        self.matchScoringType = matchScoringTypes.SCORE  # default values
        self.matchTeamType = matchTeamTypes.HEAD_TO_HEAD  # default value
        self.matchModMode = matchModModes.NORMAL  # default value
        self.seed = 0
        self.matchDataCache = bytes()
        self.isTourney = isTourney
        self.isLocked = False  # if True, users can't change slots/teams. Used in tourney matches
        self.isStarting = False
        self._lock = threading.Lock()
        self.createTime = int(time.time())
        self.games = []

        # Create all slots and reset them
        self.slots = []
        for _ in range(0, 16):
            self.slots.append(slot())

        # Create streams
        glob.streams.add(self.streamName)
        glob.streams.add(self.playingStreamName)

        # Create #multiplayer channel
        glob.channels.addHiddenChannel("#multi_{}".format(self.matchID))
        log.info("MPROOM{}: {} match created!".format(
            self.matchID, "Tourney" if self.isTourney else "Normal"))
Exemple #7
0
def handle(tornadoRequest):
    # Data to return
    responseToken = None
    responseTokenString = "ayy"
    responseData = bytes()

    # Get IP from tornado request
    requestIP = tornadoRequest.getRequestIP()

    # Avoid exceptions
    clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"]
    osuVersion = "unknown"

    # Split POST body so we can get username/password/hardware data
    # 2:-3 thing is because requestData has some escape stuff that we don't need
    loginData = str(tornadoRequest.request.body)[2:-3].split("\\n")
    try:
        # Make sure loginData is valid
        if len(loginData) < 3:
            raise exceptions.invalidArgumentsException()

        # Get HWID, MAC address and more
        # Structure (new line = "|", already split)
        # [0] osu! version
        # [1] plain mac addressed, separated by "."
        # [2] mac addresses hash set
        # [3] unique ID
        # [4] disk ID
        splitData = loginData[2].split("|")
        osuVersion = splitData[0]  # osu! version
        timeOffset = int(splitData[1])  # timezone
        showCity = int(splitData[2])  # allow to show city
        clientData = splitData[3].split(":")[:5]  # security hash
        blockNonFriendPM = int(splitData[4])  # allow PM
        if len(clientData) < 4:
            raise exceptions.forceUpdateException()

        # Try to get the ID from username
        username = str(loginData[0])
        userID = userUtils.getID(username)

        if not userID:
            # Invalid username
            raise exceptions.loginFailedException()
        if not userUtils.checkLogin(userID, loginData[1]):
            # Invalid password
            raise exceptions.loginFailedException()

        # Make sure we are not banned or locked
        priv = userUtils.getPrivileges(userID)
        if userUtils.isBanned(
                userID) and priv & privileges.USER_PENDING_VERIFICATION == 0:
            raise exceptions.loginBannedException()
        if userUtils.isLocked(
                userID) and priv & privileges.USER_PENDING_VERIFICATION == 0:
            raise exceptions.loginLockedException()

        # 2FA check
        if userUtils.check2FA(userID, requestIP):
            log.warning("Need 2FA check for user {}".format(loginData[0]))
            raise exceptions.need2FAException()

        # No login errors!

        # Verify this user (if pending activation)
        firstLogin = False
        if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware(
                userID):
            if userUtils.verifyUser(userID, clientData):
                # Valid account
                log.info("Account ID {} verified successfully!".format(userID))
                glob.verifiedCache[str(userID)] = 1
                firstLogin = True
            else:
                # Multiaccount detected
                log.info("Account ID {} NOT verified!".format(userID))
                glob.verifiedCache[str(userID)] = 0
                raise exceptions.loginBannedException()

        # Save HWID in db for multiaccount detection
        hwAllowed = userUtils.logHardware(userID, clientData, firstLogin)

        # This is false only if HWID is empty
        # if HWID is banned, we get restricted so there's no
        # need to deny bancho access
        if not hwAllowed:
            raise exceptions.haxException()

        # Log user IP
        userUtils.logIP(userID, requestIP)

        # Log user osuver
        kotrikhelper.setUserLastOsuVer(userID, osuVersion)

        # Delete old tokens for that user and generate a new one
        isTournament = "tourney" in osuVersion
        numericVersion = re.sub(r'[^0-9.]', '', osuVersion)
        if not isTournament:
            glob.tokens.deleteOldTokens(userID)
        if numericVersion < glob.conf.config["server"]["osuminver"]:
            raise exceptions.forceUpdateException()
        responseToken = glob.tokens.addToken(userID,
                                             requestIP,
                                             timeOffset=timeOffset,
                                             tournament=isTournament)
        responseTokenString = responseToken.token

        # Check restricted mode (and eventually send message)
        responseToken.checkRestricted()

        # Send message if donor expires soon
        if responseToken.privileges & privileges.USER_DONOR > 0:
            expireDate = userUtils.getDonorExpire(responseToken.userID)
            if expireDate - int(time.time()) <= 86400 * 3:
                expireDays = round((expireDate - int(time.time())) / 86400)
                expireIn = "{} days".format(
                    expireDays) if expireDays > 1 else "less than 24 hours"
                responseToken.enqueue(
                    serverPackets.notification(
                        "Your donor tag expires in {}! When your donor tag expires, you won't have any of the donor privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Ripple and you don't want to lose your donor privileges, you can donate again by clicking on 'Support us' on Ripple's website."
                        .format(expireIn)))

        # Deprecate telegram 2fa and send alert
        if userUtils.deprecateTelegram2Fa(userID):
            responseToken.enqueue(
                serverPackets.notification(
                    "As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://ripple.moe. Thank you for your patience."
                ))

        # Set silence end UNIX time in token
        responseToken.silenceEndTime = userUtils.getSilenceEnd(userID)

        # Get only silence remaining seconds
        silenceSeconds = responseToken.getSilenceSecondsLeft()

        # Get supporter/GMT
        userGMT = False
        if not userUtils.isRestricted(userID):
            userSupporter = True
        else:
            userSupporter = False
        userTournament = False
        if responseToken.admin:
            userGMT = True
        if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0:
            userTournament = True

        # Server restarting check
        if glob.restarting:
            raise exceptions.banchoRestartingException()
        """
		if userUtils.checkIfFlagged(userID):
			responseToken.enqueue(serverPackets.notification("Staff suspect you of cheat! You have 5 days to make a full pc startup liveplay, or you will get restricted and you'll have to wait a month to appeal!"))
		"""

        # Check If today is 4/20 (Peppy Day)
        if today == peppyday:
            if glob.conf.extra["mode"]["peppyday"]:
                responseToken.enqueue(
                    serverPackets.notification(
                        "Everyone on today will have peppy as their profile picture! Have fun on peppy day"
                    ))

        # Send login notification before maintenance message
        if glob.banchoConf.config["loginNotification"] != "":
            responseToken.enqueue(
                serverPackets.notification(
                    glob.banchoConf.config["loginNotification"]))

        # Maintenance check
        if glob.banchoConf.config["banchoMaintenance"]:
            if not userGMT:
                # We are not mod/admin, delete token, send notification and logout
                glob.tokens.deleteToken(responseTokenString)
                raise exceptions.banchoMaintenanceException()
            else:
                # We are mod/admin, send warning notification and continue
                responseToken.enqueue(
                    serverPackets.notification(
                        "Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode."
                    ))

        # BAN CUSTOM CHEAT CLIENTS
        # 0Ainu = First Ainu build
        # b20190326.2 = Ainu build 2 (MPGH PAGE 10)
        # b20190401.22f56c084ba339eefd9c7ca4335e246f80 = Ainu Aoba's Birthday Build
        # b20190906.1 = Unknown Ainu build? (unreleased, I think)
        # b20191223.3 = Unknown Ainu build? (Taken from most users osuver in cookiezi.pw)
        # b20190226.2 = hqOsu (hq-af)
        if glob.conf.extra["mode"]["anticheat"]:
            # Ainu Client 2020 update
            if tornadoRequest.request.headers.get("ainu") == "happy":
                log.info(
                    "Account ID {} tried to use Ainu (Cheat) Client 2020!".
                    format(userID))
                if userUtils.isRestricted(userID):
                    responseToken.enqueue(
                        serverPackets.notification(
                            "You're banned because you're currently using Ainu Client... Happy New Year 2020 and Enjoy your restriction :)"
                        ))
                    #if glob.conf.config["discord"]["enable"] == True:
                    webhook = aobaHelper.Webhook(
                        glob.conf.config["discord"]["anticheat"],
                        color=0xadd8e6,
                        footer="what a bitch [ Login Gate AC ]")
                    webhook.set_title(
                        title="Catched some cheater Account ID {}".format(
                            userID))
                    webhook.set_desc(
                        "{} tried to use Ainu (Cheat) Client 2020! AGAIN!!!".
                        format(username))
                    log.info("Sent to webhook {} DONE!!".format(
                        glob.conf.config["discord"]["enable"]))
                    aobaHelper.Webhook.post()
                else:
                    glob.tokens.deleteToken(userID)
                    userUtils.restrict(userID)
                    #if glob.conf.config["discord"]["enable"] == True:
                    webhook = aobaHelper.Webhook(
                        glob.conf.config["discord"]["anticheat"],
                        color=0xadd8e6,
                        footer="what a bitch [ Login Gate AC ]")
                    webhook.set_title(
                        title="Catched some cheater Account ID {}".format(
                            userID))
                    webhook.set_desc(
                        "{} tried to use Ainu (Cheat) Client 2020 and got restricted!"
                        .format(username))
                    log.info("Sent to webhook {} DONE!!".format(
                        glob.conf.config["discord"]["enable"]))
                    webhook.post()
                    raise exceptions.loginCheatClientsException()

            # Ainu Client 2019
            elif aobaHelper.getOsuVer(userID) in [
                    "0Ainu", "b20190326.2",
                    "b20190401.22f56c084ba339eefd9c7ca4335e246f80",
                    "b20190906.1", "b20191223.3"
            ]:
                log.info(
                    "Account ID {} tried to use Ainu (Cheat) Client!".format(
                        userID))
                if userUtils.isRestricted(userID):
                    responseToken.enqueue(
                        serverPackets.notification(
                            "You're banned because you're currently using Ainu Client. Enjoy your restriction :)"
                        ))
                    #if glob.conf.config["discord"]["enable"] == True:
                    webhook = aobaHelper.Webhook(
                        glob.conf.config["discord"]["anticheat"],
                        color=0xadd8e6,
                        footer="what a bitch [ Login Gate AC ]")
                    webhook.set_title(
                        title="Catched some cheater Account ID {}".format(
                            userID))
                    webhook.set_desc(
                        "{} tried to use Ainu (Cheat) Client! AGAIN!!!".format(
                            username))
                    log.info("Sent to webhook {} DONE!!".format(
                        glob.conf.config["discord"]["enable"]))
                    webhook.post()
                else:
                    glob.tokens.deleteToken(userID)
                    userUtils.restrict(userID)
                    #if glob.conf.config["discord"]["enable"] == True:
                    webhook = aobaHelper.Webhook(
                        glob.conf.config["discord"]["anticheat"],
                        color=0xadd8e6,
                        footer="what a bitch [ Login Gate AC ]")
                    webhook.set_title(
                        title="Catched some cheater Account ID {}".format(
                            userID))
                    webhook.set_desc(
                        "{} tried to use Ainu (Cheat) Client and got restricted!"
                        .format(username))
                    log.info("Sent to webhook {} DONE!!".format(
                        glob.conf.config["discord"]["enable"]))
                    webhook.post()
                    raise exceptions.loginCheatClientsException()

            # hqOsu
            elif aobaHelper.getOsuVer(userID) == "b20190226.2":
                log.info("Account ID {} tried to use hqOsu!".format(userID))
                if userUtils.isRestricted(userID):
                    responseToken.enqueue(
                        serverPackets.notification(
                            "Trying to use hqOsu in here? Well... No, sorry. We don't allow cheats here. Go play https://cookiezi.pw or other cheating server."
                        ))
                    #if glob.conf.config["discord"]["enable"] == True:
                    webhook = aobaHelper.Webhook(
                        glob.conf.config["discord"]["anticheat"],
                        color=0xadd8e6,
                        footer="what a bitch [ Login Gate AC ]")
                    webhook.set_title(
                        title="Catched some cheater Account ID {}".format(
                            userID))
                    webhook.set_desc(
                        "{} tried to use hqOsu! AGAIN!!!".format(username))
                    log.info("Sent to webhook {} DONE!!".format(
                        glob.conf.config["discord"]["enable"]))
                    webhook.post()
                else:
                    glob.tokens.deleteToken(userID)
                    userUtils.restrict(userID)
                    #if glob.conf.config["discord"]["enable"] == True:
                    webhook = aobaHelper.Webhook(
                        glob.conf.config["discord"]["anticheat"],
                        color=0xadd8e6,
                        footer="What a bitch [ Login Gate AC ]")
                    webhook.set_title(
                        title="Catched some cheater Account ID {}".format(
                            userID))
                    webhook.set_desc(
                        "{} tried to use hqOsu and got restricted!".format(
                            username))
                    log.info("Sent to webhook {} DONE!!".format(
                        glob.conf.config["discord"]["enable"]))
                    webhook.post()
                    raise exceptions.loginCheatClientsException()

        # Send all needed login packets
        responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds))
        responseToken.enqueue(serverPackets.userID(userID))
        responseToken.enqueue(serverPackets.protocolVersion())
        responseToken.enqueue(
            serverPackets.userSupporterGMT(userSupporter, userGMT,
                                           userTournament))
        responseToken.enqueue(serverPackets.userPanel(userID, True))
        responseToken.enqueue(serverPackets.userStats(userID, True))

        # Channel info end (before starting!?! wtf bancho?)
        responseToken.enqueue(serverPackets.channelInfoEnd())
        # Default opened channels
        # TODO: Configurable default channels
        chat.joinChannel(token=responseToken, channel="#osu")
        chat.joinChannel(token=responseToken, channel="#announce")

        # Join admin channel if we are an admin
        if responseToken.admin:
            chat.joinChannel(token=responseToken, channel="#admin")

        # Output channels info
        for key, value in glob.channels.channels.items():
            if value.publicRead and not value.hidden:
                responseToken.enqueue(serverPackets.channelInfo(key))

        # Send friends list
        responseToken.enqueue(serverPackets.friendList(userID))

        # Send main menu icon
        if glob.banchoConf.config["menuIcon"] != "":
            responseToken.enqueue(
                serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"]))

        # Send online users' panels
        with glob.tokens:
            for _, token in glob.tokens.tokens.items():
                if not token.restricted:
                    responseToken.enqueue(serverPackets.userPanel(
                        token.userID))

        # Get location and country from ip.zxq.co or database
        if glob.localize:
            # Get location and country from IP
            latitude, longitude = locationHelper.getLocation(requestIP)
            countryLetters = locationHelper.getCountry(requestIP)
            country = countryHelper.getCountryID(countryLetters)
        else:
            # Set location to 0,0 and get country from db
            log.warning("Location skipped")
            latitude = 0
            longitude = 0
            countryLetters = "XX"
            country = countryHelper.getCountryID(userUtils.getCountry(userID))

        # Set location and country
        responseToken.setLocation(latitude, longitude)
        responseToken.country = country

        # Set country in db if user has no country (first bancho login)
        if userUtils.getCountry(userID) == "XX":
            userUtils.setCountry(userID, countryLetters)

        # Send to everyone our userpanel if we are not restricted or tournament
        if not responseToken.restricted:
            glob.streams.broadcast("main", serverPackets.userPanel(userID))

        # Set reponse data to right value and reset our queue
        responseData = responseToken.queue
        responseToken.resetQueue()
    except exceptions.loginFailedException:
        # Login failed error packet
        # (we don't use enqueue because we don't have a token since login has failed)
        responseData += serverPackets.loginFailed()
    except exceptions.invalidArgumentsException:
        # Invalid POST data
        # (we don't use enqueue because we don't have a token since login has failed)
        responseData += serverPackets.loginFailed()
        responseData += serverPackets.notification(
            "I see what you're doing...")
    except exceptions.loginBannedException:
        # Login banned error packet
        responseData += serverPackets.loginBanned()
    except exceptions.loginLockedException:
        # Login banned error packet
        responseData += serverPackets.loginLocked()
    except exceptions.loginCheatClientsException:
        # Banned for logging in with cheats
        responseData += serverPackets.loginCheats()
    except exceptions.banchoMaintenanceException:
        # Bancho is in maintenance mode
        responseData = bytes()
        if responseToken is not None:
            responseData = responseToken.queue
        responseData += serverPackets.notification(
            "Our bancho server is in maintenance mode. Please try to login again later."
        )
        responseData += serverPackets.loginFailed()
    except exceptions.banchoRestartingException:
        # Bancho is restarting
        responseData += serverPackets.notification(
            "Bancho is restarting. Try again in a few minutes.")
        responseData += serverPackets.loginFailed()
    except exceptions.need2FAException:
        # User tried to log in from unknown IP
        responseData += serverPackets.needVerification()
    except exceptions.haxException:
        # Uh...
        responseData += serverPackets.notification("Your HWID is banned.")
        responseData += serverPackets.loginFailed()
    except exceptions.forceUpdateException:
        # This happens when you:
        # - Using older build than config set
        # - Using oldoldold client, we don't have client data. Force update.
        # (we don't use enqueue because we don't have a token since login has failed)
        responseData += serverPackets.forceUpdate()
    except:
        log.error("Unknown error!\n```\n{}\n{}```".format(
            sys.exc_info(), traceback.format_exc()))
    finally:
        # Console and discord log
        if len(loginData) < 3:
            log.info(
                "Invalid bancho login request from **{}** (insufficient POST data)"
                .format(requestIP), "bunker")

        # Return token string and data
        return responseTokenString, responseData
    def asyncPost(self):
        try:
            if glob.debug:
                requestsManager.printArguments(self)

            # Make sure screenshot file was passed
            if "ss" not in self.request.files:
                raise exceptions.invalidArgumentsException(MODULE_NAME)

            # Check user auth because of sneaky people
            if not requestsManager.checkArguments(self.request.arguments,
                                                  ["u", "p"]):
                raise exceptions.invalidArgumentsException(MODULE_NAME)
            username = self.get_argument("u")
            password = self.get_argument("p")
            ip = self.getRequestIP()
            userID = userUtils.getID(username)
            if not userUtils.checkLogin(userID, password):
                raise exceptions.loginFailedException(MODULE_NAME, username)
            if userUtils.check2FA(userID, ip):
                raise exceptions.need2FAException(MODULE_NAME, username, ip)

            # Rate limit
            if glob.redis.get("lets:screenshot:{}".format(userID)) is not None:
                self.write("no")
                return
            glob.redis.set("lets:screenshot:{}".format(userID), 1, 60)

            #check if user folder exist
            screenshotID = generalUtils.randomString(8)
            if not os.path.isdir("{}/{}/".format(
                    glob.conf.config["server"]["storagepath"], userID)):
                os.mkdir("{}/{}/".format(
                    glob.conf.config["server"]["storagepath"], userID))

            # Get a random screenshot id
            found = False
            screenshotID = ""
            while not found:
                screenshotID = generalUtils.randomString(8)
                if not os.path.isfile("{}/{}/{}.jpg".format(
                        glob.conf.config["server"]["storagepath"], userID,
                        screenshotID)):
                    found = True

            # Write screenshot file to screenshots folder
            with open(
                    "{}/{}/{}.jpg".format(
                        glob.conf.config["server"]["storagepath"], userID,
                        screenshotID), "wb") as f:
                f.write(self.request.files["ss"][0]["body"])

            # Output
            log.info("New screenshot ({})".format(screenshotID))

            # Return screenshot link
            self.write("https://storage.aeris-dev.pw/get/{}/{}.jpg".format(
                userID, screenshotID))
        except exceptions.need2FAException:
            pass
        except exceptions.invalidArgumentsException:
            pass
        except exceptions.loginFailedException:
            pass
Exemple #9
0
    def asyncGet(self):
        try:
            # Get request ip
            ip = self.getRequestIP()

            # Argument check
            if not requestsManager.checkArguments(self.request.arguments,
                                                  ["u", "h"]):
                raise exceptions.invalidArgumentsException(MODULE_NAME)

            # Get user ID
            username = self.get_argument("u")
            userID = userUtils.getID(username)
            if userID is None:
                self.write("error: pass\n")
                return

            # Check login
            log.info("{} ({}) wants to connect".format(username, userID))
            if not userUtils.checkLogin(userID, self.get_argument("h"), ip):
                self.write("error: pass\n")
                return

            # Ban check
            if userUtils.isBanned(userID):
                return

            # Lock check
            if userUtils.isLocked(userID):
                return
            # 2FA check
            if userUtils.check2FA(userID, ip):
                self.write("error: verify\n")

            # Update latest activity
            userUtils.updateLatestActivity(userID)

            if "x" in self.request.arguments:
                if len(self.get_argument("x")) > 4:
                    '''
					When "x" is found in the arguments, it means two things,
					1. "Monitor" has just been triggered (desktop screenshot """"""anticheat"""""")
					2. Files named "LL" (used by *a certain cheat website* for login data) have been found on the users computer.
					This should *NEVER* happen, but just incase it does, i'll send a notification to the discord.
					'''
                    webhook = Webhook(
                        glob.conf.config["discord"]
                        ["ahook"],  #send shit to discord hq
                        color=0xc32c74,
                        footer="stupid anticheat")
                    if glob.conf.config["discord"]["enable"]:
                        webhook.set_title(
                            title=f"Catched some cheater {username} ({userID})"
                        )
                        webhook.set_desc(
                            f'They just tried to send bancho_monitor and they have LL files!'
                        )
                        webhook.set_footer(text="peppycode anticheat")
                        webhook.post()

            # Get country and output it
            country = glob.db.fetch(
                "SELECT country FROM users_stats WHERE id = %s",
                [userID])["country"]
            self.write(country)
        except exceptions.invalidArgumentsException:
            pass
        except exceptions.loginFailedException:
            self.write("error: pass\n")
        except exceptions.userBannedException:
            pass
        except exceptions.userLockedException:
            pass
        except exceptions.need2FAException:
            self.write("error: verify\n")
Exemple #10
0
    def asyncPost(self):
        try:
            if glob.conf["DEBUG"]:
                requestsManager.printArguments(self)

            # Make sure screenshot file was passed
            if "ss" not in self.request.files:
                raise exceptions.invalidArgumentsException(MODULE_NAME)

            # Check user auth because of sneaky people
            if not requestsManager.checkArguments(self.request.arguments,
                                                  ["u", "p"]):
                raise exceptions.invalidArgumentsException(MODULE_NAME)
            username = self.get_argument("u")
            password = self.get_argument("p")
            ip = self.getRequestIP()
            userID = userUtils.getID(username)
            if not userUtils.checkLogin(userID, password):
                raise exceptions.loginFailedException(MODULE_NAME, username)
            if userUtils.check2FA(userID, ip):
                raise exceptions.need2FAException(MODULE_NAME, username, ip)

            # Rate limit
            if glob.redis.get("lets:screenshot:{}".format(userID)) is not None:
                self.write("no")
                return
            glob.redis.set("lets:screenshot:{}".format(userID), 1, 60)

            # Get a random screenshot id
            hasS3 = bool(glob.conf["S3_SCREENSHOTS_BUCKET"]) \
             and bool(glob.conf["S3_SCREENSHOTS_REGION"]) \
             and bool(glob.conf["S3_SCREENSHOTS_ENDPOINT_URL"])
            found = False
            screenshotID = ""
            while not found:
                screenshotID = generalUtils.randomString(8)
                if hasS3:
                    try:
                        glob.threadScope.s3Screenshots.head_object(
                            Bucket=glob.conf["S3_SCREENSHOTS_BUCKET"],
                            Key=f"{screenshotID}.jpg")
                        found = False
                    except botocore.errorfactory.ClientError:
                        found = True
                else:
                    found = not os.path.isfile("{}/{}.jpg".format(
                        glob.conf["SCREENSHOTS_FOLDER"], screenshotID))

            # Output
            log.info("New screenshot ({})".format(screenshotID))

            # Write screenshot file to .data folder
            if hasS3:
                with io.BytesIO(self.request.files["ss"][0]["body"]) as f:
                    glob.threadScope.s3Screenshots.upload_fileobj(
                        f,
                        glob.conf["S3_SCREENSHOTS_BUCKET"],
                        f"{screenshotID}.jpg",
                        ExtraArgs={"ACL": "public-read"})
            else:
                with open(
                        "{}/{}.jpg".format(glob.conf["SCREENSHOTS_FOLDER"],
                                           screenshotID), "wb") as f:
                    f.write(self.request.files["ss"][0]["body"])

            # Return screenshot link
            self.write("{}/ss/{}.jpg".format(glob.conf["SERVER_URL"],
                                             screenshotID))
        except exceptions.need2FAException:
            pass
        except exceptions.invalidArgumentsException:
            pass
        except exceptions.loginFailedException:
            pass
Exemple #11
0
    def asyncGet(self):
        try:
            # OOF
            UsingRelax = False
            UsingAuto = False

            # Get request ip
            ip = self.getRequestIP()

            # Check arguments
            if not requestsManager.checkArguments(self.request.arguments,
                                                  ["c", "u", "h"]):
                raise exceptions.invalidArgumentsException(MODULE_NAME)

            # Get arguments
            username = self.get_argument("u")
            password = self.get_argument("h")
            replayID = self.get_argument("c")

            # Login check
            userID = userUtils.getID(username)
            if userID == 0:
                raise exceptions.loginFailedException(MODULE_NAME, userID)
            if not userUtils.checkLogin(userID, password, ip):
                raise exceptions.loginFailedException(MODULE_NAME, username)
            if userUtils.check2FA(userID, ip):
                raise exceptions.need2FAException(MODULE_NAME, username, ip)

            # Get user ID
            current_gamemode_redis = "lets:user_current_gamemode:{}".format(
                userID)
            cgamemode = int(glob.redis.get(current_gamemode_redis))
            replayData = glob.db.fetch(
                "SELECT scores.*, users.username AS uname FROM scores LEFT JOIN users ON scores.userid = users.id WHERE scores.id = %s",
                [replayID])
            if cgamemode == 3:
                log.debug("autopilot")
                UsingRelax = False
                usingAuto = True
                fileName = "{}_ap/replay_{}.osr".format(
                    glob.conf.config["server"]["replayspath"], replayID)
            if cgamemode == 2:
                log.debug("relax")
                fileName = "{}_relax/replay_{}.osr".format(
                    glob.conf.config["server"]["replayspath"], replayID)
                UsingRelax = True
                UsingAuto = False
            if cgamemode == 1:
                log.debug("std")
                UsingRelax = False
                UsingAuto = False
                fileName = "{}/replay_{}.osr".format(
                    glob.conf.config["server"]["replayspath"], replayID)

            # Increment 'replays watched by others' if needed
            if replayData is not None:
                if username != replayData["uname"]:
                    userUtils.incrementReplaysWatched(replayData["userid"],
                                                      replayData["play_mode"])

            Play = "VANILLA"
            if UsingRelax:
                Play = "RELAX"
            if UsingAuto:
                Play = "AUTOPILOT"
            # Serve replay
            log.info("[{}] Serving replay_{}.osr".format(Play, replayID))

            if os.path.isfile(fileName):
                with open(fileName, "rb") as f:
                    fileContent = f.read()
                self.write(fileContent)
            else:
                log.info("Replay {} doesn't exist".format(replayID))
                self.write("")
        except exceptions.invalidArgumentsException:
            pass
        except exceptions.need2FAException:
            pass
        except exceptions.loginFailedException:
            pass
Exemple #12
0
    def start(self):
        """
		Start IRC server main loop

		:return:
		"""
        # Sentry
        sentryClient = None
        if glob.sentry:
            sentryClient = raven.Client(glob.conf.config["sentry"]["ircdns"])

        serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        try:
            serversocket.bind(("0.0.0.0", self.port))
        except socket.error as e:
            log.error("[IRC] Could not bind port {}:{}".format(self.port, e))
            sys.exit(1)
        serversocket.listen(5)
        lastAliveCheck = time.time()

        # Main server loop
        while True:
            try:
                (iwtd, owtd, ewtd) = select.select(
                    [serversocket] + [x.socket for x in self.clients.values()],
                    [
                        x.socket for x in self.clients.values()
                        if x.writeBufferSize() > 0
                    ], [], 1)

                # Handle incoming connections
                for x in iwtd:
                    if x in self.clients:
                        self.clients[x].readSocket()
                    else:
                        (conn, addr) = x.accept()
                        try:
                            self.clients[conn] = Client(self, conn)
                            log.info(
                                "[IRC] Accepted connection from {}:{}".format(
                                    addr[0], addr[1]))
                        except socket.error:
                            try:
                                conn.close()
                            except:
                                pass

                # Handle outgoing connections
                for x in owtd:
                    if x in self.clients:  # client may have been disconnected
                        self.clients[x].writeSocket()

                # Make sure all IRC clients are still connected
                now = time.time()
                if lastAliveCheck + 10 < now:
                    for client in list(self.clients.values()):
                        client.checkAlive()
                    lastAliveCheck = now
            except:
                log.error("[IRC] Unknown error!\n```\n{}\n{}```".format(
                    sys.exc_info(), traceback.format_exc()))
                if glob.sentry and sentryClient is not None:
                    sentryClient.captureException()
def handle(userToken, packetData):
	# Read new settings
	packetData = clientPackets.changeMatchSettings(packetData)

	# Get match ID
	matchID = userToken.matchID
		
	# Make sure the match exists
	if matchID not in glob.matches.matches:
		return

	# Host check
	with glob.matches.matches[matchID] as match:
		if userToken.userID != match.hostUserID:
			return

		# Some dank memes easter egg
		memeTitles = [
			"OWC 2020",
			"AC is a duck",
			"Dank memes",
			"1337ms Ping",
			"Iscriviti a Xenotoze",
			"...e i marò?",
			"Superman dies",
			"The brace is on fire",
			"print_foot()",
			"#FREEZEBARKEZ",
			"osu!thailand devs are actually cats",
			"Thank Mr Shaural",
			"NEVER GIVE UP",
			"T I E D  W I T H  U N I T E D",
			"HIGHEST HDHR LOBBY OF ALL TIME",
			"This is gasoline and I set myself on fire",
			"Everyone is cheating apparently",
			"Kurwa mac",
			"TATOE",
			"This is not your drama landfill.",
			"I like cheese",
			"AOBA IS NOT A CAT HE IS A DO(N)G",
			"YAMI NI NOMARE YO"
		]

		# Set match name
		match.matchName = packetData["matchName"] if packetData["matchName"] != "meme" else random.choice(memeTitles)

		# Update match settings
		# NOBODY SHOULD INTRUDE A SINGLE PLAYER MATCH AFTER ALL (tempfix)
		inProgress = bool(packetData["inProgress"])
		slotTaken = [slot for slot in match.slots if slot.status & slotStatuses.OCCUPIED]
		slotPlay   = [slot for slot in slotTaken if slot.status & slotStatuses.PLAYING]
		# log.info("MPROOM{}: ProgPkt({}) SlotPlay({} left)".format(match.matchID,inProgress,len(slotPlay)))
		if len(slotTaken) <= 1:
			match.inProgress = False
		else:
			match.inProgress = len(slotPlay) > 0 and inProgress
		if packetData["matchPassword"] != "":
			match.matchPassword = generalUtils.stringMd5(packetData["matchPassword"])
		else:
			match.matchPassword = ""
		match.beatmapName = packetData["beatmapName"]
		match.beatmapID = packetData["beatmapID"]
		match.hostUserID = packetData["hostUserID"]
		match.gameMode = packetData["gameMode"]

		oldBeatmapMD5 = match.beatmapMD5
		oldMods = match.mods
		oldMatchTeamType = match.matchTeamType

		match.mods = packetData["mods"]
		match.beatmapMD5 = packetData["beatmapMD5"]
		match.matchScoringType = packetData["scoringType"]
		match.matchTeamType = packetData["teamType"]
		match.matchModMode = packetData["multiSpecial"]

		# Reset ready if needed
		if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5:
			match.resetReady()

		# Reset mods if needed
		if match.matchModMode & matchModModes.FREE_MOD:
			# TODO: Implement a FREEMOD barrier configuration
			#   which works as follows:
			#   - turn off all FM mods on main mods
			#   - turn only FM mods on personal/player's free mods
			# match.mods = 0
			if match.freeModHandler == matchFreeModTypes.datenshi:
				for i, slot in zip(range(len(match.slots)), match.slots):
					slot.mods = packetData['slot{}Mods'.format(i)]
			else:
				match.mods = match.mods & ~mods.FM
				for i, slot in zip(range(len(match.slots)), match.slots):
					#if match.hostUserID == 3:
					#	log.info("MPROOM{} - Slot {} Mods {}".format(match.matchID, i + 1, slot.mods))
					slot.mods = packetData['slot{}Mods'.format(i)] & mods.FM
		else:
			# Synchronize mods if it's no FM
			for i, slot in zip(range(len(match.slots)), match.slots):
				slot.mods = match.mods
		
		match.seed = packetData['seed']

		# Initialize teams if team type changed
		if match.matchTeamType != oldMatchTeamType:
			match.initializeTeams()

		# Force no freemods if tag coop
		# if match.matchTeamType == matchTeamTypes.TAG_COOP or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
		#	match.matchModMode = matchModModes.NORMAL

		# Send updated settings
		match.sendUpdates()

		# Console output
		log.info("MPROOM{}: Updated room settings".format(match.matchID))
Exemple #14
0
 def handleDisconnect(self):
     self.sio.disconnect()
     log.info('socketio server disconnect!')
     log.info('try to reconnecting...')
     self.connect()
     log.info('socketio server reconnected!')
Exemple #15
0
    def autorankCheck(beatmap, recursive=True):
        # No autorank check for frozen maps
        if beatmap.rankedStatusFrozen not in (0, 3):
            return
        # No autorank check for already ranked beatmap
        # Only handle this if the map is ranked by autorank.
        if beatmap.rankedStatusFrozen != 3 and beatmap.rankedStatus >= 2:
            return
        # Only check if the map is autorankable.
        if not autorankFlagOK(beatmap.beatmapID):
            return
        # Check recursiveness
        if recursive:
            beatmap_id_query = glob.db.fetchAll(
                'select beatmap_md5, beatmap_id from beatmaps where beatmapset_id = %s',
                [beatmap.beatmapSetID])
            for beatmap_id_data in beatmap_id_query:
                # Skip self.
                if int(beatmap_id_data['beatmap_id']) == beatmap.beatmapID:
                    continue
                siblingmap = bm.beatmap(None, beatmap.beatmapSetID)
                siblingmap.setData(beatmap_id_data['beatmap_md5'],
                                   beatmap.beatmapSetID, True)
        # Not checking maps with non updated date.
        #if recursive:
        #log.info(f"Checking {beatmap.artist} - {beatmap.title} for autorank eligiblity.")
        # log.info(f"Checking {beatmap.songName} for autorank eligiblity.")
        obtainDateTime = lambda t: datetime.datetime.strptime(
            t, "%Y-%m-%d %H:%M:%S")
        obtainUnixClock = lambda t: int(time.mktime(t.timetuple()))
        if beatmap.updateDate == 0:
            log.info(f"Updating {beatmap.fileMD5} data")
            data = osuapiHelper.osuApiRequest('get_beatmaps',
                                              'h={}'.format(beatmap.fileMD5))
            if not data:
                return
            dateTouch = obtainDateTime(data['last_update'])
            beatmap.updateDate = obtainUnixClock(dateTouch)
        else:
            dateTouch = datetime.datetime.fromtimestamp(beatmap.updateDate)

        dateNow = datetime.datetime.today()
        dateQualify = dateTouch + datetime.timedelta(days=GRAVEYARD_DAYS -
                                                     QUALIFIED_DAYS)
        dateRanked = dateTouch + datetime.timedelta(days=GRAVEYARD_DAYS)
        forLove = autorankFlagForLove(beatmap.beatmapID)
        rankStatus = beatmap.rankedStatus
        needWipe = False
        if dateNow >= dateRanked:
            needWipe = rankStatus == rankedStatuses.QUALIFIED
            if forLove:
                log.debug(f"Considering {beatmap.fileMD5} to be loved")
                beatmap.rankedStatus = rankedStatuses.LOVED
            else:
                log.debug(f"Considering {beatmap.fileMD5} on ranking")
                beatmap.rankedStatus = rankedStatuses.RANKED
            beatmap.rankedStatusFrozen = 3
        elif dateNow >= dateQualify and not forLove:
            log.debug(f"Considering {beatmap.fileMD5} for qualified")
            beatmap.rankedStatus = rankedStatuses.QUALIFIED
            beatmap.rankedStatusFrozen = 3
        else:
            needWipe = rankStatus >= rankedStatuses.RANKED
            beatmap.rankedStatus = rankedStatuses.PENDING
            beatmap.rankedStatusFrozen = 0
        if rankStatus != beatmap.rankedStatus:
            dbBeatmap = glob.db.fetch(
                'select ranked_status_freezed as f from beatmaps where beatmap_md5 = %s',
                [beatmap.fileMD5])
            oldFreeze = 0
            if dbBeatmap is not None:
                oldFreeze = dbBeatmap['f']
            glob.db.execute(
                'update beatmaps set ranked_status_freezed = 0 where beatmap_md5 = %s',
                [beatmap.fileMD5])
            if oldFreeze != beatmap.rankedStatusFrozen and recursive:
                autorankAnnounce(beatmap)
            glob.db.execute(
                'update beatmaps set rankedby = %s where beatmap_md5 = %s',
                [autorankUserID(beatmap.beatmapID), beatmap.fileMD5])
        if needWipe:
            log.info(f"Wiping {beatmap.fileMD5} leaderboard")
            beatmap.clearLeaderboard()
            pass
Exemple #16
0
def handle(tornadoRequest):
    # Data to return
    responseToken = None
    responseTokenString = "ayy"
    responseData = bytes()

    # Get IP from tornado request
    requestIP = tornadoRequest.getRequestIP()

    # Avoid exceptions
    clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"]
    osuVersion = "unknown"

    # Split POST body so we can get username/password/hardware data
    # 2:-3 thing is because requestData has some escape stuff that we don't need
    loginData = str(tornadoRequest.request.body)[2:-3].split("\\n")
    try:
        # Make sure loginData is valid
        if len(loginData) < 3:
            raise exceptions.invalidArgumentsException()

        # Get HWID, MAC address and more
        # Structure (new line = "|", already split)
        # [0] osu! version
        # [1] plain mac addressed, separated by "."
        # [2] mac addresses hash set
        # [3] unique ID
        # [4] disk ID
        splitData = loginData[2].split("|")
        osuVersion = splitData[0]
        timeOffset = int(splitData[1])
        clientData = splitData[3].split(":")[:5]
        if len(clientData) < 4:
            raise exceptions.forceUpdateException()

        # Try to get the ID from username
        username = str(loginData[0])
        userID = userUtils.getID(username)

        if not userID:
            # Invalid username
            raise exceptions.loginFailedException()
        if not userUtils.checkLogin(userID, loginData[1]):
            # Invalid password
            raise exceptions.loginFailedException()

        # Make sure we are not banned or locked
        priv = userUtils.getPrivileges(userID)
        if userUtils.isBanned(
                userID
        ) == True and priv & privileges.USER_PENDING_VERIFICATION == 0:
            raise exceptions.loginBannedException()
        if userUtils.isLocked(
                userID
        ) == True and priv & privileges.USER_PENDING_VERIFICATION == 0:
            raise exceptions.loginLockedException()

        # 2FA check
        if userUtils.check2FA(userID, requestIP):
            log.warning("Need 2FA check for user {}".format(loginData[0]))
            raise exceptions.need2FAException()

        # No login errors!

        # Verify this user (if pending activation)
        firstLogin = False
        if priv & privileges.USER_PENDING_VERIFICATION > 0 or userUtils.hasVerifiedHardware(
                userID) == False:
            if userUtils.verifyUser(userID, clientData):
                # Valid account
                log.info("Account {} verified successfully!".format(userID))
                glob.verifiedCache[str(userID)] = 1
                firstLogin = True
            else:
                # Multiaccount detected
                log.info("Account {} NOT verified!".format(userID))
                glob.verifiedCache[str(userID)] = 0
                raise exceptions.loginBannedException()

        # Save HWID in db for multiaccount detection
        hwAllowed = userUtils.logHardware(userID, clientData, firstLogin)

        # This is false only if HWID is empty
        # if HWID is banned, we get restricted so there's no
        # need to deny bancho access
        if not hwAllowed:
            raise exceptions.haxException()

        # Log user IP
        userUtils.logIP(userID, requestIP)

        # Delete old tokens for that user and generate a new one
        isTournament = "tourney" in osuVersion
        if not isTournament:
            glob.tokens.deleteOldTokens(userID)
        responseToken = glob.tokens.addToken(userID,
                                             requestIP,
                                             timeOffset=timeOffset,
                                             tournament=isTournament)
        responseTokenString = responseToken.token

        # Check restricted mode (and eventually send message)
        responseToken.checkRestricted()

        # Send message if donor expires soon
        if responseToken.privileges & privileges.USER_DONOR > 0:
            expireDate = userUtils.getDonorExpire(responseToken.userID)
            if expireDate - int(time.time()) <= 86400 * 3:
                expireDays = round((expireDate - int(time.time())) / 86400)
                expireIn = "{} days".format(
                    expireDays) if expireDays > 1 else "less than 24 hours"
                responseToken.enqueue(
                    serverPackets.notification(
                        "Your donor tag expires in {}! When your donor tag expires, you won't have any of the donor privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Ripple and you don't want to lose your donor privileges, you can donate again by clicking on 'Support us' on Ripple's website."
                        .format(expireIn)))

        # Set silence end UNIX time in token
        responseToken.silenceEndTime = userUtils.getSilenceEnd(userID)

        # Get only silence remaining seconds
        silenceSeconds = responseToken.getSilenceSecondsLeft()

        # Get supporter/GMT
        userGMT = False
        userSupporter = True
        userTournament = False
        if responseToken.admin:
            userGMT = True
        if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0:
            userTournament = True

        # Server restarting check
        if glob.restarting:
            raise exceptions.banchoRestartingException()

        # Send login notification before maintenance message
        if glob.banchoConf.config["loginNotification"] != "":
            responseToken.enqueue(
                serverPackets.notification(
                    glob.banchoConf.config["loginNotification"]))

        # Maintenance check
        if glob.banchoConf.config["banchoMaintenance"]:
            if not userGMT:
                # We are not mod/admin, delete token, send notification and logout
                glob.tokens.deleteToken(responseTokenString)
                raise exceptions.banchoMaintenanceException()
            else:
                # We are mod/admin, send warning notification and continue
                responseToken.enqueue(
                    serverPackets.notification(
                        "Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode."
                    ))

        # Send all needed login packets
        responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds))
        responseToken.enqueue(serverPackets.userID(userID))
        responseToken.enqueue(serverPackets.protocolVersion())
        responseToken.enqueue(
            serverPackets.userSupporterGMT(userSupporter, userGMT,
                                           userTournament))
        responseToken.enqueue(serverPackets.userPanel(userID, True))
        responseToken.enqueue(serverPackets.userStats(userID, True))

        # Channel info end (before starting!?! wtf bancho?)
        responseToken.enqueue(serverPackets.channelInfoEnd())
        # Default opened channels
        # TODO: Configurable default channels
        chat.joinChannel(token=responseToken, channel="#osu")
        chat.joinChannel(token=responseToken, channel="#announce")
        chat.joinChannel(token=responseToken, channel="#nowranked")

        # Join admin channel if we are an admin
        if responseToken.admin:
            chat.joinChannel(token=responseToken, channel="#admin")

        # Output channels info
        for key, value in glob.channels.channels.items():
            if value.publicRead == True and value.hidden == False:
                responseToken.enqueue(serverPackets.channelInfo(key))

        # Send friends list
        responseToken.enqueue(serverPackets.friendList(userID))

        # Send main menu icon
        if glob.banchoConf.config["menuIcon"] != "":
            responseToken.enqueue(
                serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"]))

        # Send online users' panels
        with glob.tokens:
            for _, token in glob.tokens.tokens.items():
                if not token.restricted:
                    responseToken.enqueue(serverPackets.userPanel(
                        token.userID))

        # Get location and country from ip.zxq.co or database
        if glob.localize and (
                firstLogin == True
                or responseToken.privileges & privileges.USER_DONOR <= 0):
            # Get location and country from IP
            latitude, longitude = locationHelper.getLocation(requestIP)
            countryLetters = locationHelper.getCountry(requestIP)
            country = countryHelper.getCountryID(countryLetters)
        else:
            # Set location to 0,0 and get country from db
            log.warning("Location skipped")
            latitude = 0
            longitude = 0
            countryLetters = "XX"
            country = countryHelper.getCountryID(userUtils.getCountry(userID))

        # Set location and country
        responseToken.setLocation(latitude, longitude)
        responseToken.country = country

        # Set country in db if user has no country (first bancho login)
        if userUtils.getCountry(userID) == "XX":
            userUtils.setCountry(userID, countryLetters)

        # Send to everyone our userpanel if we are not restricted or tournament
        if not responseToken.restricted:
            glob.streams.broadcast("main", serverPackets.userPanel(userID))

        # Set reponse data to right value and reset our queue
        responseData = responseToken.queue
        responseToken.resetQueue()
    except exceptions.loginFailedException:
        # Login failed error packet
        # (we don't use enqueue because we don't have a token since login has failed)
        responseData += serverPackets.loginFailed()
    except exceptions.invalidArgumentsException:
        # Invalid POST data
        # (we don't use enqueue because we don't have a token since login has failed)
        responseData += serverPackets.loginFailed()
        responseData += serverPackets.notification(
            "I see what you're doing...")
    except exceptions.loginBannedException:
        # Login banned error packet
        responseData += serverPackets.loginBanned()
    except exceptions.loginLockedException:
        # Login banned error packet
        responseData += serverPackets.loginLocked()
    except exceptions.banchoMaintenanceException:
        # Bancho is in maintenance mode
        responseData = bytes()
        if responseToken is not None:
            responseData = responseToken.queue
        responseData += serverPackets.notification(
            "Our bancho server is in maintenance mode. Please try to login again later."
        )
        responseData += serverPackets.loginFailed()
    except exceptions.banchoRestartingException:
        # Bancho is restarting
        responseData += serverPackets.notification(
            "Bancho is restarting. Try again in a few minutes.")
        responseData += serverPackets.loginFailed()
    except exceptions.need2FAException:
        # User tried to log in from unknown IP
        responseData += serverPackets.needVerification()
    except exceptions.haxException:
        # Using oldoldold client, we don't have client data. Force update.
        # (we don't use enqueue because we don't have a token since login has failed)
        responseData += serverPackets.forceUpdate()
        responseData += serverPackets.notification(
            "Hory shitto, your client is TOO old! Nice prehistory! Please turn update it from the settings!"
        )
    except:
        log.error("Unknown error!\n```\n{}\n{}```".format(
            sys.exc_info(), traceback.format_exc()))
    finally:
        # Console and discord log
        if len(loginData) < 3:
            log.info(
                "Invalid bancho login request from **{}** (insufficient POST data)"
                .format(requestIP), "bunker")

        # Return token string and data
        return responseTokenString, responseData
Exemple #17
0
    def asyncGet(self):
        statusCode = 400
        data = {"message": "unknown error"}
        try:
            # Check arguments
            if not requestsManager.checkArguments(self.request.arguments,
                                                  ["b"]):
                raise exceptions.invalidArgumentsException(MODULE_NAME)

            # Get beatmap ID and make sure it's a valid number
            beatmapID = self.get_argument("b")
            if not beatmapID.isdigit():
                raise exceptions.invalidArgumentsException(MODULE_NAME)

            # Get mods
            if "m" in self.request.arguments:
                modsEnum = self.get_argument("m")
                if not modsEnum.isdigit():
                    raise exceptions.invalidArgumentsException(MODULE_NAME)
                modsEnum = int(modsEnum)
            else:
                modsEnum = 0

            # Get game mode
            if "g" in self.request.arguments:
                gameMode = self.get_argument("g")
                if not gameMode.isdigit():
                    raise exceptions.invalidArgumentsException(MODULE_NAME)
                gameMode = int(gameMode)
            else:
                gameMode = 0

            # Get acc
            if "a" in self.request.arguments:
                accuracy = self.get_argument("a")
                try:
                    accuracy = float(accuracy)
                except ValueError:
                    raise exceptions.invalidArgumentsException(MODULE_NAME)
            else:
                accuracy = -1.0

            # Print message
            log.info("Requested pp for beatmap {}".format(beatmapID))
            """ Peppy >:(
			# Get beatmap md5 from osuapi
			# TODO: Move this to beatmap object
			osuapiData = osuapiHelper.osuApiRequest("get_beatmaps", "b={}".format(beatmapID))
			if osuapiData is None or "file_md5" not in osuapiData or "beatmapset_id" not in osuapiData:
				raise exceptions.invalidBeatmapException(MODULE_NAME)

			beatmapMd5 = osuapiData["file_md5"]
			beatmapSetID = osuapiData["beatmapset_id"]
			"""
            dbData = glob.db.fetch(
                "SELECT beatmap_md5, beatmapset_id FROM beatmaps WHERE beatmap_id = {}"
                .format(beatmapID))
            if dbData is None:
                raise exceptions.invalidBeatmapException(MODULE_NAME)

            beatmapMd5 = dbData["beatmap_md5"]
            beatmapSetID = dbData["beatmapset_id"]

            # Create beatmap object
            bmap = beatmap.beatmap(beatmapMd5, beatmapSetID)

            # Check beatmap length
            if bmap.hitLength < 0:
                raise exceptions.beatmapTooLongException(MODULE_NAME)

            returnPP = []
            if gameMode == gameModes.STD and bmap.starsStd == 0:
                # Mode Specific beatmap, auto detect game mode
                if bmap.starsTaiko > 0:
                    gameMode = gameModes.TAIKO
                if bmap.starsCtb > 0:
                    gameMode = gameModes.CTB
                if bmap.starsMania > 0:
                    gameMode = gameModes.MANIA

            # Calculate pp
            if gameMode == gameModes.STD or gameMode == gameModes.TAIKO:
                # Std pp
                if accuracy < 0 and modsEnum == 0:
                    # Generic acc
                    # Get cached pp values
                    cachedPP = bmap.getCachedTillerinoPP()
                    if cachedPP != [0, 0, 0, 0]:
                        log.debug("Got cached pp.")
                        returnPP = cachedPP
                    else:
                        log.debug(
                            "Cached pp not found. Calculating pp with oppai..."
                        )
                        # Cached pp not found, calculate them
                        oppai = rippoppai.oppai(bmap,
                                                mods=modsEnum,
                                                tillerino=True)
                        returnPP = oppai.pp
                        bmap.starsStd = oppai.stars

                        # Cache values in DB
                        log.debug("Saving cached pp...")
                        if type(returnPP) == list and len(returnPP) == 4:
                            bmap.saveCachedTillerinoPP(returnPP)
                else:
                    # Specific accuracy, calculate
                    # Create oppai instance
                    log.debug(
                        "Specific request ({}%/{}). Calculating pp with oppai..."
                        .format(accuracy, modsEnum))
                    oppai = rippoppai.oppai(bmap,
                                            mods=modsEnum,
                                            tillerino=True)
                    bmap.starsStd = oppai.stars
                    if accuracy > 0:
                        returnPP.append(calculatePPFromAcc(oppai, accuracy))
                    else:
                        returnPP = oppai.pp
            else:
                raise exceptions.unsupportedGameModeException()

            # Data to return
            data = {
                "song_name":
                bmap.songName,
                "pp":
                [round(x, 2)
                 for x in returnPP] if type(returnPP) == list else returnPP,
                "length":
                bmap.hitLength,
                "stars":
                bmap.starsStd,
                "ar":
                bmap.AR,
                "bpm":
                bmap.bpm,
            }

            # Set status code and message
            statusCode = 200
            data["message"] = "ok"
        except exceptions.invalidArgumentsException:
            # Set error and message
            statusCode = 400
            data["message"] = "missing required arguments"
        except exceptions.invalidBeatmapException:
            statusCode = 400
            data["message"] = "beatmap not found"
        except exceptions.beatmapTooLongException:
            statusCode = 400
            data["message"] = "requested beatmap is too long"
        except exceptions.unsupportedGameModeException:
            statusCode = 400
            data["message"] = "Unsupported gamemode"
        finally:
            # Add status code to data
            data["status"] = statusCode

            # Debug output
            log.debug(str(data))

            # Send response
            #self.clear()
            self.write(json.dumps(data))
            self.set_header("Content-Type", "application/json")
            self.set_status(statusCode)
Exemple #18
0
    def asyncGet(self):
        try:
            # Get request ip
            ip = self.getRequestIP()

            # Check arguments
            if not requestsManager.checkArguments(self.request.arguments,
                                                  ["c", "u", "h"]):
                raise exceptions.invalidArgumentsException(self.MODULE_NAME)

            # Get arguments
            username = self.get_argument("u")
            password = self.get_argument("h")
            replayID = self.get_argument("c")

            # Login check
            userID = userUtils.getID(username)
            if userID == 0:
                raise exceptions.loginFailedException(self.MODULE_NAME, userID)
            if not userUtils.checkLogin(userID, password, ip):
                raise exceptions.loginFailedException(self.MODULE_NAME,
                                                      username)
            if userUtils.check2FA(userID, ip):
                raise exceptions.need2FAException(self.MODULE_NAME, username,
                                                  ip)

            # Get user ID
            replayData = glob.db.fetch(
                "SELECT osu_scores.*, phpbb_users.username AS uname FROM osu_scores LEFT JOIN phpbb_users ON osu_scores.user_id = phpbb_users.user_id WHERE osu_scores.score_id = %s",
                [replayID])

            # Increment 'replays watched by others' if needed
            if replayData is not None:
                if username != replayData["uname"]:
                    userUtils.incrementReplaysWatched(replayData["userid"],
                                                      replayData["play_mode"])

            # Serve replay
            log.info("Serving replay_{}.osr".format(replayID))
            r = ""
            replayID = int(replayID)
            try:
                r = replayHelper.getRawReplayS3(replayID)
            except timeout_decorator.TimeoutError:
                log.warning("S3 timed out")
                sentry.captureMessage("S3 timeout while fetching replay.")
                glob.stats["replay_download_failures"].labels(
                    type="raw_s3_timeout").inc()
            except FileNotFoundError:
                log.warning("Replay {} doesn't exist".format(replayID))
            except:
                glob.stats["replay_download_failures"].labels(
                    type="raw_other").inc()
                raise
            finally:
                self.write(r)
        except exceptions.invalidArgumentsException:
            pass
        except exceptions.need2FAException:
            pass
        except exceptions.loginFailedException:
            pass
Exemple #19
0
    def allPlayersCompleted(self):
        """
		Cleanup match stuff and send match end packet to everyone

		:return:
		"""
        # Collect some info about the match that just ended to send to the api
        infoToSend = {
            "id": self.matchID,
            "name": self.matchName,
            "beatmap_id": self.beatmapID,
            "mods": self.mods,
            "game_mode": self.gameMode,
            "host_id": self.hostUserID,
            "host_user_name": userUtils.getUsername(self.hostUserID),
            "game_type": self.matchTeamType,
            "game_score_condition": self.matchScoringType,
            "game_mod_mode": self.matchModMode,
            "scores": {}
        }

        # Add score info for each player
        for i in range(0, 16):
            if self.slots[i].user is not None and self.slots[
                    i].status == slotStatuses.PLAYING:
                infoToSend["scores"][glob.tokens.tokens[
                    self.slots[i].user].userID] = {
                        "score":
                        self.slots[i].score,
                        "mods":
                        self.slots[i].mods,
                        "failed":
                        self.slots[i].failed,
                        "pass":
                        self.slots[i].passed,
                        "team":
                        self.slots[i].team,
                        "username":
                        userUtils.getUsername(glob.tokens.tokens[
                            self.slots[i].user].userID)  # vinse shit ;3
                    }

        # Send the info to the api
        glob.redis.publish("api:mp_complete_match", json.dumps(infoToSend))
        self.games.append(json.dumps(infoToSend))

        # Reset inProgress
        self.inProgress = False

        # Reset slots
        self.resetSlots()

        # Send match update
        self.sendUpdates()

        # Send match complete
        glob.streams.broadcast(self.streamName, serverPackets.matchComplete())

        # Destroy playing stream
        glob.streams.dispose(self.playingStreamName)
        glob.streams.remove(self.playingStreamName)

        # Console output
        log.info("MPROOM{}: Match completed".format(self.matchID))

        # If this is a tournament match, then we send a notification in the chat
        # saying that the match has completed.
        chanName = "#multi_{}".format(self.matchID)
        if self.isTourney and (chanName in glob.channels.channels):
            chat.sendMessage(glob.BOT_NAME, chanName,
                             "Match has just finished.")
Exemple #20
0
    def asyncGet(self):
        statusCode = 400
        data = {"message": "unknown error"}
        try:
            # Check arguments
            if not requestsManager.checkArguments(self.request.arguments,
                                                  ["b"]):
                raise exceptions.invalidArgumentsException(MODULE_NAME)

            # Get beatmap ID and make sure it's a valid number
            beatmapID = self.get_argument("b")
            if not beatmapID.isdigit():
                raise exceptions.invalidArgumentsException(MODULE_NAME)

            # Get mods
            if "m" in self.request.arguments:
                modsEnum = self.get_argument("m")
                if not modsEnum.isdigit():
                    raise exceptions.invalidArgumentsException(MODULE_NAME)
                modsEnum = int(modsEnum)
            else:
                modsEnum = 0

            # Get game mode
            if "g" in self.request.arguments:
                gameMode = self.get_argument("g")
                if not gameMode.isdigit():
                    raise exceptions.invalidArgumentsException(MODULE_NAME)
                gameMode = int(gameMode)
            else:
                gameMode = 0

            # Get acc
            if "a" in self.request.arguments:
                accuracy = self.get_argument("a")
                try:
                    accuracy = float(accuracy)
                except ValueError:
                    raise exceptions.invalidArgumentsException(MODULE_NAME)
            else:
                accuracy = None

            # Print message
            log.info("Requested pp for beatmap {}".format(beatmapID))

            # Get beatmap md5 from osuapi
            # TODO: Move this to beatmap object
            osuapiData = osuapiHelper.osuApiRequest("get_beatmaps",
                                                    "b={}".format(beatmapID))
            if osuapiData is None or "file_md5" not in osuapiData or "beatmapset_id" not in osuapiData:
                raise exceptions.invalidBeatmapException(MODULE_NAME)
            beatmapMd5 = osuapiData["file_md5"]
            beatmapSetID = osuapiData["beatmapset_id"]

            # Create beatmap object
            bmap = beatmap.beatmap(beatmapMd5, beatmapSetID)

            # Check beatmap length
            if bmap.hitLength > 900:
                raise exceptions.beatmapTooLongException(MODULE_NAME)

            if gameMode == gameModes.STD and bmap.starsStd == 0:
                # Mode Specific beatmap, auto detect game mode
                if bmap.starsTaiko > 0:
                    gameMode = gameModes.TAIKO
                if bmap.starsCtb > 0:
                    gameMode = gameModes.CTB
                if bmap.starsMania > 0:
                    gameMode = gameModes.MANIA

            # Calculate pp
            if gameMode in (gameModes.STD, gameModes.TAIKO):
                # osu!standard and osu!taiko
                oppai = ez.Ez(bmap,
                              mods_=modsEnum,
                              tillerino=accuracy is None,
                              acc=accuracy,
                              gameMode=gameMode)
                bmap.starsStd = oppai.stars
                returnPP = oppai.pp
            elif gameMode == gameModes.CTB:
                # osu!catch
                ciccio = cicciobello.Cicciobello(bmap,
                                                 mods_=modsEnum,
                                                 tillerino=accuracy is None,
                                                 accuracy=accuracy)
                bmap.starsStd = ciccio.stars
                returnPP = ciccio.pp
            else:
                raise exceptions.unsupportedGameModeException()

            # Data to return
            data = {
                "song_name": bmap.songName,
                "pp":
                [x for x in returnPP] if type(returnPP) is list else returnPP,
                "game_mode": gameMode,
                "length": bmap.hitLength,
                "stars": bmap.starsStd,
                "ar": bmap.AR,
                "bpm": bmap.bpm,
            }

            # Set status code and message
            statusCode = 200
            data["message"] = "ok"
        except exceptions.invalidArgumentsException:
            # Set error and message
            statusCode = 400
            data["message"] = "missing required arguments"
        except exceptions.invalidBeatmapException:
            statusCode = 400
            data["message"] = "beatmap not found"
        except exceptions.beatmapTooLongException:
            statusCode = 400
            data["message"] = "requested beatmap is too long"
        except exceptions.unsupportedGameModeException:
            statusCode = 400
            data["message"] = "Unsupported gamemode"
        finally:
            # Add status code to data
            data["status"] = statusCode

            # Debug output
            log.debug(str(data))

            # Send response
            self.write(json.dumps(data))
            self.set_header("Content-Type", "application/json")
            self.set_status(statusCode)
Exemple #21
0
def partChannel(userID=0,
                channel="",
                token=None,
                toIRC=True,
                kick=False,
                force=False):
    """
	Part a channel

	:param userID: user ID of the user that parts the channel. Optional. token can be used instead.
	:param token: user token object of user that parts the channel. Optional. userID can be used instead.
	:param channel: channel name
	:param toIRC: if True, send this channel join event to IRC. Must be true if joining from bancho. Optional. Default: True
	:param kick: if True, channel tab will be closed on client. Used when leaving lobby. Optional. Default: False
	:param force: whether to allow game clients to part #spect_ and #multi_ channels
	:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side
	"""
    try:
        # Make sure the client is not drunk and sends partChannel when closing a PM tab
        if not channel.startswith("#"):
            return

        # Get token if not defined
        if token is None:
            token = glob.tokens.getTokenFromUserID(userID)
            # Make sure the token exists
            if token is None:
                raise exceptions.userNotFoundException()
        else:
            token = token

        # Determine internal/client name if needed
        # (toclient is used clientwise for #multiplayer and #spectator channels)
        channel, channelClient = channelMasking(token, channel, userID=userID)
        if channel is None and channelClient is None:
            return 0

        # Make sure the channel exists
        if channel not in glob.channels.channels:
            raise exceptions.channelUnknownException()

        # Make sure a game client is not trying to join a #multi_ or #spect_ channel manually
        channelObject = glob.channels.channels[channel]
        if channelObject.isSpecial and not token.irc and not force:
            raise exceptions.channelUnknownException()

        # Make sure the user is in the channel
        if channel not in token.joinedChannels:
            raise exceptions.userNotInChannelException()

        # Part channel (token-side and channel-side)
        token.partChannel(channelObject)

        # Delete temporary channel if everyone left
        if "chat/{}".format(channelObject.name) in glob.streams.streams:
            if channelObject.temp and len(glob.streams.streams[
                    "chat/{}".format(channelObject.name)].clients) - 1 == 0:
                glob.channels.removeChannel(channelObject.name)

        # Force close tab if needed
        # NOTE: Maybe always needed, will check later
        if kick:
            token.enqueue(serverPackets.channelKicked(channelClient))

        # IRC part
        if glob.irc and toIRC:
            glob.ircServer.banchoPartChannel(token.username, channel)

        # Console output
        log.info("{} parted channel {} ({})".format(token.username, channel,
                                                    channelClient))

        # Return IRC code
        return 0
    except exceptions.channelUnknownException:
        log.warning("{} attempted to part an unknown channel ({})".format(
            token.username, channel))
        return 403
    except exceptions.userNotInChannelException:
        log.warning(
            "{} attempted to part {}, but he's not in that channel".format(
                token.username, channel))
        return 442
    except exceptions.userNotFoundException:
        log.warning("User not connected to IRC/Bancho")
        return 442  # idk
Exemple #22
0
	def allPlayersCompleted(self):
		"""
		Cleanup match stuff and send match end packet to everyone

		:return:
		"""
		# Collect some info about the match that just ended to send to the api
		infoToSend = {
			"id": self.matchID,
			"name": self.matchName,
			"beatmap_id": self.beatmapID,
			"mods": self.mods,
			"game_mode": self.gameMode,
			"scores": {}
		}

		# Add score info for each player
		for i in range(0,16):
			if self.slots[i].user is not None and self.slots[i].status == slotStatuses.PLAYING:
				infoToSend["scores"][glob.tokens.tokens[self.slots[i].user].userID] = {
					"score": self.slots[i].score,
					"mods": self.slots[i].mods,
					"failed": self.slots[i].failed,
					"pass": self.slots[i].passed,
					"team": self.slots[i].team
				}

		# Send the info to the api
		glob.redis.publish("api:mp_complete_match", json.dumps(infoToSend))

		# Reset inProgress
		self.inProgress = False

		# Reset slots
		self.resetSlots()

		# Send match update
		self.sendUpdates()

		# Send match complete
		glob.streams.broadcast(self.streamName, serverPackets.matchComplete())

		# Destroy playing stream
		glob.streams.dispose(self.playingStreamName)
		glob.streams.remove(self.playingStreamName)

		# Console output
		log.info("MPROOM{}: Match completed".format(self.matchID))

		# Set vinse id if needed
		chanName = "#multi_{}".format(self.matchID)
		if self.vinseID is None:
			self.vinseID = (int(time.time()) // (60 * 15)) << 32 | self.matchID
			chat.sendMessage(glob.BOT_NAME, chanName, "Match history available [{} here]".format(
				"https://multi.bigtu.vip/match/{}".format(self.vinseID)
			))
		if not self.bloodcatAlert:
			chat.sendMessage(
				glob.BOT_NAME,
				chanName,
				"and uh... in case you're playing unranked or broken maps "
				"that are now available through ripple's osu!direct, you can "
				"type '!bloodcat' in the chat to get a download link for the "
				"currently selected map from Bloodcat! If osu!direct is not working, "
				"You can still use '!beatconnect' as a mirror too! "
			)
			self.bloodcatAlert = True

		# If this is a tournament match, then we send a notification in the chat
		# saying that the match has completed.
		if self.isTourney and (chanName in glob.channels.channels):
			chat.sendMessage(glob.BOT_NAME, chanName, "Match has just finished.")
Exemple #23
0
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
Exemple #24
0
def handle(tornadoRequest):
    # Data to return
    responseToken = None
    responseTokenString = "ayy"
    responseData = bytes()

    # Get IP from tornado request
    requestIP = tornadoRequest.getRequestIP()

    # Avoid exceptions
    clientData = ["unknown", "unknown", "unknown", "unknown", "unknown"]
    osuVersion = "unknown"

    # Split POST body so we can get username/password/hardware data
    # 2:-3 thing is because requestData has some escape stuff that we don't need
    loginData = str(tornadoRequest.request.body)[2:-3].split("\\n")
    try:
        # Make sure loginData is valid
        if len(loginData) < 3:
            raise exceptions.invalidArgumentsException()

        # Get HWID, MAC address and more
        # Structure (new line = "|", already split)
        # [0] osu! version
        # [1] plain mac addressed, separated by "."
        # [2] mac addresses hash set
        # [3] unique ID
        # [4] disk ID
        splitData = loginData[2].split("|")
        osuVersion = splitData[0]
        timeOffset = int(splitData[1])
        clientData = splitData[3].split(":")[:5]
        if len(clientData) < 4:
            raise exceptions.forceUpdateException()

        # Try to get the ID from username
        username = str(loginData[0])
        userID = userUtils.getID(username)

        if not userID:
            # Invalid username
            raise exceptions.loginFailedException()
        if not userUtils.checkLogin(userID, loginData[1]):
            # Invalid password
            raise exceptions.loginFailedException()

        # Make sure we are not banned or locked
        priv = userUtils.getPrivileges(userID)
        if userUtils.isBanned(
                userID) and priv & privileges.USER_PENDING_VERIFICATION == 0:
            raise exceptions.loginBannedException()
        if userUtils.isLocked(
                userID) and priv & privileges.USER_PENDING_VERIFICATION == 0:
            raise exceptions.loginLockedException()

        # 2FA check
        if userUtils.check2FA(userID, requestIP):
            log.warning("Need 2FA check for user {}".format(loginData[0]))
            raise exceptions.need2FAException()

        # No login errors!

        # Verify this user (if pending activation)
        firstLogin = False
        if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware(
                userID):
            if userUtils.verifyUser(userID, clientData):
                # Valid account
                log.info("Account {} verified successfully!".format(userID))
                glob.verifiedCache[str(userID)] = 1
                firstLogin = True
            else:
                # Multiaccount detected
                log.info("Account {} NOT verified!".format(userID))
                glob.verifiedCache[str(userID)] = 0
                raise exceptions.loginBannedException()

        # Save HWID in db for multiaccount detection
        hwAllowed = userUtils.logHardware(userID, clientData, firstLogin)

        # This is false only if HWID is empty
        # if HWID is banned, we get restricted so there's no
        # need to deny bancho access
        if not hwAllowed:
            raise exceptions.haxException()

        # Log user IP
        userUtils.logIP(userID, requestIP)

        # Log user osuver
        kotrikhelper.setUserLastOsuVer(userID, osuVersion)

        # Delete old tokens for that user and generate a new one
        isTournament = "tourney" in osuVersion
        if not isTournament:
            glob.tokens.deleteOldTokens(userID)
        responseToken = glob.tokens.addToken(userID,
                                             requestIP,
                                             timeOffset=timeOffset,
                                             tournament=isTournament)
        responseTokenString = responseToken.token

        # Check restricted mode (and eventually send message)
        responseToken.checkRestricted()

        # Send message if donor expires soon
        if responseToken.privileges & privileges.USER_DONOR > 0:
            expireDate = userUtils.getDonorExpire(responseToken.userID)
            if expireDate - int(time.time()) <= 86400 * 3:
                expireDays = round((expireDate - int(time.time())) / 86400)
                expireIn = "{} days".format(
                    expireDays) if expireDays > 1 else "less than 24 hours"
                responseToken.enqueue(
                    serverPackets.notification(
                        "Your donor perks are about to run out in less than 24 hours! All perks associated with your donor perks will be gone within the next 24 hours. If you don't want this, you may want to buy donor again! However, if you are a staff member, ignore this message!"
                        .format(expireIn)))

        # Deprecate telegram 2fa and send alert
        if userUtils.deprecateTelegram2Fa(userID):
            responseToken.enqueue(
                serverPackets.notification(
                    "As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://ripple.moe. Thank you for your patience."
                ))

        # Set silence end UNIX time in token
        responseToken.silenceEndTime = userUtils.getSilenceEnd(userID)

        # Get only silence remaining seconds
        silenceSeconds = responseToken.getSilenceSecondsLeft()

        # Get supporter/GMT
        userGMT = False
        if not userUtils.isRestricted(userID):
            userSupporter = True
        else:
            userSupporter = False
        userTournament = False
        if responseToken.admin:
            userGMT = True
        if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0:
            userTournament = True

        # Server restarting check
        if glob.restarting:
            raise exceptions.banchoRestartingException()

        # Send login notification before maintenance message
        if glob.banchoConf.config["loginNotification"] != "":
            responseToken.enqueue(
                serverPackets.notification(
                    glob.banchoConf.config["loginNotification"]))

        # Maintenance check
        if glob.banchoConf.config["banchoMaintenance"]:
            if not userGMT:
                # We are not mod/admin, delete token, send notification and logout
                glob.tokens.deleteToken(responseTokenString)
                raise exceptions.banchoMaintenanceException()
            else:
                # We are mod/admin, send warning notification and continue
                responseToken.enqueue(
                    serverPackets.notification(
                        "Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode."
                    ))

        # BAN CUSTOM CHEAT CLIENTS
        # 0Ainu = First Ainu build
        # b20190326.2 = Ainu build 2 (MPGH PAGE 10)
        # b20190401.22f56c084ba339eefd9c7ca4335e246f80 = Ainu Aoba's Birthday Build
        # b20191223.3 = Unknown Ainu build? (Taken from most users osuver in cookiezi.pw)
        if glob.conf.extra["mode"]["anticheat"]:
            # Ainu Client 2020 update
            if tornadoRequest.request.headers.get("ainu") == "happy":
                log.info(
                    "Account {} tried to use Ainu Client 2020!".format(userID))
                if userUtils.isRestricted(userID):
                    responseToken.enqueue(
                        serverPackets.notification("f**k off thanks"))
                else:
                    glob.tokens.deleteToken(userID)
                    userUtils.ban(userID)
                    raise exceptions.loginCheatClientsException()
            # Ainu Client 2019
            elif aobaHelper.getOsuVer(userID) in [
                    "0Ainu", "b20190326.2",
                    "b20190401.22f56c084ba339eefd9c7ca4335e246f80",
                    "b20191223.3"
            ]:
                log.info("Account {} tried to use Ainu Client!".format(userID))
                if userUtils.isRestricted(userID):
                    responseToken.enqueue(
                        serverPackets.notification("f**k off thanks"))
                else:
                    glob.tokens.deleteToken(userID)
                    userUtils.ban(userID)
                    raise exceptions.loginCheatClientsException()

        # Send all needed login packets
        responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds))
        responseToken.enqueue(serverPackets.userID(userID))
        responseToken.enqueue(serverPackets.protocolVersion())
        responseToken.enqueue(
            serverPackets.userSupporterGMT(userSupporter, userGMT,
                                           userTournament))
        responseToken.enqueue(serverPackets.userPanel(userID, True))
        responseToken.enqueue(serverPackets.userStats(userID, True))

        # Channel info end (before starting!?! wtf bancho?)
        responseToken.enqueue(serverPackets.channelInfoEnd())
        # Default opened channels
        # TODO: Configurable default channels
        chat.joinChannel(token=responseToken, channel="#osu")
        chat.joinChannel(token=responseToken, channel="#announce")

        # Join admin channel if we are an admin
        if responseToken.admin:
            chat.joinChannel(token=responseToken, channel="#admin")

        # Output channels info
        for key, value in glob.channels.channels.items():
            if value.publicRead and not value.hidden:
                responseToken.enqueue(serverPackets.channelInfo(key))

        # Send friends list
        responseToken.enqueue(serverPackets.friendList(userID))

        # Send main menu icon
        if glob.banchoConf.config["menuIcon"] != "":
            responseToken.enqueue(
                serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"]))

        # Send online users' panels
        with glob.tokens:
            for _, token in glob.tokens.tokens.items():
                if not token.restricted:
                    responseToken.enqueue(serverPackets.userPanel(
                        token.userID))

        # Get location and country from ip.zxq.co or database
        if glob.localize:
            # Get location and country from IP
            latitude, longitude = locationHelper.getLocation(requestIP)
            countryLetters = locationHelper.getCountry(requestIP)
            country = countryHelper.getCountryID(countryLetters)
        else:
            # Set location to 0,0 and get country from db
            log.warning("Location skipped")
            latitude = 0
            longitude = 0
            countryLetters = "XX"
            country = countryHelper.getCountryID(userUtils.getCountry(userID))

        # Set location and country
        responseToken.setLocation(latitude, longitude)
        responseToken.country = country

        # Set country in db if user has no country (first bancho login)
        if userUtils.getCountry(userID) == "XX":
            userUtils.setCountry(userID, countryLetters)

        # Send to everyone our userpanel if we are not restricted or tournament
        if not responseToken.restricted:
            glob.streams.broadcast("main", serverPackets.userPanel(userID))

        # Set reponse data to right value and reset our queue
        responseData = responseToken.queue
        responseToken.resetQueue()
    except exceptions.loginFailedException:
        # Login failed error packet
        # (we don't use enqueue because we don't have a token since login has failed)
        responseData += serverPackets.loginFailed()
    except exceptions.invalidArgumentsException:
        # Invalid POST data
        # (we don't use enqueue because we don't have a token since login has failed)
        responseData += serverPackets.loginFailed()
        responseData += serverPackets.notification(
            "I see what you're doing...")
    except exceptions.loginBannedException:
        # Login banned error packet
        responseData += serverPackets.loginBanned()
    except exceptions.loginLockedException:
        # Login banned error packet
        responseData += serverPackets.loginLocked()
    except exceptions.loginCheatClientsException:
        # Banned for logging in with cheats
        responseData += serverPackets.loginCheats()
    except exceptions.banchoMaintenanceException:
        # Bancho is in maintenance mode
        responseData = bytes()
        if responseToken is not None:
            responseData = responseToken.queue
        responseData += serverPackets.notification(
            "Our bancho server is in maintenance mode. Please try to login again later."
        )
        responseData += serverPackets.loginFailed()
    except exceptions.banchoRestartingException:
        # Bancho is restarting
        responseData += serverPackets.notification(
            "Bancho is restarting. Try again in a few minutes.")
        responseData += serverPackets.loginFailed()
    except exceptions.need2FAException:
        # User tried to log in from unknown IP
        responseData += serverPackets.needVerification()
    except exceptions.haxException:
        # Using oldoldold client, we don't have client data. Force update.
        # (we don't use enqueue because we don't have a token since login has failed)
        responseData += serverPackets.forceUpdate()
        responseData += serverPackets.notification(
            "Update your client! We aren't in 1990 mate.")
    except:
        log.error("Unknown error!\n```\n{}\n{}```".format(
            sys.exc_info(), traceback.format_exc()))
    finally:
        # Console and discord log
        if len(loginData) < 3:
            log.info(
                "Invalid bancho login request from **{}** (insufficient POST data)"
                .format(requestIP), "bunker")

        # Return token string and data
        return responseTokenString, responseData
def handle(userToken, packetData):
    # Read new settings
    packetData = clientPackets.changeMatchSettings(packetData)

    # Get match ID
    matchID = userToken.matchID

    # Make sure the match exists
    if matchID not in glob.matches.matches:
        return

    # Host check
    with glob.matches.matches[matchID] as match:
        if userToken.userID != match.hostUserID:
            return

        # Some dank memes easter egg
        memeTitles = [
            "OWC 2020", "AC is a duck", "Dank memes", "1337ms Ping",
            "Iscriviti a Xenotoze", "...e i marò?", "Superman dies",
            "The brace is on fire", "print_foot()", "#FREEZEBARKEZ",
            "osu!thailand devs are actually cats", "Thank Mr Shaural",
            "NEVER GIVE UP", "T I E D  W I T H  U N I T E D",
            "HIGHEST HDHR LOBBY OF ALL TIME",
            "This is gasoline and I set myself on fire",
            "Everyone is cheating apparently", "Kurwa mac", "TATOE",
            "This is not your drama landfill.", "I like cheese",
            "AOBA IS NOT A CAT HE IS A DO(N)G", "Datingu startuato"
        ]

        # Set match name
        match.matchName = packetData["matchName"] if packetData[
            "matchName"] != "meme" else random.choice(memeTitles)

        # Update match settings
        match.inProgress = packetData["inProgress"]
        if packetData["matchPassword"] != "":
            match.matchPassword = generalUtils.stringMd5(
                packetData["matchPassword"])
        else:
            match.matchPassword = ""
        match.beatmapName = packetData["beatmapName"]
        match.beatmapID = packetData["beatmapID"]
        match.hostUserID = packetData["hostUserID"]
        match.gameMode = packetData["gameMode"]

        oldBeatmapMD5 = match.beatmapMD5
        oldMods = match.mods
        oldMatchTeamType = match.matchTeamType

        match.mods = packetData["mods"]
        match.beatmapMD5 = packetData["beatmapMD5"]
        match.matchScoringType = packetData["scoringType"]
        match.matchTeamType = packetData["teamType"]
        match.matchModMode = packetData["freeMods"]

        # Reset ready if needed
        if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5:
            match.resetReady()

        # Reset mods if needed
        if match.matchModMode == matchModModes.NORMAL:
            # Reset slot mods if not freeMods
            match.resetMods()
        else:
            # Reset match mods if freemod
            match.mods = 0

        # Initialize teams if team type changed
        if match.matchTeamType != oldMatchTeamType:
            match.initializeTeams()

        # Force no freemods if tag coop
        if match.matchTeamType == matchTeamTypes.TAG_COOP or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
            match.matchModMode = matchModModes.NORMAL

        # Send updated settings
        match.sendUpdates()

        # Console output
        log.info("MPROOM{}: Updated room settings".format(match.matchID))
Exemple #26
0
	def asyncGet(self):
		statusCode = 400
		data = {"message": "unknown error"}
		try:
			# Check arguments
			if not requestsManager.checkArguments(self.request.arguments, ["b"]):
				raise exceptions.invalidArgumentsException(MODULE_NAME)

			# Get beatmap ID and make sure it's a valid number
			beatmapID = self.get_argument("b")
			if not beatmapID.isdigit():
				raise exceptions.invalidArgumentsException(MODULE_NAME)

			# Get mods
			if "m" in self.request.arguments:
				modsEnum = self.get_argument("m")
				if not modsEnum.isdigit():
					raise exceptions.invalidArgumentsException(MODULE_NAME)
				modsEnum = int(modsEnum)
			else:
				modsEnum = 0

			# Get game mode
			if "g" in self.request.arguments:
				gameMode = self.get_argument("g")
				if not gameMode.isdigit():
					raise exceptions.invalidArgumentsException(MODULE_NAME)
				gameMode = int(gameMode)
			else:
				gameMode = 0

			# Get acc
			if "a" in self.request.arguments:
				accuracy = self.get_argument("a")
				try:
					accuracy = float(accuracy)
					if math.isnan(accuracy):
						accuracy = -1.0
				except ValueError:
					raise exceptions.invalidArgumentsException(MODULE_NAME)
			else:
				accuracy = -1.0

			if "x" in self.request.arguments:
				misses = self.get_argument("x")
				try:
					misses = int(misses)
				except ValueError:
					raise exceptions.invalidArgumentsException(MODULE_NAME)
			else:
				misses = 0

			if "c" in self.request.arguments:
				combo = self.get_argument("c")
				if not combo.isdigit():
					raise exceptions.invalidArgumentsException(MODULE_NAME)
				combo = int(combo)
			else:
				combo = 0


			# Print message
			log.info("Requested pp for beatmap {}".format(beatmapID))

			# Get beatmap md5 from osuapi
			# TODO: Move this to beatmap object
			"""osuapiData = osuapiHelper.osuApiRequest("get_beatmaps", "b={}".format(beatmapID))
			if osuapiData is None or "file_md5" not in osuapiData or "beatmapset_id" not in osuapiData:
				raise exceptions.invalidBeatmapException(MODULE_NAME)
			beatmapMd5 = osuapiData["file_md5"]
			beatmapSetID = osuapiData["beatmapset_id"]"""
			dbData = glob.db.fetch("SELECT beatmap_md5, beatmapset_id, mode FROM beatmaps WHERE beatmap_id = {}".format(beatmapID))
			if dbData is None:
				raise exceptions.invalidBeatmapException(MODULE_NAME)

			beatmapMd5 = dbData["beatmap_md5"]
			beatmapSetID = dbData["beatmapset_id"]

			# Create beatmap object
			bmap = beatmap.beatmap(beatmapMd5, beatmapSetID)
			stars = 0
			# Check beatmap length
			if bmap.hitLength > 900:
				raise exceptions.beatmapTooLongException(MODULE_NAME)

			returnPP = []
			if gameMode == gameModes.STD and bmap.starsStd == 0:
				gameMode = dbData["mode"]
				if(gameMode == 2):
					raise exceptions.unsupportedGameModeException
			if accuracy > 100 or combo > bmap.maxCombo or misses < 0 or math.isnan(accuracy):
				raise exceptions.invalidArgumentsException(MODULE_NAME)
			# Calculate pp
			if gameMode != 2:
				# Std pp
				if accuracy < 0 and combo == 0 and misses == 0 and gameMode == dbData["mode"]:
					# Generic acc
					# Get cached pp values
					cachedPP = bmap.getCachedTillerinoPP()
					if(modsEnum == 0 and cachedPP != [0,0,0,0]):
						log.debug("Got cached pp.")
						returnPP = cachedPP
						stars = bmap.starsStd
					else:
						log.debug("Cached pp not found. Calculating pp with oppai...")
						# Cached pp not found, calculate them
						if(gameMode == 1 or gameMode == 0):
							oppai = rippoppai.oppai(bmap, mods=modsEnum, tillerino=True, stars=True)
							returnPP = oppai.pp
							stars = oppai.stars
						else:
							xeno = omppcPy.piano(bmap, mods=modsEnum, tillerino=True, stars=True)
							returnPP = xeno.pp
							stars = xeno.stars

						# Cache values in DB
						log.debug("Saving cached pp...")
						if(modsEnum == 0 and type(returnPP) != int and len(returnPP) == 4):
							bmap.saveCachedTillerinoPP(returnPP)
				else:
					# Specific accuracy, calculate
					# Create oppai instance
					if(gameMode == 3):
							log.info("Specific request ({}%/{}). Calculating pp with omppc...".format(accuracy, modsEnum))
							xeno = omppcPy.piano(bmap, acc=accuracy, mods=modsEnum, tillerino=False, stars=True)
							returnPP.append(xeno.pp)
							stars = xeno.stars	
					else:
						if(gameMode != 0 and gameMode != 1):
							raise exceptions.unsupportedGameModeException
						log.debug("Specific request ({}%/{}). Calculating pp with oppai...".format(accuracy, modsEnum))
						oppai = rippoppai.oppai(bmap, mods=modsEnum, tillerino=False, stars=True)
						stars = oppai.stars
						if accuracy > 0:
							oppai.acc = accuracy
						if combo > 0:
							oppai.combo = combo
						if misses > 0:
							oppai.misses = misses	
						oppai.getPP()
						returnPP.append(oppai.pp)
			else:
				raise exceptions.unsupportedGameModeException

			# Data to return
			data = {
				"song_name": bmap.songName,
				"pp": returnPP,
				"length": round(bmap.hitLength*0.75) if modsEnum & mods.DOUBLETIME > 0 else bmap.hitLength if modsEnum & mods.HALFTIME < 1 else round(bmap.hitLength*1.50),
				"stars": stars,
				"ar": round(min(10, bmap.AR * 1.4),1) if modsEnum & mods.HARDROCK > 0 else bmap.AR if modsEnum & mods.EASY < 1 else round(max(0, bmap.AR / 2),1),
				"od": round(min(10, bmap.OD * 1.4),1) if modsEnum & mods.HARDROCK > 0 else bmap.OD if modsEnum & mods.EASY < 1 else round(max(0, bmap.OD / 2),1),
				"bpm": round(bmap.bpm*1.5) if modsEnum & mods.DOUBLETIME > 0 else bmap.bpm if modsEnum & mods.HALFTIME < 1 else round(bmap.bpm*0.75),
			}
			# Set status code and message
			statusCode = 200
			data["message"] = "ok"
		except exceptions.invalidArgumentsException:
			# Set error and message
			statusCode = 400
			data["message"] = "missing required arguments"
		except exceptions.invalidBeatmapException:
			statusCode = 400
			data["message"] = "beatmap not found"
		except exceptions.beatmapTooLongException:
			statusCode = 400
			data["message"] = "requested beatmap is too long"
		except exceptions.unsupportedGameModeException:
			statusCode = 400
			data["message"] = "Unsupported gamemode"
		finally:
			# Add status code to data
			data["status"] = statusCode

			# Debug output
			log.debug(str(data))

			# Send response
			#self.clear()
			self.write(json.dumps(data))
			self.set_header("Content-Type", "application/json")
			self.set_status(statusCode)
Exemple #27
0
def verifyUser(userID, hashes):
	"""
	Activate `userID`'s account.

	:param userID: user id
	:param hashes: 	Peppy's botnet (client data) structure (new line = "|", already split)
					[0] osu! version
					[1] plain mac addressed, separated by "."
					[2] mac addresses hash set
					[3] unique ID
					[4] disk ID
	:return: True if verified successfully, else False (multiaccount)
	"""
	# Check for valid hash set
	for i in hashes[2:5]:
		if i == "":
			log.warning("Invalid hash set ({}) for user {} while verifying the account".format(str(hashes), userID), "bunk")
			return False

	# Get username
	username = getUsername(userID)

	# Make sure there are no other accounts activated with this exact mac/unique id/hwid
	if hashes[2] == "b4ec3c4334a0249dae95c284ec5983df" or hashes[4] == "ffae06fb022871fe9beb58b005c5e21d":
		# Running under wine, check only by uniqueid
		log.info("{user} ({userID}) ha triggerato Sannino:\n**Full data:** {hashes}\n**Usual wine mac address hash:** b4ec3c4334a0249dae95c284ec5983df\n**Usual wine disk id:** ffae06fb022871fe9beb58b005c5e21d".format(user=username, userID=userID, hashes=hashes), "bunker")
		log.debug("Veryfing with Linux/Mac hardware")
		match = glob.db.fetchAll("SELECT userid FROM hw_user WHERE unique_id = %(uid)s AND userid != %(userid)s AND activated = 1 LIMIT 1", {
			"uid": hashes[3],
			"userid": userID
		})
	else:
		# Running under windows, full check
		log.debug("Veryfing with Windows hardware")
		match = glob.db.fetchAll("SELECT userid FROM hw_user WHERE mac = %(mac)s AND unique_id = %(uid)s AND disk_id = %(diskid)s AND userid != %(userid)s AND activated = 1 LIMIT 1", {
			"mac": hashes[2],
			"uid": hashes[3],
			"diskid": hashes[4],
			"userid": userID
		})

	if match:
		# This is a multiaccount, restrict other account and ban this account

		# Get original userID and username (lowest ID)
		originalUserID = match[0]["userid"]
		originalUsername = getUsername(originalUserID)

		# Ban this user and append notes
		ban(userID)	# this removes the USER_PENDING_VERIFICATION flag too
		appendNotes(userID, "{}'s multiaccount ({}), found HWID match while verifying account ({})".format(originalUsername, originalUserID, hashes[2:5]))
		appendNotes(originalUserID, "Has created multiaccount {} ({})".format(username, userID))

		# Restrict the original
		restrict(originalUserID)

		# Discord message
		log.warning("User **{originalUsername}** ({originalUserID}) has been restricted because he has created multiaccount **{username}** ({userID}). The multiaccount has been banned.".format(
			originalUsername=originalUsername,
			originalUserID=originalUserID,
			username=username,
			userID=userID
		), "cm")

		# Disallow login
		return False
	else:
		# No matches found, set USER_PUBLIC and USER_NORMAL flags and reset USER_PENDING_VERIFICATION flag
		resetPendingFlag(userID)
		#log.info("User **{}** ({}) has verified his account with hash set _{}_".format(username, userID, hashes[2:5]), "cm")

		# Allow login
		return True
Exemple #28
0
def handle(userToken, packetData):
    # Get usertoken data
    userID = userToken.userID
    username = userToken.username

    # Make sure we are not banned
    #if userUtils.isBanned(userID):
    #	userToken.enqueue(serverPackets.loginBanned())
    #	return

    # Send restricted message if needed
    #if userToken.restricted:
    #	userToken.checkRestricted(True)

    # Change action packet
    packetData = clientPackets.userActionChange(packetData)

    # If we are not in spectate status but we're spectating someone, stop spectating
    '''
if userToken.spectating != 0 and userToken.actionID != actions.WATCHING and userToken.actionID != actions.IDLE and userToken.actionID != actions.AFK:
	userToken.stopSpectating()

# If we are not in multiplayer but we are in a match, part match
if userToken.matchID != -1 and userToken.actionID != actions.MULTIPLAYING and userToken.actionID != actions.MULTIPLAYER and userToken.actionID != actions.AFK:
	userToken.partMatch()
		'''

    # Update cached stats if our pp changed if we've just submitted a score or we've changed gameMode
    #if (userToken.actionID == actions.PLAYING or userToken.actionID == actions.MULTIPLAYING) or (userToken.pp != userUtils.getPP(userID, userToken.gameMode)) or (userToken.gameMode != packetData["gameMode"]):

    # Update cached stats if we've changed gamemode
    #will delete this
    userToken.autopilot = packetData['actionMods'] & 8192
    userToken.relax = packetData['actionMods'] & 128

    if packetData['actionMods'] & 128:
        userToken.enqueue(serverPackets.notification('You switched to relax!'))
        if userToken.actionID in (0, 1, 14):
            userToken.actionText = packetData["actionText"] + " on Relax"
        else:
            userToken.actionText = packetData["actionText"] + " on Relax"
            userToken.updateCachedStatsRx()
    elif packetData['actionMods'] & 8192:
        userToken.enqueue(
            serverPackets.notification('You switched to autopilot!'))
        if userToken.actionID in (0, 1, 14):
            userToken.actionText = packetData["actionText"] + " on Autopilot"
        else:
            userToken.actionText = packetData["actionText"] + " on Autopilot"
        userToken.updateCachedStatsAp()
    else:
        userToken.enqueue(
            serverPackets.notification('You switched to vanilla!'))
        userToken.actionText = packetData["actionText"]
        userToken.updateCachedStats()

    if userToken.gameMode != packetData["gameMode"]:
        userToken.gameMode = packetData["gameMode"]
        userToken.updateCachedStats()

    # Always update action id, text, md5 and beatmapID
    userToken.actionID = packetData["actionID"]

    userToken.actionMd5 = packetData["actionMd5"]
    userToken.actionMods = packetData["actionMods"]
    userToken.beatmapID = packetData["beatmapID"]

    # Enqueue our new user panel and stats to us and our spectators
    recipients = [userToken]
    if len(userToken.spectators) > 0:
        for i in userToken.spectators:
            if i in glob.tokens.tokens:
                recipients.append(glob.tokens.tokens[i])

    for i in recipients:
        if i is not None:
            # Force our own packet
            force = True if i == userToken else False
            i.enqueue(serverPackets.userPanel(userID, force))
            i.enqueue(serverPackets.userStats(userID, force))

    # Console output
    log.info("{} changed action: {} [{}][{}][{}]".format(
        username, str(userToken.actionID), userToken.actionText,
        userToken.actionMd5, userToken.beatmapID))
Exemple #29
0
    def _addComment(self):
        username = self.get_argument("u")
        target = self.get_argument("target", default=None)
        mode = int(self.get_argument("m", default=0))
        specialFormat = self.get_argument("f", default=None)
        userID = userUtils.getID(username)

        # Technically useless
        if userID < 0:
            return

        # Get beatmap/set/score ids
        try:
            beatmapID = int(self.get_argument("b", default=0))
            beatmapSetID = int(self.get_argument("s", default=0))
            scoreID = int(self.get_argument("r", default=0))
        except ValueError:
            raise exceptions.invalidArgumentsException(MODULE_NAME)

        # Add a comment, removing all illegal characters and trimming after 128 characters
        comment = self.get_argument("comment").replace("\r", "").replace(
            "\t", "").replace("\n", "")[:128]
        try:
            time_ = int(self.get_argument("starttime"))
        except ValueError:
            raise exceptions.invalidArgumentsException(MODULE_NAME)

        # Type of comment
        who = "normal"
        if target == "replay" and glob.db.fetch(
                "SELECT COUNT(*) AS c FROM scores WHERE id = %s AND userid = %s AND completed = 3",
            (scoreID, userID))["c"] > 0:
            # From player, on their score
            who = "player"
        elif userUtils.isInAnyPrivilegeGroup(
                userID, ("developer", "community manager", "bat")):
            # From BAT/Admin
            who = "admin"
        elif userUtils.isInPrivilegeGroup(userID, "donor"):
            # Supporter
            who = "donor"

        if target == "song":
            # Set comment
            if beatmapSetID <= 0:
                return
            value = beatmapSetID
            column = "beatmapset_id"
        elif target == "map":
            # Beatmap comment
            if beatmapID <= 0:
                return
            value = beatmapID
            column = "beatmap_id"
        elif target == "replay":
            # Score comment
            if scoreID <= 0:
                return
            value = scoreID
            column = "score_id"
        else:
            # Invalid target
            return

        # Make sure the user hasn't submitted another comment on the same map/set/song in a 5 seconds range
        if glob.db.fetch(
                "SELECT COUNT(*) AS c FROM comments WHERE user_id = %s AND {} = %s AND `time` BETWEEN %s AND %s"
                .format(column),
            (userID, value, time_ - 5000, time_ + 5000))["c"] > 0:
            return

        # Store the comment
        glob.db.execute(
            "INSERT INTO comments ({}, user_id, mode, comment, `time`, who, special_format) "
            "VALUES (%s, %s, %s, %s, %s, %s, %s)".format(column),
            (value, userID, mode, comment, time_, who, specialFormat))
        log.info("Submitted {} ({}) comment, mode {}, user {}: '{}'".format(
            column, value, mode, userID, comment))
 def asyncPost(self):
     try:
         if not requestsManager.checkArguments(self.request.arguments, [
                 "user[username]", "user[user_email]", "user[password]",
                 "check"
         ]):
             return self.write("what are you doing here?")
         username = self.get_argument("user[username]")
         email = self.get_argument("user[user_email]")
         password = self.get_argument("user[password]")  # Raw password
         accountCreated = self.get_argument("check")
         if accountCreated == "1":
             return self.write(
                 '{"form_error":{"user":{"check":["Account already created."]}}}'
             )
         emailCheck = glob.db.fetch("SELECT 1 FROM users WHERE email = %s",
                                    [email])
         usernameCheck = glob.db.fetch(
             "SELECT 1 FROM users WHERE username = %s", [username])
         if emailCheck != None:
             return self.write(
                 '{"form_error":{"user":{"user_email":["Email address already used."]}}}'
             )
         if usernameCheck != None or username.lower() in [
                 "peppy", "rrtyui", "cookiezi", "azer", "loctav",
                 "banchobot", "happystick", "doomsday", "sharingan33",
                 "andrea", "cptnxn", "reimu-desu", "hvick225", "_index",
                 "my aim sucks", "kynan", "rafis", "sayonara-bye",
                 "thelewa", "wubwoofwolf", "millhioref", "tom94",
                 "tillerino", "clsw", "spectator", "exgon", "axarious",
                 "angelsim", "recia", "nara", "emperorpenguin83", "bikko",
                 "xilver", "vettel", "kuu01", "_yu68", "tasuke912", "dusk",
                 "ttobas", "velperk", "jakads", "jhlee0133", "abcdullah",
                 "yuko-", "entozer", "hdhr", "ekoro", "snowwhite",
                 "osuplayer111", "musty", "nero", "elysion", "ztrot",
                 "koreapenguin", "fort", "asphyxia", "niko", "shigetora"
         ]:
             return self.write(
                 '{"form_error":{"user":{"username":["Username already used or it is forbidden."]}}}'
             )
         if len(password) < 8 or len(password) > 32:
             return self.write(
                 '{"form_error":{"user":{"password":["Password too short or long! (Password length must be more than 8 and less than 32)"]}}}'
             )
         if "_" in username and " " in username:
             self.write(
                 '{"form_error":{"user":{"username":["An username can not contain both underscores and spaces."]}}}'
             )
         userID = int(
             glob.db.execute(
                 "INSERT INTO users(username, username_safe, password_md5, salt, email, register_datetime, privileges, password_version) VALUES (%s,       %s,            %s,            '',  %s,     %s,                 1048576,          2)",
                 [
                     username,
                     userUtils.safeUsername(username),
                     passwordUtils.genBcrypt(
                         hashlib.md5(password.encode('utf-8')).hexdigest()),
                     email,
                     int(time.time())
                 ]))
         glob.db.execute(
             "INSERT INTO users_stats(id, username, user_color, user_style, ranked_score_std, playcount_std, total_score_std, ranked_score_taiko, playcount_taiko, total_score_taiko, ranked_score_ctb, playcount_ctb, total_score_ctb, ranked_score_mania, playcount_mania, total_score_mania) VALUES (%s, %s, 'black', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)",
             [userID, username])
         glob.db.execute(
             "INSERT INTO rx_stats(id, username, user_color, user_style, ranked_score_std, playcount_std, total_score_std, ranked_score_taiko, playcount_taiko, total_score_taiko, ranked_score_ctb, playcount_ctb, total_score_ctb, ranked_score_mania, playcount_mania, total_score_mania) VALUES (%s, %s, 'black', '', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)",
             [userID, username])
         log.info(
             "{} created their account using ingame registration.".format(
                 username))
     except Exception as e:
         log.error(e)