diff options
Diffstat (limited to '')
-rw-r--r-- | external/unbound/libunbound/libunbound.c | 1227 |
1 files changed, 1227 insertions, 0 deletions
diff --git a/external/unbound/libunbound/libunbound.c b/external/unbound/libunbound/libunbound.c new file mode 100644 index 000000000..78d31968a --- /dev/null +++ b/external/unbound/libunbound/libunbound.c @@ -0,0 +1,1227 @@ +/* + * unbound.c - unbound validating resolver public API implementation + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains functions to resolve DNS queries and + * validate the answers. Synchonously and asynchronously. + * + */ + +/* include the public api first, it should be able to stand alone */ +#include "libunbound/unbound.h" +#include "libunbound/unbound-event.h" +#include "config.h" +#include <ctype.h> +#include "libunbound/context.h" +#include "libunbound/libworker.h" +#include "util/locks.h" +#include "util/config_file.h" +#include "util/alloc.h" +#include "util/module.h" +#include "util/regional.h" +#include "util/log.h" +#include "util/random.h" +#include "util/net_help.h" +#include "util/tube.h" +#include "services/modstack.h" +#include "services/localzone.h" +#include "services/cache/infra.h" +#include "services/cache/rrset.h" +#include "ldns/sbuffer.h" +#ifdef HAVE_PTHREAD +#include <signal.h> +#endif + +#if defined(UB_ON_WINDOWS) && defined (HAVE_WINDOWS_H) +#include <windows.h> +#include <iphlpapi.h> +#endif /* UB_ON_WINDOWS */ + +/** create context functionality, but no pipes */ +static struct ub_ctx* ub_ctx_create_nopipe(void) +{ + struct ub_ctx* ctx; + unsigned int seed; +#ifdef USE_WINSOCK + int r; + WSADATA wsa_data; +#endif + + log_init(NULL, 0, NULL); /* logs to stderr */ + log_ident_set("libunbound"); +#ifdef USE_WINSOCK + if((r = WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0) { + log_err("could not init winsock. WSAStartup: %s", + wsa_strerror(r)); + return NULL; + } +#endif + verbosity = 0; /* errors only */ + checklock_start(); + ctx = (struct ub_ctx*)calloc(1, sizeof(*ctx)); + if(!ctx) { + errno = ENOMEM; + return NULL; + } + alloc_init(&ctx->superalloc, NULL, 0); + seed = (unsigned int)time(NULL) ^ (unsigned int)getpid(); + if(!(ctx->seed_rnd = ub_initstate(seed, NULL))) { + seed = 0; + ub_randfree(ctx->seed_rnd); + free(ctx); + errno = ENOMEM; + return NULL; + } + seed = 0; + lock_basic_init(&ctx->qqpipe_lock); + lock_basic_init(&ctx->rrpipe_lock); + lock_basic_init(&ctx->cfglock); + ctx->env = (struct module_env*)calloc(1, sizeof(*ctx->env)); + if(!ctx->env) { + ub_randfree(ctx->seed_rnd); + free(ctx); + errno = ENOMEM; + return NULL; + } + ctx->env->cfg = config_create_forlib(); + if(!ctx->env->cfg) { + free(ctx->env); + ub_randfree(ctx->seed_rnd); + free(ctx); + errno = ENOMEM; + return NULL; + } + ctx->env->alloc = &ctx->superalloc; + ctx->env->worker = NULL; + ctx->env->need_to_validate = 0; + modstack_init(&ctx->mods); + rbtree_init(&ctx->queries, &context_query_cmp); + return ctx; +} + +struct ub_ctx* +ub_ctx_create(void) +{ + struct ub_ctx* ctx = ub_ctx_create_nopipe(); + if(!ctx) + return NULL; + if((ctx->qq_pipe = tube_create()) == NULL) { + int e = errno; + ub_randfree(ctx->seed_rnd); + config_delete(ctx->env->cfg); + modstack_desetup(&ctx->mods, ctx->env); + free(ctx->env); + free(ctx); + errno = e; + return NULL; + } + if((ctx->rr_pipe = tube_create()) == NULL) { + int e = errno; + tube_delete(ctx->qq_pipe); + ub_randfree(ctx->seed_rnd); + config_delete(ctx->env->cfg); + modstack_desetup(&ctx->mods, ctx->env); + free(ctx->env); + free(ctx); + errno = e; + return NULL; + } + return ctx; +} + +struct ub_ctx* +ub_ctx_create_event(struct event_base* eb) +{ + struct ub_ctx* ctx = ub_ctx_create_nopipe(); + if(!ctx) + return NULL; + /* no pipes, but we have the locks to make sure everything works */ + ctx->created_bg = 0; + ctx->dothread = 1; /* the processing is in the same process, + makes ub_cancel and ub_ctx_delete do the right thing */ + ctx->event_base = eb; + return ctx; +} + +/** delete q */ +static void +delq(rbnode_t* n, void* ATTR_UNUSED(arg)) +{ + struct ctx_query* q = (struct ctx_query*)n; + context_query_delete(q); +} + +/** stop the bg thread */ +static void ub_stop_bg(struct ub_ctx* ctx) +{ + /* stop the bg thread */ + lock_basic_lock(&ctx->cfglock); + if(ctx->created_bg) { + uint8_t* msg; + uint32_t len; + uint32_t cmd = UB_LIBCMD_QUIT; + lock_basic_unlock(&ctx->cfglock); + lock_basic_lock(&ctx->qqpipe_lock); + (void)tube_write_msg(ctx->qq_pipe, (uint8_t*)&cmd, + (uint32_t)sizeof(cmd), 0); + lock_basic_unlock(&ctx->qqpipe_lock); + lock_basic_lock(&ctx->rrpipe_lock); + while(tube_read_msg(ctx->rr_pipe, &msg, &len, 0)) { + /* discard all results except a quit confirm */ + if(context_serial_getcmd(msg, len) == UB_LIBCMD_QUIT) { + free(msg); + break; + } + free(msg); + } + lock_basic_unlock(&ctx->rrpipe_lock); + + /* if bg worker is a thread, wait for it to exit, so that all + * resources are really gone. */ + lock_basic_lock(&ctx->cfglock); + if(ctx->dothread) { + lock_basic_unlock(&ctx->cfglock); + ub_thread_join(ctx->bg_tid); + } else { + lock_basic_unlock(&ctx->cfglock); + } + } + else { + lock_basic_unlock(&ctx->cfglock); + } +} + +void +ub_ctx_delete(struct ub_ctx* ctx) +{ + struct alloc_cache* a, *na; + int do_stop = 1; + if(!ctx) return; + + /* see if bg thread is created and if threads have been killed */ + /* no locks, because those may be held by terminated threads */ + /* for processes the read pipe is closed and we see that on read */ +#ifdef HAVE_PTHREAD + if(ctx->created_bg && ctx->dothread) { + if(pthread_kill(ctx->bg_tid, 0) == ESRCH) { + /* thread has been killed */ + do_stop = 0; + } + } +#endif /* HAVE_PTHREAD */ + if(do_stop) + ub_stop_bg(ctx); + libworker_delete_event(ctx->event_worker); + + modstack_desetup(&ctx->mods, ctx->env); + a = ctx->alloc_list; + while(a) { + na = a->super; + a->super = &ctx->superalloc; + alloc_clear(a); + free(a); + a = na; + } + local_zones_delete(ctx->local_zones); + lock_basic_destroy(&ctx->qqpipe_lock); + lock_basic_destroy(&ctx->rrpipe_lock); + lock_basic_destroy(&ctx->cfglock); + tube_delete(ctx->qq_pipe); + tube_delete(ctx->rr_pipe); + if(ctx->env) { + slabhash_delete(ctx->env->msg_cache); + rrset_cache_delete(ctx->env->rrset_cache); + infra_delete(ctx->env->infra_cache); + config_delete(ctx->env->cfg); + free(ctx->env); + } + ub_randfree(ctx->seed_rnd); + alloc_clear(&ctx->superalloc); + traverse_postorder(&ctx->queries, delq, NULL); + free(ctx); +#ifdef USE_WINSOCK + WSACleanup(); +#endif +} + +int +ub_ctx_set_option(struct ub_ctx* ctx, const char* opt, const char* val) +{ + lock_basic_lock(&ctx->cfglock); + if(ctx->finalized) { + lock_basic_unlock(&ctx->cfglock); + return UB_AFTERFINAL; + } + if(!config_set_option(ctx->env->cfg, opt, val)) { + lock_basic_unlock(&ctx->cfglock); + return UB_SYNTAX; + } + lock_basic_unlock(&ctx->cfglock); + return UB_NOERROR; +} + +int +ub_ctx_get_option(struct ub_ctx* ctx, const char* opt, char** str) +{ + int r; + lock_basic_lock(&ctx->cfglock); + r = config_get_option_collate(ctx->env->cfg, opt, str); + lock_basic_unlock(&ctx->cfglock); + if(r == 0) r = UB_NOERROR; + else if(r == 1) r = UB_SYNTAX; + else if(r == 2) r = UB_NOMEM; + return r; +} + +int +ub_ctx_config(struct ub_ctx* ctx, const char* fname) +{ + lock_basic_lock(&ctx->cfglock); + if(ctx->finalized) { + lock_basic_unlock(&ctx->cfglock); + return UB_AFTERFINAL; + } + if(!config_read(ctx->env->cfg, fname, NULL)) { + lock_basic_unlock(&ctx->cfglock); + return UB_SYNTAX; + } + lock_basic_unlock(&ctx->cfglock); + return UB_NOERROR; +} + +int +ub_ctx_add_ta(struct ub_ctx* ctx, const char* ta) +{ + char* dup = strdup(ta); + if(!dup) return UB_NOMEM; + lock_basic_lock(&ctx->cfglock); + if(ctx->finalized) { + lock_basic_unlock(&ctx->cfglock); + free(dup); + return UB_AFTERFINAL; + } + if(!cfg_strlist_insert(&ctx->env->cfg->trust_anchor_list, dup)) { + lock_basic_unlock(&ctx->cfglock); + free(dup); + return UB_NOMEM; + } + lock_basic_unlock(&ctx->cfglock); + return UB_NOERROR; +} + +int +ub_ctx_add_ta_file(struct ub_ctx* ctx, const char* fname) +{ + char* dup = strdup(fname); + if(!dup) return UB_NOMEM; + lock_basic_lock(&ctx->cfglock); + if(ctx->finalized) { + lock_basic_unlock(&ctx->cfglock); + free(dup); + return UB_AFTERFINAL; + } + if(!cfg_strlist_insert(&ctx->env->cfg->trust_anchor_file_list, dup)) { + lock_basic_unlock(&ctx->cfglock); + free(dup); + return UB_NOMEM; + } + lock_basic_unlock(&ctx->cfglock); + return UB_NOERROR; +} + +int +ub_ctx_trustedkeys(struct ub_ctx* ctx, const char* fname) +{ + char* dup = strdup(fname); + if(!dup) return UB_NOMEM; + lock_basic_lock(&ctx->cfglock); + if(ctx->finalized) { + lock_basic_unlock(&ctx->cfglock); + free(dup); + return UB_AFTERFINAL; + } + if(!cfg_strlist_insert(&ctx->env->cfg->trusted_keys_file_list, dup)) { + lock_basic_unlock(&ctx->cfglock); + free(dup); + return UB_NOMEM; + } + lock_basic_unlock(&ctx->cfglock); + return UB_NOERROR; +} + +int +ub_ctx_debuglevel(struct ub_ctx* ctx, int d) +{ + lock_basic_lock(&ctx->cfglock); + verbosity = d; + ctx->env->cfg->verbosity = d; + lock_basic_unlock(&ctx->cfglock); + return UB_NOERROR; +} + +int ub_ctx_debugout(struct ub_ctx* ctx, void* out) +{ + lock_basic_lock(&ctx->cfglock); + log_file((FILE*)out); + ctx->logfile_override = 1; + ctx->log_out = out; + lock_basic_unlock(&ctx->cfglock); + return UB_NOERROR; +} + +int +ub_ctx_async(struct ub_ctx* ctx, int dothread) +{ +#ifdef THREADS_DISABLED + if(dothread) /* cannot do threading */ + return UB_NOERROR; +#endif + lock_basic_lock(&ctx->cfglock); + if(ctx->finalized) { + lock_basic_unlock(&ctx->cfglock); + return UB_AFTERFINAL; + } + ctx->dothread = dothread; + lock_basic_unlock(&ctx->cfglock); + return UB_NOERROR; +} + +int +ub_poll(struct ub_ctx* ctx) +{ + /* no need to hold lock while testing for readability. */ + return tube_poll(ctx->rr_pipe); +} + +int +ub_fd(struct ub_ctx* ctx) +{ + return tube_read_fd(ctx->rr_pipe); +} + +/** process answer from bg worker */ +static int +process_answer_detail(struct ub_ctx* ctx, uint8_t* msg, uint32_t len, + ub_callback_t* cb, void** cbarg, int* err, + struct ub_result** res) +{ + struct ctx_query* q; + if(context_serial_getcmd(msg, len) != UB_LIBCMD_ANSWER) { + log_err("error: bad data from bg worker %d", + (int)context_serial_getcmd(msg, len)); + return 0; + } + + lock_basic_lock(&ctx->cfglock); + q = context_deserialize_answer(ctx, msg, len, err); + if(!q) { + lock_basic_unlock(&ctx->cfglock); + /* probably simply the lookup that failed, i.e. + * response returned before cancel was sent out, so noerror */ + return 1; + } + log_assert(q->async); + + /* grab cb while locked */ + if(q->cancelled) { + *cb = NULL; + *cbarg = NULL; + } else { + *cb = q->cb; + *cbarg = q->cb_arg; + } + if(*err) { + *res = NULL; + ub_resolve_free(q->res); + } else { + /* parse the message, extract rcode, fill result */ + sldns_buffer* buf = sldns_buffer_new(q->msg_len); + struct regional* region = regional_create(); + *res = q->res; + (*res)->rcode = LDNS_RCODE_SERVFAIL; + if(region && buf) { + sldns_buffer_clear(buf); + sldns_buffer_write(buf, q->msg, q->msg_len); + sldns_buffer_flip(buf); + libworker_enter_result(*res, buf, region, + q->msg_security); + } + (*res)->answer_packet = q->msg; + (*res)->answer_len = (int)q->msg_len; + q->msg = NULL; + sldns_buffer_free(buf); + regional_destroy(region); + } + q->res = NULL; + /* delete the q from list */ + (void)rbtree_delete(&ctx->queries, q->node.key); + ctx->num_async--; + context_query_delete(q); + lock_basic_unlock(&ctx->cfglock); + + if(*cb) return 2; + ub_resolve_free(*res); + return 1; +} + +/** process answer from bg worker */ +static int +process_answer(struct ub_ctx* ctx, uint8_t* msg, uint32_t len) +{ + int err; + ub_callback_t cb; + void* cbarg; + struct ub_result* res; + int r; + + r = process_answer_detail(ctx, msg, len, &cb, &cbarg, &err, &res); + + /* no locks held while calling callback, so that library is + * re-entrant. */ + if(r == 2) + (*cb)(cbarg, err, res); + + return r; +} + +int +ub_process(struct ub_ctx* ctx) +{ + int r; + uint8_t* msg; + uint32_t len; + while(1) { + msg = NULL; + lock_basic_lock(&ctx->rrpipe_lock); + r = tube_read_msg(ctx->rr_pipe, &msg, &len, 1); + lock_basic_unlock(&ctx->rrpipe_lock); + if(r == 0) + return UB_PIPE; + else if(r == -1) + break; + if(!process_answer(ctx, msg, len)) { + free(msg); + return UB_PIPE; + } + free(msg); + } + return UB_NOERROR; +} + +int +ub_wait(struct ub_ctx* ctx) +{ + int err; + ub_callback_t cb; + void* cbarg; + struct ub_result* res; + int r; + uint8_t* msg; + uint32_t len; + /* this is basically the same loop as _process(), but with changes. + * holds the rrpipe lock and waits with tube_wait */ + while(1) { + lock_basic_lock(&ctx->rrpipe_lock); + lock_basic_lock(&ctx->cfglock); + if(ctx->num_async == 0) { + lock_basic_unlock(&ctx->cfglock); + lock_basic_unlock(&ctx->rrpipe_lock); + break; + } + lock_basic_unlock(&ctx->cfglock); + + /* keep rrpipe locked, while + * o waiting for pipe readable + * o parsing message + * o possibly decrementing num_async + * do callback without lock + */ + r = tube_wait(ctx->rr_pipe); + if(r) { + r = tube_read_msg(ctx->rr_pipe, &msg, &len, 1); + if(r == 0) { + lock_basic_unlock(&ctx->rrpipe_lock); + return UB_PIPE; + } + if(r == -1) { + lock_basic_unlock(&ctx->rrpipe_lock); + continue; + } + r = process_answer_detail(ctx, msg, len, + &cb, &cbarg, &err, &res); + lock_basic_unlock(&ctx->rrpipe_lock); + free(msg); + if(r == 0) + return UB_PIPE; + if(r == 2) + (*cb)(cbarg, err, res); + } else { + lock_basic_unlock(&ctx->rrpipe_lock); + } + } + return UB_NOERROR; +} + +int +ub_resolve(struct ub_ctx* ctx, const char* name, int rrtype, + int rrclass, struct ub_result** result) +{ + struct ctx_query* q; + int r; + *result = NULL; + + lock_basic_lock(&ctx->cfglock); + if(!ctx->finalized) { + r = context_finalize(ctx); + if(r) { + lock_basic_unlock(&ctx->cfglock); + return r; + } + } + /* create new ctx_query and attempt to add to the list */ + lock_basic_unlock(&ctx->cfglock); + q = context_new(ctx, name, rrtype, rrclass, NULL, NULL); + if(!q) + return UB_NOMEM; + /* become a resolver thread for a bit */ + + r = libworker_fg(ctx, q); + if(r) { + lock_basic_lock(&ctx->cfglock); + (void)rbtree_delete(&ctx->queries, q->node.key); + context_query_delete(q); + lock_basic_unlock(&ctx->cfglock); + return r; + } + q->res->answer_packet = q->msg; + q->res->answer_len = (int)q->msg_len; + q->msg = NULL; + *result = q->res; + q->res = NULL; + + lock_basic_lock(&ctx->cfglock); + (void)rbtree_delete(&ctx->queries, q->node.key); + context_query_delete(q); + lock_basic_unlock(&ctx->cfglock); + return UB_NOERROR; +} + +int +ub_resolve_event(struct ub_ctx* ctx, const char* name, int rrtype, + int rrclass, void* mydata, ub_event_callback_t callback, int* async_id) +{ + struct ctx_query* q; + int r; + + if(async_id) + *async_id = 0; + lock_basic_lock(&ctx->cfglock); + if(!ctx->finalized) { + int r = context_finalize(ctx); + if(r) { + lock_basic_unlock(&ctx->cfglock); + return r; + } + } + lock_basic_unlock(&ctx->cfglock); + if(!ctx->event_worker) { + ctx->event_worker = libworker_create_event(ctx, + ctx->event_base); + if(!ctx->event_worker) { + return UB_INITFAIL; + } + } + + /* create new ctx_query and attempt to add to the list */ + q = context_new(ctx, name, rrtype, rrclass, (ub_callback_t)callback, + mydata); + if(!q) + return UB_NOMEM; + + /* attach to mesh */ + if((r=libworker_attach_mesh(ctx, q, async_id)) != 0) + return r; + return UB_NOERROR; +} + + +int +ub_resolve_async(struct ub_ctx* ctx, const char* name, int rrtype, + int rrclass, void* mydata, ub_callback_t callback, int* async_id) +{ + struct ctx_query* q; + uint8_t* msg = NULL; + uint32_t len = 0; + + if(async_id) + *async_id = 0; + lock_basic_lock(&ctx->cfglock); + if(!ctx->finalized) { + int r = context_finalize(ctx); + if(r) { + lock_basic_unlock(&ctx->cfglock); + return r; + } + } + if(!ctx->created_bg) { + int r; + ctx->created_bg = 1; + lock_basic_unlock(&ctx->cfglock); + r = libworker_bg(ctx); + if(r) { + lock_basic_lock(&ctx->cfglock); + ctx->created_bg = 0; + lock_basic_unlock(&ctx->cfglock); + return r; + } + } else { + lock_basic_unlock(&ctx->cfglock); + } + + /* create new ctx_query and attempt to add to the list */ + q = context_new(ctx, name, rrtype, rrclass, callback, mydata); + if(!q) + return UB_NOMEM; + + /* write over pipe to background worker */ + lock_basic_lock(&ctx->cfglock); + msg = context_serialize_new_query(q, &len); + if(!msg) { + (void)rbtree_delete(&ctx->queries, q->node.key); + ctx->num_async--; + context_query_delete(q); + lock_basic_unlock(&ctx->cfglock); + return UB_NOMEM; + } + if(async_id) + *async_id = q->querynum; + lock_basic_unlock(&ctx->cfglock); + + lock_basic_lock(&ctx->qqpipe_lock); + if(!tube_write_msg(ctx->qq_pipe, msg, len, 0)) { + lock_basic_unlock(&ctx->qqpipe_lock); + free(msg); + return UB_PIPE; + } + lock_basic_unlock(&ctx->qqpipe_lock); + free(msg); + return UB_NOERROR; +} + +int +ub_cancel(struct ub_ctx* ctx, int async_id) +{ + struct ctx_query* q; + uint8_t* msg = NULL; + uint32_t len = 0; + lock_basic_lock(&ctx->cfglock); + q = (struct ctx_query*)rbtree_search(&ctx->queries, &async_id); + if(!q || !q->async) { + /* it is not there, so nothing to do */ + lock_basic_unlock(&ctx->cfglock); + return UB_NOID; + } + log_assert(q->async); + q->cancelled = 1; + + /* delete it */ + if(!ctx->dothread) { /* if forked */ + (void)rbtree_delete(&ctx->queries, q->node.key); + ctx->num_async--; + msg = context_serialize_cancel(q, &len); + context_query_delete(q); + lock_basic_unlock(&ctx->cfglock); + if(!msg) { + return UB_NOMEM; + } + /* send cancel to background worker */ + lock_basic_lock(&ctx->qqpipe_lock); + if(!tube_write_msg(ctx->qq_pipe, msg, len, 0)) { + lock_basic_unlock(&ctx->qqpipe_lock); + free(msg); + return UB_PIPE; + } + lock_basic_unlock(&ctx->qqpipe_lock); + free(msg); + } else { + lock_basic_unlock(&ctx->cfglock); + } + return UB_NOERROR; +} + +void +ub_resolve_free(struct ub_result* result) +{ + char** p; + if(!result) return; + free(result->qname); + if(result->canonname != result->qname) + free(result->canonname); + if(result->data) + for(p = result->data; *p; p++) + free(*p); + free(result->data); + free(result->len); + free(result->answer_packet); + free(result->why_bogus); + free(result); +} + +const char* +ub_strerror(int err) +{ + switch(err) { + case UB_NOERROR: return "no error"; + case UB_SOCKET: return "socket io error"; + case UB_NOMEM: return "out of memory"; + case UB_SYNTAX: return "syntax error"; + case UB_SERVFAIL: return "server failure"; + case UB_FORKFAIL: return "could not fork"; + case UB_INITFAIL: return "initialization failure"; + case UB_AFTERFINAL: return "setting change after finalize"; + case UB_PIPE: return "error in pipe communication with async"; + case UB_READFILE: return "error reading file"; + case UB_NOID: return "error async_id does not exist"; + default: return "unknown error"; + } +} + +int +ub_ctx_set_fwd(struct ub_ctx* ctx, const char* addr) +{ + struct sockaddr_storage storage; + socklen_t stlen; + struct config_stub* s; + char* dupl; + lock_basic_lock(&ctx->cfglock); + if(ctx->finalized) { + lock_basic_unlock(&ctx->cfglock); + errno=EINVAL; + return UB_AFTERFINAL; + } + if(!addr) { + /* disable fwd mode - the root stub should be first. */ + if(ctx->env->cfg->forwards && + strcmp(ctx->env->cfg->forwards->name, ".") == 0) { + s = ctx->env->cfg->forwards; + ctx->env->cfg->forwards = s->next; + s->next = NULL; + config_delstubs(s); + } + lock_basic_unlock(&ctx->cfglock); + return UB_NOERROR; + } + lock_basic_unlock(&ctx->cfglock); + + /* check syntax for addr */ + if(!extstrtoaddr(addr, &storage, &stlen)) { + errno=EINVAL; + return UB_SYNTAX; + } + + /* it parses, add root stub in front of list */ + lock_basic_lock(&ctx->cfglock); + if(!ctx->env->cfg->forwards || + strcmp(ctx->env->cfg->forwards->name, ".") != 0) { + s = calloc(1, sizeof(*s)); + if(!s) { + lock_basic_unlock(&ctx->cfglock); + errno=ENOMEM; + return UB_NOMEM; + } + s->name = strdup("."); + if(!s->name) { + free(s); + lock_basic_unlock(&ctx->cfglock); + errno=ENOMEM; + return UB_NOMEM; + } + s->next = ctx->env->cfg->forwards; + ctx->env->cfg->forwards = s; + } else { + log_assert(ctx->env->cfg->forwards); + s = ctx->env->cfg->forwards; + } + dupl = strdup(addr); + if(!dupl) { + lock_basic_unlock(&ctx->cfglock); + errno=ENOMEM; + return UB_NOMEM; + } + if(!cfg_strlist_insert(&s->addrs, dupl)) { + free(dupl); + lock_basic_unlock(&ctx->cfglock); + errno=ENOMEM; + return UB_NOMEM; + } + lock_basic_unlock(&ctx->cfglock); + return UB_NOERROR; +} + +int +ub_ctx_resolvconf(struct ub_ctx* ctx, const char* fname) +{ + FILE* in; + int numserv = 0; + char buf[1024]; + char* parse, *addr; + int r; + + if(fname == NULL) { +#if !defined(UB_ON_WINDOWS) || !defined(HAVE_WINDOWS_H) + fname = "/etc/resolv.conf"; +#else + FIXED_INFO *info; + ULONG buflen = sizeof(*info); + IP_ADDR_STRING *ptr; + + info = (FIXED_INFO *) malloc(sizeof (FIXED_INFO)); + if (info == NULL) + return UB_READFILE; + + if (GetNetworkParams(info, &buflen) == ERROR_BUFFER_OVERFLOW) { + free(info); + info = (FIXED_INFO *) malloc(buflen); + if (info == NULL) + return UB_READFILE; + } + + if (GetNetworkParams(info, &buflen) == NO_ERROR) { + int retval=0; + ptr = &(info->DnsServerList); + while (ptr) { + numserv++; + if((retval=ub_ctx_set_fwd(ctx, + ptr->IpAddress.String)!=0)) { + free(info); + return retval; + } + ptr = ptr->Next; + } + free(info); + if (numserv==0) + return UB_READFILE; + return UB_NOERROR; + } + free(info); + return UB_READFILE; +#endif /* WINDOWS */ + } + in = fopen(fname, "r"); + if(!in) { + /* error in errno! perror(fname) */ + return UB_READFILE; + } + while(fgets(buf, (int)sizeof(buf), in)) { + buf[sizeof(buf)-1] = 0; + parse=buf; + while(*parse == ' ' || *parse == '\t') + parse++; + if(strncmp(parse, "nameserver", 10) == 0) { + numserv++; + parse += 10; /* skip 'nameserver' */ + /* skip whitespace */ + while(*parse == ' ' || *parse == '\t') + parse++; + addr = parse; + /* skip [0-9a-fA-F.:]*, i.e. IP4 and IP6 address */ + while(isxdigit(*parse) || *parse=='.' || *parse==':') + parse++; + /* terminate after the address, remove newline */ + *parse = 0; + + if((r = ub_ctx_set_fwd(ctx, addr)) != UB_NOERROR) { + fclose(in); + return r; + } + } + } + fclose(in); + if(numserv == 0) { + /* from resolv.conf(5) if none given, use localhost */ + return ub_ctx_set_fwd(ctx, "127.0.0.1"); + } + return UB_NOERROR; +} + +int +ub_ctx_hosts(struct ub_ctx* ctx, const char* fname) +{ + FILE* in; + char buf[1024], ldata[1024]; + char* parse, *addr, *name, *ins; + lock_basic_lock(&ctx->cfglock); + if(ctx->finalized) { + lock_basic_unlock(&ctx->cfglock); + errno=EINVAL; + return UB_AFTERFINAL; + } + lock_basic_unlock(&ctx->cfglock); + if(fname == NULL) { +#if defined(UB_ON_WINDOWS) && defined(HAVE_WINDOWS_H) + /* + * If this is Windows NT/XP/2K it's in + * %WINDIR%\system32\drivers\etc\hosts. + * If this is Windows 95/98/Me it's in %WINDIR%\hosts. + */ + name = getenv("WINDIR"); + if (name != NULL) { + int retval=0; + snprintf(buf, sizeof(buf), "%s%s", name, + "\\system32\\drivers\\etc\\hosts"); + if((retval=ub_ctx_hosts(ctx, buf)) !=0 ) { + snprintf(buf, sizeof(buf), "%s%s", name, + "\\hosts"); + retval=ub_ctx_hosts(ctx, buf); + } + free(name); + return retval; + } + return UB_READFILE; +#else + fname = "/etc/hosts"; +#endif /* WIN32 */ + } + in = fopen(fname, "r"); + if(!in) { + /* error in errno! perror(fname) */ + return UB_READFILE; + } + while(fgets(buf, (int)sizeof(buf), in)) { + buf[sizeof(buf)-1] = 0; + parse=buf; + while(*parse == ' ' || *parse == '\t') + parse++; + if(*parse == '#') + continue; /* skip comment */ + /* format: <addr> spaces <name> spaces <name> ... */ + addr = parse; + /* skip addr */ + while(isxdigit(*parse) || *parse == '.' || *parse == ':') + parse++; + if(*parse == '\n' || *parse == 0) + continue; + if(*parse == '%') + continue; /* ignore macOSX fe80::1%lo0 localhost */ + if(*parse != ' ' && *parse != '\t') { + /* must have whitespace after address */ + fclose(in); + errno=EINVAL; + return UB_SYNTAX; + } + *parse++ = 0; /* end delimiter for addr ... */ + /* go to names and add them */ + while(*parse) { + while(*parse == ' ' || *parse == '\t' || *parse=='\n') + parse++; + if(*parse == 0 || *parse == '#') + break; + /* skip name, allows (too) many printable characters */ + name = parse; + while('!' <= *parse && *parse <= '~') + parse++; + if(*parse) + *parse++ = 0; /* end delimiter for name */ + snprintf(ldata, sizeof(ldata), "%s %s %s", + name, str_is_ip6(addr)?"AAAA":"A", addr); + ins = strdup(ldata); + if(!ins) { + /* out of memory */ + fclose(in); + errno=ENOMEM; + return UB_NOMEM; + } + lock_basic_lock(&ctx->cfglock); + if(!cfg_strlist_insert(&ctx->env->cfg->local_data, + ins)) { + lock_basic_unlock(&ctx->cfglock); + fclose(in); + free(ins); + errno=ENOMEM; + return UB_NOMEM; + } + lock_basic_unlock(&ctx->cfglock); + } + } + fclose(in); + return UB_NOERROR; +} + +/** finalize the context, if not already finalized */ +static int ub_ctx_finalize(struct ub_ctx* ctx) +{ + int res = 0; + lock_basic_lock(&ctx->cfglock); + if (!ctx->finalized) { + res = context_finalize(ctx); + } + lock_basic_unlock(&ctx->cfglock); + return res; +} + +/* Print local zones and RR data */ +int ub_ctx_print_local_zones(struct ub_ctx* ctx) +{ + int res = ub_ctx_finalize(ctx); + if (res) return res; + + local_zones_print(ctx->local_zones); + + return UB_NOERROR; +} + +/* Add a new zone */ +int ub_ctx_zone_add(struct ub_ctx* ctx, const char *zone_name, + const char *zone_type) +{ + enum localzone_type t; + struct local_zone* z; + uint8_t* nm; + int nmlabs; + size_t nmlen; + + int res = ub_ctx_finalize(ctx); + if (res) return res; + + if(!local_zone_str2type(zone_type, &t)) { + return UB_SYNTAX; + } + + if(!parse_dname(zone_name, &nm, &nmlen, &nmlabs)) { + return UB_SYNTAX; + } + + lock_rw_wrlock(&ctx->local_zones->lock); + if((z=local_zones_find(ctx->local_zones, nm, nmlen, nmlabs, + LDNS_RR_CLASS_IN))) { + /* already present in tree */ + lock_rw_wrlock(&z->lock); + z->type = t; /* update type anyway */ + lock_rw_unlock(&z->lock); + lock_rw_unlock(&ctx->local_zones->lock); + free(nm); + return UB_NOERROR; + } + if(!local_zones_add_zone(ctx->local_zones, nm, nmlen, nmlabs, + LDNS_RR_CLASS_IN, t)) { + lock_rw_unlock(&ctx->local_zones->lock); + return UB_NOMEM; + } + lock_rw_unlock(&ctx->local_zones->lock); + return UB_NOERROR; +} + +/* Remove zone */ +int ub_ctx_zone_remove(struct ub_ctx* ctx, const char *zone_name) +{ + struct local_zone* z; + uint8_t* nm; + int nmlabs; + size_t nmlen; + + int res = ub_ctx_finalize(ctx); + if (res) return res; + + if(!parse_dname(zone_name, &nm, &nmlen, &nmlabs)) { + return UB_SYNTAX; + } + + lock_rw_wrlock(&ctx->local_zones->lock); + if((z=local_zones_find(ctx->local_zones, nm, nmlen, nmlabs, + LDNS_RR_CLASS_IN))) { + /* present in tree */ + local_zones_del_zone(ctx->local_zones, z); + } + lock_rw_unlock(&ctx->local_zones->lock); + free(nm); + return UB_NOERROR; +} + +/* Add new RR data */ +int ub_ctx_data_add(struct ub_ctx* ctx, const char *data) +{ + int res = ub_ctx_finalize(ctx); + if (res) return res; + + res = local_zones_add_RR(ctx->local_zones, data); + return (!res) ? UB_NOMEM : UB_NOERROR; +} + +/* Remove RR data */ +int ub_ctx_data_remove(struct ub_ctx* ctx, const char *data) +{ + uint8_t* nm; + int nmlabs; + size_t nmlen; + int res = ub_ctx_finalize(ctx); + if (res) return res; + + if(!parse_dname(data, &nm, &nmlen, &nmlabs)) + return UB_SYNTAX; + + local_zones_del_data(ctx->local_zones, nm, nmlen, nmlabs, + LDNS_RR_CLASS_IN); + + free(nm); + return UB_NOERROR; +} + +const char* ub_version(void) +{ + return PACKAGE_VERSION; +} + +int +ub_ctx_set_event(struct ub_ctx* ctx, struct event_base* base) { + if (!ctx || !ctx->event_base || !base) { + return UB_INITFAIL; + } + if (ctx->event_base == base) { + /* already set */ + return UB_NOERROR; + } + + lock_basic_lock(&ctx->cfglock); + /* destroy the current worker - safe to pass in NULL */ + libworker_delete_event(ctx->event_worker); + ctx->event_worker = NULL; + ctx->event_base = base; + ctx->created_bg = 0; + ctx->dothread = 1; + lock_basic_unlock(&ctx->cfglock); + return UB_NOERROR; +} |