exitlist (9015B)
1 #!/usr/bin/python 2 # Copyright 2005-2006 Nick Mathewson 3 # See the LICENSE file in the Tor distribution for licensing information. 4 5 # Requires Python 2.2 or later. 6 7 """ 8 exitlist -- Given a Tor directory on stdin, lists the Tor servers 9 that accept connections to given addresses. 10 11 example usage: 12 13 cat ~/.tor/cached-descriptors* | python exitlist 18.244.0.188:80 14 15 You should look at the "FetchUselessDescriptors" and "FetchDirInfoEarly" 16 config options in the man page. 17 18 Note that this script won't give you a perfect list of IP addresses 19 that might connect to you using Tor. 20 False negatives: 21 - Some Tor servers might exit from other addresses than the one they 22 publish in their descriptor. 23 False positives: 24 - This script just looks at the descriptor lists, so it counts relays 25 that were running a day in the past and aren't running now (or are 26 now running at a different address). 27 28 See https://check.torproject.org/ for an alternative (more accurate!) 29 approach. 30 31 """ 32 33 # 34 # Change this to True if you want more verbose output. By default, we 35 # only print the IPs of the servers that accept any the listed 36 # addresses, one per line. 37 # 38 VERBOSE = False 39 40 # 41 # Change this to True if you want to reverse the output, and list the 42 # servers that accept *none* of the listed addresses. 43 # 44 INVERSE = False 45 46 # 47 # Change this list to contain all of the target services you are interested 48 # in. It must contain one entry per line, each consisting of an IPv4 address, 49 # a colon, and a port number. This default is only used if we don't learn 50 # about any addresses from the command-line. 51 # 52 ADDRESSES_OF_INTEREST = """ 53 1.2.3.4:80 54 """ 55 56 57 # 58 # YOU DO NOT NEED TO EDIT AFTER THIS POINT. 59 # 60 61 import sys 62 import re 63 import getopt 64 import socket 65 import struct 66 import time 67 68 assert sys.version_info >= (2,2) 69 70 71 def maskIP(ip,mask): 72 return "".join([chr(ord(a) & ord(b)) for a,b in zip(ip,mask)]) 73 74 def maskFromLong(lng): 75 return struct.pack("!L", lng) 76 77 def maskByBits(n): 78 return maskFromLong(0xffffffffl ^ ((1L<<(32-n))-1)) 79 80 class Pattern: 81 """ 82 >>> import socket 83 >>> ip1 = socket.inet_aton("192.169.64.11") 84 >>> ip2 = socket.inet_aton("192.168.64.11") 85 >>> ip3 = socket.inet_aton("18.244.0.188") 86 87 >>> print Pattern.parse("18.244.0.188") 88 18.244.0.188/255.255.255.255:1-65535 89 >>> print Pattern.parse("18.244.0.188/16:*") 90 18.244.0.0/255.255.0.0:1-65535 91 >>> print Pattern.parse("18.244.0.188/2.2.2.2:80") 92 2.0.0.0/2.2.2.2:80-80 93 >>> print Pattern.parse("192.168.0.1/255.255.00.0:22-25") 94 192.168.0.0/255.255.0.0:22-25 95 >>> p1 = Pattern.parse("192.168.0.1/255.255.00.0:22-25") 96 >>> import socket 97 >>> p1.appliesTo(ip1, 22) 98 False 99 >>> p1.appliesTo(ip2, 22) 100 True 101 >>> p1.appliesTo(ip2, 25) 102 True 103 >>> p1.appliesTo(ip2, 26) 104 False 105 """ 106 def __init__(self, ip, mask, portMin, portMax): 107 self.ip = maskIP(ip,mask) 108 self.mask = mask 109 self.portMin = portMin 110 self.portMax = portMax 111 112 def __str__(self): 113 return "%s/%s:%s-%s"%(socket.inet_ntoa(self.ip), 114 socket.inet_ntoa(self.mask), 115 self.portMin, 116 self.portMax) 117 118 def parse(s): 119 if ":" in s: 120 addrspec, portspec = s.split(":",1) 121 else: 122 addrspec, portspec = s, "*" 123 124 if addrspec == '*': 125 ip,mask = "\x00\x00\x00\x00","\x00\x00\x00\x00" 126 elif '/' not in addrspec: 127 ip = socket.inet_aton(addrspec) 128 mask = "\xff\xff\xff\xff" 129 else: 130 ip,mask = addrspec.split("/",1) 131 ip = socket.inet_aton(ip) 132 if "." in mask: 133 mask = socket.inet_aton(mask) 134 else: 135 mask = maskByBits(int(mask)) 136 137 if portspec == '*': 138 portMin = 1 139 portMax = 65535 140 elif '-' not in portspec: 141 portMin = portMax = int(portspec) 142 else: 143 portMin, portMax = map(int,portspec.split("-",1)) 144 145 return Pattern(ip,mask,portMin,portMax) 146 147 parse = staticmethod(parse) 148 149 def appliesTo(self, ip, port): 150 return ((maskIP(ip,self.mask) == self.ip) and 151 (self.portMin <= port <= self.portMax)) 152 153 class Policy: 154 """ 155 >>> import socket 156 >>> ip1 = socket.inet_aton("192.169.64.11") 157 >>> ip2 = socket.inet_aton("192.168.64.11") 158 >>> ip3 = socket.inet_aton("18.244.0.188") 159 160 >>> pol = Policy.parseLines(["reject *:80","accept 18.244.0.188:*"]) 161 >>> print str(pol).strip() 162 reject 0.0.0.0/0.0.0.0:80-80 163 accept 18.244.0.188/255.255.255.255:1-65535 164 >>> pol.accepts(ip1,80) 165 False 166 >>> pol.accepts(ip3,80) 167 False 168 >>> pol.accepts(ip3,81) 169 True 170 """ 171 172 def __init__(self, lst): 173 self.lst = lst 174 175 def parseLines(lines): 176 r = [] 177 for item in lines: 178 a,p=item.split(" ",1) 179 if a == 'accept': 180 a = True 181 elif a == 'reject': 182 a = False 183 else: 184 raise ValueError("Unrecognized action %r",a) 185 p = Pattern.parse(p) 186 r.append((p,a)) 187 return Policy(r) 188 189 parseLines = staticmethod(parseLines) 190 191 def __str__(self): 192 r = [] 193 for pat, accept in self.lst: 194 rule = accept and "accept" or "reject" 195 r.append("%s %s\n"%(rule,pat)) 196 return "".join(r) 197 198 def accepts(self, ip, port): 199 for pattern,accept in self.lst: 200 if pattern.appliesTo(ip,port): 201 return accept 202 return True 203 204 class Server: 205 def __init__(self, name, ip, policy, published, fingerprint): 206 self.name = name 207 self.ip = ip 208 self.policy = policy 209 self.published = published 210 self.fingerprint = fingerprint 211 212 def uniq_sort(lst): 213 d = {} 214 for item in lst: d[item] = 1 215 lst = d.keys() 216 lst.sort() 217 return lst 218 219 def run(): 220 global VERBOSE 221 global INVERSE 222 global ADDRESSES_OF_INTEREST 223 224 if len(sys.argv) > 1: 225 try: 226 opts, pargs = getopt.getopt(sys.argv[1:], "vx") 227 except getopt.GetoptError, e: 228 print """ 229 usage: cat ~/.tor/cached-routers* | %s [-v] [-x] [host:port [host:port [...]]] 230 -v verbose output 231 -x invert results 232 """ % sys.argv[0] 233 sys.exit(0) 234 235 for o, a in opts: 236 if o == "-v": 237 VERBOSE = True 238 if o == "-x": 239 INVERSE = True 240 if len(pargs): 241 ADDRESSES_OF_INTEREST = "\n".join(pargs) 242 243 servers = [] 244 policy = [] 245 name = ip = None 246 published = 0 247 fp = "" 248 for line in sys.stdin.xreadlines(): 249 if line.startswith('router '): 250 if name: 251 servers.append(Server(name, ip, Policy.parseLines(policy), 252 published, fp)) 253 _, name, ip, rest = line.split(" ", 3) 254 policy = [] 255 published = 0 256 fp = "" 257 elif line.startswith('fingerprint') or \ 258 line.startswith('opt fingerprint'): 259 elts = line.strip().split() 260 if elts[0] == 'opt': del elts[0] 261 assert elts[0] == 'fingerprint' 262 del elts[0] 263 fp = "".join(elts) 264 elif line.startswith('accept ') or line.startswith('reject '): 265 policy.append(line.strip()) 266 elif line.startswith('published '): 267 date = time.strptime(line[len('published '):].strip(), 268 "%Y-%m-%d %H:%M:%S") 269 published = time.mktime(date) 270 271 if name: 272 servers.append(Server(name, ip, Policy.parseLines(policy), published, 273 fp)) 274 275 targets = [] 276 for line in ADDRESSES_OF_INTEREST.split("\n"): 277 line = line.strip() 278 if not line: continue 279 p = Pattern.parse(line) 280 targets.append((p.ip, p.portMin)) 281 282 # remove all but the latest server of each IP/Nickname pair. 283 latest = {} 284 for s in servers: 285 if (not latest.has_key((s.fingerprint)) 286 or s.published > latest[(s.fingerprint)]): 287 latest[s.fingerprint] = s 288 servers = latest.values() 289 290 accepters, rejecters = {}, {} 291 for s in servers: 292 for ip,port in targets: 293 if s.policy.accepts(ip,port): 294 accepters[s.ip] = s 295 break 296 else: 297 rejecters[s.ip] = s 298 299 # If any server at IP foo accepts, the IP does not reject. 300 for k in accepters.keys(): 301 if rejecters.has_key(k): 302 del rejecters[k] 303 304 if INVERSE: 305 printlist = rejecters.values() 306 else: 307 printlist = accepters.values() 308 309 ents = [] 310 if VERBOSE: 311 ents = uniq_sort([ "%s\t%s"%(s.ip,s.name) for s in printlist ]) 312 else: 313 ents = uniq_sort([ s.ip for s in printlist ]) 314 for e in ents: 315 print e 316 317 def _test(): 318 import doctest, exitparse 319 return doctest.testmod(exitparse) 320 #_test() 321 322 run() 323