Merge branch '1.11' into 156-int
This commit is contained in:
commit
cba4c68365
45
NEWS
45
NEWS
|
@ -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
1
README
|
@ -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
|
||||
|
|
|
@ -26,7 +26,6 @@ import os
|
|||
import select
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
tox>=1.4,<1.5
|
||||
epydoc>=3.0,<3.1
|
|
@ -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()
|
||||
|
|
|
@ -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)"
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
pycrypto
|
||||
tox
|
2
setup.py
2
setup.py
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue