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