From: Matthijs Mekking Date: Wed, 15 Oct 2025 14:04:28 +0000 (+0200) Subject: Change output of rndc dnssec -status X-Git-Tag: v9.21.16~11^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0941b5754cb5a2701051e1b7b890114a23fa4755;p=thirdparty%2Fbind9.git Change output of rndc dnssec -status Wrap 'dns_keymgr_status()' in 'dns_zone_dnssecstatus()' so we can easily retrieve the zone string name and refresh key time value. In addition to the current time, output when the next key event is expected. Don't log keys that are completely hidden unless verbose is set. Don't log key state values unless verbose is set, or they are in a weird state. For expected key states, log a more useful message of the stage of the rollover. If we are in the middle of a key rollover, don't log when the next key rollover is scheduled. Condense the output for better readability. --- diff --git a/bin/named/server.c b/bin/named/server.c index 7c370ddb247..51505941170 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -14344,10 +14344,8 @@ named_server_dnssec(named_server_t *server, isc_lex_t *lex, * Output the DNSSEC status of the key and signing policy. */ isc_result_t r; - LOCK(&kasp->lock); - r = dns_keymgr_status(kasp, &keys, now, &output[0], - sizeof(output)); - UNLOCK(&kasp->lock); + r = dns_zone_dnssecstatus(zone, kasp, &keys, now, verbose, + &output[0], sizeof(output)); CHECK(putstr(text, output)); if (r != ISC_R_SUCCESS) { CHECK(putstr(text, diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index f5d8d94ba4b..2d6f739c030 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -2306,6 +2306,26 @@ dst_key_tkeytoken(const dst_key_t *key) { return key->key_tkeytoken; } +/* + * A key is considered hidden if it has all states set to HIDDEN or NA. + * + */ +bool +dst_key_is_hidden(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + + for (int i = 0; i < DST_MAX_KEYSTATES; i++) { + dst_key_state_t st; + if (dst_key_getstate(key, i, &st) != ISC_R_SUCCESS) { + st = DST_KEY_STATE_HIDDEN; + } + if (st != DST_KEY_STATE_HIDDEN) { + return false; + } + } + return true; +} + /* * A key is considered unused if it does not have any timing metadata set * other than "Created". diff --git a/lib/dns/include/dns/keymgr.h b/lib/dns/include/dns/keymgr.h index 8e1508304ac..50b9c18419f 100644 --- a/lib/dns/include/dns/keymgr.h +++ b/lib/dns/include/dns/keymgr.h @@ -167,19 +167,20 @@ dns_keymgr_rollover(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, isc_result_t dns_keymgr_status(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, - isc_stdtime_t now, char *out, size_t out_len); + isc_buffer_t *buf, isc_stdtime_t now, bool verbose, + bool checkds); /*%< * Retrieve the status of given 'kasp' policy and keys in the - * 'keyring' and store the printable output in the 'out' buffer. + * 'keyring' and store the printable output in the 'buf' buffer. * * Requires: *\li 'kasp' is not NULL. *\li 'keyring' is not NULL. - *\li 'out' is not NULL. + *\li 'buf' is not NULL. * * Returns: *\li ISC_R_SUCCESS on success. - *\li ISC_R_NOSPACE if the 'out' buffer is too small. + *\li ISC_R_NOSPACE if the 'buf' buffer is too small. *\li ISC_R_FAILURE if other error occurred. *\li Printable status in 'out'. * diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index d9821e7213b..04aa03dd92a 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -691,6 +691,28 @@ dns_zone_unload(dns_zone_t *zone); *\li 'zone' to be a valid zone. */ +isc_result_t +dns_zone_dnssecstatus(dns_zone_t *zone, dns_kasp_t *kasp, + dns_dnsseckeylist_t *keys, isc_stdtime_t now, + bool verbose, char *out, size_t out_len); +/*%< + * Retrieve the DNSSEC status of given 'zone' and store the printable output + * in the 'out' buffer. + * + * Requires: + *\li 'zone' is not NULL. + *\li 'kasp' is not NULL. + *\li 'keys' is not NULL. + *\li 'out' is not NULL. + * + * Returns: + *\li ISC_R_SUCCESS on success. + *\li ISC_R_NOSPACE if the 'out' buffer is too small. + *\li ISC_R_FAILURE if other error occurred. + *\li Printable status in 'out'. + * + */ + dns_kasp_t * dns_zone_getkasp(dns_zone_t *zone); /*%< diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index 061369d50ca..622b564f121 100644 --- a/lib/dns/include/dst/dst.h +++ b/lib/dns/include/dst/dst.h @@ -1103,6 +1103,15 @@ dst_key_is_unused(const dst_key_t *key); * 'key' to be valid. */ +bool +dst_key_is_hidden(const dst_key_t *key); +/*%< + * Check if this key is completely hidden. + * + * Requires: + * 'key' to be valid. + */ + bool dst_key_is_published(dst_key_t *key, isc_stdtime_t now, isc_stdtime_t *publish); /*%< diff --git a/lib/dns/keymgr.c b/lib/dns/keymgr.c index 892fcaafda7..6ccb4ef8774 100644 --- a/lib/dns/keymgr.c +++ b/lib/dns/keymgr.c @@ -2552,113 +2552,311 @@ failure: } static isc_result_t -rollover_status(dns_dnsseckey_t *dkey, dns_kasp_t *kasp, isc_stdtime_t now, - isc_buffer_t *buf, bool zsk) { - char timestr[26]; /* Minimal buf as per ctime_r() spec. */ +keystate_status(dst_key_t *key, isc_buffer_t *buf, const char *pre, int ks) { + dst_key_state_t state = NA; isc_result_t result = ISC_R_SUCCESS; - isc_stdtime_t active_time = 0; - dst_key_state_t state = NA, goal = NA; - int rrsig, active, retire; - dst_key_t *key = dkey->key; - if (zsk) { - rrsig = DST_KEY_ZRRSIG; - active = DST_TIME_ACTIVATE; - retire = DST_TIME_INACTIVE; - } else { - rrsig = DST_KEY_KRRSIG; - active = DST_TIME_PUBLISH; - retire = DST_TIME_DELETE; + (void)dst_key_getstate(key, ks, &state); + switch (state) { + case HIDDEN: + RETERR(isc_buffer_printf(buf, " - %shidden\n", pre)); + break; + case RUMOURED: + RETERR(isc_buffer_printf(buf, " - %srumoured\n", pre)); + break; + case OMNIPRESENT: + RETERR(isc_buffer_printf(buf, " - %somnipresent\n", pre)); + break; + case UNRETENTIVE: + RETERR(isc_buffer_printf(buf, " - %sunretentive\n", pre)); + break; + case NA: + default: + /* print nothing */ + break; } - RETERR(isc_buffer_printf(buf, "\n")); +failure: + return result; +} + +static isc_result_t +rollover_status(dns_dnsseckey_t *dkey, dns_kasp_t *kasp, + dns_dnsseckeylist_t *keyring, isc_stdtime_t now, + isc_buffer_t *buf, bool *verbose, bool checkds) { + isc_result_t result = ISC_R_SUCCESS; + dst_key_t *key = dkey->key; + dst_key_state_t goal = HIDDEN; + dst_key_state_t dnskey = HIDDEN; + dst_key_state_t zrrsig = HIDDEN; + dst_key_state_t ds = HIDDEN; + bool ksk = false; + bool zsk = false; + bool log_next_rollover = false; + int active_state = DST_TIME_ACTIVATE; + int retire_state = DST_TIME_INACTIVE; (void)dst_key_getstate(key, DST_KEY_GOAL, &goal); - (void)dst_key_getstate(key, rrsig, &state); - (void)dst_key_gettime(key, active, &active_time); - if (active_time == 0) { - // only interested in keys that were once active. - return ISC_R_SUCCESS; + (void)dst_key_getstate(key, DST_KEY_DNSKEY, &dnskey); + (void)dst_key_getstate(key, DST_KEY_ZRRSIG, &zrrsig); + (void)dst_key_getstate(key, DST_KEY_DS, &ds); + + // publish status + RETERR(keytime_status(key, now, buf, " Published: ", DST_KEY_DNSKEY, + DST_TIME_PUBLISH)); + + // signing status + result = dst_key_getbool(key, DST_BOOL_KSK, &ksk); + if (result == ISC_R_SUCCESS && ksk) { + RETERR(keytime_status(key, now, buf, " Key signing: ", + DST_KEY_KRRSIG, DST_TIME_PUBLISH)); + } + result = dst_key_getbool(key, DST_BOOL_ZSK, &zsk); + if (result == ISC_R_SUCCESS && zsk) { + RETERR(keytime_status(key, now, buf, " Zone signing: ", + DST_KEY_ZRRSIG, DST_TIME_ACTIVATE)); } - if (goal == HIDDEN && (state == UNRETENTIVE || state == HIDDEN)) { - isc_stdtime_t remove_time = 0; - // is the key removed yet? - state = NA; - (void)dst_key_getstate(key, DST_KEY_DNSKEY, &state); - if (state == RUMOURED || state == OMNIPRESENT) { - result = dst_key_gettime(key, DST_TIME_DELETE, - &remove_time); - if (result == ISC_R_SUCCESS) { + if (zsk) { + if (goal == OMNIPRESENT) { + if (dnskey == HIDDEN && zrrsig == HIDDEN) { + RETERR(isc_buffer_printf( + buf, " Key is created but not " + "published yet.\n")); + } else if (dnskey == RUMOURED && zrrsig == HIDDEN) { + RETERR(isc_buffer_printf( + buf, " Key is pre-published.\n")); + } else if (dnskey == RUMOURED && zrrsig == RUMOURED) { + RETERR(isc_buffer_printf(buf, " Introducing " + "new key.\n")); + } else if (dnskey == OMNIPRESENT && zrrsig == HIDDEN) { + RETERR(isc_buffer_printf( + buf, " Key is published, but not yet " + "signing.\n")); + } else if (dnskey == OMNIPRESENT && zrrsig == RUMOURED) + { + if (keymgr_dep(key, keyring, NULL)) { + RETERR(isc_buffer_printf( + buf, + " Key is published, waiting " + "for the zone to be completely " + "signed with this key.\n")); + } else { + RETERR(isc_buffer_printf( + buf, + " Key is published, " + "introducing signatures.\n")); + } + } else if (dnskey == OMNIPRESENT && + zrrsig == OMNIPRESENT) + { + if (!ksk) { + log_next_rollover = true; + } + } else { RETERR(isc_buffer_printf( - buf, " Key is retired, will be " - "removed on ")); - isc_stdtime_tostring(remove_time, timestr, - sizeof(timestr)); - RETERR(isc_buffer_printf(buf, "%s", timestr)); + buf, " Key is in unexpected state, " + "performing auto-healing.\n")); + *verbose = true; + } + } else if (goal == HIDDEN) { + if (dnskey == OMNIPRESENT && zrrsig == OMNIPRESENT) { + if (!ksk) { + RETERR(isc_buffer_printf( + buf, " Key will be retired " + "after successor key " + "becomes active.\n")); + } + } else if (dnskey == OMNIPRESENT && + zrrsig == UNRETENTIVE) + { + RETERR(isc_buffer_printf( + buf, + " Key is retired, waiting until all " + "signatures generated with this key " + "are replaced with successor.\n")); + } else if (dnskey == OMNIPRESENT && zrrsig == HIDDEN) { + RETERR(isc_buffer_printf( + buf, " Key is retired, no longer " + "signing the zone.\n")); + } else if (dnskey == UNRETENTIVE && zrrsig == HIDDEN) { + RETERR(isc_buffer_printf( + buf, " Key is removed from zone.\n")); + } else if (dnskey == HIDDEN && zrrsig == HIDDEN) { + RETERR(isc_buffer_printf( + buf, " Key is completely hidden " + "(waiting to be purged).\n")); + } else { + RETERR(isc_buffer_printf( + buf, " WARNING: Key is in unexpected " + "state, " + "performing auto-healing.\n")); + *verbose = true; } - } else { - RETERR(isc_buffer_printf(buf, " Key has been removed " - "from the zone")); } - } else { - isc_stdtime_t retire_time = 0; - result = dst_key_gettime(key, retire, &retire_time); - if (result == ISC_R_SUCCESS) { - if (now < retire_time) { - if (goal == OMNIPRESENT) { + } else if (ksk) { + if (goal == OMNIPRESENT) { + if (dnskey == HIDDEN && ds == HIDDEN) { + if (!zsk) { + RETERR(isc_buffer_printf( + buf, " Key is created but not " + "published yet.\n")); + } + } else if (dnskey == RUMOURED && ds == HIDDEN) { + if (!zsk) { + RETERR(isc_buffer_printf( + buf, + " Key is pre-published.\n")); + } + } else if (dnskey == OMNIPRESENT && ds == HIDDEN) { + if (keymgr_dep(key, keyring, NULL)) { RETERR(isc_buffer_printf( - buf, " Next rollover " - "scheduled on ")); - retire_time = keymgr_prepublication_time( - dkey, kasp, - retire_time - active_time, now); + buf, + " Waiting for the DS to be " + "submitted to the parent.\n")); } else { RETERR(isc_buffer_printf( - buf, " Key will retire on ")); + buf, + " Wait for zone to be fully " + "signed before submitting the " + "DS to the parent.\n")); + } + } else if (dnskey == OMNIPRESENT && ds == RUMOURED) { + isc_stdtime_t dstime = now; + isc_result_t ret = dst_key_gettime( + key, DST_TIME_DSPUBLISH, &dstime); + if (ret != ISC_R_SUCCESS || dstime > now) { + RETERR(isc_buffer_printf( + buf, + " Waiting for the DS to be " + "published to the parent.\n")); + if (checkds) { + RETERR(isc_buffer_printf( + buf, + " checkds is enabled, " + "BIND will check the " + "DS RRset " + "periodically.\n")); + } else { + RETERR(isc_buffer_printf( + buf, + " ! Once the DS is in " + "the parent, run 'rndc " + "dnssec -checkds -key " + "%d published' to mark " + "it as published.\n", + dst_key_id(key))); + } + } else { + RETERR(isc_buffer_printf( + buf, " Waiting TTL period for " + "validators to pick up " + "the new DS RRset.\n")); + } + } else if (dnskey == OMNIPRESENT && ds == OMNIPRESENT) { + log_next_rollover = true; + active_state = DST_TIME_PUBLISH; + retire_state = DST_TIME_DELETE; + } else { + RETERR(isc_buffer_printf( + buf, " WARNING: Key is in unexpected " + "state, " + "performing auto-healing.\n")); + *verbose = true; + } + } else if (goal == HIDDEN) { + if (dnskey == OMNIPRESENT && ds == OMNIPRESENT) { + RETERR(isc_buffer_printf( + buf, + " Key will be retired after the DS is " + "withdrawn from the parent.\n")); + } else if (dnskey == OMNIPRESENT && ds == UNRETENTIVE) { + isc_stdtime_t dstime = now; + isc_result_t ret = dst_key_gettime( + key, DST_TIME_DSDELETE, &dstime); + if (ret != ISC_R_SUCCESS || dstime > now) { + RETERR(isc_buffer_printf( + buf, + " Waiting for the DS to be " + "removed from the parent.\n")); + if (checkds) { + RETERR(isc_buffer_printf( + buf, + " checkds is enabled, " + "BIND will check the " + "DS RRset " + "periodically.\n")); + } else { + RETERR(isc_buffer_printf( + buf, + " ! Once the DS is " + "removed from the " + "parent, run 'rndc " + "dnssec -checkds -key " + "%d withdrawn' to mark " + "it as withdrawn.\n", + dst_key_id(key))); + } + } else { + RETERR(isc_buffer_printf( + buf, " Waiting TTL period for " + "validators to pick up " + "the new DS RRset.\n")); + } + } else if (dnskey == OMNIPRESENT && ds == HIDDEN) { + RETERR(isc_buffer_printf( + buf, " Key is removed from chain of " + "trust.\n")); + } else if (dnskey == UNRETENTIVE && ds == HIDDEN) { + if (!zsk) { + RETERR(isc_buffer_printf( + buf, " Key is removed from " + "zone.\n")); + } + } else if (dnskey == HIDDEN && ds == HIDDEN) { + if (!zsk) { + RETERR(isc_buffer_printf( + buf, + " Key is completely hidden " + "(waiting to be purged).\n")); } + } else { + RETERR(isc_buffer_printf( + buf, " WARNING: Key is in unexpected " + "state, " + "performing auto-healing.\n")); + *verbose = true; + } + } + } + + // rollover status + if (log_next_rollover) { + isc_stdtime_t active_time = 0; + isc_stdtime_t retire_time = 0; + (void)dst_key_gettime(key, active_state, &active_time); + result = dst_key_gettime(key, retire_state, &retire_time); + if (result == ISC_R_SUCCESS) { + char timestr[26]; /* Minimal buf as per ctime_r() spec. + */ + if (now < retire_time) { + RETERR(isc_buffer_printf(buf, " Next rollover " + "scheduled on ")); + retire_time = keymgr_prepublication_time( + dkey, kasp, retire_time - active_time, + now); } else { RETERR(isc_buffer_printf(buf, " Rollover is " "due since ")); } isc_stdtime_tostring(retire_time, timestr, sizeof(timestr)); - RETERR(isc_buffer_printf(buf, "%s", timestr)); + RETERR(isc_buffer_printf(buf, "%s\n", timestr)); } else { RETERR(isc_buffer_printf(buf, - " No rollover scheduled")); + " No rollover scheduled.\n")); } } - RETERR(isc_buffer_printf(buf, "\n")); - -failure: - return result; -} - -static isc_result_t -keystate_status(dst_key_t *key, isc_buffer_t *buf, const char *pre, int ks) { - dst_key_state_t state = NA; - isc_result_t result = ISC_R_SUCCESS; - - (void)dst_key_getstate(key, ks, &state); - switch (state) { - case HIDDEN: - RETERR(isc_buffer_printf(buf, " - %shidden\n", pre)); - break; - case RUMOURED: - RETERR(isc_buffer_printf(buf, " - %srumoured\n", pre)); - break; - case OMNIPRESENT: - RETERR(isc_buffer_printf(buf, " - %somnipresent\n", pre)); - break; - case UNRETENTIVE: - RETERR(isc_buffer_printf(buf, " - %sunretentive\n", pre)); - break; - case NA: - default: - /* print nothing */ - break; - } failure: return result; @@ -2666,76 +2864,58 @@ failure: isc_result_t dns_keymgr_status(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, - isc_stdtime_t now, char *out, size_t out_len) { - isc_buffer_t buf; + isc_buffer_t *buf, isc_stdtime_t now, bool verbose, + bool checkds) { isc_result_t result = ISC_R_SUCCESS; - char timestr[26]; /* Minimal buf as per ctime_r() spec. */ REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(keyring != NULL); - REQUIRE(out != NULL); - - isc_buffer_init(&buf, out, out_len); - - // policy name - RETERR(isc_buffer_printf(&buf, "dnssec-policy: %s\n", - dns_kasp_getname(kasp))); - RETERR(isc_buffer_printf(&buf, "current time: ")); - isc_stdtime_tostring(now, timestr, sizeof(timestr)); - RETERR(isc_buffer_printf(&buf, "%s\n", timestr)); + REQUIRE(buf != NULL); ISC_LIST_FOREACH(*keyring, dkey, link) { char algstr[DNS_NAME_FORMATSIZE]; - bool ksk = false, zsk = false; if (dst_key_is_unused(dkey->key)) { continue; } + if (!verbose && dst_key_is_hidden(dkey->key)) { + continue; + } + // key data dns_secalg_format((dns_secalg_t)dst_key_alg(dkey->key), algstr, sizeof(algstr)); - RETERR(isc_buffer_printf(&buf, "\nkey: %d (%s), %s\n", - dst_key_id(dkey->key), algstr, - keymgr_keyrole(dkey->key))); - - // publish status - RETERR(keytime_status(dkey->key, now, &buf, - " published: ", DST_KEY_DNSKEY, - DST_TIME_PUBLISH)); - - // signing status - result = dst_key_getbool(dkey->key, DST_BOOL_KSK, &ksk); - if (result == ISC_R_SUCCESS && ksk) { - RETERR(keytime_status( - dkey->key, now, &buf, " key signing: ", - DST_KEY_KRRSIG, DST_TIME_PUBLISH)); - } - result = dst_key_getbool(dkey->key, DST_BOOL_ZSK, &zsk); - if (result == ISC_R_SUCCESS && zsk) { - RETERR(keytime_status( - dkey->key, now, &buf, " zone signing: ", - DST_KEY_ZRRSIG, DST_TIME_ACTIVATE)); - } + RETERR(isc_buffer_printf(buf, "\n%s %d (%s):\n", + keymgr_keyrole(dkey->key), + dst_key_id(dkey->key), algstr)); // rollover status - RETERR(rollover_status(dkey, kasp, now, &buf, zsk)); - - // key states - RETERR(keystate_status(dkey->key, &buf, - "goal: ", DST_KEY_GOAL)); - RETERR(keystate_status(dkey->key, &buf, - "dnskey: ", DST_KEY_DNSKEY)); - RETERR(keystate_status(dkey->key, &buf, - "ds: ", DST_KEY_DS)); - RETERR(keystate_status(dkey->key, &buf, - "zone rrsig: ", DST_KEY_ZRRSIG)); - RETERR(keystate_status(dkey->key, &buf, - "key rrsig: ", DST_KEY_KRRSIG)); + RETERR(rollover_status(dkey, kasp, keyring, now, buf, &verbose, + checkds)); + + if (verbose) { + // key states + RETERR(isc_buffer_printf(buf, " Key states:\n")); + + RETERR(keystate_status( + dkey->key, buf, + "goal: ", DST_KEY_GOAL)); + RETERR(keystate_status( + dkey->key, buf, + "dnskey: ", DST_KEY_DNSKEY)); + RETERR(keystate_status(dkey->key, buf, + "ds: ", DST_KEY_DS)); + RETERR(keystate_status( + dkey->key, buf, + "zone rrsig: ", DST_KEY_ZRRSIG)); + RETERR(keystate_status( + dkey->key, buf, + "key rrsig: ", DST_KEY_KRRSIG)); + } } failure: - return result; } diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 4ddeb709688..cfe76c97068 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -22283,6 +22283,42 @@ dns_zone_rekey(dns_zone_t *zone, bool fullsign, bool forcekeymgr) { } } +isc_result_t +dns_zone_dnssecstatus(dns_zone_t *zone, dns_kasp_t *kasp, + dns_dnsseckeylist_t *keys, isc_stdtime_t now, + bool verbose, char *out, size_t out_len) { + isc_buffer_t buf; + isc_result_t result = ISC_R_SUCCESS; + isc_time_t refreshkeytime; + isc_stdtime_t refresh; + char timestr[26]; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(out != NULL); + + isc_buffer_init(&buf, out, out_len); + + RETERR(isc_buffer_printf( + &buf, "DNSSEC status for zone '%s' using policy '%s':\n", + zone->strname, dns_kasp_getname(kasp))); + + isc_stdtime_tostring(now, timestr, sizeof(timestr)); + RETERR(isc_buffer_printf(&buf, "Current time: %s\n", timestr)); + + dns_zone_getrefreshkeytime(zone, &refreshkeytime); + refresh = isc_time_seconds(&refreshkeytime); + isc_stdtime_tostring(refresh, timestr, sizeof(timestr)); + RETERR(isc_buffer_printf(&buf, "Next key event: %s\n", timestr)); + + bool checkds = zone->checkdstype != dns_checkdstype_no; + LOCK(&kasp->lock); + result = dns_keymgr_status(kasp, keys, &buf, now, verbose, checkds); + UNLOCK(&kasp->lock); + +failure: + return result; +} + isc_result_t dns_zone_nscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version, unsigned int *errors) {