/* * services/authzone.c - authoritative zone that is locally hosted. * * Copyright (c) 2017, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains the functions for an authority zone. This zone * is queried by the iterator, just like a stub or forward zone, but then * the data is locally held. */ #include "config.h" #include "services/authzone.h" #include "util/data/dname.h" #include "util/data/msgparse.h" #include "util/data/msgreply.h" #include "util/data/msgencode.h" #include "util/data/packed_rrset.h" #include "util/regional.h" #include "util/net_help.h" #include "util/netevent.h" #include "util/config_file.h" #include "util/log.h" #include "util/module.h" #include "util/random.h" #include "services/cache/dns.h" #include "services/outside_network.h" #include "services/listen_dnsport.h" #include "services/mesh.h" #include "sldns/rrdef.h" #include "sldns/pkthdr.h" #include "sldns/sbuffer.h" #include "sldns/str2wire.h" #include "sldns/wire2str.h" #include "sldns/parseutil.h" #include "sldns/keyraw.h" #include "validator/val_nsec3.h" #include "validator/val_nsec.h" #include "validator/val_secalgo.h" #include "validator/val_sigcrypt.h" #include "validator/val_anchor.h" #include "validator/val_utils.h" #include /** bytes to use for NSEC3 hash buffer. 20 for sha1 */ #define N3HASHBUFLEN 32 /** max number of CNAMEs we are willing to follow (in one answer) */ #define MAX_CNAME_CHAIN 8 /** timeout for probe packets for SOA */ #define AUTH_PROBE_TIMEOUT 100 /* msec */ /** when to stop with SOA probes (when exponential timeouts exceed this) */ #define AUTH_PROBE_TIMEOUT_STOP 1000 /* msec */ /* auth transfer timeout for TCP connections, in msec */ #define AUTH_TRANSFER_TIMEOUT 10000 /* msec */ /* auth transfer max backoff for failed transfers and probes */ #define AUTH_TRANSFER_MAX_BACKOFF 86400 /* sec */ /* auth http port number */ #define AUTH_HTTP_PORT 80 /* auth https port number */ #define AUTH_HTTPS_PORT 443 /* max depth for nested $INCLUDEs */ #define MAX_INCLUDE_DEPTH 10 /** number of timeouts before we fallback from IXFR to AXFR, * because some versions of servers (eg. dnsmasq) drop IXFR packets. */ #define NUM_TIMEOUTS_FALLBACK_IXFR 3 /** pick up nextprobe task to start waiting to perform transfer actions */ static void xfr_set_timeout(struct auth_xfer* xfr, struct module_env* env, int failure, int lookup_only); /** move to sending the probe packets, next if fails. task_probe */ static void xfr_probe_send_or_end(struct auth_xfer* xfr, struct module_env* env); /** pick up probe task with specified(or NULL) destination first, * or transfer task if nothing to probe, or false if already in progress */ static int xfr_start_probe(struct auth_xfer* xfr, struct module_env* env, struct auth_master* spec); /** delete xfer structure (not its tree entry) */ static void auth_xfer_delete(struct auth_xfer* xfr); /** create new dns_msg */ static struct dns_msg* msg_create(struct regional* region, struct query_info* qinfo) { struct dns_msg* msg = (struct dns_msg*)regional_alloc(region, sizeof(struct dns_msg)); if(!msg) return NULL; msg->qinfo.qname = regional_alloc_init(region, qinfo->qname, qinfo->qname_len); if(!msg->qinfo.qname) return NULL; msg->qinfo.qname_len = qinfo->qname_len; msg->qinfo.qtype = qinfo->qtype; msg->qinfo.qclass = qinfo->qclass; msg->qinfo.local_alias = NULL; /* non-packed reply_info, because it needs to grow the array */ msg->rep = (struct reply_info*)regional_alloc_zero(region, sizeof(struct reply_info)-sizeof(struct rrset_ref)); if(!msg->rep) return NULL; msg->rep->flags = (uint16_t)(BIT_QR | BIT_AA); msg->rep->authoritative = 1; msg->rep->reason_bogus = LDNS_EDE_NONE; msg->rep->qdcount = 1; /* rrsets is NULL, no rrsets yet */ return msg; } /** grow rrset array by one in msg */ static int msg_grow_array(struct regional* region, struct dns_msg* msg) { if(msg->rep->rrsets == NULL) { msg->rep->rrsets = regional_alloc_zero(region, sizeof(struct ub_packed_rrset_key*)*(msg->rep->rrset_count+1)); if(!msg->rep->rrsets) return 0; } else { struct ub_packed_rrset_key** rrsets_old = msg->rep->rrsets; msg->rep->rrsets = regional_alloc_zero(region, sizeof(struct ub_packed_rrset_key*)*(msg->rep->rrset_count+1)); if(!msg->rep->rrsets) return 0; memmove(msg->rep->rrsets, rrsets_old, sizeof(struct ub_packed_rrset_key*)*msg->rep->rrset_count); } return 1; } /** get ttl of rrset */ static time_t get_rrset_ttl(struct ub_packed_rrset_key* k) { struct packed_rrset_data* d = (struct packed_rrset_data*) k->entry.data; return d->ttl; } /** Copy rrset into region from domain-datanode and packet rrset */ static struct ub_packed_rrset_key* auth_packed_rrset_copy_region(struct auth_zone* z, struct auth_data* node, struct auth_rrset* rrset, struct regional* region, time_t adjust) { struct ub_packed_rrset_key key; memset(&key, 0, sizeof(key)); key.entry.key = &key; key.entry.data = rrset->data; key.rk.dname = node->name; key.rk.dname_len = node->namelen; key.rk.type = htons(rrset->type); key.rk.rrset_class = htons(z->dclass); key.entry.hash = rrset_key_hash(&key.rk); return packed_rrset_copy_region(&key, region, adjust); } /** fix up msg->rep TTL and prefetch ttl */ static void msg_ttl(struct dns_msg* msg) { if(msg->rep->rrset_count == 0) return; if(msg->rep->rrset_count == 1) { msg->rep->ttl = get_rrset_ttl(msg->rep->rrsets[0]); msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; } else if(get_rrset_ttl(msg->rep->rrsets[msg->rep->rrset_count-1]) < msg->rep->ttl) { msg->rep->ttl = get_rrset_ttl(msg->rep->rrsets[ msg->rep->rrset_count-1]); msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; } } /** see if rrset is a duplicate in the answer message */ static int msg_rrset_duplicate(struct dns_msg* msg, uint8_t* nm, size_t nmlen, uint16_t type, uint16_t dclass) { size_t i; for(i=0; irep->rrset_count; i++) { struct ub_packed_rrset_key* k = msg->rep->rrsets[i]; if(ntohs(k->rk.type) == type && k->rk.dname_len == nmlen && ntohs(k->rk.rrset_class) == dclass && query_dname_compare(k->rk.dname, nm) == 0) return 1; } return 0; } /** add rrset to answer section (no auth, add rrsets yet) */ static int msg_add_rrset_an(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node, struct auth_rrset* rrset) { log_assert(msg->rep->ns_numrrsets == 0); log_assert(msg->rep->ar_numrrsets == 0); if(!rrset || !node) return 1; if(msg_rrset_duplicate(msg, node->name, node->namelen, rrset->type, z->dclass)) return 1; /* grow array */ if(!msg_grow_array(region, msg)) return 0; /* copy it */ if(!(msg->rep->rrsets[msg->rep->rrset_count] = auth_packed_rrset_copy_region(z, node, rrset, region, 0))) return 0; msg->rep->rrset_count++; msg->rep->an_numrrsets++; msg_ttl(msg); return 1; } /** add rrset to authority section (no additional section rrsets yet) */ static int msg_add_rrset_ns(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node, struct auth_rrset* rrset) { log_assert(msg->rep->ar_numrrsets == 0); if(!rrset || !node) return 1; if(msg_rrset_duplicate(msg, node->name, node->namelen, rrset->type, z->dclass)) return 1; /* grow array */ if(!msg_grow_array(region, msg)) return 0; /* copy it */ if(!(msg->rep->rrsets[msg->rep->rrset_count] = auth_packed_rrset_copy_region(z, node, rrset, region, 0))) return 0; msg->rep->rrset_count++; msg->rep->ns_numrrsets++; msg_ttl(msg); return 1; } /** add rrset to additional section */ static int msg_add_rrset_ar(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node, struct auth_rrset* rrset) { if(!rrset || !node) return 1; if(msg_rrset_duplicate(msg, node->name, node->namelen, rrset->type, z->dclass)) return 1; /* grow array */ if(!msg_grow_array(region, msg)) return 0; /* copy it */ if(!(msg->rep->rrsets[msg->rep->rrset_count] = auth_packed_rrset_copy_region(z, node, rrset, region, 0))) return 0; msg->rep->rrset_count++; msg->rep->ar_numrrsets++; msg_ttl(msg); return 1; } struct auth_zones* auth_zones_create(void) { struct auth_zones* az = (struct auth_zones*)calloc(1, sizeof(*az)); if(!az) { log_err("out of memory"); return NULL; } rbtree_init(&az->ztree, &auth_zone_cmp); rbtree_init(&az->xtree, &auth_xfer_cmp); lock_rw_init(&az->lock); lock_protect(&az->lock, &az->ztree, sizeof(az->ztree)); lock_protect(&az->lock, &az->xtree, sizeof(az->xtree)); /* also lock protects the rbnode's in struct auth_zone, auth_xfer */ lock_rw_init(&az->rpz_lock); lock_protect(&az->rpz_lock, &az->rpz_first, sizeof(az->rpz_first)); return az; } int auth_zone_cmp(const void* z1, const void* z2) { /* first sort on class, so that hierarchy can be maintained within * a class */ struct auth_zone* a = (struct auth_zone*)z1; struct auth_zone* b = (struct auth_zone*)z2; int m; if(a->dclass != b->dclass) { if(a->dclass < b->dclass) return -1; return 1; } /* sorted such that higher zones sort before lower zones (their * contents) */ return dname_lab_cmp(a->name, a->namelabs, b->name, b->namelabs, &m); } int auth_data_cmp(const void* z1, const void* z2) { struct auth_data* a = (struct auth_data*)z1; struct auth_data* b = (struct auth_data*)z2; int m; /* canonical sort, because DNSSEC needs that */ return dname_canon_lab_cmp(a->name, a->namelabs, b->name, b->namelabs, &m); } int auth_xfer_cmp(const void* z1, const void* z2) { /* first sort on class, so that hierarchy can be maintained within * a class */ struct auth_xfer* a = (struct auth_xfer*)z1; struct auth_xfer* b = (struct auth_xfer*)z2; int m; if(a->dclass != b->dclass) { if(a->dclass < b->dclass) return -1; return 1; } /* sorted such that higher zones sort before lower zones (their * contents) */ return dname_lab_cmp(a->name, a->namelabs, b->name, b->namelabs, &m); } /** delete auth rrset node */ static void auth_rrset_delete(struct auth_rrset* rrset) { if(!rrset) return; free(rrset->data); free(rrset); } /** delete auth data domain node */ static void auth_data_delete(struct auth_data* n) { struct auth_rrset* p, *np; if(!n) return; p = n->rrsets; while(p) { np = p->next; auth_rrset_delete(p); p = np; } free(n->name); free(n); } /** helper traverse to delete zones */ static void auth_data_del(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct auth_data* z = (struct auth_data*)n->key; auth_data_delete(z); } /** delete an auth zone structure (tree remove must be done elsewhere) */ static void auth_zone_delete(struct auth_zone* z, struct auth_zones* az) { if(!z) return; lock_rw_destroy(&z->lock); traverse_postorder(&z->data, auth_data_del, NULL); if(az && z->rpz) { /* keep RPZ linked list intact */ lock_rw_wrlock(&az->rpz_lock); if(z->rpz_az_prev) z->rpz_az_prev->rpz_az_next = z->rpz_az_next; else az->rpz_first = z->rpz_az_next; if(z->rpz_az_next) z->rpz_az_next->rpz_az_prev = z->rpz_az_prev; lock_rw_unlock(&az->rpz_lock); } if(z->rpz) rpz_delete(z->rpz); free(z->name); free(z->zonefile); free(z); } struct auth_zone* auth_zone_create(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass) { struct auth_zone* z = (struct auth_zone*)calloc(1, sizeof(*z)); if(!z) { return NULL; } z->node.key = z; z->dclass = dclass; z->namelen = nmlen; z->namelabs = dname_count_labels(nm); z->name = memdup(nm, nmlen); if(!z->name) { free(z); return NULL; } rbtree_init(&z->data, &auth_data_cmp); lock_rw_init(&z->lock); lock_protect(&z->lock, &z->name, sizeof(*z)-sizeof(rbnode_type)- sizeof(&z->rpz_az_next)-sizeof(&z->rpz_az_prev)); lock_rw_wrlock(&z->lock); /* z lock protects all, except rbtree itself and the rpz linked list * pointers, which are protected using az->lock */ if(!rbtree_insert(&az->ztree, &z->node)) { lock_rw_unlock(&z->lock); auth_zone_delete(z, NULL); log_warn("duplicate auth zone"); return NULL; } return z; } struct auth_zone* auth_zone_find(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass) { struct auth_zone key; key.node.key = &key; key.dclass = dclass; key.name = nm; key.namelen = nmlen; key.namelabs = dname_count_labels(nm); return (struct auth_zone*)rbtree_search(&az->ztree, &key); } struct auth_xfer* auth_xfer_find(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass) { struct auth_xfer key; key.node.key = &key; key.dclass = dclass; key.name = nm; key.namelen = nmlen; key.namelabs = dname_count_labels(nm); return (struct auth_xfer*)rbtree_search(&az->xtree, &key); } /** find an auth zone or sorted less-or-equal, return true if exact */ static int auth_zone_find_less_equal(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass, struct auth_zone** z) { struct auth_zone key; key.node.key = &key; key.dclass = dclass; key.name = nm; key.namelen = nmlen; key.namelabs = dname_count_labels(nm); return rbtree_find_less_equal(&az->ztree, &key, (rbnode_type**)z); } /** find the auth zone that is above the given name */ struct auth_zone* auth_zones_find_zone(struct auth_zones* az, uint8_t* name, size_t name_len, uint16_t dclass) { uint8_t* nm = name; size_t nmlen = name_len; struct auth_zone* z; if(auth_zone_find_less_equal(az, nm, nmlen, dclass, &z)) { /* exact match */ return z; } else { /* less-or-nothing */ if(!z) return NULL; /* nothing smaller, nothing above it */ /* we found smaller name; smaller may be above the name, * but not below it. */ nm = dname_get_shared_topdomain(z->name, name); dname_count_size_labels(nm, &nmlen); z = NULL; } /* search up */ while(!z) { z = auth_zone_find(az, nm, nmlen, dclass); if(z) return z; if(dname_is_root(nm)) break; dname_remove_label(&nm, &nmlen); } return NULL; } /** find or create zone with name str. caller must have lock on az. * returns a wrlocked zone */ static struct auth_zone* auth_zones_find_or_add_zone(struct auth_zones* az, char* name) { uint8_t nm[LDNS_MAX_DOMAINLEN+1]; size_t nmlen = sizeof(nm); struct auth_zone* z; if(sldns_str2wire_dname_buf(name, nm, &nmlen) != 0) { log_err("cannot parse auth zone name: %s", name); return 0; } z = auth_zone_find(az, nm, nmlen, LDNS_RR_CLASS_IN); if(!z) { /* not found, create the zone */ z = auth_zone_create(az, nm, nmlen, LDNS_RR_CLASS_IN); } else { lock_rw_wrlock(&z->lock); } return z; } /** find or create xfer zone with name str. caller must have lock on az. * returns a locked xfer */ static struct auth_xfer* auth_zones_find_or_add_xfer(struct auth_zones* az, struct auth_zone* z) { struct auth_xfer* x; x = auth_xfer_find(az, z->name, z->namelen, z->dclass); if(!x) { /* not found, create the zone */ x = auth_xfer_create(az, z); } else { lock_basic_lock(&x->lock); } return x; } int auth_zone_set_zonefile(struct auth_zone* z, char* zonefile) { if(z->zonefile) free(z->zonefile); if(zonefile == NULL) { z->zonefile = NULL; } else { z->zonefile = strdup(zonefile); if(!z->zonefile) { log_err("malloc failure"); return 0; } } return 1; } /** set auth zone fallback. caller must have lock on zone */ int auth_zone_set_fallback(struct auth_zone* z, char* fallbackstr) { if(strcmp(fallbackstr, "yes") != 0 && strcmp(fallbackstr, "no") != 0){ log_err("auth zone fallback, expected yes or no, got %s", fallbackstr); return 0; } z->fallback_enabled = (strcmp(fallbackstr, "yes")==0); return 1; } /** create domain with the given name */ static struct auth_data* az_domain_create(struct auth_zone* z, uint8_t* nm, size_t nmlen) { struct auth_data* n = (struct auth_data*)malloc(sizeof(*n)); if(!n) return NULL; memset(n, 0, sizeof(*n)); n->node.key = n; n->name = memdup(nm, nmlen); if(!n->name) { free(n); return NULL; } n->namelen = nmlen; n->namelabs = dname_count_labels(nm); if(!rbtree_insert(&z->data, &n->node)) { log_warn("duplicate auth domain name"); free(n->name); free(n); return NULL; } return n; } /** find domain with exactly the given name */ static struct auth_data* az_find_name(struct auth_zone* z, uint8_t* nm, size_t nmlen) { struct auth_zone key; key.node.key = &key; key.name = nm; key.namelen = nmlen; key.namelabs = dname_count_labels(nm); return (struct auth_data*)rbtree_search(&z->data, &key); } /** Find domain name (or closest match) */ static void az_find_domain(struct auth_zone* z, struct query_info* qinfo, int* node_exact, struct auth_data** node) { struct auth_zone key; key.node.key = &key; key.name = qinfo->qname; key.namelen = qinfo->qname_len; key.namelabs = dname_count_labels(key.name); *node_exact = rbtree_find_less_equal(&z->data, &key, (rbnode_type**)node); } /** find or create domain with name in zone */ static struct auth_data* az_domain_find_or_create(struct auth_zone* z, uint8_t* dname, size_t dname_len) { struct auth_data* n = az_find_name(z, dname, dname_len); if(!n) { n = az_domain_create(z, dname, dname_len); } return n; } /** find rrset of given type in the domain */ static struct auth_rrset* az_domain_rrset(struct auth_data* n, uint16_t t) { struct auth_rrset* rrset; if(!n) return NULL; rrset = n->rrsets; while(rrset) { if(rrset->type == t) return rrset; rrset = rrset->next; } return NULL; } /** remove rrset of this type from domain */ static void domain_remove_rrset(struct auth_data* node, uint16_t rr_type) { struct auth_rrset* rrset, *prev; if(!node) return; prev = NULL; rrset = node->rrsets; while(rrset) { if(rrset->type == rr_type) { /* found it, now delete it */ if(prev) prev->next = rrset->next; else node->rrsets = rrset->next; auth_rrset_delete(rrset); return; } prev = rrset; rrset = rrset->next; } } /** find an rrsig index in the rrset. returns true if found */ static int az_rrset_find_rrsig(struct packed_rrset_data* d, uint8_t* rdata, size_t len, size_t* index) { size_t i; for(i=d->count; icount + d->rrsig_count; i++) { if(d->rr_len[i] != len) continue; if(memcmp(d->rr_data[i], rdata, len) == 0) { *index = i; return 1; } } return 0; } /** see if rdata is duplicate */ static int rdata_duplicate(struct packed_rrset_data* d, uint8_t* rdata, size_t len) { size_t i; for(i=0; icount + d->rrsig_count; i++) { if(d->rr_len[i] != len) continue; if(memcmp(d->rr_data[i], rdata, len) == 0) return 1; } return 0; } /** get rrsig type covered from rdata. * @param rdata: rdata in wireformat, starting with 16bit rdlength. * @param rdatalen: length of rdata buffer. * @return type covered (or 0). */ static uint16_t rrsig_rdata_get_type_covered(uint8_t* rdata, size_t rdatalen) { if(rdatalen < 4) return 0; return sldns_read_uint16(rdata+2); } /** remove RR from existing RRset. Also sig, if it is a signature. * reallocates the packed rrset for a new one, false on alloc failure */ static int rrset_remove_rr(struct auth_rrset* rrset, size_t index) { struct packed_rrset_data* d, *old = rrset->data; size_t i; if(index >= old->count + old->rrsig_count) return 0; /* index out of bounds */ d = (struct packed_rrset_data*)calloc(1, packed_rrset_sizeof(old) - ( sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t) + old->rr_len[index])); if(!d) { log_err("malloc failure"); return 0; } d->ttl = old->ttl; d->count = old->count; d->rrsig_count = old->rrsig_count; if(index < d->count) d->count--; else d->rrsig_count--; d->trust = old->trust; d->security = old->security; /* set rr_len, needed for ptr_fixup */ d->rr_len = (size_t*)((uint8_t*)d + sizeof(struct packed_rrset_data)); if(index > 0) memmove(d->rr_len, old->rr_len, (index)*sizeof(size_t)); if(index+1 < old->count+old->rrsig_count) memmove(&d->rr_len[index], &old->rr_len[index+1], (old->count+old->rrsig_count - (index+1))*sizeof(size_t)); packed_rrset_ptr_fixup(d); /* move over ttls */ if(index > 0) memmove(d->rr_ttl, old->rr_ttl, (index)*sizeof(time_t)); if(index+1 < old->count+old->rrsig_count) memmove(&d->rr_ttl[index], &old->rr_ttl[index+1], (old->count+old->rrsig_count - (index+1))*sizeof(time_t)); /* move over rr_data */ for(i=0; icount+d->rrsig_count; i++) { size_t oldi; if(i < index) oldi = i; else oldi = i+1; memmove(d->rr_data[i], old->rr_data[oldi], d->rr_len[i]); } /* recalc ttl (lowest of remaining RR ttls) */ if(d->count + d->rrsig_count > 0) d->ttl = d->rr_ttl[0]; for(i=0; icount+d->rrsig_count; i++) { if(d->rr_ttl[i] < d->ttl) d->ttl = d->rr_ttl[i]; } free(rrset->data); rrset->data = d; return 1; } /** add RR to existing RRset. If insert_sig is true, add to rrsigs. * This reallocates the packed rrset for a new one */ static int rrset_add_rr(struct auth_rrset* rrset, uint32_t rr_ttl, uint8_t* rdata, size_t rdatalen, int insert_sig) { struct packed_rrset_data* d, *old = rrset->data; size_t total, old_total; d = (struct packed_rrset_data*)calloc(1, packed_rrset_sizeof(old) + sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t) + rdatalen); if(!d) { log_err("out of memory"); return 0; } /* copy base values */ memcpy(d, old, sizeof(struct packed_rrset_data)); if(!insert_sig) { d->count++; } else { d->rrsig_count++; } old_total = old->count + old->rrsig_count; total = d->count + d->rrsig_count; /* set rr_len, needed for ptr_fixup */ d->rr_len = (size_t*)((uint8_t*)d + sizeof(struct packed_rrset_data)); if(old->count != 0) memmove(d->rr_len, old->rr_len, old->count*sizeof(size_t)); if(old->rrsig_count != 0) memmove(d->rr_len+d->count, old->rr_len+old->count, old->rrsig_count*sizeof(size_t)); if(!insert_sig) d->rr_len[d->count-1] = rdatalen; else d->rr_len[total-1] = rdatalen; packed_rrset_ptr_fixup(d); if((time_t)rr_ttl < d->ttl) d->ttl = rr_ttl; /* copy old values into new array */ if(old->count != 0) { memmove(d->rr_ttl, old->rr_ttl, old->count*sizeof(time_t)); /* all the old rr pieces are allocated sequential, so we * can copy them in one go */ memmove(d->rr_data[0], old->rr_data[0], (old->rr_data[old->count-1] - old->rr_data[0]) + old->rr_len[old->count-1]); } if(old->rrsig_count != 0) { memmove(d->rr_ttl+d->count, old->rr_ttl+old->count, old->rrsig_count*sizeof(time_t)); memmove(d->rr_data[d->count], old->rr_data[old->count], (old->rr_data[old_total-1] - old->rr_data[old->count]) + old->rr_len[old_total-1]); } /* insert new value */ if(!insert_sig) { d->rr_ttl[d->count-1] = rr_ttl; memmove(d->rr_data[d->count-1], rdata, rdatalen); } else { d->rr_ttl[total-1] = rr_ttl; memmove(d->rr_data[total-1], rdata, rdatalen); } rrset->data = d; free(old); return 1; } /** Create new rrset for node with packed rrset with one RR element */ static struct auth_rrset* rrset_create(struct auth_data* node, uint16_t rr_type, uint32_t rr_ttl, uint8_t* rdata, size_t rdatalen) { struct auth_rrset* rrset = (struct auth_rrset*)calloc(1, sizeof(*rrset)); struct auth_rrset* p, *prev; struct packed_rrset_data* d; if(!rrset) { log_err("out of memory"); return NULL; } rrset->type = rr_type; /* the rrset data structure, with one RR */ d = (struct packed_rrset_data*)calloc(1, sizeof(struct packed_rrset_data) + sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t) + rdatalen); if(!d) { free(rrset); log_err("out of memory"); return NULL; } rrset->data = d; d->ttl = rr_ttl; d->trust = rrset_trust_prim_noglue; d->rr_len = (size_t*)((uint8_t*)d + sizeof(struct packed_rrset_data)); d->rr_data = (uint8_t**)&(d->rr_len[1]); d->rr_ttl = (time_t*)&(d->rr_data[1]); d->rr_data[0] = (uint8_t*)&(d->rr_ttl[1]); /* insert the RR */ d->rr_len[0] = rdatalen; d->rr_ttl[0] = rr_ttl; memmove(d->rr_data[0], rdata, rdatalen); d->count++; /* insert rrset into linked list for domain */ /* find sorted place to link the rrset into the list */ prev = NULL; p = node->rrsets; while(p && p->type<=rr_type) { prev = p; p = p->next; } /* so, prev is smaller, and p is larger than rr_type */ rrset->next = p; if(prev) prev->next = rrset; else node->rrsets = rrset; return rrset; } /** count number (and size) of rrsigs that cover a type */ static size_t rrsig_num_that_cover(struct auth_rrset* rrsig, uint16_t rr_type, size_t* sigsz) { struct packed_rrset_data* d = rrsig->data; size_t i, num = 0; *sigsz = 0; log_assert(d && rrsig->type == LDNS_RR_TYPE_RRSIG); for(i=0; icount+d->rrsig_count; i++) { if(rrsig_rdata_get_type_covered(d->rr_data[i], d->rr_len[i]) == rr_type) { num++; (*sigsz) += d->rr_len[i]; } } return num; } /** See if rrsig set has covered sigs for rrset and move them over */ static int rrset_moveover_rrsigs(struct auth_data* node, uint16_t rr_type, struct auth_rrset* rrset, struct auth_rrset* rrsig) { size_t sigs, sigsz, i, j, total; struct packed_rrset_data* sigold = rrsig->data; struct packed_rrset_data* old = rrset->data; struct packed_rrset_data* d, *sigd; log_assert(rrset->type == rr_type); log_assert(rrsig->type == LDNS_RR_TYPE_RRSIG); sigs = rrsig_num_that_cover(rrsig, rr_type, &sigsz); if(sigs == 0) { /* 0 rrsigs to move over, done */ return 1; } /* allocate rrset sigsz larger for extra sigs elements, and * allocate rrsig sigsz smaller for less sigs elements. */ d = (struct packed_rrset_data*)calloc(1, packed_rrset_sizeof(old) + sigs*(sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t)) + sigsz); if(!d) { log_err("out of memory"); return 0; } /* copy base values */ total = old->count + old->rrsig_count; memcpy(d, old, sizeof(struct packed_rrset_data)); d->rrsig_count += sigs; /* setup rr_len */ d->rr_len = (size_t*)((uint8_t*)d + sizeof(struct packed_rrset_data)); if(total != 0) memmove(d->rr_len, old->rr_len, total*sizeof(size_t)); j = d->count+d->rrsig_count-sigs; for(i=0; icount+sigold->rrsig_count; i++) { if(rrsig_rdata_get_type_covered(sigold->rr_data[i], sigold->rr_len[i]) == rr_type) { d->rr_len[j] = sigold->rr_len[i]; j++; } } packed_rrset_ptr_fixup(d); /* copy old values into new array */ if(total != 0) { memmove(d->rr_ttl, old->rr_ttl, total*sizeof(time_t)); /* all the old rr pieces are allocated sequential, so we * can copy them in one go */ memmove(d->rr_data[0], old->rr_data[0], (old->rr_data[total-1] - old->rr_data[0]) + old->rr_len[total-1]); } /* move over the rrsigs to the larger rrset*/ j = d->count+d->rrsig_count-sigs; for(i=0; icount+sigold->rrsig_count; i++) { if(rrsig_rdata_get_type_covered(sigold->rr_data[i], sigold->rr_len[i]) == rr_type) { /* move this one over to location j */ d->rr_ttl[j] = sigold->rr_ttl[i]; memmove(d->rr_data[j], sigold->rr_data[i], sigold->rr_len[i]); if(d->rr_ttl[j] < d->ttl) d->ttl = d->rr_ttl[j]; j++; } } /* put it in and deallocate the old rrset */ rrset->data = d; free(old); /* now make rrsig set smaller */ if(sigold->count+sigold->rrsig_count == sigs) { /* remove all sigs from rrsig, remove it entirely */ domain_remove_rrset(node, LDNS_RR_TYPE_RRSIG); return 1; } log_assert(packed_rrset_sizeof(sigold) > sigs*(sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t)) + sigsz); sigd = (struct packed_rrset_data*)calloc(1, packed_rrset_sizeof(sigold) - sigs*(sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t)) - sigsz); if(!sigd) { /* no need to free up d, it has already been placed in the * node->rrset structure */ log_err("out of memory"); return 0; } /* copy base values */ memcpy(sigd, sigold, sizeof(struct packed_rrset_data)); /* in sigd the RRSIGs are stored in the base of the RR, in count */ sigd->count -= sigs; /* setup rr_len */ sigd->rr_len = (size_t*)((uint8_t*)sigd + sizeof(struct packed_rrset_data)); j = 0; for(i=0; icount+sigold->rrsig_count; i++) { if(rrsig_rdata_get_type_covered(sigold->rr_data[i], sigold->rr_len[i]) != rr_type) { sigd->rr_len[j] = sigold->rr_len[i]; j++; } } packed_rrset_ptr_fixup(sigd); /* copy old values into new rrsig array */ j = 0; for(i=0; icount+sigold->rrsig_count; i++) { if(rrsig_rdata_get_type_covered(sigold->rr_data[i], sigold->rr_len[i]) != rr_type) { /* move this one over to location j */ sigd->rr_ttl[j] = sigold->rr_ttl[i]; memmove(sigd->rr_data[j], sigold->rr_data[i], sigold->rr_len[i]); if(j==0) sigd->ttl = sigd->rr_ttl[j]; else { if(sigd->rr_ttl[j] < sigd->ttl) sigd->ttl = sigd->rr_ttl[j]; } j++; } } /* put it in and deallocate the old rrset */ rrsig->data = sigd; free(sigold); return 1; } /** copy the rrsigs from the rrset to the rrsig rrset, because the rrset * is going to be deleted. reallocates the RRSIG rrset data. */ static int rrsigs_copy_from_rrset_to_rrsigset(struct auth_rrset* rrset, struct auth_rrset* rrsigset) { size_t i; if(rrset->data->rrsig_count == 0) return 1; /* move them over one by one, because there might be duplicates, * duplicates are ignored */ for(i=rrset->data->count; idata->count+rrset->data->rrsig_count; i++) { uint8_t* rdata = rrset->data->rr_data[i]; size_t rdatalen = rrset->data->rr_len[i]; time_t rr_ttl = rrset->data->rr_ttl[i]; if(rdata_duplicate(rrsigset->data, rdata, rdatalen)) { continue; } if(!rrset_add_rr(rrsigset, rr_ttl, rdata, rdatalen, 0)) return 0; } return 1; } /** Add rr to node, ignores duplicate RRs, * rdata points to buffer with rdatalen octets, starts with 2bytelength. */ static int az_domain_add_rr(struct auth_data* node, uint16_t rr_type, uint32_t rr_ttl, uint8_t* rdata, size_t rdatalen, int* duplicate) { struct auth_rrset* rrset; /* packed rrsets have their rrsigs along with them, sort them out */ if(rr_type == LDNS_RR_TYPE_RRSIG) { uint16_t ctype = rrsig_rdata_get_type_covered(rdata, rdatalen); if((rrset=az_domain_rrset(node, ctype))!= NULL) { /* a node of the correct type exists, add the RRSIG * to the rrset of the covered data type */ if(rdata_duplicate(rrset->data, rdata, rdatalen)) { if(duplicate) *duplicate = 1; return 1; } if(!rrset_add_rr(rrset, rr_ttl, rdata, rdatalen, 1)) return 0; } else if((rrset=az_domain_rrset(node, rr_type))!= NULL) { /* add RRSIG to rrset of type RRSIG */ if(rdata_duplicate(rrset->data, rdata, rdatalen)) { if(duplicate) *duplicate = 1; return 1; } if(!rrset_add_rr(rrset, rr_ttl, rdata, rdatalen, 0)) return 0; } else { /* create rrset of type RRSIG */ if(!rrset_create(node, rr_type, rr_ttl, rdata, rdatalen)) return 0; } } else { /* normal RR type */ if((rrset=az_domain_rrset(node, rr_type))!= NULL) { /* add data to existing node with data type */ if(rdata_duplicate(rrset->data, rdata, rdatalen)) { if(duplicate) *duplicate = 1; return 1; } if(!rrset_add_rr(rrset, rr_ttl, rdata, rdatalen, 0)) return 0; } else { struct auth_rrset* rrsig; /* create new node with data type */ if(!(rrset=rrset_create(node, rr_type, rr_ttl, rdata, rdatalen))) return 0; /* see if node of type RRSIG has signatures that * cover the data type, and move them over */ /* and then make the RRSIG type smaller */ if((rrsig=az_domain_rrset(node, LDNS_RR_TYPE_RRSIG)) != NULL) { if(!rrset_moveover_rrsigs(node, rr_type, rrset, rrsig)) return 0; } } } return 1; } /** insert RR into zone, ignore duplicates */ static int az_insert_rr(struct auth_zone* z, uint8_t* rr, size_t rr_len, size_t dname_len, int* duplicate) { struct auth_data* node; uint8_t* dname = rr; uint16_t rr_type = sldns_wirerr_get_type(rr, rr_len, dname_len); uint16_t rr_class = sldns_wirerr_get_class(rr, rr_len, dname_len); uint32_t rr_ttl = sldns_wirerr_get_ttl(rr, rr_len, dname_len); size_t rdatalen = ((size_t)sldns_wirerr_get_rdatalen(rr, rr_len, dname_len))+2; /* rdata points to rdata prefixed with uint16 rdatalength */ uint8_t* rdata = sldns_wirerr_get_rdatawl(rr, rr_len, dname_len); if(rr_class != z->dclass) { log_err("wrong class for RR"); return 0; } if(!(node=az_domain_find_or_create(z, dname, dname_len))) { log_err("cannot create domain"); return 0; } if(!az_domain_add_rr(node, rr_type, rr_ttl, rdata, rdatalen, duplicate)) { log_err("cannot add RR to domain"); return 0; } if(z->rpz) { if(!(rpz_insert_rr(z->rpz, z->name, z->namelen, dname, dname_len, rr_type, rr_class, rr_ttl, rdata, rdatalen, rr, rr_len))) return 0; } return 1; } /** Remove rr from node, ignores nonexisting RRs, * rdata points to buffer with rdatalen octets, starts with 2bytelength. */ static int az_domain_remove_rr(struct auth_data* node, uint16_t rr_type, uint8_t* rdata, size_t rdatalen, int* nonexist) { struct auth_rrset* rrset; size_t index = 0; /* find the plain RR of the given type */ if((rrset=az_domain_rrset(node, rr_type))!= NULL) { if(packed_rrset_find_rr(rrset->data, rdata, rdatalen, &index)) { if(rrset->data->count == 1 && rrset->data->rrsig_count == 0) { /* last RR, delete the rrset */ domain_remove_rrset(node, rr_type); } else if(rrset->data->count == 1 && rrset->data->rrsig_count != 0) { /* move RRSIGs to the RRSIG rrset, or * this one becomes that RRset */ struct auth_rrset* rrsigset = az_domain_rrset( node, LDNS_RR_TYPE_RRSIG); if(rrsigset) { /* move left over rrsigs to the * existing rrset of type RRSIG */ rrsigs_copy_from_rrset_to_rrsigset( rrset, rrsigset); /* and then delete the rrset */ domain_remove_rrset(node, rr_type); } else { /* no rrset of type RRSIG, this * set is now of that type, * just remove the rr */ if(!rrset_remove_rr(rrset, index)) return 0; rrset->type = LDNS_RR_TYPE_RRSIG; rrset->data->count = rrset->data->rrsig_count; rrset->data->rrsig_count = 0; } } else { /* remove the RR from the rrset */ if(!rrset_remove_rr(rrset, index)) return 0; } return 1; } /* rr not found in rrset */ } /* is it a type RRSIG, look under the covered type */ if(rr_type == LDNS_RR_TYPE_RRSIG) { uint16_t ctype = rrsig_rdata_get_type_covered(rdata, rdatalen); if((rrset=az_domain_rrset(node, ctype))!= NULL) { if(az_rrset_find_rrsig(rrset->data, rdata, rdatalen, &index)) { /* rrsig should have d->count > 0, be * over some rr of that type */ /* remove the rrsig from the rrsigs list of the * rrset */ if(!rrset_remove_rr(rrset, index)) return 0; return 1; } } /* also RRSIG not found */ } /* nothing found to delete */ if(nonexist) *nonexist = 1; return 1; } /** remove RR from zone, ignore if it does not exist, false on alloc failure*/ static int az_remove_rr(struct auth_zone* z, uint8_t* rr, size_t rr_len, size_t dname_len, int* nonexist) { struct auth_data* node; uint8_t* dname = rr; uint16_t rr_type = sldns_wirerr_get_type(rr, rr_len, dname_len); uint16_t rr_class = sldns_wirerr_get_class(rr, rr_len, dname_len); size_t rdatalen = ((size_t)sldns_wirerr_get_rdatalen(rr, rr_len, dname_len))+2; /* rdata points to rdata prefixed with uint16 rdatalength */ uint8_t* rdata = sldns_wirerr_get_rdatawl(rr, rr_len, dname_len); if(rr_class != z->dclass) { log_err("wrong class for RR"); /* really also a nonexisting entry, because no records * of that class in the zone, but return an error because * getting records of the wrong class is a failure of the * zone transfer */ return 0; } node = az_find_name(z, dname, dname_len); if(!node) { /* node with that name does not exist */ /* nonexisting entry, because no such name */ *nonexist = 1; return 1; } if(!az_domain_remove_rr(node, rr_type, rdata, rdatalen, nonexist)) { /* alloc failure or so */ return 0; } /* remove the node, if necessary */ /* an rrsets==NULL entry is not kept around for empty nonterminals, * and also parent nodes are not kept around, so we just delete it */ if(node->rrsets == NULL) { (void)rbtree_delete(&z->data, node); auth_data_delete(node); } if(z->rpz) { rpz_remove_rr(z->rpz, z->namelen, dname, dname_len, rr_type, rr_class, rdata, rdatalen); } return 1; } /** decompress an RR into the buffer where it'll be an uncompressed RR * with uncompressed dname and uncompressed rdata (dnames) */ static int decompress_rr_into_buffer(struct sldns_buffer* buf, uint8_t* pkt, size_t pktlen, uint8_t* dname, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, uint8_t* rr_data, uint16_t rr_rdlen) { sldns_buffer pktbuf; size_t dname_len = 0; size_t rdlenpos; size_t rdlen; uint8_t* rd; const sldns_rr_descriptor* desc; sldns_buffer_init_frm_data(&pktbuf, pkt, pktlen); sldns_buffer_clear(buf); /* decompress dname */ sldns_buffer_set_position(&pktbuf, (size_t)(dname - sldns_buffer_current(&pktbuf))); dname_len = pkt_dname_len(&pktbuf); if(dname_len == 0) return 0; /* parse fail on dname */ if(!sldns_buffer_available(buf, dname_len)) return 0; dname_pkt_copy(&pktbuf, sldns_buffer_current(buf), dname); sldns_buffer_skip(buf, (ssize_t)dname_len); /* type, class, ttl and rdatalength fields */ if(!sldns_buffer_available(buf, 10)) return 0; sldns_buffer_write_u16(buf, rr_type); sldns_buffer_write_u16(buf, rr_class); sldns_buffer_write_u32(buf, rr_ttl); rdlenpos = sldns_buffer_position(buf); sldns_buffer_write_u16(buf, 0); /* rd length position */ /* decompress rdata */ desc = sldns_rr_descript(rr_type); rd = rr_data; rdlen = rr_rdlen; if(rdlen > 0 && desc && desc->_dname_count > 0) { int count = (int)desc->_dname_count; int rdf = 0; size_t len; /* how much rdata to plain copy */ size_t uncompressed_len, compressed_len; size_t oldpos; /* decompress dnames. */ while(rdlen > 0 && count) { switch(desc->_wireformat[rdf]) { case LDNS_RDF_TYPE_DNAME: sldns_buffer_set_position(&pktbuf, (size_t)(rd - sldns_buffer_begin(&pktbuf))); oldpos = sldns_buffer_position(&pktbuf); /* moves pktbuf to right after the * compressed dname, and returns uncompressed * dname length */ uncompressed_len = pkt_dname_len(&pktbuf); if(!uncompressed_len) return 0; /* parse error in dname */ if(!sldns_buffer_available(buf, uncompressed_len)) /* dname too long for buffer */ return 0; dname_pkt_copy(&pktbuf, sldns_buffer_current(buf), rd); sldns_buffer_skip(buf, (ssize_t)uncompressed_len); compressed_len = sldns_buffer_position( &pktbuf) - oldpos; rd += compressed_len; rdlen -= compressed_len; count--; len = 0; break; case LDNS_RDF_TYPE_STR: len = rd[0] + 1; break; default: len = get_rdf_size(desc->_wireformat[rdf]); break; } if(len) { if(!sldns_buffer_available(buf, len)) return 0; /* too long for buffer */ sldns_buffer_write(buf, rd, len); rd += len; rdlen -= len; } rdf++; } } /* copy remaining data */ if(rdlen > 0) { if(!sldns_buffer_available(buf, rdlen)) return 0; sldns_buffer_write(buf, rd, rdlen); } /* fixup rdlength */ sldns_buffer_write_u16_at(buf, rdlenpos, sldns_buffer_position(buf)-rdlenpos-2); sldns_buffer_flip(buf); return 1; } /** insert RR into zone, from packet, decompress RR, * if duplicate is nonNULL set the flag but otherwise ignore duplicates */ static int az_insert_rr_decompress(struct auth_zone* z, uint8_t* pkt, size_t pktlen, struct sldns_buffer* scratch_buffer, uint8_t* dname, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, uint8_t* rr_data, uint16_t rr_rdlen, int* duplicate) { uint8_t* rr; size_t rr_len; size_t dname_len; if(!decompress_rr_into_buffer(scratch_buffer, pkt, pktlen, dname, rr_type, rr_class, rr_ttl, rr_data, rr_rdlen)) { log_err("could not decompress RR"); return 0; } rr = sldns_buffer_begin(scratch_buffer); rr_len = sldns_buffer_limit(scratch_buffer); dname_len = dname_valid(rr, rr_len); return az_insert_rr(z, rr, rr_len, dname_len, duplicate); } /** remove RR from zone, from packet, decompress RR, * if nonexist is nonNULL set the flag but otherwise ignore nonexisting entries*/ static int az_remove_rr_decompress(struct auth_zone* z, uint8_t* pkt, size_t pktlen, struct sldns_buffer* scratch_buffer, uint8_t* dname, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, uint8_t* rr_data, uint16_t rr_rdlen, int* nonexist) { uint8_t* rr; size_t rr_len; size_t dname_len; if(!decompress_rr_into_buffer(scratch_buffer, pkt, pktlen, dname, rr_type, rr_class, rr_ttl, rr_data, rr_rdlen)) { log_err("could not decompress RR"); return 0; } rr = sldns_buffer_begin(scratch_buffer); rr_len = sldns_buffer_limit(scratch_buffer); dname_len = dname_valid(rr, rr_len); return az_remove_rr(z, rr, rr_len, dname_len, nonexist); } /** * Parse zonefile * @param z: zone to read in. * @param in: file to read from (just opened). * @param rr: buffer to use for RRs, 64k. * passed so that recursive includes can use the same buffer and do * not grow the stack too much. * @param rrbuflen: sizeof rr buffer. * @param state: parse state with $ORIGIN, $TTL and 'prev-dname' and so on, * that is kept between includes. * The lineno is set at 1 and then increased by the function. * @param fname: file name. * @param depth: recursion depth for includes * @param cfg: config for chroot. * returns false on failure, has printed an error message */ static int az_parse_file(struct auth_zone* z, FILE* in, uint8_t* rr, size_t rrbuflen, struct sldns_file_parse_state* state, char* fname, int depth, struct config_file* cfg) { size_t rr_len, dname_len; int status; state->lineno = 1; while(!feof(in)) { rr_len = rrbuflen; dname_len = 0; status = sldns_fp2wire_rr_buf(in, rr, &rr_len, &dname_len, state); if(status == LDNS_WIREPARSE_ERR_INCLUDE && rr_len == 0) { /* we have $INCLUDE or $something */ if(strncmp((char*)rr, "$INCLUDE ", 9) == 0 || strncmp((char*)rr, "$INCLUDE\t", 9) == 0) { FILE* inc; int lineno_orig = state->lineno; char* incfile = (char*)rr + 8; if(depth > MAX_INCLUDE_DEPTH) { log_err("%s:%d max include depth" "exceeded", fname, state->lineno); return 0; } /* skip spaces */ while(*incfile == ' ' || *incfile == '\t') incfile++; /* adjust for chroot on include file */ if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(incfile, cfg->chrootdir, strlen(cfg->chrootdir)) == 0) incfile += strlen(cfg->chrootdir); incfile = strdup(incfile); if(!incfile) { log_err("malloc failure"); return 0; } verbose(VERB_ALGO, "opening $INCLUDE %s", incfile); inc = fopen(incfile, "r"); if(!inc) { log_err("%s:%d cannot open include " "file %s: %s", fname, lineno_orig, incfile, strerror(errno)); free(incfile); return 0; } /* recurse read that file now */ if(!az_parse_file(z, inc, rr, rrbuflen, state, incfile, depth+1, cfg)) { log_err("%s:%d cannot parse include " "file %s", fname, lineno_orig, incfile); fclose(inc); free(incfile); return 0; } fclose(inc); verbose(VERB_ALGO, "done with $INCLUDE %s", incfile); free(incfile); state->lineno = lineno_orig; } continue; } if(status != 0) { log_err("parse error %s %d:%d: %s", fname, state->lineno, LDNS_WIREPARSE_OFFSET(status), sldns_get_errorstr_parse(status)); return 0; } if(rr_len == 0) { /* EMPTY line, TTL or ORIGIN */ continue; } /* insert wirerr in rrbuf */ if(!az_insert_rr(z, rr, rr_len, dname_len, NULL)) { char buf[17]; sldns_wire2str_type_buf(sldns_wirerr_get_type(rr, rr_len, dname_len), buf, sizeof(buf)); log_err("%s:%d cannot insert RR of type %s", fname, state->lineno, buf); return 0; } } return 1; } int auth_zone_read_zonefile(struct auth_zone* z, struct config_file* cfg) { uint8_t rr[LDNS_RR_BUF_SIZE]; struct sldns_file_parse_state state; char* zfilename; FILE* in; if(!z || !z->zonefile || z->zonefile[0]==0) return 1; /* no file, or "", nothing to read */ zfilename = z->zonefile; if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(zfilename, cfg->chrootdir, strlen(cfg->chrootdir)) == 0) zfilename += strlen(cfg->chrootdir); if(verbosity >= VERB_ALGO) { char nm[255+1]; dname_str(z->name, nm); verbose(VERB_ALGO, "read zonefile %s for %s", zfilename, nm); } in = fopen(zfilename, "r"); if(!in) { char* n = sldns_wire2str_dname(z->name, z->namelen); if(z->zone_is_slave && errno == ENOENT) { /* we fetch the zone contents later, no file yet */ verbose(VERB_ALGO, "no zonefile %s for %s", zfilename, n?n:"error"); free(n); return 1; } log_err("cannot open zonefile %s for %s: %s", zfilename, n?n:"error", strerror(errno)); free(n); return 0; } /* clear the data tree */ traverse_postorder(&z->data, auth_data_del, NULL); rbtree_init(&z->data, &auth_data_cmp); /* clear the RPZ policies */ if(z->rpz) rpz_clear(z->rpz); memset(&state, 0, sizeof(state)); /* default TTL to 3600 */ state.default_ttl = 3600; /* set $ORIGIN to the zone name */ if(z->namelen <= sizeof(state.origin)) { memcpy(state.origin, z->name, z->namelen); state.origin_len = z->namelen; } /* parse the (toplevel) file */ if(!az_parse_file(z, in, rr, sizeof(rr), &state, zfilename, 0, cfg)) { char* n = sldns_wire2str_dname(z->name, z->namelen); log_err("error parsing zonefile %s for %s", zfilename, n?n:"error"); free(n); fclose(in); return 0; } fclose(in); if(z->rpz) rpz_finish_config(z->rpz); return 1; } /** write buffer to file and check return codes */ static int write_out(FILE* out, const char* str, size_t len) { size_t r; if(len == 0) return 1; r = fwrite(str, 1, len, out); if(r == 0) { log_err("write failed: %s", strerror(errno)); return 0; } else if(r < len) { log_err("write failed: too short (disk full?)"); return 0; } return 1; } /** convert auth rr to string */ static int auth_rr_to_string(uint8_t* nm, size_t nmlen, uint16_t tp, uint16_t cl, struct packed_rrset_data* data, size_t i, char* s, size_t buflen) { int w = 0; size_t slen = buflen, datlen; uint8_t* dat; if(i >= data->count) tp = LDNS_RR_TYPE_RRSIG; dat = nm; datlen = nmlen; w += sldns_wire2str_dname_scan(&dat, &datlen, &s, &slen, NULL, 0, NULL); w += sldns_str_print(&s, &slen, "\t"); w += sldns_str_print(&s, &slen, "%lu\t", (unsigned long)data->rr_ttl[i]); w += sldns_wire2str_class_print(&s, &slen, cl); w += sldns_str_print(&s, &slen, "\t"); w += sldns_wire2str_type_print(&s, &slen, tp); w += sldns_str_print(&s, &slen, "\t"); datlen = data->rr_len[i]-2; dat = data->rr_data[i]+2; w += sldns_wire2str_rdata_scan(&dat, &datlen, &s, &slen, tp, NULL, 0, NULL); if(tp == LDNS_RR_TYPE_DNSKEY) { w += sldns_str_print(&s, &slen, " ;{id = %u}", sldns_calc_keytag_raw(data->rr_data[i]+2, data->rr_len[i]-2)); } w += sldns_str_print(&s, &slen, "\n"); if(w >= (int)buflen) { log_nametypeclass(NO_VERBOSE, "RR too long to print", nm, tp, cl); return 0; } return 1; } /** write rrset to file */ static int auth_zone_write_rrset(struct auth_zone* z, struct auth_data* node, struct auth_rrset* r, FILE* out) { size_t i, count = r->data->count + r->data->rrsig_count; char buf[LDNS_RR_BUF_SIZE]; for(i=0; iname, node->namelen, r->type, z->dclass, r->data, i, buf, sizeof(buf))) { verbose(VERB_ALGO, "failed to rr2str rr %d", (int)i); continue; } if(!write_out(out, buf, strlen(buf))) return 0; } return 1; } /** write domain to file */ static int auth_zone_write_domain(struct auth_zone* z, struct auth_data* n, FILE* out) { struct auth_rrset* r; /* if this is zone apex, write SOA first */ if(z->namelen == n->namelen) { struct auth_rrset* soa = az_domain_rrset(n, LDNS_RR_TYPE_SOA); if(soa) { if(!auth_zone_write_rrset(z, n, soa, out)) return 0; } } /* write all the RRsets for this domain */ for(r = n->rrsets; r; r = r->next) { if(z->namelen == n->namelen && r->type == LDNS_RR_TYPE_SOA) continue; /* skip SOA here */ if(!auth_zone_write_rrset(z, n, r, out)) return 0; } return 1; } int auth_zone_write_file(struct auth_zone* z, const char* fname) { FILE* out; struct auth_data* n; out = fopen(fname, "w"); if(!out) { log_err("could not open %s: %s", fname, strerror(errno)); return 0; } RBTREE_FOR(n, struct auth_data*, &z->data) { if(!auth_zone_write_domain(z, n, out)) { log_err("could not write domain to %s", fname); fclose(out); return 0; } } fclose(out); return 1; } /** offline verify for zonemd, while reading a zone file to immediately * spot bad hashes in zonefile as they are read. * Creates temp buffers, but uses anchors and validation environment * from the module_env. */ static void zonemd_offline_verify(struct auth_zone* z, struct module_env* env_for_val, struct module_stack* mods) { struct module_env env; time_t now = 0; if(!z->zonemd_check) return; env = *env_for_val; env.scratch_buffer = sldns_buffer_new(env.cfg->msg_buffer_size); if(!env.scratch_buffer) { log_err("out of memory"); goto clean_exit; } env.scratch = regional_create(); if(!env.now) { env.now = &now; now = time(NULL); } if(!env.scratch) { log_err("out of memory"); goto clean_exit; } auth_zone_verify_zonemd(z, &env, mods, NULL, 1, 0); clean_exit: /* clean up and exit */ sldns_buffer_free(env.scratch_buffer); regional_destroy(env.scratch); } /** read all auth zones from file (if they have) */ static int auth_zones_read_zones(struct auth_zones* az, struct config_file* cfg, struct module_env* env, struct module_stack* mods) { struct auth_zone* z; lock_rw_wrlock(&az->lock); RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_wrlock(&z->lock); if(!auth_zone_read_zonefile(z, cfg)) { lock_rw_unlock(&z->lock); lock_rw_unlock(&az->lock); return 0; } if(z->zonefile && z->zonefile[0]!=0 && env) zonemd_offline_verify(z, env, mods); lock_rw_unlock(&z->lock); } lock_rw_unlock(&az->lock); return 1; } /** fetch the content of a ZONEMD RR from the rdata */ static int zonemd_fetch_parameters(struct auth_rrset* zonemd_rrset, size_t i, uint32_t* serial, int* scheme, int* hashalgo, uint8_t** hash, size_t* hashlen) { size_t rr_len; uint8_t* rdata; if(i >= zonemd_rrset->data->count) return 0; rr_len = zonemd_rrset->data->rr_len[i]; if(rr_len < 2+4+1+1) return 0; /* too short, for rdlen+serial+scheme+algo */ rdata = zonemd_rrset->data->rr_data[i]; *serial = sldns_read_uint32(rdata+2); *scheme = rdata[6]; *hashalgo = rdata[7]; *hashlen = rr_len - 8; if(*hashlen == 0) *hash = NULL; else *hash = rdata+8; return 1; } /** * See if the ZONEMD scheme, hash occurs more than once. * @param zonemd_rrset: the zonemd rrset to check with the RRs in it. * @param index: index of the original, this is allowed to have that * scheme and hashalgo, but other RRs should not have it. * @param scheme: the scheme to check for. * @param hashalgo: the hash algorithm to check for. * @return true if it occurs more than once. */ static int zonemd_is_duplicate_scheme_hash(struct auth_rrset* zonemd_rrset, size_t index, int scheme, int hashalgo) { size_t j; for(j=0; jdata->count; j++) { uint32_t serial2 = 0; int scheme2 = 0, hashalgo2 = 0; uint8_t* hash2 = NULL; size_t hashlen2 = 0; if(index == j) { /* this is the original */ continue; } if(!zonemd_fetch_parameters(zonemd_rrset, j, &serial2, &scheme2, &hashalgo2, &hash2, &hashlen2)) { /* malformed, skip it */ continue; } if(scheme == scheme2 && hashalgo == hashalgo2) { /* duplicate scheme, hash */ verbose(VERB_ALGO, "zonemd duplicate for scheme %d " "and hash %d", scheme, hashalgo); return 1; } } return 0; } /** * Check ZONEMDs if present for the auth zone. Depending on config * it can warn or fail on that. Checks the hash of the ZONEMD. * @param z: auth zone to check for. * caller must hold lock on zone. * @param env: module env for temp buffers. * @param reason: returned on failure. * @return false on failure, true if hash checks out. */ static int auth_zone_zonemd_check_hash(struct auth_zone* z, struct module_env* env, char** reason) { /* loop over ZONEMDs and see which one is valid. if not print * failure (depending on config) */ struct auth_data* apex; struct auth_rrset* zonemd_rrset; size_t i; struct regional* region = NULL; struct sldns_buffer* buf = NULL; uint32_t soa_serial = 0; char* unsupported_reason = NULL; int only_unsupported = 1; region = env->scratch; regional_free_all(region); buf = env->scratch_buffer; if(!auth_zone_get_serial(z, &soa_serial)) { *reason = "zone has no SOA serial"; return 0; } apex = az_find_name(z, z->name, z->namelen); if(!apex) { *reason = "zone has no apex"; return 0; } zonemd_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_ZONEMD); if(!zonemd_rrset || zonemd_rrset->data->count==0) { *reason = "zone has no ZONEMD"; return 0; /* no RRset or no RRs in rrset */ } /* we have a ZONEMD, check if it is correct */ for(i=0; idata->count; i++) { uint32_t serial = 0; int scheme = 0, hashalgo = 0; uint8_t* hash = NULL; size_t hashlen = 0; if(!zonemd_fetch_parameters(zonemd_rrset, i, &serial, &scheme, &hashalgo, &hash, &hashlen)) { /* malformed RR */ *reason = "ZONEMD rdata malformed"; only_unsupported = 0; continue; } /* check for duplicates */ if(zonemd_is_duplicate_scheme_hash(zonemd_rrset, i, scheme, hashalgo)) { /* duplicate hash of the same scheme,hash * is not allowed. */ *reason = "ZONEMD RRSet contains more than one RR " "with the same scheme and hash algorithm"; only_unsupported = 0; continue; } regional_free_all(region); if(serial != soa_serial) { *reason = "ZONEMD serial is wrong"; only_unsupported = 0; continue; } *reason = NULL; if(auth_zone_generate_zonemd_check(z, scheme, hashalgo, hash, hashlen, region, buf, reason)) { /* success */ if(*reason) { if(!unsupported_reason) unsupported_reason = *reason; /* continue to check for valid ZONEMD */ if(verbosity >= VERB_ALGO) { char zstr[255+1]; dname_str(z->name, zstr); verbose(VERB_ALGO, "auth-zone %s ZONEMD %d %d is unsupported: %s", zstr, (int)scheme, (int)hashalgo, *reason); } *reason = NULL; continue; } if(verbosity >= VERB_ALGO) { char zstr[255+1]; dname_str(z->name, zstr); if(!*reason) verbose(VERB_ALGO, "auth-zone %s ZONEMD hash is correct", zstr); } return 1; } only_unsupported = 0; /* try next one */ } /* have we seen no failures but only unsupported algo, * and one unsupported algorithm, or more. */ if(only_unsupported && unsupported_reason) { /* only unsupported algorithms, with valid serial, not * malformed. Did not see supported algorithms, failed or * successful ones. */ *reason = unsupported_reason; return 1; } /* fail, we may have reason */ if(!*reason) *reason = "no ZONEMD records found"; if(verbosity >= VERB_ALGO) { char zstr[255+1]; dname_str(z->name, zstr); verbose(VERB_ALGO, "auth-zone %s ZONEMD failed: %s", zstr, *reason); } return 0; } /** find the apex SOA RRset, if it exists */ struct auth_rrset* auth_zone_get_soa_rrset(struct auth_zone* z) { struct auth_data* apex; struct auth_rrset* soa; apex = az_find_name(z, z->name, z->namelen); if(!apex) return NULL; soa = az_domain_rrset(apex, LDNS_RR_TYPE_SOA); return soa; } /** find serial number of zone or false if none */ int auth_zone_get_serial(struct auth_zone* z, uint32_t* serial) { struct auth_data* apex; struct auth_rrset* soa; struct packed_rrset_data* d; apex = az_find_name(z, z->name, z->namelen); if(!apex) return 0; soa = az_domain_rrset(apex, LDNS_RR_TYPE_SOA); if(!soa || soa->data->count==0) return 0; /* no RRset or no RRs in rrset */ if(soa->data->rr_len[0] < 2+4*5) return 0; /* SOA too short */ d = soa->data; *serial = sldns_read_uint32(d->rr_data[0]+(d->rr_len[0]-20)); return 1; } /** Find auth_zone SOA and populate the values in xfr(soa values). */ int xfr_find_soa(struct auth_zone* z, struct auth_xfer* xfr) { struct auth_data* apex; struct auth_rrset* soa; struct packed_rrset_data* d; apex = az_find_name(z, z->name, z->namelen); if(!apex) return 0; soa = az_domain_rrset(apex, LDNS_RR_TYPE_SOA); if(!soa || soa->data->count==0) return 0; /* no RRset or no RRs in rrset */ if(soa->data->rr_len[0] < 2+4*5) return 0; /* SOA too short */ /* SOA record ends with serial, refresh, retry, expiry, minimum, * as 4 byte fields */ d = soa->data; xfr->have_zone = 1; xfr->serial = sldns_read_uint32(d->rr_data[0]+(d->rr_len[0]-20)); xfr->refresh = sldns_read_uint32(d->rr_data[0]+(d->rr_len[0]-16)); xfr->retry = sldns_read_uint32(d->rr_data[0]+(d->rr_len[0]-12)); xfr->expiry = sldns_read_uint32(d->rr_data[0]+(d->rr_len[0]-8)); /* soa minimum at d->rr_len[0]-4 */ return 1; } /** * Setup auth_xfer zone * This populates the have_zone, soa values, and so on times. * Doesn't do network traffic yet, can set option flags. * @param z: locked by caller, and modified for setup * @param x: locked by caller, and modified. * @return false on failure. */ static int auth_xfer_setup(struct auth_zone* z, struct auth_xfer* x) { /* for a zone without zone transfers, x==NULL, so skip them, * i.e. the zone config is fixed with no masters or urls */ if(!z || !x) return 1; if(!xfr_find_soa(z, x)) { return 1; } /* nothing for probe, nextprobe and transfer tasks */ return 1; } /** * Setup all zones * @param az: auth zones structure * @return false on failure. */ static int auth_zones_setup_zones(struct auth_zones* az) { struct auth_zone* z; struct auth_xfer* x; lock_rw_wrlock(&az->lock); RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_wrlock(&z->lock); x = auth_xfer_find(az, z->name, z->namelen, z->dclass); if(x) { lock_basic_lock(&x->lock); } if(!auth_xfer_setup(z, x)) { if(x) { lock_basic_unlock(&x->lock); } lock_rw_unlock(&z->lock); lock_rw_unlock(&az->lock); return 0; } if(x) { lock_basic_unlock(&x->lock); } lock_rw_unlock(&z->lock); } lock_rw_unlock(&az->lock); return 1; } /** set config items and create zones */ static int auth_zones_cfg(struct auth_zones* az, struct config_auth* c) { struct auth_zone* z; struct auth_xfer* x = NULL; /* create zone */ if(c->isrpz) { /* if the rpz lock is needed, grab it before the other * locks to avoid a lock dependency cycle */ lock_rw_wrlock(&az->rpz_lock); } lock_rw_wrlock(&az->lock); if(!(z=auth_zones_find_or_add_zone(az, c->name))) { lock_rw_unlock(&az->lock); if(c->isrpz) { lock_rw_unlock(&az->rpz_lock); } return 0; } if(c->masters || c->urls) { if(!(x=auth_zones_find_or_add_xfer(az, z))) { lock_rw_unlock(&az->lock); lock_rw_unlock(&z->lock); if(c->isrpz) { lock_rw_unlock(&az->rpz_lock); } return 0; } } if(c->for_downstream) az->have_downstream = 1; lock_rw_unlock(&az->lock); /* set options */ z->zone_deleted = 0; if(!auth_zone_set_zonefile(z, c->zonefile)) { if(x) { lock_basic_unlock(&x->lock); } lock_rw_unlock(&z->lock); if(c->isrpz) { lock_rw_unlock(&az->rpz_lock); } return 0; } z->for_downstream = c->for_downstream; z->for_upstream = c->for_upstream; z->fallback_enabled = c->fallback_enabled; z->zonemd_check = c->zonemd_check; z->zonemd_reject_absence = c->zonemd_reject_absence; if(c->isrpz && !z->rpz){ if(!(z->rpz = rpz_create(c))){ fatal_exit("Could not setup RPZ zones"); return 0; } lock_protect(&z->lock, &z->rpz->local_zones, sizeof(*z->rpz)); /* the az->rpz_lock is locked above */ z->rpz_az_next = az->rpz_first; if(az->rpz_first) az->rpz_first->rpz_az_prev = z; az->rpz_first = z; } if(c->isrpz) { lock_rw_unlock(&az->rpz_lock); } /* xfer zone */ if(x) { z->zone_is_slave = 1; /* set options on xfer zone */ if(!xfer_set_masters(&x->task_probe->masters, c, 0)) { lock_basic_unlock(&x->lock); lock_rw_unlock(&z->lock); return 0; } if(!xfer_set_masters(&x->task_transfer->masters, c, 1)) { lock_basic_unlock(&x->lock); lock_rw_unlock(&z->lock); return 0; } lock_basic_unlock(&x->lock); } lock_rw_unlock(&z->lock); return 1; } /** set all auth zones deleted, then in auth_zones_cfg, it marks them * as nondeleted (if they are still in the config), and then later * we can find deleted zones */ static void az_setall_deleted(struct auth_zones* az) { struct auth_zone* z; lock_rw_wrlock(&az->lock); RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_wrlock(&z->lock); z->zone_deleted = 1; lock_rw_unlock(&z->lock); } lock_rw_unlock(&az->lock); } /** find zones that are marked deleted and delete them. * This is called from apply_cfg, and there are no threads and no * workers, so the xfr can just be deleted. */ static void az_delete_deleted_zones(struct auth_zones* az) { struct auth_zone* z; struct auth_zone* delete_list = NULL, *next; struct auth_xfer* xfr; lock_rw_wrlock(&az->lock); RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_wrlock(&z->lock); if(z->zone_deleted) { /* we cannot alter the rbtree right now, but * we can put it on a linked list and then * delete it */ z->delete_next = delete_list; delete_list = z; } lock_rw_unlock(&z->lock); } /* now we are out of the tree loop and we can loop and delete * the zones */ z = delete_list; while(z) { next = z->delete_next; xfr = auth_xfer_find(az, z->name, z->namelen, z->dclass); if(xfr) { (void)rbtree_delete(&az->xtree, &xfr->node); auth_xfer_delete(xfr); } (void)rbtree_delete(&az->ztree, &z->node); auth_zone_delete(z, az); z = next; } lock_rw_unlock(&az->lock); } int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg, int setup, int* is_rpz, struct module_env* env, struct module_stack* mods) { struct config_auth* p; az_setall_deleted(az); for(p = cfg->auths; p; p = p->next) { if(!p->name || p->name[0] == 0) { log_warn("auth-zone without a name, skipped"); continue; } *is_rpz = (*is_rpz || p->isrpz); if(!auth_zones_cfg(az, p)) { log_err("cannot config auth zone %s", p->name); return 0; } } az_delete_deleted_zones(az); if(!auth_zones_read_zones(az, cfg, env, mods)) return 0; if(setup) { if(!auth_zones_setup_zones(az)) return 0; } return 1; } /** delete chunks * @param at: transfer structure with chunks list. The chunks and their * data are freed. */ static void auth_chunks_delete(struct auth_transfer* at) { if(at->chunks_first) { struct auth_chunk* c, *cn; c = at->chunks_first; while(c) { cn = c->next; free(c->data); free(c); c = cn; } } at->chunks_first = NULL; at->chunks_last = NULL; } /** free master addr list */ static void auth_free_master_addrs(struct auth_addr* list) { struct auth_addr *n; while(list) { n = list->next; free(list); list = n; } } /** free the masters list */ static void auth_free_masters(struct auth_master* list) { struct auth_master* n; while(list) { n = list->next; auth_free_master_addrs(list->list); free(list->host); free(list->file); free(list); list = n; } } /** delete auth xfer structure * @param xfr: delete this xfer and its tasks. */ static void auth_xfer_delete(struct auth_xfer* xfr) { if(!xfr) return; lock_basic_destroy(&xfr->lock); free(xfr->name); if(xfr->task_nextprobe) { comm_timer_delete(xfr->task_nextprobe->timer); free(xfr->task_nextprobe); } if(xfr->task_probe) { auth_free_masters(xfr->task_probe->masters); comm_point_delete(xfr->task_probe->cp); comm_timer_delete(xfr->task_probe->timer); free(xfr->task_probe); } if(xfr->task_transfer) { auth_free_masters(xfr->task_transfer->masters); comm_point_delete(xfr->task_transfer->cp); comm_timer_delete(xfr->task_transfer->timer); if(xfr->task_transfer->chunks_first) { auth_chunks_delete(xfr->task_transfer); } free(xfr->task_transfer); } auth_free_masters(xfr->allow_notify_list); free(xfr); } /** helper traverse to delete zones */ static void auth_zone_del(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct auth_zone* z = (struct auth_zone*)n->key; auth_zone_delete(z, NULL); } /** helper traverse to delete xfer zones */ static void auth_xfer_del(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct auth_xfer* z = (struct auth_xfer*)n->key; auth_xfer_delete(z); } void auth_zones_delete(struct auth_zones* az) { if(!az) return; lock_rw_destroy(&az->lock); lock_rw_destroy(&az->rpz_lock); traverse_postorder(&az->ztree, auth_zone_del, NULL); traverse_postorder(&az->xtree, auth_xfer_del, NULL); free(az); } /** true if domain has only nsec3 */ static int domain_has_only_nsec3(struct auth_data* n) { struct auth_rrset* rrset = n->rrsets; int nsec3_seen = 0; while(rrset) { if(rrset->type == LDNS_RR_TYPE_NSEC3) { nsec3_seen = 1; } else if(rrset->type != LDNS_RR_TYPE_RRSIG) { return 0; } rrset = rrset->next; } return nsec3_seen; } /** see if the domain has a wildcard child '*.domain' */ static struct auth_data* az_find_wildcard_domain(struct auth_zone* z, uint8_t* nm, size_t nmlen) { uint8_t wc[LDNS_MAX_DOMAINLEN]; if(nmlen+2 > sizeof(wc)) return NULL; /* result would be too long */ wc[0] = 1; /* length of wildcard label */ wc[1] = (uint8_t)'*'; /* wildcard label */ memmove(wc+2, nm, nmlen); return az_find_name(z, wc, nmlen+2); } /** find wildcard between qname and cename */ static struct auth_data* az_find_wildcard(struct auth_zone* z, struct query_info* qinfo, struct auth_data* ce) { uint8_t* nm = qinfo->qname; size_t nmlen = qinfo->qname_len; struct auth_data* node; if(!dname_subdomain_c(nm, z->name)) return NULL; /* out of zone */ while((node=az_find_wildcard_domain(z, nm, nmlen))==NULL) { /* see if we can go up to find the wildcard */ if(nmlen == z->namelen) return NULL; /* top of zone reached */ if(ce && nmlen == ce->namelen) return NULL; /* ce reached */ if(dname_is_root(nm)) return NULL; /* cannot go up */ dname_remove_label(&nm, &nmlen); } return node; } /** domain is not exact, find first candidate ce (name that matches * a part of qname) in tree */ static struct auth_data* az_find_candidate_ce(struct auth_zone* z, struct query_info* qinfo, struct auth_data* n) { uint8_t* nm; size_t nmlen; if(n) { nm = dname_get_shared_topdomain(qinfo->qname, n->name); } else { nm = qinfo->qname; } dname_count_size_labels(nm, &nmlen); n = az_find_name(z, nm, nmlen); /* delete labels and go up on name */ while(!n) { if(dname_is_root(nm)) return NULL; /* cannot go up */ dname_remove_label(&nm, &nmlen); n = az_find_name(z, nm, nmlen); } return n; } /** go up the auth tree to next existing name. */ static struct auth_data* az_domain_go_up(struct auth_zone* z, struct auth_data* n) { uint8_t* nm = n->name; size_t nmlen = n->namelen; while(!dname_is_root(nm)) { dname_remove_label(&nm, &nmlen); if((n=az_find_name(z, nm, nmlen)) != NULL) return n; } return NULL; } /** Find the closest encloser, an name that exists and is above the * qname. * return true if the node (param node) is existing, nonobscured and * can be used to generate answers from. It is then also node_exact. * returns false if the node is not good enough (or it wasn't node_exact) * in this case the ce can be filled. * if ce is NULL, no ce exists, and likely the zone is completely empty, * not even with a zone apex. * if ce is nonNULL it is the closest enclosing upper name (that exists * itself for answer purposes). That name may have DNAME, NS or wildcard * rrset is the closest DNAME or NS rrset that was found. */ static int az_find_ce(struct auth_zone* z, struct query_info* qinfo, struct auth_data* node, int node_exact, struct auth_data** ce, struct auth_rrset** rrset) { struct auth_data* n = node; *ce = NULL; *rrset = NULL; if(!node_exact) { /* if not exact, lookup closest exact match */ n = az_find_candidate_ce(z, qinfo, n); } else { /* if exact, the node itself is the first candidate ce */ *ce = n; } /* no direct answer from nsec3-only domains */ if(n && domain_has_only_nsec3(n)) { node_exact = 0; *ce = NULL; } /* with exact matches, walk up the labels until we find the * delegation, or DNAME or zone end */ while(n) { /* see if the current candidate has issues */ /* not zone apex and has type NS */ if(n->namelen != z->namelen && (*rrset=az_domain_rrset(n, LDNS_RR_TYPE_NS)) && /* delegate here, but DS at exact the dp has notype */ (qinfo->qtype != LDNS_RR_TYPE_DS || n->namelen != qinfo->qname_len)) { /* referral */ /* this is ce and the lowernode is nonexisting */ *ce = n; return 0; } /* not equal to qname and has type DNAME */ if(n->namelen != qinfo->qname_len && (*rrset=az_domain_rrset(n, LDNS_RR_TYPE_DNAME))) { /* this is ce and the lowernode is nonexisting */ *ce = n; return 0; } if(*ce == NULL && !domain_has_only_nsec3(n)) { /* if not found yet, this exact name must be * our lowest match (but not nsec3onlydomain) */ *ce = n; } /* walk up the tree by removing labels from name and lookup */ n = az_domain_go_up(z, n); } /* found no problems, if it was an exact node, it is fine to use */ return node_exact; } /** add additional A/AAAA from domain names in rrset rdata (+offset) * offset is number of bytes in rdata where the dname is located. */ static int az_add_additionals_from(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_rrset* rrset, size_t offset) { struct packed_rrset_data* d = rrset->data; size_t i; if(!d) return 0; for(i=0; icount; i++) { size_t dlen; struct auth_data* domain; struct auth_rrset* ref; if(d->rr_len[i] < 2+offset) continue; /* too short */ if(!(dlen = dname_valid(d->rr_data[i]+2+offset, d->rr_len[i]-2-offset))) continue; /* malformed */ domain = az_find_name(z, d->rr_data[i]+2+offset, dlen); if(!domain) continue; if((ref=az_domain_rrset(domain, LDNS_RR_TYPE_A)) != NULL) { if(!msg_add_rrset_ar(z, region, msg, domain, ref)) return 0; } if((ref=az_domain_rrset(domain, LDNS_RR_TYPE_AAAA)) != NULL) { if(!msg_add_rrset_ar(z, region, msg, domain, ref)) return 0; } } return 1; } /** add negative SOA record (with negative TTL) */ static int az_add_negative_soa(struct auth_zone* z, struct regional* region, struct dns_msg* msg) { time_t minimum; size_t i; struct packed_rrset_data* d; struct auth_rrset* soa; struct auth_data* apex = az_find_name(z, z->name, z->namelen); if(!apex) return 0; soa = az_domain_rrset(apex, LDNS_RR_TYPE_SOA); if(!soa) return 0; /* must be first to put in message; we want to fix the TTL with * one RRset here, otherwise we'd need to loop over the RRs to get * the resulting lower TTL */ log_assert(msg->rep->rrset_count == 0); if(!msg_add_rrset_ns(z, region, msg, apex, soa)) return 0; /* fixup TTL */ d = (struct packed_rrset_data*)msg->rep->rrsets[msg->rep->rrset_count-1]->entry.data; /* last 4 bytes are minimum ttl in network format */ if(d->count == 0) return 0; if(d->rr_len[0] < 2+4) return 0; minimum = (time_t)sldns_read_uint32(d->rr_data[0]+(d->rr_len[0]-4)); minimum = d->ttlttl:minimum; d->ttl = minimum; for(i=0; i < d->count + d->rrsig_count; i++) d->rr_ttl[i] = minimum; msg->rep->ttl = get_rrset_ttl(msg->rep->rrsets[0]); msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; return 1; } /** See if the query goes to empty nonterminal (that has no auth_data, * but there are nodes underneath. We already checked that there are * not NS, or DNAME above, so that we only need to check if some node * exists below (with nonempty rr list), return true if emptynonterminal */ static int az_empty_nonterminal(struct auth_zone* z, struct query_info* qinfo, struct auth_data* node) { struct auth_data* next; if(!node) { /* no smaller was found, use first (smallest) node as the * next one */ next = (struct auth_data*)rbtree_first(&z->data); } else { next = (struct auth_data*)rbtree_next(&node->node); } while(next && (rbnode_type*)next != RBTREE_NULL && next->rrsets == NULL) { /* the next name has empty rrsets, is an empty nonterminal * itself, see if there exists something below it */ next = (struct auth_data*)rbtree_next(&node->node); } if((rbnode_type*)next == RBTREE_NULL || !next) { /* there is no next node, so something below it cannot * exist */ return 0; } /* a next node exists, if there was something below the query, * this node has to be it. See if it is below the query name */ if(dname_strict_subdomain_c(next->name, qinfo->qname)) return 1; return 0; } /** create synth cname target name in buffer, or fail if too long */ static size_t synth_cname_buf(uint8_t* qname, size_t qname_len, size_t dname_len, uint8_t* dtarg, size_t dtarglen, uint8_t* buf, size_t buflen) { size_t newlen = qname_len + dtarglen - dname_len; if(newlen > buflen) { /* YXDOMAIN error */ return 0; } /* new name is concatenation of qname front (without DNAME owner) * and DNAME target name */ memcpy(buf, qname, qname_len-dname_len); memmove(buf+(qname_len-dname_len), dtarg, dtarglen); return newlen; } /** create synthetic CNAME rrset for in a DNAME answer in region, * false on alloc failure, cname==NULL when name too long. */ static int create_synth_cname(uint8_t* qname, size_t qname_len, struct regional* region, struct auth_data* node, struct auth_rrset* dname, uint16_t dclass, struct ub_packed_rrset_key** cname) { uint8_t buf[LDNS_MAX_DOMAINLEN]; uint8_t* dtarg; size_t dtarglen, newlen; struct packed_rrset_data* d; /* get DNAME target name */ if(dname->data->count < 1) return 0; if(dname->data->rr_len[0] < 3) return 0; /* at least rdatalen +1 */ dtarg = dname->data->rr_data[0]+2; dtarglen = dname->data->rr_len[0]-2; if(sldns_read_uint16(dname->data->rr_data[0]) != dtarglen) return 0; /* rdatalen in DNAME rdata is malformed */ if(dname_valid(dtarg, dtarglen) != dtarglen) return 0; /* DNAME RR has malformed rdata */ if(qname_len == 0) return 0; /* too short */ if(qname_len <= node->namelen) return 0; /* qname too short for dname removal */ /* synthesize a CNAME */ newlen = synth_cname_buf(qname, qname_len, node->namelen, dtarg, dtarglen, buf, sizeof(buf)); if(newlen == 0) { /* YXDOMAIN error */ *cname = NULL; return 1; } *cname = (struct ub_packed_rrset_key*)regional_alloc(region, sizeof(struct ub_packed_rrset_key)); if(!*cname) return 0; /* out of memory */ memset(&(*cname)->entry, 0, sizeof((*cname)->entry)); (*cname)->entry.key = (*cname); (*cname)->rk.type = htons(LDNS_RR_TYPE_CNAME); (*cname)->rk.rrset_class = htons(dclass); (*cname)->rk.flags = 0; (*cname)->rk.dname = regional_alloc_init(region, qname, qname_len); if(!(*cname)->rk.dname) return 0; /* out of memory */ (*cname)->rk.dname_len = qname_len; (*cname)->entry.hash = rrset_key_hash(&(*cname)->rk); d = (struct packed_rrset_data*)regional_alloc_zero(region, sizeof(struct packed_rrset_data) + sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t) + sizeof(uint16_t) + newlen); if(!d) return 0; /* out of memory */ (*cname)->entry.data = d; d->ttl = 0; /* 0 for synthesized CNAME TTL */ d->count = 1; d->rrsig_count = 0; d->trust = rrset_trust_ans_noAA; d->rr_len = (size_t*)((uint8_t*)d + sizeof(struct packed_rrset_data)); d->rr_len[0] = newlen + sizeof(uint16_t); packed_rrset_ptr_fixup(d); d->rr_ttl[0] = d->ttl; sldns_write_uint16(d->rr_data[0], newlen); memmove(d->rr_data[0] + sizeof(uint16_t), buf, newlen); return 1; } /** add a synthesized CNAME to the answer section */ static int add_synth_cname(struct auth_zone* z, uint8_t* qname, size_t qname_len, struct regional* region, struct dns_msg* msg, struct auth_data* dname, struct auth_rrset* rrset) { struct ub_packed_rrset_key* cname; /* synthesize a CNAME */ if(!create_synth_cname(qname, qname_len, region, dname, rrset, z->dclass, &cname)) { /* out of memory */ return 0; } if(!cname) { /* cname cannot be create because of YXDOMAIN */ msg->rep->flags |= LDNS_RCODE_YXDOMAIN; return 1; } /* add cname to message */ if(!msg_grow_array(region, msg)) return 0; msg->rep->rrsets[msg->rep->rrset_count] = cname; msg->rep->rrset_count++; msg->rep->an_numrrsets++; msg_ttl(msg); return 1; } /** Change a dname to a different one, for wildcard namechange */ static void az_change_dnames(struct dns_msg* msg, uint8_t* oldname, uint8_t* newname, size_t newlen, int an_only) { size_t i; size_t start = 0, end = msg->rep->rrset_count; if(!an_only) start = msg->rep->an_numrrsets; if(an_only) end = msg->rep->an_numrrsets; for(i=start; irep->rrsets[i]->rk.dname, oldname) == 0) { msg->rep->rrsets[i]->rk.dname = newname; msg->rep->rrsets[i]->rk.dname_len = newlen; } } } /** find NSEC record covering the query */ static struct auth_rrset* az_find_nsec_cover(struct auth_zone* z, struct auth_data** node) { uint8_t* nm = (*node)->name; size_t nmlen = (*node)->namelen; struct auth_rrset* rrset; /* find the NSEC for the smallest-or-equal node */ /* if node == NULL, we did not find a smaller name. But the zone * name is the smallest name and should have an NSEC. So there is * no NSEC to return (for a properly signed zone) */ /* for empty nonterminals, the auth-data node should not exist, * and thus we don't need to go rbtree_previous here to find * a domain with an NSEC record */ /* but there could be glue, and if this is node, then it has no NSEC. * Go up to find nonglue (previous) NSEC-holding nodes */ while((rrset=az_domain_rrset(*node, LDNS_RR_TYPE_NSEC)) == NULL) { if(dname_is_root(nm)) return NULL; if(nmlen == z->namelen) return NULL; dname_remove_label(&nm, &nmlen); /* adjust *node for the nsec rrset to find in */ *node = az_find_name(z, nm, nmlen); } return rrset; } /** Find NSEC and add for wildcard denial */ static int az_nsec_wildcard_denial(struct auth_zone* z, struct regional* region, struct dns_msg* msg, uint8_t* cenm, size_t cenmlen) { struct query_info qinfo; int node_exact; struct auth_data* node; struct auth_rrset* nsec; uint8_t wc[LDNS_MAX_DOMAINLEN]; if(cenmlen+2 > sizeof(wc)) return 0; /* result would be too long */ wc[0] = 1; /* length of wildcard label */ wc[1] = (uint8_t)'*'; /* wildcard label */ memmove(wc+2, cenm, cenmlen); /* we have '*.ce' in wc wildcard name buffer */ /* get nsec cover for that */ qinfo.qname = wc; qinfo.qname_len = cenmlen+2; qinfo.qtype = 0; qinfo.qclass = 0; az_find_domain(z, &qinfo, &node_exact, &node); if((nsec=az_find_nsec_cover(z, &node)) != NULL) { if(!msg_add_rrset_ns(z, region, msg, node, nsec)) return 0; } return 1; } /** Find the NSEC3PARAM rrset (if any) and if true you have the parameters */ static int az_nsec3_param(struct auth_zone* z, int* algo, size_t* iter, uint8_t** salt, size_t* saltlen) { struct auth_data* apex; struct auth_rrset* param; size_t i; apex = az_find_name(z, z->name, z->namelen); if(!apex) return 0; param = az_domain_rrset(apex, LDNS_RR_TYPE_NSEC3PARAM); if(!param || param->data->count==0) return 0; /* no RRset or no RRs in rrset */ /* find out which NSEC3PARAM RR has supported parameters */ /* skip unknown flags (dynamic signer is recalculating nsec3 chain) */ for(i=0; idata->count; i++) { uint8_t* rdata = param->data->rr_data[i]+2; size_t rdatalen = param->data->rr_len[i]; if(rdatalen < 2+5) continue; /* too short */ if(!nsec3_hash_algo_size_supported((int)(rdata[0]))) continue; /* unsupported algo */ if(rdatalen < (size_t)(2+5+(size_t)rdata[4])) continue; /* salt missing */ if((rdata[1]&NSEC3_UNKNOWN_FLAGS)!=0) continue; /* unknown flags */ *algo = (int)(rdata[0]); *iter = sldns_read_uint16(rdata+2); *saltlen = rdata[4]; if(*saltlen == 0) *salt = NULL; else *salt = rdata+5; return 1; } /* no supported params */ return 0; } /** Hash a name with nsec3param into buffer, it has zone name appended. * return length of hash */ static size_t az_nsec3_hash(uint8_t* buf, size_t buflen, uint8_t* nm, size_t nmlen, int algo, size_t iter, uint8_t* salt, size_t saltlen) { size_t hlen = nsec3_hash_algo_size_supported(algo); /* buffer has domain name, nsec3hash, and 256 is for max saltlen * (salt has 0-255 length) */ unsigned char p[LDNS_MAX_DOMAINLEN+1+N3HASHBUFLEN+256]; size_t i; if(nmlen+saltlen > sizeof(p) || hlen+saltlen > sizeof(p)) return 0; if(hlen > buflen) return 0; /* somehow too large for destination buffer */ /* hashfunc(name, salt) */ memmove(p, nm, nmlen); query_dname_tolower(p); if(salt && saltlen > 0) memmove(p+nmlen, salt, saltlen); (void)secalgo_nsec3_hash(algo, p, nmlen+saltlen, (unsigned char*)buf); for(i=0; i 0) memmove(p+hlen, salt, saltlen); (void)secalgo_nsec3_hash(algo, p, hlen+saltlen, (unsigned char*)buf); } return hlen; } /** Hash name and return b32encoded hashname for lookup, zone name appended */ static int az_nsec3_hashname(struct auth_zone* z, uint8_t* hashname, size_t* hashnmlen, uint8_t* nm, size_t nmlen, int algo, size_t iter, uint8_t* salt, size_t saltlen) { uint8_t hash[N3HASHBUFLEN]; size_t hlen; int ret; hlen = az_nsec3_hash(hash, sizeof(hash), nm, nmlen, algo, iter, salt, saltlen); if(!hlen) return 0; /* b32 encode */ if(*hashnmlen < hlen*2+1+z->namelen) /* approx b32 as hexb16 */ return 0; ret = sldns_b32_ntop_extended_hex(hash, hlen, (char*)(hashname+1), (*hashnmlen)-1); if(ret<1) return 0; hashname[0] = (uint8_t)ret; ret++; if((*hashnmlen) - ret < z->namelen) return 0; memmove(hashname+ret, z->name, z->namelen); *hashnmlen = z->namelen+(size_t)ret; return 1; } /** Find the datanode that covers the nsec3hash-name */ static struct auth_data* az_nsec3_findnode(struct auth_zone* z, uint8_t* hashnm, size_t hashnmlen) { struct query_info qinfo; struct auth_data* node; int node_exact; qinfo.qclass = 0; qinfo.qtype = 0; qinfo.qname = hashnm; qinfo.qname_len = hashnmlen; /* because canonical ordering and b32 nsec3 ordering are the same. * this is a good lookup to find the nsec3 name. */ az_find_domain(z, &qinfo, &node_exact, &node); /* but we may have to skip non-nsec3 nodes */ /* this may be a lot, the way to speed that up is to have a * separate nsec3 tree with nsec3 nodes */ while(node && (rbnode_type*)node != RBTREE_NULL && !az_domain_rrset(node, LDNS_RR_TYPE_NSEC3)) { node = (struct auth_data*)rbtree_previous(&node->node); } if((rbnode_type*)node == RBTREE_NULL) node = NULL; return node; } /** Find cover for hashed(nm, nmlen) (or NULL) */ static struct auth_data* az_nsec3_find_cover(struct auth_zone* z, uint8_t* nm, size_t nmlen, int algo, size_t iter, uint8_t* salt, size_t saltlen) { struct auth_data* node; uint8_t hname[LDNS_MAX_DOMAINLEN]; size_t hlen = sizeof(hname); if(!az_nsec3_hashname(z, hname, &hlen, nm, nmlen, algo, iter, salt, saltlen)) return NULL; node = az_nsec3_findnode(z, hname, hlen); if(node) return node; /* we did not find any, perhaps because the NSEC3 hash is before * the first hash, we have to find the 'last hash' in the zone */ node = (struct auth_data*)rbtree_last(&z->data); while(node && (rbnode_type*)node != RBTREE_NULL && !az_domain_rrset(node, LDNS_RR_TYPE_NSEC3)) { node = (struct auth_data*)rbtree_previous(&node->node); } if((rbnode_type*)node == RBTREE_NULL) node = NULL; return node; } /** Find exact match for hashed(nm, nmlen) NSEC3 record or NULL */ static struct auth_data* az_nsec3_find_exact(struct auth_zone* z, uint8_t* nm, size_t nmlen, int algo, size_t iter, uint8_t* salt, size_t saltlen) { struct auth_data* node; uint8_t hname[LDNS_MAX_DOMAINLEN]; size_t hlen = sizeof(hname); if(!az_nsec3_hashname(z, hname, &hlen, nm, nmlen, algo, iter, salt, saltlen)) return NULL; node = az_find_name(z, hname, hlen); if(az_domain_rrset(node, LDNS_RR_TYPE_NSEC3)) return node; return NULL; } /** Return nextcloser name (as a ref into the qname). This is one label * more than the cenm (cename must be a suffix of qname) */ static void az_nsec3_get_nextcloser(uint8_t* cenm, uint8_t* qname, size_t qname_len, uint8_t** nx, size_t* nxlen) { int celabs = dname_count_labels(cenm); int qlabs = dname_count_labels(qname); int strip = qlabs - celabs -1; log_assert(dname_strict_subdomain(qname, qlabs, cenm, celabs)); *nx = qname; *nxlen = qname_len; if(strip>0) dname_remove_labels(nx, nxlen, strip); } /** Find the closest encloser that has exact NSEC3. * updated cenm to the new name. If it went up no-exact-ce is true. */ static struct auth_data* az_nsec3_find_ce(struct auth_zone* z, uint8_t** cenm, size_t* cenmlen, int* no_exact_ce, int algo, size_t iter, uint8_t* salt, size_t saltlen) { struct auth_data* node; while((node = az_nsec3_find_exact(z, *cenm, *cenmlen, algo, iter, salt, saltlen)) == NULL) { if(*cenmlen == z->namelen) { /* next step up would take us out of the zone. fail */ return NULL; } *no_exact_ce = 1; dname_remove_label(cenm, cenmlen); } return node; } /* Insert NSEC3 record in authority section, if NULL does nothing */ static int az_nsec3_insert(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node) { struct auth_rrset* nsec3; if(!node) return 1; /* no node, skip this */ nsec3 = az_domain_rrset(node, LDNS_RR_TYPE_NSEC3); if(!nsec3) return 1; /* if no nsec3 RR, skip it */ if(!msg_add_rrset_ns(z, region, msg, node, nsec3)) return 0; return 1; } /** add NSEC3 records to the zone for the nsec3 proof. * Specify with the flags with parts of the proof are required. * the ce is the exact matching name (for notype) but also delegation points. * qname is the one where the nextcloser name can be derived from. * If NSEC3 is not properly there (in the zone) nothing is added. * always enabled: include nsec3 proving about the Closest Encloser. * that is an exact match that should exist for it. * If that does not exist, a higher exact match + nxproof is enabled * (for some sort of opt-out empty nonterminal cases). * nodataproof: search for exact match and include that instead. * ceproof: include ce proof NSEC3 (omitted for wildcard replies). * nxproof: include denial of the qname. * wcproof: include denial of wildcard (wildcard.ce). */ static int az_add_nsec3_proof(struct auth_zone* z, struct regional* region, struct dns_msg* msg, uint8_t* cenm, size_t cenmlen, uint8_t* qname, size_t qname_len, int nodataproof, int ceproof, int nxproof, int wcproof) { int algo; size_t iter, saltlen; uint8_t* salt; int no_exact_ce = 0; struct auth_data* node; /* find parameters of nsec3 proof */ if(!az_nsec3_param(z, &algo, &iter, &salt, &saltlen)) return 1; /* no nsec3 */ if(nodataproof) { /* see if the node has a hash of itself for the nodata * proof nsec3, this has to be an exact match nsec3. */ struct auth_data* match; match = az_nsec3_find_exact(z, qname, qname_len, algo, iter, salt, saltlen); if(match) { if(!az_nsec3_insert(z, region, msg, match)) return 0; /* only nodata NSEC3 needed, no CE or others. */ return 1; } } /* find ce that has an NSEC3 */ if(ceproof) { node = az_nsec3_find_ce(z, &cenm, &cenmlen, &no_exact_ce, algo, iter, salt, saltlen); if(no_exact_ce) nxproof = 1; if(!az_nsec3_insert(z, region, msg, node)) return 0; } if(nxproof) { uint8_t* nx; size_t nxlen; /* create nextcloser domain name */ az_nsec3_get_nextcloser(cenm, qname, qname_len, &nx, &nxlen); /* find nsec3 that matches or covers it */ node = az_nsec3_find_cover(z, nx, nxlen, algo, iter, salt, saltlen); if(!az_nsec3_insert(z, region, msg, node)) return 0; } if(wcproof) { /* create wildcard name *.ce */ uint8_t wc[LDNS_MAX_DOMAINLEN]; size_t wclen; if(cenmlen+2 > sizeof(wc)) return 0; /* result would be too long */ wc[0] = 1; /* length of wildcard label */ wc[1] = (uint8_t)'*'; /* wildcard label */ memmove(wc+2, cenm, cenmlen); wclen = cenmlen+2; /* find nsec3 that matches or covers it */ node = az_nsec3_find_cover(z, wc, wclen, algo, iter, salt, saltlen); if(!az_nsec3_insert(z, region, msg, node)) return 0; } return 1; } /** generate answer for positive answer */ static int az_generate_positive_answer(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node, struct auth_rrset* rrset) { if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; /* see if we want additional rrs */ if(rrset->type == LDNS_RR_TYPE_MX) { if(!az_add_additionals_from(z, region, msg, rrset, 2)) return 0; } else if(rrset->type == LDNS_RR_TYPE_SRV) { if(!az_add_additionals_from(z, region, msg, rrset, 6)) return 0; } else if(rrset->type == LDNS_RR_TYPE_NS) { if(!az_add_additionals_from(z, region, msg, rrset, 0)) return 0; } return 1; } /** generate answer for type ANY answer */ static int az_generate_any_answer(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node) { struct auth_rrset* rrset; int added = 0; /* add a couple (at least one) RRs */ if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_SOA)) != NULL) { if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; added++; } if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_MX)) != NULL) { if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; added++; } if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_A)) != NULL) { if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; added++; } if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_AAAA)) != NULL) { if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; added++; } if(added == 0 && node && node->rrsets) { if(!msg_add_rrset_an(z, region, msg, node, node->rrsets)) return 0; } return 1; } /** follow cname chain and add more data to the answer section */ static int follow_cname_chain(struct auth_zone* z, uint16_t qtype, struct regional* region, struct dns_msg* msg, struct packed_rrset_data* d) { int maxchain = 0; /* see if we can add the target of the CNAME into the answer */ while(maxchain++ < MAX_CNAME_CHAIN) { struct auth_data* node; struct auth_rrset* rrset; size_t clen; /* d has cname rdata */ if(d->count == 0) break; /* no CNAME */ if(d->rr_len[0] < 2+1) break; /* too small */ if((clen=dname_valid(d->rr_data[0]+2, d->rr_len[0]-2))==0) break; /* malformed */ if(!dname_subdomain_c(d->rr_data[0]+2, z->name)) break; /* target out of zone */ if((node = az_find_name(z, d->rr_data[0]+2, clen))==NULL) break; /* no such target name */ if((rrset=az_domain_rrset(node, qtype))!=NULL) { /* done we found the target */ if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; break; } if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_CNAME))==NULL) break; /* no further CNAME chain, notype */ if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; d = rrset->data; } return 1; } /** generate answer for cname answer */ static int az_generate_cname_answer(struct auth_zone* z, struct query_info* qinfo, struct regional* region, struct dns_msg* msg, struct auth_data* node, struct auth_rrset* rrset) { if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; if(!rrset) return 1; if(!follow_cname_chain(z, qinfo->qtype, region, msg, rrset->data)) return 0; return 1; } /** generate answer for notype answer */ static int az_generate_notype_answer(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node) { struct auth_rrset* rrset; if(!az_add_negative_soa(z, region, msg)) return 0; /* DNSSEC denial NSEC */ if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_NSEC))!=NULL) { if(!msg_add_rrset_ns(z, region, msg, node, rrset)) return 0; } else if(node) { /* DNSSEC denial NSEC3 */ if(!az_add_nsec3_proof(z, region, msg, node->name, node->namelen, msg->qinfo.qname, msg->qinfo.qname_len, 1, 1, 0, 0)) return 0; } return 1; } /** generate answer for referral answer */ static int az_generate_referral_answer(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* ce, struct auth_rrset* rrset) { struct auth_rrset* ds, *nsec; /* turn off AA flag, referral is nonAA because it leaves the zone */ log_assert(ce); msg->rep->flags &= ~BIT_AA; if(!msg_add_rrset_ns(z, region, msg, ce, rrset)) return 0; /* add DS or deny it */ if((ds=az_domain_rrset(ce, LDNS_RR_TYPE_DS))!=NULL) { if(!msg_add_rrset_ns(z, region, msg, ce, ds)) return 0; } else { /* deny the DS */ if((nsec=az_domain_rrset(ce, LDNS_RR_TYPE_NSEC))!=NULL) { if(!msg_add_rrset_ns(z, region, msg, ce, nsec)) return 0; } else { if(!az_add_nsec3_proof(z, region, msg, ce->name, ce->namelen, msg->qinfo.qname, msg->qinfo.qname_len, 1, 1, 0, 0)) return 0; } } /* add additional rrs for type NS */ if(!az_add_additionals_from(z, region, msg, rrset, 0)) return 0; return 1; } /** generate answer for DNAME answer */ static int az_generate_dname_answer(struct auth_zone* z, struct query_info* qinfo, struct regional* region, struct dns_msg* msg, struct auth_data* ce, struct auth_rrset* rrset) { log_assert(ce); /* add the DNAME and then a CNAME */ if(!msg_add_rrset_an(z, region, msg, ce, rrset)) return 0; if(!add_synth_cname(z, qinfo->qname, qinfo->qname_len, region, msg, ce, rrset)) return 0; if(FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_YXDOMAIN) return 1; if(msg->rep->rrset_count == 0 || !msg->rep->rrsets[msg->rep->rrset_count-1]) return 0; if(!follow_cname_chain(z, qinfo->qtype, region, msg, (struct packed_rrset_data*)msg->rep->rrsets[ msg->rep->rrset_count-1]->entry.data)) return 0; return 1; } /** generate answer for wildcard answer */ static int az_generate_wildcard_answer(struct auth_zone* z, struct query_info* qinfo, struct regional* region, struct dns_msg* msg, struct auth_data* ce, struct auth_data* wildcard, struct auth_data* node) { struct auth_rrset* rrset, *nsec; int insert_ce = 0; if((rrset=az_domain_rrset(wildcard, qinfo->qtype)) != NULL) { /* wildcard has type, add it */ if(!msg_add_rrset_an(z, region, msg, wildcard, rrset)) return 0; az_change_dnames(msg, wildcard->name, msg->qinfo.qname, msg->qinfo.qname_len, 1); } else if((rrset=az_domain_rrset(wildcard, LDNS_RR_TYPE_CNAME))!=NULL) { /* wildcard has cname instead, do that */ if(!msg_add_rrset_an(z, region, msg, wildcard, rrset)) return 0; az_change_dnames(msg, wildcard->name, msg->qinfo.qname, msg->qinfo.qname_len, 1); if(!follow_cname_chain(z, qinfo->qtype, region, msg, rrset->data)) return 0; } else if(qinfo->qtype == LDNS_RR_TYPE_ANY && wildcard->rrsets) { /* add ANY rrsets from wildcard node */ if(!az_generate_any_answer(z, region, msg, wildcard)) return 0; az_change_dnames(msg, wildcard->name, msg->qinfo.qname, msg->qinfo.qname_len, 1); } else { /* wildcard has nodata, notype answer */ /* call other notype routine for dnssec notype denials */ if(!az_generate_notype_answer(z, region, msg, wildcard)) return 0; /* because the notype, there is no positive data with an * RRSIG that indicates the wildcard position. Thus the * wildcard qname denial needs to have a CE nsec3. */ insert_ce = 1; } /* ce and node for dnssec denial of wildcard original name */ if((nsec=az_find_nsec_cover(z, &node)) != NULL) { if(!msg_add_rrset_ns(z, region, msg, node, nsec)) return 0; } else if(ce) { uint8_t* wildup = wildcard->name; size_t wilduplen= wildcard->namelen; dname_remove_label(&wildup, &wilduplen); if(!az_add_nsec3_proof(z, region, msg, wildup, wilduplen, msg->qinfo.qname, msg->qinfo.qname_len, 0, insert_ce, 1, 0)) return 0; } /* fixup name of wildcard from *.zone to qname, use already allocated * pointer to msg qname */ az_change_dnames(msg, wildcard->name, msg->qinfo.qname, msg->qinfo.qname_len, 0); return 1; } /** generate answer for nxdomain answer */ static int az_generate_nxdomain_answer(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* ce, struct auth_data* node) { struct auth_rrset* nsec; msg->rep->flags |= LDNS_RCODE_NXDOMAIN; if(!az_add_negative_soa(z, region, msg)) return 0; if((nsec=az_find_nsec_cover(z, &node)) != NULL) { if(!msg_add_rrset_ns(z, region, msg, node, nsec)) return 0; if(ce && !az_nsec_wildcard_denial(z, region, msg, ce->name, ce->namelen)) return 0; } else if(ce) { if(!az_add_nsec3_proof(z, region, msg, ce->name, ce->namelen, msg->qinfo.qname, msg->qinfo.qname_len, 0, 1, 1, 1)) return 0; } return 1; } /** Create answers when an exact match exists for the domain name */ static int az_generate_answer_with_node(struct auth_zone* z, struct query_info* qinfo, struct regional* region, struct dns_msg* msg, struct auth_data* node) { struct auth_rrset* rrset; /* positive answer, rrset we are looking for exists */ if((rrset=az_domain_rrset(node, qinfo->qtype)) != NULL) { return az_generate_positive_answer(z, region, msg, node, rrset); } /* CNAME? */ if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_CNAME)) != NULL) { return az_generate_cname_answer(z, qinfo, region, msg, node, rrset); } /* type ANY ? */ if(qinfo->qtype == LDNS_RR_TYPE_ANY) { return az_generate_any_answer(z, region, msg, node); } /* NOERROR/NODATA (no such type at domain name) */ return az_generate_notype_answer(z, region, msg, node); } /** Generate answer without an existing-node that we can use. * So it'll be a referral, DNAME or nxdomain */ static int az_generate_answer_nonexistnode(struct auth_zone* z, struct query_info* qinfo, struct regional* region, struct dns_msg* msg, struct auth_data* ce, struct auth_rrset* rrset, struct auth_data* node) { struct auth_data* wildcard; /* we do not have an exact matching name (that exists) */ /* see if we have a NS or DNAME in the ce */ if(ce && rrset && rrset->type == LDNS_RR_TYPE_NS) { return az_generate_referral_answer(z, region, msg, ce, rrset); } if(ce && rrset && rrset->type == LDNS_RR_TYPE_DNAME) { return az_generate_dname_answer(z, qinfo, region, msg, ce, rrset); } /* if there is an empty nonterminal, wildcard and nxdomain don't * happen, it is a notype answer */ if(az_empty_nonterminal(z, qinfo, node)) { return az_generate_notype_answer(z, region, msg, node); } /* see if we have a wildcard under the ce */ if((wildcard=az_find_wildcard(z, qinfo, ce)) != NULL) { return az_generate_wildcard_answer(z, qinfo, region, msg, ce, wildcard, node); } /* generate nxdomain answer */ return az_generate_nxdomain_answer(z, region, msg, ce, node); } /** Lookup answer in a zone. */ static int auth_zone_generate_answer(struct auth_zone* z, struct query_info* qinfo, struct regional* region, struct dns_msg** msg, int* fallback) { struct auth_data* node, *ce; struct auth_rrset* rrset; int node_exact, node_exists; /* does the zone want fallback in case of failure? */ *fallback = z->fallback_enabled; if(!(*msg=msg_create(region, qinfo))) return 0; /* lookup if there is a matching domain name for the query */ az_find_domain(z, qinfo, &node_exact, &node); /* see if node exists for generating answers from (i.e. not glue and * obscured by NS or DNAME or NSEC3-only), and also return the * closest-encloser from that, closest node that should be used * to generate answers from that is above the query */ node_exists = az_find_ce(z, qinfo, node, node_exact, &ce, &rrset); if(verbosity >= VERB_ALGO) { char zname[256], qname[256], nname[256], cename[256], tpstr[32], rrstr[32]; sldns_wire2str_dname_buf(qinfo->qname, qinfo->qname_len, qname, sizeof(qname)); sldns_wire2str_type_buf(qinfo->qtype, tpstr, sizeof(tpstr)); sldns_wire2str_dname_buf(z->name, z->namelen, zname, sizeof(zname)); if(node) sldns_wire2str_dname_buf(node->name, node->namelen, nname, sizeof(nname)); else snprintf(nname, sizeof(nname), "NULL"); if(ce) sldns_wire2str_dname_buf(ce->name, ce->namelen, cename, sizeof(cename)); else snprintf(cename, sizeof(cename), "NULL"); if(rrset) sldns_wire2str_type_buf(rrset->type, rrstr, sizeof(rrstr)); else snprintf(rrstr, sizeof(rrstr), "NULL"); log_info("auth_zone %s query %s %s, domain %s %s %s, " "ce %s, rrset %s", zname, qname, tpstr, nname, (node_exact?"exact":"notexact"), (node_exists?"exist":"notexist"), cename, rrstr); } if(node_exists) { /* the node is fine, generate answer from node */ return az_generate_answer_with_node(z, qinfo, region, *msg, node); } return az_generate_answer_nonexistnode(z, qinfo, region, *msg, ce, rrset, node); } int auth_zones_lookup(struct auth_zones* az, struct query_info* qinfo, struct regional* region, struct dns_msg** msg, int* fallback, uint8_t* dp_nm, size_t dp_nmlen) { int r; struct auth_zone* z; /* find the zone that should contain the answer. */ lock_rw_rdlock(&az->lock); z = auth_zone_find(az, dp_nm, dp_nmlen, qinfo->qclass); if(!z) { lock_rw_unlock(&az->lock); /* no auth zone, fallback to internet */ *fallback = 1; return 0; } lock_rw_rdlock(&z->lock); lock_rw_unlock(&az->lock); /* if not for upstream queries, fallback */ if(!z->for_upstream) { lock_rw_unlock(&z->lock); *fallback = 1; return 0; } if(z->zone_expired) { *fallback = z->fallback_enabled; lock_rw_unlock(&z->lock); return 0; } /* see what answer that zone would generate */ r = auth_zone_generate_answer(z, qinfo, region, msg, fallback); lock_rw_unlock(&z->lock); return r; } /** encode auth answer */ static void auth_answer_encode(struct query_info* qinfo, struct module_env* env, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, struct regional* temp, struct dns_msg* msg) { uint16_t udpsize; udpsize = edns->udp_size; edns->edns_version = EDNS_ADVERTISED_VERSION; edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; if(!inplace_cb_reply_local_call(env, qinfo, NULL, msg->rep, (int)FLAGS_GET_RCODE(msg->rep->flags), edns, repinfo, temp, env->now_tv) || !reply_info_answer_encode(qinfo, msg->rep, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0)) { error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); } } /** encode auth error answer */ static void auth_error_encode(struct query_info* qinfo, struct module_env* env, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, struct regional* temp, int rcode) { edns->edns_version = EDNS_ADVERTISED_VERSION; edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; if(!inplace_cb_reply_local_call(env, qinfo, NULL, NULL, rcode, edns, repinfo, temp, env->now_tv)) edns->opt_list_inplace_cb_out = NULL; error_encode(buf, rcode|BIT_AA, qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); } int auth_zones_answer(struct auth_zones* az, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, struct sldns_buffer* buf, struct regional* temp) { struct dns_msg* msg = NULL; struct auth_zone* z; int r; int fallback = 0; lock_rw_rdlock(&az->lock); if(!az->have_downstream) { /* no downstream auth zones */ lock_rw_unlock(&az->lock); return 0; } if(qinfo->qtype == LDNS_RR_TYPE_DS) { uint8_t* delname = qinfo->qname; size_t delnamelen = qinfo->qname_len; dname_remove_label(&delname, &delnamelen); z = auth_zones_find_zone(az, delname, delnamelen, qinfo->qclass); } else { z = auth_zones_find_zone(az, qinfo->qname, qinfo->qname_len, qinfo->qclass); } if(!z) { /* no zone above it */ lock_rw_unlock(&az->lock); return 0; } lock_rw_rdlock(&z->lock); lock_rw_unlock(&az->lock); if(!z->for_downstream) { lock_rw_unlock(&z->lock); return 0; } if(z->zone_expired) { if(z->fallback_enabled) { lock_rw_unlock(&z->lock); return 0; } lock_rw_unlock(&z->lock); lock_rw_wrlock(&az->lock); az->num_query_down++; lock_rw_unlock(&az->lock); auth_error_encode(qinfo, env, edns, repinfo, buf, temp, LDNS_RCODE_SERVFAIL); return 1; } /* answer it from zone z */ r = auth_zone_generate_answer(z, qinfo, temp, &msg, &fallback); lock_rw_unlock(&z->lock); if(!r && fallback) { /* fallback to regular answering (recursive) */ return 0; } lock_rw_wrlock(&az->lock); az->num_query_down++; lock_rw_unlock(&az->lock); /* encode answer */ if(!r) auth_error_encode(qinfo, env, edns, repinfo, buf, temp, LDNS_RCODE_SERVFAIL); else auth_answer_encode(qinfo, env, edns, repinfo, buf, temp, msg); return 1; } int auth_zones_can_fallback(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass) { int r; struct auth_zone* z; lock_rw_rdlock(&az->lock); z = auth_zone_find(az, nm, nmlen, dclass); if(!z) { lock_rw_unlock(&az->lock); /* no such auth zone, fallback */ return 1; } lock_rw_rdlock(&z->lock); lock_rw_unlock(&az->lock); r = z->fallback_enabled || (!z->for_upstream); lock_rw_unlock(&z->lock); return r; } int auth_zone_parse_notify_serial(sldns_buffer* pkt, uint32_t *serial) { struct query_info q; uint16_t rdlen; memset(&q, 0, sizeof(q)); sldns_buffer_set_position(pkt, 0); if(!query_info_parse(&q, pkt)) return 0; if(LDNS_ANCOUNT(sldns_buffer_begin(pkt)) == 0) return 0; /* skip name of RR in answer section */ if(sldns_buffer_remaining(pkt) < 1) return 0; if(pkt_dname_len(pkt) == 0) return 0; /* check type */ if(sldns_buffer_remaining(pkt) < 10 /* type,class,ttl,rdatalen*/) return 0; if(sldns_buffer_read_u16(pkt) != LDNS_RR_TYPE_SOA) return 0; sldns_buffer_skip(pkt, 2); /* class */ sldns_buffer_skip(pkt, 4); /* ttl */ rdlen = sldns_buffer_read_u16(pkt); /* rdatalen */ if(sldns_buffer_remaining(pkt) < rdlen) return 0; if(rdlen < 22) return 0; /* bad soa length */ sldns_buffer_skip(pkt, (ssize_t)(rdlen-20)); *serial = sldns_buffer_read_u32(pkt); /* return true when has serial in answer section */ return 1; } /** see if addr appears in the list */ static int addr_in_list(struct auth_addr* list, struct sockaddr_storage* addr, socklen_t addrlen) { struct auth_addr* p; for(p=list; p; p=p->next) { if(sockaddr_cmp_addr(addr, addrlen, &p->addr, p->addrlen)==0) return 1; } return 0; } /** check if an address matches a master specification (or one of its * addresses in the addr list) */ static int addr_matches_master(struct auth_master* master, struct sockaddr_storage* addr, socklen_t addrlen, struct auth_master** fromhost) { struct sockaddr_storage a; socklen_t alen = 0; int net = 0; if(addr_in_list(master->list, addr, addrlen)) { *fromhost = master; return 1; } /* compare address (but not port number, that is the destination * port of the master, the port number of the received notify is * allowed to by any port on that master) */ if(extstrtoaddr(master->host, &a, &alen) && sockaddr_cmp_addr(addr, addrlen, &a, alen)==0) { *fromhost = master; return 1; } /* prefixes, addr/len, like 10.0.0.0/8 */ /* not http and has a / and there is one / */ if(master->allow_notify && !master->http && strchr(master->host, '/') != NULL && strchr(master->host, '/') == strrchr(master->host, '/') && netblockstrtoaddr(master->host, UNBOUND_DNS_PORT, &a, &alen, &net) && alen == addrlen) { if(addr_in_common(addr, (addr_is_ip6(addr, addrlen)?128:32), &a, net, alen) >= net) { *fromhost = NULL; /* prefix does not have destination to send the probe or transfer with */ return 1; /* matches the netblock */ } } return 0; } /** check access list for notifies */ static int az_xfr_allowed_notify(struct auth_xfer* xfr, struct sockaddr_storage* addr, socklen_t addrlen, struct auth_master** fromhost) { struct auth_master* p; for(p=xfr->allow_notify_list; p; p=p->next) { if(addr_matches_master(p, addr, addrlen, fromhost)) { return 1; } } return 0; } /** see if the serial means the zone has to be updated, i.e. the serial * is newer than the zone serial, or we have no zone */ static int xfr_serial_means_update(struct auth_xfer* xfr, uint32_t serial) { if(!xfr->have_zone) return 1; /* no zone, anything is better */ if(xfr->zone_expired) return 1; /* expired, the sent serial is better than expired data */ if(compare_serial(xfr->serial, serial) < 0) return 1; /* our serial is smaller than the sent serial, the data is newer, fetch it */ return 0; } /** note notify serial, updates the notify information in the xfr struct */ static void xfr_note_notify_serial(struct auth_xfer* xfr, int has_serial, uint32_t serial) { if(xfr->notify_received && xfr->notify_has_serial && has_serial) { /* see if this serial is newer */ if(compare_serial(xfr->notify_serial, serial) < 0) xfr->notify_serial = serial; } else if(xfr->notify_received && xfr->notify_has_serial && !has_serial) { /* remove serial, we have notify without serial */ xfr->notify_has_serial = 0; xfr->notify_serial = 0; } else if(xfr->notify_received && !xfr->notify_has_serial) { /* we already have notify without serial, keep it * that way; no serial check when current operation * is done */ } else { xfr->notify_received = 1; xfr->notify_has_serial = has_serial; xfr->notify_serial = serial; } } /** process a notify serial, start new probe or note serial. xfr is locked */ static void xfr_process_notify(struct auth_xfer* xfr, struct module_env* env, int has_serial, uint32_t serial, struct auth_master* fromhost) { /* if the serial of notify is older than we have, don't fetch * a zone, we already have it */ if(has_serial && !xfr_serial_means_update(xfr, serial)) { lock_basic_unlock(&xfr->lock); return; } /* start new probe with this addr src, or note serial */ if(!xfr_start_probe(xfr, env, fromhost)) { /* not started because already in progress, note the serial */ xfr_note_notify_serial(xfr, has_serial, serial); lock_basic_unlock(&xfr->lock); } /* successful end of start_probe unlocked xfr->lock */ } int auth_zones_notify(struct auth_zones* az, struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t dclass, struct sockaddr_storage* addr, socklen_t addrlen, int has_serial, uint32_t serial, int* refused) { struct auth_xfer* xfr; struct auth_master* fromhost = NULL; /* see which zone this is */ lock_rw_rdlock(&az->lock); xfr = auth_xfer_find(az, nm, nmlen, dclass); if(!xfr) { lock_rw_unlock(&az->lock); /* no such zone, refuse the notify */ *refused = 1; return 0; } lock_basic_lock(&xfr->lock); lock_rw_unlock(&az->lock); /* check access list for notifies */ if(!az_xfr_allowed_notify(xfr, addr, addrlen, &fromhost)) { lock_basic_unlock(&xfr->lock); /* notify not allowed, refuse the notify */ *refused = 1; return 0; } /* process the notify */ xfr_process_notify(xfr, env, has_serial, serial, fromhost); return 1; } int auth_zones_startprobesequence(struct auth_zones* az, struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t dclass) { struct auth_xfer* xfr; lock_rw_rdlock(&az->lock); xfr = auth_xfer_find(az, nm, nmlen, dclass); if(!xfr) { lock_rw_unlock(&az->lock); return 0; } lock_basic_lock(&xfr->lock); lock_rw_unlock(&az->lock); xfr_process_notify(xfr, env, 0, 0, NULL); return 1; } /** set a zone expired */ static void auth_xfer_set_expired(struct auth_xfer* xfr, struct module_env* env, int expired) { struct auth_zone* z; /* expire xfr */ lock_basic_lock(&xfr->lock); xfr->zone_expired = expired; lock_basic_unlock(&xfr->lock); /* find auth_zone */ lock_rw_rdlock(&env->auth_zones->lock); z = auth_zone_find(env->auth_zones, xfr->name, xfr->namelen, xfr->dclass); if(!z) { lock_rw_unlock(&env->auth_zones->lock); return; } lock_rw_wrlock(&z->lock); lock_rw_unlock(&env->auth_zones->lock); /* expire auth_zone */ z->zone_expired = expired; lock_rw_unlock(&z->lock); } /** find master (from notify or probe) in list of masters */ static struct auth_master* find_master_by_host(struct auth_master* list, char* host) { struct auth_master* p; for(p=list; p; p=p->next) { if(strcmp(p->host, host) == 0) return p; } return NULL; } /** delete the looked up auth_addrs for all the masters in the list */ static void xfr_masterlist_free_addrs(struct auth_master* list) { struct auth_master* m; for(m=list; m; m=m->next) { if(m->list) { auth_free_master_addrs(m->list); m->list = NULL; } } } /** copy a list of auth_addrs */ static struct auth_addr* auth_addr_list_copy(struct auth_addr* source) { struct auth_addr* list = NULL, *last = NULL; struct auth_addr* p; for(p=source; p; p=p->next) { struct auth_addr* a = (struct auth_addr*)memdup(p, sizeof(*p)); if(!a) { log_err("malloc failure"); auth_free_master_addrs(list); return NULL; } a->next = NULL; if(last) last->next = a; if(!list) list = a; last = a; } return list; } /** copy a master to a new structure, NULL on alloc failure */ static struct auth_master* auth_master_copy(struct auth_master* o) { struct auth_master* m; if(!o) return NULL; m = (struct auth_master*)memdup(o, sizeof(*o)); if(!m) { log_err("malloc failure"); return NULL; } m->next = NULL; if(m->host) { m->host = strdup(m->host); if(!m->host) { free(m); log_err("malloc failure"); return NULL; } } if(m->file) { m->file = strdup(m->file); if(!m->file) { free(m->host); free(m); log_err("malloc failure"); return NULL; } } if(m->list) { m->list = auth_addr_list_copy(m->list); if(!m->list) { free(m->file); free(m->host); free(m); return NULL; } } return m; } /** copy the master addresses from the task_probe lookups to the allow_notify * list of masters */ static void probe_copy_masters_for_allow_notify(struct auth_xfer* xfr) { struct auth_master* list = NULL, *last = NULL; struct auth_master* p; /* build up new list with copies */ for(p = xfr->task_transfer->masters; p; p=p->next) { struct auth_master* m = auth_master_copy(p); if(!m) { auth_free_masters(list); /* failed because of malloc failure, use old list */ return; } m->next = NULL; if(last) last->next = m; if(!list) list = m; last = m; } /* success, replace list */ auth_free_masters(xfr->allow_notify_list); xfr->allow_notify_list = list; } /** start the lookups for task_transfer */ static void xfr_transfer_start_lookups(struct auth_xfer* xfr) { /* delete all the looked up addresses in the list */ xfr->task_transfer->scan_addr = NULL; xfr_masterlist_free_addrs(xfr->task_transfer->masters); /* start lookup at the first master */ xfr->task_transfer->lookup_target = xfr->task_transfer->masters; xfr->task_transfer->lookup_aaaa = 0; } /** move to the next lookup of hostname for task_transfer */ static void xfr_transfer_move_to_next_lookup(struct auth_xfer* xfr, struct module_env* env) { if(!xfr->task_transfer->lookup_target) return; /* already at end of list */ if(!xfr->task_transfer->lookup_aaaa && env->cfg->do_ip6) { /* move to lookup AAAA */ xfr->task_transfer->lookup_aaaa = 1; return; } xfr->task_transfer->lookup_target = xfr->task_transfer->lookup_target->next; xfr->task_transfer->lookup_aaaa = 0; if(!env->cfg->do_ip4 && xfr->task_transfer->lookup_target!=NULL) xfr->task_transfer->lookup_aaaa = 1; } /** start the lookups for task_probe */ static void xfr_probe_start_lookups(struct auth_xfer* xfr) { /* delete all the looked up addresses in the list */ xfr->task_probe->scan_addr = NULL; xfr_masterlist_free_addrs(xfr->task_probe->masters); /* start lookup at the first master */ xfr->task_probe->lookup_target = xfr->task_probe->masters; xfr->task_probe->lookup_aaaa = 0; } /** move to the next lookup of hostname for task_probe */ static void xfr_probe_move_to_next_lookup(struct auth_xfer* xfr, struct module_env* env) { if(!xfr->task_probe->lookup_target) return; /* already at end of list */ if(!xfr->task_probe->lookup_aaaa && env->cfg->do_ip6) { /* move to lookup AAAA */ xfr->task_probe->lookup_aaaa = 1; return; } xfr->task_probe->lookup_target = xfr->task_probe->lookup_target->next; xfr->task_probe->lookup_aaaa = 0; if(!env->cfg->do_ip4 && xfr->task_probe->lookup_target!=NULL) xfr->task_probe->lookup_aaaa = 1; } /** start the iteration of the task_transfer list of masters */ static void xfr_transfer_start_list(struct auth_xfer* xfr, struct auth_master* spec) { if(spec) { xfr->task_transfer->scan_specific = find_master_by_host( xfr->task_transfer->masters, spec->host); if(xfr->task_transfer->scan_specific) { xfr->task_transfer->scan_target = NULL; xfr->task_transfer->scan_addr = NULL; if(xfr->task_transfer->scan_specific->list) xfr->task_transfer->scan_addr = xfr->task_transfer->scan_specific->list; return; } } /* no specific (notified) host to scan */ xfr->task_transfer->scan_specific = NULL; xfr->task_transfer->scan_addr = NULL; /* pick up first scan target */ xfr->task_transfer->scan_target = xfr->task_transfer->masters; if(xfr->task_transfer->scan_target && xfr->task_transfer-> scan_target->list) xfr->task_transfer->scan_addr = xfr->task_transfer->scan_target->list; } /** start the iteration of the task_probe list of masters */ static void xfr_probe_start_list(struct auth_xfer* xfr, struct auth_master* spec) { if(spec) { xfr->task_probe->scan_specific = find_master_by_host( xfr->task_probe->masters, spec->host); if(xfr->task_probe->scan_specific) { xfr->task_probe->scan_target = NULL; xfr->task_probe->scan_addr = NULL; if(xfr->task_probe->scan_specific->list) xfr->task_probe->scan_addr = xfr->task_probe->scan_specific->list; return; } } /* no specific (notified) host to scan */ xfr->task_probe->scan_specific = NULL; xfr->task_probe->scan_addr = NULL; /* pick up first scan target */ xfr->task_probe->scan_target = xfr->task_probe->masters; if(xfr->task_probe->scan_target && xfr->task_probe->scan_target->list) xfr->task_probe->scan_addr = xfr->task_probe->scan_target->list; } /** pick up the master that is being scanned right now, task_transfer */ static struct auth_master* xfr_transfer_current_master(struct auth_xfer* xfr) { if(xfr->task_transfer->scan_specific) return xfr->task_transfer->scan_specific; return xfr->task_transfer->scan_target; } /** pick up the master that is being scanned right now, task_probe */ static struct auth_master* xfr_probe_current_master(struct auth_xfer* xfr) { if(xfr->task_probe->scan_specific) return xfr->task_probe->scan_specific; return xfr->task_probe->scan_target; } /** true if at end of list, task_transfer */ static int xfr_transfer_end_of_list(struct auth_xfer* xfr) { return !xfr->task_transfer->scan_specific && !xfr->task_transfer->scan_target; } /** true if at end of list, task_probe */ static int xfr_probe_end_of_list(struct auth_xfer* xfr) { return !xfr->task_probe->scan_specific && !xfr->task_probe->scan_target; } /** move to next master in list, task_transfer */ static void xfr_transfer_nextmaster(struct auth_xfer* xfr) { if(!xfr->task_transfer->scan_specific && !xfr->task_transfer->scan_target) return; if(xfr->task_transfer->scan_addr) { xfr->task_transfer->scan_addr = xfr->task_transfer->scan_addr->next; if(xfr->task_transfer->scan_addr) return; } if(xfr->task_transfer->scan_specific) { xfr->task_transfer->scan_specific = NULL; xfr->task_transfer->scan_target = xfr->task_transfer->masters; if(xfr->task_transfer->scan_target && xfr->task_transfer-> scan_target->list) xfr->task_transfer->scan_addr = xfr->task_transfer->scan_target->list; return; } if(!xfr->task_transfer->scan_target) return; xfr->task_transfer->scan_target = xfr->task_transfer->scan_target->next; if(xfr->task_transfer->scan_target && xfr->task_transfer-> scan_target->list) xfr->task_transfer->scan_addr = xfr->task_transfer->scan_target->list; return; } /** move to next master in list, task_probe */ static void xfr_probe_nextmaster(struct auth_xfer* xfr) { if(!xfr->task_probe->scan_specific && !xfr->task_probe->scan_target) return; if(xfr->task_probe->scan_addr) { xfr->task_probe->scan_addr = xfr->task_probe->scan_addr->next; if(xfr->task_probe->scan_addr) return; } if(xfr->task_probe->scan_specific) { xfr->task_probe->scan_specific = NULL; xfr->task_probe->scan_target = xfr->task_probe->masters; if(xfr->task_probe->scan_target && xfr->task_probe-> scan_target->list) xfr->task_probe->scan_addr = xfr->task_probe->scan_target->list; return; } if(!xfr->task_probe->scan_target) return; xfr->task_probe->scan_target = xfr->task_probe->scan_target->next; if(xfr->task_probe->scan_target && xfr->task_probe-> scan_target->list) xfr->task_probe->scan_addr = xfr->task_probe->scan_target->list; return; } /** create SOA probe packet for xfr */ static void xfr_create_soa_probe_packet(struct auth_xfer* xfr, sldns_buffer* buf, uint16_t id) { struct query_info qinfo; memset(&qinfo, 0, sizeof(qinfo)); qinfo.qname = xfr->name; qinfo.qname_len = xfr->namelen; qinfo.qtype = LDNS_RR_TYPE_SOA; qinfo.qclass = xfr->dclass; qinfo_query_encode(buf, &qinfo); sldns_buffer_write_u16_at(buf, 0, id); } /** create IXFR/AXFR packet for xfr */ static void xfr_create_ixfr_packet(struct auth_xfer* xfr, sldns_buffer* buf, uint16_t id, struct auth_master* master) { struct query_info qinfo; uint32_t serial; int have_zone; have_zone = xfr->have_zone; serial = xfr->serial; memset(&qinfo, 0, sizeof(qinfo)); qinfo.qname = xfr->name; qinfo.qname_len = xfr->namelen; xfr->task_transfer->got_xfr_serial = 0; xfr->task_transfer->rr_scan_num = 0; xfr->task_transfer->incoming_xfr_serial = 0; xfr->task_transfer->on_ixfr_is_axfr = 0; xfr->task_transfer->on_ixfr = 1; qinfo.qtype = LDNS_RR_TYPE_IXFR; if(!have_zone || xfr->task_transfer->ixfr_fail || !master->ixfr) { qinfo.qtype = LDNS_RR_TYPE_AXFR; xfr->task_transfer->ixfr_fail = 0; xfr->task_transfer->on_ixfr = 0; } qinfo.qclass = xfr->dclass; qinfo_query_encode(buf, &qinfo); sldns_buffer_write_u16_at(buf, 0, id); /* append serial for IXFR */ if(qinfo.qtype == LDNS_RR_TYPE_IXFR) { size_t end = sldns_buffer_limit(buf); sldns_buffer_clear(buf); sldns_buffer_set_position(buf, end); /* auth section count 1 */ sldns_buffer_write_u16_at(buf, LDNS_NSCOUNT_OFF, 1); /* write SOA */ sldns_buffer_write_u8(buf, 0xC0); /* compressed ptr to qname */ sldns_buffer_write_u8(buf, 0x0C); sldns_buffer_write_u16(buf, LDNS_RR_TYPE_SOA); sldns_buffer_write_u16(buf, qinfo.qclass); sldns_buffer_write_u32(buf, 0); /* ttl */ sldns_buffer_write_u16(buf, 22); /* rdata length */ sldns_buffer_write_u8(buf, 0); /* . */ sldns_buffer_write_u8(buf, 0); /* . */ sldns_buffer_write_u32(buf, serial); /* serial */ sldns_buffer_write_u32(buf, 0); /* refresh */ sldns_buffer_write_u32(buf, 0); /* retry */ sldns_buffer_write_u32(buf, 0); /* expire */ sldns_buffer_write_u32(buf, 0); /* minimum */ sldns_buffer_flip(buf); } } /** check if returned packet is OK */ static int check_packet_ok(sldns_buffer* pkt, uint16_t qtype, struct auth_xfer* xfr, uint32_t* serial) { /* parse to see if packet worked, valid reply */ /* check serial number of SOA */ if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) return 0; /* check ID */ if(LDNS_ID_WIRE(sldns_buffer_begin(pkt)) != xfr->task_probe->id) return 0; /* check flag bits and rcode */ if(!LDNS_QR_WIRE(sldns_buffer_begin(pkt))) return 0; if(LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_PACKET_QUERY) return 0; if(LDNS_RCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_RCODE_NOERROR) return 0; /* check qname */ if(LDNS_QDCOUNT(sldns_buffer_begin(pkt)) != 1) return 0; sldns_buffer_skip(pkt, LDNS_HEADER_SIZE); if(sldns_buffer_remaining(pkt) < xfr->namelen) return 0; if(query_dname_compare(sldns_buffer_current(pkt), xfr->name) != 0) return 0; sldns_buffer_skip(pkt, (ssize_t)xfr->namelen); /* check qtype, qclass */ if(sldns_buffer_remaining(pkt) < 4) return 0; if(sldns_buffer_read_u16(pkt) != qtype) return 0; if(sldns_buffer_read_u16(pkt) != xfr->dclass) return 0; if(serial) { uint16_t rdlen; /* read serial number, from answer section SOA */ if(LDNS_ANCOUNT(sldns_buffer_begin(pkt)) == 0) return 0; /* read from first record SOA record */ if(sldns_buffer_remaining(pkt) < 1) return 0; if(dname_pkt_compare(pkt, sldns_buffer_current(pkt), xfr->name) != 0) return 0; if(!pkt_dname_len(pkt)) return 0; /* type, class, ttl, rdatalen */ if(sldns_buffer_remaining(pkt) < 4+4+2) return 0; if(sldns_buffer_read_u16(pkt) != qtype) return 0; if(sldns_buffer_read_u16(pkt) != xfr->dclass) return 0; sldns_buffer_skip(pkt, 4); /* ttl */ rdlen = sldns_buffer_read_u16(pkt); if(sldns_buffer_remaining(pkt) < rdlen) return 0; if(sldns_buffer_remaining(pkt) < 1) return 0; if(!pkt_dname_len(pkt)) /* soa name */ return 0; if(sldns_buffer_remaining(pkt) < 1) return 0; if(!pkt_dname_len(pkt)) /* soa name */ return 0; if(sldns_buffer_remaining(pkt) < 20) return 0; *serial = sldns_buffer_read_u32(pkt); } return 1; } /** read one line from chunks into buffer at current position */ static int chunkline_get_line(struct auth_chunk** chunk, size_t* chunk_pos, sldns_buffer* buf) { int readsome = 0; while(*chunk) { /* more text in this chunk? */ if(*chunk_pos < (*chunk)->len) { readsome = 1; while(*chunk_pos < (*chunk)->len) { char c = (char)((*chunk)->data[*chunk_pos]); (*chunk_pos)++; if(sldns_buffer_remaining(buf) < 2) { /* buffer too short */ verbose(VERB_ALGO, "http chunkline, " "line too long"); return 0; } sldns_buffer_write_u8(buf, (uint8_t)c); if(c == '\n') { /* we are done */ return 1; } } } /* move to next chunk */ *chunk = (*chunk)->next; *chunk_pos = 0; } /* no more text */ if(readsome) return 1; return 0; } /** count number of open and closed parenthesis in a chunkline */ static int chunkline_count_parens(sldns_buffer* buf, size_t start) { size_t end = sldns_buffer_position(buf); size_t i; int count = 0; int squote = 0, dquote = 0; for(i=start; i 0) { chunkline_remove_trailcomment(buf, pos); pos = sldns_buffer_position(buf); if(!chunkline_get_line(chunk, chunk_pos, buf)) { if(sldns_buffer_position(buf) < sldns_buffer_limit(buf)) sldns_buffer_write_u8_at(buf, sldns_buffer_position(buf), 0); else sldns_buffer_write_u8_at(buf, sldns_buffer_position(buf)-1, 0); sldns_buffer_flip(buf); return 0; } parens += chunkline_count_parens(buf, pos); } if(sldns_buffer_remaining(buf) < 1) { verbose(VERB_ALGO, "http chunkline: " "line too long"); return 0; } sldns_buffer_write_u8_at(buf, sldns_buffer_position(buf), 0); sldns_buffer_flip(buf); return 1; } /** process $ORIGIN for http, 0 nothing, 1 done, 2 error */ static int http_parse_origin(sldns_buffer* buf, struct sldns_file_parse_state* pstate) { char* line = (char*)sldns_buffer_begin(buf); if(strncmp(line, "$ORIGIN", 7) == 0 && isspace((unsigned char)line[7])) { int s; pstate->origin_len = sizeof(pstate->origin); s = sldns_str2wire_dname_buf(sldns_strip_ws(line+8), pstate->origin, &pstate->origin_len); if(s) { pstate->origin_len = 0; return 2; } return 1; } return 0; } /** process $TTL for http, 0 nothing, 1 done, 2 error */ static int http_parse_ttl(sldns_buffer* buf, struct sldns_file_parse_state* pstate) { char* line = (char*)sldns_buffer_begin(buf); if(strncmp(line, "$TTL", 4) == 0 && isspace((unsigned char)line[4])) { const char* end = NULL; int overflow = 0; pstate->default_ttl = sldns_str2period( sldns_strip_ws(line+5), &end, &overflow); if(overflow) { return 2; } return 1; } return 0; } /** find noncomment RR line in chunks, collates lines if ( ) format */ static int chunkline_non_comment_RR(struct auth_chunk** chunk, size_t* chunk_pos, sldns_buffer* buf, struct sldns_file_parse_state* pstate) { int ret; while(chunkline_get_line_collated(chunk, chunk_pos, buf)) { if(chunkline_is_comment_line_or_empty(buf)) { /* a comment, go to next line */ continue; } if((ret=http_parse_origin(buf, pstate))!=0) { if(ret == 2) return 0; continue; /* $ORIGIN has been handled */ } if((ret=http_parse_ttl(buf, pstate))!=0) { if(ret == 2) return 0; continue; /* $TTL has been handled */ } return 1; } /* no noncomments, fail */ return 0; } /** check syntax of chunklist zonefile, parse first RR, return false on * failure and return a string in the scratch buffer (first RR string) * on failure. */ static int http_zonefile_syntax_check(struct auth_xfer* xfr, sldns_buffer* buf) { uint8_t rr[LDNS_RR_BUF_SIZE]; size_t rr_len, dname_len = 0; struct sldns_file_parse_state pstate; struct auth_chunk* chunk; size_t chunk_pos; int e; memset(&pstate, 0, sizeof(pstate)); pstate.default_ttl = 3600; if(xfr->namelen < sizeof(pstate.origin)) { pstate.origin_len = xfr->namelen; memmove(pstate.origin, xfr->name, xfr->namelen); } chunk = xfr->task_transfer->chunks_first; chunk_pos = 0; if(!chunkline_non_comment_RR(&chunk, &chunk_pos, buf, &pstate)) { return 0; } rr_len = sizeof(rr); e=sldns_str2wire_rr_buf((char*)sldns_buffer_begin(buf), rr, &rr_len, &dname_len, pstate.default_ttl, pstate.origin_len?pstate.origin:NULL, pstate.origin_len, pstate.prev_rr_len?pstate.prev_rr:NULL, pstate.prev_rr_len); if(e != 0) { log_err("parse failure on first RR[%d]: %s", LDNS_WIREPARSE_OFFSET(e), sldns_get_errorstr_parse(LDNS_WIREPARSE_ERROR(e))); return 0; } /* check that class is correct */ if(sldns_wirerr_get_class(rr, rr_len, dname_len) != xfr->dclass) { log_err("parse failure: first record in downloaded zonefile " "from wrong RR class"); return 0; } return 1; } /** sum sizes of chunklist */ static size_t chunklist_sum(struct auth_chunk* list) { struct auth_chunk* p; size_t s = 0; for(p=list; p; p=p->next) { s += p->len; } return s; } /** remove newlines from collated line */ static void chunkline_newline_removal(sldns_buffer* buf) { size_t i, end=sldns_buffer_limit(buf); for(i=0; idefault_ttl, pstate->origin_len?pstate->origin:NULL, pstate->origin_len, pstate->prev_rr_len?pstate->prev_rr:NULL, pstate->prev_rr_len); if(e != 0) { log_err("%s/%s parse failure RR[%d]: %s in '%s'", xfr->task_transfer->master->host, xfr->task_transfer->master->file, LDNS_WIREPARSE_OFFSET(e), sldns_get_errorstr_parse(LDNS_WIREPARSE_ERROR(e)), line); return 0; } if(rr_len == 0) return 1; /* empty line or so */ /* set prev */ if(dname_len < sizeof(pstate->prev_rr)) { memmove(pstate->prev_rr, rr, dname_len); pstate->prev_rr_len = dname_len; } return az_insert_rr(z, rr, rr_len, dname_len, NULL); } /** RR list iterator, returns RRs from answer section one by one from the * dns packets in the chunklist */ static void chunk_rrlist_start(struct auth_xfer* xfr, struct auth_chunk** rr_chunk, int* rr_num, size_t* rr_pos) { *rr_chunk = xfr->task_transfer->chunks_first; *rr_num = 0; *rr_pos = 0; } /** RR list iterator, see if we are at the end of the list */ static int chunk_rrlist_end(struct auth_chunk* rr_chunk, int rr_num) { while(rr_chunk) { if(rr_chunk->len < LDNS_HEADER_SIZE) return 1; if(rr_num < (int)LDNS_ANCOUNT(rr_chunk->data)) return 0; /* no more RRs in this chunk */ /* continue with next chunk, see if it has RRs */ rr_chunk = rr_chunk->next; rr_num = 0; } return 1; } /** RR list iterator, move to next RR */ static void chunk_rrlist_gonext(struct auth_chunk** rr_chunk, int* rr_num, size_t* rr_pos, size_t rr_nextpos) { /* already at end of chunks? */ if(!*rr_chunk) return; /* move within this chunk */ if((*rr_chunk)->len >= LDNS_HEADER_SIZE && (*rr_num)+1 < (int)LDNS_ANCOUNT((*rr_chunk)->data)) { (*rr_num) += 1; *rr_pos = rr_nextpos; return; } /* no more RRs in this chunk */ /* continue with next chunk, see if it has RRs */ if(*rr_chunk) *rr_chunk = (*rr_chunk)->next; while(*rr_chunk) { *rr_num = 0; *rr_pos = 0; if((*rr_chunk)->len >= LDNS_HEADER_SIZE && LDNS_ANCOUNT((*rr_chunk)->data) > 0) { return; } *rr_chunk = (*rr_chunk)->next; } } /** RR iterator, get current RR information, false on parse error */ static int chunk_rrlist_get_current(struct auth_chunk* rr_chunk, int rr_num, size_t rr_pos, uint8_t** rr_dname, uint16_t* rr_type, uint16_t* rr_class, uint32_t* rr_ttl, uint16_t* rr_rdlen, uint8_t** rr_rdata, size_t* rr_nextpos) { sldns_buffer pkt; /* integrity checks on position */ if(!rr_chunk) return 0; if(rr_chunk->len < LDNS_HEADER_SIZE) return 0; if(rr_num >= (int)LDNS_ANCOUNT(rr_chunk->data)) return 0; if(rr_pos >= rr_chunk->len) return 0; /* fetch rr information */ sldns_buffer_init_frm_data(&pkt, rr_chunk->data, rr_chunk->len); if(rr_pos == 0) { size_t i; /* skip question section */ sldns_buffer_set_position(&pkt, LDNS_HEADER_SIZE); for(i=0; idata); i++) { if(pkt_dname_len(&pkt) == 0) return 0; if(sldns_buffer_remaining(&pkt) < 4) return 0; sldns_buffer_skip(&pkt, 4); /* type and class */ } } else { sldns_buffer_set_position(&pkt, rr_pos); } *rr_dname = sldns_buffer_current(&pkt); if(pkt_dname_len(&pkt) == 0) return 0; if(sldns_buffer_remaining(&pkt) < 10) return 0; *rr_type = sldns_buffer_read_u16(&pkt); *rr_class = sldns_buffer_read_u16(&pkt); *rr_ttl = sldns_buffer_read_u32(&pkt); *rr_rdlen = sldns_buffer_read_u16(&pkt); if(sldns_buffer_remaining(&pkt) < (*rr_rdlen)) return 0; *rr_rdata = sldns_buffer_current(&pkt); sldns_buffer_skip(&pkt, (ssize_t)(*rr_rdlen)); *rr_nextpos = sldns_buffer_position(&pkt); return 1; } /** print log message where we are in parsing the zone transfer */ static void log_rrlist_position(const char* label, struct auth_chunk* rr_chunk, uint8_t* rr_dname, uint16_t rr_type, size_t rr_counter) { sldns_buffer pkt; size_t dlen; uint8_t buf[256]; char str[256]; char typestr[32]; sldns_buffer_init_frm_data(&pkt, rr_chunk->data, rr_chunk->len); sldns_buffer_set_position(&pkt, (size_t)(rr_dname - sldns_buffer_begin(&pkt))); if((dlen=pkt_dname_len(&pkt)) == 0) return; if(dlen >= sizeof(buf)) return; dname_pkt_copy(&pkt, buf, rr_dname); dname_str(buf, str); (void)sldns_wire2str_type_buf(rr_type, typestr, sizeof(typestr)); verbose(VERB_ALGO, "%s at[%d] %s %s", label, (int)rr_counter, str, typestr); } /** check that start serial is OK for ixfr. we are at rr_counter == 0, * and we are going to check rr_counter == 1 (has to be type SOA) serial */ static int ixfr_start_serial(struct auth_chunk* rr_chunk, int rr_num, size_t rr_pos, uint8_t* rr_dname, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, uint16_t rr_rdlen, uint8_t* rr_rdata, size_t rr_nextpos, uint32_t transfer_serial, uint32_t xfr_serial) { uint32_t startserial; /* move forward on RR */ chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos); if(chunk_rrlist_end(rr_chunk, rr_num)) { /* no second SOA */ verbose(VERB_OPS, "IXFR has no second SOA record"); return 0; } if(!chunk_rrlist_get_current(rr_chunk, rr_num, rr_pos, &rr_dname, &rr_type, &rr_class, &rr_ttl, &rr_rdlen, &rr_rdata, &rr_nextpos)) { verbose(VERB_OPS, "IXFR cannot parse second SOA record"); /* failed to parse RR */ return 0; } if(rr_type != LDNS_RR_TYPE_SOA) { verbose(VERB_OPS, "IXFR second record is not type SOA"); return 0; } if(rr_rdlen < 22) { verbose(VERB_OPS, "IXFR, second SOA has short rdlength"); return 0; /* bad SOA rdlen */ } startserial = sldns_read_uint32(rr_rdata+rr_rdlen-20); if(startserial == transfer_serial) { /* empty AXFR, not an IXFR */ verbose(VERB_OPS, "IXFR second serial same as first"); return 0; } if(startserial != xfr_serial) { /* wrong start serial, it does not match the serial in * memory */ verbose(VERB_OPS, "IXFR is from serial %u to %u but %u " "in memory, rejecting the zone transfer", (unsigned)startserial, (unsigned)transfer_serial, (unsigned)xfr_serial); return 0; } /* everything OK in second SOA serial */ return 1; } /** apply IXFR to zone in memory. z is locked. false on failure(mallocfail) */ static int apply_ixfr(struct auth_xfer* xfr, struct auth_zone* z, struct sldns_buffer* scratch_buffer) { struct auth_chunk* rr_chunk; int rr_num; size_t rr_pos; uint8_t* rr_dname, *rr_rdata; uint16_t rr_type, rr_class, rr_rdlen; uint32_t rr_ttl; size_t rr_nextpos; int have_transfer_serial = 0; uint32_t transfer_serial = 0; size_t rr_counter = 0; int delmode = 0; int softfail = 0; /* start RR iterator over chunklist of packets */ chunk_rrlist_start(xfr, &rr_chunk, &rr_num, &rr_pos); while(!chunk_rrlist_end(rr_chunk, rr_num)) { if(!chunk_rrlist_get_current(rr_chunk, rr_num, rr_pos, &rr_dname, &rr_type, &rr_class, &rr_ttl, &rr_rdlen, &rr_rdata, &rr_nextpos)) { /* failed to parse RR */ return 0; } if(verbosity>=7) log_rrlist_position("apply ixfr", rr_chunk, rr_dname, rr_type, rr_counter); /* twiddle add/del mode and check for start and end */ if(rr_counter == 0 && rr_type != LDNS_RR_TYPE_SOA) return 0; if(rr_counter == 1 && rr_type != LDNS_RR_TYPE_SOA) { /* this is an AXFR returned from the IXFR master */ /* but that should already have been detected, by * on_ixfr_is_axfr */ return 0; } if(rr_type == LDNS_RR_TYPE_SOA) { uint32_t serial; if(rr_rdlen < 22) return 0; /* bad SOA rdlen */ serial = sldns_read_uint32(rr_rdata+rr_rdlen-20); if(have_transfer_serial == 0) { have_transfer_serial = 1; transfer_serial = serial; delmode = 1; /* gets negated below */ /* check second RR before going any further */ if(!ixfr_start_serial(rr_chunk, rr_num, rr_pos, rr_dname, rr_type, rr_class, rr_ttl, rr_rdlen, rr_rdata, rr_nextpos, transfer_serial, xfr->serial)) { return 0; } } else if(transfer_serial == serial) { have_transfer_serial++; if(rr_counter == 1) { /* empty AXFR, with SOA; SOA; */ /* should have been detected by * on_ixfr_is_axfr */ return 0; } if(have_transfer_serial == 3) { /* see serial three times for end */ /* eg. IXFR: * SOA 3 start * SOA 1 second RR, followed by del * SOA 2 followed by add * SOA 2 followed by del * SOA 3 followed by add * SOA 3 end */ /* ended by SOA record */ xfr->serial = transfer_serial; break; } } /* twiddle add/del mode */ /* switch from delete part to add part and back again * just before the soa, it gets deleted and added too * this means we switch to delete mode for the final * SOA(so skip that one) */ delmode = !delmode; } /* process this RR */ /* if the RR is deleted twice or added twice, then we * softfail, and continue with the rest of the IXFR, so * that we serve something fairly nice during the refetch */ if(verbosity>=7) log_rrlist_position((delmode?"del":"add"), rr_chunk, rr_dname, rr_type, rr_counter); if(delmode) { /* delete this RR */ int nonexist = 0; if(!az_remove_rr_decompress(z, rr_chunk->data, rr_chunk->len, scratch_buffer, rr_dname, rr_type, rr_class, rr_ttl, rr_rdata, rr_rdlen, &nonexist)) { /* failed, malloc error or so */ return 0; } if(nonexist) { /* it was removal of a nonexisting RR */ if(verbosity>=4) log_rrlist_position( "IXFR error nonexistent RR", rr_chunk, rr_dname, rr_type, rr_counter); softfail = 1; } } else if(rr_counter != 0) { /* skip first SOA RR for addition, it is added in * the addition part near the end of the ixfr, when * that serial is seen the second time. */ int duplicate = 0; /* add this RR */ if(!az_insert_rr_decompress(z, rr_chunk->data, rr_chunk->len, scratch_buffer, rr_dname, rr_type, rr_class, rr_ttl, rr_rdata, rr_rdlen, &duplicate)) { /* failed, malloc error or so */ return 0; } if(duplicate) { /* it was a duplicate */ if(verbosity>=4) log_rrlist_position( "IXFR error duplicate RR", rr_chunk, rr_dname, rr_type, rr_counter); softfail = 1; } } rr_counter++; chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos); } if(softfail) { verbose(VERB_ALGO, "IXFR did not apply cleanly, fetching full zone"); return 0; } return 1; } /** apply AXFR to zone in memory. z is locked. false on failure(mallocfail) */ static int apply_axfr(struct auth_xfer* xfr, struct auth_zone* z, struct sldns_buffer* scratch_buffer) { struct auth_chunk* rr_chunk; int rr_num; size_t rr_pos; uint8_t* rr_dname, *rr_rdata; uint16_t rr_type, rr_class, rr_rdlen; uint32_t rr_ttl; uint32_t serial = 0; size_t rr_nextpos; size_t rr_counter = 0; int have_end_soa = 0; /* clear the data tree */ traverse_postorder(&z->data, auth_data_del, NULL); rbtree_init(&z->data, &auth_data_cmp); /* clear the RPZ policies */ if(z->rpz) rpz_clear(z->rpz); xfr->have_zone = 0; xfr->serial = 0; /* insert all RRs in to the zone */ /* insert the SOA only once, skip the last one */ /* start RR iterator over chunklist of packets */ chunk_rrlist_start(xfr, &rr_chunk, &rr_num, &rr_pos); while(!chunk_rrlist_end(rr_chunk, rr_num)) { if(!chunk_rrlist_get_current(rr_chunk, rr_num, rr_pos, &rr_dname, &rr_type, &rr_class, &rr_ttl, &rr_rdlen, &rr_rdata, &rr_nextpos)) { /* failed to parse RR */ return 0; } if(verbosity>=7) log_rrlist_position("apply_axfr", rr_chunk, rr_dname, rr_type, rr_counter); if(rr_type == LDNS_RR_TYPE_SOA) { if(rr_counter != 0) { /* end of the axfr */ have_end_soa = 1; break; } if(rr_rdlen < 22) return 0; /* bad SOA rdlen */ serial = sldns_read_uint32(rr_rdata+rr_rdlen-20); } /* add this RR */ if(!az_insert_rr_decompress(z, rr_chunk->data, rr_chunk->len, scratch_buffer, rr_dname, rr_type, rr_class, rr_ttl, rr_rdata, rr_rdlen, NULL)) { /* failed, malloc error or so */ return 0; } rr_counter++; chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos); } if(!have_end_soa) { log_err("no end SOA record for AXFR"); return 0; } xfr->serial = serial; xfr->have_zone = 1; return 1; } /** apply HTTP to zone in memory. z is locked. false on failure(mallocfail) */ static int apply_http(struct auth_xfer* xfr, struct auth_zone* z, struct sldns_buffer* scratch_buffer) { /* parse data in chunks */ /* parse RR's and read into memory. ignore $INCLUDE from the * downloaded file*/ struct sldns_file_parse_state pstate; struct auth_chunk* chunk; size_t chunk_pos; int ret; memset(&pstate, 0, sizeof(pstate)); pstate.default_ttl = 3600; if(xfr->namelen < sizeof(pstate.origin)) { pstate.origin_len = xfr->namelen; memmove(pstate.origin, xfr->name, xfr->namelen); } if(verbosity >= VERB_ALGO) verbose(VERB_ALGO, "http download %s of size %d", xfr->task_transfer->master->file, (int)chunklist_sum(xfr->task_transfer->chunks_first)); if(xfr->task_transfer->chunks_first && verbosity >= VERB_ALGO) { char preview[1024]; if(xfr->task_transfer->chunks_first->len+1 > sizeof(preview)) { memmove(preview, xfr->task_transfer->chunks_first->data, sizeof(preview)-1); preview[sizeof(preview)-1]=0; } else { memmove(preview, xfr->task_transfer->chunks_first->data, xfr->task_transfer->chunks_first->len); preview[xfr->task_transfer->chunks_first->len]=0; } log_info("auth zone http downloaded content preview: %s", preview); } /* perhaps a little syntax check before we try to apply the data? */ if(!http_zonefile_syntax_check(xfr, scratch_buffer)) { log_err("http download %s/%s does not contain a zonefile, " "but got '%s'", xfr->task_transfer->master->host, xfr->task_transfer->master->file, sldns_buffer_begin(scratch_buffer)); return 0; } /* clear the data tree */ traverse_postorder(&z->data, auth_data_del, NULL); rbtree_init(&z->data, &auth_data_cmp); /* clear the RPZ policies */ if(z->rpz) rpz_clear(z->rpz); xfr->have_zone = 0; xfr->serial = 0; chunk = xfr->task_transfer->chunks_first; chunk_pos = 0; pstate.lineno = 0; while(chunkline_get_line_collated(&chunk, &chunk_pos, scratch_buffer)) { /* process this line */ pstate.lineno++; chunkline_newline_removal(scratch_buffer); if(chunkline_is_comment_line_or_empty(scratch_buffer)) { continue; } /* parse line and add RR */ if((ret=http_parse_origin(scratch_buffer, &pstate))!=0) { if(ret == 2) { verbose(VERB_ALGO, "error parsing ORIGIN on line [%s:%d] %s", xfr->task_transfer->master->file, pstate.lineno, sldns_buffer_begin(scratch_buffer)); return 0; } continue; /* $ORIGIN has been handled */ } if((ret=http_parse_ttl(scratch_buffer, &pstate))!=0) { if(ret == 2) { verbose(VERB_ALGO, "error parsing TTL on line [%s:%d] %s", xfr->task_transfer->master->file, pstate.lineno, sldns_buffer_begin(scratch_buffer)); return 0; } continue; /* $TTL has been handled */ } if(!http_parse_add_rr(xfr, z, scratch_buffer, &pstate)) { verbose(VERB_ALGO, "error parsing line [%s:%d] %s", xfr->task_transfer->master->file, pstate.lineno, sldns_buffer_begin(scratch_buffer)); return 0; } } return 1; } /** write http chunks to zonefile to create downloaded file */ static int auth_zone_write_chunks(struct auth_xfer* xfr, const char* fname) { FILE* out; struct auth_chunk* p; out = fopen(fname, "w"); if(!out) { log_err("could not open %s: %s", fname, strerror(errno)); return 0; } for(p = xfr->task_transfer->chunks_first; p ; p = p->next) { if(!write_out(out, (char*)p->data, p->len)) { log_err("could not write http download to %s", fname); fclose(out); return 0; } } fclose(out); return 1; } /** write to zonefile after zone has been updated */ static void xfr_write_after_update(struct auth_xfer* xfr, struct module_env* env) { struct config_file* cfg = env->cfg; struct auth_zone* z; char tmpfile[1024]; char* zfilename; lock_basic_unlock(&xfr->lock); /* get lock again, so it is a readlock and concurrently queries * can be answered */ lock_rw_rdlock(&env->auth_zones->lock); z = auth_zone_find(env->auth_zones, xfr->name, xfr->namelen, xfr->dclass); if(!z) { lock_rw_unlock(&env->auth_zones->lock); /* the zone is gone, ignore xfr results */ lock_basic_lock(&xfr->lock); return; } lock_rw_rdlock(&z->lock); lock_basic_lock(&xfr->lock); lock_rw_unlock(&env->auth_zones->lock); if(z->zonefile == NULL || z->zonefile[0] == 0) { lock_rw_unlock(&z->lock); /* no write needed, no zonefile set */ return; } zfilename = z->zonefile; if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(zfilename, cfg->chrootdir, strlen(cfg->chrootdir)) == 0) zfilename += strlen(cfg->chrootdir); if(verbosity >= VERB_ALGO) { char nm[255+1]; dname_str(z->name, nm); verbose(VERB_ALGO, "write zonefile %s for %s", zfilename, nm); } /* write to tempfile first */ if((size_t)strlen(zfilename) + 16 > sizeof(tmpfile)) { verbose(VERB_ALGO, "tmpfilename too long, cannot update " " zonefile %s", zfilename); lock_rw_unlock(&z->lock); return; } snprintf(tmpfile, sizeof(tmpfile), "%s.tmp%u", zfilename, (unsigned)getpid()); if(xfr->task_transfer->master->http) { /* use the stored chunk list to write them */ if(!auth_zone_write_chunks(xfr, tmpfile)) { unlink(tmpfile); lock_rw_unlock(&z->lock); return; } } else if(!auth_zone_write_file(z, tmpfile)) { unlink(tmpfile); lock_rw_unlock(&z->lock); return; } #ifdef UB_ON_WINDOWS (void)unlink(zfilename); /* windows does not replace file with rename() */ #endif if(rename(tmpfile, zfilename) < 0) { log_err("could not rename(%s, %s): %s", tmpfile, zfilename, strerror(errno)); unlink(tmpfile); lock_rw_unlock(&z->lock); return; } lock_rw_unlock(&z->lock); } /** reacquire locks and structures. Starts with no locks, ends * with xfr and z locks, if fail, no z lock */ static int xfr_process_reacquire_locks(struct auth_xfer* xfr, struct module_env* env, struct auth_zone** z) { /* release xfr lock, then, while holding az->lock grab both * z->lock and xfr->lock */ lock_rw_rdlock(&env->auth_zones->lock); *z = auth_zone_find(env->auth_zones, xfr->name, xfr->namelen, xfr->dclass); if(!*z) { lock_rw_unlock(&env->auth_zones->lock); lock_basic_lock(&xfr->lock); *z = NULL; return 0; } lock_rw_wrlock(&(*z)->lock); lock_basic_lock(&xfr->lock); lock_rw_unlock(&env->auth_zones->lock); return 1; } /** process chunk list and update zone in memory, * return false if it did not work */ static int xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env, int* ixfr_fail) { struct auth_zone* z; /* obtain locks and structures */ lock_basic_unlock(&xfr->lock); if(!xfr_process_reacquire_locks(xfr, env, &z)) { /* the zone is gone, ignore xfr results */ return 0; } /* holding xfr and z locks */ /* apply data */ if(xfr->task_transfer->master->http) { if(!apply_http(xfr, z, env->scratch_buffer)) { lock_rw_unlock(&z->lock); verbose(VERB_ALGO, "http from %s: could not store data", xfr->task_transfer->master->host); return 0; } } else if(xfr->task_transfer->on_ixfr && !xfr->task_transfer->on_ixfr_is_axfr) { if(!apply_ixfr(xfr, z, env->scratch_buffer)) { lock_rw_unlock(&z->lock); verbose(VERB_ALGO, "xfr from %s: could not store IXFR" " data", xfr->task_transfer->master->host); *ixfr_fail = 1; return 0; } } else { if(!apply_axfr(xfr, z, env->scratch_buffer)) { lock_rw_unlock(&z->lock); verbose(VERB_ALGO, "xfr from %s: could not store AXFR" " data", xfr->task_transfer->master->host); return 0; } } xfr->zone_expired = 0; z->zone_expired = 0; if(!xfr_find_soa(z, xfr)) { lock_rw_unlock(&z->lock); verbose(VERB_ALGO, "xfr from %s: no SOA in zone after update" " (or malformed RR)", xfr->task_transfer->master->host); return 0; } /* release xfr lock while verifying zonemd because it may have * to spawn lookups in the state machines */ lock_basic_unlock(&xfr->lock); /* holding z lock */ auth_zone_verify_zonemd(z, env, &env->mesh->mods, NULL, 0, 0); if(z->zone_expired) { char zname[256]; /* ZONEMD must have failed */ /* reacquire locks, so we hold xfr lock on exit of routine, * and both xfr and z again after releasing xfr for potential * state machine mesh callbacks */ lock_rw_unlock(&z->lock); if(!xfr_process_reacquire_locks(xfr, env, &z)) return 0; dname_str(xfr->name, zname); verbose(VERB_ALGO, "xfr from %s: ZONEMD failed for %s, transfer is failed", xfr->task_transfer->master->host, zname); xfr->zone_expired = 1; lock_rw_unlock(&z->lock); return 0; } /* reacquire locks, so we hold xfr lock on exit of routine, * and both xfr and z again after releasing xfr for potential * state machine mesh callbacks */ lock_rw_unlock(&z->lock); if(!xfr_process_reacquire_locks(xfr, env, &z)) return 0; /* holding xfr and z locks */ if(xfr->have_zone) xfr->lease_time = *env->now; if(z->rpz) rpz_finish_config(z->rpz); /* unlock */ lock_rw_unlock(&z->lock); if(verbosity >= VERB_QUERY && xfr->have_zone) { char zname[256]; dname_str(xfr->name, zname); verbose(VERB_QUERY, "auth zone %s updated to serial %u", zname, (unsigned)xfr->serial); } /* see if we need to write to a zonefile */ xfr_write_after_update(xfr, env); return 1; } /** disown task_transfer. caller must hold xfr.lock */ static void xfr_transfer_disown(struct auth_xfer* xfr) { /* remove timer (from this worker's event base) */ comm_timer_delete(xfr->task_transfer->timer); xfr->task_transfer->timer = NULL; /* remove the commpoint */ comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; /* we don't own this item anymore */ xfr->task_transfer->worker = NULL; xfr->task_transfer->env = NULL; } /** lookup a host name for its addresses, if needed */ static int xfr_transfer_lookup_host(struct auth_xfer* xfr, struct module_env* env) { struct sockaddr_storage addr; socklen_t addrlen = 0; struct auth_master* master = xfr->task_transfer->lookup_target; struct query_info qinfo; uint16_t qflags = BIT_RD; uint8_t dname[LDNS_MAX_DOMAINLEN+1]; struct edns_data edns; sldns_buffer* buf = env->scratch_buffer; if(!master) return 0; if(extstrtoaddr(master->host, &addr, &addrlen)) { /* not needed, host is in IP addr format */ return 0; } if(master->allow_notify) return 0; /* allow-notifies are not transferred from, no lookup is needed */ /* use mesh_new_callback to probe for non-addr hosts, * and then wait for them to be looked up (in cache, or query) */ qinfo.qname_len = sizeof(dname); if(sldns_str2wire_dname_buf(master->host, dname, &qinfo.qname_len) != 0) { log_err("cannot parse host name of master %s", master->host); return 0; } qinfo.qname = dname; qinfo.qclass = xfr->dclass; qinfo.qtype = LDNS_RR_TYPE_A; if(xfr->task_transfer->lookup_aaaa) qinfo.qtype = LDNS_RR_TYPE_AAAA; qinfo.local_alias = NULL; if(verbosity >= VERB_ALGO) { char buf1[512]; char buf2[LDNS_MAX_DOMAINLEN+1]; dname_str(xfr->name, buf2); snprintf(buf1, sizeof(buf1), "auth zone %s: master lookup" " for task_transfer", buf2); log_query_info(VERB_ALGO, buf1, &qinfo); } edns.edns_present = 1; edns.ext_rcode = 0; edns.edns_version = 0; edns.bits = EDNS_DO; edns.opt_list_in = NULL; edns.opt_list_out = NULL; edns.opt_list_inplace_cb_out = NULL; edns.padding_block_size = 0; if(sldns_buffer_capacity(buf) < 65535) edns.udp_size = (uint16_t)sldns_buffer_capacity(buf); else edns.udp_size = 65535; /* unlock xfr during mesh_new_callback() because the callback can be * called straight away */ lock_basic_unlock(&xfr->lock); if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, &auth_xfer_transfer_lookup_callback, xfr, 0)) { lock_basic_lock(&xfr->lock); log_err("out of memory lookup up master %s", master->host); return 0; } lock_basic_lock(&xfr->lock); return 1; } /** initiate TCP to the target and fetch zone. * returns true if that was successfully started, and timeout setup. */ static int xfr_transfer_init_fetch(struct auth_xfer* xfr, struct module_env* env) { struct sockaddr_storage addr; socklen_t addrlen = 0; struct auth_master* master = xfr->task_transfer->master; char *auth_name = NULL; struct timeval t; int timeout; if(!master) return 0; if(master->allow_notify) return 0; /* only for notify */ /* get master addr */ if(xfr->task_transfer->scan_addr) { addrlen = xfr->task_transfer->scan_addr->addrlen; memmove(&addr, &xfr->task_transfer->scan_addr->addr, addrlen); } else { if(!authextstrtoaddr(master->host, &addr, &addrlen, &auth_name)) { /* the ones that are not in addr format are supposed * to be looked up. The lookup has failed however, * so skip them */ char zname[255+1]; dname_str(xfr->name, zname); log_err("%s: failed lookup, cannot transfer from master %s", zname, master->host); return 0; } } /* remove previous TCP connection (if any) */ if(xfr->task_transfer->cp) { comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; } if(!xfr->task_transfer->timer) { xfr->task_transfer->timer = comm_timer_create(env->worker_base, auth_xfer_transfer_timer_callback, xfr); if(!xfr->task_transfer->timer) { log_err("malloc failure"); return 0; } } timeout = AUTH_TRANSFER_TIMEOUT; #ifndef S_SPLINT_S t.tv_sec = timeout/1000; t.tv_usec = (timeout%1000)*1000; #endif if(master->http) { /* perform http fetch */ /* store http port number into sockaddr, * unless someone used unbound's host@port notation */ xfr->task_transfer->on_ixfr = 0; if(strchr(master->host, '@') == NULL) sockaddr_store_port(&addr, addrlen, master->port); xfr->task_transfer->cp = outnet_comm_point_for_http( env->outnet, auth_xfer_transfer_http_callback, xfr, &addr, addrlen, -1, master->ssl, master->host, master->file, env->cfg); if(!xfr->task_transfer->cp) { char zname[255+1], as[256]; dname_str(xfr->name, zname); addr_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "cannot create http cp " "connection for %s to %s", zname, as); return 0; } comm_timer_set(xfr->task_transfer->timer, &t); if(verbosity >= VERB_ALGO) { char zname[255+1], as[256]; dname_str(xfr->name, zname); addr_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "auth zone %s transfer next HTTP fetch from %s started", zname, as); } /* Create or refresh the list of allow_notify addrs */ probe_copy_masters_for_allow_notify(xfr); return 1; } /* perform AXFR/IXFR */ /* set the packet to be written */ /* create new ID */ xfr->task_transfer->id = GET_RANDOM_ID(env->rnd); xfr_create_ixfr_packet(xfr, env->scratch_buffer, xfr->task_transfer->id, master); /* connect on fd */ xfr->task_transfer->cp = outnet_comm_point_for_tcp(env->outnet, auth_xfer_transfer_tcp_callback, xfr, &addr, addrlen, env->scratch_buffer, -1, auth_name != NULL, auth_name); if(!xfr->task_transfer->cp) { char zname[255+1], as[256]; dname_str(xfr->name, zname); addr_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "cannot create tcp cp connection for " "xfr %s to %s", zname, as); return 0; } comm_timer_set(xfr->task_transfer->timer, &t); if(verbosity >= VERB_ALGO) { char zname[255+1], as[256]; dname_str(xfr->name, zname); addr_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "auth zone %s transfer next %s fetch from %s started", zname, (xfr->task_transfer->on_ixfr?"IXFR":"AXFR"), as); } return 1; } /** perform next lookup, next transfer TCP, or end and resume wait time task */ static void xfr_transfer_nexttarget_or_end(struct auth_xfer* xfr, struct module_env* env) { log_assert(xfr->task_transfer->worker == env->worker); /* are we performing lookups? */ while(xfr->task_transfer->lookup_target) { if(xfr_transfer_lookup_host(xfr, env)) { /* wait for lookup to finish, * note that the hostname may be in unbound's cache * and we may then get an instant cache response, * and that calls the callback just like a full * lookup and lookup failures also call callback */ if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s transfer next target lookup", zname); } lock_basic_unlock(&xfr->lock); return; } xfr_transfer_move_to_next_lookup(xfr, env); } /* initiate TCP and fetch the zone from the master */ /* and set timeout on it */ while(!xfr_transfer_end_of_list(xfr)) { xfr->task_transfer->master = xfr_transfer_current_master(xfr); if(xfr_transfer_init_fetch(xfr, env)) { /* successfully started, wait for callback */ lock_basic_unlock(&xfr->lock); return; } /* failed to fetch, next master */ xfr_transfer_nextmaster(xfr); } if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s transfer failed, wait", zname); } /* we failed to fetch the zone, move to wait task * use the shorter retry timeout */ xfr_transfer_disown(xfr); /* pick up the nextprobe task and wait */ if(xfr->task_nextprobe->worker == NULL) xfr_set_timeout(xfr, env, 1, 0); lock_basic_unlock(&xfr->lock); } /** add addrs from A or AAAA rrset to the master */ static void xfr_master_add_addrs(struct auth_master* m, struct ub_packed_rrset_key* rrset, uint16_t rrtype) { size_t i; struct packed_rrset_data* data; if(!m || !rrset) return; if(rrtype != LDNS_RR_TYPE_A && rrtype != LDNS_RR_TYPE_AAAA) return; data = (struct packed_rrset_data*)rrset->entry.data; for(i=0; icount; i++) { struct auth_addr* a; size_t len = data->rr_len[i] - 2; uint8_t* rdata = data->rr_data[i]+2; if(rrtype == LDNS_RR_TYPE_A && len != INET_SIZE) continue; /* wrong length for A */ if(rrtype == LDNS_RR_TYPE_AAAA && len != INET6_SIZE) continue; /* wrong length for AAAA */ /* add and alloc it */ a = (struct auth_addr*)calloc(1, sizeof(*a)); if(!a) { log_err("out of memory"); return; } if(rrtype == LDNS_RR_TYPE_A) { struct sockaddr_in* sa; a->addrlen = (socklen_t)sizeof(*sa); sa = (struct sockaddr_in*)&a->addr; sa->sin_family = AF_INET; sa->sin_port = (in_port_t)htons(UNBOUND_DNS_PORT); memmove(&sa->sin_addr, rdata, INET_SIZE); } else { struct sockaddr_in6* sa; a->addrlen = (socklen_t)sizeof(*sa); sa = (struct sockaddr_in6*)&a->addr; sa->sin6_family = AF_INET6; sa->sin6_port = (in_port_t)htons(UNBOUND_DNS_PORT); memmove(&sa->sin6_addr, rdata, INET6_SIZE); } if(verbosity >= VERB_ALGO) { char s[64]; addr_to_str(&a->addr, a->addrlen, s, sizeof(s)); verbose(VERB_ALGO, "auth host %s lookup %s", m->host, s); } /* append to list */ a->next = m->list; m->list = a; } } /** callback for task_transfer lookup of host name, of A or AAAA */ void auth_xfer_transfer_lookup_callback(void* arg, int rcode, sldns_buffer* buf, enum sec_status ATTR_UNUSED(sec), char* ATTR_UNUSED(why_bogus), int ATTR_UNUSED(was_ratelimited)) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; log_assert(xfr->task_transfer); lock_basic_lock(&xfr->lock); env = xfr->task_transfer->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return; /* stop on quit */ } /* process result */ if(rcode == LDNS_RCODE_NOERROR) { uint16_t wanted_qtype = LDNS_RR_TYPE_A; struct regional* temp = env->scratch; struct query_info rq; struct reply_info* rep; if(xfr->task_transfer->lookup_aaaa) wanted_qtype = LDNS_RR_TYPE_AAAA; memset(&rq, 0, sizeof(rq)); rep = parse_reply_in_temp_region(buf, temp, &rq); if(rep && rq.qtype == wanted_qtype && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR) { /* parsed successfully */ struct ub_packed_rrset_key* answer = reply_find_answer_rrset(&rq, rep); if(answer) { xfr_master_add_addrs(xfr->task_transfer-> lookup_target, answer, wanted_qtype); } else { if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s transfer lookup has nodata", zname, xfr->task_transfer->lookup_target->host, (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); } } } else { if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s transfer lookup has no answer", zname, xfr->task_transfer->lookup_target->host, (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); } } regional_free_all(temp); } else { if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s transfer lookup failed", zname, xfr->task_transfer->lookup_target->host, (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); } } if(xfr->task_transfer->lookup_target->list && xfr->task_transfer->lookup_target == xfr_transfer_current_master(xfr)) xfr->task_transfer->scan_addr = xfr->task_transfer->lookup_target->list; /* move to lookup AAAA after A lookup, move to next hostname lookup, * or move to fetch the zone, or, if nothing to do, end task_transfer */ xfr_transfer_move_to_next_lookup(xfr, env); xfr_transfer_nexttarget_or_end(xfr, env); } /** check if xfer (AXFR or IXFR) packet is OK. * return false if we lost connection (SERVFAIL, or unreadable). * return false if we need to move from IXFR to AXFR, with gonextonfail * set to false, so the same master is tried again, but with AXFR. * return true if fine to link into data. * return true with transferdone=true when the transfer has ended. */ static int check_xfer_packet(sldns_buffer* pkt, struct auth_xfer* xfr, int* gonextonfail, int* transferdone) { uint8_t* wire = sldns_buffer_begin(pkt); int i; if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) { verbose(VERB_ALGO, "xfr to %s failed, packet too small", xfr->task_transfer->master->host); return 0; } if(!LDNS_QR_WIRE(wire)) { verbose(VERB_ALGO, "xfr to %s failed, packet has no QR flag", xfr->task_transfer->master->host); return 0; } if(LDNS_TC_WIRE(wire)) { verbose(VERB_ALGO, "xfr to %s failed, packet has TC flag", xfr->task_transfer->master->host); return 0; } /* check ID */ if(LDNS_ID_WIRE(wire) != xfr->task_transfer->id) { verbose(VERB_ALGO, "xfr to %s failed, packet wrong ID", xfr->task_transfer->master->host); return 0; } if(LDNS_RCODE_WIRE(wire) != LDNS_RCODE_NOERROR) { char rcode[32]; sldns_wire2str_rcode_buf((int)LDNS_RCODE_WIRE(wire), rcode, sizeof(rcode)); /* if we are doing IXFR, check for fallback */ if(xfr->task_transfer->on_ixfr) { if(LDNS_RCODE_WIRE(wire) == LDNS_RCODE_NOTIMPL || LDNS_RCODE_WIRE(wire) == LDNS_RCODE_SERVFAIL || LDNS_RCODE_WIRE(wire) == LDNS_RCODE_REFUSED || LDNS_RCODE_WIRE(wire) == LDNS_RCODE_FORMERR) { verbose(VERB_ALGO, "xfr to %s, fallback " "from IXFR to AXFR (with rcode %s)", xfr->task_transfer->master->host, rcode); xfr->task_transfer->ixfr_fail = 1; *gonextonfail = 0; return 0; } } verbose(VERB_ALGO, "xfr to %s failed, packet with rcode %s", xfr->task_transfer->master->host, rcode); return 0; } if(LDNS_OPCODE_WIRE(wire) != LDNS_PACKET_QUERY) { verbose(VERB_ALGO, "xfr to %s failed, packet with bad opcode", xfr->task_transfer->master->host); return 0; } if(LDNS_QDCOUNT(wire) > 1) { verbose(VERB_ALGO, "xfr to %s failed, packet has qdcount %d", xfr->task_transfer->master->host, (int)LDNS_QDCOUNT(wire)); return 0; } /* check qname */ sldns_buffer_set_position(pkt, LDNS_HEADER_SIZE); for(i=0; i<(int)LDNS_QDCOUNT(wire); i++) { size_t pos = sldns_buffer_position(pkt); uint16_t qtype, qclass; if(pkt_dname_len(pkt) == 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed dname", xfr->task_transfer->master->host); return 0; } if(dname_pkt_compare(pkt, sldns_buffer_at(pkt, pos), xfr->name) != 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "wrong qname", xfr->task_transfer->master->host); return 0; } if(sldns_buffer_remaining(pkt) < 4) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated query RR", xfr->task_transfer->master->host); return 0; } qtype = sldns_buffer_read_u16(pkt); qclass = sldns_buffer_read_u16(pkt); if(qclass != xfr->dclass) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "wrong qclass", xfr->task_transfer->master->host); return 0; } if(xfr->task_transfer->on_ixfr) { if(qtype != LDNS_RR_TYPE_IXFR) { verbose(VERB_ALGO, "xfr to %s failed, packet " "with wrong qtype, expected IXFR", xfr->task_transfer->master->host); return 0; } } else { if(qtype != LDNS_RR_TYPE_AXFR) { verbose(VERB_ALGO, "xfr to %s failed, packet " "with wrong qtype, expected AXFR", xfr->task_transfer->master->host); return 0; } } } /* check parse of RRs in packet, store first SOA serial * to be able to detect last SOA (with that serial) to see if done */ /* also check for IXFR 'zone up to date' reply */ for(i=0; i<(int)LDNS_ANCOUNT(wire); i++) { size_t pos = sldns_buffer_position(pkt); uint16_t tp, rdlen; if(pkt_dname_len(pkt) == 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed dname in answer section", xfr->task_transfer->master->host); return 0; } if(sldns_buffer_remaining(pkt) < 10) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated RR", xfr->task_transfer->master->host); return 0; } tp = sldns_buffer_read_u16(pkt); (void)sldns_buffer_read_u16(pkt); /* class */ (void)sldns_buffer_read_u32(pkt); /* ttl */ rdlen = sldns_buffer_read_u16(pkt); if(sldns_buffer_remaining(pkt) < rdlen) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated RR rdata", xfr->task_transfer->master->host); return 0; } /* RR parses (haven't checked rdata itself), now look at * SOA records to see serial number */ if(xfr->task_transfer->rr_scan_num == 0 && tp != LDNS_RR_TYPE_SOA) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed zone transfer, no start SOA", xfr->task_transfer->master->host); return 0; } if(xfr->task_transfer->rr_scan_num == 1 && tp != LDNS_RR_TYPE_SOA) { /* second RR is not a SOA record, this is not an IXFR * the master is replying with an AXFR */ xfr->task_transfer->on_ixfr_is_axfr = 1; } if(tp == LDNS_RR_TYPE_SOA) { uint32_t serial; if(rdlen < 22) { verbose(VERB_ALGO, "xfr to %s failed, packet " "with SOA with malformed rdata", xfr->task_transfer->master->host); return 0; } if(dname_pkt_compare(pkt, sldns_buffer_at(pkt, pos), xfr->name) != 0) { verbose(VERB_ALGO, "xfr to %s failed, packet " "with SOA with wrong dname", xfr->task_transfer->master->host); return 0; } /* read serial number of SOA */ serial = sldns_buffer_read_u32_at(pkt, sldns_buffer_position(pkt)+rdlen-20); /* check for IXFR 'zone has SOA x' reply */ if(xfr->task_transfer->on_ixfr && xfr->task_transfer->rr_scan_num == 0 && LDNS_ANCOUNT(wire)==1) { verbose(VERB_ALGO, "xfr to %s ended, " "IXFR reply that zone has serial %u," " fallback from IXFR to AXFR", xfr->task_transfer->master->host, (unsigned)serial); xfr->task_transfer->ixfr_fail = 1; *gonextonfail = 0; return 0; } /* if first SOA, store serial number */ if(xfr->task_transfer->got_xfr_serial == 0) { xfr->task_transfer->got_xfr_serial = 1; xfr->task_transfer->incoming_xfr_serial = serial; verbose(VERB_ALGO, "xfr %s: contains " "SOA serial %u", xfr->task_transfer->master->host, (unsigned)serial); /* see if end of AXFR */ } else if(!xfr->task_transfer->on_ixfr || xfr->task_transfer->on_ixfr_is_axfr) { /* second SOA with serial is the end * for AXFR */ *transferdone = 1; verbose(VERB_ALGO, "xfr %s: last AXFR packet", xfr->task_transfer->master->host); /* for IXFR, count SOA records with that serial */ } else if(xfr->task_transfer->incoming_xfr_serial == serial && xfr->task_transfer->got_xfr_serial == 1) { xfr->task_transfer->got_xfr_serial++; /* if not first soa, if serial==firstserial, the * third time we are at the end, for IXFR */ } else if(xfr->task_transfer->incoming_xfr_serial == serial && xfr->task_transfer->got_xfr_serial == 2) { verbose(VERB_ALGO, "xfr %s: last IXFR packet", xfr->task_transfer->master->host); *transferdone = 1; /* continue parse check, if that succeeds, * transfer is done */ } } xfr->task_transfer->rr_scan_num++; /* skip over RR rdata to go to the next RR */ sldns_buffer_skip(pkt, (ssize_t)rdlen); } /* check authority section */ /* we skip over the RRs checking packet format */ for(i=0; i<(int)LDNS_NSCOUNT(wire); i++) { uint16_t rdlen; if(pkt_dname_len(pkt) == 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed dname in authority section", xfr->task_transfer->master->host); return 0; } if(sldns_buffer_remaining(pkt) < 10) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated RR", xfr->task_transfer->master->host); return 0; } (void)sldns_buffer_read_u16(pkt); /* type */ (void)sldns_buffer_read_u16(pkt); /* class */ (void)sldns_buffer_read_u32(pkt); /* ttl */ rdlen = sldns_buffer_read_u16(pkt); if(sldns_buffer_remaining(pkt) < rdlen) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated RR rdata", xfr->task_transfer->master->host); return 0; } /* skip over RR rdata to go to the next RR */ sldns_buffer_skip(pkt, (ssize_t)rdlen); } /* check additional section */ for(i=0; i<(int)LDNS_ARCOUNT(wire); i++) { uint16_t rdlen; if(pkt_dname_len(pkt) == 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed dname in additional section", xfr->task_transfer->master->host); return 0; } if(sldns_buffer_remaining(pkt) < 10) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated RR", xfr->task_transfer->master->host); return 0; } (void)sldns_buffer_read_u16(pkt); /* type */ (void)sldns_buffer_read_u16(pkt); /* class */ (void)sldns_buffer_read_u32(pkt); /* ttl */ rdlen = sldns_buffer_read_u16(pkt); if(sldns_buffer_remaining(pkt) < rdlen) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated RR rdata", xfr->task_transfer->master->host); return 0; } /* skip over RR rdata to go to the next RR */ sldns_buffer_skip(pkt, (ssize_t)rdlen); } return 1; } /** Link the data from this packet into the worklist of transferred data */ static int xfer_link_data(sldns_buffer* pkt, struct auth_xfer* xfr) { /* alloc it */ struct auth_chunk* e; e = (struct auth_chunk*)calloc(1, sizeof(*e)); if(!e) return 0; e->next = NULL; e->len = sldns_buffer_limit(pkt); e->data = memdup(sldns_buffer_begin(pkt), e->len); if(!e->data) { free(e); return 0; } /* alloc succeeded, link into list */ if(!xfr->task_transfer->chunks_first) xfr->task_transfer->chunks_first = e; if(xfr->task_transfer->chunks_last) xfr->task_transfer->chunks_last->next = e; xfr->task_transfer->chunks_last = e; return 1; } /** task transfer. the list of data is complete. process it and if failed * move to next master, if succeeded, end the task transfer */ static void process_list_end_transfer(struct auth_xfer* xfr, struct module_env* env) { int ixfr_fail = 0; if(xfr_process_chunk_list(xfr, env, &ixfr_fail)) { /* it worked! */ auth_chunks_delete(xfr->task_transfer); /* we fetched the zone, move to wait task */ xfr_transfer_disown(xfr); if(xfr->notify_received && (!xfr->notify_has_serial || (xfr->notify_has_serial && xfr_serial_means_update(xfr, xfr->notify_serial)))) { uint32_t sr = xfr->notify_serial; int has_sr = xfr->notify_has_serial; /* we received a notify while probe/transfer was * in progress. start a new probe and transfer */ xfr->notify_received = 0; xfr->notify_has_serial = 0; xfr->notify_serial = 0; if(!xfr_start_probe(xfr, env, NULL)) { /* if we couldn't start it, already in * progress; restore notify serial, * while xfr still locked */ xfr->notify_received = 1; xfr->notify_has_serial = has_sr; xfr->notify_serial = sr; lock_basic_unlock(&xfr->lock); } return; } else { /* pick up the nextprobe task and wait (normail wait time) */ if(xfr->task_nextprobe->worker == NULL) xfr_set_timeout(xfr, env, 0, 0); } lock_basic_unlock(&xfr->lock); return; } /* processing failed */ /* when done, delete data from list */ auth_chunks_delete(xfr->task_transfer); if(ixfr_fail) { xfr->task_transfer->ixfr_fail = 1; } else { xfr_transfer_nextmaster(xfr); } xfr_transfer_nexttarget_or_end(xfr, env); } /** callback for the task_transfer timer */ void auth_xfer_transfer_timer_callback(void* arg) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; int gonextonfail = 1; log_assert(xfr->task_transfer); lock_basic_lock(&xfr->lock); env = xfr->task_transfer->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return; /* stop on quit */ } verbose(VERB_ALGO, "xfr stopped, connection timeout to %s", xfr->task_transfer->master->host); /* see if IXFR caused the failure, if so, try AXFR */ if(xfr->task_transfer->on_ixfr) { xfr->task_transfer->ixfr_possible_timeout_count++; if(xfr->task_transfer->ixfr_possible_timeout_count >= NUM_TIMEOUTS_FALLBACK_IXFR) { verbose(VERB_ALGO, "xfr to %s, fallback " "from IXFR to AXFR (because of timeouts)", xfr->task_transfer->master->host); xfr->task_transfer->ixfr_fail = 1; gonextonfail = 0; } } /* delete transferred data from list */ auth_chunks_delete(xfr->task_transfer); comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; if(gonextonfail) xfr_transfer_nextmaster(xfr); xfr_transfer_nexttarget_or_end(xfr, env); } /** callback for task_transfer tcp connections */ int auth_xfer_transfer_tcp_callback(struct comm_point* c, void* arg, int err, struct comm_reply* ATTR_UNUSED(repinfo)) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; int gonextonfail = 1; int transferdone = 0; log_assert(xfr->task_transfer); lock_basic_lock(&xfr->lock); env = xfr->task_transfer->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return 0; /* stop on quit */ } /* stop the timer */ comm_timer_disable(xfr->task_transfer->timer); if(err != NETEVENT_NOERROR) { /* connection failed, closed, or timeout */ /* stop this transfer, cleanup * and continue task_transfer*/ verbose(VERB_ALGO, "xfr stopped, connection lost to %s", xfr->task_transfer->master->host); /* see if IXFR caused the failure, if so, try AXFR */ if(xfr->task_transfer->on_ixfr) { xfr->task_transfer->ixfr_possible_timeout_count++; if(xfr->task_transfer->ixfr_possible_timeout_count >= NUM_TIMEOUTS_FALLBACK_IXFR) { verbose(VERB_ALGO, "xfr to %s, fallback " "from IXFR to AXFR (because of timeouts)", xfr->task_transfer->master->host); xfr->task_transfer->ixfr_fail = 1; gonextonfail = 0; } } failed: /* delete transferred data from list */ auth_chunks_delete(xfr->task_transfer); comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; if(gonextonfail) xfr_transfer_nextmaster(xfr); xfr_transfer_nexttarget_or_end(xfr, env); return 0; } /* note that IXFR worked without timeout */ if(xfr->task_transfer->on_ixfr) xfr->task_transfer->ixfr_possible_timeout_count = 0; /* handle returned packet */ /* if it fails, cleanup and end this transfer */ /* if it needs to fallback from IXFR to AXFR, do that */ if(!check_xfer_packet(c->buffer, xfr, &gonextonfail, &transferdone)) { goto failed; } /* if it is good, link it into the list of data */ /* if the link into list of data fails (malloc fail) cleanup and end */ if(!xfer_link_data(c->buffer, xfr)) { verbose(VERB_ALGO, "xfr stopped to %s, malloc failed", xfr->task_transfer->master->host); goto failed; } /* if the transfer is done now, disconnect and process the list */ if(transferdone) { comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; process_list_end_transfer(xfr, env); return 0; } /* if we want to read more messages, setup the commpoint to read * a DNS packet, and the timeout */ lock_basic_unlock(&xfr->lock); c->tcp_is_reading = 1; sldns_buffer_clear(c->buffer); comm_point_start_listening(c, -1, AUTH_TRANSFER_TIMEOUT); return 0; } /** callback for task_transfer http connections */ int auth_xfer_transfer_http_callback(struct comm_point* c, void* arg, int err, struct comm_reply* repinfo) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; log_assert(xfr->task_transfer); lock_basic_lock(&xfr->lock); env = xfr->task_transfer->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return 0; /* stop on quit */ } verbose(VERB_ALGO, "auth zone transfer http callback"); /* stop the timer */ comm_timer_disable(xfr->task_transfer->timer); if(err != NETEVENT_NOERROR && err != NETEVENT_DONE) { /* connection failed, closed, or timeout */ /* stop this transfer, cleanup * and continue task_transfer*/ verbose(VERB_ALGO, "http stopped, connection lost to %s", xfr->task_transfer->master->host); failed: /* delete transferred data from list */ auth_chunks_delete(xfr->task_transfer); if(repinfo) repinfo->c = NULL; /* signal cp deleted to the routine calling this callback */ comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; xfr_transfer_nextmaster(xfr); xfr_transfer_nexttarget_or_end(xfr, env); return 0; } /* if it is good, link it into the list of data */ /* if the link into list of data fails (malloc fail) cleanup and end */ if(sldns_buffer_limit(c->buffer) > 0) { verbose(VERB_ALGO, "auth zone http queued up %d bytes", (int)sldns_buffer_limit(c->buffer)); if(!xfer_link_data(c->buffer, xfr)) { verbose(VERB_ALGO, "http stopped to %s, malloc failed", xfr->task_transfer->master->host); goto failed; } } /* if the transfer is done now, disconnect and process the list */ if(err == NETEVENT_DONE) { if(repinfo) repinfo->c = NULL; /* signal cp deleted to the routine calling this callback */ comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; process_list_end_transfer(xfr, env); return 0; } /* if we want to read more messages, setup the commpoint to read * a DNS packet, and the timeout */ lock_basic_unlock(&xfr->lock); c->tcp_is_reading = 1; sldns_buffer_clear(c->buffer); comm_point_start_listening(c, -1, AUTH_TRANSFER_TIMEOUT); return 0; } /** start transfer task by this worker , xfr is locked. */ static void xfr_start_transfer(struct auth_xfer* xfr, struct module_env* env, struct auth_master* master) { log_assert(xfr->task_transfer != NULL); log_assert(xfr->task_transfer->worker == NULL); log_assert(xfr->task_transfer->chunks_first == NULL); log_assert(xfr->task_transfer->chunks_last == NULL); xfr->task_transfer->worker = env->worker; xfr->task_transfer->env = env; /* init transfer process */ /* find that master in the transfer's list of masters? */ xfr_transfer_start_list(xfr, master); /* start lookup for hostnames in transfer master list */ xfr_transfer_start_lookups(xfr); /* initiate TCP, and set timeout on it */ xfr_transfer_nexttarget_or_end(xfr, env); } /** disown task_probe. caller must hold xfr.lock */ static void xfr_probe_disown(struct auth_xfer* xfr) { /* remove timer (from this worker's event base) */ comm_timer_delete(xfr->task_probe->timer); xfr->task_probe->timer = NULL; /* remove the commpoint */ comm_point_delete(xfr->task_probe->cp); xfr->task_probe->cp = NULL; /* we don't own this item anymore */ xfr->task_probe->worker = NULL; xfr->task_probe->env = NULL; } /** send the UDP probe to the master, this is part of task_probe */ static int xfr_probe_send_probe(struct auth_xfer* xfr, struct module_env* env, int timeout) { struct sockaddr_storage addr; socklen_t addrlen = 0; struct timeval t; /* pick master */ struct auth_master* master = xfr_probe_current_master(xfr); char *auth_name = NULL; if(!master) return 0; if(master->allow_notify) return 0; /* only for notify */ if(master->http) return 0; /* only masters get SOA UDP probe, not urls, if those are in this list */ /* get master addr */ if(xfr->task_probe->scan_addr) { addrlen = xfr->task_probe->scan_addr->addrlen; memmove(&addr, &xfr->task_probe->scan_addr->addr, addrlen); } else { if(!authextstrtoaddr(master->host, &addr, &addrlen, &auth_name)) { /* the ones that are not in addr format are supposed * to be looked up. The lookup has failed however, * so skip them */ char zname[255+1]; dname_str(xfr->name, zname); log_err("%s: failed lookup, cannot probe to master %s", zname, master->host); return 0; } if (auth_name != NULL) { if (addr.ss_family == AF_INET && (int)ntohs(((struct sockaddr_in *)&addr)->sin_port) == env->cfg->ssl_port) ((struct sockaddr_in *)&addr)->sin_port = htons((uint16_t)env->cfg->port); else if (addr.ss_family == AF_INET6 && (int)ntohs(((struct sockaddr_in6 *)&addr)->sin6_port) == env->cfg->ssl_port) ((struct sockaddr_in6 *)&addr)->sin6_port = htons((uint16_t)env->cfg->port); } } /* create packet */ /* create new ID for new probes, but not on timeout retries, * this means we'll accept replies to previous retries to same ip */ if(timeout == AUTH_PROBE_TIMEOUT) xfr->task_probe->id = GET_RANDOM_ID(env->rnd); xfr_create_soa_probe_packet(xfr, env->scratch_buffer, xfr->task_probe->id); /* we need to remove the cp if we have a different ip4/ip6 type now */ if(xfr->task_probe->cp && ((xfr->task_probe->cp_is_ip6 && !addr_is_ip6(&addr, addrlen)) || (!xfr->task_probe->cp_is_ip6 && addr_is_ip6(&addr, addrlen))) ) { comm_point_delete(xfr->task_probe->cp); xfr->task_probe->cp = NULL; } if(!xfr->task_probe->cp) { if(addr_is_ip6(&addr, addrlen)) xfr->task_probe->cp_is_ip6 = 1; else xfr->task_probe->cp_is_ip6 = 0; xfr->task_probe->cp = outnet_comm_point_for_udp(env->outnet, auth_xfer_probe_udp_callback, xfr, &addr, addrlen); if(!xfr->task_probe->cp) { char zname[255+1], as[256]; dname_str(xfr->name, zname); addr_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "cannot create udp cp for " "probe %s to %s", zname, as); return 0; } } if(!xfr->task_probe->timer) { xfr->task_probe->timer = comm_timer_create(env->worker_base, auth_xfer_probe_timer_callback, xfr); if(!xfr->task_probe->timer) { log_err("malloc failure"); return 0; } } /* send udp packet */ if(!comm_point_send_udp_msg(xfr->task_probe->cp, env->scratch_buffer, (struct sockaddr*)&addr, addrlen, 0)) { char zname[255+1], as[256]; dname_str(xfr->name, zname); addr_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "failed to send soa probe for %s to %s", zname, as); return 0; } if(verbosity >= VERB_ALGO) { char zname[255+1], as[256]; dname_str(xfr->name, zname); addr_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "auth zone %s soa probe sent to %s", zname, as); } xfr->task_probe->timeout = timeout; #ifndef S_SPLINT_S t.tv_sec = timeout/1000; t.tv_usec = (timeout%1000)*1000; #endif comm_timer_set(xfr->task_probe->timer, &t); return 1; } /** callback for task_probe timer */ void auth_xfer_probe_timer_callback(void* arg) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; log_assert(xfr->task_probe); lock_basic_lock(&xfr->lock); env = xfr->task_probe->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return; /* stop on quit */ } if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s soa probe timeout", zname); } if(xfr->task_probe->timeout <= AUTH_PROBE_TIMEOUT_STOP) { /* try again with bigger timeout */ if(xfr_probe_send_probe(xfr, env, xfr->task_probe->timeout*2)) { lock_basic_unlock(&xfr->lock); return; } } /* delete commpoint so a new one is created, with a fresh port nr */ comm_point_delete(xfr->task_probe->cp); xfr->task_probe->cp = NULL; /* too many timeouts (or fail to send), move to next or end */ xfr_probe_nextmaster(xfr); xfr_probe_send_or_end(xfr, env); } /** callback for task_probe udp packets */ int auth_xfer_probe_udp_callback(struct comm_point* c, void* arg, int err, struct comm_reply* repinfo) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; log_assert(xfr->task_probe); lock_basic_lock(&xfr->lock); env = xfr->task_probe->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return 0; /* stop on quit */ } /* the comm_point_udp_callback is in a for loop for NUM_UDP_PER_SELECT * and we set rep.c=NULL to stop if from looking inside the commpoint*/ repinfo->c = NULL; /* stop the timer */ comm_timer_disable(xfr->task_probe->timer); /* see if we got a packet and what that means */ if(err == NETEVENT_NOERROR) { uint32_t serial = 0; if(check_packet_ok(c->buffer, LDNS_RR_TYPE_SOA, xfr, &serial)) { /* successful lookup */ if(verbosity >= VERB_ALGO) { char buf[256]; dname_str(xfr->name, buf); verbose(VERB_ALGO, "auth zone %s: soa probe " "serial is %u", buf, (unsigned)serial); } /* see if this serial indicates that the zone has * to be updated */ if(xfr_serial_means_update(xfr, serial)) { /* if updated, start the transfer task, if needed */ verbose(VERB_ALGO, "auth_zone updated, start transfer"); if(xfr->task_transfer->worker == NULL) { struct auth_master* master = xfr_probe_current_master(xfr); /* if we have download URLs use them * in preference to this master we * just probed the SOA from */ if(xfr->task_transfer->masters && xfr->task_transfer->masters->http) master = NULL; xfr_probe_disown(xfr); xfr_start_transfer(xfr, env, master); return 0; } /* other tasks are running, we don't do this anymore */ xfr_probe_disown(xfr); lock_basic_unlock(&xfr->lock); /* return, we don't sent a reply to this udp packet, * and we setup the tasks to do next */ return 0; } else { verbose(VERB_ALGO, "auth_zone master reports unchanged soa serial"); /* we if cannot find updates amongst the * masters, this means we then have a new lease * on the zone */ xfr->task_probe->have_new_lease = 1; } } else { if(verbosity >= VERB_ALGO) { char buf[256]; dname_str(xfr->name, buf); verbose(VERB_ALGO, "auth zone %s: bad reply to soa probe", buf); } } } else { if(verbosity >= VERB_ALGO) { char buf[256]; dname_str(xfr->name, buf); verbose(VERB_ALGO, "auth zone %s: soa probe failed", buf); } } /* failed lookup or not an update */ /* delete commpoint so a new one is created, with a fresh port nr */ comm_point_delete(xfr->task_probe->cp); xfr->task_probe->cp = NULL; /* if the result was not a successful probe, we need * to send the next one */ xfr_probe_nextmaster(xfr); xfr_probe_send_or_end(xfr, env); return 0; } /** lookup a host name for its addresses, if needed */ static int xfr_probe_lookup_host(struct auth_xfer* xfr, struct module_env* env) { struct sockaddr_storage addr; socklen_t addrlen = 0; struct auth_master* master = xfr->task_probe->lookup_target; struct query_info qinfo; uint16_t qflags = BIT_RD; uint8_t dname[LDNS_MAX_DOMAINLEN+1]; struct edns_data edns; sldns_buffer* buf = env->scratch_buffer; if(!master) return 0; if(extstrtoaddr(master->host, &addr, &addrlen)) { /* not needed, host is in IP addr format */ return 0; } if(master->allow_notify && !master->http && strchr(master->host, '/') != NULL && strchr(master->host, '/') == strrchr(master->host, '/')) { return 0; /* is IP/prefix format, not something to look up */ } /* use mesh_new_callback to probe for non-addr hosts, * and then wait for them to be looked up (in cache, or query) */ qinfo.qname_len = sizeof(dname); if(sldns_str2wire_dname_buf(master->host, dname, &qinfo.qname_len) != 0) { log_err("cannot parse host name of master %s", master->host); return 0; } qinfo.qname = dname; qinfo.qclass = xfr->dclass; qinfo.qtype = LDNS_RR_TYPE_A; if(xfr->task_probe->lookup_aaaa) qinfo.qtype = LDNS_RR_TYPE_AAAA; qinfo.local_alias = NULL; if(verbosity >= VERB_ALGO) { char buf1[512]; char buf2[LDNS_MAX_DOMAINLEN+1]; dname_str(xfr->name, buf2); snprintf(buf1, sizeof(buf1), "auth zone %s: master lookup" " for task_probe", buf2); log_query_info(VERB_ALGO, buf1, &qinfo); } edns.edns_present = 1; edns.ext_rcode = 0; edns.edns_version = 0; edns.bits = EDNS_DO; edns.opt_list_in = NULL; edns.opt_list_out = NULL; edns.opt_list_inplace_cb_out = NULL; edns.padding_block_size = 0; if(sldns_buffer_capacity(buf) < 65535) edns.udp_size = (uint16_t)sldns_buffer_capacity(buf); else edns.udp_size = 65535; /* unlock xfr during mesh_new_callback() because the callback can be * called straight away */ lock_basic_unlock(&xfr->lock); if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, &auth_xfer_probe_lookup_callback, xfr, 0)) { lock_basic_lock(&xfr->lock); log_err("out of memory lookup up master %s", master->host); return 0; } lock_basic_lock(&xfr->lock); return 1; } /** move to sending the probe packets, next if fails. task_probe */ static void xfr_probe_send_or_end(struct auth_xfer* xfr, struct module_env* env) { /* are we doing hostname lookups? */ while(xfr->task_probe->lookup_target) { if(xfr_probe_lookup_host(xfr, env)) { /* wait for lookup to finish, * note that the hostname may be in unbound's cache * and we may then get an instant cache response, * and that calls the callback just like a full * lookup and lookup failures also call callback */ if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s probe next target lookup", zname); } lock_basic_unlock(&xfr->lock); return; } xfr_probe_move_to_next_lookup(xfr, env); } /* probe of list has ended. Create or refresh the list of of * allow_notify addrs */ probe_copy_masters_for_allow_notify(xfr); if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s probe: notify addrs updated", zname); } if(xfr->task_probe->only_lookup) { /* only wanted lookups for copy, stop probe and start wait */ xfr->task_probe->only_lookup = 0; if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s probe: finished only_lookup", zname); } xfr_probe_disown(xfr); if(xfr->task_nextprobe->worker == NULL) xfr_set_timeout(xfr, env, 0, 0); lock_basic_unlock(&xfr->lock); return; } /* send probe packets */ while(!xfr_probe_end_of_list(xfr)) { if(xfr_probe_send_probe(xfr, env, AUTH_PROBE_TIMEOUT)) { /* successfully sent probe, wait for callback */ lock_basic_unlock(&xfr->lock); return; } /* failed to send probe, next master */ xfr_probe_nextmaster(xfr); } /* done with probe sequence, wait */ if(xfr->task_probe->have_new_lease) { /* if zone not updated, start the wait timer again */ if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth_zone %s unchanged, new lease, wait", zname); } xfr_probe_disown(xfr); if(xfr->have_zone) xfr->lease_time = *env->now; if(xfr->task_nextprobe->worker == NULL) xfr_set_timeout(xfr, env, 0, 0); } else { if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s soa probe failed, wait to retry", zname); } /* we failed to send this as well, move to the wait task, * use the shorter retry timeout */ xfr_probe_disown(xfr); /* pick up the nextprobe task and wait */ if(xfr->task_nextprobe->worker == NULL) xfr_set_timeout(xfr, env, 1, 0); } lock_basic_unlock(&xfr->lock); } /** callback for task_probe lookup of host name, of A or AAAA */ void auth_xfer_probe_lookup_callback(void* arg, int rcode, sldns_buffer* buf, enum sec_status ATTR_UNUSED(sec), char* ATTR_UNUSED(why_bogus), int ATTR_UNUSED(was_ratelimited)) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; log_assert(xfr->task_probe); lock_basic_lock(&xfr->lock); env = xfr->task_probe->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return; /* stop on quit */ } /* process result */ if(rcode == LDNS_RCODE_NOERROR) { uint16_t wanted_qtype = LDNS_RR_TYPE_A; struct regional* temp = env->scratch; struct query_info rq; struct reply_info* rep; if(xfr->task_probe->lookup_aaaa) wanted_qtype = LDNS_RR_TYPE_AAAA; memset(&rq, 0, sizeof(rq)); rep = parse_reply_in_temp_region(buf, temp, &rq); if(rep && rq.qtype == wanted_qtype && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR) { /* parsed successfully */ struct ub_packed_rrset_key* answer = reply_find_answer_rrset(&rq, rep); if(answer) { xfr_master_add_addrs(xfr->task_probe-> lookup_target, answer, wanted_qtype); } else { if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s probe lookup has nodata", zname, xfr->task_probe->lookup_target->host, (xfr->task_probe->lookup_aaaa?"AAAA":"A")); } } } else { if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s probe lookup has no address", zname, xfr->task_probe->lookup_target->host, (xfr->task_probe->lookup_aaaa?"AAAA":"A")); } } regional_free_all(temp); } else { if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s probe lookup failed", zname, xfr->task_probe->lookup_target->host, (xfr->task_probe->lookup_aaaa?"AAAA":"A")); } } if(xfr->task_probe->lookup_target->list && xfr->task_probe->lookup_target == xfr_probe_current_master(xfr)) xfr->task_probe->scan_addr = xfr->task_probe->lookup_target->list; /* move to lookup AAAA after A lookup, move to next hostname lookup, * or move to send the probes, or, if nothing to do, end task_probe */ xfr_probe_move_to_next_lookup(xfr, env); xfr_probe_send_or_end(xfr, env); } /** disown task_nextprobe. caller must hold xfr.lock */ static void xfr_nextprobe_disown(struct auth_xfer* xfr) { /* delete the timer, because the next worker to pick this up may * not have the same event base */ comm_timer_delete(xfr->task_nextprobe->timer); xfr->task_nextprobe->timer = NULL; xfr->task_nextprobe->next_probe = 0; /* we don't own this item anymore */ xfr->task_nextprobe->worker = NULL; xfr->task_nextprobe->env = NULL; } /** xfer nextprobe timeout callback, this is part of task_nextprobe */ void auth_xfer_timer(void* arg) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; log_assert(xfr->task_nextprobe); lock_basic_lock(&xfr->lock); env = xfr->task_nextprobe->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return; /* stop on quit */ } /* see if zone has expired, and if so, also set auth_zone expired */ if(xfr->have_zone && !xfr->zone_expired && *env->now >= xfr->lease_time + xfr->expiry) { lock_basic_unlock(&xfr->lock); auth_xfer_set_expired(xfr, env, 1); lock_basic_lock(&xfr->lock); } xfr_nextprobe_disown(xfr); if(!xfr_start_probe(xfr, env, NULL)) { /* not started because already in progress */ lock_basic_unlock(&xfr->lock); } } /** return true if there are probe (SOA UDP query) targets in the master list*/ static int have_probe_targets(struct auth_master* list) { struct auth_master* p; for(p=list; p; p = p->next) { if(!p->allow_notify && p->host) return 1; } return 0; } /** start task_probe if possible, if no masters for probe start task_transfer * returns true if task has been started, and false if the task is already * in progress. */ static int xfr_start_probe(struct auth_xfer* xfr, struct module_env* env, struct auth_master* spec) { /* see if we need to start a probe (or maybe it is already in * progress (due to notify)) */ if(xfr->task_probe->worker == NULL) { if(!have_probe_targets(xfr->task_probe->masters) && !(xfr->task_probe->only_lookup && xfr->task_probe->masters != NULL)) { /* useless to pick up task_probe, no masters to * probe. Instead attempt to pick up task transfer */ if(xfr->task_transfer->worker == NULL) { xfr_start_transfer(xfr, env, spec); return 1; } /* task transfer already in progress */ return 0; } /* pick up the probe task ourselves */ xfr->task_probe->worker = env->worker; xfr->task_probe->env = env; xfr->task_probe->cp = NULL; /* start the task */ /* have not seen a new lease yet, this scan */ xfr->task_probe->have_new_lease = 0; /* if this was a timeout, no specific first master to scan */ /* otherwise, spec is nonNULL the notified master, scan * first and also transfer first from it */ xfr_probe_start_list(xfr, spec); /* setup to start the lookup of hostnames of masters afresh */ xfr_probe_start_lookups(xfr); /* send the probe packet or next send, or end task */ xfr_probe_send_or_end(xfr, env); return 1; } return 0; } /** for task_nextprobe. * determine next timeout for auth_xfer. Also (re)sets timer. * @param xfr: task structure * @param env: module environment, with worker and time. * @param failure: set true if timer should be set for failure retry. * @param lookup_only: only perform lookups when timer done, 0 sec timeout */ static void xfr_set_timeout(struct auth_xfer* xfr, struct module_env* env, int failure, int lookup_only) { struct timeval tv; log_assert(xfr->task_nextprobe != NULL); log_assert(xfr->task_nextprobe->worker == NULL || xfr->task_nextprobe->worker == env->worker); /* normally, nextprobe = startoflease + refresh, * but if expiry is sooner, use that one. * after a failure, use the retry timer instead. */ xfr->task_nextprobe->next_probe = *env->now; if(xfr->lease_time && !failure) xfr->task_nextprobe->next_probe = xfr->lease_time; if(!failure) { xfr->task_nextprobe->backoff = 0; } else { if(xfr->task_nextprobe->backoff == 0) xfr->task_nextprobe->backoff = 3; else xfr->task_nextprobe->backoff *= 2; if(xfr->task_nextprobe->backoff > AUTH_TRANSFER_MAX_BACKOFF) xfr->task_nextprobe->backoff = AUTH_TRANSFER_MAX_BACKOFF; } if(xfr->have_zone) { time_t wait = xfr->refresh; if(failure) wait = xfr->retry; if(xfr->expiry < wait) xfr->task_nextprobe->next_probe += xfr->expiry; else xfr->task_nextprobe->next_probe += wait; if(failure) xfr->task_nextprobe->next_probe += xfr->task_nextprobe->backoff; /* put the timer exactly on expiry, if possible */ if(xfr->lease_time && xfr->lease_time+xfr->expiry < xfr->task_nextprobe->next_probe && xfr->lease_time+xfr->expiry > *env->now) xfr->task_nextprobe->next_probe = xfr->lease_time+xfr->expiry; } else { xfr->task_nextprobe->next_probe += xfr->task_nextprobe->backoff; } if(!xfr->task_nextprobe->timer) { xfr->task_nextprobe->timer = comm_timer_create( env->worker_base, auth_xfer_timer, xfr); if(!xfr->task_nextprobe->timer) { /* failed to malloc memory. likely zone transfer * also fails for that. skip the timeout */ char zname[255+1]; dname_str(xfr->name, zname); log_err("cannot allocate timer, no refresh for %s", zname); return; } } xfr->task_nextprobe->worker = env->worker; xfr->task_nextprobe->env = env; if(*(xfr->task_nextprobe->env->now) <= xfr->task_nextprobe->next_probe) tv.tv_sec = xfr->task_nextprobe->next_probe - *(xfr->task_nextprobe->env->now); else tv.tv_sec = 0; if(tv.tv_sec != 0 && lookup_only && xfr->task_probe->masters) { /* don't lookup_only, if lookup timeout is 0 anyway, * or if we don't have masters to lookup */ tv.tv_sec = 0; if(xfr->task_probe->worker == NULL) xfr->task_probe->only_lookup = 1; } if(verbosity >= VERB_ALGO) { char zname[255+1]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s timeout in %d seconds", zname, (int)tv.tv_sec); } tv.tv_usec = 0; comm_timer_set(xfr->task_nextprobe->timer, &tv); } /** initial pick up of worker timeouts, ties events to worker event loop */ void auth_xfer_pickup_initial(struct auth_zones* az, struct module_env* env) { struct auth_xfer* x; lock_rw_wrlock(&az->lock); RBTREE_FOR(x, struct auth_xfer*, &az->xtree) { lock_basic_lock(&x->lock); /* set lease_time, because we now have timestamp in env, * (not earlier during startup and apply_cfg), and this * notes the start time when the data was acquired */ if(x->have_zone) x->lease_time = *env->now; if(x->task_nextprobe && x->task_nextprobe->worker == NULL) { xfr_set_timeout(x, env, 0, 1); } lock_basic_unlock(&x->lock); } lock_rw_unlock(&az->lock); } void auth_zones_cleanup(struct auth_zones* az) { struct auth_xfer* x; lock_rw_wrlock(&az->lock); RBTREE_FOR(x, struct auth_xfer*, &az->xtree) { lock_basic_lock(&x->lock); if(x->task_nextprobe && x->task_nextprobe->worker != NULL) { xfr_nextprobe_disown(x); } if(x->task_probe && x->task_probe->worker != NULL) { xfr_probe_disown(x); } if(x->task_transfer && x->task_transfer->worker != NULL) { auth_chunks_delete(x->task_transfer); xfr_transfer_disown(x); } lock_basic_unlock(&x->lock); } lock_rw_unlock(&az->lock); } /** * malloc the xfer and tasks * @param z: auth_zone with name of zone. */ static struct auth_xfer* auth_xfer_new(struct auth_zone* z) { struct auth_xfer* xfr; xfr = (struct auth_xfer*)calloc(1, sizeof(*xfr)); if(!xfr) return NULL; xfr->name = memdup(z->name, z->namelen); if(!xfr->name) { free(xfr); return NULL; } xfr->node.key = xfr; xfr->namelen = z->namelen; xfr->namelabs = z->namelabs; xfr->dclass = z->dclass; xfr->task_nextprobe = (struct auth_nextprobe*)calloc(1, sizeof(struct auth_nextprobe)); if(!xfr->task_nextprobe) { free(xfr->name); free(xfr); return NULL; } xfr->task_probe = (struct auth_probe*)calloc(1, sizeof(struct auth_probe)); if(!xfr->task_probe) { free(xfr->task_nextprobe); free(xfr->name); free(xfr); return NULL; } xfr->task_transfer = (struct auth_transfer*)calloc(1, sizeof(struct auth_transfer)); if(!xfr->task_transfer) { free(xfr->task_probe); free(xfr->task_nextprobe); free(xfr->name); free(xfr); return NULL; } lock_basic_init(&xfr->lock); lock_protect(&xfr->lock, &xfr->name, sizeof(xfr->name)); lock_protect(&xfr->lock, &xfr->namelen, sizeof(xfr->namelen)); lock_protect(&xfr->lock, xfr->name, xfr->namelen); lock_protect(&xfr->lock, &xfr->namelabs, sizeof(xfr->namelabs)); lock_protect(&xfr->lock, &xfr->dclass, sizeof(xfr->dclass)); lock_protect(&xfr->lock, &xfr->notify_received, sizeof(xfr->notify_received)); lock_protect(&xfr->lock, &xfr->notify_serial, sizeof(xfr->notify_serial)); lock_protect(&xfr->lock, &xfr->zone_expired, sizeof(xfr->zone_expired)); lock_protect(&xfr->lock, &xfr->have_zone, sizeof(xfr->have_zone)); lock_protect(&xfr->lock, &xfr->serial, sizeof(xfr->serial)); lock_protect(&xfr->lock, &xfr->retry, sizeof(xfr->retry)); lock_protect(&xfr->lock, &xfr->refresh, sizeof(xfr->refresh)); lock_protect(&xfr->lock, &xfr->expiry, sizeof(xfr->expiry)); lock_protect(&xfr->lock, &xfr->lease_time, sizeof(xfr->lease_time)); lock_protect(&xfr->lock, &xfr->task_nextprobe->worker, sizeof(xfr->task_nextprobe->worker)); lock_protect(&xfr->lock, &xfr->task_probe->worker, sizeof(xfr->task_probe->worker)); lock_protect(&xfr->lock, &xfr->task_transfer->worker, sizeof(xfr->task_transfer->worker)); lock_basic_lock(&xfr->lock); return xfr; } /** Create auth_xfer structure. * This populates the have_zone, soa values, and so on times. * and sets the timeout, if a zone transfer is needed a short timeout is set. * For that the auth_zone itself must exist (and read in zonefile) * returns false on alloc failure. */ struct auth_xfer* auth_xfer_create(struct auth_zones* az, struct auth_zone* z) { struct auth_xfer* xfr; /* malloc it */ xfr = auth_xfer_new(z); if(!xfr) { log_err("malloc failure"); return NULL; } /* insert in tree */ (void)rbtree_insert(&az->xtree, &xfr->node); return xfr; } /** create new auth_master structure */ static struct auth_master* auth_master_new(struct auth_master*** list) { struct auth_master *m; m = (struct auth_master*)calloc(1, sizeof(*m)); if(!m) { log_err("malloc failure"); return NULL; } /* set first pointer to m, or next pointer of previous element to m */ (**list) = m; /* store m's next pointer as future point to store at */ (*list) = &(m->next); return m; } /** dup_prefix : create string from initial part of other string, malloced */ static char* dup_prefix(char* str, size_t num) { char* result; size_t len = strlen(str); if(len < num) num = len; /* not more than strlen */ result = (char*)malloc(num+1); if(!result) { log_err("malloc failure"); return result; } memmove(result, str, num); result[num] = 0; return result; } /** dup string and print error on error */ static char* dup_all(char* str) { char* result = strdup(str); if(!result) { log_err("malloc failure"); return NULL; } return result; } /** find first of two characters */ static char* str_find_first_of_chars(char* s, char a, char b) { char* ra = strchr(s, a); char* rb = strchr(s, b); if(!ra) return rb; if(!rb) return ra; if(ra < rb) return ra; return rb; } /** parse URL into host and file parts, false on malloc or parse error */ static int parse_url(char* url, char** host, char** file, int* port, int* ssl) { char* p = url; /* parse http://www.example.com/file.htm * or http://127.0.0.1 (index.html) * or https://[::1@1234]/a/b/c/d */ *ssl = 1; *port = AUTH_HTTPS_PORT; /* parse http:// or https:// */ if(strncmp(p, "http://", 7) == 0) { p += 7; *ssl = 0; *port = AUTH_HTTP_PORT; } else if(strncmp(p, "https://", 8) == 0) { p += 8; } else if(strstr(p, "://") && strchr(p, '/') > strstr(p, "://") && strchr(p, ':') >= strstr(p, "://")) { char* uri = dup_prefix(p, (size_t)(strstr(p, "://")-p)); log_err("protocol %s:// not supported (for url %s)", uri?uri:"", p); free(uri); return 0; } /* parse hostname part */ if(p[0] == '[') { char* end = strchr(p, ']'); p++; /* skip over [ */ if(end) { *host = dup_prefix(p, (size_t)(end-p)); if(!*host) return 0; p = end+1; /* skip over ] */ } else { *host = dup_all(p); if(!*host) return 0; p = end; } } else { char* end = str_find_first_of_chars(p, ':', '/'); if(end) { *host = dup_prefix(p, (size_t)(end-p)); if(!*host) return 0; } else { *host = dup_all(p); if(!*host) return 0; } p = end; /* at next : or / or NULL */ } /* parse port number */ if(p && p[0] == ':') { char* end = NULL; *port = strtol(p+1, &end, 10); p = end; } /* parse filename part */ while(p && *p == '/') p++; if(!p || p[0] == 0) *file = strdup("/"); else *file = strdup(p); if(!*file) { log_err("malloc failure"); return 0; } return 1; } int xfer_set_masters(struct auth_master** list, struct config_auth* c, int with_http) { struct auth_master* m; struct config_strlist* p; /* list points to the first, or next pointer for the new element */ while(*list) { list = &( (*list)->next ); } if(with_http) for(p = c->urls; p; p = p->next) { m = auth_master_new(&list); if(!m) return 0; m->http = 1; if(!parse_url(p->str, &m->host, &m->file, &m->port, &m->ssl)) return 0; } for(p = c->masters; p; p = p->next) { m = auth_master_new(&list); if(!m) return 0; m->ixfr = 1; /* this flag is not configurable */ m->host = strdup(p->str); if(!m->host) { log_err("malloc failure"); return 0; } } for(p = c->allow_notify; p; p = p->next) { m = auth_master_new(&list); if(!m) return 0; m->allow_notify = 1; m->host = strdup(p->str); if(!m->host) { log_err("malloc failure"); return 0; } } return 1; } #define SERIAL_BITS 32 int compare_serial(uint32_t a, uint32_t b) { const uint32_t cutoff = ((uint32_t) 1 << (SERIAL_BITS - 1)); if (a == b) { return 0; } else if ((a < b && b - a < cutoff) || (a > b && a - b > cutoff)) { return -1; } else { return 1; } } int zonemd_hashalgo_supported(int hashalgo) { if(hashalgo == ZONEMD_ALGO_SHA384) return 1; if(hashalgo == ZONEMD_ALGO_SHA512) return 1; return 0; } int zonemd_scheme_supported(int scheme) { if(scheme == ZONEMD_SCHEME_SIMPLE) return 1; return 0; } /** initialize hash for hashing with zonemd hash algo */ static struct secalgo_hash* zonemd_digest_init(int hashalgo, char** reason) { struct secalgo_hash *h; if(hashalgo == ZONEMD_ALGO_SHA384) { /* sha384 */ h = secalgo_hash_create_sha384(); if(!h) *reason = "digest sha384 could not be created"; return h; } else if(hashalgo == ZONEMD_ALGO_SHA512) { /* sha512 */ h = secalgo_hash_create_sha512(); if(!h) *reason = "digest sha512 could not be created"; return h; } /* unknown hash algo */ *reason = "unsupported algorithm"; return NULL; } /** update the hash for zonemd */ static int zonemd_digest_update(int hashalgo, struct secalgo_hash* h, uint8_t* data, size_t len, char** reason) { if(hashalgo == ZONEMD_ALGO_SHA384) { if(!secalgo_hash_update(h, data, len)) { *reason = "digest sha384 failed"; return 0; } return 1; } else if(hashalgo == ZONEMD_ALGO_SHA512) { if(!secalgo_hash_update(h, data, len)) { *reason = "digest sha512 failed"; return 0; } return 1; } /* unknown hash algo */ *reason = "unsupported algorithm"; return 0; } /** finish the hash for zonemd */ static int zonemd_digest_finish(int hashalgo, struct secalgo_hash* h, uint8_t* result, size_t hashlen, size_t* resultlen, char** reason) { if(hashalgo == ZONEMD_ALGO_SHA384) { if(hashlen < 384/8) { *reason = "digest buffer too small for sha384"; return 0; } if(!secalgo_hash_final(h, result, hashlen, resultlen)) { *reason = "digest sha384 finish failed"; return 0; } return 1; } else if(hashalgo == ZONEMD_ALGO_SHA512) { if(hashlen < 512/8) { *reason = "digest buffer too small for sha512"; return 0; } if(!secalgo_hash_final(h, result, hashlen, resultlen)) { *reason = "digest sha512 finish failed"; return 0; } return 1; } /* unknown algo */ *reason = "unsupported algorithm"; return 0; } /** add rrsets from node to the list */ static size_t authdata_rrsets_to_list(struct auth_rrset** array, size_t arraysize, struct auth_rrset* first) { struct auth_rrset* rrset = first; size_t num = 0; while(rrset) { if(num >= arraysize) return num; array[num] = rrset; num++; rrset = rrset->next; } return num; } /** compare rr list entries */ static int rrlist_compare(const void* arg1, const void* arg2) { struct auth_rrset* r1 = *(struct auth_rrset**)arg1; struct auth_rrset* r2 = *(struct auth_rrset**)arg2; uint16_t t1, t2; if(r1 == NULL) t1 = LDNS_RR_TYPE_RRSIG; else t1 = r1->type; if(r2 == NULL) t2 = LDNS_RR_TYPE_RRSIG; else t2 = r2->type; if(t1 < t2) return -1; if(t1 > t2) return 1; return 0; } /** add type RRSIG to rr list if not one there already, * this is to perform RRSIG collate processing at that point. */ static void addrrsigtype_if_needed(struct auth_rrset** array, size_t arraysize, size_t* rrnum, struct auth_data* node) { if(az_domain_rrset(node, LDNS_RR_TYPE_RRSIG)) return; /* already one there */ if((*rrnum) >= arraysize) return; /* array too small? */ array[*rrnum] = NULL; /* nothing there, but need entry in list */ (*rrnum)++; } /** collate the RRs in an RRset using the simple scheme */ static int zonemd_simple_rrset(struct auth_zone* z, int hashalgo, struct secalgo_hash* h, struct auth_data* node, struct auth_rrset* rrset, struct regional* region, struct sldns_buffer* buf, char** reason) { /* canonicalize */ struct ub_packed_rrset_key key; memset(&key, 0, sizeof(key)); key.entry.key = &key; key.entry.data = rrset->data; key.rk.dname = node->name; key.rk.dname_len = node->namelen; key.rk.type = htons(rrset->type); key.rk.rrset_class = htons(z->dclass); if(!rrset_canonicalize_to_buffer(region, buf, &key)) { *reason = "out of memory"; return 0; } regional_free_all(region); /* hash */ if(!zonemd_digest_update(hashalgo, h, sldns_buffer_begin(buf), sldns_buffer_limit(buf), reason)) { return 0; } return 1; } /** count number of RRSIGs in a domain name rrset list */ static size_t zonemd_simple_count_rrsig(struct auth_rrset* rrset, struct auth_rrset** rrlist, size_t rrnum, struct auth_zone* z, struct auth_data* node) { size_t i, count = 0; if(rrset) { size_t j; for(j = 0; jdata->count; j++) { if(rrsig_rdata_get_type_covered(rrset->data-> rr_data[j], rrset->data->rr_len[j]) == LDNS_RR_TYPE_ZONEMD && query_dname_compare(z->name, node->name)==0) { /* omit RRSIGs over type ZONEMD at apex */ continue; } count++; } } for(i=0; itype == LDNS_RR_TYPE_ZONEMD && query_dname_compare(z->name, node->name)==0) { /* omit RRSIGs over type ZONEMD at apex */ continue; } count += (rrlist[i]?rrlist[i]->data->rrsig_count:0); } return count; } /** allocate sparse rrset data for the number of entries in tepm region */ static int zonemd_simple_rrsig_allocs(struct regional* region, struct packed_rrset_data* data, size_t count) { data->rr_len = regional_alloc(region, sizeof(*data->rr_len) * count); if(!data->rr_len) { return 0; } data->rr_ttl = regional_alloc(region, sizeof(*data->rr_ttl) * count); if(!data->rr_ttl) { return 0; } data->rr_data = regional_alloc(region, sizeof(*data->rr_data) * count); if(!data->rr_data) { return 0; } return 1; } /** add the RRSIGs from the rrs in the domain into the data */ static void add_rrlist_rrsigs_into_data(struct packed_rrset_data* data, size_t* done, struct auth_rrset** rrlist, size_t rrnum, struct auth_zone* z, struct auth_data* node) { size_t i; for(i=0; itype == LDNS_RR_TYPE_ZONEMD && query_dname_compare(z->name, node->name)==0) { /* omit RRSIGs over type ZONEMD at apex */ continue; } for(j = 0; jdata->rrsig_count; j++) { data->rr_len[*done] = rrlist[i]->data->rr_len[rrlist[i]->data->count + j]; data->rr_ttl[*done] = rrlist[i]->data->rr_ttl[rrlist[i]->data->count + j]; /* reference the rdata in the rrset, no need to * copy it, it is no longer needed at the end of * the routine */ data->rr_data[*done] = rrlist[i]->data->rr_data[rrlist[i]->data->count + j]; (*done)++; } } } static void add_rrset_into_data(struct packed_rrset_data* data, size_t* done, struct auth_rrset* rrset, struct auth_zone* z, struct auth_data* node) { if(rrset) { size_t j; for(j = 0; jdata->count; j++) { if(rrsig_rdata_get_type_covered(rrset->data-> rr_data[j], rrset->data->rr_len[j]) == LDNS_RR_TYPE_ZONEMD && query_dname_compare(z->name, node->name)==0) { /* omit RRSIGs over type ZONEMD at apex */ continue; } data->rr_len[*done] = rrset->data->rr_len[j]; data->rr_ttl[*done] = rrset->data->rr_ttl[j]; /* reference the rdata in the rrset, no need to * copy it, it is no longer need at the end of * the routine */ data->rr_data[*done] = rrset->data->rr_data[j]; (*done)++; } } } /** collate the RRSIGs using the simple scheme */ static int zonemd_simple_rrsig(struct auth_zone* z, int hashalgo, struct secalgo_hash* h, struct auth_data* node, struct auth_rrset* rrset, struct auth_rrset** rrlist, size_t rrnum, struct regional* region, struct sldns_buffer* buf, char** reason) { /* the rrset pointer can be NULL, this means it is type RRSIG and * there is no ordinary type RRSIG there. The RRSIGs are stored * with the RRsets in their data. * * The RRset pointer can be nonNULL. This happens if there is * no RR that is covered by the RRSIG for the domain. Then this * RRSIG RR is stored in an rrset of type RRSIG. The other RRSIGs * are stored in the rrset entries for the RRs in the rr list for * the domain node. We need to collate the rrset's data, if any, and * the rrlist's rrsigs */ /* if this is the apex, omit RRSIGs that cover type ZONEMD */ /* build rrsig rrset */ size_t done = 0; struct ub_packed_rrset_key key; struct packed_rrset_data data; memset(&key, 0, sizeof(key)); memset(&data, 0, sizeof(data)); key.entry.key = &key; key.entry.data = &data; key.rk.dname = node->name; key.rk.dname_len = node->namelen; key.rk.type = htons(LDNS_RR_TYPE_RRSIG); key.rk.rrset_class = htons(z->dclass); data.count = zonemd_simple_count_rrsig(rrset, rrlist, rrnum, z, node); if(!zonemd_simple_rrsig_allocs(region, &data, data.count)) { *reason = "out of memory"; regional_free_all(region); return 0; } /* all the RRSIGs stored in the other rrsets for this domain node */ add_rrlist_rrsigs_into_data(&data, &done, rrlist, rrnum, z, node); /* plus the RRSIGs stored in an rrset of type RRSIG for this node */ add_rrset_into_data(&data, &done, rrset, z, node); /* canonicalize */ if(!rrset_canonicalize_to_buffer(region, buf, &key)) { *reason = "out of memory"; regional_free_all(region); return 0; } regional_free_all(region); /* hash */ if(!zonemd_digest_update(hashalgo, h, sldns_buffer_begin(buf), sldns_buffer_limit(buf), reason)) { return 0; } return 1; } /** collate a domain's rrsets using the simple scheme */ static int zonemd_simple_domain(struct auth_zone* z, int hashalgo, struct secalgo_hash* h, struct auth_data* node, struct regional* region, struct sldns_buffer* buf, char** reason) { const size_t rrlistsize = 65536; struct auth_rrset* rrlist[rrlistsize]; size_t i, rrnum = 0; /* see if the domain is out of scope, the zone origin, * that would be omitted */ if(!dname_subdomain_c(node->name, z->name)) return 1; /* continue */ /* loop over the rrsets in ascending order. */ rrnum = authdata_rrsets_to_list(rrlist, rrlistsize, node->rrsets); addrrsigtype_if_needed(rrlist, rrlistsize, &rrnum, node); qsort(rrlist, rrnum, sizeof(*rrlist), rrlist_compare); for(i=0; itype == LDNS_RR_TYPE_ZONEMD && query_dname_compare(z->name, node->name) == 0) { /* omit type ZONEMD at apex */ continue; } if(rrlist[i] == NULL || rrlist[i]->type == LDNS_RR_TYPE_RRSIG) { if(!zonemd_simple_rrsig(z, hashalgo, h, node, rrlist[i], rrlist, rrnum, region, buf, reason)) return 0; } else if(!zonemd_simple_rrset(z, hashalgo, h, node, rrlist[i], region, buf, reason)) { return 0; } } return 1; } /** collate the zone using the simple scheme */ static int zonemd_simple_collate(struct auth_zone* z, int hashalgo, struct secalgo_hash* h, struct regional* region, struct sldns_buffer* buf, char** reason) { /* our tree is sorted in canonical order, so we can just loop over * the tree */ struct auth_data* n; RBTREE_FOR(n, struct auth_data*, &z->data) { if(!zonemd_simple_domain(z, hashalgo, h, n, region, buf, reason)) return 0; } return 1; } int auth_zone_generate_zonemd_hash(struct auth_zone* z, int scheme, int hashalgo, uint8_t* hash, size_t hashlen, size_t* resultlen, struct regional* region, struct sldns_buffer* buf, char** reason) { struct secalgo_hash* h = zonemd_digest_init(hashalgo, reason); if(!h) { if(!*reason) *reason = "digest init fail"; return 0; } if(scheme == ZONEMD_SCHEME_SIMPLE) { if(!zonemd_simple_collate(z, hashalgo, h, region, buf, reason)) { if(!*reason) *reason = "scheme simple collate fail"; secalgo_hash_delete(h); return 0; } } if(!zonemd_digest_finish(hashalgo, h, hash, hashlen, resultlen, reason)) { secalgo_hash_delete(h); *reason = "digest finish fail"; return 0; } secalgo_hash_delete(h); return 1; } int auth_zone_generate_zonemd_check(struct auth_zone* z, int scheme, int hashalgo, uint8_t* hash, size_t hashlen, struct regional* region, struct sldns_buffer* buf, char** reason) { uint8_t gen[512]; size_t genlen = 0; *reason = NULL; if(!zonemd_hashalgo_supported(hashalgo)) { /* allow it */ *reason = "unsupported algorithm"; return 1; } if(!zonemd_scheme_supported(scheme)) { /* allow it */ *reason = "unsupported scheme"; return 1; } if(hashlen < 12) { /* the ZONEMD draft requires digests to fail if too small */ *reason = "digest length too small, less than 12"; return 0; } /* generate digest */ if(!auth_zone_generate_zonemd_hash(z, scheme, hashalgo, gen, sizeof(gen), &genlen, region, buf, reason)) { /* reason filled in by zonemd hash routine */ return 0; } /* check digest length */ if(hashlen != genlen) { *reason = "incorrect digest length"; if(verbosity >= VERB_ALGO) { verbose(VERB_ALGO, "zonemd scheme=%d hashalgo=%d", scheme, hashalgo); log_hex("ZONEMD should be ", gen, genlen); log_hex("ZONEMD to check is", hash, hashlen); } return 0; } /* check digest */ if(memcmp(hash, gen, genlen) != 0) { *reason = "incorrect digest"; if(verbosity >= VERB_ALGO) { verbose(VERB_ALGO, "zonemd scheme=%d hashalgo=%d", scheme, hashalgo); log_hex("ZONEMD should be ", gen, genlen); log_hex("ZONEMD to check is", hash, hashlen); } return 0; } return 1; } /** log auth zone message with zone name in front. */ static void auth_zone_log(uint8_t* name, enum verbosity_value level, const char* format, ...) ATTR_FORMAT(printf, 3, 4); static void auth_zone_log(uint8_t* name, enum verbosity_value level, const char* format, ...) { va_list args; va_start(args, format); if(verbosity >= level) { char str[255+1]; char msg[MAXSYSLOGMSGLEN]; dname_str(name, str); vsnprintf(msg, sizeof(msg), format, args); verbose(level, "auth zone %s %s", str, msg); } va_end(args); } /** ZONEMD, dnssec verify the rrset with the dnskey */ static int zonemd_dnssec_verify_rrset(struct auth_zone* z, struct module_env* env, struct module_stack* mods, struct ub_packed_rrset_key* dnskey, struct auth_data* node, struct auth_rrset* rrset, char** why_bogus, uint8_t* sigalg) { struct ub_packed_rrset_key pk; enum sec_status sec; struct val_env* ve; int m; m = modstack_find(mods, "validator"); if(m == -1) { auth_zone_log(z->name, VERB_ALGO, "zonemd dnssec verify: have " "DNSKEY chain of trust, but no validator module"); return 0; } ve = (struct val_env*)env->modinfo[m]; memset(&pk, 0, sizeof(pk)); pk.entry.key = &pk; pk.entry.data = rrset->data; pk.rk.dname = node->name; pk.rk.dname_len = node->namelen; pk.rk.type = htons(rrset->type); pk.rk.rrset_class = htons(z->dclass); if(verbosity >= VERB_ALGO) { char typestr[32]; typestr[0]=0; sldns_wire2str_type_buf(rrset->type, typestr, sizeof(typestr)); auth_zone_log(z->name, VERB_ALGO, "zonemd: verify %s RRset with DNSKEY", typestr); } sec = dnskeyset_verify_rrset(env, ve, &pk, dnskey, sigalg, why_bogus, NULL, LDNS_SECTION_ANSWER, NULL); if(sec == sec_status_secure) { return 1; } if(why_bogus) auth_zone_log(z->name, VERB_ALGO, "DNSSEC verify was bogus: %s", *why_bogus); return 0; } /** check for nsec3, the RR with params equal, if bitmap has the type */ static int nsec3_of_param_has_type(struct auth_rrset* nsec3, int algo, size_t iter, uint8_t* salt, size_t saltlen, uint16_t rrtype) { int i, count = (int)nsec3->data->count; struct ub_packed_rrset_key pk; memset(&pk, 0, sizeof(pk)); pk.entry.data = nsec3->data; for(i=0; idata; if(nsec_has_type(&pk, LDNS_RR_TYPE_ZONEMD)) { *reason = "DNSSEC NSEC bitmap says type ZONEMD exists"; return 0; } auth_zone_log(z->name, VERB_ALGO, "zonemd DNSSEC NSEC verification of absence of ZONEMD secure"); } else { /* NSEC3 perhaps ? */ int algo; size_t iter, saltlen; uint8_t* salt; struct auth_rrset* nsec3param = az_domain_rrset(apex, LDNS_RR_TYPE_NSEC3PARAM); struct auth_data* match; struct auth_rrset* nsec3; if(!nsec3param) { *reason = "zone has no NSEC information but ZONEMD missing"; return 0; } if(!az_nsec3_param(z, &algo, &iter, &salt, &saltlen)) { *reason = "zone has no NSEC information but ZONEMD missing"; return 0; } /* find the NSEC3 record */ match = az_nsec3_find_exact(z, z->name, z->namelen, algo, iter, salt, saltlen); if(!match) { *reason = "zone has no NSEC3 domain for the apex but ZONEMD missing"; return 0; } nsec3 = az_domain_rrset(match, LDNS_RR_TYPE_NSEC3); if(!nsec3) { *reason = "zone has no NSEC3 RRset for the apex but ZONEMD missing"; return 0; } /* dnssec verify the NSEC3 */ if(!zonemd_dnssec_verify_rrset(z, env, mods, dnskey, match, nsec3, why_bogus, sigalg)) { *reason = "DNSSEC verify failed for NSEC3 RRset"; return 0; } /* check type bitmap */ if(nsec3_of_param_has_type(nsec3, algo, iter, salt, saltlen, LDNS_RR_TYPE_ZONEMD)) { *reason = "DNSSEC NSEC3 bitmap says type ZONEMD exists"; return 0; } auth_zone_log(z->name, VERB_ALGO, "zonemd DNSSEC NSEC3 verification of absence of ZONEMD secure"); } return 1; } /** Verify the SOA and ZONEMD DNSSEC signatures. * return false on failure, reason contains description of failure. */ static int zonemd_check_dnssec_soazonemd(struct auth_zone* z, struct module_env* env, struct module_stack* mods, struct ub_packed_rrset_key* dnskey, struct auth_data* apex, struct auth_rrset* zonemd_rrset, char** reason, char** why_bogus, uint8_t* sigalg) { struct auth_rrset* soa; if(!apex) { *reason = "zone has no apex domain"; return 0; } soa = az_domain_rrset(apex, LDNS_RR_TYPE_SOA); if(!soa) { *reason = "zone has no SOA RRset"; return 0; } if(!zonemd_dnssec_verify_rrset(z, env, mods, dnskey, apex, soa, why_bogus, sigalg)) { *reason = "DNSSEC verify failed for SOA RRset"; return 0; } if(!zonemd_dnssec_verify_rrset(z, env, mods, dnskey, apex, zonemd_rrset, why_bogus, sigalg)) { *reason = "DNSSEC verify failed for ZONEMD RRset"; return 0; } auth_zone_log(z->name, VERB_ALGO, "zonemd DNSSEC verification of SOA and ZONEMD RRsets secure"); return 1; } /** * Fail the ZONEMD verification. * @param z: auth zone that fails. * @param env: environment with config, to ignore failure or not. * @param reason: failure string description. * @param why_bogus: failure string for DNSSEC verification failure. * @param result: strdup result in here if not NULL. */ static void auth_zone_zonemd_fail(struct auth_zone* z, struct module_env* env, char* reason, char* why_bogus, char** result) { char zstr[255+1]; /* if fail: log reason, and depending on config also take action * and drop the zone, eg. it is gone from memory, set zone_expired */ dname_str(z->name, zstr); if(!reason) reason = "verification failed"; if(result) { if(why_bogus) { char res[1024]; snprintf(res, sizeof(res), "%s: %s", reason, why_bogus); *result = strdup(res); } else { *result = strdup(reason); } if(!*result) log_err("out of memory"); } else { log_warn("auth zone %s: ZONEMD verification failed: %s", zstr, reason); } if(env->cfg->zonemd_permissive_mode) { verbose(VERB_ALGO, "zonemd-permissive-mode enabled, " "not blocking zone %s", zstr); return; } /* expired means the zone gives servfail and is not used by * lookup if fallback_enabled*/ z->zone_expired = 1; } /** * Verify the zonemd with DNSSEC and hash check, with given key. * @param z: auth zone. * @param env: environment with config and temp buffers. * @param mods: module stack with validator env for verification. * @param dnskey: dnskey that we can use, or NULL. If nonnull, the key * has been verified and is the start of the chain of trust. * @param is_insecure: if true, the dnskey is not used, the zone is insecure. * And dnssec is not used. It is DNSSEC secure insecure or not under * a trust anchor. * @param sigalg: if nonNULL provide algorithm downgrade protection. * Otherwise one algorithm is enough. Must have space of ALGO_NEEDS_MAX+1. * @param result: if not NULL result reason copied here. */ static void auth_zone_verify_zonemd_with_key(struct auth_zone* z, struct module_env* env, struct module_stack* mods, struct ub_packed_rrset_key* dnskey, int is_insecure, char** result, uint8_t* sigalg) { char* reason = NULL, *why_bogus = NULL; struct auth_data* apex = NULL; struct auth_rrset* zonemd_rrset = NULL; int zonemd_absent = 0, zonemd_absence_dnssecok = 0; /* see if ZONEMD is present or absent. */ apex = az_find_name(z, z->name, z->namelen); if(!apex) { zonemd_absent = 1; } else { zonemd_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_ZONEMD); if(!zonemd_rrset || zonemd_rrset->data->count==0) { zonemd_absent = 1; zonemd_rrset = NULL; } } /* if no DNSSEC, done. */ /* if no ZONEMD, and DNSSEC, use DNSKEY to verify NSEC or NSEC3 for * zone apex. Check ZONEMD bit is turned off or else fail */ /* if ZONEMD, and DNSSEC, check DNSSEC signature on SOA and ZONEMD, * or else fail */ if(!dnskey && !is_insecure) { auth_zone_zonemd_fail(z, env, "DNSKEY missing", NULL, result); return; } else if(!zonemd_rrset && dnskey && !is_insecure) { /* fetch, DNSSEC verify, and check NSEC/NSEC3 */ if(!zonemd_check_dnssec_absence(z, env, mods, dnskey, apex, &reason, &why_bogus, sigalg)) { auth_zone_zonemd_fail(z, env, reason, why_bogus, result); return; } zonemd_absence_dnssecok = 1; } else if(zonemd_rrset && dnskey && !is_insecure) { /* check DNSSEC verify of SOA and ZONEMD */ if(!zonemd_check_dnssec_soazonemd(z, env, mods, dnskey, apex, zonemd_rrset, &reason, &why_bogus, sigalg)) { auth_zone_zonemd_fail(z, env, reason, why_bogus, result); return; } } if(zonemd_absent && z->zonemd_reject_absence) { auth_zone_zonemd_fail(z, env, "ZONEMD absent and that is not allowed by config", NULL, result); return; } if(zonemd_absent && zonemd_absence_dnssecok) { auth_zone_log(z->name, VERB_ALGO, "DNSSEC verified nonexistence of ZONEMD"); if(result) { *result = strdup("DNSSEC verified nonexistence of ZONEMD"); if(!*result) log_err("out of memory"); } return; } if(zonemd_absent) { auth_zone_log(z->name, VERB_ALGO, "no ZONEMD present"); if(result) { *result = strdup("no ZONEMD present"); if(!*result) log_err("out of memory"); } return; } /* check ZONEMD checksum and report or else fail. */ if(!auth_zone_zonemd_check_hash(z, env, &reason)) { auth_zone_zonemd_fail(z, env, reason, NULL, result); return; } /* success! log the success */ if(reason) auth_zone_log(z->name, VERB_ALGO, "ZONEMD %s", reason); else auth_zone_log(z->name, VERB_ALGO, "ZONEMD verification successful"); if(result) { if(reason) *result = strdup(reason); else *result = strdup("ZONEMD verification successful"); if(!*result) log_err("out of memory"); } } /** * verify the zone DNSKEY rrset from the trust anchor * This is possible because the anchor is for the zone itself, and can * thus apply straight to the zone DNSKEY set. * @param z: the auth zone. * @param env: environment with time and temp buffers. * @param mods: module stack for validator environment for dnssec validation. * @param anchor: trust anchor to use * @param is_insecure: returned, true if the zone is securely insecure. * @param why_bogus: if the routine fails, returns the failure reason. * @param keystorage: where to store the ub_packed_rrset_key that is created * on success. A pointer to it is returned on success. * @return the dnskey RRset, reference to zone data and keystorage, or * NULL on failure. */ static struct ub_packed_rrset_key* zonemd_get_dnskey_from_anchor(struct auth_zone* z, struct module_env* env, struct module_stack* mods, struct trust_anchor* anchor, int* is_insecure, char** why_bogus, struct ub_packed_rrset_key* keystorage) { struct auth_data* apex; struct auth_rrset* dnskey_rrset; enum sec_status sec; struct val_env* ve; int m; apex = az_find_name(z, z->name, z->namelen); if(!apex) { *why_bogus = "have trust anchor, but zone has no apex domain for DNSKEY"; return 0; } dnskey_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_DNSKEY); if(!dnskey_rrset || dnskey_rrset->data->count==0) { *why_bogus = "have trust anchor, but zone has no DNSKEY"; return 0; } m = modstack_find(mods, "validator"); if(m == -1) { *why_bogus = "have trust anchor, but no validator module"; return 0; } ve = (struct val_env*)env->modinfo[m]; memset(keystorage, 0, sizeof(*keystorage)); keystorage->entry.key = keystorage; keystorage->entry.data = dnskey_rrset->data; keystorage->rk.dname = apex->name; keystorage->rk.dname_len = apex->namelen; keystorage->rk.type = htons(LDNS_RR_TYPE_DNSKEY); keystorage->rk.rrset_class = htons(z->dclass); auth_zone_log(z->name, VERB_QUERY, "zonemd: verify DNSKEY RRset with trust anchor"); sec = val_verify_DNSKEY_with_TA(env, ve, keystorage, anchor->ds_rrset, anchor->dnskey_rrset, NULL, why_bogus, NULL, NULL); regional_free_all(env->scratch); if(sec == sec_status_secure) { /* success */ *is_insecure = 0; return keystorage; } else if(sec == sec_status_insecure) { /* insecure */ *is_insecure = 1; } else { /* bogus */ *is_insecure = 0; auth_zone_log(z->name, VERB_ALGO, "zonemd: verify DNSKEY RRset with trust anchor failed: %s", *why_bogus); } return NULL; } /** verify the DNSKEY from the zone with looked up DS record */ static struct ub_packed_rrset_key* auth_zone_verify_zonemd_key_with_ds(struct auth_zone* z, struct module_env* env, struct module_stack* mods, struct ub_packed_rrset_key* ds, int* is_insecure, char** why_bogus, struct ub_packed_rrset_key* keystorage, uint8_t* sigalg) { struct auth_data* apex; struct auth_rrset* dnskey_rrset; enum sec_status sec; struct val_env* ve; int m; /* fetch DNSKEY from zone data */ apex = az_find_name(z, z->name, z->namelen); if(!apex) { *why_bogus = "in verifywithDS, zone has no apex"; return NULL; } dnskey_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_DNSKEY); if(!dnskey_rrset || dnskey_rrset->data->count==0) { *why_bogus = "in verifywithDS, zone has no DNSKEY"; return NULL; } m = modstack_find(mods, "validator"); if(m == -1) { *why_bogus = "in verifywithDS, have no validator module"; return NULL; } ve = (struct val_env*)env->modinfo[m]; memset(keystorage, 0, sizeof(*keystorage)); keystorage->entry.key = keystorage; keystorage->entry.data = dnskey_rrset->data; keystorage->rk.dname = apex->name; keystorage->rk.dname_len = apex->namelen; keystorage->rk.type = htons(LDNS_RR_TYPE_DNSKEY); keystorage->rk.rrset_class = htons(z->dclass); auth_zone_log(z->name, VERB_QUERY, "zonemd: verify zone DNSKEY with DS"); sec = val_verify_DNSKEY_with_DS(env, ve, keystorage, ds, sigalg, why_bogus, NULL, NULL); regional_free_all(env->scratch); if(sec == sec_status_secure) { /* success */ return keystorage; } else if(sec == sec_status_insecure) { /* insecure */ *is_insecure = 1; } else { /* bogus */ *is_insecure = 0; if(*why_bogus == NULL) *why_bogus = "verify failed"; auth_zone_log(z->name, VERB_ALGO, "zonemd: verify DNSKEY RRset with DS failed: %s", *why_bogus); } return NULL; } /** callback for ZONEMD lookup of DNSKEY */ void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode, sldns_buffer* buf, enum sec_status sec, char* why_bogus, int ATTR_UNUSED(was_ratelimited)) { struct auth_zone* z = (struct auth_zone*)arg; struct module_env* env; char* reason = NULL, *ds_bogus = NULL, *typestr="DNSKEY"; struct ub_packed_rrset_key* dnskey = NULL, *ds = NULL; int is_insecure = 0, downprot; struct ub_packed_rrset_key keystorage; uint8_t sigalg[ALGO_NEEDS_MAX+1]; lock_rw_wrlock(&z->lock); env = z->zonemd_callback_env; /* release the env variable so another worker can pick up the * ZONEMD verification task if it wants to */ z->zonemd_callback_env = NULL; if(!env || env->outnet->want_to_quit || z->zone_deleted) { lock_rw_unlock(&z->lock); return; /* stop on quit */ } if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DS) typestr = "DS"; downprot = env->cfg->harden_algo_downgrade; /* process result */ if(sec == sec_status_bogus) { reason = why_bogus; if(!reason) { if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY) reason = "lookup of DNSKEY was bogus"; else reason = "lookup of DS was bogus"; } auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was bogus: %s", typestr, reason); } else if(rcode == LDNS_RCODE_NOERROR) { uint16_t wanted_qtype = z->zonemd_callback_qtype; struct regional* temp = env->scratch; struct query_info rq; struct reply_info* rep; memset(&rq, 0, sizeof(rq)); rep = parse_reply_in_temp_region(buf, temp, &rq); if(rep && rq.qtype == wanted_qtype && query_dname_compare(z->name, rq.qname) == 0 && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR) { /* parsed successfully */ struct ub_packed_rrset_key* answer = reply_find_answer_rrset(&rq, rep); if(answer && sec == sec_status_secure) { if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY) dnskey = answer; else ds = answer; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was secure", typestr); } else if(sec == sec_status_secure && !answer) { is_insecure = 1; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s has no content, but is secure, treat as insecure", typestr); } else if(sec == sec_status_insecure) { is_insecure = 1; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was insecure", typestr); } else if(sec == sec_status_indeterminate) { is_insecure = 1; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was indeterminate, treat as insecure", typestr); } else { auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s has nodata", typestr); if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY) reason = "lookup of DNSKEY has nodata"; else reason = "lookup of DS has nodata"; } } else if(rep && rq.qtype == wanted_qtype && query_dname_compare(z->name, rq.qname) == 0 && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN && sec == sec_status_secure) { /* secure nxdomain, so the zone is like some RPZ zone * that does not exist in the wider internet, with * a secure nxdomain answer outside of it. So we * treat the zonemd zone without a dnssec chain of * trust, as insecure. */ is_insecure = 1; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was secure NXDOMAIN, treat as insecure", typestr); } else if(rep && rq.qtype == wanted_qtype && query_dname_compare(z->name, rq.qname) == 0 && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN && sec == sec_status_insecure) { is_insecure = 1; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was insecure NXDOMAIN, treat as insecure", typestr); } else if(rep && rq.qtype == wanted_qtype && query_dname_compare(z->name, rq.qname) == 0 && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN && sec == sec_status_indeterminate) { is_insecure = 1; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was indeterminate NXDOMAIN, treat as insecure", typestr); } else { auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s has no answer", typestr); if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY) reason = "lookup of DNSKEY has no answer"; else reason = "lookup of DS has no answer"; } } else { auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s failed", typestr); if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY) reason = "lookup of DNSKEY failed"; else reason = "lookup of DS failed"; } if(!reason && !is_insecure && !dnskey && ds) { dnskey = auth_zone_verify_zonemd_key_with_ds(z, env, &env->mesh->mods, ds, &is_insecure, &ds_bogus, &keystorage, downprot?sigalg:NULL); if(!dnskey && !is_insecure && !reason) reason = "DNSKEY verify with DS failed"; } if(reason) { auth_zone_zonemd_fail(z, env, reason, ds_bogus, NULL); lock_rw_unlock(&z->lock); return; } auth_zone_verify_zonemd_with_key(z, env, &env->mesh->mods, dnskey, is_insecure, NULL, downprot?sigalg:NULL); regional_free_all(env->scratch); lock_rw_unlock(&z->lock); } /** lookup DNSKEY for ZONEMD verification */ static int zonemd_lookup_dnskey(struct auth_zone* z, struct module_env* env) { struct query_info qinfo; uint16_t qflags = BIT_RD; struct edns_data edns; sldns_buffer* buf = env->scratch_buffer; int fetch_ds = 0; if(!z->fallback_enabled) { /* we cannot actually get the DNSKEY, because it is in the * zone we have ourselves, and it is not served yet * (possibly), so fetch type DS */ fetch_ds = 1; } if(z->zonemd_callback_env) { /* another worker is already working on the callback * for the DNSKEY lookup for ZONEMD verification. * We do not also have to do ZONEMD verification, let that * worker do it */ auth_zone_log(z->name, VERB_ALGO, "zonemd needs lookup of %s and that already is worked on by another worker", (fetch_ds?"DS":"DNSKEY")); return 1; } /* use mesh_new_callback to lookup the DNSKEY, * and then wait for them to be looked up (in cache, or query) */ qinfo.qname_len = z->namelen; qinfo.qname = z->name; qinfo.qclass = z->dclass; if(fetch_ds) qinfo.qtype = LDNS_RR_TYPE_DS; else qinfo.qtype = LDNS_RR_TYPE_DNSKEY; qinfo.local_alias = NULL; if(verbosity >= VERB_ALGO) { char buf1[512]; char buf2[LDNS_MAX_DOMAINLEN+1]; dname_str(z->name, buf2); snprintf(buf1, sizeof(buf1), "auth zone %s: lookup %s " "for zonemd verification", buf2, (fetch_ds?"DS":"DNSKEY")); log_query_info(VERB_ALGO, buf1, &qinfo); } edns.edns_present = 1; edns.ext_rcode = 0; edns.edns_version = 0; edns.bits = EDNS_DO; edns.opt_list_in = NULL; edns.opt_list_out = NULL; edns.opt_list_inplace_cb_out = NULL; if(sldns_buffer_capacity(buf) < 65535) edns.udp_size = (uint16_t)sldns_buffer_capacity(buf); else edns.udp_size = 65535; /* store the worker-specific module env for the callback. * We can then reference this when the callback executes */ z->zonemd_callback_env = env; z->zonemd_callback_qtype = qinfo.qtype; /* the callback can be called straight away */ lock_rw_unlock(&z->lock); if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, &auth_zonemd_dnskey_lookup_callback, z, 0)) { lock_rw_wrlock(&z->lock); log_err("out of memory lookup of %s for zonemd", (fetch_ds?"DS":"DNSKEY")); return 0; } lock_rw_wrlock(&z->lock); return 1; } void auth_zone_verify_zonemd(struct auth_zone* z, struct module_env* env, struct module_stack* mods, char** result, int offline, int only_online) { char* reason = NULL, *why_bogus = NULL; struct trust_anchor* anchor = NULL; struct ub_packed_rrset_key* dnskey = NULL; struct ub_packed_rrset_key keystorage; int is_insecure = 0; /* verify the ZONEMD if present. * If not present check if absence is allowed by DNSSEC */ if(!z->zonemd_check) return; if(z->data.count == 0) return; /* no data */ /* if zone is under a trustanchor */ /* is it equal to trustanchor - get dnskey's verified */ /* else, find chain of trust by fetching DNSKEYs lookup for zone */ /* result if that, if insecure, means no DNSSEC for the ZONEMD, * otherwise we have the zone DNSKEY for the DNSSEC verification. */ if(env->anchors) anchor = anchors_lookup(env->anchors, z->name, z->namelen, z->dclass); if(anchor && anchor->numDS == 0 && anchor->numDNSKEY == 0) { /* domain-insecure trust anchor for unsigned zones */ lock_basic_unlock(&anchor->lock); if(only_online) return; dnskey = NULL; is_insecure = 1; } else if(anchor && query_dname_compare(z->name, anchor->name) == 0) { if(only_online) { lock_basic_unlock(&anchor->lock); return; } /* equal to trustanchor, no need for online lookups */ dnskey = zonemd_get_dnskey_from_anchor(z, env, mods, anchor, &is_insecure, &why_bogus, &keystorage); lock_basic_unlock(&anchor->lock); if(!dnskey && !reason && !is_insecure) { reason = "verify DNSKEY RRset with trust anchor failed"; } } else if(anchor) { lock_basic_unlock(&anchor->lock); /* perform online lookups */ if(offline) return; /* setup online lookups, and wait for them */ if(zonemd_lookup_dnskey(z, env)) { /* wait for the lookup */ return; } reason = "could not lookup DNSKEY for chain of trust"; } else { /* the zone is not under a trust anchor */ if(only_online) return; dnskey = NULL; is_insecure = 1; } if(reason) { auth_zone_zonemd_fail(z, env, reason, why_bogus, result); return; } auth_zone_verify_zonemd_with_key(z, env, mods, dnskey, is_insecure, result, NULL); regional_free_all(env->scratch); } void auth_zones_pickup_zonemd_verify(struct auth_zones* az, struct module_env* env) { struct auth_zone key; uint8_t savezname[255+1]; size_t savezname_len; struct auth_zone* z; key.node.key = &key; lock_rw_rdlock(&az->lock); RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_wrlock(&z->lock); if(!z->zonemd_check) { lock_rw_unlock(&z->lock); continue; } key.dclass = z->dclass; key.namelabs = z->namelabs; if(z->namelen > sizeof(savezname)) { lock_rw_unlock(&z->lock); log_err("auth_zones_pickup_zonemd_verify: zone name too long"); continue; } savezname_len = z->namelen; memmove(savezname, z->name, z->namelen); lock_rw_unlock(&az->lock); auth_zone_verify_zonemd(z, env, &env->mesh->mods, NULL, 0, 1); lock_rw_unlock(&z->lock); lock_rw_rdlock(&az->lock); /* find the zone we had before, it is not deleted, * because we have a flag for that that is processed at * apply_cfg time */ key.namelen = savezname_len; key.name = savezname; z = (struct auth_zone*)rbtree_search(&az->ztree, &key); if(!z) break; } lock_rw_unlock(&az->lock); }