#----------------------------------------------------------------------
# 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.")