diff --git a/contrib/exitlist b/contrib/exitlist new file mode 100755 index 0000000000..188b8db44a --- /dev/null +++ b/contrib/exitlist @@ -0,0 +1,241 @@ +#!/usr/bin/python +# Copyright 2005 Nick Mathewson +# See the LICENSE file in the Tor distribution for licensing information. + +""" + exitlist -- Given a Tor directory on stdin, lists the Tor servers + that accept connections to given addreses. + + example usage: + + python2.2 exitlist < ~/.tor/cached-directory +""" + +# Requires Python 2.2 or later. + +# +# Change this to True if you want more verbose output. By default, we +# only print the IPs of the servers that accept any the listed +# addresses, one per line. +# +VERBOSE = False + +# +# Change this to True if you want to reverse the output, and list the +# servers that accept *none* of the listed addresses. +# +INVERSE = False + +# +# Change this list to contain all of the target services you are interested +# in. It must contain one entry per line, each consisting of an IPv4 address, +# a colon, and a port number. +# +ADDRESSES_OF_INTEREST = """ + 192.168.0.1:80 +""" + + +# +# YOU DO NOT NEED TO EDIT AFTER THIS POINT. +# + +import sys +import re +import getopt +import socket +import struct + +assert sys.version_info >= (2,2) + + +def maskIP(ip,mask): + return "".join([chr(ord(a) & ord(b)) for a,b in zip(ip,mask)]) + +def maskFromLong(lng): + return struct.pack("!L", lng) + +def maskByBits(n): + return maskFromLong(0xffffffffl ^ ((1L<<(32-n))-1)) + +class Pattern: + """ + >>> import socket + >>> ip1 = socket.inet_aton("192.169.64.11") + >>> ip2 = socket.inet_aton("192.168.64.11") + >>> ip3 = socket.inet_aton("18.244.0.188") + + >>> print Pattern.parse("18.244.0.188") + 18.244.0.188/255.255.255.255:1-65535 + >>> print Pattern.parse("18.244.0.188/16:*") + 18.244.0.0/255.255.0.0:1-65535 + >>> print Pattern.parse("18.244.0.188/2.2.2.2:80") + 2.0.0.0/2.2.2.2:80-80 + >>> print Pattern.parse("192.168.0.1/255.255.00.0:22-25") + 192.168.0.0/255.255.0.0:22-25 + >>> p1 = Pattern.parse("192.168.0.1/255.255.00.0:22-25") + >>> import socket + >>> p1.appliesTo(ip1, 22) + False + >>> p1.appliesTo(ip2, 22) + True + >>> p1.appliesTo(ip2, 25) + True + >>> p1.appliesTo(ip2, 26) + False + """ + def __init__(self, ip, mask, portMin, portMax): + self.ip = maskIP(ip,mask) + self.mask = mask + self.portMin = portMin + self.portMax = portMax + + def __str__(self): + return "%s/%s:%s-%s"%(socket.inet_ntoa(self.ip), + socket.inet_ntoa(self.mask), + self.portMin, + self.portMax) + + def parse(s): + if ":" in s: + addrspec, portspec = s.split(":",1) + else: + addrspec, portspec = s, "*" + + if addrspec == '*': + ip,mask = "\x00\x00\x00\x00","\x00\x00\x00\x00" + elif '/' not in addrspec: + ip = socket.inet_aton(addrspec) + mask = "\xff\xff\xff\xff" + else: + ip,mask = addrspec.split("/",1) + ip = socket.inet_aton(ip) + if "." in mask: + mask = socket.inet_aton(mask) + else: + mask = maskByBits(int(mask)) + + if portspec == '*': + portMin = 1 + portMax = 65535 + elif '-' not in portspec: + portMin = portMax = int(portspec) + else: + portMin, portMax = map(int,portspec.split("-",1)) + + return Pattern(ip,mask,portMin,portMax) + + parse = staticmethod(parse) + + def appliesTo(self, ip, port): + return ((maskIP(ip,self.mask) == self.ip) and + (self.portMin <= port <= self.portMax)) + +class Policy: + """ + >>> import socket + >>> ip1 = socket.inet_aton("192.169.64.11") + >>> ip2 = socket.inet_aton("192.168.64.11") + >>> ip3 = socket.inet_aton("18.244.0.188") + + >>> pol = Policy.parseLines(["reject *:80","accept 18.244.0.188:*"]) + >>> print str(pol).strip() + reject 0.0.0.0/0.0.0.0:80-80 + accept 18.244.0.188/255.255.255.255:1-65535 + >>> pol.accepts(ip1,80) + False + >>> pol.accepts(ip3,80) + False + >>> pol.accepts(ip3,81) + True + """ + + def __init__(self, lst): + self.lst = lst + + def parseLines(lines): + r = [] + for item in lines: + a,p=item.split(" ",1) + if a == 'accept': + a = True + elif a == 'reject': + a = False + else: + raise ValueError("Unrecognized action %r",a) + p = Pattern.parse(p) + r.append((p,a)) + return Policy(r) + + parseLines = staticmethod(parseLines) + + def __str__(self): + r = [] + for pat, accept in self.lst: + rule = accept and "accept" or "reject" + r.append("%s %s\n"%(rule,pat)) + return "".join(r) + + def accepts(self, ip, port): + for pattern,accept in self.lst: + if pattern.appliesTo(ip,port): + return accept + return True + +class Server: + def __init__(self, name, ip, policy): + self.name = name + self.ip = ip + self.policy = policy + +def run(): + servers = [] + policy = [] + name = ip = None + for line in sys.stdin.xreadlines(): + if line.startswith('router '): + if name: + servers.append(Server(name, ip, Policy.parseLines(policy))) + _, name, ip, rest = line.split(" ", 3) + policy = [] + elif line.startswith('accept ') or line.startswith('reject '): + policy.append(line.strip()) + + if name: + servers.append(Server(name, ip, Policy.parseLines(policy))) + + targets = [] + for line in ADDRESSES_OF_INTEREST.split("\n"): + line = line.strip() + if not line: continue + p = Pattern.parse(line) + targets.append((p.ip, p.portMin)) + + accepters, rejecters = [], [] + for s in servers: + for ip,port in targets: + if s.policy.accepts(ip,port): + accepters.append(s) + break + else: + rejecters.append(s) + + if INVERSE: + printlist = rejecters + else: + printlist = accepters + + if VERBOSE: + for s in printlist: + print "%s\t%s"%(s.ip,s.name) + else: + for s in printlist: + print s.ip + +def _test(): + import doctest, exitparse + return doctest.testmod(exitparse) +#_test() + +run() +