commit f05b54f10a1c212cd4da4d31934506156cfe4cb2 Author: Damian Johnson atagar@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()