/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/transport/ssh.py

  • Committer: Jelmer Vernooij
  • Date: 2020-03-22 01:35:14 UTC
  • mfrom: (7490.7.6 work)
  • mto: This revision was merged to the branch mainline in revision 7499.
  • Revision ID: jelmer@jelmer.uk-20200322013514-7vw1ntwho04rcuj3
merge lp:brz/3.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
30
28
 
31
29
from .. import (
32
30
    config,
 
31
    bedding,
33
32
    errors,
34
33
    osutils,
35
34
    trace,
54
53
BRZ_HOSTKEYS = {}
55
54
 
56
55
 
57
 
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
58
 
 
59
 
# Paramiko 1.5 tries to open a socket.AF_UNIX in order to connect
60
 
# to ssh-agent. That attribute doesn't exist on win32 (it does in cygwin)
61
 
# so we get an AttributeError exception. So we will not try to
62
 
# connect to an agent if we are on win32 and using Paramiko older than 1.6
63
 
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
64
 
 
65
 
 
66
56
class SSHVendorManager(object):
67
57
    """Manager for manage SSH vendors."""
68
58
 
88
78
        """Clear previously cached lookup result."""
89
79
        self._cached_ssh_vendor = None
90
80
 
91
 
    def _get_vendor_by_environment(self, environment=None):
92
 
        """Return the vendor or None based on BRZ_SSH environment variable.
93
 
 
94
 
        :raises UnknownSSH: if the BRZ_SSH environment variable contains
95
 
                            unknown vendor name
96
 
        """
97
 
        if environment is None:
98
 
            environment = os.environ
99
 
        if 'BRZ_SSH' in environment:
100
 
            vendor_name = environment['BRZ_SSH']
 
81
    def _get_vendor_by_config(self):
 
82
        vendor_name = config.GlobalStack().get('ssh')
 
83
        if vendor_name is not None:
101
84
            try:
102
85
                vendor = self._ssh_vendors[vendor_name]
103
86
            except KeyError:
114
97
            p = subprocess.Popen(args,
115
98
                                 stdout=subprocess.PIPE,
116
99
                                 stderr=subprocess.PIPE,
 
100
                                 bufsize=0,
117
101
                                 **os_specific_subprocess_params())
118
102
            stdout, stderr = p.communicate()
119
103
        except OSError:
120
 
            stdout = stderr = ''
121
 
        return stdout + stderr
 
104
            stdout = stderr = b''
 
105
        return (stdout + stderr).decode(osutils.get_terminal_encoding())
122
106
 
123
107
    def _get_vendor_by_version_string(self, version, progname):
124
108
        """Return the vendor or None based on output from the subprocess.
155
139
    def _get_vendor_from_path(self, path):
156
140
        """Return the vendor or None using the program at the given path"""
157
141
        version = self._get_ssh_version_string([path, '-V'])
158
 
        return self._get_vendor_by_version_string(version, 
159
 
            os.path.splitext(os.path.basename(path))[0])
 
142
        return self._get_vendor_by_version_string(version,
 
143
                                                  os.path.splitext(os.path.basename(path))[0])
160
144
 
161
 
    def get_vendor(self, environment=None):
 
145
    def get_vendor(self):
162
146
        """Find out what version of SSH is on the system.
163
147
 
164
148
        :raises SSHVendorNotFound: if no any SSH vendor is found
166
150
                            unknown vendor name
167
151
        """
168
152
        if self._cached_ssh_vendor is None:
169
 
            vendor = self._get_vendor_by_environment(environment)
 
153
            vendor = self._get_vendor_by_config()
170
154
            if vendor is None:
171
155
                vendor = self._get_vendor_by_inspection()
172
156
                if vendor is None:
177
161
            self._cached_ssh_vendor = vendor
178
162
        return self._cached_ssh_vendor
179
163
 
 
164
 
180
165
_ssh_vendor_manager = SSHVendorManager()
181
166
_get_ssh_vendor = _ssh_vendor_manager.get_vendor
182
167
register_default_ssh_vendor = _ssh_vendor_manager.register_default_vendor
275
260
            self._raise_connection_error(host, port=port, orig_error=e)
276
261
        return SFTPClient(SocketAsChannelAdapter(sock))
277
262
 
 
263
 
278
264
register_ssh_vendor('loopback', LoopbackVendor())
279
265
 
280
266
 
309
295
            trace.warning('Adding %s host key for %s: %s'
310
296
                          % (keytype, host, server_key_hex))
311
297
            add = getattr(BRZ_HOSTKEYS, 'add', None)
312
 
            if add is not None: # paramiko >= 1.X.X
 
298
            if add is not None:  # paramiko >= 1.X.X
313
299
                BRZ_HOSTKEYS.add(host, keytype, server_key)
314
300
            else:
315
301
                BRZ_HOSTKEYS.setdefault(host, {})[keytype] = server_key
318
304
            save_host_keys()
319
305
        if server_key != our_server_key:
320
306
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
321
 
            filename2 = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
 
307
            filename2 = _ssh_host_keys_config_dir()
322
308
            raise errors.TransportError(
323
309
                'Host keys for %s do not match!  %s != %s' %
324
310
                (host, our_server_key_hex, server_key_hex),
346
332
            self._raise_connection_error(host, port=port, orig_error=e,
347
333
                                         msg='Unable to invoke remote bzr')
348
334
 
 
335
 
349
336
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
350
337
if paramiko is not None:
351
338
    vendor = ParamikoVendor()
386
373
            stdin = stdout = subproc_sock
387
374
        proc = subprocess.Popen(argv, stdin=stdin, stdout=stdout,
388
375
                                stderr=self._stderr_target,
 
376
                                bufsize=0,
389
377
                                **os_specific_subprocess_params())
390
378
        if subproc_sock is not None:
391
379
            subproc_sock.close()
438
426
            args.extend(['--', host] + command)
439
427
        return args
440
428
 
 
429
 
441
430
register_ssh_vendor('openssh', OpenSSHSubprocessVendor())
442
431
 
443
432
 
460
449
            args.extend([host] + command)
461
450
        return args
462
451
 
 
452
 
463
453
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
464
454
 
465
455
 
482
472
            args.extend([host] + command)
483
473
        return args
484
474
 
 
475
 
485
476
register_ssh_vendor('lsh', LSHSubprocessVendor())
486
477
 
487
478
 
504
495
            args.extend([host] + command)
505
496
        return args
506
497
 
 
498
 
507
499
register_ssh_vendor('plink', PLinkSubprocessVendor())
508
500
 
509
501
 
514
506
    if username is None:
515
507
        username = auth.get_user('ssh', host, port=port,
516
508
                                 default=getpass.getuser())
517
 
    if _use_ssh_agent:
518
 
        agent = paramiko.Agent()
519
 
        for key in agent.get_keys():
520
 
            trace.mutter('Trying SSH agent key %s'
521
 
                         % self._hexify(key.get_fingerprint()))
522
 
            try:
523
 
                paramiko_transport.auth_publickey(username, key)
524
 
                return
525
 
            except paramiko.SSHException as e:
526
 
                pass
 
509
    agent = paramiko.Agent()
 
510
    for key in agent.get_keys():
 
511
        trace.mutter('Trying SSH agent key %s'
 
512
                     % hexlify(key.get_fingerprint()).upper())
 
513
        try:
 
514
            paramiko_transport.auth_publickey(username, key)
 
515
            return
 
516
        except paramiko.SSHException as e:
 
517
            pass
527
518
 
528
519
    # okay, try finding id_rsa or id_dss?  (posix only)
529
520
    if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
559
550
    # requires something other than a single password, but we currently don't
560
551
    # support that.
561
552
    if ('password' not in supported_auth_types and
562
 
        'keyboard-interactive' not in supported_auth_types):
 
553
            'keyboard-interactive' not in supported_auth_types):
563
554
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
564
 
            '\n  %s@%s\nsupported auth types: %s'
565
 
            % (username, host, supported_auth_types))
 
555
                                     '\n  %s@%s\nsupported auth types: %s'
 
556
                                     % (username, host, supported_auth_types))
566
557
 
567
558
    if password:
568
559
        try:
611
602
    return False
612
603
 
613
604
 
 
605
def _ssh_host_keys_config_dir():
 
606
    return osutils.pathjoin(bedding.config_dir(), 'ssh_host_keys')
 
607
 
 
608
 
614
609
def load_host_keys():
615
610
    """
616
611
    Load system host keys (probably doesn't work on windows) and any
622
617
            os.path.expanduser('~/.ssh/known_hosts'))
623
618
    except IOError as e:
624
619
        trace.mutter('failed to load system host keys: ' + str(e))
625
 
    brz_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
 
620
    brz_hostkey_path = _ssh_host_keys_config_dir()
626
621
    try:
627
622
        BRZ_HOSTKEYS = paramiko.util.load_host_keys(brz_hostkey_path)
628
623
    except IOError as e:
635
630
    Save "discovered" host keys in $(config)/ssh_host_keys/.
636
631
    """
637
632
    global SYSTEM_HOSTKEYS, BRZ_HOSTKEYS
638
 
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
639
 
    config.ensure_config_dir_exists()
 
633
    bzr_hostkey_path = _ssh_host_keys_config_dir()
 
634
    bedding.ensure_config_dir_exists()
640
635
 
641
636
    try:
642
 
        f = open(bzr_hostkey_path, 'w')
643
 
        f.write('# SSH host keys collected by bzr\n')
644
 
        for hostname, keys in BRZ_HOSTKEYS.items():
645
 
            for keytype, key in keys.items():
646
 
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
647
 
        f.close()
 
637
        with open(bzr_hostkey_path, 'w') as f:
 
638
            f.write('# SSH host keys collected by bzr\n')
 
639
            for hostname, keys in BRZ_HOSTKEYS.items():
 
640
                for keytype, key in keys.items():
 
641
                    f.write('%s %s %s\n' %
 
642
                            (hostname, keytype, key.get_base64()))
648
643
    except IOError as e:
649
644
        trace.mutter('failed to save bzr host keys: ' + str(e))
650
645
 
674
669
                'close_fds': True,
675
670
                }
676
671
 
 
672
 
677
673
import weakref
678
674
_subproc_weakrefs = set()
679
675
 
 
676
 
680
677
def _close_ssh_proc(proc, sock):
681
678
    """Carefully close stdin/stdout and reap the SSH process.
682
679
 
737
734
        self._sock = sock
738
735
        # Add a weakref to proc that will attempt to do the same as self.close
739
736
        # to avoid leaving processes lingering indefinitely.
 
737
 
740
738
        def terminate(ref):
741
739
            _subproc_weakrefs.remove(ref)
742
740
            _close_ssh_proc(proc, sock)
775
773
 
776
774
    def close(self):
777
775
        return self.channel.close()
778
 
 
779