[or-cvs] r14809: This is the first shot at a bulk exit checking script that i (check/trunk/cgi-bin)

ioerror at seul.org ioerror at seul.org
Thu May 29 09:45:34 UTC 2008


Author: ioerror
Date: 2008-05-29 05:45:33 -0400 (Thu, 29 May 2008)
New Revision: 14809

Added:
   check/trunk/cgi-bin/TorBulkExitList.py
Log:
This is the first shot at a bulk exit checking script that includes caching. This allows people to easily detect possible Tor servers that actively allow exiting to their specified server ip address. mod_python as TorCheck.


Added: check/trunk/cgi-bin/TorBulkExitList.py
===================================================================
--- check/trunk/cgi-bin/TorBulkExitList.py	                        (rev 0)
+++ check/trunk/cgi-bin/TorBulkExitList.py	2008-05-29 09:45:33 UTC (rev 14809)
@@ -0,0 +1,270 @@
+import re
+import socket
+import stat
+import os
+import time
+import DNS
+from mod_python import apache
+from mod_python import util
+
+DNS.ParseResolvConf()
+def bulkCheck(RemoteServerIP):
+    parsedExitList = "/tmp/parsed-exit-list"
+    cacheFile = parsedExitList + "-" + RemoteServerIP + ".cache"
+    confirmedExits = []
+
+    # Do we have a fresh exit cache?
+    maxListAge = 1600
+    try:
+        cacheStat = os.stat(cacheFile)
+        listAge = checkListAge(cacheFile)
+    except OSError:
+        cacheStat = None
+
+    # Without a fresh exit cache for the given ServerIP
+    # We'll generate one
+    if cacheStat is None or listAge > maxListAge:
+
+        # We're not reading from the cache
+        # Lets build a query list and cache the results
+        exits = open(parsedExitList, 'r')
+        possibleExits = exits.readlines()
+
+        # Check exiting to Tor, build a list of each positive reply and return
+        # the list
+        for possibleExit in possibleExits:
+            try:
+                if (isUsingTor(possibleExit, "217.247.237.209", "80") == 0 ):
+                    confirmedExits.append(possibleExit)
+            except:
+                return None
+
+        confirmedExits.sort()
+
+        # We didn't have a cache, we'll truncate any file in its place
+        cachedExitList = open(cacheFile, 'w')
+        for exitToCache in confirmedExits:
+            cachedExitList.write(exitToCache)
+
+        cachedExitList.close()
+
+        return confirmedExits
+
+    else:
+        # Lets return the cache
+        cachedExits = open(parsedExitList, 'r')
+        cachedExitList = cachedExits.readlines()
+        return cachedExitList
+
+def getRawList():
+    """
+    Eventually, this will use urllib to fetch a real url.
+    In theory, this function fetches a raw exit list from a given url if the
+    current unparsed exit list is older than a given threshold. Currently
+    this simply uses a static file from the file system. It returns a path to
+    an unparsed exit list as produced by the DNS Exit List software.
+    """
+
+    # Someday, do a real http get request here
+    # read into buffer, return buffer RawExitList with contents.
+    # follow instructions from http://docs.python.org/lib/module-urllib.html
+    RawExitListURL = "http://exitlist.torproject.org/exitAddresses"
+
+    # Currently fake this and return a static file:
+    RawExitList = '/home/rorreoi/Documents/work/tor/subversion/check/trunk/cgi-bin/exit-addresses'
+
+    return RawExitList
+
+def updateCache():
+    """
+    When this function returns, if there is no error, a parsed exit node cache
+    file exists. These are all of the nodes that may allow exiting. This is
+    useful for building tests for given exits.
+    """
+
+    maxListAge = 1600
+    parsedExitList = "/tmp/parsed-exit-list"
+
+    try:
+        # They may be a directory and so this would all fail.
+        # It may be better to check to see if this is a file.
+        parsedListStat = os.stat(parsedExitList)
+    except OSError:
+        parsedListStat = None
+
+    listAge = checkListAge(parsedExitList)
+
+    # If we lack a parsed list, perhaps we have a raw list?
+    if parsedListStat is None or listAge > maxListAge:
+        RawExitList = getRawList()
+        RawList = os.stat(RawExitList)
+        possibleExits = parseRawExitList(RawExitList)
+
+        parsedList = open(parsedExitList, 'w')
+        parsedList.write("\n".join(possibleExits))
+        parsedList.close()
+
+def checkListAge(list):
+    """
+    Check the age of the list in seconds.
+    """
+
+    try:
+        listStatus = os.stat(list)
+        now = time.time()
+        listCreationTime = os.stat(list).st_ctime
+        listAge = now - listCreationTime
+    except OSError:
+        listAge = None
+
+    return listAge
+
+def parseRawExitList(RawExitList):
+    exitAddresses = open(RawExitList)
+    possibleExits = []
+
+    # We'll only match IP addresses of Exit Nodes
+    search = re.compile('(^ExitAddress\ )([0-9.]*)\ ')
+    lines = exitAddresses.readlines()
+    for line in lines:
+         match = search.match(line)
+         if match:
+             possibleExits.append(match.group(2))
+
+    possibleExits.sort()
+    return possibleExits
+
+
+def isUsingTor(clientIp, ELTarget, ELPort):
+    stripIP = clientIp.rstrip('\n')
+    splitIp = stripIP.split('.')
+    splitIp.reverse()
+    ELExitNode = ".".join(splitIp)
+
+    # We'll attempt to reach this port on the Target host
+    ELPort = "80"
+
+    # We'll try to reach this host
+    ElTarget = "217.247.237.209"
+
+    # This is the ExitList DNS server we want to query
+    ELHost = "ip-port.exitlist.torproject.org"
+
+    # Prepare the question as an A record request
+    ELQuestion = ELExitNode + "." + ELPort + "." + ElTarget + "." + ELHost
+    print("Attempting to ask: %s" % ELQuestion)
+    request = DNS.DnsRequest(name=ELQuestion,qtype='A')
+    print("Asked: %s" % ELQuestion)
+
+    # Increase time out length
+    #answer=request.timeout = 30
+    # Ask the question and load the data into our answer
+    answer=request.req()
+
+    # Parse the answer and decide if it's allowing exits
+    # 127.0.0.2 is an exit and NXDOMAIN is not
+    if answer.header['status'] == "NXDOMAIN":
+        # We're not exiting from a Tor exit
+        return 1
+    else:
+        if not answer.answers:
+            # We're getting unexpected data - fail closed
+            return 2
+        for a in answer.answers:
+            if a['data'] != "127.0.0.2":
+                return 2
+        # If we're here, we've had a positive exit answer
+        return 0
+
+def parseAddress(req):
+    # Get the ip from apache
+    user_supplied_ip = None
+    formSubmission=util.FieldStorage(req)
+    user_supplied_ip = formSubmission.getfirst("ip", None)
+
+    # Check the IP, fail with a None
+    # We may want to clean this up with a regex only allowing [0-9.]
+    if user_supplied_ip is not None:
+        try:
+            parsed_ip = socket.inet_ntoa(socket.inet_aton(user_supplied_ip))
+        except socket.error:
+            return None
+    else:
+        return None
+
+    return parsed_ip 
+    
+def handler(req):
+    
+    req.send_http_header()
+    req.content_type = 'text/plain; charset=utf-8'
+
+    RemoteServerIP = parseAddress(req)
+    RemotePort = "80"
+    
+    if RemoteServerIP is not None:
+
+        updateCache()
+        TestedExits = bulkCheck(RemoteServerIP)
+        req.write("# This is a list of all Tor exit nodes that can contact " + RemoteServerIP + 
+        " on Port " + RemotePort + " #\n")
+        req.write("# You can update this list by visiting " + \
+        "https://check.torproject.org/TorBulkExitList.py?ip=%s #\n" % RemoteServerIP)
+        for exit in TestedExits:
+            req.write(str(exit))
+
+        return apache.OK
+
+    else:
+
+        req.send_http_header()
+        req.content_type = 'text/html; charset=utf-8'
+        req.write('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" '\
+        '"http://www.w3.org/TR/REC-html40/loose.dtd">\n')
+        req.write('<html>\n')
+        req.write('<head>\n')
+        req.write('<meta http-equiv="content-type" content="text/html; '\
+        'charset=utf-8">\n')
+        req.write('<title>Bulk Tor Exit Exporter</title>\n')
+        req.write('<link rel="shortcut icon" type="image/x-icon" '\
+        'href="./favicon.ico">\n')
+        req.write('<style type="text/css">\n')
+        req.write('img,acronym {\n')
+        req.write('  border: 0;')
+        req.write('  text-decoration: none;')
+        req.write('}')
+        req.write('</style>')
+        req.write('</head>\n')
+        req.write('<body>\n')
+        req.write('<center>\n')
+        req.write('\n')
+        req.write('<br>\n');
+
+        req.write('\n')
+
+        req.write('<img alt="' + ("Tor icon") + \
+        '" src="https://check.torproject.org/images/tor-on.png">\n<br>')
+        req.write('<br>\n<br>\n')
+       
+        req.write('Welcome to the Tor Bulk Exit List exporting tool.<br><br>\n')
+        req.write("""
+        If you are a service provider and you wish to build a list of possible
+        Tor nodes that can contact one your servers, enter that single server
+        address below. This service allows you to keep your users ip addresses private.
+        This list alows you to have a nearly real time authorative source for Tor
+        exits that allow contacting your server on port 80. While we don't log
+        the IP that queries for a given list, we do keep a cache of answers for
+        all queries made. This is purely for performance reasons and they are
+        automatically deleted after a given threshold. <br><br>\n""")        
+
+        req.write('Please enter an IP:<br>\n')
+        req.write('<form action="/TorBulkExitList.py" name=ip\n')
+        req.write('<input type="text" name="ip"><br>\n')
+        req.write('<input type="submit" value="Submit">')
+        req.write('</form>')
+
+        req.write('</center>\n')
+        req.write('</body>')
+        req.write('</html>')
+
+        return apache.OK


Property changes on: check/trunk/cgi-bin/TorBulkExitList.py
___________________________________________________________________
Name: svn:executable
   + *



More information about the tor-commits mailing list