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