2005-02-25 06:45:40 +01:00
|
|
|
#!/usr/bin/python
|
2006-01-07 01:17:12 +01:00
|
|
|
# Copyright 2005-2006 Nick Mathewson
|
2005-02-25 06:45:40 +01:00
|
|
|
# See the LICENSE file in the Tor distribution for licensing information.
|
|
|
|
|
2006-04-08 08:20:46 +02:00
|
|
|
# Requires Python 2.2 or later.
|
|
|
|
|
2005-02-25 06:45:40 +01:00
|
|
|
"""
|
|
|
|
exitlist -- Given a Tor directory on stdin, lists the Tor servers
|
|
|
|
that accept connections to given addreses.
|
|
|
|
|
2006-04-02 09:54:01 +02:00
|
|
|
example usage (Tor 0.1.0.x and earlier):
|
2005-02-25 06:45:40 +01:00
|
|
|
|
2007-01-14 04:16:06 +01:00
|
|
|
python exitlist 18.244.0.188:80 < ~/.tor/cached-directory
|
2006-01-06 16:03:57 +01:00
|
|
|
|
|
|
|
example usage (Tor 0.1.1.10-alpha and later):
|
|
|
|
|
2007-01-14 04:16:06 +01:00
|
|
|
cat ~/.tor/cached-routers* | python exitlist 18.244.0.188:80
|
2006-01-06 16:03:57 +01:00
|
|
|
|
2006-04-09 03:07:03 +02:00
|
|
|
If you're using Tor 0.1.1.18-rc or later, you should look at
|
|
|
|
the "FetchUselessDescriptors" config option in the man page.
|
|
|
|
|
2006-08-02 07:17:22 +02:00
|
|
|
Note that this script won't give you a perfect list of IP addresses
|
|
|
|
that might connect to you using Tor, since some Tor servers might exit
|
|
|
|
from other addresses than the one they publish.
|
|
|
|
|
2005-02-25 06:45:40 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
#
|
|
|
|
# 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,
|
2007-01-14 04:16:06 +01:00
|
|
|
# a colon, and a port number. This default is only used if we don't learn
|
|
|
|
# about any addresses from the command-line.
|
2005-02-25 06:45:40 +01:00
|
|
|
#
|
|
|
|
ADDRESSES_OF_INTEREST = """
|
2007-01-14 04:16:06 +01:00
|
|
|
1.2.3.4:80
|
2005-02-25 06:45:40 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# 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
|
|
|
|
|
2006-09-22 22:20:35 +02:00
|
|
|
def uniq_sort(lst):
|
|
|
|
d = {}
|
|
|
|
for item in lst: d[item] = 1
|
|
|
|
lst = d.keys()
|
|
|
|
lst.sort()
|
|
|
|
return lst
|
|
|
|
|
2005-02-25 06:45:40 +01:00
|
|
|
def run():
|
2007-01-14 04:16:06 +01:00
|
|
|
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: %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)
|
|
|
|
|
2005-02-25 06:45:40 +01:00
|
|
|
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
|
|
|
|
|
2006-09-22 22:20:35 +02:00
|
|
|
ents = []
|
2005-02-25 06:45:40 +01:00
|
|
|
if VERBOSE:
|
2006-09-22 22:20:35 +02:00
|
|
|
ents = uniq_sort([ "%s\t%s"%(s.ip,s.name) for s in printlist ])
|
2005-02-25 06:45:40 +01:00
|
|
|
else:
|
2006-09-22 22:20:35 +02:00
|
|
|
ents = uniq_sort([ s.ip for s in printlist ])
|
|
|
|
for e in ents:
|
|
|
|
print e
|
2005-02-25 06:45:40 +01:00
|
|
|
|
|
|
|
def _test():
|
|
|
|
import doctest, exitparse
|
|
|
|
return doctest.testmod(exitparse)
|
|
|
|
#_test()
|
|
|
|
|
|
|
|
run()
|
|
|
|
|