From ec361efc5a17f61c4e46e10784757bcf59743345 Mon Sep 17 00:00:00 2001 From: Kyle Warwick-Mathieu Date: Mon, 4 May 2015 02:31:37 -0400 Subject: [PATCH 1/7] Re-did admin page to support new ban system Sorry AdmiralCurtis but your fixes focussed a lot on the new logic and we couldn't really work with it. Sorry if we wasted your time Revert "Re-did admin page to support new ban system" This reverts commit c4cda866bfb4f4839e725edf1bd32339755cdeb1. Reapply Admin page changes. --- admin_page_server.py | 100 +++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 56 deletions(-) diff --git a/admin_page_server.py b/admin_page_server.py index 6ab53db..414d158 100644 --- a/admin_page_server.py +++ b/admin_page_server.py @@ -81,8 +81,8 @@ class AdminPage(resource.Resource): '' '' '

' - 'Blacklist ' - 'Whitelist ' + 'All Users | ' + 'Active Bans ' '

' ) return s @@ -114,56 +114,47 @@ class AdminPage(resource.Resource): request.write(error_message) return is_auth - def update_whitelist(self, request): + def update_banlist(self, request): address = request.getClientIP() dbconn = sqlite3.connect('gpcm.db') - userid = request.args['userid'][0].strip() gameid = request.args['gameid'][0].upper().strip() - macadr = request.args['macadr'][0].strip() - actiontype = request.args['actiontype'][0] - if not userid.isdigit() or not gameid.isalnum() or not macadr.isalnum(): + ipaddr = request.args['ipaddr'][0].strip() + actiontype = request.args['action'][0] + if not gameid.isalnum(): request.setResponseCode(500) - logger.log(logging.INFO,address+" Bad data "+userid+" "+gameid+" "+macadr) + logger.log(logging.INFO,address+" Bad data "+gameid+" "+ipaddr) return "Bad data" - if actiontype == 'add': - dbconn.cursor().execute('insert into whitelist values(?,?,?)',(userid,gameid,macadr)) - responsedata = "Added macadr=%s for gameid=%s, userid=%s" % (macadr,gameid,userid) + if actiontype == 'ban': + dbconn.cursor().execute('insert into banned values(?,?)',(gameid[:-1],ipaddr)) + responsedata = "Added gameid=%s, ipaddr=%s" % (gameid[:-1],ipaddr) else: - dbconn.cursor().execute('delete from whitelist where userid=? and gameid=? and macadr=?',(userid,gameid,macadr)) - responsedata = "Removed macadr=%s for gameid=%s, userid=%s" % (macadr,gameid,userid) + dbconn.cursor().execute('delete from banned where gameid=? and ipaddr=?',(gameid[:-1],ipaddr)) + responsedata = "Removed gameid=%s, ipaddr=%s" % (gameid[:-1],ipaddr) dbconn.commit() dbconn.close() logger.log(logging.INFO,address+" "+responsedata) request.setHeader("Content-Type", "text/html; charset=utf-8") - request.setHeader("Location", "/whitelist") + request.setHeader("Location", "/banhammer") request.setResponseCode(303) return responsedata - def render_whitelist(self, request): + def render_banlist(self, request): address = request.getClientIP() dbconn = sqlite3.connect('gpcm.db') - logger.log(logging.INFO,address+" Viewed whitelist") + logger.log(logging.INFO,address+" Viewed banlist") responsedata = ("" '[CLICK HERE TO LOG OUT]' - "
" - "userid:\r\n" - "gameid:\r\n" - "macadr:\r\n" - "\r\n" - "
\r\n" "" - "\r\n") - for row in dbconn.cursor().execute("select * from whitelist"): - userid = str(row[0]) - gameid = str(row[1]) - macadr = str(row[2]) - responsedata += ("" - "\r\n") + for row in dbconn.cursor().execute("select * from banned"): + gameid = str(row[0]) + ipaddr = str(row[1]) + responsedata += ("" + "\r\n") + "" + "\r\n" + "\r\n") responsedata += "
useridgameidmacadr
"+userid+""+gameid+""+macadr+"
" - "" + "
gameidipAddr
"+gameid+""+ipaddr+"" "" - "" - "\r\n" - "
" dbconn.close() request.setHeader("Content-Type", "text/html; charset=utf-8") @@ -189,13 +180,16 @@ class AdminPage(resource.Resource): 'order by users.gameid ' '') dbconn = sqlite3.connect('gpcm.db') + banned_list = [] + for row in dbconn.cursor().execute("SELECT * FROM BANNED"): + banned_list.append(str(row[0])+":"+str(row[1])) responsedata = ("" '[CLICK HERE TO LOG OUT]' "

" "" "" "" - "\r\n") + "\r\n") for row in dbconn.cursor().execute(sqlstatement): dwc_pid = str(row[0]) enabled = str(row[1]) @@ -204,7 +198,7 @@ class AdminPage(resource.Resource): is_console = int(str(row[4])) userid = str(row[5]) gsbrcd = str(nasdata['gsbrcd']) - ipaddr = str (nasdata['ipaddr']) + ipaddr = str(nasdata['ipaddr']) ingamesn = '' if 'ingamesn' in nasdata: ingamesn = str(nasdata['ingamesn']) @@ -226,20 +220,18 @@ class AdminPage(resource.Resource): responsedata += "" responsedata += "" responsedata += "" - if enabled == "1": - responsedata += ("") - else: - responsedata += ("") + else: + responsedata += ("") responsedata += "
ingamesn or devnamegameidEnablednewest dwc_pidgsbrcduseridIP
gsbrcduseridipAddr
"+gsbrcd+""+userid+""+ipaddr+"
" - "" + if gameid[:-1]+":"+ipaddr in banned_list: + responsedata += ("
" "" - "" - "" - "
" - "" - "" - "" "" + "" "
" + "" + "" + "" + "
" dbconn.close() request.setHeader("Content-Type", "text/html; charset=utf-8") @@ -284,11 +276,11 @@ class AdminPage(resource.Resource): title = None response = '' - if request.path == "/whitelist": - title = 'AltWfc Whitelist' - response = self.render_whitelist(request) + if request.path == "/banlist": + title = 'AltWfc Banned Users' + response = self.render_banlist(request) elif request.path == "/banhammer": - title = 'AltWfc Blacklist' + title = 'AltWfc Users' response = self.render_blacklist(request) return self.get_header(title) + response + self.get_footer() @@ -300,12 +292,8 @@ class AdminPage(resource.Resource): if not self.is_authorized(request): return "" - if request.path == "/updatewhitelist": - return self.update_whitelist(request) - elif request.path == "/enableuser": - return self.enable_disable_user(request, True) - elif request.path == "/disableuser": - return self.enable_disable_user(request, False) + if request.path == "/updatebanlist": + return self.update_banlist(request) else: return self.get_header() + self.get_footer() From 4c627bf7ef2de682008868d13b387084770c8c53 Mon Sep 17 00:00:00 2001 From: Kyle Warwick-Mathieu Date: Mon, 4 May 2015 02:33:36 -0400 Subject: [PATCH 2/7] Udated gs_database to new ban system standards --- gamespy/gs_database.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/gamespy/gs_database.py b/gamespy/gs_database.py index 44c1057..fa0a264 100644 --- a/gamespy/gs_database.py +++ b/gamespy/gs_database.py @@ -122,7 +122,7 @@ class GamespyDatabase(object): tx.nonquery("CREATE UNIQUE INDEX gamestatprofile_triple on gamestat_profile(profileid,dindex,ptype)") tx.nonquery("CREATE TABLE gameinfo (profileid INT, dindex TEXT, ptype TEXT, data TEXT)") tx.nonquery("CREATE TABLE nas_logins (userid TEXT, authtoken TEXT, data TEXT)") - tx.nonquery("CREATE TABLE IF NOT EXISTS whitelist (userid TEXT, gameid TEXT, macadr TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS banned (gameid TEXT, ipaddr TEXT)") # Create some indexes for performance. tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS users_profileid_idx ON users (profileid)") @@ -215,16 +215,6 @@ class GamespyDatabase(object): def create_user(self, userid, password, email, uniquenick, gsbrcd, console, csnum, cfc, bssid, devname, birth, gameid, macadr): - #Check for console ban - with Transaction(self.conn) as tx: - row = tx.queryone("SELECT * FROM users WHERE userid = ? and gameid = ? and enabled = 0 " - "and gameid not in (select gameid from whitelist where gameid=? and macadr=?) limit 1" - ,(userid, gameid, gameid, macadr)) - r = self.get_dict(row) - if r != None: - logger.log(logging.INFO, "--- REJECTING BANNED CONSOLE --- userid=%s,gameid=%s,macadr=%s", userid,gameid,macadr) - return None - if self.check_user_exists(userid, gsbrcd) == 0: profileid = self.get_next_free_profileid() @@ -413,6 +403,11 @@ class GamespyDatabase(object): else: return json.loads(r["data"]) + def is_banned(self,postdata): + with Transaction(self.conn) as tx: + row = tx.queryone("SELECT COUNT(*) FROM banned WHERE gameid = ? AND ipaddr = ?",(postdata['gamecd'][:-1],postdata['ipaddr'])) + return int(row[0]) > 0 + def get_next_available_userid(self): with Transaction(self.conn) as tx: row = tx.queryone("SELECT max(userid) AS maxuser FROM users") From 08ab1dd35afa7e07f8570aafdfa9b741143ec45e Mon Sep 17 00:00:00 2001 From: Kyle Warwick-Mathieu Date: Mon, 4 May 2015 02:36:33 -0400 Subject: [PATCH 3/7] Updated NAS (changes in description) -shows IP even with reverse proxy (the recommended setup to take full advantage of the sake server) -new error codes -23911=profile disabled (literally) -23913=UserID/acctcreate denied -23917=server access denied (banned) -fixed SMTDDR's error in listening port when he sent this to me - no worries --- nas_server.py | 77 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/nas_server.py b/nas_server.py index 22dde9a..807039f 100644 --- a/nas_server.py +++ b/nas_server.py @@ -43,7 +43,7 @@ logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_ # this is used for Mystery Gift distribution on Generation 4 Pokemon games gamecodes_return_random_file = ['ADAD', 'ADAE', 'ADAF', 'ADAI', 'ADAJ', 'ADAK', 'ADAS', 'CPUD', 'CPUE', 'CPUF', 'CPUI', 'CPUJ', 'CPUK', 'CPUS', 'IPGD', 'IPGE', 'IPGF', 'IPGI', 'IPGJ', 'IPGK', 'IPGS'] -#address = ("0.0.0.0", 80) +#address = ("0.0.0.0", 8080) address = ("127.0.0.1", 9000) class NasServer(object): @@ -88,10 +88,11 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): 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]) - post['ipaddr'] = client_address[0] 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) @@ -106,41 +107,59 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): if action == "acctcreate": # TODO: test for duplicate accounts - ret["returncd"] = "002" - ret['userid'] = self.server.db.get_next_available_userid() + if self.server.db.is_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":"because you're 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) + logger.log(logging.DEBUG, "acctcreate response to %s", 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 - - if not self.server.db.check_user_exists(post["userid"], post["gsbrcd"]) or self.server.db.check_user_enabled(post["userid"], post["gsbrcd"]): - authtoken = self.server.db.generate_authtoken(post["userid"], post) - ret.update({ - "returncd": "001", + if self.server.db.is_banned(post): + logger.log(logging.DEBUG, "login denied for banned user "+str(post)) + ret = { + "datetime": time.strftime("%Y%m%d%H%M%S"), + "returncd": "3914", "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) + "retry": "1", + "reason":"because you're banned" + } else: - # user is banned - ret.update({ - "returncd": "3912", - "locator": "gamespy.com", - }) - logger.log(logging.DEBUG, "login response to %s (user banned)", client_address) - logger.log(logging.DEBUG, ret) + challenge = utils.generate_random_str(8) + post["challenge"] = challenge + + if not self.server.db.check_user_exists(post["userid"], post["gsbrcd"]) or self.server.db.check_user_enabled(post["userid"], post["gsbrcd"]): + authtoken = self.server.db.generate_authtoken(post["userid"], post) + ret.update({ + "returncd": "001", + "locator": "gamespy.com", + "challenge": challenge, + "token": authtoken, + }) - ret = self.dict_to_str(ret) + logger.log(logging.DEBUG, "login response to %s", client_address) + logger.log(logging.DEBUG, ret) + else: + # user is banned + ret.update({ + "returncd": "3912", + "locator": "gamespy.com", + }) + logger.log(logging.DEBUG, "login response to %s (user banned)", 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" From 7d5951dd0147404d039c9af93b7153ebd1fcbd5f Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 4 May 2015 13:35:28 +0200 Subject: [PATCH 4/7] Change table creation code to IF NOT EXISTS syntax so that newly added tables can auto-create. --- gamespy/gs_database.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/gamespy/gs_database.py b/gamespy/gs_database.py index fa0a264..d20ff8e 100644 --- a/gamespy/gs_database.py +++ b/gamespy/gs_database.py @@ -105,26 +105,22 @@ class GamespyDatabase(object): def initialize_database(self): with Transaction(self.conn) as tx: - row = tx.queryone("SELECT COUNT(*) FROM sqlite_master WHERE name = 'users' AND type = 'table'") - count = int(row[0]) - # I highly doubt having everything in a database be of the type TEXT is a good practice, # but I'm not good with databases and I'm not 100% positive that, for instance, that all # user id's will be ints, or all passwords will be ints, etc, despite not seeing any # evidence yet to say otherwise as far as Nintendo DS games go. - if count < 1: - tx.nonquery("CREATE TABLE users (profileid INT, userid TEXT, password TEXT, gsbrcd TEXT, email TEXT, uniquenick TEXT, pid TEXT, lon TEXT, lat TEXT, loc TEXT, firstname TEXT, lastname TEXT, stat TEXT, partnerid TEXT, console INT, csnum TEXT, cfc TEXT, bssid TEXT, devname BLOB, birth TEXT, gameid TEXT, enabled INT, zipcode TEXT, aim TEXT)") - tx.nonquery("CREATE TABLE sessions (session TEXT, profileid INT, loginticket TEXT)") - tx.nonquery("CREATE TABLE buddies (userProfileId INT, buddyProfileId INT, time INT, status INT, notified INT, gameid TEXT, blocked INT)") - tx.nonquery("CREATE TABLE pending_messages (sourceid INT, targetid INT, msg TEXT)") - tx.nonquery("CREATE TABLE gamestat_profile (profileid INT, dindex TEXT, ptype TEXT, data TEXT)") - tx.nonquery("CREATE UNIQUE INDEX gamestatprofile_triple on gamestat_profile(profileid,dindex,ptype)") - tx.nonquery("CREATE TABLE gameinfo (profileid INT, dindex TEXT, ptype TEXT, data TEXT)") - tx.nonquery("CREATE TABLE nas_logins (userid TEXT, authtoken TEXT, data TEXT)") - tx.nonquery("CREATE TABLE IF NOT EXISTS banned (gameid TEXT, ipaddr TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS users (profileid INT, userid TEXT, password TEXT, gsbrcd TEXT, email TEXT, uniquenick TEXT, pid TEXT, lon TEXT, lat TEXT, loc TEXT, firstname TEXT, lastname TEXT, stat TEXT, partnerid TEXT, console INT, csnum TEXT, cfc TEXT, bssid TEXT, devname BLOB, birth TEXT, gameid TEXT, enabled INT, zipcode TEXT, aim TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS sessions (session TEXT, profileid INT, loginticket TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS buddies (userProfileId INT, buddyProfileId INT, time INT, status INT, notified INT, gameid TEXT, blocked INT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS pending_messages (sourceid INT, targetid INT, msg TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS gamestat_profile (profileid INT, dindex TEXT, ptype TEXT, data TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS gameinfo (profileid INT, dindex TEXT, ptype TEXT, data TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS nas_logins (userid TEXT, authtoken TEXT, data TEXT)") + tx.nonquery("CREATE TABLE IF NOT EXISTS banned (gameid TEXT, ipaddr TEXT)") # Create some indexes for performance. + tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS gamestatprofile_triple on gamestat_profile(profileid,dindex,ptype)") tx.nonquery("CREATE UNIQUE INDEX IF NOT EXISTS users_profileid_idx ON users (profileid)") tx.nonquery("CREATE INDEX IF NOT EXISTS users_userid_idx ON users (userid)") tx.nonquery("CREATE INDEX IF NOT EXISTS pending_messages_targetid_idx ON pending_messages (targetid)") From d95bf30e5b113e5bb899bd58b76dfaec9402a7fb Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 4 May 2015 13:52:22 +0200 Subject: [PATCH 5/7] Admin page: Fix broken unban buttons on /banlist. --- admin_page_server.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/admin_page_server.py b/admin_page_server.py index 414d158..cce7ea6 100644 --- a/admin_page_server.py +++ b/admin_page_server.py @@ -124,12 +124,17 @@ class AdminPage(resource.Resource): request.setResponseCode(500) logger.log(logging.INFO,address+" Bad data "+gameid+" "+ipaddr) return "Bad data" + + # this strips the region identifier from game IDs, not sure if this actually always accurate but limited testing suggests it is + if len(gameid) > 3: + gameid = gameid[:-1] + if actiontype == 'ban': - dbconn.cursor().execute('insert into banned values(?,?)',(gameid[:-1],ipaddr)) - responsedata = "Added gameid=%s, ipaddr=%s" % (gameid[:-1],ipaddr) + dbconn.cursor().execute('insert into banned values(?,?)',(gameid,ipaddr)) + responsedata = "Added gameid=%s, ipaddr=%s" % (gameid,ipaddr) else: - dbconn.cursor().execute('delete from banned where gameid=? and ipaddr=?',(gameid[:-1],ipaddr)) - responsedata = "Removed gameid=%s, ipaddr=%s" % (gameid[:-1],ipaddr) + dbconn.cursor().execute('delete from banned where gameid=? and ipaddr=?',(gameid,ipaddr)) + responsedata = "Removed gameid=%s, ipaddr=%s" % (gameid,ipaddr) dbconn.commit() dbconn.close() logger.log(logging.INFO,address+" "+responsedata) From 37dff10c8ba5de6b460de6978585ac7684d8c2c8 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 4 May 2015 14:06:42 +0200 Subject: [PATCH 6/7] Admin page: Auto-return to the page you pushed the Unban button from. --- admin_page_server.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/admin_page_server.py b/admin_page_server.py index cce7ea6..0579c55 100644 --- a/admin_page_server.py +++ b/admin_page_server.py @@ -139,7 +139,12 @@ class AdminPage(resource.Resource): dbconn.close() logger.log(logging.INFO,address+" "+responsedata) request.setHeader("Content-Type", "text/html; charset=utf-8") - request.setHeader("Location", "/banhammer") + + referer = request.getHeader('referer') + if not referer: + referer = "/banhammer" + request.setHeader("Location", referer) + request.setResponseCode(303) return responsedata From 38a6796edd153e7a84acb5bc47a044e5591212ef Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 4 May 2015 14:30:14 +0200 Subject: [PATCH 7/7] NAS: Cleanup. --- nas_server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nas_server.py b/nas_server.py index 807039f..f097670 100644 --- a/nas_server.py +++ b/nas_server.py @@ -43,7 +43,7 @@ logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_ # this is used for Mystery Gift distribution on Generation 4 Pokemon games gamecodes_return_random_file = ['ADAD', 'ADAE', 'ADAF', 'ADAI', 'ADAJ', 'ADAK', 'ADAS', 'CPUD', 'CPUE', 'CPUF', 'CPUI', 'CPUJ', 'CPUK', 'CPUS', 'IPGD', 'IPGE', 'IPGF', 'IPGI', 'IPGJ', 'IPGK', 'IPGS'] -#address = ("0.0.0.0", 8080) +#address = ("0.0.0.0", 80) address = ("127.0.0.1", 9000) class NasServer(object): @@ -114,7 +114,7 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): "returncd": "3913", "locator": "gamespy.com", "retry": "1", - "reason":"because you're banned" + "reason": "User banned." } else: ret["returncd"] = "002" @@ -133,7 +133,7 @@ class NasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): "returncd": "3914", "locator": "gamespy.com", "retry": "1", - "reason":"because you're banned" + "reason": "User banned." } else: challenge = utils.generate_random_str(8)