~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: Aaron Bentley
  • Date: 2007-06-11 14:59:52 UTC
  • mto: (2520.5.2 bzr.mpbundle)
  • mto: This revision was merged to the branch mainline in revision 2631.
  • Revision ID: abentley@panoramicfeedback.com-20070611145952-cwt4480gphdhen6l
Get installation started

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