diff --git a/demos/rforward.py b/demos/rforward.py index 3406c388b..974fae4ca 100755 --- a/demos/rforward.py +++ b/demos/rforward.py @@ -75,7 +75,7 @@ def reverse_forward_tunnel(server_port, remote_host, remote_port, transport): if chan is None: continue thr = threading.Thread(target=handler, args=(chan, remote_host, remote_port)) - thr.setDaemon(True) + thr.daemon = True thr.start() diff --git a/paramiko/_version.py b/paramiko/_version.py index bd44e4160..264dfc70e 100644 --- a/paramiko/_version.py +++ b/paramiko/_version.py @@ -1,3 +1,3 @@ # last version component is odd for pre-release development, even for stable release -__version_info__ = (2, 7, 8) +__version_info__ = (2, 7, 10) __version__ = '.'.join(map(str, __version_info__)) diff --git a/paramiko/buffered_pipe.py b/paramiko/buffered_pipe.py index c78d81f6d..23c53ab26 100644 --- a/paramiko/buffered_pipe.py +++ b/paramiko/buffered_pipe.py @@ -107,7 +107,7 @@ def feed(self, data): if self._event is not None: self._event.set() self._buffer_frombytes(b(data)) - self._cv.notifyAll() + self._cv.notify_all() finally: self._lock.release() @@ -209,7 +209,7 @@ def close(self): self._lock.acquire() try: self._closed = True - self._cv.notifyAll() + self._cv.notify_all() if self._event is not None: self._event.set() finally: diff --git a/paramiko/channel.py b/paramiko/channel.py index 71fccb118..277896532 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -1034,7 +1034,7 @@ def _window_adjust(self, m): if self.ultra_debug: self._log(DEBUG, 'window up {}'.format(nbytes)) self.out_window_size += nbytes - self.out_buffer_cv.notifyAll() + self.out_buffer_cv.notify_all() finally: self.lock.release() @@ -1203,7 +1203,7 @@ def _set_closed(self): self.closed = True self.in_buffer.close() self.in_stderr_buffer.close() - self.out_buffer_cv.notifyAll() + self.out_buffer_cv.notify_all() # Notify any waiters that we are closed self.event.set() self.status_event.set() diff --git a/paramiko/common.py b/paramiko/common.py index eab6647ed..2927f1bec 100644 --- a/paramiko/common.py +++ b/paramiko/common.py @@ -20,7 +20,7 @@ Common constants and global variables. """ import logging -from paramiko.py3compat import byte_chr, PY2, long, b +from paramiko.py3compat import byte_chr, PY2, long, text_type MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG, \ MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT = range(1, 7) @@ -162,24 +162,17 @@ def asbytes(s): - """ - Coerce to bytes if possible or return unchanged. - """ - try: - # Attempt to run through our version of b(), which does the Right Thing - # for string/unicode/buffer (Py2) or bytes/str (Py3), and raises - # TypeError if it's not one of those types. - return b(s) - except TypeError: - try: - # If it wasn't a string/byte/buffer type object, try calling an - # asbytes() method, which many of our internal classes implement. - return s.asbytes() - except AttributeError: - # Finally, just do nothing & assume this object is sufficiently - # byte-y or buffer-y that everything will work out (or that callers - # are capable of handling whatever it is.) - return s + """Coerce to bytes if possible or return unchanged.""" + if isinstance(s, bytes): + return s + if isinstance(s, text_type): + # Accept text and encode as utf-8 for compatibility only. + return s.encode("utf-8") + asbytes = getattr(s, "asbytes", None) + if asbytes is not None: + return asbytes() + # May be an object that implements the buffer api, let callers handle. + return s xffffffff = long(0xffffffff) diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index 218c400d5..1eb57c9c5 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -152,6 +152,7 @@ def __delitem__(self, key): for e in list(self._entries): if e.key.get_name() == key: self._entries.remove(e) + break else: raise KeyError(key) diff --git a/paramiko/kex_gss.py b/paramiko/kex_gss.py index 59b49c368..871dcf7bc 100644 --- a/paramiko/kex_gss.py +++ b/paramiko/kex_gss.py @@ -206,7 +206,7 @@ def _parse_kexgss_complete(self, m): hm.add_mpint(self.e) hm.add_mpint(self.f) hm.add_mpint(K) - H = sha1(str(hm)).digest() + H = sha1(hm.asbytes()).digest() self.transport._set_K_H(K, H) if srv_token is not None: self.kexgss.ssh_init_sec_context(target=self.gss_host, diff --git a/paramiko/pkey.py b/paramiko/pkey.py index 62f6b3cbd..c57499f3e 100644 --- a/paramiko/pkey.py +++ b/paramiko/pkey.py @@ -112,26 +112,20 @@ def asbytes(self): def __str__(self): return self.asbytes() - # noinspection PyUnresolvedReferences - # TODO: The comparison functions should be removed as per: - # https://docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons def __cmp__(self, other): + # python-2 only, same purpose as __eq__() + return cmp(self.asbytes(), other.asbytes()) # noqa + + def __eq__(self, other): """ - Compare this key to another. Returns 0 if this key is equivalent to - the given key, or non-0 if they are different. Only the public parts + Compare this key to another. Returns True if this key is equivalent to + the given key, or False if they are different. Only the public parts of the key are compared, so a public key will compare equal to its corresponding private key. :param .PKey other: key to compare to. """ - hs = hash(self) - ho = hash(other) - if hs != ho: - return cmp(hs, ho) # noqa - return cmp(self.asbytes(), other.asbytes()) # noqa - - def __eq__(self, other): - return hash(self) == hash(other) + return self.asbytes() == other.asbytes() def get_name(self): """ diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py index 1f6af1c8a..d2c2ae0c3 100644 --- a/paramiko/rsakey.py +++ b/paramiko/rsakey.py @@ -123,10 +123,15 @@ def verify_ssh_sig(self, data, msg): if isinstance(key, rsa.RSAPrivateKey): key = key.public_key() + # pad received signature with leading zeros, key.verify() expects + # a signature of key_size bits (e.g. PuTTY doesn't pad) + sign = msg.get_binary() + diff = key.key_size - len(sign) * 8 + if diff > 0: + sign = b"\x00" * ((diff + 7) // 8) + sign + try: - key.verify( - msg.get_binary(), data, padding.PKCS1v15(), hashes.SHA1() - ) + key.verify(sign, data, padding.PKCS1v15(), hashes.SHA1()) except InvalidSignature: return False else: diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py index 08dd89059..3ecb518e5 100644 --- a/paramiko/sftp_file.py +++ b/paramiko/sftp_file.py @@ -511,7 +511,7 @@ def _start_prefetch(self, chunks): self._prefetch_done = False t = threading.Thread(target=self._prefetch_thread, args=(chunks,)) - t.setDaemon(True) + t.daemon = True t.start() def _prefetch_thread(self, chunks): diff --git a/paramiko/ssh_gss.py b/paramiko/ssh_gss.py index c1c045749..b0534eb3d 100644 --- a/paramiko/ssh_gss.py +++ b/paramiko/ssh_gss.py @@ -609,7 +609,7 @@ def ssh_init_sec_context(self, target, desired_mech=None, error, token = self._gss_ctxt.authorize(recv_token) token = token[0].Buffer except pywintypes.error as e: - e.strerror += ", Target: {}".format(e, self._gss_host) + e.strerror += ", Target: {}".format(self._gss_host) raise if error == 0: diff --git a/paramiko/transport.py b/paramiko/transport.py index 184843e6d..d8f75c69d 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -333,7 +333,7 @@ def __init__(self, # okay, normal socket-ish flow here... threading.Thread.__init__(self) - self.setDaemon(True) + self.daemon = True self.sock = sock # we set the timeout so we can check self.active periodically to # see if we should bail. socket.timeout exception is never propagated. diff --git a/paramiko/util.py b/paramiko/util.py index 6bdd49a9c..867742413 100644 --- a/paramiko/util.py +++ b/paramiko/util.py @@ -227,7 +227,7 @@ def mod_inverse(x, m): def get_thread_id(): global _g_thread_ids, _g_thread_counter, _g_thread_lock - tid = id(threading.currentThread()) + tid = id(threading.current_thread()) try: return _g_thread_ids[tid] except KeyError: diff --git a/tests/loop.py b/tests/loop.py index c9d20853c..b832c799c 100644 --- a/tests/loop.py +++ b/tests/loop.py @@ -81,7 +81,7 @@ def __feed(self, data): self.__lock.acquire() try: self.__in_buffer += data - self.__cv.notifyAll() + self.__cv.notify_all() finally: self.__lock.release() diff --git a/tests/test_client.py b/tests/test_client.py index ab580cbf0..4663a2598 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -404,12 +404,17 @@ def test_cleanup(self): self.tc.close() del self.tc - # force a collection to see whether the SSHClient object is deallocated - # 2 GCs are needed on PyPy, time is needed for Python 3 - time.sleep(0.3) + # GC is unpredictable, depending on python version and implementation + time.sleep(0.1) gc.collect() + time.sleep(0.2) + gc.collect() + time.sleep(0.1) + gc.collect() + time.sleep(0.2) + gc.collect() + time.sleep(0.1) gc.collect() - self.assertTrue(p() is None) def test_client_can_be_used_as_context_manager(self): @@ -596,7 +601,7 @@ def _setup_for_env(self): self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion') self.event.wait(1.0) - self.assertTrue(self.event.isSet()) + self.assertTrue(self.event.is_set()) self.assertTrue(self.ts.is_active()) def test_update_environment(self): diff --git a/tests/test_hostkeys.py b/tests/test_hostkeys.py index 4955095e8..e37415d7c 100644 --- a/tests/test_hostkeys.py +++ b/tests/test_hostkeys.py @@ -128,3 +128,20 @@ def test_delitem(self): pass # Good else: assert False, "Entry was not deleted from HostKeys on delitem!" + + def test_entry_delitem(self): + hostdict = paramiko.HostKeys('hostfile.temp') + target = 'happy.example.com' + entry = hostdict[target] + key_type_list = [key_type for key_type in entry] + for key_type in key_type_list: + del entry[key_type] + + # will KeyError if not present + for key_type in key_type_list: + try: + del entry[key_type] + except KeyError: + pass # Good + else: + assert False, "Key was not deleted from Entry on delitem!" diff --git a/tests/test_transport.py b/tests/test_transport.py index e21169497..78eaa8583 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -717,7 +717,7 @@ def test_rekey_deadlock(self): class SendThread(threading.Thread): def __init__(self, chan, iterations, done_event): threading.Thread.__init__(self, None, None, self.__class__.__name__) - self.setDaemon(True) + self.daemon = True self.chan = chan self.iterations = iterations self.done_event = done_event @@ -738,7 +738,7 @@ def run(self): class ReceiveThread(threading.Thread): def __init__(self, chan, done_event): threading.Thread.__init__(self, None, None, self.__class__.__name__) - self.setDaemon(True) + self.daemon = True self.chan = chan self.done_event = done_event self.watchdog_event = threading.Event()