[tor-commits] [stem/master] Reconnecting socket after auth failures

atagar at torproject.org atagar at torproject.org
Tue Dec 13 18:10:36 UTC 2011


commit 297a41b085a92eb1c5b5c92464d3bc9cd27cef94
Author: Damian Johnson <atagar at torproject.org>
Date:   Tue Dec 13 10:06:53 2011 -0800

    Reconnecting socket after auth failures
    
    Tor disconnects the control socket after Failed AUTHENTICATE calls in an effort
    to mitigate the issue discussed in...
    http://archives.seul.org/or/announce/Sep-2007/msg00000.html
    
    This is unintuitive to stem users so I'm making a best effort attempt to
    reconnect the socket in those cases. This isn't guaranteed to succeed, but
    checking is_alive() is far nicer for callers than a try/connect/except block.
---
 stem/connection.py                      |   43 ++++++++++++++++++++-----------
 test/integ/connection/authentication.py |   12 ++++++--
 2 files changed, 37 insertions(+), 18 deletions(-)

diff --git a/stem/connection.py b/stem/connection.py
index 71b6e5b..e9bd5f7 100644
--- a/stem/connection.py
+++ b/stem/connection.py
@@ -126,8 +126,11 @@ def authenticate_none(control_socket, suppress_ctl_errors = True):
   authenticate before they can be used, even if tor hasn't been configured to
   use any authentication.
   
-  For general usage use the authenticate function instead. If authentication
-  fails then tor will close the control socket.
+  If authentication fails tor will disconnect and we'll make a best effort
+  attempt to re-establish the connection. This may not succeed, so check
+  is_alive() before using the socket further.
+  
+  For general usage use the authenticate() function instead.
   
   Arguments:
     control_socket (stem.socket.ControlSocket) - socket to be authenticated
@@ -145,10 +148,13 @@ def authenticate_none(control_socket, suppress_ctl_errors = True):
     
     # if we got anything but an OK response then error
     if str(auth_response) != "OK":
-      control_socket.close()
+      try: control_socket.connect()
+      except: pass
+      
       raise OpenAuthRejected(str(auth_response), auth_response)
   except stem.socket.ControllerError, exc:
-    control_socket.close()
+    try: control_socket.connect()
+    except: pass
     
     if not suppress_ctl_errors: raise exc
     else: raise OpenAuthRejected("Socket failed (%s)" % exc)
@@ -158,8 +164,11 @@ def authenticate_password(control_socket, password, suppress_ctl_errors = True):
   Authenticates to a control socket that uses a password (via the
   HashedControlPassword torrc option). Quotes in the password are escaped.
   
-  For general usage use the authenticate function instead. If authentication
-  fails then tor will close the control socket.
+  If authentication fails tor will disconnect and we'll make a best effort
+  attempt to re-establish the connection. This may not succeed, so check
+  is_alive() before using the socket further.
+  
+  For general usage use the authenticate() function instead.
   
   note: If you use this function directly, rather than authenticate(), we may
   mistakenly raise a PasswordAuthRejected rather than IncorrectPassword. This
@@ -191,7 +200,8 @@ def authenticate_password(control_socket, password, suppress_ctl_errors = True):
     
     # if we got anything but an OK response then error
     if str(auth_response) != "OK":
-      control_socket.close()
+      try: control_socket.connect()
+      except: pass
       
       # all we have to go on is the error message from tor...
       # Password did not match HashedControlPassword value value from configuration...
@@ -202,7 +212,8 @@ def authenticate_password(control_socket, password, suppress_ctl_errors = True):
       else:
         raise PasswordAuthRejected(str(auth_response), auth_response)
   except stem.socket.ControllerError, exc:
-    control_socket.close()
+    try: control_socket.connect()
+    except: pass
     
     if not suppress_ctl_errors: raise exc
     else: raise PasswordAuthRejected("Socket failed (%s)" % exc)
@@ -217,8 +228,11 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
   The IncorrectCookieSize and UnreadableCookieFile exceptions take precidence
   over the other types.
   
-  For general usage use the authenticate function instead. If authentication
-  fails then tor will close the control socket.
+  If authentication fails tor will disconnect and we'll make a best effort
+  attempt to re-establish the connection. This may not succeed, so check
+  is_alive() before using the socket further.
+  
+  For general usage use the authenticate() function instead.
   
   note: If you use this function directly, rather than authenticate(), we may
   mistakenly raise a CookieAuthRejected rather than IncorrectCookieValue. This
@@ -241,7 +255,6 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
   """
   
   if not os.path.exists(cookie_path):
-    control_socket.close()
     raise UnreadableCookieFile("Authentication failed: '%s' doesn't exist" % cookie_path)
   
   # Abort if the file isn't 32 bytes long. This is to avoid exposing arbitrary
@@ -256,7 +269,6 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
   auth_cookie_size = os.path.getsize(cookie_path)
   
   if auth_cookie_size != 32:
-    control_socket.close()
     exc_msg = "Authentication failed: authentication cookie '%s' is the wrong size (%i bytes instead of 32)" % (cookie_path, auth_cookie_size)
     raise IncorrectCookieSize(exc_msg)
   
@@ -265,7 +277,6 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
     auth_cookie_contents = auth_cookie_file.read()
     auth_cookie_file.close()
   except IOError, exc:
-    control_socket.close()
     raise UnreadableCookieFile("Authentication failed: unable to read '%s' (%s)" % (cookie_path, exc)) 
   
   try:
@@ -274,7 +285,8 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
     
     # if we got anything but an OK response then error
     if str(auth_response) != "OK":
-      control_socket.close()
+      try: control_socket.connect()
+      except: pass
       
       # all we have to go on is the error message from tor...
       # ... Authentication cookie did not match expected value.
@@ -286,7 +298,8 @@ def authenticate_cookie(control_socket, cookie_path, suppress_ctl_errors = True)
       else:
         raise CookieAuthRejected(str(auth_response), auth_response)
   except stem.socket.ControllerError, exc:
-    control_socket.close()
+    try: control_socket.connect()
+    except: pass
     
     if not suppress_ctl_errors: raise exc
     else: raise CookieAuthRejected("Socket failed (%s)" % exc)
diff --git a/test/integ/connection/authentication.py b/test/integ/connection/authentication.py
index ee36946..3d7d199 100644
--- a/test/integ/connection/authentication.py
+++ b/test/integ/connection/authentication.py
@@ -246,8 +246,9 @@ class TestAuthenticate(unittest.TestCase):
       control_socket.close()
       self.fail()
     except stem.connection.AuthenticationFailure, exc:
-      self.assertFalse(control_socket.is_alive())
+      self.assertTrue(control_socket.is_alive())
       self.assertEqual(failure_msg, str(exc))
+      control_socket.close()
   
   def _check_auth(self, auth_type, *auth_args):
     """
@@ -268,8 +269,13 @@ class TestAuthenticate(unittest.TestCase):
     control_socket = runner.get_tor_socket(False)
     auth_function = self._get_auth_function(control_socket, auth_type, *auth_args)
     
-    # run the authentication, letting this raise if there's a problem
-    auth_function()
+    # run the authentication, re-raising if there's a problem
+    try:
+      auth_function()
+    except stem.connection.AuthenticationFailure, exc:
+      self.assertTrue(control_socket.is_alive())
+      control_socket.close()
+      raise exc
     
     # issues a 'GETINFO config-file' query to confirm that we can use the socket
     control_socket.send("GETINFO config-file")



More information about the tor-commits mailing list