124
119
if 'OpenSSH' in version:
125
mutter('ssh implementation is OpenSSH')
120
trace.mutter('ssh implementation is OpenSSH')
126
121
vendor = OpenSSHSubprocessVendor()
127
122
elif 'SSH Secure Shell' in version:
128
mutter('ssh implementation is SSH Corp.')
123
trace.mutter('ssh implementation is SSH Corp.')
129
124
vendor = SSHCorpSubprocessVendor()
130
125
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.")
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.")
135
130
vendor = PLinkSubprocessVendor()
138
133
def _get_vendor_by_inspection(self):
139
134
"""Return the vendor or None by checking for known SSH implementations."""
140
for args in [['ssh', '-V'], ['plink', '-V']]:
135
for args in (['ssh', '-V'], ['plink', '-V']):
141
136
version = self._get_ssh_version_string(args)
142
137
vendor = self._get_vendor_by_version_string(version, args)
143
138
if vendor is not None:
156
151
if vendor is None:
157
152
vendor = self._get_vendor_by_inspection()
158
153
if vendor is None:
159
mutter('falling back to default implementation')
154
trace.mutter('falling back to default implementation')
160
155
vendor = self._default_ssh_vendor
161
156
if vendor is None:
162
raise SSHVendorNotFound()
157
raise errors.SSHVendorNotFound()
163
158
self._cached_ssh_vendor = vendor
164
159
return self._cached_ssh_vendor
177
172
signal.signal(signal.SIGINT, signal.SIG_IGN)
180
class LoopbackSFTP(object):
175
class SocketAsChannelAdapter(object):
181
176
"""Simple wrapper for a socket that pretends to be a paramiko Channel."""
183
178
def __init__(self, sock):
184
179
self.__socket = sock
182
return "bzr SocketAsChannelAdapter"
186
184
def send(self, data):
187
185
return self.__socket.send(data)
189
187
def recv(self, n):
190
return self.__socket.recv(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.
192
198
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
229
239
This just unifies all the locations that try to raise ConnectionError,
230
240
so that they format things properly.
232
raise SocketConnectionError(host=host, port=port, msg=msg,
233
orig_error=orig_error)
242
raise errors.SocketConnectionError(host=host, port=port, msg=msg,
243
orig_error=orig_error)
236
246
class LoopbackVendor(SSHVendor):
237
247
"""SSH "vendor" that connects over a plain TCP socket, not SSH."""
239
249
def connect_sftp(self, username, password, host, port):
240
250
sock = socket.socket()
242
252
sock.connect((host, port))
243
253
except socket.error, e:
244
254
self._raise_connection_error(host, port=port, orig_error=e)
245
return SFTPClient(LoopbackSFTP(sock))
255
return SFTPClient(SocketAsChannelAdapter(sock))
247
257
register_ssh_vendor('loopback', LoopbackVendor())
273
283
except (paramiko.SSHException, socket.error), e:
274
284
self._raise_connection_error(host, port=port, orig_error=e)
276
286
server_key = t.get_remote_server_key()
277
287
server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
278
288
keytype = server_key.get_name()
279
289
if host in SYSTEM_HOSTKEYS and keytype in SYSTEM_HOSTKEYS[host]:
280
290
our_server_key = SYSTEM_HOSTKEYS[host][keytype]
281
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
291
our_server_key_hex = paramiko.util.hexify(
292
our_server_key.get_fingerprint())
282
293
elif host in BZR_HOSTKEYS and keytype in BZR_HOSTKEYS[host]:
283
294
our_server_key = BZR_HOSTKEYS[host][keytype]
284
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
295
our_server_key_hex = paramiko.util.hexify(
296
our_server_key.get_fingerprint())
286
warning('Adding %s host key for %s: %s' % (keytype, host, server_key_hex))
298
trace.warning('Adding %s host key for %s: %s'
299
% (keytype, host, server_key_hex))
287
300
add = getattr(BZR_HOSTKEYS, 'add', None)
288
301
if add is not None: # paramiko >= 1.X.X
289
302
BZR_HOSTKEYS.add(host, keytype, server_key)
291
304
BZR_HOSTKEYS.setdefault(host, {})[keytype] = server_key
292
305
our_server_key = server_key
293
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
306
our_server_key_hex = paramiko.util.hexify(
307
our_server_key.get_fingerprint())
295
309
if server_key != our_server_key:
296
310
filename1 = os.path.expanduser('~/.ssh/known_hosts')
297
filename2 = pathjoin(config_dir(), 'ssh_host_keys')
298
raise TransportError('Host keys for %s do not match! %s != %s' % \
311
filename2 = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
312
raise errors.TransportError(
313
'Host keys for %s do not match! %s != %s' %
299
314
(host, our_server_key_hex, server_key_hex),
300
315
['Try editing %s or %s' % (filename1, filename2)])
302
_paramiko_auth(username, password, host, t)
317
_paramiko_auth(username, password, host, port, t)
305
320
def connect_sftp(self, username, password, host, port):
306
321
t = self._connect(username, password, host, port)
326
341
register_ssh_vendor('paramiko', vendor)
327
342
register_ssh_vendor('none', vendor)
328
343
register_default_ssh_vendor(vendor)
344
_sftp_connection_errors = (EOFError, paramiko.SSHException)
347
_sftp_connection_errors = (EOFError,)
332
350
class SubprocessVendor(SSHVendor):
333
351
"""Abstract base class for vendors that use pipes to a subprocess."""
335
353
def _connect(self, argv):
336
354
proc = subprocess.Popen(argv,
337
355
stdin=subprocess.PIPE,
344
362
argv = self._get_vendor_specific_argv(username, host, port,
345
363
subsystem='sftp')
346
364
sock = self._connect(argv)
347
return SFTPClient(sock)
348
except (EOFError, paramiko.SSHException), e:
365
return SFTPClient(SocketAsChannelAdapter(sock))
366
except _sftp_connection_errors, e:
349
367
self._raise_connection_error(host, port=port, orig_error=e)
350
368
except (OSError, IOError), e:
351
369
# If the machine is fast enough, ssh can actually exit
382
400
class OpenSSHSubprocessVendor(SubprocessVendor):
383
401
"""SSH vendor that uses the 'ssh' executable from OpenSSH."""
385
403
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
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')
393
406
'-oForwardX11=no', '-oForwardAgent=no',
394
407
'-oClearAllForwardings=yes', '-oProtocol=2',
412
425
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
414
assert subsystem is not None or command is not None, (
415
'Must specify a command or subsystem')
416
if subsystem is not None:
417
assert command is None, (
418
'subsystem and command are mutually exclusive')
419
427
args = ['ssh', '-x']
420
428
if port is not None:
421
429
args.extend(['-p', str(port)])
436
444
def _get_vendor_specific_argv(self, username, host, port, subsystem=None,
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']
446
args = ['plink', '-x', '-a', '-ssh', '-2', '-batch']
444
447
if port is not None:
445
448
args.extend(['-P', str(port)])
446
449
if username is not None:
454
457
register_ssh_vendor('plink', PLinkSubprocessVendor())
457
def _paramiko_auth(username, password, host, paramiko_transport):
460
def _paramiko_auth(username, password, host, port, paramiko_transport):
458
461
# paramiko requires a username, but it might be none if nothing was supplied
459
462
# use the local username, just in case.
460
463
# We don't override username, because if we aren't using paramiko,
461
464
# the username might be specified in ~/.ssh/config and we don't want to
462
465
# force it to something else
463
466
# Also, it would mess up the self.relpath() functionality
464
username = username or getpass.getuser()
467
auth = config.AuthenticationConfig()
469
username = auth.get_user('ssh', host, port=port)
471
# Default to local user
472
username = getpass.getuser()
466
474
if _use_ssh_agent:
467
475
agent = paramiko.Agent()
468
476
for key in agent.get_keys():
469
mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
477
trace.mutter('Trying SSH agent key %s'
478
% paramiko.util.hexify(key.get_fingerprint()))
471
480
paramiko_transport.auth_publickey(username, key)
473
482
except paramiko.SSHException, e:
476
485
# okay, try finding id_rsa or id_dss? (posix only)
477
486
if _try_pkey_auth(paramiko_transport, paramiko.RSAKey, username, 'id_rsa'):
489
498
# give up and ask for a password
490
password = bzrlib.ui.ui_factory.get_password(
491
prompt='SSH %(user)s@%(host)s password',
492
user=username, host=host)
499
password = auth.get_password('ssh', host, username, port=port)
494
501
paramiko_transport.auth_password(username, password)
495
502
except paramiko.SSHException, e:
496
raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
503
raise errors.ConnectionError(
504
'Unable to authenticate to SSH host as %s@%s' % (username, host), e)
500
507
def _try_pkey_auth(paramiko_transport, pkey_class, username, filename):
504
511
paramiko_transport.auth_publickey(username, key)
506
513
except paramiko.PasswordRequiredException:
507
password = bzrlib.ui.ui_factory.get_password(
508
prompt='SSH %(filename)s password',
514
password = ui.ui_factory.get_password(
515
prompt='SSH %(filename)s password', filename=filename)
511
517
key = pkey_class.from_private_key_file(filename, password)
512
518
paramiko_transport.auth_publickey(username, key)
514
520
except paramiko.SSHException:
515
mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
521
trace.mutter('SSH authentication via %s key failed.'
522
% (os.path.basename(filename),))
516
523
except paramiko.SSHException:
517
mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
524
trace.mutter('SSH authentication via %s key failed.'
525
% (os.path.basename(filename),))
528
536
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
530
SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
538
SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(
539
os.path.expanduser('~/.ssh/known_hosts'))
531
540
except IOError, e:
532
mutter('failed to load system host keys: ' + str(e))
533
bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
541
trace.mutter('failed to load system host keys: ' + str(e))
542
bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
535
544
BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
536
545
except IOError, e:
537
mutter('failed to load bzr host keys: ' + str(e))
546
trace.mutter('failed to load bzr host keys: ' + str(e))
543
552
Save "discovered" host keys in $(config)/ssh_host_keys/.
545
554
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
546
bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
547
ensure_config_dir_exists()
555
bzr_hostkey_path = osutils.pathjoin(config.config_dir(), 'ssh_host_keys')
556
config.ensure_config_dir_exists()
550
559
f = open(bzr_hostkey_path, 'w')
592
601
def send(self, data):
593
602
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
602
604
def recv(self, count):
603
605
return os.read(self.proc.stdout.fileno(), count)