/** * Copyright (C) 2024 Graham Leggett * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /* * redwax_unbound - DNS server access routines. * */ #include #include "config.h" #include "redwax-tool.h" #include "redwax_unbound.h" #include "redwax_util.h" #if HAVE_UNBOUND_H #include "apr_portable.h" #include "apr_uri.h" #include #include module unbound_module; typedef struct { struct ub_ctx *ctx; apr_pollfd_t socket_read; redwax_pollfd_t read_ctx; apr_pollfd_t socket_write; redwax_pollfd_t write_ctx; apr_status_t status; int socket_read_work; int dns_work; int dns_server_set; int dns_trust_anchor_set; } unbound_config_t; static apr_status_t cleanup_ub(void *dummy) { struct ub_ctx *ctx = dummy; ub_ctx_delete(ctx); return APR_SUCCESS; } static apr_status_t redwax_unbound_initialise(redwax_tool_t *r) { unbound_config_t *config; config = apr_pcalloc(r->pool, sizeof(unbound_config_t)); redwax_set_module_config(r->per_module, &unbound_module, config); /* create an unbound context */ config->ctx = ub_ctx_create(); if(!config->ctx) { redwax_print_error(r, "Could not create unbound context\n"); return APR_EINIT; } apr_pool_cleanup_register(r->pool, config->ctx, cleanup_ub, apr_pool_cleanup_null); return OK; } static apr_status_t redwax_unbound_set_dns_server(redwax_tool_t *r, const char *arg) { unbound_config_t *config = redwax_get_module_config(r->per_module, &unbound_module); int rv; if ((rv = ub_ctx_set_fwd(config->ctx, arg)) != 0) { redwax_print_error(r, "Could not assign DNS server '%s': %s\n", arg, ub_strerror(rv)); return APR_EINIT; } config->dns_server_set++; return APR_SUCCESS; } static apr_status_t redwax_unbound_set_dns_trust_anchor(redwax_tool_t *r, const char *arg) { unbound_config_t *config = redwax_get_module_config(r->per_module, &unbound_module); int rv; if ((rv = ub_ctx_add_ta_file(config->ctx, arg)) != 0) { redwax_print_error(r, "Could not read DNS trust anchor '%s': %s (%s)\n", arg, ub_strerror(rv), strerror(errno)); return APR_EINIT; } config->dns_trust_anchor_set++; return APR_SUCCESS; } static apr_status_t redwax_unbound_set_tlsa(redwax_tool_t *r, const char *arg) { apr_uri_t uri; apr_status_t status; if (r->dane_basename) { redwax_print_error(r, "URI '%s': filter-verify-tlsa can only be specified once\n", arg); return APR_EINVAL; } status = apr_uri_parse(r->pool, arg, &uri); if (APR_SUCCESS != status) { redwax_print_error(r, "Could not parse URI '%s': %pm\n", arg, &status); return status; } if (!uri.hostname) { redwax_print_error(r, "URI '%s': hostname missing\n", arg); return APR_EINVAL; } else { r->dane_basename = uri.hostname; } if (!strcmp(uri.scheme, "tcp") || !strcmp(uri.scheme, "udp")) { if (uri.port) { const char **qname; const char *tlsa = apr_psprintf(r->pool, "_%d._%s.%s", uri.port, uri.scheme, uri.hostname); redwax_dns_t *dns = apr_array_push(r->dns_requests); dns->r = r; dns->basename = uri.hostname; dns->qname = tlsa; dns->rrtype = 52 /* TYPE TLSA */; dns->rrclass = 1 /* CLASS IN (internet) */; dns->cb = rt_run_process_tlsa; qname = apr_array_push(r->tlsa_qnames); *qname = tlsa; return APR_SUCCESS; } else { redwax_print_error(r, "URI '%s': port missing\n", arg); return APR_EINVAL; } } if (uri.scheme) { int found = 0; setservent(1); struct servent *ent; while ((ent = getservent())) { if (!strcmp(uri.scheme, ent->s_name)) { const char **qname; const char *tlsa = apr_psprintf(r->pool, "_%d._%s.%s", uri.port ? uri.port : ntohs(ent->s_port), ent->s_proto, uri.hostname); redwax_dns_t *dns = apr_array_push(r->dns_requests); dns->r = r; dns->basename = uri.hostname; dns->qname = tlsa; dns->rrtype = 52 /* TYPE TLSA */; dns->rrclass = 1 /* CLASS IN (internet) */; dns->cb = rt_run_process_tlsa; qname = apr_array_push(r->tlsa_qnames); *qname = tlsa; found = 1; } } endservent(); if (!found) { redwax_print_error(r, "URI '%s': protocol '%s' not found\n", arg, uri.scheme); return APR_EINVAL; } else { return APR_SUCCESS; } } else { redwax_print_error(r, "URI '%s': scheme missing\n", arg); return APR_EINVAL; } } static apr_status_t redwax_unbound_set_smimea(redwax_tool_t *r, const char *arg) { return APR_ENOTIMPL; } static apr_status_t redwax_unbound_filter_read_cb(redwax_tool_t *r, void *baton, apr_pollfd_t *descriptor) { unbound_config_t *config = redwax_get_module_config(r->per_module, &unbound_module); apr_status_t status = APR_SUCCESS; int rv; /* ack readcb work */ r->poll_work--; /* ack socket read work */ config->socket_read_work--; rv = ub_process(config->ctx); if (rv) { redwax_print_error(r, "Could not process DNS: %s (%s)\n", ub_strerror(rv), strerror(errno)); return APR_EGENERAL; } if (config->status) { return config->status; } if (config->dns_work) { status = apr_pollcb_add(r->poll, &config->socket_read); if (APR_SUCCESS != status) { redwax_print_error(r, "Could not add read descriptor to poll: %pm\n", &status); return status; } /* req readcb work */ r->poll_work++; /* req socket read work */ config->socket_read_work++; } return status; } void redwax_unbound_filter_result_cb(void *ctx, int rv, struct ub_result* result) { redwax_dns_t *dns = ctx; redwax_tool_t *r = dns->r; unbound_config_t *config = redwax_get_module_config(r->per_module, &unbound_module); /* ack dns lookup work */ config->dns_work--; if (rv) { redwax_print_error(r, "Could not resolve: %s\n", ub_strerror(rv)); return; } dns->canonname = result->canonname; dns->havedata = result->havedata; dns->nxdomain = result->nxdomain; dns->secure = result->secure; dns->bogus = result->bogus; dns->why_bogus = result->why_bogus; if (result->data) { int i, count; for (count = 0; result->data[count]; count++); dns->rdata = apr_array_make(r->pool, count, sizeof(redwax_rdata_t)); for (i = 0; i < count; i++) { redwax_rdata_t *rdata = apr_array_push(dns->rdata); rdata->data = (unsigned char *)result->data[i]; rdata->len = result->len[i]; rt_run_process_dns(r, dns, rdata); } } config->status = dns->cb(r, dns); ub_resolve_free(result); } static apr_status_t redwax_unbound_filter_write_cb(redwax_tool_t *r, void *baton, apr_pollfd_t *descriptor) { unbound_config_t *config = redwax_get_module_config(r->per_module, &unbound_module); redwax_dns_t *dns = apr_array_pop(r->dns_requests); apr_status_t status = APR_SUCCESS; int rv; /* ack writecb work */ r->poll_work--; rv = ub_resolve_async(config->ctx, dns->qname, dns->rrtype, dns->rrclass, (void *)dns, redwax_unbound_filter_result_cb, NULL); if (rv) { redwax_print_error(r, "Could not resolve '%s': %s (%s)\n", dns->qname, ub_strerror(rv), strerror(errno)); return APR_EGENERAL; } /* req dns lookup work */ config->dns_work++; if (!config->socket_read_work) { status = apr_pollcb_add(r->poll, &config->socket_read); if (APR_SUCCESS != status) { redwax_print_error(r, "Could not add read descriptor to poll: %pm\n", &status); return status; } /* req readcb work */ r->poll_work++; /* req socket read work */ config->socket_read_work++; } if (r->dns_requests->nelts) { status = apr_pollcb_add(r->poll, &config->socket_write); if (APR_SUCCESS != status) { redwax_print_error(r, "Could not add write descriptor to poll: %pm\n", &status); return status; } /* req writecb work */ r->poll_work++; } return status; } static apr_status_t redwax_unbound_filter_poll(redwax_tool_t *r) { unbound_config_t *config; apr_socket_t *s = NULL; apr_os_sock_t fd; apr_status_t status; if (!r->dns_requests->nelts) { return OK; } config = redwax_get_module_config(r->per_module, &unbound_module); /* * Apply defaults if no explicit values are set. */ /* read /etc/resolv.conf for DNS proxy settings (from DHCP) */ if (!config->dns_server_set) { int rv; if ((rv = ub_ctx_resolvconf(config->ctx, "/etc/resolv.conf"))) { redwax_print_error(r, "Could not read /etc/resolv.conf: %s (%s)\n", ub_strerror(rv), strerror(errno)); return APR_EINIT; } } /* read public keys for DNSSEC verification */ if (!config->dns_trust_anchor_set) { int rv; if ((rv = ub_ctx_add_ta_file(config->ctx, REDWAX_DEFAULT_ROOT_KEY))) { redwax_print_error(r, "Could not read " REDWAX_DEFAULT_ROOT_KEY ": %s (%s)\n", ub_strerror(rv), strerror(errno)); return APR_EINIT; } } fd = ub_fd(config->ctx); if (-1 == fd) { redwax_print_error(r, "Could not obtain unbound fd: %s\n", strerror(errno)); return APR_EGENERAL; } status = apr_os_sock_put(&s, &fd, r->pool); if (APR_SUCCESS != status) { redwax_print_error(r, "Could not convert unbound fd: %pm\n", &status); return status; } /* set up the read callback */ config->read_ctx.cb = redwax_unbound_filter_read_cb; /* set up read descriptor */ config->socket_read.desc_type = APR_POLL_SOCKET; config->socket_read.reqevents = APR_POLLIN; config->socket_read.desc.s = s; config->socket_read.client_data = &config->read_ctx; /* set up the write callback */ config->write_ctx.cb = redwax_unbound_filter_write_cb; /* set up write descriptor */ config->socket_write.desc_type = APR_POLL_SOCKET; config->socket_write.reqevents = APR_POLLOUT; config->socket_write.desc.s = s; config->socket_write.client_data = &config->write_ctx; status = apr_pollcb_add(r->poll, &config->socket_write); if (APR_SUCCESS != status) { redwax_print_error(r, "Could not add descriptor to poll: %pm\n", &status); return status; } /* req writecb work */ r->poll_work++; return APR_SUCCESS; } void redwax_add_default_unbound_hooks() { rt_hook_initialise(redwax_unbound_initialise, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_set_dns_server(redwax_unbound_set_dns_server, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_set_dns_trust_anchor(redwax_unbound_set_dns_trust_anchor, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_set_tlsa(redwax_unbound_set_tlsa, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_set_smimea(redwax_unbound_set_smimea, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_filter_poll(redwax_unbound_filter_poll, NULL, NULL, APR_HOOK_MIDDLE); } #else void redwax_add_default_unbound_hooks() { } #endif REDWAX_DECLARE_MODULE(unbound) = { STANDARD_MODULE_STUFF, redwax_add_default_unbound_hooks /* register hooks */ };