[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