35
from bzrlib.transport import (
34
from ..transport import (
38
from bzrlib.tests import test_server
41
class StubServer (paramiko.ServerInterface):
43
def __init__(self, test_case):
37
from . import test_server
40
class StubServer(paramiko.ServerInterface):
42
def __init__(self, test_case_server):
44
43
paramiko.ServerInterface.__init__(self)
45
self._test_case = test_case
44
self.log = test_case_server.log
47
46
def check_auth_password(self, username, password):
49
self._test_case.log('sftpserver - authorizing: %s' % (username,))
48
self.log('sftpserver - authorizing: %s' % (username,))
50
49
return paramiko.AUTH_SUCCESSFUL
52
51
def check_channel_request(self, kind, chanid):
54
'sftpserver - channel request: %s, %s' % (kind, chanid))
52
self.log('sftpserver - channel request: %s, %s' % (kind, chanid))
55
53
return paramiko.OPEN_SUCCEEDED
58
class StubSFTPHandle (paramiko.SFTPHandle):
56
class StubSFTPHandle(paramiko.SFTPHandle):
61
60
return paramiko.SFTPAttributes.from_stat(
62
61
os.fstat(self.readfile.fileno()))
64
63
return paramiko.SFTPServer.convert_errno(e.errno)
66
65
def chattr(self, attr):
265
class SocketListener(threading.Thread):
267
def __init__(self, callback):
268
threading.Thread.__init__(self)
269
self._callback = callback
270
self._socket = socket.socket()
271
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
272
self._socket.bind(('localhost', 0))
273
self._socket.listen(1)
274
self.host, self.port = self._socket.getsockname()[:2]
275
self._stop_event = threading.Event()
278
# called from outside this thread
279
self._stop_event.set()
280
# use a timeout here, because if the test fails, the server thread may
281
# never notice the stop_event.
286
trace.mutter('SocketListener %r has started', self)
288
readable, writable_unused, exception_unused = \
289
select.select([self._socket], [], [], 0.1)
290
if self._stop_event.isSet():
291
trace.mutter('SocketListener %r has stopped', self)
293
if len(readable) == 0:
296
s, addr_unused = self._socket.accept()
297
trace.mutter('SocketListener %r has accepted connection %r',
299
# because the loopback socket is inline, and transports are
300
# never explicitly closed, best to launch a new thread.
301
threading.Thread(target=self._callback, args=(s,)).start()
302
except socket.error, x:
303
sys.excepthook(*sys.exc_info())
304
trace.warning('Socket error during accept() '
305
'within unit test server thread: %r' % x)
307
# probably a failed test; unit test thread will log the
309
sys.excepthook(*sys.exc_info())
311
'Exception from within unit test server thread: %r' % x)
314
281
class SocketDelay(object):
315
282
"""A socket decorator to make TCP appear slower.
386
353
return bytes_sent
389
class SFTPServer(test_server.TestServer):
356
class TestingSFTPConnectionHandler(socketserver.BaseRequestHandler):
359
self.wrap_for_latency()
360
tcs = self.server.test_case_server
361
ptrans = paramiko.Transport(self.request)
362
self.paramiko_transport = ptrans
363
# Set it to a channel under 'bzr' so that we get debug info
364
ptrans.set_log_channel('brz.paramiko.transport')
365
ptrans.add_server_key(tcs.get_host_key())
366
ptrans.set_subsystem_handler('sftp', paramiko.SFTPServer,
367
StubSFTPServer, root=tcs._root,
368
home=tcs._server_homedir)
369
server = tcs._server_interface(tcs)
370
# This blocks until the key exchange has been done
371
ptrans.start_server(None, server)
374
# Wait for the conversation to finish, when the paramiko.Transport
376
# TODO: Consider timing out after XX seconds rather than hanging.
377
# Also we could check paramiko_transport.active and possibly
378
# paramiko_transport.getException().
379
self.paramiko_transport.join()
381
def wrap_for_latency(self):
382
tcs = self.server.test_case_server
384
# Give the socket (which the request really is) a latency adding
386
self.request = SocketDelay(self.request, tcs.add_latency)
389
class TestingSFTPWithoutSSHConnectionHandler(TestingSFTPConnectionHandler):
392
self.wrap_for_latency()
393
# Re-import these as locals, so that they're still accessible during
394
# interpreter shutdown (when all module globals get set to None, leading
395
# to confusing errors like "'NoneType' object has no attribute 'error'".
397
class FakeChannel(object):
398
def get_transport(self):
401
def get_log_channel(self):
402
return 'brz.paramiko'
407
def get_hexdump(self):
413
tcs = self.server.test_case_server
414
sftp_server = paramiko.SFTPServer(
415
FakeChannel(), 'sftp', StubServer(tcs), StubSFTPServer,
416
root=tcs._root, home=tcs._server_homedir)
417
self.sftp_server = sftp_server
418
sys_stderr = sys.stderr # Used in error reporting during shutdown
420
sftp_server.start_subsystem(
421
'sftp', None, ssh.SocketAsChannelAdapter(self.request))
422
except socket.error as e:
423
if (len(e.args) > 0) and (e.args[0] == errno.EPIPE):
424
# it's okay for the client to disconnect abruptly
425
# (bug in paramiko 1.6: it should absorb this exception)
429
except Exception as e:
430
# This typically seems to happen during interpreter shutdown, so
431
# most of the useful ways to report this error won't work.
432
# Writing the exception type, and then the text of the exception,
433
# seems to be the best we can do.
434
# FIXME: All interpreter shutdown errors should have been related
435
# to daemon threads, cleanup needed -- vila 20100623
436
sys_stderr.write('\nEXCEPTION %r: ' % (e.__class__,))
437
sys_stderr.write('%s\n\n' % (e,))
440
self.sftp_server.finish_subsystem()
443
class TestingSFTPServer(test_server.TestingThreadingTCPServer):
445
def __init__(self, server_address, request_handler_class, test_case_server):
446
test_server.TestingThreadingTCPServer.__init__(
447
self, server_address, request_handler_class)
448
self.test_case_server = test_case_server
451
class SFTPServer(test_server.TestingTCPServerInAThread):
390
452
"""Common code for SFTP server facilities."""
392
454
def __init__(self, server_interface=StubServer):
455
self.host = '127.0.0.1'
457
super(SFTPServer, self).__init__((self.host, self.port),
459
TestingSFTPConnectionHandler)
393
460
self._original_vendor = None
395
self._server_homedir = None
396
self._listener = None
398
461
self._vendor = ssh.ParamikoVendor()
399
462
self._server_interface = server_interface
463
self._host_key = None
402
465
self.add_latency = 0
467
self._server_homedir = None
404
470
def _get_sftp_url(self, path):
405
471
"""Calculate an sftp url to this server for path."""
406
return 'sftp://foo:bar@%s:%d/%s' % (self._listener.host,
407
self._listener.port, path)
472
return "sftp://foo:bar@%s:%s/%s" % (self.host, self.port, path)
409
474
def log(self, message):
410
475
"""StubServer uses this to log when a new server is created."""
411
476
self.logs.append(message)
413
def _run_server_entry(self, sock):
414
"""Entry point for all implementations of _run_server.
416
If self.add_latency is > 0.000001 then sock is given a latency adding
419
if self.add_latency > 0.000001:
420
sock = SocketDelay(sock, self.add_latency)
421
return self._run_server(sock)
423
def _run_server(self, s):
424
ssh_server = paramiko.Transport(s)
425
key_file = osutils.pathjoin(self._homedir, 'test_rsa.key')
426
f = open(key_file, 'w')
427
f.write(STUB_SERVER_KEY)
429
host_key = paramiko.RSAKey.from_private_key_file(key_file)
430
ssh_server.add_server_key(host_key)
431
server = self._server_interface(self)
432
ssh_server.set_subsystem_handler('sftp', paramiko.SFTPServer,
433
StubSFTPServer, root=self._root,
434
home=self._server_homedir)
435
event = threading.Event()
436
ssh_server.start_server(event, server)
478
def create_server(self):
479
server = self.server_class((self.host, self.port),
480
self.request_handler_class,
484
def get_host_key(self):
485
if self._host_key is None:
486
key_file = osutils.pathjoin(self._homedir, 'test_rsa.key')
487
f = open(key_file, 'w')
489
f.write(STUB_SERVER_KEY)
492
self._host_key = paramiko.RSAKey.from_private_key_file(key_file)
493
return self._host_key
439
495
def start_server(self, backing_server=None):
440
496
# XXX: TODO: make sftpserver back onto backing_server rather than local
442
if not (backing_server is None or
443
isinstance(backing_server, test_server.LocalURLServer)):
498
if not (backing_server is None
499
or isinstance(backing_server, test_server.LocalURLServer)):
444
500
raise AssertionError(
445
501
'backing_server should not be %r, because this can only serve '
446
502
'the local current working directory.' % (backing_server,))
447
503
self._original_vendor = ssh._ssh_vendor_manager._cached_ssh_vendor
448
504
ssh._ssh_vendor_manager._cached_ssh_vendor = self._vendor
449
# FIXME: the following block should certainly just be self._homedir =
450
# osutils.getcwd() but that fails badly on Unix -- vila 20100224
505
self._homedir = osutils.getcwd()
451
506
if sys.platform == 'win32':
452
# Win32 needs to use the UNICODE api
453
self._homedir = os.getcwdu()
507
# Normalize the path or it will be wrongly escaped
508
self._homedir = osutils.normpath(self._homedir)
455
# But Linux SFTP servers should just deal in bytestreams
456
self._homedir = os.getcwd()
510
self._homedir = self._homedir
457
511
if self._server_homedir is None:
458
512
self._server_homedir = self._homedir
460
514
if sys.platform == 'win32':
462
self._listener = SocketListener(self._run_server_entry)
463
self._listener.setDaemon(True)
464
self._listener.start()
516
super(SFTPServer, self).start_server()
466
518
def stop_server(self):
467
self._listener.stop()
468
ssh._ssh_vendor_manager._cached_ssh_vendor = self._original_vendor
520
super(SFTPServer, self).stop_server()
522
ssh._ssh_vendor_manager._cached_ssh_vendor = self._original_vendor
470
524
def get_bogus_url(self):
471
"""See bzrlib.transport.Server.get_bogus_url."""
472
# this is chosen to try to prevent trouble with proxies, wierd dns, etc
525
"""See breezy.transport.Server.get_bogus_url."""
526
# this is chosen to try to prevent trouble with proxies, weird dns, etc
473
527
# we bind a random socket, so that we get a guaranteed unused port
474
528
# we just never listen on that port
475
529
s = socket.socket()
495
549
def __init__(self):
496
550
super(SFTPServerWithoutSSH, self).__init__()
497
551
self._vendor = ssh.LoopbackVendor()
499
def _run_server(self, sock):
500
# Re-import these as locals, so that they're still accessible during
501
# interpreter shutdown (when all module globals get set to None, leading
502
# to confusing errors like "'NoneType' object has no attribute 'error'".
503
class FakeChannel(object):
504
def get_transport(self):
506
def get_log_channel(self):
510
def get_hexdump(self):
515
server = paramiko.SFTPServer(
516
FakeChannel(), 'sftp', StubServer(self), StubSFTPServer,
517
root=self._root, home=self._server_homedir)
519
server.start_subsystem(
520
'sftp', None, ssh.SocketAsChannelAdapter(sock))
521
except socket.error, e:
522
if (len(e.args) > 0) and (e.args[0] == errno.EPIPE):
523
# it's okay for the client to disconnect abruptly
524
# (bug in paramiko 1.6: it should absorb this exception)
529
# This typically seems to happen during interpreter shutdown, so
530
# most of the useful ways to report this error are won't work.
531
# Writing the exception type, and then the text of the exception,
532
# seems to be the best we can do.
534
sys.stderr.write('\nEXCEPTION %r: ' % (e.__class__,))
535
sys.stderr.write('%s\n\n' % (e,))
536
server.finish_subsystem()
552
self.request_handler_class = TestingSFTPWithoutSSHConnectionHandler
539
558
class SFTPAbsoluteServer(SFTPServerWithoutSSH):
540
559
"""A test server for sftp transports, using absolute urls."""
542
561
def get_url(self):
543
"""See bzrlib.transport.Server.get_url."""
562
"""See breezy.transport.Server.get_url."""
544
563
homedir = self._homedir
545
564
if sys.platform != 'win32':
546
565
# Remove the initial '/' on all platforms but win32