diff --git a/gamespy/gs_database.py b/gamespy/gs_database.py index 606fb7b..a901780 100644 --- a/gamespy/gs_database.py +++ b/gamespy/gs_database.py @@ -52,6 +52,10 @@ class GamespyDatabase(object): logger.log(-1, q) c.execute(q) + q = "CREATE TABLE nas_logins (userid TEXT, authtoken TEXT, data TEXT)" + logger.log(-1, q) + c.execute(q) + self.conn.commit() def get_dict(self, row): @@ -361,6 +365,58 @@ class GamespyDatabase(object): return sessions + # nas server functions + def get_nas_login(self, authtoken): + q = "SELECT data FROM nas_logins WHERE authtoken = ?" + q2 = q.replace("?", "%s") % (authtoken) + logger.log(-1, q2) + + c = self.conn.cursor() + c.execute(q, [authtoken]) + r = self.get_dict(c.fetchone()) + c.close() + + if r == None: + return None + else: + return r["data"] + + def generate_authtoken(self, userid, data): + size = 128 + 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 LIKE ?" + q2 = q.replace("?", "%s") % (userid) + logger.log(-1, q2) + + c.execute(q, [authtoken]) + r = self.get_dict(c.fetchone()) + + 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 + + # Buddy functions def add_buddy(self, userProfileId, buddyProfileId): now = int(time.time()) @@ -502,9 +558,9 @@ class GamespyDatabase(object): c.execute(q, [data, profileid, dindex, ptype]) self.conn.commit() - def pd_get(self, profileid, dindex, ptype, data): + def pd_get(self, profileid, dindex, ptype): q = "SELECT * FROM gamestat_profile WHERE profileid = ? AND dindex = ? AND ptype = ?" - q2 = q.replace("?", "%s") % (dindex, ptype, data) + q2 = q.replace("?", "%s") % (profileid, dindex, ptype) logger.log(-1, q2) c = self.conn.cursor() diff --git a/gamespy/gs_utility.py b/gamespy/gs_utility.py index ca361c5..19eb5c8 100644 --- a/gamespy/gs_utility.py +++ b/gamespy/gs_utility.py @@ -1,8 +1,9 @@ import base64 import hashlib +import json import time -import other.utils as utils +import other.utils as utils def generate_secret_keys(filename="gslist.cfg"): key_file = open(filename) @@ -64,20 +65,15 @@ def prepare_rc4_base64(_key, _data): data.append(0) return base64.b64encode(buffer(data)) -# Parse my custom authtoken generated by the emulated nas.nintendowifi.net/ac -def parse_authtoken(authtoken): +# get the login data from nas.nintendowifi.net/ac from an authtoken +def parse_authtoken(authtoken, db): messages = {} + nas_data = db.get_nas_login(authtoken) - if authtoken[:3] == "NDS": - authtoken = authtoken[3:] + if nas_data == None: + return None - dec = base64.standard_b64decode(authtoken) - - for item in dec.split('|'): - s = item.split('\\') - messages[s[0]] = s[1] - - return messages + return json.loads(nas_data) def generate_response(challenge, ac_challenge, secretkey, authtoken): diff --git a/gamespy_gamestats_server.py b/gamespy_gamestats_server.py index c603153..22b03d1 100644 --- a/gamespy_gamestats_server.py +++ b/gamespy_gamestats_server.py @@ -146,7 +146,7 @@ class Gamestats(LineReceiver): self.transport.write(bytes(msg)) def perform_authp(self, data_parsed): - authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken']) + authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken'], self.db) #print authtoken_parsed if "lid" in data_parsed: diff --git a/gamespy_profile_server.py b/gamespy_profile_server.py index 139b089..0ed0988 100644 --- a/gamespy_profile_server.py +++ b/gamespy_profile_server.py @@ -154,7 +154,7 @@ class PlayerSession(LineReceiver): self.log(logging.ERROR, "Found unknown command, don't know how to handle '%s'." % data_parsed['__cmd__']) def perform_login(self, data_parsed): - authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken']) + 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 diff --git a/master_server.py b/master_server.py index 5e2834b..10dc3c8 100644 --- a/master_server.py +++ b/master_server.py @@ -5,6 +5,7 @@ from gamespy_natneg_server import GameSpyNatNegServer from gamespy_qr_server import GameSpyQRServer from gamespy_server_browser_server import GameSpyServerBrowserServer from gamespy_gamestats_server import GameSpyGamestatsServer +from nintendo_nas_server import NintendoNasServer import threading @@ -36,6 +37,10 @@ def start_natneg_server(): natneg_server = GameSpyNatNegServer() natneg_server.start() +def start_nas_server(): + nas_server = NintendoNasServer() + nas_server.start() + if __name__ == "__main__": backend_server_thread = threading.Thread(target=start_backend_server) backend_server_thread.start() @@ -56,4 +61,7 @@ if __name__ == "__main__": #server_browser_server_thread.start() natneg_server_thread = threading.Thread(target=start_natneg_server) - natneg_server_thread.start() \ No newline at end of file + natneg_server_thread.start() + + nas_server_thread = threading.Thread(target=start_nas_server) + nas_server_thread.start() diff --git a/nintendo_nas_server.py b/nintendo_nas_server.py new file mode 100644 index 0000000..65ce707 --- /dev/null +++ b/nintendo_nas_server.py @@ -0,0 +1,95 @@ +import base64 +import json +import logging +import time +import urllib +import urlparse +import BaseHTTPServer + +import gamespy.gs_database as gs_database +import gamespy.gs_utility as gs_utils +import other.utils as utils + +logger_output_to_console = True +logger_output_to_file = True +logger_name = "NintendoNasServer" +logger_filename = "nintendo_nas_server.log" +logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) + +#address = ("0.0.0.0", 80) +address = ("127.0.0.1", 9000) + +class NintendoNasServer(object): + def start(self): + httpd = NintendoNasHTTPServer((address[0], address[1]), NintendoNasHTTPServerHandler) + logger.log(logging.INFO, "Now listening for connections on %s:%d...", address[0], address[1]) + httpd.serve_forever() + +class NintendoNasHTTPServer(BaseHTTPServer.HTTPServer): + def __init__(self, server_address, RequestHandlerClass): + self.db = gs_database.GamespyDatabase() + BaseHTTPServer.HTTPServer.__init__(self, server_address, RequestHandlerClass) + +class NintendoNasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): + 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", "*")) + + #elif self.path == "/pr": + # TODO + #elif self.path == "/download": + # TODO + + def base64_dec(self, data): + return base64.b64decode(data.replace("*", "=")) + + def base64_enc(self, data): + return base64.b64encode(data).replace("=", "*") + +if __name__ == "__main__": + nas = NintendoNasServer() + nas.start()