~bzr-pqm/bzr/bzr.dev

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