def test_int_validator(self): self.protocol.answers.append('config/names=\nfoo Integer') self.protocol.answers.append({'foo': '123'}) conf = TorConfig(self.protocol) conf.foo = 2.33 conf.save() self.assertEqual(conf.foo, 2) conf.foo = '1' conf.save() self.assertEqual(conf.foo, 1) conf.foo = '-100' conf.save() self.assertEqual(conf.foo, -100) conf.foo = 0 conf.save() self.assertEqual(conf.foo, 0) conf.foo = '0' conf.save() self.assertEqual(conf.foo, 0) for value in ('no', 'Not a value', None): try: conf.foo = value except (ValueError, TypeError): pass else: self.fail("No excpetion thrown")
def test_save_no_protocol(self): conf = TorConfig() conf.HiddenServices = [ HiddenService(conf, '/fake/path', ['80 127.0.0.1:1234']) ] conf.save()
def test_hidden_service_parse_error(self): conf = TorConfig(FakeControlProtocol(['config/names='])) try: conf._setup_hidden_services('''FakeHiddenServiceKey=foo''') self.fail() except RuntimeError, e: self.assertTrue('parse' in str(e))
def test_no_tor_binary(self): """FIXME: do I really need all this crap in here?""" from txtorcon import torconfig oldone = torconfig.find_tor_binary self.transport = proto_helpers.StringTransport() config = TorConfig() d = None 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 try: self.protocol = FakeControlProtocol([]) torconfig.find_tor_binary = lambda: None trans = FakeProcessTransport() trans.protocol = self.protocol self.othertrans = trans creator = functools.partial(Connector(), self.protocol, self.transport) try: d = launch_tor(config, FakeReactor(self, trans, lambda x: None), connection_creator=creator) self.fail() except TorNotFound: pass # success! finally: torconfig.find_tor_binary = oldone return d
def test_invalid_parser(self): self.protocol.answers.append( 'config/names=\nSomethingExciting NonExistantParserType\nOK') TorConfig(self.protocol) errs = self.flushLoggedErrors() self.assertEqual(len(errs), 1) self.assertTrue('NonExistantParserType' in str(errs[0]))
def test_ephemeral_remove_not_ok(self): protocol = FakeControlProtocol([]) config = TorConfig(protocol) eph_d = EphemeralOnionService.create( Mock(), config, ports=["80 127.0.0.1:80"], ) cmd, d = protocol.commands[0] self.assertEqual(u"ADD_ONION NEW:BEST Port=80,127.0.0.1:80", cmd) d.callback("PrivateKey={}\nServiceID={}".format(_test_private_key_blob, _test_onion_id)) cb = protocol.events['HS_DESC'] for x in range(6): cb('UPLOAD {} UNKNOWN hsdir_{}'.format(_test_onion_id, x)) for x in range(6): cb('UPLOADED {} UNKNOWN hsdir_{}'.format(_test_onion_id, x)) hs = yield eph_d remove_d = hs.remove() cmd, d = protocol.commands[-1] self.assertEqual(u"DEL_ONION {}".format(_test_onion_id), cmd) d.callback('bad stuff') with self.assertRaises(RuntimeError): yield remove_d
def test_set_ports(self): protocol = FakeControlProtocol([]) config = TorConfig(protocol) hsdir = self.mktemp() os.mkdir(hsdir) with open(join(hsdir, 'hs_ed25519_secret_key'), 'wb') as f: f.write(b'\x01\x02\x03\x04') with open(join(hsdir, 'hostname'), 'w') as f: f.write('{}.onion'.format(_test_onion_id)) hs_d = FilesystemOnionService.create( Mock(), config, hsdir=hsdir, ports=["80 127.0.0.1:4321"], version=3, ) # arrange HS_DESC callbacks so we get the hs instance back cb = protocol.events['HS_DESC'] cb('UPLOAD {} UNKNOWN hsdir0'.format(_test_onion_id)) cb('UPLOADED {} UNKNOWN hsdir0'.format(_test_onion_id)) hs = yield hs_d hs.ports = ["443 127.0.0.1:443"] self.assertEqual(1, len(hs.ports))
def setUp(self): from txtorcon import endpoints endpoints._global_tor_config = None del endpoints._global_tor_lock endpoints._global_tor_lock = defer.DeferredLock() self.reactor = FakeReactorTcp(self) self.protocol = FakeControlProtocol([]) self.protocol.event_happened('INFO', 'something craaaaaaazy') self.protocol.event_happened( 'INFO', 'connection_dir_client_reached_eof(): Uploaded rendezvous ' 'descriptor (status 200 ("Service descriptor (v2) stored"))' ) self.config = TorConfig(self.protocol) self.protocol.answers.append( 'config/names=\nHiddenServiceOptions Virtual\nControlPort LineList' ) self.protocol.answers.append('HiddenServiceOptions') # why do i have to pass a dict for this V but not this ^ self.protocol.answers.append({'ControlPort': '37337'}) self.patcher = patch( 'txtorcon.controller.find_tor_binary', return_value='/not/tor' ) self.patcher.start()
def setupCollector(tor_process_protocol): def setup_complete(port): print("Exposed collector Tor hidden service on httpo://%s" % port.onion_uri) tempfile.tempdir = os.path.join(_repo_dir, 'tmp') if not os.path.isdir(tempfile.gettempdir()): os.makedirs(tempfile.gettempdir()) _temp_dir = tempfile.mkdtemp() if config.main.tor_datadir is None: log.warn("Option 'tor_datadir' in oonib.conf is unspecified!") log.msg("Creating tmp directory in current directory for datadir.") log.debug("Using %s" % _temp_dir) datadir = _temp_dir else: datadir = config.main.tor_datadir torconfig = TorConfig(tor_process_protocol.tor_protocol) public_port = 80 # XXX there is currently a bug in txtorcon that prevents data_dir from # being passed properly. Details on the bug can be found here: # https://github.com/meejah/txtorcon/pull/22 hs_endpoint = TCPHiddenServiceEndpoint(reactor, torconfig, public_port, data_dir=datadir) hidden_service = hs_endpoint.listen(reportingBackend) hidden_service.addCallback(setup_complete) hidden_service.addErrback(txSetupFailed)
def setUp(self): reactor = Mock() proto = Mock() directlyProvides(proto, ITorControlProtocol) self.cfg = TorConfig() self.cfg.HidServAuth = ["existing.onion some_token"] self.tor = Tor(reactor, proto, _tor_config=self.cfg)
def test_parse_user_path(self, ftb): # this makes sure we expand users and symlinks in # hiddenServiceDir args. see Issue #77 # make sure we have a valid thing from get_global_tor without # actually launching tor config = TorConfig() config.post_bootstrap = defer.succeed(config) from txtorcon import torconfig torconfig._global_tor_config = None get_global_tor( self.reactor, _tor_launcher=lambda react, config, prog: defer.succeed(config) ) ep = serverFromString( self.reactor, 'onion:88:localPort=1234:hiddenServiceDir=~/blam/blarg' ) # would be nice to have a fixed path here, but then would have # to run as a known user :/ # maybe using the docker stuff to run integration tests better here? self.assertEqual( os.path.expanduser('~/blam/blarg'), ep.hidden_service_dir )
def test_onion_keys(self): # FIXME test without crapping on filesystem self.protocol.answers.append('HiddenServiceDir=/fake/path\n') d = tempfile.mkdtemp() try: with open(os.path.join(d, 'hostname'), 'w') as f: f.write('public') with open(os.path.join(d, 'private_key'), 'w') as f: f.write('private') with open(os.path.join(d, 'client_keys'), 'w') as f: f.write('client-name hungry\ndescriptor-cookie omnomnom\n') conf = TorConfig(self.protocol) hs = HiddenService(conf, d, []) self.assertEqual(hs.hostname, 'public') self.assertEqual(hs.private_key, 'private') self.assertEqual(len(hs.client_keys), 1) self.assertEqual(hs.client_keys[0].name, 'hungry') self.assertEqual(hs.client_keys[0].cookie, 'omnomnom') self.assertEqual(hs.client_keys[0].key, None) finally: shutil.rmtree(d, ignore_errors=True)
def test_immediate_hiddenservice_append(self): '''issue #88. we check that a .append(hs) works on a blank TorConfig''' conf = TorConfig() hs = HiddenService(conf, '/dev/null', ['80 127.0.0.1:1234']) conf.HiddenServices.append(hs) self.assertEqual(len(conf.HiddenServices), 1) self.assertEqual(conf.HiddenServices[0], hs)
def test_iteration(self): conf = TorConfig() conf.SOCKSPort = 9876 conf.save() x = list(conf) self.assertEqual(x, ['SOCKSPort']) conf.save()
def test_set_dir(self): protocol = FakeControlProtocol([]) config = TorConfig(protocol) hsdir0 = self.mktemp() os.mkdir(hsdir0) hsdir1 = self.mktemp() os.mkdir(hsdir1) with open(join(hsdir0, "hostname"), "w") as f: f.write('{}.onion'.format(_test_onion_id)) hs_d = FilesystemOnionService.create( Mock(), config, hsdir=hsdir0, ports=["80 127.0.0.1:4321"], version=3, ) # arrange HS_DESC callbacks so we get the hs instance back cb = protocol.events['HS_DESC'] cb('UPLOAD {} UNKNOWN hsdir0'.format(_test_onion_id)) cb('UPLOADED {} UNKNOWN hsdir0'.format(_test_onion_id)) hs = yield hs_d hs.dir = hsdir1 self.assertEqual(hs.dir, hsdir1)
def startTor(): def updates(prog, tag, summary): print("%d%%: %s" % (prog, summary)) tempfile.tempdir = os.path.join(_repo_dir, 'tmp') if not os.path.isdir(tempfile.gettempdir()): os.makedirs(tempfile.gettempdir()) _temp_dir = tempfile.mkdtemp() torconfig = TorConfig() torconfig.SocksPort = config.main.socks_port if config.main.tor2webmode: torconfig.Tor2webMode = 1 torconfig.CircuitBuildTimeout = 60 if config.main.tor_datadir is None: log.warn("Option 'tor_datadir' in oonib.conf is unspecified!") log.msg("Creating tmp directory in current directory for datadir.") log.debug("Using %s" % _temp_dir) datadir = _temp_dir else: datadir = config.main.tor_datadir torconfig.DataDirectory = datadir torconfig.save() if config.main.tor_binary is not None: d = launch_tor(torconfig, reactor, tor_binary=config.main.tor_binary, progress_updates=updates) else: d = launch_tor(torconfig, reactor, progress_updates=updates) d.addCallback(setupCollector, datadir) if ooniBouncer: d.addCallback(setupBouncer, datadir) d.addErrback(txSetupFailed)
def test_ephemeral_bad_return_value(self): protocol = FakeControlProtocol([]) config = TorConfig(protocol) progress_messages = [] def progress(*args): progress_messages.append(args) eph_d = EphemeralOnionService.create( Mock(), config, ports=["80 127.0.0.1:80"], progress=progress, private_key=DISCARD, ) cmd, d = protocol.commands[0] self.assertEqual(u"ADD_ONION NEW:BEST Port=80,127.0.0.1:80 Flags=DiscardPK", cmd) d.callback("BadKey=nothing") def check(f): self.assertIn("Expected ADD_ONION to return ServiceID", str(f.value)) return None eph_d.addCallbacks(self.fail, check) return eph_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_version_v3_progress(self): protocol = FakeControlProtocol([]) config = TorConfig(protocol) hsdir = self.mktemp() os.mkdir(hsdir) with open(join(hsdir, "hostname"), "w") as f: f.write('{}.onion'.format(_test_onion_id)) def my_progress(a, b, c): pass eph_d = FilesystemOnionService.create( Mock(), config, hsdir, ports=["80 127.0.0.1:80"], progress=my_progress, version=3, ) # arrange HS_DESC callbacks so we get the hs instance back cb = protocol.events['HS_DESC'] cb('UPLOAD {} UNKNOWN hsdir0'.format(_test_onion_id)) cb('UPLOADED {} UNKNOWN hsdir0'.format(_test_onion_id)) yield eph_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') 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_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_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_float_parser_error(self): self.protocol.answers.append('config/names=\nfoo Float\nOK') self.protocol.answers.append({'foo': '1.23fff'}) TorConfig(self.protocol) errs = self.flushLoggedErrors(ValueError) self.assertEqual(len(errs), 1) self.assertTrue(isinstance(errs[0].value, ValueError))
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))
def test_options_hidden(self): self.protocol.answers.append( 'HiddenServiceDir=/fake/path\nHiddenServicePort=80 127.0.0.1:1234\n' ) conf = TorConfig(self.protocol) self.assertTrue('HiddenServiceOptions' not in conf.config) self.assertEqual(len(conf.HiddenServices), 1) self.assertTrue(not conf.needs_save()) conf.hiddenservices.append( HiddenService(conf, '/some/dir', '80 127.0.0.1:2345', 'auth', 2)) conf.hiddenservices[0].ports.append('443 127.0.0.1:443') self.assertTrue(conf.needs_save()) conf.save() self.assertEqual(len(self.protocol.sets), 7) self.assertEqual(self.protocol.sets[0], ('HiddenServiceDir', '/fake/path')) self.assertEqual(self.protocol.sets[1], ('HiddenServicePort', '80 127.0.0.1:1234')) self.assertEqual(self.protocol.sets[2], ('HiddenServicePort', '443 127.0.0.1:443')) self.assertEqual(self.protocol.sets[3], ('HiddenServiceDir', '/some/dir')) self.assertEqual(self.protocol.sets[4], ('HiddenServicePort', '80 127.0.0.1:2345')) self.assertEqual(self.protocol.sets[5], ('HiddenServiceVersion', '2')) self.assertEqual(self.protocol.sets[6], ('HiddenServiceAuthorizeClient', 'auth'))
def test_parse_relative_path(self): # this makes sure we convert a relative path to absolute # hiddenServiceDir args. see Issue #77 # make sure we have a valid thing from get_global_tor without # actually launching tor config = TorConfig() config.post_bootstrap = defer.succeed(config) from txtorcon import torconfig torconfig._global_tor_config = None get_global_tor( self.reactor, _tor_launcher=lambda react, config, prog: defer.succeed(config)) orig = os.path.realpath('.') try: with util.TempDir() as t: t = str(t) os.chdir(t) os.mkdir(os.path.join(t, 'foo')) hsdir = os.path.join(t, 'foo', 'blam') os.mkdir(hsdir) ep = serverFromString( self.reactor, 'onion:88:localPort=1234:hiddenServiceDir=foo/blam') self.assertEqual(os.path.realpath(hsdir), ep.hidden_service_dir) finally: os.chdir(orig)
def test_add_hidden_service_to_empty_config(self): conf = TorConfig() h = HiddenService(conf, '/fake/path', ['80 127.0.0.1:1234'], '', 3) conf.hiddenservices.append(h) self.assertEqual(len(conf.hiddenservices), 1) self.assertEqual(h, conf.hiddenservices[0]) self.assertTrue(conf.needs_save())
def test_stealth_auth(self): ''' make sure we produce a HiddenService instance with stealth-auth lines if we had authentication specified in the first place. ''' config = TorConfig(self.protocol) ep = TCPHiddenServiceEndpoint(self.reactor, config, 123, '/dev/null', stealth_auth=['alice', 'bob']) # make sure listen() correctly configures our hidden-serivce # with the explicit directory we passed in above d = ep.listen(NoOpProtocolFactory()) def foo(fail): print "ERROR", fail d.addErrback(foo) port = yield d self.assertEqual(1, len(config.HiddenServices)) self.assertEqual(config.HiddenServices[0].dir, '/dev/null') self.assertEqual(config.HiddenServices[0].authorize_client[0], 'stealth alice,bob') self.assertEqual(None, ep.onion_uri) config.HiddenServices[0].hostname = 'oh my' self.assertEqual('oh my', ep.onion_uri)
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_version_v3_progress_await_all(self): protocol = FakeControlProtocol([]) config = TorConfig(protocol) hsdir = self.mktemp() os.mkdir(hsdir) with open(join(hsdir, "hostname"), "w") as f: f.write('{}.onion'.format(_test_onion_id)) class Bad(Exception): pass def my_progress(a, b, c): raise Bad("it's bad") eph_d = FilesystemOnionService.create( Mock(), config, hsdir, ports=["80 127.0.0.1:80"], progress=my_progress, version=3, await_all_uploads=True, ) # arrange HS_DESC callbacks so we get the hs instance back cb = protocol.events['HS_DESC'] cb('UPLOAD {} UNKNOWN hsdir0'.format(_test_onion_id)) cb('UPLOADED {} UNKNOWN hsdir0'.format(_test_onion_id)) yield eph_d errs = self.flushLoggedErrors(Bad) self.assertEqual(3, len(errs)) # because there's a "100%" one too