~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: 2010-09-01 08:02:42 UTC
  • mfrom: (5390.3.3 faster-revert-593560)
  • Revision ID: pqm@pqm.ubuntu.com-20100901080242-esg62ody4frwmy66
(spiv) Avoid repeatedly calling self.target.all_file_ids() in
 InterTree.iter_changes. (Andrew Bennetts)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 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
17
17
 
18
18
"""Foundation SSH support for SFTP and smart server."""
19
19
 
20
 
from __future__ import absolute_import
21
 
 
22
20
import errno
23
21
import getpass
24
22
import logging
26
24
import socket
27
25
import subprocess
28
26
import sys
29
 
from binascii import hexlify
30
27
 
31
28
from bzrlib import (
32
29
    config,
129
126
        elif 'SSH Secure Shell' in version:
130
127
            trace.mutter('ssh implementation is SSH Corp.')
131
128
            vendor = SSHCorpSubprocessVendor()
132
 
        elif 'lsh' in version:
133
 
            trace.mutter('ssh implementation is GNU lsh.')
134
 
            vendor = LSHSubprocessVendor()
135
129
        # As plink user prompts are not handled currently, don't auto-detect
136
130
        # it by inspection below, but keep this vendor detection for if a path
137
131
        # is given in BZR_SSH. See https://bugs.launchpad.net/bugs/414743
138
132
        elif 'plink' in version and progname == 'plink':
139
133
            # Checking if "plink" was the executed argument as Windows
140
 
            # sometimes reports 'ssh -V' incorrectly with 'plink' in its
 
134
            # sometimes reports 'ssh -V' incorrectly with 'plink' in it's
141
135
            # version.  See https://bugs.launchpad.net/bzr/+bug/107155
142
136
            trace.mutter("ssh implementation is Putty's plink.")
143
137
            vendor = PLinkSubprocessVendor()
277
271
class ParamikoVendor(SSHVendor):
278
272
    """Vendor that uses paramiko."""
279
273
 
280
 
    def _hexify(self, s):
281
 
        return hexlify(s).upper()
282
 
 
283
274
    def _connect(self, username, password, host, port):
284
275
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
285
276
 
293
284
            self._raise_connection_error(host, port=port, orig_error=e)
294
285
 
295
286
        server_key = t.get_remote_server_key()
296
 
        server_key_hex = self._hexify(server_key.get_fingerprint())
 
287
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
297
288
        keytype = server_key.get_name()
298
289
        if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
299
290
            our_server_key = SYSTEM_HOSTKEYS[host][keytype]
300
 
            our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
 
291
            our_server_key_hex = paramiko.util.hexify(
 
292
                our_server_key.get_fingerprint())
301
293
        elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
302
294
            our_server_key = BZR_HOSTKEYS[host][keytype]
303
 
            our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
 
295
            our_server_key_hex = paramiko.util.hexify(
 
296
                our_server_key.get_fingerprint())
304
297
        else:
305
298
            trace.warning('Adding %s host key for %s: %s'
306
299
                          % (keytype, host, server_key_hex))
310
303
            else:
311
304
                BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
312
305
            our_server_key = server_key
313
 
            our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
 
306
            our_server_key_hex = paramiko.util.hexify(
 
307
                our_server_key.get_fingerprint())
314
308
            save_host_keys()
315
309
        if server_key != our_server_key:
316
310
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
342
336
            self._raise_connection_error(host, port=port, orig_error=e,
343
337
                                         msg='Unable to invoke remote bzr')
344
338
 
345
 
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
346
339
if paramiko is not None:
347
340
    vendor = ParamikoVendor()
348
341
    register_ssh_vendor('paramiko', vendor)
349
342
    register_ssh_vendor('none', vendor)
350
343
    register_default_ssh_vendor(vendor)
351
 
    _ssh_connection_errors += (paramiko.SSHException,)
 
344
    _sftp_connection_errors = (EOFError, paramiko.SSHException)
352
345
    del vendor
 
346
else:
 
347
    _sftp_connection_errors = (EOFError,)
353
348
 
354
349
 
355
350
class SubprocessVendor(SSHVendor):
356
351
    """Abstract base class for vendors that use pipes to a subprocess."""
357
352
 
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
 
 
363
353
    def _connect(self, argv):
364
354
        # Attempt to make a socketpair to use as stdin/stdout for the SSH
365
355
        # subprocess.  We prefer sockets to pipes because they support
367
357
        # whatever) chunks.
368
358
        try:
369
359
            my_sock, subproc_sock = socket.socketpair()
370
 
            osutils.set_fd_cloexec(my_sock)
371
360
        except (AttributeError, socket.error):
372
361
            # This platform doesn't support socketpair(), so just use ordinary
373
362
            # pipes instead.
374
363
            stdin = stdout = subprocess.PIPE
375
 
            my_sock, subproc_sock = None, None
 
364
            sock = None
376
365
        else:
377
366
            stdin = stdout = subproc_sock
 
367
            sock = my_sock
378
368
        proc = subprocess.Popen(argv, stdin=stdin, stdout=stdout,
379
 
                                stderr=self._stderr_target,
380
369
                                **os_specific_subprocess_params())
381
 
        if subproc_sock is not None:
382
 
            subproc_sock.close()
383
 
        return SSHSubprocessConnection(proc, sock=my_sock)
 
370
        return SSHSubprocessConnection(proc, sock=sock)
384
371
 
385
372
    def connect_sftp(self, username, password, host, port):
386
373
        try:
388
375
                                                  subsystem='sftp')
389
376
            sock = self._connect(argv)
390
377
            return SFTPClient(SocketAsChannelAdapter(sock))
391
 
        except _ssh_connection_errors, e:
 
378
        except _sftp_connection_errors, e:
 
379
            self._raise_connection_error(host, port=port, orig_error=e)
 
380
        except (OSError, IOError), e:
 
381
            # If the machine is fast enough, ssh can actually exit
 
382
            # before we try and send it the sftp request, which
 
383
            # raises a Broken Pipe
 
384
            if e.errno not in (errno.EPIPE,):
 
385
                raise
392
386
            self._raise_connection_error(host, port=port, orig_error=e)
393
387
 
394
388
    def connect_ssh(self, username, password, host, port, command):
396
390
            argv = self._get_vendor_specific_argv(username, host, port,
397
391
                                                  command=command)
398
392
            return self._connect(argv)
399
 
        except _ssh_connection_errors, e:
 
393
        except (EOFError), e:
 
394
            self._raise_connection_error(host, port=port, orig_error=e)
 
395
        except (OSError, IOError), e:
 
396
            # If the machine is fast enough, ssh can actually exit
 
397
            # before we try and send it the sftp request, which
 
398
            # raises a Broken Pipe
 
399
            if e.errno not in (errno.EPIPE,):
 
400
                raise
400
401
            self._raise_connection_error(host, port=port, orig_error=e)
401
402
 
402
403
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
417
418
                                  command=None):
418
419
        args = [self.executable_path,
419
420
                '-oForwardX11=no', '-oForwardAgent=no',
420
 
                '-oClearAllForwardings=yes',
 
421
                '-oClearAllForwardings=yes', '-oProtocol=2',
421
422
                '-oNoHostAuthenticationForLocalhost=yes']
422
423
        if port is not None:
423
424
            args.extend(['-p', str(port)])
453
454
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
454
455
 
455
456
 
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
 
 
477
457
class PLinkSubprocessVendor(SubprocessVendor):
478
458
    """SSH vendor that uses the 'plink' executable from Putty."""
479
459
 
506
486
        agent = paramiko.Agent()
507
487
        for key in agent.get_keys():
508
488
            trace.mutter('Trying SSH agent key %s'
509
 
                         % self._hexify(key.get_fingerprint()))
 
489
                         % paramiko.util.hexify(key.get_fingerprint()))
510
490
            try:
511
491
                paramiko_transport.auth_publickey(username, key)
512
492
                return
582
562
        return True
583
563
    except paramiko.PasswordRequiredException:
584
564
        password = ui.ui_factory.get_password(
585
 
            prompt=u'SSH %(filename)s password',
586
 
            filename=filename.decode(osutils._fs_enc))
 
565
            prompt='SSH %(filename)s password', filename=filename)
587
566
        try:
588
567
            key = pkey_class.from_private_key_file(filename, password)
589
568
            paramiko_transport.auth_publickey(username, key)
665
644
import weakref
666
645
_subproc_weakrefs = set()
667
646
 
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:
 
647
def _close_ssh_proc(proc):
 
648
    for func in [proc.stdin.close, proc.stdout.close, proc.wait]:
684
649
        try:
685
650
            func()
686
651
        except OSError:
687
 
            # It's ok for the pipe to already be closed, or the process to
688
 
            # already be finished.
689
 
            continue
 
652
            pass
690
653
 
691
654
 
692
655
class SSHConnection(object):
727
690
        # to avoid leaving processes lingering indefinitely.
728
691
        def terminate(ref):
729
692
            _subproc_weakrefs.remove(ref)
730
 
            _close_ssh_proc(proc, sock)
 
693
            _close_ssh_proc(proc)
731
694
        _subproc_weakrefs.add(weakref.ref(self, terminate))
732
695
 
733
696
    def send(self, data):
743
706
            return os.read(self.proc.stdout.fileno(), count)
744
707
 
745
708
    def close(self):
746
 
        _close_ssh_proc(self.proc, self._sock)
 
709
        _close_ssh_proc(self.proc)
747
710
 
748
711
    def get_sock_or_pipes(self):
749
712
        if self._sock is not None: