# HG changeset patch # User Tero Marttila # Date 1331902991 -7200 # Node ID bddc9a060a7357d976e79ebd6c9360444c3931bd # Parent 879917cebb9c923251900e918b7155d663387d88 process-zone: implement --check-hosts; fix logging processName diff -r 879917cebb9c -r bddc9a060a73 bin/process-zone --- a/bin/process-zone Fri Mar 16 15:02:50 2012 +0200 +++ b/bin/process-zone Fri Mar 16 15:03:11 2012 +0200 @@ -10,7 +10,7 @@ import codecs import logging -log = logging.getLogger() +log = logging.getLogger('main') # command-line options, global state options = None @@ -20,8 +20,10 @@ Parse command-line arguments. """ + prog = argv[0] + parser = optparse.OptionParser( - prog = argv[0], + prog = prog, usage = '%prog: [options]', version = __version__, @@ -38,6 +40,7 @@ parser.add_option_group(general) + # input/output parser.add_option('-c', '--input-charset', metavar='CHARSET', default='utf-8', help="Encoding used for input files") @@ -47,6 +50,14 @@ parser.add_option('--output-charset', metavar='CHARSET', default='utf-8', help="Encoding used for output files") + # check stage + parser.add_option('--check-hosts', action='store_true', + help="Check that host/IPs are unique. Use --quiet to silence warnings, and test exit status") + + parser.add_option('--check-exempt', action='append', + help="Allow given names to have multiple records") + + # forward stage parser.add_option('--forward-zone', action='store_true', help="Generate forward zone") @@ -56,6 +67,7 @@ parser.add_option('--forward-mx', metavar='MX', help="Generate MX records for forward zone") + # reverse stage parser.add_option('--reverse-domain', metavar='DOMAIN', help="Domain to use for hosts in reverse zone") @@ -65,6 +77,8 @@ # defaults parser.set_defaults( loglevel = logging.WARN, + + check_exempt = [], ) # parse @@ -72,7 +86,7 @@ # configure logging.basicConfig( - format = '%(processName)s: %(name)s: %(levelname)s %(funcName)s : %(message)s', + format = prog + ': %(name)s: %(levelname)s %(funcName)s : %(message)s', level = options.loglevel, ) @@ -167,6 +181,48 @@ if data : yield data +def check_zone_hosts (zone, whitelist=None) : + """ + Parse host/IP pairs from the zone, and verify that they are unique. + + As an exception, names listed in the given whitelist may have multiple IPs. + """ + + by_name = {} + by_ip = {} + + fail = None + + for item in zone : + text = ' '.join(pp for pp in item if pp) + name, ttl, type, data, comment = item + + # name + if name not in by_name : + by_name[name] = text + + elif name in whitelist : + log.debug("Duplicate whitelist entry: %r", item) + + else : + # fail! + log.warn("Duplicate name: %s <-> %s", text, by_name[name]) + fail = True + + # ip + if type == 'A' : + ip = data + + if ip not in by_ip : + by_ip[ip] = text + + else : + # fail! + log.warn("Duplicate IP: %s <-> %s", text, by_ip[ip]) + fail = True + + return fail + def process_zone_forwards (zone, txt=False, mx=False) : """ Process zone data -> forward zone data. @@ -307,6 +363,19 @@ zone += list(parse_zone(file)) + # check? + if options.check_hosts : + whitelist = set(options.check_exempt) + + log.debug("checking hosts; whitelist=%r", whitelist) + + if check_zone_hosts(zone, whitelist=whitelist) : + log.warn("Hosts check failed") + return 2 + + else : + log.info("Hosts check OK") + # output file output = open_file(options.output, 'w', options.output_charset) @@ -325,8 +394,13 @@ zone = list(process_zone_reverse(zone, origin=origin, domain=domain)) + elif options.check_hosts : + # we only did that, done + return 0 + else : log.warn("Nothing to do") + return 1 write_zone(output, zone)