#include "config.h"
#ifdef NSEC3
#include <stdio.h>
#include <stdlib.h>
#include "nsec3.h"
#include "iterated_hash.h"
#include "namedb.h"
#include "nsd.h"
#include "answer.h"
#include "options.h"
#define NSEC3_RDATA_BITMAP 5
static int
cmp_hash_tree(const void* x, const void* y)
{
const domain_type* a = (const domain_type*)x;
const domain_type* b = (const domain_type*)y;
if(!a->nsec3) return (b->nsec3?-1:0);
if(!b->nsec3) return 1;
if(!a->nsec3->hash_wc) return (b->nsec3->hash_wc?-1:0);
if(!b->nsec3->hash_wc) return 1;
return memcmp(a->nsec3->hash_wc->hash.hash,
b->nsec3->hash_wc->hash.hash, NSEC3_HASH_LEN);
}
static int
cmp_wchash_tree(const void* x, const void* y)
{
const domain_type* a = (const domain_type*)x;
const domain_type* b = (const domain_type*)y;
if(!a->nsec3) return (b->nsec3?-1:0);
if(!b->nsec3) return 1;
if(!a->nsec3->hash_wc) return (b->nsec3->hash_wc?-1:0);
if(!b->nsec3->hash_wc) return 1;
return memcmp(a->nsec3->hash_wc->wc.hash,
b->nsec3->hash_wc->wc.hash, NSEC3_HASH_LEN);
}
static int
cmp_dshash_tree(const void* x, const void* y)
{
const domain_type* a = (const domain_type*)x;
const domain_type* b = (const domain_type*)y;
if(!a->nsec3) return (b->nsec3?-1:0);
if(!b->nsec3) return 1;
if(!a->nsec3->ds_parent_hash) return (b->nsec3->ds_parent_hash?-1:0);
if(!b->nsec3->ds_parent_hash) return 1;
return memcmp(a->nsec3->ds_parent_hash->hash,
b->nsec3->ds_parent_hash->hash, NSEC3_HASH_LEN);
}
static int
cmp_nsec3_tree(const void* x, const void* y)
{
const domain_type* a = (const domain_type*)x;
const domain_type* b = (const domain_type*)y;
assert(dname_name(domain_dname_const(a))[0] == 32);
assert(dname_name(domain_dname_const(b))[0] == 32);
return memcmp(dname_name(domain_dname_const(a)), dname_name(domain_dname_const(b)), 33);
}
void nsec3_zone_trees_create(struct region* region, zone_type* zone)
{
if(!zone->nsec3tree)
zone->nsec3tree = rbtree_create(region, cmp_nsec3_tree);
if(!zone->hashtree)
zone->hashtree = rbtree_create(region, cmp_hash_tree);
if(!zone->wchashtree)
zone->wchashtree = rbtree_create(region, cmp_wchash_tree);
if(!zone->dshashtree)
zone->dshashtree = rbtree_create(region, cmp_dshash_tree);
}
static void
detect_nsec3_params(rr_type* nsec3_apex,
const unsigned char** salt, int* salt_len, int* iter)
{
assert(salt && salt_len && iter);
assert(nsec3_apex);
if(nsec3_apex->rdlength < 5)
return;
if(nsec3_apex->rdlength < 5 + (uint16_t)(nsec3_apex->rdata[4]))
return;
*salt_len = nsec3_apex->rdata[4];
*salt = (unsigned char*)(nsec3_apex->rdata + 5);
*iter = read_uint16(nsec3_apex->rdata + 2);
}
const dname_type *
nsec3_b32_create(region_type* region, zone_type* zone, unsigned char* hash)
{
const dname_type* dname;
char b32[SHA_DIGEST_LENGTH*2+1];
b32_ntop(hash, SHA_DIGEST_LENGTH, b32, sizeof(b32));
dname=dname_parse(region, b32);
dname=dname_concatenate(region, dname, domain_dname(zone->apex));
return dname;
}
void
nsec3_hash_and_store(zone_type* zone, const dname_type* dname, uint8_t* store)
{
const unsigned char* nsec3_salt = NULL;
int nsec3_saltlength = 0;
int nsec3_iterations = 0;
detect_nsec3_params(zone->nsec3_param, &nsec3_salt,
&nsec3_saltlength, &nsec3_iterations);
assert(nsec3_iterations >= 0 && nsec3_iterations <= 65536);
iterated_hash((unsigned char*)store, nsec3_salt, nsec3_saltlength,
dname_name(dname), dname->name_size, nsec3_iterations);
}
#define STORE_HASH(x,y) memmove(domain->nsec3->x,y,NSEC3_HASH_LEN); domain->nsec3->have_##x =1;
static void
nsec3_lookup_hash_and_wc(region_type* region, zone_type* zone,
const dname_type* dname, domain_type* domain, region_type* tmpregion)
{
const dname_type* wcard;
if(domain->nsec3->hash_wc) {
return;
}
domain->nsec3->hash_wc = (nsec3_hash_wc_node_type *)
region_alloc(region, sizeof(nsec3_hash_wc_node_type));
domain->nsec3->hash_wc->hash.node.key = NULL;
domain->nsec3->hash_wc->wc.node.key = NULL;
nsec3_hash_and_store(zone, dname, domain->nsec3->hash_wc->hash.hash);
wcard = dname_parse(tmpregion, "*");
wcard = dname_concatenate(tmpregion, wcard, dname);
nsec3_hash_and_store(zone, wcard, domain->nsec3->hash_wc->wc.hash);
}
static void
nsec3_lookup_hash_ds(region_type* region, zone_type* zone,
const dname_type* dname, domain_type* domain)
{
if(domain->nsec3->ds_parent_hash) {
return;
}
domain->nsec3->ds_parent_hash = (nsec3_hash_node_type *)
region_alloc(region, sizeof(nsec3_hash_node_type));
domain->nsec3->ds_parent_hash->node.key = NULL;
nsec3_hash_and_store(zone, dname, domain->nsec3->ds_parent_hash->hash);
}
static int
nsec3_has_soa(rr_type* rr)
{
size_t offset;
if(rr->rdlength < 6)
return 0;
offset = 4;
if(rr->rdlength < offset+1 ||
rr->rdlength < offset+1+rr->rdata[offset])
return 0;
offset += 1 + rr->rdata[offset];
if(rr->rdlength < offset+1 ||
rr->rdlength < offset+1+rr->rdata[offset])
return 0;
offset += 1 + rr->rdata[offset];
if (rr->rdlength >= offset + 3 &&
rr->rdata[offset] == 0 &&
(rr->rdata[offset+2] & 0x02))
return 1;
return 0;
}
static rr_type*
check_apex_soa(namedb_type* namedb, zone_type *zone, int nolog)
{
uint8_t h[NSEC3_HASH_LEN];
domain_type* domain;
const dname_type* hashed_apex, *dname = domain_dname(zone->apex);
unsigned j;
rrset_type* nsec3_rrset;
region_type* tmpregion;
nsec3_hash_and_store(zone, dname, h);
tmpregion = region_create(xalloc, free);
hashed_apex = nsec3_b32_create(tmpregion, zone, h);
domain = domain_table_find(namedb->domains, hashed_apex);
if(!domain) {
if(!nolog) {
log_msg(LOG_ERR, "%s NSEC3PARAM entry has no hash(apex).",
domain_to_string(zone->apex));
log_msg(LOG_ERR, "hash(apex)= %s",
dname_to_string(hashed_apex, NULL));
}
region_destroy(tmpregion);
return NULL;
}
nsec3_rrset = domain_find_rrset(domain, zone, TYPE_NSEC3);
if(!nsec3_rrset) {
if(!nolog) {
log_msg(LOG_ERR, "%s NSEC3PARAM entry: hash(apex) has no NSEC3 RRset.",
domain_to_string(zone->apex));
log_msg(LOG_ERR, "hash(apex)= %s",
dname_to_string(hashed_apex, NULL));
}
region_destroy(tmpregion);
return NULL;
}
for(j=0; j<nsec3_rrset->rr_count; j++) {
if(nsec3_has_soa(nsec3_rrset->rrs[j])) {
region_destroy(tmpregion);
return nsec3_rrset->rrs[j];
}
}
if(!nolog) {
log_msg(LOG_ERR, "%s NSEC3PARAM entry: hash(apex) NSEC3 has no SOA flag.",
domain_to_string(zone->apex));
log_msg(LOG_ERR, "hash(apex)= %s",
dname_to_string(hashed_apex, NULL));
}
region_destroy(tmpregion);
return NULL;
}
static void
nsec3param_to_str(struct rr* rr, char* str, size_t buflen)
{
size_t len;
if(rr->rdlength < 5 || rr->rdlength < 5 + (uint16_t)rr->rdata[4]) {
str[0]=0;
return;
}
len = snprintf(str, buflen, "%u %u %u ",
(unsigned)rr->rdata[0], (unsigned)rr->rdata[1],
(unsigned)read_uint16(rr->rdata+2));
if(rr->rdata[4] == 0) {
if(buflen > len + 2)
str[len++] = '-';
} else {
len += hex_ntop(rr->rdata+5, rr->rdata[4], str+len, buflen-len-1);
}
if(buflen > len + 1) {
str[len] = 0;
} else {
assert(buflen > 0);
str[buflen-1] = 0;
}
}
static struct rr*
db_find_nsec3param(struct namedb* db, struct zone* z, struct rr* avoid_rr,
int checkchain)
{
unsigned i;
rrset_type* rrset = domain_find_rrset(z->apex, z, TYPE_NSEC3PARAM);
if(!rrset)
return NULL;
for(i=0; i<rrset->rr_count; i++) {
if(rrset->rrs[i] == avoid_rr) continue;
if(rrset->rrs[i]->rdlength < 5) continue;
if(rrset->rrs[i]->rdata[0] == NSEC3_SHA1_HASH &&
rrset->rrs[i]->rdata[1] == 0) {
if(checkchain) {
z->nsec3_param = rrset->rrs[i];
if(!check_apex_soa(db, z, 1)) {
char str[MAX_RDLENGTH*2+16];
nsec3param_to_str(z->nsec3_param,
str, sizeof(str));
VERBOSITY(1, (LOG_WARNING, "zone %s NSEC3PARAM %s has broken chain, ignoring", domain_to_string(z->apex), str));
continue;
}
}
if(2 <= verbosity) {
char str[MAX_RDLENGTH*2+16];
nsec3param_to_str(rrset->rrs[i], str,
sizeof(str));
VERBOSITY(2, (LOG_INFO, "rehash of zone %s with parameters %s",
domain_to_string(z->apex), str));
}
return rrset->rrs[i];
}
}
return NULL;
}
void
nsec3_find_zone_param(struct namedb* db, struct zone* zone,
struct rr* avoid_rr, int checkchain)
{
zone->nsec3_param = db_find_nsec3param(db, zone, avoid_rr, checkchain);
}
static int
nsec3_rdata_params_ok(const rr_type *prr, const rr_type* rr)
{
if(prr->rdlength > rr->rdlength)
return 0;
if(prr->rdlength < 5)
return 0;
return prr->rdata[0] == rr->rdata[0] &&
(memcmp(prr->rdata+2, rr->rdata+2, prr->rdlength-2) == 0);
}
int
nsec3_rr_uses_params(rr_type* rr, zone_type* zone)
{
if(!rr || rr->rdlength < 6)
return 0;
return nsec3_rdata_params_ok(zone->nsec3_param, rr);
}
int
nsec3_in_chain_count(domain_type* domain, zone_type* zone)
{
rrset_type* rrset = domain_find_rrset(domain, zone, TYPE_NSEC3);
unsigned i;
int count = 0;
if(!rrset || !zone->nsec3_param)
return 0;
for(i=0; i<rrset->rr_count; i++) {
if(nsec3_rr_uses_params(rrset->rrs[i], zone))
count++;
}
return count;
}
struct domain*
nsec3_chain_find_prev(struct zone* zone, struct domain* domain)
{
if(domain->nsec3 && domain->nsec3->nsec3_node.key) {
rbnode_type* r = rbtree_previous(&domain->nsec3->nsec3_node);
if(r != RBTREE_NULL) {
return (domain_type*)r->key;
}
}
if(zone->nsec3_last && zone->nsec3_last != domain)
return zone->nsec3_last;
return NULL;
}
static void
hash_tree_clear(rbtree_type* tree)
{
if(!tree) return;
tree->count = 0;
tree->root = RBTREE_NULL;
}
void
nsec3_clear_precompile(struct namedb* db, zone_type* zone)
{
domain_type* walk;
prehash_clear(db->domains);
hash_tree_clear(zone->nsec3tree);
hash_tree_clear(zone->hashtree);
hash_tree_clear(zone->wchashtree);
hash_tree_clear(zone->dshashtree);
walk = zone->apex;
while(walk && domain_is_subdomain(walk, zone->apex)) {
if(walk->nsec3) {
if(nsec3_condition_hash(walk, zone)) {
walk->nsec3->nsec3_node.key = NULL;
walk->nsec3->nsec3_cover = NULL;
walk->nsec3->nsec3_wcard_child_cover = NULL;
walk->nsec3->nsec3_is_exact = 0;
if (walk->nsec3->hash_wc) {
region_recycle(db->domains->region,
walk->nsec3->hash_wc,
sizeof(nsec3_hash_wc_node_type));
walk->nsec3->hash_wc = NULL;
}
}
if(nsec3_condition_dshash(walk, zone)) {
walk->nsec3->nsec3_ds_parent_cover = NULL;
walk->nsec3->nsec3_ds_parent_is_exact = 0;
if (walk->nsec3->ds_parent_hash) {
region_recycle(db->domains->region,
walk->nsec3->ds_parent_hash,
sizeof(nsec3_hash_node_type));
walk->nsec3->ds_parent_hash = NULL;
}
}
}
walk = domain_next(walk);
}
zone->nsec3_last = NULL;
}
int
nsec3_domain_part_of_zone(domain_type* d, zone_type* z)
{
while(d) {
if(d->is_apex)
return (z->apex == d);
d = d->parent;
}
return 0;
}
int
nsec3_condition_hash(domain_type* d, zone_type* z)
{
return d->is_existing && !domain_has_only_NSEC3(d, z) &&
nsec3_domain_part_of_zone(d, z) && !domain_is_glue(d, z);
}
int
nsec3_condition_dshash(domain_type* d, zone_type* z)
{
return d->is_existing && !domain_has_only_NSEC3(d, z) &&
(domain_find_rrset(d, z, TYPE_DS) ||
domain_find_rrset(d, z, TYPE_NS)) && d != z->apex
&& nsec3_domain_part_of_zone(d->parent, z);
}
zone_type*
nsec3_tree_zone(namedb_type* db, domain_type* d)
{
while(d) {
if(d->is_apex) {
rrset_type *rrset;
for (rrset = d->rrsets; rrset; rrset = rrset->next)
if (rrset_rrtype(rrset) == TYPE_SOA ||
rrset_rrtype(rrset) == TYPE_DNSKEY ||
rrset_rrtype(rrset) == TYPE_NSEC3PARAM)
return rrset->zone;
return namedb_find_zone(db, domain_dname(d));
}
d = d->parent;
}
return NULL;
}
zone_type*
nsec3_tree_dszone(namedb_type* db, domain_type* d)
{
if(d->is_apex)
d = d->parent;
while (d) {
if (d->is_apex) {
zone_type *zone = NULL;
for (rrset_type *rrset = d->rrsets; rrset; rrset = rrset->next)
if (rrset_rrtype(rrset) == TYPE_SOA ||
rrset_rrtype(rrset) == TYPE_DNSKEY ||
rrset_rrtype(rrset) == TYPE_NSEC3PARAM)
zone = rrset->zone;
if (!zone)
zone = namedb_find_zone(db, domain_dname(d));
if (zone && zone->dshashtree)
return zone;
}
d = d->parent;
}
return NULL;
}
int
nsec3_find_cover(zone_type* zone, uint8_t* hash, size_t hashlen,
domain_type** result)
{
rbnode_type* r = NULL;
int exact;
domain_type d;
uint8_t n[48];
b32_ntop(hash, hashlen, (char*)(n+5), sizeof(n)-5);
#ifdef USE_RADIX_TREE
d.dname = (dname_type*)n;
#else
d.node.key = n;
#endif
n[0] = 34;
n[1] = 2;
n[2] = 0;
n[3] = 0;
n[4] = 32;
assert(result);
assert(zone->nsec3_param && zone->nsec3tree);
exact = rbtree_find_less_equal(zone->nsec3tree, &d, &r);
if(r) {
*result = (domain_type*)r->key;
} else {
*result = zone->nsec3_last;
}
return exact;
}
void
nsec3_precompile_domain(struct namedb* db, struct domain* domain,
struct zone* zone, region_type* tmpregion)
{
domain_type* result = 0;
int exact;
allocate_domain_nsec3(db->domains, domain);
nsec3_lookup_hash_and_wc(db->region,
zone, domain_dname(domain), domain, tmpregion);
zone_add_domain_in_hash_tree(db->region, &zone->hashtree,
cmp_hash_tree, domain, &domain->nsec3->hash_wc->hash.node);
zone_add_domain_in_hash_tree(db->region, &zone->wchashtree,
cmp_wchash_tree, domain, &domain->nsec3->hash_wc->wc.node);
exact = nsec3_find_cover(zone, domain->nsec3->hash_wc->hash.hash,
sizeof(domain->nsec3->hash_wc->hash.hash), &result);
domain->nsec3->nsec3_cover = result;
if(exact)
domain->nsec3->nsec3_is_exact = 1;
else domain->nsec3->nsec3_is_exact = 0;
(void)nsec3_find_cover(zone, domain->nsec3->hash_wc->wc.hash,
sizeof(domain->nsec3->hash_wc->wc.hash), &result);
domain->nsec3->nsec3_wcard_child_cover = result;
}
void
nsec3_precompile_domain_ds(struct namedb* db, struct domain* domain,
struct zone* zone)
{
domain_type* result = 0;
int exact;
allocate_domain_nsec3(db->domains, domain);
nsec3_lookup_hash_ds(db->region, zone, domain_dname(domain), domain);
exact = nsec3_find_cover(zone, domain->nsec3->ds_parent_hash->hash,
sizeof(domain->nsec3->ds_parent_hash->hash), &result);
domain->nsec3->nsec3_ds_parent_is_exact = exact != 0;
domain->nsec3->nsec3_ds_parent_cover = result;
zone_add_domain_in_hash_tree(db->region, &zone->dshashtree,
cmp_dshash_tree, domain, &domain->nsec3->ds_parent_hash->node);
}
static void
parse_nsec3_name(const dname_type* dname, uint8_t* hash, size_t buflen)
{
size_t lablen = (buflen-1) * 8 / 5;
const uint8_t* wire = dname_name(dname);
assert(lablen == 32 && buflen == NSEC3_HASH_LEN+1);
if(wire[0] != lablen) {
memset(hash, 0, buflen);
return;
}
(void)b32_pton((char*)wire+1, hash, buflen);
}
void
nsec3_precompile_nsec3rr(namedb_type* db, struct domain* domain,
struct zone* zone)
{
allocate_domain_nsec3(db->domains, domain);
zone_add_domain_in_hash_tree(db->region, &zone->nsec3tree,
cmp_nsec3_tree, domain, &domain->nsec3->nsec3_node);
if(rbtree_last(zone->nsec3tree)->key == domain) {
zone->nsec3_last = domain;
}
}
void
nsec3_precompile_newparam(namedb_type* db, zone_type* zone)
{
region_type* tmpregion = region_create(xalloc, free);
domain_type* walk;
time_t s = time(NULL);
unsigned long n = 0, c = 0;
for(walk=zone->apex; walk && domain_is_subdomain(walk, zone->apex);
walk = domain_next(walk)) {
n++;
if(nsec3_in_chain_count(walk, zone) != 0) {
nsec3_precompile_nsec3rr(db, walk, zone);
}
}
for(walk=zone->apex; walk && domain_is_subdomain(walk, zone->apex);
walk = domain_next(walk)) {
if(nsec3_condition_hash(walk, zone)) {
nsec3_precompile_domain(db, walk, zone, tmpregion);
region_free_all(tmpregion);
}
if(nsec3_condition_dshash(walk, zone))
nsec3_precompile_domain_ds(db, walk, zone);
if(++c % ZONEC_PCT_COUNT == 0 && time(NULL) > s + ZONEC_PCT_TIME) {
s = time(NULL);
VERBOSITY(1, (LOG_INFO, "nsec3 %s %d %%",
zone->opts->name,
(n==0)?0:(int)(c*((unsigned long)100)/n)));
}
}
region_destroy(tmpregion);
}
void
prehash_zone_complete(struct namedb* db, struct zone* zone)
{
nsec3_clear_precompile(db, zone);
assert(db && zone);
nsec3_find_zone_param(db, zone, NULL, 1);
if(!zone->nsec3_param || !check_apex_soa(db, zone, 0)) {
zone->nsec3_param = NULL;
zone->nsec3_last = NULL;
return;
}
nsec3_zone_trees_create(db->region, zone);
nsec3_precompile_newparam(db, zone);
}
static void
init_lookup_key_hash_tree(domain_type* d, uint8_t* hash)
{ memcpy(d->nsec3->hash_wc->hash.hash, hash, NSEC3_HASH_LEN); }
static void
init_lookup_key_wc_tree(domain_type* d, uint8_t* hash)
{ memcpy(d->nsec3->hash_wc->wc.hash, hash, NSEC3_HASH_LEN); }
static void
init_lookup_key_ds_tree(domain_type* d, uint8_t* hash)
{ memcpy(d->nsec3->ds_parent_hash->hash, hash, NSEC3_HASH_LEN); }
static int
process_first(rbtree_type* tree, uint8_t* hash, rbnode_type** p,
void (*init)(domain_type*, uint8_t*))
{
domain_type d;
struct nsec3_domain_data n;
nsec3_hash_wc_node_type hash_wc;
nsec3_hash_node_type ds_parent_hash;
if(!tree) {
*p = RBTREE_NULL;
return 0;
}
hash_wc.hash.node.key = NULL;
hash_wc.wc.node.key = NULL;
n.hash_wc = &hash_wc;
ds_parent_hash.node.key = NULL;
n.ds_parent_hash = &ds_parent_hash;
d.nsec3 = &n;
init(&d, hash);
if(rbtree_find_less_equal(tree, &d, p)) {
return 1;
}
if(!*p)
*p = rbtree_first(tree);
else
*p = rbtree_next(*p);
return 0;
}
static void
process_end(rbtree_type* tree, uint8_t* hash, rbnode_type** p,
void (*init)(domain_type*, uint8_t*))
{
domain_type d;
struct nsec3_domain_data n;
nsec3_hash_wc_node_type hash_wc;
nsec3_hash_node_type ds_parent_hash;
if(!tree) {
*p = RBTREE_NULL;
return;
}
hash_wc.hash.node.key = NULL;
hash_wc.wc.node.key = NULL;
n.hash_wc = &hash_wc;
ds_parent_hash.node.key = NULL;
n.ds_parent_hash = &ds_parent_hash;
d.nsec3 = &n;
init(&d, hash);
if(rbtree_find_less_equal(tree, &d, p)) {
return;
}
if(!*p) {
*p = rbtree_first(tree);
return;
}
*p = rbtree_next(*p);
}
static void
process_range(zone_type* zone, domain_type* start,
domain_type* end, domain_type* nsec3)
{
rbnode_type *p = RBTREE_NULL, *pwc = RBTREE_NULL, *pds = RBTREE_NULL;
rbnode_type *p_end = RBTREE_NULL, *pwc_end = RBTREE_NULL, *pds_end = RBTREE_NULL;
if(start) {
uint8_t hash[NSEC3_HASH_LEN+1];
parse_nsec3_name(domain_dname(start), hash, sizeof(hash));
if(process_first(zone->hashtree, hash, &p, init_lookup_key_hash_tree)) {
((domain_type*)(p->key))->nsec3->nsec3_cover = nsec3;
((domain_type*)(p->key))->nsec3->nsec3_is_exact = 1;
p = rbtree_next(p);
}
(void)process_first(zone->wchashtree, hash, &pwc, init_lookup_key_wc_tree);
if(process_first(zone->dshashtree, hash, &pds, init_lookup_key_ds_tree)){
((domain_type*)(pds->key))->nsec3->
nsec3_ds_parent_cover = nsec3;
((domain_type*)(pds->key))->nsec3->
nsec3_ds_parent_is_exact = 1;
pds = rbtree_next(pds);
}
} else {
if(zone->hashtree)
p = rbtree_first(zone->hashtree);
if(zone->wchashtree)
pwc = rbtree_first(zone->wchashtree);
if(zone->dshashtree)
pds = rbtree_first(zone->dshashtree);
}
if(end) {
uint8_t hash[NSEC3_HASH_LEN+1];
parse_nsec3_name(domain_dname(end), hash, sizeof(hash));
process_end(zone->hashtree, hash, &p_end, init_lookup_key_hash_tree);
process_end(zone->wchashtree, hash, &pwc_end, init_lookup_key_wc_tree);
process_end(zone->dshashtree, hash, &pds_end, init_lookup_key_ds_tree);
}
while(p != RBTREE_NULL && p != p_end) {
((domain_type*)(p->key))->nsec3->nsec3_cover = nsec3;
((domain_type*)(p->key))->nsec3->nsec3_is_exact = 0;
p = rbtree_next(p);
}
while(pwc != RBTREE_NULL && pwc != pwc_end) {
((domain_type*)(pwc->key))->nsec3->
nsec3_wcard_child_cover = nsec3;
pwc = rbtree_next(pwc);
}
while(pds != RBTREE_NULL && pds != pds_end) {
((domain_type*)(pds->key))->nsec3->
nsec3_ds_parent_cover = nsec3;
((domain_type*)(pds->key))->nsec3->
nsec3_ds_parent_is_exact = 0;
pds = rbtree_next(pds);
}
}
static void
process_prehash_domain(domain_type* domain, zone_type* zone)
{
rbnode_type* nx;
assert(domain->nsec3 && domain->nsec3->nsec3_node.key);
nx = rbtree_next(&domain->nsec3->nsec3_node);
if(nx != RBTREE_NULL) {
domain_type* end = (domain_type*)nx->key;
process_range(zone, domain, end, domain);
} else {
domain_type* first = (domain_type*)(rbtree_first(
zone->nsec3tree)->key);
process_range(zone, domain, NULL, domain);
process_range(zone, NULL, first, domain);
}
}
void prehash_zone(struct namedb* db, struct zone* zone)
{
domain_type* d;
if(!zone->nsec3_param) {
prehash_clear(db->domains);
return;
}
if(!check_apex_soa(db, zone, 1)) {
prehash_clear(db->domains);
prehash_zone_complete(db, zone);
return;
}
for(d = db->domains->prehash_list; d; d = d->nsec3->prehash_next) {
process_prehash_domain(d, zone);
}
prehash_clear(db->domains);
if(!check_apex_soa(db, zone, 0)) {
zone->nsec3_param = NULL;
zone->nsec3_last = NULL;
}
}
static void
nsec3_add_rrset(struct query* query, struct answer* answer,
rr_section_type section, struct domain* domain)
{
if(domain) {
rrset_type* rrset = domain_find_rrset(domain, query->zone, TYPE_NSEC3);
if(rrset)
answer_add_rrset(answer, section, domain, rrset);
}
}
static void
nsec3_add_nonexist_proof(struct query* query, struct answer* answer,
struct domain* encloser, const dname_type* qname)
{
uint8_t hash[NSEC3_HASH_LEN];
const dname_type* to_prove;
domain_type* cover=0;
assert(encloser);
to_prove = dname_partial_copy(query->region, qname,
dname_label_match_count(qname, domain_dname(encloser))+1);
nsec3_hash_and_store(query->zone, to_prove, hash);
if(nsec3_find_cover(query->zone, hash, sizeof(hash), &cover))
{
domain_type* walk;
char hashbuf[512];
char reversebuf[512];
(void)b32_ntop(hash, sizeof(hash), hashbuf, sizeof(hashbuf));
snprintf(reversebuf, sizeof(reversebuf), "(no name in the zone hashes to this nsec3 record)");
walk = query->zone->apex;
while(walk) {
if(walk->nsec3 && walk->nsec3->nsec3_cover == cover) {
snprintf(reversebuf, sizeof(reversebuf),
"%s %s", domain_to_string(walk),
walk->nsec3->nsec3_is_exact?"exact":"no_exact_hash_match");
if(walk->nsec3->nsec3_is_exact)
break;
}
if(walk->nsec3 && walk->nsec3->nsec3_ds_parent_cover == cover) {
snprintf(reversebuf, sizeof(reversebuf),
"%s %s", domain_to_string(walk),
walk->nsec3->nsec3_ds_parent_is_exact?"exact":"no_exact_hash_match");
if(walk->nsec3->nsec3_ds_parent_is_exact)
break;
}
walk = domain_next(walk);
}
VERBOSITY(3, (LOG_ERR, "nsec3 hash collision for name=%s hash=%s reverse=%s",
dname_to_string(to_prove, NULL), hashbuf, reversebuf));
RCODE_SET(query->packet, RCODE_SERVFAIL);
ASSIGN_EDE_CODE_AND_STRING_LITERAL(query->edns.ede,
EDE_OTHER, "NSEC3 hash collision");
return;
}
else
{
nsec3_add_rrset(query, answer, AUTHORITY_SECTION, cover);
}
}
static void
nsec3_add_closest_encloser_proof(
struct query* query, struct answer* answer,
struct domain* closest_encloser, const dname_type* qname)
{
if(!closest_encloser)
return;
nsec3_add_nonexist_proof(query, answer, closest_encloser, qname);
if(closest_encloser->nsec3 && closest_encloser->nsec3->nsec3_is_exact)
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
closest_encloser->nsec3->nsec3_cover);
}
void
nsec3_answer_wildcard(struct query *query, struct answer *answer,
struct domain *wildcard, const dname_type* qname)
{
if(!wildcard)
return;
if(!query->zone->nsec3_param)
return;
nsec3_add_nonexist_proof(query, answer, wildcard, qname);
}
static void
nsec3_add_ds_proof(struct query *query, struct answer *answer,
struct domain *domain, int delegpt)
{
assert(domain != query->zone->apex);
if(domain->nsec3 && domain->nsec3->nsec3_ds_parent_is_exact) {
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
domain->nsec3->nsec3_ds_parent_cover);
} else if (!delegpt && domain->nsec3 && domain->nsec3->nsec3_is_exact
&& nsec3_domain_part_of_zone(domain->nsec3->nsec3_cover,
query->zone)) {
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
domain->nsec3->nsec3_cover);
} else {
domain_type* par = domain->parent;
domain_type* prev_par = 0;
while(par && (!par->nsec3 || !par->nsec3->nsec3_is_exact))
{
prev_par = par;
par = par->parent;
}
assert(par);
if(!par->nsec3) return;
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
par->nsec3->nsec3_cover);
if(prev_par && prev_par->nsec3) {
assert(prev_par != domain &&
!prev_par->nsec3->nsec3_is_exact);
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
prev_par->nsec3->nsec3_cover);
} else {
if(domain->nsec3) {
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
domain->nsec3->nsec3_ds_parent_cover);
}
}
}
}
void
nsec3_answer_nodata(struct query* query, struct answer* answer,
struct domain* original)
{
if(!query->zone->nsec3_param)
return;
if(query->qtype == TYPE_DS)
{
if(original == query->zone->apex) {
if(original->nsec3 && original->nsec3->nsec3_is_exact)
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
original->nsec3->nsec3_cover);
return;
}
nsec3_add_ds_proof(query, answer, original, 0);
if (original==original->wildcard_child_closest_match
&& label_is_wildcard(dname_name(domain_dname(original)))) {
if(original->parent && original->parent->nsec3 &&
original->parent->nsec3->nsec3_is_exact)
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
original->parent->nsec3->nsec3_cover);
}
}
else if (original==original->wildcard_child_closest_match
&& label_is_wildcard(dname_name(domain_dname(original)))) {
if(original->parent && original->parent->nsec3 &&
original->parent->nsec3->nsec3_is_exact)
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
original->parent->nsec3->nsec3_cover);
if(original->nsec3)
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
original->nsec3->nsec3_cover);
}
else {
if(original->nsec3) {
if(!original->nsec3->nsec3_is_exact) {
while(original->parent && original->parent->nsec3 && !original->parent->nsec3->nsec3_is_exact)
original = original->parent;
}
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
original->nsec3->nsec3_cover);
if(!original->nsec3->nsec3_is_exact) {
if(original->parent && original->parent->nsec3 && original->parent->nsec3->nsec3_is_exact)
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
original->parent->nsec3->nsec3_cover);
}
}
}
}
void
nsec3_answer_delegation(struct query *query, struct answer *answer)
{
if(!query->zone->nsec3_param)
return;
nsec3_add_ds_proof(query, answer, query->delegation_domain, 1);
}
int
domain_has_only_NSEC3(struct domain* domain, struct zone* zone)
{
rrset_type* rrset = domain->rrsets;
int nsec3_seen = 0;
while(rrset)
{
if(!zone || rrset->zone == zone)
{
if(rrset->rrs[0]->type == TYPE_NSEC3)
nsec3_seen = 1;
else if(rrset->rrs[0]->type != TYPE_RRSIG)
return 0;
}
rrset = rrset->next;
}
return nsec3_seen;
}
void
nsec3_answer_authoritative(struct domain** match, struct query *query,
struct answer *answer, struct domain* closest_encloser,
const dname_type* qname)
{
if(!query->zone->nsec3_param)
return;
assert(match);
if(*match && !(*match)->is_existing &&
#if 0
query->qtype != TYPE_NSEC3 &&
#endif
domain_has_only_NSEC3(*match, query->zone))
{
*match = 0;
if(query->zone->apex->nsec3 &&
query->zone->apex->nsec3->nsec3_is_exact)
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
query->zone->apex->nsec3->nsec3_cover);
if(closest_encloser->nsec3)
nsec3_add_rrset(query, answer, AUTHORITY_SECTION, closest_encloser->nsec3->nsec3_cover);
if(query->zone->apex->nsec3)
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
query->zone->apex->nsec3->nsec3_wcard_child_cover);
if (domain_wildcard_child(query->zone->apex)) {
RCODE_SET(query->packet, RCODE_SERVFAIL);
ASSIGN_EDE_CODE_AND_STRING_LITERAL(query->edns.ede,
EDE_OTHER, "Wildcard and NSEC3 domain clash");
}
return;
}
else if(*match && (*match)->is_existing &&
#if 0
query->qtype != TYPE_NSEC3 &&
#endif
(domain_has_only_NSEC3(*match, query->zone) ||
!domain_find_any_rrset(*match, query->zone)))
{
nsec3_answer_nodata(query, answer, *match);
return;
}
if(!*match) {
nsec3_add_closest_encloser_proof(query, answer, closest_encloser,
qname);
if(closest_encloser->nsec3)
nsec3_add_rrset(query, answer, AUTHORITY_SECTION,
closest_encloser->nsec3->nsec3_wcard_child_cover);
}
}
#endif