109
114
stdout = stderr = ''
110
115
return stdout + stderr
112
def _get_vendor_by_version_string(self, version, args):
117
def _get_vendor_by_version_string(self, version):
113
118
"""Return the vendor or None based on output from the subprocess.
115
120
:param version: The output of 'ssh -V' like command.
116
:param args: Command line that was run.
119
123
if 'OpenSSH' in version:
120
trace.mutter('ssh implementation is OpenSSH')
124
mutter('ssh implementation is OpenSSH')
121
125
vendor = OpenSSHSubprocessVendor()
122
126
elif 'SSH Secure Shell' in version:
123
trace.mutter('ssh implementation is SSH Corp.')
127
mutter('ssh implementation is SSH Corp.')
124
128
vendor = SSHCorpSubprocessVendor()
125
elif 'plink' in version and args[0] == 'plink':
126
# Checking if "plink" was the executed argument as Windows
127
# sometimes reports 'ssh -V' incorrectly with 'plink' in it's
128
# version. See https://bugs.launchpad.net/bzr/+bug/107155
129
trace.mutter("ssh implementation is Putty's plink.")
129
elif 'plink' in version:
130
mutter("ssh implementation is Putty's plink.")
130
131
vendor = PLinkSubprocessVendor()
133
134
def _get_vendor_by_inspection(self):
134
135
"""Return the vendor or None by checking for known SSH implementations."""
135
for args in (['ssh', '-V'], ['plink', '-V']):
136
for args in [['ssh', '-V'], ['plink', '-V']]:
136
137
version = self._get_ssh_version_string(args)
137
vendor = self._get_vendor_by_version_string(version, args)
138
vendor = self._get_vendor_by_version_string(version)
138
139
if vendor is not None:
151
152
if vendor is None:
152
153
vendor = self._get_vendor_by_inspection()
153
154
if vendor is None:
154
trace.mutter('falling back to default implementation')
155
mutter('falling back to default implementation')
155
156
vendor = self._default_ssh_vendor
156
157
if vendor is None:
157
raise errors.SSHVendorNotFound()
158
raise SSHVendorNotFound()
158
159
self._cached_ssh_vendor = vendor
159
160
return self._cached_ssh_vendor
172
173
signal.signal(signal.SIGINT, signal.SIG_IGN)
175
class SocketAsChannelAdapter(object):
176
class LoopbackSFTP(object):
176
177
"""Simple wrapper for a socket that pretends to be a paramiko Channel."""
178
179
def __init__(self, sock):
179
180
self.__socket = sock
182
return "bzr SocketAsChannelAdapter"
184
182
def send(self, data):
185
183
return self.__socket.send(data)
187
185
def recv(self, n):
189
return self.__socket.recv(n)
190
except socket.error, e:
191
if e.args[0] in (errno.EPIPE, errno.ECONNRESET, errno.ECONNABORTED,
193
# Connection has closed. Paramiko expects an empty string in
194
# this case, not an exception.
186
return self.__socket.recv(n)
198
188
def recv_ready(self):
199
# TODO: jam 20051215 this function is necessary to support the
200
# pipelined() function. In reality, it probably should use
201
# poll() or select() to actually return if there is data
202
# available, otherwise we probably don't get any benefit
239
225
This just unifies all the locations that try to raise ConnectionError,
240
226
so that they format things properly.
242
raise errors.SocketConnectionError(host=host, port=port, msg=msg,
243
orig_error=orig_error)
228
raise SocketConnectionError(host=host, port=port, msg=msg,
229
orig_error=orig_error)
246
232
class LoopbackVendor(SSHVendor):
247
233
"""SSH "vendor" that connects over a plain TCP socket, not SSH."""
249
235
def connect_sftp(self, username, password, host, port):
250
236
sock = socket.socket()
252
238
sock.connect((host, port))
253
239
except socket.error, e:
254
240
self._raise_connection_error(host, port=port, orig_error=e)
255
return SFTPClient(SocketAsChannelAdapter(sock))
241
return SFTPClient(LoopbackSFTP(sock))
257
243
register_ssh_vendor('loopback', LoopbackVendor())
283
269
except (paramiko.SSHException, socket.error), e:
284
270
self._raise_connection_error(host, port=port, orig_error=e)
286
272
server_key = t.get_remote_server_key()
287
273
server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
288
274
keytype = server_key.get_name()
289
275
if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
290
276
our_server_key = SYSTEM_HOSTKEYS[host][keytype]
291
our_server_key_hex = paramiko.util.hexify(
292
our_server_key.get_fingerprint())
277
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
293
278
elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
294
279
our_server_key = BZR_HOSTKEYS[host][keytype]
295
our_server_key_hex = paramiko.util.hexify(
296
our_server_key.get_fingerprint())
280
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
298
trace.warning('Adding %s host key for %s: %s'
299
% (keytype, host, server_key_hex))
282
warning('Adding %s host key for %s: %s' % (keytype, host, server_key_hex))
300
283
add = getattr(BZR_HOSTKEYS, 'add', None)
301
284
if add is not None: # paramiko >= 1.X.X
302
285
BZR_HOSTKEYS.add(host, keytype, server_key)
304
287
BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
305
288
our_server_key = server_key
306
our_server_key_hex = paramiko.util.hexify(
307
our_server_key.get_fingerprint())
289
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
309
291
if server_key != our_server_key:
310
292
filename1 = os.path.expanduser('~/.ssh/known_hosts')
311
filename2 = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
312
raise errors.TransportError(
313
'Host keys for %s do not match! %s != %s' %
293
filename2 = pathjoin(config_dir(), 'ssh_host_keys')
294
raise TransportError('Host keys for %s do not match! %s != %s' % \
314
295
(host, our_server_key_hex, server_key_hex),
315
296
['Try editing %s or %s' % (filename1, filename2)])
317
_paramiko_auth(username, password, host, port, t)
298
_paramiko_auth(username, password, host, t)
320
301
def connect_sftp(self, username, password, host, port):
321
302
t = self._connect(username, password, host, port)
341
322
register_ssh_vendor('paramiko', vendor)
342
323
register_ssh_vendor('none', vendor)
343
324
register_default_ssh_vendor(vendor)
344
_sftp_connection_errors = (EOFError, paramiko.SSHException)
347
_sftp_connection_errors = (EOFError,)
350
328
class SubprocessVendor(SSHVendor):
351
329
"""Abstract base class for vendors that use pipes to a subprocess."""
353
331
def _connect(self, argv):
354
332
proc = subprocess.Popen(argv,
355
333
stdin=subprocess.PIPE,
362
340
argv = self._get_vendor_specific_argv(username, host, port,
363
341
subsystem='sftp')
364
342
sock = self._connect(argv)
365
return SFTPClient(SocketAsChannelAdapter(sock))
366
except _sftp_connection_errors, e:
343
return SFTPClient(sock)
344
except (EOFError, paramiko.SSHException), e:
367
345
self._raise_connection_error(host, port=port, orig_error=e)
368
346
except (OSError, IOError), e:
369
347
# If the machine is fast enough, ssh can actually exit
400
378
class OpenSSHSubprocessVendor(SubprocessVendor):
401
379
"""SSH vendor that uses the 'ssh' executable from OpenSSH."""
403
381
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
383
assert subsystem is not None or command is not None, (
384
'Must specify a command or subsystem')
385
if subsystem is not None:
386
assert command is None, (
387
'subsystem and command are mutually exclusive')
406
389
'-oForwardX11=no', '-oForwardAgent=no',
407
390
'-oClearAllForwardings=yes', '-oProtocol=2',
425
408
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
410
assert subsystem is not None or command is not None, (
411
'Must specify a command or subsystem')
412
if subsystem is not None:
413
assert command is None, (
414
'subsystem and command are mutually exclusive')
427
415
args = ['ssh', '-x']
428
416
if port is not None:
429
417
args.extend(['-p', str(port)])
444
432
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
446
args = ['plink', '-x', '-a', '-ssh', '-2', '-batch']
434
assert subsystem is not None or command is not None, (
435
'Must specify a command or subsystem')
436
if subsystem is not None:
437
assert command is None, (
438
'subsystem and command are mutually exclusive')
439
args = ['plink', '-x', '-a', '-ssh', '-2']
447
440
if port is not None:
448
441
args.extend(['-P', str(port)])
449
442
if username is not None:
457
450
register_ssh_vendor('plink', PLinkSubprocessVendor())
460
def _paramiko_auth(username, password, host, port, paramiko_transport):
453
def _paramiko_auth(username, password, host, paramiko_transport):
461
454
# paramiko requires a username, but it might be none if nothing was supplied
462
455
# use the local username, just in case.
463
456
# We don't override username, because if we aren't using paramiko,
464
457
# the username might be specified in ~/.ssh/config and we don't want to
465
458
# force it to something else
466
459
# Also, it would mess up the self.relpath() functionality
467
auth = config.AuthenticationConfig()
469
username = auth.get_user('ssh', host, port=port)
471
# Default to local user
472
username = getpass.getuser()
460
username = username or getpass.getuser()
474
462
if _use_ssh_agent:
475
463
agent = paramiko.Agent()
476
464
for key in agent.get_keys():
477
trace.mutter('Trying SSH agent key %s'
478
% paramiko.util.hexify(key.get_fingerprint()))
465
mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
480
467
paramiko_transport.auth_publickey(username, key)
482
469
except paramiko.SSHException, e:
485
472
# okay, try finding id_rsa or id_dss? (posix only)
486
473
if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
498
485
# give up and ask for a password
499
password = auth.get_password('ssh', host, username, port=port)
486
password = bzrlib.ui.ui_factory.get_password(
487
prompt='SSH %(user)s@%(host)s password',
488
user=username, host=host)
501
490
paramiko_transport.auth_password(username, password)
502
491
except paramiko.SSHException, e:
503
raise errors.ConnectionError(
504
'Unable to authenticate to SSH host as %s@%s' % (username, host), e)
492
raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
507
496
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
511
500
paramiko_transport.auth_publickey(username, key)
513
502
except paramiko.PasswordRequiredException:
514
password = ui.ui_factory.get_password(
515
prompt='SSH %(filename)s password', filename=filename)
503
password = bzrlib.ui.ui_factory.get_password(
504
prompt='SSH %(filename)s password',
517
507
key = pkey_class.from_private_key_file(filename, password)
518
508
paramiko_transport.auth_publickey(username, key)
520
510
except paramiko.SSHException:
521
trace.mutter('SSH authentication via %s key failed.'
522
% (os.path.basename(filename),))
511
mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
523
512
except paramiko.SSHException:
524
trace.mutter('SSH authentication via %s key failed.'
525
% (os.path.basename(filename),))
513
mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
536
524
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
538
SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(
539
os.path.expanduser('~/.ssh/known_hosts'))
541
trace.mutter('failed to load system host keys: ' + str(e))
542
bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
526
SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
528
mutter('failed to load system host keys: ' + str(e))
529
bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
544
531
BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
546
trace.mutter('failed to load bzr host keys: ' + str(e))
533
mutter('failed to load bzr host keys: ' + str(e))
552
539
Save "discovered" host keys in $(config)/ssh_host_keys/.
554
541
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
555
bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
556
config.ensure_config_dir_exists()
542
bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
543
ensure_config_dir_exists()
559
546
f = open(bzr_hostkey_path, 'w')
601
588
def send(self, data):
602
589
return os.write(self.proc.stdin.fileno(), data)
591
def recv_ready(self):
592
# TODO: jam 20051215 this function is necessary to support the
593
# pipelined() function. In reality, it probably should use
594
# poll() or select() to actually return if there is data
595
# available, otherwise we probably don't get any benefit
604
598
def recv(self, count):
605
599
return os.read(self.proc.stdout.fileno(), count)