166 lines
5.2 KiB
Python
166 lines
5.2 KiB
Python
# Copyright (c) Twisted Matrix Laboratories.
|
|
# See LICENSE for details.
|
|
|
|
"""
|
|
TAP plugin for creating telnet- and ssh-accessible manhole servers.
|
|
|
|
@author: Jp Calderone
|
|
"""
|
|
|
|
from zope.interface import implementer
|
|
|
|
from twisted.internet import protocol
|
|
from twisted.application import service, strports
|
|
from twisted.cred import portal, checkers
|
|
from twisted.python import usage, filepath
|
|
|
|
from twisted.conch import manhole, manhole_ssh, telnet
|
|
from twisted.conch.insults import insults
|
|
from twisted.conch.ssh import keys
|
|
|
|
|
|
|
|
class makeTelnetProtocol:
|
|
def __init__(self, portal):
|
|
self.portal = portal
|
|
|
|
def __call__(self):
|
|
auth = telnet.AuthenticatingTelnetProtocol
|
|
args = (self.portal,)
|
|
return telnet.TelnetTransport(auth, *args)
|
|
|
|
|
|
|
|
class chainedProtocolFactory:
|
|
def __init__(self, namespace):
|
|
self.namespace = namespace
|
|
|
|
def __call__(self):
|
|
return insults.ServerProtocol(manhole.ColoredManhole, self.namespace)
|
|
|
|
|
|
|
|
@implementer(portal.IRealm)
|
|
class _StupidRealm:
|
|
def __init__(self, proto, *a, **kw):
|
|
self.protocolFactory = proto
|
|
self.protocolArgs = a
|
|
self.protocolKwArgs = kw
|
|
|
|
def requestAvatar(self, avatarId, *interfaces):
|
|
if telnet.ITelnetProtocol in interfaces:
|
|
return (telnet.ITelnetProtocol,
|
|
self.protocolFactory(*self.protocolArgs,
|
|
**self.protocolKwArgs),
|
|
lambda: None)
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
class Options(usage.Options):
|
|
optParameters = [
|
|
["telnetPort", "t", None,
|
|
("strports description of the address on which to listen for telnet "
|
|
"connections")],
|
|
["sshPort", "s", None,
|
|
("strports description of the address on which to listen for ssh "
|
|
"connections")],
|
|
["passwd", "p", "/etc/passwd",
|
|
"name of a passwd(5)-format username/password file"],
|
|
["sshKeyDir", None, "<USER DATA DIR>",
|
|
"Directory where the autogenerated SSH key is kept."],
|
|
["sshKeyName", None, "server.key",
|
|
"Filename of the autogenerated SSH key."],
|
|
["sshKeySize", None, 4096,
|
|
"Size of the automatically generated SSH key."],
|
|
]
|
|
|
|
def __init__(self):
|
|
usage.Options.__init__(self)
|
|
self['namespace'] = None
|
|
|
|
def postOptions(self):
|
|
if self['telnetPort'] is None and self['sshPort'] is None:
|
|
raise usage.UsageError(
|
|
"At least one of --telnetPort and --sshPort must be specified")
|
|
|
|
|
|
|
|
def makeService(options):
|
|
"""
|
|
Create a manhole server service.
|
|
|
|
@type options: L{dict}
|
|
@param options: A mapping describing the configuration of
|
|
the desired service. Recognized key/value pairs are::
|
|
|
|
"telnetPort": strports description of the address on which
|
|
to listen for telnet connections. If None,
|
|
no telnet service will be started.
|
|
|
|
"sshPort": strports description of the address on which to
|
|
listen for ssh connections. If None, no ssh
|
|
service will be started.
|
|
|
|
"namespace": dictionary containing desired initial locals
|
|
for manhole connections. If None, an empty
|
|
dictionary will be used.
|
|
|
|
"passwd": Name of a passwd(5)-format username/password file.
|
|
|
|
"sshKeyDir": The folder that the SSH server key will be kept in.
|
|
|
|
"sshKeyName": The filename of the key.
|
|
|
|
"sshKeySize": The size of the key, in bits. Default is 4096.
|
|
|
|
@rtype: L{twisted.application.service.IService}
|
|
@return: A manhole service.
|
|
"""
|
|
svc = service.MultiService()
|
|
|
|
namespace = options['namespace']
|
|
if namespace is None:
|
|
namespace = {}
|
|
|
|
checker = checkers.FilePasswordDB(options['passwd'])
|
|
|
|
if options['telnetPort']:
|
|
telnetRealm = _StupidRealm(telnet.TelnetBootstrapProtocol,
|
|
insults.ServerProtocol,
|
|
manhole.ColoredManhole,
|
|
namespace)
|
|
|
|
telnetPortal = portal.Portal(telnetRealm, [checker])
|
|
|
|
telnetFactory = protocol.ServerFactory()
|
|
telnetFactory.protocol = makeTelnetProtocol(telnetPortal)
|
|
telnetService = strports.service(options['telnetPort'],
|
|
telnetFactory)
|
|
telnetService.setServiceParent(svc)
|
|
|
|
if options['sshPort']:
|
|
sshRealm = manhole_ssh.TerminalRealm()
|
|
sshRealm.chainedProtocolFactory = chainedProtocolFactory(namespace)
|
|
|
|
sshPortal = portal.Portal(sshRealm, [checker])
|
|
sshFactory = manhole_ssh.ConchFactory(sshPortal)
|
|
|
|
if options['sshKeyDir'] != "<USER DATA DIR>":
|
|
keyDir = options['sshKeyDir']
|
|
else:
|
|
from twisted.python._appdirs import getDataDirectory
|
|
keyDir = getDataDirectory()
|
|
|
|
keyLocation = filepath.FilePath(keyDir).child(options['sshKeyName'])
|
|
|
|
sshKey = keys._getPersistentRSAKey(keyLocation,
|
|
int(options['sshKeySize']))
|
|
sshFactory.publicKeys[b"ssh-rsa"] = sshKey
|
|
sshFactory.privateKeys[b"ssh-rsa"] = sshKey
|
|
|
|
sshService = strports.service(options['sshPort'], sshFactory)
|
|
sshService.setServiceParent(svc)
|
|
|
|
return svc
|