aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy T. Bouse <jbouse@debian.org>2015-03-12 21:35:58 -0400
committerJeremy T. Bouse <jbouse@debian.org>2015-03-12 21:35:58 -0400
commitf784a533d6e1d09e89dc254f3493b491e19c94f0 (patch)
tree6079fb034538b346b999a6fefcae4b1c557713c5
parent941814e1efaf9a46992476e50badcecbcbfc9a41 (diff)
downloadpython-paramiko-f784a533d6e1d09e89dc254f3493b491e19c94f0.tar
python-paramiko-f784a533d6e1d09e89dc254f3493b491e19c94f0.tar.gz
Imported Upstream version 1.15.2
-rw-r--r--.travis.yml1
-rw-r--r--demos/demo_server.py2
-rw-r--r--paramiko/_version.py2
-rw-r--r--paramiko/_winapi.py8
-rw-r--r--paramiko/agent.py3
-rw-r--r--paramiko/auth_handler.py2
-rw-r--r--paramiko/ber.py6
-rw-r--r--paramiko/channel.py8
-rw-r--r--paramiko/common.py6
-rw-r--r--paramiko/config.py54
-rw-r--r--paramiko/dsskey.py2
-rw-r--r--paramiko/ecdsakey.py2
-rw-r--r--paramiko/file.py16
-rw-r--r--paramiko/hostkeys.py4
-rw-r--r--paramiko/pkey.py4
-rw-r--r--paramiko/proxy.py19
-rw-r--r--paramiko/rsakey.py2
-rw-r--r--paramiko/sftp_attr.py7
-rw-r--r--paramiko/sftp_client.py4
-rw-r--r--paramiko/sftp_server.py4
-rw-r--r--paramiko/transport.py94
-rw-r--r--paramiko/util.py20
-rw-r--r--paramiko/win_pageant.py5
-rw-r--r--sites/www/changelog.rst47
-rw-r--r--tests/test_auth.py4
-rw-r--r--tests/test_client.py8
-rwxr-xr-xtests/test_file.py6
-rw-r--r--tests/test_gssapi.py4
-rw-r--r--tests/test_kex_gss.py6
-rwxr-xr-xtests/test_sftp.py7
-rw-r--r--tests/test_ssh_gss.py6
-rw-r--r--tests/test_transport.py28
-rw-r--r--tests/test_util.py40
-rw-r--r--tox.ini2
34 files changed, 265 insertions, 168 deletions
diff --git a/.travis.yml b/.travis.yml
index a9a04c8..9a55dbb 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 5b3d516..c4af9b1 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/_version.py b/paramiko/_version.py
index d9f7874..3bf9dac 100644
--- a/paramiko/_version.py
+++ b/paramiko/_version.py
@@ -1,2 +1,2 @@
-__version_info__ = (1, 15, 1)
+__version_info__ = (1, 15, 2)
__version__ = '.'.join(map(str, __version_info__))
diff --git a/paramiko/_winapi.py b/paramiko/_winapi.py
index 0d55d29..f48e189 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 4f46344..a75ac59 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 b5fea65..c001aee 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 0515230..a388df0 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 9de278c..8a97c97 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 97b2f95..0b0cc2a 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 20ca4aa..233a87d 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
@@ -126,6 +131,16 @@ class SSHConfig (object):
ret = self._expand_variables(ret, hostname)
return ret
+ def get_hostnames(self):
+ """
+ Return the set of literal hostnames defined in the SSH config (both
+ explicit hostnames and wildcard entries).
+ """
+ hosts = set()
+ for entry in self._config:
+ hosts.update(entry['host'])
+ return hosts
+
def _allowed(self, hosts, hostname):
match = False
for host in hosts:
@@ -210,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 1901596..d7dd627 100644
--- a/paramiko/dsskey.py
+++ b/paramiko/dsskey.py
@@ -154,6 +154,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
@@ -169,7 +170,6 @@ class DSSKey (PKey):
key = DSSKey(vals=(dsa.p, dsa.q, dsa.g, dsa.y))
key.x = dsa.x
return key
- generate = staticmethod(generate)
### internals...
diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py
index a7f3c5e..6b04795 100644
--- a/paramiko/ecdsakey.py
+++ b/paramiko/ecdsakey.py
@@ -126,6 +126,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=curves.NIST256p, progress_func=None):
"""
Generate a new private RSA key. This factory function can be used to
@@ -139,7 +140,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 311e198..e3b0a16 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 b94ff0d..8486887 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 2daf372..1b4af01 100644
--- a/paramiko/pkey.py
+++ b/paramiko/pkey.py
@@ -160,6 +160,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
@@ -181,8 +182,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)
@@ -202,7 +203,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/proxy.py b/paramiko/proxy.py
index 0664ac6..ca602c4 100644
--- a/paramiko/proxy.py
+++ b/paramiko/proxy.py
@@ -24,6 +24,7 @@ import signal
from subprocess import Popen, PIPE
from select import select
import socket
+import time
from paramiko.ssh_exception import ProxyCommandFailure
from paramiko.util import ClosingContextManager
@@ -79,20 +80,24 @@ class ProxyCommand(ClosingContextManager):
:return: the length of the read content, as an `int`
"""
try:
- start = datetime.now()
+ start = time.time()
while len(self.buffer) < size:
+ select_timeout = None
if self.timeout is not None:
- elapsed = (datetime.now() - start).microseconds
- timeout = self.timeout * 1000 * 1000 # to microseconds
- if elapsed >= timeout:
+ elapsed = (time.time() - start)
+ if elapsed >= self.timeout:
raise socket.timeout()
- r, w, x = select([self.process.stdout], [], [], 0.0)
+ select_timeout = self.timeout - elapsed
+
+ r, w, x = select(
+ [self.process.stdout], [], [], select_timeout)
if r and r[0] == self.process.stdout:
- b = os.read(self.process.stdout.fileno(), 1)
+ b = os.read(
+ self.process.stdout.fileno(), size - len(self.buffer))
# Store in class-level buffer for persistence across
# timeouts; this makes us act more like a real socket
# (where timeouts don't actually drop data.)
- self.buffer.append(b)
+ self.buffer.extend(b)
result = ''.join(self.buffer)
self.buffer = []
return result
diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py
index d1f3ecf..4ebd835 100644
--- a/paramiko/rsakey.py
+++ b/paramiko/rsakey.py
@@ -131,6 +131,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
@@ -148,7 +149,6 @@ class RSAKey (PKey):
key.p = rsa.p
key.q = rsa.q
return key
- generate = staticmethod(generate)
### internals...
diff --git a/paramiko/sftp_attr.py b/paramiko/sftp_attr.py
index d12eff8..cf48f65 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 62127cc..89840ea 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 2d8d190..ce287e8 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 bf30c78..36da304 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -43,8 +43,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
@@ -88,7 +88,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.
"""
_PROTO_ID = '2.0'
@@ -277,7 +277,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
@@ -405,7 +405,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):
@@ -470,7 +470,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):
@@ -508,6 +508,7 @@ class Transport (threading.Thread, ClosingContextManager):
pass
return None
+ @staticmethod
def load_server_moduli(filename=None):
"""
(optional)
@@ -547,7 +548,6 @@ class Transport (threading.Thread, ClosingContextManager):
# none succeeded
Transport._modulus_pack = None
return False
- load_server_moduli = staticmethod(load_server_moduli)
def close(self):
"""
@@ -729,7 +729,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:
@@ -849,7 +849,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
@@ -900,7 +900,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
@@ -1074,6 +1074,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
@@ -1459,7 +1461,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:
@@ -1552,7 +1554,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:
@@ -2149,7 +2151,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:
@@ -2207,21 +2209,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)
@@ -2233,30 +2220,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 88ca2bc..d9a29d7 100644
--- a/paramiko/util.py
+++ b/paramiko/util.py
@@ -23,7 +23,6 @@ Useful functions used by the rest of paramiko.
from __future__ import generators
import array
-from binascii import hexlify, unhexlify
import errno
import sys
import struct
@@ -106,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
@@ -307,9 +299,9 @@ class Counter (object):
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)
- new = classmethod(new)
def constant_time_bytes_eq(a, b):
diff --git a/paramiko/win_pageant.py b/paramiko/win_pageant.py
index 20b1b0b..4b482be 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 3e654f6..bb93f88 100644
--- a/sites/www/changelog.rst
+++ b/sites/www/changelog.rst
@@ -2,11 +2,54 @@
Changelog
=========
+* :release:`1.15.2 <2014-12-19>`
+* :release:`1.14.2 <2014-12-19>`
+* :release:`1.13.3 <2014-12-19>`
+* :bug:`413` (also :issue:`414`, :issue:`420`, :issue:`454`) Be significantly
+ smarter about polling & timing behavior when running proxy commands, to avoid
+ unnecessary (often 100%!) CPU usage. Major thanks to Jason Dunsmore for
+ report & initial patchset and to Chris Adams & John Morrissey for followup
+ improvements.
+* :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 backported` Replace handrolled ``ssh_config`` parsing code with
+ use of the ``shlex`` module. Thanks to Yan Kalchevskiy.
+* :support:`422 backported` Clean up some unused imports. Courtesy of Olle
+ Lundberg.
+* :support:`421 backported` Modernize threading calls to user newer API. Thanks
+ to Olle Lundberg.
+* :support:`419 backported` 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
arguments for `.Transport` (thanks to a botched merge). This has been
corrected. Thanks to Dylan Thacker-Smith for the report & patch.
+* :feature:`167` Add `.SSHConfig.get_hostnames` for easier introspection of a
+ loaded SSH config file or object. Courtesy of Søren Løvborg.
* :release:`1.15.0 <2014-09-18>`
* :support:`393` Replace internal use of PyCrypto's ``SHA.new`` with the
stdlib's ``hashlib.sha1``. Thanks to Alex Gaynor.
@@ -60,8 +103,8 @@ Changelog
async/generator based file listings. Thanks to John Begeman.
* :support:`378 backported` Minor code cleanup in the SSH config module
courtesy of Olle Lundberg.
-* :support:`249` Consolidate version information into one spot. Thanks to Gabi
- Davar for the reminder.
+* :support:`249 backported` Consolidate version information into one spot.
+ Thanks to Gabi Davar for the reminder.
* :release:`1.14.1 <2014-08-25>`
* :release:`1.13.2 <2014-08-25>`
* :bug:`376` Be less aggressive about expanding variables in ``ssh_config``
diff --git a/tests/test_auth.py b/tests/test_auth.py
index 1d972d5..ec78e3c 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 28d1cb4..3d2e75c 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -128,7 +128,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())
@@ -226,7 +226,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())
@@ -281,7 +281,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)
@@ -316,7 +316,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 22a34ac..a6ff69e 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 a328dd6..96c268d 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 8769d09..3bf788d 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 72c7ba0..cb8f7f8 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 595081b..e20d348 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 50b1d86..5cf9a86 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 35e1576..bfdc525 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 *
@@ -349,6 +349,11 @@ IdentityFile something_%l_using_fqdn
config.parse(config_file)
self.assertEqual(config.lookup("abcqwerty")["hostname"], "127.0.0.1")
+ def test_14_get_hostnames(self):
+ f = StringIO(test_config_file)
+ config = paramiko.util.parse_ssh_config(f)
+ self.assertEqual(config.get_hostnames(), set(['*', '*.example.com', 'spoo.example.com']))
+
def test_quoted_host_names(self):
test_config_file = """\
Host "param pam" param "pam"
@@ -448,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
+ )
diff --git a/tox.ini b/tox.ini
index 83704df..7d4fcf8 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,5 +2,5 @@
envlist = py26,py27,py32,py33,py34
[testenv]
-commands = pip install --use-mirrors -q -r tox-requirements.txt
+commands = pip install -q -r tox-requirements.txt
python test.py