~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: Robert Collins
  • Date: 2007-07-04 01:39:50 UTC
  • mto: This revision was merged to the branch mainline in revision 2581.
  • Revision ID: robertc@robertcollins.net-20070704013950-7pp23plwyqjvgkxg
Review feedback.

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