diff options
author | Jeff Forcier <jeff@bitprophet.org> | 2017-06-01 13:57:03 -0700 |
---|---|---|
committer | Jeff Forcier <jeff@bitprophet.org> | 2017-06-01 13:57:03 -0700 |
commit | 29a2fc8309a901af14d44e8fc2d7148b96f161a6 (patch) | |
tree | 95853cf348d40bd6cc020e62aa0b6546c21fe322 | |
parent | 67042db7ef373ca00d45152c7a8c8a6c049d0fed (diff) | |
parent | 327d17c79bcc531d5f7564592994807d1fb91cca (diff) |
Merge branch '2.1'
-rw-r--r-- | demos/demo_server.py | 4 | ||||
-rw-r--r-- | paramiko/auth_handler.py | 5 | ||||
-rw-r--r-- | paramiko/hostkeys.py | 35 | ||||
-rw-r--r-- | paramiko/server.py | 6 | ||||
-rw-r--r-- | paramiko/sftp_client.py | 2 | ||||
-rw-r--r-- | paramiko/sftp_server.py | 2 | ||||
-rw-r--r-- | paramiko/ssh_exception.py | 7 | ||||
-rw-r--r-- | paramiko/transport.py | 369 | ||||
-rw-r--r-- | tests/test_hostkeys.py | 12 | ||||
-rw-r--r-- | tests/test_ssh_gss.py | 4 |
10 files changed, 304 insertions, 142 deletions
diff --git a/demos/demo_server.py b/demos/demo_server.py index 4867e9ca..3a7ec854 100644 --- a/demos/demo_server.py +++ b/demos/demo_server.py @@ -96,9 +96,7 @@ class Server (paramiko.ServerInterface): return paramiko.AUTH_FAILED def enable_auth_gssapi(self): - UseGSSAPI = True - GSSAPICleanupCredentials = False - return UseGSSAPI + return True def get_allowed_auths(self, username): return 'gssapi-keyex,gssapi-with-mic,password,publickey' diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py index 2de71241..33f01da6 100644 --- a/paramiko/auth_handler.py +++ b/paramiko/auth_handler.py @@ -302,6 +302,7 @@ class AuthHandler (object): maj_status = m.get_int() min_status = m.get_int() err_msg = m.get_string() + m.get_string() # Lang tag - discarded raise SSHException("GSS-API Error:\nMajor Status: %s\n\ Minor Status: %s\ \nError Message:\ %s\n") % (str(maj_status), @@ -394,8 +395,8 @@ class AuthHandler (object): (self.auth_username != username)): self.transport._log( WARNING, - 'Auth rejected because the client attempted to change ' - 'username in mid-flight') + 'Auth rejected because the client attempted to change username in mid-flight' # noqa + ) self._disconnect_no_more_auth() return self.auth_username = username diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index f5c622ed..18a0d333 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -186,18 +186,28 @@ class HostKeys (MutableMapping): entries = [] for e in self._entries: - for h in e.hostnames: - if ( - h == hostname or - h.startswith('|1|') and - not hostname.startswith('|1|') and - constant_time_bytes_eq(self.hash_host(hostname, h), h) - ): - entries.append(e) + if self._hostname_matches(hostname, e): + entries.append(e) if len(entries) == 0: return None return SubDict(hostname, entries, self) + def _hostname_matches(self, hostname, entry): + """ + Tests whether ``hostname`` string matches given SubDict ``entry``. + + :returns bool: + """ + for h in entry.hostnames: + if ( + h == hostname or + h.startswith('|1|') and + not hostname.startswith('|1|') and + constant_time_bytes_eq(self.hash_host(hostname, h), h) + ): + return True + return False + def check(self, hostname, key): """ Return True if the given key is associated with the given hostname @@ -236,7 +246,14 @@ class HostKeys (MutableMapping): return ret def __delitem__(self, key): - pass # Needed for instantiating HostKeys. + index = None + for i, entry in enumerate(self._entries): + if self._hostname_matches(key, entry): + index = i + break + if index is None: + raise KeyError(key) + self._entries.pop(index) def __setitem__(self, hostname, entry): # don't use this please. diff --git a/paramiko/server.py b/paramiko/server.py index 953bb33f..b2a07916 100644 --- a/paramiko/server.py +++ b/paramiko/server.py @@ -660,8 +660,10 @@ class SubsystemHandler (threading.Thread): except Exception as e: self.__transport._log( ERROR, - 'Exception in subsystem handler for "{}": {}'.format( - self.__name, e)) + 'Exception in subsystem handler for "{0}": {1}'.format( + self.__name, e + ) + ) self.__transport._log(ERROR, util.tb_strings()) try: self.finish_subsystem() diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index f3895caf..ea6f88a9 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -717,8 +717,6 @@ class SFTPClient(BaseSFTP, ClosingContextManager): reader=fr, writer=fl, file_size=file_size, callback=callback ) - return file_size - def get(self, remotepath, localpath, callback=None): """ Copy a remote file (``remotepath``) from the SFTP server to the local diff --git a/paramiko/sftp_server.py b/paramiko/sftp_server.py index d7b46e41..f94c5e39 100644 --- a/paramiko/sftp_server.py +++ b/paramiko/sftp_server.py @@ -200,7 +200,7 @@ class SFTPServer (BaseSFTP, SubsystemHandler): item._pack(msg) else: raise Exception( - 'unknown type for {!r} type {!r}'.format( + 'unknown type for {0!r} type {1!r}'.format( item, type(item))) self._send_packet(t, msg) diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index e3584d89..746fd213 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -65,8 +65,9 @@ class BadAuthenticationType (AuthenticationException): self.args = (explanation, types, ) def __str__(self): - return '{} (allowed_types={!r})'.format( - SSHException.__str__(self), self.allowed_types) + return '{0} (allowed_types={1!r})'.format( + SSHException.__str__(self), self.allowed_types + ) class PartialAuthentication (AuthenticationException): @@ -108,7 +109,7 @@ class BadHostKeyException (SSHException): .. versionadded:: 1.6 """ def __init__(self, hostname, got_key, expected_key): - message = 'Host key for server {} does not match: got {} expected {}' + message = 'Host key for server {0} does not match: got {0}, expected {0}' # noqa message = message.format( hostname, got_key.get_base64(), expected_key.get_base64()) diff --git a/paramiko/transport.py b/paramiko/transport.py index ca9a62b4..08c8340d 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -17,7 +17,6 @@ # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. -# flake8: noqa """ Core protocol implementation """ @@ -58,7 +57,7 @@ from paramiko.dsskey import DSSKey from paramiko.kex_gex import KexGex, KexGexSHA256 from paramiko.kex_group1 import KexGroup1 from paramiko.kex_group14 import KexGroup14 -from paramiko.kex_gss import KexGSSGex, KexGSSGroup1, KexGSSGroup14, NullHostKey +from paramiko.kex_gss import KexGSSGex, KexGSSGroup1, KexGSSGroup14 from paramiko.message import Message from paramiko.packet import Packetizer, NeedRekeyException from paramiko.primes import ModulusPack @@ -67,8 +66,9 @@ from paramiko.rsakey import RSAKey from paramiko.ecdsakey import ECDSAKey from paramiko.server import ServerInterface from paramiko.sftp_client import SFTPClient -from paramiko.ssh_exception import (SSHException, BadAuthenticationType, - ChannelException, ProxyCommandFailure) +from paramiko.ssh_exception import ( + SSHException, BadAuthenticationType, ChannelException, ProxyCommandFailure, +) from paramiko.util import retry_on_signal, ClosingContextManager, clamp_value @@ -126,7 +126,7 @@ class Transport (threading.Thread, ClosingContextManager): 'ssh-rsa', 'ssh-dss', ) + tuple(ECDSAKey.supported_key_format_identifiers()) - _preferred_kex = ( + _preferred_kex = ( 'diffie-hellman-group1-sha1', 'diffie-hellman-group14-sha1', 'diffie-hellman-group-exchange-sha1', @@ -243,8 +243,8 @@ class Transport (threading.Thread, ClosingContextManager): gss_deleg_creds=True): """ Create a new SSH session over an existing socket, or socket-like - object. This only creates the `.Transport` object; it doesn't begin the - SSH session yet. Use `connect` or `start_client` to begin a client + object. This only creates the `.Transport` object; it doesn't begin + the SSH session yet. Use `connect` or `start_client` to begin a client session, or `start_server` to begin a server session. If the object is not actually a socket, it must have the following @@ -299,10 +299,13 @@ class Transport (threading.Thread, ClosingContextManager): # connect to the given (host, port) hostname, port = sock reason = 'No suitable address family' - for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM): + addrinfos = socket.getaddrinfo( + hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM + ) + for family, socktype, proto, canonname, sockaddr in addrinfos: if socktype == socket.SOCK_STREAM: af = family - addr = sockaddr + # addr = sockaddr sock = socket.socket(af, socket.SOCK_STREAM) try: retry_on_signal(lambda: sock.connect((hostname, port))) @@ -362,7 +365,8 @@ class Transport (threading.Thread, ClosingContextManager): self.in_kex = False self.authenticated = False self._expected_packet = tuple() - self.lock = threading.Lock() # synchronization (always higher level than write_lock) + # synchronization (always higher level than write_lock) + self.lock = threading.Lock() # tracking open channels self._channels = ChannelMap() @@ -383,11 +387,15 @@ class Transport (threading.Thread, ClosingContextManager): self.logger = util.get_logger(self.log_name) self.packetizer.set_log(self.logger) self.auth_handler = None - self.global_response = None # response Message from an arbitrary global request - self.completion_event = None # user-defined event callbacks - self.banner_timeout = 15 # how long (seconds) to wait for the SSH banner - self.handshake_timeout = 15 # how long (seconds) to wait for the handshake to finish after SSH banner sent. - + # response Message from an arbitrary global request + self.global_response = None + # user-defined event callbacks + self.completion_event = None + # how long (seconds) to wait for the SSH banner + self.banner_timeout = 15 + # how long (seconds) to wait for the handshake to finish after SSH + # banner sent. + self.handshake_timeout = 15 # server mode: self.server_mode = False @@ -406,8 +414,10 @@ class Transport (threading.Thread, ClosingContextManager): out += ' (unconnected)' else: if self.local_cipher != '': - out += ' (cipher %s, %d bits)' % (self.local_cipher, - self._cipher_info[self.local_cipher]['key-size'] * 8) + out += ' (cipher %s, %d bits)' % ( + self.local_cipher, + self._cipher_info[self.local_cipher]['key-size'] * 8 + ) if self.is_authenticated(): out += ' (active; %d open channel(s))' % len(self._channels) elif self.initial_kex_done: @@ -688,7 +698,12 @@ class Transport (threading.Thread, ClosingContextManager): """ return self.active - def open_session(self, window_size=None, max_packet_size=None, timeout=None): + def open_session( + self, + window_size=None, + max_packet_size=None, + timeout=None, + ): """ Request a new channel to the server, of type ``"session"``. This is just an alias for calling `open_channel` with an argument of @@ -749,7 +764,8 @@ class Transport (threading.Thread, ClosingContextManager): def open_forwarded_tcpip_channel(self, src_addr, dest_addr): """ - Request a new channel back to the client, of type ``"forwarded-tcpip"``. + Request a new channel back to the client, of type ``forwarded-tcpip``. + This is used after a client has requested port forwarding, for sending incoming connections back to the client. @@ -858,7 +874,11 @@ class Transport (threading.Thread, ClosingContextManager): If a handler is given, that handler is called from a different thread whenever a forwarded connection arrives. The handler parameters are:: - handler(channel, (origin_addr, origin_port), (server_addr, server_port)) + handler( + channel, + (origin_addr, origin_port), + (server_addr, server_port), + ) where ``server_addr`` and ``server_port`` are the address and port that the server was listening on. @@ -881,15 +901,17 @@ class Transport (threading.Thread, ClosingContextManager): if not self.active: raise SSHException('SSH session not active') port = int(port) - response = self.global_request('tcpip-forward', (address, port), wait=True) + response = self.global_request( + 'tcpip-forward', (address, port), wait=True + ) if response is None: raise SSHException('TCP forwarding request denied') if port == 0: port = response.get_int() if handler is None: def default_handler(channel, src_addr, dest_addr_port): - #src_addr, src_port = src_addr_port - #dest_addr, dest_port = dest_addr_port + # src_addr, src_port = src_addr_port + # dest_addr, dest_port = dest_addr_port self._queue_incoming_channel(channel) handler = default_handler self._tcp_handler = handler @@ -975,8 +997,9 @@ class Transport (threading.Thread, ClosingContextManager): seconds to wait before sending a keepalive packet (or 0 to disable keepalives). """ - self.packetizer.set_keepalive(interval, - lambda x=weakref.proxy(self): x.global_request('keepalive@lag.net', wait=False)) + def _request(x=weakref.proxy(self)): + return x.global_request('keepalive@lag.net', wait=False) + self.packetizer.set_keepalive(interval, _request) def global_request(self, kind, data=None, wait=True): """ @@ -1018,8 +1041,8 @@ class Transport (threading.Thread, ClosingContextManager): def accept(self, timeout=None): """ Return the next channel opened by the client over this transport, in - server mode. If no channel is opened before the given timeout, ``None`` - is returned. + server mode. If no channel is opened before the given timeout, + ``None`` is returned. :param int timeout: seconds to wait for a channel, or ``None`` to wait forever @@ -1040,8 +1063,17 @@ class Transport (threading.Thread, ClosingContextManager): self.lock.release() return chan - def connect(self, hostkey=None, username='', password=None, pkey=None, - gss_host=None, gss_auth=False, gss_kex=False, gss_deleg_creds=True): + def connect( + self, + hostkey=None, + username='', + password=None, + pkey=None, + gss_host=None, + gss_auth=False, + gss_kex=False, + gss_deleg_creds=True, + ): """ Negotiate an SSH2 session, and optionally verify the server's host key and authenticate using a password or private key. This is a shortcut @@ -1093,16 +1125,23 @@ class Transport (threading.Thread, ClosingContextManager): # the host key. if (hostkey is not None) and not gss_kex: key = self.get_remote_server_key() - if (key.get_name() != hostkey.get_name()) or (key.asbytes() != hostkey.asbytes()): + if ( + key.get_name() != hostkey.get_name() or + key.asbytes() != hostkey.asbytes() + ): self._log(DEBUG, 'Bad host key from server') - self._log(DEBUG, 'Expected: %s: %s' % (hostkey.get_name(), repr(hostkey.asbytes()))) - self._log(DEBUG, 'Got : %s: %s' % (key.get_name(), repr(key.asbytes()))) + self._log(DEBUG, 'Expected: %s: %s' % ( + hostkey.get_name(), repr(hostkey.asbytes())) + ) + self._log(DEBUG, 'Got : %s: %s' % ( + key.get_name(), repr(key.asbytes())) + ) raise SSHException('Bad host key from server') self._log(DEBUG, 'Host key verified (%s)' % hostkey.get_name()) if (pkey is not None) or (password is not None) or gss_auth or gss_kex: if gss_auth: - self._log(DEBUG, 'Attempting GSS-API auth... (gssapi-with-mic)') + self._log(DEBUG, 'Attempting GSS-API auth... (gssapi-with-mic)') # noqa self.auth_gssapi_with_mic(username, gss_host, gss_deleg_creds) elif gss_kex: self._log(DEBUG, 'Attempting GSS-API auth... (gssapi-keyex)') @@ -1165,7 +1204,11 @@ class Transport (threading.Thread, ClosingContextManager): successfully; False if authentication failed and/or the session is closed. """ - return self.active and (self.auth_handler is not None) and self.auth_handler.is_authenticated() + return ( + self.active and + self.auth_handler is not None and + self.auth_handler.is_authenticated() + ) def get_username(self): """ @@ -1264,7 +1307,8 @@ class Transport (threading.Thread, ClosingContextManager): :raises SSHException: if there was a network error """ if (not self.active) or (not self.initial_kex_done): - # we should never try to send the password unless we're on a secure link + # we should never try to send the password unless we're on a secure + # link raise SSHException('No existing session') if event is None: my_event = threading.Event() @@ -1278,7 +1322,8 @@ class Transport (threading.Thread, ClosingContextManager): try: return self.auth_handler.wait_for_response(my_event) except BadAuthenticationType as e: - # if password auth isn't allowed, but keyboard-interactive *is*, try to fudge it + # if password auth isn't allowed, but keyboard-interactive *is*, + # try to fudge it if not fallback or ('keyboard-interactive' not in e.allowed_types): raise try: @@ -1392,7 +1437,9 @@ class Transport (threading.Thread, ClosingContextManager): raise SSHException('No existing session') my_event = threading.Event() self.auth_handler = AuthHandler(self) - self.auth_handler.auth_interactive(username, handler, my_event, submethods) + self.auth_handler.auth_interactive( + username, handler, my_event, submethods + ) return self.auth_handler.wait_for_response(my_event) def auth_interactive_dumb(self, username, handler=None, submethods=''): @@ -1410,8 +1457,8 @@ class Transport (threading.Thread, ClosingContextManager): print(title.strip()) if instructions: print(instructions.strip()) - for prompt,show_input in prompt_list: - print(prompt.strip(),end=' ') + for prompt, show_input in prompt_list: + print(prompt.strip(), end=' ') answers.append(input()) return answers return self.auth_interactive(username, handler, submethods) @@ -1437,7 +1484,9 @@ class Transport (threading.Thread, ClosingContextManager): raise SSHException('No existing session') my_event = threading.Event() self.auth_handler = AuthHandler(self) - self.auth_handler.auth_gssapi_with_mic(username, gss_host, gss_deleg_creds, my_event) + self.auth_handler.auth_gssapi_with_mic( + username, gss_host, gss_deleg_creds, my_event + ) return self.auth_handler.wait_for_response(my_event) def auth_gssapi_keyex(self, username): @@ -1531,9 +1580,10 @@ class Transport (threading.Thread, ClosingContextManager): def getpeername(self): """ Return the address of the remote side of this Transport, if possible. - This is effectively a wrapper around ``'getpeername'`` on the underlying - socket. If the socket-like object has no ``'getpeername'`` method, - then ``("unknown", 0)`` is returned. + + This is effectively a wrapper around ``getpeername`` on the underlying + socket. If the socket-like object has no ``getpeername`` method, then + ``("unknown", 0)`` is returned. :return: the address of the remote host, if known, as a ``(str, int)`` @@ -1560,12 +1610,14 @@ class Transport (threading.Thread, ClosingContextManager): # our socket and packetizer are both closed (but where we'd # otherwise be sitting forever on that recv()). while ( - self.is_alive() and self is not threading.current_thread() - and not self.sock._closed and not self.packetizer.closed + self.is_alive() and + self is not threading.current_thread() and + not self.sock._closed and + not self.packetizer.closed ): self.join(0.1) - ### internals... + # internals... def _log(self, level, msg, *args): if issubclass(type(msg), list): @@ -1603,28 +1655,32 @@ class Transport (threading.Thread, ClosingContextManager): while True: self.clear_to_send.wait(0.1) if not self.active: - self._log(DEBUG, 'Dropping user packet because connection is dead.') + self._log(DEBUG, 'Dropping user packet because connection is dead.') # noqa return self.clear_to_send_lock.acquire() if self.clear_to_send.is_set(): break self.clear_to_send_lock.release() if time.time() > start + self.clear_to_send_timeout: - raise SSHException('Key-exchange timed out waiting for key negotiation') + raise SSHException('Key-exchange timed out waiting for key negotiation') # noqa try: self._send_message(data) finally: self.clear_to_send_lock.release() def _set_K_H(self, k, h): - """used by a kex object to set the K (root key) and H (exchange hash)""" + """ + Used by a kex obj to set the K (root key) and H (exchange hash). + """ self.K = k self.H = h if self.session_id is None: self.session_id = h def _expect_packet(self, *ptypes): - """used by a kex object to register the next packet type it expects to see""" + """ + Used by a kex obj to register the next packet type it expects to see. + """ self._expected_packet = tuple(ptypes) def _verify_key(self, host_key, sig): @@ -1632,7 +1688,7 @@ class Transport (threading.Thread, ClosingContextManager): if key is None: raise SSHException('Unknown host key type') if not key.verify_ssh_sig(self.H, Message(sig)): - raise SSHException('Signature verification (%s) failed.' % self.host_key_type) + raise SSHException('Signature verification (%s) failed.' % self.host_key_type) # noqa self.host_key = key def _compute_key(self, id, nbytes): @@ -1645,7 +1701,9 @@ class Transport (threading.Thread, ClosingContextManager): # Fallback to SHA1 for kex engines that fail to specify a hex # algorithm, or for e.g. transport tests that don't run kexinit. hash_algo = getattr(self.kex_engine, 'hash_algo', None) - hash_select_msg = "kex engine %s specified hash_algo %r" % (self.kex_engine.__class__.__name__, hash_algo) + hash_select_msg = "kex engine %s specified hash_algo %r" % ( + self.kex_engine.__class__.__name__, hash_algo + ) if hash_algo is None: hash_algo = sha1 hash_select_msg += ", falling back to sha1" @@ -1741,14 +1799,15 @@ class Transport (threading.Thread, ClosingContextManager): # 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): %s' % hex(long(id(self)) & xffffffff)) + self._log(DEBUG, 'starting thread (server mode): %s' % tid) else: - self._log(DEBUG, 'starting thread (client mode): %s' % hex(long(id(self)) & xffffffff)) + self._log(DEBUG, 'starting thread (client mode): %s' % tid) try: try: self.packetizer.write_all(b(self.local_version + '\r\n')) - self._log(DEBUG, 'Local version/idstring: %s' % self.local_version) + self._log(DEBUG, 'Local version/idstring: %s' % 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 @@ -1780,7 +1839,7 @@ class Transport (threading.Thread, ClosingContextManager): continue if len(self._expected_packet) > 0: if ptype not in self._expected_packet: - raise SSHException('Expecting packet from %r, got %d' % (self._expected_packet, ptype)) + raise SSHException('Expecting packet from %r, got %d' % (self._expected_packet, ptype)) # noqa self._expected_packet = tuple() if (ptype >= 30) and (ptype <= 41): self.kex_engine.parse_next(ptype, m) @@ -1794,13 +1853,17 @@ class Transport (threading.Thread, ClosingContextManager): 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' % chanid) + self._log(DEBUG, 'Ignoring message for dead channel %d' % chanid) # noqa else: - self._log(ERROR, 'Channel request for unknown channel %d' % chanid) + self._log(ERROR, 'Channel request for unknown channel %d' % chanid) # noqa self.active = False self.packetizer.close() - elif (self.auth_handler is not None) and (ptype in self.auth_handler._handler_table): - self.auth_handler._handler_table[ptype](self.auth_handler, m) + 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) else: self._log(WARNING, 'Oops, unhandled type %d' % ptype) msg = Message() @@ -1867,7 +1930,7 @@ class Transport (threading.Thread, ClosingContextManager): msg += "local={0}, remote={1}".format(local, remote) self._log(DEBUG, msg) - ### protocol stages + # protocol stages def _negotiate_keys(self, m): # throws SSHException on anything unusual @@ -1896,7 +1959,9 @@ class Transport (threading.Thread, ClosingContextManager): except ProxyCommandFailure: raise except Exception as e: - raise SSHException('Error reading SSH protocol banner' + str(e)) + raise SSHException( + 'Error reading SSH protocol banner' + str(e) + ) if buf[:4] == 'SSH-': break self._log(DEBUG, 'Banner: ' + buf) @@ -1906,10 +1971,10 @@ class Transport (threading.Thread, ClosingContextManager): self.remote_version = buf self._log(DEBUG, 'Remote version/idstring: %s' % buf) # pull off any attached comment - comment = '' + # NOTE: comment used to be stored in a variable and then...never used. + # since 2003. ca 877cd974b8182d26fa76d566072917ea67b64e67 i = buf.find(' ') if i >= 0: - comment = buf[i+1:] buf = buf[:i] # parse out version string and make sure it matches segs = buf.split('-', 2) @@ -1918,8 +1983,10 @@ class Transport (threading.Thread, ClosingContextManager): version = segs[1] client = segs[2] if version != '1.99' and version != '2.0': - raise SSHException('Incompatible version (%s instead of 2.0)' % (version,)) - self._log(INFO, 'Connected (version %s, client %s)' % (version, client)) + msg = 'Incompatible version ({0} instead of 2.0)' + raise SSHException(msg.format(version)) + msg = 'Connected (version {0}, client {1})'.format(version, client) + self._log(INFO, msg) def _send_kex_init(self): """ @@ -1934,14 +2001,24 @@ class Transport (threading.Thread, ClosingContextManager): self.in_kex = True if self.server_mode: mp_required_prefix = 'diffie-hellman-group-exchange-sha' - kex_mp = [k for k in self._preferred_kex if k.startswith(mp_required_prefix)] + kex_mp = [ + k for k + in self._preferred_kex + if k.startswith(mp_required_prefix) + ] if (self._modulus_pack is None) and (len(kex_mp) > 0): - # can't do group-exchange if we don't have a pack of potential primes - pkex = [k for k in self.get_security_options().kex - if not k.startswith(mp_required_prefix)] + # can't do group-exchange if we don't have a pack of potential + # primes + pkex = [ + k for k + in self.get_security_options().kex + if not k.startswith(mp_required_prefix) + ] self.get_security_options().kex = pkex - available_server_keys = list(filter(list(self.server_key_dict.keys()).__contains__, - self._preferred_keys)) + available_server_keys = list(filter( + list(self.server_key_dict.keys()).__contains__, + self._preferred_keys + )) else: available_server_keys = self._preferred_keys @@ -1965,7 +2042,7 @@ class Transport (threading.Thread, ClosingContextManager): self._send_message(m) def _parse_kex_init(self, m): - cookie = m.get_bytes(16) + m.get_bytes(16) # cookie, discarded kex_algo_list = m.get_list() server_key_algo_list = m.get_list() client_encrypt_algo_list = m.get_list() @@ -1977,18 +2054,21 @@ class Transport (threading.Thread, ClosingContextManager): client_lang_list = m.get_list() server_lang_list = m.get_list() kex_follows = m.get_boolean() - unused = m.get_int() - - self._log(DEBUG, 'kex algos:' + str(kex_algo_list) + ' server key:' + str(server_key_algo_list) + - ' client encrypt:' + str(client_encrypt_algo_list) + - ' server encrypt:' + str(server_encrypt_algo_list) + - ' client mac:' + str(client_mac_algo_list) + - ' server mac:' + str(server_mac_algo_list) + - ' client compress:' + str(client_compress_algo_list) + - ' server compress:' + str(server_compress_algo_list) + - ' client lang:' + str(client_lang_list) + - ' server lang:' + str(server_lang_list) + - ' kex follows?' + str(kex_follows)) + m.get_int() # unused + + self._log(DEBUG, + 'kex algos:' + str(kex_algo_list) + + ' server key:' + str(server_key_algo_list) + + ' client encrypt:' + str(client_encrypt_algo_list) + + ' server encrypt:' + str(server_encrypt_algo_list) + + ' client mac:' + str(client_mac_algo_list) + + ' server mac:' + str(server_mac_algo_list) + + ' client compress:' + str(client_compress_algo_list) + + ' server compress:' + str(server_compress_algo_list) + + ' client lang:' + str(client_lang_list) + + ' server lang:' + str(server_lang_list) + + ' kex follows?' + str(kex_follows) + ) # as a server, we pick the first item in the client's list that we # support. @@ -2005,34 +2085,48 @@ class Transport (threading.Thread, ClosingContextManager): self._preferred_kex )) if len(agreed_kex) == 0: - raise SSHException('Incompatible ssh peer (no acceptable kex algorithm)') + raise SSHException('Incompatible ssh peer (no acceptable kex algorithm)') # noqa self.kex_engine = self._kex_info[agreed_kex[0]](self) self._log(DEBUG, "Kex agreed: %s" % agreed_kex[0]) if self.server_mode: - available_server_keys = list(filter(list(self.server_key_dict.keys()).__contains__, - self._preferred_keys)) - agreed_keys = list(filter(available_server_keys.__contains__, server_key_algo_list)) + available_server_keys = list(filter( + list(self.server_key_dict.keys()).__contains__, + self._preferred_keys + )) + agreed_keys = list(filter( + available_server_keys.__contains__, server_key_algo_list + )) else: - agreed_keys = list(filter(server_key_algo_list.__contains__, self._preferred_keys)) + agreed_keys = list(filter( + server_key_algo_list.__contains__, self._preferred_keys + )) if len(agreed_keys) == 0: - raise SSHException('Incompatible ssh peer (no acceptable host key)') + raise SSHException('Incompatible ssh peer (no acceptable host key)') # noqa self.host_key_type = agreed_keys[0] if self.server_mode and (self.get_server_key() is None): - raise SSHException('Incompatible ssh peer (can\'t match requested host key type)') + raise SSHException('Incompatible ssh peer (can\'t match requested host key type)') # noqa if self.server_mode: - agreed_local_ciphers = list(filter(self._preferred_ciphers.__contains__, - server_encrypt_algo_list)) - agreed_remote_ciphers = list(filter(self._preferred_ciphers.__contains__, - client_encrypt_algo_list)) + agreed_local_ciphers = list(filter( + self._preferred_ciphers.__contains__, + server_encrypt_algo_list + )) + agreed_remote_ciphers = list(filter( + self._preferred_ciphers.__contains__, + client_encrypt_algo_list + )) else: - agreed_local_ciphers = list(filter(client_encrypt_algo_list.__contains__, - self._preferred_ciphers)) - agreed_remote_ciphers = list(filter(server_encrypt_algo_list.__contains__, - self._preferred_ciphers)) - if (len(agreed_local_ciphers) == 0) or (len(agreed_remote_ciphers) == 0): - raise SSHException('Incompatible ssh server (no acceptable ciphers)') + agreed_local_ciphers = list(filter( + client_encrypt_algo_list.__contains__, + self._preferred_ciphers + )) + agreed_remote_ciphers = list(filter( + server_encrypt_algo_list.__contains__, + self._preferred_ciphers + )) + if len(agreed_local_ciphers) == 0 or len(agreed_remote_ciphers) == 0: + raise SSHException('Incompatible ssh server (no acceptable ciphers)') # noqa self.local_cipher = agreed_local_ciphers[0] self.remote_cipher = agreed_remote_ciphers[0] self._log_agreement( @@ -2040,11 +2134,19 @@ class Transport (threading.Thread, ClosingContextManager): ) if self.server_mode: - agreed_remote_macs = list(filter(self._preferred_macs.__contains__, client_mac_algo_list)) - agreed_local_macs = list(filter(self._preferred_macs.__contains__, server_mac_algo_list)) + agreed_remote_macs = list(filter( + self._preferred_macs.__contains__, client_mac_algo_list + )) + agreed_local_macs = list(filter( + self._preferred_macs.__contains__, server_mac_algo_list + )) else: - agreed_local_macs = list(filter(client_mac_algo_list.__contains__, self._preferred_macs)) - agreed_remote_macs = list(filter(server_mac_algo_list.__contains__, self._preferred_macs)) + agreed_local_macs = list(filter( + client_mac_algo_list.__contains__, self._preferred_macs + )) + agreed_remote_macs = list(filter( + server_mac_algo_list.__contains__, self._preferred_macs + )) if (len(agreed_local_macs) == 0) or (len(agreed_remote_macs) == 0): raise SSHException('Incompatible ssh server (no acceptable macs)') self.local_mac = agreed_local_macs[0] @@ -2054,13 +2156,32 @@ class Transport (threading.Thread, ClosingContextManager): ) if self.server_mode: - agreed_remote_compression = list(filter(self._preferred_compression.__contains__, client_compress_algo_list)) - agreed_local_compression = list(filter(self._preferred_compression.__contains__, server_compress_algo_list)) + agreed_remote_compression = list(filter( + self._preferred_compression.__contains__, + client_compress_algo_list + )) + agreed_local_compression = list(filter( + self._preferred_compression.__contains__, + server_compress_algo_list + )) else: - agreed_local_compression = list(filter(client_compress_algo_list.__contains__, self._preferred_compression)) - agreed_remote_compression = list(filter(server_compress_algo_list.__contains__, self._preferred_compression)) - if (len(agreed_local_compression) == 0) or (len(agreed_remote_compression) == 0): - raise SSHException('Incompatible ssh server (no acceptable compression) %r %r %r' % (agreed_local_compression, agreed_remote_compression, self._preferred_compression)) + agreed_local_compression = list(filter( + client_compress_algo_list.__contains__, + self._preferred_compression + )) + agreed_remote_compression = list(filter( + server_compress_algo_list.__contains__, + self._preferred_compression + )) + if ( + len(agreed_local_compression) == 0 or + len(agreed_remote_compression) == 0 + ): + msg = 'Incompatible ssh server (no acceptable compression) {0!r} {1!r} {2!r}' # noqa + raise SSHException(msg.format( + agreed_local_compression, agreed_remote_compression, + self._preferred_compression, + )) self.local_compression = agreed_local_compression[0] self.remote_compression = agreed_remote_compression[0] self._log_agreement( @@ -2082,11 +2203,17 @@ class Transport (threading.Thread, ClosingContextManager): block_size = self._cipher_info[self.remote_cipher]['block-size'] if self.server_mode: IV_in = self._compute_key('A', block_size) - key_in = self._compute_key('C', self._cipher_info[self.remote_cipher]['key-size']) + key_in = self._compute_key( + 'C', self._cipher_info[self.remote_cipher]['key-size'] + ) else: IV_in = self._compute_key('B', block_size) - key_in = self._compute_key('D', self._cipher_info[self.remote_cipher]['key-size']) - engine = self._get_cipher(self.remote_cipher, key_in, IV_in, self._DECRYPT) + key_in = self._compute_key( + 'D', self._cipher_info[self.remote_cipher]['key-size'] + ) + engine = self._get_cipher( + self.remote_cipher, key_in, IV_in, self._DECRYPT + ) mac_size = self._mac_info[self.remote_mac]['size'] mac_engine = self._mac_info[self.remote_mac]['class'] # initial mac keys are done in the hash's natural size (not the @@ -2095,9 +2222,17 @@ class Transport (threading.Thread, ClosingContextManager): mac_key = self._compute_key('E', mac_engine().digest_size) else: mac_key = self._compute_key('F', mac_engine().digest_size) - self.packetizer.set_inbound_cipher(engine, block_size, mac_engine, mac_size, mac_key) + self.packetizer.set_inbound_cipher( + engine, block_size, mac_engine, mac_size, mac_key + ) compress_in = self._compression_info[self.remote_compression][1] - if (compress_in is not None) and ((self.remote_compression != 'zlib@openssh.com') or self.authenticated): + if ( + compress_in is not None and + ( + self.remote_compression != 'zlib@openssh.com' or + self.authenticated + ) + ): self._log(DEBUG, 'Switching on inbound compression ...') self.packetizer.set_inbound_compressor(compress_in()) diff --git a/tests/test_hostkeys.py b/tests/test_hostkeys.py index 2bdcad9c..2c7ceeb9 100644 --- a/tests/test_hostkeys.py +++ b/tests/test_hostkeys.py @@ -115,3 +115,15 @@ class HostKeysTest (unittest.TestCase): self.assertEqual(b'7EC91BB336CB6D810B124B1353C32396', fp) fp = hexlify(hostdict['secure.example.com']['ssh-dss'].get_fingerprint()).upper() self.assertEqual(b'4478F0B9A23CC5182009FF755BC1D26C', fp) + + def test_delitem(self): + hostdict = paramiko.HostKeys('hostfile.temp') + target = 'happy.example.com' + entry = hostdict[target] # will KeyError if not present + del hostdict[target] + try: + entry = hostdict[target] + except KeyError: + pass # Good + else: + assert False, "Entry was not deleted from HostKeys on delitem!" diff --git a/tests/test_ssh_gss.py b/tests/test_ssh_gss.py index e20d348f..967b3b81 100644 --- a/tests/test_ssh_gss.py +++ b/tests/test_ssh_gss.py @@ -43,9 +43,7 @@ class NullServer (paramiko.ServerInterface): return paramiko.AUTH_FAILED def enable_auth_gssapi(self): - UseGSSAPI = True - GSSAPICleanupCredentials = True - return UseGSSAPI + return True def check_channel_request(self, kind, chanid): return paramiko.OPEN_SUCCEEDED |