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/ssh/connection.py | |
parent | 523f645a83a0ec97a0332dbc3863bb354c92a328 (diff) | |
download | ydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz |
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py2/twisted/conch/ssh/connection.py')
-rw-r--r-- | contrib/python/Twisted/py2/twisted/conch/ssh/connection.py | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py2/twisted/conch/ssh/connection.py b/contrib/python/Twisted/py2/twisted/conch/ssh/connection.py new file mode 100644 index 0000000000..16ef6444a0 --- /dev/null +++ b/contrib/python/Twisted/py2/twisted/conch/ssh/connection.py @@ -0,0 +1,653 @@ +# -*- test-case-name: twisted.conch.test.test_connection -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +This module contains the implementation of the ssh-connection service, which +allows access to the shell and port-forwarding. + +Maintainer: Paul Swartz +""" +from __future__ import division, absolute_import + +import string +import struct + +import twisted.internet.error +from twisted.conch.ssh import service, common +from twisted.conch import error +from twisted.internet import defer +from twisted.python import log +from twisted.python.compat import ( + nativeString, networkString, long, _bytesChr as chr) + + + +class SSHConnection(service.SSHService): + """ + An implementation of the 'ssh-connection' service. It is used to + multiplex multiple channels over the single SSH connection. + + @ivar localChannelID: the next number to use as a local channel ID. + @type localChannelID: L{int} + @ivar channels: a L{dict} mapping a local channel ID to C{SSHChannel} + subclasses. + @type channels: L{dict} + @ivar localToRemoteChannel: a L{dict} mapping a local channel ID to a + remote channel ID. + @type localToRemoteChannel: L{dict} + @ivar channelsToRemoteChannel: a L{dict} mapping a C{SSHChannel} subclass + to remote channel ID. + @type channelsToRemoteChannel: L{dict} + @ivar deferreds: a L{dict} mapping a local channel ID to a C{list} of + C{Deferreds} for outstanding channel requests. Also, the 'global' + key stores the C{list} of pending global request C{Deferred}s. + """ + name = b'ssh-connection' + + def __init__(self): + self.localChannelID = 0 # this is the current # to use for channel ID + self.localToRemoteChannel = {} # local channel ID -> remote channel ID + self.channels = {} # local channel ID -> subclass of SSHChannel + self.channelsToRemoteChannel = {} # subclass of SSHChannel -> + # remote channel ID + self.deferreds = {"global": []} # local channel -> list of deferreds + # for pending requests or 'global' -> list of + # deferreds for global requests + self.transport = None # gets set later + + + def serviceStarted(self): + if hasattr(self.transport, 'avatar'): + self.transport.avatar.conn = self + + + def serviceStopped(self): + """ + Called when the connection is stopped. + """ + # Close any fully open channels + for channel in list(self.channelsToRemoteChannel.keys()): + self.channelClosed(channel) + # Indicate failure to any channels that were in the process of + # opening but not yet open. + while self.channels: + (_, channel) = self.channels.popitem() + log.callWithLogger(channel, channel.openFailed, + twisted.internet.error.ConnectionLost()) + # Errback any unfinished global requests. + self._cleanupGlobalDeferreds() + + + def _cleanupGlobalDeferreds(self): + """ + All pending requests that have returned a deferred must be errbacked + when this service is stopped, otherwise they might be left uncalled and + uncallable. + """ + for d in self.deferreds["global"]: + d.errback(error.ConchError("Connection stopped.")) + del self.deferreds["global"][:] + + + # packet methods + def ssh_GLOBAL_REQUEST(self, packet): + """ + The other side has made a global request. Payload:: + string request type + bool want reply + <request specific data> + + This dispatches to self.gotGlobalRequest. + """ + requestType, rest = common.getNS(packet) + wantReply, rest = ord(rest[0:1]), rest[1:] + ret = self.gotGlobalRequest(requestType, rest) + if wantReply: + reply = MSG_REQUEST_FAILURE + data = b'' + if ret: + reply = MSG_REQUEST_SUCCESS + if isinstance(ret, (tuple, list)): + data = ret[1] + self.transport.sendPacket(reply, data) + + def ssh_REQUEST_SUCCESS(self, packet): + """ + Our global request succeeded. Get the appropriate Deferred and call + it back with the packet we received. + """ + log.msg('RS') + self.deferreds['global'].pop(0).callback(packet) + + def ssh_REQUEST_FAILURE(self, packet): + """ + Our global request failed. Get the appropriate Deferred and errback + it with the packet we received. + """ + log.msg('RF') + self.deferreds['global'].pop(0).errback( + error.ConchError('global request failed', packet)) + + def ssh_CHANNEL_OPEN(self, packet): + """ + The other side wants to get a channel. Payload:: + string channel name + uint32 remote channel number + uint32 remote window size + uint32 remote maximum packet size + <channel specific data> + + We get a channel from self.getChannel(), give it a local channel number + and notify the other side. Then notify the channel by calling its + channelOpen method. + """ + channelType, rest = common.getNS(packet) + senderChannel, windowSize, maxPacket = struct.unpack('>3L', rest[:12]) + packet = rest[12:] + try: + channel = self.getChannel(channelType, windowSize, maxPacket, + packet) + localChannel = self.localChannelID + self.localChannelID += 1 + channel.id = localChannel + self.channels[localChannel] = channel + self.channelsToRemoteChannel[channel] = senderChannel + self.localToRemoteChannel[localChannel] = senderChannel + self.transport.sendPacket(MSG_CHANNEL_OPEN_CONFIRMATION, + struct.pack('>4L', senderChannel, localChannel, + channel.localWindowSize, + channel.localMaxPacket)+channel.specificData) + log.callWithLogger(channel, channel.channelOpen, packet) + except Exception as e: + log.err(e, 'channel open failed') + if isinstance(e, error.ConchError): + textualInfo, reason = e.args + if isinstance(textualInfo, (int, long)): + # See #3657 and #3071 + textualInfo, reason = reason, textualInfo + else: + reason = OPEN_CONNECT_FAILED + textualInfo = "unknown failure" + self.transport.sendPacket( + MSG_CHANNEL_OPEN_FAILURE, + struct.pack('>2L', senderChannel, reason) + + common.NS(networkString(textualInfo)) + common.NS(b'')) + + def ssh_CHANNEL_OPEN_CONFIRMATION(self, packet): + """ + The other side accepted our MSG_CHANNEL_OPEN request. Payload:: + uint32 local channel number + uint32 remote channel number + uint32 remote window size + uint32 remote maximum packet size + <channel specific data> + + Find the channel using the local channel number and notify its + channelOpen method. + """ + (localChannel, remoteChannel, windowSize, + maxPacket) = struct.unpack('>4L', packet[: 16]) + specificData = packet[16:] + channel = self.channels[localChannel] + channel.conn = self + self.localToRemoteChannel[localChannel] = remoteChannel + self.channelsToRemoteChannel[channel] = remoteChannel + channel.remoteWindowLeft = windowSize + channel.remoteMaxPacket = maxPacket + log.callWithLogger(channel, channel.channelOpen, specificData) + + def ssh_CHANNEL_OPEN_FAILURE(self, packet): + """ + The other side did not accept our MSG_CHANNEL_OPEN request. Payload:: + uint32 local channel number + uint32 reason code + string reason description + + Find the channel using the local channel number and notify it by + calling its openFailed() method. + """ + localChannel, reasonCode = struct.unpack('>2L', packet[:8]) + reasonDesc = common.getNS(packet[8:])[0] + channel = self.channels[localChannel] + del self.channels[localChannel] + channel.conn = self + reason = error.ConchError(reasonDesc, reasonCode) + log.callWithLogger(channel, channel.openFailed, reason) + + def ssh_CHANNEL_WINDOW_ADJUST(self, packet): + """ + The other side is adding bytes to its window. Payload:: + uint32 local channel number + uint32 bytes to add + + Call the channel's addWindowBytes() method to add new bytes to the + remote window. + """ + localChannel, bytesToAdd = struct.unpack('>2L', packet[:8]) + channel = self.channels[localChannel] + log.callWithLogger(channel, channel.addWindowBytes, bytesToAdd) + + def ssh_CHANNEL_DATA(self, packet): + """ + The other side is sending us data. Payload:: + uint32 local channel number + string data + + Check to make sure the other side hasn't sent too much data (more + than what's in the window, or more than the maximum packet size). If + they have, close the channel. Otherwise, decrease the available + window and pass the data to the channel's dataReceived(). + """ + localChannel, dataLength = struct.unpack('>2L', packet[:8]) + channel = self.channels[localChannel] + # XXX should this move to dataReceived to put client in charge? + if (dataLength > channel.localWindowLeft or + dataLength > channel.localMaxPacket): # more data than we want + log.callWithLogger(channel, log.msg, 'too much data') + self.sendClose(channel) + return + #packet = packet[:channel.localWindowLeft+4] + data = common.getNS(packet[4:])[0] + channel.localWindowLeft -= dataLength + if channel.localWindowLeft < channel.localWindowSize // 2: + self.adjustWindow(channel, channel.localWindowSize - \ + channel.localWindowLeft) + #log.msg('local window left: %s/%s' % (channel.localWindowLeft, + # channel.localWindowSize)) + log.callWithLogger(channel, channel.dataReceived, data) + + def ssh_CHANNEL_EXTENDED_DATA(self, packet): + """ + The other side is sending us exteneded data. Payload:: + uint32 local channel number + uint32 type code + string data + + Check to make sure the other side hasn't sent too much data (more + than what's in the window, or than the maximum packet size). If + they have, close the channel. Otherwise, decrease the available + window and pass the data and type code to the channel's + extReceived(). + """ + localChannel, typeCode, dataLength = struct.unpack('>3L', packet[:12]) + channel = self.channels[localChannel] + if (dataLength > channel.localWindowLeft or + dataLength > channel.localMaxPacket): + log.callWithLogger(channel, log.msg, 'too much extdata') + self.sendClose(channel) + return + data = common.getNS(packet[8:])[0] + channel.localWindowLeft -= dataLength + if channel.localWindowLeft < channel.localWindowSize // 2: + self.adjustWindow(channel, channel.localWindowSize - + channel.localWindowLeft) + log.callWithLogger(channel, channel.extReceived, typeCode, data) + + def ssh_CHANNEL_EOF(self, packet): + """ + The other side is not sending any more data. Payload:: + uint32 local channel number + + Notify the channel by calling its eofReceived() method. + """ + localChannel = struct.unpack('>L', packet[:4])[0] + channel = self.channels[localChannel] + log.callWithLogger(channel, channel.eofReceived) + + def ssh_CHANNEL_CLOSE(self, packet): + """ + The other side is closing its end; it does not want to receive any + more data. Payload:: + uint32 local channel number + + Notify the channnel by calling its closeReceived() method. If + the channel has also sent a close message, call self.channelClosed(). + """ + localChannel = struct.unpack('>L', packet[:4])[0] + channel = self.channels[localChannel] + log.callWithLogger(channel, channel.closeReceived) + channel.remoteClosed = True + if channel.localClosed and channel.remoteClosed: + self.channelClosed(channel) + + def ssh_CHANNEL_REQUEST(self, packet): + """ + The other side is sending a request to a channel. Payload:: + uint32 local channel number + string request name + bool want reply + <request specific data> + + Pass the message to the channel's requestReceived method. If the + other side wants a reply, add callbacks which will send the + reply. + """ + localChannel = struct.unpack('>L', packet[:4])[0] + requestType, rest = common.getNS(packet[4:]) + wantReply = ord(rest[0:1]) + channel = self.channels[localChannel] + d = defer.maybeDeferred(log.callWithLogger, channel, + channel.requestReceived, requestType, rest[1:]) + if wantReply: + d.addCallback(self._cbChannelRequest, localChannel) + d.addErrback(self._ebChannelRequest, localChannel) + return d + + def _cbChannelRequest(self, result, localChannel): + """ + Called back if the other side wanted a reply to a channel request. If + the result is true, send a MSG_CHANNEL_SUCCESS. Otherwise, raise + a C{error.ConchError} + + @param result: the value returned from the channel's requestReceived() + method. If it's False, the request failed. + @type result: L{bool} + @param localChannel: the local channel ID of the channel to which the + request was made. + @type localChannel: L{int} + @raises ConchError: if the result is False. + """ + if not result: + raise error.ConchError('failed request') + self.transport.sendPacket(MSG_CHANNEL_SUCCESS, struct.pack('>L', + self.localToRemoteChannel[localChannel])) + + def _ebChannelRequest(self, result, localChannel): + """ + Called if the other wisde wanted a reply to the channel requeset and + the channel request failed. + + @param result: a Failure, but it's not used. + @param localChannel: the local channel ID of the channel to which the + request was made. + @type localChannel: L{int} + """ + self.transport.sendPacket(MSG_CHANNEL_FAILURE, struct.pack('>L', + self.localToRemoteChannel[localChannel])) + + def ssh_CHANNEL_SUCCESS(self, packet): + """ + Our channel request to the other side succeeded. Payload:: + uint32 local channel number + + Get the C{Deferred} out of self.deferreds and call it back. + """ + localChannel = struct.unpack('>L', packet[:4])[0] + if self.deferreds.get(localChannel): + d = self.deferreds[localChannel].pop(0) + log.callWithLogger(self.channels[localChannel], + d.callback, '') + + def ssh_CHANNEL_FAILURE(self, packet): + """ + Our channel request to the other side failed. Payload:: + uint32 local channel number + + Get the C{Deferred} out of self.deferreds and errback it with a + C{error.ConchError}. + """ + localChannel = struct.unpack('>L', packet[:4])[0] + if self.deferreds.get(localChannel): + d = self.deferreds[localChannel].pop(0) + log.callWithLogger(self.channels[localChannel], + d.errback, + error.ConchError('channel request failed')) + + # methods for users of the connection to call + + def sendGlobalRequest(self, request, data, wantReply=0): + """ + Send a global request for this connection. Current this is only used + for remote->local TCP forwarding. + + @type request: L{bytes} + @type data: L{bytes} + @type wantReply: L{bool} + @rtype C{Deferred}/L{None} + """ + self.transport.sendPacket(MSG_GLOBAL_REQUEST, + common.NS(request) + + (wantReply and b'\xff' or b'\x00') + + data) + if wantReply: + d = defer.Deferred() + self.deferreds['global'].append(d) + return d + + def openChannel(self, channel, extra=b''): + """ + Open a new channel on this connection. + + @type channel: subclass of C{SSHChannel} + @type extra: L{bytes} + """ + log.msg('opening channel %s with %s %s'%(self.localChannelID, + channel.localWindowSize, channel.localMaxPacket)) + self.transport.sendPacket(MSG_CHANNEL_OPEN, common.NS(channel.name) + + struct.pack('>3L', self.localChannelID, + channel.localWindowSize, channel.localMaxPacket) + + extra) + channel.id = self.localChannelID + self.channels[self.localChannelID] = channel + self.localChannelID += 1 + + def sendRequest(self, channel, requestType, data, wantReply=0): + """ + Send a request to a channel. + + @type channel: subclass of C{SSHChannel} + @type requestType: L{bytes} + @type data: L{bytes} + @type wantReply: L{bool} + @rtype C{Deferred}/L{None} + """ + if channel.localClosed: + return + log.msg('sending request %r' % (requestType)) + self.transport.sendPacket(MSG_CHANNEL_REQUEST, struct.pack('>L', + self.channelsToRemoteChannel[channel]) + + common.NS(requestType)+chr(wantReply) + + data) + if wantReply: + d = defer.Deferred() + self.deferreds.setdefault(channel.id, []).append(d) + return d + + def adjustWindow(self, channel, bytesToAdd): + """ + Tell the other side that we will receive more data. This should not + normally need to be called as it is managed automatically. + + @type channel: subclass of L{SSHChannel} + @type bytesToAdd: L{int} + """ + if channel.localClosed: + return # we're already closed + self.transport.sendPacket(MSG_CHANNEL_WINDOW_ADJUST, struct.pack('>2L', + self.channelsToRemoteChannel[channel], + bytesToAdd)) + log.msg('adding %i to %i in channel %i' % (bytesToAdd, + channel.localWindowLeft, channel.id)) + channel.localWindowLeft += bytesToAdd + + def sendData(self, channel, data): + """ + Send data to a channel. This should not normally be used: instead use + channel.write(data) as it manages the window automatically. + + @type channel: subclass of L{SSHChannel} + @type data: L{bytes} + """ + if channel.localClosed: + return # we're already closed + self.transport.sendPacket(MSG_CHANNEL_DATA, struct.pack('>L', + self.channelsToRemoteChannel[channel]) + + common.NS(data)) + + def sendExtendedData(self, channel, dataType, data): + """ + Send extended data to a channel. This should not normally be used: + instead use channel.writeExtendedData(data, dataType) as it manages + the window automatically. + + @type channel: subclass of L{SSHChannel} + @type dataType: L{int} + @type data: L{bytes} + """ + if channel.localClosed: + return # we're already closed + self.transport.sendPacket(MSG_CHANNEL_EXTENDED_DATA, struct.pack('>2L', + self.channelsToRemoteChannel[channel],dataType) \ + + common.NS(data)) + + def sendEOF(self, channel): + """ + Send an EOF (End of File) for a channel. + + @type channel: subclass of L{SSHChannel} + """ + if channel.localClosed: + return # we're already closed + log.msg('sending eof') + self.transport.sendPacket(MSG_CHANNEL_EOF, struct.pack('>L', + self.channelsToRemoteChannel[channel])) + + def sendClose(self, channel): + """ + Close a channel. + + @type channel: subclass of L{SSHChannel} + """ + if channel.localClosed: + return # we're already closed + log.msg('sending close %i' % channel.id) + self.transport.sendPacket(MSG_CHANNEL_CLOSE, struct.pack('>L', + self.channelsToRemoteChannel[channel])) + channel.localClosed = True + if channel.localClosed and channel.remoteClosed: + self.channelClosed(channel) + + # methods to override + def getChannel(self, channelType, windowSize, maxPacket, data): + """ + The other side requested a channel of some sort. + channelType is the type of channel being requested, + windowSize is the initial size of the remote window, + maxPacket is the largest packet we should send, + data is any other packet data (often nothing). + + We return a subclass of L{SSHChannel}. + + By default, this dispatches to a method 'channel_channelType' with any + non-alphanumerics in the channelType replace with _'s. If it cannot + find a suitable method, it returns an OPEN_UNKNOWN_CHANNEL_TYPE error. + The method is called with arguments of windowSize, maxPacket, data. + + @type channelType: L{bytes} + @type windowSize: L{int} + @type maxPacket: L{int} + @type data: L{bytes} + @rtype: subclass of L{SSHChannel}/L{tuple} + """ + log.msg('got channel %r request' % (channelType)) + if hasattr(self.transport, "avatar"): # this is a server! + chan = self.transport.avatar.lookupChannel(channelType, + windowSize, + maxPacket, + data) + else: + channelType = channelType.translate(TRANSLATE_TABLE) + attr = 'channel_%s' % nativeString(channelType) + f = getattr(self, attr, None) + if f is not None: + chan = f(windowSize, maxPacket, data) + else: + chan = None + if chan is None: + raise error.ConchError('unknown channel', + OPEN_UNKNOWN_CHANNEL_TYPE) + else: + chan.conn = self + return chan + + def gotGlobalRequest(self, requestType, data): + """ + We got a global request. pretty much, this is just used by the client + to request that we forward a port from the server to the client. + Returns either: + - 1: request accepted + - 1, <data>: request accepted with request specific data + - 0: request denied + + By default, this dispatches to a method 'global_requestType' with + -'s in requestType replaced with _'s. The found method is passed data. + If this method cannot be found, this method returns 0. Otherwise, it + returns the return value of that method. + + @type requestType: L{bytes} + @type data: L{bytes} + @rtype: L{int}/L{tuple} + """ + log.msg('got global %s request' % requestType) + if hasattr(self.transport, 'avatar'): # this is a server! + return self.transport.avatar.gotGlobalRequest(requestType, data) + + requestType = nativeString(requestType.replace(b'-',b'_')) + f = getattr(self, 'global_%s' % requestType, None) + if not f: + return 0 + return f(data) + + def channelClosed(self, channel): + """ + Called when a channel is closed. + It clears the local state related to the channel, and calls + channel.closed(). + MAKE SURE YOU CALL THIS METHOD, even if you subclass L{SSHConnection}. + If you don't, things will break mysteriously. + + @type channel: L{SSHChannel} + """ + if channel in self.channelsToRemoteChannel: # actually open + channel.localClosed = channel.remoteClosed = True + del self.localToRemoteChannel[channel.id] + del self.channels[channel.id] + del self.channelsToRemoteChannel[channel] + for d in self.deferreds.pop(channel.id, []): + d.errback(error.ConchError("Channel closed.")) + log.callWithLogger(channel, channel.closed) + + + +MSG_GLOBAL_REQUEST = 80 +MSG_REQUEST_SUCCESS = 81 +MSG_REQUEST_FAILURE = 82 +MSG_CHANNEL_OPEN = 90 +MSG_CHANNEL_OPEN_CONFIRMATION = 91 +MSG_CHANNEL_OPEN_FAILURE = 92 +MSG_CHANNEL_WINDOW_ADJUST = 93 +MSG_CHANNEL_DATA = 94 +MSG_CHANNEL_EXTENDED_DATA = 95 +MSG_CHANNEL_EOF = 96 +MSG_CHANNEL_CLOSE = 97 +MSG_CHANNEL_REQUEST = 98 +MSG_CHANNEL_SUCCESS = 99 +MSG_CHANNEL_FAILURE = 100 + +OPEN_ADMINISTRATIVELY_PROHIBITED = 1 +OPEN_CONNECT_FAILED = 2 +OPEN_UNKNOWN_CHANNEL_TYPE = 3 +OPEN_RESOURCE_SHORTAGE = 4 + +EXTENDED_DATA_STDERR = 1 + +messages = {} +for name, value in locals().copy().items(): + if name[:4] == 'MSG_': + messages[value] = name # Doesn't handle doubles + +alphanums = networkString(string.ascii_letters + string.digits) +TRANSLATE_TABLE = b''.join([chr(i) in alphanums and chr(i) or b'_' + for i in range(256)]) +SSHConnection.protocolMessages = messages |