~bzr-pqm/bzr/bzr.dev

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