|  | @@ -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()
 | 
	
		
			
				|  |  | +
 |