diff -Nru python-ssh-1.7.14/.pc/.quilt_patches python-ssh-1.8.0/.pc/.quilt_patches --- python-ssh-1.7.14/.pc/.quilt_patches 1970-01-01 00:00:00.000000000 +0000 +++ python-ssh-1.8.0/.pc/.quilt_patches 2013-01-24 19:24:59.000000000 +0000 @@ -0,0 +1 @@ +debian/patches diff -Nru python-ssh-1.7.14/.pc/.quilt_series python-ssh-1.8.0/.pc/.quilt_series --- python-ssh-1.7.14/.pc/.quilt_series 1970-01-01 00:00:00.000000000 +0000 +++ python-ssh-1.8.0/.pc/.quilt_series 2013-01-24 19:24:59.000000000 +0000 @@ -0,0 +1 @@ +series diff -Nru python-ssh-1.7.14/.pc/.version python-ssh-1.8.0/.pc/.version --- python-ssh-1.7.14/.pc/.version 1970-01-01 00:00:00.000000000 +0000 +++ python-ssh-1.8.0/.pc/.version 2013-01-24 19:24:59.000000000 +0000 @@ -0,0 +1 @@ +2 diff -Nru python-ssh-1.7.14/PKG-INFO python-ssh-1.8.0/PKG-INFO --- python-ssh-1.7.14/PKG-INFO 2012-05-08 05:03:34.000000000 +0000 +++ python-ssh-1.8.0/PKG-INFO 2012-10-29 01:50:15.000000000 +0000 @@ -1,8 +1,8 @@ Metadata-Version: 1.0 Name: ssh -Version: 1.7.14 +Version: 1.8.0 Summary: SSH2 protocol library -Home-page: UNKNOWN +Home-page: https://github.com/bitprophet/ssh/ Author: Jeff Forcier Author-email: jeff@bitprophet.org License: LGPL diff -Nru python-ssh-1.7.14/debian/changelog python-ssh-1.8.0/debian/changelog --- python-ssh-1.7.14/debian/changelog 2013-01-24 19:24:59.000000000 +0000 +++ python-ssh-1.8.0/debian/changelog 2013-01-24 19:24:59.000000000 +0000 @@ -1,20 +1,8 @@ -python-ssh (1.7.14-2chl1~lucid1) lucid; urgency=low +python-ssh (1.8.0-1chl1~lucid1) lucid; urgency=low - * README is gone + * Package 1.8.0 for lucid - -- Chris Lea Fri, 20 Jul 2012 23:30:03 +0000 - -python-ssh (1.7.14-1chl1~lucid1) lucid; urgency=low - - * Package 1.7.14 for lucid - - -- Chris Lea Fri, 20 Jul 2012 19:06:17 +0000 - -python-ssh (1.7.13-1chl1~lucid1) lucid; urgency=low - - * Package 1.7.13 for lucid - - -- Chris Lea Tue, 06 Mar 2012 02:38:06 +0000 + -- Chris Lea Thu, 24 Jan 2013 11:03:53 -0800 python-ssh (1.7.8-3chl1~lucid1) lucid; urgency=low diff -Nru python-ssh-1.7.14/debian/patches/crypto_version.patch python-ssh-1.8.0/debian/patches/crypto_version.patch --- python-ssh-1.7.14/debian/patches/crypto_version.patch 2013-01-24 19:24:59.000000000 +0000 +++ python-ssh-1.8.0/debian/patches/crypto_version.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -diff -urN ssh-1.7.8.orig/setup.py ssh-1.7.8/setup.py ---- ssh-1.7.8.orig/setup.py 2011-10-23 22:06:53.000000000 +0000 -+++ ssh-1.7.8/setup.py 2011-10-27 23:13:59.000000000 +0000 -@@ -36,7 +36,7 @@ - try: - from setuptools import setup - kw = { -- 'install_requires': 'pycrypto >= 2.1, < 2.4', -+ 'install_requires': 'pycrypto >= 2.1', - } - except ImportError: - from distutils.core import setup diff -Nru python-ssh-1.7.14/debian/patches/series python-ssh-1.8.0/debian/patches/series --- python-ssh-1.7.14/debian/patches/series 2013-01-24 19:24:59.000000000 +0000 +++ python-ssh-1.8.0/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -#crypto_version.patch diff -Nru python-ssh-1.7.14/setup.py python-ssh-1.8.0/setup.py --- python-ssh-1.7.14/setup.py 2012-05-08 03:22:19.000000000 +0000 +++ python-ssh-1.8.0/setup.py 2012-09-14 19:01:37.000000000 +0000 @@ -50,10 +50,11 @@ setup(name = "ssh", - version = "1.7.14", + version = "1.8.0", description = "SSH2 protocol library", author = "Jeff Forcier", author_email = "jeff@bitprophet.org", + url="https://github.com/bitprophet/ssh/", packages = [ 'ssh' ], license = 'LGPL', platforms = 'Posix; MacOS X; Windows', diff -Nru python-ssh-1.7.14/ssh/__init__.py python-ssh-1.8.0/ssh/__init__.py --- python-ssh-1.7.14/ssh/__init__.py 2012-05-08 03:21:38.000000000 +0000 +++ python-ssh-1.8.0/ssh/__init__.py 2012-09-14 19:02:23.000000000 +0000 @@ -55,7 +55,7 @@ __author__ = "Jeff Forcier " -__version__ = "1.7.14" +__version__ = "1.8.0" __license__ = "GNU Lesser General Public License (LGPL)" diff -Nru python-ssh-1.7.14/ssh/agent.py python-ssh-1.8.0/ssh/agent.py --- python-ssh-1.7.14/ssh/agent.py 2012-05-08 03:03:26.000000000 +0000 +++ python-ssh-1.8.0/ssh/agent.py 2012-09-10 18:40:40.000000000 +0000 @@ -35,6 +35,7 @@ from ssh.pkey import PKey from ssh.channel import Channel from ssh.common import io_sleep +from ssh.util import retry_on_signal SSH2_AGENTC_REQUEST_IDENTITIES, SSH2_AGENT_IDENTITIES_ANSWER, \ SSH2_AGENTC_SIGN_REQUEST, SSH2_AGENT_SIGN_RESPONSE = range(11, 15) @@ -202,7 +203,7 @@ if ('SSH_AUTH_SOCK' in os.environ) and (sys.platform != 'win32'): conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: - conn.connect(os.environ['SSH_AUTH_SOCK']) + retry_on_signal(lambda: conn.connect(os.environ['SSH_AUTH_SOCK'])) except: # probably a dangling env var: the ssh agent is gone return diff -Nru python-ssh-1.7.14/ssh/client.py python-ssh-1.8.0/ssh/client.py --- python-ssh-1.7.14/ssh/client.py 2012-04-19 22:17:28.000000000 +0000 +++ python-ssh-1.8.0/ssh/client.py 2012-09-10 18:40:40.000000000 +0000 @@ -34,6 +34,7 @@ from ssh.rsakey import RSAKey from ssh.ssh_exception import SSHException, BadHostKeyException from ssh.transport import Transport +from ssh.util import retry_on_signal SSH_PORT = 22 @@ -82,7 +83,7 @@ def missing_host_key(self, client, hostname, key): client._log(DEBUG, 'Rejecting %s host key for %s: %s' % (key.get_name(), hostname, hexlify(key.get_fingerprint()))) - raise SSHException('Unknown server %s' % hostname) + raise SSHException('Server %r not found in known_hosts' % hostname) class WarningPolicy (MissingHostKeyPolicy): @@ -293,7 +294,7 @@ sock.settimeout(timeout) except: pass - sock.connect(addr) + retry_on_signal(lambda: sock.connect(addr)) t = self._transport = Transport(sock) t.use_compression(compress=compress) if self._log_channel is not None: @@ -419,67 +420,86 @@ - Plain username/password auth, if a password was given. (The password might be needed to unlock a private key.) + + The password is required for two-factor authentication. """ saved_exception = None + two_factor = False + allowed_types = [] if pkey is not None: try: self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint())) - self._transport.auth_publickey(username, pkey) - return + allowed_types = self._transport.auth_publickey(username, pkey) + two_factor = (allowed_types == ['password']) + if not two_factor: + return except SSHException, e: saved_exception = e - for key_filename in key_filenames: - for pkey_class in (RSAKey, DSSKey): - try: - key = pkey_class.from_private_key_file(key_filename, password) - self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename)) - self._transport.auth_publickey(username, key) - return - except SSHException, e: - saved_exception = e + if not two_factor: + for key_filename in key_filenames: + for pkey_class in (RSAKey, DSSKey): + try: + key = pkey_class.from_private_key_file(key_filename, password) + self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename)) + self._transport.auth_publickey(username, key) + two_factor = (allowed_types == ['password']) + if not two_factor: + return + break + except SSHException, e: + saved_exception = e - if allow_agent: + if not two_factor and allow_agent: if self._agent == None: self._agent = Agent() for key in self._agent.get_keys(): try: self._log(DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint())) - self._transport.auth_publickey(username, key) - return + # for 2-factor auth a successfully auth'd key will result in ['password'] + allowed_types = self._transport.auth_publickey(username, key) + two_factor = (allowed_types == ['password']) + if not two_factor: + return + break except SSHException, e: saved_exception = e - keyfiles = [] - rsa_key = os.path.expanduser('~/.ssh/id_rsa') - dsa_key = os.path.expanduser('~/.ssh/id_dsa') - if os.path.isfile(rsa_key): - keyfiles.append((RSAKey, rsa_key)) - if os.path.isfile(dsa_key): - keyfiles.append((DSSKey, dsa_key)) - # look in ~/ssh/ for windows users: - rsa_key = os.path.expanduser('~/ssh/id_rsa') - dsa_key = os.path.expanduser('~/ssh/id_dsa') - if os.path.isfile(rsa_key): - keyfiles.append((RSAKey, rsa_key)) - if os.path.isfile(dsa_key): - keyfiles.append((DSSKey, dsa_key)) - - if not look_for_keys: + if not two_factor: keyfiles = [] - - for pkey_class, filename in keyfiles: - try: - key = pkey_class.from_private_key_file(filename, password) - self._log(DEBUG, 'Trying discovered key %s in %s' % (hexlify(key.get_fingerprint()), filename)) - self._transport.auth_publickey(username, key) - return - except SSHException, e: - saved_exception = e - except IOError, e: - saved_exception = e + rsa_key = os.path.expanduser('~/.ssh/id_rsa') + dsa_key = os.path.expanduser('~/.ssh/id_dsa') + if os.path.isfile(rsa_key): + keyfiles.append((RSAKey, rsa_key)) + if os.path.isfile(dsa_key): + keyfiles.append((DSSKey, dsa_key)) + # look in ~/ssh/ for windows users: + rsa_key = os.path.expanduser('~/ssh/id_rsa') + dsa_key = os.path.expanduser('~/ssh/id_dsa') + if os.path.isfile(rsa_key): + keyfiles.append((RSAKey, rsa_key)) + if os.path.isfile(dsa_key): + keyfiles.append((DSSKey, dsa_key)) + + if not look_for_keys: + keyfiles = [] + + for pkey_class, filename in keyfiles: + try: + key = pkey_class.from_private_key_file(filename, password) + self._log(DEBUG, 'Trying discovered key %s in %s' % (hexlify(key.get_fingerprint()), filename)) + # for 2-factor auth a successfully auth'd key will result in ['password'] + allowed_types = self._transport.auth_publickey(username, key) + two_factor = (allowed_types == ['password']) + if not two_factor: + return + break + except SSHException, e: + saved_exception = e + except IOError, e: + saved_exception = e if password is not None: try: @@ -487,6 +507,8 @@ return except SSHException, e: saved_exception = e + elif two_factor: + raise SSHException('Two-factor authentication requires a password') # if we got an auth-failed exception earlier, re-raise it if saved_exception is not None: diff -Nru python-ssh-1.7.14/ssh/config.py python-ssh-1.8.0/ssh/config.py --- python-ssh-1.7.14/ssh/config.py 2012-04-19 22:17:28.000000000 +0000 +++ python-ssh-1.8.0/ssh/config.py 2012-07-15 23:49:24.000000000 +0000 @@ -104,11 +104,14 @@ @type hostname: str """ matches = [x for x in self._config if fnmatch.fnmatch(hostname, x['host'])] - # sort in order of shortest match (usually '*') to longest - matches.sort(lambda x,y: cmp(len(x['host']), len(y['host']))) + # Move * to the end + _star = matches.pop(0) + matches.append(_star) ret = {} for m in matches: - ret.update(m) + for k,v in m.iteritems(): + if not k in ret: + ret[k] = v ret = self._expand_variables(ret, hostname) del ret['host'] return ret diff -Nru python-ssh-1.7.14/ssh/hostkeys.py python-ssh-1.8.0/ssh/hostkeys.py --- python-ssh-1.7.14/ssh/hostkeys.py 2012-04-19 22:17:28.000000000 +0000 +++ python-ssh-1.8.0/ssh/hostkeys.py 2012-07-15 23:45:13.000000000 +0000 @@ -21,6 +21,7 @@ """ import base64 +import binascii from Crypto.Hash import SHA, HMAC import UserDict @@ -29,6 +30,14 @@ from ssh.rsakey import RSAKey +class InvalidHostKey(Exception): + + def __init__(self, line, exc): + self.line = line + self.exc = exc + self.args = (line, exc) + + class HostKeyEntry: """ Representation of a line in an OpenSSH-style "known hosts" file. @@ -63,12 +72,15 @@ # Decide what kind of key we're looking at and create an object # to hold it accordingly. - if keytype == 'ssh-rsa': - key = RSAKey(data=base64.decodestring(key)) - elif keytype == 'ssh-dss': - key = DSSKey(data=base64.decodestring(key)) - else: - return None + try: + if keytype == 'ssh-rsa': + key = RSAKey(data=base64.decodestring(key)) + elif keytype == 'ssh-dss': + key = DSSKey(data=base64.decodestring(key)) + else: + return None + except binascii.Error, e: + raise InvalidHostKey(line, e) return cls(names, key) from_line = classmethod(from_line) diff -Nru python-ssh-1.7.14/ssh/packet.py python-ssh-1.8.0/ssh/packet.py --- python-ssh-1.7.14/ssh/packet.py 2012-04-19 22:17:28.000000000 +0000 +++ python-ssh-1.8.0/ssh/packet.py 2012-09-10 18:40:40.000000000 +0000 @@ -57,8 +57,11 @@ # READ the secsh RFC's before raising these values. if anything, # they should probably be lower. - REKEY_PACKETS = pow(2, 30) - REKEY_BYTES = pow(2, 30) + REKEY_PACKETS = pow(2, 29) + REKEY_BYTES = pow(2, 29) + + REKEY_PACKETS_OVERFLOW_MAX = pow(2,29) # Allow receiving this many packets after a re-key request before terminating + REKEY_BYTES_OVERFLOW_MAX = pow(2,29) # Allow receiving this many bytes after a re-key request before terminating def __init__(self, socket): self.__socket = socket @@ -74,6 +77,7 @@ self.__sent_packets = 0 self.__received_bytes = 0 self.__received_packets = 0 + self.__received_bytes_overflow = 0 self.__received_packets_overflow = 0 # current inbound/outbound ciphering: @@ -134,6 +138,7 @@ self.__mac_key_in = mac_key self.__received_bytes = 0 self.__received_packets = 0 + self.__received_bytes_overflow = 0 self.__received_packets_overflow = 0 # wait until the reset happens in both directions before clearing rekey flag self.__init_count |= 2 @@ -236,23 +241,23 @@ def write_all(self, out): self.__keepalive_last = time.time() while len(out) > 0: - got_timeout = False + retry_write = False try: n = self.__socket.send(out) except socket.timeout: - got_timeout = True + retry_write = True except socket.error, e: if (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EAGAIN): - got_timeout = True + retry_write = True elif (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EINTR): # syscall interrupted; try again - pass + retry_write = True else: n = -1 except Exception: # could be: (32, 'Broken pipe') n = -1 - if got_timeout: + if retry_write: n = 0 if self.__closed: n = -1 @@ -316,6 +321,7 @@ # only ask once for rekeying self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes sent)' % (self.__sent_packets, self.__sent_bytes)) + self.__received_bytes_overflow = 0 self.__received_packets_overflow = 0 self._trigger_rekey() finally: @@ -368,19 +374,23 @@ self.__sequence_number_in = (self.__sequence_number_in + 1) & 0xffffffffL # check for rekey - self.__received_bytes += packet_size + self.__mac_size_in + 4 + 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 20 packets to comply before + # 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 >= 20: + 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() @@ -459,6 +469,12 @@ break except socket.timeout: pass + except EnvironmentError, e: + if ((type(e.args) is tuple) and (len(e.args) > 0) and + (e.args[0] == errno.EINTR)): + pass + else: + raise if self.__closed: raise EOFError() now = time.time() diff -Nru python-ssh-1.7.14/ssh/transport.py python-ssh-1.8.0/ssh/transport.py --- python-ssh-1.7.14/ssh/transport.py 2012-05-06 22:10:09.000000000 +0000 +++ python-ssh-1.8.0/ssh/transport.py 2012-09-13 23:42:52.000000000 +0000 @@ -45,6 +45,7 @@ from ssh.server import ServerInterface from ssh.sftp_client import SFTPClient from ssh.ssh_exception import SSHException, BadAuthenticationType, ChannelException +from ssh.util import retry_on_signal from Crypto import Random from Crypto.Cipher import Blowfish, AES, DES3, ARC4 @@ -289,7 +290,7 @@ addr = sockaddr sock = socket.socket(af, socket.SOCK_STREAM) try: - sock.connect((hostname, port)) + retry_on_signal(lambda: sock.connect((hostname, port))) except socket.error, e: reason = str(e) else: @@ -1529,6 +1530,10 @@ # indefinitely, creating a GC cycle and not letting Transport ever be # GC'd. it's a bug in Thread.) + # Hold reference to 'sys' so we can test sys.modules to detect + # interpreter shutdown. + self.sys = sys + # Required to prevent RNG errors when running inside many subprocess # containers. Random.atfork() @@ -1540,94 +1545,102 @@ else: self._log(DEBUG, 'starting thread (client mode): %s' % hex(long(id(self)) & 0xffffffffL)) try: - self.packetizer.write_all(self.local_version + '\r\n') - self._check_banner() - self._send_kex_init() - self._expect_packet(MSG_KEXINIT) - - while self.active: - if self.packetizer.need_rekey() and not self.in_kex: - self._send_kex_init() - try: - ptype, m = self.packetizer.read_message() - except NeedRekeyException: - continue - if ptype == MSG_IGNORE: - continue - elif ptype == MSG_DISCONNECT: - self._parse_disconnect(m) - self.active = False - self.packetizer.close() - break - elif ptype == MSG_DEBUG: - self._parse_debug(m) - 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)) - self._expected_packet = tuple() - if (ptype >= 30) and (ptype <= 39): - self.kex_engine.parse_next(ptype, m) + try: + self.packetizer.write_all(self.local_version + '\r\n') + self._check_banner() + self._send_kex_init() + self._expect_packet(MSG_KEXINIT) + + while self.active: + if self.packetizer.need_rekey() and not self.in_kex: + self._send_kex_init() + try: + ptype, m = self.packetizer.read_message() + except NeedRekeyException: continue - - if ptype in self._handler_table: - self._handler_table[ptype](self, m) - elif ptype in self._channel_handler_table: - chanid = m.get_int() - chan = self._channels.get(chanid) - if chan is not None: - self._channel_handler_table[ptype](chan, m) - elif chanid in self.channels_seen: - self._log(DEBUG, 'Ignoring message for dead channel %d' % chanid) - else: - self._log(ERROR, 'Channel request for unknown channel %d' % chanid) + if ptype == MSG_IGNORE: + continue + elif ptype == MSG_DISCONNECT: + self._parse_disconnect(m) 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) + break + elif ptype == MSG_DEBUG: + self._parse_debug(m) + 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)) + self._expected_packet = tuple() + if (ptype >= 30) and (ptype <= 39): + self.kex_engine.parse_next(ptype, m) + continue + + if ptype in self._handler_table: + self._handler_table[ptype](self, m) + elif ptype in self._channel_handler_table: + chanid = m.get_int() + chan = self._channels.get(chanid) + if chan is not None: + self._channel_handler_table[ptype](chan, m) + elif chanid in self.channels_seen: + self._log(DEBUG, 'Ignoring message for dead channel %d' % chanid) + else: + self._log(ERROR, 'Channel request for unknown channel %d' % chanid) + 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) + else: + self._log(WARNING, 'Oops, unhandled type %d' % ptype) + msg = Message() + msg.add_byte(chr(MSG_UNIMPLEMENTED)) + msg.add_int(m.seqno) + self._send_message(msg) + except SSHException, e: + self._log(ERROR, 'Exception: ' + str(e)) + self._log(ERROR, util.tb_strings()) + self.saved_exception = e + except EOFError, e: + self._log(DEBUG, 'EOF in transport thread') + #self._log(DEBUG, util.tb_strings()) + self.saved_exception = e + except socket.error, e: + if type(e.args) is tuple: + emsg = '%s (%d)' % (e.args[1], e.args[0]) else: - self._log(WARNING, 'Oops, unhandled type %d' % ptype) - msg = Message() - msg.add_byte(chr(MSG_UNIMPLEMENTED)) - msg.add_int(m.seqno) - self._send_message(msg) - except SSHException, e: - self._log(ERROR, 'Exception: ' + str(e)) - self._log(ERROR, util.tb_strings()) - self.saved_exception = e - except EOFError, e: - self._log(DEBUG, 'EOF in transport thread') - #self._log(DEBUG, util.tb_strings()) - self.saved_exception = e - except socket.error, e: - if type(e.args) is tuple: - emsg = '%s (%d)' % (e.args[1], e.args[0]) - else: - emsg = e.args - self._log(ERROR, 'Socket exception: ' + emsg) - self.saved_exception = e - except Exception, e: - self._log(ERROR, 'Unknown exception: ' + str(e)) - self._log(ERROR, util.tb_strings()) - self.saved_exception = e - _active_threads.remove(self) - for chan in self._channels.values(): - chan._unlink() - if self.active: - self.active = False - self.packetizer.close() - if self.completion_event != None: - self.completion_event.set() - if self.auth_handler is not None: - self.auth_handler.abort() - for event in self.channel_events.values(): - event.set() - try: - self.lock.acquire() - self.server_accept_cv.notify() - finally: - self.lock.release() - self.sock.close() + emsg = e.args + self._log(ERROR, 'Socket exception: ' + emsg) + self.saved_exception = e + except Exception, e: + self._log(ERROR, 'Unknown exception: ' + str(e)) + self._log(ERROR, util.tb_strings()) + self.saved_exception = e + _active_threads.remove(self) + for chan in self._channels.values(): + chan._unlink() + if self.active: + self.active = False + self.packetizer.close() + if self.completion_event != None: + self.completion_event.set() + if self.auth_handler is not None: + self.auth_handler.abort() + for event in self.channel_events.values(): + event.set() + try: + self.lock.acquire() + self.server_accept_cv.notify() + finally: + self.lock.release() + self.sock.close() + except: + # Don't raise spurious 'NoneType has no attribute X' errors when we + # wake up during interpreter shutdown. Or rather -- raise + # everything *if* sys.modules (used as a convenient sentinel) + # appears to still exist. + if self.sys.modules is not None: + raise ### protocol stages diff -Nru python-ssh-1.7.14/ssh/util.py python-ssh-1.8.0/ssh/util.py --- python-ssh-1.7.14/ssh/util.py 2012-04-19 22:17:28.000000000 +0000 +++ python-ssh-1.8.0/ssh/util.py 2012-09-10 18:40:40.000000000 +0000 @@ -24,6 +24,7 @@ import array from binascii import hexlify, unhexlify +import errno import sys import struct import traceback @@ -270,6 +271,14 @@ l.addFilter(_pfilter) return l +def retry_on_signal(function): + """Retries function until it doesn't raise an EINTR error""" + while True: + try: + return function() + except EnvironmentError, e: + if e.errno != errno.EINTR: + raise class Counter (object): """Stateful counter for CTR mode crypto""" diff -Nru python-ssh-1.7.14/ssh.egg-info/PKG-INFO python-ssh-1.8.0/ssh.egg-info/PKG-INFO --- python-ssh-1.7.14/ssh.egg-info/PKG-INFO 2012-05-08 05:03:34.000000000 +0000 +++ python-ssh-1.8.0/ssh.egg-info/PKG-INFO 2012-10-29 01:50:15.000000000 +0000 @@ -1,8 +1,8 @@ Metadata-Version: 1.0 Name: ssh -Version: 1.7.14 +Version: 1.8.0 Summary: SSH2 protocol library -Home-page: UNKNOWN +Home-page: https://github.com/bitprophet/ssh/ Author: Jeff Forcier Author-email: jeff@bitprophet.org License: LGPL diff -Nru python-ssh-1.7.14/test.py python-ssh-1.8.0/test.py --- python-ssh-1.7.14/test.py 2012-05-08 03:27:30.000000000 +0000 +++ python-ssh-1.8.0/test.py 2012-09-10 18:27:08.000000000 +0000 @@ -28,6 +28,7 @@ import unittest from optparse import OptionParser import ssh +import threading sys.path.append('tests') @@ -141,7 +142,15 @@ if len(args) > 0: filter = '|'.join(args) suite = filter_suite_by_re(suite, filter) - runner.run(suite) + result = runner.run(suite) + # Clean up stale threads from poorly cleaned-up tests. + # TODO: make that not a problem, jeez + for thread in threading.enumerate(): + if thread is not threading.currentThread(): + thread._Thread__stop() + # Exit correctly + if not result.wasSuccessful(): + sys.exit(1) if __name__ == '__main__': diff -Nru python-ssh-1.7.14/tests/test_client.py python-ssh-1.8.0/tests/test_client.py --- python-ssh-1.7.14/tests/test_client.py 2012-04-19 22:17:28.000000000 +0000 +++ python-ssh-1.8.0/tests/test_client.py 2012-09-10 18:28:06.000000000 +0000 @@ -68,11 +68,9 @@ thread.start() def tearDown(self): - if hasattr(self, 'tc'): - self.tc.close() - self.ts.close() - self.socks.close() - self.sockl.close() + for attr in "tc ts socks sockl".split(): + if hasattr(self, attr): + getattr(self, attr).close() def _run(self): self.socks, addr = self.sockl.accept() diff -Nru python-ssh-1.7.14/tests/test_util.py python-ssh-1.8.0/tests/test_util.py --- python-ssh-1.7.14/tests/test_util.py 2012-05-08 05:02:29.000000000 +0000 +++ python-ssh-1.8.0/tests/test_util.py 2012-09-10 18:40:40.000000000 +0000 @@ -22,6 +22,7 @@ from binascii import hexlify import cStringIO +import errno import os import unittest from Crypto.Hash import SHA @@ -159,3 +160,46 @@ x = rng.read(32) self.assertEquals(len(x), 32) + def test_7_host_config_expose_issue_33(self): + test_config_file = """ +Host www13.* + Port 22 + +Host *.example.com + Port 2222 + +Host * + Port 3333 + """ + f = cStringIO.StringIO(test_config_file) + config = ssh.util.parse_ssh_config(f) + host = 'www13.example.com' + self.assertEquals( + ssh.util.lookup_ssh_host_config(host, config), + {'hostname': host, 'port': '22'} + ) + + def test_8_eintr_retry(self): + self.assertEquals('foo', ssh.util.retry_on_signal(lambda: 'foo')) + + # Variables that are set by raises_intr + intr_errors_remaining = [3] + call_count = [0] + def raises_intr(): + call_count[0] += 1 + if intr_errors_remaining[0] > 0: + intr_errors_remaining[0] -= 1 + raise IOError(errno.EINTR, 'file', 'interrupted system call') + self.assertTrue(ssh.util.retry_on_signal(raises_intr) is None) + self.assertEquals(0, intr_errors_remaining[0]) + self.assertEquals(4, call_count[0]) + + def raises_ioerror_not_eintr(): + raise IOError(errno.ENOENT, 'file', 'file not found') + self.assertRaises(IOError, + lambda: ssh.util.retry_on_signal(raises_ioerror_not_eintr)) + + def raises_other_exception(): + raise AssertionError('foo') + self.assertRaises(AssertionError, + lambda: ssh.util.retry_on_signal(raises_other_exception))