Beispiel #1
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
Beispiel #2
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')
Beispiel #3
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))
Beispiel #4
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)
Beispiel #5
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)
Beispiel #6
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)
Beispiel #7
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")
Beispiel #8
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):
        @implementer(IStreamAttacher)
        class MyAttacher(object):
            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'):
            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() + b"\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(b"250+ns/all=")
        self.send(b".")
        self.send(b"250 OK")

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

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

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

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

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

        self.send(b"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(IPv4Address(u'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(IPv4Address(u'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(b"250+ns/all=")
        self.send(b".")
        self.send(b"250 OK")

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

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

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

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

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

        self.send(b"250 OK")

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

        return d

    def test_unset_attacher(self):
        @implementer(IStreamAttacher)
        class MyAttacher(object):
            def attach_stream(self, stream, circuits):
                return None

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

    def test_attacher_twice(self):
        """
        It should be an error to set an attacher twice
        """
        @implementer(IStreamAttacher)
        class MyAttacher(object):
            pass

        attacher = MyAttacher()
        self.state.set_attacher(attacher, FakeReactor(self))
        # attach the *same* instance twice; not an error
        self.state.set_attacher(attacher, FakeReactor(self))
        with self.assertRaises(RuntimeError) as ctx:
            self.state.set_attacher(MyAttacher(), FakeReactor(self))
        self.assertTrue("already have an attacher" in str(ctx.exception))

    @defer.inlineCallbacks
    def _test_attacher_both_apis(self):
        """
        similar to above, but first set_attacher is implicit via
        Circuit.stream_via
        """
        reactor = Mock()
        directlyProvides(reactor, IReactorCore)

        @implementer(IStreamAttacher)
        class MyAttacher(object):
            pass

        circ = Circuit(self.state)
        circ.state = 'BUILT'

        # use the "preferred" API, which will set an attacher
        factory = Mock()
        proto = Mock()
        proto.when_done = Mock(return_value=defer.succeed(None))
        factory.connect = Mock(return_value=defer.succeed(proto))
        ep = circ.stream_via(reactor, 'meejah.ca', 443, factory)
        addr = Mock()
        addr.host = '10.0.0.1'
        addr.port = 1011
        ep._target_endpoint._get_address = Mock(
            return_value=defer.succeed(addr))
        print("EP", ep)
        attacher = yield _get_circuit_attacher(reactor, self.state)
        print("attacher", attacher)
        d = ep.connect('foo')
        print("doin' it")
        stream = Mock()
        import ipaddress
        stream.source_addr = ipaddress.IPv4Address(u'10.0.0.1')
        stream.source_port = 1011
        attacher.attach_stream(stream, [])
        yield d

        # ...now use the low-level API (should be an error)
        with self.assertRaises(RuntimeError) as ctx:
            self.state.set_attacher(MyAttacher(), FakeReactor(self))
        self.assertTrue("already have an attacher" in str(ctx.exception))

    def test_attacher(self):
        @implementer(IStreamAttacher)
        class MyAttacher(object):
            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(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 0')

        # we should totally ignore .exit URIs
        attacher.streams = []
        self.send(
            b"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(
            b"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], b'ATTACHSTREAM 3 0')

        # normal attach
        circ = FakeCircuit(1)
        circ.state = 'BUILT'
        self.state.circuits[1] = circ
        attacher.answer = circ
        self.send(
            b"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], b'ATTACHSTREAM 4 1')

    def test_attacher_defer(self):
        @implementer(IStreamAttacher)
        class MyAttacher(object):
            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(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')

    @defer.inlineCallbacks
    def test_attacher_errors(self):
        @implementer(IStreamAttacher)
        class MyAttacher(object):
            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 as 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 as e:
            msg = str(e)
        self.assertTrue('only attach to BUILT' in msg)

        attacher.answer = 'not a Circuit instance'
        msg = ''
        try:
            yield self.state._maybe_attach(stream)
        except Exception as e:
            msg = str(e)
        self.assertTrue('Circuit instance' in msg)

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

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

        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(b"250 OK")

        self.transport.clear()
        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(self.transport.value(), b'')

    def test_close_stream_with_id(self):
        stream = Stream(self.state)
        stream.id = 1

        self.state.streams[1] = stream
        self.state.close_stream(stream)
        self.assertEqual(self.transport.value(), b'CLOSESTREAM 1 1\r\n')

    def test_close_stream_with_stream(self):
        stream = Stream(self.state)
        stream.id = 1

        self.state.streams[1] = stream
        self.state.close_stream(stream.id)
        self.assertEqual(self.transport.value(), b'CLOSESTREAM 1 1\r\n')

    def test_close_stream_invalid_reason(self):
        stream = Stream(self.state)
        stream.id = 1
        self.state.streams[1] = stream
        self.assertRaises(ValueError, self.state.close_stream, stream,
                          'FOO_INVALID_REASON')

    def test_close_circuit_with_id(self):
        circuit = Circuit(self.state)
        circuit.id = 1

        self.state.circuits[1] = circuit
        self.state.close_circuit(circuit.id)
        self.assertEqual(self.transport.value(), b'CLOSECIRCUIT 1\r\n')

    def test_close_circuit_with_circuit(self):
        circuit = Circuit(self.state)
        circuit.id = 1

        self.state.circuits[1] = circuit
        self.state.close_circuit(circuit)
        self.assertEqual(self.transport.value(), b'CLOSECIRCUIT 1\r\n')

    def test_close_circuit_with_flags(self):
        circuit = Circuit(self.state)
        circuit.id = 1
        # try:
        #     self.state.close_circuit(circuit.id, IfUnused=True)
        #     self.assertTrue(False)
        # except KeyError:
        #     pass

        self.state.circuits[1] = circuit
        self.state.close_circuit(circuit.id, IfUnused=True)
        self.assertEqual(self.transport.value(),
                         b'CLOSECIRCUIT 1 IfUnused\r\n')

    def test_circuit_destroy(self):
        self.state._circuit_update('365 LAUNCHED PURPOSE=GENERAL')
        self.assertTrue(365 in self.state.circuits)
        self.state._circuit_update(
            '365 FAILED $E11D2B2269CC25E67CA6C9FB5843497539A74FD0=eris,$50DD343021E509EB3A5A7FD0D8A4F8364AFBDCB5=venus,$253DFF1838A2B7782BE7735F74E50090D46CA1BC=chomsky PURPOSE=GENERAL REASON=TIMEOUT'
        )
        self.assertTrue(365 not in self.state.circuits)

    def test_circuit_destroy_already(self):
        self.state._circuit_update('365 LAUNCHED PURPOSE=GENERAL')
        self.assertTrue(365 in self.state.circuits)
        self.state._circuit_update(
            '365 CLOSED $E11D2B2269CC25E67CA6C9FB5843497539A74FD0=eris,$50DD343021E509EB3A5A7FD0D8A4F8364AFBDCB5=venus,$253DFF1838A2B7782BE7735F74E50090D46CA1BC=chomsky PURPOSE=GENERAL REASON=TIMEOUT'
        )
        self.assertTrue(365 not in self.state.circuits)
        self.state._circuit_update(
            '365 CLOSED $E11D2B2269CC25E67CA6C9FB5843497539A74FD0=eris,$50DD343021E509EB3A5A7FD0D8A4F8364AFBDCB5=venus,$253DFF1838A2B7782BE7735F74E50090D46CA1BC=chomsky PURPOSE=GENERAL REASON=TIMEOUT'
        )
        self.assertTrue(365 not in self.state.circuits)

    def test_circuit_listener(self):
        events = 'CIRC STREAM ORCONN BW DEBUG INFO NOTICE WARN ERR NEWDESC ADDRMAP AUTHDIR_NEWDESCS DESCCHANGED NS STATUS_GENERAL STATUS_CLIENT STATUS_SERVER GUARD STREAM_BW CLIENTS_SEEN NEWCONSENSUS BUILDTIMEOUT_SET'
        self.protocol._set_valid_events(events)
        self.state._add_events()
        for ignored in self.state.event_map.items():
            self.send(b"250 OK")

        # we use this router later on in an EXTEND
        self.state._update_network_status("""ns/all=
r PPrivCom012 2CGDscCeHXeV/y1xFrq1EGqj5g4 QX7NVLwx7pwCuk6s8sxB4rdaCKI 2011-12-20 08:34:19 84.19.178.6 9001 0
s Fast Guard Running Stable Unnamed Valid
w Bandwidth=51500
p reject 1-65535""")

        expected = [('new', {
            'id': 456
        }), ('launched', {}), ('extend', {
            'id': 123
        })]
        listen = CircuitListener(expected)
        # first add a Circuit before we listen
        self.protocol.dataReceived(
            b"650 CIRC 123 LAUNCHED PURPOSE=GENERAL\r\n")
        self.assertEqual(len(self.state.circuits), 1)

        # make sure we get added to existing circuits
        self.state.add_circuit_listener(listen)
        first_circuit = list(self.state.circuits.values())[0]
        self.assertTrue(listen in first_circuit.listeners)

        # now add a Circuit after we started listening
        self.protocol.dataReceived(
            b"650 CIRC 456 LAUNCHED PURPOSE=GENERAL\r\n")
        self.assertEqual(len(self.state.circuits), 2)
        self.assertTrue(
            listen in list(self.state.circuits.values())[0].listeners)
        self.assertTrue(
            listen in list(self.state.circuits.values())[1].listeners)

        # now update the first Circuit to ensure we're really, really
        # listening
        self.protocol.dataReceived(
            b"650 CIRC 123 EXTENDED $D82183B1C09E1D7795FF2D7116BAB5106AA3E60E~PPrivCom012 PURPOSE=GENERAL\r\n"
        )
        self.assertEqual(len(listen.expected), 0)

    def test_router_from_id_invalid_key(self):
        self.failUnlessRaises(KeyError, self.state.router_from_id,
                              'somethingcompletelydifferent..thatis42long')

    def test_router_from_named_router(self):
        r = self.state.router_from_id(
            '$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=foo')
        self.assertEqual(r.id_hex, '$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')
        self.assertEqual(r.unique_name, 'foo')

    def confirm_router_state(self, x):
        self.assertTrue(
            '$624926802351575FF7E4E3D60EFA3BFB56E67E8A' in self.state.routers)
        router = self.state.routers[
            '$624926802351575FF7E4E3D60EFA3BFB56E67E8A']
        self.assertTrue('exit' in router.flags)
        self.assertTrue('fast' in router.flags)
        self.assertTrue('guard' in router.flags)
        self.assertTrue('hsdir' in router.flags)
        self.assertTrue('named' in router.flags)
        self.assertTrue('running' in router.flags)
        self.assertTrue('stable' in router.flags)
        self.assertTrue('v2dir' in router.flags)
        self.assertTrue('valid' in router.flags)
        self.assertTrue('futureproof' in router.flags)
        self.assertEqual(router.bandwidth, 518000)
        self.assertTrue(router.accepts_port(43))
        self.assertTrue(router.accepts_port(53))
        self.assertTrue(not router.accepts_port(44))
        self.assertTrue(router.accepts_port(989))
        self.assertTrue(router.accepts_port(990))
        self.assertTrue(not router.accepts_port(991))
        self.assertTrue(not router.accepts_port(988))

    def test_router_with_ipv6_address(self):
        self.state._update_network_status("""ns/all=
r PPrivCom012 2CGDscCeHXeV/y1xFrq1EGqj5g4 QX7NVLwx7pwCuk6s8sxB4rdaCKI 2011-12-20 08:34:19 84.19.178.6 9001 0
a [2001:0:0:0::0]:4321
s Fast Guard Running Stable Named Valid
w Bandwidth=51500
p reject 1-65535""")
        self.assertEqual(
            len(self.state.routers_by_name['PPrivCom012'][0].ip_v6), 1)
        self.assertEqual(self.state.routers_by_name['PPrivCom012'][0].ip_v6[0],
                         '[2001:0:0:0::0]:4321')

    def test_invalid_routers(self):
        try:
            self.state._update_network_status('''ns/all=
r fake YkkmgCNRV1/35OPWDvo7+1bmfoo tanLV/4ZfzpYQW0xtGFqAa46foo 2011-12-12 16:29:16 12.45.56.78 443 80
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 FutureProof
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.fail()

        except RuntimeError as e:
            self.assertTrue('"s "' in str(e))

    def test_routers_no_policy(self):
        """
        ensure we can parse a router descriptor which has no p line
        """

        self.state._update_network_status('''ns/all=
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 FutureProof
w Bandwidth=518000
r PPrivCom012 2CGDscCeHXeV/y1xFrq1EGqj5g4 QX7NVLwx7pwCuk6s8sxB4rdaCKI 2011-12-20 08:34:19 84.19.178.6 9001 0
s Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof
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.assertTrue('fake' in self.state.routers.keys())
        self.assertTrue('PPrivCom012' in self.state.routers.keys())

    def test_routers_no_bandwidth(self):
        """
        ensure we can parse a router descriptor which has no w line
        """

        self.state._update_network_status('''ns/all=
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 FutureProof
r PPrivCom012 2CGDscCeHXeV/y1xFrq1EGqj5g4 QX7NVLwx7pwCuk6s8sxB4rdaCKI 2011-12-20 08:34:19 84.19.178.6 9001 0
s Exit Fast Guard HSDir Named Running Stable V2Dir Valid FutureProof
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.assertTrue('fake' in self.state.routers.keys())
        self.assertTrue('PPrivCom012' in self.state.routers.keys())

    def test_router_factory(self):
        self.state._update_network_status('''ns/all=
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 FutureProof
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
r fake YxxmgCNRV1/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 FutureProof
w Bandwidth=543000
p accept 43,53
.''')
        self.assertTrue(
            '$624926802351575FF7E4E3D60EFA3BFB56E67E8A' in self.state.routers)
        r = self.state.routers['$624926802351575FF7E4E3D60EFA3BFB56E67E8A']
        self.assertEqual(r.controller, self.state.protocol)
        self.assertEqual(r.bandwidth, 518000)
        self.assertEqual(len(self.state.routers_by_name['fake']), 2)

        # now we do an update
        self.state._update_network_status('''ns/all=
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 FutureProof Authority
w Bandwidth=543000
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.assertEqual(r.bandwidth, 543000)

    def test_empty_stream_update(self):
        self.state._stream_update('''stream-status=''')

    def test_addrmap(self):
        self.state._addr_map(
            'example.com 127.0.0.1 "2012-01-01 00:00:00" EXPIRES=NEVER')

    def test_double_newconsensus(self):
        """
        The arrival of a second NEWCONSENSUS event causes parsing
        errors.
        """

        # bootstrap the TorState so we can send it a "real" 650
        # update

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

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

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

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

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

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

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

        self.send(b"250 OK")

        # state is now bootstrapped, we can send our NEWCONSENSUS update

        self.protocol.dataReceived(b'\r\n'.join(b'''650+NEWCONSENSUS
r Unnamed ABJlguUFz1lvQS0jq8nhTdRiXEk /zIVUg1tKMUeyUBoyimzorbQN9E 2012-05-23 01:10:22 219.94.255.254 9001 0
s Fast Guard Running Stable Valid
w Bandwidth=166
p reject 1-65535
.
650 OK
'''.split(b'\n')))

        self.protocol.dataReceived(b'\r\n'.join(b'''650+NEWCONSENSUS
r Unnamed ABJlguUFz1lvQS0jq8nhTdRiXEk /zIVUg1tKMUeyUBoyimzorbQN9E 2012-05-23 01:10:22 219.94.255.254 9001 0
s Fast Guard Running Stable Valid
w Bandwidth=166
p reject 1-65535
.
650 OK
'''.split(b'\n')))

        self.assertTrue('Unnamed' in self.state.routers)
        self.assertTrue(
            '$00126582E505CF596F412D23ABC9E14DD4625C49' in self.state.routers)

    def test_NEWCONSENSUS_ends_with_OK_on_w(self):
        """
        The arrival of a second NEWCONSENSUS event causes parsing
        errors.
        """

        # bootstrap the TorState so we can send it a "real" 650
        # update

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

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

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

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

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

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

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

        self.send(b"250 OK")

        # state is now bootstrapped, we can send our NEWCONSENSUS update

        self.protocol.dataReceived(b'\r\n'.join(b'''650+NEWCONSENSUS
r Unnamed ABJlguUFz1lvQS0jq8nhTdRiXEk /zIVUg1tKMUeyUBoyimzorbQN9E 2012-05-23 01:10:22 219.94.255.254 9001 0
s Fast Guard Running Stable Valid
w Bandwidth=166
.
650 OK
'''.split(b'\n')))

        self.assertTrue('Unnamed' in self.state.routers)
        self.assertTrue(
            '$00126582E505CF596F412D23ABC9E14DD4625C49' in self.state.routers)

    def test_NEWCONSENSUS_ends_with_OK_on_s(self):
        """
        The arrival of a second NEWCONSENSUS event causes parsing
        errors.
        """

        # bootstrap the TorState so we can send it a "real" 650
        # update

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

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

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

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

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

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

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

        self.send(b"250 OK")

        # state is now bootstrapped, we can send our NEWCONSENSUS update

        self.protocol.dataReceived(b'\r\n'.join(b'''650+NEWCONSENSUS
r Unnamed ABJlguUFz1lvQS0jq8nhTdRiXEk /zIVUg1tKMUeyUBoyimzorbQN9E 2012-05-23 01:10:22 219.94.255.254 9001 0
s Fast Guard Running Stable Valid
.
650 OK
'''.split(b'\n')))

        self.assertTrue('Unnamed' in self.state.routers)
        self.assertTrue(
            '$00126582E505CF596F412D23ABC9E14DD4625C49' in self.state.routers)

    def test_stream_create(self):
        self.state._stream_update('1610 NEW 0 1.2.3.4:56')
        self.assertTrue(1610 in self.state.streams)

    def test_stream_destroy(self):
        self.state._stream_update('1610 NEW 0 1.2.3.4:56')
        self.assertTrue(1610 in self.state.streams)
        self.state._stream_update(
            "1610 FAILED 0 www.example.com:0 REASON=DONE REMOTE_REASON=FAILED")
        self.assertTrue(1610 not in self.state.streams)

    def test_stream_detach(self):
        circ = FakeCircuit(1)
        circ.state = 'BUILT'
        self.state.circuits[1] = circ

        self.state._stream_update('1610 NEW 0 1.2.3.4:56')
        self.assertTrue(1610 in self.state.streams)
        self.state._stream_update("1610 SUCCEEDED 1 4.3.2.1:80")
        self.assertEqual(self.state.streams[1610].circuit, circ)

        self.state._stream_update(
            "1610 DETACHED 0 www.example.com:0 REASON=DONE REMOTE_REASON=FAILED"
        )
        self.assertEqual(self.state.streams[1610].circuit, None)

    def test_stream_listener(self):
        self.protocol._set_valid_events(
            'CIRC STREAM ORCONN BW DEBUG INFO NOTICE WARN ERR NEWDESC ADDRMAP AUTHDIR_NEWDESCS DESCCHANGED NS STATUS_GENERAL STATUS_CLIENT STATUS_SERVER GUARD STREAM_BW CLIENTS_SEEN NEWCONSENSUS BUILDTIMEOUT_SET'
        )
        self.state._add_events()
        for ignored in self.state.event_map.items():
            self.send(b"250 OK")

        expected = [
            ('new', {}),
        ]
        listen = StreamListener(expected)
        self.send(
            b"650 STREAM 77 NEW 0 www.yahoo.cn:80 SOURCE_ADDR=127.0.0.1:54315 PURPOSE=USER"
        )
        self.state.add_stream_listener(listen)

        self.assertEqual(1, len(self.state.streams.values()))
        self.assertTrue(
            listen in list(self.state.streams.values())[0].listeners)
        self.assertEqual(len(self.state.streams), 1)
        self.assertEqual(len(listen.expected), 1)

        self.send(
            b"650 STREAM 78 NEW 0 www.yahoo.cn:80 SOURCE_ADDR=127.0.0.1:54315 PURPOSE=USER"
        )
        self.assertEqual(len(self.state.streams), 2)
        self.assertEqual(len(listen.expected), 0)

    def test_build_circuit(self):
        class FakeRouter:
            def __init__(self, i):
                self.id_hex = i
                self.flags = []

        path = []
        for x in range(3):
            path.append(FakeRouter("$%040d" % x))
        # can't just check flags for guard status, need to know if
        # it's in the running Tor's notion of Entry Guards
        path[0].flags = ['guard']

        self.state.build_circuit(path, using_guards=True)
        self.assertEqual(
            self.transport.value(),
            b'EXTENDCIRCUIT 0 0000000000000000000000000000000000000000,0000000000000000000000000000000000000001,0000000000000000000000000000000000000002\r\n'
        )
        # should have gotten a warning about this not being an entry
        # guard
        self.assertEqual(len(self.flushWarnings()), 1)

    def test_build_circuit_no_routers(self):
        self.state.build_circuit()
        self.assertEqual(self.transport.value(), b'EXTENDCIRCUIT 0\r\n')

    def test_build_circuit_unfound_router(self):
        self.state.build_circuit(
            routers=[b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'],
            using_guards=False)
        self.assertEqual(
            self.transport.value(),
            b'EXTENDCIRCUIT 0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n')

    def circuit_callback(self, circ):
        self.assertTrue(isinstance(circ, Circuit))
        self.assertEqual(circ.id, 1234)

    def test_build_circuit_final_callback(self):
        class FakeRouter:
            def __init__(self, i):
                self.id_hex = i
                self.flags = []

        path = []
        for x in range(3):
            path.append(FakeRouter("$%040d" % x))
        # can't just check flags for guard status, need to know if
        # it's in the running Tor's notion of Entry Guards
        path[0].flags = ['guard']

        # FIXME TODO we should verify we get a circuit_new event for
        # this circuit

        d = self.state.build_circuit(path, using_guards=True)
        d.addCallback(self.circuit_callback)
        self.assertEqual(
            self.transport.value(),
            b'EXTENDCIRCUIT 0 0000000000000000000000000000000000000000,0000000000000000000000000000000000000001,0000000000000000000000000000000000000002\r\n'
        )
        self.send(b"250 EXTENDED 1234")
        # should have gotten a warning about this not being an entry
        # guard
        self.assertEqual(len(self.flushWarnings()), 1)
        return d

    def test_build_circuit_error(self):
        """
        tests that we check the callback properly
        """

        try:
            self.state._find_circuit_after_extend("FOO 1234")
            self.assertTrue(False)
        except RuntimeError as e:
            self.assertTrue('Expected EXTENDED' in str(e))

    def test_listener_mixins(self):
        self.assertTrue(verifyClass(IStreamListener, StreamListenerMixin))
        self.assertTrue(verifyClass(ICircuitListener, CircuitListenerMixin))

    def test_build_circuit_timedout(self):
        class FakeRouter:
            def __init__(self, i):
                self.id_hex = i
                self.flags = []

        path = []
        for x in range(3):
            path.append(FakeRouter("$%040d" % x))
        # can't just check flags for guard status, need to know if
        # it's in the running Tor's notion of Entry Guards
        path[0].flags = ['guard']

        # FIXME TODO we should verify we get a circuit_new event for
        # this circuit
        timeout = 10
        clock = task.Clock()

        d = build_timeout_circuit(self.state,
                                  clock,
                                  path,
                                  timeout,
                                  using_guards=False)
        clock.advance(10)

        def check_for_timeout_error(f):
            self.assertTrue(isinstance(f.type(), CircuitBuildTimedOutError))

        d.addErrback(check_for_timeout_error)
        return d

    def test_build_circuit_timeout_after_progress(self):
        """
        Similar to above but we timeout after Tor has ack'd our
        circuit-creation attempt, but before reaching BUILT.
        """
        class FakeRouter:
            def __init__(self, i):
                self.id_hex = i
                self.flags = []

        class FakeCircuit(Circuit):
            def close(self):
                return defer.succeed(None)

        path = []
        for x in range(3):
            path.append(FakeRouter("$%040d" % x))

        def fake_queue(cmd):
            self.assertTrue(cmd.startswith('EXTENDCIRCUIT 0'))
            return defer.succeed("EXTENDED 1234")

        queue_command = patch.object(self.protocol, 'queue_command',
                                     fake_queue)
        circuit_factory = patch.object(self.state, 'circuit_factory',
                                       FakeCircuit)
        with queue_command, circuit_factory:
            timeout = 10
            clock = task.Clock()

            d = build_timeout_circuit(self.state,
                                      clock,
                                      path,
                                      timeout,
                                      using_guards=False)
            clock.advance(timeout + 1)

            def check_for_timeout_error(f):
                self.assertTrue(isinstance(f.type(),
                                           CircuitBuildTimedOutError))

            d.addErrback(check_for_timeout_error)
        return d

    def test_build_circuit_not_timedout(self):
        class FakeRouter:
            def __init__(self, i):
                self.id_hex = i
                self.flags = []

        path = []
        for x in range(3):
            path.append(FakeRouter("$%040d" % x))
        path[0].flags = ['guard']

        timeout = 10
        clock = task.Clock()
        d = build_timeout_circuit(self.state,
                                  clock,
                                  path,
                                  timeout,
                                  using_guards=True)
        d.addCallback(self.circuit_callback)

        self.assertEqual(
            self.transport.value(),
            b'EXTENDCIRCUIT 0 0000000000000000000000000000000000000000,0000000000000000000000000000000000000001,0000000000000000000000000000000000000002\r\n'
        )
        self.send(b"250 EXTENDED 1234")
        # we can't just .send(b'650 CIRC 1234 BUILT') this because we
        # didn't fully hook up the protocol to the state, e.g. via
        # post_bootstrap etc.
        self.state.circuits[1234].update(['1234', 'BUILT'])
        # should have gotten a warning about this not being an entry
        # guard
        self.assertEqual(len(self.flushWarnings()), 1)
        return d
Beispiel #9
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)