[tor-commits] [stem/master] Add auth_type and client_names arguments to create_hidden_service()
atagar at torproject.org
atagar at torproject.org
Thu Apr 23 16:48:47 UTC 2015
commit b4402bfb6965106e8ab43612b641d65905e09603
Author: Damian Johnson <atagar at torproject.org>
Date: Thu Apr 23 09:50:16 2015 -0700
Add auth_type and client_names arguments to create_hidden_service()
Support for specifying authentication and clients when making hidden services.
This is a tweaked version of a patch from federico3 on...
https://trac.torproject.org/projects/tor/ticket/14320
---
docs/change_log.rst | 1 +
stem/control.py | 52 ++++++++++++++++++++++++++++++--------
test/integ/control/controller.py | 22 +++++++++++++++-
3 files changed, 64 insertions(+), 11 deletions(-)
diff --git a/docs/change_log.rst b/docs/change_log.rst
index 4c96528..47f2a49 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -55,6 +55,7 @@ conversion (:trac:`14075`).
* :class:`~stem.response.events.CircuitEvent` support for the new SOCKS_USERNAME and SOCKS_PASSWORD arguments (:trac:`14555`, :spec:`2975974`)
* The 'strict' argument of :func:`~stem.exit_policy.ExitPolicy.can_exit_to` didn't behave as documented (:trac:`14314`)
* Threads spawned for status change listeners were never joined on, potentially causing noise during interpreter shutdown
+ * Added support for specifying the authentication type and client names in :func:`~stem.control.Controller.create_hidden_service` (:trac:`14320`)
* **Descriptors**
diff --git a/stem/control.py b/stem/control.py
index 69b326d..a688d21 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -376,6 +376,7 @@ AccountingStats = collections.namedtuple('AccountingStats', [
CreateHiddenServiceOutput = collections.namedtuple('CreateHiddenServiceOutput', [
'path',
'hostname',
+ 'hostname_for_client',
'config',
])
@@ -2279,15 +2280,20 @@ class Controller(BaseController):
self.set_options(hidden_service_options)
- def create_hidden_service(self, path, port, target_address = None, target_port = None):
+ def create_hidden_service(self, path, port, target_address = None, target_port = None, auth_type = None, client_names = None):
"""
Create a new hidden service. If the directory is already present, a
new port is added. This provides a **namedtuple** of the following...
* path (str) - hidden service directory
- * hostname (str) - onion address of the service, this is only retrieved
- if we can read the hidden service directory
+ * hostname (str) - Content of the hostname file, if no **client_names**
+ are provided this is the onion address of the service. This is only
+ retrieved if we can read the hidden service directory.
+
+ * hostname_for_client (dict) - mapping of client names to their onion
+ address, this is only set if the **client_names** was provided and we
+ can read the hidden service directory
* config (dict) - tor's new hidden service configuration
@@ -2297,11 +2303,16 @@ class Controller(BaseController):
.. versionadded:: 1.3.0
+ .. versionchanged:: 1.4.0
+ Added the auth_type and client_names arguments.
+
:param str path: path for the hidden service's data directory
:param int port: hidden service port
:param str target_address: address of the service, by default 127.0.0.1
:param int target_port: port of the service, by default this is the same as
**port**
+ :param str auth_type: authentication type: basic, stealth or None to disable auth
+ :param list client_names: client names (1-16 characters "A-Za-z0-9+-_")
:returns: **CreateHiddenServiceOutput** if we create or update a hidden service, **None** otherwise
@@ -2314,6 +2325,8 @@ class Controller(BaseController):
raise ValueError("%s isn't a valid IPv4 address" % target_address)
elif target_port is not None and not stem.util.connection.is_valid_port(target_port):
raise ValueError("%s isn't a valid port number" % target_port)
+ elif auth_type not in (None, 'basic', 'stealth'):
+ raise ValueError("%s isn't a recognized type of authentication" % auth_type)
port = int(port)
target_address = target_address if target_address else '127.0.0.1'
@@ -2325,9 +2338,14 @@ class Controller(BaseController):
return None
conf.setdefault(path, OrderedDict()).setdefault('HiddenServicePort', []).append((port, target_address, target_port))
+
+ if auth_type and client_names:
+ hsac = "%s %s" % (auth_type, ','.join(client_names))
+ conf[path]['HiddenServiceAuthorizeClient'] = hsac
+
self.set_hidden_service_conf(conf)
- hostname = None
+ hostname, hostname_for_client = None, {}
if self.is_localhost():
hostname_path = os.path.join(path, 'hostname')
@@ -2349,16 +2367,30 @@ class Controller(BaseController):
else:
time.sleep(0.05)
- if os.path.exists(hostname_path):
- try:
- with open(hostname_path) as hostname_file:
- hostname = hostname_file.read().strip()
- except:
- pass
+ try:
+ with open(hostname_path) as hostname_file:
+ hostname = hostname_file.read().strip()
+
+ if client_names and '\n' in hostname:
+ # When there's multiple clients this looks like...
+ #
+ # ndisjxzkgcdhrwqf.onion sjUwjTSPznqWLdOPuwRUzg # client: c1
+ # ndisjxzkgcdhrwqf.onion sUu92axuL5bKnA76s2KRfw # client: c2
+
+ for line in hostname.splitlines():
+ if ' # client: ' in line:
+ address = line.split()[0]
+ client = line.split(' # client: ', 1)[1]
+
+ if len(address) == 22 and address.endswith('.onion'):
+ hostname_for_client[client] = address
+ except:
+ pass
return CreateHiddenServiceOutput(
path = path,
hostname = hostname,
+ hostname_for_client = hostname_for_client,
config = conf,
)
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 2265cb2..f5eef33 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -480,6 +480,7 @@ class TestController(unittest.TestCase):
service1_path = os.path.join(test_dir, 'test_hidden_service1')
service2_path = os.path.join(test_dir, 'test_hidden_service2')
service3_path = os.path.join(test_dir, 'test_hidden_service3')
+ service4_path = os.path.join(test_dir, 'test_hidden_service4')
empty_service_path = os.path.join(test_dir, 'test_hidden_service_empty')
with runner.get_tor_controller() as controller:
@@ -547,12 +548,31 @@ class TestController(unittest.TestCase):
controller.remove_hidden_service(hs_path, 8989)
self.assertEqual(3, len(controller.get_hidden_service_conf()))
+
+ # add a new service, this time with client authentication
+
+ hs_path = os.path.join(os.getcwd(), service4_path)
+ hs_attributes = controller.create_hidden_service(hs_path, 8888, auth_type = 'basic', client_names = ['c1', 'c2'])
+
+ self.assertEqual(2, len(hs_attributes.hostname.splitlines()))
+ self.assertEqual(2, len(hs_attributes.hostname_for_client))
+ self.assertTrue(hs_attributes.hostname_for_client['c1'].endswith('.onion'))
+ self.assertTrue(hs_attributes.hostname_for_client['c2'].endswith('.onion'))
+
+ conf = controller.get_hidden_service_conf()
+ self.assertEqual(4, len(conf))
+ self.assertEqual(1, len(conf[hs_path]['HiddenServicePort']))
+
+ # remove a hidden service
+
+ controller.remove_hidden_service(hs_path, 8888)
+ self.assertEqual(3, len(controller.get_hidden_service_conf()))
finally:
controller.set_hidden_service_conf({}) # drop hidden services created during the test
# clean up the hidden service directories created as part of this test
- for path in (service1_path, service2_path, service3_path):
+ for path in (service1_path, service2_path, service3_path, service4_path):
try:
shutil.rmtree(path)
except:
More information about the tor-commits
mailing list