/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: 2018-03-31 17:16:06 UTC
  • mto: (6883.23.7 bundle-git)
  • mto: This revision was merged to the branch mainline in revision 6955.
  • Revision ID: jelmer@jelmer.uk-20180331171606-kc0lxw0uoxinxx4p
add --lossy option to 'bzr push'.

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) 2006-2011 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
 
20
22
import errno
21
23
import getpass
22
24
import logging
24
26
import socket
25
27
import subprocess
26
28
import sys
 
29
from binascii import hexlify
27
30
 
28
 
from bzrlib import (
 
31
from .. import (
29
32
    config,
30
33
    errors,
31
34
    osutils,
35
38
 
36
39
try:
37
40
    import paramiko
38
 
except ImportError, e:
 
41
except ImportError as e:
39
42
    # If we have an ssh subprocess, we don't strictly need paramiko for all ssh
40
43
    # access
41
44
    paramiko = None
43
46
    from paramiko.sftp_client import SFTPClient
44
47
 
45
48
 
 
49
class StrangeHostname(errors.BzrError):
 
50
    _fmt = "Refusing to connect to strange SSH hostname %(hostname)s"
 
51
 
 
52
 
46
53
SYSTEM_HOSTKEYS = {}
47
 
BZR_HOSTKEYS = {}
 
54
BRZ_HOSTKEYS = {}
48
55
 
49
56
 
50
57
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
60
67
    """Manager for manage SSH vendors."""
61
68
 
62
69
    # Note, although at first sign the class interface seems similar to
63
 
    # bzrlib.registry.Registry it is not possible/convenient to directly use
 
70
    # breezy.registry.Registry it is not possible/convenient to directly use
64
71
    # the Registry because the class just has "get()" interface instead of the
65
72
    # Registry's "get(key)".
66
73
 
82
89
        self._cached_ssh_vendor = None
83
90
 
84
91
    def _get_vendor_by_environment(self, environment=None):
85
 
        """Return the vendor or None based on BZR_SSH environment variable.
 
92
        """Return the vendor or None based on BRZ_SSH environment variable.
86
93
 
87
 
        :raises UnknownSSH: if the BZR_SSH environment variable contains
 
94
        :raises UnknownSSH: if the BRZ_SSH environment variable contains
88
95
                            unknown vendor name
89
96
        """
90
97
        if environment is None:
91
98
            environment = os.environ
92
 
        if 'BZR_SSH' in environment:
93
 
            vendor_name = environment['BZR_SSH']
 
99
        if 'BRZ_SSH' in environment:
 
100
            vendor_name = environment['BRZ_SSH']
94
101
            try:
95
102
                vendor = self._ssh_vendors[vendor_name]
96
103
            except KeyError:
126
133
        elif 'SSH Secure Shell' in version:
127
134
            trace.mutter('ssh implementation is SSH Corp.')
128
135
            vendor = SSHCorpSubprocessVendor()
 
136
        elif 'lsh' in version:
 
137
            trace.mutter('ssh implementation is GNU lsh.')
 
138
            vendor = LSHSubprocessVendor()
129
139
        # As plink user prompts are not handled currently, don't auto-detect
130
140
        # 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
 
141
        # is given in BRZ_SSH. See https://bugs.launchpad.net/bugs/414743
132
142
        elif 'plink' in version and progname == 'plink':
133
143
            # Checking if "plink" was the executed argument as Windows
134
 
            # sometimes reports 'ssh -V' incorrectly with 'plink' in it's
 
144
            # sometimes reports 'ssh -V' incorrectly with 'plink' in its
135
145
            # version.  See https://bugs.launchpad.net/bzr/+bug/107155
136
146
            trace.mutter("ssh implementation is Putty's plink.")
137
147
            vendor = PLinkSubprocessVendor()
152
162
        """Find out what version of SSH is on the system.
153
163
 
154
164
        :raises SSHVendorNotFound: if no any SSH vendor is found
155
 
        :raises UnknownSSH: if the BZR_SSH environment variable contains
 
165
        :raises UnknownSSH: if the BRZ_SSH environment variable contains
156
166
                            unknown vendor name
157
167
        """
158
168
        if self._cached_ssh_vendor is None:
199
209
    def recv(self, n):
200
210
        try:
201
211
            return self.__socket.recv(n)
202
 
        except socket.error, e:
 
212
        except socket.error as e:
203
213
            if e.args[0] in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED,
204
214
                             errno.EBADF):
205
215
                # Connection has closed.  Paramiko expects an empty string in
239
249
    def connect_ssh(self, username, password, host, port, command):
240
250
        """Make an SSH connection.
241
251
 
242
 
        :returns: something with a `close` method, and a `get_filelike_channels`
243
 
            method that returns a pair of (read, write) filelike objects.
 
252
        :returns: an SSHConnection.
244
253
        """
245
254
        raise NotImplementedError(self.connect_ssh)
246
255
 
262
271
        sock = socket.socket()
263
272
        try:
264
273
            sock.connect((host, port))
265
 
        except socket.error, e:
 
274
        except socket.error as e:
266
275
            self._raise_connection_error(host, port=port, orig_error=e)
267
276
        return SFTPClient(SocketAsChannelAdapter(sock))
268
277
 
269
278
register_ssh_vendor('loopback', LoopbackVendor())
270
279
 
271
280
 
272
 
class _ParamikoSSHConnection(object):
273
 
    def __init__(self, channel):
274
 
        self.channel = channel
275
 
 
276
 
    def get_filelike_channels(self):
277
 
        return self.channel.makefile('rb'), self.channel.makefile('wb')
278
 
 
279
 
    def close(self):
280
 
        return self.channel.close()
281
 
 
282
 
 
283
281
class ParamikoVendor(SSHVendor):
284
282
    """Vendor that uses paramiko."""
285
283
 
 
284
    def _hexify(self, s):
 
285
        return hexlify(s).upper()
 
286
 
286
287
    def _connect(self, username, password, host, port):
287
 
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
 
288
        global SYSTEM_HOSTKEYS, BRZ_HOSTKEYS
288
289
 
289
290
        load_host_keys()
290
291
 
292
293
            t = paramiko.Transport((host, port or 22))
293
294
            t.set_log_channel('bzr.paramiko')
294
295
            t.start_client()
295
 
        except (paramiko.SSHException, socket.error), e:
 
296
        except (paramiko.SSHException, socket.error) as e:
296
297
            self._raise_connection_error(host, port=port, orig_error=e)
297
298
 
298
299
        server_key = t.get_remote_server_key()
299
 
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
 
300
        server_key_hex = self._hexify(server_key.get_fingerprint())
300
301
        keytype = server_key.get_name()
301
302
        if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
302
303
            our_server_key = SYSTEM_HOSTKEYS[host][keytype]
303
 
            our_server_key_hex = paramiko.util.hexify(
304
 
                our_server_key.get_fingerprint())
305
 
        elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
306
 
            our_server_key = BZR_HOSTKEYS[host][keytype]
307
 
            our_server_key_hex = paramiko.util.hexify(
308
 
                our_server_key.get_fingerprint())
 
304
            our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
 
305
        elif host in BRZ_HOSTKEYS and keytype in BRZ_HOSTKEYS[host]:
 
306
            our_server_key = BRZ_HOSTKEYS[host][keytype]
 
307
            our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
309
308
        else:
310
309
            trace.warning('Adding %s host key for %s: %s'
311
310
                          % (keytype, host, server_key_hex))
312
 
            add = getattr(BZR_HOSTKEYS, 'add', None)
 
311
            add = getattr(BRZ_HOSTKEYS, 'add', None)
313
312
            if add is not None: # paramiko >= 1.X.X
314
 
                BZR_HOSTKEYS.add(host, keytype, server_key)
 
313
                BRZ_HOSTKEYS.add(host, keytype, server_key)
315
314
            else:
316
 
                BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
 
315
                BRZ_HOSTKEYS.setdefault(host, {})[keytype] = server_key
317
316
            our_server_key = server_key
318
 
            our_server_key_hex = paramiko.util.hexify(
319
 
                our_server_key.get_fingerprint())
 
317
            our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
320
318
            save_host_keys()
321
319
        if server_key != our_server_key:
322
320
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
333
331
        t = self._connect(username, password, host, port)
334
332
        try:
335
333
            return t.open_sftp_client()
336
 
        except paramiko.SSHException, e:
 
334
        except paramiko.SSHException as e:
337
335
            self._raise_connection_error(host, port=port, orig_error=e,
338
336
                                         msg='Unable to start sftp client')
339
337
 
344
342
            cmdline = ' '.join(command)
345
343
            channel.exec_command(cmdline)
346
344
            return _ParamikoSSHConnection(channel)
347
 
        except paramiko.SSHException, e:
 
345
        except paramiko.SSHException as e:
348
346
            self._raise_connection_error(host, port=port, orig_error=e,
349
347
                                         msg='Unable to invoke remote bzr')
350
348
 
 
349
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
351
350
if paramiko is not None:
352
351
    vendor = ParamikoVendor()
353
352
    register_ssh_vendor('paramiko', vendor)
354
353
    register_ssh_vendor('none', vendor)
355
354
    register_default_ssh_vendor(vendor)
356
 
    _sftp_connection_errors = (EOFError, paramiko.SSHException)
 
355
    _ssh_connection_errors += (paramiko.SSHException,)
357
356
    del vendor
358
 
else:
359
 
    _sftp_connection_errors = (EOFError,)
360
357
 
361
358
 
362
359
class SubprocessVendor(SSHVendor):
363
360
    """Abstract base class for vendors that use pipes to a subprocess."""
364
361
 
 
362
    # In general stderr should be inherited from the parent process so prompts
 
363
    # are visible on the terminal. This can be overriden to another file for
 
364
    # tests, but beware of using PIPE which may hang due to not being read.
 
365
    _stderr_target = None
 
366
 
 
367
    @staticmethod
 
368
    def _check_hostname(arg):
 
369
        if arg.startswith('-'):
 
370
            raise StrangeHostname(hostname=arg)
 
371
 
365
372
    def _connect(self, argv):
366
 
        proc = subprocess.Popen(argv,
367
 
                                stdin=subprocess.PIPE,
368
 
                                stdout=subprocess.PIPE,
 
373
        # Attempt to make a socketpair to use as stdin/stdout for the SSH
 
374
        # subprocess.  We prefer sockets to pipes because they support
 
375
        # non-blocking short reads, allowing us to optimistically read 64k (or
 
376
        # whatever) chunks.
 
377
        try:
 
378
            my_sock, subproc_sock = socket.socketpair()
 
379
            osutils.set_fd_cloexec(my_sock)
 
380
        except (AttributeError, socket.error):
 
381
            # This platform doesn't support socketpair(), so just use ordinary
 
382
            # pipes instead.
 
383
            stdin = stdout = subprocess.PIPE
 
384
            my_sock, subproc_sock = None, None
 
385
        else:
 
386
            stdin = stdout = subproc_sock
 
387
        proc = subprocess.Popen(argv, stdin=stdin, stdout=stdout,
 
388
                                stderr=self._stderr_target,
369
389
                                **os_specific_subprocess_params())
370
 
        return SSHSubprocess(proc)
 
390
        if subproc_sock is not None:
 
391
            subproc_sock.close()
 
392
        return SSHSubprocessConnection(proc, sock=my_sock)
371
393
 
372
394
    def connect_sftp(self, username, password, host, port):
373
395
        try:
375
397
                                                  subsystem='sftp')
376
398
            sock = self._connect(argv)
377
399
            return SFTPClient(SocketAsChannelAdapter(sock))
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
 
400
        except _ssh_connection_errors as e:
386
401
            self._raise_connection_error(host, port=port, orig_error=e)
387
402
 
388
403
    def connect_ssh(self, username, password, host, port, command):
390
405
            argv = self._get_vendor_specific_argv(username, host, port,
391
406
                                                  command=command)
392
407
            return self._connect(argv)
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
 
408
        except _ssh_connection_errors as e:
401
409
            self._raise_connection_error(host, port=port, orig_error=e)
402
410
 
403
411
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
418
426
                                  command=None):
419
427
        args = [self.executable_path,
420
428
                '-oForwardX11=no', '-oForwardAgent=no',
421
 
                '-oClearAllForwardings=yes', '-oProtocol=2',
 
429
                '-oClearAllForwardings=yes',
422
430
                '-oNoHostAuthenticationForLocalhost=yes']
423
431
        if port is not None:
424
432
            args.extend(['-p', str(port)])
425
433
        if username is not None:
426
434
            args.extend(['-l', username])
427
435
        if subsystem is not None:
428
 
            args.extend(['-s', host, subsystem])
 
436
            args.extend(['-s', '--', host, subsystem])
429
437
        else:
430
 
            args.extend([host] + command)
 
438
            args.extend(['--', host] + command)
431
439
        return args
432
440
 
433
441
register_ssh_vendor('openssh', OpenSSHSubprocessVendor())
440
448
 
441
449
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
442
450
                                  command=None):
 
451
        self._check_hostname(host)
443
452
        args = [self.executable_path, '-x']
444
453
        if port is not None:
445
454
            args.extend(['-p', str(port)])
454
463
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
455
464
 
456
465
 
 
466
class LSHSubprocessVendor(SubprocessVendor):
 
467
    """SSH vendor that uses the 'lsh' executable from GNU"""
 
468
 
 
469
    executable_path = 'lsh'
 
470
 
 
471
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
 
472
                                  command=None):
 
473
        self._check_hostname(host)
 
474
        args = [self.executable_path]
 
475
        if port is not None:
 
476
            args.extend(['-p', str(port)])
 
477
        if username is not None:
 
478
            args.extend(['-l', username])
 
479
        if subsystem is not None:
 
480
            args.extend(['--subsystem', subsystem, host])
 
481
        else:
 
482
            args.extend([host] + command)
 
483
        return args
 
484
 
 
485
register_ssh_vendor('lsh', LSHSubprocessVendor())
 
486
 
 
487
 
457
488
class PLinkSubprocessVendor(SubprocessVendor):
458
489
    """SSH vendor that uses the 'plink' executable from Putty."""
459
490
 
461
492
 
462
493
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
463
494
                                  command=None):
 
495
        self._check_hostname(host)
464
496
        args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
465
497
        if port is not None:
466
498
            args.extend(['-P', str(port)])
486
518
        agent = paramiko.Agent()
487
519
        for key in agent.get_keys():
488
520
            trace.mutter('Trying SSH agent key %s'
489
 
                         % paramiko.util.hexify(key.get_fingerprint()))
 
521
                         % self._hexify(key.get_fingerprint()))
490
522
            try:
491
523
                paramiko_transport.auth_publickey(username, key)
492
524
                return
493
 
            except paramiko.SSHException, e:
 
525
            except paramiko.SSHException as e:
494
526
                pass
495
527
 
496
528
    # okay, try finding id_rsa or id_dss?  (posix only)
512
544
            paramiko_transport.auth_none(username)
513
545
        finally:
514
546
            paramiko_transport.logger.setLevel(old_level)
515
 
    except paramiko.BadAuthenticationType, e:
 
547
    except paramiko.BadAuthenticationType as e:
516
548
        # Supported methods are in the exception
517
549
        supported_auth_types = e.allowed_types
518
 
    except paramiko.SSHException, e:
 
550
    except paramiko.SSHException as e:
519
551
        # Don't know what happened, but just ignore it
520
552
        pass
521
553
    # We treat 'keyboard-interactive' and 'password' auth methods identically,
536
568
        try:
537
569
            paramiko_transport.auth_password(username, password)
538
570
            return
539
 
        except paramiko.SSHException, e:
 
571
        except paramiko.SSHException as e:
540
572
            pass
541
573
 
542
574
    # give up and ask for a password
545
577
    if password is not None:
546
578
        try:
547
579
            paramiko_transport.auth_password(username, password)
548
 
        except paramiko.SSHException, e:
 
580
        except paramiko.SSHException as e:
549
581
            raise errors.ConnectionError(
550
582
                'Unable to authenticate to SSH host as'
551
583
                '\n  %s@%s\n' % (username, host), e)
562
594
        return True
563
595
    except paramiko.PasswordRequiredException:
564
596
        password = ui.ui_factory.get_password(
565
 
            prompt='SSH %(filename)s password', filename=filename)
 
597
            prompt=u'SSH %(filename)s password',
 
598
            filename=filename.decode(osutils._fs_enc))
566
599
        try:
567
600
            key = pkey_class.from_private_key_file(filename, password)
568
601
            paramiko_transport.auth_publickey(username, key)
583
616
    Load system host keys (probably doesn't work on windows) and any
584
617
    "discovered" keys from previous sessions.
585
618
    """
586
 
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
 
619
    global SYSTEM_HOSTKEYS, BRZ_HOSTKEYS
587
620
    try:
588
621
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(
589
622
            os.path.expanduser('~/.ssh/known_hosts'))
590
 
    except IOError, e:
 
623
    except IOError as e:
591
624
        trace.mutter('failed to load system host keys: ' + str(e))
592
 
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
 
625
    brz_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
593
626
    try:
594
 
        BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
595
 
    except IOError, e:
596
 
        trace.mutter('failed to load bzr host keys: ' + str(e))
 
627
        BRZ_HOSTKEYS = paramiko.util.load_host_keys(brz_hostkey_path)
 
628
    except IOError as e:
 
629
        trace.mutter('failed to load brz host keys: ' + str(e))
597
630
        save_host_keys()
598
631
 
599
632
 
601
634
    """
602
635
    Save "discovered" host keys in $(config)/ssh_host_keys/.
603
636
    """
604
 
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
 
637
    global SYSTEM_HOSTKEYS, BRZ_HOSTKEYS
605
638
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
606
639
    config.ensure_config_dir_exists()
607
640
 
608
641
    try:
609
 
        f = open(bzr_hostkey_path, 'w')
610
 
        f.write('# SSH host keys collected by bzr\n')
611
 
        for hostname, keys in BZR_HOSTKEYS.iteritems():
612
 
            for keytype, key in keys.iteritems():
613
 
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
614
 
        f.close()
615
 
    except IOError, e:
 
642
        with open(bzr_hostkey_path, 'w') as f:
 
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
    except IOError as e:
616
648
        trace.mutter('failed to save bzr host keys: ' + str(e))
617
649
 
618
650
 
644
676
import weakref
645
677
_subproc_weakrefs = set()
646
678
 
647
 
def _close_ssh_proc(proc):
648
 
    for func in [proc.stdin.close, proc.stdout.close, proc.wait]:
 
679
def _close_ssh_proc(proc, sock):
 
680
    """Carefully close stdin/stdout and reap the SSH process.
 
681
 
 
682
    If the pipes are already closed and/or the process has already been
 
683
    wait()ed on, that's ok, and no error is raised.  The goal is to do our best
 
684
    to clean up (whether or not a clean up was already tried).
 
685
    """
 
686
    funcs = []
 
687
    for closeable in (proc.stdin, proc.stdout, sock):
 
688
        # We expect that either proc (a subprocess.Popen) will have stdin and
 
689
        # stdout streams to close, or that we will have been passed a socket to
 
690
        # close, with the option not in use being None.
 
691
        if closeable is not None:
 
692
            funcs.append(closeable.close)
 
693
    funcs.append(proc.wait)
 
694
    for func in funcs:
649
695
        try:
650
696
            func()
651
697
        except OSError:
652
 
            pass
653
 
 
654
 
 
655
 
class SSHSubprocess(object):
656
 
    """A socket-like object that talks to an ssh subprocess via pipes."""
657
 
 
658
 
    def __init__(self, proc):
 
698
            # It's ok for the pipe to already be closed, or the process to
 
699
            # already be finished.
 
700
            continue
 
701
 
 
702
 
 
703
class SSHConnection(object):
 
704
    """Abstract base class for SSH connections."""
 
705
 
 
706
    def get_sock_or_pipes(self):
 
707
        """Returns a (kind, io_object) pair.
 
708
 
 
709
        If kind == 'socket', then io_object is a socket.
 
710
 
 
711
        If kind == 'pipes', then io_object is a pair of file-like objects
 
712
        (read_from, write_to).
 
713
        """
 
714
        raise NotImplementedError(self.get_sock_or_pipes)
 
715
 
 
716
    def close(self):
 
717
        raise NotImplementedError(self.close)
 
718
 
 
719
 
 
720
class SSHSubprocessConnection(SSHConnection):
 
721
    """A connection to an ssh subprocess via pipes or a socket.
 
722
 
 
723
    This class is also socket-like enough to be used with
 
724
    SocketAsChannelAdapter (it has 'send' and 'recv' methods).
 
725
    """
 
726
 
 
727
    def __init__(self, proc, sock=None):
 
728
        """Constructor.
 
729
 
 
730
        :param proc: a subprocess.Popen
 
731
        :param sock: if proc.stdin/out is a socket from a socketpair, then sock
 
732
            should breezy's half of that socketpair.  If not passed, proc's
 
733
            stdin/out is assumed to be ordinary pipes.
 
734
        """
659
735
        self.proc = proc
 
736
        self._sock = sock
660
737
        # Add a weakref to proc that will attempt to do the same as self.close
661
738
        # to avoid leaving processes lingering indefinitely.
662
739
        def terminate(ref):
663
740
            _subproc_weakrefs.remove(ref)
664
 
            _close_ssh_proc(proc)
 
741
            _close_ssh_proc(proc, sock)
665
742
        _subproc_weakrefs.add(weakref.ref(self, terminate))
666
743
 
667
744
    def send(self, data):
668
 
        return os.write(self.proc.stdin.fileno(), data)
 
745
        if self._sock is not None:
 
746
            return self._sock.send(data)
 
747
        else:
 
748
            return os.write(self.proc.stdin.fileno(), data)
669
749
 
670
750
    def recv(self, count):
671
 
        return os.read(self.proc.stdout.fileno(), count)
672
 
 
673
 
    def close(self):
674
 
        _close_ssh_proc(self.proc)
675
 
 
676
 
    def get_filelike_channels(self):
677
 
        return (self.proc.stdout, self.proc.stdin)
 
751
        if self._sock is not None:
 
752
            return self._sock.recv(count)
 
753
        else:
 
754
            return os.read(self.proc.stdout.fileno(), count)
 
755
 
 
756
    def close(self):
 
757
        _close_ssh_proc(self.proc, self._sock)
 
758
 
 
759
    def get_sock_or_pipes(self):
 
760
        if self._sock is not None:
 
761
            return 'socket', self._sock
 
762
        else:
 
763
            return 'pipes', (self.proc.stdout, self.proc.stdin)
 
764
 
 
765
 
 
766
class _ParamikoSSHConnection(SSHConnection):
 
767
    """An SSH connection via paramiko."""
 
768
 
 
769
    def __init__(self, channel):
 
770
        self.channel = channel
 
771
 
 
772
    def get_sock_or_pipes(self):
 
773
        return ('socket', self.channel)
 
774
 
 
775
    def close(self):
 
776
        return self.channel.close()
 
777
 
678
778