~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-08-17 18:13:57 UTC
  • mfrom: (5268.7.29 transport-segments)
  • Revision ID: pqm@pqm.ubuntu.com-20110817181357-y5q5eth1hk8bl3om
(jelmer) Allow specifying the colocated branch to use in the branch URL,
 and retrieving the branch name using ControlDir._get_selected_branch.
 (Jelmer Vernooij)

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
20
import errno
21
21
import getpass
 
22
import logging
22
23
import os
23
24
import socket
24
25
import subprocess
93
94
            try:
94
95
                vendor = self._ssh_vendors[vendor_name]
95
96
            except KeyError:
96
 
                raise errors.UnknownSSH(vendor_name)
 
97
                vendor = self._get_vendor_from_path(vendor_name)
 
98
                if vendor is None:
 
99
                    raise errors.UnknownSSH(vendor_name)
 
100
                vendor.executable_path = vendor_name
97
101
            return vendor
98
102
        return None
99
103
 
109
113
            stdout = stderr = ''
110
114
        return stdout + stderr
111
115
 
112
 
    def _get_vendor_by_version_string(self, version, args):
 
116
    def _get_vendor_by_version_string(self, version, progname):
113
117
        """Return the vendor or None based on output from the subprocess.
114
118
 
115
119
        :param version: The output of 'ssh -V' like command.
122
126
        elif 'SSH Secure Shell' in version:
123
127
            trace.mutter('ssh implementation is SSH Corp.')
124
128
            vendor = SSHCorpSubprocessVendor()
125
 
        elif 'plink' in version and args[0] == 'plink':
 
129
        elif 'lsh' in version:
 
130
            trace.mutter('ssh implementation is GNU lsh.')
 
131
            vendor = LSHSubprocessVendor()
 
132
        # As plink user prompts are not handled currently, don't auto-detect
 
133
        # it by inspection below, but keep this vendor detection for if a path
 
134
        # is given in BZR_SSH. See https://bugs.launchpad.net/bugs/414743
 
135
        elif 'plink' in version and progname == 'plink':
126
136
            # Checking if "plink" was the executed argument as Windows
127
 
            # sometimes reports 'ssh -V' incorrectly with 'plink' in it's
 
137
            # sometimes reports 'ssh -V' incorrectly with 'plink' in its
128
138
            # version.  See https://bugs.launchpad.net/bzr/+bug/107155
129
139
            trace.mutter("ssh implementation is Putty's plink.")
130
140
            vendor = PLinkSubprocessVendor()
132
142
 
133
143
    def _get_vendor_by_inspection(self):
134
144
        """Return the vendor or None by checking for known SSH implementations."""
135
 
        for args in (['ssh', '-V'], ['plink', '-V']):
136
 
            version = self._get_ssh_version_string(args)
137
 
            vendor = self._get_vendor_by_version_string(version, args)
138
 
            if vendor is not None:
139
 
                return vendor
140
 
        return None
 
145
        version = self._get_ssh_version_string(['ssh', '-V'])
 
146
        return self._get_vendor_by_version_string(version, "ssh")
 
147
 
 
148
    def _get_vendor_from_path(self, path):
 
149
        """Return the vendor or None using the program at the given path"""
 
150
        version = self._get_ssh_version_string([path, '-V'])
 
151
        return self._get_vendor_by_version_string(version, 
 
152
            os.path.splitext(os.path.basename(path))[0])
141
153
 
142
154
    def get_vendor(self, environment=None):
143
155
        """Find out what version of SSH is on the system.
164
176
register_ssh_vendor = _ssh_vendor_manager.register_vendor
165
177
 
166
178
 
167
 
def _ignore_sigint():
 
179
def _ignore_signals():
168
180
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
169
181
    # doesn't handle it itself.
170
182
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
171
183
    import signal
172
184
    signal.signal(signal.SIGINT, signal.SIG_IGN)
 
185
    # GZ 2010-02-19: Perhaps make this check if breakin is installed instead
 
186
    if signal.getsignal(signal.SIGQUIT) != signal.SIG_DFL:
 
187
        signal.signal(signal.SIGQUIT, signal.SIG_IGN)
173
188
 
174
189
 
175
190
class SocketAsChannelAdapter(object):
180
195
 
181
196
    def get_name(self):
182
197
        return "bzr SocketAsChannelAdapter"
183
 
    
 
198
 
184
199
    def send(self, data):
185
200
        return self.__socket.send(data)
186
201
 
211
226
 
212
227
    def connect_sftp(self, username, password, host, port):
213
228
        """Make an SSH connection, and return an SFTPClient.
214
 
        
 
229
 
215
230
        :param username: an ascii string
216
231
        :param password: an ascii string
217
232
        :param host: a host name as an ascii string
226
241
 
227
242
    def connect_ssh(self, username, password, host, port, command):
228
243
        """Make an SSH connection.
229
 
        
230
 
        :returns: something with a `close` method, and a `get_filelike_channels`
231
 
            method that returns a pair of (read, write) filelike objects.
 
244
 
 
245
        :returns: an SSHConnection.
232
246
        """
233
247
        raise NotImplementedError(self.connect_ssh)
234
248
 
257
271
register_ssh_vendor('loopback', LoopbackVendor())
258
272
 
259
273
 
260
 
class _ParamikoSSHConnection(object):
261
 
    def __init__(self, channel):
262
 
        self.channel = channel
263
 
 
264
 
    def get_filelike_channels(self):
265
 
        return self.channel.makefile('rb'), self.channel.makefile('wb')
266
 
 
267
 
    def close(self):
268
 
        return self.channel.close()
269
 
 
270
 
 
271
274
class ParamikoVendor(SSHVendor):
272
275
    """Vendor that uses paramiko."""
273
276
 
336
339
            self._raise_connection_error(host, port=port, orig_error=e,
337
340
                                         msg='Unable to invoke remote bzr')
338
341
 
 
342
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
339
343
if paramiko is not None:
340
344
    vendor = ParamikoVendor()
341
345
    register_ssh_vendor('paramiko', vendor)
342
346
    register_ssh_vendor('none', vendor)
343
347
    register_default_ssh_vendor(vendor)
344
 
    _sftp_connection_errors = (EOFError, paramiko.SSHException)
 
348
    _ssh_connection_errors += (paramiko.SSHException,)
345
349
    del vendor
346
 
else:
347
 
    _sftp_connection_errors = (EOFError,)
348
350
 
349
351
 
350
352
class SubprocessVendor(SSHVendor):
351
353
    """Abstract base class for vendors that use pipes to a subprocess."""
352
354
 
353
355
    def _connect(self, argv):
354
 
        proc = subprocess.Popen(argv,
355
 
                                stdin=subprocess.PIPE,
356
 
                                stdout=subprocess.PIPE,
 
356
        # Attempt to make a socketpair to use as stdin/stdout for the SSH
 
357
        # subprocess.  We prefer sockets to pipes because they support
 
358
        # non-blocking short reads, allowing us to optimistically read 64k (or
 
359
        # whatever) chunks.
 
360
        try:
 
361
            my_sock, subproc_sock = socket.socketpair()
 
362
            osutils.set_fd_cloexec(my_sock)
 
363
        except (AttributeError, socket.error):
 
364
            # This platform doesn't support socketpair(), so just use ordinary
 
365
            # pipes instead.
 
366
            stdin = stdout = subprocess.PIPE
 
367
            my_sock, subproc_sock = None, None
 
368
        else:
 
369
            stdin = stdout = subproc_sock
 
370
        proc = subprocess.Popen(argv, stdin=stdin, stdout=stdout,
357
371
                                **os_specific_subprocess_params())
358
 
        return SSHSubprocess(proc)
 
372
        if subproc_sock is not None:
 
373
            subproc_sock.close()
 
374
        return SSHSubprocessConnection(proc, sock=my_sock)
359
375
 
360
376
    def connect_sftp(self, username, password, host, port):
361
377
        try:
363
379
                                                  subsystem='sftp')
364
380
            sock = self._connect(argv)
365
381
            return SFTPClient(SocketAsChannelAdapter(sock))
366
 
        except _sftp_connection_errors, e:
367
 
            self._raise_connection_error(host, port=port, orig_error=e)
368
 
        except (OSError, IOError), e:
369
 
            # If the machine is fast enough, ssh can actually exit
370
 
            # before we try and send it the sftp request, which
371
 
            # raises a Broken Pipe
372
 
            if e.errno not in (errno.EPIPE,):
373
 
                raise
 
382
        except _ssh_connection_errors, e:
374
383
            self._raise_connection_error(host, port=port, orig_error=e)
375
384
 
376
385
    def connect_ssh(self, username, password, host, port, command):
378
387
            argv = self._get_vendor_specific_argv(username, host, port,
379
388
                                                  command=command)
380
389
            return self._connect(argv)
381
 
        except (EOFError), e:
382
 
            self._raise_connection_error(host, port=port, orig_error=e)
383
 
        except (OSError, IOError), e:
384
 
            # If the machine is fast enough, ssh can actually exit
385
 
            # before we try and send it the sftp request, which
386
 
            # raises a Broken Pipe
387
 
            if e.errno not in (errno.EPIPE,):
388
 
                raise
 
390
        except _ssh_connection_errors, e:
389
391
            self._raise_connection_error(host, port=port, orig_error=e)
390
392
 
391
393
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
392
394
                                  command=None):
393
395
        """Returns the argument list to run the subprocess with.
394
 
        
 
396
 
395
397
        Exactly one of 'subsystem' and 'command' must be specified.
396
398
        """
397
399
        raise NotImplementedError(self._get_vendor_specific_argv)
400
402
class OpenSSHSubprocessVendor(SubprocessVendor):
401
403
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
402
404
 
 
405
    executable_path = 'ssh'
 
406
 
403
407
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
404
408
                                  command=None):
405
 
        args = ['ssh',
 
409
        args = [self.executable_path,
406
410
                '-oForwardX11=no', '-oForwardAgent=no',
407
 
                '-oClearAllForwardings=yes', '-oProtocol=2',
 
411
                '-oClearAllForwardings=yes',
408
412
                '-oNoHostAuthenticationForLocalhost=yes']
409
413
        if port is not None:
410
414
            args.extend(['-p', str(port)])
422
426
class SSHCorpSubprocessVendor(SubprocessVendor):
423
427
    """SSH vendor that uses the 'ssh' executable from SSH Corporation."""
424
428
 
 
429
    executable_path = 'ssh'
 
430
 
425
431
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
426
432
                                  command=None):
427
 
        args = ['ssh', '-x']
 
433
        args = [self.executable_path, '-x']
428
434
        if port is not None:
429
435
            args.extend(['-p', str(port)])
430
436
        if username is not None:
435
441
            args.extend([host] + command)
436
442
        return args
437
443
 
438
 
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
 
444
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
 
445
 
 
446
 
 
447
class LSHSubprocessVendor(SubprocessVendor):
 
448
    """SSH vendor that uses the 'lsh' executable from GNU"""
 
449
 
 
450
    executable_path = 'lsh'
 
451
 
 
452
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
 
453
                                  command=None):
 
454
        args = [self.executable_path]
 
455
        if port is not None:
 
456
            args.extend(['-p', str(port)])
 
457
        if username is not None:
 
458
            args.extend(['-l', username])
 
459
        if subsystem is not None:
 
460
            args.extend(['--subsystem', subsystem, host])
 
461
        else:
 
462
            args.extend([host] + command)
 
463
        return args
 
464
 
 
465
register_ssh_vendor('lsh', LSHSubprocessVendor())
439
466
 
440
467
 
441
468
class PLinkSubprocessVendor(SubprocessVendor):
442
469
    """SSH vendor that uses the 'plink' executable from Putty."""
443
470
 
 
471
    executable_path = 'plink'
 
472
 
444
473
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
445
474
                                  command=None):
446
 
        args = ['plink', '-x', '-a', '-ssh', '-2', '-batch']
 
475
        args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
447
476
        if port is not None:
448
477
            args.extend(['-P', str(port)])
449
478
        if username is not None:
458
487
 
459
488
 
460
489
def _paramiko_auth(username, password, host, port, paramiko_transport):
461
 
    # paramiko requires a username, but it might be none if nothing was supplied
462
 
    # use the local username, just in case.
463
 
    # We don't override username, because if we aren't using paramiko,
464
 
    # the username might be specified in ~/.ssh/config and we don't want to
465
 
    # force it to something else
466
 
    # Also, it would mess up the self.relpath() functionality
467
490
    auth = config.AuthenticationConfig()
 
491
    # paramiko requires a username, but it might be none if nothing was
 
492
    # supplied.  If so, use the local username.
468
493
    if username is None:
469
 
        username = auth.get_user('ssh', host, port=port)
470
 
        if username is None:
471
 
            # Default to local user
472
 
            username = getpass.getuser()
473
 
 
 
494
        username = auth.get_user('ssh', host, port=port,
 
495
                                 default=getpass.getuser())
474
496
    if _use_ssh_agent:
475
497
        agent = paramiko.Agent()
476
498
        for key in agent.get_keys():
488
510
    if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
489
511
        return
490
512
 
 
513
    # If we have gotten this far, we are about to try for passwords, do an
 
514
    # auth_none check to see if it is even supported.
 
515
    supported_auth_types = []
 
516
    try:
 
517
        # Note that with paramiko <1.7.5 this logs an INFO message:
 
518
        #    Authentication type (none) not permitted.
 
519
        # So we explicitly disable the logging level for this action
 
520
        old_level = paramiko_transport.logger.level
 
521
        paramiko_transport.logger.setLevel(logging.WARNING)
 
522
        try:
 
523
            paramiko_transport.auth_none(username)
 
524
        finally:
 
525
            paramiko_transport.logger.setLevel(old_level)
 
526
    except paramiko.BadAuthenticationType, e:
 
527
        # Supported methods are in the exception
 
528
        supported_auth_types = e.allowed_types
 
529
    except paramiko.SSHException, e:
 
530
        # Don't know what happened, but just ignore it
 
531
        pass
 
532
    # We treat 'keyboard-interactive' and 'password' auth methods identically,
 
533
    # because Paramiko's auth_password method will automatically try
 
534
    # 'keyboard-interactive' auth (using the password as the response) if
 
535
    # 'password' auth is not available.  Apparently some Debian and Gentoo
 
536
    # OpenSSH servers require this.
 
537
    # XXX: It's possible for a server to require keyboard-interactive auth that
 
538
    # requires something other than a single password, but we currently don't
 
539
    # support that.
 
540
    if ('password' not in supported_auth_types and
 
541
        'keyboard-interactive' not in supported_auth_types):
 
542
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
 
543
            '\n  %s@%s\nsupported auth types: %s'
 
544
            % (username, host, supported_auth_types))
 
545
 
491
546
    if password:
492
547
        try:
493
548
            paramiko_transport.auth_password(username, password)
497
552
 
498
553
    # give up and ask for a password
499
554
    password = auth.get_password('ssh', host, username, port=port)
500
 
    try:
501
 
        paramiko_transport.auth_password(username, password)
502
 
    except paramiko.SSHException, e:
503
 
        raise errors.ConnectionError(
504
 
            'Unable to authenticate to SSH host as %s@%s' % (username, host), e)
 
555
    # get_password can still return None, which means we should not prompt
 
556
    if password is not None:
 
557
        try:
 
558
            paramiko_transport.auth_password(username, password)
 
559
        except paramiko.SSHException, e:
 
560
            raise errors.ConnectionError(
 
561
                'Unable to authenticate to SSH host as'
 
562
                '\n  %s@%s\n' % (username, host), e)
 
563
    else:
 
564
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
 
565
                                     '  %s@%s' % (username, host))
505
566
 
506
567
 
507
568
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
512
573
        return True
513
574
    except paramiko.PasswordRequiredException:
514
575
        password = ui.ui_factory.get_password(
515
 
            prompt='SSH %(filename)s password', filename=filename)
 
576
            prompt=u'SSH %(filename)s password',
 
577
            filename=filename.decode(osutils._fs_enc))
516
578
        try:
517
579
            key = pkey_class.from_private_key_file(filename, password)
518
580
            paramiko_transport.auth_publickey(username, key)
569
631
def os_specific_subprocess_params():
570
632
    """Get O/S specific subprocess parameters."""
571
633
    if sys.platform == 'win32':
572
 
        # setting the process group and closing fds is not supported on 
 
634
        # setting the process group and closing fds is not supported on
573
635
        # win32
574
636
        return {}
575
637
    else:
576
 
        # We close fds other than the pipes as the child process does not need 
 
638
        # We close fds other than the pipes as the child process does not need
577
639
        # them to be open.
578
640
        #
579
641
        # We also set the child process to ignore SIGINT.  Normally the signal
581
643
        # this causes it to be seen only by bzr and not by ssh.  Python will
582
644
        # generate a KeyboardInterrupt in bzr, and we will then have a chance
583
645
        # to release locks or do other cleanup over ssh before the connection
584
 
        # goes away.  
 
646
        # goes away.
585
647
        # <https://launchpad.net/products/bzr/+bug/5987>
586
648
        #
587
649
        # Running it in a separate process group is not good because then it
588
650
        # can't get non-echoed input of a password or passphrase.
589
651
        # <https://launchpad.net/products/bzr/+bug/40508>
590
 
        return {'preexec_fn': _ignore_sigint,
 
652
        return {'preexec_fn': _ignore_signals,
591
653
                'close_fds': True,
592
654
                }
593
655
 
594
 
 
595
 
class SSHSubprocess(object):
596
 
    """A socket-like object that talks to an ssh subprocess via pipes."""
597
 
 
598
 
    def __init__(self, proc):
 
656
import weakref
 
657
_subproc_weakrefs = set()
 
658
 
 
659
def _close_ssh_proc(proc, sock):
 
660
    """Carefully close stdin/stdout and reap the SSH process.
 
661
 
 
662
    If the pipes are already closed and/or the process has already been
 
663
    wait()ed on, that's ok, and no error is raised.  The goal is to do our best
 
664
    to clean up (whether or not a clean up was already tried).
 
665
    """
 
666
    funcs = []
 
667
    for closeable in (proc.stdin, proc.stdout, sock):
 
668
        # We expect that either proc (a subprocess.Popen) will have stdin and
 
669
        # stdout streams to close, or that we will have been passed a socket to
 
670
        # close, with the option not in use being None.
 
671
        if closeable is not None:
 
672
            funcs.append(closeable.close)
 
673
    funcs.append(proc.wait)
 
674
    for func in funcs:
 
675
        try:
 
676
            func()
 
677
        except OSError:
 
678
            # It's ok for the pipe to already be closed, or the process to
 
679
            # already be finished.
 
680
            continue
 
681
 
 
682
 
 
683
class SSHConnection(object):
 
684
    """Abstract base class for SSH connections."""
 
685
 
 
686
    def get_sock_or_pipes(self):
 
687
        """Returns a (kind, io_object) pair.
 
688
 
 
689
        If kind == 'socket', then io_object is a socket.
 
690
 
 
691
        If kind == 'pipes', then io_object is a pair of file-like objects
 
692
        (read_from, write_to).
 
693
        """
 
694
        raise NotImplementedError(self.get_sock_or_pipes)
 
695
 
 
696
    def close(self):
 
697
        raise NotImplementedError(self.close)
 
698
 
 
699
 
 
700
class SSHSubprocessConnection(SSHConnection):
 
701
    """A connection to an ssh subprocess via pipes or a socket.
 
702
 
 
703
    This class is also socket-like enough to be used with
 
704
    SocketAsChannelAdapter (it has 'send' and 'recv' methods).
 
705
    """
 
706
 
 
707
    def __init__(self, proc, sock=None):
 
708
        """Constructor.
 
709
 
 
710
        :param proc: a subprocess.Popen
 
711
        :param sock: if proc.stdin/out is a socket from a socketpair, then sock
 
712
            should bzrlib's half of that socketpair.  If not passed, proc's
 
713
            stdin/out is assumed to be ordinary pipes.
 
714
        """
599
715
        self.proc = proc
 
716
        self._sock = sock
 
717
        # Add a weakref to proc that will attempt to do the same as self.close
 
718
        # to avoid leaving processes lingering indefinitely.
 
719
        def terminate(ref):
 
720
            _subproc_weakrefs.remove(ref)
 
721
            _close_ssh_proc(proc, sock)
 
722
        _subproc_weakrefs.add(weakref.ref(self, terminate))
600
723
 
601
724
    def send(self, data):
602
 
        return os.write(self.proc.stdin.fileno(), data)
 
725
        if self._sock is not None:
 
726
            return self._sock.send(data)
 
727
        else:
 
728
            return os.write(self.proc.stdin.fileno(), data)
603
729
 
604
730
    def recv(self, count):
605
 
        return os.read(self.proc.stdout.fileno(), count)
606
 
 
607
 
    def close(self):
608
 
        self.proc.stdin.close()
609
 
        self.proc.stdout.close()
610
 
        self.proc.wait()
611
 
 
612
 
    def get_filelike_channels(self):
613
 
        return (self.proc.stdout, self.proc.stdin)
 
731
        if self._sock is not None:
 
732
            return self._sock.recv(count)
 
733
        else:
 
734
            return os.read(self.proc.stdout.fileno(), count)
 
735
 
 
736
    def close(self):
 
737
        _close_ssh_proc(self.proc, self._sock)
 
738
 
 
739
    def get_sock_or_pipes(self):
 
740
        if self._sock is not None:
 
741
            return 'socket', self._sock
 
742
        else:
 
743
            return 'pipes', (self.proc.stdout, self.proc.stdin)
 
744
 
 
745
 
 
746
class _ParamikoSSHConnection(SSHConnection):
 
747
    """An SSH connection via paramiko."""
 
748
 
 
749
    def __init__(self, channel):
 
750
        self.channel = channel
 
751
 
 
752
    def get_sock_or_pipes(self):
 
753
        return ('socket', self.channel)
 
754
 
 
755
    def close(self):
 
756
        return self.channel.close()
 
757
 
614
758