58
57
# connect to an agent if we are on win32 and using Paramiko older than 1.6
59
58
_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']
61
class SSHVendorManager(object):
62
"""Manager for manage SSH vendors."""
64
# Note, although at first sign the class interface seems similar to
65
# bzrlib.registry.Registry it is not possible/convenient to directly use
66
# the Registry because the class just has "get()" interface instead of the
67
# Registry's "get(key)".
70
self._ssh_vendors = {}
71
self._cached_ssh_vendor = None
72
self._default_ssh_vendor = None
74
def register_default_vendor(self, vendor):
75
"""Register default SSH vendor."""
76
self._default_ssh_vendor = vendor
78
def register_vendor(self, name, vendor):
79
"""Register new SSH vendor by name."""
80
self._ssh_vendors[name] = vendor
82
def clear_cache(self):
83
"""Clear previously cached lookup result."""
84
self._cached_ssh_vendor = None
86
def _get_vendor_by_environment(self, environment=None):
87
"""Return the vendor or None based on BZR_SSH environment variable.
89
:raises UnknownSSH: if the BZR_SSH environment variable contains
92
if environment is None:
93
environment = os.environ
94
if 'BZR_SSH' in environment:
95
vendor_name = environment['BZR_SSH']
97
vendor = self._ssh_vendors[vendor_name]
99
vendor = self._get_vendor_from_path(vendor_name)
101
raise errors.UnknownSSH(vendor_name)
102
vendor.executable_path = vendor_name
106
def _get_ssh_version_string(self, args):
107
"""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():
109
p = subprocess.Popen(args,
110
stdout=subprocess.PIPE,
111
stderr=subprocess.PIPE,
112
**os_specific_subprocess_params())
113
stdout, stderr = p.communicate()
116
return stdout + stderr
118
def _get_vendor_by_version_string(self, version, progname):
119
"""Return the vendor or None based on output from the subprocess.
121
:param version: The output of 'ssh -V' like command.
122
:param args: Command line that was run.
125
if 'OpenSSH' in version:
126
trace.mutter('ssh implementation is OpenSSH')
127
vendor = OpenSSHSubprocessVendor()
128
elif 'SSH Secure Shell' in version:
129
trace.mutter('ssh implementation is SSH Corp.')
130
vendor = SSHCorpSubprocessVendor()
131
elif 'lsh' in version:
132
trace.mutter('ssh implementation is GNU lsh.')
133
vendor = LSHSubprocessVendor()
134
# As plink user prompts are not handled currently, don't auto-detect
135
# it by inspection below, but keep this vendor detection for if a path
136
# is given in BZR_SSH. See https://bugs.launchpad.net/bugs/414743
137
elif 'plink' in version and progname == 'plink':
138
# Checking if "plink" was the executed argument as Windows
139
# sometimes reports 'ssh -V' incorrectly with 'plink' in its
140
# version. See https://bugs.launchpad.net/bzr/+bug/107155
141
trace.mutter("ssh implementation is Putty's plink.")
142
vendor = PLinkSubprocessVendor()
145
def _get_vendor_by_inspection(self):
146
"""Return the vendor or None by checking for known SSH implementations."""
147
version = self._get_ssh_version_string(['ssh', '-V'])
148
return self._get_vendor_by_version_string(version, "ssh")
150
def _get_vendor_from_path(self, path):
151
"""Return the vendor or None using the program at the given path"""
152
version = self._get_ssh_version_string([path, '-V'])
153
return self._get_vendor_by_version_string(version,
154
os.path.splitext(os.path.basename(path))[0])
156
def get_vendor(self, environment=None):
157
"""Find out what version of SSH is on the system.
159
:raises SSHVendorNotFound: if no any SSH vendor is found
160
:raises UnknownSSH: if the BZR_SSH environment variable contains
163
if self._cached_ssh_vendor is None:
164
vendor = self._get_vendor_by_environment(environment)
166
vendor = self._get_vendor_by_inspection()
168
trace.mutter('falling back to default implementation')
169
vendor = self._default_ssh_vendor
171
raise errors.SSHVendorNotFound()
172
self._cached_ssh_vendor = vendor
173
return self._cached_ssh_vendor
175
_ssh_vendor_manager = SSHVendorManager()
176
_get_ssh_vendor = _ssh_vendor_manager.get_vendor
177
register_default_ssh_vendor = _ssh_vendor_manager.register_default_vendor
178
register_ssh_vendor = _ssh_vendor_manager.register_vendor
181
def _ignore_signals():
113
182
# TODO: This should possibly ignore SIGHUP as well, but bzr currently
114
183
# doesn't handle it itself.
115
184
# <https://launchpad.net/products/bzr/+bug/41433/+index>
117
186
signal.signal(signal.SIGINT, signal.SIG_IGN)
121
class LoopbackSFTP(object):
187
# GZ 2010-02-19: Perhaps make this check if breakin is installed instead
188
if signal.getsignal(signal.SIGQUIT) != signal.SIG_DFL:
189
signal.signal(signal.SIGQUIT, signal.SIG_IGN)
192
class SocketAsChannelAdapter(object):
122
193
"""Simple wrapper for a socket that pretends to be a paramiko Channel."""
124
195
def __init__(self, sock):
125
196
self.__socket = sock
199
return "bzr SocketAsChannelAdapter"
127
201
def send(self, data):
128
202
return self.__socket.send(data)
130
204
def recv(self, n):
131
return self.__socket.recv(n)
206
return self.__socket.recv(n)
207
except socket.error, e:
208
if e.args[0] in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED,
210
# Connection has closed. Paramiko expects an empty string in
211
# this case, not an exception.
133
215
def recv_ready(self):
216
# TODO: jam 20051215 this function is necessary to support the
217
# pipelined() function. In reality, it probably should use
218
# poll() or select() to actually return if there is data
219
# available, otherwise we probably don't get any benefit
170
255
This just unifies all the locations that try to raise ConnectionError,
171
256
so that they format things properly.
173
raise SocketConnectionError(host=host, port=port, msg=msg,
174
orig_error=orig_error)
258
raise errors.SocketConnectionError(host=host, port=port, msg=msg,
259
orig_error=orig_error)
177
262
class LoopbackVendor(SSHVendor):
178
263
"""SSH "vendor" that connects over a plain TCP socket, not SSH."""
180
265
def connect_sftp(self, username, password, host, port):
181
266
sock = socket.socket()
183
268
sock.connect((host, port))
184
269
except socket.error, e:
185
270
self._raise_connection_error(host, port=port, orig_error=e)
186
return SFTPClient(LoopbackSFTP(sock))
271
return SFTPClient(SocketAsChannelAdapter(sock))
188
273
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
276
class ParamikoVendor(SSHVendor):
203
277
"""Vendor that uses paramiko."""
205
279
def _connect(self, username, password, host, port):
206
280
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
214
288
except (paramiko.SSHException, socket.error), e:
215
289
self._raise_connection_error(host, port=port, orig_error=e)
217
291
server_key = t.get_remote_server_key()
218
292
server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
219
293
keytype = server_key.get_name()
220
294
if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
221
295
our_server_key = SYSTEM_HOSTKEYS[host][keytype]
222
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
296
our_server_key_hex = paramiko.util.hexify(
297
our_server_key.get_fingerprint())
223
298
elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
224
299
our_server_key = BZR_HOSTKEYS[host][keytype]
225
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
300
our_server_key_hex = paramiko.util.hexify(
301
our_server_key.get_fingerprint())
227
warning('Adding %s host key for %s: %s' % (keytype, host, server_key_hex))
303
trace.warning('Adding %s host key for %s: %s'
304
% (keytype, host, server_key_hex))
228
305
add = getattr(BZR_HOSTKEYS, 'add', None)
229
306
if add is not None: # paramiko >= 1.X.X
230
307
BZR_HOSTKEYS.add(host, keytype, server_key)
232
309
BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
233
310
our_server_key = server_key
234
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
311
our_server_key_hex = paramiko.util.hexify(
312
our_server_key.get_fingerprint())
236
314
if server_key != our_server_key:
237
315
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' % \
316
filename2 = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
317
raise errors.TransportError(
318
'Host keys for %s do not match! %s != %s' %
240
319
(host, our_server_key_hex, server_key_hex),
241
320
['Try editing %s or %s' % (filename1, filename2)])
243
_paramiko_auth(username, password, host, t)
322
_paramiko_auth(username, password, host, port, t)
246
325
def connect_sftp(self, username, password, host, port):
247
326
t = self._connect(username, password, host, port)
262
341
self._raise_connection_error(host, port=port, orig_error=e,
263
342
msg='Unable to invoke remote bzr')
344
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
265
345
if paramiko is not None:
266
register_ssh_vendor('paramiko', ParamikoVendor())
346
vendor = ParamikoVendor()
347
register_ssh_vendor('paramiko', vendor)
348
register_ssh_vendor('none', vendor)
349
register_default_ssh_vendor(vendor)
350
_ssh_connection_errors += (paramiko.SSHException,)
269
354
class SubprocessVendor(SSHVendor):
270
355
"""Abstract base class for vendors that use pipes to a subprocess."""
357
# In general stderr should be inherited from the parent process so prompts
358
# are visible on the terminal. This can be overriden to another file for
359
# tests, but beware of using PIPE which may hang due to not being read.
360
_stderr_target = None
272
362
def _connect(self, argv):
273
proc = subprocess.Popen(argv,
274
stdin=subprocess.PIPE,
275
stdout=subprocess.PIPE,
363
# Attempt to make a socketpair to use as stdin/stdout for the SSH
364
# subprocess. We prefer sockets to pipes because they support
365
# non-blocking short reads, allowing us to optimistically read 64k (or
368
my_sock, subproc_sock = socket.socketpair()
369
osutils.set_fd_cloexec(my_sock)
370
except (AttributeError, socket.error):
371
# This platform doesn't support socketpair(), so just use ordinary
373
stdin = stdout = subprocess.PIPE
374
my_sock, subproc_sock = None, None
376
stdin = stdout = subproc_sock
377
proc = subprocess.Popen(argv, stdin=stdin, stdout=stdout,
378
stderr=self._stderr_target,
276
379
**os_specific_subprocess_params())
277
return SSHSubprocess(proc)
380
if subproc_sock is not None:
382
return SSHSubprocessConnection(proc, sock=my_sock)
279
384
def connect_sftp(self, username, password, host, port):
281
386
argv = self._get_vendor_specific_argv(username, host, port,
282
387
subsystem='sftp')
283
388
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,):
389
return SFTPClient(SocketAsChannelAdapter(sock))
390
except _ssh_connection_errors, e:
293
391
self._raise_connection_error(host, port=port, orig_error=e)
295
393
def connect_ssh(self, username, password, host, port, command):
297
395
argv = self._get_vendor_specific_argv(username, host, port,
299
397
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,):
398
except _ssh_connection_errors, e:
308
399
self._raise_connection_error(host, port=port, orig_error=e)
310
401
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
312
403
"""Returns the argument list to run the subprocess with.
314
405
Exactly one of 'subsystem' and 'command' must be specified.
316
407
raise NotImplementedError(self._get_vendor_specific_argv)
318
register_ssh_vendor('none', ParamikoVendor())
321
410
class OpenSSHSubprocessVendor(SubprocessVendor):
322
411
"""SSH vendor that uses the 'ssh' executable from OpenSSH."""
413
executable_path = 'ssh'
324
415
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')
417
args = [self.executable_path,
332
418
'-oForwardX11=no', '-oForwardAgent=no',
333
'-oClearAllForwardings=yes', '-oProtocol=2',
419
'-oClearAllForwardings=yes',
334
420
'-oNoHostAuthenticationForLocalhost=yes']
335
421
if port is not None:
336
422
args.extend(['-p', str(port)])
366
449
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()
452
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
455
class LSHSubprocessVendor(SubprocessVendor):
456
"""SSH vendor that uses the 'lsh' executable from GNU"""
458
executable_path = 'lsh'
460
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
462
args = [self.executable_path]
464
args.extend(['-p', str(port)])
465
if username is not None:
466
args.extend(['-l', username])
467
if subsystem is not None:
468
args.extend(['--subsystem', subsystem, host])
470
args.extend([host] + command)
473
register_ssh_vendor('lsh', LSHSubprocessVendor())
476
class PLinkSubprocessVendor(SubprocessVendor):
477
"""SSH vendor that uses the 'plink' executable from Putty."""
479
executable_path = 'plink'
481
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
483
args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
485
args.extend(['-P', str(port)])
486
if username is not None:
487
args.extend(['-l', username])
488
if subsystem is not None:
489
args.extend(['-s', host, subsystem])
491
args.extend([host] + command)
494
register_ssh_vendor('plink', PLinkSubprocessVendor())
497
def _paramiko_auth(username, password, host, port, paramiko_transport):
498
auth = config.AuthenticationConfig()
499
# paramiko requires a username, but it might be none if nothing was
500
# supplied. If so, use the local username.
502
username = auth.get_user('ssh', host, port=port,
503
default=getpass.getuser())
381
504
if _use_ssh_agent:
382
505
agent = paramiko.Agent()
383
506
for key in agent.get_keys():
384
mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
507
trace.mutter('Trying SSH agent key %s'
508
% paramiko.util.hexify(key.get_fingerprint()))
386
510
paramiko_transport.auth_publickey(username, key)
388
512
except paramiko.SSHException, e:
391
515
# okay, try finding id_rsa or id_dss? (posix only)
392
516
if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
394
518
if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
521
# If we have gotten this far, we are about to try for passwords, do an
522
# auth_none check to see if it is even supported.
523
supported_auth_types = []
525
# Note that with paramiko <1.7.5 this logs an INFO message:
526
# Authentication type (none) not permitted.
527
# So we explicitly disable the logging level for this action
528
old_level = paramiko_transport.logger.level
529
paramiko_transport.logger.setLevel(logging.WARNING)
531
paramiko_transport.auth_none(username)
533
paramiko_transport.logger.setLevel(old_level)
534
except paramiko.BadAuthenticationType, e:
535
# Supported methods are in the exception
536
supported_auth_types = e.allowed_types
537
except paramiko.SSHException, e:
538
# Don't know what happened, but just ignore it
540
# We treat 'keyboard-interactive' and 'password' auth methods identically,
541
# because Paramiko's auth_password method will automatically try
542
# 'keyboard-interactive' auth (using the password as the response) if
543
# 'password' auth is not available. Apparently some Debian and Gentoo
544
# OpenSSH servers require this.
545
# XXX: It's possible for a server to require keyboard-interactive auth that
546
# requires something other than a single password, but we currently don't
548
if ('password' not in supported_auth_types and
549
'keyboard-interactive' not in supported_auth_types):
550
raise errors.ConnectionError('Unable to authenticate to SSH host as'
551
'\n %s@%s\nsupported auth types: %s'
552
% (username, host, supported_auth_types))
399
556
paramiko_transport.auth_password(username, password)
404
561
# 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' %
562
password = auth.get_password('ssh', host, username, port=port)
563
# get_password can still return None, which means we should not prompt
564
if password is not None:
566
paramiko_transport.auth_password(username, password)
567
except paramiko.SSHException, e:
568
raise errors.ConnectionError(
569
'Unable to authenticate to SSH host as'
570
'\n %s@%s\n' % (username, host), e)
572
raise errors.ConnectionError('Unable to authenticate to SSH host as'
573
' %s@%s' % (username, host))
415
576
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
487
651
# this causes it to be seen only by bzr and not by ssh. Python will
488
652
# generate a KeyboardInterrupt in bzr, and we will then have a chance
489
653
# to release locks or do other cleanup over ssh before the connection
491
655
# <https://launchpad.net/products/bzr/+bug/5987>
493
657
# Running it in a separate process group is not good because then it
494
658
# can't get non-echoed input of a password or passphrase.
495
659
# <https://launchpad.net/products/bzr/+bug/40508>
496
return {'preexec_fn': _ignore_sigint,
660
return {'preexec_fn': _ignore_signals,
497
661
'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):
665
_subproc_weakrefs = set()
667
def _close_ssh_proc(proc, sock):
668
"""Carefully close stdin/stdout and reap the SSH process.
670
If the pipes are already closed and/or the process has already been
671
wait()ed on, that's ok, and no error is raised. The goal is to do our best
672
to clean up (whether or not a clean up was already tried).
675
for closeable in (proc.stdin, proc.stdout, sock):
676
# We expect that either proc (a subprocess.Popen) will have stdin and
677
# stdout streams to close, or that we will have been passed a socket to
678
# close, with the option not in use being None.
679
if closeable is not None:
680
funcs.append(closeable.close)
681
funcs.append(proc.wait)
686
# It's ok for the pipe to already be closed, or the process to
687
# already be finished.
691
class SSHConnection(object):
692
"""Abstract base class for SSH connections."""
694
def get_sock_or_pipes(self):
695
"""Returns a (kind, io_object) pair.
697
If kind == 'socket', then io_object is a socket.
699
If kind == 'pipes', then io_object is a pair of file-like objects
700
(read_from, write_to).
702
raise NotImplementedError(self.get_sock_or_pipes)
705
raise NotImplementedError(self.close)
708
class SSHSubprocessConnection(SSHConnection):
709
"""A connection to an ssh subprocess via pipes or a socket.
711
This class is also socket-like enough to be used with
712
SocketAsChannelAdapter (it has 'send' and 'recv' methods).
715
def __init__(self, proc, sock=None):
718
:param proc: a subprocess.Popen
719
:param sock: if proc.stdin/out is a socket from a socketpair, then sock
720
should bzrlib's half of that socketpair. If not passed, proc's
721
stdin/out is assumed to be ordinary pipes.
725
# Add a weakref to proc that will attempt to do the same as self.close
726
# to avoid leaving processes lingering indefinitely.
728
_subproc_weakrefs.remove(ref)
729
_close_ssh_proc(proc, sock)
730
_subproc_weakrefs.add(weakref.ref(self, terminate))
507
732
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
733
if self._sock is not None:
734
return self._sock.send(data)
736
return os.write(self.proc.stdin.fileno(), data)
517
738
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)
739
if self._sock is not None:
740
return self._sock.recv(count)
742
return os.read(self.proc.stdout.fileno(), count)
745
_close_ssh_proc(self.proc, self._sock)
747
def get_sock_or_pipes(self):
748
if self._sock is not None:
749
return 'socket', self._sock
751
return 'pipes', (self.proc.stdout, self.proc.stdin)
754
class _ParamikoSSHConnection(SSHConnection):
755
"""An SSH connection via paramiko."""
757
def __init__(self, channel):
758
self.channel = channel
760
def get_sock_or_pipes(self):
761
return ('socket', self.channel)
764
return self.channel.close()