diff --git a/aclocal.m4 b/aclocal.m4 index 95cd61e9..0cd56f0f 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,6 +1,6 @@ -# generated automatically by aclocal 1.15 -*- Autoconf -*- +# generated automatically by aclocal 1.14.1 -*- Autoconf -*- -# Copyright (C) 1996-2014 Free Software Foundation, Inc. +# Copyright (C) 1996-2013 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, diff --git a/configure b/configure index 7c6cca29..b9b00f3e 100755 --- a/configure +++ b/configure @@ -760,7 +760,7 @@ with_sysroot enable_libtool_lock enable_rpath enable_tcp_fastopen -enable_broken_native_stub_dnssec +enable_native_stub_dnssec with_ssl enable_draft_edns_cookies with_libidn @@ -1406,9 +1406,8 @@ Optional Features: --disable-libtool-lock avoid locking (might break parallel builds) --disable-rpath disable hardcoded rpath (default=enabled) --enable-tcp-fastopen Enable TCP Fast Open - --enable-broken-native-stub-dnssec - Enable very experimental and broken native stub - DNSSEC support + --disable-native-stub-dnssec + Disable native stub DNSSEC support --enable-draft-edns-cookies Enable experimental edns cookies @@ -11738,20 +11737,20 @@ _ACEOF ;; esac -# Check whether --enable-broken-native-stub-dnssec was given. -if test "${enable_broken_native_stub_dnssec+set}" = set; then : - enableval=$enable_broken_native_stub_dnssec; +# Check whether --enable-native-stub-dnssec was given. +if test "${enable_native_stub_dnssec+set}" = set; then : + enableval=$enable_native_stub_dnssec; fi -case "$enable_broken_native_stub_dnssec" in - yes) +case "$enable_native_stub_dnssec" in + no) + ;; + yes|*) cat >>confdefs.h <<_ACEOF #define STUB_NATIVE_DNSSEC 1 _ACEOF - ;; - no|*) ;; esac diff --git a/configure.ac b/configure.ac index 97d2a9a8..e766900f 100644 --- a/configure.ac +++ b/configure.ac @@ -144,12 +144,12 @@ esac # ]) # fi -AC_ARG_ENABLE(broken-native-stub-dnssec, AC_HELP_STRING([--enable-broken-native-stub-dnssec], [Enable very experimental and broken native stub DNSSEC support])) -case "$enable_broken_native_stub_dnssec" in - yes) - AC_DEFINE_UNQUOTED([STUB_NATIVE_DNSSEC], [1], [Define this to enable the very experimental and broken native stub DNSSEC support.]) +AC_ARG_ENABLE(native-stub-dnssec, AC_HELP_STRING([--disable-native-stub-dnssec], [Disable native stub DNSSEC support])) +case "$enable_native_stub_dnssec" in + no) ;; - no|*) + yes|*) + AC_DEFINE_UNQUOTED([STUB_NATIVE_DNSSEC], [1], [Define this to enable native stub DNSSEC support.]) ;; esac diff --git a/src/config.h.in b/src/config.h.in index e2405b25..ff26526c 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -192,8 +192,7 @@ /* Define to 1 if you have the ANSI C header files. */ #undef STDC_HEADERS -/* Define this to enable the very experimental and broken native stub DNSSEC - support. */ +/* Define this to enable native stub DNSSEC support. */ #undef STUB_NATIVE_DNSSEC /* System configuration dir */ diff --git a/src/context.c b/src/context.c index e389d400..491ff515 100644 --- a/src/context.c +++ b/src/context.c @@ -43,6 +43,7 @@ #include #include #include +#include #include "config.h" #include "gldns/str2wire.h" @@ -815,6 +816,7 @@ getdns_context_create_with_extended_memory_functions( getdns_return_t r; struct getdns_context *result = NULL; mf_union mf; + gldns_buffer gbuf; if (!context || !malloc || !realloc || !free) return GETDNS_RETURN_INVALID_PARAMETER; @@ -860,7 +862,30 @@ getdns_context_create_with_extended_memory_functions( result->append_name = GETDNS_APPEND_NAME_ALWAYS; result->suffix = NULL; - result->dnssec_trust_anchors = NULL; + gldns_buffer_init_frm_data(&gbuf, result->trust_anchors_spc + , sizeof(result->trust_anchors_spc)); + + if (!_getdns_parse_ta_file(NULL, &gbuf)) { + result->trust_anchors = NULL; + result->trust_anchors_len = 0; + + } else if ((result->trust_anchors_len = gldns_buffer_position(&gbuf)) + > sizeof(result->trust_anchors_spc)) { + + if ((result->trust_anchors = GETDNS_XMALLOC( + result->mf, uint8_t, result->trust_anchors_len))) { + + gldns_buffer_init_frm_data(&gbuf + , result->trust_anchors + , result->trust_anchors_len); + if (!_getdns_parse_ta_file(NULL, &gbuf)) { + result->trust_anchors = NULL; + result->trust_anchors_len = 0; + } + } + } else + result->trust_anchors = result->trust_anchors_spc; + result->upstreams = NULL; result->edns_extended_rcode = 0; @@ -873,7 +898,7 @@ getdns_context_create_with_extended_memory_functions( goto error; result->fchg_resolvconf = NULL; - result->fchg_hosts = NULL; + result->fchg_hosts = NULL; if (set_from_os && (r = set_os_defaults(result))) goto error; @@ -883,12 +908,12 @@ getdns_context_create_with_extended_memory_functions( if ((r = create_default_dns_transports(result))) goto error; result->limit_outstanding_queries = 0; - result->has_ta = priv_getdns_parse_ta_file(NULL, NULL); result->return_dnssec_status = GETDNS_EXTENSION_FALSE; /* unbound context is initialized here */ /* Unbound needs SSL to be init'ed this early when TLS is used. However we * don't know that till later so we will have to do this every time. */ + SSL_library_init(); result->unbound_ctx = NULL; if ((r = rebuild_ub_ctx(result))) @@ -985,7 +1010,10 @@ getdns_context_destroy(struct getdns_context *context) getdns_list_destroy(context->dns_root_servers); getdns_list_destroy(context->suffix); - getdns_list_destroy(context->dnssec_trust_anchors); + + if (context->trust_anchors && + context->trust_anchors != context->trust_anchors_spc) + GETDNS_FREE(context->mf, context->trust_anchors); /* destroy the contexts */ if (context->unbound_ctx) @@ -1116,7 +1144,7 @@ rebuild_ub_ctx(struct getdns_context* context) { set_ub_dns_transport(context); /* Set default trust anchor */ - if (context->has_ta) { + if (context->trust_anchors) { (void) ub_ctx_add_ta_file( context->unbound_ctx, TRUST_ANCHOR_FILE); } @@ -1563,23 +1591,26 @@ getdns_context_set_suffix(struct getdns_context *context, struct getdns_list * v * */ getdns_return_t -getdns_context_set_dnssec_trust_anchors(struct getdns_context *context, - struct getdns_list * value) +getdns_context_set_dnssec_trust_anchors( + getdns_context *context, getdns_list *value) { - struct getdns_list *copy = NULL; - RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); - if (value != NULL) { - if (getdns_list_copy(value, ©) != GETDNS_RETURN_GOOD) { - return GETDNS_RETURN_CONTEXT_UPDATE_FAIL; - } - value = copy; - } - getdns_list_destroy(context->dnssec_trust_anchors); - context->dnssec_trust_anchors = value; + RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); - dispatch_updated(context, GETDNS_CONTEXT_CODE_DNSSEC_TRUST_ANCHORS); + if (context->trust_anchors && + context->trust_anchors != context->trust_anchors_spc) + GETDNS_FREE(context->mf, context->trust_anchors); - return GETDNS_RETURN_GOOD; + if (value) { + context->trust_anchors_len = sizeof(context->trust_anchors_spc); + context->trust_anchors = _getdns_list2wire(value, + context->trust_anchors_spc, &context->trust_anchors_len, + &context->mf); + } else { + context->trust_anchors = NULL; + context->trust_anchors_len = 0; + } + dispatch_updated(context, GETDNS_CONTEXT_CODE_DNSSEC_TRUST_ANCHORS); + return GETDNS_RETURN_GOOD; } /* getdns_context_set_dnssec_trust_anchors */ static void @@ -2432,8 +2463,6 @@ getdns_context_local_namespace_resolve( { getdns_context *context = dnsreq->context; host_name_addrs *hnas; - uint8_t query_name[256]; - size_t query_name_len = sizeof(query_name); uint8_t lookup[256]; getdns_list empty_list = { 0 }; getdns_bindata bindata; @@ -2451,10 +2480,7 @@ getdns_context_local_namespace_resolve( return GETDNS_RETURN_GENERIC_ERROR; /*Do the lookup*/ - if (gldns_str2wire_dname_buf(dnsreq->name,query_name,&query_name_len)) - return GETDNS_RETURN_GENERIC_ERROR; - - (void)memcpy(lookup, query_name, query_name_len); + (void)memcpy(lookup, dnsreq->name, dnsreq->name_len); canonicalize_dname(lookup); if (!(hnas = (host_name_addrs *) @@ -2470,8 +2496,8 @@ getdns_context_local_namespace_resolve( if (!(*response = getdns_dict_create_with_context(context))) return GETDNS_RETURN_GENERIC_ERROR; - bindata.size = query_name_len; - bindata.data = query_name; + bindata.size = dnsreq->name_len; + bindata.data = dnsreq->name; if (getdns_dict_set_bindata(*response, "canonical_name", &bindata)) goto error; @@ -2673,15 +2699,23 @@ getdns_context_get_suffix(getdns_context *context, getdns_list **value) { } getdns_return_t -getdns_context_get_dnssec_trust_anchors(getdns_context *context, - getdns_list **value) { - RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); - RETURN_IF_NULL(value, GETDNS_RETURN_INVALID_PARAMETER); - *value = NULL; - if (context->dnssec_trust_anchors) { - return getdns_list_copy(context->dnssec_trust_anchors, value); - } - return GETDNS_RETURN_GOOD; +getdns_context_get_dnssec_trust_anchors( + getdns_context *context, getdns_list **value) +{ + RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); + RETURN_IF_NULL(value, GETDNS_RETURN_INVALID_PARAMETER); + + if (context->trust_anchors) { + if ((*value = getdns_list_create_with_context(context))) + _getdns_wire2list( context->trust_anchors + , context->trust_anchors_len + , *value); + else + return GETDNS_RETURN_MEMORY_ERROR; + } else + *value = NULL; + + return GETDNS_RETURN_GOOD; } getdns_return_t diff --git a/src/context.h b/src/context.h index 9550fe37..926b6d41 100644 --- a/src/context.h +++ b/src/context.h @@ -138,7 +138,8 @@ struct getdns_context { struct getdns_list *dns_root_servers; getdns_append_name_t append_name; struct getdns_list *suffix; - struct getdns_list *dnssec_trust_anchors; + uint8_t *trust_anchors; + size_t trust_anchors_len; getdns_upstreams *upstreams; uint16_t limit_outstanding_queries; uint32_t dnssec_allowed_skew; @@ -168,7 +169,7 @@ struct getdns_context { /* A tree to hold local host information*/ getdns_rbtree_t local_hosts; - int has_ta; /* No DNSSEC without trust anchor */ + int return_dnssec_status; /* which resolution type the contexts are configured for @@ -194,6 +195,8 @@ struct getdns_context { struct filechg *fchg_resolvconf; struct filechg *fchg_hosts; + uint8_t trust_anchors_spc[1024]; + }; /* getdns_context */ /** internal functions **/ diff --git a/src/dict.h b/src/dict.h index 2d45b651..0a74cc14 100644 --- a/src/dict.h +++ b/src/dict.h @@ -70,6 +70,10 @@ struct getdns_dict struct mem_funcs mf; }; +inline struct getdns_dict *_getdns_dict_create_with_mf(struct mem_funcs *mf) +{ return getdns_dict_create_with_extended_memory_functions( + mf->mf_arg, mf->mf.ext.malloc, mf->mf.ext.realloc, mf->mf.ext.free); } + #endif /* dict.h */ diff --git a/src/dnssec.c b/src/dnssec.c index c623d6df..ce670544 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -49,735 +49,1815 @@ #include "rr-dict.h" #include "gldns/str2wire.h" #include "gldns/wire2str.h" +#include "gldns/keyraw.h" +#include "gldns/parseutil.h" #include "general.h" +#include "dict.h" +#include "list.h" -void priv_getdns_call_user_callback(getdns_dns_req *, struct getdns_dict *); - -struct validation_chain { - getdns_rbtree_t root; - struct mem_funcs mf; - getdns_dns_req *dns_req; - size_t lock; - uint64_t *timeout; -}; - -struct chain_response { - int err; - getdns_list *result; - struct validation_chain *chain; - getdns_dns_req *dns_req; -}; - -struct chain_link { - getdns_rbnode_t node; - struct chain_response DNSKEY; - struct chain_response DS; -}; - -static void launch_chain_link_lookup(struct validation_chain *chain, - priv_getdns_rdf_iter *rdf_dname); -static void destroy_chain(struct validation_chain *chain); - -#ifdef STUB_NATIVE_DNSSEC -static void -native_stub_validate_dnssec(getdns_dns_req *dns_req, getdns_list *support) +/* parent is same or parent of subdomain,*/ +static int is_subdomain( + const uint8_t * const parent, const uint8_t *subdomain) { - getdns_network_req *netreq, **netreq_p; - getdns_list *trust_anchors; - getdns_dict *reply = NULL; - getdns_list *to_validate; - getdns_list *list; - getdns_dict *rr_dict; - size_t i; + while (*subdomain) { + if (priv_getdns_dname_equal(parent, subdomain)) + return 1; - if (!(trust_anchors = getdns_root_trust_anchor(NULL))) - return; - - for (netreq_p = dns_req->netreqs; (netreq = *netreq_p) ; netreq_p++) { - if (!(reply = priv_getdns_create_reply_dict(dns_req->context, - netreq, NULL, NULL))) - continue; - if (!(to_validate = - getdns_list_create_with_context(dns_req->context))) - break; - if (getdns_dict_get_list(reply, "answer", &list)) { - getdns_list_destroy(to_validate); - break; - } - for (i = 0; !getdns_list_get_dict(list, i, &rr_dict); i++) - (void) getdns_list_append_dict(to_validate, rr_dict); - - if (getdns_dict_get_list(reply, "authority", &list)) { - getdns_list_destroy(to_validate); - break; - } - for (i = 0; !getdns_list_get_dict(list, i, &rr_dict); i++) - (void) getdns_list_append_dict(to_validate, rr_dict); - - switch ((int)getdns_validate_dnssec( - to_validate, support, trust_anchors)) { - case GETDNS_DNSSEC_SECURE: - netreq->secure = 1; - netreq->bogus = 0; - break; - case GETDNS_DNSSEC_BOGUS: - netreq->secure = 0; - netreq->bogus = 1; - break; - default: - /* GETDNS_DNSSEC_INSECURE */ - netreq->secure = 0; - netreq->bogus = 0; - break; - } - getdns_list_destroy(to_validate); - getdns_dict_destroy(reply); - reply = NULL; + subdomain += *subdomain + 1; } - getdns_list_destroy(trust_anchors); - getdns_dict_destroy(reply); -} -#endif - -static void callback_on_complete_chain(struct validation_chain *chain) -{ - getdns_context *context = chain->dns_req->context; - getdns_dict *response; - struct chain_link *link; - size_t ongoing = chain->lock; - getdns_list *keys; - size_t i; - getdns_dict *rr_dict; - - RBTREE_FOR(link, struct chain_link *, - (getdns_rbtree_t *)&(chain->root)) { - if (link->DNSKEY.result == NULL && link->DNSKEY.err == 0) - ongoing++; - if (link->DS.result == NULL && link->DS.err == 0 && - (((const char *)link->node.key)[0] != '.' || - ((const char *)link->node.key)[1] != '\0' )) - ongoing++; - } - if (ongoing > 0) - return; - - if (!(keys = getdns_list_create_with_context(context))) { - priv_getdns_call_user_callback(chain->dns_req, - create_getdns_response(chain->dns_req)); - destroy_chain(chain); - return; - } - RBTREE_FOR(link, struct chain_link *, - (getdns_rbtree_t *)&(chain->root)) { - for (i = 0; !getdns_list_get_dict( link->DNSKEY.result - , i, &rr_dict); i++) - (void) getdns_list_append_dict(keys, rr_dict); - for (i = 0; !getdns_list_get_dict( link->DS.result - , i, &rr_dict); i++) - (void) getdns_list_append_dict(keys, rr_dict); - } -#ifdef STUB_NATIVE_DNSSEC - native_stub_validate_dnssec(chain->dns_req, keys); -#endif - if ((response = create_getdns_response(chain->dns_req)) && - chain->dns_req->dnssec_return_validation_chain) { - (void)getdns_dict_set_list(response, "validation_chain", keys); - } - getdns_list_destroy(keys); - priv_getdns_call_user_callback(chain->dns_req, response); - destroy_chain(chain); + return *parent == 0; } - -static void -chain_response_callback(struct getdns_dns_req *dns_req) +#if defined(SEC_DEBUG) && SEC_DEBUG +inline static void debug_sec_print_rr(const char *msg, priv_getdns_rr_iter *rr) { - struct chain_response *response = - (struct chain_response *) dns_req->user_pointer; - getdns_context *context = dns_req->context; - getdns_network_req **netreq_p, *netreq; - priv_getdns_rr_iter rr_iter_storage, *rr_iter; - priv_getdns_rdf_iter rdf_storage, *rdf; - gldns_pkt_section section; - uint16_t rr_type, type_covered; - getdns_dict *rr_dict; - getdns_list *keys; - size_t nkeys; - getdns_return_t r; + char str_spc[8192], *str = str_spc; + size_t str_len = sizeof(str_spc); + uint8_t *data = rr->pos; + size_t data_len = rr->nxt - rr->pos; - response->dns_req = dns_req; - if (!(keys = getdns_list_create_with_context(context))) - goto done; - - for (netreq_p = dns_req->netreqs; (netreq = *netreq_p); netreq_p++) { - for ( rr_iter = priv_getdns_rr_iter_init(&rr_iter_storage - , netreq->response - , netreq->response_len) - ; rr_iter - ; rr_iter = priv_getdns_rr_iter_next(rr_iter) - ) { - section = priv_getdns_rr_iter_section(rr_iter); - if (section != GLDNS_SECTION_ANSWER) - continue; - - rr_type = gldns_read_uint16(rr_iter->rr_type); - - if (rr_type == GETDNS_RRTYPE_DS || - rr_type == GETDNS_RRTYPE_DNSKEY) { - if (!(rr_dict = priv_getdns_rr_iter2rr_dict( - context, rr_iter))) - continue; - r = getdns_list_append_dict(keys, rr_dict); - getdns_dict_destroy(rr_dict); - if (r) break; - } - if (rr_type != GETDNS_RRTYPE_RRSIG) - continue; - - if (!(rdf = priv_getdns_rdf_iter_init( - &rdf_storage, rr_iter))) - continue; - - type_covered = gldns_read_uint16(rdf->pos); - if (type_covered == GETDNS_RRTYPE_DS) { - if ((rdf = priv_getdns_rdf_iter_init_at( - &rdf_storage, rr_iter, 7))) - launch_chain_link_lookup( - response->chain, rdf); - - } else if (type_covered != GETDNS_RRTYPE_DNSKEY) - continue; - - if (!(rr_dict = priv_getdns_rr_iter2rr_dict( - context, rr_iter))) - continue; - r = getdns_list_append_dict(keys, rr_dict); - getdns_dict_destroy(rr_dict); - if (r) break; - } + if (!rr || !rr->pos) { + DEBUG_SEC("%s\n", msg); + return; } - if (getdns_list_get_length(keys, &nkeys)) - getdns_list_destroy(keys); + (void) gldns_wire2str_rr_scan( + &data, &data_len, &str, &str_len, rr->pkt, rr->pkt_end - rr->pkt); + DEBUG_SEC("%s%s", msg, str_spc); +} +inline static void debug_sec_print_dname(const char *msg, uint8_t *label) +{ + char str[1024]; - else if (!nkeys) - getdns_list_destroy(keys); + if (label && gldns_wire2str_dname_buf(label, 256, str, sizeof(str))) + DEBUG_SEC("%s%s\n", msg, str); else - response->result = keys; + DEBUG_SEC("%s\n", msg); +} +inline static void debug_sec_print_pkt(const char *msg, uint8_t *pkt, size_t pkt_len) +{ + char *str; + DEBUG_SEC("%s%s\n", msg, (str = gldns_wire2str_pkt(pkt, pkt_len))); + if (str) free(str); +} +#else +#define debug_sec_print_rr(...) DEBUG_OFF(__VA_ARGS__) +#define debug_sec_print_dname(...) DEBUG_OFF(__VA_ARGS__) +#define debug_sec_print_pkt(...) DEBUG_OFF(__VA_ARGS__) +#endif +static inline uint16_t rr_iter_type(priv_getdns_rr_iter *rr) +{ return rr->rr_type + 2 <= rr->nxt ? gldns_read_uint16(rr->rr_type) : 0; } +static inline uint16_t rr_iter_class(priv_getdns_rr_iter *rr) +{ return rr->rr_type + 4 <= rr->nxt ? gldns_read_uint16(rr->rr_type + 2) : 0; } -done: if (response->err == 0 && response->result == NULL) - response->err = -1; +static priv_getdns_rr_iter *rr_iter_ansauth(priv_getdns_rr_iter *rr) +{ + while (rr && rr->pos && !( + priv_getdns_rr_iter_section(rr) == GLDNS_SECTION_ANSWER || + priv_getdns_rr_iter_section(rr) == GLDNS_SECTION_AUTHORITY)) - callback_on_complete_chain(response->chain); + rr = priv_getdns_rr_iter_next(rr); + + return rr && rr->pos ? rr : NULL; } -static void chain_response_init( - struct validation_chain *chain, struct chain_response *response) +static int rr_owner_equal(priv_getdns_rr_iter *rr, uint8_t *name) { - response->err = 0; - response->result = NULL; - response->chain = chain; - response->dns_req = NULL; + uint8_t owner_spc[256], *owner; + size_t owner_len = sizeof(owner_spc); + + return (owner = priv_getdns_owner_if_or_as_decompressed(rr, owner_spc + , &owner_len)) + && priv_getdns_dname_equal(owner, name); } -static int -resolve(char* name, int rrtype, struct chain_response *response) +static priv_getdns_rr_iter *rr_iter_name_class_type(priv_getdns_rr_iter *rr, + uint8_t *name, uint16_t rr_class, uint16_t rr_type) { - getdns_return_t r; - getdns_dict *extensions; + while (rr_iter_ansauth(rr) && !( + rr_iter_type(rr) == rr_type && + rr_iter_class(rr) == rr_class && + rr_owner_equal(rr, name))) - if (!(extensions = getdns_dict_create_with_context( - response->chain->dns_req->context))) - return GETDNS_RETURN_MEMORY_ERROR; + rr = priv_getdns_rr_iter_next(rr); - if (!(r = getdns_dict_set_int(extensions, - "dnssec_ok_checking_disabled", GETDNS_EXTENSION_TRUE))) - - r = priv_getdns_general_loop(response->chain->dns_req->context, - response->chain->dns_req->loop, name, rrtype, extensions, - response, NULL, NULL, chain_response_callback); - - getdns_dict_destroy(extensions); - return r; + return rr && rr->pos ? rr : NULL; } -static void -launch_chain_link_lookup( - struct validation_chain *chain, priv_getdns_rdf_iter *rdf_dname) +static priv_getdns_rr_iter *rr_iter_not_name_class_type(priv_getdns_rr_iter *rr, + uint8_t *name, uint16_t rr_class, uint16_t rr_type) { - int r; - struct chain_link *link; - uint8_t dname_spc[256], *dname; - char name[1024]; - size_t dname_spc_sz = sizeof(dname_spc); + while (rr_iter_ansauth(rr) && ( + rr_iter_type(rr) == GETDNS_RRTYPE_RRSIG || ( + rr_iter_type(rr) == rr_type && + rr_iter_class(rr) == rr_class && + rr_owner_equal(rr, name)))) + + rr = priv_getdns_rr_iter_next(rr); - if (!(dname = priv_getdns_rdf_if_or_as_decompressed( - rdf_dname, dname_spc, &dname_spc_sz))) - return; - - if (!gldns_wire2str_dname_buf(dname, (dname == dname_spc ? - sizeof(dname_spc) : rdf_dname->nxt - rdf_dname->pos), - name, sizeof(name))) - return; - - if ((link = (struct chain_link *) - getdns_rbtree_search((getdns_rbtree_t *)&(chain->root), name))) - return; - - link = GETDNS_MALLOC(chain->mf, struct chain_link); - link->node.key = getdns_strdup(&chain->mf, name); - - chain_response_init(chain, &link->DNSKEY); - chain_response_init(chain, &link->DS); - - getdns_rbtree_insert(&(chain->root), (getdns_rbnode_t *)link); - - chain->lock++; - r = resolve(name, GETDNS_RRTYPE_DNSKEY, &link->DNSKEY); - if (r != 0) - link->DNSKEY.err = r; - - if (name[0] != '.' || name[1] != '\0') { - r = resolve(name, GETDNS_RRTYPE_DS, &link->DS); - if (r != 0) - link->DS.err = r; - } - chain->lock--; + return rr && rr->pos ? rr : NULL; } -static struct validation_chain *create_chain(getdns_dns_req *dns_req, - uint64_t *timeout) +static priv_getdns_rr_iter *rr_iter_rrsig_covering(priv_getdns_rr_iter *rr, + uint8_t *name, uint16_t rr_class, uint16_t rr_type) { - struct validation_chain *chain = GETDNS_MALLOC( - dns_req->context->mf, struct validation_chain); + while (rr_iter_ansauth(rr) && !( + rr_iter_type(rr) == GETDNS_RRTYPE_RRSIG && + rr_iter_class(rr) == rr_class && + rr->rr_type + 12 <= rr->nxt && + gldns_read_uint16(rr->rr_type + 10) == rr_type && + rr_owner_equal(rr, name))) - if (! chain) + rr = priv_getdns_rr_iter_next(rr); + + return rr && rr->pos ? rr : NULL; +} + +typedef struct getdns_rrset { + uint8_t *name; + uint16_t rr_class; + uint16_t rr_type; + uint8_t *pkt; + size_t pkt_len; + uint8_t name_spc[]; +} getdns_rrset; + +typedef struct rrtype_iter { + priv_getdns_rr_iter rr_i; + getdns_rrset *rrset; +} rrtype_iter; + +typedef struct rrsig_iter { + priv_getdns_rr_iter rr_i; + getdns_rrset *rrset; +} rrsig_iter; + +static rrtype_iter *rrtype_iter_next(rrtype_iter *i) +{ + return (rrtype_iter *) rr_iter_name_class_type( + priv_getdns_rr_iter_next(&i->rr_i), + i->rrset->name, i->rrset->rr_class, i->rrset->rr_type); +} + +static rrtype_iter *rrtype_iter_init(rrtype_iter *i, getdns_rrset *rrset) +{ + i->rrset = rrset; + return (rrtype_iter *) rr_iter_name_class_type( + priv_getdns_rr_iter_init(&i->rr_i, rrset->pkt, rrset->pkt_len ), + i->rrset->name, i->rrset->rr_class, i->rrset->rr_type); +} + +inline static int rrset_has_rrs(getdns_rrset *rrset) +{ + rrtype_iter rr_spc; + return rrtype_iter_init(&rr_spc, rrset) != NULL; +} + +static rrsig_iter *rrsig_iter_next(rrsig_iter *i) +{ + return (rrsig_iter *) rr_iter_rrsig_covering( + priv_getdns_rr_iter_next(&i->rr_i), + i->rrset->name, i->rrset->rr_class, i->rrset->rr_type); +} + +static rrsig_iter *rrsig_iter_init(rrsig_iter *i, getdns_rrset *rrset) +{ + i->rrset = rrset; + return (rrsig_iter *) rr_iter_rrsig_covering( + priv_getdns_rr_iter_init(&i->rr_i, rrset->pkt, rrset->pkt_len), + i->rrset->name, i->rrset->rr_class, i->rrset->rr_type); +} + +inline static int rrset_has_rrsigs(getdns_rrset *rrset) +{ + rrsig_iter rrsig; + return rrsig_iter_init(&rrsig, rrset) != NULL; +} + +#if defined(SEC_DEBUG) && SEC_DEBUG +static void debug_sec_print_rrset(const char *msg, getdns_rrset *rrset) +{ + char owner[1024]; + char buf_space[2048]; + gldns_buffer buf; + rrtype_iter *rr, rr_space; + rrsig_iter *rrsig, rrsig_space; + size_t i; + + if (!rrset) { + DEBUG_SEC(""); + return; + } + gldns_buffer_init_frm_data(&buf, buf_space, sizeof(buf_space)); + if (gldns_wire2str_dname_buf(rrset->name, 256, owner, sizeof(owner))) + gldns_buffer_printf(&buf, "%s ", owner); + else gldns_buffer_printf(&buf, " "); + + switch (rrset->rr_class) { + case GETDNS_RRCLASS_IN : gldns_buffer_printf(&buf, "IN ") ; break; + case GETDNS_RRCLASS_CH : gldns_buffer_printf(&buf, "CH ") ; break; + case GETDNS_RRCLASS_HS : gldns_buffer_printf(&buf, "HS ") ; break; + case GETDNS_RRCLASS_NONE: gldns_buffer_printf(&buf, "NONE "); break; + case GETDNS_RRCLASS_ANY : gldns_buffer_printf(&buf, "ANY ") ; break; + default : gldns_buffer_printf(&buf, "CLASS%d " + , rrset->rr_class); + break; + } + gldns_buffer_printf(&buf, "%s", priv_getdns_rr_type_name(rrset->rr_type)); + + gldns_buffer_printf(&buf, ", rrs:"); + for ( rr = rrtype_iter_init(&rr_space, rrset), i = 1 + ; rr + ; rr = rrtype_iter_next(rr), i++) + gldns_buffer_printf(&buf, " %d", (int)i); + + gldns_buffer_printf(&buf, ", rrsigs:"); + for ( rrsig = rrsig_iter_init(&rrsig_space, rrset), i = 1 + ; rrsig + ; rrsig = rrsig_iter_next(rrsig), i++) + gldns_buffer_printf(&buf, " %d", (int)i); + + DEBUG_SEC("%s%s\n", msg, buf_space); +} +#else +#define debug_sec_print_rrset(...) DEBUG_OFF(__VA_ARGS__) +#endif + +typedef struct rrset_iter rrset_iter; +struct rrset_iter { + getdns_rrset rrset; + uint8_t name_spc[256]; + size_t name_len; + priv_getdns_rr_iter rr_i; +}; + +static rrset_iter *rrset_iter_init(rrset_iter *i, uint8_t *pkt, size_t pkt_len) +{ + priv_getdns_rr_iter *rr; + + i->rrset.name = i->name_spc; + i->rrset.pkt = pkt; + i->rrset.pkt_len = pkt_len; + i->name_len = 0; + + for ( rr = priv_getdns_rr_iter_init(&i->rr_i, pkt, pkt_len) + ;(rr = rr_iter_ansauth(rr)) + ; rr = priv_getdns_rr_iter_next(rr)) { + + if ((i->rrset.rr_type = rr_iter_type(rr)) + == GETDNS_RRTYPE_RRSIG) + continue; + + i->rrset.rr_class = rr_iter_class(rr); + + if (!(i->rrset.name = priv_getdns_owner_if_or_as_decompressed( + rr, i->name_spc, &i->name_len))) + continue; + + return i; + } + return NULL; +} + +static rrset_iter *rrset_iter_rewind(rrset_iter *i) +{ + return rrset_iter_init(i, i->rrset.pkt, i->rrset.pkt_len); +} + +static rrset_iter *rrset_iter_next(rrset_iter *i) +{ + priv_getdns_rr_iter *rr; + + if (!(rr = i && i->rr_i.pos ? &i->rr_i : NULL)) return NULL; - getdns_rbtree_init(&(chain->root), - (int (*)(const void *, const void *)) strcmp); - chain->mf.mf_arg = dns_req->context->mf.mf_arg; - chain->mf.mf.ext.malloc = dns_req->context->mf.mf.ext.malloc; - chain->mf.mf.ext.realloc = dns_req->context->mf.mf.ext.realloc; - chain->mf.mf.ext.free = dns_req->context->mf.mf.ext.free; - chain->dns_req = dns_req; - chain->lock = 0; - chain->timeout = timeout; - return chain; + if (!(rr = rr_iter_not_name_class_type(rr, + i->rrset.name, i->rrset.rr_class, i->rrset.rr_type))) + return NULL; + + i->rrset.rr_type = rr_iter_type(rr); + i->rrset.rr_class = rr_iter_class(rr); + if (!(i->rrset.name = priv_getdns_owner_if_or_as_decompressed( + rr, i->name_spc, &i->name_len))) + return rrset_iter_next(i); + + return i; } -static void destroy_chain_link(getdns_rbnode_t * node, void *arg) +static getdns_rrset *rrset_iter_value(rrset_iter *i) { - struct chain_link *link = (struct chain_link*) node; - struct validation_chain *chain = (struct validation_chain*) arg; - - free((void *)link->node.key); - - getdns_list_destroy(link->DNSKEY.result); - if (link->DNSKEY.dns_req) { - getdns_context_clear_outbound_request(link->DNSKEY.dns_req); - dns_req_free(link->DNSKEY.dns_req); - } - getdns_list_destroy(link->DS.result); - if (link->DS.dns_req) { - getdns_context_clear_outbound_request(link->DS.dns_req); - dns_req_free(link->DS.dns_req); - } - GETDNS_FREE(chain->mf, link); + if (!i) + return NULL; + if (!i->rr_i.pos) + return NULL; + return &i->rrset; } -static void destroy_chain(struct validation_chain *chain) -{ - getdns_traverse_postorder(&(chain->root), destroy_chain_link, chain); - GETDNS_FREE(chain->mf, chain); -} +/* ------------------------------------------------------------------------- */ -/* Do some additional requests to fetch the complete validation chain */ -static void -getdns_get_validation_chain(getdns_dns_req *dns_req, uint64_t *timeout) -{ - getdns_network_req **netreq_p, *netreq; - struct validation_chain *chain = create_chain(dns_req, timeout); - priv_getdns_rr_iter rr_iter_storage, *rr_iter; - priv_getdns_rdf_iter rdf_storage, *rdf; - gldns_pkt_section section; - uint16_t rr_type; +typedef struct chain_head chain_head; +typedef struct chain_node chain_node; - if (! chain) { - priv_getdns_call_user_callback( - dns_req, create_getdns_response(dns_req)); - return; - } - for (netreq_p = dns_req->netreqs; (netreq = *netreq_p); netreq_p++) { +struct chain_head { + struct mem_funcs my_mf; - for ( rr_iter = priv_getdns_rr_iter_init(&rr_iter_storage - , netreq->response - , netreq->response_len) - ; rr_iter - ; rr_iter = priv_getdns_rr_iter_next(rr_iter) - ) { - section = priv_getdns_rr_iter_section(rr_iter); - if (section != GLDNS_SECTION_ANSWER && - section != GLDNS_SECTION_AUTHORITY) - continue; + chain_head *next; + chain_node *parent; + size_t node_count; /* Number of nodes attached directly + * to this head. For cleaning. */ + getdns_rrset rrset; + getdns_network_req *netreq; - rr_type = gldns_read_uint16(rr_iter->rr_type); - if (rr_type != GETDNS_RRTYPE_RRSIG) - continue; + uint8_t name_spc[]; +}; - if (!(rdf = priv_getdns_rdf_iter_init_at( - &rdf_storage , rr_iter, 7))) - continue; - - launch_chain_link_lookup(chain, rdf); - } - } - callback_on_complete_chain(chain); -} - - -void priv_getdns_get_validation_chain(getdns_dns_req *dns_req) -{ - getdns_get_validation_chain(dns_req, NULL); -} - -/********************** functions for validate_dnssec *************************/ - -static getdns_return_t -priv_getdns_rr_list_from_list(struct getdns_list *list, ldns_rr_list **rr_list) -{ - getdns_return_t r; - size_t i, l; - struct getdns_dict *rr_dict; - ldns_rr *rr; - - if ((r = getdns_list_get_length(list, &l))) - return r; - - if (! (*rr_list = ldns_rr_list_new())) - return GETDNS_RETURN_MEMORY_ERROR; - - for (i = 0; i < l; i++) { - if ((r = getdns_list_get_dict(list, i, &rr_dict))) - break; - - if ((r = priv_getdns_create_rr_from_dict(rr_dict, &rr))) - break; - - if (! ldns_rr_list_push_rr(*rr_list, rr)) { - ldns_rr_free(rr); - r = GETDNS_RETURN_GENERIC_ERROR; - break; - } - } - if (r) - ldns_rr_list_deep_free(*rr_list); - return r; -} - -static int -priv_getdns_rr_dict_with_compressed_names(getdns_dict *rr_dict) -{ - uint32_t rr_type; - getdns_dict *rdata; - - if (getdns_dict_get_int(rr_dict, "type", &rr_type)) - return 0; - if (rr_type == GETDNS_RRTYPE_RRSIG) { - if (getdns_dict_get_dict(rr_dict, "rdata", &rdata)) - return 0; - if (getdns_dict_get_int(rdata, "type_covered", &rr_type)) - return 0; - } - switch (rr_type) { - case GETDNS_RRTYPE_NS: - case GETDNS_RRTYPE_MD: - case GETDNS_RRTYPE_CNAME: - case GETDNS_RRTYPE_SOA: - case GETDNS_RRTYPE_MG: - case GETDNS_RRTYPE_MR: - case GETDNS_RRTYPE_PTR: - case GETDNS_RRTYPE_MINFO: - case GETDNS_RRTYPE_MX: - return 1; - default: - return 0; - } -} - -static int -ldns_dname_compare_v(const void *a, const void *b) { - return ldns_dname_compare((ldns_rdf *)a, (ldns_rdf *)b); -} - -ldns_status -priv_getdns_ldns_dnssec_zone_add_rr(ldns_dnssec_zone *zone, ldns_rr *rr) -{ - ldns_dnssec_name *new_name; - ldns_rbnode_t *new_node; - - if (ldns_rr_get_type(rr) != LDNS_RR_TYPE_NSEC3) - return ldns_dnssec_zone_add_rr(zone, rr); +struct chain_node { + chain_node *parent; - if (!(new_name = ldns_dnssec_name_new())) - return LDNS_STATUS_MEM_ERR; + getdns_rrset dnskey; + getdns_network_req *dnskey_req; - new_name->name = ldns_rdf_clone(ldns_rr_owner(rr)); - new_name->hashed_name = ldns_dname_label(ldns_rr_owner(rr), 0); - new_name->name_alloced = true; + getdns_rrset ds; + getdns_network_req *ds_req; - if (!(new_node = LDNS_MALLOC(ldns_rbnode_t))) { - ldns_dnssec_name_free(new_name); - return LDNS_STATUS_MEM_ERR; - } - new_node->key = new_name->name; - new_node->data = new_name; - if (!zone->names) - zone->names = ldns_rbtree_create(ldns_dname_compare_v); - (void)ldns_rbtree_insert(zone->names, new_node); + getdns_network_req *soa_req; -#ifdef LDNS_DNSSEC_ZONE_HASHED_NAMES - if (!(new_node = LDNS_MALLOC(ldns_rbnode_t))) { - ldns_dnssec_name_free(new_name); - return LDNS_STATUS_MEM_ERR; - } - new_node->key = new_name->hashed_name; - new_node->data = new_name; - if (!zone->hashed_names) { - zone->_nsec3params = rr; - zone->hashed_names = ldns_rbtree_create(ldns_dname_compare_v); - } - (void)ldns_rbtree_insert(zone->hashed_names, new_node); -#endif + chain_head *chains; +}; - return ldns_dnssec_zone_add_rr(zone, rr); +inline static size_t _dname_len(uint8_t *name) +{ + uint8_t *p; + for (p = name; *p; p += *p + 1); + return p - name + 1; } -static getdns_return_t -priv_getdns_dnssec_zone_from_list(struct getdns_list *list, - ldns_dnssec_zone **zone) +inline static size_t _dname_label_count(uint8_t *name) { - getdns_return_t r; - size_t i, l; - struct getdns_dict *rr_dict; - ldns_rr *rr; - ldns_status s; + size_t c; + for (c = 0; *name; name += *name + 1, c++); + return c; +} - if ((r = getdns_list_get_length(list, &l))) - return r; +static int key_matches_signer(getdns_rrset *dnskey, getdns_rrset *rrset) +{ + rrtype_iter rr_spc, *rr; + rrsig_iter rrsig_spc, *rrsig; + uint16_t keytag; + priv_getdns_rdf_iter rdf_spc, *rdf; + uint8_t signer_spc[256], *signer; + size_t signer_len = sizeof(signer_spc); - if (! (*zone = ldns_dnssec_zone_new())) - return GETDNS_RETURN_MEMORY_ERROR; + assert(dnskey->rr_type == GETDNS_RRTYPE_DNSKEY); - for (i = 0; i < l; i++) { - if ((r = getdns_list_get_dict(list, i, &rr_dict))) - break; - if (priv_getdns_rr_dict_with_compressed_names(rr_dict)) + for ( rr = rrtype_iter_init(&rr_spc, dnskey) + ; rr ; rr = rrtype_iter_next(rr) ) { + + keytag = gldns_calc_keytag_raw(rr->rr_i.rr_type + 10, + rr->rr_i.nxt - rr->rr_i.rr_type - 10); + + for ( rrsig = rrsig_iter_init(&rrsig_spc, rrset) + ; rrsig ; rrsig = rrsig_iter_next(rrsig) ) { + + if (/* Space for keytag & signer in rrsig rdata? */ + rrsig->rr_i.nxt >= rrsig->rr_i.rr_type + 28 + + /* Does the keytag match? */ + && gldns_read_uint16(rrsig->rr_i.rr_type + 26) + == keytag + + /* Does the signer name match? */ + && (rdf = priv_getdns_rdf_iter_init_at( + &rdf_spc, &rrsig->rr_i, 7)) + + && (signer = priv_getdns_rdf_if_or_as_decompressed( + rdf, signer_spc, &signer_len)) + + && priv_getdns_dname_equal(dnskey->name, signer)) + + return 1; + } + } + return 0; +} + +static ldns_rr *rr2ldns_rr(priv_getdns_rr_iter *rr) +{ + ldns_rr *rr_l; + size_t pos = rr->pos - rr->pkt; + + if (ldns_wire2rr(&rr_l, rr->pkt, rr->pkt_end - rr->pkt, &pos, + priv_getdns_rr_iter_section(rr)) == LDNS_STATUS_OK) + return rr_l; + else + return NULL; +} + +static ldns_rr_list *rrset2ldns_rr_list(getdns_rrset *rrset) +{ + rrtype_iter rr_spc, *rr; + ldns_rr_list *rr_list = ldns_rr_list_new(); + ldns_rr *rr_l; + + if (rr_list) { + for ( rr = rrtype_iter_init(&rr_spc, rrset) + ; rr ; rr = rrtype_iter_next(rr) ) + if ((rr_l = rr2ldns_rr(&rr->rr_i))) + ldns_rr_list_push_rr(rr_list, rr_l); + } + return rr_list; +} + + +/* Verifies the signature rrsig for rrset rrset with key key. + * When the rrset was a wildcard expansion (rrsig labels < labels owner name), + * nc_name will be set to the next closer (within rrset->name). + */ +static int _getdns_verify_rrsig( + getdns_rrset *rrset, rrsig_iter *rrsig, rrtype_iter *key, uint8_t **nc_name) +{ + ldns_rr_list *rrset_l = rrset2ldns_rr_list(rrset); + ldns_rr *rrsig_l = rr2ldns_rr(&rrsig->rr_i); + ldns_rr *key_l = rr2ldns_rr(&key->rr_i); + int r; + size_t to_skip; + + /* nc_name should already have been initialized by the parent! */ + assert(nc_name); + assert(!*nc_name); + + r = rrset_l && rrsig_l && key_l && + ldns_verify_rrsig(rrset_l, rrsig_l, key_l) == LDNS_STATUS_OK; + + ldns_rr_list_deep_free(rrset_l); + ldns_rr_free(rrsig_l); + ldns_rr_free(key_l); + + if (!r) + return 0; + + /* Verification has already been done, so the labels rdata field is + * definitely readable + */ + assert(rrsig->rr_i.rr_type + 14 <= rrsig->rr_i.nxt); + + /* If the number of labels in the owner name mathes the "labels" rdata + * field, then this was not a wildcard expansion, and everything is + * good. + */ + if ((size_t)rrsig->rr_i.rr_type[13] == _dname_label_count(rrset->name)) + return 1; + + /* This is a valid wildcard expansion. Calculate and return the + * "Next closer" name, because we need another NSEC to cover it + * for wildcards to hold...xi + * (except for rrsigs for NSECs, but those are dealt with later) + */ + to_skip = _dname_label_count(rrset->name) + - (size_t)rrsig->rr_i.rr_type[13] - 1; + + for ( *nc_name = rrset->name + ; to_skip + ; *nc_name += **nc_name + 1, to_skip--); + + return 1; +} + +static uint8_t *_getdns_nsec3_hash_label(uint8_t *label, size_t label_len, + uint8_t *name, uint8_t algorithm, uint16_t iterations, uint8_t *salt) +{ + ldns_rdf name_l = { _dname_len(name), LDNS_RDF_TYPE_DNAME, name }; + ldns_rdf *hname_l; + + if (!(hname_l = ldns_nsec3_hash_name( + &name_l, algorithm, iterations, *salt, salt + 1))) + return NULL; + + if ( label_len < hname_l->_size-1 + || label_len < *((uint8_t *)hname_l->_data) + 1 + || hname_l->_size-1 < *((uint8_t *)hname_l->_data) + 1) { + ldns_rdf_deep_free(hname_l); + return NULL; + } + memcpy(label, hname_l->_data, *((uint8_t *)hname_l->_data) + 1); + ldns_rdf_deep_free(hname_l); + return label; +} + +static int dnskey_signed_rrset( + rrtype_iter *dnskey, getdns_rrset *rrset, uint8_t **nc_name) +{ + rrsig_iter rrsig_spc, *rrsig; + priv_getdns_rdf_iter rdf_spc, *rdf; + uint8_t signer_spc[256], *signer; + size_t signer_len = sizeof(signer_spc); + uint16_t keytag; + + assert(dnskey->rrset->rr_type == GETDNS_RRTYPE_DNSKEY); + assert(nc_name); + + *nc_name = NULL; + + keytag = gldns_calc_keytag_raw(dnskey->rr_i.rr_type + 10, + dnskey->rr_i.nxt - dnskey->rr_i.rr_type - 10); + + for ( rrsig = rrsig_iter_init(&rrsig_spc, rrset) + ; rrsig ; rrsig = rrsig_iter_next(rrsig) ) { + + if (/* Space for keytag & signer in rrsig rdata? */ + rrsig->rr_i.nxt >= rrsig->rr_i.rr_type + 28 + + /* Does the keytag match? */ + && gldns_read_uint16(rrsig->rr_i.rr_type + 26) == keytag + + /* Does the signer name match? */ + && (rdf = priv_getdns_rdf_iter_init_at( + &rdf_spc, &rrsig->rr_i, 7)) + + && (signer = priv_getdns_rdf_if_or_as_decompressed( + rdf, signer_spc, &signer_len)) + + && priv_getdns_dname_equal(dnskey->rrset->name, signer) + + /* Does the signature verify? */ + && _getdns_verify_rrsig(rrset, rrsig, dnskey, nc_name)) { + + debug_sec_print_rr("key ", &dnskey->rr_i); + debug_sec_print_rrset("signed ", rrset); + + return 1; + } + } + return 0; +} + +static int find_nsec_covering_name( + getdns_rrset *dnskey, getdns_rrset *rrset, uint8_t *name, int *opt_out); + +static int a_key_signed_rrset(getdns_rrset *keyset, getdns_rrset *rrset) +{ + rrtype_iter dnskey_spc, *dnskey; + uint8_t *nc_name; + + assert(keyset->rr_type == GETDNS_RRTYPE_DNSKEY); + + for ( dnskey = rrtype_iter_init(&dnskey_spc, keyset) + ; dnskey ; dnskey = rrtype_iter_next(dnskey) ) { + + if (!dnskey_signed_rrset(dnskey, rrset, &nc_name)) continue; - if ((r = priv_getdns_create_rr_from_dict(rr_dict, &rr))) - break; + if (!nc_name) /* Not a wildcard, then success! */ + return 1; - if ((s = priv_getdns_ldns_dnssec_zone_add_rr(*zone, rr))) { - ldns_rr_free(rr); - r = GETDNS_RETURN_GENERIC_ERROR; - break; - } + /* Wildcard RRSIG for a NSEC on the wildcard. + * There is no more specific! + */ + if (rrset->rr_type == GETDNS_RRTYPE_NSEC && + rrset->name[0] == 1 && rrset->name[1] == '*') + return 1; + + debug_sec_print_rrset("wildcard expanded to: ", rrset); + debug_sec_print_dname("Find NSEC covering the more sepecific: " + , nc_name); + + if (find_nsec_covering_name(keyset, rrset, nc_name, NULL)) + return 1; } - if (r) - ldns_dnssec_zone_free(*zone); - return r; + return 0; } -typedef struct zone_iter { - ldns_dnssec_zone *zone; - ldns_rbnode_t *cur_node; - ldns_dnssec_rrsets *cur_rrset; - - ldns_dnssec_rrsets nsec_rrset; - ldns_dnssec_rrs nsec_rrs; -} zone_iter; - -static void -rrset_iter_init_zone(zone_iter *i, ldns_dnssec_zone *zone) -{ - ldns_dnssec_name *name; - assert(i); - - i->zone = zone; - if ((i->cur_node = zone->names - ? ldns_rbtree_first(zone->names) - : LDNS_RBTREE_NULL) == LDNS_RBTREE_NULL) { - - i->cur_rrset = NULL; - return; - } - - i->cur_rrset = ((ldns_dnssec_name *)i->cur_node->data)->rrsets; - if (!i->cur_rrset) { - name = ((ldns_dnssec_name *)i->cur_node->data); - if (name->nsec && name->nsec_signatures) { - i->cur_rrset = &i->nsec_rrset; - i->nsec_rrset.rrs = &i->nsec_rrs; - i->nsec_rrs.rr = name->nsec; - i->nsec_rrs.next = NULL; - i->nsec_rrset.type = ldns_rr_get_type(name->nsec); - i->nsec_rrset.signatures = - name->nsec_signatures; - i->nsec_rrset.next = NULL; - return; - } - } -} - -static ldns_dnssec_rrsets * -rrset_iter_value(zone_iter *i) +static int ds_authenticates_keys(getdns_rrset *ds_set, getdns_rrset *dnskey_set) { - assert(i); + rrtype_iter dnskey_spc, *dnskey; + rrtype_iter ds_spc, *ds; + uint16_t keytag; + uint8_t *nc_name; + ldns_rr *dnskey_l; + ldns_rr *ds_l = NULL; - return i->cur_rrset; + assert(ds_set->rr_type == GETDNS_RRTYPE_DS); + assert(dnskey_set->rr_type == GETDNS_RRTYPE_DNSKEY); + + if (!priv_getdns_dname_equal(ds_set->name, dnskey_set->name)) + return 0; + + for ( dnskey = rrtype_iter_init(&dnskey_spc, dnskey_set) + ; dnskey ; dnskey = rrtype_iter_next(dnskey)) { + + keytag = gldns_calc_keytag_raw(dnskey->rr_i.rr_type + 10, + dnskey->rr_i.nxt - dnskey->rr_i.rr_type - 10); + + dnskey_l = NULL; + + for ( ds = rrtype_iter_init(&ds_spc, ds_set) + ; ds ; ds = rrtype_iter_next(ds)) { + + if (/* Space for keytag & signer in rrsig rdata? */ + ds->rr_i.nxt < ds->rr_i.rr_type + 12 + + /* Does the keytag match? */ + || gldns_read_uint16(ds->rr_i.rr_type+10)!=keytag) + continue; + + if (!dnskey_l) + if (!(dnskey_l = rr2ldns_rr(&dnskey->rr_i))) + continue; + + if (!(ds_l = rr2ldns_rr(&ds->rr_i))) + continue; + + if (!ldns_rr_compare_ds(ds_l, dnskey_l)) { + ldns_rr_free(ds_l); + ds_l = NULL; + continue; + } + ldns_rr_free(dnskey_l); + + if (dnskey_signed_rrset(dnskey, dnskey_set, &nc_name) + && !nc_name /* No DNSKEY's on wildcards! */) { + + debug_sec_print_rrset( + "keyset authenticated: ", dnskey_set); + return 1; + } + debug_sec_print_rrset( + "keyset failed authentication: ", dnskey_set); + + return 0; + } + ldns_rr_free(dnskey_l); + } + return 0; } -static void -rrset_iter_next(zone_iter *i) +static int bitmap_contains_rrtype(priv_getdns_rdf_iter *bitmap, uint16_t rr_type) { - int was_nsec_rrset; - ldns_dnssec_name *name; - assert(i); + uint8_t *dptr, *dend; + uint8_t window = rr_type >> 8; + uint8_t subtype = rr_type & 0xFF; - if (! i->cur_rrset) - return; + if (!bitmap) + return 0; + dptr = bitmap->pos; + dend = bitmap->nxt; - was_nsec_rrset = (i->cur_rrset == &i->nsec_rrset); - if (! (i->cur_rrset = i->cur_rrset->next)) { + /* Type Bitmap = ( Window Block # | Bitmap Length | Bitmap ) + + * dptr[0] dptr[1] dptr[2:] + */ + while (dptr < dend && dptr[0] <= window) { + if (dptr[0] == window && subtype / 8 < dptr[1] && + dptr + dptr[1] + 2 <= dend) + return dptr[2 + subtype / 8] & (0x80 >> (subtype % 8)); + dptr += dptr[1] + 2; /* next window */ + } + return 0; +} - if (!was_nsec_rrset) { - name = ((ldns_dnssec_name *)i->cur_node->data); - if (name->nsec && name->nsec_signatures) { - i->cur_rrset = &i->nsec_rrset; - i->nsec_rrset.rrs = &i->nsec_rrs; - i->nsec_rrs.rr = name->nsec; - i->nsec_rrs.next = NULL; - i->nsec_rrset.type = ldns_rr_get_type(name->nsec); - i->nsec_rrset.signatures = - name->nsec_signatures; - i->nsec_rrset.next = NULL; - return; +static int nsec_bitmap_excludes_rrtype(getdns_rrset *nsec_rrset, uint16_t rr_type) +{ + rrtype_iter nsec_space, *nsec_rr; + priv_getdns_rdf_iter bitmap_spc, *bitmap; + + return (nsec_rrset->rr_type == GETDNS_RRTYPE_NSEC || + nsec_rrset->rr_type == GETDNS_RRTYPE_NSEC3 ) + && (nsec_rr = rrtype_iter_init(&nsec_space, nsec_rrset)) + && (bitmap = priv_getdns_rdf_iter_init_at(&bitmap_spc, &nsec_rr->rr_i, + nsec_rrset->rr_type == GETDNS_RRTYPE_NSEC ? 1: 5)) + && !bitmap_contains_rrtype(bitmap, rr_type); + + return 0; +} + +static uint8_t **reverse_labels(uint8_t *dname, uint8_t **labels) +{ + if (*dname) + labels = reverse_labels(dname + *dname + 1, labels); + *labels = dname; + return labels + 1; +} + +static uint8_t *dname_shared_parent(uint8_t *left, uint8_t *right) +{ + uint8_t *llabels[128], *rlabels[128], **last_llabel, **last_rlabel, + **llabel, **rlabel, *l, *r, sz; + + last_llabel = reverse_labels(left, llabels); + last_rlabel = reverse_labels(right, rlabels); + + for ( llabel = llabels, rlabel = rlabels + ; llabel < last_llabel + ; llabel++, rlabel++ ) { + + if ( rlabel == last_rlabel + || (sz = **llabel) != **rlabel) + return llabel[-1]; + + for (l = *llabel+1, r = *rlabel+1; sz; l++, r++, sz-- ) + if (*l != *r && tolower((unsigned char)*l) != + tolower((unsigned char)*r)) + return llabel[-1]; + } + return llabel[-1]; +} + +static int dname_compare(uint8_t *left, uint8_t *right) +{ + uint8_t *llabels[128], *rlabels[128], **last_llabel, **last_rlabel, + **llabel, **rlabel, *l, *r, lsz, rsz; + + last_llabel = reverse_labels(left, llabels); + last_rlabel = reverse_labels(right, rlabels); + + for ( llabel = llabels, rlabel = rlabels + ; llabel < last_llabel + ; llabel++, rlabel++ ) { + + if (rlabel == last_rlabel) + return 1; + + for ( l = *llabel, lsz = *l++, r = *rlabel, rsz = *r++ + ; lsz; l++, r++, lsz--, rsz-- ) { + + if (!rsz) + return 1; + if (*l != *r && tolower((unsigned char)*l) != + tolower((unsigned char)*r)) { + if (tolower((unsigned char)*l) < + tolower((unsigned char)*r)) + return -1; + return 1; } } - i->cur_node = ldns_rbtree_next(i->cur_node); - i->cur_rrset = i->cur_node != LDNS_RBTREE_NULL - ? ((ldns_dnssec_name *)i->cur_node->data)->rrsets - : NULL; + } + return rlabel == last_rlabel ? 0 : -1; +} + +static int nsec_covers_name( + getdns_rrset *nsec, uint8_t *name, uint8_t **ce_name) +{ + uint8_t owner_spc[256], *owner; + size_t owner_len = sizeof(owner_spc); + uint8_t next_spc[256], *next; + size_t next_len = sizeof(next_spc); + rrtype_iter rr_spc, *rr; + priv_getdns_rdf_iter rdf_spc, *rdf; + int nsec_cmp; + uint8_t *common1, *common2; + + if ( !(rr = rrtype_iter_init(&rr_spc, nsec)) + || !(rdf = priv_getdns_rdf_iter_init(&rdf_spc, &rr->rr_i)) + || !(owner = priv_getdns_owner_if_or_as_decompressed( + &rr->rr_i, owner_spc, &owner_len)) + || !(next = priv_getdns_rdf_if_or_as_decompressed( + rdf, next_spc, &next_len))) + return 0; + + debug_sec_print_dname("nsec owner: ", owner); + debug_sec_print_dname("name : ", name); + debug_sec_print_dname("nsec next : ", next); + + if (ce_name) { + common1 = dname_shared_parent(name, owner); + common2 = dname_shared_parent(name, next); + *ce_name = _dname_label_count(common1) + > _dname_label_count(common2) ? common1 : common2; + debug_sec_print_dname("nsec closest encloser: ", *ce_name); + } + + nsec_cmp = dname_compare(owner, next); + if (nsec_cmp < 0) { + DEBUG_SEC("nsec owner < next\n"); + return dname_compare(name, owner) >= 0 + && dname_compare(name, next) < 0; + } else if (nsec_cmp > 0) { + /* The wrap around nsec */ + DEBUG_SEC("nsec owner > next\n"); + return dname_compare(name, owner) >= 0; + } else { + DEBUG_SEC("nsec owner == next\n"); + return 1; } } -static ldns_rr_list * -rrs2rr_list(ldns_dnssec_rrs *rrs) +static uint8_t *name2nsec3_label( + getdns_rrset *nsec3, uint8_t *name, uint8_t *label, size_t label_len) { - ldns_rr_list *r = ldns_rr_list_new(); - if (r) - while (rrs) { - (void) ldns_rr_list_push_rr(r, rrs->rr); - rrs = rrs->next; - } + rrsig_iter rrsig_spc, *rrsig; + priv_getdns_rdf_iter rdf_spc, *rdf; + uint8_t signer_spc[256], *signer; + size_t signer_len = sizeof(signer_spc); + rrtype_iter rr_spc, *rr; + + if (/* With the "first" signature */ + (rrsig = rrsig_iter_init(&rrsig_spc, nsec3)) + + /* Access the signer name rdata field (7th) */ + && (rdf = priv_getdns_rdf_iter_init_at( + &rdf_spc, &rrsig->rr_i, 7)) + + /* Verify & decompress */ + && (signer = priv_getdns_rdf_if_or_as_decompressed( + rdf, signer_spc, &signer_len)) + + /* signer of the NSEC3 is direct parent for this NSEC3? */ + && priv_getdns_dname_equal( + signer, nsec3->name + *nsec3->name + 1) + + /* signer of the NSEC3 is parent of name? */ + && is_subdomain(signer, name) + + /* Initialize rr for getting NSEC3 rdata fields */ + && (rr = rrtype_iter_init(&rr_spc, nsec3)) + + /* Check for available space to get rdata fields */ + && rr->rr_i.rr_type + 15 <= rr->rr_i.nxt + && rr->rr_i.rr_type + 14 + rr->rr_i.rr_type[14] <= rr->rr_i.nxt) + + /* Get the hashed label */ + return _getdns_nsec3_hash_label(label, label_len, name, + rr->rr_i.rr_type[10], + gldns_read_uint16(rr->rr_i.rr_type + 12), + rr->rr_i.rr_type + 14); + return NULL; +} + +static uint8_t *_dname_label_copy(uint8_t *dst, uint8_t *src, size_t dst_len) +{ + uint8_t *r = dst, i; + + if (!src || *src + 1 > dst_len) + return NULL; + + for (i = *src + 1; i > 0; i--) + *dst++ = tolower(*src++); + return r; } -static ldns_status -verify_rrset(ldns_dnssec_rrsets *rrset_and_sigs, - const ldns_rr_list *keys, ldns_rr_list *good_keys) +static int nsec3_matches_name(getdns_rrset *nsec3, uint8_t *name) { - ldns_status s; - ldns_rr_list *rrset = rrs2rr_list(rrset_and_sigs->rrs); - ldns_rr_list *sigs = rrs2rr_list(rrset_and_sigs->signatures); - s = ldns_verify(rrset, sigs, keys, good_keys); - ldns_rr_list_free(sigs); - ldns_rr_list_free(rrset); - return s; + uint8_t label[64], owner[64]; + + if (name2nsec3_label(nsec3, name, label, sizeof(label)) + && _dname_label_copy(owner, nsec3->name, sizeof(owner))) + + return *nsec3->name == label[0] /* Labels same size? */ + && memcmp(owner + 1, label + 1, label[0]) == 0; + + return 0; } -static ldns_status -chase(ldns_dnssec_rrsets *rrset, ldns_dnssec_zone *support, - ldns_rr_list *support_keys, ldns_rr_list *trusted) +static int nsec3_covers_name(getdns_rrset *nsec3, uint8_t *name, int *opt_out) { - ldns_status s; - ldns_rr_list *verifying_keys; - size_t i, j; - ldns_rr *rr; - ldns_dnssec_rrsets *key_rrset; - ldns_dnssec_rrs *rrs; + uint8_t label[65], next[65], owner[65]; + rrtype_iter rr_spc, *rr; + priv_getdns_rdf_iter rdf_spc, *rdf; + int nsz = 0, nsec_cmp; - /* Secure by trusted keys? */ - s = verify_rrset(rrset, trusted, NULL); - if (s == 0) + if (!name2nsec3_label(nsec3, name, label, sizeof(label)-1)) + return 0; + label[label[0]+1] = 0; + + if ( !(rr = rrtype_iter_init(&rr_spc, nsec3)) + || !(rdf = priv_getdns_rdf_iter_init_at(&rdf_spc, &rr->rr_i, 4)) + || rdf->pos + *rdf->pos + 1 > rdf->nxt + || (nsz = gldns_b32_ntop_extended_hex(rdf->pos + 1, *rdf->pos, + (char *)next + 1, sizeof(next)-2)) < 0 + || *nsec3->name > sizeof(owner) - 2 + || !_dname_label_copy(owner, nsec3->name, sizeof(owner)-1)) { + + DEBUG_SEC("Error getting NSEC3 owner & next labels\n"); + return 0; + } + owner[owner[0]+1] = 0; + next[(next[0] = (uint8_t)nsz)+1] = 0; + + if (opt_out) + *opt_out = (rr->rr_i.rr_type[11] & 1) != 0; + + debug_sec_print_dname("NSEC3 for: ", name); + debug_sec_print_dname(" is: ", label); + debug_sec_print_dname("inbetween: ", owner); + debug_sec_print_dname(" and: ", next); + + nsec_cmp = dname_compare(owner, next); + if (nsec_cmp < 0) { + DEBUG_SEC("nsec owner < next\n"); + return dname_compare(label, owner) >= 0 + && dname_compare(label, next) < 0; + } else if (nsec_cmp > 0) { + /* The wrap around nsec */ + DEBUG_SEC("nsec owner > next\n"); + return dname_compare(label, owner) >= 0; + } else { + DEBUG_SEC("nsec owner == next\n"); + return 1; + } +} + +static int find_nsec_covering_name( + getdns_rrset *dnskey, getdns_rrset *rrset, uint8_t *name, int *opt_out) +{ + rrset_iter i_spc, *i; + getdns_rrset *n; + + if (opt_out) + *opt_out = 0; + + for ( i = rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len) + ; i ; i = rrset_iter_next(i)) { + + if ((n = rrset_iter_value(i))->rr_type == GETDNS_RRTYPE_NSEC3 + && nsec3_covers_name(n, name, opt_out) + && a_key_signed_rrset(dnskey, n)) { + + debug_sec_print_rrset("NSEC3: ", n); + debug_sec_print_dname("covered: ", name); + + return 1; + } + if ((n = rrset_iter_value(i))->rr_type == GETDNS_RRTYPE_NSEC + && nsec_covers_name(n, name, NULL) + && a_key_signed_rrset(dnskey, n)) { + + debug_sec_print_rrset("NSEC: ", n); + debug_sec_print_dname("covered: ", name); + + return 1; + } + } + return 0; +} + +static int nsec3_find_next_closer( + getdns_rrset *dnskey, getdns_rrset *rrset, uint8_t *nc_name) +{ + uint8_t wc_name[256] = { 1, (uint8_t)'*' }; + int opt_out; + + if (!find_nsec_covering_name(dnskey, rrset, nc_name, &opt_out)) + return 0; + + /* Wild card not needed on a "covering" NODATA response, + * because of opt-out? + * + * We check for opt-out bit, because rcode is unreliable... + * ... the checked packet might be artificially constructed + * (if we came here via getdns_validate_dnssec) in which case + * rcode is always NOERROR. + */ + if (opt_out) + return 1; + + nc_name += *nc_name + 1; + (void) memcpy(wc_name + 2, nc_name, _dname_len(nc_name)); + + return find_nsec_covering_name(dnskey, rrset, wc_name, NULL); +} + +static int key_proves_nonexistance(getdns_rrset *dnskey, getdns_rrset *rrset) +{ + getdns_rrset nsec_rrset, *cover, *ce; + rrset_iter i_spc, *i; + uint8_t *ce_name, *nc_name; + uint8_t wc_name[256] = { 1, (uint8_t)'*' }; + + assert(dnskey->rr_type == GETDNS_RRTYPE_DNSKEY); + + /* The NSEC NODATA case + * ==================== + * NSEC has same ownername as the rrset to deny. + * Only the rr_type is missing from the bitmap. + */ + nsec_rrset = *rrset; + nsec_rrset.rr_type = GETDNS_RRTYPE_NSEC; + if (nsec_bitmap_excludes_rrtype(&nsec_rrset, rrset->rr_type) && + a_key_signed_rrset(dnskey, &nsec_rrset)) { + + debug_sec_print_rrset("NSEC NODATA proof for: ", rrset); + return 1; + } + + /* The NSEC Name error case + * ======================== + * - First find the NSEC that covers the owner name. + */ + for ( i = rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len) + ; i ; i = rrset_iter_next(i)) { + + if ((cover=rrset_iter_value(i))->rr_type != GETDNS_RRTYPE_NSEC + || !nsec_covers_name(cover, rrset->name, &ce_name) + || !a_key_signed_rrset(dnskey, cover)) + continue; + + debug_sec_print_dname("Closest Encloser: ", ce_name); + memcpy(wc_name + 2, ce_name, _dname_len(ce_name)); + debug_sec_print_dname(" Wildcard: ", wc_name); + + return find_nsec_covering_name(dnskey, rrset, wc_name, NULL); + } + + /* The NSEC3 NODATA case + * ===================== + * NSEC3 has same (hashed) ownername as the rrset to deny. + * Only the rr_type (and CNAME) is missing from the bitmap. + */ + for ( i = rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len) + ; i ; i = rrset_iter_next(i)) { + + if ((ce = rrset_iter_value(i))->rr_type == GETDNS_RRTYPE_NSEC3 + && nsec_bitmap_excludes_rrtype(ce, rrset->rr_type) + && nsec_bitmap_excludes_rrtype(ce, GETDNS_RRTYPE_CNAME) + && nsec3_matches_name(ce, rrset->name) + && a_key_signed_rrset(dnskey, ce)) { + + debug_sec_print_rrset("NSEC3 No Data for: ", rrset); + return 1; + } + } + /* The NSEC3 Name error case + * ========================+ + * First find the closest encloser. + */ + for ( nc_name = rrset->name, ce_name = rrset->name + *rrset->name + 1 + ; *ce_name ; nc_name = ce_name, ce_name += *ce_name + 1) { + + for ( i = rrset_iter_init(&i_spc, rrset->pkt, rrset->pkt_len) + ; i ; i = rrset_iter_next(i)) { + + if ( (ce = rrset_iter_value(i))->rr_type + != GETDNS_RRTYPE_NSEC3 + || !nsec3_matches_name(ce, ce_name) + || !a_key_signed_rrset(dnskey, ce)) + continue; + + debug_sec_print_rrset("Closest Encloser: ", ce); + debug_sec_print_dname("Closest Encloser: ", ce_name); + debug_sec_print_dname(" Next closer: ", nc_name); + + if (nsec3_find_next_closer(dnskey, rrset, nc_name)) + return 1; + } + } + return 0; +} + +static int chain_node_get_trusted_keys( + chain_node *node, getdns_rrset *ta, getdns_rrset **keys) +{ + int s; + + /* Descend down to the root */ + if (! node) + return GETDNS_DNSSEC_BOGUS; + + else if (ta->rr_type == GETDNS_RRTYPE_DS) { + + if (ds_authenticates_keys(&node->ds, &node->dnskey)) { + *keys = &node->dnskey; + return GETDNS_DNSSEC_SECURE; + } + + } else if (ta->rr_type == GETDNS_RRTYPE_DNSKEY) { + + /* ta is KSK */ + if (a_key_signed_rrset(ta, &node->dnskey)) { + *keys = &node->dnskey; + return GETDNS_DNSSEC_SECURE; + } + /* ta is ZSK */ + if (key_proves_nonexistance(ta, &node->ds)) + return GETDNS_DNSSEC_INSECURE; + + if (a_key_signed_rrset(ta, &node->ds)) { + if (ds_authenticates_keys(&node->ds, &node->dnskey)) { + *keys = &node->dnskey; + return GETDNS_DNSSEC_SECURE; + } + return GETDNS_DNSSEC_BOGUS; + } + } else + return GETDNS_DNSSEC_BOGUS; + + if (GETDNS_DNSSEC_SECURE != + (s = chain_node_get_trusted_keys(node->parent, ta, keys))) return s; - /* No, chase with support records.. - * Is there a verifying key in the support records? - */ - verifying_keys = ldns_rr_list_new(); - s = verify_rrset(rrset, support_keys, verifying_keys); - if (s != 0) - goto done_free_verifying_keys; + /* keys is an authenticated dnskey rrset always now (i.e. ZSK) */ + ta = *keys; + /* Back up to the head */ + if (key_proves_nonexistance(ta, &node->ds)) + return GETDNS_DNSSEC_INSECURE; - /* Ok, we have verifying keys from the support records. - * Compare them with the *trusted* keys or DSes, - * or chase them further down the validation chain. - */ - for (i = 0; i < ldns_rr_list_rr_count(verifying_keys); i++) { - /* Lookup the rrset for key rr from the support records */ - rr = ldns_rr_list_rr(verifying_keys, i); - key_rrset = ldns_dnssec_zone_find_rrset( - support, ldns_rr_owner(rr), ldns_rr_get_type(rr)); - if (! key_rrset) { - s = LDNS_STATUS_CRYPTO_NO_DNSKEY; + if (key_matches_signer(ta, &node->ds)) { + if (a_key_signed_rrset(ta, &node->ds) && + ds_authenticates_keys(&node->ds, &node->dnskey)) { + + *keys = &node->dnskey; + return GETDNS_DNSSEC_SECURE; + } + return GETDNS_DNSSEC_BOGUS; + } + return GETDNS_DNSSEC_SECURE; +} + +static int chain_head_validate_with_ta(chain_head *head, getdns_rrset *ta) +{ + getdns_rrset *keys; + int s; + + if ((s = chain_node_get_trusted_keys(head->parent, ta, &keys)) + != GETDNS_DNSSEC_SECURE) + return s; + + if (rrset_has_rrs(&head->rrset)) { + if (a_key_signed_rrset(keys, &head->rrset)) + return GETDNS_DNSSEC_SECURE; + + } else if (key_proves_nonexistance(keys, &head->rrset)) + return GETDNS_DNSSEC_SECURE; + + return GETDNS_DNSSEC_BOGUS; +} + +static int chain_head_validate(chain_head *head, rrset_iter *tas) +{ + rrset_iter *i; + getdns_rrset *ta; + int s = GETDNS_DNSSEC_INDETERMINATE; + + for (i = rrset_iter_rewind(tas); i ;i = rrset_iter_next(i)) { + ta = rrset_iter_value(i); + + if ((ta->rr_type != GETDNS_RRTYPE_DNSKEY && + ta->rr_type != GETDNS_RRTYPE_DS + ) || !is_subdomain(ta->name, head->rrset.name)) + continue; + + /* The best status for any ta counts */ + switch (chain_head_validate_with_ta(head, ta)) { + case GETDNS_DNSSEC_SECURE : s = GETDNS_DNSSEC_SECURE; + case GETDNS_DNSSEC_INSECURE: if (s != GETDNS_DNSSEC_SECURE) + s = GETDNS_DNSSEC_INSECURE; + break; + case GETDNS_DNSSEC_BOGUS : if (s != GETDNS_DNSSEC_SECURE && + s != GETDNS_DNSSEC_INSECURE) + s = GETDNS_DNSSEC_BOGUS; + break; + default : break; + } + } + return s; +} + +static int chain_validate_dnssec(chain_head *chain, rrset_iter *tas) +{ + int s = GETDNS_DNSSEC_INDETERMINATE; + chain_head *head; + + /* The netreq status is the worst for any head */ + for (head = chain; head; head = head->next) { + switch (chain_head_validate(head, tas)) { + case GETDNS_DNSSEC_SECURE: + if (s == GETDNS_DNSSEC_INDETERMINATE) + s = GETDNS_DNSSEC_SECURE; + break; + + case GETDNS_DNSSEC_INSECURE: + if (s != GETDNS_DNSSEC_BOGUS) + s = GETDNS_DNSSEC_INSECURE; + break; + + case GETDNS_DNSSEC_BOGUS : + s = GETDNS_DNSSEC_BOGUS; + break; + + default: break; } - /* When we signed ourselves, we have to cross domain border - * and look for a matching DS signed by a parents key - */ - if (rrset == key_rrset) { - /* Is the verifying key trusted? - * (i.e. DS in trusted) - */ - for (j = 0; j < ldns_rr_list_rr_count(trusted); j++) - if (ldns_rr_compare_ds(ldns_rr_list_rr( - trusted, j), rr)) - break; - /* If so, check for the next verifying key - * (or exit SECURE) - */ - if (j < ldns_rr_list_rr_count(trusted)) + } + return s; +} + +static void chain_set_netreq_dnssec_status(chain_head *chain, rrset_iter *tas) +{ + chain_head *head; + + /* The netreq status is the worst for any head */ + for (head = chain; head; head = head->next) { + if (!head->netreq) + continue; + + switch (chain_head_validate(head, tas)) { + + case GETDNS_DNSSEC_SECURE: + if (head->netreq->dnssec_status == + GETDNS_DNSSEC_INDETERMINATE) + head->netreq->dnssec_status = + GETDNS_DNSSEC_SECURE; + break; + + case GETDNS_DNSSEC_INSECURE: + if (head->netreq->dnssec_status != GETDNS_DNSSEC_BOGUS) + head->netreq->dnssec_status = + GETDNS_DNSSEC_INSECURE; + break; + + case GETDNS_DNSSEC_BOGUS : + head->netreq->dnssec_status = GETDNS_DNSSEC_BOGUS; + break; + + default: + break; + } + } +} + +static size_t count_outstanding_requests(chain_head *head) +{ + size_t count; + chain_node *node; + + if (!head) + return 0; + + for ( node = head->parent, count = 0 + ; node + ; node = node->parent) { + + if (node->dnskey_req && + node->dnskey_req->state != NET_REQ_FINISHED && + node->dnskey_req->state != NET_REQ_CANCELED) + count++; + + if (node->ds_req && + node->ds_req->state != NET_REQ_FINISHED && + node->ds_req->state != NET_REQ_CANCELED) + count++; + + if (node->soa_req && + node->soa_req->state != NET_REQ_FINISHED && + node->soa_req->state != NET_REQ_CANCELED) + count++; + } + return count + count_outstanding_requests(head->next); +} + +static void append_rrs2val_chain_list(getdns_context *ctxt, + getdns_list *val_chain_list, getdns_network_req *netreq) +{ + rrset_iter *i, i_spc; + getdns_rrset *rrset; + rrtype_iter *rr, rr_spc; + rrsig_iter *rrsig, rrsig_spc; + getdns_dict *rr_dict; + + for ( i = rrset_iter_init(&i_spc,netreq->response,netreq->response_len) + ; i + ; i = rrset_iter_next(i)) { + + rrset = rrset_iter_value(i); + + if (rrset->rr_type != GETDNS_RRTYPE_DNSKEY && + rrset->rr_type != GETDNS_RRTYPE_DS && + rrset->rr_type != GETDNS_RRTYPE_NSEC && + rrset->rr_type != GETDNS_RRTYPE_NSEC3) + continue; + + for ( rr = rrtype_iter_init(&rr_spc, rrset) + ; rr; rr = rrtype_iter_next(rr)) { + + if (!(rr_dict = priv_getdns_rr_iter2rr_dict( + &ctxt->mf, &rr->rr_i))) continue; - /* Search for a matching DS in the support records */ - key_rrset = ldns_dnssec_zone_find_rrset( - support, ldns_rr_owner(rr), LDNS_RR_TYPE_DS); - if (! key_rrset) { - s = LDNS_STATUS_CRYPTO_NO_DNSKEY; - break; - } - /* Now check if DS matches the DNSKEY! */ - for (rrs = key_rrset->rrs; rrs; rrs = rrs->next) - if (ldns_rr_compare_ds(rr, rrs->rr)) - break; - /* No DS found, try one of the other keys */ - if (! rrs) - continue; + (void)getdns_list_append_dict(val_chain_list, rr_dict); + getdns_dict_destroy(rr_dict); + } + for ( rrsig = rrsig_iter_init(&rrsig_spc, rrset) + ; rrsig; rrsig = rrsig_iter_next(rrsig)) { + + if (!(rr_dict = priv_getdns_rr_iter2rr_dict( + &ctxt->mf, &rrsig->rr_i))) + continue; + + (void)getdns_list_append_dict(val_chain_list, rr_dict); + getdns_dict_destroy(rr_dict); } - /* Pursue the chase with the verifying key (or its DS) - * and we're done. - */ - s = chase(key_rrset, support, support_keys, trusted); - break; } - if (i == ldns_rr_list_rr_count(verifying_keys)) - s = LDNS_STATUS_CRYPTO_NO_DNSKEY; -done_free_verifying_keys: - ldns_rr_list_free(verifying_keys); - return s; +} + +static void check_chain_complete(chain_head *chain) +{ + getdns_dns_req *dnsreq; + getdns_context *context; + size_t o, node_count; + chain_head *head, *next; + chain_node *node; + getdns_list *val_chain_list; + getdns_dict *response_dict; +#ifdef STUB_NATIVE_DNSSEC + rrset_iter tas_iter; +#endif + + if ((o = count_outstanding_requests(chain)) > 0) { + DEBUG_SEC("%zu outstanding requests\n", o); + return; + } + DEBUG_SEC("Chain done!\n"); + dnsreq = chain->netreq->owner; + context = dnsreq->context; + +#ifdef STUB_NATIVE_DNSSEC + /* Perform validation only on GETDNS_RESOLUTION_STUB (unbound_id == -1) + * TODO: When minimizing the validation chain (i.e. returning a single + * RRSIG per RRSET, it might be usefull to perform a fake dnssec + * validation to find out which RRSIGs should be returned. + */ + if (chain->netreq->unbound_id == -1 && context->trust_anchors) + chain_set_netreq_dnssec_status(chain,rrset_iter_init(&tas_iter, + context->trust_anchors, context->trust_anchors_len)); +#endif + + val_chain_list = dnsreq->dnssec_return_validation_chain + ? getdns_list_create_with_context(context) : NULL; + + /* Walk chain to add values to val_chain_list and to cleanup */ + for ( head = chain; head ; head = next ) { + next = head->next; + for ( node_count = head->node_count, node = head->parent + ; node_count + ; node_count--, node = node->parent ) { + + if (node->dnskey_req) { + append_rrs2val_chain_list(context, + val_chain_list, node->dnskey_req); + dns_req_free(node->dnskey_req->owner); + } + if (node->ds_req) { + append_rrs2val_chain_list(context, + val_chain_list, node->ds_req); + dns_req_free(node->ds_req->owner); + } + } + GETDNS_FREE(head->my_mf, head); + } + + response_dict = create_getdns_response(dnsreq); + if (val_chain_list) { + (void) getdns_dict_set_list( + response_dict, "validation_chain", val_chain_list); + getdns_list_destroy(val_chain_list); + } + + /* Final user callback */ + priv_getdns_call_user_callback(dnsreq, response_dict); +} + +static void val_chain_node_soa_cb(getdns_dns_req *dnsreq); +static void val_chain_sched_soa_node(chain_node *node) +{ + getdns_context *context; + getdns_eventloop *loop; + getdns_dns_req *dnsreq; + char name[1024]; + + context = node->chains->netreq->owner->context; + loop = node->chains->netreq->owner->loop; + + if (!gldns_wire2str_dname_buf(node->ds.name, 256, name, sizeof(name))) + return; + + if (! node->soa_req && + ! priv_getdns_general_loop(context, loop, name, GETDNS_RRTYPE_SOA, + dnssec_ok_checking_disabled, node, &dnsreq, NULL, + val_chain_node_soa_cb)) + + node->soa_req = dnsreq->netreqs[0]; +} + +static void val_chain_sched_soa(chain_head *head, uint8_t *dname) +{ + chain_node *node; + + if (!head->netreq) + return; + + if (!*dname) + return; + + for ( node = head->parent + ; node && !priv_getdns_dname_equal(dname, node->ds.name) + ; node = node->parent); + + if (node) + val_chain_sched_soa_node(node); +} + +static void val_chain_node_cb(getdns_dns_req *dnsreq); +static void val_chain_sched_node(chain_node *node) +{ + getdns_context *context; + getdns_eventloop *loop; + getdns_dns_req *dnsreq; + char name[1024]; + + context = node->chains->netreq->owner->context; + loop = node->chains->netreq->owner->loop; + + if (!gldns_wire2str_dname_buf(node->ds.name, 256, name, sizeof(name))) + return; + + DEBUG_SEC("schedule DS & DNSKEY lookup for %s\n", name); + + if (! node->dnskey_req /* not scheduled */ && + ! priv_getdns_general_loop(context, loop, name, GETDNS_RRTYPE_DNSKEY, + dnssec_ok_checking_disabled, node, &dnsreq, NULL, val_chain_node_cb)) + + node->dnskey_req = dnsreq->netreqs[0]; + + if (! node->ds_req && node->parent /* not root */ && + ! priv_getdns_general_loop(context, loop, name, GETDNS_RRTYPE_DS, + dnssec_ok_checking_disabled, node, &dnsreq, NULL, val_chain_node_cb)) + + node->ds_req = dnsreq->netreqs[0]; +} + +static void val_chain_sched(chain_head *head, uint8_t *dname) +{ + chain_node *node; + + if (!head->netreq) + return; + + for ( node = head->parent + ; node && !priv_getdns_dname_equal(dname, node->ds.name) + ; node = node->parent); + if (node) + val_chain_sched_node(node); +} + +static void val_chain_sched_signer_node(chain_node *node, rrsig_iter *rrsig) +{ + priv_getdns_rdf_iter rdf_spc, *rdf; + uint8_t signer_spc[256], *signer; + size_t signer_len; + + if (!(rdf = priv_getdns_rdf_iter_init_at(&rdf_spc, &rrsig->rr_i, 7))) + return; + + if (!(signer = priv_getdns_rdf_if_or_as_decompressed( + rdf, signer_spc, &signer_len))) + return; + + while (node && !priv_getdns_dname_equal(signer, node->ds.name)) + node = node->parent; + if (node) + val_chain_sched_node(node); +} + +static void val_chain_sched_signer(chain_head *head, rrsig_iter *rrsig) +{ + if (!head->netreq) + return; + + val_chain_sched_signer_node(head->parent, rrsig); +} + +static void val_chain_node_cb(getdns_dns_req *dnsreq) +{ + chain_node *node = (chain_node *)dnsreq->user_pointer; + getdns_network_req *netreq = dnsreq->netreqs[0]; + rrset_iter *i, i_spc; + getdns_rrset *rrset; + rrsig_iter *rrsig, rrsig_spc; + + getdns_context_clear_outbound_request(dnsreq); + switch (netreq->request_type) { + case GETDNS_RRTYPE_DS : node->ds.pkt = netreq->response; + node->ds.pkt_len = netreq->response_len; + break; + case GETDNS_RRTYPE_DNSKEY: node->dnskey.pkt = netreq->response; + node->dnskey.pkt_len = netreq->response_len; + default : check_chain_complete(node->chains); + return; + } + for ( i = rrset_iter_init(&i_spc,netreq->response,netreq->response_len) + ; i + ; i = rrset_iter_next(i)) { + + rrset = rrset_iter_value(i); + + if (rrset->rr_type != GETDNS_RRTYPE_DS && + rrset->rr_type != GETDNS_RRTYPE_NSEC && + rrset->rr_type != GETDNS_RRTYPE_NSEC3) + continue; + + for ( rrsig = rrsig_iter_init(&rrsig_spc, rrset) + ; rrsig; rrsig = rrsig_iter_next(rrsig)) + + val_chain_sched_signer_node(node, rrsig); + } + check_chain_complete(node->chains); +} + + +static getdns_rrset *rrset_by_type( + rrset_iter *i_spc, getdns_network_req *netreq, uint16_t rr_type) +{ + rrset_iter *i; + getdns_rrset *rrset; + + for ( i = rrset_iter_init(i_spc,netreq->response,netreq->response_len) + ; i + ; i = rrset_iter_next(i)) { + + rrset = rrset_iter_value(i); + if (rrset->rr_type == rr_type) /* Check class too? */ + return rrset; + } + return NULL; +} + +static void val_chain_node_soa_cb(getdns_dns_req *dnsreq) +{ + chain_node *node = (chain_node *)dnsreq->user_pointer; + getdns_network_req *netreq = dnsreq->netreqs[0]; + rrset_iter i_spc; + getdns_rrset *rrset; + + getdns_context_clear_outbound_request(dnsreq); + + if ((rrset = rrset_by_type(&i_spc, netreq, GETDNS_RRTYPE_SOA))) { + + while (node && + ! priv_getdns_dname_equal(node->ds.name, rrset->name)) + node = node->parent; + + if (node) + val_chain_sched_node(node); + } else + val_chain_sched_soa_node(node->parent); + + check_chain_complete(node->chains); +} + +static chain_head *add_rrset2val_chain(struct mem_funcs *mf, + chain_head **chain_p, getdns_rrset *rrset, getdns_network_req *netreq) +{ + chain_head *head; + uint8_t *labels[128], **last_label, **label; + + size_t max_labels; /* max labels in common */ + chain_head *max_head; + chain_node *max_node; + + size_t dname_len, head_sz, node_count, n; + uint8_t *dname, *region; + chain_node *node; + + last_label = reverse_labels(rrset->name, labels); + + /* Try to find a chain with the most overlapping labels. + * max_labels will be the number of labels in common from the root + * (so at least one; the root) + * max_head will be the head of the chain with max # labebs in common + */ + max_head = NULL; + max_labels = 0; + for (head = *chain_p; head; head = head->next) { + /* Also, try to prevent adding double rrsets */ + if ( rrset->rr_class == head->rrset.rr_class + && rrset->rr_type == head->rrset.rr_type + && rrset->pkt == head->rrset.pkt + && rrset->pkt_len == head->rrset.pkt_len + && priv_getdns_dname_equal(rrset->name, head->rrset.name)) + return NULL; + + for (label = labels; label < last_label; label++) { + if (! is_subdomain(*label, head->rrset.name)) + break; + } + if (label - labels > max_labels) { + max_labels = label - labels; + max_head = head; + } + } + /* Chain found. Now set max_node to the point in the chain where nodes + * will be common. + */ + if (max_head) { + for ( node = max_head->parent, n = 0 + ; node + ; node = node->parent, n++); + + for ( n -= max_labels, node = max_head->parent + ; n + ; n--, node = node->parent); + + max_node = node; + } else + max_node = NULL; + + /* node_count is the amount of nodes to still allocate. + * the last one's parent has to hook into the max_node. + */ + dname_len = *labels - last_label[-1] + 1; + head_sz = (sizeof(chain_head) + dname_len + 7) / 8 * 8; + node_count = last_label - labels - max_labels; + DEBUG_SEC( "%zu labels in common. %zu labels to allocate\n" + , max_labels, node_count); + + if (! (region = GETDNS_XMALLOC(*mf, uint8_t, head_sz + + node_count * sizeof(chain_node)))) + return NULL; + + /* Append the head on the linked list of heads */ + for (head = *chain_p; head && head->next; head = head->next); + if (head) + head = head->next = (chain_head *)region; + else + head = *chain_p = (chain_head *)region; + + head->my_mf = *mf; + head->next = NULL; + head->rrset.name = head->name_spc; + memcpy(head->name_spc, rrset->name, dname_len); + head->rrset.rr_class = rrset->rr_class; + head->rrset.rr_type = rrset->rr_type; + head->rrset.pkt = rrset->pkt; + head->rrset.pkt_len = rrset->pkt_len; + head->netreq = netreq; + head->node_count = node_count; + + if (!node_count) { + head->parent = max_head->parent; + return head; + } + + /* Initialize the nodes */ + node = (chain_node *)(region + head_sz); + head->parent = node; + + for ( node = (chain_node *)(region + head_sz), head->parent = node + , dname = head->rrset.name + ; node_count + ; node_count--, node = node->parent =&node[1], dname += *dname + 1) { + + node->ds.name = dname; + node->dnskey.name = dname; + node->ds.rr_class = head->rrset.rr_class; + node->dnskey.rr_class = head->rrset.rr_class; + node->ds.rr_type = GETDNS_RRTYPE_DS; + node->dnskey.rr_type = GETDNS_RRTYPE_DNSKEY; + node->ds.pkt = NULL; + node->ds.pkt_len = 0; + node->dnskey.pkt = NULL; + node->dnskey.pkt_len = 0; + node->ds_req = NULL; + node->dnskey_req = NULL; + node->soa_req = NULL; + + node->chains = *chain_p; + } + /* On the first chain, max_node == NULL. + * Schedule a root DNSKEY query, we always need that. + */ + if (!(node[-1].parent = max_node)) + val_chain_sched(head, (uint8_t *)"\0"); + + return head; +} + +/* Create the validation chain structure for the given packet. + * When netreq is set, queries will be scheduled for the DS + * and DNSKEY RR's for the nodes on the validation chain. + */ +static void add_pkt2val_chain(struct mem_funcs *mf, + chain_head **chain_p, uint8_t *pkt, size_t pkt_len, + getdns_network_req *netreq) +{ + rrset_iter *i, i_spc; + getdns_rrset *rrset; + rrsig_iter *rrsig, rrsig_spc; + size_t n_rrsigs; + chain_head *head; + + assert(pkt); + assert(pkt_len >= GLDNS_HEADER_SIZE); + + /* For all things with signatures, create a chain */ + + /* For all things without signature, find SOA (zonecut) and query DS */ + + for ( i = rrset_iter_init(&i_spc, pkt, pkt_len) + ; i + ; i = rrset_iter_next(i)) { + + rrset = rrset_iter_value(i); + debug_sec_print_rrset("rrset: ", rrset); + + if (!(head = add_rrset2val_chain(mf, chain_p, rrset, netreq))) + continue; + + for ( rrsig = rrsig_iter_init(&rrsig_spc, rrset), n_rrsigs = 0 + ; rrsig + ; rrsig = rrsig_iter_next(rrsig), n_rrsigs++) { + + val_chain_sched_signer(head, rrsig); + } + if (n_rrsigs) + continue; + + if (rrset->rr_type == GETDNS_RRTYPE_SOA) + val_chain_sched(head, rrset->name); + else if (rrset->rr_type == GETDNS_RRTYPE_CNAME) + val_chain_sched_soa(head, rrset->name + *rrset->name + 1); + else + val_chain_sched_soa(head, rrset->name); + } +} + +/* For NOERROR/NODATA or NXDOMAIN responses add extra rrset to + * the validation chain so the denial of existence will be + * checked eventually. + * But only if we know the question of course... + */ +static void add_question2val_chain(struct mem_funcs *mf, + chain_head **chain_p, uint8_t *pkt, size_t pkt_len, + uint8_t *qname, uint16_t qtype, uint16_t qclass, + getdns_network_req *netreq) +{ + getdns_rrset q_rrset; + uint8_t cname_spc[256]; + size_t cname_len = sizeof(cname_spc); + size_t anti_loop; + priv_getdns_rdf_iter rdf_spc, *rdf; + rrtype_iter *rr, rr_spc; + + chain_head *head; + + assert(pkt); + assert(pkt_len >= GLDNS_HEADER_SIZE); + assert(qname); + + /* First find the canonical name for the question */ + q_rrset.name = qname; + q_rrset.rr_type = GETDNS_RRTYPE_CNAME; + q_rrset.rr_class = qclass; + q_rrset.pkt = pkt; + q_rrset.pkt_len = pkt_len; + + for (anti_loop = 1000; anti_loop; anti_loop--) { + if (!(rr = rrtype_iter_init(&rr_spc, &q_rrset))) + break; + if (!(rdf = priv_getdns_rdf_iter_init(&rdf_spc, &rr->rr_i))) + break; + q_rrset.name = priv_getdns_rdf_if_or_as_decompressed( + rdf, cname_spc, &cname_len); + } + q_rrset.rr_type = qtype; + if (!(rr = rrtype_iter_init(&rr_spc, &q_rrset))) { + /* No answer for the question. Add a head for this rrset + * anyway, to validate proof of non-existance, or to find + * proof that the packet is insecure. + */ + debug_sec_print_rrset("Adding NX rrset: ", &q_rrset); + head = add_rrset2val_chain(mf, chain_p, &q_rrset, netreq); + + /* On empty packet, find SOA (zonecut) for the qname */ + if (head && GLDNS_ANCOUNT(pkt) == 0 && GLDNS_NSCOUNT(pkt) == 0) + + val_chain_sched_soa(head, q_rrset.name); + } +} + +void priv_getdns_get_validation_chain(getdns_dns_req *dnsreq) +{ + getdns_network_req *netreq, **netreq_p; + chain_head *chain = NULL; + + for (netreq_p = dnsreq->netreqs; (netreq = *netreq_p) ; netreq_p++) { + add_pkt2val_chain( &dnsreq->my_mf, &chain + , netreq->response, netreq->response_len + , netreq + ); + add_question2val_chain( &dnsreq->my_mf, &chain + , netreq->response, netreq->response_len + , netreq->owner->name + , netreq->request_type + , netreq->request_class + , netreq + ); + } + + if (chain) + check_chain_complete(chain); + else + priv_getdns_call_user_callback(dnsreq, + create_getdns_response(dnsreq)); } /* @@ -789,86 +1869,122 @@ getdns_validate_dnssec(getdns_list *records_to_validate, getdns_list *support_records, getdns_list *trust_anchors) { - getdns_return_t r; - ldns_rr_list *trusted; - ldns_dnssec_zone *support; - ldns_rr_list *support_keys; - ldns_dnssec_zone *to_validate; - zone_iter i; - ldns_dnssec_rrsets *rrset; - ldns_dnssec_rrs *rrs; - ldns_status s = LDNS_STATUS_ERR; + uint8_t to_val_buf[4096], *to_val, + support_buf[4096], *support, + tas_buf[4096], *tas; - if ((r = priv_getdns_rr_list_from_list(trust_anchors, &trusted))) - return r; + size_t to_val_len = sizeof(to_val_buf), + support_len = sizeof(support_buf), + tas_len = sizeof(tas_buf); - if ((r = priv_getdns_dnssec_zone_from_list( - support_records, &support))) - goto done_free_trusted; + getdns_return_t r = GETDNS_RETURN_MEMORY_ERROR; + struct mem_funcs *mf; - if ((r = priv_getdns_dnssec_zone_from_list( - records_to_validate, &to_validate))) - goto done_free_support; + chain_head *chain, *head; + chain_node *node; - if (! (support_keys = ldns_rr_list_new())) { - r = GETDNS_RETURN_MEMORY_ERROR; - goto done_free_to_validate; + uint8_t qname_spc[256], *qname = NULL; + size_t qname_len = sizeof(qname_spc); + uint16_t qtype = 0, qclass = GETDNS_RRCLASS_IN; + + priv_getdns_rr_iter rr_spc, *rr; + rrset_iter tas_iter; + +#if defined(SEC_DEBUG) && SEC_DEBUG + fflush(stdout); +#endif + + if (!records_to_validate || !support_records || !trust_anchors) + return GETDNS_RETURN_INVALID_PARAMETER; + mf = &records_to_validate->mf; + + /* First convert everything to wire format + */ + if (!(to_val = _getdns_list2wire(records_to_validate, + to_val_buf, &to_val_len, mf))) + return GETDNS_RETURN_MEMORY_ERROR; + + if (!(support = _getdns_list2wire(support_records, + support_buf, &support_len, mf))) + goto exit_free_to_val; + + if (!(tas = _getdns_list2wire(trust_anchors, + tas_buf, &tas_len, mf))) + goto exit_free_support; + + if (GLDNS_QDCOUNT(to_val) == 0 && GLDNS_ANCOUNT(to_val) == 0) { + r = GETDNS_RETURN_GENERIC_ERROR; + goto exit_free_tas; } - /* Create a rr_list of all the keys in the support records */ - for (rrset_iter_init_zone(&i, support); - (rrset = rrset_iter_value(&i)); rrset_iter_next(&i)) - if (ldns_dnssec_rrsets_type(rrset) == LDNS_RR_TYPE_DS || - ldns_dnssec_rrsets_type(rrset) == LDNS_RR_TYPE_DNSKEY) + chain = NULL; + /* First create a chain (head + nodes) for each rr in the answer and + * authority section of the fake to_val packet. + */ + add_pkt2val_chain(mf, &chain, to_val, to_val_len, NULL); - for (rrs = rrset->rrs; rrs; rrs = rrs->next) - (void) ldns_rr_list_push_rr( - support_keys, rrs->rr); + /* When records_to_validate contained replies, like the replies_tree + * list in a response dict, the returned wireformat packet may contain + * multiple questions in the question section. For each reply one + * question. + * + * For each question in the question section add a chain head. + */ + for ( rr = priv_getdns_rr_iter_init(&rr_spc, to_val, to_val_len) + ; rr && priv_getdns_rr_iter_section(rr) == GLDNS_SECTION_QUESTION + ; rr = priv_getdns_rr_iter_next(rr) ) { - /* Now walk through the rrsets to validate */ - for (rrset_iter_init_zone(&i, to_validate); - (rrset = rrset_iter_value(&i)); rrset_iter_next(&i)) { + if ((qname = priv_getdns_owner_if_or_as_decompressed( + rr, qname_spc, &qname_len)) + && rr->nxt >= rr->rr_type + 4) { - if ((s = chase(rrset, support, support_keys, trusted))) - break; + qtype = gldns_read_uint16(rr->rr_type); + qclass = gldns_read_uint16(rr->rr_type + 2); + + add_question2val_chain(mf, &chain, to_val, to_val_len, + qname, qtype, qclass, NULL); + } } - if (s == LDNS_STATUS_CRYPTO_BOGUS) - r = GETDNS_DNSSEC_BOGUS; - else if (s != LDNS_STATUS_OK) - r = GETDNS_DNSSEC_INSECURE; - else - r = GETDNS_DNSSEC_SECURE; - ldns_rr_list_free(support_keys); -done_free_to_validate: - ldns_dnssec_zone_deep_free(to_validate); -done_free_support: - ldns_dnssec_zone_deep_free(support); -done_free_trusted: - ldns_rr_list_deep_free(trusted); + /* Now equip the nodes with the support records wireformat */ + for (head = chain; head; head = head->next) { + for (node = head->parent; node; node = node->parent) { + + node->dnskey.pkt = support; + node->dnskey.pkt_len = support_len; + node->ds.pkt = support; + node->ds.pkt_len = support_len; + } + } + /* Now equip the nodes with the support records wireformat */ + + r = (getdns_return_t)chain_validate_dnssec( + chain, rrset_iter_init(&tas_iter, tas, tas_len)); + +exit_free_tas: + if (tas != tas_buf) + GETDNS_FREE(*mf, tas); +exit_free_support: + if (support != support_buf) + GETDNS_FREE(*mf, support); +exit_free_to_val: + if (to_val != to_val_buf) + GETDNS_FREE(*mf, to_val); + return r; -} /* getdns_validate_dnssec */ +} -int -priv_getdns_parse_ta_file(time_t *ta_mtime, getdns_list *ta_rrs) +uint16_t +_getdns_parse_ta_file(time_t *ta_mtime, gldns_buffer *gbuf) { struct gldns_file_parse_state pst; struct stat st; - struct { - uint16_t id; - uint16_t flags; - uint16_t qdcount; - uint16_t ancount; - uint16_t nscount; - uint16_t arcount; - uint8_t rr[8192]; /* Reasonable max size for a single RR */ - } pkt; + uint8_t rr[8192]; /* Reasonable size for a single DNSKEY or DS RR */ size_t len, dname_len; FILE *in; - priv_getdns_rr_iter rr_iter; - getdns_dict *rr_dict = NULL; - int ta_count = 0; + uint16_t ta_count = 0; + size_t pkt_start; if (stat(TRUST_ANCHOR_FILE, &st) != 0) return 0; @@ -879,38 +1995,34 @@ priv_getdns_parse_ta_file(time_t *ta_mtime, getdns_list *ta_rrs) if (!(in = fopen(TRUST_ANCHOR_FILE, "r"))) return 0; - pkt.id = pkt.flags = pkt.qdcount = pkt.nscount = pkt.arcount = 0; - pkt.ancount = htons(1); - memset(&pst, 0, sizeof(pst)); pst.default_ttl = 3600; pst.lineno = 1; + pkt_start = gldns_buffer_position(gbuf); + /* Empty header */ + gldns_buffer_write_u32(gbuf, 0); + gldns_buffer_write_u32(gbuf, 0); + gldns_buffer_write_u32(gbuf, 0); + while (!feof(in)) { - len = sizeof(pkt.rr); + len = sizeof(rr); dname_len = 0; - if (gldns_fp2wire_rr_buf(in, pkt.rr, &len, &dname_len, &pst)) + if (gldns_fp2wire_rr_buf(in, rr, &len, &dname_len, &pst)) break; if (len == 0) /* empty, $TTL, $ORIGIN */ continue; - if (gldns_wirerr_get_type(pkt.rr, len, dname_len) + if (gldns_wirerr_get_type(rr, len, dname_len) != LDNS_RR_TYPE_DS && - gldns_wirerr_get_type(pkt.rr, len, dname_len) + gldns_wirerr_get_type(rr, len, dname_len) != LDNS_RR_TYPE_DNSKEY) continue; - if (!priv_getdns_rr_iter_init(&rr_iter, (void *)&pkt, sizeof(pkt))) - break; - if (!(rr_dict = priv_getdns_rr_iter2rr_dict(NULL, &rr_iter))) - break; - if (ta_rrs && getdns_list_append_dict(ta_rrs, rr_dict)) - break; - getdns_dict_destroy(rr_dict); - rr_dict = NULL; + + gldns_buffer_write(gbuf, rr, len); ta_count++; } - if (rr_dict) - getdns_dict_destroy(rr_dict); fclose(in); + gldns_buffer_write_u16_at(gbuf, pkt_start+GLDNS_ANCOUNT_OFF, ta_count); return ta_count; } @@ -918,9 +2030,29 @@ priv_getdns_parse_ta_file(time_t *ta_mtime, getdns_list *ta_rrs) getdns_list * getdns_root_trust_anchor(time_t *utc_date_of_anchor) { - getdns_list *ta_rrs = getdns_list_create(); - (void) priv_getdns_parse_ta_file(utc_date_of_anchor, ta_rrs); + gldns_buffer *gbuf; + getdns_list *ta_rrs; + + if (!(ta_rrs = getdns_list_create())) + return NULL; + + if (!(gbuf = gldns_buffer_new(4096))) + goto error_free_ta_rrs; + + if (!_getdns_parse_ta_file(utc_date_of_anchor, gbuf)) + goto error_free_gbuf; + + _getdns_wire2list( gldns_buffer_export(gbuf) + , gldns_buffer_position(gbuf), ta_rrs); + + gldns_buffer_free(gbuf); return ta_rrs; + +error_free_gbuf: + gldns_buffer_free(gbuf); +error_free_ta_rrs: + getdns_list_destroy(ta_rrs); + return NULL; } /* dnssec.c */ diff --git a/src/dnssec.h b/src/dnssec.h index 30af7705..30ab6b3e 100644 --- a/src/dnssec.h +++ b/src/dnssec.h @@ -39,12 +39,14 @@ #define DNSSEC_H_ #include "getdns/getdns.h" +#include "config.h" +#include "gldns/gbuffer.h" #include "types-internal.h" /* Do some additional requests to fetch the complete validation chain */ void priv_getdns_get_validation_chain(getdns_dns_req *dns_req); -int priv_getdns_parse_ta_file(time_t *ta_mtime, getdns_list *ta_rrs); +uint16_t _getdns_parse_ta_file(time_t *ta_mtime, gldns_buffer *gbuf); #endif diff --git a/src/general.c b/src/general.c index 6dd0a779..8340e5f9 100644 --- a/src/general.c +++ b/src/general.c @@ -38,8 +38,8 @@ #include #include #include -#include #include "config.h" +#include "gldns/wire2str.h" #include "context.h" #include "types-internal.h" #include "util-internal.h" @@ -137,6 +137,7 @@ submit_network_request(getdns_network_req *netreq) { getdns_return_t r; getdns_dns_req *dns_req = netreq->owner; + char name[1024]; if (dns_req->context->resolution_type == GETDNS_RESOLUTION_RECURSING /* TODO: Until DNSSEC with the new async stub resolver is finished, @@ -160,9 +161,11 @@ submit_network_request(getdns_network_req *netreq) dns_req->context->timeout, &dns_req->timeout))) return r; } + (void) gldns_wire2str_dname_buf(dns_req->name, + dns_req->name_len, name, sizeof(name)); return ub_resolve_async(dns_req->context->unbound_ctx, - dns_req->name, netreq->request_type, netreq->request_class, + name, netreq->request_type, netreq->request_class, netreq, ub_resolve_callback, &(netreq->unbound_id)) ? GETDNS_RETURN_GENERIC_ERROR : GETDNS_RETURN_GOOD; } @@ -173,7 +176,7 @@ submit_network_request(getdns_network_req *netreq) static getdns_return_t getdns_general_ns(getdns_context *context, getdns_eventloop *loop, const char *name, uint16_t request_type, getdns_dict *extensions, - void *userarg, getdns_transaction_t *transaction_id, + void *userarg, getdns_dns_req **dnsreq_p, getdns_callback_t callbackfn, internal_cb_t internal_cb, int usenamespaces) { getdns_return_t r = GETDNS_RETURN_GOOD; @@ -185,10 +188,10 @@ getdns_general_ns(getdns_context *context, getdns_eventloop *loop, if (!context || !name || (!callbackfn && !internal_cb)) return GETDNS_RETURN_INVALID_PARAMETER; - if ((r = validate_dname(name))) + if ((r = priv_getdns_validate_dname(name))) return r; - if (extensions && (r = validate_extensions(extensions))) + if (extensions && (r = priv_getdns_validate_extensions(extensions))) return r; /* Set up the context assuming we won't use the specified namespaces. @@ -204,8 +207,8 @@ getdns_general_ns(getdns_context *context, getdns_eventloop *loop, req->user_callback = callbackfn; req->internal_cb = internal_cb; - if (transaction_id) - *transaction_id = req->trans_id; + if (dnsreq_p) + *dnsreq_p = req; getdns_context_track_outbound_request(req); @@ -254,12 +257,12 @@ getdns_general_ns(getdns_context *context, getdns_eventloop *loop, getdns_return_t priv_getdns_general_loop(getdns_context *context, getdns_eventloop *loop, const char *name, uint16_t request_type, getdns_dict *extensions, - void *userarg, getdns_transaction_t *transaction_id, + void *userarg, getdns_dns_req **dnsreq_p, getdns_callback_t callback, internal_cb_t internal_cb) { return getdns_general_ns(context, loop, name, request_type, extensions, - userarg, transaction_id, callback, internal_cb, 0); + userarg, dnsreq_p, callback, internal_cb, 0); } /* getdns_general_loop */ @@ -271,6 +274,7 @@ priv_getdns_address_loop(getdns_context *context, getdns_eventloop *loop, getdns_dict *my_extensions = extensions; getdns_return_t r; uint32_t value; + getdns_dns_req *dnsreq = NULL; if (!my_extensions) { if (!(my_extensions=getdns_dict_create_with_context(context))) @@ -286,7 +290,9 @@ priv_getdns_address_loop(getdns_context *context, getdns_eventloop *loop, r = getdns_general_ns(context, loop, name, GETDNS_RRTYPE_AAAA, my_extensions, - userarg, transaction_id, callback, NULL, 1); + userarg, &dnsreq, callback, NULL, 1); + if (dnsreq && transaction_id) + *transaction_id = dnsreq->trans_id; if (my_extensions != extensions) getdns_dict_destroy(my_extensions); @@ -304,6 +310,7 @@ priv_getdns_hostname_loop(getdns_context *context, getdns_eventloop *loop, uint16_t req_type; char name[1024]; getdns_return_t retval; + getdns_dns_req *dnsreq = NULL; if ((retval = getdns_dict_get_bindata(address, "address_data", @@ -377,7 +384,9 @@ priv_getdns_hostname_loop(getdns_context *context, getdns_eventloop *loop, return GETDNS_RETURN_INVALID_PARAMETER; } retval = priv_getdns_general_loop(context, loop, name, req_type, - extensions, userarg, transaction_id, callback, NULL); + extensions, userarg, &dnsreq, callback, NULL); + if (dnsreq && transaction_id) + *transaction_id = dnsreq->trans_id; return retval; } /* getdns_hostname_loop */ @@ -386,8 +395,13 @@ priv_getdns_service_loop(getdns_context *context, getdns_eventloop *loop, const char *name, getdns_dict *extensions, void *userarg, getdns_transaction_t * transaction_id, getdns_callback_t callback) { - return getdns_general_ns(context, loop, name, GETDNS_RRTYPE_SRV, - extensions, userarg, transaction_id, callback, NULL, 1); + getdns_return_t r; + getdns_dns_req *dnsreq = NULL; + r = getdns_general_ns(context, loop, name, GETDNS_RRTYPE_SRV, + extensions, userarg, &dnsreq, callback, NULL, 1); + if (dnsreq && transaction_id) + *transaction_id = dnsreq->trans_id; + return r; } /* getdns_service_loop */ /** @@ -399,11 +413,16 @@ getdns_general(getdns_context *context, void *userarg, getdns_transaction_t * transaction_id, getdns_callback_t callback) { - if (!context) return GETDNS_RETURN_INVALID_PARAMETER; - return priv_getdns_general_loop(context, context->extension, - name, request_type, extensions, - userarg, transaction_id, callback, NULL); + getdns_return_t r; + getdns_dns_req *dnsreq = NULL; + if (!context) return GETDNS_RETURN_INVALID_PARAMETER; + r = priv_getdns_general_loop(context, context->extension, + name, request_type, extensions, + userarg, &dnsreq, callback, NULL); + if (dnsreq && transaction_id) + *transaction_id = dnsreq->trans_id; + return r; } /* getdns_general */ /* diff --git a/src/general.h b/src/general.h index 055f2f7e..94107e38 100644 --- a/src/general.h +++ b/src/general.h @@ -42,13 +42,13 @@ /* private inner helper used by sync and async */ -void priv_getdns_call_user_callback(getdns_dns_req *, struct getdns_dict *); +void priv_getdns_call_user_callback(getdns_dns_req *, getdns_dict *); void priv_getdns_check_dns_req_complete(getdns_dns_req *dns_req); getdns_return_t priv_getdns_general_loop(getdns_context *context, getdns_eventloop *loop, const char *name, uint16_t request_type, getdns_dict *extensions, - void *userarg, getdns_transaction_t *transaction_id, + void *userarg, getdns_dns_req **dnsreq, getdns_callback_t callbackfn, internal_cb_t internal_cb); getdns_return_t diff --git a/src/list.h b/src/list.h index 0fc320a4..113361ae 100644 --- a/src/list.h +++ b/src/list.h @@ -74,6 +74,10 @@ struct getdns_list struct mem_funcs mf; }; +inline struct getdns_list *_getdns_list_create_with_mf(struct mem_funcs *mf) +{ return getdns_list_create_with_extended_memory_functions( + mf->mf_arg, mf->mf.ext.malloc, mf->mf.ext.realloc, mf->mf.ext.free); } + #endif /* list.h */ diff --git a/src/request-internal.c b/src/request-internal.c index 33ef055c..68ed0c6a 100644 --- a/src/request-internal.c +++ b/src/request-internal.c @@ -40,6 +40,13 @@ #include "gldns/str2wire.h" #include "gldns/gbuffer.h" #include "gldns/pkthdr.h" +#include "dict.h" + +getdns_dict dnssec_ok_checking_disabled_spc = { + { RBTREE_NULL, 0, (int (*)(const void *, const void *)) strcmp }, + { 0 } +}; +getdns_dict *dnssec_ok_checking_disabled = &dnssec_ok_checking_disabled_spc; static int is_extension_set(getdns_dict *extensions, const char *extension) @@ -49,6 +56,8 @@ is_extension_set(getdns_dict *extensions, const char *extension) if (! extensions) return 0; + else if (extensions == dnssec_ok_checking_disabled) + return 0; r = getdns_dict_get_int(extensions, extension, &value); return r == GETDNS_RETURN_GOOD && value == GETDNS_EXTENSION_TRUE; @@ -87,6 +96,8 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner, net_req->state = NET_REQ_NOT_SENT; net_req->owner = owner; + net_req->dnssec_status = GETDNS_DNSSEC_INDETERMINATE; + net_req->upstream = NULL; net_req->fd = -1; net_req->transport_count = owner->context->dns_transport_count; @@ -193,8 +204,6 @@ dns_req_free(getdns_dns_req * req) req->timeout.timeout_cb = NULL; } - /* free strduped name */ - GETDNS_FREE(req->my_mf, req->name); GETDNS_FREE(req->my_mf, req); } @@ -210,14 +219,12 @@ dns_req_new(getdns_context *context, getdns_eventloop *loop, = is_extension_set(extensions, "dnssec_return_only_secure"); int dnssec_return_validation_chain = is_extension_set(extensions, "dnssec_return_validation_chain"); - int dnssec_ok_checking_disabled - = is_extension_set(extensions, "dnssec_ok_checking_disabled"); int edns_cookies = is_extension_set(extensions, "edns_cookies"); int dnssec_extension_set = dnssec_return_status || dnssec_return_only_secure || dnssec_return_validation_chain - || dnssec_ok_checking_disabled;; + || (extensions == dnssec_ok_checking_disabled); uint32_t edns_do_bit; int edns_maximum_udp_payload_size; @@ -252,6 +259,9 @@ dns_req_new(getdns_context *context, getdns_eventloop *loop, size_t max_query_sz, max_response_sz, netreq_sz, dnsreq_base_sz; uint8_t *region; + if (extensions == dnssec_ok_checking_disabled) + extensions = NULL; + have_add_opt_parameters = getdns_dict_get_dict(extensions, "add_opt_parameters", &add_opt_parameters) == GETDNS_RETURN_GOOD; @@ -347,7 +357,12 @@ dns_req_new(getdns_context *context, getdns_eventloop *loop, result->netreqs[1] = NULL; result->my_mf = context->mf; - result->name = getdns_strdup(&(result->my_mf), name); + + result->name_len = sizeof(result->name); + if (gldns_str2wire_dname_buf(name, result->name, &result->name_len)) { + GETDNS_FREE(result->my_mf, result); + return NULL; + } result->context = context; result->loop = loop; result->canceled = 0; diff --git a/src/rr-dict.c b/src/rr-dict.c index d6dcea8c..8a9f2f94 100644 --- a/src/rr-dict.c +++ b/src/rr-dict.c @@ -255,9 +255,9 @@ static priv_getdns_rdata_def soa_rdata[] = { { "rname" , GETDNS_RDF_N_C }, { "serial" , GETDNS_RDF_I4 }, { "refresh" , GETDNS_RDF_I4 }, - { "refresh" , GETDNS_RDF_I4 }, { "retry" , GETDNS_RDF_I4 }, - { "expire" , GETDNS_RDF_I4 }}; + { "expire" , GETDNS_RDF_I4 }, + { "minimum" , GETDNS_RDF_I4 }}; static priv_getdns_rdata_def mg_rdata[] = { { "mgmname" , GETDNS_RDF_N_C }}; static priv_getdns_rdata_def mr_rdata[] = { @@ -737,192 +737,95 @@ priv_getdns_rr_type_name(int rr_type) return priv_getdns_rr_def_lookup(rr_type)->name; } -static getdns_return_t priv_getdns_construct_wire_rdata_from_rdata( - struct getdns_dict *rdata, uint32_t rr_type, - uint8_t **wire, size_t *wire_size) -{ - getdns_return_t r = GETDNS_RETURN_GOOD; - const ldns_rr_descriptor *rr_descript; - const priv_getdns_rr_def *def; - size_t i, size; - struct getdns_bindata *bindata; - uint32_t value; - uint8_t *ptr; - - assert(rdata); - assert(wire); - assert(wire_size); - - def = priv_getdns_rr_def_lookup(rr_type); - rr_descript = ldns_rr_descript(rr_type); - - /* First calculate needed size */ - size = 0; - for (i = 0; !r && i < def->n_rdata_fields; i++) { - if (def->rdata[i].type & GETDNS_RDF_BINDATA) - if ((r = getdns_dict_get_bindata(rdata, - def->rdata[i].name, &bindata))) - break; - else { - size += bindata->size; - continue; - } - else if (!(def->rdata[i].type & GETDNS_RDF_INTEGER)) { - r = GETDNS_RETURN_GENERIC_ERROR; - break; - } - switch (ldns_rr_descriptor_field_type(rr_descript, i)) { - - case LDNS_RDF_TYPE_CLASS: - case LDNS_RDF_TYPE_ALG : - case LDNS_RDF_TYPE_INT8 : size += 1; - break; - case LDNS_RDF_TYPE_TYPE : - case LDNS_RDF_TYPE_CERT_ALG: - case LDNS_RDF_TYPE_INT16: size += 2; - break; - case LDNS_RDF_TYPE_TIME : - case LDNS_RDF_TYPE_PERIOD: - case LDNS_RDF_TYPE_INT32: size += 4; - break; - default: r = GETDNS_RETURN_GENERIC_ERROR; - break; - } - } - *wire_size = size + 2; - *wire = ptr = GETDNS_XMALLOC(rdata->mf, uint8_t, size + 2); - if (! ptr) - return GETDNS_RETURN_MEMORY_ERROR; - - ptr[0] = (uint8_t) (size >> 8) & 0xff; - ptr[1] = (uint8_t) size & 0xff; - ptr += 2; - for (i = 0; !r && i < def->n_rdata_fields; i++) { - if (def->rdata[i].type & GETDNS_RDF_BINDATA) - if ((r = getdns_dict_get_bindata(rdata, - def->rdata[i].name, &bindata))) - break; - else { - (void) memcpy(ptr, bindata->data, - bindata->size); - ptr += bindata->size; - continue; - } - else if (!(def->rdata[i].type & GETDNS_RDF_INTEGER)) { - r = GETDNS_RETURN_GENERIC_ERROR; - break; - } - if ((r = getdns_dict_get_int( - rdata, def->rdata[i].name, &value))) - break; - - switch (ldns_rr_descriptor_field_type(rr_descript, i)) { - - case LDNS_RDF_TYPE_CLASS: - case LDNS_RDF_TYPE_ALG : - case LDNS_RDF_TYPE_INT8 : ptr[0] = (uint8_t) value & 0xff; - ptr += 1; - break; - case LDNS_RDF_TYPE_TYPE : - case LDNS_RDF_TYPE_CERT_ALG: - case LDNS_RDF_TYPE_INT16: ptr[0] = (uint8_t)(value>> 8) & 0xff; - ptr[1] = (uint8_t) value & 0xff; - ptr += 2; - break; - case LDNS_RDF_TYPE_TIME : - case LDNS_RDF_TYPE_PERIOD: - case LDNS_RDF_TYPE_INT32: ptr[0] = (uint8_t)(value>>24) & 0xff; - ptr[1] = (uint8_t)(value>>16) & 0xff; - ptr[2] = (uint8_t)(value>>8 ) & 0xff; - ptr[3] = (uint8_t) value & 0xff; - ptr += 4; - break; - default: r = GETDNS_RETURN_GENERIC_ERROR; - break; - } - } - if (r) - GETDNS_FREE(rdata->mf, ptr); - return r; -} - -static getdns_return_t -priv_getdns_dict_get_raw_rdata(struct getdns_dict *rdata, - uint8_t **wire, size_t *wire_size) -{ - getdns_return_t r; - struct getdns_bindata *bindata; - - if ((r = getdns_dict_get_bindata(rdata, "rdata_raw", &bindata))) - return r; - - *wire_size = bindata->size + 2; - *wire = GETDNS_XMALLOC(rdata->mf, uint8_t, *wire_size); - if (! *wire) - return GETDNS_RETURN_MEMORY_ERROR; - - (*wire)[0] = (uint8_t) (bindata->size >> 8) & 0xff; - (*wire)[1] = (uint8_t) bindata->size & 0xff; - - (void) memcpy(*wire + 2, bindata->data, bindata->size); - return GETDNS_RETURN_GOOD; -} - getdns_return_t -priv_getdns_create_rr_from_dict(struct getdns_dict *rr_dict, ldns_rr **rr) +priv_getdns_rr_dict2wire(getdns_dict *rr_dict, gldns_buffer *buf) { getdns_return_t r = GETDNS_RETURN_GOOD; struct getdns_bindata *name; + struct getdns_bindata *rdata_raw; + struct getdns_bindata *bindata; struct getdns_dict *rdata; uint32_t rr_type; - ldns_rdf *owner; - ldns_status s; - size_t pos; - uint8_t *wire; - size_t wire_size; + uint32_t rr_class = GETDNS_RRCLASS_IN; + uint32_t rr_ttl = 0; + uint32_t value; + const priv_getdns_rr_def *rr_def; + const priv_getdns_rdata_def *rd_def; + int n_rdata_fields; + size_t j, rdata_size_mark; assert(rr_dict); - assert(rr); + assert(buf); - *rr = ldns_rr_new(); - if (! *rr) - return GETDNS_RETURN_MEMORY_ERROR; - do { - r = getdns_dict_get_bindata(rr_dict, "name", &name); - if (r != GETDNS_RETURN_GOOD) - break; - owner = ldns_rdf_new_frm_data( - LDNS_RDF_TYPE_DNAME, name->size, name->data); - if (! owner) { - r = GETDNS_RETURN_MEMORY_ERROR; + if ((r = getdns_dict_get_bindata(rr_dict, "name", &name))) + goto error; + gldns_buffer_write(buf, name->data, name->size); + + if ((r = getdns_dict_get_int(rr_dict, "type", &rr_type))) + goto error; + gldns_buffer_write_u16(buf, (uint16_t)rr_type); + + (void) getdns_dict_get_int(rr_dict, "class", &rr_class); + gldns_buffer_write_u16(buf, (uint16_t)rr_class); + + (void) getdns_dict_get_int(rr_dict, "ttl", &rr_ttl); + gldns_buffer_write_u32(buf, rr_ttl); + + /* Does rdata contain compressed names? + * Because rdata_raw is unusable then. + */ + rr_def = priv_getdns_rr_def_lookup(rr_type); + for ( rd_def = rr_def->rdata + , n_rdata_fields = rr_def->n_rdata_fields + ; n_rdata_fields ; n_rdata_fields-- , rd_def++ ) { + + if (rd_def->type & GETDNS_RDF_COMPRESSED) break; + } + + if ((r = getdns_dict_get_dict(rr_dict, "rdata", &rdata))) + goto error; + + if (n_rdata_fields == 0 && GETDNS_RETURN_GOOD == + (r = getdns_dict_get_bindata(rdata, "rdata_raw", &rdata_raw))) { + + gldns_buffer_write_u16(buf, (uint16_t)rdata_raw->size); + gldns_buffer_write(buf, rdata_raw->data, rdata_raw->size); + + } else if (n_rdata_fields || r == GETDNS_RETURN_NO_SUCH_DICT_NAME) { + + rdata_size_mark = gldns_buffer_position(buf); + gldns_buffer_skip(buf, 2); + + for ( rd_def = rr_def->rdata + , n_rdata_fields = rr_def->n_rdata_fields + ; n_rdata_fields ; n_rdata_fields-- , rd_def++ ) { + + if (rd_def->type & GETDNS_RDF_BINDATA) { + if ((r = getdns_dict_get_bindata(rdata, + rd_def->name, &bindata))) + break; + + gldns_buffer_write(buf, bindata->data + , bindata->size ); + continue; + } + if (!(rd_def->type & GETDNS_RDF_INTEGER)) { + r = GETDNS_RETURN_GENERIC_ERROR; + break; + } + if ((r = getdns_dict_get_int( + rdata, rd_def->name, &value))) + break; + + for (j = rd_def->type & GETDNS_RDF_FIXEDSZ; j; j--) + gldns_buffer_write_u8(buf, + (uint8_t)(value >> (8 * (j - 1))) & 0xff); } - ldns_rr_set_owner(*rr, owner); - - r = getdns_dict_get_int(rr_dict, "type", &rr_type); - if (r != GETDNS_RETURN_GOOD) - break; - ldns_rr_set_type(*rr, rr_type); - - r = getdns_dict_get_dict(rr_dict, "rdata", &rdata); - if (r != GETDNS_RETURN_GOOD) - break; - - r = priv_getdns_dict_get_raw_rdata(rdata, &wire, &wire_size); - if (r == GETDNS_RETURN_NO_SUCH_DICT_NAME) { - r = priv_getdns_construct_wire_rdata_from_rdata( - rdata, rr_type, &wire, &wire_size); - } - if (r != GETDNS_RETURN_GOOD) - break; - pos = 0; - s = ldns_wire2rdf(*rr, wire, wire_size, &pos); - GETDNS_FREE(rr_dict->mf, wire); - if (s == LDNS_STATUS_OK) - return r; - r = GETDNS_RETURN_GENERIC_ERROR; - } while (0); - ldns_rr_free(*rr); + gldns_buffer_write_u16_at(buf, rdata_size_mark, + (uint16_t)(gldns_buffer_position(buf)-rdata_size_mark-2)); + } +error: return r; } diff --git a/src/rr-dict.h b/src/rr-dict.h index 244f556b..9228105e 100644 --- a/src/rr-dict.h +++ b/src/rr-dict.h @@ -32,8 +32,9 @@ #ifndef RR_DICT_H_ #define RR_DICT_H_ -#include +#include "config.h" #include "getdns/getdns.h" +#include "gldns/gbuffer.h" typedef uint8_t *(*priv_getdns_rdf_end_t)( uint8_t *pkt, uint8_t *pkt_end, uint8_t *rdf); @@ -51,56 +52,57 @@ typedef struct priv_getdns_rdf_special { /* draft-levine-dnsextlang'ish type rr and rdata definitions */ -#define GETDNS_RDF_INTEGER 0x010000 -#define GETDNS_RDF_BINDATA 0x020000 -#define GETDNS_RDF_DNAME 0x060000 -#define GETDNS_RDF_REPEAT 0x100000 +#define GETDNS_RDF_INTEGER 0x010000 +#define GETDNS_RDF_BINDATA 0x020000 +#define GETDNS_RDF_DNAME 0x060000 +#define GETDNS_RDF_COMPRESSED 0x080000 +#define GETDNS_RDF_REPEAT 0x100000 -#define GETDNS_RDF_FIXEDSZ 0x0000FF -#define GETDNS_RDF_LEN_VAL 0x00FF00 +#define GETDNS_RDF_FIXEDSZ 0x0000FF +#define GETDNS_RDF_LEN_VAL 0x00FF00 typedef enum priv_getdns_rdf_wf_type { - GETDNS_RDF_N = 0x060000, /* N */ - GETDNS_RDF_N_A = GETDNS_RDF_N, /* N[A] */ - GETDNS_RDF_N_A_C = GETDNS_RDF_N, /* N[A,C] */ - GETDNS_RDF_N_C = GETDNS_RDF_N, /* N[C] */ - GETDNS_RDF_N_M = 0x160000, /* N[M] */ + GETDNS_RDF_N = 0x060000, /* N */ + GETDNS_RDF_N_A = 0x060000, /* N[A] */ + GETDNS_RDF_N_C = 0x0E0000, /* N[C] */ + GETDNS_RDF_N_A_C = 0x0E0000, /* N[A,C] */ + GETDNS_RDF_N_M = 0x160000, /* N[M] */ - GETDNS_RDF_I1 = 0x010001, /* I1 */ - GETDNS_RDF_I2 = 0x010002, /* I2 */ - GETDNS_RDF_I4 = 0x010004, /* I4 */ + GETDNS_RDF_I1 = 0x010001, /* I1 */ + GETDNS_RDF_I2 = 0x010002, /* I2 */ + GETDNS_RDF_I4 = 0x010004, /* I4 */ - GETDNS_RDF_T = 0x010004, /* T */ + GETDNS_RDF_T = 0x010004, /* T */ /* Time values using ring arithmetics * (rfc1982) for TKEY['inception'], * TKEY['expiration'], * RRSIG['inception'] and * RRSIG['expiration'] */ - GETDNS_RDF_T6 = 0x020006, /* T6 */ + GETDNS_RDF_T6 = 0x020006, /* T6 */ /* Absolute time values (since epoch) * for TSIG['time_signed'] */ - GETDNS_RDF_A = 0x020004, /* A */ - GETDNS_RDF_AA = 0x020008, /* AA */ - GETDNS_RDF_AAAA = 0x020010, /* AAAA */ + GETDNS_RDF_A = 0x020004, /* A */ + GETDNS_RDF_AA = 0x020008, /* AA */ + GETDNS_RDF_AAAA = 0x020010, /* AAAA */ - GETDNS_RDF_S = 0x020100, /* S */ - GETDNS_RDF_S_L = 0x020000, /* S[L] */ - GETDNS_RDF_S_M = 0x120100, /* S[M] */ + GETDNS_RDF_S = 0x020100, /* S */ + GETDNS_RDF_S_L = 0x020000, /* S[L] */ + GETDNS_RDF_S_M = 0x120100, /* S[M] */ - GETDNS_RDF_B = 0x020000, /* B */ - GETDNS_RDF_B_C = 0x020100, /* B[C] */ + GETDNS_RDF_B = 0x020000, /* B */ + GETDNS_RDF_B_C = 0x020100, /* B[C] */ - GETDNS_RDF_B32_C = 0x020100, /* B32[C] */ + GETDNS_RDF_B32_C = 0x020100, /* B32[C] */ - GETDNS_RDF_X = 0x020000, /* X */ - GETDNS_RDF_X_C = 0x020100, /* X[C] */ + GETDNS_RDF_X = 0x020000, /* X */ + GETDNS_RDF_X_C = 0x020100, /* X[C] */ /* for NSEC3['salt'] and * NSEC3PARAM['salt']. */ - GETDNS_RDF_X_S = 0x020200, /* X[S] */ + GETDNS_RDF_X_S = 0x020200, /* X[S] */ /* for OPT['option_data'], * TKEY['key_data'], * TKEY['other_data'], @@ -109,12 +111,12 @@ typedef enum priv_getdns_rdf_wf_type { * Although those do not have an * official presentation format. */ - GETDNS_RDF_X6 = 0x020006, - GETDNS_RDF_X8 = 0x020008, + GETDNS_RDF_X6 = 0x020006, + GETDNS_RDF_X8 = 0x020008, - GETDNS_RDF_R = 0x100000, /* Repeat */ + GETDNS_RDF_R = 0x100000, /* Repeat */ - GETDNS_RDF_SPECIAL = 0x800000, + GETDNS_RDF_SPECIAL = 0x800000, } priv_getdns_rdf_type; typedef struct priv_getdns_rdata_def { @@ -131,8 +133,8 @@ typedef struct priv_getdns_rr_def { const priv_getdns_rr_def *priv_getdns_rr_def_lookup(uint16_t rr_type); -getdns_return_t priv_getdns_create_rr_from_dict( - struct getdns_dict *rr_dict, ldns_rr **rr); +getdns_return_t priv_getdns_rr_dict2wire( + getdns_dict *rr_dict, gldns_buffer *buf); const char *priv_getdns_rr_type_name(int rr_type); diff --git a/src/rr-iter.c b/src/rr-iter.c index f6188307..6b68ac1c 100644 --- a/src/rr-iter.c +++ b/src/rr-iter.c @@ -98,6 +98,13 @@ priv_getdns_rr_iter_init(priv_getdns_rr_iter *i, uint8_t *pkt, size_t pkt_len) return find_rrtype(i); } +priv_getdns_rr_iter * +priv_getdns_rr_iter_rewind(priv_getdns_rr_iter *i) +{ + assert(i); + + return priv_getdns_rr_iter_init(i, i->pkt, i->pkt_end - i->pkt); +} priv_getdns_rr_iter * priv_getdns_rr_iter_next(priv_getdns_rr_iter *i) diff --git a/src/rr-iter.h b/src/rr-iter.h index cfc9faf1..858ee1d9 100644 --- a/src/rr-iter.h +++ b/src/rr-iter.h @@ -62,6 +62,8 @@ typedef struct priv_getdns_rr_iter { priv_getdns_rr_iter *priv_getdns_rr_iter_init(priv_getdns_rr_iter *i, uint8_t *pkt, size_t pkt_len); +priv_getdns_rr_iter *priv_getdns_rr_iter_rewind(priv_getdns_rr_iter *i); + priv_getdns_rr_iter *priv_getdns_rr_iter_next(priv_getdns_rr_iter *i); uint8_t *priv_getdns_owner_if_or_as_decompressed( diff --git a/src/stub.c b/src/stub.c index 74f4f0bd..7738f774 100644 --- a/src/stub.c +++ b/src/stub.c @@ -31,6 +31,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include "config.h" #include #include "stub.h" @@ -40,7 +41,6 @@ #include "gldns/str2wire.h" #include "rr-iter.h" #include "context.h" -#include #include "util-internal.h" #include "general.h" @@ -273,21 +273,6 @@ create_starttls_request(getdns_dns_req *dnsreq, getdns_upstream *upstream, return 1; } -static int -dname_equal(uint8_t *s1, uint8_t *s2) -{ - uint8_t i; - for (;;) { - if (*s1 != *s2) - return 0; - else if (!*s1) - return 1; - for (i = *s1++, s2++; i > 0; i--, s1++, s2++) - if ((*s1 & 0xDF) != (*s2 & 0xDF)) - return 0; - } -} - static int is_starttls_response(getdns_network_req *netreq) { @@ -295,10 +280,10 @@ is_starttls_response(getdns_network_req *netreq) priv_getdns_rdf_iter rdf_iter_storage, *rdf_iter; uint16_t rr_type; gldns_pkt_section section; - uint8_t starttls_name_space[256], - *starttls_name = starttls_name_space; + uint8_t starttls_name_space[256], *starttls_name; uint8_t owner_name_space[256], *owner_name; - size_t starttls_name_len = 256, owner_name_len; + size_t starttls_name_len = sizeof(starttls_name_space); + size_t owner_name_len = sizeof(owner_name_space);; /* Servers that are not STARTTLS aware will refuse the CH query*/ if (GLDNS_RCODE_NOERROR != GLDNS_RCODE_WIRE(netreq->response)) @@ -307,9 +292,6 @@ is_starttls_response(getdns_network_req *netreq) if (GLDNS_ANCOUNT(netreq->response) != 1) return 0; - (void) gldns_str2wire_dname_buf( - netreq->owner->name, starttls_name_space, &starttls_name_len); - for ( rr_iter = priv_getdns_rr_iter_init(&rr_iter_storage , netreq->response , netreq->response_len) @@ -318,25 +300,25 @@ is_starttls_response(getdns_network_req *netreq) section = priv_getdns_rr_iter_section(rr_iter); rr_type = gldns_read_uint16(rr_iter->rr_type); - if (section != GLDNS_SECTION_ANSWER || rr_type != GETDNS_RRTYPE_TXT) + if (section != GLDNS_SECTION_ANSWER + || rr_type != GETDNS_RRTYPE_TXT) continue; owner_name = priv_getdns_owner_if_or_as_decompressed( rr_iter, owner_name_space, &owner_name_len); - if (!dname_equal(starttls_name, owner_name)) + if (!priv_getdns_dname_equal(netreq->owner->name, owner_name)) continue; if (!(rdf_iter = priv_getdns_rdf_iter_init( &rdf_iter_storage, rr_iter))) continue; - /* re-use the starttls_name for the response dname*/ - starttls_name = priv_getdns_rdf_if_or_as_decompressed( - rdf_iter,starttls_name_space,&starttls_name_len); - if (dname_equal(starttls_name, owner_name)) + + if ((starttls_name = priv_getdns_rdf_if_or_as_decompressed( + rdf_iter, starttls_name_space, &starttls_name_len)) && + priv_getdns_dname_equal(starttls_name, owner_name)) return 1; - else - return 0; - continue; + + return 0; } return 0; } @@ -1144,10 +1126,6 @@ stub_udp_read_cb(void *userarg) } netreq->response_len = read; dnsreq->upstreams->current = 0; - - /* TODO: DNSSEC */ - netreq->secure = 0; - netreq->bogus = 0; done: netreq->state = NET_REQ_FINISHED; priv_getdns_check_dns_req_complete(dnsreq); @@ -1227,10 +1205,6 @@ stub_tcp_read_cb(void *userarg) netreq->tcp.read_buf = NULL; dnsreq->upstreams->current = 0; - /* TODO: DNSSEC */ - netreq->secure = 0; - netreq->bogus = 0; - stub_cleanup(netreq); close(netreq->fd); priv_getdns_check_dns_req_complete(dnsreq); @@ -1317,10 +1291,6 @@ upstream_read_cb(void *userarg) * on a working connection until we hit a problem.*/ upstream->upstreams->current = 0; - /* TODO: DNSSEC */ - netreq->secure = 0; - netreq->bogus = 0; - if (netreq->owner == upstream->starttls_req) { dnsreq = netreq->owner; if (is_starttls_response(netreq)) { diff --git a/src/test/getdns_query.c b/src/test/getdns_query.c index f451bd4c..fa6cf3d3 100644 --- a/src/test/getdns_query.c +++ b/src/test/getdns_query.c @@ -166,6 +166,90 @@ print_usage(FILE *out, const char *progname) fprintf(out, "\t-q\tQuiet mode - don't print response\n"); } +static getdns_return_t validate_chain(getdns_dict *response) +{ + getdns_return_t r; + getdns_list *validation_chain; + getdns_list *replies_tree; + getdns_dict *reply; + getdns_list *to_validate; + getdns_list *trust_anchor; + size_t i; + int s; + + if (!(to_validate = getdns_list_create())) + return GETDNS_RETURN_MEMORY_ERROR; + + if (!(trust_anchor = getdns_root_trust_anchor(NULL))) + return GETDNS_RETURN_GENERIC_ERROR; + + if ((r = getdns_dict_get_list( + response, "validation_chain", &validation_chain))) + return r; + + if ((r = getdns_dict_get_list( + response, "replies_tree", &replies_tree))) + return r; + + fprintf(stdout, "replies_tree %zu, dnssec_status: ", i); + switch ((s = getdns_validate_dnssec( + replies_tree, validation_chain, trust_anchor))) { + + case GETDNS_DNSSEC_SECURE: + fprintf(stdout, "GETDNS_DNSSEC_SECURE\n"); + break; + case GETDNS_DNSSEC_BOGUS: + fprintf(stdout, "GETDNS_DNSSEC_BOGUS\n"); + break; + case GETDNS_DNSSEC_INDETERMINATE: + fprintf(stdout, "GETDNS_DNSSEC_INDETERMINATE\n"); + break; + case GETDNS_DNSSEC_INSECURE: + fprintf(stdout, "GETDNS_DNSSEC_INSECURE\n"); + break; + case GETDNS_DNSSEC_NOT_PERFORMED: + fprintf(stdout, "GETDNS_DNSSEC_NOT_PERFORMED\n"); + break; + default: + fprintf(stdout, "%d\n", (int)s); + } + + i = 0; + while (!(r = getdns_list_get_dict(replies_tree, i++, &reply))) { + + if ((r = getdns_list_set_dict(to_validate, 0, reply))) + return r; + + fprintf( stdout + , "reply %zu, dnssec_status: ", i); + switch ((s = getdns_validate_dnssec( + to_validate, validation_chain, trust_anchor))) { + + case GETDNS_DNSSEC_SECURE: + fprintf(stdout, "GETDNS_DNSSEC_SECURE\n"); + break; + case GETDNS_DNSSEC_BOGUS: + fprintf(stdout, "GETDNS_DNSSEC_BOGUS\n"); + break; + case GETDNS_DNSSEC_INDETERMINATE: + fprintf(stdout, "GETDNS_DNSSEC_INDETERMINATE\n"); + break; + case GETDNS_DNSSEC_INSECURE: + fprintf(stdout, "GETDNS_DNSSEC_INSECURE\n"); + break; + case GETDNS_DNSSEC_NOT_PERFORMED: + fprintf(stdout, "GETDNS_DNSSEC_NOT_PERFORMED\n"); + break; + default: + fprintf(stdout, "%d\n", (int)s); + } + } + if (r != GETDNS_RETURN_NO_SUCH_LIST_ITEM) + return r; + + return GETDNS_RETURN_GOOD; +} + void callback(getdns_context *context, getdns_callback_type_t callback_type, getdns_dict *response, void *userarg, getdns_transaction_t trans_id) { @@ -178,6 +262,7 @@ void callback(getdns_context *context, getdns_callback_type_t callback_type, : getdns_pretty_print_dict(response))) { fprintf(stdout, "ASYNC response:\n%s\n", response_str); + validate_chain(response); free(response_str); } fprintf(stdout, @@ -617,6 +702,7 @@ main(int argc, char **argv) fprintf( stdout, "SYNC response:\n%s\n" , response_str); + validate_chain(response); free(response_str); } else { r = GETDNS_RETURN_MEMORY_ERROR; diff --git a/src/types-internal.h b/src/types-internal.h index 12049aef..804a93ef 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -189,9 +189,8 @@ typedef struct getdns_network_req /* request class */ uint16_t request_class; - /* result */ - int secure; - int bogus; + /* dnssec status */ + int dnssec_status; /* For stub resolving */ struct getdns_upstream *upstream; @@ -232,7 +231,8 @@ typedef struct getdns_dns_req { getdns_rbnode_t node; /* name */ - char *name; + uint8_t name[256]; + size_t name_len; /* canceled flag */ int canceled; @@ -319,6 +319,8 @@ typedef struct getdns_dns_req { /* utility methods */ +extern getdns_dict *dnssec_ok_checking_disabled; + /* dns request utils */ getdns_dns_req *dns_req_new(getdns_context *context, getdns_eventloop *loop, const char *name, uint16_t request_type, getdns_dict *extensions); diff --git a/src/util-internal.c b/src/util-internal.c index 9a6f35c7..4febac14 100644 --- a/src/util-internal.c +++ b/src/util-internal.c @@ -37,6 +37,7 @@ #include #include +#include #include #include "getdns/getdns.h" #include "dict.h" @@ -60,7 +61,6 @@ static getdns_extension_format extformats[] = { {"add_opt_parameters", t_dict}, {"add_warning_for_bad_dns", t_int}, - {"dnssec_ok_checking_disabled", t_int}, {"dnssec_return_only_secure", t_int}, {"dnssec_return_status", t_int}, {"dnssec_return_validation_chain", t_int}, @@ -179,7 +179,7 @@ sockaddr_to_dict(struct getdns_context *context, struct sockaddr_storage *addres } getdns_dict * -priv_getdns_rr_iter2rr_dict(getdns_context *context, priv_getdns_rr_iter *i) +priv_getdns_rr_iter2rr_dict(struct mem_funcs *mf, priv_getdns_rr_iter *i) { getdns_dict *rr_dict, *rdata_dict; getdns_bindata bindata; @@ -192,7 +192,7 @@ priv_getdns_rr_iter2rr_dict(getdns_context *context, priv_getdns_rr_iter *i) uint16_t rr_type; assert(i); - if (!(rr_dict = getdns_dict_create_with_context(context))) + if (!(rr_dict = _getdns_dict_create_with_mf(mf))) return NULL; bindata.data = priv_getdns_owner_if_or_as_decompressed( @@ -248,8 +248,8 @@ priv_getdns_rr_iter2rr_dict(getdns_context *context, priv_getdns_rr_iter *i) goto error; } - if (!(rdata_dict = getdns_dict_create_with_context(context))) - goto error; + if (!(rdata_dict = _getdns_dict_create_with_mf(mf))) + return NULL; if (i->rr_type + 10 <= i->nxt) { bindata.size = i->nxt - (i->rr_type + 10); @@ -330,7 +330,8 @@ priv_getdns_rr_iter2rr_dict(getdns_context *context, priv_getdns_rr_iter *i) /* list with rdf values */ if (! repeat_list && !(repeat_list = - getdns_list_create_with_context(context))) + _getdns_list_create_with_mf(mf))) + goto rdata_error; switch (val_type) { @@ -357,7 +358,7 @@ priv_getdns_rr_iter2rr_dict(getdns_context *context, priv_getdns_rr_iter *i) if (repeat_dict) { if (! repeat_list && !(repeat_list = - getdns_list_create_with_context(context))) + _getdns_list_create_with_mf(mf))) goto rdata_error; if (getdns_list_append_dict( @@ -368,7 +369,7 @@ priv_getdns_rr_iter2rr_dict(getdns_context *context, priv_getdns_rr_iter *i) repeat_dict = NULL; } if (!(repeat_dict = - getdns_dict_create_with_context(context))) + _getdns_dict_create_with_mf(mf))) goto rdata_error; } assert(repeat_dict); @@ -393,7 +394,7 @@ priv_getdns_rr_iter2rr_dict(getdns_context *context, priv_getdns_rr_iter *i) } if (repeat_dict) { if (!repeat_list && !(repeat_list = - getdns_list_create_with_context(context))) + _getdns_list_create_with_mf(mf))) goto rdata_error; if (getdns_list_append_dict(repeat_list, repeat_dict)) goto rdata_error; @@ -422,8 +423,8 @@ error: return NULL; } -static int -dname_equal(uint8_t *s1, uint8_t *s2) +int +priv_getdns_dname_equal(const uint8_t *s1, const uint8_t *s2) { uint8_t i; for (;;) { @@ -432,7 +433,8 @@ dname_equal(uint8_t *s1, uint8_t *s2) else if (!*s1) return 1; for (i = *s1++, s2++; i > 0; i--, s1++, s2++) - if ((*s1 & 0xDF) != (*s2 & 0xDF)) + if (*s1 != *s2 && tolower((unsigned char)*s1) + != tolower((unsigned char)*s2)) return 0; } } @@ -543,8 +545,7 @@ priv_getdns_create_reply_dict(getdns_context *context, getdns_network_req *req, if ((r = getdns_dict_set_dict(result, "header", header))) goto error; - (void) gldns_str2wire_dname_buf( - req->owner->name, canonical_name_space, &canonical_name_len); + canonical_name = req->owner->name; for ( rr_iter = priv_getdns_rr_iter_init(&rr_iter_storage , req->response @@ -553,7 +554,7 @@ priv_getdns_create_reply_dict(getdns_context *context, getdns_network_req *req, ; rr_iter = priv_getdns_rr_iter_next(rr_iter)) { if (!set_dict(&rr_dict, - priv_getdns_rr_iter2rr_dict(context, rr_iter))) + priv_getdns_rr_iter2rr_dict(&context->mf, rr_iter))) continue; section = priv_getdns_rr_iter_section(rr_iter); @@ -580,7 +581,7 @@ priv_getdns_create_reply_dict(getdns_context *context, getdns_network_req *req, owner_name = priv_getdns_owner_if_or_as_decompressed( rr_iter, owner_name_space, &owner_name_len); - if (!dname_equal(canonical_name, owner_name)) + if (!priv_getdns_dname_equal(canonical_name, owner_name)) continue; if (!(rdf_iter = priv_getdns_rdf_iter_init( @@ -653,7 +654,7 @@ priv_getdns_create_reply_dict(getdns_context *context, getdns_network_req *req, owner_name = priv_getdns_owner_if_or_as_decompressed( rr_iter, owner_name_space, &owner_name_len); - if (!dname_equal(canonical_name, owner_name)) + if (!priv_getdns_dname_equal(canonical_name, owner_name)) continue; if (!(rdf_iter = priv_getdns_rdf_iter_init( @@ -733,21 +734,22 @@ create_getdns_response(getdns_dns_req *completed_request) continue; nreplies++; - if (netreq->secure) + if (netreq->dnssec_status == GETDNS_DNSSEC_SECURE) nsecure++; - else if (! netreq->bogus) + else if (! netreq->dnssec_status != GETDNS_DNSSEC_BOGUS) ninsecure++; - if (dnssec_return_status && netreq->bogus) + + if (dnssec_return_status && + netreq->dnssec_status == GETDNS_DNSSEC_BOGUS) nbogus++; - else if (GLDNS_RCODE_NOERROR == - GLDNS_RCODE_WIRE(netreq->response)) - nanswers++; + if (! completed_request->dnssec_return_validation_chain) { - if (dnssec_return_status && netreq->bogus) + if (dnssec_return_status && + netreq->dnssec_status == GETDNS_DNSSEC_BOGUS) continue; else if (completed_request->dnssec_return_only_secure - && ! netreq->secure) + && netreq->dnssec_status != GETDNS_DNSSEC_SECURE) continue; } if (!(reply = priv_getdns_create_reply_dict(context, @@ -762,15 +764,18 @@ create_getdns_response(getdns_dns_req *completed_request) result, "canonical_name", canonical_name)) goto error; } + /* TODO: Check instead if canonical_name for request_type + * is in the answer section. + */ + if (GLDNS_RCODE_NOERROR == + GLDNS_RCODE_WIRE(netreq->response)) + nanswers++; + if (dnssec_return_status || completed_request->dnssec_return_validation_chain) { if (getdns_dict_set_int(reply, "dnssec_status", - ( netreq->secure ? GETDNS_DNSSEC_SECURE - : netreq->bogus ? GETDNS_DNSSEC_BOGUS - : rrsigs_in_answer && - context->has_ta ? GETDNS_DNSSEC_INDETERMINATE - : GETDNS_DNSSEC_INSECURE ))) + netreq->dnssec_status)) goto error; } @@ -830,7 +835,7 @@ extformatcmp(const void *a, const void *b) /*---------------------------------------- validate_extensions */ getdns_return_t -validate_extensions(struct getdns_dict * extensions) +priv_getdns_validate_extensions(struct getdns_dict * extensions) { struct getdns_dict_item *item; getdns_extension_format *extformat; @@ -852,16 +857,18 @@ validate_extensions(struct getdns_dict * extensions) return GETDNS_RETURN_EXTENSION_MISFORMAT; } return GETDNS_RETURN_GOOD; -} /* validate_extensions */ +} /* priv_getdns_validate_extensions */ getdns_return_t getdns_apply_network_result(getdns_network_req* netreq, struct ub_result* ub_res) { - size_t dname_len; - - netreq->secure = ub_res->secure; - netreq->bogus = ub_res->bogus; + if (ub_res->bogus) + netreq->dnssec_status = GETDNS_DNSSEC_BOGUS; + else if (ub_res->secure) + netreq->dnssec_status = GETDNS_DNSSEC_SECURE; + else if (netreq->owner->context->trust_anchors) + netreq->dnssec_status = GETDNS_DNSSEC_INSECURE; if (ub_res == NULL) /* Timeout */ return GETDNS_RETURN_GOOD; @@ -900,24 +907,24 @@ getdns_apply_network_result(getdns_network_req* netreq, GLDNS_RA_SET(netreq->response); GLDNS_RCODE_SET(netreq->response, ub_res->rcode); - dname_len = netreq->max_udp_payload_size - GLDNS_HEADER_SIZE; - if (gldns_str2wire_dname_buf(netreq->owner->name, - netreq->response + GLDNS_HEADER_SIZE, &dname_len)) - return GETDNS_RETURN_GENERIC_ERROR; + (void) memcpy( netreq->response + GLDNS_HEADER_SIZE + , netreq->owner->name, netreq->owner->name_len); - gldns_write_uint16( netreq->response + GLDNS_HEADER_SIZE + dname_len + gldns_write_uint16( netreq->response + GLDNS_HEADER_SIZE + + netreq->owner->name_len , netreq->request_type); - gldns_write_uint16( netreq->response + GLDNS_HEADER_SIZE + dname_len + 2 + gldns_write_uint16( netreq->response + GLDNS_HEADER_SIZE + + netreq->owner->name_len + 2 , netreq->request_class); - netreq->response_len = GLDNS_HEADER_SIZE + dname_len + 4; + netreq->response_len = GLDNS_HEADER_SIZE + netreq->owner->name_len + 4; return GETDNS_RETURN_GOOD; } getdns_return_t -validate_dname(const char* dname) { +priv_getdns_validate_dname(const char* dname) { int len; int label_len; const char* s; @@ -980,7 +987,150 @@ validate_dname(const char* dname) { return GETDNS_RETURN_BAD_DOMAIN_NAME; } return GETDNS_RETURN_GOOD; -} /* validate_dname */ +} /* priv_getdns_validate_dname */ +static void _getdns_list2wire_buf(gldns_buffer *buf, getdns_list *l) +{ + getdns_dict *rr_dict, *q_dict; + getdns_list *section; + getdns_return_t r; + size_t i, j, pkt_start, ancount, qdcount; + uint32_t qtype, qclass; + getdns_bindata *qname; + + pkt_start = gldns_buffer_position(buf); + /* Empty header */ + gldns_buffer_write_u32(buf, 0); + gldns_buffer_write_u32(buf, 0); + gldns_buffer_write_u32(buf, 0); + + for ( i = 0, qdcount = 0 + ; (r = getdns_list_get_dict(l, i, &rr_dict)) + != GETDNS_RETURN_NO_SUCH_LIST_ITEM + ; i++ ) { + + if (r) { + if (r == GETDNS_RETURN_WRONG_TYPE_REQUESTED) + continue; + else + break; + } + if (getdns_dict_get_dict(rr_dict, "question", &q_dict) + == GETDNS_RETURN_GOOD) { + + /* rr_dict was actually a reply + * with a question section/rr_dict + */ + rr_dict = q_dict; + } + if (getdns_dict_get_int(rr_dict, "qtype", &qtype) || + getdns_dict_get_bindata(rr_dict, "qname", &qname)) + continue; + (void) getdns_dict_get_int(rr_dict, "qclass", &qclass); + gldns_buffer_write(buf, qname->data, qname->size); + gldns_buffer_write_u16(buf, (uint16_t)qtype); + gldns_buffer_write_u16(buf, (uint16_t)qclass); + qdcount++; + } + gldns_buffer_write_u16_at(buf, pkt_start+GLDNS_QDCOUNT_OFF, qdcount); + for ( i = 0, ancount = 0 + ; (r = getdns_list_get_dict(l, i, &rr_dict)) + != GETDNS_RETURN_NO_SUCH_LIST_ITEM + ; i++ ) { + + if (r) { + if (r == GETDNS_RETURN_WRONG_TYPE_REQUESTED) + continue; + else + break; + } + if (priv_getdns_rr_dict2wire(rr_dict, buf) + == GETDNS_RETURN_GOOD) { + + ancount++; + continue; + } + if (getdns_dict_get_list(rr_dict, "answer", §ion) + == GETDNS_RETURN_GOOD) { + + for ( j = 0 + ; (r = getdns_list_get_dict(section, j, &q_dict)) + != GETDNS_RETURN_NO_SUCH_LIST_ITEM + ; j++ ) { + + if (r) { + if (r == + GETDNS_RETURN_WRONG_TYPE_REQUESTED) + continue; + else + break; + } + if (priv_getdns_rr_dict2wire(q_dict, buf) + == GETDNS_RETURN_GOOD) + + ancount++; + } + } + if (getdns_dict_get_list(rr_dict, "authority", §ion) + == GETDNS_RETURN_GOOD) { + + for ( j = 0 + ; (r = getdns_list_get_dict(section, j, &q_dict)) + != GETDNS_RETURN_NO_SUCH_LIST_ITEM + ; j++ ) { + + if (r) { + if (r == + GETDNS_RETURN_WRONG_TYPE_REQUESTED) + continue; + else + break; + } + if (priv_getdns_rr_dict2wire(q_dict, buf) + == GETDNS_RETURN_GOOD) + + ancount++; + } + } + } + gldns_buffer_write_u16_at(buf, pkt_start+GLDNS_ANCOUNT_OFF, ancount); +} + +uint8_t *_getdns_list2wire( + getdns_list *l, uint8_t *buf, size_t *buf_len, struct mem_funcs *mf) +{ + gldns_buffer gbuf; + size_t sz; + + gldns_buffer_init_frm_data(&gbuf, buf, *buf_len); + _getdns_list2wire_buf(&gbuf, l); + + if ((sz = gldns_buffer_position(&gbuf)) <= *buf_len) + return buf; + + if (!(buf = GETDNS_XMALLOC(*mf, uint8_t, (*buf_len = sz)))) + return NULL; + + gldns_buffer_init_frm_data(&gbuf, buf, sz); + _getdns_list2wire_buf(&gbuf, l); + return buf; +} + +void _getdns_wire2list(uint8_t *pkt, size_t pkt_len, getdns_list *l) +{ + priv_getdns_rr_iter rr_spc, *rr; + getdns_dict *rr_dict; + + for ( rr = priv_getdns_rr_iter_init(&rr_spc, pkt, pkt_len) + ; rr ; rr = priv_getdns_rr_iter_next(rr)) { + + if (!(rr_dict = priv_getdns_rr_iter2rr_dict(&l->mf, rr))) + continue; + + (void)getdns_list_append_dict(l, rr_dict); + getdns_dict_destroy(rr_dict); + } +} + /* util-internal.c */ diff --git a/src/util-internal.h b/src/util-internal.h index c58abd31..5963fecf 100644 --- a/src/util-internal.h +++ b/src/util-internal.h @@ -44,6 +44,7 @@ #define SCHED_DEBUG 0 #define WIRE_DEBUG 0 #define STUB_DEBUG 0 +#define SEC_DEBUG 1 #ifdef S_SPLINT_S # define INLINE @@ -122,7 +123,7 @@ getdns_return_t sockaddr_to_dict(struct getdns_context *context, struct sockaddr_storage *sockaddr, struct getdns_dict ** output); getdns_dict * -priv_getdns_rr_iter2rr_dict(getdns_context *context, priv_getdns_rr_iter *i); +priv_getdns_rr_iter2rr_dict(struct mem_funcs *mf, priv_getdns_rr_iter *i); struct getdns_dns_req; struct getdns_dict *create_getdns_response(struct getdns_dns_req *completed_request); @@ -130,7 +131,15 @@ struct getdns_dict *create_getdns_response(struct getdns_dns_req *completed_requ getdns_dict *priv_getdns_create_reply_dict(getdns_context *context, getdns_network_req *req, getdns_list *just_addrs, int *rrsigs_in_answer); -getdns_return_t validate_dname(const char* dname); +getdns_return_t priv_getdns_validate_dname(const char* dname); + +int priv_getdns_dname_equal(const uint8_t *s1, const uint8_t *s2); + +uint8_t *_getdns_list2wire( + getdns_list *l, uint8_t *buf, size_t *buf_len, struct mem_funcs *mf); + +void _getdns_wire2list(uint8_t *pkt, size_t pkt_len, getdns_list *l); + /** * detect unrecognized extension strings or invalid extension formats @@ -140,7 +149,7 @@ getdns_return_t validate_dname(const char* dname); * @return GETDNS_RETURN_NO_SUCH_EXTENSION A name in the extensions dict is not a valid extension. * @return GETDNS_RETURN_EXTENSION_MISFORMAT One or more of the extensions has a bad format. */ -getdns_return_t validate_extensions(struct getdns_dict * extensions); +getdns_return_t priv_getdns_validate_extensions(struct getdns_dict * extensions); #define DEBUG_ON(...) do { \ struct timeval tv; \ @@ -170,6 +179,13 @@ getdns_return_t validate_extensions(struct getdns_dict * extensions); #define DEBUG_STUB(...) DEBUG_OFF(__VA_ARGS__) #endif +#if defined(SEC_DEBUG) && SEC_DEBUG +#include +#define DEBUG_SEC(...) DEBUG_ON(__VA_ARGS__) +#else +#define DEBUG_SEC(...) DEBUG_OFF(__VA_ARGS__) +#endif + INLINE getdns_eventloop_event *getdns_eventloop_event_init( getdns_eventloop_event *ev,void *userarg, getdns_eventloop_callback read_cb, getdns_eventloop_callback write_cb, getdns_eventloop_callback timeout_cb)