126
129
elif 'SSH Secure Shell' in version:
127
130
trace.mutter('ssh implementation is SSH Corp.')
128
131
vendor = SSHCorpSubprocessVendor()
132
elif 'lsh' in version:
133
trace.mutter('ssh implementation is GNU lsh.')
134
vendor = LSHSubprocessVendor()
129
135
# As plink user prompts are not handled currently, don't auto-detect
130
136
# it by inspection below, but keep this vendor detection for if a path
131
# is given in BZR_SSH. See https://bugs.launchpad.net/bugs/414743
137
# is given in BRZ_SSH. See https://bugs.launchpad.net/bugs/414743
132
138
elif 'plink' in version and progname == 'plink':
133
139
# Checking if "plink" was the executed argument as Windows
134
# sometimes reports 'ssh -V' incorrectly with 'plink' in it's
140
# sometimes reports 'ssh -V' incorrectly with 'plink' in its
135
141
# version. See https://bugs.launchpad.net/bzr/+bug/107155
136
142
trace.mutter("ssh implementation is Putty's plink.")
137
143
vendor = PLinkSubprocessVendor()
262
267
sock = socket.socket()
264
269
sock.connect((host, port))
265
except socket.error, e:
270
except socket.error as e:
266
271
self._raise_connection_error(host, port=port, orig_error=e)
267
272
return SFTPClient(SocketAsChannelAdapter(sock))
269
274
register_ssh_vendor('loopback', LoopbackVendor())
272
class _ParamikoSSHConnection(object):
273
def __init__(self, channel):
274
self.channel = channel
276
def get_filelike_channels(self):
277
return self.channel.makefile('rb'), self.channel.makefile('wb')
280
return self.channel.close()
283
277
class ParamikoVendor(SSHVendor):
284
278
"""Vendor that uses paramiko."""
280
def _hexify(self, s):
281
return hexlify(s).upper()
286
283
def _connect(self, username, password, host, port):
287
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
284
global SYSTEM_HOSTKEYS, BRZ_HOSTKEYS
292
289
t = paramiko.Transport((host, port or 22))
293
290
t.set_log_channel('bzr.paramiko')
295
except (paramiko.SSHException, socket.error), e:
292
except (paramiko.SSHException, socket.error) as e:
296
293
self._raise_connection_error(host, port=port, orig_error=e)
298
295
server_key = t.get_remote_server_key()
299
server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
296
server_key_hex = self._hexify(server_key.get_fingerprint())
300
297
keytype = server_key.get_name()
301
298
if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
302
299
our_server_key = SYSTEM_HOSTKEYS[host][keytype]
303
our_server_key_hex = paramiko.util.hexify(
304
our_server_key.get_fingerprint())
305
elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
306
our_server_key = BZR_HOSTKEYS[host][keytype]
307
our_server_key_hex = paramiko.util.hexify(
308
our_server_key.get_fingerprint())
300
our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
301
elif host in BRZ_HOSTKEYS and keytype in BRZ_HOSTKEYS[host]:
302
our_server_key = BRZ_HOSTKEYS[host][keytype]
303
our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
310
305
trace.warning('Adding %s host key for %s: %s'
311
306
% (keytype, host, server_key_hex))
312
add = getattr(BZR_HOSTKEYS, 'add', None)
307
add = getattr(BRZ_HOSTKEYS, 'add', None)
313
308
if add is not None: # paramiko >= 1.X.X
314
BZR_HOSTKEYS.add(host, keytype, server_key)
309
BRZ_HOSTKEYS.add(host, keytype, server_key)
316
BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
311
BRZ_HOSTKEYS.setdefault(host, {})[keytype] = server_key
317
312
our_server_key = server_key
318
our_server_key_hex = paramiko.util.hexify(
319
our_server_key.get_fingerprint())
313
our_server_key_hex = self._hexify(our_server_key.get_fingerprint())
321
315
if server_key != our_server_key:
322
316
filename1 = os.path.expanduser('~/.ssh/known_hosts')
344
338
cmdline = ' '.join(command)
345
339
channel.exec_command(cmdline)
346
340
return _ParamikoSSHConnection(channel)
347
except paramiko.SSHException, e:
341
except paramiko.SSHException as e:
348
342
self._raise_connection_error(host, port=port, orig_error=e,
349
343
msg='Unable to invoke remote bzr')
345
_ssh_connection_errors = (EOFError, OSError, IOError, socket.error)
351
346
if paramiko is not None:
352
347
vendor = ParamikoVendor()
353
348
register_ssh_vendor('paramiko', vendor)
354
349
register_ssh_vendor('none', vendor)
355
350
register_default_ssh_vendor(vendor)
356
_sftp_connection_errors = (EOFError, paramiko.SSHException)
351
_ssh_connection_errors += (paramiko.SSHException,)
359
_sftp_connection_errors = (EOFError,)
362
355
class SubprocessVendor(SSHVendor):
363
356
"""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
365
363
def _connect(self, argv):
366
proc = subprocess.Popen(argv,
367
stdin=subprocess.PIPE,
368
stdout=subprocess.PIPE,
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,
369
380
**os_specific_subprocess_params())
370
return SSHSubprocess(proc)
381
if subproc_sock is not None:
383
return SSHSubprocessConnection(proc, sock=my_sock)
372
385
def connect_sftp(self, username, password, host, port):
375
388
subsystem='sftp')
376
389
sock = self._connect(argv)
377
390
return SFTPClient(SocketAsChannelAdapter(sock))
378
except _sftp_connection_errors, e:
379
self._raise_connection_error(host, port=port, orig_error=e)
380
except (OSError, IOError), e:
381
# If the machine is fast enough, ssh can actually exit
382
# before we try and send it the sftp request, which
383
# raises a Broken Pipe
384
if e.errno not in (errno.EPIPE,):
391
except _ssh_connection_errors as e:
386
392
self._raise_connection_error(host, port=port, orig_error=e)
388
394
def connect_ssh(self, username, password, host, port, command):
390
396
argv = self._get_vendor_specific_argv(username, host, port,
392
398
return self._connect(argv)
393
except (EOFError), e:
394
self._raise_connection_error(host, port=port, orig_error=e)
395
except (OSError, IOError), e:
396
# If the machine is fast enough, ssh can actually exit
397
# before we try and send it the sftp request, which
398
# raises a Broken Pipe
399
if e.errno not in (errno.EPIPE,):
399
except _ssh_connection_errors as e:
401
400
self._raise_connection_error(host, port=port, orig_error=e)
403
402
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
454
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())
457
477
class PLinkSubprocessVendor(SubprocessVendor):
458
478
"""SSH vendor that uses the 'plink' executable from Putty."""
583
604
Load system host keys (probably doesn't work on windows) and any
584
605
"discovered" keys from previous sessions.
586
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
607
global SYSTEM_HOSTKEYS, BRZ_HOSTKEYS
588
609
SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(
589
610
os.path.expanduser('~/.ssh/known_hosts'))
591
612
trace.mutter('failed to load system host keys: ' + str(e))
592
bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
613
brz_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
594
BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
596
trace.mutter('failed to load bzr host keys: ' + str(e))
615
BRZ_HOSTKEYS = paramiko.util.load_host_keys(brz_hostkey_path)
617
trace.mutter('failed to load brz host keys: ' + str(e))
602
623
Save "discovered" host keys in $(config)/ssh_host_keys/.
604
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
625
global SYSTEM_HOSTKEYS, BRZ_HOSTKEYS
605
626
bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
606
627
config.ensure_config_dir_exists()
609
630
f = open(bzr_hostkey_path, 'w')
610
631
f.write('# SSH host keys collected by bzr\n')
611
for hostname, keys in BZR_HOSTKEYS.iteritems():
612
for keytype, key in keys.iteritems():
632
for hostname, keys in BRZ_HOSTKEYS.items():
633
for keytype, key in keys.items():
613
634
f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
616
637
trace.mutter('failed to save bzr host keys: ' + str(e))
645
666
_subproc_weakrefs = set()
647
def _close_ssh_proc(proc):
648
for func in [proc.stdin.close, proc.stdout.close, proc.wait]:
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)
655
class SSHSubprocess(object):
656
"""A socket-like object that talks to an ssh subprocess via pipes."""
658
def __init__(self, proc):
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 breezy's half of that socketpair. If not passed, proc's
722
stdin/out is assumed to be ordinary pipes.
660
726
# Add a weakref to proc that will attempt to do the same as self.close
661
727
# to avoid leaving processes lingering indefinitely.
662
728
def terminate(ref):
663
729
_subproc_weakrefs.remove(ref)
664
_close_ssh_proc(proc)
730
_close_ssh_proc(proc, sock)
665
731
_subproc_weakrefs.add(weakref.ref(self, terminate))
667
733
def send(self, data):
668
return os.write(self.proc.stdin.fileno(), data)
734
if self._sock is not None:
735
return self._sock.send(data)
737
return os.write(self.proc.stdin.fileno(), data)
670
739
def recv(self, count):
671
return os.read(self.proc.stdout.fileno(), count)
674
_close_ssh_proc(self.proc)
676
def get_filelike_channels(self):
677
return (self.proc.stdout, self.proc.stdin)
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()