diff -r 1e925a1cc8de -r 600ad9eb6f25 pvl/hosts/zone.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pvl/hosts/zone.py Tue Feb 24 21:38:12 2015 +0200 @@ -0,0 +1,251 @@ +""" + Generate zonefile records from hosts +""" + +import ipaddr +import logging; log = logging.getLogger('pvl.hosts.zone') +import pvl.dns + +class HostZoneError(Exception): + pass + +def resolve (origin, domain, name) : + """ + Resolve relative CNAME for label@origin -> alias@domain + """ + + fqdn = pvl.dns.join(name, domain) + + if not origin: + return fqdn + + elif domain == origin: + return name + + elif fqdn.endswith('.' + origin): + return pvl.dns.relative(origin, fqdn) + + elif domain: + raise HostZoneError("{name}: domain {domain} out of zone {origin}".format(name=name, domain=domain, origin=origin)) + + else: + raise HostZoneError("{name}: fqdn {fqdn} out of zone {origin}".format(name=name, fqdn=fqdn, origin=origin)) + +def host_forward (host, origin) : + """ + Yield ZoneRecords for hosts within the given zone origin + """ + + try: + label = resolve(origin, host.domain, host.name) + except HostZoneError as error: + log.info("%s: skip: %s", host, error) + return + + if host.forward: + forward = pvl.dns.zone.fqdn(host.forward) + + log.info("%s: forward: %s", host, forward) + + yield pvl.dns.ZoneRecord.CNAME(label, forward) + return + + elif host.forward is not None: + log.info("%s: skip forward", host) + return + + # forward + if host.ip : + yield pvl.dns.ZoneRecord.A(label, host.ip) + + if host.ip6 : + yield pvl.dns.ZoneRecord.AAAA(label, host.ip6) + + if host.location: + location_alias, location_domain = host.location + + if not location_domain: + location_domain = host.domain + + yield pvl.dns.ZoneRecord.CNAME(resolve(origin, location_domain, location_alias), label) + + for alias in host.alias4: + yield pvl.dns.ZoneRecord.CNAME(resolve(origin, host.domain, alias), label) + + for alias in host.alias4: + yield pvl.dns.ZoneRecord.A(resolve(origin, host.domain, alias), host.ip) + + for alias in host.alias6: + yield pvl.dns.ZoneRecord.AAAA(resolve(origin, host.domain, alias), host.ip6) + +def host_reverse (host, prefix) : + """ + Yield (ip, fqnd) for host within given prefix. + """ + + if prefix.version == 4 : + ip = host.ip + elif prefix.version == 6 : + ip = host.ip6 + else : + raise ValueError("%s: unknown ip version: %s" % (prefix, prefix.version)) + + if not ip : + log.debug("%s: no ip%d", host, prefix.version) + return + + if ip not in prefix : + log.debug("%s: %s out of prefix: %s", host, ip, prefix) + return + + # relative label + label = pvl.dns.reverse_label(prefix, ip) + + if host.reverse : + alias = pvl.dns.zone.fqdn(host.reverse) + + log.info("%s %s[%s]: CNAME %s", host, prefix, ip, alias) + + yield ip, pvl.dns.zone.ZoneRecord.CNAME(label, alias) + + elif host.reverse is None : + fqdn = host.fqdn() + + log.info("%s %s[%s]: PTR %s", host, prefix, ip, fqdn) + + yield ip, pvl.dns.zone.ZoneRecord.PTR(label, fqdn) + + else : + log.info("%s %s[%s]: omit", host, prefix, ip) + +def apply_hosts_forward (options, hosts, origin) : + """ + Generate DNS ZoneRecords for for hosts within the given zone origin. + + Yields ZoneRecords in Host-order + """ + + if options.add_origin : + yield pvl.dns.ZoneDirective.build(None, 'ORIGIN', origin) + + by_name = dict() + by_cname = dict() + + for host in hosts: + if not host.domain: + log.debug("%s: skip without domain", host) + + for rr in host_forward(host, origin) : + if rr.name in by_cname: + raise HostZoneError(host, "{host}: CNAME {cname} conflict: {rr}".format(host=host, cname=by_cname[rr.name].name, rr=rr)) + elif rr.type == 'CNAME' and rr.name in by_name: + raise HostZoneError(host, "{host}: CNAME {cname} conflict: {rr}".format(host=host, cname=rr.name, rr=by_name[rr.name])) + + by_name[rr.name] = rr + + if rr.type == 'CNAME': + by_cname[rr.name] = rr + + # preserve ordering + yield rr + +def apply_hosts_reverse (options, hosts, prefix) : + """ + Generate DNS ZoneRecords within the given prefix's reverse-dns zone for hosts. + + Yields ZoneRecords in IPAddress-order + """ + + # collect data for records + by_ip = dict() + + for host in hosts: + for ip, rr in host_reverse(host, prefix) : + if ip in by_ip : + raise HostZoneError(host, "{host}: IP {ip} conflict: {other}".format(host=host, ip=ip, other=by_ip[ip])) + + # do not retain order + by_ip[ip] = rr + + if options.unknown_host : + # enumerate all of them + iter_ips = prefix.iterhosts() + else : + iter_ips = sorted(by_ip) + + for ip in iter_ips : + if ip in by_ip : + yield by_ip[ip] + + elif options.unknown_host: + # synthesize a record + label = pvl.dns.reverse_label(prefix, ip) + fqdn = pvl.dns.zone.fqdn(options.unknown_host, options.hosts_domain) + + log.info("%s %s[%s]: unused PTR %s", options.unknown_host, ip, prefix, fqdn) + + yield pvl.dns.zone.ZoneRecord.PTR(label, fqdn) + + else : + continue + +import pvl.args +import pvl.hosts.config + +import optparse + +def forward_main () : + """ + Generate bind zonefiles from host definitions. + """ + + parser = optparse.OptionParser(forward_main.__doc__) + parser.add_option_group(pvl.args.parser(parser)) + parser.add_option_group(pvl.hosts.config.optparser(parser)) + + parser.add_option('--add-origin', action='store_true', + help="Include $ORIGIN directive in zone") + + parser.add_option('--forward-zone', metavar='DOMAIN', + help="Generate forward zone for domain") + + # input + options, args = parser.parse_args() + + pvl.args.apply(options) + + hosts = pvl.hosts.apply(options, args) + + # process + for rr in apply_hosts_forward(options, hosts, options.forward_zone): + print unicode(rr) + + return 0 + +def reverse_main () : + """ + Generate bind zonefiles from host definitions. + """ + + parser = optparse.OptionParser(reverse_main.__doc__) + parser.add_option_group(pvl.args.parser(parser)) + parser.add_option_group(pvl.hosts.config.optparser(parser)) + + parser.add_option('--reverse-zone', metavar='PREFIX', + help="Generate reverse zone for prefix") + + parser.add_option('--unknown-host', metavar='NAME', + help="Generate records for unused IPs") + + # input + options, args = parser.parse_args() + + pvl.args.apply(options) + + hosts = pvl.hosts.apply(options, args) + + # process + for rr in apply_hosts_reverse(options, hosts, pvl.dns.parse_prefix(options.reverse_zone)): + print unicode(rr) + + return 0