~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: Vincent Ladeuil
  • Date: 2010-03-02 10:21:39 UTC
  • mfrom: (4797.2.24 2.1)
  • mto: This revision was merged to the branch mainline in revision 5069.
  • Revision ID: v.ladeuil+lp@free.fr-20100302102139-b5cba7h6xu13mekg
Merge 2.1 into trunk including fixes for #331095, #507557, #185103, #524184 and #369501

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
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
        # 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':
126
133
            # Checking if "plink" was the executed argument as Windows
127
134
            # sometimes reports 'ssh -V' incorrectly with 'plink' in it's
128
135
            # version.  See https://bugs.launchpad.net/bzr/+bug/107155
132
139
 
133
140
    def _get_vendor_by_inspection(self):
134
141
        """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
 
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])
141
150
 
142
151
    def get_vendor(self, environment=None):
143
152
        """Find out what version of SSH is on the system.
164
173
register_ssh_vendor = _ssh_vendor_manager.register_vendor
165
174
 
166
175
 
167
 
def _ignore_sigint():
 
176
def _ignore_signals():
168
177
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
169
178
    # doesn't handle it itself.
170
179
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
171
180
    import signal
172
181
    signal.signal(signal.SIGINT, signal.SIG_IGN)
173
 
 
174
 
 
175
 
class LoopbackSFTP(object):
 
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)
 
185
 
 
186
 
 
187
class SocketAsChannelAdapter(object):
176
188
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
177
189
 
178
190
    def __init__(self, sock):
179
191
        self.__socket = sock
180
192
 
 
193
    def get_name(self):
 
194
        return "bzr SocketAsChannelAdapter"
 
195
 
181
196
    def send(self, data):
182
197
        return self.__socket.send(data)
183
198
 
184
199
    def recv(self, n):
185
 
        return self.__socket.recv(n)
 
200
        try:
 
201
            return self.__socket.recv(n)
 
202
        except socket.error, e:
 
203
            if e.args[0] in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED,
 
204
                             errno.EBADF):
 
205
                # Connection has closed.  Paramiko expects an empty string in
 
206
                # this case, not an exception.
 
207
                return ''
 
208
            raise
186
209
 
187
210
    def recv_ready(self):
 
211
        # TODO: jam 20051215 this function is necessary to support the
 
212
        # pipelined() function. In reality, it probably should use
 
213
        # poll() or select() to actually return if there is data
 
214
        # available, otherwise we probably don't get any benefit
188
215
        return True
189
216
 
190
217
    def close(self):
196
223
 
197
224
    def connect_sftp(self, username, password, host, port):
198
225
        """Make an SSH connection, and return an SFTPClient.
199
 
        
 
226
 
200
227
        :param username: an ascii string
201
228
        :param password: an ascii string
202
229
        :param host: a host name as an ascii string
211
238
 
212
239
    def connect_ssh(self, username, password, host, port, command):
213
240
        """Make an SSH connection.
214
 
        
 
241
 
215
242
        :returns: something with a `close` method, and a `get_filelike_channels`
216
243
            method that returns a pair of (read, write) filelike objects.
217
244
        """
237
264
            sock.connect((host, port))
238
265
        except socket.error, e:
239
266
            self._raise_connection_error(host, port=port, orig_error=e)
240
 
        return SFTPClient(LoopbackSFTP(sock))
 
267
        return SFTPClient(SocketAsChannelAdapter(sock))
241
268
 
242
269
register_ssh_vendor('loopback', LoopbackVendor())
243
270
 
326
353
    register_ssh_vendor('paramiko', vendor)
327
354
    register_ssh_vendor('none', vendor)
328
355
    register_default_ssh_vendor(vendor)
 
356
    _sftp_connection_errors = (EOFError, paramiko.SSHException)
329
357
    del vendor
 
358
else:
 
359
    _sftp_connection_errors = (EOFError,)
330
360
 
331
361
 
332
362
class SubprocessVendor(SSHVendor):
344
374
            argv = self._get_vendor_specific_argv(username, host, port,
345
375
                                                  subsystem='sftp')
346
376
            sock = self._connect(argv)
347
 
            return SFTPClient(sock)
348
 
        except (EOFError, paramiko.SSHException), e:
 
377
            return SFTPClient(SocketAsChannelAdapter(sock))
 
378
        except _sftp_connection_errors, e:
349
379
            self._raise_connection_error(host, port=port, orig_error=e)
350
380
        except (OSError, IOError), e:
351
381
            # If the machine is fast enough, ssh can actually exit
373
403
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
374
404
                                  command=None):
375
405
        """Returns the argument list to run the subprocess with.
376
 
        
 
406
 
377
407
        Exactly one of 'subsystem' and 'command' must be specified.
378
408
        """
379
409
        raise NotImplementedError(self._get_vendor_specific_argv)
382
412
class OpenSSHSubprocessVendor(SubprocessVendor):
383
413
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
384
414
 
 
415
    executable_path = 'ssh'
 
416
 
385
417
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
386
418
                                  command=None):
387
 
        assert subsystem is not None or command is not None, (
388
 
            'Must specify a command or subsystem')
389
 
        if subsystem is not None:
390
 
            assert command is None, (
391
 
                'subsystem and command are mutually exclusive')
392
 
        args = ['ssh',
 
419
        args = [self.executable_path,
393
420
                '-oForwardX11=no', '-oForwardAgent=no',
394
421
                '-oClearAllForwardings=yes', '-oProtocol=2',
395
422
                '-oNoHostAuthenticationForLocalhost=yes']
409
436
class SSHCorpSubprocessVendor(SubprocessVendor):
410
437
    """SSH vendor that uses the 'ssh' executable from SSH Corporation."""
411
438
 
 
439
    executable_path = 'ssh'
 
440
 
412
441
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
413
442
                                  command=None):
414
 
        assert subsystem is not None or command is not None, (
415
 
            'Must specify a command or subsystem')
416
 
        if subsystem is not None:
417
 
            assert command is None, (
418
 
                'subsystem and command are mutually exclusive')
419
 
        args = ['ssh', '-x']
 
443
        args = [self.executable_path, '-x']
420
444
        if port is not None:
421
445
            args.extend(['-p', str(port)])
422
446
        if username is not None:
427
451
            args.extend([host] + command)
428
452
        return args
429
453
 
430
 
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
 
454
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
431
455
 
432
456
 
433
457
class PLinkSubprocessVendor(SubprocessVendor):
434
458
    """SSH vendor that uses the 'plink' executable from Putty."""
435
459
 
 
460
    executable_path = 'plink'
 
461
 
436
462
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
437
463
                                  command=None):
438
 
        assert subsystem is not None or command is not None, (
439
 
            'Must specify a command or subsystem')
440
 
        if subsystem is not None:
441
 
            assert command is None, (
442
 
                'subsystem and command are mutually exclusive')
443
 
        args = ['plink', '-x', '-a', '-ssh', '-2']
 
464
        args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
444
465
        if port is not None:
445
466
            args.extend(['-P', str(port)])
446
467
        if username is not None:
455
476
 
456
477
 
457
478
def _paramiko_auth(username, password, host, port, paramiko_transport):
458
 
    # paramiko requires a username, but it might be none if nothing was supplied
459
 
    # use the local username, just in case.
460
 
    # We don't override username, because if we aren't using paramiko,
461
 
    # the username might be specified in ~/.ssh/config and we don't want to
462
 
    # force it to something else
463
 
    # Also, it would mess up the self.relpath() functionality
464
479
    auth = config.AuthenticationConfig()
 
480
    # paramiko requires a username, but it might be none if nothing was
 
481
    # supplied.  If so, use the local username.
465
482
    if username is None:
466
 
        username = auth.get_user('ssh', host, port=port)
467
 
        if username is None:
468
 
            # Default to local user
469
 
            username = getpass.getuser()
470
 
 
 
483
        username = auth.get_user('ssh', host, port=port,
 
484
                                 default=getpass.getuser())
471
485
    if _use_ssh_agent:
472
486
        agent = paramiko.Agent()
473
487
        for key in agent.get_keys():
485
499
    if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
486
500
        return
487
501
 
 
502
    # If we have gotten this far, we are about to try for passwords, do an
 
503
    # auth_none check to see if it is even supported.
 
504
    supported_auth_types = []
 
505
    try:
 
506
        # Note that with paramiko <1.7.5 this logs an INFO message:
 
507
        #    Authentication type (none) not permitted.
 
508
        # So we explicitly disable the logging level for this action
 
509
        old_level = paramiko_transport.logger.level
 
510
        paramiko_transport.logger.setLevel(logging.WARNING)
 
511
        try:
 
512
            paramiko_transport.auth_none(username)
 
513
        finally:
 
514
            paramiko_transport.logger.setLevel(old_level)
 
515
    except paramiko.BadAuthenticationType, e:
 
516
        # Supported methods are in the exception
 
517
        supported_auth_types = e.allowed_types
 
518
    except paramiko.SSHException, e:
 
519
        # Don't know what happened, but just ignore it
 
520
        pass
 
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):
 
531
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
 
532
            '\n  %s@%s\nsupported auth types: %s'
 
533
            % (username, host, supported_auth_types))
 
534
 
488
535
    if password:
489
536
        try:
490
537
            paramiko_transport.auth_password(username, password)
494
541
 
495
542
    # give up and ask for a password
496
543
    password = auth.get_password('ssh', host, username, port=port)
497
 
    try:
498
 
        paramiko_transport.auth_password(username, password)
499
 
    except paramiko.SSHException, e:
500
 
        raise errors.ConnectionError(
501
 
            'Unable to authenticate to SSH host as %s@%s' % (username, host), e)
 
544
    # get_password can still return None, which means we should not prompt
 
545
    if password is not None:
 
546
        try:
 
547
            paramiko_transport.auth_password(username, password)
 
548
        except paramiko.SSHException, e:
 
549
            raise errors.ConnectionError(
 
550
                'Unable to authenticate to SSH host as'
 
551
                '\n  %s@%s\n' % (username, host), e)
 
552
    else:
 
553
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
 
554
                                     '  %s@%s' % (username, host))
502
555
 
503
556
 
504
557
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
566
619
def os_specific_subprocess_params():
567
620
    """Get O/S specific subprocess parameters."""
568
621
    if sys.platform == 'win32':
569
 
        # setting the process group and closing fds is not supported on 
 
622
        # setting the process group and closing fds is not supported on
570
623
        # win32
571
624
        return {}
572
625
    else:
573
 
        # We close fds other than the pipes as the child process does not need 
 
626
        # We close fds other than the pipes as the child process does not need
574
627
        # them to be open.
575
628
        #
576
629
        # We also set the child process to ignore SIGINT.  Normally the signal
578
631
        # this causes it to be seen only by bzr and not by ssh.  Python will
579
632
        # generate a KeyboardInterrupt in bzr, and we will then have a chance
580
633
        # to release locks or do other cleanup over ssh before the connection
581
 
        # goes away.  
 
634
        # goes away.
582
635
        # <https://launchpad.net/products/bzr/+bug/5987>
583
636
        #
584
637
        # Running it in a separate process group is not good because then it
585
638
        # can't get non-echoed input of a password or passphrase.
586
639
        # <https://launchpad.net/products/bzr/+bug/40508>
587
 
        return {'preexec_fn': _ignore_sigint,
 
640
        return {'preexec_fn': _ignore_signals,
588
641
                'close_fds': True,
589
642
                }
590
643
 
 
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
 
591
654
 
592
655
class SSHSubprocess(object):
593
656
    """A socket-like object that talks to an ssh subprocess via pipes."""
594
657
 
595
658
    def __init__(self, proc):
596
659
        self.proc = proc
 
660
        # Add a weakref to proc that will attempt to do the same as self.close
 
661
        # to avoid leaving processes lingering indefinitely.
 
662
        def terminate(ref):
 
663
            _subproc_weakrefs.remove(ref)
 
664
            _close_ssh_proc(proc)
 
665
        _subproc_weakrefs.add(weakref.ref(self, terminate))
597
666
 
598
667
    def send(self, data):
599
668
        return os.write(self.proc.stdin.fileno(), data)
600
669
 
601
 
    def recv_ready(self):
602
 
        # TODO: jam 20051215 this function is necessary to support the
603
 
        # pipelined() function. In reality, it probably should use
604
 
        # poll() or select() to actually return if there is data
605
 
        # available, otherwise we probably don't get any benefit
606
 
        return True
607
 
 
608
670
    def recv(self, count):
609
671
        return os.read(self.proc.stdout.fileno(), count)
610
672
 
611
673
    def close(self):
612
 
        self.proc.stdin.close()
613
 
        self.proc.stdout.close()
614
 
        self.proc.wait()
 
674
        _close_ssh_proc(self.proc)
615
675
 
616
676
    def get_filelike_channels(self):
617
677
        return (self.proc.stdout, self.proc.stdin)