aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy T. Bouse <jbouse@debian.org>2014-02-14 21:29:59 -0500
committerJeremy T. Bouse <jbouse@debian.org>2014-02-14 21:29:59 -0500
commit3f4d155d984fd27cedd0a333bf44e4724f33e30a (patch)
treeee06fc2a9ec6c5f144015dcbd67763f539d9aa51
parentd45f78c1381a1f583306c5b6a89989f478980c2f (diff)
parent3bb46c9cb414ca82afab715d2d0cc00ed71cfb6d (diff)
downloadpython-paramiko-3f4d155d984fd27cedd0a333bf44e4724f33e30a.tar
python-paramiko-3f4d155d984fd27cedd0a333bf44e4724f33e30a.tar.gz
Merge tag 'upstream/1.12.2'
Upstream version 1.12.2
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml20
-rw-r--r--NEWS52
-rw-r--r--README2
-rwxr-xr-xdemos/demo.py3
-rwxr-xr-xdemos/demo_keygen.py2
-rw-r--r--demos/demo_server.py2
-rwxr-xr-xdemos/demo_sftp.py2
-rwxr-xr-xdemos/demo_simple.py4
-rw-r--r--demos/forward.py6
-rw-r--r--demos/interactive.py2
-rwxr-xr-xdemos/rforward.py2
-rw-r--r--dev-requirements.txt10
-rw-r--r--fabfile.py28
-rw-r--r--paramiko/__init__.py8
-rw-r--r--paramiko/_winapi.py274
-rw-r--r--paramiko/agent.py12
-rw-r--r--paramiko/auth_handler.py2
-rw-r--r--paramiko/ber.py2
-rw-r--r--paramiko/buffered_pipe.py2
-rw-r--r--paramiko/channel.py9
-rw-r--r--paramiko/client.py9
-rw-r--r--paramiko/common.py2
-rw-r--r--paramiko/compress.py2
-rw-r--r--paramiko/config.py54
-rw-r--r--paramiko/dsskey.py2
-rw-r--r--paramiko/ecdsakey.py181
-rw-r--r--paramiko/file.py2
-rw-r--r--paramiko/hostkeys.py24
-rw-r--r--paramiko/kex_gex.py2
-rw-r--r--paramiko/kex_group1.py2
-rw-r--r--paramiko/logging22.py2
-rw-r--r--paramiko/message.py2
-rw-r--r--paramiko/packet.py15
-rw-r--r--paramiko/pipe.py2
-rw-r--r--paramiko/pkey.py2
-rw-r--r--paramiko/primes.py2
-rw-r--r--paramiko/proxy.py33
-rw-r--r--paramiko/resource.py2
-rw-r--r--paramiko/rsakey.py2
-rw-r--r--paramiko/server.py23
-rw-r--r--paramiko/sftp.py2
-rw-r--r--paramiko/sftp_attr.py2
-rw-r--r--paramiko/sftp_client.py4
-rw-r--r--paramiko/sftp_file.py26
-rw-r--r--paramiko/sftp_handle.py2
-rw-r--r--paramiko/sftp_server.py2
-rw-r--r--paramiko/sftp_si.py2
-rw-r--r--paramiko/ssh_exception.py2
-rw-r--r--paramiko/transport.py25
-rw-r--r--paramiko/util.py2
-rw-r--r--paramiko/win_pageant.py95
-rw-r--r--requirements.txt2
-rw-r--r--setup.py8
-rw-r--r--setup_helper.py2
-rw-r--r--sites/_shared_static/logo.pngbin0 -> 6401 bytes
-rw-r--r--sites/docs/conf.py4
-rw-r--r--sites/docs/index.rst6
-rw-r--r--sites/shared_conf.py41
-rw-r--r--sites/www/_templates/rss.xml19
-rw-r--r--sites/www/blog.py140
-rw-r--r--sites/www/blog.rst16
-rw-r--r--sites/www/blog/first-post.rst7
-rw-r--r--sites/www/blog/second-post.rst7
-rw-r--r--sites/www/changelog.rst126
-rw-r--r--sites/www/conf.py35
-rw-r--r--sites/www/contact.rst11
-rw-r--r--sites/www/contributing.rst19
-rw-r--r--sites/www/index.rst38
-rw-r--r--sites/www/installing.rst105
-rw-r--r--tasks.py34
-rwxr-xr-xtest.py2
-rw-r--r--tests/loop.py2
-rw-r--r--tests/stub_sftp.py2
-rw-r--r--tests/test_auth.py2
-rw-r--r--tests/test_buffered_pipe.py2
-rw-r--r--tests/test_client.py33
-rw-r--r--tests/test_ecdsa.key5
-rw-r--r--tests/test_ecdsa_password.key8
-rwxr-xr-xtests/test_file.py2
-rw-r--r--tests/test_hostkeys.py2
-rw-r--r--tests/test_kex.py2
-rw-r--r--tests/test_message.py2
-rw-r--r--tests/test_packetizer.py2
-rw-r--r--tests/test_pkey.py66
-rwxr-xr-xtests/test_sftp.py2
-rw-r--r--tests/test_sftp_big.py2
-rw-r--r--tests/test_transport.py2
-rw-r--r--tests/test_util.py13
-rw-r--r--tox-requirements.txt2
-rw-r--r--tox.ini2
91 files changed, 1502 insertions, 249 deletions
diff --git a/.gitignore b/.gitignore
index 4b57895..e149bb8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,6 @@ dist/
paramiko.egg-info/
test.log
docs/
+!sites/docs
+_build
+.coverage
diff --git a/.travis.yml b/.travis.yml
index 6896b89..97165c4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,30 @@
language: python
python:
- - "2.5"
- "2.6"
- "2.7"
install:
# Self-install for setup.py-driven deps
- pip install -e .
-script: python test.py
+ # 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"
+ template:
+ - "%{repository}@%{branch}: %{message} (%{build_url})"
on_success: change
on_failure: change
+ use_notice: true
+ email: false
+after_success:
+ - coveralls
diff --git a/NEWS b/NEWS
index 7a983ba..761f8e4 100644
--- a/NEWS
+++ b/NEWS
@@ -9,57 +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.10.1 (5th Apr 2013)
-----------------------
+**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
-* #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 68e7434..2a23e28 100644
--- a/README
+++ b/README
@@ -8,6 +8,7 @@ paramiko
:Copyright: Copyright (c) 2013 Jeff Forcier <jeff@bitprophet.org>
:License: LGPL
:Homepage: https://github.com/paramiko/paramiko/
+:API docs: http://docs.paramiko.org
What
@@ -35,6 +36,7 @@ Requirements
- python 2.5 or better <http://www.python.org/>
- pycrypto 2.1 or better <https://www.dlitz.net/software/pycrypto/>
+ - ecdsa 0.9 or better <https://pypi.python.org/pypi/ecdsa>
If you have setuptools, you can build and install paramiko and all its
dependencies with this command (as root)::
diff --git a/demos/demo.py b/demos/demo.py
index 05524d3..aa4bdaa 100755
--- a/demos/demo.py
+++ b/demos/demo.py
@@ -9,7 +9,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -26,7 +26,6 @@ import os
import select
import socket
import sys
-import threading
import time
import traceback
diff --git a/demos/demo_keygen.py b/demos/demo_keygen.py
index b4ce5b8..bdd7388 100755
--- a/demos/demo_keygen.py
+++ b/demos/demo_keygen.py
@@ -9,7 +9,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/demos/demo_server.py b/demos/demo_server.py
index 4972928..915b0c6 100644
--- a/demos/demo_server.py
+++ b/demos/demo_server.py
@@ -9,7 +9,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/demos/demo_sftp.py b/demos/demo_sftp.py
index 992615f..7c4aaba 100755
--- a/demos/demo_sftp.py
+++ b/demos/demo_sftp.py
@@ -9,7 +9,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/demos/demo_simple.py b/demos/demo_simple.py
index 231da8d..50f344a 100755
--- a/demos/demo_simple.py
+++ b/demos/demo_simple.py
@@ -9,7 +9,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -63,7 +63,7 @@ password = getpass.getpass('Password for %s@%s: ' % (username, hostname))
try:
client = paramiko.SSHClient()
client.load_system_host_keys()
- client.set_missing_host_key_policy(paramiko.WarningPolicy)
+ client.set_missing_host_key_policy(paramiko.WarningPolicy())
print '*** Connecting...'
client.connect(hostname, port, username, password)
chan = client.invoke_shell()
diff --git a/demos/forward.py b/demos/forward.py
index 4e10785..5048c77 100644
--- a/demos/forward.py
+++ b/demos/forward.py
@@ -9,7 +9,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -78,9 +78,11 @@ class Handler (SocketServer.BaseRequestHandler):
if len(data) == 0:
break
self.request.send(data)
+
+ peername = self.request.getpeername()
chan.close()
self.request.close()
- verbose('Tunnel closed from %r' % (self.request.getpeername(),))
+ verbose('Tunnel closed from %r' % (peername,))
def forward_tunnel(local_port, remote_host, remote_port, transport):
diff --git a/demos/interactive.py b/demos/interactive.py
index 4cbc617..f3be74d 100644
--- a/demos/interactive.py
+++ b/demos/interactive.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/demos/rforward.py b/demos/rforward.py
index ef4c532..4a5d2e4 100755
--- a/demos/rforward.py
+++ b/demos/rforward.py
@@ -9,7 +9,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/dev-requirements.txt b/dev-requirements.txt
new file mode 100644
index 0000000..7217218
--- /dev/null
+++ b/dev-requirements.txt
@@ -0,0 +1,10 @@
+# Older junk
+tox>=1.4,<1.5
+epydoc>=3.0,<3.1
+# For newer tasks like building Sphinx docs.
+# NOTE: Requires Python >=2.6
+invoke>=0.7.0
+invocations>=0.4.4
+sphinx>=1.1.3
+alabaster>=0.3.0
+releases>=0.5.1
diff --git a/fabfile.py b/fabfile.py
index 29394f9..7883dab 100644
--- a/fabfile.py
+++ b/fabfile.py
@@ -1,8 +1,10 @@
-from fabric.api import task, sudo, env
+from fabric.api import task, sudo, env, local, hosts
from fabric.contrib.project import rsync_project
+from fabric.contrib.console import confirm
@task
+@hosts("paramiko.org")
def upload_docs():
target = "/var/www/paramiko.org"
staging = "/tmp/paramiko_docs"
@@ -11,3 +13,27 @@ def upload_docs():
sudo("rm -rf %s/*" % target)
rsync_project(local_dir='docs/', remote_dir=staging, delete=True)
sudo("cp -R %s/* %s/" % (staging, target))
+
+@task
+def build_docs():
+ local("epydoc --no-private -o docs/ paramiko")
+
+@task
+def clean():
+ local("rm -rf build dist docs")
+ local("rm -f MANIFEST *.log demos/*.log")
+ local("rm -f paramiko/*.pyc")
+ local("rm -f test.log")
+ local("rm -rf paramiko.egg-info")
+
+@task
+def test():
+ local("python ./test.py")
+
+@task
+def release():
+ confirm("Only hit Enter if you remembered to update the version!")
+ confirm("Also, did you remember to tag your release?")
+ build_docs()
+ local("python setup.py sdist register upload")
+ upload_docs()
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index 099314e..648b595 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -46,6 +46,8 @@ Paramiko is written entirely in python (no C or platform-dependent code) and is
released under the GNU Lesser General Public License (LGPL).
Website: U{https://github.com/paramiko/paramiko/}
+
+Mailing list: U{paramiko@librelist.com<mailto:paramiko@librelist.com>}
"""
import sys
@@ -55,7 +57,8 @@ if sys.version_info < (2, 5):
__author__ = "Jeff Forcier <jeff@bitprophet.org>"
-__version__ = "1.10.1"
+__version__ = "1.12.2"
+__version_info__ = tuple([ int(d) for d in __version__.split(".") ])
__license__ = "GNU Lesser General Public License (LGPL)"
@@ -69,6 +72,7 @@ from ssh_exception import SSHException, PasswordRequiredException, \
from server import ServerInterface, SubsystemHandler, InteractiveQuery
from rsakey import RSAKey
from dsskey import DSSKey
+from ecdsakey import ECDSAKey
from sftp import SFTPError, BaseSFTP
from sftp_client import SFTP, SFTPClient
from sftp_server import SFTPServer
diff --git a/paramiko/_winapi.py b/paramiko/_winapi.py
new file mode 100644
index 0000000..b875924
--- /dev/null
+++ b/paramiko/_winapi.py
@@ -0,0 +1,274 @@
+"""
+Windows API functions implemented as ctypes functions and classes as found
+in jaraco.windows (2.10).
+
+If you encounter issues with this module, please consider reporting the issues
+in jaraco.windows and asking the author to port the fixes back here.
+"""
+
+import ctypes
+import ctypes.wintypes
+import __builtin__
+
+try:
+ USHORT = ctypes.wintypes.USHORT
+except AttributeError:
+ USHORT = ctypes.c_ushort
+
+######################
+# jaraco.windows.error
+
+def format_system_message(errno):
+ """
+ Call FormatMessage with a system error number to retrieve
+ the descriptive error message.
+ """
+ # first some flags used by FormatMessageW
+ ALLOCATE_BUFFER = 0x100
+ ARGUMENT_ARRAY = 0x2000
+ FROM_HMODULE = 0x800
+ FROM_STRING = 0x400
+ FROM_SYSTEM = 0x1000
+ IGNORE_INSERTS = 0x200
+
+ # Let FormatMessageW allocate the buffer (we'll free it below)
+ # Also, let it know we want a system error message.
+ flags = ALLOCATE_BUFFER | FROM_SYSTEM
+ source = None
+ message_id = errno
+ language_id = 0
+ result_buffer = ctypes.wintypes.LPWSTR()
+ buffer_size = 0
+ arguments = None
+ bytes = ctypes.windll.kernel32.FormatMessageW(
+ flags,
+ source,
+ message_id,
+ language_id,
+ ctypes.byref(result_buffer),
+ buffer_size,
+ arguments,
+ )
+ # note the following will cause an infinite loop if GetLastError
+ # repeatedly returns an error that cannot be formatted, although
+ # this should not happen.
+ handle_nonzero_success(bytes)
+ message = result_buffer.value
+ ctypes.windll.kernel32.LocalFree(result_buffer)
+ return message
+
+
+class WindowsError(__builtin__.WindowsError):
+ "more info about errors at http://msdn.microsoft.com/en-us/library/ms681381(VS.85).aspx"
+
+ def __init__(self, value=None):
+ if value is None:
+ value = ctypes.windll.kernel32.GetLastError()
+ strerror = format_system_message(value)
+ super(WindowsError, self).__init__(value, strerror)
+
+ @property
+ def message(self):
+ return self.strerror
+
+ @property
+ def code(self):
+ return self.winerror
+
+ def __str__(self):
+ return self.message
+
+ def __repr__(self):
+ return '{self.__class__.__name__}({self.winerror})'.format(**vars())
+
+def handle_nonzero_success(result):
+ if result == 0:
+ raise WindowsError()
+
+
+CreateFileMapping = ctypes.windll.kernel32.CreateFileMappingW
+CreateFileMapping.argtypes = [
+ ctypes.wintypes.HANDLE,
+ ctypes.c_void_p,
+ ctypes.wintypes.DWORD,
+ ctypes.wintypes.DWORD,
+ ctypes.wintypes.DWORD,
+ ctypes.wintypes.LPWSTR,
+]
+CreateFileMapping.restype = ctypes.wintypes.HANDLE
+
+MapViewOfFile = ctypes.windll.kernel32.MapViewOfFile
+MapViewOfFile.restype = ctypes.wintypes.HANDLE
+
+class MemoryMap(object):
+ """
+ A memory map object which can have security attributes overrideden.
+ """
+ def __init__(self, name, length, security_attributes=None):
+ self.name = name
+ self.length = length
+ self.security_attributes = security_attributes
+ self.pos = 0
+
+ def __enter__(self):
+ p_SA = (
+ ctypes.byref(self.security_attributes)
+ if self.security_attributes else None
+ )
+ INVALID_HANDLE_VALUE = -1
+ PAGE_READWRITE = 0x4
+ FILE_MAP_WRITE = 0x2
+ filemap = ctypes.windll.kernel32.CreateFileMappingW(
+ INVALID_HANDLE_VALUE, p_SA, PAGE_READWRITE, 0, self.length,
+ unicode(self.name))
+ handle_nonzero_success(filemap)
+ if filemap == INVALID_HANDLE_VALUE:
+ raise Exception("Failed to create file mapping")
+ self.filemap = filemap
+ self.view = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0)
+ return self
+
+ def seek(self, pos):
+ self.pos = pos
+
+ def write(self, 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.kernel32.RtlMoveMemory(out, self.view + self.pos, n)
+ self.pos += n
+ return out.raw
+
+ def __exit__(self, exc_type, exc_val, tb):
+ ctypes.windll.kernel32.UnmapViewOfFile(self.view)
+ ctypes.windll.kernel32.CloseHandle(self.filemap)
+
+#########################
+# jaraco.windows.security
+
+class TokenInformationClass:
+ TokenUser = 1
+
+class TOKEN_USER(ctypes.Structure):
+ num = 1
+ _fields_ = [
+ ('SID', ctypes.c_void_p),
+ ('ATTRIBUTES', ctypes.wintypes.DWORD),
+ ]
+
+
+class SECURITY_DESCRIPTOR(ctypes.Structure):
+ """
+ typedef struct _SECURITY_DESCRIPTOR
+ {
+ UCHAR Revision;
+ UCHAR Sbz1;
+ SECURITY_DESCRIPTOR_CONTROL Control;
+ PSID Owner;
+ PSID Group;
+ PACL Sacl;
+ PACL Dacl;
+ } SECURITY_DESCRIPTOR;
+ """
+ SECURITY_DESCRIPTOR_CONTROL = USHORT
+ REVISION = 1
+
+ _fields_ = [
+ ('Revision', ctypes.c_ubyte),
+ ('Sbz1', ctypes.c_ubyte),
+ ('Control', SECURITY_DESCRIPTOR_CONTROL),
+ ('Owner', ctypes.c_void_p),
+ ('Group', ctypes.c_void_p),
+ ('Sacl', ctypes.c_void_p),
+ ('Dacl', ctypes.c_void_p),
+ ]
+
+class SECURITY_ATTRIBUTES(ctypes.Structure):
+ """
+ typedef struct _SECURITY_ATTRIBUTES {
+ DWORD nLength;
+ LPVOID lpSecurityDescriptor;
+ BOOL bInheritHandle;
+ } SECURITY_ATTRIBUTES;
+ """
+ _fields_ = [
+ ('nLength', ctypes.wintypes.DWORD),
+ ('lpSecurityDescriptor', ctypes.c_void_p),
+ ('bInheritHandle', ctypes.wintypes.BOOL),
+ ]
+
+ def __init__(self, *args, **kwargs):
+ super(SECURITY_ATTRIBUTES, self).__init__(*args, **kwargs)
+ self.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
+
+ def _get_descriptor(self):
+ return self._descriptor
+ def _set_descriptor(self, descriptor):
+ self._descriptor = descriptor
+ self.lpSecurityDescriptor = ctypes.addressof(descriptor)
+ descriptor = property(_get_descriptor, _set_descriptor)
+
+def GetTokenInformation(token, information_class):
+ """
+ Given a token, get the token information for it.
+ """
+ data_size = ctypes.wintypes.DWORD()
+ ctypes.windll.advapi32.GetTokenInformation(token, information_class.num,
+ 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,
+ ctypes.byref(data), ctypes.sizeof(data),
+ ctypes.byref(data_size)))
+ return ctypes.cast(data, ctypes.POINTER(TOKEN_USER)).contents
+
+class TokenAccess:
+ TOKEN_QUERY = 0x8
+
+def OpenProcessToken(proc_handle, access):
+ result = ctypes.wintypes.HANDLE()
+ proc_handle = ctypes.wintypes.HANDLE(proc_handle)
+ handle_nonzero_success(ctypes.windll.advapi32.OpenProcessToken(
+ proc_handle, access, ctypes.byref(result)))
+ return result
+
+def get_current_user():
+ """
+ Return a TOKEN_USER for the owner of this process.
+ """
+ process = OpenProcessToken(
+ ctypes.windll.kernel32.GetCurrentProcess(),
+ TokenAccess.TOKEN_QUERY,
+ )
+ return GetTokenInformation(process, TOKEN_USER)
+
+def get_security_attributes_for_user(user=None):
+ """
+ Return a SECURITY_ATTRIBUTES structure with the SID set to the
+ specified user (uses current user if none is specified).
+ """
+ if user is None:
+ user = get_current_user()
+
+ assert isinstance(user, TOKEN_USER), "user must be TOKEN_USER instance"
+
+ SD = SECURITY_DESCRIPTOR()
+ SA = SECURITY_ATTRIBUTES()
+ # by attaching the actual security descriptor, it will be garbage-
+ # collected with the security attributes
+ SA.descriptor = SD
+ SA.bInheritHandle = 1
+
+ ctypes.windll.advapi32.InitializeSecurityDescriptor(ctypes.byref(SD),
+ SECURITY_DESCRIPTOR.REVISION)
+ ctypes.windll.advapi32.SetSecurityDescriptorOwner(ctypes.byref(SD),
+ user.SID, 0)
+ return SA
diff --git a/paramiko/agent.py b/paramiko/agent.py
index 1dd3063..23a5a2e 100644
--- a/paramiko/agent.py
+++ b/paramiko/agent.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -255,11 +255,11 @@ class AgentServerProxy(AgentSSH):
self.close()
def connect(self):
- conn_sock = self.__t.open_forward_agent_channel()
- if conn_sock is None:
- raise SSHException('lost ssh-agent')
- conn_sock.set_name('auth-agent')
- self._connect(conn_sock)
+ conn_sock = self.__t.open_forward_agent_channel()
+ if conn_sock is None:
+ raise SSHException('lost ssh-agent')
+ conn_sock.set_name('auth-agent')
+ self._connect(conn_sock)
def close(self):
"""
diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py
index e3bd82d..acb7c8b 100644
--- a/paramiko/auth_handler.py
+++ b/paramiko/auth_handler.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/ber.py b/paramiko/ber.py
index 19568dd..3941581 100644
--- a/paramiko/ber.py
+++ b/paramiko/ber.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/buffered_pipe.py b/paramiko/buffered_pipe.py
index b19d74b..4ef5cf7 100644
--- a/paramiko/buffered_pipe.py
+++ b/paramiko/buffered_pipe.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/channel.py b/paramiko/channel.py
index 0c603c6..d1e6333 100644
--- a/paramiko/channel.py
+++ b/paramiko/channel.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -1027,6 +1027,13 @@ class Channel (object):
ok = False
else:
ok = server.check_channel_shell_request(self)
+ elif key == 'env':
+ name = m.get_string()
+ value = m.get_string()
+ if server is None:
+ ok = False
+ else:
+ ok = server.check_channel_env_request(self, name, value)
elif key == 'exec':
cmd = m.get_string()
if server is None:
diff --git a/paramiko/client.py b/paramiko/client.py
index 5b71958..be89609 100644
--- a/paramiko/client.py
+++ b/paramiko/client.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -186,8 +186,13 @@ class SSHClient (object):
@raise IOError: if the file could not be written
"""
+
+ # update local host keys from file (in case other SSH clients
+ # have written to the known_hosts file meanwhile.
+ if self._host_keys_filename is not None:
+ self.load_host_keys(self._host_keys_filename)
+
f = open(filename, 'w')
- f.write('# SSH host keys collected by paramiko\n')
for hostname, keys in self._host_keys.iteritems():
for keytype, key in keys.iteritems():
f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
diff --git a/paramiko/common.py b/paramiko/common.py
index 25d5457..3d7ca58 100644
--- a/paramiko/common.py
+++ b/paramiko/common.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/compress.py b/paramiko/compress.py
index 40b430f..b55f0b1 100644
--- a/paramiko/compress.py
+++ b/paramiko/compress.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/config.py b/paramiko/config.py
index e41bae4..1705de7 100644
--- a/paramiko/config.py
+++ b/paramiko/config.py
@@ -8,7 +8,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -35,9 +35,10 @@ class LazyFqdn(object):
Returns the host's fqdn on request as string.
"""
- def __init__(self, config):
+ def __init__(self, config, host=None):
self.fqdn = None
self.config = config
+ self.host = host
def __str__(self):
if self.fqdn is None:
@@ -54,19 +55,27 @@ class LazyFqdn(object):
fqdn = None
address_family = self.config.get('addressfamily', 'any').lower()
if address_family != 'any':
- family = socket.AF_INET if address_family == 'inet' \
- else socket.AF_INET6
- results = socket.getaddrinfo(host,
- None,
- family,
- socket.SOCK_DGRAM,
- socket.IPPROTO_IP,
- socket.AI_CANONNAME)
- for res in results:
- af, socktype, proto, canonname, sa = res
- if canonname and '.' in canonname:
- fqdn = canonname
- break
+ try:
+ family = socket.AF_INET if address_family == 'inet' \
+ else socket.AF_INET6
+ results = socket.getaddrinfo(
+ self.host,
+ None,
+ family,
+ socket.SOCK_DGRAM,
+ socket.IPPROTO_IP,
+ socket.AI_CANONNAME
+ )
+ for res in results:
+ af, socktype, proto, canonname, sa = res
+ if canonname and '.' in canonname:
+ fqdn = canonname
+ break
+ # giaerror -> socket.getaddrinfo() can't resolve self.host
+ # (which is from socket.gethostname()). Fall back to the
+ # getfqdn() call below.
+ except socket.gaierror:
+ pass
# Handle 'any' / unspecified
if fqdn is None:
fqdn = socket.getfqdn()
@@ -126,16 +135,17 @@ class SSHConfig (object):
self._config.append(host)
value = value.split()
host = {key: value, 'config': {}}
- #identityfile is a special case, since it is allowed to be
+ #identityfile, localforward, remoteforward keys are special cases, since they are allowed to be
# specified multiple times and they should be tried in order
# of specification.
- elif key == 'identityfile':
+
+ elif key in ['identityfile', 'localforward', 'remoteforward']:
if key in host['config']:
- host['config']['identityfile'].append(value)
+ host['config'][key].append(value)
else:
- host['config']['identityfile'] = [value]
+ host['config'][key] = [value]
elif key not in host['config']:
- host['config'].update({key: value})
+ host['config'].update({key: value})
self._config.append(host)
def lookup(self, hostname):
@@ -215,7 +225,7 @@ class SSHConfig (object):
remoteuser = user
host = socket.gethostname().split('.')[0]
- fqdn = LazyFqdn(config)
+ fqdn = LazyFqdn(config, host)
homedir = os.path.expanduser('~')
replacements = {'controlpath':
[
@@ -252,5 +262,5 @@ class SSHConfig (object):
config[k][item] = config[k][item].\
replace(find, str(replace))
else:
- config[k] = config[k].replace(find, str(replace))
+ config[k] = config[k].replace(find, str(replace))
return config
diff --git a/paramiko/dsskey.py b/paramiko/dsskey.py
index 53ca92b..f6ecb2a 100644
--- a/paramiko/dsskey.py
+++ b/paramiko/dsskey.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py
new file mode 100644
index 0000000..ac840ab
--- /dev/null
+++ b/paramiko/ecdsakey.py
@@ -0,0 +1,181 @@
+# 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 distrubuted 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.
+
+"""
+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 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.ssh_exception import SSHException
+
+
+class ECDSAKey (PKey):
+ """
+ Representation of an ECDSA key which can be used to sign and verify SSH2
+ data.
+ """
+
+ def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None):
+ self.verifying_key = None
+ self.signing_key = None
+ if file_obj is not None:
+ self._from_private_key(file_obj, password)
+ return
+ if filename is not None:
+ self._from_private_key_file(filename, password)
+ return
+ if (msg is None) and (data is not None):
+ msg = Message(data)
+ if vals is not None:
+ self.verifying_key, self.signing_key = vals
+ else:
+ if msg is None:
+ raise SSHException('Key object may not be empty')
+ if msg.get_string() != 'ecdsa-sha2-nistp256':
+ raise SSHException('Invalid key')
+ curvename = msg.get_string()
+ if curvename != 'nistp256':
+ raise SSHException("Can't handle curve of type %s" % curvename)
+
+ pointinfo = msg.get_string()
+ if pointinfo[0] != "\x04":
+ raise SSHException('Point compression is being used: %s'%
+ binascii.hexlify(pointinfo))
+ self.verifying_key = VerifyingKey.from_string(pointinfo[1:],
+ curve=curves.NIST256p)
+ self.size = 256
+
+ def __str__(self):
+ key = self.verifying_key
+ m = Message()
+ m.add_string('ecdsa-sha2-nistp256')
+ m.add_string('nistp256')
+
+ point_str = "\x04" + key.to_string()
+
+ m.add_string(point_str)
+ return str(m)
+
+ def __hash__(self):
+ h = hash(self.get_name())
+ h = h * 37 + hash(self.verifying_key.pubkey.point.x())
+ h = h * 37 + hash(self.verifying_key.pubkey.point.y())
+ return hash(h)
+
+ def get_name(self):
+ return 'ecdsa-sha2-nistp256'
+
+ def get_bits(self):
+ return self.size
+
+ def can_sign(self):
+ return self.signing_key is not None
+
+ def sign_ssh_data(self, rpool, data):
+ digest = SHA256.new(data).digest()
+ sig = self.signing_key.sign_digest(digest, entropy=rpool.read,
+ sigencode=self._sigencode)
+ m = Message()
+ m.add_string('ecdsa-sha2-nistp256')
+ m.add_string(sig)
+ return m
+
+ def verify_ssh_sig(self, data, msg):
+ if msg.get_string() != 'ecdsa-sha2-nistp256':
+ return False
+ sig = msg.get_string()
+
+ # verify the signature by SHA'ing the data and encrypting it
+ # using the public key.
+ hash_obj = SHA256.new(data).digest()
+ return self.verifying_key.verify_digest(sig, hash_obj,
+ sigdecode=self._sigdecode)
+
+ def write_private_key_file(self, filename, password=None):
+ key = self.signing_key or self.verifying_key
+ self._write_private_key_file('EC', filename, key.to_der(), password)
+
+ def write_private_key(self, file_obj, password=None):
+ key = self.signing_key or self.verifying_key
+ self._write_private_key('EC', file_obj, key.to_der(), password)
+
+ def generate(bits, progress_func=None):
+ """
+ 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}
+ """
+ signing_key = ECDSA.generate()
+ key = ECDSAKey(vals=(signing_key, signing_key.get_verifying_key()))
+ 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)
+
+ def _from_private_key(self, file_obj, password):
+ data = self._read_private_key('EC', file_obj, password)
+ self._decode_key(data)
+
+ ALLOWED_PADDINGS = ['\x01', '\x02\x02', '\x03\x03\x03', '\x04\x04\x04\x04',
+ '\x05\x05\x05\x05\x05', '\x06\x06\x06\x06\x06\x06',
+ '\x07\x07\x07\x07\x07\x07\x07']
+ def _decode_key(self, data):
+ s, padding = der.remove_sequence(data)
+ if padding:
+ if padding not in self.ALLOWED_PADDINGS:
+ raise ValueError, "weird padding: %s" % (binascii.hexlify(empty))
+ data = data[:-len(padding)]
+ key = SigningKey.from_der(data)
+ self.signing_key = key
+ self.verifying_key = key.get_verifying_key()
+ self.size = 256
+
+ def _sigencode(self, r, s, order):
+ msg = Message()
+ msg.add_mpint(r)
+ msg.add_mpint(s)
+ return str(msg)
+
+ def _sigdecode(self, sig, order):
+ msg = Message(sig)
+ r = msg.get_mpint()
+ s = msg.get_mpint()
+ return (r, s)
diff --git a/paramiko/file.py b/paramiko/file.py
index 7e2904e..5fd81cf 100644
--- a/paramiko/file.py
+++ b/paramiko/file.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py
index e739312..9bcf0d5 100644
--- a/paramiko/hostkeys.py
+++ b/paramiko/hostkeys.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -28,6 +28,8 @@ import UserDict
from paramiko.common import *
from paramiko.dsskey import DSSKey
from paramiko.rsakey import RSAKey
+from paramiko.util import get_logger
+from paramiko.ecdsakey import ECDSAKey
class InvalidHostKey(Exception):
@@ -48,7 +50,7 @@ class HostKeyEntry:
self.hostnames = hostnames
self.key = key
- def from_line(cls, line):
+ def from_line(cls, line, lineno=None):
"""
Parses the given line of text to find the names for the host,
the type of key, and the key data. The line is expected to be in the
@@ -61,9 +63,12 @@ class HostKeyEntry:
@param line: a line from an OpenSSH known_hosts file
@type line: str
"""
+ log = get_logger('paramiko.hostkeys')
fields = line.split(' ')
if len(fields) < 3:
# Bad number of fields
+ log.info("Not enough fields found in known_hosts in line %s (%r)" %
+ (lineno, line))
return None
fields = fields[:3]
@@ -77,8 +82,12 @@ class HostKeyEntry:
key = RSAKey(data=base64.decodestring(key))
elif keytype == 'ssh-dss':
key = DSSKey(data=base64.decodestring(key))
+ elif keytype == 'ecdsa-sha2-nistp256':
+ key = ECDSAKey(data=base64.decodestring(key))
else:
+ log.info("Unable to handle key of type %s" % (keytype,))
return None
+
except binascii.Error, e:
raise InvalidHostKey(line, e)
@@ -160,13 +169,18 @@ class HostKeys (UserDict.DictMixin):
@raise IOError: if there was an error reading the file
"""
f = open(filename, 'r')
- for line in f:
+ for lineno, line in enumerate(f):
line = line.strip()
if (len(line) == 0) or (line[0] == '#'):
continue
- e = HostKeyEntry.from_line(line)
+ e = HostKeyEntry.from_line(line, lineno)
if e is not None:
- self._entries.append(e)
+ _hostnames = e.hostnames
+ for h in _hostnames:
+ if self.check(h, e.key):
+ e.hostnames.remove(h)
+ if len(e.hostnames):
+ self._entries.append(e)
f.close()
def save(self, filename):
diff --git a/paramiko/kex_gex.py b/paramiko/kex_gex.py
index 9c98339..c0455a1 100644
--- a/paramiko/kex_gex.py
+++ b/paramiko/kex_gex.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/kex_group1.py b/paramiko/kex_group1.py
index 1386cf3..6e89b6d 100644
--- a/paramiko/kex_group1.py
+++ b/paramiko/kex_group1.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/logging22.py b/paramiko/logging22.py
index ed1d891..e68c52c 100644
--- a/paramiko/logging22.py
+++ b/paramiko/logging22.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/message.py b/paramiko/message.py
index 47acc34..c0e8692 100644
--- a/paramiko/message.py
+++ b/paramiko/message.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/packet.py b/paramiko/packet.py
index 38a6d4b..6ab7363 100644
--- a/paramiko/packet.py
+++ b/paramiko/packet.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -33,17 +33,13 @@ from paramiko.ssh_exception import SSHException, ProxyCommandFailure
from paramiko.message import Message
-got_r_hmac = False
try:
- import r_hmac
- got_r_hmac = True
+ from r_hmac import HMAC
except ImportError:
- pass
+ from Crypto.Hash.HMAC import HMAC
+
def compute_hmac(key, message, digest_class):
- if got_r_hmac:
- return r_hmac.HMAC(key, message, digest_class).digest()
- from Crypto.Hash import HMAC
- return HMAC.HMAC(key, message, digest_class).digest()
+ return HMAC(key, message, digest_class).digest()
class NeedRekeyException (Exception):
@@ -156,7 +152,6 @@ class Packetizer (object):
def close(self):
self.__closed = True
- self.__socket.close()
def set_hexdump(self, hexdump):
self.__dump_packets = hexdump
diff --git a/paramiko/pipe.py b/paramiko/pipe.py
index 37191ef..db43d54 100644
--- a/paramiko/pipe.py
+++ b/paramiko/pipe.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/pkey.py b/paramiko/pkey.py
index 3e71222..b1199df 100644
--- a/paramiko/pkey.py
+++ b/paramiko/pkey.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/primes.py b/paramiko/primes.py
index 9ebfec1..9419cd6 100644
--- a/paramiko/primes.py
+++ b/paramiko/primes.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/proxy.py b/paramiko/proxy.py
index 218b76e..3f5c8bb 100644
--- a/paramiko/proxy.py
+++ b/paramiko/proxy.py
@@ -20,10 +20,13 @@
L{ProxyCommand}.
"""
+from datetime import datetime
import os
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
@@ -48,6 +51,8 @@ class ProxyCommand(object):
"""
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):
"""
@@ -64,7 +69,7 @@ class ProxyCommand(object):
# died and we can't proceed. The best option here is to
# raise an exception informing the user that the informed
# ProxyCommand is not working.
- raise BadProxyCommand(' '.join(self.cmd), e.strerror)
+ raise ProxyCommandFailure(' '.join(self.cmd), e.strerror)
return len(content)
def recv(self, size):
@@ -78,14 +83,30 @@ class ProxyCommand(object):
@rtype: 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, e:
- raise BadProxyCommand(' '.join(self.cmd), e.strerror)
+ raise ProxyCommandFailure(' '.join(self.cmd), e.strerror)
def close(self):
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/resource.py b/paramiko/resource.py
index 0d5c82f..6ef86d8 100644
--- a/paramiko/resource.py
+++ b/paramiko/resource.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py
index 1e2d8f9..c7500f8 100644
--- a/paramiko/rsakey.py
+++ b/paramiko/rsakey.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/server.py b/paramiko/server.py
index dac9bf1..fdb4094 100644
--- a/paramiko/server.py
+++ b/paramiko/server.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -550,6 +550,27 @@ class ServerInterface (object):
"""
return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
+ def check_channel_env_request(self, channel, name, value):
+ """
+ Check whether a given environment variable can be specified for the
+ given channel. This method should return C{True} if the server
+ is willing to set the specified environment variable. Note that
+ some environment variables (e.g., PATH) can be exceedingly
+ dangerous, so blindly allowing the client to set the environment
+ is almost certainly not a good idea.
+
+ The default implementation always returns C{False}.
+
+ @param channel: the L{Channel} the env request arrived on
+ @type channel: L{Channel}
+ @param name: foo bar baz
+ @type name: str
+ @param value: flklj
+ @type value: str
+ @rtype: bool
+ """
+ return False
+
class SubsystemHandler (threading.Thread):
"""
diff --git a/paramiko/sftp.py b/paramiko/sftp.py
index a0b08e0..a97c300 100644
--- a/paramiko/sftp.py
+++ b/paramiko/sftp.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/sftp_attr.py b/paramiko/sftp_attr.py
index 1f09421..b459b04 100644
--- a/paramiko/sftp_attr.py
+++ b/paramiko/sftp_attr.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py
index 17ea493..cf94582 100644
--- a/paramiko/sftp_client.py
+++ b/paramiko/sftp_client.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -736,7 +736,7 @@ 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
diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py
index e056d70..a39b1f4 100644
--- a/paramiko/sftp_file.py
+++ b/paramiko/sftp_file.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -20,6 +20,8 @@
L{SFTPFile}
"""
+from __future__ import with_statement
+
from binascii import hexlify
from collections import deque
import socket
@@ -53,7 +55,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()
@@ -91,7 +94,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 self._prefetch_extents.values() if x[0] <= offset]
if len(k) == 0:
return False
k.sort(lambda x, y: cmp(x[0], y[0]))
@@ -447,7 +450,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 +459,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,10 +474,12 @@ 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"
diff --git a/paramiko/sftp_handle.py b/paramiko/sftp_handle.py
index a6cd44a..29d3d0d 100644
--- a/paramiko/sftp_handle.py
+++ b/paramiko/sftp_handle.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/sftp_server.py b/paramiko/sftp_server.py
index 7cc6c0c..1833c2e 100644
--- a/paramiko/sftp_server.py
+++ b/paramiko/sftp_server.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/sftp_si.py b/paramiko/sftp_si.py
index 401a4e9..b0ee3c4 100644
--- a/paramiko/sftp_si.py
+++ b/paramiko/sftp_si.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py
index f2406dc..b502b56 100644
--- a/paramiko/ssh_exception.py
+++ b/paramiko/ssh_exception.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/transport.py b/paramiko/transport.py
index fd6dab7..b536175 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -42,6 +42,7 @@ from paramiko.message import Message
from paramiko.packet import Packetizer, NeedRekeyException
from paramiko.primes import ModulusPack
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,
@@ -202,7 +203,7 @@ class Transport (threading.Thread):
_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' )
+ _preferred_keys = ( 'ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256' )
_preferred_kex = ( 'diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1' )
_preferred_compression = ( 'none', )
@@ -227,6 +228,7 @@ class Transport (threading.Thread):
_key_info = {
'ssh-rsa': RSAKey,
'ssh-dss': DSSKey,
+ 'ecdsa-sha2-nistp256': ECDSAKey,
}
_kex_info = {
@@ -400,7 +402,6 @@ class Transport (threading.Thread):
@since: 1.5.3
"""
- self.sock.close()
self.close()
def get_security_options(self):
@@ -614,11 +615,10 @@ class Transport (threading.Thread):
"""
if not self.active:
return
- self.active = False
- self.packetizer.close()
- self.join()
+ self.stop_thread()
for chan in self._channels.values():
chan._unlink()
+ self.sock.close()
def get_remote_server_key(self):
"""
@@ -1391,6 +1391,8 @@ class Transport (threading.Thread):
def stop_thread(self):
self.active = False
self.packetizer.close()
+ while self.isAlive():
+ self.join(10)
### internals...
@@ -1439,7 +1441,7 @@ class Transport (threading.Thread):
break
self.clear_to_send_lock.release()
if time.time() > start + self.clear_to_send_timeout:
- raise SSHException('Key-exchange timed out waiting for key negotiation')
+ raise SSHException('Key-exchange timed out waiting for key negotiation')
try:
self._send_message(data)
finally:
@@ -1539,10 +1541,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:
@@ -1612,7 +1610,10 @@ class Transport (threading.Thread):
self.saved_exception = e
except socket.error, 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)
diff --git a/paramiko/util.py b/paramiko/util.py
index f4bfbec..85ee6b0 100644
--- a/paramiko/util.py
+++ b/paramiko/util.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/paramiko/win_pageant.py b/paramiko/win_pageant.py
index d77d58f..d588e81 100644
--- a/paramiko/win_pageant.py
+++ b/paramiko/win_pageant.py
@@ -8,7 +8,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -21,28 +21,20 @@
Functions for communicating with Pageant, the basic windows ssh agent program.
"""
-import os
-import struct
-import tempfile
-import mmap
+from __future__ import with_statement
+
import array
-import platform
import ctypes.wintypes
+import platform
+import struct
-# if you're on windows, you should have one of these, i guess?
-# ctypes is part of standard library since Python 2.5
-_has_win32all = False
-_has_ctypes = False
try:
- # win32gui is preferred over win32ui to avoid MFC dependencies
- import win32gui
- _has_win32all = True
+ import _thread as thread # Python 3.x
except ImportError:
- try:
- import ctypes
- _has_ctypes = True
- except ImportError:
- pass
+ import thread # Python 2.5-2.7
+
+from . import _winapi
+
_AGENT_COPYDATA_ID = 0x804e50ba
_AGENT_MAX_MSGLEN = 8192
@@ -52,16 +44,7 @@ win32con_WM_COPYDATA = 74
def _get_pageant_window_object():
- if _has_win32all:
- try:
- hwnd = win32gui.FindWindow('Pageant', 'Pageant')
- return hwnd
- except win32gui.error:
- pass
- elif _has_ctypes:
- # Return 0 if there is no Pageant window.
- return ctypes.windll.user32.FindWindowA('Pageant', 'Pageant')
- return None
+ return ctypes.windll.user32.FindWindowA('Pageant', 'Pageant')
def can_talk_to_agent():
@@ -71,11 +54,12 @@ def can_talk_to_agent():
This checks both if we have the required libraries (win32all or ctypes)
and if there is a Pageant currently running.
"""
- if (_has_win32all or _has_ctypes) and _get_pageant_window_object():
- return True
- return False
+ 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
@@ -85,53 +69,46 @@ class COPYDATASTRUCT(ctypes.Structure):
('num_data', ULONG_PTR),
('data_size', ctypes.wintypes.DWORD),
('data_loc', ctypes.c_void_p),
- ]
+ ]
+
def _query_pageant(msg):
+ """
+ Communication with the Pageant process is done through a shared
+ memory-mapped file.
+ """
hwnd = _get_pageant_window_object()
if not hwnd:
# Raise a failure to connect exception, pageant isn't running anymore!
return None
- # Write our pageant request string into the file (pageant will read this to determine what to do)
- filename = tempfile.mktemp('.pag')
- map_filename = os.path.basename(filename)
-
- f = open(filename, 'w+b')
- f.write(msg )
- # Ensure the rest of the file is empty, otherwise pageant will read this
- f.write('\0' * (_AGENT_MAX_MSGLEN - len(msg)))
- # Create the shared file map that pageant will use to read from
- pymap = mmap.mmap(f.fileno(), _AGENT_MAX_MSGLEN, tagname=map_filename, access=mmap.ACCESS_WRITE)
- try:
+ # create a name for the mmap
+ map_name = 'PageantRequest%08x' % thread.get_ident()
+
+ pymap = _winapi.MemoryMap(map_name, _AGENT_MAX_MSGLEN,
+ _winapi.get_security_attributes_for_user(),
+ )
+ with pymap:
+ pymap.write(msg)
# Create an array buffer containing the mapped filename
- char_buffer = array.array("c", map_filename + '\0')
+ char_buffer = array.array("c", map_name + '\0')
char_buffer_address, char_buffer_size = char_buffer.buffer_info()
# Create a string to use for the SendMessage function call
- cds = COPYDATASTRUCT(_AGENT_COPYDATA_ID, char_buffer_size, char_buffer_address)
+ cds = COPYDATASTRUCT(_AGENT_COPYDATA_ID, char_buffer_size,
+ char_buffer_address)
- if _has_win32all:
- # win32gui.SendMessage should also allow the same pattern as
- # ctypes, but let's keep it like this for now...
- response = win32gui.SendMessage(hwnd, win32con_WM_COPYDATA, ctypes.sizeof(cds), ctypes.addressof(cds))
- elif _has_ctypes:
- response = ctypes.windll.user32.SendMessageA(hwnd, win32con_WM_COPYDATA, ctypes.sizeof(cds), ctypes.byref(cds))
- else:
- response = 0
+ response = ctypes.windll.user32.SendMessageA(hwnd,
+ win32con_WM_COPYDATA, ctypes.sizeof(cds), ctypes.byref(cds))
if response > 0:
+ pymap.seek(0)
datalen = pymap.read(4)
retlen = struct.unpack('>I', datalen)[0]
return datalen + pymap.read(retlen)
return None
- finally:
- pymap.close()
- f.close()
- # Remove the file, it was temporary only
- os.unlink(filename)
-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/requirements.txt b/requirements.txt
deleted file mode 100644
index 75112a2..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-pycrypto
-tox
diff --git a/setup.py b/setup.py
index d6caccf..7d6706e 100644
--- a/setup.py
+++ b/setup.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -40,7 +40,9 @@ import sys
try:
from setuptools import setup
kw = {
- 'install_requires': 'pycrypto >= 2.1, != 2.4',
+ 'install_requires': ['pycrypto >= 2.1, != 2.4',
+ 'ecdsa',
+ ],
}
except ImportError:
from distutils.core import setup
@@ -52,7 +54,7 @@ if sys.platform == 'darwin':
setup(name = "paramiko",
- version = "1.10.1",
+ version = "1.12.2",
description = "SSH2 protocol library",
author = "Jeff Forcier",
author_email = "jeff@bitprophet.org",
diff --git a/setup_helper.py b/setup_helper.py
index e8f3f2f..ff6b0e1 100644
--- a/setup_helper.py
+++ b/setup_helper.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/sites/_shared_static/logo.png b/sites/_shared_static/logo.png
new file mode 100644
index 0000000..bc76697
--- /dev/null
+++ b/sites/_shared_static/logo.png
Binary files differ
diff --git a/sites/docs/conf.py b/sites/docs/conf.py
new file mode 100644
index 0000000..0c7ffe5
--- /dev/null
+++ b/sites/docs/conf.py
@@ -0,0 +1,4 @@
+# Obtain shared config values
+import os, sys
+sys.path.append(os.path.abspath('..'))
+from shared_conf import *
diff --git a/sites/docs/index.rst b/sites/docs/index.rst
new file mode 100644
index 0000000..08b3432
--- /dev/null
+++ b/sites/docs/index.rst
@@ -0,0 +1,6 @@
+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 website <http://paramiko.org>`_.
diff --git a/sites/shared_conf.py b/sites/shared_conf.py
new file mode 100644
index 0000000..86ecdfe
--- /dev/null
+++ b/sites/shared_conf.py
@@ -0,0 +1,41 @@
+from datetime import datetime
+import os
+import sys
+
+import alabaster
+
+
+# Alabaster theme + mini-extension
+html_theme_path = [alabaster.get_path()]
+extensions = ['alabaster']
+# Paths relative to invoking conf.py - not this shared file
+html_static_path = ['../_shared_static']
+html_theme = 'alabaster'
+html_theme_options = {
+ 'description': "A Python implementation of SSHv2.",
+ 'github_user': 'paramiko',
+ 'github_repo': 'paramiko',
+ 'gittip_user': 'bitprophet',
+ 'analytics_id': 'UA-18486793-2',
+
+ 'link': '#3782BE',
+ 'link_hover': '#3782BE',
+}
+html_sidebars = {
+ '**': [
+ 'about.html',
+ 'navigation.html',
+ 'searchbox.html',
+ 'donate.html',
+ ]
+}
+
+# Regular settings
+project = u'Paramiko'
+year = datetime.now().year
+copyright = u'%d Jeff Forcier' % year
+master_doc = 'index'
+templates_path = ['_templates']
+exclude_trees = ['_build']
+source_suffix = '.rst'
+default_role = 'obj'
diff --git a/sites/www/_templates/rss.xml b/sites/www/_templates/rss.xml
new file mode 100644
index 0000000..f6f9cbd
--- /dev/null
+++ b/sites/www/_templates/rss.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
+ <channel>
+ <atom:link href="{{ atom }}" rel="self" type="application/rss+xml" />
+ <title>{{ title }}</title>
+ <link>{{ link }}</link>
+ <description>{{ description }}</description>
+ <pubDate>{{ date }}</pubDate>
+ {% for link, title, desc, date in posts %}
+ <item>
+ <link>{{ link }}</link>
+ <guid>{{ link }}</guid>
+ <title><![CDATA[{{ title }}]]></title>
+ <description><![CDATA[{{ desc }}]]></description>
+ <pubDate>{{ date }}</pubDate>
+ </item>
+ {% endfor %}
+ </channel>
+</rss>
diff --git a/sites/www/blog.py b/sites/www/blog.py
new file mode 100644
index 0000000..3b129eb
--- /dev/null
+++ b/sites/www/blog.py
@@ -0,0 +1,140 @@
+from collections import namedtuple
+from datetime import datetime
+import time
+import email.utils
+
+from sphinx.util.compat import Directive
+from docutils import nodes
+
+
+class BlogDateDirective(Directive):
+ """
+ Used to parse/attach date info to blog post documents.
+
+ No nodes generated, since none are needed.
+ """
+ has_content = True
+
+ def run(self):
+ # Tag parent document with parsed date value.
+ self.state.document.blog_date = datetime.strptime(
+ self.content[0], "%Y-%m-%d"
+ )
+ # Don't actually insert any nodes, we're already done.
+ return []
+
+class blog_post_list(nodes.General, nodes.Element):
+ pass
+
+class BlogPostListDirective(Directive):
+ """
+ Simply spits out a 'blog_post_list' temporary node for replacement.
+
+ Gets replaced at doctree-resolved time - only then will all blog post
+ documents be written out (& their date directives executed).
+ """
+ def run(self):
+ return [blog_post_list('')]
+
+
+Post = namedtuple('Post', 'name doc title date opener')
+
+def get_posts(app):
+ # Obtain blog posts
+ post_names = filter(lambda x: x.startswith('blog/'), app.env.found_docs)
+ posts = map(lambda x: (x, app.env.get_doctree(x)), post_names)
+ # Obtain common data used for list page & RSS
+ data = []
+ for post, doc in sorted(posts, key=lambda x: x[1].blog_date, reverse=True):
+ # Welp. No "nice" way to get post title. Thanks Sphinx.
+ title = doc[0][0][0]
+ # Date. This may or may not end up reflecting the required
+ # *input* format, but doing it here gives us flexibility.
+ date = doc.blog_date
+ # 1st paragraph as opener. TODO: allow a role or something marking
+ # where to actually pull from?
+ opener = doc.traverse(nodes.paragraph)[0]
+ data.append(Post(post, doc, title, date, opener))
+ return data
+
+def replace_blog_post_lists(app, doctree, fromdocname):
+ """
+ Replace blog_post_list nodes with ordered list-o-links to posts.
+ """
+ # Obtain blog posts
+ post_names = filter(lambda x: x.startswith('blog/'), app.env.found_docs)
+ posts = map(lambda x: (x, app.env.get_doctree(x)), post_names)
+ # Build "list" of links/etc
+ post_links = []
+ for post, doc, title, date, opener in get_posts(app):
+ # Link itself
+ uri = app.builder.get_relative_uri(fromdocname, post)
+ link = nodes.reference('', '', refdocname=post, refuri=uri)
+ # Title, bolded. TODO: use 'topic' or something maybe?
+ link.append(nodes.strong('', title))
+ date = date.strftime("%Y-%m-%d")
+ # Meh @ not having great docutils nodes which map to this.
+ html = '<div class="timestamp"><span>%s</span></div>' % date
+ timestamp = nodes.raw(text=html, format='html')
+ # NOTE: may group these within another element later if styling
+ # necessitates it
+ group = [timestamp, nodes.paragraph('', '', link), opener]
+ post_links.extend(group)
+
+ # Replace temp node(s) w/ expanded list-o-links
+ for node in doctree.traverse(blog_post_list):
+ node.replace_self(post_links)
+
+def rss_timestamp(timestamp):
+ # Use horribly inappropriate module for its magical daylight-savings-aware
+ # timezone madness. Props to Tinkerer for the idea.
+ return email.utils.formatdate(
+ time.mktime(timestamp.timetuple()),
+ localtime=True
+ )
+
+def generate_rss(app):
+ # Meh at having to run this subroutine like 3x per build. Not worth trying
+ # to be clever for now tho.
+ posts_ = get_posts(app)
+ # LOL URLs
+ root = app.config.rss_link
+ if not root.endswith('/'):
+ root += '/'
+ # Oh boy
+ posts = [
+ (
+ root + app.builder.get_target_uri(x.name),
+ x.title,
+ str(x.opener[0]), # Grab inner text element from paragraph
+ rss_timestamp(x.date),
+ )
+ for x in posts_
+ ]
+ location = 'blog/rss.xml'
+ context = {
+ 'title': app.config.project,
+ 'link': root,
+ 'atom': root + location,
+ 'description': app.config.rss_description,
+ # 'posts' is sorted by date already
+ 'date': rss_timestamp(posts_[0].date),
+ 'posts': posts,
+ }
+ yield (location, context, 'rss.xml')
+
+def setup(app):
+ # Link in RSS feed back to main website, e.g. 'http://paramiko.org'
+ app.add_config_value('rss_link', None, '')
+ # Ditto for RSS description field
+ app.add_config_value('rss_description', None, '')
+ # Interprets date metadata in blog post documents
+ app.add_directive('date', BlogDateDirective)
+ # Inserts blog post list node (in e.g. a listing page) for replacement
+ # below
+ app.add_node(blog_post_list)
+ app.add_directive('blog-posts', BlogPostListDirective)
+ # Performs abovementioned replacement
+ app.connect('doctree-resolved', replace_blog_post_lists)
+ # Generates RSS page from whole cloth at page generation step
+ app.connect('html-collect-pages', generate_rss)
diff --git a/sites/www/blog.rst b/sites/www/blog.rst
new file mode 100644
index 0000000..af9651e
--- /dev/null
+++ b/sites/www/blog.rst
@@ -0,0 +1,16 @@
+====
+Blog
+====
+
+.. blog-posts directive gets replaced with an ordered list of blog posts.
+
+.. blog-posts::
+
+
+.. The following toctree ensures blog posts get processed.
+
+.. toctree::
+ :hidden:
+ :glob:
+
+ blog/*
diff --git a/sites/www/blog/first-post.rst b/sites/www/blog/first-post.rst
new file mode 100644
index 0000000..7b07507
--- /dev/null
+++ b/sites/www/blog/first-post.rst
@@ -0,0 +1,7 @@
+===========
+First post!
+===========
+
+A blog post.
+
+.. date:: 2013-12-04
diff --git a/sites/www/blog/second-post.rst b/sites/www/blog/second-post.rst
new file mode 100644
index 0000000..c4463f3
--- /dev/null
+++ b/sites/www/blog/second-post.rst
@@ -0,0 +1,7 @@
+===========
+Another one
+===========
+
+.. date:: 2013-12-05
+
+Indeed!
diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst
new file mode 100644
index 0000000..ffacf6e
--- /dev/null
+++ b/sites/www/changelog.rst
@@ -0,0 +1,126 @@
+=========
+Changelog
+=========
+
+* :release:`1.12.2 <2014-02-14>`
+* :release:`1.11.4 <2014-02-14>`
+* :release:`1.10.6 <2014-02-14>`
+* :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>`
+* :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>`
+* :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
+ what you have in your known_hosts file.
+ * Mega thanks to Ethan Glasser-Camp for the patch.
+
+* :feature:`136` Add server-side support for the SSH protocol's 'env' command.
+ Thanks to Benjamin Pollack for the 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.
+* :bug:`200` Fix an exception-causing typo in ``demo_simple.py``. Thanks to Alex
+ Buchanan for catch & Dave Foster for patch.
+* :bug:`199` Typo fix in the license header cross-project. Thanks to Armin
+ Ronacher for catch & patch.
+* :release:`1.11.1 <2013-09-20>`
+* :release:`1.10.3 <2013-09-20>`
+* :bug:`162` Clean up HMAC module import to avoid deadlocks in certain uses of
+ SSHClient. Thanks to Gernot Hillier for the catch & suggested fix.
+* :bug:`36` Fix the port-forwarding demo to avoid file descriptor errors.
+ Thanks to Jonathan Halcrow for catch & patch.
+* :bug:`168` Update config handling to properly handle multiple 'localforward'
+ and 'remoteforward' keys. Thanks to Emre Yılmaz for the patch.
+* :release:`1.11.0 <2013-07-26>`
+* :release:`1.10.2 <2013-07-26>`
+* :bug:`98 major` 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.
+* :support:`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.
+* :bug:`87 major` 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.
+* :bug:`153` (also :issue:`67`) Warn on parse failure when reading known_hosts
+ file. Thanks to ``@glasserc`` for patch.
+* :bug:`146` Indentation fixes for readability. Thanks to Abhinav Upadhyay for
+ catch & patch.
+* :release:`1.10.1 <2013-04-05>`
+* :bug:`142` (`Fabric #811 <https://github.com/fabric/fabric/issues/811>`_)
+ SFTP put of empty file will still return the attributes of the put file.
+ Thanks to Jason R. Coombs for the patch.
+* :bug:`154` (`Fabric #876 <https://github.com/fabric/fabric/issues/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.
+* :release:`1.10.0 <2013-03-01>`
+* :feature:`66` Batch SFTP writes to help speed up file transfers. Thanks to
+ Olle Lundberg for the patch.
+* :bug:`133 major` 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.
+* :feature:`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.
+* :feature:`110` Honor SSH config ``AddressFamily`` setting when looking up
+ local host's FQDN. Thanks to John Hensley for the patch.
+* :feature:`128` Defer FQDN resolution until needed, when parsing SSH config
+ files. Thanks to Parantapa Bhattacharya for catch & patch.
+* :bug:`102 major` 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.
+* :feature:`127` Turn ``SFTPFile`` into a context manager. Thanks to Michael
+ Williamson for the patch.
+* :feature:`116` Limit ``Message.get_bytes`` to an upper bound of 1MB to protect
+ against potential DoS vectors. Thanks to ``@mvschaik`` for catch & patch.
+* :feature:`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.
+* :feature:`71` Add ``SFTPClient.putfo`` and ``.getfo`` methods to allow direct
+ uploading/downloading of file-like objects. Thanks to Eric Buehl for the
+ patch.
+* :feature:`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.
+* :support:`94` Remove duplication of SSH port constant. Thanks to Olle
+ Lundberg for the catch.
+* :feature:`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.
diff --git a/sites/www/conf.py b/sites/www/conf.py
new file mode 100644
index 0000000..481acdf
--- /dev/null
+++ b/sites/www/conf.py
@@ -0,0 +1,35 @@
+# Obtain shared config values
+import sys
+import os
+from os.path import abspath, join, dirname
+
+sys.path.append(abspath(join(dirname(__file__), '..')))
+from shared_conf import *
+
+# Local blog extension
+sys.path.append(abspath('.'))
+extensions.append('blog')
+rss_link = 'http://paramiko.org'
+rss_description = 'Paramiko project news'
+
+# Releases changelog extension
+extensions.append('releases')
+releases_release_uri = "https://github.com/paramiko/paramiko/tree/%s"
+releases_issue_uri = "https://github.com/paramiko/paramiko/issues/%s"
+
+# Intersphinx for referencing API/usage docs
+extensions.append('sphinx.ext.intersphinx')
+# Default is 'local' building, but reference the public docs site when building
+# under RTD.
+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),
+#}
+
+# Sister-site links to API docs
+html_theme_options['extra_nav_links'] = {
+ "API Docs": 'http://docs.paramiko.org',
+}
diff --git a/sites/www/contact.rst b/sites/www/contact.rst
new file mode 100644
index 0000000..2b6583f
--- /dev/null
+++ b/sites/www/contact.rst
@@ -0,0 +1,11 @@
+=======
+Contact
+=======
+
+You can get in touch with the developer & user community in any of the
+following ways:
+
+* IRC: ``#paramiko`` on Freenode
+* Mailing list: ``paramiko@librelist.com`` (see `the LibreList homepage
+ <http://librelist.com>`_ for usage details).
+* This website - a blog section is forthcoming.
diff --git a/sites/www/contributing.rst b/sites/www/contributing.rst
new file mode 100644
index 0000000..2b752cc
--- /dev/null
+++ b/sites/www/contributing.rst
@@ -0,0 +1,19 @@
+============
+Contributing
+============
+
+How to get the code
+===================
+
+Our primary Git repository is on Github at `paramiko/paramiko
+<https://github.com/paramiko/paramiko>`_; please follow their instructions for
+cloning to your local system. (If you intend to submit patches/pull requests,
+we recommend forking first, then cloning your fork. Github has excellent
+documentation for all this.)
+
+
+How to submit bug reports or new code
+=====================================
+
+Please see `this project-agnostic contribution guide
+<http://contribution-guide.org>`_ - we follow it explicitly.
diff --git a/sites/www/index.rst b/sites/www/index.rst
new file mode 100644
index 0000000..7fefedd
--- /dev/null
+++ b/sites/www/index.rst
@@ -0,0 +1,38 @@
+Welcome to Paramiko!
+====================
+
+Paramiko is a Python (2.5+) 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.
+
+This website covers project information for Paramiko such as the changelog,
+contribution guidelines, development roadmap, news/blog, and so forth. Detailed
+usage and API documentation can be found at our code documentation site,
+`docs.paramiko.org <http://docs.paramiko.org>`_.
+
+.. toctree::
+ changelog
+ installing
+ contributing
+ contact
+
+.. Hide blog in hidden toctree for now (to avoid warnings.)
+
+.. toctree::
+ :hidden:
+
+ blog
+
+
+.. rubric:: Footnotes
+
+.. [#]
+ SSH is defined in RFCs
+ `4251 <http://www.rfc-editor.org/rfc/rfc4251.txt>`_,
+ `4252 <http://www.rfc-editor.org/rfc/rfc4252.txt>`_,
+ `4253 <http://www.rfc-editor.org/rfc/rfc4253.txt>`_, and
+ `4254 <http://www.rfc-editor.org/rfc/rfc4254.txt>`_;
+ the primary working implementation of the protocol is the `OpenSSH project
+ <http://openssh.org>`_. Paramiko implements a large portion of the SSH
+ feature set, but there are occasional gaps.
diff --git a/sites/www/installing.rst b/sites/www/installing.rst
new file mode 100644
index 0000000..0d4dc1a
--- /dev/null
+++ b/sites/www/installing.rst
@@ -0,0 +1,105 @@
+==========
+Installing
+==========
+
+Paramiko itself
+===============
+
+The recommended way to get Invoke is to **install the latest stable release**
+via `pip <http://pip-installer.org>`_::
+
+ $ pip install paramiko
+
+.. note::
+ 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.
+
+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.
+
+.. _pycrypto-and-pip:
+
+Possible gotcha on older Python and/or pip versions
+---------------------------------------------------
+
+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:
+
+* Python = 2.5.x
+* PyCrypto >= 2.1 (required for most modern versions of Paramiko)
+* ``pip`` < 0.8.1
+
+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)
+
+
+C extension
+-----------
+
+Unless you are installing from a precompiled source such as a Debian apt
+repository or RedHat RPM, or using :ref:`pypm <pypm>`, you will also need the
+ability to build Python C-based modules from source in order to install
+PyCrypto. Users on **Unix-based platforms** such as Ubuntu or Mac OS X will
+need the traditional C build toolchain installed (e.g. Developer Tools / XCode
+Tools on the Mac, or the ``build-essential`` package on Ubuntu or Debian Linux
+-- basically, anything with ``gcc``, ``make`` and so forth) as well as the
+Python development libraries, often named ``python-dev`` or similar.
+
+For **Windows** users we recommend using :ref:`pypm`, installing a C
+development environment such as `Cygwin <http://cygwin.com>`_ or obtaining a
+precompiled Win32 PyCrypto package from `voidspace's Python modules page
+<http://www.voidspace.org.uk/python/modules.shtml#pycrypto>`_.
+
+.. note::
+ Some Windows users whose Python is 64-bit have found that the PyCrypto
+ dependency ``winrandom`` may not install properly, leading to ImportErrors.
+ In this scenario, you'll probably need to compile ``winrandom`` yourself
+ via e.g. MS Visual Studio. See `Fabric #194
+ <https://github.com/fabric/fabric/issues/194>`_ for info.
+
+
+.. _pypm:
+
+ActivePython and PyPM
+=====================
+
+Windows users who already have ActiveState's `ActivePython
+<http://www.activestate.com/activepython/downloads>`_ distribution installed
+may find Paramiko is best installed with `its package manager, PyPM
+<http://code.activestate.com/pypm/>`_. Below is example output from an
+installation of Paramiko via ``pypm``::
+
+ C:\> pypm install paramiko
+ The following packages will be installed into "%APPDATA%\Python" (2.7):
+ paramiko-1.7.8 pycrypto-2.4
+ Get: [pypm-free.activestate.com] paramiko 1.7.8
+ Get: [pypm-free.activestate.com] pycrypto 2.4
+ Installing paramiko-1.7.8
+ Installing pycrypto-2.4
+ C:\>
diff --git a/tasks.py b/tasks.py
new file mode 100644
index 0000000..f8f4017
--- /dev/null
+++ b/tasks.py
@@ -0,0 +1,34 @@
+from os.path import join
+
+from invoke import Collection, ctask as task
+from invocations import docs as _docs
+
+
+d = 'sites'
+
+# Usage doc/API site (published as docs.paramiko.org)
+path = join(d, 'docs')
+docs = Collection.from_module(_docs, name='docs', config={
+ 'sphinx.source': path,
+ 'sphinx.target': join(path, '_build'),
+})
+
+# Main/about/changelog site ((www.)?paramiko.org)
+path = join(d, 'www')
+www = Collection.from_module(_docs, name='www', config={
+ 'sphinx.source': path,
+ 'sphinx.target': join(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")
+
+
+ns = Collection(test, coverage, docs=docs, www=www)
diff --git a/test.py b/test.py
index f3dd4d2..6702e53 100755
--- a/test.py
+++ b/test.py
@@ -9,7 +9,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/tests/loop.py b/tests/loop.py
index ffa8e3c..91c216d 100644
--- a/tests/loop.py
+++ b/tests/loop.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/tests/stub_sftp.py b/tests/stub_sftp.py
index 7f1ecc7..3021d81 100644
--- a/tests/stub_sftp.py
+++ b/tests/stub_sftp.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/tests/test_auth.py b/tests/test_auth.py
index 816e978..61fe63f 100644
--- a/tests/test_auth.py
+++ b/tests/test_auth.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/tests/test_buffered_pipe.py b/tests/test_buffered_pipe.py
index b9d91f6..47ece93 100644
--- a/tests/test_buffered_pipe.py
+++ b/tests/test_buffered_pipe.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/tests/test_client.py b/tests/test_client.py
index 08ef1f9..fae1d32 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -20,11 +20,14 @@
Some unit tests for SSHClient.
"""
+from __future__ import with_statement # Python 2.5 support
import socket
import threading
import time
import unittest
import weakref
+import warnings
+import os
from binascii import hexlify
import paramiko
@@ -184,7 +187,33 @@ class SSHClientTest (unittest.TestCase):
self.assertEquals(1, len(self.tc.get_host_keys()))
self.assertEquals(public_host_key, self.tc.get_host_keys()['[%s]:%d' % (self.addr, self.port)]['ssh-rsa'])
- def test_5_cleanup(self):
+ def test_5_save_host_keys(self):
+ """
+ verify that SSHClient correctly saves a known_hosts file.
+ """
+ warnings.filterwarnings('ignore', 'tempnam.*')
+
+ host_key = paramiko.RSAKey.from_private_key_file('tests/test_rsa.key')
+ public_host_key = paramiko.RSAKey(data=str(host_key))
+ localname = os.tempnam()
+
+ client = paramiko.SSHClient()
+ self.assertEquals(0, len(client.get_host_keys()))
+
+ host_id = '[%s]:%d' % (self.addr, self.port)
+
+ client.get_host_keys().add(host_id, 'ssh-rsa', public_host_key)
+ self.assertEquals(1, len(client.get_host_keys()))
+ self.assertEquals(public_host_key, client.get_host_keys()[host_id]['ssh-rsa'])
+
+ client.save_host_keys(localname)
+
+ with open(localname) as fd:
+ assert host_id in fd.read()
+
+ os.unlink(localname)
+
+ def test_6_cleanup(self):
"""
verify that when an SSHClient is collected, its transport (and the
transport's packetizer) is closed.
diff --git a/tests/test_ecdsa.key b/tests/test_ecdsa.key
new file mode 100644
index 0000000..42d4473
--- /dev/null
+++ b/tests/test_ecdsa.key
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIKB6ty3yVyKEnfF/zprx0qwC76MsMlHY4HXCnqho2eKioAoGCCqGSM49
+AwEHoUQDQgAElI9mbdlaS+T9nHxY/59lFnn80EEecZDBHq4gLpccY8Mge5ZTMiMD
+ADRvOqQ5R98Sxst765CAqXmRtz8vwoD96g==
+-----END EC PRIVATE KEY-----
diff --git a/tests/test_ecdsa_password.key b/tests/test_ecdsa_password.key
new file mode 100644
index 0000000..eb7910e
--- /dev/null
+++ b/tests/test_ecdsa_password.key
@@ -0,0 +1,8 @@
+-----BEGIN EC PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,EEB56BC745EDB2DE04FC3FE1F8DA387E
+
+wdt7QTCa6ahTJLaEPH7NhHyBcxhzrzf93d4UwQOuAhkM6//jKD4lF9fErHBW0f3B
+ExberCU3UxfEF3xX2thXiLw47JgeOCeQUlqRFx92p36k6YmfNGX6W8CsZ3d+XodF
+Z+pb6m285CiSX+W95NenFMexXFsIpntiCvTifTKJ8os=
+-----END EC PRIVATE KEY-----
diff --git a/tests/test_file.py b/tests/test_file.py
index c539b22..6cb3507 100755
--- a/tests/test_file.py
+++ b/tests/test_file.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/tests/test_hostkeys.py b/tests/test_hostkeys.py
index e28a41d..44070cb 100644
--- a/tests/test_hostkeys.py
+++ b/tests/test_hostkeys.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/tests/test_kex.py b/tests/test_kex.py
index f6e6996..39d2e17 100644
--- a/tests/test_kex.py
+++ b/tests/test_kex.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/tests/test_message.py b/tests/test_message.py
index 7bfd44d..ad622a2 100644
--- a/tests/test_message.py
+++ b/tests/test_message.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/tests/test_packetizer.py b/tests/test_packetizer.py
index d1eb584..1f5bec0 100644
--- a/tests/test_packetizer.py
+++ b/tests/test_packetizer.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/tests/test_pkey.py b/tests/test_pkey.py
index 89d5580..8e8c4aa 100644
--- a/tests/test_pkey.py
+++ b/tests/test_pkey.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -23,14 +23,17 @@ Some unit tests for public/private key objects.
from binascii import hexlify, unhexlify
import StringIO
import unittest
-from paramiko import RSAKey, DSSKey, Message, util
+from paramiko import RSAKey, DSSKey, ECDSAKey, Message, util
from paramiko.common import rng
# from openssh's ssh-keygen
PUB_RSA = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA049W6geFpmsljTwfvI1UmKWWJPNFI74+vNKTk4dmzkQY2yAMs6FhlvhlI8ysU4oj71ZsRYMecHbBbxdN79+JRFVYTKaLqjwGENeTd+yv4q+V2PvZv3fLnzApI3l7EJCqhWwJUHJ1jAkZzqDx0tyOL4uoZpww3nmE0kb3y21tH4c='
PUB_DSS = 'ssh-dss AAAAB3NzaC1kc3MAAACBAOeBpgNnfRzr/twmAQRu2XwWAp3CFtrVnug6s6fgwj/oLjYbVtjAy6pl/h0EKCWx2rf1IetyNsTxWrniA9I6HeDj65X1FyDkg6g8tvCnaNB8Xp/UUhuzHuGsMIipRxBxw9LF608EqZcj1E3ytktoW5B5OcjrkEoz3xG7C+rpIjYvAAAAFQDwz4UnmsGiSNu5iqjn3uTzwUpshwAAAIEAkxfFeY8P2wZpDjX0MimZl5wkoFQDL25cPzGBuB4OnB8NoUk/yjAHIIpEShw8V+LzouMK5CTJQo5+Ngw3qIch/WgRmMHy4kBq1SsXMjQCte1So6HBMvBPIW5SiMTmjCfZZiw4AYHK+B/JaOwaG9yRg2Ejg4Ok10+XFDxlqZo8Y+wAAACARmR7CCPjodxASvRbIyzaVpZoJ/Z6x7dAumV+ysrV1BVYd0lYukmnjO1kKBWApqpH1ve9XDQYN8zgxM4b16L21kpoWQnZtXrY3GZ4/it9kUgyB7+NwacIBlXa8cMDL7Q/69o0d54U0X/NeX5QxuYR6OMJlrkQB7oiW/P/1mwjQgE='
+PUB_ECDSA = 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJSPZm3ZWkvk/Zx8WP+fZRZ5/NBBHnGQwR6uIC6XHGPDIHuWUzIjAwA0bzqkOUffEsbLe+uQgKl5kbc/L8KA/eo='
+
FINGER_RSA = '1024 60:73:38:44:cb:51:86:65:7f:de:da:a2:2b:5a:57:d5'
FINGER_DSS = '1024 44:78:f0:b9:a2:3c:c5:18:20:09:ff:75:5b:c1:d2:6c'
+FINGER_ECDSA = '256 25:19:eb:55:e6:a1:47:ff:4f:38:d2:75:6f:a5:d5:60'
SIGNED_RSA = '20:d7:8a:31:21:cb:f7:92:12:f2:a4:89:37:f5:78:af:e6:16:b6:25:b9:97:3d:a2:cd:5f:ca:20:21:73:4c:ad:34:73:8f:20:77:28:e2:94:15:08:d8:91:40:7a:85:83:bf:18:37:95:dc:54:1a:9b:88:29:6c:73:ca:38:b4:04:f1:56:b9:f2:42:9d:52:1b:29:29:b4:4f:fd:c9:2d:af:47:d2:40:76:30:f3:63:45:0c:d9:1d:43:86:0f:1c:70:e2:93:12:34:f3:ac:c5:0a:2f:14:50:66:59:f1:88:ee:c1:4a:e9:d1:9c:4e:46:f0:0e:47:6f:38:74:f1:44:a8'
RSA_PRIVATE_OUT = """\
@@ -66,6 +69,13 @@ QPSch9pT9XHqn+1rZ4bK+QGA
-----END DSA PRIVATE KEY-----
"""
+ECDSA_PRIVATE_OUT = """\
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIKB6ty3yVyKEnfF/zprx0qwC76MsMlHY4HXCnqho2eKioAoGCCqGSM49
+AwEHoUQDQgAElI9mbdlaS+T9nHxY/59lFnn80EEecZDBHq4gLpccY8Mge5ZTMiMD
+ADRvOqQ5R98Sxst765CAqXmRtz8vwoD96g==
+-----END EC PRIVATE KEY-----
+"""
class KeyTest (unittest.TestCase):
@@ -176,7 +186,7 @@ class KeyTest (unittest.TestCase):
msg.rewind()
pub = DSSKey(data=str(key))
self.assert_(pub.verify_ssh_sig('ice weasels', msg))
-
+
def test_A_generate_rsa(self):
key = RSAKey.generate(1024)
msg = key.sign_ssh_data(rng, 'jerri blank')
@@ -188,3 +198,53 @@ class KeyTest (unittest.TestCase):
msg = key.sign_ssh_data(rng, 'jerri blank')
msg.rewind()
self.assert_(key.verify_ssh_sig('jerri blank', msg))
+
+ def test_10_load_ecdsa(self):
+ key = ECDSAKey.from_private_key_file('tests/test_ecdsa.key')
+ self.assertEquals('ecdsa-sha2-nistp256', key.get_name())
+ exp_ecdsa = FINGER_ECDSA.split()[1].replace(':', '')
+ my_ecdsa = hexlify(key.get_fingerprint())
+ self.assertEquals(exp_ecdsa, my_ecdsa)
+ self.assertEquals(PUB_ECDSA.split()[1], key.get_base64())
+ self.assertEquals(256, key.get_bits())
+
+ s = StringIO.StringIO()
+ key.write_private_key(s)
+ self.assertEquals(ECDSA_PRIVATE_OUT, s.getvalue())
+ s.seek(0)
+ key2 = ECDSAKey.from_private_key(s)
+ self.assertEquals(key, key2)
+
+ def test_11_load_ecdsa_password(self):
+ key = ECDSAKey.from_private_key_file('tests/test_ecdsa_password.key', 'television')
+ self.assertEquals('ecdsa-sha2-nistp256', key.get_name())
+ exp_ecdsa = FINGER_ECDSA.split()[1].replace(':', '')
+ my_ecdsa = hexlify(key.get_fingerprint())
+ self.assertEquals(exp_ecdsa, my_ecdsa)
+ self.assertEquals(PUB_ECDSA.split()[1], key.get_base64())
+ self.assertEquals(256, key.get_bits())
+
+ def test_12_compare_ecdsa(self):
+ # verify that the private & public keys compare equal
+ key = ECDSAKey.from_private_key_file('tests/test_ecdsa.key')
+ self.assertEquals(key, key)
+ pub = ECDSAKey(data=str(key))
+ self.assert_(key.can_sign())
+ self.assert_(not pub.can_sign())
+ self.assertEquals(key, pub)
+
+ def test_13_sign_ecdsa(self):
+ # verify that the rsa private key can sign and verify
+ key = ECDSAKey.from_private_key_file('tests/test_ecdsa.key')
+ msg = key.sign_ssh_data(rng, 'ice weasels')
+ self.assert_(type(msg) is Message)
+ msg.rewind()
+ self.assertEquals('ecdsa-sha2-nistp256', msg.get_string())
+ # ECDSA signatures, like DSS signatures, tend to be different
+ # each time, so we can't compare against a "known correct"
+ # signature.
+ # Even the length of the signature can change.
+
+ msg.rewind()
+ pub = ECDSAKey(data=str(key))
+ self.assert_(pub.verify_ssh_sig('ice weasels', msg))
diff --git a/tests/test_sftp.py b/tests/test_sftp.py
index b1697ea..cc512c1 100755
--- a/tests/test_sftp.py
+++ b/tests/test_sftp.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/tests/test_sftp_big.py b/tests/test_sftp_big.py
index a32a700..04b15b0 100644
--- a/tests/test_sftp_big.py
+++ b/tests/test_sftp_big.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/tests/test_transport.py b/tests/test_transport.py
index 1c57d18..e8f7f36 100644
--- a/tests/test_transport.py
+++ b/tests/test_transport.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
diff --git a/tests/test_util.py b/tests/test_util.py
index efda9b2..12677a9 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -7,7 +7,7 @@
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
-# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# 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.
@@ -329,3 +329,14 @@ IdentityFile id_dsa22
paramiko.util.lookup_ssh_host_config(host, config),
values
)
+
+ def test_12_config_addressfamily_and_lazy_fqdn(self):
+ """
+ Ensure the code path honoring non-'all' AddressFamily doesn't asplode
+ """
+ test_config = """
+AddressFamily inet
+IdentityFile something_%l_using_fqdn
+"""
+ config = paramiko.util.parse_ssh_config(cStringIO.StringIO(test_config))
+ assert config.lookup('meh') # will die during lookup() if bug regresses
diff --git a/tox-requirements.txt b/tox-requirements.txt
new file mode 100644
index 0000000..26224ce
--- /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 6cb8001..af4fbf2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,5 +2,5 @@
envlist = py25,py26,py27
[testenv]
-commands = pip install --use-mirrors -q -r requirements.txt
+commands = pip install --use-mirrors -q -r tox-requirements.txt
python test.py