[X2Go-Commits] python-paramiko.git - master (branch) updated: d77a4d6421a726584da228c30587bfa265fee8ea
X2Go dev team
git-admin at x2go.org
Wed May 8 13:40:30 CEST 2013
The branch, master has been updated
via d77a4d6421a726584da228c30587bfa265fee8ea (commit)
via 3c2f01c91f798bc2ecefbd7c3864dc5e8c5af5c8 (commit)
via 777d1576ca825f43d00f699865ead9bc0c37c637 (commit)
via d0c7a5d8847903dd30cc6deeb5446e071d9f2bec (commit)
via b33d9c7bca23704fe473f36a19bcfb74b2056dd8 (commit)
via b2b8d5d0a6e808b385e6f3ec41f3cfef7ee1a6ec (commit)
via 0b9393b063d038f2d4ea985ff4763cccead31b02 (commit)
via f00f87c9bed2149c35b410ca02aa17e229c3de95 (commit)
via 8c7f120c2cfb119ece27b4eea375de10ef5df554 (commit)
via 675d79d743be0b9753ef36ca25b3af5e38de0ae2 (commit)
via aee2355d24277405867a8cb4ce8ce9616fee9d5b (commit)
via a1fa1ba9cc2cf6cc65ffebb629da30333de84058 (commit)
via 3966ac103c988218be02371803817b66b4deab17 (commit)
via e6c23f23f4e6b8e6eb577d9b486dc21114b5a398 (commit)
via f861c2ff48f420b3be16f15bbf36c6ee3f779c38 (commit)
via 6747d9944af483796890809b37a9cb260e08ee6d (commit)
via 1b928df15ec4b95b0f96f26dc3740f1a0a91ca4a (commit)
via 81f87f1d5eb4fa89472114f45faa3a994fc7db7d (commit)
via 080bece2586d348b9791892cd6e5670a7afdb1a9 (commit)
via 4f481a57a291316e75b0ce0b5f159e66b49ffe3a (commit)
via b96e7e41322e0c71d379cc04a524287a1c5fae4d (commit)
via b329512636d5f7610c02cc452943a65e2c39470a (commit)
via 02d071be07fef362764b13a99dd9335a668c9117 (commit)
via 1d494eb0db49316365d9328059a8bf869c7e95c0 (commit)
via 2e2a915807f647528f971743e489b0052f46d288 (commit)
via 73a0d03bdc86a89ca1b452210887f9e86439a8cc (commit)
via 17ba0d5b61fe0c87b6b7b657d1b74176931f890f (commit)
via 2a774d1e8a3a607bc88eaf712224d3cdd3352f4f (commit)
via 9695747875ab26d4acb7feea27e1ece689ad2572 (commit)
via 5c9aa3dcdce10167a76b4abfff73de6097cd56a5 (commit)
via 2e069824ed8a53c47dde442805487051b6fc3ef3 (commit)
via 0392e3df8f5efab2551ca8f5f758f3acdb5d5ecc (commit)
via a7ee2509e48d7e2bb533ce5e50796b9a887c7a8a (commit)
via d5db60329701df2423909773af9cc9aa5df4d4f6 (commit)
via 0cc6bb970f54467841084f5a6199985d0ec045b1 (commit)
via c3056914926e0940ed5716fcdaf2f4a0a8b71fb7 (commit)
via 9858ccf207f4d2df5b006fcf114d3ee303789afb (commit)
via abe009b14980d8547b111f07ca9ca782c0bb47a6 (commit)
via 3cd7f585d01afe7eb97374e26a9f72197fc77a42 (commit)
via a3fe422198b1faf75afaf8e2ccfc752dcba64c82 (commit)
via 3a9119d78a8745e4e45bea0881bfae3deb2adab3 (commit)
via 721f74d8c240071e444ed1cdfff432c75339e8b2 (commit)
via bd1a97a0453afb07466ff3cacc2df9b3ee065224 (commit)
via dd7edd8ec85e339b2ad6dd7d64abf25c8396b0ea (commit)
via edc9eaf4f2993858b10594dbda3d321c45444417 (commit)
via 277526315e18c7190a92471eab61d3610331c7df (commit)
via 1903ee1432676f1d3a51daba7ae2edffdd4a28cd (commit)
via 732417bf988228811943873bc7cab29e425847bc (commit)
via 06f970482060c707d210561159c3d9e5ba5a5f1f (commit)
via 93dce43e8643c9dc79fadd546426d9434ae6e534 (commit)
via 38767982cd1a5181536a5f24f830e71933f7fb10 (commit)
via f41fc8fd28c9fd7a41fd6c291d507a7a1b3cde56 (commit)
via 109d2b200a1e419f139166380fd0d136f4d57321 (commit)
via ea3c3f53b662eafa5dfa8e25d9543a568b81b353 (commit)
via c79e6a3f92938af07915c23ea1d7cbb75dac6d89 (commit)
via 32424ba109f2c9a3843b69c6768e04616fceb5fd (commit)
via ac1310c4a171587ae8fd15268a1981eaf139be57 (commit)
via b3d5156019566c972a3dbbea851cffed3cf08514 (commit)
via 42d77483e846db5e43296d7055d059e43168dab7 (commit)
via 85551dffd6d6008cf0409ee776d6792a8e51468f (commit)
via 57d776b31851bf6f50e46b3c41d064fff90d4385 (commit)
via 21689d964798fbbd037e7b995088cee883796225 (commit)
via 98ae4e975dd8fd25af4b615bbbd034cc0831e1fa (commit)
via 3563fca9942a752697cb17af71b179c4f6cc4361 (commit)
via b9242c654abd3ec5137f482436f57a306b151573 (commit)
via 9d2fb82284a42de81bcb35d2536e682687d1bd81 (commit)
via bf4b535920eee426ceae1ade30a62e7afd698619 (commit)
via 2f1daad1b9dfb2a8ff38795d5b3a1d95765a0ea4 (commit)
via e034a24f87ca24171fceb9f9069149f3fc76d51b (commit)
via 7e5911a1ff67c1d7be58e3cb8faa456156a455ed (commit)
via adad068b132d0f0c64174b75fb72ee238c289992 (commit)
via 8e697988af494f4cd499911c9a15ee25d6e2a492 (commit)
via a69abd46067743c965c4eb687fc2b4a52f9b57b5 (commit)
via f493a00c1173fd7b15018639fe77fcfdd7787200 (commit)
via ac9370d3e0e4a645f893028c9dee0ece20c4f0d3 (commit)
via 37d0247301cafe1cf683cc7b3edb95dc6b30caac (commit)
via e761502e8ec5958c27228006314e423057ff8c9d (commit)
via 6b5d7483583627ae5cceee2a59c9c23b78ed0a0b (commit)
via 0d38f3f1f266092a2a5e91cae9527acd8bbb72e7 (commit)
via 0c56e2a40bae88163c9422b1bca5fa6fc4c97640 (commit)
via 6284666cfda3174aac619221a518b57a2bcd5ba0 (commit)
via bf87cd124d1e973e1ba80de6cba0585d217801d8 (commit)
via 235050a67ccf0500b91de10e2f208c0456cbe43c (commit)
via 08109136b4217b3fc620436819f4c92434189955 (commit)
via 0b6aebb8a95d44102968a3e0caf94cd234369e5d (commit)
via 602250fdf9515a8127cd567d79afa8134d4cf923 (commit)
via 21cb9a2d86b2c0419444ea7c2e5e1c35a62b6373 (commit)
via 876c9bdbdaf111793c06ab154c808910766ea8d9 (commit)
via d5edad63a37c36aae83be0d5ddd61a2ec816f1b1 (commit)
via 5f5137414c91c931f04522b1bb8c2800294d6a90 (commit)
via 6c4c00a3f3941a37e3bc25e9ed59e889b93cd06d (commit)
via ce86a53a37c1573a5633699cf7f795768907f9a2 (commit)
via 7bde7840dd4ffeae3c794eca216244975c856f94 (commit)
via 9f21d360409b1ef45cab70a3b5cb79674be34fa2 (commit)
via 3bbcf808d8da43a379cee5ce3d004d3c6eb6e1b7 (commit)
via cd51bfc031eb9163fd6c85bb3d4ec23476bb2090 (commit)
via 0ae0e9800c7bfb3f8ea40fa0d33ebf6dff49f759 (commit)
via 2cbe38308097f920cec0aa87fed85f2cfe6d9612 (commit)
via 9c0d467667568fa67d45ef288cc1f6ee8a7ebfe6 (commit)
via 70fce374b403788d665de896ecffc65f7b2ad875 (commit)
via 5073b7236da0f8052049701136cd2f94acad07d2 (commit)
via 7255dcf042d3099d8b8f7d618da0fbc19e401008 (commit)
via 531606b0d6ebb46f8f36de554a571d96790ce3f8 (commit)
via 2223aa10ccc473c440de7233e9daa69019a8bb01 (commit)
via 2ae06c70afb11c5229188d5c8cbbef4c5d4d1b0f (commit)
via 287f9c3423eaa5a48357bcccd5892437f764706e (commit)
via bda161330ffe6556db63afd3742a1727b969acb6 (commit)
via 03c350903e9ece9d9729ec2af9f9107188c3d8a7 (commit)
via 682a3eff8447ef83c10b0917c06f595803db97bb (commit)
via 7a4d3c4e424e6669d091be495eb8a000b4815ebe (commit)
via 203c7379ac1211cc393d127ab83be774faa0f913 (commit)
via 8496eff0b7afc07675e1f42815f83633d87097ee (commit)
via 537f95dbb36c578e65cfd9cbc1a1eabc03f38428 (commit)
via bc3674d0f0c61b5c7af9cfbc9248cf574d0998b0 (commit)
via 962d4a3cece3ebd2e941dccf1cd555f33f3bbd83 (commit)
via f6ed6a8bbff9aabc58176b04e61c84b0509ecb7b (commit)
via b9c39fc1d2d634967b125f776347b422e7a96ab4 (commit)
via 2575b3efc48cb9f42c3105015284b9afd9c67625 (commit)
via d47e6b9e7f3df5497d0ba664656e1a8b396ddd20 (commit)
via 5ed0e11a7f6a85c8190f3167e64f279741dd8359 (commit)
via 2832f3c60fdf078066a266cd32027a0b8ed65ce9 (commit)
via 2403504b44de773c3f566e7d647bc0e8661af918 (commit)
via 10c51e27263afb62c395ca2905195482d7c3b630 (commit)
via c4d4818cddf851665c8e9777355209d1aab8d4bb (commit)
via 13892788c315bc2a3aeb7f2c22a5307ed7a3f47e (commit)
via c0ef3fd4936984be100f9a4738c89b02344ff141 (commit)
via 0698254b188736e47dc804e16595e96e8e37bd21 (commit)
via 64d6734086fbf5db898495c69959921715617617 (commit)
via a07a339006259eb7ad60993415493d2d25a760d3 (commit)
via 5670e111c95612fc1c96135ca0e942ed89ec5420 (commit)
via 78654e82ecfceba30c61974b18fd4809b1ea9899 (commit)
via 71f8c5c9f53e3eda550ddb68ba573eed289d74ec (commit)
via d7aa342c20b67f1846c7458b09fdd3b0f308dd71 (commit)
via b42c73356c2c5afbfbd4bf0b1278368d5910ea5d (commit)
via 79dffacf4e4ec63a4db8869c03aae33bbbbfa0b5 (commit)
via 45aa88b530cff0ad09f5da83efd4697ba7986563 (commit)
via e0d71b5efb47434ad5957632e8fba0e6fec6389f (commit)
via 668870aa83e095c81371ffce8d69404ac29e536f (commit)
via 23f3099b6f7808934400b96b707e122ed2dd302c (commit)
via c78a5856e88a67344cb47e170ec8e237df1fc2c6 (commit)
via 221131fa21e624a30d0d7cd5ed29e2749ddfd246 (commit)
via 04cc4d55102c111d3e054ef8d83a0a62c34de561 (commit)
via 7ce9875ed7d3e3738ed775ffda4ded1ce12d35f2 (commit)
via d66d75f277cfd49b8adc84498b0b09d71092bd0b (commit)
via b22c11ab1b889032345a80b89af6ebee50ef7904 (commit)
via 2dd74f953d0a789a9c65662492f8340d66246e5b (commit)
via ad587fa0ef32eeeb633245c7ae68759765c0e439 (commit)
via f33481cc44fb57a788b5726b529383f300d06b36 (commit)
via 3174b6c894b125426a7a4bae7f934a0dbe64d5b1 (commit)
from 06d987c362e75a5d86c411c20932a3818fb4fa40 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit d77a4d6421a726584da228c30587bfa265fee8ea
Merge: 777d157 3c2f01c
Author: Jeff Forcier <jeff at bitprophet.org>
Date: Sun May 5 13:59:41 2013 -0700
Merge branch '1.10'
commit 3c2f01c91f798bc2ecefbd7c3864dc5e8c5af5c8
Author: Jeff Forcier <jeff at bitprophet.org>
Date: Sun May 5 13:59:34 2013 -0700
Flip bad known_hosts line to INFO from WARN re #153
-----------------------------------------------------------------------
Summary of changes:
.gitignore | 2 +
.travis.yml | 4 +-
Makefile | 2 +-
NEWS | 76 ++++++++++++-
README | 27 +----
demos/demo.py | 1 -
paramiko/__init__.py | 8 +-
paramiko/_winapi.py | 269 +++++++++++++++++++++++++++++++++++++++++++++++
paramiko/agent.py | 17 ++-
paramiko/channel.py | 24 +++--
paramiko/client.py | 26 +++--
paramiko/config.py | 180 +++++++++++++++++++++----------
paramiko/file.py | 4 +
paramiko/hostkeys.py | 18 +++-
paramiko/message.py | 3 +-
paramiko/packet.py | 12 ++-
paramiko/sftp_client.py | 130 ++++++++++++++++-------
paramiko/sftp_file.py | 25 +++--
paramiko/transport.py | 5 +-
paramiko/win_pageant.py | 81 +++++---------
requirements.txt | 2 +
setup.py | 2 +-
tests/test_sftp.py | 45 +++++---
tests/test_util.py | 117 ++++++++++++++++++---
tox.ini | 6 ++
25 files changed, 838 insertions(+), 248 deletions(-)
create mode 100644 paramiko/_winapi.py
create mode 100644 requirements.txt
create mode 100644 tox.ini
The diff of changes is:
diff --git a/.gitignore b/.gitignore
index 3283ff3..4b57895 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
*.pyc
build/
dist/
+.tox/
paramiko.egg-info/
test.log
+docs/
diff --git a/.travis.yml b/.travis.yml
index 90dbb80..6896b89 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,9 +8,7 @@ install:
- pip install -e .
script: python test.py
notifications:
- email:
- on_failure: change
irc:
- channels: "irc.freenode.org#fabric"
+ channels: "irc.freenode.org#paramiko"
on_success: change
on_failure: change
diff --git a/Makefile b/Makefile
index 3c12990..572f867 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
release: docs
python setup.py sdist register upload
-docs:
+docs: paramiko/*
epydoc --no-private -o docs/ paramiko
clean:
diff --git a/NEWS b/NEWS
index 5542046..ef85eff 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,80 @@ Issues noted as "Fabric #NN" can be found at https://github.com/fabric/fabric/.
Releases
========
+v1.11.0 (DD MM YYYY)
+--------------------
+
+* #98: On Windows, when interacting with the PuTTY PAgeant, Paramiko now
+ creates the shared memory map with explicit Security Attributes of the user,
+ which is the same technique employed by the canonical PuTTY library to avoid
+ permissions issues when Paramiko is running under a different UAC context
+ than the PuTTY Ageant process. Thanks to Jason R. Coombs for the patch.
+* #100: Remove use of PyWin32 in `win_pageant` module. Module was already
+ dependent on ctypes for constructing appropriate structures and had ctypes
+ implementations of all functionality. Thanks to Jason R. Coombs for the
+ patch.
+* #87: Ensure updates to `known_hosts` files account for any updates to said
+ files after Paramiko initially read them. (Includes related fix to guard
+ against duplicate entries during subsequent `known_hosts` loads.) Thanks to
+ `@sunweaver` for the contribution.
+
+v1.10.2 (DD MM 2013)
+--------------------
+
+* #153, #67: Warn on parse failure when reading known_hosts
+ file. Thanks to `@glasserc` for patch.
+* #146: Indentation fixes for readability. Thanks to Abhinav Upadhyay for catch
+ & patch.
+
+v1.10.1 (5th Apr 2013)
+----------------------
+
+* #142: (Fabric #811) SFTP put of empty file will still return the attributes
+ of the put file. Thanks to Jason R. Coombs for the patch.
+* #154: (Fabric #876) Forwarded SSH agent connections left stale local pipes
+ lying around, which could cause local (and sometimes remote or network)
+ resource starvation when running many agent-using remote commands. Thanks to
+ Kevin Tegtmeier for catch & patch.
+
+v1.10.0 (1st Mar 2013)
+--------------------
+
+* #66: Batch SFTP writes to help speed up file transfers. Thanks to Olle
+ Lundberg for the patch.
+* #133: Fix handling of window-change events to be on-spec and not
+ attempt to wait for a response from the remote sshd; this fixes problems with
+ less common targets such as some Cisco devices. Thanks to Phillip Heller for
+ catch & patch.
+* #93: Overhaul SSH config parsing to be in line with `man ssh_config` (& the
+ behavior of `ssh` itself), including addition of parameter expansion within
+ config values. Thanks to Olle Lundberg for the patch.
+* #110: Honor SSH config `AddressFamily` setting when looking up local
+ host's FQDN. Thanks to John Hensley for the patch.
+* #128: Defer FQDN resolution until needed, when parsing SSH config files.
+ Thanks to Parantapa Bhattacharya for catch & patch.
+* #102: Forego random padding for packets when running under `*-ctr` ciphers.
+ This corrects some slowdowns on platforms where random byte generation is
+ inefficient (e.g. Windows). Thanks to `@warthog618` for catch & patch, and
+ Michael van der Kolff for code/technique review.
+* #127: Turn `SFTPFile` into a context manager. Thanks to Michael Williamson
+ for the patch.
+* #116: Limit `Message.get_bytes` to an upper bound of 1MB to protect against
+ potential DoS vectors. Thanks to `@mvschaik` for catch & patch.
+* #115: Add convenience `get_pty` kwarg to `Client.exec_command` so users not
+ manually controlling a channel object can still toggle PTY creation. Thanks
+ to Michael van der Kolff for the patch.
+* #71: Add `SFTPClient.putfo` and `.getfo` methods to allow direct
+ uploading/downloading of file-like objects. Thanks to Eric Buehl for the
+ patch.
+* #113: Add `timeout` parameter to `SSHClient.exec_command` for easier setting
+ of the command's internal channel object's timeout. Thanks to Cernov Vladimir
+ for the patch.
+* #94: Remove duplication of SSH port constant. Thanks to Olle Lundberg for the
+ catch.
+* #80: Expose the internal "is closed" property of the file transfer class
+ `BufferedFile` as `.closed`, better conforming to Python's file interface.
+ Thanks to `@smunaut` and James Hiscock for catch & patch.
+
v1.9.0 (6th Nov 2012)
---------------------
@@ -286,7 +360,7 @@ v1.5 (paras) 02oct05
separation
* demo scripts fixed to have a better chance of loading the host keys
correctly on windows/cygwin
-
+
v1.4 (oddish) 17jul05
---------------------
* added SSH-agent support (for posix) from john rochester
diff --git a/README b/README
index 9e9cc3a..1899819 100644
--- a/README
+++ b/README
@@ -5,22 +5,17 @@ paramiko
:Paramiko: Python SSH module
:Copyright: Copyright (c) 2003-2009 Robey Pointer <robeypointer at gmail.com>
-:Copyright: Copyright (c) 2012 Jeff Forcier <jeff at bitprophet.org>
+:Copyright: Copyright (c) 2013 Jeff Forcier <jeff at bitprophet.org>
:License: LGPL
:Homepage: https://github.com/paramiko/paramiko/
-
-
-paramiko 1.8.0
-==============
-
-Release of MM.YY.DD
+:API docs: http://docs.paramiko.org
What
----
"paramiko" is a combination of the esperanto words for "paranoid" and
-"friend". it's a module for python 2.2+ that implements the SSH2 protocol
+"friend". it's a module for python 2.5+ that implements the SSH2 protocol
for secure (encrypted and authenticated) connections to remote machines.
unlike SSL (aka TLS), SSH2 protocol does not require hierarchical
certificates signed by a powerful central authority. you may know SSH2 as
@@ -39,8 +34,7 @@ that should have come with this archive.
Requirements
------------
- - python 2.3 or better <http://www.python.org/>
- (python 2.2 is also supported, but not recommended)
+ - python 2.5 or better <http://www.python.org/>
- pycrypto 2.1 or better <https://www.dlitz.net/software/pycrypto/>
If you have setuptools, you can build and install paramiko and all its
@@ -58,19 +52,6 @@ should also work on Windows, though i don't test it as frequently there.
if you run into Windows problems, send me a patch: portability is important
to me.
-python 2.2 may work, thanks to some patches from Roger Binns. things to
-watch out for:
-
- * sockets in 2.2 don't support timeouts, so the 'select' module is
- imported to do polling.
- * logging is mostly stubbed out. it works just enough to let paramiko
- create log files for debugging, if you want them. to get real logging,
- you can backport python 2.3's logging package. Roger has done that
- already:
- http://sourceforge.net/project/showfiles.php?group_id=75211&package_id=113804
-
-you really should upgrade to python 2.3. laziness is no excuse! :)
-
some python distributions don't include the utf-8 string encodings, for
reasons of space (misdirected as that is). if your distribution is
missing encodings, you'll see an error like this::
diff --git a/demos/demo.py b/demos/demo.py
index 05524d3..c21a926 100755
--- a/demos/demo.py
+++ b/demos/demo.py
@@ -26,7 +26,6 @@ import os
import select
import socket
import sys
-import threading
import time
import traceback
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index 29e470a..099314e 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -18,7 +18,7 @@
"""
I{Paramiko} (a combination of the esperanto words for "paranoid" and "friend")
-is a module for python 2.3 or greater that implements the SSH2 protocol for
+is a module for python 2.5 or greater that implements the SSH2 protocol for
secure (encrypted and authenticated) connections to remote machines. Unlike
SSL (aka TLS), the SSH2 protocol does not require hierarchical certificates
signed by a powerful central authority. You may know SSH2 as the protocol that
@@ -50,12 +50,12 @@ Website: U{https://github.com/paramiko/paramiko/}
import sys
-if sys.version_info < (2, 2):
- raise RuntimeError('You need python 2.2 for this module.')
+if sys.version_info < (2, 5):
+ raise RuntimeError('You need python 2.5+ for this module.')
__author__ = "Jeff Forcier <jeff at bitprophet.org>"
-__version__ = "1.9.0"
+__version__ = "1.10.1"
__license__ = "GNU Lesser General Public License (LGPL)"
diff --git a/paramiko/_winapi.py b/paramiko/_winapi.py
new file mode 100644
index 0000000..f141b00
--- /dev/null
+++ b/paramiko/_winapi.py
@@ -0,0 +1,269 @@
+"""
+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__
+
+######################
+# 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()
+
+
+#####################
+# jaraco.windows.mmap
+
+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):
+ ctypes.windll.msvcrt.memcpy(self.view + self.pos, msg, len(msg))
+ self.pos += len(msg)
+
+ def read(self, n):
+ """
+ Read n bytes from mapped view.
+ """
+ out = ctypes.create_string_buffer(n)
+ ctypes.windll.msvcrt.memcpy(out, self.view + self.pos, n)
+ 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 = ctypes.wintypes.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 5d04dce..d4ff703 100644
--- a/paramiko/agent.py
+++ b/paramiko/agent.py
@@ -130,15 +130,22 @@ class AgentProxyThread(threading.Thread):
if len(data) != 0:
self.__inr.send(data)
else:
+ self._close()
break
elif self.__inr == fd:
data = self.__inr.recv(512)
if len(data) != 0:
self._agent._conn.send(data)
else:
+ self._close()
break
time.sleep(io_sleep)
+ def _close(self):
+ self._exit = True
+ self.__inr.close()
+ self._agent._conn.close()
+
class AgentLocalProxy(AgentProxyThread):
"""
Class to be used when wanting to ask a local SSH Agent being
@@ -248,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/channel.py b/paramiko/channel.py
index 534f8d7..0c603c6 100644
--- a/paramiko/channel.py
+++ b/paramiko/channel.py
@@ -122,7 +122,8 @@ class Channel (object):
out += '>'
return out
- def get_pty(self, term='vt100', width=80, height=24):
+ def get_pty(self, term='vt100', width=80, height=24, width_pixels=0,
+ height_pixels=0):
"""
Request a pseudo-terminal from the server. This is usually used right
after creating a client channel, to ask the server to provide some
@@ -136,6 +137,10 @@ class Channel (object):
@type width: int
@param height: height (in characters) of the terminal screen
@type height: int
+ @param width_pixels: width (in pixels) of the terminal screen
+ @type width_pixels: int
+ @param height_pixels: height (in pixels) of the terminal screen
+ @type height_pixels: int
@raise SSHException: if the request was rejected or the channel was
closed
@@ -150,8 +155,8 @@ class Channel (object):
m.add_string(term)
m.add_int(width)
m.add_int(height)
- # pixel height, width (usually useless)
- m.add_int(0).add_int(0)
+ m.add_int(width_pixels)
+ m.add_int(height_pixels)
m.add_string('')
self._event_pending()
self.transport._send_user_message(m)
@@ -239,7 +244,7 @@ class Channel (object):
self.transport._send_user_message(m)
self._wait_for_event()
- def resize_pty(self, width=80, height=24):
+ def resize_pty(self, width=80, height=24, width_pixels=0, height_pixels=0):
"""
Resize the pseudo-terminal. This can be used to change the width and
height of the terminal emulation created in a previous L{get_pty} call.
@@ -248,6 +253,10 @@ class Channel (object):
@type width: int
@param height: new height (in characters) of the terminal screen
@type height: int
+ @param width_pixels: new width (in pixels) of the terminal screen
+ @type width_pixels: int
+ @param height_pixels: new height (in pixels) of the terminal screen
+ @type height_pixels: int
@raise SSHException: if the request was rejected or the channel was
closed
@@ -258,13 +267,12 @@ class Channel (object):
m.add_byte(chr(MSG_CHANNEL_REQUEST))
m.add_int(self.remote_chanid)
m.add_string('window-change')
- m.add_boolean(True)
+ m.add_boolean(False)
m.add_int(width)
m.add_int(height)
- m.add_int(0).add_int(0)
- self._event_pending()
+ m.add_int(width_pixels)
+ m.add_int(height_pixels)
self.transport._send_user_message(m)
- self._wait_for_event()
def exit_status_ready(self):
"""
diff --git a/paramiko/client.py b/paramiko/client.py
index 07560a3..493d548 100644
--- a/paramiko/client.py
+++ b/paramiko/client.py
@@ -28,6 +28,7 @@ import warnings
from paramiko.agent import Agent
from paramiko.common import *
+from paramiko.config import SSH_PORT
from paramiko.dsskey import DSSKey
from paramiko.hostkeys import HostKeys
from paramiko.resource import ResourceManager
@@ -37,8 +38,6 @@ from paramiko.transport import Transport
from paramiko.util import retry_on_signal
-SSH_PORT = 22
-
class MissingHostKeyPolicy (object):
"""
Interface for defining the policy that L{SSHClient} should use when the
@@ -187,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.known_hosts is not None:
+ self.load_host_keys(self.known_hosts)
+
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()))
@@ -350,7 +354,7 @@ class SSHClient (object):
self._agent.close()
self._agent = None
- def exec_command(self, command, bufsize=-1):
+ def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False):
"""
Execute a command on the SSH server. A new L{Channel} is opened and
the requested command is executed. The command's input and output
@@ -361,19 +365,25 @@ class SSHClient (object):
@type command: str
@param bufsize: interpreted the same way as by the built-in C{file()} function in python
@type bufsize: int
+ @param timeout: set command's channel timeout. See L{Channel.settimeout}.settimeout
+ @type timeout: int
@return: the stdin, stdout, and stderr of the executing command
@rtype: tuple(L{ChannelFile}, L{ChannelFile}, L{ChannelFile})
@raise SSHException: if the server fails to execute the command
"""
chan = self._transport.open_session()
+ if(get_pty):
+ chan.get_pty()
+ chan.settimeout(timeout)
chan.exec_command(command)
stdin = chan.makefile('wb', bufsize)
stdout = chan.makefile('rb', bufsize)
stderr = chan.makefile_stderr('rb', bufsize)
return stdin, stdout, stderr
- def invoke_shell(self, term='vt100', width=80, height=24):
+ def invoke_shell(self, term='vt100', width=80, height=24, width_pixels=0,
+ height_pixels=0):
"""
Start an interactive shell session on the SSH server. A new L{Channel}
is opened and connected to a pseudo-terminal using the requested
@@ -385,13 +395,17 @@ class SSHClient (object):
@type width: int
@param height: the height (in characters) of the terminal window
@type height: int
+ @param width_pixels: the width (in pixels) of the terminal window
+ @type width_pixels: int
+ @param height_pixels: the height (in pixels) of the terminal window
+ @type height_pixels: int
@return: a new channel connected to the remote shell
@rtype: L{Channel}
@raise SSHException: if the server fails to invoke a shell
"""
chan = self._transport.open_session()
- chan.get_pty(term, width, height)
+ chan.get_pty(term, width, height, width_pixels, height_pixels)
chan.invoke_shell()
return chan
diff --git a/paramiko/config.py b/paramiko/config.py
index 2828d90..31caf29 100644
--- a/paramiko/config.py
+++ b/paramiko/config.py
@@ -1,4 +1,5 @@
# Copyright (C) 2006-2007 Robey Pointer <robeypointer at gmail.com>
+# Copyright (C) 2012 Olle Lundberg <geek at nerd.sh>
#
# This file is part of paramiko.
#
@@ -25,10 +26,55 @@ import os
import re
import socket
-SSH_PORT=22
+SSH_PORT = 22
proxy_re = re.compile(r"^(proxycommand)\s*=*\s*(.*)", re.I)
+class LazyFqdn(object):
+ """
+ Returns the host's fqdn on request as string.
+ """
+
+ def __init__(self, config):
+ self.fqdn = None
+ self.config = config
+
+ def __str__(self):
+ if self.fqdn is None:
+ #
+ # If the SSH config contains AddressFamily, use that when
+ # determining the local host's FQDN. Using socket.getfqdn() from
+ # the standard library is the most general solution, but can
+ # result in noticeable delays on some platforms when IPv6 is
+ # misconfigured or not available, as it calls getaddrinfo with no
+ # address family specified, so both IPv4 and IPv6 are checked.
+ #
+
+ # Handle specific option
+ fqdn = None
+ address_family = self.config.get('addressfamily', 'any').lower()
+ if address_family != 'any':
+ family = socket.AF_INET if address_family == 'inet' \
+ else socket.AF_INET6
+ results = socket.getaddrinfo(host,
+ None,
+ family,
+ socket.SOCK_DGRAM,
+ socket.IPPROTO_IP,
+ socket.AI_CANONNAME)
+ for res in results:
+ af, socktype, proto, canonname, sa = res
+ if canonname and '.' in canonname:
+ fqdn = canonname
+ break
+ # Handle 'any' / unspecified
+ if fqdn is None:
+ fqdn = socket.getfqdn()
+ # Cache
+ self.fqdn = fqdn
+ return self.fqdn
+
+
class SSHConfig (object):
"""
Representation of config information as stored in the format used by
@@ -44,7 +90,7 @@ class SSHConfig (object):
"""
Create a new OpenSSH config object.
"""
- self._config = [ { 'host': '*' } ]
+ self._config = []
def parse(self, file_obj):
"""
@@ -53,7 +99,7 @@ class SSHConfig (object):
@param file_obj: a file-like object to read the config file from
@type file_obj: file
"""
- configs = [self._config[0]]
+ host = {"host": ['*'], "config": {}}
for line in file_obj:
line = line.rstrip('\n').lstrip()
if (line == '') or (line[0] == '#'):
@@ -77,20 +123,20 @@ class SSHConfig (object):
value = line[i:].lstrip()
if key == 'host':
- del configs[:]
- # the value may be multiple hosts, space-delimited
- for host in value.split():
- # do we have a pre-existing host config to append to?
- matches = [c for c in self._config if c['host'] == host]
- if len(matches) > 0:
- configs.append(matches[0])
- else:
- config = { 'host': host }
- self._config.append(config)
- configs.append(config)
- else:
- for config in configs:
- config[key] = value
+ self._config.append(host)
+ value = value.split()
+ host = {key: value, 'config': {}}
+ #identityfile is a special case, since it is allowed to be
+ # specified multiple times and they should be tried in order
+ # of specification.
+ elif key == 'identityfile':
+ if key in host['config']:
+ host['config']['identityfile'].append(value)
+ else:
+ host['config']['identityfile'] = [value]
+ elif key not in host['config']:
+ host['config'].update({key: value})
+ self._config.append(host)
def lookup(self, hostname):
"""
@@ -105,31 +151,45 @@ class SSHConfig (object):
will win out.
The keys in the returned dict are all normalized to lowercase (look for
- C{"port"}, not C{"Port"}. No other processing is done to the keys or
- values.
+ C{"port"}, not C{"Port"}. The values are processed according to the
+ rules for substitution variable expansion in C{ssh_config}.
@param hostname: the hostname to lookup
@type hostname: str
"""
- matches = [x for x in self._config if fnmatch.fnmatch(hostname, x['host'])]
- # Move * to the end
- _star = matches.pop(0)
- matches.append(_star)
+
+ matches = [config for config in self._config if
+ self._allowed(hostname, config['host'])]
+
ret = {}
- for m in matches:
- for k,v in m.iteritems():
- if not k in ret:
- ret[k] = v
+ for match in matches:
+ for key, value in match['config'].iteritems():
+ if key not in ret:
+ # Create a copy of the original value,
+ # else it will reference the original list
+ # in self._config and update that value too
+ # when the extend() is being called.
+ ret[key] = value[:]
+ elif key == 'identityfile':
+ ret[key].extend(value)
ret = self._expand_variables(ret, hostname)
- del ret['host']
return ret
- def _expand_variables(self, config, hostname ):
+ def _allowed(self, hostname, hosts):
+ match = False
+ for host in hosts:
+ if host.startswith('!') and fnmatch.fnmatch(hostname, host[1:]):
+ return False
+ elif fnmatch.fnmatch(hostname, host):
+ match = True
+ return match
+
+ def _expand_variables(self, config, hostname):
"""
Return a dict of config options with expanded substitutions
for a given hostname.
- Please refer to man ssh_config(5) for the parameters that
+ Please refer to man C{ssh_config} for the parameters that
are replaced.
@param config: the config for the hostname
@@ -139,7 +199,7 @@ class SSHConfig (object):
"""
if 'hostname' in config:
- config['hostname'] = config['hostname'].replace('%h',hostname)
+ config['hostname'] = config['hostname'].replace('%h', hostname)
else:
config['hostname'] = hostname
@@ -155,34 +215,42 @@ class SSHConfig (object):
remoteuser = user
host = socket.gethostname().split('.')[0]
- fqdn = socket.getfqdn()
+ fqdn = LazyFqdn(config)
homedir = os.path.expanduser('~')
- replacements = {
- 'controlpath': [
- ('%h', config['hostname']),
- ('%l', fqdn),
- ('%L', host),
- ('%n', hostname),
- ('%p', port),
- ('%r', remoteuser),
- ('%u', user)
- ],
- 'identityfile': [
- ('~', homedir),
- ('%d', homedir),
- ('%h', config['hostname']),
- ('%l', fqdn),
- ('%u', user),
- ('%r', remoteuser)
- ],
- 'proxycommand': [
- ('%h', config['hostname']),
- ('%p', port),
- ('%r', remoteuser),
- ],
- }
+ replacements = {'controlpath':
+ [
+ ('%h', config['hostname']),
+ ('%l', fqdn),
+ ('%L', host),
+ ('%n', hostname),
+ ('%p', port),
+ ('%r', remoteuser),
+ ('%u', user)
+ ],
+ 'identityfile':
+ [
+ ('~', homedir),
+ ('%d', homedir),
+ ('%h', config['hostname']),
+ ('%l', fqdn),
+ ('%u', user),
+ ('%r', remoteuser)
+ ],
+ 'proxycommand':
+ [
+ ('%h', config['hostname']),
+ ('%p', port),
+ ('%r', remoteuser)
+ ]
+ }
+
for k in config:
if k in replacements:
for find, replace in replacements[k]:
+ if isinstance(config[k], list):
+ for item in range(len(config[k])):
+ config[k][item] = config[k][item].\
+ replace(find, str(replace))
+ else:
config[k] = config[k].replace(find, str(replace))
return config
diff --git a/paramiko/file.py b/paramiko/file.py
index d4aec8e..7e2904e 100644
--- a/paramiko/file.py
+++ b/paramiko/file.py
@@ -354,6 +354,10 @@ class BufferedFile (object):
"""
return self
+ @property
+ def closed(self):
+ return self._closed
+
### overrides...
diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py
index e739312..f64e6e6 100644
--- a/paramiko/hostkeys.py
+++ b/paramiko/hostkeys.py
@@ -28,6 +28,7 @@ import UserDict
from paramiko.common import *
from paramiko.dsskey import DSSKey
from paramiko.rsakey import RSAKey
+from paramiko.util import get_logger
class InvalidHostKey(Exception):
@@ -48,7 +49,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 +62,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]
@@ -78,6 +82,7 @@ class HostKeyEntry:
elif keytype == 'ssh-dss':
key = DSSKey(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 +165,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/message.py b/paramiko/message.py
index 366c43c..47acc34 100644
--- a/paramiko/message.py
+++ b/paramiko/message.py
@@ -110,7 +110,8 @@ class Message (object):
@rtype: string
"""
b = self.packet.read(n)
- if len(b) < n:
+ max_pad_size = 1<<20 # Limit padding to 1 MB
+ if len(b) < n and n < max_pad_size:
return b + '\x00' * (n - len(b))
return b
diff --git a/paramiko/packet.py b/paramiko/packet.py
index 5d918e2..38a6d4b 100644
--- a/paramiko/packet.py
+++ b/paramiko/packet.py
@@ -87,6 +87,7 @@ class Packetizer (object):
self.__mac_size_in = 0
self.__block_engine_out = None
self.__block_engine_in = None
+ self.__sdctr_out = False
self.__mac_engine_out = None
self.__mac_engine_in = None
self.__mac_key_out = ''
@@ -110,11 +111,12 @@ class Packetizer (object):
"""
self.__logger = log
- def set_outbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key):
+ def set_outbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key, sdctr=False):
"""
Switch outbound data cipher.
"""
self.__block_engine_out = block_engine
+ self.__sdctr_out = sdctr
self.__block_size_out = block_size
self.__mac_engine_out = mac_engine
self.__mac_size_out = mac_size
@@ -490,12 +492,12 @@ class Packetizer (object):
padding = 3 + bsize - ((len(payload) + 8) % bsize)
packet = struct.pack('>IB', len(payload) + padding + 1, padding)
packet += payload
- if self.__block_engine_out is not None:
- packet += rng.read(padding)
- else:
- # cute trick i caught openssh doing: if we're not encrypting,
+ if self.__sdctr_out or self.__block_engine_out is None:
+ # cute trick i caught openssh doing: if we're not encrypting or SDCTR mode (RFC4344),
# don't waste random bytes for the padding
packet += (chr(0) * padding)
+ else:
+ packet += rng.read(padding)
return packet
def _trigger_rekey(self):
diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py
index 3eaefc9..17ea493 100644
--- a/paramiko/sftp_client.py
+++ b/paramiko/sftp_client.py
@@ -198,7 +198,7 @@ class SFTPClient (BaseSFTP):
Open a file on the remote server. The arguments are the same as for
python's built-in C{file} (aka C{open}). A file-like object is
returned, which closely mimics the behavior of a normal python file
- object.
+ object, including the ability to be used as a context manager.
The mode indicates how the file is to be opened: C{'r'} for reading,
C{'w'} for writing (truncating an existing file), C{'a'} for appending,
@@ -533,6 +533,56 @@ class SFTPClient (BaseSFTP):
"""
return self._cwd
+ def putfo(self, fl, remotepath, file_size=0, callback=None, confirm=True):
+ """
+ Copy the contents of an open file object (C{fl}) to the SFTP server as
+ C{remotepath}. Any exception raised by operations will be passed through.
+
+ The SFTP operations use pipelining for speed.
+
+ @param fl: opened file or file-like object to copy
+ @type localpath: object
+ @param remotepath: the destination path on the SFTP server
+ @type remotepath: str
+ @param file_size: optional size parameter passed to callback. If none is
+ specified, size defaults to 0
+ @type file_size: int
+ @param callback: optional callback function that accepts the bytes
+ transferred so far and the total bytes to be transferred
+ (since 1.7.4)
+ @type callback: function(int, int)
+ @param confirm: whether to do a stat() on the file afterwards to
+ confirm the file size (since 1.7.7)
+ @type confirm: bool
+
+ @return: an object containing attributes about the given file
+ (since 1.7.4)
+ @rtype: SFTPAttributes
+
+ @since: 1.4
+ """
+ fr = self.file(remotepath, 'wb')
+ fr.set_pipelined(True)
+ size = 0
+ try:
+ while True:
+ data = fl.read(32768)
+ fr.write(data)
+ size += len(data)
+ if callback is not None:
+ callback(size, file_size)
+ if len(data) == 0:
+ break
+ finally:
+ fr.close()
+ if confirm:
+ s = self.stat(remotepath)
+ if s.st_size != size:
+ raise IOError('size mismatch in put! %d != %d' % (s.st_size, size))
+ else:
+ s = SFTPAttributes()
+ return s
+
def put(self, localpath, remotepath, callback=None, confirm=True):
"""
Copy a local file (C{localpath}) to the SFTP server as C{remotepath}.
@@ -562,29 +612,46 @@ class SFTPClient (BaseSFTP):
file_size = os.stat(localpath).st_size
fl = file(localpath, 'rb')
try:
- fr = self.file(remotepath, 'wb')
- fr.set_pipelined(True)
- size = 0
- try:
- while True:
- data = fl.read(32768)
- if len(data) == 0:
- break
- fr.write(data)
- size += len(data)
- if callback is not None:
- callback(size, file_size)
- finally:
- fr.close()
+ return self.putfo(fl, remotepath, os.stat(localpath).st_size, callback, confirm)
finally:
fl.close()
- if confirm:
- s = self.stat(remotepath)
- if s.st_size != size:
- raise IOError('size mismatch in put! %d != %d' % (s.st_size, size))
- else:
- s = SFTPAttributes()
- return s
+
+ def getfo(self, remotepath, fl, callback=None):
+ """
+ Copy a remote file (C{remotepath}) from the SFTP server and write to
+ an open file or file-like object, C{fl}. Any exception raised by
+ operations will be passed through. This method is primarily provided
+ as a convenience.
+
+ @param remotepath: opened file or file-like object to copy to
+ @type remotepath: object
+ @param fl: the destination path on the local host or open file
+ object
+ @type localpath: str
+ @param callback: optional callback function that accepts the bytes
+ transferred so far and the total bytes to be transferred
+ (since 1.7.4)
+ @type callback: function(int, int)
+ @return: the number of bytes written to the opened file object
+
+ @since: 1.4
+ """
+ fr = self.file(remotepath, 'rb')
+ file_size = self.stat(remotepath).st_size
+ fr.prefetch()
+ try:
+ size = 0
+ while True:
+ data = fr.read(32768)
+ fl.write(data)
+ size += len(data)
+ if callback is not None:
+ callback(size, file_size)
+ if len(data) == 0:
+ break
+ finally:
+ fr.close()
+ return size
def get(self, remotepath, localpath, callback=None):
"""
@@ -603,25 +670,12 @@ class SFTPClient (BaseSFTP):
@since: 1.4
"""
- fr = self.file(remotepath, 'rb')
file_size = self.stat(remotepath).st_size
- fr.prefetch()
+ fl = file(localpath, 'wb')
try:
- fl = file(localpath, 'wb')
- try:
- size = 0
- while True:
- data = fr.read(32768)
- fl.write(data)
- size += len(data)
- if callback is not None:
- callback(size, file_size)
- if len(data) == 0:
- break
- finally:
- fl.close()
+ size = self.getfo(remotepath, fl, callback)
finally:
- fr.close()
+ fl.close()
s = os.stat(localpath)
if s.st_size != size:
raise IOError('size mismatch in get! %d != %d' % (s.st_size, size))
diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py
index 8c5c7ac..e056d70 100644
--- a/paramiko/sftp_file.py
+++ b/paramiko/sftp_file.py
@@ -21,6 +21,7 @@ L{SFTPFile}
"""
from binascii import hexlify
+from collections import deque
import socket
import threading
import time
@@ -34,6 +35,9 @@ from paramiko.sftp_attr import SFTPAttributes
class SFTPFile (BufferedFile):
"""
Proxy object for a file on the remote server, in client mode SFTP.
+
+ Instances of this class may be used as context managers in the same way
+ that built-in Python file objects are.
"""
# Some sftp servers will choke if you send read/write requests larger than
@@ -51,6 +55,7 @@ class SFTPFile (BufferedFile):
self._prefetch_data = {}
self._prefetch_reads = []
self._saved_exception = None
+ self._reqs = deque()
def __del__(self):
self._close(async=True)
@@ -160,12 +165,14 @@ class SFTPFile (BufferedFile):
def _write(self, data):
# may write less than requested if it would exceed max packet size
chunk = min(len(data), self.MAX_REQUEST_SIZE)
- req = self.sftp._async_request(type(None), CMD_WRITE, self.handle, long(self._realpos), str(data[:chunk]))
- if not self.pipelined or self.sftp.sock.recv_ready():
- t, msg = self.sftp._read_response(req)
- if t != CMD_STATUS:
- raise SFTPError('Expected status')
- # convert_status already called
+ self._reqs.append(self.sftp._async_request(type(None), CMD_WRITE, self.handle, long(self._realpos), str(data[:chunk])))
+ if not self.pipelined or (len(self._reqs) > 100 and self.sftp.sock.recv_ready()):
+ while len(self._reqs):
+ req = self._reqs.popleft()
+ t, msg = self.sftp._read_response(req)
+ if t != CMD_STATUS:
+ raise SFTPError('Expected status')
+ # convert_status already called
return chunk
def settimeout(self, timeout):
@@ -474,3 +481,9 @@ class SFTPFile (BufferedFile):
x = self._saved_exception
self._saved_exception = None
raise x
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close()
diff --git a/paramiko/transport.py b/paramiko/transport.py
index c801031..201a253 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -1439,7 +1439,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:
@@ -1885,7 +1885,8 @@ class Transport (threading.Thread):
mac_key = self._compute_key('F', mac_engine.digest_size)
else:
mac_key = self._compute_key('E', mac_engine.digest_size)
- self.packetizer.set_outbound_cipher(engine, block_size, mac_engine, mac_size, mac_key)
+ sdctr = self.local_cipher.endswith('-ctr')
+ self.packetizer.set_outbound_cipher(engine, block_size, mac_engine, mac_size, mac_key, sdctr)
compress_out = self._compression_info[self.local_compression][0]
if (compress_out is not None) and ((self.local_compression != 'zlib at openssh.com') or self.authenticated):
self._log(DEBUG, 'Switching on outbound compression ...')
diff --git a/paramiko/win_pageant.py b/paramiko/win_pageant.py
index d77d58f..b96a740 100644
--- a/paramiko/win_pageant.py
+++ b/paramiko/win_pageant.py
@@ -21,28 +21,15 @@
Functions for communicating with Pageant, the basic windows ssh agent program.
"""
-import os
+from __future__ import with_statement
+
import struct
-import tempfile
-import mmap
+import threading
import array
import platform
import ctypes.wintypes
-# 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
-except ImportError:
- try:
- import ctypes
- _has_ctypes = True
- except ImportError:
- pass
+from . import _winapi
_AGENT_COPYDATA_ID = 0x804e50ba
_AGENT_MAX_MSGLEN = 8192
@@ -52,16 +39,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,9 +49,7 @@ 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):
@@ -88,48 +64,39 @@ class COPYDATASTRUCT(ctypes.Structure):
]
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' % threading.current_thread().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):
"""
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..75112a2
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+pycrypto
+tox
diff --git a/setup.py b/setup.py
index 1bb1a71..02a71e5 100644
--- a/setup.py
+++ b/setup.py
@@ -52,7 +52,7 @@ if sys.platform == 'darwin':
setup(name = "paramiko",
- version = "1.9.0",
+ version = "1.11.0",
description = "SSH2 protocol library",
author = "Jeff Forcier",
author_email = "jeff at bitprophet.org",
diff --git a/tests/test_sftp.py b/tests/test_sftp.py
index 2eadabc..b1697ea 100755
--- a/tests/test_sftp.py
+++ b/tests/test_sftp.py
@@ -23,15 +23,15 @@ a real actual sftp server is contacted, and a new folder is created there to
do test file operations in (so no existing files will be harmed).
"""
+from __future__ import with_statement
+
from binascii import hexlify
-import logging
import os
-import random
-import struct
+import warnings
import sys
import threading
-import time
import unittest
+import StringIO
import paramiko
from stub_sftp import StubServer, StubSFTPServer
@@ -188,6 +188,17 @@ class SFTPTest (unittest.TestCase):
finally:
sftp.remove(FOLDER + '/duck.txt')
+ def test_3_sftp_file_can_be_used_as_context_manager(self):
+ """
+ verify that an opened file can be used as a context manager
+ """
+ try:
+ with sftp.open(FOLDER + '/duck.txt', 'w') as f:
+ f.write(ARTICLE)
+ self.assertEqual(sftp.stat(FOLDER + '/duck.txt').st_size, 1483)
+ finally:
+ sftp.remove(FOLDER + '/duck.txt')
+
def test_4_append(self):
"""
verify that a file can be opened for append, and tell() still works.
@@ -214,7 +225,7 @@ class SFTPTest (unittest.TestCase):
"""
f = sftp.open(FOLDER + '/first.txt', 'w')
try:
- f.write('content!\n');
+ f.write('content!\n')
f.close()
sftp.rename(FOLDER + '/first.txt', FOLDER + '/second.txt')
try:
@@ -425,7 +436,7 @@ class SFTPTest (unittest.TestCase):
self.assertEqual(sftp.readlink(FOLDER + '/link.txt'), 'original.txt')
f = sftp.open(FOLDER + '/link.txt', 'r')
- self.assertEqual(f.readlines(), [ 'original\n' ])
+ self.assertEqual(f.readlines(), ['original\n'])
f.close()
cwd = sftp.normalize('.')
@@ -553,7 +564,6 @@ class SFTPTest (unittest.TestCase):
"""
verify that get/put work.
"""
- import os, warnings
warnings.filterwarnings('ignore', 'tempnam.*')
localname = os.tempnam()
@@ -618,7 +628,7 @@ class SFTPTest (unittest.TestCase):
try:
f = sftp.open(FOLDER + '/unusual.txt', 'wx')
self.fail('expected exception')
- except IOError, x:
+ except IOError:
pass
finally:
sftp.unlink(FOLDER + '/unusual.txt')
@@ -658,12 +668,12 @@ class SFTPTest (unittest.TestCase):
f.close()
try:
f = sftp.open(FOLDER + '/zero', 'r')
- data = f.readv([(0, 12)])
+ f.readv([(0, 12)])
f.close()
f = sftp.open(FOLDER + '/zero', 'r')
f.prefetch()
- data = f.read(100)
+ f.read(100)
f.close()
finally:
sftp.unlink(FOLDER + '/zero')
@@ -672,7 +682,6 @@ class SFTPTest (unittest.TestCase):
"""
verify that get/put work without confirmation.
"""
- import os, warnings
warnings.filterwarnings('ignore', 'tempnam.*')
localname = os.tempnam()
@@ -684,7 +693,7 @@ class SFTPTest (unittest.TestCase):
def progress_callback(x, y):
saved_progress.append((x, y))
res = sftp.put(localname, FOLDER + '/bunny.txt', progress_callback, False)
-
+
self.assertEquals(SFTPAttributes().attr, res.attr)
f = sftp.open(FOLDER + '/bunny.txt', 'r')
@@ -717,3 +726,15 @@ class SFTPTest (unittest.TestCase):
finally:
sftp.remove(FOLDER + '/append.txt')
+ def test_putfo_empty_file(self):
+ """
+ Send an empty file and confirm it is sent.
+ """
+ target = FOLDER + '/empty file.txt'
+ stream = StringIO.StringIO()
+ try:
+ attrs = sftp.putfo(stream, target)
+ # the returned attributes should not be null
+ self.assertNotEqual(attrs, None)
+ finally:
+ sftp.remove(target)
diff --git a/tests/test_util.py b/tests/test_util.py
index 093a215..efda9b2 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -104,23 +104,32 @@ class UtilTest(ParamikoTest):
f = cStringIO.StringIO(test_config_file)
config = paramiko.util.parse_ssh_config(f)
self.assertEquals(config._config,
- [ {'identityfile': '~/.ssh/id_rsa', 'host': '*', 'user': 'robey',
- 'crazy': 'something dumb '},
- {'host': '*.example.com', 'user': 'bjork', 'port': '3333'},
- {'host': 'spoo.example.com', 'crazy': 'something else'}])
+ [{'host': ['*'], 'config': {}}, {'host': ['*'], 'config': {'identityfile': ['~/.ssh/id_rsa'], 'user': 'robey'}},
+ {'host': ['*.example.com'], 'config': {'user': 'bjork', 'port': '3333'}},
+ {'host': ['*'], 'config': {'crazy': 'something dumb '}},
+ {'host': ['spoo.example.com'], 'config': {'crazy': 'something else'}}])
def test_3_host_config(self):
global test_config_file
f = cStringIO.StringIO(test_config_file)
config = paramiko.util.parse_ssh_config(f)
+
for host, values in {
- 'irc.danger.com': {'user': 'robey', 'crazy': 'something dumb '},
- 'irc.example.com': {'user': 'bjork', 'crazy': 'something dumb ', 'port': '3333'},
- 'spoo.example.com': {'user': 'bjork', 'crazy': 'something else', 'port': '3333'}
+ 'irc.danger.com': {'crazy': 'something dumb ',
+ 'hostname': 'irc.danger.com',
+ 'user': 'robey'},
+ 'irc.example.com': {'crazy': 'something dumb ',
+ 'hostname': 'irc.example.com',
+ 'user': 'robey',
+ 'port': '3333'},
+ 'spoo.example.com': {'crazy': 'something dumb ',
+ 'hostname': 'spoo.example.com',
+ 'user': 'robey',
+ 'port': '3333'}
}.items():
values = dict(values,
hostname=host,
- identityfile=os.path.expanduser("~/.ssh/id_rsa")
+ identityfile=[os.path.expanduser("~/.ssh/id_rsa")]
)
self.assertEquals(
paramiko.util.lookup_ssh_host_config(host, config),
@@ -151,8 +160,8 @@ class UtilTest(ParamikoTest):
# just verify that we can pull out 32 bytes and not get an exception.
x = rng.read(32)
self.assertEquals(len(x), 32)
-
- def test_7_host_config_expose_ssh_issue_33(self):
+
+ def test_7_host_config_expose_issue_33(self):
test_config_file = """
Host www13.*
Port 22
@@ -220,16 +229,16 @@ Host equals-delimited
ProxyCommand should perform interpolation on the value
"""
config = paramiko.util.parse_ssh_config(cStringIO.StringIO("""
-Host *
- Port 25
- ProxyCommand host %h port %p
-
Host specific
Port 37
ProxyCommand host %h port %p lol
Host portonly
Port 155
+
+Host *
+ Port 25
+ ProxyCommand host %h port %p
"""))
for host, val in (
('foo.com', "host foo.com port 25"),
@@ -240,3 +249,83 @@ Host portonly
host_config(host, config)['proxycommand'],
val
)
+
+ def test_11_host_config_test_negation(self):
+ test_config_file = """
+Host www13.* !*.example.com
+ Port 22
+
+Host *.example.com !www13.*
+ Port 2222
+
+Host www13.*
+ Port 8080
+
+Host *
+ Port 3333
+ """
+ f = cStringIO.StringIO(test_config_file)
+ config = paramiko.util.parse_ssh_config(f)
+ host = 'www13.example.com'
+ self.assertEquals(
+ paramiko.util.lookup_ssh_host_config(host, config),
+ {'hostname': host, 'port': '8080'}
+ )
+
+ def test_12_host_config_test_proxycommand(self):
+ test_config_file = """
+Host proxy-with-equal-divisor-and-space
+ProxyCommand = foo=bar
+
+Host proxy-with-equal-divisor-and-no-space
+ProxyCommand=foo=bar
+
+Host proxy-without-equal-divisor
+ProxyCommand foo=bar:%h-%p
+ """
+ for host, values in {
+ 'proxy-with-equal-divisor-and-space' :{'hostname': 'proxy-with-equal-divisor-and-space',
+ 'proxycommand': 'foo=bar'},
+ 'proxy-with-equal-divisor-and-no-space':{'hostname': 'proxy-with-equal-divisor-and-no-space',
+ 'proxycommand': 'foo=bar'},
+ 'proxy-without-equal-divisor' :{'hostname': 'proxy-without-equal-divisor',
+ 'proxycommand':
+ 'foo=bar:proxy-without-equal-divisor-22'}
+ }.items():
+
+ f = cStringIO.StringIO(test_config_file)
+ config = paramiko.util.parse_ssh_config(f)
+ self.assertEquals(
+ paramiko.util.lookup_ssh_host_config(host, config),
+ values
+ )
+
+ def test_11_host_config_test_identityfile(self):
+ test_config_file = """
+
+IdentityFile id_dsa0
+
+Host *
+IdentityFile id_dsa1
+
+Host dsa2
+IdentityFile id_dsa2
+
+Host dsa2*
+IdentityFile id_dsa22
+ """
+ for host, values in {
+ 'foo' :{'hostname': 'foo',
+ 'identityfile': ['id_dsa0', 'id_dsa1']},
+ 'dsa2' :{'hostname': 'dsa2',
+ 'identityfile': ['id_dsa0', 'id_dsa1', 'id_dsa2', 'id_dsa22']},
+ 'dsa22' :{'hostname': 'dsa22',
+ 'identityfile': ['id_dsa0', 'id_dsa1', 'id_dsa22']}
+ }.items():
+
+ f = cStringIO.StringIO(test_config_file)
+ config = paramiko.util.parse_ssh_config(f)
+ self.assertEquals(
+ paramiko.util.lookup_ssh_host_config(host, config),
+ values
+ )
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..6cb8001
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,6 @@
+[tox]
+envlist = py25,py26,py27
+
+[testenv]
+commands = pip install --use-mirrors -q -r requirements.txt
+ python test.py
hooks/post-receive
--
python-paramiko.git (Debian package python-paramiko)
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "python-paramiko.git" (Debian package python-paramiko).
More information about the x2go-commits
mailing list