~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: Patch Queue Manager
  • Date: 2015-04-21 05:32:33 UTC
  • mfrom: (6602.1.1 bzr.dev)
  • Revision ID: pqm@pqm.ubuntu.com-20150421053233-x63rhby1q3612v2h
(richard-wilbur) (jelmer)Make bzr build reproducible for Debian. (Jelmer
 Vernooij)

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