[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[or-cvs] r14809: This is the first shot at a bulk exit checking script that i (check/trunk/cgi-bin)
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
+ *