exitlist 8.8 KB

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