[or-cvs] r17915: {} Move network scanners to NetworkScanners/ (in torflow/trunk: . NetworkScanners NetworkScanners/data NetworkScanners/libs data)
mikeperry at seul.org
mikeperry at seul.org
Mon Jan 5 16:51:38 UTC 2009
Author: mikeperry
Date: 2009-01-05 11:51:38 -0500 (Mon, 05 Jan 2009)
New Revision: 17915
Added:
torflow/trunk/NetworkScanners/
torflow/trunk/NetworkScanners/data/
torflow/trunk/NetworkScanners/data/soat/
torflow/trunk/NetworkScanners/libs/
torflow/trunk/NetworkScanners/soat.py
torflow/trunk/NetworkScanners/soatstats.py
torflow/trunk/NetworkScanners/speedracer.pl
torflow/trunk/NetworkScanners/wordlist.txt
Removed:
torflow/trunk/NetworkScanners/libs/BTAnalysis/
torflow/trunk/NetworkScanners/libs/moniTor.py
torflow/trunk/data/soat/
torflow/trunk/soat.py
torflow/trunk/soatstats.py
torflow/trunk/speedracer.pl
torflow/trunk/tools/
torflow/trunk/wordlist.txt
Log:
Move network scanners to NetworkScanners/
Copied: torflow/trunk/NetworkScanners/data/soat (from rev 17873, torflow/trunk/data/soat)
Copied: torflow/trunk/NetworkScanners/libs (from rev 17912, torflow/trunk/tools)
Deleted: torflow/trunk/NetworkScanners/libs/moniTor.py
===================================================================
--- torflow/trunk/tools/moniTor.py 2009-01-05 14:32:58 UTC (rev 17912)
+++ torflow/trunk/NetworkScanners/libs/moniTor.py 2009-01-05 16:51:38 UTC (rev 17915)
@@ -1,121 +0,0 @@
-#!/usr/bin/env python
-#
-#
-# This is a "top-like" interface for Tor information
-# It's goal at the start is to just tell you basic information
-# In the future, you may be able to control Tor with it.
-#
-# See this for some of the original ideas:
-# http://archives.seul.org/or/dev/Jan-2008/msg00005.html
-#
-# A typical output of moniTor could look like this (with some fake data
-# for the purpose of this example):
-#
-# ~ Name/ID: gabelmoo 6833 3D07 61BC F397 A587 A0C0 B963 E4A9 E99E C4D3
-# ~ Version: 0.2.0.15-alpha-dev (r13077) on Linux x86_64
-# ~ Config: /home/tor/gabelmoo/torrc, Exit policy: no exit allowed
-# ~ IP address: 88.198.7.215, OR port: 443, Dir port: 80
-#
-# ~ CPU: 9.0% this tor, 3.4% other processes, 87.6% idle
-# ~ Mem: 49.9% this tor, 2.0% other processes, 48.1% free
-# ~ Connections: 1090 OR conns, 320 Dir conns
-# ~ Bandwidth: 1.2 MB/s current, 1.3 MB/s avg
-#
-# ~ Recent events (see also /home/tor/gabelmoo/monitor.log):
-# ~ 14:30:01 [warn] Consensus does not include configured authority 'moria
-# ~ 14:30:01 [warn] Consensus does not include configured authority 'ides'
-# ~ 14:30:01 [warn] 0 unknown, 0 missing key, 2 good, 0 bad, 1 no signatur
-# ~ 14:30:01 [warn] Not enough info to publish pending consensus
-#
-
-
-__author__ = "Jacob Appelbaum"
-__version__ = "0.1-2008_01_16"
-__copyright__ = "http://www.torproject.org/Jacob Appelbaum 2008"
-
-import curses
-import time
-import sys
-import socket
-
-# Hack.. Can also set PYTHONPATH..
-# http://docs.python.org/tut/node8.html#searchPath
-sys.path.append('../')
-from TorCtl import TorCtl, TorUtil
-from TorCtl.TorCtl import *
-
-# Parse authenticate string from file here
-
-#moniTorConf = "/etc/moniTor.conf"
-#authSecret = open(moniTorConf).read().strip()
-authSecret = ""
-
-def parse_config():
-
- #moniTorConf = "/etc/moniTor.conf"
- #authSecret = open(moniTorConf).read().strip()
- #authSecret = ""
-
- return
-
-def create_oracle(host,port):
- """ Create a useful TorCtl object
- """
- print "I'm going to connect to %s and connect to port %i" %(sh,sp)
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.connect((host,port))
- oracle = Connection(s)
- oracle_thread = oracle.launch_thread()
- oracle.authenticate(authSecret)
-
- return oracle, oracle_thread
-
-# Much like run_example from TorCtl
-def collect_status(oracle):
- """ A basic loop for collecting static information from our TorCtl object
- """
- # add name/id, exit policy, or-port, dir-port
-
- static_keys = ['version', 'config-file', 'address', 'fingerprint', 'exit-policy/default', 'accounting/enabled']
- static_info = dict([(key, oracle.get_info(key)[key]) for key in static_keys])
-
- # Dynamic information can be collected by using our returned socket
- return static_info, static_keys
-
-if __name__ == '__main__':
- if len(sys.argv) > 1:
- print "Syntax: ",sys.argv[0]
- sys.exit(1)
- else:
- sys.argv.append("localhost:9051")
-
- parse_config()
- sh,sp = parseHostAndPort(sys.argv[1])
-
- torctl_oracle, torctl_oracle_thread = create_oracle(sh,sp)
- static_info, static_keys, = collect_status(torctl_oracle)
-
- # Number of connections, current bw
- dynamic_keys = ['version', 'config-file', 'address', 'fingerprint']
-
- torctl_oracle.set_event_handler(DebugEventHandler())
- torctl_oracle.set_events([EVENT_TYPE.STREAM, EVENT_TYPE.CIRC,
- EVENT_TYPE.NS, EVENT_TYPE.NEWDESC,
- EVENT_TYPE.ORCONN, EVENT_TYPE.BW], True)
-
-
- while True:
- # Populate the dynamic info each run
- dynamic_info = dict([(key, torctl_oracle.get_info(key)[key]) for key in dynamic_keys])
-
- # Now we can draw a few interesting things to the screen
- for key in static_info:
- print key + " is " + static_info[key]
-
- for key in dynamic_info:
- print key + " is " + dynamic_info[key]
-
- time.sleep(1)
- # So ghetto, so ghetto!
- os.system('clear')
-
Copied: torflow/trunk/NetworkScanners/soat.py (from rev 17873, torflow/trunk/soat.py)
===================================================================
--- torflow/trunk/NetworkScanners/soat.py (rev 0)
+++ torflow/trunk/NetworkScanners/soat.py 2009-01-05 16:51:38 UTC (rev 17915)
@@ -0,0 +1,1368 @@
+#!/usr/bin/python
+#
+# 2008 Aleksei Gorny, mentored by Mike Perry
+
+'''
+Snakes on a Tor exit node scanner
+
+The SoaT scanner checks whether exit nodes behave by initiating connections
+to semi-randomly chosen targets using several protocols (http, https, ssh, smtp, imap, etc)
+and comparing content received directly and via tor.
+
+It interacts with metatroller and the control port to be aware of the tor network status.
+
+To run SoaT:
+1) make sure you have py-openssl packages installed (see README)
+2) open Tor control port in the torrc
+3) start metatroller in the background (python ./metatroller.py)
+4) start soat (python ./soat.py) with some testing flags (run it without any flags
+ to see which options are available)
+5) check the results later by running soatstats (python ./soatstats.py)
+
+'''
+
+__all__ = ["ExitNodeScanner", "DNSRebindScanner", "load_wordlist", "get_urls"]
+
+import commands
+import getopt
+import httplib
+import os
+import random
+import re
+from sets import Set
+import smtplib
+import socket
+import string
+import sys
+import time
+import urllib
+import urllib2
+
+import soatstats
+from soatstats import *
+
+sys.path.append("../")
+
+from TorCtl import TorUtil, TorCtl, PathSupport
+from TorCtl.TorUtil import meta_port, meta_host, control_port, control_host, tor_port, tor_host
+from TorCtl.TorUtil import *
+from TorCtl.PathSupport import *
+from TorCtl.TorCtl import Connection, EventHandler
+
+from OpenSSL import *
+
+sys.path.append("./libs/")
+from BeautifulSoup.BeautifulSoup import BeautifulSoup, SoupStrainer
+from SocksiPy import socks
+import Pyssh.pyssh
+
+#
+# config stuff
+#
+
+# these are used when searching for 'random' urls for testing
+wordlist_file = './wordlist.txt';
+allowed_filetypes = ['all','pdf']
+result_per_type = 5
+
+#
+# ports to test in the consistency test
+#
+
+ports_to_check = [
+ ["pop", ExitPolicyRestriction('255.255.255.255', 110), "pops", ExitPolicyRestriction('255.255.255.255', 995)],
+ ["imap", ExitPolicyRestriction('255.255.255.255', 143), "imaps", ExitPolicyRestriction('255.255.255.255', 993)],
+ ["telnet", ExitPolicyRestriction('255.255.255.255', 23), "ssh", ExitPolicyRestriction('255.255.255.255', 22)],
+ ["smtp", ExitPolicyRestriction('255.255.255.255', 25), "smtps", ExitPolicyRestriction('255.255.255.255', 465)],
+ ["http", ExitPolicyRestriction('255.255.255.255', 80), "https", ExitPolicyRestriction('255.255.255.255', 443)]
+]
+
+#
+# non-public IPv4 address ranges network portions
+# refer to: www.iana.org/assignments/ipv4-address-space, www.iana.org/assignments/multicast-addresses
+#
+ipv4_nonpublic = [
+ '00000000', # default route and its network: 0.0.0.0/8
+ '00001010', # private 10.0.0.0/8
+ '01111111', # loopback 127.0.0.0/8
+ '1010100111111110', # link-local 169.254.0.0/16
+ '101011000001', # private 172.16.0.0/12
+ '1100000010101000', # private 192.168.0.0/16
+ '111' # multicast & experimental 224.0.0.0/3
+]
+
+# tags and attributes to check in the http test: XXX these should be reviewed
+# See also: http://ha.ckers.org/xss.html
+# Note: the more we add, the greater the potential for false positives...
+# We also only care about the ones that work for FF2/FF3.
+tags_to_check = ['a', 'area', 'base', 'applet', 'embed', 'form', 'frame',
+ 'iframe', 'img', 'link', 'object', 'script', 'meta', 'body']
+attrs_to_check = ['onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmouseover',
+ 'onmousemove', 'onmouseout', 'onkeypress','onkeydown','onkeyup']
+#
+# constants
+#
+
+linebreak = '\r\n'
+
+# a simple interface to handle a socket connection
+class Client:
+
+ def __init__(self, host, port):
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.connect((host, port))
+ self.buffer = self.sock.makefile('rb')
+
+ def writeline(self, line):
+ self.sock.send(line + linebreak)
+
+ def readline(self):
+ response = self.buffer.readline()
+ if not response:
+ raise EOFError
+ elif response[-2:] == linebreak:
+ response = response[:-2]
+ elif response[-1:] in linebreak:
+ response = response[:-1]
+ return response
+
+class DNSRebindScanner(EventHandler):
+ '''
+ A tor control event handler extending TorCtl.EventHandler
+ Monitors for REMAP events (see check_dns_rebind())
+ '''
+ def __init__(self, exit_node_scanner):
+ EventHandler.__init__(self)
+ self.__soat = exit_node_scanner
+
+ def stream_status_event(self, event):
+ if event.status == 'REMAP':
+ octets = map(lambda x: int2bin(x).zfill(8), event.target_host.split('.'))
+ ipbin = ''.join(octets)
+ for network in ipv4_nonpublic:
+ if ipbin[:len(network)] == network:
+ handler = DataHandler()
+ result = DNSRebindTestResult(self.__soat.get_exit_node(), '', TEST_FAILURE)
+ handler.saveResult(result)
+
+class ExitNodeScanner:
+ ''' The scanner class '''
+ def __init__(self):
+ '''
+ Establish a connection to metatroller & control port,
+ configure metatroller, load the number of previously tested nodes
+ '''
+ # establish a metatroller connection
+ plog('INFO', 'ExitNodeScanner starting up...')
+ try:
+ self.__meta = Client(meta_host, meta_port)
+ except socket.error:
+ plog('ERROR', 'Couldn\'t connect to metatroller. Is it on?')
+ exit()
+
+ # skip two lines of metatroller introduction
+ data = self.__meta.readline()
+ data = self.__meta.readline()
+
+ # configure metatroller
+ commands = [
+ 'PATHLEN 2',
+ 'PERCENTFAST 88',
+ 'USEALLEXITS 1',
+ 'UNIFORM 0',
+ 'BWCUTOFF 1',
+ 'ORDEREXITS 1',
+ 'GUARDNODES 0',
+ 'RESETSTATS']
+ plog('INFO', 'Executing preliminary configuration commands')
+ for c in commands:
+ self.__meta.writeline(c)
+ reply = self.__meta.readline()
+ if reply[:3] != '250': # first three chars indicate the reply code
+ reply += self.__meta.readline()
+ plog('ERROR', 'Error configuring metatroller (' + command + ' failed)')
+ plog('ERROR', reply)
+ exit()
+
+ # establish a control port connection
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect((control_host, control_port))
+ c = Connection(s)
+ c.authenticate()
+ self.__control = c
+ except socket.error, e:
+ plog('ERROR', 'Couldn\'t connect to the control port')
+ plog('ERROR', e)
+ exit()
+ except AttributeError, e:
+ plog('ERROR', 'A service other that the Tor control port is listening on ' + control_host + ':' + control_port)
+ plog('ERROR', e)
+ exit()
+
+ # get a data handler
+ self.__datahandler = DataHandler()
+
+ # TODO get stats about previous runs
+ plog('INFO', 'Loading the previous run stats')
+
+ ssh_results = self.__datahandler.getSsh()
+ ssl_results = self.__datahandler.getSsl()
+ http_results = self.__datahandler.getHttp()
+
+ # get lists of tested nodes
+ self.ssh_tested = Set([x.exit_node for x in ssh_results])
+ self.http_tested = Set([x.exit_node for x in http_results])
+ self.ssl_tested = Set([x.exit_node for x in ssl_results])
+
+ # get the number of failures
+ self.ssh_fail = [self.__datahandler.filterResults(ssh_results, protocols=["ssh"], show_bad=True)]
+ self.http_fail = [self.__datahandler.filterResults(http_results, protocols=["http"], show_bad=True)]
+ self.ssl_fail = [self.__datahandler.filterResults(ssl_results, protocols=["ssl"], show_bad=True)]
+
+ plog('INFO', 'ExitNodeScanner up and ready')
+
+ def get_exit_node(self):
+ ''' ask metatroller for the last exit used '''
+ self.__meta.writeline("GETLASTEXIT")
+ reply = self.__meta.readline()
+
+ if reply[:3] != '250':
+ reply += self.__meta.readline()
+ plog('ERROR', reply)
+ return 0
+
+ p = re.compile('250 LASTEXIT=[\S]+')
+ m = p.match(reply)
+ self.__exit = m.group()[13:] # drop the irrelevant characters
+ plog('NOTICE','Current node: ' + self.__exit)
+ return self.__exit
+
+ def get_new_circuit(self):
+ ''' tell metatroller to close the current circuit and open a new one '''
+ plog('NOTICE', 'Trying to construct a new circuit')
+ self.__meta.writeline("NEWEXIT")
+ reply = self.__meta.readline()
+
+ if reply[:3] != '250':
+ plog('ERROR', 'Choosing a new exit failed')
+ plog('ERROR', reply)
+
+ def set_new_exit(self, exit):
+ '''
+ tell metatroller to set the given node as the exit in the next circuit
+ '''
+ plog('NOTICE', 'Trying to set ' + `exit` + ' as the exit for the next circuit')
+ self.__meta.writeline("SETEXIT $"+exit)
+ reply = self.__meta.readline()
+
+ if reply[:3] != '250':
+ plog('ERROR', 'Setting ' + exit + ' as the new exit failed')
+ plog('ERROR', reply)
+
+ def report_bad_exit(self, exit):
+ '''
+ report an evil exit to the control port using AuthDirBadExit
+ Note: currently not used
+ '''
+ # self.__contol.set_option('AuthDirBadExit', exit) ?
+ pass
+
+ def get_nodes_for_port(self, port):
+ ''' ask control port for a list of nodes that allow exiting to a given port '''
+ routers = self.__control.read_routers(self.__control.get_network_status())
+ restriction = NodeRestrictionList([FlagsRestriction(["Running", "Valid"]), ExitPolicyRestriction('255.255.255.255', port)])
+ return [x for x in routers if restriction.r_is_ok(x)]
+
+ def check_all_exits_port_consistency(self):
+ '''
+ an independent test that finds nodes that allow connections over a common protocol
+ while disallowing connections over its secure version (for instance http/https)
+ '''
+
+ # get the structure
+ routers = self.__control.read_routers(self.__control.get_network_status())
+ bad_exits = Set([])
+ specific_bad_exits = [None]*len(ports_to_check)
+ for i in range(len(ports_to_check)):
+ specific_bad_exits[i] = []
+
+ # check exit policies
+ for router in routers:
+ for i in range(len(ports_to_check)):
+ [common_protocol, common_restriction, secure_protocol, secure_restriction] = ports_to_check[i]
+ if common_restriction.r_is_ok(router) and not secure_restriction.r_is_ok(router):
+ bad_exits.add(router)
+ specific_bad_exits[i].append(router)
+ plog('INFO', 'Router ' + router.nickname + ' allows ' + common_protocol + ' but not ' + secure_protocol)
+
+ # report results
+ plog('INFO', 'Total exits: ' + `len(routers)`)
+ for i in range(len(ports_to_check)):
+ [common_protocol, _, secure_protocol, _] = ports_to_check[i]
+ plog('INFO', 'Exits with ' + common_protocol + ' / ' + secure_protocol + ' problem: ' + `len(specific_bad_exits[i])` + ' (~' + `(len(specific_bad_exits[i]) * 100 / len(routers))` + '%)')
+ plog('INFO', 'Total bad exits: ' + `len(bad_exits)` + ' (~' + `(len(bad_exits) * 100 / len(routers))` + '%)')
+
+ def check_http(self, address):
+ ''' check whether a http connection to a given address is molested '''
+ plog('INFO', 'Conducting an http test with destination ' + address)
+
+ defaultsocket = socket.socket
+ socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, tor_host, tor_port)
+ socket.socket = socks.socksocket
+
+ pcontent = self.http_request(address)
+
+ # reset the connection to direct
+ socket.socket = defaultsocket
+
+ exit_node = self.get_exit_node()
+ if exit_node == 0 or exit_node == '0' or not exit_node:
+ plog('INFO', 'We had no exit node to test, skipping to the next test.')
+ return TEST_SUCCESS
+
+ # an address representation acceptable for a filename
+ address_file = self.__datahandler.safeFilename(address[7:])
+
+ # if we have no content, we had a connection error
+ if pcontent == 0:
+ result = HttpTestResult(exit_node, address, 0, TEST_INCONCLUSIVE)
+ self.__datahandler.saveResult(result)
+ return TEST_INCONCLUSIVE
+
+ elements = SoupStrainer(lambda name, attrs : name in tags_to_check or
+ len(Set(attrs).intersection(Set(attrs_to_check))) > 0)
+ pcontent = pcontent.decode('ascii', 'ignore')
+ psoup = BeautifulSoup(pcontent, parseOnlyThese=elements)
+
+ # load the original tag structure
+ # if we don't have any yet, get it
+ soup = 0
+ try:
+ tag_file = open(http_tags_dir + address_file + '.tags', 'r')
+ soup = BeautifulSoup(tag_file.read())
+ tag_file.close()
+ except IOError:
+ content = self.http_request(address)
+ content = content.decode('ascii','ignore')
+ soup = BeautifulSoup(content, parseOnlyThese=elements)
+ tag_file = open(http_tags_dir + address_file + '.tags', 'w')
+ tag_file.write(soup.__str__() + ' ') # the space is needed in case we have some page with no matching tags at all
+ tag_file.close()
+ except TypeError, e:
+ plog('ERROR', 'Failed parsing the tag tree for ' + address)
+ plog('ERROR', e)
+ return TEST_INCONCLUSIVE
+ if soup == 0:
+ plog('ERROR', 'Failed to get the correct tag structure for ' + address)
+ return TEST_INCONCLUSIVE
+
+ self.http_tested.add(exit_node)
+
+ # compare the content
+ # if content matches, everything is ok
+ if psoup == soup:
+ result = HttpTestResult(exit_node, address, 0, TEST_SUCCESS)
+ self.__datahandler.saveResult(result)
+ return TEST_SUCCESS
+
+ # if content doesnt match, update the direct content
+ content_new = self.http_request(address)
+ content_new = content_new.decode('ascii', 'ignore')
+ if content_new == 0:
+ result = HttpTestResult(exit_node, address, 0, TEST_INCONCLUSIVE)
+ self.__datahandler.saveResult(result)
+ return TEST_INCONCLUSIVE
+
+ soup_new = BeautifulSoup(content_new, parseOnlyThese=elements)
+ # compare the new and old content
+ # if they match, means the node has been changing the content
+ if soup == soup_new:
+ result = HttpTestResult(exit_node, address, 0, TEST_FAILURE)
+ self.__datahandler.saveResult(result)
+ tag_file = open(http_tags_dir + `exit_node` + '_' + address_file + '.tags', 'w')
+ tag_file.write(psoup.__str__())
+ tag_file.close()
+ return TEST_FAILURE
+
+ # if content has changed outside of tor, update the saved file
+ tag_file = open(http_tags_dir + address_file + '.tags', 'w')
+ tag_file.write(soup_new.__str__())
+ tag_file.close()
+
+ # compare the node content and the new content
+ # if it matches, everything is ok
+ if psoup == soup_new:
+ result = HttpTestResult(exit_node, address, 0, TEST_SUCCESS)
+ self.__datahandler.saveResult(result)
+ return TEST_SUCCESS
+
+ # if it doesn't match, means the node has been changing the content
+ result = HttpTestResult(exit_node, address, 0, TEST_FAILURE)
+ self.__datahandler.saveResult(result)
+ tag_file = open(http_tags_dir + `exit_node` + '_' + address_file + '.tags', 'w')
+ tag_file.write(psoup.__str__())
+ tag_file.close()
+
+ return TEST_FAILURE
+
+ def check_openssh(self, address):
+ ''' check whether an openssh connection to a given address is molested '''
+ # TODO
+ #ssh = pyssh.Ssh('username', 'host', 22)
+ #ssh.set_sshpath(pyssh.SSH_PATH)
+ #response = self.ssh.sendcmd('ls')
+ #print response
+
+ return 0
+
+ def check_openssl(self, address):
+ ''' check whether an https connection to a given address is molested '''
+ plog('INFO', 'Conducting an ssl test with destination ' + address)
+
+ # an address representation acceptable for a filename
+ address_file = self.__datahandler.safeFilename(address[8:])
+
+ # get the cert via tor
+
+ defaultsocket = socket.socket
+ socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, tor_host, tor_port)
+ socket.socket = socks.socksocket
+
+ cert = self.ssl_request(address)
+
+ # reset the connection method back to direct
+ socket.socket = defaultsocket
+
+ exit_node = self.get_exit_node()
+ if exit_node == 0 or exit_node == '0' or not exit_node:
+ plog('INFO', 'We had no exit node to test, skipping to the next test.')
+ return TEST_FAILURE
+
+ # if we got no cert, there was an ssl error
+ if cert == 0:
+ result = SSLTestResult(exit_node, address, 0, TEST_INCONCLUSIVE)
+ self.__datahandler.saveResult(result)
+ return TEST_INCONCLUSIVE
+
+ # load the original cert and compare
+ # if we don't have the original cert yet, get it
+ original_cert = 0
+ try:
+ cert_file = open(ssl_certs_dir + address_file + '.pem', 'r')
+ cert_string = cert_file.read()
+ original_cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_string)
+ except IOError:
+ plog('INFO', 'Opening a direct ssl connection to ' + address)
+ original_cert = self.ssl_request(address)
+ if not original_cert:
+ plog('ERROR', 'Error getting the correct cert for ' + address)
+ return TEST_INCONCLUSIVE
+ if original_cert.has_expired():
+ plog('ERROR', 'The ssl cert for ' + address + 'seems to have expired. Skipping to the next test...')
+ return TEST_INCONCLUSIVE
+ cert_file = open(ssl_certs_dir + address_file + '.pem', 'w')
+ cert_file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, original_cert))
+ cert_file.close()
+ except OpenSSL.crypto.Error:
+ plog('ERROR', 'There are non-related files in ' + ssl_certs_dir + '. You should probably clean it.')
+ return TEST_INCONCLUSIVE
+ if not original_cert:
+ plog('ERROR', 'Error getting the correct cert for ' + address)
+ return TEST_INCONCLUSIVE
+
+ # get an easily comparable representation of the certs
+ cert_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
+ original_cert_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, original_cert)
+
+ # in any case we can consider the node looked at
+ self.ssl_tested.add(exit_node)
+
+ # if certs match, everything is ok
+ if cert_pem == original_cert_pem:
+ cert_file = ssl_certs_dir + address_file + '.pem'
+ result = SSLTestResult(exit_node, address, cert_file, TEST_SUCCESS)
+ self.__datahandler.saveResult(result)
+ return TEST_SUCCESS
+
+ # if certs dont match, open up a direct connection and update the cert
+ plog('INFO', 'Opening a direct ssl connection to ' + address)
+ original_cert_new = self.ssl_request(address)
+ if original_cert_new == 0:
+ plog('ERROR', 'Error getting the correct cert for ' + address)
+ result = SSLTestResult(exit_node, address, 0, TEST_INCONCLUSIVE)
+ self.__datahandler.saveResult(result)
+ return TEST_INCONCLUSIVE
+
+ original_cert_new_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, original_cert_new)
+
+ # compare the old and new cert
+ # if certs match, means the exit node has been messing with the cert
+ if original_cert_pem == original_cert_new_pem:
+ plog('ERROR', 'Exit node ' + `exit_node` + ' seems to be meddling with certificates. (' + address + ')')
+
+ cert_file_name = ssl_certs_dir + address_file + '_' + `exit_node` + '.pem'
+ cert_file = open(cert_file_name, 'w')
+ cert_file.write(cert_pem)
+ cert_file.close()
+
+ result = SSLTestResult(exit_node, address, cert_file_name, TEST_FAILURE)
+ self.__datahandler.saveResult(result)
+ return TEST_FAILURE
+
+ # if comparsion fails, replace the old cert with the new one
+ # XXX: Hrmm, probably should store as a seperate IP file in this case
+ # so we don't keep alternating on sites that have round robin
+ # DNS and different certs for each IP..
+ cert_file = open(ssl_certs_dir + address_file + '.pem', 'w')
+ cert_file.write(original_cert_new_pem)
+ cert_file.close()
+
+ # compare the new cert and the node cert
+ # if certs match, everything is ok
+ if cert_pem == original_cert_new_pem:
+ cert_file = ssl_certs_dir + address_file + '.pem'
+ result = SSLTestResult(exit_node, address, cert_file, TEST_SUCCESS)
+ self.__datahandler.saveResult(result)
+ return TEST_SUCCESS
+
+ # if certs dont match, means the exit node has been messing with the cert
+ plog('ERROR', 'Exit node ' + `exit_node` + ' seems to be meddling with certificates. (' + address + ')')
+
+ cert_file_name = ssl_certs_dir + address + '_' + `exit_node` + '.pem'
+ cert_file = open(cert_file_name, 'w')
+ cert_file.write(cert_pem)
+ cert_file.close()
+
+ result = SSLTestResult(exit_node, address, cert_file_name, TEST_FAILURE)
+ self.__datahandler.saveResult(result)
+
+ return TEST_FAILURE
+
+ def check_smtp(self, address, port=''):
+ '''
+ check whether smtp + tls connection to a given address is molested
+ this is done by going through the STARTTLS sequence and comparing server
+ responses for the direct and tor connections
+ '''
+
+ plog('INFO', 'Conducting an smtp test with destination ' + address)
+
+ defaultsocket = socket.socket
+ socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, tor_host, tor_port)
+ socket.socket = socks.socksocket
+
+ ehlo1_reply = 0
+ has_starttls = 0
+ ehlo2_reply = 0
+
+ try:
+ s = smtplib.SMTP(address, port)
+ ehlo1_reply = s.ehlo()[0]
+ if ehlo1_reply != 250:
+ raise smtplib.SMTPException('First ehlo failed')
+ has_starttls = s.has_extn('starttls')
+ if not has_starttls:
+ raise smtplib.SMTPException('It seems the server doesn\'t support starttls')
+ s.starttls()
+ # TODO check certs?
+ ehlo2_reply = s.ehlo()[0]
+ if ehlo2_reply != 250:
+ raise smtplib.SMTPException('Second ehlo failed')
+ except socket.gaierror, e:
+ plog('ERROR', 'A connection error occured while testing smtp at ' + address)
+ plog('ERROR', e)
+ socket.socket = defaultsocket
+ return TEST_INCONCLUSIVE
+ except smtplib.SMTPException, e:
+ plog('ERROR','An error occured while testing smtp at ' + address)
+ plog('ERROR', e)
+ return TEST_INCONCLUSIVE
+ # reset the connection method back to direct
+ socket.socket = defaultsocket
+
+ # check whether the test was valid at all
+ exit_node = self.get_exit_node()
+ if exit_node == 0 or exit_node == '0':
+ plog('INFO', 'We had no exit node to test, skipping to the next test.')
+ return TEST_SUCCESS
+
+ # now directly
+
+ ehlo1_reply_d = 0
+ has_starttls_d = 0
+ ehlo2_reply_d = 0
+
+ try:
+ s = smtplib.SMTP(address, port)
+ ehlo1_reply_d = s.ehlo()[0]
+ if ehlo1_reply != 250:
+ raise smtplib.SMTPException('First ehlo failed')
+ has_starttls_d = s.has_extn('starttls')
+ if not has_starttls_d:
+ raise smtplib.SMTPException('It seems that the server doesn\'t support starttls')
+ s.starttls()
+ ehlo2_reply_d = s.ehlo()[0]
+ if ehlo2_reply_d != 250:
+ raise smtplib.SMTPException('Second ehlo failed')
+ except socket.gaierror, e:
+ plog('ERROR', 'A connection error occured while testing smtp at ' + address)
+ plog('ERROR', e)
+ socket.socket = defaultsocket
+ return TEST_INCONCLUSIVE
+ except smtplib.SMTPException, e:
+ plog('ERROR', 'An error occurred while testing smtp at ' + address)
+ plog('ERROR', e)
+ return TEST_INCONCLUSIVE
+
+ print ehlo1_reply, ehlo1_reply_d, has_starttls, has_starttls_d, ehlo2_reply, ehlo2_reply_d
+
+ # compare
+ if ehlo1_reply != ehlo1_reply_d or has_starttls != has_starttls_d or ehlo2_reply != ehlo2_reply_d:
+ result = SMTPTestResult(exit_node, address, TEST_FAILURE)
+ self.__datahandler.saveResult(result)
+ return TEST_FAILURE
+
+ result = SMTPTestResult(exit_node, address, TEST_SUCCESS)
+ self.__datahandler.saveResult(result)
+ return TEST_SUCCESS
+
+ def check_pop(self, address, port=''):
+ '''
+ check whether a pop + tls connection to a given address is molested
+ it is implied that the server reads/sends messages compliant with RFC1939 & RFC2449
+ '''
+
+ plog('INFO', 'Conducting a pop test with destination ' + address)
+
+ if not port:
+ port = 110
+
+ defaultsocket = socket.socket
+ socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, tor_host, tor_port)
+ socket.socket = socks.socksocket
+
+ capabilities_ok = False
+ starttls_present = False
+ tls_started = None
+ tls_succeeded = None
+
+ try:
+ pop = Client(address, port)
+
+ # read the server greeting
+ server_greeting = pop.readline()
+
+ # get the server capabilities
+ pop.writeline('CAPA')
+ capabilities = ''
+ while 1:
+ curr = pop.readline()
+ if '+OK' in curr:
+ capabilities_ok = True
+ elif curr == '.':
+ break
+ elif 'STLS' in curr:
+ starttls_present = True
+
+ if not capabilities_ok:
+ return TEST_INCONCLUSIVE
+
+ # try to start tls negotiation
+ if starttls_present:
+ pop.writeline('STLS')
+
+ starttls_started = '+OK' in starttls_response
+
+ # negotiate TLS and issue some request to feel good about it
+ # TODO check certs?
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ c = SSL.Connection(ctx, pop.sock)
+ c.set_connect_state()
+ c.do_handshake()
+ c.send('CAPA' + linebreak)
+
+ while tls_succeeded == None:
+ line = ''
+ char = None
+ while char != '\n':
+ char = c.read(1)
+ if not char:
+ break
+ elif char == '.':
+ tls_succeeded = False
+ line += char
+
+ if '-ERR' in line:
+ tls_succeeded = False
+ elif '+OK' in line:
+ tls_succeeded = True
+ elif not line:
+ tls_succeeded = False
+
+ except socket.error, e:
+ plog('ERROR', 'Connection to ' + address + ':' + port + ' refused')
+ plog('ERROR', e)
+ socket.socket = defaultsocket
+ return TEST_INCONCLUSIVE
+ except OpenSSL.SSL.SysCallError, e:
+ plog('ERROR', 'Error while negotiating an SSL connection to ' + address + ':' + port)
+ plog('ERROR', e)
+ socket.socket = defaultsocket
+ return TEST_INCONCLUSIVE
+
+ # reset the connection to default
+ socket.socket = defaultsocket
+
+ # check whether the test was valid at all
+ exit_node = self.get_exit_node()
+ if exit_node == 0 or exit_node == '0':
+ plog('INFO', 'We had no exit node to test, skipping to the next test.')
+ return TEST_SUCCESS
+
+ # do the same for the direct connection
+
+ capabilities_ok_d = False
+ starttls_present_d = False
+ tls_started_d = None
+ tls_succeeded_d = None
+
+ try:
+ pop = Client(address, port)
+
+ # read the server greeting
+ server_greeting = pop.readline()
+
+ # get the server capabilities
+ pop.writeline('CAPA')
+ capabilities = ''
+ while 1:
+ curr = pop.readline()
+ if '+OK' in curr:
+ capabilities_ok_d = True
+ elif curr == '.':
+ break
+ elif 'STLS' in curr:
+ starttls_present_d = True
+
+ if not capabilities_ok_d:
+ return TEST_INCONCLUSIVE
+
+ # try to start tls negotiation
+ if starttls_present_d:
+ pop.writeline('STLS')
+
+ starttls_started_d = '+OK' in starttls_response
+
+ # negotiate TLS, issue some request to feel good about it
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ c = SSL.Connection(ctx, pop.sock)
+ c.set_connect_state()
+ c.do_handshake()
+ c.send('CAPA' + linebreak)
+
+ while tls_succeeded_d == None:
+ line = ''
+ char = None
+ while char != '\n':
+ char = c.read(1)
+ if not char:
+ break
+ elif char == '.':
+ tls_succeeded_d = False
+ line += char
+
+ if '-ERR' in line:
+ tls_succeeded_d = False
+ elif '+OK' in line:
+ tls_succeeded_d = True
+ elif not line:
+ tls_succeeded_d = False
+
+ except socket.error, e:
+ plog('ERROR', 'Connection to ' + address + ':' + port + ' refused')
+ plog('ERROR', e)
+ socket.socket = defaultsocket
+ return TEST_INCONCLUSIVE
+ except OpenSSL.SSL.SysCallError, e:
+ plog('ERROR', 'Error while negotiating an SSL connection to ' + address + ':' + port)
+ plog('ERROR', e)
+ socket.socket = defaultsocket
+ return TEST_INCONCLUSIVE
+
+ # compare
+ if (capabilities_ok != capabilities_ok_d or starttls_present != starttls_present_d or
+ tls_started != tls_started_d or tls_suceeded != tls_succeeded_d):
+ result = POPTestResult(exit_node, address, TEST_FAILURE)
+ self.__datahandler.saveResult(result)
+ return TEST_FAILURE
+
+ result = POPTestResult(exit_node, address, TEST_SUCCESS)
+ self.__datahandler.saveResult(result)
+ return TEST_SUCCESS
+
+ def check_imap(self, address, port=''):
+ '''
+ check whether an imap + tls connection to a given address is molested
+ it is implied that the server reads/sends messages compliant with RFC3501
+ '''
+ plog('INFO', 'Conducting an imap test with destination ' + address)
+
+ if not port:
+ port = 143
+
+ defaultsocket = socket.socket
+ socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, tor_host, tor_port)
+ socket.socket = socks.socksocket
+
+ capabilities_ok = None
+ starttls_present = None
+ tls_started = None
+ tls_succeeded = None
+
+ try:
+ imap = Client(address, port)
+
+ # read server greeting
+ server_greeting = imap.readline()
+
+ # get server capabilities
+ imap.writeline('a001 CAPABILITY')
+ capabilities = imap.readline() # first line - list of capabilities
+ capabilities_ok = 'OK' in imap.readline() # second line - the request status
+
+ if not capabilities_ok:
+ return TEST_INCONCLUSIVE
+
+ # check if starttls is present
+ starttls_present = 'STARTTLS' in capabilities
+
+ if starttls_present:
+ imap.writeline('a002 STARTTLS')
+ tls_started = 'OK' in imap.readline()
+
+ # negotiate TLS, issue a request to feel good about it
+ # TODO check the cert aswell ?
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ c = SSL.Connection(ctx, imap.sock)
+ c.set_connect_state()
+ c.do_handshake()
+ c.send('a003 CAPABILITY' + linebreak)
+
+ while tls_succeeded == None:
+ line = ''
+ char = None
+ while char != '\n':
+ char = c.read(1)
+ if not char:
+ break
+ line += char
+
+ if 'Error' in line or 'error' in line:
+ tls_succeeded = False
+ elif 'OK' in line:
+ tls_succeeded = True
+ elif not line:
+ tls_succeeded = False
+
+ except socket.error, e:
+ plog('ERROR', 'Connection to ' + address + ':' + port + ' refused')
+ plog('ERROR', e)
+ socket.socket = defaultsocket
+ return TEST_INCONCLUSIVE
+ except OpenSSL.SSL.SysCallError, e:
+ plog('ERROR', 'Error while negotiating an SSL connection to ' + address + ':' + port)
+ plog('ERROR', e)
+ socket.socket = defaultsocket
+ return TEST_INCONCLUSIVE
+
+ socket.socket = defaultsocket
+
+ # check whether the test was valid at all
+ exit_node = self.get_exit_node()
+ if exit_node == 0 or exit_node == '0':
+ plog('INFO', 'We had no exit node to test, skipping to the next test.')
+ return TEST_SUCCESS
+
+ # do the same for the direct connection
+ capabilities_ok_d = None
+ starttls_present_d = None
+ tls_started_d = None
+ tls_succeeded_d = None
+
+ try:
+ imap = Client(address, port)
+
+ # read server greeting
+ server_greeting = imap.readline()
+
+ # get server capabilities
+ imap.writeline('a001 CAPABILITY')
+ capabilities = imap.readline() # first line - list of capabilities
+ capabilities_ok_d = 'OK' in imap.readline() # second line - the request status
+
+ if not capabilities_ok_d:
+ return TEST_INCONCLUSIVE
+
+ # check if starttls is present
+ starttls_present_d = 'STARTTLS' in capabilities
+
+ if starttls_present_d:
+ imap.writeline('a002 STARTTLS')
+ tls_started = 'OK' in imap.readline()
+
+ # negotiate TLS, issue some request to feel good about it
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ c = SSL.Connection(ctx, imap.sock)
+ c.set_connect_state()
+ c.do_handshake()
+ c.send('a003 CAPABILITY' + linebreak)
+
+ while tls_succeeded_d == None:
+ line = ''
+ char = None
+ while char != '\n':
+ char = c.read(1)
+ if not char:
+ break
+ line += char
+
+ if 'Error' in line or 'error' in line:
+ tls_succeeded_d = False
+ elif 'OK' in line:
+ tls_succeeded_d = True
+ elif not line:
+ tls_succeeded_d = False
+
+ except socket.error, e:
+ plog('ERROR', 'Connection to ' + address + ':' + port + ' refused')
+ plog('ERROR', e)
+ socket.socket = defaultsocket
+ return TEST_INCONCLUSIVE
+ except OpenSSL.SSL.SysCallError, e:
+ plog('ERROR', 'Error while negotiating an SSL connection to ' + address + ':' + port)
+ plog('ERROR', e)
+ socket.socket = defaultsocket
+ return TEST_INCONCLUSIVE
+
+ # compare
+ if (capabilities_ok != capabilities_ok_d or starttls_present != starttls_present_d or
+ tls_started != tls_started_d or tls_succeeded != tls_succeeded_d):
+ result = IMAPTestResult(exit_node, address, TEST_FAILURE)
+ self.__datahandler.saveResult(result)
+ return TEST_FAILURE
+
+ result = IMAPTestResult(exit_node, address, TEST_SUCCESS)
+ self.__datahandler.saveResult(result)
+ return TEST_SUCCESS
+
+ def check_dns(self, address):
+ ''' A basic comparison DNS test. Rather unreliable. '''
+ # TODO Spawns a lot of false positives (for ex. doesn't work for google.com).
+ plog('INFO', 'Conducting a basic dns test for destination ' + address)
+
+ ip = tor_resolve(address)
+
+ # check whether the test was valid at all
+ exit_node = self.get_exit_node()
+ if exit_node == 0 or exit_node == '0':
+ plog('INFO', 'We had no exit node to test, skipping to the next test.')
+ return TEST_SUCCESS
+
+ ips_d = Set([])
+ try:
+ results = socket.getaddrinfo(address,None)
+ for result in results:
+ ips_d.add(result[4][0])
+ except socket.herror, e:
+ plog('ERROR', 'An error occured while performing a basic dns test')
+ plog('ERROR', e)
+ return TEST_INCONCLUSIVE
+
+ if ip in ips_d:
+ result = DNSTestResult(exit_node, address, TEST_SUCCESS)
+ return TEST_SUCCESS
+ else:
+ plog('ERROR', 'The basic DNS test suspects ' + exit_node + ' to be malicious.')
+ result = DNSTestResult(exit_node, address, TEST_FAILURE)
+ return TEST_FAILURE
+
+ def check_dns_rebind(self):
+ '''
+ A DNS-rebind attack test that runs in the background and monitors REMAP events
+ The test makes sure that external hosts are not resolved to private addresses
+ '''
+ plog('INFO', 'Monitoring REMAP events for weirdness')
+ self.__dnshandler = DNSRebindScanner(self)
+ self.__control.set_event_handler(self.__dnshandler)
+ self.__control.set_events([TorCtl.EVENT_TYPE.STREAM], True)
+
+ def http_request(self, address):
+ ''' perform a http GET-request and return the content received '''
+ request = urllib2.Request(address)
+ request.add_header('User-Agent','Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.8.1) Gecko/20061010 Firefox/2.0')
+
+ content = 0
+ try:
+ reply = urllib2.urlopen(request)
+ content = reply.read()
+ except (ValueError, urllib2.URLError):
+ plog('ERROR', 'The http-request address ' + address + ' is malformed')
+ return 0
+ except (IndexError, TypeError):
+ plog('ERROR', 'An error occured while negotiating socks5 with Tor')
+ return 0
+
+ return content
+
+ def ssh_request(self):
+ pass
+
+ def ssl_request(self, address):
+ ''' initiate an ssl connection and return the server certificate '''
+
+ # drop the https:// prefix if present (not needed for a socket connection)
+ if address[:8] == 'https://':
+ address = address[8:]
+
+ # specify the context
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.set_verify_depth(1)
+
+ # ready the certificate request
+ request = crypto.X509Req()
+
+ # open an ssl connection
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ c = SSL.Connection(ctx, s)
+ c.set_connect_state()
+
+ try:
+ c.connect((address, 443))
+ c.send(crypto.dump_certificate_request(crypto.FILETYPE_PEM,request))
+ except socket.error, e:
+ plog('ERROR','An error occured while opening an ssl connection to ' + address)
+ plog('ERROR', e)
+ return 0
+ except (IndexError, TypeError):
+ plog('ERROR', 'An error occured while negotiating socks5 with Tor')
+ return 0
+
+ # return the cert
+ return c.get_peer_certificate()
+
+# some helpful methods
+
+def load_wordlist(file):
+ ''' load a list of strings from a file (which contains words separated by newlines) '''
+ plog('INFO', 'Loading the wordlist')
+
+ wordlist = []
+ fh = None
+ try:
+ fh = open(file, 'r')
+ except IOError, e:
+ plog('ERROR', 'Reading the wordlist file failed.')
+ plog('ERROR', e)
+
+ try:
+ for line in fh:
+ wordlist.append(line[:-1]) # get rid of the linebreaks
+ finally:
+ fh.close()
+
+ return wordlist
+
+def get_urls(wordlist, filetypes=['any'], results_per_type=5, protocol='any', g_results_per_page=10):
+ '''
+ construct a list of urls based on the wordlist, filetypes and protocol.
+
+ Note: since we currently use google, which doesn't index by protocol,
+ searches for anything but 'any' could be rather slow
+ '''
+ plog('INFO', 'Searching google for relevant sites...')
+
+ urllist = []
+ for filetype in filetypes:
+ type_urls = []
+
+ while len(type_urls) < results_per_type:
+ query = random.choice(wordlist)
+ if filetype != 'any':
+ query += ' filetype:' + filetype
+ if protocol != 'any':
+ query += ' allinurl:' + protocol # this isn't too reliable, but we'll re-filter results later
+ #query += '&num=' + `g_results_per_page`
+
+ # search google for relevant pages
+ # note: google only accepts requests from idenitified browsers
+ # TODO gracefully handle the case when google doesn't want to give us result anymore
+ host = 'www.google.com'
+ params = urllib.urlencode({'q' : query})
+ headers = {'User-Agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.8.1) Gecko/20061010 Firefox/2.0'}
+ search_path = '/search' + '?' + params
+
+ connection = None
+ response = None
+
+ try:
+ connection = httplib.HTTPConnection(host)
+ connection.request("GET", search_path, {}, headers)
+ response = connection.getresponse()
+ if response.status != 200:
+ raise Exception(response.status, response.reason)
+ except socket.gaierror, e:
+ plog('ERROR', 'Connection to google.com failed')
+ plog('ERROR', e)
+ return list(Set(urllist))
+
+ content = response.read()
+ links = SoupStrainer('a')
+ soup = BeautifulSoup(content, parseOnlyThese=links)
+
+ # get the links and do some additional filtering
+ for link in soup.findAll('a', {'class' : 'l'}):
+ url = link['href']
+ if (protocol != 'any' and url[:len(protocol)] != protocol or
+ filetype != 'any' and url[-len(filetype):] != filetype):
+ pass
+ else:
+ type_urls.append(link['href'])
+
+ if type_urls > results_per_type:
+ type_urls = random.sample(type_urls, results_per_type) # make sure we don't get more urls than needed
+ urllist.extend(type_urls)
+
+ return list(Set(urllist))
+
+def tor_resolve(address):
+ ''' performs a DNS query explicitly via tor '''
+ return commands.getoutput("tor-resolve " + address)
+
+def int2bin(n):
+ '''
+ simple decimal -> binary conversion, needed for comparing IP addresses
+ '''
+ n = int(n)
+ if n < 0:
+ raise ValueError, "Negative values are not accepted."
+ elif n == 0:
+ return '0'
+ else:
+ bin = ''
+ while n > 0:
+ bin += str(n % 2)
+ n = n >> 1
+ return bin[::-1]
+#
+# main logic
+#
+def main(argv):
+ # make sure we have something to test for
+ if len(argv) < 2:
+ print ''
+ print 'Please provide at least one test option:'
+ print '--ssl (~works)'
+ print '--http (gives some false positives)'
+ print '--ssh (doesn\'t work yet)'
+ print '--smtp (~works)'
+ print '--pop (~works)'
+ print '--imap (~works)'
+ print '--dns (a basic test, not really reliable)'
+ print '--dnsrebind (works with the ssl test)'
+ print '--policies (~works)'
+ print ''
+ sys.exit(0)
+
+ opts = ['ssl','http','ssh','smtp','pop','imap','dns','dnsrebind','policies']
+ flags, trailer = getopt.getopt(argv[1:], [], opts)
+
+ # get specific test types
+ do_ssl = ('--ssl','') in flags
+ do_http = ('--http','') in flags
+ do_ssh = ('--ssh','') in flags
+ do_smtp = ('--smtp','') in flags
+ do_pop = ('--pop','') in flags
+ do_imap = ('--imap','') in flags
+ do_dns_basic = ('--dns','') in flags
+ do_dns_rebind = ('--dnsrebind','') in flags
+ do_consistency = ('--policies','') in flags
+
+ # load the wordlist to search for sites lates on
+ wordlist = load_wordlist(wordlist_file)
+
+ # initiate the scanner
+ scanner = ExitNodeScanner()
+
+ # initiate the passive dns rebind attack monitor
+ if do_dns_rebind:
+ scanner.check_dns_rebind()
+
+ # check for sketchy exit policies
+ if do_consistency:
+ scanner.check_all_exits_port_consistency()
+
+ # maybe only the consistency test was required
+ if not (do_ssl or do_http or do_ssh or do_smtp or do_pop or do_imap or do_dns_basic):
+ plog('INFO', 'Done.')
+ sys.exit(0)
+
+ # declare some variables and assign values if neccessary
+ ssl_nodes = http_nodes = ssh_nodes = smtp_nodes = pop_nodes = imap_nodes = dns_nodes = []
+ ssl_nodes_n = http_nodes_n = ssh_nodes_n = smtp_nodes_n = pop_nodes_n = imap_nodes_n = dns_nodes_n = 0
+ ssl_urls = http_urls = ssh_urls = smtp_urls = pop_urls = imap_urls = dns_urls = []
+ ssl_fail = http_fail = ssh_fail = smtp_fail = pop_fail = imap_fail = imap_urls = 0
+
+ if do_ssl:
+ ssl_nodes = scanner.get_nodes_for_port(443)
+ ssl_nodes_n = len(ssl_nodes)
+ # the search for https urls is yet too slow
+ ssl_urls = ['https://mail.google.com', 'https://addons.mozilla.org', 'https://www.fastmail.fm']
+ ssl_fail = len(scanner.ssl_fail)
+
+ if len(ssl_urls) == 0:
+ plog('ERROR', 'No urls specified for ssl testing.')
+ do_ssl = False
+
+ if do_http:
+ http_nodes = scanner.get_nodes_for_port(80)
+ http_nodes_n = len(http_nodes)
+ http_urls = get_urls(wordlist, protocol='http', results_per_type=10, g_results_per_page=20)
+ http_fail = len(scanner.http_fail)
+
+ if len(http_urls) == 0:
+ plog('ERROR', 'No urls specified for http testing.')
+ do_http = False
+
+ if do_ssh:
+ ssh_nodes = scanner.get_nodes_for_port(22)
+ ssh_nodes_n = len(ssh_nodes)
+ ssh_urls = []
+ ssh_fail = len(scanner.ssh_fail)
+
+ if len(ssl_urls) == 0:
+ plog('ERROR', 'No urls specified for ssh testing.')
+ do_ssh = False
+
+ if do_smtp:
+ smtp_urls = [('smtp.gmail.com','587')]
+
+ if len(smtp_urls) == 0:
+ plog('ERROR', 'No urls specified for smtp testing.')
+ do_smtp = False
+
+ if do_pop:
+ pop_urls = []
+
+ if len(pop_urls) == 0:
+ plog('ERROR', 'No urls specified for pop testing.')
+ do_pop = False
+
+ if do_imap:
+ imap_urls = []
+
+ if len(imap_urls) == 0:
+ plog('ERROR', 'No urls specified for imap testing.')
+ do_imap = False
+
+ if do_dns_basic:
+ dns_urls = []
+
+ if len(dns_urls) == 0:
+ plog('ERROR', 'No urls specified for dns testing.')
+ do_dns_basic = False
+
+ # maybe no tests could be initialized
+ if not (do_ssl or do_http or do_ssh or do_smtp or do_pop or do_imap or do_dns_basic):
+ plog('INFO', 'Done.')
+ sys.exit(0)
+
+ # start testing
+ while 1:
+
+ # https test
+ if do_ssl:
+ candidates = [x for x in ssl_nodes if ('$' + `x.idhex`) not in scanner.ssl_tested]
+ if len(candidates) > 0:
+ current_exit = random.choice(candidates)
+ scanner.set_new_exit(current_exit.idhex)
+
+ scanner.get_new_circuit()
+ ssl_site = random.choice(ssl_urls)
+ scanner.check_openssl(ssl_site)
+
+ ssl_tested_n = len(scanner.ssl_tested)
+ if ssl_nodes_n > ssl_tested_n:
+ plog('INFO', 'Nodes ssl-tested: ' + `ssl_tested_n` + '/' + `ssl_nodes_n`
+ + ' (~' + `((ssl_tested_n * 100) / ssl_nodes_n)` + '%)')
+
+ # http test
+ if do_http:
+ candidates = [x for x in http_nodes if ('$' + `x.idhex`) not in scanner.http_tested]
+ if len(candidates) > 0 :
+ current_exit = random.choice(candidates)
+ scanner.set_new_exit(current_exit.idhex)
+
+ scanner.get_new_circuit()
+ http_site = random.choice(http_urls)
+ scanner.check_http(http_site)
+
+ http_tested_n = len(scanner.http_tested)
+ if http_nodes_n > http_tested_n:
+ plog('INFO', 'Nodes http-tested: ' + `http_tested_n` + '/' + `http_nodes_n`
+ + ' (~' + `((http_tested_n * 100) / http_nodes_n)` + '%)')
+
+ # ssh test
+ if do_ssh:
+ candidates = [x for x in ssh_nodes if ('$' + `x.idhex`) not in scanner.ssh_tested]
+ if len(candidates) > 0:
+ current_exit = random.choice(candidates)
+ scanner.set_new_exit(current_exit.idhex)
+
+ scanner.get_new_circuit()
+ ssh_site = random.choice(ssh_urls)
+ scanner.check_ssh(ssh_site)
+
+ ssh_tested_n = len(scanner.ssh_tested)
+ if ssh_nodes_n > ssh_tested_n:
+ plog('INFO', 'Nodes ssh-tested: ' + `ssh_tested_n` + '/' + `ssh_nodes_n`
+ + '(~' + `((ssh_tested_n * 100) / ssh_nodes_n)` + '%')
+
+ # smtp test
+ if do_smtp:
+ scanner.get_new_circuit()
+ smtp_site = random.choice(smtp_urls)
+ scanner.check_smtp(smtp_site[0], smtp_site[1])
+
+ # pop test
+ if do_pop:
+ scanner.get_new_circuit()
+ pop_site = random.choice(pop_urls)
+ scanner.check_pop(pop_site[0], pop_site[1])
+
+ # imap test
+ if do_imap:
+ scanner.get_new_circuit()
+ imap_site = random.choice(imap_urls)
+ scanner.check_imap(imap_site[0], imap_site[1])
+
+ #
+ # managing url lists
+ # if we've been having too many false positives lately, get a new target list
+ #
+
+ if do_http and len(scanner.http_fail) - http_fail >= len(http_urls):
+ http_urls = get_urls(wordlist, protocol='http', results_per_type=10, g_results_per_page=20)
+ http_fail = len(scanner.http_fail)
+
+#
+# initiate the program
+#
+if __name__ == '__main__':
+ try:
+ main(sys.argv)
+ except KeyboardInterrupt:
+ plog('INFO', "Ctrl + C was pressed. Exiting ... ")
+ except Exception, e:
+ plog('ERROR', "An unexpected error occured.")
+ plog('ERROR', e)
Copied: torflow/trunk/NetworkScanners/soatstats.py (from rev 17873, torflow/trunk/soatstats.py)
===================================================================
--- torflow/trunk/NetworkScanners/soatstats.py (rev 0)
+++ torflow/trunk/NetworkScanners/soatstats.py 2009-01-05 16:51:38 UTC (rev 17915)
@@ -0,0 +1,598 @@
+#!/usr/bin/python
+#
+# 2008 Aleksei Gorny, mentored by Mike Perry
+
+import dircache
+import operator
+import os
+import pickle
+import sys
+import time
+
+import sets
+from sets import Set
+
+#
+# Data storage
+#
+
+# data locations
+
+data_dir = './data/soat/'
+ssl_certs_dir = data_dir + 'ssl/certs/'
+http_tags_dir = data_dir + 'http/tags/'
+
+# constants
+
+TEST_SUCCESS = 0
+TEST_INCONCLUSIVE = 1
+TEST_FAILURE = 2
+
+# classes to use with pickle to dump test results into files
+
+class TestResult(object):
+ ''' Parent class for all test result classes '''
+ def __init__(self, exit_node, site, status):
+ self.exit_node = exit_node
+ self.site = site
+ self.timestamp = time.time()
+ self.status = status
+
+class SSLTestResult(TestResult):
+ ''' Represents the result of an openssl test '''
+ def __init__(self, exit_node, ssl_site, cert_file, status):
+ super(SSLTestResult, self).__init__(exit_node, ssl_site, status)
+ self.cert = cert_file
+
+class HttpTestResult(TestResult):
+ ''' Represents the result of a http test '''
+ def __init__(self, exit_node, website, tag_prints, status):
+ super(HttpTestResult, self).__init__(exit_node, website, status)
+ self.tag_prints = tag_prints
+
+class SSHTestResult(TestResult):
+ ''' Represents the result of an ssh test '''
+ def __init__(self, exit_node, ssh_site, status):
+ super(SSHTestResult, self).__init__(exit_node, ssh_site, status)
+
+class DNSTestResult(TestResult):
+ ''' Represents the result of a dns test '''
+ def __init__(self, exit_node, dns_site, status):
+ super(DNSTestResult, self).__init__(exit_node, dns_site, status)
+
+class DNSRebindTestResult(TestResult):
+ ''' Represents the result of a dns rebind test '''
+ def __init__(self, exit_node, dns_rebind_site, status):
+ super(DNSRebindTestResult, self).__init__(exit_node, dns_rebind_site, status)
+
+class SMTPTestResult(TestResult):
+ ''' Represents the result of an smtp test '''
+ def __init__(self, exit_node, smtp_site, status):
+ super(SMTPTestResult, self).__init__(exit_node, smtp_site, status)
+
+class IMAPTestResult(TestResult):
+ ''' Represents the result of an imap test '''
+ def __init__(self, exit_node, imap_site, status):
+ super(IMAPTestResult, self).__init__(exit_node, imap_site, status)
+
+class POPTestResult(TestResult):
+ ''' Represents the result of a pop test '''
+ def __init__(self, exit_node, pop_site, status):
+ super(POPTestResult, self).__init__(exit_node, pop_site, status)
+
+class DataHandler:
+ ''' Class for saving and managing test result data '''
+ def filterResults(self, results, protocols=[], show_good=False,
+ show_bad=False, show_inconclusive=False):
+ ''' filter results based on protocol and success level '''
+
+ protocol_filters = []
+ status_filters = []
+
+ for protocol in protocols:
+ protocol_filters.append(lambda x, p=protocol: x.__class__.__name__.lower()[:-10].endswith(p))
+ if show_good:
+ status_filters.append(lambda x: x.status == TEST_SUCCESS)
+ if show_bad:
+ status_filters.append(lambda x: x.status == TEST_FAILURE)
+ if show_inconclusive:
+ status_filters.append(lambda x: x.status == TEST_INCONCLUSIVE)
+
+ if len(protocol_filters) == 0 or len(status_filters) == 0:
+ return []
+
+ protocol_filter = lambda x: reduce(operator.__or__, [f(x) for f in protocol_filters])
+ status_filter = lambda x: reduce(operator.__or__, [f(x) for f in status_filters])
+
+ return [x for x in results if (protocol_filter(x) and status_filter(x))]
+
+ def filterByNode(self, results, id):
+ ''' filter by node'''
+ return filter(lambda x: x.exit_node == id, results)
+
+ def getAll(self):
+ ''' get all available results'''
+ return self.__getResults(data_dir)
+
+ def getSsh(self):
+ ''' get results of ssh tests '''
+ return self.__getResults(data_dir + 'ssh/')
+
+ def getHttp(self):
+ ''' get results of http tests '''
+ return self.__getResults(data_dir + 'http/')
+
+ def getSsl(self):
+ ''' get results of ssl tests '''
+ return self.__getResults(data_dir + 'ssl/')
+
+ def getSmtp(self):
+ ''' get results of smtp tests '''
+ return self.__getResults(data_dir + 'smtp/')
+
+ def getPop(self):
+ ''' get results of pop tests '''
+ return self.__getResults(data_dir + 'pop/')
+
+ def getImap(self):
+ ''' get results of imap tests '''
+ return self.__getResults(data_dir + 'imap/')
+
+ def getDns(self):
+ ''' get results of basic dns tests '''
+ return self.__getResults(data_dir + 'dns')
+
+ def getDnsRebind(self):
+ ''' get results of dns rebind tests '''
+ return self.__getResults(data_dir + 'dnsbrebind/')
+
+ def __getResults(self, dir):
+ '''
+ recursively traverse the directory tree starting with dir
+ gather test results from files ending with .result
+ '''
+ results = []
+
+ for root, dirs, files in os.walk(dir):
+ for file in files:
+ if file.endswith('result'):
+ fh = open(os.path.join(root, file))
+ result = pickle.load(fh)
+ results.append(result)
+
+ return results
+
+ def safeFilename(self, str):
+ '''
+ remove characters illegal in some systems
+ and trim the string to a reasonable length
+ '''
+ replaced = (str.replace('/','_').replace('\\','_').replace('?','_').replace(':','_').
+ replace('|','_').replace('*','_').replace('<','_').replace('>','_').replace('"',''))
+ return replaced[:200]
+
+ def saveResult(self, result):
+ ''' generic method for saving test results '''
+ address = ''
+ if result.__class__.__name__ == 'HttpTestResult':
+ address = self.safeFilename(result.site[7:])
+ elif result.__class__.__name__ == 'SSLTestResult':
+ address = self.safeFilename(result.site[8:])
+ elif 'TestResult' in result.__class__.__name__:
+ address = self.safeFilename(result.site)
+ else:
+ raise Exception, 'This doesn\'t seems to be a result instance.'
+
+ dir = data_dir + result.__class__.__name__[:-10].lower() + '/'
+ if result.status == TEST_SUCCESS:
+ dir += 'successful/'
+ if result.status == TEST_INCONCLUSIVE:
+ dir += 'inconclusive/'
+ if result.status == TEST_FAILURE:
+ dir += 'failed/'
+
+ result_file = open(dir + `result.exit_node` + address + '.result', 'w')
+ pickle.dump(result, result_file)
+ result_file.close()
+
+#
+# Displaying stats on the console
+#
+
+class StatsConsole:
+ ''' Class to display statistics from CLI'''
+
+ def Listen(self):
+ while 1:
+ input = raw_input(">>>")
+ if input == 'e' or input == 'exit':
+ exit()
+ elif input == 's' or input == 'summary':
+ self.Summary()
+ elif input == 'h' or input == 'help' or len(input) > 6:
+ self.Help()
+ else:
+ self.Reply(input)
+
+ def Summary(self):
+ dh = DataHandler()
+ data = dh.getAll()
+
+ nodeSet = Set([])
+ sshSet = Set([])
+ sslSet = Set([])
+ httpSet = Set([])
+ smtpSet = Set([])
+ popSet = Set([])
+ imapSet = Set([])
+ dnsSet = Set([])
+ dnsrebindSet = Set([])
+
+ total = len(data)
+ good = bad = inconclusive = 0
+ ssh = http = ssl = pop = imap = smtp = dns = dnsrebind = 0
+
+ for result in data:
+ nodeSet.add(result.exit_node)
+
+ if result.status == 0:
+ good += 1
+ elif result.status == 1:
+ inconclusive += 1
+ elif result.status == 2:
+ bad += 1
+
+ if result.__class__.__name__ == 'SSHTestResult':
+ sshSet.add(result.exit_node)
+ ssh += 1
+ elif result.__class__.__name__ == 'HttpTestResult':
+ httpSet.add(result.exit_node)
+ http += 1
+ elif result.__class__.__name__ == 'SSLTestResult':
+ sslSet.add(result.exit_node)
+ ssl += 1
+ elif result.__class__.__name__ == 'IMAPTestResult':
+ imapSet.add(result.exit_node)
+ imap += 1
+ elif result.__class__.__name__ == 'POPTestResult':
+ popSet.add(result.exit_node)
+ pop += 1
+ elif result.__class__.__name__ == 'SMTPTestResult':
+ smtpSet.add(result.exit_node)
+ smtp += 1
+ elif result.__class__.__name__ == 'DNSTestResult':
+ dnsSet.add(result.exit_node)
+ dns += 1
+ elif result.__class__.__name__ == 'DNSRebindTestResult':
+ dnsrebindSet.add(result.exit_node)
+ dnsrebind += 1
+
+ swidth = 25
+ nwidth = 10
+ width = swidth + nwidth
+
+ header_format = '%-*s%*s'
+ format = '%-*s%*i'
+
+ print '=' * width
+ print header_format % (swidth, 'Parameter', nwidth, 'Count')
+ print '-' * width
+
+ stats = [
+ ('Tests completed', total),
+ ('Nodes tested', len(nodeSet)),
+ ('Nodes SSL-tested', len(sslSet)),
+ ('Nodes HTTP-tested', len(httpSet)),
+ ('Nodes SSH-tested', len(sshSet)),
+ ('Nodes POP-tested', len(popSet)),
+ ('Nodes IMAP-tested', len(imapSet)),
+ ('Nodes SMTP-tested', len(smtpSet)),
+ ('Nodes DNS-tested', len(dnsSet)),
+ ('Nodes DNSRebind-tested', len(dnsrebindSet)),
+ ('Failed tests', bad),
+ ('Succeeded tests', good),
+ ('Inconclusive tests', inconclusive),
+ ('SSH tests', ssh),
+ ('HTTP tests', http),
+ ('SSL tests', ssl),
+ ('POP tests', pop),
+ ('IMAP tests', imap),
+ ('SMTP tests', smtp),
+ ('DNS tests', dns),
+ ('DNS rebind tests', dnsrebind)
+ ]
+
+ for (k,v) in stats:
+ print format % (swidth, k, nwidth, v)
+ print '=' * width
+
+ def Reply(self, input):
+
+ good = bad = inconclusive = False
+ protocols = []
+
+ if 'a' in input:
+ good = bad = inconclusive = True
+ protocols.extend(["ssh", "http", "ssl", "imap", "pop", "smtp"])
+ else:
+ good = 'g' in input
+ bad = 'b' in input
+ inconclusive = 'i' in input
+
+ if 's' in input:
+ protocols.append("ssh")
+ if 'h' in input:
+ protocols.append("http")
+ if 'l' in input:
+ protocols.append("ssl")
+ if 'p' in input:
+ protocols.append("imap")
+ if 'o' in input:
+ protocols.append("pop")
+ if 't' in input:
+ protocols.append("smtp")
+ if 'd' in input:
+ protocols.append("dns")
+ if 'r' in input:
+ protocols.append("dnsrebind")
+
+ dh = DataHandler()
+ data = dh.getAll()
+ filtered = dh.filterResults(data, protocols, good, bad, inconclusive)
+
+ nodewidth = 45
+ typewidth = 10
+ sitewidth = 30
+ timewidth = 30
+ statuswidth = 6
+ width = nodewidth + typewidth + sitewidth + timewidth + statuswidth
+
+ format = '%-*s%-*s%-*s%-*s%-*s'
+
+ print '=' * width
+ print format % (nodewidth, 'Exit node', typewidth, 'Test type', sitewidth, 'Remote site',
+ timewidth, 'Time', statuswidth, 'Status')
+ print '-' * width
+ for result in filtered:
+ print format % (nodewidth, `result.exit_node`,
+ typewidth, result.__class__.__name__[:-10],
+ sitewidth, result.site,
+ timewidth, time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime(result.timestamp)),
+ statuswidth, `result.status`)
+ print '=' * width
+
+ def Help(self):
+ print ''
+ print 'Options:'
+ print '* summmary (s) - display a short summary about all tests done so far'
+ print '* exit (e) - terminate the program'
+ print '* help (h) - display this help text'
+ print '* all (a) - list all the results'
+ print '* (shlgbi) - display a filtered list of test results. Letters are optional and mean the following:'
+ print ' s - show ssh results'
+ print ' h - show http results'
+ print ' l - show ssl results'
+ print ' g - show good results'
+ print ' b - show bad results'
+ print ' i - show inconclusive results'
+ print ' p - show imap results'
+ print ' o - show pop results'
+ print ' t - show smtp results'
+ print ' d - show dns results'
+ print ' r - show dnsrebind results'
+ print ''
+
+#
+# Displaying stats in a graphical setting (first check if we have wx)
+#
+
+nowx = False
+try:
+ import wx
+ from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin, ColumnSorterMixin
+except:
+ nowx = True
+
+if not nowx:
+
+ class ListMixin(wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin):
+ def __init__(self, parent, map):
+ wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT)
+ ListCtrlAutoWidthMixin.__init__(self)
+ ColumnSorterMixin.__init__(self, len(map))
+ self.itemDataMap = map
+
+ def GetListCtrl(self):
+ return self
+
+ # menu item ids
+ ID_EXIT = 1
+
+ ID_SHOW_GOOD = 11
+ ID_SHOW_BAD = 12
+ ID_SHOW_UNSURE = 13
+
+ ID_SHOW_SSL = 21
+ ID_SHOW_HTTP = 22
+ ID_SHOW_SSH = 23
+ ID_SHOW_SMTP = 24
+ ID_SHOW_IMAP = 25
+ ID_SHOW_POP = 26
+ ID_SHOW_DNS = 27
+ ID_SHOW_DNSREBIND = 28
+
+ ID_NODE = 31
+
+ class MainFrame(wx.Frame):
+ ''' the main application window for displaying statistics with a GUI'''
+ def __init__(self):
+ wx.Frame.__init__(self, None, title="Soat test results", size=(900,500))
+
+ # get the data
+
+ self.dataHandler = DataHandler()
+ self.dataList = self.dataHandler.getAll()
+ self.filteredList = self.dataList
+
+ # display it
+
+ self.CreateStatusBar()
+ self.initMenuBar()
+ self.initContent()
+
+ self.Center()
+ self.Show()
+
+ def initMenuBar(self):
+ fileMenu = wx.Menu()
+ fileMenu.Append(ID_EXIT, "E&xit", "Exit the program")
+
+ viewMenu = wx.Menu()
+ self.showGood = viewMenu.Append(ID_SHOW_GOOD, 'Show &Good', 'Show sucessful test results', kind=wx.ITEM_CHECK)
+ self.showBad = viewMenu.Append(ID_SHOW_BAD, 'Show &Bad', 'Show unsucessful test results', kind=wx.ITEM_CHECK)
+ self.showUnsure = viewMenu.Append(ID_SHOW_UNSURE, 'Show &Inconclusive', 'Show inconclusive test results', kind=wx.ITEM_CHECK)
+ viewMenu.AppendSeparator()
+ self.showSSL = viewMenu.Append(ID_SHOW_SSL, 'Show SS&L', 'Show SSL test results', kind=wx.ITEM_CHECK)
+ self.showHTTP = viewMenu.Append(ID_SHOW_HTTP, 'Show &HTTP', 'Show HTTP test results', kind=wx.ITEM_CHECK)
+ self.showSSH = viewMenu.Append(ID_SHOW_SSH, 'Show &SSH', 'Show SSH test results', kind=wx.ITEM_CHECK)
+ viewMenu.AppendSeparator()
+ self.showSMTP = viewMenu.Append(ID_SHOW_SMTP, 'Show SMTP', 'Show SMTP test results', kind=wx.ITEM_CHECK)
+ self.showIMAP = viewMenu.Append(ID_SHOW_IMAP, 'Show IMAP', 'Show IMAP test results', kind=wx.ITEM_CHECK)
+ self.showPOP = viewMenu.Append(ID_SHOW_POP, 'Show POP', 'Show POP test results', kind=wx.ITEM_CHECK)
+ viewMenu.AppendSeparator()
+ self.showDNS = viewMenu.Append(ID_SHOW_DNS, 'Show DNS', 'Show DNS test results', kind=wx.ITEM_CHECK)
+ self.showDNSRebind = viewMenu.Append(ID_SHOW_DNSREBIND, 'Show DNSRebind', 'Show DNS rebind test results', kind=wx.ITEM_CHECK)
+ viewMenu.AppendSeparator()
+ viewMenu.Append(ID_NODE, '&Find node...', 'View test results for a given node [NOT IMPLEMENTED]')
+
+ menuBar = wx.MenuBar()
+ menuBar.Append(fileMenu,"&File")
+ menuBar.Append(viewMenu,"&View")
+
+ self.SetMenuBar(menuBar)
+
+ wx.EVT_MENU(self, ID_EXIT, self.OnExit)
+
+ wx.EVT_MENU(self, ID_SHOW_GOOD, self.GenerateFilteredList)
+ wx.EVT_MENU(self, ID_SHOW_BAD, self.GenerateFilteredList)
+ wx.EVT_MENU(self, ID_SHOW_UNSURE, self.GenerateFilteredList)
+ viewMenu.Check(ID_SHOW_GOOD, True)
+ viewMenu.Check(ID_SHOW_BAD, True)
+ viewMenu.Check(ID_SHOW_UNSURE, True)
+
+ for i in range(ID_SHOW_SSL, ID_SHOW_DNSREBIND + 1):
+ viewMenu.Check(i, True)
+ wx.EVT_MENU(self, i, self.GenerateFilteredList)
+
+ def initContent(self):
+ base = wx.Panel(self, -1)
+ sizer = wx.GridBagSizer(0,0)
+
+ box = wx.StaticBox(base, -1, 'Summary')
+ boxSizer = wx.StaticBoxSizer(box, wx.HORIZONTAL)
+
+ total = wx.StaticText(base, -1, 'Total tests: ' + `len(self.filteredList)`)
+ boxSizer.Add(total, 0, wx.LEFT | wx.TOP | wx.BOTTOM, 10)
+
+ nodes = wx.StaticText(base, -1, 'Nodes scanned: ' + `len(Set([x.exit_node for x in self.filteredList]))`)
+ boxSizer.Add(nodes, 0, wx.LEFT | wx.TOP | wx.BOTTOM , 10)
+
+ bad = wx.StaticText(base, -1, 'Failed tests: ' + `len([x for x in self.filteredList if x.status == 2])`)
+ boxSizer.Add(bad, 0, wx.LEFT | wx.TOP | wx.BOTTOM, 10)
+
+ suspicious = wx.StaticText(base, -1, 'Inconclusive tests: ' + `len([x for x in self.filteredList if x.status == 1])`)
+ boxSizer.Add(suspicious, 0, wx.ALL, 10)
+
+ sizer.Add(boxSizer, (0,0), (1, 5), wx.EXPAND | wx.ALL, 15)
+
+ dataMap = {}
+ self.fillDataMap(dataMap)
+
+ self.listCtrl = ListMixin(base, dataMap)
+ self.listCtrl.InsertColumn(0, 'exit node', width=380)
+ self.listCtrl.InsertColumn(1, 'type', width=70)
+ self.listCtrl.InsertColumn(2, 'site', width=180)
+ self.listCtrl.InsertColumn(3, 'time', width=180)
+ self.listCtrl.InsertColumn(4, 'status', wx.LIST_FORMAT_CENTER, width=50)
+
+ self.fillListCtrl(dataMap)
+
+ sizer.Add(self.listCtrl, (1,0), (1,5), wx.EXPAND | wx.LEFT | wx.BOTTOM | wx.RIGHT, border=15)
+
+ sizer.AddGrowableCol(3)
+ sizer.AddGrowableRow(1)
+
+ base.SetSizerAndFit(sizer)
+
+ # make a nasty dictionary from the current self.filteredList object so columns would be sortable
+ def fillDataMap(self, dataMap):
+ for i in range(len(self.filteredList)):
+ dataMap.update([(i,(self.filteredList[i].exit_node,
+ self.filteredList[i].__class__.__name__[:-10],
+ self.filteredList[i].site,
+ time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime(self.filteredList[i].timestamp)),
+ self.filteredList[i].status))])
+
+ # fill the result listing with data
+ def fillListCtrl(self, dataMap):
+ if self.listCtrl.GetItemCount() > 0:
+ self.listCtrl.DeleteAllItems()
+
+ for k, i in dataMap.items():
+ index = self.listCtrl.InsertStringItem(sys.maxint, `i[0]`)
+ self.listCtrl.SetStringItem(index, 1, i[1])
+ self.listCtrl.SetStringItem(index, 2, `i[2]`)
+ self.listCtrl.SetStringItem(index, 3, i[3])
+ self.listCtrl.SetStringItem(index, 4, `i[4]`)
+ self.listCtrl.SetItemData(index,k)
+
+ def OnExit(self,e):
+ self.Close(True)
+
+ def GenerateFilteredList(self, e):
+ protocols = []
+ if self.showSSH.IsChecked():
+ protocols.append("ssh")
+ if self.showHTTP.IsChecked():
+ protocols.append("http")
+ if self.showSSL.IsChecked():
+ protocols.append("ssl")
+ if self.showIMAP.IsChecked():
+ protocols.append("imap")
+ if self.showPOP.IsChecked():
+ protocols.append("pop")
+ if self.showSMTP.IsChecked():
+ protocols.append("smtp")
+ if self.showDNS.IsChecked():
+ protocols.append("dns")
+ if self.showDNSRebind.IsChecked():
+ protocols.append("dnsrebind")
+
+ self.filteredList = list(self.dataHandler.filterResults(self.dataList, protocols,
+ self.showGood.IsChecked(), self.showBad.IsChecked(), self.showUnsure.IsChecked()))
+
+ dataMap = {}
+ self.fillDataMap(dataMap)
+ self.fillListCtrl(dataMap)
+ self.listCtrl.RefreshItems(0, len(dataMap))
+
+if __name__ == "__main__":
+ if len(sys.argv) == 1:
+ console = StatsConsole()
+ console.Listen()
+ elif len(sys.argv) == 2 and sys.argv[1] == 'wx':
+ if nowx:
+ print 'wxpython doesn\'t seem to be installed on your system'
+ print 'you can use the console interface instead (see help)'
+ else:
+ app = wx.App(0)
+ MainFrame()
+ app.MainLoop()
+ else:
+ print ''
+ print 'This app displays results of tests carried out by soat.py (in a user-friendly way).'
+ print ''
+ print 'Usage:'
+ print 'python soatstats.py - app starts console-only'
+ print 'python soatstats.py wx - app starts with a wxpython gui'
+ print ''
Copied: torflow/trunk/NetworkScanners/speedracer.pl (from rev 17881, torflow/trunk/speedracer.pl)
===================================================================
--- torflow/trunk/NetworkScanners/speedracer.pl (rev 0)
+++ torflow/trunk/NetworkScanners/speedracer.pl 2009-01-05 16:51:38 UTC (rev 17915)
@@ -0,0 +1,263 @@
+#!/usr/bin/perl -w
+
+
+use strict;
+use IO::Socket;
+use IO::Socket::INET;
+use Time::HiRes qw( usleep ualarm gettimeofday tv_interval );
+
+my $META_PORT = "9052";
+my $META_HOST = "127.0.0.1";
+
+my $USER_AGENT = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)";
+
+# http://bitter.stalin.se/torfile
+# http://www.sigma.su.se/~who/torfile
+my $URL = "https://svn.torproject.org/svn/tor/trunk/doc/design-paper/tor-design.pdf";
+my $COUNT = 2;
+my $START_PCT = 0;
+my $STOP_PCT = 20;
+my $PCT_STEP = 5;
+my $DOUBLE_FETCH = 0;
+my $CURL_PROXY="--socks4a 127.0.0.1:9060";
+
+my $LOG_LEVEL = "DEBUG";
+my %log_levels = ("DEBUG", 0, "INFO", 1, "NOTICE", 2, "WARN", 3, "ERROR", 4);
+
+
+sub plog
+{
+ my $level = shift;
+ my $msg = shift;
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
+
+ $year += 1900; # lame.
+ $mon += 1;
+
+ print "$level \[" . localtime() . "\]: " . $msg if($msg && $log_levels{$level} >= $log_levels{$LOG_LEVEL})
+ #print "$level\[$year-$mon-$mday $hour:$min:$sec\]: " . $msg if($log_levels{$level} >= $log_levels{$LOG_LEVEL})
+}
+
+sub is_in
+{
+ my $element = shift;
+ my $ary = shift;
+ my $is_there = 0;
+ foreach (@$ary) {
+ if ($_ eq $element) {
+ $is_there = 1;
+ last;
+ }
+ }
+
+ return $is_there;
+}
+
+sub compare_arrays {
+ my ($first, $second) = @_;
+ no warnings; # silence spurious -w undef complaints
+ return 0 unless @$first == @$second;
+ for (my $i = 0; $i < @$first; $i++) {
+ return 0 if $first->[$i] ne $second->[$i];
+ }
+ return 1;
+}
+
+sub query_exit
+{
+ my $mcp = shift;
+ my $line;
+ print $mcp "GETLASTEXIT\r\n";
+ $line = <$mcp>;
+ $line =~ /LASTEXIT=([\S]+)/;
+
+ return $1;
+}
+
+
+sub speedrace
+{
+ my $mcp = shift;
+ my $skip = shift;
+ my $pct = shift;
+ my @build_times;
+ my @fetch_times;
+ my $tot_fetch_time = 0;
+ my $tot_build_time = 0;
+ my $i = 0;
+ my $line;
+
+ # Weak new-nym
+ print $mcp "PERCENTSKIP $skip\r\n";
+ $line = <$mcp>;
+ die "Error setting percentskip: $line" if (not $line =~ /^250/);
+
+ print $mcp "PERCENTFAST $pct\r\n";
+ $line = <$mcp>;
+ die "Error setting percentfast: $line" if (not $line =~ /^250/);
+
+ # So this is a really big hack. Since metatroller builds circuits on
+ # the fly where as tor has a pool of pre-built circuits to use,
+ # we want to get it to build a circuit for us but not count
+ # that construction time. The way we do this is to issue
+ # a NEWNYM and then get the url TWICE.
+
+ while($#build_times+1 < $COUNT) {
+ my $t0;
+ my $delta_build;
+ my $delta_fetch;
+ my $fetch_exit;
+ my $build_exit;
+ my $ret;
+
+ print $mcp "NEWNYM\r\n";
+ $line = <$mcp>;
+ die "Error sending NEWNYM: $line" if (not $line =~ /^250/);
+
+ # Build the circuit...
+ do {
+ $i++;
+
+ $t0 = [gettimeofday()];
+ $ret =
+# system("tsocks wget -U \"$USER_AGENT\" \'$URL\' -O - 2>&1 > /dev/null");
+ system("curl $CURL_PROXY -m 600 -A \"$USER_AGENT\" \'$URL\' >& /dev/null");
+
+ if($ret == 2) {
+ plog "NOTICE", "wget got Sigint. Dying\n";
+ exit;
+ }
+ plog "NOTICE", "wget failed with ret=$ret.. Retrying...\n"
+ if($ret != 0);
+ $delta_build = tv_interval $t0;
+ plog "NOTICE", "Timer exceeded limit: $delta_build\n"
+ if($delta_build >= 550.0);
+ } while(0);
+# } while($ret != 0 || $delta_build >= 550.0);
+
+ $build_exit = query_exit($mcp);
+ $fetch_exit = $build_exit;
+
+ plog "DEBUG", "Got 1st via $build_exit\n";
+
+ # Now do it for real
+ if($DOUBLE_FETCH) {
+ do {
+ $i++;
+ $t0 = [gettimeofday()];
+ $ret =
+# system("tsocks wget -U \"$USER_AGENT\" \'$URL\' -O - 2>&1 > /dev/null");
+ system("curl $CURL_PROXY -m 600 -A \"$USER_AGENT\" \'$URL\' >& /dev/null");
+
+ if($ret == 2) {
+ plog "NOTICE", "wget got Sigint. Dying\n";
+ exit;
+ }
+ plog "NOTICE", "wget failed with ret=$ret.. Retrying with clock still running\n"
+ if($ret != 0);
+ $delta_fetch = tv_interval $t0;
+ plog "NOTICE", "Timer exceeded limit: $delta_fetch\n"
+ if($delta_fetch >= 550.0);
+ } while($ret != 0 || $delta_fetch >= 550.0);
+
+ $fetch_exit = query_exit($mcp);
+
+ if($fetch_exit eq $build_exit) {
+ $tot_build_time += $delta_build;
+ push(@build_times, $delta_build);
+ plog "DEBUG", "$skip-$pct% circuit build+fetch took $delta_build for $fetch_exit\n";
+
+ push(@fetch_times, $delta_fetch);
+ $tot_fetch_time += $delta_fetch;
+ plog "DEBUG", "$skip-$pct% fetch took $delta_fetch for $fetch_exit\n";
+ } else {
+ plog "NOTICE", "Ignoring strange exit swap $build_exit -> $fetch_exit. Circuit failure?\n";
+ }
+ } else {
+ $tot_build_time += $delta_build;
+ push(@build_times, $delta_build);
+ plog "DEBUG", "$skip-$pct% circuit build+fetch took $delta_build for $fetch_exit\n";
+ }
+ }
+ my $avg_build_time = $tot_build_time/($#build_times+1);
+ my $build_dev = 0;
+ foreach(@build_times) {
+ $build_dev +=
+ ($_ - $avg_build_time)*($_ - $avg_build_time);
+ }
+ $build_dev = sqrt($build_dev / ($#build_times+1));
+
+ if($DOUBLE_FETCH) {
+ my $avg_fetch_time = $tot_fetch_time/($#fetch_times+1);
+ my $fetch_dev = 0;
+ foreach(@fetch_times) {
+ $fetch_dev +=
+ ($_ - $avg_fetch_time)*($_ - $avg_fetch_time);
+ }
+ $fetch_dev = sqrt($fetch_dev / ($#fetch_times+1));
+ plog "INFO", "RANGE $skip-$pct " . ($#fetch_times+1) . " fetches: avg=$avg_fetch_time, dev=$fetch_dev\n";
+ }
+ plog "INFO", "RANGE $skip-$pct " . ($#build_times+1) . " build+fetches: avg=$avg_build_time, dev=$build_dev\n";
+ plog "INFO", " " . ($COUNT*($DOUBLE_FETCH+1)) . " fetches took $i tries\n";
+}
+
+sub main
+{
+ my $mcp = IO::Socket::INET->new(
+ Proto => "tcp",
+ PeerAddr => $META_HOST,
+ PeerPort => $META_PORT)
+ or die "The Metatroller is not enabled";
+ my $line = <$mcp>;
+ $line = <$mcp>;
+
+ delete $ENV{"http_proxy"};
+ delete $ENV{"HTTP_PROXY"};
+ delete $ENV{"proxy"};
+ delete $ENV{"PROXY"};
+ $ENV{"TSOCKS_CONF_FILE"} = "./tsocks.conf";
+
+ print $mcp "GUARDNODES 0\r\n";
+ $line = <$mcp>;
+ die "Error setting Guard Nodes: $line" if (not $line =~ /^250/);
+
+ print $mcp "UNIFORM 1\r\n";
+ $line = <$mcp>;
+ die "Error setting UNIFORM: $line" if (not $line =~ /^250/);
+
+ print $mcp "ORDEREXITS 1\r\n";
+ $line = <$mcp>;
+ die "Error setting ORDEREXITS: $line" if (not $line =~ /^250/);
+
+ print $mcp "PATHLEN 2\r\n";
+ $line = <$mcp>;
+ die "Error setting PATHLEN: $line" if (not $line =~ /^250/);
+
+ my $pct = $START_PCT;
+ plog "INFO", "Beginning time loop\n";
+
+ while($pct < $STOP_PCT) {
+ print $mcp "RESETSTATS\r\n";
+ $line = <$mcp>;
+ die "Error on RESETSTATS: $line" if (not $line =~ /^250/);
+ print $mcp "COMMIT\r\n";
+ $line = <$mcp>;
+ die "Error on COMMIT: $line" if (not $line =~ /^250/);
+ plog "DEBUG", "Reset stats\n";
+ speedrace($mcp, $pct, $pct+$PCT_STEP);
+ plog "DEBUG", "speedroced\n";
+ print $mcp "CLOSEALLCIRCS\r\n";
+ $line = <$mcp>;
+ die "Error on CLOSEALLCIRCS: $line" if (not $line =~ /^250/);
+ print $mcp "SAVESTATS ./data/speedraces/stats-$pct:".($pct+$PCT_STEP)."\r\n";
+ $line = <$mcp>;
+ die "Error on SAVESTATS: $line" if (not $line =~ /^250/);
+ plog "DEBUG", "Wrote stats\n";
+ $pct += $PCT_STEP;
+ print $mcp "COMMIT\r\n";
+ $line = <$mcp>;
+ die "Error on COMMIT: $line" if (not $line =~ /^250/);
+ }
+}
+
+main();
Copied: torflow/trunk/NetworkScanners/wordlist.txt (from rev 17873, torflow/trunk/wordlist.txt)
===================================================================
--- torflow/trunk/NetworkScanners/wordlist.txt (rev 0)
+++ torflow/trunk/NetworkScanners/wordlist.txt 2009-01-05 16:51:38 UTC (rev 17915)
@@ -0,0 +1,30 @@
+document
+important
+download
+setup
+install
+plugin
+file
+program
+run
+microsoft
+windows
+xp
+installer
+slides
+presentation
+paper
+browser
+winscp
+vidalia+bundle
+putty
+torpark
+firefox+setup
+mozilla
+privoxy
+privacy
+extension
+firefox+extension
+example
+sample
+censorship
Deleted: torflow/trunk/soat.py
===================================================================
--- torflow/trunk/soat.py 2009-01-05 16:27:59 UTC (rev 17914)
+++ torflow/trunk/soat.py 2009-01-05 16:51:38 UTC (rev 17915)
@@ -1,1366 +0,0 @@
-#!/usr/bin/python
-#
-# 2008 Aleksei Gorny, mentored by Mike Perry
-
-'''
-Snakes on a Tor exit node scanner
-
-The SoaT scanner checks whether exit nodes behave by initiating connections
-to semi-randomly chosen targets using several protocols (http, https, ssh, smtp, imap, etc)
-and comparing content received directly and via tor.
-
-It interacts with metatroller and the control port to be aware of the tor network status.
-
-To run SoaT:
-1) make sure you have py-openssl packages installed (see README)
-2) open Tor control port in the torrc
-3) start metatroller in the background (python ./metatroller.py)
-4) start soat (python ./soat.py) with some testing flags (run it without any flags
- to see which options are available)
-5) check the results later by running soatstats (python ./soatstats.py)
-
-'''
-
-__all__ = ["ExitNodeScanner", "DNSRebindScanner", "load_wordlist", "get_urls"]
-
-import commands
-import getopt
-import httplib
-import os
-import random
-import re
-from sets import Set
-import smtplib
-import socket
-import string
-import sys
-import time
-import urllib
-import urllib2
-
-import soatstats
-from soatstats import *
-
-from TorCtl import TorUtil, TorCtl, PathSupport
-from TorCtl.TorUtil import meta_port, meta_host, control_port, control_host, tor_port, tor_host
-from TorCtl.TorUtil import *
-from TorCtl.PathSupport import *
-from TorCtl.TorCtl import Connection, EventHandler
-
-from OpenSSL import *
-
-sys.path.append("./tools")
-from BeautifulSoup.BeautifulSoup import BeautifulSoup, SoupStrainer
-from SocksiPy import socks
-import Pyssh.pyssh
-
-#
-# config stuff
-#
-
-# these are used when searching for 'random' urls for testing
-wordlist_file = './wordlist.txt';
-allowed_filetypes = ['all','pdf']
-result_per_type = 5
-
-#
-# ports to test in the consistency test
-#
-
-ports_to_check = [
- ["pop", ExitPolicyRestriction('255.255.255.255', 110), "pops", ExitPolicyRestriction('255.255.255.255', 995)],
- ["imap", ExitPolicyRestriction('255.255.255.255', 143), "imaps", ExitPolicyRestriction('255.255.255.255', 993)],
- ["telnet", ExitPolicyRestriction('255.255.255.255', 23), "ssh", ExitPolicyRestriction('255.255.255.255', 22)],
- ["smtp", ExitPolicyRestriction('255.255.255.255', 25), "smtps", ExitPolicyRestriction('255.255.255.255', 465)],
- ["http", ExitPolicyRestriction('255.255.255.255', 80), "https", ExitPolicyRestriction('255.255.255.255', 443)]
-]
-
-#
-# non-public IPv4 address ranges network portions
-# refer to: www.iana.org/assignments/ipv4-address-space, www.iana.org/assignments/multicast-addresses
-#
-ipv4_nonpublic = [
- '00000000', # default route and its network: 0.0.0.0/8
- '00001010', # private 10.0.0.0/8
- '01111111', # loopback 127.0.0.0/8
- '1010100111111110', # link-local 169.254.0.0/16
- '101011000001', # private 172.16.0.0/12
- '1100000010101000', # private 192.168.0.0/16
- '111' # multicast & experimental 224.0.0.0/3
-]
-
-# tags and attributes to check in the http test: XXX these should be reviewed
-# See also: http://ha.ckers.org/xss.html
-# Note: the more we add, the greater the potential for false positives...
-# We also only care about the ones that work for FF2/FF3.
-tags_to_check = ['a', 'area', 'base', 'applet', 'embed', 'form', 'frame',
- 'iframe', 'img', 'link', 'object', 'script', 'meta', 'body']
-attrs_to_check = ['onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmouseover',
- 'onmousemove', 'onmouseout', 'onkeypress','onkeydown','onkeyup']
-#
-# constants
-#
-
-linebreak = '\r\n'
-
-# a simple interface to handle a socket connection
-class Client:
-
- def __init__(self, host, port):
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.sock.connect((host, port))
- self.buffer = self.sock.makefile('rb')
-
- def writeline(self, line):
- self.sock.send(line + linebreak)
-
- def readline(self):
- response = self.buffer.readline()
- if not response:
- raise EOFError
- elif response[-2:] == linebreak:
- response = response[:-2]
- elif response[-1:] in linebreak:
- response = response[:-1]
- return response
-
-class DNSRebindScanner(EventHandler):
- '''
- A tor control event handler extending TorCtl.EventHandler
- Monitors for REMAP events (see check_dns_rebind())
- '''
- def __init__(self, exit_node_scanner):
- EventHandler.__init__(self)
- self.__soat = exit_node_scanner
-
- def stream_status_event(self, event):
- if event.status == 'REMAP':
- octets = map(lambda x: int2bin(x).zfill(8), event.target_host.split('.'))
- ipbin = ''.join(octets)
- for network in ipv4_nonpublic:
- if ipbin[:len(network)] == network:
- handler = DataHandler()
- result = DNSRebindTestResult(self.__soat.get_exit_node(), '', TEST_FAILURE)
- handler.saveResult(result)
-
-class ExitNodeScanner:
- ''' The scanner class '''
- def __init__(self):
- '''
- Establish a connection to metatroller & control port,
- configure metatroller, load the number of previously tested nodes
- '''
- # establish a metatroller connection
- plog('INFO', 'ExitNodeScanner starting up...')
- try:
- self.__meta = Client(meta_host, meta_port)
- except socket.error:
- plog('ERROR', 'Couldn\'t connect to metatroller. Is it on?')
- exit()
-
- # skip two lines of metatroller introduction
- data = self.__meta.readline()
- data = self.__meta.readline()
-
- # configure metatroller
- commands = [
- 'PATHLEN 2',
- 'PERCENTFAST 88',
- 'USEALLEXITS 1',
- 'UNIFORM 0',
- 'BWCUTOFF 1',
- 'ORDEREXITS 1',
- 'GUARDNODES 0',
- 'RESETSTATS']
- plog('INFO', 'Executing preliminary configuration commands')
- for c in commands:
- self.__meta.writeline(c)
- reply = self.__meta.readline()
- if reply[:3] != '250': # first three chars indicate the reply code
- reply += self.__meta.readline()
- plog('ERROR', 'Error configuring metatroller (' + command + ' failed)')
- plog('ERROR', reply)
- exit()
-
- # establish a control port connection
- try:
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.connect((control_host, control_port))
- c = Connection(s)
- c.authenticate()
- self.__control = c
- except socket.error, e:
- plog('ERROR', 'Couldn\'t connect to the control port')
- plog('ERROR', e)
- exit()
- except AttributeError, e:
- plog('ERROR', 'A service other that the Tor control port is listening on ' + control_host + ':' + control_port)
- plog('ERROR', e)
- exit()
-
- # get a data handler
- self.__datahandler = DataHandler()
-
- # TODO get stats about previous runs
- plog('INFO', 'Loading the previous run stats')
-
- ssh_results = self.__datahandler.getSsh()
- ssl_results = self.__datahandler.getSsl()
- http_results = self.__datahandler.getHttp()
-
- # get lists of tested nodes
- self.ssh_tested = Set([x.exit_node for x in ssh_results])
- self.http_tested = Set([x.exit_node for x in http_results])
- self.ssl_tested = Set([x.exit_node for x in ssl_results])
-
- # get the number of failures
- self.ssh_fail = [self.__datahandler.filterResults(ssh_results, protocols=["ssh"], show_bad=True)]
- self.http_fail = [self.__datahandler.filterResults(http_results, protocols=["http"], show_bad=True)]
- self.ssl_fail = [self.__datahandler.filterResults(ssl_results, protocols=["ssl"], show_bad=True)]
-
- plog('INFO', 'ExitNodeScanner up and ready')
-
- def get_exit_node(self):
- ''' ask metatroller for the last exit used '''
- self.__meta.writeline("GETLASTEXIT")
- reply = self.__meta.readline()
-
- if reply[:3] != '250':
- reply += self.__meta.readline()
- plog('ERROR', reply)
- return 0
-
- p = re.compile('250 LASTEXIT=[\S]+')
- m = p.match(reply)
- self.__exit = m.group()[13:] # drop the irrelevant characters
- plog('NOTICE','Current node: ' + self.__exit)
- return self.__exit
-
- def get_new_circuit(self):
- ''' tell metatroller to close the current circuit and open a new one '''
- plog('NOTICE', 'Trying to construct a new circuit')
- self.__meta.writeline("NEWEXIT")
- reply = self.__meta.readline()
-
- if reply[:3] != '250':
- plog('ERROR', 'Choosing a new exit failed')
- plog('ERROR', reply)
-
- def set_new_exit(self, exit):
- '''
- tell metatroller to set the given node as the exit in the next circuit
- '''
- plog('NOTICE', 'Trying to set ' + `exit` + ' as the exit for the next circuit')
- self.__meta.writeline("SETEXIT $"+exit)
- reply = self.__meta.readline()
-
- if reply[:3] != '250':
- plog('ERROR', 'Setting ' + exit + ' as the new exit failed')
- plog('ERROR', reply)
-
- def report_bad_exit(self, exit):
- '''
- report an evil exit to the control port using AuthDirBadExit
- Note: currently not used
- '''
- # self.__contol.set_option('AuthDirBadExit', exit) ?
- pass
-
- def get_nodes_for_port(self, port):
- ''' ask control port for a list of nodes that allow exiting to a given port '''
- routers = self.__control.read_routers(self.__control.get_network_status())
- restriction = NodeRestrictionList([FlagsRestriction(["Running", "Valid"]), ExitPolicyRestriction('255.255.255.255', port)])
- return [x for x in routers if restriction.r_is_ok(x)]
-
- def check_all_exits_port_consistency(self):
- '''
- an independent test that finds nodes that allow connections over a common protocol
- while disallowing connections over its secure version (for instance http/https)
- '''
-
- # get the structure
- routers = self.__control.read_routers(self.__control.get_network_status())
- bad_exits = Set([])
- specific_bad_exits = [None]*len(ports_to_check)
- for i in range(len(ports_to_check)):
- specific_bad_exits[i] = []
-
- # check exit policies
- for router in routers:
- for i in range(len(ports_to_check)):
- [common_protocol, common_restriction, secure_protocol, secure_restriction] = ports_to_check[i]
- if common_restriction.r_is_ok(router) and not secure_restriction.r_is_ok(router):
- bad_exits.add(router)
- specific_bad_exits[i].append(router)
- plog('INFO', 'Router ' + router.nickname + ' allows ' + common_protocol + ' but not ' + secure_protocol)
-
- # report results
- plog('INFO', 'Total exits: ' + `len(routers)`)
- for i in range(len(ports_to_check)):
- [common_protocol, _, secure_protocol, _] = ports_to_check[i]
- plog('INFO', 'Exits with ' + common_protocol + ' / ' + secure_protocol + ' problem: ' + `len(specific_bad_exits[i])` + ' (~' + `(len(specific_bad_exits[i]) * 100 / len(routers))` + '%)')
- plog('INFO', 'Total bad exits: ' + `len(bad_exits)` + ' (~' + `(len(bad_exits) * 100 / len(routers))` + '%)')
-
- def check_http(self, address):
- ''' check whether a http connection to a given address is molested '''
- plog('INFO', 'Conducting an http test with destination ' + address)
-
- defaultsocket = socket.socket
- socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, tor_host, tor_port)
- socket.socket = socks.socksocket
-
- pcontent = self.http_request(address)
-
- # reset the connection to direct
- socket.socket = defaultsocket
-
- exit_node = self.get_exit_node()
- if exit_node == 0 or exit_node == '0' or not exit_node:
- plog('INFO', 'We had no exit node to test, skipping to the next test.')
- return TEST_SUCCESS
-
- # an address representation acceptable for a filename
- address_file = self.__datahandler.safeFilename(address[7:])
-
- # if we have no content, we had a connection error
- if pcontent == 0:
- result = HttpTestResult(exit_node, address, 0, TEST_INCONCLUSIVE)
- self.__datahandler.saveResult(result)
- return TEST_INCONCLUSIVE
-
- elements = SoupStrainer(lambda name, attrs : name in tags_to_check or
- len(Set(attrs).intersection(Set(attrs_to_check))) > 0)
- pcontent = pcontent.decode('ascii', 'ignore')
- psoup = BeautifulSoup(pcontent, parseOnlyThese=elements)
-
- # load the original tag structure
- # if we don't have any yet, get it
- soup = 0
- try:
- tag_file = open(http_tags_dir + address_file + '.tags', 'r')
- soup = BeautifulSoup(tag_file.read())
- tag_file.close()
- except IOError:
- content = self.http_request(address)
- content = content.decode('ascii','ignore')
- soup = BeautifulSoup(content, parseOnlyThese=elements)
- tag_file = open(http_tags_dir + address_file + '.tags', 'w')
- tag_file.write(soup.__str__() + ' ') # the space is needed in case we have some page with no matching tags at all
- tag_file.close()
- except TypeError, e:
- plog('ERROR', 'Failed parsing the tag tree for ' + address)
- plog('ERROR', e)
- return TEST_INCONCLUSIVE
- if soup == 0:
- plog('ERROR', 'Failed to get the correct tag structure for ' + address)
- return TEST_INCONCLUSIVE
-
- self.http_tested.add(exit_node)
-
- # compare the content
- # if content matches, everything is ok
- if psoup == soup:
- result = HttpTestResult(exit_node, address, 0, TEST_SUCCESS)
- self.__datahandler.saveResult(result)
- return TEST_SUCCESS
-
- # if content doesnt match, update the direct content
- content_new = self.http_request(address)
- content_new = content_new.decode('ascii', 'ignore')
- if content_new == 0:
- result = HttpTestResult(exit_node, address, 0, TEST_INCONCLUSIVE)
- self.__datahandler.saveResult(result)
- return TEST_INCONCLUSIVE
-
- soup_new = BeautifulSoup(content_new, parseOnlyThese=elements)
- # compare the new and old content
- # if they match, means the node has been changing the content
- if soup == soup_new:
- result = HttpTestResult(exit_node, address, 0, TEST_FAILURE)
- self.__datahandler.saveResult(result)
- tag_file = open(http_tags_dir + `exit_node` + '_' + address_file + '.tags', 'w')
- tag_file.write(psoup.__str__())
- tag_file.close()
- return TEST_FAILURE
-
- # if content has changed outside of tor, update the saved file
- tag_file = open(http_tags_dir + address_file + '.tags', 'w')
- tag_file.write(soup_new.__str__())
- tag_file.close()
-
- # compare the node content and the new content
- # if it matches, everything is ok
- if psoup == soup_new:
- result = HttpTestResult(exit_node, address, 0, TEST_SUCCESS)
- self.__datahandler.saveResult(result)
- return TEST_SUCCESS
-
- # if it doesn't match, means the node has been changing the content
- result = HttpTestResult(exit_node, address, 0, TEST_FAILURE)
- self.__datahandler.saveResult(result)
- tag_file = open(http_tags_dir + `exit_node` + '_' + address_file + '.tags', 'w')
- tag_file.write(psoup.__str__())
- tag_file.close()
-
- return TEST_FAILURE
-
- def check_openssh(self, address):
- ''' check whether an openssh connection to a given address is molested '''
- # TODO
- #ssh = pyssh.Ssh('username', 'host', 22)
- #ssh.set_sshpath(pyssh.SSH_PATH)
- #response = self.ssh.sendcmd('ls')
- #print response
-
- return 0
-
- def check_openssl(self, address):
- ''' check whether an https connection to a given address is molested '''
- plog('INFO', 'Conducting an ssl test with destination ' + address)
-
- # an address representation acceptable for a filename
- address_file = self.__datahandler.safeFilename(address[8:])
-
- # get the cert via tor
-
- defaultsocket = socket.socket
- socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, tor_host, tor_port)
- socket.socket = socks.socksocket
-
- cert = self.ssl_request(address)
-
- # reset the connection method back to direct
- socket.socket = defaultsocket
-
- exit_node = self.get_exit_node()
- if exit_node == 0 or exit_node == '0' or not exit_node:
- plog('INFO', 'We had no exit node to test, skipping to the next test.')
- return TEST_FAILURE
-
- # if we got no cert, there was an ssl error
- if cert == 0:
- result = SSLTestResult(exit_node, address, 0, TEST_INCONCLUSIVE)
- self.__datahandler.saveResult(result)
- return TEST_INCONCLUSIVE
-
- # load the original cert and compare
- # if we don't have the original cert yet, get it
- original_cert = 0
- try:
- cert_file = open(ssl_certs_dir + address_file + '.pem', 'r')
- cert_string = cert_file.read()
- original_cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_string)
- except IOError:
- plog('INFO', 'Opening a direct ssl connection to ' + address)
- original_cert = self.ssl_request(address)
- if not original_cert:
- plog('ERROR', 'Error getting the correct cert for ' + address)
- return TEST_INCONCLUSIVE
- if original_cert.has_expired():
- plog('ERROR', 'The ssl cert for ' + address + 'seems to have expired. Skipping to the next test...')
- return TEST_INCONCLUSIVE
- cert_file = open(ssl_certs_dir + address_file + '.pem', 'w')
- cert_file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, original_cert))
- cert_file.close()
- except OpenSSL.crypto.Error:
- plog('ERROR', 'There are non-related files in ' + ssl_certs_dir + '. You should probably clean it.')
- return TEST_INCONCLUSIVE
- if not original_cert:
- plog('ERROR', 'Error getting the correct cert for ' + address)
- return TEST_INCONCLUSIVE
-
- # get an easily comparable representation of the certs
- cert_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
- original_cert_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, original_cert)
-
- # in any case we can consider the node looked at
- self.ssl_tested.add(exit_node)
-
- # if certs match, everything is ok
- if cert_pem == original_cert_pem:
- cert_file = ssl_certs_dir + address_file + '.pem'
- result = SSLTestResult(exit_node, address, cert_file, TEST_SUCCESS)
- self.__datahandler.saveResult(result)
- return TEST_SUCCESS
-
- # if certs dont match, open up a direct connection and update the cert
- plog('INFO', 'Opening a direct ssl connection to ' + address)
- original_cert_new = self.ssl_request(address)
- if original_cert_new == 0:
- plog('ERROR', 'Error getting the correct cert for ' + address)
- result = SSLTestResult(exit_node, address, 0, TEST_INCONCLUSIVE)
- self.__datahandler.saveResult(result)
- return TEST_INCONCLUSIVE
-
- original_cert_new_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, original_cert_new)
-
- # compare the old and new cert
- # if certs match, means the exit node has been messing with the cert
- if original_cert_pem == original_cert_new_pem:
- plog('ERROR', 'Exit node ' + `exit_node` + ' seems to be meddling with certificates. (' + address + ')')
-
- cert_file_name = ssl_certs_dir + address_file + '_' + `exit_node` + '.pem'
- cert_file = open(cert_file_name, 'w')
- cert_file.write(cert_pem)
- cert_file.close()
-
- result = SSLTestResult(exit_node, address, cert_file_name, TEST_FAILURE)
- self.__datahandler.saveResult(result)
- return TEST_FAILURE
-
- # if comparsion fails, replace the old cert with the new one
- # XXX: Hrmm, probably should store as a seperate IP file in this case
- # so we don't keep alternating on sites that have round robin
- # DNS and different certs for each IP..
- cert_file = open(ssl_certs_dir + address_file + '.pem', 'w')
- cert_file.write(original_cert_new_pem)
- cert_file.close()
-
- # compare the new cert and the node cert
- # if certs match, everything is ok
- if cert_pem == original_cert_new_pem:
- cert_file = ssl_certs_dir + address_file + '.pem'
- result = SSLTestResult(exit_node, address, cert_file, TEST_SUCCESS)
- self.__datahandler.saveResult(result)
- return TEST_SUCCESS
-
- # if certs dont match, means the exit node has been messing with the cert
- plog('ERROR', 'Exit node ' + `exit_node` + ' seems to be meddling with certificates. (' + address + ')')
-
- cert_file_name = ssl_certs_dir + address + '_' + `exit_node` + '.pem'
- cert_file = open(cert_file_name, 'w')
- cert_file.write(cert_pem)
- cert_file.close()
-
- result = SSLTestResult(exit_node, address, cert_file_name, TEST_FAILURE)
- self.__datahandler.saveResult(result)
-
- return TEST_FAILURE
-
- def check_smtp(self, address, port=''):
- '''
- check whether smtp + tls connection to a given address is molested
- this is done by going through the STARTTLS sequence and comparing server
- responses for the direct and tor connections
- '''
-
- plog('INFO', 'Conducting an smtp test with destination ' + address)
-
- defaultsocket = socket.socket
- socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, tor_host, tor_port)
- socket.socket = socks.socksocket
-
- ehlo1_reply = 0
- has_starttls = 0
- ehlo2_reply = 0
-
- try:
- s = smtplib.SMTP(address, port)
- ehlo1_reply = s.ehlo()[0]
- if ehlo1_reply != 250:
- raise smtplib.SMTPException('First ehlo failed')
- has_starttls = s.has_extn('starttls')
- if not has_starttls:
- raise smtplib.SMTPException('It seems the server doesn\'t support starttls')
- s.starttls()
- # TODO check certs?
- ehlo2_reply = s.ehlo()[0]
- if ehlo2_reply != 250:
- raise smtplib.SMTPException('Second ehlo failed')
- except socket.gaierror, e:
- plog('ERROR', 'A connection error occured while testing smtp at ' + address)
- plog('ERROR', e)
- socket.socket = defaultsocket
- return TEST_INCONCLUSIVE
- except smtplib.SMTPException, e:
- plog('ERROR','An error occured while testing smtp at ' + address)
- plog('ERROR', e)
- return TEST_INCONCLUSIVE
- # reset the connection method back to direct
- socket.socket = defaultsocket
-
- # check whether the test was valid at all
- exit_node = self.get_exit_node()
- if exit_node == 0 or exit_node == '0':
- plog('INFO', 'We had no exit node to test, skipping to the next test.')
- return TEST_SUCCESS
-
- # now directly
-
- ehlo1_reply_d = 0
- has_starttls_d = 0
- ehlo2_reply_d = 0
-
- try:
- s = smtplib.SMTP(address, port)
- ehlo1_reply_d = s.ehlo()[0]
- if ehlo1_reply != 250:
- raise smtplib.SMTPException('First ehlo failed')
- has_starttls_d = s.has_extn('starttls')
- if not has_starttls_d:
- raise smtplib.SMTPException('It seems that the server doesn\'t support starttls')
- s.starttls()
- ehlo2_reply_d = s.ehlo()[0]
- if ehlo2_reply_d != 250:
- raise smtplib.SMTPException('Second ehlo failed')
- except socket.gaierror, e:
- plog('ERROR', 'A connection error occured while testing smtp at ' + address)
- plog('ERROR', e)
- socket.socket = defaultsocket
- return TEST_INCONCLUSIVE
- except smtplib.SMTPException, e:
- plog('ERROR', 'An error occurred while testing smtp at ' + address)
- plog('ERROR', e)
- return TEST_INCONCLUSIVE
-
- print ehlo1_reply, ehlo1_reply_d, has_starttls, has_starttls_d, ehlo2_reply, ehlo2_reply_d
-
- # compare
- if ehlo1_reply != ehlo1_reply_d or has_starttls != has_starttls_d or ehlo2_reply != ehlo2_reply_d:
- result = SMTPTestResult(exit_node, address, TEST_FAILURE)
- self.__datahandler.saveResult(result)
- return TEST_FAILURE
-
- result = SMTPTestResult(exit_node, address, TEST_SUCCESS)
- self.__datahandler.saveResult(result)
- return TEST_SUCCESS
-
- def check_pop(self, address, port=''):
- '''
- check whether a pop + tls connection to a given address is molested
- it is implied that the server reads/sends messages compliant with RFC1939 & RFC2449
- '''
-
- plog('INFO', 'Conducting a pop test with destination ' + address)
-
- if not port:
- port = 110
-
- defaultsocket = socket.socket
- socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, tor_host, tor_port)
- socket.socket = socks.socksocket
-
- capabilities_ok = False
- starttls_present = False
- tls_started = None
- tls_succeeded = None
-
- try:
- pop = Client(address, port)
-
- # read the server greeting
- server_greeting = pop.readline()
-
- # get the server capabilities
- pop.writeline('CAPA')
- capabilities = ''
- while 1:
- curr = pop.readline()
- if '+OK' in curr:
- capabilities_ok = True
- elif curr == '.':
- break
- elif 'STLS' in curr:
- starttls_present = True
-
- if not capabilities_ok:
- return TEST_INCONCLUSIVE
-
- # try to start tls negotiation
- if starttls_present:
- pop.writeline('STLS')
-
- starttls_started = '+OK' in starttls_response
-
- # negotiate TLS and issue some request to feel good about it
- # TODO check certs?
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- c = SSL.Connection(ctx, pop.sock)
- c.set_connect_state()
- c.do_handshake()
- c.send('CAPA' + linebreak)
-
- while tls_succeeded == None:
- line = ''
- char = None
- while char != '\n':
- char = c.read(1)
- if not char:
- break
- elif char == '.':
- tls_succeeded = False
- line += char
-
- if '-ERR' in line:
- tls_succeeded = False
- elif '+OK' in line:
- tls_succeeded = True
- elif not line:
- tls_succeeded = False
-
- except socket.error, e:
- plog('ERROR', 'Connection to ' + address + ':' + port + ' refused')
- plog('ERROR', e)
- socket.socket = defaultsocket
- return TEST_INCONCLUSIVE
- except OpenSSL.SSL.SysCallError, e:
- plog('ERROR', 'Error while negotiating an SSL connection to ' + address + ':' + port)
- plog('ERROR', e)
- socket.socket = defaultsocket
- return TEST_INCONCLUSIVE
-
- # reset the connection to default
- socket.socket = defaultsocket
-
- # check whether the test was valid at all
- exit_node = self.get_exit_node()
- if exit_node == 0 or exit_node == '0':
- plog('INFO', 'We had no exit node to test, skipping to the next test.')
- return TEST_SUCCESS
-
- # do the same for the direct connection
-
- capabilities_ok_d = False
- starttls_present_d = False
- tls_started_d = None
- tls_succeeded_d = None
-
- try:
- pop = Client(address, port)
-
- # read the server greeting
- server_greeting = pop.readline()
-
- # get the server capabilities
- pop.writeline('CAPA')
- capabilities = ''
- while 1:
- curr = pop.readline()
- if '+OK' in curr:
- capabilities_ok_d = True
- elif curr == '.':
- break
- elif 'STLS' in curr:
- starttls_present_d = True
-
- if not capabilities_ok_d:
- return TEST_INCONCLUSIVE
-
- # try to start tls negotiation
- if starttls_present_d:
- pop.writeline('STLS')
-
- starttls_started_d = '+OK' in starttls_response
-
- # negotiate TLS, issue some request to feel good about it
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- c = SSL.Connection(ctx, pop.sock)
- c.set_connect_state()
- c.do_handshake()
- c.send('CAPA' + linebreak)
-
- while tls_succeeded_d == None:
- line = ''
- char = None
- while char != '\n':
- char = c.read(1)
- if not char:
- break
- elif char == '.':
- tls_succeeded_d = False
- line += char
-
- if '-ERR' in line:
- tls_succeeded_d = False
- elif '+OK' in line:
- tls_succeeded_d = True
- elif not line:
- tls_succeeded_d = False
-
- except socket.error, e:
- plog('ERROR', 'Connection to ' + address + ':' + port + ' refused')
- plog('ERROR', e)
- socket.socket = defaultsocket
- return TEST_INCONCLUSIVE
- except OpenSSL.SSL.SysCallError, e:
- plog('ERROR', 'Error while negotiating an SSL connection to ' + address + ':' + port)
- plog('ERROR', e)
- socket.socket = defaultsocket
- return TEST_INCONCLUSIVE
-
- # compare
- if (capabilities_ok != capabilities_ok_d or starttls_present != starttls_present_d or
- tls_started != tls_started_d or tls_suceeded != tls_succeeded_d):
- result = POPTestResult(exit_node, address, TEST_FAILURE)
- self.__datahandler.saveResult(result)
- return TEST_FAILURE
-
- result = POPTestResult(exit_node, address, TEST_SUCCESS)
- self.__datahandler.saveResult(result)
- return TEST_SUCCESS
-
- def check_imap(self, address, port=''):
- '''
- check whether an imap + tls connection to a given address is molested
- it is implied that the server reads/sends messages compliant with RFC3501
- '''
- plog('INFO', 'Conducting an imap test with destination ' + address)
-
- if not port:
- port = 143
-
- defaultsocket = socket.socket
- socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, tor_host, tor_port)
- socket.socket = socks.socksocket
-
- capabilities_ok = None
- starttls_present = None
- tls_started = None
- tls_succeeded = None
-
- try:
- imap = Client(address, port)
-
- # read server greeting
- server_greeting = imap.readline()
-
- # get server capabilities
- imap.writeline('a001 CAPABILITY')
- capabilities = imap.readline() # first line - list of capabilities
- capabilities_ok = 'OK' in imap.readline() # second line - the request status
-
- if not capabilities_ok:
- return TEST_INCONCLUSIVE
-
- # check if starttls is present
- starttls_present = 'STARTTLS' in capabilities
-
- if starttls_present:
- imap.writeline('a002 STARTTLS')
- tls_started = 'OK' in imap.readline()
-
- # negotiate TLS, issue a request to feel good about it
- # TODO check the cert aswell ?
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- c = SSL.Connection(ctx, imap.sock)
- c.set_connect_state()
- c.do_handshake()
- c.send('a003 CAPABILITY' + linebreak)
-
- while tls_succeeded == None:
- line = ''
- char = None
- while char != '\n':
- char = c.read(1)
- if not char:
- break
- line += char
-
- if 'Error' in line or 'error' in line:
- tls_succeeded = False
- elif 'OK' in line:
- tls_succeeded = True
- elif not line:
- tls_succeeded = False
-
- except socket.error, e:
- plog('ERROR', 'Connection to ' + address + ':' + port + ' refused')
- plog('ERROR', e)
- socket.socket = defaultsocket
- return TEST_INCONCLUSIVE
- except OpenSSL.SSL.SysCallError, e:
- plog('ERROR', 'Error while negotiating an SSL connection to ' + address + ':' + port)
- plog('ERROR', e)
- socket.socket = defaultsocket
- return TEST_INCONCLUSIVE
-
- socket.socket = defaultsocket
-
- # check whether the test was valid at all
- exit_node = self.get_exit_node()
- if exit_node == 0 or exit_node == '0':
- plog('INFO', 'We had no exit node to test, skipping to the next test.')
- return TEST_SUCCESS
-
- # do the same for the direct connection
- capabilities_ok_d = None
- starttls_present_d = None
- tls_started_d = None
- tls_succeeded_d = None
-
- try:
- imap = Client(address, port)
-
- # read server greeting
- server_greeting = imap.readline()
-
- # get server capabilities
- imap.writeline('a001 CAPABILITY')
- capabilities = imap.readline() # first line - list of capabilities
- capabilities_ok_d = 'OK' in imap.readline() # second line - the request status
-
- if not capabilities_ok_d:
- return TEST_INCONCLUSIVE
-
- # check if starttls is present
- starttls_present_d = 'STARTTLS' in capabilities
-
- if starttls_present_d:
- imap.writeline('a002 STARTTLS')
- tls_started = 'OK' in imap.readline()
-
- # negotiate TLS, issue some request to feel good about it
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- c = SSL.Connection(ctx, imap.sock)
- c.set_connect_state()
- c.do_handshake()
- c.send('a003 CAPABILITY' + linebreak)
-
- while tls_succeeded_d == None:
- line = ''
- char = None
- while char != '\n':
- char = c.read(1)
- if not char:
- break
- line += char
-
- if 'Error' in line or 'error' in line:
- tls_succeeded_d = False
- elif 'OK' in line:
- tls_succeeded_d = True
- elif not line:
- tls_succeeded_d = False
-
- except socket.error, e:
- plog('ERROR', 'Connection to ' + address + ':' + port + ' refused')
- plog('ERROR', e)
- socket.socket = defaultsocket
- return TEST_INCONCLUSIVE
- except OpenSSL.SSL.SysCallError, e:
- plog('ERROR', 'Error while negotiating an SSL connection to ' + address + ':' + port)
- plog('ERROR', e)
- socket.socket = defaultsocket
- return TEST_INCONCLUSIVE
-
- # compare
- if (capabilities_ok != capabilities_ok_d or starttls_present != starttls_present_d or
- tls_started != tls_started_d or tls_succeeded != tls_succeeded_d):
- result = IMAPTestResult(exit_node, address, TEST_FAILURE)
- self.__datahandler.saveResult(result)
- return TEST_FAILURE
-
- result = IMAPTestResult(exit_node, address, TEST_SUCCESS)
- self.__datahandler.saveResult(result)
- return TEST_SUCCESS
-
- def check_dns(self, address):
- ''' A basic comparison DNS test. Rather unreliable. '''
- # TODO Spawns a lot of false positives (for ex. doesn't work for google.com).
- plog('INFO', 'Conducting a basic dns test for destination ' + address)
-
- ip = tor_resolve(address)
-
- # check whether the test was valid at all
- exit_node = self.get_exit_node()
- if exit_node == 0 or exit_node == '0':
- plog('INFO', 'We had no exit node to test, skipping to the next test.')
- return TEST_SUCCESS
-
- ips_d = Set([])
- try:
- results = socket.getaddrinfo(address,None)
- for result in results:
- ips_d.add(result[4][0])
- except socket.herror, e:
- plog('ERROR', 'An error occured while performing a basic dns test')
- plog('ERROR', e)
- return TEST_INCONCLUSIVE
-
- if ip in ips_d:
- result = DNSTestResult(exit_node, address, TEST_SUCCESS)
- return TEST_SUCCESS
- else:
- plog('ERROR', 'The basic DNS test suspects ' + exit_node + ' to be malicious.')
- result = DNSTestResult(exit_node, address, TEST_FAILURE)
- return TEST_FAILURE
-
- def check_dns_rebind(self):
- '''
- A DNS-rebind attack test that runs in the background and monitors REMAP events
- The test makes sure that external hosts are not resolved to private addresses
- '''
- plog('INFO', 'Monitoring REMAP events for weirdness')
- self.__dnshandler = DNSRebindScanner(self)
- self.__control.set_event_handler(self.__dnshandler)
- self.__control.set_events([TorCtl.EVENT_TYPE.STREAM], True)
-
- def http_request(self, address):
- ''' perform a http GET-request and return the content received '''
- request = urllib2.Request(address)
- request.add_header('User-Agent','Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.8.1) Gecko/20061010 Firefox/2.0')
-
- content = 0
- try:
- reply = urllib2.urlopen(request)
- content = reply.read()
- except (ValueError, urllib2.URLError):
- plog('ERROR', 'The http-request address ' + address + ' is malformed')
- return 0
- except (IndexError, TypeError):
- plog('ERROR', 'An error occured while negotiating socks5 with Tor')
- return 0
-
- return content
-
- def ssh_request(self):
- pass
-
- def ssl_request(self, address):
- ''' initiate an ssl connection and return the server certificate '''
-
- # drop the https:// prefix if present (not needed for a socket connection)
- if address[:8] == 'https://':
- address = address[8:]
-
- # specify the context
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- ctx.set_verify_depth(1)
-
- # ready the certificate request
- request = crypto.X509Req()
-
- # open an ssl connection
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- c = SSL.Connection(ctx, s)
- c.set_connect_state()
-
- try:
- c.connect((address, 443))
- c.send(crypto.dump_certificate_request(crypto.FILETYPE_PEM,request))
- except socket.error, e:
- plog('ERROR','An error occured while opening an ssl connection to ' + address)
- plog('ERROR', e)
- return 0
- except (IndexError, TypeError):
- plog('ERROR', 'An error occured while negotiating socks5 with Tor')
- return 0
-
- # return the cert
- return c.get_peer_certificate()
-
-# some helpful methods
-
-def load_wordlist(file):
- ''' load a list of strings from a file (which contains words separated by newlines) '''
- plog('INFO', 'Loading the wordlist')
-
- wordlist = []
- fh = None
- try:
- fh = open(file, 'r')
- except IOError, e:
- plog('ERROR', 'Reading the wordlist file failed.')
- plog('ERROR', e)
-
- try:
- for line in fh:
- wordlist.append(line[:-1]) # get rid of the linebreaks
- finally:
- fh.close()
-
- return wordlist
-
-def get_urls(wordlist, filetypes=['any'], results_per_type=5, protocol='any', g_results_per_page=10):
- '''
- construct a list of urls based on the wordlist, filetypes and protocol.
-
- Note: since we currently use google, which doesn't index by protocol,
- searches for anything but 'any' could be rather slow
- '''
- plog('INFO', 'Searching google for relevant sites...')
-
- urllist = []
- for filetype in filetypes:
- type_urls = []
-
- while len(type_urls) < results_per_type:
- query = random.choice(wordlist)
- if filetype != 'any':
- query += ' filetype:' + filetype
- if protocol != 'any':
- query += ' allinurl:' + protocol # this isn't too reliable, but we'll re-filter results later
- #query += '&num=' + `g_results_per_page`
-
- # search google for relevant pages
- # note: google only accepts requests from idenitified browsers
- # TODO gracefully handle the case when google doesn't want to give us result anymore
- host = 'www.google.com'
- params = urllib.urlencode({'q' : query})
- headers = {'User-Agent' : 'Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.8.1) Gecko/20061010 Firefox/2.0'}
- search_path = '/search' + '?' + params
-
- connection = None
- response = None
-
- try:
- connection = httplib.HTTPConnection(host)
- connection.request("GET", search_path, {}, headers)
- response = connection.getresponse()
- if response.status != 200:
- raise Exception(response.status, response.reason)
- except socket.gaierror, e:
- plog('ERROR', 'Connection to google.com failed')
- plog('ERROR', e)
- return list(Set(urllist))
-
- content = response.read()
- links = SoupStrainer('a')
- soup = BeautifulSoup(content, parseOnlyThese=links)
-
- # get the links and do some additional filtering
- for link in soup.findAll('a', {'class' : 'l'}):
- url = link['href']
- if (protocol != 'any' and url[:len(protocol)] != protocol or
- filetype != 'any' and url[-len(filetype):] != filetype):
- pass
- else:
- type_urls.append(link['href'])
-
- if type_urls > results_per_type:
- type_urls = random.sample(type_urls, results_per_type) # make sure we don't get more urls than needed
- urllist.extend(type_urls)
-
- return list(Set(urllist))
-
-def tor_resolve(address):
- ''' performs a DNS query explicitly via tor '''
- return commands.getoutput("tor-resolve " + address)
-
-def int2bin(n):
- '''
- simple decimal -> binary conversion, needed for comparing IP addresses
- '''
- n = int(n)
- if n < 0:
- raise ValueError, "Negative values are not accepted."
- elif n == 0:
- return '0'
- else:
- bin = ''
- while n > 0:
- bin += str(n % 2)
- n = n >> 1
- return bin[::-1]
-#
-# main logic
-#
-def main(argv):
- # make sure we have something to test for
- if len(argv) < 2:
- print ''
- print 'Please provide at least one test option:'
- print '--ssl (~works)'
- print '--http (gives some false positives)'
- print '--ssh (doesn\'t work yet)'
- print '--smtp (~works)'
- print '--pop (~works)'
- print '--imap (~works)'
- print '--dns (a basic test, not really reliable)'
- print '--dnsrebind (works with the ssl test)'
- print '--policies (~works)'
- print ''
- sys.exit(0)
-
- opts = ['ssl','http','ssh','smtp','pop','imap','dns','dnsrebind','policies']
- flags, trailer = getopt.getopt(argv[1:], [], opts)
-
- # get specific test types
- do_ssl = ('--ssl','') in flags
- do_http = ('--http','') in flags
- do_ssh = ('--ssh','') in flags
- do_smtp = ('--smtp','') in flags
- do_pop = ('--pop','') in flags
- do_imap = ('--imap','') in flags
- do_dns_basic = ('--dns','') in flags
- do_dns_rebind = ('--dnsrebind','') in flags
- do_consistency = ('--policies','') in flags
-
- # load the wordlist to search for sites lates on
- wordlist = load_wordlist(wordlist_file)
-
- # initiate the scanner
- scanner = ExitNodeScanner()
-
- # initiate the passive dns rebind attack monitor
- if do_dns_rebind:
- scanner.check_dns_rebind()
-
- # check for sketchy exit policies
- if do_consistency:
- scanner.check_all_exits_port_consistency()
-
- # maybe only the consistency test was required
- if not (do_ssl or do_http or do_ssh or do_smtp or do_pop or do_imap or do_dns_basic):
- plog('INFO', 'Done.')
- sys.exit(0)
-
- # declare some variables and assign values if neccessary
- ssl_nodes = http_nodes = ssh_nodes = smtp_nodes = pop_nodes = imap_nodes = dns_nodes = []
- ssl_nodes_n = http_nodes_n = ssh_nodes_n = smtp_nodes_n = pop_nodes_n = imap_nodes_n = dns_nodes_n = 0
- ssl_urls = http_urls = ssh_urls = smtp_urls = pop_urls = imap_urls = dns_urls = []
- ssl_fail = http_fail = ssh_fail = smtp_fail = pop_fail = imap_fail = imap_urls = 0
-
- if do_ssl:
- ssl_nodes = scanner.get_nodes_for_port(443)
- ssl_nodes_n = len(ssl_nodes)
- # the search for https urls is yet too slow
- ssl_urls = ['https://mail.google.com', 'https://addons.mozilla.org', 'https://www.fastmail.fm']
- ssl_fail = len(scanner.ssl_fail)
-
- if len(ssl_urls) == 0:
- plog('ERROR', 'No urls specified for ssl testing.')
- do_ssl = False
-
- if do_http:
- http_nodes = scanner.get_nodes_for_port(80)
- http_nodes_n = len(http_nodes)
- http_urls = get_urls(wordlist, protocol='http', results_per_type=10, g_results_per_page=20)
- http_fail = len(scanner.http_fail)
-
- if len(http_urls) == 0:
- plog('ERROR', 'No urls specified for http testing.')
- do_http = False
-
- if do_ssh:
- ssh_nodes = scanner.get_nodes_for_port(22)
- ssh_nodes_n = len(ssh_nodes)
- ssh_urls = []
- ssh_fail = len(scanner.ssh_fail)
-
- if len(ssl_urls) == 0:
- plog('ERROR', 'No urls specified for ssh testing.')
- do_ssh = False
-
- if do_smtp:
- smtp_urls = [('smtp.gmail.com','587')]
-
- if len(smtp_urls) == 0:
- plog('ERROR', 'No urls specified for smtp testing.')
- do_smtp = False
-
- if do_pop:
- pop_urls = []
-
- if len(pop_urls) == 0:
- plog('ERROR', 'No urls specified for pop testing.')
- do_pop = False
-
- if do_imap:
- imap_urls = []
-
- if len(imap_urls) == 0:
- plog('ERROR', 'No urls specified for imap testing.')
- do_imap = False
-
- if do_dns_basic:
- dns_urls = []
-
- if len(dns_urls) == 0:
- plog('ERROR', 'No urls specified for dns testing.')
- do_dns_basic = False
-
- # maybe no tests could be initialized
- if not (do_ssl or do_http or do_ssh or do_smtp or do_pop or do_imap or do_dns_basic):
- plog('INFO', 'Done.')
- sys.exit(0)
-
- # start testing
- while 1:
-
- # https test
- if do_ssl:
- candidates = [x for x in ssl_nodes if ('$' + `x.idhex`) not in scanner.ssl_tested]
- if len(candidates) > 0:
- current_exit = random.choice(candidates)
- scanner.set_new_exit(current_exit.idhex)
-
- scanner.get_new_circuit()
- ssl_site = random.choice(ssl_urls)
- scanner.check_openssl(ssl_site)
-
- ssl_tested_n = len(scanner.ssl_tested)
- if ssl_nodes_n > ssl_tested_n:
- plog('INFO', 'Nodes ssl-tested: ' + `ssl_tested_n` + '/' + `ssl_nodes_n`
- + ' (~' + `((ssl_tested_n * 100) / ssl_nodes_n)` + '%)')
-
- # http test
- if do_http:
- candidates = [x for x in http_nodes if ('$' + `x.idhex`) not in scanner.http_tested]
- if len(candidates) > 0 :
- current_exit = random.choice(candidates)
- scanner.set_new_exit(current_exit.idhex)
-
- scanner.get_new_circuit()
- http_site = random.choice(http_urls)
- scanner.check_http(http_site)
-
- http_tested_n = len(scanner.http_tested)
- if http_nodes_n > http_tested_n:
- plog('INFO', 'Nodes http-tested: ' + `http_tested_n` + '/' + `http_nodes_n`
- + ' (~' + `((http_tested_n * 100) / http_nodes_n)` + '%)')
-
- # ssh test
- if do_ssh:
- candidates = [x for x in ssh_nodes if ('$' + `x.idhex`) not in scanner.ssh_tested]
- if len(candidates) > 0:
- current_exit = random.choice(candidates)
- scanner.set_new_exit(current_exit.idhex)
-
- scanner.get_new_circuit()
- ssh_site = random.choice(ssh_urls)
- scanner.check_ssh(ssh_site)
-
- ssh_tested_n = len(scanner.ssh_tested)
- if ssh_nodes_n > ssh_tested_n:
- plog('INFO', 'Nodes ssh-tested: ' + `ssh_tested_n` + '/' + `ssh_nodes_n`
- + '(~' + `((ssh_tested_n * 100) / ssh_nodes_n)` + '%')
-
- # smtp test
- if do_smtp:
- scanner.get_new_circuit()
- smtp_site = random.choice(smtp_urls)
- scanner.check_smtp(smtp_site[0], smtp_site[1])
-
- # pop test
- if do_pop:
- scanner.get_new_circuit()
- pop_site = random.choice(pop_urls)
- scanner.check_pop(pop_site[0], pop_site[1])
-
- # imap test
- if do_imap:
- scanner.get_new_circuit()
- imap_site = random.choice(imap_urls)
- scanner.check_imap(imap_site[0], imap_site[1])
-
- #
- # managing url lists
- # if we've been having too many false positives lately, get a new target list
- #
-
- if do_http and len(scanner.http_fail) - http_fail >= len(http_urls):
- http_urls = get_urls(wordlist, protocol='http', results_per_type=10, g_results_per_page=20)
- http_fail = len(scanner.http_fail)
-
-#
-# initiate the program
-#
-if __name__ == '__main__':
- try:
- main(sys.argv)
- except KeyboardInterrupt:
- plog('INFO', "Ctrl + C was pressed. Exiting ... ")
- except Exception, e:
- plog('ERROR', "An unexpected error occured.")
- plog('ERROR', e)
Deleted: torflow/trunk/soatstats.py
===================================================================
--- torflow/trunk/soatstats.py 2009-01-05 16:27:59 UTC (rev 17914)
+++ torflow/trunk/soatstats.py 2009-01-05 16:51:38 UTC (rev 17915)
@@ -1,598 +0,0 @@
-#!/usr/bin/python
-#
-# 2008 Aleksei Gorny, mentored by Mike Perry
-
-import dircache
-import operator
-import os
-import pickle
-import sys
-import time
-
-import sets
-from sets import Set
-
-#
-# Data storage
-#
-
-# data locations
-
-data_dir = './data/soat/'
-ssl_certs_dir = data_dir + 'ssl/certs/'
-http_tags_dir = data_dir + 'http/tags/'
-
-# constants
-
-TEST_SUCCESS = 0
-TEST_INCONCLUSIVE = 1
-TEST_FAILURE = 2
-
-# classes to use with pickle to dump test results into files
-
-class TestResult(object):
- ''' Parent class for all test result classes '''
- def __init__(self, exit_node, site, status):
- self.exit_node = exit_node
- self.site = site
- self.timestamp = time.time()
- self.status = status
-
-class SSLTestResult(TestResult):
- ''' Represents the result of an openssl test '''
- def __init__(self, exit_node, ssl_site, cert_file, status):
- super(SSLTestResult, self).__init__(exit_node, ssl_site, status)
- self.cert = cert_file
-
-class HttpTestResult(TestResult):
- ''' Represents the result of a http test '''
- def __init__(self, exit_node, website, tag_prints, status):
- super(HttpTestResult, self).__init__(exit_node, website, status)
- self.tag_prints = tag_prints
-
-class SSHTestResult(TestResult):
- ''' Represents the result of an ssh test '''
- def __init__(self, exit_node, ssh_site, status):
- super(SSHTestResult, self).__init__(exit_node, ssh_site, status)
-
-class DNSTestResult(TestResult):
- ''' Represents the result of a dns test '''
- def __init__(self, exit_node, dns_site, status):
- super(DNSTestResult, self).__init__(exit_node, dns_site, status)
-
-class DNSRebindTestResult(TestResult):
- ''' Represents the result of a dns rebind test '''
- def __init__(self, exit_node, dns_rebind_site, status):
- super(DNSRebindTestResult, self).__init__(exit_node, dns_rebind_site, status)
-
-class SMTPTestResult(TestResult):
- ''' Represents the result of an smtp test '''
- def __init__(self, exit_node, smtp_site, status):
- super(SMTPTestResult, self).__init__(exit_node, smtp_site, status)
-
-class IMAPTestResult(TestResult):
- ''' Represents the result of an imap test '''
- def __init__(self, exit_node, imap_site, status):
- super(IMAPTestResult, self).__init__(exit_node, imap_site, status)
-
-class POPTestResult(TestResult):
- ''' Represents the result of a pop test '''
- def __init__(self, exit_node, pop_site, status):
- super(POPTestResult, self).__init__(exit_node, pop_site, status)
-
-class DataHandler:
- ''' Class for saving and managing test result data '''
- def filterResults(self, results, protocols=[], show_good=False,
- show_bad=False, show_inconclusive=False):
- ''' filter results based on protocol and success level '''
-
- protocol_filters = []
- status_filters = []
-
- for protocol in protocols:
- protocol_filters.append(lambda x, p=protocol: x.__class__.__name__.lower()[:-10].endswith(p))
- if show_good:
- status_filters.append(lambda x: x.status == TEST_SUCCESS)
- if show_bad:
- status_filters.append(lambda x: x.status == TEST_FAILURE)
- if show_inconclusive:
- status_filters.append(lambda x: x.status == TEST_INCONCLUSIVE)
-
- if len(protocol_filters) == 0 or len(status_filters) == 0:
- return []
-
- protocol_filter = lambda x: reduce(operator.__or__, [f(x) for f in protocol_filters])
- status_filter = lambda x: reduce(operator.__or__, [f(x) for f in status_filters])
-
- return [x for x in results if (protocol_filter(x) and status_filter(x))]
-
- def filterByNode(self, results, id):
- ''' filter by node'''
- return filter(lambda x: x.exit_node == id, results)
-
- def getAll(self):
- ''' get all available results'''
- return self.__getResults(data_dir)
-
- def getSsh(self):
- ''' get results of ssh tests '''
- return self.__getResults(data_dir + 'ssh/')
-
- def getHttp(self):
- ''' get results of http tests '''
- return self.__getResults(data_dir + 'http/')
-
- def getSsl(self):
- ''' get results of ssl tests '''
- return self.__getResults(data_dir + 'ssl/')
-
- def getSmtp(self):
- ''' get results of smtp tests '''
- return self.__getResults(data_dir + 'smtp/')
-
- def getPop(self):
- ''' get results of pop tests '''
- return self.__getResults(data_dir + 'pop/')
-
- def getImap(self):
- ''' get results of imap tests '''
- return self.__getResults(data_dir + 'imap/')
-
- def getDns(self):
- ''' get results of basic dns tests '''
- return self.__getResults(data_dir + 'dns')
-
- def getDnsRebind(self):
- ''' get results of dns rebind tests '''
- return self.__getResults(data_dir + 'dnsbrebind/')
-
- def __getResults(self, dir):
- '''
- recursively traverse the directory tree starting with dir
- gather test results from files ending with .result
- '''
- results = []
-
- for root, dirs, files in os.walk(dir):
- for file in files:
- if file.endswith('result'):
- fh = open(os.path.join(root, file))
- result = pickle.load(fh)
- results.append(result)
-
- return results
-
- def safeFilename(self, str):
- '''
- remove characters illegal in some systems
- and trim the string to a reasonable length
- '''
- replaced = (str.replace('/','_').replace('\\','_').replace('?','_').replace(':','_').
- replace('|','_').replace('*','_').replace('<','_').replace('>','_').replace('"',''))
- return replaced[:200]
-
- def saveResult(self, result):
- ''' generic method for saving test results '''
- address = ''
- if result.__class__.__name__ == 'HttpTestResult':
- address = self.safeFilename(result.site[7:])
- elif result.__class__.__name__ == 'SSLTestResult':
- address = self.safeFilename(result.site[8:])
- elif 'TestResult' in result.__class__.__name__:
- address = self.safeFilename(result.site)
- else:
- raise Exception, 'This doesn\'t seems to be a result instance.'
-
- dir = data_dir + result.__class__.__name__[:-10].lower() + '/'
- if result.status == TEST_SUCCESS:
- dir += 'successful/'
- if result.status == TEST_INCONCLUSIVE:
- dir += 'inconclusive/'
- if result.status == TEST_FAILURE:
- dir += 'failed/'
-
- result_file = open(dir + `result.exit_node` + address + '.result', 'w')
- pickle.dump(result, result_file)
- result_file.close()
-
-#
-# Displaying stats on the console
-#
-
-class StatsConsole:
- ''' Class to display statistics from CLI'''
-
- def Listen(self):
- while 1:
- input = raw_input(">>>")
- if input == 'e' or input == 'exit':
- exit()
- elif input == 's' or input == 'summary':
- self.Summary()
- elif input == 'h' or input == 'help' or len(input) > 6:
- self.Help()
- else:
- self.Reply(input)
-
- def Summary(self):
- dh = DataHandler()
- data = dh.getAll()
-
- nodeSet = Set([])
- sshSet = Set([])
- sslSet = Set([])
- httpSet = Set([])
- smtpSet = Set([])
- popSet = Set([])
- imapSet = Set([])
- dnsSet = Set([])
- dnsrebindSet = Set([])
-
- total = len(data)
- good = bad = inconclusive = 0
- ssh = http = ssl = pop = imap = smtp = dns = dnsrebind = 0
-
- for result in data:
- nodeSet.add(result.exit_node)
-
- if result.status == 0:
- good += 1
- elif result.status == 1:
- inconclusive += 1
- elif result.status == 2:
- bad += 1
-
- if result.__class__.__name__ == 'SSHTestResult':
- sshSet.add(result.exit_node)
- ssh += 1
- elif result.__class__.__name__ == 'HttpTestResult':
- httpSet.add(result.exit_node)
- http += 1
- elif result.__class__.__name__ == 'SSLTestResult':
- sslSet.add(result.exit_node)
- ssl += 1
- elif result.__class__.__name__ == 'IMAPTestResult':
- imapSet.add(result.exit_node)
- imap += 1
- elif result.__class__.__name__ == 'POPTestResult':
- popSet.add(result.exit_node)
- pop += 1
- elif result.__class__.__name__ == 'SMTPTestResult':
- smtpSet.add(result.exit_node)
- smtp += 1
- elif result.__class__.__name__ == 'DNSTestResult':
- dnsSet.add(result.exit_node)
- dns += 1
- elif result.__class__.__name__ == 'DNSRebindTestResult':
- dnsrebindSet.add(result.exit_node)
- dnsrebind += 1
-
- swidth = 25
- nwidth = 10
- width = swidth + nwidth
-
- header_format = '%-*s%*s'
- format = '%-*s%*i'
-
- print '=' * width
- print header_format % (swidth, 'Parameter', nwidth, 'Count')
- print '-' * width
-
- stats = [
- ('Tests completed', total),
- ('Nodes tested', len(nodeSet)),
- ('Nodes SSL-tested', len(sslSet)),
- ('Nodes HTTP-tested', len(httpSet)),
- ('Nodes SSH-tested', len(sshSet)),
- ('Nodes POP-tested', len(popSet)),
- ('Nodes IMAP-tested', len(imapSet)),
- ('Nodes SMTP-tested', len(smtpSet)),
- ('Nodes DNS-tested', len(dnsSet)),
- ('Nodes DNSRebind-tested', len(dnsrebindSet)),
- ('Failed tests', bad),
- ('Succeeded tests', good),
- ('Inconclusive tests', inconclusive),
- ('SSH tests', ssh),
- ('HTTP tests', http),
- ('SSL tests', ssl),
- ('POP tests', pop),
- ('IMAP tests', imap),
- ('SMTP tests', smtp),
- ('DNS tests', dns),
- ('DNS rebind tests', dnsrebind)
- ]
-
- for (k,v) in stats:
- print format % (swidth, k, nwidth, v)
- print '=' * width
-
- def Reply(self, input):
-
- good = bad = inconclusive = False
- protocols = []
-
- if 'a' in input:
- good = bad = inconclusive = True
- protocols.extend(["ssh", "http", "ssl", "imap", "pop", "smtp"])
- else:
- good = 'g' in input
- bad = 'b' in input
- inconclusive = 'i' in input
-
- if 's' in input:
- protocols.append("ssh")
- if 'h' in input:
- protocols.append("http")
- if 'l' in input:
- protocols.append("ssl")
- if 'p' in input:
- protocols.append("imap")
- if 'o' in input:
- protocols.append("pop")
- if 't' in input:
- protocols.append("smtp")
- if 'd' in input:
- protocols.append("dns")
- if 'r' in input:
- protocols.append("dnsrebind")
-
- dh = DataHandler()
- data = dh.getAll()
- filtered = dh.filterResults(data, protocols, good, bad, inconclusive)
-
- nodewidth = 45
- typewidth = 10
- sitewidth = 30
- timewidth = 30
- statuswidth = 6
- width = nodewidth + typewidth + sitewidth + timewidth + statuswidth
-
- format = '%-*s%-*s%-*s%-*s%-*s'
-
- print '=' * width
- print format % (nodewidth, 'Exit node', typewidth, 'Test type', sitewidth, 'Remote site',
- timewidth, 'Time', statuswidth, 'Status')
- print '-' * width
- for result in filtered:
- print format % (nodewidth, `result.exit_node`,
- typewidth, result.__class__.__name__[:-10],
- sitewidth, result.site,
- timewidth, time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime(result.timestamp)),
- statuswidth, `result.status`)
- print '=' * width
-
- def Help(self):
- print ''
- print 'Options:'
- print '* summmary (s) - display a short summary about all tests done so far'
- print '* exit (e) - terminate the program'
- print '* help (h) - display this help text'
- print '* all (a) - list all the results'
- print '* (shlgbi) - display a filtered list of test results. Letters are optional and mean the following:'
- print ' s - show ssh results'
- print ' h - show http results'
- print ' l - show ssl results'
- print ' g - show good results'
- print ' b - show bad results'
- print ' i - show inconclusive results'
- print ' p - show imap results'
- print ' o - show pop results'
- print ' t - show smtp results'
- print ' d - show dns results'
- print ' r - show dnsrebind results'
- print ''
-
-#
-# Displaying stats in a graphical setting (first check if we have wx)
-#
-
-nowx = False
-try:
- import wx
- from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin, ColumnSorterMixin
-except:
- nowx = True
-
-if not nowx:
-
- class ListMixin(wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin):
- def __init__(self, parent, map):
- wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT)
- ListCtrlAutoWidthMixin.__init__(self)
- ColumnSorterMixin.__init__(self, len(map))
- self.itemDataMap = map
-
- def GetListCtrl(self):
- return self
-
- # menu item ids
- ID_EXIT = 1
-
- ID_SHOW_GOOD = 11
- ID_SHOW_BAD = 12
- ID_SHOW_UNSURE = 13
-
- ID_SHOW_SSL = 21
- ID_SHOW_HTTP = 22
- ID_SHOW_SSH = 23
- ID_SHOW_SMTP = 24
- ID_SHOW_IMAP = 25
- ID_SHOW_POP = 26
- ID_SHOW_DNS = 27
- ID_SHOW_DNSREBIND = 28
-
- ID_NODE = 31
-
- class MainFrame(wx.Frame):
- ''' the main application window for displaying statistics with a GUI'''
- def __init__(self):
- wx.Frame.__init__(self, None, title="Soat test results", size=(900,500))
-
- # get the data
-
- self.dataHandler = DataHandler()
- self.dataList = self.dataHandler.getAll()
- self.filteredList = self.dataList
-
- # display it
-
- self.CreateStatusBar()
- self.initMenuBar()
- self.initContent()
-
- self.Center()
- self.Show()
-
- def initMenuBar(self):
- fileMenu = wx.Menu()
- fileMenu.Append(ID_EXIT, "E&xit", "Exit the program")
-
- viewMenu = wx.Menu()
- self.showGood = viewMenu.Append(ID_SHOW_GOOD, 'Show &Good', 'Show sucessful test results', kind=wx.ITEM_CHECK)
- self.showBad = viewMenu.Append(ID_SHOW_BAD, 'Show &Bad', 'Show unsucessful test results', kind=wx.ITEM_CHECK)
- self.showUnsure = viewMenu.Append(ID_SHOW_UNSURE, 'Show &Inconclusive', 'Show inconclusive test results', kind=wx.ITEM_CHECK)
- viewMenu.AppendSeparator()
- self.showSSL = viewMenu.Append(ID_SHOW_SSL, 'Show SS&L', 'Show SSL test results', kind=wx.ITEM_CHECK)
- self.showHTTP = viewMenu.Append(ID_SHOW_HTTP, 'Show &HTTP', 'Show HTTP test results', kind=wx.ITEM_CHECK)
- self.showSSH = viewMenu.Append(ID_SHOW_SSH, 'Show &SSH', 'Show SSH test results', kind=wx.ITEM_CHECK)
- viewMenu.AppendSeparator()
- self.showSMTP = viewMenu.Append(ID_SHOW_SMTP, 'Show SMTP', 'Show SMTP test results', kind=wx.ITEM_CHECK)
- self.showIMAP = viewMenu.Append(ID_SHOW_IMAP, 'Show IMAP', 'Show IMAP test results', kind=wx.ITEM_CHECK)
- self.showPOP = viewMenu.Append(ID_SHOW_POP, 'Show POP', 'Show POP test results', kind=wx.ITEM_CHECK)
- viewMenu.AppendSeparator()
- self.showDNS = viewMenu.Append(ID_SHOW_DNS, 'Show DNS', 'Show DNS test results', kind=wx.ITEM_CHECK)
- self.showDNSRebind = viewMenu.Append(ID_SHOW_DNSREBIND, 'Show DNSRebind', 'Show DNS rebind test results', kind=wx.ITEM_CHECK)
- viewMenu.AppendSeparator()
- viewMenu.Append(ID_NODE, '&Find node...', 'View test results for a given node [NOT IMPLEMENTED]')
-
- menuBar = wx.MenuBar()
- menuBar.Append(fileMenu,"&File")
- menuBar.Append(viewMenu,"&View")
-
- self.SetMenuBar(menuBar)
-
- wx.EVT_MENU(self, ID_EXIT, self.OnExit)
-
- wx.EVT_MENU(self, ID_SHOW_GOOD, self.GenerateFilteredList)
- wx.EVT_MENU(self, ID_SHOW_BAD, self.GenerateFilteredList)
- wx.EVT_MENU(self, ID_SHOW_UNSURE, self.GenerateFilteredList)
- viewMenu.Check(ID_SHOW_GOOD, True)
- viewMenu.Check(ID_SHOW_BAD, True)
- viewMenu.Check(ID_SHOW_UNSURE, True)
-
- for i in range(ID_SHOW_SSL, ID_SHOW_DNSREBIND + 1):
- viewMenu.Check(i, True)
- wx.EVT_MENU(self, i, self.GenerateFilteredList)
-
- def initContent(self):
- base = wx.Panel(self, -1)
- sizer = wx.GridBagSizer(0,0)
-
- box = wx.StaticBox(base, -1, 'Summary')
- boxSizer = wx.StaticBoxSizer(box, wx.HORIZONTAL)
-
- total = wx.StaticText(base, -1, 'Total tests: ' + `len(self.filteredList)`)
- boxSizer.Add(total, 0, wx.LEFT | wx.TOP | wx.BOTTOM, 10)
-
- nodes = wx.StaticText(base, -1, 'Nodes scanned: ' + `len(Set([x.exit_node for x in self.filteredList]))`)
- boxSizer.Add(nodes, 0, wx.LEFT | wx.TOP | wx.BOTTOM , 10)
-
- bad = wx.StaticText(base, -1, 'Failed tests: ' + `len([x for x in self.filteredList if x.status == 2])`)
- boxSizer.Add(bad, 0, wx.LEFT | wx.TOP | wx.BOTTOM, 10)
-
- suspicious = wx.StaticText(base, -1, 'Inconclusive tests: ' + `len([x for x in self.filteredList if x.status == 1])`)
- boxSizer.Add(suspicious, 0, wx.ALL, 10)
-
- sizer.Add(boxSizer, (0,0), (1, 5), wx.EXPAND | wx.ALL, 15)
-
- dataMap = {}
- self.fillDataMap(dataMap)
-
- self.listCtrl = ListMixin(base, dataMap)
- self.listCtrl.InsertColumn(0, 'exit node', width=380)
- self.listCtrl.InsertColumn(1, 'type', width=70)
- self.listCtrl.InsertColumn(2, 'site', width=180)
- self.listCtrl.InsertColumn(3, 'time', width=180)
- self.listCtrl.InsertColumn(4, 'status', wx.LIST_FORMAT_CENTER, width=50)
-
- self.fillListCtrl(dataMap)
-
- sizer.Add(self.listCtrl, (1,0), (1,5), wx.EXPAND | wx.LEFT | wx.BOTTOM | wx.RIGHT, border=15)
-
- sizer.AddGrowableCol(3)
- sizer.AddGrowableRow(1)
-
- base.SetSizerAndFit(sizer)
-
- # make a nasty dictionary from the current self.filteredList object so columns would be sortable
- def fillDataMap(self, dataMap):
- for i in range(len(self.filteredList)):
- dataMap.update([(i,(self.filteredList[i].exit_node,
- self.filteredList[i].__class__.__name__[:-10],
- self.filteredList[i].site,
- time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime(self.filteredList[i].timestamp)),
- self.filteredList[i].status))])
-
- # fill the result listing with data
- def fillListCtrl(self, dataMap):
- if self.listCtrl.GetItemCount() > 0:
- self.listCtrl.DeleteAllItems()
-
- for k, i in dataMap.items():
- index = self.listCtrl.InsertStringItem(sys.maxint, `i[0]`)
- self.listCtrl.SetStringItem(index, 1, i[1])
- self.listCtrl.SetStringItem(index, 2, `i[2]`)
- self.listCtrl.SetStringItem(index, 3, i[3])
- self.listCtrl.SetStringItem(index, 4, `i[4]`)
- self.listCtrl.SetItemData(index,k)
-
- def OnExit(self,e):
- self.Close(True)
-
- def GenerateFilteredList(self, e):
- protocols = []
- if self.showSSH.IsChecked():
- protocols.append("ssh")
- if self.showHTTP.IsChecked():
- protocols.append("http")
- if self.showSSL.IsChecked():
- protocols.append("ssl")
- if self.showIMAP.IsChecked():
- protocols.append("imap")
- if self.showPOP.IsChecked():
- protocols.append("pop")
- if self.showSMTP.IsChecked():
- protocols.append("smtp")
- if self.showDNS.IsChecked():
- protocols.append("dns")
- if self.showDNSRebind.IsChecked():
- protocols.append("dnsrebind")
-
- self.filteredList = list(self.dataHandler.filterResults(self.dataList, protocols,
- self.showGood.IsChecked(), self.showBad.IsChecked(), self.showUnsure.IsChecked()))
-
- dataMap = {}
- self.fillDataMap(dataMap)
- self.fillListCtrl(dataMap)
- self.listCtrl.RefreshItems(0, len(dataMap))
-
-if __name__ == "__main__":
- if len(sys.argv) == 1:
- console = StatsConsole()
- console.Listen()
- elif len(sys.argv) == 2 and sys.argv[1] == 'wx':
- if nowx:
- print 'wxpython doesn\'t seem to be installed on your system'
- print 'you can use the console interface instead (see help)'
- else:
- app = wx.App(0)
- MainFrame()
- app.MainLoop()
- else:
- print ''
- print 'This app displays results of tests carried out by soat.py (in a user-friendly way).'
- print ''
- print 'Usage:'
- print 'python soatstats.py - app starts console-only'
- print 'python soatstats.py wx - app starts with a wxpython gui'
- print ''
Deleted: torflow/trunk/speedracer.pl
===================================================================
--- torflow/trunk/speedracer.pl 2009-01-05 16:27:59 UTC (rev 17914)
+++ torflow/trunk/speedracer.pl 2009-01-05 16:51:38 UTC (rev 17915)
@@ -1,263 +0,0 @@
-#!/usr/bin/perl -w
-
-
-use strict;
-use IO::Socket;
-use IO::Socket::INET;
-use Time::HiRes qw( usleep ualarm gettimeofday tv_interval );
-
-my $META_PORT = "9052";
-my $META_HOST = "127.0.0.1";
-
-my $USER_AGENT = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)";
-
-# http://bitter.stalin.se/torfile
-# http://www.sigma.su.se/~who/torfile
-my $URL = "https://svn.torproject.org/svn/tor/trunk/doc/design-paper/tor-design.pdf";
-my $COUNT = 2;
-my $START_PCT = 0;
-my $STOP_PCT = 20;
-my $PCT_STEP = 5;
-my $DOUBLE_FETCH = 0;
-my $CURL_PROXY="--socks4a 127.0.0.1:9060";
-
-my $LOG_LEVEL = "DEBUG";
-my %log_levels = ("DEBUG", 0, "INFO", 1, "NOTICE", 2, "WARN", 3, "ERROR", 4);
-
-
-sub plog
-{
- my $level = shift;
- my $msg = shift;
- my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
-
- $year += 1900; # lame.
- $mon += 1;
-
- print "$level \[" . localtime() . "\]: " . $msg if($msg && $log_levels{$level} >= $log_levels{$LOG_LEVEL})
- #print "$level\[$year-$mon-$mday $hour:$min:$sec\]: " . $msg if($log_levels{$level} >= $log_levels{$LOG_LEVEL})
-}
-
-sub is_in
-{
- my $element = shift;
- my $ary = shift;
- my $is_there = 0;
- foreach (@$ary) {
- if ($_ eq $element) {
- $is_there = 1;
- last;
- }
- }
-
- return $is_there;
-}
-
-sub compare_arrays {
- my ($first, $second) = @_;
- no warnings; # silence spurious -w undef complaints
- return 0 unless @$first == @$second;
- for (my $i = 0; $i < @$first; $i++) {
- return 0 if $first->[$i] ne $second->[$i];
- }
- return 1;
-}
-
-sub query_exit
-{
- my $mcp = shift;
- my $line;
- print $mcp "GETLASTEXIT\r\n";
- $line = <$mcp>;
- $line =~ /LASTEXIT=([\S]+)/;
-
- return $1;
-}
-
-
-sub speedrace
-{
- my $mcp = shift;
- my $skip = shift;
- my $pct = shift;
- my @build_times;
- my @fetch_times;
- my $tot_fetch_time = 0;
- my $tot_build_time = 0;
- my $i = 0;
- my $line;
-
- # Weak new-nym
- print $mcp "PERCENTSKIP $skip\r\n";
- $line = <$mcp>;
- die "Error setting percentskip: $line" if (not $line =~ /^250/);
-
- print $mcp "PERCENTFAST $pct\r\n";
- $line = <$mcp>;
- die "Error setting percentfast: $line" if (not $line =~ /^250/);
-
- # So this is a really big hack. Since metatroller builds circuits on
- # the fly where as tor has a pool of pre-built circuits to use,
- # we want to get it to build a circuit for us but not count
- # that construction time. The way we do this is to issue
- # a NEWNYM and then get the url TWICE.
-
- while($#build_times+1 < $COUNT) {
- my $t0;
- my $delta_build;
- my $delta_fetch;
- my $fetch_exit;
- my $build_exit;
- my $ret;
-
- print $mcp "NEWNYM\r\n";
- $line = <$mcp>;
- die "Error sending NEWNYM: $line" if (not $line =~ /^250/);
-
- # Build the circuit...
- do {
- $i++;
-
- $t0 = [gettimeofday()];
- $ret =
-# system("tsocks wget -U \"$USER_AGENT\" \'$URL\' -O - 2>&1 > /dev/null");
- system("curl $CURL_PROXY -m 600 -A \"$USER_AGENT\" \'$URL\' >& /dev/null");
-
- if($ret == 2) {
- plog "NOTICE", "wget got Sigint. Dying\n";
- exit;
- }
- plog "NOTICE", "wget failed with ret=$ret.. Retrying...\n"
- if($ret != 0);
- $delta_build = tv_interval $t0;
- plog "NOTICE", "Timer exceeded limit: $delta_build\n"
- if($delta_build >= 550.0);
- } while(0);
-# } while($ret != 0 || $delta_build >= 550.0);
-
- $build_exit = query_exit($mcp);
- $fetch_exit = $build_exit;
-
- plog "DEBUG", "Got 1st via $build_exit\n";
-
- # Now do it for real
- if($DOUBLE_FETCH) {
- do {
- $i++;
- $t0 = [gettimeofday()];
- $ret =
-# system("tsocks wget -U \"$USER_AGENT\" \'$URL\' -O - 2>&1 > /dev/null");
- system("curl $CURL_PROXY -m 600 -A \"$USER_AGENT\" \'$URL\' >& /dev/null");
-
- if($ret == 2) {
- plog "NOTICE", "wget got Sigint. Dying\n";
- exit;
- }
- plog "NOTICE", "wget failed with ret=$ret.. Retrying with clock still running\n"
- if($ret != 0);
- $delta_fetch = tv_interval $t0;
- plog "NOTICE", "Timer exceeded limit: $delta_fetch\n"
- if($delta_fetch >= 550.0);
- } while($ret != 0 || $delta_fetch >= 550.0);
-
- $fetch_exit = query_exit($mcp);
-
- if($fetch_exit eq $build_exit) {
- $tot_build_time += $delta_build;
- push(@build_times, $delta_build);
- plog "DEBUG", "$skip-$pct% circuit build+fetch took $delta_build for $fetch_exit\n";
-
- push(@fetch_times, $delta_fetch);
- $tot_fetch_time += $delta_fetch;
- plog "DEBUG", "$skip-$pct% fetch took $delta_fetch for $fetch_exit\n";
- } else {
- plog "NOTICE", "Ignoring strange exit swap $build_exit -> $fetch_exit. Circuit failure?\n";
- }
- } else {
- $tot_build_time += $delta_build;
- push(@build_times, $delta_build);
- plog "DEBUG", "$skip-$pct% circuit build+fetch took $delta_build for $fetch_exit\n";
- }
- }
- my $avg_build_time = $tot_build_time/($#build_times+1);
- my $build_dev = 0;
- foreach(@build_times) {
- $build_dev +=
- ($_ - $avg_build_time)*($_ - $avg_build_time);
- }
- $build_dev = sqrt($build_dev / ($#build_times+1));
-
- if($DOUBLE_FETCH) {
- my $avg_fetch_time = $tot_fetch_time/($#fetch_times+1);
- my $fetch_dev = 0;
- foreach(@fetch_times) {
- $fetch_dev +=
- ($_ - $avg_fetch_time)*($_ - $avg_fetch_time);
- }
- $fetch_dev = sqrt($fetch_dev / ($#fetch_times+1));
- plog "INFO", "RANGE $skip-$pct " . ($#fetch_times+1) . " fetches: avg=$avg_fetch_time, dev=$fetch_dev\n";
- }
- plog "INFO", "RANGE $skip-$pct " . ($#build_times+1) . " build+fetches: avg=$avg_build_time, dev=$build_dev\n";
- plog "INFO", " " . ($COUNT*($DOUBLE_FETCH+1)) . " fetches took $i tries\n";
-}
-
-sub main
-{
- my $mcp = IO::Socket::INET->new(
- Proto => "tcp",
- PeerAddr => $META_HOST,
- PeerPort => $META_PORT)
- or die "The Metatroller is not enabled";
- my $line = <$mcp>;
- $line = <$mcp>;
-
- delete $ENV{"http_proxy"};
- delete $ENV{"HTTP_PROXY"};
- delete $ENV{"proxy"};
- delete $ENV{"PROXY"};
- $ENV{"TSOCKS_CONF_FILE"} = "./tsocks.conf";
-
- print $mcp "GUARDNODES 0\r\n";
- $line = <$mcp>;
- die "Error setting Guard Nodes: $line" if (not $line =~ /^250/);
-
- print $mcp "UNIFORM 1\r\n";
- $line = <$mcp>;
- die "Error setting UNIFORM: $line" if (not $line =~ /^250/);
-
- print $mcp "ORDEREXITS 1\r\n";
- $line = <$mcp>;
- die "Error setting ORDEREXITS: $line" if (not $line =~ /^250/);
-
- print $mcp "PATHLEN 2\r\n";
- $line = <$mcp>;
- die "Error setting PATHLEN: $line" if (not $line =~ /^250/);
-
- my $pct = $START_PCT;
- plog "INFO", "Beginning time loop\n";
-
- while($pct < $STOP_PCT) {
- print $mcp "RESETSTATS\r\n";
- $line = <$mcp>;
- die "Error on RESETSTATS: $line" if (not $line =~ /^250/);
- print $mcp "COMMIT\r\n";
- $line = <$mcp>;
- die "Error on COMMIT: $line" if (not $line =~ /^250/);
- plog "DEBUG", "Reset stats\n";
- speedrace($mcp, $pct, $pct+$PCT_STEP);
- plog "DEBUG", "speedroced\n";
- print $mcp "CLOSEALLCIRCS\r\n";
- $line = <$mcp>;
- die "Error on CLOSEALLCIRCS: $line" if (not $line =~ /^250/);
- print $mcp "SAVESTATS ./data/speedraces/stats-$pct:".($pct+$PCT_STEP)."\r\n";
- $line = <$mcp>;
- die "Error on SAVESTATS: $line" if (not $line =~ /^250/);
- plog "DEBUG", "Wrote stats\n";
- $pct += $PCT_STEP;
- print $mcp "COMMIT\r\n";
- $line = <$mcp>;
- die "Error on COMMIT: $line" if (not $line =~ /^250/);
- }
-}
-
-main();
Deleted: torflow/trunk/wordlist.txt
===================================================================
--- torflow/trunk/wordlist.txt 2009-01-05 16:27:59 UTC (rev 17914)
+++ torflow/trunk/wordlist.txt 2009-01-05 16:51:38 UTC (rev 17915)
@@ -1,30 +0,0 @@
-document
-important
-download
-setup
-install
-plugin
-file
-program
-run
-microsoft
-windows
-xp
-installer
-slides
-presentation
-paper
-browser
-winscp
-vidalia+bundle
-putty
-torpark
-firefox+setup
-mozilla
-privoxy
-privacy
-extension
-firefox+extension
-example
-sample
-censorship
More information about the tor-commits
mailing list