~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-10-06 16:32:42 UTC
  • mfrom: (6597.2.4 split-diff-tests)
  • Revision ID: pqm@pqm.ubuntu.com-20141006163242-c2cll01cwc24grkk
(vila) Split some tests to be able to get finer grained failures (Vincent
 Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Robey Pointer <robey@lag.net>
 
1
# Copyright (C) 2006-2011 Robey Pointer <robey@lag.net>
2
2
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
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
 
                           SocketConnectionError,
31
 
                           SSHVendorNotFound,
32
 
                           TransportError,
33
 
                           UnknownSSH,
34
 
                           )
35
 
 
36
 
from bzrlib.osutils import pathjoin
37
 
from bzrlib.trace import mutter, warning
38
 
import bzrlib.ui
 
30
from bzrlib import (
 
31
    config,
 
32
    errors,
 
33
    osutils,
 
34
    trace,
 
35
    ui,
 
36
    )
39
37
 
40
38
try:
41
39
    import paramiko
98
96
            try:
99
97
                vendor = self._ssh_vendors[vendor_name]
100
98
            except KeyError:
101
 
                raise UnknownSSH(vendor_name)
 
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
102
103
            return vendor
103
104
        return None
104
105
 
114
115
            stdout = stderr = ''
115
116
        return stdout + stderr
116
117
 
117
 
    def _get_vendor_by_version_string(self, version):
 
118
    def _get_vendor_by_version_string(self, version, progname):
118
119
        """Return the vendor or None based on output from the subprocess.
119
120
 
120
121
        :param version: The output of 'ssh -V' like command.
 
122
        :param args: Command line that was run.
121
123
        """
122
124
        vendor = None
123
125
        if 'OpenSSH' in version:
124
 
            mutter('ssh implementation is OpenSSH')
 
126
            trace.mutter('ssh implementation is OpenSSH')
125
127
            vendor = OpenSSHSubprocessVendor()
126
128
        elif 'SSH Secure Shell' in version:
127
 
            mutter('ssh implementation is SSH Corp.')
 
129
            trace.mutter('ssh implementation is SSH Corp.')
128
130
            vendor = SSHCorpSubprocessVendor()
129
 
        elif 'plink' in version:
130
 
            mutter("ssh implementation is Putty's plink.")
 
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.")
131
142
            vendor = PLinkSubprocessVendor()
132
143
        return vendor
133
144
 
134
145
    def _get_vendor_by_inspection(self):
135
146
        """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
 
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])
142
155
 
143
156
    def get_vendor(self, environment=None):
144
157
        """Find out what version of SSH is on the system.
152
165
            if vendor is None:
153
166
                vendor = self._get_vendor_by_inspection()
154
167
                if vendor is None:
155
 
                    mutter('falling back to default implementation')
 
168
                    trace.mutter('falling back to default implementation')
156
169
                    vendor = self._default_ssh_vendor
157
170
                    if vendor is None:
158
 
                        raise SSHVendorNotFound()
 
171
                        raise errors.SSHVendorNotFound()
159
172
            self._cached_ssh_vendor = vendor
160
173
        return self._cached_ssh_vendor
161
174
 
165
178
register_ssh_vendor = _ssh_vendor_manager.register_vendor
166
179
 
167
180
 
168
 
def _ignore_sigint():
 
181
def _ignore_signals():
169
182
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
170
183
    # doesn't handle it itself.
171
184
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
172
185
    import signal
173
186
    signal.signal(signal.SIGINT, signal.SIG_IGN)
174
 
 
175
 
 
176
 
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):
177
193
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
178
194
 
179
195
    def __init__(self, sock):
180
196
        self.__socket = sock
181
 
 
 
197
 
 
198
    def get_name(self):
 
199
        return "bzr SocketAsChannelAdapter"
 
200
 
182
201
    def send(self, data):
183
202
        return self.__socket.send(data)
184
203
 
185
204
    def recv(self, n):
186
 
        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
187
214
 
188
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
189
220
        return True
190
221
 
191
222
    def close(self):
194
225
 
195
226
class SSHVendor(object):
196
227
    """Abstract base class for SSH vendor implementations."""
197
 
    
 
228
 
198
229
    def connect_sftp(self, username, password, host, port):
199
230
        """Make an SSH connection, and return an SFTPClient.
200
 
        
 
231
 
201
232
        :param username: an ascii string
202
233
        :param password: an ascii string
203
234
        :param host: a host name as an ascii string
212
243
 
213
244
    def connect_ssh(self, username, password, host, port, command):
214
245
        """Make an SSH connection.
215
 
        
216
 
        :returns: something with a `close` method, and a `get_filelike_channels`
217
 
            method that returns a pair of (read, write) filelike objects.
 
246
 
 
247
        :returns: an SSHConnection.
218
248
        """
219
249
        raise NotImplementedError(self.connect_ssh)
220
 
        
 
250
 
221
251
    def _raise_connection_error(self, host, port=None, orig_error=None,
222
252
                                msg='Unable to connect to SSH host'):
223
253
        """Raise a SocketConnectionError with properly formatted host.
225
255
        This just unifies all the locations that try to raise ConnectionError,
226
256
        so that they format things properly.
227
257
        """
228
 
        raise SocketConnectionError(host=host, port=port, msg=msg,
229
 
                                    orig_error=orig_error)
 
258
        raise errors.SocketConnectionError(host=host, port=port, msg=msg,
 
259
                                           orig_error=orig_error)
230
260
 
231
261
 
232
262
class LoopbackVendor(SSHVendor):
233
263
    """SSH "vendor" that connects over a plain TCP socket, not SSH."""
234
 
    
 
264
 
235
265
    def connect_sftp(self, username, password, host, port):
236
266
        sock = socket.socket()
237
267
        try:
238
268
            sock.connect((host, port))
239
269
        except socket.error, e:
240
270
            self._raise_connection_error(host, port=port, orig_error=e)
241
 
        return SFTPClient(LoopbackSFTP(sock))
 
271
        return SFTPClient(SocketAsChannelAdapter(sock))
242
272
 
243
273
register_ssh_vendor('loopback', LoopbackVendor())
244
274
 
245
275
 
246
 
class _ParamikoSSHConnection(object):
247
 
    def __init__(self, channel):
248
 
        self.channel = channel
249
 
 
250
 
    def get_filelike_channels(self):
251
 
        return self.channel.makefile('rb'), self.channel.makefile('wb')
252
 
 
253
 
    def close(self):
254
 
        return self.channel.close()
255
 
 
256
 
 
257
276
class ParamikoVendor(SSHVendor):
258
277
    """Vendor that uses paramiko."""
259
278
 
260
279
    def _connect(self, username, password, host, port):
261
280
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
262
 
        
 
281
 
263
282
        load_host_keys()
264
283
 
265
284
        try:
268
287
            t.start_client()
269
288
        except (paramiko.SSHException, socket.error), e:
270
289
            self._raise_connection_error(host, port=port, orig_error=e)
271
 
            
 
290
 
272
291
        server_key = t.get_remote_server_key()
273
292
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
274
293
        keytype = server_key.get_name()
275
294
        if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
276
295
            our_server_key = SYSTEM_HOSTKEYS[host][keytype]
277
 
            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())
278
298
        elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
279
299
            our_server_key = BZR_HOSTKEYS[host][keytype]
280
 
            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())
281
302
        else:
282
 
            warning('Adding %s host key for %s: %s' % (keytype, host, server_key_hex))
 
303
            trace.warning('Adding %s host key for %s: %s'
 
304
                          % (keytype, host, server_key_hex))
283
305
            add = getattr(BZR_HOSTKEYS, 'add', None)
284
306
            if add is not None: # paramiko >= 1.X.X
285
307
                BZR_HOSTKEYS.add(host, keytype, server_key)
286
308
            else:
287
309
                BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
288
310
            our_server_key = server_key
289
 
            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())
290
313
            save_host_keys()
291
314
        if server_key != our_server_key:
292
315
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
293
 
            filename2 = pathjoin(config_dir(), 'ssh_host_keys')
294
 
            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' %
295
319
                (host, our_server_key_hex, server_key_hex),
296
320
                ['Try editing %s or %s' % (filename1, filename2)])
297
321
 
298
 
        _paramiko_auth(username, password, host, t)
 
322
        _paramiko_auth(username, password, host, port, t)
299
323
        return t
300
 
        
 
324
 
301
325
    def connect_sftp(self, username, password, host, port):
302
326
        t = self._connect(username, password, host, port)
303
327
        try:
317
341
            self._raise_connection_error(host, port=port, orig_error=e,
318
342
                                         msg='Unable to invoke remote bzr')
319
343
 
 
344
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
320
345
if paramiko is not None:
321
346
    vendor = ParamikoVendor()
322
347
    register_ssh_vendor('paramiko', vendor)
323
348
    register_ssh_vendor('none', vendor)
324
349
    register_default_ssh_vendor(vendor)
 
350
    _ssh_connection_errors += (paramiko.SSHException,)
325
351
    del vendor
326
352
 
327
353
 
328
354
class SubprocessVendor(SSHVendor):
329
355
    """Abstract base class for vendors that use pipes to a subprocess."""
330
 
    
 
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
 
331
362
    def _connect(self, argv):
332
 
        proc = subprocess.Popen(argv,
333
 
                                stdin=subprocess.PIPE,
334
 
                                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,
335
379
                                **os_specific_subprocess_params())
336
 
        return SSHSubprocess(proc)
 
380
        if subproc_sock is not None:
 
381
            subproc_sock.close()
 
382
        return SSHSubprocessConnection(proc, sock=my_sock)
337
383
 
338
384
    def connect_sftp(self, username, password, host, port):
339
385
        try:
340
386
            argv = self._get_vendor_specific_argv(username, host, port,
341
387
                                                  subsystem='sftp')
342
388
            sock = self._connect(argv)
343
 
            return SFTPClient(sock)
344
 
        except (EOFError, paramiko.SSHException), e:
345
 
            self._raise_connection_error(host, port=port, orig_error=e)
346
 
        except (OSError, IOError), e:
347
 
            # If the machine is fast enough, ssh can actually exit
348
 
            # before we try and send it the sftp request, which
349
 
            # raises a Broken Pipe
350
 
            if e.errno not in (errno.EPIPE,):
351
 
                raise
 
389
            return SFTPClient(SocketAsChannelAdapter(sock))
 
390
        except _ssh_connection_errors, e:
352
391
            self._raise_connection_error(host, port=port, orig_error=e)
353
392
 
354
393
    def connect_ssh(self, username, password, host, port, command):
356
395
            argv = self._get_vendor_specific_argv(username, host, port,
357
396
                                                  command=command)
358
397
            return self._connect(argv)
359
 
        except (EOFError), e:
360
 
            self._raise_connection_error(host, port=port, orig_error=e)
361
 
        except (OSError, IOError), e:
362
 
            # If the machine is fast enough, ssh can actually exit
363
 
            # before we try and send it the sftp request, which
364
 
            # raises a Broken Pipe
365
 
            if e.errno not in (errno.EPIPE,):
366
 
                raise
 
398
        except _ssh_connection_errors, e:
367
399
            self._raise_connection_error(host, port=port, orig_error=e)
368
400
 
369
401
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
370
402
                                  command=None):
371
403
        """Returns the argument list to run the subprocess with.
372
 
        
 
404
 
373
405
        Exactly one of 'subsystem' and 'command' must be specified.
374
406
        """
375
407
        raise NotImplementedError(self._get_vendor_specific_argv)
377
409
 
378
410
class OpenSSHSubprocessVendor(SubprocessVendor):
379
411
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
380
 
    
 
412
 
 
413
    executable_path = 'ssh'
 
414
 
381
415
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
382
416
                                  command=None):
383
 
        assert subsystem is not None or command is not None, (
384
 
            'Must specify a command or subsystem')
385
 
        if subsystem is not None:
386
 
            assert command is None, (
387
 
                'subsystem and command are mutually exclusive')
388
 
        args = ['ssh',
 
417
        args = [self.executable_path,
389
418
                '-oForwardX11=no', '-oForwardAgent=no',
390
 
                '-oClearAllForwardings=yes', '-oProtocol=2',
 
419
                '-oClearAllForwardings=yes',
391
420
                '-oNoHostAuthenticationForLocalhost=yes']
392
421
        if port is not None:
393
422
            args.extend(['-p', str(port)])
405
434
class SSHCorpSubprocessVendor(SubprocessVendor):
406
435
    """SSH vendor that uses the 'ssh' executable from SSH Corporation."""
407
436
 
 
437
    executable_path = 'ssh'
 
438
 
408
439
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
409
440
                                  command=None):
410
 
        assert subsystem is not None or command is not None, (
411
 
            'Must specify a command or subsystem')
412
 
        if subsystem is not None:
413
 
            assert command is None, (
414
 
                'subsystem and command are mutually exclusive')
415
 
        args = ['ssh', '-x']
 
441
        args = [self.executable_path, '-x']
416
442
        if port is not None:
417
443
            args.extend(['-p', str(port)])
418
444
        if username is not None:
422
448
        else:
423
449
            args.extend([host] + command)
424
450
        return args
425
 
    
426
 
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
 
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())
427
474
 
428
475
 
429
476
class PLinkSubprocessVendor(SubprocessVendor):
430
477
    """SSH vendor that uses the 'plink' executable from Putty."""
431
478
 
 
479
    executable_path = 'plink'
 
480
 
432
481
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
433
482
                                  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']
 
483
        args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
440
484
        if port is not None:
441
485
            args.extend(['-P', str(port)])
442
486
        if username is not None:
450
494
register_ssh_vendor('plink', PLinkSubprocessVendor())
451
495
 
452
496
 
453
 
def _paramiko_auth(username, password, host, paramiko_transport):
454
 
    # paramiko requires a username, but it might be none if nothing was supplied
455
 
    # use the local username, just in case.
456
 
    # We don't override username, because if we aren't using paramiko,
457
 
    # the username might be specified in ~/.ssh/config and we don't want to
458
 
    # force it to something else
459
 
    # Also, it would mess up the self.relpath() functionality
460
 
    username = username or getpass.getuser()
461
 
 
 
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())
462
504
    if _use_ssh_agent:
463
505
        agent = paramiko.Agent()
464
506
        for key in agent.get_keys():
465
 
            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()))
466
509
            try:
467
510
                paramiko_transport.auth_publickey(username, key)
468
511
                return
469
512
            except paramiko.SSHException, e:
470
513
                pass
471
 
    
 
514
 
472
515
    # okay, try finding id_rsa or id_dss?  (posix only)
473
516
    if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
474
517
        return
475
518
    if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
476
519
        return
477
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
 
478
554
    if password:
479
555
        try:
480
556
            paramiko_transport.auth_password(username, password)
483
559
            pass
484
560
 
485
561
    # give up and ask for a password
486
 
    password = bzrlib.ui.ui_factory.get_password(
487
 
            prompt='SSH %(user)s@%(host)s password',
488
 
            user=username, host=host)
489
 
    try:
490
 
        paramiko_transport.auth_password(username, password)
491
 
    except paramiko.SSHException, e:
492
 
        raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
493
 
                              (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))
494
574
 
495
575
 
496
576
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
500
580
        paramiko_transport.auth_publickey(username, key)
501
581
        return True
502
582
    except paramiko.PasswordRequiredException:
503
 
        password = bzrlib.ui.ui_factory.get_password(
504
 
                prompt='SSH %(filename)s password',
505
 
                filename=filename)
 
583
        password = ui.ui_factory.get_password(
 
584
            prompt=u'SSH %(filename)s password',
 
585
            filename=filename.decode(osutils._fs_enc))
506
586
        try:
507
587
            key = pkey_class.from_private_key_file(filename, password)
508
588
            paramiko_transport.auth_publickey(username, key)
509
589
            return True
510
590
        except paramiko.SSHException:
511
 
            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),))
512
593
    except paramiko.SSHException:
513
 
        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),))
514
596
    except IOError:
515
597
        pass
516
598
    return False
523
605
    """
524
606
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
525
607
    try:
526
 
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
527
 
    except Exception, e:
528
 
        mutter('failed to load system host keys: ' + str(e))
529
 
    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')
530
613
    try:
531
614
        BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
532
 
    except Exception, e:
533
 
        mutter('failed to load bzr host keys: ' + str(e))
 
615
    except IOError, e:
 
616
        trace.mutter('failed to load bzr host keys: ' + str(e))
534
617
        save_host_keys()
535
618
 
536
619
 
539
622
    Save "discovered" host keys in $(config)/ssh_host_keys/.
540
623
    """
541
624
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
542
 
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
543
 
    ensure_config_dir_exists()
 
625
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
 
626
    config.ensure_config_dir_exists()
544
627
 
545
628
    try:
546
629
        f = open(bzr_hostkey_path, 'w')
550
633
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
551
634
        f.close()
552
635
    except IOError, e:
553
 
        mutter('failed to save bzr host keys: ' + str(e))
 
636
        trace.mutter('failed to save bzr host keys: ' + str(e))
554
637
 
555
638
 
556
639
def os_specific_subprocess_params():
557
640
    """Get O/S specific subprocess parameters."""
558
641
    if sys.platform == 'win32':
559
 
        # setting the process group and closing fds is not supported on 
 
642
        # setting the process group and closing fds is not supported on
560
643
        # win32
561
644
        return {}
562
645
    else:
563
 
        # 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
564
647
        # them to be open.
565
648
        #
566
649
        # We also set the child process to ignore SIGINT.  Normally the signal
568
651
        # this causes it to be seen only by bzr and not by ssh.  Python will
569
652
        # generate a KeyboardInterrupt in bzr, and we will then have a chance
570
653
        # to release locks or do other cleanup over ssh before the connection
571
 
        # goes away.  
 
654
        # goes away.
572
655
        # <https://launchpad.net/products/bzr/+bug/5987>
573
656
        #
574
657
        # Running it in a separate process group is not good because then it
575
658
        # can't get non-echoed input of a password or passphrase.
576
659
        # <https://launchpad.net/products/bzr/+bug/40508>
577
 
        return {'preexec_fn': _ignore_sigint,
 
660
        return {'preexec_fn': _ignore_signals,
578
661
                'close_fds': True,
579
662
                }
580
663
 
581
 
 
582
 
class SSHSubprocess(object):
583
 
    """A socket-like object that talks to an ssh subprocess via pipes."""
584
 
 
585
 
    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
        """
586
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))
587
731
 
588
732
    def send(self, data):
589
 
        return os.write(self.proc.stdin.fileno(), data)
590
 
 
591
 
    def recv_ready(self):
592
 
        # TODO: jam 20051215 this function is necessary to support the
593
 
        # pipelined() function. In reality, it probably should use
594
 
        # poll() or select() to actually return if there is data
595
 
        # available, otherwise we probably don't get any benefit
596
 
        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)
597
737
 
598
738
    def recv(self, count):
599
 
        return os.read(self.proc.stdout.fileno(), count)
600
 
 
601
 
    def close(self):
602
 
        self.proc.stdin.close()
603
 
        self.proc.stdout.close()
604
 
        self.proc.wait()
605
 
 
606
 
    def get_filelike_channels(self):
607
 
        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
 
608
766