[tor-commits] [arm/release] Adding tor control socket support
atagar at torproject.org
atagar at torproject.org
Sun Sep 25 21:38:25 UTC 2011
commit cca2aae001bab0e6bb09e81a4e9df11bec2bc2bb
Author: Damian Johnson <atagar at torproject.org>
Date: Wed Aug 10 09:55:13 2011 -0700
Adding tor control socket support
This adds support for connecting via a tor control socket. Connection behavior
is as follows...
- if an argument or armrc option for the control port or socket path are
provided then just use that connection method
- if neither or both args are provided then the default behavior is...
- try connecting to the control socket (defaulting to /var/lib/tor/control)
- try connecting to the control ports (defaulting to 9051 and 9052)
- fall back to starting arm without a tor instance and show setup wizard
Control socket support is a feature request by Dererk on...
https://trac.torproject.org/projects/tor/ticket/3638
---
armrc.sample | 5 ++-
src/cli/headerPanel.py | 55 +++++++++++++++++---------
src/starter.py | 100 +++++++++++++++++++++++++++++++++++-------------
src/util/torTools.py | 22 ++++++++++
4 files changed, 135 insertions(+), 47 deletions(-)
diff --git a/armrc.sample b/armrc.sample
index d547b6c..22fb903 100644
--- a/armrc.sample
+++ b/armrc.sample
@@ -2,6 +2,7 @@
startup.controlPassword
startup.interface.ipAddress 127.0.0.1
startup.interface.port 9051
+startup.interface.socket /var/lib/tor/control
startup.blindModeEnabled false
startup.events N3
startup.dataDirectory ~/.arm
@@ -66,7 +67,9 @@ features.refreshRate 5
features.confirmQuit true
# Allows arm to start when there's no running tor instance if true, otherwise
-# we terminate right away.
+# we terminate right away. This is ignored if the user provides an option
+# specifying how to connect to tor (ie, a 'startup.interface.*' option or
+# startup argument).
features.allowDetachedStartup true
# Paremters for the log panel
diff --git a/src/cli/headerPanel.py b/src/cli/headerPanel.py
index 4e78f6c..d5ce332 100644
--- a/src/cli/headerPanel.py
+++ b/src/cli/headerPanel.py
@@ -21,6 +21,7 @@ import threading
import TorCtl.TorCtl
+import starter
import cli.popups
import cli.controller
@@ -40,6 +41,7 @@ VERSION_STATUS_COLORS = {"new": "blue", "new in series": "blue", "obsolete": "re
DEFAULT_CONFIG = {"startup.interface.ipAddress": "127.0.0.1",
"startup.interface.port": 9051,
+ "startup.interface.socket": "/var/lib/tor/control",
"features.showFdUsage": False,
"log.fdUsageSixtyPercent": log.NOTICE,
"log.fdUsageNinetyPercent": log.WARN}
@@ -132,27 +134,42 @@ 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:
- try:
- ctlAddr, ctlPort = self._config["startup.interface.ipAddress"], self._config["startup.interface.port"]
- tmpConn, authType, authValue = TorCtl.TorCtl.preauth_connect(ctlAddr, ctlPort)
-
- if authType == TorCtl.TorCtl.AUTH_TYPE.PASSWORD:
- authValue = cli.popups.inputPrompt("Controller Password: ")
- if not authValue: raise IOError() # cancel reconnection
-
- tmpConn.authenticate(authValue)
- torTools.getConn().init(tmpConn)
+ torctlConn = 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:
+ if not allowPortConnection:
+ cli.popups.showMsg("Unable to reconnect (%s)" % exc, 3)
+ elif not allowPortConnection:
+ cli.popups.showMsg("Unable to reconnect (socket '%s' doesn't exist)" % self._config["startup.interface.socket"], 3)
+
+ if not torctlConn and allowPortConnection:
+ try:
+ ctlAddr, ctlPort = self._config["startup.interface.ipAddress"], self._config["startup.interface.port"]
+ tmpConn, authType, authValue = TorCtl.TorCtl.preauth_connect(ctlAddr, ctlPort)
+
+ if authType == TorCtl.TorCtl.AUTH_TYPE.PASSWORD:
+ authValue = cli.popups.inputPrompt("Controller Password: ")
+ if not authValue: raise IOError() # cancel reconnection
+
+ tmpConn.authenticate(authValue)
+ torctlConn = tmpConn
+ except Exception, exc:
+ # attempts to use the wizard port too
+ try:
+ cli.controller.getController().getTorManager().connectManagedInstance()
+ log.log(log.NOTICE, "Reconnected to Tor's control port")
+ cli.popups.showMsg("Tor reconnected", 1)
+ except:
+ # 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)
log.log(log.NOTICE, "Reconnected to Tor's control port")
cli.popups.showMsg("Tor reconnected", 1)
- except Exception, exc:
- # attempts to use the wizard port too
- try:
- cli.controller.getController().getTorManager().connectManagedInstance()
- log.log(log.NOTICE, "Reconnected to Tor's control port")
- cli.popups.showMsg("Tor reconnected", 1)
- except:
- # displays notice for the first failed connection attempt
- if exc.args: cli.popups.showMsg("Unable to reconnect (%s)" % exc, 3)
else: isKeystrokeConsumed = False
return isKeystrokeConsumed
diff --git a/src/starter.py b/src/starter.py
index 01acf24..a148947 100644
--- a/src/starter.py
+++ b/src/starter.py
@@ -35,6 +35,7 @@ DEFAULT_CONFIG = os.path.expanduser("~/.arm/armrc")
CONFIG = {"startup.controlPassword": None,
"startup.interface.ipAddress": "127.0.0.1",
"startup.interface.port": 9051,
+ "startup.interface.socket": "/var/lib/tor/control",
"startup.blindModeEnabled": False,
"startup.events": "N3",
"startup.dataDirectory": "~/.arm",
@@ -52,14 +53,16 @@ CONFIG = {"startup.controlPassword": None,
"log.configDescriptions.persistance.saveFailed": util.log.NOTICE,
"log.savingDebugLog": util.log.NOTICE}
-OPT = "gi:c:dbe:vh"
-OPT_EXPANDED = ["gui", "interface=", "config=", "debug", "blind", "event=", "version", "help"]
+OPT = "gi:s:c:dbe:vh"
+OPT_EXPANDED = ["gui", "interface=", "socket=", "config=", "debug", "blind", "event=", "version", "help"]
HELP_MSG = """Usage arm [OPTION]
Terminal status monitor for Tor relays.
-g, --gui launch the Gtk+ interface
-i, --interface [ADDRESS:]PORT change control interface from %s:%i
+ -s, --socket SOCKET_PATH attach using unix domain socket if present,
+ SOCKET_PATH defaults to: %s
-c, --config CONFIG_PATH loaded configuration options, CONFIG_PATH
defaults to: %s
-d, --debug writes all arm logs to %s
@@ -72,7 +75,7 @@ Terminal status monitor for Tor relays.
Example:
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
-""" % (CONFIG["startup.interface.ipAddress"], CONFIG["startup.interface.port"], DEFAULT_CONFIG, LOG_DUMP_PATH, CONFIG["startup.events"], cli.logPanel.EVENT_LISTING)
+""" % (CONFIG["startup.interface.ipAddress"], CONFIG["startup.interface.port"], CONFIG["startup.interface.socket"], DEFAULT_CONFIG, LOG_DUMP_PATH, CONFIG["startup.events"], cli.logPanel.EVENT_LISTING)
# filename used for cached tor config descriptions
CONFIG_DESC_FILENAME = "torConfigDesc.txt"
@@ -94,6 +97,29 @@ STANDARD_CFG_NOT_FOUND_MSG = "No configuration found at '%s', using defaults"
# torrc entries that are scrubbed when dumping
PRIVATE_TORRC_ENTRIES = ["HashedControlPassword", "Bridge", "HiddenServiceDir"]
+def allowConnectionTypes():
+ """
+ This provides a tuple with booleans indicating if we should or shouldn't
+ attempt to connect by various methods...
+ (allowPortConnection, allowSocketConnection, allowDetachedStart)
+ """
+
+ confKeys = util.conf.getConfig("arm").getKeys()
+
+ isPortArgPresent = "startup.interface.ipAddress" in confKeys or "startup.interface.port" in confKeys
+ isSocketArgPresent = "startup.interface.socket" in confKeys
+
+ skipPortConnection = isSocketArgPresent and not isPortArgPresent
+ skipSocketConnection = isPortArgPresent and not isSocketArgPresent
+
+ # Flag to indicate if we'll start arm reguardless of being unable to connect
+ # to Tor. This is the default behavior if the user hasn't provided a port or
+ # socket to connect to, so we can show the relay setup wizard.
+
+ allowDetachedStart = CONFIG["features.allowDetachedStartup"] and not isPortArgPresent and not isSocketArgPresent
+
+ return (not skipPortConnection, not skipSocketConnection, allowDetachedStart)
+
def _loadConfigurationDescriptions(pathPrefix):
"""
Attempts to load descriptions for tor's configuration options, fetching them
@@ -168,7 +194,7 @@ def _loadConfigurationDescriptions(pathPrefix):
msg = DESC_INTERNAL_LOAD_FAILED_MSG % util.sysTools.getFileErrorMsg(exc)
util.log.log(CONFIG["log.configDescriptions.internalLoadFailed"], msg)
-def _torCtlConnect(controlAddr="127.0.0.1", controlPort=9051, passphrase=None, incorrectPasswordMsg=""):
+def _torCtlConnect(controlAddr="127.0.0.1", controlPort=9051, passphrase=None, incorrectPasswordMsg="", printError=True):
"""
Custom handler for establishing a TorCtl connection.
"""
@@ -222,7 +248,7 @@ def _torCtlConnect(controlAddr="127.0.0.1", controlPort=9051, passphrase=None, i
# again prompting for the user to enter it
print incorrectPasswordMsg
return _torCtlConnect(controlAddr, controlPort)
- elif not CONFIG["features.allowDetachedStartup"]:
+ elif printError:
print exc
return None
@@ -300,6 +326,8 @@ if __name__ == '__main__':
param["startup.interface.ipAddress"] = controlAddr
param["startup.interface.port"] = controlPort
+ elif opt in ("-s", "--socket"):
+ param["startup.interface.socket"] = arg
elif opt in ("-g", "--gui"): launchGui = True
elif opt in ("-c", "--config"): configPath = arg # sets path of user's config
elif opt in ("-d", "--debug"): isDebugMode = True # dumps all logs
@@ -361,7 +389,7 @@ if __name__ == '__main__':
for utilModule in (util.conf, util.connections, util.hostnames, util.log, util.panel, util.procTools, util.sysTools, util.torConfig, util.torTools, util.uiTools):
utilModule.loadConfig(config)
- # snycs config and parameters, saving changed config options and overwriting
+ # syncs config and parameters, saving changed config options and overwriting
# undefined parameters with defaults
for key in param.keys():
if param[key] == None: param[key] = CONFIG[key]
@@ -389,28 +417,46 @@ if __name__ == '__main__':
# temporarily disables TorCtl logging to prevent issues from going to stdout while starting
TorCtl.TorUtil.loglevel = "NONE"
- # 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)
- if conn == None and not CONFIG["features.allowDetachedStartup"]: sys.exit(1)
-
- # removing references to the controller password so the memory can be freed
- # (unfortunately python does allow for direct access to the memory so this
- # is the best we can do)
- del authPassword
- if "startup.controlPassword" in config.contents:
- del config.contents["startup.controlPassword"]
-
- pwLineNum = None
- for i in range(len(config.rawContents)):
- if config.rawContents[i].strip().startswith("startup.controlPassword"):
- pwLineNum = i
- break
+ # By default attempts to connect using the control socket if it exists. This
+ # skips attempting to connect by socket or port if the user has given
+ # arguments for connecting to the other.
+
+ conn = None
+ allowPortConnection, allowSocketConnection, allowDetachedStart = allowConnectionTypes()
+
+ socketPath = param["startup.interface.socket"]
+ if os.path.exists(socketPath) and allowSocketConnection:
+ try: conn = util.torTools.connect_socket(socketPath)
+ 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:
+ # 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)
- if pwLineNum != None:
- del config.rawContents[i]
+ # removing references to the controller password so the memory can be freed
+ # (unfortunately python does allow for direct access to the memory so this
+ # is the best we can do)
+ del authPassword
+ if "startup.controlPassword" in config.contents:
+ del config.contents["startup.controlPassword"]
+
+ pwLineNum = None
+ for i in range(len(config.rawContents)):
+ if config.rawContents[i].strip().startswith("startup.controlPassword"):
+ pwLineNum = i
+ break
+
+ if pwLineNum != None:
+ del config.rawContents[i]
+
+ if conn == 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
diff --git a/src/util/torTools.py b/src/util/torTools.py
index f81e10a..366deb0 100644
--- a/src/util/torTools.py
+++ b/src/util/torTools.py
@@ -111,6 +111,28 @@ IS_STARTUP_SIGNAL = True
def loadConfig(config):
config.update(CONFIG)
+# TODO: temporary code until this is added to torctl as part of...
+# https://trac.torproject.org/projects/tor/ticket/3638
+def connect_socket(socketPath="/var/lib/tor/control", ConnClass=TorCtl.Connection):
+ """
+ Connects to a unix domain socket available to controllers (set via tor's
+ ControlSocket option). This raises an IOError if unable to do so.
+
+ Arguments:
+ socketPath - path of the socket to attach to
+ ConnClass - connection type to instantiate
+ """
+
+ import socket
+ try:
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(socketPath)
+ conn = ConnClass(s)
+ conn.authenticate("")
+ return conn
+ except Exception, exc:
+ raise IOError(exc)
+
def getPid(controlPort=9051, pidFilePath=None):
"""
Attempts to determine the process id for a running tor process, using the
More information about the tor-commits
mailing list