diff options
author | Alex Gaynor <alex.gaynor@gmail.com> | 2014-12-18 11:19:35 -0800 |
---|---|---|
committer | Alex Gaynor <alex.gaynor@gmail.com> | 2014-12-18 11:19:35 -0800 |
commit | b3884d2c44233f16cd334c4abcfddcf5e8bf8ef6 (patch) | |
tree | 107d26c33a7fb1428bda2ed5be9b9e2e09277c72 | |
parent | 37756f3a8892dbb41dfda74dcf37e21053bf4e34 (diff) | |
parent | 681f32583fe052c0516a2fda67e163169676ad11 (diff) |
Merge branch 'master' into switch-to-cryptography
Conflicts:
paramiko/ecdsakey.py
paramiko/util.py
-rw-r--r-- | .travis.yml | 1 | ||||
-rw-r--r-- | demos/demo_server.py | 2 | ||||
-rw-r--r-- | paramiko/_winapi.py | 8 | ||||
-rw-r--r-- | paramiko/agent.py | 3 | ||||
-rw-r--r-- | paramiko/auth_handler.py | 2 | ||||
-rw-r--r-- | paramiko/ber.py | 6 | ||||
-rw-r--r-- | paramiko/channel.py | 8 | ||||
-rw-r--r-- | paramiko/common.py | 6 | ||||
-rw-r--r-- | paramiko/config.py | 44 | ||||
-rw-r--r-- | paramiko/dsskey.py | 2 | ||||
-rw-r--r-- | paramiko/ecdsakey.py | 5 | ||||
-rw-r--r-- | paramiko/file.py | 16 | ||||
-rw-r--r-- | paramiko/hostkeys.py | 4 | ||||
-rw-r--r-- | paramiko/pkey.py | 4 | ||||
-rw-r--r-- | paramiko/rsakey.py | 2 | ||||
-rw-r--r-- | paramiko/sftp_attr.py | 7 | ||||
-rw-r--r-- | paramiko/sftp_client.py | 4 | ||||
-rw-r--r-- | paramiko/sftp_server.py | 4 | ||||
-rw-r--r-- | paramiko/transport.py | 94 | ||||
-rw-r--r-- | paramiko/util.py | 53 | ||||
-rw-r--r-- | paramiko/win_pageant.py | 5 | ||||
-rw-r--r-- | sites/www/changelog.rst | 32 | ||||
-rw-r--r-- | tests/test_auth.py | 4 | ||||
-rw-r--r-- | tests/test_client.py | 8 | ||||
-rwxr-xr-x | tests/test_file.py | 6 | ||||
-rw-r--r-- | tests/test_gssapi.py | 4 | ||||
-rw-r--r-- | tests/test_kex_gss.py | 6 | ||||
-rwxr-xr-x | tests/test_sftp.py | 7 | ||||
-rw-r--r-- | tests/test_ssh_gss.py | 6 | ||||
-rw-r--r-- | tests/test_transport.py | 28 | ||||
-rw-r--r-- | tests/test_util.py | 35 |
31 files changed, 258 insertions, 158 deletions
diff --git a/.travis.yml b/.travis.yml index 0bda3da9..80ecb21d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: python +sudo: false python: - "2.6" - "2.7" diff --git a/demos/demo_server.py b/demos/demo_server.py index 5b3d5164..c4af9b10 100644 --- a/demos/demo_server.py +++ b/demos/demo_server.py @@ -159,7 +159,7 @@ try: print('Authenticated!') server.event.wait(10) - if not server.event.isSet(): + if not server.event.is_set(): print('*** Client never asked for a shell.') sys.exit(1) diff --git a/paramiko/_winapi.py b/paramiko/_winapi.py index 0d55d291..f48e1890 100644 --- a/paramiko/_winapi.py +++ b/paramiko/_winapi.py @@ -213,12 +213,14 @@ class SECURITY_ATTRIBUTES(ctypes.Structure): super(SECURITY_ATTRIBUTES, self).__init__(*args, **kwargs) self.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES) - def _get_descriptor(self): + @property + def descriptor(self): return self._descriptor - def _set_descriptor(self, descriptor): + + @descriptor.setter + def descriptor(self, value): self._descriptor = descriptor self.lpSecurityDescriptor = ctypes.addressof(descriptor) - descriptor = property(_get_descriptor, _set_descriptor) def GetTokenInformation(token, information_class): """ diff --git a/paramiko/agent.py b/paramiko/agent.py index 4f463449..a75ac59e 100644 --- a/paramiko/agent.py +++ b/paramiko/agent.py @@ -73,7 +73,8 @@ class AgentSSH(object): self._keys = tuple(keys) def _close(self): - #self._conn.close() + if self._conn is not None: + self._conn.close() self._conn = None self._keys = () diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py index b5fea654..c001aeee 100644 --- a/paramiko/auth_handler.py +++ b/paramiko/auth_handler.py @@ -195,7 +195,7 @@ class AuthHandler (object): if (e is None) or issubclass(e.__class__, EOFError): e = AuthenticationException('Authentication failed.') raise e - if event.isSet(): + if event.is_set(): break if not self.is_authenticated(): e = self.transport.get_exception() diff --git a/paramiko/ber.py b/paramiko/ber.py index 05152303..a388df07 100644 --- a/paramiko/ber.py +++ b/paramiko/ber.py @@ -45,7 +45,7 @@ class BER(object): def decode(self): return self.decode_next() - + def decode_next(self): if self.idx >= len(self.content): return None @@ -89,6 +89,7 @@ class BER(object): # 1: boolean (00 false, otherwise true) raise BERException('Unknown ber encoding type %d (robey is lazy)' % ident) + @staticmethod def decode_sequence(data): out = [] ber = BER(data) @@ -98,7 +99,6 @@ class BER(object): break out.append(x) return out - decode_sequence = staticmethod(decode_sequence) def encode_tlv(self, ident, val): # no need to support ident > 31 here @@ -125,9 +125,9 @@ class BER(object): else: raise BERException('Unknown type for encoding: %s' % repr(type(x))) + @staticmethod def encode_sequence(data): ber = BER() for item in data: ber.encode(item) return ber.asbytes() - encode_sequence = staticmethod(encode_sequence) diff --git a/paramiko/channel.py b/paramiko/channel.py index 9de278cb..8a97c974 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -290,7 +290,7 @@ class Channel (ClosingContextManager): .. versionadded:: 1.7.3 """ - return self.closed or self.status_event.isSet() + return self.closed or self.status_event.is_set() def recv_exit_status(self): """ @@ -305,7 +305,7 @@ class Channel (ClosingContextManager): .. versionadded:: 1.2 """ self.status_event.wait() - assert self.status_event.isSet() + assert self.status_event.is_set() return self.exit_status def send_exit_status(self, status): @@ -890,7 +890,7 @@ class Channel (ClosingContextManager): self.out_max_packet_size = self.transport. \ _sanitize_packet_size(max_packet_size) self.active = 1 - self._log(DEBUG, 'Max packet out: %d bytes' % max_packet_size) + self._log(DEBUG, 'Max packet out: %d bytes' % self.out_max_packet_size) def _request_success(self, m): self._log(DEBUG, 'Sesch channel %d request ok' % self.chanid) @@ -1077,7 +1077,7 @@ class Channel (ClosingContextManager): def _wait_for_event(self): self.event.wait() - assert self.event.isSet() + assert self.event.is_set() if self.event_ready: return e = self.transport.get_exception() diff --git a/paramiko/common.py b/paramiko/common.py index 97b2f958..0b0cc2a7 100644 --- a/paramiko/common.py +++ b/paramiko/common.py @@ -195,7 +195,11 @@ DEFAULT_MAX_PACKET_SIZE = 2 ** 15 # lower bound on the max packet size we'll accept from the remote host # Minimum packet size is 32768 bytes according to # http://www.ietf.org/rfc/rfc4254.txt -MIN_PACKET_SIZE = 2 ** 15 +MIN_WINDOW_SIZE = 2 ** 15 + +# However, according to http://www.ietf.org/rfc/rfc4253.txt it is perfectly +# legal to accept a size much smaller, as OpenSSH client does as size 16384. +MIN_PACKET_SIZE = 2 ** 12 # Max windows size according to http://www.ietf.org/rfc/rfc4254.txt MAX_WINDOW_SIZE = 2**32 -1 diff --git a/paramiko/config.py b/paramiko/config.py index 4972c27b..233a87d9 100644 --- a/paramiko/config.py +++ b/paramiko/config.py @@ -24,6 +24,7 @@ Configuration file (aka ``ssh_config``) support. import fnmatch import os import re +import shlex import socket SSH_PORT = 22 @@ -54,7 +55,6 @@ class SSHConfig (object): :param file file_obj: a file-like object to read the config file from """ - host = {"host": ['*'], "config": {}} for line in file_obj: line = line.rstrip('\r\n').lstrip() @@ -73,6 +73,9 @@ class SSHConfig (object): 'host': self._get_hosts(value), 'config': {} } + elif key == 'proxycommand' and value.lower() == 'none': + # Proxycommands of none should not be added as an actual value. (Issue #415) + continue else: if value.startswith('"') and value.endswith('"'): value = value[1:-1] @@ -93,13 +96,15 @@ class SSHConfig (object): """ Return a dict of config options for a given hostname. - The host-matching rules of OpenSSH's ``ssh_config`` man page are used, - which means that all configuration options from matching host - specifications are merged, with more specific hostmasks taking - precedence. In other words, if ``"Port"`` is set under ``"Host *"`` - and also ``"Host *.example.com"``, and the lookup is for - ``"ssh.example.com"``, then the port entry for ``"Host *.example.com"`` - will win out. + The host-matching rules of OpenSSH's ``ssh_config`` man page are used: + For each parameter, the first obtained value will be used. The + configuration files contain sections separated by ``Host'' + specifications, and that section is only applied for hosts that match + one of the patterns given in the specification. + + Since the first obtained value for each parameter is used, more host- + specific declarations should be given near the beginning of the file, + and general defaults at the end. The keys in the returned dict are all normalized to lowercase (look for ``"port"``, not ``"Port"``. The values are processed according to the @@ -220,25 +225,10 @@ class SSHConfig (object): """ Return a list of host_names from host value. """ - i, length = 0, len(host) - hosts = [] - while i < length: - if host[i] == '"': - end = host.find('"', i + 1) - if end < 0: - raise Exception("Unparsable host %s" % host) - hosts.append(host[i + 1:end]) - i = end + 1 - elif not host[i].isspace(): - end = i + 1 - while end < length and not host[end].isspace() and host[end] != '"': - end += 1 - hosts.append(host[i:end]) - i = end - else: - i += 1 - - return hosts + try: + return shlex.split(host) + except ValueError: + raise Exception("Unparsable host %s" % host) class LazyFqdn(object): diff --git a/paramiko/dsskey.py b/paramiko/dsskey.py index 496e8527..6ea29d9c 100644 --- a/paramiko/dsskey.py +++ b/paramiko/dsskey.py @@ -188,6 +188,7 @@ class DSSKey(PKey): def write_private_key(self, file_obj, password=None): self._write_private_key('DSA', file_obj, self._encode_key(), password) + @staticmethod def generate(bits=1024, progress_func=None): """ Generate a new private DSS key. This factory function can be used to @@ -208,7 +209,6 @@ class DSSKey(PKey): )) key.x = numbers.x return key - generate = staticmethod(generate) ### internals... diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py index 831fa003..c3d66c30 100644 --- a/paramiko/ecdsakey.py +++ b/paramiko/ecdsakey.py @@ -23,7 +23,6 @@ ECDSA keys import base64 import binascii import textwrap -from hashlib import sha256 from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend @@ -36,7 +35,7 @@ from paramiko.common import four_byte, one_byte from paramiko.dsskey import _DSSSigValue from paramiko.message import Message from paramiko.pkey import PKey -from paramiko.py3compat import byte_chr, u +from paramiko.py3compat import byte_chr from paramiko.ssh_exception import SSHException from paramiko.util import deflate_long, inflate_long @@ -157,6 +156,7 @@ class ECDSAKey(PKey): key = self.signing_key or self.verifying_key self._write_private_key('EC', file_obj, key.to_der(), password) + @staticmethod def generate(curve=ec.SECP256R1(), progress_func=None): """ Generate a new private RSA key. This factory function can be used to @@ -168,7 +168,6 @@ class ECDSAKey(PKey): signing_key = SigningKey.generate(curve) key = ECDSAKey(vals=(signing_key, signing_key.get_verifying_key())) return key - generate = staticmethod(generate) ### internals... diff --git a/paramiko/file.py b/paramiko/file.py index 311e1982..e3b0a16a 100644 --- a/paramiko/file.py +++ b/paramiko/file.py @@ -206,6 +206,7 @@ class BufferedFile (ClosingContextManager): if not (self._flags & self.FLAG_READ): raise IOError('File not open for reading') line = self._rbuffer + truncated = False while True: if self._at_trailing_cr and (self._flags & self.FLAG_UNIVERSAL_NEWLINE) and (len(line) > 0): # edge case: the newline may be '\r\n' and we may have read @@ -220,11 +221,11 @@ class BufferedFile (ClosingContextManager): # enough. if (size is not None) and (size >= 0): if len(line) >= size: - # truncate line and return + # truncate line self._rbuffer = line[size:] line = line[:size] - self._pos += len(line) - return line if self._flags & self.FLAG_BINARY else u(line) + truncated = True + break n = size - len(line) else: n = self._bufsize @@ -246,10 +247,17 @@ class BufferedFile (ClosingContextManager): rpos = line.find(cr_byte) if (rpos >= 0) and (rpos < pos or pos < 0): pos = rpos + if pos == -1: + # we couldn't find a newline in the truncated string, return it + self._pos += len(line) + return line if self._flags & self.FLAG_BINARY else u(line) xpos = pos + 1 if (line[pos] == cr_byte_value) and (xpos < len(line)) and (line[xpos] == linefeed_byte_value): xpos += 1 - self._rbuffer = line[xpos:] + # if the string was truncated, _rbuffer needs to have the string after + # the newline character plus the truncated part of the line we stored + # earlier in _rbuffer + self._rbuffer = line[xpos:] + self._rbuffer if truncated else line[xpos:] lf = line[pos:xpos] line = line[:pos] + linefeed_byte if (len(self._rbuffer) == 0) and (lf == cr_byte): diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index b94ff0db..84868875 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -255,6 +255,7 @@ class HostKeys (MutableMapping): ret.append(self.lookup(k)) return ret + @staticmethod def hash_host(hostname, salt=None): """ Return a "hashed" form of the hostname, as used by OpenSSH when storing @@ -274,7 +275,6 @@ class HostKeys (MutableMapping): hmac = HMAC(salt, b(hostname), sha1).digest() hostkey = '|1|%s|%s' % (u(encodebytes(salt)), u(encodebytes(hmac))) return hostkey.replace('\n', '') - hash_host = staticmethod(hash_host) class InvalidHostKey(Exception): @@ -294,6 +294,7 @@ class HostKeyEntry: self.hostnames = hostnames self.key = key + @classmethod def from_line(cls, line, lineno=None): """ Parses the given line of text to find the names for the host, @@ -336,7 +337,6 @@ class HostKeyEntry: raise InvalidHostKey(line, e) return cls(names, key) - from_line = classmethod(from_line) def to_line(self): """ diff --git a/paramiko/pkey.py b/paramiko/pkey.py index 90c45116..24fb0d60 100644 --- a/paramiko/pkey.py +++ b/paramiko/pkey.py @@ -171,6 +171,7 @@ class PKey(object): """ return False + @classmethod def from_private_key_file(cls, filename, password=None): """ Create a key object by reading a private key file. If the private @@ -192,8 +193,8 @@ class PKey(object): """ key = cls(filename=filename, password=password) return key - from_private_key_file = classmethod(from_private_key_file) + @classmethod def from_private_key(cls, file_obj, password=None): """ Create a key object by reading a private key from a file (or file-like) @@ -213,7 +214,6 @@ class PKey(object): """ key = cls(file_obj=file_obj, password=password) return key - from_private_key = classmethod(from_private_key) def write_private_key_file(self, filename, password=None): """ diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py index ef39c41f..aac57f91 100644 --- a/paramiko/rsakey.py +++ b/paramiko/rsakey.py @@ -155,6 +155,7 @@ class RSAKey(PKey): def write_private_key(self, file_obj, password=None): self._write_private_key('RSA', file_obj, self._encode_key(), password) + @staticmethod def generate(bits, progress_func=None): """ Generate a new private RSA key. This factory function can be used to @@ -175,7 +176,6 @@ class RSAKey(PKey): key.dmq1 = numbers.dmq1 key.iqmp = numbers.iqmp return key - generate = staticmethod(generate) ### internals... diff --git a/paramiko/sftp_attr.py b/paramiko/sftp_attr.py index d12eff8d..cf48f654 100644 --- a/paramiko/sftp_attr.py +++ b/paramiko/sftp_attr.py @@ -60,6 +60,7 @@ class SFTPAttributes (object): self.st_mtime = None self.attr = {} + @classmethod def from_stat(cls, obj, filename=None): """ Create an `.SFTPAttributes` object from an existing ``stat`` object (an @@ -79,13 +80,12 @@ class SFTPAttributes (object): if filename is not None: attr.filename = filename return attr - from_stat = classmethod(from_stat) def __repr__(self): return '<SFTPAttributes: %s>' % self._debug_str() ### internals... - + @classmethod def _from_msg(cls, msg, filename=None, longname=None): attr = cls() attr._unpack(msg) @@ -94,7 +94,6 @@ class SFTPAttributes (object): if longname is not None: attr.longname = longname return attr - _from_msg = classmethod(_from_msg) def _unpack(self, msg): self._flags = msg.get_int() @@ -159,6 +158,7 @@ class SFTPAttributes (object): out += ']' return out + @staticmethod def _rwx(n, suid, sticky=False): if suid: suid = 2 @@ -168,7 +168,6 @@ class SFTPAttributes (object): else: out += '-xSs'[suid + (n & 1)] return out - _rwx = staticmethod(_rwx) def __str__(self): """create a unix-style long description of the file (like ls -l)""" diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 62127cc2..89840eaa 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -65,7 +65,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): Used to open an SFTP session across an open SSH `.Transport` and perform remote file operations. - + Instances of this class may be used as context managers. """ def __init__(self, sock): @@ -101,6 +101,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): raise SSHException('EOF during negotiation') self._log(INFO, 'Opened sftp connection (server version %d)' % server_version) + @classmethod def from_transport(cls, t, window_size=None, max_packet_size=None): """ Create an SFTP client channel from an open `.Transport`. @@ -129,7 +130,6 @@ class SFTPClient(BaseSFTP, ClosingContextManager): return None chan.invoke_subsystem('sftp') return cls(chan) - from_transport = classmethod(from_transport) def _log(self, level, msg, *args): if isinstance(msg, list): diff --git a/paramiko/sftp_server.py b/paramiko/sftp_server.py index 2d8d1909..ce287e8f 100644 --- a/paramiko/sftp_server.py +++ b/paramiko/sftp_server.py @@ -129,6 +129,7 @@ class SFTPServer (BaseSFTP, SubsystemHandler): self.file_table = {} self.folder_table = {} + @staticmethod def convert_errno(e): """ Convert an errno value (as from an ``OSError`` or ``IOError``) into a @@ -146,8 +147,8 @@ class SFTPServer (BaseSFTP, SubsystemHandler): return SFTP_NO_SUCH_FILE else: return SFTP_FAILURE - convert_errno = staticmethod(convert_errno) + @staticmethod def set_file_attr(filename, attr): """ Change a file's attributes on the local filesystem. The contents of @@ -173,7 +174,6 @@ class SFTPServer (BaseSFTP, SubsystemHandler): if attr._flags & attr.FLAG_SIZE: with open(filename, 'w+') as f: f.truncate(attr.st_size) - set_file_attr = staticmethod(set_file_attr) ### internals... diff --git a/paramiko/transport.py b/paramiko/transport.py index b2599ac4..1ee73cdb 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -46,8 +46,8 @@ from paramiko.common import xffffffff, cMSG_CHANNEL_OPEN, cMSG_IGNORE, \ MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, MSG_CHANNEL_OPEN, \ MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE, MSG_CHANNEL_DATA, \ MSG_CHANNEL_EXTENDED_DATA, MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_REQUEST, \ - MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MIN_PACKET_SIZE, MAX_WINDOW_SIZE, \ - DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE + MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MIN_WINDOW_SIZE, MIN_PACKET_SIZE, \ + MAX_WINDOW_SIZE, DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE from paramiko.compress import ZlibCompressor, ZlibDecompressor from paramiko.dsskey import DSSKey from paramiko.kex_gex import KexGex @@ -86,7 +86,7 @@ class Transport (threading.Thread, ClosingContextManager): `channels <.Channel>`, across the session. Multiple channels can be multiplexed across a single session (and often are, in the case of port forwardings). - + Instances of this class may be used as context managers. """ _ENCRYPT = object() @@ -319,7 +319,7 @@ class Transport (threading.Thread, ClosingContextManager): self._channels = ChannelMap() self.channel_events = {} # (id -> Event) self.channels_seen = {} # (id -> True) - self._channel_counter = 1 + self._channel_counter = 0 self.default_max_packet_size = default_max_packet_size self.default_window_size = default_window_size self._forward_agent_handler = None @@ -447,7 +447,7 @@ class Transport (threading.Thread, ClosingContextManager): if e is not None: raise e raise SSHException('Negotiation failed.') - if event.isSet(): + if event.is_set(): break def start_server(self, event=None, server=None): @@ -512,7 +512,7 @@ class Transport (threading.Thread, ClosingContextManager): if e is not None: raise e raise SSHException('Negotiation failed.') - if event.isSet(): + if event.is_set(): break def add_server_key(self, key): @@ -550,6 +550,7 @@ class Transport (threading.Thread, ClosingContextManager): pass return None + @staticmethod def load_server_moduli(filename=None): """ (optional) @@ -589,7 +590,6 @@ class Transport (threading.Thread, ClosingContextManager): # none succeeded Transport._modulus_pack = None return False - load_server_moduli = staticmethod(load_server_moduli) def close(self): """ @@ -771,7 +771,7 @@ class Transport (threading.Thread, ClosingContextManager): if e is None: e = SSHException('Unable to open channel.') raise e - if event.isSet(): + if event.is_set(): break chan = self._channels.get(chanid) if chan is not None: @@ -891,7 +891,7 @@ class Transport (threading.Thread, ClosingContextManager): if e is not None: raise e raise SSHException('Negotiation failed.') - if self.completion_event.isSet(): + if self.completion_event.is_set(): break return @@ -942,7 +942,7 @@ class Transport (threading.Thread, ClosingContextManager): self.completion_event.wait(0.1) if not self.active: return None - if self.completion_event.isSet(): + if self.completion_event.is_set(): break return self.global_response @@ -1116,6 +1116,8 @@ class Transport (threading.Thread, ClosingContextManager): supplied, this method returns ``None``. :returns: server supplied banner (`str`), or ``None``. + + .. versionadded:: 1.13 """ if not self.active or (self.auth_handler is None): return None @@ -1501,7 +1503,7 @@ class Transport (threading.Thread, ClosingContextManager): self._log(DEBUG, 'Dropping user packet because connection is dead.') return self.clear_to_send_lock.acquire() - if self.clear_to_send.isSet(): + if self.clear_to_send.is_set(): break self.clear_to_send_lock.release() if time.time() > start + self.clear_to_send_timeout: @@ -1606,7 +1608,7 @@ class Transport (threading.Thread, ClosingContextManager): def _sanitize_window_size(self, window_size): if window_size is None: window_size = self.default_window_size - return clamp_value(MIN_PACKET_SIZE, window_size, MAX_WINDOW_SIZE) + return clamp_value(MIN_WINDOW_SIZE, window_size, MAX_WINDOW_SIZE) def _sanitize_packet_size(self, max_packet_size): if max_packet_size is None: @@ -2203,7 +2205,7 @@ class Transport (threading.Thread, ClosingContextManager): always_display = m.get_boolean() msg = m.get_string() lang = m.get_string() - self._log(DEBUG, 'Debug msg: ' + util.safe_string(msg)) + self._log(DEBUG, 'Debug msg: {0}'.format(util.safe_string(msg))) def _get_subsystem_handler(self, name): try: @@ -2261,21 +2263,6 @@ class SecurityOptions (object): """ return '<paramiko.SecurityOptions for %s>' % repr(self._transport) - def _get_ciphers(self): - return self._transport._preferred_ciphers - - def _get_digests(self): - return self._transport._preferred_macs - - def _get_key_types(self): - return self._transport._preferred_keys - - def _get_kex(self): - return self._transport._preferred_kex - - def _get_compression(self): - return self._transport._preferred_compression - def _set(self, name, orig, x): if type(x) is list: x = tuple(x) @@ -2287,30 +2274,51 @@ class SecurityOptions (object): raise ValueError('unknown cipher') setattr(self._transport, name, x) - def _set_ciphers(self, x): + @property + def ciphers(self): + """Symmetric encryption ciphers""" + return self._transport._preferred_ciphers + + @ciphers.setter + def ciphers(self, x): self._set('_preferred_ciphers', '_cipher_info', x) - def _set_digests(self, x): + @property + def digests(self): + """Digest (one-way hash) algorithms""" + return self._transport._preferred_macs + + @digests.setter + def digests(self, x): self._set('_preferred_macs', '_mac_info', x) - def _set_key_types(self, x): + @property + def key_types(self): + """Public-key algorithms""" + return self._transport._preferred_keys + + @key_types.setter + def key_types(self, x): self._set('_preferred_keys', '_key_info', x) - def _set_kex(self, x): + + @property + def kex(self): + """Key exchange algorithms""" + return self._transport._preferred_kex + + @kex.setter + def kex(self, x): self._set('_preferred_kex', '_kex_info', x) - def _set_compression(self, x): - self._set('_preferred_compression', '_compression_info', x) + @property + def compression(self): + """Compression algorithms""" + return self._transport._preferred_compression - ciphers = property(_get_ciphers, _set_ciphers, None, - "Symmetric encryption ciphers") - digests = property(_get_digests, _set_digests, None, - "Digest (one-way hash) algorithms") - key_types = property(_get_key_types, _set_key_types, None, - "Public-key algorithms") - kex = property(_get_kex, _set_kex, None, "Key exchange algorithms") - compression = property(_get_compression, _set_compression, None, - "Compression algorithms") + @compression.setter + def compression(self, x): + self._set('_preferred_compression', '_compression_info', x) class ChannelMap (object): diff --git a/paramiko/util.py b/paramiko/util.py index d90fd981..4d89ccf6 100644 --- a/paramiko/util.py +++ b/paramiko/util.py @@ -22,7 +22,7 @@ Useful functions used by the rest of paramiko. from __future__ import generators -from binascii import hexlify, unhexlify +import array import errno import sys import struct @@ -105,21 +105,14 @@ def format_binary_line(data): return '%-50s %s' % (left, right) -def hexify(s): - return hexlify(s).upper() - - -def unhexify(s): - return unhexlify(s) - - def safe_string(s): - out = '' + out = b('') for c in s: - if (byte_ord(c) >= 32) and (byte_ord(c) <= 127): - out += c + i = byte_ord(c) + if 32 <= i <= 127: + out += byte_chr(i) else: - out += '%%%02X' % byte_ord(c) + out += b('%%%02X' % i) return out @@ -280,6 +273,40 @@ def retry_on_signal(function): raise +<<<<<<< HEAD +======= +class Counter (object): + """Stateful counter for CTR mode crypto""" + def __init__(self, nbits, initial_value=long(1), overflow=long(0)): + self.blocksize = nbits / 8 + self.overflow = overflow + # start with value - 1 so we don't have to store intermediate values when counting + # could the iv be 0? + if initial_value == 0: + self.value = array.array('c', max_byte * self.blocksize) + else: + x = deflate_long(initial_value - 1, add_sign_padding=False) + self.value = array.array('c', zero_byte * (self.blocksize - len(x)) + x) + + def __call__(self): + """Increament the counter and return the new value""" + i = self.blocksize - 1 + while i > -1: + c = self.value[i] = byte_chr((byte_ord(self.value[i]) + 1) % 256) + if c != zero_byte: + return self.value.tostring() + i -= 1 + # counter reset + x = deflate_long(self.overflow, add_sign_padding=False) + self.value = array.array('c', zero_byte * (self.blocksize - len(x)) + x) + return self.value.tostring() + + @classmethod + def new(cls, nbits, initial_value=long(1), overflow=long(0)): + return cls(nbits, initial_value=initial_value, overflow=overflow) + + +>>>>>>> master def constant_time_bytes_eq(a, b): if len(a) != len(b): return False diff --git a/paramiko/win_pageant.py b/paramiko/win_pageant.py index 20b1b0b9..4b482bee 100644 --- a/paramiko/win_pageant.py +++ b/paramiko/win_pageant.py @@ -26,6 +26,7 @@ import ctypes.wintypes import platform import struct from paramiko.util import * +from paramiko.py3compat import b try: import _thread as thread # Python 3.x @@ -43,7 +44,7 @@ win32con_WM_COPYDATA = 74 def _get_pageant_window_object(): - return ctypes.windll.user32.FindWindowA('Pageant', 'Pageant') + return ctypes.windll.user32.FindWindowA(b('Pageant'), b('Pageant')) def can_talk_to_agent(): @@ -90,7 +91,7 @@ def _query_pageant(msg): with pymap: pymap.write(msg) # Create an array buffer containing the mapped filename - char_buffer = array.array("c", b(map_name) + zero_byte) + char_buffer = array.array("b", b(map_name) + zero_byte) char_buffer_address, char_buffer_size = char_buffer.buffer_info() # Create a string to use for the SendMessage function call cds = COPYDATASTRUCT(_AGENT_COPYDATA_ID, char_buffer_size, diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 374e04b3..4e56ad1f 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,38 @@ Changelog ========= +* :bug:`455` Tweak packet size handling to conform better to the OpenSSH RFCs; + this helps address issues with interactive program cursors. Courtesy of Jeff + Quast. +* :bug:`428` Fix an issue in `~paramiko.file.BufferedFile` (primarily used in + the SFTP modules) concerning incorrect behavior by + `~paramiko.file.BufferedFile.readlines` on files whose size exceeds the + buffer size. Thanks to ``@achapp`` for catch & patch. +* :bug:`415` Fix ``ssh_config`` parsing to correctly interpret ``ProxyCommand + none`` as the lack of a proxy command, instead of as a literal command string + of ``"none"``. Thanks to Richard Spiers for the catch & Sean Johnson for the + fix. +* :support:`431` Replace handrolled ``ssh_config`` parsing code with use of the + ``shlex`` module. Thanks to Yan Kalchevskiy. +* :support:`422` Clean up some unused imports. Courtesy of Olle Lundberg. +* :support:`421` Modernize threading calls to user newer API. Thanks to Olle + Lundberg. +* :support:`419` Modernize a bunch of the codebase internals to leverage + decorators. Props to ``@beckjake`` for realizing we're no longer on Python + 2.2 :D +* :bug:`266` Change numbering of `~paramiko.transport.Transport` channels to + start at 0 instead of 1 for better compatibility with OpenSSH & certain + server implementations which break on 1-indexed channels. Thanks to + ``@egroeper`` for catch & patch. +* :bug:`459` Tighten up agent connection closure behavior to avoid spurious + ``ResourceWarning`` display in some situations. Thanks to ``@tkrapp`` for the + catch. +* :bug:`429` Server-level debug message logging was overlooked during the + Python 3 compatibility update; Python 3 clients attempting to log SSH debug + packets encountered type errors. This is now fixed. Thanks to ``@mjmaenpaa`` + for the catch. +* :bug:`320` Update our win_pageant module to be Python 3 compatible. Thanks to +``@sherbang`` and ``@adamkerz`` for the patches. * :release:`1.15.1 <2014-09-22>` * :bug:`399` SSH agent forwarding (potentially other functionality as well) would hang due to incorrect values passed into the new window size diff --git a/tests/test_auth.py b/tests/test_auth.py index 1d972d53..ec78e3ce 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -118,12 +118,12 @@ class AuthTest (unittest.TestCase): self.ts.add_server_key(host_key) self.event = threading.Event() self.server = NullServer() - self.assertTrue(not self.event.isSet()) + self.assertTrue(not self.event.is_set()) self.ts.start_server(self.event, self.server) def verify_finished(self): self.event.wait(1.0) - self.assertTrue(self.event.isSet()) + self.assertTrue(self.event.is_set()) self.assertTrue(self.ts.is_active()) def test_1_bad_auth_type(self): diff --git a/tests/test_client.py b/tests/test_client.py index 1978004e..cef7b28d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -131,7 +131,7 @@ class SSHClientTest (unittest.TestCase): # Authentication successful? self.event.wait(1.0) - self.assertTrue(self.event.isSet()) + self.assertTrue(self.event.is_set()) self.assertTrue(self.ts.is_active()) self.assertEqual('slowdive', self.ts.get_username()) self.assertEqual(True, self.ts.is_authenticated()) @@ -229,7 +229,7 @@ class SSHClientTest (unittest.TestCase): 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()) self.assertEqual('slowdive', self.ts.get_username()) self.assertEqual(True, self.ts.is_authenticated()) @@ -283,7 +283,7 @@ class SSHClientTest (unittest.TestCase): 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()) p = weakref.ref(self.tc._transport.packetizer) @@ -312,7 +312,7 @@ class SSHClientTest (unittest.TestCase): 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()) self.assertTrue(self.tc._transport is not None) diff --git a/tests/test_file.py b/tests/test_file.py index 22a34aca..a6ff69e9 100755 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -70,13 +70,17 @@ class BufferedFileTest (unittest.TestCase): def test_2_readline(self): f = LoopbackFile('r+U') - f.write(b'First line.\nSecond line.\r\nThird line.\nFinal line non-terminated.') + f.write(b'First line.\nSecond line.\r\nThird line.\n' + + b'Fourth line.\nFinal line non-terminated.') + self.assertEqual(f.readline(), 'First line.\n') # universal newline mode should convert this linefeed: self.assertEqual(f.readline(), 'Second line.\n') # truncated line: self.assertEqual(f.readline(7), 'Third l') self.assertEqual(f.readline(), 'ine.\n') + # newline should be detected and only the fourth line returned + self.assertEqual(f.readline(39), 'Fourth line.\n') self.assertEqual(f.readline(), 'Final line non-terminated.') self.assertEqual(f.readline(), '') f.close() diff --git a/tests/test_gssapi.py b/tests/test_gssapi.py index a328dd65..96c268d9 100644 --- a/tests/test_gssapi.py +++ b/tests/test_gssapi.py @@ -27,15 +27,13 @@ import socket class GSSAPITest(unittest.TestCase): - + @staticmethod def init(hostname=None, srv_mode=False): global krb5_mech, targ_name, server_mode krb5_mech = "1.2.840.113554.1.2.2" targ_name = hostname server_mode = srv_mode - init = staticmethod(init) - def test_1_pyasn1(self): """ Test the used methods of pyasn1. diff --git a/tests/test_kex_gss.py b/tests/test_kex_gss.py index 8769d09c..3bf788da 100644 --- a/tests/test_kex_gss.py +++ b/tests/test_kex_gss.py @@ -58,14 +58,12 @@ class NullServer (paramiko.ServerInterface): class GSSKexTest(unittest.TestCase): - + @staticmethod def init(username, hostname): global krb5_principal, targ_name krb5_principal = username targ_name = hostname - init = staticmethod(init) - def setUp(self): self.username = krb5_principal self.hostname = socket.getfqdn(targ_name) @@ -111,7 +109,7 @@ class GSSKexTest(unittest.TestCase): gss_auth=True, gss_kex=True) self.event.wait(1.0) - self.assert_(self.event.isSet()) + self.assert_(self.event.is_set()) self.assert_(self.ts.is_active()) self.assertEquals(self.username, self.ts.get_username()) self.assertEquals(True, self.ts.is_authenticated()) diff --git a/tests/test_sftp.py b/tests/test_sftp.py index 72c7ba03..cb8f7f84 100755 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -97,7 +97,7 @@ def get_sftp(): class SFTPTest (unittest.TestCase): - + @staticmethod def init(hostname, username, keyfile, passwd): global sftp, tc @@ -129,8 +129,8 @@ class SFTPTest (unittest.TestCase): sys.stderr.write('\n') sys.exit(1) sftp = paramiko.SFTP.from_transport(t) - init = staticmethod(init) + @staticmethod def init_loopback(): global sftp, tc @@ -150,12 +150,11 @@ class SFTPTest (unittest.TestCase): event.wait(1.0) sftp = paramiko.SFTP.from_transport(tc) - init_loopback = staticmethod(init_loopback) + @staticmethod def set_big_file_test(onoff): global g_big_file_test g_big_file_test = onoff - set_big_file_test = staticmethod(set_big_file_test) def setUp(self): global FOLDER diff --git a/tests/test_ssh_gss.py b/tests/test_ssh_gss.py index 595081b8..e20d348f 100644 --- a/tests/test_ssh_gss.py +++ b/tests/test_ssh_gss.py @@ -57,14 +57,12 @@ class NullServer (paramiko.ServerInterface): class GSSAuthTest(unittest.TestCase): - + @staticmethod def init(username, hostname): global krb5_principal, targ_name krb5_principal = username targ_name = hostname - init = staticmethod(init) - def setUp(self): self.username = krb5_principal self.hostname = socket.getfqdn(targ_name) @@ -104,7 +102,7 @@ class GSSAuthTest(unittest.TestCase): gss_auth=True) self.event.wait(1.0) - self.assert_(self.event.isSet()) + self.assert_(self.event.is_set()) self.assert_(self.ts.is_active()) self.assertEquals(self.username, self.ts.get_username()) self.assertEquals(True, self.ts.is_authenticated()) diff --git a/tests/test_transport.py b/tests/test_transport.py index 50b1d86b..5cf9a867 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -35,7 +35,7 @@ from paramiko import Transport, SecurityOptions, ServerInterface, RSAKey, DSSKey from paramiko import AUTH_FAILED, AUTH_SUCCESSFUL from paramiko import OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED from paramiko.common import MSG_KEXINIT, cMSG_CHANNEL_WINDOW_ADJUST, \ - MIN_PACKET_SIZE, MAX_WINDOW_SIZE, \ + MIN_PACKET_SIZE, MIN_WINDOW_SIZE, MAX_WINDOW_SIZE, \ DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE from paramiko.py3compat import bytes from paramiko.message import Message @@ -136,12 +136,12 @@ class TransportTest(unittest.TestCase): event = threading.Event() self.server = NullServer() - self.assertTrue(not event.isSet()) + self.assertTrue(not event.is_set()) self.ts.start_server(event, self.server) self.tc.connect(hostkey=public_host_key, username='slowdive', password='pygmalion') event.wait(1.0) - self.assertTrue(event.isSet()) + self.assertTrue(event.is_set()) self.assertTrue(self.ts.is_active()) def test_1_security_options(self): @@ -180,7 +180,7 @@ class TransportTest(unittest.TestCase): self.ts.add_server_key(host_key) event = threading.Event() server = NullServer() - self.assertTrue(not event.isSet()) + self.assertTrue(not event.is_set()) self.assertEqual(None, self.tc.get_username()) self.assertEqual(None, self.ts.get_username()) self.assertEqual(False, self.tc.is_authenticated()) @@ -189,7 +189,7 @@ class TransportTest(unittest.TestCase): self.tc.connect(hostkey=public_host_key, username='slowdive', password='pygmalion') event.wait(1.0) - self.assertTrue(event.isSet()) + self.assertTrue(event.is_set()) self.assertTrue(self.ts.is_active()) self.assertEqual('slowdive', self.tc.get_username()) self.assertEqual('slowdive', self.ts.get_username()) @@ -205,13 +205,13 @@ class TransportTest(unittest.TestCase): self.ts.add_server_key(host_key) event = threading.Event() server = NullServer() - self.assertTrue(not event.isSet()) + self.assertTrue(not event.is_set()) self.socks.send(LONG_BANNER) self.ts.start_server(event, server) self.tc.connect(hostkey=public_host_key, username='slowdive', password='pygmalion') event.wait(1.0) - self.assertTrue(event.isSet()) + self.assertTrue(event.is_set()) self.assertTrue(self.ts.is_active()) def test_4_special(self): @@ -680,7 +680,7 @@ class TransportTest(unittest.TestCase): def run(self): try: for i in range(1, 1+self.iterations): - if self.done_event.isSet(): + if self.done_event.is_set(): break self.watchdog_event.set() #print i, "SEND" @@ -699,7 +699,7 @@ class TransportTest(unittest.TestCase): def run(self): try: - while not self.done_event.isSet(): + while not self.done_event.is_set(): if self.chan.recv_ready(): chan.recv(65536) self.watchdog_event.set() @@ -753,12 +753,12 @@ class TransportTest(unittest.TestCase): # Act as a watchdog timer, checking deadlocked = False - while not deadlocked and not done_event.isSet(): + while not deadlocked and not done_event.is_set(): for event in (st.watchdog_event, rt.watchdog_event): event.wait(timeout) - if done_event.isSet(): + if done_event.is_set(): break - if not event.isSet(): + if not event.is_set(): deadlocked = True break event.clear() @@ -779,7 +779,7 @@ class TransportTest(unittest.TestCase): """ verify that we conform to the rfc of packet and window sizes. """ - for val, correct in [(32767, MIN_PACKET_SIZE), + for val, correct in [(4095, MIN_PACKET_SIZE), (None, DEFAULT_MAX_PACKET_SIZE), (2**32, MAX_WINDOW_SIZE)]: self.assertEqual(self.tc._sanitize_packet_size(val), correct) @@ -788,7 +788,7 @@ class TransportTest(unittest.TestCase): """ verify that we conform to the rfc of packet and window sizes. """ - for val, correct in [(32767, MIN_PACKET_SIZE), + for val, correct in [(32767, MIN_WINDOW_SIZE), (None, DEFAULT_WINDOW_SIZE), (2**32, MAX_WINDOW_SIZE)]: self.assertEqual(self.tc._sanitize_window_size(val), correct) diff --git a/tests/test_util.py b/tests/test_util.py index f961fbbc..bfdc525e 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -27,8 +27,8 @@ from hashlib import sha1 import unittest import paramiko.util -from paramiko.util import lookup_ssh_host_config as host_config -from paramiko.py3compat import StringIO, byte_ord +from paramiko.util import lookup_ssh_host_config as host_config, safe_string +from paramiko.py3compat import StringIO, byte_ord, b test_config_file = """\ Host * @@ -453,3 +453,34 @@ Host param3 parara ) for host in incorrect_data: self.assertRaises(Exception, conf._get_hosts, host) + + def test_safe_string(self): + vanilla = b("vanilla") + has_bytes = b("has \7\3 bytes") + safe_vanilla = safe_string(vanilla) + safe_has_bytes = safe_string(has_bytes) + expected_bytes = b("has %07%03 bytes") + err = "{0!r} != {1!r}" + assert safe_vanilla == vanilla, err.format(safe_vanilla, vanilla) + assert safe_has_bytes == expected_bytes, \ + err.format(safe_has_bytes, expected_bytes) + + def test_proxycommand_none_issue_418(self): + test_config_file = """ +Host proxycommand-standard-none + ProxyCommand None + +Host proxycommand-with-equals-none + ProxyCommand=None + """ + for host, values in { + 'proxycommand-standard-none': {'hostname': 'proxycommand-standard-none'}, + 'proxycommand-with-equals-none': {'hostname': 'proxycommand-with-equals-none'} + }.items(): + + f = StringIO(test_config_file) + config = paramiko.util.parse_ssh_config(f) + self.assertEqual( + paramiko.util.lookup_ssh_host_config(host, config), + values + ) |