the other half of my patch. more coming later.
[tor.git] / contrib / exitlist
blob86295c7d87aa3fdb12180edf6d5ac3885362885b
1 #!/usr/bin/python
2 # Copyright 2005-2006 Nick Mathewson
3 # See the LICENSE file in the Tor distribution for licensing information.
5 # Requires Python 2.2 or later.
7 """
8 exitlist -- Given a Tor directory on stdin, lists the Tor servers
9 that accept connections to given addreses.
11 example usage (Tor 0.1.0.x and earlier):
13 python exitlist 18.244.0.188:80 < ~/.tor/cached-directory
15 example usage (Tor 0.1.1.10-alpha and later):
17 cat ~/.tor/cached-routers* | python exitlist 18.244.0.188:80
19 If you're using Tor 0.1.1.18-rc or later, you should look at
20 the "FetchUselessDescriptors" config option in the man page.
22 Note that this script won't give you a perfect list of IP addresses
23 that might connect to you using Tor, since some Tor servers might exit
24 from other addresses than the one they publish.
26 """
29 # Change this to True if you want more verbose output. By default, we
30 # only print the IPs of the servers that accept any the listed
31 # addresses, one per line.
33 VERBOSE = False
36 # Change this to True if you want to reverse the output, and list the
37 # servers that accept *none* of the listed addresses.
39 INVERSE = False
42 # Change this list to contain all of the target services you are interested
43 # in. It must contain one entry per line, each consisting of an IPv4 address,
44 # a colon, and a port number. This default is only used if we don't learn
45 # about any addresses from the command-line.
47 ADDRESSES_OF_INTEREST = """
48 1.2.3.4:80
49 """
53 # YOU DO NOT NEED TO EDIT AFTER THIS POINT.
56 import sys
57 import re
58 import getopt
59 import socket
60 import struct
62 assert sys.version_info >= (2,2)
65 def maskIP(ip,mask):
66 return "".join([chr(ord(a) & ord(b)) for a,b in zip(ip,mask)])
68 def maskFromLong(lng):
69 return struct.pack("!L", lng)
71 def maskByBits(n):
72 return maskFromLong(0xffffffffl ^ ((1L<<(32-n))-1))
74 class Pattern:
75 """
76 >>> import socket
77 >>> ip1 = socket.inet_aton("192.169.64.11")
78 >>> ip2 = socket.inet_aton("192.168.64.11")
79 >>> ip3 = socket.inet_aton("18.244.0.188")
81 >>> print Pattern.parse("18.244.0.188")
82 18.244.0.188/255.255.255.255:1-65535
83 >>> print Pattern.parse("18.244.0.188/16:*")
84 18.244.0.0/255.255.0.0:1-65535
85 >>> print Pattern.parse("18.244.0.188/2.2.2.2:80")
86 2.0.0.0/2.2.2.2:80-80
87 >>> print Pattern.parse("192.168.0.1/255.255.00.0:22-25")
88 192.168.0.0/255.255.0.0:22-25
89 >>> p1 = Pattern.parse("192.168.0.1/255.255.00.0:22-25")
90 >>> import socket
91 >>> p1.appliesTo(ip1, 22)
92 False
93 >>> p1.appliesTo(ip2, 22)
94 True
95 >>> p1.appliesTo(ip2, 25)
96 True
97 >>> p1.appliesTo(ip2, 26)
98 False
99 """
100 def __init__(self, ip, mask, portMin, portMax):
101 self.ip = maskIP(ip,mask)
102 self.mask = mask
103 self.portMin = portMin
104 self.portMax = portMax
106 def __str__(self):
107 return "%s/%s:%s-%s"%(socket.inet_ntoa(self.ip),
108 socket.inet_ntoa(self.mask),
109 self.portMin,
110 self.portMax)
112 def parse(s):
113 if ":" in s:
114 addrspec, portspec = s.split(":",1)
115 else:
116 addrspec, portspec = s, "*"
118 if addrspec == '*':
119 ip,mask = "\x00\x00\x00\x00","\x00\x00\x00\x00"
120 elif '/' not in addrspec:
121 ip = socket.inet_aton(addrspec)
122 mask = "\xff\xff\xff\xff"
123 else:
124 ip,mask = addrspec.split("/",1)
125 ip = socket.inet_aton(ip)
126 if "." in mask:
127 mask = socket.inet_aton(mask)
128 else:
129 mask = maskByBits(int(mask))
131 if portspec == '*':
132 portMin = 1
133 portMax = 65535
134 elif '-' not in portspec:
135 portMin = portMax = int(portspec)
136 else:
137 portMin, portMax = map(int,portspec.split("-",1))
139 return Pattern(ip,mask,portMin,portMax)
141 parse = staticmethod(parse)
143 def appliesTo(self, ip, port):
144 return ((maskIP(ip,self.mask) == self.ip) and
145 (self.portMin <= port <= self.portMax))
147 class Policy:
149 >>> import socket
150 >>> ip1 = socket.inet_aton("192.169.64.11")
151 >>> ip2 = socket.inet_aton("192.168.64.11")
152 >>> ip3 = socket.inet_aton("18.244.0.188")
154 >>> pol = Policy.parseLines(["reject *:80","accept 18.244.0.188:*"])
155 >>> print str(pol).strip()
156 reject 0.0.0.0/0.0.0.0:80-80
157 accept 18.244.0.188/255.255.255.255:1-65535
158 >>> pol.accepts(ip1,80)
159 False
160 >>> pol.accepts(ip3,80)
161 False
162 >>> pol.accepts(ip3,81)
163 True
166 def __init__(self, lst):
167 self.lst = lst
169 def parseLines(lines):
170 r = []
171 for item in lines:
172 a,p=item.split(" ",1)
173 if a == 'accept':
174 a = True
175 elif a == 'reject':
176 a = False
177 else:
178 raise ValueError("Unrecognized action %r",a)
179 p = Pattern.parse(p)
180 r.append((p,a))
181 return Policy(r)
183 parseLines = staticmethod(parseLines)
185 def __str__(self):
186 r = []
187 for pat, accept in self.lst:
188 rule = accept and "accept" or "reject"
189 r.append("%s %s\n"%(rule,pat))
190 return "".join(r)
192 def accepts(self, ip, port):
193 for pattern,accept in self.lst:
194 if pattern.appliesTo(ip,port):
195 return accept
196 return True
198 class Server:
199 def __init__(self, name, ip, policy):
200 self.name = name
201 self.ip = ip
202 self.policy = policy
204 def uniq_sort(lst):
205 d = {}
206 for item in lst: d[item] = 1
207 lst = d.keys()
208 lst.sort()
209 return lst
211 def run():
212 global VERBOSE
213 global INVERSE
214 global ADDRESSES_OF_INTEREST
216 if len(sys.argv) > 1:
217 try:
218 opts, pargs = getopt.getopt(sys.argv[1:], "vx")
219 except getopt.GetoptError, e:
220 print """
221 usage: %s [-v] [-x] [host:port [host:port [...]]]
222 -v verbose output
223 -x invert results
224 """ % sys.argv[0]
225 sys.exit(0)
227 for o, a in opts:
228 if o == "-v":
229 VERBOSE = True
230 if o == "-x":
231 INVERSE = True
232 if len(pargs):
233 ADDRESSES_OF_INTEREST = "\n".join(pargs)
235 servers = []
236 policy = []
237 name = ip = None
238 for line in sys.stdin.xreadlines():
239 if line.startswith('router '):
240 if name:
241 servers.append(Server(name, ip, Policy.parseLines(policy)))
242 _, name, ip, rest = line.split(" ", 3)
243 policy = []
244 elif line.startswith('accept ') or line.startswith('reject '):
245 policy.append(line.strip())
247 if name:
248 servers.append(Server(name, ip, Policy.parseLines(policy)))
250 targets = []
251 for line in ADDRESSES_OF_INTEREST.split("\n"):
252 line = line.strip()
253 if not line: continue
254 p = Pattern.parse(line)
255 targets.append((p.ip, p.portMin))
257 accepters, rejecters = [], []
258 for s in servers:
259 for ip,port in targets:
260 if s.policy.accepts(ip,port):
261 accepters.append(s)
262 break
263 else:
264 rejecters.append(s)
266 if INVERSE:
267 printlist = rejecters
268 else:
269 printlist = accepters
271 ents = []
272 if VERBOSE:
273 ents = uniq_sort([ "%s\t%s"%(s.ip,s.name) for s in printlist ])
274 else:
275 ents = uniq_sort([ s.ip for s in printlist ])
276 for e in ents:
277 print e
279 def _test():
280 import doctest, exitparse
281 return doctest.testmod(exitparse)
282 #_test()
284 run()