def _create_socks_endpoint(reactor, control_protocol, socks_config=None): """ Internal helper. This uses an already-configured SOCKS endpoint from the attached Tor, or creates a new TCP one (and configures Tor with it). If socks_config is non-None, it is a SOCKSPort line and will either be used if it already exists or will be created. """ socks_ports = yield control_protocol.get_conf('SOCKSPort') if socks_ports: socks_ports = list(socks_ports.values())[0] if not isinstance(socks_ports, list): socks_ports = [socks_ports] else: # return from get_conf was an empty dict; we want a list socks_ports = [] # everything in the SocksPort list can include "options" after the # initial value. We don't care about those, but do need to strip # them. socks_ports = [port.split()[0] for port in socks_ports] # could check platform? but why would you have unix ports on a # platform that doesn't? unix_ports = set([p.startswith('unix:') for p in socks_ports]) tcp_ports = set(socks_ports) - unix_ports socks_endpoint = None for p in list(unix_ports) + list(tcp_ports): # prefer unix-ports if socks_config and p != socks_config: continue try: socks_endpoint = _endpoint_from_socksport_line(reactor, p) except Exception as e: log.msg("clientFromString('{}') failed: {}".format(p, e)) # if we still don't have an endpoint, nothing worked (or there # were no SOCKSPort lines at all) so we add config to tor if socks_endpoint is None: if socks_config is None: # is a unix-socket in /tmp on a supported platform better than # this? port = yield available_tcp_port(reactor) socks_config = str(port) socks_ports.append(socks_config) # NOTE! We must set all the ports in one command or we'll # destroy pre-existing config args = [] for p in socks_ports: args.append('SOCKSPort') args.append(p) yield control_protocol.set_conf(*args) socks_endpoint = _endpoint_from_socksport_line(reactor, socks_config) defer.returnValue(socks_endpoint)
def _create_default_config(reactor, control_port=None): """ Internal method to create a new TorConfig instance with defaults. """ config = TorConfig() if control_port is None: control_port = yield available_tcp_port(reactor) config.ControlPort = control_port config.SOCKSPort = 0 defer.returnValue(config)
def _create_default_config(reactor, control_port=None): """ Internal method to create a new TorConfig instance with defaults. """ config = TorConfig() if control_port is None: control_port = yield available_tcp_port(reactor) config.ControlPort = control_port config.SOCKSPort = 0 defer.returnValue(config)
def setUp(self): yield super(TestOnionRoutedAgent, self).setUp() class DummyResource(Resource): isLeaf = True def render_GET(self, request): return "%s" % request.method self.port = yield available_tcp_port(reactor) self.site = Site(DummyResource()) self.test_service = yield reactor.listenTCP(self.port, self.site)
def setUp(self): yield super(TestOnionRoutedAgent, self).setUp() class DummyResource(Resource): isLeaf = True def render_GET(self, request): return "%s" % request.method self.port = yield available_tcp_port(reactor) self.site = Site(DummyResource()) self.test_service = yield reactor.listenTCP(self.port, self.site)
def setconf_singleport_exit(tor): port = available_tcp_port(reactor) def add_single_port_exit(port): tor.protocol.set_conf('PublishServerDescriptor', '0', 'PortForwarding', '1', 'AssumeReachable' ,'1', 'ClientRejectInternalAddresses', '0', 'OrPort', 'auto', 'ExitPolicyRejectPrivate', '0', 'ExitPolicy', 'accept 127.0.0.1:{}, reject *:*'.format(port)) return port.addCallback(add_single_port_exit).addCallback( lambda ign: tor.routers[tor.protocol.get_info("fingerprint")])
def _validate_ports(reactor, ports): """ Internal helper for Onion services. Validates an incoming list of port mappings and returns a list of strings suitable for passing to other onion-services functions. Accepts 3 different ways of specifying ports: - list of ints: each int is the public port, local port random - list of 2-tuples of ints: (pubic, local) ports. - list of strings like "80 127.0.0.1:1234" This is async in case it needs to ask for a random, unallocated local port. """ if not isinstance(ports, (list, tuple)): raise ValueError("'ports' must be a list of strings, ints or 2-tuples") processed_ports = [] for port in ports: if isinstance(port, (set, list, tuple)): if len(port) != 2: raise ValueError( "'ports' must contain a single int or a 2-tuple of ints") remote, local = port try: remote = int(remote) except ValueError: raise ValueError("'ports' has a tuple with a non-integer " "component: {}".format(port)) try: local = int(local) except ValueError: if not local.startswith('unix:/'): raise ValueError("local port must be either an integer" " or start with unix:/") processed_ports.append("{} {}".format(remote, local)) else: processed_ports.append("{} 127.0.0.1:{}".format(remote, local)) elif isinstance(port, (six.text_type, str)): _validate_single_port_string(port) processed_ports.append(port) else: try: remote = int(port) except (ValueError, TypeError): raise ValueError( "'ports' has a non-integer entry: {}".format(port)) local = yield available_tcp_port(reactor) processed_ports.append("{} 127.0.0.1:{}".format(remote, local)) defer.returnValue(processed_ports)
def setconf_singleport_exit(tor): port = available_tcp_port(reactor) def add_single_port_exit(port): tor.protocol.set_conf('PublishServerDescriptor', '0', 'PortForwarding', '1', 'AssumeReachable', '1', 'ClientRejectInternalAddresses', '0', 'OrPort', 'auto', 'ExitPolicyRejectPrivate', '0', 'ExitPolicy', 'accept 127.0.0.1:{}, reject *:*'.format(port)) return port.addCallback(add_single_port_exit).addCallback( lambda ign: tor.routers[tor.protocol.get_info("fingerprint")])
def setUp(self): yield super(TestStreamBandwidthListener, self).setUp() self.fetch_size = 8*2**20 # 8MB self.stream_bandwidth_listener = yield StreamBandwidthListener(self.tor) class DummyResource(Resource): isLeaf = True def render_GET(self, request): return 'a'*8*2**20 self.port = yield available_tcp_port(reactor) self.site = Site(DummyResource()) self.test_service = yield reactor.listenTCP(self.port, self.site) self.not_enough_measurements = NotEnoughMeasurements( "Not enough measurements to calculate STREAM_BW samples.")
def test_real_addr(self): # FIXME should choose a port which definitely isn't used. # it's apparently frowned upon to use the "real" reactor in # tests, but I was using "nc" before, and I think this is # preferable. from twisted.internet import reactor port = yield available_tcp_port(reactor) ep = TCP4ServerEndpoint(reactor, port) listener = yield ep.listen(FakeProtocolFactory()) try: pid = process_from_address('0.0.0.0', port, self.fakestate) finally: listener.stopListening() self.assertEqual(pid, os.getpid())
def setUp(self): yield super(TestBwscan, self).setUp() yield setconf_fetch_all_descs(self.tor) class DummyResource(Resource): isLeaf = True def render_GET(self, request): size = request.uri.split('/')[-1] if 'k' in size: size = int(size[:-1])*(2**10) elif 'M' in size: size = int(size[:-1])*(2**20) return 'a'*size self.port = yield available_tcp_port(reactor) self.test_service = yield reactor.listenTCP( self.port, Site(DummyResource()))
def test_real_addr(self): # FIXME should choose a port which definitely isn't used. # it's apparently frowned upon to use the "real" reactor in # tests, but I was using "nc" before, and I think this is # preferable. from twisted.internet import reactor port = yield available_tcp_port(reactor) ep = TCP4ServerEndpoint(reactor, port) listener = yield ep.listen(FakeProtocolFactory()) try: pid = process_from_address('0.0.0.0', port, self.fakestate) finally: listener.stopListening() self.assertEqual(pid, os.getpid())
def setUp(self): yield super(TestStreamBandwidthListener, self).setUp() self.fetch_size = 8 * 2**20 # 8MB self.stream_bandwidth_listener = yield StreamBandwidthListener( self.tor) class DummyResource(Resource): isLeaf = True def render_GET(self, request): return 'a' * 8 * 2**20 self.port = yield available_tcp_port(reactor) self.site = Site(DummyResource()) self.test_service = yield reactor.listenTCP(self.port, self.site) self.not_enough_measurements = NotEnoughMeasurements( "Not enough measurements to calculate STREAM_BW samples.")
def _default_socks_endpoint(self): """ Returns a Deferred that fires with our default SOCKS endpoint (which might mean setting one up in our attacked Tor if it doesn't have one) """ if self._socks_endpoint is not None: returnValue(self._socks_endpoint) else: try: ep = self._config.socks_endpoint(self._reactor) except RuntimeError: # should we try to use unix socket on platforms that # support it? port = yield available_tcp_port(self._reactor) ep = yield self._config.create_socks_endpoint( self._reactor, str(port)) self._socks_endpoint = ep returnValue(self._socks_endpoint)
def launch( reactor, progress_updates=None, control_port=None, data_directory=None, socks_port=None, stdout=None, stderr=None, timeout=None, tor_binary=None, user=None, # XXX like the config['User'] special-casing from before # 'users' probably never need these: connection_creator=None, kill_on_stderr=True, _tor_config=None, # a TorConfig instance, mostly for tests ): """ launches a new Tor process, and returns a Deferred that fires with a new :class:`txtorcon.Tor` instance. From this instance, you can create or get any "interesting" instances you need: the :class:`txtorcon.TorConfig` instance, create endpoints, create :class:`txtorcon.TorState` instance(s), etc. Note that there is NO way to pass in a config; we only expost a couple of basic Tor options. If you need anything beyond these, you can access the ``TorConfig`` instance (via ``.config``) and make any changes there, reflecting them in tor with ``.config.save()``. You can igore all the options and safe defaults will be provided. However, **it is recommended to pass data_directory** especially if you will be starting up Tor frequently, as it saves a bunch of time (and bandwidth for the directory authorities). "Safe defaults" means: - a tempdir for a ``DataDirectory`` is used (respecting ``TMP``) and is deleted when this tor is shut down (you therefore *probably* want to supply the ``data_directory=`` kwarg); - a random, currently-unused local TCP port is used as the ``SocksPort`` (specify ``socks_port=`` if you want your own). If you want no SOCKS listener at all, pass ``socks_port=0`` - we set ``__OwningControllerProcess`` and call ``TAKEOWNERSHIP`` so that if our control connection goes away, tor shuts down (see `control-spec <https://gitweb.torproject.org/torspec.git/blob/HEAD:/control-spec.txt>`_ 3.23). - the launched Tor will use ``COOKIE`` authentication. :param reactor: a Twisted IReactorCore implementation (usually twisted.internet.reactor) :param progress_updates: a callback which gets progress updates; gets 3 args: percent, tag, summary (FIXME make an interface for this). :param data_directory: set as the ``DataDirectory`` option to Tor, this is where tor keeps its state information (cached relays, etc); starting with an already-populated state directory is a lot faster. If ``None`` (the default), we create a tempdir for this **and delete it on exit**. It is recommended you pass something here. :param stdout: a file-like object to which we write anything that Tor prints on stdout (just needs to support write()). :param stderr: a file-like object to which we write anything that Tor prints on stderr (just needs .write()). Note that we kill Tor off by default if anything appears on stderr; pass "kill_on_stderr=False" if you don't want this behavior. :param tor_binary: path to the Tor binary to run. If None (the default), we try to find the tor binary. :param kill_on_stderr: When True (the default), if Tor prints anything on stderr we kill off the process, close the TorControlProtocol and raise an exception. :param connection_creator: is mostly available to ease testing, so you probably don't want to supply this. If supplied, it is a callable that should return a Deferred that delivers an :api:`twisted.internet.interfaces.IProtocol <IProtocol>` or ConnectError. See :api:`twisted.internet.interfaces.IStreamClientEndpoint`.connect Note that this parameter is ignored if config.ControlPort == 0 :return: a Deferred which callbacks with :class:`txtorcon.Tor` instance, from which you can retrieve the TorControlProtocol instance via the ``.protocol`` property. HACKS: 1. It's hard to know when Tor has both (completely!) written its authentication cookie file AND is listening on the control port. It seems that waiting for the first 'bootstrap' message on stdout is sufficient. Seems fragile...and doesn't work 100% of the time, so FIXME look at Tor source. XXX this "User" thing was, IIRC, a feature for root-using scripts (!!) that were going to launch tor, but where tor would drop to a different user. Do we still want to support this? Probably relevant to Docker (where everything is root! yay!) ``User``: if this exists, we attempt to set ownership of the tempdir to this user (but only if our effective UID is 0). """ # We have a slight problem with the approach: we need to pass a # few minimum values to a torrc file so that Tor will start up # enough that we may connect to it. Ideally, we'd be able to # start a Tor up which doesn't really do anything except provide # "AUTHENTICATE" and "GETINFO config/names" so we can do our # config validation. if not IReactorCore.providedBy(reactor): raise ValueError("'reactor' argument must provide IReactorCore" " (got '{}': {})".format( type(reactor).__class__.__name__, repr(reactor))) if tor_binary is None: tor_binary = find_tor_binary() if tor_binary is None: # We fail right here instead of waiting for the reactor to start raise TorNotFound('Tor binary could not be found') # make sure we got things that have write() for stderr, stdout # kwargs (XXX is there a "better" way to check for file-like object?) for arg in [stderr, stdout]: if arg and not getattr(arg, "write", None): raise RuntimeError( 'File-like object needed for stdout or stderr args.') config = _tor_config or TorConfig() if data_directory is not None: user_set_data_directory = True config.DataDirectory = data_directory try: os.mkdir(data_directory, 0o0700) except OSError: pass else: user_set_data_directory = False data_directory = tempfile.mkdtemp(prefix='tortmp') config.DataDirectory = data_directory # note: we also set up the ProcessProtocol to delete this when # Tor exits, this is "just in case" fallback: reactor.addSystemEventTrigger( 'before', 'shutdown', functools.partial(delete_file_or_tree, data_directory)) # things that used launch_tor() had to set ControlPort and/or # SocksPort on the config to pass them, so we honour that here. if control_port is None and _tor_config is not None: try: control_port = config.ControlPort except KeyError: control_port = None if socks_port is None and _tor_config is not None: try: socks_port = config.SocksPort except KeyError: socks_port = None if socks_port is None: socks_port = yield available_tcp_port(reactor) config.SOCKSPort = socks_port try: our_user = user or config.User except KeyError: pass else: if sys.platform in ('linux', 'linux2', 'darwin') and os.geteuid() == 0: os.chown(data_directory, pwd.getpwnam(our_user).pw_uid, -1) # user can pass in a control port, or we set one up here if control_port is None: # on posix-y systems, we can use a unix-socket if sys.platform in ('linux', 'linux2', 'darwin'): # note: tor will not accept a relative path for ControlPort control_port = 'unix:{}'.format( os.path.join(os.path.realpath(data_directory), 'control.socket')) else: control_port = yield available_tcp_port(reactor) else: if str(control_port).startswith('unix:'): control_path = control_port.lstrip('unix:') containing_dir = dirname(control_path) if not exists(containing_dir): raise ValueError( "The directory containing '{}' must exist".format( containing_dir)) # Tor will be sad if the directory isn't 0700 mode = (0o0777 & os.stat(containing_dir).st_mode) if mode & ~(0o0700): raise ValueError( "The directory containing a unix control-socket ('{}') " "must only be readable by the user".format(containing_dir)) config.ControlPort = control_port config.CookieAuthentication = 1 config.__OwningControllerProcess = os.getpid() if connection_creator is None: if str(control_port).startswith('unix:'): connection_creator = functools.partial( UNIXClientEndpoint(reactor, control_port[5:]).connect, TorProtocolFactory()) else: connection_creator = functools.partial( TCP4ClientEndpoint(reactor, 'localhost', control_port).connect, TorProtocolFactory()) # not an "else" on purpose; if we passed in "control_port=0" *and* # a custom connection creator, we should still set this to None so # it's never called (since we can't connect with ControlPort=0) if control_port == 0: connection_creator = None # NOTE well, that if we don't pass "-f" then Tor will merrily load # its default torrc, and apply our options over top... :/ should # file a bug probably? --no-defaults or something maybe? (does # --defaults-torrc - or something work?) config_args = [ '-f', '/dev/null/non-existant-on-purpose', '--ignore-missing-torrc' ] # ...now add all our config options on the command-line. This # avoids writing a temporary torrc. for (k, v) in config.config_args(): config_args.append(k) config_args.append(v) process_protocol = TorProcessProtocol( connection_creator, progress_updates, config, reactor, timeout, kill_on_stderr, stdout, stderr, ) if control_port == 0: connected_cb = succeed(None) else: connected_cb = process_protocol.when_connected() # we set both to_delete and the shutdown events because this # process might be shut down way before the reactor, but if the # reactor bombs out without the subprocess getting closed cleanly, # we'll want the system shutdown events triggered so the temporary # files get cleaned up either way # we don't want to delete the user's directories, just temporary # ones this method created. if not user_set_data_directory: process_protocol.to_delete = [data_directory] reactor.addSystemEventTrigger( 'before', 'shutdown', functools.partial(delete_file_or_tree, data_directory)) log.msg('Spawning tor process with DataDirectory', data_directory) args = [tor_binary] + config_args # XXX note to self; we create data_directory above, so when this # is master we can close # https://github.com/meejah/txtorcon/issues/178 transport = reactor.spawnProcess( process_protocol, tor_binary, args=args, env={'HOME': data_directory}, path=data_directory if os.path.exists(data_directory) else None, # XXX error if it doesn't exist? ) # FIXME? don't need rest of the args: uid, gid, usePTY, childFDs) transport.closeStdin() proto = yield connected_cb # note "proto" here is a TorProcessProtocol # we might need to attach this protocol to the TorConfig if config.protocol is None and proto is not None and proto.tor_protocol is not None: # proto is None in the ControlPort=0 case yield config.attach_protocol(proto.tor_protocol) # note that attach_protocol waits for the protocol to be # boostrapped if necessary returnValue( Tor( reactor, config.protocol, _tor_config=config, _process_proto=process_protocol, ))
def get_random_tor_ports(): d2 = available_tcp_port(reactor) d2.addCallback(lambda port: config.__setattr__('SocksPort', port)) d2.addCallback(lambda _: available_tcp_port(reactor)) d2.addCallback(lambda port: config.__setattr__('ControlPort', port)) return d2
def get_random_tor_ports(): d2 = available_tcp_port(reactor) d2.addCallback(lambda port: config.__setattr__('SocksPort', port)) d2.addCallback(lambda _: available_tcp_port(reactor)) d2.addCallback(lambda port: config.__setattr__('ControlPort', port)) return d2
def launch(reactor, progress_updates=None, control_port=None, data_directory=None, socks_port=None, stdout=None, stderr=None, timeout=None, tor_binary=None, user=None, # XXX like the config['User'] special-casing from before # 'users' probably never need these: connection_creator=None, kill_on_stderr=True, _tor_config=None, # a TorConfig instance, mostly for tests ): """ launches a new Tor process, and returns a Deferred that fires with a new :class:`txtorcon.Tor` instance. From this instance, you can create or get any "interesting" instances you need: the :class:`txtorcon.TorConfig` instance, create endpoints, create :class:`txtorcon.TorState` instance(s), etc. Note that there is NO way to pass in a config; we only expost a couple of basic Tor options. If you need anything beyond these, you can access the ``TorConfig`` instance (via ``.config``) and make any changes there, reflecting them in tor with ``.config.save()``. You can igore all the options and safe defaults will be provided. However, **it is recommended to pass data_directory** especially if you will be starting up Tor frequently, as it saves a bunch of time (and bandwidth for the directory authorities). "Safe defaults" means: - a tempdir for a ``DataDirectory`` is used (respecting ``TMP``) and is deleted when this tor is shut down (you therefore *probably* want to supply the ``data_directory=`` kwarg); - a random, currently-unused local TCP port is used as the ``SocksPort`` (specify ``socks_port=`` if you want your own). If you want no SOCKS listener at all, pass ``socks_port=0`` - we set ``__OwningControllerProcess`` and call ``TAKEOWNERSHIP`` so that if our control connection goes away, tor shuts down (see `control-spec <https://gitweb.torproject.org/torspec.git/blob/HEAD:/control-spec.txt>`_ 3.23). - the launched Tor will use ``COOKIE`` authentication. :param reactor: a Twisted IReactorCore implementation (usually twisted.internet.reactor) :param progress_updates: a callback which gets progress updates; gets 3 args: percent, tag, summary (FIXME make an interface for this). :param data_directory: set as the ``DataDirectory`` option to Tor, this is where tor keeps its state information (cached relays, etc); starting with an already-populated state directory is a lot faster. If ``None`` (the default), we create a tempdir for this **and delete it on exit**. It is recommended you pass something here. :param stdout: a file-like object to which we write anything that Tor prints on stdout (just needs to support write()). :param stderr: a file-like object to which we write anything that Tor prints on stderr (just needs .write()). Note that we kill Tor off by default if anything appears on stderr; pass "kill_on_stderr=False" if you don't want this behavior. :param tor_binary: path to the Tor binary to run. If None (the default), we try to find the tor binary. :param kill_on_stderr: When True (the default), if Tor prints anything on stderr we kill off the process, close the TorControlProtocol and raise an exception. :param connection_creator: is mostly available to ease testing, so you probably don't want to supply this. If supplied, it is a callable that should return a Deferred that delivers an :api:`twisted.internet.interfaces.IProtocol <IProtocol>` or ConnectError. See :api:`twisted.internet.interfaces.IStreamClientEndpoint`.connect Note that this parameter is ignored if config.ControlPort == 0 :return: a Deferred which callbacks with :class:`txtorcon.Tor` instance, from which you can retrieve the TorControlProtocol instance via the ``.protocol`` property. HACKS: 1. It's hard to know when Tor has both (completely!) written its authentication cookie file AND is listening on the control port. It seems that waiting for the first 'bootstrap' message on stdout is sufficient. Seems fragile...and doesn't work 100% of the time, so FIXME look at Tor source. XXX this "User" thing was, IIRC, a feature for root-using scripts (!!) that were going to launch tor, but where tor would drop to a different user. Do we still want to support this? Probably relevant to Docker (where everything is root! yay!) ``User``: if this exists, we attempt to set ownership of the tempdir to this user (but only if our effective UID is 0). """ # We have a slight problem with the approach: we need to pass a # few minimum values to a torrc file so that Tor will start up # enough that we may connect to it. Ideally, we'd be able to # start a Tor up which doesn't really do anything except provide # "AUTHENTICATE" and "GETINFO config/names" so we can do our # config validation. if not IReactorCore.providedBy(reactor): raise ValueError( "'reactor' argument must provide IReactorCore" " (got '{}': {})".format( type(reactor).__class__.__name__, repr(reactor) ) ) if tor_binary is None: tor_binary = find_tor_binary() if tor_binary is None: # We fail right here instead of waiting for the reactor to start raise TorNotFound('Tor binary could not be found') # make sure we got things that have write() for stderr, stdout # kwargs (XXX is there a "better" way to check for file-like object?) for arg in [stderr, stdout]: if arg and not getattr(arg, "write", None): raise RuntimeError( 'File-like object needed for stdout or stderr args.' ) config = _tor_config or TorConfig() if data_directory is not None: user_set_data_directory = True config.DataDirectory = data_directory try: os.mkdir(data_directory, 0o0700) except OSError: pass else: user_set_data_directory = False data_directory = tempfile.mkdtemp(prefix='tortmp') config.DataDirectory = data_directory # note: we also set up the ProcessProtocol to delete this when # Tor exits, this is "just in case" fallback: reactor.addSystemEventTrigger( 'before', 'shutdown', functools.partial(delete_file_or_tree, data_directory) ) # things that used launch_tor() had to set ControlPort and/or # SocksPort on the config to pass them, so we honour that here. if control_port is None and _tor_config is not None: try: control_port = config.ControlPort except KeyError: control_port = None if socks_port is None and _tor_config is not None: try: socks_port = config.SocksPort except KeyError: socks_port = None if socks_port is None: socks_port = yield available_tcp_port(reactor) config.SOCKSPort = socks_port try: our_user = user or config.User except KeyError: pass else: if sys.platform in ('linux', 'linux2', 'darwin') and os.geteuid() == 0: os.chown(data_directory, pwd.getpwnam(our_user).pw_uid, -1) # user can pass in a control port, or we set one up here if control_port is None: # on posix-y systems, we can use a unix-socket if sys.platform in ('linux', 'linux2', 'darwin'): # note: tor will not accept a relative path for ControlPort control_port = 'unix:{}'.format( os.path.join(os.path.realpath(data_directory), 'control.socket') ) else: control_port = yield available_tcp_port(reactor) else: if str(control_port).startswith('unix:'): control_path = control_port.lstrip('unix:') containing_dir = dirname(control_path) if not exists(containing_dir): raise ValueError( "The directory containing '{}' must exist".format( containing_dir ) ) # Tor will be sad if the directory isn't 0700 mode = (0o0777 & os.stat(containing_dir).st_mode) if mode & ~(0o0700): raise ValueError( "The directory containing a unix control-socket ('{}') " "must only be readable by the user".format(containing_dir) ) config.ControlPort = control_port config.CookieAuthentication = 1 config.__OwningControllerProcess = os.getpid() if connection_creator is None: if str(control_port).startswith('unix:'): connection_creator = functools.partial( UNIXClientEndpoint(reactor, control_port[5:]).connect, TorProtocolFactory() ) else: connection_creator = functools.partial( TCP4ClientEndpoint(reactor, 'localhost', control_port).connect, TorProtocolFactory() ) # not an "else" on purpose; if we passed in "control_port=0" *and* # a custom connection creator, we should still set this to None so # it's never called (since we can't connect with ControlPort=0) if control_port == 0: connection_creator = None # NOTE well, that if we don't pass "-f" then Tor will merrily load # its default torrc, and apply our options over top... :/ should # file a bug probably? --no-defaults or something maybe? (does # --defaults-torrc - or something work?) config_args = ['-f', '/dev/null/non-existant-on-purpose', '--ignore-missing-torrc'] # ...now add all our config options on the command-line. This # avoids writing a temporary torrc. for (k, v) in config.config_args(): config_args.append(k) config_args.append(v) process_protocol = TorProcessProtocol( connection_creator, progress_updates, config, reactor, timeout, kill_on_stderr, stdout, stderr, ) if control_port == 0: connected_cb = succeed(None) else: connected_cb = process_protocol.when_connected() # we set both to_delete and the shutdown events because this # process might be shut down way before the reactor, but if the # reactor bombs out without the subprocess getting closed cleanly, # we'll want the system shutdown events triggered so the temporary # files get cleaned up either way # we don't want to delete the user's directories, just temporary # ones this method created. if not user_set_data_directory: process_protocol.to_delete = [data_directory] reactor.addSystemEventTrigger( 'before', 'shutdown', functools.partial(delete_file_or_tree, data_directory) ) log.msg('Spawning tor process with DataDirectory', data_directory) args = [tor_binary] + config_args # XXX note to self; we create data_directory above, so when this # is master we can close # https://github.com/meejah/txtorcon/issues/178 transport = reactor.spawnProcess( process_protocol, tor_binary, args=args, env={'HOME': data_directory}, path=data_directory if os.path.exists(data_directory) else None, # XXX error if it doesn't exist? ) # FIXME? don't need rest of the args: uid, gid, usePTY, childFDs) transport.closeStdin() proto = yield connected_cb # note "proto" here is a TorProcessProtocol # we might need to attach this protocol to the TorConfig if config.protocol is None and proto is not None and proto.tor_protocol is not None: # proto is None in the ControlPort=0 case yield config.attach_protocol(proto.tor_protocol) # note that attach_protocol waits for the protocol to be # boostrapped if necessary returnValue( Tor( reactor, config.protocol, _tor_config=config, _process_proto=process_protocol, ) )