/brz/remove-bazaar

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