diff options
Diffstat (limited to 'external/unbound/services/localzone.c')
-rw-r--r-- | external/unbound/services/localzone.c | 797 |
1 files changed, 604 insertions, 193 deletions
diff --git a/external/unbound/services/localzone.c b/external/unbound/services/localzone.c index c50ad0f15..dcce46e86 100644 --- a/external/unbound/services/localzone.c +++ b/external/unbound/services/localzone.c @@ -51,6 +51,12 @@ #include "util/netevent.h" #include "util/data/msgreply.h" #include "util/data/msgparse.h" +#include "util/as112.h" +#include "util/config_file.h" + +/* maximum RRs in an RRset, to cap possible 'endless' list RRs. + * with 16 bytes for an A record, a 64K packet has about 4000 max */ +#define LOCALZONE_RRSET_COUNT_MAX 4096 struct local_zones* local_zones_create(void) @@ -68,7 +74,7 @@ local_zones_create(void) /** helper traverse to delete zones */ static void -lzdel(rbnode_t* n, void* ATTR_UNUSED(arg)) +lzdel(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct local_zone* z = (struct local_zone*)n->key; local_zone_delete(z); @@ -93,6 +99,7 @@ local_zone_delete(struct local_zone* z) lock_rw_destroy(&z->lock); regional_destroy(z->region); free(z->name); + free(z->taglist); free(z); } @@ -152,13 +159,13 @@ local_zone_create(uint8_t* nm, size_t len, int labs, z->namelen = len; z->namelabs = labs; lock_rw_init(&z->lock); - z->region = regional_create(); + z->region = regional_create_custom(sizeof(struct regional)); if(!z->region) { free(z); return NULL; } rbtree_init(&z->data, &local_data_cmp); - lock_protect(&z->lock, &z->parent, sizeof(*z)-sizeof(rbnode_t)); + lock_protect(&z->lock, &z->parent, sizeof(*z)-sizeof(rbnode_type)); /* also the zones->lock protects node, parent, name*, class */ return z; } @@ -170,6 +177,7 @@ lz_enter_zone_dname(struct local_zones* zones, uint8_t* nm, size_t len, { struct local_zone* z = local_zone_create(nm, len, labs, t, c); if(!z) { + free(nm); log_err("out of memory"); return NULL; } @@ -178,11 +186,18 @@ lz_enter_zone_dname(struct local_zones* zones, uint8_t* nm, size_t len, lock_rw_wrlock(&zones->lock); lock_rw_wrlock(&z->lock); if(!rbtree_insert(&zones->ztree, &z->node)) { + struct local_zone* oldz; log_warn("duplicate local-zone"); lock_rw_unlock(&z->lock); - local_zone_delete(z); + /* save zone name locally before deallocation, + * otherwise, nm is gone if we zone_delete now. */ + oldz = z; + /* find the correct zone, so not an error for duplicate */ + z = local_zones_find(zones, nm, len, labs, c); + lock_rw_wrlock(&z->lock); lock_rw_unlock(&zones->lock); - return NULL; + local_zone_delete(oldz); + return z; } lock_rw_unlock(&zones->lock); return z; @@ -214,9 +229,8 @@ lz_enter_zone(struct local_zones* zones, const char* name, const char* type, return z; } -/** return name and class and rdata of rr; parses string */ -static int -get_rr_content(const char* str, uint8_t** nm, uint16_t* type, +int +rrstr_get_rr_content(const char* str, uint8_t** nm, uint16_t* type, uint16_t* dclass, time_t* ttl, uint8_t* rr, size_t len, uint8_t** rdata, size_t* rdata_len) { @@ -269,16 +283,20 @@ get_rr_nameclass(const char* str, uint8_t** nm, uint16_t* dclass) * Find an rrset in local data structure. * @param data: local data domain name structure. * @param type: type to look for (host order). + * @param alias_ok: 1 if matching a non-exact, alias type such as CNAME is + * allowed. otherwise 0. * @return rrset pointer or NULL if not found. */ static struct local_rrset* -local_data_find_type(struct local_data* data, uint16_t type) +local_data_find_type(struct local_data* data, uint16_t type, int alias_ok) { struct local_rrset* p; type = htons(type); for(p = data->rrsets; p; p = p->next) { if(p->rrset->rk.type == type) return p; + if(alias_ok && p->rrset->rk.type == htons(LDNS_RR_TYPE_CNAME)) + return p; } return NULL; } @@ -334,15 +352,20 @@ new_local_rrset(struct regional* region, struct local_data* node, } /** insert RR into RRset data structure; Wastes a couple of bytes */ -static int -insert_rr(struct regional* region, struct packed_rrset_data* pd, - uint8_t* rdata, size_t rdata_len, time_t ttl) +int +rrset_insert_rr(struct regional* region, struct packed_rrset_data* pd, + uint8_t* rdata, size_t rdata_len, time_t ttl, const char* rrstr) { size_t* oldlen = pd->rr_len; time_t* oldttl = pd->rr_ttl; uint8_t** olddata = pd->rr_data; /* add RR to rrset */ + if(pd->count > LOCALZONE_RRSET_COUNT_MAX) { + log_warn("RRset '%s' has more than %d records, record ignored", + rrstr, LOCALZONE_RRSET_COUNT_MAX); + return 1; + } pd->count++; pd->rr_len = regional_alloc(region, sizeof(*pd->rr_len)*pd->count); pd->rr_ttl = regional_alloc(region, sizeof(*pd->rr_ttl)*pd->count); @@ -432,8 +455,8 @@ lz_enter_rr_into_zone(struct local_zone* z, const char* rrstr) uint8_t rr[LDNS_RR_BUF_SIZE]; uint8_t* rdata; size_t rdata_len; - if(!get_rr_content(rrstr, &nm, &rrtype, &rrclass, &ttl, rr, sizeof(rr), - &rdata, &rdata_len)) { + if(!rrstr_get_rr_content(rrstr, &nm, &rrtype, &rrclass, &ttl, rr, + sizeof(rr), &rdata, &rdata_len)) { log_err("bad local-data: %s", rrstr); return 0; } @@ -453,7 +476,23 @@ lz_enter_rr_into_zone(struct local_zone* z, const char* rrstr) log_assert(node); free(nm); - rrset = local_data_find_type(node, rrtype); + /* Reject it if we would end up having CNAME and other data (including + * another CNAME) for a redirect zone. */ + if(z->type == local_zone_redirect && node->rrsets) { + const char* othertype = NULL; + if (rrtype == LDNS_RR_TYPE_CNAME) + othertype = "other"; + else if (node->rrsets->rrset->rk.type == + htons(LDNS_RR_TYPE_CNAME)) { + othertype = "CNAME"; + } + if(othertype) { + log_err("local-data '%s' in redirect zone must not " + "coexist with %s local-data", rrstr, othertype); + return 0; + } + } + rrset = local_data_find_type(node, rrtype, 0); if(!rrset) { rrset = new_local_rrset(z->region, node, rrtype, rrclass); if(!rrset) @@ -473,7 +512,7 @@ lz_enter_rr_into_zone(struct local_zone* z, const char* rrstr) verbose(VERB_ALGO, "ignoring duplicate RR: %s", rrstr); return 1; } - return insert_rr(z->region, pd, rdata, rdata_len, ttl); + return rrset_insert_rr(z->region, pd, rdata, rdata_len, ttl, rrstr); } /** enter a data RR into auth data; a zone for it must exist */ @@ -505,6 +544,123 @@ lz_enter_rr_str(struct local_zones* zones, const char* rr) return r; } +/** enter tagstring into zone */ +static int +lz_enter_zone_tag(struct local_zones* zones, char* zname, uint8_t* list, + size_t len, uint16_t rr_class) +{ + uint8_t dname[LDNS_MAX_DOMAINLEN+1]; + size_t dname_len = sizeof(dname); + int dname_labs, r = 0; + struct local_zone* z; + + if(sldns_str2wire_dname_buf(zname, dname, &dname_len) != 0) { + log_err("cannot parse zone name in local-zone-tag: %s", zname); + return 0; + } + dname_labs = dname_count_labels(dname); + + lock_rw_rdlock(&zones->lock); + z = local_zones_find(zones, dname, dname_len, dname_labs, rr_class); + if(!z) { + lock_rw_unlock(&zones->lock); + log_err("no local-zone for tag %s", zname); + return 0; + } + lock_rw_wrlock(&z->lock); + lock_rw_unlock(&zones->lock); + free(z->taglist); + z->taglist = memdup(list, len); + z->taglen = len; + if(z->taglist) + r = 1; + lock_rw_unlock(&z->lock); + return r; +} + +/** enter override into zone */ +static int +lz_enter_override(struct local_zones* zones, char* zname, char* netblock, + char* type, uint16_t rr_class) +{ + uint8_t dname[LDNS_MAX_DOMAINLEN+1]; + size_t dname_len = sizeof(dname); + int dname_labs; + struct sockaddr_storage addr; + int net; + socklen_t addrlen; + struct local_zone* z; + enum localzone_type t; + + /* parse zone name */ + if(sldns_str2wire_dname_buf(zname, dname, &dname_len) != 0) { + log_err("cannot parse zone name in local-zone-override: %s %s", + zname, netblock); + return 0; + } + dname_labs = dname_count_labels(dname); + + /* parse netblock */ + if(!netblockstrtoaddr(netblock, UNBOUND_DNS_PORT, &addr, &addrlen, + &net)) { + log_err("cannot parse netblock in local-zone-override: %s %s", + zname, netblock); + return 0; + } + + /* parse zone type */ + if(!local_zone_str2type(type, &t)) { + log_err("cannot parse type in local-zone-override: %s %s %s", + zname, netblock, type); + return 0; + } + + /* find localzone entry */ + lock_rw_rdlock(&zones->lock); + z = local_zones_find(zones, dname, dname_len, dname_labs, rr_class); + if(!z) { + lock_rw_unlock(&zones->lock); + log_err("no local-zone for local-zone-override %s", zname); + return 0; + } + lock_rw_wrlock(&z->lock); + lock_rw_unlock(&zones->lock); + + /* create netblock addr_tree if not present yet */ + if(!z->override_tree) { + z->override_tree = (struct rbtree_type*)regional_alloc_zero( + z->region, sizeof(*z->override_tree)); + if(!z->override_tree) { + lock_rw_unlock(&z->lock); + log_err("out of memory"); + return 0; + } + addr_tree_init(z->override_tree); + } + /* add new elem to tree */ + if(z->override_tree) { + struct local_zone_override* n; + n = (struct local_zone_override*)regional_alloc_zero( + z->region, sizeof(*n)); + if(!n) { + lock_rw_unlock(&z->lock); + log_err("out of memory"); + return 0; + } + n->type = t; + if(!addr_tree_insert(z->override_tree, + (struct addr_tree_node*)n, &addr, addrlen, net)) { + lock_rw_unlock(&z->lock); + log_err("duplicate local-zone-override %s %s", + zname, netblock); + return 1; + } + } + + lock_rw_unlock(&z->lock); + return 1; +} + /** parse local-zone: statements */ static int lz_enter_zones(struct local_zones* zones, struct config_file* cfg) @@ -592,10 +748,11 @@ static int lz_enter_defaults(struct local_zones* zones, struct config_file* cfg) { struct local_zone* z; + const char** zstr; - /* this list of zones is from RFC 6303 */ + /* this list of zones is from RFC 6303 and RFC 7686 */ - /* block localhost level zones, first, later the LAN zones */ + /* block localhost level zones first, then onion and later the LAN zones */ /* localhost. zone */ if(!lz_exists(zones, "localhost.") && @@ -653,111 +810,44 @@ lz_enter_defaults(struct local_zones* zones, struct config_file* cfg) } lock_rw_unlock(&z->lock); } + /* onion. zone (RFC 7686) */ + if(!lz_exists(zones, "onion.") && + !lz_nodefault(cfg, "onion.")) { + if(!(z=lz_enter_zone(zones, "onion.", "static", + LDNS_RR_CLASS_IN)) || + !lz_enter_rr_into_zone(z, + "onion. 10800 IN NS localhost.") || + !lz_enter_rr_into_zone(z, + "onion. 10800 IN SOA localhost. nobody.invalid. " + "1 3600 1200 604800 10800")) { + log_err("out of memory adding default zone"); + if(z) { lock_rw_unlock(&z->lock); } + return 0; + } + lock_rw_unlock(&z->lock); + } - /* if unblock lan-zones, then do not add the zones below. - * we do add the zones above, about 127.0.0.1, because localhost is - * not on the lan. */ - if(cfg->unblock_lan_zones) - return 1; + /* block AS112 zones, unless asked not to */ + if(!cfg->unblock_lan_zones) { + for(zstr = as112_zones; *zstr; zstr++) { + if(!add_as112_default(zones, cfg, *zstr)) { + log_err("out of memory adding default zone"); + return 0; + } + } + } + return 1; +} - /* block LAN level zones */ - if ( !add_as112_default(zones, cfg, "10.in-addr.arpa.") || - !add_as112_default(zones, cfg, "16.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "17.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "18.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "19.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "20.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "21.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "22.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "23.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "24.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "25.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "26.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "27.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "28.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "29.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "30.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "31.172.in-addr.arpa.") || - !add_as112_default(zones, cfg, "168.192.in-addr.arpa.") || - !add_as112_default(zones, cfg, "0.in-addr.arpa.") || - !add_as112_default(zones, cfg, "64.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "65.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "66.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "67.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "68.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "69.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "70.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "71.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "72.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "73.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "74.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "75.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "76.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "77.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "78.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "79.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "80.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "81.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "82.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "83.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "84.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "85.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "86.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "87.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "88.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "89.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "90.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "91.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "92.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "93.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "94.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "95.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "96.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "97.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "98.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "99.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "100.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "101.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "102.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "103.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "104.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "105.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "106.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "107.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "108.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "109.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "110.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "111.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "112.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "113.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "114.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "115.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "116.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "117.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "118.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "119.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "120.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "121.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "122.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "123.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "124.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "125.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "126.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "127.100.in-addr.arpa.") || - !add_as112_default(zones, cfg, "254.169.in-addr.arpa.") || - !add_as112_default(zones, cfg, "2.0.192.in-addr.arpa.") || - !add_as112_default(zones, cfg, "100.51.198.in-addr.arpa.") || - !add_as112_default(zones, cfg, "113.0.203.in-addr.arpa.") || - !add_as112_default(zones, cfg, "255.255.255.255.in-addr.arpa.") || - !add_as112_default(zones, cfg, "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.") || - !add_as112_default(zones, cfg, "d.f.ip6.arpa.") || - !add_as112_default(zones, cfg, "8.e.f.ip6.arpa.") || - !add_as112_default(zones, cfg, "9.e.f.ip6.arpa.") || - !add_as112_default(zones, cfg, "a.e.f.ip6.arpa.") || - !add_as112_default(zones, cfg, "b.e.f.ip6.arpa.") || - !add_as112_default(zones, cfg, "8.b.d.0.1.0.0.2.ip6.arpa.")) { - log_err("out of memory adding default zone"); - return 0; +/** parse local-zone-override: statements */ +static int +lz_enter_overrides(struct local_zones* zones, struct config_file* cfg) +{ + struct config_str3list* p; + for(p = cfg->local_zone_overrides; p; p = p->next) { + if(!lz_enter_override(zones, p->str, p->str2, p->str3, + LDNS_RR_CLASS_IN)) + return 0; } return 1; } @@ -791,6 +881,9 @@ init_parents(struct local_zones* zones) break; } prev = node; + + if(node->override_tree) + addr_tree_init_parents(node->override_tree); lock_rw_unlock(&node->lock); } lock_rw_unlock(&zones->lock); @@ -878,6 +971,22 @@ lz_setup_implicit(struct local_zones* zones, struct config_file* cfg) return 1; } +/** enter local-zone-tag info */ +static int +lz_enter_zone_tags(struct local_zones* zones, struct config_file* cfg) +{ + struct config_strbytelist* p; + int c = 0; + for(p = cfg->local_zone_tags; p; p = p->next) { + if(!lz_enter_zone_tag(zones, p->str, p->str2, p->str2len, + LDNS_RR_CLASS_IN)) + return 0; + c++; + } + if(c) verbose(VERB_ALGO, "applied tags to %d local zones", c); + return 1; +} + /** enter auth data */ static int lz_enter_data(struct local_zones* zones, struct config_file* cfg) @@ -913,6 +1022,10 @@ local_zones_apply_cfg(struct local_zones* zones, struct config_file* cfg) if(!lz_enter_defaults(zones, cfg)) { return 0; } + /* enter local zone overrides */ + if(!lz_enter_overrides(zones, cfg)) { + return 0; + } /* create implicit transparent zone from data. */ if(!lz_setup_implicit(zones, cfg)) { return 0; @@ -920,6 +1033,10 @@ local_zones_apply_cfg(struct local_zones* zones, struct config_file* cfg) /* setup parent ptrs for lookup during data entry */ init_parents(zones); + /* insert local zone tags */ + if(!lz_enter_zone_tags(zones, cfg)) { + return 0; + } /* insert local data */ if(!lz_enter_data(zones, cfg)) { return 0; @@ -933,33 +1050,41 @@ struct local_zone* local_zones_lookup(struct local_zones* zones, uint8_t* name, size_t len, int labs, uint16_t dclass) { - rbnode_t* res = NULL; + return local_zones_tags_lookup(zones, name, len, labs, + dclass, NULL, 0, 1); +} + +struct local_zone* +local_zones_tags_lookup(struct local_zones* zones, + uint8_t* name, size_t len, int labs, uint16_t dclass, + uint8_t* taglist, size_t taglen, int ignoretags) +{ + rbnode_type* res = NULL; struct local_zone *result; struct local_zone key; + int m; key.node.key = &key; key.dclass = dclass; key.name = name; key.namelen = len; key.namelabs = labs; - if(rbtree_find_less_equal(&zones->ztree, &key, &res)) { - /* exact */ - return (struct local_zone*)res; - } else { - /* smaller element (or no element) */ - int m; - result = (struct local_zone*)res; - if(!result || result->dclass != dclass) - return NULL; - /* count number of labels matched */ - (void)dname_lab_cmp(result->name, result->namelabs, key.name, - key.namelabs, &m); - while(result) { /* go up until qname is subdomain of zone */ - if(result->namelabs <= m) - break; - result = result->parent; - } - return result; + rbtree_find_less_equal(&zones->ztree, &key, &res); + result = (struct local_zone*)res; + /* exact or smaller element (or no element) */ + if(!result || result->dclass != dclass) + return NULL; + /* count number of labels matched */ + (void)dname_lab_cmp(result->name, result->namelabs, key.name, + key.namelabs, &m); + while(result) { /* go up until qname is zone or subdomain of zone */ + if(result->namelabs <= m) + if(ignoretags || !result->taglist || + taglist_intersect(result->taglist, + result->taglen, taglist, taglen)) + break; + result = result->parent; } + return result; } struct local_zone* @@ -1031,6 +1156,18 @@ void local_zones_print(struct local_zones* zones) log_nametypeclass(0, "inform_deny zone", z->name, 0, z->dclass); break; + case local_zone_always_transparent: + log_nametypeclass(0, "always_transparent zone", + z->name, 0, z->dclass); + break; + case local_zone_always_refuse: + log_nametypeclass(0, "always_refuse zone", + z->name, 0, z->dclass); + break; + case local_zone_always_nxdomain: + log_nametypeclass(0, "always_nxdomain zone", + z->name, 0, z->dclass); + break; default: log_nametypeclass(0, "badtyped zone", z->name, 0, z->dclass); @@ -1044,8 +1181,8 @@ void local_zones_print(struct local_zones* zones) /** encode answer consisting of 1 rrset */ static int -local_encode(struct query_info* qinfo, struct edns_data* edns, - sldns_buffer* buf, struct regional* temp, +local_encode(struct query_info* qinfo, struct module_env* env, + struct edns_data* edns, sldns_buffer* buf, struct regional* temp, struct ub_packed_rrset_key* rrset, int ansec, int rcode) { struct reply_info rep; @@ -1064,22 +1201,155 @@ local_encode(struct query_info* qinfo, struct edns_data* edns, edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; - if(!reply_info_answer_encode(qinfo, &rep, + if(!inplace_cb_reply_local_call(env, qinfo, NULL, &rep, rcode, edns, temp) + || !reply_info_answer_encode(qinfo, &rep, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), - buf, 0, 0, temp, udpsize, edns, + 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); + sldns_buffer_read_u16_at(buf, 2), edns); return 1; } +/** encode local error answer */ +static void +local_error_encode(struct query_info* qinfo, struct module_env* env, + struct edns_data* edns, sldns_buffer* buf, struct regional* temp, + int rcode, int r) +{ + 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, temp)) + edns->opt_list = NULL; + error_encode(buf, r, qinfo, *(uint16_t*)sldns_buffer_begin(buf), + sldns_buffer_read_u16_at(buf, 2), edns); +} + +/** find local data tag string match for the given type in the list */ +int +local_data_find_tag_datas(const struct query_info* qinfo, + struct config_strlist* list, struct ub_packed_rrset_key* r, + struct regional* temp) +{ + struct config_strlist* p; + char buf[65536]; + uint8_t rr[LDNS_RR_BUF_SIZE]; + size_t len; + int res; + struct packed_rrset_data* d; + for(p=list; p; p=p->next) { + uint16_t rdr_type; + + len = sizeof(rr); + /* does this element match the type? */ + snprintf(buf, sizeof(buf), ". %s", p->str); + res = sldns_str2wire_rr_buf(buf, rr, &len, NULL, 3600, + NULL, 0, NULL, 0); + if(res != 0) + /* parse errors are already checked before, in + * acllist check_data, skip this for robustness */ + continue; + if(len < 1 /* . */ + 8 /* typeclassttl*/ + 2 /*rdatalen*/) + continue; + rdr_type = sldns_wirerr_get_type(rr, len, 1); + if(rdr_type != qinfo->qtype && rdr_type != LDNS_RR_TYPE_CNAME) + continue; + + /* do we have entries already? if not setup key */ + if(r->rk.dname == NULL) { + r->entry.key = r; + r->rk.dname = qinfo->qname; + r->rk.dname_len = qinfo->qname_len; + r->rk.type = htons(rdr_type); + r->rk.rrset_class = htons(qinfo->qclass); + r->rk.flags = 0; + d = (struct packed_rrset_data*)regional_alloc_zero( + temp, sizeof(struct packed_rrset_data) + + sizeof(size_t) + sizeof(uint8_t*) + + sizeof(time_t)); + if(!d) return 0; /* out of memory */ + r->entry.data = d; + d->ttl = sldns_wirerr_get_ttl(rr, len, 1); + 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 = (struct packed_rrset_data*)r->entry.data; + /* add entry to the data */ + if(d->count != 0) { + size_t* oldlen = d->rr_len; + uint8_t** olddata = d->rr_data; + time_t* oldttl = d->rr_ttl; + /* increase arrays for lookup */ + /* this is of course slow for very many records, + * but most redirects are expected with few records */ + d->rr_len = (size_t*)regional_alloc_zero(temp, + (d->count+1)*sizeof(size_t)); + d->rr_data = (uint8_t**)regional_alloc_zero(temp, + (d->count+1)*sizeof(uint8_t*)); + d->rr_ttl = (time_t*)regional_alloc_zero(temp, + (d->count+1)*sizeof(time_t)); + if(!d->rr_len || !d->rr_data || !d->rr_ttl) + return 0; /* out of memory */ + /* first one was allocated after struct d, but new + * ones get their own array increment alloc, so + * copy old content */ + memmove(d->rr_len, oldlen, d->count*sizeof(size_t)); + memmove(d->rr_data, olddata, d->count*sizeof(uint8_t*)); + memmove(d->rr_ttl, oldttl, d->count*sizeof(time_t)); + } + + d->rr_len[d->count] = sldns_wirerr_get_rdatalen(rr, len, 1)+2; + d->rr_ttl[d->count] = sldns_wirerr_get_ttl(rr, len, 1); + d->rr_data[d->count] = regional_alloc_init(temp, + sldns_wirerr_get_rdatawl(rr, len, 1), + d->rr_len[d->count]); + if(!d->rr_data[d->count]) + return 0; /* out of memory */ + d->count++; + } + if(r->rk.dname) + return 1; + return 0; +} + +static int +find_tag_datas(struct query_info* qinfo, struct config_strlist* list, + struct ub_packed_rrset_key* r, struct regional* temp) +{ + int result = local_data_find_tag_datas(qinfo, list, r, temp); + + /* If we've found a non-exact alias type of local data, make a shallow + * copy of the RRset and remember it in qinfo to complete the alias + * chain later. */ + if(result && qinfo->qtype != LDNS_RR_TYPE_CNAME && + r->rk.type == htons(LDNS_RR_TYPE_CNAME)) { + qinfo->local_alias = + regional_alloc_zero(temp, sizeof(struct local_rrset)); + if(!qinfo->local_alias) + return 0; /* out of memory */ + qinfo->local_alias->rrset = + regional_alloc_init(temp, r, sizeof(*r)); + if(!qinfo->local_alias->rrset) + return 0; /* out of memory */ + } + return result; +} + /** answer local data match */ static int -local_data_answer(struct local_zone* z, struct query_info* qinfo, - struct edns_data* edns, sldns_buffer* buf, struct regional* temp, - int labs, struct local_data** ldp) +local_data_answer(struct local_zone* z, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, + struct regional* temp, int labs, struct local_data** ldp, + enum localzone_type lz_type, int tag, struct config_strlist** tag_datas, + size_t tag_datas_size, char** tagname, int num_tags) { struct local_data key; struct local_data* ld; @@ -1088,58 +1358,95 @@ local_data_answer(struct local_zone* z, struct query_info* qinfo, key.name = qinfo->qname; key.namelen = qinfo->qname_len; key.namelabs = labs; - if(z->type == local_zone_redirect) { + if(lz_type == local_zone_redirect) { key.name = z->name; key.namelen = z->namelen; key.namelabs = z->namelabs; + if(tag != -1 && (size_t)tag<tag_datas_size && tag_datas[tag]) { + struct ub_packed_rrset_key r; + memset(&r, 0, sizeof(r)); + if(find_tag_datas(qinfo, tag_datas[tag], &r, temp)) { + verbose(VERB_ALGO, "redirect with tag data [%d] %s", + tag, (tag<num_tags?tagname[tag]:"null")); + + /* If we found a matching alias, we should + * use it as part of the answer, but we can't + * encode it until we complete the alias + * chain. */ + if(qinfo->local_alias) + return 1; + return local_encode(qinfo, env, edns, buf, temp, + &r, 1, LDNS_RCODE_NOERROR); + } + } } ld = (struct local_data*)rbtree_search(&z->data, &key.node); *ldp = ld; if(!ld) { return 0; } - lr = local_data_find_type(ld, qinfo->qtype); + lr = local_data_find_type(ld, qinfo->qtype, 1); if(!lr) return 0; - if(z->type == local_zone_redirect) { + + /* Special case for alias matching. See local_data_answer(). */ + if(lz_type == local_zone_redirect && + qinfo->qtype != LDNS_RR_TYPE_CNAME && + lr->rrset->rk.type == htons(LDNS_RR_TYPE_CNAME)) { + qinfo->local_alias = + regional_alloc_zero(temp, sizeof(struct local_rrset)); + if(!qinfo->local_alias) + return 0; /* out of memory */ + qinfo->local_alias->rrset = + regional_alloc_init(temp, lr->rrset, sizeof(*lr->rrset)); + if(!qinfo->local_alias->rrset) + return 0; /* out of memory */ + qinfo->local_alias->rrset->rk.dname = qinfo->qname; + qinfo->local_alias->rrset->rk.dname_len = qinfo->qname_len; + return 1; + } + if(lz_type == local_zone_redirect) { /* convert rrset name to query name; like a wildcard */ struct ub_packed_rrset_key r = *lr->rrset; r.rk.dname = qinfo->qname; r.rk.dname_len = qinfo->qname_len; - return local_encode(qinfo, edns, buf, temp, &r, 1, + return local_encode(qinfo, env, edns, buf, temp, &r, 1, LDNS_RCODE_NOERROR); } - return local_encode(qinfo, edns, buf, temp, lr->rrset, 1, + return local_encode(qinfo, env, edns, buf, temp, lr->rrset, 1, LDNS_RCODE_NOERROR); } /** * answer in case where no exact match is found * @param z: zone for query + * @param env: module environment * @param qinfo: query * @param edns: edns from query * @param buf: buffer for answer. * @param temp: temp region for encoding * @param ld: local data, if NULL, no such name exists in localdata. + * @param lz_type: type of the local zone * @return 1 if a reply is to be sent, 0 if not. */ static int -lz_zone_answer(struct local_zone* z, struct query_info* qinfo, - struct edns_data* edns, sldns_buffer* buf, struct regional* temp, - struct local_data* ld) +lz_zone_answer(struct local_zone* z, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, + struct regional* temp, struct local_data* ld, enum localzone_type lz_type) { - if(z->type == local_zone_deny || z->type == local_zone_inform_deny) { + if(lz_type == local_zone_deny || lz_type == local_zone_inform_deny) { /** no reply at all, signal caller by clearing buffer. */ sldns_buffer_clear(buf); sldns_buffer_flip(buf); return 1; - } else if(z->type == local_zone_refuse) { - error_encode(buf, (LDNS_RCODE_REFUSED|BIT_AA), qinfo, - *(uint16_t*)sldns_buffer_begin(buf), - sldns_buffer_read_u16_at(buf, 2), edns); + } else if(lz_type == local_zone_refuse + || lz_type == local_zone_always_refuse) { + local_error_encode(qinfo, env, edns, buf, temp, + LDNS_RCODE_REFUSED, (LDNS_RCODE_REFUSED|BIT_AA)); return 1; - } else if(z->type == local_zone_static || - z->type == local_zone_redirect) { + } else if(lz_type == local_zone_static || + lz_type == local_zone_redirect || + lz_type == local_zone_always_nxdomain) { /* for static, reply nodata or nxdomain * for redirect, reply nodata */ /* no additional section processing, @@ -1147,30 +1454,30 @@ lz_zone_answer(struct local_zone* z, struct query_info* qinfo, * or using closest match for NSEC. * or using closest match for returning delegation downwards */ - int rcode = ld?LDNS_RCODE_NOERROR:LDNS_RCODE_NXDOMAIN; + int rcode = (ld || lz_type == local_zone_redirect)? + LDNS_RCODE_NOERROR:LDNS_RCODE_NXDOMAIN; if(z->soa) - return local_encode(qinfo, edns, buf, temp, + return local_encode(qinfo, env, edns, buf, temp, z->soa, 0, rcode); - error_encode(buf, (rcode|BIT_AA), qinfo, - *(uint16_t*)sldns_buffer_begin(buf), - sldns_buffer_read_u16_at(buf, 2), edns); + local_error_encode(qinfo, env, edns, buf, temp, rcode, + (rcode|BIT_AA)); return 1; - } else if(z->type == local_zone_typetransparent) { + } else if(lz_type == local_zone_typetransparent + || lz_type == local_zone_always_transparent) { /* no NODATA or NXDOMAINS for this zone type */ return 0; } - /* else z->type == local_zone_transparent */ + /* else lz_type == local_zone_transparent */ /* if the zone is transparent and the name exists, but the type * does not, then we should make this noerror/nodata */ if(ld && ld->rrsets) { int rcode = LDNS_RCODE_NOERROR; if(z->soa) - return local_encode(qinfo, edns, buf, temp, + return local_encode(qinfo, env, edns, buf, temp, z->soa, 0, rcode); - error_encode(buf, (rcode|BIT_AA), qinfo, - *(uint16_t*)sldns_buffer_begin(buf), - sldns_buffer_read_u16_at(buf, 2), edns); + local_error_encode(qinfo, env, edns, buf, temp, rcode, + (rcode|BIT_AA)); return 1; } @@ -1193,44 +1500,136 @@ lz_inform_print(struct local_zone* z, struct query_info* qinfo, log_nametypeclass(0, txt, qinfo->qname, qinfo->qtype, qinfo->qclass); } +static enum localzone_type +lz_type(uint8_t *taglist, size_t taglen, uint8_t *taglist2, size_t taglen2, + uint8_t *tagactions, size_t tagactionssize, enum localzone_type lzt, + struct comm_reply* repinfo, struct rbtree_type* override_tree, + int* tag, char** tagname, int num_tags) +{ + struct local_zone_override* lzo; + if(repinfo && override_tree) { + lzo = (struct local_zone_override*)addr_tree_lookup( + override_tree, &repinfo->addr, repinfo->addrlen); + if(lzo && lzo->type) { + verbose(VERB_ALGO, "local zone override to type %s", + local_zone_type2str(lzo->type)); + return lzo->type; + } + } + if(!taglist || !taglist2) + return lzt; + return local_data_find_tag_action(taglist, taglen, taglist2, taglen2, + tagactions, tagactionssize, lzt, tag, tagname, num_tags); +} + +enum localzone_type +local_data_find_tag_action(const uint8_t* taglist, size_t taglen, + const uint8_t* taglist2, size_t taglen2, const uint8_t* tagactions, + size_t tagactionssize, enum localzone_type lzt, int* tag, + char* const* tagname, int num_tags) +{ + size_t i, j; + uint8_t tagmatch; + + for(i=0; i<taglen && i<taglen2; i++) { + tagmatch = (taglist[i] & taglist2[i]); + for(j=0; j<8 && tagmatch>0; j++) { + if((tagmatch & 0x1)) { + *tag = (int)(i*8+j); + verbose(VERB_ALGO, "matched tag [%d] %s", + *tag, (*tag<num_tags?tagname[*tag]:"null")); + /* does this tag have a tag action? */ + if(i*8+j < tagactionssize && tagactions + && tagactions[i*8+j] != 0) { + verbose(VERB_ALGO, "tag action [%d] %s to type %s", + *tag, (*tag<num_tags?tagname[*tag]:"null"), + local_zone_type2str( + (enum localzone_type) + tagactions[i*8+j])); + return (enum localzone_type)tagactions[i*8+j]; + } + return lzt; + } + tagmatch >>= 1; + } + } + return lzt; +} + int -local_zones_answer(struct local_zones* zones, struct query_info* qinfo, - struct edns_data* edns, sldns_buffer* buf, struct regional* temp, - struct comm_reply* repinfo) +local_zones_answer(struct local_zones* zones, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, + struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist, + size_t taglen, uint8_t* tagactions, size_t tagactionssize, + struct config_strlist** tag_datas, size_t tag_datas_size, + char** tagname, int num_tags, struct view* view) { /* see if query is covered by a zone, * if so: - try to match (exact) local data * - look at zone type for negative response. */ int labs = dname_count_labels(qinfo->qname); - struct local_data* ld; - struct local_zone* z; - int r; - lock_rw_rdlock(&zones->lock); - z = local_zones_lookup(zones, qinfo->qname, - qinfo->qname_len, labs, qinfo->qclass); + struct local_data* ld = NULL; + struct local_zone* z = NULL; + enum localzone_type lzt = local_zone_transparent; + int r, tag = -1; + + if(view) { + lock_rw_rdlock(&view->lock); + if(view->local_zones && + (z = local_zones_lookup(view->local_zones, + qinfo->qname, qinfo->qname_len, labs, + qinfo->qclass))) { + verbose(VERB_ALGO, + "using localzone from view: %s", + view->name); + lock_rw_rdlock(&z->lock); + lzt = z->type; + } + if(!z && !view->isfirst){ + lock_rw_unlock(&view->lock); + return 0; + } + lock_rw_unlock(&view->lock); + } if(!z) { + /* try global local_zones tree */ + lock_rw_rdlock(&zones->lock); + if(!(z = local_zones_tags_lookup(zones, qinfo->qname, + qinfo->qname_len, labs, qinfo->qclass, taglist, + taglen, 0))) { + lock_rw_unlock(&zones->lock); + return 0; + } + lock_rw_rdlock(&z->lock); + + lzt = lz_type(taglist, taglen, z->taglist, z->taglen, + tagactions, tagactionssize, z->type, repinfo, + z->override_tree, &tag, tagname, num_tags); lock_rw_unlock(&zones->lock); - return 0; } - lock_rw_rdlock(&z->lock); - lock_rw_unlock(&zones->lock); - - if((z->type == local_zone_inform || z->type == local_zone_inform_deny) + if((lzt == local_zone_inform || lzt == local_zone_inform_deny) && repinfo) lz_inform_print(z, qinfo, repinfo); - if(local_data_answer(z, qinfo, edns, buf, temp, labs, &ld)) { + if(lzt != local_zone_always_refuse + && lzt != local_zone_always_transparent + && lzt != local_zone_always_nxdomain + && local_data_answer(z, env, qinfo, edns, buf, temp, labs, &ld, lzt, + tag, tag_datas, tag_datas_size, tagname, num_tags)) { lock_rw_unlock(&z->lock); - return 1; + /* We should tell the caller that encode is deferred if we found + * a local alias. */ + return !qinfo->local_alias; } - r = lz_zone_answer(z, qinfo, edns, buf, temp, ld); + r = lz_zone_answer(z, env, qinfo, edns, buf, temp, ld, lzt); lock_rw_unlock(&z->lock); - return r; + return r && !qinfo->local_alias; /* see above */ } const char* local_zone_type2str(enum localzone_type t) { switch(t) { + case local_zone_unset: return "unset"; case local_zone_deny: return "deny"; case local_zone_refuse: return "refuse"; case local_zone_redirect: return "redirect"; @@ -1240,6 +1639,9 @@ const char* local_zone_type2str(enum localzone_type t) case local_zone_nodefault: return "nodefault"; case local_zone_inform: return "inform"; case local_zone_inform_deny: return "inform_deny"; + case local_zone_always_transparent: return "always_transparent"; + case local_zone_always_refuse: return "always_refuse"; + case local_zone_always_nxdomain: return "always_nxdomain"; } return "badtyped"; } @@ -1262,6 +1664,12 @@ int local_zone_str2type(const char* type, enum localzone_type* t) *t = local_zone_inform; else if(strcmp(type, "inform_deny") == 0) *t = local_zone_inform_deny; + else if(strcmp(type, "always_transparent") == 0) + *t = local_zone_always_transparent; + else if(strcmp(type, "always_refuse") == 0) + *t = local_zone_always_refuse; + else if(strcmp(type, "always_nxdomain") == 0) + *t = local_zone_always_nxdomain; else return 0; return 1; } @@ -1298,7 +1706,10 @@ struct local_zone* local_zones_add_zone(struct local_zones* zones, { /* create */ struct local_zone* z = local_zone_create(name, len, labs, tp, dclass); - if(!z) return NULL; + if(!z) { + free(name); + return NULL; + } lock_rw_wrlock(&z->lock); /* find the closest parent */ |