14
14
# You should have received a copy of the GNU General Public License
15
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
18
"""Foundation SSH support for SFTP and smart server."""
20
from __future__ import absolute_import
29
from binascii import hexlify
27
from bzrlib.config import config_dir, ensure_config_dir_exists
28
from bzrlib.errors import (ConnectionError,
30
SocketConnectionError,
36
from bzrlib.osutils import pathjoin
37
from bzrlib.trace import mutter, warning
126
124
if 'OpenSSH' in version:
127
trace.mutter('ssh implementation is OpenSSH')
125
mutter('ssh implementation is OpenSSH')
128
126
vendor = OpenSSHSubprocessVendor()
129
127
elif 'SSH Secure Shell' in version:
130
trace.mutter('ssh implementation is SSH Corp.')
128
mutter('ssh implementation is SSH Corp.')
131
129
vendor = SSHCorpSubprocessVendor()
132
elif 'lsh' in version:
133
trace.mutter('ssh implementation is GNU lsh.')
134
vendor = LSHSubprocessVendor()
135
# As plink user prompts are not handled currently, don't auto-detect
136
# it by inspection below, but keep this vendor detection for if a path
137
# is given in BZR_SSH. See https://bugs.launchpad.net/bugs/414743
138
elif 'plink' in version and progname == 'plink':
139
# Checking if "plink" was the executed argument as Windows
140
# sometimes reports 'ssh -V' incorrectly with 'plink' in its
141
# version. See https://bugs.launchpad.net/bzr/+bug/107155
142
trace.mutter("ssh implementation is Putty's plink.")
130
elif 'plink' in version and args[0] == 'plink':
131
# Checking if "plink" was the executed argument as Windows sometimes
132
# reports 'ssh -V' incorrectly with 'plink' in it's version.
133
# See https://bugs.launchpad.net/bzr/+bug/107155
134
mutter("ssh implementation is Putty's plink.")
143
135
vendor = PLinkSubprocessVendor()
146
138
def _get_vendor_by_inspection(self):
147
139
"""Return the vendor or None by checking for known SSH implementations."""
148
version = self._get_ssh_version_string(['ssh', '-V'])
149
return self._get_vendor_by_version_string(version, "ssh")
151
def _get_vendor_from_path(self, path):
152
"""Return the vendor or None using the program at the given path"""
153
version = self._get_ssh_version_string([path, '-V'])
154
return self._get_vendor_by_version_string(version,
155
os.path.splitext(os.path.basename(path))[0])
140
for args in [['ssh', '-V'], ['plink', '-V']]:
141
version = self._get_ssh_version_string(args)
142
vendor = self._get_vendor_by_version_string(version, args)
143
if vendor is not None:
157
147
def get_vendor(self, environment=None):
158
148
"""Find out what version of SSH is on the system.
179
169
register_ssh_vendor = _ssh_vendor_manager.register_vendor
182
def _ignore_signals():
172
def _ignore_sigint():
183
173
# TODO: This should possibly ignore SIGHUP as well, but bzr currently
184
174
# doesn't handle it itself.
185
175
# <https://launchpad.net/products/bzr/+bug/41433/+index>
187
177
signal.signal(signal.SIGINT, signal.SIG_IGN)
188
# GZ 2010-02-19: Perhaps make this check if breakin is installed instead
189
if signal.getsignal(signal.SIGQUIT) != signal.SIG_DFL:
190
signal.signal(signal.SIGQUIT, signal.SIG_IGN)
193
class SocketAsChannelAdapter(object):
180
class LoopbackSFTP(object):
194
181
"""Simple wrapper for a socket that pretends to be a paramiko Channel."""
196
183
def __init__(self, sock):
197
184
self.__socket = sock
200
return "bzr SocketAsChannelAdapter"
202
186
def send(self, data):
203
187
return self.__socket.send(data)
205
189
def recv(self, n):
207
return self.__socket.recv(n)
208
except socket.error, e:
209
if e.args[0] in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED,
211
# Connection has closed. Paramiko expects an empty string in
212
# this case, not an exception.
190
return self.__socket.recv(n)
216
192
def recv_ready(self):
217
# TODO: jam 20051215 this function is necessary to support the
218
# pipelined() function. In reality, it probably should use
219
# poll() or select() to actually return if there is data
220
# available, otherwise we probably don't get any benefit
256
229
This just unifies all the locations that try to raise ConnectionError,
257
230
so that they format things properly.
259
raise errors.SocketConnectionError(host=host, port=port, msg=msg,
260
orig_error=orig_error)
232
raise SocketConnectionError(host=host, port=port, msg=msg,
233
orig_error=orig_error)
263
236
class LoopbackVendor(SSHVendor):
264
237
"""SSH "vendor" that connects over a plain TCP socket, not SSH."""
266
239
def connect_sftp(self, username, password, host, port):
267
240
sock = socket.socket()
269
242
sock.connect((host, port))
270
243
except socket.error, e:
271
244
self._raise_connection_error(host, port=port, orig_error=e)
272
return SFTPClient(SocketAsChannelAdapter(sock))
245
return SFTPClient(LoopbackSFTP(sock))
274
247
register_ssh_vendor('loopback', LoopbackVendor())
250
class _ParamikoSSHConnection(object):
251
def __init__(self, channel):
252
self.channel = channel
254
def get_filelike_channels(self):
255
return self.channel.makefile('rb'), self.channel.makefile('wb')
258
return self.channel.close()
277
261
class ParamikoVendor(SSHVendor):
278
262
"""Vendor that uses paramiko."""
280
def _hexify(self, s):
281
return hexlify(s).upper()
283
264
def _connect(self, username, password, host, port):
284
265
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
292
273
except (paramiko.SSHException, socket.error), e:
293
274
self._raise_connection_error(host, port=port, orig_error=e)
295
276
server_key = t.get_remote_server_key()
296
server_key_hex = self._hexify(server_key.get_fingerprint())
277
server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
297
278
keytype = server_key.get_name()
298
279
if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
299
280
our_server_key = SYSTEM_HOSTKEYS[host][keytype]
300
our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
281
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
301
282
elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
302
283
our_server_key = BZR_HOSTKEYS[host][keytype]
303
our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
284
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
305
trace.warning('Adding %s host key for %s: %s'
306
% (keytype, host, server_key_hex))
286
warning('Adding %s host key for %s: %s' % (keytype, host, server_key_hex))
307
287
add = getattr(BZR_HOSTKEYS, 'add', None)
308
288
if add is not None: # paramiko >= 1.X.X
309
289
BZR_HOSTKEYS.add(host, keytype, server_key)
311
291
BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
312
292
our_server_key = server_key
313
our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
293
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
315
295
if server_key != our_server_key:
316
296
filename1 = os.path.expanduser('~/.ssh/known_hosts')
317
filename2 = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
318
raise errors.TransportError(
319
'Host keys for %s do not match! %s != %s' %
297
filename2 = pathjoin(config_dir(), 'ssh_host_keys')
298
raise TransportError('Host keys for %s do not match! %s != %s' % \
320
299
(host, our_server_key_hex, server_key_hex),
321
300
['Try editing %s or %s' % (filename1, filename2)])
323
_paramiko_auth(username, password, host, port, t)
302
_paramiko_auth(username, password, host, t)
326
305
def connect_sftp(self, username, password, host, port):
327
306
t = self._connect(username, password, host, port)
342
321
self._raise_connection_error(host, port=port, orig_error=e,
343
322
msg='Unable to invoke remote bzr')
345
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
346
324
if paramiko is not None:
347
325
vendor = ParamikoVendor()
348
326
register_ssh_vendor('paramiko', vendor)
349
327
register_ssh_vendor('none', vendor)
350
328
register_default_ssh_vendor(vendor)
351
_ssh_connection_errors += (paramiko.SSHException,)
355
332
class SubprocessVendor(SSHVendor):
356
333
"""Abstract base class for vendors that use pipes to a subprocess."""
358
# In general stderr should be inherited from the parent process so prompts
359
# are visible on the terminal. This can be overriden to another file for
360
# tests, but beware of using PIPE which may hang due to not being read.
361
_stderr_target = None
363
335
def _connect(self, argv):
364
# Attempt to make a socketpair to use as stdin/stdout for the SSH
365
# subprocess. We prefer sockets to pipes because they support
366
# non-blocking short reads, allowing us to optimistically read 64k (or
369
my_sock, subproc_sock = socket.socketpair()
370
osutils.set_fd_cloexec(my_sock)
371
except (AttributeError, socket.error):
372
# This platform doesn't support socketpair(), so just use ordinary
374
stdin = stdout = subprocess.PIPE
375
my_sock, subproc_sock = None, None
377
stdin = stdout = subproc_sock
378
proc = subprocess.Popen(argv, stdin=stdin, stdout=stdout,
379
stderr=self._stderr_target,
336
proc = subprocess.Popen(argv,
337
stdin=subprocess.PIPE,
338
stdout=subprocess.PIPE,
380
339
**os_specific_subprocess_params())
381
if subproc_sock is not None:
383
return SSHSubprocessConnection(proc, sock=my_sock)
340
return SSHSubprocess(proc)
385
342
def connect_sftp(self, username, password, host, port):
387
344
argv = self._get_vendor_specific_argv(username, host, port,
388
345
subsystem='sftp')
389
346
sock = self._connect(argv)
390
return SFTPClient(SocketAsChannelAdapter(sock))
391
except _ssh_connection_errors, e:
347
return SFTPClient(sock)
348
except (EOFError, paramiko.SSHException), e:
349
self._raise_connection_error(host, port=port, orig_error=e)
350
except (OSError, IOError), e:
351
# If the machine is fast enough, ssh can actually exit
352
# before we try and send it the sftp request, which
353
# raises a Broken Pipe
354
if e.errno not in (errno.EPIPE,):
392
356
self._raise_connection_error(host, port=port, orig_error=e)
394
358
def connect_ssh(self, username, password, host, port, command):
396
360
argv = self._get_vendor_specific_argv(username, host, port,
398
362
return self._connect(argv)
399
except _ssh_connection_errors, e:
363
except (EOFError), e:
364
self._raise_connection_error(host, port=port, orig_error=e)
365
except (OSError, IOError), e:
366
# If the machine is fast enough, ssh can actually exit
367
# before we try and send it the sftp request, which
368
# raises a Broken Pipe
369
if e.errno not in (errno.EPIPE,):
400
371
self._raise_connection_error(host, port=port, orig_error=e)
402
373
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
404
375
"""Returns the argument list to run the subprocess with.
406
377
Exactly one of 'subsystem' and 'command' must be specified.
408
379
raise NotImplementedError(self._get_vendor_specific_argv)
411
382
class OpenSSHSubprocessVendor(SubprocessVendor):
412
383
"""SSH vendor that uses the 'ssh' executable from OpenSSH."""
414
executable_path = 'ssh'
416
385
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
418
args = [self.executable_path,
387
assert subsystem is not None or command is not None, (
388
'Must specify a command or subsystem')
389
if subsystem is not None:
390
assert command is None, (
391
'subsystem and command are mutually exclusive')
419
393
'-oForwardX11=no', '-oForwardAgent=no',
420
'-oClearAllForwardings=yes',
394
'-oClearAllForwardings=yes', '-oProtocol=2',
421
395
'-oNoHostAuthenticationForLocalhost=yes']
422
396
if port is not None:
423
397
args.extend(['-p', str(port)])
450
427
args.extend([host] + command)
453
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
456
class LSHSubprocessVendor(SubprocessVendor):
457
"""SSH vendor that uses the 'lsh' executable from GNU"""
459
executable_path = 'lsh'
461
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
463
args = [self.executable_path]
465
args.extend(['-p', str(port)])
466
if username is not None:
467
args.extend(['-l', username])
468
if subsystem is not None:
469
args.extend(['--subsystem', subsystem, host])
471
args.extend([host] + command)
474
register_ssh_vendor('lsh', LSHSubprocessVendor())
430
register_ssh_vendor('ssh', SSHCorpSubprocessVendor())
477
433
class PLinkSubprocessVendor(SubprocessVendor):
478
434
"""SSH vendor that uses the 'plink' executable from Putty."""
480
executable_path = 'plink'
482
436
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
484
args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
438
assert subsystem is not None or command is not None, (
439
'Must specify a command or subsystem')
440
if subsystem is not None:
441
assert command is None, (
442
'subsystem and command are mutually exclusive')
443
args = ['plink', '-x', '-a', '-ssh', '-2']
485
444
if port is not None:
486
445
args.extend(['-P', str(port)])
487
446
if username is not None:
495
454
register_ssh_vendor('plink', PLinkSubprocessVendor())
498
def _paramiko_auth(username, password, host, port, paramiko_transport):
499
auth = config.AuthenticationConfig()
500
# paramiko requires a username, but it might be none if nothing was
501
# supplied. If so, use the local username.
503
username = auth.get_user('ssh', host, port=port,
504
default=getpass.getuser())
457
def _paramiko_auth(username, password, host, paramiko_transport):
458
# paramiko requires a username, but it might be none if nothing was supplied
459
# use the local username, just in case.
460
# We don't override username, because if we aren't using paramiko,
461
# the username might be specified in ~/.ssh/config and we don't want to
462
# force it to something else
463
# Also, it would mess up the self.relpath() functionality
464
username = username or getpass.getuser()
505
466
if _use_ssh_agent:
506
467
agent = paramiko.Agent()
507
468
for key in agent.get_keys():
508
trace.mutter('Trying SSH agent key %s'
509
% self._hexify(key.get_fingerprint()))
469
mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
511
471
paramiko_transport.auth_publickey(username, key)
513
473
except paramiko.SSHException, e:
516
476
# okay, try finding id_rsa or id_dss? (posix only)
517
477
if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
519
479
if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
522
# If we have gotten this far, we are about to try for passwords, do an
523
# auth_none check to see if it is even supported.
524
supported_auth_types = []
526
# Note that with paramiko <1.7.5 this logs an INFO message:
527
# Authentication type (none) not permitted.
528
# So we explicitly disable the logging level for this action
529
old_level = paramiko_transport.logger.level
530
paramiko_transport.logger.setLevel(logging.WARNING)
532
paramiko_transport.auth_none(username)
534
paramiko_transport.logger.setLevel(old_level)
535
except paramiko.BadAuthenticationType, e:
536
# Supported methods are in the exception
537
supported_auth_types = e.allowed_types
538
except paramiko.SSHException, e:
539
# Don't know what happened, but just ignore it
541
# We treat 'keyboard-interactive' and 'password' auth methods identically,
542
# because Paramiko's auth_password method will automatically try
543
# 'keyboard-interactive' auth (using the password as the response) if
544
# 'password' auth is not available. Apparently some Debian and Gentoo
545
# OpenSSH servers require this.
546
# XXX: It's possible for a server to require keyboard-interactive auth that
547
# requires something other than a single password, but we currently don't
549
if ('password' not in supported_auth_types and
550
'keyboard-interactive' not in supported_auth_types):
551
raise errors.ConnectionError('Unable to authenticate to SSH host as'
552
'\n %s@%s\nsupported auth types: %s'
553
% (username, host, supported_auth_types))
557
484
paramiko_transport.auth_password(username, password)
562
489
# give up and ask for a password
563
password = auth.get_password('ssh', host, username, port=port)
564
# get_password can still return None, which means we should not prompt
565
if password is not None:
567
paramiko_transport.auth_password(username, password)
568
except paramiko.SSHException, e:
569
raise errors.ConnectionError(
570
'Unable to authenticate to SSH host as'
571
'\n %s@%s\n' % (username, host), e)
573
raise errors.ConnectionError('Unable to authenticate to SSH host as'
574
' %s@%s' % (username, host))
490
password = bzrlib.ui.ui_factory.get_password(
491
prompt='SSH %(user)s@%(host)s password',
492
user=username, host=host)
494
paramiko_transport.auth_password(username, password)
495
except paramiko.SSHException, e:
496
raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
577
500
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
581
504
paramiko_transport.auth_publickey(username, key)
583
506
except paramiko.PasswordRequiredException:
584
password = ui.ui_factory.get_password(
585
prompt=u'SSH %(filename)s password',
586
filename=filename.decode(osutils._fs_enc))
507
password = bzrlib.ui.ui_factory.get_password(
508
prompt='SSH %(filename)s password',
588
511
key = pkey_class.from_private_key_file(filename, password)
589
512
paramiko_transport.auth_publickey(username, key)
591
514
except paramiko.SSHException:
592
trace.mutter('SSH authentication via %s key failed.'
593
% (os.path.basename(filename),))
515
mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
594
516
except paramiko.SSHException:
595
trace.mutter('SSH authentication via %s key failed.'
596
% (os.path.basename(filename),))
517
mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
652
572
# this causes it to be seen only by bzr and not by ssh. Python will
653
573
# generate a KeyboardInterrupt in bzr, and we will then have a chance
654
574
# to release locks or do other cleanup over ssh before the connection
656
576
# <https://launchpad.net/products/bzr/+bug/5987>
658
578
# Running it in a separate process group is not good because then it
659
579
# can't get non-echoed input of a password or passphrase.
660
580
# <https://launchpad.net/products/bzr/+bug/40508>
661
return {'preexec_fn': _ignore_signals,
581
return {'preexec_fn': _ignore_sigint,
662
582
'close_fds': True,
666
_subproc_weakrefs = set()
668
def _close_ssh_proc(proc, sock):
669
"""Carefully close stdin/stdout and reap the SSH process.
671
If the pipes are already closed and/or the process has already been
672
wait()ed on, that's ok, and no error is raised. The goal is to do our best
673
to clean up (whether or not a clean up was already tried).
676
for closeable in (proc.stdin, proc.stdout, sock):
677
# We expect that either proc (a subprocess.Popen) will have stdin and
678
# stdout streams to close, or that we will have been passed a socket to
679
# close, with the option not in use being None.
680
if closeable is not None:
681
funcs.append(closeable.close)
682
funcs.append(proc.wait)
687
# It's ok for the pipe to already be closed, or the process to
688
# already be finished.
692
class SSHConnection(object):
693
"""Abstract base class for SSH connections."""
695
def get_sock_or_pipes(self):
696
"""Returns a (kind, io_object) pair.
698
If kind == 'socket', then io_object is a socket.
700
If kind == 'pipes', then io_object is a pair of file-like objects
701
(read_from, write_to).
703
raise NotImplementedError(self.get_sock_or_pipes)
706
raise NotImplementedError(self.close)
709
class SSHSubprocessConnection(SSHConnection):
710
"""A connection to an ssh subprocess via pipes or a socket.
712
This class is also socket-like enough to be used with
713
SocketAsChannelAdapter (it has 'send' and 'recv' methods).
716
def __init__(self, proc, sock=None):
719
:param proc: a subprocess.Popen
720
:param sock: if proc.stdin/out is a socket from a socketpair, then sock
721
should bzrlib's half of that socketpair. If not passed, proc's
722
stdin/out is assumed to be ordinary pipes.
586
class SSHSubprocess(object):
587
"""A socket-like object that talks to an ssh subprocess via pipes."""
589
def __init__(self, proc):
726
# Add a weakref to proc that will attempt to do the same as self.close
727
# to avoid leaving processes lingering indefinitely.
729
_subproc_weakrefs.remove(ref)
730
_close_ssh_proc(proc, sock)
731
_subproc_weakrefs.add(weakref.ref(self, terminate))
733
592
def send(self, data):
734
if self._sock is not None:
735
return self._sock.send(data)
737
return os.write(self.proc.stdin.fileno(), data)
593
return os.write(self.proc.stdin.fileno(), data)
595
def recv_ready(self):
596
# TODO: jam 20051215 this function is necessary to support the
597
# pipelined() function. In reality, it probably should use
598
# poll() or select() to actually return if there is data
599
# available, otherwise we probably don't get any benefit
739
602
def recv(self, count):
740
if self._sock is not None:
741
return self._sock.recv(count)
743
return os.read(self.proc.stdout.fileno(), count)
746
_close_ssh_proc(self.proc, self._sock)
748
def get_sock_or_pipes(self):
749
if self._sock is not None:
750
return 'socket', self._sock
752
return 'pipes', (self.proc.stdout, self.proc.stdin)
755
class _ParamikoSSHConnection(SSHConnection):
756
"""An SSH connection via paramiko."""
758
def __init__(self, channel):
759
self.channel = channel
761
def get_sock_or_pipes(self):
762
return ('socket', self.channel)
765
return self.channel.close()
603
return os.read(self.proc.stdout.fileno(), count)
606
self.proc.stdin.close()
607
self.proc.stdout.close()
610
def get_filelike_channels(self):
611
return (self.proc.stdout, self.proc.stdin)