~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: Vincent Ladeuil
  • Date: 2016-01-21 11:42:23 UTC
  • mto: This revision was merged to the branch mainline in revision 6610.
  • Revision ID: v.ladeuil+lp@free.fr-20160121114223-ngcvndi02ydiqs5z
Allow hyphens in option names to unbreak compatibility.

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
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
22
24
import logging
24
26
import socket
25
27
import subprocess
26
28
import sys
 
29
from binascii import hexlify
27
30
 
28
31
from bzrlib import (
29
32
    config,
126
129
        elif 'SSH Secure Shell' in version:
127
130
            trace.mutter('ssh implementation is SSH Corp.')
128
131
            vendor = SSHCorpSubprocessVendor()
 
132
        elif 'lsh' in version:
 
133
            trace.mutter('ssh implementation is GNU lsh.')
 
134
            vendor = LSHSubprocessVendor()
129
135
        # As plink user prompts are not handled currently, don't auto-detect
130
136
        # it by inspection below, but keep this vendor detection for if a path
131
137
        # is given in BZR_SSH. See https://bugs.launchpad.net/bugs/414743
132
138
        elif 'plink' in version and progname == 'plink':
133
139
            # Checking if "plink" was the executed argument as Windows
134
 
            # sometimes reports 'ssh -V' incorrectly with 'plink' in it's
 
140
            # sometimes reports 'ssh -V' incorrectly with 'plink' in its
135
141
            # version.  See https://bugs.launchpad.net/bzr/+bug/107155
136
142
            trace.mutter("ssh implementation is Putty's plink.")
137
143
            vendor = PLinkSubprocessVendor()
173
179
register_ssh_vendor = _ssh_vendor_manager.register_vendor
174
180
 
175
181
 
176
 
def _ignore_sigint():
 
182
def _ignore_signals():
177
183
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
178
184
    # doesn't handle it itself.
179
185
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
180
186
    import signal
181
187
    signal.signal(signal.SIGINT, signal.SIG_IGN)
 
188
    # GZ 2010-02-19: Perhaps make this check if breakin is installed instead
 
189
    if signal.getsignal(signal.SIGQUIT) != signal.SIG_DFL:
 
190
        signal.signal(signal.SIGQUIT, signal.SIG_IGN)
182
191
 
183
192
 
184
193
class SocketAsChannelAdapter(object):
236
245
    def connect_ssh(self, username, password, host, port, command):
237
246
        """Make an SSH connection.
238
247
 
239
 
        :returns: something with a `close` method, and a `get_filelike_channels`
240
 
            method that returns a pair of (read, write) filelike objects.
 
248
        :returns: an SSHConnection.
241
249
        """
242
250
        raise NotImplementedError(self.connect_ssh)
243
251
 
266
274
register_ssh_vendor('loopback', LoopbackVendor())
267
275
 
268
276
 
269
 
class _ParamikoSSHConnection(object):
270
 
    def __init__(self, channel):
271
 
        self.channel = channel
272
 
 
273
 
    def get_filelike_channels(self):
274
 
        return self.channel.makefile('rb'), self.channel.makefile('wb')
275
 
 
276
 
    def close(self):
277
 
        return self.channel.close()
278
 
 
279
 
 
280
277
class ParamikoVendor(SSHVendor):
281
278
    """Vendor that uses paramiko."""
282
279
 
 
280
    def _hexify(self, s):
 
281
        return hexlify(s).upper()
 
282
 
283
283
    def _connect(self, username, password, host, port):
284
284
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
285
285
 
293
293
            self._raise_connection_error(host, port=port, orig_error=e)
294
294
 
295
295
        server_key = t.get_remote_server_key()
296
 
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
 
296
        server_key_hex = self._hexify(server_key.get_fingerprint())
297
297
        keytype = server_key.get_name()
298
298
        if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
299
299
            our_server_key = SYSTEM_HOSTKEYS[host][keytype]
300
 
            our_server_key_hex = paramiko.util.hexify(
301
 
                our_server_key.get_fingerprint())
 
300
            our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
302
301
        elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
303
302
            our_server_key = BZR_HOSTKEYS[host][keytype]
304
 
            our_server_key_hex = paramiko.util.hexify(
305
 
                our_server_key.get_fingerprint())
 
303
            our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
306
304
        else:
307
305
            trace.warning('Adding %s host key for %s: %s'
308
306
                          % (keytype, host, server_key_hex))
312
310
            else:
313
311
                BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
314
312
            our_server_key = server_key
315
 
            our_server_key_hex = paramiko.util.hexify(
316
 
                our_server_key.get_fingerprint())
 
313
            our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
317
314
            save_host_keys()
318
315
        if server_key != our_server_key:
319
316
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
345
342
            self._raise_connection_error(host, port=port, orig_error=e,
346
343
                                         msg='Unable to invoke remote bzr')
347
344
 
 
345
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
348
346
if paramiko is not None:
349
347
    vendor = ParamikoVendor()
350
348
    register_ssh_vendor('paramiko', vendor)
351
349
    register_ssh_vendor('none', vendor)
352
350
    register_default_ssh_vendor(vendor)
353
 
    _sftp_connection_errors = (EOFError, paramiko.SSHException)
 
351
    _ssh_connection_errors += (paramiko.SSHException,)
354
352
    del vendor
355
 
else:
356
 
    _sftp_connection_errors = (EOFError,)
357
353
 
358
354
 
359
355
class SubprocessVendor(SSHVendor):
360
356
    """Abstract base class for vendors that use pipes to a subprocess."""
361
357
 
 
358
    # In general stderr should be inherited from the parent process so prompts
 
359
    # are visible on the terminal. This can be overriden to another file for
 
360
    # tests, but beware of using PIPE which may hang due to not being read.
 
361
    _stderr_target = None
 
362
 
362
363
    def _connect(self, argv):
363
 
        proc = subprocess.Popen(argv,
364
 
                                stdin=subprocess.PIPE,
365
 
                                stdout=subprocess.PIPE,
 
364
        # Attempt to make a socketpair to use as stdin/stdout for the SSH
 
365
        # subprocess.  We prefer sockets to pipes because they support
 
366
        # non-blocking short reads, allowing us to optimistically read 64k (or
 
367
        # whatever) chunks.
 
368
        try:
 
369
            my_sock, subproc_sock = socket.socketpair()
 
370
            osutils.set_fd_cloexec(my_sock)
 
371
        except (AttributeError, socket.error):
 
372
            # This platform doesn't support socketpair(), so just use ordinary
 
373
            # pipes instead.
 
374
            stdin = stdout = subprocess.PIPE
 
375
            my_sock, subproc_sock = None, None
 
376
        else:
 
377
            stdin = stdout = subproc_sock
 
378
        proc = subprocess.Popen(argv, stdin=stdin, stdout=stdout,
 
379
                                stderr=self._stderr_target,
366
380
                                **os_specific_subprocess_params())
367
 
        return SSHSubprocess(proc)
 
381
        if subproc_sock is not None:
 
382
            subproc_sock.close()
 
383
        return SSHSubprocessConnection(proc, sock=my_sock)
368
384
 
369
385
    def connect_sftp(self, username, password, host, port):
370
386
        try:
372
388
                                                  subsystem='sftp')
373
389
            sock = self._connect(argv)
374
390
            return SFTPClient(SocketAsChannelAdapter(sock))
375
 
        except _sftp_connection_errors, e:
376
 
            self._raise_connection_error(host, port=port, orig_error=e)
377
 
        except (OSError, IOError), e:
378
 
            # If the machine is fast enough, ssh can actually exit
379
 
            # before we try and send it the sftp request, which
380
 
            # raises a Broken Pipe
381
 
            if e.errno not in (errno.EPIPE,):
382
 
                raise
 
391
        except _ssh_connection_errors, e:
383
392
            self._raise_connection_error(host, port=port, orig_error=e)
384
393
 
385
394
    def connect_ssh(self, username, password, host, port, command):
387
396
            argv = self._get_vendor_specific_argv(username, host, port,
388
397
                                                  command=command)
389
398
            return self._connect(argv)
390
 
        except (EOFError), e:
391
 
            self._raise_connection_error(host, port=port, orig_error=e)
392
 
        except (OSError, IOError), e:
393
 
            # If the machine is fast enough, ssh can actually exit
394
 
            # before we try and send it the sftp request, which
395
 
            # raises a Broken Pipe
396
 
            if e.errno not in (errno.EPIPE,):
397
 
                raise
 
399
        except _ssh_connection_errors, e:
398
400
            self._raise_connection_error(host, port=port, orig_error=e)
399
401
 
400
402
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
415
417
                                  command=None):
416
418
        args = [self.executable_path,
417
419
                '-oForwardX11=no', '-oForwardAgent=no',
418
 
                '-oClearAllForwardings=yes', '-oProtocol=2',
 
420
                '-oClearAllForwardings=yes',
419
421
                '-oNoHostAuthenticationForLocalhost=yes']
420
422
        if port is not None:
421
423
            args.extend(['-p', str(port)])
451
453
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
452
454
 
453
455
 
 
456
class LSHSubprocessVendor(SubprocessVendor):
 
457
    """SSH vendor that uses the 'lsh' executable from GNU"""
 
458
 
 
459
    executable_path = 'lsh'
 
460
 
 
461
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
 
462
                                  command=None):
 
463
        args = [self.executable_path]
 
464
        if port is not None:
 
465
            args.extend(['-p', str(port)])
 
466
        if username is not None:
 
467
            args.extend(['-l', username])
 
468
        if subsystem is not None:
 
469
            args.extend(['--subsystem', subsystem, host])
 
470
        else:
 
471
            args.extend([host] + command)
 
472
        return args
 
473
 
 
474
register_ssh_vendor('lsh', LSHSubprocessVendor())
 
475
 
 
476
 
454
477
class PLinkSubprocessVendor(SubprocessVendor):
455
478
    """SSH vendor that uses the 'plink' executable from Putty."""
456
479
 
483
506
        agent = paramiko.Agent()
484
507
        for key in agent.get_keys():
485
508
            trace.mutter('Trying SSH agent key %s'
486
 
                         % paramiko.util.hexify(key.get_fingerprint()))
 
509
                         % self._hexify(key.get_fingerprint()))
487
510
            try:
488
511
                paramiko_transport.auth_publickey(username, key)
489
512
                return
559
582
        return True
560
583
    except paramiko.PasswordRequiredException:
561
584
        password = ui.ui_factory.get_password(
562
 
            prompt='SSH %(filename)s password', filename=filename)
 
585
            prompt=u'SSH %(filename)s password',
 
586
            filename=filename.decode(osutils._fs_enc))
563
587
        try:
564
588
            key = pkey_class.from_private_key_file(filename, password)
565
589
            paramiko_transport.auth_publickey(username, key)
634
658
        # Running it in a separate process group is not good because then it
635
659
        # can't get non-echoed input of a password or passphrase.
636
660
        # <https://launchpad.net/products/bzr/+bug/40508>
637
 
        return {'preexec_fn': _ignore_sigint,
 
661
        return {'preexec_fn': _ignore_signals,
638
662
                'close_fds': True,
639
663
                }
640
664
 
641
665
import weakref
642
666
_subproc_weakrefs = set()
643
667
 
644
 
def _close_ssh_proc(proc):
645
 
    for func in [proc.stdin.close, proc.stdout.close, proc.wait]:
 
668
def _close_ssh_proc(proc, sock):
 
669
    """Carefully close stdin/stdout and reap the SSH process.
 
670
 
 
671
    If the pipes are already closed and/or the process has already been
 
672
    wait()ed on, that's ok, and no error is raised.  The goal is to do our best
 
673
    to clean up (whether or not a clean up was already tried).
 
674
    """
 
675
    funcs = []
 
676
    for closeable in (proc.stdin, proc.stdout, sock):
 
677
        # We expect that either proc (a subprocess.Popen) will have stdin and
 
678
        # stdout streams to close, or that we will have been passed a socket to
 
679
        # close, with the option not in use being None.
 
680
        if closeable is not None:
 
681
            funcs.append(closeable.close)
 
682
    funcs.append(proc.wait)
 
683
    for func in funcs:
646
684
        try:
647
685
            func()
648
686
        except OSError:
649
 
            pass
650
 
 
651
 
 
652
 
class SSHSubprocess(object):
653
 
    """A socket-like object that talks to an ssh subprocess via pipes."""
654
 
 
655
 
    def __init__(self, proc):
 
687
            # It's ok for the pipe to already be closed, or the process to
 
688
            # already be finished.
 
689
            continue
 
690
 
 
691
 
 
692
class SSHConnection(object):
 
693
    """Abstract base class for SSH connections."""
 
694
 
 
695
    def get_sock_or_pipes(self):
 
696
        """Returns a (kind, io_object) pair.
 
697
 
 
698
        If kind == 'socket', then io_object is a socket.
 
699
 
 
700
        If kind == 'pipes', then io_object is a pair of file-like objects
 
701
        (read_from, write_to).
 
702
        """
 
703
        raise NotImplementedError(self.get_sock_or_pipes)
 
704
 
 
705
    def close(self):
 
706
        raise NotImplementedError(self.close)
 
707
 
 
708
 
 
709
class SSHSubprocessConnection(SSHConnection):
 
710
    """A connection to an ssh subprocess via pipes or a socket.
 
711
 
 
712
    This class is also socket-like enough to be used with
 
713
    SocketAsChannelAdapter (it has 'send' and 'recv' methods).
 
714
    """
 
715
 
 
716
    def __init__(self, proc, sock=None):
 
717
        """Constructor.
 
718
 
 
719
        :param proc: a subprocess.Popen
 
720
        :param sock: if proc.stdin/out is a socket from a socketpair, then sock
 
721
            should bzrlib's half of that socketpair.  If not passed, proc's
 
722
            stdin/out is assumed to be ordinary pipes.
 
723
        """
656
724
        self.proc = proc
 
725
        self._sock = sock
657
726
        # Add a weakref to proc that will attempt to do the same as self.close
658
727
        # to avoid leaving processes lingering indefinitely.
659
728
        def terminate(ref):
660
729
            _subproc_weakrefs.remove(ref)
661
 
            _close_ssh_proc(proc)
 
730
            _close_ssh_proc(proc, sock)
662
731
        _subproc_weakrefs.add(weakref.ref(self, terminate))
663
732
 
664
733
    def send(self, data):
665
 
        return os.write(self.proc.stdin.fileno(), data)
 
734
        if self._sock is not None:
 
735
            return self._sock.send(data)
 
736
        else:
 
737
            return os.write(self.proc.stdin.fileno(), data)
666
738
 
667
739
    def recv(self, count):
668
 
        return os.read(self.proc.stdout.fileno(), count)
669
 
 
670
 
    def close(self):
671
 
        _close_ssh_proc(self.proc)
672
 
 
673
 
    def get_filelike_channels(self):
674
 
        return (self.proc.stdout, self.proc.stdin)
 
740
        if self._sock is not None:
 
741
            return self._sock.recv(count)
 
742
        else:
 
743
            return os.read(self.proc.stdout.fileno(), count)
 
744
 
 
745
    def close(self):
 
746
        _close_ssh_proc(self.proc, self._sock)
 
747
 
 
748
    def get_sock_or_pipes(self):
 
749
        if self._sock is not None:
 
750
            return 'socket', self._sock
 
751
        else:
 
752
            return 'pipes', (self.proc.stdout, self.proc.stdin)
 
753
 
 
754
 
 
755
class _ParamikoSSHConnection(SSHConnection):
 
756
    """An SSH connection via paramiko."""
 
757
 
 
758
    def __init__(self, channel):
 
759
        self.channel = channel
 
760
 
 
761
    def get_sock_or_pipes(self):
 
762
        return ('socket', self.channel)
 
763
 
 
764
    def close(self):
 
765
        return self.channel.close()
 
766
 
675
767