~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/ssh.py

  • Committer: Alexander Belchenko
  • Date: 2006-10-14 08:51:07 UTC
  • mto: (2080.1.1 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 2081.
  • Revision ID: bialix@ukr.net-20061014085107-8dff865674eed30a
win32 installer: make short info page instead of full GPL license text

Show diffs side-by-side

added added

removed removed

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