commit b888d3657c85beed4c63d1db4e3df06f4ffeef3d Author: Damian Johnson atagar@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) +
tor-commits@lists.torproject.org