From 4b02a9b424be448aef5e00abe3bb22f56c84144b Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 28 Jan 2016 08:53:16 -0500 Subject: Drop support for RC4. It's cryptoanalytically completely 100% broken, and practical attacks have been demonstrated against it's usage in TLS. As far as I'm aware, there's no use case for RC4 based on compatibility. --- paramiko/transport.py | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index 18fb103b..5b440a4d 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -64,7 +64,7 @@ from paramiko.ssh_exception import (SSHException, BadAuthenticationType, ChannelException, ProxyCommandFailure) from paramiko.util import retry_on_signal, ClosingContextManager, clamp_value -from Crypto.Cipher import Blowfish, AES, DES3, ARC4 +from Crypto.Cipher import Blowfish, AES, DES3 try: from Crypto.Util import Counter except ImportError: @@ -106,8 +106,6 @@ class Transport (threading.Thread, ClosingContextManager): 'aes192-cbc', 'aes256-cbc', '3des-cbc', - 'arcfour128', - 'arcfour256', ) _preferred_macs = ( 'hmac-sha2-256', @@ -179,18 +177,6 @@ class Transport (threading.Thread, ClosingContextManager): 'block-size': 8, 'key-size': 24 }, - 'arcfour128': { - 'class': ARC4, - 'mode': None, - 'block-size': 8, - 'key-size': 16 - }, - 'arcfour256': { - 'class': ARC4, - 'mode': None, - 'block-size': 8, - 'key-size': 32 - }, } _mac_info = { @@ -1636,14 +1622,7 @@ class Transport (threading.Thread, ClosingContextManager): def _get_cipher(self, name, key, iv): if name not in self._cipher_info: raise SSHException('Unknown client cipher ' + name) - if name in ('arcfour128', 'arcfour256'): - # arcfour cipher - cipher = self._cipher_info[name]['class'].new(key) - # as per RFC 4345, the first 1536 bytes of keystream - # generated by the cipher MUST be discarded - cipher.encrypt(" " * 1536) - return cipher - elif name.endswith("-ctr"): + if name.endswith("-ctr"): # CTR modes, we need a counter counter = Counter.new(nbits=self._cipher_info[name]['block-size'] * 8, initial_value=util.inflate_long(iv, True)) return self._cipher_info[name]['class'].new(key, self._cipher_info[name]['mode'], iv, counter) -- cgit v1.2.1 From a811ed36e1d90e67df6edb5cbe27642d4afae268 Mon Sep 17 00:00:00 2001 From: Kyle Agronick Date: Thu, 4 May 2017 10:06:07 -0400 Subject: remove ResourceManager to fix leak of Transport/SSHClient The back-reference from Transport to SSHClient was added because the SSHClient had a destructor that would close the Transport, and some users did not want the Transport closed when the SSHClient was garbage collected. The SSHClient destructor was not a normal destructor, it was implemented with the ResourceManager singleton. Together with the back-reference, this prevented the GC cycle detector from freeing the SSHClient and Transport. --- paramiko/client.py | 2 -- paramiko/resource.py | 71 ---------------------------------------------------- 2 files changed, 73 deletions(-) delete mode 100644 paramiko/resource.py diff --git a/paramiko/client.py b/paramiko/client.py index bbda1f80..025cf96c 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -34,7 +34,6 @@ from paramiko.dsskey import DSSKey from paramiko.ecdsakey import ECDSAKey from paramiko.hostkeys import HostKeys from paramiko.py3compat import string_types -from paramiko.resource import ResourceManager from paramiko.rsakey import RSAKey from paramiko.ssh_exception import ( SSHException, BadHostKeyException, NoValidConnectionsError @@ -341,7 +340,6 @@ class SSHClient (ClosingContextManager): t.banner_timeout = banner_timeout t.start_client() t.set_sshclient(self) - ResourceManager.register(self, t) server_key = t.get_remote_server_key() keytype = server_key.get_name() diff --git a/paramiko/resource.py b/paramiko/resource.py deleted file mode 100644 index 5fed22ad..00000000 --- a/paramiko/resource.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (C) 2003-2007 Robey Pointer -# -# This file is part of paramiko. -# -# Paramiko is free software; you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation; either version 2.1 of the License, or (at your option) -# any later version. -# -# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR -# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Paramiko; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - -""" -Resource manager. -""" - -import weakref - - -class ResourceManager (object): - """ - A registry of objects and resources that should be closed when those - objects are deleted. - - This is meant to be a safer alternative to Python's ``__del__`` method, - which can cause reference cycles to never be collected. Objects registered - with the ResourceManager can be collected but still free resources when - they die. - - Resources are registered using `register`, and when an object is garbage - collected, each registered resource is closed by having its ``close()`` - method called. Multiple resources may be registered per object, but a - resource will only be closed once, even if multiple objects register it. - (The last object to register it wins.) - """ - - def __init__(self): - self._table = {} - - def register(self, obj, resource): - """ - Register a resource to be closed with an object is collected. - - When the given ``obj`` is garbage-collected by the Python interpreter, - the ``resource`` will be closed by having its ``close()`` method - called. Any exceptions are ignored. - - :param object obj: the object to track - :param object resource: - the resource to close when the object is collected - """ - def callback(ref): - try: - resource.close() - except: - pass - del self._table[id(resource)] - - # keep the weakref in a table so it sticks around long enough to get - # its callback called. :) - self._table[id(resource)] = weakref.ref(obj, callback) - - -# singleton -ResourceManager = ResourceManager() -- cgit v1.2.1 From 60f40bb42ef11450599dc1bf8d54016991812811 Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Thu, 4 May 2017 10:12:24 -0400 Subject: remove back-reference from Transport to SSHClient It was only recently added, and it's not really needed after the ResourceManager removal. Removing it allows the SSHClient to be garbage-collected if only the Transport (and Channels) are still in use. --- paramiko/client.py | 1 - paramiko/transport.py | 5 ----- 2 files changed, 6 deletions(-) diff --git a/paramiko/client.py b/paramiko/client.py index 025cf96c..d947e1bc 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -339,7 +339,6 @@ class SSHClient (ClosingContextManager): if banner_timeout is not None: t.banner_timeout = banner_timeout t.start_client() - t.set_sshclient(self) server_key = t.get_remote_server_key() keytype = server_key.get_name() diff --git a/paramiko/transport.py b/paramiko/transport.py index 96bd4a07..a5fcd047 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -285,7 +285,6 @@ class Transport (threading.Thread, ClosingContextManager): arguments. """ self.active = False - self._sshclient = None if isinstance(sock, string_types): # convert "host:port" into (host, port) @@ -651,9 +650,6 @@ class Transport (threading.Thread, ClosingContextManager): Transport._modulus_pack = None return False - def set_sshclient(self, sshclient): - self._sshclient = sshclient - def close(self): """ Close this session, and any open channels that are tied to it. @@ -664,7 +660,6 @@ class Transport (threading.Thread, ClosingContextManager): for chan in list(self._channels.values()): chan._unlink() self.sock.close() - self._sshclient = None def get_remote_server_key(self): """ -- cgit v1.2.1 From 91d644ae47f8e1d64f9e3c677ce2a5415be0caef Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Fri, 5 May 2017 01:58:39 -0400 Subject: update changelog with fix for #949 --- sites/www/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index f7adf14a..553b98d1 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,10 @@ Changelog ========= +* :bug:`949` SSHClient and Transport could cause a memory leak if there's + a connection problem or protocol error, even if ``Transport.close()`` + is called. Thanks Kyle Agronick for the discovery and investigation, + and Pierce Lopez for assistance. * :support:`974 backported` Overhaul the codebase to be PEP-8, etc, compliant (i.e. passes the maintainer's preferred `flake8 `_ configuration) and add a ``flake8`` step to the Travis config. Big thanks to -- cgit v1.2.1 From 978b652cc4385f7706c49f3391bf2d91e93d4b18 Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Fri, 2 Jun 2017 03:18:00 -0400 Subject: re-enable client cleanup test for python3 --- tests/test_client.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 9c5761d6..f2f2ea45 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -280,13 +280,10 @@ class SSHClientTest (unittest.TestCase): verify that when an SSHClient is collected, its transport (and the transport's packetizer) is closed. """ - # Unclear why this is borked on Py3, but it is, and does not seem worth - # pursuing at the moment. Skipped on PyPy because it fails on travis - # for unknown reasons, works fine locally. - # XXX: It's the release of the references to e.g packetizer that fails - # in py3... - if not PY2 or platform.python_implementation() == "PyPy": + # Skipped on PyPy because it fails on travis for unknown reasons + if platform.python_implementation() == "PyPy": return + threading.Thread(target=self._run).start() self.tc = paramiko.SSHClient() @@ -304,8 +301,8 @@ class SSHClientTest (unittest.TestCase): del self.tc # force a collection to see whether the SSHClient object is deallocated - # correctly. 2 GCs are needed to make sure it's really collected on - # PyPy + # 2 GCs are needed on PyPy, time is needed for Python 3 + time.sleep(0.3) gc.collect() gc.collect() -- cgit v1.2.1 From d510b1ae91978a169bb4bd2a9e5e165d2311bc6b Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Mon, 5 Jun 2017 04:02:59 -0400 Subject: test transport security options can be set to defaults ensures all defaults key/cipher/digest etc types are supported --- tests/test_transport.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_transport.py b/tests/test_transport.py index 2ebdf854..c426cef1 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -165,6 +165,15 @@ class TransportTest(unittest.TestCase): except TypeError: pass + def test_1b_security_options_reset(self): + o = self.tc.get_security_options() + # should not throw any exceptions + o.ciphers = o.ciphers + o.digests = o.digests + o.key_types = o.key_types + o.kex = o.kex + o.compression = o.compression + def test_2_compute_key(self): self.tc.K = 123281095979686581523377256114209720774539068973101330872763622971399429481072519713536292772709507296759612401802191955568143056534122385270077606457721553469730659233569339356140085284052436697480759510519672848743794433460113118986816826624865291116513647975790797391795651716378444844877749505443714557929 self.tc.H = b'\x0C\x83\x07\xCD\xE6\x85\x6F\xF3\x0B\xA9\x36\x84\xEB\x0F\x04\xC2\x52\x0E\x9E\xD3' -- cgit v1.2.1 From 5caf0eaaa54fcfe43a9efd32652afa159d00c596 Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Mon, 5 Jun 2017 04:03:52 -0400 Subject: transport: _key_info for ecdsa-sha2-nistp384 and -nistp521 To support host keys of these key types, which are already in _preferred_keys! --- paramiko/transport.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/paramiko/transport.py b/paramiko/transport.py index 96bd4a07..5ab24980 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -210,6 +210,8 @@ class Transport (threading.Thread, ClosingContextManager): 'ssh-rsa': RSAKey, 'ssh-dss': DSSKey, 'ecdsa-sha2-nistp256': ECDSAKey, + 'ecdsa-sha2-nistp384': ECDSAKey, + 'ecdsa-sha2-nistp521': ECDSAKey, } _kex_info = { -- cgit v1.2.1 From 5f454ded40172c2b0d5763fb9b8df9ef55e5f05c Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Mon, 5 Jun 2017 04:14:22 -0400 Subject: changelog: update for #794 --- sites/www/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index f7adf14a..2e38fafb 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :bug:`794` Finishing touches to support ecdsa-sha2-nistp384 and + ecdsa-sha2-nistp521 host keys. Thanks ``@ncoult`` and ``@kasdoe`` for + reports. * :support:`974 backported` Overhaul the codebase to be PEP-8, etc, compliant (i.e. passes the maintainer's preferred `flake8 `_ configuration) and add a ``flake8`` step to the Travis config. Big thanks to -- cgit v1.2.1 From cbec93b9a466ec8dad856f89e90e5401e70730b6 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 5 Jun 2017 19:28:36 -0700 Subject: Overhaul changelog re #981. Closes #794. --- sites/www/changelog.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 2e38fafb..db2043cf 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,9 +2,11 @@ Changelog ========= -* :bug:`794` Finishing touches to support ecdsa-sha2-nistp384 and - ecdsa-sha2-nistp521 host keys. Thanks ``@ncoult`` and ``@kasdoe`` for - reports. +* :bug:`794` (via :issue:`981`) Prior support for ``ecdsa-sha2-nistp(384|521)`` + algorithms didn't fully extend to covering host keys, preventing connection + to hosts which only offer these key types and no others. This is now fixed. + Thanks to ``@ncoult`` and ``@kasdoe`` for reports and Pierce Lopez for the + patch. * :support:`974 backported` Overhaul the codebase to be PEP-8, etc, compliant (i.e. passes the maintainer's preferred `flake8 `_ configuration) and add a ``flake8`` step to the Travis config. Big thanks to -- cgit v1.2.1 From f499b43cffb855f09b426c7eaa3bcea849700a87 Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Mon, 20 Feb 2017 15:48:19 -0500 Subject: Transport: prefer ecdsa server host keys over rsa openssh client seems to prefer ecdsa server host keys (even over ssh-ed25519) --- paramiko/transport.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index 5ab24980..a308e0c9 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -122,9 +122,12 @@ class Transport (threading.Thread, ClosingContextManager): 'hmac-sha1', ) _preferred_keys = ( + 'ecdsa-sha2-nistp256', + 'ecdsa-sha2-nistp384', + 'ecdsa-sha2-nistp521', 'ssh-rsa', 'ssh-dss', - ) + tuple(ECDSAKey.supported_key_format_identifiers()) + ) _preferred_kex = ( 'diffie-hellman-group1-sha1', 'diffie-hellman-group14-sha1', -- cgit v1.2.1 From 691f6191601366255d55176bcdb103987626cc86 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 5 Jun 2017 19:59:28 -0700 Subject: Changelog closes #911, closes #900 --- sites/www/changelog.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index db2043cf..f8b3d328 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,11 @@ Changelog ========= +* :bug:`900` (via :issue:`911`) Prefer newer ``ecdsa-sha2-nistp`` keys over RSA + and DSA keys during host key selection. This improves compatibility with + OpenSSH, both in terms of general behavior, and also re: ability to properly + leverage OpenSSH-modified ``known_hosts`` files. Credit: ``@kasdoe`` for + original report/PR and Pierce Lopez for the second draft. * :bug:`794` (via :issue:`981`) Prior support for ``ecdsa-sha2-nistp(384|521)`` algorithms didn't fully extend to covering host keys, preventing connection to hosts which only offer these key types and no others. This is now fixed. -- cgit v1.2.1 From fd94765e54272706b55e50c692c814d07c8008d1 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 5 Jun 2017 20:49:29 -0700 Subject: Move to 'inv sites' style doc nitpicking. Existing just-warnings setup was old and not as rigorous as my usual. Required a lot of stupid little changes, but now same tooling for all projects, toot toot. --- .travis.yml | 7 ++--- paramiko/agent.py | 4 +-- paramiko/buffered_pipe.py | 10 +++--- paramiko/channel.py | 43 +++++++++++++++----------- paramiko/client.py | 20 ++++++------ paramiko/dsskey.py | 2 +- paramiko/ecdsakey.py | 2 +- paramiko/file.py | 20 ++++++------ paramiko/hostkeys.py | 4 +-- paramiko/kex_gss.py | 4 +-- paramiko/packet.py | 12 ++++--- paramiko/pkey.py | 32 +++++++++---------- paramiko/rsakey.py | 2 +- paramiko/server.py | 67 ++++++++++++++++++++-------------------- paramiko/sftp_client.py | 19 ++++++------ paramiko/sftp_file.py | 24 +++++++------- paramiko/sftp_handle.py | 10 +++--- paramiko/sftp_server.py | 2 +- paramiko/sftp_si.py | 20 ++++++------ paramiko/ssh_exception.py | 18 +++++------ paramiko/ssh_gss.py | 43 +++++++++----------------- paramiko/transport.py | 79 +++++++++++++++++++++++++++-------------------- sites/www/changelog.rst | 19 ++++++------ 23 files changed, 235 insertions(+), 228 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4cacb017..c8faf0a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,11 +20,8 @@ install: script: # Main tests, w/ coverage! - inv test --coverage - # Ensure documentation & invoke pipeline run OK. - # Run 'docs' first since its objects.inv is referred to by 'www'. - # Also force warnings to be errors since most of them tend to be actual - # problems. - - invoke docs -o -W www -o -W + # Ensure documentation builds, both sites, maxxed nitpicking + - inv sites # flake8 is now possible! - flake8 notifications: diff --git a/paramiko/agent.py b/paramiko/agent.py index a7cab4d8..bc857efa 100644 --- a/paramiko/agent.py +++ b/paramiko/agent.py @@ -253,7 +253,7 @@ class AgentServerProxy(AgentSSH): """ :param .Transport t: Transport used for SSH Agent communication forwarding - :raises SSHException: mostly if we lost the agent + :raises: `.SSHException` -- mostly if we lost the agent """ def __init__(self, t): AgentSSH.__init__(self) @@ -347,7 +347,7 @@ class Agent(AgentSSH): opened, if one is running. If no agent is running, initialization will succeed, but `get_keys` will return an empty tuple. - :raises SSHException: + :raises: `.SSHException` -- if an SSH agent is found, but speaks an incompatible protocol """ def __init__(self): diff --git a/paramiko/buffered_pipe.py b/paramiko/buffered_pipe.py index 9a65cd95..d9f5149d 100644 --- a/paramiko/buffered_pipe.py +++ b/paramiko/buffered_pipe.py @@ -90,7 +90,7 @@ class BufferedPipe (object): Feed new data into this pipe. This method is assumed to be called from a separate thread, so synchronization is done. - :param data: the data to add, as a `str` or `bytes` + :param data: the data to add, as a ``str`` or ``bytes`` """ self._lock.acquire() try: @@ -134,11 +134,11 @@ class BufferedPipe (object): :param int nbytes: maximum number of bytes to read :param float timeout: maximum seconds to wait (or ``None``, the default, to wait forever) - :return: the read data, as a `bytes` + :return: the read data, as a ``str`` or ``bytes`` - :raises PipeTimeout: - if a timeout was specified and no data was ready before that - timeout + :raises: + `.PipeTimeout` -- if a timeout was specified and no data was ready + before that timeout """ out = bytes() self._lock.acquire() diff --git a/paramiko/channel.py b/paramiko/channel.py index f2ecc4c0..1f603cf0 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -46,8 +46,9 @@ def open_only(func): """ Decorator for `.Channel` methods which performs an openness check. - :raises SSHException: - If the wrapped method is called on an unopened `.Channel`. + :raises: + `.SSHException` -- If the wrapped method is called on an unopened + `.Channel`. """ @wraps(func) def _check(self, *args, **kwds): @@ -165,8 +166,9 @@ class Channel (ClosingContextManager): :param int width_pixels: width (in pixels) of the terminal screen :param int height_pixels: height (in pixels) of the terminal screen - :raises SSHException: - if the request was rejected or the channel was closed + :raises: + `.SSHException` -- if the request was rejected or the channel was + closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) @@ -197,7 +199,8 @@ class Channel (ClosingContextManager): When the shell exits, the channel will be closed and can't be reused. You must open a new channel if you wish to open another shell. - :raises SSHException: if the request was rejected or the channel was + :raises: + `.SSHException` -- if the request was rejected or the channel was closed """ m = Message() @@ -222,7 +225,8 @@ class Channel (ClosingContextManager): :param str command: a shell command to execute. - :raises SSHException: if the request was rejected or the channel was + :raises: + `.SSHException` -- if the request was rejected or the channel was closed """ m = Message() @@ -247,8 +251,9 @@ class Channel (ClosingContextManager): :param str subsystem: name of the subsystem being requested. - :raises SSHException: - if the request was rejected or the channel was closed + :raises: + `.SSHException` -- if the request was rejected or the channel was + closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) @@ -271,8 +276,9 @@ class Channel (ClosingContextManager): :param int width_pixels: new width (in pixels) of the terminal screen :param int height_pixels: new height (in pixels) of the terminal screen - :raises SSHException: - if the request was rejected or the channel was closed + :raises: + `.SSHException` -- if the request was rejected or the channel was + closed """ m = Message() m.add_byte(cMSG_CHANNEL_REQUEST) @@ -313,11 +319,11 @@ class Channel (ClosingContextManager): `.Transport` or session's ``window_size`` (e.g. that set by the ``default_window_size`` kwarg for `.Transport.__init__`) will cause `.recv_exit_status` to hang indefinitely if it is called prior to a - sufficiently large `~Channel..read` (or if there are no threads - calling `~Channel.read` in the background). + sufficiently large `.Channel.recv` (or if there are no threads + calling `.Channel.recv` in the background). In these cases, ensuring that `.recv_exit_status` is called *after* - `~Channel.read` (or, again, using threads) can avoid the hang. + `.Channel.recv` (or, again, using threads) can avoid the hang. :return: the exit code (as an `int`) of the process on the server. @@ -391,8 +397,8 @@ class Channel (ClosingContextManager): if True, only a single x11 connection will be forwarded (by default, any number of x11 connections can arrive over this session) - :param function handler: - an optional handler to use for incoming X11 connections + :param handler: + an optional callable handler to use for incoming X11 connections :return: the auth_cookie used """ if auth_protocol is None: @@ -421,8 +427,9 @@ class Channel (ClosingContextManager): Request for a forward SSH Agent on this channel. This is only valid for an ssh-agent from OpenSSH !!! - :param function handler: - a required handler to use for incoming SSH Agent connections + :param handler: + a required callable handler to use for incoming SSH Agent + connections :return: True if we are ok, else False (at that time we always return ok) @@ -613,7 +620,7 @@ class Channel (ClosingContextManager): length zero is returned, the channel stream has closed. :param int nbytes: maximum number of bytes to read. - :return: received data, as a `bytes` + :return: received data, as a ``str``/``bytes``. :raises socket.timeout: if no data is ready before the timeout set by `settimeout`. diff --git a/paramiko/client.py b/paramiko/client.py index bbda1f80..ef1dc43e 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -91,7 +91,7 @@ class SSHClient (ClosingContextManager): :param str filename: the filename to read, or ``None`` - :raises IOError: + :raises: ``IOError`` -- if a filename was provided and the file could not be read """ if filename is None: @@ -118,7 +118,7 @@ class SSHClient (ClosingContextManager): :param str filename: the filename to read - :raises IOError: if the filename could not be read + :raises: ``IOError`` -- if the filename could not be read """ self._host_keys_filename = filename self._host_keys.load(filename) @@ -131,7 +131,7 @@ class SSHClient (ClosingContextManager): :param str filename: the filename to save to - :raises IOError: if the file could not be written + :raises: ``IOError`` -- if the file could not be written """ # update local host keys from file (in case other SSH clients @@ -282,10 +282,12 @@ class SSHClient (ClosingContextManager): :param float banner_timeout: an optional timeout (in seconds) to wait for the SSH banner to be presented. - :raises BadHostKeyException: if the server's host key could not be + :raises: + `.BadHostKeyException` -- if the server's host key could not be verified - :raises AuthenticationException: if authentication failed - :raises SSHException: if there was any other error connecting or + :raises: `.AuthenticationException` -- if authentication failed + :raises: + `.SSHException` -- if there was any other error connecting or establishing an SSH session :raises socket.error: if a socket error occurred while connecting @@ -416,12 +418,12 @@ class SSHClient (ClosingContextManager): interpreted the same way as by the built-in ``file()`` function in Python :param int timeout: - set command's channel timeout. See `Channel.settimeout`.settimeout + set command's channel timeout. See `.Channel.settimeout` :return: the stdin, stdout, and stderr of the executing command, as a 3-tuple - :raises SSHException: if the server fails to execute the command + :raises: `.SSHException` -- if the server fails to execute the command """ chan = self._transport.open_session(timeout=timeout) if get_pty: @@ -448,7 +450,7 @@ class SSHClient (ClosingContextManager): :param int height_pixels: the height (in pixels) of the terminal window :return: a new `.Channel` connected to the remote shell - :raises SSHException: if the server fails to invoke a shell + :raises: `.SSHException` -- if the server fails to invoke a shell """ chan = self._transport.open_session() chan.get_pty(term, width, height, width_pixels, height_pixels) diff --git a/paramiko/dsskey.py b/paramiko/dsskey.py index ac6875bc..55ef1e9b 100644 --- a/paramiko/dsskey.py +++ b/paramiko/dsskey.py @@ -208,7 +208,7 @@ class DSSKey(PKey): generate a new host key or authentication key. :param int bits: number of bits the generated key should be. - :param function progress_func: Unused + :param progress_func: Unused :return: new `.DSSKey` private key """ numbers = dsa.generate_private_key( diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py index 51f8d8ce..f5dacac8 100644 --- a/paramiko/ecdsakey.py +++ b/paramiko/ecdsakey.py @@ -231,7 +231,7 @@ class ECDSAKey(PKey): Generate a new private ECDSA key. This factory function can be used to generate a new host key or authentication key. - :param function progress_func: Not used for this type of key. + :param progress_func: Not used for this type of key. :returns: A new private key (`.ECDSAKey`) object """ if bits is not None: diff --git a/paramiko/file.py b/paramiko/file.py index e31ad9dd..5212091a 100644 --- a/paramiko/file.py +++ b/paramiko/file.py @@ -67,7 +67,7 @@ class BufferedFile (ClosingContextManager): file. This iterator happens to return the file itself, since a file is its own iterator. - :raises ValueError: if the file is closed. + :raises: ``ValueError`` -- if the file is closed. """ if self._closed: raise ValueError('I/O operation on closed file') @@ -93,10 +93,10 @@ class BufferedFile (ClosingContextManager): def next(self): """ Returns the next line from the input, or raises - `~exceptions.StopIteration` when EOF is hit. Unlike Python file + ``StopIteration`` when EOF is hit. Unlike Python file objects, it's okay to mix calls to `next` and `readline`. - :raises StopIteration: when the end of the file is reached. + :raises: ``StopIteration`` -- when the end of the file is reached. :returns: a line (`str`) read from the file. """ @@ -107,11 +107,11 @@ class BufferedFile (ClosingContextManager): else: def __next__(self): """ - Returns the next line from the input, or raises `.StopIteration` + Returns the next line from the input, or raises ``StopIteration`` when EOF is hit. Unlike python file objects, it's okay to mix calls to `.next` and `.readline`. - :raises StopIteration: when the end of the file is reached. + :raises: ``StopIteration`` -- when the end of the file is reached. :returns: a line (`str`) read from the file. """ @@ -152,8 +152,8 @@ class BufferedFile (ClosingContextManager): def readinto(self, buff): """ - Read up to ``len(buff)`` bytes into :class:`bytearray` *buff* and - return the number of bytes read. + Read up to ``len(buff)`` bytes into ``bytearray`` *buff* and return the + number of bytes read. :returns: The number of bytes read. @@ -368,7 +368,7 @@ class BufferedFile (ClosingContextManager): type of movement: 0 = absolute; 1 = relative to the current position; 2 = relative to the end of the file. - :raises IOError: if the file doesn't support random access. + :raises: ``IOError`` -- if the file doesn't support random access. """ raise IOError('File does not support seeking.') @@ -389,7 +389,7 @@ class BufferedFile (ClosingContextManager): written yet. (Use `flush` or `close` to force buffered data to be written out.) - :param str/bytes data: data to write + :param data: ``str``/``bytes`` data to write """ data = b(data) if self._closed: @@ -423,7 +423,7 @@ class BufferedFile (ClosingContextManager): name is intended to match `readlines`; `writelines` does not add line separators.) - :param iterable sequence: an iterable sequence of strings. + :param sequence: an iterable sequence of strings. """ for line in sequence: self.write(line) diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index 18a0d333..008ba592 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -90,7 +90,7 @@ class HostKeys (MutableMapping): :param str filename: name of the file to read host keys from - :raises IOError: if there was an error reading the file + :raises: ``IOError`` -- if there was an error reading the file """ with open(filename, 'r') as f: for lineno, line in enumerate(f, 1): @@ -118,7 +118,7 @@ class HostKeys (MutableMapping): :param str filename: name of the file to write - :raises IOError: if there was an error writing the file + :raises: ``IOError`` -- if there was an error writing the file .. versionadded:: 1.6.1 """ diff --git a/paramiko/kex_gss.py b/paramiko/kex_gss.py index 40ceb5cd..ba24c0a0 100644 --- a/paramiko/kex_gss.py +++ b/paramiko/kex_gss.py @@ -108,7 +108,7 @@ class KexGSSGroup1(object): """ Parse the next packet. - :param char ptype: The type of the incoming packet + :param ptype: The (string) type of the incoming packet :param `.Message` m: The paket content """ if self.transport.server_mode and (ptype == MSG_KEXGSS_INIT): @@ -345,7 +345,7 @@ class KexGSSGex(object): """ Parse the next packet. - :param char ptype: The type of the incoming packet + :param ptype: The (string) type of the incoming packet :param `.Message` m: The paket content """ if ptype == MSG_KEXGSS_GROUPREQ: diff --git a/paramiko/packet.py b/paramiko/packet.py index 16288a0a..95a26c6e 100644 --- a/paramiko/packet.py +++ b/paramiko/packet.py @@ -43,6 +43,9 @@ def compute_hmac(key, message, digest_class): class NeedRekeyException (Exception): + """ + Exception indicating a rekey is needed. + """ pass @@ -253,8 +256,9 @@ class Packetizer (object): :param int n: number of bytes to read :return: the data read, as a `str` - :raises EOFError: - if the socket was closed before all the bytes could be read + :raises: + ``EOFError`` -- if the socket was closed before all the bytes could + be read """ out = bytes() # handle over-reading from reading the banner line @@ -413,8 +417,8 @@ class Packetizer (object): Only one thread should ever be in this function (no other locking is done). - :raises SSHException: if the packet is mangled - :raises NeedRekeyException: if the transport should rekey + :raises: `.SSHException` -- if the packet is mangled + :raises: `.NeedRekeyException` -- if the transport should rekey """ header = self.read_all(self.__block_size_in, check_rekey=True) if self.__block_engine_in is not None: diff --git a/paramiko/pkey.py b/paramiko/pkey.py index af9370fc..f5b0cd18 100644 --- a/paramiko/pkey.py +++ b/paramiko/pkey.py @@ -68,7 +68,7 @@ class PKey(object): :param str data: an optional string containing a public key of this type - :raises SSHException: + :raises: `.SSHException` -- if a key cannot be created from the ``data`` or ``msg`` given, or no key was passed in. """ @@ -95,7 +95,7 @@ class PKey(object): of the key are compared, so a public key will compare equal to its corresponding private key. - :param .Pkey other: key to compare to. + :param .PKey other: key to compare to. """ hs = hash(self) ho = hash(other) @@ -191,10 +191,10 @@ class PKey(object): encrypted :return: a new `.PKey` based on the given private key - :raises IOError: if there was an error reading the file - :raises PasswordRequiredException: if the private key file is + :raises: ``IOError`` -- if there was an error reading the file + :raises: `.PasswordRequiredException` -- if the private key file is encrypted, and ``password`` is ``None`` - :raises SSHException: if the key file is invalid + :raises: `.SSHException` -- if the key file is invalid """ key = cls(filename=filename, password=password) return key @@ -212,10 +212,10 @@ class PKey(object): an optional password to use to decrypt the key, if it's encrypted :return: a new `.PKey` based on the given private key - :raises IOError: if there was an error reading the key - :raises PasswordRequiredException: + :raises: ``IOError`` -- if there was an error reading the key + :raises: `.PasswordRequiredException` -- if the private key file is encrypted, and ``password`` is ``None`` - :raises SSHException: if the key file is invalid + :raises: `.SSHException` -- if the key file is invalid """ key = cls(file_obj=file_obj, password=password) return key @@ -229,8 +229,8 @@ class PKey(object): :param str password: an optional password to use to encrypt the key file - :raises IOError: if there was an error writing the file - :raises SSHException: if the key is invalid + :raises: ``IOError`` -- if there was an error writing the file + :raises: `.SSHException` -- if the key is invalid """ raise Exception('Not implemented in PKey') @@ -242,8 +242,8 @@ class PKey(object): :param file_obj: the file-like object to write into :param str password: an optional password to use to encrypt the key - :raises IOError: if there was an error writing to the file - :raises SSHException: if the key is invalid + :raises: ``IOError`` -- if there was an error writing to the file + :raises: `.SSHException` -- if the key is invalid """ raise Exception('Not implemented in PKey') @@ -263,10 +263,10 @@ class PKey(object): encrypted. :return: data blob (`str`) that makes up the private key. - :raises IOError: if there was an error reading the file. - :raises PasswordRequiredException: if the private key file is + :raises: ``IOError`` -- if there was an error reading the file. + :raises: `.PasswordRequiredException` -- if the private key file is encrypted, and ``password`` is ``None``. - :raises SSHException: if the key file is invalid. + :raises: `.SSHException` -- if the key file is invalid. """ with open(filename, 'r') as f: data = self._read_private_key(tag, f, password) @@ -340,7 +340,7 @@ class PKey(object): :param str data: data blob that makes up the private key. :param str password: an optional password to use to encrypt the file. - :raises IOError: if there was an error writing the file. + :raises: ``IOError`` -- if there was an error writing the file. """ with open(filename, 'w') as f: os.chmod(filename, o600) diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py index 8ccf4c30..f6d11a09 100644 --- a/paramiko/rsakey.py +++ b/paramiko/rsakey.py @@ -160,7 +160,7 @@ class RSAKey(PKey): generate a new host key or authentication key. :param int bits: number of bits the generated key should be. - :param function progress_func: Unused + :param progress_func: Unused :return: new `.RSAKey` private key """ key = rsa.generate_private_key( diff --git a/paramiko/server.py b/paramiko/server.py index b2a07916..adc606bf 100644 --- a/paramiko/server.py +++ b/paramiko/server.py @@ -106,15 +106,15 @@ class ServerInterface (object): Determine if a client may open channels with no (further) authentication. - Return `.AUTH_FAILED` if the client must authenticate, or - `.AUTH_SUCCESSFUL` if it's okay for the client to not + Return ``AUTH_FAILED`` if the client must authenticate, or + ``AUTH_SUCCESSFUL`` if it's okay for the client to not authenticate. - The default implementation always returns `.AUTH_FAILED`. + The default implementation always returns ``AUTH_FAILED``. :param str username: the username of the client. :return: - `.AUTH_FAILED` if the authentication fails; `.AUTH_SUCCESSFUL` if + ``AUTH_FAILED`` if the authentication fails; ``AUTH_SUCCESSFUL`` if it succeeds. :rtype: int """ @@ -125,21 +125,21 @@ class ServerInterface (object): Determine if a given username and password supplied by the client is acceptable for use in authentication. - Return `.AUTH_FAILED` if the password is not accepted, - `.AUTH_SUCCESSFUL` if the password is accepted and completes - the authentication, or `.AUTH_PARTIALLY_SUCCESSFUL` if your + Return ``AUTH_FAILED`` if the password is not accepted, + ``AUTH_SUCCESSFUL`` if the password is accepted and completes + the authentication, or ``AUTH_PARTIALLY_SUCCESSFUL`` if your authentication is stateful, and this key is accepted for authentication, but more authentication is required. (In this latter case, `get_allowed_auths` will be called to report to the client what options it has for continuing the authentication.) - The default implementation always returns `.AUTH_FAILED`. + The default implementation always returns ``AUTH_FAILED``. :param str username: the username of the authenticating client. :param str password: the password given by the client. :return: - `.AUTH_FAILED` if the authentication fails; `.AUTH_SUCCESSFUL` if - it succeeds; `.AUTH_PARTIALLY_SUCCESSFUL` if the password auth is + ``AUTH_FAILED`` if the authentication fails; ``AUTH_SUCCESSFUL`` if + it succeeds; ``AUTH_PARTIALLY_SUCCESSFUL`` if the password auth is successful, but authentication must continue. :rtype: int """ @@ -152,9 +152,9 @@ class ServerInterface (object): check the username and key and decide if you would accept a signature made using this key. - Return `.AUTH_FAILED` if the key is not accepted, - `.AUTH_SUCCESSFUL` if the key is accepted and completes the - authentication, or `.AUTH_PARTIALLY_SUCCESSFUL` if your + Return ``AUTH_FAILED`` if the key is not accepted, + ``AUTH_SUCCESSFUL`` if the key is accepted and completes the + authentication, or ``AUTH_PARTIALLY_SUCCESSFUL`` if your authentication is stateful, and this password is accepted for authentication, but more authentication is required. (In this latter case, `get_allowed_auths` will be called to report to the client what @@ -164,13 +164,13 @@ class ServerInterface (object): If you're willing to accept the key, Paramiko will do the work of verifying the client's signature. - The default implementation always returns `.AUTH_FAILED`. + The default implementation always returns ``AUTH_FAILED``. :param str username: the username of the authenticating client :param .PKey key: the key object provided by the client :return: - `.AUTH_FAILED` if the client can't authenticate with this key; - `.AUTH_SUCCESSFUL` if it can; `.AUTH_PARTIALLY_SUCCESSFUL` if it + ``AUTH_FAILED`` if the client can't authenticate with this key; + ``AUTH_SUCCESSFUL`` if it can; ``AUTH_PARTIALLY_SUCCESSFUL`` if it can authenticate with this key but must continue with authentication :rtype: int @@ -184,19 +184,19 @@ class ServerInterface (object): ``"keyboard-interactive"`` auth type, which requires you to send a series of questions for the client to answer. - Return `.AUTH_FAILED` if this auth method isn't supported. Otherwise, + Return ``AUTH_FAILED`` if this auth method isn't supported. Otherwise, you should return an `.InteractiveQuery` object containing the prompts and instructions for the user. The response will be sent via a call to `check_auth_interactive_response`. - The default implementation always returns `.AUTH_FAILED`. + The default implementation always returns ``AUTH_FAILED``. :param str username: the username of the authenticating client :param str submethods: a comma-separated list of methods preferred by the client (usually empty) :return: - `.AUTH_FAILED` if this auth method isn't supported; otherwise an + ``AUTH_FAILED`` if this auth method isn't supported; otherwise an object containing queries for the user :rtype: int or `.InteractiveQuery` """ @@ -208,9 +208,9 @@ class ServerInterface (object): supported. You should override this method in server mode if you want to support the ``"keyboard-interactive"`` auth type. - Return `.AUTH_FAILED` if the responses are not accepted, - `.AUTH_SUCCESSFUL` if the responses are accepted and complete - the authentication, or `.AUTH_PARTIALLY_SUCCESSFUL` if your + Return ``AUTH_FAILED`` if the responses are not accepted, + ``AUTH_SUCCESSFUL`` if the responses are accepted and complete + the authentication, or ``AUTH_PARTIALLY_SUCCESSFUL`` if your authentication is stateful, and this set of responses is accepted for authentication, but more authentication is required. (In this latter case, `get_allowed_auths` will be called to report to the client what @@ -221,12 +221,12 @@ class ServerInterface (object): client to respond with more answers, calling this method again. This cycle can continue indefinitely. - The default implementation always returns `.AUTH_FAILED`. + The default implementation always returns ``AUTH_FAILED``. :param list responses: list of `str` responses from the client :return: - `.AUTH_FAILED` if the authentication fails; `.AUTH_SUCCESSFUL` if - it succeeds; `.AUTH_PARTIALLY_SUCCESSFUL` if the interactive auth + ``AUTH_FAILED`` if the authentication fails; ``AUTH_SUCCESSFUL`` if + it succeeds; ``AUTH_PARTIALLY_SUCCESSFUL`` if the interactive auth is successful, but authentication must continue; otherwise an object containing queries for the user :rtype: int or `.InteractiveQuery` @@ -243,8 +243,8 @@ class ServerInterface (object): :param str username: The username of the authenticating client :param int gss_authenticated: The result of the krb5 authentication :param str cc_filename: The krb5 client credentials cache filename - :return: `.AUTH_FAILED` if the user is not authenticated otherwise - `.AUTH_SUCCESSFUL` + :return: ``AUTH_FAILED`` if the user is not authenticated otherwise + ``AUTH_SUCCESSFUL`` :rtype: int :note: Kerberos credential delegation is not supported. :see: `.ssh_gss` @@ -257,7 +257,7 @@ class ServerInterface (object): your local kerberos library to make sure that the krb5_principal has an account on the server and is allowed to log in as a user. - :see: `http://www.unix.com/man-page/all/3/krb5_kuserok/` + :see: http://www.unix.com/man-page/all/3/krb5_kuserok/ """ if gss_authenticated == AUTH_SUCCESSFUL: return AUTH_SUCCESSFUL @@ -275,8 +275,8 @@ class ServerInterface (object): :param str username: The username of the authenticating client :param int gss_authenticated: The result of the krb5 authentication :param str cc_filename: The krb5 client credentials cache filename - :return: `.AUTH_FAILED` if the user is not authenticated otherwise - `.AUTH_SUCCESSFUL` + :return: ``AUTH_FAILED`` if the user is not authenticated otherwise + ``AUTH_SUCCESSFUL`` :rtype: int :note: Kerberos credential delegation is not supported. :see: `.ssh_gss` `.kex_gss` @@ -289,7 +289,7 @@ class ServerInterface (object): your local kerberos library to make sure that the krb5_principal has an account on the server and is allowed to log in as a user. - :see: `http://www.unix.com/man-page/all/3/krb5_kuserok/` + :see: http://www.unix.com/man-page/all/3/krb5_kuserok/ """ if gss_authenticated == AUTH_SUCCESSFUL: return AUTH_SUCCESSFUL @@ -301,9 +301,8 @@ class ServerInterface (object): authentication. The default implementation always returns false. - :return: True if GSSAPI authentication is enabled otherwise false - :rtype: Boolean - :see: : `.ssh_gss` + :returns bool: Whether GSSAPI authentication is enabled. + :see: `.ssh_gss` """ UseGSSAPI = False return UseGSSAPI diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index ea6f88a9..12fccb2f 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -83,8 +83,8 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param .Channel sock: an open `.Channel` using the ``"sftp"`` subsystem - :raises SSHException: if there's an exception while negotiating - sftp + :raises: + `.SSHException` -- if there's an exception while negotiating sftp """ BaseSFTP.__init__(self) self.sock = sock @@ -321,7 +321,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param int bufsize: desired buffering (-1 = default buffer size) :return: an `.SFTPFile` object representing the open file - :raises IOError: if the file could not be opened. + :raises: ``IOError`` -- if the file could not be opened. """ filename = self._adjust_cwd(filename) self._log(DEBUG, 'open(%r, %r)' % (filename, mode)) @@ -356,7 +356,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param str path: path (absolute or relative) of the file to remove - :raises IOError: if the path refers to a folder (directory) + :raises: ``IOError`` -- if the path refers to a folder (directory) """ path = self._adjust_cwd(path) self._log(DEBUG, 'remove(%r)' % path) @@ -371,7 +371,8 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param str oldpath: existing name of the file or folder :param str newpath: new name for the file or folder - :raises IOError: if ``newpath`` is a folder, or something else goes + :raises: + ``IOError`` -- if ``newpath`` is a folder, or something else goes wrong """ oldpath = self._adjust_cwd(oldpath) @@ -522,8 +523,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): method on Python file objects. :param str path: path of the file to modify - :param size: the new size of the file - :type size: int or long + :param int size: the new size of the file """ path = self._adjust_cwd(path) self._log(DEBUG, 'truncate(%r, %r)' % (path, size)) @@ -562,7 +562,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param str path: path to be normalized :return: normalized form of the given path (as a `str`) - :raises IOError: if the path can't be resolved on the server + :raises: ``IOError`` -- if the path can't be resolved on the server """ path = self._adjust_cwd(path) self._log(DEBUG, 'normalize(%r)' % path) @@ -585,7 +585,8 @@ class SFTPClient(BaseSFTP, ClosingContextManager): :param str path: new current working directory - :raises IOError: if the requested path doesn't exist on the server + :raises: + ``IOError`` -- if the requested path doesn't exist on the server .. versionadded:: 1.4 """ diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py index 58653c79..63ababdb 100644 --- a/paramiko/sftp_file.py +++ b/paramiko/sftp_file.py @@ -251,6 +251,11 @@ class SFTPFile (BufferedFile): return True def seek(self, offset, whence=0): + """ + Set the file’s current position. + + See `file.seek` for details. + """ self.flush() if whence == self.SEEK_SET: self._realpos = self._pos = offset @@ -267,8 +272,8 @@ class SFTPFile (BufferedFile): exactly like `.SFTPClient.stat`, except that it operates on an already-open file. - :return: an `.SFTPAttributes` object containing attributes about this - file. + :returns: + an `.SFTPAttributes` object containing attributes about this file. """ t, msg = self.sftp._request(CMD_FSTAT, self.handle) if t != CMD_ATTRS: @@ -332,7 +337,6 @@ class SFTPFile (BufferedFile): Python file objects. :param size: the new size of the file - :type size: int or long """ self.sftp._log( DEBUG, @@ -370,21 +374,18 @@ class SFTPFile (BufferedFile): :param offset: offset into the file to begin hashing (0 means to start from the beginning) - :type offset: int or long :param length: number of bytes to hash (0 means continue to the end of the file) - :type length: int or long :param int block_size: number of bytes to hash per result (must not be less than 256; 0 means to compute only one hash of the entire segment) - :type block_size: int :return: `str` of bytes representing the hash of each block, concatenated together - :raises IOError: if the server doesn't support the "check-file" - extension, or possibly doesn't support the hash algorithm - requested + :raises: + ``IOError`` -- if the server doesn't support the "check-file" + extension, or possibly doesn't support the hash algorithm requested .. note:: Many (most?) servers don't support this extension yet. @@ -466,9 +467,8 @@ class SFTPFile (BufferedFile): once. :param chunks: - a list of (offset, length) tuples indicating which sections of the - file to read - :type chunks: list(tuple(long, int)) + a list of ``(offset, length)`` tuples indicating which sections of + the file to read :return: a list of blocks read, in the same order as in ``chunks`` .. versionadded:: 1.5.4 diff --git a/paramiko/sftp_handle.py b/paramiko/sftp_handle.py index 2d2e621c..ca473900 100644 --- a/paramiko/sftp_handle.py +++ b/paramiko/sftp_handle.py @@ -77,7 +77,7 @@ class SFTPHandle (ClosingContextManager): to be 64 bits. If the end of the file has been reached, this method may return an - empty string to signify EOF, or it may also return `.SFTP_EOF`. + empty string to signify EOF, or it may also return ``SFTP_EOF``. The default implementation checks for an attribute on ``self`` named ``readfile``, and if present, performs the read operation on the Python @@ -85,7 +85,6 @@ class SFTPHandle (ClosingContextManager): common case where you are wrapping a Python file object.) :param offset: position in the file to start reading from. - :type offset: int or long :param int length: number of bytes to attempt to read. :return: data read from the file, or an SFTP error code, as a `str`. """ @@ -120,9 +119,8 @@ class SFTPHandle (ClosingContextManager): refer to the same file. :param offset: position in the file to start reading from. - :type offset: int or long :param str data: data to write into the file. - :return: an SFTP error code like `.SFTP_OK`. + :return: an SFTP error code like ``SFTP_OK``. """ writefile = getattr(self, 'writefile', None) if writefile is None: @@ -152,7 +150,7 @@ class SFTPHandle (ClosingContextManager): :return: an attributes object for the given file, or an SFTP error code - (like `.SFTP_PERMISSION_DENIED`). + (like ``SFTP_PERMISSION_DENIED``). :rtype: `.SFTPAttributes` or error code """ return SFTP_OP_UNSUPPORTED @@ -164,7 +162,7 @@ class SFTPHandle (ClosingContextManager): check for the presence of fields before using them. :param .SFTPAttributes attr: the attributes to change on this file. - :return: an `int` error code like `.SFTP_OK`. + :return: an `int` error code like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED diff --git a/paramiko/sftp_server.py b/paramiko/sftp_server.py index f94c5e39..1cfe286b 100644 --- a/paramiko/sftp_server.py +++ b/paramiko/sftp_server.py @@ -72,7 +72,7 @@ class SFTPServer (BaseSFTP, SubsystemHandler): :param str name: name of the requested subsystem. :param .ServerInterface server: the server object associated with this channel and subsystem - :param class sftp_si: + :param sftp_si: a subclass of `.SFTPServerInterface` to use for handling individual requests. """ diff --git a/paramiko/sftp_si.py b/paramiko/sftp_si.py index c335eaec..09e7025c 100644 --- a/paramiko/sftp_si.py +++ b/paramiko/sftp_si.py @@ -72,7 +72,7 @@ class SFTPServerInterface (object): on that file. On success, a new object subclassed from `.SFTPHandle` should be returned. This handle will be used for future operations on the file (read, write, etc). On failure, an error code such as - `.SFTP_PERMISSION_DENIED` should be returned. + ``SFTP_PERMISSION_DENIED`` should be returned. ``flags`` contains the requested mode for opening (read-only, write-append, etc) as a bitset of flags from the ``os`` module: @@ -120,7 +120,7 @@ class SFTPServerInterface (object): `.SFTPAttributes.from_stat` will usually do what you want. In case of an error, you should return one of the ``SFTP_*`` error - codes, such as `.SFTP_PERMISSION_DENIED`. + codes, such as ``SFTP_PERMISSION_DENIED``. :param str path: the requested path (relative or absolute) to be listed. @@ -150,7 +150,7 @@ class SFTPServerInterface (object): for. :return: an `.SFTPAttributes` object for the given file, or an SFTP error - code (like `.SFTP_PERMISSION_DENIED`). + code (like ``SFTP_PERMISSION_DENIED``). """ return SFTP_OP_UNSUPPORTED @@ -168,7 +168,7 @@ class SFTPServerInterface (object): :type path: str :return: an `.SFTPAttributes` object for the given file, or an SFTP error - code (like `.SFTP_PERMISSION_DENIED`). + code (like ``SFTP_PERMISSION_DENIED``). """ return SFTP_OP_UNSUPPORTED @@ -178,7 +178,7 @@ class SFTPServerInterface (object): :param str path: the requested path (relative or absolute) of the file to delete. - :return: an SFTP error code `int` like `.SFTP_OK`. + :return: an SFTP error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED @@ -197,7 +197,7 @@ class SFTPServerInterface (object): :param str oldpath: the requested path (relative or absolute) of the existing file. :param str newpath: the requested new path of the file. - :return: an SFTP error code `int` like `.SFTP_OK`. + :return: an SFTP error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED @@ -214,7 +214,7 @@ class SFTPServerInterface (object): :param str path: requested path (relative or absolute) of the new folder. :param .SFTPAttributes attr: requested attributes of the new folder. - :return: an SFTP error code `int` like `.SFTP_OK`. + :return: an SFTP error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED @@ -226,7 +226,7 @@ class SFTPServerInterface (object): :param str path: requested path (relative or absolute) of the folder to remove. - :return: an SFTP error code `int` like `.SFTP_OK`. + :return: an SFTP error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED @@ -241,7 +241,7 @@ class SFTPServerInterface (object): :param attr: requested attributes to change on the file (an `.SFTPAttributes` object) - :return: an error code `int` like `.SFTP_OK`. + :return: an error code `int` like ``SFTP_OK``. """ return SFTP_OP_UNSUPPORTED @@ -277,7 +277,7 @@ class SFTPServerInterface (object): :param str path: path (relative or absolute) of the symbolic link. :return: the target `str` path of the symbolic link, or an error code like - `.SFTP_NO_SUCH_FILE`. + ``SFTP_NO_SUCH_FILE``. """ return SFTP_OP_UNSUPPORTED diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index 280a7f39..e9ab8d66 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -50,12 +50,10 @@ class BadAuthenticationType (AuthenticationException): the server isn't allowing that type. (It may only allow public-key, for example.) - :ivar list allowed_types: - list of allowed authentication types provided by the server (possible - values are: ``"none"``, ``"password"``, and ``"publickey"``). - .. versionadded:: 1.1 """ + #: list of allowed authentication types provided by the server (possible + #: values are: ``"none"``, ``"password"``, and ``"publickey"``). allowed_types = [] def __init__(self, explanation, types): @@ -87,7 +85,7 @@ class ChannelException (SSHException): """ Exception raised when an attempt to open a new `.Channel` fails. - :ivar int code: the error code returned by the server + :param int code: the error code returned by the server .. versionadded:: 1.6 """ @@ -102,9 +100,9 @@ class BadHostKeyException (SSHException): """ The host key given by the SSH server did not match what we were expecting. - :ivar str hostname: the hostname of the SSH server - :ivar PKey got_key: the host key presented by the server - :ivar PKey expected_key: the host key expected + :param str hostname: the hostname of the SSH server + :param PKey got_key: the host key presented by the server + :param PKey expected_key: the host key expected .. versionadded:: 1.6 """ @@ -125,8 +123,8 @@ class ProxyCommandFailure (SSHException): """ The "ProxyCommand" found in the .ssh/config file returned an error. - :ivar str command: The command line that is generating this exception. - :ivar str error: The error captured from the proxy command output. + :param str command: The command line that is generating this exception. + :param str error: The error captured from the proxy command output. """ def __init__(self, command, error): SSHException.__init__(self, diff --git a/paramiko/ssh_gss.py b/paramiko/ssh_gss.py index 9c88c6fc..414485f9 100644 --- a/paramiko/ssh_gss.py +++ b/paramiko/ssh_gss.py @@ -72,9 +72,8 @@ def GSSAuth(auth_method, gss_deleg_creds=True): We delegate credentials by default. :return: Either an `._SSH_GSSAPI` (Unix) object or an `_SSH_SSPI` (Windows) object - :rtype: Object - :raise ImportError: If no GSS-API / SSPI module could be imported. + :raises: ``ImportError`` -- If no GSS-API / SSPI module could be imported. :see: `RFC 4462 `_ :note: Check for the available API and return either an `._SSH_GSSAPI` @@ -131,7 +130,6 @@ class _SSH_GSSAuth(object): as the only service value. :param str service: The desired SSH service - :rtype: Void """ if service.find("ssh-"): self._service = service @@ -142,7 +140,6 @@ class _SSH_GSSAuth(object): username is not set by C{ssh_init_sec_context}. :param str username: The name of the user who attempts to login - :rtype: Void """ self._username = username @@ -155,7 +152,6 @@ class _SSH_GSSAuth(object): :return: A byte sequence containing the number of supported OIDs, the length of the OID and the actual OID encoded with DER - :rtype: Bytes :note: In server mode we just return the OID length and the DER encoded OID. """ @@ -172,7 +168,6 @@ class _SSH_GSSAuth(object): :param str desired_mech: The desired GSS-API mechanism of the client :return: ``True`` if the given OID is supported, otherwise C{False} - :rtype: Boolean """ mech, __ = decoder.decode(desired_mech) if mech.__str__() != self._krb5_mech: @@ -187,7 +182,6 @@ class _SSH_GSSAuth(object): :param int integer: The integer value to convert :return: The byte sequence of an 32 bit integer - :rtype: Bytes """ return struct.pack("!I", integer) @@ -207,7 +201,6 @@ class _SSH_GSSAuth(object): string service (ssh-connection), string authentication-method (gssapi-with-mic or gssapi-keyex) - :rtype: Bytes """ mic = self._make_uint32(len(session_id)) mic += session_id @@ -256,11 +249,11 @@ class _SSH_GSSAPI(_SSH_GSSAuth): ("pseudo negotiated" mechanism, because we support just the krb5 mechanism :-)) :param str recv_token: The GSS-API token received from the Server - :raise SSHException: Is raised if the desired mechanism of the client - is not supported + :raises: + `.SSHException` -- Is raised if the desired mechanism of the client + is not supported :return: A ``String`` if the GSS-API has returned a token or ``None`` if no token was returned - :rtype: String or None """ self._username = username self._gss_host = target @@ -304,8 +297,6 @@ class _SSH_GSSAPI(_SSH_GSSAuth): gssapi-keyex: Returns the MIC token from GSS-API with the SSH session ID as message. - :rtype: String - :see: `._ssh_build_mic` """ self._session_id = session_id if not gss_kex: @@ -329,7 +320,6 @@ class _SSH_GSSAPI(_SSH_GSSAuth): if it's not the initial call. :return: A ``String`` if the GSS-API has returned a token or ``None`` if no token was returned - :rtype: String or None """ # hostname and username are not required for GSSAPI, but for SSPI self._gss_host = hostname @@ -348,7 +338,7 @@ class _SSH_GSSAPI(_SSH_GSSAuth): :param str session_id: The SSH session ID :param str username: The name of the user who attempts to login :return: None if the MIC check was successful - :raises gssapi.GSSException: if the MIC check failed + :raises: ``gssapi.GSSException`` -- if the MIC check failed """ self._session_id = session_id self._username = username @@ -371,7 +361,6 @@ class _SSH_GSSAPI(_SSH_GSSAuth): Checks if credentials are delegated (server mode). :return: ``True`` if credentials are delegated, otherwise ``False`` - :rtype: bool """ if self._gss_srv_ctxt.delegated_cred is not None: return True @@ -384,8 +373,9 @@ class _SSH_GSSAPI(_SSH_GSSAuth): (server mode). :param str client_token: The GSS-API token received form the client - :raise NotImplementedError: Credential delegation is currently not - supported in server mode + :raises: + ``NotImplementedError`` -- Credential delegation is currently not + supported in server mode """ raise NotImplementedError @@ -427,11 +417,11 @@ class _SSH_SSPI(_SSH_GSSAuth): ("pseudo negotiated" mechanism, because we support just the krb5 mechanism :-)) :param recv_token: The SSPI token received from the Server - :raise SSHException: Is raised if the desired mechanism of the client - is not supported + :raises: + `.SSHException` -- Is raised if the desired mechanism of the client + is not supported :return: A ``String`` if the SSPI has returned a token or ``None`` if no token was returned - :rtype: String or None """ self._username = username self._gss_host = target @@ -476,8 +466,6 @@ class _SSH_SSPI(_SSH_GSSAuth): gssapi-keyex: Returns the MIC token from SSPI with the SSH session ID as message. - :rtype: String - :see: `._ssh_build_mic` """ self._session_id = session_id if not gss_kex: @@ -501,7 +489,6 @@ class _SSH_SSPI(_SSH_GSSAuth): if it's not the initial call. :return: A ``String`` if the SSPI has returned a token or ``None`` if no token was returned - :rtype: String or None """ self._gss_host = hostname self._username = username @@ -522,7 +509,7 @@ class _SSH_SSPI(_SSH_GSSAuth): :param str session_id: The SSH session ID :param str username: The name of the user who attempts to login :return: None if the MIC check was successful - :raises sspi.error: if the MIC check failed + :raises: ``sspi.error`` -- if the MIC check failed """ self._session_id = session_id self._username = username @@ -548,7 +535,6 @@ class _SSH_SSPI(_SSH_GSSAuth): Checks if credentials are delegated (server mode). :return: ``True`` if credentials are delegated, otherwise ``False`` - :rtype: Boolean """ return ( self._gss_flags & sspicon.ISC_REQ_DELEGATE and @@ -562,7 +548,8 @@ class _SSH_SSPI(_SSH_GSSAuth): (server mode). :param str client_token: The SSPI token received form the client - :raise NotImplementedError: Credential delegation is currently not - supported in server mode + :raises: + ``NotImplementedError`` -- Credential delegation is currently not + supported in server mode """ raise NotImplementedError diff --git a/paramiko/transport.py b/paramiko/transport.py index a308e0c9..0f3d4426 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -83,7 +83,7 @@ import atexit atexit.register(_join_lingering_threads) -class Transport (threading.Thread, ClosingContextManager): +class Transport(threading.Thread, ClosingContextManager): """ An SSH Transport attaches to a stream (usually a socket), negotiates an encrypted session, authenticates, and then creates stream tunnels, called @@ -459,7 +459,6 @@ class Transport (threading.Thread, ClosingContextManager): :param str gss_host: The targets name in the kerberos database Default: The name of the host to connect to - :rtype: Void """ # We need the FQDN to get this working with SSPI self.gss_host = socket.getfqdn(gss_host) @@ -492,8 +491,9 @@ class Transport (threading.Thread, ClosingContextManager): :param .threading.Event event: an event to trigger when negotiation is complete (optional) - :raises SSHException: if negotiation fails (and no ``event`` was passed - in) + :raises: + `.SSHException` -- if negotiation fails (and no ``event`` was + passed in) """ self.active = True if event is not None: @@ -553,8 +553,9 @@ class Transport (threading.Thread, ClosingContextManager): an object used to perform authentication and create `channels <.Channel>` - :raises SSHException: if negotiation fails (and no ``event`` was passed - in) + :raises: + `.SSHException` -- if negotiation fails (and no ``event`` was + passed in) """ if server is None: server = ServerInterface() @@ -680,7 +681,7 @@ class Transport (threading.Thread, ClosingContextManager): string)``. You can get the same effect by calling `.PKey.get_name` for the key type, and ``str(key)`` for the key string. - :raises SSHException: if no session is currently active. + :raises: `.SSHException` -- if no session is currently active. :return: public key (`.PKey`) of the remote server """ @@ -720,7 +721,8 @@ class Transport (threading.Thread, ClosingContextManager): :return: a new `.Channel` - :raises SSHException: if the request is rejected or the session ends + :raises: + `.SSHException` -- if the request is rejected or the session ends prematurely .. versionchanged:: 1.13.4/1.14.3/1.15.3 @@ -743,7 +745,8 @@ class Transport (threading.Thread, ClosingContextManager): x11 port, ie. 6010) :return: a new `.Channel` - :raises SSHException: if the request is rejected or the session ends + :raises: + `.SSHException` -- if the request is rejected or the session ends prematurely """ return self.open_channel('x11', src_addr=src_addr) @@ -757,7 +760,7 @@ class Transport (threading.Thread, ClosingContextManager): :return: a new `.Channel` - :raises SSHException: + :raises: `.SSHException` -- if the request is rejected or the session ends prematurely """ return self.open_channel('auth-agent@openssh.com') @@ -809,7 +812,8 @@ class Transport (threading.Thread, ClosingContextManager): :return: a new `.Channel` on success - :raises SSHException: if the request is rejected, the session ends + :raises: + `.SSHException` -- if the request is rejected, the session ends prematurely or there is a timeout openning a channel .. versionchanged:: 1.15 @@ -896,7 +900,8 @@ class Transport (threading.Thread, ClosingContextManager): :return: the port number (`int`) allocated by the server - :raises SSHException: if the server refused the TCP forward request + :raises: + `.SSHException` -- if the server refused the TCP forward request """ if not self.active: raise SSHException('SSH session not active') @@ -970,8 +975,9 @@ class Transport (threading.Thread, ClosingContextManager): traffic both ways as the two sides swap keys and do computations. This method returns when the session has switched to new keys. - :raises SSHException: if the key renegotiation failed (which causes the - session to end) + :raises: + `.SSHException` -- if the key renegotiation failed (which causes + the session to end) """ self.completion_event = threading.Event() self._send_kex_init() @@ -1112,7 +1118,7 @@ class Transport (threading.Thread, ClosingContextManager): :param bool gss_deleg_creds: Whether to delegate GSS-API client credentials. - :raises SSHException: if the SSH2 negotiation fails, the host key + :raises: `.SSHException` -- if the SSH2 negotiation fails, the host key supplied by the server is incorrect, or authentication fails. """ if hostkey is not None: @@ -1186,7 +1192,7 @@ class Transport (threading.Thread, ClosingContextManager): passed to the `.SubsystemHandler` constructor later. :param str name: name of the subsystem. - :param class handler: + :param handler: subclass of `.SubsystemHandler` that handles this subsystem. """ try: @@ -1247,9 +1253,11 @@ class Transport (threading.Thread, ClosingContextManager): `list` of auth types permissible for the next stage of authentication (normally empty) - :raises BadAuthenticationType: if "none" authentication isn't allowed + :raises: + `.BadAuthenticationType` -- if "none" authentication isn't allowed by the server for this user - :raises SSHException: if the authentication failed due to a network + :raises: + `.SSHException` -- if the authentication failed due to a network error .. versionadded:: 1.5 @@ -1300,11 +1308,13 @@ class Transport (threading.Thread, ClosingContextManager): `list` of auth types permissible for the next stage of authentication (normally empty) - :raises BadAuthenticationType: if password authentication isn't + :raises: + `.BadAuthenticationType` -- if password authentication isn't allowed by the server for this user (and no event was passed in) - :raises AuthenticationException: if the authentication failed (and no + :raises: + `.AuthenticationException` -- if the authentication failed (and no event was passed in) - :raises SSHException: if there was a network error + :raises: `.SSHException` -- if there was a network error """ if (not self.active) or (not self.initial_kex_done): # we should never try to send the password unless we're on a secure @@ -1369,11 +1379,13 @@ class Transport (threading.Thread, ClosingContextManager): `list` of auth types permissible for the next stage of authentication (normally empty) - :raises BadAuthenticationType: if public-key authentication isn't + :raises: + `.BadAuthenticationType` -- if public-key authentication isn't allowed by the server for this user (and no event was passed in) - :raises AuthenticationException: if the authentication failed (and no + :raises: + `.AuthenticationException` -- if the authentication failed (and no event was passed in) - :raises SSHException: if there was a network error + :raises: `.SSHException` -- if there was a network error """ if (not self.active) or (not self.initial_kex_done): # we should never try to authenticate unless we're on a secure link @@ -1425,10 +1437,10 @@ class Transport (threading.Thread, ClosingContextManager): `list` of auth types permissible for the next stage of authentication (normally empty). - :raises BadAuthenticationType: if public-key authentication isn't + :raises: `.BadAuthenticationType` -- if public-key authentication isn't allowed by the server for this user - :raises AuthenticationException: if the authentication failed - :raises SSHException: if there was a network error + :raises: `.AuthenticationException` -- if the authentication failed + :raises: `.SSHException` -- if there was a network error .. versionadded:: 1.5 """ @@ -1473,11 +1485,12 @@ class Transport (threading.Thread, ClosingContextManager): :return: list of auth types permissible for the next stage of authentication (normally empty) :rtype: list - :raise BadAuthenticationType: if gssapi-with-mic isn't + :raises: `.BadAuthenticationType` -- if gssapi-with-mic isn't allowed by the server (and no event was passed in) - :raise AuthenticationException: if the authentication failed (and no + :raises: + `.AuthenticationException` -- if the authentication failed (and no event was passed in) - :raise SSHException: if there was a network error + :raises: `.SSHException` -- if there was a network error """ if (not self.active) or (not self.initial_kex_done): # we should never try to authenticate unless we're on a secure link @@ -1497,12 +1510,12 @@ class Transport (threading.Thread, ClosingContextManager): :returns: a `list` of auth types permissible for the next stage of authentication (normally empty) - :raises BadAuthenticationType: + :raises: `.BadAuthenticationType` -- if GSS-API Key Exchange was not performed (and no event was passed in) - :raises AuthenticationException: + :raises: `.AuthenticationException` -- if the authentication failed (and no event was passed in) - :raises SSHException: if there was a network error + :raises: `.SSHException` -- if there was a network error """ if (not self.active) or (not self.initial_kex_done): # we should never try to authenticate unless we're on a secure link diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index f8b3d328..ebad4f22 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -16,7 +16,7 @@ Changelog (i.e. passes the maintainer's preferred `flake8 `_ configuration) and add a ``flake8`` step to the Travis config. Big thanks to Dorian Pula! -* :bug:`683` Make `util.log_to_file()` append instead of replace. Thanks +* :bug:`683` Make ``util.log_to_file`` append instead of replace. Thanks to ``@vlcinsky`` for the report. * :release:`2.0.5 <2017-02-20>` * :release:`1.18.2 <2017-02-20>` @@ -92,7 +92,7 @@ Changelog * :bug:`334 (1.17+)` Make the ``subprocess`` import in ``proxy.py`` lazy so users on platforms without it (such as Google App Engine) can import Paramiko successfully. (Relatedly, make it easier to tweak an active socket check - timeout [in `Transport `] which was previously + timeout [in `Transport `] which was previously hardcoded.) Credit: Shinya Okano. * :support:`854 backported (1.17+)` Fix incorrect docstring/param-list for `Transport.auth_gssapi_keyex @@ -147,10 +147,10 @@ Changelog ``proxycommand`` key in parsed config structures). Thanks to Pat Brisbin for the catch. * :bug:`676` (via :issue:`677`) Fix a backwards incompatibility issue that - cropped up in `SFTPFile.prefetch <~paramiko.sftp_file.prefetch>` re: the - erroneously non-optional ``file_size`` parameter. Should only affect users - who manually call ``prefetch``. Thanks to ``@stevevanhooser`` for catch & - patch. + cropped up in `SFTPFile.prefetch ` re: + the erroneously non-optional ``file_size`` parameter. Should only affect + users who manually call ``prefetch``. Thanks to ``@stevevanhooser`` for catch + & patch. * :feature:`394` Replace PyCrypto with the Python Cryptographic Authority (PyCA) 'Cryptography' library suite. This improves security, installability, and performance; adds PyPy support; and much more. @@ -240,7 +240,7 @@ Changelog * :release:`1.15.4 <2015-11-02>` * :release:`1.14.3 <2015-11-02>` * :release:`1.13.4 <2015-11-02>` -* :bug:`366` Fix `~paramiko.sftp_attributes.SFTPAttributes` so its string +* :bug:`366` Fix `~paramiko.sftp_attr.SFTPAttributes` so its string representation doesn't raise exceptions on empty/initialized instances. Patch by Ulrich Petri. * :bug:`359` Use correct attribute name when trying to use Python 3's @@ -351,8 +351,9 @@ Changelog * :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. + arguments for `~paramiko.transport.Transport` (thanks to a botched merge). + This has been corrected. Thanks to Dylan Thacker-Smith for the report & + patch. * :feature:`167` Add `~paramiko.config.SSHConfig.get_hostnames` for easier introspection of a loaded SSH config file or object. Courtesy of Søren Løvborg. -- cgit v1.2.1 From 9dadca814288a5c8bb4f5ccd186d854d576e3816 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 5 Jun 2017 23:04:59 -0700 Subject: Changelog because I deserve it --- sites/www/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index ebad4f22..8ffde787 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :support:`- backported` A big formatting pass to clean up an enormous number + of invalid Sphinx reference links, discovered by switching to a modern, + rigorous nitpicking doc-building mode. * :bug:`900` (via :issue:`911`) Prefer newer ``ecdsa-sha2-nistp`` keys over RSA and DSA keys during host key selection. This improves compatibility with OpenSSH, both in terms of general behavior, and also re: ability to properly -- cgit v1.2.1 From d285b80ecb6102b0ad501b74d02e04d61e8ec632 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 5 Jun 2017 23:09:00 -0700 Subject: No idea how this fancy quote got in here or why it wasn't causing barfs prior --- paramiko/sftp_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py index 63ababdb..337cdbeb 100644 --- a/paramiko/sftp_file.py +++ b/paramiko/sftp_file.py @@ -252,7 +252,7 @@ class SFTPFile (BufferedFile): def seek(self, offset, whence=0): """ - Set the file’s current position. + Set the file's current position. See `file.seek` for details. """ -- cgit v1.2.1 From 9db59ac05e03ede7ba5d5059b7e00effca275430 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Tue, 6 Jun 2017 12:34:12 -0700 Subject: Changelog re #667 --- sites/www/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 8ffde787..ec1c09cb 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,10 @@ Changelog ========= +* :bug:`667` The RC4/arcfour family of ciphers has been broken since version + 2.0; but since the algorithm is now known to be completely insecure, we are + opting to remove support outright instead of fixing it. Thanks to Alex Gaynor + for catch & patch. * :support:`- backported` A big formatting pass to clean up an enormous number of invalid Sphinx reference links, discovered by switching to a modern, rigorous nitpicking doc-building mode. -- cgit v1.2.1 From d6e57d34bafb65c6ce62a022d1b509f35cf82d49 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Tue, 6 Jun 2017 13:26:13 -0700 Subject: Partially apply #983 for 2.0+ --- paramiko/transport.py | 2 +- sites/www/changelog.rst | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index 4a3ae8f4..802b496f 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -113,10 +113,10 @@ class Transport(threading.Thread, ClosingContextManager): _preferred_macs = ( 'hmac-sha2-256', 'hmac-sha2-512', + 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96', - 'hmac-sha1', ) _preferred_keys = ( 'ecdsa-sha2-nistp256', diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index ec1c09cb..9aead611 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :bug:`983` Move ``sha1`` above the now-arguably-broken ``md5`` in the list of + preferred MAC algorithms, as an incremental security improvement for users + whose target systems offer both. Credit: Pierce Lopez. * :bug:`667` The RC4/arcfour family of ciphers has been broken since version 2.0; but since the algorithm is now known to be completely insecure, we are opting to remove support outright instead of fixing it. Thanks to Alex Gaynor -- cgit v1.2.1 From 86688c1b9d37dd75f99b6eefb00200a1abb4cf51 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Tue, 6 Jun 2017 17:35:49 -0700 Subject: Hand-picked backport of #912, fixes #741 --- paramiko/pkey.py | 10 ++++++++-- paramiko/transport.py | 1 + sites/www/changelog.rst | 10 ++++++++++ tests/test_pkey.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/paramiko/pkey.py b/paramiko/pkey.py index f5b0cd18..35a26fc7 100644 --- a/paramiko/pkey.py +++ b/paramiko/pkey.py @@ -48,6 +48,12 @@ class PKey(object): 'blocksize': 16, 'mode': modes.CBC }, + 'AES-256-CBC': { + 'cipher': algorithms.AES, + 'keysize': 32, + 'blocksize': 16, + 'mode': modes.CBC + }, 'DES-EDE3-CBC': { 'cipher': algorithms.TripleDES, 'keysize': 24, @@ -344,13 +350,13 @@ class PKey(object): """ with open(filename, 'w') as f: os.chmod(filename, o600) - self._write_private_key(f, key, format) + self._write_private_key(f, key, format, password=password) def _write_private_key(self, f, key, format, password=None): if password is None: encryption = serialization.NoEncryption() else: - encryption = serialization.BestEncryption(password) + encryption = serialization.BestAvailableEncryption(b(password)) f.write(key.private_bytes( serialization.Encoding.PEM, diff --git a/paramiko/transport.py b/paramiko/transport.py index 802b496f..136d7fb2 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -78,6 +78,7 @@ def _join_lingering_threads(): for thr in _active_threads: thr.stop_thread() + import atexit atexit.register(_join_lingering_threads) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 9aead611..e4aa5261 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,16 @@ Changelog ========= +* :bug:`741` (also :issue:`809`, :issue:`772`; all via :issue:`912`) Writing + encrypted/password-protected private key files was silently broken since 2.0 + due to an incorrect API call; this has been fixed. + + Includes a directly related fix, namely adding the ability to read + ``AES-256-CBC`` ciphered private keys (which is now what we tend to write out + as it is Cryptography's default private key cipher.) + + Thanks to ``@virlos`` for the original report, Chris Harris and ``@ibuler`` + for initial draft PRs, and ``@jhgorrell`` for the final patch. * :bug:`983` Move ``sha1`` above the now-arguably-broken ``md5`` in the list of preferred MAC algorithms, as an incremental security improvement for users whose target systems offer both. Credit: Pierce Lopez. diff --git a/tests/test_pkey.py b/tests/test_pkey.py index 24d78c3e..394a2cf4 100644 --- a/tests/test_pkey.py +++ b/tests/test_pkey.py @@ -120,6 +120,18 @@ class KeyTest (unittest.TestCase): def tearDown(self): pass + def assert_keyfile_is_encrypted(self, keyfile): + """ + A quick check that filename looks like an encrypted key. + """ + with open(keyfile, "r") as fh: + self.assertEqual( + fh.readline()[:-1], + "-----BEGIN RSA PRIVATE KEY-----" + ) + self.assertEqual(fh.readline()[:-1], "Proc-Type: 4,ENCRYPTED") + self.assertEqual(fh.readline()[0:10], "DEK-Info: ") + def test_1_generate_key_bytes(self): key = util.generate_key_bytes(md5, x1234, 'happy birthday', 30) exp = b'\x61\xE1\xF2\x72\xF4\xC1\xC4\x56\x15\x86\xBD\x32\x24\x98\xC0\xE9\x24\x67\x27\x80\xF4\x7B\xB3\x7D\xDA\x7D\x54\x01\x9E\x64' @@ -426,6 +438,7 @@ class KeyTest (unittest.TestCase): # When the bug under test exists, this will ValueError. try: key.write_private_key_file(newfile, password=newpassword) + self.assert_keyfile_is_encrypted(newfile) # Verify the inner key data still matches (when no ValueError) key2 = RSAKey(filename=newfile, password=newpassword) self.assertEqual(key, key2) @@ -436,3 +449,18 @@ class KeyTest (unittest.TestCase): key = RSAKey.from_private_key_file(test_path('test_rsa.key')) comparable = TEST_KEY_BYTESTR_2 if PY2 else TEST_KEY_BYTESTR_3 self.assertEqual(str(key), comparable) + + def test_keyfile_is_actually_encrypted(self): + # Read an existing encrypted private key + file_ = test_path('test_rsa_password.key') + password = 'television' + newfile = file_ + '.new' + newpassword = 'radio' + key = RSAKey(filename=file_, password=password) + # Write out a newly re-encrypted copy with a new password. + # When the bug under test exists, this will ValueError. + try: + key.write_private_key_file(newfile, password=newpassword) + self.assert_keyfile_is_encrypted(newfile) + finally: + os.remove(newfile) -- cgit v1.2.1 From eb035f6ede42a3237c6cf5c352c3e459915c510a Mon Sep 17 00:00:00 2001 From: Nikolai R Kristiansen Date: Sun, 7 May 2017 23:01:34 +0200 Subject: Replace coveralls with codecov, comments disabled --- .travis.yml | 4 ++-- README.rst | 4 ++-- codecov.yml | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 codecov.yml diff --git a/.travis.yml b/.travis.yml index c8faf0a2..7ca8db09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ install: # Self-install for setup.py-driven deps - pip install -e . # Dev (doc/test running) requirements - - pip install coveralls # For coveralls.io specifically + - pip install codecov # For codecov specifically - pip install -r dev-requirements.txt script: # Main tests, w/ coverage! @@ -33,4 +33,4 @@ notifications: on_failure: change email: false after_success: - - coveralls + - codecov diff --git a/README.rst b/README.rst index e267f69a..399dceb9 100644 --- a/README.rst +++ b/README.rst @@ -6,8 +6,8 @@ Paramiko .. image:: https://travis-ci.org/paramiko/paramiko.svg?branch=master :target: https://travis-ci.org/paramiko/paramiko -.. image:: https://coveralls.io/repos/paramiko/paramiko/badge.svg?branch=master&service=github - :target: https://coveralls.io/github/paramiko/paramiko?branch=master +.. image:: https://codecov.io/gh/paramiko/paramiko/branch/master/graph/badge.svg + :target: https://codecov.io/gh/paramiko/paramiko :Paramiko: Python SSH module :Copyright: Copyright (c) 2003-2009 Robey Pointer diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..69cb7601 --- /dev/null +++ b/codecov.yml @@ -0,0 +1 @@ +comment: false -- cgit v1.2.1 From e2e1466f416f2a9ca9f8ecd19259f3e50d25a349 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 9 Jun 2017 12:41:00 -0700 Subject: Changelog re #956 --- sites/www/changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index dfa2fedc..01eb45ff 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :support:`956 backported` Switch code coverage service from coveralls.io to + codecov.io (& then disable the latter's auto-comments.) Thanks to Nikolai + Røed Kristiansen for the patch. * :bug:`949` SSHClient and Transport could cause a memory leak if there's a connection problem or protocol error, even if ``Transport.close()`` is called. Thanks Kyle Agronick for the discovery and investigation, -- cgit v1.2.1 From a234be3261b2d03288398fdbd7ccff10ab51fd33 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 9 Jun 2017 12:55:07 -0700 Subject: Unify 1.x and 2.x changelog entries. TBH this might just be the last damn 1.x series of releases. This is still too much work! --- sites/www/changelog.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 01eb45ff..ebf8253b 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,13 +2,6 @@ Changelog ========= -* :support:`956 backported` Switch code coverage service from coveralls.io to - codecov.io (& then disable the latter's auto-comments.) Thanks to Nikolai - Røed Kristiansen for the patch. -* :bug:`949` SSHClient and Transport could cause a memory leak if there's - a connection problem or protocol error, even if ``Transport.close()`` - is called. Thanks Kyle Agronick for the discovery and investigation, - and Pierce Lopez for assistance. * :bug:`741` (also :issue:`809`, :issue:`772`; all via :issue:`912`) Writing encrypted/password-protected private key files was silently broken since 2.0 due to an incorrect API call; this has been fixed. @@ -19,6 +12,9 @@ Changelog Thanks to ``@virlos`` for the original report, Chris Harris and ``@ibuler`` for initial draft PRs, and ``@jhgorrell`` for the final patch. +* :support:`956 (1.17+)` Switch code coverage service from coveralls.io to + codecov.io (& then disable the latter's auto-comments.) Thanks to Nikolai + Røed Kristiansen for the patch. * :bug:`983` Move ``sha1`` above the now-arguably-broken ``md5`` in the list of preferred MAC algorithms, as an incremental security improvement for users whose target systems offer both. Credit: Pierce Lopez. @@ -43,8 +39,12 @@ Changelog (i.e. passes the maintainer's preferred `flake8 `_ configuration) and add a ``flake8`` step to the Travis config. Big thanks to Dorian Pula! -* :bug:`683` Make ``util.log_to_file`` append instead of replace. Thanks - to ``@vlcinsky`` for the report. +* :bug:`949 (1.17+)` SSHClient and Transport could cause a memory leak if + there's a connection problem or protocol error, even if ``Transport.close()`` + is called. Thanks Kyle Agronick for the discovery and investigation, and + Pierce Lopez for assistance. +* :bug:`683 (1.17+)` Make ``util.log_to_file`` append instead of replace. + Thanks to ``@vlcinsky`` for the report. * :release:`2.0.5 <2017-02-20>` * :release:`1.18.2 <2017-02-20>` * :release:`1.17.4 <2017-02-20>` -- cgit v1.2.1 From 85dd4d777ccfe9e9fd23c650d43386a0b50444b7 Mon Sep 17 00:00:00 2001 From: Martin Packman Date: Thu, 25 May 2017 20:35:46 +0100 Subject: Add new skipUnlessBuiltin function for testing --- tests/__init__.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/__init__.py b/tests/__init__.py index e69de29b..8878f14d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,36 @@ +# Copyright (C) 2017 Martin Packman +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +"""Base classes and helpers for testing paramiko.""" + +import unittest + +from paramiko.py3compat import ( + builtins, + ) + + +def skipUnlessBuiltin(name): + """Skip decorated test if builtin name does not exist.""" + if getattr(builtins, name, None) is None: + skip = getattr(unittest, "skip", None) + if skip is None: + # Python 2.6 pseudo-skip + return lambda func: None + return skip("No builtin " + repr(name)) + return lambda func: func -- cgit v1.2.1 From e66899a87ed2e510b66116284fc95763f5a2f4ec Mon Sep 17 00:00:00 2001 From: Martin Packman Date: Thu, 25 May 2017 20:37:14 +0100 Subject: Allow any buffer type to written to BufferedFile Fixes #967 Also adds test coverage for writing various types to BufferedFile which required some small changes to the test LoopbackFile subclass. Change against the 1.17 branch. --- paramiko/file.py | 7 +++++-- tests/test_file.py | 57 ++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/paramiko/file.py b/paramiko/file.py index 5212091a..35637b8e 100644 --- a/paramiko/file.py +++ b/paramiko/file.py @@ -18,7 +18,7 @@ from paramiko.common import ( linefeed_byte_value, crlf, cr_byte, linefeed_byte, cr_byte_value, ) -from paramiko.py3compat import BytesIO, PY2, u, b, bytes_types +from paramiko.py3compat import BytesIO, PY2, u, bytes_types, text_type from paramiko.util import ClosingContextManager @@ -391,7 +391,10 @@ class BufferedFile (ClosingContextManager): :param data: ``str``/``bytes`` data to write """ - data = b(data) + if isinstance(data, text_type): + # GZ 2017-05-25: Accepting text on a binary stream unconditionally + # cooercing to utf-8 seems questionable, but compatibility reasons? + data = data.encode('utf-8') if self._closed: raise IOError('File is closed') if not (self._flags & self.FLAG_WRITE): diff --git a/tests/test_file.py b/tests/test_file.py index 7fab6985..b33ecd51 100755 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -21,10 +21,14 @@ Some unit tests for the BufferedFile abstraction. """ import unittest -from paramiko.file import BufferedFile -from paramiko.common import linefeed_byte, crlf, cr_byte import sys +from paramiko.common import linefeed_byte, crlf, cr_byte +from paramiko.file import BufferedFile +from paramiko.py3compat import BytesIO + +from tests import skipUnlessBuiltin + class LoopbackFile (BufferedFile): """ @@ -33,19 +37,16 @@ class LoopbackFile (BufferedFile): def __init__(self, mode='r', bufsize=-1): BufferedFile.__init__(self) self._set_mode(mode, bufsize) - self.buffer = bytes() + self.buffer = BytesIO() + self.offset = 0 def _read(self, size): - if len(self.buffer) == 0: - return None - if size > len(self.buffer): - size = len(self.buffer) - data = self.buffer[:size] - self.buffer = self.buffer[size:] + data = self.buffer.getvalue()[self.offset:self.offset+size] + self.offset += len(data) return data def _write(self, data): - self.buffer += data + self.buffer.write(data) return len(data) @@ -187,6 +188,42 @@ class BufferedFileTest (unittest.TestCase): self.assertEqual(data, b'hello') f.close() + def test_write_bad_type(self): + with LoopbackFile('wb') as f: + self.assertRaises(TypeError, f.write, object()) + + def test_write_unicode_as_binary(self): + text = u"\xa7 why is writing text to a binary file allowed?\n" + with LoopbackFile('rb+') as f: + f.write(text) + self.assertEqual(f.read(), text.encode("utf-8")) + + @skipUnlessBuiltin('memoryview') + def test_write_bytearray(self): + with LoopbackFile('rb+') as f: + f.write(bytearray(12)) + self.assertEqual(f.read(), 12 * b"\0") + + @skipUnlessBuiltin('buffer') + def test_write_buffer(self): + data = 3 * b"pretend giant block of data\n" + offsets = range(0, len(data), 8) + with LoopbackFile('rb+') as f: + for offset in offsets: + f.write(buffer(data, offset, 8)) + self.assertEqual(f.read(), data) + + @skipUnlessBuiltin('memoryview') + def test_write_memoryview(self): + data = 3 * b"pretend giant block of data\n" + offsets = range(0, len(data), 8) + with LoopbackFile('rb+') as f: + view = memoryview(data) + for offset in offsets: + f.write(view[offset:offset+8]) + self.assertEqual(f.read(), data) + + if __name__ == '__main__': from unittest import main main() -- cgit v1.2.1 From 02b0aef5932438c2dd0b7d649a6e61450fb71be3 Mon Sep 17 00:00:00 2001 From: Martin Packman Date: Thu, 25 May 2017 22:13:01 +0100 Subject: Import from py3compat directly in test_client --- tests/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index f2f2ea45..38006df0 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -35,7 +35,7 @@ import time from tests.util import test_path import paramiko -from paramiko.common import PY2 +from paramiko.common import PY2, b from paramiko.ssh_exception import SSHException -- cgit v1.2.1 From c2c402cb6e4a4f86ff3053389bb6300c4b4505f1 Mon Sep 17 00:00:00 2001 From: Martin Packman Date: Thu, 25 May 2017 22:14:19 +0100 Subject: Allow any buffer type to be sent to Channel Fixes #968 Changes the behaviour of the underlying asbytes helper to pass along unknown types. Most callers already handle this by passing the bytes along to a file or socket-like object which will raise TypeError anyway. Adds test coverage through the Transport implementation. Change against the 1.17 branch. --- paramiko/common.py | 19 +++++++------- tests/test_transport.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/paramiko/common.py b/paramiko/common.py index 556f046a..3b18858b 100644 --- a/paramiko/common.py +++ b/paramiko/common.py @@ -21,7 +21,7 @@ Common constants and global variables. """ import logging from paramiko.py3compat import ( - byte_chr, PY2, bytes_types, string_types, b, long, + byte_chr, PY2, bytes_types, text_type, string_types, b, long, ) MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG, \ @@ -163,14 +163,15 @@ else: def asbytes(s): - if not isinstance(s, bytes_types): - if isinstance(s, string_types): - s = b(s) - else: - try: - s = s.asbytes() - except Exception: - raise Exception('Unknown type') + if isinstance(s, bytes_types): + return s + if isinstance(s, text_type): + # GZ 2017-05-25: Accept text and encode as utf-8 for compatibilty only. + return s.encode("utf-8") + asbytes = getattr(s, "asbytes", None) + if asbytes is not None: + return asbytes() + # May be an object that implements the buffer api, let callers decide return s diff --git a/tests/test_transport.py b/tests/test_transport.py index c426cef1..3e352919 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -43,6 +43,7 @@ from paramiko.common import ( ) from paramiko.py3compat import bytes from paramiko.message import Message +from tests import skipUnlessBuiltin from tests.loop import LoopSocket from tests.util import test_path @@ -858,3 +859,71 @@ class TransportTest(unittest.TestCase): self.assertEqual([chan], r) self.assertEqual([], w) self.assertEqual([], e) + + def test_channel_send_misc(self): + """ + verify behaviours sending various instances to a channel + """ + self.setup_test_server() + text = u"\xa7 slice me nicely" + with self.tc.open_session() as chan: + schan = self.ts.accept(1.0) + if schan is None: + self.fail("Test server transport failed to accept") + sfile = schan.makefile() + + # TypeError raised on non string or buffer type + self.assertRaises(TypeError, chan.send, object()) + self.assertRaises(TypeError, chan.sendall, object()) + + # sendall() accepts a unicode instance + chan.sendall(text) + expected = text.encode("utf-8") + self.assertEqual(sfile.read(len(expected)), expected) + + @skipUnlessBuiltin('buffer') + def test_channel_send_buffer(self): + """ + verify sending buffer instances to a channel + """ + self.setup_test_server() + data = 3 * b'some test data\n whole' + with self.tc.open_session() as chan: + schan = self.ts.accept(1.0) + if schan is None: + self.fail("Test server transport failed to accept") + sfile = schan.makefile() + + # send() accepts buffer instances + sent = 0 + while sent < len(data): + sent += chan.send(buffer(data, sent, 8)) + self.assertEqual(sfile.read(len(data)), data) + + # sendall() accepts a buffer instance + chan.sendall(buffer(data)) + self.assertEqual(sfile.read(len(data)), data) + + @skipUnlessBuiltin('memoryview') + def test_channel_send_memoryview(self): + """ + verify sending memoryview instances to a channel + """ + self.setup_test_server() + data = 3 * b'some test data\n whole' + with self.tc.open_session() as chan: + schan = self.ts.accept(1.0) + if schan is None: + self.fail("Test server transport failed to accept") + sfile = schan.makefile() + + # send() accepts memoryview slices + sent = 0 + view = memoryview(data) + while sent < len(view): + sent += chan.send(view[sent:sent+8]) + self.assertEqual(sfile.read(len(data)), data) + + # sendall() accepts a memoryview instance + chan.sendall(memoryview(data)) + self.assertEqual(sfile.read(len(data)), data) -- cgit v1.2.1 From 17231b9a7c56bf06f1a848fc52554541a09a2eba Mon Sep 17 00:00:00 2001 From: Martin Packman Date: Fri, 26 May 2017 01:53:44 +0100 Subject: Allow any buffer type to be written to SFTPFile Fixes #967 #968 Rollup of earlier branches proposed as #969 and #970 with additional fix inside sftp_client. Includes new tests for SFTPFile usage. Change against the 1.17 branch. --- paramiko/sftp_client.py | 5 +---- tests/test_sftp.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 12fccb2f..ef04f37a 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -758,13 +758,10 @@ class SFTPClient(BaseSFTP, ClosingContextManager): msg.add_int64(item) elif isinstance(item, int): msg.add_int(item) - elif isinstance(item, (string_types, bytes_types)): - msg.add_string(item) elif isinstance(item, SFTPAttributes): item._pack(msg) else: - raise Exception( - 'unknown type for %r type %r' % (item, type(item))) + msg.add_string(item) num = self.request_number self._expecting[num] = fileobj self.request_number += 1 diff --git a/tests/test_sftp.py b/tests/test_sftp.py index d3064fff..98a9cebb 100755 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -35,6 +35,7 @@ from tempfile import mkstemp import paramiko from paramiko.py3compat import PY2, b, u, StringIO from paramiko.common import o777, o600, o666, o644 +from tests import skipUnlessBuiltin from tests.stub_sftp import StubServer, StubSFTPServer from tests.loop import LoopSocket from tests.util import test_path @@ -817,6 +818,35 @@ class SFTPTest (unittest.TestCase): sftp_attributes = SFTPAttributes() self.assertEqual(str(sftp_attributes), "?--------- 1 0 0 0 (unknown date) ?") + @skipUnlessBuiltin('buffer') + def test_write_buffer(self): + """Test write() using a buffer instance.""" + data = 3 * b'A potentially large block of data to chunk up.\n' + try: + with sftp.open('%s/write_buffer' % FOLDER, 'wb') as f: + for offset in range(0, len(data), 8): + f.write(buffer(data, offset, 8)) + + with sftp.open('%s/write_buffer' % FOLDER, 'rb') as f: + self.assertEqual(f.read(), data) + finally: + sftp.remove('%s/write_buffer' % FOLDER) + + @skipUnlessBuiltin('memoryview') + def test_write_memoryview(self): + """Test write() using a memoryview instance.""" + data = 3 * b'A potentially large block of data to chunk up.\n' + try: + with sftp.open('%s/write_memoryview' % FOLDER, 'wb') as f: + view = memoryview(data) + for offset in range(0, len(data), 8): + f.write(view[offset:offset+8]) + + with sftp.open('%s/write_memoryview' % FOLDER, 'rb') as f: + self.assertEqual(f.read(), data) + finally: + sftp.remove('%s/write_memoryview' % FOLDER) + if __name__ == '__main__': SFTPTest.init_loopback() -- cgit v1.2.1 From 8c9df8ba637e222a3e9da6d365d225f9cad81f18 Mon Sep 17 00:00:00 2001 From: Martin Packman Date: Thu, 1 Jun 2017 01:08:02 +0100 Subject: Tweak comments as suggested in review Thanks to bitprophet and ploxiln. --- paramiko/common.py | 5 +++-- paramiko/file.py | 3 +-- paramiko/sftp_client.py | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/paramiko/common.py b/paramiko/common.py index 3b18858b..8ce735d7 100644 --- a/paramiko/common.py +++ b/paramiko/common.py @@ -163,15 +163,16 @@ else: def asbytes(s): + """Coerce to bytes if possible or return unchanged.""" if isinstance(s, bytes_types): return s if isinstance(s, text_type): - # GZ 2017-05-25: Accept text and encode as utf-8 for compatibilty only. + # Accept text and encode as utf-8 for compatibility only. return s.encode("utf-8") asbytes = getattr(s, "asbytes", None) if asbytes is not None: return asbytes() - # May be an object that implements the buffer api, let callers decide + # May be an object that implements the buffer api, let callers handle. return s diff --git a/paramiko/file.py b/paramiko/file.py index 35637b8e..a1bdafbe 100644 --- a/paramiko/file.py +++ b/paramiko/file.py @@ -392,8 +392,7 @@ class BufferedFile (ClosingContextManager): :param data: ``str``/``bytes`` data to write """ if isinstance(data, text_type): - # GZ 2017-05-25: Accepting text on a binary stream unconditionally - # cooercing to utf-8 seems questionable, but compatibility reasons? + # Accept text and encode as utf-8 for compatibility only. data = data.encode('utf-8') if self._closed: raise IOError('File is closed') diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index ef04f37a..a1ee6723 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -761,6 +761,8 @@ class SFTPClient(BaseSFTP, ClosingContextManager): elif isinstance(item, SFTPAttributes): item._pack(msg) else: + # For all other types, rely on as_string() to either coerce + # to bytes before writing or raise a suitable exception. msg.add_string(item) num = self.request_number self._expecting[num] = fileobj -- cgit v1.2.1 From dd0524c2dc07892022ff187b156e10fa1f1c1716 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 9 Jun 2017 13:50:54 -0700 Subject: Flake8/etc tweaks re #971 in 2.x --- paramiko/common.py | 4 +--- paramiko/sftp_client.py | 4 +--- tests/test_client.py | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/paramiko/common.py b/paramiko/common.py index 8ce735d7..0012372a 100644 --- a/paramiko/common.py +++ b/paramiko/common.py @@ -20,9 +20,7 @@ Common constants and global variables. """ import logging -from paramiko.py3compat import ( - byte_chr, PY2, bytes_types, text_type, string_types, b, long, -) +from paramiko.py3compat import byte_chr, PY2, bytes_types, text_type, long MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG, \ MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT = range(1, 7) diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index a1ee6723..ee5ab073 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -28,9 +28,7 @@ from paramiko import util from paramiko.channel import Channel from paramiko.message import Message from paramiko.common import INFO, DEBUG, o777 -from paramiko.py3compat import ( - bytestring, b, u, long, string_types, bytes_types, -) +from paramiko.py3compat import bytestring, b, u, long from paramiko.sftp import ( BaseSFTP, CMD_OPENDIR, CMD_HANDLE, SFTPError, CMD_READDIR, CMD_NAME, CMD_CLOSE, SFTP_FLAG_READ, SFTP_FLAG_WRITE, SFTP_FLAG_CREATE, diff --git a/tests/test_client.py b/tests/test_client.py index 38006df0..bfdf5f81 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -35,7 +35,7 @@ import time from tests.util import test_path import paramiko -from paramiko.common import PY2, b +from paramiko.py3compat import PY2, b from paramiko.ssh_exception import SSHException -- cgit v1.2.1 From 22439d24a7a304cf6f9e2f97b934000f5908664e Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 9 Jun 2017 13:51:02 -0700 Subject: Port #971 changelog from 1.x to 2.x --- sites/www/changelog.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index ebf8253b..b850db69 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,11 @@ Changelog ========= +* :bug:`971 (1.17+)` Allow any type implementing the buffer API to be used with + `BufferedFile `, `Channel + `, and `SFTPFile `. + This resolves a regression introduced in 1.13 with the Python 3 porting + changes, when using types such as ``memoryview``. Credit: Martin Packman. * :bug:`741` (also :issue:`809`, :issue:`772`; all via :issue:`912`) Writing encrypted/password-protected private key files was silently broken since 2.0 due to an incorrect API call; this has been fixed. -- cgit v1.2.1 From b808d5e6eb7e5cc3ab6cc71b7b25a1feb543bdfc Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Fri, 9 Jun 2017 14:00:46 -0700 Subject: Hand-port #984 to 2.0 --- paramiko/transport.py | 2 +- sites/www/changelog.rst | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/paramiko/transport.py b/paramiko/transport.py index d219550d..2153b899 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -106,9 +106,9 @@ class Transport(threading.Thread, ClosingContextManager): 'aes192-ctr', 'aes256-ctr', 'aes128-cbc', - 'blowfish-cbc', 'aes192-cbc', 'aes256-cbc', + 'blowfish-cbc', '3des-cbc', ) _preferred_macs = ( diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index b850db69..32bb9250 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +* :bug:`984` Enhance default cipher preference order such that + ``aes(192|256)-cbc`` are preferred over ``blowfish-cbc``. Thanks to Alex + Gaynor. * :bug:`971 (1.17+)` Allow any type implementing the buffer API to be used with `BufferedFile `, `Channel `, and `SFTPFile `. -- cgit v1.2.1