38
37
from bzrlib.tests import test_server
 
41
 
class StubServer (paramiko.ServerInterface):
 
 
40
class StubServer(paramiko.ServerInterface):
 
43
 
    def __init__(self, test_case):
 
 
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(
 
 
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.
 
287
 
            readable, writable_unused, exception_unused = \
 
288
 
                select.select([self._socket], [], [], 0.1)
 
289
 
            if self._stop_event.isSet():
 
291
 
            if len(readable) == 0:
 
294
 
                s, addr_unused = self._socket.accept()
 
295
 
                # because the loopback socket is inline, and transports are
 
296
 
                # never explicitly closed, best to launch a new thread.
 
297
 
                threading.Thread(target=self._callback, args=(s,)).start()
 
298
 
            except socket.error, x:
 
299
 
                sys.excepthook(*sys.exc_info())
 
300
 
                trace.warning('Socket error during accept() '
 
301
 
                              'within unit test server thread: %r' % x)
 
303
 
                # probably a failed test; unit test thread will log the
 
305
 
                sys.excepthook(*sys.exc_info())
 
307
 
                    'Exception from within unit test server thread: %r' % x)
 
310
265
class SocketDelay(object):
 
311
266
    """A socket decorator to make TCP appear slower.
 
 
382
337
        return bytes_sent
 
385
 
class SFTPServer(test_server.TestServer):
 
 
340
class TestingSFTPConnectionHandler(SocketServer.BaseRequestHandler):
 
 
343
        self.wrap_for_latency()
 
 
344
        tcs = self.server.test_case_server
 
 
345
        ptrans = paramiko.Transport(self.request)
 
 
346
        self.paramiko_transport = ptrans
 
 
347
        # Set it to a channel under 'bzr' so that we get debug info
 
 
348
        ptrans.set_log_channel('bzr.paramiko.transport')
 
 
349
        ptrans.add_server_key(tcs.get_host_key())
 
 
350
        ptrans.set_subsystem_handler('sftp', paramiko.SFTPServer,
 
 
351
                                     StubSFTPServer, root=tcs._root,
 
 
352
                                     home=tcs._server_homedir)
 
 
353
        server = tcs._server_interface(tcs)
 
 
354
        # This blocks until the key exchange has been done
 
 
355
        ptrans.start_server(None, server)
 
 
358
        # Wait for the conversation to finish, when the paramiko.Transport
 
 
360
        # TODO: Consider timing out after XX seconds rather than hanging.
 
 
361
        #       Also we could check paramiko_transport.active and possibly
 
 
362
        #       paramiko_transport.getException().
 
 
363
        self.paramiko_transport.join()
 
 
365
    def wrap_for_latency(self):
 
 
366
        tcs = self.server.test_case_server
 
 
368
            # Give the socket (which the request really is) a latency adding
 
 
370
            self.request = SocketDelay(self.request, tcs.add_latency)
 
 
373
class TestingSFTPWithoutSSHConnectionHandler(TestingSFTPConnectionHandler):
 
 
376
        self.wrap_for_latency()
 
 
377
        # Re-import these as locals, so that they're still accessible during
 
 
378
        # interpreter shutdown (when all module globals get set to None, leading
 
 
379
        # to confusing errors like "'NoneType' object has no attribute 'error'".
 
 
380
        class FakeChannel(object):
 
 
381
            def get_transport(self):
 
 
383
            def get_log_channel(self):
 
 
384
                return 'bzr.paramiko'
 
 
387
            def get_hexdump(self):
 
 
392
        tcs = self.server.test_case_server
 
 
393
        sftp_server = paramiko.SFTPServer(
 
 
394
            FakeChannel(), 'sftp', StubServer(tcs), StubSFTPServer,
 
 
395
            root=tcs._root, home=tcs._server_homedir)
 
 
396
        self.sftp_server = sftp_server
 
 
397
        sys_stderr = sys.stderr # Used in error reporting during shutdown
 
 
399
            sftp_server.start_subsystem(
 
 
400
                'sftp', None, ssh.SocketAsChannelAdapter(self.request))
 
 
401
        except socket.error, e:
 
 
402
            if (len(e.args) > 0) and (e.args[0] == errno.EPIPE):
 
 
403
                # it's okay for the client to disconnect abruptly
 
 
404
                # (bug in paramiko 1.6: it should absorb this exception)
 
 
409
            # This typically seems to happen during interpreter shutdown, so
 
 
410
            # most of the useful ways to report this error won't work.
 
 
411
            # Writing the exception type, and then the text of the exception,
 
 
412
            # seems to be the best we can do.
 
 
413
            # FIXME: All interpreter shutdown errors should have been related
 
 
414
            # to daemon threads, cleanup needed -- vila 20100623
 
 
415
            sys_stderr.write('\nEXCEPTION %r: ' % (e.__class__,))
 
 
416
            sys_stderr.write('%s\n\n' % (e,))
 
 
419
        self.sftp_server.finish_subsystem()
 
 
422
class TestingSFTPServer(test_server.TestingThreadingTCPServer):
 
 
424
    def __init__(self, server_address, request_handler_class, test_case_server):
 
 
425
        test_server.TestingThreadingTCPServer.__init__(
 
 
426
            self, server_address, request_handler_class)
 
 
427
        self.test_case_server = test_case_server
 
 
430
class SFTPServer(test_server.TestingTCPServerInAThread):
 
386
431
    """Common code for SFTP server facilities."""
 
388
433
    def __init__(self, server_interface=StubServer):
 
 
434
        self.host = '127.0.0.1'
 
 
436
        super(SFTPServer, self).__init__((self.host, self.port),
 
 
438
                                         TestingSFTPConnectionHandler)
 
389
439
        self._original_vendor = None
 
391
 
        self._server_homedir = None
 
392
 
        self._listener = None
 
394
440
        self._vendor = ssh.ParamikoVendor()
 
395
441
        self._server_interface = server_interface
 
 
442
        self._host_key = None
 
398
444
        self.add_latency = 0
 
 
446
        self._server_homedir = None
 
400
449
    def _get_sftp_url(self, path):
 
401
450
        """Calculate an sftp url to this server for path."""
 
402
 
        return 'sftp://foo:bar@%s:%d/%s' % (self._listener.host,
 
403
 
                                            self._listener.port, path)
 
 
451
        return "sftp://foo:bar@%s:%s/%s" % (self.host, self.port, path)
 
405
453
    def log(self, message):
 
406
454
        """StubServer uses this to log when a new server is created."""
 
407
455
        self.logs.append(message)
 
409
 
    def _run_server_entry(self, sock):
 
410
 
        """Entry point for all implementations of _run_server.
 
412
 
        If self.add_latency is > 0.000001 then sock is given a latency adding
 
415
 
        if self.add_latency > 0.000001:
 
416
 
            sock = SocketDelay(sock, self.add_latency)
 
417
 
        return self._run_server(sock)
 
419
 
    def _run_server(self, s):
 
420
 
        ssh_server = paramiko.Transport(s)
 
421
 
        key_file = osutils.pathjoin(self._homedir, 'test_rsa.key')
 
422
 
        f = open(key_file, 'w')
 
423
 
        f.write(STUB_SERVER_KEY)
 
425
 
        host_key = paramiko.RSAKey.from_private_key_file(key_file)
 
426
 
        ssh_server.add_server_key(host_key)
 
427
 
        server = self._server_interface(self)
 
428
 
        ssh_server.set_subsystem_handler('sftp', paramiko.SFTPServer,
 
429
 
                                         StubSFTPServer, root=self._root,
 
430
 
                                         home=self._server_homedir)
 
431
 
        event = threading.Event()
 
432
 
        ssh_server.start_server(event, server)
 
 
457
    def create_server(self):
 
 
458
        server = self.server_class((self.host, self.port),
 
 
459
                                   self.request_handler_class,
 
 
463
    def get_host_key(self):
 
 
464
        if self._host_key is None:
 
 
465
            key_file = osutils.pathjoin(self._homedir, 'test_rsa.key')
 
 
466
            f = open(key_file, 'w')
 
 
468
                f.write(STUB_SERVER_KEY)
 
 
471
            self._host_key = paramiko.RSAKey.from_private_key_file(key_file)
 
 
472
        return self._host_key
 
435
474
    def start_server(self, backing_server=None):
 
436
475
        # XXX: TODO: make sftpserver back onto backing_server rather than local
 
 
442
481
                'the local current working directory.' % (backing_server,))
 
443
482
        self._original_vendor = ssh._ssh_vendor_manager._cached_ssh_vendor
 
444
483
        ssh._ssh_vendor_manager._cached_ssh_vendor = self._vendor
 
445
 
        # FIXME: the following block should certainly just be self._homedir =
 
446
 
        # osutils.getcwd() but that fails badly on Unix -- vila 20100224
 
447
484
        if sys.platform == 'win32':
 
448
485
            # Win32 needs to use the UNICODE api
 
449
486
            self._homedir = os.getcwdu()
 
 
487
            # Normalize the path or it will be wrongly escaped
 
 
488
            self._homedir = osutils.normpath(self._homedir)
 
451
 
            # But Linux SFTP servers should just deal in bytestreams
 
 
490
            # But unix SFTP servers should just deal in bytestreams
 
452
491
            self._homedir = os.getcwd()
 
453
492
        if self._server_homedir is None:
 
454
493
            self._server_homedir = self._homedir
 
456
495
        if sys.platform == 'win32':
 
458
 
        self._listener = SocketListener(self._run_server_entry)
 
459
 
        self._listener.setDaemon(True)
 
460
 
        self._listener.start()
 
 
497
        super(SFTPServer, self).start_server()
 
462
499
    def stop_server(self):
 
463
 
        self._listener.stop()
 
464
 
        ssh._ssh_vendor_manager._cached_ssh_vendor = self._original_vendor
 
 
501
            super(SFTPServer, self).stop_server()
 
 
503
            ssh._ssh_vendor_manager._cached_ssh_vendor = self._original_vendor
 
466
505
    def get_bogus_url(self):
 
467
506
        """See bzrlib.transport.Server.get_bogus_url."""
 
468
 
        # this is chosen to try to prevent trouble with proxies, wierd dns, etc
 
 
507
        # this is chosen to try to prevent trouble with proxies, weird dns, etc
 
469
508
        # we bind a random socket, so that we get a guaranteed unused port
 
470
509
        # we just never listen on that port
 
471
510
        s = socket.socket()
 
 
491
530
    def __init__(self):
 
492
531
        super(SFTPServerWithoutSSH, self).__init__()
 
493
532
        self._vendor = ssh.LoopbackVendor()
 
495
 
    def _run_server(self, sock):
 
496
 
        # Re-import these as locals, so that they're still accessible during
 
497
 
        # interpreter shutdown (when all module globals get set to None, leading
 
498
 
        # to confusing errors like "'NoneType' object has no attribute 'error'".
 
499
 
        class FakeChannel(object):
 
500
 
            def get_transport(self):
 
502
 
            def get_log_channel(self):
 
506
 
            def get_hexdump(self):
 
511
 
        server = paramiko.SFTPServer(
 
512
 
            FakeChannel(), 'sftp', StubServer(self), StubSFTPServer,
 
513
 
            root=self._root, home=self._server_homedir)
 
515
 
            server.start_subsystem(
 
516
 
                'sftp', None, ssh.SocketAsChannelAdapter(sock))
 
517
 
        except socket.error, e:
 
518
 
            if (len(e.args) > 0) and (e.args[0] == errno.EPIPE):
 
519
 
                # it's okay for the client to disconnect abruptly
 
520
 
                # (bug in paramiko 1.6: it should absorb this exception)
 
525
 
            # This typically seems to happen during interpreter shutdown, so
 
526
 
            # most of the useful ways to report this error are won't work.
 
527
 
            # Writing the exception type, and then the text of the exception,
 
528
 
            # seems to be the best we can do.
 
530
 
            sys.stderr.write('\nEXCEPTION %r: ' % (e.__class__,))
 
531
 
            sys.stderr.write('%s\n\n' % (e,))
 
532
 
        server.finish_subsystem()
 
 
533
        self.request_handler_class = TestingSFTPWithoutSSHConnectionHandler
 
535
539
class SFTPAbsoluteServer(SFTPServerWithoutSSH):