summaryrefslogblamecommitdiff
path: root/server.py
blob: 87b06342002285c18be94e7e297ee0f663bb3352 (plain) (tree)
1
2
3
4
5
6
7
8
9

                  
                 

                                    

                     
                                              
 
                
                                             
                                          
                                            







                                                               
               
 












                                                                      
                                                                             
            
                                             

                                           
                                 


                                                                            
                                                     

                                       
                                       


                                        

                             
                                                         
                                 

                        
                                 


                                                                
                                         



                                                                   
 
                     
                                                 
                                                              
                                                           
                  
 

                                                                                 






                                                                          


                                


                             
                           
                         
 






                                                                          
                                                    





                                               









                                                                   






                                                                               























                                                               

                                     
               
    
                                      
               
                                



                                                      



                                            
                                                                
                                    
                                             
                                

                                                             
                                                                                                                                        
                 


                                                           
                                              




                                     





                                              



                                                                        

















                                                                                 

                                             
       
                                        
                         
 
                    
               

                                             
                         

 
#!/usr/bin/python3

## 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

###############################################################
# 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
        if cmd in ["broadcast", "global", "msg", "say", "kami"]:
            sendmsg("NOTICE:" + com)
        elif cmd in ["ban", "block", "nuke"]:
            security.ban_ip(com)
        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.")
        # TODO: Disconnect a client, disconnect all unauthed client,
        # disconnect & ban all unauthed clients - all these are measures
        # to manually fight a DoS
        # Also, "permaban" an IP - open("K-Line.txt", "a")
        # And grant gems to an user
        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()

# TODO: Cleanup here
time.sleep(0.5)
for c in clients:
    c.close(reason="Server is shutting down")
print("Server finished.")