exitlist 8.6 KB

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