~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

Merge from bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
import socket
24
24
import subprocess
25
25
import sys
26
 
import weakref
27
26
 
28
27
from bzrlib.config import config_dir, ensure_config_dir_exists
29
28
from bzrlib.errors import (ConnectionError,
47
46
BZR_HOSTKEYS = {}
48
47
 
49
48
 
50
 
# This is a weakref dictionary, so that we can reuse connections
51
 
# that are still active. Long term, it might be nice to have some
52
 
# sort of expiration policy, such as disconnect if inactive for
53
 
# X seconds. But that requires a lot more fanciness.
54
 
_connected_hosts = weakref.WeakValueDictionary()
55
 
 
56
49
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
57
50
 
58
51
# Paramiko 1.5 tries to open a socket.AF_UNIX in order to connect
61
54
# connect to an agent if we are on win32 and using Paramiko older than 1.6
62
55
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
63
56
 
 
57
_ssh_vendors = {}
 
58
 
 
59
def register_ssh_vendor(name, vendor):
 
60
    """Register SSH vendor."""
 
61
    _ssh_vendors[name] = vendor
 
62
 
 
63
    
 
64
_ssh_vendor = None
 
65
def _get_ssh_vendor():
 
66
    """Find out what version of SSH is on the system."""
 
67
    global _ssh_vendor
 
68
    if _ssh_vendor is not None:
 
69
        return _ssh_vendor
 
70
 
 
71
    if 'BZR_SSH' in os.environ:
 
72
        vendor_name = os.environ['BZR_SSH']
 
73
        try:
 
74
            _ssh_vendor = _ssh_vendors[vendor_name]
 
75
        except KeyError:
 
76
            raise UnknownSSH(vendor_name)
 
77
        return _ssh_vendor
 
78
 
 
79
    try:
 
80
        p = subprocess.Popen(['ssh', '-V'],
 
81
                             stdin=subprocess.PIPE,
 
82
                             stdout=subprocess.PIPE,
 
83
                             stderr=subprocess.PIPE,
 
84
                             **os_specific_subprocess_params())
 
85
        returncode = p.returncode
 
86
        stdout, stderr = p.communicate()
 
87
    except OSError:
 
88
        returncode = -1
 
89
        stdout = stderr = ''
 
90
    if 'OpenSSH' in stderr:
 
91
        mutter('ssh implementation is OpenSSH')
 
92
        _ssh_vendor = OpenSSHSubprocessVendor()
 
93
    elif 'SSH Secure Shell' in stderr:
 
94
        mutter('ssh implementation is SSH Corp.')
 
95
        _ssh_vendor = SSHCorpSubprocessVendor()
 
96
 
 
97
    if _ssh_vendor is not None:
 
98
        return _ssh_vendor
 
99
 
 
100
    # XXX: 20051123 jamesh
 
101
    # A check for putty's plink or lsh would go here.
 
102
 
 
103
    mutter('falling back to paramiko implementation')
 
104
    _ssh_vendor = ssh.ParamikoVendor()
 
105
    return _ssh_vendor
 
106
 
 
107
 
64
108
 
65
109
def _ignore_sigint():
66
110
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
91
135
 
92
136
 
93
137
class SSHVendor(object):
 
138
    """Abstract base class for SSH vendor implementations."""
 
139
    
94
140
    def connect_sftp(self, username, password, host, port):
 
141
        """Make an SSH connection, and return an SFTPClient.
 
142
        
 
143
        :param username: an ascii string
 
144
        :param password: an ascii string
 
145
        :param host: a host name as an ascii string
 
146
        :param port: a port number
 
147
        :type port: int
 
148
 
 
149
        :raises: ConnectionError if it cannot connect.
 
150
 
 
151
        :rtype: paramiko.sftp_client.SFTPClient
 
152
        """
95
153
        raise NotImplementedError(self.connect_sftp)
96
154
 
97
155
    def connect_ssh(self, username, password, host, port, command):
 
156
        """Make an SSH connection, and return a pipe-like object.
 
157
        
 
158
        (This is currently unused, it's just here to indicate future directions
 
159
        for this code.)
 
160
        """
98
161
        raise NotImplementedError(self.connect_ssh)
99
162
        
100
163
 
101
164
class LoopbackVendor(SSHVendor):
 
165
    """SSH "vendor" that connects over a plain TCP socket, not SSH."""
102
166
    
103
167
    def connect_sftp(self, username, password, host, port):
104
168
        sock = socket.socket()
109
173
                                  % (host, port, e))
110
174
        return SFTPClient(LoopbackSFTP(sock))
111
175
 
 
176
register_ssh_vendor('loopback', LoopbackVendor())
 
177
 
112
178
 
113
179
class ParamikoVendor(SSHVendor):
 
180
    """Vendor that uses paramiko."""
114
181
 
115
182
    def connect_sftp(self, username, password, host, port):
116
183
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
158
225
                                  (host, port), e)
159
226
        return sftp
160
227
 
 
228
register_ssh_vendor('paramiko', ParamikoVendor())
 
229
 
161
230
 
162
231
class SubprocessVendor(SSHVendor):
 
232
    """Abstract base class for vendors that use pipes to a subprocess."""
 
233
    
163
234
    def connect_sftp(self, username, password, host, port):
164
235
        try:
165
 
            args = self.get_args(username, host, port, subsystem='sftp')
166
 
            proc = subprocess.Popen(args,
 
236
            argv = self._get_vendor_specific_argv(username, host, port,
 
237
                                                  subsystem='sftp')
 
238
            proc = subprocess.Popen(argv,
167
239
                                    stdin=subprocess.PIPE,
168
240
                                    stdout=subprocess.PIPE,
169
241
                                    **os_specific_subprocess_params())
181
253
            raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
182
254
                                  % (host, port, e))
183
255
 
184
 
    def get_args(self, username, host, port, subsystem=None, command=None):
185
 
        raise NotImplementedError(self.get_args)
 
256
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
 
257
                                  command=None):
 
258
        """Returns the argument list to run the subprocess with.
 
259
        
 
260
        Exactly one of 'subsystem' and 'command' must be specified.
 
261
        """
 
262
        raise NotImplementedError(self._get_vendor_specific_argv)
 
263
 
 
264
register_ssh_vendor('none', ParamikoVendor())
186
265
 
187
266
 
188
267
class OpenSSHSubprocessVendor(SubprocessVendor):
 
268
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
189
269
    
190
 
    def get_args(self, username, host, port, subsystem=None, command=None):
 
270
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
 
271
                                  command=None):
191
272
        assert subsystem is not None or command is not None, (
192
273
            'Must specify a command or subsystem')
193
274
        if subsystem is not None:
207
288
            args.extend([host] + command)
208
289
        return args
209
290
 
 
291
register_ssh_vendor('openssh', OpenSSHSubprocessVendor())
 
292
 
210
293
 
211
294
class SSHCorpSubprocessVendor(SubprocessVendor):
 
295
    """SSH vendor that uses the 'ssh' executable from SSH Corporation."""
212
296
 
213
 
    def get_args(self, username, host, port, subsystem=None, command=None):
 
297
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
 
298
                                  command=None):
214
299
        assert subsystem is not None or command is not None, (
215
300
            'Must specify a command or subsystem')
216
301
        if subsystem is not None:
226
311
        else:
227
312
            args.extend([host] + command)
228
313
        return args
229
 
 
230
314
    
 
315
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
 
316
 
231
317
 
232
318
def _paramiko_auth(username, password, host, paramiko_transport):
233
319
    # paramiko requires a username, but it might be none if nothing was supplied
357
443
                'close_fds': True,
358
444
                }
359
445
 
 
446
 
360
447
class SSHSubprocess(object):
361
448
    """A socket-like object that talks to an ssh subprocess via pipes."""
362
 
    # TODO: this class probably belongs in bzrlib/transport/ssh.py
363
449
 
364
450
    def __init__(self, proc):
365
451
        self.proc = proc