Mde the SSH protocol compliant with the new system, as per #924. There is no known client with SSH OOB capabilities, so this protocol only accepts text output and will output prompts on the same line.

This commit is contained in:
Griatch 2016-02-20 16:20:32 +01:00
parent a28ae2728e
commit ccdc58d652

View file

@ -11,11 +11,18 @@ Using standard ssh client,
from __future__ import print_function from __future__ import print_function
from builtins import object from builtins import object
import os import os
import re
from twisted.cred.checkers import credentials from twisted.cred.checkers import credentials
from twisted.cred.portal import Portal from twisted.cred.portal import Portal
from twisted.conch.ssh.keys import Key
from twisted.conch.interfaces import IConchUser from twisted.conch.interfaces import IConchUser
try:
from twisted.conch.ssh.keys import Key
except ImportError:
raise ImportError ("To use SSH, you need to install the crypto libraries:\n"
" pip install pycrypto pyasn1\n")
from twisted.conch.ssh.userauth import SSHUserAuthServer from twisted.conch.ssh.userauth import SSHUserAuthServer
from twisted.conch.ssh import common from twisted.conch.ssh import common
from twisted.conch.insults import insults from twisted.conch.insults import insults
@ -25,11 +32,15 @@ from twisted.internet import defer
from twisted.conch import interfaces as iconch from twisted.conch import interfaces as iconch
from twisted.python import components from twisted.python import components
from django.conf import settings from django.conf import settings
from evennia.server import session from evennia.server import session
from evennia.players.models import PlayerDB from evennia.players.models import PlayerDB
from evennia.utils import ansi, utils from evennia.utils import ansi
from evennia.utils.utils import to_str
ENCODINGS = settings.ENCODINGS _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
CTRL_C = '\x03' CTRL_C = '\x03'
CTRL_D = '\x04' CTRL_D = '\x04'
@ -75,6 +86,7 @@ class SshProtocol(Manhole, session.Session):
# initialize the session # initialize the session
client_address = self.getClientAddress() client_address = self.getClientAddress()
client_address = client_address.host if client_address else None
self.init_session("ssh", client_address, self.cfactory.sessionhandler) self.init_session("ssh", client_address, self.cfactory.sessionhandler)
# since we might have authenticated already, we might set this here. # since we might have authenticated already, we might set this here.
@ -170,9 +182,9 @@ class SshProtocol(Manhole, session.Session):
string (str): Input text. string (str): Input text.
""" """
self.sessionhandler.data_in(self, string) self.sessionhandler.data_in(self, text=string)
def lineSend(self, string): def sendLine(self, string):
""" """
Communication Evennia -> User. Any string sent should Communication Evennia -> User. Any string sent should
already have been properly formatted and processed before already have been properly formatted and processed before
@ -201,28 +213,74 @@ class SshProtocol(Manhole, session.Session):
self.data_out(reason) self.data_out(reason)
self.connectionLost(reason) self.connectionLost(reason)
def data_out(self, text=None, **kwargs): def data_out(self, **kwargs):
""" """
Data Evennia -> User access hook. 'data' argument is a dict Data Evennia -> User
parsed for string settings.
Kwargs: Kwargs:
text (str): Text to send. kwargs (any): Options to the protocol.
raw (bool): Leave all ansi markup and tokens unparsed
nomarkup (bool): Remove all ansi markup.
""" """
try: self.sessionhandler.data_out(self, **kwargs)
text = utils.to_str(text if text else "", encoding=self.encoding)
except Exception as e: def send_text(self, *args, **kwargs):
self.lineSend(str(e)) """
Send text data. This is an in-band telnet operation.
Args:
text (str): The first argument is always the text string to send. No other arguments
are considered.
Kwargs:
options (dict): Send-option flags
- mxp: Enforce MXP link support.
- ansi: Enforce no ANSI colors.
- xterm256: Enforce xterm256 colors, regardless of TTYPE.
- noxterm256: Enforce no xterm256 color support, regardless of TTYPE.
- nomarkup: Strip all ANSI markup. This is the same as noxterm256,noansi
- raw: Pass string through without any ansi processing
(i.e. include Evennia ansi markers but do not
convert them into ansi tokens)
- echo: Turn on/off line echo on the client. Turn
off line echo for client, for example for password.
Note that it must be actively turned back on again!
"""
#print "telnet.send_text", args,kwargs
text = args[0] if args else ""
if text is None:
return return
raw = kwargs.get("raw", False) text = to_str(text, force_string=True)
nomarkup = kwargs.get("nomarkup", False)
# handle arguments
options = kwargs.get("options", {})
ttype = self.protocol_flags.get('TTYPE', {})
xterm256 = options.get("xterm256", ttype.get('256 COLORS', False) if ttype.get("init_done") else True)
useansi = options.get("ansi", ttype and ttype.get('ANSI', False) if ttype.get("init_done") else True)
raw = options.get("raw", False)
nomarkup = options.get("nomarkup", not (xterm256 or useansi))
#echo = options.get("echo", None)
screenreader = options.get("screenreader", self.screenreader)
if screenreader:
# screenreader mode cleans up output
text = ansi.parse_ansi(text, strip_ansi=True, xterm256=False, mxp=False)
text = _RE_SCREENREADER_REGEX.sub("", text)
if raw: if raw:
self.lineSend(text) # no processing
self.sendLine(text)
return
else: else:
self.lineSend(ansi.parse_ansi(text.strip("{r") + "{r", strip_ansi=nomarkup)) # 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=nomarkup, xterm256=xterm256, mxp=False)
self.sendLine(linetosend)
def send_prompt(self, *args, **kwargs):
self.send_text(*args, **kwargs)
def send_default(self, *args, **kwargs):
pass
class ExtraInfoAuthServer(SSHUserAuthServer): class ExtraInfoAuthServer(SSHUserAuthServer):
@ -274,7 +332,7 @@ class PlayerDBPasswordChecker(object):
password = up.password password = up.password
player = PlayerDB.objects.get_player_from_name(username) player = PlayerDB.objects.get_player_from_name(username)
res = (None, self.factory) res = (None, self.factory)
if player and player.user.check_password(password): if player and player.check_password(password):
res = (player, self.factory) res = (player, self.factory)
return defer.succeed(res) return defer.succeed(res)
@ -304,7 +362,7 @@ class TerminalSessionTransport_getPeer(object):
""" """
Taken from twisted's TerminalSessionTransport which doesn't Taken from twisted's TerminalSessionTransport which doesn't
provide getPeer to the transport. This one does. provide getPeer to the transport. This one does.
j
""" """
def __init__(self, proto, chainedProtocol, avatar, width, height): def __init__(self, proto, chainedProtocol, avatar, width, height):
self.proto = proto self.proto = proto
@ -322,7 +380,7 @@ class TerminalSessionTransport_getPeer(object):
self.proto.loseConnection() self.proto.loseConnection()
def getPeer(): def getPeer():
session.conn.transport.transport.getPeer() return session.conn.transport.transport.getPeer()
self.chainedProtocol.makeConnection( self.chainedProtocol.makeConnection(
_Glue(getPeer=getPeer, write=self.proto.write, _Glue(getPeer=getPeer, write=self.proto.write,
@ -364,8 +422,8 @@ def makeFactory(configdict):
Creates the ssh server factory. Creates the ssh server factory.
""" """
pubkeyfile = "ssh-public.key" pubkeyfile = os.path.join(_GAME_DIR, "server", "ssh-public.key")
privkeyfile = "ssh-private.key" privkeyfile = os.path.join(_GAME_DIR, "server", "ssh-private.key")
def chainProtocolFactory(username=None): def chainProtocolFactory(username=None):
return insults.ServerProtocol( return insults.ServerProtocol(
@ -384,9 +442,12 @@ def makeFactory(configdict):
publicKey, privateKey = getKeyPair(pubkeyfile, privkeyfile) publicKey, privateKey = getKeyPair(pubkeyfile, privkeyfile)
factory.publicKeys = {'ssh-rsa': publicKey} factory.publicKeys = {'ssh-rsa': publicKey}
factory.privateKeys = {'ssh-rsa': privateKey} factory.privateKeys = {'ssh-rsa': privateKey}
except Exception as e: except Exception as err:
print(" getKeyPair error: %(e)s\n WARNING: Evennia could not auto-generate SSH keypair. Using conch default keys instead." % {'e': e}) print ( "getKeyPair error: {err}\n WARNING: Evennia could not " \
print(" If this error persists, create game/%(pub)s and game/%(priv)s yourself using third-party tools." % {'pub': pubkeyfile, 'priv': privkeyfile}) "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 = factory.services.copy()
factory.services['ssh-userauth'] = ExtraInfoAuthServer factory.services['ssh-userauth'] = ExtraInfoAuthServer