~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-01-13 05:14:24 UTC
  • mfrom: (3936.1.3 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20090113051424-nrk3zkfe09h46i9y
(mbp) merge 1.11 and advance to 1.12

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 Canonical Ltd
 
2
# Copyright (C) 2005, 2006, 2007 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.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
 
27
from bzrlib import (
 
28
    config,
 
29
    errors,
 
30
    osutils,
 
31
    trace,
 
32
    ui,
 
33
    )
38
34
 
39
35
try:
40
36
    import paramiko
58
54
# connect to an agent if we are on win32 and using Paramiko older than 1.6
59
55
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
60
56
 
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']
 
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."""
77
102
        try:
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
 
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
110
165
 
111
166
 
112
167
def _ignore_sigint():
115
170
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
116
171
    import signal
117
172
    signal.signal(signal.SIGINT, signal.SIG_IGN)
118
 
    
119
 
 
120
 
 
121
 
class LoopbackSFTP(object):
 
173
 
 
174
 
 
175
class SocketAsChannelAdapter(object):
122
176
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
123
177
 
124
178
    def __init__(self, sock):
125
179
        self.__socket = sock
126
 
 
 
180
 
 
181
    def get_name(self):
 
182
        return "bzr SocketAsChannelAdapter"
 
183
    
127
184
    def send(self, data):
128
185
        return self.__socket.send(data)
129
186
 
130
187
    def recv(self, n):
131
 
        return self.__socket.recv(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
132
197
 
133
198
    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
134
203
        return True
135
204
 
136
205
    def close(self):
139
208
 
140
209
class SSHVendor(object):
141
210
    """Abstract base class for SSH vendor implementations."""
142
 
    
 
211
 
143
212
    def connect_sftp(self, username, password, host, port):
144
213
        """Make an SSH connection, and return an SFTPClient.
145
214
        
162
231
            method that returns a pair of (read, write) filelike objects.
163
232
        """
164
233
        raise NotImplementedError(self.connect_ssh)
165
 
        
 
234
 
166
235
    def _raise_connection_error(self, host, port=None, orig_error=None,
167
236
                                msg='Unable to connect to SSH host'):
168
237
        """Raise a SocketConnectionError with properly formatted host.
170
239
        This just unifies all the locations that try to raise ConnectionError,
171
240
        so that they format things properly.
172
241
        """
173
 
        raise SocketConnectionError(host=host, port=port, msg=msg,
174
 
                                    orig_error=orig_error)
 
242
        raise errors.SocketConnectionError(host=host, port=port, msg=msg,
 
243
                                           orig_error=orig_error)
175
244
 
176
245
 
177
246
class LoopbackVendor(SSHVendor):
178
247
    """SSH "vendor" that connects over a plain TCP socket, not SSH."""
179
 
    
 
248
 
180
249
    def connect_sftp(self, username, password, host, port):
181
250
        sock = socket.socket()
182
251
        try:
183
252
            sock.connect((host, port))
184
253
        except socket.error, e:
185
254
            self._raise_connection_error(host, port=port, orig_error=e)
186
 
        return SFTPClient(LoopbackSFTP(sock))
 
255
        return SFTPClient(SocketAsChannelAdapter(sock))
187
256
 
188
257
register_ssh_vendor('loopback', LoopbackVendor())
189
258
 
204
273
 
205
274
    def _connect(self, username, password, host, port):
206
275
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
207
 
        
 
276
 
208
277
        load_host_keys()
209
278
 
210
279
        try:
213
282
            t.start_client()
214
283
        except (paramiko.SSHException, socket.error), e:
215
284
            self._raise_connection_error(host, port=port, orig_error=e)
216
 
            
 
285
 
217
286
        server_key = t.get_remote_server_key()
218
287
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
219
288
        keytype = server_key.get_name()
220
289
        if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
221
290
            our_server_key = SYSTEM_HOSTKEYS[host][keytype]
222
 
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
 
291
            our_server_key_hex = paramiko.util.hexify(
 
292
                our_server_key.get_fingerprint())
223
293
        elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
224
294
            our_server_key = BZR_HOSTKEYS[host][keytype]
225
 
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
 
295
            our_server_key_hex = paramiko.util.hexify(
 
296
                our_server_key.get_fingerprint())
226
297
        else:
227
 
            warning('Adding %s host key for %s: %s' % (keytype, host, server_key_hex))
 
298
            trace.warning('Adding %s host key for %s: %s'
 
299
                          % (keytype, host, server_key_hex))
228
300
            add = getattr(BZR_HOSTKEYS, 'add', None)
229
301
            if add is not None: # paramiko >= 1.X.X
230
302
                BZR_HOSTKEYS.add(host, keytype, server_key)
231
303
            else:
232
304
                BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
233
305
            our_server_key = server_key
234
 
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
 
306
            our_server_key_hex = paramiko.util.hexify(
 
307
                our_server_key.get_fingerprint())
235
308
            save_host_keys()
236
309
        if server_key != our_server_key:
237
310
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
238
 
            filename2 = pathjoin(config_dir(), 'ssh_host_keys')
239
 
            raise TransportError('Host keys for %s do not match!  %s != %s' % \
 
311
            filename2 = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
 
312
            raise errors.TransportError(
 
313
                'Host keys for %s do not match!  %s != %s' %
240
314
                (host, our_server_key_hex, server_key_hex),
241
315
                ['Try editing %s or %s' % (filename1, filename2)])
242
316
 
243
 
        _paramiko_auth(username, password, host, t)
 
317
        _paramiko_auth(username, password, host, port, t)
244
318
        return t
245
 
        
 
319
 
246
320
    def connect_sftp(self, username, password, host, port):
247
321
        t = self._connect(username, password, host, port)
248
322
        try:
263
337
                                         msg='Unable to invoke remote bzr')
264
338
 
265
339
if paramiko is not None:
266
 
    register_ssh_vendor('paramiko', ParamikoVendor())
 
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,)
267
348
 
268
349
 
269
350
class SubprocessVendor(SSHVendor):
270
351
    """Abstract base class for vendors that use pipes to a subprocess."""
271
 
    
 
352
 
272
353
    def _connect(self, argv):
273
354
        proc = subprocess.Popen(argv,
274
355
                                stdin=subprocess.PIPE,
281
362
            argv = self._get_vendor_specific_argv(username, host, port,
282
363
                                                  subsystem='sftp')
283
364
            sock = self._connect(argv)
284
 
            return SFTPClient(sock)
285
 
        except (EOFError, paramiko.SSHException), e:
 
365
            return SFTPClient(SocketAsChannelAdapter(sock))
 
366
        except _sftp_connection_errors, e:
286
367
            self._raise_connection_error(host, port=port, orig_error=e)
287
368
        except (OSError, IOError), e:
288
369
            # If the machine is fast enough, ssh can actually exit
315
396
        """
316
397
        raise NotImplementedError(self._get_vendor_specific_argv)
317
398
 
318
 
register_ssh_vendor('none', ParamikoVendor())
319
 
 
320
399
 
321
400
class OpenSSHSubprocessVendor(SubprocessVendor):
322
401
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
323
 
    
 
402
 
324
403
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
325
404
                                  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')
331
405
        args = ['ssh',
332
406
                '-oForwardX11=no', '-oForwardAgent=no',
333
407
                '-oClearAllForwardings=yes', '-oProtocol=2',
350
424
 
351
425
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
352
426
                                  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')
358
427
        args = ['ssh', '-x']
359
428
        if port is not None:
360
429
            args.extend(['-p', str(port)])
365
434
        else:
366
435
            args.extend([host] + command)
367
436
        return args
368
 
    
 
437
 
369
438
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
370
439
 
371
440
 
372
 
def _paramiko_auth(username, password, host, paramiko_transport):
373
 
    # paramiko requires a username, but it might be none if nothing was supplied
374
 
    # use the local username, just in case.
375
 
    # We don't override username, because if we aren't using paramiko,
376
 
    # the username might be specified in ~/.ssh/config and we don't want to
377
 
    # force it to something else
378
 
    # Also, it would mess up the self.relpath() functionality
379
 
    username = username or getpass.getuser()
 
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):
 
461
    # paramiko requires a username, but it might be none if nothing was
 
462
    # supplied.  If so, use the local username.
 
463
    if username is None:
 
464
        username = getpass.getuser()
380
465
 
381
466
    if _use_ssh_agent:
382
467
        agent = paramiko.Agent()
383
468
        for key in agent.get_keys():
384
 
            mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
 
469
            trace.mutter('Trying SSH agent key %s'
 
470
                         % paramiko.util.hexify(key.get_fingerprint()))
385
471
            try:
386
472
                paramiko_transport.auth_publickey(username, key)
387
473
                return
388
474
            except paramiko.SSHException, e:
389
475
                pass
390
 
    
 
476
 
391
477
    # okay, try finding id_rsa or id_dss?  (posix only)
392
478
    if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
393
479
        return
402
488
            pass
403
489
 
404
490
    # give up and ask for a password
405
 
    password = bzrlib.ui.ui_factory.get_password(
406
 
            prompt='SSH %(user)s@%(host)s password',
407
 
            user=username, host=host)
 
491
    auth = config.AuthenticationConfig()
 
492
    password = auth.get_password('ssh', host, username, port=port)
408
493
    try:
409
494
        paramiko_transport.auth_password(username, password)
410
495
    except paramiko.SSHException, e:
411
 
        raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
412
 
                              (username, host), e)
 
496
        raise errors.ConnectionError(
 
497
            'Unable to authenticate to SSH host as %s@%s' % (username, host), e)
413
498
 
414
499
 
415
500
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
419
504
        paramiko_transport.auth_publickey(username, key)
420
505
        return True
421
506
    except paramiko.PasswordRequiredException:
422
 
        password = bzrlib.ui.ui_factory.get_password(
423
 
                prompt='SSH %(filename)s password',
424
 
                filename=filename)
 
507
        password = ui.ui_factory.get_password(
 
508
            prompt='SSH %(filename)s password', filename=filename)
425
509
        try:
426
510
            key = pkey_class.from_private_key_file(filename, password)
427
511
            paramiko_transport.auth_publickey(username, key)
428
512
            return True
429
513
        except paramiko.SSHException:
430
 
            mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
 
514
            trace.mutter('SSH authentication via %s key failed.'
 
515
                         % (os.path.basename(filename),))
431
516
    except paramiko.SSHException:
432
 
        mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
 
517
        trace.mutter('SSH authentication via %s key failed.'
 
518
                     % (os.path.basename(filename),))
433
519
    except IOError:
434
520
        pass
435
521
    return False
442
528
    """
443
529
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
444
530
    try:
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')
 
531
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(
 
532
            os.path.expanduser('~/.ssh/known_hosts'))
 
533
    except IOError, e:
 
534
        trace.mutter('failed to load system host keys: ' + str(e))
 
535
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
449
536
    try:
450
537
        BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
451
 
    except Exception, e:
452
 
        mutter('failed to load bzr host keys: ' + str(e))
 
538
    except IOError, e:
 
539
        trace.mutter('failed to load bzr host keys: ' + str(e))
453
540
        save_host_keys()
454
541
 
455
542
 
458
545
    Save "discovered" host keys in $(config)/ssh_host_keys/.
459
546
    """
460
547
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
461
 
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
462
 
    ensure_config_dir_exists()
 
548
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
 
549
    config.ensure_config_dir_exists()
463
550
 
464
551
    try:
465
552
        f = open(bzr_hostkey_path, 'w')
469
556
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
470
557
        f.close()
471
558
    except IOError, e:
472
 
        mutter('failed to save bzr host keys: ' + str(e))
 
559
        trace.mutter('failed to save bzr host keys: ' + str(e))
473
560
 
474
561
 
475
562
def os_specific_subprocess_params():
507
594
    def send(self, data):
508
595
        return os.write(self.proc.stdin.fileno(), data)
509
596
 
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
 
 
517
597
    def recv(self, count):
518
598
        return os.read(self.proc.stdout.fileno(), count)
519
599