summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Deiss <s.deiss@science-computing.de>2014-03-26 11:39:26 +0100
committerSebastian Deiss <s.deiss@science-computing.de>2014-03-26 11:39:26 +0100
commita23d9bc654b5278f1ab43df825ed559e4f9a2332 (patch)
tree593ae35cd2b7f550f5b9ff3d4babac57f072e741
parent5407b1a27468d5abedde93b51aad5bd97bd046c4 (diff)
parentbd8f96d33a3e1ee6162540a6f230ef93e435528a (diff)
downloadparamiko-a23d9bc654b5278f1ab43df825ed559e4f9a2332.tar.gz
Merge branch 'master' into gssapi-py3-support
Conflicts: .gitignore README demos/demo_simple.py dev-requirements.txt paramiko/__init__.py paramiko/_winapi.py paramiko/agent.py paramiko/auth_handler.py paramiko/ber.py paramiko/buffered_pipe.py paramiko/channel.py paramiko/client.py paramiko/common.py paramiko/dsskey.py paramiko/ecdsakey.py paramiko/file.py paramiko/hostkeys.py paramiko/kex_gex.py paramiko/kex_group1.py paramiko/message.py paramiko/packet.py paramiko/pkey.py paramiko/primes.py paramiko/proxy.py paramiko/py3compat.py paramiko/server.py paramiko/sftp_client.py paramiko/transport.py paramiko/util.py paramiko/win_pageant.py setup.py sites/shared_conf.py sites/www/changelog.rst sites/www/conf.py sites/www/index.rst sites/www/installing.rst test.py tests/loop.py tests/stub_sftp.py tests/test_auth.py tests/test_client.py tests/test_file.py tests/test_hostkeys.py tests/test_kex.py tests/test_message.py tests/test_packetizer.py tests/test_pkey.py tests/test_sftp.py tests/test_sftp_big.py tests/test_transport.py tests/test_util.py
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml15
-rw-r--r--Makefile15
-rw-r--r--NEWS161
-rw-r--r--README10
-rwxr-xr-xdemos/demo_simple.py1
-rw-r--r--dev-requirements.txt11
-rw-r--r--fabfile.py39
-rw-r--r--paramiko/__init__.py43
-rw-r--r--paramiko/_winapi.py21
-rw-r--r--paramiko/agent.py99
-rw-r--r--paramiko/auth_handler.py37
-rw-r--r--paramiko/ber.py19
-rw-r--r--paramiko/buffered_pipe.py62
-rw-r--r--paramiko/channel.py454
-rw-r--r--paramiko/client.py324
-rw-r--r--paramiko/common.py54
-rw-r--r--paramiko/config.py143
-rw-r--r--paramiko/dsskey.py25
-rw-r--r--paramiko/ecdsakey.py15
-rw-r--r--paramiko/file.py125
-rw-r--r--paramiko/hostkeys.py247
-rw-r--r--paramiko/kex_gex.py8
-rw-r--r--paramiko/kex_group1.py15
-rw-r--r--paramiko/logging22.py66
-rw-r--r--paramiko/message.py146
-rw-r--r--paramiko/packet.py99
-rw-r--r--paramiko/pipe.py27
-rw-r--r--paramiko/pkey.py204
-rw-r--r--paramiko/primes.py14
-rw-r--r--paramiko/proxy.py51
-rw-r--r--paramiko/py3compat.py6
-rw-r--r--paramiko/resource.py17
-rw-r--r--paramiko/rsakey.py28
-rw-r--r--paramiko/server.py582
-rw-r--r--paramiko/sftp.py39
-rw-r--r--paramiko/sftp_attr.py44
-rw-r--r--paramiko/sftp_client.py448
-rw-r--r--paramiko/sftp_file.py209
-rw-r--r--paramiko/sftp_handle.py89
-rw-r--r--paramiko/sftp_server.py75
-rw-r--r--paramiko/sftp_si.py248
-rw-r--r--paramiko/ssh_exception.py39
-rw-r--r--paramiko/transport.py1032
-rw-r--r--paramiko/util.py100
-rw-r--r--paramiko/win_pageant.py22
-rw-r--r--setup.cfg2
-rw-r--r--setup.py46
-rw-r--r--sites/docs/api/agent.rst6
-rw-r--r--sites/docs/api/buffered_pipe.rst4
-rw-r--r--sites/docs/api/channel.rst4
-rw-r--r--sites/docs/api/client.rst5
-rw-r--r--sites/docs/api/config.rst5
-rw-r--r--sites/docs/api/file.rst4
-rw-r--r--sites/docs/api/hostkeys.rst5
-rw-r--r--sites/docs/api/keys.rst6
-rw-r--r--sites/docs/api/message.rst4
-rw-r--r--sites/docs/api/packet.rst4
-rw-r--r--sites/docs/api/pipe.rst4
-rw-r--r--sites/docs/api/proxy.rst4
-rw-r--r--sites/docs/api/server.rst5
-rw-r--r--sites/docs/api/sftp.rst13
-rw-r--r--sites/docs/api/ssh_exception.rst4
-rw-r--r--sites/docs/api/transport.rst5
-rw-r--r--sites/docs/conf.py16
-rw-r--r--sites/docs/index.rst72
-rw-r--r--sites/shared_conf.py4
-rw-r--r--sites/www/changelog.rst68
-rw-r--r--sites/www/conf.py8
-rw-r--r--sites/www/faq.rst9
-rw-r--r--sites/www/index.rst3
-rw-r--r--sites/www/installing.rst60
-rw-r--r--tasks.py49
-rwxr-xr-xtest.py12
-rw-r--r--tests/loop.py2
-rw-r--r--tests/stub_sftp.py6
-rw-r--r--tests/test_auth.py3
-rw-r--r--tests/test_buffered_pipe.py14
-rw-r--r--tests/test_client.py7
-rwxr-xr-xtests/test_file.py2
-rw-r--r--tests/test_hostkeys.py3
-rw-r--r--tests/test_kex.py12
-rw-r--r--tests/test_message.py2
-rw-r--r--tests/test_packetizer.py8
-rw-r--r--tests/test_pkey.py3
-rwxr-xr-xtests/test_sftp.py6
-rw-r--r--tests/test_sftp_big.py7
-rw-r--r--tests/test_transport.py9
-rw-r--r--tests/test_util.py5
-rw-r--r--tox-requirements.txt2
-rw-r--r--tox.ini2
91 files changed, 2878 insertions, 3177 deletions
diff --git a/.gitignore b/.gitignore
index bdf72de4..44b45974 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,6 @@ paramiko.egg-info/
test.log
docs/
demos/*.log
+!sites/docs
+_build
+.coverage
diff --git a/.travis.yml b/.travis.yml
index 291acf96..7042570f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,7 +7,18 @@ python:
install:
# Self-install for setup.py-driven deps
- pip install -e .
-script: python test.py --verbose
+ # Dev (doc/test running) requirements
+ - pip install coveralls # For coveralls.io specifically
+ - pip install -r dev-requirements.txt
+script:
+ # Main tests, with coverage!
+ - invoke 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
+ - invoke www -o -W
notifications:
irc:
channels: "irc.freenode.org#paramiko"
@@ -17,3 +28,5 @@ notifications:
on_failure: change
use_notice: true
email: false
+after_success:
+ - coveralls
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 572f867a..00000000
--- a/Makefile
+++ /dev/null
@@ -1,15 +0,0 @@
-release: docs
- python setup.py sdist register upload
-
-docs: paramiko/*
- epydoc --no-private -o docs/ paramiko
-
-clean:
- rm -rf build dist docs
- rm -f MANIFEST *.log demos/*.log
- rm -f paramiko/*.pyc
- rm -f test.log
- rm -rf paramiko.egg-info
-
-test:
- python ./test.py
diff --git a/NEWS b/NEWS
index c2d450bc..761f8e48 100644
--- a/NEWS
+++ b/NEWS
@@ -9,166 +9,13 @@ Issues noted as "'ssh' #NN" can be found at https://github.com/bitprophet/ssh/.
Issues noted as "Fabric #NN" can be found at https://github.com/fabric/fabric/.
-Releases
-========
-
-v1.12.1 (8th Jan 2014)
-----------------------
-
-* #176: Fix AttributeError bugs in known_hosts file (re)loading. Thanks to
- Nathan Scowcroft for the patch & Martin Blumenstingl for the initial test
- case.
-* #225: Note ecdsa requirement in README. Thanks to Amaury Rodriguez for the
- catch.
-
-v1.11.3 (8th Jan 2014)
-----------------------
-
-* #176: Fix AttributeError bugs in known_hosts file (re)loading. Thanks to
- Nathan Scowcroft for the patch & Martin Blumenstingl for the initial test
- case.
-
-v1.10.5 (8th Jan 2014)
-----------------------
-
-* #176: Fix AttributeError bugs in known_hosts file (re)loading. Thanks to
- Nathan Scowcroft for the patch & Martin Blumenstingl for the initial test
- case.
-
-v1.12.0 (27th Sep 2013)
------------------------
-
-* #152: Add tentative support for ECDSA keys. *This adds the ecdsa
- module as a new dependency of Paramiko.* The module is available at
- [warner/python-ecdsa on Github](https://github.com/warner/python-ecdsa) and
- [ecdsa on PyPI](https://pypi.python.org/pypi/ecdsa).
- * Note that you might still run into problems with key negotiation --
- Paramiko picks the first key that the server offers, which might not be
- what you have in your known_hosts file.
- * Mega thanks to Ethan Glasser-Camp for the patch.
-* #136: Add server-side support for the SSH protocol's 'env' command. Thanks to
- Benjamin Pollack for the patch.
-
-v1.11.2 (27th Sep 2013)
------------------------
-
-* #156: Fix potential deadlock condition when using Channel objects as sockets
- (e.g. when using SSH gatewaying). Thanks to Steven Noonan and Frank Arnold
- for catch & patch.
-* #179: Fix a missing variable causing errors when an ssh_config file has a
- non-default AddressFamily set. Thanks to Ed Marshall & Tomaz Muraus for catch
- & patch.
-* #200: Fix an exception-causing typo in `demo_simple.py`. Thanks to Alex
- Buchanan for catch & Dave Foster for patch.
-* #199: Typo fix in the license header cross-project. Thanks to Armin Ronacher
- for catch & patch.
-
-v1.10.4 (27th Sep 2013)
------------------------
-* #179: Fix a missing variable causing errors when an ssh_config file has a
- non-default AddressFamily set. Thanks to Ed Marshall & Tomaz Muraus for catch
- & patch.
-* #200: Fix an exception-causing typo in `demo_simple.py`. Thanks to Alex
- Buchanan for catch & Dave Foster for patch.
-* #199: Typo fix in the license header cross-project. Thanks to Armin Ronacher
- for catch & patch.
+**PLEASE NOTE:** For changes in 1.10.x and newer releases, please see
+www.paramiko.org's changelog page, or the source file, sites/www/changelog.rst
-v1.11.1 (20th Sep 2013)
------------------------
-
-* #162: Clean up HMAC module import to avoid deadlocks in certain uses of
- SSHClient. Thanks to Gernot Hillier for the catch & suggested
- fix.
-* #36: Fix the port-forwarding demo to avoid file descriptor errors. Thanks to
- Jonathan Halcrow for catch & patch.
-* #168: Update config handling to properly handle multiple 'localforward' and
- 'remoteforward' keys. Thanks to Emre Yılmaz for the patch.
-
-v1.10.3 (20th Sep 2013)
------------------------
-
-* #162: Clean up HMAC module import to avoid deadlocks in certain uses of
- SSHClient. Thanks to Gernot Hillier for the catch & suggested
- fix.
-* #36: Fix the port-forwarding demo to avoid file descriptor errors. Thanks to
- Jonathan Halcrow for catch & patch.
-* #168: Update config handling to properly handle multiple 'localforward' and
- 'remoteforward' keys. Thanks to Emre Yılmaz for the patch.
-
-v1.11.0 (26th Jul 2013)
------------------------
-
-* #98: On Windows, when interacting with the PuTTY PAgeant, Paramiko now
- creates the shared memory map with explicit Security Attributes of the user,
- which is the same technique employed by the canonical PuTTY library to avoid
- permissions issues when Paramiko is running under a different UAC context
- than the PuTTY Ageant process. Thanks to Jason R. Coombs for the patch.
-* #100: Remove use of PyWin32 in `win_pageant` module. Module was already
- dependent on ctypes for constructing appropriate structures and had ctypes
- implementations of all functionality. Thanks to Jason R. Coombs for the
- patch.
-* #87: Ensure updates to `known_hosts` files account for any updates to said
- files after Paramiko initially read them. (Includes related fix to guard
- against duplicate entries during subsequent `known_hosts` loads.) Thanks to
- `@sunweaver` for the contribution.
-
-v1.10.2 (26th Jul 2013)
------------------------
-* #153, #67: Warn on parse failure when reading known_hosts file. Thanks to
- `@glasserc` for patch.
-* #146: Indentation fixes for readability. Thanks to Abhinav Upadhyay for catch
- & patch.
-
-v1.10.1 (5th Apr 2013)
-----------------------
-
-* #142: (Fabric #811) SFTP put of empty file will still return the attributes
- of the put file. Thanks to Jason R. Coombs for the patch.
-* #154: (Fabric #876) Forwarded SSH agent connections left stale local pipes
- lying around, which could cause local (and sometimes remote or network)
- resource starvation when running many agent-using remote commands. Thanks to
- Kevin Tegtmeier for catch & patch.
-
-v1.10.0 (1st Mar 2013)
---------------------
-
-* #66: Batch SFTP writes to help speed up file transfers. Thanks to Olle
- Lundberg for the patch.
-* #133: Fix handling of window-change events to be on-spec and not
- attempt to wait for a response from the remote sshd; this fixes problems with
- less common targets such as some Cisco devices. Thanks to Phillip Heller for
- catch & patch.
-* #93: Overhaul SSH config parsing to be in line with `man ssh_config` (& the
- behavior of `ssh` itself), including addition of parameter expansion within
- config values. Thanks to Olle Lundberg for the patch.
-* #110: Honor SSH config `AddressFamily` setting when looking up local
- host's FQDN. Thanks to John Hensley for the patch.
-* #128: Defer FQDN resolution until needed, when parsing SSH config files.
- Thanks to Parantapa Bhattacharya for catch & patch.
-* #102: Forego random padding for packets when running under `*-ctr` ciphers.
- This corrects some slowdowns on platforms where random byte generation is
- inefficient (e.g. Windows). Thanks to `@warthog618` for catch & patch, and
- Michael van der Kolff for code/technique review.
-* #127: Turn `SFTPFile` into a context manager. Thanks to Michael Williamson
- for the patch.
-* #116: Limit `Message.get_bytes` to an upper bound of 1MB to protect against
- potential DoS vectors. Thanks to `@mvschaik` for catch & patch.
-* #115: Add convenience `get_pty` kwarg to `Client.exec_command` so users not
- manually controlling a channel object can still toggle PTY creation. Thanks
- to Michael van der Kolff for the patch.
-* #71: Add `SFTPClient.putfo` and `.getfo` methods to allow direct
- uploading/downloading of file-like objects. Thanks to Eric Buehl for the
- patch.
-* #113: Add `timeout` parameter to `SSHClient.exec_command` for easier setting
- of the command's internal channel object's timeout. Thanks to Cernov Vladimir
- for the patch.
-* #94: Remove duplication of SSH port constant. Thanks to Olle Lundberg for the
- catch.
-* #80: Expose the internal "is closed" property of the file transfer class
- `BufferedFile` as `.closed`, better conforming to Python's file interface.
- Thanks to `@smunaut` and James Hiscock for catch & patch.
+Releases
+========
v1.9.0 (6th Nov 2012)
---------------------
diff --git a/README b/README
index 2712e9ff..31cc94e0 100644
--- a/README
+++ b/README
@@ -5,7 +5,7 @@ paramiko
:Paramiko: Python SSH module
:Copyright: Copyright (c) 2003-2009 Robey Pointer <robeypointer@gmail.com>
-:Copyright: Copyright (c) 2014 Jeff Forcier <jeff@bitprophet.org>
+:Copyright: Copyright (c) 2013-2014 Jeff Forcier <jeff@bitprophet.org>
:License: LGPL
:Homepage: https://github.com/paramiko/paramiko/
:API docs: http://docs.paramiko.org
@@ -34,7 +34,8 @@ that should have come with this archive.
Requirements
------------
- - python 2.6 or better <http://www.python.org/>
+ - Python 2.6 or better <http://www.python.org/> - this includes Python
+ 3.3 and higher as well.
- pycrypto 2.1 or better <https://www.dlitz.net/software/pycrypto/>
- ecdsa 0.9 or better <https://pypi.python.org/pypi/ecdsa>
@@ -142,10 +143,7 @@ Use
---
the demo scripts are probably the best example of how to use this package.
-there is also a lot of documentation, generated with epydoc, in the doc/
-folder. point your browser there. seriously, do it. mad props to
-epydoc, which actually motivated me to write more documentation than i
-ever would have before.
+there is also a lot of documentation, generated with Sphinx autodoc, in the doc/ folder.
there are also unit tests here::
diff --git a/demos/demo_simple.py b/demos/demo_simple.py
index a9d363da..7f179b90 100755
--- a/demos/demo_simple.py
+++ b/demos/demo_simple.py
@@ -85,6 +85,7 @@ try:
except Exception:
password = getpass.getpass('Password for %s@%s: ' % (username, hostname))
client.connect(hostname, Port, username, password)
+
chan = client.invoke_shell()
print(repr(client.get_transport()))
print('*** Here we go!\n')
diff --git a/dev-requirements.txt b/dev-requirements.txt
index cdd120a9..a96421cd 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,2 +1,9 @@
-tox>=1.4
-epydoc>=3.0
+# Older junk
+tox>=1.4,<1.5
+# For newer tasks like building Sphinx docs.
+# NOTE: Requires Python >=2.6
+invoke>=0.7.0
+invocations>=0.5.0
+sphinx>=1.1.3
+alabaster>=0.3.1
+releases>=0.5.2 \ No newline at end of file
diff --git a/fabfile.py b/fabfile.py
deleted file mode 100644
index 7883daba..00000000
--- a/fabfile.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from fabric.api import task, sudo, env, local, hosts
-from fabric.contrib.project import rsync_project
-from fabric.contrib.console import confirm
-
-
-@task
-@hosts("paramiko.org")
-def upload_docs():
- target = "/var/www/paramiko.org"
- staging = "/tmp/paramiko_docs"
- sudo("mkdir -p %s" % staging)
- sudo("chown -R %s %s" % (env.user, staging))
- sudo("rm -rf %s/*" % target)
- rsync_project(local_dir='docs/', remote_dir=staging, delete=True)
- sudo("cp -R %s/* %s/" % (staging, target))
-
-@task
-def build_docs():
- local("epydoc --no-private -o docs/ paramiko")
-
-@task
-def clean():
- local("rm -rf build dist docs")
- local("rm -f MANIFEST *.log demos/*.log")
- local("rm -f paramiko/*.pyc")
- local("rm -f test.log")
- local("rm -rf paramiko.egg-info")
-
-@task
-def test():
- local("python ./test.py")
-
-@task
-def release():
- confirm("Only hit Enter if you remembered to update the version!")
- confirm("Also, did you remember to tag your release?")
- build_docs()
- local("python setup.py sdist register upload")
- upload_docs()
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index 8304cf6d..f4ce937b 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -16,44 +16,10 @@
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-"""
-I{Paramiko} (a combination of the esperanto words for "paranoid" and "friend")
-is a module for python 2.6 or greater that implements the SSH2 protocol for
-secure (encrypted and authenticated) connections to remote machines. Unlike
-SSL (aka TLS), the SSH2 protocol does not require hierarchical certificates
-signed by a powerful central authority. You may know SSH2 as the protocol that
-replaced C{telnet} and C{rsh} for secure access to remote shells, but the
-protocol also includes the ability to open arbitrary channels to remote
-services across an encrypted tunnel. (This is how C{sftp} works, for example.)
-
-The high-level client API starts with creation of an L{SSHClient} object.
-For more direct control, pass a socket (or socket-like object) to a
-L{Transport}, and use L{start_server <Transport.start_server>} or
-L{start_client <Transport.start_client>} to negoatite
-with the remote host as either a server or client. As a client, you are
-responsible for authenticating using a password or private key, and checking
-the server's host key. I{(Key signature and verification is done by paramiko,
-but you will need to provide private keys and check that the content of a
-public key matches what you expected to see.)} As a server, you are
-responsible for deciding which users, passwords, and keys to allow, and what
-kind of channels to allow.
-
-Once you have finished, either side may request flow-controlled L{Channel}s to
-the other side, which are python objects that act like sockets, but send and
-receive data over the encrypted session.
-
-Paramiko is written entirely in python (no C or platform-dependent code) and is
-released under the GNU Lesser General Public License (LGPL).
-
-Website: U{https://github.com/paramiko/paramiko/}
-
-Mailing list: U{paramiko@librelist.com<mailto:paramiko@librelist.com>}
-"""
-
import sys
if sys.version_info < (2, 6):
- raise RuntimeError('You need python 2.6+ for this module.')
+ raise RuntimeError('You need Python 2.6+ for this module.')
__author__ = "Jeff Forcier <jeff@bitprophet.org>"
@@ -90,13 +56,6 @@ from paramiko.hostkeys import HostKeys
from paramiko.config import SSHConfig
from paramiko.proxy import ProxyCommand
-# fix module names for epydoc
-for c in list(locals().values()):
- if issubclass(type(c), type) or type(c).__name__ == 'classobj':
- # classobj for exceptions :/
- c.__module__ = __name__
-del c
-
from paramiko.common import AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, \
OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_FAILED_CONNECT_FAILED, \
OPEN_FAILED_UNKNOWN_CHANNEL_TYPE, OPEN_FAILED_RESOURCE_SHORTAGE
diff --git a/paramiko/_winapi.py b/paramiko/_winapi.py
index 25d1a308..0d55d291 100644
--- a/paramiko/_winapi.py
+++ b/paramiko/_winapi.py
@@ -14,6 +14,11 @@ try:
except ImportError:
import __builtin__ as builtins
+try:
+ USHORT = ctypes.wintypes.USHORT
+except AttributeError:
+ USHORT = ctypes.c_ushort
+
######################
# jaraco.windows.error
@@ -85,9 +90,6 @@ def handle_nonzero_success(result):
raise WindowsError()
-#####################
-# jaraco.windows.mmap
-
CreateFileMapping = ctypes.windll.kernel32.CreateFileMappingW
CreateFileMapping.argtypes = [
ctypes.wintypes.HANDLE,
@@ -134,15 +136,18 @@ class MemoryMap(object):
self.pos = pos
def write(self, msg):
- ctypes.windll.msvcrt.memcpy(self.view + self.pos, msg, len(msg))
- self.pos += len(msg)
+ n = len(msg)
+ if self.pos + n >= self.length: # A little safety.
+ raise ValueError("Refusing to write %d bytes" % n)
+ ctypes.windll.kernel32.RtlMoveMemory(self.view + self.pos, msg, n)
+ self.pos += n
def read(self, n):
"""
Read n bytes from mapped view.
"""
out = ctypes.create_string_buffer(n)
- ctypes.windll.msvcrt.memcpy(out, self.view + self.pos, n)
+ ctypes.windll.kernel32.RtlMoveMemory(out, self.view + self.pos, n)
self.pos += n
return out.raw
@@ -177,7 +182,7 @@ class SECURITY_DESCRIPTOR(ctypes.Structure):
PACL Dacl;
} SECURITY_DESCRIPTOR;
"""
- SECURITY_DESCRIPTOR_CONTROL = ctypes.wintypes.USHORT
+ SECURITY_DESCRIPTOR_CONTROL = USHORT
REVISION = 1
_fields_ = [
@@ -221,7 +226,7 @@ def GetTokenInformation(token, information_class):
"""
data_size = ctypes.wintypes.DWORD()
ctypes.windll.advapi32.GetTokenInformation(token, information_class.num,
- 0, 0, ctypes.byref(data_size))
+ 0, 0, ctypes.byref(data_size))
data = ctypes.create_string_buffer(data_size.value)
handle_nonzero_success(ctypes.windll.advapi32.GetTokenInformation(token,
information_class.num,
diff --git a/paramiko/agent.py b/paramiko/agent.py
index 8f2a486a..68e9be9f 100644
--- a/paramiko/agent.py
+++ b/paramiko/agent.py
@@ -17,7 +17,7 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-SSH Agent interface for Unix clients.
+SSH Agent interface
"""
import os
@@ -29,12 +29,12 @@ import time
import tempfile
import stat
from select import select
+from paramiko.common import asbytes, io_sleep
+from paramiko.py3compat import byte_chr
from paramiko.ssh_exception import SSHException
from paramiko.message import Message
from paramiko.pkey import PKey
-from paramiko.channel import Channel
-from paramiko.common import *
from paramiko.util import retry_on_signal
cSSH2_AGENTC_REQUEST_IDENTITIES = byte_chr(11)
@@ -43,17 +43,8 @@ cSSH2_AGENTC_SIGN_REQUEST = byte_chr(13)
SSH2_AGENT_SIGN_RESPONSE = 14
-class AgentSSH(object):
- """
- Client interface for using private keys from an SSH agent running on the
- local machine. If an SSH agent is running, this class can be used to
- connect to it and retreive L{PKey} objects which can be used when
- attempting to authenticate to remote SSH servers.
- Because the SSH agent protocol uses environment variables and unix-domain
- sockets, this probably doesn't work on Windows. It does work on most
- posix platforms though (Linux and MacOS X, for example).
- """
+class AgentSSH(object):
def __init__(self):
self._conn = None
self._keys = ()
@@ -64,8 +55,9 @@ class AgentSSH(object):
no SSH agent was running (or it couldn't be contacted), an empty list
will be returned.
- @return: a list of keys available on the SSH agent
- @rtype: tuple of L{AgentKey}
+ :return:
+ a tuple of `.AgentKey` objects representing keys available on the
+ SSH agent
"""
return self._keys
@@ -103,8 +95,11 @@ class AgentSSH(object):
result += extra
return result
+
class AgentProxyThread(threading.Thread):
- """ Class in charge of communication between two chan """
+ """
+ Class in charge of communication between two channels.
+ """
def __init__(self, agent):
threading.Thread.__init__(self, target=self.run)
self._agent = agent
@@ -112,7 +107,7 @@ class AgentProxyThread(threading.Thread):
def run(self):
try:
- (r,addr) = self.get_connection()
+ (r, addr) = self.get_connection()
self.__inr = r
self.__addr = addr
self._agent.connect()
@@ -149,6 +144,7 @@ class AgentProxyThread(threading.Thread):
self.__inr.close()
self._agent._conn.close()
+
class AgentLocalProxy(AgentProxyThread):
"""
Class to be used when wanting to ask a local SSH Agent being
@@ -158,18 +154,20 @@ class AgentLocalProxy(AgentProxyThread):
AgentProxyThread.__init__(self, agent)
def get_connection(self):
- """ Return a pair of socket object and string address
- May Block !
+ """
+ Return a pair of socket object and string address.
+
+ May block!
"""
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
conn.bind(self._agent._get_filename())
conn.listen(1)
- (r,addr) = conn.accept()
- return (r, addr)
+ (r, addr) = conn.accept()
+ return r, addr
except:
raise
- return None
+
class AgentRemoteProxy(AgentProxyThread):
"""
@@ -180,22 +178,20 @@ class AgentRemoteProxy(AgentProxyThread):
self.__chan = chan
def get_connection(self):
- """
- Class to be used when wanting to ask a local SSH Agent being
- asked from a remote fake agent (so use a unix socket for ex.)
- """
- return (self.__chan, None)
+ return self.__chan, None
+
class AgentClientProxy(object):
"""
Class proxying request as a client:
- -> client ask for a request_forward_agent()
- -> server creates a proxy and a fake SSH Agent
- -> server ask for establishing a connection when needed,
+
+ #. client ask for a request_forward_agent()
+ #. server creates a proxy and a fake SSH Agent
+ #. server ask for establishing a connection when needed,
calling the forward_agent_handler at client side.
- -> the forward_agent_handler launch a thread for connecting
+ #. the forward_agent_handler launch a thread for connecting
the remote fake agent and the local agent
- -> Communication occurs ...
+ #. Communication occurs ...
"""
def __init__(self, chanRemote):
self._conn = None
@@ -208,7 +204,7 @@ class AgentClientProxy(object):
def connect(self):
"""
- Method automatically called by the run() method of the AgentProxyThread
+ Method automatically called by ``AgentProxyThread.run``.
"""
if ('SSH_AUTH_SOCK' in os.environ) and (sys.platform != 'win32'):
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
@@ -239,11 +235,12 @@ class AgentClientProxy(object):
if self._conn is not None:
self._conn.close()
+
class AgentServerProxy(AgentSSH):
"""
- @param t : transport used for the Forward for SSH Agent communication
+ :param .Transport t: Transport used for SSH Agent communication forwarding
- @raise SSHException: mostly if we lost the agent
+ :raises SSHException: mostly if we lost the agent
"""
def __init__(self, t):
AgentSSH.__init__(self)
@@ -279,16 +276,15 @@ class AgentServerProxy(AgentSSH):
"""
Helper for the environnement under unix
- @return: the SSH_AUTH_SOCK Environnement variables
- @rtype: dict
+ :return:
+ a dict containing the ``SSH_AUTH_SOCK`` environnement variables
"""
- env = {}
- env['SSH_AUTH_SOCK'] = self._get_filename()
- return env
+ return {'SSH_AUTH_SOCK': self._get_filename()}
def _get_filename(self):
return self._file
+
class AgentRequestHandler(object):
def __init__(self, chanClient):
self._conn = None
@@ -306,27 +302,22 @@ class AgentRequestHandler(object):
for p in self.__clientProxys:
p.close()
+
class Agent(AgentSSH):
"""
Client interface for using private keys from an SSH agent running on the
local machine. If an SSH agent is running, this class can be used to
- connect to it and retreive L{PKey} objects which can be used when
+ connect to it and retreive `.PKey` objects which can be used when
attempting to authenticate to remote SSH servers.
- Because the SSH agent protocol uses environment variables and unix-domain
- sockets, this probably doesn't work on Windows. It does work on most
- posix platforms though (Linux and MacOS X, for example).
- """
+ Upon initialization, a session with the local machine's SSH agent is
+ opened, if one is running. If no agent is running, initialization will
+ succeed, but `get_keys` will return an empty tuple.
+ :raises SSHException:
+ if an SSH agent is found, but speaks an incompatible protocol
+ """
def __init__(self):
- """
- Open a session with the local machine's SSH agent, if one is running.
- If no agent is running, initialization will succeed, but L{get_keys}
- will return an empty tuple.
-
- @raise SSHException: if an SSH agent is found, but speaks an
- incompatible protocol
- """
AgentSSH.__init__(self)
if ('SSH_AUTH_SOCK' in os.environ) and (sys.platform != 'win32'):
@@ -353,13 +344,13 @@ class Agent(AgentSSH):
"""
self._close()
+
class AgentKey(PKey):
"""
Private key held in a local SSH agent. This type of key can be used for
authenticating to a remote server (signing). Most other key operations
work as expected.
"""
-
def __init__(self, agent, blob):
self.agent = agent
self.blob = blob
diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py
index ee5fb174..a048e9b3 100644
--- a/paramiko/auth_handler.py
+++ b/paramiko/auth_handler.py
@@ -17,18 +17,21 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-L{AuthHandler}
+`.AuthHandler`
"""
-import threading
import weakref
+from paramiko.common import cMSG_SERVICE_REQUEST, cMSG_DISCONNECT, \
+ DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, \
+ cMSG_USERAUTH_REQUEST, cMSG_SERVICE_ACCEPT, DEBUG, AUTH_SUCCESSFUL, INFO, \
+ cMSG_USERAUTH_SUCCESS, cMSG_USERAUTH_FAILURE, AUTH_PARTIALLY_SUCCESSFUL, \
+ cMSG_USERAUTH_INFO_REQUEST, WARNING, AUTH_FAILED, cMSG_USERAUTH_PK_OK, \
+ cMSG_USERAUTH_INFO_RESPONSE, MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT, \
+ MSG_USERAUTH_REQUEST, MSG_USERAUTH_SUCCESS, MSG_USERAUTH_FAILURE, \
+ MSG_USERAUTH_BANNER, MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE
-# this helps freezing utils
-import encodings.utf_8
-
-from paramiko.common import *
-from paramiko import util
from paramiko.message import Message
+from paramiko.py3compat import bytestring
from paramiko.ssh_exception import SSHException, AuthenticationException, \
BadAuthenticationType, PartialAuthentication
from paramiko.server import InteractiveQuery
@@ -46,6 +49,7 @@ class AuthHandler (object):
self.authenticated = False
self.auth_event = None
self.auth_method = ''
+ self.banner = None
self.password = None
self.private_key = None
self.interactive_handler = None
@@ -139,10 +143,8 @@ class AuthHandler (object):
if self.auth_event is not None:
self.auth_event.set()
-
### internals...
-
def _request_auth(self):
m = Message()
m.add_byte(cMSG_SERVICE_REQUEST)
@@ -174,7 +176,7 @@ class AuthHandler (object):
m.add_string(username)
m.add_string(service)
m.add_string('publickey')
- m.add_boolean(1)
+ m.add_boolean(True)
m.add_string(key.get_name())
m.add_string(key)
return m.asbytes()
@@ -193,7 +195,7 @@ class AuthHandler (object):
e = self.transport.get_exception()
if e is None:
e = AuthenticationException('Authentication failed.')
- # this is horrible. python Exception isn't yet descended from
+ # this is horrible. Python Exception isn't yet descended from
# object, so type(e) won't work. :(
if issubclass(e.__class__, PartialAuthentication):
return e.allowed_types
@@ -331,9 +333,9 @@ class AuthHandler (object):
m.add_byte(cMSG_USERAUTH_FAILURE)
m.add_string(self.transport.server_object.get_allowed_auths(username))
if result == AUTH_PARTIALLY_SUCCESSFUL:
- m.add_boolean(1)
+ m.add_boolean(True)
else:
- m.add_boolean(0)
+ m.add_boolean(False)
self.auth_fail_count += 1
self.transport._send_message(m)
if self.auth_fail_count >= 10:
@@ -360,7 +362,7 @@ class AuthHandler (object):
m = Message()
m.add_byte(cMSG_USERAUTH_FAILURE)
m.add_string('none')
- m.add_boolean(0)
+ m.add_boolean(False)
self.transport._send_message(m)
return
if self.authenticated:
@@ -555,7 +557,7 @@ class AuthHandler (object):
self.transport._log(INFO, 'Authentication (%s) successful!' % self.auth_method)
self.authenticated = True
self.transport._auth_trigger()
- if self.auth_event != None:
+ if self.auth_event is not None:
self.auth_event.set()
def _parse_userauth_failure(self, m):
@@ -573,11 +575,12 @@ class AuthHandler (object):
self.transport._log(INFO, 'Authentication (%s) failed.' % self.auth_method)
self.authenticated = False
self.username = None
- if self.auth_event != None:
+ if self.auth_event is not None:
self.auth_event.set()
def _parse_userauth_banner(self, m):
banner = m.get_string()
+ self.banner = banner
lang = m.get_string()
self.transport._log(INFO, 'Auth banner: %s' % banner)
# who cares.
@@ -615,7 +618,6 @@ class AuthHandler (object):
return
self._send_auth_result(self.auth_username, 'keyboard-interactive', result)
-
_handler_table = {
MSG_SERVICE_REQUEST: _parse_service_request,
MSG_SERVICE_ACCEPT: _parse_service_accept,
@@ -626,4 +628,3 @@ class AuthHandler (object):
MSG_USERAUTH_INFO_REQUEST: _parse_userauth_info_request,
MSG_USERAUTH_INFO_RESPONSE: _parse_userauth_info_response,
}
-
diff --git a/paramiko/ber.py b/paramiko/ber.py
index c4f35210..af4e2138 100644
--- a/paramiko/ber.py
+++ b/paramiko/ber.py
@@ -15,10 +15,10 @@
# 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.
-
+from paramiko.common import max_byte, zero_byte
+from paramiko.py3compat import b, byte_ord, byte_chr, long
import paramiko.util as util
-from paramiko.common import *
class BERException (Exception):
@@ -71,12 +71,12 @@ class BER(object):
t = size & 0x7f
if self.idx + t > len(self.content):
return None
- size = util.inflate_long(self.content[self.idx : self.idx + t], True)
+ size = util.inflate_long(self.content[self.idx: self.idx + t], True)
self.idx += t
if self.idx + size > len(self.content):
# can't fit
return None
- data = self.content[self.idx : self.idx + size]
+ data = self.content[self.idx: self.idx + size]
self.idx += size
# now switch on id
if ident == 0x30:
@@ -91,9 +91,9 @@ class BER(object):
def decode_sequence(data):
out = []
- b = BER(data)
+ ber = BER(data)
while True:
- x = b.decode_next()
+ x = ber.decode_next()
if x is None:
break
out.append(x)
@@ -126,8 +126,13 @@ class BER(object):
raise BERException('Unknown type for encoding: %s' % repr(type(x)))
def encode_sequence(data):
- b = BER()
+ ber = BER()
for item in data:
+<<<<<<< HEAD
b.encode(item)
return b.asbytes()
+=======
+ ber.encode(item)
+ return ber.asbytes()
+>>>>>>> master
encode_sequence = staticmethod(encode_sequence)
diff --git a/paramiko/buffered_pipe.py b/paramiko/buffered_pipe.py
index 02408a20..ac35b3e1 100644
--- a/paramiko/buffered_pipe.py
+++ b/paramiko/buffered_pipe.py
@@ -17,7 +17,7 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-Attempt to generalize the "feeder" part of a Channel: an object which can be
+Attempt to generalize the "feeder" part of a `.Channel`: an object which can be
read from and closed, but is reading from a buffer fed by another thread. The
read operations are blocking and can have a timeout set.
"""
@@ -25,12 +25,12 @@ read operations are blocking and can have a timeout set.
import array
import threading
import time
-from paramiko.common import *
+from paramiko.py3compat import PY2, b
class PipeTimeout (IOError):
"""
- Indicates that a timeout was reached on a read from a L{BufferedPipe}.
+ Indicates that a timeout was reached on a read from a `.BufferedPipe`.
"""
pass
@@ -39,7 +39,7 @@ class BufferedPipe (object):
"""
A buffer that obeys normal read (with timeout) & close semantics for a
file or socket, but is fed data from another thread. This is used by
- L{Channel}.
+ `.Channel`.
"""
def __init__(self):
@@ -62,15 +62,13 @@ class BufferedPipe (object):
def _buffer_tobytes(self, limit=None):
return self._buffer[:limit].tobytes()
-
def set_event(self, event):
"""
Set an event on this buffer. When data is ready to be read (or the
buffer has been closed), the event will be set. When no data is
ready, the event will be cleared.
- @param event: the event to set/clear
- @type event: Event
+ :param threading.Event event: the event to set/clear
"""
self._event = event
if len(self._buffer) > 0:
@@ -83,8 +81,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
- @type data: str
+ :param data: the data to add, as a `str`
"""
self._lock.acquire()
try:
@@ -98,12 +95,12 @@ class BufferedPipe (object):
def read_ready(self):
"""
Returns true if data is buffered and ready to be read from this
- feeder. A C{False} result does not mean that the feeder has closed;
+ feeder. A ``False`` result does not mean that the feeder has closed;
it means you may need to wait before more data arrives.
- @return: C{True} if a L{read} call would immediately return at least
- one byte; C{False} otherwise.
- @rtype: bool
+ :return:
+ ``True`` if a `read` call would immediately return at least one
+ byte; ``False`` otherwise.
"""
self._lock.acquire()
try:
@@ -117,24 +114,22 @@ class BufferedPipe (object):
"""
Read data from the pipe. The return value is a string representing
the data received. The maximum amount of data to be received at once
- is specified by C{nbytes}. If a string of length zero is returned,
+ is specified by ``nbytes``. If a string of length zero is returned,
the pipe has been closed.
- The optional C{timeout} argument can be a nonnegative float expressing
- seconds, or C{None} for no timeout. If a float is given, a
- C{PipeTimeout} will be raised if the timeout period value has
- elapsed before any data arrives.
-
- @param nbytes: maximum number of bytes to read
- @type nbytes: int
- @param timeout: maximum seconds to wait (or C{None}, the default, to
- wait forever)
- @type timeout: float
- @return: data
- @rtype: str
+ The optional ``timeout`` argument can be a nonnegative float expressing
+ seconds, or ``None`` for no timeout. If a float is given, a
+ `.PipeTimeout` will be raised if the timeout period value has elapsed
+ before any data arrives.
+
+ :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 `str`
- @raise 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()
@@ -173,8 +168,9 @@ class BufferedPipe (object):
"""
Clear out the buffer and return all data that was in it.
- @return: any data that was in the buffer prior to clearing it out
- @rtype: str
+ :return:
+ any data that was in the buffer prior to clearing it out, as a
+ `str`
"""
self._lock.acquire()
try:
@@ -188,7 +184,7 @@ class BufferedPipe (object):
def close(self):
"""
- Close this pipe object. Future calls to L{read} after the buffer
+ Close this pipe object. Future calls to `read` after the buffer
has been emptied will return immediately with an empty string.
"""
self._lock.acquire()
@@ -204,12 +200,10 @@ class BufferedPipe (object):
"""
Return the number of bytes buffered.
- @return: number of bytes bufferes
- @rtype: int
+ :return: number (`int`) of bytes buffered
"""
self._lock.acquire()
try:
return len(self._buffer)
finally:
self._lock.release()
-
diff --git a/paramiko/channel.py b/paramiko/channel.py
index 9980fcea..e10ddbac 100644
--- a/paramiko/channel.py
+++ b/paramiko/channel.py
@@ -21,15 +21,17 @@ Abstraction for an SSH2 channel.
"""
import binascii
-import sys
import time
import threading
import socket
-import os
-from paramiko.common import *
from paramiko import util
+from paramiko.common import cMSG_CHANNEL_REQUEST, cMSG_CHANNEL_WINDOW_ADJUST, \
+ cMSG_CHANNEL_DATA, cMSG_CHANNEL_EXTENDED_DATA, DEBUG, ERROR, \
+ cMSG_CHANNEL_SUCCESS, cMSG_CHANNEL_FAILURE, cMSG_CHANNEL_EOF, \
+ cMSG_CHANNEL_CLOSE
from paramiko.message import Message
+from paramiko.py3compat import bytes_types
from paramiko.ssh_exception import SSHException
from paramiko.file import BufferedFile
from paramiko.buffered_pipe import BufferedPipe, PipeTimeout
@@ -42,29 +44,28 @@ MIN_PACKET_SIZE = 1024
class Channel (object):
"""
- A secure tunnel across an SSH L{Transport}. A Channel is meant to behave
+ A secure tunnel across an SSH `.Transport`. A Channel is meant to behave
like a socket, and has an API that should be indistinguishable from the
- python socket API.
+ Python socket API.
Because SSH2 has a windowing kind of flow control, if you stop reading data
from a Channel and its buffer fills up, the server will be unable to send
you any more data until you read some of it. (This won't affect other
channels on the same transport -- all channels on a single transport are
flow-controlled independently.) Similarly, if the server isn't reading
- data you send, calls to L{send} may block, unless you set a timeout. This
+ data you send, calls to `send` may block, unless you set a timeout. This
is exactly like a normal network socket, so it shouldn't be too surprising.
"""
def __init__(self, chanid):
"""
Create a new channel. The channel is not associated with any
- particular session or L{Transport} until the Transport attaches it.
+ particular session or `.Transport` until the Transport attaches it.
Normally you would only call this method from the constructor of a
- subclass of L{Channel}.
+ subclass of `.Channel`.
- @param chanid: the ID of this channel, as passed by an existing
- L{Transport}.
- @type chanid: int
+ :param int chanid:
+ the ID of this channel, as passed by an existing `.Transport`.
"""
self.chanid = chanid
self.remote_chanid = 0
@@ -104,8 +105,6 @@ class Channel (object):
def __repr__(self):
"""
Return a string representation of this object, for debugging.
-
- @rtype: str
"""
out = '<paramiko.Channel %d' % self.chanid
if self.closed:
@@ -115,7 +114,7 @@ class Channel (object):
out += ' (EOF received)'
if self.eof_sent:
out += ' (EOF sent)'
- out += ' (open) window=%d' % (self.out_window_size)
+ out += ' (open) window=%d' % self.out_window_size
if len(self.in_buffer) > 0:
out += ' in-buffer=%d' % (len(self.in_buffer),)
out += ' -> ' + repr(self.transport)
@@ -127,23 +126,18 @@ class Channel (object):
"""
Request a pseudo-terminal from the server. This is usually used right
after creating a client channel, to ask the server to provide some
- basic terminal semantics for a shell invoked with L{invoke_shell}.
+ basic terminal semantics for a shell invoked with `invoke_shell`.
It isn't necessary (or desirable) to call this method if you're going
- to exectue a single command with L{exec_command}.
-
- @param term: the terminal type to emulate (for example, C{'vt100'})
- @type term: str
- @param width: width (in characters) of the terminal screen
- @type width: int
- @param height: height (in characters) of the terminal screen
- @type height: int
- @param width_pixels: width (in pixels) of the terminal screen
- @type width_pixels: int
- @param height_pixels: height (in pixels) of the terminal screen
- @type height_pixels: int
+ to exectue a single command with `exec_command`.
+
+ :param str term: the terminal type to emulate (for example, ``'vt100'``)
+ :param int width: width (in characters) of the terminal screen
+ :param int height: height (in characters) of the terminal screen
+ :param int width_pixels: width (in pixels) of the terminal screen
+ :param int height_pixels: height (in pixels) of the terminal screen
- @raise SSHException: if the request was rejected or the channel was
- closed
+ :raises SSHException:
+ if the request was rejected or the channel was closed
"""
if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open')
@@ -168,14 +162,14 @@ class Channel (object):
allows it, the channel will then be directly connected to the stdin,
stdout, and stderr of the shell.
- Normally you would call L{get_pty} before this, in which case the
+ Normally you would call `get_pty` before this, in which case the
shell will operate through the pty, and the channel will be connected
to the stdin and stdout of the pty.
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.
- @raise SSHException: if the request was rejected or the channel was
+ :raises SSHException: if the request was rejected or the channel was
closed
"""
if self.closed or self.eof_received or self.eof_sent or not self.active:
@@ -184,7 +178,7 @@ class Channel (object):
m.add_byte(cMSG_CHANNEL_REQUEST)
m.add_int(self.remote_chanid)
m.add_string('shell')
- m.add_boolean(1)
+ m.add_boolean(True)
self._event_pending()
self.transport._send_user_message(m)
self._wait_for_event()
@@ -199,10 +193,9 @@ class Channel (object):
can't be reused. You must open a new channel if you wish to execute
another command.
- @param command: a shell command to execute.
- @type command: str
+ :param str command: a shell command to execute.
- @raise SSHException: if the request was rejected or the channel was
+ :raises SSHException: if the request was rejected or the channel was
closed
"""
if self.closed or self.eof_received or self.eof_sent or not self.active:
@@ -219,18 +212,17 @@ class Channel (object):
def invoke_subsystem(self, subsystem):
"""
- Request a subsystem on the server (for example, C{sftp}). If the
+ Request a subsystem on the server (for example, ``sftp``). If the
server allows it, the channel will then be directly connected to the
requested subsystem.
When the subsystem finishes, the channel will be closed and can't be
reused.
- @param subsystem: name of the subsystem being requested.
- @type subsystem: str
+ :param str subsystem: name of the subsystem being requested.
- @raise SSHException: if the request was rejected or the channel was
- closed
+ :raises SSHException:
+ if the request was rejected or the channel was closed
"""
if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open')
@@ -247,19 +239,15 @@ class Channel (object):
def resize_pty(self, width=80, height=24, width_pixels=0, height_pixels=0):
"""
Resize the pseudo-terminal. This can be used to change the width and
- height of the terminal emulation created in a previous L{get_pty} call.
-
- @param width: new width (in characters) of the terminal screen
- @type width: int
- @param height: new height (in characters) of the terminal screen
- @type height: int
- @param width_pixels: new width (in pixels) of the terminal screen
- @type width_pixels: int
- @param height_pixels: new height (in pixels) of the terminal screen
- @type height_pixels: int
-
- @raise SSHException: if the request was rejected or the channel was
- closed
+ height of the terminal emulation created in a previous `get_pty` call.
+
+ :param int width: new width (in characters) of the terminal screen
+ :param int height: new height (in characters) of the terminal screen
+ :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
"""
if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open')
@@ -278,27 +266,27 @@ class Channel (object):
"""
Return true if the remote process has exited and returned an exit
status. You may use this to poll the process status if you don't
- want to block in L{recv_exit_status}. Note that the server may not
+ want to block in `recv_exit_status`. Note that the server may not
return an exit status in some cases (like bad servers).
- @return: True if L{recv_exit_status} will return immediately
- @rtype: bool
- @since: 1.7.3
+ :return:
+ ``True`` if `recv_exit_status` will return immediately, else ``False``.
+
+ .. versionadded:: 1.7.3
"""
return self.closed or self.status_event.isSet()
def recv_exit_status(self):
"""
Return the exit status from the process on the server. This is
- mostly useful for retrieving the reults of an L{exec_command}.
+ mostly useful for retrieving the reults of an `exec_command`.
If the command hasn't finished yet, this method will wait until
it does, or until the channel is closed. If no exit status is
provided by the server, -1 is returned.
- @return: the exit code of the process on the server.
- @rtype: int
+ :return: the exit code (as an `int`) of the process on the server.
- @since: 1.2
+ .. versionadded:: 1.2
"""
self.status_event.wait()
assert self.status_event.isSet()
@@ -311,10 +299,9 @@ class Channel (object):
get some sort of status code back from an executed command after
it completes.
- @param status: the exit code of the process
- @type status: int
+ :param int status: the exit code of the process
- @since: 1.2
+ .. versionadded:: 1.2
"""
# in many cases, the channel will not still be open here.
# that's fine.
@@ -347,25 +334,24 @@ class Channel (object):
If a handler is passed in, the handler is called from another thread
whenever a new x11 connection arrives. The default handler queues up
incoming x11 connections, which may be retrieved using
- L{Transport.accept}. The handler's calling signature is::
+ `.Transport.accept`. The handler's calling signature is::
handler(channel: Channel, (address: str, port: int))
- @param screen_number: the x11 screen number (0, 10, etc)
- @type screen_number: int
- @param auth_protocol: the name of the X11 authentication method used;
- if none is given, C{"MIT-MAGIC-COOKIE-1"} is used
- @type auth_protocol: str
- @param auth_cookie: hexadecimal string containing the x11 auth cookie;
- if none is given, a secure random 128-bit value is generated
- @type auth_cookie: str
- @param single_connection: if True, only a single x11 connection will be
- forwarded (by default, any number of x11 connections can arrive
- over this session)
- @type single_connection: bool
- @param handler: an optional handler to use for incoming X11 connections
- @type handler: function
- @return: the auth_cookie used
+ :param int screen_number: the x11 screen number (0, 10, etc)
+ :param str auth_protocol:
+ the name of the X11 authentication method used; if none is given,
+ ``"MIT-MAGIC-COOKIE-1"`` is used
+ :param str auth_cookie:
+ hexadecimal string containing the x11 auth cookie; if none is
+ given, a secure random 128-bit value is generated
+ :param bool single_connection:
+ 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
+ :return: the auth_cookie used
"""
if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open')
@@ -392,15 +378,14 @@ class Channel (object):
def request_forward_agent(self, handler):
"""
Request for a forward SSH Agent on this channel.
- This is only valid for an ssh-agent from openssh !!!
+ This is only valid for an ssh-agent from OpenSSH !!!
- @param handler: a required handler to use for incoming SSH Agent connections
- @type handler: function
+ :param function handler:
+ a required handler to use for incoming SSH Agent connections
- @return: if we are ok or not (at that time we always return ok)
- @rtype: boolean
+ :return: True if we are ok, else False (at that time we always return ok)
- @raise: SSHException in case of channel problem.
+ :raises: SSHException in case of channel problem.
"""
if self.closed or self.eof_received or self.eof_sent or not self.active:
raise SSHException('Channel is not open')
@@ -416,10 +401,7 @@ class Channel (object):
def get_transport(self):
"""
- Return the L{Transport} associated with this channel.
-
- @return: the L{Transport} that was used to create this channel.
- @rtype: L{Transport}
+ Return the `.Transport` associated with this channel.
"""
return self.transport
@@ -427,55 +409,49 @@ class Channel (object):
"""
Set a name for this channel. Currently it's only used to set the name
of the channel in logfile entries. The name can be fetched with the
- L{get_name} method.
+ `get_name` method.
- @param name: new channel name
- @type name: str
+ :param str name: new channel name
"""
self._name = name
def get_name(self):
"""
- Get the name of this channel that was previously set by L{set_name}.
-
- @return: the name of this channel.
- @rtype: str
+ Get the name of this channel that was previously set by `set_name`.
"""
return self._name
def get_id(self):
"""
- Return the ID # for this channel. The channel ID is unique across
- a L{Transport} and usually a small number. It's also the number
- passed to L{ServerInterface.check_channel_request} when determining
- whether to accept a channel request in server mode.
-
- @return: the ID of this channel.
- @rtype: int
+ Return the `int` ID # for this channel.
+
+ The channel ID is unique across a `.Transport` and usually a small
+ number. It's also the number passed to
+ `.ServerInterface.check_channel_request` when determining whether to
+ accept a channel request in server mode.
"""
return self.chanid
def set_combine_stderr(self, combine):
"""
Set whether stderr should be combined into stdout on this channel.
- The default is C{False}, but in some cases it may be convenient to
+ The default is ``False``, but in some cases it may be convenient to
have both streams combined.
- If this is C{False}, and L{exec_command} is called (or C{invoke_shell}
- with no pty), output to stderr will not show up through the L{recv}
- and L{recv_ready} calls. You will have to use L{recv_stderr} and
- L{recv_stderr_ready} to get stderr output.
+ If this is ``False``, and `exec_command` is called (or ``invoke_shell``
+ with no pty), output to stderr will not show up through the `recv`
+ and `recv_ready` calls. You will have to use `recv_stderr` and
+ `recv_stderr_ready` to get stderr output.
- If this is C{True}, data will never show up via L{recv_stderr} or
- L{recv_stderr_ready}.
+ If this is ``True``, data will never show up via `recv_stderr` or
+ `recv_stderr_ready`.
- @param combine: C{True} if stderr output should be combined into
- stdout on this channel.
- @type combine: bool
- @return: previous setting.
- @rtype: bool
+ :param bool combine:
+ ``True`` if stderr output should be combined into stdout on this
+ channel.
+ :return: the previous setting (a `bool`).
- @since: 1.1
+ .. versionadded:: 1.1
"""
data = bytes()
self.lock.acquire()
@@ -491,57 +467,51 @@ class Channel (object):
self._feed(data)
return old
-
### socket API
-
def settimeout(self, timeout):
"""
- Set a timeout on blocking read/write operations. The C{timeout}
- argument can be a nonnegative float expressing seconds, or C{None}. If
+ Set a timeout on blocking read/write operations. The ``timeout``
+ argument can be a nonnegative float expressing seconds, or ``None``. If
a float is given, subsequent channel read/write operations will raise
a timeout exception if the timeout period value has elapsed before the
- operation has completed. Setting a timeout of C{None} disables
+ operation has completed. Setting a timeout of ``None`` disables
timeouts on socket operations.
- C{chan.settimeout(0.0)} is equivalent to C{chan.setblocking(0)};
- C{chan.settimeout(None)} is equivalent to C{chan.setblocking(1)}.
+ ``chan.settimeout(0.0)`` is equivalent to ``chan.setblocking(0)``;
+ ``chan.settimeout(None)`` is equivalent to ``chan.setblocking(1)``.
- @param timeout: seconds to wait for a pending read/write operation
- before raising C{socket.timeout}, or C{None} for no timeout.
- @type timeout: float
+ :param float timeout:
+ seconds to wait for a pending read/write operation before raising
+ ``socket.timeout``, or ``None`` for no timeout.
"""
self.timeout = timeout
def gettimeout(self):
"""
Returns the timeout in seconds (as a float) associated with socket
- operations, or C{None} if no timeout is set. This reflects the last
- call to L{setblocking} or L{settimeout}.
-
- @return: timeout in seconds, or C{None}.
- @rtype: float
+ operations, or ``None`` if no timeout is set. This reflects the last
+ call to `setblocking` or `settimeout`.
"""
return self.timeout
def setblocking(self, blocking):
"""
- Set blocking or non-blocking mode of the channel: if C{blocking} is 0,
+ Set blocking or non-blocking mode of the channel: if ``blocking`` is 0,
the channel is set to non-blocking mode; otherwise it's set to blocking
mode. Initially all channels are in blocking mode.
- In non-blocking mode, if a L{recv} call doesn't find any data, or if a
- L{send} call can't immediately dispose of the data, an error exception
+ In non-blocking mode, if a `recv` call doesn't find any data, or if a
+ `send` call can't immediately dispose of the data, an error exception
is raised. In blocking mode, the calls block until they can proceed. An
- EOF condition is considered "immediate data" for L{recv}, so if the
+ EOF condition is considered "immediate data" for `recv`, so if the
channel is closed in the read direction, it will never block.
- C{chan.setblocking(0)} is equivalent to C{chan.settimeout(0)};
- C{chan.setblocking(1)} is equivalent to C{chan.settimeout(None)}.
+ ``chan.setblocking(0)`` is equivalent to ``chan.settimeout(0)``;
+ ``chan.setblocking(1)`` is equivalent to ``chan.settimeout(None)``.
- @param blocking: 0 to set non-blocking mode; non-0 to set blocking
- mode.
- @type blocking: int
+ :param int blocking:
+ 0 to set non-blocking mode; non-0 to set blocking mode.
"""
if blocking:
self.settimeout(None)
@@ -551,12 +521,10 @@ class Channel (object):
def getpeername(self):
"""
Return the address of the remote side of this Channel, if possible.
- This is just a wrapper around C{'getpeername'} on the Transport, used
- to provide enough of a socket-like interface to allow asyncore to work.
- (asyncore likes to call C{'getpeername'}.)
- @return: the address if the remote host, if known
- @rtype: tuple(str, int)
+ This simply wraps `.Transport.getpeername`, used to provide enough of a
+ socket-like interface to allow asyncore to work. (asyncore likes to
+ call ``'getpeername'``.)
"""
return self.transport.getpeername()
@@ -564,7 +532,7 @@ class Channel (object):
"""
Close the channel. All future read/write operations on the channel
will fail. The remote end will receive no more data (after queued data
- is flushed). Channels are automatically closed when their L{Transport}
+ is flushed). Channels are automatically closed when their `.Transport`
is closed or when they are garbage collected.
"""
self.lock.acquire()
@@ -589,12 +557,12 @@ class Channel (object):
def recv_ready(self):
"""
Returns true if data is buffered and ready to be read from this
- channel. A C{False} result does not mean that the channel has closed;
+ channel. A ``False`` result does not mean that the channel has closed;
it means you may need to wait before more data arrives.
- @return: C{True} if a L{recv} call on this channel would immediately
- return at least one byte; C{False} otherwise.
- @rtype: boolean
+ :return:
+ ``True`` if a `recv` call on this channel would immediately return
+ at least one byte; ``False`` otherwise.
"""
return self.in_buffer.read_ready()
@@ -602,16 +570,14 @@ class Channel (object):
"""
Receive data from the channel. The return value is a string
representing the data received. The maximum amount of data to be
- received at once is specified by C{nbytes}. If a string of length zero
+ received at once is specified by ``nbytes``. If a string of length zero
is returned, the channel stream has closed.
- @param nbytes: maximum number of bytes to read.
- @type nbytes: int
- @return: data.
- @rtype: str
+ :param int nbytes: maximum number of bytes to read.
+ :return: received data, as a `str`
- @raise socket.timeout: if no data is ready before the timeout set by
- L{settimeout}.
+ :raises socket.timeout:
+ if no data is ready before the timeout set by `settimeout`.
"""
try:
out = self.in_buffer.read(nbytes, self.timeout)
@@ -632,36 +598,34 @@ class Channel (object):
def recv_stderr_ready(self):
"""
Returns true if data is buffered and ready to be read from this
- channel's stderr stream. Only channels using L{exec_command} or
- L{invoke_shell} without a pty will ever have data on the stderr
+ channel's stderr stream. Only channels using `exec_command` or
+ `invoke_shell` without a pty will ever have data on the stderr
stream.
- @return: C{True} if a L{recv_stderr} call on this channel would
- immediately return at least one byte; C{False} otherwise.
- @rtype: boolean
+ :return:
+ ``True`` if a `recv_stderr` call on this channel would immediately
+ return at least one byte; ``False`` otherwise.
- @since: 1.1
+ .. versionadded:: 1.1
"""
return self.in_stderr_buffer.read_ready()
def recv_stderr(self, nbytes):
"""
Receive data from the channel's stderr stream. Only channels using
- L{exec_command} or L{invoke_shell} without a pty will ever have data
+ `exec_command` or `invoke_shell` without a pty will ever have data
on the stderr stream. The return value is a string representing the
data received. The maximum amount of data to be received at once is
- specified by C{nbytes}. If a string of length zero is returned, the
+ specified by ``nbytes``. If a string of length zero is returned, the
channel stream has closed.
- @param nbytes: maximum number of bytes to read.
- @type nbytes: int
- @return: data.
- @rtype: str
+ :param int nbytes: maximum number of bytes to read.
+ :return: received data as a `str`
- @raise socket.timeout: if no data is ready before the timeout set by
- L{settimeout}.
+ :raises socket.timeout: if no data is ready before the timeout set by
+ `settimeout`.
- @since: 1.1
+ .. versionadded:: 1.1
"""
try:
out = self.in_stderr_buffer.read(nbytes, self.timeout)
@@ -685,12 +649,12 @@ class Channel (object):
This means the channel is either closed (so any write attempt would
return immediately) or there is at least one byte of space in the
outbound buffer. If there is at least one byte of space in the
- outbound buffer, a L{send} call will succeed immediately and return
+ outbound buffer, a `send` call will succeed immediately and return
the number of bytes actually written.
- @return: C{True} if a L{send} call on this channel would immediately
- succeed or fail
- @rtype: boolean
+ :return:
+ ``True`` if a `send` call on this channel would immediately succeed
+ or fail
"""
self.lock.acquire()
try:
@@ -708,13 +672,11 @@ class Channel (object):
transmitted, the application needs to attempt delivery of the remaining
data.
- @param s: data to send
- @type s: str
- @return: number of bytes actually sent
- @rtype: int
+ :param str s: data to send
+ :return: number of bytes actually sent, as an `int`
- @raise socket.timeout: if no data could be sent before the timeout set
- by L{settimeout}.
+ :raises socket.timeout: if no data could be sent before the timeout set
+ by `settimeout`.
"""
size = len(s)
self.lock.acquire()
@@ -743,15 +705,13 @@ class Channel (object):
data has been sent: if only some of the data was transmitted, the
application needs to attempt delivery of the remaining data.
- @param s: data to send.
- @type s: str
- @return: number of bytes actually sent.
- @rtype: int
+ :param str s: data to send.
+ :return: number of bytes actually sent, as an `int`.
- @raise socket.timeout: if no data could be sent before the timeout set
- by L{settimeout}.
+ :raises socket.timeout:
+ if no data could be sent before the timeout set by `settimeout`.
- @since: 1.1
+ .. versionadded:: 1.1
"""
size = len(s)
self.lock.acquire()
@@ -775,20 +735,20 @@ class Channel (object):
def sendall(self, s):
"""
Send data to the channel, without allowing partial results. Unlike
- L{send}, this method continues to send data from the given string until
+ `send`, this method continues to send data from the given string until
either all data has been sent or an error occurs. Nothing is returned.
- @param s: data to send.
- @type s: str
+ :param str s: data to send.
- @raise socket.timeout: if sending stalled for longer than the timeout
- set by L{settimeout}.
- @raise socket.error: if an error occured before the entire string was
- sent.
+ :raises socket.timeout:
+ if sending stalled for longer than the timeout set by `settimeout`.
+ :raises socket.error:
+ if an error occured before the entire string was sent.
- @note: If the channel is closed while only part of the data hase been
+ .. note::
+ If the channel is closed while only part of the data hase been
sent, there is no way to determine how much data (if any) was sent.
- This is irritating, but identically follows python's API.
+ This is irritating, but identically follows Python's API.
"""
while s:
if self.closed:
@@ -801,19 +761,18 @@ class Channel (object):
def sendall_stderr(self, s):
"""
Send data to the channel's "stderr" stream, without allowing partial
- results. Unlike L{send_stderr}, this method continues to send data
+ results. Unlike `send_stderr`, this method continues to send data
from the given string until all data has been sent or an error occurs.
Nothing is returned.
- @param s: data to send to the client as "stderr" output.
- @type s: str
+ :param str s: data to send to the client as "stderr" output.
- @raise socket.timeout: if sending stalled for longer than the timeout
- set by L{settimeout}.
- @raise socket.error: if an error occured before the entire string was
- sent.
+ :raises socket.timeout:
+ if sending stalled for longer than the timeout set by `settimeout`.
+ :raises socket.error:
+ if an error occured before the entire string was sent.
- @since: 1.1
+ .. versionadded:: 1.1
"""
while s:
if self.closed:
@@ -825,49 +784,46 @@ class Channel (object):
def makefile(self, *params):
"""
Return a file-like object associated with this channel. The optional
- C{mode} and C{bufsize} arguments are interpreted the same way as by
- the built-in C{file()} function in python.
+ ``mode`` and ``bufsize`` arguments are interpreted the same way as by
+ the built-in ``file()`` function in Python.
- @return: object which can be used for python file I/O.
- @rtype: L{ChannelFile}
+ :return: `.ChannelFile` object which can be used for Python file I/O.
"""
return ChannelFile(*([self] + list(params)))
def makefile_stderr(self, *params):
"""
Return a file-like object associated with this channel's stderr
- stream. Only channels using L{exec_command} or L{invoke_shell}
+ stream. Only channels using `exec_command` or `invoke_shell`
without a pty will ever have data on the stderr stream.
- The optional C{mode} and C{bufsize} arguments are interpreted the
- same way as by the built-in C{file()} function in python. For a
+ The optional ``mode`` and ``bufsize`` arguments are interpreted the
+ same way as by the built-in ``file()`` function in Python. For a
client, it only makes sense to open this file for reading. For a
server, it only makes sense to open this file for writing.
- @return: object which can be used for python file I/O.
- @rtype: L{ChannelFile}
+ :return: `.ChannelFile` object which can be used for Python file I/O.
- @since: 1.1
+ .. versionadded:: 1.1
"""
return ChannelStderrFile(*([self] + list(params)))
def fileno(self):
"""
Returns an OS-level file descriptor which can be used for polling, but
- but I{not} for reading or writing. This is primaily to allow python's
- C{select} module to work.
+ but not for reading or writing. This is primaily to allow Python's
+ ``select`` module to work.
- The first time C{fileno} is called on a channel, a pipe is created to
+ The first time ``fileno`` is called on a channel, a pipe is created to
simulate real OS-level file descriptor (FD) behavior. Because of this,
two OS-level FDs are created, which will use up FDs faster than normal.
(You won't notice this effect unless you have hundreds of channels
open at the same time.)
- @return: an OS-level file descriptor
- @rtype: int
+ :return: an OS-level file descriptor (`int`)
- @warning: This method causes channel reads to be slightly less
- efficient.
+ .. warning::
+ This method causes channel reads to be slightly less efficient.
"""
self.lock.acquire()
try:
@@ -884,14 +840,14 @@ class Channel (object):
def shutdown(self, how):
"""
- Shut down one or both halves of the connection. If C{how} is 0,
- further receives are disallowed. If C{how} is 1, further sends
- are disallowed. If C{how} is 2, further sends and receives are
+ Shut down one or both halves of the connection. If ``how`` is 0,
+ further receives are disallowed. If ``how`` is 1, further sends
+ are disallowed. If ``how`` is 2, further sends and receives are
disallowed. This closes the stream in one or both directions.
- @param how: 0 (stop receiving), 1 (stop sending), or 2 (stop
- receiving and sending).
- @type how: int
+ :param int how:
+ 0 (stop receiving), 1 (stop sending), or 2 (stop receiving and
+ sending).
"""
if (how == 0) or (how == 2):
# feign "read" shutdown
@@ -910,10 +866,10 @@ class Channel (object):
Shutdown the receiving side of this socket, closing the stream in
the incoming direction. After this call, future reads on this
channel will fail instantly. This is a convenience method, equivalent
- to C{shutdown(0)}, for people who don't make it a habit to
+ to ``shutdown(0)``, for people who don't make it a habit to
memorize unix constants from the 1970s.
- @since: 1.2
+ .. versionadded:: 1.2
"""
self.shutdown(0)
@@ -922,17 +878,15 @@ class Channel (object):
Shutdown the sending side of this socket, closing the stream in
the outgoing direction. After this call, future writes on this
channel will fail instantly. This is a convenience method, equivalent
- to C{shutdown(1)}, for people who don't make it a habit to
+ to ``shutdown(1)``, for people who don't make it a habit to
memorize unix constants from the 1970s.
- @since: 1.2
+ .. versionadded:: 1.2
"""
self.shutdown(1)
-
### calls from Transport
-
def _set_transport(self, transport):
self.transport = transport
self.logger = util.get_logger(self.transport.get_log_channel())
@@ -1107,10 +1061,8 @@ class Channel (object):
if m is not None:
self.transport._send_user_message(m)
-
### internals...
-
def _log(self, level, msg, *args):
self.logger.log(level, "[chan " + self._name + "] " + msg, *args)
@@ -1196,7 +1148,7 @@ class Channel (object):
def _wait_for_send_window(self, size):
"""
(You are already holding the lock.)
- Wait for the send window to open up, and allocate up to C{size} bytes
+ Wait for the send window to open up, and allocate up to ``size`` bytes
for transmission. If no space opens up before the timeout, a timeout
exception is raised. Returns the number of bytes available to send
(may be less than requested).
@@ -1215,7 +1167,7 @@ class Channel (object):
return 0
then = time.time()
self.out_buffer_cv.wait(timeout)
- if timeout != None:
+ if timeout is not None:
timeout -= time.time() - then
if timeout <= 0.0:
raise socket.timeout()
@@ -1234,16 +1186,18 @@ class Channel (object):
class ChannelFile (BufferedFile):
"""
- A file-like wrapper around L{Channel}. A ChannelFile is created by calling
- L{Channel.makefile}.
-
- @bug: To correctly emulate the file object created from a socket's
- C{makefile} method, a L{Channel} and its C{ChannelFile} should be able
- to be closed or garbage-collected independently. Currently, closing
- the C{ChannelFile} does nothing but flush the buffer.
+ A file-like wrapper around `.Channel`. A ChannelFile is created by calling
+ `Channel.makefile`.
+
+ .. warning::
+ To correctly emulate the file object created from a socket's `makefile
+ <python:socket.socket.makefile>` method, a `.Channel` and its
+ `.ChannelFile` should be able to be closed or garbage-collected
+ independently. Currently, closing the `ChannelFile` does nothing but
+ flush the buffer.
"""
- def __init__(self, channel, mode = 'r', bufsize = -1):
+ def __init__(self, channel, mode='r', bufsize=-1):
self.channel = channel
BufferedFile.__init__(self)
self._set_mode(mode, bufsize)
@@ -1251,8 +1205,6 @@ class ChannelFile (BufferedFile):
def __repr__(self):
"""
Returns a string representation of this object, for debugging.
-
- @rtype: str
"""
return '<paramiko.ChannelFile from ' + repr(self.channel) + '>'
@@ -1265,7 +1217,7 @@ class ChannelFile (BufferedFile):
class ChannelStderrFile (ChannelFile):
- def __init__(self, channel, mode = 'r', bufsize = -1):
+ def __init__(self, channel, mode='r', bufsize=-1):
ChannelFile.__init__(self, channel, mode, bufsize)
def _read(self, size):
diff --git a/paramiko/client.py b/paramiko/client.py
index f4149510..539299ea 100644
--- a/paramiko/client.py
+++ b/paramiko/client.py
@@ -17,7 +17,7 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-L{SSHClient}.
+SSH client & key policies
"""
from binascii import hexlify
@@ -27,10 +27,11 @@ import socket
import warnings
from paramiko.agent import Agent
-from paramiko.common import *
+from paramiko.common import DEBUG
from paramiko.config import SSH_PORT
from paramiko.dsskey import DSSKey
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
@@ -38,67 +39,10 @@ from paramiko.transport import Transport
from paramiko.util import retry_on_signal
-class MissingHostKeyPolicy (object):
- """
- Interface for defining the policy that L{SSHClient} should use when the
- SSH server's hostname is not in either the system host keys or the
- application's keys. Pre-made classes implement policies for automatically
- adding the key to the application's L{HostKeys} object (L{AutoAddPolicy}),
- and for automatically rejecting the key (L{RejectPolicy}).
-
- This function may be used to ask the user to verify the key, for example.
- """
-
- def missing_host_key(self, client, hostname, key):
- """
- Called when an L{SSHClient} receives a server key for a server that
- isn't in either the system or local L{HostKeys} object. To accept
- the key, simply return. To reject, raised an exception (which will
- be passed to the calling application).
- """
- pass
-
-
-class AutoAddPolicy (MissingHostKeyPolicy):
- """
- Policy for automatically adding the hostname and new host key to the
- local L{HostKeys} object, and saving it. This is used by L{SSHClient}.
- """
-
- def missing_host_key(self, client, hostname, key):
- client._host_keys.add(hostname, key.get_name(), key)
- if client._host_keys_filename is not None:
- client.save_host_keys(client._host_keys_filename)
- client._log(DEBUG, 'Adding %s host key for %s: %s' %
- (key.get_name(), hostname, hexlify(key.get_fingerprint())))
-
-
-class RejectPolicy (MissingHostKeyPolicy):
- """
- Policy for automatically rejecting the unknown hostname & key. This is
- used by L{SSHClient}.
- """
-
- def missing_host_key(self, client, hostname, key):
- client._log(DEBUG, 'Rejecting %s host key for %s: %s' %
- (key.get_name(), hostname, hexlify(key.get_fingerprint())))
- raise SSHException('Server %r not found in known_hosts' % hostname)
-
-
-class WarningPolicy (MissingHostKeyPolicy):
- """
- Policy for logging a python-style warning for an unknown host key, but
- accepting it. This is used by L{SSHClient}.
- """
- def missing_host_key(self, client, hostname, key):
- warnings.warn('Unknown %s host key for %s: %s' %
- (key.get_name(), hostname, hexlify(key.get_fingerprint())))
-
-
class SSHClient (object):
"""
A high-level representation of a session with an SSH server. This class
- wraps L{Transport}, L{Channel}, and L{SFTPClient} to take care of most
+ wraps `.Transport`, `.Channel`, and `.SFTPClient` to take care of most
aspects of authenticating and opening channels. A typical use case is::
client = SSHClient()
@@ -110,7 +54,7 @@ class SSHClient (object):
checking. The default mechanism is to try to use local key files or an
SSH agent (if one is running).
- @since: 1.6
+ .. versionadded:: 1.6
"""
def __init__(self):
@@ -128,22 +72,21 @@ class SSHClient (object):
def load_system_host_keys(self, filename=None):
"""
Load host keys from a system (read-only) file. Host keys read with
- this method will not be saved back by L{save_host_keys}.
+ this method will not be saved back by `save_host_keys`.
This method can be called multiple times. Each new set of host keys
will be merged with the existing set (new replacing old if there are
conflicts).
- If C{filename} is left as C{None}, an attempt will be made to read
+ If ``filename`` is left as ``None``, an attempt will be made to read
keys from the user's local "known hosts" file, as used by OpenSSH,
and no exception will be raised if the file can't be read. This is
probably only useful on posix.
- @param filename: the filename to read, or C{None}
- @type filename: str
+ :param str filename: the filename to read, or ``None``
- @raise IOError: if a filename was provided and the file could not be
- read
+ :raises IOError:
+ if a filename was provided and the file could not be read
"""
if filename is None:
# try the user's .ssh key file, and mask exceptions
@@ -158,19 +101,18 @@ class SSHClient (object):
def load_host_keys(self, filename):
"""
Load host keys from a local host-key file. Host keys read with this
- method will be checked I{after} keys loaded via L{load_system_host_keys},
- but will be saved back by L{save_host_keys} (so they can be modified).
- The missing host key policy L{AutoAddPolicy} adds keys to this set and
+ method will be checked after keys loaded via `load_system_host_keys`,
+ but will be saved back by `save_host_keys` (so they can be modified).
+ The missing host key policy `.AutoAddPolicy` adds keys to this set and
saves them, when connecting to a previously-unknown server.
This method can be called multiple times. Each new set of host keys
will be merged with the existing set (new replacing old if there are
conflicts). When automatically saving, the last hostname is used.
- @param filename: the filename to read
- @type filename: str
+ :param str filename: the filename to read
- @raise 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)
@@ -178,13 +120,12 @@ class SSHClient (object):
def save_host_keys(self, filename):
"""
Save the host keys back to a file. Only the host keys loaded with
- L{load_host_keys} (plus any added directly) will be saved -- not any
- host keys loaded with L{load_system_host_keys}.
+ `load_host_keys` (plus any added directly) will be saved -- not any
+ host keys loaded with `load_system_host_keys`.
- @param filename: the filename to save to
- @type filename: str
+ :param str filename: the filename to save to
- @raise 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
@@ -199,34 +140,32 @@ class SSHClient (object):
def get_host_keys(self):
"""
- Get the local L{HostKeys} object. This can be used to examine the
+ Get the local `.HostKeys` object. This can be used to examine the
local host keys or change them.
- @return: the local host keys
- @rtype: L{HostKeys}
+ :return: the local host keys as a `.HostKeys` object.
"""
return self._host_keys
def set_log_channel(self, name):
"""
- Set the channel for logging. The default is C{"paramiko.transport"}
+ Set the channel for logging. The default is ``"paramiko.transport"``
but it can be set to anything you want.
- @param name: new channel name for logging
- @type name: str
+ :param str name: new channel name for logging
"""
self._log_channel = name
def set_missing_host_key_policy(self, policy):
"""
Set the policy to use when connecting to a server that doesn't have a
- host key in either the system or local L{HostKeys} objects. The
- default policy is to reject all unknown servers (using L{RejectPolicy}).
- You may substitute L{AutoAddPolicy} or write your own policy class.
+ host key in either the system or local `.HostKeys` objects. The
+ default policy is to reject all unknown servers (using `.RejectPolicy`).
+ You may substitute `.AutoAddPolicy` or write your own policy class.
- @param policy: the policy to use when receiving a host key from a
+ :param .MissingHostKeyPolicy policy:
+ the policy to use when receiving a host key from a
previously-unknown server
- @type policy: L{MissingHostKeyPolicy}
"""
self._policy = policy
@@ -236,64 +175,53 @@ class SSHClient (object):
gss_deleg_creds=True, gss_host=None):
"""
Connect to an SSH server and authenticate to it. The server's host key
- is checked against the system host keys (see L{load_system_host_keys})
- and any local host keys (L{load_host_keys}). If the server's hostname
+ is checked against the system host keys (see `load_system_host_keys`)
+ and any local host keys (`load_host_keys`). If the server's hostname
is not found in either set of host keys, the missing host key policy
- is used (see L{set_missing_host_key_policy}). The default policy is
- to reject the key and raise an L{SSHException}.
+ is used (see `set_missing_host_key_policy`). The default policy is
+ to reject the key and raise an `.SSHException`.
Authentication is attempted in the following order of priority:
- - The C{pkey} or C{key_filename} passed in (if any)
+ - The ``pkey`` or ``key_filename`` passed in (if any)
- Any key we can find through an SSH agent
- - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/}
+ - Any "id_rsa" or "id_dsa" key discoverable in ``~/.ssh/``
- Plain username/password auth, if a password was given
If a private key requires a password to unlock it, and a password is
passed in, that password will be used to attempt to unlock the key.
- @param hostname: the server to connect to
- @type hostname: str
- @param port: the server port to connect to
- @type port: int
- @param username: the username to authenticate as (defaults to the
- current local username)
- @type username: str
- @param password: a password to use for authentication or for unlocking
- a private key
- @type password: str
- @param pkey: an optional private key to use for authentication
- @type pkey: L{PKey}
- @param key_filename: the filename, or list of filenames, of optional
- private key(s) to try for authentication
- @type key_filename: str or list(str)
- @param timeout: an optional timeout (in seconds) for the TCP connect
- @type timeout: float
- @param allow_agent: set to False to disable connecting to the SSH agent
- @type allow_agent: bool
- @param look_for_keys: set to False to disable searching for discoverable
- private key files in C{~/.ssh/}
- @type look_for_keys: bool
- @param compress: set to True to turn on compression
- @type compress: bool
- @param sock: an open socket or socket-like object (such as a
- L{Channel}) to use for communication to the target host
- @type sock: socket
- @param gss_auth: C{True} if you want to use GSS-API authentication
- @type gss_auth: Boolean
- @param gss_kex: Perform GSS-API Key Exchange and user authentication
- @type gss_kex: Boolean
- @param gss_deleg_creds: Delegate GSS-API client credentials or not
- @type gss_deleg_creds: Boolean
- @param gss_host: The targets name in the kerberos database. default: hostname
- @type gss_host: String
-
- @raise BadHostKeyException: if the server's host key could not be
+ :param str hostname: the server to connect to
+ :param int port: the server port to connect to
+ :param str username:
+ the username to authenticate as (defaults to the current local
+ username)
+ :param str password:
+ a password to use for authentication or for unlocking a private key
+ :param .PKey pkey: an optional private key to use for authentication
+ :param str key_filename:
+ the filename, or list of filenames, of optional private key(s) to
+ try for authentication
+ :param float timeout: an optional timeout (in seconds) for the TCP connect
+ :param bool allow_agent: set to False to disable connecting to the SSH agent
+ :param bool look_for_keys:
+ set to False to disable searching for discoverable private key
+ files in ``~/.ssh/``
+ :param bool compress: set to True to turn on compression
+ :param socket sock:
+ an open socket or socket-like object (such as a `.Channel`) to use
+ for communication to the target host
+ :param bool gss_auth: ``True`` if you want to use GSS-API authentication
+ :param bool gss_kex: Perform GSS-API Key Exchange and user authentication
+ :param bool gss_deleg_creds: Delegate GSS-API client credentials or not
+ :param str gss_host: The targets name in the kerberos database. default: hostname
+
+ :raises BadHostKeyException: if the server's host key could not be
verified
- @raise AuthenticationException: if authentication failed
- @raise 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
- @raise socket.error: if a socket error occurred while connecting
+ :raises socket.error: if a socket error occurred while connecting
"""
if not sock:
for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
@@ -360,7 +288,7 @@ class SSHClient (object):
if key_filename is None:
key_filenames = []
elif isinstance(key_filename, string_types):
- key_filenames = [ key_filename ]
+ key_filenames = [key_filename]
else:
key_filenames = key_filename
if gss_host is None:
@@ -370,37 +298,38 @@ class SSHClient (object):
def close(self):
"""
- Close this SSHClient and its underlying L{Transport}.
+ Close this SSHClient and its underlying `.Transport`.
"""
if self._transport is None:
return
self._transport.close()
self._transport = None
- if self._agent != None:
+ if self._agent is not None:
self._agent.close()
self._agent = None
def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False):
"""
- Execute a command on the SSH server. A new L{Channel} is opened and
+ Execute a command on the SSH server. A new `.Channel` is opened and
the requested command is executed. The command's input and output
- streams are returned as python C{file}-like objects representing
+ streams are returned as Python ``file``-like objects representing
stdin, stdout, and stderr.
- @param command: the command to execute
- @type command: str
- @param bufsize: interpreted the same way as by the built-in C{file()} function in python
- @type bufsize: int
- @param timeout: set command's channel timeout. See L{Channel.settimeout}.settimeout
- @type timeout: int
- @return: the stdin, stdout, and stderr of the executing command
- @rtype: tuple(L{ChannelFile}, L{ChannelFile}, L{ChannelFile})
+ :param str command: the command to execute
+ :param int bufsize:
+ 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
+ :return:
+ the stdin, stdout, and stderr of the executing command, as a
+ 3-tuple
- @raise SSHException: if the server fails to execute the command
+ :raises SSHException: if the server fails to execute the command
"""
chan = self._transport.open_session()
- if(get_pty):
+ if get_pty:
chan.get_pty()
chan.settimeout(timeout)
chan.exec_command(command)
@@ -412,24 +341,19 @@ class SSHClient (object):
def invoke_shell(self, term='vt100', width=80, height=24, width_pixels=0,
height_pixels=0):
"""
- Start an interactive shell session on the SSH server. A new L{Channel}
+ Start an interactive shell session on the SSH server. A new `.Channel`
is opened and connected to a pseudo-terminal using the requested
terminal type and size.
- @param term: the terminal type to emulate (for example, C{"vt100"})
- @type term: str
- @param width: the width (in characters) of the terminal window
- @type width: int
- @param height: the height (in characters) of the terminal window
- @type height: int
- @param width_pixels: the width (in pixels) of the terminal window
- @type width_pixels: int
- @param height_pixels: the height (in pixels) of the terminal window
- @type height_pixels: int
- @return: a new channel connected to the remote shell
- @rtype: L{Channel}
-
- @raise SSHException: if the server fails to invoke a shell
+ :param str term:
+ the terminal type to emulate (for example, ``"vt100"``)
+ :param int width: the width (in characters) of the terminal window
+ :param int height: the height (in characters) of the terminal window
+ :param int width_pixels: the width (in pixels) of the terminal window
+ :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
"""
chan = self._transport.open_session()
chan.get_pty(term, width, height, width_pixels, height_pixels)
@@ -440,19 +364,17 @@ class SSHClient (object):
"""
Open an SFTP session on the SSH server.
- @return: a new SFTP session object
- @rtype: L{SFTPClient}
+ :return: a new `.SFTPClient` session object
"""
return self._transport.open_sftp_client()
def get_transport(self):
"""
- Return the underlying L{Transport} object for this SSH connection.
+ Return the underlying `.Transport` object for this SSH connection.
This can be used to perform lower-level tasks, like opening specific
kinds of channels.
- @return: the Transport for this connection
- @rtype: L{Transport}
+ :return: the `.Transport` for this connection
"""
return self._transport
@@ -523,7 +445,7 @@ class SSHClient (object):
saved_exception = e
if not two_factor and allow_agent:
- if self._agent == None:
+ if self._agent is None:
self._agent = Agent()
for key in self._agent.get_keys():
@@ -574,8 +496,8 @@ class SSHClient (object):
try:
self._transport.auth_password(username, password)
return
- except SSHException:
- saved_exception = sys.exc_info()[1]
+ except SSHException as e:
+ saved_exception = e
elif two_factor:
raise SSHException('Two-factor authentication requires a password')
@@ -587,3 +509,59 @@ class SSHClient (object):
def _log(self, level, msg):
self._transport._log(level, msg)
+
+class MissingHostKeyPolicy (object):
+ """
+ Interface for defining the policy that `.SSHClient` should use when the
+ SSH server's hostname is not in either the system host keys or the
+ application's keys. Pre-made classes implement policies for automatically
+ adding the key to the application's `.HostKeys` object (`.AutoAddPolicy`),
+ and for automatically rejecting the key (`.RejectPolicy`).
+
+ This function may be used to ask the user to verify the key, for example.
+ """
+
+ def missing_host_key(self, client, hostname, key):
+ """
+ Called when an `.SSHClient` receives a server key for a server that
+ isn't in either the system or local `.HostKeys` object. To accept
+ the key, simply return. To reject, raised an exception (which will
+ be passed to the calling application).
+ """
+ pass
+
+
+class AutoAddPolicy (MissingHostKeyPolicy):
+ """
+ Policy for automatically adding the hostname and new host key to the
+ local `.HostKeys` object, and saving it. This is used by `.SSHClient`.
+ """
+
+ def missing_host_key(self, client, hostname, key):
+ client._host_keys.add(hostname, key.get_name(), key)
+ if client._host_keys_filename is not None:
+ client.save_host_keys(client._host_keys_filename)
+ client._log(DEBUG, 'Adding %s host key for %s: %s' %
+ (key.get_name(), hostname, hexlify(key.get_fingerprint())))
+
+
+class RejectPolicy (MissingHostKeyPolicy):
+ """
+ Policy for automatically rejecting the unknown hostname & key. This is
+ used by `.SSHClient`.
+ """
+
+ def missing_host_key(self, client, hostname, key):
+ client._log(DEBUG, 'Rejecting %s host key for %s: %s' %
+ (key.get_name(), hostname, hexlify(key.get_fingerprint())))
+ raise SSHException('Server %r not found in known_hosts' % hostname)
+
+
+class WarningPolicy (MissingHostKeyPolicy):
+ """
+ Policy for logging a Python-style warning for an unknown host key, but
+ accepting it. This is used by `.SSHClient`.
+ """
+ def missing_host_key(self, client, hostname, key):
+ warnings.warn('Unknown %s host key for %s: %s' %
+ (key.get_name(), hostname, hexlify(key.get_fingerprint())))
diff --git a/paramiko/common.py b/paramiko/common.py
index de7e38d2..ed8d9a09 100644
--- a/paramiko/common.py
+++ b/paramiko/common.py
@@ -19,7 +19,8 @@
"""
Common constants and global variables.
"""
-from paramiko.py3compat import *
+import logging
+from paramiko.py3compat import byte_chr, PY2, bytes_types, string_types, b, long
MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG, MSG_SERVICE_REQUEST, \
MSG_SERVICE_ACCEPT = range(1, 7)
@@ -37,10 +38,35 @@ MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \
MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MSG_CHANNEL_REQUEST, \
MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE = range(90, 101)
-for key in list(locals().keys()):
- if key.startswith('MSG_'):
- locals()['c' + key] = byte_chr(locals()[key])
-del key
+cMSG_DISCONNECT = byte_chr(MSG_DISCONNECT)
+cMSG_IGNORE = byte_chr(MSG_IGNORE)
+cMSG_UNIMPLEMENTED = byte_chr(MSG_UNIMPLEMENTED)
+cMSG_DEBUG = byte_chr(MSG_DEBUG)
+cMSG_SERVICE_REQUEST = byte_chr(MSG_SERVICE_REQUEST)
+cMSG_SERVICE_ACCEPT = byte_chr(MSG_SERVICE_ACCEPT)
+cMSG_KEXINIT = byte_chr(MSG_KEXINIT)
+cMSG_NEWKEYS = byte_chr(MSG_NEWKEYS)
+cMSG_USERAUTH_REQUEST = byte_chr(MSG_USERAUTH_REQUEST)
+cMSG_USERAUTH_FAILURE = byte_chr(MSG_USERAUTH_FAILURE)
+cMSG_USERAUTH_SUCCESS = byte_chr(MSG_USERAUTH_SUCCESS)
+cMSG_USERAUTH_BANNER = byte_chr(MSG_USERAUTH_BANNER)
+cMSG_USERAUTH_PK_OK = byte_chr(MSG_USERAUTH_PK_OK)
+cMSG_USERAUTH_INFO_REQUEST = byte_chr(MSG_USERAUTH_INFO_REQUEST)
+cMSG_USERAUTH_INFO_RESPONSE = byte_chr(MSG_USERAUTH_INFO_RESPONSE)
+cMSG_GLOBAL_REQUEST = byte_chr(MSG_GLOBAL_REQUEST)
+cMSG_REQUEST_SUCCESS = byte_chr(MSG_REQUEST_SUCCESS)
+cMSG_REQUEST_FAILURE = byte_chr(MSG_REQUEST_FAILURE)
+cMSG_CHANNEL_OPEN = byte_chr(MSG_CHANNEL_OPEN)
+cMSG_CHANNEL_OPEN_SUCCESS = byte_chr(MSG_CHANNEL_OPEN_SUCCESS)
+cMSG_CHANNEL_OPEN_FAILURE = byte_chr(MSG_CHANNEL_OPEN_FAILURE)
+cMSG_CHANNEL_WINDOW_ADJUST = byte_chr(MSG_CHANNEL_WINDOW_ADJUST)
+cMSG_CHANNEL_DATA = byte_chr(MSG_CHANNEL_DATA)
+cMSG_CHANNEL_EXTENDED_DATA = byte_chr(MSG_CHANNEL_EXTENDED_DATA)
+cMSG_CHANNEL_EOF = byte_chr(MSG_CHANNEL_EOF)
+cMSG_CHANNEL_CLOSE = byte_chr(MSG_CHANNEL_CLOSE)
+cMSG_CHANNEL_REQUEST = byte_chr(MSG_CHANNEL_REQUEST)
+cMSG_CHANNEL_SUCCESS = byte_chr(MSG_CHANNEL_SUCCESS)
+cMSG_CHANNEL_FAILURE = byte_chr(MSG_CHANNEL_FAILURE)
# for debugging:
MSG_NAMES = {
@@ -116,24 +142,6 @@ from Crypto import Random
# keep a crypto-strong PRNG nearby
rng = Random.new()
-import sys
-if sys.version_info < (2, 3):
- try:
- import logging
- except:
- import logging22 as logging
- import select
- PY22 = True
-
- import socket
- if not hasattr(socket, 'timeout'):
- class timeout(socket.error): pass
- socket.timeout = timeout
- del timeout
-else:
- import logging
- PY22 = False
-
zero_byte = byte_chr(0)
one_byte = byte_chr(1)
four_byte = byte_chr(4)
diff --git a/paramiko/config.py b/paramiko/config.py
index 51415be1..77fa13d7 100644
--- a/paramiko/config.py
+++ b/paramiko/config.py
@@ -18,7 +18,7 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-L{SSHConfig}.
+Configuration file (aka ``ssh_config``) support.
"""
import fnmatch
@@ -30,69 +30,15 @@ SSH_PORT = 22
proxy_re = re.compile(r"^(proxycommand)\s*=*\s*(.*)", re.I)
-class LazyFqdn(object):
- """
- Returns the host's fqdn on request as string.
- """
-
- def __init__(self, config, host=None):
- self.fqdn = None
- self.config = config
- self.host = host
-
- def __str__(self):
- if self.fqdn is None:
- #
- # If the SSH config contains AddressFamily, use that when
- # determining the local host's FQDN. Using socket.getfqdn() from
- # the standard library is the most general solution, but can
- # result in noticeable delays on some platforms when IPv6 is
- # misconfigured or not available, as it calls getaddrinfo with no
- # address family specified, so both IPv4 and IPv6 are checked.
- #
-
- # Handle specific option
- fqdn = None
- address_family = self.config.get('addressfamily', 'any').lower()
- if address_family != 'any':
- try:
- family = socket.AF_INET if address_family == 'inet' \
- else socket.AF_INET6
- results = socket.getaddrinfo(
- self.host,
- None,
- family,
- socket.SOCK_DGRAM,
- socket.IPPROTO_IP,
- socket.AI_CANONNAME
- )
- for res in results:
- af, socktype, proto, canonname, sa = res
- if canonname and '.' in canonname:
- fqdn = canonname
- break
- # giaerror -> socket.getaddrinfo() can't resolve self.host
- # (which is from socket.gethostname()). Fall back to the
- # getfqdn() call below.
- except socket.gaierror:
- pass
- # Handle 'any' / unspecified
- if fqdn is None:
- fqdn = socket.getfqdn()
- # Cache
- self.fqdn = fqdn
- return self.fqdn
-
-
class SSHConfig (object):
"""
Representation of config information as stored in the format used by
- OpenSSH. Queries can be made via L{lookup}. The format is described in
- OpenSSH's C{ssh_config} man page. This class is provided primarily as a
+ OpenSSH. Queries can be made via `lookup`. The format is described in
+ OpenSSH's ``ssh_config`` man page. This class is provided primarily as a
convenience to posix users (since the OpenSSH format is a de-facto
standard on posix) but should work fine on Windows too.
- @since: 1.6
+ .. versionadded:: 1.6
"""
def __init__(self):
@@ -105,8 +51,7 @@ class SSHConfig (object):
"""
Read an OpenSSH config from the given file object.
- @param file_obj: a file-like object to read the config file from
- @type file_obj: file
+ :param file file_obj: a file-like object to read the config file from
"""
host = {"host": ['*'], "config": {}}
for line in file_obj:
@@ -152,22 +97,20 @@ class SSHConfig (object):
"""
Return a dict of config options for a given hostname.
- The host-matching rules of OpenSSH's C{ssh_config} man page are used,
+ The host-matching rules of OpenSSH's ``ssh_config`` man page are used,
which means that all configuration options from matching host
specifications are merged, with more specific hostmasks taking
- precedence. In other words, if C{"Port"} is set under C{"Host *"}
- and also C{"Host *.example.com"}, and the lookup is for
- C{"ssh.example.com"}, then the port entry for C{"Host *.example.com"}
+ precedence. In other words, if ``"Port"`` is set under ``"Host *"``
+ and also ``"Host *.example.com"``, and the lookup is for
+ ``"ssh.example.com"``, then the port entry for ``"Host *.example.com"``
will win out.
The keys in the returned dict are all normalized to lowercase (look for
- C{"port"}, not C{"Port"}. The values are processed according to the
- rules for substitution variable expansion in C{ssh_config}.
+ ``"port"``, not ``"Port"``. The values are processed according to the
+ rules for substitution variable expansion in ``ssh_config``.
- @param hostname: the hostname to lookup
- @type hostname: str
+ :param str hostname: the hostname to lookup
"""
-
matches = [config for config in self._config if
self._allowed(hostname, config['host'])]
@@ -199,13 +142,11 @@ class SSHConfig (object):
Return a dict of config options with expanded substitutions
for a given hostname.
- Please refer to man C{ssh_config} for the parameters that
+ Please refer to man ``ssh_config`` for the parameters that
are replaced.
- @param config: the config for the hostname
- @type hostname: dict
- @param hostname: the hostname that the config belongs to
- @type hostname: str
+ :param dict config: the config for the hostname
+ :param str hostname: the hostname that the config belongs to
"""
if 'hostname' in config:
@@ -264,3 +205,57 @@ class SSHConfig (object):
else:
config[k] = config[k].replace(find, str(replace))
return config
+
+
+class LazyFqdn(object):
+ """
+ Returns the host's fqdn on request as string.
+ """
+
+ def __init__(self, config, host=None):
+ self.fqdn = None
+ self.config = config
+ self.host = host
+
+ def __str__(self):
+ if self.fqdn is None:
+ #
+ # If the SSH config contains AddressFamily, use that when
+ # determining the local host's FQDN. Using socket.getfqdn() from
+ # the standard library is the most general solution, but can
+ # result in noticeable delays on some platforms when IPv6 is
+ # misconfigured or not available, as it calls getaddrinfo with no
+ # address family specified, so both IPv4 and IPv6 are checked.
+ #
+
+ # Handle specific option
+ fqdn = None
+ address_family = self.config.get('addressfamily', 'any').lower()
+ if address_family != 'any':
+ try:
+ family = socket.AF_INET if address_family == 'inet' \
+ else socket.AF_INET6
+ results = socket.getaddrinfo(
+ self.host,
+ None,
+ family,
+ socket.SOCK_DGRAM,
+ socket.IPPROTO_IP,
+ socket.AI_CANONNAME
+ )
+ for res in results:
+ af, socktype, proto, canonname, sa = res
+ if canonname and '.' in canonname:
+ fqdn = canonname
+ break
+ # giaerror -> socket.getaddrinfo() can't resolve self.host
+ # (which is from socket.gethostname()). Fall back to the
+ # getfqdn() call below.
+ except socket.gaierror:
+ pass
+ # Handle 'any' / unspecified
+ if fqdn is None:
+ fqdn = socket.getfqdn()
+ # Cache
+ self.fqdn = fqdn
+ return self.fqdn
diff --git a/paramiko/dsskey.py b/paramiko/dsskey.py
index a02a4ddb..c26966e8 100644
--- a/paramiko/dsskey.py
+++ b/paramiko/dsskey.py
@@ -17,14 +17,15 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-L{DSSKey}
+DSS keys.
"""
from Crypto.PublicKey import DSA
from Crypto.Hash import SHA
-from paramiko.common import *
from paramiko import util
+from paramiko.common import zero_byte, rng
+from paramiko.py3compat import long
from paramiko.ssh_exception import SSHException
from paramiko.message import Message
from paramiko.ber import BER, BERException
@@ -110,9 +111,9 @@ class DSSKey (PKey):
rstr = util.deflate_long(r, 0)
sstr = util.deflate_long(s, 0)
if len(rstr) < 20:
- rstr = zero_byte * (20 - len(rstr)) + rstr
+ rstr += zero_byte * (20 - len(rstr))
if len(sstr) < 20:
- sstr = zero_byte * (20 - len(sstr)) + sstr
+ sstr += zero_byte * (20 - len(sstr))
m.add_string(rstr + sstr)
return m
@@ -137,7 +138,7 @@ class DSSKey (PKey):
def _encode_key(self):
if self.x is None:
raise SSHException('Not enough key information')
- keylist = [ 0, self.p, self.q, self.g, self.y, self.x ]
+ keylist = [0, self.p, self.q, self.g, self.y, self.x]
try:
b = BER()
b.encode(keylist)
@@ -156,13 +157,11 @@ class DSSKey (PKey):
Generate a new private DSS key. This factory function can be used to
generate a new host key or authentication key.
- @param bits: number of bits the generated key should be.
- @type bits: int
- @param progress_func: an optional function to call at key points in
- key generation (used by C{pyCrypto.PublicKey}).
- @type progress_func: function
- @return: new private key
- @rtype: L{DSSKey}
+ :param int bits: number of bits the generated key should be.
+ :param function progress_func:
+ an optional function to call at key points in key generation (used
+ by ``pyCrypto.PublicKey``).
+ :return: new `.DSSKey` private key
"""
dsa = DSA.generate(bits, rng.read, progress_func)
key = DSSKey(vals=(dsa.p, dsa.q, dsa.g, dsa.y))
@@ -170,10 +169,8 @@ class DSSKey (PKey):
return key
generate = staticmethod(generate)
-
### internals...
-
def _from_private_key_file(self, filename, password):
data = self._read_private_key_file('DSA', filename, password)
self._decode_key(data)
diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py
index 3ecf0a58..6ae2d277 100644
--- a/paramiko/ecdsakey.py
+++ b/paramiko/ecdsakey.py
@@ -22,15 +22,13 @@ L{ECDSAKey}
import binascii
from ecdsa import SigningKey, VerifyingKey, der, curves
-from ecdsa.util import number_to_string, sigencode_string, sigencode_strings, sigdecode_strings
-from Crypto.Hash import SHA256, MD5
-from Crypto.Cipher import DES3
+from Crypto.Hash import SHA256
+from ecdsa.test_pyecdsa import ECDSA
+from paramiko.common import four_byte, one_byte
-from paramiko.common import *
-from paramiko import util
from paramiko.message import Message
-from paramiko.ber import BER, BERException
from paramiko.pkey import PKey
+from paramiko.py3compat import byte_chr, u
from paramiko.ssh_exception import SSHException
@@ -145,10 +143,8 @@ class ECDSAKey (PKey):
return key
generate = staticmethod(generate)
-
### internals...
-
def _from_private_key_file(self, filename, password):
data = self._read_private_key_file('EC', filename, password)
self._decode_key(data)
@@ -159,6 +155,7 @@ class ECDSAKey (PKey):
ALLOWED_PADDINGS = [one_byte, byte_chr(2) * 2, byte_chr(3) * 3, byte_chr(4) * 4,
byte_chr(5) * 5, byte_chr(6) * 6, byte_chr(7) * 7]
+
def _decode_key(self, data):
s, padding = der.remove_sequence(data)
if padding:
@@ -180,4 +177,4 @@ class ECDSAKey (PKey):
msg = Message(sig)
r = msg.get_mpint()
s = msg.get_mpint()
- return (r, s)
+ return r, s
diff --git a/paramiko/file.py b/paramiko/file.py
index c9f191a4..f57aa79f 100644
--- a/paramiko/file.py
+++ b/paramiko/file.py
@@ -15,17 +15,14 @@
# 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.
-
-"""
-BufferedFile.
-"""
-
-from paramiko.common import *
+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
class BufferedFile (object):
"""
- Reusable base class to implement python-style file buffering around a
+ Reusable base class to implement Python-style file buffering around a
simpler stream.
"""
@@ -67,10 +64,7 @@ class BufferedFile (object):
file. This iterator happens to return the file itself, since a file is
its own iterator.
- @raise ValueError: if the file is closed.
-
- @return: an interator.
- @rtype: iterator
+ :raises ValueError: if the file is closed.
"""
if self._closed:
raise ValueError('I/O operation on closed file')
@@ -95,14 +89,13 @@ class BufferedFile (object):
if PY2:
def next(self):
"""
- Returns the next line from the input, or raises L{StopIteration} when
- EOF is hit. Unlike python file objects, it's okay to mix calls to
- C{next} and L{readline}.
+ Returns the next line from the input, or raises
+ `~exceptions.StopIteration` when EOF is hit. Unlike Python file
+ objects, it's okay to mix calls to `next` and `readline`.
- @raise StopIteration: when the end of the file is reached.
+ :raises StopIteration: when the end of the file is reached.
- @return: a line read from the file.
- @rtype: str
+ :return: a line (`str`) read from the file.
"""
line = self.readline()
if not line:
@@ -127,15 +120,14 @@ class BufferedFile (object):
def read(self, size=None):
"""
- Read at most C{size} bytes from the file (less if we hit the end of the
- file first). If the C{size} argument is negative or omitted, read all
+ Read at most ``size`` bytes from the file (less if we hit the end of the
+ file first). If the ``size`` argument is negative or omitted, read all
the remaining data in the file.
- @param size: maximum number of bytes to read
- @type size: int
- @return: data read from the file, or an empty string if EOF was
+ :param int size: maximum number of bytes to read
+ :return:
+ data read from the file (as a `str`), or an empty string if EOF was
encountered immediately
- @rtype: str
"""
if self._closed:
raise IOError('File is closed')
@@ -188,14 +180,14 @@ class BufferedFile (object):
incomplete line may be returned. An empty string is returned only when
EOF is encountered immediately.
- @note: Unlike stdio's C{fgets()}, the returned string contains null
- characters (C{'\\0'}) if they occurred in the input.
+ .. note::
+ Unlike stdio's ``fgets``, the returned string contains null
+ characters (``'\\0'``) if they occurred in the input.
- @param size: maximum length of returned string.
- @type size: int
- @return: next line of the file, or an empty string if the end of the
+ :param int size: maximum length of returned string.
+ :return:
+ next line of the file (`str`), or an empty string if the end of the
file has been reached.
- @rtype: str
"""
# it's almost silly how complex this function is.
if self._closed:
@@ -241,7 +233,7 @@ class BufferedFile (object):
pos = line.find(linefeed_byte)
if self._flags & self.FLAG_UNIVERSAL_NEWLINE:
rpos = line.find(cr_byte)
- if (rpos >= 0) and ((rpos < pos) or (pos < 0)):
+ if (rpos >= 0) and (rpos < pos or pos < 0):
pos = rpos
xpos = pos + 1
if (line[pos] == cr_byte_value) and (xpos < len(line)) and (line[xpos] == linefeed_byte_value):
@@ -260,15 +252,13 @@ class BufferedFile (object):
def readlines(self, sizehint=None):
"""
- Read all remaining lines using L{readline} and return them as a list.
- If the optional C{sizehint} argument is present, instead of reading up
+ Read all remaining lines using `readline` and return them as a list.
+ If the optional ``sizehint`` argument is present, instead of reading up
to EOF, whole lines totalling approximately sizehint bytes (possibly
after rounding up to an internal buffer size) are read.
- @param sizehint: desired maximum number of bytes to read.
- @type sizehint: int
- @return: list of lines read from the file.
- @rtype: list
+ :param int sizehint: desired maximum number of bytes to read.
+ :return: `list` of lines read from the file.
"""
lines = []
byte_count = 0
@@ -284,21 +274,20 @@ class BufferedFile (object):
def seek(self, offset, whence=0):
"""
- Set the file's current position, like stdio's C{fseek}. Not all file
+ Set the file's current position, like stdio's ``fseek``. Not all file
objects support seeking.
- @note: If a file is opened in append mode (C{'a'} or C{'a+'}), any seek
+ .. note:: If a file is opened in append mode (``'a'`` or ``'a+'``), any seek
operations will be undone at the next write (as the file position
will move back to the end of the file).
- @param offset: position to move to within the file, relative to
- C{whence}.
- @type offset: int
- @param whence: type of movement: 0 = absolute; 1 = relative to the
- current position; 2 = relative to the end of the file.
- @type whence: int
-
- @raise IOError: if the file doesn't support random access.
+ :param int offset:
+ position to move to within the file, relative to ``whence``.
+ :param int whence:
+ 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.
"""
raise IOError('File does not support seeking.')
@@ -308,20 +297,18 @@ class BufferedFile (object):
useful if the underlying file doesn't support random access, or was
opened in append mode.
- @return: file position (in bytes).
- @rtype: int
+ :return: file position (`number <int>` of bytes).
"""
return self._pos
def write(self, data):
"""
- Write data to the file. If write buffering is on (C{bufsize} was
+ Write data to the file. If write buffering is on (``bufsize`` was
specified and non-zero), some or all of the data may not actually be
- written yet. (Use L{flush} or L{close} to force buffered data to be
+ written yet. (Use `flush` or `close` to force buffered data to be
written out.)
- @param data: data to write.
- @type data: str
+ :param str data: data to write
"""
data = b(data)
if self._closed:
@@ -352,11 +339,10 @@ class BufferedFile (object):
"""
Write a sequence of strings to the file. The sequence can be any
iterable object producing strings, typically a list of strings. (The
- name is intended to match L{readlines}; C{writelines} does not add line
+ name is intended to match `readlines`; `writelines` does not add line
separators.)
- @param sequence: an iterable sequence of strings.
- @type sequence: sequence
+ :param iterable sequence: an iterable sequence of strings.
"""
for line in sequence:
self.write(line)
@@ -364,11 +350,8 @@ class BufferedFile (object):
def xreadlines(self):
"""
- Identical to C{iter(f)}. This is a deprecated file interface that
- predates python iterator support.
-
- @return: an iterator.
- @rtype: iterator
+ Identical to ``iter(f)``. This is a deprecated file interface that
+ predates Python iterator support.
"""
return self
@@ -376,40 +359,36 @@ class BufferedFile (object):
def closed(self):
return self._closed
-
### overrides...
-
def _read(self, size):
"""
- I{(subclass override)}
- Read data from the stream. Return C{None} or raise C{EOFError} to
+ (subclass override)
+ Read data from the stream. Return ``None`` or raise ``EOFError`` to
indicate EOF.
"""
raise EOFError()
def _write(self, data):
"""
- I{(subclass override)}
+ (subclass override)
Write data into the stream.
"""
raise IOError('write not implemented')
def _get_size(self):
"""
- I{(subclass override)}
- Return the size of the file. This is called from within L{_set_mode}
+ (subclass override)
+ Return the size of the file. This is called from within `_set_mode`
if the file is opened in append mode, so the file position can be
- tracked and L{seek} and L{tell} will work correctly. If the file is
+ tracked and `seek` and `tell` will work correctly. If the file is
a stream that can't be randomly accessed, you don't need to override
this method,
"""
return 0
-
### internals...
-
def _set_mode(self, mode='r', bufsize=-1):
"""
Subclasses call this method to initialize the BufferedFile.
@@ -437,13 +416,13 @@ class BufferedFile (object):
self._flags |= self.FLAG_READ
if ('w' in mode) or ('+' in mode):
self._flags |= self.FLAG_WRITE
- if ('a' in mode):
+ if 'a' in mode:
self._flags |= self.FLAG_WRITE | self.FLAG_APPEND
self._size = self._get_size()
self._pos = self._realpos = self._size
- if ('b' in mode):
+ if 'b' in mode:
self._flags |= self.FLAG_BINARY
- if ('U' in mode):
+ if 'U' in mode:
self._flags |= self.FLAG_UNIVERSAL_NEWLINE
# built-in file objects have this attribute to store which kinds of
# line terminations they've seen:
diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py
index cf586a29..f32fbeb6 100644
--- a/paramiko/hostkeys.py
+++ b/paramiko/hostkeys.py
@@ -16,122 +16,42 @@
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-"""
-L{HostKeys}
-"""
-import base64
import binascii
from Crypto.Hash import SHA, HMAC
+from paramiko.common import rng
+from paramiko.py3compat import b, u, encodebytes, decodebytes
+
try:
from collections import MutableMapping
except ImportError:
+ # noinspection PyUnresolvedReferences
from UserDict import DictMixin as MutableMapping
-from paramiko.common import *
from paramiko.dsskey import DSSKey
from paramiko.rsakey import RSAKey
-from paramiko.util import get_logger
+from paramiko.util import get_logger, constant_time_bytes_eq
from paramiko.ecdsakey import ECDSAKey
-class InvalidHostKey(Exception):
-
- def __init__(self, line, exc):
- self.line = line
- self.exc = exc
- self.args = (line, exc)
-
-
-class HostKeyEntry:
- """
- Representation of a line in an OpenSSH-style "known hosts" file.
- """
-
- def __init__(self, hostnames=None, key=None):
- self.valid = (hostnames is not None) and (key is not None)
- self.hostnames = hostnames
- self.key = key
-
- def from_line(cls, line, lineno=None):
- """
- Parses the given line of text to find the names for the host,
- the type of key, and the key data. The line is expected to be in the
- format used by the openssh known_hosts file.
-
- Lines are expected to not have leading or trailing whitespace.
- We don't bother to check for comments or empty lines. All of
- that should be taken care of before sending the line to us.
-
- @param line: a line from an OpenSSH known_hosts file
- @type line: str
- """
- log = get_logger('paramiko.hostkeys')
- fields = line.split(' ')
- if len(fields) < 3:
- # Bad number of fields
- log.info("Not enough fields found in known_hosts in line %s (%r)" %
- (lineno, line))
- return None
- fields = fields[:3]
-
- names, keytype, key = fields
- names = names.split(',')
-
- # Decide what kind of key we're looking at and create an object
- # to hold it accordingly.
- try:
- key = b(key)
- if keytype == 'ssh-rsa':
- key = RSAKey(data=decodebytes(key))
- elif keytype == 'ssh-dss':
- key = DSSKey(data=decodebytes(key))
- elif keytype == 'ecdsa-sha2-nistp256':
- key = ECDSAKey(data=decodebytes(key))
- else:
- log.info("Unable to handle key of type %s" % (keytype,))
- return None
-
- except binascii.Error as e:
- raise InvalidHostKey(line, sys.exc_info()[1])
-
- return cls(names, key)
- from_line = classmethod(from_line)
-
- def to_line(self):
- """
- Returns a string in OpenSSH known_hosts file format, or None if
- the object is not in a valid state. A trailing newline is
- included.
- """
- if self.valid:
- return '%s %s %s\n' % (','.join(self.hostnames), self.key.get_name(),
- self.key.get_base64())
- return None
-
- def __repr__(self):
- return '<HostKeyEntry %r: %r>' % (self.hostnames, self.key)
-
-
class HostKeys (MutableMapping):
"""
- Representation of an openssh-style "known hosts" file. Host keys can be
+ Representation of an OpenSSH-style "known hosts" file. Host keys can be
read from one or more files, and then individual hosts can be looked up to
verify server keys during SSH negotiation.
- A HostKeys object can be treated like a dict; any dict lookup is equivalent
- to calling L{lookup}.
+ A `.HostKeys` object can be treated like a dict; any dict lookup is
+ equivalent to calling `lookup`.
- @since: 1.5.3
+ .. versionadded:: 1.5.3
"""
def __init__(self, filename=None):
"""
- Create a new HostKeys object, optionally loading keys from an openssh
+ Create a new HostKeys object, optionally loading keys from an OpenSSH
style host-key file.
- @param filename: filename to load host keys from, or C{None}
- @type filename: str
+ :param str filename: filename to load host keys from, or ``None``
"""
# emulate a dict of { hostname: { keytype: PKey } }
self._entries = []
@@ -141,14 +61,11 @@ class HostKeys (MutableMapping):
def add(self, hostname, keytype, key):
"""
Add a host key entry to the table. Any existing entry for a
- C{(hostname, keytype)} pair will be replaced.
-
- @param hostname: the hostname (or IP) to add
- @type hostname: str
- @param keytype: key type (C{"ssh-rsa"} or C{"ssh-dss"})
- @type keytype: str
- @param key: the key to add
- @type key: L{PKey}
+ ``(hostname, keytype)`` pair will be replaced.
+
+ :param str hostname: the hostname (or IP) to add
+ :param str keytype: key type (``"ssh-rsa"`` or ``"ssh-dss"``)
+ :param .PKey key: the key to add
"""
for e in self._entries:
if (hostname in e.hostnames) and (e.key.get_name() == keytype):
@@ -158,19 +75,18 @@ class HostKeys (MutableMapping):
def load(self, filename):
"""
- Read a file of known SSH host keys, in the format used by openssh.
+ Read a file of known SSH host keys, in the format used by OpenSSH.
This type of file unfortunately doesn't exist on Windows, but on
posix, it will usually be stored in
- C{os.path.expanduser("~/.ssh/known_hosts")}.
+ ``os.path.expanduser("~/.ssh/known_hosts")``.
If this method is called multiple times, the host keys are merged,
- not cleared. So multiple calls to C{load} will just call L{add},
+ not cleared. So multiple calls to `load` will just call `add`,
replacing any existing entries and adding new ones.
- @param filename: name of the file to read host keys from
- @type filename: str
+ :param str filename: name of the file to read host keys from
- @raise 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):
@@ -188,17 +104,16 @@ class HostKeys (MutableMapping):
def save(self, filename):
"""
- Save host keys into a file, in the format used by openssh. The order of
+ Save host keys into a file, in the format used by OpenSSH. The order of
keys in the file will be preserved when possible (if these keys were
loaded from a file originally). The single exception is that combined
lines will be split into individual key lines, which is arguably a bug.
- @param filename: name of the file to write
- @type filename: str
+ :param str filename: name of the file to write
- @raise IOError: if there was an error writing the file
+ :raises IOError: if there was an error writing the file
- @since: 1.6.1
+ .. versionadded:: 1.6.1
"""
with open(filename, 'w') as f:
for e in self._entries:
@@ -209,13 +124,11 @@ class HostKeys (MutableMapping):
def lookup(self, hostname):
"""
Find a hostkey entry for a given hostname or IP. If no entry is found,
- C{None} is returned. Otherwise a dictionary of keytype to key is
- returned. The keytype will be either C{"ssh-rsa"} or C{"ssh-dss"}.
+ ``None`` is returned. Otherwise a dictionary of keytype to key is
+ returned. The keytype will be either ``"ssh-rsa"`` or ``"ssh-dss"``.
- @param hostname: the hostname (or IP) to lookup
- @type hostname: str
- @return: keys associated with this host (or C{None})
- @rtype: dict(str, L{PKey})
+ :param str hostname: the hostname (or IP) to lookup
+ :return: dict of `str` -> `.PKey` keys associated with this host (or ``None``)
"""
class SubDict (MutableMapping):
def __init__(self, hostname, entries, hostkeys):
@@ -263,7 +176,7 @@ class HostKeys (MutableMapping):
entries = []
for e in self._entries:
for h in e.hostnames:
- if (h.startswith('|1|') and (self.hash_host(hostname, h) == h)) or (h == hostname):
+ if h.startswith('|1|') and constant_time_bytes_eq(self.hash_host(hostname, h), h) or h == hostname:
entries.append(e)
if len(entries) == 0:
return None
@@ -274,13 +187,10 @@ class HostKeys (MutableMapping):
Return True if the given key is associated with the given hostname
in this dictionary.
- @param hostname: hostname (or IP) of the SSH server
- @type hostname: str
- @param key: the key to check
- @type key: L{PKey}
- @return: C{True} if the key is associated with the hostname; C{False}
- if not
- @rtype: bool
+ :param str hostname: hostname (or IP) of the SSH server
+ :param .PKey key: the key to check
+ :return:
+ ``True`` if the key is associated with the hostname; else ``False``
"""
k = self.lookup(hostname)
if k is None:
@@ -305,7 +215,6 @@ class HostKeys (MutableMapping):
def __delitem__(self, key):
k = self[key]
- pass
def __getitem__(self, key):
ret = self.lookup(key)
@@ -329,7 +238,7 @@ class HostKeys (MutableMapping):
self._entries.append(HostKeyEntry([hostname], entry[key_type]))
def keys(self):
- # python 2.4 sets would be nice here.
+ # Python 2.4 sets would be nice here.
ret = []
for e in self._entries:
for h in e.hostnames:
@@ -345,15 +254,12 @@ class HostKeys (MutableMapping):
def hash_host(hostname, salt=None):
"""
- Return a "hashed" form of the hostname, as used by openssh when storing
+ Return a "hashed" form of the hostname, as used by OpenSSH when storing
hashed hostnames in the known_hosts file.
- @param hostname: the hostname to hash
- @type hostname: str
- @param salt: optional salt to use when hashing (must be 20 bytes long)
- @type salt: str
- @return: the hashed hostname
- @rtype: str
+ :param str hostname: the hostname to hash
+ :param str salt: optional salt to use when hashing (must be 20 bytes long)
+ :return: the hashed hostname as a `str`
"""
if salt is None:
salt = rng.read(SHA.digest_size)
@@ -367,3 +273,78 @@ class HostKeys (MutableMapping):
return hostkey.replace('\n', '')
hash_host = staticmethod(hash_host)
+
+class InvalidHostKey(Exception):
+ def __init__(self, line, exc):
+ self.line = line
+ self.exc = exc
+ self.args = (line, exc)
+
+
+class HostKeyEntry:
+ """
+ Representation of a line in an OpenSSH-style "known hosts" file.
+ """
+
+ def __init__(self, hostnames=None, key=None):
+ self.valid = (hostnames is not None) and (key is not None)
+ self.hostnames = hostnames
+ self.key = key
+
+ def from_line(cls, line, lineno=None):
+ """
+ Parses the given line of text to find the names for the host,
+ the type of key, and the key data. The line is expected to be in the
+ format used by the OpenSSH known_hosts file.
+
+ Lines are expected to not have leading or trailing whitespace.
+ We don't bother to check for comments or empty lines. All of
+ that should be taken care of before sending the line to us.
+
+ :param str line: a line from an OpenSSH known_hosts file
+ """
+ log = get_logger('paramiko.hostkeys')
+ fields = line.split(' ')
+ if len(fields) < 3:
+ # Bad number of fields
+ log.info("Not enough fields found in known_hosts in line %s (%r)" %
+ (lineno, line))
+ return None
+ fields = fields[:3]
+
+ names, keytype, key = fields
+ names = names.split(',')
+
+ # Decide what kind of key we're looking at and create an object
+ # to hold it accordingly.
+ try:
+ key = b(key)
+ if keytype == 'ssh-rsa':
+ key = RSAKey(data=decodebytes(key))
+ elif keytype == 'ssh-dss':
+ key = DSSKey(data=decodebytes(key))
+ elif keytype == 'ecdsa-sha2-nistp256':
+ key = ECDSAKey(data=decodebytes(key))
+ else:
+ log.info("Unable to handle key of type %s" % (keytype,))
+ return None
+
+ except binascii.Error as e:
+ raise InvalidHostKey(line, e)
+
+ return cls(names, key)
+ from_line = classmethod(from_line)
+
+ def to_line(self):
+ """
+ Returns a string in OpenSSH known_hosts file format, or None if
+ the object is not in a valid state. A trailing newline is
+ included.
+ """
+ if self.valid:
+ return '%s %s %s\n' % (','.join(self.hostnames), self.key.get_name(),
+ self.key.get_base64())
+ return None
+
+ def __repr__(self):
+ return '<HostKeyEntry %r: %r>' % (self.hostnames, self.key)
diff --git a/paramiko/kex_gex.py b/paramiko/kex_gex.py
index 02494539..02e507b7 100644
--- a/paramiko/kex_gex.py
+++ b/paramiko/kex_gex.py
@@ -17,17 +17,17 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-Variant on L{KexGroup1 <paramiko.kex_group1.KexGroup1>} where the prime "p" and
+Variant on `KexGroup1 <paramiko.kex_group1.KexGroup1>` where the prime "p" and
generator "g" are provided by the server. A bit more work is required on the
client side, and a B{lot} more on the server side.
"""
from Crypto.Hash import SHA
-from Crypto.Util import number
-from paramiko.common import *
from paramiko import util
+from paramiko.common import DEBUG
from paramiko.message import Message
+from paramiko.py3compat import byte_chr, byte_ord, byte_mask
from paramiko.ssh_exception import SSHException
@@ -88,10 +88,8 @@ class KexGex (object):
return self._parse_kexdh_gex_request_old(m)
raise SSHException('KexGex asked to handle packet type %d' % ptype)
-
### internals...
-
def _generate_x(self):
# generate an "x" (1 < x < (p-1)/2).
q = (self.p - 1) // 2
diff --git a/paramiko/kex_group1.py b/paramiko/kex_group1.py
index 25eb2566..f588332e 100644
--- a/paramiko/kex_group1.py
+++ b/paramiko/kex_group1.py
@@ -23,15 +23,19 @@ Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of
from Crypto.Hash import SHA
-from paramiko.common import *
from paramiko import util
+from paramiko.common import max_byte, zero_byte
from paramiko.message import Message
+from paramiko.py3compat import byte_chr, long, byte_mask
from paramiko.ssh_exception import SSHException
_MSG_KEXDH_INIT, _MSG_KEXDH_REPLY = range(30, 32)
c_MSG_KEXDH_INIT, c_MSG_KEXDH_REPLY = [byte_chr(c) for c in range(30, 32)]
+b7fffffffffffffff = byte_chr(0x7f) + max_byte * 7
+b0000000000000000 = zero_byte * 8
+
class KexGroup1(object):
@@ -39,9 +43,6 @@ class KexGroup1(object):
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF
G = 2
- b7fffffffffffffff = byte_chr(0x7f) + max_byte * 7
- b0000000000000000 = zero_byte * 8
-
name = 'diffie-hellman-group1-sha1'
def __init__(self, transport):
@@ -72,10 +73,8 @@ class KexGroup1(object):
return self._parse_kexdh_reply(m)
raise SSHException('KexGroup1 asked to handle packet type %d' % ptype)
-
### internals...
-
def _generate_x(self):
# generate an "x" (1 < x < q), where q is (p-1)/2.
# p is a 128-byte (1024-bit) number, where the first 64 bits are 1.
@@ -85,8 +84,8 @@ class KexGroup1(object):
while 1:
x_bytes = self.transport.rng.read(128)
x_bytes = byte_mask(x_bytes[0], 0x7f) + x_bytes[1:]
- if (x_bytes[:8] != self.b7fffffffffffffff) and \
- (x_bytes[:8] != self.b0000000000000000):
+ if (x_bytes[:8] != b7fffffffffffffff and
+ x_bytes[:8] != b0000000000000000):
break
self.x = util.inflate_long(x_bytes)
diff --git a/paramiko/logging22.py b/paramiko/logging22.py
deleted file mode 100644
index e68c52cb..00000000
--- a/paramiko/logging22.py
+++ /dev/null
@@ -1,66 +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.
-
-"""
-Stub out logging on python < 2.3.
-"""
-
-
-DEBUG = 10
-INFO = 20
-WARNING = 30
-ERROR = 40
-CRITICAL = 50
-
-
-def getLogger(name):
- return _logger
-
-
-class logger (object):
- def __init__(self):
- self.handlers = [ ]
- self.level = ERROR
-
- def setLevel(self, level):
- self.level = level
-
- def addHandler(self, h):
- self.handlers.append(h)
-
- def addFilter(self, filter):
- pass
-
- def log(self, level, text):
- if level >= self.level:
- for h in self.handlers:
- h.f.write(text + '\n')
- h.f.flush()
-
-class StreamHandler (object):
- def __init__(self, f):
- self.f = f
-
- def setFormatter(self, f):
- pass
-
-class Formatter (object):
- def __init__(self, x, y):
- pass
-
-_logger = logger()
diff --git a/paramiko/message.py b/paramiko/message.py
index 45086213..b893e76d 100644
--- a/paramiko/message.py
+++ b/paramiko/message.py
@@ -23,14 +23,15 @@ Implementation of an SSH2 "message".
import struct
from paramiko import util
-from paramiko.common import *
+from paramiko.common import zero_byte, max_byte, one_byte, asbytes
+from paramiko.py3compat import long, BytesIO, u, integer_types
class Message (object):
"""
- An SSH2 I{Message} is a stream of bytes that encodes some combination of
- strings, integers, bools, and infinite-precision integers (known in python
- as I{long}s). This class builds or breaks down such a byte stream.
+ An SSH2 message is a stream of bytes that encodes some combination of
+ strings, integers, bools, and infinite-precision integers (known in Python
+ as longs). This class builds or breaks down such a byte stream.
Normally you don't need to deal with anything this low-level, but it's
exposed for people implementing custom extensions, or features that
@@ -41,34 +42,32 @@ class Message (object):
def __init__(self, content=None):
"""
- Create a new SSH2 Message.
+ Create a new SSH2 message.
- @param content: the byte stream to use as the Message content (passed
- in only when decomposing a Message).
- @type content: string
+ :param str content:
+ the byte stream to use as the message content (passed in only when
+ decomposing a message).
"""
- if content != None:
+ if content is not None:
self.packet = BytesIO(content)
else:
self.packet = BytesIO()
def __str__(self):
+ """
+ Return the byte stream content of this message, as a string/bytes obj.
+ """
return self.asbytes()
def __repr__(self):
"""
Returns a string representation of this object, for debugging.
-
- @rtype: string
"""
return 'paramiko.Message(' + repr(self.packet.getvalue()) + ')'
def asbytes(self):
"""
Return the byte stream content of this Message, as bytes.
-
- @return: the contents of this Message.
- @rtype: bytes
"""
return self.packet.getvalue()
@@ -81,11 +80,8 @@ class Message (object):
def get_remainder(self):
"""
- Return the bytes of this Message that haven't already been parsed and
- returned.
-
- @return: a string of the bytes not parsed yet.
- @rtype: string
+ Return the bytes (as a `str`) of this message that haven't already been
+ parsed and returned.
"""
position = self.packet.tell()
remainder = self.packet.read()
@@ -94,12 +90,9 @@ class Message (object):
def get_so_far(self):
"""
- Returns the bytes of this Message that have been parsed and returned.
- The string passed into a Message's constructor can be regenerated by
- concatenating C{get_so_far} and L{get_remainder}.
-
- @return: a string of the bytes parsed so far.
- @rtype: string
+ Returns the `str` bytes of this message that have been parsed and
+ returned. The string passed into a message's constructor can be
+ regenerated by concatenating ``get_so_far`` and `get_remainder`.
"""
position = self.packet.tell()
self.rewind()
@@ -107,36 +100,31 @@ class Message (object):
def get_bytes(self, n):
"""
- Return the next C{n} bytes of the Message, without decomposing into
- an int, string, etc. Just the raw bytes are returned.
-
- @return: a string of the next C{n} bytes of the Message, or a string
- of C{n} zero bytes, if there aren't C{n} bytes remaining.
- @rtype: string
+ Return the next ``n`` bytes of the message (as a `str`), without
+ decomposing into an int, decoded string, etc. Just the raw bytes are
+ returned. Returns a string of ``n`` zero bytes if there weren't ``n``
+ bytes remaining in the message.
"""
b = self.packet.read(n)
- max_pad_size = 1<<20 # Limit padding to 1 MB
- if len(b) < n and n < max_pad_size:
+ max_pad_size = 1 << 20 # Limit padding to 1 MB
+ if len(b) < n < max_pad_size:
return b + zero_byte * (n - len(b))
return b
def get_byte(self):
"""
- Return the next byte of the Message, without decomposing it. This
- is equivalent to L{get_bytes(1)<get_bytes>}.
+ Return the next byte of the message, without decomposing it. This
+ is equivalent to `get_bytes(1) <get_bytes>`.
- @return: the next byte of the Message, or C{'\000'} if there aren't
+ :return:
+ the next (`str`) byte of the message, or ``'\000'`` if there aren't
any bytes remaining.
- @rtype: string
"""
return self.get_bytes(1)
def get_boolean(self):
"""
Fetch a boolean from the stream.
-
- @return: C{True} or C{False} (from the Message).
- @rtype: bool
"""
b = self.get_bytes(1)
return b != zero_byte
@@ -145,6 +133,18 @@ class Message (object):
"""
Fetch an int from the stream.
+ :return: a 32-bit unsigned `int`.
+ """
+ byte = self.get_bytes(1)
+ if byte == max_byte:
+ return util.inflate_long(self.get_binary())
+ byte += self.get_bytes(3)
+ return struct.unpack('>I', byte)[0]
+
+ def get_size(self):
+ """
+ Fetch an int from the stream.
+
@return: a 32-bit unsigned integer.
@rtype: int
"""
@@ -167,8 +167,7 @@ class Message (object):
"""
Fetch a 64-bit int from the stream.
- @return: a 64-bit unsigned integer.
- @rtype: long
+ :return: a 64-bit unsigned integer (`long`).
"""
return struct.unpack('>Q', self.get_bytes(8))[0]
@@ -176,19 +175,15 @@ class Message (object):
"""
Fetch a long int (mpint) from the stream.
- @return: an arbitrary-length integer.
- @rtype: long
+ :return: an arbitrary-length integer (`long`).
"""
return util.inflate_long(self.get_binary())
def get_string(self):
"""
- Fetch a string from the stream. This could be a byte string and may
+ Fetch a `str` from the stream. This could be a byte string and may
contain unprintable characters. (It's not unheard of for a string to
- contain another byte-stream Message.)
-
- @return: a string.
- @rtype: string
+ contain another byte-stream message.)
"""
return self.get_bytes(self.get_size())
@@ -217,11 +212,9 @@ class Message (object):
def get_list(self):
"""
- Fetch a list of strings from the stream. These are trivially encoded
- as comma-separated values in a string.
-
- @return: a list of strings.
- @rtype: list of strings
+ Fetch a `list` of `strings <str>` from the stream.
+
+ These are trivially encoded as comma-separated values in a string.
"""
return self.get_text().split(',')
@@ -229,8 +222,7 @@ class Message (object):
"""
Write bytes to the stream, without any formatting.
- @param b: bytes to add
- @type b: str
+ :param str b: bytes to add
"""
self.packet.write(b)
return self
@@ -239,8 +231,7 @@ class Message (object):
"""
Write a single byte to the stream, without any formatting.
- @param b: byte to add
- @type b: str
+ :param str b: byte to add
"""
self.packet.write(b)
return self
@@ -249,8 +240,7 @@ class Message (object):
"""
Add a boolean value to the stream.
- @param b: boolean value to add
- @type b: bool
+ :param bool b: boolean value to add
"""
if b:
self.packet.write(one_byte)
@@ -262,11 +252,23 @@ class Message (object):
"""
Add an integer to the stream.
- @param n: integer to add
- @type n: int
+ :param int n: integer to add
"""
self.packet.write(struct.pack('>I', n))
return self
+
+ def add_int(self, n):
+ """
+ Add an integer to the stream.
+
+ :param int n: integer to add
+ """
+ if n >= Message.big_int:
+ self.packet.write(max_byte)
+ self.add_string(util.deflate_long(n))
+ else:
+ self.packet.write(struct.pack('>I', n))
+ return self
def add_int(self, n):
"""
@@ -286,8 +288,7 @@ class Message (object):
"""
Add a 64-bit int to the stream.
- @param n: long int to add
- @type n: long
+ :param long n: long int to add
"""
self.packet.write(struct.pack('>Q', n))
return self
@@ -297,8 +298,7 @@ class Message (object):
Add a long int to the stream, encoded as an infinite-precision
integer. This method only works on positive numbers.
- @param z: long int to add
- @type z: long
+ :param long z: long int to add
"""
self.add_string(util.deflate_long(z))
return self
@@ -307,8 +307,7 @@ class Message (object):
"""
Add a string to the stream.
- @param s: string to add
- @type s: str
+ :param str s: string to add
"""
s = asbytes(s)
self.add_size(len(s))
@@ -321,8 +320,7 @@ class Message (object):
a single string of values separated by commas. (Yes, really, that's
how SSH2 does it.)
- @param l: list of strings to add
- @type l: list(str)
+ :param list l: list of strings to add
"""
self.add_string(','.join(l))
return self
@@ -341,11 +339,11 @@ class Message (object):
"""
Add a sequence of items to the stream. The values are encoded based
on their type: str, int, bool, list, or long.
+
+ .. warning::
+ Longs are encoded non-deterministically. Don't use this method.
- @param seq: the sequence of items
- @type seq: sequence
-
- @bug: longs are encoded non-deterministically. Don't use this method.
+ :param seq: the sequence of items
"""
for item in seq:
self._add(item)
diff --git a/paramiko/packet.py b/paramiko/packet.py
index b941c75b..0f51df5e 100644
--- a/paramiko/packet.py
+++ b/paramiko/packet.py
@@ -17,18 +17,19 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-Packetizer.
+Packet handling
"""
import errno
-import select
import socket
import struct
import threading
import time
-from paramiko.common import *
from paramiko import util
+from paramiko.common import linefeed_byte, cr_byte_value, asbytes, MSG_NAMES, \
+ DEBUG, xffffffff, zero_byte, rng
+from paramiko.py3compat import u, byte_ord
from paramiko.ssh_exception import SSHException, ProxyCommandFailure
from paramiko.message import Message
@@ -57,8 +58,8 @@ class Packetizer (object):
REKEY_PACKETS = pow(2, 29)
REKEY_BYTES = pow(2, 29)
- REKEY_PACKETS_OVERFLOW_MAX = pow(2,29) # Allow receiving this many packets after a re-key request before terminating
- REKEY_BYTES_OVERFLOW_MAX = pow(2,29) # Allow receiving this many bytes after a re-key request before terminating
+ REKEY_PACKETS_OVERFLOW_MAX = pow(2, 29) # Allow receiving this many packets after a re-key request before terminating
+ REKEY_BYTES_OVERFLOW_MAX = pow(2, 29) # Allow receiving this many bytes after a re-key request before terminating
def __init__(self, socket):
self.__socket = socket
@@ -104,7 +105,7 @@ class Packetizer (object):
def set_log(self, log):
"""
- Set the python log object to use for logging.
+ Set the Python log object to use for logging.
"""
self.__logger = log
@@ -169,17 +170,15 @@ class Packetizer (object):
def need_rekey(self):
"""
- Returns C{True} if a new set of keys needs to be negotiated. This
+ Returns ``True`` if a new set of keys needs to be negotiated. This
will be triggered during a packet read or write, so it should be
checked after every read or write, or at least after every few.
-
- @return: C{True} if a new set of keys needs to be negotiated
"""
return self.__need_rekey
def set_keepalive(self, interval, callback):
"""
- Turn on/off the callback keepalive. If C{interval} seconds pass with
+ Turn on/off the callback keepalive. If ``interval`` seconds pass with
no data read from or written to the socket, the callback will be
executed and the timer will be reset.
"""
@@ -191,12 +190,11 @@ class Packetizer (object):
"""
Read as close to N bytes as possible, blocking as long as necessary.
- @param n: number of bytes to read
- @type n: int
- @return: the data read
- @rtype: str
- @raise EOFError: if the socket was closed before all the bytes could
- be read
+ :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
"""
out = bytes()
# handle over-reading from reading the banner line
@@ -204,8 +202,6 @@ class Packetizer (object):
out = self.__remainder[:n]
self.__remainder = self.__remainder[n:]
n -= len(out)
- if PY22:
- return self._py22_read_all(n, out)
while n > 0:
got_timeout = False
try:
@@ -254,7 +250,7 @@ class Packetizer (object):
else:
n = -1
except ProxyCommandFailure:
- raise # so it doesn't get swallowed by the below catchall
+ raise # so it doesn't get swallowed by the below catchall
except Exception:
# could be: (32, 'Broken pipe')
n = -1
@@ -278,7 +274,7 @@ class Packetizer (object):
while not linefeed_byte in buf:
buf += self._read_timeout(timeout)
n = buf.index(linefeed_byte)
- self.__remainder = buf[n+1:]
+ self.__remainder = buf[n + 1:]
buf = buf[:n]
if (len(buf) > 0) and (buf[-1] == cr_byte_value):
buf = buf[:-1]
@@ -304,12 +300,12 @@ class Packetizer (object):
if self.__dump_packets:
self._log(DEBUG, 'Write packet <%s>, length %d' % (cmd_name, orig_len))
self._log(DEBUG, util.format_binary(packet, 'OUT: '))
- if self.__block_engine_out != None:
+ if self.__block_engine_out is not None:
out = self.__block_engine_out.encrypt(packet)
else:
out = packet
# + mac
- if self.__block_engine_out != None:
+ if self.__block_engine_out is not None:
payload = struct.pack('>I', self.__sequence_number_out) + packet
out += compute_hmac(self.__mac_key_out, payload, self.__mac_engine_out)[:self.__mac_size_out]
self.__sequence_number_out = (self.__sequence_number_out + 1) & xffffffff
@@ -317,8 +313,8 @@ class Packetizer (object):
self.__sent_bytes += len(out)
self.__sent_packets += 1
- if ((self.__sent_packets >= self.REKEY_PACKETS) or (self.__sent_bytes >= self.REKEY_BYTES)) \
- and not self.__need_rekey:
+ if (self.__sent_packets >= self.REKEY_PACKETS or self.__sent_bytes >= self.REKEY_BYTES)\
+ and not self.__need_rekey:
# only ask once for rekeying
self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes sent)' %
(self.__sent_packets, self.__sent_bytes))
@@ -333,14 +329,14 @@ class Packetizer (object):
Only one thread should ever be in this function (no other locking is
done).
- @raise SSHException: if the packet is mangled
- @raise 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 != None:
+ if self.__block_engine_in is not None:
header = self.__block_engine_in.decrypt(header)
if self.__dump_packets:
- self._log(DEBUG, util.format_binary(header, 'IN: '));
+ self._log(DEBUG, util.format_binary(header, 'IN: '))
packet_size = struct.unpack('>I', header[:4])[0]
# leftover contains decrypted bytes from the first block (after the length field)
leftover = header[4:]
@@ -349,17 +345,17 @@ class Packetizer (object):
buf = self.read_all(packet_size + self.__mac_size_in - len(leftover))
packet = buf[:packet_size - len(leftover)]
post_packet = buf[packet_size - len(leftover):]
- if self.__block_engine_in != None:
+ if self.__block_engine_in is not None:
packet = self.__block_engine_in.decrypt(packet)
if self.__dump_packets:
- self._log(DEBUG, util.format_binary(packet, 'IN: '));
+ self._log(DEBUG, util.format_binary(packet, 'IN: '))
packet = leftover + packet
if self.__mac_size_in > 0:
mac = post_packet[:self.__mac_size_in]
mac_payload = struct.pack('>II', self.__sequence_number_in, packet_size) + packet
my_mac = compute_hmac(self.__mac_key_in, mac_payload, self.__mac_engine_in)[:self.__mac_size_in]
- if my_mac != mac:
+ if not util.constant_time_bytes_eq(my_mac, mac):
raise SSHException('Mismatched MAC')
padding = byte_ord(packet[0])
payload = packet[1:packet_size - padding]
@@ -404,10 +400,8 @@ class Packetizer (object):
self._log(DEBUG, 'Read packet <%s>, length %d' % (cmd_name, len(payload)))
return cmd, msg
-
########## protected
-
def _log(self, level, msg):
if self.__logger is None:
return
@@ -419,7 +413,7 @@ class Packetizer (object):
def _check_keepalive(self):
if (not self.__keepalive_interval) or (not self.__block_engine_out) or \
- self.__need_rekey:
+ self.__need_rekey:
# wait till we're encrypting, and not in the middle of rekeying
return
now = time.time()
@@ -427,40 +421,7 @@ class Packetizer (object):
self.__keepalive_callback()
self.__keepalive_last = now
- def _py22_read_all(self, n, out):
- while n > 0:
- r, w, e = select.select([self.__socket], [], [], 0.1)
- if self.__socket not in r:
- if self.__closed:
- raise EOFError()
- self._check_keepalive()
- else:
- x = self.__socket.recv(n)
- if len(x) == 0:
- raise EOFError()
- out += x
- n -= len(x)
- return out
-
- def _py22_read_timeout(self, timeout):
- start = time.time()
- while True:
- r, w, e = select.select([self.__socket], [], [], 0.1)
- if self.__socket in r:
- x = self.__socket.recv(1)
- if len(x) == 0:
- raise EOFError()
- break
- if self.__closed:
- raise EOFError()
- now = time.time()
- if now - start >= timeout:
- raise socket.timeout()
- return x
-
def _read_timeout(self, timeout):
- if PY22:
- return self._py22_read_timeout(timeout)
start = time.time()
while True:
try:
@@ -471,8 +432,8 @@ class Packetizer (object):
except socket.timeout:
pass
except EnvironmentError as e:
- if ((type(e.args) is tuple) and (len(e.args) > 0) and
- (e.args[0] == errno.EINTR)):
+ if (type(e.args) is tuple and len(e.args) > 0 and
+ e.args[0] == errno.EINTR):
pass
else:
raise
diff --git a/paramiko/pipe.py b/paramiko/pipe.py
index 619b807e..4f62d7c5 100644
--- a/paramiko/pipe.py
+++ b/paramiko/pipe.py
@@ -17,11 +17,12 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-Abstraction of a one-way pipe where the read end can be used in select().
-Normally this is trivial, but Windows makes it nearly impossible.
+Abstraction of a one-way pipe where the read end can be used in
+`select.select`. Normally this is trivial, but Windows makes it nearly
+impossible.
The pipe acts like an Event, which can be set or cleared. When set, the pipe
-will trigger as readable in select().
+will trigger as readable in `select <select.select>`.
"""
import sys
@@ -30,7 +31,7 @@ import socket
from paramiko.py3compat import b
-def make_pipe ():
+def make_pipe():
if sys.platform[:3] != 'win':
p = PosixPipe()
else:
@@ -39,34 +40,34 @@ def make_pipe ():
class PosixPipe (object):
- def __init__ (self):
+ def __init__(self):
self._rfd, self._wfd = os.pipe()
self._set = False
self._forever = False
self._closed = False
- def close (self):
+ def close(self):
os.close(self._rfd)
os.close(self._wfd)
# used for unit tests:
self._closed = True
- def fileno (self):
+ def fileno(self):
return self._rfd
- def clear (self):
+ def clear(self):
if not self._set or self._forever:
return
os.read(self._rfd, 1)
self._set = False
- def set (self):
+ def set(self):
if self._set or self._closed:
return
self._set = True
os.write(self._wfd, b'*')
- def set_forever (self):
+ def set_forever(self):
self._forever = True
self.set()
@@ -76,7 +77,7 @@ class WindowsPipe (object):
On Windows, only an OS-level "WinSock" may be used in select(), but reads
and writes must be to the actual socket object.
"""
- def __init__ (self):
+ def __init__(self):
serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serv.bind(('127.0.0.1', 0))
serv.listen(1)
@@ -91,13 +92,13 @@ class WindowsPipe (object):
self._forever = False
self._closed = False
- def close (self):
+ def close(self):
self._rsock.close()
self._wsock.close()
# used for unit tests:
self._closed = True
- def fileno (self):
+ def fileno(self):
return self._rsock.fileno()
def clear (self):
diff --git a/paramiko/pkey.py b/paramiko/pkey.py
index 0a174ca7..c8f84e0a 100644
--- a/paramiko/pkey.py
+++ b/paramiko/pkey.py
@@ -27,9 +27,9 @@ import os
from Crypto.Hash import MD5
from Crypto.Cipher import DES3, AES
-from paramiko.common import *
from paramiko import util
-from paramiko.message import Message
+from paramiko.common import o600, rng, zero_byte
+from paramiko.py3compat import u, encodebytes, decodebytes, b
from paramiko.ssh_exception import SSHException, PasswordRequiredException
@@ -40,43 +40,39 @@ class PKey (object):
# known encryption types for private key files:
_CIPHER_TABLE = {
- 'AES-128-CBC': { 'cipher': AES, 'keysize': 16, 'blocksize': 16, 'mode': AES.MODE_CBC },
- 'DES-EDE3-CBC': { 'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC },
+ 'AES-128-CBC': {'cipher': AES, 'keysize': 16, 'blocksize': 16, 'mode': AES.MODE_CBC},
+ 'DES-EDE3-CBC': {'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC},
}
-
def __init__(self, msg=None, data=None):
"""
- Create a new instance of this public key type. If C{msg} is given,
+ Create a new instance of this public key type. If ``msg`` is given,
the key's public part(s) will be filled in from the message. If
- C{data} is given, the key's public part(s) will be filled in from
+ ``data`` is given, the key's public part(s) will be filled in from
the string.
- @param msg: an optional SSH L{Message} containing a public key of this
- type.
- @type msg: L{Message}
- @param data: an optional string containing a public key of this type
- @type data: str
+ :param .Message msg:
+ an optional SSH `.Message` containing a public key of this type.
+ :param str data: an optional string containing a public key of this type
- @raise SSHException: if a key cannot be created from the C{data} or
- C{msg} given, or no key was passed in.
+ :raises SSHException:
+ if a key cannot be created from the ``data`` or ``msg`` given, or
+ no key was passed in.
"""
pass
def asbytes(self):
"""
- Return a string of an SSH L{Message} made up of the public part(s) of
- this key. This string is suitable for passing to L{__init__} to
+ Return a string of an SSH `.Message` made up of the public part(s) of
+ this key. This string is suitable for passing to `__init__` to
re-create the key object later.
-
- @return: string representation of an SSH key message.
- @rtype: str
"""
return bytes()
def __str__(self):
return self.asbytes()
+ # noinspection PyUnresolvedReferences
def __cmp__(self, other):
"""
Compare this key to another. Returns 0 if this key is equivalent to
@@ -84,10 +80,7 @@ class PKey (object):
of the key are compared, so a public key will compare equal to its
corresponding private key.
- @param other: key to compare to.
- @type other: L{PKey}
- @return: 0 if the two keys are equivalent, non-0 otherwise.
- @rtype: int
+ :param .Pkey other: key to compare to.
"""
hs = hash(self)
ho = hash(other)
@@ -102,9 +95,9 @@ class PKey (object):
"""
Return the name of this private key implementation.
- @return: name of this private key type, in SSH terminology (for
- example, C{"ssh-rsa"}).
- @rtype: str
+ :return:
+ name of this private key type, in SSH terminology, as a `str` (for
+ example, ``"ssh-rsa"``).
"""
return ''
@@ -113,18 +106,14 @@ class PKey (object):
Return the number of significant bits in this key. This is useful
for judging the relative security of a key.
- @return: bits in the key.
- @rtype: int
+ :return: bits in the key (as an `int`)
"""
return 0
def can_sign(self):
"""
- Return C{True} if this key has the private part necessary for signing
+ Return ``True`` if this key has the private part necessary for signing
data.
-
- @return: C{True} if this is a private key.
- @rtype: bool
"""
return False
@@ -133,9 +122,9 @@ class PKey (object):
Return an MD5 fingerprint of the public part of this key. Nothing
secret is revealed.
- @return: a 16-byte string (binary) of the MD5 fingerprint, in SSH
+ :return:
+ a 16-byte `string <str>` (binary) of the MD5 fingerprint, in SSH
format.
- @rtype: str
"""
return MD5.new(self.asbytes()).digest()
@@ -145,22 +134,18 @@ class PKey (object):
secret is revealed. This format is compatible with that used to store
public key files or recognized host keys.
- @return: a base64 string containing the public part of the key.
- @rtype: str
+ :return: a base64 `string <str>` containing the public part of the key.
"""
return u(encodebytes(self.asbytes())).replace('\n', '')
def sign_ssh_data(self, rng, data):
"""
- Sign a blob of data with this private key, and return a L{Message}
+ Sign a blob of data with this private key, and return a `.Message`
representing an SSH signature message.
- @param rng: a secure random number generator.
- @type rng: L{Crypto.Util.rng.RandomPool}
- @param data: the data to sign.
- @type data: str
- @return: an SSH signature message.
- @rtype: L{Message}
+ :param .Crypto.Util.rng.RandomPool rng: a secure random number generator.
+ :param str data: the data to sign.
+ :return: an SSH signature `message <.Message>`.
"""
return bytes()
@@ -169,37 +154,31 @@ class PKey (object):
Given a blob of data, and an SSH message representing a signature of
that data, verify that it was signed with this key.
- @param data: the data that was signed.
- @type data: str
- @param msg: an SSH signature message
- @type msg: L{Message}
- @return: C{True} if the signature verifies correctly; C{False}
- otherwise.
- @rtype: boolean
+ :param str data: the data that was signed.
+ :param .Message msg: an SSH signature message
+ :return:
+ ``True`` if the signature verifies correctly; ``False`` otherwise.
"""
return False
def from_private_key_file(cls, filename, password=None):
"""
Create a key object by reading a private key file. If the private
- key is encrypted and C{password} is not C{None}, the given password
- will be used to decrypt the key (otherwise L{PasswordRequiredException}
- is thrown). Through the magic of python, this factory method will
- exist in all subclasses of PKey (such as L{RSAKey} or L{DSSKey}), but
+ key is encrypted and ``password`` is not ``None``, the given password
+ will be used to decrypt the key (otherwise `.PasswordRequiredException`
+ is thrown). Through the magic of Python, this factory method will
+ exist in all subclasses of PKey (such as `.RSAKey` or `.DSSKey`), but
is useless on the abstract PKey class.
- @param filename: name of the file to read
- @type filename: str
- @param password: an optional password to use to decrypt the key file,
+ :param str filename: name of the file to read
+ :param str password: an optional password to use to decrypt the key file,
if it's encrypted
- @type password: str
- @return: a new key object based on the given private key
- @rtype: L{PKey}
+ :return: a new `.PKey` based on the given private key
- @raise IOError: if there was an error reading the file
- @raise PasswordRequiredException: if the private key file is
- encrypted, and C{password} is C{None}
- @raise SSHException: if the key file is invalid
+ :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
"""
key = cls(filename=filename, password=password)
return key
@@ -208,22 +187,19 @@ class PKey (object):
def from_private_key(cls, file_obj, password=None):
"""
Create a key object by reading a private key from a file (or file-like)
- object. If the private key is encrypted and C{password} is not C{None},
+ object. If the private key is encrypted and ``password`` is not ``None``,
the given password will be used to decrypt the key (otherwise
- L{PasswordRequiredException} is thrown).
+ `.PasswordRequiredException` is thrown).
- @param file_obj: the file to read from
- @type file_obj: file
- @param password: an optional password to use to decrypt the key, if it's
- encrypted
- @type password: str
- @return: a new key object based on the given private key
- @rtype: L{PKey}
+ :param file file_obj: the file to read from
+ :param str password:
+ an optional password to use to decrypt the key, if it's encrypted
+ :return: a new `.PKey` based on the given private key
- @raise IOError: if there was an error reading the key
- @raise PasswordRequiredException: if the private key file is encrypted,
- and C{password} is C{None}
- @raise SSHException: if the key file is invalid
+ :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
"""
key = cls(file_obj=file_obj, password=password)
return key
@@ -232,55 +208,49 @@ class PKey (object):
def write_private_key_file(self, filename, password=None):
"""
Write private key contents into a file. If the password is not
- C{None}, the key is encrypted before writing.
+ ``None``, the key is encrypted before writing.
- @param filename: name of the file to write
- @type filename: str
- @param password: an optional password to use to encrypt the key file
- @type password: str
+ :param str filename: name of the file to write
+ :param str password:
+ an optional password to use to encrypt the key file
- @raise IOError: if there was an error writing the file
- @raise 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')
def write_private_key(self, file_obj, password=None):
"""
Write private key contents into a file (or file-like) object. If the
- password is not C{None}, the key is encrypted before writing.
+ password is not ``None``, the key is encrypted before writing.
- @param file_obj: the file object to write into
- @type file_obj: file
- @param password: an optional password to use to encrypt the key
- @type password: str
+ :param file file_obj: the file object to write into
+ :param str password: an optional password to use to encrypt the key
- @raise IOError: if there was an error writing to the file
- @raise 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')
def _read_private_key_file(self, tag, filename, password=None):
"""
Read an SSH2-format private key file, looking for a string of the type
- C{"BEGIN xxx PRIVATE KEY"} for some C{xxx}, base64-decode the text we
+ ``"BEGIN xxx PRIVATE KEY"`` for some ``xxx``, base64-decode the text we
find, and return it as a string. If the private key is encrypted and
- C{password} is not C{None}, the given password will be used to decrypt
- the key (otherwise L{PasswordRequiredException} is thrown).
-
- @param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block.
- @type tag: str
- @param filename: name of the file to read.
- @type filename: str
- @param password: an optional password to use to decrypt the key file,
- if it's encrypted.
- @type password: str
- @return: data blob that makes up the private key.
- @rtype: str
-
- @raise IOError: if there was an error reading the file.
- @raise PasswordRequiredException: if the private key file is
- encrypted, and C{password} is C{None}.
- @raise SSHException: if the key file is invalid.
+ ``password`` is not ``None``, the given password will be used to decrypt
+ the key (otherwise `.PasswordRequiredException` is thrown).
+
+ :param str tag: ``"RSA"`` or ``"DSA"``, the tag used to mark the data block.
+ :param str filename: name of the file to read.
+ :param str password:
+ an optional password to use to decrypt the key file, if it's
+ 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
+ encrypted, and ``password`` is ``None``.
+ :raises SSHException: if the key file is invalid.
"""
with open(filename, 'r') as f:
data = self._read_private_key(tag, f, password)
@@ -340,16 +310,12 @@ class PKey (object):
a trivially-encoded format (base64) which is completely insecure. If
a password is given, DES-EDE3-CBC is used.
- @param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block.
- @type tag: str
- @param filename: name of the file to write.
- @type filename: str
- @param data: data blob that makes up the private key.
- @type data: str
- @param password: an optional password to use to encrypt the file.
- @type password: str
+ :param str tag: ``"RSA"`` or ``"DSA"``, the tag used to mark the data block.
+ :param file filename: name of the file to write.
+ :param str data: data blob that makes up the private key.
+ :param str password: an optional password to use to encrypt the file.
- @raise IOError: if there was an error writing the file.
+ :raises IOError: if there was an error writing the file.
"""
with open(filename, 'w', o600) as f:
# grrr... the mode doesn't always take hold
@@ -379,7 +345,7 @@ class PKey (object):
s = u(encodebytes(data))
# re-wrap to 64-char lines
s = ''.join(s.split('\n'))
- s = '\n'.join([s[i : i+64] for i in range(0, len(s), 64)])
+ s = '\n'.join([s[i: i + 64] for i in range(0, len(s), 64)])
f.write(s)
f.write('\n')
f.write('-----END %s PRIVATE KEY-----\n' % tag)
diff --git a/paramiko/primes.py b/paramiko/primes.py
index d10a15fb..7d3b6a3e 100644
--- a/paramiko/primes.py
+++ b/paramiko/primes.py
@@ -23,16 +23,17 @@ Utility functions for dealing with primes.
from Crypto.Util import number
from paramiko import util
+from paramiko.py3compat import byte_mask, long
from paramiko.ssh_exception import SSHException
from paramiko.common import *
def _generate_prime(bits, rng):
- "primtive attempt at prime generation"
+ """primtive attempt at prime generation"""
hbyte_mask = pow(2, bits % 8) - 1
while True:
# loop catches the case where we increment n into a higher bit-range
- x = rng.read((bits+7) // 8)
+ x = rng.read((bits + 7) // 8)
if hbyte_mask > 0:
x = byte_mask(x[0], hbyte_mask) + x[1:]
n = util.inflate_long(x, 1)
@@ -44,9 +45,10 @@ def _generate_prime(bits, rng):
break
return n
+
def _roll_random(rng, n):
- "returns a random # from 0 to N-1"
- bits = util.bit_length(n-1)
+ """returns a random # from 0 to N-1"""
+ bits = util.bit_length(n - 1)
byte_count = (bits + 7) // 8
hbyte_mask = pow(2, bits % 8) - 1
@@ -110,7 +112,7 @@ class ModulusPack (object):
def read_file(self, filename):
"""
- @raise IOError: passed from any file operations that fail.
+ :raises IOError: passed from any file operations that fail.
"""
self.pack = {}
with open(filename, 'r') as f:
@@ -130,7 +132,7 @@ class ModulusPack (object):
good = -1
# find nearest bitsize >= preferred
for b in bitsizes:
- if (b >= prefer) and (b < max) and ((b < good) or (good == -1)):
+ if (b >= prefer) and (b < max) and (b < good or good == -1):
good = b
# if that failed, find greatest bitsize >= min
if good == -1:
diff --git a/paramiko/proxy.py b/paramiko/proxy.py
index 72927c04..8959b244 100644
--- a/paramiko/proxy.py
+++ b/paramiko/proxy.py
@@ -16,15 +16,14 @@
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-"""
-L{ProxyCommand}.
-"""
+from datetime import datetime
import os
-import sys
from shlex import split as shlsplit
import signal
from subprocess import Popen, PIPE
+from select import select
+import socket
from paramiko.ssh_exception import ProxyCommandFailure
@@ -34,29 +33,29 @@ class ProxyCommand(object):
Wraps a subprocess running ProxyCommand-driven programs.
This class implements a the socket-like interface needed by the
- L{Transport} and L{Packetizer} classes. Using this class instead of a
+ `.Transport` and `.Packetizer` classes. Using this class instead of a
regular socket makes it possible to talk with a Popen'd command that will
proxy traffic between the client and a server hosted in another machine.
"""
def __init__(self, command_line):
"""
Create a new CommandProxy instance. The instance created by this
- class can be passed as an argument to the L{Transport} class.
+ class can be passed as an argument to the `.Transport` class.
- @param command_line: the command that should be executed and
- used as the proxy.
- @type command_line: str
+ :param str command_line:
+ the command that should be executed and used as the proxy.
"""
self.cmd = shlsplit(command_line)
self.process = Popen(self.cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ self.timeout = None
+ self.buffer = []
def send(self, content):
"""
Write the content received from the SSH client to the standard
input of the forked command.
- @param content: string to be sent to the forked command
- @type content: str
+ :param str content: string to be sent to the forked command
"""
try:
self.process.stdin.write(content)
@@ -72,14 +71,30 @@ class ProxyCommand(object):
"""
Read from the standard output of the forked program.
- @param size: how many chars should be read
- @type size: int
+ :param int size: how many chars should be read
- @return: the length of the read content
- @rtype: int
+ :return: the length of the read content, as an `int`
"""
try:
- return os.read(self.process.stdout.fileno(), size)
+ start = datetime.now()
+ while len(self.buffer) < size:
+ if self.timeout is not None:
+ elapsed = (datetime.now() - start).microseconds
+ timeout = self.timeout * 1000 * 1000 # to microseconds
+ if elapsed >= timeout:
+ raise socket.timeout()
+ r, w, x = select([self.process.stdout], [], [], 0.0)
+ if r and r[0] == self.process.stdout:
+ b = os.read(self.process.stdout.fileno(), 1)
+ # Store in class-level buffer for persistence across
+ # timeouts; this makes us act more like a real socket
+ # (where timeouts don't actually drop data.)
+ self.buffer.append(b)
+ result = ''.join(self.buffer)
+ self.buffer = []
+ return result
+ except socket.timeout:
+ raise # socket.timeout is a subclass of IOError
except IOError as e:
raise ProxyCommandFailure(' '.join(self.cmd), e.strerror)
@@ -87,6 +102,4 @@ class ProxyCommand(object):
os.kill(self.process.pid, signal.SIGTERM)
def settimeout(self, timeout):
- # Timeouts are meaningless for this implementation, but are part of the
- # spec, so must be present.
- pass
+ self.timeout = timeout
diff --git a/paramiko/py3compat.py b/paramiko/py3compat.py
index 22285992..2fae079b 100644
--- a/paramiko/py3compat.py
+++ b/paramiko/py3compat.py
@@ -113,7 +113,13 @@ else:
return s
def byte_ord(c):
+<<<<<<< HEAD
assert isinstance(c, int)
+=======
+ # In case we're handed a string instead of an int.
+ if not isinstance(c, int):
+ c = ord(c)
+>>>>>>> master
return c
def byte_chr(c):
diff --git a/paramiko/resource.py b/paramiko/resource.py
index 6ef86d8c..9809afbe 100644
--- a/paramiko/resource.py
+++ b/paramiko/resource.py
@@ -28,13 +28,13 @@ 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 C{__del__} method,
+ 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 L{register}, and when an object is garbage
- collected, each registered resource is closed by having its C{close()}
+ 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.)
@@ -47,14 +47,13 @@ class ResourceManager (object):
"""
Register a resource to be closed with an object is collected.
- When the given C{obj} is garbage-collected by the python interpreter,
- the C{resource} will be closed by having its C{close()} method called.
+ 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 obj: the object to track
- @type obj: object
- @param resource: the resource to close when the object is collected
- @type resource: object
+ :param object obj: the object to track
+ :param object resource:
+ the resource to close when the object is collected
"""
def callback(ref):
try:
diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py
index 65f1463d..c93f3218 100644
--- a/paramiko/rsakey.py
+++ b/paramiko/rsakey.py
@@ -17,18 +17,18 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-L{RSAKey}
+RSA keys.
"""
from Crypto.PublicKey import RSA
-from Crypto.Hash import SHA, MD5
-from Crypto.Cipher import DES3
+from Crypto.Hash import SHA
-from paramiko.common import *
from paramiko import util
+from paramiko.common import rng, max_byte, zero_byte, one_byte
from paramiko.message import Message
from paramiko.ber import BER, BERException
from paramiko.pkey import PKey
+from paramiko.py3compat import long
from paramiko.ssh_exception import SSHException
SHA1_DIGESTINFO = b'\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'
@@ -113,9 +113,9 @@ class RSAKey (PKey):
def _encode_key(self):
if (self.p is None) or (self.q is None):
raise SSHException('Not enough key info to write private key file')
- keylist = [ 0, self.n, self.e, self.d, self.p, self.q,
- self.d % (self.p - 1), self.d % (self.q - 1),
- util.mod_inverse(self.q, self.p) ]
+ keylist = [0, self.n, self.e, self.d, self.p, self.q,
+ self.d % (self.p - 1), self.d % (self.q - 1),
+ util.mod_inverse(self.q, self.p)]
try:
b = BER()
b.encode(keylist)
@@ -134,13 +134,11 @@ class RSAKey (PKey):
Generate a new private RSA key. This factory function can be used to
generate a new host key or authentication key.
- @param bits: number of bits the generated key should be.
- @type bits: int
- @param progress_func: an optional function to call at key points in
- key generation (used by C{pyCrypto.PublicKey}).
- @type progress_func: function
- @return: new private key
- @rtype: L{RSAKey}
+ :param int bits: number of bits the generated key should be.
+ :param function progress_func:
+ an optional function to call at key points in key generation (used
+ by ``pyCrypto.PublicKey``).
+ :return: new `.RSAKey` private key
"""
rsa = RSA.generate(bits, rng.read, progress_func)
key = RSAKey(vals=(rsa.e, rsa.n))
@@ -150,10 +148,8 @@ class RSAKey (PKey):
return key
generate = staticmethod(generate)
-
### internals...
-
def _pkcs1imify(self, data):
"""
turn a 20-byte SHA1 hash into a blob of data as large as the key's N,
diff --git a/paramiko/server.py b/paramiko/server.py
index 38decf5b..25c39063 100644
--- a/paramiko/server.py
+++ b/paramiko/server.py
@@ -17,62 +17,21 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-L{ServerInterface} is an interface to override for server support.
+`.ServerInterface` is an interface to override for server support.
"""
import threading
-from paramiko.common import *
from paramiko import util
-
-
-class InteractiveQuery (object):
- """
- A query (set of prompts) for a user during interactive authentication.
- """
-
- def __init__(self, name='', instructions='', *prompts):
- """
- Create a new interactive query to send to the client. The name and
- instructions are optional, but are generally displayed to the end
- user. A list of prompts may be included, or they may be added via
- the L{add_prompt} method.
-
- @param name: name of this query
- @type name: str
- @param instructions: user instructions (usually short) about this query
- @type instructions: str
- @param prompts: one or more authentication prompts
- @type prompts: str
- """
- self.name = name
- self.instructions = instructions
- self.prompts = []
- for x in prompts:
- if isinstance(x, string_types):
- self.add_prompt(x)
- else:
- self.add_prompt(x[0], x[1])
-
- def add_prompt(self, prompt, echo=True):
- """
- Add a prompt to this query. The prompt should be a (reasonably short)
- string. Multiple prompts can be added to the same query.
-
- @param prompt: the user prompt
- @type prompt: str
- @param echo: C{True} (default) if the user's response should be echoed;
- C{False} if not (for a password or similar)
- @type echo: bool
- """
- self.prompts.append((prompt, echo))
+from paramiko.common import DEBUG, ERROR, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, AUTH_FAILED
+from paramiko.py3compat import string_types
class ServerInterface (object):
"""
- This class defines an interface for controlling the behavior of paramiko
+ This class defines an interface for controlling the behavior of Paramiko
in server mode.
- Methods on this class are called from paramiko's primary thread, so you
+ Methods on this class are called from Paramiko's primary thread, so you
shouldn't do too much work in them. (Certainly nothing that blocks or
sleeps.)
"""
@@ -80,7 +39,7 @@ class ServerInterface (object):
def check_channel_request(self, kind, chanid):
"""
Determine if a channel request of a given type will be granted, and
- return C{OPEN_SUCCEEDED} or an error code. This method is
+ return ``OPEN_SUCCEEDED`` or an error code. This method is
called in server mode when the client requests a channel, after
authentication is complete.
@@ -88,37 +47,37 @@ class ServerInterface (object):
useless), you should also override some of the channel request methods
below, which are used to determine which services will be allowed on
a given channel:
- - L{check_channel_pty_request}
- - L{check_channel_shell_request}
- - L{check_channel_subsystem_request}
- - L{check_channel_window_change_request}
- - L{check_channel_x11_request}
- - L{check_channel_forward_agent_request}
-
- The C{chanid} parameter is a small number that uniquely identifies the
- channel within a L{Transport}. A L{Channel} object is not created
- unless this method returns C{OPEN_SUCCEEDED} -- once a
- L{Channel} object is created, you can call L{Channel.get_id} to
+
+ - `check_channel_pty_request`
+ - `check_channel_shell_request`
+ - `check_channel_subsystem_request`
+ - `check_channel_window_change_request`
+ - `check_channel_x11_request`
+ - `check_channel_forward_agent_request`
+
+ The ``chanid`` parameter is a small number that uniquely identifies the
+ channel within a `.Transport`. A `.Channel` object is not created
+ unless this method returns ``OPEN_SUCCEEDED`` -- once a
+ `.Channel` object is created, you can call `.Channel.get_id` to
retrieve the channel ID.
- The return value should either be C{OPEN_SUCCEEDED} (or
- C{0}) to allow the channel request, or one of the following error
+ The return value should either be ``OPEN_SUCCEEDED`` (or
+ ``0``) to allow the channel request, or one of the following error
codes to reject it:
- - C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}
- - C{OPEN_FAILED_CONNECT_FAILED}
- - C{OPEN_FAILED_UNKNOWN_CHANNEL_TYPE}
- - C{OPEN_FAILED_RESOURCE_SHORTAGE}
+
+ - ``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``
+ - ``OPEN_FAILED_CONNECT_FAILED``
+ - ``OPEN_FAILED_UNKNOWN_CHANNEL_TYPE``
+ - ``OPEN_FAILED_RESOURCE_SHORTAGE``
The default implementation always returns
- C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}.
+ ``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``.
- @param kind: the kind of channel the client would like to open
- (usually C{"session"}).
- @type kind: str
- @param chanid: ID of the channel
- @type chanid: int
- @return: a success or failure code (listed above)
- @rtype: int
+ :param str kind:
+ the kind of channel the client would like to open (usually
+ ``"session"``).
+ :param int chanid: ID of the channel
+ :return: an `int` success or failure code (listed above)
"""
return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
@@ -129,15 +88,13 @@ class ServerInterface (object):
of authentication methods that might be successful.
The "list" is actually a string of comma-separated names of types of
- authentication. Possible values are C{"password"}, C{"publickey"},
- and C{"none"}.
+ authentication. Possible values are ``"password"``, ``"publickey"``,
+ and ``"none"``.
- The default implementation always returns C{"password"}.
+ The default implementation always returns ``"password"``.
- @param username: the username requesting authentication.
- @type username: str
- @return: a comma-separated list of authentication types
- @rtype: str
+ :param str username: the username requesting authentication.
+ :return: a comma-separated `str` of authentication types
"""
return 'password'
@@ -146,17 +103,17 @@ class ServerInterface (object):
Determine if a client may open channels with no (further)
authentication.
- Return L{AUTH_FAILED} if the client must authenticate, or
- L{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 L{AUTH_FAILED}.
+ The default implementation always returns `.AUTH_FAILED`.
- @param username: the username of the client.
- @type username: str
- @return: L{AUTH_FAILED} if the authentication fails;
- L{AUTH_SUCCESSFUL} if it succeeds.
- @rtype: int
+ :param str username: the username of the client.
+ :return:
+ `.AUTH_FAILED` if the authentication fails; `.AUTH_SUCCESSFUL` if
+ it succeeds.
+ :rtype: int
"""
return AUTH_FAILED
@@ -165,25 +122,23 @@ class ServerInterface (object):
Determine if a given username and password supplied by the client is
acceptable for use in authentication.
- Return L{AUTH_FAILED} if the password is not accepted,
- L{AUTH_SUCCESSFUL} if the password is accepted and completes
- the authentication, or L{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, L{get_allowed_auths} will be called to report to the client what
+ 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 L{AUTH_FAILED}.
+ The default implementation always returns `.AUTH_FAILED`.
- @param username: the username of the authenticating client.
- @type username: str
- @param password: the password given by the client.
- @type password: str
- @return: L{AUTH_FAILED} if the authentication fails;
- L{AUTH_SUCCESSFUL} if it succeeds;
- L{AUTH_PARTIALLY_SUCCESSFUL} if the password auth is
+ :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
successful, but authentication must continue.
- @rtype: int
+ :rtype: int
"""
return AUTH_FAILED
@@ -194,29 +149,28 @@ class ServerInterface (object):
check the username and key and decide if you would accept a signature
made using this key.
- Return L{AUTH_FAILED} if the key is not accepted,
- L{AUTH_SUCCESSFUL} if the key is accepted and completes the
- authentication, or L{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, L{get_allowed_auths} will be called to report to the client what
+ case, `get_allowed_auths` will be called to report to the client what
options it has for continuing the authentication.)
Note that you don't have to actually verify any key signtature here.
- If you're willing to accept the key, paramiko will do the work of
+ If you're willing to accept the key, Paramiko will do the work of
verifying the client's signature.
- The default implementation always returns L{AUTH_FAILED}.
-
- @param username: the username of the authenticating client
- @type username: str
- @param key: the key object provided by the client
- @type key: L{PKey <pkey.PKey>}
- @return: L{AUTH_FAILED} if the client can't authenticate
- with this key; L{AUTH_SUCCESSFUL} if it can;
- L{AUTH_PARTIALLY_SUCCESSFUL} if it can authenticate with
- this key but must continue with authentication
- @rtype: int
+ 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
+ can authenticate with this key but must continue with
+ authentication
+ :rtype: int
"""
return AUTH_FAILED
@@ -224,24 +178,24 @@ class ServerInterface (object):
"""
Begin an interactive authentication challenge, if supported. You
should override this method in server mode if you want to support the
- C{"keyboard-interactive"} auth type, which requires you to send a
+ ``"keyboard-interactive"`` auth type, which requires you to send a
series of questions for the client to answer.
- Return L{AUTH_FAILED} if this auth method isn't supported. Otherwise,
- you should return an L{InteractiveQuery} object containing the prompts
+ 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 L{check_auth_interactive_response}.
+ to `check_auth_interactive_response`.
- The default implementation always returns L{AUTH_FAILED}.
+ The default implementation always returns `.AUTH_FAILED`.
- @param username: the username of the authenticating client
- @type username: str
- @param submethods: a comma-separated list of methods preferred by the
- client (usually empty)
- @type submethods: str
- @return: L{AUTH_FAILED} if this auth method isn't supported; otherwise
- an object containing queries for the user
- @rtype: int or L{InteractiveQuery}
+ :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
+ object containing queries for the user
+ :rtype: int or `.InteractiveQuery`
"""
return AUTH_FAILED
@@ -249,31 +203,30 @@ class ServerInterface (object):
"""
Continue or finish an interactive authentication challenge, if
supported. You should override this method in server mode if you want
- to support the C{"keyboard-interactive"} auth type.
+ to support the ``"keyboard-interactive"`` auth type.
- Return L{AUTH_FAILED} if the responses are not accepted,
- L{AUTH_SUCCESSFUL} if the responses are accepted and complete
- the authentication, or L{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, L{get_allowed_auths} will be called to report to the client what
+ case, `get_allowed_auths` will be called to report to the client what
options it has for continuing the authentication.)
If you wish to continue interactive authentication with more questions,
- you may return an L{InteractiveQuery} object, which should cause the
+ you may return an `.InteractiveQuery` object, which should cause the
client to respond with more answers, calling this method again. This
cycle can continue indefinitely.
- The default implementation always returns L{AUTH_FAILED}.
+ The default implementation always returns `.AUTH_FAILED`.
- @param responses: list of responses from the client
- @type responses: list(str)
- @return: L{AUTH_FAILED} if the authentication fails;
- L{AUTH_SUCCESSFUL} if it succeeds;
- L{AUTH_PARTIALLY_SUCCESSFUL} if the interactive auth is
- successful, but authentication must continue; otherwise an object
- containing queries for the user
- @rtype: int or L{InteractiveQuery}
+ :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
+ is successful, but authentication must continue; otherwise an
+ object containing queries for the user
+ :rtype: int or `.InteractiveQuery`
"""
return AUTH_FAILED
@@ -361,22 +314,20 @@ class ServerInterface (object):
"""
Handle a request for port forwarding. The client is asking that
connections to the given address and port be forwarded back across
- this ssh connection. An address of C{"0.0.0.0"} indicates a global
- address (any address associated with this server) and a port of C{0}
+ this ssh connection. An address of ``"0.0.0.0"`` indicates a global
+ address (any address associated with this server) and a port of ``0``
indicates that no specific port is requested (usually the OS will pick
a port).
- The default implementation always returns C{False}, rejecting the
+ The default implementation always returns ``False``, rejecting the
port forwarding request. If the request is accepted, you should return
the port opened for listening.
- @param address: the requested address
- @type address: str
- @param port: the requested port
- @type port: int
- @return: the port number that was opened for listening, or C{False} to
- reject
- @rtype: int
+ :param str address: the requested address
+ :param int port: the requested port
+ :return:
+ the port number (`int`) that was opened for listening, or ``False``
+ to reject
"""
return False
@@ -386,19 +337,17 @@ class ServerInterface (object):
If the given address and port is being forwarded across this ssh
connection, the port should be closed.
- @param address: the forwarded address
- @type address: str
- @param port: the forwarded port
- @type port: int
+ :param str address: the forwarded address
+ :param int port: the forwarded port
"""
pass
def check_global_request(self, kind, msg):
"""
- Handle a global request of the given C{kind}. This method is called
+ Handle a global request of the given ``kind``. This method is called
in server mode and client mode, whenever the remote host makes a global
request. If there are any arguments to the request, they will be in
- C{msg}.
+ ``msg``.
There aren't any useful global requests defined, aside from port
forwarding, so usually this type of request is an extension to the
@@ -409,115 +358,100 @@ class ServerInterface (object):
sent back with the successful result. (Note that the items in the
tuple can only be strings, ints, longs, or bools.)
- The default implementation always returns C{False}, indicating that it
+ The default implementation always returns ``False``, indicating that it
does not support any global requests.
- @note: Port forwarding requests are handled separately, in
- L{check_port_forward_request}.
-
- @param kind: the kind of global request being made.
- @type kind: str
- @param msg: any extra arguments to the request.
- @type msg: L{Message}
- @return: C{True} or a tuple of data if the request was granted;
- C{False} otherwise.
- @rtype: bool
+ .. note:: Port forwarding requests are handled separately, in
+ `check_port_forward_request`.
+
+ :param str kind: the kind of global request being made.
+ :param .Message msg: any extra arguments to the request.
+ :return:
+ ``True`` or a `tuple` of data if the request was granted; ``False``
+ otherwise.
"""
return False
-
### Channel requests
-
def check_channel_pty_request(self, channel, term, width, height, pixelwidth, pixelheight,
modes):
"""
Determine if a pseudo-terminal of the given dimensions (usually
requested for shell access) can be provided on the given channel.
- The default implementation always returns C{False}.
-
- @param channel: the L{Channel} the pty request arrived on.
- @type channel: L{Channel}
- @param term: type of terminal requested (for example, C{"vt100"}).
- @type term: str
- @param width: width of screen in characters.
- @type width: int
- @param height: height of screen in characters.
- @type height: int
- @param pixelwidth: width of screen in pixels, if known (may be C{0} if
- unknown).
- @type pixelwidth: int
- @param pixelheight: height of screen in pixels, if known (may be C{0}
- if unknown).
- @type pixelheight: int
- @return: C{True} if the psuedo-terminal has been allocated; C{False}
+ The default implementation always returns ``False``.
+
+ :param .Channel channel: the `.Channel` the pty request arrived on.
+ :param str term: type of terminal requested (for example, ``"vt100"``).
+ :param int width: width of screen in characters.
+ :param int height: height of screen in characters.
+ :param int pixelwidth:
+ width of screen in pixels, if known (may be ``0`` if unknown).
+ :param int pixelheight:
+ height of screen in pixels, if known (may be ``0`` if unknown).
+ :return:
+ ``True`` if the psuedo-terminal has been allocated; ``False``
otherwise.
- @rtype: bool
"""
return False
def check_channel_shell_request(self, channel):
"""
Determine if a shell will be provided to the client on the given
- channel. If this method returns C{True}, the channel should be
+ channel. If this method returns ``True``, the channel should be
connected to the stdin/stdout of a shell (or something that acts like
a shell).
- The default implementation always returns C{False}.
+ The default implementation always returns ``False``.
- @param channel: the L{Channel} the request arrived on.
- @type channel: L{Channel}
- @return: C{True} if this channel is now hooked up to a shell; C{False}
- if a shell can't or won't be provided.
- @rtype: bool
+ :param .Channel channel: the `.Channel` the request arrived on.
+ :return:
+ ``True`` if this channel is now hooked up to a shell; ``False`` if
+ a shell can't or won't be provided.
"""
return False
def check_channel_exec_request(self, channel, command):
"""
Determine if a shell command will be executed for the client. If this
- method returns C{True}, the channel should be connected to the stdin,
+ method returns ``True``, the channel should be connected to the stdin,
stdout, and stderr of the shell command.
- The default implementation always returns C{False}.
+ The default implementation always returns ``False``.
- @param channel: the L{Channel} the request arrived on.
- @type channel: L{Channel}
- @param command: the command to execute.
- @type command: str
- @return: C{True} if this channel is now hooked up to the stdin,
- stdout, and stderr of the executing command; C{False} if the
- command will not be executed.
- @rtype: bool
+ :param .Channel channel: the `.Channel` the request arrived on.
+ :param str command: the command to execute.
+ :return:
+ ``True`` if this channel is now hooked up to the stdin, stdout, and
+ stderr of the executing command; ``False`` if the command will not
+ be executed.
- @since: 1.1
+ .. versionadded:: 1.1
"""
return False
def check_channel_subsystem_request(self, channel, name):
"""
Determine if a requested subsystem will be provided to the client on
- the given channel. If this method returns C{True}, all future I/O
+ the given channel. If this method returns ``True``, all future I/O
through this channel will be assumed to be connected to the requested
- subsystem. An example of a subsystem is C{sftp}.
+ subsystem. An example of a subsystem is ``sftp``.
The default implementation checks for a subsystem handler assigned via
- L{Transport.set_subsystem_handler}.
+ `.Transport.set_subsystem_handler`.
If one has been set, the handler is invoked and this method returns
- C{True}. Otherwise it returns C{False}.
+ ``True``. Otherwise it returns ``False``.
- @note: Because the default implementation uses the L{Transport} to
+ .. note:: Because the default implementation uses the `.Transport` to
identify valid subsystems, you probably won't need to override this
method.
- @param channel: the L{Channel} the pty request arrived on.
- @type channel: L{Channel}
- @param name: name of the requested subsystem.
- @type name: str
- @return: C{True} if this channel is now hooked up to the requested
- subsystem; C{False} if that subsystem can't or won't be provided.
- @rtype: bool
+ :param .Channel channel: the `.Channel` the pty request arrived on.
+ :param str name: name of the requested subsystem.
+ :return:
+ ``True`` if this channel is now hooked up to the requested
+ subsystem; ``False`` if that subsystem can't or won't be provided.
"""
handler_class, larg, kwarg = channel.get_transport()._get_subsystem_handler(name)
if handler_class is None:
@@ -531,102 +465,88 @@ class ServerInterface (object):
Determine if the pseudo-terminal on the given channel can be resized.
This only makes sense if a pty was previously allocated on it.
- The default implementation always returns C{False}.
+ The default implementation always returns ``False``.
- @param channel: the L{Channel} the pty request arrived on.
- @type channel: L{Channel}
- @param width: width of screen in characters.
- @type width: int
- @param height: height of screen in characters.
- @type height: int
- @param pixelwidth: width of screen in pixels, if known (may be C{0} if
- unknown).
- @type pixelwidth: int
- @param pixelheight: height of screen in pixels, if known (may be C{0}
- if unknown).
- @type pixelheight: int
- @return: C{True} if the terminal was resized; C{False} if not.
- @rtype: bool
+ :param .Channel channel: the `.Channel` the pty request arrived on.
+ :param int width: width of screen in characters.
+ :param int height: height of screen in characters.
+ :param int pixelwidth:
+ width of screen in pixels, if known (may be ``0`` if unknown).
+ :param int pixelheight:
+ height of screen in pixels, if known (may be ``0`` if unknown).
+ :return: ``True`` if the terminal was resized; ``False`` if not.
"""
return False
def check_channel_x11_request(self, channel, single_connection, auth_protocol, auth_cookie, screen_number):
"""
Determine if the client will be provided with an X11 session. If this
- method returns C{True}, X11 applications should be routed through new
- SSH channels, using L{Transport.open_x11_channel}.
+ method returns ``True``, X11 applications should be routed through new
+ SSH channels, using `.Transport.open_x11_channel`.
- The default implementation always returns C{False}.
+ The default implementation always returns ``False``.
- @param channel: the L{Channel} the X11 request arrived on
- @type channel: L{Channel}
- @param single_connection: C{True} if only a single X11 channel should
- be opened
- @type single_connection: bool
- @param auth_protocol: the protocol used for X11 authentication
- @type auth_protocol: str
- @param auth_cookie: the cookie used to authenticate to X11
- @type auth_cookie: str
- @param screen_number: the number of the X11 screen to connect to
- @type screen_number: int
- @return: C{True} if the X11 session was opened; C{False} if not
- @rtype: bool
+ :param .Channel channel: the `.Channel` the X11 request arrived on
+ :param bool single_connection:
+ ``True`` if only a single X11 channel should be opened, else
+ ``False``.
+ :param str auth_protocol: the protocol used for X11 authentication
+ :param str auth_cookie: the cookie used to authenticate to X11
+ :param int screen_number: the number of the X11 screen to connect to
+ :return: ``True`` if the X11 session was opened; ``False`` if not
"""
return False
def check_channel_forward_agent_request(self, channel):
"""
Determine if the client will be provided with an forward agent session.
- If this method returns C{True}, the server will allow SSH Agent
+ If this method returns ``True``, the server will allow SSH Agent
forwarding.
- The default implementation always returns C{False}.
+ The default implementation always returns ``False``.
- @param channel: the L{Channel} the request arrived on
- @type channel: L{Channel}
- @return: C{True} if the AgentForward was loaded; C{False} if not
- @rtype: bool
+ :param .Channel channel: the `.Channel` the request arrived on
+ :return: ``True`` if the AgentForward was loaded; ``False`` if not
"""
return False
def check_channel_direct_tcpip_request(self, chanid, origin, destination):
"""
Determine if a local port forwarding channel will be granted, and
- return C{OPEN_SUCCEEDED} or an error code. This method is
+ return ``OPEN_SUCCEEDED`` or an error code. This method is
called in server mode when the client requests a channel, after
authentication is complete.
- The C{chanid} parameter is a small number that uniquely identifies the
- channel within a L{Transport}. A L{Channel} object is not created
- unless this method returns C{OPEN_SUCCEEDED} -- once a
- L{Channel} object is created, you can call L{Channel.get_id} to
+ The ``chanid`` parameter is a small number that uniquely identifies the
+ channel within a `.Transport`. A `.Channel` object is not created
+ unless this method returns ``OPEN_SUCCEEDED`` -- once a
+ `.Channel` object is created, you can call `.Channel.get_id` to
retrieve the channel ID.
The origin and destination parameters are (ip_address, port) tuples
that correspond to both ends of the TCP connection in the forwarding
tunnel.
- The return value should either be C{OPEN_SUCCEEDED} (or
- C{0}) to allow the channel request, or one of the following error
+ The return value should either be ``OPEN_SUCCEEDED`` (or
+ ``0``) to allow the channel request, or one of the following error
codes to reject it:
- - C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}
- - C{OPEN_FAILED_CONNECT_FAILED}
- - C{OPEN_FAILED_UNKNOWN_CHANNEL_TYPE}
- - C{OPEN_FAILED_RESOURCE_SHORTAGE}
+
+ - ``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``
+ - ``OPEN_FAILED_CONNECT_FAILED``
+ - ``OPEN_FAILED_UNKNOWN_CHANNEL_TYPE``
+ - ``OPEN_FAILED_RESOURCE_SHORTAGE``
The default implementation always returns
- C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}.
-
- @param chanid: ID of the channel
- @type chanid: int
- @param origin: 2-tuple containing the IP address and port of the
- originator (client side)
- @type origin: tuple
- @param destination: 2-tuple containing the IP address and port of the
- destination (server side)
- @type destination: tuple
- @return: a success or failure code (listed above)
- @rtype: int
+ ``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``.
+
+ :param int chanid: ID of the channel
+ :param tuple origin:
+ 2-tuple containing the IP address and port of the originator
+ (client side)
+ :param tuple destination:
+ 2-tuple containing the IP address and port of the destination
+ (server side)
+ :return: an `int` success or failure code (listed above)
"""
return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
@@ -652,37 +572,71 @@ class ServerInterface (object):
return False
+class InteractiveQuery (object):
+ """
+ A query (set of prompts) for a user during interactive authentication.
+ """
+
+ def __init__(self, name='', instructions='', *prompts):
+ """
+ Create a new interactive query to send to the client. The name and
+ instructions are optional, but are generally displayed to the end
+ user. A list of prompts may be included, or they may be added via
+ the `add_prompt` method.
+
+ :param str name: name of this query
+ :param str instructions:
+ user instructions (usually short) about this query
+ :param str prompts: one or more authentication prompts
+ """
+ self.name = name
+ self.instructions = instructions
+ self.prompts = []
+ for x in prompts:
+ if isinstance(x, string_types):
+ self.add_prompt(x)
+ else:
+ self.add_prompt(x[0], x[1])
+
+ def add_prompt(self, prompt, echo=True):
+ """
+ Add a prompt to this query. The prompt should be a (reasonably short)
+ string. Multiple prompts can be added to the same query.
+
+ :param str prompt: the user prompt
+ :param bool echo:
+ ``True`` (default) if the user's response should be echoed;
+ ``False`` if not (for a password or similar)
+ """
+ self.prompts.append((prompt, echo))
+
+
class SubsystemHandler (threading.Thread):
"""
Handler for a subsytem in server mode. If you create a subclass of this
- class and pass it to
- L{Transport.set_subsystem_handler},
- an object of this
+ class and pass it to `.Transport.set_subsystem_handler`, an object of this
class will be created for each request for this subsystem. Each new object
- will be executed within its own new thread by calling L{start_subsystem}.
+ will be executed within its own new thread by calling `start_subsystem`.
When that method completes, the channel is closed.
- For example, if you made a subclass C{MP3Handler} and registered it as the
- handler for subsystem C{"mp3"}, then whenever a client has successfully
- authenticated and requests subsytem C{"mp3"}, an object of class
- C{MP3Handler} will be created, and L{start_subsystem} will be called on
+ For example, if you made a subclass ``MP3Handler`` and registered it as the
+ handler for subsystem ``"mp3"``, then whenever a client has successfully
+ authenticated and requests subsytem ``"mp3"``, an object of class
+ ``MP3Handler`` will be created, and `start_subsystem` will be called on
it from a new thread.
"""
def __init__(self, channel, name, server):
"""
- Create a new handler for a channel. This is used by L{ServerInterface}
+ Create a new handler for a channel. This is used by `.ServerInterface`
to start up a new handler when a channel requests this subsystem. You
don't need to override this method, but if you do, be sure to pass the
- C{channel} and C{name} parameters through to the original C{__init__}
+ ``channel`` and ``name`` parameters through to the original ``__init__``
method here.
- @param channel: the channel associated with this subsystem request.
- @type channel: L{Channel}
- @param name: name of the requested subsystem.
- @type name: str
- @param server: the server object for the session that started this
- subsystem
- @type server: L{ServerInterface}
+ :param .Channel channel: the channel associated with this subsystem request.
+ :param str name: name of the requested subsystem.
+ :param .ServerInterface server:
+ the server object for the session that started this subsystem
"""
threading.Thread.__init__(self, target=self._run)
self.__channel = channel
@@ -692,10 +646,8 @@ class SubsystemHandler (threading.Thread):
def get_server(self):
"""
- Return the L{ServerInterface} object associated with this channel and
+ Return the `.ServerInterface` object associated with this channel and
subsystem.
-
- @rtype: L{ServerInterface}
"""
return self.__server
@@ -720,22 +672,20 @@ class SubsystemHandler (threading.Thread):
subsystem is finished, this method will return. After this method
returns, the channel is closed.
- The combination of C{transport} and C{channel} are unique; this handler
- corresponds to exactly one L{Channel} on one L{Transport}.
+ The combination of ``transport`` and ``channel`` are unique; this handler
+ corresponds to exactly one `.Channel` on one `.Transport`.
- @note: It is the responsibility of this method to exit if the
- underlying L{Transport} is closed. This can be done by checking
- L{Transport.is_active} or noticing an EOF
- on the L{Channel}. If this method loops forever without checking
- for this case, your python interpreter may refuse to exit because
- this thread will still be running.
+ .. note::
+ It is the responsibility of this method to exit if the underlying
+ `.Transport` is closed. This can be done by checking
+ `.Transport.is_active` or noticing an EOF on the `.Channel`. If
+ this method loops forever without checking for this case, your
+ Python interpreter may refuse to exit because this thread will
+ still be running.
- @param name: name of the requested subsystem.
- @type name: str
- @param transport: the server-mode L{Transport}.
- @type transport: L{Transport}
- @param channel: the channel associated with this subsystem request.
- @type channel: L{Channel}
+ :param str name: name of the requested subsystem.
+ :param .Transport transport: the server-mode `.Transport`.
+ :param .Channel channel: the channel associated with this subsystem request.
"""
pass
@@ -744,6 +694,6 @@ class SubsystemHandler (threading.Thread):
Perform any cleanup at the end of a subsystem. The default
implementation just closes the channel.
- @since: 1.1
+ .. versionadded:: 1.1
"""
self.__channel.close()
diff --git a/paramiko/sftp.py b/paramiko/sftp.py
index 3e05de9f..f44a804d 100644
--- a/paramiko/sftp.py
+++ b/paramiko/sftp.py
@@ -20,32 +20,31 @@ import select
import socket
import struct
-from paramiko.common import *
from paramiko import util
-from paramiko.channel import Channel
+from paramiko.common import asbytes, DEBUG
from paramiko.message import Message
+from paramiko.py3compat import byte_chr, byte_ord
CMD_INIT, CMD_VERSION, CMD_OPEN, CMD_CLOSE, CMD_READ, CMD_WRITE, CMD_LSTAT, CMD_FSTAT, \
- CMD_SETSTAT, CMD_FSETSTAT, CMD_OPENDIR, CMD_READDIR, CMD_REMOVE, CMD_MKDIR, \
- CMD_RMDIR, CMD_REALPATH, CMD_STAT, CMD_RENAME, CMD_READLINK, CMD_SYMLINK \
- = range(1, 21)
+ CMD_SETSTAT, CMD_FSETSTAT, CMD_OPENDIR, CMD_READDIR, CMD_REMOVE, CMD_MKDIR, \
+ CMD_RMDIR, CMD_REALPATH, CMD_STAT, CMD_RENAME, CMD_READLINK, CMD_SYMLINK = range(1, 21)
CMD_STATUS, CMD_HANDLE, CMD_DATA, CMD_NAME, CMD_ATTRS = range(101, 106)
CMD_EXTENDED, CMD_EXTENDED_REPLY = range(200, 202)
SFTP_OK = 0
SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED, SFTP_FAILURE, SFTP_BAD_MESSAGE, \
- SFTP_NO_CONNECTION, SFTP_CONNECTION_LOST, SFTP_OP_UNSUPPORTED = range(1, 9)
-
-SFTP_DESC = [ 'Success',
- 'End of file',
- 'No such file',
- 'Permission denied',
- 'Failure',
- 'Bad message',
- 'No connection',
- 'Connection lost',
- 'Operation unsupported' ]
+ SFTP_NO_CONNECTION, SFTP_CONNECTION_LOST, SFTP_OP_UNSUPPORTED = range(1, 9)
+
+SFTP_DESC = ['Success',
+ 'End of file',
+ 'No such file',
+ 'Permission denied',
+ 'Failure',
+ 'Bad message',
+ 'No connection',
+ 'Connection lost',
+ 'Operation unsupported']
SFTP_FLAG_READ = 0x1
SFTP_FLAG_WRITE = 0x2
@@ -99,10 +98,8 @@ class BaseSFTP (object):
self.sock = None
self.ultra_debug = False
-
### internals...
-
def _send_version(self):
self._send_packet(CMD_INIT, struct.pack('>I', _VERSION))
t, data = self._read_packet()
@@ -121,7 +118,7 @@ class BaseSFTP (object):
raise SFTPError('Incompatible sftp protocol')
version = struct.unpack('>I', data[:4])[0]
# advertise that we support "check-file"
- extension_pairs = [ 'check-file', 'md5,sha1' ]
+ extension_pairs = ['check-file', 'md5,sha1']
msg = Message()
msg.add_int(_VERSION)
msg.add(*extension_pairs)
@@ -151,7 +148,7 @@ class BaseSFTP (object):
# return or raise an exception, but calling select on a closed
# socket will.)
while True:
- read, write, err = select.select([ self.sock ], [], [], 0.1)
+ read, write, err = select.select([self.sock], [], [], 0.1)
if len(read) > 0:
x = self.sock.recv(n)
break
@@ -181,7 +178,7 @@ class BaseSFTP (object):
size = struct.unpack('>I', x)[0]
data = self._read_all(size)
if self.ultra_debug:
- self._log(DEBUG, util.format_binary(data, 'IN: '));
+ self._log(DEBUG, util.format_binary(data, 'IN: '))
if size > 0:
t = byte_ord(data[0])
#self._log(DEBUG2, 'read: %s (len=%d)' % (CMD_NAMES.get(t), '0x%02x' % t, len(data)-1))
diff --git a/paramiko/sftp_attr.py b/paramiko/sftp_attr.py
index 0ed030d8..d12eff8d 100644
--- a/paramiko/sftp_attr.py
+++ b/paramiko/sftp_attr.py
@@ -18,26 +18,27 @@
import stat
import time
-from paramiko.common import *
-from paramiko.sftp import *
+from paramiko.common import x80000000, o700, o70, xffffffff
+from paramiko.py3compat import long, b
class SFTPAttributes (object):
"""
Representation of the attributes of a file (or proxied file) for SFTP in
client or server mode. It attemps to mirror the object returned by
- C{os.stat} as closely as possible, so it may have the following fields,
- with the same meanings as those returned by an C{os.stat} object:
- - st_size
- - st_uid
- - st_gid
- - st_mode
- - st_atime
- - st_mtime
+ `os.stat` as closely as possible, so it may have the following fields,
+ with the same meanings as those returned by an `os.stat` object:
+
+ - ``st_size``
+ - ``st_uid``
+ - ``st_gid``
+ - ``st_mode``
+ - ``st_atime``
+ - ``st_mtime``
Because SFTP allows flags to have other arbitrary named attributes, these
- are stored in a dict named C{attr}. Occasionally, the filename is also
- stored, in C{filename}.
+ are stored in a dict named ``attr``. Occasionally, the filename is also
+ stored, in ``filename``.
"""
FLAG_SIZE = 1
@@ -61,15 +62,12 @@ class SFTPAttributes (object):
def from_stat(cls, obj, filename=None):
"""
- Create an SFTPAttributes object from an existing C{stat} object (an
- object returned by C{os.stat}).
-
- @param obj: an object returned by C{os.stat} (or equivalent).
- @type obj: object
- @param filename: the filename associated with this file.
- @type filename: str
- @return: new L{SFTPAttributes} object with the same attribute fields.
- @rtype: L{SFTPAttributes}
+ Create an `.SFTPAttributes` object from an existing ``stat`` object (an
+ object returned by `os.stat`).
+
+ :param object obj: an object returned by `os.stat` (or equivalent).
+ :param str filename: the filename associated with this file.
+ :return: new `.SFTPAttributes` object with the same attribute fields.
"""
attr = cls()
attr.st_size = obj.st_size
@@ -86,10 +84,8 @@ class SFTPAttributes (object):
def __repr__(self):
return '<SFTPAttributes: %s>' % self._debug_str()
-
### internals...
-
def _from_msg(cls, msg, filename=None, longname=None):
attr = cls()
attr._unpack(msg)
@@ -175,7 +171,7 @@ class SFTPAttributes (object):
_rwx = staticmethod(_rwx)
def __str__(self):
- "create a unix-style long description of the file (like ls -l)"
+ """create a unix-style long description of the file (like ls -l)"""
if self.st_mode is not None:
kind = stat.S_IFMT(self.st_mode)
if kind == stat.S_IFIFO:
diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py
index 0f55c6a8..ce6fbec6 100644
--- a/paramiko/sftp_client.py
+++ b/paramiko/sftp_client.py
@@ -1,6 +1,6 @@
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
#
-# This file is part of paramiko.
+# 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
@@ -16,9 +16,6 @@
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-"""
-Client-mode SFTP support.
-"""
from binascii import hexlify
import errno
@@ -27,8 +24,18 @@ import stat
import threading
import time
import weakref
+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.sftp import BaseSFTP, CMD_OPENDIR, CMD_HANDLE, SFTPError, CMD_READDIR, \
+ CMD_NAME, CMD_CLOSE, SFTP_FLAG_READ, SFTP_FLAG_WRITE, SFTP_FLAG_CREATE, \
+ SFTP_FLAG_TRUNC, SFTP_FLAG_APPEND, SFTP_FLAG_EXCL, CMD_OPEN, CMD_REMOVE, \
+ CMD_RENAME, CMD_MKDIR, CMD_RMDIR, CMD_STAT, CMD_ATTRS, CMD_LSTAT, \
+ CMD_SYMLINK, CMD_SETSTAT, CMD_READLINK, CMD_REALPATH, CMD_STATUS, SFTP_OK, \
+ SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED
-from paramiko.sftp import *
from paramiko.sftp_attr import SFTPAttributes
from paramiko.ssh_exception import SSHException
from paramiko.sftp_file import SFTPFile
@@ -50,24 +57,25 @@ def _to_unicode(s):
b_slash = b'/'
-class SFTPClient (BaseSFTP):
+
+class SFTPClient(BaseSFTP):
"""
- SFTP client object. C{SFTPClient} is used to open an sftp session across
- an open ssh L{Transport} and do remote file operations.
+ SFTP client object.
+
+ Used to open an SFTP session across an open SSH `.Transport` and perform
+ remote file operations.
"""
-
def __init__(self, sock):
"""
- Create an SFTP client from an existing L{Channel}. The channel
- should already have requested the C{"sftp"} subsystem.
+ Create an SFTP client from an existing `.Channel`. The channel
+ should already have requested the ``"sftp"`` subsystem.
An alternate way to create an SFTP client context is by using
- L{from_transport}.
+ `from_transport`.
- @param sock: an open L{Channel} using the C{"sftp"} subsystem
- @type sock: L{Channel}
+ :param .Channel sock: an open `.Channel` using the ``"sftp"`` subsystem
- @raise SSHException: if there's an exception while negotiating
+ :raises SSHException: if there's an exception while negotiating
sftp
"""
BaseSFTP.__init__(self)
@@ -92,13 +100,12 @@ class SFTPClient (BaseSFTP):
def from_transport(cls, t):
"""
- Create an SFTP client channel from an open L{Transport}.
+ Create an SFTP client channel from an open `.Transport`.
- @param t: an open L{Transport} which is already authenticated
- @type t: L{Transport}
- @return: a new L{SFTPClient} object, referring to an sftp session
- (channel) across the transport
- @rtype: L{SFTPClient}
+ :param .Transport t: an open `.Transport` which is already authenticated
+ :return:
+ a new `.SFTPClient` object, referring to an sftp session (channel)
+ across the transport
"""
chan = t.open_session()
if chan is None:
@@ -110,64 +117,57 @@ class SFTPClient (BaseSFTP):
def _log(self, level, msg, *args):
if isinstance(msg, list):
for m in msg:
- super(SFTPClient, self)._log(level, "[chan %s] " + m, *([ self.sock.get_name() ] + list(args)))
+ super(SFTPClient, self)._log(level, "[chan %s] " + m, *([self.sock.get_name()] + list(args)))
else:
- super(SFTPClient, self)._log(level, "[chan %s] " + msg, *([ self.sock.get_name() ] + list(args)))
+ super(SFTPClient, self)._log(level, "[chan %s] " + msg, *([self.sock.get_name()] + list(args)))
def close(self):
"""
Close the SFTP session and its underlying channel.
- @since: 1.4
+ .. versionadded:: 1.4
"""
self._log(INFO, 'sftp session closed.')
self.sock.close()
def get_channel(self):
"""
- Return the underlying L{Channel} object for this SFTP session. This
+ Return the underlying `.Channel` object for this SFTP session. This
might be useful for doing things like setting a timeout on the channel.
- @return: the SSH channel
- @rtype: L{Channel}
-
- @since: 1.7.1
+ .. versionadded:: 1.7.1
"""
return self.sock
def listdir(self, path='.'):
"""
- Return a list containing the names of the entries in the given C{path}.
+ Return a list containing the names of the entries in the given ``path``.
+
The list is in arbitrary order. It does not include the special
- entries C{'.'} and C{'..'} even if they are present in the folder.
- This method is meant to mirror C{os.listdir} as closely as possible.
- For a list of full L{SFTPAttributes} objects, see L{listdir_attr}.
+ entries ``'.'`` and ``'..'`` even if they are present in the folder.
+ This method is meant to mirror ``os.listdir`` as closely as possible.
+ For a list of full `.SFTPAttributes` objects, see `listdir_attr`.
- @param path: path to list (defaults to C{'.'})
- @type path: str
- @return: list of filenames
- @rtype: list of str
+ :param str path: path to list (defaults to ``'.'``)
"""
return [f.filename for f in self.listdir_attr(path)]
def listdir_attr(self, path='.'):
"""
- Return a list containing L{SFTPAttributes} objects corresponding to
- files in the given C{path}. The list is in arbitrary order. It does
- not include the special entries C{'.'} and C{'..'} even if they are
+ Return a list containing `.SFTPAttributes` objects corresponding to
+ files in the given ``path``. The list is in arbitrary order. It does
+ not include the special entries ``'.'`` and ``'..'`` even if they are
present in the folder.
- The returned L{SFTPAttributes} objects will each have an additional
- field: C{longname}, which may contain a formatted string of the file's
+ The returned `.SFTPAttributes` objects will each have an additional
+ field: ``longname``, which may contain a formatted string of the file's
attributes, in unix format. The content of this string will probably
depend on the SFTP server implementation.
- @param path: path to list (defaults to C{'.'})
- @type path: str
- @return: list of attributes
- @rtype: list of L{SFTPAttributes}
+ :param str path: path to list (defaults to ``'.'``)
+ :return: list of `.SFTPAttributes` objects
- @since: 1.2
+ .. versionadded:: 1.2
"""
path = self._adjust_cwd(path)
self._log(DEBUG, 'listdir(%r)' % path)
@@ -197,37 +197,34 @@ class SFTPClient (BaseSFTP):
def open(self, filename, mode='r', bufsize=-1):
"""
Open a file on the remote server. The arguments are the same as for
- python's built-in C{file} (aka C{open}). A file-like object is
- returned, which closely mimics the behavior of a normal python file
- object, including the ability to be used as a context manager.
-
- The mode indicates how the file is to be opened: C{'r'} for reading,
- C{'w'} for writing (truncating an existing file), C{'a'} for appending,
- C{'r+'} for reading/writing, C{'w+'} for reading/writing (truncating an
- existing file), C{'a+'} for reading/appending. The python C{'b'} flag
- is ignored, since SSH treats all files as binary. The C{'U'} flag is
- supported in a compatible way.
-
- Since 1.5.2, an C{'x'} flag indicates that the operation should only
+ Python's built-in `python:file` (aka `python:open`). A file-like
+ object is returned, which closely mimics the behavior of a normal
+ Python file object, including the ability to be used as a context
+ manager.
+
+ The mode indicates how the file is to be opened: ``'r'`` for reading,
+ ``'w'`` for writing (truncating an existing file), ``'a'`` for
+ appending, ``'r+'`` for reading/writing, ``'w+'`` for reading/writing
+ (truncating an existing file), ``'a+'`` for reading/appending. The
+ Python ``'b'`` flag is ignored, since SSH treats all files as binary.
+ The ``'U'`` flag is supported in a compatible way.
+
+ Since 1.5.2, an ``'x'`` flag indicates that the operation should only
succeed if the file was created and did not previously exist. This has
- no direct mapping to python's file flags, but is commonly known as the
- C{O_EXCL} flag in posix.
+ no direct mapping to Python's file flags, but is commonly known as the
+ ``O_EXCL`` flag in posix.
- The file will be buffered in standard python style by default, but
- can be altered with the C{bufsize} parameter. C{0} turns off
- buffering, C{1} uses line buffering, and any number greater than 1
- (C{>1}) uses that specific buffer size.
+ The file will be buffered in standard Python style by default, but
+ can be altered with the ``bufsize`` parameter. ``0`` turns off
+ buffering, ``1`` uses line buffering, and any number greater than 1
+ (``>1``) uses that specific buffer size.
- @param filename: name of the file to open
- @type filename: str
- @param mode: mode (python-style) to open in
- @type mode: str
- @param bufsize: desired buffering (-1 = default buffer size)
- @type bufsize: int
- @return: a file object representing the open file
- @rtype: SFTPFile
+ :param str filename: name of the file to open
+ :param str mode: mode (Python-style) to open in
+ :param int bufsize: desired buffering (-1 = default buffer size)
+ :return: an `.SFTPFile` object representing the open file
- @raise 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))
@@ -236,11 +233,11 @@ class SFTPClient (BaseSFTP):
imode |= SFTP_FLAG_READ
if ('w' in mode) or ('+' in mode) or ('a' in mode):
imode |= SFTP_FLAG_WRITE
- if ('w' in mode):
+ if 'w' in mode:
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC
- if ('a' in mode):
+ if 'a' in mode:
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_APPEND
- if ('x' in mode):
+ if 'x' in mode:
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_EXCL
attrblock = SFTPAttributes()
t, msg = self._request(CMD_OPEN, filename, imode, attrblock)
@@ -250,18 +247,17 @@ class SFTPClient (BaseSFTP):
self._log(DEBUG, 'open(%r, %r) -> %s' % (filename, mode, hexlify(handle)))
return SFTPFile(self, handle, mode, bufsize)
- # python continues to vacillate about "open" vs "file"...
+ # Python continues to vacillate about "open" vs "file"...
file = open
def remove(self, path):
"""
Remove the file at the given path. This only works on files; for
- removing folders (directories), use L{rmdir}.
+ removing folders (directories), use `rmdir`.
- @param path: path (absolute or relative) of the file to remove
- @type path: str
+ :param str path: path (absolute or relative) of the file to remove
- @raise 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)
@@ -271,14 +267,12 @@ class SFTPClient (BaseSFTP):
def rename(self, oldpath, newpath):
"""
- Rename a file or folder from C{oldpath} to C{newpath}.
+ Rename a file or folder from ``oldpath`` to ``newpath``.
- @param oldpath: existing name of the file or folder
- @type oldpath: str
- @param newpath: new name for the file or folder
- @type newpath: str
+ :param str oldpath: existing name of the file or folder
+ :param str newpath: new name for the file or folder
- @raise IOError: if C{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)
@@ -288,14 +282,12 @@ class SFTPClient (BaseSFTP):
def mkdir(self, path, mode=o777):
"""
- Create a folder (directory) named C{path} with numeric mode C{mode}.
+ Create a folder (directory) named ``path`` with numeric mode ``mode``.
The default mode is 0777 (octal). On some systems, mode is ignored.
Where it is used, the current umask value is first masked out.
- @param path: name of the folder to create
- @type path: str
- @param mode: permissions (posix-style) for the newly-created folder
- @type mode: int
+ :param str path: name of the folder to create
+ :param int mode: permissions (posix-style) for the newly-created folder
"""
path = self._adjust_cwd(path)
self._log(DEBUG, 'mkdir(%r, %r)' % (path, mode))
@@ -305,10 +297,9 @@ class SFTPClient (BaseSFTP):
def rmdir(self, path):
"""
- Remove the folder named C{path}.
+ Remove the folder named ``path``.
- @param path: name of the folder to remove
- @type path: str
+ :param str path: name of the folder to remove
"""
path = self._adjust_cwd(path)
self._log(DEBUG, 'rmdir(%r)' % path)
@@ -318,20 +309,20 @@ class SFTPClient (BaseSFTP):
"""
Retrieve information about a file on the remote system. The return
value is an object whose attributes correspond to the attributes of
- python's C{stat} structure as returned by C{os.stat}, except that it
+ Python's ``stat`` structure as returned by ``os.stat``, except that it
contains fewer fields. An SFTP server may return as much or as little
info as it wants, so the results may vary from server to server.
- Unlike a python C{stat} object, the result may not be accessed as a
- tuple. This is mostly due to the author's slack factor.
+ Unlike a Python `python:stat` object, the result may not be accessed as
+ a tuple. This is mostly due to the author's slack factor.
- The fields supported are: C{st_mode}, C{st_size}, C{st_uid}, C{st_gid},
- C{st_atime}, and C{st_mtime}.
+ The fields supported are: ``st_mode``, ``st_size``, ``st_uid``,
+ ``st_gid``, ``st_atime``, and ``st_mtime``.
- @param path: the filename to stat
- @type path: str
- @return: an object containing attributes about the given file
- @rtype: SFTPAttributes
+ :param str path: the filename to stat
+ :return:
+ an `.SFTPAttributes` object containing attributes about the given
+ file
"""
path = self._adjust_cwd(path)
self._log(DEBUG, 'stat(%r)' % path)
@@ -344,12 +335,12 @@ class SFTPClient (BaseSFTP):
"""
Retrieve information about a file on the remote system, without
following symbolic links (shortcuts). This otherwise behaves exactly
- the same as L{stat}.
+ the same as `stat`.
- @param path: the filename to stat
- @type path: str
- @return: an object containing attributes about the given file
- @rtype: SFTPAttributes
+ :param str path: the filename to stat
+ :return:
+ an `.SFTPAttributes` object containing attributes about the given
+ file
"""
path = self._adjust_cwd(path)
self._log(DEBUG, 'lstat(%r)' % path)
@@ -360,13 +351,11 @@ class SFTPClient (BaseSFTP):
def symlink(self, source, dest):
"""
- Create a symbolic link (shortcut) of the C{source} path at
- C{destination}.
+ Create a symbolic link (shortcut) of the ``source`` path at
+ ``destination``.
- @param source: path of the original file
- @type source: str
- @param dest: path of the newly created symlink
- @type dest: str
+ :param str source: path of the original file
+ :param str dest: path of the newly created symlink
"""
dest = self._adjust_cwd(dest)
self._log(DEBUG, 'symlink(%r, %r)' % (source, dest))
@@ -376,13 +365,11 @@ class SFTPClient (BaseSFTP):
def chmod(self, path, mode):
"""
Change the mode (permissions) of a file. The permissions are
- unix-style and identical to those used by python's C{os.chmod}
+ unix-style and identical to those used by Python's `os.chmod`
function.
- @param path: path of the file to change the permissions of
- @type path: str
- @param mode: new permissions
- @type mode: int
+ :param str path: path of the file to change the permissions of
+ :param int mode: new permissions
"""
path = self._adjust_cwd(path)
self._log(DEBUG, 'chmod(%r, %r)' % (path, mode))
@@ -392,17 +379,14 @@ class SFTPClient (BaseSFTP):
def chown(self, path, uid, gid):
"""
- Change the owner (C{uid}) and group (C{gid}) of a file. As with
- python's C{os.chown} function, you must pass both arguments, so if you
- only want to change one, use L{stat} first to retrieve the current
+ Change the owner (``uid``) and group (``gid``) of a file. As with
+ Python's `os.chown` function, you must pass both arguments, so if you
+ only want to change one, use `stat` first to retrieve the current
owner and group.
- @param path: path of the file to change the owner and group of
- @type path: str
- @param uid: new owner's uid
- @type uid: int
- @param gid: new group id
- @type gid: int
+ :param str path: path of the file to change the owner and group of
+ :param int uid: new owner's uid
+ :param int gid: new group id
"""
path = self._adjust_cwd(path)
self._log(DEBUG, 'chown(%r, %r, %r)' % (path, uid, gid))
@@ -412,18 +396,17 @@ class SFTPClient (BaseSFTP):
def utime(self, path, times):
"""
- Set the access and modified times of the file specified by C{path}. If
- C{times} is C{None}, then the file's access and modified times are set
- to the current time. Otherwise, C{times} must be a 2-tuple of numbers,
- of the form C{(atime, mtime)}, which is used to set the access and
- modified times, respectively. This bizarre API is mimicked from python
+ Set the access and modified times of the file specified by ``path``. If
+ ``times`` is ``None``, then the file's access and modified times are set
+ to the current time. Otherwise, ``times`` must be a 2-tuple of numbers,
+ of the form ``(atime, mtime)``, which is used to set the access and
+ modified times, respectively. This bizarre API is mimicked from Python
for the sake of consistency -- I apologize.
- @param path: path of the file to modify
- @type path: str
- @param times: C{None} or a tuple of (access time, modified time) in
- standard internet epoch time (seconds since 01 January 1970 GMT)
- @type times: tuple(int)
+ :param str path: path of the file to modify
+ :param tuple times:
+ ``None`` or a tuple of (access time, modified time) in standard
+ internet epoch time (seconds since 01 January 1970 GMT)
"""
path = self._adjust_cwd(path)
if times is None:
@@ -435,14 +418,13 @@ class SFTPClient (BaseSFTP):
def truncate(self, path, size):
"""
- Change the size of the file specified by C{path}. This usually extends
- or shrinks the size of the file, just like the C{truncate()} method on
- python file objects.
+ Change the size of the file specified by ``path``. This usually
+ extends or shrinks the size of the file, just like the `~file.truncate`
+ method on Python file objects.
- @param path: path of the file to modify
- @type path: str
- @param size: the new size of the file
- @type size: int or long
+ :param str path: path of the file to modify
+ :param size: the new size of the file
+ :type size: int or long
"""
path = self._adjust_cwd(path)
self._log(DEBUG, 'truncate(%r, %r)' % (path, size))
@@ -453,13 +435,11 @@ class SFTPClient (BaseSFTP):
def readlink(self, path):
"""
Return the target of a symbolic link (shortcut). You can use
- L{symlink} to create these. The result may be either an absolute or
+ `symlink` to create these. The result may be either an absolute or
relative pathname.
- @param path: path of the symbolic link file
- @type path: str
- @return: target path
- @rtype: str
+ :param str path: path of the symbolic link file
+ :return: target path, as a `str`
"""
path = self._adjust_cwd(path)
self._log(DEBUG, 'readlink(%r)' % path)
@@ -477,15 +457,13 @@ class SFTPClient (BaseSFTP):
"""
Return the normalized path (on the server) of a given path. This
can be used to quickly resolve symbolic links or determine what the
- server is considering to be the "current folder" (by passing C{'.'}
- as C{path}).
+ server is considering to be the "current folder" (by passing ``'.'``
+ as ``path``).
- @param path: path to be normalized
- @type path: str
- @return: normalized form of the given path
- @rtype: str
+ :param str path: path to be normalized
+ :return: normalized form of the given path (as a `str`)
- @raise 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)
@@ -500,18 +478,17 @@ class SFTPClient (BaseSFTP):
def chdir(self, path=None):
"""
Change the "current directory" of this SFTP session. Since SFTP
- doesn't really have the concept of a current working directory, this
- is emulated by paramiko. Once you use this method to set a working
- directory, all operations on this SFTPClient object will be relative
- to that path. You can pass in C{None} to stop using a current working
+ doesn't really have the concept of a current working directory, this is
+ emulated by Paramiko. Once you use this method to set a working
+ directory, all operations on this `.SFTPClient` object will be relative
+ to that path. You can pass in ``None`` to stop using a current working
directory.
- @param path: new current working directory
- @type path: str
+ :param str path: new current working directory
- @raise IOError: if the requested path doesn't exist on the server
+ :raises IOError: if the requested path doesn't exist on the server
- @since: 1.4
+ .. versionadded:: 1.4
"""
if path is None:
self._cwd = None
@@ -523,43 +500,41 @@ class SFTPClient (BaseSFTP):
def getcwd(self):
"""
Return the "current working directory" for this SFTP session, as
- emulated by paramiko. If no directory has been set with L{chdir},
- this method will return C{None}.
+ emulated by Paramiko. If no directory has been set with `chdir`,
+ this method will return ``None``.
- @return: the current working directory on the server, or C{None}
- @rtype: str
-
- @since: 1.4
+ .. versionadded:: 1.4
"""
return self._cwd and u(self._cwd)
def putfo(self, fl, remotepath, file_size=0, callback=None, confirm=True):
"""
- Copy the contents of an open file object (C{fl}) to the SFTP server as
- C{remotepath}. Any exception raised by operations will be passed through.
+ Copy the contents of an open file object (``fl``) to the SFTP server as
+ ``remotepath``. Any exception raised by operations will be passed
+ through.
The SFTP operations use pipelining for speed.
- @param fl: opened file or file-like object to copy
- @type localpath: object
- @param remotepath: the destination path on the SFTP server
- @type remotepath: str
- @param file_size: optional size parameter passed to callback. If none is
- specified, size defaults to 0
- @type file_size: int
- @param callback: optional callback function that accepts the bytes
- transferred so far and the total bytes to be transferred
+ :param file fl: opened file or file-like object to copy
+ :param str remotepath: the destination path on the SFTP server
+ :param int file_size:
+ optional size parameter passed to callback. If none is specified,
+ size defaults to 0
+ :param callable callback:
+ optional callback function (form: ``func(int, int)``) that accepts
+ the bytes transferred so far and the total bytes to be transferred
(since 1.7.4)
- @type callback: function(int, int)
- @param confirm: whether to do a stat() on the file afterwards to
- confirm the file size (since 1.7.7)
- @type confirm: bool
+ :param bool confirm:
+ whether to do a stat() on the file afterwards to confirm the file
+ size (since 1.7.7)
- @return: an object containing attributes about the given file
- (since 1.7.4)
- @rtype: SFTPAttributes
+ :return:
+ an `.SFTPAttributes` object containing attributes about the given
+ file.
- @since: 1.4
+ .. versionadded:: 1.4
+ .. versionchanged:: 1.7.4
+ Began returning rich attribute objects.
"""
with self.file(remotepath, 'wb') as fr:
fr.set_pipelined(True)
@@ -582,29 +557,28 @@ class SFTPClient (BaseSFTP):
def put(self, localpath, remotepath, callback=None, confirm=True):
"""
- Copy a local file (C{localpath}) to the SFTP server as C{remotepath}.
+ Copy a local file (``localpath``) to the SFTP server as ``remotepath``.
Any exception raised by operations will be passed through. This
method is primarily provided as a convenience.
The SFTP operations use pipelining for speed.
- @param localpath: the local file to copy
- @type localpath: str
- @param remotepath: the destination path on the SFTP server
- @type remotepath: str
- @param callback: optional callback function that accepts the bytes
- transferred so far and the total bytes to be transferred
- (since 1.7.4)
- @type callback: function(int, int)
- @param confirm: whether to do a stat() on the file afterwards to
- confirm the file size (since 1.7.7)
- @type confirm: bool
+ :param str localpath: the local file to copy
+ :param str remotepath: the destination path on the SFTP server
+ :param callable callback:
+ optional callback function (form: ``func(int, int)``) that accepts
+ the bytes transferred so far and the total bytes to be transferred
+ :param bool confirm:
+ whether to do a stat() on the file afterwards to confirm the file
+ size
- @return: an object containing attributes about the given file
- (since 1.7.4)
- @rtype: SFTPAttributes
+ :return: an `.SFTPAttributes` object containing attributes about the given file
- @since: 1.4
+ .. versionadded:: 1.4
+ .. versionchanged:: 1.7.4
+ ``callback`` and rich attribute return value added.
+ .. versionchanged:: 1.7.7
+ ``confirm`` param added.
"""
file_size = os.stat(localpath).st_size
with open(localpath, 'rb') as fl:
@@ -612,23 +586,22 @@ class SFTPClient (BaseSFTP):
def getfo(self, remotepath, fl, callback=None):
"""
- Copy a remote file (C{remotepath}) from the SFTP server and write to
- an open file or file-like object, C{fl}. Any exception raised by
+ Copy a remote file (``remotepath``) from the SFTP server and write to
+ an open file or file-like object, ``fl``. Any exception raised by
operations will be passed through. This method is primarily provided
as a convenience.
- @param remotepath: opened file or file-like object to copy to
- @type remotepath: object
- @param fl: the destination path on the local host or open file
- object
- @type localpath: str
- @param callback: optional callback function that accepts the bytes
- transferred so far and the total bytes to be transferred
- (since 1.7.4)
- @type callback: function(int, int)
- @return: the number of bytes written to the opened file object
+ :param object remotepath: opened file or file-like object to copy to
+ :param str fl:
+ the destination path on the local host or open file object
+ :param callable callback:
+ optional callback function (form: ``func(int, int)``) that accepts
+ the bytes transferred so far and the total bytes to be transferred
+ :return: the `number <int>` of bytes written to the opened file object
- @since: 1.4
+ .. versionadded:: 1.4
+ .. versionchanged:: 1.7.4
+ Added the ``callable`` param.
"""
with self.open(remotepath, 'rb') as fr:
file_size = self.stat(remotepath).st_size
@@ -646,20 +619,19 @@ class SFTPClient (BaseSFTP):
def get(self, remotepath, localpath, callback=None):
"""
- Copy a remote file (C{remotepath}) from the SFTP server to the local
- host as C{localpath}. Any exception raised by operations will be
+ Copy a remote file (``remotepath``) from the SFTP server to the local
+ host as ``localpath``. Any exception raised by operations will be
passed through. This method is primarily provided as a convenience.
- @param remotepath: the remote file to copy
- @type remotepath: str
- @param localpath: the destination path on the local host
- @type localpath: str
- @param callback: optional callback function that accepts the bytes
- transferred so far and the total bytes to be transferred
- (since 1.7.4)
- @type callback: function(int, int)
+ :param str remotepath: the remote file to copy
+ :param str localpath: the destination path on the local host
+ :param callable callback:
+ optional callback function (form: ``func(int, int)``) that accepts
+ the bytes transferred so far and the total bytes to be transferred
- @since: 1.4
+ .. versionadded:: 1.4
+ .. versionchanged:: 1.7.4
+ Added the ``callback`` param
"""
file_size = self.stat(remotepath).st_size
with open(localpath, 'wb') as fl:
@@ -668,10 +640,8 @@ class SFTPClient (BaseSFTP):
if s.st_size != size:
raise IOError('size mismatch in get! %d != %d' % (s.st_size, size))
-
### internals...
-
def _request(self, t, *arg):
num = self._async_request(type(None), t, *arg)
return self._read_response(num)
@@ -724,11 +694,11 @@ class SFTPClient (BaseSFTP):
self._convert_status(msg)
return t, msg
if fileobj is not type(None):
- fileobj._async_response(t, msg)
+ fileobj._async_response(t, msg, num)
if waitfor is None:
# just doing a single check
break
- return (None, None)
+ return None, None
def _finish_responses(self, fileobj):
while fileobj in self._expecting.values():
@@ -769,6 +739,8 @@ class SFTPClient (BaseSFTP):
return self._cwd + b_slash + path
-class SFTP (SFTPClient):
- "an alias for L{SFTPClient} for backwards compatability"
+class SFTP(SFTPClient):
+ """
+ An alias for `.SFTPClient` for backwards compatability.
+ """
pass
diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py
index f0f7e382..03d67b33 100644
--- a/paramiko/sftp_file.py
+++ b/paramiko/sftp_file.py
@@ -17,18 +17,22 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-L{SFTPFile}
+SFTP file object
"""
+from __future__ import with_statement
+
from binascii import hexlify
from collections import deque
import socket
import threading
import time
+from paramiko.common import DEBUG
-from paramiko.common import *
-from paramiko.sftp import *
from paramiko.file import BufferedFile
+from paramiko.py3compat import long
+from paramiko.sftp import CMD_CLOSE, CMD_READ, CMD_DATA, SFTPError, CMD_WRITE, \
+ CMD_STATUS, CMD_FSTAT, CMD_ATTRS, CMD_FSETSTAT, CMD_EXTENDED
from paramiko.sftp_attr import SFTPAttributes
@@ -53,7 +57,8 @@ class SFTPFile (BufferedFile):
self._prefetching = False
self._prefetch_done = False
self._prefetch_data = {}
- self._prefetch_reads = []
+ self._prefetch_extents = {}
+ self._prefetch_lock = threading.Lock()
self._saved_exception = None
self._reqs = deque()
@@ -61,6 +66,9 @@ class SFTPFile (BufferedFile):
self._close(async=True)
def close(self):
+ """
+ Close the file.
+ """
self._close(async=False)
def _close(self, async=False):
@@ -91,7 +99,7 @@ class SFTPFile (BufferedFile):
pass
def _data_in_prefetch_requests(self, offset, size):
- k = [i for i in self._prefetch_reads if i[0] <= offset]
+ k = [x for x in list(self._prefetch_extents.values()) if x[0] <= offset]
if len(k) == 0:
return False
k.sort(key=lambda x: x[0])
@@ -178,34 +186,34 @@ class SFTPFile (BufferedFile):
def settimeout(self, timeout):
"""
Set a timeout on read/write operations on the underlying socket or
- ssh L{Channel}.
+ ssh `.Channel`.
- @see: L{Channel.settimeout}
- @param timeout: seconds to wait for a pending read/write operation
- before raising C{socket.timeout}, or C{None} for no timeout
- @type timeout: float
+ :param float timeout:
+ seconds to wait for a pending read/write operation before raising
+ ``socket.timeout``, or ``None`` for no timeout
+
+ .. seealso:: `.Channel.settimeout`
"""
self.sftp.sock.settimeout(timeout)
def gettimeout(self):
"""
- Returns the timeout in seconds (as a float) associated with the socket
- or ssh L{Channel} used for this file.
+ Returns the timeout in seconds (as a `float`) associated with the
+ socket or ssh `.Channel` used for this file.
- @see: L{Channel.gettimeout}
- @rtype: float
+ .. seealso:: `.Channel.gettimeout`
"""
return self.sftp.sock.gettimeout()
def setblocking(self, blocking):
"""
Set blocking or non-blocking mode on the underiying socket or ssh
- L{Channel}.
+ `.Channel`.
+
+ :param int blocking:
+ 0 to set non-blocking mode; non-0 to set blocking mode.
- @see: L{Channel.setblocking}
- @param blocking: 0 to set non-blocking mode; non-0 to set blocking
- mode.
- @type blocking: int
+ .. seealso:: `.Channel.setblocking`
"""
self.sftp.sock.setblocking(blocking)
@@ -223,11 +231,10 @@ class SFTPFile (BufferedFile):
def stat(self):
"""
Retrieve information about this file from the remote system. This is
- exactly like L{SFTP.stat}, except that it operates on an already-open
- file.
+ exactly like `.SFTPClient.stat`, except that it operates on an
+ already-open file.
- @return: an object containing attributes about this file.
- @rtype: SFTPAttributes
+ :return: an `.SFTPAttributes` object containing attributes about this file.
"""
t, msg = self.sftp._request(CMD_FSTAT, self.handle)
if t != CMD_ATTRS:
@@ -237,11 +244,10 @@ class SFTPFile (BufferedFile):
def chmod(self, mode):
"""
Change the mode (permissions) of this file. The permissions are
- unix-style and identical to those used by python's C{os.chmod}
+ unix-style and identical to those used by Python's `os.chmod`
function.
- @param mode: new permissions
- @type mode: int
+ :param int mode: new permissions
"""
self.sftp._log(DEBUG, 'chmod(%s, %r)' % (hexlify(self.handle), mode))
attr = SFTPAttributes()
@@ -250,15 +256,13 @@ class SFTPFile (BufferedFile):
def chown(self, uid, gid):
"""
- Change the owner (C{uid}) and group (C{gid}) of this file. As with
- python's C{os.chown} function, you must pass both arguments, so if you
- only want to change one, use L{stat} first to retrieve the current
+ Change the owner (``uid``) and group (``gid``) of this file. As with
+ Python's `os.chown` function, you must pass both arguments, so if you
+ only want to change one, use `stat` first to retrieve the current
owner and group.
- @param uid: new owner's uid
- @type uid: int
- @param gid: new group id
- @type gid: int
+ :param int uid: new owner's uid
+ :param int gid: new group id
"""
self.sftp._log(DEBUG, 'chown(%s, %r, %r)' % (hexlify(self.handle), uid, gid))
attr = SFTPAttributes()
@@ -268,15 +272,15 @@ class SFTPFile (BufferedFile):
def utime(self, times):
"""
Set the access and modified times of this file. If
- C{times} is C{None}, then the file's access and modified times are set
- to the current time. Otherwise, C{times} must be a 2-tuple of numbers,
- of the form C{(atime, mtime)}, which is used to set the access and
- modified times, respectively. This bizarre API is mimicked from python
+ ``times`` is ``None``, then the file's access and modified times are set
+ to the current time. Otherwise, ``times`` must be a 2-tuple of numbers,
+ of the form ``(atime, mtime)``, which is used to set the access and
+ modified times, respectively. This bizarre API is mimicked from Python
for the sake of consistency -- I apologize.
- @param times: C{None} or a tuple of (access time, modified time) in
- standard internet epoch time (seconds since 01 January 1970 GMT)
- @type times: tuple(int)
+ :param tuple times:
+ ``None`` or a tuple of (access time, modified time) in standard
+ internet epoch time (seconds since 01 January 1970 GMT)
"""
if times is None:
times = (time.time(), time.time())
@@ -288,11 +292,11 @@ class SFTPFile (BufferedFile):
def truncate(self, size):
"""
Change the size of this file. This usually extends
- or shrinks the size of the file, just like the C{truncate()} method on
- python file objects.
+ or shrinks the size of the file, just like the ``truncate()`` method on
+ Python file objects.
- @param size: the new size of the file
- @type size: int or long
+ :param size: the new size of the file
+ :type size: int or long
"""
self.sftp._log(DEBUG, 'truncate(%s, %r)' % (hexlify(self.handle), size))
attr = SFTPAttributes()
@@ -305,46 +309,48 @@ class SFTPFile (BufferedFile):
to verify a successful upload or download, or for various rsync-like
operations.
- The file is hashed from C{offset}, for C{length} bytes. If C{length}
- is 0, the remainder of the file is hashed. Thus, if both C{offset}
- and C{length} are zero, the entire file is hashed.
+ The file is hashed from ``offset``, for ``length`` bytes. If ``length``
+ is 0, the remainder of the file is hashed. Thus, if both ``offset``
+ and ``length`` are zero, the entire file is hashed.
- Normally, C{block_size} will be 0 (the default), and this method will
+ Normally, ``block_size`` will be 0 (the default), and this method will
return a byte string representing the requested hash (for example, a
string of length 16 for MD5, or 20 for SHA-1). If a non-zero
- C{block_size} is given, each chunk of the file (from C{offset} to
- C{offset + length}) of C{block_size} bytes is computed as a separate
+ ``block_size`` is given, each chunk of the file (from ``offset`` to
+ ``offset + length``) of ``block_size`` bytes is computed as a separate
hash. The hash results are all concatenated and returned as a single
string.
- For example, C{check('sha1', 0, 1024, 512)} will return a string of
+ For example, ``check('sha1', 0, 1024, 512)`` will return a string of
length 40. The first 20 bytes will be the SHA-1 of the first 512 bytes
of the file, and the last 20 bytes will be the SHA-1 of the next 512
bytes.
- @param hash_algorithm: the name of the hash algorithm to use (normally
- C{"sha1"} or C{"md5"})
- @type hash_algorithm: str
- @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 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: string of bytes representing the hash of each block,
- concatenated together
- @rtype: str
+ :param str hash_algorithm:
+ the name of the hash algorithm to use (normally ``"sha1"`` or
+ ``"md5"``)
+ :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
- @note: Many (most?) servers don't support this extension yet.
-
- @raise IOError: if the server doesn't support the "check-file"
+ :raises IOError: if the server doesn't support the "check-file"
extension, or possibly doesn't support the hash algorithm
requested
- @since: 1.4
+ .. note:: Many (most?) servers don't support this extension yet.
+
+ .. versionadded:: 1.4
"""
t, msg = self.sftp._request(CMD_EXTENDED, 'check-file', self.handle,
hash_algorithm, long(offset), long(length), block_size)
@@ -357,35 +363,35 @@ class SFTPFile (BufferedFile):
"""
Turn on/off the pipelining of write operations to this file. When
pipelining is on, paramiko won't wait for the server response after
- each write operation. Instead, they're collected as they come in.
- At the first non-write operation (including L{close}), all remaining
+ each write operation. Instead, they're collected as they come in. At
+ the first non-write operation (including `.close`), all remaining
server responses are collected. This means that if there was an error
- with one of your later writes, an exception might be thrown from
- within L{close} instead of L{write}.
+ with one of your later writes, an exception might be thrown from within
+ `.close` instead of `.write`.
- By default, files are I{not} pipelined.
+ By default, files are not pipelined.
- @param pipelined: C{True} if pipelining should be turned on for this
- file; C{False} otherwise
- @type pipelined: bool
+ :param bool pipelined:
+ ``True`` if pipelining should be turned on for this file; ``False``
+ otherwise
- @since: 1.5
+ .. versionadded:: 1.5
"""
self.pipelined = pipelined
def prefetch(self):
"""
- Pre-fetch the remaining contents of this file in anticipation of
- future L{read} calls. If reading the entire file, pre-fetching can
+ Pre-fetch the remaining contents of this file in anticipation of future
+ `.read` calls. If reading the entire file, pre-fetching can
dramatically improve the download speed by avoiding roundtrip latency.
The file's contents are incrementally buffered in a background thread.
- The prefetched data is stored in a buffer until read via the L{read}
+ The prefetched data is stored in a buffer until read via the `.read`
method. Once data has been read, it's removed from the buffer. The
- data may be read in a random order (using L{seek}); chunks of the
+ data may be read in a random order (using `.seek`); chunks of the
buffer that haven't been read will continue to be buffered.
- @since: 1.5.1
+ .. versionadded:: 1.5.1
"""
size = self.stat().st_size
# queue up async reads for the rest of the file
@@ -401,17 +407,17 @@ class SFTPFile (BufferedFile):
def readv(self, chunks):
"""
Read a set of blocks from the file by (offset, length). This is more
- efficient than doing a series of L{seek} and L{read} calls, since the
+ efficient than doing a series of `.seek` and `.read` calls, since the
prefetch machinery is used to retrieve all the requested blocks at
once.
- @param chunks: a list of (offset, length) tuples indicating which
- sections of the file to read
- @type chunks: list(tuple(long, int))
- @return: a list of blocks read, in the same order as in C{chunks}
- @rtype: list(str)
+ :param chunks:
+ a list of (offset, length) tuples indicating which sections of the
+ file to read
+ :type chunks: list(tuple(long, int))
+ :return: a list of blocks read, in the same order as in ``chunks``
- @since: 1.5.4
+ .. versionadded:: 1.5.4
"""
self.sftp._log(DEBUG, 'readv(%s, %r)' % (hexlify(self.handle), chunks))
@@ -433,11 +439,9 @@ class SFTPFile (BufferedFile):
for x in chunks:
self.seek(x[0])
yield self.read(x[1])
-
### internals...
-
def _get_size(self):
try:
return self.stat().st_size
@@ -447,7 +451,6 @@ class SFTPFile (BufferedFile):
def _start_prefetch(self, chunks):
self._prefetching = True
self._prefetch_done = False
- self._prefetch_reads.extend(chunks)
t = threading.Thread(target=self._prefetch_thread, args=(chunks,))
t.setDaemon(True)
@@ -457,9 +460,11 @@ class SFTPFile (BufferedFile):
# do these read requests in a temporary thread because there may be
# a lot of them, so it may block.
for offset, length in chunks:
- self.sftp._async_request(self, CMD_READ, self.handle, long(offset), int(length))
+ with self._prefetch_lock:
+ num = self.sftp._async_request(self, CMD_READ, self.handle, long(offset), int(length))
+ self._prefetch_extents[num] = (offset, length)
- def _async_response(self, t, msg):
+ def _async_response(self, t, msg, num):
if t == CMD_STATUS:
# save exception and re-raise it on next file operation
try:
@@ -470,13 +475,15 @@ class SFTPFile (BufferedFile):
if t != CMD_DATA:
raise SFTPError('Expected data')
data = msg.get_string()
- offset, length = self._prefetch_reads.pop(0)
- self._prefetch_data[offset] = data
- if len(self._prefetch_reads) == 0:
- self._prefetch_done = True
+ with self._prefetch_lock:
+ offset, length = self._prefetch_extents[num]
+ self._prefetch_data[offset] = data
+ del self._prefetch_extents[num]
+ if len(self._prefetch_extents) == 0:
+ self._prefetch_done = True
def _check_exception(self):
- "if there's a saved exception, raise & clear it"
+ """if there's a saved exception, raise & clear it"""
if self._saved_exception is not None:
x = self._saved_exception
self._saved_exception = None
diff --git a/paramiko/sftp_handle.py b/paramiko/sftp_handle.py
index 9fc21008..92dd9cfe 100644
--- a/paramiko/sftp_handle.py
+++ b/paramiko/sftp_handle.py
@@ -21,9 +21,7 @@ Abstraction of an SFTP file handle (for server mode).
"""
import os
-
-from paramiko.common import *
-from paramiko.sftp import *
+from paramiko.sftp import SFTP_OP_UNSUPPORTED, SFTP_OK
class SFTPHandle (object):
@@ -33,21 +31,20 @@ class SFTPHandle (object):
by the client to refer to the underlying file.
Server implementations can (and should) subclass SFTPHandle to implement
- features of a file handle, like L{stat} or L{chattr}.
+ features of a file handle, like `stat` or `chattr`.
"""
def __init__(self, flags=0):
"""
Create a new file handle representing a local file being served over
- SFTP. If C{flags} is passed in, it's used to determine if the file
+ SFTP. If ``flags`` is passed in, it's used to determine if the file
is open in append mode.
- @param flags: optional flags as passed to L{SFTPServerInterface.open}
- @type flags: int
+ :param int flags: optional flags as passed to `.SFTPServerInterface.open`
"""
self.__flags = flags
self.__name = None
# only for handles to folders:
- self.__files = { }
+ self.__files = {}
self.__tell = None
def close(self):
@@ -56,10 +53,10 @@ class SFTPHandle (object):
Normally you would use this method to close the underlying OS level
file object(s).
- The default implementation checks for attributes on C{self} named
- C{readfile} and/or C{writefile}, and if either or both are present,
- their C{close()} methods are called. This means that if you are
- using the default implementations of L{read} and L{write}, this
+ The default implementation checks for attributes on ``self`` named
+ ``readfile`` and/or ``writefile``, and if either or both are present,
+ their ``close()`` methods are called. This means that if you are
+ using the default implementations of `read` and `write`, this
method's default implementation should be fine also.
"""
readfile = getattr(self, 'readfile', None)
@@ -71,24 +68,22 @@ class SFTPHandle (object):
def read(self, offset, length):
"""
- Read up to C{length} bytes from this file, starting at position
- C{offset}. The offset may be a python long, since SFTP allows it
+ Read up to ``length`` bytes from this file, starting at position
+ ``offset``. The offset may be a Python long, since SFTP allows it
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 L{SFTP_EOF}.
+ empty string to signify EOF, or it may also return `.SFTP_EOF`.
- The default implementation checks for an attribute on C{self} named
- C{readfile}, and if present, performs the read operation on the python
+ The default implementation checks for an attribute on ``self`` named
+ ``readfile``, and if present, performs the read operation on the Python
file-like object found there. (This is meant as a time saver for the
- common case where you are wrapping a python file object.)
+ 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 length: number of bytes to attempt to read.
- @type length: int
- @return: data read from the file, or an SFTP error code.
- @rtype: str
+ :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`.
"""
readfile = getattr(self, 'readfile', None)
if readfile is None:
@@ -108,23 +103,22 @@ class SFTPHandle (object):
def write(self, offset, data):
"""
- Write C{data} into this file at position C{offset}. Extending the
- file past its original end is expected. Unlike python's normal
- C{write()} methods, this method cannot do a partial write: it must
- write all of C{data} or else return an error.
+ Write ``data`` into this file at position ``offset``. Extending the
+ file past its original end is expected. Unlike Python's normal
+ ``write()`` methods, this method cannot do a partial write: it must
+ write all of ``data`` or else return an error.
- The default implementation checks for an attribute on C{self} named
- C{writefile}, and if present, performs the write operation on the
- python file-like object found there. The attribute is named
- differently from C{readfile} to make it easy to implement read-only
+ The default implementation checks for an attribute on ``self`` named
+ ``writefile``, and if present, performs the write operation on the
+ Python file-like object found there. The attribute is named
+ differently from ``readfile`` to make it easy to implement read-only
(or write-only) files, but if both attributes are present, they should
refer to the same file.
- @param offset: position in the file to start reading from.
- @type offset: int or long
- @param data: data to write into the file.
- @type data: str
- @return: an SFTP error code like L{SFTP_OK}.
+ :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`.
"""
writefile = getattr(self, 'writefile', None)
if writefile is None:
@@ -148,33 +142,30 @@ class SFTPHandle (object):
def stat(self):
"""
- Return an L{SFTPAttributes} object referring to this open file, or an
- error code. This is equivalent to L{SFTPServerInterface.stat}, except
+ Return an `.SFTPAttributes` object referring to this open file, or an
+ error code. This is equivalent to `.SFTPServerInterface.stat`, except
it's called on an open file instead of a path.
- @return: an attributes object for the given file, or an SFTP error
- code (like L{SFTP_PERMISSION_DENIED}).
- @rtype: L{SFTPAttributes} I{or error code}
+ :return:
+ an attributes object for the given file, or an SFTP error code
+ (like `.SFTP_PERMISSION_DENIED`).
+ :rtype: `.SFTPAttributes` or error code
"""
return SFTP_OP_UNSUPPORTED
def chattr(self, attr):
"""
- Change the attributes of this file. The C{attr} object will contain
+ Change the attributes of this file. The ``attr`` object will contain
only those fields provided by the client in its request, so you should
check for the presence of fields before using them.
- @param attr: the attributes to change on this file.
- @type attr: L{SFTPAttributes}
- @return: an error code like L{SFTP_OK}.
- @rtype: int
+ :param .SFTPAttributes attr: the attributes to change on this file.
+ :return: an `int` error code like `.SFTP_OK`.
"""
return SFTP_OP_UNSUPPORTED
-
### internals...
-
def _set_files(self, files):
"""
Used by the SFTP server code to cache a directory listing. (In
diff --git a/paramiko/sftp_server.py b/paramiko/sftp_server.py
index 4539edea..dadfd026 100644
--- a/paramiko/sftp_server.py
+++ b/paramiko/sftp_server.py
@@ -24,14 +24,26 @@ import os
import errno
from Crypto.Hash import MD5, SHA
-from paramiko.common import *
+import sys
+from paramiko import util
+from paramiko.sftp import BaseSFTP, Message, SFTP_FAILURE, \
+ SFTP_PERMISSION_DENIED, SFTP_NO_SUCH_FILE
+from paramiko.sftp_si import SFTPServerInterface
+from paramiko.sftp_attr import SFTPAttributes
+from paramiko.common import DEBUG
+from paramiko.py3compat import long, string_types, bytes_types, b
from paramiko.server import SubsystemHandler
-from paramiko.sftp import *
-from paramiko.sftp_si import *
-from paramiko.sftp_attr import *
# known hash algorithms for the "check-file" extension
+from paramiko.sftp import CMD_HANDLE, SFTP_DESC, CMD_STATUS, SFTP_EOF, CMD_NAME, \
+ SFTP_BAD_MESSAGE, CMD_EXTENDED_REPLY, SFTP_FLAG_READ, SFTP_FLAG_WRITE, \
+ SFTP_FLAG_APPEND, SFTP_FLAG_CREATE, SFTP_FLAG_TRUNC, SFTP_FLAG_EXCL, \
+ CMD_NAMES, CMD_OPEN, CMD_CLOSE, SFTP_OK, CMD_READ, CMD_DATA, CMD_WRITE, \
+ CMD_REMOVE, CMD_RENAME, CMD_MKDIR, CMD_RMDIR, CMD_OPENDIR, CMD_READDIR, \
+ CMD_STAT, CMD_ATTRS, CMD_LSTAT, CMD_FSTAT, CMD_SETSTAT, CMD_FSETSTAT, \
+ CMD_READLINK, CMD_SYMLINK, CMD_REALPATH, CMD_EXTENDED, SFTP_OP_UNSUPPORTED
+
_hash_class = {
'sha1': SHA,
'md5': MD5,
@@ -40,28 +52,25 @@ _hash_class = {
class SFTPServer (BaseSFTP, SubsystemHandler):
"""
- Server-side SFTP subsystem support. Since this is a L{SubsystemHandler},
- it can be (and is meant to be) set as the handler for C{"sftp"} requests.
- Use L{Transport.set_subsystem_handler} to activate this class.
+ Server-side SFTP subsystem support. Since this is a `.SubsystemHandler`,
+ it can be (and is meant to be) set as the handler for ``"sftp"`` requests.
+ Use `.Transport.set_subsystem_handler` to activate this class.
"""
def __init__(self, channel, name, server, sftp_si=SFTPServerInterface, *largs, **kwargs):
"""
The constructor for SFTPServer is meant to be called from within the
- L{Transport} as a subsystem handler. C{server} and any additional
+ `.Transport` as a subsystem handler. ``server`` and any additional
parameters or keyword parameters are passed from the original call to
- L{Transport.set_subsystem_handler}.
+ `.Transport.set_subsystem_handler`.
- @param channel: channel passed from the L{Transport}.
- @type channel: L{Channel}
- @param name: name of the requested subsystem.
- @type name: str
- @param server: the server object associated with this channel and
- subsystem
- @type server: L{ServerInterface}
- @param sftp_si: a subclass of L{SFTPServerInterface} to use for handling
- individual requests.
- @type sftp_si: class
+ :param .Channel channel: channel passed from the `.Transport`.
+ :param str name: name of the requested subsystem.
+ :param .ServerInterface server:
+ the server object associated with this channel and subsystem
+ :param class sftp_si:
+ a subclass of `.SFTPServerInterface` to use for handling individual
+ requests.
"""
BaseSFTP.__init__(self)
SubsystemHandler.__init__(self, channel, name, server)
@@ -70,8 +79,8 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
self.ultra_debug = transport.get_hexdump()
self.next_handle = 1
# map of handle-string to SFTPHandle for files & folders:
- self.file_table = { }
- self.folder_table = { }
+ self.file_table = {}
+ self.folder_table = {}
self.server = sftp_si(server, *largs, **kwargs)
def _log(self, level, msg):
@@ -122,14 +131,12 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
def convert_errno(e):
"""
- Convert an errno value (as from an C{OSError} or C{IOError}) into a
+ Convert an errno value (as from an ``OSError`` or ``IOError``) into a
standard SFTP result code. This is a convenience function for trapping
exceptions in server code and returning an appropriate result.
- @param e: an errno code, as from C{OSError.errno}.
- @type e: int
- @return: an SFTP error code like L{SFTP_NO_SUCH_FILE}.
- @rtype: int
+ :param int e: an errno code, as from ``OSError.errno``.
+ :return: an `int` SFTP error code like ``SFTP_NO_SUCH_FILE``.
"""
if e == errno.EACCES:
# permission denied
@@ -144,18 +151,16 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
def set_file_attr(filename, attr):
"""
Change a file's attributes on the local filesystem. The contents of
- C{attr} are used to change the permissions, owner, group ownership,
+ ``attr`` are used to change the permissions, owner, group ownership,
and/or modification & access time of the file, depending on which
- attributes are present in C{attr}.
+ attributes are present in ``attr``.
This is meant to be a handy helper function for translating SFTP file
requests into local file operations.
- @param filename: name of the file to alter (should usually be an
- absolute path).
- @type filename: str
- @param attr: attributes to change.
- @type attr: L{SFTPAttributes}
+ :param str filename:
+ name of the file to alter (should usually be an absolute path).
+ :param .SFTPAttributes attr: attributes to change.
"""
if sys.platform != 'win32':
# mode operations are meaningless on win32
@@ -170,10 +175,8 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
f.truncate(attr.st_size)
set_file_attr = staticmethod(set_file_attr)
-
### internals...
-
def _response(self, request_number, t, *arg):
msg = Message()
msg.add_int(request_number)
@@ -297,7 +300,7 @@ class SFTPServer (BaseSFTP, SubsystemHandler):
self._send_packet(CMD_EXTENDED_REPLY, msg)
def _convert_pflags(self, pflags):
- "convert SFTP-style open() flags to python's os.open() flags"
+ """convert SFTP-style open() flags to Python's os.open() flags"""
if (pflags & SFTP_FLAG_READ) and (pflags & SFTP_FLAG_WRITE):
flags = os.O_RDWR
elif pflags & SFTP_FLAG_WRITE:
diff --git a/paramiko/sftp_si.py b/paramiko/sftp_si.py
index b0ee3c42..61db956c 100644
--- a/paramiko/sftp_si.py
+++ b/paramiko/sftp_si.py
@@ -17,19 +17,18 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-L{SFTPServerInterface} is an interface to override for SFTP server support.
+An interface to override for SFTP server support.
"""
import os
-
-from paramiko.common import *
-from paramiko.sftp import *
+import sys
+from paramiko.sftp import SFTP_OP_UNSUPPORTED
class SFTPServerInterface (object):
"""
This class defines an interface for controlling the behavior of paramiko
- when using the L{SFTPServer} subsystem to provide an SFTP server.
+ when using the `.SFTPServer` subsystem to provide an SFTP server.
Methods on this class are called from the SFTP session's thread, so you can
block as long as necessary without affecting other sessions (even other
@@ -41,14 +40,13 @@ class SFTPServerInterface (object):
clients & servers obey the requirement that paths be encoded in UTF-8.
"""
- def __init__ (self, server, *largs, **kwargs):
+ def __init__(self, server, *largs, **kwargs):
"""
Create a new SFTPServerInterface object. This method does nothing by
default and is meant to be overridden by subclasses.
- @param server: the server object associated with this channel and
- SFTP subsystem
- @type server: L{ServerInterface}
+ :param .ServerInterface server:
+ the server object associated with this channel and SFTP subsystem
"""
super(SFTPServerInterface, self).__init__(*largs, **kwargs)
@@ -64,7 +62,7 @@ class SFTPServerInterface (object):
"""
The SFTP server session has just ended, either cleanly or via an
exception. This method is meant to be overridden to perform any
- necessary cleanup before this C{SFTPServerInterface} object is
+ necessary cleanup before this `.SFTPServerInterface` object is
destroyed.
"""
pass
@@ -72,103 +70,105 @@ class SFTPServerInterface (object):
def open(self, path, flags, attr):
"""
Open a file on the server and create a handle for future operations
- on that file. On success, a new object subclassed from L{SFTPHandle}
+ 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
- L{SFTP_PERMISSION_DENIED} should be returned.
-
- C{flags} contains the requested mode for opening (read-only,
- write-append, etc) as a bitset of flags from the C{os} module:
- - C{os.O_RDONLY}
- - C{os.O_WRONLY}
- - C{os.O_RDWR}
- - C{os.O_APPEND}
- - C{os.O_CREAT}
- - C{os.O_TRUNC}
- - C{os.O_EXCL}
- (One of C{os.O_RDONLY}, C{os.O_WRONLY}, or C{os.O_RDWR} will always
+ `.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:
+
+ - ``os.O_RDONLY``
+ - ``os.O_WRONLY``
+ - ``os.O_RDWR``
+ - ``os.O_APPEND``
+ - ``os.O_CREAT``
+ - ``os.O_TRUNC``
+ - ``os.O_EXCL``
+
+ (One of ``os.O_RDONLY``, ``os.O_WRONLY``, or ``os.O_RDWR`` will always
be set.)
- The C{attr} object contains requested attributes of the file if it
+ The ``attr`` object contains requested attributes of the file if it
has to be created. Some or all attribute fields may be missing if
the client didn't specify them.
- @note: The SFTP protocol defines all files to be in "binary" mode.
- There is no equivalent to python's "text" mode.
-
- @param path: the requested path (relative or absolute) of the file
- to be opened.
- @type path: str
- @param flags: flags or'd together from the C{os} module indicating the
- requested mode for opening the file.
- @type flags: int
- @param attr: requested attributes of the file if it is newly created.
- @type attr: L{SFTPAttributes}
- @return: a new L{SFTPHandle} I{or error code}.
- @rtype L{SFTPHandle}
+ .. note:: The SFTP protocol defines all files to be in "binary" mode.
+ There is no equivalent to Python's "text" mode.
+
+ :param str path:
+ the requested path (relative or absolute) of the file to be opened.
+ :param int flags:
+ flags or'd together from the ``os`` module indicating the requested
+ mode for opening the file.
+ :param .SFTPAttributes attr:
+ requested attributes of the file if it is newly created.
+ :return: a new `.SFTPHandle` or error code.
"""
return SFTP_OP_UNSUPPORTED
def list_folder(self, path):
"""
- Return a list of files within a given folder. The C{path} will use
- posix notation (C{"/"} separates folder names) and may be an absolute
+ Return a list of files within a given folder. The ``path`` will use
+ posix notation (``"/"`` separates folder names) and may be an absolute
or relative path.
- The list of files is expected to be a list of L{SFTPAttributes}
+ The list of files is expected to be a list of `.SFTPAttributes`
objects, which are similar in structure to the objects returned by
- C{os.stat}. In addition, each object should have its C{filename}
+ ``os.stat``. In addition, each object should have its ``filename``
field filled in, since this is important to a directory listing and
- not normally present in C{os.stat} results. The method
- L{SFTPAttributes.from_stat} will usually do what you want.
+ not normally present in ``os.stat`` results. The method
+ `.SFTPAttributes.from_stat` will usually do what you want.
- In case of an error, you should return one of the C{SFTP_*} error
- codes, such as L{SFTP_PERMISSION_DENIED}.
+ In case of an error, you should return one of the ``SFTP_*`` error
+ codes, such as `.SFTP_PERMISSION_DENIED`.
- @param path: the requested path (relative or absolute) to be listed.
- @type path: str
- @return: a list of the files in the given folder, using
- L{SFTPAttributes} objects.
- @rtype: list of L{SFTPAttributes} I{or error code}
+ :param str path: the requested path (relative or absolute) to be listed.
+ :return:
+ a list of the files in the given folder, using `.SFTPAttributes`
+ objects.
- @note: You should normalize the given C{path} first (see the
- C{os.path} module) and check appropriate permissions before returning
- the list of files. Be careful of malicious clients attempting to use
- relative paths to escape restricted folders, if you're doing a direct
- translation from the SFTP server path to your local filesystem.
+ .. note::
+ You should normalize the given ``path`` first (see the `os.path`
+ module) and check appropriate permissions before returning the list
+ of files. Be careful of malicious clients attempting to use
+ relative paths to escape restricted folders, if you're doing a
+ direct translation from the SFTP server path to your local
+ filesystem.
"""
return SFTP_OP_UNSUPPORTED
def stat(self, path):
"""
- Return an L{SFTPAttributes} object for a path on the server, or an
+ Return an `.SFTPAttributes` object for a path on the server, or an
error code. If your server supports symbolic links (also known as
- "aliases"), you should follow them. (L{lstat} is the corresponding
+ "aliases"), you should follow them. (`lstat` is the corresponding
call that doesn't follow symlinks/aliases.)
- @param path: the requested path (relative or absolute) to fetch
- file statistics for.
- @type path: str
- @return: an attributes object for the given file, or an SFTP error
- code (like L{SFTP_PERMISSION_DENIED}).
- @rtype: L{SFTPAttributes} I{or error code}
+ :param str path:
+ the requested path (relative or absolute) to fetch file statistics
+ for.
+ :return:
+ an `.SFTPAttributes` object for the given file, or an SFTP error
+ code (like `.SFTP_PERMISSION_DENIED`).
"""
return SFTP_OP_UNSUPPORTED
def lstat(self, path):
"""
- Return an L{SFTPAttributes} object for a path on the server, or an
+ Return an `.SFTPAttributes` object for a path on the server, or an
error code. If your server supports symbolic links (also known as
- "aliases"), you should I{not} follow them -- instead, you should
- return data on the symlink or alias itself. (L{stat} is the
+ "aliases"), you should not follow them -- instead, you should
+ return data on the symlink or alias itself. (`stat` is the
corresponding call that follows symlinks/aliases.)
- @param path: the requested path (relative or absolute) to fetch
- file statistics for.
- @type path: str
- @return: an attributes object for the given file, or an SFTP error
- code (like L{SFTP_PERMISSION_DENIED}).
- @rtype: L{SFTPAttributes} I{or error code}
+ :param str path:
+ the requested path (relative or absolute) to fetch file statistics
+ for.
+ :type path: str
+ :return:
+ an `.SFTPAttributes` object for the given file, or an SFTP error
+ code (like `.SFTP_PERMISSION_DENIED`).
"""
return SFTP_OP_UNSUPPORTED
@@ -176,11 +176,9 @@ class SFTPServerInterface (object):
"""
Delete a file, if possible.
- @param path: the requested path (relative or absolute) of the file
- to delete.
- @type path: str
- @return: an SFTP error code like L{SFTP_OK}.
- @rtype: int
+ :param str path:
+ the requested path (relative or absolute) of the file to delete.
+ :return: an SFTP error code `int` like `.SFTP_OK`.
"""
return SFTP_OP_UNSUPPORTED
@@ -192,83 +190,74 @@ class SFTPServerInterface (object):
probably a good idea to implement "move" in this method too, even for
files that cross disk partition boundaries, if at all possible.
- @note: You should return an error if a file with the same name as
- C{newpath} already exists. (The rename operation should be
+ .. note:: You should return an error if a file with the same name as
+ ``newpath`` already exists. (The rename operation should be
non-desctructive.)
- @param oldpath: the requested path (relative or absolute) of the
- existing file.
- @type oldpath: str
- @param newpath: the requested new path of the file.
- @type newpath: str
- @return: an SFTP error code like L{SFTP_OK}.
- @rtype: int
+ :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 SFTP_OP_UNSUPPORTED
def mkdir(self, path, attr):
"""
- Create a new directory with the given attributes. The C{attr}
+ Create a new directory with the given attributes. The ``attr``
object may be considered a "hint" and ignored.
- The C{attr} object will contain only those fields provided by the
- client in its request, so you should use C{hasattr} to check for
- the presense of fields before using them. In some cases, the C{attr}
+ The ``attr`` object will contain only those fields provided by the
+ client in its request, so you should use ``hasattr`` to check for
+ the presense of fields before using them. In some cases, the ``attr``
object may be completely empty.
- @param path: requested path (relative or absolute) of the new
- folder.
- @type path: str
- @param attr: requested attributes of the new folder.
- @type attr: L{SFTPAttributes}
- @return: an SFTP error code like L{SFTP_OK}.
- @rtype: int
+ :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 SFTP_OP_UNSUPPORTED
def rmdir(self, path):
"""
- Remove a directory if it exists. The C{path} should refer to an
+ Remove a directory if it exists. The ``path`` should refer to an
existing, empty folder -- otherwise this method should return an
error.
- @param path: requested path (relative or absolute) of the folder
- to remove.
- @type path: str
- @return: an SFTP error code like L{SFTP_OK}.
- @rtype: int
+ :param str path:
+ requested path (relative or absolute) of the folder to remove.
+ :return: an SFTP error code `int` like `.SFTP_OK`.
"""
return SFTP_OP_UNSUPPORTED
def chattr(self, path, attr):
"""
- Change the attributes of a file. The C{attr} object will contain
+ Change the attributes of a file. The ``attr`` object will contain
only those fields provided by the client in its request, so you
should check for the presence of fields before using them.
- @param path: requested path (relative or absolute) of the file to
- change.
- @type path: str
- @param attr: requested attributes to change on the file.
- @type attr: L{SFTPAttributes}
- @return: an error code like L{SFTP_OK}.
- @rtype: int
+ :param str path:
+ requested path (relative or absolute) of the file to change.
+ :param attr:
+ requested attributes to change on the file (an `.SFTPAttributes`
+ object)
+ :return: an error code `int` like `.SFTP_OK`.
"""
return SFTP_OP_UNSUPPORTED
def canonicalize(self, path):
"""
Return the canonical form of a path on the server. For example,
- if the server's home folder is C{/home/foo}, the path
- C{"../betty"} would be canonicalized to C{"/home/betty"}. Note
+ if the server's home folder is ``/home/foo``, the path
+ ``"../betty"`` would be canonicalized to ``"/home/betty"``. Note
the obvious security issues: if you're serving files only from a
specific folder, you probably don't want this method to reveal path
names outside that folder.
- You may find the python methods in C{os.path} useful, especially
- C{os.path.normpath} and C{os.path.realpath}.
+ You may find the Python methods in ``os.path`` useful, especially
+ ``os.path.normpath`` and ``os.path.realpath``.
- The default implementation returns C{os.path.normpath('/' + path)}.
+ The default implementation returns ``os.path.normpath('/' + path)``.
"""
if os.path.isabs(path):
out = os.path.normpath(path)
@@ -285,26 +274,23 @@ class SFTPServerInterface (object):
If the specified path doesn't refer to a symbolic link, an error
should be returned.
- @param path: path (relative or absolute) of the symbolic link.
- @type path: str
- @return: the target path of the symbolic link, or an error code like
- L{SFTP_NO_SUCH_FILE}.
- @rtype: str I{or error code}
+ :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`.
"""
return SFTP_OP_UNSUPPORTED
def symlink(self, target_path, path):
"""
- Create a symbolic link on the server, as new pathname C{path},
- with C{target_path} as the target of the link.
+ Create a symbolic link on the server, as new pathname ``path``,
+ with ``target_path`` as the target of the link.
- @param target_path: path (relative or absolute) of the target for
- this new symbolic link.
- @type target_path: str
- @param path: path (relative or absolute) of the symbolic link to
- create.
- @type path: str
- @return: an error code like C{SFTP_OK}.
- @rtype: int
+ :param str target_path:
+ path (relative or absolute) of the target for this new symbolic
+ link.
+ :param str path:
+ path (relative or absolute) of the symbolic link to create.
+ :return: an error code `int` like ``SFTP_OK``.
"""
return SFTP_OP_UNSUPPORTED
diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py
index b502b563..63ca6409 100644
--- a/paramiko/ssh_exception.py
+++ b/paramiko/ssh_exception.py
@@ -16,10 +16,6 @@
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-"""
-Exceptions defined by paramiko.
-"""
-
class SSHException (Exception):
"""
@@ -34,7 +30,7 @@ class AuthenticationException (SSHException):
possible to retry with different credentials. (Other classes specify more
specific reasons.)
- @since: 1.6
+ .. versionadded:: 1.6
"""
pass
@@ -52,12 +48,11 @@ class BadAuthenticationType (AuthenticationException):
the server isn't allowing that type. (It may only allow public-key, for
example.)
- @ivar allowed_types: list of allowed authentication types provided by the
- server (possible values are: C{"none"}, C{"password"}, and
- C{"publickey"}).
- @type allowed_types: list
+ :ivar list allowed_types:
+ list of allowed authentication types provided by the server (possible
+ values are: ``"none"``, ``"password"``, and ``"publickey"``).
- @since: 1.1
+ .. versionadded:: 1.1
"""
allowed_types = []
@@ -82,12 +77,11 @@ class PartialAuthentication (AuthenticationException):
class ChannelException (SSHException):
"""
- Exception raised when an attempt to open a new L{Channel} fails.
+ Exception raised when an attempt to open a new `.Channel` fails.
- @ivar code: the error code returned by the server
- @type code: int
+ :ivar int code: the error code returned by the server
- @since: 1.6
+ .. versionadded:: 1.6
"""
def __init__(self, code, text):
SSHException.__init__(self, text)
@@ -98,14 +92,11 @@ class BadHostKeyException (SSHException):
"""
The host key given by the SSH server did not match what we were expecting.
- @ivar hostname: the hostname of the SSH server
- @type hostname: str
- @ivar key: the host key presented by the server
- @type key: L{PKey}
- @ivar expected_key: the host key expected
- @type expected_key: L{PKey}
+ :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
- @since: 1.6
+ .. versionadded:: 1.6
"""
def __init__(self, hostname, got_key, expected_key):
SSHException.__init__(self, 'Host key for server %s does not match!' % hostname)
@@ -118,10 +109,8 @@ class ProxyCommandFailure (SSHException):
"""
The "ProxyCommand" found in the .ssh/config file returned an error.
- @ivar command: The command line that is generating this exception.
- @type command: str
- @ivar error: The error captured from the proxy command output.
- @type error: str
+ :ivar str command: The command line that is generating this exception.
+ :ivar str error: The error captured from the proxy command output.
"""
def __init__(self, command, error):
SSHException.__init__(self,
diff --git a/paramiko/transport.py b/paramiko/transport.py
index fcf074ae..393b1d35 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -17,13 +17,10 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-L{Transport} handles the core SSH2 protocol.
+Core protocol implementation
"""
-import os
import socket
-import string
-import struct
import sys
import threading
import time
@@ -34,7 +31,17 @@ from paramiko import util
from paramiko.auth_handler import AuthHandler
from paramiko.ssh_gss import GSSAuth
from paramiko.channel import Channel
-from paramiko.common import *
+from paramiko.common import rng, xffffffff, cMSG_CHANNEL_OPEN, cMSG_IGNORE, \
+ cMSG_GLOBAL_REQUEST, DEBUG, MSG_KEXINIT, MSG_IGNORE, MSG_DISCONNECT, \
+ MSG_DEBUG, ERROR, WARNING, cMSG_UNIMPLEMENTED, INFO, cMSG_KEXINIT, \
+ cMSG_NEWKEYS, MSG_NEWKEYS, cMSG_REQUEST_SUCCESS, cMSG_REQUEST_FAILURE, \
+ CONNECTION_FAILED_CODE, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, \
+ OPEN_SUCCEEDED, cMSG_CHANNEL_OPEN_FAILURE, cMSG_CHANNEL_OPEN_SUCCESS, \
+ MSG_GLOBAL_REQUEST, MSG_REQUEST_SUCCESS, MSG_REQUEST_FAILURE, \
+ MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, MSG_CHANNEL_OPEN, \
+ MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE, MSG_CHANNEL_DATA, \
+ MSG_CHANNEL_EXTENDED_DATA, MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_REQUEST, \
+ MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE
from paramiko.compress import ZlibCompressor, ZlibDecompressor
from paramiko.dsskey import DSSKey
from paramiko.kex_gex import KexGex
@@ -44,12 +51,13 @@ from paramiko.kex_gss import KexGSSGex, KexGSSGroup1, KexGSSGroup14, NullHostKey
from paramiko.message import Message
from paramiko.packet import Packetizer, NeedRekeyException
from paramiko.primes import ModulusPack
+from paramiko.py3compat import string_types, long, byte_ord, b
from paramiko.rsakey import RSAKey
from paramiko.ecdsakey import ECDSAKey
from paramiko.server import ServerInterface
from paramiko.sftp_client import SFTPClient
from paramiko.ssh_exception import (SSHException, BadAuthenticationType,
- ChannelException, ProxyCommandFailure)
+ ChannelException, ProxyCommandFailure)
from paramiko.util import retry_on_signal
from Crypto import Random
@@ -63,177 +71,56 @@ except ImportError:
# for thread cleanup
_active_threads = []
+
def _join_lingering_threads():
for thr in _active_threads:
thr.stop_thread()
+
import atexit
atexit.register(_join_lingering_threads)
-class SecurityOptions (object):
- """
- Simple object containing the security preferences of an ssh transport.
- These are tuples of acceptable ciphers, digests, key types, and key
- exchange algorithms, listed in order of preference.
-
- Changing the contents and/or order of these fields affects the underlying
- L{Transport} (but only if you change them before starting the session).
- If you try to add an algorithm that paramiko doesn't recognize,
- C{ValueError} will be raised. If you try to assign something besides a
- tuple to one of the fields, C{TypeError} will be raised.
- """
- #__slots__ = [ 'ciphers', 'digests', 'key_types', 'kex', 'compression', '_transport' ]
- __slots__ = '_transport'
-
- def __init__(self, transport):
- self._transport = transport
-
- def __repr__(self):
- """
- Returns a string representation of this object, for debugging.
-
- @rtype: str
- """
- return '<paramiko.SecurityOptions for %s>' % repr(self._transport)
-
- def _get_ciphers(self):
- return self._transport._preferred_ciphers
-
- def _get_digests(self):
- return self._transport._preferred_macs
-
- def _get_key_types(self):
- return self._transport._preferred_keys
-
- def _get_kex(self):
- return self._transport._preferred_kex
-
- def _get_compression(self):
- return self._transport._preferred_compression
-
- def _set(self, name, orig, x):
- if type(x) is list:
- x = tuple(x)
- if type(x) is not tuple:
- raise TypeError('expected tuple or list')
- possible = list(getattr(self._transport, orig).keys())
- forbidden = [n for n in x if n not in possible]
- if len(forbidden) > 0:
- raise ValueError('unknown cipher')
- setattr(self._transport, name, x)
-
- def _set_ciphers(self, x):
- self._set('_preferred_ciphers', '_cipher_info', x)
-
- def _set_digests(self, x):
- self._set('_preferred_macs', '_mac_info', x)
-
- def _set_key_types(self, x):
- self._set('_preferred_keys', '_key_info', x)
-
- def _set_kex(self, x):
- self._set('_preferred_kex', '_kex_info', x)
-
- def _set_compression(self, x):
- self._set('_preferred_compression', '_compression_info', x)
-
- ciphers = property(_get_ciphers, _set_ciphers, None,
- "Symmetric encryption ciphers")
- digests = property(_get_digests, _set_digests, None,
- "Digest (one-way hash) algorithms")
- key_types = property(_get_key_types, _set_key_types, None,
- "Public-key algorithms")
- kex = property(_get_kex, _set_kex, None, "Key exchange algorithms")
- compression = property(_get_compression, _set_compression, None,
- "Compression algorithms")
-
-
-class ChannelMap (object):
- def __init__(self):
- # (id -> Channel)
- self._map = weakref.WeakValueDictionary()
- self._lock = threading.Lock()
-
- def put(self, chanid, chan):
- self._lock.acquire()
- try:
- self._map[chanid] = chan
- finally:
- self._lock.release()
-
- def get(self, chanid):
- self._lock.acquire()
- try:
- return self._map.get(chanid, None)
- finally:
- self._lock.release()
-
- def delete(self, chanid):
- self._lock.acquire()
- try:
- try:
- del self._map[chanid]
- except KeyError:
- pass
- finally:
- self._lock.release()
-
- def values(self):
- self._lock.acquire()
- try:
- return list(self._map.values())
- finally:
- self._lock.release()
-
- def __len__(self):
- self._lock.acquire()
- try:
- return len(self._map)
- finally:
- self._lock.release()
-
-
class Transport (threading.Thread):
"""
An SSH Transport attaches to a stream (usually a socket), negotiates an
encrypted session, authenticates, and then creates stream tunnels, called
- L{Channel}s, across the session. Multiple channels can be multiplexed
- across a single session (and often are, in the case of port forwardings).
+ `channels <.Channel>`, across the session. Multiple channels can be
+ multiplexed across a single session (and often are, in the case of port
+ forwardings).
"""
-
_PROTO_ID = '2.0'
- _CLIENT_ID = 'paramiko_%s' % (paramiko.__version__)
+ _CLIENT_ID = 'paramiko_%s' % paramiko.__version__
- _preferred_ciphers = ( 'aes128-ctr', 'aes256-ctr', 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc',
- 'arcfour128', 'arcfour256' )
- _preferred_macs = ( 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' )
- _preferred_keys = ( 'ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256' )
+ _preferred_ciphers = ('aes128-ctr', 'aes256-ctr', 'aes128-cbc', 'blowfish-cbc',
+ 'aes256-cbc', '3des-cbc', 'arcfour128', 'arcfour256')
+ _preferred_macs = ('hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96')
+ _preferred_keys = ('ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256')
_preferred_kex = ( 'diffie-hellman-group1-sha1', 'diffie-hellman-group14-sha1', 'diffie-hellman-group-exchange-sha1' )
- _preferred_compression = ( 'none', )
+ _preferred_compression = ('none',)
_cipher_info = {
- 'aes128-ctr': { 'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 16 },
- 'aes256-ctr': { 'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 32 },
- 'blowfish-cbc': { 'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16 },
- 'aes128-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 16 },
- 'aes256-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 32 },
- '3des-cbc': { 'class': DES3, 'mode': DES3.MODE_CBC, '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 },
- }
+ 'aes128-ctr': {'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 16},
+ 'aes256-ctr': {'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 32},
+ 'blowfish-cbc': {'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16},
+ 'aes128-cbc': {'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 16},
+ 'aes256-cbc': {'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 32},
+ '3des-cbc': {'class': DES3, 'mode': DES3.MODE_CBC, '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 = {
- 'hmac-sha1': { 'class': SHA, 'size': 20 },
- 'hmac-sha1-96': { 'class': SHA, 'size': 12 },
- 'hmac-md5': { 'class': MD5, 'size': 16 },
- 'hmac-md5-96': { 'class': MD5, 'size': 12 },
- }
+ 'hmac-sha1': {'class': SHA, 'size': 20},
+ 'hmac-sha1-96': {'class': SHA, 'size': 12},
+ 'hmac-md5': {'class': MD5, 'size': 16},
+ 'hmac-md5-96': {'class': MD5, 'size': 12},
+ }
_key_info = {
'ssh-rsa': RSAKey,
'ssh-dss': DSSKey,
'ecdsa-sha2-nistp256': ECDSAKey,
- }
+ }
_kex_info = {
'diffie-hellman-group1-sha1': KexGroup1,
@@ -242,47 +129,47 @@ class Transport (threading.Thread):
'gss-group1-sha1-toWM5Slw5Ew8Mqkay+al2g==': KexGSSGroup1,
'gss-group14-sha1-toWM5Slw5Ew8Mqkay+al2g==': KexGSSGroup14,
'gss-gex-sha1-toWM5Slw5Ew8Mqkay+al2g==': KexGSSGex
- }
+ }
_compression_info = {
# zlib@openssh.com is just zlib, but only turned on after a successful
# authentication. openssh servers may only offer this type because
# they've had troubles with security holes in zlib in the past.
- 'zlib@openssh.com': ( ZlibCompressor, ZlibDecompressor ),
- 'zlib': ( ZlibCompressor, ZlibDecompressor ),
- 'none': ( None, None ),
+ 'zlib@openssh.com': (ZlibCompressor, ZlibDecompressor),
+ 'zlib': (ZlibCompressor, ZlibDecompressor),
+ 'none': (None, None),
}
-
_modulus_pack = None
def __init__(self, sock, gss_kex=False, gss_deleg_creds=True):
"""
Create a new SSH session over an existing socket, or socket-like
- object. This only creates the Transport object; it doesn't begin the
- SSH session yet. Use L{connect} or L{start_client} to begin a client
- session, or L{start_server} to begin a server session.
+ object. This only creates the `.Transport` object; it doesn't begin the
+ SSH session yet. Use `connect` or `start_client` to begin a client
+ session, or `start_server` to begin a server session.
If the object is not actually a socket, it must have the following
methods:
- - C{send(str)}: Writes from 1 to C{len(str)} bytes, and
- returns an int representing the number of bytes written. Returns
- 0 or raises C{EOFError} if the stream has been closed.
- - C{recv(int)}: Reads from 1 to C{int} bytes and returns them as a
- string. Returns 0 or raises C{EOFError} if the stream has been
- closed.
- - C{close()}: Closes the socket.
- - C{settimeout(n)}: Sets a (float) timeout on I/O operations.
+
+ - ``send(str)``: Writes from 1 to ``len(str)`` bytes, and returns an
+ int representing the number of bytes written. Returns
+ 0 or raises ``EOFError`` if the stream has been closed.
+ - ``recv(int)``: Reads from 1 to ``int`` bytes and returns them as a
+ string. Returns 0 or raises ``EOFError`` if the stream has been
+ closed.
+ - ``close()``: Closes the socket.
+ - ``settimeout(n)``: Sets a (float) timeout on I/O operations.
For ease of use, you may also pass in an address (as a tuple) or a host
- string as the C{sock} argument. (A host string is a hostname with an
- optional port (separated by C{":"}) which will be converted into a
- tuple of C{(hostname, port)}.) A socket will be connected to this
- address and used for communication. Exceptions from the C{socket} call
- may be thrown in this case.
+ string as the ``sock`` argument. (A host string is a hostname with an
+ optional port (separated by ``":"``) which will be converted into a
+ tuple of ``(hostname, port)``.) A socket will be connected to this
+ address and used for communication. Exceptions from the ``socket``
+ call may be thrown in this case.
- @param sock: a socket or socket-like object to create the session over.
- @type sock: socket
+ :param socket sock:
+ a socket or socket-like object to create the session over.
"""
if isinstance(sock, string_types):
# convert "host:port" into (host, port)
@@ -364,8 +251,8 @@ class Transport (threading.Thread):
# tracking open channels
self._channels = ChannelMap()
- self.channel_events = { } # (id -> Event)
- self.channels_seen = { } # (id -> True)
+ self.channel_events = {} # (id -> Event)
+ self.channels_seen = {} # (id -> True)
self._channel_counter = 1
self.window_size = 65536
self.max_packet_size = 34816
@@ -388,16 +275,14 @@ class Transport (threading.Thread):
# server mode:
self.server_mode = False
self.server_object = None
- self.server_key_dict = { }
- self.server_accepts = [ ]
+ self.server_key_dict = {}
+ self.server_accepts = []
self.server_accept_cv = threading.Condition(self.lock)
- self.subsystem_table = { }
+ self.subsystem_table = {}
def __repr__(self):
"""
Returns a string representation of this object, for debugging.
-
- @rtype: str
"""
out = '<paramiko.Transport at %s' % hex(long(id(self)) & xffffffff)
if not self.active:
@@ -423,20 +308,17 @@ class Transport (threading.Thread):
use the connection (without corrupting the session). Use this method
to clean up a Transport object without disrupting the other process.
- @since: 1.5.3
+ .. versionadded:: 1.5.3
"""
self.sock.close()
self.close()
def get_security_options(self):
"""
- Return a L{SecurityOptions} object which can be used to tweak the
- encryption algorithms this transport will permit, and the order of
- preference for them.
-
- @return: an object that can be used to change the preferred algorithms
- for encryption, digest (hash), public key, and key exchange.
- @rtype: L{SecurityOptions}
+ Return a `.SecurityOptions` object which can be used to tweak the
+ encryption algorithms this transport will permit (for encryption,
+ digest/hash operations, public keys, and key exchanges) and the order
+ of preference for them.
"""
return SecurityOptions(self)
@@ -455,32 +337,31 @@ class Transport (threading.Thread):
def start_client(self, event=None):
"""
Negotiate a new SSH2 session as a client. This is the first step after
- creating a new L{Transport}. A separate thread is created for protocol
+ creating a new `.Transport`. A separate thread is created for protocol
negotiation.
If an event is passed in, this method returns immediately. When
- negotiation is done (successful or not), the given C{Event} will
- be triggered. On failure, L{is_active} will return C{False}.
+ negotiation is done (successful or not), the given ``Event`` will
+ be triggered. On failure, `is_active` will return ``False``.
- (Since 1.4) If C{event} is C{None}, this method will not return until
+ (Since 1.4) If ``event`` is ``None``, this method will not return until
negotation is done. On success, the method returns normally.
Otherwise an SSHException is raised.
After a successful negotiation, you will usually want to authenticate,
- calling L{auth_password <Transport.auth_password>} or
- L{auth_publickey <Transport.auth_publickey>}.
+ calling `auth_password <Transport.auth_password>` or
+ `auth_publickey <Transport.auth_publickey>`.
- @note: L{connect} is a simpler method for connecting as a client.
+ .. note:: `connect` is a simpler method for connecting as a client.
- @note: After calling this method (or L{start_server} or L{connect}),
+ .. note:: After calling this method (or `start_server` or `connect`),
you should no longer directly read from or write to the original
socket object.
- @param event: an event to trigger when negotiation is complete
- (optional)
- @type event: threading.Event
+ :param .threading.Event event:
+ an event to trigger when negotiation is complete (optional)
- @raise SSHException: if negotiation fails (and no C{event} was passed
+ :raises SSHException: if negotiation fails (and no ``event`` was passed
in)
"""
self.active = True
@@ -507,41 +388,42 @@ class Transport (threading.Thread):
def start_server(self, event=None, server=None):
"""
Negotiate a new SSH2 session as a server. This is the first step after
- creating a new L{Transport} and setting up your server host key(s). A
+ creating a new `.Transport` and setting up your server host key(s). A
separate thread is created for protocol negotiation.
If an event is passed in, this method returns immediately. When
- negotiation is done (successful or not), the given C{Event} will
- be triggered. On failure, L{is_active} will return C{False}.
+ negotiation is done (successful or not), the given ``Event`` will
+ be triggered. On failure, `is_active` will return ``False``.
- (Since 1.4) If C{event} is C{None}, this method will not return until
+ (Since 1.4) If ``event`` is ``None``, this method will not return until
negotation is done. On success, the method returns normally.
Otherwise an SSHException is raised.
After a successful negotiation, the client will need to authenticate.
- Override the methods
- L{get_allowed_auths <ServerInterface.get_allowed_auths>},
- L{check_auth_none <ServerInterface.check_auth_none>},
- L{check_auth_password <ServerInterface.check_auth_password>}, and
- L{check_auth_publickey <ServerInterface.check_auth_publickey>} in the
- given C{server} object to control the authentication process.
-
- After a successful authentication, the client should request to open
- a channel. Override
- L{check_channel_request <ServerInterface.check_channel_request>} in the
- given C{server} object to allow channels to be opened.
-
- @note: After calling this method (or L{start_client} or L{connect}),
- you should no longer directly read from or write to the original
- socket object.
-
- @param event: an event to trigger when negotiation is complete.
- @type event: threading.Event
- @param server: an object used to perform authentication and create
- L{Channel}s.
- @type server: L{server.ServerInterface}
-
- @raise SSHException: if negotiation fails (and no C{event} was passed
+ Override the methods `get_allowed_auths
+ <.ServerInterface.get_allowed_auths>`, `check_auth_none
+ <.ServerInterface.check_auth_none>`, `check_auth_password
+ <.ServerInterface.check_auth_password>`, and `check_auth_publickey
+ <.ServerInterface.check_auth_publickey>` in the given ``server`` object
+ to control the authentication process.
+
+ After a successful authentication, the client should request to open a
+ channel. Override `check_channel_request
+ <.ServerInterface.check_channel_request>` in the given ``server``
+ object to allow channels to be opened.
+
+ .. note::
+ After calling this method (or `start_client` or `connect`), you
+ should no longer directly read from or write to the original socket
+ object.
+
+ :param .threading.Event event:
+ an event to trigger when negotiation is complete.
+ :param .ServerInterface server:
+ an object used to perform authentication and create `channels
+ <.Channel>`
+
+ :raises SSHException: if negotiation fails (and no ``event`` was passed
in)
"""
if server is None:
@@ -577,9 +459,8 @@ class Transport (threading.Thread):
key info, not just the public half. Only one key of each type (RSA or
DSS) is kept.
- @param key: the host key to add, usually an L{RSAKey <rsakey.RSAKey>} or
- L{DSSKey <dsskey.DSSKey>}.
- @type key: L{PKey <pkey.PKey>}
+ :param .PKey key:
+ the host key to add, usually an `.RSAKey` or `.DSSKey`.
"""
self.server_key_dict[key.get_name()] = key
@@ -587,15 +468,16 @@ class Transport (threading.Thread):
"""
Return the active host key, in server mode. After negotiating with the
client, this method will return the negotiated host key. If only one
- type of host key was set with L{add_server_key}, that's the only key
+ type of host key was set with `add_server_key`, that's the only key
that will ever be returned. But in cases where you have set more than
one type of host key (for example, an RSA key and a DSS key), the key
type will be negotiated by the client, and this method will return the
key of the type agreed on. If the host key has not been negotiated
- yet, C{None} is returned. In client mode, the behavior is undefined.
+ yet, ``None`` is returned. In client mode, the behavior is undefined.
- @return: host key of the type negotiated by the client, or C{None}.
- @rtype: L{PKey <pkey.PKey>}
+ :return:
+ host key (`.PKey`) of the type negotiated by the client, or
+ ``None``.
"""
try:
return self.server_key_dict[self.host_key_type]
@@ -605,7 +487,7 @@ class Transport (threading.Thread):
def load_server_moduli(filename=None):
"""
- I{(optional)}
+ (optional)
Load a file of prime moduli for use in doing group-exchange key
negotiation in server mode. It's a rather obscure option and can be
safely ignored.
@@ -614,24 +496,23 @@ class Transport (threading.Thread):
negotiation, which asks the server to send a random prime number that
fits certain criteria. These primes are pretty difficult to compute,
so they can't be generated on demand. But many systems contain a file
- of suitable primes (usually named something like C{/etc/ssh/moduli}).
- If you call C{load_server_moduli} and it returns C{True}, then this
+ of suitable primes (usually named something like ``/etc/ssh/moduli``).
+ If you call `load_server_moduli` and it returns ``True``, then this
file of primes has been loaded and we will support "group-exchange" in
server mode. Otherwise server mode will just claim that it doesn't
support that method of key negotiation.
- @param filename: optional path to the moduli file, if you happen to
- know that it's not in a standard location.
- @type filename: str
- @return: True if a moduli file was successfully loaded; False
- otherwise.
- @rtype: bool
+ :param str filename:
+ optional path to the moduli file, if you happen to know that it's
+ not in a standard location.
+ :return:
+ True if a moduli file was successfully loaded; False otherwise.
- @note: This has no effect when used in client mode.
+ .. note:: This has no effect when used in client mode.
"""
Transport._modulus_pack = ModulusPack(rng)
# places to look for the openssh "moduli" file
- file_list = [ '/etc/ssh/moduli', '/usr/local/etc/moduli' ]
+ file_list = ['/etc/ssh/moduli', '/usr/local/etc/moduli']
if filename is not None:
file_list.insert(0, filename)
for fn in file_list:
@@ -660,15 +541,14 @@ class Transport (threading.Thread):
"""
Return the host key of the server (in client mode).
- @note: Previously this call returned a tuple of (key type, key string).
- You can get the same effect by calling
- L{PKey.get_name <pkey.PKey.get_name>} for the key type, and
- C{str(key)} for the key string.
+ .. note::
+ Previously this call returned a tuple of ``(key type, key
+ string)``. You can get the same effect by calling `.PKey.get_name`
+ for the key type, and ``str(key)`` for the key string.
- @raise SSHException: if no session is currently active.
+ :raises SSHException: if no session is currently active.
- @return: public key of the remote server
- @rtype: L{PKey <pkey.PKey>}
+ :return: public key (`.PKey`) of the remote server
"""
if (not self.active) or (not self.initial_kex_done):
raise SSHException('No existing session')
@@ -678,37 +558,36 @@ class Transport (threading.Thread):
"""
Return true if this session is active (open).
- @return: True if the session is still active (open); False if the
- session is closed
- @rtype: bool
+ :return:
+ True if the session is still active (open); False if the session is
+ closed
"""
return self.active
def open_session(self):
"""
- Request a new channel to the server, of type C{"session"}. This
- is just an alias for C{open_channel('session')}.
+ Request a new channel to the server, of type ``"session"``. This is
+ just an alias for calling `open_channel` with an argument of
+ ``"session"``.
- @return: a new L{Channel}
- @rtype: L{Channel}
+ :return: a new `.Channel`
- @raise 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('session')
def open_x11_channel(self, src_addr=None):
"""
- Request a new channel to the client, of type C{"x11"}. This
- is just an alias for C{open_channel('x11', src_addr=src_addr)}.
+ Request a new channel to the client, of type ``"x11"``. This
+ is just an alias for ``open_channel('x11', src_addr=src_addr)``.
- @param src_addr: the source address of the x11 server (port is the
+ :param tuple src_addr:
+ the source address (``(str, int)``) of the x11 server (port is the
x11 port, ie. 6010)
- @type src_addr: (str, int)
- @return: a new L{Channel}
- @rtype: L{Channel}
+ :return: a new `.Channel`
- @raise 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)
@@ -716,51 +595,47 @@ class Transport (threading.Thread):
def open_forward_agent_channel(self):
"""
Request a new channel to the client, of type
- C{"auth-agent@openssh.com"}.
+ ``"auth-agent@openssh.com"``.
- This is just an alias for C{open_channel('auth-agent@openssh.com')}.
- @return: a new L{Channel}
- @rtype: L{Channel}
+ This is just an alias for ``open_channel('auth-agent@openssh.com')``.
- @raise SSHException: if the request is rejected or the session ends
- prematurely
+ :return: a new `.Channel`
+
+ :raises SSHException:
+ if the request is rejected or the session ends prematurely
"""
return self.open_channel('auth-agent@openssh.com')
def open_forwarded_tcpip_channel(self, src_addr, dest_addr):
"""
- Request a new channel back to the client, of type C{"forwarded-tcpip"}.
+ Request a new channel back to the client, of type ``"forwarded-tcpip"``.
This is used after a client has requested port forwarding, for sending
incoming connections back to the client.
- @param src_addr: originator's address
- @param src_port: originator's port
- @param dest_addr: local (server) connected address
- @param dest_port: local (server) connected port
+ :param src_addr: originator's address
+ :param dest_addr: local (server) connected address
"""
return self.open_channel('forwarded-tcpip', dest_addr, src_addr)
def open_channel(self, kind, dest_addr=None, src_addr=None):
"""
- Request a new channel to the server. L{Channel}s are socket-like
- objects used for the actual transfer of data across the session.
- You may only request a channel after negotiating encryption (using
- L{connect} or L{start_client}) and authenticating.
-
- @param kind: the kind of channel requested (usually C{"session"},
- C{"forwarded-tcpip"}, C{"direct-tcpip"}, or C{"x11"})
- @type kind: str
- @param dest_addr: the destination address of this port forwarding,
- if C{kind} is C{"forwarded-tcpip"} or C{"direct-tcpip"} (ignored
- for other channel types)
- @type dest_addr: (str, int)
- @param src_addr: the source address of this port forwarding, if
- C{kind} is C{"forwarded-tcpip"}, C{"direct-tcpip"}, or C{"x11"}
- @type src_addr: (str, int)
- @return: a new L{Channel} on success
- @rtype: L{Channel}
-
- @raise SSHException: if the request is rejected or the session ends
+ Request a new channel to the server. `Channels <.Channel>` are
+ socket-like objects used for the actual transfer of data across the
+ session. You may only request a channel after negotiating encryption
+ (using `connect` or `start_client`) and authenticating.
+
+ :param str kind:
+ the kind of channel requested (usually ``"session"``,
+ ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"``)
+ :param tuple dest_addr:
+ the destination address (address + port tuple) of this port
+ forwarding, if ``kind`` is ``"forwarded-tcpip"`` or
+ ``"direct-tcpip"`` (ignored for other channel types)
+ :param src_addr: the source address of this port forwarding, if
+ ``kind`` is ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"``
+ :return: a new `.Channel` on success
+
+ :raises SSHException: if the request is rejected or the session ends
prematurely
"""
if not self.active:
@@ -792,7 +667,7 @@ class Transport (threading.Thread):
self.lock.release()
self._send_user_message(m)
while True:
- event.wait(0.1);
+ event.wait(0.1)
if not self.active:
e = self.get_exception()
if e is None:
@@ -818,24 +693,22 @@ class Transport (threading.Thread):
handler(channel, (origin_addr, origin_port), (server_addr, server_port))
- where C{server_addr} and C{server_port} are the address and port that
+ where ``server_addr`` and ``server_port`` are the address and port that
the server was listening on.
If no handler is set, the default behavior is to send new incoming
forwarded connections into the accept queue, to be picked up via
- L{accept}.
+ `accept`.
- @param address: the address to bind when forwarding
- @type address: str
- @param port: the port to forward, or 0 to ask the server to allocate
- any port
- @type port: int
- @param handler: optional handler for incoming forwarded connections
- @type handler: function(Channel, (str, int), (str, int))
- @return: the port # allocated by the server
- @rtype: int
+ :param str address: the address to bind when forwarding
+ :param int port:
+ the port to forward, or 0 to ask the server to allocate any port
+ :param callable handler:
+ optional handler for incoming forwarded connections, of the form
+ ``func(Channel, (str, int), (str, int))``.
+ :return: the port number (`int`) allocated by the server
- @raise 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')
@@ -846,7 +719,7 @@ class Transport (threading.Thread):
if port == 0:
port = response.get_int()
if handler is None:
- def default_handler(channel, src_addr_port, dest_addr_port):
+ def default_handler(channel, src_addr, dest_addr_port):
#src_addr, src_port = src_addr_port
#dest_addr, dest_port = dest_addr_port
self._queue_incoming_channel(channel)
@@ -860,10 +733,8 @@ class Transport (threading.Thread):
connections to the given address & port will be forwarded across this
ssh connection.
- @param address: the address to stop forwarding
- @type address: str
- @param port: the port to stop forwarding
- @type port: int
+ :param str address: the address to stop forwarding
+ :param int port: the port to stop forwarding
"""
if not self.active:
return
@@ -872,13 +743,13 @@ class Transport (threading.Thread):
def open_sftp_client(self):
"""
- Create an SFTP client channel from an open transport. On success,
- an SFTP session will be opened with the remote host, and a new
- SFTPClient object will be returned.
+ Create an SFTP client channel from an open transport. On success, an
+ SFTP session will be opened with the remote host, and a new
+ `.SFTPClient` object will be returned.
- @return: a new L{SFTPClient} object, referring to an sftp session
- (channel) across this transport
- @rtype: L{SFTPClient}
+ :return:
+ a new `.SFTPClient` referring to an sftp session (channel) across
+ this transport
"""
return SFTPClient.from_transport(self)
@@ -889,9 +760,9 @@ class Transport (threading.Thread):
also be used as a keep-alive for long lived connections traversing
firewalls.
- @param byte_count: the number of random bytes to send in the payload of the
- ignored packet -- defaults to a random number from 10 to 41.
- @type byte_count: int
+ :param int byte_count:
+ the number of random bytes to send in the payload of the ignored
+ packet -- defaults to a random number from 10 to 41.
"""
m = Message()
m.add_byte(cMSG_IGNORE)
@@ -909,7 +780,7 @@ class Transport (threading.Thread):
traffic both ways as the two sides swap keys and do computations. This
method returns when the session has switched to new keys.
- @raise SSHException: if the key renegotiation failed (which causes the
+ :raises SSHException: if the key renegotiation failed (which causes the
session to end)
"""
self.completion_event = threading.Event()
@@ -928,34 +799,33 @@ class Transport (threading.Thread):
def set_keepalive(self, interval):
"""
Turn on/off keepalive packets (default is off). If this is set, after
- C{interval} seconds without sending any data over the connection, a
+ ``interval`` seconds without sending any data over the connection, a
"keepalive" packet will be sent (and ignored by the remote host). This
can be useful to keep connections alive over a NAT, for example.
- @param interval: seconds to wait before sending a keepalive packet (or
+ :param int interval:
+ seconds to wait before sending a keepalive packet (or
0 to disable keepalives).
- @type interval: int
"""
self.packetizer.set_keepalive(interval,
- lambda x=weakref.proxy(self): x.global_request('keepalive@lag.net', wait=False))
+ lambda x=weakref.proxy(self): x.global_request('keepalive@lag.net', wait=False))
def global_request(self, kind, data=None, wait=True):
"""
Make a global request to the remote host. These are normally
extensions to the SSH2 protocol.
- @param kind: name of the request.
- @type kind: str
- @param data: an optional tuple containing additional data to attach
- to the request.
- @type data: tuple
- @param wait: C{True} if this method should not return until a response
- is received; C{False} otherwise.
- @type wait: bool
- @return: a L{Message} containing possible additional data if the
- request was successful (or an empty L{Message} if C{wait} was
- C{False}); C{None} if the request was denied.
- @rtype: L{Message}
+ :param str kind: name of the request.
+ :param tuple data:
+ an optional tuple containing additional data to attach to the
+ request.
+ :param bool wait:
+ ``True`` if this method should not return until a response is
+ received; ``False`` otherwise.
+ :return:
+ a `.Message` containing possible additional data if the request was
+ successful (or an empty `.Message` if ``wait`` was ``False``);
+ ``None`` if the request was denied.
"""
if wait:
self.completion_event = threading.Event()
@@ -980,14 +850,12 @@ class Transport (threading.Thread):
def accept(self, timeout=None):
"""
Return the next channel opened by the client over this transport, in
- server mode. If no channel is opened before the given timeout, C{None}
+ server mode. If no channel is opened before the given timeout, ``None``
is returned.
- @param timeout: seconds to wait for a channel, or C{None} to wait
- forever
- @type timeout: int
- @return: a new Channel opened by the client
- @rtype: L{Channel}
+ :param int timeout:
+ seconds to wait for a channel, or ``None`` to wait forever
+ :return: a new `.Channel` opened by the client
"""
self.lock.acquire()
try:
@@ -1009,46 +877,42 @@ class Transport (threading.Thread):
"""
Negotiate an SSH2 session, and optionally verify the server's host key
and authenticate using a password or private key. This is a shortcut
- for L{start_client}, L{get_remote_server_key}, and
- L{Transport.auth_password} or L{Transport.auth_publickey}. Use those
+ for `start_client`, `get_remote_server_key`, and
+ `Transport.auth_password` or `Transport.auth_publickey`. Use those
methods if you want more control.
You can use this method immediately after creating a Transport to
negotiate encryption with a server. If it fails, an exception will be
thrown. On success, the method will return cleanly, and an encrypted
- session exists. You may immediately call L{open_channel} or
- L{open_session} to get a L{Channel} object, which is used for data
+ session exists. You may immediately call `open_channel` or
+ `open_session` to get a `.Channel` object, which is used for data
transfer.
- @note: If you fail to supply a password or private key, this method may
- succeed, but a subsequent L{open_channel} or L{open_session} call may
- fail because you haven't authenticated yet.
-
- @param hostkey: the host key expected from the server, or C{None} if
- you don't want to do host key verification.
- @type hostkey: L{PKey<pkey.PKey>}
- @param username: the username to authenticate as.
- @type username: str
- @param password: a password to use for authentication, if you want to
- use password authentication; otherwise C{None}.
- @type password: str
- @param pkey: a private key to use for authentication, if you want to
- use private key authentication; otherwise C{None}.
- @type pkey: L{PKey<pkey.PKey>}
- @param gss_host: The host to authenticate to
- @type gss_host: str
- @param gss_auth: Enable or Disable GSSAPI Authentication
- @type gss_auth: Boolean
- @param gss_kex: Perform GSS-API Key Exchange and user authentication
- @type gss_kex: Boolean
- @param gss_deleg_creds: Use delegated credentails with GSSAPI
- @type gss_deleg_cred: Boolean
-
- @raise SSHException: if the SSH2 negotiation fails, the host key
+ .. note::
+ If you fail to supply a password or private key, this method may
+ succeed, but a subsequent `open_channel` or `open_session` call may
+ fail because you haven't authenticated yet.
+
+ :param .PKey hostkey:
+ the host key expected from the server, or ``None`` if you don't
+ want to do host key verification.
+ :param str username: the username to authenticate as.
+ :param str password:
+ a password to use for authentication, if you want to use password
+ authentication; otherwise ``None``.
+ :param .PKey pkey:
+ a private key to use for authentication, if you want to use private
+ key authentication; otherwise ``None``.
+ :param str gss_host: The targets name in the kerberos database. default: hostname
+ :param bool gss_auth: ``True`` if you want to use GSS-API authentication
+ :param bool gss_kex: Perform GSS-API Key Exchange and user authentication
+ :param bool gss_deleg_creds: Delegate GSS-API client credentials or not
+
+ :raises SSHException: if the SSH2 negotiation fails, the host key
supplied by the server is incorrect, or authentication fails.
"""
if hostkey is not None:
- self._preferred_keys = [ hostkey.get_name() ]
+ self._preferred_keys = [hostkey.get_name()]
self.start_client()
@@ -1084,13 +948,13 @@ class Transport (threading.Thread):
"""
Return any exception that happened during the last server request.
This can be used to fetch more specific error information after using
- calls like L{start_client}. The exception (if any) is cleared after
+ calls like `start_client`. The exception (if any) is cleared after
this call.
- @return: an exception, or C{None} if there is no stored exception.
- @rtype: Exception
+ :return:
+ an exception, or ``None`` if there is no stored exception.
- @since: 1.1
+ .. versionadded:: 1.1
"""
self.lock.acquire()
try:
@@ -1104,17 +968,15 @@ class Transport (threading.Thread):
"""
Set the handler class for a subsystem in server mode. If a request
for this subsystem is made on an open ssh channel later, this handler
- will be constructed and called -- see L{SubsystemHandler} for more
+ will be constructed and called -- see `.SubsystemHandler` for more
detailed documentation.
Any extra parameters (including keyword arguments) are saved and
- passed to the L{SubsystemHandler} constructor later.
+ passed to the `.SubsystemHandler` constructor later.
- @param name: name of the subsystem.
- @type name: str
- @param handler: subclass of L{SubsystemHandler} that handles this
- subsystem.
- @type handler: class
+ :param str name: name of the subsystem.
+ :param class handler:
+ subclass of `.SubsystemHandler` that handles this subsystem.
"""
try:
self.lock.acquire()
@@ -1126,10 +988,10 @@ class Transport (threading.Thread):
"""
Return true if this session is active and authenticated.
- @return: True if the session is still open and has been authenticated
+ :return:
+ True if the session is still open and has been authenticated
successfully; False if authentication failed and/or the session is
closed.
- @rtype: bool
"""
return self.active and (self.auth_handler is not None) and self.auth_handler.is_authenticated()
@@ -1137,34 +999,44 @@ class Transport (threading.Thread):
"""
Return the username this connection is authenticated for. If the
session is not authenticated (or authentication failed), this method
- returns C{None}.
+ returns ``None``.
- @return: username that was authenticated, or C{None}.
- @rtype: string
+ :return: username that was authenticated (a `str`), or ``None``.
"""
if not self.active or (self.auth_handler is None):
return None
return self.auth_handler.get_username()
+ def get_banner(self):
+ """
+ Return the banner supplied by the server upon connect. If no banner is
+ supplied, this method returns C{None}.
+
+ @return: server supplied banner, or C{None}.
+ @rtype: string
+ """
+ if not self.active or (self.auth_handler is None):
+ return None
+ return self.auth_handler.banner
+
def auth_none(self, username):
"""
Try to authenticate to the server using no authentication at all.
This will almost always fail. It may be useful for determining the
list of authentication types supported by the server, by catching the
- L{BadAuthenticationType} exception raised.
+ `.BadAuthenticationType` exception raised.
- @param username: the username to authenticate as
- @type username: string
- @return: list of auth types permissible for the next stage of
+ :param str username: the username to authenticate as
+ :return:
+ `list` of auth types permissible for the next stage of
authentication (normally empty)
- @rtype: list
- @raise BadAuthenticationType: if "none" authentication isn't allowed
+ :raises BadAuthenticationType: if "none" authentication isn't allowed
by the server for this user
- @raise SSHException: if the authentication failed due to a network
+ :raises SSHException: if the authentication failed due to a network
error
- @since: 1.5
+ .. versionadded:: 1.5
"""
if (not self.active) or (not self.initial_kex_done):
raise SSHException('No existing session')
@@ -1178,16 +1050,16 @@ class Transport (threading.Thread):
Authenticate to the server using a password. The username and password
are sent over an encrypted link.
- If an C{event} is passed in, this method will return immediately, and
+ If an ``event`` is passed in, this method will return immediately, and
the event will be triggered once authentication succeeds or fails. On
- success, L{is_authenticated} will return C{True}. On failure, you may
- use L{get_exception} to get more detailed error information.
+ success, `is_authenticated` will return ``True``. On failure, you may
+ use `get_exception` to get more detailed error information.
Since 1.1, if no event is passed, this method will block until the
authentication succeeds or fails. On failure, an exception is raised.
Otherwise, the method simply returns.
- Since 1.5, if no event is passed and C{fallback} is C{True} (the
+ Since 1.5, if no event is passed and ``fallback`` is ``True`` (the
default), if the server doesn't support plain password authentication
but does support so-called "keyboard-interactive" mode, an attempt
will be made to authenticate using this interactive mode. If it fails,
@@ -1200,26 +1072,23 @@ class Transport (threading.Thread):
this method will return a list of auth types permissible for the next
step. Otherwise, in the normal case, an empty list is returned.
- @param username: the username to authenticate as
- @type username: str
- @param password: the password to authenticate with
- @type password: str or unicode
- @param event: an event to trigger when the authentication attempt is
- complete (whether it was successful or not)
- @type event: threading.Event
- @param fallback: C{True} if an attempt at an automated "interactive"
- password auth should be made if the server doesn't support normal
- password auth
- @type fallback: bool
- @return: list of auth types permissible for the next stage of
+ :param str username: the username to authenticate as
+ :param basestring password: the password to authenticate with
+ :param .threading.Event event:
+ an event to trigger when the authentication attempt is complete
+ (whether it was successful or not)
+ :param bool fallback:
+ ``True`` if an attempt at an automated "interactive" password auth
+ should be made if the server doesn't support normal password auth
+ :return:
+ `list` of auth types permissible for the next stage of
authentication (normally empty)
- @rtype: list
- @raise 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)
- @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 send the password unless we're on a secure link
@@ -1249,22 +1118,21 @@ class Transport (threading.Thread):
# to try to fake out automated scripting of the exact
# type we're doing here. *shrug* :)
return []
- return [ password ]
+ return [password]
return self.auth_interactive(username, handler)
except SSHException:
# attempt failed; just raise the original exception
raise e
- return None
def auth_publickey(self, username, key, event=None):
"""
Authenticate to the server using a private key. The key is used to
sign data from the server, so it must include the private part.
- If an C{event} is passed in, this method will return immediately, and
+ If an ``event`` is passed in, this method will return immediately, and
the event will be triggered once authentication succeeds or fails. On
- success, L{is_authenticated} will return C{True}. On failure, you may
- use L{get_exception} to get more detailed error information.
+ success, `is_authenticated` will return ``True``. On failure, you may
+ use `get_exception` to get more detailed error information.
Since 1.1, if no event is passed, this method will block until the
authentication succeeds or fails. On failure, an exception is raised.
@@ -1274,22 +1142,20 @@ class Transport (threading.Thread):
this method will return a list of auth types permissible for the next
step. Otherwise, in the normal case, an empty list is returned.
- @param username: the username to authenticate as
- @type username: string
- @param key: the private key to authenticate with
- @type key: L{PKey <pkey.PKey>}
- @param event: an event to trigger when the authentication attempt is
- complete (whether it was successful or not)
- @type event: threading.Event
- @return: list of auth types permissible for the next stage of
+ :param str username: the username to authenticate as
+ :param .PKey key: the private key to authenticate with
+ :param .threading.Event event:
+ an event to trigger when the authentication attempt is complete
+ (whether it was successful or not)
+ :return:
+ `list` of auth types permissible for the next stage of
authentication (normally empty)
- @rtype: list
- @raise 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)
- @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
@@ -1317,15 +1183,15 @@ class Transport (threading.Thread):
if the server continues to ask questions.
The handler is expected to be a callable that will handle calls of the
- form: C{handler(title, instructions, prompt_list)}. The C{title} is
- meant to be a dialog-window title, and the C{instructions} are user
- instructions (both are strings). C{prompt_list} will be a list of
- prompts, each prompt being a tuple of C{(str, bool)}. The string is
+ form: ``handler(title, instructions, prompt_list)``. The ``title`` is
+ meant to be a dialog-window title, and the ``instructions`` are user
+ instructions (both are strings). ``prompt_list`` will be a list of
+ prompts, each prompt being a tuple of ``(str, bool)``. The string is
the prompt and the boolean indicates whether the user text should be
echoed.
A sample call would thus be:
- C{handler('title', 'instructions', [('Password:', False)])}.
+ ``handler('title', 'instructions', [('Password:', False)])``.
The handler should return a list or tuple of answers to the server's
questions.
@@ -1334,22 +1200,19 @@ class Transport (threading.Thread):
this method will return a list of auth types permissible for the next
step. Otherwise, in the normal case, an empty list is returned.
- @param username: the username to authenticate as
- @type username: string
- @param handler: a handler for responding to server questions
- @type handler: callable
- @param submethods: a string list of desired submethods (optional)
- @type submethods: str
- @return: list of auth types permissible for the next stage of
+ :param str username: the username to authenticate as
+ :param callable handler: a handler for responding to server questions
+ :param str submethods: a string list of desired submethods (optional)
+ :return:
+ `list` of auth types permissible for the next stage of
authentication (normally empty).
- @rtype: list
- @raise BadAuthenticationType: if public-key authentication isn't
+ :raises BadAuthenticationType: if public-key authentication isn't
allowed by the server for this user
- @raise AuthenticationException: if the authentication failed
- @raise SSHException: if there was a network error
+ :raises AuthenticationException: if the authentication failed
+ :raises SSHException: if there was a network error
- @since: 1.5
+ .. versionadded:: 1.5
"""
if (not self.active) or (not self.initial_kex_done):
# we should never try to authenticate unless we're on a secure link
@@ -1413,14 +1276,13 @@ class Transport (threading.Thread):
def set_log_channel(self, name):
"""
Set the channel for this transport's logging. The default is
- C{"paramiko.transport"} but it can be set to anything you want.
- (See the C{logging} module for more info.) SSH Channels will log
- to a sub-channel of the one specified.
+ ``"paramiko.transport"`` but it can be set to anything you want. (See
+ the `.logging` module for more info.) SSH Channels will log to a
+ sub-channel of the one specified.
- @param name: new channel name for logging
- @type name: str
+ :param str name: new channel name for logging
- @since: 1.1
+ .. versionadded:: 1.1
"""
self.log_name = name
self.logger = util.get_logger(name)
@@ -1430,10 +1292,9 @@ class Transport (threading.Thread):
"""
Return the channel name used for this transport's logging.
- @return: channel name.
- @rtype: str
+ :return: channel name as a `str`
- @since: 1.2
+ .. versionadded:: 1.2
"""
return self.log_name
@@ -1443,54 +1304,54 @@ class Transport (threading.Thread):
the logs. Normally you would want this off (which is the default),
but if you are debugging something, it may be useful.
- @param hexdump: C{True} to log protocol traffix (in hex) to the log;
- C{False} otherwise.
- @type hexdump: bool
+ :param bool hexdump:
+ ``True`` to log protocol traffix (in hex) to the log; ``False``
+ otherwise.
"""
self.packetizer.set_hexdump(hexdump)
def get_hexdump(self):
"""
- Return C{True} if the transport is currently logging hex dumps of
+ Return ``True`` if the transport is currently logging hex dumps of
protocol traffic.
- @return: C{True} if hex dumps are being logged
- @rtype: bool
+ :return: ``True`` if hex dumps are being logged, else ``False``.
- @since: 1.4
+ .. versionadded:: 1.4
"""
return self.packetizer.get_hexdump()
def use_compression(self, compress=True):
"""
Turn on/off compression. This will only have an affect before starting
- the transport (ie before calling L{connect}, etc). By default,
+ the transport (ie before calling `connect`, etc). By default,
compression is off since it negatively affects interactive sessions.
- @param compress: C{True} to ask the remote client/server to compress
- traffic; C{False} to refuse compression
- @type compress: bool
+ :param bool compress:
+ ``True`` to ask the remote client/server to compress traffic;
+ ``False`` to refuse compression
- @since: 1.5.2
+ .. versionadded:: 1.5.2
"""
if compress:
- self._preferred_compression = ( 'zlib@openssh.com', 'zlib', 'none' )
+ self._preferred_compression = ('zlib@openssh.com', 'zlib', 'none')
else:
- self._preferred_compression = ( 'none', )
+ self._preferred_compression = ('none',)
def getpeername(self):
"""
Return the address of the remote side of this Transport, if possible.
- This is effectively a wrapper around C{'getpeername'} on the underlying
- socket. If the socket-like object has no C{'getpeername'} method,
- then C{("unknown", 0)} is returned.
+ This is effectively a wrapper around ``'getpeername'`` on the underlying
+ socket. If the socket-like object has no ``'getpeername'`` method,
+ then ``("unknown", 0)`` is returned.
- @return: the address if the remote host, if known
- @rtype: tuple(str, int)
+ :return:
+ the address of the remote host, if known, as a ``(str, int)``
+ tuple.
"""
gp = getattr(self.sock, 'getpeername', None)
if gp is None:
- return ('unknown', 0)
+ return 'unknown', 0
return gp()
def stop_thread(self):
@@ -1499,10 +1360,8 @@ class Transport (threading.Thread):
while self.isAlive():
self.join(10)
-
### internals...
-
def _log(self, level, msg, *args):
if issubclass(type(msg), list):
for m in msg:
@@ -1511,11 +1370,11 @@ class Transport (threading.Thread):
self.logger.log(level, msg, *args)
def _get_modulus_pack(self):
- "used by KexGex to find primes for group exchange"
+ """used by KexGex to find primes for group exchange"""
return self._modulus_pack
def _next_channel(self):
- "you are holding the lock"
+ """you are holding the lock"""
chanid = self._channel_counter
while self._channels.get(chanid) is not None:
self._channel_counter = (self._channel_counter + 1) & 0xffffff
@@ -1524,7 +1383,7 @@ class Transport (threading.Thread):
return chanid
def _unlink_channel(self, chanid):
- "used by a Channel to remove itself from the active channel list"
+ """used by a Channel to remove itself from the active channel list"""
self._channels.delete(chanid)
def _send_message(self, data):
@@ -1553,14 +1412,14 @@ class Transport (threading.Thread):
self.clear_to_send_lock.release()
def _set_K_H(self, k, h):
- "used by a kex object to set the K (root key) and H (exchange hash)"
+ """used by a kex object to set the K (root key) and H (exchange hash)"""
self.K = k
self.H = h
- if self.session_id == None:
+ if self.session_id is None:
self.session_id = h
def _expect_packet(self, *ptypes):
- "used by a kex object to register the next packet type it expects to see"
+ """used by a kex object to register the next packet type it expects to see"""
self._expected_packet = tuple(ptypes)
def _verify_key(self, host_key, sig):
@@ -1572,7 +1431,7 @@ class Transport (threading.Thread):
self.host_key = key
def _compute_key(self, id, nbytes):
- "id is 'A' - 'F' for the various keys used by ssh"
+ """id is 'A' - 'F' for the various keys used by ssh"""
m = Message()
m.add_mpint(self.K)
m.add_bytes(self.H)
@@ -1646,10 +1505,6 @@ class Transport (threading.Thread):
# containers.
Random.atfork()
- # Hold reference to 'sys' so we can test sys.modules to detect
- # interpreter shutdown.
- self.sys = sys
-
# active=True occurs before the thread is launched, to avoid a race
_active_threads.append(self)
if self.server_mode:
@@ -1719,7 +1574,10 @@ class Transport (threading.Thread):
self.saved_exception = e
except socket.error as e:
if type(e.args) is tuple:
- emsg = '%s (%d)' % (e.args[1], e.args[0])
+ if e.args:
+ emsg = '%s (%d)' % (e.args[1], e.args[0])
+ else: # empty tuple, e.g. socket.timeout
+ emsg = str(e) or repr(e)
else:
emsg = e.args
self._log(ERROR, 'Socket exception: ' + emsg)
@@ -1734,7 +1592,7 @@ class Transport (threading.Thread):
if self.active:
self.active = False
self.packetizer.close()
- if self.completion_event != None:
+ if self.completion_event is not None:
self.completion_event.set()
if self.auth_handler is not None:
self.auth_handler.abort()
@@ -1754,10 +1612,8 @@ class Transport (threading.Thread):
if self.sys.modules is not None:
raise
-
### protocol stages
-
def _negotiate_keys(self, m):
# throws SSHException on anything unusual
self.clear_to_send_lock.acquire()
@@ -1765,7 +1621,7 @@ class Transport (threading.Thread):
self.clear_to_send.clear()
finally:
self.clear_to_send_lock.release()
- if self.local_kex_init == None:
+ if self.local_kex_init is None:
# remote side wants to renegotiate
self._send_kex_init()
self._parse_kex_init(m)
@@ -1827,7 +1683,7 @@ class Transport (threading.Thread):
pkex.remove('diffie-hellman-group-exchange-sha1')
self.get_security_options().kex = pkex
available_server_keys = list(filter(list(self.server_key_dict.keys()).__contains__,
- self._preferred_keys))
+ self._preferred_keys))
else:
available_server_keys = self._preferred_keys
@@ -1865,15 +1721,15 @@ class Transport (threading.Thread):
kex_follows = m.get_boolean()
unused = m.get_int()
- self._log(DEBUG, 'kex algos:' + str(kex_algo_list) + ' server key:' + str(server_key_algo_list) + \
- ' client encrypt:' + str(client_encrypt_algo_list) + \
- ' server encrypt:' + str(server_encrypt_algo_list) + \
- ' client mac:' + str(client_mac_algo_list) + \
- ' server mac:' + str(server_mac_algo_list) + \
- ' client compress:' + str(client_compress_algo_list) + \
- ' server compress:' + str(server_compress_algo_list) + \
- ' client lang:' + str(client_lang_list) + \
- ' server lang:' + str(server_lang_list) + \
+ self._log(DEBUG, 'kex algos:' + str(kex_algo_list) + ' server key:' + str(server_key_algo_list) +
+ ' client encrypt:' + str(client_encrypt_algo_list) +
+ ' server encrypt:' + str(server_encrypt_algo_list) +
+ ' client mac:' + str(client_mac_algo_list) +
+ ' server mac:' + str(server_mac_algo_list) +
+ ' client compress:' + str(client_compress_algo_list) +
+ ' server compress:' + str(server_compress_algo_list) +
+ ' client lang:' + str(client_lang_list) +
+ ' server lang:' + str(server_lang_list) +
' kex follows?' + str(kex_follows))
# as a server, we pick the first item in the client's list that we support.
@@ -1900,14 +1756,14 @@ class Transport (threading.Thread):
if self.server_mode:
agreed_local_ciphers = list(filter(self._preferred_ciphers.__contains__,
- server_encrypt_algo_list))
+ server_encrypt_algo_list))
agreed_remote_ciphers = list(filter(self._preferred_ciphers.__contains__,
- client_encrypt_algo_list))
+ client_encrypt_algo_list))
else:
agreed_local_ciphers = list(filter(client_encrypt_algo_list.__contains__,
- self._preferred_ciphers))
+ self._preferred_ciphers))
agreed_remote_ciphers = list(filter(server_encrypt_algo_list.__contains__,
- self._preferred_ciphers))
+ self._preferred_ciphers))
if (len(agreed_local_ciphers) == 0) or (len(agreed_remote_ciphers) == 0):
raise SSHException('Incompatible ssh server (no acceptable ciphers)')
self.local_cipher = agreed_local_ciphers[0]
@@ -1948,7 +1804,7 @@ class Transport (threading.Thread):
self.remote_kex_init = cMSG_KEXINIT + m.get_so_far()
def _activate_inbound(self):
- "switch on newly negotiated encryption parameters for inbound traffic"
+ """switch on newly negotiated encryption parameters for inbound traffic"""
block_size = self._cipher_info[self.remote_cipher]['block-size']
if self.server_mode:
IV_in = self._compute_key('A', block_size)
@@ -1972,7 +1828,7 @@ class Transport (threading.Thread):
self.packetizer.set_inbound_compressor(compress_in())
def _activate_outbound(self):
- "switch on newly negotiated encryption parameters for outbound traffic"
+ """switch on newly negotiated encryption parameters for outbound traffic"""
m = Message()
m.add_byte(cMSG_NEWKEYS)
self._send_message(m)
@@ -2029,7 +1885,7 @@ class Transport (threading.Thread):
# this was the first key exchange
self.initial_kex_done = True
# send an event?
- if self.completion_event != None:
+ if self.completion_event is not None:
self.completion_event.set()
# it's now okay to send data again (if this was a re-key)
if not self.packetizer.need_rekey():
@@ -2057,7 +1913,7 @@ class Transport (threading.Thread):
address = m.get_text()
port = m.get_int()
ok = self.server_object.check_port_forward_request(address, port)
- if ok != False:
+ if ok:
ok = (ok,)
elif kind == 'cancel-tcpip-forward':
address = m.get_text()
@@ -2180,8 +2036,7 @@ class Transport (threading.Thread):
origin_addr = m.get_text()
origin_port = m.get_int()
reason = self.server_object.check_channel_direct_tcpip_request(
- my_chanid, (origin_addr, origin_port),
- (dest_addr, dest_port))
+ my_chanid, (origin_addr, origin_port), (dest_addr, dest_port))
else:
reason = self.server_object.check_channel_request(kind, my_chanid)
if reason != OPEN_SUCCEEDED:
@@ -2235,7 +2090,7 @@ class Transport (threading.Thread):
try:
self.lock.acquire()
if name not in self.subsystem_table:
- return (None, [], {})
+ return None, [], {}
return self.subsystem_table[name]
finally:
self.lock.release()
@@ -2249,7 +2104,7 @@ class Transport (threading.Thread):
MSG_CHANNEL_OPEN_FAILURE: _parse_channel_open_failure,
MSG_CHANNEL_OPEN: _parse_channel_open,
MSG_KEXINIT: _negotiate_keys,
- }
+ }
_channel_handler_table = {
MSG_CHANNEL_SUCCESS: Channel._request_success,
@@ -2260,4 +2115,125 @@ class Transport (threading.Thread):
MSG_CHANNEL_REQUEST: Channel._handle_request,
MSG_CHANNEL_EOF: Channel._handle_eof,
MSG_CHANNEL_CLOSE: Channel._handle_close,
- }
+ }
+
+
+class SecurityOptions (object):
+ """
+ Simple object containing the security preferences of an ssh transport.
+ These are tuples of acceptable ciphers, digests, key types, and key
+ exchange algorithms, listed in order of preference.
+
+ Changing the contents and/or order of these fields affects the underlying
+ `.Transport` (but only if you change them before starting the session).
+ If you try to add an algorithm that paramiko doesn't recognize,
+ ``ValueError`` will be raised. If you try to assign something besides a
+ tuple to one of the fields, ``TypeError`` will be raised.
+ """
+ #__slots__ = [ 'ciphers', 'digests', 'key_types', 'kex', 'compression', '_transport' ]
+ __slots__ = '_transport'
+
+ def __init__(self, transport):
+ self._transport = transport
+
+ def __repr__(self):
+ """
+ Returns a string representation of this object, for debugging.
+ """
+ return '<paramiko.SecurityOptions for %s>' % repr(self._transport)
+
+ def _get_ciphers(self):
+ return self._transport._preferred_ciphers
+
+ def _get_digests(self):
+ return self._transport._preferred_macs
+
+ def _get_key_types(self):
+ return self._transport._preferred_keys
+
+ def _get_kex(self):
+ return self._transport._preferred_kex
+
+ def _get_compression(self):
+ return self._transport._preferred_compression
+
+ def _set(self, name, orig, x):
+ if type(x) is list:
+ x = tuple(x)
+ if type(x) is not tuple:
+ raise TypeError('expected tuple or list')
+ possible = list(getattr(self._transport, orig).keys())
+ forbidden = [n for n in x if n not in possible]
+ if len(forbidden) > 0:
+ raise ValueError('unknown cipher')
+ setattr(self._transport, name, x)
+
+ def _set_ciphers(self, x):
+ self._set('_preferred_ciphers', '_cipher_info', x)
+
+ def _set_digests(self, x):
+ self._set('_preferred_macs', '_mac_info', x)
+
+ def _set_key_types(self, x):
+ self._set('_preferred_keys', '_key_info', x)
+
+ def _set_kex(self, x):
+ self._set('_preferred_kex', '_kex_info', x)
+
+ def _set_compression(self, x):
+ self._set('_preferred_compression', '_compression_info', x)
+
+ ciphers = property(_get_ciphers, _set_ciphers, None,
+ "Symmetric encryption ciphers")
+ digests = property(_get_digests, _set_digests, None,
+ "Digest (one-way hash) algorithms")
+ key_types = property(_get_key_types, _set_key_types, None,
+ "Public-key algorithms")
+ kex = property(_get_kex, _set_kex, None, "Key exchange algorithms")
+ compression = property(_get_compression, _set_compression, None,
+ "Compression algorithms")
+
+
+class ChannelMap (object):
+ def __init__(self):
+ # (id -> Channel)
+ self._map = weakref.WeakValueDictionary()
+ self._lock = threading.Lock()
+
+ def put(self, chanid, chan):
+ self._lock.acquire()
+ try:
+ self._map[chanid] = chan
+ finally:
+ self._lock.release()
+
+ def get(self, chanid):
+ self._lock.acquire()
+ try:
+ return self._map.get(chanid, None)
+ finally:
+ self._lock.release()
+
+ def delete(self, chanid):
+ self._lock.acquire()
+ try:
+ try:
+ del self._map[chanid]
+ except KeyError:
+ pass
+ finally:
+ self._lock.release()
+
+ def values(self):
+ self._lock.acquire()
+ try:
+ return list(self._map.values())
+ finally:
+ self._lock.release()
+
+ def __len__(self):
+ self._lock.acquire()
+ try:
+ return len(self._map)
+ finally:
+ self._lock.release()
diff --git a/paramiko/util.py b/paramiko/util.py
index e9f6250c..dbcbbae4 100644
--- a/paramiko/util.py
+++ b/paramiko/util.py
@@ -29,25 +29,15 @@ import sys
import struct
import traceback
import threading
+import logging
-from paramiko.common import *
+from paramiko.common import DEBUG, zero_byte, xffffffff, max_byte
+from paramiko.py3compat import PY2, long, byte_ord, b, byte_chr
from paramiko.config import SSHConfig
-# Change by RogerB - python < 2.3 doesn't have enumerate so we implement it
-if sys.version_info < (2,3):
- class enumerate:
- def __init__ (self, sequence):
- self.sequence = sequence
- def __iter__ (self):
- count = 0
- for item in self.sequence:
- yield (count, item)
- count += 1
-
-
def inflate_long(s, always_positive=False):
- "turns a normalized byte string into a long-int (adapted from Crypto.Util.number)"
+ """turns a normalized byte string into a long-int (adapted from Crypto.Util.number)"""
out = long(0)
negative = 0
if not always_positive and (len(s) > 0) and (byte_ord(s[0]) >= 0x80):
@@ -56,6 +46,8 @@ def inflate_long(s, always_positive=False):
filler = zero_byte
if negative:
filler = max_byte
+ # never convert this to ``s +=`` because this is a string, not a number
+ # noinspection PyAugmentAssignment
s = filler * (4 - len(s) % 4) + s
for i in range(0, len(s), 4):
out = (out << 32) + struct.unpack('>I', s[i:i+4])[0]
@@ -66,14 +58,15 @@ def inflate_long(s, always_positive=False):
deflate_zero = zero_byte if PY2 else 0
deflate_ff = max_byte if PY2 else 0xff
+
def deflate_long(n, add_sign_padding=True):
- "turns a long-int into a normalized byte string (adapted from Crypto.Util.number)"
+ """turns a long-int into a normalized byte string (adapted from Crypto.Util.number)"""
# after much testing, this algorithm was deemed to be the fastest
s = bytes()
n = long(n)
while (n != 0) and (n != -1):
s = struct.pack('>I', n & xffffffff) + s
- n = n >> 32
+ n >>= 32
# strip off leading zeros, FFs
for i in enumerate(s):
if (n == 0) and (i[1] != deflate_zero):
@@ -95,6 +88,7 @@ def deflate_long(n, add_sign_padding=True):
s = max_byte + s
return s
+
def format_binary(data, prefix=''):
x = 0
out = []
@@ -105,17 +99,21 @@ def format_binary(data, prefix=''):
out.append(format_binary_line(data[x:]))
return [prefix + x for x in out]
+
def format_binary_line(data):
left = ' '.join(['%02X' % byte_ord(c) for c in data])
right = ''.join([('.%c..' % c)[(byte_ord(c)+63)//95] for c in data])
return '%-50s %s' % (left, right)
+
def hexify(s):
return hexlify(s).upper()
+
def unhexify(s):
return unhexlify(s)
+
def safe_string(s):
out = ''
for c in s:
@@ -125,13 +123,12 @@ def safe_string(s):
out += '%%%02X' % byte_ord(c)
return out
-# ''.join([['%%%02X' % ord(c), c][(ord(c) >= 32) and (ord(c) <= 127)] for c in s])
def bit_length(n):
try:
return n.bitlength()
except AttributeError:
- norm = deflate_long(n, 0)
+ norm = deflate_long(n, False)
hbyte = byte_ord(norm[0])
if hbyte == 0:
return 1
@@ -141,26 +138,25 @@ def bit_length(n):
bitlen -= 1
return bitlen
+
def tb_strings():
return ''.join(traceback.format_exception(*sys.exc_info())).split('\n')
+
def generate_key_bytes(hashclass, salt, key, nbytes):
"""
Given a password, passphrase, or other human-source key, scramble it
through a secure hash into some keyworthy bytes. This specific algorithm
is used for encrypting/decrypting private key files.
- @param hashclass: class from L{Crypto.Hash} that can be used as a secure
- hashing function (like C{MD5} or C{SHA}).
- @type hashclass: L{Crypto.Hash}
- @param salt: data to salt the hash with.
- @type salt: byte string
- @param key: human-entered password or passphrase.
- @type key: string
- @param nbytes: number of bytes to generate.
- @type nbytes: int
- @return: key data
- @rtype: string
+ :param class hashclass:
+ class from `Crypto.Hash` that can be used as a secure hashing function
+ (like ``MD5`` or ``SHA``).
+ :param salt: data to salt the hash with.
+ :type salt: byte string
+ :param str key: human-entered password or passphrase.
+ :param int nbytes: number of bytes to generate.
+ :return: Key data `str`
"""
keydata = bytes()
digest = bytes()
@@ -178,42 +174,45 @@ def generate_key_bytes(hashclass, salt, key, nbytes):
nbytes -= size
return keydata
+
def load_host_keys(filename):
"""
Read a file of known SSH host keys, in the format used by openssh, and
- return a compound dict of C{hostname -> keytype ->} L{PKey <paramiko.pkey.PKey>}.
- The hostname may be an IP address or DNS name. The keytype will be either
- C{"ssh-rsa"} or C{"ssh-dss"}.
+ return a compound dict of ``hostname -> keytype ->`` `PKey
+ <paramiko.pkey.PKey>`. The hostname may be an IP address or DNS name. The
+ keytype will be either ``"ssh-rsa"`` or ``"ssh-dss"``.
This type of file unfortunately doesn't exist on Windows, but on posix,
- it will usually be stored in C{os.path.expanduser("~/.ssh/known_hosts")}.
+ it will usually be stored in ``os.path.expanduser("~/.ssh/known_hosts")``.
- Since 1.5.3, this is just a wrapper around L{HostKeys}.
+ Since 1.5.3, this is just a wrapper around `.HostKeys`.
- @param filename: name of the file to read host keys from
- @type filename: str
- @return: dict of host keys, indexed by hostname and then keytype
- @rtype: dict(hostname, dict(keytype, L{PKey <paramiko.pkey.PKey>}))
+ :param str filename: name of the file to read host keys from
+ :return:
+ nested dict of `.PKey` objects, indexed by hostname and then keytype
"""
from paramiko.hostkeys import HostKeys
return HostKeys(filename)
+
def parse_ssh_config(file_obj):
"""
- Provided only as a backward-compatible wrapper around L{SSHConfig}.
+ Provided only as a backward-compatible wrapper around `.SSHConfig`.
"""
config = SSHConfig()
config.parse(file_obj)
return config
+
def lookup_ssh_host_config(hostname, config):
"""
- Provided only as a backward-compatible wrapper around L{SSHConfig}.
+ Provided only as a backward-compatible wrapper around `.SSHConfig`.
"""
return config.lookup(hostname)
+
def mod_inverse(x, m):
- # it's crazy how small python can make this function.
+ # it's crazy how small Python can make this function.
u1, u2, u3 = 1, 0, m
v1, v2, v3 = 0, 1, x
@@ -229,6 +228,8 @@ def mod_inverse(x, m):
_g_thread_ids = {}
_g_thread_counter = 0
_g_thread_lock = threading.Lock()
+
+
def get_thread_id():
global _g_thread_ids, _g_thread_counter, _g_thread_lock
tid = id(threading.currentThread())
@@ -243,8 +244,9 @@ def get_thread_id():
_g_thread_lock.release()
return ret
+
def log_to_file(filename, level=DEBUG):
- "send paramiko logs to a logfile, if they're not already going somewhere"
+ """send paramiko logs to a logfile, if they're not already going somewhere"""
l = logging.getLogger("paramiko")
if len(l.handlers) > 0:
return
@@ -255,6 +257,7 @@ def log_to_file(filename, level=DEBUG):
'%Y%m%d-%H:%M:%S'))
l.addHandler(lh)
+
# make only one filter object, so it doesn't get applied more than once
class PFilter (object):
def filter(self, record):
@@ -262,11 +265,13 @@ class PFilter (object):
return True
_pfilter = PFilter()
+
def get_logger(name):
l = logging.getLogger(name)
l.addFilter(_pfilter)
return l
+
def retry_on_signal(function):
"""Retries function until it doesn't raise an EINTR error"""
while True:
@@ -276,6 +281,7 @@ def retry_on_signal(function):
if e.errno != errno.EINTR:
raise
+
class Counter (object):
"""Stateful counter for CTR mode crypto"""
def __init__(self, nbits, initial_value=long(1), overflow=long(0)):
@@ -305,3 +311,13 @@ class Counter (object):
def new(cls, nbits, initial_value=long(1), overflow=long(0)):
return cls(nbits, initial_value=initial_value, overflow=overflow)
new = classmethod(new)
+
+
+def constant_time_bytes_eq(a, b):
+ if len(a) != len(b):
+ return False
+ res = 0
+ # noinspection PyUnresolvedReferences
+ for i in (xrange if PY2 else range)(len(a)):
+ res |= byte_ord(a[i]) ^ byte_ord(b[i])
+ return res == 0
diff --git a/paramiko/win_pageant.py b/paramiko/win_pageant.py
index 3c23f2ff..20b1b0b9 100644
--- a/paramiko/win_pageant.py
+++ b/paramiko/win_pageant.py
@@ -21,15 +21,20 @@
Functions for communicating with Pageant, the basic windows ssh agent program.
"""
-import struct
-import threading
import array
-import platform
import ctypes.wintypes
+import platform
+import struct
from paramiko.util import *
+try:
+ import _thread as thread # Python 3.x
+except ImportError:
+ import thread # Python 2.5-2.7
+
from . import _winapi
+
_AGENT_COPYDATA_ID = 0x804e50ba
_AGENT_MAX_MSGLEN = 8192
# Note: The WM_COPYDATA value is pulled from win32con, as a workaround
@@ -50,7 +55,10 @@ def can_talk_to_agent():
"""
return bool(_get_pageant_window_object())
+
ULONG_PTR = ctypes.c_uint64 if platform.architecture()[0] == '64bit' else ctypes.c_uint32
+
+
class COPYDATASTRUCT(ctypes.Structure):
"""
ctypes implementation of
@@ -60,7 +68,8 @@ class COPYDATASTRUCT(ctypes.Structure):
('num_data', ULONG_PTR),
('data_size', ctypes.wintypes.DWORD),
('data_loc', ctypes.c_void_p),
- ]
+ ]
+
def _query_pageant(msg):
"""
@@ -73,7 +82,7 @@ def _query_pageant(msg):
return None
# create a name for the mmap
- map_name = 'PageantRequest%08x' % threading.current_thread().ident
+ map_name = 'PageantRequest%08x' % thread.get_ident()
pymap = _winapi.MemoryMap(map_name, _AGENT_MAX_MSGLEN,
_winapi.get_security_attributes_for_user(),
@@ -97,7 +106,8 @@ def _query_pageant(msg):
return datalen + pymap.read(retlen)
return None
-class PageantConnection (object):
+
+class PageantConnection(object):
"""
Mock "connection" to an agent which roughly approximates the behavior of
a unix local-domain socket (as used by Agent). Requests are sent to the
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 00000000..5e409001
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[wheel]
+universal = 1
diff --git a/setup.py b/setup.py
index be404b1b..2910a7fe 100644
--- a/setup.py
+++ b/setup.py
@@ -53,21 +53,31 @@ if sys.platform == 'darwin':
setup_helper.install_custom_make_tarball()
-setup(name = "paramiko",
- version = "1.13.0",
- description = "SSH2 protocol library",
- author = "Jeff Forcier",
- author_email = "jeff@bitprophet.org",
- url = "https://github.com/paramiko/paramiko/",
- packages = [ 'paramiko' ],
- license = 'LGPL',
- platforms = 'Posix; MacOS X; Windows',
- classifiers = [ 'Development Status :: 5 - Production/Stable',
- 'Intended Audience :: Developers',
- 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
- 'Operating System :: OS Independent',
- 'Topic :: Internet',
- 'Topic :: Security :: Cryptography' ],
- long_description = longdesc,
- **kw
- )
+setup(
+ name = "paramiko",
+ version = "1.13.0",
+ description = "SSH2 protocol library",
+ long_description = longdesc,
+ author = "Jeff Forcier",
+ author_email = "jeff@bitprophet.org",
+ url = "https://github.com/paramiko/paramiko/",
+ packages = [ 'paramiko' ],
+ license = 'LGPL',
+ platforms = 'Posix; MacOS X; Windows',
+ classifiers = [
+ 'Development Status :: 5 - Production/Stable',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
+ 'Operating System :: OS Independent',
+ 'Topic :: Internet',
+ 'Topic :: Security :: Cryptography',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.2',
+ 'Programming Language :: Python :: 3.3',
+ ],
+ **kw
+)
diff --git a/sites/docs/api/agent.rst b/sites/docs/api/agent.rst
new file mode 100644
index 00000000..3b614a82
--- /dev/null
+++ b/sites/docs/api/agent.rst
@@ -0,0 +1,6 @@
+SSH Agents
+==========
+
+.. automodule:: paramiko.agent
+ :inherited-members:
+ :no-special-members:
diff --git a/sites/docs/api/buffered_pipe.rst b/sites/docs/api/buffered_pipe.rst
new file mode 100644
index 00000000..54d87c59
--- /dev/null
+++ b/sites/docs/api/buffered_pipe.rst
@@ -0,0 +1,4 @@
+Buffered pipes
+==============
+
+.. automodule:: paramiko.buffered_pipe
diff --git a/sites/docs/api/channel.rst b/sites/docs/api/channel.rst
new file mode 100644
index 00000000..e71526f6
--- /dev/null
+++ b/sites/docs/api/channel.rst
@@ -0,0 +1,4 @@
+Channel
+=======
+
+.. automodule:: paramiko.channel
diff --git a/sites/docs/api/client.rst b/sites/docs/api/client.rst
new file mode 100644
index 00000000..b201812f
--- /dev/null
+++ b/sites/docs/api/client.rst
@@ -0,0 +1,5 @@
+Client
+======
+
+.. automodule:: paramiko.client
+ :member-order: bysource
diff --git a/sites/docs/api/config.rst b/sites/docs/api/config.rst
new file mode 100644
index 00000000..afb004c9
--- /dev/null
+++ b/sites/docs/api/config.rst
@@ -0,0 +1,5 @@
+Configuration
+=============
+
+.. automodule:: paramiko.config
+ :member-order: bysource
diff --git a/sites/docs/api/file.rst b/sites/docs/api/file.rst
new file mode 100644
index 00000000..199f3ecc
--- /dev/null
+++ b/sites/docs/api/file.rst
@@ -0,0 +1,4 @@
+Buffered files
+==============
+
+.. automodule:: paramiko.file
diff --git a/sites/docs/api/hostkeys.rst b/sites/docs/api/hostkeys.rst
new file mode 100644
index 00000000..770652fd
--- /dev/null
+++ b/sites/docs/api/hostkeys.rst
@@ -0,0 +1,5 @@
+Host keys / ``known_hosts`` files
+=================================
+
+.. automodule:: paramiko.hostkeys
+ :member-order: bysource
diff --git a/sites/docs/api/keys.rst b/sites/docs/api/keys.rst
new file mode 100644
index 00000000..af7b58c4
--- /dev/null
+++ b/sites/docs/api/keys.rst
@@ -0,0 +1,6 @@
+Key handling
+============
+
+.. automodule:: paramiko.pkey
+.. automodule:: paramiko.dsskey
+.. automodule:: paramiko.rsakey
diff --git a/sites/docs/api/message.rst b/sites/docs/api/message.rst
new file mode 100644
index 00000000..8d531e01
--- /dev/null
+++ b/sites/docs/api/message.rst
@@ -0,0 +1,4 @@
+Message
+=======
+
+.. automodule:: paramiko.message
diff --git a/sites/docs/api/packet.rst b/sites/docs/api/packet.rst
new file mode 100644
index 00000000..4089203b
--- /dev/null
+++ b/sites/docs/api/packet.rst
@@ -0,0 +1,4 @@
+Packetizer
+==========
+
+.. automodule:: paramiko.packet
diff --git a/sites/docs/api/pipe.rst b/sites/docs/api/pipe.rst
new file mode 100644
index 00000000..e480446f
--- /dev/null
+++ b/sites/docs/api/pipe.rst
@@ -0,0 +1,4 @@
+Cross-platform pipe implementations
+===================================
+
+.. automodule:: paramiko.pipe
diff --git a/sites/docs/api/proxy.rst b/sites/docs/api/proxy.rst
new file mode 100644
index 00000000..489b14e7
--- /dev/null
+++ b/sites/docs/api/proxy.rst
@@ -0,0 +1,4 @@
+``ProxyCommand`` support
+========================
+
+.. automodule:: paramiko.proxy
diff --git a/sites/docs/api/server.rst b/sites/docs/api/server.rst
new file mode 100644
index 00000000..ea854544
--- /dev/null
+++ b/sites/docs/api/server.rst
@@ -0,0 +1,5 @@
+Server implementation
+=====================
+
+.. automodule:: paramiko.server
+ :member-order: bysource
diff --git a/sites/docs/api/sftp.rst b/sites/docs/api/sftp.rst
new file mode 100644
index 00000000..956eada2
--- /dev/null
+++ b/sites/docs/api/sftp.rst
@@ -0,0 +1,13 @@
+SFTP
+====
+
+.. automodule:: paramiko.sftp
+.. automodule:: paramiko.sftp_client
+.. automodule:: paramiko.sftp_server
+.. automodule:: paramiko.sftp_attr
+.. automodule:: paramiko.sftp_file
+ :inherited-members:
+ :no-special-members:
+ :show-inheritance:
+.. automodule:: paramiko.sftp_handle
+.. automodule:: paramiko.sftp_si
diff --git a/sites/docs/api/ssh_exception.rst b/sites/docs/api/ssh_exception.rst
new file mode 100644
index 00000000..64872dc2
--- /dev/null
+++ b/sites/docs/api/ssh_exception.rst
@@ -0,0 +1,4 @@
+Exceptions
+==========
+
+.. automodule:: paramiko.ssh_exception
diff --git a/sites/docs/api/transport.rst b/sites/docs/api/transport.rst
new file mode 100644
index 00000000..7c9d9fd1
--- /dev/null
+++ b/sites/docs/api/transport.rst
@@ -0,0 +1,5 @@
+Transport
+=========
+
+.. automodule:: paramiko.transport
+ :member-order: bysource
diff --git a/sites/docs/conf.py b/sites/docs/conf.py
new file mode 100644
index 00000000..619ff816
--- /dev/null
+++ b/sites/docs/conf.py
@@ -0,0 +1,16 @@
+# Obtain shared config values
+import os, sys
+sys.path.append(os.path.abspath('..'))
+sys.path.append(os.path.abspath('../..'))
+from shared_conf import *
+
+# Enable autodoc, intersphinx
+extensions.extend(['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'])
+
+# Autodoc settings
+autodoc_default_flags = ['members', 'special-members']
+
+# Intersphinx connection to stdlib
+intersphinx_mapping = {
+ 'python': ('http://docs.python.org/2.6', None),
+}
diff --git a/sites/docs/index.rst b/sites/docs/index.rst
new file mode 100644
index 00000000..f336b393
--- /dev/null
+++ b/sites/docs/index.rst
@@ -0,0 +1,72 @@
+====================================
+Welcome to Paramiko's documentation!
+====================================
+
+This site covers Paramiko's usage & API documentation. For basic info on what
+Paramiko is, including its public changelog & how the project is maintained,
+please see `the main project website <http://paramiko.org>`_.
+
+
+API documentation
+=================
+
+The high-level client API starts with creation of an `.SSHClient` object. For
+more direct control, pass a socket (or socket-like object) to a `.Transport`,
+and use `start_server <.Transport.start_server>` or `start_client
+<.Transport.start_client>` to negotiate with the remote host as either a server
+or client.
+
+As a client, you are responsible for authenticating using a password or private
+key, and checking the server's host key. (Key signature and verification is
+done by paramiko, but you will need to provide private keys and check that the
+content of a public key matches what you expected to see.)
+
+As a server, you are responsible for deciding which users, passwords, and keys
+to allow, and what kind of channels to allow.
+
+Once you have finished, either side may request flow-controlled `channels
+<.Channel>` to the other side, which are Python objects that act like sockets,
+but send and receive data over the encrypted session.
+
+For details, please see the following tables of contents (which are organized
+by area of interest.)
+
+
+Core SSH protocol classes
+-------------------------
+
+.. toctree::
+ api/channel
+ api/client
+ api/message
+ api/packet
+ api/transport
+
+
+Authentication & keys
+---------------------
+
+.. toctree::
+ api/agent
+ api/hostkeys
+ api/keys
+
+
+Other primary functions
+-----------------------
+
+.. toctree::
+ api/config
+ api/proxy
+ api/server
+ api/sftp
+
+
+Miscellany
+----------
+
+.. toctree::
+ api/buffered_pipe
+ api/file
+ api/pipe
+ api/ssh_exception
diff --git a/sites/shared_conf.py b/sites/shared_conf.py
index 86ecdfe8..52cec938 100644
--- a/sites/shared_conf.py
+++ b/sites/shared_conf.py
@@ -31,9 +31,9 @@ html_sidebars = {
}
# Regular settings
-project = u'Paramiko'
+project = 'Paramiko'
year = datetime.now().year
-copyright = u'%d Jeff Forcier' % year
+copyright = '%d Jeff Forcier' % year
master_doc = 'index'
templates_path = ['_templates']
exclude_trees = ['_build']
diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst
index 2680086e..5b221e90 100644
--- a/sites/www/changelog.rst
+++ b/sites/www/changelog.rst
@@ -3,24 +3,66 @@ Changelog
=========
* :feature:`250` GSS-API / SSPI authenticated Diffie-Hellman Key Exchange and user authentication.
-* :bug:`193` (and its attentant PRs :issue:`230` & :issue:`253`): Fix SSH agent
+* :support:`290` (also :issue:`292`) Add support for building universal
+ (Python 2+3 compatible) wheel files during the release process. Courtesy of
+ Alex Gaynor.
+* :support:`284` Add Python language trove identifiers to ``setup.py``. Thanks
+ to Alex Gaynor for catch & patch.
+* :bug:`235` Improve string type testing in a handful of spots (e.g. ``s/if
+ type(x) is str/if isinstance(x, basestring)/g``.) Thanks to ``@ksamuel`` for
+ the report.
+* :release:`1.13.0 <2014-03-13>`
+* :release:`1.12.3 <2014-03-13>`
+* :release:`1.11.5 <2014-03-13>`
+* :release:`1.10.7 <2014-03-13>`
+* :feature:`16` **Python 3 support!** Our test suite passes under Python 3, and
+ it (& Fabric's test suite) continues to pass under Python 2.
+
+ The merged code was built on many contributors' efforts, both code &
+ feedback. In no particular order, we thank Daniel Goertzen, Ivan Kolodyazhny,
+ Tomi Pieviläinen, Jason R. Coombs, Jan N. Schulze, ``@Lazik``, Dorian Pula,
+ Scott Maxwell, Tshepang Lekhonkhobe, Aaron Meurer, and Dave Halter.
+* :support:`256 backported` Convert API documentation to Sphinx, yielding a new
+ API docs website to replace the old Epydoc one. Thanks to Olle Lundberg for
+ the initial conversion work.
+* :bug:`-` Use constant-time hash comparison operations where possible, to
+ protect against `timing-based attacks
+ <http://codahale.com/a-lesson-in-timing-attacks/>`_. Thanks to Alex Gaynor
+ for the patch.
+* :release:`1.12.2 <2014-02-14>`
+* :release:`1.11.4 <2014-02-14>`
+* :release:`1.10.6 <2014-02-14>`
+* :feature:`58` Allow client code to access the stored SSH server banner via
+ `Transport.get_banner <paramiko.transport.Transport.get_banner>`. Thanks to
+ ``@Jhoanor`` for the patch.
+* :bug:`252` (`Fabric #1020 <https://github.com/fabric/fabric/issues/1020>`_)
+ Enhanced the implementation of ``ProxyCommand`` to avoid a deadlock/hang
+ condition that frequently occurs at ``Transport`` shutdown time. Thanks to
+ Mateusz Kobos, Matthijs van der Vleuten and Guillaume Zitta for the original
+ reports and to Marius Gedminas for helping test nontrivial use cases.
+* :bug:`268` Fix some missed renames of ``ProxyCommand`` related error classes.
+ Thanks to Marius Gedminas for catch & patch.
+* :bug:`34` (PR :issue:`35`) Fix SFTP prefetching incompatibility with some
+ SFTP servers regarding request/response ordering. Thanks to Richard
+ Kettlewell.
+* :bug:`193` (and its attentant PRs :issue:`230` & :issue:`253`) Fix SSH agent
problems present on Windows. Thanks to David Hobbs for initial report and to
Aarni Koskela & Olle Lundberg for the patches.
* :release:`1.12.1 <2014-01-08>`
-* :release:`1.11.3 <2014-01-08>` 176
-* :release:`1.10.5 <2014-01-08>` 176
-* :bug:`225` Note ecdsa requirement in README. Thanks to Amaury Rodriguez for
- the catch.
+* :release:`1.11.3 <2014-01-08>`
+* :release:`1.10.5 <2014-01-08>`
+* :bug:`225 (1.12+)` Note ecdsa requirement in README. Thanks to Amaury
+ Rodriguez for the catch.
* :bug:`176` Fix AttributeError bugs in known_hosts file (re)loading. Thanks
to Nathan Scowcroft for the patch & Martin Blumenstingl for the initial test
case.
* :release:`1.12.0 <2013-09-27>`
* :release:`1.11.2 <2013-09-27>`
-* :release:`1.10.4 <2013-09-27>` 199, 200, 179
-* :feature:`152` Add tentative support for ECDSA keys. *This adds the ecdsa
- module as a new dependency of Paramiko.* The module is available at
- [warner/python-ecdsa on Github](https://github.com/warner/python-ecdsa) and
- [ecdsa on PyPI](https://pypi.python.org/pypi/ecdsa).
+* :release:`1.10.4 <2013-09-27>`
+* :feature:`152` Add tentative support for ECDSA keys. **This adds the ecdsa
+ module as a new dependency of Paramiko.** The module is available at
+ `warner/python-ecdsa on Github <https://github.com/warner/python-ecdsa>`_ and
+ `ecdsa on PyPI <https://pypi.python.org/pypi/ecdsa>`_.
* Note that you might still run into problems with key negotiation --
Paramiko picks the first key that the server offers, which might not be
@@ -29,9 +71,9 @@ Changelog
* :feature:`136` Add server-side support for the SSH protocol's 'env' command.
Thanks to Benjamin Pollack for the patch.
-* :bug:`156` Fix potential deadlock condition when using Channel objects as
- sockets (e.g. when using SSH gatewaying). Thanks to Steven Noonan and Frank
- Arnold for catch & patch.
+* :bug:`156 (1.11+)` Fix potential deadlock condition when using Channel
+ objects as sockets (e.g. when using SSH gatewaying). Thanks to Steven Noonan
+ and Frank Arnold for catch & patch.
* :bug:`179` Fix a missing variable causing errors when an ssh_config file has
a non-default AddressFamily set. Thanks to Ed Marshall & Tomaz Muraus for
catch & patch.
diff --git a/sites/www/conf.py b/sites/www/conf.py
index 481acdff..1c6c9254 100644
--- a/sites/www/conf.py
+++ b/sites/www/conf.py
@@ -24,10 +24,10 @@ extensions.append('sphinx.ext.intersphinx')
target = join(dirname(__file__), '..', 'docs', '_build')
if os.environ.get('READTHEDOCS') == 'True':
# TODO: switch to docs.paramiko.org post go-live of sphinx API docs
- target = 'http://paramiko-docs.readthedocs.org/en/latest/'
-#intersphinx_mapping = {
-# 'docs': (target, None),
-#}
+ target = 'http://docs.paramiko.org/en/latest/'
+intersphinx_mapping = {
+ 'docs': (target, None),
+}
# Sister-site links to API docs
html_theme_options['extra_nav_links'] = {
diff --git a/sites/www/faq.rst b/sites/www/faq.rst
new file mode 100644
index 00000000..a7e80014
--- /dev/null
+++ b/sites/www/faq.rst
@@ -0,0 +1,9 @@
+===================================
+Frequently Asked/Answered Questions
+===================================
+
+Which version should I use? I see multiple active releases.
+===========================================================
+
+Please see :ref:`the installation docs <release-lines>` which have an explicit
+section about this topic.
diff --git a/sites/www/index.rst b/sites/www/index.rst
index 7fefedd2..cb3961ce 100644
--- a/sites/www/index.rst
+++ b/sites/www/index.rst
@@ -1,7 +1,7 @@
Welcome to Paramiko!
====================
-Paramiko is a Python (2.5+) implementation of the SSHv2 protocol [#]_,
+Paramiko is a Python (2.6+, 3.3+) implementation of the SSHv2 protocol [#]_,
providing both client and server functionality. While it leverages a Python C
extension for low level cryptography (`PyCrypto <http://pycrypto.org>`_),
Paramiko itself is a pure Python interface around SSH networking concepts.
@@ -13,6 +13,7 @@ usage and API documentation can be found at our code documentation site,
.. toctree::
changelog
+ FAQs <faq>
installing
contributing
contact
diff --git a/sites/www/installing.rst b/sites/www/installing.rst
index 0d4dc1ac..74c5c6e8 100644
--- a/sites/www/installing.rst
+++ b/sites/www/installing.rst
@@ -2,10 +2,12 @@
Installing
==========
+.. _paramiko-itself:
+
Paramiko itself
===============
-The recommended way to get Invoke is to **install the latest stable release**
+The recommended way to get Paramiko is to **install the latest stable release**
via `pip <http://pip-installer.org>`_::
$ pip install paramiko
@@ -14,50 +16,44 @@ via `pip <http://pip-installer.org>`_::
Users who want the bleeding edge can install the development version via
``pip install paramiko==dev``.
-We currently support **Python 2.5/2.6/2.7**, with support for Python 3 coming
-soon. Users on Python 2.4 or older are urged to upgrade. Paramiko *may* work on
-Python 2.4 still, but there is no longer any support guarantee.
+We currently support **Python 2.6, 2.7 and 3.3** (Python **3.2** should also
+work but has a less-strong compatibility guarantee from us.) Users on Python
+2.5 or older are urged to upgrade.
Paramiko has two dependencies: the pure-Python ECDSA module `ecdsa`, and the
PyCrypto C extension. `ecdsa` is easily installable from wherever you
obtained Paramiko's package; PyCrypto may require more work. Read on for
details.
-PyCrypto
-========
-
-`PyCrypto <https://www.dlitz.net/software/pycrypto/>`_ provides the low-level
-(C-based) encryption algorithms we need to implement the SSH protocol. There
-are a couple gotchas associated with installing PyCrypto: its compatibility
-with Python's package tools, and the fact that it is a C-based extension.
+.. _release-lines:
-.. _pycrypto-and-pip:
+Release lines
+-------------
-Possible gotcha on older Python and/or pip versions
----------------------------------------------------
+Users desiring stability may wish to pin themselves to a specific release line
+once they first start using Paramiko; to assist in this, we guarantee bugfixes
+for at least the last 2-3 releases including the latest stable one. This currently means Paramiko **1.11** through **1.13**.
-We strongly recommend using ``pip`` to as it is newer and generally better than
-``easy_install``. However, a combination of bugs in specific (now rather old)
-versions of Python, ``pip`` and PyCrypto can prevent installation of PyCrypto.
-Specifically:
+If you're unsure which version to install, we have suggestions:
-* Python = 2.5.x
-* PyCrypto >= 2.1 (required for most modern versions of Paramiko)
-* ``pip`` < 0.8.1
+* **Completely new users** should always default to the **latest stable
+ release** (as above, whatever is newest / whatever shows up with ``pip
+ install paramiko``.)
+* **Users upgrading from a much older version** (e.g. the 1.7.x line) should
+ probably get the **oldest actively supported line** (see the paragraph above
+ this list for what that currently is.)
+* **Everybody else** is hopefully already "on" a given version and can
+ carefully upgrade to whichever version they care to, when their release line
+ stops being supported.
-When all three criteria are met, you may encounter ``No such file or
-directory`` IOErrors when trying to ``pip install paramiko`` or ``pip install
-PyCrypto``.
-The fix is to make sure at least one of the above criteria is not met, by doing
-the following (in order of preference):
-
-* Upgrade to ``pip`` 0.8.1 or above, e.g. by running ``pip install -U pip``.
-* Upgrade to Python 2.6 or above.
-* Downgrade to Paramiko 1.7.6 or 1.7.7, which do not require PyCrypto >= 2.1,
- and install PyCrypto 2.0.1 (the oldest version on PyPI which works with
- Paramiko 1.7.6/1.7.7)
+PyCrypto
+========
+`PyCrypto <https://www.dlitz.net/software/pycrypto/>`_ provides the low-level
+(C-based) encryption algorithms we need to implement the SSH protocol. There
+are a couple gotchas associated with installing PyCrypto: its compatibility
+with Python's package tools, and the fact that it is a C-based extension.
C extension
-----------
diff --git a/tasks.py b/tasks.py
new file mode 100644
index 00000000..38282b8e
--- /dev/null
+++ b/tasks.py
@@ -0,0 +1,49 @@
+from os import mkdir
+from os.path import join
+from shutil import rmtree, copytree
+
+from invoke import Collection, ctask as task
+from invocations import docs as _docs
+from invocations.packaging import publish
+
+
+d = 'sites'
+
+# Usage doc/API site (published as docs.paramiko.org)
+docs_path = join(d, 'docs')
+docs_build = join(docs_path, '_build')
+docs = Collection.from_module(_docs, name='docs', config={
+ 'sphinx.source': docs_path,
+ 'sphinx.target': docs_build,
+})
+
+# Main/about/changelog site ((www.)?paramiko.org)
+www_path = join(d, 'www')
+www = Collection.from_module(_docs, name='www', config={
+ 'sphinx.source': www_path,
+ 'sphinx.target': join(www_path, '_build'),
+})
+
+
+# Until we move to spec-based testing
+@task
+def test(ctx):
+ ctx.run("python test.py --verbose")
+
+@task
+def coverage(ctx):
+ ctx.run("coverage run --source=paramiko test.py --verbose")
+
+
+# Until we stop bundling docs w/ releases. Need to discover use cases first.
+@task('docs') # Will invoke the API doc site build
+def release(ctx):
+ # Move the built docs into where Epydocs used to live
+ target = 'docs'
+ rmtree(target, ignore_errors=True)
+ copytree(docs_build, target)
+ # Publish
+ publish(ctx, wheel=True)
+
+
+ns = Collection(test, coverage, release, docs=docs, www=www)
diff --git a/test.py b/test.py
index a954ef27..d0da40dc 100755
--- a/test.py
+++ b/test.py
@@ -44,18 +44,6 @@ from tests.test_packetizer import PacketizerTest
from tests.test_auth import AuthTest
from tests.test_transport import TransportTest
from tests.test_client import SSHClientTest
-from test_message import MessageTest
-from test_file import BufferedFileTest
-from test_buffered_pipe import BufferedPipeTest
-from test_util import UtilTest
-from test_hostkeys import HostKeysTest
-from test_pkey import KeyTest
-from test_kex import KexTest
-from test_packetizer import PacketizerTest
-from test_auth import AuthTest
-from test_transport import TransportTest
-from test_sftp import SFTPTest
-from test_sftp_big import BigSFTPTest
from test_client import SSHClientTest
from test_gssapi import GSSAPITest
from test_ssh_gss import GSSAuthTest
diff --git a/tests/loop.py b/tests/loop.py
index 6e933f83..4f5dc163 100644
--- a/tests/loop.py
+++ b/tests/loop.py
@@ -21,7 +21,7 @@
"""
import threading, socket
-from paramiko.common import *
+from paramiko.common import asbytes
class LoopSocket (object):
diff --git a/tests/stub_sftp.py b/tests/stub_sftp.py
index 58e4be26..24380ba1 100644
--- a/tests/stub_sftp.py
+++ b/tests/stub_sftp.py
@@ -24,7 +24,7 @@ import os
import sys
from paramiko import ServerInterface, SFTPServerInterface, SFTPServer, SFTPAttributes, \
SFTPHandle, SFTP_OK, AUTH_SUCCESSFUL, OPEN_SUCCEEDED
-from paramiko.common import *
+from paramiko.common import o666
class StubServer (ServerInterface):
@@ -64,7 +64,7 @@ class StubSFTPServer (SFTPServerInterface):
def list_folder(self, path):
path = self._realpath(path)
try:
- out = [ ]
+ out = []
flist = os.listdir(path)
for fname in flist:
attr = SFTPAttributes.from_stat(os.stat(os.path.join(path, fname)))
@@ -91,7 +91,7 @@ class StubSFTPServer (SFTPServerInterface):
def open(self, path, flags, attr):
path = self._realpath(path)
try:
- binary_flag = getattr(os, 'O_BINARY', 0)
+ binary_flag = getattr(os, 'O_BINARY', 0)
flags |= binary_flag
mode = getattr(attr, 'st_mode', None)
if mode is not None:
diff --git a/tests/test_auth.py b/tests/test_auth.py
index d26b1807..1d972d53 100644
--- a/tests/test_auth.py
+++ b/tests/test_auth.py
@@ -25,10 +25,9 @@ import threading
import unittest
from paramiko import Transport, ServerInterface, RSAKey, DSSKey, \
- SSHException, BadAuthenticationType, InteractiveQuery, ChannelException, \
+ BadAuthenticationType, InteractiveQuery, \
AuthenticationException
from paramiko import AUTH_FAILED, AUTH_PARTIALLY_SUCCESSFUL, AUTH_SUCCESSFUL
-from paramiko import OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
from paramiko.py3compat import u
from tests.loop import LoopSocket
from tests.util import test_path
diff --git a/tests/test_buffered_pipe.py b/tests/test_buffered_pipe.py
index b9d2bef4..b66edff9 100644
--- a/tests/test_buffered_pipe.py
+++ b/tests/test_buffered_pipe.py
@@ -22,7 +22,6 @@ Some unit tests for BufferedPipe.
import threading
import time
-import unittest
from paramiko.buffered_pipe import BufferedPipe, PipeTimeout
from paramiko import pipe
from paramiko.py3compat import b
@@ -30,16 +29,16 @@ from paramiko.py3compat import b
from tests.util import ParamikoTest
-def delay_thread(pipe):
- pipe.feed('a')
+def delay_thread(p):
+ p.feed('a')
time.sleep(0.5)
- pipe.feed('b')
- pipe.close()
+ p.feed('b')
+ p.close()
-def close_thread(pipe):
+def close_thread(p):
time.sleep(0.2)
- pipe.close()
+ p.close()
class BufferedPipeTest(ParamikoTest):
@@ -91,4 +90,3 @@ class BufferedPipeTest(ParamikoTest):
self.assertTrue(p._set)
p2.clear()
self.assertFalse(p._set)
-
diff --git a/tests/test_client.py b/tests/test_client.py
index 97150979..7e5c80b4 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -29,6 +29,7 @@ import warnings
import os
from tests.util import test_path
import paramiko
+from paramiko.common import PY2
class NullServer (paramiko.ServerInterface):
@@ -157,7 +158,7 @@ class SSHClientTest (unittest.TestCase):
self.tc = paramiko.SSHClient()
self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port), 'ssh-rsa', public_host_key)
- self.tc.connect(self.addr, self.port, username='slowdive', key_filename=[ test_path('test_rsa.key'), test_path('test_dss.key') ])
+ self.tc.connect(self.addr, self.port, username='slowdive', key_filename=[test_path('test_rsa.key'), test_path('test_dss.key')])
self.event.wait(1.0)
self.assertTrue(self.event.isSet())
@@ -218,6 +219,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.
+ if not PY2:
+ return
threading.Thread(target=self._run).start()
host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key'))
public_host_key = paramiko.RSAKey(data=host_key.asbytes())
diff --git a/tests/test_file.py b/tests/test_file.py
index 33a49130..e11d7fd5 100755
--- a/tests/test_file.py
+++ b/tests/test_file.py
@@ -22,7 +22,7 @@ Some unit tests for the BufferedFile abstraction.
import unittest
from paramiko.file import BufferedFile
-from paramiko.common import *
+from paramiko.common import linefeed_byte, crlf, cr_byte
class LoopbackFile (BufferedFile):
diff --git a/tests/test_hostkeys.py b/tests/test_hostkeys.py
index 9a7e3689..0ee1bbf0 100644
--- a/tests/test_hostkeys.py
+++ b/tests/test_hostkeys.py
@@ -20,12 +20,11 @@
Some unit tests for HostKeys.
"""
-import base64
from binascii import hexlify
import os
import unittest
import paramiko
-from paramiko.py3compat import b, decodebytes
+from paramiko.py3compat import decodebytes
test_hosts_file = """\
diff --git a/tests/test_kex.py b/tests/test_kex.py
index e69c051b..c522be46 100644
--- a/tests/test_kex.py
+++ b/tests/test_kex.py
@@ -26,7 +26,7 @@ import paramiko.util
from paramiko.kex_group1 import KexGroup1
from paramiko.kex_gex import KexGex
from paramiko import Message
-from paramiko.common import *
+from paramiko.common import byte_chr
class FakeRng (object):
@@ -37,8 +37,10 @@ class FakeRng (object):
class FakeKey (object):
def __str__(self):
return 'fake-key'
+
def asbytes(self):
return b'fake-key'
+
def sign_ssh_data(self, rng, H):
return b'fake-sig'
@@ -46,6 +48,7 @@ class FakeKey (object):
class FakeModulusPack (object):
P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF
G = 2
+
def get_modulus(self, min, ask, max):
return self.G, self.P
@@ -59,19 +62,26 @@ class FakeTransport (object):
def _send_message(self, m):
self._message = m
+
def _expect_packet(self, *t):
self._expect = t
+
def _set_K_H(self, K, H):
self._K = K
self._H = H
+
def _verify_key(self, host_key, sig):
self._verify = (host_key, sig)
+
def _activate_outbound(self):
self._activated = True
+
def _log(self, level, s):
pass
+
def get_server_key(self):
return FakeKey()
+
def _get_modulus_pack(self):
return FakeModulusPack()
diff --git a/tests/test_message.py b/tests/test_message.py
index 4da52cfb..f308c037 100644
--- a/tests/test_message.py
+++ b/tests/test_message.py
@@ -22,7 +22,7 @@ Some unit tests for ssh protocol message blocks.
import unittest
from paramiko.message import Message
-from paramiko.common import *
+from paramiko.common import byte_chr, zero_byte
class MessageTest (unittest.TestCase):
diff --git a/tests/test_packetizer.py b/tests/test_packetizer.py
index 5c36fed6..d4d5544e 100644
--- a/tests/test_packetizer.py
+++ b/tests/test_packetizer.py
@@ -23,9 +23,9 @@ Some unit tests for the ssh2 protocol in Transport.
import unittest
from tests.loop import LoopSocket
from Crypto.Cipher import AES
-from Crypto.Hash import SHA, HMAC
+from Crypto.Hash import SHA
from paramiko import Message, Packetizer, util
-from paramiko.common import *
+from paramiko.common import byte_chr, zero_byte
x55 = byte_chr(0x55)
x1f = byte_chr(0x1f)
@@ -33,7 +33,7 @@ x1f = byte_chr(0x1f)
class PacketizerTest (unittest.TestCase):
- def test_1_write (self):
+ def test_1_write(self):
rsock = LoopSocket()
wsock = LoopSocket()
rsock.link(wsock)
@@ -56,7 +56,7 @@ class PacketizerTest (unittest.TestCase):
self.assertEqual(44, len(data))
self.assertEqual(b'\x43\x91\x97\xbd\x5b\x50\xac\x25\x87\xc2\xc4\x6b\xc7\xe9\x38\xc0', data[:16])
- def test_2_read (self):
+ def test_2_read(self):
rsock = LoopSocket()
wsock = LoopSocket()
rsock.link(wsock)
diff --git a/tests/test_pkey.py b/tests/test_pkey.py
index 2e565a5f..6ff68fc2 100644
--- a/tests/test_pkey.py
+++ b/tests/test_pkey.py
@@ -23,7 +23,8 @@ Some unit tests for public/private key objects.
from binascii import hexlify
import unittest
from paramiko import RSAKey, DSSKey, ECDSAKey, Message, util
-from paramiko.common import rng, StringIO, byte_chr, b, bytes
+from paramiko.py3compat import StringIO, byte_chr, b, bytes
+from paramiko.common import rng
from tests.util import test_path
# from openssh's ssh-keygen
diff --git a/tests/test_sftp.py b/tests/test_sftp.py
index f8fab1ce..6417ac90 100755
--- a/tests/test_sftp.py
+++ b/tests/test_sftp.py
@@ -25,13 +25,15 @@ do test file operations in (so no existing files will be harmed).
from binascii import hexlify
import os
+import sys
import warnings
import threading
import unittest
from tempfile import mkstemp
import paramiko
-from paramiko.common import *
+from paramiko.py3compat import PY2, b, u, StringIO
+from paramiko.common import o777, o600, o666, o644
from tests.stub_sftp import StubServer, StubSFTPServer
from tests.loop import LoopSocket
from tests.util import test_path
@@ -553,6 +555,7 @@ class SFTPTest (unittest.TestCase):
with open(localname, 'wb') as f:
f.write(text)
saved_progress = []
+
def progress_callback(x, y):
saved_progress.append((x, y))
sftp.put(localname, FOLDER + '/bunny.txt', progress_callback)
@@ -662,6 +665,7 @@ class SFTPTest (unittest.TestCase):
with open(localname, 'w') as f:
f.write(text)
saved_progress = []
+
def progress_callback(x, y):
saved_progress.append((x, y))
res = sftp.put(localname, FOLDER + '/bunny.txt', progress_callback, False)
diff --git a/tests/test_sftp_big.py b/tests/test_sftp_big.py
index 6870c6b4..521fbdc8 100644
--- a/tests/test_sftp_big.py
+++ b/tests/test_sftp_big.py
@@ -23,19 +23,14 @@ a real actual sftp server is contacted, and a new folder is created there to
do test file operations in (so no existing files will be harmed).
"""
-import logging
import os
import random
import struct
import sys
-import threading
import time
import unittest
-import paramiko
-from paramiko.common import *
-from tests.stub_sftp import StubServer, StubSFTPServer
-from tests.loop import LoopSocket
+from paramiko.common import o660
from tests.test_sftp import get_sftp
FOLDER = os.environ.get('TEST_FOLDER', 'temp-testing000')
diff --git a/tests/test_transport.py b/tests/test_transport.py
index 876759c8..485a18e8 100644
--- a/tests/test_transport.py
+++ b/tests/test_transport.py
@@ -23,17 +23,16 @@ Some unit tests for the ssh2 protocol in Transport.
from binascii import hexlify
import select
import socket
-import sys
import time
import threading
-import unittest
import random
from paramiko import Transport, SecurityOptions, ServerInterface, RSAKey, DSSKey, \
- SSHException, BadAuthenticationType, InteractiveQuery, ChannelException
-from paramiko import AUTH_FAILED, AUTH_PARTIALLY_SUCCESSFUL, AUTH_SUCCESSFUL
+ SSHException, ChannelException
+from paramiko import AUTH_FAILED, AUTH_SUCCESSFUL
from paramiko import OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
-from paramiko.common import MSG_KEXINIT, MSG_CHANNEL_WINDOW_ADJUST, b, bytes
+from paramiko.common import MSG_KEXINIT, cMSG_CHANNEL_WINDOW_ADJUST
+from paramiko.py3compat import bytes
from paramiko.message import Message
from tests.loop import LoopSocket
from tests.util import ParamikoTest, test_path
diff --git a/tests/test_util.py b/tests/test_util.py
index 4f85c391..6bde4045 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -23,11 +23,10 @@ Some unit tests for utility functions.
from binascii import hexlify
import errno
import os
-import unittest
from Crypto.Hash import SHA
import paramiko.util
from paramiko.util import lookup_ssh_host_config as host_config
-from paramiko.py3compat import StringIO, byte_ord, b
+from paramiko.py3compat import StringIO, byte_ord
from tests.util import ParamikoTest
@@ -338,4 +337,4 @@ AddressFamily inet
IdentityFile something_%l_using_fqdn
"""
config = paramiko.util.parse_ssh_config(StringIO(test_config))
- assert config.lookup('meh') # will die during lookup() if bug regresses
+ assert config.lookup('meh') # will die during lookup() if bug regresses
diff --git a/tox-requirements.txt b/tox-requirements.txt
new file mode 100644
index 00000000..26224ce6
--- /dev/null
+++ b/tox-requirements.txt
@@ -0,0 +1,2 @@
+# Not sure why tox can't just read setup.py?
+pycrypto
diff --git a/tox.ini b/tox.ini
index c63c93d3..55e3fe64 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,5 +2,5 @@
envlist = py25,py26,py27,py32,py33
[testenv]
-commands = pip install --use-mirrors -q -r dev-requirements.txt
+commands = pip install --use-mirrors -q -r tox-requirements.txt
python test.py