[tor-commits] [arm/master] Helper function for authenticating to the tor controller

atagar at torproject.org atagar at torproject.org
Sun Sep 15 22:29:21 UTC 2013


commit b86a709cdba4c57f5e5fa9418f6e795dcfa8c63b
Author: Damian Johnson <atagar at torproject.org>
Date:   Sat Sep 14 13:15:57 2013 -0700

    Helper function for authenticating to the tor controller
    
    More refactoring for arm's starter module. This moves authentication into a
    helper method, greatly expanding the error output for users and adds unit
    tests.
---
 arm/settings.cfg |   28 +++++++++++++++++++
 arm/starter.py   |   79 +++++++++++++++++++++++++++++++++++++-----------------
 test/starter.py  |   74 +++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 155 insertions(+), 26 deletions(-)

diff --git a/arm/settings.cfg b/arm/settings.cfg
index c73a16f..368d418 100644
--- a/arm/settings.cfg
+++ b/arm/settings.cfg
@@ -18,6 +18,34 @@ msg.help
 |arm -b -i 1643          hide connection data, attaching to control port 1643
 |arm -e we -c /tmp/cfg   use this configuration file with 'WARN'/'ERR' events
 
+msg.wrong_port_type
+|Please check in your torrc that %i is the ControlPort. Maybe you configured
+|it to be the ORPort or SocksPort instead?
+
+msg.wrong_socket_type
+|Unable to connect to tor. Are you sure the interface you specified belongs to
+|tor?
+
+msg.uncrcognized_auth_type
+|Tor is using a type of authentication we do not recognize...
+|
+|  %s
+|
+|Please check that arm is up to date and if there is an existing issue on
+|'http://bugs.torproject.org'. If there isn't one then let us know!
+
+msg.missing_password_bug
+|BUG: You provided a password but despite this stem reported that it was
+|missing. This shouldn't happen - please let us know about it!
+|
+|  http://bugs.torproject.org
+
+msg.unreadable_cookie_file
+|We were unable to read tor's authentication cookie...
+|
+|  Path: %s
+|  Issue: %s
+
 # Important tor configuration options (shown by default)
 config.important BandwidthRate
 config.important BandwidthBurst
diff --git a/arm/starter.py b/arm/starter.py
index 057fb86..fa2289d 100644
--- a/arm/starter.py
+++ b/arm/starter.py
@@ -8,13 +8,13 @@ command line parameters.
 
 import collections
 import getopt
+import getpass
 import os
 import sys
 
 import stem.util.connection
 
 import time
-import getpass
 import locale
 import logging
 import platform
@@ -42,6 +42,11 @@ CONFIG = stem.util.conf.config_dict("arm", {
   "startup.blindModeEnabled": False,
   "startup.events": "N3",
   "msg.help": "",
+  "msg.wrong_port_type": "",
+  "msg.wrong_socket_type": "",
+  "msg.uncrcognized_auth_type": "",
+  "msg.missing_password_bug": "",
+  "msg.unreadable_cookie_file": "",
 })
 
 # notices given if the user is running arm or tor as root
@@ -80,6 +85,16 @@ ARGS = {
 OPT = "gi:s:c:dbe:vh"
 OPT_EXPANDED = ["interface=", "socket=", "config=", "debug", "blind", "event=", "version", "help"]
 
+try:
+  pathPrefix = os.path.dirname(sys.argv[0])
+  if pathPrefix and not pathPrefix.endswith("/"):
+    pathPrefix = pathPrefix + "/"
+
+  config = stem.util.conf.get_config("arm")
+  config.load("%sarm/settings.cfg" % pathPrefix)
+except IOError, exc:
+  stem.util.log.warn(NO_INTERNAL_CFG_MSG % arm.util.sysTools.getFileErrorMsg(exc))
+
 
 def _get_args(argv):
   """
@@ -168,6 +183,43 @@ def _get_controller(args):
     raise ValueError("Unable to connect to tor. Maybe it's running without a ControlPort?")
 
 
+def _authenticate(controller, password):
+  """
+  Authenticates to the given Controller.
+
+  :param stem.control.Controller controller: controller to be authenticated to
+  :param str args: password to authenticate with, **None** if nothing was provided
+
+  :raises: **ValueError** if unable to authenticate
+  """
+
+  chroot = arm.util.torTools.get_chroot()
+
+  try:
+    controller.authenticate(password = password, chroot_path = chroot)
+  except stem.connection.IncorrectSocketType:
+    control_socket = controller.get_socket()
+
+    if isinstance(control_socket, stem.socket.ControlPort):
+      raise ValueError(CONFIG['msg.wrong_port_type'] % control_socket.get_port())
+    else:
+      raise ValueError(CONFIG['msg.wrong_socket_type'])
+  except stem.connection.UnrecognizedAuthMethods as exc:
+    raise ValueError(CONFIG['msg.uncrcognized_auth_type'] % ', '.join(exc.unknown_auth_methods))
+  except stem.connection.IncorrectPassword:
+    raise ValueError("Incorrect password")
+  except stem.connection.MissingPassword:
+    if password:
+      raise ValueError(CONFIG['msg.missing_password_bug'])
+
+    password = getpass.getpass("Tor controller password: ")
+    return _authenticate(controller, password)
+  except stem.connection.UnreadableCookieFile as exc:
+    raise ValueError(CONFIG['msg.unreadable_cookie_file'] % (exc.cookie_path, str(exc)))
+  except stem.connection.AuthenticationFailure as exc:
+    raise ValueError("Unable to authenticate: %s" % exc)
+
+
 def _dumpConfig():
   """
   Dumps the current arm and tor configurations at the DEBUG runlevel. This
@@ -222,11 +274,6 @@ def main():
     pathPrefix = pathPrefix + "/"
 
   try:
-    config.load("%sarm/settings.cfg" % pathPrefix)
-  except IOError, exc:
-    stem.util.log.warn(NO_INTERNAL_CFG_MSG % arm.util.sysTools.getFileErrorMsg(exc))
-  
-  try:
     args = _get_args(sys.argv[1:])
   except getopt.GetoptError as exc:
     print "%s (for usage provide --help)" % exc
@@ -289,29 +336,11 @@ def main():
 
   try:
     controller = _get_controller(args)
+    _authenticate(controller, CONFIG['tor.password'])
   except ValueError as exc:
     print exc
     exit(1)
 
-  chroot = arm.util.torTools.get_chroot()
-
-  try:
-    controller.authenticate(password = CONFIG["tor.password"], chroot_path = chroot)
-  except (stem.connection.MissingPassword, stem.connection.IncorrectPassword) as exc:
-    if isinstance(stem.connection.IncorrectPassword, exc):
-      print "Password found in '%s' was incorrect" % args.config
-
-    try:
-      passphrase = getpass.getpass("Controller password: ")
-      controller.authenticate(password = passphrase, chroot_path = chroot)
-      del passphrase  # removing reference as early as possible to free memory
-    except stem.connection.IncorrectPassword:
-      print "Incorrect password"
-      sys.exit(1)
-    except stem.connection.AuthenticationFailure as exc:
-      print "Unable to authenticate: %s" % exc
-      sys.exit(1)
-
   # Removing references to the controller password so the memory can be
   # freed. Without direct memory access this is about the best we can do.
 
diff --git a/test/starter.py b/test/starter.py
index f8c4f0c..d1c90cf 100644
--- a/test/starter.py
+++ b/test/starter.py
@@ -7,9 +7,16 @@ import unittest
 
 from mock import Mock, patch
 
-from arm.starter import _get_args, _get_controller, ARGS
+from arm.starter import (
+  _get_args,
+  _get_controller,
+  _authenticate,
+  ARGS,
+)
 
 import stem
+import stem.connection
+import stem.socket
 
 class TestArgumentParsing(unittest.TestCase):
   def test_that_we_get_default_values(self):
@@ -127,3 +134,68 @@ class TestGetController(unittest.TestCase):
       self.fail()
     except ValueError, exc:
       self.assertEqual(msg, str(exc))
+
+class TestAuthenticate(unittest.TestCase):
+  @patch('arm.util.torTools.get_chroot')
+  def test_success(self, get_chroot_mock):
+    controller = Mock()
+
+    get_chroot_mock.return_value = ''  # no chroot
+    _authenticate(controller, None)
+    controller.authenticate.assert_called_with(password = None, chroot_path = '')
+    controller.authenticate.reset_mock()
+
+    get_chroot_mock.return_value = '/my/chroot'
+    _authenticate(controller, 's3krit!!!')
+    controller.authenticate.assert_called_with(password = 's3krit!!!', chroot_path = '/my/chroot')
+
+  @patch('arm.util.torTools.get_chroot', Mock(return_value = ''))
+  @patch('getpass.getpass')
+  def test_success_with_password_prompt(self, getpass_mock):
+    controller = Mock()
+
+    def authenticate_mock(password, **kwargs):
+      if password is None:
+        raise stem.connection.MissingPassword('no password')
+      elif password == 'my_password':
+        return None  # success
+      else:
+        raise ValueError("Unexpected authenticate_mock input: %s" % password)
+
+    controller.authenticate.side_effect = authenticate_mock
+    getpass_mock.return_value = 'my_password'
+
+    _authenticate(controller, None)
+    controller.authenticate.assert_any_call(password = None, chroot_path = '')
+    controller.authenticate.assert_any_call(password = 'my_password', chroot_path = '')
+
+  @patch('arm.util.torTools.get_chroot', Mock(return_value = ''))
+  def test_failure(self):
+    controller = Mock()
+
+    controller.authenticate.side_effect = stem.connection.IncorrectSocketType('unable to connect to socket')
+    controller.get_socket.return_value = stem.socket.ControlPort(connect = False)
+    self._assert_authenticate_fails_with(controller, 'Please check in your torrc that 9051 is the ControlPort.')
+
+    controller.get_socket.return_value = stem.socket.ControlSocketFile(connect = False)
+    self._assert_authenticate_fails_with(controller, 'Are you sure the interface you specified belongs to')
+
+    controller.authenticate.side_effect = stem.connection.UnrecognizedAuthMethods('unable to connect', ['telepathy'])
+    self._assert_authenticate_fails_with(controller, 'Tor is using a type of authentication we do not recognize...\n\n  telepathy')
+
+    controller.authenticate.side_effect = stem.connection.IncorrectPassword('password rejected')
+    self._assert_authenticate_fails_with(controller, 'Incorrect password')
+
+    controller.authenticate.side_effect = stem.connection.UnreadableCookieFile('permission denied', '/tmp/my_cookie', False)
+    self._assert_authenticate_fails_with(controller, "We were unable to read tor's authentication cookie...\n\n  Path: /tmp/my_cookie\n  Issue: permission denied")
+
+    controller.authenticate.side_effect = stem.connection.OpenAuthRejected('crazy failure')
+    self._assert_authenticate_fails_with(controller, 'Unable to authenticate: crazy failure')
+
+  def _assert_authenticate_fails_with(self, controller, msg):
+    try:
+      _get_controller(_authenticate(controller, None))
+      self.fail()
+    except ValueError, exc:
+      if not msg in str(exc):
+        self.fail("Expected...\n\n%s\n\n... which couldn't be found in...\n\n%s" % (msg, exc))





More information about the tor-commits mailing list