Esempio n. 1
0
def handle_bancho(conn: Connection) -> None:
    if 'User-Agent' not in conn.req.headers:
        return

    if conn.req.headers['User-Agent'] != 'osu!':
        # Most likely a request from a browser.
        conn.resp.send(
            b'<!DOCTYPE html>' + '<br>'.join(
                (f'Running gulag v{glob.version}',
                 f'Players online: {len(glob.players) - 1}',
                 '<a href="https://github.com/cmyui/gulag">Source code</a>',
                 '', '<b>Bancho Handlers</b>', '<br>'.join(
                     f'{int(x)}: {str(x)[9:]}'
                     for x in glob.bancho_map.keys()), '',
                 '<b>/web/ Handlers</b>', '<br>'.join(
                     glob.web_map.keys()))).encode(), 200)
        return

    resp = bytearray()

    if 'osu-token' not in conn.req.headers:
        # Login is a bit of a special case,
        # so we'll handle it separately.
        login_data = loginEvent(conn.req.body, conn.req.headers['X-Real-IP'])

        resp.extend(login_data[0])
        conn.resp.add_header(f'cho-token: {login_data[1]}')

    elif not (p := glob.players.get(conn.req.headers['osu-token'])):
        printlog('Token not found, forcing relog.')
        resp.extend(
            packets.notification('Server is restarting.') +
            packets.restartServer(
                0)  # send 0ms since the server is already up!
        )
Esempio n. 2
0
File: cho.py Progetto: Mxnuuel/gulag
async def bancho_handler(conn: Connection) -> bytes:
    if ('User-Agent' not in conn.headers
            or conn.headers['User-Agent'] != 'osu!'):
        return

    # check for 'osu-token' in the headers.
    # if it's not there, this is a login request.

    if 'osu-token' not in conn.headers:
        # login is a bit of a special case,
        # so we'll handle it separately.
        async with glob.players._lock:
            resp, token = await login(conn.body, conn.headers['X-Real-IP'],
                                      conn.headers)

        conn.add_resp_header(f'cho-token: {token}')
        return resp

    # get the player from the specified osu token.
    player = await glob.players.get(token=conn.headers['osu-token'])

    if not player:
        # token was not found; changes are, we just restarted
        # the server. just tell their client to re-connect.
        return packets.restartServer(0)

    # bancho connections can be comprised of multiple packets;
    # our reader is designed to iterate through them individually,
    # allowing logic to be implemented around the actual handler.

    # NOTE: the reader will internally discard any
    # packets whose logic has not been defined.
    # TODO: why is the packet reader async lol
    async for packet in BanchoPacketReader(conn.body):
        await packet.handle(player)

        if glob.config.debug:
            log(f'{packet.type!r}', Ansi.LMAGENTA)

    player.last_recv_time = time.time()

    # TODO: this could probably be done better?
    resp = bytearray()

    while not player.queue_empty():
        # read all queued packets into stream
        resp += player.dequeue()

    conn.add_resp_header('Content-Type: text/html; charset=UTF-8')
    resp = bytes(resp)

    # even if the packet is empty, we have to
    # send back an empty response so the client
    # knows it was successfully delivered.
    return resp
Esempio n. 3
0
async def bancho_handler(conn: Connection) -> bytes:
    if ('User-Agent' not in conn.headers
            or conn.headers['User-Agent'] != 'osu!'):
        return

    # check for 'osu-token' in the headers.
    # if it's not there, this is a login request.

    if 'osu-token' not in conn.headers:
        # login is a bit of a special case,
        # so we'll handle it separately.
        async with glob.players._lock:
            resp, token = await login(conn.body, conn.headers['X-Real-IP'])

        conn.add_resp_header(f'cho-token: {token}')
        return resp

    # get the player from the specified osu token.
    player = glob.players.get(token=conn.headers['osu-token'])

    if not player:
        # token was not found; changes are, we just restarted
        # the server. just tell their client to re-connect.
        return packets.notification('Server is restarting') + \
               packets.restartServer(0) # send 0ms since server is up

    # restricted users may only use certain packet handlers.
    if not player.restricted:
        packet_map = glob.bancho_packets['all']
    else:
        packet_map = glob.bancho_packets['restricted']

    # bancho connections can be comprised of multiple packets;
    # our reader is designed to iterate through them individually,
    # allowing logic to be implemented around the actual handler.

    # NOTE: the reader will internally discard any
    # packets whose logic has not been defined.
    packets_read = []
    for packet in BanchoPacketReader(conn.body, packet_map):
        await packet.handle(player)
        packets_read.append(packet.type)

    if glob.config.debug:
        packets_str = ', '.join([p.name for p in packets_read]) or 'None'
        log(f'[BANCHO] {player} | {packets_str}.', AnsiRGB(0xff68ab))

    player.last_recv_time = time.time()
    conn.add_resp_header('Content-Type: text/html; charset=UTF-8')
    return player.dequeue() or b''
Esempio n. 4
0
async def bancho_handler(conn: Connection) -> bytes:
    if 'User-Agent' not in conn.headers:
        return

    if conn.headers['User-Agent'] != 'osu!':
        # most likely a request from a browser.
        return b'<!DOCTYPE html>' + '<br>'.join((
            f'Running gulag v{glob.version}',
            f'Players online: {len(glob.players) - 1}',
            '<a href="https://github.com/cmyui/gulag">Source code</a>',
            '',
            '<b>Packets handled</b>',
            '<br>'.join(f'{p.name} ({p.value})' for p in glob.bancho_packets)
        )).encode()

    # check for 'osu-token' in the headers.
    # if it's not there, this is a login request.

    if 'osu-token' not in conn.headers:
        # login is a bit of a special case,
        # so we'll handle it separately.
        resp, token = await login(
            conn.body, conn.headers['X-Real-IP']
        )

        conn.add_resp_header(f'cho-token: {token}')
        return resp

    # get the player from the specified osu token.
    player = await glob.players.get(token=conn.headers['osu-token'])

    if not player:
        # token was not found; changes are, we just restarted
        # the server. just tell their client to re-connect.
        return packets.notification('Server is restarting') + \
               packets.restartServer(0) # send 0ms since server is up

    # bancho connections can be comprised of multiple packets;
    # our reader is designed to iterate through them individually,
    # allowing logic to be implemented around the actual handler.

    # NOTE: the reader will internally discard any
    # packets whose logic has not been defined.
    async for packet in BanchoPacketReader(conn.body):
        await packet.handle(player)

        if glob.config.debug:
            log(f'{packet.type!r}', Ansi.LMAGENTA)

    player.last_recv_time = time.time()

    # TODO: this could probably be done better?
    resp = bytearray()

    while not player.queue_empty():
        # read all queued packets into stream
        resp += player.dequeue()

    conn.add_resp_header('Content-Type: text/html; charset=UTF-8')
    resp = bytes(resp)

    # even if the packet is empty, we have to
    # send back an empty response so the client
    # knows it was successfully delivered.
    return resp
Esempio n. 5
0
        pcache.append(user)
        for p in pcache: # enqueue other users to client
            data += (packets.userPresence(p) + packets.userStats(p))

        resp = await make_response(bytes(data))
        resp.headers['cho-token'] = token
        if glob.config.debug:
            log(f'{username} successfully logged in. | Time Elapsed (using bcrypt cache: {ub}): {(time.time() - start) * 1000:.2f}ms', Ansi.LBLUE)
        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

    # ew
    ucache = glob.players
    for p in ucache:
        if p.get('id') == user['id']:
            user = p

    # handle any packets the client has sent
    for packet in BanchoPacketReader(body, glob.packets):
        await packet.handle(user)
        if glob.config.debug:
            log(f'Handled packet {packet.type!r}', Ansi.LBLUE)
Esempio n. 6
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
Esempio n. 7
0
async def handle_bancho(conn: AsyncConnection) -> None:
    """Handle a bancho request (POST c.ppy.sh/)."""
    if 'User-Agent' not in conn.headers:
        return

    if conn.headers['User-Agent'] != 'osu!':
        # most likely a request from a browser.
        resp = '<br>'.join(
            (f'Running gulag v{glob.version}',
             f'Players online: {len(glob.players) - 1}',
             '<a href="https://github.com/cmyui/gulag">Source code</a>', '',
             '<b>Bancho Handlers</b>', '<br>'.join(f'{h.name} ({h.value})'
                                                   for h in glob.bancho_map),
             '', '<b>/web/ Handlers</b>', '<br>'.join(glob.web_map), '',
             '<b>/api/ Handlers</b>', '<br>'.join(glob.api_map)))

        await conn.send(200, f'<!DOCTYPE html>{resp}'.encode())
        return

    # check for 'osu-token' in the headers.
    # if it's not there, this is a login request.

    if 'osu-token' not in conn.headers:
        # login is a bit of a special case,
        # so we'll handle it separately.
        resp, token = await bancho.login(conn.body, conn.headers['X-Real-IP'])

        await conn.add_resp_header(f'cho-token: {token}')
        await conn.send(200, resp)
        return

    # get the player from the specified osu token.
    p = glob.players.get(conn.headers['osu-token'])

    if not p:
        # token was not found; changes are, we just restarted
        # the server. just tell their client to re-connect.
        resp = packets.notification('Server is restarting') + \
               packets.restartServer(0) # send 0ms since server is up

        await conn.send(200, resp)
        return

    # bancho connections can be comprised of multiple packets;
    # our reader is designed to iterate through them individually,
    # allowing logic to be implemented around the actual handler.

    # NOTE: this will internally discard any
    # packets whose logic has not been defined.
    async for packet in BanchoPacketReader(conn.body):
        # TODO: wait_for system here with
        # a packet and a callable check.

        await packet.handle(p)

        if glob.config.debug:
            log(repr(packet.type), Ansi.LMAGENTA)

    p.last_recv_time = int(time.time())

    # TODO: this could probably be done better?
    resp = bytearray()

    while not p.queue_empty():
        # read all queued packets into stream
        resp.extend(p.dequeue())

    resp = bytes(resp)

    # compress with gzip if enabled.
    if glob.config.gzip['web'] > 0:
        resp = gzip.compress(resp, glob.config.gzip['web'])
        await conn.add_resp_header('Content-Encoding: gzip')

    # add headers and such
    await conn.add_resp_header('Content-Type: text/html; charset=UTF-8')

    # even if the packet is empty, we have to
    # send back an empty response so the client
    # knows it was successfully delivered.
    await conn.send(200, resp)