[X2go-Commits] python-paramiko.git - sunweaver (branch) updated: 93c1a0da5d5ce2bbb1251bf7d77c336aedcb3332

X2Go dev team git-admin at x2go.org
Mon Nov 12 10:35:34 CET 2012


The branch, sunweaver has been updated
       via  93c1a0da5d5ce2bbb1251bf7d77c336aedcb3332 (commit)
       via  06d987c362e75a5d86c411c20932a3818fb4fa40 (commit)
       via  a32addcfb781199b2216d7b87cf3178b97620809 (commit)
       via  65de2529a9e21f66c72f6335886db5a7f2ea6b75 (commit)
       via  42f1b451a63767b9489dfae51a042236d31303b5 (commit)
       via  7f4c26f8601b2335b210896d2cb06348957e7aae (commit)
       via  a3b44c7ed9a26231fcdf0e795305311da8814f92 (commit)
       via  ebd007b21757d3a88b7f769fa6000662fae2eb9a (commit)
       via  e7ab3c068fe39cb7be783127039113213fdb2be4 (commit)
       via  308c5f57d9a06e925001ca11df323a78d443d4b2 (commit)
       via  7a3cb790a6561f391be6118b75b9f74513820517 (commit)
       via  fd392d6b2090d227937add287a2ad0f8eb5cba59 (commit)
       via  191a5fc08cb8ce08917ad4e8739d7d5efbec2be2 (commit)
       via  0981c25cd8ef651e6274feae8b3b6acd0869b680 (commit)
       via  0a276ac34bac3c9729992e3fdb3622d8916c5096 (commit)
       via  394ab2699e254145e4904e1408036bb1683c14ca (commit)
       via  5d15467ad4f14b13e2d68f8f3b0c8d33dde4afeb (commit)
       via  27271fa455228f14b1e16b576f0c8c40f532c227 (commit)
       via  7cd2f2715bb7bca536a1b8d85a2d62f64831bd3a (commit)
       via  270bb94a46d94298c2fbe3926578650e3f21ae5d (commit)
       via  928c06274816669a94753b80f493a2e4e1b9357a (commit)
       via  fb5d245b3148250bb3d7d6b46c2ec2b7914982a7 (commit)
       via  8e8dcea2953dfd888602f37330c9f26e3f151244 (commit)
       via  31244a2ccbf9e2e8607236e859cbf38ddafd6060 (commit)
       via  f9b7ce902ff4532aaa1ea51911a3f798b9c9d653 (commit)
       via  31ea4f0734a086f2345aaea57fd6fc1c3ea4a87e (commit)
       via  fd5e29b5a8aff1fe9f11f9f7bee5f0eb85ae569a (commit)
      from  f5a286289df1d5f9fbd3c458f9379c571d496df9 (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 93c1a0da5d5ce2bbb1251bf7d77c336aedcb3332
Merge: f5a2862 06d987c
Author: Mike Gabriel <mike.gabriel at das-netzwerkteam.de>
Date:   Wed Nov 7 15:34:31 2012 +0100

    Merge branch 'master' into sunweaver

-----------------------------------------------------------------------

Summary of changes:
 .travis.yml               |    2 +
 NEWS                      |   21 ++++++++---
 paramiko/__init__.py      |    7 +++-
 paramiko/client.py        |   37 ++++++++++--------
 paramiko/config.py        |   56 +++++++++++++++++-----------
 paramiko/packet.py        |    4 +-
 paramiko/proxy.py         |   91 +++++++++++++++++++++++++++++++++++++++++++++
 paramiko/ssh_exception.py |   17 +++++++++
 paramiko/transport.py     |    5 ++-
 setup.py                  |    2 +-
 tests/test_util.py        |   48 +++++++++++++++++++++++-
 11 files changed, 241 insertions(+), 49 deletions(-)
 create mode 100644 paramiko/proxy.py

The diff of changes is:
diff --git a/.travis.yml b/.travis.yml
index 312a184..90dbb80 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,6 +8,8 @@ install:
   - pip install -e .
 script: python test.py
 notifications:
+  email:
+    on_failure: change
   irc:
     channels: "irc.freenode.org#fabric"
     on_success: change
diff --git a/NEWS b/NEWS
index ed9fd00..5542046 100644
--- a/NEWS
+++ b/NEWS
@@ -12,12 +12,21 @@ Issues noted as "Fabric #NN" can be found at https://github.com/fabric/fabric/.
 Releases
 ========
 
-v1.9.0 (DD MM YYYY)
--------------------
-
+v1.9.0 (6th Nov 2012)
+---------------------
 
-v1.8.1 (DD MM YYYY)
--------------------
+* #97 (with a little #93): Improve config parsing of `ProxyCommand` directives
+  and provide a wrapper class to allow subprocess-driven proxy commands to be
+  used as `sock=` arguments for `SSHClient.connect`.
+* #77: Allow `SSHClient.connect()` to take an explicit `sock` parameter
+  overriding creation of an internal, implicit socket object.
+* Thanks in no particular order to Erwin Bolwidt, Oskari Saarenmaa, Steven
+  Noonan, Vladimir Lazarenko, Lincoln de Sousa, Valentino Volonghi, Olle
+  Lundberg, and Github user `@acrish` for the various and sundry patches
+  leading to the above changes.
+
+v1.8.1 (6th Nov 2012)
+---------------------
 
 * #90: Ensure that callbacks handed to `SFTPClient.get()` always fire at least
   once, even for zero-length files downloaded. Thanks to Github user `@enB` for
@@ -32,6 +41,8 @@ v1.8.1 (DD MM YYYY)
 v1.8.0 (3rd Oct 2012)
 ---------------------
 
+* #17 ('ssh' 28): Fix spurious `NoneType has no attribute 'error'` and similar
+  exceptions that crop up on interpreter exit.
 * 'ssh' 32: Raise a more useful error explaining which `known_hosts` key line was
   problematic, when encountering `binascii` issues decoding known host keys.
   Thanks to `@thomasvs` for catch & patch.
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index 8c15853..29e470a 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -55,7 +55,7 @@ if sys.version_info < (2, 2):
 
 
 __author__ = "Jeff Forcier <jeff at bitprophet.org>"
-__version__ = "1.8.0"
+__version__ = "1.9.0"
 __license__ = "GNU Lesser General Public License (LGPL)"
 
 
@@ -65,7 +65,7 @@ from auth_handler import AuthHandler
 from channel import Channel, ChannelFile
 from ssh_exception import SSHException, PasswordRequiredException, \
     BadAuthenticationType, ChannelException, BadHostKeyException, \
-    AuthenticationException
+    AuthenticationException, ProxyCommandFailure
 from server import ServerInterface, SubsystemHandler, InteractiveQuery
 from rsakey import RSAKey
 from dsskey import DSSKey
@@ -83,6 +83,7 @@ from agent import Agent, AgentKey
 from pkey import PKey
 from hostkeys import HostKeys
 from config import SSHConfig
+from proxy import ProxyCommand
 
 # fix module names for epydoc
 for c in locals().values():
@@ -119,6 +120,8 @@ __all__ = [ 'Transport',
             'BadAuthenticationType',
             'ChannelException',
             'BadHostKeyException',
+            'ProxyCommand',
+            'ProxyCommandFailure',
             'SFTP',
             'SFTPFile',
             'SFTPHandle',
diff --git a/paramiko/client.py b/paramiko/client.py
index 21ee3be..0f05f30 100644
--- a/paramiko/client.py
+++ b/paramiko/client.py
@@ -234,7 +234,7 @@ class SSHClient (object):
 
     def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None,
                 key_filename=None, timeout=None, allow_agent=True, look_for_keys=True,
-                compress=False):
+                compress=False, sock=None):
         """
         Connect to an SSH server and authenticate to it.  The server's host key
         is checked against the system host keys (see L{load_system_host_keys})
@@ -277,6 +277,9 @@ class SSHClient (object):
         @type look_for_keys: bool
         @param compress: set to True to turn on compression
         @type compress: bool
+        @param sock: an open socket or socket-like object (such as a
+            L{Channel}) to use for communication to the target host
+        @type sock: socket
 
         @raise BadHostKeyException: if the server's host key could not be
             verified
@@ -285,21 +288,23 @@ class SSHClient (object):
             establishing an SSH session
         @raise socket.error: if a socket error occurred while connecting
         """
-        for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
-            if socktype == socket.SOCK_STREAM:
-                af = family
-                addr = sockaddr
-                break
-        else:
-            # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :(
-            af, _, _, _, addr = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
-        sock = socket.socket(af, socket.SOCK_STREAM)
-        if timeout is not None:
-            try:
-                sock.settimeout(timeout)
-            except:
-                pass
-        retry_on_signal(lambda: sock.connect(addr))
+        if not sock:
+            for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
+                if socktype == socket.SOCK_STREAM:
+                    af = family
+                    addr = sockaddr
+                    break
+            else:
+                # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :(
+                af, _, _, _, addr = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
+            sock = socket.socket(af, socket.SOCK_STREAM)
+            if timeout is not None:
+                try:
+                    sock.settimeout(timeout)
+                except:
+                    pass
+            retry_on_signal(lambda: sock.connect(addr))
+
         t = self._transport = Transport(sock)
         t.use_compression(compress=compress)
         if self._log_channel is not None:
diff --git a/paramiko/config.py b/paramiko/config.py
index 458d5dd..2828d90 100644
--- a/paramiko/config.py
+++ b/paramiko/config.py
@@ -22,9 +22,12 @@ L{SSHConfig}.
 
 import fnmatch
 import os
+import re
 import socket
 
 SSH_PORT=22
+proxy_re = re.compile(r"^(proxycommand)\s*=*\s*(.*)", re.I)
+
 
 class SSHConfig (object):
     """
@@ -56,8 +59,13 @@ class SSHConfig (object):
             if (line == '') or (line[0] == '#'):
                 continue
             if '=' in line:
-                key, value = line.split('=', 1)
-                key = key.strip().lower()
+                # Ensure ProxyCommand gets properly split
+                if line.lower().strip().startswith('proxycommand'):
+                    match = proxy_re.match(line)
+                    key, value = match.group(1).lower(), match.group(2)
+                else:
+                    key, value = line.split('=', 1)
+                    key = key.strip().lower()
             else:
                 # find first whitespace, and split there
                 i = 0
@@ -149,26 +157,30 @@ class SSHConfig (object):
         host = socket.gethostname().split('.')[0]
         fqdn = socket.getfqdn()
         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)
-                ]
-                }
+        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]:
diff --git a/paramiko/packet.py b/paramiko/packet.py
index 9782061..5d918e2 100644
--- a/paramiko/packet.py
+++ b/paramiko/packet.py
@@ -29,7 +29,7 @@ import time
 
 from paramiko.common import *
 from paramiko import util
-from paramiko.ssh_exception import SSHException
+from paramiko.ssh_exception import SSHException, ProxyCommandFailure
 from paramiko.message import Message
 
 
@@ -254,6 +254,8 @@ class Packetizer (object):
                     retry_write = True
                 else:
                     n = -1
+            except ProxyCommandFailure:
+                raise # so it doesn't get swallowed by the below catchall
             except Exception:
                 # could be: (32, 'Broken pipe')
                 n = -1
diff --git a/paramiko/proxy.py b/paramiko/proxy.py
new file mode 100644
index 0000000..218b76e
--- /dev/null
+++ b/paramiko/proxy.py
@@ -0,0 +1,91 @@
+# Copyright (C) 2012  Yipit, Inc <coders at yipit.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{ProxyCommand}.
+"""
+
+import os
+from shlex import split as shlsplit
+import signal
+from subprocess import Popen, PIPE
+
+from paramiko.ssh_exception import ProxyCommandFailure
+
+
+class ProxyCommand(object):
+    """
+    Wraps a subprocess running ProxyCommand-driven programs.
+
+    This class implements a the socket-like interface needed by the
+    L{Transport} and L{Packetizer} classes. Using this class instead of a
+    regular socket makes it possible to talk with a Popen'd command that will
+    proxy traffic between the client and a server hosted in another machine.
+    """
+    def __init__(self, command_line):
+        """
+        Create a new CommandProxy instance. The instance created by this
+        class can be passed as an argument to the L{Transport} class.
+
+        @param command_line: the command that should be executed and
+            used as the proxy.
+        @type command_line: str
+        """
+        self.cmd = shlsplit(command_line)
+        self.process = Popen(self.cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
+
+    def send(self, content):
+        """
+        Write the content received from the SSH client to the standard
+        input of the forked command.
+
+        @param content: string to be sent to the forked command
+        @type content: str
+        """
+        try:
+            self.process.stdin.write(content)
+        except IOError, e:
+            # There was a problem with the child process. It probably
+            # 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)
+        return len(content)
+
+    def recv(self, size):
+        """
+        Read from the standard output of the forked program.
+
+        @param size: how many chars should be read
+        @type size: int
+
+        @return: the length of the read content
+        @rtype: int
+        """
+        try:
+            return os.read(self.process.stdout.fileno(), size)
+        except IOError, e:
+            raise BadProxyCommand(' '.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
diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py
index 68924d0..f2406dc 100644
--- a/paramiko/ssh_exception.py
+++ b/paramiko/ssh_exception.py
@@ -113,3 +113,20 @@ class BadHostKeyException (SSHException):
         self.key = got_key
         self.expected_key = expected_key
 
+
+class ProxyCommandFailure (SSHException):
+    """
+    The "ProxyCommand" found in the .ssh/config file returned an error.
+
+    @ivar command: The command line that is generating this exception.
+    @type command: str
+    @ivar error: The error captured from the proxy command output.
+    @type error: str
+    """
+    def __init__(self, command, error):
+        SSHException.__init__(self,
+            '"ProxyCommand (%s)" returned non-zero exit status: %s' % (
+                command, error
+            )
+        )
+        self.error = error
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 04680a9..c801031 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -44,7 +44,8 @@ from paramiko.primes import ModulusPack
 from paramiko.rsakey import RSAKey
 from paramiko.server import ServerInterface
 from paramiko.sftp_client import SFTPClient
-from paramiko.ssh_exception import SSHException, BadAuthenticationType, ChannelException
+from paramiko.ssh_exception import (SSHException, BadAuthenticationType,
+    ChannelException, ProxyCommandFailure)
 from paramiko.util import retry_on_signal
 
 from Crypto import Random
@@ -1674,6 +1675,8 @@ class Transport (threading.Thread):
                 timeout = 2
             try:
                 buf = self.packetizer.readline(timeout)
+            except ProxyCommandFailure:
+                raise
             except Exception, x:
                 raise SSHException('Error reading SSH protocol banner' + str(x))
             if buf[:4] == 'SSH-':
diff --git a/setup.py b/setup.py
index 73407b0..1bb1a71 100644
--- a/setup.py
+++ b/setup.py
@@ -52,7 +52,7 @@ if sys.platform == 'darwin':
 
 
 setup(name = "paramiko",
-      version = "1.8.0",
+      version = "1.9.0",
       description = "SSH2 protocol library",
       author = "Jeff Forcier",
       author_email = "jeff at bitprophet.org",
diff --git a/tests/test_util.py b/tests/test_util.py
index 458709b..093a215 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -27,6 +27,7 @@ import os
 import unittest
 from Crypto.Hash import SHA
 import paramiko.util
+from paramiko.util import lookup_ssh_host_config as host_config
 
 from util import ParamikoTest
 
@@ -151,7 +152,7 @@ class UtilTest(ParamikoTest):
         x = rng.read(32)
         self.assertEquals(len(x), 32)
         
-    def test_7_host_config_expose_issue_33(self):
+    def test_7_host_config_expose_ssh_issue_33(self):
         test_config_file = """
 Host www13.*
     Port 22
@@ -194,3 +195,48 @@ Host *
             raise AssertionError('foo')
         self.assertRaises(AssertionError,
                           lambda: paramiko.util.retry_on_signal(raises_other_exception))
+
+    def test_9_proxycommand_config_equals_parsing(self):
+        """
+        ProxyCommand should not split on equals signs within the value.
+        """
+        conf = """
+Host space-delimited
+    ProxyCommand foo bar=biz baz
+
+Host equals-delimited
+    ProxyCommand=foo bar=biz baz
+"""
+        f = cStringIO.StringIO(conf)
+        config = paramiko.util.parse_ssh_config(f)
+        for host in ('space-delimited', 'equals-delimited'):
+            self.assertEquals(
+                host_config(host, config)['proxycommand'],
+                'foo bar=biz baz'
+            )
+
+    def test_10_proxycommand_interpolation(self):
+        """
+        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
+"""))
+        for host, val in (
+            ('foo.com', "host foo.com port 25"),
+            ('specific', "host specific port 37 lol"),
+            ('portonly', "host portonly port 155"),
+        ):
+            self.assertEquals(
+                host_config(host, config)['proxycommand'],
+                val
+            )


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