#---------------------------------------------------------------------- # poosock.py JJS 05/20/99 # # This file defines a main program which forms a POO server # using TCP/IP sockets. # #---------------------------------------------------------------------- # standard modules import whrandom import string import types import copy import sys import md5 from socket import * from select import * # if you don't use this, change useSelect below from time import sleep, ctime, time # POO modules import poo poo.flushNoCR = 1 #---------------------------------------------------------------------- # global (module) vars # HOST = '' # null string means local host (this machine) PORT = 4000 # port to use MAXCONS = 10 # maximum connections to allow SHUTDOWN = "shutdown" # shutdown command SAVE = "savedb" # save command TIMEOUT = 0.33 # max time to wait between poo.gUpdate() calls Banned = [] # list of domains/hosts not allowed to log on Allowed = [] # list of domains/hosts allowed to log on useSelect = 1 # set to 1 to use select(), 0 otherwise endl = "\r\n" # string to end outgoing lines (for standard Telnet) running = 0 # 0=starting up, 1=running, -1=shutting down sock = None # socket used to receive connections connlist = [] # list of connected users LoginList = [] # list of people trying to log in stdout = sys.stdout # standard output (server console) #---------------------------------------------------------------------- # function to print to the log file (stdout) # def Log(msg, addr=None): sys.stdout = stdout if not addr: print "%-43s" % ctime(time())[:19], msg else: print "%19s %15s %6d" % \ (ctime(time())[:19], addr[0], addr[1]), msg stdout.flush() #---------------------------------------------------------------------- # function to find the end of line character(s) # return '\n', '\r', '\r\n', etc., or '' if none of these found # def EolChars(s): possibilities = ('\r\n', '\n\r', '\n', '\r') for p in possibilities: if string.rfind(s, p) >= 0: Log("EOL: " + repr(p)) return p return '' #---------------------------------------------------------------------- # function to negotiate TELNET settings # (for more info, see ftp://ftp.internic.net/rfc/rfc854.txt) # ...strip negotiation from the data, and return any remaining data # def Negotiate(data, conn): # If we get a \377, then some special telnet code is next. # codes \373--\376 are followed by a third byte specifying the 'option code' # other codes are just two bytes while '\377' in data: x=string.find(data, '\377') if len(data) > x+2 and data[x+1] in '\373\374\375\376': if data[x+1] in '\375\376': # It's a DO or a DON'T # and we WON'T conn.write('\377\374'+data[x+2]) data=data[:x]+data[x+3:] else: data=data[:x]+data[x+2:] return data #---------------------------------------------------------------------- # function to see if host/domain is allowed to connect # NOTE: if the host is not allowed OR banned, the host can't connect # Returns 1 if host is banned, 0 if allowed # def CheckDomain(addr): out = 0 # See if they are allowed ad = string.splitfields(addr[0],'.') for dh in Allowed: print "Checking "+dh[0]+"."+dh[1]+"."+dh[2]+"."+dh[3]+"." if dh[0] == '*': ad[0] = '*' if dh[1] == '*': ad[1] = '*' if dh[2] == '*': ad[2] = '*' if dh[3] == '*': ad[3] = '*' if ad == dh and out == 0: out = 0 print " Allowed",ad[0]+"."+ad[1]+"."+ad[2]+"."+ad[3]+"." elif not ad == dh: out = 1 print "Not Allowed",ad[0]+"."+ad[1]+"."+ad[2]+"."+ad[3]+"." # See if they are banned ad = string.splitfields(addr[0],'.') for dh in Banned: print "Checking "+dh[0]+"."+dh[1]+"."+dh[2]+"."+dh[3]+"." if dh[0] == '*': ad[0] = '*' if dh[1] == '*': ad[1] = '*' if dh[2] == '*': ad[2] = '*' if dh[3] == '*': ad[3] = '*' if not ad == dh and out == 0: out = 0 print "Not Banned",ad[0]+"."+ad[1]+"."+ad[2]+"."+ad[3]+"." elif ad == dh: out = 1 print "Banned",ad[0]+"."+ad[1]+"."+ad[2]+"."+ad[3]+"." return out #---------------------------------------------------------------------- # function to start the server # def StartServer(): global HOST, PORT, running, sock sock = socket(AF_INET, SOCK_STREAM) try: sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) except: pass sock.bind(HOST, PORT) sock.listen(1) sock.setblocking(0) Log( "Server open on port " + str(PORT) ) running = 1 #---------------------------------------------------------------------- # function to disconnect a user # def Disconnect(user, msg=''): global endl,connlist, LoginList Log("disconnected "+user.name, user.addr) if user.conn and msg: user.send( msg+endl ) if user.POOuser: user.POOuser.Logout() try: user.POOuser.__dict__['__outstream'].close() except: pass if user.conn: user.conn.close() user.conn = None if user in connlist: connlist.remove(user) if user in LoginList: LoginList.remove(user) user.connected = 0 #---------------------------------------------------------------------- # function to shut the server down # def ShutDown(): global connlist, running, endl, sock, stdout if running < -1: return Log( "Shutting server down." ) for p in connlist: Disconnect(p, "Server shutting down.") poo.gSave() sys.stdout = stdout sock.close() running = -2 #---------------------------------------------------------------------- # handle a new connection # def NewConn( conn, addr ): global connlist, MAXCONS, endl, stdout Log("Connection Received", addr ) if running < 1: conn.send("Server is not running." + endl) conn.close() if len(connlist)+len(LoginList) >= MAXCONS: conn.send("Sorry, only " + str(MAXCONS) + \ " connections are allowed. Please try again later." \ + endl ) conn.close() return if CheckDomain(addr): conn.send("Sorry, connections from your host/domain " + \ "are not allowed at this site." + endl ) conn.close() # looks good -- log 'em in... Conn(conn, addr) #---------------------------------------------------------------------- # network update -- call this function periodically # def NetUpdate(): global connlist, running, sock if not running: return StartServer() if running < 0: return ShutDown() # check for incoming connections try: conn, addr = sock.accept() NewConn( conn, addr ) except: pass # handle incoming messages connsToCheck = connlist + LoginList sys.stdout = stdout if useSelect: filenums = map(lambda x:x.fileno(), connsToCheck) fnswinput = select(filenums, [], [], TIMEOUT)[0] connsToCheck = filter(lambda x,y=fnswinput:x.fileno() in y, connsToCheck) for u in connsToCheck: try: data = u.conn.recv(1024) except: data = None if data: if '\377' in data: data = Negotiate(data, u) #data = filter(lambda x: x>=' ' or x=='\n' or x=='\t', data) stripped = string.rstrip(data) if data: u.HandleMsg(data) elif data == '': # if they were in the recv(), but data is '', they have disconnected Disconnect(u,"disconnected") #---------------------------------------------------------------------- # # Conn class -- a connection, its POO object, etc. # class Conn: def __init__(self,conn, addr=''): global LoginList self.connected = 0 self.conn = conn self.addr = addr self.POOuser = None self.name = '<login>' self.password = '' self.tries = 0 self.partialtext = '' self.eolchars = '' LoginList.append(self) if self.conn: self.conn.setblocking(0) # show login message try: f = open('connect.txt','r') for line in f.readlines(): self.send(line[:-1]+endl) except: pass self.send("Name: ") def send(self, pWhat): if not self.conn: return try: self.conn.send(pWhat) except: pass write = send def flush(self): try: self.conn.flush() except: pass def HandleMsg(self,msg): global running # split msg into lines, # passing complete lines to the POO engine, # and caching incomplete lines for future use if not self.eolchars: self.eolchars = EolChars(msg) if not self.eolchars: self.partialtext = self.partialtext + msg return # split into lines... eollen = len(self.eolchars) if self.partialtext: msg = self.partialtext + msg self.partialtext = '' while msg: eol = string.find( msg, self.eolchars ) if eol >= 0: line = msg[:eol] msg = msg[eol + eollen:] # check for special poosock messages if line == SHUTDOWN: Log("Got Shutdown Command", self.addr) running=-1 elif line == SAVE: poo.gSave() elif line == "quit": Disconnect(self,"Goodbye!") else: # do normal POO message handling if self.POOuser: self.POOuser.handleMsg( line ) else: self.HandleLoginLine( line ) else: # incomplete line -- save for later self.partialtext = self.partialtext + msg return def HandleLoginLine(self, msg): if self.name == '<login>': # first msg should be name if len(msg)<2: return words = map(lambda x:string.strip(x), string.split(msg)) if (words[0] == "connect" or words[0] == "c") and len(words) > 2: self.HandleLoginLine(words[1]) self.HandleLoginLine(words[2]) return self.name = msg if msg == 'guest': self.HandleLoginLine('guest') # auto-password else: self.send('Password: ') return if self.password == '': self.password = md5.new(msg).digest() # check to see if this matches some player mtch = filter(self.Matches, poo.gObjlist.values()) if not mtch: self.tries = self.tries + 1 if self.tries >= 3: return self.Abort('Invalid login.'); self.name = '<login>' self.password = '' self.send('Login incorrect.' + endl + 'Name: ') return mtch = mtch[0] # shouldn't be more than one! if mtch.connected(): # user is already logged in... #return self.Abort('Already logged in!'); self.send('NOTE: previous connection has been aborted.' + endl) # looks good -- let 'em in self.EnterPOO(mtch) def Abort(self,msg='You are kicked off:'): global endl, LoginList, connlist if msg: self.send(msg+endl) Log('Aborting ['+msg+']', self.addr) self.conn.close() # we should be in LoginList; but we'll check both to be sure: if self in LoginList: LoginList.remove(self) if self in connlist: connlist.remove(self) def fileno(self): try: return self.conn.fileno() except: return None def Matches(self,puser): # return 1 if matches this POOuser try: return (self.password == puser.password and \ self.name == puser.name) except: return 0 def EnterPOO(self,match): global LoginList, connlist, endl # make sure if any previous connections match the same player, # we disconnect them now for c in connlist: if c.POOuser == match: Disconnect(c, "reconnection from %s %s" % tuple(self.addr)) self.POOuser = match LoginList.remove(self) connlist.append(self) self.POOuser.Login( poo.Outfix(self, self.POOuser )) Log(self.name+" logged in", self.addr) # end of class Conn #---------------------------------------------------------------------- #### MAIN PROGRAM #### Log( "Loading database") poo.initialize() # load file # here set Banned and Allowed domains, e.g.: # Allowed = [['192','101','199','*']] Log("Starting server") StartServer() # start the server while running > -2: if not useSelect: sleep(TIMEOUT) NetUpdate() # loop until done poo.gUpdate() Log("Program terminated.")