diff options
author | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:16:14 +0300 |
---|---|---|
committer | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:43:30 +0300 |
commit | b8cf9e88f4c5c64d9406af533d8948deb050d695 (patch) | |
tree | 218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py2/twisted/conch/endpoints.py | |
parent | 523f645a83a0ec97a0332dbc3863bb354c92a328 (diff) | |
download | ydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz |
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py2/twisted/conch/endpoints.py')
-rw-r--r-- | contrib/python/Twisted/py2/twisted/conch/endpoints.py | 872 |
1 files changed, 872 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py2/twisted/conch/endpoints.py b/contrib/python/Twisted/py2/twisted/conch/endpoints.py new file mode 100644 index 0000000000..2e19d0870f --- /dev/null +++ b/contrib/python/Twisted/py2/twisted/conch/endpoints.py @@ -0,0 +1,872 @@ +# -*- 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}. + """ |