~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: INADA Naoki
  • Date: 2011-05-05 09:15:34 UTC
  • mto: (5830.3.3 i18n-msgfmt)
  • mto: This revision was merged to the branch mainline in revision 5873.
  • Revision ID: songofacandy@gmail.com-20110505091534-7sv835xpofwrmpt4
Add update-pot command to Makefile and tools/bzrgettext script that
extracts help text from bzr commands.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Robey Pointer <robey@lag.net>
2
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2006-2011 Robey Pointer <robey@lag.net>
 
2
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
5
5
# it under the terms of the GNU General Public License as published by
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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
22
23
import os
23
24
import socket
24
25
import subprocess
25
26
import sys
26
27
 
27
 
from bzrlib.config import config_dir, ensure_config_dir_exists
28
 
from bzrlib.errors import (ConnectionError,
29
 
                           ParamikoNotPresent,
30
 
                           TransportError,
31
 
                           )
32
 
 
33
 
from bzrlib.osutils import pathjoin
34
 
from bzrlib.trace import mutter, warning
35
 
import bzrlib.ui
 
28
from bzrlib import (
 
29
    config,
 
30
    errors,
 
31
    osutils,
 
32
    trace,
 
33
    ui,
 
34
    )
36
35
 
37
36
try:
38
37
    import paramiko
39
38
except ImportError, e:
40
 
    raise ParamikoNotPresent(e)
 
39
    # If we have an ssh subprocess, we don't strictly need paramiko for all ssh
 
40
    # access
 
41
    paramiko = None
41
42
else:
42
43
    from paramiko.sftp_client import SFTPClient
43
44
 
54
55
# connect to an agent if we are on win32 and using Paramiko older than 1.6
55
56
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
56
57
 
57
 
_ssh_vendors = {}
58
 
 
59
 
def register_ssh_vendor(name, vendor):
60
 
    """Register SSH vendor."""
61
 
    _ssh_vendors[name] = vendor
62
 
 
63
 
    
64
 
_ssh_vendor = None
65
 
def _get_ssh_vendor():
66
 
    """Find out what version of SSH is on the system."""
67
 
    global _ssh_vendor
68
 
    if _ssh_vendor is not None:
69
 
        return _ssh_vendor
70
 
 
71
 
    if 'BZR_SSH' in os.environ:
72
 
        vendor_name = os.environ['BZR_SSH']
 
58
 
 
59
class SSHVendorManager(object):
 
60
    """Manager for manage SSH vendors."""
 
61
 
 
62
    # Note, although at first sign the class interface seems similar to
 
63
    # bzrlib.registry.Registry it is not possible/convenient to directly use
 
64
    # the Registry because the class just has "get()" interface instead of the
 
65
    # Registry's "get(key)".
 
66
 
 
67
    def __init__(self):
 
68
        self._ssh_vendors = {}
 
69
        self._cached_ssh_vendor = None
 
70
        self._default_ssh_vendor = None
 
71
 
 
72
    def register_default_vendor(self, vendor):
 
73
        """Register default SSH vendor."""
 
74
        self._default_ssh_vendor = vendor
 
75
 
 
76
    def register_vendor(self, name, vendor):
 
77
        """Register new SSH vendor by name."""
 
78
        self._ssh_vendors[name] = vendor
 
79
 
 
80
    def clear_cache(self):
 
81
        """Clear previously cached lookup result."""
 
82
        self._cached_ssh_vendor = None
 
83
 
 
84
    def _get_vendor_by_environment(self, environment=None):
 
85
        """Return the vendor or None based on BZR_SSH environment variable.
 
86
 
 
87
        :raises UnknownSSH: if the BZR_SSH environment variable contains
 
88
                            unknown vendor name
 
89
        """
 
90
        if environment is None:
 
91
            environment = os.environ
 
92
        if 'BZR_SSH' in environment:
 
93
            vendor_name = environment['BZR_SSH']
 
94
            try:
 
95
                vendor = self._ssh_vendors[vendor_name]
 
96
            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
            return vendor
 
102
        return None
 
103
 
 
104
    def _get_ssh_version_string(self, args):
 
105
        """Return SSH version string from the subprocess."""
73
106
        try:
74
 
            _ssh_vendor = _ssh_vendors[vendor_name]
75
 
        except KeyError:
76
 
            raise UnknownSSH(vendor_name)
77
 
        return _ssh_vendor
78
 
 
79
 
    try:
80
 
        p = subprocess.Popen(['ssh', '-V'],
81
 
                             stdin=subprocess.PIPE,
82
 
                             stdout=subprocess.PIPE,
83
 
                             stderr=subprocess.PIPE,
84
 
                             **os_specific_subprocess_params())
85
 
        returncode = p.returncode
86
 
        stdout, stderr = p.communicate()
87
 
    except OSError:
88
 
        returncode = -1
89
 
        stdout = stderr = ''
90
 
    if 'OpenSSH' in stderr:
91
 
        mutter('ssh implementation is OpenSSH')
92
 
        _ssh_vendor = OpenSSHSubprocessVendor()
93
 
    elif 'SSH Secure Shell' in stderr:
94
 
        mutter('ssh implementation is SSH Corp.')
95
 
        _ssh_vendor = SSHCorpSubprocessVendor()
96
 
 
97
 
    if _ssh_vendor is not None:
98
 
        return _ssh_vendor
99
 
 
100
 
    # XXX: 20051123 jamesh
101
 
    # A check for putty's plink or lsh would go here.
102
 
 
103
 
    mutter('falling back to paramiko implementation')
104
 
    _ssh_vendor = ssh.ParamikoVendor()
105
 
    return _ssh_vendor
106
 
 
107
 
 
108
 
 
109
 
def _ignore_sigint():
 
107
            p = subprocess.Popen(args,
 
108
                                 stdout=subprocess.PIPE,
 
109
                                 stderr=subprocess.PIPE,
 
110
                                 **os_specific_subprocess_params())
 
111
            stdout, stderr = p.communicate()
 
112
        except OSError:
 
113
            stdout = stderr = ''
 
114
        return stdout + stderr
 
115
 
 
116
    def _get_vendor_by_version_string(self, version, progname):
 
117
        """Return the vendor or None based on output from the subprocess.
 
118
 
 
119
        :param version: The output of 'ssh -V' like command.
 
120
        :param args: Command line that was run.
 
121
        """
 
122
        vendor = None
 
123
        if 'OpenSSH' in version:
 
124
            trace.mutter('ssh implementation is OpenSSH')
 
125
            vendor = OpenSSHSubprocessVendor()
 
126
        elif 'SSH Secure Shell' in version:
 
127
            trace.mutter('ssh implementation is SSH Corp.')
 
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.")
 
140
            vendor = PLinkSubprocessVendor()
 
141
        return vendor
 
142
 
 
143
    def _get_vendor_by_inspection(self):
 
144
        """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])
 
153
 
 
154
    def get_vendor(self, environment=None):
 
155
        """Find out what version of SSH is on the system.
 
156
 
 
157
        :raises SSHVendorNotFound: if no any SSH vendor is found
 
158
        :raises UnknownSSH: if the BZR_SSH environment variable contains
 
159
                            unknown vendor name
 
160
        """
 
161
        if self._cached_ssh_vendor is None:
 
162
            vendor = self._get_vendor_by_environment(environment)
 
163
            if vendor is None:
 
164
                vendor = self._get_vendor_by_inspection()
 
165
                if vendor is None:
 
166
                    trace.mutter('falling back to default implementation')
 
167
                    vendor = self._default_ssh_vendor
 
168
                    if vendor is None:
 
169
                        raise errors.SSHVendorNotFound()
 
170
            self._cached_ssh_vendor = vendor
 
171
        return self._cached_ssh_vendor
 
172
 
 
173
_ssh_vendor_manager = SSHVendorManager()
 
174
_get_ssh_vendor = _ssh_vendor_manager.get_vendor
 
175
register_default_ssh_vendor = _ssh_vendor_manager.register_default_vendor
 
176
register_ssh_vendor = _ssh_vendor_manager.register_vendor
 
177
 
 
178
 
 
179
def _ignore_signals():
110
180
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
111
181
    # doesn't handle it itself.
112
182
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
113
183
    import signal
114
184
    signal.signal(signal.SIGINT, signal.SIG_IGN)
115
 
    
116
 
 
117
 
 
118
 
class LoopbackSFTP(object):
 
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):
119
191
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
120
192
 
121
193
    def __init__(self, sock):
122
194
        self.__socket = sock
123
 
 
 
195
 
 
196
    def get_name(self):
 
197
        return "bzr SocketAsChannelAdapter"
 
198
 
124
199
    def send(self, data):
125
200
        return self.__socket.send(data)
126
201
 
127
202
    def recv(self, n):
128
 
        return self.__socket.recv(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
129
212
 
130
213
    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
131
218
        return True
132
219
 
133
220
    def close(self):
136
223
 
137
224
class SSHVendor(object):
138
225
    """Abstract base class for SSH vendor implementations."""
139
 
    
 
226
 
140
227
    def connect_sftp(self, username, password, host, port):
141
228
        """Make an SSH connection, and return an SFTPClient.
142
 
        
 
229
 
143
230
        :param username: an ascii string
144
231
        :param password: an ascii string
145
232
        :param host: a host name as an ascii string
153
240
        raise NotImplementedError(self.connect_sftp)
154
241
 
155
242
    def connect_ssh(self, username, password, host, port, command):
156
 
        """Make an SSH connection, and return a pipe-like object.
157
 
        
158
 
        (This is currently unused, it's just here to indicate future directions
159
 
        for this code.)
 
243
        """Make an SSH connection.
 
244
 
 
245
        :returns: an SSHConnection.
160
246
        """
161
247
        raise NotImplementedError(self.connect_ssh)
162
 
        
 
248
 
 
249
    def _raise_connection_error(self, host, port=None, orig_error=None,
 
250
                                msg='Unable to connect to SSH host'):
 
251
        """Raise a SocketConnectionError with properly formatted host.
 
252
 
 
253
        This just unifies all the locations that try to raise ConnectionError,
 
254
        so that they format things properly.
 
255
        """
 
256
        raise errors.SocketConnectionError(host=host, port=port, msg=msg,
 
257
                                           orig_error=orig_error)
 
258
 
163
259
 
164
260
class LoopbackVendor(SSHVendor):
165
261
    """SSH "vendor" that connects over a plain TCP socket, not SSH."""
166
 
    
 
262
 
167
263
    def connect_sftp(self, username, password, host, port):
168
264
        sock = socket.socket()
169
265
        try:
170
266
            sock.connect((host, port))
171
267
        except socket.error, e:
172
 
            raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
173
 
                                  % (host, port, e))
174
 
        return SFTPClient(LoopbackSFTP(sock))
 
268
            self._raise_connection_error(host, port=port, orig_error=e)
 
269
        return SFTPClient(SocketAsChannelAdapter(sock))
175
270
 
176
271
register_ssh_vendor('loopback', LoopbackVendor())
177
272
 
179
274
class ParamikoVendor(SSHVendor):
180
275
    """Vendor that uses paramiko."""
181
276
 
182
 
    def connect_sftp(self, username, password, host, port):
 
277
    def _connect(self, username, password, host, port):
183
278
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
184
 
        
 
279
 
185
280
        load_host_keys()
186
281
 
187
282
        try:
189
284
            t.set_log_channel('bzr.paramiko')
190
285
            t.start_client()
191
286
        except (paramiko.SSHException, socket.error), e:
192
 
            raise ConnectionError('Unable to reach SSH host %s:%s: %s' 
193
 
                                  % (host, port, e))
194
 
            
 
287
            self._raise_connection_error(host, port=port, orig_error=e)
 
288
 
195
289
        server_key = t.get_remote_server_key()
196
290
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
197
291
        keytype = server_key.get_name()
198
292
        if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
199
293
            our_server_key = SYSTEM_HOSTKEYS[host][keytype]
200
 
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
 
294
            our_server_key_hex = paramiko.util.hexify(
 
295
                our_server_key.get_fingerprint())
201
296
        elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
202
297
            our_server_key = BZR_HOSTKEYS[host][keytype]
203
 
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
 
298
            our_server_key_hex = paramiko.util.hexify(
 
299
                our_server_key.get_fingerprint())
204
300
        else:
205
 
            warning('Adding %s host key for %s: %s' % (keytype, host, server_key_hex))
206
 
            if host not in BZR_HOSTKEYS:
207
 
                BZR_HOSTKEYS[host] = {}
208
 
            BZR_HOSTKEYS[host][keytype] = server_key
 
301
            trace.warning('Adding %s host key for %s: %s'
 
302
                          % (keytype, host, server_key_hex))
 
303
            add = getattr(BZR_HOSTKEYS, 'add', None)
 
304
            if add is not None: # paramiko >= 1.X.X
 
305
                BZR_HOSTKEYS.add(host, keytype, server_key)
 
306
            else:
 
307
                BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
209
308
            our_server_key = server_key
210
 
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
 
309
            our_server_key_hex = paramiko.util.hexify(
 
310
                our_server_key.get_fingerprint())
211
311
            save_host_keys()
212
312
        if server_key != our_server_key:
213
313
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
214
 
            filename2 = pathjoin(config_dir(), 'ssh_host_keys')
215
 
            raise TransportError('Host keys for %s do not match!  %s != %s' % \
 
314
            filename2 = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
 
315
            raise errors.TransportError(
 
316
                'Host keys for %s do not match!  %s != %s' %
216
317
                (host, our_server_key_hex, server_key_hex),
217
318
                ['Try editing %s or %s' % (filename1, filename2)])
218
319
 
219
 
        _paramiko_auth(username, password, host, t)
220
 
        
221
 
        try:
222
 
            sftp = t.open_sftp_client()
223
 
        except paramiko.SSHException, e:
224
 
            raise ConnectionError('Unable to start sftp client %s:%d' %
225
 
                                  (host, port), e)
226
 
        return sftp
227
 
 
228
 
register_ssh_vendor('paramiko', ParamikoVendor())
 
320
        _paramiko_auth(username, password, host, port, t)
 
321
        return t
 
322
 
 
323
    def connect_sftp(self, username, password, host, port):
 
324
        t = self._connect(username, password, host, port)
 
325
        try:
 
326
            return t.open_sftp_client()
 
327
        except paramiko.SSHException, e:
 
328
            self._raise_connection_error(host, port=port, orig_error=e,
 
329
                                         msg='Unable to start sftp client')
 
330
 
 
331
    def connect_ssh(self, username, password, host, port, command):
 
332
        t = self._connect(username, password, host, port)
 
333
        try:
 
334
            channel = t.open_session()
 
335
            cmdline = ' '.join(command)
 
336
            channel.exec_command(cmdline)
 
337
            return _ParamikoSSHConnection(channel)
 
338
        except paramiko.SSHException, e:
 
339
            self._raise_connection_error(host, port=port, orig_error=e,
 
340
                                         msg='Unable to invoke remote bzr')
 
341
 
 
342
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
 
343
if paramiko is not None:
 
344
    vendor = ParamikoVendor()
 
345
    register_ssh_vendor('paramiko', vendor)
 
346
    register_ssh_vendor('none', vendor)
 
347
    register_default_ssh_vendor(vendor)
 
348
    _ssh_connection_errors += (paramiko.SSHException,)
 
349
    del vendor
229
350
 
230
351
 
231
352
class SubprocessVendor(SSHVendor):
232
353
    """Abstract base class for vendors that use pipes to a subprocess."""
233
 
    
 
354
 
 
355
    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,
 
371
                                **os_specific_subprocess_params())
 
372
        if subproc_sock is not None:
 
373
            subproc_sock.close()
 
374
        return SSHSubprocessConnection(proc, sock=my_sock)
 
375
 
234
376
    def connect_sftp(self, username, password, host, port):
235
377
        try:
236
378
            argv = self._get_vendor_specific_argv(username, host, port,
237
379
                                                  subsystem='sftp')
238
 
            proc = subprocess.Popen(argv,
239
 
                                    stdin=subprocess.PIPE,
240
 
                                    stdout=subprocess.PIPE,
241
 
                                    **os_specific_subprocess_params())
242
 
            sock = SSHSubprocess(proc)
243
 
            return SFTPClient(sock)
244
 
        except (EOFError, paramiko.SSHException), e:
245
 
            raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
246
 
                                  % (host, port, e))
247
 
        except (OSError, IOError), e:
248
 
            # If the machine is fast enough, ssh can actually exit
249
 
            # before we try and send it the sftp request, which
250
 
            # raises a Broken Pipe
251
 
            if e.errno not in (errno.EPIPE,):
252
 
                raise
253
 
            raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
254
 
                                  % (host, port, e))
 
380
            sock = self._connect(argv)
 
381
            return SFTPClient(SocketAsChannelAdapter(sock))
 
382
        except _ssh_connection_errors, e:
 
383
            self._raise_connection_error(host, port=port, orig_error=e)
 
384
 
 
385
    def connect_ssh(self, username, password, host, port, command):
 
386
        try:
 
387
            argv = self._get_vendor_specific_argv(username, host, port,
 
388
                                                  command=command)
 
389
            return self._connect(argv)
 
390
        except _ssh_connection_errors, e:
 
391
            self._raise_connection_error(host, port=port, orig_error=e)
255
392
 
256
393
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
257
394
                                  command=None):
258
395
        """Returns the argument list to run the subprocess with.
259
 
        
 
396
 
260
397
        Exactly one of 'subsystem' and 'command' must be specified.
261
398
        """
262
399
        raise NotImplementedError(self._get_vendor_specific_argv)
263
400
 
264
 
register_ssh_vendor('none', ParamikoVendor())
265
 
 
266
401
 
267
402
class OpenSSHSubprocessVendor(SubprocessVendor):
268
403
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
269
 
    
 
404
 
 
405
    executable_path = 'ssh'
 
406
 
270
407
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
271
408
                                  command=None):
272
 
        assert subsystem is not None or command is not None, (
273
 
            'Must specify a command or subsystem')
274
 
        if subsystem is not None:
275
 
            assert command is None, (
276
 
                'subsystem and command are mutually exclusive')
277
 
        args = ['ssh',
 
409
        args = [self.executable_path,
278
410
                '-oForwardX11=no', '-oForwardAgent=no',
279
 
                '-oClearAllForwardings=yes', '-oProtocol=2',
 
411
                '-oClearAllForwardings=yes',
280
412
                '-oNoHostAuthenticationForLocalhost=yes']
281
413
        if port is not None:
282
414
            args.extend(['-p', str(port)])
294
426
class SSHCorpSubprocessVendor(SubprocessVendor):
295
427
    """SSH vendor that uses the 'ssh' executable from SSH Corporation."""
296
428
 
 
429
    executable_path = 'ssh'
 
430
 
297
431
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
298
432
                                  command=None):
299
 
        assert subsystem is not None or command is not None, (
300
 
            'Must specify a command or subsystem')
301
 
        if subsystem is not None:
302
 
            assert command is None, (
303
 
                'subsystem and command are mutually exclusive')
304
 
        args = ['ssh', '-x']
 
433
        args = [self.executable_path, '-x']
305
434
        if port is not None:
306
435
            args.extend(['-p', str(port)])
307
436
        if username is not None:
311
440
        else:
312
441
            args.extend([host] + command)
313
442
        return args
314
 
    
315
 
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
316
 
 
317
 
 
318
 
def _paramiko_auth(username, password, host, paramiko_transport):
319
 
    # paramiko requires a username, but it might be none if nothing was supplied
320
 
    # use the local username, just in case.
321
 
    # We don't override username, because if we aren't using paramiko,
322
 
    # the username might be specified in ~/.ssh/config and we don't want to
323
 
    # force it to something else
324
 
    # Also, it would mess up the self.relpath() functionality
325
 
    username = username or getpass.getuser()
326
 
 
 
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())
 
466
 
 
467
 
 
468
class PLinkSubprocessVendor(SubprocessVendor):
 
469
    """SSH vendor that uses the 'plink' executable from Putty."""
 
470
 
 
471
    executable_path = 'plink'
 
472
 
 
473
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
 
474
                                  command=None):
 
475
        args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
 
476
        if port is not None:
 
477
            args.extend(['-P', str(port)])
 
478
        if username is not None:
 
479
            args.extend(['-l', username])
 
480
        if subsystem is not None:
 
481
            args.extend(['-s', host, subsystem])
 
482
        else:
 
483
            args.extend([host] + command)
 
484
        return args
 
485
 
 
486
register_ssh_vendor('plink', PLinkSubprocessVendor())
 
487
 
 
488
 
 
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())
327
496
    if _use_ssh_agent:
328
497
        agent = paramiko.Agent()
329
498
        for key in agent.get_keys():
330
 
            mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
 
499
            trace.mutter('Trying SSH agent key %s'
 
500
                         % paramiko.util.hexify(key.get_fingerprint()))
331
501
            try:
332
502
                paramiko_transport.auth_publickey(username, key)
333
503
                return
334
504
            except paramiko.SSHException, e:
335
505
                pass
336
 
    
 
506
 
337
507
    # okay, try finding id_rsa or id_dss?  (posix only)
338
508
    if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
339
509
        return
340
510
    if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
341
511
        return
342
512
 
 
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
 
343
546
    if password:
344
547
        try:
345
548
            paramiko_transport.auth_password(username, password)
348
551
            pass
349
552
 
350
553
    # give up and ask for a password
351
 
    password = bzrlib.ui.ui_factory.get_password(
352
 
            prompt='SSH %(user)s@%(host)s password',
353
 
            user=username, host=host)
354
 
    try:
355
 
        paramiko_transport.auth_password(username, password)
356
 
    except paramiko.SSHException, e:
357
 
        raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
358
 
                              (username, host), e)
 
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))
359
566
 
360
567
 
361
568
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
365
572
        paramiko_transport.auth_publickey(username, key)
366
573
        return True
367
574
    except paramiko.PasswordRequiredException:
368
 
        password = bzrlib.ui.ui_factory.get_password(
369
 
                prompt='SSH %(filename)s password',
370
 
                filename=filename)
 
575
        password = ui.ui_factory.get_password(
 
576
            prompt='SSH %(filename)s password', filename=filename)
371
577
        try:
372
578
            key = pkey_class.from_private_key_file(filename, password)
373
579
            paramiko_transport.auth_publickey(username, key)
374
580
            return True
375
581
        except paramiko.SSHException:
376
 
            mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
 
582
            trace.mutter('SSH authentication via %s key failed.'
 
583
                         % (os.path.basename(filename),))
377
584
    except paramiko.SSHException:
378
 
        mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
 
585
        trace.mutter('SSH authentication via %s key failed.'
 
586
                     % (os.path.basename(filename),))
379
587
    except IOError:
380
588
        pass
381
589
    return False
388
596
    """
389
597
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
390
598
    try:
391
 
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
392
 
    except Exception, e:
393
 
        mutter('failed to load system host keys: ' + str(e))
394
 
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
 
599
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(
 
600
            os.path.expanduser('~/.ssh/known_hosts'))
 
601
    except IOError, e:
 
602
        trace.mutter('failed to load system host keys: ' + str(e))
 
603
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
395
604
    try:
396
605
        BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
397
 
    except Exception, e:
398
 
        mutter('failed to load bzr host keys: ' + str(e))
 
606
    except IOError, e:
 
607
        trace.mutter('failed to load bzr host keys: ' + str(e))
399
608
        save_host_keys()
400
609
 
401
610
 
404
613
    Save "discovered" host keys in $(config)/ssh_host_keys/.
405
614
    """
406
615
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
407
 
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
408
 
    ensure_config_dir_exists()
 
616
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
 
617
    config.ensure_config_dir_exists()
409
618
 
410
619
    try:
411
620
        f = open(bzr_hostkey_path, 'w')
415
624
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
416
625
        f.close()
417
626
    except IOError, e:
418
 
        mutter('failed to save bzr host keys: ' + str(e))
 
627
        trace.mutter('failed to save bzr host keys: ' + str(e))
419
628
 
420
629
 
421
630
def os_specific_subprocess_params():
422
631
    """Get O/S specific subprocess parameters."""
423
632
    if sys.platform == 'win32':
424
 
        # setting the process group and closing fds is not supported on 
 
633
        # setting the process group and closing fds is not supported on
425
634
        # win32
426
635
        return {}
427
636
    else:
428
 
        # We close fds other than the pipes as the child process does not need 
 
637
        # We close fds other than the pipes as the child process does not need
429
638
        # them to be open.
430
639
        #
431
640
        # We also set the child process to ignore SIGINT.  Normally the signal
433
642
        # this causes it to be seen only by bzr and not by ssh.  Python will
434
643
        # generate a KeyboardInterrupt in bzr, and we will then have a chance
435
644
        # to release locks or do other cleanup over ssh before the connection
436
 
        # goes away.  
 
645
        # goes away.
437
646
        # <https://launchpad.net/products/bzr/+bug/5987>
438
647
        #
439
648
        # Running it in a separate process group is not good because then it
440
649
        # can't get non-echoed input of a password or passphrase.
441
650
        # <https://launchpad.net/products/bzr/+bug/40508>
442
 
        return {'preexec_fn': _ignore_sigint,
 
651
        return {'preexec_fn': _ignore_signals,
443
652
                'close_fds': True,
444
653
                }
445
654
 
446
 
 
447
 
class SSHSubprocess(object):
448
 
    """A socket-like object that talks to an ssh subprocess via pipes."""
449
 
 
450
 
    def __init__(self, proc):
 
655
import weakref
 
656
_subproc_weakrefs = set()
 
657
 
 
658
def _close_ssh_proc(proc, sock):
 
659
    """Carefully close stdin/stdout and reap the SSH process.
 
660
 
 
661
    If the pipes are already closed and/or the process has already been
 
662
    wait()ed on, that's ok, and no error is raised.  The goal is to do our best
 
663
    to clean up (whether or not a clean up was already tried).
 
664
    """
 
665
    funcs = []
 
666
    for closeable in (proc.stdin, proc.stdout, sock):
 
667
        # We expect that either proc (a subprocess.Popen) will have stdin and
 
668
        # stdout streams to close, or that we will have been passed a socket to
 
669
        # close, with the option not in use being None.
 
670
        if closeable is not None:
 
671
            funcs.append(closeable.close)
 
672
    funcs.append(proc.wait)
 
673
    for func in funcs:
 
674
        try:
 
675
            func()
 
676
        except OSError:
 
677
            # It's ok for the pipe to already be closed, or the process to
 
678
            # already be finished.
 
679
            continue
 
680
 
 
681
 
 
682
class SSHConnection(object):
 
683
    """Abstract base class for SSH connections."""
 
684
 
 
685
    def get_sock_or_pipes(self):
 
686
        """Returns a (kind, io_object) pair.
 
687
 
 
688
        If kind == 'socket', then io_object is a socket.
 
689
 
 
690
        If kind == 'pipes', then io_object is a pair of file-like objects
 
691
        (read_from, write_to).
 
692
        """
 
693
        raise NotImplementedError(self.get_sock_or_pipes)
 
694
 
 
695
    def close(self):
 
696
        raise NotImplementedError(self.close)
 
697
 
 
698
 
 
699
class SSHSubprocessConnection(SSHConnection):
 
700
    """A connection to an ssh subprocess via pipes or a socket.
 
701
 
 
702
    This class is also socket-like enough to be used with
 
703
    SocketAsChannelAdapter (it has 'send' and 'recv' methods).
 
704
    """
 
705
 
 
706
    def __init__(self, proc, sock=None):
 
707
        """Constructor.
 
708
 
 
709
        :param proc: a subprocess.Popen
 
710
        :param sock: if proc.stdin/out is a socket from a socketpair, then sock
 
711
            should bzrlib's half of that socketpair.  If not passed, proc's
 
712
            stdin/out is assumed to be ordinary pipes.
 
713
        """
451
714
        self.proc = proc
 
715
        self._sock = sock
 
716
        # Add a weakref to proc that will attempt to do the same as self.close
 
717
        # to avoid leaving processes lingering indefinitely.
 
718
        def terminate(ref):
 
719
            _subproc_weakrefs.remove(ref)
 
720
            _close_ssh_proc(proc, sock)
 
721
        _subproc_weakrefs.add(weakref.ref(self, terminate))
452
722
 
453
723
    def send(self, data):
454
 
        return os.write(self.proc.stdin.fileno(), data)
455
 
 
456
 
    def recv_ready(self):
457
 
        # TODO: jam 20051215 this function is necessary to support the
458
 
        # pipelined() function. In reality, it probably should use
459
 
        # poll() or select() to actually return if there is data
460
 
        # available, otherwise we probably don't get any benefit
461
 
        return True
 
724
        if self._sock is not None:
 
725
            return self._sock.send(data)
 
726
        else:
 
727
            return os.write(self.proc.stdin.fileno(), data)
462
728
 
463
729
    def recv(self, count):
464
 
        return os.read(self.proc.stdout.fileno(), count)
465
 
 
466
 
    def close(self):
467
 
        self.proc.stdin.close()
468
 
        self.proc.stdout.close()
469
 
        self.proc.wait()
 
730
        if self._sock is not None:
 
731
            return self._sock.recv(count)
 
732
        else:
 
733
            return os.read(self.proc.stdout.fileno(), count)
 
734
 
 
735
    def close(self):
 
736
        _close_ssh_proc(self.proc, self._sock)
 
737
 
 
738
    def get_sock_or_pipes(self):
 
739
        if self._sock is not None:
 
740
            return 'socket', self._sock
 
741
        else:
 
742
            return 'pipes', (self.proc.stdout, self.proc.stdin)
 
743
 
 
744
 
 
745
class _ParamikoSSHConnection(SSHConnection):
 
746
    """An SSH connection via paramiko."""
 
747
 
 
748
    def __init__(self, channel):
 
749
        self.channel = channel
 
750
 
 
751
    def get_sock_or_pipes(self):
 
752
        return ('socket', self.channel)
 
753
 
 
754
    def close(self):
 
755
        return self.channel.close()
470
756
 
471
757