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