def sign_ssh_data(self, data): key = dsa.DSAPrivateNumbers( x=self.x, public_numbers=dsa.DSAPublicNumbers( y=self.y, parameter_numbers=dsa.DSAParameterNumbers( p=self.p, q=self.q, g=self.g ) ) ).private_key(backend=default_backend()) signer = key.signer(hashes.SHA1()) signer.update(data) r, s = decode_dss_signature(signer.finalize()) m = Message() m.add_string('ssh-dss') # apparently, in rare cases, r or s may be shorter than 20 bytes! rstr = util.deflate_long(r, 0) sstr = util.deflate_long(s, 0) if len(rstr) < 20: rstr = zero_byte * (20 - len(rstr)) + rstr if len(sstr) < 20: sstr = zero_byte * (20 - len(sstr)) + sstr m.add_string(rstr + sstr) return m
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None): self.p = None self.q = None self.g = None self.y = None self.x = None self.public_blob = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.p, self.q, self.g, self.y = vals else: self._check_type_and_load_cert( msg=msg, key_type='ssh-dss', cert_type='*****@*****.**', ) self.p = msg.get_mpint() self.q = msg.get_mpint() self.g = msg.get_mpint() self.y = msg.get_mpint() self.size = util.bit_length(self.p)
def generate_key_pair(request): """ Response to generate private/public RSA key pair """ get_user(request, settings.ASTAKOS_AUTH_URL) if request.method != "POST": return http.HttpResponseNotAllowed(["POST"]) if not SUPPORT_GENERATE_KEYS: raise Exception("Application does not support ssh keys generation") if PublicKeyPair.user_limit_exceeded(request.user_uniq): return http.HttpResponseServerError("SSH keys limit exceeded") # generate RSA key from Crypto import Random Random.atfork() key = rsakey.RSA.generate(SSH_KEY_LENGTH) # get PEM string pem = exportKey(key, 'PEM') public_data = Message() public_data.add_string('ssh-rsa') public_data.add_mpint(key.key.e) public_data.add_mpint(key.key.n) # generate public content public = str("ssh-rsa %s" % base64.b64encode(str(public_data))) data = {'private': pem, 'public': public} return http.HttpResponse(json.dumps(data), mimetype="application/json")
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None, filecontent=None): self.n = None self.e = None self.d = None self.p = None self.q = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if filecontent is not None: self._decode_key(base64.decodestring(filecontent)) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.e, self.n = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_string() != 'ssh-rsa': raise SSHException('Invalid key') self.e = msg.get_mpint() self.n = msg.get_mpint() self.size = util.bit_length(self.n)
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None, validate_point=True): self.verifying_key = None self.signing_key = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.signing_key, self.verifying_key = vals c_class = self.signing_key.curve.__class__ self.ecdsa_curve = self._ECDSA_CURVES.get_by_curve_class(c_class) else: if msg is None: raise SSHException('Key object may not be empty') self.ecdsa_curve = self._ECDSA_CURVES.get_by_key_format_identifier( msg.get_text()) if self.ecdsa_curve is None: raise SSHException('Invalid key') curvename = msg.get_text() if curvename != self.ecdsa_curve.nist_name: raise SSHException("Can't handle curve of type %s" % curvename) pointinfo = msg.get_binary() try: numbers = ec.EllipticCurvePublicNumbers.from_encoded_point( self.ecdsa_curve.curve_class(), pointinfo ) except ValueError: raise SSHException("Invalid public key") self.verifying_key = numbers.public_key(backend=default_backend())
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None, validate_point=True): self.verifying_key = None self.signing_key = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.signing_key, self.verifying_key = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != 'ecdsa-sha2-nistp256': raise SSHException('Invalid key') curvename = msg.get_text() if curvename != 'nistp256': raise SSHException("Can't handle curve of type %s" % curvename) pointinfo = msg.get_binary() if pointinfo[0:1] != four_byte: raise SSHException('Point compression is being used: %s' % binascii.hexlify(pointinfo)) curve = ec.SECP256R1() numbers = ec.EllipticCurvePublicNumbers( x=inflate_long(pointinfo[1:1 + curve.key_size // 8], always_positive=True), y=inflate_long(pointinfo[1 + curve.key_size // 8:], always_positive=True), curve=curve ) self.verifying_key = numbers.public_key(backend=default_backend()) self.size = 256
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None): self.p = None self.q = None self.g = None self.y = None self.x = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.p, self.q, self.g, self.y = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != 'ssh-dss': raise SSHException('Invalid key') self.p = msg.get_mpint() self.q = msg.get_mpint() self.g = msg.get_mpint() self.y = msg.get_mpint() self.size = util.bit_length(self.p)
def sign_ssh_data(self, data): sig = self.signing_key.sign_deterministic( data, sigencode=self._sigencode, hashfunc=sha256) m = Message() m.add_string('ecdsa-sha2-nistp256') m.add_string(sig) return m
def _parse_kexdh_gex_request_old(self, m): # same as above, but without min_bits or max_bits (used by older # clients like putty) self.preferred_bits = m.get_int() # smoosh the user's preferred size into our own limits if self.preferred_bits > self.max_bits: self.preferred_bits = self.max_bits if self.preferred_bits < self.min_bits: self.preferred_bits = self.min_bits # generate prime pack = self.transport._get_modulus_pack() if pack is None: raise SSHException("Can't do server-side gex with no modulus pack") self.transport._log( DEBUG, "Picking p (~ {} bits)".format(self.preferred_bits) ) self.g, self.p = pack.get_modulus( self.min_bits, self.preferred_bits, self.max_bits ) m = Message() m.add_byte(c_MSG_KEXDH_GEX_GROUP) m.add_mpint(self.p) m.add_mpint(self.g) self.transport._send_message(m) self.transport._expect_packet(_MSG_KEXDH_GEX_INIT) self.old_style = True
def __init__(self, msg=None, data=None, filename=None, password=None, file_obj=None): self.public_blob = None verifying_key = signing_key = None if msg is None and data is not None: msg = Message(data) if msg is not None: self._check_type_and_load_cert( msg=msg, key_type="ssh-ed25519", cert_type="*****@*****.**", ) verifying_key = nacl.signing.VerifyKey(msg.get_binary()) elif filename is not None: with open(filename, "r") as f: data = self._read_private_key("OPENSSH", f) elif file_obj is not None: data = self._read_private_key("OPENSSH", file_obj) if filename or file_obj: signing_key = self._parse_signing_key_data(data, password) if signing_key is None and verifying_key is None: raise ValueError("need a key") self._signing_key = signing_key self._verifying_key = verifying_key
def from_string(cls, string): """ Create a public blob from a ``-cert.pub``-style string. """ fields = string.split(None, 2) if len(fields) < 2: msg = "Not enough fields for public blob: {}" raise ValueError(msg.format(fields)) key_type = fields[0] key_blob = decodebytes(b(fields[1])) try: comment = fields[2].strip() except IndexError: comment = None # Verify that the blob message first (string) field matches the # key_type m = Message(key_blob) blob_type = m.get_text() if blob_type != key_type: deets = "key type={!r}, but blob type={!r}".format( key_type, blob_type ) raise ValueError("Invalid PublicBlob contents: {}".format(deets)) # All good? All good. return cls(type_=key_type, blob=key_blob, comment=comment)
def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None): self.verifying_key = None self.signing_key = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.verifying_key, self.signing_key = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_text() != 'ecdsa-sha2-nistp256': raise SSHException('Invalid key') curvename = msg.get_text() if curvename != 'nistp256': raise SSHException("Can't handle curve of type %s" % curvename) pointinfo = msg.get_binary() if pointinfo[0:1] != four_byte: raise SSHException('Point compression is being used: %s' % binascii.hexlify(pointinfo)) self.verifying_key = VerifyingKey.from_string(pointinfo[1:], curve=curves.NIST256p) self.size = 256
def _read_response(self, waitfor=None): while True: try: t, data = self._read_packet() except EOFError as e: raise SSHException('Server connection dropped: %s' % str(e)) msg = Message(data) num = msg.get_int() if num not in self._expecting: # might be response for a file that was closed before responses came back self._log(DEBUG, 'Unexpected response #%d' % (num,)) if waitfor is None: # just doing a single check break continue fileobj = self._expecting[num] del self._expecting[num] if num == waitfor: # synchronous if t == CMD_STATUS: self._convert_status(msg) return t, msg if fileobj is not type(None): fileobj._async_response(t, msg, num) if waitfor is None: # just doing a single check break return None, None
def sign_token(key_path, fingerprint, data): # from agent pkey = get_key_from_agent(fingerprint) if not pkey: # or from file (without passphrase) # assuming '.pub' file extension if not os.path.exists(key_path[:-4]): raise SignatureException('WrongKeyPath') try: pkey = RSAKey.from_private_key_file(key_path[:-4]) except PasswordRequiredException: raise SignatureException('EncryptedKey') if not pkey: raise SignatureException('KeyNotFound') try: # paramiko is inconsistent here in that the agent's key # returns Message objects for 'sign_ssh_data' whereas RSAKey # objects returns byte strings. # Workaround: cast both return values to string and build a # new Message object s = str(pkey.sign_ssh_data(data)) m = Message(s) m.rewind() if not m.get_string() == 'ssh-rsa': raise SignatureException('RSAKeyRequired') return base64.b64encode(m.get_string()) except Exception: raise SignatureException('SignatureCreateFailure')
def _parse_kexdh_gex_request(self, m): minbits = m.get_int() preferredbits = m.get_int() maxbits = m.get_int() # smoosh the user's preferred size into our own limits if preferredbits > self.max_bits: preferredbits = self.max_bits if preferredbits < self.min_bits: preferredbits = self.min_bits # fix min/max if they're inconsistent. technically, we could just pout # and hang up, but there's no harm in giving them the benefit of the # doubt and just picking a bitsize for them. if minbits > preferredbits: minbits = preferredbits if maxbits < preferredbits: maxbits = preferredbits # now save a copy self.min_bits = minbits self.preferred_bits = preferredbits self.max_bits = maxbits # generate prime pack = self.transport._get_modulus_pack() if pack is None: raise SSHException('Can\'t do server-side gex with no modulus pack') self.transport._log(DEBUG, 'Picking p (%d <= %d <= %d bits)' % (minbits, preferredbits, maxbits)) self.g, self.p = pack.get_modulus(minbits, preferredbits, maxbits) m = Message() m.add_byte(chr(_MSG_KEXDH_GEX_GROUP)) m.add_mpint(self.p) m.add_mpint(self.g) self.transport._send_message(m) self.transport._expect_packet(_MSG_KEXDH_GEX_INIT)
def __init__( self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None, validate_point=True, ): self.verifying_key = None self.signing_key = None self.public_blob = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.signing_key, self.verifying_key = vals c_class = self.signing_key.curve.__class__ self.ecdsa_curve = self._ECDSA_CURVES.get_by_curve_class(c_class) else: # Must set ecdsa_curve first; subroutines called herein may need to # spit out our get_name(), which relies on this. key_type = msg.get_text() # But this also means we need to hand it a real key/curve # identifier, so strip out any cert business. (NOTE: could push # that into _ECDSACurveSet.get_by_key_format_identifier(), but it # feels more correct to do it here?) suffix = "*****@*****.**" if key_type.endswith(suffix): key_type = key_type[: -len(suffix)] self.ecdsa_curve = self._ECDSA_CURVES.get_by_key_format_identifier( key_type ) key_types = self._ECDSA_CURVES.get_key_format_identifier_list() cert_types = [ "{}[email protected]".format(x) for x in key_types ] self._check_type_and_load_cert( msg=msg, key_type=key_types, cert_type=cert_types ) curvename = msg.get_text() if curvename != self.ecdsa_curve.nist_name: raise SSHException( "Can't handle curve of type {}".format(curvename) ) pointinfo = msg.get_binary() try: numbers = ec.EllipticCurvePublicNumbers.from_encoded_point( self.ecdsa_curve.curve_class(), pointinfo ) except ValueError: raise SSHException("Invalid public key") self.verifying_key = numbers.public_key(backend=default_backend())
def _parse_userauth_gssapi_token(self, m): client_token = m.get_string() # use the client token as input to establish a secure # context. sshgss = self.sshgss try: token = sshgss.ssh_accept_sec_context( self.gss_host, client_token, self.auth_username ) except Exception as e: self.transport.saved_exception = e result = AUTH_FAILED self._restore_delegate_auth_handler() self._send_auth_result(self.auth_username, self.method, result) raise if token is not None: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) m.add_string(token) self.transport._expected_packet = ( MSG_USERAUTH_GSSAPI_TOKEN, MSG_USERAUTH_GSSAPI_MIC, MSG_USERAUTH_REQUEST, ) self.transport._send_message(m)
def sign_ssh_data(self, rpool, data): digest = SHA256.new(data).digest() sig = self.signing_key.sign_digest(digest, entropy=rpool.read, sigencode=self._sigencode) m = Message() m.add_string('ecdsa-sha2-nistp256') m.add_string(sig) return m
def sign_ssh_data(self, rpool, data): digest = SHA.new(data).digest() rsa = RSA.construct((long(self.n), long(self.e), long(self.d))) sig = util.deflate_long(rsa.sign(self._pkcs1imify(digest), bytes())[0], 0) m = Message() m.add_string('ssh-rsa') m.add_string(sig) return m
def sign_ssh_data(self, data): ecdsa = ec.ECDSA(self.ecdsa_curve.hash_object()) sig = self.signing_key.sign(data, ecdsa) r, s = decode_dss_signature(sig) m = Message() m.add_string(self.ecdsa_curve.key_format_identifier) m.add_string(self._sigencode(r, s)) return m
def sign_ssh_data(self, data): signer = self.signing_key.signer(ec.ECDSA(hashes.SHA256())) signer.update(data) sig = signer.finalize() r, s = decode_rfc6979_signature(sig) m = Message() m.add_string('ecdsa-sha2-nistp256') m.add_string(self._sigencode(r, s)) return 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 test_4_misc(self): msg = Message(self.__d) self.assertEquals(msg.get_int(), 5) self.assertEquals(msg.get_mpint(), 0x1122334455) self.assertEquals(msg.get_so_far(), self.__d[:13]) self.assertEquals(msg.get_remainder(), self.__d[13:]) msg.rewind() self.assertEquals(msg.get_int(), 5) self.assertEquals(msg.get_so_far(), self.__d[:4]) self.assertEquals(msg.get_remainder(), self.__d[4:])
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 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 _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_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 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 _parse_service_request(self, m): service = m.get_string() if self.transport.server_mode and (service == 'ssh-userauth'): # accepted m = Message() m.add_byte(chr(MSG_SERVICE_ACCEPT)) m.add_string(service) self.transport._send_message(m) return # dunno this one self._disconnect_service_not_available()
def _send_message(self, msg): msg = str(msg) self.conn.send(struct.pack('>I', len(msg)) + msg) l = self._read_all(4) msg = Message(self._read_all(struct.unpack('>I', l)[0])) return ord(msg.get_byte()), msg
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 invoke_subsystem(self, subsystem): """ Request a subsystem on the server (for example, ``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 str subsystem: name of the subsystem being requested. :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('subsystem') m.add_boolean(True) m.add_string(subsystem) self._event_pending() self.transport._send_user_message(m) self._wait_for_event()
@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, 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.
def __init__(self, agent, blob): self.agent = agent self.blob = blob self.public_blob = None self.name = Message(blob).get_text()
def resize_pty(self, width=80, height=24): """ Resize the pseudo-terminal. This can be used to change the width and height of the terminal emulation created in a previous L{get_pty} call. @param width: new width (in characters) of the terminal screen @type width: int @param height: new height (in characters) of the terminal screen @type height: int @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('window-change') m.add_boolean(True) m.add_int(width) m.add_int(height) m.add_int(0).add_int(0) self.event.clear() self.transport._send_user_message(m) self._wait_for_event()
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 _parse_userauth_request(self, m): if not self.transport.server_mode: # er, uh... what? m = Message() m.add_byte(cMSG_USERAUTH_FAILURE) m.add_string('none') m.add_boolean(False) self.transport._send_message(m) return if self.authenticated: # ignore return username = m.get_text() service = m.get_text() method = m.get_text() self.transport._log( DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username)) if service != 'ssh-connection': self._disconnect_service_not_available() return if (self.auth_username is not None) and (self.auth_username != username): self.transport._log( WARNING, 'Auth rejected because the client attempted to change username in mid-flight' ) self._disconnect_no_more_auth() return self.auth_username = username # check if GSS-API authentication is enabled gss_auth = self.transport.server_object.enable_auth_gssapi() if method == 'none': result = self.transport.server_object.check_auth_none(username) elif method == 'password': changereq = m.get_boolean() password = m.get_binary() try: password = password.decode('UTF-8') except UnicodeError: # some clients/servers expect non-utf-8 passwords! # in this case, just return the raw byte string. pass if changereq: # always treated as failure, since we don't support changing passwords, but collect # the list of valid auth types from the callback anyway self.transport._log( DEBUG, 'Auth request to change passwords (rejected)') newpassword = m.get_binary() try: newpassword = newpassword.decode('UTF-8', 'replace') except UnicodeError: pass result = AUTH_FAILED else: result = self.transport.server_object.check_auth_password( username, password) elif method == 'publickey': sig_attached = m.get_boolean() keytype = m.get_text() keyblob = m.get_binary() try: key = self.transport._key_info[keytype](Message(keyblob)) except SSHException as e: self.transport._log(INFO, 'Auth rejected: public key: %s' % str(e)) key = None except: self.transport._log( INFO, 'Auth rejected: unsupported or mangled public key') key = None if key is None: self._disconnect_no_more_auth() return # first check if this key is okay... if not, we can skip the verify result = self.transport.server_object.check_auth_publickey( username, key) if result != AUTH_FAILED: # key is okay, verify it if not sig_attached: # client wants to know if this key is acceptable, before it # signs anything... send special "ok" message m = Message() m.add_byte(cMSG_USERAUTH_PK_OK) m.add_string(keytype) m.add_string(keyblob) self.transport._send_message(m) return sig = Message(m.get_binary()) blob = self._get_session_blob(key, service, username) if not key.verify_ssh_sig(blob, sig): self.transport._log(INFO, 'Auth rejected: invalid signature') result = AUTH_FAILED elif method == 'keyboard-interactive': lang = m.get_string() submethods = m.get_string() result = self.transport.server_object.check_auth_interactive( username, submethods) if isinstance(result, InteractiveQuery): # make interactive query instead of response self._interactive_query(result) return elif method == "gssapi-with-mic" and gss_auth: sshgss = GSSAuth(method) # Read the number of OID mechanisms supported by the client. # OpenSSH sends just one OID. It's the Kerveros V5 OID and that's # the only OID we support. mechs = m.get_int() # We can't accept more than one OID, so if the SSH client sends # more than one, disconnect. if mechs > 1: self.transport._log( INFO, 'Disconnect: Received more than one GSS-API OID mechanism') self._disconnect_no_more_auth() desired_mech = m.get_string() mech_ok = sshgss.ssh_check_mech(desired_mech) # if we don't support the mechanism, disconnect. if not mech_ok: self.transport._log( INFO, 'Disconnect: Received an invalid GSS-API OID mechanism') self._disconnect_no_more_auth() # send the Kerberos V5 GSSAPI OID to the client supported_mech = sshgss.ssh_gss_oids("server") # RFC 4462 says we are not required to implement GSS-API error # messages. See section 3.8 in http://www.ietf.org/rfc/rfc4462.txt while True: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_RESPONSE) m.add_bytes(supported_mech) self.transport._send_message(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_TOKEN: client_token = m.get_string() # use the client token as input to establish a secure # context. try: token = sshgss.ssh_accept_sec_context( self.gss_host, client_token, username) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise if token is not None: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) m.add_string(token) self.transport._send_message(m) else: raise SSHException("Client asked to handle paket %s" % MSG_NAMES[ptype]) # check MIC ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_MIC: break mic_token = m.get_string() try: sshgss.ssh_check_mic(mic_token, self.transport.session_id, username) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise if retval == 0: # TODO: Implement client credential saving. # The OpenSSH server is able to create a TGT with the delegated # client credentials, but this is not supported by GSS-API. result = AUTH_SUCCESSFUL self.transport.server_object.check_auth_gssapi_with_mic( username, result) else: result = AUTH_FAILED elif method == "gssapi-keyex" and gss_auth: mic_token = m.get_string() sshgss = self.transport.kexgss_ctxt if sshgss is None: # If there is no valid context, we reject the authentication result = AUTH_FAILED self._send_auth_result(username, method, result) try: sshgss.ssh_check_mic(mic_token, self.transport.session_id, self.auth_username) except Exception: result = AUTH_FAILED self._send_auth_result(username, method, result) raise if retval == 0: result = AUTH_SUCCESSFUL self.transport.server_object.check_auth_gssapi_keyex( username, result) else: result = AUTH_FAILED else: result = self.transport.server_object.check_auth_none(username) # okay, send result self._send_auth_result(username, method, result)
def _interactive_query(self, q): # make interactive query instead of response m = Message() m.add_byte(cMSG_USERAUTH_INFO_REQUEST) m.add_string(q.name) m.add_string(q.instructions) m.add_string(bytes()) m.add_int(len(q.prompts)) for p in q.prompts: m.add_string(p[0]) m.add_boolean(p[1]) self.transport._send_message(m)
def _parse_kexecdh_init(self, m): Q_C_bytes = m.get_string() self.Q_C = ec.EllipticCurvePublicNumbers.from_encoded_point( self.curve, Q_C_bytes) K_S = self.transport.get_server_key().asbytes() K = self.P.exchange(ec.ECDH(), self.Q_C.public_key(default_backend())) K = long(hexlify(K), 16) # compute exchange hash hm = Message() hm.add(self.transport.remote_version, self.transport.local_version, self.transport.remote_kex_init, self.transport.local_kex_init) hm.add_string(K_S) hm.add_string(Q_C_bytes) # SEC1: V2.0 2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion hm.add_string(self.Q_S.public_numbers().encode_point()) hm.add_mpint(long(K)) H = self.hash_algo(hm.asbytes()).digest() self.transport._set_K_H(K, H) sig = self.transport.get_server_key().sign_ssh_data(H) # construct reply m = Message() m.add_byte(c_MSG_KEXECDH_REPLY) m.add_string(K_S) m.add_string(self.Q_S.public_numbers().encode_point()) m.add_string(sig) self.transport._send_message(m) self.transport._activate_outbound()
def read_message(self): """ Only one thread should ever be in this function (no other locking is done). :raises SSHException: if the packet is mangled :raises NeedRekeyException: if the transport should rekey """ header = self.read_all(self.__block_size_in, check_rekey=True) if self.__block_engine_in is not None: header = self.__block_engine_in.decrypt(header) if self.__dump_packets: self._log(DEBUG, util.format_binary(header, 'IN: ')) packet_size = struct.unpack('>I', header[:4])[0] # leftover contains decrypted bytes from the first block (after the length field) leftover = header[4:] if (packet_size - len(leftover)) % self.__block_size_in != 0: raise SSHException('Invalid packet blocking') buf = self.read_all(packet_size + self.__mac_size_in - len(leftover)) packet = buf[:packet_size - len(leftover)] post_packet = buf[packet_size - len(leftover):] if self.__block_engine_in is not None: packet = self.__block_engine_in.decrypt(packet) if self.__dump_packets: self._log(DEBUG, util.format_binary(packet, 'IN: ')) packet = leftover + packet if self.__mac_size_in > 0: mac = post_packet[:self.__mac_size_in] mac_payload = struct.pack('>II', self.__sequence_number_in, packet_size) + packet my_mac = compute_hmac(self.__mac_key_in, mac_payload, self.__mac_engine_in)[:self.__mac_size_in] if not util.constant_time_bytes_eq(my_mac, mac): raise SSHException('Mismatched MAC') padding = byte_ord(packet[0]) payload = packet[1:packet_size - padding] if self.__dump_packets: self._log(DEBUG, 'Got payload (%d bytes, %d padding)' % (packet_size, padding)) if self.__compress_engine_in is not None: payload = self.__compress_engine_in(payload) msg = Message(payload[1:]) msg.seqno = self.__sequence_number_in self.__sequence_number_in = (self.__sequence_number_in + 1) & xffffffff # check for rekey raw_packet_size = packet_size + self.__mac_size_in + 4 self.__received_bytes += raw_packet_size self.__received_packets += 1 if self.__need_rekey: # we've asked to rekey -- give them some packets to comply before # dropping the connection self.__received_bytes_overflow += raw_packet_size self.__received_packets_overflow += 1 if (self.__received_packets_overflow >= self.REKEY_PACKETS_OVERFLOW_MAX) or \ (self.__received_bytes_overflow >= self.REKEY_BYTES_OVERFLOW_MAX): raise SSHException('Remote transport is ignoring rekey requests') elif (self.__received_packets >= self.REKEY_PACKETS) or \ (self.__received_bytes >= self.REKEY_BYTES): # only ask once for rekeying self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes received)' % (self.__received_packets, self.__received_bytes)) self.__received_bytes_overflow = 0 self.__received_packets_overflow = 0 self._trigger_rekey() cmd = byte_ord(payload[0]) if cmd in MSG_NAMES: cmd_name = MSG_NAMES[cmd] else: cmd_name = '$%x' % cmd if self.__dump_packets: self._log(DEBUG, 'Read packet <%s>, length %d' % (cmd_name, len(payload))) return cmd, msg
def _parse_kexecdh_reply(self, m): K_S = m.get_string() Q_S_bytes = m.get_string() self.Q_S = ec.EllipticCurvePublicNumbers.from_encoded_point( self.curve, Q_S_bytes) sig = m.get_binary() K = self.P.exchange(ec.ECDH(), self.Q_S.public_key(default_backend())) K = long(hexlify(K), 16) # compute exchange hash and verify signature hm = Message() hm.add(self.transport.local_version, self.transport.remote_version, self.transport.local_kex_init, self.transport.remote_kex_init) hm.add_string(K_S) # SEC1: V2.0 2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion hm.add_string(self.Q_C.public_numbers().encode_point()) hm.add_string(Q_S_bytes) hm.add_mpint(K) self.transport._set_K_H(K, self.hash_algo(hm.asbytes()).digest()) self.transport._verify_key(K_S, sig) self.transport._activate_outbound()
def _sigencode(self, r, s): msg = Message() msg.add_mpint(r) msg.add_mpint(s) return msg.asbytes()
def _sigdecode(self, sig): msg = Message(sig) r = msg.get_mpint() s = msg.get_mpint() return r, s
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.randpool.get_bytes(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.clear() self.transport._send_user_message(m) self._wait_for_event() self.transport._set_x11_handler(handler) return auth_cookie
def _send_auth_result(self, username, method, result): # okay, send result m = Message() if result == AUTH_SUCCESSFUL: self.transport._log(INFO, 'Auth granted (%s).' % method) m.add_byte(cMSG_USERAUTH_SUCCESS) self.authenticated = True else: self.transport._log(INFO, 'Auth rejected (%s).' % method) m.add_byte(cMSG_USERAUTH_FAILURE) m.add_string( self.transport.server_object.get_allowed_auths(username)) if result == AUTH_PARTIALLY_SUCCESSFUL: m.add_boolean(True) else: m.add_boolean(False) self.auth_fail_count += 1 self.transport._send_message(m) if self.auth_fail_count >= 10: self._disconnect_no_more_auth() if result == AUTH_SUCCESSFUL: self.transport._auth_trigger()
def send_exit_status(self, status): """ Send the exit status of an executed command to the client. (This really only makes sense in server mode.) Many clients expect to get some sort of status code back from an executed command after it completes. @param status: the exit code of the process @type status: int @since: 1.2 """ # in many cases, the channel will not still be open here. # that's fine. m = Message() m.add_byte(chr(MSG_CHANNEL_REQUEST)) m.add_int(self.remote_chanid) m.add_string('exit-status') m.add_boolean(False) m.add_int(status) self.transport._send_user_message(m)
def _parse_service_accept(self, m): service = m.get_text() if service == 'ssh-userauth': self.transport._log(DEBUG, 'userauth is OK') m = Message() m.add_byte(cMSG_USERAUTH_REQUEST) m.add_string(self.username) m.add_string('ssh-connection') m.add_string(self.auth_method) if self.auth_method == 'password': m.add_boolean(False) password = bytestring(self.password) m.add_string(password) elif self.auth_method == 'publickey': m.add_boolean(True) m.add_string(self.private_key.get_name()) m.add_string(self.private_key) blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username) sig = self.private_key.sign_ssh_data(blob) m.add_string(sig) elif self.auth_method == 'keyboard-interactive': m.add_string('') m.add_string(self.submethods) elif self.auth_method == "gssapi-with-mic": sshgss = GSSAuth(self.auth_method, self.gss_deleg_creds) m.add_bytes(sshgss.ssh_gss_oids()) # send the supported GSSAPI OIDs to the server self.transport._send_message(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_BANNER: self._parse_userauth_banner(m) ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_RESPONSE: # Read the mechanism selected by the server. We send just # the Kerberos V5 OID, so the server can only respond with # this OID. mech = m.get_string() m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) m.add_string( sshgss.ssh_init_sec_context( self.gss_host, mech, self.username, )) self.transport._send_message(m) while True: ptype, m = self.transport.packetizer.read_message() if ptype == MSG_USERAUTH_GSSAPI_TOKEN: srv_token = m.get_string() next_token = sshgss.ssh_init_sec_context( self.gss_host, mech, self.username, srv_token) # After this step the GSSAPI should not return any # token. If it does, we keep sending the token to # the server until no more token is returned. if next_token is None: break else: m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN) m.add_string(next_token) self.transport.send_message(m) else: raise SSHException("Received Package: %s" % MSG_NAMES[ptype]) m = Message() m.add_byte(cMSG_USERAUTH_GSSAPI_MIC) # send the MIC to the server m.add_string(sshgss.ssh_get_mic(self.transport.session_id)) elif ptype == MSG_USERAUTH_GSSAPI_ERRTOK: # RFC 4462 says we are not required to implement GSS-API # error messages. # See RFC 4462 Section 3.8 in # http://www.ietf.org/rfc/rfc4462.txt raise SSHException("Server returned an error token") elif ptype == MSG_USERAUTH_GSSAPI_ERROR: maj_status = m.get_int() min_status = m.get_int() err_msg = m.get_string() lang_tag = m.get_string() # we don't care! raise SSHException("GSS-API Error:\nMajor Status: %s\n\ Minor Status: %s\ \nError Message:\ %s\n") % (str(maj_status), str(min_status), err_msg) elif ptype == MSG_USERAUTH_FAILURE: self._parse_userauth_failure(m) return else: raise SSHException("Received Package: %s" % MSG_NAMES[ptype]) elif self.auth_method == 'gssapi-keyex' and\ self.transport.gss_kex_used: kexgss = self.transport.kexgss_ctxt kexgss.set_username(self.username) mic_token = kexgss.ssh_get_mic(self.transport.session_id) m.add_string(mic_token) elif self.auth_method == 'none': pass else: raise SSHException('Unknown auth method "%s"' % self.auth_method) self.transport._send_message(m) else: self.transport._log(DEBUG, 'Service request "%s" accepted (?)' % service)
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 _get_session_blob(self, key, service, username): m = Message() m.add_string(self.transport.session_id) m.add_byte(cMSG_USERAUTH_REQUEST) m.add_string(username) m.add_string(service) m.add_string('publickey') m.add_boolean(True) m.add_string(key.get_name()) m.add_string(key) return m.asbytes()
def get_pty(self, term='vt100', width=80, height=24): """ 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 L{invoke_shell}. It isn't necessary (or desirable) to call this method if you're going to exectue a single command with L{exec_command}. @param term: the terminal type to emulate (for example, C{'vt100'}) @type term: str @param width: width (in characters) of the terminal screen @type width: int @param height: height (in characters) of the terminal screen @type height: int @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('pty-req') m.add_boolean(True) m.add_string(term) m.add_int(width) m.add_int(height) # pixel height, width (usually useless) m.add_int(0).add_int(0) m.add_string('') self.event.clear() self.transport._send_user_message(m) self._wait_for_event()
def _request_auth(self): m = Message() m.add_byte(cMSG_SERVICE_REQUEST) m.add_string('ssh-userauth') self.transport._send_message(m)
def add_key(self, key): header = asbytes(cSSH_AGENTC_ADD_IDENTITY) body = Message() body.add_string("ssh-rsa") body.add_mpint(key.public_numbers.n) body.add_mpint(key.public_numbers.e) body.add_mpint(key.d) body.add_mpint(key.iqmp) body.add_mpint(key.p) body.add_mpint(key.q) body.add_string("") packet_len = len(header) + body.packet.tell() self._conn.send( struct.pack(">I", packet_len) + header + body.packet.getvalue()) data = self._read_all(4) msg = Message(self._read_all(struct.unpack(">I", data)[0])) if ord(msg.get_byte()) != SSH_AGENT_SUCCESS: raise SSHException("could not add key to agent") return
def listdir_iter(self, path='.', read_aheads=50): """ Generator version of `.listdir_attr`. See the API docs for `.listdir_attr` for overall details. This function adds one more kwarg on top of `.listdir_attr`: ``read_aheads``, an integer controlling how many ``SSH_FXP_READDIR`` requests are made to the server. The default of 50 should suffice for most file listings as each request/response cycle may contain multiple files (dependant on server implementation.) .. versionadded:: 1.15 """ path = self._adjust_cwd(path) self._log(DEBUG, 'listdir(%r)' % path) t, msg = self._request(CMD_OPENDIR, path) if t != CMD_HANDLE: raise SFTPError('Expected handle') handle = msg.get_string() nums = list() while True: try: # Send out a bunch of readdir requests so that we can read the # responses later on Section 6.7 of the SSH file transfer RFC # explains this # http://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt for i in range(read_aheads): num = self._async_request(type(None), CMD_READDIR, handle) nums.append(num) # For each of our sent requests # Read and parse the corresponding packets # If we're at the end of our queued requests, then fire off # some more requests # Exit the loop when we've reached the end of the directory # handle for num in nums: t, pkt_data = self._read_packet() msg = Message(pkt_data) new_num = msg.get_int() if num == new_num: if t == CMD_STATUS: self._convert_status(msg) count = msg.get_int() for i in range(count): filename = msg.get_text() longname = msg.get_text() attr = SFTPAttributes._from_msg( msg, filename, longname) if (filename != '.') and (filename != '..'): yield attr # If we've hit the end of our queued requests, reset nums. nums = list() except EOFError: self._request(CMD_CLOSE, handle) return
def _send_message(self, msg): msg = asbytes(msg) self._conn.send(struct.pack(">I", len(msg)) + msg) data = self._read_all(4) msg = Message(self._read_all(struct.unpack(">I", data)[0])) return ord(msg.get_byte()), msg
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) elif key == 'x11-req': single_connection = m.get_boolean() auth_proto = m.get_string() auth_cookie = m.get_string() 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) 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)
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 """ m = Message() m.add_byte(cMSG_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)
class Channel(object): """ A secure tunnel across an SSH L{Transport}. A Channel is meant to behave like a socket, and has an API that should be indistinguishable from the python socket API. Because SSH2 has a windowing kind of flow control, if you stop reading data from a Channel and its buffer fills up, the server will be unable to send you any more data until you read some of it. (This won't affect other channels on the same transport -- all channels on a single transport are flow-controlled independently.) Similarly, if the server isn't reading data you send, calls to L{send} may block, unless you set a timeout. This is exactly like a normal network socket, so it shouldn't be too surprising. """ def __init__(self, chanid): """ Create a new channel. The channel is not associated with any particular session or L{Transport} until the Transport attaches it. Normally you would only call this method from the constructor of a subclass of L{Channel}. @param chanid: the ID of this channel, as passed by an existing L{Transport}. @type chanid: int """ self.chanid = chanid self.remote_chanid = 0 self.transport = None self.active = False self.eof_received = 0 self.eof_sent = 0 self.in_buffer = BufferedPipe() self.in_stderr_buffer = BufferedPipe() self.timeout = None self.closed = False self.ultra_debug = False self.lock = threading.Lock() self.out_buffer_cv = threading.Condition(self.lock) self.in_window_size = 0 self.out_window_size = 0 self.in_max_packet_size = 0 self.out_max_packet_size = 0 self.in_window_threshold = 0 self.in_window_sofar = 0 self.status_event = threading.Event() self._name = str(chanid) self.logger = util.get_logger('paramiko.transport') self._pipe = None self.event = threading.Event() self.combine_stderr = False self.exit_status = -1 self.origin_addr = None def __del__(self): try: self.close() except: pass def __repr__(self): """ Return a string representation of this object, for debugging. @rtype: str """ out = '<paramiko.Channel %d' % self.chanid if self.closed: out += ' (closed)' elif self.active: if self.eof_received: out += ' (EOF received)' if self.eof_sent: out += ' (EOF sent)' out += ' (open) window=%d' % (self.out_window_size) if len(self.in_buffer) > 0: out += ' in-buffer=%d' % (len(self.in_buffer), ) out += ' -> ' + repr(self.transport) out += '>' return out def get_pty(self, term='vt100', width=80, height=24): """ 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 L{invoke_shell}. It isn't necessary (or desirable) to call this method if you're going to exectue a single command with L{exec_command}. @param term: the terminal type to emulate (for example, C{'vt100'}) @type term: str @param width: width (in characters) of the terminal screen @type width: int @param height: height (in characters) of the terminal screen @type height: int @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('pty-req') m.add_boolean(True) m.add_string(term) m.add_int(width) m.add_int(height) # pixel height, width (usually useless) m.add_int(0).add_int(0) m.add_string('') self.event.clear() self.transport._send_user_message(m) self._wait_for_event() 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 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): """ Resize the pseudo-terminal. This can be used to change the width and height of the terminal emulation created in a previous L{get_pty} call. @param width: new width (in characters) of the terminal screen @type width: int @param height: new height (in characters) of the terminal screen @type height: int @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('window-change') m.add_boolean(True) m.add_int(width) m.add_int(height) m.add_int(0).add_int(0) self.event.clear() self.transport._send_user_message(m) self._wait_for_event() def exit_status_ready(self): """ Return true if the remote process has exited and returned an exit status. You may use this to poll the process status if you don't want to block in L{recv_exit_status}. Note that the server may not return an exit status in some cases (like bad servers). @return: True if L{recv_exit_status} will return immediately @rtype: bool @since: 1.7.3 """ return self.closed or self.status_event.isSet() def recv_exit_status(self): """ Return the exit status from the process on the server. This is mostly useful for retrieving the reults of an L{exec_command}. If the command hasn't finished yet, this method will wait until it does, or until the channel is closed. If no exit status is provided by the server, -1 is returned. @return: the exit code of the process on the server. @rtype: int @since: 1.2 """ while True: if self.closed or self.status_event.isSet(): break self.status_event.wait(0.1) return self.exit_status def send_exit_status(self, status): """ Send the exit status of an executed command to the client. (This really only makes sense in server mode.) Many clients expect to get some sort of status code back from an executed command after it completes. @param status: the exit code of the process @type status: int @since: 1.2 """ # in many cases, the channel will not still be open here. # that's fine. m = Message() m.add_byte(chr(MSG_CHANNEL_REQUEST)) m.add_int(self.remote_chanid) m.add_string('exit-status') m.add_boolean(False) m.add_int(status) self.transport._send_user_message(m) 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.randpool.get_bytes(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.clear() self.transport._send_user_message(m) self._wait_for_event() self.transport._set_x11_handler(handler) return auth_cookie def get_transport(self): """ Return the L{Transport} associated with this channel. @return: the L{Transport} that was used to create this channel. @rtype: L{Transport} """ return self.transport def set_name(self, name): """ Set a name for this channel. Currently it's only used to set the name of the channel in logfile entries. The name can be fetched with the L{get_name} method. @param name: new channel name @type name: str """ self._name = name def get_name(self): """ Get the name of this channel that was previously set by L{set_name}. @return: the name of this channel. @rtype: str """ return self._name def get_id(self): """ Return the ID # for this channel. The channel ID is unique across a L{Transport} and usually a small number. It's also the number passed to L{ServerInterface.check_channel_request} when determining whether to accept a channel request in server mode. @return: the ID of this channel. @rtype: int """ return self.chanid def set_combine_stderr(self, combine): """ Set whether stderr should be combined into stdout on this channel. The default is C{False}, but in some cases it may be convenient to have both streams combined. If this is C{False}, and L{exec_command} is called (or C{invoke_shell} with no pty), output to stderr will not show up through the L{recv} and L{recv_ready} calls. You will have to use L{recv_stderr} and L{recv_stderr_ready} to get stderr output. If this is C{True}, data will never show up via L{recv_stderr} or L{recv_stderr_ready}. @param combine: C{True} if stderr output should be combined into stdout on this channel. @type combine: bool @return: previous setting. @rtype: bool @since: 1.1 """ data = '' self.lock.acquire() try: old = self.combine_stderr self.combine_stderr = combine if combine and not old: # copy old stderr buffer into primary buffer data = self.in_stderr_buffer.empty() finally: self.lock.release() if len(data) > 0: self._feed(data) return old ### socket API def settimeout(self, timeout): """ Set a timeout on blocking read/write operations. The C{timeout} argument can be a nonnegative float expressing seconds, or C{None}. If a float is given, subsequent channel read/write operations will raise a timeout exception if the timeout period value has elapsed before the operation has completed. Setting a timeout of C{None} disables timeouts on socket operations. C{chan.settimeout(0.0)} is equivalent to C{chan.setblocking(0)}; C{chan.settimeout(None)} is equivalent to C{chan.setblocking(1)}. @param timeout: seconds to wait for a pending read/write operation before raising C{socket.timeout}, or C{None} for no timeout. @type timeout: float """ self.timeout = timeout def gettimeout(self): """ Returns the timeout in seconds (as a float) associated with socket operations, or C{None} if no timeout is set. This reflects the last call to L{setblocking} or L{settimeout}. @return: timeout in seconds, or C{None}. @rtype: float """ return self.timeout def setblocking(self, blocking): """ Set blocking or non-blocking mode of the channel: if C{blocking} is 0, the channel is set to non-blocking mode; otherwise it's set to blocking mode. Initially all channels are in blocking mode. In non-blocking mode, if a L{recv} call doesn't find any data, or if a L{send} call can't immediately dispose of the data, an error exception is raised. In blocking mode, the calls block until they can proceed. C{chan.setblocking(0)} is equivalent to C{chan.settimeout(0)}; C{chan.setblocking(1)} is equivalent to C{chan.settimeout(None)}. @param blocking: 0 to set non-blocking mode; non-0 to set blocking mode. @type blocking: int """ if blocking: self.settimeout(None) else: self.settimeout(0.0) def getpeername(self): """ Return the address of the remote side of this Channel, if possible. This is just a wrapper around C{'getpeername'} on the Transport, used to provide enough of a socket-like interface to allow asyncore to work. (asyncore likes to call C{'getpeername'}.) @return: the address if the remote host, if known @rtype: tuple(str, int) """ return self.transport.getpeername() def close(self): """ Close the channel. All future read/write operations on the channel will fail. The remote end will receive no more data (after queued data is flushed). Channels are automatically closed when their L{Transport} is closed or when they are garbage collected. """ self.lock.acquire() try: # only close the pipe when the user explicitly closes the channel. # otherwise they will get unpleasant surprises. (and do it before # checking self.closed, since the remote host may have already # closed the connection.) if self._pipe is not None: self._pipe.close() self._pipe = None if not self.active or self.closed: return msgs = self._close_internal() finally: self.lock.release() for m in msgs: if m is not None: self.transport._send_user_message(m) def recv_ready(self): """ Returns true if data is buffered and ready to be read from this channel. A C{False} result does not mean that the channel has closed; it means you may need to wait before more data arrives. @return: C{True} if a L{recv} call on this channel would immediately return at least one byte; C{False} otherwise. @rtype: boolean """ return self.in_buffer.read_ready() 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}. """ try: out = self.in_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 __init__(self, agent, blob): self.agent = agent self.blob = blob self.name = Message(blob).get_string()
def exec_command(self, command, timeout=90): """ 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. :param int timeout: set thread timeout when waiting for event :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(timeout=timeout)