~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: Martin Pool
  • Date: 2009-03-18 02:17:57 UTC
  • mto: (4070.11.9 249908-doc-generate)
  • mto: This revision was merged to the branch mainline in revision 4165.
  • Revision ID: mbp@sourcefrog.net-20090318021757-k72mahr4tjxk9i2v
Quote URL scheme to stop ReST linkifying it

Show diffs side-by-side

added added

removed removed

Lines of Context:
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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
23
22
import os
24
23
import socket
25
24
import subprocess
94
93
            try:
95
94
                vendor = self._ssh_vendors[vendor_name]
96
95
            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
 
96
                raise errors.UnknownSSH(vendor_name)
101
97
            return vendor
102
98
        return None
103
99
 
113
109
            stdout = stderr = ''
114
110
        return stdout + stderr
115
111
 
116
 
    def _get_vendor_by_version_string(self, version, progname):
 
112
    def _get_vendor_by_version_string(self, version, args):
117
113
        """Return the vendor or None based on output from the subprocess.
118
114
 
119
115
        :param version: The output of 'ssh -V' like command.
126
122
        elif 'SSH Secure Shell' in version:
127
123
            trace.mutter('ssh implementation is SSH Corp.')
128
124
            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':
 
125
        elif 'plink' in version and args[0] == 'plink':
133
126
            # Checking if "plink" was the executed argument as Windows
134
127
            # sometimes reports 'ssh -V' incorrectly with 'plink' in it's
135
128
            # version.  See https://bugs.launchpad.net/bzr/+bug/107155
139
132
 
140
133
    def _get_vendor_by_inspection(self):
141
134
        """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])
 
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
150
141
 
151
142
    def get_vendor(self, environment=None):
152
143
        """Find out what version of SSH is on the system.
409
400
class OpenSSHSubprocessVendor(SubprocessVendor):
410
401
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
411
402
 
412
 
    executable_path = 'ssh'
413
 
 
414
403
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
415
404
                                  command=None):
416
 
        args = [self.executable_path,
 
405
        args = ['ssh',
417
406
                '-oForwardX11=no', '-oForwardAgent=no',
418
407
                '-oClearAllForwardings=yes', '-oProtocol=2',
419
408
                '-oNoHostAuthenticationForLocalhost=yes']
433
422
class SSHCorpSubprocessVendor(SubprocessVendor):
434
423
    """SSH vendor that uses the 'ssh' executable from SSH Corporation."""
435
424
 
436
 
    executable_path = 'ssh'
437
 
 
438
425
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
439
426
                                  command=None):
440
 
        args = [self.executable_path, '-x']
 
427
        args = ['ssh', '-x']
441
428
        if port is not None:
442
429
            args.extend(['-p', str(port)])
443
430
        if username is not None:
448
435
            args.extend([host] + command)
449
436
        return args
450
437
 
451
 
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
 
438
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
452
439
 
453
440
 
454
441
class PLinkSubprocessVendor(SubprocessVendor):
455
442
    """SSH vendor that uses the 'plink' executable from Putty."""
456
443
 
457
 
    executable_path = 'plink'
458
 
 
459
444
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
460
445
                                  command=None):
461
 
        args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
 
446
        args = ['plink', '-x', '-a', '-ssh', '-2', '-batch']
462
447
        if port is not None:
463
448
            args.extend(['-P', str(port)])
464
449
        if username is not None:
473
458
 
474
459
 
475
460
def _paramiko_auth(username, password, host, port, paramiko_transport):
476
 
    auth = config.AuthenticationConfig()
477
461
    # paramiko requires a username, but it might be none if nothing was
478
462
    # supplied.  If so, use the local username.
479
463
    if username is None:
480
 
        username = auth.get_user('ssh', host, port=port,
481
 
                                 default=getpass.getuser())
 
464
        username = getpass.getuser()
 
465
 
482
466
    if _use_ssh_agent:
483
467
        agent = paramiko.Agent()
484
468
        for key in agent.get_keys():
496
480
    if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
497
481
        return
498
482
 
499
 
    # If we have gotten this far, we are about to try for passwords, do an
500
 
    # auth_none check to see if it is even supported.
501
 
    supported_auth_types = []
502
 
    try:
503
 
        # Note that with paramiko <1.7.5 this logs an INFO message:
504
 
        #    Authentication type (none) not permitted.
505
 
        # So we explicitly disable the logging level for this action
506
 
        old_level = paramiko_transport.logger.level
507
 
        paramiko_transport.logger.setLevel(logging.WARNING)
508
 
        try:
509
 
            paramiko_transport.auth_none(username)
510
 
        finally:
511
 
            paramiko_transport.logger.setLevel(old_level)
512
 
    except paramiko.BadAuthenticationType, e:
513
 
        # Supported methods are in the exception
514
 
        supported_auth_types = e.allowed_types
515
 
    except paramiko.SSHException, e:
516
 
        # Don't know what happened, but just ignore it
517
 
        pass
518
 
    # We treat 'keyboard-interactive' and 'password' auth methods identically,
519
 
    # because Paramiko's auth_password method will automatically try
520
 
    # 'keyboard-interactive' auth (using the password as the response) if
521
 
    # 'password' auth is not available.  Apparently some Debian and Gentoo
522
 
    # OpenSSH servers require this.
523
 
    # XXX: It's possible for a server to require keyboard-interactive auth that
524
 
    # requires something other than a single password, but we currently don't
525
 
    # support that.
526
 
    if ('password' not in supported_auth_types and
527
 
        'keyboard-interactive' not in supported_auth_types):
528
 
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
529
 
            '\n  %s@%s\nsupported auth types: %s'
530
 
            % (username, host, supported_auth_types))
531
 
 
532
483
    if password:
533
484
        try:
534
485
            paramiko_transport.auth_password(username, password)
537
488
            pass
538
489
 
539
490
    # give up and ask for a password
 
491
    auth = config.AuthenticationConfig()
540
492
    password = auth.get_password('ssh', host, username, port=port)
541
 
    # get_password can still return None, which means we should not prompt
542
 
    if password is not None:
543
 
        try:
544
 
            paramiko_transport.auth_password(username, password)
545
 
        except paramiko.SSHException, e:
546
 
            raise errors.ConnectionError(
547
 
                'Unable to authenticate to SSH host as'
548
 
                '\n  %s@%s\n' % (username, host), e)
549
 
    else:
550
 
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
551
 
                                     '  %s@%s' % (username, host))
 
493
    try:
 
494
        paramiko_transport.auth_password(username, password)
 
495
    except paramiko.SSHException, e:
 
496
        raise errors.ConnectionError(
 
497
            'Unable to authenticate to SSH host as %s@%s' % (username, host), e)
552
498
 
553
499
 
554
500
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
638
584
                'close_fds': True,
639
585
                }
640
586
 
641
 
import weakref
642
 
_subproc_weakrefs = set()
643
 
 
644
 
def _close_ssh_proc(proc):
645
 
    for func in [proc.stdin.close, proc.stdout.close, proc.wait]:
646
 
        try:
647
 
            func()
648
 
        except OSError:
649
 
            pass
650
 
 
651
587
 
652
588
class SSHSubprocess(object):
653
589
    """A socket-like object that talks to an ssh subprocess via pipes."""
654
590
 
655
591
    def __init__(self, proc):
656
592
        self.proc = proc
657
 
        # Add a weakref to proc that will attempt to do the same as self.close
658
 
        # to avoid leaving processes lingering indefinitely.
659
 
        def terminate(ref):
660
 
            _subproc_weakrefs.remove(ref)
661
 
            _close_ssh_proc(proc)
662
 
        _subproc_weakrefs.add(weakref.ref(self, terminate))
663
593
 
664
594
    def send(self, data):
665
595
        return os.write(self.proc.stdin.fileno(), data)
668
598
        return os.read(self.proc.stdout.fileno(), count)
669
599
 
670
600
    def close(self):
671
 
        _close_ssh_proc(self.proc)
 
601
        self.proc.stdin.close()
 
602
        self.proc.stdout.close()
 
603
        self.proc.wait()
672
604
 
673
605
    def get_filelike_channels(self):
674
606
        return (self.proc.stdout, self.proc.stdin)