50
# This is a weakref dictionary, so that we can reuse connections
51
# that are still active. Long term, it might be nice to have some
52
# sort of expiration policy, such as disconnect if inactive for
53
# X seconds. But that requires a lot more fanciness.
54
_connected_hosts = weakref.WeakValueDictionary()
56
49
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
58
51
# Paramiko 1.5 tries to open a socket.AF_UNIX in order to connect
61
54
# connect to an agent if we are on win32 and using Paramiko older than 1.6
62
55
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
59
def register_ssh_vendor(name, vendor):
60
"""Register SSH vendor."""
61
_ssh_vendors[name] = vendor
65
def _get_ssh_vendor():
66
"""Find out what version of SSH is on the system."""
68
if _ssh_vendor is not None:
71
if 'BZR_SSH' in os.environ:
72
vendor_name = os.environ['BZR_SSH']
74
_ssh_vendor = _ssh_vendors[vendor_name]
76
raise UnknownSSH(vendor_name)
80
p = subprocess.Popen(['ssh', '-V'],
81
stdin=subprocess.PIPE,
82
stdout=subprocess.PIPE,
83
stderr=subprocess.PIPE,
84
**os_specific_subprocess_params())
85
returncode = p.returncode
86
stdout, stderr = p.communicate()
90
if 'OpenSSH' in stderr:
91
mutter('ssh implementation is OpenSSH')
92
_ssh_vendor = OpenSSHSubprocessVendor()
93
elif 'SSH Secure Shell' in stderr:
94
mutter('ssh implementation is SSH Corp.')
95
_ssh_vendor = SSHCorpSubprocessVendor()
97
if _ssh_vendor is not None:
100
# XXX: 20051123 jamesh
101
# A check for putty's plink or lsh would go here.
103
mutter('falling back to paramiko implementation')
104
_ssh_vendor = ssh.ParamikoVendor()
65
109
def _ignore_sigint():
66
110
# TODO: This should possibly ignore SIGHUP as well, but bzr currently
93
137
class SSHVendor(object):
138
"""Abstract base class for SSH vendor implementations."""
94
140
def connect_sftp(self, username, password, host, port):
141
"""Make an SSH connection, and return an SFTPClient.
143
:param username: an ascii string
144
:param password: an ascii string
145
:param host: a host name as an ascii string
146
:param port: a port number
149
:raises: ConnectionError if it cannot connect.
151
:rtype: paramiko.sftp_client.SFTPClient
95
153
raise NotImplementedError(self.connect_sftp)
97
155
def connect_ssh(self, username, password, host, port, command):
156
"""Make an SSH connection, and return a pipe-like object.
158
(This is currently unused, it's just here to indicate future directions
98
161
raise NotImplementedError(self.connect_ssh)
101
164
class LoopbackVendor(SSHVendor):
165
"""SSH "vendor" that connects over a plain TCP socket, not SSH."""
103
167
def connect_sftp(self, username, password, host, port):
104
168
sock = socket.socket()
109
173
% (host, port, e))
110
174
return SFTPClient(LoopbackSFTP(sock))
176
register_ssh_vendor('loopback', LoopbackVendor())
113
179
class ParamikoVendor(SSHVendor):
180
"""Vendor that uses paramiko."""
115
182
def connect_sftp(self, username, password, host, port):
116
183
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
228
register_ssh_vendor('paramiko', ParamikoVendor())
162
231
class SubprocessVendor(SSHVendor):
232
"""Abstract base class for vendors that use pipes to a subprocess."""
163
234
def connect_sftp(self, username, password, host, port):
165
args = self.get_args(username, host, port, subsystem='sftp')
166
proc = subprocess.Popen(args,
236
argv = self._get_vendor_specific_argv(username, host, port,
238
proc = subprocess.Popen(argv,
167
239
stdin=subprocess.PIPE,
168
240
stdout=subprocess.PIPE,
169
241
**os_specific_subprocess_params())
181
253
raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
182
254
% (host, port, e))
184
def get_args(self, username, host, port, subsystem=None, command=None):
185
raise NotImplementedError(self.get_args)
256
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
258
"""Returns the argument list to run the subprocess with.
260
Exactly one of 'subsystem' and 'command' must be specified.
262
raise NotImplementedError(self._get_vendor_specific_argv)
264
register_ssh_vendor('none', ParamikoVendor())
188
267
class OpenSSHSubprocessVendor(SubprocessVendor):
268
"""SSH vendor that uses the 'ssh' executable from OpenSSH."""
190
def get_args(self, username, host, port, subsystem=None, command=None):
270
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
191
272
assert subsystem is not None or command is not None, (
192
273
'Must specify a command or subsystem')
193
274
if subsystem is not None:
207
288
args.extend([host] + command)
291
register_ssh_vendor('openssh', OpenSSHSubprocessVendor())
211
294
class SSHCorpSubprocessVendor(SubprocessVendor):
295
"""SSH vendor that uses the 'ssh' executable from SSH Corporation."""
213
def get_args(self, username, host, port, subsystem=None, command=None):
297
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
214
299
assert subsystem is not None or command is not None, (
215
300
'Must specify a command or subsystem')
216
301
if subsystem is not None:
227
312
args.extend([host] + command)
315
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
232
318
def _paramiko_auth(username, password, host, paramiko_transport):
233
319
# paramiko requires a username, but it might be none if nothing was supplied
357
443
'close_fds': True,
360
447
class SSHSubprocess(object):
361
448
"""A socket-like object that talks to an ssh subprocess via pipes."""
362
# TODO: this class probably belongs in bzrlib/transport/ssh.py
364
450
def __init__(self, proc):