~bzr-pqm/bzr/bzr.dev

5594.1.1 by Vincent Ladeuil
Fix socketpair-based SSH transport leaking socket into other child processes
1
# Copyright (C) 2006-2011 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
4183.7.1 by Sabin Iacob
update FSF mailing address
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
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
4304.2.1 by Vincent Ladeuil
Fix bug #367726 by reverting some default user handling introduced
21
import getpass
4555.1.1 by John Arbash Meinel
Fix bug #375867, check if password is a supported auth type
22
import logging
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
23
import os
24
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.
25
import subprocess
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
26
import sys
27
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
28
from bzrlib import (
29
    config,
30
    errors,
31
    osutils,
32
    trace,
33
    ui,
34
    )
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
35
36
try:
37
    import paramiko
38
except ImportError, e:
2104.5.1 by John Arbash Meinel
Remove the strict dependency on paramiko for ssh access
39
    # If we have an ssh subprocess, we don't strictly need paramiko for all ssh
40
    # access
41
    paramiko = None
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
42
else:
43
    from paramiko.sftp_client import SFTPClient
44
45
46
SYSTEM_HOSTKEYS = {}
47
BZR_HOSTKEYS = {}
48
49
1951.1.5 by Andrew Bennetts
Fix some missing imports with a bit of help from pyflakes.
50
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
51
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
52
# Paramiko 1.5 tries to open a socket.AF_UNIX in order to connect
53
# to ssh-agent. That attribute doesn't exist on win32 (it does in cygwin)
54
# so we get an AttributeError exception. So we will not try to
55
# connect to an agent if we are on win32 and using Paramiko older than 1.6
56
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
57
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
58
59
class SSHVendorManager(object):
60
    """Manager for manage SSH vendors."""
61
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
62
    # Note, although at first sign the class interface seems similar to
2221.5.22 by Dmitry Vasiliev
Updated note about registry.Registry
63
    # bzrlib.registry.Registry it is not possible/convenient to directly use
64
    # the Registry because the class just has "get()" interface instead of the
65
    # Registry's "get(key)".
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
66
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
67
    def __init__(self):
68
        self._ssh_vendors = {}
2221.5.8 by Dmitry Vasiliev
Added SSHVendorManager.clear_cache() method
69
        self._cached_ssh_vendor = None
2221.5.5 by Dmitry Vasiliev
Added 'register_default_vendor' method to the SSHVendorManager
70
        self._default_ssh_vendor = None
71
72
    def register_default_vendor(self, vendor):
73
        """Register default SSH vendor."""
74
        self._default_ssh_vendor = vendor
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
75
76
    def register_vendor(self, name, vendor):
2221.5.5 by Dmitry Vasiliev
Added 'register_default_vendor' method to the SSHVendorManager
77
        """Register new SSH vendor by name."""
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
78
        self._ssh_vendors[name] = vendor
79
2221.5.8 by Dmitry Vasiliev
Added SSHVendorManager.clear_cache() method
80
    def clear_cache(self):
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
81
        """Clear previously cached lookup result."""
2221.5.8 by Dmitry Vasiliev
Added SSHVendorManager.clear_cache() method
82
        self._cached_ssh_vendor = None
83
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
84
    def _get_vendor_by_environment(self, environment=None):
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
85
        """Return the vendor or None based on BZR_SSH environment variable.
86
87
        :raises UnknownSSH: if the BZR_SSH environment variable contains
88
                            unknown vendor name
89
        """
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
90
        if environment is None:
91
            environment = os.environ
92
        if 'BZR_SSH' in environment:
93
            vendor_name = environment['BZR_SSH']
94
            try:
95
                vendor = self._ssh_vendors[vendor_name]
96
            except KeyError:
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
97
                vendor = self._get_vendor_from_path(vendor_name)
98
                if vendor is None:
99
                    raise errors.UnknownSSH(vendor_name)
100
                vendor.executable_path = vendor_name
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
101
            return vendor
102
        return None
103
104
    def _get_ssh_version_string(self, args):
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
105
        """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
106
        try:
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
107
            p = subprocess.Popen(args,
108
                                 stdout=subprocess.PIPE,
109
                                 stderr=subprocess.PIPE,
110
                                 **os_specific_subprocess_params())
111
            stdout, stderr = p.communicate()
112
        except OSError:
113
            stdout = stderr = ''
114
        return stdout + stderr
115
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
116
    def _get_vendor_by_version_string(self, version, progname):
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
117
        """Return the vendor or None based on output from the subprocess.
118
119
        :param version: The output of 'ssh -V' like command.
2772.3.1 by Martin Pool
Fix detection of ssh implementation on Windows
120
        :param args: Command line that was run.
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
121
        """
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
122
        vendor = None
123
        if 'OpenSSH' in version:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
124
            trace.mutter('ssh implementation is OpenSSH')
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
125
            vendor = OpenSSHSubprocessVendor()
126
        elif 'SSH Secure Shell' in version:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
127
            trace.mutter('ssh implementation is SSH Corp.')
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
128
            vendor = SSHCorpSubprocessVendor()
5444.2.3 by Matthew Gordon
Added missing check for GNU lsh in _get_vendor_by_version_string().
129
        elif 'lsh' in version:
130
            trace.mutter('ssh implementation is GNU lsh.')
131
            vendor = LSHSubprocessVendor()
4595.17.2 by Martin
Merge bzr.dev 4789 to resolve conflict from the disabling of plink auto-detection, and relocate NEWS
132
        # As plink user prompts are not handled currently, don't auto-detect
133
        # it by inspection below, but keep this vendor detection for if a path
134
        # is given in BZR_SSH. See https://bugs.launchpad.net/bugs/414743
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
135
        elif 'plink' in version and progname == 'plink':
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
136
            # Checking if "plink" was the executed argument as Windows
5448.2.1 by Martin
Fix some "its" vs. "it's" spelling confusion in bzrlib code... also, ahem, a name in the NEWS file
137
            # sometimes reports 'ssh -V' incorrectly with 'plink' in its
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
138
            # version.  See https://bugs.launchpad.net/bzr/+bug/107155
139
            trace.mutter("ssh implementation is Putty's plink.")
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
140
            vendor = PLinkSubprocessVendor()
141
        return vendor
142
143
    def _get_vendor_by_inspection(self):
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
144
        """Return the vendor or None by checking for known SSH implementations."""
4595.17.2 by Martin
Merge bzr.dev 4789 to resolve conflict from the disabling of plink auto-detection, and relocate NEWS
145
        version = self._get_ssh_version_string(['ssh', '-V'])
146
        return self._get_vendor_by_version_string(version, "ssh")
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
147
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
148
    def _get_vendor_from_path(self, path):
149
        """Return the vendor or None using the program at the given path"""
150
        version = self._get_ssh_version_string([path, '-V'])
151
        return self._get_vendor_by_version_string(version, 
152
            os.path.splitext(os.path.basename(path))[0])
153
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
154
    def get_vendor(self, environment=None):
2221.5.15 by Dmitry Vasiliev
Added docstrings for all SSHVendorManager's methods
155
        """Find out what version of SSH is on the system.
156
157
        :raises SSHVendorNotFound: if no any SSH vendor is found
158
        :raises UnknownSSH: if the BZR_SSH environment variable contains
159
                            unknown vendor name
160
        """
2221.5.8 by Dmitry Vasiliev
Added SSHVendorManager.clear_cache() method
161
        if self._cached_ssh_vendor is None:
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
162
            vendor = self._get_vendor_by_environment(environment)
163
            if vendor is None:
164
                vendor = self._get_vendor_by_inspection()
165
                if vendor is None:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
166
                    trace.mutter('falling back to default implementation')
2221.5.5 by Dmitry Vasiliev
Added 'register_default_vendor' method to the SSHVendorManager
167
                    vendor = self._default_ssh_vendor
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
168
                    if vendor is None:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
169
                        raise errors.SSHVendorNotFound()
2221.5.8 by Dmitry Vasiliev
Added SSHVendorManager.clear_cache() method
170
            self._cached_ssh_vendor = vendor
171
        return self._cached_ssh_vendor
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
172
173
_ssh_vendor_manager = SSHVendorManager()
174
_get_ssh_vendor = _ssh_vendor_manager.get_vendor
2221.5.5 by Dmitry Vasiliev
Added 'register_default_vendor' method to the SSHVendorManager
175
register_default_ssh_vendor = _ssh_vendor_manager.register_default_vendor
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
176
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
177
178
5050.2.1 by Martin
Drive-by fix for breakin killing off ssh child processes
179
def _ignore_signals():
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
180
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
181
    # doesn't handle it itself.
182
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
183
    import signal
184
    signal.signal(signal.SIGINT, signal.SIG_IGN)
5050.2.1 by Martin
Drive-by fix for breakin killing off ssh child processes
185
    # GZ 2010-02-19: Perhaps make this check if breakin is installed instead
186
    if signal.getsignal(signal.SIGQUIT) != signal.SIG_DFL:
187
        signal.signal(signal.SIGQUIT, signal.SIG_IGN)
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
188
189
3353.1.3 by Andrew Bennetts
Always adapt sockets to look like paramiko Channels before passing them to paramiko's SFTPClient.
190
class SocketAsChannelAdapter(object):
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
191
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
192
193
    def __init__(self, sock):
194
        self.__socket = sock
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
195
3353.1.2 by Andrew Bennetts
Add get_name to LoopbackSFTP. Makes the current tests pass with current paramiko.
196
    def get_name(self):
3353.1.3 by Andrew Bennetts
Always adapt sockets to look like paramiko Channels before passing them to paramiko's SFTPClient.
197
        return "bzr SocketAsChannelAdapter"
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
198
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
199
    def send(self, data):
200
        return self.__socket.send(data)
201
202
    def recv(self, n):
3353.1.3 by Andrew Bennetts
Always adapt sockets to look like paramiko Channels before passing them to paramiko's SFTPClient.
203
        try:
204
            return self.__socket.recv(n)
205
        except socket.error, e:
206
            if e.args[0] in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED,
207
                             errno.EBADF):
208
                # Connection has closed.  Paramiko expects an empty string in
209
                # this case, not an exception.
210
                return ''
211
            raise
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
212
213
    def recv_ready(self):
3353.1.3 by Andrew Bennetts
Always adapt sockets to look like paramiko Channels before passing them to paramiko's SFTPClient.
214
        # TODO: jam 20051215 this function is necessary to support the
215
        # pipelined() function. In reality, it probably should use
216
        # poll() or select() to actually return if there is data
217
        # available, otherwise we probably don't get any benefit
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
218
        return True
219
220
    def close(self):
221
        self.__socket.close()
222
223
224
class SSHVendor(object):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
225
    """Abstract base class for SSH vendor implementations."""
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
226
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
227
    def connect_sftp(self, username, password, host, port):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
228
        """Make an SSH connection, and return an SFTPClient.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
229
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
230
        :param username: an ascii string
231
        :param password: an ascii string
232
        :param host: a host name as an ascii string
233
        :param port: a port number
234
        :type port: int
235
236
        :raises: ConnectionError if it cannot connect.
237
238
        :rtype: paramiko.sftp_client.SFTPClient
239
        """
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
240
        raise NotImplementedError(self.connect_sftp)
241
242
    def connect_ssh(self, username, password, host, port, command):
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
243
        """Make an SSH connection.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
244
5284.5.1 by Andrew Bennetts
Use socketpairs (rather than pipes) for SSH subprocesses where possible, and formalise some internal APIs a little more.
245
        :returns: an SSHConnection.
1951.1.12 by Andrew Bennetts
Cosmetic tweaks.
246
        """
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
247
        raise NotImplementedError(self.connect_ssh)
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
248
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
249
    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
250
                                msg='Unable to connect to SSH host'):
251
        """Raise a SocketConnectionError with properly formatted host.
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
252
253
        This just unifies all the locations that try to raise ConnectionError,
254
        so that they format things properly.
255
        """
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
256
        raise errors.SocketConnectionError(host=host, port=port, msg=msg,
257
                                           orig_error=orig_error)
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
258
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
259
260
class LoopbackVendor(SSHVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
261
    """SSH "vendor" that connects over a plain TCP socket, not SSH."""
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
262
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
263
    def connect_sftp(self, username, password, host, port):
264
        sock = socket.socket()
265
        try:
266
            sock.connect((host, port))
267
        except socket.error, e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
268
            self._raise_connection_error(host, port=port, orig_error=e)
3353.1.3 by Andrew Bennetts
Always adapt sockets to look like paramiko Channels before passing them to paramiko's SFTPClient.
269
        return SFTPClient(SocketAsChannelAdapter(sock))
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
270
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
271
register_ssh_vendor('loopback', LoopbackVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
272
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
273
274
class ParamikoVendor(SSHVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
275
    """Vendor that uses paramiko."""
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
276
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
277
    def _connect(self, username, password, host, port):
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
278
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
2900.2.8 by Vincent Ladeuil
Make sftp and bzr+ssh aware of authentication config.
279
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
280
        load_host_keys()
281
282
        try:
283
            t = paramiko.Transport((host, port or 22))
284
            t.set_log_channel('bzr.paramiko')
285
            t.start_client()
286
        except (paramiko.SSHException, socket.error), e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
287
            self._raise_connection_error(host, port=port, orig_error=e)
2900.2.8 by Vincent Ladeuil
Make sftp and bzr+ssh aware of authentication config.
288
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
289
        server_key = t.get_remote_server_key()
290
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
291
        keytype = server_key.get_name()
1711.9.10 by John Arbash Meinel
Update transport/ssh.py to remove has_key usage
292
        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
293
            our_server_key = SYSTEM_HOSTKEYS[host][keytype]
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
294
            our_server_key_hex = paramiko.util.hexify(
295
                our_server_key.get_fingerprint())
1711.9.10 by John Arbash Meinel
Update transport/ssh.py to remove has_key usage
296
        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
297
            our_server_key = BZR_HOSTKEYS[host][keytype]
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
298
            our_server_key_hex = paramiko.util.hexify(
299
                our_server_key.get_fingerprint())
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
300
        else:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
301
            trace.warning('Adding %s host key for %s: %s'
302
                          % (keytype, host, server_key_hex))
2127.3.1 by Alexander Belchenko
Use BZR_HOSTKEYS.add instead of deprecated dict-like paramiko interface
303
            add = getattr(BZR_HOSTKEYS, 'add', None)
304
            if add is not None: # paramiko >= 1.X.X
305
                BZR_HOSTKEYS.add(host, keytype, server_key)
306
            else:
1551.9.2 by Aaron Bentley
Bugfix for paramiko connections
307
                BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
308
            our_server_key = server_key
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
309
            our_server_key_hex = paramiko.util.hexify(
310
                our_server_key.get_fingerprint())
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
311
            save_host_keys()
312
        if server_key != our_server_key:
313
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
314
            filename2 = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
315
            raise errors.TransportError(
316
                'Host keys for %s do not match!  %s != %s' %
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
317
                (host, our_server_key_hex, server_key_hex),
318
                ['Try editing %s or %s' % (filename1, filename2)])
319
2900.2.8 by Vincent Ladeuil
Make sftp and bzr+ssh aware of authentication config.
320
        _paramiko_auth(username, password, host, port, t)
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
321
        return t
2900.2.8 by Vincent Ladeuil
Make sftp and bzr+ssh aware of authentication config.
322
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
323
    def connect_sftp(self, username, password, host, port):
324
        t = self._connect(username, password, host, port)
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
325
        try:
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
326
            return t.open_sftp_client()
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
327
        except paramiko.SSHException, e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
328
            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
329
                                         msg='Unable to start sftp client')
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
330
331
    def connect_ssh(self, username, password, host, port, command):
332
        t = self._connect(username, password, host, port)
333
        try:
334
            channel = t.open_session()
335
            cmdline = ' '.join(command)
336
            channel.exec_command(cmdline)
337
            return _ParamikoSSHConnection(channel)
338
        except paramiko.SSHException, e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
339
            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
340
                                         msg='Unable to invoke remote bzr')
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
341
5430.6.1 by Andrew Bennetts
Simplify connect_sftp/ssh error handling, hopefully resolving intermittent test failure in test_bad_connection_ssh.
342
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
2104.5.1 by John Arbash Meinel
Remove the strict dependency on paramiko for ssh access
343
if paramiko is not None:
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
344
    vendor = ParamikoVendor()
345
    register_ssh_vendor('paramiko', vendor)
346
    register_ssh_vendor('none', vendor)
2221.5.5 by Dmitry Vasiliev
Added 'register_default_vendor' method to the SSHVendorManager
347
    register_default_ssh_vendor(vendor)
5430.6.1 by Andrew Bennetts
Simplify connect_sftp/ssh error handling, hopefully resolving intermittent test failure in test_bad_connection_ssh.
348
    _ssh_connection_errors += (paramiko.SSHException,)
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
349
    del vendor
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
350
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
351
352
class SubprocessVendor(SSHVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
353
    """Abstract base class for vendors that use pipes to a subprocess."""
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
354
2018.1.6 by Andrew Bennetts
Remove a little bit of duplication in ssh.py
355
    def _connect(self, argv):
5284.5.1 by Andrew Bennetts
Use socketpairs (rather than pipes) for SSH subprocesses where possible, and formalise some internal APIs a little more.
356
        # Attempt to make a socketpair to use as stdin/stdout for the SSH
357
        # subprocess.  We prefer sockets to pipes because they support
358
        # non-blocking short reads, allowing us to optimistically read 64k (or
359
        # whatever) chunks.
360
        try:
361
            my_sock, subproc_sock = socket.socketpair()
5582.6.1 by Max Bowsher
Fix socketpair-based SSH transport leaking socket into other child processes.
362
            osutils.set_fd_cloexec(my_sock)
5284.5.1 by Andrew Bennetts
Use socketpairs (rather than pipes) for SSH subprocesses where possible, and formalise some internal APIs a little more.
363
        except (AttributeError, socket.error):
364
            # This platform doesn't support socketpair(), so just use ordinary
365
            # pipes instead.
366
            stdin = stdout = subprocess.PIPE
5050.54.3 by Max Bowsher
Also close the subprocess side of the socketpair within bzrlib.
367
            my_sock, subproc_sock = None, None
5284.5.1 by Andrew Bennetts
Use socketpairs (rather than pipes) for SSH subprocesses where possible, and formalise some internal APIs a little more.
368
        else:
369
            stdin = stdout = subproc_sock
370
        proc = subprocess.Popen(argv, stdin=stdin, stdout=stdout,
2018.1.6 by Andrew Bennetts
Remove a little bit of duplication in ssh.py
371
                                **os_specific_subprocess_params())
5050.54.3 by Max Bowsher
Also close the subprocess side of the socketpair within bzrlib.
372
        if subproc_sock is not None:
373
            subproc_sock.close()
374
        return SSHSubprocessConnection(proc, sock=my_sock)
2018.1.6 by Andrew Bennetts
Remove a little bit of duplication in ssh.py
375
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
376
    def connect_sftp(self, username, password, host, port):
377
        try:
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
378
            argv = self._get_vendor_specific_argv(username, host, port,
379
                                                  subsystem='sftp')
2018.1.6 by Andrew Bennetts
Remove a little bit of duplication in ssh.py
380
            sock = self._connect(argv)
3353.1.3 by Andrew Bennetts
Always adapt sockets to look like paramiko Channels before passing them to paramiko's SFTPClient.
381
            return SFTPClient(SocketAsChannelAdapter(sock))
5430.6.1 by Andrew Bennetts
Simplify connect_sftp/ssh error handling, hopefully resolving intermittent test failure in test_bad_connection_ssh.
382
        except _ssh_connection_errors, e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
383
            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.
384
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
385
    def connect_ssh(self, username, password, host, port, command):
386
        try:
387
            argv = self._get_vendor_specific_argv(username, host, port,
388
                                                  command=command)
2018.1.6 by Andrew Bennetts
Remove a little bit of duplication in ssh.py
389
            return self._connect(argv)
5430.6.1 by Andrew Bennetts
Simplify connect_sftp/ssh error handling, hopefully resolving intermittent test failure in test_bad_connection_ssh.
390
        except _ssh_connection_errors, e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
391
            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).
392
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
393
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
394
                                  command=None):
395
        """Returns the argument list to run the subprocess with.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
396
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
397
        Exactly one of 'subsystem' and 'command' must be specified.
398
        """
399
        raise NotImplementedError(self._get_vendor_specific_argv)
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
400
401
402
class OpenSSHSubprocessVendor(SubprocessVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
403
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
404
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
405
    executable_path = 'ssh'
406
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
407
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
408
                                  command=None):
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
409
        args = [self.executable_path,
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
                '-oForwardX11=no', '-oForwardAgent=no',
5459.4.1 by Neil Martinsen-Burrell
dont force openssh to use protocol=2
411
                '-oClearAllForwardings=yes',
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
412
                '-oNoHostAuthenticationForLocalhost=yes']
413
        if port is not None:
414
            args.extend(['-p', str(port)])
415
        if username is not None:
416
            args.extend(['-l', username])
417
        if subsystem is not None:
418
            args.extend(['-s', host, subsystem])
419
        else:
420
            args.extend([host] + command)
421
        return args
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
422
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
423
register_ssh_vendor('openssh', OpenSSHSubprocessVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
424
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
425
426
class SSHCorpSubprocessVendor(SubprocessVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
427
    """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.
428
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
429
    executable_path = 'ssh'
430
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
431
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
432
                                  command=None):
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
433
        args = [self.executable_path, '-x']
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
434
        if port is not None:
435
            args.extend(['-p', str(port)])
436
        if username is not None:
437
            args.extend(['-l', username])
438
        if subsystem is not None:
439
            args.extend(['-s', subsystem, host])
440
        else:
441
            args.extend([host] + command)
442
        return args
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
443
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
444
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
445
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
446
5444.2.1 by Matthew Gordon
Added GNU lsh support to supported SSH vendors.
447
class LSHSubprocessVendor(SubprocessVendor):
448
    """SSH vendor that uses the 'lsh' executable from GNU"""
449
450
    executable_path = 'lsh'
451
452
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
453
                                  command=None):
454
        args = [self.executable_path]
455
        if port is not None:
456
            args.extend(['-p', str(port)])
457
        if username is not None:
458
            args.extend(['-l', username])
459
        if subsystem is not None:
460
            args.extend(['--subsystem', subsystem, host])
461
        else:
462
            args.extend([host] + command)
463
        return args
464
465
register_ssh_vendor('lsh', LSHSubprocessVendor())
466
467
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
468
class PLinkSubprocessVendor(SubprocessVendor):
469
    """SSH vendor that uses the 'plink' executable from Putty."""
470
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
471
    executable_path = 'plink'
472
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
473
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
474
                                  command=None):
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
475
        args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
476
        if port is not None:
477
            args.extend(['-P', str(port)])
478
        if username is not None:
479
            args.extend(['-l', username])
480
        if subsystem is not None:
2221.5.3 by Dmitry Vasiliev
Fixed plink's arguments order. Added tests for such a case.
481
            args.extend(['-s', host, subsystem])
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
482
        else:
483
            args.extend([host] + command)
484
        return args
485
486
register_ssh_vendor('plink', PLinkSubprocessVendor())
487
488
2900.2.8 by Vincent Ladeuil
Make sftp and bzr+ssh aware of authentication config.
489
def _paramiko_auth(username, password, host, port, paramiko_transport):
4222.3.4 by Jelmer Vernooij
Default to getpass.getuser() in AuthenticationConfig.get_user(), but allow
490
    auth = config.AuthenticationConfig()
3777.1.5 by Aaron Bentley
Remove AuthenticationConfig handling from Paramiko SSHVendor
491
    # paramiko requires a username, but it might be none if nothing was
492
    # supplied.  If so, use the local username.
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
493
    if username is None:
4304.2.1 by Vincent Ladeuil
Fix bug #367726 by reverting some default user handling introduced
494
        username = auth.get_user('ssh', host, port=port,
495
                                 default=getpass.getuser())
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
496
    if _use_ssh_agent:
497
        agent = paramiko.Agent()
498
        for key in agent.get_keys():
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
499
            trace.mutter('Trying SSH agent key %s'
500
                         % paramiko.util.hexify(key.get_fingerprint()))
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
501
            try:
502
                paramiko_transport.auth_publickey(username, key)
503
                return
504
            except paramiko.SSHException, e:
505
                pass
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
506
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
507
    # okay, try finding id_rsa or id_dss?  (posix only)
508
    if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
509
        return
510
    if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
511
        return
512
4555.1.1 by John Arbash Meinel
Fix bug #375867, check if password is a supported auth type
513
    # If we have gotten this far, we are about to try for passwords, do an
514
    # auth_none check to see if it is even supported.
515
    supported_auth_types = []
516
    try:
517
        # Note that with paramiko <1.7.5 this logs an INFO message:
518
        #    Authentication type (none) not permitted.
519
        # So we explicitly disable the logging level for this action
520
        old_level = paramiko_transport.logger.level
521
        paramiko_transport.logger.setLevel(logging.WARNING)
522
        try:
523
            paramiko_transport.auth_none(username)
524
        finally:
525
            paramiko_transport.logger.setLevel(old_level)
526
    except paramiko.BadAuthenticationType, e:
527
        # Supported methods are in the exception
528
        supported_auth_types = e.allowed_types
529
    except paramiko.SSHException, e:
530
        # Don't know what happened, but just ignore it
531
        pass
4634.56.1 by Andrew Bennetts
Try paramiko's auth_password if the server supports 'keyboard-interactive' auth, even if it doesn't support 'password'.
532
    # We treat 'keyboard-interactive' and 'password' auth methods identically,
533
    # because Paramiko's auth_password method will automatically try
534
    # 'keyboard-interactive' auth (using the password as the response) if
535
    # 'password' auth is not available.  Apparently some Debian and Gentoo
536
    # OpenSSH servers require this.
537
    # XXX: It's possible for a server to require keyboard-interactive auth that
538
    # requires something other than a single password, but we currently don't
539
    # support that.
540
    if ('password' not in supported_auth_types and
541
        'keyboard-interactive' not in supported_auth_types):
4555.1.1 by John Arbash Meinel
Fix bug #375867, check if password is a supported auth type
542
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
4555.1.3 by John Arbash Meinel
Reformat the errors so they aren't so long.
543
            '\n  %s@%s\nsupported auth types: %s'
4555.1.1 by John Arbash Meinel
Fix bug #375867, check if password is a supported auth type
544
            % (username, host, supported_auth_types))
545
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
546
    if password:
547
        try:
548
            paramiko_transport.auth_password(username, password)
549
            return
550
        except paramiko.SSHException, e:
551
            pass
552
553
    # give up and ask for a password
2900.2.12 by Vincent Ladeuil
Since all schemes query AuthenticationConfig then prompt user, make that
554
    password = auth.get_password('ssh', host, username, port=port)
4555.1.1 by John Arbash Meinel
Fix bug #375867, check if password is a supported auth type
555
    # get_password can still return None, which means we should not prompt
556
    if password is not None:
557
        try:
558
            paramiko_transport.auth_password(username, password)
559
        except paramiko.SSHException, e:
4555.1.3 by John Arbash Meinel
Reformat the errors so they aren't so long.
560
            raise errors.ConnectionError(
561
                'Unable to authenticate to SSH host as'
562
                '\n  %s@%s\n' % (username, host), e)
4555.1.1 by John Arbash Meinel
Fix bug #375867, check if password is a supported auth type
563
    else:
4555.1.3 by John Arbash Meinel
Reformat the errors so they aren't so long.
564
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
565
                                     '  %s@%s' % (username, host))
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
566
567
568
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
569
    filename = os.path.expanduser('~/.ssh/' + filename)
570
    try:
571
        key = pkey_class.from_private_key_file(filename)
572
        paramiko_transport.auth_publickey(username, key)
573
        return True
574
    except paramiko.PasswordRequiredException:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
575
        password = ui.ui_factory.get_password(
5863.6.1 by Jelmer Vernooij
Require a unicode prompt to be passed into all methods that prompt.
576
            prompt=u'SSH %(filename)s password',
577
            filename=filename.decode(osutils._fs_enc))
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
578
        try:
579
            key = pkey_class.from_private_key_file(filename, password)
580
            paramiko_transport.auth_publickey(username, key)
581
            return True
582
        except paramiko.SSHException:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
583
            trace.mutter('SSH authentication via %s key failed.'
584
                         % (os.path.basename(filename),))
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
585
    except paramiko.SSHException:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
586
        trace.mutter('SSH authentication via %s key failed.'
587
                     % (os.path.basename(filename),))
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
588
    except IOError:
589
        pass
590
    return False
591
592
593
def load_host_keys():
594
    """
595
    Load system host keys (probably doesn't work on windows) and any
596
    "discovered" keys from previous sessions.
597
    """
598
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
599
    try:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
600
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(
601
            os.path.expanduser('~/.ssh/known_hosts'))
2358.3.1 by Martin Pool
Update some too-general exception blocks
602
    except IOError, e:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
603
        trace.mutter('failed to load system host keys: ' + str(e))
604
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
605
    try:
606
        BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
2358.3.1 by Martin Pool
Update some too-general exception blocks
607
    except IOError, e:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
608
        trace.mutter('failed to load bzr host keys: ' + str(e))
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
609
        save_host_keys()
610
611
612
def save_host_keys():
613
    """
614
    Save "discovered" host keys in $(config)/ssh_host_keys/.
615
    """
616
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
617
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
618
    config.ensure_config_dir_exists()
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
619
620
    try:
621
        f = open(bzr_hostkey_path, 'w')
622
        f.write('# SSH host keys collected by bzr\n')
623
        for hostname, keys in BZR_HOSTKEYS.iteritems():
624
            for keytype, key in keys.iteritems():
625
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
626
        f.close()
627
    except IOError, e:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
628
        trace.mutter('failed to save bzr host keys: ' + str(e))
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
629
630
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
631
def os_specific_subprocess_params():
632
    """Get O/S specific subprocess parameters."""
633
    if sys.platform == 'win32':
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
634
        # setting the process group and closing fds is not supported on
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
635
        # win32
636
        return {}
637
    else:
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
638
        # We close fds other than the pipes as the child process does not need
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
639
        # them to be open.
640
        #
641
        # We also set the child process to ignore SIGINT.  Normally the signal
642
        # would be sent to every process in the foreground process group, but
643
        # this causes it to be seen only by bzr and not by ssh.  Python will
644
        # generate a KeyboardInterrupt in bzr, and we will then have a chance
645
        # to release locks or do other cleanup over ssh before the connection
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
646
        # goes away.
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
647
        # <https://launchpad.net/products/bzr/+bug/5987>
648
        #
649
        # Running it in a separate process group is not good because then it
650
        # can't get non-echoed input of a password or passphrase.
651
        # <https://launchpad.net/products/bzr/+bug/40508>
5050.2.1 by Martin
Drive-by fix for breakin killing off ssh child processes
652
        return {'preexec_fn': _ignore_signals,
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
653
                'close_fds': True,
654
                }
655
4824.1.1 by Andrew Bennetts
Terminate SSHSubprocesses when no refs to them are left, in case .close is never called.
656
import weakref
657
_subproc_weakrefs = set()
658
5050.54.1 by Max Bowsher
Do close the socket used for stdin/out to a ssh subprocess.
659
def _close_ssh_proc(proc, sock):
5050.30.1 by Andrew Bennetts
Fix AttributeError in _close_ssh_proc.
660
    """Carefully close stdin/stdout and reap the SSH process.
661
662
    If the pipes are already closed and/or the process has already been
663
    wait()ed on, that's ok, and no error is raised.  The goal is to do our best
664
    to clean up (whether or not a clean up was already tried).
665
    """
5050.54.1 by Max Bowsher
Do close the socket used for stdin/out to a ssh subprocess.
666
    funcs = []
667
    for closeable in (proc.stdin, proc.stdout, sock):
668
        # We expect that either proc (a subprocess.Popen) will have stdin and
669
        # stdout streams to close, or that we will have been passed a socket to
670
        # close, with the option not in use being None.
671
        if closeable is not None:
672
            funcs.append(closeable.close)
673
    funcs.append(proc.wait)
674
    for func in funcs:
675
        try:
676
            func()
4824.1.1 by Andrew Bennetts
Terminate SSHSubprocesses when no refs to them are left, in case .close is never called.
677
        except OSError:
5050.30.1 by Andrew Bennetts
Fix AttributeError in _close_ssh_proc.
678
            # It's ok for the pipe to already be closed, or the process to
679
            # already be finished.
680
            continue
4824.1.1 by Andrew Bennetts
Terminate SSHSubprocesses when no refs to them are left, in case .close is never called.
681
1951.1.12 by Andrew Bennetts
Cosmetic tweaks.
682
5284.5.1 by Andrew Bennetts
Use socketpairs (rather than pipes) for SSH subprocesses where possible, and formalise some internal APIs a little more.
683
class SSHConnection(object):
684
    """Abstract base class for SSH connections."""
685
686
    def get_sock_or_pipes(self):
687
        """Returns a (kind, io_object) pair.
688
689
        If kind == 'socket', then io_object is a socket.
690
691
        If kind == 'pipes', then io_object is a pair of file-like objects
692
        (read_from, write_to).
693
        """
694
        raise NotImplementedError(self.get_sock_or_pipes)
695
696
    def close(self):
697
        raise NotImplementedError(self.close)
698
699
700
class SSHSubprocessConnection(SSHConnection):
5284.5.3 by Andrew Bennetts
Docstring tweaks.
701
    """A connection to an ssh subprocess via pipes or a socket.
702
703
    This class is also socket-like enough to be used with
704
    SocketAsChannelAdapter (it has 'send' and 'recv' methods).
705
    """
5284.5.1 by Andrew Bennetts
Use socketpairs (rather than pipes) for SSH subprocesses where possible, and formalise some internal APIs a little more.
706
707
    def __init__(self, proc, sock=None):
708
        """Constructor.
709
710
        :param proc: a subprocess.Popen
711
        :param sock: if proc.stdin/out is a socket from a socketpair, then sock
712
            should bzrlib's half of that socketpair.  If not passed, proc's
713
            stdin/out is assumed to be ordinary pipes.
714
        """
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
715
        self.proc = proc
5284.5.1 by Andrew Bennetts
Use socketpairs (rather than pipes) for SSH subprocesses where possible, and formalise some internal APIs a little more.
716
        self._sock = sock
4824.1.1 by Andrew Bennetts
Terminate SSHSubprocesses when no refs to them are left, in case .close is never called.
717
        # Add a weakref to proc that will attempt to do the same as self.close
718
        # to avoid leaving processes lingering indefinitely.
719
        def terminate(ref):
720
            _subproc_weakrefs.remove(ref)
5050.54.1 by Max Bowsher
Do close the socket used for stdin/out to a ssh subprocess.
721
            _close_ssh_proc(proc, sock)
4824.1.1 by Andrew Bennetts
Terminate SSHSubprocesses when no refs to them are left, in case .close is never called.
722
        _subproc_weakrefs.add(weakref.ref(self, terminate))
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
723
724
    def send(self, data):
5284.5.1 by Andrew Bennetts
Use socketpairs (rather than pipes) for SSH subprocesses where possible, and formalise some internal APIs a little more.
725
        if self._sock is not None:
726
            return self._sock.send(data)
727
        else:
728
            return os.write(self.proc.stdin.fileno(), data)
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
729
730
    def recv(self, count):
5284.5.1 by Andrew Bennetts
Use socketpairs (rather than pipes) for SSH subprocesses where possible, and formalise some internal APIs a little more.
731
        if self._sock is not None:
5303.1.1 by Vincent Ladeuil
Fix typo: recv() on sockets, read() on files ;)
732
            return self._sock.recv(count)
5284.5.1 by Andrew Bennetts
Use socketpairs (rather than pipes) for SSH subprocesses where possible, and formalise some internal APIs a little more.
733
        else:
734
            return os.read(self.proc.stdout.fileno(), count)
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
735
736
    def close(self):
5050.54.1 by Max Bowsher
Do close the socket used for stdin/out to a ssh subprocess.
737
        _close_ssh_proc(self.proc, self._sock)
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
738
5284.5.1 by Andrew Bennetts
Use socketpairs (rather than pipes) for SSH subprocesses where possible, and formalise some internal APIs a little more.
739
    def get_sock_or_pipes(self):
740
        if self._sock is not None:
741
            return 'socket', self._sock
742
        else:
743
            return 'pipes', (self.proc.stdout, self.proc.stdin)
744
745
746
class _ParamikoSSHConnection(SSHConnection):
5284.5.3 by Andrew Bennetts
Docstring tweaks.
747
    """An SSH connection via paramiko."""
748
5284.5.1 by Andrew Bennetts
Use socketpairs (rather than pipes) for SSH subprocesses where possible, and formalise some internal APIs a little more.
749
    def __init__(self, channel):
750
        self.channel = channel
751
752
    def get_sock_or_pipes(self):
5284.5.2 by Andrew Bennetts
Use the socket-medium with paramiko connections as well as socketpair-to-subprocess connections, as quick inspection of the paramiko source suggests it handles EINTR ok.
753
        return ('socket', self.channel)
5284.5.1 by Andrew Bennetts
Use socketpairs (rather than pipes) for SSH subprocesses where possible, and formalise some internal APIs a little more.
754
755
    def close(self):
756
        return self.channel.close()
757
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
758