Merge branch 'master' into 112-int
Conflicts: paramiko/win_pageant.py
This commit is contained in:
commit
a7ee2509e4
|
@ -1,6 +1,7 @@
|
|||
*.pyc
|
||||
build/
|
||||
dist/
|
||||
.tox/
|
||||
paramiko.egg-info/
|
||||
test.log
|
||||
docs/
|
||||
|
|
33
NEWS
33
NEWS
|
@ -12,9 +12,40 @@ Issues noted as "Fabric #NN" can be found at https://github.com/fabric/fabric/.
|
|||
Releases
|
||||
========
|
||||
|
||||
v1.10.0 (DD MM YYYY)
|
||||
v1.11.0 (DD MM YYYY)
|
||||
--------------------
|
||||
* #100: Remove use of PyWin32 in `win_pageant` module. Module was already
|
||||
dependent on ctypes for constructing appropriate structures and had ctypes
|
||||
implementations of all functionality. Thanks to Jason R. Coombs for the
|
||||
patch.
|
||||
|
||||
v1.10.0 (1st Mar 2013)
|
||||
--------------------
|
||||
|
||||
* #66: Batch SFTP writes to help speed up file transfers. Thanks to Olle
|
||||
Lundberg for the patch.
|
||||
* #133: Fix handling of window-change events to be on-spec and not
|
||||
attempt to wait for a response from the remote sshd; this fixes problems with
|
||||
less common targets such as some Cisco devices. Thanks to Phillip Heller for
|
||||
catch & patch.
|
||||
* #93: Overhaul SSH config parsing to be in line with `man ssh_config` (& the
|
||||
behavior of `ssh` itself), including addition of parameter expansion within
|
||||
config values. Thanks to Olle Lundberg for the patch.
|
||||
* #110: Honor SSH config `AddressFamily` setting when looking up local
|
||||
host's FQDN. Thanks to John Hensley for the patch.
|
||||
* #128: Defer FQDN resolution until needed, when parsing SSH config files.
|
||||
Thanks to Parantapa Bhattacharya for catch & patch.
|
||||
* #102: Forego random padding for packets when running under `*-ctr` ciphers.
|
||||
This corrects some slowdowns on platforms where random byte generation is
|
||||
inefficient (e.g. Windows). Thanks to `@warthog618` for catch & patch, and
|
||||
Michael van der Kolff for code/technique review.
|
||||
* #127: Turn `SFTPFile` into a context manager. Thanks to Michael Williamson
|
||||
for the patch.
|
||||
* #116: Limit `Message.get_bytes` to an upper bound of 1MB to protect against
|
||||
potential DoS vectors. Thanks to `@mvschaik` for catch & patch.
|
||||
* #115: Add convenience `get_pty` kwarg to `Client.exec_command` so users not
|
||||
manually controlling a channel object can still toggle PTY creation. Thanks
|
||||
to Michael van der Kolff for the patch.
|
||||
* #71: Add `SFTPClient.putfo` and `.getfo` methods to allow direct
|
||||
uploading/downloading of file-like objects. Thanks to Eric Buehl for the
|
||||
patch.
|
||||
|
|
20
README
20
README
|
@ -5,7 +5,7 @@ paramiko
|
|||
|
||||
:Paramiko: Python SSH module
|
||||
:Copyright: Copyright (c) 2003-2009 Robey Pointer <robeypointer@gmail.com>
|
||||
:Copyright: Copyright (c) 2012 Jeff Forcier <jeff@bitprophet.org>
|
||||
:Copyright: Copyright (c) 2013 Jeff Forcier <jeff@bitprophet.org>
|
||||
:License: LGPL
|
||||
:Homepage: https://github.com/paramiko/paramiko/
|
||||
|
||||
|
@ -20,7 +20,7 @@ What
|
|||
----
|
||||
|
||||
"paramiko" is a combination of the esperanto words for "paranoid" and
|
||||
"friend". it's a module for python 2.2+ that implements the SSH2 protocol
|
||||
"friend". it's a module for python 2.5+ that implements the SSH2 protocol
|
||||
for secure (encrypted and authenticated) connections to remote machines.
|
||||
unlike SSL (aka TLS), SSH2 protocol does not require hierarchical
|
||||
certificates signed by a powerful central authority. you may know SSH2 as
|
||||
|
@ -39,8 +39,7 @@ that should have come with this archive.
|
|||
Requirements
|
||||
------------
|
||||
|
||||
- python 2.3 or better <http://www.python.org/>
|
||||
(python 2.2 is also supported, but not recommended)
|
||||
- python 2.5 or better <http://www.python.org/>
|
||||
- pycrypto 2.1 or better <https://www.dlitz.net/software/pycrypto/>
|
||||
|
||||
If you have setuptools, you can build and install paramiko and all its
|
||||
|
@ -58,19 +57,6 @@ should also work on Windows, though i don't test it as frequently there.
|
|||
if you run into Windows problems, send me a patch: portability is important
|
||||
to me.
|
||||
|
||||
python 2.2 may work, thanks to some patches from Roger Binns. things to
|
||||
watch out for:
|
||||
|
||||
* sockets in 2.2 don't support timeouts, so the 'select' module is
|
||||
imported to do polling.
|
||||
* logging is mostly stubbed out. it works just enough to let paramiko
|
||||
create log files for debugging, if you want them. to get real logging,
|
||||
you can backport python 2.3's logging package. Roger has done that
|
||||
already:
|
||||
http://sourceforge.net/project/showfiles.php?group_id=75211&package_id=113804
|
||||
|
||||
you really should upgrade to python 2.3. laziness is no excuse! :)
|
||||
|
||||
some python distributions don't include the utf-8 string encodings, for
|
||||
reasons of space (misdirected as that is). if your distribution is
|
||||
missing encodings, you'll see an error like this::
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
"""
|
||||
I{Paramiko} (a combination of the esperanto words for "paranoid" and "friend")
|
||||
is a module for python 2.3 or greater that implements the SSH2 protocol for
|
||||
is a module for python 2.5 or greater that implements the SSH2 protocol for
|
||||
secure (encrypted and authenticated) connections to remote machines. Unlike
|
||||
SSL (aka TLS), the SSH2 protocol does not require hierarchical certificates
|
||||
signed by a powerful central authority. You may know SSH2 as the protocol that
|
||||
|
@ -50,8 +50,8 @@ Website: U{https://github.com/paramiko/paramiko/}
|
|||
|
||||
import sys
|
||||
|
||||
if sys.version_info < (2, 2):
|
||||
raise RuntimeError('You need python 2.2 for this module.')
|
||||
if sys.version_info < (2, 5):
|
||||
raise RuntimeError('You need python 2.5+ for this module.')
|
||||
|
||||
|
||||
__author__ = "Jeff Forcier <jeff@bitprophet.org>"
|
||||
|
|
|
@ -122,7 +122,8 @@ class Channel (object):
|
|||
out += '>'
|
||||
return out
|
||||
|
||||
def get_pty(self, term='vt100', width=80, height=24):
|
||||
def get_pty(self, term='vt100', width=80, height=24, width_pixels=0,
|
||||
height_pixels=0):
|
||||
"""
|
||||
Request a pseudo-terminal from the server. This is usually used right
|
||||
after creating a client channel, to ask the server to provide some
|
||||
|
@ -136,6 +137,10 @@ class Channel (object):
|
|||
@type width: int
|
||||
@param height: height (in characters) of the terminal screen
|
||||
@type height: int
|
||||
@param width_pixels: width (in pixels) of the terminal screen
|
||||
@type width_pixels: int
|
||||
@param height_pixels: height (in pixels) of the terminal screen
|
||||
@type height_pixels: int
|
||||
|
||||
@raise SSHException: if the request was rejected or the channel was
|
||||
closed
|
||||
|
@ -150,8 +155,8 @@ class Channel (object):
|
|||
m.add_string(term)
|
||||
m.add_int(width)
|
||||
m.add_int(height)
|
||||
# pixel height, width (usually useless)
|
||||
m.add_int(0).add_int(0)
|
||||
m.add_int(width_pixels)
|
||||
m.add_int(height_pixels)
|
||||
m.add_string('')
|
||||
self._event_pending()
|
||||
self.transport._send_user_message(m)
|
||||
|
@ -239,7 +244,7 @@ class Channel (object):
|
|||
self.transport._send_user_message(m)
|
||||
self._wait_for_event()
|
||||
|
||||
def resize_pty(self, width=80, height=24):
|
||||
def resize_pty(self, width=80, height=24, width_pixels=0, height_pixels=0):
|
||||
"""
|
||||
Resize the pseudo-terminal. This can be used to change the width and
|
||||
height of the terminal emulation created in a previous L{get_pty} call.
|
||||
|
@ -248,6 +253,10 @@ class Channel (object):
|
|||
@type width: int
|
||||
@param height: new height (in characters) of the terminal screen
|
||||
@type height: int
|
||||
@param width_pixels: new width (in pixels) of the terminal screen
|
||||
@type width_pixels: int
|
||||
@param height_pixels: new height (in pixels) of the terminal screen
|
||||
@type height_pixels: int
|
||||
|
||||
@raise SSHException: if the request was rejected or the channel was
|
||||
closed
|
||||
|
@ -258,13 +267,12 @@ class Channel (object):
|
|||
m.add_byte(chr(MSG_CHANNEL_REQUEST))
|
||||
m.add_int(self.remote_chanid)
|
||||
m.add_string('window-change')
|
||||
m.add_boolean(True)
|
||||
m.add_boolean(False)
|
||||
m.add_int(width)
|
||||
m.add_int(height)
|
||||
m.add_int(0).add_int(0)
|
||||
self._event_pending()
|
||||
m.add_int(width_pixels)
|
||||
m.add_int(height_pixels)
|
||||
self.transport._send_user_message(m)
|
||||
self._wait_for_event()
|
||||
|
||||
def exit_status_ready(self):
|
||||
"""
|
||||
|
|
|
@ -349,7 +349,7 @@ class SSHClient (object):
|
|||
self._agent.close()
|
||||
self._agent = None
|
||||
|
||||
def exec_command(self, command, bufsize=-1, timeout=None):
|
||||
def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False):
|
||||
"""
|
||||
Execute a command on the SSH server. A new L{Channel} is opened and
|
||||
the requested command is executed. The command's input and output
|
||||
|
@ -368,6 +368,8 @@ class SSHClient (object):
|
|||
@raise SSHException: if the server fails to execute the command
|
||||
"""
|
||||
chan = self._transport.open_session()
|
||||
if(get_pty):
|
||||
chan.get_pty()
|
||||
chan.settimeout(timeout)
|
||||
chan.exec_command(command)
|
||||
stdin = chan.makefile('wb', bufsize)
|
||||
|
@ -375,7 +377,8 @@ class SSHClient (object):
|
|||
stderr = chan.makefile_stderr('rb', bufsize)
|
||||
return stdin, stdout, stderr
|
||||
|
||||
def invoke_shell(self, term='vt100', width=80, height=24):
|
||||
def invoke_shell(self, term='vt100', width=80, height=24, width_pixels=0,
|
||||
height_pixels=0):
|
||||
"""
|
||||
Start an interactive shell session on the SSH server. A new L{Channel}
|
||||
is opened and connected to a pseudo-terminal using the requested
|
||||
|
@ -387,13 +390,17 @@ class SSHClient (object):
|
|||
@type width: int
|
||||
@param height: the height (in characters) of the terminal window
|
||||
@type height: int
|
||||
@param width_pixels: the width (in pixels) of the terminal window
|
||||
@type width_pixels: int
|
||||
@param height_pixels: the height (in pixels) of the terminal window
|
||||
@type height_pixels: int
|
||||
@return: a new channel connected to the remote shell
|
||||
@rtype: L{Channel}
|
||||
|
||||
@raise SSHException: if the server fails to invoke a shell
|
||||
"""
|
||||
chan = self._transport.open_session()
|
||||
chan.get_pty(term, width, height)
|
||||
chan.get_pty(term, width, height, width_pixels, height_pixels)
|
||||
chan.invoke_shell()
|
||||
return chan
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright (C) 2006-2007 Robey Pointer <robeypointer@gmail.com>
|
||||
# Copyright (C) 2012 Olle Lundberg <geek@nerd.sh>
|
||||
#
|
||||
# This file is part of paramiko.
|
||||
#
|
||||
|
@ -29,6 +30,51 @@ SSH_PORT = 22
|
|||
proxy_re = re.compile(r"^(proxycommand)\s*=*\s*(.*)", re.I)
|
||||
|
||||
|
||||
class LazyFqdn(object):
|
||||
"""
|
||||
Returns the host's fqdn on request as string.
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
self.fqdn = None
|
||||
self.config = config
|
||||
|
||||
def __str__(self):
|
||||
if self.fqdn is None:
|
||||
#
|
||||
# If the SSH config contains AddressFamily, use that when
|
||||
# determining the local host's FQDN. Using socket.getfqdn() from
|
||||
# the standard library is the most general solution, but can
|
||||
# result in noticeable delays on some platforms when IPv6 is
|
||||
# misconfigured or not available, as it calls getaddrinfo with no
|
||||
# address family specified, so both IPv4 and IPv6 are checked.
|
||||
#
|
||||
|
||||
# Handle specific option
|
||||
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
|
||||
# Handle 'any' / unspecified
|
||||
if fqdn is None:
|
||||
fqdn = socket.getfqdn()
|
||||
# Cache
|
||||
self.fqdn = fqdn
|
||||
return self.fqdn
|
||||
|
||||
|
||||
class SSHConfig (object):
|
||||
"""
|
||||
Representation of config information as stored in the format used by
|
||||
|
@ -44,7 +90,7 @@ class SSHConfig (object):
|
|||
"""
|
||||
Create a new OpenSSH config object.
|
||||
"""
|
||||
self._config = [ { 'host': '*' } ]
|
||||
self._config = []
|
||||
|
||||
def parse(self, file_obj):
|
||||
"""
|
||||
|
@ -53,7 +99,7 @@ class SSHConfig (object):
|
|||
@param file_obj: a file-like object to read the config file from
|
||||
@type file_obj: file
|
||||
"""
|
||||
configs = [self._config[0]]
|
||||
host = {"host": ['*'], "config": {}}
|
||||
for line in file_obj:
|
||||
line = line.rstrip('\n').lstrip()
|
||||
if (line == '') or (line[0] == '#'):
|
||||
|
@ -77,20 +123,20 @@ class SSHConfig (object):
|
|||
value = line[i:].lstrip()
|
||||
|
||||
if key == 'host':
|
||||
del configs[:]
|
||||
# the value may be multiple hosts, space-delimited
|
||||
for host in value.split():
|
||||
# do we have a pre-existing host config to append to?
|
||||
matches = [c for c in self._config if c['host'] == host]
|
||||
if len(matches) > 0:
|
||||
configs.append(matches[0])
|
||||
else:
|
||||
config = { 'host': host }
|
||||
self._config.append(config)
|
||||
configs.append(config)
|
||||
else:
|
||||
for config in configs:
|
||||
config[key] = value
|
||||
self._config.append(host)
|
||||
value = value.split()
|
||||
host = {key: value, 'config': {}}
|
||||
#identityfile is a special case, since it is allowed to be
|
||||
# specified multiple times and they should be tried in order
|
||||
# of specification.
|
||||
elif key == 'identityfile':
|
||||
if key in host['config']:
|
||||
host['config']['identityfile'].append(value)
|
||||
else:
|
||||
host['config']['identityfile'] = [value]
|
||||
elif key not in host['config']:
|
||||
host['config'].update({key: value})
|
||||
self._config.append(host)
|
||||
|
||||
def lookup(self, hostname):
|
||||
"""
|
||||
|
@ -105,31 +151,45 @@ class SSHConfig (object):
|
|||
will win out.
|
||||
|
||||
The keys in the returned dict are all normalized to lowercase (look for
|
||||
C{"port"}, not C{"Port"}. No other processing is done to the keys or
|
||||
values.
|
||||
C{"port"}, not C{"Port"}. The values are processed according to the
|
||||
rules for substitution variable expansion in C{ssh_config}.
|
||||
|
||||
@param hostname: the hostname to lookup
|
||||
@type hostname: str
|
||||
"""
|
||||
matches = [x for x in self._config if fnmatch.fnmatch(hostname, x['host'])]
|
||||
# Move * to the end
|
||||
_star = matches.pop(0)
|
||||
matches.append(_star)
|
||||
|
||||
matches = [config for config in self._config if
|
||||
self._allowed(hostname, config['host'])]
|
||||
|
||||
ret = {}
|
||||
for m in matches:
|
||||
for k,v in m.iteritems():
|
||||
if not k in ret:
|
||||
ret[k] = v
|
||||
for match in matches:
|
||||
for key, value in match['config'].iteritems():
|
||||
if key not in ret:
|
||||
# Create a copy of the original value,
|
||||
# else it will reference the original list
|
||||
# in self._config and update that value too
|
||||
# when the extend() is being called.
|
||||
ret[key] = value[:]
|
||||
elif key == 'identityfile':
|
||||
ret[key].extend(value)
|
||||
ret = self._expand_variables(ret, hostname)
|
||||
del ret['host']
|
||||
return ret
|
||||
|
||||
def _expand_variables(self, config, hostname ):
|
||||
def _allowed(self, hostname, hosts):
|
||||
match = False
|
||||
for host in hosts:
|
||||
if host.startswith('!') and fnmatch.fnmatch(hostname, host[1:]):
|
||||
return False
|
||||
elif fnmatch.fnmatch(hostname, host):
|
||||
match = True
|
||||
return match
|
||||
|
||||
def _expand_variables(self, config, hostname):
|
||||
"""
|
||||
Return a dict of config options with expanded substitutions
|
||||
for a given hostname.
|
||||
|
||||
Please refer to man ssh_config(5) for the parameters that
|
||||
Please refer to man C{ssh_config} for the parameters that
|
||||
are replaced.
|
||||
|
||||
@param config: the config for the hostname
|
||||
|
@ -139,7 +199,7 @@ class SSHConfig (object):
|
|||
"""
|
||||
|
||||
if 'hostname' in config:
|
||||
config['hostname'] = config['hostname'].replace('%h',hostname)
|
||||
config['hostname'] = config['hostname'].replace('%h', hostname)
|
||||
else:
|
||||
config['hostname'] = hostname
|
||||
|
||||
|
@ -155,34 +215,42 @@ class SSHConfig (object):
|
|||
remoteuser = user
|
||||
|
||||
host = socket.gethostname().split('.')[0]
|
||||
fqdn = socket.getfqdn()
|
||||
fqdn = LazyFqdn(config)
|
||||
homedir = os.path.expanduser('~')
|
||||
replacements = {
|
||||
'controlpath': [
|
||||
('%h', config['hostname']),
|
||||
('%l', fqdn),
|
||||
('%L', host),
|
||||
('%n', hostname),
|
||||
('%p', port),
|
||||
('%r', remoteuser),
|
||||
('%u', user)
|
||||
],
|
||||
'identityfile': [
|
||||
('~', homedir),
|
||||
('%d', homedir),
|
||||
('%h', config['hostname']),
|
||||
('%l', fqdn),
|
||||
('%u', user),
|
||||
('%r', remoteuser)
|
||||
],
|
||||
'proxycommand': [
|
||||
('%h', config['hostname']),
|
||||
('%p', port),
|
||||
('%r', remoteuser),
|
||||
],
|
||||
}
|
||||
replacements = {'controlpath':
|
||||
[
|
||||
('%h', config['hostname']),
|
||||
('%l', fqdn),
|
||||
('%L', host),
|
||||
('%n', hostname),
|
||||
('%p', port),
|
||||
('%r', remoteuser),
|
||||
('%u', user)
|
||||
],
|
||||
'identityfile':
|
||||
[
|
||||
('~', homedir),
|
||||
('%d', homedir),
|
||||
('%h', config['hostname']),
|
||||
('%l', fqdn),
|
||||
('%u', user),
|
||||
('%r', remoteuser)
|
||||
],
|
||||
'proxycommand':
|
||||
[
|
||||
('%h', config['hostname']),
|
||||
('%p', port),
|
||||
('%r', remoteuser)
|
||||
]
|
||||
}
|
||||
|
||||
for k in config:
|
||||
if k in replacements:
|
||||
for find, replace in replacements[k]:
|
||||
config[k] = config[k].replace(find, str(replace))
|
||||
if isinstance(config[k], list):
|
||||
for item in range(len(config[k])):
|
||||
config[k][item] = config[k][item].\
|
||||
replace(find, str(replace))
|
||||
else:
|
||||
config[k] = config[k].replace(find, str(replace))
|
||||
return config
|
||||
|
|
|
@ -110,7 +110,8 @@ class Message (object):
|
|||
@rtype: string
|
||||
"""
|
||||
b = self.packet.read(n)
|
||||
if len(b) < n:
|
||||
max_pad_size = 1<<20 # Limit padding to 1 MB
|
||||
if len(b) < n and n < max_pad_size:
|
||||
return b + '\x00' * (n - len(b))
|
||||
return b
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@ class Packetizer (object):
|
|||
self.__mac_size_in = 0
|
||||
self.__block_engine_out = None
|
||||
self.__block_engine_in = None
|
||||
self.__sdctr_out = False
|
||||
self.__mac_engine_out = None
|
||||
self.__mac_engine_in = None
|
||||
self.__mac_key_out = ''
|
||||
|
@ -110,11 +111,12 @@ class Packetizer (object):
|
|||
"""
|
||||
self.__logger = log
|
||||
|
||||
def set_outbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key):
|
||||
def set_outbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key, sdctr=False):
|
||||
"""
|
||||
Switch outbound data cipher.
|
||||
"""
|
||||
self.__block_engine_out = block_engine
|
||||
self.__sdctr_out = sdctr
|
||||
self.__block_size_out = block_size
|
||||
self.__mac_engine_out = mac_engine
|
||||
self.__mac_size_out = mac_size
|
||||
|
@ -490,12 +492,12 @@ class Packetizer (object):
|
|||
padding = 3 + bsize - ((len(payload) + 8) % bsize)
|
||||
packet = struct.pack('>IB', len(payload) + padding + 1, padding)
|
||||
packet += payload
|
||||
if self.__block_engine_out is not None:
|
||||
packet += rng.read(padding)
|
||||
else:
|
||||
# cute trick i caught openssh doing: if we're not encrypting,
|
||||
if self.__sdctr_out or self.__block_engine_out is None:
|
||||
# cute trick i caught openssh doing: if we're not encrypting or SDCTR mode (RFC4344),
|
||||
# don't waste random bytes for the padding
|
||||
packet += (chr(0) * padding)
|
||||
else:
|
||||
packet += rng.read(padding)
|
||||
return packet
|
||||
|
||||
def _trigger_rekey(self):
|
||||
|
|
|
@ -198,7 +198,7 @@ class SFTPClient (BaseSFTP):
|
|||
Open a file on the remote server. The arguments are the same as for
|
||||
python's built-in C{file} (aka C{open}). A file-like object is
|
||||
returned, which closely mimics the behavior of a normal python file
|
||||
object.
|
||||
object, including the ability to be used as a context manager.
|
||||
|
||||
The mode indicates how the file is to be opened: C{'r'} for reading,
|
||||
C{'w'} for writing (truncating an existing file), C{'a'} for appending,
|
||||
|
|
|
@ -21,6 +21,7 @@ L{SFTPFile}
|
|||
"""
|
||||
|
||||
from binascii import hexlify
|
||||
from collections import deque
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
@ -34,6 +35,9 @@ from paramiko.sftp_attr import SFTPAttributes
|
|||
class SFTPFile (BufferedFile):
|
||||
"""
|
||||
Proxy object for a file on the remote server, in client mode SFTP.
|
||||
|
||||
Instances of this class may be used as context managers in the same way
|
||||
that built-in Python file objects are.
|
||||
"""
|
||||
|
||||
# Some sftp servers will choke if you send read/write requests larger than
|
||||
|
@ -51,6 +55,7 @@ class SFTPFile (BufferedFile):
|
|||
self._prefetch_data = {}
|
||||
self._prefetch_reads = []
|
||||
self._saved_exception = None
|
||||
self._reqs = deque()
|
||||
|
||||
def __del__(self):
|
||||
self._close(async=True)
|
||||
|
@ -160,12 +165,14 @@ class SFTPFile (BufferedFile):
|
|||
def _write(self, data):
|
||||
# may write less than requested if it would exceed max packet size
|
||||
chunk = min(len(data), self.MAX_REQUEST_SIZE)
|
||||
req = self.sftp._async_request(type(None), CMD_WRITE, self.handle, long(self._realpos), str(data[:chunk]))
|
||||
if not self.pipelined or self.sftp.sock.recv_ready():
|
||||
t, msg = self.sftp._read_response(req)
|
||||
if t != CMD_STATUS:
|
||||
raise SFTPError('Expected status')
|
||||
# convert_status already called
|
||||
self._reqs.append(self.sftp._async_request(type(None), CMD_WRITE, self.handle, long(self._realpos), str(data[:chunk])))
|
||||
if not self.pipelined or (len(self._reqs) > 100 and self.sftp.sock.recv_ready()):
|
||||
while len(self._reqs):
|
||||
req = self._reqs.popleft()
|
||||
t, msg = self.sftp._read_response(req)
|
||||
if t != CMD_STATUS:
|
||||
raise SFTPError('Expected status')
|
||||
# convert_status already called
|
||||
return chunk
|
||||
|
||||
def settimeout(self, timeout):
|
||||
|
@ -474,3 +481,9 @@ class SFTPFile (BufferedFile):
|
|||
x = self._saved_exception
|
||||
self._saved_exception = None
|
||||
raise x
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.close()
|
||||
|
|
|
@ -1885,7 +1885,8 @@ class Transport (threading.Thread):
|
|||
mac_key = self._compute_key('F', mac_engine.digest_size)
|
||||
else:
|
||||
mac_key = self._compute_key('E', mac_engine.digest_size)
|
||||
self.packetizer.set_outbound_cipher(engine, block_size, mac_engine, mac_size, mac_key)
|
||||
sdctr = self.local_cipher.endswith('-ctr')
|
||||
self.packetizer.set_outbound_cipher(engine, block_size, mac_engine, mac_size, mac_key, sdctr)
|
||||
compress_out = self._compression_info[self.local_compression][0]
|
||||
if (compress_out is not None) and ((self.local_compression != 'zlib@openssh.com') or self.authenticated):
|
||||
self._log(DEBUG, 'Switching on outbound compression ...')
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
pycrypto
|
||||
tox
|
|
@ -23,6 +23,8 @@ a real actual sftp server is contacted, and a new folder is created there to
|
|||
do test file operations in (so no existing files will be harmed).
|
||||
"""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
from binascii import hexlify
|
||||
import logging
|
||||
import os
|
||||
|
@ -188,6 +190,17 @@ class SFTPTest (unittest.TestCase):
|
|||
finally:
|
||||
sftp.remove(FOLDER + '/duck.txt')
|
||||
|
||||
def test_3_sftp_file_can_be_used_as_context_manager(self):
|
||||
"""
|
||||
verify that an opened file can be used as a context manager
|
||||
"""
|
||||
try:
|
||||
with sftp.open(FOLDER + '/duck.txt', 'w') as f:
|
||||
f.write(ARTICLE)
|
||||
self.assertEqual(sftp.stat(FOLDER + '/duck.txt').st_size, 1483)
|
||||
finally:
|
||||
sftp.remove(FOLDER + '/duck.txt')
|
||||
|
||||
def test_4_append(self):
|
||||
"""
|
||||
verify that a file can be opened for append, and tell() still works.
|
||||
|
|
|
@ -104,23 +104,32 @@ class UtilTest(ParamikoTest):
|
|||
f = cStringIO.StringIO(test_config_file)
|
||||
config = paramiko.util.parse_ssh_config(f)
|
||||
self.assertEquals(config._config,
|
||||
[ {'identityfile': '~/.ssh/id_rsa', 'host': '*', 'user': 'robey',
|
||||
'crazy': 'something dumb '},
|
||||
{'host': '*.example.com', 'user': 'bjork', 'port': '3333'},
|
||||
{'host': 'spoo.example.com', 'crazy': 'something else'}])
|
||||
[{'host': ['*'], 'config': {}}, {'host': ['*'], 'config': {'identityfile': ['~/.ssh/id_rsa'], 'user': 'robey'}},
|
||||
{'host': ['*.example.com'], 'config': {'user': 'bjork', 'port': '3333'}},
|
||||
{'host': ['*'], 'config': {'crazy': 'something dumb '}},
|
||||
{'host': ['spoo.example.com'], 'config': {'crazy': 'something else'}}])
|
||||
|
||||
def test_3_host_config(self):
|
||||
global test_config_file
|
||||
f = cStringIO.StringIO(test_config_file)
|
||||
config = paramiko.util.parse_ssh_config(f)
|
||||
|
||||
for host, values in {
|
||||
'irc.danger.com': {'user': 'robey', 'crazy': 'something dumb '},
|
||||
'irc.example.com': {'user': 'bjork', 'crazy': 'something dumb ', 'port': '3333'},
|
||||
'spoo.example.com': {'user': 'bjork', 'crazy': 'something else', 'port': '3333'}
|
||||
'irc.danger.com': {'crazy': 'something dumb ',
|
||||
'hostname': 'irc.danger.com',
|
||||
'user': 'robey'},
|
||||
'irc.example.com': {'crazy': 'something dumb ',
|
||||
'hostname': 'irc.example.com',
|
||||
'user': 'robey',
|
||||
'port': '3333'},
|
||||
'spoo.example.com': {'crazy': 'something dumb ',
|
||||
'hostname': 'spoo.example.com',
|
||||
'user': 'robey',
|
||||
'port': '3333'}
|
||||
}.items():
|
||||
values = dict(values,
|
||||
hostname=host,
|
||||
identityfile=os.path.expanduser("~/.ssh/id_rsa")
|
||||
identityfile=[os.path.expanduser("~/.ssh/id_rsa")]
|
||||
)
|
||||
self.assertEquals(
|
||||
paramiko.util.lookup_ssh_host_config(host, config),
|
||||
|
@ -151,8 +160,8 @@ class UtilTest(ParamikoTest):
|
|||
# just verify that we can pull out 32 bytes and not get an exception.
|
||||
x = rng.read(32)
|
||||
self.assertEquals(len(x), 32)
|
||||
|
||||
def test_7_host_config_expose_ssh_issue_33(self):
|
||||
|
||||
def test_7_host_config_expose_issue_33(self):
|
||||
test_config_file = """
|
||||
Host www13.*
|
||||
Port 22
|
||||
|
@ -220,16 +229,16 @@ Host equals-delimited
|
|||
ProxyCommand should perform interpolation on the value
|
||||
"""
|
||||
config = paramiko.util.parse_ssh_config(cStringIO.StringIO("""
|
||||
Host *
|
||||
Port 25
|
||||
ProxyCommand host %h port %p
|
||||
|
||||
Host specific
|
||||
Port 37
|
||||
ProxyCommand host %h port %p lol
|
||||
|
||||
Host portonly
|
||||
Port 155
|
||||
|
||||
Host *
|
||||
Port 25
|
||||
ProxyCommand host %h port %p
|
||||
"""))
|
||||
for host, val in (
|
||||
('foo.com', "host foo.com port 25"),
|
||||
|
@ -240,3 +249,83 @@ Host portonly
|
|||
host_config(host, config)['proxycommand'],
|
||||
val
|
||||
)
|
||||
|
||||
def test_11_host_config_test_negation(self):
|
||||
test_config_file = """
|
||||
Host www13.* !*.example.com
|
||||
Port 22
|
||||
|
||||
Host *.example.com !www13.*
|
||||
Port 2222
|
||||
|
||||
Host www13.*
|
||||
Port 8080
|
||||
|
||||
Host *
|
||||
Port 3333
|
||||
"""
|
||||
f = cStringIO.StringIO(test_config_file)
|
||||
config = paramiko.util.parse_ssh_config(f)
|
||||
host = 'www13.example.com'
|
||||
self.assertEquals(
|
||||
paramiko.util.lookup_ssh_host_config(host, config),
|
||||
{'hostname': host, 'port': '8080'}
|
||||
)
|
||||
|
||||
def test_12_host_config_test_proxycommand(self):
|
||||
test_config_file = """
|
||||
Host proxy-with-equal-divisor-and-space
|
||||
ProxyCommand = foo=bar
|
||||
|
||||
Host proxy-with-equal-divisor-and-no-space
|
||||
ProxyCommand=foo=bar
|
||||
|
||||
Host proxy-without-equal-divisor
|
||||
ProxyCommand foo=bar:%h-%p
|
||||
"""
|
||||
for host, values in {
|
||||
'proxy-with-equal-divisor-and-space' :{'hostname': 'proxy-with-equal-divisor-and-space',
|
||||
'proxycommand': 'foo=bar'},
|
||||
'proxy-with-equal-divisor-and-no-space':{'hostname': 'proxy-with-equal-divisor-and-no-space',
|
||||
'proxycommand': 'foo=bar'},
|
||||
'proxy-without-equal-divisor' :{'hostname': 'proxy-without-equal-divisor',
|
||||
'proxycommand':
|
||||
'foo=bar:proxy-without-equal-divisor-22'}
|
||||
}.items():
|
||||
|
||||
f = cStringIO.StringIO(test_config_file)
|
||||
config = paramiko.util.parse_ssh_config(f)
|
||||
self.assertEquals(
|
||||
paramiko.util.lookup_ssh_host_config(host, config),
|
||||
values
|
||||
)
|
||||
|
||||
def test_11_host_config_test_identityfile(self):
|
||||
test_config_file = """
|
||||
|
||||
IdentityFile id_dsa0
|
||||
|
||||
Host *
|
||||
IdentityFile id_dsa1
|
||||
|
||||
Host dsa2
|
||||
IdentityFile id_dsa2
|
||||
|
||||
Host dsa2*
|
||||
IdentityFile id_dsa22
|
||||
"""
|
||||
for host, values in {
|
||||
'foo' :{'hostname': 'foo',
|
||||
'identityfile': ['id_dsa0', 'id_dsa1']},
|
||||
'dsa2' :{'hostname': 'dsa2',
|
||||
'identityfile': ['id_dsa0', 'id_dsa1', 'id_dsa2', 'id_dsa22']},
|
||||
'dsa22' :{'hostname': 'dsa22',
|
||||
'identityfile': ['id_dsa0', 'id_dsa1', 'id_dsa22']}
|
||||
}.items():
|
||||
|
||||
f = cStringIO.StringIO(test_config_file)
|
||||
config = paramiko.util.parse_ssh_config(f)
|
||||
self.assertEquals(
|
||||
paramiko.util.lookup_ssh_host_config(host, config),
|
||||
values
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue