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:
parent
d4a78a11a6
commit
31687b8a05
8 changed files with 65 additions and 10 deletions
|
|
@ -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 {}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue