commit 4d88884b0cfdc98ca4c4741d4e1073267418038c Author: Sathyanarayanan Gunasekaran gsathya.ceg@gmail.com Date: Fri Mar 23 15:18:44 2012 +0530
Add doc and fix whitespace --- stem/exit_policy.py | 303 +++++++++++++++++++++++++++++---------------------- 1 files changed, 171 insertions(+), 132 deletions(-)
diff --git a/stem/exit_policy.py b/stem/exit_policy.py index 287b620..0a51f5f 100644 --- a/stem/exit_policy.py +++ b/stem/exit_policy.py @@ -1,152 +1,191 @@ +""" +Tor Exit Policy information and requirements for its features. These can be +easily parsed and compared, for instance... + +>>> exit_policies = stem.exit_policy.ExitPolicy() +>>> exit_policies.add("reject *:*") +>>> print exit_policies +reject *:* + +ExitPolicyLine - Single rule from the exit policy + |- __str__ - string representation + +- check - check if exiting to this ip is allowed + +ExitPolicy - List of ExitPolicyLine objects + |- __str__ - string representation + |- __iter__ - ExitPolicyLine entries for the exit policy + |- check - check if exiting to this ip is allowed + |- add - add new rule to the exit policy + +- isExitingAllowed - check if exit node +""" + # ip address ranges substituted by the 'private' keyword PRIVATE_IP_RANGES = ("0.0.0.0/8", "169.254.0.0/16", "127.0.0.0/8", "192.168.0.0/16", "10.0.0.0/8", "172.16.0.0/12")
class ExitPolicyLine: - def __init__(self, ruleEntry): - # sanitize the input a bit, cleaning up tabs and stripping quotes - ruleEntry = ruleEntry.replace("\t", " ").replace(""", "") - - self.ruleEntry = ruleEntry - self.isAccept = ruleEntry.startswith("accept") - - # strips off "accept " or "reject " and extra spaces - ruleEntry = ruleEntry[7:].replace(" ", "") - - # split ip address (with mask if provided) and port - if ":" in ruleEntry: entryIp, entryPort = ruleEntry.split(":", 1) - else: entryIp, entryPort = ruleEntry, "*" - - # sets the ip address component - self.isIpWildcard = entryIp == "*" or entryIp.endswith("/0") - - if "/" in entryIp: - ipComp = entryIp.split("/", 1) - self.ipAddress = ipComp[0] - self.ipMask = int(ipComp[1]) - else: - self.ipAddress = entryIp - self.ipMask = 32 - - # constructs the binary address just in case of comparison with a mask - if self.ipAddress != "*": - self.ipAddressBin = "" - for octet in self.ipAddress.split("."): - # Converts the int to a binary string, padded with zeros. Source: - # http://www.daniweb.com/code/snippet216539.html - self.ipAddressBin += "".join([str((int(octet) >> y) & 1) for y in range(7, -1, -1)]) - else: - self.ipAddressBin = "0" * 32 + """ + Single rule from the user's exit policy. These are chained together to form + complete policies. + """
- # sets the port component - self.minPort, self.maxPort = 0, 0 - self.isPortWildcard = entryPort == "*" - - if entryPort != "*": - if "-" in entryPort: - portComp = entryPort.split("-", 1) - self.minPort = int(portComp[0]) - self.maxPort = int(portComp[1]) - else: - self.minPort = int(entryPort) - self.maxPort = int(entryPort) - - def __str__(self): - # This provides the actual policy rather than the entry used to construct - # it so the 'private' keyword is expanded. - - acceptanceLabel = "accept" if self.isAccept else "reject" + def __init__(self, rule_entry): + """ + Exit Policy line constructor. + """ + # sanitize the input a bit, cleaning up tabs and stripping quotes + rule_entry = rule_entry.replace("\t", " ").replace(""", "")
- if self.isIpWildcard: - ipLabel = "*" - elif self.ipMask != 32: - ipLabel = "%s/%i" % (self.ipAddress, self.ipMask) - else: ipLabel = self.ipAddress + self.rule_entry = rule_entry + self.is_accept = rule_entry.startswith("accept")
- if self.isPortWildcard: - portLabel = "*" - elif self.minPort != self.maxPort: - portLabel = "%i-%i" % (self.minPort, self.maxPort) - else: portLabel = str(self.minPort) + # strips off "accept " or "reject " and extra spaces + rule_entry = rule_entry[7:].replace(" ", "")
- myPolicy = "%s %s:%s" % (acceptanceLabel, ipLabel, portLabel) - return myPolicy + # split ip address (with mask if provided) and port + if ":" in rule_entry: entry_ip, entry_port = rule_entry.split(":", 1) + else: entry_ip, entry_port = rule_entry, "*"
- def check(self, ipAddress, port): - """ - Checks if the rule chain allows exiting to this address, returning true if - so and false otherwise. - """ + # sets the ip address component + self.is_ip_wildcard = entry_ip == "*" or entry_ip.endswith("/0")
- port = int(port) + # separate host and mask + if "/" in entry_ip: + ip_comp = entry_ip.split("/", 1) + self.ip_address = ip_comp[0] + self.ip_mask = int(ip_comp[1]) + else: + self.ip_address = entry_ip + self.ip_mask = 32
- # does the port check first since comparing ip masks is more work - isPortMatch = self.isPortWildcard or (port >= self.minPort and port <= self.maxPort) + # constructs the binary address just in case of comparison with a mask + if self.ip_address != "*": + self.ip_address_bin = "" + for octet in self.ip_address.split("."): + # Converts the int to a binary string, padded with zeros. Source: + # http://www.daniweb.com/code/snippet216539.html + self.ip_address_bin += "".join([str((int(octet) >> y) & 1) for y in range(7, -1, -1)]) + else: + self.ip_address_bin = "0" * 32 + + # sets the port component + self.min_port, self.max_port = 0, 0 + self.is_port_wildcard = entry_port == "*"
- if isPortMatch: - isIpMatch = self.isIpWildcard or self.ipAddress == ipAddress + if entry_port != "*": + if "-" in entry_port: + port_comp = entry_port.split("-", 1) + self.min_port = int(port_comp[0]) + self.max_port = int(port_comp[1]) + else: + self.min_port = int(entry_port) + self.max_port = int(entry_port)
- # expands the check to include the mask if it has one - if not isIpMatch and self.ipMask != 32: - inputAddressBin = "" - for octet in ipAddress.split("."): - inputAddressBin += "".join([str((int(octet) >> y) & 1) for y in range(7, -1, -1)]) - - isIpMatch = self.ipAddressBin[:self.ipMask] == inputAddressBin[:self.ipMask] - - if isIpMatch: return self.isAccept + def __str__(self): + # This provides the actual policy rather than the entry used to construct + # it so the 'private' keyword is expanded. + + acceptance_label = "accept" if self.is_accept else "reject" + + if self.is_ip_wildcard: + ip_label = "*" + elif self.ip_mask != 32: + ip_label = "%s/%i" % (self.ip_address, self.ip_mask) + else: ip_label = self.ip_address + + if self.is_port_wildcard: + port_label = "*" + elif self.min_port != self.max_port: + port_label = "%i-%i" % (self.min_port, self.max_port) + else: port_label = str(self.min_port) + + my_policy = "%s %s:%s" % (acceptance_label, ip_label, port_label) + return my_policy + + def check(self, ip_address, port): + """ + Checks if the rule chain allows exiting to this address, returning true if + so and false otherwise. + """ + + port = int(port) + + # does the port check first since comparing ip masks is more work + is_port_match = self.is_port_wildcard or (port >= self.min_port and port <= self.max_port) + + if is_port_match: + is_ip_match = self.is_ip_wildcard or self.ip_address == ip_address + + # expands the check to include the mask if it has one + if not is_ip_match and self.ip_mask != 32: + input_address_bin = "" + for octet in ip_address.split("."): + input_address_bin += "".join([str((int(octet) >> y) & 1) for y in range(7, -1, -1)]) + + is_ip_match = self.ip_address_bin[:self.ip_mask] == input_address_bin[:self.ip_mask] + + if is_ip_match: return self.is_accept
- # fell off the chain without a conclusion (shouldn't happen...) - return False + # fell off the chain without a conclusion (shouldn't happen...) + return False
- + class ExitPolicy: + """ + Provides a wrapper to ExitPolicyLine. This is iterable and can be stringified for + individual Exit Policy lines. + """ + + def __init__(self): """ - Single rule from the user's exit policy. These are chained together to form - complete policies. + ExitPolicy constructor """ - - def __init__(self): - """ - Exit policy rule constructor. - """ - self._policies = [] - - def add(self, ruleEntry): - # checks for the private alias (which expands this to a chain of entries) - if "private" in ruleEntry.lower(): - for addr in PRIVATE_IP_RANGES: - newEntry = ruleEntry.replace("private", addr) - self._policies.append(ExitPolicyLine(newEntry)) - else: - self._policies.append(ExitPolicyLine(ruleEntry)) - - def isExitingAllowed(self): - """ - Provides true if the policy allows exiting whatsoever, false otherwise. - """ - for policy in self._policies: - if policy.isAccept: return True - elif policy.isIpWildcard and self.isPortWildcard: return False - - - def check(self, ipAddress, port): - """ - Checks if the rule chain allows exiting to this address, returning true if - so and false otherwise. - """ - - for policy in self._policies: - if policy.check(ipAddress, port): return True + self._policies = []
- return False - - def __iter__(self): - for policy in self._policies: - yield policy + def add(self, rule_entry): + """ + This method is used to add an Exit Policy rule to the list of policies. + + Arguments: + rule_entry (str) - exit policy rule in the format "accept|reject ADDR[/MASK][:PORT]" + ex - "accept 18.7.22.69:*" + """ + # checks for the private alias (which expands this to a chain of entries) + if "private" in rule_entry.lower(): + for addr in PRIVATE_IP_RANGES: + new_entry = rule_entry.replace("private", addr) + self._policies.append(ExitPolicyLine(new_entry)) + else: + self._policies.append(ExitPolicyLine(rule_entry)) + + def is_exiting_allowed(self): + """ + Provides true if the policy allows exiting whatsoever, false otherwise. + """ + for policy in self._policies: + if policy.is_accept: return True + elif policy.is_ip_wildcard and policy.is_port_wildcard: return False + + def check(self, ip_address, port): + """ + Checks if the rule chain allows exiting to this address, returning true if + so and false otherwise. + """ + + for policy in self._policies: + if policy.check(ip_address, port): return True + + return False + + def __iter__(self): + """ + Provides an ordered listing of policies in this Exit Policy + """ + for policy in self._policies: + yield policy + + def __str__(self): + """ + Provides the string used to construct the Exit Policy + """ + return ' , '.join([str(policy) for policy in self._policies])
- def __str__(self): - # This provides the actual policy rather than the entry used to construct - # it so the 'private' keyword is expanded. - - return ' , '.join([str(policy) for policy in self._policies]) -