# 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
