/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>
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
2
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
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,
2221.5.10 by Dmitry Vasiliev
Imports placed in alphabetical order
31
                           SSHVendorNotFound,
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
32
                           TransportError,
1996.2.1 by Andrew Bennetts
Fix NameError reported by Alexander Belchenko.
33
                           UnknownSSH,
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
34
                           )
35
1951.1.5 by Andrew Bennetts
Fix some missing imports with a bit of help from pyflakes.
36
from bzrlib.osutils import pathjoin
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
37
from bzrlib.trace import mutter, warning
1951.1.5 by Andrew Bennetts
Fix some missing imports with a bit of help from pyflakes.
38
import bzrlib.ui
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
39
40
try:
41
    import paramiko
42
except ImportError, e:
2104.5.1 by John Arbash Meinel
Remove the strict dependency on paramiko for ssh access
43
    # If we have an ssh subprocess, we don't strictly need paramiko for all ssh
44
    # access
45
    paramiko = None
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
46
else:
47
    from paramiko.sftp_client import SFTPClient
48
49
50
SYSTEM_HOSTKEYS = {}
51
BZR_HOSTKEYS = {}
52
53
1951.1.5 by Andrew Bennetts
Fix some missing imports with a bit of help from pyflakes.
54
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
55
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
56
# Paramiko 1.5 tries to open a socket.AF_UNIX in order to connect
57
# to ssh-agent. That attribute doesn't exist on win32 (it does in cygwin)
58
# so we get an AttributeError exception. So we will not try to
59
# connect to an agent if we are on win32 and using Paramiko older than 1.6
60
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
61
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
62
63
class SSHVendorManager(object):
64
    """Manager for manage SSH vendors."""
65
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
66
    # Note, although at first sign the class interface seems similar to
2221.5.22 by Dmitry Vasiliev
Updated note about registry.Registry
67
    # bzrlib.registry.Registry it is not possible/convenient to directly use
68
    # the Registry because the class just has "get()" interface instead of the
69
    # Registry's "get(key)".
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
70
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
71
    def __init__(self):
72
        self._ssh_vendors = {}
2221.5.8 by Dmitry Vasiliev
Added SSHVendorManager.clear_cache() method
73
        self._cached_ssh_vendor = None
2221.5.5 by Dmitry Vasiliev
Added 'register_default_vendor' method to the SSHVendorManager
74
        self._default_ssh_vendor = None
75
76
    def register_default_vendor(self, vendor):
77
        """Register default SSH vendor."""
78
        self._default_ssh_vendor = vendor
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
79
80
    def register_vendor(self, name, vendor):
2221.5.5 by Dmitry Vasiliev
Added 'register_default_vendor' method to the SSHVendorManager
81
        """Register new SSH vendor by name."""
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
82
        self._ssh_vendors[name] = vendor
83
2221.5.8 by Dmitry Vasiliev
Added SSHVendorManager.clear_cache() method
84
    def clear_cache(self):
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
85
        """Clear previously cached lookup result."""
2221.5.8 by Dmitry Vasiliev
Added SSHVendorManager.clear_cache() method
86
        self._cached_ssh_vendor = None
87
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
88
    def _get_vendor_by_environment(self, environment=None):
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
89
        """Return the vendor or None based on BZR_SSH environment variable.
90
91
        :raises UnknownSSH: if the BZR_SSH environment variable contains
92
                            unknown vendor name
93
        """
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
94
        if environment is None:
95
            environment = os.environ
96
        if 'BZR_SSH' in environment:
97
            vendor_name = environment['BZR_SSH']
98
            try:
99
                vendor = self._ssh_vendors[vendor_name]
100
            except KeyError:
101
                raise UnknownSSH(vendor_name)
102
            return vendor
103
        return None
104
105
    def _get_ssh_version_string(self, args):
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
106
        """Return SSH version string from the subprocess."""
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
107
        try:
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
108
            p = subprocess.Popen(args,
109
                                 stdout=subprocess.PIPE,
110
                                 stderr=subprocess.PIPE,
111
                                 **os_specific_subprocess_params())
112
            stdout, stderr = p.communicate()
113
        except OSError:
114
            stdout = stderr = ''
115
        return stdout + stderr
116
2772.3.1 by Martin Pool
Fix detection of ssh implementation on Windows
117
    def _get_vendor_by_version_string(self, version, args):
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
118
        """Return the vendor or None based on output from the subprocess.
119
120
        :param version: The output of 'ssh -V' like command.
2772.3.1 by Martin Pool
Fix detection of ssh implementation on Windows
121
        :param args: Command line that was run.
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
122
        """
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
123
        vendor = None
124
        if 'OpenSSH' in version:
125
            mutter('ssh implementation is OpenSSH')
126
            vendor = OpenSSHSubprocessVendor()
127
        elif 'SSH Secure Shell' in version:
128
            mutter('ssh implementation is SSH Corp.')
129
            vendor = SSHCorpSubprocessVendor()
2772.3.1 by Martin Pool
Fix detection of ssh implementation on Windows
130
        elif 'plink' in version and args[0] == 'plink':
2767.3.1 by Martin Albisetti
Fixed bug #107155
131
            # Checking if "plink" was the executed argument as Windows sometimes 
132
            # reports 'ssh -V' incorrectly with 'plink' in it's version. 
133
            # See https://bugs.launchpad.net/bzr/+bug/107155
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
134
            mutter("ssh implementation is Putty's plink.")
135
            vendor = PLinkSubprocessVendor()
136
        return vendor
137
138
    def _get_vendor_by_inspection(self):
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
139
        """Return the vendor or None by checking for known SSH implementations."""
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
140
        for args in [['ssh', '-V'], ['plink', '-V']]:
141
            version = self._get_ssh_version_string(args)
2767.3.1 by Martin Albisetti
Fixed bug #107155
142
            vendor = self._get_vendor_by_version_string(version, args)
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
143
            if vendor is not None:
144
                return vendor
145
        return None
146
147
    def get_vendor(self, environment=None):
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
148
        """Find out what version of SSH is on the system.
149
150
        :raises SSHVendorNotFound: if no any SSH vendor is found
151
        :raises UnknownSSH: if the BZR_SSH environment variable contains
152
                            unknown vendor name
153
        """
2221.5.8 by Dmitry Vasiliev
Added SSHVendorManager.clear_cache() method
154
        if self._cached_ssh_vendor is None:
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
155
            vendor = self._get_vendor_by_environment(environment)
156
            if vendor is None:
157
                vendor = self._get_vendor_by_inspection()
158
                if vendor is None:
159
                    mutter('falling back to default implementation')
2221.5.5 by Dmitry Vasiliev
Added 'register_default_vendor' method to the SSHVendorManager
160
                    vendor = self._default_ssh_vendor
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
161
                    if vendor is None:
162
                        raise SSHVendorNotFound()
2221.5.8 by Dmitry Vasiliev
Added SSHVendorManager.clear_cache() method
163
            self._cached_ssh_vendor = vendor
164
        return self._cached_ssh_vendor
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
165
166
_ssh_vendor_manager = SSHVendorManager()
167
_get_ssh_vendor = _ssh_vendor_manager.get_vendor
2221.5.5 by Dmitry Vasiliev
Added 'register_default_vendor' method to the SSHVendorManager
168
register_default_ssh_vendor = _ssh_vendor_manager.register_default_vendor
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
169
register_ssh_vendor = _ssh_vendor_manager.register_vendor
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
170
171
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
172
def _ignore_sigint():
173
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
174
    # doesn't handle it itself.
175
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
176
    import signal
177
    signal.signal(signal.SIGINT, signal.SIG_IGN)
178
179
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
180
class LoopbackSFTP(object):
181
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
182
183
    def __init__(self, sock):
184
        self.__socket = sock
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
185
 
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
186
    def send(self, data):
187
        return self.__socket.send(data)
188
189
    def recv(self, n):
190
        return self.__socket.recv(n)
191
192
    def recv_ready(self):
193
        return True
194
195
    def close(self):
196
        self.__socket.close()
197
198
199
class SSHVendor(object):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
200
    """Abstract base class for SSH vendor implementations."""
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
201
    
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
202
    def connect_sftp(self, username, password, host, port):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
203
        """Make an SSH connection, and return an SFTPClient.
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
204
        
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
205
        :param username: an ascii string
206
        :param password: an ascii string
207
        :param host: a host name as an ascii string
208
        :param port: a port number
209
        :type port: int
210
211
        :raises: ConnectionError if it cannot connect.
212
213
        :rtype: paramiko.sftp_client.SFTPClient
214
        """
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
215
        raise NotImplementedError(self.connect_sftp)
216
217
    def connect_ssh(self, username, password, host, port, command):
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
218
        """Make an SSH connection.
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
219
        
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
220
        :returns: something with a `close` method, and a `get_filelike_channels`
221
            method that returns a pair of (read, write) filelike objects.
1951.1.12 by Andrew Bennetts
Cosmetic tweaks.
222
        """
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
223
        raise NotImplementedError(self.connect_ssh)
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
224
        
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
225
    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
226
                                msg='Unable to connect to SSH host'):
227
        """Raise a SocketConnectionError with properly formatted host.
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
228
229
        This just unifies all the locations that try to raise ConnectionError,
230
        so that they format things properly.
231
        """
2052.4.4 by John Arbash Meinel
Create a SocketConnectionError to make creating nice errors easier
232
        raise SocketConnectionError(host=host, port=port, msg=msg,
233
                                    orig_error=orig_error)
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
234
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
235
236
class LoopbackVendor(SSHVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
237
    """SSH "vendor" that connects over a plain TCP socket, not SSH."""
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
238
    
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
239
    def connect_sftp(self, username, password, host, port):
240
        sock = socket.socket()
241
        try:
242
            sock.connect((host, port))
243
        except socket.error, e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
244
            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
245
        return SFTPClient(LoopbackSFTP(sock))
246
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
247
register_ssh_vendor('loopback', LoopbackVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
248
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
249
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
250
class _ParamikoSSHConnection(object):
251
    def __init__(self, channel):
252
        self.channel = channel
253
254
    def get_filelike_channels(self):
255
        return self.channel.makefile('rb'), self.channel.makefile('wb')
256
257
    def close(self):
258
        return self.channel.close()
259
260
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
261
class ParamikoVendor(SSHVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
262
    """Vendor that uses paramiko."""
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
263
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
264
    def _connect(self, username, password, host, port):
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
265
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
266
        
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
267
        load_host_keys()
268
269
        try:
270
            t = paramiko.Transport((host, port or 22))
271
            t.set_log_channel('bzr.paramiko')
272
            t.start_client()
273
        except (paramiko.SSHException, socket.error), e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
274
            self._raise_connection_error(host, port=port, orig_error=e)
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
275
            
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
276
        server_key = t.get_remote_server_key()
277
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
278
        keytype = server_key.get_name()
1711.9.10 by John Arbash Meinel
Update transport/ssh.py to remove has_key usage
279
        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
280
            our_server_key = SYSTEM_HOSTKEYS[host][keytype]
281
            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
282
        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
283
            our_server_key = BZR_HOSTKEYS[host][keytype]
284
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
285
        else:
286
            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
287
            add = getattr(BZR_HOSTKEYS, 'add', None)
288
            if add is not None: # paramiko >= 1.X.X
289
                BZR_HOSTKEYS.add(host, keytype, server_key)
290
            else:
1551.9.2 by Aaron Bentley
Bugfix for paramiko connections
291
                BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
292
            our_server_key = server_key
293
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
294
            save_host_keys()
295
        if server_key != our_server_key:
296
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
297
            filename2 = pathjoin(config_dir(), 'ssh_host_keys')
298
            raise TransportError('Host keys for %s do not match!  %s != %s' % \
299
                (host, our_server_key_hex, server_key_hex),
300
                ['Try editing %s or %s' % (filename1, filename2)])
301
302
        _paramiko_auth(username, password, host, t)
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
303
        return t
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
304
        
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
305
    def connect_sftp(self, username, password, host, port):
306
        t = self._connect(username, password, host, port)
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
307
        try:
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
308
            return t.open_sftp_client()
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
309
        except paramiko.SSHException, e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
310
            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
311
                                         msg='Unable to start sftp client')
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
312
313
    def connect_ssh(self, username, password, host, port, command):
314
        t = self._connect(username, password, host, port)
315
        try:
316
            channel = t.open_session()
317
            cmdline = ' '.join(command)
318
            channel.exec_command(cmdline)
319
            return _ParamikoSSHConnection(channel)
320
        except paramiko.SSHException, e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
321
            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
322
                                         msg='Unable to invoke remote bzr')
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
323
2104.5.1 by John Arbash Meinel
Remove the strict dependency on paramiko for ssh access
324
if paramiko is not None:
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
325
    vendor = ParamikoVendor()
326
    register_ssh_vendor('paramiko', vendor)
327
    register_ssh_vendor('none', vendor)
2221.5.5 by Dmitry Vasiliev
Added 'register_default_vendor' method to the SSHVendorManager
328
    register_default_ssh_vendor(vendor)
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
329
    del vendor
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
330
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
331
332
class SubprocessVendor(SSHVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
333
    """Abstract base class for vendors that use pipes to a subprocess."""
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
334
    
2018.1.6 by Andrew Bennetts
Remove a little bit of duplication in ssh.py
335
    def _connect(self, argv):
336
        proc = subprocess.Popen(argv,
337
                                stdin=subprocess.PIPE,
338
                                stdout=subprocess.PIPE,
339
                                **os_specific_subprocess_params())
340
        return SSHSubprocess(proc)
341
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
342
    def connect_sftp(self, username, password, host, port):
343
        try:
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
344
            argv = self._get_vendor_specific_argv(username, host, port,
345
                                                  subsystem='sftp')
2018.1.6 by Andrew Bennetts
Remove a little bit of duplication in ssh.py
346
            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.
347
            return SFTPClient(sock)
348
        except (EOFError, paramiko.SSHException), e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
349
            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.
350
        except (OSError, IOError), e:
351
            # If the machine is fast enough, ssh can actually exit
352
            # before we try and send it the sftp request, which
353
            # raises a Broken Pipe
354
            if e.errno not in (errno.EPIPE,):
355
                raise
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
356
            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.
357
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
358
    def connect_ssh(self, username, password, host, port, command):
359
        try:
360
            argv = self._get_vendor_specific_argv(username, host, port,
361
                                                  command=command)
2018.1.6 by Andrew Bennetts
Remove a little bit of duplication in ssh.py
362
            return self._connect(argv)
363
        except (EOFError), e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
364
            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).
365
        except (OSError, IOError), e:
366
            # If the machine is fast enough, ssh can actually exit
367
            # before we try and send it the sftp request, which
368
            # raises a Broken Pipe
369
            if e.errno not in (errno.EPIPE,):
370
                raise
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
371
            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).
372
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
373
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
374
                                  command=None):
375
        """Returns the argument list to run the subprocess with.
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
376
        
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
377
        Exactly one of 'subsystem' and 'command' must be specified.
378
        """
379
        raise NotImplementedError(self._get_vendor_specific_argv)
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
380
381
382
class OpenSSHSubprocessVendor(SubprocessVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
383
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
384
    
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
385
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
386
                                  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.
387
        assert subsystem is not None or command is not None, (
388
            'Must specify a command or subsystem')
389
        if subsystem is not None:
390
            assert command is None, (
391
                'subsystem and command are mutually exclusive')
392
        args = ['ssh',
393
                '-oForwardX11=no', '-oForwardAgent=no',
394
                '-oClearAllForwardings=yes', '-oProtocol=2',
395
                '-oNoHostAuthenticationForLocalhost=yes']
396
        if port is not None:
397
            args.extend(['-p', str(port)])
398
        if username is not None:
399
            args.extend(['-l', username])
400
        if subsystem is not None:
401
            args.extend(['-s', host, subsystem])
402
        else:
403
            args.extend([host] + command)
404
        return args
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
405
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
406
register_ssh_vendor('openssh', OpenSSHSubprocessVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
407
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
408
409
class SSHCorpSubprocessVendor(SubprocessVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
410
    """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.
411
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
412
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
413
                                  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.
414
        assert subsystem is not None or command is not None, (
415
            'Must specify a command or subsystem')
416
        if subsystem is not None:
417
            assert command is None, (
418
                'subsystem and command are mutually exclusive')
419
        args = ['ssh', '-x']
420
        if port is not None:
421
            args.extend(['-p', str(port)])
422
        if username is not None:
423
            args.extend(['-l', username])
424
        if subsystem is not None:
425
            args.extend(['-s', subsystem, host])
426
        else:
427
            args.extend([host] + command)
428
        return args
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
429
    
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
430
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
431
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
432
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
433
class PLinkSubprocessVendor(SubprocessVendor):
434
    """SSH vendor that uses the 'plink' executable from Putty."""
435
436
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
437
                                  command=None):
438
        assert subsystem is not None or command is not None, (
439
            'Must specify a command or subsystem')
440
        if subsystem is not None:
441
            assert command is None, (
442
                'subsystem and command are mutually exclusive')
443
        args = ['plink', '-x', '-a', '-ssh', '-2']
444
        if port is not None:
445
            args.extend(['-P', str(port)])
446
        if username is not None:
447
            args.extend(['-l', username])
448
        if subsystem is not None:
2221.5.3 by Dmitry Vasiliev
Fixed plink's arguments order. Added tests for such a case.
449
            args.extend(['-s', host, subsystem])
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
450
        else:
451
            args.extend([host] + command)
452
        return args
453
454
register_ssh_vendor('plink', PLinkSubprocessVendor())
455
456
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
457
def _paramiko_auth(username, password, host, paramiko_transport):
458
    # paramiko requires a username, but it might be none if nothing was supplied
459
    # use the local username, just in case.
460
    # We don't override username, because if we aren't using paramiko,
461
    # the username might be specified in ~/.ssh/config and we don't want to
462
    # force it to something else
463
    # Also, it would mess up the self.relpath() functionality
464
    username = username or getpass.getuser()
465
466
    if _use_ssh_agent:
467
        agent = paramiko.Agent()
468
        for key in agent.get_keys():
469
            mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
470
            try:
471
                paramiko_transport.auth_publickey(username, key)
472
                return
473
            except paramiko.SSHException, e:
474
                pass
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
475
    
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
476
    # okay, try finding id_rsa or id_dss?  (posix only)
477
    if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
478
        return
479
    if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
480
        return
481
482
    if password:
483
        try:
484
            paramiko_transport.auth_password(username, password)
485
            return
486
        except paramiko.SSHException, e:
487
            pass
488
489
    # give up and ask for a password
490
    password = bzrlib.ui.ui_factory.get_password(
491
            prompt='SSH %(user)s@%(host)s password',
492
            user=username, host=host)
493
    try:
494
        paramiko_transport.auth_password(username, password)
495
    except paramiko.SSHException, e:
496
        raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
497
                              (username, host), e)
498
499
500
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
501
    filename = os.path.expanduser('~/.ssh/' + filename)
502
    try:
503
        key = pkey_class.from_private_key_file(filename)
504
        paramiko_transport.auth_publickey(username, key)
505
        return True
506
    except paramiko.PasswordRequiredException:
507
        password = bzrlib.ui.ui_factory.get_password(
508
                prompt='SSH %(filename)s password',
509
                filename=filename)
510
        try:
511
            key = pkey_class.from_private_key_file(filename, password)
512
            paramiko_transport.auth_publickey(username, key)
513
            return True
514
        except paramiko.SSHException:
515
            mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
516
    except paramiko.SSHException:
517
        mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
518
    except IOError:
519
        pass
520
    return False
521
522
523
def load_host_keys():
524
    """
525
    Load system host keys (probably doesn't work on windows) and any
526
    "discovered" keys from previous sessions.
527
    """
528
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
529
    try:
530
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
2358.3.1 by Martin Pool
Update some too-general exception blocks
531
    except IOError, e:
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
532
        mutter('failed to load system host keys: ' + str(e))
533
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
534
    try:
535
        BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
2358.3.1 by Martin Pool
Update some too-general exception blocks
536
    except IOError, e:
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
537
        mutter('failed to load bzr host keys: ' + str(e))
538
        save_host_keys()
539
540
541
def save_host_keys():
542
    """
543
    Save "discovered" host keys in $(config)/ssh_host_keys/.
544
    """
545
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
546
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
547
    ensure_config_dir_exists()
548
549
    try:
550
        f = open(bzr_hostkey_path, 'w')
551
        f.write('# SSH host keys collected by bzr\n')
552
        for hostname, keys in BZR_HOSTKEYS.iteritems():
553
            for keytype, key in keys.iteritems():
554
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
555
        f.close()
556
    except IOError, e:
557
        mutter('failed to save bzr host keys: ' + str(e))
558
559
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
560
def os_specific_subprocess_params():
561
    """Get O/S specific subprocess parameters."""
562
    if sys.platform == 'win32':
563
        # setting the process group and closing fds is not supported on 
564
        # win32
565
        return {}
566
    else:
567
        # We close fds other than the pipes as the child process does not need 
568
        # them to be open.
569
        #
570
        # We also set the child process to ignore SIGINT.  Normally the signal
571
        # would be sent to every process in the foreground process group, but
572
        # this causes it to be seen only by bzr and not by ssh.  Python will
573
        # generate a KeyboardInterrupt in bzr, and we will then have a chance
574
        # to release locks or do other cleanup over ssh before the connection
575
        # goes away.  
576
        # <https://launchpad.net/products/bzr/+bug/5987>
577
        #
578
        # Running it in a separate process group is not good because then it
579
        # can't get non-echoed input of a password or passphrase.
580
        # <https://launchpad.net/products/bzr/+bug/40508>
581
        return {'preexec_fn': _ignore_sigint,
582
                'close_fds': True,
583
                }
584
1951.1.12 by Andrew Bennetts
Cosmetic tweaks.
585
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
586
class SSHSubprocess(object):
587
    """A socket-like object that talks to an ssh subprocess via pipes."""
588
589
    def __init__(self, proc):
590
        self.proc = proc
591
592
    def send(self, data):
593
        return os.write(self.proc.stdin.fileno(), data)
594
595
    def recv_ready(self):
596
        # TODO: jam 20051215 this function is necessary to support the
597
        # pipelined() function. In reality, it probably should use
598
        # poll() or select() to actually return if there is data
599
        # available, otherwise we probably don't get any benefit
600
        return True
601
602
    def recv(self, count):
603
        return os.read(self.proc.stdout.fileno(), count)
604
605
    def close(self):
606
        self.proc.stdin.close()
607
        self.proc.stdout.close()
608
        self.proc.wait()
609
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
610
    def get_filelike_channels(self):
611
        return (self.proc.stdout, self.proc.stdin)
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
612