~bzr-pqm/bzr/bzr.dev

1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
1
# Copyright (C) 2005 Robey Pointer <robey@lag.net>
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
2
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
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
4595.17.2 by Martin
Merge bzr.dev 4789 to resolve conflict from the disabling of plink auto-detection, and relocate NEWS
134
            # sometimes reports 'ssh -V' incorrectly with 'plink' in it's
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
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
176
def _ignore_sigint():
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)
182
183
3353.1.3 by Andrew Bennetts
Always adapt sockets to look like paramiko Channels before passing them to paramiko's SFTPClient.
184
class SocketAsChannelAdapter(object):
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
185
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
186
187
    def __init__(self, sock):
188
        self.__socket = sock
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
189
3353.1.2 by Andrew Bennetts
Add get_name to LoopbackSFTP. Makes the current tests pass with current paramiko.
190
    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.
191
        return "bzr SocketAsChannelAdapter"
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
192
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
193
    def send(self, data):
194
        return self.__socket.send(data)
195
196
    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.
197
        try:
198
            return self.__socket.recv(n)
199
        except socket.error, e:
200
            if e.args[0] in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED,
201
                             errno.EBADF):
202
                # Connection has closed.  Paramiko expects an empty string in
203
                # this case, not an exception.
204
                return ''
205
            raise
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
206
207
    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.
208
        # TODO: jam 20051215 this function is necessary to support the
209
        # pipelined() function. In reality, it probably should use
210
        # poll() or select() to actually return if there is data
211
        # 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
212
        return True
213
214
    def close(self):
215
        self.__socket.close()
216
217
218
class SSHVendor(object):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
219
    """Abstract base class for SSH vendor implementations."""
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
220
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
221
    def connect_sftp(self, username, password, host, port):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
222
        """Make an SSH connection, and return an SFTPClient.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
223
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
224
        :param username: an ascii string
225
        :param password: an ascii string
226
        :param host: a host name as an ascii string
227
        :param port: a port number
228
        :type port: int
229
230
        :raises: ConnectionError if it cannot connect.
231
232
        :rtype: paramiko.sftp_client.SFTPClient
233
        """
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
234
        raise NotImplementedError(self.connect_sftp)
235
236
    def connect_ssh(self, username, password, host, port, command):
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
237
        """Make an SSH connection.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
238
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
239
        :returns: something with a `close` method, and a `get_filelike_channels`
240
            method that returns a pair of (read, write) filelike objects.
1951.1.12 by Andrew Bennetts
Cosmetic tweaks.
241
        """
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
242
        raise NotImplementedError(self.connect_ssh)
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
243
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
244
    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
245
                                msg='Unable to connect to SSH host'):
246
        """Raise a SocketConnectionError with properly formatted host.
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
247
248
        This just unifies all the locations that try to raise ConnectionError,
249
        so that they format things properly.
250
        """
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
251
        raise errors.SocketConnectionError(host=host, port=port, msg=msg,
252
                                           orig_error=orig_error)
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
253
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
254
255
class LoopbackVendor(SSHVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
256
    """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.
257
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
258
    def connect_sftp(self, username, password, host, port):
259
        sock = socket.socket()
260
        try:
261
            sock.connect((host, port))
262
        except socket.error, e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
263
            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.
264
        return SFTPClient(SocketAsChannelAdapter(sock))
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
265
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
266
register_ssh_vendor('loopback', LoopbackVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
267
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
268
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
269
class _ParamikoSSHConnection(object):
270
    def __init__(self, channel):
271
        self.channel = channel
272
273
    def get_filelike_channels(self):
274
        return self.channel.makefile('rb'), self.channel.makefile('wb')
275
276
    def close(self):
277
        return self.channel.close()
278
279
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
280
class ParamikoVendor(SSHVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
281
    """Vendor that uses paramiko."""
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
282
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
283
    def _connect(self, username, password, host, port):
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
284
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
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
        load_host_keys()
287
288
        try:
289
            t = paramiko.Transport((host, port or 22))
290
            t.set_log_channel('bzr.paramiko')
291
            t.start_client()
292
        except (paramiko.SSHException, socket.error), e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
293
            self._raise_connection_error(host, port=port, orig_error=e)
2900.2.8 by Vincent Ladeuil
Make sftp and bzr+ssh aware of authentication config.
294
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
295
        server_key = t.get_remote_server_key()
296
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
297
        keytype = server_key.get_name()
1711.9.10 by John Arbash Meinel
Update transport/ssh.py to remove has_key usage
298
        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
299
            our_server_key = SYSTEM_HOSTKEYS[host][keytype]
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
300
            our_server_key_hex = paramiko.util.hexify(
301
                our_server_key.get_fingerprint())
1711.9.10 by John Arbash Meinel
Update transport/ssh.py to remove has_key usage
302
        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
303
            our_server_key = BZR_HOSTKEYS[host][keytype]
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
304
            our_server_key_hex = paramiko.util.hexify(
305
                our_server_key.get_fingerprint())
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
306
        else:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
307
            trace.warning('Adding %s host key for %s: %s'
308
                          % (keytype, host, server_key_hex))
2127.3.1 by Alexander Belchenko
Use BZR_HOSTKEYS.add instead of deprecated dict-like paramiko interface
309
            add = getattr(BZR_HOSTKEYS, 'add', None)
310
            if add is not None: # paramiko >= 1.X.X
311
                BZR_HOSTKEYS.add(host, keytype, server_key)
312
            else:
1551.9.2 by Aaron Bentley
Bugfix for paramiko connections
313
                BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
314
            our_server_key = server_key
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
315
            our_server_key_hex = paramiko.util.hexify(
316
                our_server_key.get_fingerprint())
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
317
            save_host_keys()
318
        if server_key != our_server_key:
319
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
320
            filename2 = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
321
            raise errors.TransportError(
322
                '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
323
                (host, our_server_key_hex, server_key_hex),
324
                ['Try editing %s or %s' % (filename1, filename2)])
325
2900.2.8 by Vincent Ladeuil
Make sftp and bzr+ssh aware of authentication config.
326
        _paramiko_auth(username, password, host, port, t)
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
327
        return t
2900.2.8 by Vincent Ladeuil
Make sftp and bzr+ssh aware of authentication config.
328
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
329
    def connect_sftp(self, username, password, host, port):
330
        t = self._connect(username, password, host, port)
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
331
        try:
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
332
            return t.open_sftp_client()
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
333
        except paramiko.SSHException, e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
334
            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
335
                                         msg='Unable to start sftp client')
2018.1.9 by Andrew Bennetts
Implement ParamikoVendor.connect_ssh
336
337
    def connect_ssh(self, username, password, host, port, command):
338
        t = self._connect(username, password, host, port)
339
        try:
340
            channel = t.open_session()
341
            cmdline = ' '.join(command)
342
            channel.exec_command(cmdline)
343
            return _ParamikoSSHConnection(channel)
344
        except paramiko.SSHException, e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
345
            self._raise_connection_error(host, port=port, orig_error=e,
2052.4.4 by John Arbash Meinel
Create a SocketConnectionError to make creating nice errors easier
346
                                         msg='Unable to invoke remote bzr')
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
347
2104.5.1 by John Arbash Meinel
Remove the strict dependency on paramiko for ssh access
348
if paramiko is not None:
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
349
    vendor = ParamikoVendor()
350
    register_ssh_vendor('paramiko', vendor)
351
    register_ssh_vendor('none', vendor)
2221.5.5 by Dmitry Vasiliev
Added 'register_default_vendor' method to the SSHVendorManager
352
    register_default_ssh_vendor(vendor)
3066.2.1 by John Arbash Meinel
We don't require paramiko for bzr+ssh.
353
    _sftp_connection_errors = (EOFError, paramiko.SSHException)
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
354
    del vendor
3066.2.1 by John Arbash Meinel
We don't require paramiko for bzr+ssh.
355
else:
356
    _sftp_connection_errors = (EOFError,)
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
357
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
358
359
class SubprocessVendor(SSHVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
360
    """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.
361
2018.1.6 by Andrew Bennetts
Remove a little bit of duplication in ssh.py
362
    def _connect(self, argv):
363
        proc = subprocess.Popen(argv,
364
                                stdin=subprocess.PIPE,
365
                                stdout=subprocess.PIPE,
366
                                **os_specific_subprocess_params())
367
        return SSHSubprocess(proc)
368
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
369
    def connect_sftp(self, username, password, host, port):
370
        try:
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
371
            argv = self._get_vendor_specific_argv(username, host, port,
372
                                                  subsystem='sftp')
2018.1.6 by Andrew Bennetts
Remove a little bit of duplication in ssh.py
373
            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.
374
            return SFTPClient(SocketAsChannelAdapter(sock))
3066.2.1 by John Arbash Meinel
We don't require paramiko for bzr+ssh.
375
        except _sftp_connection_errors, e:
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
376
            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.
377
        except (OSError, IOError), e:
378
            # If the machine is fast enough, ssh can actually exit
379
            # before we try and send it the sftp request, which
380
            # raises a Broken Pipe
381
            if e.errno not in (errno.EPIPE,):
382
                raise
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)
390
        except (EOFError), 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
        except (OSError, IOError), e:
393
            # If the machine is fast enough, ssh can actually exit
394
            # before we try and send it the sftp request, which
395
            # raises a Broken Pipe
396
            if e.errno not in (errno.EPIPE,):
397
                raise
2052.4.2 by John Arbash Meinel
Refactor all 'raise ConnectionError' into a helper function
398
            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).
399
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
400
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
401
                                  command=None):
402
        """Returns the argument list to run the subprocess with.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
403
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
404
        Exactly one of 'subsystem' and 'command' must be specified.
405
        """
406
        raise NotImplementedError(self._get_vendor_specific_argv)
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
407
408
409
class OpenSSHSubprocessVendor(SubprocessVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
410
    """SSH vendor that uses the 'ssh' executable from OpenSSH."""
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
411
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
412
    executable_path = 'ssh'
413
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
414
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
415
                                  command=None):
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
416
        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.
417
                '-oForwardX11=no', '-oForwardAgent=no',
418
                '-oClearAllForwardings=yes', '-oProtocol=2',
419
                '-oNoHostAuthenticationForLocalhost=yes']
420
        if port is not None:
421
            args.extend(['-p', str(port)])
422
        if username is not None:
423
            args.extend(['-l', username])
424
        if subsystem is not None:
425
            args.extend(['-s', host, subsystem])
426
        else:
427
            args.extend([host] + command)
428
        return args
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
429
1951.1.11 by Andrew Bennetts
Change register_ssh_vendor to take an instance rather than a class.
430
register_ssh_vendor('openssh', OpenSSHSubprocessVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
431
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
432
433
class SSHCorpSubprocessVendor(SubprocessVendor):
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
434
    """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.
435
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
436
    executable_path = 'ssh'
437
1951.1.9 by Andrew Bennetts
Add docstrings and tweak method names in ssh.py
438
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
439
                                  command=None):
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
440
        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.
441
        if port is not None:
442
            args.extend(['-p', str(port)])
443
        if username is not None:
444
            args.extend(['-l', username])
445
        if subsystem is not None:
446
            args.extend(['-s', subsystem, host])
447
        else:
448
            args.extend([host] + command)
449
        return args
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
450
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
451
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
1951.1.10 by Andrew Bennetts
Move register_ssh_vendor, _ssh_vendor and _get_ssh_vendor into ssh.py
452
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
453
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
454
class PLinkSubprocessVendor(SubprocessVendor):
455
    """SSH vendor that uses the 'plink' executable from Putty."""
456
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
457
    executable_path = 'plink'
458
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
459
    def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
460
                                  command=None):
4595.17.1 by Martin
Add ability to give a path to a particular ssh client in BZR_SSH envvar
461
        args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
462
        if port is not None:
463
            args.extend(['-P', str(port)])
464
        if username is not None:
465
            args.extend(['-l', username])
466
        if subsystem is not None:
2221.5.3 by Dmitry Vasiliev
Fixed plink's arguments order. Added tests for such a case.
467
            args.extend(['-s', host, subsystem])
2221.5.1 by Dmitry Vasiliev
Added support for Putty's SSH implementation
468
        else:
469
            args.extend([host] + command)
470
        return args
471
472
register_ssh_vendor('plink', PLinkSubprocessVendor())
473
474
2900.2.8 by Vincent Ladeuil
Make sftp and bzr+ssh aware of authentication config.
475
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
476
    auth = config.AuthenticationConfig()
3777.1.5 by Aaron Bentley
Remove AuthenticationConfig handling from Paramiko SSHVendor
477
    # paramiko requires a username, but it might be none if nothing was
478
    # supplied.  If so, use the local username.
2900.2.15 by Vincent Ladeuil
AuthenticationConfig can be queried for logins too (first step).
479
    if username is None:
4304.2.1 by Vincent Ladeuil
Fix bug #367726 by reverting some default user handling introduced
480
        username = auth.get_user('ssh', host, port=port,
481
                                 default=getpass.getuser())
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
482
    if _use_ssh_agent:
483
        agent = paramiko.Agent()
484
        for key in agent.get_keys():
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
485
            trace.mutter('Trying SSH agent key %s'
486
                         % paramiko.util.hexify(key.get_fingerprint()))
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
487
            try:
488
                paramiko_transport.auth_publickey(username, key)
489
                return
490
            except paramiko.SSHException, e:
491
                pass
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
492
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
493
    # okay, try finding id_rsa or id_dss?  (posix only)
494
    if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
495
        return
496
    if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
497
        return
498
4555.1.1 by John Arbash Meinel
Fix bug #375867, check if password is a supported auth type
499
    # If we have gotten this far, we are about to try for passwords, do an
500
    # auth_none check to see if it is even supported.
501
    supported_auth_types = []
502
    try:
503
        # Note that with paramiko <1.7.5 this logs an INFO message:
504
        #    Authentication type (none) not permitted.
505
        # So we explicitly disable the logging level for this action
506
        old_level = paramiko_transport.logger.level
507
        paramiko_transport.logger.setLevel(logging.WARNING)
508
        try:
509
            paramiko_transport.auth_none(username)
510
        finally:
511
            paramiko_transport.logger.setLevel(old_level)
512
    except paramiko.BadAuthenticationType, e:
513
        # Supported methods are in the exception
514
        supported_auth_types = e.allowed_types
515
    except paramiko.SSHException, e:
516
        # Don't know what happened, but just ignore it
517
        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'.
518
    # We treat 'keyboard-interactive' and 'password' auth methods identically,
519
    # because Paramiko's auth_password method will automatically try
520
    # 'keyboard-interactive' auth (using the password as the response) if
521
    # 'password' auth is not available.  Apparently some Debian and Gentoo
522
    # OpenSSH servers require this.
523
    # XXX: It's possible for a server to require keyboard-interactive auth that
524
    # requires something other than a single password, but we currently don't
525
    # support that.
526
    if ('password' not in supported_auth_types and
527
        '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
528
        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.
529
            '\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
530
            % (username, host, supported_auth_types))
531
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
532
    if password:
533
        try:
534
            paramiko_transport.auth_password(username, password)
535
            return
536
        except paramiko.SSHException, e:
537
            pass
538
539
    # give up and ask for a password
2900.2.12 by Vincent Ladeuil
Since all schemes query AuthenticationConfig then prompt user, make that
540
    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
541
    # get_password can still return None, which means we should not prompt
542
    if password is not None:
543
        try:
544
            paramiko_transport.auth_password(username, password)
545
        except paramiko.SSHException, e:
4555.1.3 by John Arbash Meinel
Reformat the errors so they aren't so long.
546
            raise errors.ConnectionError(
547
                'Unable to authenticate to SSH host as'
548
                '\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
549
    else:
4555.1.3 by John Arbash Meinel
Reformat the errors so they aren't so long.
550
        raise errors.ConnectionError('Unable to authenticate to SSH host as'
551
                                     '  %s@%s' % (username, host))
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
552
553
554
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
555
    filename = os.path.expanduser('~/.ssh/' + filename)
556
    try:
557
        key = pkey_class.from_private_key_file(filename)
558
        paramiko_transport.auth_publickey(username, key)
559
        return True
560
    except paramiko.PasswordRequiredException:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
561
        password = ui.ui_factory.get_password(
562
            prompt='SSH %(filename)s password', filename=filename)
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
563
        try:
564
            key = pkey_class.from_private_key_file(filename, password)
565
            paramiko_transport.auth_publickey(username, key)
566
            return True
567
        except paramiko.SSHException:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
568
            trace.mutter('SSH authentication via %s key failed.'
569
                         % (os.path.basename(filename),))
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
570
    except paramiko.SSHException:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
571
        trace.mutter('SSH authentication via %s key failed.'
572
                     % (os.path.basename(filename),))
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
573
    except IOError:
574
        pass
575
    return False
576
577
578
def load_host_keys():
579
    """
580
    Load system host keys (probably doesn't work on windows) and any
581
    "discovered" keys from previous sessions.
582
    """
583
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
584
    try:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
585
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(
586
            os.path.expanduser('~/.ssh/known_hosts'))
2358.3.1 by Martin Pool
Update some too-general exception blocks
587
    except IOError, e:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
588
        trace.mutter('failed to load system host keys: ' + str(e))
589
    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
590
    try:
591
        BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
2358.3.1 by Martin Pool
Update some too-general exception blocks
592
    except IOError, e:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
593
        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
594
        save_host_keys()
595
596
597
def save_host_keys():
598
    """
599
    Save "discovered" host keys in $(config)/ssh_host_keys/.
600
    """
601
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
602
    bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
603
    config.ensure_config_dir_exists()
1951.1.4 by Andrew Bennetts
Start moving SSH connection code into bzrlib/transport/ssh.py
604
605
    try:
606
        f = open(bzr_hostkey_path, 'w')
607
        f.write('# SSH host keys collected by bzr\n')
608
        for hostname, keys in BZR_HOSTKEYS.iteritems():
609
            for keytype, key in keys.iteritems():
610
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
611
        f.close()
612
    except IOError, e:
2900.2.18 by Vincent Ladeuil
Previous commits didn't check the test suite enough.
613
        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
614
615
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
616
def os_specific_subprocess_params():
617
    """Get O/S specific subprocess parameters."""
618
    if sys.platform == 'win32':
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
619
        # 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.
620
        # win32
621
        return {}
622
    else:
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
623
        # 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.
624
        # them to be open.
625
        #
626
        # We also set the child process to ignore SIGINT.  Normally the signal
627
        # would be sent to every process in the foreground process group, but
628
        # this causes it to be seen only by bzr and not by ssh.  Python will
629
        # generate a KeyboardInterrupt in bzr, and we will then have a chance
630
        # 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
631
        # 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.
632
        # <https://launchpad.net/products/bzr/+bug/5987>
633
        #
634
        # Running it in a separate process group is not good because then it
635
        # can't get non-echoed input of a password or passphrase.
636
        # <https://launchpad.net/products/bzr/+bug/40508>
637
        return {'preexec_fn': _ignore_sigint,
638
                'close_fds': True,
639
                }
640
4824.1.1 by Andrew Bennetts
Terminate SSHSubprocesses when no refs to them are left, in case .close is never called.
641
import weakref
642
_subproc_weakrefs = set()
643
644
def _close_ssh_proc(proc):
645
    for func in [proc.stdin.close, proc.stdout.close, proc.wait]:
646
        try:
647
            func()
648
        except OSError:
649
            pass
650
1951.1.12 by Andrew Bennetts
Cosmetic tweaks.
651
1951.1.7 by Andrew Bennetts
Move more generic SSH code from sftp.py into ssh.py, and start unifying the connection establishing logic.
652
class SSHSubprocess(object):
653
    """A socket-like object that talks to an ssh subprocess via pipes."""
654
655
    def __init__(self, proc):
656
        self.proc = proc
4824.1.1 by Andrew Bennetts
Terminate SSHSubprocesses when no refs to them are left, in case .close is never called.
657
        # Add a weakref to proc that will attempt to do the same as self.close
658
        # to avoid leaving processes lingering indefinitely.
659
        def terminate(ref):
660
            _subproc_weakrefs.remove(ref)
661
            _close_ssh_proc(proc)
662
        _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.
663
664
    def send(self, data):
665
        return os.write(self.proc.stdin.fileno(), data)
666
667
    def recv(self, count):
668
        return os.read(self.proc.stdout.fileno(), count)
669
670
    def close(self):
4824.1.1 by Andrew Bennetts
Terminate SSHSubprocesses when no refs to them are left, in case .close is never called.
671
        _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.
672
2018.1.1 by Andrew Bennetts
Make bzr+ssh:// actually work (at least with absolute paths).
673
    def get_filelike_channels(self):
674
        return (self.proc.stdout, self.proc.stdin)
2221.5.21 by Dmitry Vasiliev
Reverted trailing whitespace removal
675