~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: John Arbash Meinel
  • Date: 2011-05-11 11:35:28 UTC
  • mto: This revision was merged to the branch mainline in revision 5851.
  • Revision ID: john@arbash-meinel.com-20110511113528-qepibuwxicjrbb2h
Break compatibility with python <2.6.

This includes auditing the code for places where we were doing
explicit 'sys.version' checks and removing them as appropriate.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 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
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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
22
23
import os
23
24
import socket
24
25
import subprocess
93
94
            try:
94
95
                vendor = self._ssh_vendors[vendor_name]
95
96
            except KeyError:
96
 
                raise errors.UnknownSSH(vendor_name)
 
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
97
101
            return vendor
98
102
        return None
99
103
 
109
113
            stdout = stderr = ''
110
114
        return stdout + stderr
111
115
 
112
 
    def _get_vendor_by_version_string(self, version, args):
 
116
    def _get_vendor_by_version_string(self, version, progname):
113
117
        """Return the vendor or None based on output from the subprocess.
114
118
 
115
119
        :param version: The output of 'ssh -V' like command.
122
126
        elif 'SSH Secure Shell' in version:
123
127
            trace.mutter('ssh implementation is SSH Corp.')
124
128
            vendor = SSHCorpSubprocessVendor()
125
 
        elif 'plink' in version and args[0] == 'plink':
 
129
        elif 'lsh' in version:
 
130
            trace.mutter('ssh implementation is GNU lsh.')
 
131
            vendor = LSHSubprocessVendor()
 
132
        # As plink user prompts are not handled currently, don't auto-detect
 
133
        # it by inspection below, but keep this vendor detection for if a path
 
134
        # is given in BZR_SSH. See https://bugs.launchpad.net/bugs/414743
 
135
        elif 'plink' in version and progname == 'plink':
126
136
            # Checking if "plink" was the executed argument as Windows
127
 
            # sometimes reports 'ssh -V' incorrectly with 'plink' in it's
 
137
            # sometimes reports 'ssh -V' incorrectly with 'plink' in its
128
138
            # version.  See https://bugs.launchpad.net/bzr/+bug/107155
129
139
            trace.mutter("ssh implementation is Putty's plink.")
130
140
            vendor = PLinkSubprocessVendor()
132
142
 
133
143
    def _get_vendor_by_inspection(self):
134
144
        """Return the vendor or None by checking for known SSH implementations."""
135
 
        # detection of plink vendor is disabled because of bug #107593
136
 
        # https://bugs.launchpad.net/bzr/+bug/107593
137
 
        # who want plink should explicitly enable it with BZR_SSH environment
138
 
        # variable.
139
 
        #~for args in (['ssh', '-V'], ['plink', '-V']):
140
 
        for args in (['ssh', '-V'],):
141
 
            version = self._get_ssh_version_string(args)
142
 
            vendor = self._get_vendor_by_version_string(version, args)
143
 
            if vendor is not None:
144
 
                return vendor
145
 
        return None
 
145
        version = self._get_ssh_version_string(['ssh', '-V'])
 
146
        return self._get_vendor_by_version_string(version, "ssh")
 
147
 
 
148
    def _get_vendor_from_path(self, path):
 
149
        """Return the vendor or None using the program at the given path"""
 
150
        version = self._get_ssh_version_string([path, '-V'])
 
151
        return self._get_vendor_by_version_string(version, 
 
152
            os.path.splitext(os.path.basename(path))[0])
146
153
 
147
154
    def get_vendor(self, environment=None):
148
155
        """Find out what version of SSH is on the system.
169
176
register_ssh_vendor = _ssh_vendor_manager.register_vendor
170
177
 
171
178
 
172
 
def _ignore_sigint():
 
179
def _ignore_signals():
173
180
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
174
181
    # doesn't handle it itself.
175
182
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
176
183
    import signal
177
184
    signal.signal(signal.SIGINT, signal.SIG_IGN)
178
 
 
179
 
 
180
 
class LoopbackSFTP(object):
 
185
    # GZ 2010-02-19: Perhaps make this check if breakin is installed instead
 
186
    if signal.getsignal(signal.SIGQUIT) != signal.SIG_DFL:
 
187
        signal.signal(signal.SIGQUIT, signal.SIG_IGN)
 
188
 
 
189
 
 
190
class SocketAsChannelAdapter(object):
181
191
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
182
192
 
183
193
    def __init__(self, sock):
184
194
        self.__socket = sock
185
195
 
 
196
    def get_name(self):
 
197
        return "bzr SocketAsChannelAdapter"
 
198
 
186
199
    def send(self, data):
187
200
        return self.__socket.send(data)
188
201
 
189
202
    def recv(self, n):
190
 
        return self.__socket.recv(n)
 
203
        try:
 
204
            return self.__socket.recv(n)
 
205
        except socket.error, e:
 
206
            if e.args[0] in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED,
 
207
                             errno.EBADF):
 
208
                # Connection has closed.  Paramiko expects an empty string in
 
209
                # this case, not an exception.
 
210
                return ''
 
211
            raise
191
212
 
192
213
    def recv_ready(self):
 
214
        # TODO: jam 20051215 this function is necessary to support the
 
215
        # pipelined() function. In reality, it probably should use
 
216
        # poll() or select() to actually return if there is data
 
217
        # available, otherwise we probably don't get any benefit
193
218
        return True
194
219
 
195
220
    def close(self):
201
226
 
202
227
    def connect_sftp(self, username, password, host, port):
203
228
        """Make an SSH connection, and return an SFTPClient.
204
 
        
 
229
 
205
230
        :param username: an ascii string
206
231
        :param password: an ascii string
207
232
        :param host: a host name as an ascii string
216
241
 
217
242
    def connect_ssh(self, username, password, host, port, command):
218
243
        """Make an SSH connection.
219
 
        
220
 
        :returns: something with a `close` method, and a `get_filelike_channels`
221
 
            method that returns a pair of (read, write) filelike objects.
 
244
 
 
245
        :returns: an SSHConnection.
222
246
        """
223
247
        raise NotImplementedError(self.connect_ssh)
224
248
 
242
266
            sock.connect((host, port))
243
267
        except socket.error, e:
244
268
            self._raise_connection_error(host, port=port, orig_error=e)
245
 
        return SFTPClient(LoopbackSFTP(sock))
 
269
        return SFTPClient(SocketAsChannelAdapter(sock))
246
270
 
247
271
register_ssh_vendor('loopback', LoopbackVendor())
248
272
 
249
273
 
250
 
class _ParamikoSSHConnection(object):
251
 
    def __init__(self, channel):
252
 
        self.channel = channel
253
 
 
254
 
    def get_filelike_channels(self):
255
 
        return self.channel.makefile('rb'), self.channel.makefile('wb')
256
 
 
257
 
    def close(self):
258
 
        return self.channel.close()
259
 
 
260
 
 
261
274
class ParamikoVendor(SSHVendor):
262
275
    """Vendor that uses paramiko."""
263
276
 
326
339
            self._raise_connection_error(host, port=port, orig_error=e,
327
340
                                         msg='Unable to invoke remote bzr')
328
341
 
 
342
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
329
343
if paramiko is not None:
330
344
    vendor = ParamikoVendor()
331
345
    register_ssh_vendor('paramiko', vendor)
332
346
    register_ssh_vendor('none', vendor)
333
347
    register_default_ssh_vendor(vendor)
 
348
    _ssh_connection_errors += (paramiko.SSHException,)
334
349
    del vendor
335
350
 
336
351
 
338
353
    """Abstract base class for vendors that use pipes to a subprocess."""
339
354
 
340
355
    def _connect(self, argv):
341
 
        proc = subprocess.Popen(argv,
342
 
                                stdin=subprocess.PIPE,
343
 
                                stdout=subprocess.PIPE,
 
356
        # Attempt to make a socketpair to use as stdin/stdout for the SSH
 
357
        # subprocess.  We prefer sockets to pipes because they support
 
358
        # non-blocking short reads, allowing us to optimistically read 64k (or
 
359
        # whatever) chunks.
 
360
        try:
 
361
            my_sock, subproc_sock = socket.socketpair()
 
362
            osutils.set_fd_cloexec(my_sock)
 
363
        except (AttributeError, socket.error):
 
364
            # This platform doesn't support socketpair(), so just use ordinary
 
365
            # pipes instead.
 
366
            stdin = stdout = subprocess.PIPE
 
367
            my_sock, subproc_sock = None, None
 
368
        else:
 
369
            stdin = stdout = subproc_sock
 
370
        proc = subprocess.Popen(argv, stdin=stdin, stdout=stdout,
344
371
                                **os_specific_subprocess_params())
345
 
        return SSHSubprocess(proc)
 
372
        if subproc_sock is not None:
 
373
            subproc_sock.close()
 
374
        return SSHSubprocessConnection(proc, sock=my_sock)
346
375
 
347
376
    def connect_sftp(self, username, password, host, port):
348
377
        try:
349
378
            argv = self._get_vendor_specific_argv(username, host, port,
350
379
                                                  subsystem='sftp')
351
380
            sock = self._connect(argv)
352
 
            return SFTPClient(sock)
353
 
        except (EOFError, paramiko.SSHException), e:
354
 
            self._raise_connection_error(host, port=port, orig_error=e)
355
 
        except (OSError, IOError), e:
356
 
            # If the machine is fast enough, ssh can actually exit
357
 
            # before we try and send it the sftp request, which
358
 
            # raises a Broken Pipe
359
 
            if e.errno not in (errno.EPIPE,):
360
 
                raise
 
381
            return SFTPClient(SocketAsChannelAdapter(sock))
 
382
        except _ssh_connection_errors, e:
361
383
            self._raise_connection_error(host, port=port, orig_error=e)
362
384
 
363
385
    def connect_ssh(self, username, password, host, port, command):
365
387
            argv = self._get_vendor_specific_argv(username, host, port,
366
388
                                                  command=command)
367
389
            return self._connect(argv)
368
 
        except (EOFError), e:
369
 
            self._raise_connection_error(host, port=port, orig_error=e)
370
 
        except (OSError, IOError), e:
371
 
            # If the machine is fast enough, ssh can actually exit
372
 
            # before we try and send it the sftp request, which
373
 
            # raises a Broken Pipe
374
 
            if e.errno not in (errno.EPIPE,):
375
 
                raise
 
390
        except _ssh_connection_errors, e:
376
391
            self._raise_connection_error(host, port=port, orig_error=e)
377
392
 
378
393
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
379
394
                                  command=None):
380
395
        """Returns the argument list to run the subprocess with.
381
 
        
 
396
 
382
397
        Exactly one of 'subsystem' and 'command' must be specified.
383
398
        """
384
399
        raise NotImplementedError(self._get_vendor_specific_argv)
387
402
class OpenSSHSubprocessVendor(SubprocessVendor):
388
403
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
389
404
 
 
405
    executable_path = 'ssh'
 
406
 
390
407
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
391
408
                                  command=None):
392
 
        assert subsystem is not None or command is not None, (
393
 
            'Must specify a command or subsystem')
394
 
        if subsystem is not None:
395
 
            assert command is None, (
396
 
                'subsystem and command are mutually exclusive')
397
 
        args = ['ssh',
 
409
        args = [self.executable_path,
398
410
                '-oForwardX11=no', '-oForwardAgent=no',
399
 
                '-oClearAllForwardings=yes', '-oProtocol=2',
 
411
                '-oClearAllForwardings=yes',
400
412
                '-oNoHostAuthenticationForLocalhost=yes']
401
413
        if port is not None:
402
414
            args.extend(['-p', str(port)])
414
426
class SSHCorpSubprocessVendor(SubprocessVendor):
415
427
    """SSH vendor that uses the 'ssh' executable from SSH Corporation."""
416
428
 
 
429
    executable_path = 'ssh'
 
430
 
417
431
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
418
432
                                  command=None):
419
 
        assert subsystem is not None or command is not None, (
420
 
            'Must specify a command or subsystem')
421
 
        if subsystem is not None:
422
 
            assert command is None, (
423
 
                'subsystem and command are mutually exclusive')
424
 
        args = ['ssh', '-x']
 
433
        args = [self.executable_path, '-x']
425
434
        if port is not None:
426
435
            args.extend(['-p', str(port)])
427
436
        if username is not None:
432
441
            args.extend([host] + command)
433
442
        return args
434
443
 
435
 
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
 
444
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
 
445
 
 
446
 
 
447
class LSHSubprocessVendor(SubprocessVendor):
 
448
    """SSH vendor that uses the 'lsh' executable from GNU"""
 
449
 
 
450
    executable_path = 'lsh'
 
451
 
 
452
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
 
453
                                  command=None):
 
454
        args = [self.executable_path]
 
455
        if port is not None:
 
456
            args.extend(['-p', str(port)])
 
457
        if username is not None:
 
458
            args.extend(['-l', username])
 
459
        if subsystem is not None:
 
460
            args.extend(['--subsystem', subsystem, host])
 
461
        else:
 
462
            args.extend([host] + command)
 
463
        return args
 
464
 
 
465
register_ssh_vendor('lsh', LSHSubprocessVendor())
436
466
 
437
467
 
438
468
class PLinkSubprocessVendor(SubprocessVendor):
439
469
    """SSH vendor that uses the 'plink' executable from Putty."""
440
470
 
 
471
    executable_path = 'plink'
 
472
 
441
473
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
442
474
                                  command=None):
443
 
        assert subsystem is not None or command is not None, (
444
 
            'Must specify a command or subsystem')
445
 
        if subsystem is not None:
446
 
            assert command is None, (
447
 
                'subsystem and command are mutually exclusive')
448
 
        args = ['plink', '-x', '-a', '-ssh', '-2']
 
475
        args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
449
476
        if port is not None:
450
477
            args.extend(['-P', str(port)])
451
478
        if username is not None:
460
487
 
461
488
 
462
489
def _paramiko_auth(username, password, host, port, paramiko_transport):
463
 
    # paramiko requires a username, but it might be none if nothing was supplied
464
 
    # use the local username, just in case.
465
 
    # We don't override username, because if we aren't using paramiko,
466
 
    # the username might be specified in ~/.ssh/config and we don't want to
467
 
    # force it to something else
468
 
    # Also, it would mess up the self.relpath() functionality
469
490
    auth = config.AuthenticationConfig()
 
491
    # paramiko requires a username, but it might be none if nothing was
 
492
    # supplied.  If so, use the local username.
470
493
    if username is None:
471
 
        username = auth.get_user('ssh', host, port=port)
472
 
        if username is None:
473
 
            # Default to local user
474
 
            username = getpass.getuser()
475
 
 
 
494
        username = auth.get_user('ssh', host, port=port,
 
495
                                 default=getpass.getuser())
476
496
    if _use_ssh_agent:
477
497
        agent = paramiko.Agent()
478
498
        for key in agent.get_keys():
490
510
    if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
491
511
        return
492
512
 
 
513
    # If we have gotten this far, we are about to try for passwords, do an
 
514
    # auth_none check to see if it is even supported.
 
515
    supported_auth_types = []
 
516
    try:
 
517
        # Note that with paramiko <1.7.5 this logs an INFO message:
 
518
        #    Authentication type (none) not permitted.
 
519
        # So we explicitly disable the logging level for this action
 
520
        old_level = paramiko_transport.logger.level
 
521
        paramiko_transport.logger.setLevel(logging.WARNING)
 
522
        try:
 
523
            paramiko_transport.auth_none(username)
 
524
        finally:
 
525
            paramiko_transport.logger.setLevel(old_level)
 
526
    except paramiko.BadAuthenticationType, e:
 
527
        # Supported methods are in the exception
 
528
        supported_auth_types = e.allowed_types
 
529
    except paramiko.SSHException, e:
 
530
        # Don't know what happened, but just ignore it
 
531
        pass
 
532
    # We treat 'keyboard-interactive' and 'password' auth methods identically,
 
533
    # because Paramiko's auth_password method will automatically try
 
534
    # 'keyboard-interactive' auth (using the password as the response) if
 
535
    # 'password' auth is not available.  Apparently some Debian and Gentoo
 
536
    # OpenSSH servers require this.
 
537
    # XXX: It's possible for a server to require keyboard-interactive auth that
 
538
    # requires something other than a single password, but we currently don't
 
539
    # support that.
 
540
    if ('password' not in supported_auth_types and
 
541
        'keyboard-interactive' not in supported_auth_types):
 
542
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
 
543
            '\n  %s@%s\nsupported auth types: %s'
 
544
            % (username, host, supported_auth_types))
 
545
 
493
546
    if password:
494
547
        try:
495
548
            paramiko_transport.auth_password(username, password)
499
552
 
500
553
    # give up and ask for a password
501
554
    password = auth.get_password('ssh', host, username, port=port)
502
 
    try:
503
 
        paramiko_transport.auth_password(username, password)
504
 
    except paramiko.SSHException, e:
505
 
        raise errors.ConnectionError(
506
 
            'Unable to authenticate to SSH host as %s@%s' % (username, host), e)
 
555
    # get_password can still return None, which means we should not prompt
 
556
    if password is not None:
 
557
        try:
 
558
            paramiko_transport.auth_password(username, password)
 
559
        except paramiko.SSHException, e:
 
560
            raise errors.ConnectionError(
 
561
                'Unable to authenticate to SSH host as'
 
562
                '\n  %s@%s\n' % (username, host), e)
 
563
    else:
 
564
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
 
565
                                     '  %s@%s' % (username, host))
507
566
 
508
567
 
509
568
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
571
630
def os_specific_subprocess_params():
572
631
    """Get O/S specific subprocess parameters."""
573
632
    if sys.platform == 'win32':
574
 
        # setting the process group and closing fds is not supported on 
 
633
        # setting the process group and closing fds is not supported on
575
634
        # win32
576
635
        return {}
577
636
    else:
578
 
        # We close fds other than the pipes as the child process does not need 
 
637
        # We close fds other than the pipes as the child process does not need
579
638
        # them to be open.
580
639
        #
581
640
        # We also set the child process to ignore SIGINT.  Normally the signal
583
642
        # this causes it to be seen only by bzr and not by ssh.  Python will
584
643
        # generate a KeyboardInterrupt in bzr, and we will then have a chance
585
644
        # to release locks or do other cleanup over ssh before the connection
586
 
        # goes away.  
 
645
        # goes away.
587
646
        # <https://launchpad.net/products/bzr/+bug/5987>
588
647
        #
589
648
        # Running it in a separate process group is not good because then it
590
649
        # can't get non-echoed input of a password or passphrase.
591
650
        # <https://launchpad.net/products/bzr/+bug/40508>
592
 
        return {'preexec_fn': _ignore_sigint,
 
651
        return {'preexec_fn': _ignore_signals,
593
652
                'close_fds': True,
594
653
                }
595
654
 
596
 
 
597
 
class SSHSubprocess(object):
598
 
    """A socket-like object that talks to an ssh subprocess via pipes."""
599
 
 
600
 
    def __init__(self, proc):
 
655
import weakref
 
656
_subproc_weakrefs = set()
 
657
 
 
658
def _close_ssh_proc(proc, sock):
 
659
    """Carefully close stdin/stdout and reap the SSH process.
 
660
 
 
661
    If the pipes are already closed and/or the process has already been
 
662
    wait()ed on, that's ok, and no error is raised.  The goal is to do our best
 
663
    to clean up (whether or not a clean up was already tried).
 
664
    """
 
665
    funcs = []
 
666
    for closeable in (proc.stdin, proc.stdout, sock):
 
667
        # We expect that either proc (a subprocess.Popen) will have stdin and
 
668
        # stdout streams to close, or that we will have been passed a socket to
 
669
        # close, with the option not in use being None.
 
670
        if closeable is not None:
 
671
            funcs.append(closeable.close)
 
672
    funcs.append(proc.wait)
 
673
    for func in funcs:
 
674
        try:
 
675
            func()
 
676
        except OSError:
 
677
            # It's ok for the pipe to already be closed, or the process to
 
678
            # already be finished.
 
679
            continue
 
680
 
 
681
 
 
682
class SSHConnection(object):
 
683
    """Abstract base class for SSH connections."""
 
684
 
 
685
    def get_sock_or_pipes(self):
 
686
        """Returns a (kind, io_object) pair.
 
687
 
 
688
        If kind == 'socket', then io_object is a socket.
 
689
 
 
690
        If kind == 'pipes', then io_object is a pair of file-like objects
 
691
        (read_from, write_to).
 
692
        """
 
693
        raise NotImplementedError(self.get_sock_or_pipes)
 
694
 
 
695
    def close(self):
 
696
        raise NotImplementedError(self.close)
 
697
 
 
698
 
 
699
class SSHSubprocessConnection(SSHConnection):
 
700
    """A connection to an ssh subprocess via pipes or a socket.
 
701
 
 
702
    This class is also socket-like enough to be used with
 
703
    SocketAsChannelAdapter (it has 'send' and 'recv' methods).
 
704
    """
 
705
 
 
706
    def __init__(self, proc, sock=None):
 
707
        """Constructor.
 
708
 
 
709
        :param proc: a subprocess.Popen
 
710
        :param sock: if proc.stdin/out is a socket from a socketpair, then sock
 
711
            should bzrlib's half of that socketpair.  If not passed, proc's
 
712
            stdin/out is assumed to be ordinary pipes.
 
713
        """
601
714
        self.proc = proc
 
715
        self._sock = sock
 
716
        # Add a weakref to proc that will attempt to do the same as self.close
 
717
        # to avoid leaving processes lingering indefinitely.
 
718
        def terminate(ref):
 
719
            _subproc_weakrefs.remove(ref)
 
720
            _close_ssh_proc(proc, sock)
 
721
        _subproc_weakrefs.add(weakref.ref(self, terminate))
602
722
 
603
723
    def send(self, data):
604
 
        return os.write(self.proc.stdin.fileno(), data)
605
 
 
606
 
    def recv_ready(self):
607
 
        # TODO: jam 20051215 this function is necessary to support the
608
 
        # pipelined() function. In reality, it probably should use
609
 
        # poll() or select() to actually return if there is data
610
 
        # available, otherwise we probably don't get any benefit
611
 
        return True
 
724
        if self._sock is not None:
 
725
            return self._sock.send(data)
 
726
        else:
 
727
            return os.write(self.proc.stdin.fileno(), data)
612
728
 
613
729
    def recv(self, count):
614
 
        return os.read(self.proc.stdout.fileno(), count)
615
 
 
616
 
    def close(self):
617
 
        self.proc.stdin.close()
618
 
        self.proc.stdout.close()
619
 
        self.proc.wait()
620
 
 
621
 
    def get_filelike_channels(self):
622
 
        return (self.proc.stdout, self.proc.stdin)
 
730
        if self._sock is not None:
 
731
            return self._sock.recv(count)
 
732
        else:
 
733
            return os.read(self.proc.stdout.fileno(), count)
 
734
 
 
735
    def close(self):
 
736
        _close_ssh_proc(self.proc, self._sock)
 
737
 
 
738
    def get_sock_or_pipes(self):
 
739
        if self._sock is not None:
 
740
            return 'socket', self._sock
 
741
        else:
 
742
            return 'pipes', (self.proc.stdout, self.proc.stdin)
 
743
 
 
744
 
 
745
class _ParamikoSSHConnection(SSHConnection):
 
746
    """An SSH connection via paramiko."""
 
747
 
 
748
    def __init__(self, channel):
 
749
        self.channel = channel
 
750
 
 
751
    def get_sock_or_pipes(self):
 
752
        return ('socket', self.channel)
 
753
 
 
754
    def close(self):
 
755
        return self.channel.close()
 
756
 
623
757