def exec_command(self, command): """ Execute a command on the server. If the server allows it, the channel will then be directly connected to the stdin, stdout, and stderr of the command being executed. When the command finishes executing, the channel will be closed and can't be reused. You must open a new channel if you wish to execute another command. @param command: a shell command to execute. @type command: str @return: C{True} if the operation succeeded; C{False} if not. @rtype: bool """ if self.closed or self.eof_received or self.eof_sent or not self.active: raise SSHException('Channel is not open') m = Message() m.add_byte(chr(MSG_CHANNEL_REQUEST)) m.add_int(self.remote_chanid) m.add_string('exec') m.add_boolean(1) m.add_string(command) self.event.clear() self.transport._send_user_message(m) while True: self.event.wait(0.1) if self.closed: return False if self.event.isSet(): return True
def invoke_subsystem(self, subsystem): """ Request a subsystem on the server (for example, C{sftp}). If the server allows it, the channel will then be directly connected to the requested subsystem. When the subsystem finishes, the channel will be closed and can't be reused. @param subsystem: name of the subsystem being requested. @type subsystem: str @return: C{True} if the operation succeeded; C{False} if not. @rtype: bool """ if self.closed or self.eof_received or self.eof_sent or not self.active: raise SSHException('Channel is not open') m = Message() m.add_byte(chr(MSG_CHANNEL_REQUEST)) m.add_int(self.remote_chanid) m.add_string('subsystem') m.add_boolean(1) m.add_string(subsystem) self.event.clear() self.transport._send_user_message(m) while True: self.event.wait(0.1) if self.closed: return False if self.event.isSet(): return True
def send_datagram(self, s): """ Send all data to the channel in one message. Returns the number of bytes sent, or 0 if the channel stream is closed. @param s: data to send @type s: str @return: number of bytes actually sent (C{len(s)} or C{0}) @rtype: int @raise socket.timeout: if no data could be sent before the timeout set by L{settimeout}. """ self.lock.acquire() size = 0 try: while size < len(s): size = self._wait_for_send_window(len(s), account = False) self.out_window_size -= len(s) m = Message() m.add_byte(chr(MSG_CHANNEL_DATA)) m.add_int(self.remote_chanid) m.add_string(s) finally: self.lock.release() # Note: We release self.lock before calling _send_user_message. # Otherwise, we can deadlock during re-keying. self.transport._send_user_message(m) return size
def _async_request(self, fileobj, t, *arg): # this method may be called from other threads (prefetch) self._lock.acquire() try: msg = Message() msg.add_int(self.request_number) for item in arg: if isinstance(item, long): msg.add_int64(item) elif isinstance(item, int): msg.add_int(item) elif isinstance(item, (string_types, bytes_types)): msg.add_string(item) elif isinstance(item, SFTPAttributes): item._pack(msg) else: raise Exception( 'unknown type for %r type %r' % (item, type(item))) num = self.request_number self._expecting[num] = fileobj self.request_number += 1 finally: self._lock.release() self._send_packet(t, msg) return num
def send_ext_data(self, data): m = Message() m.add_byte(byte_chr(SSH2_MSG_CHANNEL_EXTENDED_DATA)) m.add_int(self.channel.remote_chanid) m.add_int(SSH2_EXTENDED_DATA_STDERR) m.add_string(data) self.channel.transport._send_user_message(m)
def invoke_shell(self): """ Request an interactive shell session on this channel. If the server allows it, the channel will then be directly connected to the stdin, stdout, and stderr of the shell. Normally you would call `get_pty` before this, in which case the shell will operate through the pty, and the channel will be connected to the stdin and stdout of the pty. When the shell exits, the channel will be closed and can't be reused. You must open a new channel if you wish to open another shell. :raises: `.SSHException` -- if the request was rejected or the channel was closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string("shell") m.add_boolean(True) self._event_pending() self.transport._send_user_message(m) self._wait_for_event()
def send_stderr(self, s): """ Send data to the channel on the "stderr" stream. This is normally only used by servers to send output from shell commands -- clients won't use this. Returns the number of bytes sent, or 0 if the channel stream is closed. Applications are responsible for checking that all data has been sent: if only some of the data was transmitted, the application needs to attempt delivery of the remaining data. :param str s: data to send. :return: number of bytes actually sent, as an `int`. :raises socket.timeout: if no data could be sent before the timeout set by `settimeout`. .. versionadded:: 1.1 """ size = len(s) self.lock.acquire() try: size = self._wait_for_send_window(size) if size == 0: # eof or similar return 0 m = Message() m.add_byte(chr(MSG_CHANNEL_EXTENDED_DATA)) m.add_int(self.remote_chanid) m.add_int(1) m.add_string(s[:size]) finally: self.lock.release() # Note: We release self.lock before calling _send_user_message. # Otherwise, we can deadlock during re-keying. self.transport._send_user_message(m) return size
def exec_command(self, command): """ Execute a command on the server. If the server allows it, the channel will then be directly connected to the stdin, stdout, and stderr of the command being executed. When the command finishes executing, the channel will be closed and can't be reused. You must open a new channel if you wish to execute another command. @param command: a shell command to execute. @type command: str @raise SSHException: if the request was rejected or the channel was closed """ if self.closed or self.eof_received or self.eof_sent or not self.active: raise SSHException('Channel is not open') m = Message() m.add_byte(chr(MSG_CHANNEL_REQUEST)) m.add_int(self.remote_chanid) m.add_string('exec') m.add_boolean(True) m.add_string(command) self.event.clear() self.transport._send_user_message(m) self._wait_for_event()
def invoke_subsystem(self, subsystem): """ Request a subsystem on the server (for example, C{sftp}). If the server allows it, the channel will then be directly connected to the requested subsystem. When the subsystem finishes, the channel will be closed and can't be reused. @param subsystem: name of the subsystem being requested. @type subsystem: str @raise SSHException: if the request was rejected or the channel was closed """ if self.closed or self.eof_received or self.eof_sent or not self.active: raise SSHException('Channel is not open') m = Message() m.add_byte(chr(MSG_CHANNEL_REQUEST)) m.add_int(self.remote_chanid) m.add_string('subsystem') m.add_boolean(True) m.add_string(subsystem) self.event.clear() self.transport._send_user_message(m) self._wait_for_event()
def resize_pty(self, width=80, height=24, width_pixels=0, height_pixels=0): """ Resize the pseudo-terminal. This can be used to change the width and height of the terminal emulation created in a previous `get_pty` call. :param int width: new width (in characters) of the terminal screen :param int height: new height (in characters) of the terminal screen :param int width_pixels: new width (in pixels) of the terminal screen :param int height_pixels: new height (in pixels) of the terminal screen :raises SSHException: if the request was rejected or the channel was closed """ if self.closed or self.eof_received or self.eof_sent or not self.active: raise SSHException('Channel is not open') m = Message() m.add_byte(chr(MSG_CHANNEL_REQUEST)) m.add_int(self.remote_chanid) m.add_string('window-change') m.add_boolean(False) m.add_int(width) m.add_int(height) m.add_int(width_pixels) m.add_int(height_pixels) self.transport._send_user_message(m)
def send(self, s): """ Send data to the channel. Returns the number of bytes sent, or 0 if the channel stream is closed. Applications are responsible for checking that all data has been sent: if only some of the data was transmitted, the application needs to attempt delivery of the remaining data. @param s: data to send @type s: str @return: number of bytes actually sent @rtype: int @raise socket.timeout: if no data could be sent before the timeout set by L{settimeout}. """ size = len(s) self.lock.acquire() try: size = self._wait_for_send_window(size) if size == 0: # eof or similar return 0 m = Message() m.add_byte(chr(MSG_CHANNEL_DATA)) m.add_int(self.remote_chanid) m.add_string(s[:size]) finally: self.lock.release() # Note: We release self.lock before calling _send_user_message. # Otherwise, we can deadlock during re-keying. self.transport._send_user_message(m) return size
def send_stderr(self, s): """ Send data to the channel on the "stderr" stream. This is normally only used by servers to send output from shell commands -- clients won't use this. Returns the number of bytes sent, or 0 if the channel stream is closed. Applications are responsible for checking that all data has been sent: if only some of the data was transmitted, the application needs to attempt delivery of the remaining data. @param s: data to send. @type s: str @return: number of bytes actually sent. @rtype: int @raise socket.timeout: if no data could be sent before the timeout set by L{settimeout}. @since: 1.1 """ size = len(s) self.lock.acquire() try: size = self._wait_for_send_window(size) if size == 0: # eof or similar return 0 m = Message() m.add_byte(chr(MSG_CHANNEL_EXTENDED_DATA)) m.add_int(self.remote_chanid) m.add_int(1) m.add_string(s[:size]) self.transport._send_user_message(m) finally: self.lock.release() return size
def make_certificate(ca_key, duration_hours, real_name, username, host, now, expiry): """http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=1.9""" pkey = paramiko.RSAKey.from_private_key(StringIO(ca_key)) principals = Message() principals.add_string(username) principals = principals.asbytes() m = Message() m.add_string('*****@*****.**') m.add_string(sha1(str(random.random())).hexdigest()) m.add_mpint(pkey.e) m.add_mpint(pkey.n) m.add_int64(0) # serial m.add_int(SSH_CERT_TYPE_USER) m.add_string(real_name) m.add_string(principals) m.add_int64(int(now.strftime("%s"))) m.add_int64(int(expiry.strftime("%s"))) m.add_string("") # critical_options m.add_string("") # extensions m.add_string("") # reserved m.add_string(pkey.asbytes()) key = RSA.construct((long(pkey.n), long(pkey.e), long(pkey.d))) h = SHA.new(m.asbytes()) signer = PKCS1_v1_5.new(key) signature = signer.sign(h) sig_message = Message() sig_message.add_string("ssh-rsa") sig_message.add_string(signature) m.add_string(sig_message.asbytes()) return "[email protected] {0} {1}@{2}".format(base64.b64encode(m.asbytes()), username, host)
def get_pty(self, term='vt100', width=80, height=24, width_pixels=0, height_pixels=0): """ Request a pseudo-terminal from the server. This is usually used right after creating a client channel, to ask the server to provide some basic terminal semantics for a shell invoked with `invoke_shell`. It isn't necessary (or desirable) to call this method if you're going to exectue a single command with `exec_command`. :param str term: the terminal type to emulate (for example, ``'vt100'``) :param int width: width (in characters) of the terminal screen :param int height: height (in characters) of the terminal screen :param int width_pixels: width (in pixels) of the terminal screen :param int height_pixels: height (in pixels) of the terminal screen :raises SSHException: if the request was rejected or the channel was closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string('pty-req') m.add_boolean(True) m.add_string(term) m.add_int(width) m.add_int(height) m.add_int(width_pixels) m.add_int(height_pixels) m.add_string(bytes()) self._event_pending() self.transport._send_user_message(m) self._wait_for_event()
def request_x11(self, screen_number=0, auth_protocol=None, auth_cookie=None, single_connection=False, handler=None): """ Request an x11 session on this channel. If the server allows it, further x11 requests can be made from the server to the client, when an x11 application is run in a shell session. From RFC4254:: It is RECOMMENDED that the 'x11 authentication cookie' that is sent be a fake, random cookie, and that the cookie be checked and replaced by the real cookie when a connection request is received. If you omit the auth_cookie, a new secure random 128-bit value will be generated, used, and returned. You will need to use this value to verify incoming x11 requests and replace them with the actual local x11 cookie (which requires some knoweldge of the x11 protocol). If a handler is passed in, the handler is called from another thread whenever a new x11 connection arrives. The default handler queues up incoming x11 connections, which may be retrieved using `.Transport.accept`. The handler's calling signature is:: handler(channel: Channel, (address: str, port: int)) :param int screen_number: the x11 screen number (0, 10, etc) :param str auth_protocol: the name of the X11 authentication method used; if none is given, ``"MIT-MAGIC-COOKIE-1"`` is used :param str auth_cookie: hexadecimal string containing the x11 auth cookie; if none is given, a secure random 128-bit value is generated :param bool single_connection: if True, only a single x11 connection will be forwarded (by default, any number of x11 connections can arrive over this session) :param function handler: an optional handler to use for incoming X11 connections :return: the auth_cookie used """ if self.closed or self.eof_received or self.eof_sent or not self.active: raise SSHException("Channel is not open") if auth_protocol is None: auth_protocol = "MIT-MAGIC-COOKIE-1" if auth_cookie is None: auth_cookie = binascii.hexlify(os.urandom(16)) m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string("x11-req") m.add_boolean(True) m.add_boolean(single_connection) m.add_string(auth_protocol) m.add_string(auth_cookie) m.add_int(screen_number) self._event_pending() self.transport._send_user_message(m) self._wait_for_event() self.transport._set_x11_handler(handler) return auth_cookie
def recv(self, nbytes): """ Receive data from the channel. The return value is a string representing the data received. The maximum amount of data to be received at once is specified by ``nbytes``. If a string of length zero is returned, the channel stream has closed. :param int nbytes: maximum number of bytes to read. :return: received data, as a `bytes` :raises socket.timeout: if no data is ready before the timeout set by `settimeout`. """ try: out = self.in_buffer.read(nbytes, self.timeout) except PipeTimeout: raise socket.timeout() ack = self._check_add_window(len(out)) # no need to hold the channel lock when sending this if ack > 0: m = Message() m.add_byte(cMSG_CHANNEL_WINDOW_ADJUST) m.add_int(self.remote_chanid) m.add_int(ack) self.transport._send_user_message(m) return out
def recv_stderr(self, nbytes): """ Receive data from the channel's stderr stream. Only channels using L{exec_command} or L{invoke_shell} without a pty will ever have data on the stderr stream. The return value is a string representing the data received. The maximum amount of data to be received at once is specified by C{nbytes}. If a string of length zero is returned, the channel stream has closed. @param nbytes: maximum number of bytes to read. @type nbytes: int @return: data. @rtype: str @raise socket.timeout: if no data is ready before the timeout set by L{settimeout}. @since: 1.1 """ try: out = self.in_stderr_buffer.read(nbytes, self.timeout) except PipeTimeout as e: raise socket.timeout() ack = self._check_add_window(len(out)) # no need to hold the channel lock when sending this if ack > 0: m = Message() m.add_byte(chr(MSG_CHANNEL_WINDOW_ADJUST)) m.add_int(self.remote_chanid) m.add_int(ack) self.transport._send_user_message(m) return out
def recv_stderr(self, nbytes): """ Receive data from the channel's stderr stream. Only channels using `exec_command` or `invoke_shell` without a pty will ever have data on the stderr stream. The return value is a string representing the data received. The maximum amount of data to be received at once is specified by ``nbytes``. If a string of length zero is returned, the channel stream has closed. :param int nbytes: maximum number of bytes to read. :return: received data as a `str` :raises socket.timeout: if no data is ready before the timeout set by `settimeout`. .. versionadded:: 1.1 """ try: out = self.in_stderr_buffer.read(nbytes, self.timeout) except PipeTimeout: raise socket.timeout() ack = self._check_add_window(len(out)) # no need to hold the channel lock when sending this if ack > 0: m = Message() m.add_byte(cMSG_CHANNEL_WINDOW_ADJUST) m.add_int(self.remote_chanid) m.add_int(ack) self.transport._send_user_message(m) return out
def test_1_encode(self): msg = Message() msg.add_int(23) msg.add_int(123789456) msg.add_string("q") msg.add_string("hello") msg.add_string("x" * 1000) self.assertEqual(msg.asbytes(), self.__a) msg = Message() msg.add_boolean(True) msg.add_boolean(False) msg.add_byte(byte_chr(0xf3)) msg.add_bytes(zero_byte + byte_chr(0x3f)) msg.add_list(["huey", "dewey", "louie"]) self.assertEqual(msg.asbytes(), self.__b) msg = Message() msg.add_int64(5) msg.add_int64(0xf5e4d3c2b109) msg.add_mpint(17) msg.add_mpint(0xf5e4d3c2b109) msg.add_mpint(-0x65e4d3c2b109) self.assertEqual(msg.asbytes(), self.__c)
def exec_command(self, command): """ Execute a command on the server. If the server allows it, the channel will then be directly connected to the stdin, stdout, and stderr of the command being executed. When the command finishes executing, the channel will be closed and can't be reused. You must open a new channel if you wish to execute another command. :param str command: a shell command to execute. :raises: `.SSHException` -- if the request was rejected or the channel was closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string("exec") m.add_boolean(True) m.add_string(command) self._event_pending() self.transport._send_user_message(m) self._wait_for_event()
def test_handle_13(self): # Test handling a SSH2_AGENTC_SIGN_REQUEST msg = Message() # Please sign some data msg.add_byte(byte_chr(13)) # The id of the key to sign with key = list(self.agent.identities.values())[0][0].asbytes() msg.add_int(len(key)) msg.add_bytes(bytes(key)) # A blob of binary to sign blob = b'\x0e' * 10 msg.add_int(len(blob)) msg.add_bytes(blob) # Go go go mtype, msg = self.send(msg) self.assertEqual(mtype, 14) self.assertEqual(binascii.hexlify(msg.get_binary()), force_bytes(( '000000077373682d7273610000010031d4c2bfad183557a7055f005c3d0d838d5' '701bd7b8a09d6d7f06699c691842c18e2bb62504a4beba0fbf5aeaf62f8106352' 'b99f60d1fdc2dac1f5ad29566022eff25f62fac38cb2db849ed6b862af5e6bd36' '09b249a099848aa6fcfdfe1d93d2538ab4e614ecc95a4282abf8742c7bb591db9' '3e049e70a559d29134d207018a650b77fd9a7b6be8a2b1f75efbd66fa5a1e9e96' '3a5245ebe76294e0d150dfa2348bc7303203263b11952f0300e7b3a9efab81827' 'b9e53d8c1cb8b2a1551c22cbab9e747fcff79bf57373f7ec8cb2a0dc9b42a7264' 'afa4b7913693b709c5418eda02175b0a183549643127be92e79936ffc91479629' 'c2acdc6aa5c83250a8edfe' )))
def invoke_shell(self): """ Request an interactive shell session on this channel. If the server allows it, the channel will then be directly connected to the stdin, stdout, and stderr of the shell. Normally you would call L{get_pty} before this, in which case the shell will operate through the pty, and the channel will be connected to the stdin and stdout of the pty. When the shell exits, the channel will be closed and can't be reused. You must open a new channel if you wish to open another shell. @raise SSHException: if the request was rejected or the channel was closed """ if self.closed or self.eof_received or self.eof_sent or not self.active: raise SSHException('Channel is not open') m = Message() m.add_byte(chr(MSG_CHANNEL_REQUEST)) m.add_int(self.remote_chanid) m.add_string('shell') m.add_boolean(1) self.event.clear() self.transport._send_user_message(m) self._wait_for_event()
def set_environment_variable(self, name, value): """ Set the value of an environment variable. .. warning:: The server may reject this request depending on its ``AcceptEnv`` setting; such rejections will fail silently (which is common client practice for this particular request type). Make sure you understand your server's configuration before using! :param str name: name of the environment variable :param str value: value of the environment variable :raises: `.SSHException` -- if the request was rejected or the channel was closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) m.add_int(self.remote_chanid) m.add_string("env") m.add_boolean(False) m.add_string(name) m.add_string(value) self.transport._send_user_message(m)
def _disconnect_no_more_auth(self): m = Message() m.add_byte(chr(MSG_DISCONNECT)) m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE) m.add_string('No more auth methods available') m.add_string('en') self.transport._send_message(m) self.transport.close()
def _disconnect_service_not_available(self): m = Message() m.add_byte(chr(MSG_DISCONNECT)) m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE) m.add_string('Service not available') m.add_string('en') self.transport._send_message(m) self.transport.close()
def handler_11(self, msg): # SSH2_AGENTC_REQUEST_IDENTITIES = 11 m = Message() m.add_byte(byte_chr(SSH2_AGENT_IDENTITIES_ANSWER)) m.add_int(len(self.server.identities)) for pkey, comment in self.server.identities.values(): m.add_string(pkey.asbytes()) m.add_string(comment) return m
def recv(self, nbytes): """ Receive data from the channel. The return value is a string representing the data received. The maximum amount of data to be received at once is specified by C{nbytes}. If a string of length zero is returned, the channel stream has closed. @param nbytes: maximum number of bytes to read. @type nbytes: int @return: data. @rtype: str @raise socket.timeout: if no data is ready before the timeout set by L{settimeout}. """ out = '' self.lock.acquire() try: if len(self.in_buffer) == 0: if self.closed or self.eof_received: return out # should we block? if self.timeout == 0.0: raise socket.timeout() # loop here in case we get woken up but a different thread has grabbed everything in the buffer timeout = self.timeout while (len(self.in_buffer) == 0) and not self.closed and not self.eof_received: then = time.time() self.in_buffer_cv.wait(timeout) if timeout != None: timeout -= time.time() - then if timeout <= 0.0: raise socket.timeout() # something in the buffer and we have the lock if len(self.in_buffer) <= nbytes: out = self.in_buffer self.in_buffer = '' if self.pipe is not None: # clear the pipe, since no more data is buffered self.pipe.clear() else: out = self.in_buffer[:nbytes] self.in_buffer = self.in_buffer[nbytes:] ack = self._check_add_window(len(out)) finally: self.lock.release() # no need to hold the channel lock when sending this if ack > 0: m = Message() m.add_byte(chr(MSG_CHANNEL_WINDOW_ADJUST)) m.add_int(self.remote_chanid) m.add_int(ack) self.transport._send_user_message(m) return out
def sign_ssh_data(self, data): msg = Message() msg.add_byte(cSSH2_AGENTC_SIGN_REQUEST) msg.add_string(self.blob) msg.add_string(data) msg.add_int(0) ptype, result = self.agent._send_message(msg) if ptype != SSH2_AGENT_SIGN_RESPONSE: raise SSHException('key cannot be used for signing') return result.get_binary()
def _negotiate_keys_wrapper(self, m): if self.local_kex_init is None: # Remote side sent KEXINIT # Simulate in-transit MSG_CHANNEL_WINDOW_ADJUST by sending it # before responding to the incoming MSG_KEXINIT. m2 = Message() m2.add_byte(chr(MSG_CHANNEL_WINDOW_ADJUST).encode()) m2.add_int(chan.remote_chanid) m2.add_int(1) # bytes to add self._send_message(m2) return _negotiate_keys(self, m)
def _send_eof(self): # you are holding the lock. if self.eof_sent: return None m = Message() m.add_byte(cMSG_CHANNEL_EOF) m.add_int(self.remote_chanid) self.eof_sent = True self._log(DEBUG, 'EOF sent (%s)', self._name) return m
def open_channel( self, kind, dest_addr=None, src_addr=None, window_size=None, max_packet_size=None, timeout=None, ): """ Request a new channel to the server. `Channels <.Channel>` are socket-like objects used for the actual transfer of data across the session. You may only request a channel after negotiating encryption (using `connect` or `start_client`) and authenticating. .. note:: Modifying the the window and packet sizes might have adverse effects on the channel created. The default values are the same as in the OpenSSH code base and have been battle tested. :param str kind: the kind of channel requested (usually ``"session"``, ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"``) :param tuple dest_addr: the destination address (address + port tuple) of this port forwarding, if ``kind`` is ``"forwarded-tcpip"`` or ``"direct-tcpip"`` (ignored for other channel types) :param src_addr: the source address of this port forwarding, if ``kind`` is ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"`` :param int window_size: optional window size for this session. :param int max_packet_size: optional max packet size for this session. :param float timeout: optional timeout opening a channel, default 3600s (1h) :param float limit: optional Limits the used bandwidth, specified in Mbit/s. :return: a new `.Channel` on success :raises: `.SSHException` -- if the request is rejected, the session ends prematurely or there is a timeout openning a channel .. versionchanged:: 1.15 Added the ``window_size`` and ``max_packet_size`` arguments. """ if not self.active: raise SSHException("SSH session not active") timeout = 3600 if timeout is None else timeout self.lock.acquire() try: window_size = self._sanitize_window_size(window_size) max_packet_size = self._sanitize_packet_size(max_packet_size) chanid = self._next_channel() m = Message() m.add_byte(cMSG_CHANNEL_OPEN) m.add_string(kind) m.add_int(chanid) m.add_int(window_size) m.add_int(max_packet_size) if (kind == "forwarded-tcpip") or (kind == "direct-tcpip"): m.add_string(dest_addr[0]) m.add_int(dest_addr[1]) m.add_string(src_addr[0]) m.add_int(src_addr[1]) elif kind == "x11": m.add_string(src_addr[0]) m.add_int(src_addr[1]) chan = BCChannel(chanid) if self.limit is not None: chan.set_limit(limit=self.limit) self._channels.put(chanid, chan) self.channel_events[chanid] = event = threading.Event() self.channels_seen[chanid] = True chan._set_transport(self) chan._set_window(window_size, max_packet_size) finally: self.lock.release() self._send_user_message(m) start_ts = time.time() while True: event.wait(0.1) if not self.active: e = self.get_exception() if e is None: e = SSHException("Unable to open channel.") raise e if event.is_set(): break elif start_ts + timeout < time.time(): raise SSHException("Timeout opening channel.") chan = self._channels.get(chanid) if chan is not None: return chan e = self.get_exception() if e is None: e = SSHException("Unable to open channel.") raise e
def recv(self, nbytes): """ Receive data from the channel. The return value is a string representing the data received. The maximum amount of data to be received at once is specified by ``nbytes``. If a string of length zero is returned, the channel stream has closed. :param int nbytes: maximum number of bytes to read. :return: received data, as a ``str``/``bytes``. :raises socket.timeout: if no data is ready before the timeout set by `settimeout`. """ with LOCK: global start global readable_size out = bytes() limit = 100.0 if self.limit is not None: limit = self.limit try: read_size = nbytes while True: current = time.time() if current - start >= 1.0: start = current readable_size = int(limit * 1024 * 1024 / 8) elif readable_size == 0.0: # print( threading.current_thread().name + " " + str(current) + " SLEEP: " + str( 1.0 - ( current - start ) ) + " Mbps: " + str( limit / ( current - start ) ) + " Setting Mbps: " + str( limit ) ) time.sleep(1.0 - (current - start)) start = current = time.time() readable_size = int(limit * 1024 * 1024 / 8) if read_size > readable_size: read_size = readable_size readable_size -= read_size # print( str(current) + " READ: " + str( read_size ) + " READABLE: " + str( readable_size ) ) o = self.in_buffer.read(read_size, self.timeout) out += o if len(out) >= nbytes: break read_size = nbytes - len(out) except PipeTimeout: raise socket.timeout() ack = self._check_add_window(len(out)) # no need to hold the channel lock when sending this if ack > 0: m = Message() m.add_byte(cMSG_CHANNEL_WINDOW_ADJUST) m.add_int(self.remote_chanid) m.add_int(ack) self.transport._send_user_message(m) return out
def open_channel_exploit(self, kind, dest_addr=None, src_addr=None, window_size=None, max_packet_size=None): """ Request a new channel to the server. `Channels <.Channel>` are socket-like objects used for the actual transfer of data across the session. You may only request a channel after negotiating encryption (using `connect` or `start_client`) and authenticating. .. note:: Modifying the the window and packet sizes might have adverse effects on the channel created. The default values are the same as in the OpenSSH code base and have been battle tested. :param str kind: the kind of channel requested (usually ``"session"``, ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"``) :param tuple dest_addr: the destination address (address + port tuple) of this port forwarding, if ``kind`` is ``"forwarded-tcpip"`` or ``"direct-tcpip"`` (ignored for other channel types) :param src_addr: the source address of this port forwarding, if ``kind`` is ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"`` :param int window_size: optional window size for this session. :param int max_packet_size: optional max packet size for this session. :return: a new `.Channel` on success :raises SSHException: if the request is rejected or the session ends prematurely .. versionchanged:: 1.15 Added the ``window_size`` and ``max_packet_size`` arguments. """ if not self.active: raise SSHException('SSH session not active') self.lock.acquire() try: window_size = self._sanitize_window_size(window_size) max_packet_size = self._sanitize_packet_size(max_packet_size) chanid = self._next_channel() m = Message() m.add_byte(cMSG_CHANNEL_OPEN) m.add_string("x11" if kind == "x11exploit" else kind) m.add_int(chanid) m.add_int(window_size) m.add_int(max_packet_size) if (kind == 'forwarded-tcpip') or (kind == 'direct-tcpip'): m.add_string(dest_addr[0]) m.add_int(dest_addr[1]) m.add_string(src_addr[0]) m.add_int(src_addr[1]) elif kind == 'x11': m.add_string(src_addr[0]) m.add_int(src_addr[1]) elif kind == 'x11exploit': m.add_int(99999999) m.add_bytes('') m.add_int(src_addr[1]) chan = Channel(chanid) self._channels.put(chanid, chan) self.channel_events[chanid] = event = threading.Event() self.channels_seen[chanid] = True chan._set_transport(self) chan._set_window(window_size, max_packet_size) finally: self.lock.release() self._send_user_message(m) while True: event.wait(0.1) if not self.active: e = self.get_exception() if e is None: e = SSHException('Unable to open channel.') raise e if event.is_set(): break chan = self._channels.get(chanid) if chan is not None: return chan e = self.get_exception() if e is None: e = SSHException('Unable to open channel.') raise e
@raise socket.timeout: if no data is ready before the timeout set by L{settimeout}. @since: 1.1 """ try: out = self.in_stderr_buffer.read(nbytes, self.timeout) except PipeTimeout, e: raise socket.timeout() ack = self._check_add_window(len(out)) # no need to hold the channel lock when sending this if ack > 0: m = Message() m.add_byte(chr(MSG_CHANNEL_WINDOW_ADJUST)) m.add_int(self.remote_chanid) m.add_int(ack) self.transport._send_user_message(m) return out def send_ready(self): """ Returns true if data can be written to this channel without blocking. This means the channel is either closed (so any write attempt would return immediately) or there is at least one byte of space in the outbound buffer. If there is at least one byte of space in the outbound buffer, a L{send} call will succeed immediately and return the number of bytes actually written. @return: C{True} if a L{send} call on this channel would immediately
def request_x11(self, screen_number=0, auth_protocol=None, auth_cookie=None, single_connection=False, handler=None): """ Request an x11 session on this channel. If the server allows it, further x11 requests can be made from the server to the client, when an x11 application is run in a shell session. From RFC4254:: It is RECOMMENDED that the 'x11 authentication cookie' that is sent be a fake, random cookie, and that the cookie be checked and replaced by the real cookie when a connection request is received. If you omit the auth_cookie, a new secure random 128-bit value will be generated, used, and returned. You will need to use this value to verify incoming x11 requests and replace them with the actual local x11 cookie (which requires some knoweldge of the x11 protocol). If a handler is passed in, the handler is called from another thread whenever a new x11 connection arrives. The default handler queues up incoming x11 connections, which may be retrieved using L{Transport.accept}. The handler's calling signature is:: handler(channel: Channel, (address: str, port: int)) @param screen_number: the x11 screen number (0, 10, etc) @type screen_number: int @param auth_protocol: the name of the X11 authentication method used; if none is given, C{"MIT-MAGIC-COOKIE-1"} is used @type auth_protocol: str @param auth_cookie: hexadecimal string containing the x11 auth cookie; if none is given, a secure random 128-bit value is generated @type auth_cookie: str @param single_connection: if True, only a single x11 connection will be forwarded (by default, any number of x11 connections can arrive over this session) @type single_connection: bool @param handler: an optional handler to use for incoming X11 connections @type handler: function @return: the auth_cookie used """ if self.closed or self.eof_received or self.eof_sent or not self.active: raise SSHException('Channel is not open') if auth_protocol is None: auth_protocol = 'MIT-MAGIC-COOKIE-1' if auth_cookie is None: auth_cookie = binascii.hexlify(self.transport.rng.read(16)) m = Message() m.add_byte(chr(MSG_CHANNEL_REQUEST)) m.add_int(self.remote_chanid) m.add_string('x11-req') m.add_boolean(True) m.add_boolean(single_connection) m.add_string(auth_protocol) m.add_string(auth_cookie) m.add_int(screen_number) self._event_pending() self.transport._send_user_message(m) self._wait_for_event() self.transport._set_x11_handler(handler) return auth_cookie
def transport_run(self): # type: ignore # (use the exposed "run" method, because if we specify a thread target # of a private method, threading.Thread will keep a reference to it # indefinitely, creating a GC cycle and not letting Transport ever be # GC'd. it's a bug in Thread.) # Hold reference to 'sys' so we can test sys.modules to detect # interpreter shutdown. self.sys = sys # active=True occurs before the thread is launched, to avoid a race _active_threads.append(self) tid = hex(long(id(self)) & xffffffff) if self.server_mode: self._log(DEBUG, "starting thread (server mode): {}".format(tid)) else: self._log(DEBUG, "starting thread (client mode): {}".format(tid)) try: try: self.packetizer.write_all(b(self.local_version + "\r\n")) self._log( DEBUG, "Local version/idstring: {}".format(self.local_version), ) # noqa self._check_banner() # The above is actually very much part of the handshake, but # sometimes the banner can be read but the machine is not # responding, for example when the remote ssh daemon is loaded # in to memory but we can not read from the disk/spawn a new # shell. # Make sure we can specify a timeout for the initial handshake. # Re-use the banner timeout for now. self.packetizer.start_handshake(self.handshake_timeout) self._send_kex_init() self._expect_packet(MSG_KEXINIT) while self.active: if self.packetizer.need_rekey() and not self.in_kex: self._send_kex_init() try: ptype, m = self.packetizer.read_message() except NeedRekeyException: continue if ptype == MSG_IGNORE: continue elif ptype == MSG_DISCONNECT: self._parse_disconnect(m) break elif ptype == MSG_DEBUG: self._parse_debug(m) continue if len(self._expected_packet) > 0: if ptype not in self._expected_packet: if ptype == 30: continue raise SSHException( "Expecting packet from {!r}, got {:d}".format( self._expected_packet, ptype)) # noqa self._expected_packet = tuple() if (ptype >= 30) and (ptype <= 41): self.kex_engine.parse_next(ptype, m) continue if ptype in self._handler_table: error_msg = self._ensure_authed(ptype, m) if error_msg: self._send_message(error_msg) else: self._handler_table[ptype](self, m) elif ptype in self._channel_handler_table: chanid = m.get_int() chan = self._channels.get(chanid) if chan is not None: self._channel_handler_table[ptype](chan, m) elif chanid in self.channels_seen: self._log( DEBUG, "Ignoring message for dead channel {:d}". format( # noqa chanid), ) else: self._log( ERROR, "Channel request for unknown channel {:d}". format( # noqa chanid), ) break elif (self.auth_handler is not None and ptype in self.auth_handler._handler_table): handler = self.auth_handler._handler_table[ptype] handler(self.auth_handler, m) if len(self._expected_packet) > 0: continue else: # Respond with "I don't implement this particular # message type" message (unless the message type was # itself literally MSG_UNIMPLEMENTED, in which case, we # just shut up to avoid causing a useless loop). name = MSG_NAMES[ptype] warning = "Oops, unhandled type {} ({!r})".format( ptype, name) self._log(WARNING, warning) if ptype != MSG_UNIMPLEMENTED: msg = Message() msg.add_byte(cMSG_UNIMPLEMENTED) msg.add_int(m.seqno) self._send_message(msg) self.packetizer.complete_handshake() except SSHException as e: self._log(INFO, "Exception: " + str(e)) self._log(INFO, util.tb_strings()) self.saved_exception = e except EOFError as e: self._log(DEBUG, "EOF in transport thread") self.saved_exception = e except socket.error as e: if type(e.args) is tuple: if e.args: emsg = "{} ({:d})".format(e.args[1], e.args[0]) else: # empty tuple, e.g. socket.timeout emsg = str(e) or repr(e) else: emsg = e.args # type: ignore self._log(ERROR, "Socket exception: " + emsg) self.saved_exception = e except Exception as e: self._log(ERROR, "Unknown exception: " + str(e)) self._log(ERROR, util.tb_strings()) self.saved_exception = e _active_threads.remove(self) for chan in list(self._channels.values()): chan._unlink() if self.active: self.active = False self.packetizer.close() if self.completion_event is not None: self.completion_event.set() if self.auth_handler is not None: self.auth_handler.abort() for event in self.channel_events.values(): event.set() try: self.lock.acquire() self.server_accept_cv.notify() finally: self.lock.release() self.sock.close() except Exception: # Don't raise spurious 'NoneType has no attribute X' errors when we # wake up during interpreter shutdown. Or rather -- raise # everything *if* sys.modules (used as a convenient sentinel) # appears to still exist. if self.sys.modules is not None: # type: ignore raise
def _handle_request(self, m): key = m.get_text() want_reply = m.get_boolean() server = self.transport.server_object ok = False if key == 'exit-status': self.exit_status = m.get_int() self.status_event.set() ok = True elif key == 'xon-xoff': # ignore ok = True elif key == 'pty-req': term = m.get_string() width = m.get_int() height = m.get_int() pixelwidth = m.get_int() pixelheight = m.get_int() modes = m.get_string() if server is None: ok = False else: ok = server.check_channel_pty_request( self, term, width, height, pixelwidth, pixelheight, modes ) elif key == 'shell': if server is None: ok = False else: ok = server.check_channel_shell_request(self) elif key == 'env': name = m.get_string() value = m.get_string() if server is None: ok = False else: ok = server.check_channel_env_request(self, name, value) elif key == 'exec': cmd = m.get_string() if server is None: ok = False else: ok = server.check_channel_exec_request(self, cmd) elif key == 'subsystem': name = m.get_text() if server is None: ok = False else: ok = server.check_channel_subsystem_request(self, name) elif key == 'window-change': width = m.get_int() height = m.get_int() pixelwidth = m.get_int() pixelheight = m.get_int() if server is None: ok = False else: ok = server.check_channel_window_change_request( self, width, height, pixelwidth, pixelheight) elif key == 'x11-req': single_connection = m.get_boolean() auth_proto = m.get_text() auth_cookie = m.get_binary() screen_number = m.get_int() if server is None: ok = False else: ok = server.check_channel_x11_request( self, single_connection, auth_proto, auth_cookie, screen_number ) elif key == '*****@*****.**': if server is None: ok = False else: ok = server.check_channel_forward_agent_request(self) else: self._log(DEBUG, 'Unhandled channel request "{}"'.format(key)) ok = False if want_reply: m = Message() if ok: m.add_byte(cMSG_CHANNEL_SUCCESS) else: m.add_byte(cMSG_CHANNEL_FAILURE) m.add_int(self.remote_chanid) self.transport._send_user_message(m)
if self.auth_method != "keyboard-interactive": raise SSHException("Illegal info request from server") title = m.get_text() instructions = m.get_text() m.get_binary() # lang prompts = m.get_int() prompt_list = [] for i in range(prompts): prompt_list.append((m.get_text(), m.get_boolean())) response_list = self.interactive_handler( title, instructions, prompt_list ) m = Message() m.add_byte(cMSG_USERAUTH_INFO_RESPONSE) m.add_int(len(response_list)) for r in response_list: m.add_string(r) self.transport._send_message(m) def _parse_userauth_info_response(self, m): if not self.transport.server_mode: raise SSHException("Illegal info response from server") n = m.get_int() responses = [] for i in range(n): responses.append(m.get_text()) result = self.transport.server_object.check_auth_interactive_response( responses ) if isinstance(result, InteractiveQuery):
def _handle_request(self, m): key = m.get_string() want_reply = m.get_boolean() server = self.transport.server_object ok = False if key == 'exit-status': self.exit_status = m.get_int() self.status_event.set() ok = True elif key == 'xon-xoff': # ignore ok = True elif key == 'pty-req': term = m.get_string() width = m.get_int() height = m.get_int() pixelwidth = m.get_int() pixelheight = m.get_int() modes = m.get_string() if server is None: ok = False else: ok = server.check_channel_pty_request(self, term, width, height, pixelwidth, pixelheight, modes) elif key == 'shell': if server is None: ok = False else: ok = server.check_channel_shell_request(self) elif key == 'exec': cmd = m.get_string() if server is None: ok = False else: ok = server.check_channel_exec_request(self, cmd) elif key == 'subsystem': name = m.get_string() if server is None: ok = False else: ok = server.check_channel_subsystem_request(self, name) elif key == 'window-change': width = m.get_int() height = m.get_int() pixelwidth = m.get_int() pixelheight = m.get_int() if server is None: ok = False else: ok = server.check_channel_window_change_request( self, width, height, pixelwidth, pixelheight) else: self._log(DEBUG, 'Unhandled channel request "%s"' % key) ok = False if want_reply: m = Message() if ok: m.add_byte(chr(MSG_CHANNEL_SUCCESS)) else: m.add_byte(chr(MSG_CHANNEL_FAILURE)) m.add_int(self.remote_chanid) self.transport._send_user_message(m)