123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- #!/usr/bin/python
- # Copyright 2005-2006 Nick Mathewson
- # See the LICENSE file in the Tor distribution for licensing information.
- # Requires Python 2.2 or later.
- """
- exitlist -- Given a Tor directory on stdin, lists the Tor servers
- that accept connections to given addresses.
- example usage:
- cat ~/.tor/cached-descriptors* | python exitlist 18.244.0.188:80
- You should look at the "FetchUselessDescriptors" and "FetchDirInfoEarly"
- config options in the man page.
- Note that this script won't give you a perfect list of IP addresses
- that might connect to you using Tor.
- False negatives:
- - Some Tor servers might exit from other addresses than the one they
- publish in their descriptor.
- False positives:
- - This script just looks at the descriptor lists, so it counts relays
- that were running a day in the past and aren't running now (or are
- now running at a different address).
- See https://check.torproject.org/ for an alternative (more accurate!)
- approach.
- """
- #
- # Change this to True if you want more verbose output. By default, we
- # only print the IPs of the servers that accept any the listed
- # addresses, one per line.
- #
- VERBOSE = False
- #
- # Change this to True if you want to reverse the output, and list the
- # servers that accept *none* of the listed addresses.
- #
- INVERSE = False
- #
- # Change this list to contain all of the target services you are interested
- # in. It must contain one entry per line, each consisting of an IPv4 address,
- # a colon, and a port number. This default is only used if we don't learn
- # about any addresses from the command-line.
- #
- ADDRESSES_OF_INTEREST = """
- 1.2.3.4:80
- """
- #
- # YOU DO NOT NEED TO EDIT AFTER THIS POINT.
- #
- import sys
- import re
- import getopt
- import socket
- import struct
- import time
- assert sys.version_info >= (2,2)
- def maskIP(ip,mask):
- return "".join([chr(ord(a) & ord(b)) for a,b in zip(ip,mask)])
- def maskFromLong(lng):
- return struct.pack("!L", lng)
- def maskByBits(n):
- return maskFromLong(0xffffffffl ^ ((1L<<(32-n))-1))
- class Pattern:
- """
- >>> import socket
- >>> ip1 = socket.inet_aton("192.169.64.11")
- >>> ip2 = socket.inet_aton("192.168.64.11")
- >>> ip3 = socket.inet_aton("18.244.0.188")
- >>> print Pattern.parse("18.244.0.188")
- 18.244.0.188/255.255.255.255:1-65535
- >>> print Pattern.parse("18.244.0.188/16:*")
- 18.244.0.0/255.255.0.0:1-65535
- >>> print Pattern.parse("18.244.0.188/2.2.2.2:80")
- 2.0.0.0/2.2.2.2:80-80
- >>> print Pattern.parse("192.168.0.1/255.255.00.0:22-25")
- 192.168.0.0/255.255.0.0:22-25
- >>> p1 = Pattern.parse("192.168.0.1/255.255.00.0:22-25")
- >>> import socket
- >>> p1.appliesTo(ip1, 22)
- False
- >>> p1.appliesTo(ip2, 22)
- True
- >>> p1.appliesTo(ip2, 25)
- True
- >>> p1.appliesTo(ip2, 26)
- False
- """
- def __init__(self, ip, mask, portMin, portMax):
- self.ip = maskIP(ip,mask)
- self.mask = mask
- self.portMin = portMin
- self.portMax = portMax
- def __str__(self):
- return "%s/%s:%s-%s"%(socket.inet_ntoa(self.ip),
- socket.inet_ntoa(self.mask),
- self.portMin,
- self.portMax)
- def parse(s):
- if ":" in s:
- addrspec, portspec = s.split(":",1)
- else:
- addrspec, portspec = s, "*"
- if addrspec == '*':
- ip,mask = "\x00\x00\x00\x00","\x00\x00\x00\x00"
- elif '/' not in addrspec:
- ip = socket.inet_aton(addrspec)
- mask = "\xff\xff\xff\xff"
- else:
- ip,mask = addrspec.split("/",1)
- ip = socket.inet_aton(ip)
- if "." in mask:
- mask = socket.inet_aton(mask)
- else:
- mask = maskByBits(int(mask))
- if portspec == '*':
- portMin = 1
- portMax = 65535
- elif '-' not in portspec:
- portMin = portMax = int(portspec)
- else:
- portMin, portMax = map(int,portspec.split("-",1))
- return Pattern(ip,mask,portMin,portMax)
- parse = staticmethod(parse)
- def appliesTo(self, ip, port):
- return ((maskIP(ip,self.mask) == self.ip) and
- (self.portMin <= port <= self.portMax))
- class Policy:
- """
- >>> import socket
- >>> ip1 = socket.inet_aton("192.169.64.11")
- >>> ip2 = socket.inet_aton("192.168.64.11")
- >>> ip3 = socket.inet_aton("18.244.0.188")
- >>> pol = Policy.parseLines(["reject *:80","accept 18.244.0.188:*"])
- >>> print str(pol).strip()
- reject 0.0.0.0/0.0.0.0:80-80
- accept 18.244.0.188/255.255.255.255:1-65535
- >>> pol.accepts(ip1,80)
- False
- >>> pol.accepts(ip3,80)
- False
- >>> pol.accepts(ip3,81)
- True
- """
- def __init__(self, lst):
- self.lst = lst
- def parseLines(lines):
- r = []
- for item in lines:
- a,p=item.split(" ",1)
- if a == 'accept':
- a = True
- elif a == 'reject':
- a = False
- else:
- raise ValueError("Unrecognized action %r",a)
- p = Pattern.parse(p)
- r.append((p,a))
- return Policy(r)
- parseLines = staticmethod(parseLines)
- def __str__(self):
- r = []
- for pat, accept in self.lst:
- rule = accept and "accept" or "reject"
- r.append("%s %s\n"%(rule,pat))
- return "".join(r)
- def accepts(self, ip, port):
- for pattern,accept in self.lst:
- if pattern.appliesTo(ip,port):
- return accept
- return True
- class Server:
- def __init__(self, name, ip, policy, published, fingerprint):
- self.name = name
- self.ip = ip
- self.policy = policy
- self.published = published
- self.fingerprint = fingerprint
- def uniq_sort(lst):
- d = {}
- for item in lst: d[item] = 1
- lst = d.keys()
- lst.sort()
- return lst
- def run():
- global VERBOSE
- global INVERSE
- global ADDRESSES_OF_INTEREST
- if len(sys.argv) > 1:
- try:
- opts, pargs = getopt.getopt(sys.argv[1:], "vx")
- except getopt.GetoptError, e:
- print """
- usage: cat ~/.tor/cached-routers* | %s [-v] [-x] [host:port [host:port [...]]]
- -v verbose output
- -x invert results
- """ % sys.argv[0]
- sys.exit(0)
- for o, a in opts:
- if o == "-v":
- VERBOSE = True
- if o == "-x":
- INVERSE = True
- if len(pargs):
- ADDRESSES_OF_INTEREST = "\n".join(pargs)
- servers = []
- policy = []
- name = ip = None
- published = 0
- fp = ""
- for line in sys.stdin.xreadlines():
- if line.startswith('router '):
- if name:
- servers.append(Server(name, ip, Policy.parseLines(policy),
- published, fp))
- _, name, ip, rest = line.split(" ", 3)
- policy = []
- published = 0
- fp = ""
- elif line.startswith('fingerprint') or \
- line.startswith('opt fingerprint'):
- elts = line.strip().split()
- if elts[0] == 'opt': del elts[0]
- assert elts[0] == 'fingerprint'
- del elts[0]
- fp = "".join(elts)
- elif line.startswith('accept ') or line.startswith('reject '):
- policy.append(line.strip())
- elif line.startswith('published '):
- date = time.strptime(line[len('published '):].strip(),
- "%Y-%m-%d %H:%M:%S")
- published = time.mktime(date)
- if name:
- servers.append(Server(name, ip, Policy.parseLines(policy), published,
- fp))
- targets = []
- for line in ADDRESSES_OF_INTEREST.split("\n"):
- line = line.strip()
- if not line: continue
- p = Pattern.parse(line)
- targets.append((p.ip, p.portMin))
- # remove all but the latest server of each IP/Nickname pair.
- latest = {}
- for s in servers:
- if (not latest.has_key((s.fingerprint))
- or s.published > latest[(s.fingerprint)]):
- latest[s.fingerprint] = s
- servers = latest.values()
- accepters, rejecters = {}, {}
- for s in servers:
- for ip,port in targets:
- if s.policy.accepts(ip,port):
- accepters[s.ip] = s
- break
- else:
- rejecters[s.ip] = s
- # If any server at IP foo accepts, the IP does not reject.
- for k in accepters.keys():
- if rejecters.has_key(k):
- del rejecters[k]
- if INVERSE:
- printlist = rejecters.values()
- else:
- printlist = accepters.values()
- ents = []
- if VERBOSE:
- ents = uniq_sort([ "%s\t%s"%(s.ip,s.name) for s in printlist ])
- else:
- ents = uniq_sort([ s.ip for s in printlist ])
- for e in ents:
- print e
- def _test():
- import doctest, exitparse
- return doctest.testmod(exitparse)
- #_test()
- run()
|