From 5a2bca3a5735ef0974fefb92b4c8f59163c56ada Mon Sep 17 00:00:00 2001 From: polaris Date: Wed, 28 May 2014 11:26:58 -0400 Subject: [PATCH] Added some exception catching for debugging --- gamespy_gamestats_server.py | 71 ++++--- gamespy_natneg_server.py | 24 ++- gamespy_player_search_server.py | 22 ++- gamespy_profile_server.py | 122 ++++++------ gamespy_qr_server.py | 50 ++--- gamespy_server_browser_server.py | 276 +++++++++++++------------- nas_server.py | 329 ++++++++++++++++--------------- 7 files changed, 467 insertions(+), 427 deletions(-) diff --git a/gamespy_gamestats_server.py b/gamespy_gamestats_server.py index f8d5497..03c09ce 100644 --- a/gamespy_gamestats_server.py +++ b/gamespy_gamestats_server.py @@ -1,5 +1,6 @@ import logging import time +import traceback from twisted.internet.protocol import Factory from twisted.internet.endpoints import serverFromString @@ -75,52 +76,58 @@ class Gamestats(LineReceiver): logger.log(level, "[%s:%d | %s | %s] %s", self.address.host, self.address.port, self.session, self.gameid, message) def connectionMade(self): - self.log(logging.INFO, "Received connection from %s:%d" % (self.address.host, self.address.port)) + 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") + # 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"), - ]) + # 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.log(logging.DEBUG, "SENDING: '%s'..." % msg) - msg = self.crypt(msg) - self.transport.write(bytes(msg)) + msg = self.crypt(msg) + self.transport.write(bytes(msg)) + except: + self.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) def connectionLost(self, reason): return def rawDataReceived(self, data): - # Decrypt packet - msg = self.remaining_message + str(self.crypt(data)) - self.data = msg + try: + # Decrypt packet + msg = self.remaining_message + str(self.crypt(data)) + self.data = msg - commands, self.remaining_message = gs_query.parse_gamespy_message(msg) - #logger.log(logging.DEBUG, "STATS RESPONSE: %s" % msg) + commands, self.remaining_message = gs_query.parse_gamespy_message(msg) + #logger.log(logging.DEBUG, "STATS RESPONSE: %s" % msg) - cmds = { - "auth": self.perform_auth, - "authp": self.perform_authp, - "ka": self.perform_ka, - "setpd": self.perform_setpd, - "getpd": self.perform_getpd, - "newgame": self.perform_newgame, - "updgame": self.perform_updgame, - } + cmds = { + "auth": self.perform_auth, + "authp": self.perform_authp, + "ka": self.perform_ka, + "setpd": self.perform_setpd, + "getpd": self.perform_getpd, + "newgame": self.perform_newgame, + "updgame": self.perform_updgame, + } - def cmd_err(data_parsed): - logger.log(logging.DEBUG, "Found unknown command, don't know how to handle '%s'.", data_parsed['__cmd__']) + def cmd_err(data_parsed): + logger.log(logging.DEBUG, "Found unknown command, don't know how to handle '%s'.", data_parsed['__cmd__']) - for data_parsed in commands: - print(data_parsed) + for data_parsed in commands: + print(data_parsed) - cmds.get(data_parsed['__cmd__'], cmd_err)(data_parsed) + cmds.get(data_parsed['__cmd__'], cmd_err)(data_parsed) + except: + self.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) def perform_auth(self, data_parsed): self.log(logging.DEBUG, "Parsing 'auth'...") diff --git a/gamespy_natneg_server.py b/gamespy_natneg_server.py index 35c55c2..33c9cc9 100644 --- a/gamespy_natneg_server.py +++ b/gamespy_natneg_server.py @@ -10,6 +10,7 @@ import time import Queue import gamespy.gs_utility as gs_utils import other.utils as utils +import traceback from multiprocessing.managers import BaseManager @@ -43,21 +44,24 @@ class GameSpyNatNegServer(object): self.server_manager.connect() def start(self): - # Start natneg server - address = ('0.0.0.0', 27901) # accessible to outside connections (use this if you don't know what you're doing) + try: + # Start natneg server + address = ('0.0.0.0', 27901) # accessible to outside connections (use this if you don't know what you're doing) - self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.socket.bind(address) + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket.bind(address) - self.write_queue = Queue.Queue(); + self.write_queue = Queue.Queue(); - logger.log(logging.INFO, "Server is now listening on %s:%s..." % (address[0], address[1])) - threading.Thread(target=self.write_queue_worker).start() + logger.log(logging.INFO, "Server is now listening on %s:%s..." % (address[0], address[1])) + threading.Thread(target=self.write_queue_worker).start() - while 1: - recv_data, addr = self.socket.recvfrom(2048) + while 1: + recv_data, addr = self.socket.recvfrom(2048) - self.handle_packet(recv_data, addr) + self.handle_packet(recv_data, addr) + except: + logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) def write_queue_send(self, data, address): time.sleep(0.05) diff --git a/gamespy_player_search_server.py b/gamespy_player_search_server.py index cb65789..1c4ea29 100644 --- a/gamespy_player_search_server.py +++ b/gamespy_player_search_server.py @@ -1,4 +1,5 @@ import logging +import traceback from twisted.internet.protocol import Factory from twisted.internet.endpoints import serverFromString @@ -57,18 +58,21 @@ class PlayerSearch(LineReceiver): pass def rawDataReceived(self, data): - logger.log(logging.DEBUG, "SEARCH RESPONSE: %s" % data) + try: + logger.log(logging.DEBUG, "SEARCH RESPONSE: %s" % data) - data = self.leftover + data - commands, self.leftover = gs_query.parse_gamespy_message(data) + data = self.leftover + data + commands, self.leftover = gs_query.parse_gamespy_message(data) - for data_parsed in commands: - print data_parsed + for data_parsed in commands: + print data_parsed - if data_parsed['__cmd__'] == "otherslist": - self.perform_otherslist(data_parsed) - else: - logger.log(logging.DEBUG, "Found unknown search command, don't know how to handle '%s'." % data_parsed['__cmd__']) + if data_parsed['__cmd__'] == "otherslist": + self.perform_otherslist(data_parsed) + else: + logger.log(logging.DEBUG, "Found unknown search command, don't know how to handle '%s'." % data_parsed['__cmd__']) + except: + logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) def perform_otherslist(self, data_parsed): # Reference: http://wiki.tockdom.com/wiki/MKWii_Network_Protocol/Server/gpsp.gs.nintendowifi.net diff --git a/gamespy_profile_server.py b/gamespy_profile_server.py index c40b5af..6c055e1 100644 --- a/gamespy_profile_server.py +++ b/gamespy_profile_server.py @@ -1,5 +1,6 @@ import logging import time +import traceback from twisted.internet.protocol import Factory from twisted.internet.endpoints import serverFromString @@ -89,79 +90,88 @@ class PlayerSession(LineReceiver): return ipaddress def connectionMade(self): - self.transport.setTcpKeepAlive(1) + try: + self.transport.setTcpKeepAlive(1) - self.log(logging.INFO, "Received connection from %s:%d" % (self.address.host, self.address.port)) + self.log(logging.INFO, "Received connection from %s:%d" % (self.address.host, self.address.port)) - # Create new session id - self.session = "" + # Create new session id + self.session = "" - # Generate a random challenge string - self.challenge = utils.generate_random_str(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") + # 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"), - ]) + # 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)) + self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.transport.write(bytes(msg)) + except: + self.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) def connectionLost(self, reason): - self.log(logging.INFO, "Client disconnected") + try: + self.log(logging.INFO, "Client disconnected") - self.status = "0" - self.statstring = "Offline" - self.locstring = "" - self.send_status_to_friends() + self.status = "0" + self.statstring = "Offline" + self.locstring = "" + self.send_status_to_friends() - if self.profileid in self.sessions: - del self.sessions[self.profileid] + if self.profileid in self.sessions: + del self.sessions[self.profileid] - self.db.delete_session(self.session) - self.log(logging.INFO, "Deleted session " + self.session) + self.db.delete_session(self.session) + self.log(logging.INFO, "Deleted session " + self.session) + except: + self.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) def rawDataReceived(self, data): - self.log(logging.DEBUG, "RESPONSE: '%s'..." % data) + try: + self.log(logging.DEBUG, "RESPONSE: '%s'..." % data) - # In the case where command string is too big to fit into one read, any parts that could not be successfully - # parsed are stored in the variable remaining_message. On the next rawDataReceived command, the remaining - # message and the data are combined to create a full command. - data = self.remaining_message + data + # In the case where command string is too big to fit into one read, any parts that could not be successfully + # parsed are stored in the variable remaining_message. On the next rawDataReceived command, the remaining + # message and the data are combined to create a full command. + data = self.remaining_message + data - # Check to make sure the data buffer starts with a valid command. - if len(data) > 0 and data[0] != '\\': - # There is data in the buffer but it doesn't start with a \ so there's no chance of it being valid. - # Look for the first instance of \final\ and remove everything before it. - # If \final\ is not in the command string then ignore it. - final = "\\final\\" - data = data[data.index(final) + len(final):] if final in data else "" + # Check to make sure the data buffer starts with a valid command. + if len(data) > 0 and data[0] != '\\': + # There is data in the buffer but it doesn't start with a \ so there's no chance of it being valid. + # Look for the first instance of \final\ and remove everything before it. + # If \final\ is not in the command string then ignore it. + final = "\\final\\" + data = data[data.index(final) + len(final):] if final in data else "" - commands, self.remaining_message = gs_query.parse_gamespy_message(data) + commands, self.remaining_message = gs_query.parse_gamespy_message(data) - cmds = { - "login": self.perform_login, - "logout": self.perform_logout, - "getprofile": self.perform_getprofile, - "updatepro": self.perform_updatepro, - "ka": self.perform_ka, - "status": self.perform_status, - "bm": self.perform_bm, - "addbuddy": self.perform_addbuddy, - "delbuddy": self.perform_delbuddy, - "authadd": self.perform_authadd, - } - def cmd_err(data_parsed): - # Maybe write unknown commands to a separate file later so new data can be collected more easily? - self.log(logging.ERROR, "Found unknown command, don't know how to handle '%s'." % data_parsed['__cmd__']) + cmds = { + "login": self.perform_login, + "logout": self.perform_logout, + "getprofile": self.perform_getprofile, + "updatepro": self.perform_updatepro, + "ka": self.perform_ka, + "status": self.perform_status, + "bm": self.perform_bm, + "addbuddy": self.perform_addbuddy, + "delbuddy": self.perform_delbuddy, + "authadd": self.perform_authadd, + } + def cmd_err(data_parsed): + # Maybe write unknown commands to a separate file later so new data can be collected more easily? + self.log(logging.ERROR, "Found unknown command, don't know how to handle '%s'." % data_parsed['__cmd__']) - for data_parsed in commands: - #self.log(-1, data_parsed) - self.log(logging.DEBUG, data_parsed) - cmds.get(data_parsed['__cmd__'], cmd_err)(data_parsed) + for data_parsed in commands: + #self.log(-1, data_parsed) + self.log(logging.DEBUG, data_parsed) + cmds.get(data_parsed['__cmd__'], cmd_err)(data_parsed) + except: + self.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) diff --git a/gamespy_qr_server.py b/gamespy_qr_server.py index 1df349b..9fed770 100644 --- a/gamespy_qr_server.py +++ b/gamespy_qr_server.py @@ -9,6 +9,7 @@ import threading import time import ctypes import Queue +import traceback from multiprocessing.managers import BaseManager @@ -60,38 +61,41 @@ class GameSpyQRServer(object): logger.log(level, "[%s:%d] %s", address[0], address[1], message) def start(self): - manager_address = ("127.0.0.1", 27500) - manager_password = "" + try: + manager_address = ("127.0.0.1", 27500) + manager_password = "" - self.server_manager = GameSpyServerDatabase(address = manager_address, authkey= manager_password) - self.server_manager.connect() + self.server_manager = GameSpyServerDatabase(address = manager_address, authkey= manager_password) + self.server_manager.connect() - # Start QR server - address = ('0.0.0.0', 27900) # accessible to outside connections (use this if you don't know what you're doing) + # Start QR server + address = ('0.0.0.0', 27900) # accessible to outside connections (use this if you don't know what you're doing) - self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.socket.bind(address) - self.socket.setblocking(0) + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket.bind(address) + self.socket.setblocking(0) - logger.log(logging.INFO, "Server is now listening on %s:%s..." % (address[0], address[1])) + logger.log(logging.INFO, "Server is now listening on %s:%s..." % (address[0], address[1])) - # Dependencies! I don't really like this solution but it's easier than trying to manage it another way. - server_browser_server = GameSpyServerBrowserServer(self) - server_browser_server_thread = threading.Thread(target=server_browser_server.start) - server_browser_server_thread.start() + # Dependencies! I don't really like this solution but it's easier than trying to manage it another way. + server_browser_server = GameSpyServerBrowserServer(self) + server_browser_server_thread = threading.Thread(target=server_browser_server.start) + server_browser_server_thread.start() - self.write_queue = Queue.Queue(); - self.db = gs_database.GamespyDatabase() - threading.Thread(target=self.write_queue_worker).start() + self.write_queue = Queue.Queue() + self.db = gs_database.GamespyDatabase() + threading.Thread(target=self.write_queue_worker).start() - while 1: - ready = select.select([self.socket], [], [], 15) + while 1: + ready = select.select([self.socket], [], [], 15) - if ready[0]: - recv_data, address = self.socket.recvfrom(2048) - self.handle_packet(self.socket, recv_data, address) + if ready[0]: + recv_data, address = self.socket.recvfrom(2048) + self.handle_packet(self.socket, recv_data, address) - self.keepalive_check() + self.keepalive_check() + except: + logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) def write_queue_send(self, data, address): time.sleep(0.05) diff --git a/gamespy_server_browser_server.py b/gamespy_server_browser_server.py index a2e5e29..aea54d2 100644 --- a/gamespy_server_browser_server.py +++ b/gamespy_server_browser_server.py @@ -4,6 +4,7 @@ import logging import socket import ctypes +import traceback from twisted.internet.protocol import Factory from twisted.internet.endpoints import serverFromString @@ -97,143 +98,146 @@ class Session(LineReceiver): logger.log(level, "[%s:%d] %s", self.address.host, self.address.port,message) def rawDataReceived(self, data): - # First 2 bytes are the packet size. - # - # Third byte is the command byte. - # According to Openspy-Core: - # 0x00 - Server list request - # 0x01 - Server info request - # 0x02 - Send message request - # 0x03 - Keep alive reply - # 0x04 - Map loop request (?) - # 0x05 - Player search request - # - # For Tetris DS, at the very least 0x00 and 0x02 need to be implemented. - if self.forward_to_client: - if self.forward_packet == None: - self.forward_packet = data + try: + # First 2 bytes are the packet size. + # + # Third byte is the command byte. + # According to Openspy-Core: + # 0x00 - Server list request + # 0x01 - Server info request + # 0x02 - Send message request + # 0x03 - Keep alive reply + # 0x04 - Map loop request (?) + # 0x05 - Player search request + # + # For Tetris DS, at the very least 0x00 and 0x02 need to be implemented. + if self.forward_to_client: + if self.forward_packet == None: + self.forward_packet = data + else: + self.forward_packet += data + + if self.header_length + len(self.forward_packet) >= self.expected_packet_length: + # Is it possible that multiple packets will need to be waited for? + # Is it possible that more data will be in the last packet than expected? + self.forward_data_to_client(self.forward_packet, self.forward_client) + + self.forward_to_client = False + self.forward_client = None + self.header_length = 0 + self.expected_packet_length = 0 + self.forward_packet = None + return + + if data[2] == '\x00': # Server list request + self.log(logging.DEBUG, "Received server list request from %s:%s..." % (self.address.host, self.address.port)) + + # This code is so... not python. The C programmer in me is coming out strong. + # TODO: Rewrite this section later? + idx = 3 + list_version = ord(data[idx]) + idx += 1 + encoding_version = ord(data[idx]) + idx += 1 + game_version = utils.get_int(data, idx) + idx += 4 + + query_game = utils.get_string(data, idx) + idx += len(query_game) + 1 + game_name = utils.get_string(data, idx) + idx += len(game_name) + 1 + + challenge = data[idx:idx+8] + idx += 8 + + filter = utils.get_string(data, idx) + idx += len(filter) + 1 + fields = utils.get_string(data, idx) + idx += len(fields) + 1 + + options = utils.get_int_be(data, idx) + idx += 4 + + source_ip = 0 + max_servers = 0 + + NO_SERVER_LIST = 0x02 + ALTERNATE_SOURCE_IP = 0x08 + LIMIT_RESULT_COUNT = 0x80 + + send_ip = False + if (options & LIMIT_RESULT_COUNT): + max_servers = utils.get_int(data, idx) + elif (options & ALTERNATE_SOURCE_IP): + source_ip = utils.get_int(data, idx) + elif (options & NO_SERVER_LIST): + send_ip = True + + if '\\' in fields: + fields = [x for x in fields.split('\\') if x and not x.isspace()] + + #print "%02x %02x %08x" % (list_version, encoding_version, game_version) + #print "%s" % query_game + #print "%s" % game_name + #print "%s" % challenge + #print "%s" % filter + #print "%s" % fields + + #print "%08x" % options + #print "%d %08x" % (max_servers, source_ip) + + self.log(logging.DEBUG, "list version: %02x / encoding version: %02x / game version: %08x / query game: %s / game name: %s / challenge: %s / filter: %s / fields: %s / options: %08x / max servers: %d / source ip: %08x" % (list_version, encoding_version, game_version, query_game, game_name, challenge, filter, fields, options, max_servers, source_ip)) + + # Requesting ip and port of client, not server + if filter == "" or fields == "" or send_ip == True: + output = bytearray([int(x) for x in self.address.host.split('.')]) + output += utils.get_bytes_from_short_be(6500) # Does this ever change? + + enc = gs_utils.EncTypeX() + output_enc = enc.encrypt(self.secret_key_list[game_name], challenge, output) + + self.transport.write(bytes(output_enc)) + + self.log(logging.DEBUG, "Responding with own IP and game port...") + self.log(logging.DEBUG, utils.pretty_print_hex(output)) + else: + self.find_server(query_game, filter, fields, max_servers, game_name, challenge) + + + + elif data[2] == '\x02': # Send message request + packet_len = utils.get_short_be(data, 0) + dest_addr = '.'.join(["%d" % ord(x) for x in data[3:7]]) + dest_port = utils.get_short_be(data, 7) # What's the pythonic way to do this? unpack? + dest = (dest_addr, dest_port) + + self.log(logging.DEBUG, "Received send message request from %s:%s to %s:%d... expecting %d byte packet." % (self.address.host, self.address.port, dest_addr, dest_port, packet_len)) + self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data))) + + if packet_len == len(data): + # Contains entire packet, send immediately. + self.forward_data_to_client(data[3:], dest) + + self.forward_to_client = False + self.forward_client = None + self.header_length = 0 + self.expected_packet_length = 0 + self.forward_packet = None + else: + self.forward_to_client = True + self.forward_client = dest + self.header_length = len(data) + self.expected_packet_length = packet_len + + elif data[2] == '\x03': # Keep alive reply + self.log(logging.DEBUG, "Received keep alive from %s:%s..." % (self.address.host, self.address.port)) + else: - self.forward_packet += data - - if self.header_length + len(self.forward_packet) >= self.expected_packet_length: - # Is it possible that multiple packets will need to be waited for? - # Is it possible that more data will be in the last packet than expected? - self.forward_data_to_client(self.forward_packet, self.forward_client) - - self.forward_to_client = False - self.forward_client = None - self.header_length = 0 - self.expected_packet_length = 0 - self.forward_packet = None - return - - if data[2] == '\x00': # Server list request - self.log(logging.DEBUG, "Received server list request from %s:%s..." % (self.address.host, self.address.port)) - - # This code is so... not python. The C programmer in me is coming out strong. - # TODO: Rewrite this section later? - idx = 3 - list_version = ord(data[idx]) - idx += 1 - encoding_version = ord(data[idx]) - idx += 1 - game_version = utils.get_int(data, idx) - idx += 4 - - query_game = utils.get_string(data, idx) - idx += len(query_game) + 1 - game_name = utils.get_string(data, idx) - idx += len(game_name) + 1 - - challenge = data[idx:idx+8] - idx += 8 - - filter = utils.get_string(data, idx) - idx += len(filter) + 1 - fields = utils.get_string(data, idx) - idx += len(fields) + 1 - - options = utils.get_int_be(data, idx) - idx += 4 - - source_ip = 0 - max_servers = 0 - - NO_SERVER_LIST = 0x02 - ALTERNATE_SOURCE_IP = 0x08 - LIMIT_RESULT_COUNT = 0x80 - - send_ip = False - if (options & LIMIT_RESULT_COUNT): - max_servers = utils.get_int(data, idx) - elif (options & ALTERNATE_SOURCE_IP): - source_ip = utils.get_int(data, idx) - elif (options & NO_SERVER_LIST): - send_ip = True - - if '\\' in fields: - fields = [x for x in fields.split('\\') if x and not x.isspace()] - - #print "%02x %02x %08x" % (list_version, encoding_version, game_version) - #print "%s" % query_game - #print "%s" % game_name - #print "%s" % challenge - #print "%s" % filter - #print "%s" % fields - - #print "%08x" % options - #print "%d %08x" % (max_servers, source_ip) - - self.log(logging.DEBUG, "list version: %02x / encoding version: %02x / game version: %08x / query game: %s / game name: %s / challenge: %s / filter: %s / fields: %s / options: %08x / max servers: %d / source ip: %08x" % (list_version, encoding_version, game_version, query_game, game_name, challenge, filter, fields, options, max_servers, source_ip)) - - # Requesting ip and port of client, not server - if filter == "" or fields == "" or send_ip == True: - output = bytearray([int(x) for x in self.address.host.split('.')]) - output += utils.get_bytes_from_short_be(6500) # Does this ever change? - - enc = gs_utils.EncTypeX() - output_enc = enc.encrypt(self.secret_key_list[game_name], challenge, output) - - self.transport.write(bytes(output_enc)) - - self.log(logging.DEBUG, "Responding with own IP and game port...") - self.log(logging.DEBUG, utils.pretty_print_hex(output)) - else: - self.find_server(query_game, filter, fields, max_servers, game_name, challenge) - - - - elif data[2] == '\x02': # Send message request - packet_len = utils.get_short_be(data, 0) - dest_addr = '.'.join(["%d" % ord(x) for x in data[3:7]]) - dest_port = utils.get_short_be(data, 7) # What's the pythonic way to do this? unpack? - dest = (dest_addr, dest_port) - - self.log(logging.DEBUG, "Received send message request from %s:%s to %s:%d... expecting %d byte packet." % (self.address.host, self.address.port, dest_addr, dest_port, packet_len)) - self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data))) - - if packet_len == len(data): - # Contains entire packet, send immediately. - self.forward_data_to_client(data[3:], dest) - - self.forward_to_client = False - self.forward_client = None - self.header_length = 0 - self.expected_packet_length = 0 - self.forward_packet = None - else: - self.forward_to_client = True - self.forward_client = dest - self.header_length = len(data) - self.expected_packet_length = packet_len - - elif data[2] == '\x03': # Keep alive reply - self.log(logging.DEBUG, "Received keep alive from %s:%s..." % (self.address.host, self.address.port)) - - else: - self.log(logging.DEBUG, "Received unknown command (%02x) from %s:%s..." % (ord(data[2]), self.address.host, self.address.port)) - self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data))) - self.log(logging.DEBUG, utils.pretty_print_hex(data)) + self.log(logging.DEBUG, "Received unknown command (%02x) from %s:%s..." % (ord(data[2]), self.address.host, self.address.port)) + self.log(logging.DEBUG, utils.pretty_print_hex(bytearray(data))) + self.log(logging.DEBUG, utils.pretty_print_hex(data)) + except: + self.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) def get_game_id(self, data): game_id = data[5: -1] diff --git a/nas_server.py b/nas_server.py index cc9f3c9..1672023 100644 --- a/nas_server.py +++ b/nas_server.py @@ -6,6 +6,7 @@ import urlparse import BaseHTTPServer import os import random +import traceback import gamespy.gs_database as gs_database import gamespy.gs_utility as gs_utils @@ -40,208 +41,214 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): return "Nintendo Wii (http)" def do_GET(self): - # conntest server - self.send_response(200) - self.send_header("Content-type", "text/html") - self.send_header("X-Organization", "Nintendo") - self.send_header("Server", "BigIP") - self.end_headers() - self.wfile.write("ok") + try: + # conntest server + self.send_response(200) + self.send_header("Content-type", "text/html") + self.send_header("X-Organization", "Nintendo") + self.send_header("Server", "BigIP") + self.end_headers() + self.wfile.write("ok") + except: + logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) def do_POST(self): - length = int(self.headers['content-length']) - post = self.str_to_dict(self.rfile.read(length)) - ret = '' + 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 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" + 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) + logger.log(logging.DEBUG, "acctcreate response to %s", self.client_address) + logger.log(logging.DEBUG, ret) - ret = self.dict_to_str(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 + 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) + logger.log(logging.DEBUG, "login response to %s", self.client_address) + logger.log(logging.DEBUG, ret) - ret = self.dict_to_str(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) + 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 + 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] + # 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: + if post["svc"] == 9000: + ret["token"] = authtoken + else: + ret["servicetoken"] = authtoken + elif post["svc"] == "0000": # Pokemon requests this for some things ret["servicetoken"] = authtoken - elif post["svc"] == "0000": # Pokemon requests this for some things - ret["servicetoken"] = authtoken - ret["svchost"] = "n/a" + ret["svchost"] = "n/a" - logger.log(logging.DEBUG, "svcloc response to %s", self.client_address) + 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) - else: - logger.log(logging.WARNING, "Unknown action request %s from %s!", self.path, self.client_address) - ret = '' + elif self.path == "/download": + logger.log(logging.DEBUG, "Request to %s from %s", self.path, self.client_address) + logger.log(logging.DEBUG, post) - elif self.path == "/pr": - logger.log(logging.DEBUG, "Request to %s from %s", self.path, self.client_address) - logger.log(logging.DEBUG, post) - ret = {} + action = post["action"] - 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") + ret = "" + dlcdir = os.path.abspath('dlc') + dlcpath = os.path.abspath("dlc/" + post["gamecd"]) + dlc_contenttype = False - self.send_response(200) - self.send_header("Content-type", "text/plain") - self.send_header("NODE", "wifiappe1") + if os.path.commonprefix([dlcdir, dlcpath]) != dlcdir: + logging.log(logging.WARNING, 'Attempted directory traversal attack "%s", cancelling.', dlcpath) + self.send_response(403) + return - logger.log(logging.DEBUG, "pr response to %s", self.client_address) - logger.log(logging.DEBUG, ret) + def safeloadfi(fn, mode='rb'): + ''' + safeloadfi : string -> string - ret = self.dict_to_str(ret) + 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() - elif self.path == "/download": - logger.log(logging.DEBUG, "Request to %s from %s", self.path, self.client_address) - logger.log(logging.DEBUG, post) + if action == "count": + if post["gamecd"] in gamecodes_return_random_file: + ret = "1" + else: + count = 0 - action = post["action"] + if os.path.exists(dlcpath): + count = len(os.listdir(dlcpath)) - 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 + 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"] - def safeloadfi(fn, mode='rb'): - ''' - safeloadfi : string -> string + dlcfi = safeloadfi("_list.txt") + lst = self.filter_list(dlcfi, attr1, attr2, attr3) + count = self.get_file_count(lst) - 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() + ret = "%d" % count - if action == "count": - if post["gamecd"] in gamecodes_return_random_file: - ret = "1" - else: - count = 0 + 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): - count = len(os.listdir(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 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"] + if post["gamecd"] in gamecodes_return_random_file: + ret = self.filter_list_random_files(ret, 1) - dlcfi = safeloadfi("_list.txt") - lst = self.filter_list(dlcfi, attr1, attr2, attr3) - count = self.get_file_count(lst) + 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) - ret = "%d" % count + self.send_response(200) - if action == "list": - num = int(post["num"]) - offset = int(post["offset"]) + 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") - 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"] + self.send_header("X-DLS-Host", "http://127.0.0.1/") - 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) + logger.log(logging.DEBUG, "download response to %s", self.client_address) - 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"]) - 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"] + "\"") + #if dlc_contenttype == False: + # logger.log(logging.DEBUG, ret) else: - self.send_header("Content-type", "text/plain") + logger.log(logging.WARNING, "Unknown path request %s from %s!", self.path, self.client_address) - 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) + 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 str_to_dict(self, str): ret = urlparse.parse_qs(str)