~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: Kent Gibson
  • Date: 2007-03-11 13:44:18 UTC
  • mfrom: (2334 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2350.
  • Revision ID: warthog618@gmail.com-20070311134418-nu57arul94zawbj4
merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005 Robey Pointer <robey@lag.net>
2
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
5
5
# it under the terms of the GNU General Public License as published by
28
28
from bzrlib.errors import (ConnectionError,
29
29
                           ParamikoNotPresent,
30
30
                           SocketConnectionError,
 
31
                           SSHVendorNotFound,
31
32
                           TransportError,
32
33
                           UnknownSSH,
33
34
                           )
58
59
# connect to an agent if we are on win32 and using Paramiko older than 1.6
59
60
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
60
61
 
61
 
_ssh_vendors = {}
62
 
 
63
 
def register_ssh_vendor(name, vendor):
64
 
    """Register SSH vendor."""
65
 
    _ssh_vendors[name] = vendor
66
 
 
67
 
    
68
 
_ssh_vendor = None
69
 
def _get_ssh_vendor():
70
 
    """Find out what version of SSH is on the system."""
71
 
    global _ssh_vendor
72
 
    if _ssh_vendor is not None:
73
 
        return _ssh_vendor
74
 
 
75
 
    if 'BZR_SSH' in os.environ:
76
 
        vendor_name = os.environ['BZR_SSH']
 
62
 
 
63
class SSHVendorManager(object):
 
64
    """Manager for manage SSH vendors."""
 
65
 
 
66
    # Note, although at first sign the class interface seems similar to
 
67
    # bzrlib.registry.Registry it is not possible/convenient to directly use
 
68
    # the Registry because the class just has "get()" interface instead of the
 
69
    # Registry's "get(key)".
 
70
 
 
71
    def __init__(self):
 
72
        self._ssh_vendors = {}
 
73
        self._cached_ssh_vendor = None
 
74
        self._default_ssh_vendor = None
 
75
 
 
76
    def register_default_vendor(self, vendor):
 
77
        """Register default SSH vendor."""
 
78
        self._default_ssh_vendor = vendor
 
79
 
 
80
    def register_vendor(self, name, vendor):
 
81
        """Register new SSH vendor by name."""
 
82
        self._ssh_vendors[name] = vendor
 
83
 
 
84
    def clear_cache(self):
 
85
        """Clear previously cached lookup result."""
 
86
        self._cached_ssh_vendor = None
 
87
 
 
88
    def _get_vendor_by_environment(self, environment=None):
 
89
        """Return the vendor or None based on BZR_SSH environment variable.
 
90
 
 
91
        :raises UnknownSSH: if the BZR_SSH environment variable contains
 
92
                            unknown vendor name
 
93
        """
 
94
        if environment is None:
 
95
            environment = os.environ
 
96
        if 'BZR_SSH' in environment:
 
97
            vendor_name = environment['BZR_SSH']
 
98
            try:
 
99
                vendor = self._ssh_vendors[vendor_name]
 
100
            except KeyError:
 
101
                raise UnknownSSH(vendor_name)
 
102
            return vendor
 
103
        return None
 
104
 
 
105
    def _get_ssh_version_string(self, args):
 
106
        """Return SSH version string from the subprocess."""
77
107
        try:
78
 
            _ssh_vendor = _ssh_vendors[vendor_name]
79
 
        except KeyError:
80
 
            raise UnknownSSH(vendor_name)
81
 
        return _ssh_vendor
82
 
 
83
 
    try:
84
 
        p = subprocess.Popen(['ssh', '-V'],
85
 
                             stdin=subprocess.PIPE,
86
 
                             stdout=subprocess.PIPE,
87
 
                             stderr=subprocess.PIPE,
88
 
                             **os_specific_subprocess_params())
89
 
        returncode = p.returncode
90
 
        stdout, stderr = p.communicate()
91
 
    except OSError:
92
 
        returncode = -1
93
 
        stdout = stderr = ''
94
 
    if 'OpenSSH' in stderr:
95
 
        mutter('ssh implementation is OpenSSH')
96
 
        _ssh_vendor = OpenSSHSubprocessVendor()
97
 
    elif 'SSH Secure Shell' in stderr:
98
 
        mutter('ssh implementation is SSH Corp.')
99
 
        _ssh_vendor = SSHCorpSubprocessVendor()
100
 
 
101
 
    if _ssh_vendor is not None:
102
 
        return _ssh_vendor
103
 
 
104
 
    # XXX: 20051123 jamesh
105
 
    # A check for putty's plink or lsh would go here.
106
 
 
107
 
    mutter('falling back to paramiko implementation')
108
 
    _ssh_vendor = ParamikoVendor()
109
 
    return _ssh_vendor
 
108
            p = subprocess.Popen(args,
 
109
                                 stdout=subprocess.PIPE,
 
110
                                 stderr=subprocess.PIPE,
 
111
                                 **os_specific_subprocess_params())
 
112
            stdout, stderr = p.communicate()
 
113
        except OSError:
 
114
            stdout = stderr = ''
 
115
        return stdout + stderr
 
116
 
 
117
    def _get_vendor_by_version_string(self, version):
 
118
        """Return the vendor or None based on output from the subprocess.
 
119
 
 
120
        :param version: The output of 'ssh -V' like command.
 
121
        """
 
122
        vendor = None
 
123
        if 'OpenSSH' in version:
 
124
            mutter('ssh implementation is OpenSSH')
 
125
            vendor = OpenSSHSubprocessVendor()
 
126
        elif 'SSH Secure Shell' in version:
 
127
            mutter('ssh implementation is SSH Corp.')
 
128
            vendor = SSHCorpSubprocessVendor()
 
129
        elif 'plink' in version:
 
130
            mutter("ssh implementation is Putty's plink.")
 
131
            vendor = PLinkSubprocessVendor()
 
132
        return vendor
 
133
 
 
134
    def _get_vendor_by_inspection(self):
 
135
        """Return the vendor or None by checking for known SSH implementations."""
 
136
        for args in [['ssh', '-V'], ['plink', '-V']]:
 
137
            version = self._get_ssh_version_string(args)
 
138
            vendor = self._get_vendor_by_version_string(version)
 
139
            if vendor is not None:
 
140
                return vendor
 
141
        return None
 
142
 
 
143
    def get_vendor(self, environment=None):
 
144
        """Find out what version of SSH is on the system.
 
145
 
 
146
        :raises SSHVendorNotFound: if no any SSH vendor is found
 
147
        :raises UnknownSSH: if the BZR_SSH environment variable contains
 
148
                            unknown vendor name
 
149
        """
 
150
        if self._cached_ssh_vendor is None:
 
151
            vendor = self._get_vendor_by_environment(environment)
 
152
            if vendor is None:
 
153
                vendor = self._get_vendor_by_inspection()
 
154
                if vendor is None:
 
155
                    mutter('falling back to default implementation')
 
156
                    vendor = self._default_ssh_vendor
 
157
                    if vendor is None:
 
158
                        raise SSHVendorNotFound()
 
159
            self._cached_ssh_vendor = vendor
 
160
        return self._cached_ssh_vendor
 
161
 
 
162
_ssh_vendor_manager = SSHVendorManager()
 
163
_get_ssh_vendor = _ssh_vendor_manager.get_vendor
 
164
register_default_ssh_vendor = _ssh_vendor_manager.register_default_vendor
 
165
register_ssh_vendor = _ssh_vendor_manager.register_vendor
110
166
 
111
167
 
112
168
def _ignore_sigint():
115
171
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
116
172
    import signal
117
173
    signal.signal(signal.SIGINT, signal.SIG_IGN)
118
 
    
119
174
 
120
175
 
121
176
class LoopbackSFTP(object):
263
318
                                         msg='Unable to invoke remote bzr')
264
319
 
265
320
if paramiko is not None:
266
 
    register_ssh_vendor('paramiko', ParamikoVendor())
 
321
    vendor = ParamikoVendor()
 
322
    register_ssh_vendor('paramiko', vendor)
 
323
    register_ssh_vendor('none', vendor)
 
324
    register_default_ssh_vendor(vendor)
 
325
    del vendor
267
326
 
268
327
 
269
328
class SubprocessVendor(SSHVendor):
315
374
        """
316
375
        raise NotImplementedError(self._get_vendor_specific_argv)
317
376
 
318
 
register_ssh_vendor('none', ParamikoVendor())
319
 
 
320
377
 
321
378
class OpenSSHSubprocessVendor(SubprocessVendor):
322
379
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
369
426
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
370
427
 
371
428
 
 
429
class PLinkSubprocessVendor(SubprocessVendor):
 
430
    """SSH vendor that uses the 'plink' executable from Putty."""
 
431
 
 
432
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
 
433
                                  command=None):
 
434
        assert subsystem is not None or command is not None, (
 
435
            'Must specify a command or subsystem')
 
436
        if subsystem is not None:
 
437
            assert command is None, (
 
438
                'subsystem and command are mutually exclusive')
 
439
        args = ['plink', '-x', '-a', '-ssh', '-2']
 
440
        if port is not None:
 
441
            args.extend(['-P', str(port)])
 
442
        if username is not None:
 
443
            args.extend(['-l', username])
 
444
        if subsystem is not None:
 
445
            args.extend(['-s', host, subsystem])
 
446
        else:
 
447
            args.extend([host] + command)
 
448
        return args
 
449
 
 
450
register_ssh_vendor('plink', PLinkSubprocessVendor())
 
451
 
 
452
 
372
453
def _paramiko_auth(username, password, host, paramiko_transport):
373
454
    # paramiko requires a username, but it might be none if nothing was supplied
374
455
    # use the local username, just in case.