58
55
# connect to an agent if we are on win32 and using Paramiko older than 1.6
59
56
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
63
def register_ssh_vendor(name, vendor):
64
"""Register SSH vendor."""
65
_ssh_vendors[name] = vendor
69
def _get_ssh_vendor():
70
"""Find out what version of SSH is on the system."""
72
if _ssh_vendor is not None:
75
if 'BZR_SSH' in os.environ:
76
vendor_name = os.environ['BZR_SSH']
59
class SSHVendorManager(object):
60
"""Manager for manage SSH vendors."""
62
# Note, although at first sign the class interface seems similar to
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)".
68
self._ssh_vendors = {}
69
self._cached_ssh_vendor = None
70
self._default_ssh_vendor = None
72
def register_default_vendor(self, vendor):
73
"""Register default SSH vendor."""
74
self._default_ssh_vendor = vendor
76
def register_vendor(self, name, vendor):
77
"""Register new SSH vendor by name."""
78
self._ssh_vendors[name] = vendor
80
def clear_cache(self):
81
"""Clear previously cached lookup result."""
82
self._cached_ssh_vendor = None
84
def _get_vendor_by_environment(self, environment=None):
85
"""Return the vendor or None based on BZR_SSH environment variable.
87
:raises UnknownSSH: if the BZR_SSH environment variable contains
90
if environment is None:
91
environment = os.environ
92
if 'BZR_SSH' in environment:
93
vendor_name = environment['BZR_SSH']
95
vendor = self._ssh_vendors[vendor_name]
97
vendor = self._get_vendor_from_path(vendor_name)
99
raise errors.UnknownSSH(vendor_name)
100
vendor.executable_path = vendor_name
104
def _get_ssh_version_string(self, args):
105
"""Return SSH version string from the subprocess."""
78
_ssh_vendor = _ssh_vendors[vendor_name]
80
raise UnknownSSH(vendor_name)
84
p = subprocess.Popen(['ssh', '-V'],
85
stdin=subprocess.PIPE,
86
stdout=subprocess.PIPE,
87
stderr=subprocess.PIPE,
88
**os_specific_subprocess_params())
89
returncode = p.returncode
90
stdout, stderr = p.communicate()
94
if 'OpenSSH' in stderr:
95
mutter('ssh implementation is OpenSSH')
96
_ssh_vendor = OpenSSHSubprocessVendor()
97
elif 'SSH Secure Shell' in stderr:
98
mutter('ssh implementation is SSH Corp.')
99
_ssh_vendor = SSHCorpSubprocessVendor()
101
if _ssh_vendor is not None:
104
# XXX: 20051123 jamesh
105
# A check for putty's plink or lsh would go here.
107
mutter('falling back to paramiko implementation')
108
_ssh_vendor = ParamikoVendor()
112
def _ignore_sigint():
107
p = subprocess.Popen(args,
108
stdout=subprocess.PIPE,
109
stderr=subprocess.PIPE,
110
**os_specific_subprocess_params())
111
stdout, stderr = p.communicate()
114
return stdout + stderr
116
def _get_vendor_by_version_string(self, version, progname):
117
"""Return the vendor or None based on output from the subprocess.
119
:param version: The output of 'ssh -V' like command.
120
:param args: Command line that was run.
123
if 'OpenSSH' in version:
124
trace.mutter('ssh implementation is OpenSSH')
125
vendor = OpenSSHSubprocessVendor()
126
elif 'SSH Secure Shell' in version:
127
trace.mutter('ssh implementation is SSH Corp.')
128
vendor = SSHCorpSubprocessVendor()
129
elif 'lsh' in version:
130
trace.mutter('ssh implementation is GNU lsh.')
131
vendor = LSHSubprocessVendor()
132
# As plink user prompts are not handled currently, don't auto-detect
133
# it by inspection below, but keep this vendor detection for if a path
134
# is given in BZR_SSH. See https://bugs.launchpad.net/bugs/414743
135
elif 'plink' in version and progname == 'plink':
136
# Checking if "plink" was the executed argument as Windows
137
# sometimes reports 'ssh -V' incorrectly with 'plink' in its
138
# version. See https://bugs.launchpad.net/bzr/+bug/107155
139
trace.mutter("ssh implementation is Putty's plink.")
140
vendor = PLinkSubprocessVendor()
143
def _get_vendor_by_inspection(self):
144
"""Return the vendor or None by checking for known SSH implementations."""
145
version = self._get_ssh_version_string(['ssh', '-V'])
146
return self._get_vendor_by_version_string(version, "ssh")
148
def _get_vendor_from_path(self, path):
149
"""Return the vendor or None using the program at the given path"""
150
version = self._get_ssh_version_string([path, '-V'])
151
return self._get_vendor_by_version_string(version,
152
os.path.splitext(os.path.basename(path))[0])
154
def get_vendor(self, environment=None):
155
"""Find out what version of SSH is on the system.
157
:raises SSHVendorNotFound: if no any SSH vendor is found
158
:raises UnknownSSH: if the BZR_SSH environment variable contains
161
if self._cached_ssh_vendor is None:
162
vendor = self._get_vendor_by_environment(environment)
164
vendor = self._get_vendor_by_inspection()
166
trace.mutter('falling back to default implementation')
167
vendor = self._default_ssh_vendor
169
raise errors.SSHVendorNotFound()
170
self._cached_ssh_vendor = vendor
171
return self._cached_ssh_vendor
173
_ssh_vendor_manager = SSHVendorManager()
174
_get_ssh_vendor = _ssh_vendor_manager.get_vendor
175
register_default_ssh_vendor = _ssh_vendor_manager.register_default_vendor
176
register_ssh_vendor = _ssh_vendor_manager.register_vendor
179
def _ignore_signals():
113
180
# TODO: This should possibly ignore SIGHUP as well, but bzr currently
114
181
# doesn't handle it itself.
115
182
# <https://launchpad.net/products/bzr/+bug/41433/+index>
117
184
signal.signal(signal.SIGINT, signal.SIG_IGN)
121
class LoopbackSFTP(object):
185
# GZ 2010-02-19: Perhaps make this check if breakin is installed instead
186
if signal.getsignal(signal.SIGQUIT) != signal.SIG_DFL:
187
signal.signal(signal.SIGQUIT, signal.SIG_IGN)
190
class SocketAsChannelAdapter(object):
122
191
"""Simple wrapper for a socket that pretends to be a paramiko Channel."""
124
193
def __init__(self, sock):
125
194
self.__socket = sock
197
return "bzr SocketAsChannelAdapter"
127
199
def send(self, data):
128
200
return self.__socket.send(data)
130
202
def recv(self, n):
131
return self.__socket.recv(n)
204
return self.__socket.recv(n)
205
except socket.error, e:
206
if e.args[0] in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED,
208
# Connection has closed. Paramiko expects an empty string in
209
# this case, not an exception.
133
213
def recv_ready(self):
214
# TODO: jam 20051215 this function is necessary to support the
215
# pipelined() function. In reality, it probably should use
216
# poll() or select() to actually return if there is data
217
# available, otherwise we probably don't get any benefit
170
253
This just unifies all the locations that try to raise ConnectionError,
171
254
so that they format things properly.
173
raise SocketConnectionError(host=host, port=port, msg=msg,
174
orig_error=orig_error)
256
raise errors.SocketConnectionError(host=host, port=port, msg=msg,
257
orig_error=orig_error)
177
260
class LoopbackVendor(SSHVendor):
178
261
"""SSH "vendor" that connects over a plain TCP socket, not SSH."""
180
263
def connect_sftp(self, username, password, host, port):
181
264
sock = socket.socket()
183
266
sock.connect((host, port))
184
267
except socket.error, e:
185
268
self._raise_connection_error(host, port=port, orig_error=e)
186
return SFTPClient(LoopbackSFTP(sock))
269
return SFTPClient(SocketAsChannelAdapter(sock))
188
271
register_ssh_vendor('loopback', LoopbackVendor())
191
class _ParamikoSSHConnection(object):
192
def __init__(self, channel):
193
self.channel = channel
195
def get_filelike_channels(self):
196
return self.channel.makefile('rb'), self.channel.makefile('wb')
199
return self.channel.close()
202
274
class ParamikoVendor(SSHVendor):
203
275
"""Vendor that uses paramiko."""
205
277
def _connect(self, username, password, host, port):
206
278
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
214
286
except (paramiko.SSHException, socket.error), e:
215
287
self._raise_connection_error(host, port=port, orig_error=e)
217
289
server_key = t.get_remote_server_key()
218
290
server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
219
291
keytype = server_key.get_name()
220
292
if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
221
293
our_server_key = SYSTEM_HOSTKEYS[host][keytype]
222
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
294
our_server_key_hex = paramiko.util.hexify(
295
our_server_key.get_fingerprint())
223
296
elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
224
297
our_server_key = BZR_HOSTKEYS[host][keytype]
225
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
298
our_server_key_hex = paramiko.util.hexify(
299
our_server_key.get_fingerprint())
227
warning('Adding %s host key for %s: %s' % (keytype, host, server_key_hex))
301
trace.warning('Adding %s host key for %s: %s'
302
% (keytype, host, server_key_hex))
228
303
add = getattr(BZR_HOSTKEYS, 'add', None)
229
304
if add is not None: # paramiko >= 1.X.X
230
305
BZR_HOSTKEYS.add(host, keytype, server_key)
232
307
BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
233
308
our_server_key = server_key
234
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
309
our_server_key_hex = paramiko.util.hexify(
310
our_server_key.get_fingerprint())
236
312
if server_key != our_server_key:
237
313
filename1 = os.path.expanduser('~/.ssh/known_hosts')
238
filename2 = pathjoin(config_dir(), 'ssh_host_keys')
239
raise TransportError('Host keys for %s do not match! %s != %s' % \
314
filename2 = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
315
raise errors.TransportError(
316
'Host keys for %s do not match! %s != %s' %
240
317
(host, our_server_key_hex, server_key_hex),
241
318
['Try editing %s or %s' % (filename1, filename2)])
243
_paramiko_auth(username, password, host, t)
320
_paramiko_auth(username, password, host, port, t)
246
323
def connect_sftp(self, username, password, host, port):
247
324
t = self._connect(username, password, host, port)
262
339
self._raise_connection_error(host, port=port, orig_error=e,
263
340
msg='Unable to invoke remote bzr')
342
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
265
343
if paramiko is not None:
266
register_ssh_vendor('paramiko', ParamikoVendor())
344
vendor = ParamikoVendor()
345
register_ssh_vendor('paramiko', vendor)
346
register_ssh_vendor('none', vendor)
347
register_default_ssh_vendor(vendor)
348
_ssh_connection_errors += (paramiko.SSHException,)
269
352
class SubprocessVendor(SSHVendor):
270
353
"""Abstract base class for vendors that use pipes to a subprocess."""
272
355
def _connect(self, argv):
273
proc = subprocess.Popen(argv,
274
stdin=subprocess.PIPE,
275
stdout=subprocess.PIPE,
356
# Attempt to make a socketpair to use as stdin/stdout for the SSH
357
# subprocess. We prefer sockets to pipes because they support
358
# non-blocking short reads, allowing us to optimistically read 64k (or
361
my_sock, subproc_sock = socket.socketpair()
362
except (AttributeError, socket.error):
363
# This platform doesn't support socketpair(), so just use ordinary
365
stdin = stdout = subprocess.PIPE
368
stdin = stdout = subproc_sock
370
proc = subprocess.Popen(argv, stdin=stdin, stdout=stdout,
276
371
**os_specific_subprocess_params())
277
return SSHSubprocess(proc)
372
return SSHSubprocessConnection(proc, sock=sock)
279
374
def connect_sftp(self, username, password, host, port):
281
376
argv = self._get_vendor_specific_argv(username, host, port,
282
377
subsystem='sftp')
283
378
sock = self._connect(argv)
284
return SFTPClient(sock)
285
except (EOFError, paramiko.SSHException), e:
286
self._raise_connection_error(host, port=port, orig_error=e)
287
except (OSError, IOError), e:
288
# If the machine is fast enough, ssh can actually exit
289
# before we try and send it the sftp request, which
290
# raises a Broken Pipe
291
if e.errno not in (errno.EPIPE,):
379
return SFTPClient(SocketAsChannelAdapter(sock))
380
except _ssh_connection_errors, e:
293
381
self._raise_connection_error(host, port=port, orig_error=e)
295
383
def connect_ssh(self, username, password, host, port, command):
297
385
argv = self._get_vendor_specific_argv(username, host, port,
299
387
return self._connect(argv)
300
except (EOFError), e:
301
self._raise_connection_error(host, port=port, orig_error=e)
302
except (OSError, IOError), e:
303
# If the machine is fast enough, ssh can actually exit
304
# before we try and send it the sftp request, which
305
# raises a Broken Pipe
306
if e.errno not in (errno.EPIPE,):
388
except _ssh_connection_errors, e:
308
389
self._raise_connection_error(host, port=port, orig_error=e)
310
391
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
312
393
"""Returns the argument list to run the subprocess with.
314
395
Exactly one of 'subsystem' and 'command' must be specified.
316
397
raise NotImplementedError(self._get_vendor_specific_argv)
318
register_ssh_vendor('none', ParamikoVendor())
321
400
class OpenSSHSubprocessVendor(SubprocessVendor):
322
401
"""SSH vendor that uses the 'ssh' executable from OpenSSH."""
403
executable_path = 'ssh'
324
405
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
326
assert subsystem is not None or command is not None, (
327
'Must specify a command or subsystem')
328
if subsystem is not None:
329
assert command is None, (
330
'subsystem and command are mutually exclusive')
407
args = [self.executable_path,
332
408
'-oForwardX11=no', '-oForwardAgent=no',
333
'-oClearAllForwardings=yes', '-oProtocol=2',
409
'-oClearAllForwardings=yes',
334
410
'-oNoHostAuthenticationForLocalhost=yes']
335
411
if port is not None:
336
412
args.extend(['-p', str(port)])
366
439
args.extend([host] + command)
369
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
372
def _paramiko_auth(username, password, host, paramiko_transport):
373
# paramiko requires a username, but it might be none if nothing was supplied
374
# use the local username, just in case.
375
# We don't override username, because if we aren't using paramiko,
376
# the username might be specified in ~/.ssh/config and we don't want to
377
# force it to something else
378
# Also, it would mess up the self.relpath() functionality
379
username = username or getpass.getuser()
442
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
445
class LSHSubprocessVendor(SubprocessVendor):
446
"""SSH vendor that uses the 'lsh' executable from GNU"""
448
executable_path = 'lsh'
450
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
452
args = [self.executable_path]
454
args.extend(['-p', str(port)])
455
if username is not None:
456
args.extend(['-l', username])
457
if subsystem is not None:
458
args.extend(['--subsystem', subsystem, host])
460
args.extend([host] + command)
463
register_ssh_vendor('lsh', LSHSubprocessVendor())
466
class PLinkSubprocessVendor(SubprocessVendor):
467
"""SSH vendor that uses the 'plink' executable from Putty."""
469
executable_path = 'plink'
471
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
473
args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
475
args.extend(['-P', str(port)])
476
if username is not None:
477
args.extend(['-l', username])
478
if subsystem is not None:
479
args.extend(['-s', host, subsystem])
481
args.extend([host] + command)
484
register_ssh_vendor('plink', PLinkSubprocessVendor())
487
def _paramiko_auth(username, password, host, port, paramiko_transport):
488
auth = config.AuthenticationConfig()
489
# paramiko requires a username, but it might be none if nothing was
490
# supplied. If so, use the local username.
492
username = auth.get_user('ssh', host, port=port,
493
default=getpass.getuser())
381
494
if _use_ssh_agent:
382
495
agent = paramiko.Agent()
383
496
for key in agent.get_keys():
384
mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
497
trace.mutter('Trying SSH agent key %s'
498
% paramiko.util.hexify(key.get_fingerprint()))
386
500
paramiko_transport.auth_publickey(username, key)
388
502
except paramiko.SSHException, e:
391
505
# okay, try finding id_rsa or id_dss? (posix only)
392
506
if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
394
508
if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
511
# If we have gotten this far, we are about to try for passwords, do an
512
# auth_none check to see if it is even supported.
513
supported_auth_types = []
515
# Note that with paramiko <1.7.5 this logs an INFO message:
516
# Authentication type (none) not permitted.
517
# So we explicitly disable the logging level for this action
518
old_level = paramiko_transport.logger.level
519
paramiko_transport.logger.setLevel(logging.WARNING)
521
paramiko_transport.auth_none(username)
523
paramiko_transport.logger.setLevel(old_level)
524
except paramiko.BadAuthenticationType, e:
525
# Supported methods are in the exception
526
supported_auth_types = e.allowed_types
527
except paramiko.SSHException, e:
528
# Don't know what happened, but just ignore it
530
# We treat 'keyboard-interactive' and 'password' auth methods identically,
531
# because Paramiko's auth_password method will automatically try
532
# 'keyboard-interactive' auth (using the password as the response) if
533
# 'password' auth is not available. Apparently some Debian and Gentoo
534
# OpenSSH servers require this.
535
# XXX: It's possible for a server to require keyboard-interactive auth that
536
# requires something other than a single password, but we currently don't
538
if ('password' not in supported_auth_types and
539
'keyboard-interactive' not in supported_auth_types):
540
raise errors.ConnectionError('Unable to authenticate to SSH host as'
541
'\n %s@%s\nsupported auth types: %s'
542
% (username, host, supported_auth_types))
399
546
paramiko_transport.auth_password(username, password)
404
551
# give up and ask for a password
405
password = bzrlib.ui.ui_factory.get_password(
406
prompt='SSH %(user)s@%(host)s password',
407
user=username, host=host)
409
paramiko_transport.auth_password(username, password)
410
except paramiko.SSHException, e:
411
raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
552
password = auth.get_password('ssh', host, username, port=port)
553
# get_password can still return None, which means we should not prompt
554
if password is not None:
556
paramiko_transport.auth_password(username, password)
557
except paramiko.SSHException, e:
558
raise errors.ConnectionError(
559
'Unable to authenticate to SSH host as'
560
'\n %s@%s\n' % (username, host), e)
562
raise errors.ConnectionError('Unable to authenticate to SSH host as'
563
' %s@%s' % (username, host))
415
566
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
487
640
# this causes it to be seen only by bzr and not by ssh. Python will
488
641
# generate a KeyboardInterrupt in bzr, and we will then have a chance
489
642
# to release locks or do other cleanup over ssh before the connection
491
644
# <https://launchpad.net/products/bzr/+bug/5987>
493
646
# Running it in a separate process group is not good because then it
494
647
# can't get non-echoed input of a password or passphrase.
495
648
# <https://launchpad.net/products/bzr/+bug/40508>
496
return {'preexec_fn': _ignore_sigint,
649
return {'preexec_fn': _ignore_signals,
497
650
'close_fds': True,
501
class SSHSubprocess(object):
502
"""A socket-like object that talks to an ssh subprocess via pipes."""
504
def __init__(self, proc):
654
_subproc_weakrefs = set()
656
def _close_ssh_proc(proc):
657
"""Carefully close stdin/stdout and reap the SSH process.
659
If the pipes are already closed and/or the process has already been
660
wait()ed on, that's ok, and no error is raised. The goal is to do our best
661
to clean up (whether or not a clean up was already tried).
663
dotted_names = ['stdin.close', 'stdout.close', 'wait']
664
for dotted_name in dotted_names:
665
attrs = dotted_name.split('.')
669
obj = getattr(obj, attr)
670
except AttributeError:
671
# It's ok for proc.stdin or proc.stdout to be None.
676
# It's ok for the pipe to already be closed, or the process to
677
# already be finished.
681
class SSHConnection(object):
682
"""Abstract base class for SSH connections."""
684
def get_sock_or_pipes(self):
685
"""Returns a (kind, io_object) pair.
687
If kind == 'socket', then io_object is a socket.
689
If kind == 'pipes', then io_object is a pair of file-like objects
690
(read_from, write_to).
692
raise NotImplementedError(self.get_sock_or_pipes)
695
raise NotImplementedError(self.close)
698
class SSHSubprocessConnection(SSHConnection):
699
"""A connection to an ssh subprocess via pipes or a socket.
701
This class is also socket-like enough to be used with
702
SocketAsChannelAdapter (it has 'send' and 'recv' methods).
705
def __init__(self, proc, sock=None):
708
:param proc: a subprocess.Popen
709
:param sock: if proc.stdin/out is a socket from a socketpair, then sock
710
should bzrlib's half of that socketpair. If not passed, proc's
711
stdin/out is assumed to be ordinary pipes.
715
# Add a weakref to proc that will attempt to do the same as self.close
716
# to avoid leaving processes lingering indefinitely.
718
_subproc_weakrefs.remove(ref)
719
_close_ssh_proc(proc)
720
_subproc_weakrefs.add(weakref.ref(self, terminate))
507
722
def send(self, data):
508
return os.write(self.proc.stdin.fileno(), data)
510
def recv_ready(self):
511
# TODO: jam 20051215 this function is necessary to support the
512
# pipelined() function. In reality, it probably should use
513
# poll() or select() to actually return if there is data
514
# available, otherwise we probably don't get any benefit
723
if self._sock is not None:
724
return self._sock.send(data)
726
return os.write(self.proc.stdin.fileno(), data)
517
728
def recv(self, count):
518
return os.read(self.proc.stdout.fileno(), count)
521
self.proc.stdin.close()
522
self.proc.stdout.close()
525
def get_filelike_channels(self):
526
return (self.proc.stdout, self.proc.stdin)
729
if self._sock is not None:
730
return self._sock.recv(count)
732
return os.read(self.proc.stdout.fileno(), count)
735
_close_ssh_proc(self.proc)
737
def get_sock_or_pipes(self):
738
if self._sock is not None:
739
return 'socket', self._sock
741
return 'pipes', (self.proc.stdout, self.proc.stdin)
744
class _ParamikoSSHConnection(SSHConnection):
745
"""An SSH connection via paramiko."""
747
def __init__(self, channel):
748
self.channel = channel
750
def get_sock_or_pipes(self):
751
return ('socket', self.channel)
754
return self.channel.close()