+#!/usr/bin/python3
+
+from __future__ import print_function
+
+import sys
+import argparse
+import logging
+import hashlib
+import codecs
+
+from unbound import RR_TYPE_SOA, RR_TYPE_DNSKEY, RR_TYPE_NS
+from unbound import RR_TYPE_A, RR_TYPE_AAAA
+from ldns import LDNS_SECTION_ANSWER
+from ldns import ldns_wire2pkt
+
+
+from check_dane.resolve import Resolver, ResolverException
+from check_dane.resolve import format_address, dnssec_verify_rrsig_validity
+
+
+def check_main_records(resolver, zone, args):
+ """Confirms that the necessary records on a zone all verify"""
+ retval = 0
+
+ for rrtype in [RR_TYPE_DNSKEY, RR_TYPE_NS, RR_TYPE_SOA]:
+ result = resolver.resolve(zone, rrtype=rrtype, secure=True)
+ nretval = dnssec_verify_rrsig_validity(result.packet, args.warndays, args.critdays)
+ retval = max(nretval, retval)
+
+ return retval
+
+
+def check_nsec_cycle(resolver, zone, args):
+ """Confirms that NSEC records are completely available"""
+ return 0
+
+
+def check_synced(resolver, zone, args):
+ """Makes sure the zone is at the same serial on all secondaries"""
+ try:
+ result = resolver.resolve(zone, RR_TYPE_NS, secure=True)
+
+ if result.data is None:
+ logging.error("No nameservers found for zone %s", zone)
+ return 2
+
+ nameservers = result.data.as_domain_list()
+ nameserver_ips = []
+ for nameserver in nameservers:
+ ips = []
+ for rrtype in [RR_TYPE_AAAA, RR_TYPE_A]:
+ result = resolver.resolve(nameserver, rrtype=rrtype, secure=True)
+ if result.data is not None:
+ ips = ips + [format_address(data, rrtype) for data in result.data.data]
+
+ if ips == []:
+ logging.warning("Could not find any address for nameserver %s", nameserver)
+
+ nameserver_ips = nameserver_ips + ips
+
+ if nameserver_ips == []:
+ logging.error("No authoritive nameserver for %s could be resolved", zone)
+ return 2
+
+ results = dict()
+ for ip in nameserver_ips:
+ newresolver = Resolver(args.ancor, ip)
+
+ # We can't request secure here as the authoritative
+ # nameservers for the zone won't let us retrieve the
+ # signature chain below their own zone. We'll later
+ # recheck the SOA record using the main recursor
+ #
+ # Alternatively one could get the DS / DNSKEY for the zone with
+ # resolver and add it to newresolver as a hint.
+ result = newresolver.resolve(zone, rrtype=RR_TYPE_SOA)
+
+ s, result = ldns_wire2pkt(result.packet)
+ if s != 0:
+ logging.error("Parsing packet failed with errorcode %d", s)
+ return 2
+
+ rrs = result.rr_list_by_type(RR_TYPE_SOA, LDNS_SECTION_ANSWER).rrs()
+ soa = next(rrs)
+
+ serial = str(soa).split()[6]
+ results[serial] = results.get(serial, []) + [ip]
+
+ if len(results) == 1:
+ return 0
+ else:
+ logging.error("different SOAs: %s", results)
+ return 2
+
+ except ResolverException as e:
+ logging.error(e.message)
+
+
+def main():
+ logging.basicConfig(format='%(levelname)5s %(message)s')
+ parser = argparse.ArgumentParser()
+ parser.add_argument("Zone")
+
+ parser.add_argument("--verbose", action="store_true")
+ parser.add_argument("--quiet", action="store_true")
+
+ parser.add_argument("-a", "--ancor",
+ action="store", type=str, default="/etc/unbound/root.key",
+ help="DNSSEC root ancor")
+
+ parser.add_argument("--nsec", action="store_false",
+ help="Verifies the complete NSEC/NSEC3 cycle (default: false)")
+ parser.add_argument("--warndays", type=int, default=-1,
+ help="Days before rrsig expiration to warn")
+ parser.add_argument("--critdays", type=int, default=-1,
+ help="Days before rrsig expiration to raise error")
+
+
+ args = parser.parse_args()
+
+ if args.verbose:
+ logging.getLogger().setLevel(logging.DEBUG)
+ elif args.quiet:
+ logging.getLogger().setLevel(logging.WARNING)
+ else:
+ logging.getLogger().setLevel(logging.INFO)
+
+ resolver = Resolver(args.ancor)
+ zone = args.Zone.encode('idna').decode()
+
+ retval1 = check_synced(resolver, zone, args)
+ retval2 = check_main_records(resolver, zone, args)
+ if args.nsec:
+ retval3 = check_nsec_cycle(resolver, zone, args)
+ return max(retval1, retval2, retval3)
+ else:
+ return max(retval1, retval2)
+
+if __name__ == '__main__':
+ sys.exit(main())