Add a missing include in ssl version test.
[tor.git] / contrib / exitlist
blob2c74b59d615588fd36e0db1bf5b544e7d859c4cd
1 #!/usr/bin/python
2 # Copyright 2005-2006 Nick Mathewson
3 # See the LICENSE file in the Tor distribution for licensing information.
5 """
6 exitlist -- Given a Tor directory on stdin, lists the Tor servers
7 that accept connections to given addreses.
9 example usage (Tor 0.1.0.16 and earlier):
11 python2.2 exitlist < ~/.tor/cached-directory
13 example usage (Tor 0.1.1.10-alpha and later):
15 python2.2 exitlist < ~/.tor/cached-routers*
17 """
19 # Requires Python 2.2 or later.
22 # Change this to True if you want more verbose output. By default, we
23 # only print the IPs of the servers that accept any the listed
24 # addresses, one per line.
26 VERBOSE = False
29 # Change this to True if you want to reverse the output, and list the
30 # servers that accept *none* of the listed addresses.
32 INVERSE = False
35 # Change this list to contain all of the target services you are interested
36 # in. It must contain one entry per line, each consisting of an IPv4 address,
37 # a colon, and a port number.
39 ADDRESSES_OF_INTEREST = """
40 192.168.0.1:80
41 """
45 # YOU DO NOT NEED TO EDIT AFTER THIS POINT.
48 import sys
49 import re
50 import getopt
51 import socket
52 import struct
54 assert sys.version_info >= (2,2)
57 def maskIP(ip,mask):
58 return "".join([chr(ord(a) & ord(b)) for a,b in zip(ip,mask)])
60 def maskFromLong(lng):
61 return struct.pack("!L", lng)
63 def maskByBits(n):
64 return maskFromLong(0xffffffffl ^ ((1L<<(32-n))-1))
66 class Pattern:
67 """
68 >>> import socket
69 >>> ip1 = socket.inet_aton("192.169.64.11")
70 >>> ip2 = socket.inet_aton("192.168.64.11")
71 >>> ip3 = socket.inet_aton("18.244.0.188")
73 >>> print Pattern.parse("18.244.0.188")
74 18.244.0.188/255.255.255.255:1-65535
75 >>> print Pattern.parse("18.244.0.188/16:*")
76 18.244.0.0/255.255.0.0:1-65535
77 >>> print Pattern.parse("18.244.0.188/2.2.2.2:80")
78 2.0.0.0/2.2.2.2:80-80
79 >>> print Pattern.parse("192.168.0.1/255.255.00.0:22-25")
80 192.168.0.0/255.255.0.0:22-25
81 >>> p1 = Pattern.parse("192.168.0.1/255.255.00.0:22-25")
82 >>> import socket
83 >>> p1.appliesTo(ip1, 22)
84 False
85 >>> p1.appliesTo(ip2, 22)
86 True
87 >>> p1.appliesTo(ip2, 25)
88 True
89 >>> p1.appliesTo(ip2, 26)
90 False
91 """
92 def __init__(self, ip, mask, portMin, portMax):
93 self.ip = maskIP(ip,mask)
94 self.mask = mask
95 self.portMin = portMin
96 self.portMax = portMax
98 def __str__(self):
99 return "%s/%s:%s-%s"%(socket.inet_ntoa(self.ip),
100 socket.inet_ntoa(self.mask),
101 self.portMin,
102 self.portMax)
104 def parse(s):
105 if ":" in s:
106 addrspec, portspec = s.split(":",1)
107 else:
108 addrspec, portspec = s, "*"
110 if addrspec == '*':
111 ip,mask = "\x00\x00\x00\x00","\x00\x00\x00\x00"
112 elif '/' not in addrspec:
113 ip = socket.inet_aton(addrspec)
114 mask = "\xff\xff\xff\xff"
115 else:
116 ip,mask = addrspec.split("/",1)
117 ip = socket.inet_aton(ip)
118 if "." in mask:
119 mask = socket.inet_aton(mask)
120 else:
121 mask = maskByBits(int(mask))
123 if portspec == '*':
124 portMin = 1
125 portMax = 65535
126 elif '-' not in portspec:
127 portMin = portMax = int(portspec)
128 else:
129 portMin, portMax = map(int,portspec.split("-",1))
131 return Pattern(ip,mask,portMin,portMax)
133 parse = staticmethod(parse)
135 def appliesTo(self, ip, port):
136 return ((maskIP(ip,self.mask) == self.ip) and
137 (self.portMin <= port <= self.portMax))
139 class Policy:
141 >>> import socket
142 >>> ip1 = socket.inet_aton("192.169.64.11")
143 >>> ip2 = socket.inet_aton("192.168.64.11")
144 >>> ip3 = socket.inet_aton("18.244.0.188")
146 >>> pol = Policy.parseLines(["reject *:80","accept 18.244.0.188:*"])
147 >>> print str(pol).strip()
148 reject 0.0.0.0/0.0.0.0:80-80
149 accept 18.244.0.188/255.255.255.255:1-65535
150 >>> pol.accepts(ip1,80)
151 False
152 >>> pol.accepts(ip3,80)
153 False
154 >>> pol.accepts(ip3,81)
155 True
158 def __init__(self, lst):
159 self.lst = lst
161 def parseLines(lines):
162 r = []
163 for item in lines:
164 a,p=item.split(" ",1)
165 if a == 'accept':
166 a = True
167 elif a == 'reject':
168 a = False
169 else:
170 raise ValueError("Unrecognized action %r",a)
171 p = Pattern.parse(p)
172 r.append((p,a))
173 return Policy(r)
175 parseLines = staticmethod(parseLines)
177 def __str__(self):
178 r = []
179 for pat, accept in self.lst:
180 rule = accept and "accept" or "reject"
181 r.append("%s %s\n"%(rule,pat))
182 return "".join(r)
184 def accepts(self, ip, port):
185 for pattern,accept in self.lst:
186 if pattern.appliesTo(ip,port):
187 return accept
188 return True
190 class Server:
191 def __init__(self, name, ip, policy):
192 self.name = name
193 self.ip = ip
194 self.policy = policy
196 def run():
197 servers = []
198 policy = []
199 name = ip = None
200 for line in sys.stdin.xreadlines():
201 if line.startswith('router '):
202 if name:
203 servers.append(Server(name, ip, Policy.parseLines(policy)))
204 _, name, ip, rest = line.split(" ", 3)
205 policy = []
206 elif line.startswith('accept ') or line.startswith('reject '):
207 policy.append(line.strip())
209 if name:
210 servers.append(Server(name, ip, Policy.parseLines(policy)))
212 targets = []
213 for line in ADDRESSES_OF_INTEREST.split("\n"):
214 line = line.strip()
215 if not line: continue
216 p = Pattern.parse(line)
217 targets.append((p.ip, p.portMin))
219 accepters, rejecters = [], []
220 for s in servers:
221 for ip,port in targets:
222 if s.policy.accepts(ip,port):
223 accepters.append(s)
224 break
225 else:
226 rejecters.append(s)
228 if INVERSE:
229 printlist = rejecters
230 else:
231 printlist = accepters
233 if VERBOSE:
234 for s in printlist:
235 print "%s\t%s"%(s.ip,s.name)
236 else:
237 for s in printlist:
238 print s.ip
240 def _test():
241 import doctest, exitparse
242 return doctest.testmod(exitparse)
243 #_test()
245 run()