Merge conflicts against master, including cmdhandler support for direct cmdobject input together with prefix-ignore mechanism from devel.

This commit is contained in:
Griatch 2017-04-01 16:08:23 +02:00
commit a648433db8
69 changed files with 2617 additions and 1771 deletions

View file

@ -7,7 +7,7 @@ The separation works like this:
Portal - (AMP client) handles protocols. It contains a list of connected
sessions in a dictionary for identifying the respective player
connected. If it looses the AMP connection it will automatically
connected. If it loses the AMP connection it will automatically
try to reconnect.
Server - (AMP server) Handles all mud operations. The server holds its own list
@ -32,33 +32,33 @@ from twisted.internet import protocol
from twisted.internet.defer import Deferred
from evennia.utils import logger
from evennia.utils.utils import to_str, variable_from_module
import zlib # Used in Compressed class
DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0)
# communication bits
# (chr(9) and chr(10) are \t and \n, so skipping them)
PCONN = chr(1) # portal session connect
PDISCONN = chr(2) # portal session disconnect
PSYNC = chr(3) # portal session sync
SLOGIN = chr(4) # server session login
SDISCONN = chr(5) # server session disconnect
SDISCONNALL = chr(6) # server session disconnect all
SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync
PCONN = chr(1) # portal session connect
PDISCONN = chr(2) # portal session disconnect
PSYNC = chr(3) # portal session sync
SLOGIN = chr(4) # server session login
SDISCONN = chr(5) # server session disconnect
SDISCONNALL = chr(6) # server session disconnect all
SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync
SCONN = chr(11) # server creating new connection (for irc bots and etc)
PCONNSYNC = chr(12) # portal post-syncing a session
PDISCONNALL = chr(13) # portal session disconnect all
PCONNSYNC = chr(12) # portal post-syncing a session
PDISCONNALL = chr(13) # portal session disconnect all
AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed)
BATCH_RATE = 250 # max commands/sec before switching to batch-sending
BATCH_TIMEOUT = 0.5 # how often to poll to empty batch queue, in seconds
BATCH_RATE = 250 # max commands/sec before switching to batch-sending
BATCH_TIMEOUT = 0.5 # how often to poll to empty batch queue, in seconds
# buffers
_SENDBATCH = defaultdict(list)
_MSGBUFFER = defaultdict(list)
import zlib
def get_restart_mode(restart_file):
"""
@ -323,9 +323,9 @@ dumps = lambda data: to_str(pickle.dumps(to_str(data), pickle.HIGHEST_PROTOCOL))
loads = lambda data: pickle.loads(to_str(data))
#------------------------------------------------------------
# -------------------------------------------------------------
# Core AMP protocol for communication Server <-> Portal
#------------------------------------------------------------
# -------------------------------------------------------------
class AMPProtocol(amp.AMP):
"""
@ -385,7 +385,6 @@ class AMPProtocol(amp.AMP):
"""
pass
# Error handling
def errback(self, e, info):
@ -447,7 +446,7 @@ class AMPProtocol(amp.AMP):
Access method called by the Portal and executed on the Portal.
Args:
sessid (int): Unique Session id.
session (session): Session
kwargs (any, optional): Optional data.
Returns:
@ -473,7 +472,6 @@ class AMPProtocol(amp.AMP):
self.factory.portal.sessions.data_out(session, **kwargs)
return {}
def send_MsgServer2Portal(self, session, **kwargs):
"""
Access method - executed on the Server for sending data
@ -506,7 +504,7 @@ class AMPProtocol(amp.AMP):
# create a new session and sync it
server_sessionhandler.portal_connect(kwargs.get("sessiondata"))
elif operation == PCONNSYNC: #portal_session_sync
elif operation == PCONNSYNC: # portal_session_sync
server_sessionhandler.portal_session_sync(kwargs.get("sessiondata"))
elif operation == PDISCONN: # portal_session_disconnect
@ -515,7 +513,7 @@ class AMPProtocol(amp.AMP):
if session:
server_sessionhandler.portal_disconnect(session)
elif operation == PDISCONNALL: # portal_disconnect_all
elif operation == PDISCONNALL: # portal_disconnect_all
# portal orders all sessions to close
server_sessionhandler.portal_disconnect_all()
@ -545,7 +543,7 @@ class AMPProtocol(amp.AMP):
"""
return self.send_data(AdminPortal2Server, session.sessid, operation=operation, **kwargs)
# Portal administraton from the Server side
# Portal administration from the Server side
@AdminServer2Portal.responder
def portal_receive_adminserver2portal(self, packed_data):
@ -562,7 +560,6 @@ class AMPProtocol(amp.AMP):
operation = kwargs.pop("operation")
portal_sessionhandler = self.factory.portal.sessions
if operation == SLOGIN: # server_session_login
# a session has authenticated; sync it.
session = portal_sessionhandler.get(sessid)
@ -591,7 +588,7 @@ class AMPProtocol(amp.AMP):
# set a flag in case we are about to shut down soon
self.factory.server_restart_mode = True
elif operation == SCONN: # server_force_connection (for irc/etc)
elif operation == SCONN: # server_force_connection (for irc/etc)
portal_sessionhandler.server_connect(**kwargs)
else:
@ -665,4 +662,5 @@ class AMPProtocol(amp.AMP):
module=modulepath,
function=functionname,
args=dumps(args),
kwargs=dumps(kwargs)).addCallback(lambda r: loads(r["result"])).addErrback(self.errback, "FunctionCall")
kwargs=dumps(kwargs)).addCallback(
lambda r: loads(r["result"])).addErrback(self.errback, "FunctionCall")

View file

@ -15,8 +15,7 @@ from evennia.server.models import ServerConfig
from evennia.utils import create, logger
ERROR_NO_SUPERUSER = \
"""
ERROR_NO_SUPERUSER = """
No superuser exists yet. The superuser is the 'owner' account on
the Evennia server. Create a new superuser using the command
@ -26,16 +25,14 @@ ERROR_NO_SUPERUSER = \
"""
LIMBO_DESC = \
_("""
Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if you need
LIMBO_DESC = _("""
Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if you need
help, want to contribute, report issues or just join the community.
As Player #1 you can create a demo/tutorial area with {w@batchcommand tutorial_world.build{n.
As Player #1 you can create a demo/tutorial area with |w@batchcommand tutorial_world.build|n.
""")
WARNING_POSTGRESQL_FIX = \
"""
WARNING_POSTGRESQL_FIX = """
PostgreSQL-psycopg2 compatibility fix:
The in-game channels {chan1}, {chan2} and {chan3} were created,
but the superuser was not yet connected to them. Please use in

View file

@ -40,18 +40,23 @@ IRC_MAGENTA = "13"
IRC_DGREY = "14"
IRC_GRAY = "15"
# test:
# obsolete test:
# {rred {ggreen {yyellow {bblue {mmagenta {ccyan {wwhite {xdgrey
# {Rdred {Gdgreen {Ydyellow {Bdblue {Mdmagenta {Cdcyan {Wlgrey {Xblack
# {[rredbg {[ggreenbg {[yyellowbg {[bbluebg {[mmagentabg {[ccyanbg {[wlgreybg {[xblackbg
# test:
# |rred |ggreen |yyellow |bblue |mmagenta |ccyan |wwhite |xdgrey
# |Rdred |Gdgreen |Ydyellow |Bdblue |Mdmagenta |Cdcyan |Wlgrey |Xblack
# |[rredbg |[ggreenbg |[yyellowbg |[bbluebg |[mmagentabg |[ccyanbg |[wlgreybg |[xblackbg
IRC_COLOR_MAP = dict([
# obs - {-type colors are deprecated but still used in many places.
(r'{n', IRC_RESET), # reset
(r'{n', IRC_RESET), # reset
(r'{/', ""), # line break
(r'{-', " "), # tab
(r'{_', " "), # space
(r'{*', ""), # invert
(r'{-', " "), # tab
(r'{_', " "), # space
(r'{*', ""), # invert
(r'{^', ""), # blinking text
(r'{r', IRC_COLOR + IRC_RED),
@ -69,7 +74,7 @@ IRC_COLOR_MAP = dict([
(r'{B', IRC_COLOR + IRC_DBLUE),
(r'{M', IRC_COLOR + IRC_DMAGENTA),
(r'{C', IRC_COLOR + IRC_DCYAN),
(r'{W', IRC_COLOR + IRC_GRAY), # light grey
(r'{W', IRC_COLOR + IRC_GRAY), # light grey
(r'{X', IRC_COLOR + IRC_BLACK), # pure black
(r'{[r', IRC_COLOR + IRC_NORMAL + "," + IRC_DRED),
@ -79,14 +84,14 @@ IRC_COLOR_MAP = dict([
(r'{[m', IRC_COLOR + IRC_NORMAL + "," + IRC_DMAGENTA),
(r'{[c', IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN),
(r'{[w', IRC_COLOR + IRC_NORMAL + "," + IRC_GRAY), # light grey background
(r'{[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK), # pure black background
(r'{[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK), # pure black background
# |-type formatting is the thing to use.
(r'|n', IRC_RESET), # reset
(r'|n', IRC_RESET), # reset
(r'|/', ""), # line break
(r'|-', " "), # tab
(r'|_', " "), # space
(r'|*', ""), # invert
(r'|-', " "), # tab
(r'|_', " "), # space
(r'|*', ""), # invert
(r'|^', ""), # blinking text
(r'|r', IRC_COLOR + IRC_RED),
@ -104,7 +109,7 @@ IRC_COLOR_MAP = dict([
(r'|B', IRC_COLOR + IRC_DBLUE),
(r'|M', IRC_COLOR + IRC_DMAGENTA),
(r'|C', IRC_COLOR + IRC_DCYAN),
(r'|W', IRC_COLOR + IRC_GRAY), # light grey
(r'|W', IRC_COLOR + IRC_GRAY), # light grey
(r'|X', IRC_COLOR + IRC_BLACK), # pure black
(r'|[r', IRC_COLOR + IRC_NORMAL + "," + IRC_DRED),
@ -114,12 +119,13 @@ IRC_COLOR_MAP = dict([
(r'|[m', IRC_COLOR + IRC_NORMAL + "," + IRC_DMAGENTA),
(r'|[c', IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN),
(r'|[w', IRC_COLOR + IRC_NORMAL + "," + IRC_GRAY), # light grey background
(r'|[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK) # pure black background
(r'|[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK) # pure black background
])
RE_IRC_COLOR = re.compile(r"|".join([re.escape(key) for key in viewkeys(IRC_COLOR_MAP)]), re.DOTALL)
RE_MXP = re.compile(r'\{lc(.*?)\{lt(.*?)\{le', re.DOTALL)
RE_MXP = re.compile(r'\|lc(.*?)\|lt(.*?)\|le', re.DOTALL)
RE_ANSI_ESCAPES = re.compile(r"(%s)" % "|".join(("{{", "%%", "\\\\")), re.DOTALL)
def sub_irc(ircmatch):
"""
Substitute irc color info. Used by re.sub.
@ -133,6 +139,7 @@ def sub_irc(ircmatch):
"""
return IRC_COLOR_MAP.get(ircmatch.group(), "")
def parse_irc_colors(string):
"""
Parse {-type syntax and replace with IRC color markers
@ -156,9 +163,10 @@ def parse_irc_colors(string):
# IRC bot
class IRCBot(irc.IRCClient, Session):
"""
An IRC bot that tracks actitivity in a channel as well
An IRC bot that tracks activity in a channel as well
as sends text to it when prompted
"""
@ -190,7 +198,7 @@ class IRCBot(irc.IRCClient, Session):
logger.log_info("IRC bot '%s' connected to %s at %s:%s." % (self.nickname, self.channel,
self.network, self.port))
def disconnect(self, reason=None):
def disconnect(self, reason=""):
"""
Called by sessionhandler to disconnect this protocol.
@ -198,7 +206,7 @@ class IRCBot(irc.IRCClient, Session):
reason (str): Motivation for the disconnect.
"""
self.sessionhandler.disconnect(self)
self.sessionhandler.disconnect(self, reason=reason)
self.stopping = True
self.transport.loseConnection()
@ -246,14 +254,14 @@ class IRCBot(irc.IRCClient, Session):
self.sendLine("NAMES %s" % self.channel)
def irc_RPL_NAMREPLY(self, prefix, params):
"Handles IRC NAME request returns (nicklist)"
""""Handles IRC NAME request returns (nicklist)"""
channel = params[2].lower()
if channel != self.channel.lower():
return
self.nicklist += params[3].split(' ')
def irc_RPL_ENDOFNAMES(self, prefix, params):
"Called when the nicklist has finished being returned."
"""Called when the nicklist has finished being returned."""
channel = params[1].lower()
if channel != self.channel.lower():
return
@ -271,7 +279,6 @@ class IRCBot(irc.IRCClient, Session):
"""
self.data_in(text="", type="ping", user="server", channel=self.channel, timing=time)
def data_in(self, text=None, **kwargs):
"""
Data IRC -> Server.

View file

@ -37,9 +37,9 @@ if os.name == 'nt':
# For Windows we need to handle pid files manually.
PORTAL_PIDFILE = os.path.join(settings.GAME_DIR, "server", 'portal.pid')
#------------------------------------------------------------
# -------------------------------------------------------------
# Evennia Portal settings
#------------------------------------------------------------
# -------------------------------------------------------------
VERSION = get_evennia_version()
@ -76,6 +76,8 @@ AMP_ENABLED = AMP_HOST and AMP_PORT and AMP_INTERFACE
# Maintenance function - this is called repeatedly by the portal.
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
def _portal_maintenance():
"""
The maintenance function handles repeated checks and updates that
@ -94,12 +96,12 @@ def _portal_maintenance():
if _IDLE_TIMEOUT > 0:
# only start the maintenance task if we care about idling.
_maintenance_task = LoopingCall(_portal_maintenance)
_maintenance_task.start(60) # called every minute
_maintenance_task.start(60) # called every minute
#------------------------------------------------------------
# -------------------------------------------------------------
# Portal Service object
#------------------------------------------------------------
# -------------------------------------------------------------
class Portal(object):
"""
@ -180,11 +182,11 @@ class Portal(object):
self.shutdown_complete = True
reactor.callLater(0, reactor.stop)
#------------------------------------------------------------
# -------------------------------------------------------------
#
# Start the Portal proxy server and add all active services
#
#------------------------------------------------------------
# -------------------------------------------------------------
# twistd requires us to define the variable 'application' so it knows
# what to execute from.

View file

@ -28,9 +28,11 @@ _CONNECTION_QUEUE = deque()
DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0)
#------------------------------------------------------------
# -------------------------------------------------------------
# Portal-SessionHandler class
#------------------------------------------------------------
# -------------------------------------------------------------
class PortalSessionHandler(SessionHandler):
"""
This object holds the sessions connected to the portal at any time.
@ -95,7 +97,7 @@ class PortalSessionHandler(SessionHandler):
if len(_CONNECTION_QUEUE) > 1:
session.data_out(text=[["%s DoS protection is active. You are queued to connect in %g seconds ..." % (
settings.SERVERNAME,
len(_CONNECTION_QUEUE)*_MIN_TIME_BETWEEN_CONNECTS)],{}])
len(_CONNECTION_QUEUE)*_MIN_TIME_BETWEEN_CONNECTS)], {}])
now = time.time()
if (now - self.connection_last < _MIN_TIME_BETWEEN_CONNECTS) or not self.portal.amp_protocol:
if not session or not self.connection_task:
@ -176,8 +178,7 @@ class PortalSessionHandler(SessionHandler):
del self[session.sessid]
# Tell the Server to disconnect its version of the Session as well.
self.portal.amp_protocol.send_AdminPortal2Server(session,
operation=PDISCONN)
self.portal.amp_protocol.send_AdminPortal2Server(session, operation=PDISCONN)
def disconnect_all(self):
"""
@ -194,7 +195,7 @@ class PortalSessionHandler(SessionHandler):
# inform Server; wait until finished sending before we continue
# removing all the sessions.
self.portal.amp_protocol.send_AdminPortal2Server(DUMMYSESSION,
operation=PDISCONNALL).addCallback(_callback, self)
operation=PDISCONNALL).addCallback(_callback, self)
def server_connect(self, protocol_path="", config=dict()):
"""
@ -233,8 +234,8 @@ class PortalSessionHandler(SessionHandler):
Called by server to force a disconnect by sessid.
Args:
sessid (int): Session id to disconnect.
reason (str, optional): Motivation for disconect.
session (portalsession): Session to disconnect.
reason (str, optional): Motivation for disconnect.
"""
if session:
@ -335,7 +336,7 @@ class PortalSessionHandler(SessionHandler):
"""
for session in self.values():
self.data_out(session, text=[[message],{}])
self.data_out(session, text=[[message], {}])
def data_in(self, session, **kwargs):
"""
@ -352,8 +353,8 @@ class PortalSessionHandler(SessionHandler):
Data is serialized before passed on.
"""
#from evennia.server.profiling.timetrace import timetrace
#text = timetrace(text, "portalsessionhandler.data_in")
# from evennia.server.profiling.timetrace import timetrace # DEBUG
# text = timetrace(text, "portalsessionhandler.data_in") # DEBUG
try:
text = kwargs['text']
if (_MAX_CHAR_LIMIT > 0) and len(text) > _MAX_CHAR_LIMIT:
@ -365,16 +366,16 @@ class PortalSessionHandler(SessionHandler):
pass
if session:
now = time.time()
if self.command_counter > _MAX_COMMAND_RATE:
if self.command_counter > _MAX_COMMAND_RATE > 0:
# data throttle (anti DoS measure)
dT = now - self.command_counter_reset
delta_time = now - self.command_counter_reset
self.command_counter = 0
self.command_counter_reset = now
self.command_overflow = dT < 1.0
self.command_overflow = delta_time < 1.0
if self.command_overflow:
reactor.callLater(1.0, self.data_in, None)
if self.command_overflow:
self.data_out(session, text=[[_ERROR_COMMAND_OVERFLOW],{}])
self.data_out(session, text=[[_ERROR_COMMAND_OVERFLOW], {}])
return
# scrub data
kwargs = self.clean_senddata(session, kwargs)
@ -385,7 +386,7 @@ class PortalSessionHandler(SessionHandler):
self.portal.amp_protocol.send_MsgPortal2Server(session,
**kwargs)
else:
# called by the callLater callback
# called by the callLater callback
if self.command_overflow:
self.command_overflow = False
reactor.callLater(1.0, self.data_in, None)
@ -405,8 +406,8 @@ class PortalSessionHandler(SessionHandler):
method exixts, it sends the data to a method send_default.
"""
#from evennia.server.profiling.timetrace import timetrace
#text = timetrace(text, "portalsessionhandler.data_out")
# from evennia.server.profiling.timetrace import timetrace # DEBUG
# text = timetrace(text, "portalsessionhandler.data_out") # DEBUG
# distribute outgoing data to the correct session methods.
if session:

View file

@ -32,7 +32,7 @@ packages).
try:
from twisted.conch.ssh.keys import Key
except ImportError:
raise ImportError (_SSH_IMPORT_ERROR)
raise ImportError(_SSH_IMPORT_ERROR)
from twisted.conch.ssh.userauth import SSHUserAuthServer
from twisted.conch.ssh import common
@ -49,7 +49,7 @@ from evennia.players.models import PlayerDB
from evennia.utils import ansi
from evennia.utils.utils import to_str
_RE_N = re.compile(r"\{n$")
_RE_N = re.compile(r"\|n$")
_RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE)
_GAME_DIR = settings.GAME_DIR
@ -206,7 +206,7 @@ class SshProtocol(Manhole, session.Session):
"""
for line in string.split('\n'):
#this is the telnet-specific method for sending
# the telnet-specific method for sending
self.terminal.write(line)
self.terminal.nextLine()
@ -255,7 +255,7 @@ class SshProtocol(Manhole, session.Session):
Note that it must be actively turned back on again!
"""
#print "telnet.send_text", args,kwargs
# print "telnet.send_text", args,kwargs # DEBUG
text = args[0] if args else ""
if text is None:
return
@ -268,8 +268,8 @@ class SshProtocol(Manhole, session.Session):
useansi = options.get("ansi", flags.get('ANSI', True))
raw = options.get("raw", flags.get("RAW", False))
nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi))
#echo = options.get("echo", None)
screenreader = options.get("screenreader", flags.get("SCREENREADER", False))
# echo = options.get("echo", None) # DEBUG
screenreader = options.get("screenreader", flags.get("SCREENREADER", False))
if screenreader:
# screenreader mode cleans up output
@ -283,7 +283,8 @@ class SshProtocol(Manhole, session.Session):
else:
# we need to make sure to kill the color at the end in order
# to match the webclient output.
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nocolor, xterm256=xterm256, mxp=False)
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("|n" if text[-1] != "|" else "||n"),
strip_ansi=nocolor, xterm256=xterm256, mxp=False)
self.sendLine(linetosend)
def send_prompt(self, *args, **kwargs):
@ -453,11 +454,10 @@ def makeFactory(configdict):
factory.publicKeys = {'ssh-rsa': publicKey}
factory.privateKeys = {'ssh-rsa': privateKey}
except Exception as err:
print ( "getKeyPair error: {err}\n WARNING: Evennia could not " \
"auto-generate SSH keypair. Using conch default keys instead.\n" \
"If this error persists, create {pub} and " \
"{priv} yourself using third-party tools.".format(
err=err, pub=pubkeyfile, priv=privkeyfile))
print("getKeyPair error: {err}\n WARNING: Evennia could not "
"auto-generate SSH keypair. Using conch default keys instead.\n"
"If this error persists, create {pub} and "
"{priv} yourself using third-party tools.".format(err=err, pub=pubkeyfile, priv=privkeyfile))
factory.services = factory.services.copy()
factory.services['ssh-userauth'] = ExtraInfoAuthServer

View file

@ -54,6 +54,7 @@ class SSLProtocol(TelnetProtocol):
super(SSLProtocol, self).__init__(*args, **kwargs)
self.protocol_name = "ssl"
def verify_SSL_key_and_cert(keyfile, certfile):
"""
This function looks for RSA key and certificate in the current
@ -82,7 +83,7 @@ def verify_SSL_key_and_cert(keyfile, certfile):
# try to create the certificate
CERT_EXPIRE = 365 * 20 # twenty years validity
# default:
#openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300
# openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300
exestring = "openssl req -new -x509 -key %s -out %s -days %s" % (keyfile, certfile, CERT_EXPIRE)
try:
subprocess.call(exestring)

View file

@ -19,12 +19,13 @@ from evennia.server.portal.mxp import Mxp, mxp_parse
from evennia.utils import ansi
from evennia.utils.utils import to_str
_RE_N = re.compile(r"\{n$")
_RE_N = re.compile(r"\|n$")
_RE_LEND = re.compile(r"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE)
_RE_LINEBREAK = re.compile(r"\n\r|\r\n|\n|\r", re.DOTALL + re.MULTILINE)
_RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE)
_IDLE_COMMAND = settings.IDLE_COMMAND + "\n"
class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
"""
Each player connecting over telnet (ie using most traditional mud
@ -46,7 +47,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
client_address = client_address[0] if client_address else None
# this number is counted down for every handshake that completes.
# when it reaches 0 the portal/server syncs their data
self.handshakes = 7 # naws, ttype, mccp, mssp, msdp, gmcp, mxp
self.handshakes = 7 # naws, ttype, mccp, mssp, msdp, gmcp, mxp
self.init_session(self.protocol_name, client_address, self.factory.sessionhandler)
# negotiate client size
@ -79,7 +80,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
self.toggle_nop_keepalive()
def _send_nop_keepalive(self):
"Send NOP keepalive unless flag is set"
"""Send NOP keepalive unless flag is set"""
if self.protocol_flags.get("NOPKEEPALIVE"):
self._write(IAC + NOP)
@ -140,7 +141,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
enable (bool): If this option should be enabled.
"""
return (option == MCCP or option==ECHO)
return option == MCCP or option == ECHO
def disableLocal(self, option):
"""
@ -178,7 +179,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
directly.
Args:
string (str): Incoming data.
data (str): Incoming data.
"""
if not data:
@ -188,7 +189,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# legacy clients. There should never be a reason to send a
# lone NULL character so this seems to be a safe thing to
# support for backwards compatibility. It also stops the
# NULL from continously popping up as an unknown command.
# NULL from continuously popping up as an unknown command.
data = [_IDLE_COMMAND]
else:
data = _RE_LINEBREAK.split(data)
@ -205,7 +206,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
self.data_in(text=dat + "\n")
def _write(self, data):
"hook overloading the one used in plain telnet"
"""hook overloading the one used in plain telnet"""
data = data.replace('\n', '\r\n').replace('\r\r\n', '\r\n')
super(TelnetProtocol, self)._write(mccp_compress(self, data))
@ -217,24 +218,23 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
line (str): Line to send.
"""
#escape IAC in line mode, and correctly add \r\n
# escape IAC in line mode, and correctly add \r\n
line += self.delimiter
line = line.replace(IAC, IAC + IAC).replace('\n', '\r\n')
return self.transport.write(mccp_compress(self, line))
# Session hooks
def disconnect(self, reason=None):
def disconnect(self, reason=""):
"""
generic hook for the engine to call in order to
disconnect this protocol.
Args:
reason (str): Reason for disconnecting.
reason (str, optional): Reason for disconnecting.
"""
self.data_out(text=((reason or "",), {}))
self.data_out(text=((reason,), {}))
self.connectionLost(reason)
def data_in(self, **kwargs):
@ -245,8 +245,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
kwargs (any): Options from the protocol.
"""
#from evennia.server.profiling.timetrace import timetrace
#text = timetrace(text, "telnet.data_in")
# from evennia.server.profiling.timetrace import timetrace # DEBUG
# text = timetrace(text, "telnet.data_in") # DEBUG
self.sessionhandler.data_in(self, **kwargs)
@ -297,7 +297,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi))
echo = options.get("echo", None)
mxp = options.get("mxp", flags.get("MXP", False))
screenreader = options.get("screenreader", flags.get("SCREENREADER", False))
screenreader = options.get("screenreader", flags.get("SCREENREADER", False))
if screenreader:
# screenreader mode cleans up output
@ -306,9 +306,11 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
if options.get("send_prompt"):
# send a prompt instead.
prompt = text
if not raw:
# processing
prompt = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nocolor, xterm256=xterm256)
prompt = ansi.parse_ansi(_RE_N.sub("", prompt) + ("|n" if prompt[-1] != "|" else "||n"),
strip_ansi=nocolor, xterm256=xterm256)
if mxp:
prompt = mxp_parse(prompt)
prompt = prompt.replace(IAC, IAC + IAC).replace('\n', '\r\n')
@ -335,7 +337,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
else:
# we need to make sure to kill the color at the end in order
# to match the webclient output.
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nocolor, xterm256=xterm256, mxp=mxp)
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("|n" if text[-1] != "|" else "||n"),
strip_ansi=nocolor, xterm256=xterm256, mxp=mxp)
if mxp:
linetosend = mxp_parse(linetosend)
self.sendLine(linetosend)
@ -348,7 +351,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
kwargs["options"].update({"send_prompt": True})
self.send_text(*args, **kwargs)
def send_default(self, cmdname, *args, **kwargs):
"""
Send other oob data

View file

@ -32,12 +32,12 @@ from evennia.utils.utils import to_str
# MSDP-relevant telnet cmd/opt-codes
MSDP = chr(69)
MSDP_VAR = chr(1) #^A
MSDP_VAL = chr(2) #^B
MSDP_TABLE_OPEN = chr(3) #^C
MSDP_TABLE_CLOSE = chr(4) #^D
MSDP_ARRAY_OPEN = chr(5) #^E
MSDP_ARRAY_CLOSE = chr(6) #^F
MSDP_VAR = chr(1) # ^A
MSDP_VAL = chr(2) # ^B
MSDP_TABLE_OPEN = chr(3) # ^C
MSDP_TABLE_CLOSE = chr(4) # ^D
MSDP_ARRAY_OPEN = chr(5) # ^E
MSDP_ARRAY_CLOSE = chr(6) # ^F
# GMCP
GMCP = chr(201)
@ -51,13 +51,15 @@ force_str = lambda inp: to_str(inp, force_string=True)
# pre-compiled regexes
# returns 2-tuple
msdp_regex_table = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL,
MSDP_TABLE_OPEN,
MSDP_TABLE_CLOSE))
msdp_regex_table = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s"
% (MSDP_VAR, MSDP_VAL,
MSDP_TABLE_OPEN,
MSDP_TABLE_CLOSE))
# returns 2-tuple
msdp_regex_array = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL,
MSDP_ARRAY_OPEN,
MSDP_ARRAY_CLOSE))
msdp_regex_array = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s"
% (MSDP_VAR, MSDP_VAL,
MSDP_ARRAY_OPEN,
MSDP_ARRAY_CLOSE))
msdp_regex_var = re.compile(r"%s" % MSDP_VAR)
msdp_regex_val = re.compile(r"%s" % MSDP_VAL)
@ -67,7 +69,8 @@ EVENNIA_TO_GMCP = {"client_options": "Core.Supports.Get",
"repeat": "Char.Repeat.Update",
"monitor": "Char.Monitor.Update"}
# Msdp object handler
# MSDP/GMCP communication handler
class TelnetOOB(object):
"""
@ -100,7 +103,7 @@ class TelnetOOB(object):
Client reports No msdp supported or wanted.
Args:
options (Option): Not used.
option (Option): Not used.
"""
# no msdp, check GMCP
@ -173,7 +176,7 @@ class TelnetOOB(object):
if not (args or kwargs):
return msdp_cmdname
#print "encode_msdp in:", cmdname, args, kwargs
# print("encode_msdp in:", cmdname, args, kwargs) # DEBUG
msdp_args = ''
if args:
@ -182,30 +185,30 @@ class TelnetOOB(object):
msdp_args += args[0]
else:
msdp_args += "{msdp_array_open}" \
"{msdp_args}" \
"{msdp_array_close}".format(
msdp_array_open=MSDP_ARRAY_OPEN,
msdp_array_close=MSDP_ARRAY_CLOSE,
msdp_args= "".join("%s%s" % (
MSDP_VAL, json.dumps(val))
for val in args))
"{msdp_args}" \
"{msdp_array_close}".format(
msdp_array_open=MSDP_ARRAY_OPEN,
msdp_array_close=MSDP_ARRAY_CLOSE,
msdp_args="".join("%s%s"
% (MSDP_VAL, json.dumps(val))
for val in args))
msdp_kwargs = ""
if kwargs:
msdp_kwargs = msdp_cmdname
msdp_kwargs += "{msdp_table_open}" \
"{msdp_kwargs}" \
"{msdp_table_close}".format(
msdp_table_open=MSDP_TABLE_OPEN,
msdp_table_close=MSDP_TABLE_CLOSE,
msdp_kwargs = "".join("%s%s%s%s" % (
MSDP_VAR, key, MSDP_VAL, json.dumps(val))
for key, val in kwargs.iteritems()))
"{msdp_kwargs}" \
"{msdp_table_close}".format(
msdp_table_open=MSDP_TABLE_OPEN,
msdp_table_close=MSDP_TABLE_CLOSE,
msdp_kwargs="".join("%s%s%s%s"
% (MSDP_VAR, key, MSDP_VAL,
json.dumps(val))
for key, val in kwargs.iteritems()))
msdp_string = msdp_args + msdp_kwargs
#print "msdp_string:", msdp_string
# print("msdp_string:", msdp_string) # DEBUG
return msdp_string
def encode_gmcp(self, cmdname, *args, **kwargs):
@ -238,10 +241,10 @@ class TelnetOOB(object):
gmcp_string = "%s %s" % (cmdname, json.dumps([args, kwargs]))
else:
gmcp_string = "%s %s" % (cmdname, json.dumps(args))
else: # only kwargs
else: # only kwargs
gmcp_string = "%s %s" % (cmdname, json.dumps(kwargs))
#print "gmcp string", gmcp_string
# print("gmcp string", gmcp_string) # DEBUG
return gmcp_string
def decode_msdp(self, data):
@ -271,7 +274,7 @@ class TelnetOOB(object):
if hasattr(data, "__iter__"):
data = "".join(data)
#print "decode_msdp in:", data
# print("decode_msdp in:", data) # DEBUG
tables = {}
arrays = {}
@ -279,7 +282,7 @@ class TelnetOOB(object):
# decode tables
for key, table in msdp_regex_table.findall(data):
tables[key] = {} if not key in tables else tables[key]
tables[key] = {} if key not in tables else tables[key]
for varval in msdp_regex_var.split(table)[1:]:
var, val = msdp_regex_val.split(varval, 1)
if var:
@ -288,7 +291,7 @@ class TelnetOOB(object):
# decode arrays from all that was not a table
data_no_tables = msdp_regex_table.sub("", data)
for key, array in msdp_regex_array.findall(data_no_tables):
arrays[key] = [] if not key in arrays else arrays[key]
arrays[key] = [] if key not in arrays else arrays[key]
parts = msdp_regex_val.split(array)
if len(parts) == 2:
arrays[key].append(parts[1])
@ -326,10 +329,9 @@ class TelnetOOB(object):
for key, var in variables.iteritems():
cmds[key] = [[var], {}]
#print "msdp data in:", cmds
# print("msdp data in:", cmds) # DEBUG
self.protocol.data_in(**cmds)
def decode_gmcp(self, data):
"""
Decodes incoming GMCP data on the form 'varname <structure>'.
@ -353,7 +355,7 @@ class TelnetOOB(object):
if hasattr(data, "__iter__"):
data = "".join(data)
#print "decode_gmcp in:", data
# print("decode_gmcp in:", data) # DEBUG
if data:
try:
cmdname, structure = data.split(None, 1)
@ -368,7 +370,7 @@ class TelnetOOB(object):
args, kwargs = [], {}
if hasattr(structure, "__iter__"):
if isinstance(structure, dict):
kwargs = {key: value for key, value in structure.iteritems() if key }
kwargs = {key: value for key, value in structure.iteritems() if key}
else:
args = list(structure)
else:

View file

@ -27,6 +27,7 @@ MTTS = [(128, 'PROXY'),
(2, 'VT100'),
(1, 'ANSI')]
class Ttype(object):
"""
Handles ttype negotiations. Called and initiated by the
@ -104,22 +105,21 @@ class Ttype(object):
# use name to identify support for xterm256. Many of these
# only support after a certain version, but all support
# it since at least 4 years. We assume recent client here for now.
xterm256 = False
cupper = clientname.upper()
if cupper.startswith("MUDLET"):
# supports xterm256 stably since 1.1 (2010?)
xterm256 = cupper.split("MUDLET",1)[1].strip() >= "1.1"
xterm256 = cupper.split("MUDLET", 1)[1].strip() >= "1.1"
else:
xterm256 = (cupper.startswith("XTERM") or
cupper.endswith("-256COLOR") or
cupper in ("ATLANTIS", # > 0.9.9.0 (aug 2009)
"CMUD", # > 3.04 (mar 2009)
"KILDCLIENT", # > 2.2.0 (sep 2005)
"MUDLET", # > beta 15 (sep 2009)
"MUSHCLIENT", # > 4.02 (apr 2007)
"PUTTY", # > 0.58 (apr 2005)
"BEIP", # > 2.00.206 (late 2009) (BeipMu)
"POTATO")) # > 2.00 (maybe earlier)
"CMUD", # > 3.04 (mar 2009)
"KILDCLIENT", # > 2.2.0 (sep 2005)
"MUDLET", # > beta 15 (sep 2009)
"MUSHCLIENT", # > 4.02 (apr 2007)
"PUTTY", # > 0.58 (apr 2005)
"BEIP", # > 2.00.206 (late 2009) (BeipMu)
"POTATO")) # > 2.00 (maybe earlier)
# all clients supporting TTYPE at all seem to support ANSI
self.protocol.protocol_flags['ANSI'] = True

View file

@ -32,8 +32,9 @@ from django.utils.translation import ugettext as _
# Handlers for Session.db/ndb operation
class NDbHolder(object):
"Holder for allowing property access of attributes"
"""Holder for allowing property access of attributes"""
def __init__(self, obj, name, manager_name='attributes'):
_SA(self, name, _GA(obj, manager_name))
_SA(self, 'name', name)
@ -145,9 +146,9 @@ class NAttributeHandler(object):
return [key for key in self._store if not key.startswith("_")]
#------------------------------------------------------------
# -------------------------------------------------------------
# Server Session
#------------------------------------------------------------
# -------------------------------------------------------------
class ServerSession(Session):
"""
@ -160,7 +161,7 @@ class ServerSession(Session):
"""
def __init__(self):
"Initiate to avoid AttributeErrors down the line"
"""Initiate to avoid AttributeErrors down the line"""
self.puppet = None
self.player = None
self.cmdset_storage_string = ""
@ -203,7 +204,7 @@ class ServerSession(Session):
obj.player = self.player
self.puid = obj.id
self.puppet = obj
#obj.scripts.validate()
# obj.scripts.validate()
obj.locks.cache_lock_bypass(obj)
def at_login(self, player):
@ -264,7 +265,6 @@ class ServerSession(Session):
MONITOR_HANDLER.remove(player, "_saved_webclient_options",
self.sessid)
def get_player(self):
"""
Get the player associated with this session
@ -364,7 +364,6 @@ class ServerSession(Session):
self.protocol_flags.update(kwargs)
self.sessionhandler.session_portal_sync(self)
def data_out(self, **kwargs):
"""
Sending data from Evennia->Client
@ -437,7 +436,7 @@ class ServerSession(Session):
self.sessionhandler.data_in(self, **kwargs)
def __eq__(self, other):
"Handle session comparisons"
"""Handle session comparisons"""
try:
return self.address == other.address
except AttributeError:
@ -462,11 +461,9 @@ class ServerSession(Session):
return "%s%s@%s" % (self.uname, symbol, address)
def __unicode__(self):
"Unicode representation"
"""Unicode representation"""
return u"%s" % str(self)
# Dummy API hooks for use during non-loggedin operation
def at_cmdset_get(self, **kwargs):
@ -488,7 +485,7 @@ class ServerSession(Session):
def attributes(self):
return self.nattributes
#@property
# @property
def ndb_get(self):
"""
A non-persistent store (ndb: NonDataBase). Everything stored
@ -503,7 +500,7 @@ class ServerSession(Session):
self._ndb_holder = NDbHolder(self, "nattrhandler", manager_name="nattributes")
return self._ndb_holder
#@ndb.setter
# @ndb.setter
def ndb_set(self, value):
"""
Stop accidentally replacing the db object
@ -516,9 +513,9 @@ class ServerSession(Session):
string += "Use ndb.attr=value instead."
raise Exception(string)
#@ndb.deleter
# @ndb.deleter
def ndb_del(self):
"Stop accidental deletion."
"""Stop accidental deletion."""
raise Exception("Cannot delete the ndb object!")
ndb = property(ndb_get, ndb_set, ndb_del)
db = property(ndb_get, ndb_set, ndb_del)
@ -526,5 +523,5 @@ class ServerSession(Session):
# Mock access method for the session (there is no lock info
# at this stage, so we just present a uniform API)
def access(self, *args, **kwargs):
"Dummy method to mimic the logged-in API."
"""Dummy method to mimic the logged-in API."""
return True

View file

@ -23,6 +23,8 @@ from twisted.web.wsgi import WSGIResource
from django.conf import settings
from django.core.handlers.wsgi import WSGIHandler
from evennia.utils import logger
_UPSTREAM_IPS = settings.UPSTREAM_IPS
_DEBUG = settings.DEBUG
@ -70,6 +72,7 @@ class EvenniaReverseProxyResource(ReverseProxyResource):
resource (EvenniaReverseProxyResource): A proxy resource.
"""
request.notifyFinish().addErrback(lambda f: logger.log_trace("%s\nCaught errback in webserver.py:75." % f))
return EvenniaReverseProxyResource(
self.host, self.port, self.path + '/' + urlquote(path, safe=""),
self.reactor)
@ -98,6 +101,8 @@ class EvenniaReverseProxyResource(ReverseProxyResource):
request.getAllHeaders(), request.content.read(), request)
clientFactory.noisy = False
self.reactor.connectTCP(self.host, self.port, clientFactory)
# don't trigger traceback if connection is lost before request finish.
request.notifyFinish().addErrback(lambda f: f.cancel())
return NOT_DONE_YET