tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

exitlist (9015B)


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