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])