#MUDPyE - (M)ulti-(U)ser (D)imension (Py)thon (E)ngine
#Copyright (C) 2005 Corey Staten
#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 2
#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, write to the Free Software
#Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#Send feedback/questions to MUDPyE@gmail.com
import select
import socket
import thread
import time
from mpmudobject import Ex
class DNSThread(object):
"""Object that creates a DNS Resolution thread on startup."""
def __init__(self):
self.lock = thread.allocate_lock()
self.requests = []
self.shutdown = False
self.threadID = 0
threadID = thread.start_new_thread(self.__Thread_DNSResolve, ())
def __del__(self):
#Inform the DNS resolution thread to shut down.
self.lock.acquire()
self.shutdown = True
self.lock.release()
def AddRequest(self, client, ipAddr):
self.lock.acquire()
self.requests.append((client,ipAddr))
self.lock.release()
def __Thread_DNSResolve(self):
while True:
self.lock.acquire()
if self.shutdown == True:
break
curRequests = self.requests
self.requests = []
self.lock.release()
for address in curRequests:
ipAddr = address[1]
hostAddr = socket.getfqdn(ipAddr)
address[0].DNSResolved(hostAddr)
time.sleep(.2)
class TelnetClient(object):
"""This client implements the telnet protocol in a way suitable for MUDs.
Telnet commands are handled out-of-stream by this class and passed along to the MUD separately
from user commands."""
STATE_TEXT, STATE_IAC, STATE_DO, STATE_DONT, STATE_WILL, STATE_WONT, STATE_SUBREQUEST, STATE_SUBREQUESTIAC = range(8)
def __init__(self, server, clientSocket, connAddress):
self.server = server
self.clientSocket = clientSocket
self.ipAddr = connAddress[0]
self.hostAddr = ""
self.portNum = connAddress[1]
self.avatarID = ""
self.sendBuf = []
self.recvBuf = []
self.subBuf = []
self.state = TelnetClient.STATE_TEXT
self.clientSocket.setblocking(0)
self.linkdead = False
def Disconnect(self):
"""Disconnects the client. Shortcut to server.DisconnectClient(self)"""
self.server.DisconnectClient(self)
def _Linkdeath(self):
"""Handles alerting the MUD that this client has gone linkdead."""
self.linkdead = True
Ex(self.server.eventObj, "_Sys_ClientLinkdeath", self)
self.server.DisconnectClient(self)
def Send(self, message):
"""Adds message to a list of messages to be sent. These are joined when necessary in SockSend."""
self.sendBuf.append(message)
def GetMessage(self):
"""Used to cut a message from in front of a newline in standard mud fashion."""
for char in self.recvBuf:
if (char == '\n') or (char == '\r'):
pos = self.recvBuf.index(char)
message = self.recvBuf[:pos].strip()
#Remove any extra \n or \r characters.
self.recvBuf = self.recvBuf[(pos+1):].lstrip()
return message
return None
def GetRaw(self):
"""Returns and clears the raw recv buffer."""
returnString = self.recvBuf
self.recvBuf = ""
return returnString
def SockSend(self):
"""Handles joining the buffer and sending the text."""
try:
sendText = "".join(self.sendBuf)
self.sendBuf = []
while len(sendText):
bytes = self.clientSocket.send(sendText)
sendText = sendText[bytes:]
except socket.error:
if not self.linkdead:
self._LinkDeath()
def SockRecv(self):
"""Handles actual receipt of data, including maintenance of the telnet state machine."""
try:
recvMessage = self.clientSocket.recv(4096)
except socket.error:
self._Linkdeath()
return None
if recvMessage == "":
#Client disconnected of his own accord.
self._Linkdeath()
return None
commands = []
inNewLine = False
#Change
#Telnet state machine.
for char in recvMessage:
if self.state == TelnetClient.STATE_TEXT:
#IAC
if char == "\xFF":
inNewLine = False
self.state = TelnetClient.STATE_IAC
elif char == "\b":
inNewLine = False
if len(self.recvBuf):
del self.recvBuf[-1]
#Valid endlines include \r\n, \n\r, \r\0, etc.
elif (char == "\n") or (char == "\r") or (char == "\0"):
if not(inNewLine):
commands.append("".join(self.recvBuf))
inNewLine = True
self.recvBuf = []
else:
inNewLine = False
self.recvBuf.append(char)
elif self.state == TelnetClient.STATE_IAC:
#IAC
if char == "\xFF":
self.state = TelnetClient.STATE_TEXT
self.recvBuf.append(char)
#DONT
elif char == "\xFE":
self.state = TelnetClient.STATE_DONT
#DO
elif char == "\xFD":
self.state = TelnetClient.STATE_DO
#WONT
elif char == "\xFC":
self.state = TelnetClient.STATE_WONT
#WILL
elif char == "\xFB":
self.state = TelnetClient.STATE_WILL
#SB
elif char == "\xFA":
self.state = TelnetClient.STATE_SUBREQUEST
elif self.state == TelnetClient.STATE_DONT:
Ex(self.avatarObj, "_Sys_TelnetDONT", char)
self.state = TelnetClient.STATE_TEXT
elif self.state == TelnetClient.STATE_DO:
Ex(self.avatarObj, "_Sys_TelnetDO", char)
self.state = TelnetClient.STATE_TEXT
elif self.state == TelnetClient.STATE_WONT:
Ex(self.avatarObj, "_Sys_TelnetWONT", char)
self.state = TelnetClient.STATE_TEXT
elif self.state == TelnetClient.STATE_WILL:
Ex(self.avatarObj, "_Sys_TelnetWILL", char)
self.state = TelnetClient.STATE_TEXT
elif self.state == TelnetClient.STATE_SUBREQUEST:
#IAC
if char == "\xFF":
self.state = TelnetClient.STATE_SUBREQUESTIAC
else:
self.subBuf.append(char)
elif self.state == TelnetClient.STATE_SUBREQUESTIAC:
#IAC
if char == "\xFF":
self.subBuf.append(char)
self.state = TelnetClient.STATE_SUBREQUEST
#SE
elif char == "\xF0":
self.state = TelnetClient.STATE_TEXT
recvString = "".join(self.subBuf)
self.subBuf = []
Ex(self.avatarObj, "_Sys_TelnetSB", recvString)
for command in commands:
Ex(self.avatarObj, "_Sys_ReceiveCommand", command)
def DNSResolved(self, hostAddr):
self.hostAddr = hostAddr
self.server.mudWorld.loggers["engine.server"].info("DNS Resolved [%s : %s]" % (self.ipAddr, self.hostAddr))
Ex(self.avatarObj, "_Sys_DNSResolved", hostAddr)
class MudServer(object):
def __init__(self, mudWorld, portNum, maxConn, eventObj, clientAvatarType, clientClass):
self.mudWorld = mudWorld
self.mudWorld.servers.append(self)
self.clientAvatarType = clientAvatarType
self.clientClass = clientClass
self.portNum = portNum
self.maxConn = maxConn
self.eventObj = eventObj
self.clientList = []
self.hostAddr = socket.gethostname()
self.ipAddr = socket.gethostbyname(self.hostAddr)
self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.serverSocket.bind(("", self.portNum))
self.serverSocket.listen(10)
self.mudWorld.loggers["engine.server"].info("Server listening on port [%d] [%s : %s]" % (self.portNum, self.ipAddr, self.hostAddr))
def AcceptConnections(self):
while 1:
if len(self.clientList) > self.maxConn:
break
acceptList, unusedWrite, unusedErr = select.select([self.serverSocket], [], [], 0)
del unusedWrite
del unusedErr
if acceptList == []:
break
else:
(newConn, connAddress) = self.serverSocket.accept()
newClient = self.clientClass(self, newConn, connAddress)
self.mudWorld.loggers["engine.server"].info("Client [%s] connected." % newClient.ipAddr)
newAvatarObj = self.mudWorld.objDB.CreateObj(self.clientAvatarType, client=newClient)
newClient.avatarObj = newAvatarObj
self.clientList.append(newClient)
self.mudWorld.dns.AddRequest(newClient, newClient.ipAddr)
Ex(self.eventObj, "_Sys_ClientConnect", newClient)
def Shutdown(self):
self.DisconnectAllClients()
self.serverSocket.close()
self.mudWorld.loggers["engine.server"].info("Shutting down server on port [%d] [%s : %s]" % (self.portNum, self.ipAddr, self.hostAddr))
self.mudWorld.servers.remove(self)
def DisconnectAllClients(self):
for client in self.clientList:
self.DisconnectClient(client)
def DisconnectClient(self, discClient):
if not(discClient.linkdead):
Ex(self.eventObj, "_Sys_ClientDisconnect", discClient)
discClient.SockSend()
try:
discClient.clientSocket.shutdown(2)
except socket.error:
pass
try:
discClient.clientSocket.close()
except socket.error:
pass
if not discClient.linkdead:
self.mudWorld.loggers["engine.server"].info("Client [%s:%s] disconnected." % (discClient.ipAddr, discClient.hostAddr))
else:
self.mudWorld.loggers["engine.server"].info("Client [%s:%s] linkdeath." % (discClient.ipAddr, discClient.hostAddr))
self.clientList.remove(discClient)
def NetworkUpdate(self):
if len(self.clientList) == 0:
return None
clientSockList = [client.clientSocket for client in self.clientList]
#First, check for errors.
unusedRead, unusedWrite, errSockets = select.select([], [], clientSockList, 0)
errClients = [client for client in self.clientList if (client.clientSocket in errSockets)]
for client in errClients:
self.DisconnectClient(client)
#Next, check for readable sockets.
readSockets, unusedWrite, unusedErr = select.select(clientSockList, [], [], 0)
readClients = [client for client in self.clientList if (client.clientSocket in readSockets)]
for client in readClients:
client.SockRecv()
#Don't even bother checking for sendable sockets, if they exist, we'll assume they're sendable.
for client in self.clientList:
client.SockSend()