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