bin/pvl.dns-zone
author Tero Marttila <terom@paivola.fi>
Mon, 09 Mar 2015 23:31:13 +0200
changeset 738 3104fdf7ea26
parent 641 9d36e312e6a7
permissions -rw-r--r--
pvl.hosts.hosts: drop support for instanced ip.* in favor of improved interface:ip.* =
#!/usr/bin/env python

"""
    Generate bind zonefiles from a given input zonefile.

    Takes a zonefile as input, and gives a new zonefile as output.
"""

import ipaddr
import logging; log = logging.getLogger('pvl.dns-generate')
import optparse
import pvl.args
import pvl.dns.reverse
import pvl.dns.process

def check_zone (rrs, whitelist_names=set(), whitelist_types=set()):
    """
        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 = {}

    check = True

    for rr in rrs:
        name = (rr.origin, rr.name)

        # name
        if name not in by_name:
            pass

        elif rr.type in whitelist_types:
            log.debug("%s: Whitelist type duplicate: %s", rr, by_name[name])

        elif rr.name in whitelist_names:
            log.debug("%s: Whitelist name duplicate: %s", rr, by_name[name])

        else:
            log.warn("%s: Duplicate name: %s <-> %s", rr.line, rr, by_name[name])
            check = False
            
        by_name[name] = rr

        # ip
        if rr.type in ('A', 'AAAA'):
            ip, = rr.data

            if ip in by_ip:
                log.warn("%s: Duplicate IP: %s <-> %s", rr.line, rr, by_ip[ip])
                check = False
                
            by_ip[ip] = rr

    return check

def process_zone_reverse (rrs, prefix):
    """
        Process zone data -> reverse zone data.
    """

    for r in rrs:
        if r.type == 'A':
            ip, = r.data

            ip = ipaddr.IPv4Address(ip)

        elif r.type == 'AAAA':
            ip, = r.data
            
            ip = ipaddr.IPv6Address(ip)
            
        else:
            continue

        if ip not in prefix:
            log.debug("%s: skip: %s not in %s", rr, ip, prefix)
            continue

        ptr = pvl.dns.reverse_label(prefix, ip)
        fqdn = pvl.dns.fqdn(r.name, r.origin)

        yield pvl.dns.ZoneRecord.PTR(ptr, fqdn)

def main (argv):
    parser = optparse.OptionParser(main.__doc__)
    parser.add_option_group(pvl.args.parser(parser))
    parser.add_option_group(pvl.dns.process.optparser(parser))

    parser.add_option('--zone-origin',          metavar='DOMAIN',
            help="Domain to use for hosts in zone")

    # 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',         metavar='HOST', action='append',
            help="Allow given names to have multiple records")

    # reverse stage
    parser.add_option('--reverse-prefix',         metavar='NET',
            help="Generate forward zone for given subnet (192.0.2, 2001:db8)")


    parser.set_defaults(
        check_exempt        = [
            '@'
        ],
    )

    # input
    options, args = pvl.args.parse(parser, argv)

    if options.reverse_prefix and not options.zone_origin:
        log.error("--reverse-prefix requires --zone-origin")
    
    zone = list(pvl.dns.process.apply_zone_records(options, options.zone_origin, args))

    # check
    if options.check_hosts:
        whitelist_names = set(options.check_exempt)

        log.info("Checking hosts: whitelist_names=%r", whitelist_names)

        if not check_zone(zone, whitelist_names=whitelist_names):
            log.error("Check zone failed, see warnings")
            return 2

    # transform
    if options.reverse_prefix:
        prefix = pvl.dns.reverse.parse_prefix(options.reverse_prefix)

        zone = list(process_zone_reverse(zone, prefix))
    else:
        # pass through
        pass

    pvl.dns.process.apply_zone_output(options, zone)

    return 0

if __name__ == '__main__':
    pvl.args.main(main)