~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

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