summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Forcier <jeff@bitprophet.org>2017-06-09 14:06:38 -0700
committerJeff Forcier <jeff@bitprophet.org>2017-06-09 14:06:38 -0700
commiteb3a47f24450a3c7ceeb332ab082ec1990c4d012 (patch)
tree3d7a459ca36b5ed88cbcc727e5ee63b1f83a6de7
parentf5c558eaa7072cfe753564f5fbef2bd34748e6ca (diff)
parentb808d5e6eb7e5cc3ab6cc71b7b25a1feb543bdfc (diff)
downloadparamiko-eb3a47f24450a3c7ceeb332ab082ec1990c4d012.tar.gz
Merge branch '2.0' into 976-int
-rw-r--r--.travis.yml11
-rw-r--r--README.rst4
-rw-r--r--codecov.yml1
-rw-r--r--paramiko/agent.py4
-rw-r--r--paramiko/buffered_pipe.py10
-rw-r--r--paramiko/channel.py43
-rw-r--r--paramiko/client.py23
-rw-r--r--paramiko/common.py22
-rw-r--r--paramiko/dsskey.py2
-rw-r--r--paramiko/ecdsakey.py2
-rw-r--r--paramiko/file.py26
-rw-r--r--paramiko/hostkeys.py4
-rw-r--r--paramiko/kex_gss.py4
-rw-r--r--paramiko/packet.py12
-rw-r--r--paramiko/pkey.py42
-rw-r--r--paramiko/resource.py71
-rw-r--r--paramiko/rsakey.py2
-rw-r--r--paramiko/server.py67
-rw-r--r--paramiko/sftp_client.py30
-rw-r--r--paramiko/sftp_file.py24
-rw-r--r--paramiko/sftp_handle.py10
-rw-r--r--paramiko/sftp_server.py2
-rw-r--r--paramiko/sftp_si.py20
-rw-r--r--paramiko/ssh_exception.py18
-rw-r--r--paramiko/ssh_gss.py43
-rw-r--r--paramiko/transport.py126
-rw-r--r--sites/www/changelog.rst66
-rw-r--r--tests/__init__.py36
-rw-r--r--tests/test_client.py15
-rwxr-xr-xtests/test_file.py57
-rw-r--r--tests/test_pkey.py28
-rwxr-xr-xtests/test_sftp.py30
-rw-r--r--tests/test_transport.py78
33 files changed, 547 insertions, 386 deletions
diff --git a/.travis.yml b/.travis.yml
index 4cacb017..7ca8db09 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,16 +15,13 @@ 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!
- 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:
@@ -36,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 <robeypointer@gmail.com>
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
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..224109bf 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
@@ -91,7 +90,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 +117,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 +130,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 +281,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
@@ -340,8 +341,6 @@ class SSHClient (ClosingContextManager):
if banner_timeout is not None:
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()
@@ -416,12 +415,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 +447,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/common.py b/paramiko/common.py
index 556f046a..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, 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)
@@ -163,14 +161,16 @@ 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')
+ """Coerce to bytes if possible or return unchanged."""
+ if isinstance(s, bytes_types):
+ return s
+ if isinstance(s, text_type):
+ # 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 handle.
return s
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..a1bdafbe 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
@@ -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,9 +389,11 @@ 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 isinstance(data, text_type):
+ # Accept text and encode as utf-8 for compatibility only.
+ data = data.encode('utf-8')
if self._closed:
raise IOError('File is closed')
if not (self._flags & self.FLAG_WRITE):
@@ -423,7 +425,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 f6850aaa..c873f58b 100644
--- a/paramiko/hostkeys.py
+++ b/paramiko/hostkeys.py
@@ -85,7 +85,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):
@@ -113,7 +113,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 39aebe54..3406babb 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..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,
@@ -68,7 +74,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 +101,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 +197,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 +218,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 +235,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 +248,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 +269,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,17 +346,17 @@ 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)
- 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/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 <robeypointer@gmail.com>
-#
-# 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()
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..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,
@@ -83,8 +81,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 +319,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 +354,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 +369,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 +521,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 +560,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 +583,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
"""
@@ -757,13 +756,12 @@ 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)))
+ # 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
self.request_number += 1
diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py
index 58653c79..337cdbeb 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 <http://www.ietf.org/rfc/rfc4462.txt>`_
: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 7f38bffa..19d8ee70 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -71,7 +71,6 @@ from paramiko.ssh_exception import (
from paramiko.util import retry_on_signal, ClosingContextManager, clamp_value
-
# for thread cleanup
_active_threads = []
@@ -79,11 +78,12 @@ def _join_lingering_threads():
for thr in _active_threads:
thr.stop_thread()
+
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
@@ -106,25 +106,26 @@ class Transport (threading.Thread, ClosingContextManager):
'aes192-ctr',
'aes256-ctr',
'aes128-cbc',
- 'blowfish-cbc',
'aes192-cbc',
'aes256-cbc',
+ 'blowfish-cbc',
'3des-cbc',
- 'arcfour128',
- 'arcfour256',
)
_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',
+ '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',
@@ -182,18 +183,6 @@ class Transport (threading.Thread, ClosingContextManager):
'block-size': 8,
'key-size': 24
},
- 'arcfour128': {
- 'class': algorithms.ARC4,
- 'mode': None,
- 'block-size': 8,
- 'key-size': 16
- },
- 'arcfour256': {
- 'class': algorithms.ARC4,
- 'mode': None,
- 'block-size': 8,
- 'key-size': 32
- },
}
@@ -210,6 +199,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 = {
@@ -285,7 +276,6 @@ class Transport (threading.Thread, ClosingContextManager):
arguments.
"""
self.active = False
- self._sshclient = None
if isinstance(sock, string_types):
# convert "host:port" into (host, port)
@@ -449,7 +439,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)
@@ -482,8 +471,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:
@@ -543,8 +533,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()
@@ -646,9 +637,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.
@@ -659,7 +647,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):
"""
@@ -670,7 +657,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
"""
@@ -710,7 +697,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
@@ -733,7 +721,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)
@@ -747,7 +736,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')
@@ -799,7 +788,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
@@ -886,7 +876,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')
@@ -960,8 +951,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()
@@ -1102,7 +1094,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:
@@ -1176,7 +1168,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:
@@ -1237,9 +1229,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
@@ -1290,11 +1284,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
@@ -1359,11 +1355,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
@@ -1415,10 +1413,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
"""
@@ -1463,11 +1461,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
@@ -1487,12 +1486,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
@@ -1714,21 +1713,6 @@ class Transport (threading.Thread, ClosingContextManager):
def _get_cipher(self, name, key, iv, operation):
if name not in self._cipher_info:
raise SSHException('Unknown client cipher ' + name)
- if name in ('arcfour128', 'arcfour256'):
- # arcfour cipher
- cipher = Cipher(
- self._cipher_info[name]['class'](key),
- None,
- backend=default_backend()
- )
- if operation is self._ENCRYPT:
- engine = cipher.encryptor()
- else:
- engine = cipher.decryptor()
- # as per RFC 4345, the first 1536 bytes of keystream
- # generated by the cipher MUST be discarded
- engine.encrypt(" " * 1536)
- return engine
else:
cipher = Cipher(
self._cipher_info[name]['class'](key),
diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst
index f7adf14a..32bb9250 100644
--- a/sites/www/changelog.rst
+++ b/sites/www/changelog.rst
@@ -2,12 +2,57 @@
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 <paramiko.file.BufferedFile>`, `Channel
+ <paramiko.channel.Channel>`, and `SFTPFile <paramiko.sftp_file.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.
+
+ 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.
+* :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.
+* :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.
+* :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.
+ 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 <http://flake8.pycqa.org/>`_
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>`
@@ -82,7 +127,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 <paramko.transport.Transport>`] which was previously
+ timeout [in `Transport <paramiko.transport.Transport>`] which was previously
hardcoded.) Credit: Shinya Okano.
* :support:`854 backported (1.17+)` Fix incorrect docstring/param-list for
`Transport.auth_gssapi_keyex
@@ -137,10 +182,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 <paramiko.sftp_file.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.
@@ -230,7 +275,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
@@ -341,8 +386,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.
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 <gzlist@googlemail.com>
+#
+# 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
diff --git a/tests/test_client.py b/tests/test_client.py
index 9c5761d6..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
+from paramiko.py3compat import PY2, b
from paramiko.ssh_exception import SSHException
@@ -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()
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()
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)
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()
diff --git a/tests/test_transport.py b/tests/test_transport.py
index 2ebdf854..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
@@ -165,6 +166,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'
@@ -849,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)