123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872 |
- # -*- test-case-name: twisted.conch.test.test_endpoints -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- Endpoint implementations of various SSH interactions.
- """
- __all__ = [
- 'AuthenticationFailed', 'SSHCommandAddress', 'SSHCommandClientEndpoint']
- from struct import unpack
- from os.path import expanduser
- import signal
- from zope.interface import Interface, implementer
- from twisted.logger import Logger
- from twisted.python.compat import nativeString, networkString
- from twisted.python.filepath import FilePath
- from twisted.python.failure import Failure
- from twisted.internet.error import ConnectionDone, ProcessTerminated
- from twisted.internet.interfaces import IStreamClientEndpoint
- from twisted.internet.protocol import Factory
- from twisted.internet.defer import Deferred, succeed, CancelledError
- from twisted.internet.endpoints import TCP4ClientEndpoint, connectProtocol
- from twisted.conch.ssh.keys import Key
- from twisted.conch.ssh.common import getNS, NS
- from twisted.conch.ssh.transport import SSHClientTransport
- from twisted.conch.ssh.connection import SSHConnection
- from twisted.conch.ssh.userauth import SSHUserAuthClient
- from twisted.conch.ssh.channel import SSHChannel
- from twisted.conch.client.knownhosts import ConsoleUI, KnownHostsFile
- from twisted.conch.client.agent import SSHAgentClient
- from twisted.conch.client.default import _KNOWN_HOSTS
- class AuthenticationFailed(Exception):
- """
- An SSH session could not be established because authentication was not
- successful.
- """
- # This should be public. See #6541.
- class _ISSHConnectionCreator(Interface):
- """
- An L{_ISSHConnectionCreator} knows how to create SSH connections somehow.
- """
- def secureConnection():
- """
- Return a new, connected, secured, but not yet authenticated instance of
- L{twisted.conch.ssh.transport.SSHServerTransport} or
- L{twisted.conch.ssh.transport.SSHClientTransport}.
- """
- def cleanupConnection(connection, immediate):
- """
- Perform cleanup necessary for a connection object previously returned
- from this creator's C{secureConnection} method.
- @param connection: An L{twisted.conch.ssh.transport.SSHServerTransport}
- or L{twisted.conch.ssh.transport.SSHClientTransport} returned by a
- previous call to C{secureConnection}. It is no longer needed by
- the caller of that method and may be closed or otherwise cleaned up
- as necessary.
- @param immediate: If C{True} don't wait for any network communication,
- just close the connection immediately and as aggressively as
- necessary.
- """
- class SSHCommandAddress(object):
- """
- An L{SSHCommandAddress} instance represents the address of an SSH server, a
- username which was used to authenticate with that server, and a command
- which was run there.
- @ivar server: See L{__init__}
- @ivar username: See L{__init__}
- @ivar command: See L{__init__}
- """
- def __init__(self, server, username, command):
- """
- @param server: The address of the SSH server on which the command is
- running.
- @type server: L{IAddress} provider
- @param username: An authentication username which was used to
- authenticate against the server at the given address.
- @type username: L{bytes}
- @param command: A command which was run in a session channel on the
- server at the given address.
- @type command: L{bytes}
- """
- self.server = server
- self.username = username
- self.command = command
- class _CommandChannel(SSHChannel):
- """
- A L{_CommandChannel} executes a command in a session channel and connects
- its input and output to an L{IProtocol} provider.
- @ivar _creator: See L{__init__}
- @ivar _command: See L{__init__}
- @ivar _protocolFactory: See L{__init__}
- @ivar _commandConnected: See L{__init__}
- @ivar _protocol: An L{IProtocol} provider created using C{_protocolFactory}
- which is hooked up to the running command's input and output streams.
- """
- name = b'session'
- _log = Logger()
- def __init__(self, creator, command, protocolFactory, commandConnected):
- """
- @param creator: The L{_ISSHConnectionCreator} provider which was used
- to get the connection which this channel exists on.
- @type creator: L{_ISSHConnectionCreator} provider
- @param command: The command to be executed.
- @type command: L{bytes}
- @param protocolFactory: A client factory to use to build a L{IProtocol}
- provider to use to associate with the running command.
- @param commandConnected: A L{Deferred} to use to signal that execution
- of the command has failed or that it has succeeded and the command
- is now running.
- @type commandConnected: L{Deferred}
- """
- SSHChannel.__init__(self)
- self._creator = creator
- self._command = command
- self._protocolFactory = protocolFactory
- self._commandConnected = commandConnected
- self._reason = None
- def openFailed(self, reason):
- """
- When the request to open a new channel to run this command in fails,
- fire the C{commandConnected} deferred with a failure indicating that.
- """
- self._commandConnected.errback(reason)
- def channelOpen(self, ignored):
- """
- When the request to open a new channel to run this command in succeeds,
- issue an C{"exec"} request to run the command.
- """
- command = self.conn.sendRequest(
- self, b'exec', NS(self._command), wantReply=True)
- command.addCallbacks(self._execSuccess, self._execFailure)
- def _execFailure(self, reason):
- """
- When the request to execute the command in this channel fails, fire the
- C{commandConnected} deferred with a failure indicating this.
- @param reason: The cause of the command execution failure.
- @type reason: L{Failure}
- """
- self._commandConnected.errback(reason)
- def _execSuccess(self, ignored):
- """
- When the request to execute the command in this channel succeeds, use
- C{protocolFactory} to build a protocol to handle the command's input
- and output and connect the protocol to a transport representing those
- streams.
- Also fire C{commandConnected} with the created protocol after it is
- connected to its transport.
- @param ignored: The (ignored) result of the execute request
- """
- self._protocol = self._protocolFactory.buildProtocol(
- SSHCommandAddress(
- self.conn.transport.transport.getPeer(),
- self.conn.transport.creator.username,
- self.conn.transport.creator.command))
- self._protocol.makeConnection(self)
- self._commandConnected.callback(self._protocol)
- def dataReceived(self, data):
- """
- When the command's stdout data arrives over the channel, deliver it to
- the protocol instance.
- @param data: The bytes from the command's stdout.
- @type data: L{bytes}
- """
- self._protocol.dataReceived(data)
- def request_exit_status(self, data):
- """
- When the server sends the command's exit status, record it for later
- delivery to the protocol.
- @param data: The network-order four byte representation of the exit
- status of the command.
- @type data: L{bytes}
- """
- (status,) = unpack('>L', data)
- if status != 0:
- self._reason = ProcessTerminated(status, None, None)
- def request_exit_signal(self, data):
- """
- When the server sends the command's exit status, record it for later
- delivery to the protocol.
- @param data: The network-order four byte representation of the exit
- signal of the command.
- @type data: L{bytes}
- """
- shortSignalName, data = getNS(data)
- coreDumped, data = bool(ord(data[0:1])), data[1:]
- errorMessage, data = getNS(data)
- languageTag, data = getNS(data)
- signalName = "SIG%s" % (nativeString(shortSignalName),)
- signalID = getattr(signal, signalName, -1)
- self._log.info(
- "Process exited with signal {shortSignalName!r};"
- " core dumped: {coreDumped};"
- " error message: {errorMessage};"
- " language: {languageTag!r}",
- shortSignalName=shortSignalName,
- coreDumped=coreDumped,
- errorMessage=errorMessage.decode('utf-8'),
- languageTag=languageTag,
- )
- self._reason = ProcessTerminated(None, signalID, None)
- def closed(self):
- """
- When the channel closes, deliver disconnection notification to the
- protocol.
- """
- self._creator.cleanupConnection(self.conn, False)
- if self._reason is None:
- reason = ConnectionDone("ssh channel closed")
- else:
- reason = self._reason
- self._protocol.connectionLost(Failure(reason))
- class _ConnectionReady(SSHConnection):
- """
- L{_ConnectionReady} is an L{SSHConnection} (an SSH service) which only
- propagates the I{serviceStarted} event to a L{Deferred} to be handled
- elsewhere.
- """
- def __init__(self, ready):
- """
- @param ready: A L{Deferred} which should be fired when
- I{serviceStarted} happens.
- """
- SSHConnection.__init__(self)
- self._ready = ready
- def serviceStarted(self):
- """
- When the SSH I{connection} I{service} this object represents is ready
- to be used, fire the C{connectionReady} L{Deferred} to publish that
- event to some other interested party.
- """
- self._ready.callback(self)
- del self._ready
- class _UserAuth(SSHUserAuthClient):
- """
- L{_UserAuth} implements the client part of SSH user authentication in the
- convenient way a user might expect if they are familiar with the
- interactive I{ssh} command line client.
- L{_UserAuth} supports key-based authentication, password-based
- authentication, and delegating authentication to an agent.
- """
- password = None
- keys = None
- agent = None
- def getPublicKey(self):
- """
- Retrieve the next public key object to offer to the server, possibly
- delegating to an authentication agent if there is one.
- @return: The public part of a key pair that could be used to
- authenticate with the server, or L{None} if there are no more
- public keys to try.
- @rtype: L{twisted.conch.ssh.keys.Key} or L{None}
- """
- if self.agent is not None:
- return self.agent.getPublicKey()
- if self.keys:
- self.key = self.keys.pop(0)
- else:
- self.key = None
- return self.key.public()
- def signData(self, publicKey, signData):
- """
- Extend the base signing behavior by using an SSH agent to sign the
- data, if one is available.
- @type publicKey: L{Key}
- @type signData: L{str}
- """
- if self.agent is not None:
- return self.agent.signData(publicKey.blob(), signData)
- else:
- return SSHUserAuthClient.signData(self, publicKey, signData)
- def getPrivateKey(self):
- """
- Get the private part of a key pair to use for authentication. The key
- corresponds to the public part most recently returned from
- C{getPublicKey}.
- @return: A L{Deferred} which fires with the private key.
- @rtype: L{Deferred}
- """
- return succeed(self.key)
- def getPassword(self):
- """
- Get the password to use for authentication.
- @return: A L{Deferred} which fires with the password, or L{None} if the
- password was not specified.
- """
- if self.password is None:
- return
- return succeed(self.password)
- def ssh_USERAUTH_SUCCESS(self, packet):
- """
- Handle user authentication success in the normal way, but also make a
- note of the state change on the L{_CommandTransport}.
- """
- self.transport._state = b'CHANNELLING'
- return SSHUserAuthClient.ssh_USERAUTH_SUCCESS(self, packet)
- def connectToAgent(self, endpoint):
- """
- Set up a connection to the authentication agent and trigger its
- initialization.
- @param endpoint: An endpoint which can be used to connect to the
- authentication agent.
- @type endpoint: L{IStreamClientEndpoint} provider
- @return: A L{Deferred} which fires when the agent connection is ready
- for use.
- """
- factory = Factory()
- factory.protocol = SSHAgentClient
- d = endpoint.connect(factory)
- def connected(agent):
- self.agent = agent
- return agent.getPublicKeys()
- d.addCallback(connected)
- return d
- def loseAgentConnection(self):
- """
- Disconnect the agent.
- """
- if self.agent is None:
- return
- self.agent.transport.loseConnection()
- class _CommandTransport(SSHClientTransport):
- """
- L{_CommandTransport} is an SSH client I{transport} which includes a host
- key verification step before it will proceed to secure the connection.
- L{_CommandTransport} also knows how to set up a connection to an
- authentication agent if it is told where it can connect to one.
- @ivar _userauth: The L{_UserAuth} instance which is in charge of the
- overall authentication process or L{None} if the SSH connection has not
- reach yet the C{user-auth} service.
- @type _userauth: L{_UserAuth}
- """
- # STARTING -> SECURING -> AUTHENTICATING -> CHANNELLING -> RUNNING
- _state = b'STARTING'
- _hostKeyFailure = None
- _userauth = None
- def __init__(self, creator):
- """
- @param creator: The L{_NewConnectionHelper} that created this
- connection.
- @type creator: L{_NewConnectionHelper}.
- """
- self.connectionReady = Deferred(
- lambda d: self.transport.abortConnection())
- # Clear the reference to that deferred to help the garbage collector
- # and to signal to other parts of this implementation (in particular
- # connectionLost) that it has already been fired and does not need to
- # be fired again.
- def readyFired(result):
- self.connectionReady = None
- return result
- self.connectionReady.addBoth(readyFired)
- self.creator = creator
- def verifyHostKey(self, hostKey, fingerprint):
- """
- Ask the L{KnownHostsFile} provider available on the factory which
- created this protocol this protocol to verify the given host key.
- @return: A L{Deferred} which fires with the result of
- L{KnownHostsFile.verifyHostKey}.
- """
- hostname = self.creator.hostname
- ip = networkString(self.transport.getPeer().host)
- self._state = b'SECURING'
- d = self.creator.knownHosts.verifyHostKey(
- self.creator.ui, hostname, ip, Key.fromString(hostKey))
- d.addErrback(self._saveHostKeyFailure)
- return d
- def _saveHostKeyFailure(self, reason):
- """
- When host key verification fails, record the reason for the failure in
- order to fire a L{Deferred} with it later.
- @param reason: The cause of the host key verification failure.
- @type reason: L{Failure}
- @return: C{reason}
- @rtype: L{Failure}
- """
- self._hostKeyFailure = reason
- return reason
- def connectionSecure(self):
- """
- When the connection is secure, start the authentication process.
- """
- self._state = b'AUTHENTICATING'
- command = _ConnectionReady(self.connectionReady)
- self._userauth = _UserAuth(self.creator.username, command)
- self._userauth.password = self.creator.password
- if self.creator.keys:
- self._userauth.keys = list(self.creator.keys)
- if self.creator.agentEndpoint is not None:
- d = self._userauth.connectToAgent(self.creator.agentEndpoint)
- else:
- d = succeed(None)
- def maybeGotAgent(ignored):
- self.requestService(self._userauth)
- d.addBoth(maybeGotAgent)
- def connectionLost(self, reason):
- """
- When the underlying connection to the SSH server is lost, if there were
- any connection setup errors, propagate them. Also, clean up the
- connection to the ssh agent if one was created.
- """
- if self._userauth:
- self._userauth.loseAgentConnection()
- if self._state == b'RUNNING' or self.connectionReady is None:
- return
- if self._state == b'SECURING' and self._hostKeyFailure is not None:
- reason = self._hostKeyFailure
- elif self._state == b'AUTHENTICATING':
- reason = Failure(
- AuthenticationFailed("Connection lost while authenticating"))
- self.connectionReady.errback(reason)
- @implementer(IStreamClientEndpoint)
- class SSHCommandClientEndpoint(object):
- """
- L{SSHCommandClientEndpoint} exposes the command-executing functionality of
- SSH servers.
- L{SSHCommandClientEndpoint} can set up a new SSH connection, authenticate
- it in any one of a number of different ways (keys, passwords, agents),
- launch a command over that connection and then associate its input and
- output with a protocol.
- It can also re-use an existing, already-authenticated SSH connection
- (perhaps one which already has some SSH channels being used for other
- purposes). In this case it creates a new SSH channel to use to execute the
- command. Notably this means it supports multiplexing several different
- command invocations over a single SSH connection.
- """
- def __init__(self, creator, command):
- """
- @param creator: An L{_ISSHConnectionCreator} provider which will be
- used to set up the SSH connection which will be used to run a
- command.
- @type creator: L{_ISSHConnectionCreator} provider
- @param command: The command line to execute on the SSH server. This
- byte string is interpreted by a shell on the SSH server, so it may
- have a value like C{"ls /"}. Take care when trying to run a
- command like C{"/Volumes/My Stuff/a-program"} - spaces (and other
- special bytes) may require escaping.
- @type command: L{bytes}
- """
- self._creator = creator
- self._command = command
- @classmethod
- def newConnection(cls, reactor, command, username, hostname, port=None,
- keys=None, password=None, agentEndpoint=None,
- knownHosts=None, ui=None):
- """
- Create and return a new endpoint which will try to create a new
- connection to an SSH server and run a command over it. It will also
- close the connection if there are problems leading up to the command
- being executed, after the command finishes, or if the connection
- L{Deferred} is cancelled.
- @param reactor: The reactor to use to establish the connection.
- @type reactor: L{IReactorTCP} provider
- @param command: See L{__init__}'s C{command} argument.
- @param username: The username with which to authenticate to the SSH
- server.
- @type username: L{bytes}
- @param hostname: The hostname of the SSH server.
- @type hostname: L{bytes}
- @param port: The port number of the SSH server. By default, the
- standard SSH port number is used.
- @type port: L{int}
- @param keys: Private keys with which to authenticate to the SSH server,
- if key authentication is to be attempted (otherwise L{None}).
- @type keys: L{list} of L{Key}
- @param password: The password with which to authenticate to the SSH
- server, if password authentication is to be attempted (otherwise
- L{None}).
- @type password: L{bytes} or L{None}
- @param agentEndpoint: An L{IStreamClientEndpoint} provider which may be
- used to connect to an SSH agent, if one is to be used to help with
- authentication.
- @type agentEndpoint: L{IStreamClientEndpoint} provider
- @param knownHosts: The currently known host keys, used to check the
- host key presented by the server we actually connect to.
- @type knownHosts: L{KnownHostsFile}
- @param ui: An object for interacting with users to make decisions about
- whether to accept the server host keys. If L{None}, a L{ConsoleUI}
- connected to /dev/tty will be used; if /dev/tty is unavailable, an
- object which answers C{b"no"} to all prompts will be used.
- @type ui: L{None} or L{ConsoleUI}
- @return: A new instance of C{cls} (probably
- L{SSHCommandClientEndpoint}).
- """
- helper = _NewConnectionHelper(
- reactor, hostname, port, command, username, keys, password,
- agentEndpoint, knownHosts, ui)
- return cls(helper, command)
- @classmethod
- def existingConnection(cls, connection, command):
- """
- Create and return a new endpoint which will try to open a new channel
- on an existing SSH connection and run a command over it. It will
- B{not} close the connection if there is a problem executing the command
- or after the command finishes.
- @param connection: An existing connection to an SSH server.
- @type connection: L{SSHConnection}
- @param command: See L{SSHCommandClientEndpoint.newConnection}'s
- C{command} parameter.
- @type command: L{bytes}
- @return: A new instance of C{cls} (probably
- L{SSHCommandClientEndpoint}).
- """
- helper = _ExistingConnectionHelper(connection)
- return cls(helper, command)
- def connect(self, protocolFactory):
- """
- Set up an SSH connection, use a channel from that connection to launch
- a command, and hook the stdin and stdout of that command up as a
- transport for a protocol created by the given factory.
- @param protocolFactory: A L{Factory} to use to create the protocol
- which will be connected to the stdin and stdout of the command on
- the SSH server.
- @return: A L{Deferred} which will fire with an error if the connection
- cannot be set up for any reason or with the protocol instance
- created by C{protocolFactory} once it has been connected to the
- command.
- """
- d = self._creator.secureConnection()
- d.addCallback(self._executeCommand, protocolFactory)
- return d
- def _executeCommand(self, connection, protocolFactory):
- """
- Given a secured SSH connection, try to execute a command in a new
- channel created on it and associate the result with a protocol from the
- given factory.
- @param connection: See L{SSHCommandClientEndpoint.existingConnection}'s
- C{connection} parameter.
- @param protocolFactory: See L{SSHCommandClientEndpoint.connect}'s
- C{protocolFactory} parameter.
- @return: See L{SSHCommandClientEndpoint.connect}'s return value.
- """
- commandConnected = Deferred()
- def disconnectOnFailure(passthrough):
- # Close the connection immediately in case of cancellation, since
- # that implies user wants it gone immediately (e.g. a timeout):
- immediate = passthrough.check(CancelledError)
- self._creator.cleanupConnection(connection, immediate)
- return passthrough
- commandConnected.addErrback(disconnectOnFailure)
- channel = _CommandChannel(
- self._creator, self._command, protocolFactory, commandConnected)
- connection.openChannel(channel)
- return commandConnected
- class _ReadFile(object):
- """
- A weakly file-like object which can be used with L{KnownHostsFile} to
- respond in the negative to all prompts for decisions.
- """
- def __init__(self, contents):
- """
- @param contents: L{bytes} which will be returned from every C{readline}
- call.
- """
- self._contents = contents
- def write(self, data):
- """
- No-op.
- @param data: ignored
- """
- def readline(self, count=-1):
- """
- Always give back the byte string that this L{_ReadFile} was initialized
- with.
- @param count: ignored
- @return: A fixed byte-string.
- @rtype: L{bytes}
- """
- return self._contents
- def close(self):
- """
- No-op.
- """
- @implementer(_ISSHConnectionCreator)
- class _NewConnectionHelper(object):
- """
- L{_NewConnectionHelper} implements L{_ISSHConnectionCreator} by
- establishing a brand new SSH connection, securing it, and authenticating.
- """
- _KNOWN_HOSTS = _KNOWN_HOSTS
- port = 22
- def __init__(self, reactor, hostname, port, command, username, keys,
- password, agentEndpoint, knownHosts, ui,
- tty=FilePath(b"/dev/tty")):
- """
- @param tty: The path of the tty device to use in case C{ui} is L{None}.
- @type tty: L{FilePath}
- @see: L{SSHCommandClientEndpoint.newConnection}
- """
- self.reactor = reactor
- self.hostname = hostname
- if port is not None:
- self.port = port
- self.command = command
- self.username = username
- self.keys = keys
- self.password = password
- self.agentEndpoint = agentEndpoint
- if knownHosts is None:
- knownHosts = self._knownHosts()
- self.knownHosts = knownHosts
- if ui is None:
- ui = ConsoleUI(self._opener)
- self.ui = ui
- self.tty = tty
- def _opener(self):
- """
- Open the tty if possible, otherwise give back a file-like object from
- which C{b"no"} can be read.
- For use as the opener argument to L{ConsoleUI}.
- """
- try:
- return self.tty.open("rb+")
- except:
- # Give back a file-like object from which can be read a byte string
- # that KnownHostsFile recognizes as rejecting some option (b"no").
- return _ReadFile(b"no")
- @classmethod
- def _knownHosts(cls):
- """
- @return: A L{KnownHostsFile} instance pointed at the user's personal
- I{known hosts} file.
- @type: L{KnownHostsFile}
- """
- return KnownHostsFile.fromPath(FilePath(expanduser(cls._KNOWN_HOSTS)))
- def secureConnection(self):
- """
- Create and return a new SSH connection which has been secured and on
- which authentication has already happened.
- @return: A L{Deferred} which fires with the ready-to-use connection or
- with a failure if something prevents the connection from being
- setup, secured, or authenticated.
- """
- protocol = _CommandTransport(self)
- ready = protocol.connectionReady
- sshClient = TCP4ClientEndpoint(
- self.reactor, nativeString(self.hostname), self.port)
- d = connectProtocol(sshClient, protocol)
- d.addCallback(lambda ignored: ready)
- return d
- def cleanupConnection(self, connection, immediate):
- """
- Clean up the connection by closing it. The command running on the
- endpoint has ended so the connection is no longer needed.
- @param connection: The L{SSHConnection} to close.
- @type connection: L{SSHConnection}
- @param immediate: Whether to close connection immediately.
- @type immediate: L{bool}.
- """
- if immediate:
- # We're assuming the underlying connection is an ITCPTransport,
- # which is what the current implementation is restricted to:
- connection.transport.transport.abortConnection()
- else:
- connection.transport.loseConnection()
- @implementer(_ISSHConnectionCreator)
- class _ExistingConnectionHelper(object):
- """
- L{_ExistingConnectionHelper} implements L{_ISSHConnectionCreator} by
- handing out an existing SSH connection which is supplied to its
- initializer.
- """
- def __init__(self, connection):
- """
- @param connection: See L{SSHCommandClientEndpoint.existingConnection}'s
- C{connection} parameter.
- """
- self.connection = connection
- def secureConnection(self):
- """
- @return: A L{Deferred} that fires synchronously with the
- already-established connection object.
- """
- return succeed(self.connection)
- def cleanupConnection(self, connection, immediate):
- """
- Do not do any cleanup on the connection. Leave that responsibility to
- whatever code created it in the first place.
- @param connection: The L{SSHConnection} which will not be modified in
- any way.
- @type connection: L{SSHConnection}
- @param immediate: An argument which will be ignored.
- @type immediate: L{bool}.
- """
|