~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: John Arbash Meinel
  • Date: 2010-08-13 19:08:57 UTC
  • mto: (5050.17.7 2.2)
  • mto: This revision was merged to the branch mainline in revision 5379.
  • Revision ID: john@arbash-meinel.com-20100813190857-mvzwnimrxvm0zimp
Lots of documentation updates.

We had a lot of http links pointing to the old domain. They should
all now be properly updated to the new domain. (only bazaar-vcs.org
entry left is for pqm, which seems to still reside at the old url.)

Also removed one 'TODO' doc entry about switching to binary xdelta, since
we basically did just that with groupcompress.

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-2010 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
94
94
            try:
95
95
                vendor = self._ssh_vendors[vendor_name]
96
96
            except KeyError:
97
 
                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
98
101
            return vendor
99
102
        return None
100
103
 
110
113
            stdout = stderr = ''
111
114
        return stdout + stderr
112
115
 
113
 
    def _get_vendor_by_version_string(self, version, args):
 
116
    def _get_vendor_by_version_string(self, version, progname):
114
117
        """Return the vendor or None based on output from the subprocess.
115
118
 
116
119
        :param version: The output of 'ssh -V' like command.
123
126
        elif 'SSH Secure Shell' in version:
124
127
            trace.mutter('ssh implementation is SSH Corp.')
125
128
            vendor = SSHCorpSubprocessVendor()
126
 
        elif 'plink' in version and args[0] == 'plink':
 
129
        # As plink user prompts are not handled currently, don't auto-detect
 
130
        # it by inspection below, but keep this vendor detection for if a path
 
131
        # is given in BZR_SSH. See https://bugs.launchpad.net/bugs/414743
 
132
        elif 'plink' in version and progname == 'plink':
127
133
            # Checking if "plink" was the executed argument as Windows
128
134
            # sometimes reports 'ssh -V' incorrectly with 'plink' in it's
129
135
            # version.  See https://bugs.launchpad.net/bzr/+bug/107155
133
139
 
134
140
    def _get_vendor_by_inspection(self):
135
141
        """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, args)
139
 
            if vendor is not None:
140
 
                return vendor
141
 
        return None
 
142
        version = self._get_ssh_version_string(['ssh', '-V'])
 
143
        return self._get_vendor_by_version_string(version, "ssh")
 
144
 
 
145
    def _get_vendor_from_path(self, path):
 
146
        """Return the vendor or None using the program at the given path"""
 
147
        version = self._get_ssh_version_string([path, '-V'])
 
148
        return self._get_vendor_by_version_string(version, 
 
149
            os.path.splitext(os.path.basename(path))[0])
142
150
 
143
151
    def get_vendor(self, environment=None):
144
152
        """Find out what version of SSH is on the system.
165
173
register_ssh_vendor = _ssh_vendor_manager.register_vendor
166
174
 
167
175
 
168
 
def _ignore_sigint():
 
176
def _ignore_signals():
169
177
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
170
178
    # doesn't handle it itself.
171
179
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
172
180
    import signal
173
181
    signal.signal(signal.SIGINT, signal.SIG_IGN)
 
182
    # GZ 2010-02-19: Perhaps make this check if breakin is installed instead
 
183
    if signal.getsignal(signal.SIGQUIT) != signal.SIG_DFL:
 
184
        signal.signal(signal.SIGQUIT, signal.SIG_IGN)
174
185
 
175
186
 
176
187
class SocketAsChannelAdapter(object):
228
239
    def connect_ssh(self, username, password, host, port, command):
229
240
        """Make an SSH connection.
230
241
 
231
 
        :returns: something with a `close` method, and a `get_filelike_channels`
232
 
            method that returns a pair of (read, write) filelike objects.
 
242
        :returns: an SSHConnection.
233
243
        """
234
244
        raise NotImplementedError(self.connect_ssh)
235
245
 
258
268
register_ssh_vendor('loopback', LoopbackVendor())
259
269
 
260
270
 
261
 
class _ParamikoSSHConnection(object):
262
 
    def __init__(self, channel):
263
 
        self.channel = channel
264
 
 
265
 
    def get_filelike_channels(self):
266
 
        return self.channel.makefile('rb'), self.channel.makefile('wb')
267
 
 
268
 
    def close(self):
269
 
        return self.channel.close()
270
 
 
271
 
 
272
271
class ParamikoVendor(SSHVendor):
273
272
    """Vendor that uses paramiko."""
274
273
 
352
351
    """Abstract base class for vendors that use pipes to a subprocess."""
353
352
 
354
353
    def _connect(self, argv):
355
 
        proc = subprocess.Popen(argv,
356
 
                                stdin=subprocess.PIPE,
357
 
                                stdout=subprocess.PIPE,
 
354
        # Attempt to make a socketpair to use as stdin/stdout for the SSH
 
355
        # subprocess.  We prefer sockets to pipes because they support
 
356
        # non-blocking short reads, allowing us to optimistically read 64k (or
 
357
        # whatever) chunks.
 
358
        try:
 
359
            my_sock, subproc_sock = socket.socketpair()
 
360
        except (AttributeError, socket.error):
 
361
            # This platform doesn't support socketpair(), so just use ordinary
 
362
            # pipes instead.
 
363
            stdin = stdout = subprocess.PIPE
 
364
            sock = None
 
365
        else:
 
366
            stdin = stdout = subproc_sock
 
367
            sock = my_sock
 
368
        proc = subprocess.Popen(argv, stdin=stdin, stdout=stdout,
358
369
                                **os_specific_subprocess_params())
359
 
        return SSHSubprocess(proc)
 
370
        return SSHSubprocessConnection(proc, sock=sock)
360
371
 
361
372
    def connect_sftp(self, username, password, host, port):
362
373
        try:
401
412
class OpenSSHSubprocessVendor(SubprocessVendor):
402
413
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
403
414
 
 
415
    executable_path = 'ssh'
 
416
 
404
417
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
405
418
                                  command=None):
406
 
        args = ['ssh',
 
419
        args = [self.executable_path,
407
420
                '-oForwardX11=no', '-oForwardAgent=no',
408
421
                '-oClearAllForwardings=yes', '-oProtocol=2',
409
422
                '-oNoHostAuthenticationForLocalhost=yes']
423
436
class SSHCorpSubprocessVendor(SubprocessVendor):
424
437
    """SSH vendor that uses the 'ssh' executable from SSH Corporation."""
425
438
 
 
439
    executable_path = 'ssh'
 
440
 
426
441
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
427
442
                                  command=None):
428
 
        args = ['ssh', '-x']
 
443
        args = [self.executable_path, '-x']
429
444
        if port is not None:
430
445
            args.extend(['-p', str(port)])
431
446
        if username is not None:
436
451
            args.extend([host] + command)
437
452
        return args
438
453
 
439
 
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
 
454
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
440
455
 
441
456
 
442
457
class PLinkSubprocessVendor(SubprocessVendor):
443
458
    """SSH vendor that uses the 'plink' executable from Putty."""
444
459
 
 
460
    executable_path = 'plink'
 
461
 
445
462
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
446
463
                                  command=None):
447
 
        args = ['plink', '-x', '-a', '-ssh', '-2', '-batch']
 
464
        args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
448
465
        if port is not None:
449
466
            args.extend(['-P', str(port)])
450
467
        if username is not None:
501
518
    except paramiko.SSHException, e:
502
519
        # Don't know what happened, but just ignore it
503
520
        pass
504
 
    if 'password' not in supported_auth_types:
 
521
    # We treat 'keyboard-interactive' and 'password' auth methods identically,
 
522
    # because Paramiko's auth_password method will automatically try
 
523
    # 'keyboard-interactive' auth (using the password as the response) if
 
524
    # 'password' auth is not available.  Apparently some Debian and Gentoo
 
525
    # OpenSSH servers require this.
 
526
    # XXX: It's possible for a server to require keyboard-interactive auth that
 
527
    # requires something other than a single password, but we currently don't
 
528
    # support that.
 
529
    if ('password' not in supported_auth_types and
 
530
        'keyboard-interactive' not in supported_auth_types):
505
531
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
506
532
            '\n  %s@%s\nsupported auth types: %s'
507
533
            % (username, host, supported_auth_types))
611
637
        # Running it in a separate process group is not good because then it
612
638
        # can't get non-echoed input of a password or passphrase.
613
639
        # <https://launchpad.net/products/bzr/+bug/40508>
614
 
        return {'preexec_fn': _ignore_sigint,
 
640
        return {'preexec_fn': _ignore_signals,
615
641
                'close_fds': True,
616
642
                }
617
643
 
618
 
 
619
 
class SSHSubprocess(object):
620
 
    """A socket-like object that talks to an ssh subprocess via pipes."""
621
 
 
622
 
    def __init__(self, proc):
 
644
import weakref
 
645
_subproc_weakrefs = set()
 
646
 
 
647
def _close_ssh_proc(proc):
 
648
    for func in [proc.stdin.close, proc.stdout.close, proc.wait]:
 
649
        try:
 
650
            func()
 
651
        except OSError:
 
652
            pass
 
653
 
 
654
 
 
655
class SSHConnection(object):
 
656
    """Abstract base class for SSH connections."""
 
657
 
 
658
    def get_sock_or_pipes(self):
 
659
        """Returns a (kind, io_object) pair.
 
660
 
 
661
        If kind == 'socket', then io_object is a socket.
 
662
 
 
663
        If kind == 'pipes', then io_object is a pair of file-like objects
 
664
        (read_from, write_to).
 
665
        """
 
666
        raise NotImplementedError(self.get_sock_or_pipes)
 
667
 
 
668
    def close(self):
 
669
        raise NotImplementedError(self.close)
 
670
 
 
671
 
 
672
class SSHSubprocessConnection(SSHConnection):
 
673
    """A connection to an ssh subprocess via pipes or a socket.
 
674
 
 
675
    This class is also socket-like enough to be used with
 
676
    SocketAsChannelAdapter (it has 'send' and 'recv' methods).
 
677
    """
 
678
 
 
679
    def __init__(self, proc, sock=None):
 
680
        """Constructor.
 
681
 
 
682
        :param proc: a subprocess.Popen
 
683
        :param sock: if proc.stdin/out is a socket from a socketpair, then sock
 
684
            should bzrlib's half of that socketpair.  If not passed, proc's
 
685
            stdin/out is assumed to be ordinary pipes.
 
686
        """
623
687
        self.proc = proc
 
688
        self._sock = sock
 
689
        # Add a weakref to proc that will attempt to do the same as self.close
 
690
        # to avoid leaving processes lingering indefinitely.
 
691
        def terminate(ref):
 
692
            _subproc_weakrefs.remove(ref)
 
693
            _close_ssh_proc(proc)
 
694
        _subproc_weakrefs.add(weakref.ref(self, terminate))
624
695
 
625
696
    def send(self, data):
626
 
        return os.write(self.proc.stdin.fileno(), data)
 
697
        if self._sock is not None:
 
698
            return self._sock.send(data)
 
699
        else:
 
700
            return os.write(self.proc.stdin.fileno(), data)
627
701
 
628
702
    def recv(self, count):
629
 
        return os.read(self.proc.stdout.fileno(), count)
630
 
 
631
 
    def close(self):
632
 
        self.proc.stdin.close()
633
 
        self.proc.stdout.close()
634
 
        self.proc.wait()
635
 
 
636
 
    def get_filelike_channels(self):
637
 
        return (self.proc.stdout, self.proc.stdin)
 
703
        if self._sock is not None:
 
704
            return self._sock.recv(count)
 
705
        else:
 
706
            return os.read(self.proc.stdout.fileno(), count)
 
707
 
 
708
    def close(self):
 
709
        _close_ssh_proc(self.proc)
 
710
 
 
711
    def get_sock_or_pipes(self):
 
712
        if self._sock is not None:
 
713
            return 'socket', self._sock
 
714
        else:
 
715
            return 'pipes', (self.proc.stdout, self.proc.stdin)
 
716
 
 
717
 
 
718
class _ParamikoSSHConnection(SSHConnection):
 
719
    """An SSH connection via paramiko."""
 
720
 
 
721
    def __init__(self, channel):
 
722
        self.channel = channel
 
723
 
 
724
    def get_sock_or_pipes(self):
 
725
        return ('socket', self.channel)
 
726
 
 
727
    def close(self):
 
728
        return self.channel.close()
 
729
 
638
730