commit e8c3836e203a3e359a9b5b9a289dc73ddc3da92c Author: Damian Johnson atagar@torproject.org Date: Mon Jun 27 08:54:07 2011 -0700
Wizard config for setting up an exit
This expands the wizard to provide a config panel for setting up an exit relay. This has the same options as an internal relay plus several for DirPortFrontPage and ExitPolicy.
By default this will use the reduced exit policy [1], with options like Vidalia for selecting groups of services to allow. However, unlike Vidalia it won't allow the operator to pick plaintext ports without also having their encryped counterparts.
[1] https://trac.torproject.org/projects/tor/wiki/doc/ReducedExitPolicy --- src/cli/wizard.py | 155 ++++++++++++++++++++++++++++++++++++++++++++-------- src/settings.cfg | 37 ++++++++++++- 2 files changed, 167 insertions(+), 25 deletions(-)
diff --git a/src/cli/wizard.py b/src/cli/wizard.py index dadea16..0d47de5 100644 --- a/src/cli/wizard.py +++ b/src/cli/wizard.py @@ -3,6 +3,7 @@ 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 functools import curses
import cli.popups @@ -14,15 +15,30 @@ from util import enum, uiTools RelayType = enum.Enum("RELAY", "EXIT", "BRIDGE", "CLIENT")
# all options that can be configured -Options = enum.Enum("DIVIDER", "NICKNAME", "CONTACT", "NOTIFY", "BANDWIDTH", "LIMIT", "STARTUP") +Options = enum.Enum("DIVIDER", "NICKNAME", "CONTACT", "NOTIFY", "BANDWIDTH", "LIMIT", "STARTUP", "NOTICE", "POLICY", "WEBSITES", "EMAIL", "IM", "MISC", "PLAINTEXT") RelayOptions = {RelayType.RELAY: (Options.NICKNAME, Options.CONTACT, Options.NOTIFY, Options.BANDWIDTH, - Options.DIVIDER, - Options.DIVIDER, Options.LIMIT, - Options.STARTUP)} + Options.STARTUP), + RelayType.EXIT: (Options.NICKNAME, + Options.CONTACT, + Options.NOTIFY, + Options.BANDWIDTH, + Options.LIMIT, + Options.STARTUP, + Options.DIVIDER, + Options.NOTICE, + Options.POLICY, + Options.WEBSITES, + Options.EMAIL, + Options.IM, + Options.MISC, + Options.PLAINTEXT)} + +# custom exit policy options +CUSTOM_POLICIES = (Options.WEBSITES, Options.EMAIL, Options.IM, Options.MISC, Options.PLAINTEXT)
# other options provided in the prompts CANCEL, NEXT, BACK = "Cancel", "Next", "Back" @@ -30,10 +46,13 @@ CANCEL, NEXT, BACK = "Cancel", "Next", "Back" DESC_SIZE = 5 # height of the description field MSG_COLOR = "green" OPTION_COLOR = "yellow" +DISABLED_COLOR = "cyan"
CONFIG = {"wizard.message.role": "", "wizard.message.relay": "", + "wizard.message.exit": "", "wizard.toggle": {}, + "wizard.suboptions": [], "wizard.default": {}, "wizard.label.general": {}, "wizard.label.role": {}, @@ -65,6 +84,8 @@ class ConfigOption: self.descriptionCache = None self.descriptionCacheArg = None self.value = default + self.validator = None + self._isEnabled = True
def getKey(self): return self.key @@ -75,7 +96,39 @@ class ConfigOption: def getDisplayValue(self): return self.value
+ def getDisplayAttr(self): + myColor = OPTION_COLOR if self.isEnabled() else DISABLED_COLOR + return curses.A_BOLD | uiTools.getColor(myColor) + + def isEnabled(self): + return self._isEnabled + + def setEnabled(self, isEnabled): + self._isEnabled = isEnabled + + def setValidator(self, validator): + """ + Custom function used to check that a value is valid before setting it. + This functor should accept two arguments: this option and the value we're + attempting to set. If its invalid then a ValueError with the reason is + expected. + + Arguments: + validator - functor for checking the validitiy of values we set + """ + + self.validator = validator + def setValue(self, value): + """ + Attempts to set our value. If a validator has been set then we first check + if it's alright, raising a ValueError with the reason if not. + + Arguments: + value - value we're attempting to set + """ + + if self.validator: self.validator(self, value) self.value = value
def getLabel(self, prefix = ""): @@ -90,6 +143,10 @@ class ConfigOption: return [prefix + line for line in self.descriptionCache]
class ToggleConfigOption(ConfigOption): + """ + Configuration option representing a boolean. + """ + def __init__(self, key, group, default, trueLabel, falseLabel): ConfigOption.__init__(self, key, group, default) self.trueLabel = trueLabel @@ -99,11 +156,20 @@ class ToggleConfigOption(ConfigOption): return self.trueLabel if self.value else self.falseLabel
def toggle(self): + # This isn't really here to validate the value (after all this is a + # boolean, the options are limited!), but rather give a method for functors + # to be triggered when selected. + + if self.validator: self.validator(self, not self.value) self.value = not self.value
def showWizard(): - relayType, config = None, {} + """ + Provides a series of prompts, allowing the user to spawn a customized tor + instance. + """
+ relayType, config = None, {} for option in Options.values(): if option == Options.DIVIDER: config[option] = option @@ -121,12 +187,22 @@ def showWizard(): config[option] = ToggleConfigOption(option, "opt", isSet, trueLabel.strip(), falseLabel.strip()) else: config[option] = ConfigOption(option, "opt", default)
+ # sets input validators + + # enables custom policies when 'custom' is selected and disables otherwise + policyOpt = config[Options.POLICY] + policyOpt.setValidator(functools.partial(_exitPolicyAction, config)) + _exitPolicyAction(config, policyOpt, policyOpt.getValue()) + + # remembers the last selection made on the type prompt page + relaySelection = RelayType.RELAY + while True: if relayType == None: - selection = promptRelayType() + selection = promptRelayType(relaySelection)
if selection == CANCEL: break - else: relayType = selection + else: relayType, relaySelection = selection, selection else: selection = promptConfigOptions(relayType, config)
@@ -136,7 +212,7 @@ def showWizard(): # redraws screen to clear away the dialog we just showed cli.controller.getController().requestRedraw(True)
-def promptRelayType(): +def promptRelayType(initialSelection): """ Provides a prompt for selecting the general role we'd like Tor to run with. This returns a RelayType enumeration for the selection, or CANCEL if the @@ -146,9 +222,9 @@ def promptRelayType(): popup, _, _ = cli.popups.init(25, 58) if not popup: return control = cli.controller.getController() - key, selection = 0, 0 options = [ConfigOption(opt, "role", opt) for opt in RelayType.values()] options.append(ConfigOption(CANCEL, "general", CANCEL)) + selection = RelayType.indexOf(initialSelection)
try: popup.win.box() @@ -162,21 +238,21 @@ def promptRelayType(): while True: y, offset = len(topContent) + 1, 0
- for i in range(len(options)): + for opt in options: optionFormat = uiTools.getColor(MSG_COLOR) - if i == selection: optionFormat |= curses.A_STANDOUT + if opt == options[selection]: optionFormat |= curses.A_STANDOUT
# Curses has a weird bug where there's a one-pixel alignment # difference between bold and regular text, so it looks better # to render the whitespace here as not being bold.
offset += 1 - label = options[i].getLabel(" ") + label = opt.getLabel(" ") popup.addstr(y + offset, 2, label, optionFormat | curses.A_BOLD) popup.addstr(y + offset, 2 + len(label), " " * (54 - len(label)), optionFormat) offset += 1
- for line in options[i].getDescription(52, " "): + for line in opt.getDescription(52, " "): popup.addstr(y + offset, 2, uiTools.padStr(line, 54), optionFormat) offset += 1
@@ -218,22 +294,42 @@ def promptConfigOptions(relayType, config): popup.win.erase() popup.win.box()
- # provides the description for internal relays + # provides the description for the relay type for i in range(len(topContent)): popup.addstr(i + 1, 2, topContent[i], curses.A_BOLD | uiTools.getColor(MSG_COLOR))
y, offset = len(topContent) + 1, 0 - for i in range(len(options)): - if options[i] == Options.DIVIDER: + for opt in options: + if opt == Options.DIVIDER: offset += 1 continue
- label = " %-30s%s" % (options[i].getLabel(), options[i].getDisplayValue()) - optionFormat = curses.A_BOLD | uiTools.getColor(OPTION_COLOR) - if i == selection: optionFormat |= curses.A_STANDOUT + optionFormat = opt.getDisplayAttr() + if opt == options[selection]: optionFormat |= curses.A_STANDOUT
- offset += 1 - popup.addstr(y + offset, 2, uiTools.padStr(label, 54), optionFormat) + offset, indent = offset + 1, 0 + if opt.getKey() in CONFIG["wizard.suboptions"]: + # If the next entry is also a suboption then show a 'T', otherwise + # end the bracketing. + + bracketChar, nextIndex = curses.ACS_LLCORNER, options.index(opt) + 1 + if nextIndex < len(options) and isinstance(options[nextIndex], ConfigOption): + if options[nextIndex].getKey() in CONFIG["wizard.suboptions"]: + bracketChar = curses.ACS_LTEE + + popup.addch(y + offset, 3, bracketChar, opt.getDisplayAttr()) + popup.addch(y + offset, 4, curses.ACS_HLINE, opt.getDisplayAttr()) + + indent = 3 + + labelFormat = " %%-%is%%s" % (30 - indent) + label = labelFormat % (opt.getLabel(), opt.getDisplayValue()) + popup.addstr(y + offset, 2 + indent, uiTools.padStr(label, 54 - indent), optionFormat) + + # little hack to make "Block" policies red + if opt != options[selection] and not opt.getValue() and opt.getKey() in CUSTOM_POLICIES: + optionFormat = curses.A_BOLD | uiTools.getColor("red") + popup.addstr(y + offset, 33, opt.getDisplayValue(), optionFormat)
# divider between the options and description offset += 2 @@ -253,8 +349,8 @@ def promptConfigOptions(relayType, config): posOffset = -1 if key == curses.KEY_UP else 1 selection = (selection + posOffset) % len(options)
- # skips dividers - while options[selection] == Options.DIVIDER: + # skips disabled options and dividers + while options[selection] == Options.DIVIDER or not options[selection].isEnabled(): selection = (selection + posOffset) % len(options) elif uiTools.isSelectionKey(key): if selection == len(options) - 2: return BACK # selected back @@ -263,7 +359,9 @@ def promptConfigOptions(relayType, config): options[selection].toggle() else: newValue = popup.getstr(y + selection + 1, 33, options[selection].getValue(), curses.A_STANDOUT | uiTools.getColor(OPTION_COLOR), 23) - if newValue: options[selection].setValue(newValue.strip()) + if newValue: + try: options[selection].setValue(newValue.strip()) + except ValueError, exc: cli.popups.showMsg(str(exc), 3) elif key == 27: selection, key = -1, curses.KEY_ENTER # esc - cancel finally: cli.popups.finalize() @@ -280,7 +378,16 @@ def _splitStr(msg, width): results = [] while msg: msgSegment, msg = uiTools.cropStr(msg, width, None, endType = None, getRemainder = True) + if not msgSegment: break # happens if the width is less than the first word results.append(msgSegment.strip())
return results
+def _exitPolicyAction(config, option, value): + """ + Enables or disables custom exit policy options based on our selection. + """ + + for opt in CUSTOM_POLICIES: + config[opt].setEnabled(not value) + diff --git a/src/settings.cfg b/src/settings.cfg index 94ff9c1..135b0e2 100644 --- a/src/settings.cfg +++ b/src/settings.cfg @@ -343,14 +343,35 @@ msg.ARM_DEBUG Unable to query process resource usage from ps # configuration option attributes used in the relay setup wizard wizard.message.role Welcome to the Tor network! This will step you through the configuration process for becoming a part of it. To start with, what role would you like to have? wizard.message.relay Internal relays provide connections within the Tor network. Since you will only be connecting to Tor users and relays this is an easy, hassle free way of helping to make the network better. +wizard.message.exit Exits connect between the Tor network and the outside Internet. This is the most vitally important role you can take, but it also needs some forethought. Please read 'http://www.atagar.com/torExitTips/' before proceeding further to avoid any nasty surprises!
wizard.toggle Notify => Yes, No wizard.toggle Startup => Yes, No +wizard.toggle Notice => Yes, No +wizard.toggle Policy => Default, Custom +wizard.toggle Websites => Allow, Block +wizard.toggle Email => Allow, Block +wizard.toggle Im => Allow, Block +wizard.toggle Misc => Allow, Block +wizard.toggle Plaintext => Allow, Block + +wizard.suboptions Websites +wizard.suboptions Email +wizard.suboptions Im +wizard.suboptions Misc +wizard.suboptions Plaintext
wizard.default Nickname => Unnamed wizard.default Notify => true wizard.default Bandwidth => 5 MB/s wizard.default Startup => true +wizard.default Notice => true +wizard.default Policy => true +wizard.default Websites => true +wizard.default Email => true +wizard.default Im => true +wizard.default Misc => true +wizard.default Plaintext => true
wizard.label.general Cancel => Cancel wizard.label.general Back => Previous @@ -365,10 +386,17 @@ wizard.label.opt Notify => Issue Notification wizard.label.opt Bandwidth => Relay Speed wizard.label.opt Limit => Monthly Limit wizard.label.opt Startup => Run At Startup +wizard.label.opt Notice => Disclaimer Notice +wizard.label.opt Policy => Exit Policy +wizard.label.opt Websites => Web Browsing +wizard.label.opt Email => Receiving Email +wizard.label.opt Im => Instant Messaging +wizard.label.opt Misc => Other Services +wizard.label.opt Plaintext => Unencrypted Traffic
wizard.description.general Cancel => Close without starting Tor. wizard.description.role Relay => Provides interconnections with other Tor relays. This is a safe and easy of making the network better. -wizard.description.role Exit => Connects between Tor an the outside Internet. This is a vital role, but can lead to abuse complaints. +wizard.description.role Exit => Connects between Tor network and the outside Internet. This is a vital role, but can lead to abuse complaints. wizard.description.role Bridge => Non-public relay specifically for helping censored users. wizard.description.role Client => Use the network without contributing to it. wizard.description.opt Nickname => Human friendly name for your relay. If this is unique then it's used instead of your fingerprint (a forty character hex string) when pages like TorStatus refer to you. @@ -377,6 +405,13 @@ wizard.description.opt Notify => Sends automated email notifications to the abov wizard.description.opt Bandwidth => Limit for the average rate at which you relay traffic. wizard.description.opt Limit => Maximum amount of traffic to relay each month. Some ISPs, like Comcast, cap their customer's Internet usage so this is an easy way of staying below that limit. wizard.description.opt Startup => Runs Tor in the background when the system starts. +wizard.description.opt Notice => Provides a disclaimer that this is an exit on port 80 (http://www.atagar.com/exitNotice). +wizard.description.opt Policy => Ports allowed to exit from your relay. The default policy allows for common services while limiting the chance of getting a DMCA takedown for torrent traffic (http://www.atagar.com/exitPolicy). +wizard.description.opt Websites => General Internet browsing including HTTP (80) and HTTPS (443). +wizard.description.opt Email => Protocols for receiving, but not sending email. This includes POP3 (110), POP3S (995), IMAP (143), and IMAPS (993). +wizard.description.opt Im => Common instant messaging protocols including Jabber, IRC, ICQ, AIM, Yahoo, MSN, SILC, GroupWise, Gadu-Gadu, Sametime, and Zephyr. +wizard.description.opt Misc => Protocols from the default policy that aren't among the above. +wizard.description.opt Plaintext => When blocked the policy will exclude ports that aren't commonly encrypted.
# some config options are fetched via special values torrc.map HiddenServiceDir => HiddenServiceOptions
tor-commits@lists.torproject.org