Пример #1
0
async def login():
    start = time.time()
    headers = request.headers # request headers, used for things such as user ip and agent

    if 'User-Agent' not in headers or headers['User-Agent'] != 'osu!':
        # request isn't sent from osu client, return html
        message = f"{pyfiglet.figlet_format(f'Asahi v{glob.version}')}\n\ntsunyoku attempts bancho v2, gone right :sunglasses:"
        return Response(message, mimetype='text/plain')

    if 'osu-token' not in headers: # sometimes a login request will be a re-connect attempt, in which case they will already have a token, if not: login the user
        data = await request.data # request data, used to get info such as username to login the user
        if len(info := data.decode().split('\n')[:-1]) != 3: # format data so we can use it easier & also ensure it is valid at the same time
            resp = await make_response(packets.userID(-2)) # -2 userid informs client it is too old | i assume that is the only valid reason for this to happen
            resp.headers['cho-token'] = 'no' # client knows there is something up if we set token to 'no'
            return resp

        if len(cinfo := info[2].split('|')) != 5: # format client data (hash, utc etc.) & ensure it is valid
            resp = await make_response(packets.userID(-2)) # -2 userid informs client it is too old
            resp.headers['cho-token'] = 'no' # client knows there is something up if we set token to 'no'
            return resp
Пример #2
0
    def restrict(self) -> None:  # TODO: reason
        self.priv &= ~Privileges.Normal
        glob.db.execute('UPDATE users SET priv = %s WHERE id = %s',
                        [int(self.priv), self.id])

        if self in glob.players:
            # If user is online, notify and log them out.
            # XXX: If you want to lock the player's
            # client, you can send -3 rather than -1.
            self.enqueue(packets.userID(-1))
            self.enqueue(
                packets.notification('Your account has been banned.\n\n'
                                     'If you believe this was a mistake or '
                                     'have waited >= 2 months, you can appeal '
                                     'using the appeal form on the website.'))

        printlog(f'Restricted {self}.', Ansi.CYAN)
Пример #3
0
    async def ban(self, admin: 'Player', reason: str) -> None:
        self.priv &= ~Privileges.Normal
        await glob.db.execute('UPDATE users SET priv = %s WHERE id = %s',
                              [int(self.priv), self.id])

        log_msg = f'{admin} banned for "{reason}".'
        await glob.db.execute(
            'INSERT INTO logs (`from`, `to`, `msg`, `time`) '
            'VALUES (%s, %s, %s, NOW())', [admin.id, self.id, log_msg])

        if self in glob.players:
            # if user is online, notify and log them out.
            # XXX: if you want to lock the player's
            # client, you can send -3 rather than -1.
            self.enqueue(packets.userID(-1))
            self.enqueue(
                packets.notification('Your account has been banned.\n\n'
                                     'If you believe this was a mistake or '
                                     'have waited >= 2 months, you can appeal '
                                     'using the appeal form on the website.'))

        log(f'Banned {self}.', Ansi.CYAN)
Пример #4
0
    # and token in a tuple - we need both for our response.
    if len(s := origin.decode().split('\n')[:-1]) != 3:
        return

    username = s[0]
    login_time = time.time()

    if p := await glob.players.get(name=username):
        if (login_time - p.last_recv_time) > 10:
            # if the current player obj online hasn't
            # pinged the server in > 10 seconds, log
            # them out and login the new user.
            await p.logout()
        else:
            # the user is currently online, send back failure.
            data = packets.userID(-1) + \
                   packets.notification('User already logged in.')

            return data, 'no'

    if 'ainu' in headers:
        if not (t := await glob.players.get(name=username, sql=True)):
            return f'"{username}" not found.'
        reason = 'Cheat client found.'
        await t.ban(p, reason)

    pw_md5 = s[1].encode()

    if len(s := s[2].split('|')) != 5:
        return packets.userID(-2), 'no'
Пример #5
0
    if not (r := regexes.osu_ver.match(client_info[0])):
        return  # invalid request

    # quite a bit faster than using dt.strptime.
    osu_ver = dt(year=int(r['ver'][0:4]),
                 month=int(r['ver'][4:6]),
                 day=int(r['ver'][6:8]))

    # disallow the login if their osu! client is older
    # than two months old, forcing an update re-check.
    # NOTE: this is disabled on debug since older clients
    #       can sometimes be quite useful when testing.
    if not glob.config.debug:
        if osu_ver < (dt.now() - td(60)):
            return (packets.versionUpdateForced() + packets.userID(-2)), 'no'

    # ensure utc_offset is a number (negative inclusive).
    if not _isdecimal(client_info[1], _negative=True):
        return  # invalid request

    utc_offset = int(client_info[1])
    #display_city = client_info[2] == '1'

    # Client hashes contain a few values useful to us.
    # [0]: md5(osu path)
    # [1]: adapters (network physical addresses delimited by '.')
    # [2]: md5(adapters)
    # [3]: md5(uniqueid) (osu! uninstall id)
    # [4]: md5(uniqueid2) (disk signature/serial num)
    if len(client_hashes := client_info[3].split(':')[:-1]) != 5:
Пример #6
0
# client sends a request without an osu-token.
async def login(origin: bytes, ip: str) -> tuple[bytes, str]:
    # login is a bit special, we return the response bytes
    # and token in a tuple - we need both for our response.
    if len(s := origin.decode().split('\n')[:-1]) != 3:
        return

    if p := await glob.players.get_by_name(username := s[0]):
        if (time.time() - p.last_recv_time) > 10:
            # if the current player obj online hasn't
            # pinged the server in > 10 seconds, log
            # them out and login the new user.
            await p.logout()
        else:
            # the user is currently online, send back failure.
            data = packets.userID(-1) + \
                   packets.notification('User already logged in.')

            return data, 'no'

    del p

    pw_hash = s[1].encode()

    if len(s := s[2].split('|')) != 5:
        return

    if not (r := regexes.osu_ver.match(s[0])):
        # invalid client version?
        return packets.userID(-2), 'no'
Пример #7
0
    # and token in a tuple - we need both for our response.
    if len(s := origin.decode().split('\n')[:-1]) != 3:
        return

    username = s[0]
    login_time = time.time()

    if p := await glob.players.get(name=username):
        if (login_time - p.last_recv_time) > 10:
            # if the current player obj online hasn't
            # pinged the server in > 10 seconds, log
            # them out and login the new user.
            await p.logout()
        else:
            # the user is currently online, send back failure.
            data = packets.userID(-1) + \
                   packets.notification('User already logged in.')

            return data, 'no'

    del p

    pw_md5 = s[1].encode()

    if len(s := s[2].split('|')) != 5:
        return packets.userID(-2), 'no'

    if not (r := regexes.osu_ver.match(s[0])):
        # invalid client version?
        return packets.userID(-2), 'no'
Пример #8
0
            resp.headers['cho-token'] = 'no' # client knows there is something up if we set token to 'no'
            return resp

        if len(cinfo := info[2].split('|')) != 5: # format client data (hash, utc etc.) & ensure it is valid
            resp = await make_response(packets.userID(-2)) # -2 userid informs client it is too old
            resp.headers['cho-token'] = 'no' # client knows there is something up if we set token to 'no'
            return resp

        username = info[0]
        pw = info[1].encode() # password in md5 form, we will use this to compare against db's stored bcrypt later

        user = await glob.db.fetch('SELECT id, pw, country, name FROM users WHERE name = %s', [username])
        if not user: # ensure user actually exists before attempting to do anything else
            if glob.config.debug:
                log(f'User {username} does not exist. | Time Elapsed: {(time.time() - start) * 1000:.2f}ms', Ansi.LRED)
            resp = await make_response(packets.userID(-1)) # -1 userid informs client of an auth error
            resp.headers['cho-token'] = 'no' # client knows there is something up if we set token to 'no'
            return resp

        bcache = glob.cache['bcrypt'] # get our cached bcrypts to potentially enhance speed
        pw_bcrypt = user['pw'].encode()
        if pw_bcrypt in bcache:
            if pw != bcache[pw_bcrypt]: # compare provided md5 with the stored (cached) bcrypt to ensure they have provided the correct password
                if glob.config.debug:
                    log(f"{username}'s login attempt failed: provided an incorrect password | Time Elapsed (with cached bcrypt): {(time.time() - start) * 1000:.2f}ms", Ansi.LRED)
                resp = await make_response(packets.userID(-1))
                resp.headers['cho-token'] = 'no'
                return resp
            ub = True
        else:
            if not bcrypt.checkpw(pw, pw_bcrypt): # compare provided md5 with the stored bcrypt to ensure they have provided the correct password
Пример #9
0
async def login():
    headers = request.headers  # request headers, used for things such as user ip and agent

    if 'User-Agent' not in headers or headers['User-Agent'] != 'osu!':
        # request isn't sent from osu client, return nothing
        return

    if 'osu-token' not in headers:  # sometimes a login request will be a re-connect attempt, in which case they will already have a token, if not: login the user
        data = await request.data  # request data, used to get info such as username to login the user
        info = data.decode().split(
            '\n')[:-1]  # format data so we can use it easier

        username = info[0]
        pw = info[1].encode(
        )  # password in md5 form, we will use this to compare against db's stored bcrypt later

        user = await glob.db.fetch(
            'SELECT id, pw, country, name FROM users WHERE name = %s',
            [username])
        if not user:  # ensure user actually exists before attempting to do anything else
            log(f'User {username} does not exist.', Ansi.LRED)
            resp = await make_response(packets.userID(-1))
            resp.headers['cho-token'] = 'no'
            return resp

        bcache = glob.cache[
            'bcrypt']  # get our cached bcrypts to potentially enhance speed
        pw_bcrypt = user['pw'].encode()
        if pw_bcrypt in bcache:
            if pw != bcache[
                    pw_bcrypt]:  # compare provided md5 with the stored (cached) bcrypt to ensure they have provided the correct password
                log(
                    f"{username}'s login attempt failed: provided an incorrect password",
                    Ansi.LRED)
                resp = await make_response(packets.userID(-1))
                resp.headers['cho-token'] = 'no'
                return resp
        else:
            if not bcrypt.checkpw(
                    pw, pw_bcrypt
            ):  # compare provided md5 with the stored bcrypt to ensure they have provided the correct password
                log(
                    f"{username}'s login attempt failed: provided an incorrect password",
                    Ansi.LRED)
                resp = await make_response(packets.userID(-1))
                resp.headers['cho-token'] = 'no'
                return resp

            bcache[pw_bcrypt] = pw  # cache pw for future

        token = uuid.uuid4()  # generate token for client to use as auth
        ucache = glob.cache['user']
        if str(token) not in ucache:
            ucache[str(token)] = user[
                'id']  # cache token to use outside of this request
        data = bytearray(packets.userID(
            user['id']))  # initiate login by providing the user's id
        data += packets.protocolVersion(19)  # no clue what this does
        data += packets.banchoPrivileges(
            1 << 4)  # force priv to developer for now
        data += (
            packets.userPresence(user) + packets.userStats(user)
        )  # provide user & other user's presence/stats (for f9 + user stats)
        data += packets.notification(
            f'Welcome to Asahi v{glob.version}'
        )  # send notification as indicator they've logged in iguess
        data += packets.channelInfoEnd()  # no clue what this does either

        resp = await make_response(bytes(data))
        resp.headers['cho-token'] = token
        log(f'{username} successfully logged in.', Ansi.GREEN)
        return resp

    # if we have made it this far then it's a reconnect attempt with token already provided
    user_token = headers['osu-token']  # client-provided token
    tcache = glob.cache[
        'user']  # get token/userid cache to see if we need to relog the user or not
    if user_token not in tcache:
        # user is logged in but token is not found? most likely a restart so we force a reconnection
        return packets.restartServer(0)

    user = await glob.db.fetch(
        'SELECT id, pw, country, name FROM users WHERE id = %s',
        [tcache[user_token]])
    body = await request.body

    # handle any packets the client has sent | doesn't really work **for now**
    for packet in BanchoPacketReader(body, glob.packets):
        await packet.handle(user)
        log(f'Handled packet {packet}')

    resp = await make_response(b'')
    resp.headers['Content-Type'] = 'text/html; charset=UTF-8'  # ?
    return resp
Пример #10
0
# client sends a request without an osu-token.
def login(origin: bytes, ip: str) -> Tuple[bytes, str]:
    # Login is a bit special, we return the response bytes
    # and token in a tuple - we need both for our response.

    s = origin.decode().split('\n')

    if p := glob.players.get_by_name(username := s[0]):
        if (time() - p.ping_time) > 20:
            # If the current player obj online hasn't
            # pinged the server in > 20 seconds, log
            # them out and login the new user.
            p.logout()
        else:  # User is currently online, send back failure.
            return packets.notification('User already logged in.') \
                 + packets.userID(-1), 'no'

    del p

    pw_hash = s[1].encode()

    s = s[2].split('|')
    build_name = s[0]

    if not s[1].replace('-', '', 1).isnumeric():
        return packets.userID(-1), 'no'

    utc_offset = int(s[1])
    display_city = s[2] == '1'

    # TODO: use these