diff --git a/.gitignore b/.gitignore index d53a4dba..35748d59 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ tests_dict tests_list tests_stub_async tests_stub_sync +src/test/tests_dnssec src/example/example-reverse src/test/check_getdns.log check_getdns diff --git a/src/general.c b/src/general.c index 35dfef13..fe78e294 100644 --- a/src/general.c +++ b/src/general.c @@ -130,11 +130,9 @@ ub_local_resolve_timeout(evutil_socket_t fd, short what, void *arg) free(cb_data); } -/* cleanup and send an error to the user callback */ -static void -handle_network_request_error(getdns_network_req * netreq, int err) +static void call_user_callback(getdns_dns_req *dns_req, + struct getdns_dict *response) { - getdns_dns_req *dns_req = netreq->owner; struct getdns_context *context = dns_req->context; getdns_transaction_t trans_id = dns_req->trans_id; getdns_callback_t cb = dns_req->user_callback; @@ -144,30 +142,247 @@ handle_network_request_error(getdns_network_req * netreq, int err) getdns_context_clear_outbound_request(dns_req); dns_req_free(dns_req); - cb(context, GETDNS_CALLBACK_ERROR, NULL, user_arg, trans_id); + cb(context, + (response ? GETDNS_CALLBACK_COMPLETE : GETDNS_CALLBACK_ERROR), + response, user_arg, trans_id); +} + +/* cleanup and send an error to the user callback */ +static void +handle_network_request_error(getdns_network_req * netreq, int err) +{ + call_user_callback(netreq->owner, NULL); +} + +struct validation_chain { + ldns_rbtree_t root; + struct mem_funcs mf; + getdns_dns_req *dns_req; + size_t todo; +}; +struct chain_response { + int err; + ldns_rr_list *result; + int sec; + char *bogus; + struct validation_chain *chain; + int unbound_id; +}; +struct chain_link { + ldns_rbnode_t node; + struct chain_response DNSKEY; + struct chain_response DS; +}; + +static void submit_link(struct validation_chain *chain, char *name); +static void callback_on_complete_chain(struct validation_chain *chain); +static void +ub_supporting_callback(void *arg, int err, ldns_buffer *result, int sec, + char *bogus) +{ + struct chain_response *response = (struct chain_response *) arg; + ldns_status r; + ldns_pkt *p; + ldns_rr_list *answer; + ldns_rr_list *keys; + size_t i; + + response->err = err; + response->sec = sec; + response->bogus = bogus; + + if (result == NULL) + goto done; + + r = ldns_buffer2pkt_wire(&p, result); + if (r != LDNS_STATUS_OK) { + if (err == 0) + response->err = r; + goto done; + } + + keys = ldns_rr_list_new(); + answer = ldns_pkt_answer(p); + for (i = 0; i < ldns_rr_list_rr_count(answer); i++) { + ldns_rr *rr = ldns_rr_list_rr(answer, i); + + if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_DNSKEY || + ldns_rr_get_type(rr) == LDNS_RR_TYPE_DS) { + + (void) ldns_rr_list_push_rr(keys, ldns_rr_clone(rr)); + continue; + } + if (ldns_rr_get_type(rr) != LDNS_RR_TYPE_RRSIG) + continue; + + if (ldns_read_uint16(ldns_rdf_data(ldns_rr_rdf(rr, 0))) == + LDNS_RR_TYPE_DS) + submit_link(response->chain, + ldns_rdf2str(ldns_rr_rdf(rr, 7))); + + else if (ldns_read_uint16(ldns_rdf_data(ldns_rr_rdf(rr, 0))) != + LDNS_RR_TYPE_DNSKEY) + continue; + + (void) ldns_rr_list_push_rr(keys, ldns_rr_clone(rr)); + } + if (ldns_rr_list_rr_count(keys)) + response->result = keys; + else + ldns_rr_list_free(keys); + + ldns_pkt_free(p); + +done: if (response->err == 0 && response->result == NULL) + response->err = -1; + + callback_on_complete_chain(response->chain); +} + +static void submit_link(struct validation_chain *chain, char *name) +{ + int r; + struct chain_link *link = (struct chain_link *) + ldns_rbtree_search((ldns_rbtree_t *)&(chain->root), name); + + if (link) { + free(name); + return; + } + link = GETDNS_MALLOC(chain->mf, struct chain_link); + link->node.key = name; + + link->DNSKEY.err = 0; + link->DNSKEY.result = NULL; + link->DNSKEY.sec = 0; + link->DNSKEY.bogus = NULL; + link->DNSKEY.chain = chain; + link->DNSKEY.unbound_id = -1; + + link->DS.err = 0; + link->DS.result = NULL; + link->DS.sec = 0; + link->DS.bogus = NULL; + link->DS.chain = chain; + link->DS.unbound_id = -1; + + ldns_rbtree_insert(&(chain->root), (ldns_rbnode_t *)link); + /* fprintf(stderr, "submitting for: %s\n", name); */ + + r = ub_resolve_event(chain->dns_req->unbound, + name, LDNS_RR_TYPE_DNSKEY, LDNS_RR_CLASS_IN, &link->DNSKEY, + ub_supporting_callback, &link->DNSKEY.unbound_id); + if (r != 0) + link->DNSKEY.err = r; + + r = ub_resolve_event(chain->dns_req->unbound, + name, LDNS_RR_TYPE_DS, LDNS_RR_CLASS_IN, &link->DS, + ub_supporting_callback, &link->DS.unbound_id); + if (r != 0) + link->DS.err = r; +} + +void destroy_chain_link(ldns_rbnode_t * node, void *arg) +{ + struct chain_link *link = (struct chain_link*) node; + struct validation_chain *chain = (struct validation_chain*) arg; + + free((void *)link->node.key); + ldns_rr_list_deep_free(link->DNSKEY.result); + ldns_rr_list_deep_free(link->DS.result); + GETDNS_FREE(chain->mf, link); +} + +static void destroy_chain(struct getdns_context *context, + struct validation_chain *chain) +{ + ldns_traverse_postorder(&(chain->root), + destroy_chain_link, chain); + GETDNS_FREE(chain->mf, chain); +} + +static void callback_on_complete_chain(struct validation_chain *chain) +{ + struct getdns_context *context = chain->dns_req->context; + struct getdns_dict *response; + struct chain_link *link; + size_t todo = chain->todo; + ldns_rr_list *keys; + struct getdns_list *getdns_keys; + + LDNS_RBTREE_FOR(link, struct chain_link *, + (ldns_rbtree_t *)&(chain->root)) { + if (link->DNSKEY.result == NULL && link->DNSKEY.err == 0) + todo++; + if (link->DS.result == NULL && link->DS.err == 0 && + (((const char *)link->node.key)[0] != '.' || + ((const char *)link->node.key)[1] != '\0' )) + todo++; + } + /* fprintf(stderr, "todo until validation: %d\n", (int)todo); */ + if (todo == 0) { + response = create_getdns_response(chain->dns_req); + + keys = ldns_rr_list_new(); + LDNS_RBTREE_FOR(link, struct chain_link *, + (ldns_rbtree_t *)&(chain->root)) { + (void) ldns_rr_list_cat(keys, link->DNSKEY.result); + (void) ldns_rr_list_cat(keys, link->DS.result); + } + getdns_keys = create_list_from_rr_list(context, keys); + (void) getdns_dict_set_list(response, "validation_chain", + getdns_keys); + getdns_list_destroy(getdns_keys); + ldns_rr_list_free(keys); + destroy_chain(context, chain); + call_user_callback(chain->dns_req, response); + } +} + +/* Do some additional requests to fetch the complete validation chain */ +static void get_validation_chain(getdns_dns_req *dns_req) +{ + getdns_network_req *netreq = dns_req->first_req; + struct validation_chain *chain = GETDNS_MALLOC(dns_req->context->mf, + struct validation_chain); + + ldns_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->todo = 1; + + while (netreq) { + size_t i; + ldns_rr_list *answer = ldns_pkt_answer(netreq->result); + for (i = 0; i < ldns_rr_list_rr_count(answer); i++) { + ldns_rr *rr = ldns_rr_list_rr(answer, i); + if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_RRSIG) + submit_link(chain, + ldns_rdf2str(ldns_rr_rdf(rr, 7))); + } + netreq = netreq->next; + } + chain->todo--; + callback_on_complete_chain(chain); } /* cleanup and send the response to the user callback */ static void handle_dns_request_complete(getdns_dns_req * dns_req) { - struct getdns_dict *response = create_getdns_response(dns_req); + uint32_t ret_chain_ext = GETDNS_EXTENSION_FALSE; + getdns_return_t r = getdns_dict_get_int(dns_req->extensions, + "dnssec_return_validation_chain", &ret_chain_ext); - struct getdns_context *context = dns_req->context; - getdns_transaction_t trans_id = dns_req->trans_id; - getdns_callback_t cb = dns_req->user_callback; - void *user_arg = dns_req->user_pointer; - - /* clean up the request */ - getdns_context_clear_outbound_request(dns_req); - dns_req_free(dns_req); - if (response) { - cb(context, - GETDNS_CALLBACK_COMPLETE, response, user_arg, trans_id); - } else { - cb(context, GETDNS_CALLBACK_ERROR, NULL, user_arg, trans_id); - } + if (r == GETDNS_RETURN_GOOD && ret_chain_ext == GETDNS_EXTENSION_TRUE) + get_validation_chain(dns_req); + else + call_user_callback(dns_req, create_getdns_response(dns_req)); } static int diff --git a/src/test/Makefile.in b/src/test/Makefile.in index 9dbdbe55..c5d8f0f7 100644 --- a/src/test/Makefile.in +++ b/src/test/Makefile.in @@ -19,7 +19,7 @@ CC=gcc CFLAGS=@CFLAGS@ -Wall -I$(srcdir)/ -I$(srcdir)/../ -I/usr/local/include -std=c99 $(cflags) LDFLAGS=@LDFLAGS@ -L. -L.. -L/usr/local/lib LDLIBS=-lgetdns @LIBS@ -lcheck -PROGRAMS=tests_dict tests_list tests_stub_async tests_stub_sync check_getdns +PROGRAMS=tests_dict tests_list tests_stub_async tests_stub_sync check_getdns tests_dnssec .SUFFIXES: .c .o .a .lo .h @@ -48,6 +48,10 @@ check_getdns_common: check_getdns_common.o check_getdns: check_getdns.o check_getdns_common.o $(LIBTOOL) --tag=CC --mode=link $(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ $^ +tests_dnssec: tests_dnssec.o testmessages.o + $(LIBTOOL) --tag=CC --mode=link $(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ tests_dnssec.o testmessages.o + + test: all ./testscript.sh @echo "All tests OK" diff --git a/src/test/tests_dnssec.c b/src/test/tests_dnssec.c new file mode 100644 index 00000000..b66c6c9d --- /dev/null +++ b/src/test/tests_dnssec.c @@ -0,0 +1,127 @@ +/** + * \file + * unit tests for getdns_dict helper routines, these should be used to + * perform regression tests, output must be unchanged from canonical output + * stored with the sources + */ +/* The MIT License (MIT) + * Copyright (c) 2013 Verisign, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config.h" +#ifdef HAVE_EVENT2_EVENT_H +# include +#else +# include +#endif +#include +#include +#include +#include "testmessages.h" +#include + +/* Set up the callback function, which will also do the processing of the results */ +void +this_callbackfn(struct getdns_context *this_context, + uint16_t this_callback_type, + struct getdns_dict *this_response, + void *this_userarg, getdns_transaction_t this_transaction_id) +{ + if (this_callback_type == GETDNS_CALLBACK_COMPLETE) { /* This is a callback with data */ + char *res = getdns_pretty_print_dict(this_response); + fprintf(stdout, "%s\n", res); + free(res); + + } else if (this_callback_type == GETDNS_CALLBACK_CANCEL) + fprintf(stderr, + "The callback with ID %llu was cancelled. Exiting.", + (unsigned long long)this_transaction_id); + else + fprintf(stderr, + "The callback got a callback_type of %d. Exiting.", + this_callback_type); + getdns_dict_destroy(this_response); +} + +int +main(int argc, char** argv) +{ + /* Create the DNS context for this call */ + struct getdns_context *this_context = NULL; + getdns_return_t context_create_return = + getdns_context_create(&this_context, 1); + if (context_create_return != GETDNS_RETURN_GOOD) { + fprintf(stderr, "Trying to create the context failed: %d", + context_create_return); + return (GETDNS_RETURN_GENERIC_ERROR); + } + getdns_context_set_timeout(this_context, 5000); + + struct getdns_dict * this_extensions = getdns_dict_create(); + getdns_return_t this_ret = getdns_dict_set_int(this_extensions, + "dnssec_return_validation_chain", GETDNS_EXTENSION_TRUE); + if (this_ret != GETDNS_RETURN_GOOD) { + fprintf(stderr, "Setting extension " + "\"dnssec_return_validation_chain\" failed: %d\n", this_ret); + getdns_dict_destroy(this_extensions); + getdns_context_destroy(this_context); + return (GETDNS_RETURN_GENERIC_ERROR); + } + + /* Create an event base and put it in the context using the unknown function name */ + struct event_base *this_event_base; + this_event_base = event_base_new(); + if (this_event_base == NULL) { + fprintf(stderr, "Trying to create the event base failed."); + getdns_context_destroy(this_context); + return (GETDNS_RETURN_GENERIC_ERROR); + } + (void) getdns_extension_set_libevent_base(this_context, + this_event_base); + /* Set up the getdns call */ + const char *this_name = argc > 1 ? argv[1] : "www.example.com"; + char *this_userarg = "somestring"; // Could add things here to help identify this call + getdns_transaction_t this_transaction_id = 0; + + /* Make the call */ + getdns_return_t dns_request_return = + getdns_address(this_context, this_name, + this_extensions, this_userarg, &this_transaction_id, this_callbackfn); + if (dns_request_return == GETDNS_RETURN_BAD_DOMAIN_NAME) { + fprintf(stderr, "A bad domain name was used: %s. Exiting.", + this_name); + event_base_free(this_event_base); + getdns_context_destroy(this_context); + return (GETDNS_RETURN_GENERIC_ERROR); + } + else { + /* Call the event loop */ + event_base_dispatch(this_event_base); + // TODO: check the return value above + } + /* Clean up */ + event_base_free(this_event_base); + getdns_context_destroy(this_context); + /* Assuming we get here, leave gracefully */ + exit(EXIT_SUCCESS); +} /* main */ + +/* example-simple-answers.c */ diff --git a/src/util-internal.c b/src/util-internal.c index 9769653e..ea4a06c2 100644 --- a/src/util-internal.c +++ b/src/util-internal.c @@ -53,7 +53,7 @@ static getdns_extension_format extformats[] = { {"add_warning_for_bad_dns", t_int}, {"dnssec_return_only_secure", t_int}, {"dnssec_return_status", t_int}, - {"dnssec_return_supporting_responses", t_int}, + {"dnssec_return_validation_chain", t_int}, {"return_api_information", t_int}, {"return_both_v4_and_v6", t_int}, {"return_call_debugging", t_int}, @@ -346,7 +346,7 @@ create_dict_from_rr(struct getdns_context *context, ldns_rr * rr) /* helper to convert an rr_list to getdns_list. returns a list of objects where each object is a result from create_dict_from_rr */ -static struct getdns_list * +struct getdns_list * create_list_from_rr_list(struct getdns_context *context, ldns_rr_list * rr_list) { size_t i = 0; diff --git a/src/util-internal.h b/src/util-internal.h index d39b2c5d..56ee6952 100644 --- a/src/util-internal.h +++ b/src/util-internal.h @@ -111,4 +111,10 @@ getdns_return_t validate_dname(const char* dname); */ getdns_return_t validate_extensions(struct getdns_dict * extensions); +/* helper to convert an rr_list to getdns_list. + returns a list of objects where each object + is a result from create_dict_from_rr */ +struct getdns_list * +create_list_from_rr_list(struct getdns_context *context, ldns_rr_list * rr_list); + /* util-internal.h */