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