コード例 #1
0
    def do_GET(self, conn, key, append_hash, append_text=""):
        try:
            conn.send_response(200)
            conn.send_header("Content-type", "text/html")
            conn.send_header("Server", "Microsoft-IIS/6.0")
            conn.send_header("Server", "GSTPRDSTATSWEB2")
            conn.send_header("X-Powered-By", "ASP.NET")
            conn.end_headers()

            params = conn.str_to_dict(conn.path)
            ret = ""
            if "hash" not in params:
                # The token is used in combination with the game's secret key.
                # The format of the hash parameter sent from the client is
                # sha1(secret_key + token).
                token = utils.generate_random_str(32)
                ret = token
            else:
                # Handle data (generic response for now)
                ret += append_text

                if append_hash:
                    h = hashlib.sha1()
                    h.update(key + base64.urlsafe_b64encode(ret) + key)
                    ret += h.hexdigest()

            conn.wfile.write(ret)
        except:
            logger.log(logging.ERROR, "Unknown exception: %s",
                       traceback.format_exc())
    def do_GET(self, conn, key, append_hash, append_text = ""):
        try:
            conn.send_response(200)
            conn.send_header("Content-type", "text/html")
            conn.send_header("Server", "Microsoft-IIS/6.0")
            conn.send_header("Server", "GSTPRDSTATSWEB2")
            conn.send_header("X-Powered-By", "ASP.NET")
            conn.end_headers()

            params = conn.str_to_dict(conn.path)
            ret = ""
            if "hash" not in params:
                # The token is used in combination with the game's secret key.
                # The format of the hash parameter sent from the client is sha1(secret_key + token).
                token = utils.generate_random_str(32)
                ret = token
            else:
                # Handle data (generic response for now)
                ret += append_text

                if append_hash == True:
                    h = hashlib.sha1()
                    h.update(key + base64.urlsafe_b64encode(ret) + key)
                    ret += h.hexdigest()

            conn.wfile.write(ret)
        except:
            logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc())
コード例 #3
0
    def _executeAndMeasure(self, cursor, statement, parameters):
        logTransactionId = utils.generate_random_str(8)

        logger.log(SQL_LOGLEVEL, "[%s] STARTING: " % logTransactionId +
                                 statement.replace('?', '%s') % parameters)

        timeStart = time.time()
        clockStart = time.clock()

        cursor.execute(statement, parameters)

        clockEnd = time.clock()
        timeEnd = time.time()
        timeDiff = timeEnd - timeStart

        logger.log(SQL_LOGLEVEL,
                   "[%s] DONE: Took %s real time / %s processor time",
                   logTransactionId, timeDiff, clockEnd - clockStart)
        if timeDiff > 1.0:
            logger.log(logging.WARNING,
                       "[%s] WARNING: SQL Statement took %s seconds!",
                       logTransactionId, timeDiff)
            logger.log(logging.WARNING,
                       "[%s] " % logTransactionId +
                       statement.replace('?', '%s') % parameters)
        return
コード例 #4
0
    def connectionMade(self):
        try:
            self.transport.setTcpKeepAlive(1)

            self.log(logging.INFO, "Received connection from %s:%d",
                     self.address.host, self.address.port)

            # Create new session id
            self.session = ""

            # Generate a random challenge string
            self.challenge = utils.generate_random_str(
                10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")

            # The first command sent to the client is always a login challenge
            # containing the server challenge key.
            msg = gs_query.create_gamespy_message([
                ('__cmd__', "lc"),
                ('__cmd_val__', "1"),
                ('challenge', self.challenge),
                ('id', "1"),
            ])

            self.log(logging.DEBUG, "SENDING: '%s'...", msg)
            self.transport.write(bytes(msg))
        except:
            self.log(logging.ERROR, "Unknown exception: %s",
                     traceback.format_exc())
コード例 #5
0
    def connectionMade(self):
        try:
            self.log(logging.INFO,
                     "Received connection from %s:%d",
                     self.address.host, self.address.port)

            # Generate a random challenge string
            self.challenge = utils.generate_random_str(
                10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
            )

            # The first command sent to the client is always a login challenge
            # containing the server challenge key.
            msg = gs_query.create_gamespy_message([
                ('__cmd__', "lc"),
                ('__cmd_val__', "1"),
                ('challenge', self.challenge),
                ('id', "1"),
            ])

            self.log(logging.DEBUG, "SENDING: '%s'...", msg)

            msg = self.crypt(msg)
            self.transport.write(bytes(msg))
        except:
            self.log(logging.ERROR,
                     "Unknown exception: %s",
                     traceback.format_exc())
コード例 #6
0
    def generate_authtoken(self, userid, data):
        # Since the auth token passed back to the game will be random, we can make it small enough that there
        # should never be a crash due to the size of the token.
        # ^ real authtoken is 80 + 3 bytes though and I want to figure out what's causing the 52200
        # so I'm matching everything as closely as possible to the real thing
        size = 80

        # TODO: Another one of those questionable dupe-preventations
        while True:
            with Transaction(self.conn) as tx:
                authtoken = "NDS" + utils.generate_random_str(size)
                row = tx.queryone("SELECT COUNT(*) FROM nas_logins WHERE authtoken = ?", (authtoken,))
                count = int(row[0])
                if count == 0:
                    break

        with Transaction(self.conn) as tx:
            row = tx.queryone("SELECT * FROM nas_logins WHERE userid = ?", (userid,))
            r = self.get_dict(row)

        if "devname" in data:
            data["devname"] = gs_utils.base64_encode(data["devname"])
        if "ingamesn" in data:
            data["ingamesn"] = gs_utils.base64_encode(data["ingamesn"])

        data = json.dumps(data)

        with Transaction(self.conn) as tx:
            if r == None: # no row, add it
                tx.nonquery("INSERT INTO nas_logins VALUES (?, ?, ?)", (userid, authtoken, data))
            else:
                tx.nonquery("UPDATE nas_logins SET authtoken = ?, data = ? WHERE userid = ?", (authtoken, data, userid))

        return authtoken
コード例 #7
0
    def _executeAndMeasure(self, cursor, statement, parameters):
        logTransactionId = utils.generate_random_str(8)

        logger.log(
            SQL_LOGLEVEL, "[%s] STARTING: " % logTransactionId +
            statement.replace('?', '%s') % parameters)

        timeStart = time.time()
        clockStart = time.clock()

        cursor.execute(statement, parameters)

        clockEnd = time.clock()
        timeEnd = time.time()
        timeDiff = timeEnd - timeStart

        logger.log(SQL_LOGLEVEL,
                   "[%s] DONE: Took %s real time / %s processor time",
                   logTransactionId, timeDiff, clockEnd - clockStart)
        if timeDiff > 1.0:
            logger.log(logging.WARNING,
                       "[%s] WARNING: SQL Statement took %s seconds!",
                       logTransactionId, timeDiff)
            logger.log(
                logging.WARNING, "[%s] " % logTransactionId +
                statement.replace('?', '%s') % parameters)
        return
コード例 #8
0
    def generate_authtoken(self, userid, data):
        # Since the auth token passed back to the game will be random, we can make it small enough that there
        # should never be a crash due to the size of the token.
        # ^ real authtoken is 80 + 3 bytes though and I want to figure out what's causing the 52200
        # so I'm matching everything as closely as possible to the real thing
        size = 80
        authtoken = "NDS" + utils.generate_random_str(size)

        q = "SELECT authtoken FROM nas_logins WHERE authtoken = ?"
        q2 = q.replace("?", "%s") % (authtoken)
        logger.log(SQL_LOGLEVEL, q2)

        c = self.conn.cursor()
        for r in c.execute(q, [authtoken]):
            authtoken = "NDS" + utils.generate_random_str(size)

        q = "SELECT * FROM nas_logins WHERE userid = ?"
        q2 = q.replace("?", "%s") % (userid)
        logger.log(SQL_LOGLEVEL, q2)

        c.execute(q, [userid])
        r = self.get_dict(c.fetchone())

        if "devname" in data:
            data["devname"] = gs_utils.base64_encode(data["devname"])
        if "ingamesn" in data:
            data["ingamesn"] = gs_utils.base64_encode(data["ingamesn"])

        data = json.dumps(data)

        if r == None: # no row, add it
            q = "INSERT INTO nas_logins VALUES (?, ?, ?)"
            q2 = q.replace("?", "%s") % (userid, authtoken, data)
            logger.log(SQL_LOGLEVEL, q2)
            c.execute(q, [userid, authtoken, data])
        else:
            q = "UPDATE nas_logins SET authtoken = ?, data = ? WHERE userid = ?"
            q2 = q.replace("?", "%s") % (authtoken, data, userid)
            logger.log(SQL_LOGLEVEL, q2)
            c.execute(q, [authtoken, data, userid])

        c.close()
        self.conn.commit()

        return authtoken
コード例 #9
0
    def generate_authtoken(self, userid, data):
        # Since the auth token passed back to the game will be random, we can make it small enough that there
        # should never be a crash due to the size of the token.
        size = 16
        authtoken = "NDS" + utils.generate_random_str(size)

        q = "SELECT authtoken FROM nas_logins WHERE authtoken = ?"
        q2 = q.replace("?", "%s") % (authtoken)
        logger.log(-1, q2)

        c = self.conn.cursor()
        for r in c.execute(q, [authtoken]):
            authtoken = "NDS" + utils.generate_random_str(size)

        q = "SELECT * FROM nas_logins WHERE userid = ?"
        q2 = q.replace("?", "%s") % (userid)
        logger.log(-1, q2)

        c.execute(q, [userid])
        r = self.get_dict(c.fetchone())

        if "devname" in data:
            data["devname"] = gs_utils.base64_encode(data["devname"])
        if "ingamesn" in data:
            data["ingamesn"] = gs_utils.base64_encode(data["ingamesn"])

        data = json.dumps(data)

        if r == None:  # no row, add it
            q = "INSERT INTO nas_logins VALUES (?, ?, ?)"
            q2 = q.replace("?", "%s") % (userid, authtoken, data)
            logger.log(-1, q2)
            c.execute(q, [userid, authtoken, data])
        else:
            q = "UPDATE nas_logins SET authtoken = ?, data = ? WHERE userid = ?"
            q2 = q.replace("?", "%s") % (authtoken, data, userid)
            logger.log(-1, q2)
            c.execute(q, [authtoken, data, userid])

        c.close()
        self.conn.commit()

        return authtoken
コード例 #10
0
    def generate_authtoken(self, userid, data):
        # Since the auth token passed back to the game will be random, we can make it small enough that there
        # should never be a crash due to the size of the token.
        size = 16
        authtoken = "NDS" + utils.generate_random_str(size)

        q = "SELECT authtoken FROM nas_logins WHERE authtoken = ?"
        q2 = q.replace("?", "%s") % (authtoken)
        logger.log(-1, q2)

        c = self.conn.cursor()
        for r in c.execute(q, [authtoken]):
            authtoken = "NDS" + utils.generate_random_str(size)

        q = "SELECT * FROM nas_logins WHERE userid = ?"
        q2 = q.replace("?", "%s") % (userid)
        logger.log(-1, q2)

        c.execute(q, [userid])
        r = self.get_dict(c.fetchone())

        if "devname" in data:
            data["devname"] = gs_utils.base64_encode(data["devname"])

        data = json.dumps(data)

        if r == None:  # no row, add it
            q = "INSERT INTO nas_logins VALUES (?, ?, ?)"
            q2 = q.replace("?", "%s") % (userid, authtoken, data)
            logger.log(-1, q2)
            c.execute(q, [userid, authtoken, data])
        else:
            q = "UPDATE nas_logins SET authtoken = ?, data = ? WHERE userid = ?"
            q2 = q.replace("?", "%s") % (authtoken, data, userid)
            logger.log(-1, q2)
            c.execute(q, [authtoken, data, userid])

        c.close()
        self.conn.commit()

        return authtoken
コード例 #11
0
    def generate_authtoken(self, userid, data):
        """Generate authentication token.

        Since the auth token passed back to the game will be random, we can
        make it small enough that there should never be a crash due to the
        size of the token.
        ^ real authtoken is 80 + 3 bytes though and I want to figure out
        what's causing the 52200 so I'm matching everything as closely as
        possible to the real thing.
        """
        size = 80

        # TODO: Another one of those questionable dupe-preventations
        while True:
            with Transaction(self.conn) as tx:
                authtoken = "NDS" + utils.generate_random_str(size)
                row = tx.queryone(
                    "SELECT COUNT(*) FROM nas_logins WHERE authtoken = ?",
                    (authtoken,)
                )
                count = int(row[0])
                if not count:
                    break

        with Transaction(self.conn) as tx:
            row = tx.queryone(
                "SELECT * FROM nas_logins WHERE userid = ?",
                (userid,)
            )
            r = self.get_dict(row)

        if "devname" in data:
            data["devname"] = gs_utils.base64_encode(data["devname"])
        if "ingamesn" in data:
            data["ingamesn"] = gs_utils.base64_encode(data["ingamesn"])

        data = json.dumps(data)

        with Transaction(self.conn) as tx:
            if r is None:  # no row, add it
                tx.nonquery(
                    "INSERT INTO nas_logins VALUES (?, ?, ?)",
                    (userid, authtoken, data)
                )
            else:
                tx.nonquery(
                    "UPDATE nas_logins SET authtoken = ?, data = ?"
                    " WHERE userid = ?",
                    (authtoken, data, userid)
                )

        return authtoken
コード例 #12
0
    def do_POST(self):
        if self.path == "/ac":
            length = int(self.headers['content-length'])
            post = urlparse.parse_qs(self.rfile.read(length))

            for k, v in post.iteritems():
                post[k] = self.base64_dec(v[0])

            logger.log(logging.DEBUG, "Request to %s from %s", self.path, self.client_address)
            logger.log(logging.DEBUG, post)
            ret = {}
            ret["datetime"] = time.strftime("%Y%m%d%H%M%S")
            ret["retry"] = "0"
            action = post["action"]
            self.send_response(200)
            self.send_header("Content-type", "text/plain")
            self.send_header("Server", "Nintendo Wii (http)")
            self.send_header("NODE", "wifiappe3")
            self.end_headers()

            if action == "acctcreate":
                # TODO: test for duplicate accounts
                ret["returncd"] = "002"

                logger.log(logging.DEBUG, "acctcreate response to %s", self.client_address)
                logger.log(logging.DEBUG, ret)

                for k, v in ret.iteritems():
                    ret[k] = self.base64_enc(v)

                self.wfile.write(urllib.urlencode(ret).replace("%2A", "*"))
            elif action == "login":
                ret["returncd"] = "001"
                ret["locator"] = "gamespy.com"
                challenge = utils.generate_random_str(8)
                ret["challenge"] = challenge
                post["challenge"] = challenge
                authtoken = self.server.db.generate_authtoken(post["userid"], json.dumps(post))
                ret["token"] = authtoken

                logger.log(logging.DEBUG, "login response to %s", self.client_address)
                logger.log(logging.DEBUG, ret)

                for k, v in ret.iteritems():
                    ret[k] = self.base64_enc(v)

                self.wfile.write(urllib.urlencode(ret).replace("%2A", "*"))
コード例 #13
0
    def connectionMade(self):
        self.log(logging.INFO, "Received connection from %s:%d" % (self.address.host, self.address.port))

        # Generate a random challenge string
        self.challenge = utils.generate_random_str(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")

        # The first command sent to the client is always a login challenge containing the server challenge key.
        msg_d = []
        msg_d.append(('__cmd__', "lc"))
        msg_d.append(('__cmd_val__', "1"))
        msg_d.append(('challenge', self.challenge))
        msg_d.append(('id', "1"))
        msg = gs_query.create_gamespy_message(msg_d)

        self.log(logging.DEBUG, "SENDING: '%s'..." % msg)

        msg = self.crypt(msg)
        self.transport.write(bytes(msg))
コード例 #14
0
    def connectionMade(self):
        self.log(logging.INFO, "Received connection from %s:%d" % (self.address.host, self.address.port))

        # Generate a random challenge string
        self.challenge = utils.generate_random_str(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")

        # The first command sent to the client is always a login challenge containing the server challenge key.
        msg_d = []
        msg_d.append(('__cmd__', "lc"))
        msg_d.append(('__cmd_val__', "1"))
        msg_d.append(('challenge', self.challenge))
        msg_d.append(('id', "1"))
        msg = gs_query.create_gamespy_message(msg_d)

        self.log(logging.DEBUG, "SENDING: '%s'..." % msg)

        msg = self.crypt(msg)
        self.transport.write(bytes(msg))
コード例 #15
0
def handle_ac_login(handler, db, addr, post):
    """Handle ac login request."""
    if db.is_banned(post):
        ret = {
            "retry": "1",
            "returncd": "3914",
            "locator": "gamespy.com",
            "reason": "User banned."
        }
        logger.log(logging.DEBUG, "Login denied for banned user %s", str(post))
    # Un-comment these lines to enable console registration feature
    # elif not db.pending(post):
    #     logger.log(logging.DEBUG, "Login denied - Unknown console %s", post)
    #     ret = {
    #         "retry": "1",
    #         "returncd": "3921",
    #         "locator": "gamespy.com",
    #     }
    # elif not db.registered(post):
    #     logger.log(logging.DEBUG, "Login denied - console pending %s", post)
    #     ret = {
    #         "retry": "1",
    #         "returncd": "3888",
    #         "locator": "gamespy.com",
    #     }
    else:
        challenge = utils.generate_random_str(8)
        post["challenge"] = challenge

        authtoken = db.generate_authtoken(post["userid"], post)
        ret = {
            "retry": "0",
            "returncd": "001",
            "locator": "gamespy.com",
            "challenge": challenge,
            "token": authtoken,
        }

        logger.log(logging.DEBUG, "Login response to %s:%d", *addr)
        logger.log(logging.DEBUG, "%s", ret)

    return ret
コード例 #16
0
    def wait_loop(self):
        while 1:
            recv_data, address = self.socket.recvfrom(2048)

            # Tetris DS overlay 10 @ 02144184 - Handle responses back to server
            # Tetris DS overlay 10 @ 02144184 - Handle responses back to server
            #
            # After some more packet inspection, it seems the format goes something like this:
            # - All server messages seem to always start with \xfe\xfd.
            # - The first byte from the client (or third byte from the server) is a command.
            # - Bytes 2 - 5 from the client is some kind of ID. This will have to be inspected later. I believe it's a
            # session-like ID because the number changes between connections. Copying the client's ID might be enough.
            #
            # The above was as guessed.
            # The code in Tetris DS (overlay 10) @ 0216E974 handles the network command creation.
            # R1 contains the command to be sent to the server.
            # R2 contains a pointer to some unknown integer that gets written after the command.
            #
            # - Commands
            #   Commands range from 0x00 to 0x09 (for client only at least?) (Tetris DS overlay 10 @ 0216DDCC)
            #
            #   CLIENT:
            #       0x01 - Response (Tetris DS overlay 10 @ 216DCA4)
            #           Sends back base64 of RC4 encrypted string that was gotten from the server's 0x01.
            #
            #       0x03 - Send client state? (Tetris DS overlay 10 @ 216DA30)
            #           Data sent:
            #           1) Loop for each localip available on the system, write as localip%d\x00(local ip)
            #           2) localport\x00(local port)
            #           3) natneg (either 0 or 1)
            #           4) ONLY IF STATE CHANGED: statechanged\x00(state) (Possible values: 0, 1, 2, 3)
            #           5) gamename\x00(game name)
            #           6) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicip\x00(public ip)
            #           7) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicport\x00(public port)
            #
            #           if statechanged != 2:
            #               Write various other data described here: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Implementation
            #
            #       0x07 - Unknown, related to server's 0x06 (returns value sent from server)
            #
            #       0x08 - Keep alive? Sent after 0x03
            #
            #       0x09 - Availability check
            #
            #   SERVER:
            #       0x01 - Unknown
            #           Data sent:
            #           8 random ASCII characters (?) followed by the public IP and port of the client as a hex string
            #
            #       0x06 - Unknown
            #           First 4 bytes is some kind of id? I believe it's a unique identifier for the data being sent,
            #           seeing how the server can send the same IP information many times in a row. If the IP information has
            #           already been parsed then it doesn't waste time handling it.
            #
            #           After that is a "SBCM" section which is 0x14 bytes in total.
            #           SBCM information gets parsed at 2141A0C in Tetris DS overlay 10.
            #           Seems to contain IP address information.
            #
            #           The SBCM seems to contain a little information that must be parsed before.
            #           After the SBCM:
            #               \x03\x00\x00\x00 - Always the same?
            #               \x01 - Found player?
            #               \x04 - Unknown
            #               (2 bytes) - Unknown. Port?
            #               (4 bytes) - Player's IP
            #               (4 bytes) - Unknown. Some other IP? Remote server IP?
            #               \x00\x00\x00\x00 - Unknown but seems to get checked
            #
            #           Another SBCM, after a player has been found and attempting to start a game:
            #               \x03\x00\x00\x00 - Always the same?
            #               \x05 - Connecting to player?
            #               \x00 - Unknown
            #               (2 bytes) - Unknown. Port? Same as before.
            #               (4 bytes) - Player's IP
            #               (4 bytes) - Unknown. Some other IP? Remote server IP?
            #
            #       0x0a - Response to 0x01
            #           Gets sent after receiving 0x01 from the client. So, server 0x01 -> client 0x01 -> server 0x0a.
            #           Has no other data besides the client ID.
            #
            #  - \xfd\xfc commands get passed directly between the other player(s)?
            #
            #
            # Open source version of GameSpy found here: https://github.com/sfcspanky/Openspy-Core/tree/master/qr
            # Use as reference.

            session_id = struct.unpack("<I", recv_data[1:5])[0]
            session_id_raw = recv_data[1:5]
            if session_id not in self.sessions:
                # Found a new session, add to session list
                self.sessions[session_id] = self.Session(address)

            # Handle commands
            if recv_data[0] == '\x00': # Query
                self.log(logging.DEBUG, address, "NOT IMPLEMENTED! Received query from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x01': # Challenge
                self.log(logging.DEBUG, address, "Received challenge from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

                # Prepare the challenge sent from the server to be compared
                challenge = gs_utils.prepare_rc4_base64(self.sessions[session_id].secretkey, self.sessions[session_id].challenge)

                # Compare challenge
                client_challenge = recv_data[5:-1]
                if client_challenge == challenge:
                    # Challenge succeeded
                    self.sessions[session_id].sent_challenge = True

                    # Handle successful challenge stuff here
                    packet = bytearray([0xfe, 0xfd, 0x0a]) # Send client registered command
                    packet.extend(session_id_raw) # Get the session ID
                    self.socket.sendto(packet, address)
                    self.log(logging.DEBUG, address, "Sent client registered to %s:%s..." % (address[0], address[1]))

            elif recv_data[0] == '\x02': # Echo
                self.log(logging.DEBUG, address, "NOT IMPLEMENTED! Received echo from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x03': # Heartbeat
                data = recv_data[5:]
                self.log(logging.DEBUG, address, "Received heartbeat from %s:%s... %s" % (address[0], address[1], data))

                # Parse information from heartbeat here
                d = data.rstrip('\0').split('\0')

                # It may be safe to ignore "unknown" keys because the proper key names get filled in later...
                k = {}
                for i in range(0, len(d), 2):
                    self.log(logging.DEBUG, address, "%s = %s" % (d[i], d[i+1]))
                    k[d[i]] = d[i+1]

                if "gamename" in k:
                    self.sessions[session_id].secretkey = self.secret_key_list[k['gamename']]
                    #print "Got secret key %s for %s" % (self.sessions[session_id].secretkey, k['gamename'])

                if self.sessions[session_id].playerid == 0 and "dwc_pid" in k:
                    # Get the player's id and then query the profile to figure out what console they are on.
                    # The endianness of some server data depends on the endianness of the console, so we must be able
                    # to account for that.
                    self.sessions[session_id].playerid = int(k['dwc_pid'])
                    profile = self.db.get_profile_from_profileid(self.sessions[session_id].playerid)

                    if "console" in profile:
                        self.sessions[session_id].console = profile['console']


                if self.sessions[session_id].sent_challenge == False:
                    addr_hex =  ''.join(["%02X" % int(x) for x in address[0].split('.')])
                    port_hex = "%04X" % int(address[1])
                    server_challenge = utils.generate_random_str(8) + addr_hex + port_hex

                    self.sessions[session_id].challenge = server_challenge

                    packet = bytearray([0xfe, 0xfd, 0x01]) # Send challenge command
                    packet.extend(session_id_raw) # Get the session ID
                    packet.extend(server_challenge)
                    packet.extend('\x00')

                    self.socket.sendto(packet, address)
                    self.log(logging.DEBUG, address, "Sent challenge to %s:%s..." % (address[0], address[1]))

                if "statechanged" in k:
                    if k['statechanged'] == "1": # Create server
                        if k['publicport'] != "0" and k['publicip'] != "0" and k['maxplayers'] != "0":
                            # dwc_mtype controls what kind of server query we're looking for.
                            # dwc_mtype = 0 is used when looking for a matchmaking game.
                            # dwc_mtype = 1 is unknown.
                            # dwc_mtype = 2 is used when hosting a friends only game (possibly other uses too).
                            # dwc_mtype = 3 is used when looking for a friends only game (possibly other uses too).

                            # Some memory could be saved by clearing out any unwanted fields from k before sending.
                            self.server_manager.update_server_list(k['gamename'] , session_id, k, self.sessions[session_id].console)
                    elif k['statechanged'] == "2": # Close server
                        self.server_manager.delete_server(k['gamename'] , session_id)
                        #self.sessions.pop(session_id)


            elif recv_data[0] == '\x04': # Add Error
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received add error from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x05': # Echo Response
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received echo response from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x06': # Client Message
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received echo from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x07': # Client Message Ack
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received client message ack from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x08': # Keep Alive
                self.log(logging.DEBUG, address, "Received keep alive from %s:%s..." % (address[0], address[1]))

            elif recv_data[0] == '\x09': # Available
                # Availability check only sent to *.available.gs.nintendowifi.net
                self.log(logging.DEBUG, address, "Received availability request for '%s' from %s:%s..." % (recv_data[5: -1], address[0], address[1]))
                self.socket.sendto(bytearray([0xfe, 0xfd, 0x09, 0x00, 0x00, 0x00, 0x00]), address)

            elif recv_data[0] == '\x0a': # Client Registered
                # Only sent to client, never received?
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received client registered from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            else:
                self.log(logging.ERROR, address, "Unknown request from %s:%s:" % (address[0], address[1]))
                self.log(logging.DEBUG, address, utils.pretty_print_hex(recv_data))
コード例 #17
0
    def perform_login(self, data_parsed):
        authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken'],
                                                    self.db)
        #print authtoken_parsed

        # Track what console is connecting and save it in the database during user creation just in case we can use
        # the information in the future.
        console = 0  # 0 = NDS, 1 = Wii

        # get correct information
        userid = authtoken_parsed['userid']

        # The Wii does not use passwd, so take another uniquely generated string as the password.
        if "passwd" in authtoken_parsed:
            password = authtoken_parsed['passwd']
        else:
            password = authtoken_parsed['gsbrcd']
            console = 1

        gsbrcd = authtoken_parsed['gsbrcd']
        gameid = gsbrcd[:4]
        uniquenick = utils.base32_encode(int(userid)) + gsbrcd
        email = uniquenick + "@nds"  # The Wii also seems to use @nds.

        # Wii: Serial number
        if "csnum" in authtoken_parsed:
            csnum = authtoken_parsed['csnum']
            console = 1
        else:
            csnum = ""

        # Wii: Friend code
        if "cfc" in authtoken_parsed:
            cfc = authtoken_parsed['cfc']
            console = 1
        else:
            cfc = ""

        # NDS: Wifi network's BSSID
        if "bssid" in authtoken_parsed:
            bssid = authtoken_parsed['bssid']
        else:
            bssid = ""

        # NDS: Device name
        if "devname" in authtoken_parsed:
            devname = authtoken_parsed['devname']
        else:
            devname = ""

        # NDS: User's birthday
        if "birth" in authtoken_parsed:
            birth = authtoken_parsed['birth']
        else:
            birth = ""

        # Verify the client's response
        valid_response = gs_utils.generate_response(
            self.challenge, authtoken_parsed['challenge'],
            data_parsed['challenge'], data_parsed['authtoken'])
        if data_parsed['response'] != valid_response:
            self.log(
                logging.ERROR,
                "ERROR: Got invalid response. Got %s, expected %s" %
                (data_parsed['response'], valid_response))

        proof = gs_utils.generate_proof(self.challenge,
                                        authtoken_parsed['challenge'],
                                        data_parsed['challenge'],
                                        data_parsed['authtoken'])

        valid_user = self.db.check_user_exists(userid, gsbrcd)
        if valid_user == False:
            profileid = self.db.create_user(userid, password, email,
                                            uniquenick, gsbrcd, console, csnum,
                                            cfc, bssid, devname, birth, gameid)
        else:
            profileid = self.db.perform_login(userid, password, gsbrcd)

            if profileid == None:
                # Handle case where the user is invalid
                self.log(logging.ERROR, "Invalid password")

        if profileid != None:
            # Successfully logged in or created account, continue creating session.
            sesskey = self.db.create_session(profileid)

            self.sessions[profileid] = self

            msg_d = []
            msg_d.append(('__cmd__', "lc"))
            msg_d.append(('__cmd_val__', "2"))
            msg_d.append(('sesskey', sesskey))
            msg_d.append(('proof', proof))
            msg_d.append(('userid', userid))
            msg_d.append(('profileid', profileid))
            msg_d.append(('uniquenick', uniquenick))
            msg_d.append(
                ('lt', gs_utils.base64_encode(utils.generate_random_str(16)))
            )  # Some kind of token... don't know it gets used or generated, but it doesn't seem to have any negative effects if it's not properly generated.
            msg_d.append(('id', data_parsed['id']))
            msg = gs_query.create_gamespy_message(msg_d)

            # Take the first 4 letters of gsbrcd instead of gamecd because they should be consistent across game
            # regions. For example, the US version of Metroid Prime Hunters has the gamecd "AMHE" and the first 4 letters
            # of gsbrcd are "AMHE". However, the Japanese version of Metroid Prime Hunters has the gamecd "AMHJ" with
            # the first 4 letters of bsbrcd as "AMHE". Tetris DS is the other way, with the first 4 letters as the
            # Japanese version (ATRJ) while the gamecd is region specific (ATRE for US and ATRJ for JP).
            # gameid is used to send all people on the player's friends list a status updates, so don't make it region
            # specific.
            self.gameid = gsbrcd[0:4]
            self.profileid = int(profileid)

            self.log(logging.DEBUG, "SENDING: %s" % msg)
            self.transport.write(bytes(msg))

            self.buddies = self.db.get_buddy_list(self.profileid)
            self.blocked = self.db.get_blocked_list(self.profileid)

            # Get pending messages.
            self.get_pending_messages()

            # Send any friend statuses when the user logs in.
            # This will allow the user to see if their friends are hosting a game as soon as they log in.
            self.get_status_from_friends()
            self.send_status_to_friends()
コード例 #18
0
    def do_POST(self):
        try:
            length = int(self.headers['content-length'])
            post = self.str_to_dict(self.rfile.read(length))
            ret = ''

            if self.path == "/ac":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path, self.client_address)
                logger.log(logging.DEBUG, post)
                ret = {}
                ret["datetime"] = time.strftime("%Y%m%d%H%M%S")
                ret["retry"] = "0"
                action = post["action"]
                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.send_header("NODE", "wifiappe1")

                if action == "acctcreate":
                    # TODO: test for duplicate accounts
                    ret["returncd"] = "002"

                    logger.log(logging.DEBUG, "acctcreate response to %s", self.client_address)
                    logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)

                elif action == "login":
                    ret["returncd"] = "001"
                    ret["locator"] = "gamespy.com"
                    challenge = utils.generate_random_str(8)
                    ret["challenge"] = challenge
                    post["challenge"] = challenge
                    authtoken = self.server.db.generate_authtoken(post["userid"], post)
                    ret["token"] = authtoken

                    logger.log(logging.DEBUG, "login response to %s", self.client_address)
                    logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)

                elif action == "SVCLOC" or action == "svcloc": # Get service based on service id number
                    ret["returncd"] = "007"
                    ret["statusdata"] = "Y"
                    authtoken = self.server.db.generate_authtoken(post["userid"], post)

                    if 'svc' in post:
                        if post["svc"] in ("9000", "9001"): # DLC host = 9000
                            ret["svchost"] = self.headers['host'] # in case the client's DNS isn't redirecting dls1.nintendowifi.net

                            # Brawl has 2 host headers which Apache chokes on, so only return the first one or else it won't work
                            ret["svchost"] = ret["svchost"].split(',')[0]

                            if post["svc"] == 9000:
                                ret["token"] = authtoken
                            else:
                                ret["servicetoken"] = authtoken
                        elif post["svc"] == "0000": # Pokemon requests this for some things
                            ret["servicetoken"] = authtoken
                            ret["svchost"] = "n/a"

                    logger.log(logging.DEBUG, "svcloc response to %s", self.client_address)
                    logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)
                else:
                    logger.log(logging.WARNING, "Unknown action request %s from %s!", self.path, self.client_address)
                    ret = ''


            elif self.path == "/pr":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path, self.client_address)
                logger.log(logging.DEBUG, post)
                ret = {}

                words = len(post["words"].split('\t'))
                wordsret = "0" * words
                ret["prwords"] = wordsret
                ret["prwordsA"] = wordsret
                ret["prwordsC"] = wordsret
                ret["prwordsE"] = wordsret
                ret["prwordsJ"] = wordsret
                ret["prwordsK"] = wordsret
                ret["prwordsP"] = wordsret
                ret["returncd"] = "000"
                ret["datetime"] = time.strftime("%Y%m%d%H%M%S")

                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.send_header("NODE", "wifiappe1")

                logger.log(logging.DEBUG, "pr response to %s", self.client_address)
                logger.log(logging.DEBUG, ret)

                ret = self.dict_to_str(ret)

            elif self.path == "/download":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path, self.client_address)
                logger.log(logging.DEBUG, post)

                action = post["action"]

                ret = ""
                dlcdir = os.path.abspath('dlc')
                dlcpath = os.path.abspath("dlc/" + post["gamecd"])
                dlc_contenttype = False

                if os.path.commonprefix([dlcdir, dlcpath]) != dlcdir:
                    logging.log(logging.WARNING, 'Attempted directory traversal attack "%s", cancelling.', dlcpath)
                    self.send_response(403)
                    return

                def safeloadfi(fn, mode='rb'):
                    '''
                    safeloadfi : string -> string

                    Safely load contents of a file, given a filename, and closing the file afterward
                    '''
                    with open(os.path.join(dlcpath, fn), mode) as fi:
                        return fi.read()

                if action == "count":
                    if post["gamecd"] in gamecodes_return_random_file:
                        ret = "1"
                    else:
                        count = 0

                        if os.path.exists(dlcpath):
                            count = len(os.listdir(dlcpath))

                            if os.path.isfile(dlcpath + "/_list.txt"):
                                attr1 = None
                                if "attr1" in post:
                                    attr1 = post["attr1"]
                                attr2 = None
                                if "attr2" in post:
                                    attr2 = post["attr2"]
                                attr3 = None
                                if "attr3" in post:
                                    attr3 = post["attr3"]

                                dlcfi = safeloadfi("_list.txt")
                                lst = self.filter_list(dlcfi, attr1, attr2, attr3)
                                count = self.get_file_count(lst)

                        ret = "%d" % count

                if action == "list":
                    num = int(post["num"])
                    offset = int(post["offset"])

                    attr1 = None
                    if "attr1" in post:
                        attr1 = post["attr1"]
                    attr2 = None
                    if "attr2" in post:
                        attr2 = post["attr2"]
                    attr3 = None
                    if "attr3" in post:
                        attr3 = post["attr3"]

                    if os.path.exists(dlcpath):
                        # Look for a list file first.
                        # If the list file exists, send the entire thing back to the client.
                        if os.path.isfile(os.path.join(dlcpath, "_list.txt")):
                            ret = self.filter_list(safeloadfi("_list.txt"), attr1, attr2, attr3)

                            if post["gamecd"] in gamecodes_return_random_file:
                                # allow user to control which file to receive by setting the local date
                                # selected file will be the one at index (day of year) mod (file count)
                                try:
                                    userData = self.server.db.get_nas_login(post['token'])
                                    date = time.strptime(userData['devtime'], '%y%m%d%H%M%S')
                                    files = ret.splitlines()
                                    ret = files[int(date.tm_yday) % len(files)] + '\r\n'
                                except:
                                    ret = self.filter_list_random_files(ret, 1)

                if action == "contents":
                    # Get only the base filename just in case there is a path involved somewhere in the filename string.
                    dlc_contenttype = True
                    contents = os.path.basename(post["contents"])
                    ret = safeloadfi(contents)

                self.send_response(200)

                if dlc_contenttype == True:
                    self.send_header("Content-type", "application/x-dsdl")
                    self.send_header("Content-Disposition", "attachment; filename=\"" + post["contents"] + "\"")
                else:
                    self.send_header("Content-type", "text/plain")

                self.send_header("X-DLS-Host", "http://127.0.0.1/")

                logger.log(logging.DEBUG, "download response to %s", self.client_address)

                #if dlc_contenttype == False:
                #    logger.log(logging.DEBUG, ret)
            else:
                logger.log(logging.WARNING, "Unknown path request %s from %s!", self.path, self.client_address)

            self.send_header("Content-Length", str(len(ret)))
            self.end_headers()
            self.wfile.write(ret)
        except:
            logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc())
コード例 #19
0
    def perform_login(self, data_parsed):
        authtoken_parsed = gs_utils.parse_authtoken(data_parsed["authtoken"], self.db)

        if authtoken_parsed is None:
            self.log(logging.WARNING, "%s", "Invalid Authtoken.")
            msg = gs_query.create_gamespy_message(
                [
                    ("__cmd__", "error"),
                    ("__cmd_val__", ""),
                    ("err", "266"),
                    ("fatal", ""),
                    ("errmsg", "There was an error validating the" " pre-authentication."),
                    ("id", data_parsed["id"]),
                ]
            )
            self.transport.write(bytes(msg))
            return

        if "sdkrevision" in data_parsed:
            self.sdkrevision = data_parsed["sdkrevision"]

        # Verify the client's response
        valid_response = gs_utils.generate_response(
            self.challenge, authtoken_parsed["challenge"], data_parsed["challenge"], data_parsed["authtoken"]
        )
        if data_parsed["response"] != valid_response:
            self.log(
                logging.ERROR,
                "ERROR: Got invalid response." " Got %s, expected %s",
                data_parsed["response"],
                valid_response,
            )

        proof = gs_utils.generate_proof(
            self.challenge, authtoken_parsed["challenge"], data_parsed["challenge"], data_parsed["authtoken"]
        )

        userid, profileid, gsbrcd, uniquenick = gs_utils.login_profile_via_parsed_authtoken(authtoken_parsed, self.db)

        if profileid is not None:
            # Successfully logged in or created account, continue
            # creating session.
            loginticket = gs_utils.base64_encode(utils.generate_random_str(16))
            self.sesskey = self.db.create_session(profileid, loginticket)

            self.sessions[profileid] = self

            self.buddies = self.db.get_buddy_list(self.profileid)
            self.blocked = self.db.get_blocked_list(self.profileid)

            if self.sdkrevision == "11":  # Used in Tatsunoko vs Capcom

                def make_list(data):
                    return [str(d["buddyProfileId"]) for d in data if d["status"] == 1]

                block_list = make_list(self.blocked)
                msg = gs_query.create_gamespy_message(
                    [("__cmd__", "blk"), ("__cmd_val__", str(len(block_list))), ("list", ",".join(block_list))]
                )

                self.log(logging.DEBUG, "SENDING: %s", msg)
                self.transport.write(bytes(msg))

                buddy_list = make_list(self.buddies)
                msg = gs_query.create_gamespy_message(
                    [("__cmd__", "bdy"), ("__cmd_val__", str(len(buddy_list))), ("list", ",".join(buddy_list))]
                )

                self.log(logging.DEBUG, "SENDING: %s", msg)
                self.transport.write(bytes(msg))

            msg = gs_query.create_gamespy_message(
                [
                    ("__cmd__", "lc"),
                    ("__cmd_val__", "2"),
                    ("sesskey", self.sesskey),
                    ("proof", proof),
                    ("userid", userid),
                    ("profileid", profileid),
                    ("uniquenick", uniquenick),
                    # Some kind of token... don't know it gets used or generated,
                    # but it doesn't seem to have any negative effects if it's
                    # not properly generated.
                    ("lt", loginticket),
                    ("id", data_parsed["id"]),
                ]
            )

            # Take the first 4 letters of gsbrcd instead of gamecd because
            # they should be consistent across game regions. For example, the
            # US version of Metroid Prime Hunters has the gamecd "AMHE" and
            # the first 4 letters of gsbrcd are "AMHE". However, the Japanese
            # version of Metroid Prime Hunters has the gamecd "AMHJ" with the
            # first 4 letters of bsbrcd as "AMHE". Tetris DS is the other way,
            # with the first 4 letters as the Japanese version (ATRJ) while
            # the gamecd is region specific (ATRE for US and ATRJ for JP).
            # gameid is used to send all people on the player's friends list a
            # status updates, so don't make it region specific.
            self.gameid = gsbrcd[:4]
            self.profileid = int(profileid)

            self.log(logging.DEBUG, "SENDING: %s", msg)
            self.transport.write(bytes(msg))

            # Get pending messages.
            self.get_pending_messages()

            # Send any friend statuses when the user logs in.
            # This will allow the user to see if their friends are hosting a
            # game as soon as they log in.
            self.get_status_from_friends()
            self.send_status_to_friends()

            # profile = self.db.get_profile_from_profileid(profileid)
            # if profile is not None:
            #     self.statstring = profile['stat']
            #     self.locstring = profile['loc']
        else:
            self.log(logging.INFO, "%s", "Invalid password or banned user")
            msg = gs_query.create_gamespy_message(
                [
                    ("__cmd__", "error"),
                    ("__cmd_val__", ""),
                    ("err", "256"),
                    ("fatal", ""),
                    ("errmsg", "Login failed."),
                    ("id", data_parsed["id"]),
                ]
            )
            self.log(logging.DEBUG, "SENDING: %s", msg)
            self.transport.write(bytes(msg))
コード例 #20
0
    def perform_login(self, data_parsed):
        authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken'],
                                                    self.db)

        if authtoken_parsed is None:
            self.log(logging.WARNING, "%s", "Invalid Authtoken.")
            msg = gs_query.create_gamespy_message([
                ('__cmd__', "error"),
                ('__cmd_val__', ""),
                ('err', '266'),
                ('fatal', ''),
                ('errmsg', 'There was an error validating the'
                 ' pre-authentication.'),
                ('id', data_parsed['id']),
            ])
            self.transport.write(bytes(msg))
            return

        if 'sdkrevision' in data_parsed:
            self.sdkrevision = data_parsed['sdkrevision']

        # Verify the client's response
        valid_response = gs_utils.generate_response(
            self.challenge, authtoken_parsed['challenge'],
            data_parsed['challenge'], data_parsed['authtoken'])
        if data_parsed['response'] != valid_response:
            self.log(logging.ERROR, "ERROR: Got invalid response."
                     " Got %s, expected %s", data_parsed['response'],
                     valid_response)

        proof = gs_utils.generate_proof(self.challenge,
                                        authtoken_parsed['challenge'],
                                        data_parsed['challenge'],
                                        data_parsed['authtoken'])

        userid, profileid, gsbrcd, uniquenick = \
            gs_utils.login_profile_via_parsed_authtoken(authtoken_parsed,
                                                        self.db)

        if profileid is not None:
            # Successfully logged in or created account, continue
            # creating session.
            loginticket = gs_utils.base64_encode(utils.generate_random_str(16))
            self.sesskey = self.db.create_session(profileid, loginticket)

            self.sessions[profileid] = self

            self.buddies = self.db.get_buddy_list(self.profileid)
            self.blocked = self.db.get_blocked_list(self.profileid)

            if self.sdkrevision == "11":  # Used in Tatsunoko vs Capcom

                def make_list(data):
                    return [
                        str(d['buddyProfileId']) for d in data
                        if d['status'] == 1
                    ]

                block_list = make_list(self.blocked)
                msg = gs_query.create_gamespy_message([
                    ('__cmd__', "blk"),
                    ('__cmd_val__', str(len(block_list))),
                    ('list', ','.join(block_list)),
                ])

                self.log(logging.DEBUG, "SENDING: %s", msg)
                self.transport.write(bytes(msg))

                buddy_list = make_list(self.buddies)
                msg = gs_query.create_gamespy_message([
                    ('__cmd__', "bdy"),
                    ('__cmd_val__', str(len(buddy_list))),
                    ('list', ','.join(buddy_list)),
                ])

                self.log(logging.DEBUG, "SENDING: %s", msg)
                self.transport.write(bytes(msg))

            msg = gs_query.create_gamespy_message([
                ('__cmd__', "lc"),
                ('__cmd_val__', "2"),
                ('sesskey', self.sesskey),
                ('proof', proof),
                ('userid', userid),
                ('profileid', profileid),
                ('uniquenick', uniquenick),
                # Some kind of token... don't know it gets used or generated,
                # but it doesn't seem to have any negative effects if it's
                # not properly generated.
                ('lt', loginticket),
                ('id', data_parsed['id']),
            ])

            # Take the first 4 letters of gsbrcd instead of gamecd because
            # they should be consistent across game regions. For example, the
            # US version of Metroid Prime Hunters has the gamecd "AMHE" and
            # the first 4 letters of gsbrcd are "AMHE". However, the Japanese
            # version of Metroid Prime Hunters has the gamecd "AMHJ" with the
            # first 4 letters of bsbrcd as "AMHE". Tetris DS is the other way,
            # with the first 4 letters as the Japanese version (ATRJ) while
            # the gamecd is region specific (ATRE for US and ATRJ for JP).
            # gameid is used to send all people on the player's friends list a
            # status updates, so don't make it region specific.
            self.gameid = gsbrcd[:4]
            self.profileid = int(profileid)

            self.log(logging.DEBUG, "SENDING: %s", msg)
            self.transport.write(bytes(msg))

            # Get pending messages.
            self.get_pending_messages()

            # Send any friend statuses when the user logs in.
            # This will allow the user to see if their friends are hosting a
            # game as soon as they log in.
            self.get_status_from_friends()
            self.send_status_to_friends()

            # profile = self.db.get_profile_from_profileid(profileid)
            # if profile is not None:
            #     self.statstring = profile['stat']
            #     self.locstring = profile['loc']
        else:
            self.log(logging.INFO, "%s", "Invalid password or banned user")
            msg = gs_query.create_gamespy_message([
                ('__cmd__', "error"),
                ('__cmd_val__', ""),
                ('err', '256'),
                ('fatal', ''),
                ('errmsg', 'Login failed.'),
                ('id', data_parsed['id']),
            ])
            self.log(logging.DEBUG, "SENDING: %s", msg)
            self.transport.write(bytes(msg))
コード例 #21
0
    def handle_packet(self, socket, recv_data, address):
        # Tetris DS overlay 10 @ 02144184 - Handle responses back to server
        # Tetris DS overlay 10 @ 02144184 - Handle responses back to server
        #
        # After some more packet inspection, it seems the format goes something like this:
        # - All server messages seem to always start with \xfe\xfd.
        # - The first byte from the client (or third byte from the server) is a command.
        # - Bytes 2 - 5 from the client is some kind of ID. This will have to be inspected later. I believe it's a
        # session-like ID because the number changes between connections. Copying the client's ID might be enough.
        #
        # The above was as guessed.
        # The code in Tetris DS (overlay 10) @ 0216E974 handles the network command creation.
        # R1 contains the command to be sent to the server.
        # R2 contains a pointer to some unknown integer that gets written after the command.
        #
        # - Commands
        #   Commands range from 0x00 to 0x09 (for client only at least?) (Tetris DS overlay 10 @ 0216DDCC)
        #
        #   CLIENT:
        #       0x01 - Response (Tetris DS overlay 10 @ 216DCA4)
        #           Sends back base64 of RC4 encrypted string that was gotten from the server's 0x01.
        #
        #       0x03 - Send client state? (Tetris DS overlay 10 @ 216DA30)
        #           Data sent:
        #           1) Loop for each localip available on the system, write as localip%d\x00(local ip)
        #           2) localport\x00(local port)
        #           3) natneg (either 0 or 1)
        #           4) ONLY IF STATE CHANGED: statechanged\x00(state) (Possible values: 0, 1, 2, 3)
        #           5) gamename\x00(game name)
        #           6) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicip\x00(public ip)
        #           7) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicport\x00(public port)
        #
        #           if statechanged != 2:
        #               Write various other data described here: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Implementation
        #
        #       0x07 - Unknown, related to server's 0x06 (returns value sent from server)
        #
        #       0x08 - Keep alive? Sent after 0x03
        #
        #       0x09 - Availability check
        #
        #   SERVER:
        #       0x01 - Unknown
        #           Data sent:
        #           8 random ASCII characters (?) followed by the public IP and port of the client as a hex string
        #
        #       0x06 - Unknown
        #           First 4 bytes is some kind of id? I believe it's a unique identifier for the data being sent,
        #           seeing how the server can send the same IP information many times in a row. If the IP information has
        #           already been parsed then it doesn't waste time handling it.
        #
        #           After that is a "SBCM" section which is 0x14 bytes in total.
        #           SBCM information gets parsed at 2141A0C in Tetris DS overlay 10.
        #           Seems to contain IP address information.
        #
        #           The SBCM seems to contain a little information that must be parsed before.
        #           After the SBCM:
        #               \x03\x00\x00\x00 - Always the same?
        #               \x01 - Found player?
        #               \x04 - Unknown
        #               (2 bytes) - Unknown. Port?
        #               (4 bytes) - Player's IP
        #               (4 bytes) - Unknown. Some other IP? Remote server IP?
        #               \x00\x00\x00\x00 - Unknown but seems to get checked
        #
        #           Another SBCM, after a player has been found and attempting to start a game:
        #               \x03\x00\x00\x00 - Always the same?
        #               \x05 - Connecting to player?
        #               \x00 - Unknown
        #               (2 bytes) - Unknown. Port? Same as before.
        #               (4 bytes) - Player's IP
        #               (4 bytes) - Unknown. Some other IP? Remote server IP?
        #
        #       0x0a - Response to 0x01
        #           Gets sent after receiving 0x01 from the client. So, server 0x01 -> client 0x01 -> server 0x0a.
        #           Has no other data besides the client ID.
        #
        #  - \xfd\xfc commands get passed directly between the other player(s)?
        #
        #
        # Open source version of GameSpy found here: https://github.com/sfcspanky/Openspy-Core/tree/master/qr
        # Use as reference.

        session_id = None
        if recv_data[0] != '\x09':
            # Don't add a session if the client is trying to check if the game is available or not
            session_id = struct.unpack("<I", recv_data[1:5])[0]
            session_id_raw = recv_data[1:5]
            if session_id not in self.sessions:
                # Found a new session, add to session list
                self.sessions[session_id] = self.Session(address)
                self.sessions[session_id].session = session_id
                self.sessions[session_id].keepalive = int(time.time())
                self.sessions[session_id].disconnected = False

            if session_id in self.sessions and self.sessions[
                    session_id].disconnected == True:
                return

            if session_id in self.sessions:
                self.sessions[session_id].keepalive = int(
                    time.time())  # Make sure the server doesn't get removed

        # Handle commands
        if recv_data[0] == '\x00':  # Query
            self.log(
                logging.DEBUG, address, session_id,
                "NOT IMPLEMENTED! Received query from %s:%s... %s" %
                (address[0], address[1], recv_data[5:]))

        elif recv_data[0] == '\x01':  # Challenge
            self.log(
                logging.DEBUG, address, session_id,
                "Received challenge from %s:%s... %s" %
                (address[0], address[1], recv_data[5:]))

            # Prepare the challenge sent from the server to be compared
            challenge = gs_utils.prepare_rc4_base64(
                self.sessions[session_id].secretkey,
                self.sessions[session_id].challenge)

            # Compare challenge
            client_challenge = recv_data[5:-1]
            if client_challenge == challenge:
                # Challenge succeeded
                # Send message back to client saying it was accepted
                packet = bytearray([0xfe, 0xfd,
                                    0x0a])  # Send client registered command
                packet.extend(session_id_raw)  # Get the session ID
                self.write_queue.put((packet, address))
                self.log(
                    logging.DEBUG, address, session_id,
                    "Sent client registered to %s:%s..." %
                    (address[0], address[1]))

                if self.sessions[session_id].heartbeat_data != None:
                    self.update_server_list(
                        session_id, self.sessions[session_id].heartbeat_data)

            else:
                # Failed the challenge, request another during the next heartbeat
                self.sessions[session_id].sent_challenge = False
                self.server_manager.delete_server(
                    self.sessions[session_id].gamename, session_id)

        elif recv_data[0] == '\x02':  # Echo
            self.log(
                logging.DEBUG, address, session_id,
                "NOT IMPLEMENTED! Received echo from %s:%s... %s" %
                (address[0], address[1], recv_data[5:]))

        elif recv_data[0] == '\x03':  # Heartbeat
            data = recv_data[5:]
            self.log(
                logging.DEBUG, address, session_id,
                "Received heartbeat from %s:%s... %s" %
                (address[0], address[1], data))

            # Parse information from heartbeat here
            d = data.rstrip('\0').split('\0')

            # It may be safe to ignore "unknown" keys because the proper key names get filled in later...
            k = {}
            for i in range(0, len(d), 2):
                #self.log(logging.DEBUG, address, session_id, "%s = %s" % (d[i], d[i+1]))
                k[d[i]] = d[i + 1]

            if self.sessions[session_id].ingamesn != None:
                if "gamename" in k and "dwc_pid" in k:
                    try:
                        profile = self.db.get_profile_from_profileid(
                            k['dwc_pid'])
                        naslogin = self.db.get_nas_login_from_userid(
                            profile['userid'])
                        self.sessions[session_id].ingamesn = str(
                            naslogin['ingamesn']
                        )  # convert to string from unicode(which is just a base64 string anyway)
                    except Exception, e:
                        pass  # If the game doesn't have, don't worry about it.

                if self.sessions[
                        session_id].ingamesn != None and "ingamesn" not in k:
                    k['ingamesn'] = self.sessions[session_id].ingamesn

            if "gamename" in k:
                if k['gamename'] in self.secret_key_list:
                    self.sessions[session_id].secretkey = self.secret_key_list[
                        k['gamename']]
                else:
                    self.log(
                        logging.INFO, address, session_id,
                        "Connection from unknown game '%s'!" % k['gamename'])

            if self.sessions[session_id].playerid == 0 and "dwc_pid" in k:
                # Get the player's id and then query the profile to figure out what console they are on.
                # The endianness of some server data depends on the endianness of the console, so we must be able
                # to account for that.
                self.sessions[session_id].playerid = int(k['dwc_pid'])

                # Try to detect console without hitting the database first
                found_console = False
                if 'gamename' in k:
                    self.sessions[session_id].console = 0

                    if k['gamename'].endswith('ds') or k['gamename'].endswith(
                            'dsam') or k['gamename'].endswith(
                                'dsi') or k['gamename'].endswith('dsiam'):
                        self.sessions[session_id].console = 0
                        found_console = True
                    elif k['gamename'].endswith(
                            'wii') or k['gamename'].endswith(
                                'wiiam') or k['gamename'].endswith(
                                    'wiiware') or k['gamename'].endswith(
                                        'wiiwaream'):
                        self.sessions[session_id].console = 1
                        found_console = True

                if found_console == False:
                    # Couldn't detect game, try to get it from the database
                    # Try a 3 times before giving up
                    for i in range(0, 3):
                        try:
                            profile = self.db.get_profile_from_profileid(
                                self.sessions[session_id].playerid)

                            if "console" in profile:
                                self.sessions[session_id].console = profile[
                                    'console']

                            break
                        except:
                            time.sleep(0.5)

            if 'publicip' in k and k[
                    'publicip'] == "0":  #and k['dwc_hoststate'] == "2": # When dwc_hoststate == 2 then it doesn't send an IP, so calculate it ourselves
                be = self.sessions[session_id].console != 0
                k['publicip'] = str(
                    utils.get_ip(
                        bytearray([int(x) for x in address[0].split('.')]), 0,
                        be))

            if 'publicport' in k and 'localport' in k and k['publicport'] != k[
                    'localport']:
                self.log(logging.DEBUG, address, session_id, "publicport %s doesn't match localport %s, so changing publicport to %s..." \
                    % (k['publicport'], k['localport'], str(address[1])))
                k['publicport'] = str(address[1])

            if self.sessions[session_id].sent_challenge == True:
                self.update_server_list(session_id, k)
            else:
                addr_hex = ''.join(
                    ["%02X" % int(x) for x in address[0].split('.')])
                port_hex = "%04X" % int(address[1])
                server_challenge = utils.generate_random_str(
                    6) + '00' + addr_hex + port_hex

                self.sessions[session_id].challenge = server_challenge

                packet = bytearray([0xfe, 0xfd,
                                    0x01])  # Send challenge command
                packet.extend(session_id_raw)  # Get the session ID
                packet.extend(server_challenge)
                packet.extend('\x00')

                self.write_queue.put((packet, address))
                self.log(
                    logging.DEBUG, address, session_id,
                    "Sent challenge to %s:%s..." % (address[0], address[1]))

                self.sessions[session_id].sent_challenge = True
                self.sessions[session_id].heartbeat_data = k
コード例 #22
0
    def perform_login(self, data_parsed):
        authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken'], self.db)
        #print authtoken_parsed

        # Track what console is connecting and save it in the database during user creation just in case we can use
        # the information in the future.
        console = 0 # 0 = NDS, 1 = Wii

        # get correct information
        userid = authtoken_parsed['userid']

        # The Wii does not use passwd, so take another uniquely generated string as the password.
        if "passwd" in authtoken_parsed:
            password = authtoken_parsed['passwd']
        else:
            password = authtoken_parsed['gsbrcd']
            console = 1

        gsbrcd = authtoken_parsed['gsbrcd']
        gameid = gsbrcd[:4]
        uniquenick = utils.base32_encode(int(userid)) + gsbrcd
        email = uniquenick + "@nds" # The Wii also seems to use @nds.

        # Wii: Serial number
        if "csnum" in authtoken_parsed:
            csnum = authtoken_parsed['csnum']
            console = 1
        else:
            csnum = ""

        # Wii: Friend code
        if "cfc" in authtoken_parsed:
            cfc = authtoken_parsed['cfc']
            console = 1
        else:
            cfc = ""

        # NDS: Wifi network's BSSID
        if "bssid" in authtoken_parsed:
            bssid = authtoken_parsed['bssid']
        else:
            bssid = ""

        # NDS: Device name
        if "devname" in authtoken_parsed:
            devname = authtoken_parsed['devname']
        else:
            devname = ""

        # NDS: User's birthday
        if "birth" in authtoken_parsed:
            birth = authtoken_parsed['birth']
        else:
            birth = ""

        # Verify the client's response
        valid_response = gs_utils.generate_response(self.challenge, authtoken_parsed['challenge'], data_parsed['challenge'], data_parsed['authtoken'])
        if data_parsed['response'] != valid_response:
            self.log(logging.ERROR, "ERROR: Got invalid response. Got %s, expected %s" % (data_parsed['response'], valid_response))

        proof = gs_utils.generate_proof(self.challenge, authtoken_parsed['challenge'], data_parsed['challenge'], data_parsed['authtoken'])

        valid_user = self.db.check_user_exists(userid, gsbrcd)
        if valid_user == False:
            profileid = self.db.create_user(userid, password, email, uniquenick, gsbrcd, console, csnum, cfc, bssid, devname, birth, gameid)
        else:
            profileid = self.db.perform_login(userid, password, gsbrcd)

            if profileid == None:
                 # Handle case where the user is invalid
                self.log(logging.ERROR, "Invalid password")

        if profileid != None:
            # Successfully logged in or created account, continue creating session.
            loginticket = gs_utils.base64_encode(utils.generate_random_str(16))
            self.sesskey = self.db.create_session(profileid, loginticket)

            self.sessions[profileid] = self

            msg_d = []
            msg_d.append(('__cmd__', "lc"))
            msg_d.append(('__cmd_val__', "2"))
            msg_d.append(('sesskey', self.sesskey))
            msg_d.append(('proof', proof))
            msg_d.append(('userid', userid))
            msg_d.append(('profileid', profileid))
            msg_d.append(('uniquenick', uniquenick))
            msg_d.append(('lt', loginticket)) # Some kind of token... don't know it gets used or generated, but it doesn't seem to have any negative effects if it's not properly generated.
            msg_d.append(('id', data_parsed['id']))
            msg = gs_query.create_gamespy_message(msg_d)

            # Take the first 4 letters of gsbrcd instead of gamecd because they should be consistent across game
            # regions. For example, the US version of Metroid Prime Hunters has the gamecd "AMHE" and the first 4 letters
            # of gsbrcd are "AMHE". However, the Japanese version of Metroid Prime Hunters has the gamecd "AMHJ" with
            # the first 4 letters of bsbrcd as "AMHE". Tetris DS is the other way, with the first 4 letters as the
            # Japanese version (ATRJ) while the gamecd is region specific (ATRE for US and ATRJ for JP).
            # gameid is used to send all people on the player's friends list a status updates, so don't make it region
            # specific.
            self.gameid = gsbrcd[0:4]
            self.profileid = int(profileid)

            self.log(logging.DEBUG, "SENDING: %s" % msg)
            self.transport.write(bytes(msg))

            self.buddies = self.db.get_buddy_list(self.profileid)
            self.blocked = self.db.get_blocked_list(self.profileid)

            # Get pending messages.
            self.get_pending_messages()

            # Send any friend statuses when the user logs in.
            # This will allow the user to see if their friends are hosting a game as soon as they log in.
            self.get_status_from_friends()
            self.send_status_to_friends()
コード例 #23
0
    def do_POST(self):
        try:
            length = int(self.headers['content-length'])
            post = self.str_to_dict(self.rfile.read(length))

            if self.path == "/ac":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path, self.client_address)
                logger.log(logging.DEBUG, post)
                ret = {
                    "datetime": time.strftime("%Y%m%d%H%M%S"),
                    "retry": "0"
                }
                action = post["action"]
                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.send_header("NODE", "wifiappe1")

                if action == "acctcreate":
                    # TODO: test for duplicate accounts
                    ret["returncd"] = "002"

                    logger.log(logging.DEBUG, "acctcreate response to %s", self.client_address)
                    logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)

                elif action == "login":
                    challenge = utils.generate_random_str(8)
                    post["challenge"] = challenge
                    authtoken = self.server.db.generate_authtoken(post["userid"], post)
                    ret.update({
                        "returncd": "001",
                        "locator": "gamespy.com",
                        "challenge": challenge,
                        "token": authtoken,
                    })

                    logger.log(logging.DEBUG, "login response to %s", self.client_address)
                    logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)

                elif action == "SVCLOC" or action == "svcloc": # Get service based on service id number
                    ret["returncd"] = "007"
                    ret["statusdata"] = "Y"
                    authtoken = self.server.db.generate_authtoken(post["userid"], post)

                    if 'svc' in post:
                        if post["svc"] in ("9000", "9001"): # DLC host = 9000
                            ret["svchost"] = self.headers['host'] # in case the client's DNS isn't redirecting dls1.nintendowifi.net

                            # Brawl has 2 host headers which Apache chokes on, so only return the first one or else it won't work
                            ret["svchost"] = ret["svchost"].split(',')[0]

                            if post["svc"] == 9000:
                                ret["token"] = authtoken
                            else:
                                ret["servicetoken"] = authtoken
                        elif post["svc"] == "0000": # Pokemon requests this for some things
                            ret["servicetoken"] = authtoken
                            ret["svchost"] = "n/a"

                    logger.log(logging.DEBUG, "svcloc response to %s", self.client_address)
                    logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)
                else:
                    logger.log(logging.WARNING, "Unknown action request %s from %s!", self.path, self.client_address)


            elif self.path == "/pr":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path, self.client_address)
                logger.log(logging.DEBUG, post)
                words = len(post["words"].split('\t'))
                wordsret = "0" * words
                ret = {
                    "prwords": wordsret,
                    "returncd": "000",
                    "datetime": time.strftime("%Y%m%d%H%M%S")
                }

                for l in "ACEJKP":
                    ret["prwords"+l] = wordsret

                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.send_header("NODE", "wifiappe1")

                logger.log(logging.DEBUG, "pr response to %s", self.client_address)
                logger.log(logging.DEBUG, ret)

                ret = self.dict_to_str(ret)

            elif self.path == "/download":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path, self.client_address)
                logger.log(logging.DEBUG, post)

                action = post["action"]

                ret = ""
                dlcdir = os.path.abspath('dlc')
                dlcpath = os.path.abspath("dlc/" + post["gamecd"])
                dlc_contenttype = False

                if os.path.commonprefix([dlcdir, dlcpath]) != dlcdir:
                    logging.log(logging.WARNING, 'Attempted directory traversal attack "%s", cancelling.', dlcpath)
                    self.send_response(403)
                    return

                def safeloadfi(fn, mode='rb'):
                    '''
                    safeloadfi : string -> string

                    Safely load contents of a file, given a filename, and closing the file afterward
                    '''
                    with open(os.path.join(dlcpath, fn), mode) as fi:
                        return fi.read()

                if action == "count":
                    if post["gamecd"] in gamecodes_return_random_file:
                        ret = "1"
                    else:
                        count = 0

                        if os.path.exists(dlcpath):
                            count = len(os.listdir(dlcpath))

                            if os.path.isfile(dlcpath + "/_list.txt"):
                                attr1 = post.get("attr1", None)
                                attr2 = post.get("attr2", None)
                                attr3 = post.get("attr3", None)

                                dlcfi = safeloadfi("_list.txt")
                                lst = self.filter_list(dlcfi, attr1, attr2, attr3)
                                count = self.get_file_count(lst)

                        ret = "%d" % count

                if action == "list":
                    num = int(post["num"])
                    offset = int(post["offset"])

                    attr1 = post.get("attr1", None)
                    attr2 = post.get("attr2", None)
                    attr3 = post.get("attr3", None)

                    if os.path.exists(dlcpath):
                        # Look for a list file first.
                        # If the list file exists, send the entire thing back to the client.
                        if os.path.isfile(os.path.join(dlcpath, "_list.txt")):
                            ret = self.filter_list(safeloadfi("_list.txt"), attr1, attr2, attr3)
                            
                            # this is super game-specific, but we need to handle gen 5 mystery gifts properly somehow
                            if post["gamecd"].startswith("IRA") and attr1.startswith("MYSTERY"):
                                ret = self.filter_list_g5_mystery_gift(ret, post["rhgamecd"])
                                ret = self.filter_list_by_date(ret, post["token"])
                            
                            if post["gamecd"] in gamecodes_return_random_file:
                                ret = self.filter_list_by_date(ret, post["token"])

                if action == "contents":
                    # Get only the base filename just in case there is a path involved somewhere in the filename string.
                    dlc_contenttype = True
                    contents = os.path.basename(post["contents"])
                    ret = safeloadfi(contents)

                self.send_response(200)

                if dlc_contenttype == True:
                    self.send_header("Content-type", "application/x-dsdl")
                    self.send_header("Content-Disposition", "attachment; filename=\"" + post["contents"] + "\"")
                else:
                    self.send_header("Content-type", "text/plain")

                self.send_header("X-DLS-Host", "http://127.0.0.1/")

                logger.log(logging.DEBUG, "download response to %s", self.client_address)

                #if dlc_contenttype == False:
                #    logger.log(logging.DEBUG, ret)
            else:
                logger.log(logging.WARNING, "Unknown path request %s from %s!", self.path, self.client_address)

            self.send_header("Content-Length", str(len(ret)))
            self.end_headers()
            self.wfile.write(ret)
        except:
            logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc())
コード例 #24
0
    def do_POST(self):
        length = int(self.headers['content-length'])
        post = self.str_to_dict(self.rfile.read(length))

        if self.path == "/ac":
            logger.log(logging.DEBUG, "Request to %s from %s", self.path, self.client_address)
            logger.log(logging.DEBUG, post)
            ret = {}
            ret["datetime"] = time.strftime("%Y%m%d%H%M%S")
            ret["retry"] = "0"
            action = post["action"]
            self.send_response(200)
            self.send_header("Content-type", "text/plain")
            self.send_header("Server", "Nintendo Wii (http)")
            self.send_header("NODE", "wifiappe3")
            self.end_headers()

            if action == "acctcreate":
                # TODO: test for duplicate accounts
                ret["returncd"] = "002"

                logger.log(logging.DEBUG, "acctcreate response to %s", self.client_address)
                logger.log(logging.DEBUG, ret)

                self.wfile.write(self.dict_to_str(ret))

            elif action == "login":
                ret["returncd"] = "001"
                ret["locator"] = "gamespy.com"
                challenge = utils.generate_random_str(8)
                ret["challenge"] = challenge
                post["challenge"] = challenge
                authtoken = self.server.db.generate_authtoken(post["userid"], post)
                ret["token"] = authtoken

                logger.log(logging.DEBUG, "login response to %s", self.client_address)
                logger.log(logging.DEBUG, ret)

                self.wfile.write(self.dict_to_str(ret))

            elif action == "SVCLOC" or action == "svcloc": # Get service based on service id number
                ret["returncd"] = "007"
                ret["statusdata"] = "Y"
                authtoken = self.server.db.generate_authtoken(post["userid"], post)

                if 'svc' in post:
                    if post["svc"] == "9000" or post["svc"] == "9001": # DLC host = 9000
                        ret["svchost"] = self.headers['host'] # in case the client's DNS isn't redirecting dls1.nintendowifi.net

                        # Brawl has 2 host headers which Apache chokes on, so only return the first one or else it won't work
                        cindex = ret["svchost"].find(',')
                        if cindex != -1:
                            ret["svchost"] = ret["svchost"][:cindex]

                        if post["svc"] == 9000:
                            ret["token"] = authtoken
                        else:
                            ret["servicetoken"] = authtoken
                    elif post["svc"] == "0000": # Pokemon requests this for some things
                        ret["servicetoken"] = authtoken
                        ret["svchost"] = "n/a"

                logger.log(logging.DEBUG, "svcloc response to %s", self.client_address)
                logger.log(logging.DEBUG, ret)

                self.wfile.write(self.dict_to_str(ret))



        elif self.path == "/pr":
            logger.log(logging.DEBUG, "Request to %s from %s", self.path, self.client_address)
            logger.log(logging.DEBUG, post)
            ret = {}

            words = len(post["words"].split('\t'))
            wordsret = "0" * words
            ret["prwords"] = wordsret
            ret["prwordsA"] = wordsret
            ret["prwordsC"] = wordsret
            ret["prwordsE"] = wordsret
            ret["prwordsJ"] = wordsret
            ret["prwordsK"] = wordsret
            ret["prwordsP"] = wordsret
            ret["returncd"] = "000"
            ret["datetime"] = time.strftime("%Y%m%d%H%M%S")

            self.send_response(200)
            self.send_header("Content-type", "text/plain")
            self.send_header("Server", "Nintendo Wii (http)")
            self.send_header("NODE", "wifiappe3")
            self.end_headers()

            logger.log(logging.DEBUG, "pr response to %s", self.client_address)
            logger.log(logging.DEBUG, ret)

            self.wfile.write(self.dict_to_str(ret))

        elif self.path == "/download":
            logger.log(logging.DEBUG, "Request to %s from %s", self.path, self.client_address)
            logger.log(logging.DEBUG, post)

            action = post["action"]

            ret = ""
            dlcpath = "dlc/" + post["gamecd"]
            dlc_contenttype = False

            if action == "count":
                if post["gamecd"] in gamecodes_return_random_file:
                    ret = "1"
                else:
                    count = 0

                    if os.path.exists(dlcpath):
                        count = len(os.listdir(dlcpath))

                        if os.path.isfile(dlcpath + "/_list.txt"):
                            attr1 = None
                            if "attr1" in post:
                                attr1 = post["attr1"]
                            attr2 = None
                            if "attr2" in post:
                                attr2 = post["attr2"]
                            attr3 = None
                            if "attr3" in post:
                                attr3 = post["attr3"]
                            list = open(dlcpath + "/_list.txt", "rb").read()
                            list = self.filter_list(list, attr1, attr2, attr3)

                            count = self.get_file_count(list)

                    ret = "%d" % count

            if action == "list":
                num = int(post["num"])
                offset = int(post["offset"])

                attr1 = None
                if "attr1" in post:
                    attr1 = post["attr1"]
                attr2 = None
                if "attr2" in post:
                    attr2 = post["attr2"]
                attr3 = None
                if "attr3" in post:
                    attr3 = post["attr3"]

                if os.path.exists(dlcpath):
                    # Look for a list file first.
                    # If the list file exists, send the entire thing back to the client.
                    if os.path.isfile(dlcpath + "/_list.txt"):
                        ret = open(dlcpath + "/_list.txt", "rb").read()
                        ret = self.filter_list(ret, attr1, attr2, attr3)

                        if post["gamecd"] in gamecodes_return_random_file:
                            ret = self.filter_list_random_files(ret, 1)

            if action == "contents":
                # Get only the base filename just in case there is a path involved somewhere in the filename string.
                dlc_contenttype = True
                contents = os.path.basename(post["contents"])

                if os.path.isfile(dlcpath + "/" + contents):
                    ret = open(dlcpath + "/" + contents, "rb").read()

            self.send_response(200)

            if dlc_contenttype == True:
                self.send_header("Content-Length", str(len(ret)))
                self.send_header("Content-type", "application/x-dsdl")
                self.send_header("Content-Disposition", "attachment; filename=\"" + post["contents"] + "\"")
            else:
                self.send_header("Content-type", "text/plain")

            self.send_header("X-DLS-Host", "http://127.0.0.1/")
            self.end_headers()

            logger.log(logging.DEBUG, "download response to %s", self.client_address)

            #if dlc_contenttype == False:
            #    logger.log(logging.DEBUG, ret)

            self.wfile.write(ret)
コード例 #25
0
    def do_POST(self):
        length = int(self.headers['content-length'])
        post = self.str_to_dict(self.rfile.read(length))

        if self.path == "/ac":
            logger.log(logging.DEBUG, "Request to %s from %s", self.path,
                       self.client_address)
            logger.log(logging.DEBUG, post)
            ret = {}
            ret["datetime"] = time.strftime("%Y%m%d%H%M%S")
            ret["retry"] = "0"
            action = post["action"]
            self.send_response(200)
            self.send_header("Content-type", "text/plain")
            self.send_header("Server", "Nintendo Wii (http)")
            self.send_header("NODE", "wifiappe3")
            self.end_headers()

            if action == "acctcreate":
                # TODO: test for duplicate accounts
                ret["returncd"] = "002"

                logger.log(logging.DEBUG, "acctcreate response to %s",
                           self.client_address)
                logger.log(logging.DEBUG, ret)

                self.wfile.write(self.dict_to_str(ret))

            elif action == "login":
                ret["returncd"] = "001"
                ret["locator"] = "gamespy.com"
                challenge = utils.generate_random_str(8)
                ret["challenge"] = challenge
                post["challenge"] = challenge
                authtoken = self.server.db.generate_authtoken(
                    post["userid"], post)
                ret["token"] = authtoken

                logger.log(logging.DEBUG, "login response to %s",
                           self.client_address)
                logger.log(logging.DEBUG, ret)

                self.wfile.write(self.dict_to_str(ret))

            elif action == "SVCLOC" or action == "svcloc":  # Get service based on service id number
                ret["returncd"] = "007"
                ret["statusdata"] = "Y"
                authtoken = self.server.db.generate_authtoken(
                    post["userid"], post)

                if 'svc' in post:
                    if post["svc"] == "9000" or post[
                            "svc"] == "9001":  # DLC host = 9000
                        ret["svchost"] = self.headers[
                            'host']  # in case the client's DNS isn't redirecting dls1.nintendowifi.net

                        # Brawl has 2 host headers which Apache chokes on, so only return the first one or else it won't work
                        cindex = ret["svchost"].find(',')
                        if cindex != -1:
                            ret["svchost"] = ret["svchost"][:cindex]

                        if post["svc"] == 9000:
                            ret["token"] = authtoken
                        else:
                            ret["servicetoken"] = authtoken
                    elif post[
                            "svc"] == "0000":  # Pokemon requests this for some things
                        ret["servicetoken"] = authtoken
                        ret["svchost"] = "n/a"

                logger.log(logging.DEBUG, "svcloc response to %s",
                           self.client_address)
                logger.log(logging.DEBUG, ret)

                self.wfile.write(self.dict_to_str(ret))

        elif self.path == "/pr":
            logger.log(logging.DEBUG, "Request to %s from %s", self.path,
                       self.client_address)
            logger.log(logging.DEBUG, post)
            ret = {}

            words = len(post["words"].split('\t'))
            wordsret = "0" * words
            ret["prwords"] = wordsret
            ret["prwordsA"] = wordsret
            ret["prwordsC"] = wordsret
            ret["prwordsE"] = wordsret
            ret["prwordsJ"] = wordsret
            ret["prwordsK"] = wordsret
            ret["prwordsP"] = wordsret
            ret["returncd"] = "000"
            ret["datetime"] = time.strftime("%Y%m%d%H%M%S")

            self.send_response(200)
            self.send_header("Content-type", "text/plain")
            self.send_header("Server", "Nintendo Wii (http)")
            self.send_header("NODE", "wifiappe3")
            self.end_headers()

            logger.log(logging.DEBUG, "pr response to %s", self.client_address)
            logger.log(logging.DEBUG, ret)

            self.wfile.write(self.dict_to_str(ret))

        elif self.path == "/download":
            logger.log(logging.DEBUG, "Request to %s from %s", self.path,
                       self.client_address)
            logger.log(logging.DEBUG, post)

            action = post["action"]

            ret = ""
            dlcpath = "dlc/" + post["gamecd"]
            dlc_contenttype = False

            if action == "count":
                if post["gamecd"] in gamecodes_return_random_file:
                    ret = "1"
                else:
                    count = 0

                    if os.path.exists(dlcpath):
                        count = len(os.listdir(dlcpath))

                        if os.path.isfile(dlcpath + "/_list.txt"):
                            attr1 = None
                            if "attr1" in post:
                                attr1 = post["attr1"]
                            attr2 = None
                            if "attr2" in post:
                                attr2 = post["attr2"]
                            attr3 = None
                            if "attr3" in post:
                                attr3 = post["attr3"]
                            list = open(dlcpath + "/_list.txt", "rb").read()
                            list = self.filter_list(list, attr1, attr2, attr3)

                            count = self.get_file_count(list)

                    ret = "%d" % count

            if action == "list":
                num = int(post["num"])
                offset = int(post["offset"])

                attr1 = None
                if "attr1" in post:
                    attr1 = post["attr1"]
                attr2 = None
                if "attr2" in post:
                    attr2 = post["attr2"]
                attr3 = None
                if "attr3" in post:
                    attr3 = post["attr3"]

                if os.path.exists(dlcpath):
                    # Look for a list file first.
                    # If the list file exists, send the entire thing back to the client.
                    if os.path.isfile(dlcpath + "/_list.txt"):
                        ret = open(dlcpath + "/_list.txt", "rb").read()
                        ret = self.filter_list(ret, attr1, attr2, attr3)

                        if post["gamecd"] in gamecodes_return_random_file:
                            ret = self.filter_list_random_files(ret, 1)

            if action == "contents":
                # Get only the base filename just in case there is a path involved somewhere in the filename string.
                dlc_contenttype = True
                contents = os.path.basename(post["contents"])

                if os.path.isfile(dlcpath + "/" + contents):
                    ret = open(dlcpath + "/" + contents, "rb").read()

            self.send_response(200)

            if dlc_contenttype == True:
                self.send_header("Content-Length", str(len(ret)))
                self.send_header("Content-type", "application/x-dsdl")
                self.send_header(
                    "Content-Disposition",
                    "attachment; filename=\"" + post["contents"] + "\"")
            else:
                self.send_header("Content-type", "text/plain")

            self.send_header("X-DLS-Host", "http://127.0.0.1/")
            self.end_headers()

            logger.log(logging.DEBUG, "download response to %s",
                       self.client_address)

            #if dlc_contenttype == False:
            #    logger.log(logging.DEBUG, ret)

            self.wfile.write(ret)
コード例 #26
0
    def do_POST(self):
        self.server = lambda:None
        self.server.db = gs_database.GamespyDatabase()

        try:
            length = int(self.headers['content-length'])
            post = self.str_to_dict(self.rfile.read(length))
            if self.client_address[0] == '127.0.0.1':
                client_address = (self.headers.get('x-forwarded-for', self.client_address[0]), self.client_address[1])
            else:
                client_address = self.client_address

            post['ipaddr'] = client_address[0]

            if self.path == "/ac":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path, client_address)
                logger.log(logging.DEBUG, post)
                ret = {
                    "datetime": time.strftime("%Y%m%d%H%M%S"),
                    "retry": "0"
                }
                action = post["action"]
                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.send_header("NODE", "wifiappe1")

                if action == "acctcreate":
                    # TODO: test for duplicate accounts
                    if self.server.db.is_ip_banned(post):
                        logger.log(logging.DEBUG, "acctcreate denied for banned user "+str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3913",
                            "locator": "gamespy.com",
                            "retry": "1",
                            "reason": "User banned."
                        }
                    else:
                        ret["returncd"] = "002"
                        ret['userid'] = self.server.db.get_next_available_userid()

                        logger.log(logging.DEBUG, "acctcreate response to %s", client_address)
                        logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)

                elif action == "login":
                    if self.server.db.is_ip_banned(post):
                        logger.log(logging.DEBUG, "login denied for banned user "+str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3917",
                            "locator": "gamespy.com",
                            "retry": "1",
                            "reason": "User banned."
                        }
                    elif self.server.db.is_console_macadr_banned(post):
                        logger.log(logging.DEBUG, "login denied for banned console"+str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3914",
                            "locator": "gamespy.com",
                            "retry": "1",
                            "reason": "User's console is banned."
                        }
                    elif self.server.db.is_console_cfc_banned(post):
                        logger.log(logging.DEBUG, "login denied for banned console"+str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3915",
                            "locator": "gamespy.com",
                            "retry": "1",
                            "reason": "User's console is banned."
                        }
                    elif self.server.db.is_console_csnum_banned(post):
                        logger.log(logging.DEBUG, "login denied for banned console"+str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3915",
                            "locator": "gamespy.com",
                            "retry": "1",
                            "reason": "User's console is banned."
                        }
#Un0comment the lines below to activate console registration
                    #elif not self.server.db.pending(post):
                        #logger.log(logging.DEBUG, "Login denied - Unknown console"+str(post))
                        #ret = {
                            #"datetime": time.strftime("%Y%m%d%H%M%S"),
                            #"returncd": "3921",
                            #"locator": "gamespy.com",
                            #"retry": "1",
                        #}
                    #elif not self.server.db.registered(post):
                        #logger.log(logging.DEBUG, "Login denied - console pending"+str(post))
                        #ret = {
                            #"datetime": time.strftime("%Y%m%d%H%M%S"),
                            #"returncd": "3888",
                            #"locator": "gamespy.com",
                            #"retry": "1",
                        #}
                    else:
                        challenge = utils.generate_random_str(8)
                        post["challenge"] = challenge
                        
                        authtoken = self.server.db.generate_authtoken(post["userid"], post)
                        ret.update({
                            "returncd": "001",
                            "locator": "gamespy.com",
                            "challenge": challenge,
                            "token": authtoken,
                        })

                        logger.log(logging.DEBUG, "login response to %s", client_address)
                        logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)

                elif action == "SVCLOC" or action == "svcloc": # Get service based on service id number
                    ret["returncd"] = "007"
                    ret["statusdata"] = "Y"
                    authtoken = self.server.db.generate_authtoken(post["userid"], post)

                    if 'svc' in post:
                        if post["svc"] in ("9000", "9001"): # DLC host = 9000
                            ret["svchost"] = self.headers['host'] # in case the client's DNS isn't redirecting dls1.nintendowifi.net

                            # Brawl has 2 host headers which Apache chokes on, so only return the first one or else it won't work
                            ret["svchost"] = ret["svchost"].split(',')[0]

                            if post["svc"] == 9000:
                                ret["token"] = authtoken
                            else:
                                ret["servicetoken"] = authtoken
                        elif post["svc"] == "0000": # Pokemon requests this for some things
                            ret["servicetoken"] = authtoken
                            ret["svchost"] = "n/a"

                    logger.log(logging.DEBUG, "svcloc response to %s", client_address)
                    logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)
                else:
                    logger.log(logging.WARNING, "Unknown action request %s from %s!", self.path, client_address)


            elif self.path == "/pr":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path, client_address)
                logger.log(logging.DEBUG, post)
                words = len(post["words"].split('\t'))
                wordsret = "0" * words
                ret = {
                    "prwords": wordsret,
                    "returncd": "000",
                    "datetime": time.strftime("%Y%m%d%H%M%S")
                }

                for l in "ACEJKP":
                    ret["prwords"+l] = wordsret

                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.send_header("NODE", "wifiappe1")

                logger.log(logging.DEBUG, "pr response to %s", client_address)
                logger.log(logging.DEBUG, ret)

                ret = self.dict_to_str(ret)

            elif self.path == "/download":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path, client_address)
                logger.log(logging.DEBUG, post)

                action = post["action"]

                ret = ""
                dlcdir = os.path.abspath('dlc')
                dlcpath = os.path.abspath("dlc/" + post["gamecd"])
                dlc_contenttype = False

                if os.path.commonprefix([dlcdir, dlcpath]) != dlcdir:
                    logging.log(logging.WARNING, 'Attempted directory traversal attack "%s", cancelling.', dlcpath)
                    self.send_response(403)
                    return

                def safeloadfi(fn, mode='rb'):
                    '''
                    safeloadfi : string -> string

                    Safely load contents of a file, given a filename, and closing the file afterward
                    '''
                    with open(os.path.join(dlcpath, fn), mode) as fi:
                        return fi.read()

                if action == "count":
                    if post["gamecd"] in gamecodes_return_random_file:
                        ret = "1"
                    else:
                        count = 0

                        if os.path.exists(dlcpath):
                            count = len(os.listdir(dlcpath))

                            if os.path.isfile(dlcpath + "/_list.txt"):
                                attr1 = post.get("attr1", None)
                                attr2 = post.get("attr2", None)
                                attr3 = post.get("attr3", None)

                                dlcfi = safeloadfi("_list.txt")
                                lst = self.filter_list(dlcfi, attr1, attr2, attr3)
                                count = self.get_file_count(lst)

                        ret = "%d" % count

                if action == "list":
                    num = post.get("num", None)
                    offset = post.get("offset", None)

                    if num != None:
                        num = int(num)

                    if offset != None:
                        offset = int(offset)

                    attr1 = post.get("attr1", None)
                    attr2 = post.get("attr2", None)
                    attr3 = post.get("attr3", None)

                    if os.path.exists(dlcpath):
                        # Look for a list file first.
                        # If the list file exists, send the entire thing back to the client.
                        if os.path.isfile(os.path.join(dlcpath, "_list.txt")):
                            if post["gamecd"].startswith("IRA") and attr1.startswith("MYSTERY"):
                                # Pokemon BW Mystery Gifts, until we have a better solution for that
                                ret = self.filter_list(safeloadfi("_list.txt"), attr1, attr2, attr3)
                                ret = self.filter_list_g5_mystery_gift(ret, post["rhgamecd"])
                                ret = self.filter_list_by_date(ret, post["token"])
                            elif post["gamecd"] in gamecodes_return_random_file:
                                # Pokemon Gen 4 Mystery Gifts, same here
                                ret = self.filter_list(safeloadfi("_list.txt"), attr1, attr2, attr3)
                                ret = self.filter_list_by_date(ret, post["token"])
                            else:
                                # default case for most games
                                ret = self.filter_list(safeloadfi("_list.txt"), attr1, attr2, attr3, num, offset)

                if action == "contents":
                    # Get only the base filename just in case there is a path involved somewhere in the filename string.
                    dlc_contenttype = True
                    contents = os.path.basename(post["contents"])
                    ret = safeloadfi(contents)

                self.send_response(200)

                if dlc_contenttype == True:
                    self.send_header("Content-type", "application/x-dsdl")
                    self.send_header("Content-Disposition", "attachment; filename=\"" + post["contents"] + "\"")
                else:
                    self.send_header("Content-type", "text/plain")

                self.send_header("X-DLS-Host", "http://127.0.0.1/")

                logger.log(logging.DEBUG, "download response to %s", client_address)

                #if dlc_contenttype == False:
                #    logger.log(logging.DEBUG, ret)
            else:
                self.send_response(404)
                logger.log(logging.WARNING, "Unknown path request %s from %s!", self.path, client_address)
                return

            self.send_header("Content-Length", str(len(ret)))
            self.end_headers()
            self.wfile.write(ret)
        except:
            logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc())
コード例 #27
0
    def do_POST(self):
        try:
            length = int(self.headers['content-length'])
            post = self.str_to_dict(self.rfile.read(length))

            if self.path == "/ac":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path,
                           self.client_address)
                logger.log(logging.DEBUG, post)
                ret = {"datetime": time.strftime("%Y%m%d%H%M%S"), "retry": "0"}
                action = post["action"]
                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.send_header("NODE", "wifiappe1")

                if action == "acctcreate":
                    # TODO: test for duplicate accounts
                    ret["returncd"] = "002"

                    logger.log(logging.DEBUG, "acctcreate response to %s",
                               self.client_address)
                    logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)

                elif action == "login":
                    challenge = utils.generate_random_str(8)
                    post["challenge"] = challenge
                    authtoken = self.server.db.generate_authtoken(
                        post["userid"], post)
                    ret.update({
                        "returncd": "001",
                        "locator": "gamespy.com",
                        "challenge": challenge,
                        "token": authtoken,
                    })

                    logger.log(logging.DEBUG, "login response to %s",
                               self.client_address)
                    logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)

                elif action == "SVCLOC" or action == "svcloc":  # Get service based on service id number
                    ret["returncd"] = "007"
                    ret["statusdata"] = "Y"
                    authtoken = self.server.db.generate_authtoken(
                        post["userid"], post)

                    if 'svc' in post:
                        if post["svc"] in ("9000", "9001"):  # DLC host = 9000
                            ret["svchost"] = self.headers[
                                'host']  # in case the client's DNS isn't redirecting dls1.nintendowifi.net

                            # Brawl has 2 host headers which Apache chokes on, so only return the first one or else it won't work
                            ret["svchost"] = ret["svchost"].split(',')[0]

                            if post["svc"] == 9000:
                                ret["token"] = authtoken
                            else:
                                ret["servicetoken"] = authtoken
                        elif post[
                                "svc"] == "0000":  # Pokemon requests this for some things
                            ret["servicetoken"] = authtoken
                            ret["svchost"] = "n/a"

                    logger.log(logging.DEBUG, "svcloc response to %s",
                               self.client_address)
                    logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)
                else:
                    logger.log(logging.WARNING,
                               "Unknown action request %s from %s!", self.path,
                               self.client_address)

            elif self.path == "/pr":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path,
                           self.client_address)
                logger.log(logging.DEBUG, post)
                words = len(post["words"].split('\t'))
                wordsret = "0" * words
                ret = {
                    "prwords": wordsret,
                    "returncd": "000",
                    "datetime": time.strftime("%Y%m%d%H%M%S")
                }

                for l in "ACEJKP":
                    ret["prwords" + l] = wordsret

                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.send_header("NODE", "wifiappe1")

                logger.log(logging.DEBUG, "pr response to %s",
                           self.client_address)
                logger.log(logging.DEBUG, ret)

                ret = self.dict_to_str(ret)

            elif self.path == "/download":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path,
                           self.client_address)
                logger.log(logging.DEBUG, post)

                action = post["action"]

                ret = ""
                dlcdir = os.path.abspath('dlc')
                dlcpath = os.path.abspath("dlc/" + post["gamecd"])
                dlc_contenttype = False

                if os.path.commonprefix([dlcdir, dlcpath]) != dlcdir:
                    logging.log(
                        logging.WARNING,
                        'Attempted directory traversal attack "%s", cancelling.',
                        dlcpath)
                    self.send_response(403)
                    return

                def safeloadfi(fn, mode='rb'):
                    '''
                    safeloadfi : string -> string

                    Safely load contents of a file, given a filename, and closing the file afterward
                    '''
                    with open(os.path.join(dlcpath, fn), mode) as fi:
                        return fi.read()

                if action == "count":
                    if post["gamecd"] in gamecodes_return_random_file:
                        ret = "1"
                    else:
                        count = 0

                        if os.path.exists(dlcpath):
                            count = len(os.listdir(dlcpath))

                            if os.path.isfile(dlcpath + "/_list.txt"):
                                attr1 = post.get("attr1", None)
                                attr2 = post.get("attr2", None)
                                attr3 = post.get("attr3", None)

                                dlcfi = safeloadfi("_list.txt")
                                lst = self.filter_list(dlcfi, attr1, attr2,
                                                       attr3)
                                count = self.get_file_count(lst)

                        ret = "%d" % count

                if action == "list":
                    num = int(post["num"])
                    offset = int(post["offset"])

                    attr1 = post.get("attr1", None)
                    attr2 = post.get("attr2", None)
                    attr3 = post.get("attr3", None)

                    if os.path.exists(dlcpath):
                        # Look for a list file first.
                        # If the list file exists, send the entire thing back to the client.
                        if os.path.isfile(os.path.join(dlcpath, "_list.txt")):
                            ret = self.filter_list(safeloadfi("_list.txt"),
                                                   attr1, attr2, attr3)

                            # Don't try to filter any further if the list is empty.
                            if ret.strip() != "":
                                # this is super game-specific, but we need to handle gen 5 mystery gifts properly somehow
                                if post["gamecd"].startswith(
                                        "IRA") and attr1.startswith("MYSTERY"):
                                    ret = self.filter_list_g5_mystery_gift(
                                        ret, post["rhgamecd"])
                                    ret = self.filter_list_by_date(
                                        ret, post["token"])

                                if post["gamecd"] in gamecodes_return_random_file:
                                    ret = self.filter_list_by_date(
                                        ret, post["token"])

                if action == "contents":
                    # Get only the base filename just in case there is a path involved somewhere in the filename string.
                    dlc_contenttype = True
                    contents = os.path.basename(post["contents"])
                    ret = safeloadfi(contents)

                self.send_response(200)

                if dlc_contenttype == True:
                    self.send_header("Content-type", "application/x-dsdl")
                    self.send_header(
                        "Content-Disposition",
                        "attachment; filename=\"" + post["contents"] + "\"")
                else:
                    self.send_header("Content-type", "text/plain")

                self.send_header("X-DLS-Host", "http://127.0.0.1/")

                logger.log(logging.DEBUG, "download response to %s",
                           self.client_address)

                #if dlc_contenttype == False:
                #    logger.log(logging.DEBUG, ret)
            else:
                logger.log(logging.WARNING, "Unknown path request %s from %s!",
                           self.path, self.client_address)

            self.send_header("Content-Length", str(len(ret)))
            self.end_headers()
            self.wfile.write(ret)
        except:
            logger.log(logging.ERROR,
                       "Unknown exception: %s" % traceback.format_exc())
コード例 #28
0
    def do_POST(self):
        self.server = lambda: None
        self.server.db = gs_database.GamespyDatabase()

        try:
            length = int(self.headers['content-length'])
            post = self.str_to_dict(self.rfile.read(length))
            if self.client_address[0] == '127.0.0.1':
                client_address = (self.headers.get('x-forwarded-for',
                                                   self.client_address[0]),
                                  self.client_address[1])
            else:
                client_address = self.client_address

            post['ipaddr'] = client_address[0]

            if self.path == "/ac":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path,
                           client_address)
                logger.log(logging.DEBUG, post)
                ret = {"datetime": time.strftime("%Y%m%d%H%M%S"), "retry": "0"}
                action = post["action"]
                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.send_header("NODE", "wifiappe1")

                if action == "acctcreate":
                    # TODO: test for duplicate accounts
                    if self.server.db.is_ip_banned(post):
                        logger.log(
                            logging.DEBUG,
                            "acctcreate denied for banned user " + str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3913",
                            "locator": "gamespy.com",
                            "retry": "1",
                            "reason": "User banned."
                        }
                    else:
                        ret["returncd"] = "002"
                        ret['userid'] = self.server.db.get_next_available_userid(
                        )

                        logger.log(logging.DEBUG, "acctcreate response to %s",
                                   client_address)
                        logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)

                elif action == "login":
                    if self.server.db.is_ip_banned(post):
                        logger.log(logging.DEBUG,
                                   "login denied for banned user " + str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3917",
                            "locator": "gamespy.com",
                            "retry": "1",
                            "reason": "User banned."
                        }
                    elif self.server.db.is_console_macadr_banned(post):
                        logger.log(
                            logging.DEBUG,
                            "login denied for banned console" + str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3914",
                            "locator": "gamespy.com",
                            "retry": "1",
                            "reason": "User's console is banned."
                        }
                    elif self.server.db.is_console_cfc_banned(post):
                        logger.log(
                            logging.DEBUG,
                            "login denied for banned console" + str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3915",
                            "locator": "gamespy.com",
                            "retry": "1",
                            "reason": "User's console is banned."
                        }
                    elif self.server.db.is_console_csnum_banned(post):
                        logger.log(
                            logging.DEBUG,
                            "login denied for banned console" + str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3915",
                            "locator": "gamespy.com",
                            "retry": "1",
                            "reason": "User's console is banned."
                        }
                    elif self.server.db.is_ingamesn_banned(post):
                        logger.log(
                            logging.DEBUG,
                            "login denied for banned Mii name" + str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3916",
                            "locator": "gamespy.com",
                            "retry": "1",
                            "reason": "Mii name is black listed"
                        }
                    elif self.server.db.is_devname_banned(post):
                        logger.log(
                            logging.DEBUG,
                            "login denied for banned device name" + str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3916",
                            "locator": "gamespy.com",
                            "retry": "1",
                            "reason": "device name is black listed"
                        }


#Un0comment the lines below to activate console registration
                    elif not self.server.db.pending(post):
                        logger.log(
                            logging.DEBUG,
                            "Login denied - Unknown console" + str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3921",
                            "locator": "gamespy.com",
                            "retry": "1",
                        }
                    elif not self.server.db.registered(post):
                        logger.log(
                            logging.DEBUG,
                            "Login denied - console pending" + str(post))
                        ret = {
                            "datetime": time.strftime("%Y%m%d%H%M%S"),
                            "returncd": "3888",
                            "locator": "gamespy.com",
                            "retry": "1",
                        }
                    else:
                        challenge = utils.generate_random_str(8)
                        post["challenge"] = challenge

                        authtoken = self.server.db.generate_authtoken(
                            post["userid"], post)
                        ret.update({
                            "returncd": "001",
                            "locator": "gamespy.com",
                            "challenge": challenge,
                            "token": authtoken,
                        })

                        logger.log(logging.DEBUG, "login response to %s",
                                   client_address)
                        logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)

                elif action == "SVCLOC" or action == "svcloc":  # Get service based on service id number
                    ret["returncd"] = "007"
                    ret["statusdata"] = "Y"
                    authtoken = self.server.db.generate_authtoken(
                        post["userid"], post)

                    if 'svc' in post:
                        if post["svc"] in ("9000", "9001"):  # DLC host = 9000
                            ret["svchost"] = self.headers[
                                'host']  # in case the client's DNS isn't redirecting dls1.nintendowifi.net

                            # Brawl has 2 host headers which Apache chokes on, so only return the first one or else it won't work
                            ret["svchost"] = ret["svchost"].split(',')[0]

                            if post["svc"] == 9000:
                                ret["token"] = authtoken
                            else:
                                ret["servicetoken"] = authtoken
                        elif post[
                                "svc"] == "0000":  # Pokemon requests this for some things
                            ret["servicetoken"] = authtoken
                            ret["svchost"] = "n/a"

                    logger.log(logging.DEBUG, "svcloc response to %s",
                               client_address)
                    logger.log(logging.DEBUG, ret)

                    ret = self.dict_to_str(ret)
                else:
                    logger.log(logging.WARNING,
                               "Unknown action request %s from %s!", self.path,
                               client_address)

            elif self.path == "/pr":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path,
                           client_address)
                logger.log(logging.DEBUG, post)
                words = len(post["words"].split('\t'))
                wordsret = "0" * words
                ret = {
                    "prwords": wordsret,
                    "returncd": "000",
                    "datetime": time.strftime("%Y%m%d%H%M%S")
                }

                for l in "ACEJKP":
                    ret["prwords" + l] = wordsret

                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.send_header("NODE", "wifiappe1")

                logger.log(logging.DEBUG, "pr response to %s", client_address)
                logger.log(logging.DEBUG, ret)

                ret = self.dict_to_str(ret)

            elif self.path == "/download":
                logger.log(logging.DEBUG, "Request to %s from %s", self.path,
                           client_address)
                logger.log(logging.DEBUG, post)

                action = post["action"]

                ret = ""
                dlcdir = os.path.abspath('dlc')
                dlcpath = os.path.abspath("dlc/" + post["gamecd"])
                dlc_contenttype = False

                if os.path.commonprefix([dlcdir, dlcpath]) != dlcdir:
                    logging.log(
                        logging.WARNING,
                        'Attempted directory traversal attack "%s", cancelling.',
                        dlcpath)
                    self.send_response(403)
                    return

                def safeloadfi(fn, mode='rb'):
                    '''
                    safeloadfi : string -> string

                    Safely load contents of a file, given a filename, and closing the file afterward
                    '''
                    with open(os.path.join(dlcpath, fn), mode) as fi:
                        return fi.read()

                if action == "count":
                    if post["gamecd"] in gamecodes_return_random_file:
                        ret = "1"
                    else:
                        count = 0

                        if os.path.exists(dlcpath):
                            count = len(os.listdir(dlcpath))

                            if os.path.isfile(dlcpath + "/_list.txt"):
                                attr1 = post.get("attr1", None)
                                attr2 = post.get("attr2", None)
                                attr3 = post.get("attr3", None)

                                dlcfi = safeloadfi("_list.txt")
                                lst = self.filter_list(dlcfi, attr1, attr2,
                                                       attr3)
                                count = self.get_file_count(lst)

                        ret = "%d" % count

                if action == "list":
                    num = post.get("num", None)
                    offset = post.get("offset", None)

                    if num != None:
                        num = int(num)

                    if offset != None:
                        offset = int(offset)

                    attr1 = post.get("attr1", None)
                    attr2 = post.get("attr2", None)
                    attr3 = post.get("attr3", None)

                    if os.path.exists(dlcpath):
                        # Look for a list file first.
                        # If the list file exists, send the entire thing back to the client.
                        if os.path.isfile(os.path.join(dlcpath, "_list.txt")):
                            if post["gamecd"].startswith(
                                    "IRA") and attr1.startswith("MYSTERY"):
                                # Pokemon BW Mystery Gifts, until we have a better solution for that
                                ret = self.filter_list(safeloadfi("_list.txt"),
                                                       attr1, attr2, attr3)
                                ret = self.filter_list_g5_mystery_gift(
                                    ret, post["rhgamecd"])
                                ret = self.filter_list_by_date(
                                    ret, post["token"])
                            elif post[
                                    "gamecd"] in gamecodes_return_random_file:
                                # Pokemon Gen 4 Mystery Gifts, same here
                                ret = self.filter_list(safeloadfi("_list.txt"),
                                                       attr1, attr2, attr3)
                                ret = self.filter_list_by_date(
                                    ret, post["token"])
                            else:
                                # default case for most games
                                ret = self.filter_list(safeloadfi("_list.txt"),
                                                       attr1, attr2, attr3,
                                                       num, offset)

                if action == "contents":
                    # Get only the base filename just in case there is a path involved somewhere in the filename string.
                    dlc_contenttype = True
                    contents = os.path.basename(post["contents"])
                    ret = safeloadfi(contents)

                self.send_response(200)

                if dlc_contenttype == True:
                    self.send_header("Content-type", "application/x-dsdl")
                    self.send_header(
                        "Content-Disposition",
                        "attachment; filename=\"" + post["contents"] + "\"")
                else:
                    self.send_header("Content-type", "text/plain")

                self.send_header("X-DLS-Host", "http://127.0.0.1/")

                logger.log(logging.DEBUG, "download response to %s",
                           client_address)

                #if dlc_contenttype == False:
                #    logger.log(logging.DEBUG, ret)
            else:
                self.send_response(404)
                logger.log(logging.WARNING, "Unknown path request %s from %s!",
                           self.path, client_address)
                return

            self.send_header("Content-Length", str(len(ret)))
            self.end_headers()
            self.wfile.write(ret)
        except:
            logger.log(logging.ERROR,
                       "Unknown exception: %s" % traceback.format_exc())
    def perform_login(self, data_parsed):
        authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken'], self.db)
        
        if authtoken_parsed == None:
            self.log(logging.WARNING, "Invalid Authtoken.")
            msg = gs_query.create_gamespy_message([
                ('__cmd__', "error"),
                ('__cmd_val__', ""),
                ('err', '266'),
                ('fatal', ''),
                ('errmsg', 'There was an error validating the pre-authentication.'),
                ('id', data_parsed['id']),
            ])
            self.transport.write(bytes(msg))
            return

        if 'sdkrevision' in data_parsed:
            self.sdkrevision = data_parsed['sdkrevision']

        # Verify the client's response
        valid_response = gs_utils.generate_response(self.challenge, authtoken_parsed['challenge'], data_parsed['challenge'], data_parsed['authtoken'])
        if data_parsed['response'] != valid_response:
            self.log(logging.ERROR, "ERROR: Got invalid response. Got %s, expected %s" % (data_parsed['response'], valid_response))

        proof = gs_utils.generate_proof(self.challenge, authtoken_parsed['challenge'], data_parsed['challenge'], data_parsed['authtoken'])

        userid, profileid, gsbrcd, uniquenick = gs_utils.login_profile_via_parsed_authtoken(authtoken_parsed, self.db)

        if profileid != None:
            # Successfully logged in or created account, continue creating session.
            loginticket = gs_utils.base64_encode(utils.generate_random_str(16))
            self.sesskey = self.db.create_session(profileid, loginticket)

            self.sessions[profileid] = self

            self.buddies = self.db.get_buddy_list(self.profileid)
            self.blocked = self.db.get_blocked_list(self.profileid)

            if self.sdkrevision == "11": # Used in Tatsunoko vs Capcom
                def make_list(data):
                    list = []
                    for d in data:
                        if d['status'] == 1:
                            list.append(str(d['buddyProfileId']))
                    return list

                block_list = make_list(self.blocked)
                msg = gs_query.create_gamespy_message([
                    ('__cmd__', "blk"),
                    ('__cmd_val__', str(len(block_list))),
                    ('list', ','.join(block_list)),
                ])

                self.log(logging.DEBUG, "SENDING: %s" % msg)
                self.transport.write(bytes(msg))

                buddy_list = make_list(self.buddies)
                msg = gs_query.create_gamespy_message([
                    ('__cmd__', "bdy"),
                    ('__cmd_val__', str(len(buddy_list))),
                    ('list', ','.join(buddy_list)),
                ])

                self.log(logging.DEBUG, "SENDING: %s" % msg)
                self.transport.write(bytes(msg))


            msg = gs_query.create_gamespy_message([
                ('__cmd__', "lc"),
                ('__cmd_val__', "2"),
                ('sesskey', self.sesskey),
                ('proof', proof),
                ('userid', userid),
                ('profileid', profileid),
                ('uniquenick', uniquenick),
                # Some kind of token... don't know it gets used or generated, but it doesn't seem to have any negative effects if it's not properly generated.
                ('lt', loginticket),
                ('id', data_parsed['id']),
            ])

            # Take the first 4 letters of gsbrcd instead of gamecd because they should be consistent across game
            # regions. For example, the US version of Metroid Prime Hunters has the gamecd "AMHE" and the first 4 letters
            # of gsbrcd are "AMHE". However, the Japanese version of Metroid Prime Hunters has the gamecd "AMHJ" with
            # the first 4 letters of bsbrcd as "AMHE". Tetris DS is the other way, with the first 4 letters as the
            # Japanese version (ATRJ) while the gamecd is region specific (ATRE for US and ATRJ for JP).
            # gameid is used to send all people on the player's friends list a status updates, so don't make it region
            # specific.
            self.gameid = gsbrcd[:4]
            self.profileid = int(profileid)

            self.log(logging.DEBUG, "SENDING: %s" % msg)
            self.transport.write(bytes(msg))

            # Get pending messages.
            self.get_pending_messages()

            # Send any friend statuses when the user logs in.
            # This will allow the user to see if their friends are hosting a game as soon as they log in.
            self.get_status_from_friends()
            self.send_status_to_friends()

            # profile = self.db.get_profile_from_profileid(profileid)
            # if profile != None:
            #     self.statstring = profile['stat']
            #     self.locstring = profile['loc']
        else:
            self.log(logging.INFO, "Invalid password or banned user")
            msg = gs_query.create_gamespy_message([
                ('__cmd__', "error"),
                ('__cmd_val__', ""),
                ('err', '256'),
                ('fatal', ''),
                ('errmsg', 'Login failed.'),
                ('id', data_parsed['id']),
            ])
            self.log(logging.DEBUG, "SENDING: %s" % msg)
            self.transport.write(bytes(msg))
コード例 #30
0
    def handle_packet(self, socket, recv_data, address):
        # Tetris DS overlay 10 @ 02144184 - Handle responses back to server
        # Tetris DS overlay 10 @ 02144184 - Handle responses back to server
        #
        # After some more packet inspection, it seems the format goes something like this:
        # - All server messages seem to always start with \xfe\xfd.
        # - The first byte from the client (or third byte from the server) is a command.
        # - Bytes 2 - 5 from the client is some kind of ID. This will have to be inspected later. I believe it's a
        # session-like ID because the number changes between connections. Copying the client's ID might be enough.
        #
        # The above was as guessed.
        # The code in Tetris DS (overlay 10) @ 0216E974 handles the network command creation.
        # R1 contains the command to be sent to the server.
        # R2 contains a pointer to some unknown integer that gets written after the command.
        #
        # - Commands
        #   Commands range from 0x00 to 0x09 (for client only at least?) (Tetris DS overlay 10 @ 0216DDCC)
        #
        #   CLIENT:
        #       0x01 - Response (Tetris DS overlay 10 @ 216DCA4)
        #           Sends back base64 of RC4 encrypted string that was gotten from the server's 0x01.
        #
        #       0x03 - Send client state? (Tetris DS overlay 10 @ 216DA30)
        #           Data sent:
        #           1) Loop for each localip available on the system, write as localip%d\x00(local ip)
        #           2) localport\x00(local port)
        #           3) natneg (either 0 or 1)
        #           4) ONLY IF STATE CHANGED: statechanged\x00(state) (Possible values: 0, 1, 2, 3)
        #           5) gamename\x00(game name)
        #           6) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicip\x00(public ip)
        #           7) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicport\x00(public port)
        #
        #           if statechanged != 2:
        #               Write various other data described here: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Implementation
        #
        #       0x07 - Unknown, related to server's 0x06 (returns value sent from server)
        #
        #       0x08 - Keep alive? Sent after 0x03
        #
        #       0x09 - Availability check
        #
        #   SERVER:
        #       0x01 - Unknown
        #           Data sent:
        #           8 random ASCII characters (?) followed by the public IP and port of the client as a hex string
        #
        #       0x06 - Unknown
        #           First 4 bytes is some kind of id? I believe it's a unique identifier for the data being sent,
        #           seeing how the server can send the same IP information many times in a row. If the IP information has
        #           already been parsed then it doesn't waste time handling it.
        #
        #           After that is a "SBCM" section which is 0x14 bytes in total.
        #           SBCM information gets parsed at 2141A0C in Tetris DS overlay 10.
        #           Seems to contain IP address information.
        #
        #           The SBCM seems to contain a little information that must be parsed before.
        #           After the SBCM:
        #               \x03\x00\x00\x00 - Always the same?
        #               \x01 - Found player?
        #               \x04 - Unknown
        #               (2 bytes) - Unknown. Port?
        #               (4 bytes) - Player's IP
        #               (4 bytes) - Unknown. Some other IP? Remote server IP?
        #               \x00\x00\x00\x00 - Unknown but seems to get checked
        #
        #           Another SBCM, after a player has been found and attempting to start a game:
        #               \x03\x00\x00\x00 - Always the same?
        #               \x05 - Connecting to player?
        #               \x00 - Unknown
        #               (2 bytes) - Unknown. Port? Same as before.
        #               (4 bytes) - Player's IP
        #               (4 bytes) - Unknown. Some other IP? Remote server IP?
        #
        #       0x0a - Response to 0x01
        #           Gets sent after receiving 0x01 from the client. So, server 0x01 -> client 0x01 -> server 0x0a.
        #           Has no other data besides the client ID.
        #
        #  - \xfd\xfc commands get passed directly between the other player(s)?
        #
        #
        # Open source version of GameSpy found here: https://github.com/sfcspanky/Openspy-Core/tree/master/qr
        # Use as reference.

        session_id = None
        if recv_data[0] != '\x09':
            # Don't add a session if the client is trying to check if the game is available or not
            session_id = struct.unpack("<I", recv_data[1:5])[0]
            session_id_raw = recv_data[1:5]
            if session_id not in self.sessions:
                # Found a new session, add to session list
                self.sessions[session_id] = self.Session(address)
                self.sessions[session_id].session = session_id
                self.sessions[session_id].keepalive = int(time.time())
                self.sessions[session_id].disconnected = False

            if session_id in self.sessions and self.sessions[session_id].disconnected == True:
                return

            if session_id in self.sessions:
                self.sessions[session_id].keepalive = int(time.time()) # Make sure the server doesn't get removed

        # Handle commands
        if recv_data[0] == '\x00': # Query
            self.log(logging.DEBUG, address, session_id, "NOT IMPLEMENTED! Received query from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

        elif recv_data[0] == '\x01': # Challenge
            self.log(logging.DEBUG, address, session_id, "Received challenge from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            # Prepare the challenge sent from the server to be compared
            challenge = gs_utils.prepare_rc4_base64(self.sessions[session_id].secretkey, self.sessions[session_id].challenge)

            # Compare challenge
            client_challenge = recv_data[5:-1]
            if client_challenge == challenge:
                # Challenge succeeded
                # Send message back to client saying it was accepted
                packet = bytearray([0xfe, 0xfd, 0x0a]) # Send client registered command
                packet.extend(session_id_raw) # Get the session ID
                self.write_queue.put((packet, address))
                self.log(logging.DEBUG, address, session_id, "Sent client registered to %s:%s..." % (address[0], address[1]))

                if self.sessions[session_id].heartbeat_data != None:
                    self.update_server_list(session_id, self.sessions[session_id].heartbeat_data)

            else:
                # Failed the challenge, request another during the next heartbeat
                self.sessions[session_id].sent_challenge = False
                self.server_manager.delete_server(self.sessions[session_id].gamename, session_id)

        elif recv_data[0] == '\x02': # Echo
            self.log(logging.DEBUG, address, session_id, "NOT IMPLEMENTED! Received echo from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

        elif recv_data[0] == '\x03': # Heartbeat
            data = recv_data[5:]
            self.log(logging.DEBUG, address, session_id, "Received heartbeat from %s:%s... %s" % (address[0], address[1], data))

            # Parse information from heartbeat here
            d = data.rstrip('\0').split('\0')

            # It may be safe to ignore "unknown" keys because the proper key names get filled in later...
            k = {}
            for i in range(0, len(d), 2):
                #self.log(logging.DEBUG, address, session_id, "%s = %s" % (d[i], d[i+1]))
                k[d[i]] = d[i+1]

            if self.sessions[session_id].ingamesn != None:
                if "gamename" in k and "dwc_pid" in k:
                    try:
                        profile = self.db.get_profile_from_profileid(k['dwc_pid'])
                        naslogin = self.db.get_nas_login_from_userid(profile['userid'])
                        self.sessions[session_id].ingamesn = str(naslogin['ingamesn']) # convert to string from unicode(which is just a base64 string anyway)
                    except Exception,e:
                        pass # If the game doesn't have, don't worry about it.

                if self.sessions[session_id].ingamesn != None and "ingamesn" not in k:
                    k['ingamesn'] = self.sessions[session_id].ingamesn

            if "gamename" in k:
                if k['gamename'] in self.secret_key_list:
                    self.sessions[session_id].secretkey = self.secret_key_list[k['gamename']]
                else:
                    self.log(logging.INFO, address, session_id, "Connection from unknown game '%s'!" % k['gamename'])

            if self.sessions[session_id].playerid == 0 and "dwc_pid" in k:
                # Get the player's id and then query the profile to figure out what console they are on.
                # The endianness of some server data depends on the endianness of the console, so we must be able
                # to account for that.
                self.sessions[session_id].playerid = int(k['dwc_pid'])

                # Try to detect console without hitting the database first
                found_console = False
                if 'gamename' in k:
                    self.sessions[session_id].console = 0

                    if k['gamename'].endswith('ds') or k['gamename'].endswith('dsam') or k['gamename'].endswith('dsi') or k['gamename'].endswith('dsiam'):
                        self.sessions[session_id].console = 0
                        found_console = True
                    elif k['gamename'].endswith('wii') or k['gamename'].endswith('wiiam') or k['gamename'].endswith('wiiware') or k['gamename'].endswith('wiiwaream'):
                        self.sessions[session_id].console = 1
                        found_console = True

                if found_console == False:
                    # Couldn't detect game, try to get it from the database
                    # Try a 3 times before giving up
                    for i in range(0, 3):
                        try:
                            profile = self.db.get_profile_from_profileid(self.sessions[session_id].playerid)

                            if "console" in profile:
                                self.sessions[session_id].console = profile['console']

                            break
                        except:
                            time.sleep(0.5)

            if 'publicip' in k and k['publicip'] == "0": #and k['dwc_hoststate'] == "2": # When dwc_hoststate == 2 then it doesn't send an IP, so calculate it ourselves
                be = self.sessions[session_id].console != 0
                k['publicip'] = str(utils.get_ip(bytearray([int(x) for x in address[0].split('.')]), 0, be))

            if 'publicport' in k and 'localport' in k and k['publicport'] != k['localport']:
                self.log(logging.DEBUG, address, session_id, "publicport %s doesn't match localport %s, so changing publicport to %s..." \
                    % (k['publicport'], k['localport'], str(address[1])))
                k['publicport'] = str(address[1])

            if self.sessions[session_id].sent_challenge == True:
                self.update_server_list(session_id, k)
            else:
                addr_hex =  ''.join(["%02X" % int(x) for x in address[0].split('.')])
                port_hex = "%04X" % int(address[1])
                server_challenge = utils.generate_random_str(6) + '00' + addr_hex + port_hex

                self.sessions[session_id].challenge = server_challenge

                packet = bytearray([0xfe, 0xfd, 0x01]) # Send challenge command
                packet.extend(session_id_raw) # Get the session ID
                packet.extend(server_challenge)
                packet.extend('\x00')

                self.write_queue.put((packet, address))
                self.log(logging.DEBUG, address, session_id, "Sent challenge to %s:%s..." % (address[0], address[1]))

                self.sessions[session_id].sent_challenge = True
                self.sessions[session_id].heartbeat_data = k
コード例 #31
0
    def wait_loop(self):
        while 1:
            recv_data, address = self.socket.recvfrom(2048)

            # Tetris DS overlay 10 @ 02144184 - Handle responses back to server
            # Tetris DS overlay 10 @ 02144184 - Handle responses back to server
            #
            # After some more packet inspection, it seems the format goes something like this:
            # - All server messages seem to always start with \xfe\xfd.
            # - The first byte from the client (or third byte from the server) is a command.
            # - Bytes 2 - 5 from the client is some kind of ID. This will have to be inspected later. I believe it's a
            # session-like ID because the number changes between connections. Copying the client's ID might be enough.
            #
            # The above was as guessed.
            # The code in Tetris DS (overlay 10) @ 0216E974 handles the network command creation.
            # R1 contains the command to be sent to the server.
            # R2 contains a pointer to some unknown integer that gets written after the command.
            #
            # - Commands
            #   Commands range from 0x00 to 0x09 (for client only at least?) (Tetris DS overlay 10 @ 0216DDCC)
            #
            #   CLIENT:
            #       0x01 - Response (Tetris DS overlay 10 @ 216DCA4)
            #           Sends back base64 of RC4 encrypted string that was gotten from the server's 0x01.
            #
            #       0x03 - Send client state? (Tetris DS overlay 10 @ 216DA30)
            #           Data sent:
            #           1) Loop for each localip available on the system, write as localip%d\x00(local ip)
            #           2) localport\x00(local port)
            #           3) natneg (either 0 or 1)
            #           4) ONLY IF STATE CHANGED: statechanged\x00(state) (Possible values: 0, 1, 2, 3)
            #           5) gamename\x00(game name)
            #           6) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicip\x00(public ip)
            #           7) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicport\x00(public port)
            #
            #           if statechanged != 2:
            #               Write various other data described here: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Implementation
            #
            #       0x07 - Unknown, related to server's 0x06 (returns value sent from server)
            #
            #       0x08 - Keep alive? Sent after 0x03
            #
            #       0x09 - Availability check
            #
            #   SERVER:
            #       0x01 - Unknown
            #           Data sent:
            #           8 random ASCII characters (?) followed by the public IP and port of the client as a hex string
            #
            #       0x06 - Unknown
            #           First 4 bytes is some kind of id? I believe it's a unique identifier for the data being sent,
            #           seeing how the server can send the same IP information many times in a row. If the IP information has
            #           already been parsed then it doesn't waste time handling it.
            #
            #           After that is a "SBCM" section which is 0x14 bytes in total.
            #           SBCM information gets parsed at 2141A0C in Tetris DS overlay 10.
            #           Seems to contain IP address information.
            #
            #           The SBCM seems to contain a little information that must be parsed before.
            #           After the SBCM:
            #               \x03\x00\x00\x00 - Always the same?
            #               \x01 - Found player?
            #               \x04 - Unknown
            #               (2 bytes) - Unknown. Port?
            #               (4 bytes) - Player's IP
            #               (4 bytes) - Unknown. Some other IP? Remote server IP?
            #               \x00\x00\x00\x00 - Unknown but seems to get checked
            #
            #           Another SBCM, after a player has been found and attempting to start a game:
            #               \x03\x00\x00\x00 - Always the same?
            #               \x05 - Connecting to player?
            #               \x00 - Unknown
            #               (2 bytes) - Unknown. Port? Same as before.
            #               (4 bytes) - Player's IP
            #               (4 bytes) - Unknown. Some other IP? Remote server IP?
            #
            #       0x0a - Response to 0x01
            #           Gets sent after receiving 0x01 from the client. So, server 0x01 -> client 0x01 -> server 0x0a.
            #           Has no other data besides the client ID.
            #
            #  - \xfd\xfc commands get passed directly between the other player(s)?
            #
            #
            # Open source version of GameSpy found here: https://github.com/sfcspanky/Openspy-Core/tree/master/qr
            # Use as reference.

            if recv_data[0] != '\x09':
                # Don't add a session if the client is trying to check if the game is available or not
                session_id = struct.unpack("<I", recv_data[1:5])[0]
                session_id_raw = recv_data[1:5]
                if session_id not in self.sessions:
                    # Found a new session, add to session list
                    self.sessions[session_id] = self.Session(address)
                    self.sessions[session_id].session = session_id
                    self.sessions[session_id].keepalive = int(time.time())

            # Handle commands
            if recv_data[0] == '\x00': # Query
                self.log(logging.DEBUG, address, "NOT IMPLEMENTED! Received query from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x01': # Challenge
                self.log(logging.DEBUG, address, "Received challenge from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

                # Prepare the challenge sent from the server to be compared
                challenge = gs_utils.prepare_rc4_base64(self.sessions[session_id].secretkey, self.sessions[session_id].challenge)

                # Compare challenge
                client_challenge = recv_data[5:-1]
                if client_challenge == challenge:
                    # Challenge succeeded

                    # Send message back to client saying it was accepted
                    packet = bytearray([0xfe, 0xfd, 0x0a]) # Send client registered command
                    packet.extend(session_id_raw) # Get the session ID
                    self.socket.sendto(packet, address)
                    self.log(logging.DEBUG, address, "Sent client registered to %s:%s..." % (address[0], address[1]))
                else:
                    # Failed the challenge, request another during the next heartbeat
                    self.sessions[session_id].sent_challenge = False

            elif recv_data[0] == '\x02': # Echo
                self.log(logging.DEBUG, address, "NOT IMPLEMENTED! Received echo from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x03': # Heartbeat
                data = recv_data[5:]
                self.log(logging.DEBUG, address, "Received heartbeat from %s:%s... %s" % (address[0], address[1], data))

                # Parse information from heartbeat here
                d = data.rstrip('\0').split('\0')

                # It may be safe to ignore "unknown" keys because the proper key names get filled in later...
                k = {}
                for i in range(0, len(d), 2):
                    #self.log(logging.DEBUG, address, "%s = %s" % (d[i], d[i+1]))
                    k[d[i]] = d[i+1]

                if "gamename" in k:
                    self.sessions[session_id].secretkey = self.secret_key_list[k['gamename']]
                    #print "Got secret key %s for %s" % (self.sessions[session_id].secretkey, k['gamename'])

                if self.sessions[session_id].playerid == 0 and "dwc_pid" in k:
                    # Get the player's id and then query the profile to figure out what console they are on.
                    # The endianness of some server data depends on the endianness of the console, so we must be able
                    # to account for that.
                    self.sessions[session_id].playerid = int(k['dwc_pid'])
                    profile = self.db.get_profile_from_profileid(self.sessions[session_id].playerid)

                    if "console" in profile:
                        self.sessions[session_id].console = profile['console']


                if self.sessions[session_id].sent_challenge == False:
                    addr_hex =  ''.join(["%02X" % int(x) for x in address[0].split('.')])
                    port_hex = "%04X" % int(address[1])
                    server_challenge = utils.generate_random_str(8) + addr_hex + port_hex

                    self.sessions[session_id].challenge = server_challenge

                    packet = bytearray([0xfe, 0xfd, 0x01]) # Send challenge command
                    packet.extend(session_id_raw) # Get the session ID
                    packet.extend(server_challenge)
                    packet.extend('\x00')

                    self.socket.sendto(packet, address)
                    self.log(logging.DEBUG, address, "Sent challenge to %s:%s..." % (address[0], address[1]))

                    self.sessions[session_id].sent_challenge = True

                if 'publicip' in k and k['publicip'] == "0": #and k['dwc_hoststate'] == "2": # When dwc_hoststate == 2 then it doesn't send an IP, so calculate it ourselves
                    if self.sessions[session_id].console != 0:
                        k['publicip'] = str(ctypes.c_int32(utils.get_int_be(bytearray([int(x) for x in address[0].split('.')]), 0)).value) # Wii
                    else:
                        k['publicip'] = str(ctypes.c_int32(utils.get_int(bytearray([int(x) for x in address[0].split('.')]), 0)).value) # DS

                if "statechanged" in k:
                    if k['statechanged'] == "1": # Create server
                        #if k['publicport'] != "0" and k['publicip'] != "0":
                            # dwc_mtype controls what kind of server query we're looking for.
                            # dwc_mtype = 0 is used when looking for a matchmaking game.
                            # dwc_mtype = 1 is unknown.
                            # dwc_mtype = 2 is used when hosting a friends only game (possibly other uses too).
                            # dwc_mtype = 3 is used when looking for a friends only game (possibly other uses too).

                            # Some memory could be saved by clearing out any unwanted fields from k before sending.
                        self.server_manager.update_server_list(k['gamename'], session_id, k, self.sessions[session_id].console)._getvalue()

                        if session_id in self.sessions:
                            self.sessions[session_id].gamename = k['gamename']
                    elif k['statechanged'] == "2": # Close server
                        self.server_manager.delete_server(k['gamename'] , session_id)

                        if session_id in self.sessions:
                            self.sessions.pop(session_id)


            elif recv_data[0] == '\x04': # Add Error
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received add error from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x05': # Echo Response
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received echo response from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x06': # Client Message
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received echo from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            elif recv_data[0] == '\x07': # Client Message Ack
                #self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received client message ack from %s:%s... %s" % (address[0], address[1], recv_data[5:]))
                self.log(logging.DEBUG, address, "Received client message ack from %s:%s..." % (address[0], address[1]))

            elif recv_data[0] == '\x08': # Keep Alive
                self.log(logging.DEBUG, address, "Received keep alive from %s:%s..." % (address[0], address[1]))
                self.sessions[session_id].keepalive = int(time.time())

            elif recv_data[0] == '\x09': # Available
                # Availability check only sent to *.available.gs.nintendowifi.net
                self.log(logging.DEBUG, address, "Received availability request for '%s' from %s:%s..." % (recv_data[5: -1], address[0], address[1]))
                self.socket.sendto(bytearray([0xfe, 0xfd, 0x09, 0x00, 0x00, 0x00, 0x00]), address)

            elif recv_data[0] == '\x0a': # Client Registered
                # Only sent to client, never received?
                self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received client registered from %s:%s... %s" % (address[0], address[1], recv_data[5:]))

            else:
                self.log(logging.ERROR, address, "Unknown request from %s:%s:" % (address[0], address[1]))
                self.log(logging.DEBUG, address, utils.pretty_print_hex(recv_data))
コード例 #32
0
def handle_ac_login(handler, db, addr, post):
    """Handle ac login request."""
    if db.is_ip_banned(post):
        ret = {
            "retry": "1",
            "returncd": "3917",
            "locator": "gamespy.com",
            "reason": "User banned."
        }
        logger.log(logging.DEBUG, "Login denied for banned user %s", str(post))
    elif db.is_console_macadr_banned(post):
        ret = {
            "retry": "1",
            "returncd": "3914",
            "locator": "gamespy.com",
            "reason": "User's console banned."
        }
        logger.log(logging.DEBUG, "Login denied for "
                   "banned console %s", str(post))
    elif db.is_console_cfc_banned(post):
        ret = {
            "retry": "1",
            "returncd": "3915",
            "locator": "gamespy.com",
            "reason": "User's console banned."
        }
        logger.log(logging.DEBUG, "Login denied "
                   "for banned console "
                   "friend code %s", str(post))
    elif db.is_console_csnum_banned(post):
        ret = {
            "retry": "1",
            "returncd": "3915",
            "locator": "gamespy.com",
            "reason": "User's console banned."
        }
        logger.log(logging.DEBUG, "Login denied for banned console "
                   "serial number %s", str(post))
    elif db.console_abuse(post):
        ret = {
            "retry": "1",
            "returncd": "3915",
            "locator": "gamespy.com",
            "reason": "User's console banned due to abuse of MAC address."
        }
        logger.log(
            logging.DEBUG, "Login denied for console "
            "- too many MAC addresses used %s", str(post))
    elif not db.pending(post):
        logger.log(logging.DEBUG, "Login denied - Unknown console %s", post)
        ret = {
            "retry": "1",
            "returncd": "3921",
            "locator": "gamespy.com",
        }
    elif not db.registered(post):
        logger.log(logging.DEBUG, "Login denied - console pending %s", post)
        ret = {
            "retry": "1",
            "returncd": "3888",
            "locator": "gamespy.com",
        }
    elif not db.allowed_games(post):
        logger.log(logging.DEBUG, "Login denied - Game not enabled %s", post)
        ret = {
            "retry": "1",
            "returncd": "3800",
            "locator": "gamespy.com",
        }
    else:
        challenge = utils.generate_random_str(8)
        post["challenge"] = challenge

        authtoken = db.generate_authtoken(post["userid"], post)
        ret = {
            "retry": "0",
            "returncd": "001",
            "locator": "gamespy.com",
            "challenge": challenge,
            "token": authtoken,
        }

        logger.log(logging.DEBUG, "Login response to %s:%d", *addr)
        logger.log(logging.DEBUG, "%s", ret)

    return ret