Made telnet protocols resync with server once their handshakes are complete. Also changed default (pre-TTYPE) to be ansi+xterm256. Set a 5-second timeout for handshakes. This pertains to issue #434.

This commit is contained in:
Griatch 2014-08-14 10:28:50 +02:00
parent d4a78a11a6
commit 31687b8a05
8 changed files with 65 additions and 10 deletions

View file

@ -39,6 +39,7 @@ SDISCONNALL = chr(6) # server session disconnect all
SSHUTD = chr(7) # server shutdown SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync SSYNC = chr(8) # server session sync
SCONN = chr(9) # server creating new connectiong (for irc/imc2 bots etc) SCONN = chr(9) # server creating new connectiong (for irc/imc2 bots etc)
PCONNSYNC = chr(10) # portal post-syncing a session
MAXLEN = 65535 # max allowed data length in AMP protocol MAXLEN = 65535 # max allowed data length in AMP protocol
_MSGBUFFER = defaultdict(list) _MSGBUFFER = defaultdict(list)
@ -412,6 +413,9 @@ class AMPProtocol(amp.AMP):
# create a new session and sync it # create a new session and sync it
server_sessionhandler.portal_connect(data) server_sessionhandler.portal_connect(data)
elif operation == PCONNSYNC: #portal_session_sync
server_sessionhandler.portal_session_sync(data)
elif operation == PDISCONN: # portal_session_disconnect elif operation == PDISCONN: # portal_session_disconnect
# session closed from portal side # session closed from portal side
self.factory.server.sessions.portal_disconnect(sessid) self.factory.server.sessions.portal_disconnect(sessid)
@ -422,7 +426,7 @@ class AMPProtocol(amp.AMP):
# contains a dict {sessid: {arg1:val1,...}} # contains a dict {sessid: {arg1:val1,...}}
# representing the attributes to sync for each # representing the attributes to sync for each
# session. # session.
server_sessionhandler.portal_session_sync(data) server_sessionhandler.portal_sessions_sync(data)
else: else:
raise Exception("operation %(op)s not recognized." % {'op': operation}) raise Exception("operation %(op)s not recognized." % {'op': operation})
return {} return {}

View file

@ -54,6 +54,7 @@ class Mccp(object):
if hasattr(self.protocol, 'zlib'): if hasattr(self.protocol, 'zlib'):
del self.protocol.zlib del self.protocol.zlib
self.protocol.protocol_flags['MCCP'] = False self.protocol.protocol_flags['MCCP'] = False
self.protocol.handshake_done()
def do_mccp(self, option): def do_mccp(self, option):
""" """
@ -63,3 +64,4 @@ class Mccp(object):
self.protocol.protocol_flags['MCCP'] = True self.protocol.protocol_flags['MCCP'] = True
self.protocol.requestNegotiation(MCCP, '') self.protocol.requestNegotiation(MCCP, '')
self.protocol.zlib = zlib.compressobj(9) self.protocol.zlib = zlib.compressobj(9)
self.protocol.handshake_done()

View file

@ -60,13 +60,14 @@ class Msdp(object):
def no_msdp(self, option): def no_msdp(self, option):
"No msdp supported or wanted" "No msdp supported or wanted"
pass self.protocol.handshake_done()
def do_msdp(self, option): def do_msdp(self, option):
""" """
Called when client confirms that it can do MSDP. Called when client confirms that it can do MSDP.
""" """
self.protocol.protocol_flags['MSDP'] = True self.protocol.protocol_flags['MSDP'] = True
self.protocol.handshake_done()
def evennia_to_msdp(self, cmdname, *args, **kwargs): def evennia_to_msdp(self, cmdname, *args, **kwargs):
""" """

View file

@ -47,6 +47,7 @@ class Mssp(object):
""" """
This is the normal operation. This is the normal operation.
""" """
self.protocol.handshake_done()
pass pass
def do_mssp(self, option): def do_mssp(self, option):
@ -181,3 +182,4 @@ class Mssp(object):
# send to crawler by subnegotiation # send to crawler by subnegotiation
self.protocol.requestNegotiation(MSSP, varlist) self.protocol.requestNegotiation(MSSP, varlist)
self.protocol.handshake_done()

View file

@ -2,7 +2,7 @@
Sessionhandler for portal sessions Sessionhandler for portal sessions
""" """
import time import time
from src.server.sessionhandler import SessionHandler, PCONN, PDISCONN from src.server.sessionhandler import SessionHandler, PCONN, PDISCONN, PSYNC, PCONNSYNC
_MOD_IMPORT = None _MOD_IMPORT = None
@ -54,6 +54,19 @@ class PortalSessionHandler(SessionHandler):
self.portal.amp_protocol.call_remote_ServerAdmin(sessid, self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
operation=PCONN, operation=PCONN,
data=sessdata) data=sessdata)
def sync(self, session):
"""
Called by the protocol of an already connected session. This
can be used to sync the session info in a delayed manner,
such as when negotiation and handshakes are delayed.
"""
if session.sessid:
# only use if session already has sessid (i.e. has already connected)
sessdata = session.get_sync_data()
if self.portal.amp_protocol:
self.portal.amp_protocol.call_remote_ServerAdmin(session.sessid,
operation=PCONNSYNC,
data=sessdata)
def disconnect(self, session): def disconnect(self, session):
""" """

View file

@ -30,6 +30,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
""" """
# initialize the session # initialize the session
self.iaw_mode = False self.iaw_mode = False
self.handshakes = 4 # ttype, mccp, mssp, msdp
client_address = self.transport.client client_address = self.transport.client
self.init_session("telnet", client_address, self.factory.sessionhandler) self.init_session("telnet", client_address, self.factory.sessionhandler)
# negotiate ttype (client info) # negotiate ttype (client info)
@ -43,6 +44,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
self.msdp = msdp.Msdp(self) self.msdp = msdp.Msdp(self)
# add this new connection to sessionhandler so # add this new connection to sessionhandler so
# the Server becomes aware of it. # the Server becomes aware of it.
self.sessionhandler.connect(self)
# This is a fix to make sure the connection does not # This is a fix to make sure the connection does not
# continue until the handshakes are done. This is a # continue until the handshakes are done. This is a
@ -54,9 +56,26 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# to their defaults since sessionhandler.connect will sync # to their defaults since sessionhandler.connect will sync
# before the handshakes have had time to finish. Keeping this patch # before the handshakes have had time to finish. Keeping this patch
# until coming up with a more elegant solution /Griatch # until coming up with a more elegant solution /Griatch
from src.utils.utils import delay from src.utils.utils import delay
delay(1, callback=self.sessionhandler.connect, retval=self) delay(5, callback=self.handshake_done, retval=True)
#self.sessionhandler.connect(self)
def handshake_done(self, force=False):
"""
This is called by all telnet extensions once they are finished.
When all have reported, a sync with the server is performed.
The system will force-call this sync after a small time to handle
clients that don't reply to handshakes at all.
info - debug text from the protocol calling
"""
if self.handshakes > 0:
if force:
self.sessionhandler.sync(self)
return
self.handshakes -= 1
if self.handshakes <= 0:
# do the sync
self.sessionhandler.sync(self)
def enableRemote(self, option): def enableRemote(self, option):
""" """
@ -208,10 +227,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# parse **kwargs, falling back to ttype if nothing is given explicitly # parse **kwargs, falling back to ttype if nothing is given explicitly
ttype = self.protocol_flags.get('TTYPE', {}) ttype = self.protocol_flags.get('TTYPE', {})
xterm256 = kwargs.get("xterm256", ttype and ttype.get('256 COLORS', False)) xterm256 = kwargs.get("xterm256", ttype.get('256 COLORS', False) if ttype.get("init_done") else True)
useansi = kwargs.get("ansi", ttype and ttype.get('ANSI', False)) useansi = kwargs.get("ansi", ttype and ttype.get('ANSI', False) if ttype.get("init_done") else True)
raw = kwargs.get("raw", False) raw = kwargs.get("raw", False)
nomarkup = kwargs.get("nomarkup", not (xterm256 or useansi) or not ttype.get("init_done")) nomarkup = kwargs.get("nomarkup", not (xterm256 or useansi))
prompt = kwargs.get("prompt") prompt = kwargs.get("prompt")
if prompt: if prompt:
@ -229,5 +248,5 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
else: else:
# we need to make sure to kill the color at the end in order # we need to make sure to kill the color at the end in order
# to match the webclient output. # to match the webclient output.
# print "telnet data out:", self.protocol_flags, id(self.protocol_flags), id(self) #print "telnet data out:", self.protocol_flags, id(self.protocol_flags), id(self), "nomarkup: %s, xterm256: %s" % (nomarkup, xterm256)
self.sendLine(ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nomarkup, xterm256=xterm256)) self.sendLine(ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nomarkup, xterm256=xterm256))

View file

@ -54,6 +54,7 @@ class Ttype(object):
Callback if ttype is not supported by client. Callback if ttype is not supported by client.
""" """
self.protocol.protocol_flags['TTYPE']["init_done"] = True self.protocol.protocol_flags['TTYPE']["init_done"] = True
self.protocol.handshake_done()
def will_ttype(self, option): def will_ttype(self, option):
""" """
@ -139,4 +140,6 @@ class Ttype(object):
self.protocol.protocol_flags['TTYPE']['init_done'] = True self.protocol.protocol_flags['TTYPE']['init_done'] = True
# print "TTYPE final:", self.protocol.protocol_flags['TTYPE'] # print "TTYPE final:", self.protocol.protocol_flags['TTYPE']
# we must sync ttype once it'd done
self.protocol.handshake_done()
self.ttype_step += 1 self.ttype_step += 1

View file

@ -38,6 +38,7 @@ SDISCONNALL = chr(6) # server session disconnect all
SSHUTD = chr(7) # server shutdown SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync SSYNC = chr(8) # server session sync
SCONN = chr(9) # server portal connection (for bots) SCONN = chr(9) # server portal connection (for bots)
PCONNSYNC = chr(10) # portal post-syncing session
# i18n # i18n
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -210,6 +211,16 @@ class ServerSessionHandler(SessionHandler):
self.sessions[sess.sessid] = sess self.sessions[sess.sessid] = sess
sess.data_in(CMD_LOGINSTART) sess.data_in(CMD_LOGINSTART)
def portal_session_sync(self, portalsessiondata):
"""
Called by Portal when it wants to update a single session (e.g.
because of all negotiation protocols have finally replied)
"""
sessid = portalsessiondata.get("sessid")
session = self.sessions.get(sessid)
if session:
session.load_sync_data(portalsessiondata)
def portal_disconnect(self, sessid): def portal_disconnect(self, sessid):
""" """
Called by Portal when portal reports a closing of a session Called by Portal when portal reports a closing of a session
@ -227,7 +238,7 @@ class ServerSessionHandler(SessionHandler):
session.disconnect() session.disconnect()
del self.sessions[session.sessid] del self.sessions[session.sessid]
def portal_session_sync(self, portalsessions): def portal_sessions_sync(self, portalsessions):
""" """
Syncing all session ids of the portal with the ones of the Syncing all session ids of the portal with the ones of the
server. This is instantiated by the portal when reconnecting. server. This is instantiated by the portal when reconnecting.