nas_server.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. """DWC Network Server Emulator
  2. Copyright (C) 2014 polaris-
  3. Copyright (C) 2014 ToadKing
  4. Copyright (C) 2014 AdmiralCurtiss
  5. Copyright (C) 2014 msoucy
  6. Copyright (C) 2015 Sepalani
  7. This program is free software: you can redistribute it and/or modify
  8. it under the terms of the GNU Affero General Public License as
  9. published by the Free Software Foundation, either version 3 of the
  10. License, or (at your option) any later version.
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. GNU Affero General Public License for more details.
  15. You should have received a copy of the GNU Affero General Public License
  16. along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. """
  18. import logging
  19. import time
  20. import http.server
  21. import socketserver
  22. import traceback
  23. from gamespy import gs_database
  24. from other import utils
  25. import dwc_config
  26. logger = dwc_config.get_logger('NasServer')
  27. def handle_post(handler, addr, post):
  28. """Handle unknown path."""
  29. logger.log(logging.WARNING, "Unknown path request %s from %s:%d!",
  30. handler.path, *addr)
  31. handler.send_response(404)
  32. return None
  33. def handle_ac_action(handler, db, addr, post):
  34. """Handle unknown ac action request."""
  35. logger.log(logging.WARNING, "Unknown ac action: %s", handler.path)
  36. return {}
  37. def handle_ac_acctcreate(handler, db, addr, post):
  38. """Handle ac acctcreate request.
  39. TODO: Test for duplicate accounts.
  40. """
  41. if db.is_banned(post):
  42. ret = {
  43. "retry": "1",
  44. "returncd": "3913",
  45. "locator": "gamespy.com",
  46. "reason": "User banned."
  47. }
  48. logger.log(logging.DEBUG, "Acctcreate denied for banned user %s",
  49. str(post))
  50. else:
  51. ret = {
  52. "retry": "0",
  53. "returncd": "002",
  54. "userid": db.get_next_available_userid()
  55. }
  56. logger.log(logging.DEBUG, "Acctcreate response to %s:%d", *addr)
  57. logger.log(logging.DEBUG, "%s", ret)
  58. return ret
  59. def handle_ac_login(handler, db, addr, post):
  60. """Handle ac login request."""
  61. new_post = {}
  62. for key, value in post.items():
  63. # do something with value
  64. if(isinstance(key, (bytes, bytearray))):
  65. key = key.decode('utf-8')
  66. if(isinstance(value, (bytes, bytearray))):
  67. value = value.decode('utf-8')
  68. new_post[key] = value
  69. post=new_post
  70. if db.is_banned(post):
  71. ret = {
  72. "retry": "1",
  73. "returncd": "3914",
  74. "locator": "gamespy.com",
  75. "reason": "User banned."
  76. }
  77. logger.log(logging.DEBUG, "Login denied for banned user %s", str(post))
  78. # Un-comment these lines to enable console registration feature
  79. # elif not db.pending(post):
  80. # logger.log(logging.DEBUG, "Login denied - Unknown console %s", post)
  81. # ret = {
  82. # "retry": "1",
  83. # "returncd": "3921",
  84. # "locator": "gamespy.com",
  85. # }
  86. # elif not db.registered(post):
  87. # logger.log(logging.DEBUG, "Login denied - console pending %s", post)
  88. # ret = {
  89. # "retry": "1",
  90. # "returncd": "3888",
  91. # "locator": "gamespy.com",
  92. # }
  93. else:
  94. challenge = utils.generate_random_str(8)
  95. post["challenge"] = challenge
  96. authtoken = db.generate_authtoken(post["userid"], post)
  97. ret = {
  98. "retry": "0",
  99. "returncd": "001",
  100. "locator": "gamespy.com",
  101. "challenge": challenge,
  102. "token": authtoken,
  103. }
  104. logger.log(logging.DEBUG, "Login response to %s:%d", *addr)
  105. logger.log(logging.DEBUG, "%s", ret)
  106. return ret
  107. def handle_ac_svcloc(handler, db, addr, post):
  108. """Handle ac svcloc request."""
  109. new_post = {}
  110. for key, value in post.items():
  111. # do something with value
  112. if(isinstance(key, (bytes, bytearray))):
  113. key = key.decode('utf-8')
  114. if(isinstance(value, (bytes, bytearray))):
  115. value = value.decode('utf-8')
  116. new_post[key] = value
  117. post=new_post
  118. # Get service based on service id number
  119. ret = {
  120. "retry": "0",
  121. "returncd": "007",
  122. "statusdata": "Y"
  123. }
  124. authtoken = db.generate_authtoken(post["userid"], post)
  125. if 'svc' in post:
  126. if post["svc"] in ("9000", "9001"):
  127. # DLC host = 9000
  128. # In case the client's DNS isn't redirecting to
  129. # dls1.nintendowifi.net
  130. # NB: NAS config overrides this if set
  131. svchost = dwc_config.get_svchost('NasServer')
  132. ret["svchost"] = svchost if svchost else handler.headers['host']
  133. # Brawl has 2 host headers which Apache chokes
  134. # on, so only return the first one or else it
  135. # won't work
  136. ret["svchost"] = ret["svchost"].split(',')[0]
  137. if post["svc"] == 9000:
  138. ret["token"] = authtoken
  139. else:
  140. ret["servicetoken"] = authtoken
  141. elif post["svc"] == "0000":
  142. # Pokemon requests this for some things
  143. ret["servicetoken"] = authtoken
  144. ret["svchost"] = "n/a"
  145. else:
  146. # Empty svc - Fix Error Code 24101 (Boom Street)
  147. ret["svchost"] = "n/a"
  148. ret["servicetoken"] = authtoken
  149. logger.log(logging.DEBUG, "Svcloc response to %s:%d", *addr)
  150. logger.log(logging.DEBUG, "%s", ret)
  151. return ret
  152. def handle_ac(handler, addr, post):
  153. """Handle ac POST request."""
  154. logger.log(logging.DEBUG, "Ac request to %s from %s:%d",
  155. handler.path, *addr)
  156. logger.log(logging.DEBUG, "%s", post)
  157. action = (post[b"action"]).lower().decode("utf-8")
  158. command = handler.ac_actions.get(action, handle_ac_action)
  159. ret = command(handler, gs_database.GamespyDatabase(), addr, post)
  160. ret.update({"datetime": time.strftime("%Y%m%d%H%M%S")})
  161. handler.send_response(200)
  162. handler.send_header("Content-type", "text/plain")
  163. handler.send_header("NODE", "wifiappe1")
  164. return utils.dict_to_qs(ret).encode()
  165. def handle_pr(handler, addr, post):
  166. """Handle pr POST request."""
  167. logger.log(logging.DEBUG, "Pr request to %s from %s:%d",
  168. handler.path, *addr)
  169. logger.log(logging.DEBUG, "%s", post)
  170. words = len(post["words"].split('\t'))
  171. wordsret = "0" * words
  172. ret = {
  173. "prwords": wordsret,
  174. "returncd": "000",
  175. "datetime": time.strftime("%Y%m%d%H%M%S")
  176. }
  177. for l in "ACEJKP":
  178. ret["prwords" + l] = wordsret
  179. handler.send_response(200)
  180. handler.send_header("Content-type", "text/plain")
  181. handler.send_header("NODE", "wifiappe1")
  182. logger.log(logging.DEBUG, "Pr response to %s:%d", *addr)
  183. logger.log(logging.DEBUG, "%s", ret)
  184. return utils.dict_to_qs(ret)
  185. class NasHTTPServerHandler(http.server.BaseHTTPRequestHandler):
  186. """Nintendo NAS server handler."""
  187. post_paths = {
  188. "/ac": handle_ac,
  189. "/pr": handle_pr,
  190. }
  191. ac_actions = {
  192. "acctcreate": handle_ac_acctcreate,
  193. "login": handle_ac_login,
  194. "svcloc": handle_ac_svcloc,
  195. }
  196. def version_string(self):
  197. return "Nintendo Wii (http)"
  198. def do_GET(self):
  199. """Handle GET request."""
  200. try:
  201. # conntest server
  202. self.send_response(200)
  203. self.send_header("Content-type", "text/html")
  204. self.send_header("X-Organization", "Nintendo")
  205. self.send_header("Server", "BigIP")
  206. self.end_headers()
  207. self.wfile.write("ok")
  208. except:
  209. logger.log(logging.ERROR, "Exception occurred on GET request!")
  210. logger.log(logging.ERROR, "%s", traceback.format_exc())
  211. def do_POST(self):
  212. try:
  213. length = int(self.headers['content-length'])
  214. post = utils.qs_to_dict(self.rfile.read(length))
  215. client_address = (
  216. self.headers.get('x-forwarded-for', self.client_address[0]),
  217. self.client_address[1]
  218. )
  219. post['ipaddr'] = client_address[0]
  220. command = self.post_paths.get(self.path, handle_post)
  221. ret = command(self, client_address, post)
  222. if ret is not None:
  223. self.send_header("Content-Length", str(len(ret)))
  224. self.end_headers()
  225. self.wfile.write(ret)
  226. except:
  227. logger.log(logging.ERROR, "Exception occurred on POST request!")
  228. logger.log(logging.ERROR, "%s", traceback.format_exc())
  229. class NasHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
  230. """Threading HTTP server."""
  231. pass
  232. class NasServer(object):
  233. def start(self):
  234. address = dwc_config.get_ip_port('NasServer')
  235. httpd = NasHTTPServer(address, NasHTTPServerHandler)
  236. logger.log(logging.INFO, "Now listening for connections on %s:%d...",
  237. *address)
  238. httpd.serve_forever()
  239. if __name__ == "__main__":
  240. nas = NasServer()
  241. nas.start()