Ejemplo n.º 1
0
 def setUp(self):
     self.protocol = TorControlProtocol()
     self.protocol.connectionMade = lambda: None
     self.transport = proto_helpers.StringTransportWithDisconnection()
     self.protocol.makeConnection(self.transport)
     # why doesn't makeConnection do this?
     self.transport.protocol = self.protocol
class FakeTorState(object):
    def __init__(self, routers):
        self.routers = routers
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.protocol.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.protocol.transport)

    def _find_circuit_after_extend(self, x):
        return defer.succeed(None)

    def build_circuit(self, routers=None, using_guards=True):
        print "build circuit"
        cmd = "EXTENDCIRCUIT 0 "
        first = True
        for router in routers:
            if first:
                first = False
            else:
                cmd += ','
            if isinstance(router, basestring) and len(router) == 40 \
               and hashFromHexId(router):
                cmd += router
            else:
                cmd += router.id_hex[1:]
        d = self.protocol.queue_command(cmd)
        d.addCallback(self._find_circuit_after_extend)
        return d
Ejemplo n.º 3
0
class FakeEndpointAnswers:
    implements(IStreamClientEndpoint)

    def __init__(self, answers):
        self.answers = answers
        # since we use pop() we need these to be "backwards"
        self.answers.reverse()

    def get_info_raw(self, keys):
        ans = ''
        for k in keys.split():
            if len(self.answers) == 0:
                raise TorProtocolError(551, "ran out of answers")
            ans += '%s=%s\r\n' % (k, self.answers.pop())
        return ans[:-2]                 # don't want trailing \r\n

    def get_info_incremental(self, key, linecb):
        linecb('%s=%s' % (key, self.answers.pop()))
        return defer.succeed('')

    def connect(self, protocol_factory):
        self.proto = TorControlProtocol()
        self.proto.transport = proto_helpers.StringTransport()
        self.proto.get_info_raw = self.get_info_raw
        self.proto.get_info_incremental = self.get_info_incremental
        self.proto._set_valid_events('GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL')

        return defer.succeed(self.proto)
Ejemplo n.º 4
0
class ProtocolIntegrationTests(unittest.TestCase):
    """
    Tests which use a real TorControlProtocol objects, not a mock.
    """

    def setUp(self):
        self.protocol = TorControlProtocol(lambda: defer.succeed('foo'))
        self.transport = proto_helpers.StringTransport()

    def send(self, line):
        self.protocol.dataReceived(line.strip() + "\r\n")

    @defer.inlineCallbacks
    def test_with_arg(self):
        info = TorInfo(self.protocol)
        pb = info.post_bootstrap

        ## now we hook up the protocol like it connected to a real Tor
        self.protocol.makeConnection(self.transport)

        ## answer all the requests generated by TorControlProtocol boostrapping etc.
        self.send('250-AUTH METHODS=PASSWORD')
        self.send('250 OK')

        ## response to AUTHENTICATE
        self.send('250 OK')

        ## now we're in _bootstrap() in TorControlProtocol()
        self.send("250-version=foo")
        self.send("250 OK")

        self.send("250-events/names=")
        self.send("250 OK")

        self.send("250 OK")  # for USEFEATURE

        ## do the TorInfo magic
        self.send('250-info/names=')
        self.send('250-multi/path/arg/* a documentation string')
        self.send('250 OK')

        ## we had to save this up above due to the "interesting" way
        ## TorInfo switches to become a possible-nice magic thingy
        ## that does attribute-access for you.
        yield pb

        self.assertTrue(hasattr(info, 'multi'))
        self.assertTrue(hasattr(getattr(info, 'multi'), 'path'))
        self.assertTrue(hasattr(getattr(getattr(info, 'multi'), 'path'), 'arg'))

        ## Finally! The test! We see if we can get this multi-path
        ## value with an argument...
        ## a "real" tor example is "net/listeners/socks" which shows
        ## up in info/names as "net/listeners/*"

        d = info.multi.path.arg('quux')
        d.addCallback(CheckAnswer(self, 'foo'))
        self.send("250-multi/path/arg/quux=foo")
        self.send("250 OK")
        yield d
Ejemplo n.º 5
0
class ProtocolIntegrationTests(unittest.TestCase):
    """
    Tests which use a real TorControlProtocol objects, not a mock.
    """
    def setUp(self):
        self.protocol = TorControlProtocol(lambda: defer.succeed('foo'))
        self.transport = proto_helpers.StringTransport()

    def send(self, line):
        self.protocol.dataReceived(line.strip() + "\r\n")

    @defer.inlineCallbacks
    def test_with_arg(self):
        info = TorInfo(self.protocol)
        pb = info.post_bootstrap

        ## now we hook up the protocol like it connected to a real Tor
        self.protocol.makeConnection(self.transport)

        ## answer all the requests generated by TorControlProtocol boostrapping etc.
        self.send('250-AUTH METHODS=PASSWORD')
        self.send('250 OK')

        ## response to AUTHENTICATE
        self.send('250 OK')

        ## now we're in _bootstrap() in TorControlProtocol()
        self.send("250-version=foo")
        self.send("250 OK")

        self.send("250-events/names=")
        self.send("250 OK")

        self.send("250 OK")  # for USEFEATURE

        ## do the TorInfo magic
        self.send('250-info/names=')
        self.send('250-multi/path/arg/* a documentation string')
        self.send('250 OK')

        ## we had to save this up above due to the "interesting" way
        ## TorInfo switches to become a possible-nice magic thingy
        ## that does attribute-access for you.
        yield pb

        self.assertTrue(hasattr(info, 'multi'))
        self.assertTrue(hasattr(getattr(info, 'multi'), 'path'))
        self.assertTrue(hasattr(getattr(getattr(info, 'multi'), 'path'),
                                'arg'))

        ## Finally! The test! We see if we can get this multi-path
        ## value with an argument...
        ## a "real" tor example is "net/listeners/socks" which shows
        ## up in info/names as "net/listeners/*"

        d = info.multi.path.arg('quux')
        d.addCallback(CheckAnswer(self, 'foo'))
        self.send("250-multi/path/arg/quux=foo")
        self.send("250 OK")
        yield d
Ejemplo n.º 6
0
class FakeTorState(object):

    def __init__(self, routers):
        self.routers = routers
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.protocol.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.protocol.transport)

    def _find_circuit_after_extend(self, x):
        return defer.succeed(None)

    def build_circuit(self, routers=None, using_guards=True):
        cmd = "EXTENDCIRCUIT 0 "
        first = True
        for router in routers:
            if first:
                first = False
            else:
                cmd += ','
            if isinstance(router, basestring) and len(router) == 40 \
               and hashFromHexId(router):
                cmd += router
            else:
                cmd += router.id_hex[1:]
        d = self.protocol.queue_command(cmd)
        print "d %r" % (d,)
        d.addCallback(self._find_circuit_after_extend)
        return d
Ejemplo n.º 7
0
    def connect(self, protocol_factory):
        self.proto = TorControlProtocol()
        self.proto.transport = proto_helpers.StringTransport()
        self.proto.get_info_raw = self.get_info_raw
        self.proto.get_info_incremental = self.get_info_incremental
        self.proto._set_valid_events('GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL')

        return defer.succeed(self.proto)
Ejemplo n.º 8
0
 def setUp(self):
     self.protocol = TorControlProtocol()
     self.state = TorState(self.protocol)
     # avoid spew in trial logs; state prints this by default
     self.state._attacher_error = lambda f: f
     self.protocol.connectionMade = lambda: None
     self.transport = proto_helpers.StringTransport()
     self.protocol.makeConnection(self.transport)
Ejemplo n.º 9
0
 def test_set_conf_wrong_args(self):
     ctl = TorControlProtocol()
     d = ctl.set_conf('a')
     self.assertTrue(d.called)
     self.assertTrue(d.result)
     self.assertTrue('even number' in d.result.getErrorMessage())
     ## ignore the error so trial doesn't get unhappy
     d.addErrback(lambda foo: True)
     return d
Ejemplo n.º 10
0
 def test_set_conf_wrong_args(self):
     ctl = TorControlProtocol()
     d = ctl.set_conf('a')
     self.assertTrue(d.called)
     self.assertTrue(d.result)
     self.assertTrue('even number' in d.result.getErrorMessage())
     ## ignore the error so trial doesn't get unhappy
     d.addErrback(lambda foo: True)
     return d
Ejemplo n.º 11
0
    def __init__(self, test):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.transport.protocol = self.protocol

        def blam():
            self.protocol.outReceived(b"Bootstrap")
        self.transport.closeStdin = blam
        self.protocol.makeConnection(self.transport)
        self.test = test
Ejemplo n.º 12
0
class FakeReactorTcp(FakeReactor):
    implements(IReactorTCP)

    failures = 0
    _port_generator = port_generator()

    def __init__(self, test):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.transport = FakeProcessTransport()
        self.transport.protocol = self.protocol

        def blam():
            self.protocol.outReceived("Bootstrap")

        self.transport.closeStdin = blam
        self.protocol.makeConnection(self.transport)
        FakeReactor.__init__(self, test, self.transport, lambda x: None)

    def listenTCP(self, port, factory, **kwargs):
        '''returns IListeningPort'''
        if self.failures > 0:
            self.failures -= 1
            raise error.CannotListenError(None, None, None)

        if port == 0:
            port = self._port_generator.next()
        p = FakeListeningPort(port)
        p.factory = factory
        p.startListening()
        return p

    def connectTCP(self, host, port, factory, timeout, bindAddress):
        '''should return IConnector'''
        #        print "CONN", host, port, factory
        #        print dir(factory)
        #        print "XX", factory
        r = tcp.Connector(host,
                          port,
                          factory,
                          timeout,
                          bindAddress,
                          reactor=self)

        #        print dir(r)

        def blam(*args):
            print "BLAAAAAM", args

        r.connect = blam
        return r
Ejemplo n.º 13
0
    def __init__(self, test):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.transport = FakeProcessTransport()
        self.transport.protocol = self.protocol

        def blam():
            self.protocol.outReceived("Bootstrap")

        self.transport.closeStdin = blam
        self.protocol.makeConnection(self.transport)
        FakeReactor.__init__(self, test, self.transport, lambda x: None)
 def setUp(self):
     self.protocol = TorControlProtocol()
     self.protocol.connectionMade = lambda: None
     self.transport = proto_helpers.StringTransportWithDisconnection()
     self.protocol.makeConnection(self.transport)
     # why doesn't makeConnection do this?
     self.transport.protocol = self.protocol
Ejemplo n.º 15
0
class LogicTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)

    def test_set_conf_wrong_args(self):
        ctl = TorControlProtocol()
        d = ctl.set_conf('a')
        self.assertTrue(d.called)
        self.assertTrue(d.result)
        self.assertTrue('even number' in d.result.getErrorMessage())
        ## ignore the error so trial doesn't get unhappy
        d.addErrback(lambda foo: True)
        return d
Ejemplo n.º 16
0
class LogicTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)

    def test_set_conf_wrong_args(self):
        ctl = TorControlProtocol()
        d = ctl.set_conf("a")
        self.assertTrue(d.called)
        self.assertTrue(d.result)
        self.assertTrue("even number" in d.result.getErrorMessage())
        ## ignore the error so trial doesn't get unhappy
        d.addErrback(lambda foo: True)
        return d
Ejemplo n.º 17
0
 def setUp(self):
     self.protocol = TorControlProtocol()
     self.state = TorState(self.protocol)
     # avoid spew in trial logs; state prints this by default
     self.state._attacher_error = lambda f: f
     self.protocol.connectionMade = lambda: None
     self.transport = proto_helpers.StringTransport()
     self.protocol.makeConnection(self.transport)
Ejemplo n.º 18
0
class FakeEndpoint:
    implements(IStreamClientEndpoint)

    def get_info_raw(self, keys):
        return defer.succeed('\r\n'.join(map(lambda k: '%s=' % k, keys.split())))

    def get_info_incremental(self, key, linecb):
        linecb('%s=' % key)
        return defer.succeed('')

    def connect(self, protocol_factory):
        self.proto = TorControlProtocol()
        self.proto.transport = proto_helpers.StringTransport()
        self.proto.get_info_raw = self.get_info_raw
        self.proto.get_info_incremental = self.get_info_incremental
        self.proto._set_valid_events('GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL')

        return defer.succeed(self.proto)
Ejemplo n.º 19
0
class FakeReactorTcp(FakeReactor):
    implements(IReactorTCP)

    failures = 0
    _port_generator = port_generator()

    def __init__(self, test):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.transport = FakeProcessTransport()
        self.transport.protocol = self.protocol

        def blam():
            self.protocol.outReceived("Bootstrap")
        self.transport.closeStdin = blam
        self.protocol.makeConnection(self.transport)
        FakeReactor.__init__(self, test, self.transport, lambda x: None)

    def listenTCP(self, port, factory, **kwargs):
        '''returns IListeningPort'''
        if self.failures > 0:
            self.failures -= 1
            raise error.CannotListenError(None, None, None)

        if port == 0:
            port = self._port_generator.next()
        p = FakeListeningPort(port)
        p.factory = factory
        p.startListening()
        return p

    def connectTCP(self, host, port, factory, timeout, bindAddress):
        '''should return IConnector'''
#        print "CONN", host, port, factory
#        print dir(factory)
#        print "XX", factory
        r = tcp.Connector(host, port, factory, timeout, bindAddress, reactor=self)
#        print dir(r)

        def blam(*args):
            print "BLAAAAAM", args
        r.connect = blam
        return r
Ejemplo n.º 20
0
 def test_path_update(self):
     cp = TorControlProtocol()
     state = TorState(cp, False)
     circuit = Circuit(state)
     circuit.update('1 EXTENDED $E11D2B2269CC25E67CA6C9FB5843497539A74FD0=eris PURPOSE=GENERAL'.split())
     self.assertEqual(1, len(circuit.path))
     self.assertEqual(
         '$E11D2B2269CC25E67CA6C9FB5843497539A74FD0',
         circuit.path[0].id_hex
     )
     self.assertEqual('eris', circuit.path[0].name)
Ejemplo n.º 21
0
    def __init__(self, test):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.transport = FakeProcessTransport()
        self.transport.protocol = self.protocol

        def blam():
            self.protocol.outReceived("Bootstrap")
        self.transport.closeStdin = blam
        self.protocol.makeConnection(self.transport)
        FakeReactor.__init__(self, test, self.transport, lambda x: None)
Ejemplo n.º 22
0
class AuthenticationTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.transport = proto_helpers.StringTransport()

    def send(self, line):
        self.protocol.dataReceived(line.strip() + "\r\n")
        
    def test_authenticate_cookie(self):
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        cookie_data = 'cookiedata!cookiedata!cookiedata'
        open('authcookie', 'w').write(cookie_data)
        self.send('250-PROTOCOLINFO 1')
        self.send('250-AUTH METHODS=COOKIE,HASHEDPASSWORD COOKIEFILE="authcookie"')
        self.send('250-VERSION Tor="0.2.2.34"')
        self.send('250 OK')

        self.assertEqual(self.transport.value(), 'AUTHENTICATE %s\r\n' % cookie_data.encode("hex"))

    def test_authenticate_password(self):
        self.protocol.password = '******'
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        self.send('250-PROTOCOLINFO 1')
        self.send('250-AUTH METHODS=HASHEDPASSWORD')
        self.send('250-VERSION Tor="0.2.2.34"')
        self.send('250 OK')

        self.assertEqual(self.transport.value(), 'AUTHENTICATE %s\r\n' % "foo".encode("hex"))

    def confirmAuthFailed(self, *args):
        self.auth_failed = True
        
    def test_authenticate_no_password(self):
        self.protocol.post_bootstrap.addErrback(self.confirmAuthFailed)
        self.auth_failed = False
        
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n')
        
        self.send('250-PROTOCOLINFO 1')
        self.send('250-AUTH METHODS=HASHEDPASSWORD')
        self.send('250-VERSION Tor="0.2.2.34"')
        self.send('250 OK')

        self.assertTrue(self.auth_failed)
Ejemplo n.º 23
0
class TorStatePy3Tests(unittest.TestCase):

    def setUp(self):
        self.protocol = TorControlProtocol()
        self.state = TorState(self.protocol)
        # avoid spew in trial logs; state prints this by default
        self.state._attacher_error = lambda f: f
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)

    def send(self, line):
        self.protocol.dataReceived(line.strip() + b"\r\n")

    def test_attacher_coroutine(self):
        @implementer(IStreamAttacher)
        class MyAttacher(object):

            def __init__(self, answer):
                self.streams = []
                self.answer = answer

            async def attach_stream(self, stream, circuits):
                self.streams.append(stream)
                x = await defer.succeed(self.answer)
                return x

        self.state.circuits[1] = FakeCircuit(1)
        self.state.circuits[1].state = 'BUILT'
        attacher = MyAttacher(self.state.circuits[1])
        self.state.set_attacher(attacher, FakeReactor(self))

        # boilerplate to finish enough set-up in the protocol so it
        # works
        events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL'
        self.protocol._set_valid_events(events)
        self.state._add_events()
        for ignored in self.state.event_map.items():
            self.send(b"250 OK")

        self.send(b"650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER")
        self.send(b"650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE")
        self.assertEqual(len(attacher.streams), 1)
        self.assertEqual(attacher.streams[0].id, 1)
        self.assertEqual(len(self.protocol.commands), 1)
        self.assertEqual(self.protocol.commands[0][1], b'ATTACHSTREAM 1 1')
Ejemplo n.º 24
0
class TorStatePy3Tests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.state = TorState(self.protocol)
        # avoid spew in trial logs; state prints this by default
        self.state._attacher_error = lambda f: f
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)

    def send(self, line):
        self.protocol.dataReceived(line.strip() + b"\r\n")

    def test_attacher_coroutine(self):
        @implementer(IStreamAttacher)
        class MyAttacher(object):
            def __init__(self, answer):
                self.streams = []
                self.answer = answer

            async def attach_stream(self, stream, circuits):
                self.streams.append(stream)
                x = await defer.succeed(self.answer)
                return x

        self.state.circuits[1] = FakeCircuit(1)
        self.state.circuits[1].state = 'BUILT'
        attacher = MyAttacher(self.state.circuits[1])
        self.state.set_attacher(attacher, FakeReactor(self))

        # boilerplate to finish enough set-up in the protocol so it
        # works
        events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL'
        self.protocol._set_valid_events(events)
        self.state._add_events()
        for ignored in self.state.event_map.items():
            self.send(b"250 OK")

        self.send(
            b"650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER"
        )
        self.send(b"650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE")
        self.assertEqual(len(attacher.streams), 1)
        self.assertEqual(attacher.streams[0].id, 1)
        self.assertEqual(len(self.protocol.commands), 1)
        self.assertEqual(self.protocol.commands[0][1], b'ATTACHSTREAM 1 1')
Ejemplo n.º 25
0
class DisconnectionTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransportWithDisconnection()
        self.protocol.makeConnection(self.transport)
        # why doesn't makeConnection do this?
        self.transport.protocol = self.protocol

    def tearDown(self):
        self.protocol = None

    def test_disconnect_callback(self):
        """
        see that we get our callback on_disconnect if the transport
        goes away
        """
        def it_was_called(*args):
            it_was_called.yes = True
            return None

        it_was_called.yes = False
        self.protocol.on_disconnect.addCallback(it_was_called)
        self.protocol.on_disconnect.addErrback(it_was_called)
        f = failure.Failure(error.ConnectionDone("It's all over"))
        self.protocol.connectionLost(f)
        self.assertTrue(it_was_called.yes)

    def test_disconnect_errback(self):
        """
        see that we get our callback on_disconnect if the transport
        goes away
        """
        def it_was_called(*args):
            it_was_called.yes = True
            return None

        it_was_called.yes = False
        self.protocol.on_disconnect.addCallback(it_was_called)
        self.protocol.on_disconnect.addErrback(it_was_called)
        f = failure.Failure(RuntimeError("The thing didn't do the stuff."))
        self.protocol.connectionLost(f)
        self.assertTrue(it_was_called.yes)
Ejemplo n.º 26
0
class ProtocolTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)

    def tearDown(self):
        self.protocol = None

    def send(self, line):
        self.protocol.dataReceived(line.strip() + "\r\n")

    def test_statemachine_broadcast_no_code(self):
        try:
            self.protocol._broadcast_response("foo")
            self.fail()
        except RuntimeError, e:
            self.assertTrue("No code set yet" in str(e))
Ejemplo n.º 27
0
class ProtocolTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)

    def tearDown(self):
        self.protocol = None

    def send(self, line):
        self.protocol.dataReceived(line.strip() + "\r\n")

    def test_statemachine_broadcast_no_code(self):
        try:
            self.protocol._broadcast_response("foo")
            self.fail()
        except RuntimeError, e:
            self.assertTrue('No code set yet' in str(e))
class DisconnectionTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransportWithDisconnection()
        self.protocol.makeConnection(self.transport)
        # why doesn't makeConnection do this?
        self.transport.protocol = self.protocol

    def tearDown(self):
        self.protocol = None

    def test_disconnect_callback(self):
        """
        see that we get our callback on_disconnect if the transport
        goes away
        """
        def it_was_called(*args):
            it_was_called.yes = True
            return None
        it_was_called.yes = False
        self.protocol.on_disconnect.addCallback(it_was_called)
        self.protocol.on_disconnect.addErrback(it_was_called)
        f = failure.Failure(error.ConnectionDone("It's all over"))
        self.protocol.connectionLost(f)
        self.assertTrue(it_was_called.yes)

    def test_disconnect_errback(self):
        """
        see that we get our callback on_disconnect if the transport
        goes away
        """
        def it_was_called(*args):
            it_was_called.yes = True
            return None
        it_was_called.yes = False
        self.protocol.on_disconnect.addCallback(it_was_called)
        self.protocol.on_disconnect.addErrback(it_was_called)
        f = failure.Failure(RuntimeError("The thing didn't do the stuff."))
        self.protocol.connectionLost(f)
        self.assertTrue(it_was_called.yes)
Ejemplo n.º 29
0
class LaunchTorTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)
        self.clock = task.Clock()

    def setup_complete_with_timer(self, proto):
        proto._check_timeout.stop()
        proto.checkTimeout()

    def setup_complete_no_errors(self, proto, config):
        todel = proto.to_delete
        self.assertTrue(len(todel) > 0)
        proto.processEnded(Failure(error.ProcessDone(0)))
        self.assertEqual(len(proto.to_delete), 0)
        for f in todel:
            self.assertTrue(not os.path.exists(f))
        self.assertEqual(proto._timeout_delayed_call, None)

        ## make sure we set up the config to track the created tor
        ## protocol connection
        self.assertEquals(config.protocol, proto.tor_protocol)

    def setup_complete_fails(self, proto):
        todel = proto.to_delete
        self.assertTrue(len(todel) > 0)
        ## the "12" is just arbitrary, we check it later in the error-message
        proto.processEnded(
            Failure(error.ProcessTerminated(12, None, 'statusFIXME')))
        self.assertEqual(len(proto.to_delete), 0)
        for f in todel:
            self.assertTrue(not os.path.exists(f))

    def test_basic_launch(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        class OnProgress:
            def __init__(self, test, expected):
                self.test = test
                self.expected = expected

            def __call__(self, percent, tag, summary):
                self.test.assertEqual(self.expected[0],
                                      (percent, tag, summary))
                self.expected = self.expected[1:]
                self.test.assertTrue('"' not in summary)
                self.test.assertTrue(percent >= 0 and percent <= 100)

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 100%\n')
            proto.progress = OnProgress(
                self, [(90, 'circuit_create', 'Establishing a Tor circuit'),
                       (100, 'done', 'Done')])

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(connector, self.protocol, self.transport)
        d = launch_tor(config,
                       FakeReactor(self, trans, on_protocol),
                       connection_creator=creator,
                       tor_binary='/bin/echo')
        d.addCallback(self.setup_complete_no_errors, config)
        return d

    def check_setup_failure(self, fail):
        self.assertTrue("with error-code 12" in fail.getErrorMessage())
        ## cancel the errback chain, we wanted this
        return None

    def test_launch_tor_fails(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 100%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(connector, self.protocol, self.transport)
        d = launch_tor(config,
                       FakeReactor(self, trans, on_protocol),
                       connection_creator=creator,
                       tor_binary='/bin/echo')
        d.addCallback(self.setup_complete_fails)
        return self.assertFailure(d, Exception)

    def test_launch_with_timeout(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999
        timeout = 5

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        class OnProgress:
            def __init__(self, test, expected):
                self.test = test
                self.expected = expected

            def __call__(self, percent, tag, summary):
                self.test.assertEqual(self.expected[0],
                                      (percent, tag, summary))
                self.expected = self.expected[1:]
                self.test.assertTrue('"' not in summary)
                self.test.assertTrue(percent >= 0 and percent <= 100)

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 100%\n')

        trans = FakeProcessTransportNeverBootstraps()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(connector, self.protocol, self.transport)
        react = FakeReactor(self, trans, on_protocol)
        d = launch_tor(config,
                       react,
                       connection_creator=creator,
                       timeout=timeout,
                       tor_binary='/bin/echo')
        rtn = self.assertFailure(d, RuntimeError,
                                 "Timed out waiting for Tor to launch.")
        # FakeReactor is a task.Clock subclass and +1 just to be sure
        react.advance(timeout + 1)
        return rtn

    def setup_fails_stderr(self, fail):
        self.assertTrue(
            'Something went horribly wrong!' in fail.getErrorMessage())
        ## cancel the errback chain, we wanted this
        return None

    def test_tor_produces_stderr_output(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        def on_protocol(proto):
            proto.errReceived('Something went horribly wrong!\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(connector, self.protocol, self.transport)
        d = launch_tor(config,
                       FakeReactor(self, trans, on_protocol),
                       connection_creator=creator,
                       tor_binary='/bin/echo')
        d.addCallback(self.fail)  # should't get callback
        d.addErrback(self.setup_fails_stderr)
        return d

    def test_tor_connection_fails(self):
        """
        We fail to connect once, and then successfully connect --
        testing whether we're retrying properly on each Bootstrapped
        line from stdout.
        """

        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        class Connector:
            count = 0

            def __call__(self, proto, trans):
                self.count += 1
                if self.count < 2:
                    return defer.fail(error.CannotListenError(
                        None, None, None))

                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')
            proto.outReceived('Bootstrapped 100%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config,
                       FakeReactor(self, trans, on_protocol),
                       connection_creator=creator,
                       tor_binary='/bin/echo')
        d.addCallback(self.setup_complete_fails)
        return self.assertFailure(d, Exception)

    def test_tor_connection_user_data_dir(self):
        """
        Test that we don't delete a user-supplied data directory.
        """

        config = TorConfig()
        config.OrPort = 1234

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')
            proto.outReceived('Bootstrapped 100%\n')

        my_dir = tempfile.mkdtemp(prefix='tortmp')
        config.DataDirectory = my_dir
        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config,
                       FakeReactor(self, trans, on_protocol),
                       connection_creator=creator,
                       tor_binary='/bin/echo')

        def still_have_data_dir(proto, tester):
            proto.cleanup(
            )  # FIXME? not really unit-testy as this is sort of internal function
            tester.assertTrue(os.path.exists(my_dir))
            delete_file_or_tree(my_dir)

        d.addCallback(still_have_data_dir, self)
        d.addErrback(self.fail)
        return d

    def test_tor_connection_user_control_port(self):
        """
        Confirm we use a user-supplied control-port properly
        """

        config = TorConfig()
        config.OrPort = 1234
        config.ControlPort = 4321

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')
            proto.outReceived('Bootstrapped 100%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config,
                       FakeReactor(self, trans, on_protocol),
                       connection_creator=creator,
                       tor_binary='/bin/echo')

        def check_control_port(proto, tester):
            ## we just want to ensure launch_tor() didn't mess with
            ## the controlport we set
            tester.assertEquals(config.ControlPort, 4321)

        d.addCallback(check_control_port, self)
        d.addErrback(self.fail)
        return d

    def test_tor_connection_default_control_port(self):
        """
        Confirm a default control-port is set if not user-supplied.
        """

        config = TorConfig()

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')
            proto.outReceived('Bootstrapped 100%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config,
                       FakeReactor(self, trans, on_protocol),
                       connection_creator=creator,
                       tor_binary='/bin/echo')

        def check_control_port(proto, tester):
            ## ensure ControlPort was set to a default value
            tester.assertEquals(config.ControlPort, 9052)

        d.addCallback(check_control_port, self)
        d.addErrback(self.fail)
        return d

    def confirm_progress(self, exp, *args, **kwargs):
        self.assertEqual(exp, args)
        self.got_progress = True

    def test_progress_updates(self):
        from txtorcon.torconfig import TorProcessProtocol

        self.got_progress = False
        proto = TorProcessProtocol(
            None,
            functools.partial(self.confirm_progress, (10, 'tag', 'summary')))
        proto.progress(10, 'tag', 'summary')
        self.assertTrue(self.got_progress)

    def test_status_updates(self):
        from txtorcon.torconfig import TorProcessProtocol

        proto = TorProcessProtocol(None)
        proto.status_client("NOTICE CONSENSUS_ARRIVED")
Ejemplo n.º 30
0
class StateTests(unittest.TestCase):

    def setUp(self):
        self.protocol = TorControlProtocol()
        self.state = TorState(self.protocol)
        # avoid spew in trial logs; state prints this by default
        self.state._attacher_error = lambda f: f
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)

    def test_close_stream_with_attacher(self):
        class MyAttacher(object):
            implements(IStreamAttacher)

            def __init__(self):
                self.streams = []

            def attach_stream(self, stream, circuits):
                self.streams.append(stream)
                return None

        attacher = MyAttacher()
        self.state.set_attacher(attacher, FakeReactor(self))
        self.state._stream_update("76 CLOSED 0 www.example.com:0 REASON=DONE")

    def test_attacher_error_handler(self):
        # make sure error-handling "does something" that isn't blowing up
        with patch('sys.stdout') as fake_stdout:
            TorState(self.protocol)._attacher_error(Failure(RuntimeError("quote")))

    def test_stream_update(self):
        # we use a circuit ID of 0 so it doesn't try to look anything
        # up but it's not really correct to have a SUCCEEDED w/o a
        # valid circuit, I don't think
        self.state._stream_update('1610 SUCCEEDED 0 74.125.224.243:80')
        self.assertTrue(1610 in self.state.streams)

    def test_single_streams(self):
        self.state.circuits[496] = FakeCircuit(496)
        self.state._stream_status(
            'stream-status=123 SUCCEEDED 496 www.example.com:6667'
        )
        self.assertEqual(len(self.state.streams), 1)

    def test_multiple_streams(self):
        self.state.circuits[496] = FakeCircuit(496)
        self.state._stream_status(
            '\r\n'.join([
                'stream-status=',
                '123 SUCCEEDED 496 www.example.com:6667',
                '124 SUCCEEDED 496 www.example.com:6667',
            ])
        )
        self.assertEqual(len(self.state.streams), 2)

    def send(self, line):
        self.protocol.dataReceived(line.strip() + "\r\n")

    @defer.inlineCallbacks
    def test_bootstrap_callback(self):
        '''
        FIXME: something is still screwy with this; try throwing an
        exception from TorState.bootstrap and we'll just hang...
        '''

        from test_torconfig import FakeControlProtocol
        protocol = FakeControlProtocol(
            [
                "ns/all=",  # ns/all
                "",  # circuit-status
                "",  # stream-status
                "",  # address-mappings/all
                "entry-guards=\r\n$0000000000000000000000000000000000000000=name up\r\n$1111111111111111111111111111111111111111=foo up\r\n$9999999999999999999999999999999999999999=eman unusable 2012-01-01 22:00:00\r\n",  # entry-guards
                "99999",  # process/pid
                "??",  # ip-to-country/0.0.0.0
            ]
        )

        state = yield TorState.from_protocol(protocol)

        self.assertEqual(len(state.entry_guards), 2)
        self.assertTrue('$0000000000000000000000000000000000000000' in state.entry_guards)
        self.assertTrue('$1111111111111111111111111111111111111111' in state.entry_guards)
        self.assertEqual(len(state.unusable_entry_guards), 1)
        self.assertTrue('$9999999999999999999999999999999999999999' in state.unusable_entry_guards[0])

    def test_bootstrap_existing_addresses(self):
        '''
        FIXME: something is still screwy with this; try throwing an
        exception from TorState.bootstrap and we'll just hang...
        '''

        d = self.state.post_bootstrap

        clock = task.Clock()
        self.state.addrmap.scheduler = clock

        self.protocol._set_valid_events(' '.join(self.state.event_map.keys()))
        self.state._bootstrap()

        self.send("250+ns/all=")
        self.send(".")
        self.send("250 OK")

        self.send("250+circuit-status=")
        self.send(".")
        self.send("250 OK")

        self.send("250-stream-status=")
        self.send("250 OK")

        self.send("250+address-mappings/all=")
        self.send('www.example.com 127.0.0.1 "2012-01-01 00:00:00"')
        self.send('subdomain.example.com 10.0.0.0 "2012-01-01 00:01:02"')
        self.send('.')
        self.send('250 OK')

        for ignored in self.state.event_map.items():
            self.send("250 OK")

        self.send("250-entry-guards=")
        self.send("250 OK")

        self.send("250 OK")

        self.assertEqual(len(self.state.addrmap.addr), 4)
        self.assertTrue('www.example.com' in self.state.addrmap.addr)
        self.assertTrue('subdomain.example.com' in self.state.addrmap.addr)
        self.assertTrue('10.0.0.0' in self.state.addrmap.addr)
        self.assertTrue('127.0.0.1' in self.state.addrmap.addr)
        self.assertEqual('127.0.0.1', self.state.addrmap.find('www.example.com').ip)
        self.assertEqual('www.example.com', self.state.addrmap.find('127.0.0.1').name)
        self.assertEqual('10.0.0.0', self.state.addrmap.find('subdomain.example.com').ip)
        self.assertEqual('subdomain.example.com', self.state.addrmap.find('10.0.0.0').name)

        return d

    def test_bootstrap_single_existing_circuit(self):
        '''
        test with exactly one circuit. should probably test with 2 as
        well, since there was a bug with the handling of just one.
        '''

        d = self.state.post_bootstrap

        clock = task.Clock()
        self.state.addrmap.scheduler = clock

        self.protocol._set_valid_events(' '.join(self.state.event_map.keys()))
        self.state._bootstrap()

        self.send("250+ns/all=")
        self.send(".")
        self.send("250 OK")

        self.send("250-circuit-status=123 BUILT PURPOSE=GENERAL")
        self.send("250 OK")

        self.send("250-stream-status=")
        self.send("250 OK")

        self.send("250+address-mappings/all=")
        self.send('.')
        self.send('250 OK')

        for ignored in self.state.event_map.items():
            self.send("250 OK")

        self.send("250-entry-guards=")
        self.send("250 OK")

        self.send("250 OK")

        self.assertTrue(self.state.find_circuit(123))
        self.assertEquals(len(self.state.circuits), 1)

        return d

    def test_unset_attacher(self):

        class MyAttacher(object):
            implements(IStreamAttacher)

            def attach_stream(self, stream, circuits):
                return None

        fr = FakeReactor(self)
        self.state.set_attacher(MyAttacher(), fr)
        self.send("250 OK")
        self.state.set_attacher(None, fr)
        self.send("250 OK")
        self.assertEqual(
            self.transport.value(),
            'SETCONF __LeaveStreamsUnattached=1\r\nSETCONF'
            ' __LeaveStreamsUnattached=0\r\n'
        )

    def test_attacher(self):
        class MyAttacher(object):
            implements(IStreamAttacher)

            def __init__(self):
                self.streams = []
                self.answer = None

            def attach_stream(self, stream, circuits):
                self.streams.append(stream)
                return self.answer

        attacher = MyAttacher()
        self.state.set_attacher(attacher, FakeReactor(self))
        events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL'
        self.protocol._set_valid_events(events)
        self.state._add_events()
        for ignored in self.state.event_map.items():
            self.send("250 OK")

        self.send("650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER")
        self.send("650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE")
        self.assertEqual(len(attacher.streams), 1)
        self.assertEqual(attacher.streams[0].id, 1)
        self.assertEqual(len(self.protocol.commands), 1)
        self.assertEqual(self.protocol.commands[0][1], 'ATTACHSTREAM 1 0')

        # we should totally ignore .exit URIs
        attacher.streams = []
        self.send("650 STREAM 2 NEW 0 10.0.0.0.$E11D2B2269CC25E67CA6C9FB5843497539A74FD0.exit:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME")
        self.assertEqual(len(attacher.streams), 0)
        self.assertEqual(len(self.protocol.commands), 1)

        # we should NOT ignore .onion URIs
        attacher.streams = []
        self.send("650 STREAM 3 NEW 0 xxxxxxxxxxxxxxxx.onion:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME")
        self.assertEqual(len(attacher.streams), 1)
        self.assertEqual(len(self.protocol.commands), 2)
        self.assertEqual(self.protocol.commands[1][1], 'ATTACHSTREAM 3 0')

        # normal attach
        circ = FakeCircuit(1)
        circ.state = 'BUILT'
        self.state.circuits[1] = circ
        attacher.answer = circ
        self.send("650 STREAM 4 NEW 0 xxxxxxxxxxxxxxxx.onion:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME")
        self.assertEqual(len(attacher.streams), 2)
        self.assertEqual(len(self.protocol.commands), 3)
        self.assertEqual(self.protocol.commands[2][1], 'ATTACHSTREAM 4 1')

    def test_attacher_defer(self):
        class MyAttacher(object):
            implements(IStreamAttacher)

            def __init__(self, answer):
                self.streams = []
                self.answer = answer

            def attach_stream(self, stream, circuits):
                self.streams.append(stream)
                return defer.succeed(self.answer)

        self.state.circuits[1] = FakeCircuit(1)
        self.state.circuits[1].state = 'BUILT'
        attacher = MyAttacher(self.state.circuits[1])
        self.state.set_attacher(attacher, FakeReactor(self))

        # boilerplate to finish enough set-up in the protocol so it
        # works
        events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL'
        self.protocol._set_valid_events(events)
        self.state._add_events()
        for ignored in self.state.event_map.items():
            self.send("250 OK")

        self.send("650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER")
        self.send("650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE")
        self.assertEqual(len(attacher.streams), 1)
        self.assertEqual(attacher.streams[0].id, 1)
        self.assertEqual(len(self.protocol.commands), 1)
        self.assertEqual(self.protocol.commands[0][1], 'ATTACHSTREAM 1 1')

    @defer.inlineCallbacks
    def test_attacher_errors(self):
        class MyAttacher(object):
            implements(IStreamAttacher)

            def __init__(self, answer):
                self.streams = []
                self.answer = answer

            def attach_stream(self, stream, circuits):
                return self.answer

        self.state.circuits[1] = FakeCircuit(1)
        attacher = MyAttacher(FakeCircuit(2))
        self.state.set_attacher(attacher, FakeReactor(self))

        stream = Stream(self.state)
        stream.id = 3
        msg = ''
        try:
            yield self.state._maybe_attach(stream)
        except Exception, e:
            msg = str(e)
        self.assertTrue('circuit unknown' in msg)

        attacher.answer = self.state.circuits[1]
        msg = ''
        try:
            yield self.state._maybe_attach(stream)
        except Exception, e:
            msg = str(e)
Ejemplo n.º 31
0
 def setUp(self):
     self.protocol = TorControlProtocol()
     self.transport = proto_helpers.StringTransport()
Ejemplo n.º 32
0
 def setUp(self):
     self.controller = TorState(TorControlProtocol())
     self.controller.connectionMade = lambda _: None
Ejemplo n.º 33
0
class AuthenticationTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.transport = proto_helpers.StringTransport()

    def send(self, line):
        self.protocol.dataReceived(line.strip() + "\r\n")

    def test_authenticate_cookie(self):
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        cookie_data = 'cookiedata!cookiedata!cookiedata'
        with open('authcookie', 'w') as f:
            f.write(cookie_data)
        self.send('250-PROTOCOLINFO 1')
        self.send(
            '250-AUTH METHODS=COOKIE,HASHEDPASSWORD COOKIEFILE="authcookie"')
        self.send('250-VERSION Tor="0.2.2.34"')
        self.send('250 OK')

        self.assertEqual(self.transport.value(),
                         'AUTHENTICATE %s\r\n' % cookie_data.encode("hex"))

    def test_authenticate_password(self):
        self.protocol.password_function = lambda: 'foo'
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        self.send('250-PROTOCOLINFO 1')
        self.send('250-AUTH METHODS=HASHEDPASSWORD')
        self.send('250-VERSION Tor="0.2.2.34"')
        self.send('250 OK')

        self.assertEqual(self.transport.value(),
                         'AUTHENTICATE %s\r\n' % "foo".encode("hex"))

    def test_authenticate_password_deferred(self):
        d = defer.Deferred()
        self.protocol.password_function = lambda: d
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        self.send('250-PROTOCOLINFO 1')
        self.send('250-AUTH METHODS=HASHEDPASSWORD')
        self.send('250-VERSION Tor="0.2.2.34"')
        self.send('250 OK')

        ## make sure we haven't tried to authenticate before getting
        ## the password callback
        self.assertEqual(self.transport.value(), '')
        d.callback('foo')

        ## now make sure we DID try to authenticate
        self.assertEqual(self.transport.value(),
                         'AUTHENTICATE %s\r\n' % "foo".encode("hex"))

    def confirmAuthFailed(self, *args):
        self.auth_failed = True

    def test_authenticate_no_password(self):
        self.protocol.post_bootstrap.addErrback(self.confirmAuthFailed)
        self.auth_failed = False

        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n')

        self.send('250-PROTOCOLINFO 1')
        self.send('250-AUTH METHODS=HASHEDPASSWORD')
        self.send('250-VERSION Tor="0.2.2.34"')
        self.send('250 OK')

        self.assertTrue(self.auth_failed)
Ejemplo n.º 34
0
class LaunchTorTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)
        self.clock = task.Clock()

    def test_ctor_timeout_no_ireactortime(self):
        with self.assertRaises(RuntimeError) as ctx:
            TorProcessProtocol(lambda: None, timeout=42)
        self.assertTrue("Must supply an IReactorTime" in str(ctx.exception))

    def _fake_queue(self, cmd):
        if cmd.split()[0] == 'PROTOCOLINFO':
            return defer.succeed('AUTH METHODS=NULL')
        elif cmd == 'GETINFO config/names':
            return defer.succeed('config/names=')
        elif cmd == 'GETINFO signal/names':
            return defer.succeed('signal/names=')
        elif cmd == 'GETINFO version':
            return defer.succeed('version=0.1.2.3')
        elif cmd == 'GETINFO events/names':
            return defer.succeed('events/names=STATUS_CLIENT')
        return defer.succeed(None)

    def _fake_event_listener(self, what, cb):
        if what == 'STATUS_CLIENT':
            # should ignore non-BOOTSTRAP messages
            cb('STATUS_CLIENT not-bootstrap')
            cb('STATUS_CLIENT BOOTSTRAP PROGRESS=100 TAG=foo SUMMARY=bar')
        return defer.succeed(None)

    @defer.inlineCallbacks
    def test_launch_tor_unix_controlport(self):
        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.protocol.post_bootstrap.callback(self.protocol)
        self.protocol._set_valid_events("STATUS_CLIENT")
        self.protocol.add_event_listener = self._fake_event_listener
        self.protocol.queue_command = self._fake_queue

        def on_protocol(proto):
            proto.outReceived(b'Bootstrapped 90%\n')

        # launch() auto-discovers a SOCKS port
        reactor = FakeReactor(self, trans, on_protocol, [9050])
        reactor.connectUNIX = Mock()
        # prepare a suitable directory for tor unix socket
        with TempDir() as tmp:
            tmpdir = str(tmp)
            os.chmod(tmpdir, 0o0700)
            socket_file = join(tmpdir, 'test_socket_file')
            with patch('txtorcon.controller.UNIXClientEndpoint') as uce:
                endpoint = Mock()
                endpoint.connect = Mock(
                    return_value=defer.succeed(self.protocol))
                uce.return_value = endpoint

                yield launch(
                    reactor,
                    control_port="unix:{}".format(socket_file),
                    tor_binary="/bin/echo",
                    stdout=Mock(),
                    stderr=Mock(),
                )

        self.assertTrue(endpoint.connect.called)
        self.assertTrue(uce.called)
        self.assertEqual(
            socket_file,
            uce.mock_calls[0][1][1],
        )

    @defer.inlineCallbacks
    def test_launch_tor_unix_controlport_wrong_perms(self):
        reactor = FakeReactor(self, Mock(), None, [9050])
        with self.assertRaises(ValueError) as ctx:
            with TempDir() as tmp:
                tmpdir = str(tmp)
                os.chmod(tmpdir, 0o0777)
                socket_file = join(tmpdir, 'socket_test')
                yield launch(
                    reactor,
                    control_port="unix:{}".format(socket_file),
                    tor_binary="/bin/echo",
                    stdout=Mock(),
                    stderr=Mock(),
                )
        self.assertTrue(
            "must only be readable by the user" in str(ctx.exception))

    @defer.inlineCallbacks
    def test_launch_tor_unix_controlport_no_directory(self):
        reactor = FakeReactor(self, Mock(), None, [9050])
        with self.assertRaises(ValueError) as ctx:
            socket_file = '/does/not/exist'
            yield launch(
                reactor,
                control_port="unix:{}".format(socket_file),
                tor_binary="/bin/echo",
                stdout=Mock(),
                stderr=Mock(),
            )
        self.assertTrue("must exist" in str(ctx.exception))

    @patch('txtorcon.controller.find_tor_binary', return_value='/bin/echo')
    @defer.inlineCallbacks
    def test_launch_fails(self, ftb):
        trans = FakeProcessTransport()

        def on_proto(protocol):
            protocol.processEnded(
                Failure(error.ProcessTerminated(12, None, 'statusFIXME')))

        reactor = FakeReactor(self, trans, on_proto, [1234, 9052])

        try:
            yield launch(reactor)
            self.fail("Should fail")
        except RuntimeError:
            pass

        errs = self.flushLoggedErrors(RuntimeError)
        self.assertEqual(1, len(errs))
        self.assertTrue("Tor exited with error-code 12" in str(errs[0]))

    @defer.inlineCallbacks
    def test_launch_no_ireactorcore(self):
        try:
            yield launch(None)
            self.fail("should get exception")
        except ValueError as e:
            self.assertTrue("provide IReactorCore" in str(e))

    @patch('txtorcon.controller.find_tor_binary', return_value='/bin/echo')
    @patch('txtorcon.controller.TorProcessProtocol')
    @defer.inlineCallbacks
    def test_successful_launch(self, tpp, ftb):
        trans = FakeProcessTransport()
        reactor = FakeReactor(self, trans, lambda p: None, [1, 2, 3])
        config = TorConfig()

        def boot(arg=None):
            config.post_bootstrap.callback(config)

        config.__dict__['bootstrap'] = Mock(side_effect=boot)
        config.__dict__['attach_protocol'] = Mock(
            return_value=defer.succeed(None))

        def foo(*args, **kw):
            rtn = Mock()
            rtn.post_bootstrap = defer.succeed(None)
            rtn.when_connected = Mock(return_value=defer.succeed(rtn))
            return rtn

        tpp.side_effect = foo

        tor = yield launch(reactor, _tor_config=config)
        self.assertTrue(isinstance(tor, Tor))

    @defer.inlineCallbacks
    def test_quit(self):
        tor = Tor(Mock(), Mock())
        tor._protocol = Mock()
        tor._process_protocol = Mock()
        yield tor.quit()

    @defer.inlineCallbacks
    def test_quit_no_protocol(self):
        tor = Tor(Mock(), Mock())
        tor._protocol = None
        tor._process_protocol = None
        with self.assertRaises(RuntimeError) as ctx:
            yield tor.quit()
        self.assertTrue('no protocol instance' in str(ctx.exception))

    @patch('txtorcon.controller.socks')
    @defer.inlineCallbacks
    def test_dns_resolve(self, fake_socks):
        answer = object()
        tor = Tor(Mock(), Mock())
        fake_socks.resolve = Mock(return_value=defer.succeed(answer))
        ans = yield tor.dns_resolve("meejah.ca")
        self.assertEqual(ans, answer)

    @patch('txtorcon.controller.socks')
    @defer.inlineCallbacks
    def test_dns_resolve_existing_socks(self, fake_socks):
        answer = object()
        tor = Tor(Mock(), Mock())
        fake_socks.resolve = Mock(return_value=defer.succeed(answer))
        ans0 = yield tor.dns_resolve("meejah.ca")

        # do it again to exercise the _default_socks_port() case when
        # we already got the default
        fake_socks.resolve = Mock(return_value=defer.succeed(answer))
        ans1 = yield tor.dns_resolve("meejah.ca")
        self.assertEqual(ans0, answer)
        self.assertEqual(ans1, answer)

    @patch('txtorcon.controller.socks')
    @defer.inlineCallbacks
    def test_dns_resolve_no_configured_socks(self, fake_socks):
        answer = object()
        tor = Tor(Mock(), Mock())

        def boom(*args, **kw):
            raise RuntimeError("no socks")

        tor._config.socks_endpoint = Mock(side_effect=boom)
        fake_socks.resolve = Mock(return_value=defer.succeed(answer))
        ans = yield tor.dns_resolve("meejah.ca")

        self.assertEqual(ans, answer)

    @patch('txtorcon.controller.socks')
    @defer.inlineCallbacks
    def test_dns_resolve_ptr(self, fake_socks):
        answer = object()
        tor = Tor(Mock(), Mock())
        fake_socks.resolve_ptr = Mock(return_value=defer.succeed(answer))
        ans = yield tor.dns_resolve_ptr("4.3.2.1")
        self.assertEqual(ans, answer)

    @patch('txtorcon.controller.find_tor_binary', return_value='/bin/echo')
    @defer.inlineCallbacks
    def test_successful_launch_tcp_control(self, ftb):
        """
        full end-to-end test of a launch, faking things out at a "lower
        level" than most of the other tests
        """
        trans = FakeProcessTransport()

        def on_protocol(proto):
            pass

        reactor = FakeReactor(self, trans, on_protocol, [1, 2, 3])

        def connect_tcp(host, port, factory, timeout=0, bindAddress=None):
            addr = Mock()
            factory.doStart()
            proto = factory.buildProtocol(addr)
            tpp = proto._wrappedProtocol
            tpp.add_event_listener = self._fake_event_listener
            tpp.queue_command = self._fake_queue
            proto.makeConnection(Mock())
            return proto

        reactor.connectTCP = connect_tcp

        config = TorConfig()

        tor = yield launch(reactor,
                           _tor_config=config,
                           control_port='1234',
                           timeout=30)
        self.assertTrue(isinstance(tor, Tor))

    @patch('txtorcon.controller.find_tor_binary', return_value='/bin/echo')
    @patch('txtorcon.controller.sys')
    @patch('txtorcon.controller.TorProcessProtocol')
    @defer.inlineCallbacks
    def test_successful_launch_tcp_control_non_unix(self, tpp, _sys, ftb):
        _sys.platform = 'not darwin or linux2'
        trans = FakeProcessTransport()
        reactor = FakeReactor(self, trans, lambda p: None, [1, 2, 3])
        config = TorConfig()

        def boot(arg=None):
            config.post_bootstrap.callback(config)

        config.__dict__['bootstrap'] = Mock(side_effect=boot)
        config.__dict__['attach_protocol'] = Mock(
            return_value=defer.succeed(None))

        def foo(*args, **kw):
            rtn = Mock()
            rtn.post_bootstrap = defer.succeed(None)
            rtn.when_connected = Mock(return_value=defer.succeed(rtn))
            return rtn

        tpp.side_effect = foo

        tor = yield launch(reactor, _tor_config=config)
        self.assertTrue(isinstance(tor, Tor))

    @patch('txtorcon.controller.sys')
    @patch('txtorcon.controller.pwd')
    @patch('txtorcon.controller.os.geteuid')
    @patch('txtorcon.controller.os.chown')
    def test_launch_root_changes_tmp_ownership(self, chown, euid, _pwd, _sys):
        _pwd.return_value = 1000
        _sys.platform = 'linux2'
        euid.return_value = 0
        reactor = Mock()
        directlyProvides(reactor, IReactorCore)

        # note! we're providing enough options here that we react the
        # "chown" before any 'yield' statements in launch, so we don't
        # actually have to wait for it... a little rickety, though :/
        launch(reactor,
               tor_binary='/bin/echo',
               user='******',
               socks_port='1234')
        self.assertEqual(1, chown.call_count)

    @defer.inlineCallbacks
    def test_launch_timeout_exception(self):
        """
        we provide a timeout, and it expires
        """
        trans = Mock()
        trans.signalProcess = Mock(side_effect=error.ProcessExitedAlready)
        trans.loseConnection = Mock()
        on_proto = Mock()
        react = FakeReactor(self, trans, on_proto, [1234])

        def creator():
            return defer.succeed(Mock())

        d = launch(
            reactor=react,
            tor_binary='/bin/echo',
            socks_port=1234,
            timeout=10,
            connection_creator=creator,
        )
        react.advance(12)
        self.assertTrue(trans.loseConnection.called)
        with self.assertRaises(RuntimeError) as ctx:
            yield d
        self.assertTrue("timeout while launching" in str(ctx.exception))

    @defer.inlineCallbacks
    def test_launch_timeout_process_exits(self):
        # cover the "one more edge case" where we get a processEnded()
        # but we've already "done" a timeout.
        trans = Mock()
        trans.signalProcess = Mock()
        trans.loseConnection = Mock()

        class MyFakeReactor(FakeReactor):
            def spawnProcess(self,
                             processprotocol,
                             bin,
                             args,
                             env,
                             path,
                             uid=None,
                             gid=None,
                             usePTY=None,
                             childFDs=None):
                self.protocol = processprotocol
                self.protocol.makeConnection(self.transport)
                self.transport.process_protocol = processprotocol
                self.on_protocol(self.protocol)

                status = Mock()
                status.value.exitCode = None
                processprotocol.processEnded(status)
                return self.transport

        react = MyFakeReactor(self, trans, Mock(), [1234, 9052])

        d = launch(
            reactor=react,
            tor_binary='/bin/echo',
            timeout=10,
            data_directory='/dev/null',
        )
        react.advance(20)

        try:
            yield d
        except RuntimeError as e:
            self.assertTrue("Tor was killed" in str(e))

        errs = self.flushLoggedErrors(RuntimeError)
        self.assertEqual(1, len(errs))
        self.assertTrue("Tor was killed" in str(errs[0]))

    @defer.inlineCallbacks
    def test_launch_wrong_stdout(self):
        try:
            yield launch(
                FakeReactor(self, Mock(), Mock()),
                stdout=object(),
                tor_binary='/bin/echo',
            )
            self.fail("Should have thrown an error")
        except RuntimeError as e:
            self.assertTrue("file-like object needed" in str(e).lower())

    @defer.inlineCallbacks
    def test_launch_with_timeout(self):
        # XXX not entirely sure what this was/is supposed to be
        # testing, but it covers an extra 7 lines of code??
        timeout = 5

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived(b'Bootstrapped 100%\n')

        trans = FakeProcessTransportNeverBootstraps()
        trans.protocol = self.protocol
        creator = functools.partial(connector, Mock(), Mock())
        react = FakeReactor(self, trans, on_protocol, [1234, 9052])

        with self.assertRaises(RuntimeError) as ctx:
            d = launch(react,
                       connection_creator=creator,
                       timeout=timeout,
                       tor_binary='/bin/echo')
            # FakeReactor is a task.Clock subclass and +1 just to be sure
            react.advance(timeout + 1)
            yield d
        self.assertTrue('timeout while launching Tor' in str(ctx.exception))
        # could/should just use return from this to do asserts?
        self.flushLoggedErrors(RuntimeError)

    @defer.inlineCallbacks
    def test_tor_produces_stderr_output(self):
        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        def on_protocol(proto):
            proto.errReceived('Something went horribly wrong!\n')

        trans = FakeProcessTransport()
        trans.protocol = Mock()
        fakeout = StringIO()
        fakeerr = StringIO()
        creator = functools.partial(connector, Mock(), Mock())
        try:
            yield launch(
                FakeReactor(self, trans, on_protocol, [1234, 9052]),
                connection_creator=creator,
                tor_binary='/bin/echo',
                stdout=fakeout,
                stderr=fakeerr,
            )
            self.fail()  # should't get callback
        except RuntimeError as e:
            self.assertEqual('', fakeout.getvalue())
            self.assertEqual('Something went horribly wrong!\n',
                             fakeerr.getvalue())
            self.assertTrue('Something went horribly wrong!' in str(e))

    @patch('txtorcon.controller.find_tor_binary', return_value='/bin/echo')
    @defer.inlineCallbacks
    def test_tor_connection_fails(self, ftb):
        trans = FakeProcessTransport()

        def on_protocol(proto):
            proto.outReceived(b'Bootstrapped 100%\n')

        reactor = FakeReactor(self, trans, on_protocol, [1, 2, 3])

        fails = ['one']

        def connect_tcp(host, port, factory, timeout=0, bindAddress=None):
            if len(fails):
                fails.pop()
                raise error.CannotListenError('on-purpose-error', None, None)

            addr = Mock()
            factory.doStart()
            proto = factory.buildProtocol(addr)
            tpp = proto._wrappedProtocol

            def fake_event_listener(what, cb):
                if what == 'STATUS_CLIENT':
                    # should ignore non-BOOTSTRAP messages
                    cb('STATUS_CLIENT not-bootstrap')
                    cb('STATUS_CLIENT BOOTSTRAP PROGRESS=100 TAG=foo SUMMARY=bar'
                       )
                return defer.succeed(None)

            tpp.add_event_listener = fake_event_listener

            def fake_queue(cmd):
                if cmd.split()[0] == 'PROTOCOLINFO':
                    return defer.succeed('AUTH METHODS=NULL')
                elif cmd == 'GETINFO config/names':
                    return defer.succeed('config/names=')
                elif cmd == 'GETINFO signal/names':
                    return defer.succeed('signal/names=')
                elif cmd == 'GETINFO version':
                    return defer.succeed('version=0.1.2.3')
                elif cmd == 'GETINFO events/names':
                    return defer.succeed('events/names=STATUS_CLIENT')
                return defer.succeed(None)

            tpp.queue_command = fake_queue
            proto.makeConnection(Mock())
            return proto

        reactor.connectTCP = connect_tcp
        config = TorConfig()

        tor = yield launch(reactor,
                           _tor_config=config,
                           control_port='1234',
                           timeout=30)
        errs = self.flushLoggedErrors()
        self.assertTrue(isinstance(tor, Tor))
        self.assertEqual(1, len(errs))

    def _test_tor_connection_user_data_dir(self):
        """
        Test that we don't delete a user-supplied data directory.
        """

        config = TorConfig()
        config.OrPort = 1234

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived(b'Bootstrapped 90%\n')

        with TempDir(prefix='tortmp') as tmp:
            my_dir = str(tmp)
            config.DataDirectory = my_dir
            trans = FakeProcessTransport()
            trans.protocol = self.protocol
            creator = functools.partial(Connector(), self.protocol,
                                        self.transport)
            d = launch(FakeReactor(self, trans, on_protocol, [1234, 9051]),
                       connection_creator=creator,
                       tor_binary='/bin/echo')
            return d

        def still_have_data_dir(tor, tester):
            tor._process_protocol.cleanup(
            )  # FIXME? not really unit-testy as this is sort of internal function
            tester.assertTrue(os.path.exists(my_dir))
            delete_file_or_tree(my_dir)

        d.addCallback(still_have_data_dir, self)
        d.addErrback(self.fail)
        return d

    def _test_tor_connection_user_control_port(self):
        """
        Confirm we use a user-supplied control-port properly
        """

        config = TorConfig()
        config.OrPort = 1234
        config.ControlPort = 4321

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived(b'Bootstrapped 90%\n')
            proto.outReceived(b'Bootstrapped 100%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch(
            FakeReactor(self, trans, on_protocol, [9052]),
            connection_creator=creator,
            tor_binary='/bin/echo',
            socks_port=1234,
        )

        def check_control_port(proto, tester):
            # we just want to ensure launch() didn't mess with
            # the controlport we set
            tester.assertEquals(config.ControlPort, 4321)

        d.addCallback(check_control_port, self)
        d.addErrback(self.fail)
        return d

    @defer.inlineCallbacks
    def _test_tor_connection_default_control_port(self):
        """
        Confirm a default control-port is set if not user-supplied.
        """
        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived(b'Bootstrapped 90%\n')
            proto.outReceived(b'Bootstrapped 100%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        creator = functools.partial(Connector(), self.protocol, self.transport)
        tor = yield launch(
            FakeReactor(self, trans, on_protocol, [9052]),
            connection_creator=creator,
            tor_binary='/bin/echo',
            socks_port=1234,
        )

        self.assertEqual(tor.config.ControlPort, 9052)

    def test_progress_updates(self):
        self.got_progress = False

        def confirm_progress(p, t, s):
            self.assertEqual(p, 10)
            self.assertEqual(t, 'tag')
            self.assertEqual(s, 'summary')
            self.got_progress = True

        process = TorProcessProtocol(None, confirm_progress)
        process.progress(10, 'tag', 'summary')
        self.assertTrue(self.got_progress)

    def test_quit_process(self):
        process = TorProcessProtocol(None)
        process.transport = Mock()

        d = process.quit()
        self.assertFalse(d.called)

        process.processExited(Failure(error.ProcessTerminated(exitCode=15)))
        self.assertTrue(d.called)
        process.processEnded(Failure(error.ProcessDone(None)))
        self.assertTrue(d.called)
        errs = self.flushLoggedErrors()
        self.assertEqual(1, len(errs))
        self.assertTrue("Tor exited with error-code" in str(errs[0]))

    def test_quit_process_already(self):
        process = TorProcessProtocol(None)
        process.transport = Mock()

        def boom(sig):
            self.assertEqual(sig, 'TERM')
            raise error.ProcessExitedAlready()

        process.transport.signalProcess = Mock(side_effect=boom)

        d = process.quit()
        process.processEnded(Failure(error.ProcessDone(None)))
        self.assertTrue(d.called)
        errs = self.flushLoggedErrors()
        self.assertEqual(1, len(errs))
        self.assertTrue("Tor exited with error-code" in str(errs[0]))

    @defer.inlineCallbacks
    def test_quit_process_error(self):
        process = TorProcessProtocol(None)
        process.transport = Mock()

        def boom(sig):
            self.assertEqual(sig, 'TERM')
            raise RuntimeError("Something bad")

        process.transport.signalProcess = Mock(side_effect=boom)

        try:
            yield process.quit()
        except RuntimeError as e:
            self.assertEqual("Something bad", str(e))

    def XXXtest_status_updates(self):
        process = TorProcessProtocol(None)
        process.status_client("NOTICE CONSENSUS_ARRIVED")

    def XXXtest_tor_launch_success_then_shutdown(self):
        """
        There was an error where we double-callbacked a deferred,
        i.e. success and then shutdown. This repeats it.
        """
        process = TorProcessProtocol(None)
        process.status_client(
            'STATUS_CLIENT BOOTSTRAP PROGRESS=100 TAG=foo SUMMARY=cabbage')
        # XXX why this assert?
        self.assertEqual(None, process._connected_cb)

        class Value(object):
            exitCode = 123

        class Status(object):
            value = Value()

        process.processEnded(Status())
        self.assertEquals(len(self.flushLoggedErrors(RuntimeError)), 1)

    @defer.inlineCallbacks
    def test_launch_no_control_port(self):
        '''
        See Issue #80. This allows you to launch tor with a TorConfig
        with ControlPort=0 in case you don't want a control connection
        at all. In this case you get back a TorProcessProtocol and you
        own both pieces. (i.e. you have to kill it yourself).
        '''

        trans = FakeProcessTransportNoProtocol()
        trans.protocol = self.protocol

        def creator(*args, **kw):
            print("Bad: connection creator called")
            self.fail()

        def on_protocol(proto):
            self.process_proto = proto
            proto.outReceived(b'Bootstrapped 90%\n')
            proto.outReceived(b'Bootstrapped 100%\n')

        reactor = FakeReactor(self, trans, on_protocol, [9052, 9999])

        tor = yield launch(
            reactor=reactor,
            connection_creator=creator,
            tor_binary='/bin/echo',
            socks_port=1234,
            control_port=0,
        )
        self.assertEqual(tor._process_protocol, self.process_proto)
        d = tor.quit()
        reactor.advance(0)
        yield d
        errs = self.flushLoggedErrors()
        self.assertEqual(1, len(errs))
        self.assertTrue("Tor was killed" in str(errs[0]))
Ejemplo n.º 35
0
class LaunchTorTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = do_nothing
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)

    def setup_complete_no_errors(self, proto, config):
        todel = proto.to_delete
        self.assertTrue(len(todel) > 0)
        proto.processEnded(Failure(error.ProcessDone(0)))
        self.assertEqual(len(proto.to_delete), 0)
        for f in todel:
            self.assertTrue(not os.path.exists(f))

        ## make sure we set up the config to track the created tor
        ## protocol connection
        self.assertEquals(config.protocol, proto.tor_protocol)

    def setup_complete_fails(self, proto):
        todel = proto.to_delete
        self.assertTrue(len(todel) > 0)
        ## the "12" is just arbitrary, we check it later in the error-message
        proto.processEnded(Failure(error.ProcessTerminated(12, None, 'statusFIXME')))
        self.assertEqual(len(proto.to_delete), 0)
        for f in todel:
            self.assertTrue(not os.path.exists(f))

    def test_basic_launch(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        class OnProgress:
            def __init__(self, test, expected):
                self.test = test
                self.expected = expected

            def __call__(self, percent, tag, summary):
                self.test.assertEqual(self.expected[0], (percent, tag, summary))
                self.expected = self.expected[1:]
                self.test.assertTrue('"' not in summary)
                self.test.assertTrue(percent >= 0 and percent <= 100)            
            
        def on_protocol(proto):
            proto.outReceived('Bootstrapped 100%\n')
            proto.progress = OnProgress(self, [(90, 'circuit_create', 'Establishing a Tor circuit'),
                                               (100, 'done', 'Done')])

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(connector, self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator)
        d.addCallback(self.setup_complete_no_errors, config)
        return d
        
    def check_setup_failure(self, fail):
        self.assertTrue("with error-code 12" in fail.getErrorMessage())
        ## cancel the errback chain, we wanted this
        return None
                
    def test_launch_tor_fails(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap
            
        def on_protocol(proto):
            proto.outReceived('Bootstrapped 100%\n')
            
        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(connector, self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator)
        d.addCallback(self.setup_complete_fails)
        d.addErrback(self.check_setup_failure)
        return d

    def setup_fails_stderr(self, fail):
        self.assertTrue('Something went horribly wrong!' in fail.getErrorMessage())
        ## cancel the errback chain, we wanted this
        return None
        
    def test_tor_produces_stderr_output(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap
            
        def on_protocol(proto):
            proto.errReceived('Something went horribly wrong!\n')
            
        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(connector, self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator)
        d.addCallback(self.fail)        # should't get callback
        d.addErrback(self.setup_fails_stderr)
        return d
        
    def test_tor_connection_fails(self):
        """
        We fail to connect once, and then successfully connect --
        testing whether we're retrying properly on each Bootstrapped
        line from stdout.
        """
        
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        class Connector:
            count = 0

            def __call__(self, proto, trans):
                self.count += 1
                if self.count < 2:
                    return defer.fail(error.CannotListenError(None, None, None))

                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')
            proto.outReceived('Bootstrapped 100%\n')
            
        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator)
        d.addCallback(self.setup_complete_fails)
        d.addErrback(self.check_setup_failure)
        return d

    def test_tor_connection_user_data_dir(self):
        """
        Test that we don't delete a user-supplied data directory.
        """

        config = TorConfig()
        config.OrPort = 1234

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')
            proto.outReceived('Bootstrapped 100%\n')

        my_dir = tempfile.mkdtemp(prefix='tortmp')
        config.DataDirectory = my_dir
        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator)
        def still_have_data_dir(proto, tester):
            proto.cleanup()             # FIXME? not really unit-testy as this is sort of internal function
            tester.assertTrue(os.path.exists(my_dir))
            delete_file_or_tree(my_dir)
        d.addCallback(still_have_data_dir, self)
        d.addErrback(self.fail)
        return d

    def test_tor_connection_user_control_port(self):
        """
        Confirm we use a user-supplied control-port properly
        """

        config = TorConfig()
        config.OrPort = 1234
        config.ControlPort = 4321

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')
            proto.outReceived('Bootstrapped 100%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator)

        def check_control_port(proto, tester):
            ## we just want to ensure launch_tor() didn't mess with
            ## the controlport we set
            tester.assertEquals(config.ControlPort, 4321)

        d.addCallback(check_control_port, self)
        d.addErrback(self.fail)
        return d

    def test_tor_connection_default_control_port(self):
        """
        Confirm a default control-port is set if not user-supplied.
        """

        config = TorConfig()

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')
            proto.outReceived('Bootstrapped 100%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator)

        def check_control_port(proto, tester):
            ## ensure ControlPort was set to a default value
            tester.assertEquals(config.ControlPort, 9052)

        d.addCallback(check_control_port, self)
        d.addErrback(self.fail)
        return d

    def confirm_progress(self, exp, *args, **kwargs):
        self.assertEqual(exp, args)
        self.got_progress = True
        
    def test_progress_updates(self):
        from txtorcon.torconfig import TorProcessProtocol

        self.got_progress = False;
        proto = TorProcessProtocol(None, functools.partial(self.confirm_progress,
                                                           (10, 'tag', 'summary')))
        proto.progress(10, 'tag', 'summary')
        self.assertTrue(self.got_progress)

    def test_status_updates(self):
        from txtorcon.torconfig import TorProcessProtocol

        proto = TorProcessProtocol(None)
        proto.status_client("NOTICE CONSENSUS_ARRIVED")
Ejemplo n.º 36
0
 def setUp(self):
     self.protocol = TorControlProtocol()
     self.protocol.connectionMade = lambda: None
     self.transport = proto_helpers.StringTransport()
     self.protocol.makeConnection(self.transport)
     self.clock = task.Clock()
Ejemplo n.º 37
0
 def setUp(self):
     self.protocol = TorControlProtocol(lambda: defer.succeed('foo'))
     self.transport = proto_helpers.StringTransport()
Ejemplo n.º 38
0
class AuthenticationTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.transport = proto_helpers.StringTransport()

    def send(self, line):
        assert type(line) == bytes
        self.protocol.dataReceived(line.strip() + b"\r\n")

    def test_authenticate_cookie(self):
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        cookie_data = b'cookiedata!cookiedata!cookiedata'
        with open('authcookie', 'wb') as f:
            f.write(cookie_data)
        self.send(b'250-PROTOCOLINFO 1')
        self.send(
            b'250-AUTH METHODS=COOKIE,HASHEDPASSWORD COOKIEFILE="authcookie"')
        self.send(b'250-VERSION Tor="0.2.2.34"')
        self.send(b'250 OK')

        self.assertEqual(
            self.transport.value(),
            b'AUTHENTICATE ' + b2a_hex(cookie_data) + b'\r\n',
        )

    def test_authenticate_password(self):
        self.protocol.password_function = lambda: 'foo'
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        self.send(b'250-PROTOCOLINFO 1')
        self.send(b'250-AUTH METHODS=HASHEDPASSWORD')
        self.send(b'250-VERSION Tor="0.2.2.34"')
        self.send(b'250 OK')

        self.assertEqual(self.transport.value(),
                         b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n')

    def test_authenticate_password_not_bytes(self):
        self.protocol.password_function = lambda: u'foo'
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        self.send(b'250-PROTOCOLINFO 1')
        self.send(b'250-AUTH METHODS=HASHEDPASSWORD')
        self.send(b'250-VERSION Tor="0.2.2.34"')
        self.send(b'250 OK')

        self.assertEqual(self.transport.value(),
                         b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n')

    def test_authenticate_null(self):
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        self.send(b'250-PROTOCOLINFO 1')
        self.send(b'250-AUTH METHODS=NULL')
        self.send(b'250-VERSION Tor="0.2.2.34"')
        self.send(b'250 OK')

        self.assertEqual(self.transport.value(), b'AUTHENTICATE\r\n')

    def test_authenticate_password_deferred(self):
        d = defer.Deferred()
        self.protocol.password_function = lambda: d
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        self.send(b'250-PROTOCOLINFO 1')
        self.send(b'250-AUTH METHODS=HASHEDPASSWORD')
        self.send(b'250-VERSION Tor="0.2.2.34"')
        self.send(b'250 OK')

        # make sure we haven't tried to authenticate before getting
        # the password callback
        self.assertEqual(self.transport.value(), b'')
        d.callback('foo')

        # now make sure we DID try to authenticate
        self.assertEqual(self.transport.value(),
                         b'AUTHENTICATE ' + b2a_hex(b"foo") + b'\r\n')

    def test_authenticate_password_deferred_but_no_password(self):
        d = defer.Deferred()
        self.protocol.password_function = lambda: d
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        self.send(b'250-PROTOCOLINFO 1')
        self.send(b'250-AUTH METHODS=HASHEDPASSWORD')
        self.send(b'250-VERSION Tor="0.2.2.34"')
        self.send(b'250 OK')
        d.callback(None)
        return self.assertFailure(self.protocol.post_bootstrap, RuntimeError)

    def confirmAuthFailed(self, *args):
        self.auth_failed = True

    def test_authenticate_no_password(self):
        self.protocol.post_bootstrap.addErrback(self.confirmAuthFailed)
        self.auth_failed = False

        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), b'PROTOCOLINFO 1\r\n')

        self.send(b'250-PROTOCOLINFO 1')
        self.send(b'250-AUTH METHODS=HASHEDPASSWORD')
        self.send(b'250-VERSION Tor="0.2.2.34"')
        self.send(b'250 OK')

        self.assertTrue(self.auth_failed)
Ejemplo n.º 39
0
 def test_object_implements(self):
     self.assertTrue(ITorControlProtocol.providedBy(TorControlProtocol()))
Ejemplo n.º 40
0
class DisconnectionTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransportWithDisconnection()
        self.protocol.makeConnection(self.transport)
        # why doesn't makeConnection do this?
        self.transport.protocol = self.protocol

    def tearDown(self):
        self.protocol = None

    def test_disconnect_callback(self):
        """
        see that we get our callback on_disconnect if the transport
        goes away
        """
        def it_was_called(*args):
            it_was_called.yes = True
            return None
        it_was_called.yes = False
        self.protocol.on_disconnect.addCallback(it_was_called)
        self.protocol.on_disconnect.addErrback(it_was_called)
        f = failure.Failure(error.ConnectionDone("It's all over"))
        self.protocol.connectionLost(f)
        self.assertTrue(it_was_called.yes)

    def test_when_disconnect(self):
        """
        see that we get our callback for when_disconnected if the
        transport goes away
        """
        def it_was_called(arg):
            it_was_called.yes = True
            return None
        it_was_called.yes = False

        d = self.protocol.when_disconnected()
        d.addCallback(it_was_called)
        f = failure.Failure(error.ConnectionDone("It's all over"))
        self.protocol.connectionLost(f)
        self.assertTrue(it_was_called.yes)

    def test_when_disconnect_error(self):
        """
        see that we get our errback for when_disconnected if the
        transport goes away
        """
        def it_was_called(arg):
            it_was_called.yes = True
            return None
        it_was_called.yes = False

        d = self.protocol.when_disconnected()
        d.addErrback(it_was_called)
        f = failure.Failure(RuntimeError("sadness"))
        self.protocol.connectionLost(f)
        self.assertTrue(it_was_called.yes)

    def test_disconnect_errback(self):
        """
        see that we get our callback on_disconnect if the transport
        goes away
        """
        def it_was_called(*args):
            it_was_called.yes = True
            return None
        it_was_called.yes = False
        self.protocol.on_disconnect.addCallback(it_was_called)
        self.protocol.on_disconnect.addErrback(it_was_called)
        f = failure.Failure(RuntimeError("The thing didn't do the stuff."))
        self.protocol.connectionLost(f)
        self.assertTrue(it_was_called.yes)

    def test_disconnect_outstanding_commands(self):
        """
        outstanding commands should errback on disconnect
        """

        def it_was_called(f):
            str(f)
            it_was_called.count += 1
            return None
        it_was_called.count = 0

        # we want to make sure outstanding commands get errbacks
        d0 = self.protocol.queue_command("some command0")
        d1 = self.protocol.queue_command("some command1")
        d0.addErrback(it_was_called)
        d1.addErrback(it_was_called)
        self.protocol.on_disconnect.addErrback(lambda _: None)

        f = failure.Failure(RuntimeError("The thing didn't do the stuff."))
        self.protocol.connectionLost(f)
        self.assertEqual(it_was_called.count, 2)
Ejemplo n.º 41
0
class ProtocolTests(unittest.TestCase):

    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)

    def tearDown(self):
        self.protocol = None

    def send(self, line):
        assert type(line) == bytes
        self.protocol.dataReceived(line.strip() + b"\r\n")

    def test_statemachine_broadcast_no_code(self):
        try:
            self.protocol._broadcast_response("foo")
            self.fail()
        except RuntimeError as e:
            self.assertTrue('No code set yet' in str(e))

    def test_statemachine_broadcast_unknown_code(self):
        try:
            self.protocol.code = 999
            self.protocol._broadcast_response("foo")
            self.fail()
        except RuntimeError as e:
            self.assertTrue('Unknown code' in str(e))

    def test_statemachine_is_finish(self):
        self.assertTrue(not self.protocol._is_finish_line(''))
        self.assertTrue(self.protocol._is_finish_line('.'))
        self.assertTrue(self.protocol._is_finish_line('300 '))
        self.assertTrue(not self.protocol._is_finish_line('250-'))

    def test_statemachine_singleline(self):
        self.assertTrue(not self.protocol._is_single_line_response('foo'))

    def test_statemachine_continuation(self):
        try:
            self.protocol.code = 250
            self.protocol._is_continuation_line("123 ")
            self.fail()
        except RuntimeError as e:
            self.assertTrue('Unexpected code' in str(e))

    def test_statemachine_multiline(self):
        try:
            self.protocol.code = 250
            self.protocol._is_multi_line("123 ")
            self.fail()
        except RuntimeError as e:
            self.assertTrue('Unexpected code' in str(e))

    def test_response_with_no_request(self):
        with self.assertRaises(RuntimeError) as ctx:
            self.protocol.code = 200
            self.protocol._broadcast_response('200 OK')
        self.assertTrue(
            "didn't issue a command" in str(ctx.exception)
        )

    def auth_failed(self, msg):
        self.assertEqual(str(msg.value), '551 go away')
        self.got_auth_failed = True

    def test_authenticate_fail(self):
        self.got_auth_failed = False
        self.protocol._auth_failed = self.auth_failed

        self.protocol.password_function = lambda: 'foo'
        self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=HASHEDPASSWORD
VERSION Tor="0.2.2.35"
OK''')
        self.send(b'551 go away\r\n')
        self.assertTrue(self.got_auth_failed)

    def test_authenticate_no_auth_line(self):
        try:
            self.protocol._do_authenticate('''PROTOCOLINFO 1
FOOAUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE="/dev/null"
VERSION Tor="0.2.2.35"
OK''')
            self.assertTrue(False)
        except RuntimeError as e:
            self.assertTrue('find AUTH line' in str(e))

    def test_authenticate_not_enough_cookie_data(self):
        with tempfile.NamedTemporaryFile() as cookietmp:
            cookietmp.write(b'x' * 35)  # too much data
            cookietmp.flush()

            try:
                self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=COOKIE COOKIEFILE="%s"
VERSION Tor="0.2.2.35"
OK''' % cookietmp.name)
                self.assertTrue(False)
            except RuntimeError as e:
                self.assertTrue('cookie to be 32' in str(e))

    def test_authenticate_not_enough_safecookie_data(self):
        with tempfile.NamedTemporaryFile() as cookietmp:
            cookietmp.write(b'x' * 35)  # too much data
            cookietmp.flush()

            try:
                self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=SAFECOOKIE COOKIEFILE="%s"
VERSION Tor="0.2.2.35"
OK''' % cookietmp.name)
                self.assertTrue(False)
            except RuntimeError as e:
                self.assertTrue('cookie to be 32' in str(e))

    def test_authenticate_safecookie(self):
        with tempfile.NamedTemporaryFile() as cookietmp:
            cookiedata = bytes(bytearray([0] * 32))
            cookietmp.write(cookiedata)
            cookietmp.flush()

            self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=SAFECOOKIE COOKIEFILE="{}"
VERSION Tor="0.2.2.35"
OK'''.format(cookietmp.name))
            self.assertTrue(
                b'AUTHCHALLENGE SAFECOOKIE ' in self.transport.value()
            )
            x = self.transport.value().split()[-1]
            client_nonce = a2b_hex(x)
            self.transport.clear()
            server_nonce = bytes(bytearray([0] * 32))
            server_hash = hmac_sha256(
                b"Tor safe cookie authentication server-to-controller hash",
                cookiedata + client_nonce + server_nonce,
            )

            self.send(
                b'250 AUTHCHALLENGE SERVERHASH=' +
                base64.b16encode(server_hash) + b' SERVERNONCE=' +
                base64.b16encode(server_nonce) + b'\r\n'
            )
            self.assertTrue(b'AUTHENTICATE ' in self.transport.value())

    def test_authenticate_cookie_without_reading(self):
        server_nonce = bytes(bytearray([0] * 32))
        server_hash = bytes(bytearray([0] * 32))
        try:
            self.protocol._safecookie_authchallenge(
                '250 AUTHCHALLENGE SERVERHASH=%s SERVERNONCE=%s' %
                (base64.b16encode(server_hash), base64.b16encode(server_nonce))
            )
            self.assertTrue(False)
        except RuntimeError as e:
            self.assertTrue('not read' in str(e))

    def test_authenticate_unexisting_cookie_file(self):
        unexisting_file = __file__ + "-unexisting"
        try:
            self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=COOKIE COOKIEFILE="%s"
VERSION Tor="0.2.2.35"
OK''' % unexisting_file)
            self.assertTrue(False)
        except RuntimeError:
            pass

    def test_authenticate_unexisting_safecookie_file(self):
        unexisting_file = __file__ + "-unexisting"
        try:
            self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=SAFECOOKIE COOKIEFILE="{}"
VERSION Tor="0.2.2.35"
OK'''.format(unexisting_file))
            self.assertTrue(False)
        except RuntimeError:
            pass

    def test_authenticate_dont_send_cookiefile(self):
        try:
            self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=SAFECOOKIE
VERSION Tor="0.2.2.35"
OK''')
            self.assertTrue(False)
        except RuntimeError:
            pass

    def test_authenticate_password_when_cookie_unavailable(self):
        unexisting_file = __file__ + "-unexisting"
        self.protocol.password_function = lambda: 'foo'
        self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=COOKIE,HASHEDPASSWORD COOKIEFILE="{}"
VERSION Tor="0.2.2.35"
OK'''.format(unexisting_file))
        self.assertEqual(
            self.transport.value(),
            b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n',
        )

    def test_authenticate_password_when_safecookie_unavailable(self):
        unexisting_file = __file__ + "-unexisting"
        self.protocol.password_function = lambda: 'foo'
        self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=SAFECOOKIE,HASHEDPASSWORD COOKIEFILE="{}"
VERSION Tor="0.2.2.35"
OK'''.format(unexisting_file))
        self.assertEqual(
            self.transport.value(),
            b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n',
        )

    def test_authenticate_safecookie_wrong_hash(self):
        cookiedata = bytes(bytearray([0] * 32))
        server_nonce = bytes(bytearray([0] * 32))
        server_hash = bytes(bytearray([0] * 32))

        # pretend we already did PROTOCOLINFO and read the cookie
        # file
        self.protocol._cookie_data = cookiedata
        self.protocol.client_nonce = server_nonce  # all 0's anyway
        try:
            self.protocol._safecookie_authchallenge(
                '250 AUTHCHALLENGE SERVERHASH={} SERVERNONCE={}'.format(
                    b2a_hex(server_hash).decode('ascii'),
                    b2a_hex(server_nonce).decode('ascii'),
                )
            )
            self.assertTrue(False)
        except RuntimeError as e:
            self.assertTrue('hash not expected' in str(e))

    def confirm_version_events(self, arg):
        self.assertEqual(self.protocol.version, 'foo')
        events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL'.split()
        self.assertEqual(len(self.protocol.valid_events), len(events))
        self.assertTrue(all(x in self.protocol.valid_events for x in events))

    def test_bootstrap_callback(self):
        d = self.protocol.post_bootstrap
        d.addCallback(CallbackChecker(self.protocol))
        d.addCallback(self.confirm_version_events)

        events = b'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL'
        self.protocol._bootstrap()

        # answer all the requests generated by boostrapping etc.
        self.send(b"250-signal/names=")
        self.send(b"250 OK")

        self.send(b"250-version=foo")
        self.send(b"250 OK")

        self.send(b"250-events/names=" + events)
        self.send(b"250 OK")

        self.send(b"250 OK")  # for USEFEATURE

        return d

    def test_bootstrap_tor_does_not_support_signal_names(self):
        self.protocol._bootstrap()
        self.send(b'552 Unrecognized key "signal/names"')
        valid_signals = ["RELOAD", "DUMP", "DEBUG", "NEWNYM", "CLEARDNSCACHE"]
        self.assertEqual(self.protocol.valid_signals, valid_signals)

    def test_async(self):
        """
        test the example from control-spec.txt to see that we
        handle interleaved async notifications properly.
        """
        self.protocol._set_valid_events('CIRC')
        self.protocol.add_event_listener('CIRC', lambda _: None)
        self.send(b"250 OK")

        d = self.protocol.get_conf("SOCKSPORT ORPORT")
        self.send(b"650 CIRC 1000 EXTENDED moria1,moria2")
        self.send(b"250-SOCKSPORT=9050")
        self.send(b"250 ORPORT=0")
        return d

    def test_async_multiline(self):
        # same as above, but i think the 650's can be multline,
        # too. Like:
        # 650-CIRC 1000 EXTENDED moria1,moria2 0xBEEF
        # 650-EXTRAMAGIC=99
        # 650 ANONYMITY=high

        self.protocol._set_valid_events('CIRC')
        self.protocol.add_event_listener(
            'CIRC',
            CallbackChecker(
                "1000 EXTENDED moria1,moria2\nEXTRAMAGIC=99\nANONYMITY=high"
            )
        )
        self.send(b"250 OK")

        d = self.protocol.get_conf("SOCKSPORT ORPORT")
        d.addCallback(CallbackChecker({"ORPORT": "0", "SOCKSPORT": "9050"}))
        self.send(b"650-CIRC 1000 EXTENDED moria1,moria2")
        self.send(b"650-EXTRAMAGIC=99")
        self.send(b"650 ANONYMITY=high")
        self.send(b"250-SOCKSPORT=9050")
        self.send(b"250 ORPORT=0")
        return d

    def test_multiline_plus(self):
        """
        """

        d = self.protocol.get_info("FOO")
        d.addCallback(CallbackChecker({"FOO": "\na\nb\nc"}))
        self.send(b"250+FOO=")
        self.send(b"a")
        self.send(b"b")
        self.send(b"c")
        self.send(b".")
        self.send(b"250 OK")
        return d

    def test_multiline_plus_embedded_equals(self):
        """
        """

        d = self.protocol.get_info("FOO")
        d.addCallback(CallbackChecker({"FOO": "\na="}))
        self.send(b"250+FOO=")
        self.send(b"a=")
        self.send(b".")
        self.send(b"250 OK")
        return d

    def incremental_check(self, expected, actual):
        if '=' in actual:
            return
        self.assertEqual(expected, actual)

    def test_getinfo_incremental(self):
        d = self.protocol.get_info_incremental(
            "FOO",
            functools.partial(self.incremental_check, "bar")
        )
        self.send(b"250+FOO=")
        self.send(b"bar")
        self.send(b"bar")
        self.send(b".")
        self.send(b"250 OK")
        return d

    def test_getinfo_incremental_continuation(self):
        d = self.protocol.get_info_incremental(
            "FOO",
            functools.partial(self.incremental_check, "bar")
        )
        self.send(b"250-FOO=")
        self.send(b"250-bar")
        self.send(b"250-bar")
        self.send(b"250 OK")
        return d

    def test_getinfo_one_line(self):
        d = self.protocol.get_info(
            "foo",
        )
        self.send(b'250 foo=bar')
        d.addCallback(lambda _: functools.partial(self.incremental_check, "bar"))
        return d

    def test_getconf(self):
        d = self.protocol.get_conf("SOCKSPORT ORPORT")
        d.addCallback(CallbackChecker({'SocksPort': '9050', 'ORPort': '0'}))
        self.send(b"250-SocksPort=9050")
        self.send(b"250 ORPort=0")
        return d

    def test_getconf_raw(self):
        d = self.protocol.get_conf_raw("SOCKSPORT ORPORT")
        d.addCallback(CallbackChecker('SocksPort=9050\nORPort=0'))
        self.send(b"250-SocksPort=9050")
        self.send(b"250 ORPort=0")
        return d

    def test_getconf_single(self):
        d = self.protocol.get_conf_single("SOCKSPORT")
        d.addCallback(CallbackChecker('9050'))
        self.send(b"250 SocksPort=9050")
        return d

    def response_ok(self, v):
        self.assertEqual(v, '')

    def test_setconf(self):
        d = self.protocol.set_conf("foo", "bar").addCallback(
            functools.partial(self.response_ok)
        )
        self.send(b"250 OK")
        self._wait(d)
        self.assertEqual(self.transport.value(), b"SETCONF foo=bar\r\n")

    def test_setconf_with_space(self):
        d = self.protocol.set_conf("foo", "a value with a space")
        d.addCallback(functools.partial(self.response_ok))
        self.send(b"250 OK")
        self._wait(d)
        self.assertEqual(
            self.transport.value(),
            b'SETCONF foo="a value with a space"\r\n'
        )

    def test_setconf_multi(self):
        d = self.protocol.set_conf("foo", "bar", "baz", 1)
        self.send(b"250 OK")
        self._wait(d)
        self.assertEqual(
            self.transport.value(),
            b"SETCONF foo=bar baz=1\r\n",
        )

    def test_quit(self):
        d = self.protocol.quit()
        self.send(b"250 OK")
        self._wait(d)
        self.assertEqual(
            self.transport.value(),
            b"QUIT\r\n",
        )

    def test_dot(self):
        # just checking we don't expode
        self.protocol.graphviz_data()

    def test_debug(self):
        self.protocol.start_debug()
        self.assertTrue(exists('txtorcon-debug.log'))

    def error(self, failure):
        print("ERROR", failure)
        self.assertTrue(False)

    def test_twocommands(self):
        "Two commands on the wire before first response."
        d1 = self.protocol.get_conf("FOO")
        ht = {"a": "one", "b": "two"}
        d1.addCallback(CallbackChecker(ht)).addErrback(log.err)

        d2 = self.protocol.get_info_raw("BAR")
        d2.addCallback(CallbackChecker("bar")).addErrback(log.err)

        self.send(b"250-a=one")
        self.send(b"250-b=two")
        self.send(b"250 OK")
        self.send(b"250 bar")

        return d2

    def test_signal_error(self):
        try:
            self.protocol.signal('FOO')
            self.fail()
        except Exception as e:
            self.assertTrue('Invalid signal' in str(e))

    def test_signal(self):
        self.protocol.valid_signals = ['NEWNYM']
        self.protocol.signal('NEWNYM')
        self.assertEqual(
            self.transport.value(),
            b'SIGNAL NEWNYM\r\n',
        )

    def test_650_after_authenticate(self):
        self.protocol._set_valid_events('CONF_CHANGED')
        self.protocol.add_event_listener(
            'CONF_CHANGED',
            CallbackChecker("Foo=bar")
        )
        self.send(b"250 OK")

        self.send(b"650-CONF_CHANGED")
        self.send(b"650-Foo=bar")

    def test_notify_after_getinfo(self):
        self.protocol._set_valid_events('CIRC')
        self.protocol.add_event_listener(
            'CIRC',
            CallbackChecker("1000 EXTENDED moria1,moria2")
        )
        self.send(b"250 OK")

        d = self.protocol.get_info("a")
        d.addCallback(CallbackChecker({'a': 'one'})).addErrback(self.fail)
        self.send(b"250-a=one")
        self.send(b"250 OK")
        self.send(b"650 CIRC 1000 EXTENDED moria1,moria2")
        return d

    def test_notify_error(self):
        self.protocol._set_valid_events('CIRC')
        self.send(b"650 CIRC 1000 EXTENDED moria1,moria2")

    def test_getinfo(self):
        d = self.protocol.get_info("version")
        d.addCallback(CallbackChecker({'version': '0.2.2.34'}))
        d.addErrback(self.fail)

        self.send(b"250-version=0.2.2.34")
        self.send(b"250 OK")

        self.assertEqual(
            self.transport.value(),
            b"GETINFO version\r\n",
        )
        return d

    def test_getinfo_single(self):
        d = self.protocol.get_info_single("version")
        d.addCallback(CallbackChecker('0.2.2.34'))
        d.addErrback(self.fail)

        self.send(b"250-version=0.2.2.34")
        self.send(b"250 OK")

        self.assertEqual(
            self.transport.value(),
            b"GETINFO version\r\n",
        )
        return d

    def test_getinfo_for_descriptor(self):
        descriptor_info = b"""250+desc/name/moria1=
router moria1 128.31.0.34 9101 0 9131
platform Tor 0.2.5.0-alpha-dev on Linux
protocols Link 1 2 Circuit 1
published 2013-07-05 23:48:52
fingerprint 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31
uptime 1818933
bandwidth 512000 62914560 1307929
extra-info-digest 17D0142F6EBCDF60160EB1794FA6C9717D581F8C
caches-extra-info
onion-key
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBALzd4bhz1usB7wpoaAvP+BBOnNIk7mByAKV6zvyQ0p1M09oEmxPMc3qD
AAm276oJNf0eq6KWC6YprzPWFsXEIdXSqA6RWXCII1JG/jOoy6nt478BkB8TS9I9
1MJW27ppRaqnLiTmBmM+qzrsgJGwf+onAgUKKH2GxlVgahqz8x6xAgMBAAE=
-----END RSA PUBLIC KEY-----
signing-key
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBALtJ9uD7cD7iHjqNA3AgsX9prES5QN+yFQyr2uOkxzhvunnaf6SNhzWW
bkfylnMrRm/qCz/czcjZO6N6EKHcXmypehvP566B7gAQ9vDsb+l7VZVWgXvzNc2s
tl3P7qpC08rgyJh1GqmtQTCesIDqkEyWxwToympCt09ZQRq+fIttAgMBAAE=
-----END RSA PUBLIC KEY-----
hidden-service-dir
contact 1024D/28988BF5 arma mit edu
ntor-onion-key 9ZVjNkf/iLEnD685SpC5kcDytQ7u5ViiI9JOftdbE0k=
reject *:*
router-signature
-----BEGIN SIGNATURE-----
Y8Tj2e7mPbFJbguulkPEBVYzyO57p4btpWEXvRMD6vxIh/eyn25pehg5dUVBtZlL
iO3EUE0AEYah2W9gdz8t+i3Dtr0zgqLS841GC/TyDKCm+MKmN8d098qnwK0NGF9q
01NZPuSqXM1b6hnl2espFzL7XL8XEGRU+aeg+f/ukw4=
-----END SIGNATURE-----
.
250 OK"""
        d = self.protocol.get_info("desc/name/moria1")
        d.addCallback(CallbackChecker({'desc/name/moria1': '\n' + '\n'.join(descriptor_info.decode('ascii').split('\n')[1:-2])}))
        d.addErrback(self.fail)

        for line in descriptor_info.split(b'\n'):
            self.send(line)
        return d

    def test_getinfo_multiline(self):
        descriptor_info = b"""250+desc/name/moria1=
router moria1 128.31.0.34 9101 0 9131
platform Tor 0.2.5.0-alpha-dev on Linux
.
250 OK"""
        d = self.protocol.get_info("desc/name/moria1")
        gold = "\nrouter moria1 128.31.0.34 9101 0 9131\nplatform Tor 0.2.5.0-alpha-dev on Linux"
        d.addCallback(CallbackChecker({'desc/name/moria1': gold}))
        d.addErrback(self.fail)

        for line in descriptor_info.split(b'\n'):
            self.send(line)
        return d

    def test_addevent(self):
        self.protocol._set_valid_events('FOO BAR')

        self.protocol.add_event_listener('FOO', lambda _: None)
        # is it dangerous/ill-advised to depend on internal state of
        # class under test?
        d = self.protocol.defer
        self.send(b"250 OK")
        self._wait(d)
        self.assertEqual(
            self.transport.value().split(b'\r\n')[-2],
            b"SETEVENTS FOO"
        )
        self.transport.clear()

        self.protocol.add_event_listener('BAR', lambda _: None)
        d = self.protocol.defer
        self.send(b"250 OK")
        self.assertTrue(
            self.transport.value() == b"SETEVENTS FOO BAR\r\n" or
            self.transport.value() == b"SETEVENTS BAR FOO\r\n"
        )
        self._wait(d)

        try:
            self.protocol.add_event_listener(
                'SOMETHING_INVALID', lambda _: None
            )
            self.assertTrue(False)
        except Exception:
            pass

    def test_eventlistener(self):
        self.protocol._set_valid_events('STREAM')

        class EventListener(object):
            stream_events = 0

            def __call__(self, data):
                self.stream_events += 1

        listener = EventListener()
        self.protocol.add_event_listener('STREAM', listener)

        d = self.protocol.defer
        self.send(b"250 OK")
        self._wait(d)
        self.send(b"650 STREAM 1234 NEW 4321 1.2.3.4:555 REASON=MISC")
        self.send(b"650 STREAM 2345 NEW 4321 2.3.4.5:666 REASON=MISC")
        self.assertEqual(listener.stream_events, 2)

    def test_eventlistener_error(self):
        self.protocol._set_valid_events('STREAM')

        class EventListener(object):
            stream_events = 0
            do_error = False

            def __call__(self, data):
                self.stream_events += 1
                if self.do_error:
                    raise Exception("the bad thing happened")

        # we make sure the first listener has the errors to prove the
        # second one still gets called.
        listener0 = EventListener()
        listener0.do_error = True
        listener1 = EventListener()
        self.protocol.add_event_listener('STREAM', listener0)
        self.protocol.add_event_listener('STREAM', listener1)

        d = self.protocol.defer
        self.send(b"250 OK")
        self._wait(d)
        self.send(b"650 STREAM 1234 NEW 4321 1.2.3.4:555 REASON=MISC")
        self.send(b"650 STREAM 2345 NEW 4321 2.3.4.5:666 REASON=MISC")
        self.assertEqual(listener0.stream_events, 2)
        self.assertEqual(listener1.stream_events, 2)

        # should have logged the two errors
        logged = self.flushLoggedErrors()
        self.assertEqual(2, len(logged))
        self.assertTrue("the bad thing happened" in str(logged[0]))
        self.assertTrue("the bad thing happened" in str(logged[1]))

    def test_remove_eventlistener(self):
        self.protocol._set_valid_events('STREAM')

        class EventListener(object):
            stream_events = 0

            def __call__(self, data):
                self.stream_events += 1

        listener = EventListener()
        self.protocol.add_event_listener('STREAM', listener)
        self.assertEqual(self.transport.value(), b'SETEVENTS STREAM\r\n')
        self.protocol.lineReceived(b"250 OK")
        self.transport.clear()
        self.protocol.remove_event_listener('STREAM', listener)
        self.assertEqual(self.transport.value(), b'SETEVENTS \r\n')

    def test_remove_eventlistener_multiple(self):
        self.protocol._set_valid_events('STREAM')

        class EventListener(object):
            stream_events = 0

            def __call__(self, data):
                self.stream_events += 1

        listener0 = EventListener()
        listener1 = EventListener()
        self.protocol.add_event_listener('STREAM', listener0)
        self.assertEqual(self.transport.value(), b'SETEVENTS STREAM\r\n')
        self.protocol.lineReceived(b"250 OK")
        self.transport.clear()
        # add another one, shouldn't issue a tor command
        self.protocol.add_event_listener('STREAM', listener1)
        self.assertEqual(self.transport.value(), b'')

        # remove one, should still not issue a tor command
        self.protocol.remove_event_listener('STREAM', listener0)
        self.assertEqual(self.transport.value(), b'')

        # remove the other one, NOW should issue a command
        self.protocol.remove_event_listener('STREAM', listener1)
        self.assertEqual(self.transport.value(), b'SETEVENTS \r\n')

        # try removing invalid event
        try:
            self.protocol.remove_event_listener('FOO', listener0)
            self.fail()
        except Exception as e:
            self.assertTrue('FOO' in str(e))

    def test_continuation_line(self):
        d = self.protocol.get_info_raw("key")

        def check_continuation(v):
            self.assertEqual(v, "key=\nvalue0\nvalue1")
        d.addCallback(check_continuation)

        self.send(b"250+key=")
        self.send(b"value0")
        self.send(b"value1")
        self.send(b".")
        self.send(b"250 OK")

        return d

    def test_newdesc(self):
        """
        FIXME: this test is now maybe a little silly, it's just testing
        multiline GETINFO...  (Real test is in
        TorStateTests.test_newdesc_parse)
        """

        self.protocol.get_info_raw('ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A')
        d = self.protocol.defer
        d.addCallback(CallbackChecker("""ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A=
r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80
s Exit Fast Guard HSDir Named Running Stable V2Dir Valid
w Bandwidth=518000
p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888"""))

        self.send(b"250+ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A=")
        self.send(b"r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80")
        self.send(b"s Exit Fast Guard HSDir Named Running Stable V2Dir Valid")
        self.send(b"w Bandwidth=518000")
        self.send(b"p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888")
        self.send(b".")
        self.send(b"250 OK")

        return d

    def test_plus_line_no_command(self):
        self.protocol.lineReceived(b"650+NS\r\n")
        self.protocol.lineReceived(b"r Gabor gFpAHsFOHGATy12ZUswRf0ZrqAU GG6GDp40cQfR3ODvkBT0r+Q09kw 2012-05-12 16:54:56 91.219.238.71 443 80\r\n")

    def test_minus_line_no_command(self):
        """
        haven't seen 600's use - "in the wild" but don't see why it's not
        possible
        """
        self.protocol._set_valid_events('NS')
        self.protocol.add_event_listener('NS', lambda _: None)
        self.protocol.lineReceived(b"650-NS\r\n")
        self.protocol.lineReceived(b"650 OK\r\n")
Ejemplo n.º 42
0
 def setUp(self):
     self.protocol = TorControlProtocol(lambda: defer.succeed('foo'))
     self.transport = proto_helpers.StringTransport()
Ejemplo n.º 43
0
class LaunchTorTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = do_nothing
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)

    def setup_complete_no_errors(self, proto):
        todel = proto.to_delete
        self.assertTrue(len(todel) > 0)
        proto.processEnded(Failure(error.ProcessDone(0)))
        self.assertTrue(len(proto.to_delete) == 0)
        for f in todel:
            self.assertTrue(not os.path.exists(f))

    def setup_complete_fails(self, proto):
        todel = proto.to_delete
        self.assertTrue(len(todel) > 0)
        ## the "12" is just arbitrary, we check it later in the error-message
        proto.processEnded(Failure(error.ProcessTerminated(12, None, 'statusFIXME')))
        self.assertTrue(len(proto.to_delete) == 0)
        for f in todel:
            self.assertTrue(not os.path.exists(f))

    def test_basic_launch(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        class OnProgress:
            def __init__(self, test, expected):
                self.test = test
                self.expected = expected

            def __call__(self, percent, tag, summary):
                self.test.assertTrue(self.expected[0] == (percent, tag, summary))
                self.expected = self.expected[1:]
                self.test.assertTrue('"' not in summary)
                self.test.assertTrue(percent >= 0 and percent <= 100)            
            
        def on_protocol(proto):
            proto.outReceived('Bootstrapped 100%\n')
            proto.progress = OnProgress(self, [(90, 'circuit_create', 'Establishing a Tor circuit'),
                                               (100, 'done', 'Done')])

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(connector, self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator)
        d.addCallback(self.setup_complete_no_errors)
        return d
        
    def check_setup_failure(self, fail):
        self.assertTrue("with error-code 12" in fail.getErrorMessage())
        ## cancel the errback chain, we wanted this
        return None
                
    def test_launch_tor_fails(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap
            
        def on_protocol(proto):
            proto.outReceived('Bootstrapped 100%\n')
            
        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(connector, self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator)
        d.addCallback(self.setup_complete_fails)
        d.addErrback(self.check_setup_failure)
        return d

    def setup_fails_stderr(self, fail):
        self.assertTrue('Something went horribly wrong!' in fail.getErrorMessage())
        ## cancel the errback chain, we wanted this
        return None
        
    def test_tor_produces_stderr_output(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap
            
        def on_protocol(proto):
            proto.errReceived('Something went horribly wrong!\n')
            
        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(connector, self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator)
        d.addCallback(self.fail)        # should't get callback
        d.addErrback(self.setup_fails_stderr)
        return d
        
    def test_tor_connection_fails(self):
        """
        We fail to connect once, and then successfully connect --
        testing whether we're retrying properly on each Bootstrapped
        line from stdout.
        """
        
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        class Connector:
            count = 0

            def __call__(self, proto, trans):
                self.count += 1
                if self.count < 2:
                    return defer.fail(error.CannotListenError(None, None, None))

                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')
            proto.outReceived('Bootstrapped 100%\n')
            
        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator)
        d.addCallback(self.setup_complete_fails)
        d.addErrback(self.check_setup_failure)
        return d

    def confirm_progress(self, exp, *args, **kwargs):
        self.assertTrue(exp == args)
        self.got_progress = True
        
    def test_progress_updates(self):
        from txtorcon.torconfig import TorProcessProtocol

        self.got_progress = False;
        proto = TorProcessProtocol(None, functools.partial(self.confirm_progress,
                                                           (10, 'tag', 'summary')))
        proto.progress(10, 'tag', 'summary')
        self.assertTrue(self.got_progress)

    def test_status_updates(self):
        from txtorcon.torconfig import TorProcessProtocol

        proto = TorProcessProtocol(None)
        proto.status_client("NOTICE CONSENSUS_ARRIVED")
Ejemplo n.º 44
0
 def setUp(self):
     self.protocol = TorControlProtocol()
     self.protocol.connectionMade = do_nothing
     self.transport = proto_helpers.StringTransport()
     self.protocol.makeConnection(self.transport)
Ejemplo n.º 45
0
class LaunchTorTests(unittest.TestCase):

    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)
        self.clock = task.Clock()

    def setup_complete_with_timer(self, proto):
        proto._check_timeout.stop()
        proto.checkTimeout()

    def setup_complete_no_errors(self, proto, config, stdout, stderr):
        self.assertEqual("Bootstrapped 100%\n", stdout.getvalue())
        self.assertEqual("", stderr.getvalue())
        todel = proto.to_delete
        self.assertTrue(len(todel) > 0)
        proto.processEnded(Failure(error.ProcessDone(0)))
        self.assertEqual(len(proto.to_delete), 0)
        for f in todel:
            self.assertTrue(not os.path.exists(f))
        self.assertEqual(proto._timeout_delayed_call, None)

        ## make sure we set up the config to track the created tor
        ## protocol connection
        self.assertEquals(config.protocol, proto.tor_protocol)

    def setup_complete_fails(self, proto, stdout, stderr):
        self.assertEqual("Bootstrapped 90%\n", stdout.getvalue())
        self.assertEqual("", stderr.getvalue())
        todel = proto.to_delete
        self.assertTrue(len(todel) > 0)
        ## the "12" is just arbitrary, we check it later in the error-message
        proto.processEnded(Failure(error.ProcessTerminated(12, None, 'statusFIXME')))
        self.assertEqual(1, len(self.flushLoggedErrors(RuntimeError)))
        self.assertEqual(len(proto.to_delete), 0)
        for f in todel:
            self.assertTrue(not os.path.exists(f))
        return None

    def test_basic_launch(self):
        config = TorConfig()
        config.ORPort = 1234
        config.SOCKSPort = 9999

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        class OnProgress:
            def __init__(self, test, expected):
                self.test = test
                self.expected = expected

            def __call__(self, percent, tag, summary):
                self.test.assertEqual(self.expected[0], (percent, tag, summary))
                self.expected = self.expected[1:]
                self.test.assertTrue('"' not in summary)
                self.test.assertTrue(percent >= 0 and percent <= 100)

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 100%\n')
            proto.progress = OnProgress(self, [(90, 'circuit_create', 'Establishing a Tor circuit'),
                                               (100, 'done', 'Done')])

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        fakeout = StringIO()
        fakeerr = StringIO()
        creator = functools.partial(connector, self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo', stdout=fakeout, stderr=fakeerr)
        d.addCallback(self.setup_complete_no_errors, config, fakeout, fakeerr)
        return d

    def check_setup_failure(self, fail):
        self.assertTrue("with error-code 12" in fail.getErrorMessage())
        ## cancel the errback chain, we wanted this
        return None

    def test_launch_tor_fails(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        fakeout = StringIO()
        fakeerr = StringIO()
        creator = functools.partial(connector, self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo', stdout=fakeout, stderr=fakeerr)
        d.addCallback(self.setup_complete_fails, fakeout, fakeerr)
        self.flushLoggedErrors(RuntimeError)
        return d

    def test_launch_with_timeout(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999
        timeout = 5

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        class OnProgress:
            def __init__(self, test, expected):
                self.test = test
                self.expected = expected

            def __call__(self, percent, tag, summary):
                self.test.assertEqual(self.expected[0], (percent, tag, summary))
                self.expected = self.expected[1:]
                self.test.assertTrue('"' not in summary)
                self.test.assertTrue(percent >= 0 and percent <= 100)

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 100%\n')

        trans = FakeProcessTransportNeverBootstraps()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(connector, self.protocol, self.transport)
        react = FakeReactor(self, trans, on_protocol)
        d = launch_tor(config, react, connection_creator=creator,
                       timeout=timeout, tor_binary='/bin/echo')
        # FakeReactor is a task.Clock subclass and +1 just to be sure
        react.advance(timeout + 1)

        self.assertTrue(d.called)
        self.assertTrue(d.result.getErrorMessage().strip().endswith('Tor was killed (TERM).'))
        self.flushLoggedErrors(RuntimeError)
        return self.assertFailure(d, RuntimeError)

    def test_launch_with_timeout_that_doesnt_expire(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999
        timeout = 5

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        class OnProgress:
            def __init__(self, test, expected):
                self.test = test
                self.expected = expected

            def __call__(self, percent, tag, summary):
                self.test.assertEqual(self.expected[0], (percent, tag, summary))
                self.expected = self.expected[1:]
                self.test.assertTrue('"' not in summary)
                self.test.assertTrue(percent >= 0 and percent <= 100)

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 100%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(connector, self.protocol, self.transport)
        react = FakeReactor(self, trans, on_protocol)
        d = launch_tor(config, react, connection_creator=creator,
                       timeout=timeout, tor_binary='/bin/echo')
        # FakeReactor is a task.Clock subclass and +1 just to be sure
        react.advance(timeout + 1)

        self.assertTrue(d.called)
        self.assertTrue(d.result.tor_protocol == self.protocol)

    def setup_fails_stderr(self, fail, stdout, stderr):
        self.assertEqual('', stdout.getvalue())
        self.assertEqual('Something went horribly wrong!\n', stderr.getvalue())
        self.assertTrue('Something went horribly wrong!' in fail.getErrorMessage())
        ## cancel the errback chain, we wanted this
        return None

    def test_tor_produces_stderr_output(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        def on_protocol(proto):
            proto.errReceived('Something went horribly wrong!\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        fakeout = StringIO()
        fakeerr = StringIO()
        creator = functools.partial(connector, self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol),
                       connection_creator=creator, tor_binary='/bin/echo',
                       stdout=fakeout, stderr=fakeerr)
        d.addCallback(self.fail)        # should't get callback
        d.addErrback(self.setup_fails_stderr, fakeout, fakeerr)
        self.assertFalse(self.protocol.on_disconnect)
        return d

    def test_tor_connection_fails(self):
        """
        We fail to connect once, and then successfully connect --
        testing whether we're retrying properly on each Bootstrapped
        line from stdout.
        """

        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        class Connector:
            count = 0

            def __call__(self, proto, trans):
                self.count += 1
                if self.count < 2:
                    return defer.fail(error.CannotListenError(None, None, None))

                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo')
        d.addCallback(self.setup_complete_fails)
        return self.assertFailure(d, Exception)

    def test_tor_connection_user_data_dir(self):
        """
        Test that we don't delete a user-supplied data directory.
        """

        config = TorConfig()
        config.OrPort = 1234

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')

        my_dir = tempfile.mkdtemp(prefix='tortmp')
        config.DataDirectory = my_dir
        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo')

        def still_have_data_dir(proto, tester):
            proto.cleanup()  # FIXME? not really unit-testy as this is sort of internal function
            tester.assertTrue(os.path.exists(my_dir))
            delete_file_or_tree(my_dir)

        d.addCallback(still_have_data_dir, self)
        d.addErrback(self.fail)
        return d

    def test_tor_connection_user_control_port(self):
        """
        Confirm we use a user-supplied control-port properly
        """

        config = TorConfig()
        config.OrPort = 1234
        config.ControlPort = 4321

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')
            proto.outReceived('Bootstrapped 100%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo')

        def check_control_port(proto, tester):
            ## we just want to ensure launch_tor() didn't mess with
            ## the controlport we set
            tester.assertEquals(config.ControlPort, 4321)

        d.addCallback(check_control_port, self)
        d.addErrback(self.fail)
        return d

    def test_tor_connection_default_control_port(self):
        """
        Confirm a default control-port is set if not user-supplied.
        """

        config = TorConfig()

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')
            proto.outReceived('Bootstrapped 100%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo')

        def check_control_port(proto, tester):
            ## ensure ControlPort was set to a default value
            tester.assertEquals(config.ControlPort, 9052)

        d.addCallback(check_control_port, self)
        d.addErrback(self.fail)
        return d

    def test_progress_updates(self):
        self.got_progress = False

        def confirm_progress(p, t, s):
            self.assertEqual(p, 10)
            self.assertEqual(t, 'tag')
            self.assertEqual(s, 'summary')
            self.got_progress = True
        process = TorProcessProtocol(None, confirm_progress)
        process.progress(10, 'tag', 'summary')
        self.assertTrue(self.got_progress)

    def test_status_updates(self):
        process = TorProcessProtocol(None)
        process.status_client("NOTICE CONSENSUS_ARRIVED")

    def test_tor_launch_success_then_shutdown(self):
        """
        There was an error where we double-callbacked a deferred,
        i.e. success and then shutdown. This repeats it.
        """
        process = TorProcessProtocol(None)
        process.status_client('STATUS_CLIENT BOOTSTRAP PROGRESS=100 TAG=foo SUMMARY=cabbage')
        self.assertEqual(None, process.connected_cb)

        class Value(object):
            exitCode = 123

        class Status(object):
            value = Value()
        process.processEnded(Status())
        self.assertEquals(len(self.flushLoggedErrors(RuntimeError)), 1)
Ejemplo n.º 46
0
        run_count = self.summary[self.input]
        delay = float(self.input.split("-")[1])/1000
        d = defer.Deferred()
        def callback():
            self.summary[self.input] += 1
            if run_count < 3:
                d.errback(Exception("Failing"))
            else:
                d.callback(self.summary[self.input])

        reactor.callLater(delay, callback)
        return d
"""

proto = MagicMock()
proto.tor_protocol = TorControlProtocol()

mock_TorState = MagicMock()
# We use the instance of mock_TorState so that the mock caching will
# return the same instance when TorState is created.
mts = mock_TorState()
mts.protocol.get_conf = lambda x: defer.succeed({'SocksPort': '4242'})
mts.post_bootstrap = defer.succeed(mts)

# Set the tor_protocol to be already fired
state = MagicMock()
proto.tor_protocol.post_bootstrap = defer.succeed(state)

mock_launch_tor = MagicMock()
mock_launch_tor.return_value = defer.succeed(proto)
Ejemplo n.º 47
0
 def setUp(self):
     self.protocol = TorControlProtocol()
     self.transport = proto_helpers.StringTransport()
Ejemplo n.º 48
0
 def setUp(self):
     self.protocol = TorControlProtocol()
     self.protocol.connectionMade = lambda: None
     self.transport = proto_helpers.StringTransport()
     self.protocol.makeConnection(self.transport)
     self.clock = task.Clock()
Ejemplo n.º 49
0
class ProtocolTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)

    def tearDown(self):
        self.protocol = None

    def send(self, line):
        assert type(line) == bytes
        self.protocol.dataReceived(line.strip() + b"\r\n")

    def test_statemachine_broadcast_no_code(self):
        try:
            self.protocol._broadcast_response("foo")
            self.fail()
        except RuntimeError as e:
            self.assertTrue('No code set yet' in str(e))

    def test_statemachine_broadcast_unknown_code(self):
        try:
            self.protocol.code = 999
            self.protocol._broadcast_response("foo")
            self.fail()
        except RuntimeError as e:
            self.assertTrue('Unknown code' in str(e))

    def test_statemachine_is_finish(self):
        self.assertTrue(not self.protocol._is_finish_line(''))
        self.assertTrue(self.protocol._is_finish_line('.'))
        self.assertTrue(self.protocol._is_finish_line('300 '))
        self.assertTrue(not self.protocol._is_finish_line('250-'))

    def test_statemachine_singleline(self):
        self.assertTrue(not self.protocol._is_single_line_response('foo'))

    def test_statemachine_continuation(self):
        try:
            self.protocol.code = 250
            self.protocol._is_continuation_line("123 ")
            self.fail()
        except RuntimeError as e:
            self.assertTrue('Unexpected code' in str(e))

    def test_statemachine_multiline(self):
        try:
            self.protocol.code = 250
            self.protocol._is_multi_line("123 ")
            self.fail()
        except RuntimeError as e:
            self.assertTrue('Unexpected code' in str(e))

    def test_response_with_no_request(self):
        with self.assertRaises(RuntimeError) as ctx:
            self.protocol.code = 200
            self.protocol._broadcast_response('200 OK')
        self.assertTrue("didn't issue a command" in str(ctx.exception))

    def auth_failed(self, msg):
        self.assertEqual(str(msg.value), '551 go away')
        self.got_auth_failed = True

    def test_authenticate_fail(self):
        self.got_auth_failed = False
        self.protocol._auth_failed = self.auth_failed

        self.protocol.password_function = lambda: 'foo'
        self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=HASHEDPASSWORD
VERSION Tor="0.2.2.35"
OK''')
        self.send(b'551 go away\r\n')
        self.assertTrue(self.got_auth_failed)

    def test_authenticate_no_auth_line(self):
        try:
            self.protocol._do_authenticate('''PROTOCOLINFO 1
FOOAUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE="/dev/null"
VERSION Tor="0.2.2.35"
OK''')
            self.assertTrue(False)
        except RuntimeError as e:
            self.assertTrue('find AUTH line' in str(e))

    def test_authenticate_not_enough_cookie_data(self):
        with tempfile.NamedTemporaryFile() as cookietmp:
            cookietmp.write(b'x' * 35)  # too much data
            cookietmp.flush()

            try:
                self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=COOKIE COOKIEFILE="%s"
VERSION Tor="0.2.2.35"
OK''' % cookietmp.name)
                self.assertTrue(False)
            except RuntimeError as e:
                self.assertTrue('cookie to be 32' in str(e))

    def test_authenticate_not_enough_safecookie_data(self):
        with tempfile.NamedTemporaryFile() as cookietmp:
            cookietmp.write(b'x' * 35)  # too much data
            cookietmp.flush()

            try:
                self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=SAFECOOKIE COOKIEFILE="%s"
VERSION Tor="0.2.2.35"
OK''' % cookietmp.name)
                self.assertTrue(False)
            except RuntimeError as e:
                self.assertTrue('cookie to be 32' in str(e))

    def test_authenticate_safecookie(self):
        with tempfile.NamedTemporaryFile() as cookietmp:
            cookiedata = bytes(bytearray([0] * 32))
            cookietmp.write(cookiedata)
            cookietmp.flush()

            self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=SAFECOOKIE COOKIEFILE="{}"
VERSION Tor="0.2.2.35"
OK'''.format(cookietmp.name))
            self.assertTrue(
                b'AUTHCHALLENGE SAFECOOKIE ' in self.transport.value())
            x = self.transport.value().split()[-1]
            client_nonce = a2b_hex(x)
            self.transport.clear()
            server_nonce = bytes(bytearray([0] * 32))
            server_hash = hmac_sha256(
                b"Tor safe cookie authentication server-to-controller hash",
                cookiedata + client_nonce + server_nonce,
            )

            self.send(b'250 AUTHCHALLENGE SERVERHASH=' +
                      base64.b16encode(server_hash) + b' SERVERNONCE=' +
                      base64.b16encode(server_nonce) + b'\r\n')
            self.assertTrue(b'AUTHENTICATE ' in self.transport.value())

    def test_authenticate_cookie_without_reading(self):
        server_nonce = bytes(bytearray([0] * 32))
        server_hash = bytes(bytearray([0] * 32))
        try:
            self.protocol._safecookie_authchallenge(
                '250 AUTHCHALLENGE SERVERHASH=%s SERVERNONCE=%s' %
                (base64.b16encode(server_hash),
                 base64.b16encode(server_nonce)))
            self.assertTrue(False)
        except RuntimeError as e:
            self.assertTrue('not read' in str(e))

    def test_authenticate_unexisting_cookie_file(self):
        unexisting_file = __file__ + "-unexisting"
        try:
            self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=COOKIE COOKIEFILE="%s"
VERSION Tor="0.2.2.35"
OK''' % unexisting_file)
            self.assertTrue(False)
        except RuntimeError:
            pass

    def test_authenticate_unexisting_safecookie_file(self):
        unexisting_file = __file__ + "-unexisting"
        try:
            self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=SAFECOOKIE COOKIEFILE="{}"
VERSION Tor="0.2.2.35"
OK'''.format(unexisting_file))
            self.assertTrue(False)
        except RuntimeError:
            pass

    def test_authenticate_dont_send_cookiefile(self):
        try:
            self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=SAFECOOKIE
VERSION Tor="0.2.2.35"
OK''')
            self.assertTrue(False)
        except RuntimeError:
            pass

    def test_authenticate_password_when_cookie_unavailable(self):
        unexisting_file = __file__ + "-unexisting"
        self.protocol.password_function = lambda: 'foo'
        self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=COOKIE,HASHEDPASSWORD COOKIEFILE="{}"
VERSION Tor="0.2.2.35"
OK'''.format(unexisting_file))
        self.assertEqual(
            self.transport.value(),
            b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n',
        )

    def test_authenticate_password_when_safecookie_unavailable(self):
        unexisting_file = __file__ + "-unexisting"
        self.protocol.password_function = lambda: 'foo'
        self.protocol._do_authenticate('''PROTOCOLINFO 1
AUTH METHODS=SAFECOOKIE,HASHEDPASSWORD COOKIEFILE="{}"
VERSION Tor="0.2.2.35"
OK'''.format(unexisting_file))
        self.assertEqual(
            self.transport.value(),
            b'AUTHENTICATE ' + b2a_hex(b'foo') + b'\r\n',
        )

    def test_authenticate_safecookie_wrong_hash(self):
        cookiedata = bytes(bytearray([0] * 32))
        server_nonce = bytes(bytearray([0] * 32))
        server_hash = bytes(bytearray([0] * 32))

        # pretend we already did PROTOCOLINFO and read the cookie
        # file
        self.protocol._cookie_data = cookiedata
        self.protocol.client_nonce = server_nonce  # all 0's anyway
        try:
            self.protocol._safecookie_authchallenge(
                '250 AUTHCHALLENGE SERVERHASH={} SERVERNONCE={}'.format(
                    b2a_hex(server_hash).decode('ascii'),
                    b2a_hex(server_nonce).decode('ascii'),
                ))
            self.assertTrue(False)
        except RuntimeError as e:
            self.assertTrue('hash not expected' in str(e))

    def confirm_version_events(self, arg):
        self.assertEqual(self.protocol.version, 'foo')
        events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL'.split(
        )
        self.assertEqual(len(self.protocol.valid_events), len(events))
        self.assertTrue(all(x in self.protocol.valid_events for x in events))

    def test_bootstrap_callback(self):
        d = self.protocol.post_bootstrap
        d.addCallback(CallbackChecker(self.protocol))
        d.addCallback(self.confirm_version_events)

        events = b'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL'
        self.protocol._bootstrap()

        # answer all the requests generated by boostrapping etc.
        self.send(b"250-signal/names=")
        self.send(b"250 OK")

        self.send(b"250-version=foo")
        self.send(b"250 OK")

        self.send(b"250-events/names=" + events)
        self.send(b"250 OK")

        self.send(b"250 OK")  # for USEFEATURE

        return d

    def test_bootstrap_tor_does_not_support_signal_names(self):
        self.protocol._bootstrap()
        self.send(b'552 Unrecognized key "signal/names"')
        valid_signals = ["RELOAD", "DUMP", "DEBUG", "NEWNYM", "CLEARDNSCACHE"]
        self.assertEqual(self.protocol.valid_signals, valid_signals)

    def test_async(self):
        """
        test the example from control-spec.txt to see that we
        handle interleaved async notifications properly.
        """
        self.protocol._set_valid_events('CIRC')
        self.protocol.add_event_listener('CIRC', lambda _: None)
        self.send(b"250 OK")

        d = self.protocol.get_conf("SOCKSPORT ORPORT")
        self.send(b"650 CIRC 1000 EXTENDED moria1,moria2")
        self.send(b"250-SOCKSPORT=9050")
        self.send(b"250 ORPORT=0")
        return d

    def test_async_multiline(self):
        # same as above, but i think the 650's can be multline,
        # too. Like:
        # 650-CIRC 1000 EXTENDED moria1,moria2 0xBEEF
        # 650-EXTRAMAGIC=99
        # 650 ANONYMITY=high

        self.protocol._set_valid_events('CIRC')
        self.protocol.add_event_listener(
            'CIRC',
            CallbackChecker(
                "1000 EXTENDED moria1,moria2\nEXTRAMAGIC=99\nANONYMITY=high"))
        self.send(b"250 OK")

        d = self.protocol.get_conf("SOCKSPORT ORPORT")
        d.addCallback(CallbackChecker({"ORPORT": "0", "SOCKSPORT": "9050"}))
        self.send(b"650-CIRC 1000 EXTENDED moria1,moria2")
        self.send(b"650-EXTRAMAGIC=99")
        self.send(b"650 ANONYMITY=high")
        self.send(b"250-SOCKSPORT=9050")
        self.send(b"250 ORPORT=0")
        return d

    def test_multiline_plus(self):
        """
        """

        d = self.protocol.get_info("FOO")
        d.addCallback(CallbackChecker({"FOO": "\na\nb\nc"}))
        self.send(b"250+FOO=")
        self.send(b"a")
        self.send(b"b")
        self.send(b"c")
        self.send(b".")
        self.send(b"250 OK")
        return d

    def test_multiline_plus_embedded_equals(self):
        """
        """

        d = self.protocol.get_info("FOO")
        d.addCallback(CallbackChecker({"FOO": "\na="}))
        self.send(b"250+FOO=")
        self.send(b"a=")
        self.send(b".")
        self.send(b"250 OK")
        return d

    def incremental_check(self, expected, actual):
        if '=' in actual:
            return
        self.assertEqual(expected, actual)

    def test_getinfo_incremental(self):
        d = self.protocol.get_info_incremental(
            "FOO", functools.partial(self.incremental_check, "bar"))
        self.send(b"250+FOO=")
        self.send(b"bar")
        self.send(b"bar")
        self.send(b".")
        self.send(b"250 OK")
        return d

    def test_getinfo_incremental_continuation(self):
        d = self.protocol.get_info_incremental(
            "FOO", functools.partial(self.incremental_check, "bar"))
        self.send(b"250-FOO=")
        self.send(b"250-bar")
        self.send(b"250-bar")
        self.send(b"250 OK")
        return d

    def test_getinfo_one_line(self):
        d = self.protocol.get_info("foo", )
        self.send(b'250 foo=bar')
        d.addCallback(
            lambda _: functools.partial(self.incremental_check, "bar"))
        return d

    def test_getconf(self):
        d = self.protocol.get_conf("SOCKSPORT ORPORT")
        d.addCallback(CallbackChecker({'SocksPort': '9050', 'ORPort': '0'}))
        self.send(b"250-SocksPort=9050")
        self.send(b"250 ORPort=0")
        return d

    def test_getconf_raw(self):
        d = self.protocol.get_conf_raw("SOCKSPORT ORPORT")
        d.addCallback(CallbackChecker('SocksPort=9050\nORPort=0'))
        self.send(b"250-SocksPort=9050")
        self.send(b"250 ORPort=0")
        return d

    def test_getconf_single(self):
        d = self.protocol.get_conf_single("SOCKSPORT")
        d.addCallback(CallbackChecker('9050'))
        self.send(b"250 SocksPort=9050")
        return d

    def response_ok(self, v):
        self.assertEqual(v, '')

    def test_setconf(self):
        d = self.protocol.set_conf("foo", "bar").addCallback(
            functools.partial(self.response_ok))
        self.send(b"250 OK")
        self._wait(d)
        self.assertEqual(self.transport.value(), b"SETCONF foo=bar\r\n")

    def test_setconf_with_space(self):
        d = self.protocol.set_conf("foo", "a value with a space")
        d.addCallback(functools.partial(self.response_ok))
        self.send(b"250 OK")
        self._wait(d)
        self.assertEqual(self.transport.value(),
                         b'SETCONF foo="a value with a space"\r\n')

    def test_setconf_multi(self):
        d = self.protocol.set_conf("foo", "bar", "baz", 1)
        self.send(b"250 OK")
        self._wait(d)
        self.assertEqual(
            self.transport.value(),
            b"SETCONF foo=bar baz=1\r\n",
        )

    def test_quit(self):
        d = self.protocol.quit()
        self.send(b"250 OK")
        self._wait(d)
        self.assertEqual(
            self.transport.value(),
            b"QUIT\r\n",
        )

    def test_dot(self):
        # just checking we don't expode
        self.protocol.graphviz_data()

    def test_debug(self):
        self.protocol.start_debug()
        self.assertTrue(exists('txtorcon-debug.log'))

    def error(self, failure):
        print("ERROR", failure)
        self.assertTrue(False)

    def test_twocommands(self):
        "Two commands on the wire before first response."
        d1 = self.protocol.get_conf("FOO")
        ht = {"a": "one", "b": "two"}
        d1.addCallback(CallbackChecker(ht)).addErrback(log.err)

        d2 = self.protocol.get_info_raw("BAR")
        d2.addCallback(CallbackChecker("bar")).addErrback(log.err)

        self.send(b"250-a=one")
        self.send(b"250-b=two")
        self.send(b"250 OK")
        self.send(b"250 bar")

        return d2

    def test_signal_error(self):
        try:
            self.protocol.signal('FOO')
            self.fail()
        except Exception as e:
            self.assertTrue('Invalid signal' in str(e))

    def test_signal(self):
        self.protocol.valid_signals = ['NEWNYM']
        self.protocol.signal('NEWNYM')
        self.assertEqual(
            self.transport.value(),
            b'SIGNAL NEWNYM\r\n',
        )

    def test_650_after_authenticate(self):
        self.protocol._set_valid_events('CONF_CHANGED')
        self.protocol.add_event_listener('CONF_CHANGED',
                                         CallbackChecker("Foo=bar"))
        self.send(b"250 OK")

        self.send(b"650-CONF_CHANGED")
        self.send(b"650-Foo=bar")

    def test_notify_after_getinfo(self):
        self.protocol._set_valid_events('CIRC')
        self.protocol.add_event_listener(
            'CIRC', CallbackChecker("1000 EXTENDED moria1,moria2"))
        self.send(b"250 OK")

        d = self.protocol.get_info("a")
        d.addCallback(CallbackChecker({'a': 'one'})).addErrback(self.fail)
        self.send(b"250-a=one")
        self.send(b"250 OK")
        self.send(b"650 CIRC 1000 EXTENDED moria1,moria2")
        return d

    def test_notify_error(self):
        self.protocol._set_valid_events('CIRC')
        self.send(b"650 CIRC 1000 EXTENDED moria1,moria2")

    def test_getinfo(self):
        d = self.protocol.get_info("version")
        d.addCallback(CallbackChecker({'version': '0.2.2.34'}))
        d.addErrback(self.fail)

        self.send(b"250-version=0.2.2.34")
        self.send(b"250 OK")

        self.assertEqual(
            self.transport.value(),
            b"GETINFO version\r\n",
        )
        return d

    def test_getinfo_single(self):
        d = self.protocol.get_info_single("version")
        d.addCallback(CallbackChecker('0.2.2.34'))
        d.addErrback(self.fail)

        self.send(b"250-version=0.2.2.34")
        self.send(b"250 OK")

        self.assertEqual(
            self.transport.value(),
            b"GETINFO version\r\n",
        )
        return d

    def test_getinfo_for_descriptor(self):
        descriptor_info = b"""250+desc/name/moria1=
router moria1 128.31.0.34 9101 0 9131
platform Tor 0.2.5.0-alpha-dev on Linux
protocols Link 1 2 Circuit 1
published 2013-07-05 23:48:52
fingerprint 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31
uptime 1818933
bandwidth 512000 62914560 1307929
extra-info-digest 17D0142F6EBCDF60160EB1794FA6C9717D581F8C
caches-extra-info
onion-key
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBALzd4bhz1usB7wpoaAvP+BBOnNIk7mByAKV6zvyQ0p1M09oEmxPMc3qD
AAm276oJNf0eq6KWC6YprzPWFsXEIdXSqA6RWXCII1JG/jOoy6nt478BkB8TS9I9
1MJW27ppRaqnLiTmBmM+qzrsgJGwf+onAgUKKH2GxlVgahqz8x6xAgMBAAE=
-----END RSA PUBLIC KEY-----
signing-key
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBALtJ9uD7cD7iHjqNA3AgsX9prES5QN+yFQyr2uOkxzhvunnaf6SNhzWW
bkfylnMrRm/qCz/czcjZO6N6EKHcXmypehvP566B7gAQ9vDsb+l7VZVWgXvzNc2s
tl3P7qpC08rgyJh1GqmtQTCesIDqkEyWxwToympCt09ZQRq+fIttAgMBAAE=
-----END RSA PUBLIC KEY-----
hidden-service-dir
contact 1024D/28988BF5 arma mit edu
ntor-onion-key 9ZVjNkf/iLEnD685SpC5kcDytQ7u5ViiI9JOftdbE0k=
reject *:*
router-signature
-----BEGIN SIGNATURE-----
Y8Tj2e7mPbFJbguulkPEBVYzyO57p4btpWEXvRMD6vxIh/eyn25pehg5dUVBtZlL
iO3EUE0AEYah2W9gdz8t+i3Dtr0zgqLS841GC/TyDKCm+MKmN8d098qnwK0NGF9q
01NZPuSqXM1b6hnl2espFzL7XL8XEGRU+aeg+f/ukw4=
-----END SIGNATURE-----
.
250 OK"""
        d = self.protocol.get_info("desc/name/moria1")
        d.addCallback(
            CallbackChecker({
                'desc/name/moria1':
                '\n' +
                '\n'.join(descriptor_info.decode('ascii').split('\n')[1:-2])
            }))
        d.addErrback(self.fail)

        for line in descriptor_info.split(b'\n'):
            self.send(line)
        return d

    def test_getinfo_multiline(self):
        descriptor_info = b"""250+desc/name/moria1=
router moria1 128.31.0.34 9101 0 9131
platform Tor 0.2.5.0-alpha-dev on Linux
.
250 OK"""
        d = self.protocol.get_info("desc/name/moria1")
        gold = "\nrouter moria1 128.31.0.34 9101 0 9131\nplatform Tor 0.2.5.0-alpha-dev on Linux"
        d.addCallback(CallbackChecker({'desc/name/moria1': gold}))
        d.addErrback(self.fail)

        for line in descriptor_info.split(b'\n'):
            self.send(line)
        return d

    def test_addevent(self):
        self.protocol._set_valid_events('FOO BAR')

        self.protocol.add_event_listener('FOO', lambda _: None)
        # is it dangerous/ill-advised to depend on internal state of
        # class under test?
        d = self.protocol.defer
        self.send(b"250 OK")
        self._wait(d)
        self.assertEqual(self.transport.value().split(b'\r\n')[-2],
                         b"SETEVENTS FOO")
        self.transport.clear()

        self.protocol.add_event_listener('BAR', lambda _: None)
        d = self.protocol.defer
        self.send(b"250 OK")
        self.assertTrue(self.transport.value() == b"SETEVENTS FOO BAR\r\n"
                        or self.transport.value() == b"SETEVENTS BAR FOO\r\n")
        self._wait(d)

        try:
            self.protocol.add_event_listener('SOMETHING_INVALID',
                                             lambda _: None)
            self.assertTrue(False)
        except Exception:
            pass

    def test_eventlistener(self):
        self.protocol._set_valid_events('STREAM')

        class EventListener(object):
            stream_events = 0

            def __call__(self, data):
                self.stream_events += 1

        listener = EventListener()
        self.protocol.add_event_listener('STREAM', listener)

        d = self.protocol.defer
        self.send(b"250 OK")
        self._wait(d)
        self.send(b"650 STREAM 1234 NEW 4321 1.2.3.4:555 REASON=MISC")
        self.send(b"650 STREAM 2345 NEW 4321 2.3.4.5:666 REASON=MISC")
        self.assertEqual(listener.stream_events, 2)

    def test_eventlistener_error(self):
        self.protocol._set_valid_events('STREAM')

        class EventListener(object):
            stream_events = 0
            do_error = False

            def __call__(self, data):
                self.stream_events += 1
                if self.do_error:
                    raise Exception("the bad thing happened")

        # we make sure the first listener has the errors to prove the
        # second one still gets called.
        listener0 = EventListener()
        listener0.do_error = True
        listener1 = EventListener()
        self.protocol.add_event_listener('STREAM', listener0)
        self.protocol.add_event_listener('STREAM', listener1)

        d = self.protocol.defer
        self.send(b"250 OK")
        self._wait(d)
        self.send(b"650 STREAM 1234 NEW 4321 1.2.3.4:555 REASON=MISC")
        self.send(b"650 STREAM 2345 NEW 4321 2.3.4.5:666 REASON=MISC")
        self.assertEqual(listener0.stream_events, 2)
        self.assertEqual(listener1.stream_events, 2)

        # should have logged the two errors
        logged = self.flushLoggedErrors()
        self.assertEqual(2, len(logged))
        self.assertTrue("the bad thing happened" in str(logged[0]))
        self.assertTrue("the bad thing happened" in str(logged[1]))

    def test_remove_eventlistener(self):
        self.protocol._set_valid_events('STREAM')

        class EventListener(object):
            stream_events = 0

            def __call__(self, data):
                self.stream_events += 1

        listener = EventListener()
        self.protocol.add_event_listener('STREAM', listener)
        self.assertEqual(self.transport.value(), b'SETEVENTS STREAM\r\n')
        self.protocol.lineReceived(b"250 OK")
        self.transport.clear()
        self.protocol.remove_event_listener('STREAM', listener)
        self.assertEqual(self.transport.value(), b'SETEVENTS \r\n')

    def test_remove_eventlistener_multiple(self):
        self.protocol._set_valid_events('STREAM')

        class EventListener(object):
            stream_events = 0

            def __call__(self, data):
                self.stream_events += 1

        listener0 = EventListener()
        listener1 = EventListener()
        self.protocol.add_event_listener('STREAM', listener0)
        self.assertEqual(self.transport.value(), b'SETEVENTS STREAM\r\n')
        self.protocol.lineReceived(b"250 OK")
        self.transport.clear()
        # add another one, shouldn't issue a tor command
        self.protocol.add_event_listener('STREAM', listener1)
        self.assertEqual(self.transport.value(), b'')

        # remove one, should still not issue a tor command
        self.protocol.remove_event_listener('STREAM', listener0)
        self.assertEqual(self.transport.value(), b'')

        # remove the other one, NOW should issue a command
        self.protocol.remove_event_listener('STREAM', listener1)
        self.assertEqual(self.transport.value(), b'SETEVENTS \r\n')

        # try removing invalid event
        try:
            self.protocol.remove_event_listener('FOO', listener0)
            self.fail()
        except Exception as e:
            self.assertTrue('FOO' in str(e))

    def test_continuation_line(self):
        d = self.protocol.get_info_raw("key")

        def check_continuation(v):
            self.assertEqual(v, "key=\nvalue0\nvalue1")

        d.addCallback(check_continuation)

        self.send(b"250+key=")
        self.send(b"value0")
        self.send(b"value1")
        self.send(b".")
        self.send(b"250 OK")

        return d

    def test_newdesc(self):
        """
        FIXME: this test is now maybe a little silly, it's just testing
        multiline GETINFO...  (Real test is in
        TorStateTests.test_newdesc_parse)
        """

        self.protocol.get_info_raw(
            'ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A')
        d = self.protocol.defer
        d.addCallback(
            CallbackChecker("""ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A=
r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80
s Exit Fast Guard HSDir Named Running Stable V2Dir Valid
w Bandwidth=518000
p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888"""
                            ))

        self.send(b"250+ns/id/624926802351575FF7E4E3D60EFA3BFB56E67E8A=")
        self.send(
            b"r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80"
        )
        self.send(b"s Exit Fast Guard HSDir Named Running Stable V2Dir Valid")
        self.send(b"w Bandwidth=518000")
        self.send(
            b"p accept 43,53,79-81,110,143,194,220,443,953,989-990,993,995,1194,1293,1723,1863,2082-2083,2086-2087,2095-2096,3128,4321,5050,5190,5222-5223,6679,6697,7771,8000,8008,8080-8081,8090,8118,8123,8181,8300,8443,8888"
        )
        self.send(b".")
        self.send(b"250 OK")

        return d

    def test_plus_line_no_command(self):
        self.protocol.lineReceived(b"650+NS\r\n")
        self.protocol.lineReceived(
            b"r Gabor gFpAHsFOHGATy12ZUswRf0ZrqAU GG6GDp40cQfR3ODvkBT0r+Q09kw 2012-05-12 16:54:56 91.219.238.71 443 80\r\n"
        )

    def test_minus_line_no_command(self):
        """
        haven't seen 600's use - "in the wild" but don't see why it's not
        possible
        """
        self.protocol._set_valid_events('NS')
        self.protocol.add_event_listener('NS', lambda _: None)
        self.protocol.lineReceived(b"650-NS\r\n")
        self.protocol.lineReceived(b"650 OK\r\n")
Ejemplo n.º 50
0
class StateTests(unittest.TestCase):

    def setUp(self):
        self.protocol = TorControlProtocol()
        self.state = TorState(self.protocol)
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)

    def test_close_stream_with_attacher(self):
        class MyAttacher(object):
            implements(IStreamAttacher)

            def __init__(self):
                self.streams = []

            def attach_stream(self, stream, circuits):
                self.streams.append(stream)
                return None

        attacher = MyAttacher()
        self.state.set_attacher(attacher, FakeReactor(self))
        self.state._stream_update("76 CLOSED 0 www.example.com:0 REASON=DONE")

    def test_stream_update(self):
        ## we use a circuit ID of 0 so it doesn't try to look anything up but it's
        ## not really correct to have a  SUCCEEDED w/o a valid circuit, I don't think
        self.state._stream_update('1610 SUCCEEDED 0 74.125.224.243:80')
        self.assertTrue(1610 in self.state.streams)

    def test_single_streams(self):
        self.state.circuits[496] = FakeCircuit(496)
        self.state._stream_status('stream-status=123 SUCCEEDED 496 www.example.com:6667\r\nOK')
        self.assertEqual(len(self.state.streams), 1)

    def send(self, line):
        self.protocol.dataReceived(line.strip() + "\r\n")

    def test_bootstrap_callback(self):
        '''
        FIXME: something is still screwy with this; try throwing an
        exception from TorState.bootstrap and we'll just hang...
        '''

        d = self.state.post_bootstrap

        self.protocol._set_valid_events(' '.join(self.state.event_map.keys()))
        self.state._bootstrap()

        self.send("250+ns/all=")
        self.send(".")
        self.send("250 OK")

        self.send("250+circuit-status=")
        self.send(".")
        self.send("250 OK")

        self.send("250-stream-status=")
        self.send("250 OK")

        self.send("250-address-mappings/all=")
        self.send("250 OK")

        for ignored in self.state.event_map.items():
            self.send("250 OK")

        fakerouter = object()
        self.state.routers['$0000000000000000000000000000000000000000'] = fakerouter
        self.state.routers['$9999999999999999999999999999999999999999'] = fakerouter
        self.send("250+entry-guards=")
        self.send("$0000000000000000000000000000000000000000=name up")
        self.send("$1111111111111111111111111111111111111111=foo up")
        self.send("$9999999999999999999999999999999999999999=eman unusable 2012-01-01 22:00:00")
        self.send(".")
        self.send("250 OK")

        ## implicitly created Router object for the $1111...11 lookup
        ## but 0.0.0.0 will have to country, so Router will ask Tor
        ## for one via GETINFO ip-to-country
        self.send("250-ip-to-country/0.0.0.0=??")
        self.send("250 OK")

        self.assertEqual(len(self.state.entry_guards), 2)
        self.assertTrue('$0000000000000000000000000000000000000000' in self.state.entry_guards)
        self.assertEqual(self.state.entry_guards['$0000000000000000000000000000000000000000'], fakerouter)
        self.assertTrue('$1111111111111111111111111111111111111111' in self.state.entry_guards)

        self.assertEqual(len(self.state.unusable_entry_guards), 1)
        self.assertTrue('$9999999999999999999999999999999999999999' in self.state.unusable_entry_guards[0])

        return d

    def test_bootstrap_existing_addresses(self):
        '''
        FIXME: something is still screwy with this; try throwing an
        exception from TorState.bootstrap and we'll just hang...
        '''

        d = self.state.post_bootstrap

        clock = task.Clock()
        self.state.addrmap.scheduler = clock

        self.protocol._set_valid_events(' '.join(self.state.event_map.keys()))
        self.state._bootstrap()

        self.send("250+ns/all=")
        self.send(".")
        self.send("250 OK")

        self.send("250+circuit-status=")
        self.send(".")
        self.send("250 OK")

        self.send("250-stream-status=")
        self.send("250 OK")

        self.send("250+address-mappings/all=")
        self.send('www.example.com 127.0.0.1 "2012-01-01 00:00:00"')
        self.send('subdomain.example.com 10.0.0.0 "2012-01-01 00:01:02"')
        self.send('.')
        self.send('250 OK')

        for ignored in self.state.event_map.items():
            self.send("250 OK")

        self.send("250-entry-guards=")
        self.send("250 OK")

        self.send("250 OK")

        self.assertEqual(len(self.state.addrmap.addr), 2)
        self.assertTrue('www.example.com' in self.state.addrmap.addr)
        self.assertTrue('subdomain.example.com' in self.state.addrmap.addr)

        return d

    def test_bootstrap_single_existing_circuit(self):
        '''
        test with exactly one circuit. should probably test with 2 as
        well, since there was a bug with the handling of just one.
        '''

        d = self.state.post_bootstrap

        clock = task.Clock()
        self.state.addrmap.scheduler = clock

        self.protocol._set_valid_events(' '.join(self.state.event_map.keys()))
        self.state._bootstrap()

        self.send("250+ns/all=")
        self.send(".")
        self.send("250 OK")

        self.send("250-circuit-status=123 BUILT PURPOSE=GENERAL")
        self.send("250 OK")

        self.send("250-stream-status=")
        self.send("250 OK")

        self.send("250+address-mappings/all=")
        self.send('.')
        self.send('250 OK')

        for ignored in self.state.event_map.items():
            self.send("250 OK")

        self.send("250-entry-guards=")
        self.send("250 OK")

        self.send("250 OK")

        self.assertTrue(self.state.find_circuit(123))
        self.assertEquals(len(self.state.circuits), 1)

        return d

    def test_unset_attacher(self):

        class MyAttacher(object):
            implements(IStreamAttacher)

            def attach_stream(self, stream, circuits):
                return None

        fr = FakeReactor(self)
        self.state.set_attacher(MyAttacher(), fr)
        self.send("250 OK")
        self.state.set_attacher(None, fr)
        self.send("250 OK")
        self.assertEqual(self.transport.value(), 'SETCONF __LeaveStreamsUnattached=1\r\nSETCONF __LeaveStreamsUnattached=0\r\n')

    def test_attacher(self):
        class MyAttacher(object):
            implements(IStreamAttacher)

            def __init__(self):
                self.streams = []
                self.answer = None

            def attach_stream(self, stream, circuits):
                self.streams.append(stream)
                return self.answer

        attacher = MyAttacher()
        self.state.set_attacher(attacher, FakeReactor(self))
        events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL'
        self.protocol._set_valid_events(events)
        self.state._add_events()
        for ignored in self.state.event_map.items():
            self.send("250 OK")

        self.send("650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER")
        self.send("650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE")
        self.assertEqual(len(attacher.streams), 1)
        self.assertEqual(attacher.streams[0].id, 1)
        self.assertEqual(len(self.protocol.commands), 1)
        self.assertEqual(self.protocol.commands[0][1], 'ATTACHSTREAM 1 0')

        # we should totally ignore .exit URIs
        attacher.streams = []
        self.send("650 STREAM 2 NEW 0 10.0.0.0.$E11D2B2269CC25E67CA6C9FB5843497539A74FD0.exit:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME")
        self.assertEqual(len(attacher.streams), 0)
        self.assertEqual(len(self.protocol.commands), 1)

        # we should NOT ignore .onion URIs
        attacher.streams = []
        self.send("650 STREAM 3 NEW 0 xxxxxxxxxxxxxxxx.onion:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME")
        self.assertEqual(len(attacher.streams), 1)
        self.assertEqual(len(self.protocol.commands), 2)
        self.assertEqual(self.protocol.commands[1][1], 'ATTACHSTREAM 3 0')

        # normal attach
        circ = FakeCircuit(1)
        circ.state = 'BUILT'
        self.state.circuits[1] = circ
        attacher.answer = circ
        self.send("650 STREAM 4 NEW 0 xxxxxxxxxxxxxxxx.onion:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME")
        self.assertEqual(len(attacher.streams), 2)
        self.assertEqual(len(self.protocol.commands), 3)
        self.assertEqual(self.protocol.commands[2][1], 'ATTACHSTREAM 4 1')

    def test_attacher_defer(self):
        class MyAttacher(object):
            implements(IStreamAttacher)

            def __init__(self, answer):
                self.streams = []
                self.answer = answer

            def attach_stream(self, stream, circuits):
                self.streams.append(stream)
                return defer.succeed(self.answer)

        self.state.circuits[1] = FakeCircuit(1)
        attacher = MyAttacher(self.state.circuits[1])
        self.state.set_attacher(attacher, FakeReactor(self))

        ## boilerplate to finish enough set-up in the protocol so it
        ## works
        events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL'
        self.protocol._set_valid_events(events)
        self.state._add_events()
        for ignored in self.state.event_map.items():
            self.send("250 OK")

        self.send("650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER")
        self.send("650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE")
        self.assertEqual(len(attacher.streams), 1)
        self.assertEqual(attacher.streams[0].id, 1)
        self.assertEqual(len(self.protocol.commands), 1)
        self.assertEqual(self.protocol.commands[0][1], 'ATTACHSTREAM 1 1')

    def test_attacher_errors(self):
        class MyAttacher(object):
            implements(IStreamAttacher)

            def __init__(self, answer):
                self.streams = []
                self.answer = answer

            def attach_stream(self, stream, circuits):
                return self.answer

        self.state.circuits[1] = FakeCircuit(1)
        attacher = MyAttacher(FakeCircuit(2))
        self.state.set_attacher(attacher, FakeReactor(self))

        stream = Stream(self.state)
        stream.id = 3
        msg = ''
        try:
            self.state._maybe_attach(stream)
        except Exception, e:
            msg = str(e)
        self.assertTrue('circuit unknown' in msg)

        attacher.answer = self.state.circuits[1]
        msg = ''
        try:
            self.state._maybe_attach(stream)
        except Exception, e:
            msg = str(e)
class AuthenticationTests(unittest.TestCase):

    def setUp(self):
        self.protocol = TorControlProtocol()
        self.transport = proto_helpers.StringTransport()

    def send(self, line):
        self.protocol.dataReceived(line.strip() + "\r\n")

    def test_authenticate_cookie(self):
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        cookie_data = 'cookiedata!cookiedata!cookiedata'
        with open('authcookie', 'w') as f:
            f.write(cookie_data)
        self.send('250-PROTOCOLINFO 1')
        self.send('250-AUTH METHODS=COOKIE,HASHEDPASSWORD COOKIEFILE="authcookie"')
        self.send('250-VERSION Tor="0.2.2.34"')
        self.send('250 OK')

        self.assertEqual(
            self.transport.value(),
            'AUTHENTICATE %s\r\n' % cookie_data.encode("hex")
        )

    def test_authenticate_password(self):
        self.protocol.password_function = lambda: 'foo'
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        self.send('250-PROTOCOLINFO 1')
        self.send('250-AUTH METHODS=HASHEDPASSWORD')
        self.send('250-VERSION Tor="0.2.2.34"')
        self.send('250 OK')

        self.assertEqual(self.transport.value(), 'AUTHENTICATE %s\r\n' % "foo".encode("hex"))

    def test_authenticate_password_deferred(self):
        d = defer.Deferred()
        self.protocol.password_function = lambda: d
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        self.send('250-PROTOCOLINFO 1')
        self.send('250-AUTH METHODS=HASHEDPASSWORD')
        self.send('250-VERSION Tor="0.2.2.34"')
        self.send('250 OK')

        # make sure we haven't tried to authenticate before getting
        # the password callback
        self.assertEqual(self.transport.value(), '')
        d.callback('foo')

        # now make sure we DID try to authenticate
        self.assertEqual(
            self.transport.value(),
            'AUTHENTICATE %s\r\n' % "foo".encode("hex")
        )

    def test_authenticate_password_deferred_but_no_password(self):
        d = defer.Deferred()
        self.protocol.password_function = lambda: d
        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n')
        self.transport.clear()
        self.send('250-PROTOCOLINFO 1')
        self.send('250-AUTH METHODS=HASHEDPASSWORD')
        self.send('250-VERSION Tor="0.2.2.34"')
        self.send('250 OK')
        d.callback(None)
        return self.assertFailure(self.protocol.post_bootstrap, RuntimeError)

    def confirmAuthFailed(self, *args):
        self.auth_failed = True

    def test_authenticate_no_password(self):
        self.protocol.post_bootstrap.addErrback(self.confirmAuthFailed)
        self.auth_failed = False

        self.protocol.makeConnection(self.transport)
        self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n')

        self.send('250-PROTOCOLINFO 1')
        self.send('250-AUTH METHODS=HASHEDPASSWORD')
        self.send('250-VERSION Tor="0.2.2.34"')
        self.send('250 OK')

        self.assertTrue(self.auth_failed)
Ejemplo n.º 52
0
class FakeReactorTcp(object):

    failures = 0
    _port_generator = port_generator()

    def __init__(self, test):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransport()
        self.transport.protocol = self.protocol

        def blam():
            self.protocol.outReceived(b"Bootstrap")
        self.transport.closeStdin = blam
        self.protocol.makeConnection(self.transport)
        self.test = test

    def spawnProcess(self, processprotocol, bin, args, env, path,
                     uid=None, gid=None, usePTY=None, childFDs=None):
        self.protocol = processprotocol
        self.protocol.makeConnection(self.transport)
        self.transport.process_protocol = processprotocol
        return self.transport

    def addSystemEventTrigger(self, *args):
        self.test.assertEqual(args[0], 'before')
        self.test.assertEqual(args[1], 'shutdown')
        # we know this is just for the temporary file cleanup, so we
        # nuke it right away to avoid polluting /tmp by calling the
        # callback now.
        args[2]()

    def listenTCP(self, port, factory, **kwargs):
        '''returns IListeningPort'''
        if self.failures > 0:
            self.failures -= 1
            raise error.CannotListenError(None, None, None)

        if port == 0:
            port = next(self._port_generator)
        p = FakeListeningPort(port)
        p.factory = factory
        p.startListening()
        return p

    def connectTCP(self, host, port, factory, timeout, bindAddress):
        '''should return IConnector'''
        r = tcp.Connector(
            host, port, factory, timeout,
            bindAddress, reactor=self
        )

        def blam(*args):
            print("BLAAAAAM", args)
        r.connect = blam
        return r

    def connectUNIX(self, address, factory, timeout=30, checkPID=0):
        '''should return IConnector'''
        r = unix.Connector(
            address, factory, timeout, self, checkPID,
        )

        def blam(*args):
            print("BLAAAAAM", args)
        r.connect = blam
        return r
Ejemplo n.º 53
0
 def setUp(self):
     self.protocol = TorControlProtocol()
     self.state = TorState(self.protocol)
     self.protocol.connectionMade = lambda: None
     self.transport = proto_helpers.StringTransport()
     self.protocol.makeConnection(self.transport)
Ejemplo n.º 54
0
class DisconnectionTests(unittest.TestCase):
    def setUp(self):
        self.protocol = TorControlProtocol()
        self.protocol.connectionMade = lambda: None
        self.transport = proto_helpers.StringTransportWithDisconnection()
        self.protocol.makeConnection(self.transport)
        # why doesn't makeConnection do this?
        self.transport.protocol = self.protocol

    def tearDown(self):
        self.protocol = None

    def test_disconnect_callback(self):
        """
        see that we get our callback on_disconnect if the transport
        goes away
        """
        def it_was_called(*args):
            it_was_called.yes = True
            return None

        it_was_called.yes = False
        self.protocol.on_disconnect.addCallback(it_was_called)
        self.protocol.on_disconnect.addErrback(it_was_called)
        f = failure.Failure(error.ConnectionDone("It's all over"))
        self.protocol.connectionLost(f)
        self.assertTrue(it_was_called.yes)

    def test_when_disconnect(self):
        """
        see that we get our callback for when_disconnected if the
        transport goes away
        """
        def it_was_called(arg):
            it_was_called.yes = True
            return None

        it_was_called.yes = False

        d = self.protocol.when_disconnected()
        d.addCallback(it_was_called)
        f = failure.Failure(error.ConnectionDone("It's all over"))
        self.protocol.connectionLost(f)
        self.assertTrue(it_was_called.yes)

    def test_when_disconnect_error(self):
        """
        see that we get our errback for when_disconnected if the
        transport goes away
        """
        def it_was_called(arg):
            it_was_called.yes = True
            return None

        it_was_called.yes = False

        d = self.protocol.when_disconnected()
        d.addErrback(it_was_called)
        f = failure.Failure(RuntimeError("sadness"))
        self.protocol.connectionLost(f)
        self.assertTrue(it_was_called.yes)

    def test_disconnect_errback(self):
        """
        see that we get our callback on_disconnect if the transport
        goes away
        """
        def it_was_called(*args):
            it_was_called.yes = True
            return None

        it_was_called.yes = False
        self.protocol.on_disconnect.addCallback(it_was_called)
        self.protocol.on_disconnect.addErrback(it_was_called)
        f = failure.Failure(RuntimeError("The thing didn't do the stuff."))
        self.protocol.connectionLost(f)
        self.assertTrue(it_was_called.yes)

    def test_disconnect_outstanding_commands(self):
        """
        outstanding commands should errback on disconnect
        """
        def it_was_called(f):
            str(f)
            it_was_called.count += 1
            return None

        it_was_called.count = 0

        # we want to make sure outstanding commands get errbacks
        d0 = self.protocol.queue_command("some command0")
        d1 = self.protocol.queue_command("some command1")
        d0.addErrback(it_was_called)
        d1.addErrback(it_was_called)
        self.protocol.on_disconnect.addErrback(lambda _: None)

        f = failure.Failure(RuntimeError("The thing didn't do the stuff."))
        self.protocol.connectionLost(f)
        self.assertEqual(it_was_called.count, 2)