~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: Patch Queue Manager
  • Date: 2014-04-03 07:45:32 UTC
  • mfrom: (6591.1.3 lp1030521)
  • Revision ID: pqm@pqm.ubuntu.com-20140403074532-0sdwrky6ie4y20l4
(vila) Use LooseVersion from distutils to check Pyrex/Cython version in
 order to handle non-integers in the version string. (Andrew Starr-Bochicchio)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Robey Pointer <robey@lag.net>
2
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2006-2011 Robey Pointer <robey@lag.net>
 
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
13
13
#
14
14
# You should have received a copy of the GNU General Public License
15
15
# along with this program; if not, write to the Free Software
16
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
 
18
18
"""Foundation SSH support for SFTP and smart server."""
19
19
 
 
20
from __future__ import absolute_import
 
21
 
20
22
import errno
21
23
import getpass
 
24
import logging
22
25
import os
23
26
import socket
24
27
import subprocess
25
28
import sys
26
29
 
27
 
from bzrlib.config import config_dir, ensure_config_dir_exists
28
 
from bzrlib.errors import (ConnectionError,
29
 
                           ParamikoNotPresent,
30
 
                           TransportError,
31
 
                           UnknownSSH,
32
 
                           )
33
 
 
34
 
from bzrlib.osutils import pathjoin
35
 
from bzrlib.trace import mutter, warning
36
 
import bzrlib.ui
 
30
from bzrlib import (
 
31
    config,
 
32
    errors,
 
33
    osutils,
 
34
    trace,
 
35
    ui,
 
36
    )
37
37
 
38
38
try:
39
39
    import paramiko
40
40
except ImportError, e:
41
 
    raise ParamikoNotPresent(e)
 
41
    # If we have an ssh subprocess, we don't strictly need paramiko for all ssh
 
42
    # access
 
43
    paramiko = None
42
44
else:
43
45
    from paramiko.sftp_client import SFTPClient
44
46
 
55
57
# connect to an agent if we are on win32 and using Paramiko older than 1.6
56
58
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
57
59
 
58
 
_ssh_vendors = {}
59
 
 
60
 
def register_ssh_vendor(name, vendor):
61
 
    """Register SSH vendor."""
62
 
    _ssh_vendors[name] = vendor
63
 
 
64
 
    
65
 
_ssh_vendor = None
66
 
def _get_ssh_vendor():
67
 
    """Find out what version of SSH is on the system."""
68
 
    global _ssh_vendor
69
 
    if _ssh_vendor is not None:
70
 
        return _ssh_vendor
71
 
 
72
 
    if 'BZR_SSH' in os.environ:
73
 
        vendor_name = os.environ['BZR_SSH']
 
60
 
 
61
class SSHVendorManager(object):
 
62
    """Manager for manage SSH vendors."""
 
63
 
 
64
    # Note, although at first sign the class interface seems similar to
 
65
    # bzrlib.registry.Registry it is not possible/convenient to directly use
 
66
    # the Registry because the class just has "get()" interface instead of the
 
67
    # Registry's "get(key)".
 
68
 
 
69
    def __init__(self):
 
70
        self._ssh_vendors = {}
 
71
        self._cached_ssh_vendor = None
 
72
        self._default_ssh_vendor = None
 
73
 
 
74
    def register_default_vendor(self, vendor):
 
75
        """Register default SSH vendor."""
 
76
        self._default_ssh_vendor = vendor
 
77
 
 
78
    def register_vendor(self, name, vendor):
 
79
        """Register new SSH vendor by name."""
 
80
        self._ssh_vendors[name] = vendor
 
81
 
 
82
    def clear_cache(self):
 
83
        """Clear previously cached lookup result."""
 
84
        self._cached_ssh_vendor = None
 
85
 
 
86
    def _get_vendor_by_environment(self, environment=None):
 
87
        """Return the vendor or None based on BZR_SSH environment variable.
 
88
 
 
89
        :raises UnknownSSH: if the BZR_SSH environment variable contains
 
90
                            unknown vendor name
 
91
        """
 
92
        if environment is None:
 
93
            environment = os.environ
 
94
        if 'BZR_SSH' in environment:
 
95
            vendor_name = environment['BZR_SSH']
 
96
            try:
 
97
                vendor = self._ssh_vendors[vendor_name]
 
98
            except KeyError:
 
99
                vendor = self._get_vendor_from_path(vendor_name)
 
100
                if vendor is None:
 
101
                    raise errors.UnknownSSH(vendor_name)
 
102
                vendor.executable_path = vendor_name
 
103
            return vendor
 
104
        return None
 
105
 
 
106
    def _get_ssh_version_string(self, args):
 
107
        """Return SSH version string from the subprocess."""
74
108
        try:
75
 
            _ssh_vendor = _ssh_vendors[vendor_name]
76
 
        except KeyError:
77
 
            raise UnknownSSH(vendor_name)
78
 
        return _ssh_vendor
79
 
 
80
 
    try:
81
 
        p = subprocess.Popen(['ssh', '-V'],
82
 
                             stdin=subprocess.PIPE,
83
 
                             stdout=subprocess.PIPE,
84
 
                             stderr=subprocess.PIPE,
85
 
                             **os_specific_subprocess_params())
86
 
        returncode = p.returncode
87
 
        stdout, stderr = p.communicate()
88
 
    except OSError:
89
 
        returncode = -1
90
 
        stdout = stderr = ''
91
 
    if 'OpenSSH' in stderr:
92
 
        mutter('ssh implementation is OpenSSH')
93
 
        _ssh_vendor = OpenSSHSubprocessVendor()
94
 
    elif 'SSH Secure Shell' in stderr:
95
 
        mutter('ssh implementation is SSH Corp.')
96
 
        _ssh_vendor = SSHCorpSubprocessVendor()
97
 
 
98
 
    if _ssh_vendor is not None:
99
 
        return _ssh_vendor
100
 
 
101
 
    # XXX: 20051123 jamesh
102
 
    # A check for putty's plink or lsh would go here.
103
 
 
104
 
    mutter('falling back to paramiko implementation')
105
 
    _ssh_vendor = ParamikoVendor()
106
 
    return _ssh_vendor
107
 
 
108
 
 
109
 
 
110
 
def _ignore_sigint():
 
109
            p = subprocess.Popen(args,
 
110
                                 stdout=subprocess.PIPE,
 
111
                                 stderr=subprocess.PIPE,
 
112
                                 **os_specific_subprocess_params())
 
113
            stdout, stderr = p.communicate()
 
114
        except OSError:
 
115
            stdout = stderr = ''
 
116
        return stdout + stderr
 
117
 
 
118
    def _get_vendor_by_version_string(self, version, progname):
 
119
        """Return the vendor or None based on output from the subprocess.
 
120
 
 
121
        :param version: The output of 'ssh -V' like command.
 
122
        :param args: Command line that was run.
 
123
        """
 
124
        vendor = None
 
125
        if 'OpenSSH' in version:
 
126
            trace.mutter('ssh implementation is OpenSSH')
 
127
            vendor = OpenSSHSubprocessVendor()
 
128
        elif 'SSH Secure Shell' in version:
 
129
            trace.mutter('ssh implementation is SSH Corp.')
 
130
            vendor = SSHCorpSubprocessVendor()
 
131
        elif 'lsh' in version:
 
132
            trace.mutter('ssh implementation is GNU lsh.')
 
133
            vendor = LSHSubprocessVendor()
 
134
        # As plink user prompts are not handled currently, don't auto-detect
 
135
        # it by inspection below, but keep this vendor detection for if a path
 
136
        # is given in BZR_SSH. See https://bugs.launchpad.net/bugs/414743
 
137
        elif 'plink' in version and progname == 'plink':
 
138
            # Checking if "plink" was the executed argument as Windows
 
139
            # sometimes reports 'ssh -V' incorrectly with 'plink' in its
 
140
            # version.  See https://bugs.launchpad.net/bzr/+bug/107155
 
141
            trace.mutter("ssh implementation is Putty's plink.")
 
142
            vendor = PLinkSubprocessVendor()
 
143
        return vendor
 
144
 
 
145
    def _get_vendor_by_inspection(self):
 
146
        """Return the vendor or None by checking for known SSH implementations."""
 
147
        version = self._get_ssh_version_string(['ssh', '-V'])
 
148
        return self._get_vendor_by_version_string(version, "ssh")
 
149
 
 
150
    def _get_vendor_from_path(self, path):
 
151
        """Return the vendor or None using the program at the given path"""
 
152
        version = self._get_ssh_version_string([path, '-V'])
 
153
        return self._get_vendor_by_version_string(version, 
 
154
            os.path.splitext(os.path.basename(path))[0])
 
155
 
 
156
    def get_vendor(self, environment=None):
 
157
        """Find out what version of SSH is on the system.
 
158
 
 
159
        :raises SSHVendorNotFound: if no any SSH vendor is found
 
160
        :raises UnknownSSH: if the BZR_SSH environment variable contains
 
161
                            unknown vendor name
 
162
        """
 
163
        if self._cached_ssh_vendor is None:
 
164
            vendor = self._get_vendor_by_environment(environment)
 
165
            if vendor is None:
 
166
                vendor = self._get_vendor_by_inspection()
 
167
                if vendor is None:
 
168
                    trace.mutter('falling back to default implementation')
 
169
                    vendor = self._default_ssh_vendor
 
170
                    if vendor is None:
 
171
                        raise errors.SSHVendorNotFound()
 
172
            self._cached_ssh_vendor = vendor
 
173
        return self._cached_ssh_vendor
 
174
 
 
175
_ssh_vendor_manager = SSHVendorManager()
 
176
_get_ssh_vendor = _ssh_vendor_manager.get_vendor
 
177
register_default_ssh_vendor = _ssh_vendor_manager.register_default_vendor
 
178
register_ssh_vendor = _ssh_vendor_manager.register_vendor
 
179
 
 
180
 
 
181
def _ignore_signals():
111
182
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
112
183
    # doesn't handle it itself.
113
184
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
114
185
    import signal
115
186
    signal.signal(signal.SIGINT, signal.SIG_IGN)
116
 
    
117
 
 
118
 
 
119
 
class LoopbackSFTP(object):
 
187
    # GZ 2010-02-19: Perhaps make this check if breakin is installed instead
 
188
    if signal.getsignal(signal.SIGQUIT) != signal.SIG_DFL:
 
189
        signal.signal(signal.SIGQUIT, signal.SIG_IGN)
 
190
 
 
191
 
 
192
class SocketAsChannelAdapter(object):
120
193
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
121
194
 
122
195
    def __init__(self, sock):
123
196
        self.__socket = sock
124
 
 
 
197
 
 
198
    def get_name(self):
 
199
        return "bzr SocketAsChannelAdapter"
 
200
 
125
201
    def send(self, data):
126
202
        return self.__socket.send(data)
127
203
 
128
204
    def recv(self, n):
129
 
        return self.__socket.recv(n)
 
205
        try:
 
206
            return self.__socket.recv(n)
 
207
        except socket.error, e:
 
208
            if e.args[0] in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED,
 
209
                             errno.EBADF):
 
210
                # Connection has closed.  Paramiko expects an empty string in
 
211
                # this case, not an exception.
 
212
                return ''
 
213
            raise
130
214
 
131
215
    def recv_ready(self):
 
216
        # TODO: jam 20051215 this function is necessary to support the
 
217
        # pipelined() function. In reality, it probably should use
 
218
        # poll() or select() to actually return if there is data
 
219
        # available, otherwise we probably don't get any benefit
132
220
        return True
133
221
 
134
222
    def close(self):
137
225
 
138
226
class SSHVendor(object):
139
227
    """Abstract base class for SSH vendor implementations."""
140
 
    
 
228
 
141
229
    def connect_sftp(self, username, password, host, port):
142
230
        """Make an SSH connection, and return an SFTPClient.
143
 
        
 
231
 
144
232
        :param username: an ascii string
145
233
        :param password: an ascii string
146
234
        :param host: a host name as an ascii string
155
243
 
156
244
    def connect_ssh(self, username, password, host, port, command):
157
245
        """Make an SSH connection.
158
 
        
159
 
        :returns: something with a `close` method, and a `get_filelike_channels`
160
 
            method that returns a pair of (read, write) filelike objects.
 
246
 
 
247
        :returns: an SSHConnection.
161
248
        """
162
249
        raise NotImplementedError(self.connect_ssh)
163
 
        
 
250
 
 
251
    def _raise_connection_error(self, host, port=None, orig_error=None,
 
252
                                msg='Unable to connect to SSH host'):
 
253
        """Raise a SocketConnectionError with properly formatted host.
 
254
 
 
255
        This just unifies all the locations that try to raise ConnectionError,
 
256
        so that they format things properly.
 
257
        """
 
258
        raise errors.SocketConnectionError(host=host, port=port, msg=msg,
 
259
                                           orig_error=orig_error)
 
260
 
164
261
 
165
262
class LoopbackVendor(SSHVendor):
166
263
    """SSH "vendor" that connects over a plain TCP socket, not SSH."""
167
 
    
 
264
 
168
265
    def connect_sftp(self, username, password, host, port):
169
266
        sock = socket.socket()
170
267
        try:
171
268
            sock.connect((host, port))
172
269
        except socket.error, e:
173
 
            raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
174
 
                                  % (host, port, e))
175
 
        return SFTPClient(LoopbackSFTP(sock))
 
270
            self._raise_connection_error(host, port=port, orig_error=e)
 
271
        return SFTPClient(SocketAsChannelAdapter(sock))
176
272
 
177
273
register_ssh_vendor('loopback', LoopbackVendor())
178
274
 
179
275
 
180
 
class _ParamikoSSHConnection(object):
181
 
    def __init__(self, channel):
182
 
        self.channel = channel
183
 
 
184
 
    def get_filelike_channels(self):
185
 
        return self.channel.makefile('rb'), self.channel.makefile('wb')
186
 
 
187
 
    def close(self):
188
 
        return self.channel.close()
189
 
 
190
 
 
191
276
class ParamikoVendor(SSHVendor):
192
277
    """Vendor that uses paramiko."""
193
278
 
194
279
    def _connect(self, username, password, host, port):
195
280
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
196
 
        
 
281
 
197
282
        load_host_keys()
198
283
 
199
284
        try:
201
286
            t.set_log_channel('bzr.paramiko')
202
287
            t.start_client()
203
288
        except (paramiko.SSHException, socket.error), e:
204
 
            raise ConnectionError('Unable to reach SSH host %s:%s: %s' 
205
 
                                  % (host, port, e))
206
 
            
 
289
            self._raise_connection_error(host, port=port, orig_error=e)
 
290
 
207
291
        server_key = t.get_remote_server_key()
208
292
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
209
293
        keytype = server_key.get_name()
210
294
        if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
211
295
            our_server_key = SYSTEM_HOSTKEYS[host][keytype]
212
 
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
 
296
            our_server_key_hex = paramiko.util.hexify(
 
297
                our_server_key.get_fingerprint())
213
298
        elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
214
299
            our_server_key = BZR_HOSTKEYS[host][keytype]
215
 
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
 
300
            our_server_key_hex = paramiko.util.hexify(
 
301
                our_server_key.get_fingerprint())
216
302
        else:
217
 
            warning('Adding %s host key for %s: %s' % (keytype, host, server_key_hex))
218
 
            if host not in BZR_HOSTKEYS:
219
 
                BZR_HOSTKEYS[host] = {}
220
 
            BZR_HOSTKEYS[host][keytype] = server_key
 
303
            trace.warning('Adding %s host key for %s: %s'
 
304
                          % (keytype, host, server_key_hex))
 
305
            add = getattr(BZR_HOSTKEYS, 'add', None)
 
306
            if add is not None: # paramiko >= 1.X.X
 
307
                BZR_HOSTKEYS.add(host, keytype, server_key)
 
308
            else:
 
309
                BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
221
310
            our_server_key = server_key
222
 
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
 
311
            our_server_key_hex = paramiko.util.hexify(
 
312
                our_server_key.get_fingerprint())
223
313
            save_host_keys()
224
314
        if server_key != our_server_key:
225
315
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
226
 
            filename2 = pathjoin(config_dir(), 'ssh_host_keys')
227
 
            raise TransportError('Host keys for %s do not match!  %s != %s' % \
 
316
            filename2 = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
 
317
            raise errors.TransportError(
 
318
                'Host keys for %s do not match!  %s != %s' %
228
319
                (host, our_server_key_hex, server_key_hex),
229
320
                ['Try editing %s or %s' % (filename1, filename2)])
230
321
 
231
 
        _paramiko_auth(username, password, host, t)
 
322
        _paramiko_auth(username, password, host, port, t)
232
323
        return t
233
 
        
 
324
 
234
325
    def connect_sftp(self, username, password, host, port):
235
326
        t = self._connect(username, password, host, port)
236
327
        try:
237
328
            return t.open_sftp_client()
238
329
        except paramiko.SSHException, e:
239
 
            raise ConnectionError('Unable to start sftp client %s:%d' %
240
 
                                  (host, port), e)
 
330
            self._raise_connection_error(host, port=port, orig_error=e,
 
331
                                         msg='Unable to start sftp client')
241
332
 
242
333
    def connect_ssh(self, username, password, host, port, command):
243
334
        t = self._connect(username, password, host, port)
247
338
            channel.exec_command(cmdline)
248
339
            return _ParamikoSSHConnection(channel)
249
340
        except paramiko.SSHException, e:
250
 
            raise ConnectionError('Unable to invoke remote bzr %s:%d' %
251
 
                                  (host, port), e)
 
341
            self._raise_connection_error(host, port=port, orig_error=e,
 
342
                                         msg='Unable to invoke remote bzr')
252
343
 
253
 
register_ssh_vendor('paramiko', ParamikoVendor())
 
344
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
 
345
if paramiko is not None:
 
346
    vendor = ParamikoVendor()
 
347
    register_ssh_vendor('paramiko', vendor)
 
348
    register_ssh_vendor('none', vendor)
 
349
    register_default_ssh_vendor(vendor)
 
350
    _ssh_connection_errors += (paramiko.SSHException,)
 
351
    del vendor
254
352
 
255
353
 
256
354
class SubprocessVendor(SSHVendor):
257
355
    """Abstract base class for vendors that use pipes to a subprocess."""
258
 
    
 
356
 
 
357
    # In general stderr should be inherited from the parent process so prompts
 
358
    # are visible on the terminal. This can be overriden to another file for
 
359
    # tests, but beware of using PIPE which may hang due to not being read.
 
360
    _stderr_target = None
 
361
 
259
362
    def _connect(self, argv):
260
 
        proc = subprocess.Popen(argv,
261
 
                                stdin=subprocess.PIPE,
262
 
                                stdout=subprocess.PIPE,
 
363
        # Attempt to make a socketpair to use as stdin/stdout for the SSH
 
364
        # subprocess.  We prefer sockets to pipes because they support
 
365
        # non-blocking short reads, allowing us to optimistically read 64k (or
 
366
        # whatever) chunks.
 
367
        try:
 
368
            my_sock, subproc_sock = socket.socketpair()
 
369
            osutils.set_fd_cloexec(my_sock)
 
370
        except (AttributeError, socket.error):
 
371
            # This platform doesn't support socketpair(), so just use ordinary
 
372
            # pipes instead.
 
373
            stdin = stdout = subprocess.PIPE
 
374
            my_sock, subproc_sock = None, None
 
375
        else:
 
376
            stdin = stdout = subproc_sock
 
377
        proc = subprocess.Popen(argv, stdin=stdin, stdout=stdout,
 
378
                                stderr=self._stderr_target,
263
379
                                **os_specific_subprocess_params())
264
 
        return SSHSubprocess(proc)
 
380
        if subproc_sock is not None:
 
381
            subproc_sock.close()
 
382
        return SSHSubprocessConnection(proc, sock=my_sock)
265
383
 
266
384
    def connect_sftp(self, username, password, host, port):
267
385
        try:
268
386
            argv = self._get_vendor_specific_argv(username, host, port,
269
387
                                                  subsystem='sftp')
270
388
            sock = self._connect(argv)
271
 
            return SFTPClient(sock)
272
 
        except (EOFError, paramiko.SSHException), e:
273
 
            raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
274
 
                                  % (host, port, e))
275
 
        except (OSError, IOError), e:
276
 
            # If the machine is fast enough, ssh can actually exit
277
 
            # before we try and send it the sftp request, which
278
 
            # raises a Broken Pipe
279
 
            if e.errno not in (errno.EPIPE,):
280
 
                raise
281
 
            raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
282
 
                                  % (host, port, e))
 
389
            return SFTPClient(SocketAsChannelAdapter(sock))
 
390
        except _ssh_connection_errors, e:
 
391
            self._raise_connection_error(host, port=port, orig_error=e)
283
392
 
284
393
    def connect_ssh(self, username, password, host, port, command):
285
394
        try:
286
395
            argv = self._get_vendor_specific_argv(username, host, port,
287
396
                                                  command=command)
288
397
            return self._connect(argv)
289
 
        except (EOFError), e:
290
 
            raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
291
 
                                  % (host, port, e))
292
 
        except (OSError, IOError), e:
293
 
            # If the machine is fast enough, ssh can actually exit
294
 
            # before we try and send it the sftp request, which
295
 
            # raises a Broken Pipe
296
 
            if e.errno not in (errno.EPIPE,):
297
 
                raise
298
 
            raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
299
 
                                  % (host, port, e))
 
398
        except _ssh_connection_errors, e:
 
399
            self._raise_connection_error(host, port=port, orig_error=e)
300
400
 
301
401
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
302
402
                                  command=None):
303
403
        """Returns the argument list to run the subprocess with.
304
 
        
 
404
 
305
405
        Exactly one of 'subsystem' and 'command' must be specified.
306
406
        """
307
407
        raise NotImplementedError(self._get_vendor_specific_argv)
308
408
 
309
 
register_ssh_vendor('none', ParamikoVendor())
310
 
 
311
409
 
312
410
class OpenSSHSubprocessVendor(SubprocessVendor):
313
411
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
314
 
    
 
412
 
 
413
    executable_path = 'ssh'
 
414
 
315
415
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
316
416
                                  command=None):
317
 
        assert subsystem is not None or command is not None, (
318
 
            'Must specify a command or subsystem')
319
 
        if subsystem is not None:
320
 
            assert command is None, (
321
 
                'subsystem and command are mutually exclusive')
322
 
        args = ['ssh',
 
417
        args = [self.executable_path,
323
418
                '-oForwardX11=no', '-oForwardAgent=no',
324
 
                '-oClearAllForwardings=yes', '-oProtocol=2',
 
419
                '-oClearAllForwardings=yes',
325
420
                '-oNoHostAuthenticationForLocalhost=yes']
326
421
        if port is not None:
327
422
            args.extend(['-p', str(port)])
339
434
class SSHCorpSubprocessVendor(SubprocessVendor):
340
435
    """SSH vendor that uses the 'ssh' executable from SSH Corporation."""
341
436
 
 
437
    executable_path = 'ssh'
 
438
 
342
439
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
343
440
                                  command=None):
344
 
        assert subsystem is not None or command is not None, (
345
 
            'Must specify a command or subsystem')
346
 
        if subsystem is not None:
347
 
            assert command is None, (
348
 
                'subsystem and command are mutually exclusive')
349
 
        args = ['ssh', '-x']
 
441
        args = [self.executable_path, '-x']
350
442
        if port is not None:
351
443
            args.extend(['-p', str(port)])
352
444
        if username is not None:
356
448
        else:
357
449
            args.extend([host] + command)
358
450
        return args
359
 
    
360
 
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
361
 
 
362
 
 
363
 
def _paramiko_auth(username, password, host, paramiko_transport):
364
 
    # paramiko requires a username, but it might be none if nothing was supplied
365
 
    # use the local username, just in case.
366
 
    # We don't override username, because if we aren't using paramiko,
367
 
    # the username might be specified in ~/.ssh/config and we don't want to
368
 
    # force it to something else
369
 
    # Also, it would mess up the self.relpath() functionality
370
 
    username = username or getpass.getuser()
371
 
 
 
451
 
 
452
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
 
453
 
 
454
 
 
455
class LSHSubprocessVendor(SubprocessVendor):
 
456
    """SSH vendor that uses the 'lsh' executable from GNU"""
 
457
 
 
458
    executable_path = 'lsh'
 
459
 
 
460
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
 
461
                                  command=None):
 
462
        args = [self.executable_path]
 
463
        if port is not None:
 
464
            args.extend(['-p', str(port)])
 
465
        if username is not None:
 
466
            args.extend(['-l', username])
 
467
        if subsystem is not None:
 
468
            args.extend(['--subsystem', subsystem, host])
 
469
        else:
 
470
            args.extend([host] + command)
 
471
        return args
 
472
 
 
473
register_ssh_vendor('lsh', LSHSubprocessVendor())
 
474
 
 
475
 
 
476
class PLinkSubprocessVendor(SubprocessVendor):
 
477
    """SSH vendor that uses the 'plink' executable from Putty."""
 
478
 
 
479
    executable_path = 'plink'
 
480
 
 
481
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
 
482
                                  command=None):
 
483
        args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
 
484
        if port is not None:
 
485
            args.extend(['-P', str(port)])
 
486
        if username is not None:
 
487
            args.extend(['-l', username])
 
488
        if subsystem is not None:
 
489
            args.extend(['-s', host, subsystem])
 
490
        else:
 
491
            args.extend([host] + command)
 
492
        return args
 
493
 
 
494
register_ssh_vendor('plink', PLinkSubprocessVendor())
 
495
 
 
496
 
 
497
def _paramiko_auth(username, password, host, port, paramiko_transport):
 
498
    auth = config.AuthenticationConfig()
 
499
    # paramiko requires a username, but it might be none if nothing was
 
500
    # supplied.  If so, use the local username.
 
501
    if username is None:
 
502
        username = auth.get_user('ssh', host, port=port,
 
503
                                 default=getpass.getuser())
372
504
    if _use_ssh_agent:
373
505
        agent = paramiko.Agent()
374
506
        for key in agent.get_keys():
375
 
            mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
 
507
            trace.mutter('Trying SSH agent key %s'
 
508
                         % paramiko.util.hexify(key.get_fingerprint()))
376
509
            try:
377
510
                paramiko_transport.auth_publickey(username, key)
378
511
                return
379
512
            except paramiko.SSHException, e:
380
513
                pass
381
 
    
 
514
 
382
515
    # okay, try finding id_rsa or id_dss?  (posix only)
383
516
    if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
384
517
        return
385
518
    if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
386
519
        return
387
520
 
 
521
    # If we have gotten this far, we are about to try for passwords, do an
 
522
    # auth_none check to see if it is even supported.
 
523
    supported_auth_types = []
 
524
    try:
 
525
        # Note that with paramiko <1.7.5 this logs an INFO message:
 
526
        #    Authentication type (none) not permitted.
 
527
        # So we explicitly disable the logging level for this action
 
528
        old_level = paramiko_transport.logger.level
 
529
        paramiko_transport.logger.setLevel(logging.WARNING)
 
530
        try:
 
531
            paramiko_transport.auth_none(username)
 
532
        finally:
 
533
            paramiko_transport.logger.setLevel(old_level)
 
534
    except paramiko.BadAuthenticationType, e:
 
535
        # Supported methods are in the exception
 
536
        supported_auth_types = e.allowed_types
 
537
    except paramiko.SSHException, e:
 
538
        # Don't know what happened, but just ignore it
 
539
        pass
 
540
    # We treat 'keyboard-interactive' and 'password' auth methods identically,
 
541
    # because Paramiko's auth_password method will automatically try
 
542
    # 'keyboard-interactive' auth (using the password as the response) if
 
543
    # 'password' auth is not available.  Apparently some Debian and Gentoo
 
544
    # OpenSSH servers require this.
 
545
    # XXX: It's possible for a server to require keyboard-interactive auth that
 
546
    # requires something other than a single password, but we currently don't
 
547
    # support that.
 
548
    if ('password' not in supported_auth_types and
 
549
        'keyboard-interactive' not in supported_auth_types):
 
550
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
 
551
            '\n  %s@%s\nsupported auth types: %s'
 
552
            % (username, host, supported_auth_types))
 
553
 
388
554
    if password:
389
555
        try:
390
556
            paramiko_transport.auth_password(username, password)
393
559
            pass
394
560
 
395
561
    # give up and ask for a password
396
 
    password = bzrlib.ui.ui_factory.get_password(
397
 
            prompt='SSH %(user)s@%(host)s password',
398
 
            user=username, host=host)
399
 
    try:
400
 
        paramiko_transport.auth_password(username, password)
401
 
    except paramiko.SSHException, e:
402
 
        raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
403
 
                              (username, host), e)
 
562
    password = auth.get_password('ssh', host, username, port=port)
 
563
    # get_password can still return None, which means we should not prompt
 
564
    if password is not None:
 
565
        try:
 
566
            paramiko_transport.auth_password(username, password)
 
567
        except paramiko.SSHException, e:
 
568
            raise errors.ConnectionError(
 
569
                'Unable to authenticate to SSH host as'
 
570
                '\n  %s@%s\n' % (username, host), e)
 
571
    else:
 
572
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
 
573
                                     '  %s@%s' % (username, host))
404
574
 
405
575
 
406
576
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
410
580
        paramiko_transport.auth_publickey(username, key)
411
581
        return True
412
582
    except paramiko.PasswordRequiredException:
413
 
        password = bzrlib.ui.ui_factory.get_password(
414
 
                prompt='SSH %(filename)s password',
415
 
                filename=filename)
 
583
        password = ui.ui_factory.get_password(
 
584
            prompt=u'SSH %(filename)s password',
 
585
            filename=filename.decode(osutils._fs_enc))
416
586
        try:
417
587
            key = pkey_class.from_private_key_file(filename, password)
418
588
            paramiko_transport.auth_publickey(username, key)
419
589
            return True
420
590
        except paramiko.SSHException:
421
 
            mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
 
591
            trace.mutter('SSH authentication via %s key failed.'
 
592
                         % (os.path.basename(filename),))
422
593
    except paramiko.SSHException:
423
 
        mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
 
594
        trace.mutter('SSH authentication via %s key failed.'
 
595
                     % (os.path.basename(filename),))
424
596
    except IOError:
425
597
        pass
426
598
    return False
433
605
    """
434
606
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
435
607
    try:
436
 
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
437
 
    except Exception, e:
438
 
        mutter('failed to load system host keys: ' + str(e))
439
 
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
 
608
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(
 
609
            os.path.expanduser('~/.ssh/known_hosts'))
 
610
    except IOError, e:
 
611
        trace.mutter('failed to load system host keys: ' + str(e))
 
612
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
440
613
    try:
441
614
        BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
442
 
    except Exception, e:
443
 
        mutter('failed to load bzr host keys: ' + str(e))
 
615
    except IOError, e:
 
616
        trace.mutter('failed to load bzr host keys: ' + str(e))
444
617
        save_host_keys()
445
618
 
446
619
 
449
622
    Save "discovered" host keys in $(config)/ssh_host_keys/.
450
623
    """
451
624
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
452
 
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
453
 
    ensure_config_dir_exists()
 
625
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
 
626
    config.ensure_config_dir_exists()
454
627
 
455
628
    try:
456
629
        f = open(bzr_hostkey_path, 'w')
460
633
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
461
634
        f.close()
462
635
    except IOError, e:
463
 
        mutter('failed to save bzr host keys: ' + str(e))
 
636
        trace.mutter('failed to save bzr host keys: ' + str(e))
464
637
 
465
638
 
466
639
def os_specific_subprocess_params():
467
640
    """Get O/S specific subprocess parameters."""
468
641
    if sys.platform == 'win32':
469
 
        # setting the process group and closing fds is not supported on 
 
642
        # setting the process group and closing fds is not supported on
470
643
        # win32
471
644
        return {}
472
645
    else:
473
 
        # We close fds other than the pipes as the child process does not need 
 
646
        # We close fds other than the pipes as the child process does not need
474
647
        # them to be open.
475
648
        #
476
649
        # We also set the child process to ignore SIGINT.  Normally the signal
478
651
        # this causes it to be seen only by bzr and not by ssh.  Python will
479
652
        # generate a KeyboardInterrupt in bzr, and we will then have a chance
480
653
        # to release locks or do other cleanup over ssh before the connection
481
 
        # goes away.  
 
654
        # goes away.
482
655
        # <https://launchpad.net/products/bzr/+bug/5987>
483
656
        #
484
657
        # Running it in a separate process group is not good because then it
485
658
        # can't get non-echoed input of a password or passphrase.
486
659
        # <https://launchpad.net/products/bzr/+bug/40508>
487
 
        return {'preexec_fn': _ignore_sigint,
 
660
        return {'preexec_fn': _ignore_signals,
488
661
                'close_fds': True,
489
662
                }
490
663
 
491
 
 
492
 
class SSHSubprocess(object):
493
 
    """A socket-like object that talks to an ssh subprocess via pipes."""
494
 
 
495
 
    def __init__(self, proc):
 
664
import weakref
 
665
_subproc_weakrefs = set()
 
666
 
 
667
def _close_ssh_proc(proc, sock):
 
668
    """Carefully close stdin/stdout and reap the SSH process.
 
669
 
 
670
    If the pipes are already closed and/or the process has already been
 
671
    wait()ed on, that's ok, and no error is raised.  The goal is to do our best
 
672
    to clean up (whether or not a clean up was already tried).
 
673
    """
 
674
    funcs = []
 
675
    for closeable in (proc.stdin, proc.stdout, sock):
 
676
        # We expect that either proc (a subprocess.Popen) will have stdin and
 
677
        # stdout streams to close, or that we will have been passed a socket to
 
678
        # close, with the option not in use being None.
 
679
        if closeable is not None:
 
680
            funcs.append(closeable.close)
 
681
    funcs.append(proc.wait)
 
682
    for func in funcs:
 
683
        try:
 
684
            func()
 
685
        except OSError:
 
686
            # It's ok for the pipe to already be closed, or the process to
 
687
            # already be finished.
 
688
            continue
 
689
 
 
690
 
 
691
class SSHConnection(object):
 
692
    """Abstract base class for SSH connections."""
 
693
 
 
694
    def get_sock_or_pipes(self):
 
695
        """Returns a (kind, io_object) pair.
 
696
 
 
697
        If kind == 'socket', then io_object is a socket.
 
698
 
 
699
        If kind == 'pipes', then io_object is a pair of file-like objects
 
700
        (read_from, write_to).
 
701
        """
 
702
        raise NotImplementedError(self.get_sock_or_pipes)
 
703
 
 
704
    def close(self):
 
705
        raise NotImplementedError(self.close)
 
706
 
 
707
 
 
708
class SSHSubprocessConnection(SSHConnection):
 
709
    """A connection to an ssh subprocess via pipes or a socket.
 
710
 
 
711
    This class is also socket-like enough to be used with
 
712
    SocketAsChannelAdapter (it has 'send' and 'recv' methods).
 
713
    """
 
714
 
 
715
    def __init__(self, proc, sock=None):
 
716
        """Constructor.
 
717
 
 
718
        :param proc: a subprocess.Popen
 
719
        :param sock: if proc.stdin/out is a socket from a socketpair, then sock
 
720
            should bzrlib's half of that socketpair.  If not passed, proc's
 
721
            stdin/out is assumed to be ordinary pipes.
 
722
        """
496
723
        self.proc = proc
 
724
        self._sock = sock
 
725
        # Add a weakref to proc that will attempt to do the same as self.close
 
726
        # to avoid leaving processes lingering indefinitely.
 
727
        def terminate(ref):
 
728
            _subproc_weakrefs.remove(ref)
 
729
            _close_ssh_proc(proc, sock)
 
730
        _subproc_weakrefs.add(weakref.ref(self, terminate))
497
731
 
498
732
    def send(self, data):
499
 
        return os.write(self.proc.stdin.fileno(), data)
500
 
 
501
 
    def recv_ready(self):
502
 
        # TODO: jam 20051215 this function is necessary to support the
503
 
        # pipelined() function. In reality, it probably should use
504
 
        # poll() or select() to actually return if there is data
505
 
        # available, otherwise we probably don't get any benefit
506
 
        return True
 
733
        if self._sock is not None:
 
734
            return self._sock.send(data)
 
735
        else:
 
736
            return os.write(self.proc.stdin.fileno(), data)
507
737
 
508
738
    def recv(self, count):
509
 
        return os.read(self.proc.stdout.fileno(), count)
510
 
 
511
 
    def close(self):
512
 
        self.proc.stdin.close()
513
 
        self.proc.stdout.close()
514
 
        self.proc.wait()
515
 
 
516
 
    def get_filelike_channels(self):
517
 
        return (self.proc.stdout, self.proc.stdin)
 
739
        if self._sock is not None:
 
740
            return self._sock.recv(count)
 
741
        else:
 
742
            return os.read(self.proc.stdout.fileno(), count)
 
743
 
 
744
    def close(self):
 
745
        _close_ssh_proc(self.proc, self._sock)
 
746
 
 
747
    def get_sock_or_pipes(self):
 
748
        if self._sock is not None:
 
749
            return 'socket', self._sock
 
750
        else:
 
751
            return 'pipes', (self.proc.stdout, self.proc.stdin)
 
752
 
 
753
 
 
754
class _ParamikoSSHConnection(SSHConnection):
 
755
    """An SSH connection via paramiko."""
 
756
 
 
757
    def __init__(self, channel):
 
758
        self.channel = channel
 
759
 
 
760
    def get_sock_or_pipes(self):
 
761
        return ('socket', self.channel)
 
762
 
 
763
    def close(self):
 
764
        return self.channel.close()
 
765
 
518
766