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
osutils.set_fd_cloexec(my_sock)
363
except (AttributeError, socket.error):
364
# This platform doesn't support socketpair(), so just use ordinary
366
stdin = stdout = subprocess.PIPE
367
my_sock, subproc_sock = None, None
369
stdin = stdout = subproc_sock
370
proc = subprocess.Popen(argv, stdin=stdin, stdout=stdout,
276
371
**os_specific_subprocess_params())
277
return SSHSubprocess(proc)
372
if subproc_sock is not None:
374
return SSHSubprocessConnection(proc, sock=my_sock)
279
376
def connect_sftp(self, username, password, host, port):
281
378
argv = self._get_vendor_specific_argv(username, host, port,
282
379
subsystem='sftp')
283
380
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,):
381
return SFTPClient(SocketAsChannelAdapter(sock))
382
except _ssh_connection_errors, e:
293
383
self._raise_connection_error(host, port=port, orig_error=e)
295
385
def connect_ssh(self, username, password, host, port, command):
297
387
argv = self._get_vendor_specific_argv(username, host, port,
299
389
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,):
390
except _ssh_connection_errors, e:
308
391
self._raise_connection_error(host, port=port, orig_error=e)
310
393
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
312
395
"""Returns the argument list to run the subprocess with.
314
397
Exactly one of 'subsystem' and 'command' must be specified.
316
399
raise NotImplementedError(self._get_vendor_specific_argv)
318
register_ssh_vendor('none', ParamikoVendor())
321
402
class OpenSSHSubprocessVendor(SubprocessVendor):
322
403
"""SSH vendor that uses the 'ssh' executable from OpenSSH."""
405
executable_path = 'ssh'
324
407
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')
409
args = [self.executable_path,
332
410
'-oForwardX11=no', '-oForwardAgent=no',
333
'-oClearAllForwardings=yes', '-oProtocol=2',
411
'-oClearAllForwardings=yes',
334
412
'-oNoHostAuthenticationForLocalhost=yes']
335
413
if port is not None:
336
414
args.extend(['-p', str(port)])
366
441
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()
444
register_ssh_vendor('sshcorp', SSHCorpSubprocessVendor())
447
class LSHSubprocessVendor(SubprocessVendor):
448
"""SSH vendor that uses the 'lsh' executable from GNU"""
450
executable_path = 'lsh'
452
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
454
args = [self.executable_path]
456
args.extend(['-p', str(port)])
457
if username is not None:
458
args.extend(['-l', username])
459
if subsystem is not None:
460
args.extend(['--subsystem', subsystem, host])
462
args.extend([host] + command)
465
register_ssh_vendor('lsh', LSHSubprocessVendor())
468
class PLinkSubprocessVendor(SubprocessVendor):
469
"""SSH vendor that uses the 'plink' executable from Putty."""
471
executable_path = 'plink'
473
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
475
args = [self.executable_path, '-x', '-a', '-ssh', '-2', '-batch']
477
args.extend(['-P', str(port)])
478
if username is not None:
479
args.extend(['-l', username])
480
if subsystem is not None:
481
args.extend(['-s', host, subsystem])
483
args.extend([host] + command)
486
register_ssh_vendor('plink', PLinkSubprocessVendor())
489
def _paramiko_auth(username, password, host, port, paramiko_transport):
490
auth = config.AuthenticationConfig()
491
# paramiko requires a username, but it might be none if nothing was
492
# supplied. If so, use the local username.
494
username = auth.get_user('ssh', host, port=port,
495
default=getpass.getuser())
381
496
if _use_ssh_agent:
382
497
agent = paramiko.Agent()
383
498
for key in agent.get_keys():
384
mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
499
trace.mutter('Trying SSH agent key %s'
500
% paramiko.util.hexify(key.get_fingerprint()))
386
502
paramiko_transport.auth_publickey(username, key)
388
504
except paramiko.SSHException, e:
391
507
# okay, try finding id_rsa or id_dss? (posix only)
392
508
if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
394
510
if _try_pkey_auth(paramiko_transport, paramiko.DSSKey, username, 'id_dsa'):
513
# If we have gotten this far, we are about to try for passwords, do an
514
# auth_none check to see if it is even supported.
515
supported_auth_types = []
517
# Note that with paramiko <1.7.5 this logs an INFO message:
518
# Authentication type (none) not permitted.
519
# So we explicitly disable the logging level for this action
520
old_level = paramiko_transport.logger.level
521
paramiko_transport.logger.setLevel(logging.WARNING)
523
paramiko_transport.auth_none(username)
525
paramiko_transport.logger.setLevel(old_level)
526
except paramiko.BadAuthenticationType, e:
527
# Supported methods are in the exception
528
supported_auth_types = e.allowed_types
529
except paramiko.SSHException, e:
530
# Don't know what happened, but just ignore it
532
# We treat 'keyboard-interactive' and 'password' auth methods identically,
533
# because Paramiko's auth_password method will automatically try
534
# 'keyboard-interactive' auth (using the password as the response) if
535
# 'password' auth is not available. Apparently some Debian and Gentoo
536
# OpenSSH servers require this.
537
# XXX: It's possible for a server to require keyboard-interactive auth that
538
# requires something other than a single password, but we currently don't
540
if ('password' not in supported_auth_types and
541
'keyboard-interactive' not in supported_auth_types):
542
raise errors.ConnectionError('Unable to authenticate to SSH host as'
543
'\n %s@%s\nsupported auth types: %s'
544
% (username, host, supported_auth_types))
399
548
paramiko_transport.auth_password(username, password)
404
553
# 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' %
554
password = auth.get_password('ssh', host, username, port=port)
555
# get_password can still return None, which means we should not prompt
556
if password is not None:
558
paramiko_transport.auth_password(username, password)
559
except paramiko.SSHException, e:
560
raise errors.ConnectionError(
561
'Unable to authenticate to SSH host as'
562
'\n %s@%s\n' % (username, host), e)
564
raise errors.ConnectionError('Unable to authenticate to SSH host as'
565
' %s@%s' % (username, host))
415
568
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
487
642
# this causes it to be seen only by bzr and not by ssh. Python will
488
643
# generate a KeyboardInterrupt in bzr, and we will then have a chance
489
644
# to release locks or do other cleanup over ssh before the connection
491
646
# <https://launchpad.net/products/bzr/+bug/5987>
493
648
# Running it in a separate process group is not good because then it
494
649
# can't get non-echoed input of a password or passphrase.
495
650
# <https://launchpad.net/products/bzr/+bug/40508>
496
return {'preexec_fn': _ignore_sigint,
651
return {'preexec_fn': _ignore_signals,
497
652
'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):
656
_subproc_weakrefs = set()
658
def _close_ssh_proc(proc, sock):
659
"""Carefully close stdin/stdout and reap the SSH process.
661
If the pipes are already closed and/or the process has already been
662
wait()ed on, that's ok, and no error is raised. The goal is to do our best
663
to clean up (whether or not a clean up was already tried).
666
for closeable in (proc.stdin, proc.stdout, sock):
667
# We expect that either proc (a subprocess.Popen) will have stdin and
668
# stdout streams to close, or that we will have been passed a socket to
669
# close, with the option not in use being None.
670
if closeable is not None:
671
funcs.append(closeable.close)
672
funcs.append(proc.wait)
677
# It's ok for the pipe to already be closed, or the process to
678
# already be finished.
682
class SSHConnection(object):
683
"""Abstract base class for SSH connections."""
685
def get_sock_or_pipes(self):
686
"""Returns a (kind, io_object) pair.
688
If kind == 'socket', then io_object is a socket.
690
If kind == 'pipes', then io_object is a pair of file-like objects
691
(read_from, write_to).
693
raise NotImplementedError(self.get_sock_or_pipes)
696
raise NotImplementedError(self.close)
699
class SSHSubprocessConnection(SSHConnection):
700
"""A connection to an ssh subprocess via pipes or a socket.
702
This class is also socket-like enough to be used with
703
SocketAsChannelAdapter (it has 'send' and 'recv' methods).
706
def __init__(self, proc, sock=None):
709
:param proc: a subprocess.Popen
710
:param sock: if proc.stdin/out is a socket from a socketpair, then sock
711
should bzrlib's half of that socketpair. If not passed, proc's
712
stdin/out is assumed to be ordinary pipes.
716
# Add a weakref to proc that will attempt to do the same as self.close
717
# to avoid leaving processes lingering indefinitely.
719
_subproc_weakrefs.remove(ref)
720
_close_ssh_proc(proc, sock)
721
_subproc_weakrefs.add(weakref.ref(self, terminate))
507
723
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
724
if self._sock is not None:
725
return self._sock.send(data)
727
return os.write(self.proc.stdin.fileno(), data)
517
729
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)
730
if self._sock is not None:
731
return self._sock.recv(count)
733
return os.read(self.proc.stdout.fileno(), count)
736
_close_ssh_proc(self.proc, self._sock)
738
def get_sock_or_pipes(self):
739
if self._sock is not None:
740
return 'socket', self._sock
742
return 'pipes', (self.proc.stdout, self.proc.stdin)
745
class _ParamikoSSHConnection(SSHConnection):
746
"""An SSH connection via paramiko."""
748
def __init__(self, channel):
749
self.channel = channel
751
def get_sock_or_pipes(self):
752
return ('socket', self.channel)
755
return self.channel.close()