exitlist 8.7 KB

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