[tor-commits] [arm/master] Mirroring TorCtl connection with stem

atagar at torproject.org atagar at torproject.org
Mon Dec 17 04:25:17 UTC 2012


commit f05b54f10a1c212cd4da4d31934506156cfe4cb2
Author: Damian Johnson <atagar at torproject.org>
Date:   Tue Dec 11 08:43:26 2012 -0800

    Mirroring TorCtl connection with stem
    
    Arm uses a wrapper for its controller usage. This abstraction is highly
    convenient for our migration since it means that our wrapper can connect to tor
    with *both* TorCtl and stem, letting us migrate calls one at a time while
    keeping arm functional.
    
    Changing our wrapper's init() function to require both connection types, and
    revising everywhere we use it. This breaks test.py and likely breaks the gui,
    though I'll probably simply drop both of them before the next release. The gui
    was an experiment, and can be resurrected if someone becomes interested in
    developing it.
---
 src/cli/controller.py  |    7 +++++-
 src/cli/headerPanel.py |   30 ++++++++++++++++++++++++----
 src/starter.py         |   50 ++++++++++++++++++++++++++++++++---------------
 src/util/torTools.py   |   23 ++++++++++++---------
 4 files changed, 78 insertions(+), 32 deletions(-)

diff --git a/src/cli/controller.py b/src/cli/controller.py
index c1be694..c72375b 100644
--- a/src/cli/controller.py
+++ b/src/cli/controller.py
@@ -23,6 +23,7 @@ import cli.graphing.resourceStats
 import cli.connections.connPanel
 
 from TorCtl import TorCtl
+from stem.control import Controller
 
 from util import connections, conf, enum, hostnames, log, panel, sysTools, torConfig, torTools
 
@@ -555,7 +556,11 @@ class TorManager:
           raise IOError("authentication cookie '%s' is the wrong size (%i bytes instead of 32)" % (authValue, authCookieSize))
         
         torctlConn.authenticate(authValue)
-        torTools.getConn().init(torctlConn)
+        
+        controller = Controller.from_port(control_port = int(CONFIG["wizard.default"]["Control"]))
+        controller.authenticate()
+        
+        torTools.getConn().init(torctlConn, controller)
       except Exception, exc:
         raise IOError("Unable to connect to Tor: %s" % exc)
 
diff --git a/src/cli/headerPanel.py b/src/cli/headerPanel.py
index bf8dbdb..50222f1 100644
--- a/src/cli/headerPanel.py
+++ b/src/cli/headerPanel.py
@@ -20,6 +20,10 @@ import curses
 import threading
 
 import TorCtl.TorCtl
+import stem
+import stem.connection
+
+from stem.control import Controller
 
 import starter
 import cli.popups
@@ -134,12 +138,19 @@ class HeaderPanel(panel.Panel, threading.Thread):
     if key in (ord('n'), ord('N')) and torTools.getConn().isNewnymAvailable():
       self.sendNewnym()
     elif key in (ord('r'), ord('R')) and not self._isTorConnected:
-      torctlConn = None
+      torctlConn, controller = None, None
       allowPortConnection, allowSocketConnection, _ = starter.allowConnectionTypes()
       
       if os.path.exists(self._config["startup.interface.socket"]) and allowSocketConnection:
-        try: torctlConn = torTools.connect_socket(self._config["startup.interface.socket"])
-        except IOError, exc:
+        try:
+          torctlConn = torTools.connect_socket(self._config["startup.interface.socket"])
+          
+          # TODO: um... what about passwords?
+          controller = Controller.from_socket_file(self._config["startup.interface.socket"])
+          controller.authenticate()
+        except (IOError, stem.SocketError), exc:
+          torctlConn, controller = None, None
+          
           if not allowPortConnection:
             cli.popups.showMsg("Unable to reconnect (%s)" % exc, 3)
       elif not allowPortConnection:
@@ -166,7 +177,16 @@ class HeaderPanel(panel.Panel, threading.Thread):
           
           tmpConn.authenticate(authValue)
           torctlConn = tmpConn
+          
+          controller = Controller.from_port(ctlAddr, ctlPort)
+          
+          try:
+            controller.authenticate()
+          except stem.connection.MissingPassword:
+            controller.authenticate(authValue) # already got the password above
         except Exception, exc:
+          torctlConn, controller = None, None
+          
           # attempts to use the wizard port too
           try:
             cli.controller.getController().getTorManager().connectManagedInstance()
@@ -176,8 +196,8 @@ class HeaderPanel(panel.Panel, threading.Thread):
             # displays notice for the first failed connection attempt
             if exc.args: cli.popups.showMsg("Unable to reconnect (%s)" % exc, 3)
       
-      if torctlConn:
-        torTools.getConn().init(torctlConn)
+      if torctlConn and controller:
+        torTools.getConn().init(torctlConn, controller)
         log.log(log.NOTICE, "Reconnected to Tor's control port")
         cli.popups.showMsg("Tor reconnected", 1)
     else: isKeystrokeConsumed = False
diff --git a/src/starter.py b/src/starter.py
index 40c23f7..7c51aae 100644
--- a/src/starter.py
+++ b/src/starter.py
@@ -28,9 +28,12 @@ import util.torConfig
 import util.torInterpretor
 import util.torTools
 import util.uiTools
+
 import TorCtl.TorCtl
 import TorCtl.TorUtil
 
+from stem.control import Controller
+
 LOG_DUMP_PATH = os.path.expanduser("~/.arm/log")
 DEFAULT_CONFIG = os.path.expanduser("~/.arm/armrc")
 CONFIG = {"startup.controlPassword": None,
@@ -210,7 +213,7 @@ def _torCtlConnect(controlAddr="127.0.0.1", controlPort=9051, passphrase=None, i
   Custom handler for establishing a TorCtl connection.
   """
   
-  conn = None
+  conn, controller = None, None
   try:
     #conn, authType, authValue = TorCtl.TorCtl.preauth_connect(controlAddr, controlPort)
     conn, authTypes, authValue = util.torTools.preauth_connect_alt(controlAddr, controlPort)
@@ -227,7 +230,7 @@ def _torCtlConnect(controlAddr="127.0.0.1", controlPort=9051, passphrase=None, i
       
       if not passphrase:
         try: passphrase = getpass.getpass("Controller password: ")
-        except KeyboardInterrupt: return None
+        except KeyboardInterrupt: return None, None
     
     if TorCtl.TorCtl.AUTH_TYPE.COOKIE in authTypes and authValue[0] != "/":
       # Connecting to the control port will probably fail if it's using cookie
@@ -280,9 +283,16 @@ def _torCtlConnect(controlAddr="127.0.0.1", controlPort=9051, passphrase=None, i
         else: conn._authTypes.remove(TorCtl.TorCtl.AUTH_TYPE.COOKIE)
     
     conn.authenticate(passphrase)
-    return conn
+    
+    # Damn well everything above this is covered by stem. :P
+    
+    controller = Controller.from_port(controlAddr, controlPort)
+    controller.authenticate(password = passphrase, chroot_path = util.torTools.getConn().getPathPrefix())
+    
+    return conn, controller
   except Exception, exc:
     if conn: conn.close()
+    if controller: controller.close()
     
     # attempts to connect with the default wizard address too
     wizardPort = CONFIG["wizard.default"].get("Control")
@@ -295,9 +305,9 @@ def _torCtlConnect(controlAddr="127.0.0.1", controlPort=9051, passphrase=None, i
       # connection failure. Otherwise, return the connection result.
       
       if controlPort != wizardPort:
-        connResult = _torCtlConnect(controlAddr, wizardPort)
-        if connResult != None: return connResult
-      else: return None # wizard connection attempt, don't print anything
+        connResult, controller = _torCtlConnect(controlAddr, wizardPort)
+        if connResult != None: return connResult, controller
+      else: return None, None # wizard connection attempt, don't print anything
     
     if passphrase and str(exc) == "Unable to authenticate: password incorrect":
       # provide a warning that the provided password didn't work, then try
@@ -306,7 +316,8 @@ def _torCtlConnect(controlAddr="127.0.0.1", controlPort=9051, passphrase=None, i
       return _torCtlConnect(controlAddr, controlPort)
     elif printError:
       print exc
-      return None
+    
+    return None, None
 
 def _dumpConfig():
   """
@@ -487,24 +498,31 @@ if __name__ == '__main__':
   # skips attempting to connect by socket or port if the user has given
   # arguments for connecting to the other.
   
-  conn = None
+  conn, controller = None, None
   allowPortConnection, allowSocketConnection, allowDetachedStart = allowConnectionTypes()
   
   socketPath = param["startup.interface.socket"]
   if os.path.exists(socketPath) and allowSocketConnection:
-    try: conn = util.torTools.connect_socket(socketPath)
+    try:
+      conn = util.torTools.connect_socket(socketPath)
+      
+      # TODO: um... what about passwords?
+      # https://trac.torproject.org/6881
+      
+      controller = Controller.from_socket_file(socketPath)
+      controller.authenticate()
     except IOError, exc:
       if not allowPortConnection:
         print "Unable to use socket '%s': %s" % (socketPath, exc)
   elif not allowPortConnection:
     print "Socket '%s' doesn't exist" % socketPath
   
-  if not conn and allowPortConnection:
+  if (not conn or not controller) and allowPortConnection:
     # sets up TorCtl connection, prompting for the passphrase if necessary and
     # sending problems to stdout if they arise
     authPassword = config.get("startup.controlPassword", CONFIG["startup.controlPassword"])
     incorrectPasswordMsg = "Password found in '%s' was incorrect" % configPath
-    conn = _torCtlConnect(controlAddr, controlPort, authPassword, incorrectPasswordMsg, not allowDetachedStart)
+    conn, controller = _torCtlConnect(controlAddr, controlPort, authPassword, incorrectPasswordMsg, not allowDetachedStart)
     
     # removing references to the controller password so the memory can be freed
     # (unfortunately python does allow for direct access to the memory so this
@@ -522,19 +540,19 @@ if __name__ == '__main__':
       if pwLineNum != None:
         del config.rawContents[i]
   
-  if conn == None and not allowDetachedStart: sys.exit(1)
+  if (conn is None or controller is None) and not allowDetachedStart: sys.exit(1)
   
   # initializing the connection may require user input (for the password)
   # skewing the startup time results so this isn't counted
   initTime = time.time() - startTime
-  controller = util.torTools.getConn()
+  controllerWrapper = util.torTools.getConn()
   
   torUser = None
-  if conn:
-    controller.init(conn)
+  if conn and controller:
+    controllerWrapper.init(conn, controller)
     
     # give a notice if tor is running with root
-    torUser = controller.getMyUser()
+    torUser = controllerWrapper.getMyUser()
     if torUser == "root":
       util.log.log(util.log.NOTICE, TOR_ROOT_NOTICE)
   
diff --git a/src/util/torTools.py b/src/util/torTools.py
index a4d49c7..7d742e4 100644
--- a/src/util/torTools.py
+++ b/src/util/torTools.py
@@ -579,6 +579,7 @@ class Controller(TorCtl.PostEventListener):
   def __init__(self):
     TorCtl.PostEventListener.__init__(self)
     self.conn = None                    # None if uninitialized or controller's been closed
+    self.controller = None
     self.connLock = threading.RLock()
     self.eventListeners = []            # instances listening for tor controller events
     self.torctlListeners = []           # callback functions for TorCtl events
@@ -631,26 +632,25 @@ class Controller(TorCtl.PostEventListener):
     # tracks the number of sequential geoip lookup failures
     self.geoipFailureCount = 0
   
-  def init(self, conn=None):
+  def init(self, conn, controller):
     """
     Uses the given TorCtl instance for future operations, notifying listeners
     about the change.
     
     Arguments:
-      conn - TorCtl instance to be used, if None then a new instance is fetched
-             via the connect function
+      conn - TorCtl instance to be used
+      controller - stem based Controller instance
     """
     
-    if conn == None:
-      conn = TorCtl.connect()
-      
-      if conn == None: raise ValueError("Unable to initialize TorCtl instance.")
-    
-    if conn.is_live() and conn != self.conn:
+    if conn.is_live() and controller.is_alive() and conn != self.conn:
       self.connLock.acquire()
       
       if self.conn: self.close() # shut down current connection
       self.conn = conn
+      
+      self.controller = controller
+      log.log(log.INFO, "Stem connected to tor version %s" % self.controller.get_version())
+      
       self.conn.add_event_listener(self)
       for listener in self.eventListeners: self.conn.add_event_listener(listener)
       
@@ -698,6 +698,9 @@ class Controller(TorCtl.PostEventListener):
       self.conn.close()
       self.conn = None
       
+      self.controller.close()
+      self.controller = None
+      
       self._status = State.CLOSED
       self._statusTime = time.time()
       
@@ -719,7 +722,7 @@ class Controller(TorCtl.PostEventListener):
     
     result = False
     if self.conn:
-      if self.conn.is_live(): result = True
+      if self.conn.is_live() and self.controller.is_alive(): result = True
       else: self.close()
     
     self.connLock.release()





More information about the tor-commits mailing list