commit 09a468b3077bfdad3f1cab3797cb41515c2cc228 Author: Damian Johnson atagar@torproject.org Date: Sun Jan 3 15:46:56 2016 -0800
Controller method to check if a config option is set
Turns out tor's controller interface doesn't have a nice way of doing this. Thought we could with GETCONF but no luck.
This reuses the work I reverted from commit e1124d2 for get_custom_conf(), but adds caching and only exposes a simple is_set() method. --- docs/change_log.rst | 1 + stem/control.py | 54 ++++++++++++++++++++++++++++++++++++++ test/integ/control/controller.py | 29 ++++++++++++++++++++ 3 files changed, 84 insertions(+)
diff --git a/docs/change_log.rst b/docs/change_log.rst index 80938da..2caf167 100644 --- a/docs/change_log.rst +++ b/docs/change_log.rst @@ -48,6 +48,7 @@ The following are only available within Stem's `git repository * Added `stem.manual <api/manual.html>`_, which provides information available about Tor from `its manual https://www.torproject.org/docs/tor-manual.html.en`_ (:trac:`8251`) * :func:`~stem.connection.connect` and :func:`~stem.control.Controller.from_port` now connect to both port 9051 (relay's default) and 9151 (Tor Browser's default) (:trac:`16075`) * Added `support for NETWORK_LIVENESS events <api/response.html#stem.response.events.NetworkLivenessEvent>`_ (:spec:`44aac63`) + * Added :func:`~stem.control.Controller.is_set` to the :class:`~stem.control.Controller` * Added :func:`~stem.control.Controller.is_user_traffic_allowed` to the :class:`~stem.control.Controller` * Added the replica attribute to the :class:`~stem.response.events.HSDescEvent` (:spec:`4989e73`) * :func:`~stem.process.launch_tor` could leave a lingering process during an unexpected exception (:trac:`17946`) diff --git a/stem/control.py b/stem/control.py index 3ba3ed6..96597e8 100644 --- a/stem/control.py +++ b/stem/control.py @@ -93,6 +93,7 @@ If you're fine with allowing your script to raise exceptions then this can be mo | |- get_conf - gets the value of a configuration option |- get_conf_map - gets the values of multiple configuration options + |- is_set - determines if an option differs from its default |- set_conf - sets the value of a configuration option |- reset_conf - reverts configuration options to their default values |- set_options - sets or resets the values of multiple configuration options @@ -1986,6 +1987,8 @@ class Controller(BaseController):
def get_conf(self, param, default = UNDEFINED, multiple = False): """ + get_conf(param, default = UNDEFINED, multiple = False) + Queries the current value for a configuration option. Some configuration options (like the ExitPolicy) can have multiple values. This provides a **list** with all of the values if **multiple** is **True**. Otherwise this @@ -2033,6 +2036,8 @@ class Controller(BaseController):
def get_conf_map(self, params, default = UNDEFINED, multiple = True): """ + get_conf_map(params, default = UNDEFINED, multiple = True) + Similar to :func:`~stem.control.Controller.get_conf` but queries multiple configuration options, providing back a mapping of those options to their values. @@ -2170,6 +2175,54 @@ class Controller(BaseController):
return return_dict
+ @with_default() + def is_set(self, param, default = UNDEFINED): + """ + is_set(param, default = UNDEFINED) + + Checks if a configuration option differs from its default or not. + + .. versionadded:: 1.5.0 + + :param str param: configuration option to check + :param object default: response if the query fails + + :returns: **True** if option differs from its default and **False** + otherwise + + :raises: :class:`stem.ControllerError` if the call fails and we weren't + provided a default response + """ + + return param in self._get_custom_options() + + def _get_custom_options(self): + result = self._get_cache('get_custom_options') + + if not result: + config_lines = self.get_info('config-text').splitlines() + + # Tor provides some config options even if they haven't been set... + # + # https://trac.torproject.org/projects/tor/ticket/2362 + # https://trac.torproject.org/projects/tor/ticket/17909 + + default_lines = ( + 'Log notice stdout', + 'Log notice file /var/log/tor/log', + 'DataDirectory /home/%s/.tor' % self.get_user('undefined'), + 'HiddenServiceStatistics 0', + ) + + for line in default_lines: + if line in config_lines: + config_lines.remove(line) + + result = dict([line.split(' ', 1) for line in config_lines]) + self._set_cache({'get_custom_options': result}) + + return result + def set_conf(self, param, value): """ Changes the value of a tor configuration option. Our value can be any of @@ -2279,6 +2332,7 @@ class Controller(BaseController): self._set_cache({'exitpolicy': None})
self._set_cache(to_cache, 'getconf') + self._set_cache({'get_custom_options': None}) else: log.debug('%s (failed, code: %s, message: %s)' % (query, response.code, response.message))
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py index 84bf58a..7cf16ea 100644 --- a/test/integ/control/controller.py +++ b/test/integ/control/controller.py @@ -189,6 +189,8 @@ class TestController(unittest.TestCase):
self.assertTrue(isinstance(event, stem.response.events.ConfChangedEvent))
+ controller.reset_conf('NodeFamily') + @require_controller def test_reattaching_listeners(self): """ @@ -468,6 +470,33 @@ class TestController(unittest.TestCase): self.assertEqual({}, controller.get_conf_map([], 'la-di-dah'))
@require_controller + def test_is_set(self): + """ + Exercises our is_set() method. + """ + + runner = test.runner.get_runner() + + with runner.get_tor_controller() as controller: + custom_options = controller._get_custom_options() + self.assertTrue('ControlPort' in custom_options or 'ControlSocket' in custom_options) + self.assertEqual('1', custom_options['DownloadExtraInfo']) + self.assertEqual('127.0.0.1:1112', custom_options['SocksListenAddress']) + + self.assertTrue(controller.is_set('DownloadExtraInfo')) + self.assertTrue(controller.is_set('SocksListenAddress')) + self.assertFalse(controller.is_set('CellStatistics')) + self.assertFalse(controller.is_set('ConnLimit')) + + # check we update when setting and resetting values + + controller.set_conf('ConnLimit', '1005') + self.assertTrue(controller.is_set('ConnLimit')) + + controller.reset_conf('ConnLimit') + self.assertFalse(controller.is_set('ConnLimit')) + + @require_controller def test_hidden_services_conf(self): """ Exercises the hidden service family of methods (get_hidden_service_conf,