Merge branch '1.11' into 156-int

This commit is contained in:
Jeff Forcier 2013-09-27 17:30:34 -07:00
commit cba4c68365
17 changed files with 153 additions and 50 deletions

45
NEWS
View File

@ -12,8 +12,37 @@ Issues noted as "Fabric #NN" can be found at https://github.com/fabric/fabric/.
Releases
========
v1.11.0 (DD MM YYYY)
--------------------
v1.10.4 (27th Sep 2013)
-----------------------
* #179: Fix a missing variable causing errors when an ssh_config file has a
non-default AddressFamily set. Thanks to Ed Marshall & Tomaz Muraus for catch
& patch.
v1.11.1 (20th Sep 2013)
-----------------------
* #162: Clean up HMAC module import to avoid deadlocks in certain uses of
SSHClient. Thanks to Gernot Hillier for the catch & suggested
fix.
* #36: Fix the port-forwarding demo to avoid file descriptor errors. Thanks to
Jonathan Halcrow for catch & patch.
* #168: Update config handling to properly handle multiple 'localforward' and
'remoteforward' keys. Thanks to Emre Yılmaz for the patch.
v1.10.3 (20th Sep 2013)
-----------------------
* #162: Clean up HMAC module import to avoid deadlocks in certain uses of
SSHClient. Thanks to Gernot Hillier for the catch & suggested
fix.
* #36: Fix the port-forwarding demo to avoid file descriptor errors. Thanks to
Jonathan Halcrow for catch & patch.
* #168: Update config handling to properly handle multiple 'localforward' and
'remoteforward' keys. Thanks to Emre Yılmaz for the patch.
v1.11.0 (26th Jul 2013)
-----------------------
* #98: On Windows, when interacting with the PuTTY PAgeant, Paramiko now
creates the shared memory map with explicit Security Attributes of the user,
@ -24,6 +53,18 @@ v1.11.0 (DD MM YYYY)
dependent on ctypes for constructing appropriate structures and had ctypes
implementations of all functionality. Thanks to Jason R. Coombs for the
patch.
* #87: Ensure updates to `known_hosts` files account for any updates to said
files after Paramiko initially read them. (Includes related fix to guard
against duplicate entries during subsequent `known_hosts` loads.) Thanks to
`@sunweaver` for the contribution.
v1.10.2 (26th Jul 2013)
-----------------------
* #153, #67: Warn on parse failure when reading known_hosts file. Thanks to
`@glasserc` for patch.
* #146: Indentation fixes for readability. Thanks to Abhinav Upadhyay for catch
& patch.
v1.10.1 (5th Apr 2013)
----------------------

1
README
View File

@ -8,6 +8,7 @@ paramiko
:Copyright: Copyright (c) 2013 Jeff Forcier <jeff@bitprophet.org>
:License: LGPL
:Homepage: https://github.com/paramiko/paramiko/
:API docs: http://docs.paramiko.org
What

View File

@ -26,7 +26,6 @@ import os
import select
import socket
import sys
import threading
import time
import traceback

View File

@ -78,9 +78,11 @@ class Handler (SocketServer.BaseRequestHandler):
if len(data) == 0:
break
self.request.send(data)
peername = self.request.getpeername()
chan.close()
self.request.close()
verbose('Tunnel closed from %r' % (self.request.getpeername(),))
verbose('Tunnel closed from %r' % (peername,))
def forward_tunnel(local_port, remote_host, remote_port, transport):

2
dev-requirements.txt Normal file
View File

@ -0,0 +1,2 @@
tox>=1.4,<1.5
epydoc>=3.0,<3.1

28
fabfile.py vendored
View File

@ -1,8 +1,10 @@
from fabric.api import task, sudo, env
from fabric.api import task, sudo, env, local, hosts
from fabric.contrib.project import rsync_project
from fabric.contrib.console import confirm
@task
@hosts("paramiko.org")
def upload_docs():
target = "/var/www/paramiko.org"
staging = "/tmp/paramiko_docs"
@ -11,3 +13,27 @@ def upload_docs():
sudo("rm -rf %s/*" % target)
rsync_project(local_dir='docs/', remote_dir=staging, delete=True)
sudo("cp -R %s/* %s/" % (staging, target))
@task
def build_docs():
local("epydoc --no-private -o docs/ paramiko")
@task
def clean():
local("rm -rf build dist docs")
local("rm -f MANIFEST *.log demos/*.log")
local("rm -f paramiko/*.pyc")
local("rm -f test.log")
local("rm -rf paramiko.egg-info")
@task
def test():
local("python ./test.py")
@task
def release():
confirm("Only hit Enter if you remembered to update the version!")
confirm("Also, did you remember to tag your release?")
build_docs()
local("python setup.py sdist register upload")
upload_docs()

View File

@ -46,6 +46,8 @@ Paramiko is written entirely in python (no C or platform-dependent code) and is
released under the GNU Lesser General Public License (LGPL).
Website: U{https://github.com/paramiko/paramiko/}
Mailing list: U{paramiko@librelist.com<mailto:paramiko@librelist.com>}
"""
import sys
@ -55,7 +57,7 @@ if sys.version_info < (2, 5):
__author__ = "Jeff Forcier <jeff@bitprophet.org>"
__version__ = "1.10.1"
__version__ = "1.11.1"
__license__ = "GNU Lesser General Public License (LGPL)"

View File

@ -255,11 +255,11 @@ class AgentServerProxy(AgentSSH):
self.close()
def connect(self):
conn_sock = self.__t.open_forward_agent_channel()
if conn_sock is None:
raise SSHException('lost ssh-agent')
conn_sock.set_name('auth-agent')
self._connect(conn_sock)
conn_sock = self.__t.open_forward_agent_channel()
if conn_sock is None:
raise SSHException('lost ssh-agent')
conn_sock.set_name('auth-agent')
self._connect(conn_sock)
def close(self):
"""

View File

@ -186,8 +186,13 @@ class SSHClient (object):
@raise IOError: if the file could not be written
"""
# update local host keys from file (in case other SSH clients
# have written to the known_hosts file meanwhile.
if self.known_hosts is not None:
self.load_host_keys(self.known_hosts)
f = open(filename, 'w')
f.write('# SSH host keys collected by paramiko\n')
for hostname, keys in self._host_keys.iteritems():
for keytype, key in keys.iteritems():
f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))

View File

@ -35,9 +35,10 @@ class LazyFqdn(object):
Returns the host's fqdn on request as string.
"""
def __init__(self, config):
def __init__(self, config, host=None):
self.fqdn = None
self.config = config
self.host = host
def __str__(self):
if self.fqdn is None:
@ -54,19 +55,27 @@ class LazyFqdn(object):
fqdn = None
address_family = self.config.get('addressfamily', 'any').lower()
if address_family != 'any':
family = socket.AF_INET if address_family == 'inet' \
else socket.AF_INET6
results = socket.getaddrinfo(host,
None,
family,
socket.SOCK_DGRAM,
socket.IPPROTO_IP,
socket.AI_CANONNAME)
for res in results:
af, socktype, proto, canonname, sa = res
if canonname and '.' in canonname:
fqdn = canonname
break
try:
family = socket.AF_INET if address_family == 'inet' \
else socket.AF_INET6
results = socket.getaddrinfo(
self.host,
None,
family,
socket.SOCK_DGRAM,
socket.IPPROTO_IP,
socket.AI_CANONNAME
)
for res in results:
af, socktype, proto, canonname, sa = res
if canonname and '.' in canonname:
fqdn = canonname
break
# giaerror -> socket.getaddrinfo() can't resolve self.host
# (which is from socket.gethostname()). Fall back to the
# getfqdn() call below.
except socket.gaierror:
pass
# Handle 'any' / unspecified
if fqdn is None:
fqdn = socket.getfqdn()
@ -126,16 +135,17 @@ class SSHConfig (object):
self._config.append(host)
value = value.split()
host = {key: value, 'config': {}}
#identityfile is a special case, since it is allowed to be
#identityfile, localforward, remoteforward keys are special cases, since they are allowed to be
# specified multiple times and they should be tried in order
# of specification.
elif key == 'identityfile':
elif key in ['identityfile', 'localforward', 'remoteforward']:
if key in host['config']:
host['config']['identityfile'].append(value)
host['config'][key].append(value)
else:
host['config']['identityfile'] = [value]
host['config'][key] = [value]
elif key not in host['config']:
host['config'].update({key: value})
host['config'].update({key: value})
self._config.append(host)
def lookup(self, hostname):
@ -215,7 +225,7 @@ class SSHConfig (object):
remoteuser = user
host = socket.gethostname().split('.')[0]
fqdn = LazyFqdn(config)
fqdn = LazyFqdn(config, host)
homedir = os.path.expanduser('~')
replacements = {'controlpath':
[
@ -252,5 +262,5 @@ class SSHConfig (object):
config[k][item] = config[k][item].\
replace(find, str(replace))
else:
config[k] = config[k].replace(find, str(replace))
config[k] = config[k].replace(find, str(replace))
return config

View File

@ -28,6 +28,7 @@ import UserDict
from paramiko.common import *
from paramiko.dsskey import DSSKey
from paramiko.rsakey import RSAKey
from paramiko.util import get_logger
class InvalidHostKey(Exception):
@ -48,7 +49,7 @@ class HostKeyEntry:
self.hostnames = hostnames
self.key = key
def from_line(cls, line):
def from_line(cls, line, lineno=None):
"""
Parses the given line of text to find the names for the host,
the type of key, and the key data. The line is expected to be in the
@ -61,9 +62,12 @@ class HostKeyEntry:
@param line: a line from an OpenSSH known_hosts file
@type line: str
"""
log = get_logger('paramiko.hostkeys')
fields = line.split(' ')
if len(fields) < 3:
# Bad number of fields
log.info("Not enough fields found in known_hosts in line %s (%r)" %
(lineno, line))
return None
fields = fields[:3]
@ -78,6 +82,7 @@ class HostKeyEntry:
elif keytype == 'ssh-dss':
key = DSSKey(data=base64.decodestring(key))
else:
log.info("Unable to handle key of type %s" % (keytype,))
return None
except binascii.Error, e:
raise InvalidHostKey(line, e)
@ -160,13 +165,18 @@ class HostKeys (UserDict.DictMixin):
@raise IOError: if there was an error reading the file
"""
f = open(filename, 'r')
for line in f:
for lineno, line in enumerate(f):
line = line.strip()
if (len(line) == 0) or (line[0] == '#'):
continue
e = HostKeyEntry.from_line(line)
e = HostKeyEntry.from_line(line, lineno)
if e is not None:
self._entries.append(e)
_hostnames = e.hostnames
for h in _hostnames:
if self.check(h, e.key):
e.hostnames.remove(h)
if len(e.hostnames):
self._entries.append(e)
f.close()
def save(self, filename):

View File

@ -33,17 +33,13 @@ from paramiko.ssh_exception import SSHException, ProxyCommandFailure
from paramiko.message import Message
got_r_hmac = False
try:
import r_hmac
got_r_hmac = True
from r_hmac import HMAC
except ImportError:
pass
from Crypto.Hash.HMAC import HMAC
def compute_hmac(key, message, digest_class):
if got_r_hmac:
return r_hmac.HMAC(key, message, digest_class).digest()
from Crypto.Hash import HMAC
return HMAC.HMAC(key, message, digest_class).digest()
return HMAC(key, message, digest_class).digest()
class NeedRekeyException (Exception):

View File

@ -1439,7 +1439,7 @@ class Transport (threading.Thread):
break
self.clear_to_send_lock.release()
if time.time() > start + self.clear_to_send_timeout:
raise SSHException('Key-exchange timed out waiting for key negotiation')
raise SSHException('Key-exchange timed out waiting for key negotiation')
try:
self._send_message(data)
finally:

View File

@ -1,2 +0,0 @@
pycrypto
tox

View File

@ -52,7 +52,7 @@ if sys.platform == 'darwin':
setup(name = "paramiko",
version = "1.10.1",
version = "1.11.1",
description = "SSH2 protocol library",
author = "Jeff Forcier",
author_email = "jeff@bitprophet.org",

View File

@ -329,3 +329,14 @@ IdentityFile id_dsa22
paramiko.util.lookup_ssh_host_config(host, config),
values
)
def test_12_config_addressfamily_and_lazy_fqdn(self):
"""
Ensure the code path honoring non-'all' AddressFamily doesn't asplode
"""
test_config = """
AddressFamily inet
IdentityFile something_%l_using_fqdn
"""
config = paramiko.util.parse_ssh_config(cStringIO.StringIO(test_config))
assert config.lookup('meh') # will die during lookup() if bug regresses

View File

@ -2,5 +2,5 @@
envlist = py25,py26,py27
[testenv]
commands = pip install --use-mirrors -q -r requirements.txt
commands = pip install --use-mirrors -q -r dev-requirements.txt
python test.py