a todo clump for tor mirror todos
[tor.git] / contrib / exitlist
blobe85c73d489dde4ceeb899bd7b633e6a9bb9e56ae
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.2.0.7-alpha and earlier):
13 cat ~/.tor/cached-routers* | python exitlist 18.244.0.188:80
15 example usage (Tor 0.2.0.8-alpha and later):
17 cat ~/.tor/cached-descriptors* | python exitlist 18.244.0.188:80
19 You should look at the "FetchUselessDescriptors" config option in the
20 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. See
25 https://check.torproject.org/ for an alternative (more
26 accurate!) approach.
28 """
31 # Change this to True if you want more verbose output. By default, we
32 # only print the IPs of the servers that accept any the listed
33 # addresses, one per line.
35 VERBOSE = False
38 # Change this to True if you want to reverse the output, and list the
39 # servers that accept *none* of the listed addresses.
41 INVERSE = False
44 # Change this list to contain all of the target services you are interested
45 # in. It must contain one entry per line, each consisting of an IPv4 address,
46 # a colon, and a port number. This default is only used if we don't learn
47 # about any addresses from the command-line.
49 ADDRESSES_OF_INTEREST = """
50 1.2.3.4:80
51 """
55 # YOU DO NOT NEED TO EDIT AFTER THIS POINT.
58 import sys
59 import re
60 import getopt
61 import socket
62 import struct
63 import time
65 assert sys.version_info >= (2,2)
68 def maskIP(ip,mask):
69 return "".join([chr(ord(a) & ord(b)) for a,b in zip(ip,mask)])
71 def maskFromLong(lng):
72 return struct.pack("!L", lng)
74 def maskByBits(n):
75 return maskFromLong(0xffffffffl ^ ((1L<<(32-n))-1))
77 class Pattern:
78 """
79 >>> import socket
80 >>> ip1 = socket.inet_aton("192.169.64.11")
81 >>> ip2 = socket.inet_aton("192.168.64.11")
82 >>> ip3 = socket.inet_aton("18.244.0.188")
84 >>> print Pattern.parse("18.244.0.188")
85 18.244.0.188/255.255.255.255:1-65535
86 >>> print Pattern.parse("18.244.0.188/16:*")
87 18.244.0.0/255.255.0.0:1-65535
88 >>> print Pattern.parse("18.244.0.188/2.2.2.2:80")
89 2.0.0.0/2.2.2.2:80-80
90 >>> print Pattern.parse("192.168.0.1/255.255.00.0:22-25")
91 192.168.0.0/255.255.0.0:22-25
92 >>> p1 = Pattern.parse("192.168.0.1/255.255.00.0:22-25")
93 >>> import socket
94 >>> p1.appliesTo(ip1, 22)
95 False
96 >>> p1.appliesTo(ip2, 22)
97 True
98 >>> p1.appliesTo(ip2, 25)
99 True
100 >>> p1.appliesTo(ip2, 26)
101 False
103 def __init__(self, ip, mask, portMin, portMax):
104 self.ip = maskIP(ip,mask)
105 self.mask = mask
106 self.portMin = portMin
107 self.portMax = portMax
109 def __str__(self):
110 return "%s/%s:%s-%s"%(socket.inet_ntoa(self.ip),
111 socket.inet_ntoa(self.mask),
112 self.portMin,
113 self.portMax)
115 def parse(s):
116 if ":" in s:
117 addrspec, portspec = s.split(":",1)
118 else:
119 addrspec, portspec = s, "*"
121 if addrspec == '*':
122 ip,mask = "\x00\x00\x00\x00","\x00\x00\x00\x00"
123 elif '/' not in addrspec:
124 ip = socket.inet_aton(addrspec)
125 mask = "\xff\xff\xff\xff"
126 else:
127 ip,mask = addrspec.split("/",1)
128 ip = socket.inet_aton(ip)
129 if "." in mask:
130 mask = socket.inet_aton(mask)
131 else:
132 mask = maskByBits(int(mask))
134 if portspec == '*':
135 portMin = 1
136 portMax = 65535
137 elif '-' not in portspec:
138 portMin = portMax = int(portspec)
139 else:
140 portMin, portMax = map(int,portspec.split("-",1))
142 return Pattern(ip,mask,portMin,portMax)
144 parse = staticmethod(parse)
146 def appliesTo(self, ip, port):
147 return ((maskIP(ip,self.mask) == self.ip) and
148 (self.portMin <= port <= self.portMax))
150 class Policy:
152 >>> import socket
153 >>> ip1 = socket.inet_aton("192.169.64.11")
154 >>> ip2 = socket.inet_aton("192.168.64.11")
155 >>> ip3 = socket.inet_aton("18.244.0.188")
157 >>> pol = Policy.parseLines(["reject *:80","accept 18.244.0.188:*"])
158 >>> print str(pol).strip()
159 reject 0.0.0.0/0.0.0.0:80-80
160 accept 18.244.0.188/255.255.255.255:1-65535
161 >>> pol.accepts(ip1,80)
162 False
163 >>> pol.accepts(ip3,80)
164 False
165 >>> pol.accepts(ip3,81)
166 True
169 def __init__(self, lst):
170 self.lst = lst
172 def parseLines(lines):
173 r = []
174 for item in lines:
175 a,p=item.split(" ",1)
176 if a == 'accept':
177 a = True
178 elif a == 'reject':
179 a = False
180 else:
181 raise ValueError("Unrecognized action %r",a)
182 p = Pattern.parse(p)
183 r.append((p,a))
184 return Policy(r)
186 parseLines = staticmethod(parseLines)
188 def __str__(self):
189 r = []
190 for pat, accept in self.lst:
191 rule = accept and "accept" or "reject"
192 r.append("%s %s\n"%(rule,pat))
193 return "".join(r)
195 def accepts(self, ip, port):
196 for pattern,accept in self.lst:
197 if pattern.appliesTo(ip,port):
198 return accept
199 return True
201 class Server:
202 def __init__(self, name, ip, policy, published, fingerprint):
203 self.name = name
204 self.ip = ip
205 self.policy = policy
206 self.published = published
207 self.fingerprint = fingerprint
209 def uniq_sort(lst):
210 d = {}
211 for item in lst: d[item] = 1
212 lst = d.keys()
213 lst.sort()
214 return lst
216 def run():
217 global VERBOSE
218 global INVERSE
219 global ADDRESSES_OF_INTEREST
221 if len(sys.argv) > 1:
222 try:
223 opts, pargs = getopt.getopt(sys.argv[1:], "vx")
224 except getopt.GetoptError, e:
225 print """
226 usage: cat ~/.tor/cached-routers* | %s [-v] [-x] [host:port [host:port [...]]]
227 -v verbose output
228 -x invert results
229 """ % sys.argv[0]
230 sys.exit(0)
232 for o, a in opts:
233 if o == "-v":
234 VERBOSE = True
235 if o == "-x":
236 INVERSE = True
237 if len(pargs):
238 ADDRESSES_OF_INTEREST = "\n".join(pargs)
240 servers = []
241 policy = []
242 name = ip = None
243 published = 0
244 fp = ""
245 for line in sys.stdin.xreadlines():
246 if line.startswith('router '):
247 if name:
248 servers.append(Server(name, ip, Policy.parseLines(policy),
249 published, fp))
250 _, name, ip, rest = line.split(" ", 3)
251 policy = []
252 published = 0
253 fp = ""
254 elif line.startswith('fingerprint') or \
255 line.startswith('opt fingerprint'):
256 elts = line.strip().split()
257 if elts[0] == 'opt': del elts[0]
258 assert elts[0] == 'fingerprint'
259 del elts[0]
260 fp = "".join(elts)
261 elif line.startswith('accept ') or line.startswith('reject '):
262 policy.append(line.strip())
263 elif line.startswith('published '):
264 date = time.strptime(line[len('published '):].strip(),
265 "%Y-%m-%d %H:%M:%S")
266 published = time.mktime(date)
268 if name:
269 servers.append(Server(name, ip, Policy.parseLines(policy), published,
270 fp))
272 targets = []
273 for line in ADDRESSES_OF_INTEREST.split("\n"):
274 line = line.strip()
275 if not line: continue
276 p = Pattern.parse(line)
277 targets.append((p.ip, p.portMin))
279 # remove all but the latest server of each IP/Nickname pair.
280 latest = {}
281 for s in servers:
282 if (not latest.has_key((s.fingerprint))
283 or s.published > latest[(s.fingerprint)]):
284 latest[s.fingerprint] = s
285 servers = latest.values()
287 accepters, rejecters = {}, {}
288 for s in servers:
289 for ip,port in targets:
290 if s.policy.accepts(ip,port):
291 accepters[s.ip] = s
292 break
293 else:
294 rejecters[s.ip] = s
296 # If any server at IP foo accepts, the IP does not reject.
297 for k in accepters.keys():
298 if rejecters.has_key(k):
299 del rejecters[k]
301 if INVERSE:
302 printlist = rejecters.values()
303 else:
304 printlist = accepters.values()
306 ents = []
307 if VERBOSE:
308 ents = uniq_sort([ "%s\t%s"%(s.ip,s.name) for s in printlist ])
309 else:
310 ents = uniq_sort([ s.ip for s in printlist ])
311 for e in ents:
312 print e
314 def _test():
315 import doctest, exitparse
316 return doctest.testmod(exitparse)
317 #_test()
319 run()