def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransportWithDisconnection() self.protocol.makeConnection(self.transport) # why doesn't makeConnection do this? self.transport.protocol = self.protocol
class FakeTorState(object): def __init__(self, routers): self.routers = routers self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.protocol.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.protocol.transport) def _find_circuit_after_extend(self, x): return defer.succeed(None) def build_circuit(self, routers=None, using_guards=True): print "build circuit" cmd = "EXTENDCIRCUIT 0 " first = True for router in routers: if first: first = False else: cmd += ',' if isinstance(router, basestring) and len(router) == 40 \ and hashFromHexId(router): cmd += router else: cmd += router.id_hex[1:] d = self.protocol.queue_command(cmd) d.addCallback(self._find_circuit_after_extend) return d
class FakeEndpointAnswers: implements(IStreamClientEndpoint) def __init__(self, answers): self.answers = answers # since we use pop() we need these to be "backwards" self.answers.reverse() def get_info_raw(self, keys): ans = '' for k in keys.split(): if len(self.answers) == 0: raise TorProtocolError(551, "ran out of answers") ans += '%s=%s\r\n' % (k, self.answers.pop()) return ans[:-2] # don't want trailing \r\n def get_info_incremental(self, key, linecb): linecb('%s=%s' % (key, self.answers.pop())) return defer.succeed('') def connect(self, protocol_factory): self.proto = TorControlProtocol() self.proto.transport = proto_helpers.StringTransport() self.proto.get_info_raw = self.get_info_raw self.proto.get_info_incremental = self.get_info_incremental self.proto._set_valid_events('GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL') return defer.succeed(self.proto)
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
class FakeTorState(object): def __init__(self, routers): self.routers = routers self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.protocol.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.protocol.transport) def _find_circuit_after_extend(self, x): return defer.succeed(None) def build_circuit(self, routers=None, using_guards=True): cmd = "EXTENDCIRCUIT 0 " first = True for router in routers: if first: first = False else: cmd += ',' if isinstance(router, basestring) and len(router) == 40 \ and hashFromHexId(router): cmd += router else: cmd += router.id_hex[1:] d = self.protocol.queue_command(cmd) print "d %r" % (d,) d.addCallback(self._find_circuit_after_extend) return d
def connect(self, protocol_factory): self.proto = TorControlProtocol() self.proto.transport = proto_helpers.StringTransport() self.proto.get_info_raw = self.get_info_raw self.proto.get_info_incremental = self.get_info_incremental self.proto._set_valid_events('GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL') return defer.succeed(self.proto)
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_set_conf_wrong_args(self): ctl = TorControlProtocol() d = ctl.set_conf('a') self.assertTrue(d.called) self.assertTrue(d.result) self.assertTrue('even number' in d.result.getErrorMessage()) ## ignore the error so trial doesn't get unhappy d.addErrback(lambda foo: True) return d
def __init__(self, test): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.transport.protocol = self.protocol def blam(): self.protocol.outReceived(b"Bootstrap") self.transport.closeStdin = blam self.protocol.makeConnection(self.transport) self.test = test
class FakeReactorTcp(FakeReactor): implements(IReactorTCP) failures = 0 _port_generator = port_generator() def __init__(self, test): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.transport = FakeProcessTransport() self.transport.protocol = self.protocol def blam(): self.protocol.outReceived("Bootstrap") self.transport.closeStdin = blam self.protocol.makeConnection(self.transport) FakeReactor.__init__(self, test, self.transport, lambda x: None) def listenTCP(self, port, factory, **kwargs): '''returns IListeningPort''' if self.failures > 0: self.failures -= 1 raise error.CannotListenError(None, None, None) if port == 0: port = self._port_generator.next() p = FakeListeningPort(port) p.factory = factory p.startListening() return p def connectTCP(self, host, port, factory, timeout, bindAddress): '''should return IConnector''' # print "CONN", host, port, factory # print dir(factory) # print "XX", factory r = tcp.Connector(host, port, factory, timeout, bindAddress, reactor=self) # print dir(r) def blam(*args): print "BLAAAAAM", args r.connect = blam return r
def __init__(self, test): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.transport = FakeProcessTransport() self.transport.protocol = self.protocol def blam(): self.protocol.outReceived("Bootstrap") self.transport.closeStdin = blam self.protocol.makeConnection(self.transport) FakeReactor.__init__(self, test, self.transport, lambda x: None)
class LogicTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def test_set_conf_wrong_args(self): ctl = TorControlProtocol() d = ctl.set_conf('a') self.assertTrue(d.called) self.assertTrue(d.result) self.assertTrue('even number' in d.result.getErrorMessage()) ## ignore the error so trial doesn't get unhappy d.addErrback(lambda foo: True) return d
class LogicTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def test_set_conf_wrong_args(self): ctl = TorControlProtocol() d = ctl.set_conf("a") self.assertTrue(d.called) self.assertTrue(d.result) self.assertTrue("even number" in d.result.getErrorMessage()) ## ignore the error so trial doesn't get unhappy d.addErrback(lambda foo: True) return d
class FakeEndpoint: implements(IStreamClientEndpoint) def get_info_raw(self, keys): return defer.succeed('\r\n'.join(map(lambda k: '%s=' % k, keys.split()))) def get_info_incremental(self, key, linecb): linecb('%s=' % key) return defer.succeed('') def connect(self, protocol_factory): self.proto = TorControlProtocol() self.proto.transport = proto_helpers.StringTransport() self.proto.get_info_raw = self.get_info_raw self.proto.get_info_incremental = self.get_info_incremental self.proto._set_valid_events('GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL') return defer.succeed(self.proto)
def test_path_update(self): cp = TorControlProtocol() state = TorState(cp, False) circuit = Circuit(state) circuit.update('1 EXTENDED $E11D2B2269CC25E67CA6C9FB5843497539A74FD0=eris PURPOSE=GENERAL'.split()) self.assertEqual(1, len(circuit.path)) self.assertEqual( '$E11D2B2269CC25E67CA6C9FB5843497539A74FD0', circuit.path[0].id_hex ) self.assertEqual('eris', circuit.path[0].name)
class AuthenticationTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.transport = proto_helpers.StringTransport() def send(self, line): self.protocol.dataReceived(line.strip() + "\r\n") def test_authenticate_cookie(self): self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n') self.transport.clear() cookie_data = 'cookiedata!cookiedata!cookiedata' open('authcookie', 'w').write(cookie_data) self.send('250-PROTOCOLINFO 1') self.send('250-AUTH METHODS=COOKIE,HASHEDPASSWORD COOKIEFILE="authcookie"') self.send('250-VERSION Tor="0.2.2.34"') self.send('250 OK') self.assertEqual(self.transport.value(), 'AUTHENTICATE %s\r\n' % cookie_data.encode("hex")) def test_authenticate_password(self): self.protocol.password = '******' self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n') self.transport.clear() self.send('250-PROTOCOLINFO 1') self.send('250-AUTH METHODS=HASHEDPASSWORD') self.send('250-VERSION Tor="0.2.2.34"') self.send('250 OK') self.assertEqual(self.transport.value(), 'AUTHENTICATE %s\r\n' % "foo".encode("hex")) def confirmAuthFailed(self, *args): self.auth_failed = True def test_authenticate_no_password(self): self.protocol.post_bootstrap.addErrback(self.confirmAuthFailed) self.auth_failed = False self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n') self.send('250-PROTOCOLINFO 1') self.send('250-AUTH METHODS=HASHEDPASSWORD') self.send('250-VERSION Tor="0.2.2.34"') self.send('250 OK') self.assertTrue(self.auth_failed)
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')
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')
class DisconnectionTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransportWithDisconnection() self.protocol.makeConnection(self.transport) # why doesn't makeConnection do this? self.transport.protocol = self.protocol def tearDown(self): self.protocol = None def test_disconnect_callback(self): """ see that we get our callback on_disconnect if the transport goes away """ def it_was_called(*args): it_was_called.yes = True return None it_was_called.yes = False self.protocol.on_disconnect.addCallback(it_was_called) self.protocol.on_disconnect.addErrback(it_was_called) f = failure.Failure(error.ConnectionDone("It's all over")) self.protocol.connectionLost(f) self.assertTrue(it_was_called.yes) def test_disconnect_errback(self): """ see that we get our callback on_disconnect if the transport goes away """ def it_was_called(*args): it_was_called.yes = True return None it_was_called.yes = False self.protocol.on_disconnect.addCallback(it_was_called) self.protocol.on_disconnect.addErrback(it_was_called) f = failure.Failure(RuntimeError("The thing didn't do the stuff.")) self.protocol.connectionLost(f) self.assertTrue(it_was_called.yes)
class ProtocolTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def tearDown(self): self.protocol = None def send(self, line): self.protocol.dataReceived(line.strip() + "\r\n") def test_statemachine_broadcast_no_code(self): try: self.protocol._broadcast_response("foo") self.fail() except RuntimeError, e: self.assertTrue("No code set yet" in str(e))
class ProtocolTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def tearDown(self): self.protocol = None def send(self, line): self.protocol.dataReceived(line.strip() + "\r\n") def test_statemachine_broadcast_no_code(self): try: self.protocol._broadcast_response("foo") self.fail() except RuntimeError, e: self.assertTrue('No code set yet' in str(e))
class LaunchTorTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) self.clock = task.Clock() def setup_complete_with_timer(self, proto): proto._check_timeout.stop() proto.checkTimeout() def setup_complete_no_errors(self, proto, config): todel = proto.to_delete self.assertTrue(len(todel) > 0) proto.processEnded(Failure(error.ProcessDone(0))) self.assertEqual(len(proto.to_delete), 0) for f in todel: self.assertTrue(not os.path.exists(f)) self.assertEqual(proto._timeout_delayed_call, None) ## make sure we set up the config to track the created tor ## protocol connection self.assertEquals(config.protocol, proto.tor_protocol) def setup_complete_fails(self, proto): todel = proto.to_delete self.assertTrue(len(todel) > 0) ## the "12" is just arbitrary, we check it later in the error-message proto.processEnded( Failure(error.ProcessTerminated(12, None, 'statusFIXME'))) self.assertEqual(len(proto.to_delete), 0) for f in todel: self.assertTrue(not os.path.exists(f)) def test_basic_launch(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap class OnProgress: def __init__(self, test, expected): self.test = test self.expected = expected def __call__(self, percent, tag, summary): self.test.assertEqual(self.expected[0], (percent, tag, summary)) self.expected = self.expected[1:] self.test.assertTrue('"' not in summary) self.test.assertTrue(percent >= 0 and percent <= 100) def on_protocol(proto): proto.outReceived('Bootstrapped 100%\n') proto.progress = OnProgress( self, [(90, 'circuit_create', 'Establishing a Tor circuit'), (100, 'done', 'Done')]) trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(connector, self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo') d.addCallback(self.setup_complete_no_errors, config) return d def check_setup_failure(self, fail): self.assertTrue("with error-code 12" in fail.getErrorMessage()) ## cancel the errback chain, we wanted this return None def test_launch_tor_fails(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(connector, self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo') d.addCallback(self.setup_complete_fails) return self.assertFailure(d, Exception) def test_launch_with_timeout(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 timeout = 5 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap class OnProgress: def __init__(self, test, expected): self.test = test self.expected = expected def __call__(self, percent, tag, summary): self.test.assertEqual(self.expected[0], (percent, tag, summary)) self.expected = self.expected[1:] self.test.assertTrue('"' not in summary) self.test.assertTrue(percent >= 0 and percent <= 100) def on_protocol(proto): proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransportNeverBootstraps() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(connector, self.protocol, self.transport) react = FakeReactor(self, trans, on_protocol) d = launch_tor(config, react, connection_creator=creator, timeout=timeout, tor_binary='/bin/echo') rtn = self.assertFailure(d, RuntimeError, "Timed out waiting for Tor to launch.") # FakeReactor is a task.Clock subclass and +1 just to be sure react.advance(timeout + 1) return rtn def setup_fails_stderr(self, fail): self.assertTrue( 'Something went horribly wrong!' in fail.getErrorMessage()) ## cancel the errback chain, we wanted this return None def test_tor_produces_stderr_output(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.errReceived('Something went horribly wrong!\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(connector, self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo') d.addCallback(self.fail) # should't get callback d.addErrback(self.setup_fails_stderr) return d def test_tor_connection_fails(self): """ We fail to connect once, and then successfully connect -- testing whether we're retrying properly on each Bootstrapped line from stdout. """ config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 class Connector: count = 0 def __call__(self, proto, trans): self.count += 1 if self.count < 2: return defer.fail(error.CannotListenError( None, None, None)) proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo') d.addCallback(self.setup_complete_fails) return self.assertFailure(d, Exception) def test_tor_connection_user_data_dir(self): """ Test that we don't delete a user-supplied data directory. """ config = TorConfig() config.OrPort = 1234 class Connector: def __call__(self, proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') proto.outReceived('Bootstrapped 100%\n') my_dir = tempfile.mkdtemp(prefix='tortmp') config.DataDirectory = my_dir trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo') def still_have_data_dir(proto, tester): proto.cleanup( ) # FIXME? not really unit-testy as this is sort of internal function tester.assertTrue(os.path.exists(my_dir)) delete_file_or_tree(my_dir) d.addCallback(still_have_data_dir, self) d.addErrback(self.fail) return d def test_tor_connection_user_control_port(self): """ Confirm we use a user-supplied control-port properly """ config = TorConfig() config.OrPort = 1234 config.ControlPort = 4321 class Connector: def __call__(self, proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo') def check_control_port(proto, tester): ## we just want to ensure launch_tor() didn't mess with ## the controlport we set tester.assertEquals(config.ControlPort, 4321) d.addCallback(check_control_port, self) d.addErrback(self.fail) return d def test_tor_connection_default_control_port(self): """ Confirm a default control-port is set if not user-supplied. """ config = TorConfig() class Connector: def __call__(self, proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo') def check_control_port(proto, tester): ## ensure ControlPort was set to a default value tester.assertEquals(config.ControlPort, 9052) d.addCallback(check_control_port, self) d.addErrback(self.fail) return d def confirm_progress(self, exp, *args, **kwargs): self.assertEqual(exp, args) self.got_progress = True def test_progress_updates(self): from txtorcon.torconfig import TorProcessProtocol self.got_progress = False proto = TorProcessProtocol( None, functools.partial(self.confirm_progress, (10, 'tag', 'summary'))) proto.progress(10, 'tag', 'summary') self.assertTrue(self.got_progress) def test_status_updates(self): from txtorcon.torconfig import TorProcessProtocol proto = TorProcessProtocol(None) proto.status_client("NOTICE CONSENSUS_ARRIVED")
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)
def setUp(self): self.protocol = TorControlProtocol() self.transport = proto_helpers.StringTransport()
def setUp(self): self.controller = TorState(TorControlProtocol()) self.controller.connectionMade = lambda _: None
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)
class LaunchTorTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) self.clock = task.Clock() def test_ctor_timeout_no_ireactortime(self): with self.assertRaises(RuntimeError) as ctx: TorProcessProtocol(lambda: None, timeout=42) self.assertTrue("Must supply an IReactorTime" in str(ctx.exception)) def _fake_queue(self, cmd): if cmd.split()[0] == 'PROTOCOLINFO': return defer.succeed('AUTH METHODS=NULL') elif cmd == 'GETINFO config/names': return defer.succeed('config/names=') elif cmd == 'GETINFO signal/names': return defer.succeed('signal/names=') elif cmd == 'GETINFO version': return defer.succeed('version=0.1.2.3') elif cmd == 'GETINFO events/names': return defer.succeed('events/names=STATUS_CLIENT') return defer.succeed(None) def _fake_event_listener(self, what, cb): if what == 'STATUS_CLIENT': # should ignore non-BOOTSTRAP messages cb('STATUS_CLIENT not-bootstrap') cb('STATUS_CLIENT BOOTSTRAP PROGRESS=100 TAG=foo SUMMARY=bar') return defer.succeed(None) @defer.inlineCallbacks def test_launch_tor_unix_controlport(self): trans = FakeProcessTransport() trans.protocol = self.protocol self.protocol.post_bootstrap.callback(self.protocol) self.protocol._set_valid_events("STATUS_CLIENT") self.protocol.add_event_listener = self._fake_event_listener self.protocol.queue_command = self._fake_queue def on_protocol(proto): proto.outReceived(b'Bootstrapped 90%\n') # launch() auto-discovers a SOCKS port reactor = FakeReactor(self, trans, on_protocol, [9050]) reactor.connectUNIX = Mock() # prepare a suitable directory for tor unix socket with TempDir() as tmp: tmpdir = str(tmp) os.chmod(tmpdir, 0o0700) socket_file = join(tmpdir, 'test_socket_file') with patch('txtorcon.controller.UNIXClientEndpoint') as uce: endpoint = Mock() endpoint.connect = Mock( return_value=defer.succeed(self.protocol)) uce.return_value = endpoint yield launch( reactor, control_port="unix:{}".format(socket_file), tor_binary="/bin/echo", stdout=Mock(), stderr=Mock(), ) self.assertTrue(endpoint.connect.called) self.assertTrue(uce.called) self.assertEqual( socket_file, uce.mock_calls[0][1][1], ) @defer.inlineCallbacks def test_launch_tor_unix_controlport_wrong_perms(self): reactor = FakeReactor(self, Mock(), None, [9050]) with self.assertRaises(ValueError) as ctx: with TempDir() as tmp: tmpdir = str(tmp) os.chmod(tmpdir, 0o0777) socket_file = join(tmpdir, 'socket_test') yield launch( reactor, control_port="unix:{}".format(socket_file), tor_binary="/bin/echo", stdout=Mock(), stderr=Mock(), ) self.assertTrue( "must only be readable by the user" in str(ctx.exception)) @defer.inlineCallbacks def test_launch_tor_unix_controlport_no_directory(self): reactor = FakeReactor(self, Mock(), None, [9050]) with self.assertRaises(ValueError) as ctx: socket_file = '/does/not/exist' yield launch( reactor, control_port="unix:{}".format(socket_file), tor_binary="/bin/echo", stdout=Mock(), stderr=Mock(), ) self.assertTrue("must exist" in str(ctx.exception)) @patch('txtorcon.controller.find_tor_binary', return_value='/bin/echo') @defer.inlineCallbacks def test_launch_fails(self, ftb): trans = FakeProcessTransport() def on_proto(protocol): protocol.processEnded( Failure(error.ProcessTerminated(12, None, 'statusFIXME'))) reactor = FakeReactor(self, trans, on_proto, [1234, 9052]) try: yield launch(reactor) self.fail("Should fail") except RuntimeError: pass errs = self.flushLoggedErrors(RuntimeError) self.assertEqual(1, len(errs)) self.assertTrue("Tor exited with error-code 12" in str(errs[0])) @defer.inlineCallbacks def test_launch_no_ireactorcore(self): try: yield launch(None) self.fail("should get exception") except ValueError as e: self.assertTrue("provide IReactorCore" in str(e)) @patch('txtorcon.controller.find_tor_binary', return_value='/bin/echo') @patch('txtorcon.controller.TorProcessProtocol') @defer.inlineCallbacks def test_successful_launch(self, tpp, ftb): trans = FakeProcessTransport() reactor = FakeReactor(self, trans, lambda p: None, [1, 2, 3]) config = TorConfig() def boot(arg=None): config.post_bootstrap.callback(config) config.__dict__['bootstrap'] = Mock(side_effect=boot) config.__dict__['attach_protocol'] = Mock( return_value=defer.succeed(None)) def foo(*args, **kw): rtn = Mock() rtn.post_bootstrap = defer.succeed(None) rtn.when_connected = Mock(return_value=defer.succeed(rtn)) return rtn tpp.side_effect = foo tor = yield launch(reactor, _tor_config=config) self.assertTrue(isinstance(tor, Tor)) @defer.inlineCallbacks def test_quit(self): tor = Tor(Mock(), Mock()) tor._protocol = Mock() tor._process_protocol = Mock() yield tor.quit() @defer.inlineCallbacks def test_quit_no_protocol(self): tor = Tor(Mock(), Mock()) tor._protocol = None tor._process_protocol = None with self.assertRaises(RuntimeError) as ctx: yield tor.quit() self.assertTrue('no protocol instance' in str(ctx.exception)) @patch('txtorcon.controller.socks') @defer.inlineCallbacks def test_dns_resolve(self, fake_socks): answer = object() tor = Tor(Mock(), Mock()) fake_socks.resolve = Mock(return_value=defer.succeed(answer)) ans = yield tor.dns_resolve("meejah.ca") self.assertEqual(ans, answer) @patch('txtorcon.controller.socks') @defer.inlineCallbacks def test_dns_resolve_existing_socks(self, fake_socks): answer = object() tor = Tor(Mock(), Mock()) fake_socks.resolve = Mock(return_value=defer.succeed(answer)) ans0 = yield tor.dns_resolve("meejah.ca") # do it again to exercise the _default_socks_port() case when # we already got the default fake_socks.resolve = Mock(return_value=defer.succeed(answer)) ans1 = yield tor.dns_resolve("meejah.ca") self.assertEqual(ans0, answer) self.assertEqual(ans1, answer) @patch('txtorcon.controller.socks') @defer.inlineCallbacks def test_dns_resolve_no_configured_socks(self, fake_socks): answer = object() tor = Tor(Mock(), Mock()) def boom(*args, **kw): raise RuntimeError("no socks") tor._config.socks_endpoint = Mock(side_effect=boom) fake_socks.resolve = Mock(return_value=defer.succeed(answer)) ans = yield tor.dns_resolve("meejah.ca") self.assertEqual(ans, answer) @patch('txtorcon.controller.socks') @defer.inlineCallbacks def test_dns_resolve_ptr(self, fake_socks): answer = object() tor = Tor(Mock(), Mock()) fake_socks.resolve_ptr = Mock(return_value=defer.succeed(answer)) ans = yield tor.dns_resolve_ptr("4.3.2.1") self.assertEqual(ans, answer) @patch('txtorcon.controller.find_tor_binary', return_value='/bin/echo') @defer.inlineCallbacks def test_successful_launch_tcp_control(self, ftb): """ full end-to-end test of a launch, faking things out at a "lower level" than most of the other tests """ trans = FakeProcessTransport() def on_protocol(proto): pass reactor = FakeReactor(self, trans, on_protocol, [1, 2, 3]) def connect_tcp(host, port, factory, timeout=0, bindAddress=None): addr = Mock() factory.doStart() proto = factory.buildProtocol(addr) tpp = proto._wrappedProtocol tpp.add_event_listener = self._fake_event_listener tpp.queue_command = self._fake_queue proto.makeConnection(Mock()) return proto reactor.connectTCP = connect_tcp config = TorConfig() tor = yield launch(reactor, _tor_config=config, control_port='1234', timeout=30) self.assertTrue(isinstance(tor, Tor)) @patch('txtorcon.controller.find_tor_binary', return_value='/bin/echo') @patch('txtorcon.controller.sys') @patch('txtorcon.controller.TorProcessProtocol') @defer.inlineCallbacks def test_successful_launch_tcp_control_non_unix(self, tpp, _sys, ftb): _sys.platform = 'not darwin or linux2' trans = FakeProcessTransport() reactor = FakeReactor(self, trans, lambda p: None, [1, 2, 3]) config = TorConfig() def boot(arg=None): config.post_bootstrap.callback(config) config.__dict__['bootstrap'] = Mock(side_effect=boot) config.__dict__['attach_protocol'] = Mock( return_value=defer.succeed(None)) def foo(*args, **kw): rtn = Mock() rtn.post_bootstrap = defer.succeed(None) rtn.when_connected = Mock(return_value=defer.succeed(rtn)) return rtn tpp.side_effect = foo tor = yield launch(reactor, _tor_config=config) self.assertTrue(isinstance(tor, Tor)) @patch('txtorcon.controller.sys') @patch('txtorcon.controller.pwd') @patch('txtorcon.controller.os.geteuid') @patch('txtorcon.controller.os.chown') def test_launch_root_changes_tmp_ownership(self, chown, euid, _pwd, _sys): _pwd.return_value = 1000 _sys.platform = 'linux2' euid.return_value = 0 reactor = Mock() directlyProvides(reactor, IReactorCore) # note! we're providing enough options here that we react the # "chown" before any 'yield' statements in launch, so we don't # actually have to wait for it... a little rickety, though :/ launch(reactor, tor_binary='/bin/echo', user='******', socks_port='1234') self.assertEqual(1, chown.call_count) @defer.inlineCallbacks def test_launch_timeout_exception(self): """ we provide a timeout, and it expires """ trans = Mock() trans.signalProcess = Mock(side_effect=error.ProcessExitedAlready) trans.loseConnection = Mock() on_proto = Mock() react = FakeReactor(self, trans, on_proto, [1234]) def creator(): return defer.succeed(Mock()) d = launch( reactor=react, tor_binary='/bin/echo', socks_port=1234, timeout=10, connection_creator=creator, ) react.advance(12) self.assertTrue(trans.loseConnection.called) with self.assertRaises(RuntimeError) as ctx: yield d self.assertTrue("timeout while launching" in str(ctx.exception)) @defer.inlineCallbacks def test_launch_timeout_process_exits(self): # cover the "one more edge case" where we get a processEnded() # but we've already "done" a timeout. trans = Mock() trans.signalProcess = Mock() trans.loseConnection = Mock() class MyFakeReactor(FakeReactor): def spawnProcess(self, processprotocol, bin, args, env, path, uid=None, gid=None, usePTY=None, childFDs=None): self.protocol = processprotocol self.protocol.makeConnection(self.transport) self.transport.process_protocol = processprotocol self.on_protocol(self.protocol) status = Mock() status.value.exitCode = None processprotocol.processEnded(status) return self.transport react = MyFakeReactor(self, trans, Mock(), [1234, 9052]) d = launch( reactor=react, tor_binary='/bin/echo', timeout=10, data_directory='/dev/null', ) react.advance(20) try: yield d except RuntimeError as e: self.assertTrue("Tor was killed" in str(e)) errs = self.flushLoggedErrors(RuntimeError) self.assertEqual(1, len(errs)) self.assertTrue("Tor was killed" in str(errs[0])) @defer.inlineCallbacks def test_launch_wrong_stdout(self): try: yield launch( FakeReactor(self, Mock(), Mock()), stdout=object(), tor_binary='/bin/echo', ) self.fail("Should have thrown an error") except RuntimeError as e: self.assertTrue("file-like object needed" in str(e).lower()) @defer.inlineCallbacks def test_launch_with_timeout(self): # XXX not entirely sure what this was/is supposed to be # testing, but it covers an extra 7 lines of code?? timeout = 5 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived(b'Bootstrapped 100%\n') trans = FakeProcessTransportNeverBootstraps() trans.protocol = self.protocol creator = functools.partial(connector, Mock(), Mock()) react = FakeReactor(self, trans, on_protocol, [1234, 9052]) with self.assertRaises(RuntimeError) as ctx: d = launch(react, connection_creator=creator, timeout=timeout, tor_binary='/bin/echo') # FakeReactor is a task.Clock subclass and +1 just to be sure react.advance(timeout + 1) yield d self.assertTrue('timeout while launching Tor' in str(ctx.exception)) # could/should just use return from this to do asserts? self.flushLoggedErrors(RuntimeError) @defer.inlineCallbacks def test_tor_produces_stderr_output(self): def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.errReceived('Something went horribly wrong!\n') trans = FakeProcessTransport() trans.protocol = Mock() fakeout = StringIO() fakeerr = StringIO() creator = functools.partial(connector, Mock(), Mock()) try: yield launch( FakeReactor(self, trans, on_protocol, [1234, 9052]), connection_creator=creator, tor_binary='/bin/echo', stdout=fakeout, stderr=fakeerr, ) self.fail() # should't get callback except RuntimeError as e: self.assertEqual('', fakeout.getvalue()) self.assertEqual('Something went horribly wrong!\n', fakeerr.getvalue()) self.assertTrue('Something went horribly wrong!' in str(e)) @patch('txtorcon.controller.find_tor_binary', return_value='/bin/echo') @defer.inlineCallbacks def test_tor_connection_fails(self, ftb): trans = FakeProcessTransport() def on_protocol(proto): proto.outReceived(b'Bootstrapped 100%\n') reactor = FakeReactor(self, trans, on_protocol, [1, 2, 3]) fails = ['one'] def connect_tcp(host, port, factory, timeout=0, bindAddress=None): if len(fails): fails.pop() raise error.CannotListenError('on-purpose-error', None, None) addr = Mock() factory.doStart() proto = factory.buildProtocol(addr) tpp = proto._wrappedProtocol def fake_event_listener(what, cb): if what == 'STATUS_CLIENT': # should ignore non-BOOTSTRAP messages cb('STATUS_CLIENT not-bootstrap') cb('STATUS_CLIENT BOOTSTRAP PROGRESS=100 TAG=foo SUMMARY=bar' ) return defer.succeed(None) tpp.add_event_listener = fake_event_listener def fake_queue(cmd): if cmd.split()[0] == 'PROTOCOLINFO': return defer.succeed('AUTH METHODS=NULL') elif cmd == 'GETINFO config/names': return defer.succeed('config/names=') elif cmd == 'GETINFO signal/names': return defer.succeed('signal/names=') elif cmd == 'GETINFO version': return defer.succeed('version=0.1.2.3') elif cmd == 'GETINFO events/names': return defer.succeed('events/names=STATUS_CLIENT') return defer.succeed(None) tpp.queue_command = fake_queue proto.makeConnection(Mock()) return proto reactor.connectTCP = connect_tcp config = TorConfig() tor = yield launch(reactor, _tor_config=config, control_port='1234', timeout=30) errs = self.flushLoggedErrors() self.assertTrue(isinstance(tor, Tor)) self.assertEqual(1, len(errs)) def _test_tor_connection_user_data_dir(self): """ Test that we don't delete a user-supplied data directory. """ config = TorConfig() config.OrPort = 1234 class Connector: def __call__(self, proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived(b'Bootstrapped 90%\n') with TempDir(prefix='tortmp') as tmp: my_dir = str(tmp) config.DataDirectory = my_dir trans = FakeProcessTransport() trans.protocol = self.protocol creator = functools.partial(Connector(), self.protocol, self.transport) d = launch(FakeReactor(self, trans, on_protocol, [1234, 9051]), connection_creator=creator, tor_binary='/bin/echo') return d def still_have_data_dir(tor, tester): tor._process_protocol.cleanup( ) # FIXME? not really unit-testy as this is sort of internal function tester.assertTrue(os.path.exists(my_dir)) delete_file_or_tree(my_dir) d.addCallback(still_have_data_dir, self) d.addErrback(self.fail) return d def _test_tor_connection_user_control_port(self): """ Confirm we use a user-supplied control-port properly """ config = TorConfig() config.OrPort = 1234 config.ControlPort = 4321 class Connector: def __call__(self, proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived(b'Bootstrapped 90%\n') proto.outReceived(b'Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol creator = functools.partial(Connector(), self.protocol, self.transport) d = launch( FakeReactor(self, trans, on_protocol, [9052]), connection_creator=creator, tor_binary='/bin/echo', socks_port=1234, ) def check_control_port(proto, tester): # we just want to ensure launch() didn't mess with # the controlport we set tester.assertEquals(config.ControlPort, 4321) d.addCallback(check_control_port, self) d.addErrback(self.fail) return d @defer.inlineCallbacks def _test_tor_connection_default_control_port(self): """ Confirm a default control-port is set if not user-supplied. """ class Connector: def __call__(self, proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived(b'Bootstrapped 90%\n') proto.outReceived(b'Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol creator = functools.partial(Connector(), self.protocol, self.transport) tor = yield launch( FakeReactor(self, trans, on_protocol, [9052]), connection_creator=creator, tor_binary='/bin/echo', socks_port=1234, ) self.assertEqual(tor.config.ControlPort, 9052) def test_progress_updates(self): self.got_progress = False def confirm_progress(p, t, s): self.assertEqual(p, 10) self.assertEqual(t, 'tag') self.assertEqual(s, 'summary') self.got_progress = True process = TorProcessProtocol(None, confirm_progress) process.progress(10, 'tag', 'summary') self.assertTrue(self.got_progress) def test_quit_process(self): process = TorProcessProtocol(None) process.transport = Mock() d = process.quit() self.assertFalse(d.called) process.processExited(Failure(error.ProcessTerminated(exitCode=15))) self.assertTrue(d.called) process.processEnded(Failure(error.ProcessDone(None))) self.assertTrue(d.called) errs = self.flushLoggedErrors() self.assertEqual(1, len(errs)) self.assertTrue("Tor exited with error-code" in str(errs[0])) def test_quit_process_already(self): process = TorProcessProtocol(None) process.transport = Mock() def boom(sig): self.assertEqual(sig, 'TERM') raise error.ProcessExitedAlready() process.transport.signalProcess = Mock(side_effect=boom) d = process.quit() process.processEnded(Failure(error.ProcessDone(None))) self.assertTrue(d.called) errs = self.flushLoggedErrors() self.assertEqual(1, len(errs)) self.assertTrue("Tor exited with error-code" in str(errs[0])) @defer.inlineCallbacks def test_quit_process_error(self): process = TorProcessProtocol(None) process.transport = Mock() def boom(sig): self.assertEqual(sig, 'TERM') raise RuntimeError("Something bad") process.transport.signalProcess = Mock(side_effect=boom) try: yield process.quit() except RuntimeError as e: self.assertEqual("Something bad", str(e)) def XXXtest_status_updates(self): process = TorProcessProtocol(None) process.status_client("NOTICE CONSENSUS_ARRIVED") def XXXtest_tor_launch_success_then_shutdown(self): """ There was an error where we double-callbacked a deferred, i.e. success and then shutdown. This repeats it. """ process = TorProcessProtocol(None) process.status_client( 'STATUS_CLIENT BOOTSTRAP PROGRESS=100 TAG=foo SUMMARY=cabbage') # XXX why this assert? self.assertEqual(None, process._connected_cb) class Value(object): exitCode = 123 class Status(object): value = Value() process.processEnded(Status()) self.assertEquals(len(self.flushLoggedErrors(RuntimeError)), 1) @defer.inlineCallbacks def test_launch_no_control_port(self): ''' See Issue #80. This allows you to launch tor with a TorConfig with ControlPort=0 in case you don't want a control connection at all. In this case you get back a TorProcessProtocol and you own both pieces. (i.e. you have to kill it yourself). ''' trans = FakeProcessTransportNoProtocol() trans.protocol = self.protocol def creator(*args, **kw): print("Bad: connection creator called") self.fail() def on_protocol(proto): self.process_proto = proto proto.outReceived(b'Bootstrapped 90%\n') proto.outReceived(b'Bootstrapped 100%\n') reactor = FakeReactor(self, trans, on_protocol, [9052, 9999]) tor = yield launch( reactor=reactor, connection_creator=creator, tor_binary='/bin/echo', socks_port=1234, control_port=0, ) self.assertEqual(tor._process_protocol, self.process_proto) d = tor.quit() reactor.advance(0) yield d errs = self.flushLoggedErrors() self.assertEqual(1, len(errs)) self.assertTrue("Tor was killed" in str(errs[0]))
class LaunchTorTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = do_nothing self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def setup_complete_no_errors(self, proto, config): todel = proto.to_delete self.assertTrue(len(todel) > 0) proto.processEnded(Failure(error.ProcessDone(0))) self.assertEqual(len(proto.to_delete), 0) for f in todel: self.assertTrue(not os.path.exists(f)) ## make sure we set up the config to track the created tor ## protocol connection self.assertEquals(config.protocol, proto.tor_protocol) def setup_complete_fails(self, proto): todel = proto.to_delete self.assertTrue(len(todel) > 0) ## the "12" is just arbitrary, we check it later in the error-message proto.processEnded(Failure(error.ProcessTerminated(12, None, 'statusFIXME'))) self.assertEqual(len(proto.to_delete), 0) for f in todel: self.assertTrue(not os.path.exists(f)) def test_basic_launch(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap class OnProgress: def __init__(self, test, expected): self.test = test self.expected = expected def __call__(self, percent, tag, summary): self.test.assertEqual(self.expected[0], (percent, tag, summary)) self.expected = self.expected[1:] self.test.assertTrue('"' not in summary) self.test.assertTrue(percent >= 0 and percent <= 100) def on_protocol(proto): proto.outReceived('Bootstrapped 100%\n') proto.progress = OnProgress(self, [(90, 'circuit_create', 'Establishing a Tor circuit'), (100, 'done', 'Done')]) trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(connector, self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator) d.addCallback(self.setup_complete_no_errors, config) return d def check_setup_failure(self, fail): self.assertTrue("with error-code 12" in fail.getErrorMessage()) ## cancel the errback chain, we wanted this return None def test_launch_tor_fails(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(connector, self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator) d.addCallback(self.setup_complete_fails) d.addErrback(self.check_setup_failure) return d def setup_fails_stderr(self, fail): self.assertTrue('Something went horribly wrong!' in fail.getErrorMessage()) ## cancel the errback chain, we wanted this return None def test_tor_produces_stderr_output(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.errReceived('Something went horribly wrong!\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(connector, self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator) d.addCallback(self.fail) # should't get callback d.addErrback(self.setup_fails_stderr) return d def test_tor_connection_fails(self): """ We fail to connect once, and then successfully connect -- testing whether we're retrying properly on each Bootstrapped line from stdout. """ config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 class Connector: count = 0 def __call__(self, proto, trans): self.count += 1 if self.count < 2: return defer.fail(error.CannotListenError(None, None, None)) proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator) d.addCallback(self.setup_complete_fails) d.addErrback(self.check_setup_failure) return d def test_tor_connection_user_data_dir(self): """ Test that we don't delete a user-supplied data directory. """ config = TorConfig() config.OrPort = 1234 class Connector: def __call__(self, proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') proto.outReceived('Bootstrapped 100%\n') my_dir = tempfile.mkdtemp(prefix='tortmp') config.DataDirectory = my_dir trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator) def still_have_data_dir(proto, tester): proto.cleanup() # FIXME? not really unit-testy as this is sort of internal function tester.assertTrue(os.path.exists(my_dir)) delete_file_or_tree(my_dir) d.addCallback(still_have_data_dir, self) d.addErrback(self.fail) return d def test_tor_connection_user_control_port(self): """ Confirm we use a user-supplied control-port properly """ config = TorConfig() config.OrPort = 1234 config.ControlPort = 4321 class Connector: def __call__(self, proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator) def check_control_port(proto, tester): ## we just want to ensure launch_tor() didn't mess with ## the controlport we set tester.assertEquals(config.ControlPort, 4321) d.addCallback(check_control_port, self) d.addErrback(self.fail) return d def test_tor_connection_default_control_port(self): """ Confirm a default control-port is set if not user-supplied. """ config = TorConfig() class Connector: def __call__(self, proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator) def check_control_port(proto, tester): ## ensure ControlPort was set to a default value tester.assertEquals(config.ControlPort, 9052) d.addCallback(check_control_port, self) d.addErrback(self.fail) return d def confirm_progress(self, exp, *args, **kwargs): self.assertEqual(exp, args) self.got_progress = True def test_progress_updates(self): from txtorcon.torconfig import TorProcessProtocol self.got_progress = False; proto = TorProcessProtocol(None, functools.partial(self.confirm_progress, (10, 'tag', 'summary'))) proto.progress(10, 'tag', 'summary') self.assertTrue(self.got_progress) def test_status_updates(self): from txtorcon.torconfig import TorProcessProtocol proto = TorProcessProtocol(None) proto.status_client("NOTICE CONSENSUS_ARRIVED")
def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) self.clock = task.Clock()
def setUp(self): self.protocol = TorControlProtocol(lambda: defer.succeed('foo')) self.transport = proto_helpers.StringTransport()
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)
def test_object_implements(self): self.assertTrue(ITorControlProtocol.providedBy(TorControlProtocol()))
class DisconnectionTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransportWithDisconnection() self.protocol.makeConnection(self.transport) # why doesn't makeConnection do this? self.transport.protocol = self.protocol def tearDown(self): self.protocol = None def test_disconnect_callback(self): """ see that we get our callback on_disconnect if the transport goes away """ def it_was_called(*args): it_was_called.yes = True return None it_was_called.yes = False self.protocol.on_disconnect.addCallback(it_was_called) self.protocol.on_disconnect.addErrback(it_was_called) f = failure.Failure(error.ConnectionDone("It's all over")) self.protocol.connectionLost(f) self.assertTrue(it_was_called.yes) def test_when_disconnect(self): """ see that we get our callback for when_disconnected if the transport goes away """ def it_was_called(arg): it_was_called.yes = True return None it_was_called.yes = False d = self.protocol.when_disconnected() d.addCallback(it_was_called) f = failure.Failure(error.ConnectionDone("It's all over")) self.protocol.connectionLost(f) self.assertTrue(it_was_called.yes) def test_when_disconnect_error(self): """ see that we get our errback for when_disconnected if the transport goes away """ def it_was_called(arg): it_was_called.yes = True return None it_was_called.yes = False d = self.protocol.when_disconnected() d.addErrback(it_was_called) f = failure.Failure(RuntimeError("sadness")) self.protocol.connectionLost(f) self.assertTrue(it_was_called.yes) def test_disconnect_errback(self): """ see that we get our callback on_disconnect if the transport goes away """ def it_was_called(*args): it_was_called.yes = True return None it_was_called.yes = False self.protocol.on_disconnect.addCallback(it_was_called) self.protocol.on_disconnect.addErrback(it_was_called) f = failure.Failure(RuntimeError("The thing didn't do the stuff.")) self.protocol.connectionLost(f) self.assertTrue(it_was_called.yes) def test_disconnect_outstanding_commands(self): """ outstanding commands should errback on disconnect """ def it_was_called(f): str(f) it_was_called.count += 1 return None it_was_called.count = 0 # we want to make sure outstanding commands get errbacks d0 = self.protocol.queue_command("some command0") d1 = self.protocol.queue_command("some command1") d0.addErrback(it_was_called) d1.addErrback(it_was_called) self.protocol.on_disconnect.addErrback(lambda _: None) f = failure.Failure(RuntimeError("The thing didn't do the stuff.")) self.protocol.connectionLost(f) self.assertEqual(it_was_called.count, 2)
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")
class LaunchTorTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = do_nothing self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def setup_complete_no_errors(self, proto): todel = proto.to_delete self.assertTrue(len(todel) > 0) proto.processEnded(Failure(error.ProcessDone(0))) self.assertTrue(len(proto.to_delete) == 0) for f in todel: self.assertTrue(not os.path.exists(f)) def setup_complete_fails(self, proto): todel = proto.to_delete self.assertTrue(len(todel) > 0) ## the "12" is just arbitrary, we check it later in the error-message proto.processEnded(Failure(error.ProcessTerminated(12, None, 'statusFIXME'))) self.assertTrue(len(proto.to_delete) == 0) for f in todel: self.assertTrue(not os.path.exists(f)) def test_basic_launch(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap class OnProgress: def __init__(self, test, expected): self.test = test self.expected = expected def __call__(self, percent, tag, summary): self.test.assertTrue(self.expected[0] == (percent, tag, summary)) self.expected = self.expected[1:] self.test.assertTrue('"' not in summary) self.test.assertTrue(percent >= 0 and percent <= 100) def on_protocol(proto): proto.outReceived('Bootstrapped 100%\n') proto.progress = OnProgress(self, [(90, 'circuit_create', 'Establishing a Tor circuit'), (100, 'done', 'Done')]) trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(connector, self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator) d.addCallback(self.setup_complete_no_errors) return d def check_setup_failure(self, fail): self.assertTrue("with error-code 12" in fail.getErrorMessage()) ## cancel the errback chain, we wanted this return None def test_launch_tor_fails(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(connector, self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator) d.addCallback(self.setup_complete_fails) d.addErrback(self.check_setup_failure) return d def setup_fails_stderr(self, fail): self.assertTrue('Something went horribly wrong!' in fail.getErrorMessage()) ## cancel the errback chain, we wanted this return None def test_tor_produces_stderr_output(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.errReceived('Something went horribly wrong!\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(connector, self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator) d.addCallback(self.fail) # should't get callback d.addErrback(self.setup_fails_stderr) return d def test_tor_connection_fails(self): """ We fail to connect once, and then successfully connect -- testing whether we're retrying properly on each Bootstrapped line from stdout. """ config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 class Connector: count = 0 def __call__(self, proto, trans): self.count += 1 if self.count < 2: return defer.fail(error.CannotListenError(None, None, None)) proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator) d.addCallback(self.setup_complete_fails) d.addErrback(self.check_setup_failure) return d def confirm_progress(self, exp, *args, **kwargs): self.assertTrue(exp == args) self.got_progress = True def test_progress_updates(self): from txtorcon.torconfig import TorProcessProtocol self.got_progress = False; proto = TorProcessProtocol(None, functools.partial(self.confirm_progress, (10, 'tag', 'summary'))) proto.progress(10, 'tag', 'summary') self.assertTrue(self.got_progress) def test_status_updates(self): from txtorcon.torconfig import TorProcessProtocol proto = TorProcessProtocol(None) proto.status_client("NOTICE CONSENSUS_ARRIVED")
def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = do_nothing self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport)
class LaunchTorTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) self.clock = task.Clock() def setup_complete_with_timer(self, proto): proto._check_timeout.stop() proto.checkTimeout() def setup_complete_no_errors(self, proto, config, stdout, stderr): self.assertEqual("Bootstrapped 100%\n", stdout.getvalue()) self.assertEqual("", stderr.getvalue()) todel = proto.to_delete self.assertTrue(len(todel) > 0) proto.processEnded(Failure(error.ProcessDone(0))) self.assertEqual(len(proto.to_delete), 0) for f in todel: self.assertTrue(not os.path.exists(f)) self.assertEqual(proto._timeout_delayed_call, None) ## make sure we set up the config to track the created tor ## protocol connection self.assertEquals(config.protocol, proto.tor_protocol) def setup_complete_fails(self, proto, stdout, stderr): self.assertEqual("Bootstrapped 90%\n", stdout.getvalue()) self.assertEqual("", stderr.getvalue()) todel = proto.to_delete self.assertTrue(len(todel) > 0) ## the "12" is just arbitrary, we check it later in the error-message proto.processEnded(Failure(error.ProcessTerminated(12, None, 'statusFIXME'))) self.assertEqual(1, len(self.flushLoggedErrors(RuntimeError))) self.assertEqual(len(proto.to_delete), 0) for f in todel: self.assertTrue(not os.path.exists(f)) return None def test_basic_launch(self): config = TorConfig() config.ORPort = 1234 config.SOCKSPort = 9999 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap class OnProgress: def __init__(self, test, expected): self.test = test self.expected = expected def __call__(self, percent, tag, summary): self.test.assertEqual(self.expected[0], (percent, tag, summary)) self.expected = self.expected[1:] self.test.assertTrue('"' not in summary) self.test.assertTrue(percent >= 0 and percent <= 100) def on_protocol(proto): proto.outReceived('Bootstrapped 100%\n') proto.progress = OnProgress(self, [(90, 'circuit_create', 'Establishing a Tor circuit'), (100, 'done', 'Done')]) trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans fakeout = StringIO() fakeerr = StringIO() creator = functools.partial(connector, self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo', stdout=fakeout, stderr=fakeerr) d.addCallback(self.setup_complete_no_errors, config, fakeout, fakeerr) return d def check_setup_failure(self, fail): self.assertTrue("with error-code 12" in fail.getErrorMessage()) ## cancel the errback chain, we wanted this return None def test_launch_tor_fails(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans fakeout = StringIO() fakeerr = StringIO() creator = functools.partial(connector, self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo', stdout=fakeout, stderr=fakeerr) d.addCallback(self.setup_complete_fails, fakeout, fakeerr) self.flushLoggedErrors(RuntimeError) return d def test_launch_with_timeout(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 timeout = 5 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap class OnProgress: def __init__(self, test, expected): self.test = test self.expected = expected def __call__(self, percent, tag, summary): self.test.assertEqual(self.expected[0], (percent, tag, summary)) self.expected = self.expected[1:] self.test.assertTrue('"' not in summary) self.test.assertTrue(percent >= 0 and percent <= 100) def on_protocol(proto): proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransportNeverBootstraps() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(connector, self.protocol, self.transport) react = FakeReactor(self, trans, on_protocol) d = launch_tor(config, react, connection_creator=creator, timeout=timeout, tor_binary='/bin/echo') # FakeReactor is a task.Clock subclass and +1 just to be sure react.advance(timeout + 1) self.assertTrue(d.called) self.assertTrue(d.result.getErrorMessage().strip().endswith('Tor was killed (TERM).')) self.flushLoggedErrors(RuntimeError) return self.assertFailure(d, RuntimeError) def test_launch_with_timeout_that_doesnt_expire(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 timeout = 5 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap class OnProgress: def __init__(self, test, expected): self.test = test self.expected = expected def __call__(self, percent, tag, summary): self.test.assertEqual(self.expected[0], (percent, tag, summary)) self.expected = self.expected[1:] self.test.assertTrue('"' not in summary) self.test.assertTrue(percent >= 0 and percent <= 100) def on_protocol(proto): proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(connector, self.protocol, self.transport) react = FakeReactor(self, trans, on_protocol) d = launch_tor(config, react, connection_creator=creator, timeout=timeout, tor_binary='/bin/echo') # FakeReactor is a task.Clock subclass and +1 just to be sure react.advance(timeout + 1) self.assertTrue(d.called) self.assertTrue(d.result.tor_protocol == self.protocol) def setup_fails_stderr(self, fail, stdout, stderr): self.assertEqual('', stdout.getvalue()) self.assertEqual('Something went horribly wrong!\n', stderr.getvalue()) self.assertTrue('Something went horribly wrong!' in fail.getErrorMessage()) ## cancel the errback chain, we wanted this return None def test_tor_produces_stderr_output(self): config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 def connector(proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.errReceived('Something went horribly wrong!\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans fakeout = StringIO() fakeerr = StringIO() creator = functools.partial(connector, self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo', stdout=fakeout, stderr=fakeerr) d.addCallback(self.fail) # should't get callback d.addErrback(self.setup_fails_stderr, fakeout, fakeerr) self.assertFalse(self.protocol.on_disconnect) return d def test_tor_connection_fails(self): """ We fail to connect once, and then successfully connect -- testing whether we're retrying properly on each Bootstrapped line from stdout. """ config = TorConfig() config.OrPort = 1234 config.SocksPort = 9999 class Connector: count = 0 def __call__(self, proto, trans): self.count += 1 if self.count < 2: return defer.fail(error.CannotListenError(None, None, None)) proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo') d.addCallback(self.setup_complete_fails) return self.assertFailure(d, Exception) def test_tor_connection_user_data_dir(self): """ Test that we don't delete a user-supplied data directory. """ config = TorConfig() config.OrPort = 1234 class Connector: def __call__(self, proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') my_dir = tempfile.mkdtemp(prefix='tortmp') config.DataDirectory = my_dir trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo') def still_have_data_dir(proto, tester): proto.cleanup() # FIXME? not really unit-testy as this is sort of internal function tester.assertTrue(os.path.exists(my_dir)) delete_file_or_tree(my_dir) d.addCallback(still_have_data_dir, self) d.addErrback(self.fail) return d def test_tor_connection_user_control_port(self): """ Confirm we use a user-supplied control-port properly """ config = TorConfig() config.OrPort = 1234 config.ControlPort = 4321 class Connector: def __call__(self, proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo') def check_control_port(proto, tester): ## we just want to ensure launch_tor() didn't mess with ## the controlport we set tester.assertEquals(config.ControlPort, 4321) d.addCallback(check_control_port, self) d.addErrback(self.fail) return d def test_tor_connection_default_control_port(self): """ Confirm a default control-port is set if not user-supplied. """ config = TorConfig() class Connector: def __call__(self, proto, trans): proto._set_valid_events('STATUS_CLIENT') proto.makeConnection(trans) proto.post_bootstrap.callback(proto) return proto.post_bootstrap def on_protocol(proto): proto.outReceived('Bootstrapped 90%\n') proto.outReceived('Bootstrapped 100%\n') trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo') def check_control_port(proto, tester): ## ensure ControlPort was set to a default value tester.assertEquals(config.ControlPort, 9052) d.addCallback(check_control_port, self) d.addErrback(self.fail) return d def test_progress_updates(self): self.got_progress = False def confirm_progress(p, t, s): self.assertEqual(p, 10) self.assertEqual(t, 'tag') self.assertEqual(s, 'summary') self.got_progress = True process = TorProcessProtocol(None, confirm_progress) process.progress(10, 'tag', 'summary') self.assertTrue(self.got_progress) def test_status_updates(self): process = TorProcessProtocol(None) process.status_client("NOTICE CONSENSUS_ARRIVED") def test_tor_launch_success_then_shutdown(self): """ There was an error where we double-callbacked a deferred, i.e. success and then shutdown. This repeats it. """ process = TorProcessProtocol(None) process.status_client('STATUS_CLIENT BOOTSTRAP PROGRESS=100 TAG=foo SUMMARY=cabbage') self.assertEqual(None, process.connected_cb) class Value(object): exitCode = 123 class Status(object): value = Value() process.processEnded(Status()) self.assertEquals(len(self.flushLoggedErrors(RuntimeError)), 1)
run_count = self.summary[self.input] delay = float(self.input.split("-")[1])/1000 d = defer.Deferred() def callback(): self.summary[self.input] += 1 if run_count < 3: d.errback(Exception("Failing")) else: d.callback(self.summary[self.input]) reactor.callLater(delay, callback) return d """ proto = MagicMock() proto.tor_protocol = TorControlProtocol() mock_TorState = MagicMock() # We use the instance of mock_TorState so that the mock caching will # return the same instance when TorState is created. mts = mock_TorState() mts.protocol.get_conf = lambda x: defer.succeed({'SocksPort': '4242'}) mts.post_bootstrap = defer.succeed(mts) # Set the tor_protocol to be already fired state = MagicMock() proto.tor_protocol.post_bootstrap = defer.succeed(state) mock_launch_tor = MagicMock() mock_launch_tor.return_value = defer.succeed(proto)
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")
class StateTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.state = TorState(self.protocol) self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.protocol.makeConnection(self.transport) def test_close_stream_with_attacher(self): class MyAttacher(object): implements(IStreamAttacher) def __init__(self): self.streams = [] def attach_stream(self, stream, circuits): self.streams.append(stream) return None attacher = MyAttacher() self.state.set_attacher(attacher, FakeReactor(self)) self.state._stream_update("76 CLOSED 0 www.example.com:0 REASON=DONE") def test_stream_update(self): ## we use a circuit ID of 0 so it doesn't try to look anything up but it's ## not really correct to have a SUCCEEDED w/o a valid circuit, I don't think self.state._stream_update('1610 SUCCEEDED 0 74.125.224.243:80') self.assertTrue(1610 in self.state.streams) def test_single_streams(self): self.state.circuits[496] = FakeCircuit(496) self.state._stream_status('stream-status=123 SUCCEEDED 496 www.example.com:6667\r\nOK') self.assertEqual(len(self.state.streams), 1) def send(self, line): self.protocol.dataReceived(line.strip() + "\r\n") def test_bootstrap_callback(self): ''' FIXME: something is still screwy with this; try throwing an exception from TorState.bootstrap and we'll just hang... ''' d = self.state.post_bootstrap self.protocol._set_valid_events(' '.join(self.state.event_map.keys())) self.state._bootstrap() self.send("250+ns/all=") self.send(".") self.send("250 OK") self.send("250+circuit-status=") self.send(".") self.send("250 OK") self.send("250-stream-status=") self.send("250 OK") self.send("250-address-mappings/all=") self.send("250 OK") for ignored in self.state.event_map.items(): self.send("250 OK") fakerouter = object() self.state.routers['$0000000000000000000000000000000000000000'] = fakerouter self.state.routers['$9999999999999999999999999999999999999999'] = fakerouter self.send("250+entry-guards=") self.send("$0000000000000000000000000000000000000000=name up") self.send("$1111111111111111111111111111111111111111=foo up") self.send("$9999999999999999999999999999999999999999=eman unusable 2012-01-01 22:00:00") self.send(".") self.send("250 OK") ## implicitly created Router object for the $1111...11 lookup ## but 0.0.0.0 will have to country, so Router will ask Tor ## for one via GETINFO ip-to-country self.send("250-ip-to-country/0.0.0.0=??") self.send("250 OK") self.assertEqual(len(self.state.entry_guards), 2) self.assertTrue('$0000000000000000000000000000000000000000' in self.state.entry_guards) self.assertEqual(self.state.entry_guards['$0000000000000000000000000000000000000000'], fakerouter) self.assertTrue('$1111111111111111111111111111111111111111' in self.state.entry_guards) self.assertEqual(len(self.state.unusable_entry_guards), 1) self.assertTrue('$9999999999999999999999999999999999999999' in self.state.unusable_entry_guards[0]) return d def test_bootstrap_existing_addresses(self): ''' FIXME: something is still screwy with this; try throwing an exception from TorState.bootstrap and we'll just hang... ''' d = self.state.post_bootstrap clock = task.Clock() self.state.addrmap.scheduler = clock self.protocol._set_valid_events(' '.join(self.state.event_map.keys())) self.state._bootstrap() self.send("250+ns/all=") self.send(".") self.send("250 OK") self.send("250+circuit-status=") self.send(".") self.send("250 OK") self.send("250-stream-status=") self.send("250 OK") self.send("250+address-mappings/all=") self.send('www.example.com 127.0.0.1 "2012-01-01 00:00:00"') self.send('subdomain.example.com 10.0.0.0 "2012-01-01 00:01:02"') self.send('.') self.send('250 OK') for ignored in self.state.event_map.items(): self.send("250 OK") self.send("250-entry-guards=") self.send("250 OK") self.send("250 OK") self.assertEqual(len(self.state.addrmap.addr), 2) self.assertTrue('www.example.com' in self.state.addrmap.addr) self.assertTrue('subdomain.example.com' in self.state.addrmap.addr) return d def test_bootstrap_single_existing_circuit(self): ''' test with exactly one circuit. should probably test with 2 as well, since there was a bug with the handling of just one. ''' d = self.state.post_bootstrap clock = task.Clock() self.state.addrmap.scheduler = clock self.protocol._set_valid_events(' '.join(self.state.event_map.keys())) self.state._bootstrap() self.send("250+ns/all=") self.send(".") self.send("250 OK") self.send("250-circuit-status=123 BUILT PURPOSE=GENERAL") self.send("250 OK") self.send("250-stream-status=") self.send("250 OK") self.send("250+address-mappings/all=") self.send('.') self.send('250 OK') for ignored in self.state.event_map.items(): self.send("250 OK") self.send("250-entry-guards=") self.send("250 OK") self.send("250 OK") self.assertTrue(self.state.find_circuit(123)) self.assertEquals(len(self.state.circuits), 1) return d def test_unset_attacher(self): class MyAttacher(object): implements(IStreamAttacher) def attach_stream(self, stream, circuits): return None fr = FakeReactor(self) self.state.set_attacher(MyAttacher(), fr) self.send("250 OK") self.state.set_attacher(None, fr) self.send("250 OK") self.assertEqual(self.transport.value(), 'SETCONF __LeaveStreamsUnattached=1\r\nSETCONF __LeaveStreamsUnattached=0\r\n') def test_attacher(self): class MyAttacher(object): implements(IStreamAttacher) def __init__(self): self.streams = [] self.answer = None def attach_stream(self, stream, circuits): self.streams.append(stream) return self.answer attacher = MyAttacher() self.state.set_attacher(attacher, FakeReactor(self)) events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL' self.protocol._set_valid_events(events) self.state._add_events() for ignored in self.state.event_map.items(): self.send("250 OK") self.send("650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER") self.send("650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE") self.assertEqual(len(attacher.streams), 1) self.assertEqual(attacher.streams[0].id, 1) self.assertEqual(len(self.protocol.commands), 1) self.assertEqual(self.protocol.commands[0][1], 'ATTACHSTREAM 1 0') # we should totally ignore .exit URIs attacher.streams = [] self.send("650 STREAM 2 NEW 0 10.0.0.0.$E11D2B2269CC25E67CA6C9FB5843497539A74FD0.exit:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME") self.assertEqual(len(attacher.streams), 0) self.assertEqual(len(self.protocol.commands), 1) # we should NOT ignore .onion URIs attacher.streams = [] self.send("650 STREAM 3 NEW 0 xxxxxxxxxxxxxxxx.onion:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME") self.assertEqual(len(attacher.streams), 1) self.assertEqual(len(self.protocol.commands), 2) self.assertEqual(self.protocol.commands[1][1], 'ATTACHSTREAM 3 0') # normal attach circ = FakeCircuit(1) circ.state = 'BUILT' self.state.circuits[1] = circ attacher.answer = circ self.send("650 STREAM 4 NEW 0 xxxxxxxxxxxxxxxx.onion:80 SOURCE_ADDR=127.0.0.1:12345 PURPOSE=TIME") self.assertEqual(len(attacher.streams), 2) self.assertEqual(len(self.protocol.commands), 3) self.assertEqual(self.protocol.commands[2][1], 'ATTACHSTREAM 4 1') def test_attacher_defer(self): class MyAttacher(object): implements(IStreamAttacher) def __init__(self, answer): self.streams = [] self.answer = answer def attach_stream(self, stream, circuits): self.streams.append(stream) return defer.succeed(self.answer) self.state.circuits[1] = FakeCircuit(1) attacher = MyAttacher(self.state.circuits[1]) self.state.set_attacher(attacher, FakeReactor(self)) ## boilerplate to finish enough set-up in the protocol so it ## works events = 'GUARD STREAM CIRC NS NEWCONSENSUS ORCONN NEWDESC ADDRMAP STATUS_GENERAL' self.protocol._set_valid_events(events) self.state._add_events() for ignored in self.state.event_map.items(): self.send("250 OK") self.send("650 STREAM 1 NEW 0 ca.yahoo.com:80 SOURCE_ADDR=127.0.0.1:54327 PURPOSE=USER") self.send("650 STREAM 1 REMAP 0 87.248.112.181:80 SOURCE=CACHE") self.assertEqual(len(attacher.streams), 1) self.assertEqual(attacher.streams[0].id, 1) self.assertEqual(len(self.protocol.commands), 1) self.assertEqual(self.protocol.commands[0][1], 'ATTACHSTREAM 1 1') def test_attacher_errors(self): class MyAttacher(object): implements(IStreamAttacher) def __init__(self, answer): self.streams = [] self.answer = answer def attach_stream(self, stream, circuits): return self.answer self.state.circuits[1] = FakeCircuit(1) attacher = MyAttacher(FakeCircuit(2)) self.state.set_attacher(attacher, FakeReactor(self)) stream = Stream(self.state) stream.id = 3 msg = '' try: self.state._maybe_attach(stream) except Exception, e: msg = str(e) self.assertTrue('circuit unknown' in msg) attacher.answer = self.state.circuits[1] msg = '' try: self.state._maybe_attach(stream) except Exception, e: msg = str(e)
class AuthenticationTests(unittest.TestCase): def setUp(self): self.protocol = TorControlProtocol() self.transport = proto_helpers.StringTransport() def send(self, line): self.protocol.dataReceived(line.strip() + "\r\n") def test_authenticate_cookie(self): self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n') self.transport.clear() cookie_data = 'cookiedata!cookiedata!cookiedata' with open('authcookie', 'w') as f: f.write(cookie_data) self.send('250-PROTOCOLINFO 1') self.send('250-AUTH METHODS=COOKIE,HASHEDPASSWORD COOKIEFILE="authcookie"') self.send('250-VERSION Tor="0.2.2.34"') self.send('250 OK') self.assertEqual( self.transport.value(), 'AUTHENTICATE %s\r\n' % cookie_data.encode("hex") ) def test_authenticate_password(self): self.protocol.password_function = lambda: 'foo' self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n') self.transport.clear() self.send('250-PROTOCOLINFO 1') self.send('250-AUTH METHODS=HASHEDPASSWORD') self.send('250-VERSION Tor="0.2.2.34"') self.send('250 OK') self.assertEqual(self.transport.value(), 'AUTHENTICATE %s\r\n' % "foo".encode("hex")) def test_authenticate_password_deferred(self): d = defer.Deferred() self.protocol.password_function = lambda: d self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n') self.transport.clear() self.send('250-PROTOCOLINFO 1') self.send('250-AUTH METHODS=HASHEDPASSWORD') self.send('250-VERSION Tor="0.2.2.34"') self.send('250 OK') # make sure we haven't tried to authenticate before getting # the password callback self.assertEqual(self.transport.value(), '') d.callback('foo') # now make sure we DID try to authenticate self.assertEqual( self.transport.value(), 'AUTHENTICATE %s\r\n' % "foo".encode("hex") ) def test_authenticate_password_deferred_but_no_password(self): d = defer.Deferred() self.protocol.password_function = lambda: d self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n') self.transport.clear() self.send('250-PROTOCOLINFO 1') self.send('250-AUTH METHODS=HASHEDPASSWORD') self.send('250-VERSION Tor="0.2.2.34"') self.send('250 OK') d.callback(None) return self.assertFailure(self.protocol.post_bootstrap, RuntimeError) def confirmAuthFailed(self, *args): self.auth_failed = True def test_authenticate_no_password(self): self.protocol.post_bootstrap.addErrback(self.confirmAuthFailed) self.auth_failed = False self.protocol.makeConnection(self.transport) self.assertEqual(self.transport.value(), 'PROTOCOLINFO 1\r\n') self.send('250-PROTOCOLINFO 1') self.send('250-AUTH METHODS=HASHEDPASSWORD') self.send('250-VERSION Tor="0.2.2.34"') self.send('250 OK') self.assertTrue(self.auth_failed)
class FakeReactorTcp(object): failures = 0 _port_generator = port_generator() def __init__(self, test): self.protocol = TorControlProtocol() self.protocol.connectionMade = lambda: None self.transport = proto_helpers.StringTransport() self.transport.protocol = self.protocol def blam(): self.protocol.outReceived(b"Bootstrap") self.transport.closeStdin = blam self.protocol.makeConnection(self.transport) self.test = test def spawnProcess(self, processprotocol, bin, args, env, path, uid=None, gid=None, usePTY=None, childFDs=None): self.protocol = processprotocol self.protocol.makeConnection(self.transport) self.transport.process_protocol = processprotocol return self.transport def addSystemEventTrigger(self, *args): self.test.assertEqual(args[0], 'before') self.test.assertEqual(args[1], 'shutdown') # we know this is just for the temporary file cleanup, so we # nuke it right away to avoid polluting /tmp by calling the # callback now. args[2]() def listenTCP(self, port, factory, **kwargs): '''returns IListeningPort''' if self.failures > 0: self.failures -= 1 raise error.CannotListenError(None, None, None) if port == 0: port = next(self._port_generator) p = FakeListeningPort(port) p.factory = factory p.startListening() return p def connectTCP(self, host, port, factory, timeout, bindAddress): '''should return IConnector''' r = tcp.Connector( host, port, factory, timeout, bindAddress, reactor=self ) def blam(*args): print("BLAAAAAM", args) r.connect = blam return r def connectUNIX(self, address, factory, timeout=30, checkPID=0): '''should return IConnector''' r = unix.Connector( address, factory, timeout, self, checkPID, ) def blam(*args): print("BLAAAAAM", args) r.connect = blam return r
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)