#!/usr/bin/python3 ######################################################################################## # This file is part of Mana Spheres. # Copyright (C) 2020 Jesusalva # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . ######################################################################################## ## Global Modules import threading, time, json, syslog LOG_AUTH=syslog.LOG_AUTH ## Semi-local modules from websock import WebSocketServer, WebSocket ## Local Modules from utils import stdout, now, clients, debug from consts import MAX_CLIENTS, PACKET_ACK import protocol, security, player, traceback ############################################################### # Configuration f=open("pass.json", "r") p=json.load(f) f.close() PORT=p["PORT"] SECURE=p["SSL"] ############################################################### # Main Class class WebSocketConn(WebSocket): def handle(self): global clients """ Called when websocket frame is received. To access the frame data call self.data. i.e. The client sent us a message \o/ If the frame is Text then self.data is a unicode object. If the frame is Binary then self.data is a bytearray object. """ print("Message received from %s - %s" % (self.address[0], self.data)) try: r=protocol.parse(self.data, self) print("Status: %s" % str(r[0])) print("Reply: %s" % r[1]) if r[0] < PACKET_ACK: stdout("%s - %s" % (self.address[0], r[1])) syslog.syslog(LOG_AUTH, "%s - %s" % (self.address[0], r[1])) self.send_message("NACK\n") # FIXME: Parse the packet error score security.score(self, 5) else: self.send_message(r[1]) except: traceback.print_exc() self.send_message("ERROR\n") print("Message sent") #stdout(self.address[0] + u' - %s' % (self.data)) #self.send_message('ACK') def connected(self): global clients, blacklist """ Called when a websocket client connects to the server. """ #print(self.address, 'connected') #for client in clients: # print(repr(client)) # client.send_message(self.address[0] + u' - connected') stdout(self.address[0] + u' - connected') # Blacklisted if (security.is_banned(self.address[0])): stdout("Found in K-Line, connection was dropped.") self.close(self, status=1000, reason="K-Lined") return # TODO: Drop OLD connections when same IP tries to connect # TODO: Either auto-ban or limit to <= 3 connections from same IP at once # By last, keep a "sane" amount of clients connected # This program must not, under any circumstances, interfer # on the operation of Moubootaur Legends, which has total priority # over the VM resources if (len(clients) > MAX_CLIENTS): self.close(self, status=1000, reason='Server is full') else: clients.append(self) # Extend self class self.MS_score = 0 self.MS_auth = False self.token = "0" self.userid = 0 def handle_close(self): global clients """ Called when a websocket server gets a Close frame from a client. """ clients.remove(self) print(self.address, 'closed') stdout(self.address[0] + u' - disconnected') if self.token != "0": try: player.clear(self.token) except: traceback.print_exc() stdout("Error at player.clear") ########################## # Useful functions: # send_message() # send_fragment_start() / send_fragment() / send_fragment_end() # ########################## def MainWebsocket(): if SECURE: server = WebSocketServer('', PORT, WebSocketConn, certfile="certificate.pem", keyfile="key.pem") print("Begin Secure Websocket at %d" % PORT) else: server = WebSocketServer('', PORT, WebSocketConn) print("Begin UNENCRYPTED Websocket at %d" % PORT) t = threading.Thread(target=server.serve_forever) t.daemon = True # Main server should not be a daemon? t.start() return #while True: # time.sleep(86400) # Broadcast function def sendmsg(m, t="raw"): global clients # Format message according to type if t == "ping": msg="pong" else: msg=u"%s" % m # Send message for c in clients: c.send_message(msg) return # Disconnect function def disconnect(ip): global clients totaldc=0 for c in clients: if c.address[0] == ip: c.close(status=1000, reason="Remote host closed connection.") totaldc+=1 return totaldc ############################################################### # Begin stuff stdout("Starting at: T-%d" % (now())) syslog.openlog() MainWebsocket() try: print("Serving at port %d" % PORT) while True: command=str(input("> ")) # No command inserted? Do nothing if command == "": continue # We have a command, prepare it for processing stdout("CONSOLE: %s" % command) cmd=command.split(' ')[0].lower() com=" ".join(command.split(' ')[1:]) # Parse the command # TODO: grant gems to an user if cmd in ["broadcast", "global", "msg", "say", "kami"]: sendmsg("NOTICE:" + com) elif cmd in ["ban", "nuke", "kb"]: security.ban_ip(com) totaldc=disconnect(com) stdout("BAN: Disconnected %d clients." % totaldc) del totaldc elif cmd in ["unban"]: security.unban_ip(com) elif cmd in ["permaban", "block", "kline"]: security.ban_ip(com) f=open("K-Line.txt", "a") f.write(com+"\n") f.close() stdout("%s has been K-Lined." % com) totaldc=disconnect(com) stdout("Disconnected %d clients." % totaldc) del totaldc elif cmd in ["exit", "quit", "close", "term", "end"]: stdout("Preparing to close server...") sendmsg("NOTICE:Server is going down for scheduled maintenance. Please close, wait five minutes, and then re-open the app.") break elif cmd in ["dbg", "debug"]: debug = not debug stdout("Changed debug mode to %s" % str(debug)) elif cmd in ["raw", "eval"] and debug: try: print(eval(com)) except: traceback.print_exc() print("[RAW] Error.") elif cmd in ["run", "exec"] and debug: try: exec(com) except: traceback.print_exc() print("[RUN] Error.") elif cmd in ["status", "st"]: stdout("Total clients connected: %d" % len(clients)) stdout("Total blacklist size: %d" % len(security.blacklist)) elif cmd in ["kick", "dc"]: totaldc=disconnect(com) stdout("Disconnected %d clients." % totaldc) del totaldc elif cmd in ["ddos", "dcall"]: totaldc=0 for c in clients: if c.token == "0" or c.userid < 1: c.close(status=1000, reason="Remote host closed connection.") totaldc+=1 stdout("Disconnected %d clients." % totaldc) del totaldc elif cmd in ["ddosban", "dcbanall"]: totaldc=0 for c in clients: if c.token == "0" or c.userid < 1: security.ban_ip(c.address[0], now()+1800) c.close(status=1000, reason="Remote host closed connection.") totaldc+=1 stdout("Disconnected and banned %d clients." % totaldc) del totaldc else: stdout("ERROR: Unknown command.") except: stdout("Abrupt error: Terminating!") traceback.print_exc() # Wait a bit before disconnecting all clients # To make sure any pending sendmsg() will arrive time.sleep(0.20) for c in clients: c.close(reason="Server is shutting down") # Make cleanup, just in case c.close() missed time.sleep(1.0) player.sql_routine() print("Server finished.")