/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: Martin
  • Date: 2017-08-26 15:28:39 UTC
  • mto: This revision was merged to the branch mainline in revision 6762.
  • Revision ID: gzlist@googlemail.com-20170826152839-ka2fimri0mskfkct
Fix test_lazy_re on Python 3

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
642
        f = open(bzr_hostkey_path, 'w')
610
643
        f.write('# SSH host keys collected by bzr\n')
611
 
        for hostname, keys in BZR_HOSTKEYS.iteritems():
612
 
            for keytype, key in keys.iteritems():
 
644
        for hostname, keys in BRZ_HOSTKEYS.items():
 
645
            for keytype, key in keys.items():
613
646
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
614
647
        f.close()
615
 
    except IOError, e:
 
648
    except IOError as e:
616
649
        trace.mutter('failed to save bzr host keys: ' + str(e))
617
650
 
618
651
 
644
677
import weakref
645
678
_subproc_weakrefs = set()
646
679
 
647
 
def _close_ssh_proc(proc):
648
 
    for func in [proc.stdin.close, proc.stdout.close, proc.wait]:
 
680
def _close_ssh_proc(proc, sock):
 
681
    """Carefully close stdin/stdout and reap the SSH process.
 
682
 
 
683
    If the pipes are already closed and/or the process has already been
 
684
    wait()ed on, that's ok, and no error is raised.  The goal is to do our best
 
685
    to clean up (whether or not a clean up was already tried).
 
686
    """
 
687
    funcs = []
 
688
    for closeable in (proc.stdin, proc.stdout, sock):
 
689
        # We expect that either proc (a subprocess.Popen) will have stdin and
 
690
        # stdout streams to close, or that we will have been passed a socket to
 
691
        # close, with the option not in use being None.
 
692
        if closeable is not None:
 
693
            funcs.append(closeable.close)
 
694
    funcs.append(proc.wait)
 
695
    for func in funcs:
649
696
        try:
650
697
            func()
651
698
        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):
 
699
            # It's ok for the pipe to already be closed, or the process to
 
700
            # already be finished.
 
701
            continue
 
702
 
 
703
 
 
704
class SSHConnection(object):
 
705
    """Abstract base class for SSH connections."""
 
706
 
 
707
    def get_sock_or_pipes(self):
 
708
        """Returns a (kind, io_object) pair.
 
709
 
 
710
        If kind == 'socket', then io_object is a socket.
 
711
 
 
712
        If kind == 'pipes', then io_object is a pair of file-like objects
 
713
        (read_from, write_to).
 
714
        """
 
715
        raise NotImplementedError(self.get_sock_or_pipes)
 
716
 
 
717
    def close(self):
 
718
        raise NotImplementedError(self.close)
 
719
 
 
720
 
 
721
class SSHSubprocessConnection(SSHConnection):
 
722
    """A connection to an ssh subprocess via pipes or a socket.
 
723
 
 
724
    This class is also socket-like enough to be used with
 
725
    SocketAsChannelAdapter (it has 'send' and 'recv' methods).
 
726
    """
 
727
 
 
728
    def __init__(self, proc, sock=None):
 
729
        """Constructor.
 
730
 
 
731
        :param proc: a subprocess.Popen
 
732
        :param sock: if proc.stdin/out is a socket from a socketpair, then sock
 
733
            should breezy's half of that socketpair.  If not passed, proc's
 
734
            stdin/out is assumed to be ordinary pipes.
 
735
        """
659
736
        self.proc = proc
 
737
        self._sock = sock
660
738
        # Add a weakref to proc that will attempt to do the same as self.close
661
739
        # to avoid leaving processes lingering indefinitely.
662
740
        def terminate(ref):
663
741
            _subproc_weakrefs.remove(ref)
664
 
            _close_ssh_proc(proc)
 
742
            _close_ssh_proc(proc, sock)
665
743
        _subproc_weakrefs.add(weakref.ref(self, terminate))
666
744
 
667
745
    def send(self, data):
668
 
        return os.write(self.proc.stdin.fileno(), data)
 
746
        if self._sock is not None:
 
747
            return self._sock.send(data)
 
748
        else:
 
749
            return os.write(self.proc.stdin.fileno(), data)
669
750
 
670
751
    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)
 
752
        if self._sock is not None:
 
753
            return self._sock.recv(count)
 
754
        else:
 
755
            return os.read(self.proc.stdout.fileno(), count)
 
756
 
 
757
    def close(self):
 
758
        _close_ssh_proc(self.proc, self._sock)
 
759
 
 
760
    def get_sock_or_pipes(self):
 
761
        if self._sock is not None:
 
762
            return 'socket', self._sock
 
763
        else:
 
764
            return 'pipes', (self.proc.stdout, self.proc.stdin)
 
765
 
 
766
 
 
767
class _ParamikoSSHConnection(SSHConnection):
 
768
    """An SSH connection via paramiko."""
 
769
 
 
770
    def __init__(self, channel):
 
771
        self.channel = channel
 
772
 
 
773
    def get_sock_or_pipes(self):
 
774
        return ('socket', self.channel)
 
775
 
 
776
    def close(self):
 
777
        return self.channel.close()
 
778
 
678
779