~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: 2009-04-10 19:37:20 UTC
  • mfrom: (4222.3.15 username)
  • Revision ID: pqm@pqm.ubuntu.com-20090410193720-nyej7ft1k2yoyhui
(Jelmer) Prompt for user names for http if they are not in the
        configuration.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Robey Pointer <robey@lag.net>
 
1
# Copyright (C) 2005 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
18
18
"""Foundation SSH support for SFTP and smart server."""
19
19
 
20
20
import errno
21
 
import getpass
22
 
import logging
23
21
import os
24
22
import socket
25
23
import subprocess
94
92
            try:
95
93
                vendor = self._ssh_vendors[vendor_name]
96
94
            except KeyError:
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
 
95
                raise errors.UnknownSSH(vendor_name)
101
96
            return vendor
102
97
        return None
103
98
 
113
108
            stdout = stderr = ''
114
109
        return stdout + stderr
115
110
 
116
 
    def _get_vendor_by_version_string(self, version, progname):
 
111
    def _get_vendor_by_version_string(self, version, args):
117
112
        """Return the vendor or None based on output from the subprocess.
118
113
 
119
114
        :param version: The output of 'ssh -V' like command.
126
121
        elif 'SSH Secure Shell' in version:
127
122
            trace.mutter('ssh implementation is SSH Corp.')
128
123
            vendor = SSHCorpSubprocessVendor()
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':
 
124
        elif 'plink' in version and args[0] == 'plink':
133
125
            # Checking if "plink" was the executed argument as Windows
134
126
            # sometimes reports 'ssh -V' incorrectly with 'plink' in it's
135
127
            # version.  See https://bugs.launchpad.net/bzr/+bug/107155
139
131
 
140
132
    def _get_vendor_by_inspection(self):
141
133
        """Return the vendor or None by checking for known SSH implementations."""
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])
 
134
        for args in (['ssh', '-V'], ['plink', '-V']):
 
135
            version = self._get_ssh_version_string(args)
 
136
            vendor = self._get_vendor_by_version_string(version, args)
 
137
            if vendor is not None:
 
138
                return vendor
 
139
        return None
150
140
 
151
141
    def get_vendor(self, environment=None):
152
142
        """Find out what version of SSH is on the system.
173
163
register_ssh_vendor = _ssh_vendor_manager.register_vendor
174
164
 
175
165
 
176
 
def _ignore_signals():
 
166
def _ignore_sigint():
177
167
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
178
168
    # doesn't handle it itself.
179
169
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
180
170
    import signal
181
171
    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)
185
172
 
186
173
 
187
174
class SocketAsChannelAdapter(object):
412
399
class OpenSSHSubprocessVendor(SubprocessVendor):
413
400
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
414
401
 
415
 
    executable_path = 'ssh'
416
 
 
417
402
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
418
403
                                  command=None):
419
 
        args = [self.executable_path,
 
404
        args = ['ssh',
420
405
                '-oForwardX11=no', '-oForwardAgent=no',
421
406
                '-oClearAllForwardings=yes', '-oProtocol=2',
422
407
                '-oNoHostAuthenticationForLocalhost=yes']
436
421
class SSHCorpSubprocessVendor(SubprocessVendor):
437
422
    """SSH vendor that uses the 'ssh' executable from SSH Corporation."""
438
423
 
439
 
    executable_path = 'ssh'
440
 
 
441
424
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
442
425
                                  command=None):
443
 
        args = [self.executable_path, '-x']
 
426
        args = ['ssh', '-x']
444
427
        if port is not None:
445
428
            args.extend(['-p', str(port)])
446
429
        if username is not None:
451
434
            args.extend([host] + command)
452
435
        return args
453
436
 
454
 
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
 
437
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
455
438
 
456
439
 
457
440
class PLinkSubprocessVendor(SubprocessVendor):
458
441
    """SSH vendor that uses the 'plink' executable from Putty."""
459
442
 
460
 
    executable_path = 'plink'
461
 
 
462
443
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
463
444
                                  command=None):
464
 
        args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
 
445
        args = ['plink', '-x', '-a', '-ssh', '-2', '-batch']
465
446
        if port is not None:
466
447
            args.extend(['-P', str(port)])
467
448
        if username is not None:
480
461
    # paramiko requires a username, but it might be none if nothing was
481
462
    # supplied.  If so, use the local username.
482
463
    if username is None:
483
 
        username = auth.get_user('ssh', host, port=port,
484
 
                                 default=getpass.getuser())
 
464
        username = auth.get_user('ssh', host, port=port)
 
465
 
485
466
    if _use_ssh_agent:
486
467
        agent = paramiko.Agent()
487
468
        for key in agent.get_keys():
499
480
    if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
500
481
        return
501
482
 
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
 
 
535
483
    if password:
536
484
        try:
537
485
            paramiko_transport.auth_password(username, password)
541
489
 
542
490
    # give up and ask for a password
543
491
    password = auth.get_password('ssh', host, username, port=port)
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))
 
492
    try:
 
493
        paramiko_transport.auth_password(username, password)
 
494
    except paramiko.SSHException, e:
 
495
        raise errors.ConnectionError(
 
496
            'Unable to authenticate to SSH host as %s@%s' % (username, host), e)
555
497
 
556
498
 
557
499
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
637
579
        # Running it in a separate process group is not good because then it
638
580
        # can't get non-echoed input of a password or passphrase.
639
581
        # <https://launchpad.net/products/bzr/+bug/40508>
640
 
        return {'preexec_fn': _ignore_signals,
 
582
        return {'preexec_fn': _ignore_sigint,
641
583
                'close_fds': True,
642
584
                }
643
585
 
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
586
 
655
587
class SSHSubprocess(object):
656
588
    """A socket-like object that talks to an ssh subprocess via pipes."""
657
589
 
658
590
    def __init__(self, proc):
659
591
        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))
666
592
 
667
593
    def send(self, data):
668
594
        return os.write(self.proc.stdin.fileno(), data)
671
597
        return os.read(self.proc.stdout.fileno(), count)
672
598
 
673
599
    def close(self):
674
 
        _close_ssh_proc(self.proc)
 
600
        self.proc.stdin.close()
 
601
        self.proc.stdout.close()
 
602
        self.proc.wait()
675
603
 
676
604
    def get_filelike_channels(self):
677
605
        return (self.proc.stdout, self.proc.stdin)