commit b888d3657c85beed4c63d1db4e3df06f4ffeef3d
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Jul 4 22:45:19 2011 -0700
Torrc generation from the wizard input
This is most of the functionality needed to generate a torrc from the relay
setup wizard's input (minus a few special-case fields like the exit policy
and burst rate).
This is done via a template that's filled in by the wizard results. The
templating language just contains the simple set of control structures (if,
if not, or) needed for this.
---
src/cli/wizard.py | 43 +++++++++++++++-
src/resources/torrcTemplate.txt | 77 ++++++++++++++++++++++++++++
src/util/torConfig.py | 106 +++++++++++++++++++++++++++++++++++++++
3 files changed, 224 insertions(+), 2 deletions(-)
diff --git a/src/cli/wizard.py b/src/cli/wizard.py
index 8239487..03cfb48 100644
--- a/src/cli/wizard.py
+++ b/src/cli/wizard.py
@@ -3,13 +3,18 @@ Provides user prompts for setting up a new relay. This autogenerates a torrc
that's used by arm to run its own tor instance.
"""
+import os
+import sys
import functools
import curses
import cli.popups
import cli.controller
-from util import connections, enum, uiTools
+from util import connections, enum, log, torConfig, uiTools
+
+# template used to generate the torrc
+TORRC_TEMPLATE = "resources/torrcTemplate.txt"
# basic configuration types we can run as
RelayType = enum.Enum("RELAY", "EXIT", "BRIDGE", "CLIENT")
@@ -249,7 +254,10 @@ def showWizard():
selection = promptConfigOptions(relayType, config)
if selection == BACK: relayType = None
- elif selection == NEXT: break # TODO: implement next screen
+ elif selection == NEXT:
+ generatedTorrc = getTorrc(relayType, config)
+ log.log(log.NOTICE, "Resulting torrc:\n%s" % generatedTorrc)
+ break # TODO: implement next screen
# redraws screen to clear away the dialog we just showed
cli.controller.getController().requestRedraw(True)
@@ -407,6 +415,37 @@ def promptConfigOptions(relayType, config):
finally:
cli.popups.finalize()
+def getTorrc(relayType, config):
+ """
+ Provides the torrc generated for the given options.
+ """
+
+ pathPrefix = os.path.dirname(sys.argv[0])
+ if pathPrefix and not pathPrefix.endswith("/"):
+ pathPrefix = pathPrefix + "/"
+
+ templateFile = open("%s%s" % (pathPrefix, TORRC_TEMPLATE), "r")
+ template = templateFile.readlines()
+ templateFile.close()
+
+ # generates the options the template expects
+ templateOptions = {}
+
+ for key, value in config.items():
+ if isinstance(value, ConfigOption):
+ value = value.getValue()
+
+ templateOptions[key.upper()] = value
+
+ #templateOptions = dict([(key.upper(), config[key].getValue()) for key in config])
+ templateOptions[relayType.upper()] = True
+ templateOptions["LOW_PORTS"] = config[Options.LOWPORTS]
+ #templateOptions["BURST"] = config[Options.BANDWIDTH] * 2 # TODO: implement
+ templateOptions["NOTICE_PATH"] = "/path/to/.arm/exit-notice.html" # TODO: actually prepend the right prefix
+ templateOptions["EXIT_POLICY"] = "" # TODO: fill in configured policy
+
+ return torConfig.renderTorrc(template, templateOptions)
+
def _splitStr(msg, width):
"""
Splits a string into substrings of a given length.
diff --git a/src/resources/torrcTemplate.txt b/src/resources/torrcTemplate.txt
new file mode 100644
index 0000000..bb4dccd
--- /dev/null
+++ b/src/resources/torrcTemplate.txt
@@ -0,0 +1,77 @@
+# This is a tor configuration made by arm. To change the configuration by hand
+# edit this file then either...
+# - tell arm to reset tor by pressing 'x'
+# - run 'pkill -sighup tor'
+# - restart tor
+#
+# Descriptions of all of these configuraiton attibutes (and many more) are
+# available in the tor man page.
+[NEWLINE]
+ControlPort 9052 # port controllers can connect to
+CookieAuthentication 1 # method for controller authentication
+
+[IF RELAY | EXIT | BRIDGE]
+ RunAsDaemon 1 # runs as a background process
+[END IF]
+[NEWLINE]
+
+[IF RELAY | EXIT | BRIDGE]
+ [IF LOWPORTS]
+ ORPort 443 # port used for relaying traffic
+ [ELSE]
+ ORPort 9001 # port used for relaying traffic
+ [END IF]
+
+ [IF RELAY | EXIT]
+ [IF LOWPORTS]
+ DirPort 80 # port used for mirroring directory information
+ [ELSE]
+ DirPort 9030 # port used for mirroring directory information
+ [END IF]
+
+ Nickname [NICKNAME] # name for this relay
+ ContactInfo [CONTACT] # contact information in case there's an issue
+ [END IF]
+
+ [IF BRIDGE]
+ BridgeRelay 1 # makes us a bridge rather than a public relay
+
+ [IF DISTRIBUTE]
+ # TODO: drop this?
+ PublishServerDescriptor bridge # publishes to users looking for bridges
+ [ELSE]
+ PublishServerDescriptor 0 # keeps our bridge descriptor private
+ [END IF]
+ [END IF]
+
+ RelayBandwidthRate [BANDWIDTH] # limit for the bandwidth we'll use to relay
+ RelayBandwidthBurst [BURST] # maximum rate when relaying bursts of traffic
+ AccountingMax [LIMIT] # maximum amount of traffic we'll relay per month
+
+ [IF NOT CLIENT]
+ SocksPort 0 # prevents tor from being used as a client
+ [END IF]
+
+ [IF PORTFORWARD]
+ PortForwarding 1 # negotiates UPnP and NAT-PMP if needed
+ [END IF]
+
+ [IF RELAY | BRIDGE]
+ ExitPolicy reject *:* # prevents us from connecting to non-relays
+ [ELSE]
+ [IF NOTICE]
+ DirPortFrontPage [NOTICE_PATH] # disclaimer saying that this is an exit
+ [END IF]
+
+ [EXIT_POLICY]
+ [END IF]
+[ELSE]
+ ClientOnly 1 # prevents us from ever being used as a relay
+ MaxCircuitDirtiness [REUSE] # duration that circuits can be reused
+
+ [IF BRIDGED]
+ UseBridges 1 # uses the following bridges to connect
+ [BRIDGES]
+ [END IF]
+[END IF]
+
diff --git a/src/util/torConfig.py b/src/util/torConfig.py
index a63eddb..e5f1cb2 100644
--- a/src/util/torConfig.py
+++ b/src/util/torConfig.py
@@ -845,3 +845,109 @@ def _testConfigDescriptions():
print "\"%s\"" % description
if i != len(sortedOptions) - 1: print "-" * 80
+def renderTorrc(template, options, commentIndent = 30):
+ """
+ Uses the given template to generate a nicely formatted torrc with the given
+ options. The tempating language this recognizes is a simple one, recognizing
+ the following options:
+ [IF <option>] # if <option> maps to true or a non-empty string
+ [IF NOT <option>] # logical inverse
+ [IF <opt1> | <opt2>] # logical or of the options
+ [ELSE] # if the prior conditional evaluated to false
+ [END IF] # ends the control block
+
+ [<option>] # inputs the option value, omitting the line if it maps
+ # to a boolean or empty string
+ [NEWLINE] # empty line, otherwise templating white space is ignored
+
+ Arguments:
+ template - torrc template lines used to generate the results
+ options - mapping of keywords to their given values, with values
+ being booleans or strings (possibly multi-line)
+ commentIndent - minimum column that comments align on
+ """
+
+ results = []
+ templateIter = iter(template)
+ commentLineFormat = "%%-%is%%s" % commentIndent
+
+ try:
+ while True:
+ line = templateIter.next().strip()
+
+ if line.startswith("[IF ") and line.endswith("]"):
+ # checks if any of the conditional options are true or a non-empty string
+ evaluatesTrue = False
+ for cond in line[4:-1].split("|"):
+ isInverse = False
+ if cond.startswith("NOT "):
+ isInverse = True
+ cond = cond[4:]
+
+ if isInverse != bool(options.get(cond.strip())):
+ evaluatesTrue = True
+ break
+
+ if evaluatesTrue:
+ continue
+ else:
+ # skips lines until we come to an else or the end of the block
+ depth = 0
+
+ while depth != -1:
+ line = templateIter.next().strip()
+
+ if line.startswith("[IF ") and line.endswith("]"): depth += 1
+ elif line == "[END IF]": depth -= 1
+ elif depth == 0 and line == "[ELSE]": depth -= 1
+ elif line == "[ELSE]":
+ # an else block we aren't using - skip to the end of it
+ depth = 0
+
+ while depth != -1:
+ line = templateIter.next().strip()
+
+ if line.startswith("[IF "): depth += 1
+ elif line == "[END IF]": depth -= 1
+ elif line == "[NEWLINE]":
+ # explicit newline
+ results.append("")
+ elif line.startswith("#"):
+ # comment only
+ results.append(line)
+ elif line.startswith("[") and line.endswith("]"):
+ # completely dynamic entry
+ optValue = options.get(line[1:-1])
+ if optValue: results.append(optValue)
+ else:
+ # torrc option line
+ option, arg, comment = "", "", ""
+ parsedLine = line
+
+ if "#" in parsedLine:
+ parsedLine, comment = parsedLine.split("#", 1)
+ parsedLine = parsedLine.strip()
+ comment = "# %s" % comment.strip()
+
+ # parses the argument from the option
+ if " " in parsedLine:
+ option, arg = parsedLine.split(" ", 1)
+ option = option.strip()
+ else:
+ log.log(log.INFO, "torrc template option lacks an argument: '%s'" % line)
+ continue
+
+ # inputs dynamic arguments
+ if arg.startswith("[") and arg.endswith("]"):
+ arg = options.get(arg[1:-1])
+
+ # skips argument if it's false or an empty string
+ if not arg: continue
+
+ torrcEntry = "%s %s" % (option, arg)
+ if comment: results.append(commentLineFormat % (torrcEntry + " ", comment))
+ else: results.append(torrcEntry)
+ except StopIteration: pass
+
+ return "\n".join(results)
+