/* $NetBSD: nsec3.c,v 1.1.2.2 2024/02/24 13:06:59 martin Exp $ */ /* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * SPDX-License-Identifier: MPL-2.0 * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, you can obtain one at https://mozilla.org/MPL/2.0/. * * See the COPYRIGHT file distributed with this work for additional * information regarding copyright ownership. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CHECK(x) \ do { \ result = (x); \ if (result != ISC_R_SUCCESS) \ goto failure; \ } while (0) #define OPTOUT(x) (((x)&DNS_NSEC3FLAG_OPTOUT) != 0) #define CREATE(x) (((x)&DNS_NSEC3FLAG_CREATE) != 0) #define INITIAL(x) (((x)&DNS_NSEC3FLAG_INITIAL) != 0) #define REMOVE(x) (((x)&DNS_NSEC3FLAG_REMOVE) != 0) isc_result_t dns_nsec3_buildrdata(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node, unsigned int hashalg, unsigned int flags, unsigned int iterations, const unsigned char *salt, size_t salt_length, const unsigned char *nexthash, size_t hash_length, unsigned char *buffer, dns_rdata_t *rdata) { isc_result_t result; dns_rdataset_t rdataset; isc_region_t r; unsigned int i; bool found; bool found_ns; bool need_rrsig; unsigned char *nsec_bits, *bm; unsigned int max_type; dns_rdatasetiter_t *rdsiter; unsigned char *p; REQUIRE(salt_length < 256U); REQUIRE(hash_length < 256U); REQUIRE(flags <= 0xffU); REQUIRE(hashalg <= 0xffU); REQUIRE(iterations <= 0xffffU); switch (hashalg) { case dns_hash_sha1: REQUIRE(hash_length == ISC_SHA1_DIGESTLENGTH); break; } memset(buffer, 0, DNS_NSEC3_BUFFERSIZE); p = buffer; *p++ = hashalg; *p++ = flags; *p++ = iterations >> 8; *p++ = iterations; *p++ = (unsigned char)salt_length; memmove(p, salt, salt_length); p += salt_length; *p++ = (unsigned char)hash_length; memmove(p, nexthash, hash_length); p += hash_length; r.length = (unsigned int)(p - buffer); r.base = buffer; /* * Use the end of the space for a raw bitmap leaving enough * space for the window identifiers and length octets. */ bm = r.base + r.length + 512; nsec_bits = r.base + r.length; max_type = 0; if (node == NULL) { goto collapse_bitmap; } dns_rdataset_init(&rdataset); rdsiter = NULL; result = dns_db_allrdatasets(db, node, version, 0, 0, &rdsiter); if (result != ISC_R_SUCCESS) { return (result); } found = found_ns = need_rrsig = false; for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS; result = dns_rdatasetiter_next(rdsiter)) { dns_rdatasetiter_current(rdsiter, &rdataset); if (rdataset.type != dns_rdatatype_nsec && rdataset.type != dns_rdatatype_nsec3 && rdataset.type != dns_rdatatype_rrsig) { if (rdataset.type > max_type) { max_type = rdataset.type; } dns_nsec_setbit(bm, rdataset.type, 1); /* * Work out if we need to set the RRSIG bit for * this node. We set the RRSIG bit if either of * the following conditions are met: * 1) We have a SOA or DS then we need to set * the RRSIG bit as both always will be signed. * 2) We set the RRSIG bit if we don't have * a NS record but do have other data. */ if (rdataset.type == dns_rdatatype_soa || rdataset.type == dns_rdatatype_ds) { need_rrsig = true; } else if (rdataset.type == dns_rdatatype_ns) { found_ns = true; } else { found = true; } } dns_rdataset_disassociate(&rdataset); } if ((found && !found_ns) || need_rrsig) { if (dns_rdatatype_rrsig > max_type) { max_type = dns_rdatatype_rrsig; } dns_nsec_setbit(bm, dns_rdatatype_rrsig, 1); } /* * At zone cuts, deny the existence of glue in the parent zone. */ if (dns_nsec_isset(bm, dns_rdatatype_ns) && !dns_nsec_isset(bm, dns_rdatatype_soa)) { for (i = 0; i <= max_type; i++) { if (dns_nsec_isset(bm, i) && !dns_rdatatype_iszonecutauth((dns_rdatatype_t)i)) { dns_nsec_setbit(bm, i, 0); } } } dns_rdatasetiter_destroy(&rdsiter); if (result != ISC_R_NOMORE) { return (result); } collapse_bitmap: nsec_bits += dns_nsec_compressbitmap(nsec_bits, bm, max_type); r.length = (unsigned int)(nsec_bits - r.base); INSIST(r.length <= DNS_NSEC3_BUFFERSIZE); dns_rdata_fromregion(rdata, dns_db_class(db), dns_rdatatype_nsec3, &r); return (ISC_R_SUCCESS); } bool dns_nsec3_typepresent(dns_rdata_t *rdata, dns_rdatatype_t type) { dns_rdata_nsec3_t nsec3; isc_result_t result; bool present; unsigned int i, len, window; REQUIRE(rdata != NULL); REQUIRE(rdata->type == dns_rdatatype_nsec3); /* This should never fail */ result = dns_rdata_tostruct(rdata, &nsec3, NULL); INSIST(result == ISC_R_SUCCESS); present = false; for (i = 0; i < nsec3.len; i += len) { INSIST(i + 2 <= nsec3.len); window = nsec3.typebits[i]; len = nsec3.typebits[i + 1]; INSIST(len > 0 && len <= 32); i += 2; INSIST(i + len <= nsec3.len); if (window * 256 > type) { break; } if ((window + 1) * 256 <= type) { continue; } if (type < (window * 256) + len * 8) { present = dns_nsec_isset(&nsec3.typebits[i], type % 256); } break; } dns_rdata_freestruct(&nsec3); return (present); } isc_result_t dns_nsec3_generate_salt(unsigned char *salt, size_t saltlen) { if (saltlen > 255U) { return (ISC_R_RANGE); } isc_nonce_buf(salt, saltlen); return (ISC_R_SUCCESS); } isc_result_t dns_nsec3_hashname(dns_fixedname_t *result, unsigned char rethash[NSEC3_MAX_HASH_LENGTH], size_t *hash_length, const dns_name_t *name, const dns_name_t *origin, dns_hash_t hashalg, unsigned int iterations, const unsigned char *salt, size_t saltlength) { unsigned char hash[NSEC3_MAX_HASH_LENGTH]; unsigned char nametext[DNS_NAME_FORMATSIZE]; dns_fixedname_t fixed; dns_name_t *downcased; isc_buffer_t namebuffer; isc_region_t region; size_t len; if (rethash == NULL) { rethash = hash; } memset(rethash, 0, NSEC3_MAX_HASH_LENGTH); downcased = dns_fixedname_initname(&fixed); dns_name_downcase(name, downcased, NULL); /* hash the node name */ len = isc_iterated_hash(rethash, hashalg, iterations, salt, (int)saltlength, downcased->ndata, downcased->length); if (len == 0U) { return (DNS_R_BADALG); } if (hash_length != NULL) { *hash_length = len; } /* convert the hash to base32hex non-padded */ region.base = rethash; region.length = (unsigned int)len; isc_buffer_init(&namebuffer, nametext, sizeof nametext); isc_base32hexnp_totext(®ion, 1, "", &namebuffer); /* convert the hex to a domain name */ dns_fixedname_init(result); return (dns_name_fromtext(dns_fixedname_name(result), &namebuffer, origin, 0, NULL)); } unsigned int dns_nsec3_hashlength(dns_hash_t hash) { switch (hash) { case dns_hash_sha1: return (ISC_SHA1_DIGESTLENGTH); } return (0); } bool dns_nsec3_supportedhash(dns_hash_t hash) { switch (hash) { case dns_hash_sha1: return (true); } return (false); } /*% * Update a single RR in version 'ver' of 'db' and log the * update in 'diff'. * * Ensures: * \li '*tuple' == NULL. Either the tuple is freed, or its * ownership has been transferred to the diff. */ static isc_result_t do_one_tuple(dns_difftuple_t **tuple, dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff) { dns_diff_t temp_diff; isc_result_t result; /* * Create a singleton diff. */ dns_diff_init(diff->mctx, &temp_diff); ISC_LIST_APPEND(temp_diff.tuples, *tuple, link); /* * Apply it to the database. */ result = dns_diff_apply(&temp_diff, db, ver); ISC_LIST_UNLINK(temp_diff.tuples, *tuple, link); if (result != ISC_R_SUCCESS) { dns_difftuple_free(tuple); return (result); } /* * Merge it into the current pending journal entry. */ dns_diff_appendminimal(diff, tuple); /* * Do not clear temp_diff. */ return (ISC_R_SUCCESS); } /*% * Set '*exists' to true iff the given name exists, to false otherwise. */ static isc_result_t name_exists(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, bool *exists) { isc_result_t result; dns_dbnode_t *node = NULL; dns_rdatasetiter_t *iter = NULL; result = dns_db_findnode(db, name, false, &node); if (result == ISC_R_NOTFOUND) { *exists = false; return (ISC_R_SUCCESS); } if (result != ISC_R_SUCCESS) { return (result); } result = dns_db_allrdatasets(db, node, version, 0, (isc_stdtime_t)0, &iter); if (result != ISC_R_SUCCESS) { goto cleanup_node; } result = dns_rdatasetiter_first(iter); if (result == ISC_R_SUCCESS) { *exists = true; } else if (result == ISC_R_NOMORE) { *exists = false; result = ISC_R_SUCCESS; } else { *exists = false; } dns_rdatasetiter_destroy(&iter); cleanup_node: dns_db_detachnode(db, &node); return (result); } static bool match_nsec3param(const dns_rdata_nsec3_t *nsec3, const dns_rdata_nsec3param_t *nsec3param) { if (nsec3->hash == nsec3param->hash && nsec3->iterations == nsec3param->iterations && nsec3->salt_length == nsec3param->salt_length && !memcmp(nsec3->salt, nsec3param->salt, nsec3->salt_length)) { return (true); } return (false); } /*% * Delete NSEC3 records at "name" which match "param", recording the * change in "diff". */ static isc_result_t delnsec3(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, const dns_rdata_nsec3param_t *nsec3param, dns_diff_t *diff) { dns_dbnode_t *node = NULL; dns_difftuple_t *tuple = NULL; dns_rdata_nsec3_t nsec3; dns_rdataset_t rdataset; isc_result_t result; result = dns_db_findnsec3node(db, name, false, &node); if (result == ISC_R_NOTFOUND) { return (ISC_R_SUCCESS); } if (result != ISC_R_SUCCESS) { return (result); } dns_rdataset_init(&rdataset); result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3, 0, (isc_stdtime_t)0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { result = ISC_R_SUCCESS; goto cleanup_node; } if (result != ISC_R_SUCCESS) { goto cleanup_node; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); CHECK(dns_rdata_tostruct(&rdata, &nsec3, NULL)); if (!match_nsec3param(&nsec3, nsec3param)) { continue; } result = dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL, name, rdataset.ttl, &rdata, &tuple); if (result != ISC_R_SUCCESS) { goto failure; } result = do_one_tuple(&tuple, db, version, diff); if (result != ISC_R_SUCCESS) { goto failure; } } if (result != ISC_R_NOMORE) { goto failure; } result = ISC_R_SUCCESS; failure: dns_rdataset_disassociate(&rdataset); cleanup_node: dns_db_detachnode(db, &node); return (result); } static bool better_param(dns_rdataset_t *nsec3paramset, dns_rdata_t *param) { dns_rdataset_t rdataset; isc_result_t result; if (REMOVE(param->data[1])) { return (true); } dns_rdataset_init(&rdataset); dns_rdataset_clone(nsec3paramset, &rdataset); for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; if (rdataset.type != dns_rdatatype_nsec3param) { dns_rdata_t tmprdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &tmprdata); if (!dns_nsec3param_fromprivate(&tmprdata, &rdata, buf, sizeof(buf))) { continue; } } else { dns_rdataset_current(&rdataset, &rdata); } if (rdata.length != param->length) { continue; } if (rdata.data[0] != param->data[0] || REMOVE(rdata.data[1]) || rdata.data[2] != param->data[2] || rdata.data[3] != param->data[3] || rdata.data[4] != param->data[4] || memcmp(&rdata.data[5], ¶m->data[5], param->data[4])) { continue; } if (CREATE(rdata.data[1]) && !CREATE(param->data[1])) { dns_rdataset_disassociate(&rdataset); return (true); } } dns_rdataset_disassociate(&rdataset); return (false); } static isc_result_t find_nsec3(dns_rdata_nsec3_t *nsec3, dns_rdataset_t *rdataset, const dns_rdata_nsec3param_t *nsec3param) { isc_result_t result; for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(rdataset, &rdata); CHECK(dns_rdata_tostruct(&rdata, nsec3, NULL)); dns_rdata_reset(&rdata); if (match_nsec3param(nsec3, nsec3param)) { break; } } failure: return (result); } isc_result_t dns_nsec3_addnsec3(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, const dns_rdata_nsec3param_t *nsec3param, dns_ttl_t nsecttl, bool unsecure, dns_diff_t *diff) { dns_dbiterator_t *dbit = NULL; dns_dbnode_t *node = NULL; dns_dbnode_t *newnode = NULL; dns_difftuple_t *tuple = NULL; dns_fixedname_t fixed; dns_fixedname_t fprev; dns_hash_t hash; dns_name_t *hashname; dns_name_t *origin; dns_name_t *prev; dns_name_t empty; dns_rdata_nsec3_t nsec3; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_t rdataset; int pass; bool exists = false; bool maybe_remove_unsecure = false; uint8_t flags; isc_buffer_t buffer; isc_result_t result; unsigned char *old_next; unsigned char *salt; unsigned char nexthash[NSEC3_MAX_HASH_LENGTH]; unsigned char nsec3buf[DNS_NSEC3_BUFFERSIZE]; unsigned int iterations; unsigned int labels; size_t next_length; unsigned int old_length; unsigned int salt_length; hashname = dns_fixedname_initname(&fixed); prev = dns_fixedname_initname(&fprev); dns_rdataset_init(&rdataset); origin = dns_db_origin(db); /* * Chain parameters. */ hash = nsec3param->hash; iterations = nsec3param->iterations; salt_length = nsec3param->salt_length; salt = nsec3param->salt; /* * Default flags for a new chain. */ flags = nsec3param->flags & DNS_NSEC3FLAG_OPTOUT; /* * If this is the first NSEC3 in the chain nexthash will * remain pointing to itself. */ next_length = sizeof(nexthash); CHECK(dns_nsec3_hashname(&fixed, nexthash, &next_length, name, origin, hash, iterations, salt, salt_length)); INSIST(next_length <= sizeof(nexthash)); /* * Create the node if it doesn't exist and hold * a reference to it until we have added the NSEC3. */ CHECK(dns_db_findnsec3node(db, hashname, true, &newnode)); /* * Seek the iterator to the 'newnode'. */ CHECK(dns_db_createiterator(db, DNS_DB_NSEC3ONLY, &dbit)); CHECK(dns_dbiterator_seek(dbit, hashname)); CHECK(dns_dbiterator_pause(dbit)); result = dns_db_findrdataset(db, newnode, version, dns_rdatatype_nsec3, 0, (isc_stdtime_t)0, &rdataset, NULL); /* * If we updating a existing NSEC3 then find its * next field. */ if (result == ISC_R_SUCCESS) { result = find_nsec3(&nsec3, &rdataset, nsec3param); if (result == ISC_R_SUCCESS) { if (!CREATE(nsec3param->flags)) { flags = nsec3.flags; } next_length = nsec3.next_length; INSIST(next_length <= sizeof(nexthash)); memmove(nexthash, nsec3.next, next_length); dns_rdataset_disassociate(&rdataset); /* * If the NSEC3 is not for a unsecure delegation then * we are just updating it. If it is for a unsecure * delegation then we need find out if we need to * remove the NSEC3 record or not by examining the * previous NSEC3 record. */ if (!unsecure) { goto addnsec3; } else if (CREATE(nsec3param->flags) && OPTOUT(flags)) { result = dns_nsec3_delnsec3(db, version, name, nsec3param, diff); goto failure; } else { maybe_remove_unsecure = true; } } else { dns_rdataset_disassociate(&rdataset); if (result != ISC_R_NOMORE) { goto failure; } } } /* * Find the previous NSEC3 (if any) and update it if required. */ pass = 0; do { result = dns_dbiterator_prev(dbit); if (result == ISC_R_NOMORE) { pass++; CHECK(dns_dbiterator_last(dbit)); } CHECK(dns_dbiterator_current(dbit, &node, prev)); CHECK(dns_dbiterator_pause(dbit)); result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3, 0, (isc_stdtime_t)0, &rdataset, NULL); dns_db_detachnode(db, &node); if (result != ISC_R_SUCCESS) { continue; } result = find_nsec3(&nsec3, &rdataset, nsec3param); if (result == ISC_R_NOMORE) { dns_rdataset_disassociate(&rdataset); continue; } if (result != ISC_R_SUCCESS) { goto failure; } if (maybe_remove_unsecure) { dns_rdataset_disassociate(&rdataset); /* * If we have OPTOUT set in the previous NSEC3 record * we actually need to delete the NSEC3 record. * Otherwise we just need to replace the NSEC3 record. */ if (OPTOUT(nsec3.flags)) { result = dns_nsec3_delnsec3(db, version, name, nsec3param, diff); goto failure; } goto addnsec3; } else { /* * Is this is a unsecure delegation we are adding? * If so no change is required. */ if (OPTOUT(nsec3.flags) && unsecure) { dns_rdataset_disassociate(&rdataset); goto failure; } } old_next = nsec3.next; old_length = nsec3.next_length; /* * Delete the old previous NSEC3. */ CHECK(delnsec3(db, version, prev, nsec3param, diff)); /* * Fixup the previous NSEC3. */ nsec3.next = nexthash; nsec3.next_length = (unsigned char)next_length; isc_buffer_init(&buffer, nsec3buf, sizeof(nsec3buf)); CHECK(dns_rdata_fromstruct(&rdata, rdataset.rdclass, dns_rdatatype_nsec3, &nsec3, &buffer)); CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, prev, rdataset.ttl, &rdata, &tuple)); CHECK(do_one_tuple(&tuple, db, version, diff)); INSIST(old_length <= sizeof(nexthash)); memmove(nexthash, old_next, old_length); if (!CREATE(nsec3param->flags)) { flags = nsec3.flags; } dns_rdata_reset(&rdata); dns_rdataset_disassociate(&rdataset); break; } while (pass < 2); addnsec3: /* * Create the NSEC3 RDATA. */ CHECK(dns_db_findnode(db, name, false, &node)); CHECK(dns_nsec3_buildrdata(db, version, node, hash, flags, iterations, salt, salt_length, nexthash, next_length, nsec3buf, &rdata)); dns_db_detachnode(db, &node); /* * Delete the old NSEC3 and record the change. */ CHECK(delnsec3(db, version, hashname, nsec3param, diff)); /* * Add the new NSEC3 and record the change. */ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, hashname, nsecttl, &rdata, &tuple)); CHECK(do_one_tuple(&tuple, db, version, diff)); INSIST(tuple == NULL); dns_rdata_reset(&rdata); dns_db_detachnode(db, &newnode); /* * Add missing NSEC3 records for empty nodes */ dns_name_init(&empty, NULL); dns_name_clone(name, &empty); do { labels = dns_name_countlabels(&empty) - 1; if (labels <= dns_name_countlabels(origin)) { break; } dns_name_getlabelsequence(&empty, 1, labels, &empty); CHECK(name_exists(db, version, &empty, &exists)); if (exists) { break; } CHECK(dns_nsec3_hashname(&fixed, nexthash, &next_length, &empty, origin, hash, iterations, salt, salt_length)); /* * Create the node if it doesn't exist and hold * a reference to it until we have added the NSEC3 * or we discover we don't need to add make a change. */ CHECK(dns_db_findnsec3node(db, hashname, true, &newnode)); result = dns_db_findrdataset(db, newnode, version, dns_rdatatype_nsec3, 0, (isc_stdtime_t)0, &rdataset, NULL); if (result == ISC_R_SUCCESS) { result = find_nsec3(&nsec3, &rdataset, nsec3param); dns_rdataset_disassociate(&rdataset); if (result == ISC_R_SUCCESS) { dns_db_detachnode(db, &newnode); break; } if (result != ISC_R_NOMORE) { goto failure; } } /* * Find the previous NSEC3 and update it. */ CHECK(dns_dbiterator_seek(dbit, hashname)); pass = 0; do { result = dns_dbiterator_prev(dbit); if (result == ISC_R_NOMORE) { pass++; CHECK(dns_dbiterator_last(dbit)); } CHECK(dns_dbiterator_current(dbit, &node, prev)); CHECK(dns_dbiterator_pause(dbit)); result = dns_db_findrdataset( db, node, version, dns_rdatatype_nsec3, 0, (isc_stdtime_t)0, &rdataset, NULL); dns_db_detachnode(db, &node); if (result != ISC_R_SUCCESS) { continue; } result = find_nsec3(&nsec3, &rdataset, nsec3param); if (result == ISC_R_NOMORE) { dns_rdataset_disassociate(&rdataset); continue; } if (result != ISC_R_SUCCESS) { goto failure; } old_next = nsec3.next; old_length = nsec3.next_length; /* * Delete the old previous NSEC3. */ CHECK(delnsec3(db, version, prev, nsec3param, diff)); /* * Fixup the previous NSEC3. */ nsec3.next = nexthash; nsec3.next_length = (unsigned char)next_length; isc_buffer_init(&buffer, nsec3buf, sizeof(nsec3buf)); CHECK(dns_rdata_fromstruct(&rdata, rdataset.rdclass, dns_rdatatype_nsec3, &nsec3, &buffer)); CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, prev, rdataset.ttl, &rdata, &tuple)); CHECK(do_one_tuple(&tuple, db, version, diff)); INSIST(old_length <= sizeof(nexthash)); memmove(nexthash, old_next, old_length); if (!CREATE(nsec3param->flags)) { flags = nsec3.flags; } dns_rdata_reset(&rdata); dns_rdataset_disassociate(&rdataset); break; } while (pass < 2); INSIST(pass < 2); /* * Create the NSEC3 RDATA for the empty node. */ CHECK(dns_nsec3_buildrdata( db, version, NULL, hash, flags, iterations, salt, salt_length, nexthash, next_length, nsec3buf, &rdata)); /* * Delete the old NSEC3 and record the change. */ CHECK(delnsec3(db, version, hashname, nsec3param, diff)); /* * Add the new NSEC3 and record the change. */ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, hashname, nsecttl, &rdata, &tuple)); CHECK(do_one_tuple(&tuple, db, version, diff)); INSIST(tuple == NULL); dns_rdata_reset(&rdata); dns_db_detachnode(db, &newnode); } while (1); /* result cannot be ISC_R_NOMORE here */ INSIST(result != ISC_R_NOMORE); failure: if (dbit != NULL) { dns_dbiterator_destroy(&dbit); } if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } if (newnode != NULL) { dns_db_detachnode(db, &newnode); } return (result); } /*% * Add NSEC3 records for "name", recording the change in "diff". * The existing NSEC3 records are removed. */ isc_result_t dns_nsec3_addnsec3s(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, dns_ttl_t nsecttl, bool unsecure, dns_diff_t *diff) { dns_dbnode_t *node = NULL; dns_rdata_nsec3param_t nsec3param; dns_rdataset_t rdataset; isc_result_t result; dns_rdataset_init(&rdataset); /* * Find the NSEC3 parameters for this zone. */ result = dns_db_getoriginnode(db, &node); if (result != ISC_R_SUCCESS) { return (result); } result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3param, 0, 0, &rdataset, NULL); dns_db_detachnode(db, &node); if (result == ISC_R_NOTFOUND) { return (ISC_R_SUCCESS); } if (result != ISC_R_SUCCESS) { return (result); } /* * Update each active NSEC3 chain. */ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); if (nsec3param.flags != 0) { continue; } /* * We have a active chain. Update it. */ CHECK(dns_nsec3_addnsec3(db, version, name, &nsec3param, nsecttl, unsecure, diff)); } if (result == ISC_R_NOMORE) { result = ISC_R_SUCCESS; } failure: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } return (result); } bool dns_nsec3param_fromprivate(dns_rdata_t *src, dns_rdata_t *target, unsigned char *buf, size_t buflen) { dns_decompress_t dctx; isc_result_t result; isc_buffer_t buf1; isc_buffer_t buf2; /* * Algorithm 0 (reserved by RFC 4034) is used to identify * NSEC3PARAM records from DNSKEY pointers. */ if (src->length < 1 || src->data[0] != 0) { return (false); } isc_buffer_init(&buf1, src->data + 1, src->length - 1); isc_buffer_add(&buf1, src->length - 1); isc_buffer_setactive(&buf1, src->length - 1); isc_buffer_init(&buf2, buf, (unsigned int)buflen); dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE); result = dns_rdata_fromwire(target, src->rdclass, dns_rdatatype_nsec3param, &buf1, &dctx, 0, &buf2); dns_decompress_invalidate(&dctx); return (result == ISC_R_SUCCESS); } void dns_nsec3param_toprivate(dns_rdata_t *src, dns_rdata_t *target, dns_rdatatype_t privatetype, unsigned char *buf, size_t buflen) { REQUIRE(buflen >= src->length + 1); REQUIRE(DNS_RDATA_INITIALIZED(target)); memmove(buf + 1, src->data, src->length); buf[0] = 0; target->data = buf; target->length = src->length + 1; target->type = privatetype; target->rdclass = src->rdclass; target->flags = 0; ISC_LINK_INIT(target, link); } static isc_result_t rr_exists(dns_db_t *db, dns_dbversion_t *ver, const dns_name_t *name, const dns_rdata_t *rdata, bool *flag) { dns_rdataset_t rdataset; dns_dbnode_t *node = NULL; isc_result_t result; dns_rdataset_init(&rdataset); if (rdata->type == dns_rdatatype_nsec3) { CHECK(dns_db_findnsec3node(db, name, false, &node)); } else { CHECK(dns_db_findnode(db, name, false, &node)); } result = dns_db_findrdataset(db, node, ver, rdata->type, 0, (isc_stdtime_t)0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { *flag = false; result = ISC_R_SUCCESS; goto failure; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t myrdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &myrdata); if (!dns_rdata_casecompare(&myrdata, rdata)) { break; } } dns_rdataset_disassociate(&rdataset); if (result == ISC_R_SUCCESS) { *flag = true; } else if (result == ISC_R_NOMORE) { *flag = false; result = ISC_R_SUCCESS; } failure: if (node != NULL) { dns_db_detachnode(db, &node); } return (result); } isc_result_t dns_nsec3param_salttotext(dns_rdata_nsec3param_t *nsec3param, char *dst, size_t dstlen) { isc_result_t result; isc_region_t r; isc_buffer_t b; REQUIRE(nsec3param != NULL); REQUIRE(dst != NULL); if (nsec3param->salt_length == 0) { if (dstlen < 2U) { return (ISC_R_NOSPACE); } strlcpy(dst, "-", dstlen); return (ISC_R_SUCCESS); } r.base = nsec3param->salt; r.length = nsec3param->salt_length; isc_buffer_init(&b, dst, (unsigned int)dstlen); result = isc_hex_totext(&r, 2, "", &b); if (result != ISC_R_SUCCESS) { return (result); } if (isc_buffer_availablelength(&b) < 1) { return (ISC_R_NOSPACE); } isc_buffer_putuint8(&b, 0); return (ISC_R_SUCCESS); } isc_result_t dns_nsec3param_deletechains(dns_db_t *db, dns_dbversion_t *ver, dns_zone_t *zone, bool nonsec, dns_diff_t *diff) { dns_dbnode_t *node = NULL; dns_difftuple_t *tuple = NULL; dns_name_t next; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_t rdataset; bool flag; isc_result_t result = ISC_R_SUCCESS; unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE + 1]; dns_name_t *origin = dns_zone_getorigin(zone); dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone); dns_name_init(&next, NULL); dns_rdataset_init(&rdataset); result = dns_db_getoriginnode(db, &node); if (result != ISC_R_SUCCESS) { return (result); } /* * Cause all NSEC3 chains to be deleted. */ result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param, 0, (isc_stdtime_t)0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { goto try_private; } if (result != ISC_R_SUCCESS) { goto failure; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t private = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL, origin, rdataset.ttl, &rdata, &tuple)); CHECK(do_one_tuple(&tuple, db, ver, diff)); INSIST(tuple == NULL); dns_nsec3param_toprivate(&rdata, &private, privatetype, buf, sizeof(buf)); buf[2] = DNS_NSEC3FLAG_REMOVE; if (nonsec) { buf[2] |= DNS_NSEC3FLAG_NONSEC; } CHECK(rr_exists(db, ver, origin, &private, &flag)); if (!flag) { CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, origin, 0, &private, &tuple)); CHECK(do_one_tuple(&tuple, db, ver, diff)); INSIST(tuple == NULL); } dns_rdata_reset(&rdata); } if (result != ISC_R_NOMORE) { goto failure; } dns_rdataset_disassociate(&rdataset); try_private: if (privatetype == 0) { goto success; } result = dns_db_findrdataset(db, node, ver, privatetype, 0, (isc_stdtime_t)0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { goto success; } if (result != ISC_R_SUCCESS) { goto failure; } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_reset(&rdata); dns_rdataset_current(&rdataset, &rdata); INSIST(rdata.length <= sizeof(buf)); memmove(buf, rdata.data, rdata.length); /* * Private NSEC3 record length >= 6. * <0(1), hash(1), flags(1), iterations(2), saltlen(1)> */ if (rdata.length < 6 || buf[0] != 0 || (buf[2] & DNS_NSEC3FLAG_REMOVE) != 0 || (nonsec && (buf[2] & DNS_NSEC3FLAG_NONSEC) != 0)) { continue; } CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL, origin, 0, &rdata, &tuple)); CHECK(do_one_tuple(&tuple, db, ver, diff)); INSIST(tuple == NULL); rdata.data = buf; buf[2] = DNS_NSEC3FLAG_REMOVE; if (nonsec) { buf[2] |= DNS_NSEC3FLAG_NONSEC; } CHECK(rr_exists(db, ver, origin, &rdata, &flag)); if (!flag) { CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, origin, 0, &rdata, &tuple)); CHECK(do_one_tuple(&tuple, db, ver, diff)); INSIST(tuple == NULL); } } if (result != ISC_R_NOMORE) { goto failure; } success: result = ISC_R_SUCCESS; failure: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } dns_db_detachnode(db, &node); return (result); } isc_result_t dns_nsec3_addnsec3sx(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, dns_ttl_t nsecttl, bool unsecure, dns_rdatatype_t type, dns_diff_t *diff) { dns_dbnode_t *node = NULL; dns_rdata_nsec3param_t nsec3param; dns_rdataset_t rdataset; dns_rdataset_t prdataset; isc_result_t result; dns_rdataset_init(&rdataset); dns_rdataset_init(&prdataset); /* * Find the NSEC3 parameters for this zone. */ result = dns_db_getoriginnode(db, &node); if (result != ISC_R_SUCCESS) { return (result); } result = dns_db_findrdataset(db, node, version, type, 0, 0, &prdataset, NULL); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { goto failure; } result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3param, 0, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { goto try_private; } if (result != ISC_R_SUCCESS) { goto failure; } /* * Update each active NSEC3 chain. */ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); if (nsec3param.flags != 0) { continue; } /* * We have a active chain. Update it. */ CHECK(dns_nsec3_addnsec3(db, version, name, &nsec3param, nsecttl, unsecure, diff)); } if (result != ISC_R_NOMORE) { goto failure; } dns_rdataset_disassociate(&rdataset); try_private: if (!dns_rdataset_isassociated(&prdataset)) { goto success; } /* * Update each active NSEC3 chain. */ for (result = dns_rdataset_first(&prdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&prdataset)) { dns_rdata_t rdata1 = DNS_RDATA_INIT; dns_rdata_t rdata2 = DNS_RDATA_INIT; unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; dns_rdataset_current(&prdataset, &rdata1); if (!dns_nsec3param_fromprivate(&rdata1, &rdata2, buf, sizeof(buf))) { continue; } CHECK(dns_rdata_tostruct(&rdata2, &nsec3param, NULL)); if ((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) { continue; } if (better_param(&prdataset, &rdata2)) { continue; } /* * We have a active chain. Update it. */ CHECK(dns_nsec3_addnsec3(db, version, name, &nsec3param, nsecttl, unsecure, diff)); } if (result == ISC_R_NOMORE) { success: result = ISC_R_SUCCESS; } failure: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (dns_rdataset_isassociated(&prdataset)) { dns_rdataset_disassociate(&prdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } return (result); } /*% * Determine whether any NSEC3 records that were associated with * 'name' should be deleted or if they should continue to exist. * true indicates they should be deleted. * false indicates they should be retained. */ static isc_result_t deleteit(dns_db_t *db, dns_dbversion_t *ver, const dns_name_t *name, bool *yesno) { isc_result_t result; dns_fixedname_t foundname; dns_fixedname_init(&foundname); result = dns_db_find(db, name, ver, dns_rdatatype_any, DNS_DBFIND_GLUEOK | DNS_DBFIND_NOWILD, (isc_stdtime_t)0, NULL, dns_fixedname_name(&foundname), NULL, NULL); if (result == DNS_R_EMPTYNAME || result == ISC_R_SUCCESS || result == DNS_R_ZONECUT) { *yesno = false; return (ISC_R_SUCCESS); } if (result == DNS_R_GLUE || result == DNS_R_DNAME || result == DNS_R_DELEGATION || result == DNS_R_NXDOMAIN) { *yesno = true; return (ISC_R_SUCCESS); } /* * Silence compiler. */ *yesno = true; return (result); } isc_result_t dns_nsec3_delnsec3(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, const dns_rdata_nsec3param_t *nsec3param, dns_diff_t *diff) { dns_dbiterator_t *dbit = NULL; dns_dbnode_t *node = NULL; dns_difftuple_t *tuple = NULL; dns_fixedname_t fixed; dns_fixedname_t fprev; dns_hash_t hash; dns_name_t *hashname; dns_name_t *origin; dns_name_t *prev; dns_name_t empty; dns_rdata_nsec3_t nsec3; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_t rdataset; int pass; bool yesno; isc_buffer_t buffer; isc_result_t result; unsigned char *salt; unsigned char nexthash[NSEC3_MAX_HASH_LENGTH]; unsigned char nsec3buf[DNS_NSEC3_BUFFERSIZE]; unsigned int iterations; unsigned int labels; size_t next_length; unsigned int salt_length; hashname = dns_fixedname_initname(&fixed); prev = dns_fixedname_initname(&fprev); dns_rdataset_init(&rdataset); origin = dns_db_origin(db); /* * Chain parameters. */ hash = nsec3param->hash; iterations = nsec3param->iterations; salt_length = nsec3param->salt_length; salt = nsec3param->salt; /* * If this is the first NSEC3 in the chain nexthash will * remain pointing to itself. */ next_length = sizeof(nexthash); CHECK(dns_nsec3_hashname(&fixed, nexthash, &next_length, name, origin, hash, iterations, salt, salt_length)); CHECK(dns_db_createiterator(db, DNS_DB_NSEC3ONLY, &dbit)); result = dns_dbiterator_seek(dbit, hashname); if (result == ISC_R_NOTFOUND || result == DNS_R_PARTIALMATCH) { goto cleanup_orphaned_ents; } if (result != ISC_R_SUCCESS) { goto failure; } CHECK(dns_dbiterator_current(dbit, &node, NULL)); CHECK(dns_dbiterator_pause(dbit)); result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3, 0, (isc_stdtime_t)0, &rdataset, NULL); dns_db_detachnode(db, &node); if (result == ISC_R_NOTFOUND) { goto cleanup_orphaned_ents; } if (result != ISC_R_SUCCESS) { goto failure; } /* * If we find a existing NSEC3 for this chain then save the * next field. */ result = find_nsec3(&nsec3, &rdataset, nsec3param); if (result == ISC_R_SUCCESS) { next_length = nsec3.next_length; INSIST(next_length <= sizeof(nexthash)); memmove(nexthash, nsec3.next, next_length); } dns_rdataset_disassociate(&rdataset); if (result == ISC_R_NOMORE) { goto success; } if (result != ISC_R_SUCCESS) { goto failure; } /* * Find the previous NSEC3 and update it. */ pass = 0; do { result = dns_dbiterator_prev(dbit); if (result == ISC_R_NOMORE) { pass++; CHECK(dns_dbiterator_last(dbit)); } CHECK(dns_dbiterator_current(dbit, &node, prev)); CHECK(dns_dbiterator_pause(dbit)); result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3, 0, (isc_stdtime_t)0, &rdataset, NULL); dns_db_detachnode(db, &node); if (result != ISC_R_SUCCESS) { continue; } result = find_nsec3(&nsec3, &rdataset, nsec3param); if (result == ISC_R_NOMORE) { dns_rdataset_disassociate(&rdataset); continue; } if (result != ISC_R_SUCCESS) { goto failure; } /* * Delete the old previous NSEC3. */ CHECK(delnsec3(db, version, prev, nsec3param, diff)); /* * Fixup the previous NSEC3. */ nsec3.next = nexthash; nsec3.next_length = (unsigned char)next_length; if (CREATE(nsec3param->flags)) { nsec3.flags = nsec3param->flags & DNS_NSEC3FLAG_OPTOUT; } isc_buffer_init(&buffer, nsec3buf, sizeof(nsec3buf)); CHECK(dns_rdata_fromstruct(&rdata, rdataset.rdclass, dns_rdatatype_nsec3, &nsec3, &buffer)); CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, prev, rdataset.ttl, &rdata, &tuple)); CHECK(do_one_tuple(&tuple, db, version, diff)); dns_rdata_reset(&rdata); dns_rdataset_disassociate(&rdataset); break; } while (pass < 2); /* * Delete the old NSEC3 and record the change. */ CHECK(delnsec3(db, version, hashname, nsec3param, diff)); /* * Delete NSEC3 records for now non active nodes. */ cleanup_orphaned_ents: dns_name_init(&empty, NULL); dns_name_clone(name, &empty); do { labels = dns_name_countlabels(&empty) - 1; if (labels <= dns_name_countlabels(origin)) { break; } dns_name_getlabelsequence(&empty, 1, labels, &empty); CHECK(deleteit(db, version, &empty, &yesno)); if (!yesno) { break; } CHECK(dns_nsec3_hashname(&fixed, nexthash, &next_length, &empty, origin, hash, iterations, salt, salt_length)); result = dns_dbiterator_seek(dbit, hashname); if (result == ISC_R_NOTFOUND || result == DNS_R_PARTIALMATCH) { goto success; } if (result != ISC_R_SUCCESS) { goto failure; } CHECK(dns_dbiterator_current(dbit, &node, NULL)); CHECK(dns_dbiterator_pause(dbit)); result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3, 0, (isc_stdtime_t)0, &rdataset, NULL); dns_db_detachnode(db, &node); if (result == ISC_R_NOTFOUND) { goto success; } if (result != ISC_R_SUCCESS) { goto failure; } result = find_nsec3(&nsec3, &rdataset, nsec3param); if (result == ISC_R_SUCCESS) { next_length = nsec3.next_length; INSIST(next_length <= sizeof(nexthash)); memmove(nexthash, nsec3.next, next_length); } dns_rdataset_disassociate(&rdataset); if (result == ISC_R_NOMORE) { goto success; } if (result != ISC_R_SUCCESS) { goto failure; } pass = 0; do { result = dns_dbiterator_prev(dbit); if (result == ISC_R_NOMORE) { pass++; CHECK(dns_dbiterator_last(dbit)); } CHECK(dns_dbiterator_current(dbit, &node, prev)); CHECK(dns_dbiterator_pause(dbit)); result = dns_db_findrdataset( db, node, version, dns_rdatatype_nsec3, 0, (isc_stdtime_t)0, &rdataset, NULL); dns_db_detachnode(db, &node); if (result != ISC_R_SUCCESS) { continue; } result = find_nsec3(&nsec3, &rdataset, nsec3param); if (result == ISC_R_NOMORE) { dns_rdataset_disassociate(&rdataset); continue; } if (result != ISC_R_SUCCESS) { goto failure; } /* * Delete the old previous NSEC3. */ CHECK(delnsec3(db, version, prev, nsec3param, diff)); /* * Fixup the previous NSEC3. */ nsec3.next = nexthash; nsec3.next_length = (unsigned char)next_length; isc_buffer_init(&buffer, nsec3buf, sizeof(nsec3buf)); CHECK(dns_rdata_fromstruct(&rdata, rdataset.rdclass, dns_rdatatype_nsec3, &nsec3, &buffer)); CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, prev, rdataset.ttl, &rdata, &tuple)); CHECK(do_one_tuple(&tuple, db, version, diff)); dns_rdata_reset(&rdata); dns_rdataset_disassociate(&rdataset); break; } while (pass < 2); INSIST(pass < 2); /* * Delete the old NSEC3 and record the change. */ CHECK(delnsec3(db, version, hashname, nsec3param, diff)); } while (1); success: result = ISC_R_SUCCESS; failure: if (dbit != NULL) { dns_dbiterator_destroy(&dbit); } if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } return (result); } isc_result_t dns_nsec3_delnsec3s(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, dns_diff_t *diff) { return (dns_nsec3_delnsec3sx(db, version, name, 0, diff)); } isc_result_t dns_nsec3_delnsec3sx(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name, dns_rdatatype_t privatetype, dns_diff_t *diff) { dns_dbnode_t *node = NULL; dns_rdata_nsec3param_t nsec3param; dns_rdataset_t rdataset; isc_result_t result; dns_rdataset_init(&rdataset); /* * Find the NSEC3 parameters for this zone. */ result = dns_db_getoriginnode(db, &node); if (result != ISC_R_SUCCESS) { return (result); } result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3param, 0, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { goto try_private; } if (result != ISC_R_SUCCESS) { goto failure; } /* * Update each active NSEC3 chain. */ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); if (nsec3param.flags != 0) { continue; } /* * We have a active chain. Update it. */ CHECK(dns_nsec3_delnsec3(db, version, name, &nsec3param, diff)); } dns_rdataset_disassociate(&rdataset); try_private: if (privatetype == 0) { goto success; } result = dns_db_findrdataset(db, node, version, privatetype, 0, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { goto success; } if (result != ISC_R_SUCCESS) { goto failure; } /* * Update each NSEC3 chain being built. */ for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata1 = DNS_RDATA_INIT; dns_rdata_t rdata2 = DNS_RDATA_INIT; unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; dns_rdataset_current(&rdataset, &rdata1); if (!dns_nsec3param_fromprivate(&rdata1, &rdata2, buf, sizeof(buf))) { continue; } CHECK(dns_rdata_tostruct(&rdata2, &nsec3param, NULL)); if ((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) { continue; } if (better_param(&rdataset, &rdata2)) { continue; } /* * We have a active chain. Update it. */ CHECK(dns_nsec3_delnsec3(db, version, name, &nsec3param, diff)); } if (result == ISC_R_NOMORE) { success: result = ISC_R_SUCCESS; } failure: if (dns_rdataset_isassociated(&rdataset)) { dns_rdataset_disassociate(&rdataset); } if (node != NULL) { dns_db_detachnode(db, &node); } return (result); } isc_result_t dns_nsec3_active(dns_db_t *db, dns_dbversion_t *version, bool complete, bool *answer) { return (dns_nsec3_activex(db, version, complete, 0, answer)); } isc_result_t dns_nsec3_activex(dns_db_t *db, dns_dbversion_t *version, bool complete, dns_rdatatype_t privatetype, bool *answer) { dns_dbnode_t *node = NULL; dns_rdataset_t rdataset; dns_rdata_nsec3param_t nsec3param; isc_result_t result; REQUIRE(answer != NULL); dns_rdataset_init(&rdataset); result = dns_db_getoriginnode(db, &node); if (result != ISC_R_SUCCESS) { return (result); } result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec3param, 0, 0, &rdataset, NULL); if (result == ISC_R_NOTFOUND) { goto try_private; } if (result != ISC_R_SUCCESS) { dns_db_detachnode(db, &node); return (result); } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &nsec3param, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (nsec3param.flags == 0) { break; } } dns_rdataset_disassociate(&rdataset); if (result == ISC_R_SUCCESS) { dns_db_detachnode(db, &node); *answer = true; return (ISC_R_SUCCESS); } if (result == ISC_R_NOMORE) { *answer = false; } try_private: if (privatetype == 0 || complete) { dns_db_detachnode(db, &node); *answer = false; return (ISC_R_SUCCESS); } result = dns_db_findrdataset(db, node, version, privatetype, 0, 0, &rdataset, NULL); dns_db_detachnode(db, &node); if (result == ISC_R_NOTFOUND) { *answer = false; return (ISC_R_SUCCESS); } if (result != ISC_R_SUCCESS) { return (result); } for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { dns_rdata_t rdata1 = DNS_RDATA_INIT; dns_rdata_t rdata2 = DNS_RDATA_INIT; unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE]; dns_rdataset_current(&rdataset, &rdata1); if (!dns_nsec3param_fromprivate(&rdata1, &rdata2, buf, sizeof(buf))) { continue; } result = dns_rdata_tostruct(&rdata2, &nsec3param, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); if (!complete && CREATE(nsec3param.flags)) { break; } } dns_rdataset_disassociate(&rdataset); if (result == ISC_R_SUCCESS) { *answer = true; result = ISC_R_SUCCESS; } if (result == ISC_R_NOMORE) { *answer = false; result = ISC_R_SUCCESS; } return (result); } unsigned int dns_nsec3_maxiterations(void) { return (DNS_NSEC3_MAXITERATIONS); } isc_result_t dns_nsec3_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, const dns_name_t *nsec3name, dns_rdataset_t *nsec3set, dns_name_t *zonename, bool *exists, bool *data, bool *optout, bool *unknown, bool *setclosest, bool *setnearest, dns_name_t *closest, dns_name_t *nearest, dns_nseclog_t logit, void *arg) { char namebuf[DNS_NAME_FORMATSIZE]; dns_fixedname_t fzone; dns_fixedname_t qfixed; dns_label_t hashlabel; dns_name_t *qname; dns_name_t *zone; dns_rdata_nsec3_t nsec3; dns_rdata_t rdata = DNS_RDATA_INIT; int order; int scope; bool atparent; bool first; bool ns; bool soa; isc_buffer_t buffer; isc_result_t answer = ISC_R_IGNORE; isc_result_t result; unsigned char hash[NSEC3_MAX_HASH_LENGTH]; unsigned char owner[NSEC3_MAX_HASH_LENGTH]; unsigned int length; unsigned int qlabels; unsigned int zlabels; REQUIRE((exists == NULL && data == NULL) || (exists != NULL && data != NULL)); REQUIRE(nsec3set != NULL && nsec3set->type == dns_rdatatype_nsec3); REQUIRE((setclosest == NULL && closest == NULL) || (setclosest != NULL && closest != NULL)); REQUIRE((setnearest == NULL && nearest == NULL) || (setnearest != NULL && nearest != NULL)); result = dns_rdataset_first(nsec3set); if (result != ISC_R_SUCCESS) { (*logit)(arg, ISC_LOG_DEBUG(3), "failure processing NSEC3 set"); return (result); } dns_rdataset_current(nsec3set, &rdata); result = dns_rdata_tostruct(&rdata, &nsec3, NULL); if (result != ISC_R_SUCCESS) { return (result); } (*logit)(arg, ISC_LOG_DEBUG(3), "looking for relevant NSEC3"); zone = dns_fixedname_initname(&fzone); zlabels = dns_name_countlabels(nsec3name); /* * NSEC3 records must have two or more labels to be valid. */ if (zlabels < 2) { return (ISC_R_IGNORE); } /* * Strip off the NSEC3 hash to get the zone. */ zlabels--; dns_name_split(nsec3name, zlabels, NULL, zone); /* * If not below the zone name we can ignore this record. */ if (!dns_name_issubdomain(name, zone)) { return (ISC_R_IGNORE); } /* * Is this zone the same or deeper than the current zone? */ if (dns_name_countlabels(zonename) == 0 || dns_name_issubdomain(zone, zonename)) { dns_name_copynf(zone, zonename); } if (!dns_name_equal(zone, zonename)) { return (ISC_R_IGNORE); } /* * Are we only looking for the most enclosing zone? */ if (exists == NULL || data == NULL) { return (ISC_R_SUCCESS); } /* * Only set unknown once we are sure that this NSEC3 is from * the deepest covering zone. */ if (!dns_nsec3_supportedhash(nsec3.hash)) { if (unknown != NULL) { *unknown = true; } return (ISC_R_IGNORE); } /* * Recover the hash from the first label. */ dns_name_getlabel(nsec3name, 0, &hashlabel); isc_region_consume(&hashlabel, 1); isc_buffer_init(&buffer, owner, sizeof(owner)); result = isc_base32hex_decoderegion(&hashlabel, &buffer); if (result != ISC_R_SUCCESS) { return (result); } /* * The hash lengths should match. If not ignore the record. */ if (isc_buffer_usedlength(&buffer) != nsec3.next_length) { return (ISC_R_IGNORE); } /* * Work out what this NSEC3 covers. * Inside (<0) or outside (>=0). */ scope = memcmp(owner, nsec3.next, nsec3.next_length); /* * Prepare to compute all the hashes. */ qname = dns_fixedname_initname(&qfixed); dns_name_downcase(name, qname, NULL); qlabels = dns_name_countlabels(qname); first = true; while (qlabels >= zlabels) { /* * If there are too many iterations reject the NSEC3 record. */ if (nsec3.iterations > DNS_NSEC3_MAXITERATIONS) { return (DNS_R_NSEC3ITERRANGE); } length = isc_iterated_hash(hash, nsec3.hash, nsec3.iterations, nsec3.salt, nsec3.salt_length, qname->ndata, qname->length); /* * The computed hash length should match. */ if (length != nsec3.next_length) { (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring NSEC bad length %u vs %u", length, nsec3.next_length); return (ISC_R_IGNORE); } order = memcmp(hash, owner, length); if (first && order == 0) { /* * The hashes are the same. */ atparent = dns_rdatatype_atparent(type); ns = dns_nsec3_typepresent(&rdata, dns_rdatatype_ns); soa = dns_nsec3_typepresent(&rdata, dns_rdatatype_soa); if (ns && !soa) { if (!atparent) { /* * This NSEC3 record is from somewhere * higher in the DNS, and at the * parent of a delegation. It can not * be legitimately used here. */ (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring parent NSEC3"); return (ISC_R_IGNORE); } } else if (atparent && ns && soa) { /* * This NSEC3 record is from the child. * It can not be legitimately used here. */ (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring child NSEC3"); return (ISC_R_IGNORE); } if (type == dns_rdatatype_cname || type == dns_rdatatype_nxt || type == dns_rdatatype_nsec || type == dns_rdatatype_key || !dns_nsec3_typepresent(&rdata, dns_rdatatype_cname)) { *exists = true; *data = dns_nsec3_typepresent(&rdata, type); (*logit)(arg, ISC_LOG_DEBUG(3), "NSEC3 proves name exists (owner) " "data=%d", *data); return (ISC_R_SUCCESS); } (*logit)(arg, ISC_LOG_DEBUG(3), "NSEC3 proves CNAME exists"); return (ISC_R_IGNORE); } if (order == 0 && dns_nsec3_typepresent(&rdata, dns_rdatatype_ns) && !dns_nsec3_typepresent(&rdata, dns_rdatatype_soa)) { /* * This NSEC3 record is from somewhere higher in * the DNS, and at the parent of a delegation. * It can not be legitimately used here. */ (*logit)(arg, ISC_LOG_DEBUG(3), "ignoring parent NSEC3"); return (ISC_R_IGNORE); } /* * Potential closest encloser. */ if (order == 0) { if (closest != NULL && (dns_name_countlabels(closest) == 0 || dns_name_issubdomain(qname, closest)) && !dns_nsec3_typepresent(&rdata, dns_rdatatype_ds) && !dns_nsec3_typepresent(&rdata, dns_rdatatype_dname) && (dns_nsec3_typepresent(&rdata, dns_rdatatype_soa) || !dns_nsec3_typepresent(&rdata, dns_rdatatype_ns))) { dns_name_format(qname, namebuf, sizeof(namebuf)); (*logit)(arg, ISC_LOG_DEBUG(3), "NSEC3 indicates potential closest " "encloser: '%s'", namebuf); dns_name_copynf(qname, closest); *setclosest = true; } dns_name_format(qname, namebuf, sizeof(namebuf)); (*logit)(arg, ISC_LOG_DEBUG(3), "NSEC3 at super-domain %s", namebuf); return (answer); } /* * Find if the name does not exist. * * We continue as we need to find the name closest to the * closest encloser that doesn't exist. * * We also need to continue to ensure that we are not * proving the non-existence of a record in a sub-zone. * If that would be the case we will return ISC_R_IGNORE * above. */ if ((scope < 0 && order > 0 && memcmp(hash, nsec3.next, length) < 0) || (scope >= 0 && (order > 0 || memcmp(hash, nsec3.next, length) < 0))) { dns_name_format(qname, namebuf, sizeof(namebuf)); (*logit)(arg, ISC_LOG_DEBUG(3), "NSEC3 proves " "name does not exist: '%s'", namebuf); if (nearest != NULL && (dns_name_countlabels(nearest) == 0 || dns_name_issubdomain(nearest, qname))) { dns_name_copynf(qname, nearest); *setnearest = true; } *exists = false; *data = false; if (optout != NULL) { *optout = ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0); (*logit)(arg, ISC_LOG_DEBUG(3), (*optout ? "NSEC3 indicates optout" : "NSEC3 indicates secure " "range")); } answer = ISC_R_SUCCESS; } qlabels--; if (qlabels > 0) { dns_name_split(qname, qlabels, NULL, qname); } first = false; } return (answer); }