aboutsummaryrefslogtreecommitdiff
path: root/contrib/or-tools/exitlist
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/or-tools/exitlist')
-rwxr-xr-xcontrib/or-tools/exitlist323
1 files changed, 323 insertions, 0 deletions
diff --git a/contrib/or-tools/exitlist b/contrib/or-tools/exitlist
new file mode 100755
index 000000000..3fd26b516
--- /dev/null
+++ b/contrib/or-tools/exitlist
@@ -0,0 +1,323 @@
+#!/usr/bin/python
+# Copyright 2005-2006 Nick Mathewson
+# See the LICENSE file in the Tor distribution for licensing information.
+
+# Requires Python 2.2 or later.
+
+"""
+ exitlist -- Given a Tor directory on stdin, lists the Tor servers
+ that accept connections to given addreses.
+
+ example usage:
+
+ cat ~/.tor/cached-descriptors* | python exitlist 18.244.0.188:80
+
+ You should look at the "FetchUselessDescriptors" and "FetchDirInfoEarly"
+ config options in the man page.
+
+ Note that this script won't give you a perfect list of IP addresses
+ that might connect to you using Tor.
+ False negatives:
+ - Some Tor servers might exit from other addresses than the one they
+ publish in their descriptor.
+ False positives:
+ - This script just looks at the descriptor lists, so it counts relays
+ that were running a day in the past and aren't running now (or are
+ now running at a different address).
+
+ See https://check.torproject.org/ for an alternative (more accurate!)
+ approach.
+
+"""
+
+#
+# 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. This default is only used if we don't learn
+# about any addresses from the command-line.
+#
+ADDRESSES_OF_INTEREST = """
+ 1.2.3.4:80
+"""
+
+
+#
+# YOU DO NOT NEED TO EDIT AFTER THIS POINT.
+#
+
+import sys
+import re
+import getopt
+import socket
+import struct
+import time
+
+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, published, fingerprint):
+ self.name = name
+ self.ip = ip
+ self.policy = policy
+ self.published = published
+ self.fingerprint = fingerprint
+
+def uniq_sort(lst):
+ d = {}
+ for item in lst: d[item] = 1
+ lst = d.keys()
+ lst.sort()
+ return lst
+
+def run():
+ global VERBOSE
+ global INVERSE
+ global ADDRESSES_OF_INTEREST
+
+ if len(sys.argv) > 1:
+ try:
+ opts, pargs = getopt.getopt(sys.argv[1:], "vx")
+ except getopt.GetoptError, e:
+ print """
+usage: cat ~/.tor/cached-routers* | %s [-v] [-x] [host:port [host:port [...]]]
+ -v verbose output
+ -x invert results
+""" % sys.argv[0]
+ sys.exit(0)
+
+ for o, a in opts:
+ if o == "-v":
+ VERBOSE = True
+ if o == "-x":
+ INVERSE = True
+ if len(pargs):
+ ADDRESSES_OF_INTEREST = "\n".join(pargs)
+
+ servers = []
+ policy = []
+ name = ip = None
+ published = 0
+ fp = ""
+ for line in sys.stdin.xreadlines():
+ if line.startswith('router '):
+ if name:
+ servers.append(Server(name, ip, Policy.parseLines(policy),
+ published, fp))
+ _, name, ip, rest = line.split(" ", 3)
+ policy = []
+ published = 0
+ fp = ""
+ elif line.startswith('fingerprint') or \
+ line.startswith('opt fingerprint'):
+ elts = line.strip().split()
+ if elts[0] == 'opt': del elts[0]
+ assert elts[0] == 'fingerprint'
+ del elts[0]
+ fp = "".join(elts)
+ elif line.startswith('accept ') or line.startswith('reject '):
+ policy.append(line.strip())
+ elif line.startswith('published '):
+ date = time.strptime(line[len('published '):].strip(),
+ "%Y-%m-%d %H:%M:%S")
+ published = time.mktime(date)
+
+ if name:
+ servers.append(Server(name, ip, Policy.parseLines(policy), published,
+ fp))
+
+ 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))
+
+ # remove all but the latest server of each IP/Nickname pair.
+ latest = {}
+ for s in servers:
+ if (not latest.has_key((s.fingerprint))
+ or s.published > latest[(s.fingerprint)]):
+ latest[s.fingerprint] = s
+ servers = latest.values()
+
+ accepters, rejecters = {}, {}
+ for s in servers:
+ for ip,port in targets:
+ if s.policy.accepts(ip,port):
+ accepters[s.ip] = s
+ break
+ else:
+ rejecters[s.ip] = s
+
+ # If any server at IP foo accepts, the IP does not reject.
+ for k in accepters.keys():
+ if rejecters.has_key(k):
+ del rejecters[k]
+
+ if INVERSE:
+ printlist = rejecters.values()
+ else:
+ printlist = accepters.values()
+
+ ents = []
+ if VERBOSE:
+ ents = uniq_sort([ "%s\t%s"%(s.ip,s.name) for s in printlist ])
+ else:
+ ents = uniq_sort([ s.ip for s in printlist ])
+ for e in ents:
+ print e
+
+def _test():
+ import doctest, exitparse
+ return doctest.testmod(exitparse)
+#_test()
+
+run()
+