Below is the file 'dnstrawl.py' from this revision. You can also download the file.

#!/usr/bin/python

import sys
import os
import popen2
import pipes
import socket
from goatpy.utility import run_command

def axfr(server, domain):
	cmd = '/usr/bin/dig %s AXFR %s' % (pipes.quote("@"+server), pipes.quote(domain))
	result = run_command(cmd, timeout=3, send_kill=True)
	if result['exitcode'] != 0:
		return None
	else:
		return result['fromchild']

def parse_domain(data):
	rv = {}
	data = data.split('\n')
	# eg:
	#www-images.uwa.edu.au.  86400   IN      A       130.95.3.35
	#xtal.uwa.edu.au.        86400   IN      CNAME   xtal.crystal.uwa.edu.au.
	for line in data:
		line = line.strip()
		if line.startswith(';'): continue
		fields = line.split(None, 4)
		if len(fields) != 5: continue
		name, ttl, type, subtype, value = fields
		if not rv.has_key(name):
			rv[name] = []
		rv[name].append((ttl, type, subtype, value))
	return rv

def normalise_name(s):
	s = s.strip()
	if len(s) == 0: return s
	if s[-1] == '.': s = s[:-1]
	return s

def print_error(domain_name, priority, description):
	print "%-10s\t%-25s\t%s" % (priority, domain_name, description)

def uwa_mx_okay(domain_name, values):
	uwa_antivirus_addresses = ['130.95.128.56', '130.95.128.57']
	# if the primary MX is within UWA, then all other MXs must be
	# on the same subnet.
	mx = map(lambda x: x[3].split(None, 1), filter(lambda x: x[2] == "MX", values))
	mx.sort(lambda x,y: cmp(x[0], y[0]))
	# can't have a problem unless there are at least two MX entries
	if len(mx) < 2: return
	primary_mx_address = None
	for id, (priority, hostname) in enumerate(mx):
		is_primary = id == 0
		try:
			address = socket.gethostbyname(hostname)
		except:
			if id == 0: priority = "critical"
			else: priority = "alert"
			print_error(domain_name, priority, "unable to lookup MX %s (rank %d)" % (hostname, id))
			continue
		if id == 0:
			primary_mx_address = address
		else:
			# special case; if the primary MX is asclepius, nothing can go wrong.
			if primary_mx_address in uwa_antivirus_addresses:
				continue
			# special case; if this secondary address is an asclepius address, nothing can go wrong
			if address in uwa_antivirus_addresses:
				continue
			if primary_mx_address != None and primary_mx_address.startswith('130.95.'):
				if primary_mx_address.split('.')[:3] != address.split('.')[:3]:
					print_error(domain_name, "critical", "MX on wrong subnet: primary=%s,secondary=%s" % (primary_mx_address, address))

checks = [uwa_mx_okay]

if __name__ == '__main__':
	start_server, start_domain = sys.argv[1:]
	domains_to_process = [(start_domain, [start_server])]
	processed = set()

	while len(domains_to_process) > 0:
		(this_domain, this_servers), domains_to_process = domains_to_process[0], domains_to_process[1:]

		for server in this_servers:
			#print "retrieving %s from %s" % (this_domain, server)
			data = axfr(server, this_domain)
			if data != None:
				break
		if data == None: continue
		processed.add(normalise_name(this_domain))
		parsed = parse_domain(data)

		for name in parsed:
			descend_to = []
			for ttl, type, subtype, value in parsed[name]:
				if subtype == "NS": descend_to.append(value)
			if len(descend_to) != 0 and normalise_name(name) not in processed:
				domains_to_process.append((name, descend_to))
			else:
				for check in checks:
					check(name, parsed[name])