Esempio n. 1
0
    def can_user_touch_lobby(lobbyID: int,
                             uID: int,
                             checkUserIn: bool = False,
                             canRefEdit: bool = True):
        if lobbyID:
            match = glob.matches.matches[lobbyID]
            if checkUserIn:
                # check user is tourneyHost
                if match.isTourney and match.tourneyHost == uID:
                    return True

            # check user is hostUserID
            if match.hostUserID == uID:
                return True  # user can edit this

            if canRefEdit:
                # check user is ref
                if uID in match.refs:
                    return True

        # check user is tournament staff or its bot ;d
        if (userUtils.getPrivileges(userUtils.getID(fro)) & privileges.USER_TOURNAMENT_STAFF) > 0 or \
           fro == glob.BOT_NAME:
            return True

        return False
Esempio n. 2
0
def fokabotResponse(fro, chan, message):
	"""
	Check if a message has triggered FokaBot

	:param fro: sender username
	:param chan: channel name (or receiver username)
	:param message: chat mesage
	:return: FokaBot's response or False if no response
	"""
	for i in fokabotCommands.commands:
		# Loop though all commands
		if re.compile("^{}( (.+)?)?$".format(i["trigger"])).match(message.strip()):
			# message has triggered a command

			# Make sure the user has right permissions
			if i["privileges"] is not None:
				# Rank = x
				if userUtils.getPrivileges(userUtils.getID(fro)) & i["privileges"] == 0:
					return False

			# Check argument number
			message = message.split(" ")
			if i["syntax"] != "" and len(message) <= len(i["syntax"].split(" ")):
				return "Wrong syntax: {} {}".format(i["trigger"], i["syntax"])

			# Return response or execute callback
			if i["callback"] is None:
				return i["response"]
			else:
				return i["callback"](fro, chan, message[1:])

	# No commands triggered
	return False
Esempio n. 3
0
def fokabotResponse(fro, chan, message):
	"""
	Check if a message has triggered FokaBot

	:param fro: sender username
	:param chan: channel name (or receiver username)
	:param message: chat mesage
	:return: FokaBot's response or False if no response
	"""
	cmd, vals = None, None
	for (k, v) in mainHandler.store.handlers.items():
		if message.strip().startswith(k):
			cmd, vals = k, v
			break

	if not cmd:
		return False

	if vals['privileges'] and userUtils.getPrivileges(userUtils.getID(fro)) & vals["privileges"] == 0 and fro != glob.BOT_NAME:
		return False

	args = shlex.split(message.strip()[len(cmd):])
	syntaxargs = shlex.split(vals['syntax'])
	if vals['syntax'] != "" and len(args) < len(syntaxargs):
		return f"Wrong syntax: {cmd} {vals['syntax']}"

	return mainHandler.store.call_command(cmd, fro, chan, args)
Esempio n. 4
0
def fokabotCommands(token, sender, channel, message):
    msg = message.strip()
    userID = userUtils.getID(sender)
    userPr = userUtils.getPrivileges(userID)
    for command in yohaneCommands.commands:
        # Loop though all commands
        if re.compile("^{}( (.+)?)?$".format(command["trigger"])).match(msg):
            # message has triggered a command

            # Make sure the user has right permissions
            if command["privileges"] is not None:
                if userPr & command["privileges"] != command['privileges']:
                    return False

            # Check argument number
            message = message.strip().split(" ")
            if command["syntax"] != "" and len(message) <= len(
                    command["syntax"].split(" ")):
                return "Wrong syntax: {} {}".format(command["trigger"],
                                                    command["syntax"])

            # Return response or execute callback
            if command["callback"] is None:
                return command["response"]
            else:
                return command["callback"](token, sender, channel, message[1:])

    return None
Esempio n. 5
0
 def handle(self, userID):
     userID = super().parseData(userID)
     if userID is None:
         return
     targetToken = glob.tokens.getTokenFromUserID(userID)
     if targetToken != None:
         targetToken.privileges = userUtils.getPrivileges(userID)
         targetToken.checkBanned()
         targetToken.checkRestricted()
Esempio n. 6
0
def tillerino_mods(fro, chan, message):
    try:
        # Run the command in PM only
        if userUtils.getPrivileges(userUtils.getID(fro)) & privileges.USER_DONOR == 0 and chan.startswith("#"):
            return "Only donors can write here this command."

        # Get token and user ID
        token = glob.tokens.getTokenFromUsername(fro)
        if token is None:
            return False
        userID = token.userID

        # Make sure the user has triggered the bot with /np command
        if token.tillerino[0] == 0:
            return "Please give me a beatmap first with /np command."

        # Check passed mods and convert to enum
        modsList = [message[0][i:i + 2].upper() for i in range(0, len(message[0]), 2)]
        modsEnum = 0
        for i in modsList:
            if i not in ["NO", "NF", "EZ", "HD", "HR", "DT", "HT", "NC", "FL", "SO", "AP", "RX"]:
                return "Invalid mods. Allowed mods: NO, RX, NF, EZ, HD, HR, DT, HT, NC, FL, SO, RX, AP. Do not use spaces for multiple mods."
            if i == "NO":
                modsEnum = 0
                break
            elif i == "NF":
                modsEnum += mods.NOFAIL
            elif i == "EZ":
                modsEnum += mods.EASY
            elif i == "HD":
                modsEnum += mods.HIDDEN
            elif i == "HR":
                modsEnum += mods.HARDROCK
            elif i == "DT":
                modsEnum += mods.DOUBLETIME
            elif i == "HT":
                modsEnum += mods.HALFTIME
            elif i == "NC":
                modsEnum += mods.NIGHTCORE
            elif i == "FL":
                modsEnum += mods.FLASHLIGHT
            elif i == "SO":
                modsEnum += mods.SPUNOUT
            elif i == "AP":
                modsEnum += mods.RELAX2
            elif i == "RX":
                modsEnum += mods.RELAX

        # Set mods
        token.tillerino[1] = modsEnum

        # Return tillerino message for that beatmap with mods
        return get_pp_message(userID)
    except:
        return False
Esempio n. 7
0
def tillerino_np(fro, chan, message):
    try:
        # Run the command in PM only
        if userUtils.getPrivileges(userUtils.getID(
                fro)) & privileges.USER_DONOR == 0 and chan.startswith("#"):
            return "Only donors can write here this command."

        playWatch = message[1] == "playing" or message[1] == "watching"
        # Get URL from message
        beatmap_URL = message[0][1:]

        modsEnum = 0
        mapping = {
            "-Easy": mods.EASY,
            "-NoFail": mods.NOFAIL,
            "+Hidden": mods.HIDDEN,
            "+HardRock": mods.HARDROCK,
            "+Nightcore": mods.NIGHTCORE,
            "+DoubleTime": mods.DOUBLETIME,
            "-HalfTime": mods.HALFTIME,
            "+Flashlight": mods.FLASHLIGHT,
            "-SpunOut": mods.SPUNOUT
        }

        if playWatch:
            for part in message:
                part = part.replace("\x01", "")
                if part in mapping.keys():
                    modsEnum += mapping[part]

        # Get beatmap id from URL
        beatmap_ID = fokabot.npRegex.search(beatmap_URL).groups(0)[0]

        # Update latest tillerino song for current token
        token = glob.tokens.getTokenFromUsername(fro)
        if token is not None:
            token.tillerino = [int(beatmap_ID), modsEnum, -1.0]
        userID = token.userID

        # Return tillerino message
        return get_pp_message(userID)
    except:
        return False
Esempio n. 8
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):
            if userID == 1000:  #make sakuru do a different check because he plays over LAN
                if glob.db.fetch(
                        f"SELECT 2fabypassip FROM users WHERE id = 1000"
                ) == requestIP:
                    pass
            log.warning("Need 2FA check for user {}".format(loginData[0]))
            raise exceptions.need2FAException()

        # No login errors!

        # Verify this user (if pending activation)
        firstLogin = False
        if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware(
                userID):
            # Log user IP
            userUtils.logIP(userID, requestIP)
            if glob.db.fetch(
                    f"SELECT ip FROM ip_blacklist WHERE ip = '{requestIP}'"
            ):  #requestIP comes from tornado, so this isn't an sql injection vulnerability afaik
                glob.tokens.deleteToken(userID)
                userUtils.restrict(userID)
                return
            if userUtils.verifyUser(userID, clientData):
                # Valid account
                log.info("Account {} verified successfully!".format(userID))
                glob.verifiedCache[str(userID)] = 1
                firstLogin = True
            else:
                # Multiaccount detected
                log.info("Account {} NOT verified!".format(userID))
                glob.verifiedCache[str(userID)] = 0
                raise exceptions.loginBannedException()

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

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

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

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

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

        # Check if frozen
        IsFrozen = glob.db.fetch(
            f"SELECT frozen, firstloginafterfrozen, freezedate FROM users WHERE id = {userID} LIMIT 1"
        )  #ok kids, dont ever use formats in sql queries. here i can do it as the userID comes from a trusted source (this being pep.py itself) so it wont leave me susceptable to sql injection
        frozen = bool(IsFrozen["frozen"])

        present = datetime.now()
        readabledate = datetime.utcfromtimestamp(
            IsFrozen["freezedate"]).strftime('%d-%m-%Y %H:%M:%S')
        date2 = datetime.utcfromtimestamp(
            IsFrozen["freezedate"]).strftime('%d/%m/%Y')
        date3 = present.strftime('%d/%m/%Y')
        passed = date2 < date3
        if frozen and passed == False:
            responseToken.enqueue(
                serverPackets.notification(
                    f"The osuHOW staff team has found you suspicious and would like to request a liveplay. You have until {readabledate} (UTC) to provide a liveplay to the staff team. This can be done via the osuHOW Discord server. Failure to provide a valid liveplay will result in your account being automatically restricted."
                ))
        elif frozen and passed == True:
            responseToken.enqueue(
                serverPackets.notification(
                    "Your window for liveplay sumbission has expired! Your account has been restricted as per our cheating policy. Please contact staff for more information on what can be done. This can be done via the osuHOW Discord server."
                ))
            userUtils.restrict(responseToken.userID)

        #we thank unfrozen people
        first = IsFrozen["firstloginafterfrozen"]

        if not frozen and first:
            responseToken.enqueue(
                serverPackets.notification(
                    "Thank you for providing a liveplay! You have proven your legitemacy and have subsequently been unfrozen."
                ))
            glob.db.execute(
                f"UPDATE users SET firstloginafterfrozen = 0 WHERE id = {userID}"
            )

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

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

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

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

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

        # Send login notification before maintenance message
        #if glob.banchoConf.config["loginNotification"] != "":

        #creating notification
        OnlineUsers = int(
            glob.redis.get("ripple:online_users").decode("utf-8"))
        Notif = f"""- Online Users: {OnlineUsers}
		- {random.choice(glob.banchoConf.config['Quotes'])}"""
        responseToken.enqueue(serverPackets.notification(Notif))

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

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

            #hqosu legacy
            elif aobaHelper.getOsuVer(userID) == "b20190716.5":
                log.info(f"Account {userID} is maybe using hqosu legacy")

            elif tornadoRequest.request.headers.get(
                    "a") == "@_@_@_@_@_@_@_@___@_@_@_@___@_@___@":
                log.info("Account ID {} tried to use secret!".format(userID))
                if userUtils.isRestricted(userID):
                    responseToken.enqueue(
                        serverPackets.notification(
                            "You're banned because you're currently using some darkness secret that no one has..."
                        ))
                    return
                else:
                    glob.tokens.deleteToken(userID)
                    userUtils.restrict(userID)
                    #if glob.conf.config["discord"]["enable"] == True:
                    webhook = aobaHelper.Webhook(
                        glob.conf.config["discord"]["anticheat"],
                        color=0xadd8e6,
                        footer="@_@_@_@_@_@_@_@___@_@_@_@___@_@___@")
                    webhook.set_title(
                        title="Catched some cheater Account ID {}".format(
                            userID))
                    webhook.set_desc(
                        "{} tried to @_@_@_@_@_@_@_@___@_@_@_@___@_@___@ and got restricted!"
                        .format(username))
                    log.info("Sent to webhook {} DONE!!".format(
                        glob.conf.config["discord"]["enable"]))
                    webhook.post()
                    raise exceptions.loginCheatClientsException()

            elif osuVersion.startswith("skoot"):
                log.info(f"Account {userID} tried to skooooot!!!!")
                if userUtils.isRestricted(userID):
                    responseToken.enqueue(
                        serverPackets.notification(
                            "oyoyoyoyoyoyoy no skooooooooting allowed here bud"
                        ))
                else:
                    glob.tokens.deleteToken(userID)
                    userUtils.restrict(userID)
                    raise exceptions.loginCheatClientsException()
            elif osuVersion[0] != "b":
                glob.tokens.deleteToken(userID)
                raise exceptions.haxException()

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

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

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

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

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

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

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

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

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

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

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

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

        # Return token string and data
        return responseTokenString, responseData
Esempio n. 9
0
	def setScores(self):
		"""
		Set scores list
		"""
		# Reset score list
		self.scores = []
		self.scores.append(-1)

		# Make sure the beatmap is ranked
		if self.beatmap.rankedStatus < rankedStatuses.RANKED:
			return

		# Query parts
		cdef str select = ""
		cdef str joins = ""
		cdef str country = ""
		cdef str mods = ""
		cdef str friends = ""
		cdef str order = ""
		cdef str limit = ""

		# Find personal best score
		personalBestScoreID = self.getPersonalBestID()
		isPremium = userUtils.getPrivileges(self.userID) & privileges.USER_PREMIUM
		# Output our personal best if found
		if personalBestScoreID is not None:
			s = rxscore.score(personalBestScoreID)
			self.scores[0] = s
		else:
			# No personal best
			self.scores[0] = -1

		# Get top 50 scores
		select = "SELECT *"
		joins = "FROM scores_relax STRAIGHT_JOIN users ON scores_relax.userid = users.id STRAIGHT_JOIN users_stats ON users.id = users_stats.id WHERE scores_relax.beatmap_md5 = %(beatmap_md5)s AND scores_relax.play_mode = %(play_mode)s AND scores_relax.completed = 3 AND (users.privileges & 1 > 0 OR users.id = %(userid)s)"

		# Country ranking
		if self.country:
			country = "AND users_stats.country = (SELECT country FROM users_stats WHERE id = %(userid)s LIMIT 1)"
		else:
			country = ""

		# Mods ranking (ignore auto, since we use it for pp sorting)
		if self.mods > -1:
			mods = "AND scores_relax.mods = %(mods)s"
		else:
			mods = ""

		# Friends ranking
		if self.friends:
			friends = "AND (scores_relax.userid IN (SELECT user2 FROM users_relationships WHERE user1 = %(userid)s) OR scores_relax.userid = %(userid)s)"
		else:
			friends = ""
		
		if self.beatmap.rankedStatus == rankedStatuses.LOVED:
			order = "ORDER BY score DESC"
		else:
			order = "ORDER BY pp DESC"
		
		if isPremium: # Premium members can see up to 100 scores on leaderboards
			limit = "LIMIT 100"
		else:
			limit = "LIMIT 50"

		# Build query, get params and run query
		query = self.buildQuery(locals())
		params = {"beatmap_md5": self.beatmap.fileMD5, "play_mode": self.gameMode, "userid": self.userID, "mods": self.mods}
		topScores = glob.db.fetchAll(query, params)

		# Set data for all scores
		cdef int c = 1
		cdef dict topScore
		if topScores is not None:
			for topScore in topScores:
				# Create score object
				s = rxscore.score(topScore["id"], setData=False)

				# Set data and rank from topScores's row
				s.setDataFromDict(topScore)
				s.rank = c

				# Check if this top 50 score is our personal best
				if s.playerName == self.username:
					self.personalBestRank = c

				# Add this score to scores list and increment rank
				self.scores.append(s)
				c+=1

		'''# If we have more than 50 scores, run query to get scores count
		if c >= 50:
			# Count all scores on this map
			select = "SELECT COUNT(*) AS count"
			limit = "LIMIT 1"
			# Build query, get params and run query
			query = self.buildQuery(locals())
			count = glob.db.fetch(query, params)
			if count == None:
				self.totalScores = 0
			else:
				self.totalScores = count["count"]
		else:
			self.totalScores = c-1'''

		# If personal best score was not in top 50, try to get it from cache
		if personalBestScoreID is not None and self.personalBestRank < 1:
			self.personalBestRank = glob.personalBestCache.get(self.userID, self.beatmap.fileMD5, self.country, self.friends, self.mods)

		# It's not even in cache, get it from db
		if personalBestScoreID is not None and self.personalBestRank < 1:
			self.setPersonalBestRank()

		# Cache our personal best rank so we can eventually use it later as
		# before personal best rank" in submit modular when building ranking panel
		if self.personalBestRank >= 1:
			glob.personalBestCache.set(self.userID, self.personalBestRank, self.beatmap.fileMD5)
Esempio n. 10
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)

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

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

		userFlags = userUtils.getUserFlags(userID)
		if userFlags > 0: # Pending public bans. Such as chargebacks, etc.
			flagReason = userUtils.getFlagReason(userID)
			if userFlags-int(time.time()) < 0:
				responseToken.enqueue(serverPackets.notification("Your account has been automatically restricted due to a pending restriction not being having been dealt with.\n\nReason: {}".format(flagReason)))
				userUtils.restrict(userID)
				userUtils.setUserFlags(userID, 0)
				log.cmyui("{} has been automatically restricted due to not dealing with pending restriction. Reason: {}.".format(username, flagReason), discord="cm")
				log.rap(userID, "has been restricted due to a pending restriction. Reason: {}.".format(flagReason))
			else:
				if "charge" in flagReason:
					responseToken.enqueue(serverPackets.notification("Your account has been flagged with an automatic restriction.\n\nIt will occur at {time} if not dealt with.\n"
						"Reason: {reason}\n\nTo avoid being restricted for this behaviour, you can cancel or revert your chargeback before your restriction date.".format(time=datetime.utcfromtimestamp(int(userFlags)).strftime('%Y-%m-%d %H:%M:%S'), reason=flagReason)))
				elif "live" in flagReason:
					responseToken.enqueue(serverPackets.notification("Your account has been flagged with an automatic restriction.\n\nIt will occur at {time} if not dealt with.\n"
						"Reason: {reason}\n\nThis means you are required to submit a liveplay to avoid this. This only happens in cases when we are confident in foul play; and are offering you this opportunity as a final stance to prove your legitimacy, against all the odds.".format(time=datetime.utcfromtimestamp(int(userFlags)).strftime('%Y-%m-%d %H:%M:%S'), reason=flagReason)))
				else:
					responseToken.enqueue(serverPackets.notification("Your account has been flagged with an automatic restriction.\n\nIt will occur at {time} if not dealt with.\n"
						"Reason: {reason}\n\nYou have until the restriction to deal with the issue.".format(time=datetime.utcfromtimestamp(int(userFlags)).strftime('%Y-%m-%d %H:%M:%S'), reason=flagReason)))

		# Send message if premium / donor expires soon
		# ok spaghetti code time
		if responseToken.privileges & privileges.USER_DONOR:
			donorType = 'premium' if responseToken.privileges & privileges.USER_PREMIUM else 'donor'

			expireDate = userUtils.getDonorExpire(responseToken.userID)
			if expireDate-int(time.time()) < 0:
				userUtils.setPrivileges(userID, 3)
				log.cmyui("{}'s donation perks have been removed as their time has run out.".format(username), discord="cm")
				log.rap(userID, "User's donor perks have been removed as their time has run out.")
				responseToken.enqueue(serverPackets.notification("Your {donorType} tag has expired! Thank you so much for the support, it really means everything to us. If you wish to keep supporting Akatsuki and you don't want to lose your {donorType} privileges, you can donate again by clicking on 'Support us' on Akatsuki's website.".format(donorType=donorType)))
			elif expireDate-int(time.time()) <= 86400*3:
				expireDays = round((expireDate-int(time.time()))/86400)
				expireIn = "{} days".format(expireDays) if expireDays > 1 else "less than 24 hours"
				responseToken.enqueue(serverPackets.notification("Your {donorType} tag expires in {expireIn}! When your {donorType} tag expires, you won't have any of the {donorType} privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Akatsuki and you don't want to lose your {donorType} privileges, you can donate again by clicking on 'Support us' on Akatsuki's website.".format(donorType=donorType, expireIn=expireIn)))

		""" Akatsuki does not use 2fa! we suck!
		if userUtils.deprecateTelegram2Fa(userID):
			responseToken.enqueue(serverPackets.notification("As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://akatsuki.pw. Thank you for your patience."))
		"""
		
		# Set silence end UNIX time in token
		responseToken.silenceEndTime = userUtils.getSilenceEnd(userID)

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

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

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

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

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

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

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

		#Akatsuki extra channels
		chat.joinChannel(token=responseToken, channel="#nowranked")
		chat.joinChannel(token=responseToken, channel="#request")

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

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

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

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

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

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

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

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

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

		# Set reponse data to right value and reset our queue
		responseData = responseToken.queue
		responseToken.resetQueue()
	except exceptions.loginFailedException:
		# Login failed error packet
		# (we don't use enqueue because we don't have a token since login has failed)
		responseData += serverPackets.loginFailed()
	except exceptions.invalidArgumentsException:
		# Invalid POST data
		# (we don't use enqueue because we don't have a token since login has failed)
		responseData += serverPackets.loginFailed()
		responseData += serverPackets.notification("We see what you're doing..")
		log.cmyui("User {} has triggered invalidArgumentsException in loginEvent.py".format(userID), discord="cm")
	except exceptions.loginBannedException:
		# Login banned error packet
		responseData += serverPackets.loginBanned()
	except exceptions.loginLockedException:
		# Login banned error packet
		responseData += serverPackets.loginLocked()
	except exceptions.banchoMaintenanceException:
		# Bancho is in maintenance mode
		responseData = bytes()
		if responseToken is not None:
			responseData = responseToken.queue
		responseData += serverPackets.notification("Akatsuki is currently in maintenance mode. Please try to login again later.")
		responseData += serverPackets.loginFailed()
	except exceptions.banchoRestartingException:
		# Bancho is restarting
		responseData += serverPackets.notification("Akatsuki is restarting. Try again in a few minutes.")
		responseData += serverPackets.loginFailed()
	except exceptions.need2FAException:
		# User tried to log in from unknown IP
		responseData += serverPackets.needVerification()
	except exceptions.haxException:
		# Using oldoldold client, we don't have client data. Force update.
		# (we don't use enqueue because we don't have a token since login has failed)
		responseData += serverPackets.forceUpdate()
		responseData += serverPackets.notification("Custom clients of ANY kind are NOT PERMITTED on Akatsuki. Please login using the current osu! client.")
		log.cmyui("User {} has logged in with a VERY old client".format(userID), discord="cm")
	except:
		log.error("Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc()))
	finally:
		# Console and discord log
		if len(loginData) < 3:
			log.info("Invalid bancho login request from **{}** (insufficient POST data)".format(requestIP), "bunker")

		# Return token string and data
		return responseTokenString, responseData
Esempio n. 11
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="Man... this is worst player. [ Login Gate AC ]"
                    )
                    webhook.set_title(
                        title="Catched some cheater Account ID {}".format(
                            userID))
                    webhook.set_desc(
                        "{} tried to use Ainu (Cheat) Client 2020! AGAIN!!!".
                        format(username))
                    log.info("Sent to webhook {} DONE!!".format(
                        glob.conf.config["discord"]["enable"]))
                    aobaHelper.Webhook.post()
                else:
                    glob.tokens.deleteToken(userID)
                    userUtils.restrict(userID)
                    #if glob.conf.config["discord"]["enable"] == True:
                    webhook = aobaHelper.Webhook(
                        glob.conf.config["discord"]["anticheat"],
                        color=0xadd8e6,
                        footer="Man... this is worst player. [ Login Gate AC ]"
                    )
                    webhook.set_title(
                        title="Catched some cheater Account ID {}".format(
                            userID))
                    webhook.set_desc(
                        "{} tried to use Ainu (Cheat) Client 2020 and got restricted!"
                        .format(username))
                    log.info("Sent to webhook {} DONE!!".format(
                        glob.conf.config["discord"]["enable"]))
                    webhook.post()
                    raise exceptions.loginCheatClientsException()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        # Return token string and data
        return responseTokenString, responseData
Esempio n. 12
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()
        if len(splitData) > 4:
            ignoreDM = bool(int(splitData[4]))
        else:
            ignoreDM = False

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

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

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

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

        # No login errors!

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

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

        # This is false only if HWID is empty
        # if HWID is banned, we get restricted so there's no
        # need to deny bancho access

        if not hwAllowed:
            raise exceptions.haxException()

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

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

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

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

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

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

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

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

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

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

        def lildemon_list_over_pp():
            a = 'VANILLA RELAX'.split()
            b = 'std taiko ctb mania'.split()
            c = []
            for d in range(len(a)):
                m = [userUtils.getUserStats, userUtils.getUserStatsRx][d]
                for e in range(len(b)):
                    j = userUtils.noPPLimit(userID, e, d)
                    if d == 1 and e == 3:
                        continue
                    f, g, h, i = userUtils.obtainPPLimit(userID,
                                                         e,
                                                         relax=(d == 1),
                                                         modded=False)
                    k = j & 2
                    l = m(userID, e)
                    if l['pp'] >= i and not k:
                        c.append((a[d], b[e], l['pp'], i))
            return c

        # Little demon
        lildemon_overpp = lildemon_list_over_pp()
        if lildemon_overpp:
            responseToken.enqueue(
                serverPackets.notification(
                    "Hi fellow little demon! Did you forgot to submit yourself to the guild? I'm afraid you may not be able to advance with it."
                ))
            responseToken.enqueue(
                serverPackets.notification(
                    "To consider further little demon training, here I jotted down some of your achievements!\n\n{}"
                    .format("\n".join("{}:{} {:d}/{:d}".format(*ldop)
                                      for ldop in lildemon_overpp))))

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

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

        # BAN CUSTOM CHEAT CLIENTS
        # 0Ainu = First Ainu build
        # b20190326.2 = Ainu build 2 (MPGH PAGE 10)
        # b20190401.22f56c084ba339eefd9c7ca4335e246f80 = Ainu Aoba's Birthday Build
        # b20191223.3 = Unknown Ainu build? (Taken from most users osuver in cookiezi.pw)
        # b20190226.2 = hqOsu (hq-af)
        def anticheat_boot(clientName, banNotif):
            log.info("Account ID {} tried to use {}!".format(
                userID, clientName))

            def webhook_time(restrict=False):
                webhook = aobaHelper.Webhook(
                    glob.conf.config["discord"]["anticheat"],
                    color=0xadd8e6,
                    footer="Man... this is worst player. [ Login Gate AC ]")
                webhook.set_title(
                    title="Catched some cheater Account ID {}".format(userID))
                if restrict:
                    webhook.set_desc(
                        f' tried to use {clientName} and got restricted!')
                else:
                    webhook.set_desc(f' tried to use {clientName}!')
                log.info("Sent to webhook {} DONE!!".format(
                    glob.conf.config["discord"]["enable"]))
                webhook.post()

            if responseToken.admin:
                responseToken.enqueue(
                    serverPackets.notification(
                        "Kamu ngapain pake {}? stress.".format(clientName)))
            elif userUtils.isRestricted(userID):
                responseToken.enqueue(serverPackets.notification(banNotif))
                #if glob.conf.config["discord"]["enable"] == True:
                webhook_time()
            else:
                glob.tokens.deleteToken(userID)
                userUtils.restrict(userID)
                #if glob.conf.config["discord"]["enable"] == True:
                webhook_time(restrict=True)
                raise exceptions.loginCheatClientsException()
            pass

        if glob.conf.extra["mode"]["anticheat"]:
            # Ainu Client 2020 update
            if tornadoRequest.request.headers.get("ainu") == "happy":
                anticheat_boot(
                    'Ainu Client 2020',
                    "You're banned because you're currently using Ainu Client... Happy New Year 2020 and Enjoy your restriction :)"
                )

            # Ainu Client 2019
            elif aobaHelper.getOsuVer(userID) in [
                    "0Ainu", "b20190326.2",
                    "b20190401.22f56c084ba339eefd9c7ca4335e246f80",
                    "b20191223.3"
            ]:
                anticheat_boot(
                    'Ainu Client',
                    "You're banned because you're currently using Ainu Client. Enjoy your restriction :)"
                )

            # hqOsu
            elif aobaHelper.getOsuVer(userID) == "b20190226.2":
                anticheat_boot(
                    'hqOsu',
                    "Trying to use hqOsu in here? Well... No, sorry. We don't allow cheats here. Go play https://cookiezi.pw or others cheat server."
                )

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

        # Channel info end (before starting!?! wtf bancho?)
        responseToken.enqueue(serverPackets.channelInfoEnd())
        # Default opened channels
        if True:
            for channelAutoJoin in glob.db.fetchAll(
                    'SELECT bcc.privilege_bit AS pb, bc.name AS cn FROM bancho_client_channels as bcc JOIN bancho_channels AS bc ON bcc.channel_id = bc.id'
            ):
                if responseToken.admin or responseToken.privileges & (
                        1 << channelAutoJoin['pb']):
                    chat.joinChannel(token=responseToken,
                                     channel=channelAutoJoin['cn'])
        else:
            chat.joinChannel(token=responseToken, channel="#osu")
            chat.joinChannel(token=responseToken, channel="#announce")
            chat.joinChannel(token=responseToken, channel="#ranked-now")

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

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

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

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

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

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

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

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

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

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

        # Return token string and data
        return responseTokenString, responseData
Esempio n. 13
0
def tillerinoLast(fro, chan, message):
    try:
        # Run the command in PM only
        if userUtils.getPrivileges(userUtils.getID(fro)) & privileges.USER_DONOR == 0 and chan.startswith("#"):
            return "Only donors can write here this command."

        data = glob.db.fetch("""SELECT beatmaps.song_name as sn, scores.*,
			beatmaps.beatmap_id as bid, beatmaps.difficulty_std, beatmaps.difficulty_taiko, beatmaps.difficulty_ctb, beatmaps.difficulty_mania, beatmaps.max_combo as fc
		FROM scores
		LEFT JOIN beatmaps ON beatmaps.beatmap_md5=scores.beatmap_md5
		LEFT JOIN users ON users.id = scores.userid
		WHERE users.username = %s
		ORDER BY scores.time DESC
		LIMIT 1""", [fro])
        if data is None:
            return False

        diffString = "difficulty_{}".format(gameModes.getGameModeForDB(data["play_mode"]))
        rank = generalUtils.getRank(data["play_mode"], data["mods"], data["accuracy"],
                                    data["300_count"], data["100_count"], data["50_count"], data["misses_count"])

        ifPlayer = "{0} | ".format(fro) if chan != glob.BOT_NAME else ""
        ifFc = " (FC)" if data["max_combo"] == data["fc"] else " {0}x/{1}x".format(data["max_combo"], data["fc"])
        beatmapLink = "[http://osu.ppy.sh/b/{1} {0}]".format(data["sn"], data["bid"])

        hasPP = data["play_mode"] != gameModes.CTB

        msg = ifPlayer
        msg += beatmapLink
        if data["play_mode"] != gameModes.STD:
            msg += " <{0}>".format(gameModes.getGameModeForPrinting(data["play_mode"]))

        if data["mods"]:
            msg += ' +' + generalUtils.readableMods(data["mods"])

        if not hasPP:
            msg += " | {0:,}".format(data["score"])
            msg += ifFc
            msg += " | {0:.2f}%, {1}".format(data["accuracy"], rank.upper())
            msg += " {{ {0} / {1} / {2} / {3} }}".format(data["300_count"], data["100_count"], data["50_count"],
                                                         data["misses_count"])
            msg += " | {0:.2f} stars".format(data[diffString])
            return msg

        msg += " ({0:.2f}%, {1})".format(data["accuracy"], rank.upper())
        msg += ifFc
        msg += " | {0:.2f}pp".format(data["pp"])

        stars = data[diffString]
        if data["mods"]:
            token = glob.tokens.getTokenFromUsername(fro)
            if token is None:
                return False
            userID = token.userID
            token.tillerino[0] = data["bid"]
            token.tillerino[1] = data["mods"]
            token.tillerino[2] = data["accuracy"]
            oppai_data = get_pp_message(userID, just_data=True)
            if "stars" in oppai_data:
                stars = oppai_data["stars"]

        msg += " | {0:.2f} stars".format(stars)
        return msg
    except Exception as a:
        log.error(a)
        return False
Esempio n. 14
0
    def asyncGet(self):
        output = ""
        try:
            if not requestsManager.checkArguments(self.request.arguments,
                                                  ["u", "h"]):
                raise exceptions.invalidArgumentsException(MODULE_NAME)

            username = self.get_argument("u", "")
            password = self.get_argument("h", "")
            userID = userUtils.getID(username)
            if not userUtils.checkLogin(userID, password):
                self.write("error:pass")
                return
            gameMode = self.get_argument("m", "-1")
            rankedStatus = self.get_argument("r", "-1")
            query = self.get_argument("q", "")
            page = int(self.get_argument("p", "0"))

            glob.db = connectToDB(1)

            query = query.lower()

            whereClause = []

            #stars filter
            regexp = r"stars[<>=]\d+(\*\d*)?"
            for match in re.finditer(regexp, query):
                matchStr = match.group(0)
                query = query.replace(matchStr, "")
                num = float(matchStr[6:])
                whereClause.append("difficulty_std " + matchStr[5] + " " +
                                   str(num))

            #ar filter
            regexp = r"ar[<>=]\d+(\*\d*)?"
            for match in re.finditer(regexp, query):
                matchStr = match.group(0)
                query = query.replace(matchStr, "")
                num = float(matchStr[3:])
                whereClause.append("ar " + matchStr[2] + " " + str(num))

            #cs filter
            regexp = r"cs[<>=]\d+(\*\d*)?"
            for match in re.finditer(regexp, query):
                matchStr = match.group(0)
                query = query.replace(matchStr, "")
                num = float(matchStr[3:])
                whereClause.append("cs " + matchStr[2] + " " + str(num))

            #max_combo
            regexp = r"combo[<>=]\d+"
            for match in re.finditer(regexp, query):
                matchStr = match.group(0)
                query = query.replace(matchStr, "")
                num = int(matchStr[6:])
                whereClause.append("max_combo " + matchStr[5] + " " + str(num))

            #length filter
            regexp = r"length[<>=]\d+"
            for match in re.finditer(regexp, query):
                matchStr = match.group(0)
                query = query.replace(matchStr, "")
                num = int(matchStr[7:])
                whereClause.append("hit_length " + matchStr[6] + " " +
                                   str(num))

            #bpm filter
            regexp = r"bpm[<>=]\d+"
            for match in re.finditer(regexp, query):
                matchStr = match.group(0)
                query = query.replace(matchStr, "")
                num = int(matchStr[4:])
                whereClause.append("bpm " + matchStr[3] + " " + str(num))

            if query.lower() in ["newest", "top rated", "most played"]:
                query = ""

            #get response from API
            response = requests.get(
                "https://osu.gatari.pw/api/v1/beatmaps/search?r={0}&q={1}&m={2}&p={3}"
                .format(rankedStatus, query, gameMode, page))

            if len(whereClause
                   ) > 0 and userUtils.getPrivileges(userID) & 4 > 0:
                #join with Database
                bs_ids = parseBeatmapsetIdsFromDirect(response.text)

                if len(bs_ids) == 0:
                    return

                whereClause.append("beatmapset_id IN (" + ",".join(bs_ids) +
                                   ")")

                pandasResult = pandasQuery(
                    "select DISTINCT beatmapset_id from beatmaps WHERE " +
                    " AND ".join(whereClause))

                if len(pandasResult) == 0:
                    return

                filtered_bs_ids = np.array(pandasResult["beatmapset_id"])
                response_rows = response.text.split("\n")[1:-1]
                new_rows = [
                    row for row in response_rows
                    if row.split('|')[7] in list(map(str, filtered_bs_ids))
                ]
                result = str(len(new_rows)) + "\n" + "\n".join(new_rows) + "\n"
                output += result
            else:
                output += response.text

        finally:
            self.write(output)
Esempio n. 15
0
    def __init__(self,
                 userID,
                 token_=None,
                 ip="",
                 irc=False,
                 timeOffset=0,
                 tournament=False,
                 ignoreDM=False):
        """
		Create a token object and set userID and token

		:param userID: user associated to this token
		:param token_: 	if passed, set token to that value
						if not passed, token will be generated
		:param ip: client ip. optional.
		:param irc: if True, set this token as IRC client. Default: False.
		:param timeOffset: the time offset from UTC for this user. Default: 0.
		:param tournament: if True, flag this client as a tournement client. Default: True.
		"""
        # Set stuff
        self.userID = userID
        self.username = userUtils.getUsername(self.userID)
        self.safeUsername = userUtils.getSafeUsername(self.userID)
        self.privileges = userUtils.getPrivileges(self.userID)
        self.admin = userUtils.isInPrivilegeGroup(self.userID, "Developer")\
            or userUtils.isInPrivilegeGroup(self.userID, "Community Manager")\
            or userUtils.isInPrivilegeGroup(self.userID, "Chat Moderators")
        self.irc = irc
        self.kicked = False
        self.restricted = userUtils.isRestricted(self.userID)
        self.loginTime = int(time.time())
        self.pingTime = self.loginTime
        self.timeOffset = timeOffset
        self.streams = []
        self.tournament = tournament
        self.messagesBuffer = []

        # Default variables
        self.spectators = []

        # TODO: Move those two vars to a class
        self.spectating = None
        self.spectatingUserID = 0  # we need this in case we the host gets DCed

        self.location = [0, 0]
        self.joinedChannels = []
        self.ip = ip
        self.country = 0
        self.location = [0, 0]
        self.awayMessage = ""
        self.sentAway = []
        self.matchID = -1
        self.tillerino = [0, 0, -1.0]  # beatmap, mods, acc
        self.silenceEndTime = 0
        self.queue = bytes()

        # Spam protection
        self.spamRate = 0

        # Stats cache
        def check_hax_initial():
            stmt = [
                "SELECT initial_status_type as aid, initial_status_text as atxt",
                "FROM bancho_status_hax", "WHERE user_id = %s AND NOT",
                "(initial_status_type IS NULL OR initial_status_text IS NULL OR length(initial_status_text) < 1)"
            ]
            result = glob.db.fetch(" ".join(stmt), [userID])
            if result is None:
                self.actionID, self.actionText = actions.IDLE, ""
            else:
                self.actionID, self.actionText = result['aid'], result['atxt']
            pass

        check_hax_initial()
        self.actionMd5 = ""
        self.actionMods = 0
        self.gameMode = gameModes.STD
        self.beatmapID = 0
        self.rankedScore = 0
        self.accuracy = 0.0
        self.playcount = 0
        self.totalScore = 0
        self.gameRank = 0

        self.noticeFlags = []

        self.pp = 0
        self._ignoreDM = ignoreDM

        self.specialMode = 0

        # Generate/set token
        if token_ is not None:
            self.token = token_
        else:
            self.token = str(uuid.uuid4())

        # Locks
        self.processingLock = threading.Lock(
        )  # Acquired while there's an incoming packet from this user
        self._bufferLock = threading.Lock(
        )  # Acquired while writing to packets buffer
        self._spectLock = threading.RLock()

        # Set stats
        self.updateCachedStats()

        # If we have a valid ip, save bancho session in DB so we can cache LETS logins
        if ip != "":
            userUtils.saveBanchoSession(self.userID, self.ip)

        # Join main stream
        self.joinStream("main")
Esempio n. 16
0
    def __init__(self,
                 userID,
                 token_=None,
                 ip="",
                 irc=False,
                 timeOffset=0,
                 tournament=False):
        """
		Create a token object and set userID and token

		:param userID: user associated to this token
		:param token_: 	if passed, set token to that value
						if not passed, token will be generated
		:param ip: client ip. optional.
		:param irc: if True, set this token as IRC client. Default: False.
		:param timeOffset: the time offset from UTC for this user. Default: 0.
		:param tournament: if True, flag this client as a tournement client. Default: True.
		"""
        # Set stuff
        self.userID = userID
        self.username = userUtils.getUsername(self.userID)
        self.safeUsername = userUtils.getSafeUsername(self.userID)
        self.privileges = userUtils.getPrivileges(self.userID)
        self.admin = userUtils.isInPrivilegeGroup(self.userID, "developer")\
            or userUtils.isInPrivilegeGroup(self.userID, "community manager")\
            or userUtils.isInPrivilegeGroup(self.userID, "chat mod")
        self.irc = irc
        self.kicked = False
        self.restricted = userUtils.isRestricted(self.userID)
        self.loginTime = int(time.time())
        self.pingTime = self.loginTime
        self.timeOffset = timeOffset
        self.streams = []
        self.tournament = tournament
        self.messagesBuffer = []

        # Default variables
        self.spectators = []

        # TODO: Move those two vars to a class
        self.spectating = None
        self.spectatingUserID = 0  # we need this in case we the host gets DCed

        self.location = [0, 0]
        self.joinedChannels = []
        self.ip = ip
        self.country = 0
        self.location = [0, 0]
        self.awayMessage = ""
        self.sentAway = []
        self.matchID = -1
        self.tillerino = [0, 0, -1.0]  # beatmap, mods, acc
        self.silenceEndTime = 0
        self.queue = bytes()

        # Spam protection
        self.spamRate = 0

        # Stats cache
        if userID == 1000:
            self.actionID = actions.WATCHING
        else:
            self.actionID = actions.IDLE
        if userID == 1000:
            self.actionText = "HentaiHaven"
        else:
            self.actionText = ""
        self.actionMd5 = ""
        self.actionMods = 0
        self.gameMode = gameModes.STD
        self.beatmapID = 0
        self.rankedScore = 0
        self.accuracy = 0.0
        self.playcount = 0
        self.totalScore = 0
        self.gameRank = 0
        self.pp = 0

        # Relax
        self.relaxing = False
        self.relaxAnnounce = False

        # Generate/set token
        if token_ is not None:
            self.token = token_
        else:
            self.token = str(uuid.uuid4())

        # Locks
        self.processingLock = threading.Lock(
        )  # Acquired while there's an incoming packet from this user
        self._bufferLock = threading.Lock(
        )  # Acquired while writing to packets buffer
        self._spectLock = threading.RLock()

        # Set stats
        self.updateCachedStats()

        # If we have a valid ip, save bancho session in DB so we can cache LETS logins
        if ip != "":
            userUtils.saveBanchoSession(self.userID, self.ip)

        # Join main stream
        self.joinStream("main")
Esempio n. 17
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
Esempio n. 18
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)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        # Return token string and data
        return responseTokenString, responseData
Esempio n. 19
0
	def joinHandler(self, _, arguments):
		"""JOIN command handler"""
		if len(arguments) < 1:
			self.reply461("JOIN")
			return

		# Get bancho token object
		token = glob.tokens.getTokenFromUsername(self.banchoUsername)
		if token is None:
			return

		# TODO: Part all channels
		if arguments[0] == "0":
			'''for (channelname, channel) in self.channels.items():
				self.message_channel(channel, "PART", channelname, True)
				self.channel_log(channel, "left", meta=True)
				server.remove_member_from_channel(self, channelname)
			self.channels = {}
			return'''
			return

		# Get channels to join list
		channels = arguments[0].split(",")
		userID = self.supposedUserID
		userPriv = userUtils.getPrivileges(userID)
		isChatMod = bool(userPriv & privileges.ADMIN_CHAT_MOD)
		

		for channel in channels:
			# Make sure we are not already in that channel
			# (we already check this bancho-side, but we need to do it
			# also here k maron)
			if channel.lower() in token.joinedChannels:
				continue

			# Attempt to join the channel
			response = chat.IRCJoinChannel(self.banchoUsername, channel)
			if response == 0:
				# Joined successfully
				self.joinedChannels.append(channel)

				# Let everyone in this channel know that we've joined
				self.messageChannel(channel, "{} JOIN".format(self.IRCUsername), channel, True)
				if isChatMod:
					self.messageChannel(channel, "{} MODE {} {}".format(glob.BOT_NAME, channel, '+o'), self.IRCUsername, True)
				self.message(':{} MODE {} {}'.format(glob.BOT_NAME, glob.BOT_NAME, 'o'))

				# Send channel description (topic)
				description = glob.channels.channels[channel].description
				if description == "":
					self.replyCode(331, "No topic is set", channel=channel)
				else:
					self.replyCode(332, description, channel=channel)
				# self.message(":{} MODE {} {}".format(glob.BOT_NAME, '+nt', channel))

				# Build connected users list
				if "chat/{}".format(channel) not in glob.streams.streams:
					self.reply403(channel)
					continue
				users = glob.streams.streams["chat/{}".format(channel)].clients
				usernames = []
				for user in users:
					if user not in glob.tokens.tokens:
						continue
					usernames.append(chat.fixUsernameForIRC(glob.tokens.tokens[user].username))
				usernames = " ".join(usernames)

				# Send IRC users list
				self.replyCode(353, usernames, channel="= {}".format(channel))
				self.replyCode(366, "End of NAMES list", channel=channel)
			elif response == 403:
				# Channel doesn't exist (or no read permissions)
				self.reply403(channel)
				continue