diff --git a/src/server/oobhandler.py b/src/server/oobhandler.py index 14d4040fd3..98dd32fdcd 100644 --- a/src/server/oobhandler.py +++ b/src/server/oobhandler.py @@ -26,10 +26,10 @@ from django.conf import settings from src.server.models import ServerConfig from src.server.sessionhandler import SESSIONS from src.scripts.scripts import Script -from src.create import create_script +from src.utils.create import create_script from src.utils.dbserialize import dbserialize, dbunserialize, pack_dbobj from src.utils import logger -from src.utils.utils import variable_from_module, to_str +from src.utils.utils import variable_from_module, to_str, is_iter, make_iter _SA = object.__setattr__ _GA = object.__getattribute__ @@ -307,7 +307,7 @@ class OOBHandler(object): oob_tracker_name = "_track_db_value_change" self.track(attrobj, tracker_key, attr_name, sessid, property_name=oob_tracker_name) - def run(self, func_key, *args, **kwargs): + def execute_cmd(self, func_key, *args, **kwargs): """ Retrieve oobfunc from OOB_FUNCS and execute it immediately using *args and **kwargs diff --git a/src/server/portal/msdp.py b/src/server/portal/msdp.py index 86e2847dfc..6eaa9a1eb5 100644 --- a/src/server/portal/msdp.py +++ b/src/server/portal/msdp.py @@ -132,7 +132,7 @@ class Msdp(object): """ self.protocol = protocol self.protocol.protocol_flags['MSDP'] = False - self.protocol.negotiationMap[MSDP] = self.msdp_to_func + self.protocol.negotiationMap[MSDP] = self.msdp_to_evennia self.protocol.will(MSDP).addCallbacks(self.do_msdp, self.no_msdp) self.msdp_reported = {} @@ -148,10 +148,7 @@ class Msdp(object): print "msdp supported" self.protocol.protocol_flags['MSDP'] = True - def parse_msdp(self, args): - "Called with arguments to subnegotiation" - - def func_to_msdp(self, cmdname, data): + def evennia_to_msdp(self, cmdname, data): """ handle return data from cmdname by converting it to a proper msdp structure. data can either be a single value (will be @@ -179,6 +176,7 @@ class Msdp(object): def make_array(name, datalist, string): "build a simple array. Arrays may not nest tables by definition." + print "make_array", datalist, string string += MSDP_VAR + name + MSDP_ARRAY_OPEN for val in datalist: string += MSDP_VAL + val @@ -190,11 +188,11 @@ class Msdp(object): elif hasattr(data, '__iter__'): msdp_string = make_array(cmdname, data, "") else: - msdp_string = MSDP_VAR + cmdname + MSDP_VAL + data + msdp_string = MSDP_VAR + cmdname + MSDP_VAL + data if data!=None else "" return msdp_string - def msdp_to_func(self, data): + def msdp_to_evennia(self, data): """ Handle a client's requested negotiation, converting it into a function mapping - either one of the MSDP @@ -229,57 +227,55 @@ class Msdp(object): print "MSDP: table, array, variables:", tables, arrays, variables + # all variables sent through msdp to Evennia are considered commands with arguments. + # there are three forms of commands possible through msdp: + # + # VARNAME VAR -> varname(var) + # ARRAYNAME VAR VAL VAR VAL VAR VAL ENDARRAY -> arrayname(val,val,val) + # TABLENAME TABLE VARNAME VAL VARNAME VAL ENDTABLE -> tablename(varname=val, varname=val) + # + + ret = "" # default MSDP functions if "LIST" in variables: - ret += self.func_to_msdp("LIST", self.msdp_cmd_list(variables["LIST"])) - del variables["LIST"] + ret += self.evennia_to_msdp("LIST", self.msdp_cmd_list(*(variables.pop("LIST"),))) if "REPORT" in variables: - ret += self.func_to_msdp("REPORT", self.msdp_cmd_report(*(variables["REPORT"],))) - del variables["REPORT"] + ret += self.evennia_to_msdp("REPORT", self.msdp_cmd_report(*(variables.pop("REPORT"),))) if "REPORT" in arrays: - ret += self.func_to_msdp("REPORT", self.msdp_cmd_report(*arrays["REPORT"])) - del arrays["REPORT"] + ret += self.evennia_to_msdp("REPORT", self.msdp_cmd_report(*arrays.pop("REPORT"))) if "RESET" in variables: - ret += self.func_to_msdp("RESET", self.msdp_cmd_reset(*(variables["RESET"],))) - del variables["RESET"] + ret += self.evennia_to_msdp("RESET", self.msdp_cmd_reset(*(variables.pop("RESET"),))) if "RESET" in arrays: - ret += self.func_to_msdp("RESET", self.msdp_cmd_reset(*arrays["RESET"])) - del arrays["RESET"] + ret += self.evennia_to_msdp("RESET", self.msdp_cmd_reset(*arrays.pop("RESET",))) if "SEND" in variables: - ret += self.func_to_msdp("SEND", self.msdp_cmd_send(*(variables["SEND"],))) - del variables["SEND"] + ret += self.evennia_to_msdp("SEND", self.msdp_cmd_send(*(variables.pop("SEND",)))) if "SEND" in arrays: - ret += self.func_to_msdp("SEND",self.msdp_cmd_send(*arrays["SEND"])) - del arrays["SEND"] + ret += self.evennia_to_msdp("SEND",self.msdp_cmd_send(*arrays.pop("SEND"))) # if there are anything left we look for a custom function for varname, var in variables.items(): # a simple function + argument ooc_func = MSDP_COMMANDS_CUSTOM.get(varname.upper()) if ooc_func: - ret += self.func_to_msdp(varname, ooc_func(var)) + ret += self.evennia_to_msdp(varname, ooc_func(var)) for arrayname, array in arrays.items(): # we assume the array are multiple arguments to the function ooc_func = MSDP_COMMANDS_CUSTOM.get(arrayname.upper()) if ooc_func: - ret += self.func_to_msdp(arrayname, ooc_func(*array)) + ret += self.evennia_to_msdp(arrayname, ooc_func(*array)) for tablename, table in tables.items(): # we assume tables are keyword arguments to the function ooc_func = MSDP_COMMANDS_CUSTOM.get(arrayname.upper()) if ooc_func: - ret += self.func_to_msdp(tablename, ooc_func(**table)) + ret += self.evennia_to_msdp(tablename, ooc_func(**table)) + # return any result if ret: - # send return value if it exists - self.msdp_send(ret) - ret = IAC + SB + MSDP + ret + IAC + SE - #ret = IAC + SB + MSDP + MSDP_VAR + "SEND" + MSDP_VAL + "Testsend" + IAC + SE - self.protocol._write(ret) - logger.log_infomsg("MSDP_RESULT: %s" % ret) + self.data_out(ret) - def msdp_send(self, msdp_string): + def data_out(self, msdp_string): """ Return a msdp-valid subnegotiation across the protocol. """ @@ -295,21 +291,21 @@ class Msdp(object): The List command allows for retrieving various info about the server/client """ if arg == 'COMMANDS': - return self.func_to_msdp(arg, MSDP_COMMANDS) + return self.evennia_to_msdp(arg, MSDP_COMMANDS) elif arg == 'LISTS': - return self.func_to_msdp(arg, ("COMMANDS", "LISTS", "CONFIGURABLE_VARIABLES", + return self.evennia_to_msdp(arg, ("COMMANDS", "LISTS", "CONFIGURABLE_VARIABLES", "REPORTED_VARIABLES", "SENDABLE_VARIABLES")) elif arg == 'CONFIGURABLE_VARIABLES': - return self.func_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID")) + return self.evennia_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID")) elif arg == 'REPORTABLE_VARIABLES': - return self.func_to_msdp(arg, MSDP_REPORTABLE.keys()) + return self.evennia_to_msdp(arg, MSDP_REPORTABLE.keys()) elif arg == 'REPORTED_VARIABLES': # the dynamically set items to report - return self.func_to_msdp(arg, self.msdp_reported.keys()) + return self.evennia_to_msdp(arg, self.msdp_reported.keys()) elif arg == 'SENDABLE_VARIABLES': - return self.func_to_msdp(arg, MSDP_SENDABLE.keys()) + return self.evennia_to_msdp(arg, MSDP_SENDABLE.keys()) else: - return self.func_to_msdp("LIST", arg) + return self.evennia_to_msdp("LIST", arg) # default msdp commands diff --git a/src/server/portal/portalsessionhandler.py b/src/server/portal/portalsessionhandler.py index f0dbb40dac..f70d6296f8 100644 --- a/src/server/portal/portalsessionhandler.py +++ b/src/server/portal/portalsessionhandler.py @@ -138,6 +138,7 @@ class PortalSessionHandler(SessionHandler): for session in self.sessions.values(): session.data_out(message) + def data_out(self, sessid, text=None, **kwargs): """ Called by server for having the portal relay messages and data diff --git a/src/server/portal/telnet.py b/src/server/portal/telnet.py index 9ed178b2da..21fa14fef8 100644 --- a/src/server/portal/telnet.py +++ b/src/server/portal/telnet.py @@ -13,6 +13,7 @@ from src.server.session import Session from src.server.portal import ttype, mssp, msdp from src.server.portal.mccp import Mccp, mccp_compress, MCCP from src.utils import utils, ansi, logger +from src.utils.utils import make_iter, is_iter _RE_N = re.compile(r"\{n$") @@ -36,14 +37,13 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): # negotiate ttype (client info) #self.ttype = ttype.Ttype(self) # negotiate mssp (crawler communication) - self.mssp = mssp.Mssp(self) + #self.mssp = mssp.Mssp(self) # msdp - #self.msdp = msdp.Msdp(self) + self.msdp = msdp.Msdp(self) # add this new connection to sessionhandler so # the Server becomes aware of it. self.sessionhandler.connect(self) - def enableRemote(self, option): """ This sets up the remote-activated options we allow for this protocol. @@ -69,7 +69,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): else: return super(TelnetProtocol, self).disableLocal(option) - def connectionLost(self, reason): """ This is executed when the connection is lost for @@ -163,6 +162,16 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): except Exception, e: self.sendLine(str(e)) return + if "oob" in kwargs: + oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob")) + if "MSDP" in self.protocol_flags: + print "oobstruct:", oobstruct + for cmdname, args in oobstruct: + print "cmdname, args:", cmdname, args + msdp_string = self.msdp.func_to_msdp(cmdname, args) + print "msdp_string:", msdp_string + self.msdp.data_out(msdp_string) + ttype = self.protocol_flags.get('TTYPE', {}) raw = kwargs.get("raw", False) nomarkup = not (ttype or ttype.get('256 COLORS') or ttype.get('ANSI') or not ttype.get("init_done")) diff --git a/src/server/serversession.py b/src/server/serversession.py index 5df372c0c9..49dd89851b 100644 --- a/src/server/serversession.py +++ b/src/server/serversession.py @@ -20,6 +20,7 @@ from src.server.session import Session IDLE_COMMAND = settings.IDLE_COMMAND _GA = object.__getattribute__ _ObjectDB = None +_OOB_HANDLER = None # load optional out-of-band function module OOB_PLUGIN_MODULE = settings.OOB_PLUGIN_MODULE @@ -178,8 +179,13 @@ class ServerSession(Session): cmdhandler.cmdhandler(self, text, callertype="session", sessid=self.sessid) self.update_session_counters() if "oob" in kwargs: - # relay to OOB handler - pass + # handle oob instructions + global _OOB_HANDLER + if not _OOB_HANDLER: + from src.servever.oobhandler import OOB_HANDLER as _OOB_HANDLER + oobstruct = self.sessionhandler.oobstruct_parser(kwargs.pop("oob", None)) + for (funcname, args, kwargs) in oobstruct: + _OOBHANDLER.execute_cmd(funcname, *args, **kwargs) execute_cmd = data_in # alias diff --git a/src/server/sessionhandler.py b/src/server/sessionhandler.py index 0ae60c2ff3..fa4634bbba 100644 --- a/src/server/sessionhandler.py +++ b/src/server/sessionhandler.py @@ -27,6 +27,7 @@ _ServerSession = None _ServerConfig = None _ScriptDB = None + # AMP signals PCONN = chr(1) # portal session connect PDISCONN = chr(2) # portal session disconnect @@ -357,6 +358,49 @@ class ServerSessionHandler(SessionHandler): return self.sessions.get(sessid) return None + def oobstruct_parser(self, oobstruct): + """ + Helper method for each session to use to parse oob structures + (The 'oob' kwarg of the msg() method) + allowed oob structures are + cmdname + (cmdname, cmdname) + (cmdname,(arg, )) + (cmdname,(arg1,arg2)) + (cmdname,{key:val,key2:val2}) + (cmdname, (args,), {kwargs}) + ((cmdname, (arg1,arg2)), cmdname, (cmdname, (arg1,))) + outputs an ordered structure on the form + ((cmdname, (args,), {kwargs}), ...), where the two last parts of each tuple may be empty + """ + slen = len(oobstruct) + if not oobstruct: + return tuple(None, (), {}) + elif not hasattr(oobstruct, "__iter__"): + # a singular command name, without arguments or kwargs + return (oobstruct.lower(), (), {}) + # regardless of number of args/kwargs, the first element must be the function name. + # we will not catch this error if not, but allow it to propagate. + if slen == 1: + return (oobstruct[0].lower(), (), {}) + elif slen == 2: + if isinstance(oobstruct[1], dict): + # cmdname, {kwargs} + return (oobstruct[0].lower(), (), dict((key.lower(), val) for key,val in oobstruct[1].items())) + elif isinstance(oobstruct[1], (tuple, list)): + # cmdname, (args,) + return (oobstruct[0].lower(), tuple(arg.lower() for arg in oobstruct[1]), {}) + else: + # cmdname, (args,), {kwargs} + return (oobstruct[0].lower(), tuple(arg.lower for arg in oobstruct[1]), + dict((key.lower(), val) for key, val in oobstruct[2].items())) + + # either multiple funcnames or multiple func tuples; descend recursively + out = [] + for oobpart in oobstruct: + out.append(self.oobstruct_parser(oobpart)[0]) + return tuple(out) + def announce_all(self, message): """ Send message to all connected sessions @@ -379,5 +423,4 @@ class ServerSessionHandler(SessionHandler): if session: session.data_in(text=text, **kwargs) - SESSIONS = ServerSessionHandler()