~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: Aaron Bentley
  • Date: 2007-02-06 14:52:16 UTC
  • mfrom: (2266 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2268.
  • Revision ID: abentley@panoramicfeedback.com-20070206145216-fcpi8o3ufvuzwbp9
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005 Robey Pointer <robey@lag.net>
2
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
2
# Copyright (C) 2005, 2006 Canonical Ltd
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
5
5
# it under the terms of the GNU General Public License as published by
24
24
import subprocess
25
25
import sys
26
26
 
27
 
from bzrlib import (
28
 
    config,
29
 
    errors,
30
 
    osutils,
31
 
    trace,
32
 
    ui,
33
 
    )
 
27
from bzrlib.config import config_dir, ensure_config_dir_exists
 
28
from bzrlib.errors import (ConnectionError,
 
29
                           ParamikoNotPresent,
 
30
                           SocketConnectionError,
 
31
                           TransportError,
 
32
                           UnknownSSH,
 
33
                           )
 
34
 
 
35
from bzrlib.osutils import pathjoin
 
36
from bzrlib.trace import mutter, warning
 
37
import bzrlib.ui
34
38
 
35
39
try:
36
40
    import paramiko
54
58
# connect to an agent if we are on win32 and using Paramiko older than 1.6
55
59
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
56
60
 
57
 
 
58
 
class SSHVendorManager(object):
59
 
    """Manager for manage SSH vendors."""
60
 
 
61
 
    # Note, although at first sign the class interface seems similar to
62
 
    # bzrlib.registry.Registry it is not possible/convenient to directly use
63
 
    # the Registry because the class just has "get()" interface instead of the
64
 
    # Registry's "get(key)".
65
 
 
66
 
    def __init__(self):
67
 
        self._ssh_vendors = {}
68
 
        self._cached_ssh_vendor = None
69
 
        self._default_ssh_vendor = None
70
 
 
71
 
    def register_default_vendor(self, vendor):
72
 
        """Register default SSH vendor."""
73
 
        self._default_ssh_vendor = vendor
74
 
 
75
 
    def register_vendor(self, name, vendor):
76
 
        """Register new SSH vendor by name."""
77
 
        self._ssh_vendors[name] = vendor
78
 
 
79
 
    def clear_cache(self):
80
 
        """Clear previously cached lookup result."""
81
 
        self._cached_ssh_vendor = None
82
 
 
83
 
    def _get_vendor_by_environment(self, environment=None):
84
 
        """Return the vendor or None based on BZR_SSH environment variable.
85
 
 
86
 
        :raises UnknownSSH: if the BZR_SSH environment variable contains
87
 
                            unknown vendor name
88
 
        """
89
 
        if environment is None:
90
 
            environment = os.environ
91
 
        if 'BZR_SSH' in environment:
92
 
            vendor_name = environment['BZR_SSH']
93
 
            try:
94
 
                vendor = self._ssh_vendors[vendor_name]
95
 
            except KeyError:
96
 
                raise errors.UnknownSSH(vendor_name)
97
 
            return vendor
98
 
        return None
99
 
 
100
 
    def _get_ssh_version_string(self, args):
101
 
        """Return SSH version string from the subprocess."""
 
61
_ssh_vendors = {}
 
62
 
 
63
def register_ssh_vendor(name, vendor):
 
64
    """Register SSH vendor."""
 
65
    _ssh_vendors[name] = vendor
 
66
 
 
67
    
 
68
_ssh_vendor = None
 
69
def _get_ssh_vendor():
 
70
    """Find out what version of SSH is on the system."""
 
71
    global _ssh_vendor
 
72
    if _ssh_vendor is not None:
 
73
        return _ssh_vendor
 
74
 
 
75
    if 'BZR_SSH' in os.environ:
 
76
        vendor_name = os.environ['BZR_SSH']
102
77
        try:
103
 
            p = subprocess.Popen(args,
104
 
                                 stdout=subprocess.PIPE,
105
 
                                 stderr=subprocess.PIPE,
106
 
                                 **os_specific_subprocess_params())
107
 
            stdout, stderr = p.communicate()
108
 
        except OSError:
109
 
            stdout = stderr = ''
110
 
        return stdout + stderr
111
 
 
112
 
    def _get_vendor_by_version_string(self, version, args):
113
 
        """Return the vendor or None based on output from the subprocess.
114
 
 
115
 
        :param version: The output of 'ssh -V' like command.
116
 
        :param args: Command line that was run.
117
 
        """
118
 
        vendor = None
119
 
        if 'OpenSSH' in version:
120
 
            trace.mutter('ssh implementation is OpenSSH')
121
 
            vendor = OpenSSHSubprocessVendor()
122
 
        elif 'SSH Secure Shell' in version:
123
 
            trace.mutter('ssh implementation is SSH Corp.')
124
 
            vendor = SSHCorpSubprocessVendor()
125
 
        elif 'plink' in version and args[0] == 'plink':
126
 
            # Checking if "plink" was the executed argument as Windows
127
 
            # sometimes reports 'ssh -V' incorrectly with 'plink' in it's
128
 
            # version.  See https://bugs.launchpad.net/bzr/+bug/107155
129
 
            trace.mutter("ssh implementation is Putty's plink.")
130
 
            vendor = PLinkSubprocessVendor()
131
 
        return vendor
132
 
 
133
 
    def _get_vendor_by_inspection(self):
134
 
        """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
141
 
 
142
 
    def get_vendor(self, environment=None):
143
 
        """Find out what version of SSH is on the system.
144
 
 
145
 
        :raises SSHVendorNotFound: if no any SSH vendor is found
146
 
        :raises UnknownSSH: if the BZR_SSH environment variable contains
147
 
                            unknown vendor name
148
 
        """
149
 
        if self._cached_ssh_vendor is None:
150
 
            vendor = self._get_vendor_by_environment(environment)
151
 
            if vendor is None:
152
 
                vendor = self._get_vendor_by_inspection()
153
 
                if vendor is None:
154
 
                    trace.mutter('falling back to default implementation')
155
 
                    vendor = self._default_ssh_vendor
156
 
                    if vendor is None:
157
 
                        raise errors.SSHVendorNotFound()
158
 
            self._cached_ssh_vendor = vendor
159
 
        return self._cached_ssh_vendor
160
 
 
161
 
_ssh_vendor_manager = SSHVendorManager()
162
 
_get_ssh_vendor = _ssh_vendor_manager.get_vendor
163
 
register_default_ssh_vendor = _ssh_vendor_manager.register_default_vendor
164
 
register_ssh_vendor = _ssh_vendor_manager.register_vendor
 
78
            _ssh_vendor = _ssh_vendors[vendor_name]
 
79
        except KeyError:
 
80
            raise UnknownSSH(vendor_name)
 
81
        return _ssh_vendor
 
82
 
 
83
    try:
 
84
        p = subprocess.Popen(['ssh', '-V'],
 
85
                             stdin=subprocess.PIPE,
 
86
                             stdout=subprocess.PIPE,
 
87
                             stderr=subprocess.PIPE,
 
88
                             **os_specific_subprocess_params())
 
89
        returncode = p.returncode
 
90
        stdout, stderr = p.communicate()
 
91
    except OSError:
 
92
        returncode = -1
 
93
        stdout = stderr = ''
 
94
    if 'OpenSSH' in stderr:
 
95
        mutter('ssh implementation is OpenSSH')
 
96
        _ssh_vendor = OpenSSHSubprocessVendor()
 
97
    elif 'SSH Secure Shell' in stderr:
 
98
        mutter('ssh implementation is SSH Corp.')
 
99
        _ssh_vendor = SSHCorpSubprocessVendor()
 
100
 
 
101
    if _ssh_vendor is not None:
 
102
        return _ssh_vendor
 
103
 
 
104
    # XXX: 20051123 jamesh
 
105
    # A check for putty's plink or lsh would go here.
 
106
 
 
107
    mutter('falling back to paramiko implementation')
 
108
    _ssh_vendor = ParamikoVendor()
 
109
    return _ssh_vendor
165
110
 
166
111
 
167
112
def _ignore_sigint():
170
115
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
171
116
    import signal
172
117
    signal.signal(signal.SIGINT, signal.SIG_IGN)
173
 
 
174
 
 
175
 
class SocketAsChannelAdapter(object):
 
118
    
 
119
 
 
120
 
 
121
class LoopbackSFTP(object):
176
122
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
177
123
 
178
124
    def __init__(self, sock):
179
125
        self.__socket = sock
180
 
 
181
 
    def get_name(self):
182
 
        return "bzr SocketAsChannelAdapter"
183
 
    
 
126
 
184
127
    def send(self, data):
185
128
        return self.__socket.send(data)
186
129
 
187
130
    def recv(self, n):
188
 
        try:
189
 
            return self.__socket.recv(n)
190
 
        except socket.error, e:
191
 
            if e.args[0] in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED,
192
 
                             errno.EBADF):
193
 
                # Connection has closed.  Paramiko expects an empty string in
194
 
                # this case, not an exception.
195
 
                return ''
196
 
            raise
 
131
        return self.__socket.recv(n)
197
132
 
198
133
    def recv_ready(self):
199
 
        # TODO: jam 20051215 this function is necessary to support the
200
 
        # pipelined() function. In reality, it probably should use
201
 
        # poll() or select() to actually return if there is data
202
 
        # available, otherwise we probably don't get any benefit
203
134
        return True
204
135
 
205
136
    def close(self):
208
139
 
209
140
class SSHVendor(object):
210
141
    """Abstract base class for SSH vendor implementations."""
211
 
 
 
142
    
212
143
    def connect_sftp(self, username, password, host, port):
213
144
        """Make an SSH connection, and return an SFTPClient.
214
145
        
231
162
            method that returns a pair of (read, write) filelike objects.
232
163
        """
233
164
        raise NotImplementedError(self.connect_ssh)
234
 
 
 
165
        
235
166
    def _raise_connection_error(self, host, port=None, orig_error=None,
236
167
                                msg='Unable to connect to SSH host'):
237
168
        """Raise a SocketConnectionError with properly formatted host.
239
170
        This just unifies all the locations that try to raise ConnectionError,
240
171
        so that they format things properly.
241
172
        """
242
 
        raise errors.SocketConnectionError(host=host, port=port, msg=msg,
243
 
                                           orig_error=orig_error)
 
173
        raise SocketConnectionError(host=host, port=port, msg=msg,
 
174
                                    orig_error=orig_error)
244
175
 
245
176
 
246
177
class LoopbackVendor(SSHVendor):
247
178
    """SSH "vendor" that connects over a plain TCP socket, not SSH."""
248
 
 
 
179
    
249
180
    def connect_sftp(self, username, password, host, port):
250
181
        sock = socket.socket()
251
182
        try:
252
183
            sock.connect((host, port))
253
184
        except socket.error, e:
254
185
            self._raise_connection_error(host, port=port, orig_error=e)
255
 
        return SFTPClient(SocketAsChannelAdapter(sock))
 
186
        return SFTPClient(LoopbackSFTP(sock))
256
187
 
257
188
register_ssh_vendor('loopback', LoopbackVendor())
258
189
 
273
204
 
274
205
    def _connect(self, username, password, host, port):
275
206
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
276
 
 
 
207
        
277
208
        load_host_keys()
278
209
 
279
210
        try:
282
213
            t.start_client()
283
214
        except (paramiko.SSHException, socket.error), e:
284
215
            self._raise_connection_error(host, port=port, orig_error=e)
285
 
 
 
216
            
286
217
        server_key = t.get_remote_server_key()
287
218
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
288
219
        keytype = server_key.get_name()
289
220
        if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
290
221
            our_server_key = SYSTEM_HOSTKEYS[host][keytype]
291
 
            our_server_key_hex = paramiko.util.hexify(
292
 
                our_server_key.get_fingerprint())
 
222
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
293
223
        elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
294
224
            our_server_key = BZR_HOSTKEYS[host][keytype]
295
 
            our_server_key_hex = paramiko.util.hexify(
296
 
                our_server_key.get_fingerprint())
 
225
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
297
226
        else:
298
 
            trace.warning('Adding %s host key for %s: %s'
299
 
                          % (keytype, host, server_key_hex))
 
227
            warning('Adding %s host key for %s: %s' % (keytype, host, server_key_hex))
300
228
            add = getattr(BZR_HOSTKEYS, 'add', None)
301
229
            if add is not None: # paramiko >= 1.X.X
302
230
                BZR_HOSTKEYS.add(host, keytype, server_key)
303
231
            else:
304
232
                BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
305
233
            our_server_key = server_key
306
 
            our_server_key_hex = paramiko.util.hexify(
307
 
                our_server_key.get_fingerprint())
 
234
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
308
235
            save_host_keys()
309
236
        if server_key != our_server_key:
310
237
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
311
 
            filename2 = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
312
 
            raise errors.TransportError(
313
 
                'Host keys for %s do not match!  %s != %s' %
 
238
            filename2 = pathjoin(config_dir(), 'ssh_host_keys')
 
239
            raise TransportError('Host keys for %s do not match!  %s != %s' % \
314
240
                (host, our_server_key_hex, server_key_hex),
315
241
                ['Try editing %s or %s' % (filename1, filename2)])
316
242
 
317
 
        _paramiko_auth(username, password, host, port, t)
 
243
        _paramiko_auth(username, password, host, t)
318
244
        return t
319
 
 
 
245
        
320
246
    def connect_sftp(self, username, password, host, port):
321
247
        t = self._connect(username, password, host, port)
322
248
        try:
337
263
                                         msg='Unable to invoke remote bzr')
338
264
 
339
265
if paramiko is not None:
340
 
    vendor = ParamikoVendor()
341
 
    register_ssh_vendor('paramiko', vendor)
342
 
    register_ssh_vendor('none', vendor)
343
 
    register_default_ssh_vendor(vendor)
344
 
    _sftp_connection_errors = (EOFError, paramiko.SSHException)
345
 
    del vendor
346
 
else:
347
 
    _sftp_connection_errors = (EOFError,)
 
266
    register_ssh_vendor('paramiko', ParamikoVendor())
348
267
 
349
268
 
350
269
class SubprocessVendor(SSHVendor):
351
270
    """Abstract base class for vendors that use pipes to a subprocess."""
352
 
 
 
271
    
353
272
    def _connect(self, argv):
354
273
        proc = subprocess.Popen(argv,
355
274
                                stdin=subprocess.PIPE,
362
281
            argv = self._get_vendor_specific_argv(username, host, port,
363
282
                                                  subsystem='sftp')
364
283
            sock = self._connect(argv)
365
 
            return SFTPClient(SocketAsChannelAdapter(sock))
366
 
        except _sftp_connection_errors, e:
 
284
            return SFTPClient(sock)
 
285
        except (EOFError, paramiko.SSHException), e:
367
286
            self._raise_connection_error(host, port=port, orig_error=e)
368
287
        except (OSError, IOError), e:
369
288
            # If the machine is fast enough, ssh can actually exit
396
315
        """
397
316
        raise NotImplementedError(self._get_vendor_specific_argv)
398
317
 
 
318
register_ssh_vendor('none', ParamikoVendor())
 
319
 
399
320
 
400
321
class OpenSSHSubprocessVendor(SubprocessVendor):
401
322
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
402
 
 
 
323
    
403
324
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
404
325
                                  command=None):
 
326
        assert subsystem is not None or command is not None, (
 
327
            'Must specify a command or subsystem')
 
328
        if subsystem is not None:
 
329
            assert command is None, (
 
330
                'subsystem and command are mutually exclusive')
405
331
        args = ['ssh',
406
332
                '-oForwardX11=no', '-oForwardAgent=no',
407
333
                '-oClearAllForwardings=yes', '-oProtocol=2',
424
350
 
425
351
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
426
352
                                  command=None):
 
353
        assert subsystem is not None or command is not None, (
 
354
            'Must specify a command or subsystem')
 
355
        if subsystem is not None:
 
356
            assert command is None, (
 
357
                'subsystem and command are mutually exclusive')
427
358
        args = ['ssh', '-x']
428
359
        if port is not None:
429
360
            args.extend(['-p', str(port)])
434
365
        else:
435
366
            args.extend([host] + command)
436
367
        return args
437
 
 
 
368
    
438
369
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
439
370
 
440
371
 
441
 
class PLinkSubprocessVendor(SubprocessVendor):
442
 
    """SSH vendor that uses the 'plink' executable from Putty."""
443
 
 
444
 
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
445
 
                                  command=None):
446
 
        args = ['plink', '-x', '-a', '-ssh', '-2', '-batch']
447
 
        if port is not None:
448
 
            args.extend(['-P', str(port)])
449
 
        if username is not None:
450
 
            args.extend(['-l', username])
451
 
        if subsystem is not None:
452
 
            args.extend(['-s', host, subsystem])
453
 
        else:
454
 
            args.extend([host] + command)
455
 
        return args
456
 
 
457
 
register_ssh_vendor('plink', PLinkSubprocessVendor())
458
 
 
459
 
 
460
 
def _paramiko_auth(username, password, host, port, paramiko_transport):
 
372
def _paramiko_auth(username, password, host, paramiko_transport):
461
373
    # paramiko requires a username, but it might be none if nothing was supplied
462
374
    # use the local username, just in case.
463
375
    # We don't override username, because if we aren't using paramiko,
464
376
    # the username might be specified in ~/.ssh/config and we don't want to
465
377
    # force it to something else
466
378
    # Also, it would mess up the self.relpath() functionality
467
 
    auth = config.AuthenticationConfig()
468
 
    if username is None:
469
 
        username = auth.get_user('ssh', host, port=port)
470
 
        if username is None:
471
 
            # Default to local user
472
 
            username = getpass.getuser()
 
379
    username = username or getpass.getuser()
473
380
 
474
381
    if _use_ssh_agent:
475
382
        agent = paramiko.Agent()
476
383
        for key in agent.get_keys():
477
 
            trace.mutter('Trying SSH agent key %s'
478
 
                         % paramiko.util.hexify(key.get_fingerprint()))
 
384
            mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
479
385
            try:
480
386
                paramiko_transport.auth_publickey(username, key)
481
387
                return
482
388
            except paramiko.SSHException, e:
483
389
                pass
484
 
 
 
390
    
485
391
    # okay, try finding id_rsa or id_dss?  (posix only)
486
392
    if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
487
393
        return
496
402
            pass
497
403
 
498
404
    # give up and ask for a password
499
 
    password = auth.get_password('ssh', host, username, port=port)
 
405
    password = bzrlib.ui.ui_factory.get_password(
 
406
            prompt='SSH %(user)s@%(host)s password',
 
407
            user=username, host=host)
500
408
    try:
501
409
        paramiko_transport.auth_password(username, password)
502
410
    except paramiko.SSHException, e:
503
 
        raise errors.ConnectionError(
504
 
            'Unable to authenticate to SSH host as %s@%s' % (username, host), e)
 
411
        raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
 
412
                              (username, host), e)
505
413
 
506
414
 
507
415
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
511
419
        paramiko_transport.auth_publickey(username, key)
512
420
        return True
513
421
    except paramiko.PasswordRequiredException:
514
 
        password = ui.ui_factory.get_password(
515
 
            prompt='SSH %(filename)s password', filename=filename)
 
422
        password = bzrlib.ui.ui_factory.get_password(
 
423
                prompt='SSH %(filename)s password',
 
424
                filename=filename)
516
425
        try:
517
426
            key = pkey_class.from_private_key_file(filename, password)
518
427
            paramiko_transport.auth_publickey(username, key)
519
428
            return True
520
429
        except paramiko.SSHException:
521
 
            trace.mutter('SSH authentication via %s key failed.'
522
 
                         % (os.path.basename(filename),))
 
430
            mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
523
431
    except paramiko.SSHException:
524
 
        trace.mutter('SSH authentication via %s key failed.'
525
 
                     % (os.path.basename(filename),))
 
432
        mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
526
433
    except IOError:
527
434
        pass
528
435
    return False
535
442
    """
536
443
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
537
444
    try:
538
 
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(
539
 
            os.path.expanduser('~/.ssh/known_hosts'))
540
 
    except IOError, e:
541
 
        trace.mutter('failed to load system host keys: ' + str(e))
542
 
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
 
445
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
 
446
    except Exception, e:
 
447
        mutter('failed to load system host keys: ' + str(e))
 
448
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
543
449
    try:
544
450
        BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
545
 
    except IOError, e:
546
 
        trace.mutter('failed to load bzr host keys: ' + str(e))
 
451
    except Exception, e:
 
452
        mutter('failed to load bzr host keys: ' + str(e))
547
453
        save_host_keys()
548
454
 
549
455
 
552
458
    Save "discovered" host keys in $(config)/ssh_host_keys/.
553
459
    """
554
460
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
555
 
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
556
 
    config.ensure_config_dir_exists()
 
461
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
 
462
    ensure_config_dir_exists()
557
463
 
558
464
    try:
559
465
        f = open(bzr_hostkey_path, 'w')
563
469
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
564
470
        f.close()
565
471
    except IOError, e:
566
 
        trace.mutter('failed to save bzr host keys: ' + str(e))
 
472
        mutter('failed to save bzr host keys: ' + str(e))
567
473
 
568
474
 
569
475
def os_specific_subprocess_params():
601
507
    def send(self, data):
602
508
        return os.write(self.proc.stdin.fileno(), data)
603
509
 
 
510
    def recv_ready(self):
 
511
        # TODO: jam 20051215 this function is necessary to support the
 
512
        # pipelined() function. In reality, it probably should use
 
513
        # poll() or select() to actually return if there is data
 
514
        # available, otherwise we probably don't get any benefit
 
515
        return True
 
516
 
604
517
    def recv(self, count):
605
518
        return os.read(self.proc.stdout.fileno(), count)
606
519