/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
1
# Copyright (C) 2005 Robey Pointer <robey@lag.net>
2
# Copyright (C) 2005, 2006 Canonical Ltd
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
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
17
18
"""Foundation SSH support for SFTP and smart server."""
19
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
20
import errno
1951.1.5 by Andrew Bennetts
Fix some missing imports with a bit of help from pyflakes.
21
import getpass
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
22
import os
23
import socket
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
24
import subprocess
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
25
import sys
26
27
from bzrlib.config import config_dir, ensure_config_dir_exists
28
from bzrlib.errors import (ConnectionError,
29
                           ParamikoNotPresent,
30
                           TransportError,
1996.2.1 by Andrew Bennetts
Fix NameError reported by Alexander Belchenko.
31
                           UnknownSSH,
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
32
                           )
33
1951.1.5 by Andrew Bennetts
Fix some missing imports with a bit of help from pyflakes.
34
from bzrlib.osutils import pathjoin
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
35
from bzrlib.trace import mutter, warning
1951.1.5 by Andrew Bennetts
Fix some missing imports with a bit of help from pyflakes.
36
import bzrlib.ui
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
37
38
try:
39
    import paramiko
40
except ImportError, e:
41
    raise ParamikoNotPresent(e)
42
else:
43
    from paramiko.sftp_client import SFTPClient
44
45
46
SYSTEM_HOSTKEYS = {}
47
BZR_HOSTKEYS = {}
48
49
1951.1.5 by Andrew Bennetts
Fix some missing imports with a bit of help from pyflakes.
50
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
51
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
52
# Paramiko 1.5 tries to open a socket.AF_UNIX in order to connect
53
# to ssh-agent. That attribute doesn't exist on win32 (it does in cygwin)
54
# so we get an AttributeError exception. So we will not try to
55
# connect to an agent if we are on win32 and using Paramiko older than 1.6
56
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
57
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
58
_ssh_vendors = {}
59
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
60
def register_ssh_vendor(name, vendor):
61
    """Register SSH vendor."""
62
    _ssh_vendors[name] = vendor
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
63
64
    
65
_ssh_vendor = None
66
def _get_ssh_vendor():
67
    """Find out what version of SSH is on the system."""
68
    global _ssh_vendor
69
    if _ssh_vendor is not None:
70
        return _ssh_vendor
71
72
    if 'BZR_SSH' in os.environ:
73
        vendor_name = os.environ['BZR_SSH']
74
        try:
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
75
            _ssh_vendor = _ssh_vendors[vendor_name]
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
76
        except KeyError:
77
            raise UnknownSSH(vendor_name)
78
        return _ssh_vendor
79
80
    try:
81
        p = subprocess.Popen(['ssh', '-V'],
82
                             stdin=subprocess.PIPE,
83
                             stdout=subprocess.PIPE,
84
                             stderr=subprocess.PIPE,
85
                             **os_specific_subprocess_params())
86
        returncode = p.returncode
87
        stdout, stderr = p.communicate()
88
    except OSError:
89
        returncode = -1
90
        stdout = stderr = ''
91
    if 'OpenSSH' in stderr:
92
        mutter('ssh implementation is OpenSSH')
93
        _ssh_vendor = OpenSSHSubprocessVendor()
94
    elif 'SSH Secure Shell' in stderr:
95
        mutter('ssh implementation is SSH Corp.')
96
        _ssh_vendor = SSHCorpSubprocessVendor()
97
98
    if _ssh_vendor is not None:
99
        return _ssh_vendor
100
101
    # XXX: 20051123 jamesh
102
    # A check for putty's plink or lsh would go here.
103
104
    mutter('falling back to paramiko implementation')
1996.2.1 by Andrew Bennetts
Fix NameError reported by Alexander Belchenko.
105
    _ssh_vendor = ParamikoVendor()
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
106
    return _ssh_vendor
107
108
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
109
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
110
def _ignore_sigint():
111
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
112
    # doesn't handle it itself.
113
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
114
    import signal
115
    signal.signal(signal.SIGINT, signal.SIG_IGN)
116
    
117
118
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
119
class LoopbackSFTP(object):
120
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
121
122
    def __init__(self, sock):
123
        self.__socket = sock
124
 
125
    def send(self, data):
126
        return self.__socket.send(data)
127
128
    def recv(self, n):
129
        return self.__socket.recv(n)
130
131
    def recv_ready(self):
132
        return True
133
134
    def close(self):
135
        self.__socket.close()
136
137
138
class SSHVendor(object):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
139
    """Abstract base class for SSH vendor implementations."""
140
    
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
141
    def connect_sftp(self, username, password, host, port):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
142
        """Make an SSH connection, and return an SFTPClient.
143
        
144
        :param username: an ascii string
145
        :param password: an ascii string
146
        :param host: a host name as an ascii string
147
        :param port: a port number
148
        :type port: int
149
150
        :raises: ConnectionError if it cannot connect.
151
152
        :rtype: paramiko.sftp_client.SFTPClient
153
        """
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
154
        raise NotImplementedError(self.connect_sftp)
155
156
    def connect_ssh(self, username, password, host, port, command):
1951.1.12 by Andrew Bennetts
Cosmetic tweaks.
157
        """Make an SSH connection, and return a pipe-like object.
158
        
159
        (This is currently unused, it's just here to indicate future directions
160
        for this code.)
161
        """
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
162
        raise NotImplementedError(self.connect_ssh)
163
        
164
165
class LoopbackVendor(SSHVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
166
    """SSH "vendor" that connects over a plain TCP socket, not SSH."""
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
167
    
168
    def connect_sftp(self, username, password, host, port):
169
        sock = socket.socket()
170
        try:
171
            sock.connect((host, port))
172
        except socket.error, e:
173
            raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
174
                                  % (host, port, e))
175
        return SFTPClient(LoopbackSFTP(sock))
176
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
177
register_ssh_vendor('loopback', LoopbackVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
178
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
179
180
class ParamikoVendor(SSHVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
181
    """Vendor that uses paramiko."""
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
182
183
    def connect_sftp(self, username, password, host, port):
184
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
185
        
186
        load_host_keys()
187
188
        try:
189
            t = paramiko.Transport((host, port or 22))
190
            t.set_log_channel('bzr.paramiko')
191
            t.start_client()
192
        except (paramiko.SSHException, socket.error), e:
193
            raise ConnectionError('Unable to reach SSH host %s:%s: %s' 
194
                                  % (host, port, e))
195
            
196
        server_key = t.get_remote_server_key()
197
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
198
        keytype = server_key.get_name()
1711.9.10 by John Arbash Meinel
Update transport/ssh.py to remove has_key usage
199
        if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
200
            our_server_key = SYSTEM_HOSTKEYS[host][keytype]
201
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
1711.9.10 by John Arbash Meinel
Update transport/ssh.py to remove has_key usage
202
        elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
203
            our_server_key = BZR_HOSTKEYS[host][keytype]
204
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
205
        else:
206
            warning('Adding %s host key for %s: %s' % (keytype, host, server_key_hex))
1711.9.10 by John Arbash Meinel
Update transport/ssh.py to remove has_key usage
207
            if host not in BZR_HOSTKEYS:
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
208
                BZR_HOSTKEYS[host] = {}
209
            BZR_HOSTKEYS[host][keytype] = server_key
210
            our_server_key = server_key
211
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
212
            save_host_keys()
213
        if server_key != our_server_key:
214
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
215
            filename2 = pathjoin(config_dir(), 'ssh_host_keys')
216
            raise TransportError('Host keys for %s do not match!  %s != %s' % \
217
                (host, our_server_key_hex, server_key_hex),
218
                ['Try editing %s or %s' % (filename1, filename2)])
219
220
        _paramiko_auth(username, password, host, t)
221
        
222
        try:
223
            sftp = t.open_sftp_client()
224
        except paramiko.SSHException, e:
225
            raise ConnectionError('Unable to start sftp client %s:%d' %
226
                                  (host, port), e)
227
        return sftp
228
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
229
register_ssh_vendor('paramiko', ParamikoVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
230
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
231
232
class SubprocessVendor(SSHVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
233
    """Abstract base class for vendors that use pipes to a subprocess."""
234
    
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
235
    def connect_sftp(self, username, password, host, port):
236
        try:
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
237
            argv = self._get_vendor_specific_argv(username, host, port,
238
                                                  subsystem='sftp')
239
            proc = subprocess.Popen(argv,
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
240
                                    stdin=subprocess.PIPE,
241
                                    stdout=subprocess.PIPE,
242
                                    **os_specific_subprocess_params())
243
            sock = SSHSubprocess(proc)
244
            return SFTPClient(sock)
245
        except (EOFError, paramiko.SSHException), e:
246
            raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
247
                                  % (host, port, e))
248
        except (OSError, IOError), e:
249
            # If the machine is fast enough, ssh can actually exit
250
            # before we try and send it the sftp request, which
251
            # raises a Broken Pipe
252
            if e.errno not in (errno.EPIPE,):
253
                raise
254
            raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
255
                                  % (host, port, e))
256
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
257
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
258
                                  command=None):
259
        """Returns the argument list to run the subprocess with.
260
        
261
        Exactly one of 'subsystem' and 'command' must be specified.
262
        """
263
        raise NotImplementedError(self._get_vendor_specific_argv)
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
264
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
265
register_ssh_vendor('none', ParamikoVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
266
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
267
268
class OpenSSHSubprocessVendor(SubprocessVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
269
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
270
    
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
271
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
272
                                  command=None):
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
273
        assert subsystem is not None or command is not None, (
274
            'Must specify a command or subsystem')
275
        if subsystem is not None:
276
            assert command is None, (
277
                'subsystem and command are mutually exclusive')
278
        args = ['ssh',
279
                '-oForwardX11=no', '-oForwardAgent=no',
280
                '-oClearAllForwardings=yes', '-oProtocol=2',
281
                '-oNoHostAuthenticationForLocalhost=yes']
282
        if port is not None:
283
            args.extend(['-p', str(port)])
284
        if username is not None:
285
            args.extend(['-l', username])
286
        if subsystem is not None:
287
            args.extend(['-s', host, subsystem])
288
        else:
289
            args.extend([host] + command)
290
        return args
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
291
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
292
register_ssh_vendor('openssh', OpenSSHSubprocessVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
293
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
294
295
class SSHCorpSubprocessVendor(SubprocessVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
296
    """SSH vendor that uses the 'ssh' executable from SSH Corporation."""
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
297
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
298
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
299
                                  command=None):
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
300
        assert subsystem is not None or command is not None, (
301
            'Must specify a command or subsystem')
302
        if subsystem is not None:
303
            assert command is None, (
304
                'subsystem and command are mutually exclusive')
305
        args = ['ssh', '-x']
306
        if port is not None:
307
            args.extend(['-p', str(port)])
308
        if username is not None:
309
            args.extend(['-l', username])
310
        if subsystem is not None:
311
            args.extend(['-s', subsystem, host])
312
        else:
313
            args.extend([host] + command)
314
        return args
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
315
    
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
316
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
317
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
318
319
def _paramiko_auth(username, password, host, paramiko_transport):
320
    # paramiko requires a username, but it might be none if nothing was supplied
321
    # use the local username, just in case.
322
    # We don't override username, because if we aren't using paramiko,
323
    # the username might be specified in ~/.ssh/config and we don't want to
324
    # force it to something else
325
    # Also, it would mess up the self.relpath() functionality
326
    username = username or getpass.getuser()
327
328
    if _use_ssh_agent:
329
        agent = paramiko.Agent()
330
        for key in agent.get_keys():
331
            mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
332
            try:
333
                paramiko_transport.auth_publickey(username, key)
334
                return
335
            except paramiko.SSHException, e:
336
                pass
337
    
338
    # okay, try finding id_rsa or id_dss?  (posix only)
339
    if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
340
        return
341
    if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
342
        return
343
344
    if password:
345
        try:
346
            paramiko_transport.auth_password(username, password)
347
            return
348
        except paramiko.SSHException, e:
349
            pass
350
351
    # give up and ask for a password
352
    password = bzrlib.ui.ui_factory.get_password(
353
            prompt='SSH %(user)s@%(host)s password',
354
            user=username, host=host)
355
    try:
356
        paramiko_transport.auth_password(username, password)
357
    except paramiko.SSHException, e:
358
        raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
359
                              (username, host), e)
360
361
362
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
363
    filename = os.path.expanduser('~/.ssh/' + filename)
364
    try:
365
        key = pkey_class.from_private_key_file(filename)
366
        paramiko_transport.auth_publickey(username, key)
367
        return True
368
    except paramiko.PasswordRequiredException:
369
        password = bzrlib.ui.ui_factory.get_password(
370
                prompt='SSH %(filename)s password',
371
                filename=filename)
372
        try:
373
            key = pkey_class.from_private_key_file(filename, password)
374
            paramiko_transport.auth_publickey(username, key)
375
            return True
376
        except paramiko.SSHException:
377
            mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
378
    except paramiko.SSHException:
379
        mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
380
    except IOError:
381
        pass
382
    return False
383
384
385
def load_host_keys():
386
    """
387
    Load system host keys (probably doesn't work on windows) and any
388
    "discovered" keys from previous sessions.
389
    """
390
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
391
    try:
392
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
393
    except Exception, e:
394
        mutter('failed to load system host keys: ' + str(e))
395
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
396
    try:
397
        BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
398
    except Exception, e:
399
        mutter('failed to load bzr host keys: ' + str(e))
400
        save_host_keys()
401
402
403
def save_host_keys():
404
    """
405
    Save "discovered" host keys in $(config)/ssh_host_keys/.
406
    """
407
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
408
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
409
    ensure_config_dir_exists()
410
411
    try:
412
        f = open(bzr_hostkey_path, 'w')
413
        f.write('# SSH host keys collected by bzr\n')
414
        for hostname, keys in BZR_HOSTKEYS.iteritems():
415
            for keytype, key in keys.iteritems():
416
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
417
        f.close()
418
    except IOError, e:
419
        mutter('failed to save bzr host keys: ' + str(e))
420
421
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
422
def os_specific_subprocess_params():
423
    """Get O/S specific subprocess parameters."""
424
    if sys.platform == 'win32':
425
        # setting the process group and closing fds is not supported on 
426
        # win32
427
        return {}
428
    else:
429
        # We close fds other than the pipes as the child process does not need 
430
        # them to be open.
431
        #
432
        # We also set the child process to ignore SIGINT.  Normally the signal
433
        # would be sent to every process in the foreground process group, but
434
        # this causes it to be seen only by bzr and not by ssh.  Python will
435
        # generate a KeyboardInterrupt in bzr, and we will then have a chance
436
        # to release locks or do other cleanup over ssh before the connection
437
        # goes away.  
438
        # <https://launchpad.net/products/bzr/+bug/5987>
439
        #
440
        # Running it in a separate process group is not good because then it
441
        # can't get non-echoed input of a password or passphrase.
442
        # <https://launchpad.net/products/bzr/+bug/40508>
443
        return {'preexec_fn': _ignore_sigint,
444
                'close_fds': True,
445
                }
446
1951.1.12 by Andrew Bennetts
Cosmetic tweaks.
447
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
448
class SSHSubprocess(object):
449
    """A socket-like object that talks to an ssh subprocess via pipes."""
450
451
    def __init__(self, proc):
452
        self.proc = proc
453
454
    def send(self, data):
455
        return os.write(self.proc.stdin.fileno(), data)
456
457
    def recv_ready(self):
458
        # TODO: jam 20051215 this function is necessary to support the
459
        # pipelined() function. In reality, it probably should use
460
        # poll() or select() to actually return if there is data
461
        # available, otherwise we probably don't get any benefit
462
        return True
463
464
    def recv(self, count):
465
        return os.read(self.proc.stdout.fileno(), count)
466
467
    def close(self):
468
        self.proc.stdin.close()
469
        self.proc.stdout.close()
470
        self.proc.wait()
471
472