2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2 if the
4 * License as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @brief DNS services via libunbound.
21 * @copyright 2013 The FreeRADIUS server project
22 * @copyright 2013 Brian S. Julin <bjulin@clarku.edu>
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <freeradius-devel/log.h>
32 typedef struct rlm_unbound_t {
33 struct ub_ctx *ub; /* This must come first. Do not move */
34 fr_event_list_t *el; /* This must come second. Do not move. */
37 char const *xlat_a_name;
38 char const *xlat_aaaa_name;
39 char const *xlat_ptr_name;
49 FILE *log_pipe_stream[2];
54 * A mapping of configuration file names to internal variables.
56 static const CONF_PARSER module_config[] = {
57 { "filename", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_REQUIRED, rlm_unbound_t, filename), "${modconfdir}/unbound/default.conf" },
58 { "timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_unbound_t, timeout), "3000" },
59 { NULL, -1, 0, NULL, NULL } /* end the list */
63 * Callback sent to libunbound for xlat functions. Simply links the
64 * new ub_result via a pointer that has been allocated from the heap.
65 * This pointer has been pre-initialized to a magic value.
67 static void link_ubres(void* my_arg, int err, struct ub_result* result)
69 struct ub_result **ubres = (struct ub_result **)my_arg;
72 * Note that while result will be NULL on error, we are explicit
73 * here because that is actually a behavior that is suboptimal
74 * and only documented in the examples. It could change.
77 ERROR("rlm_unbound: %s", ub_strerror(err));
85 * Convert labels as found in a DNS result to a NULL terminated string.
87 * Result is written to memory pointed to by "out" but no result will
88 * be written unless it and its terminating NULL character fit in "left"
89 * bytes. Returns the number of bytes written excluding the terminating
90 * NULL, or -1 if nothing was written because it would not fit or due
91 * to a violation in the labels format.
93 static int rrlabels_tostr(char *out, char *rr, size_t left)
98 * TODO: verify that unbound results (will) always use this label
99 * format, and review the specs on this label format for nuances.
106 left = 253; /* DNS length limit */
108 /* As a whole this should be "NULL terminated" by the 0-length label */
109 if (strnlen(rr, left) > left - 1) {
113 /* It will fit, but does it it look well formed? */
117 count = *((unsigned char *)(rr + offset));
121 if (count > 63 || strlen(rr + offset) < count) {
127 /* Data is valid and fits. Copy it. */
132 count = *((unsigned char *)(rr));
136 *(out + offset) = '.';
141 memcpy(out + offset, rr, count);
146 *(out + offset) = '\0';
150 static int ub_common_wait(rlm_unbound_t *inst, REQUEST *request, char const *tag, struct ub_result **ub, int async_id)
152 useconds_t iv, waited;
154 iv = inst->timeout > 64 ? 64000 : inst->timeout * 1000;
155 ub_process(inst->ub);
157 for (waited = 0; (void*)*ub == (void *)inst; waited += iv, iv *= 2) {
159 if (waited + iv > (useconds_t)inst->timeout * 1000) {
160 usleep(inst->timeout * 1000 - waited);
161 ub_process(inst->ub);
167 /* Check if already handled by event loop */
168 if ((void *)*ub != (void *)inst) {
172 /* In case we are running single threaded */
173 ub_process(inst->ub);
176 if ((void *)*ub == (void *)inst) {
179 RDEBUG("rlm_unbound (%s): DNS took too long", tag);
181 res = ub_cancel(inst->ub, async_id);
183 REDEBUG("rlm_unbound (%s): ub_cancel: %s",
184 tag, ub_strerror(res));
192 static int ub_common_fail(REQUEST *request, char const *tag, struct ub_result *ub)
195 RWDEBUG("rlm_unbound (%s): Bogus DNS response", tag);
200 RDEBUG("rlm_unbound (%s): NXDOMAIN", tag);
205 RDEBUG("rlm_unbound (%s): empty result", tag);
212 static ssize_t xlat_a(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
214 rlm_unbound_t *inst = instance;
215 struct ub_result **ubres;
217 char *fmt2; /* For const warnings. Keep till new libunbound ships. */
219 /* This has to be on the heap, because threads. */
220 ubres = talloc(inst, struct ub_result *);
222 /* Used and thus impossible value from heap to designate incomplete */
223 *ubres = (void *)instance;
225 fmt2 = talloc_typed_strdup(inst, fmt);
226 ub_resolve_async(inst->ub, fmt2, 1, 1, ubres, link_ubres, &async_id);
229 if (ub_common_wait(inst, request, inst->xlat_a_name, ubres, async_id)) {
234 if (ub_common_fail(request, inst->xlat_a_name, *ubres)) {
238 if (!inet_ntop(AF_INET, (*ubres)->data[0], out, freespace)) {
242 ub_resolve_free(*ubres);
247 RWDEBUG("rlm_unbound (%s): no result", inst->xlat_a_name);
250 ub_resolve_free(*ubres); /* Handles NULL gracefully */
257 static ssize_t xlat_aaaa(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
259 rlm_unbound_t *inst = instance;
260 struct ub_result **ubres;
262 char *fmt2; /* For const warnings. Keep till new libunbound ships. */
264 /* This has to be on the heap, because threads. */
265 ubres = talloc(inst, struct ub_result *);
267 /* Used and thus impossible value from heap to designate incomplete */
268 *ubres = (void *)instance;
270 fmt2 = talloc_typed_strdup(inst, fmt);
271 ub_resolve_async(inst->ub, fmt2, 28, 1, ubres, link_ubres, &async_id);
274 if (ub_common_wait(inst, request, inst->xlat_aaaa_name, ubres, async_id)) {
279 if (ub_common_fail(request, inst->xlat_aaaa_name, *ubres)) {
282 if (!inet_ntop(AF_INET6, (*ubres)->data[0], out, freespace)) {
285 ub_resolve_free(*ubres);
290 RWDEBUG("rlm_unbound (%s): no result", inst->xlat_aaaa_name);
293 ub_resolve_free(*ubres); /* Handles NULL gracefully */
300 static ssize_t xlat_ptr(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
302 rlm_unbound_t *inst = instance;
303 struct ub_result **ubres;
305 char *fmt2; /* For const warnings. Keep till new libunbound ships. */
307 /* This has to be on the heap, because threads. */
308 ubres = talloc(inst, struct ub_result *);
310 /* Used and thus impossible value from heap to designate incomplete */
311 *ubres = (void *)instance;
313 fmt2 = talloc_typed_strdup(inst, fmt);
314 ub_resolve_async(inst->ub, fmt2, 12, 1, ubres, link_ubres, &async_id);
317 if (ub_common_wait(inst, request, inst->xlat_ptr_name,
323 if (ub_common_fail(request, inst->xlat_ptr_name, *ubres)) {
326 if (rrlabels_tostr(out, (*ubres)->data[0], freespace) < 0) {
329 ub_resolve_free(*ubres);
334 RWDEBUG("rlm_unbound (%s): no result", inst->xlat_ptr_name);
337 ub_resolve_free(*ubres); /* Handles NULL gracefully */
345 * Even when run in asyncronous mode, callbacks sent to libunbound still
346 * must be run in an application-side thread (via ub_process.) This is
347 * probably to keep the API usage consistent across threaded and forked
348 * embedded client modes. This callback function lets an event loop call
349 * ub_process when the instance's file descriptor becomes ready.
351 static void ub_fd_handler(UNUSED fr_event_list_t *el, UNUSED int sock, void *ctx)
353 rlm_unbound_t *inst = ctx;
356 err = ub_process(inst->ub);
358 ERROR("rlm_unbound (%s) async ub_process: %s",
359 inst->name, ub_strerror(err));
363 #ifndef HAVE_PTHREAD_H
365 /* If we have to use a pipe to redirect logging, this does the work. */
366 static void log_spew(UNUSED fr_event_list_t *el, UNUSED int sock, void *ctx)
368 rlm_unbound_t *inst = ctx;
372 * This works for pipes from processes, but not from threads
373 * right now. The latter is hinky and will require some fancy
374 * blocking/nonblocking trickery which is not figured out yet,
375 * since selecting on a pipe from a thread in the same process
376 * seems to behave differently. It will likely preclude the use
377 * of fgets and streams. Left for now since some unbound logging
378 * infrastructure is still global across multiple contexts. Maybe
379 * we can get unbound folks to provide a ub_ctx_debugout_async that
380 * takes a function hook instead to just bypass the piping when
381 * used in threaded mode.
383 while (fgets(line, 1024, inst->log_pipe_stream[0])) {
384 DEBUG("rlm_unbound (%s): %s", inst->name, line);
390 static int mod_instantiate(CONF_SECTION *conf, void *instance)
392 rlm_unbound_t *inst = instance;
400 char k[64]; /* To silence const warns until newer unbound in distros */
402 inst->el = radius_event_list_corral(EVENT_CORRAL_AUX);
403 inst->log_pipe_stream[0] = NULL;
404 inst->log_pipe_stream[1] = NULL;
406 inst->log_pipe_in_use = false;
408 inst->name = cf_section_name2(conf);
410 inst->name = cf_section_name1(conf);
413 if (inst->timeout > 10000) {
414 ERROR("rlm_unbound (%s): timeout must be 0 to 10000", inst->name);
418 inst->ub = ub_ctx_create();
420 ERROR("rlm_unbound (%s): ub_ctx_create failed", inst->name);
424 #ifdef HAVE_PTHREAD_H
426 * Note unbound threads WILL happen with -s option, if it matters.
427 * We cannot tell from here whether that option is in effect.
429 res = ub_ctx_async(inst->ub, 1);
432 * Uses forked subprocesses instead.
434 res = ub_ctx_async(inst->ub, 0);
439 /* Glean some default settings to match the main server. */
440 /* TODO: debug_level can be changed at runtime. */
441 /* TODO: log until fork when stdout or stderr and !debug_flag. */
444 if (debug_flag > 0) {
445 log_level = debug_flag;
447 } else if (main_config.debug_level > 0) {
448 log_level = main_config.debug_level;
452 /* TODO: This will need some tweaking */
463 log_level = 2; /* mid-to-heavy levels of output */
470 log_level = 3; /* Pretty crazy amounts of output */
474 log_level = 4; /* Insane amounts of output including crypts */
478 res = ub_ctx_debuglevel(inst->ub, log_level);
481 switch(default_log.dst) {
484 log_dst = L_DST_NULL;
487 log_dst = L_DST_STDOUT;
488 log_fd = dup(STDOUT_FILENO);
493 log_dst = L_DST_NULL;
496 log_dst = L_DST_STDOUT;
497 log_fd = dup(STDERR_FILENO);
501 if (main_config.log_file) {
504 strcpy(k, "logfile:");
505 /* 3rd argument isn't const'd in libunbounds API */
506 memcpy(&log_file, &main_config.log_file, sizeof(log_file));
507 res = ub_ctx_set_option(inst->ub, k, log_file);
511 log_dst = L_DST_FILES;
517 log_dst = L_DST_NULL;
521 log_dst = L_DST_SYSLOG;
525 /* Now load the config file, which can override gleaned settings. */
529 memcpy(&file, &inst->filename, sizeof(file));
530 res = ub_ctx_config(inst->ub, file);
535 * Check if the config file tried to use syslog. Unbound
536 * does not share syslog gracefully.
538 strcpy(k, "use-syslog");
539 res = ub_ctx_get_option(inst->ub, k, &optval);
540 if (res || !optval) goto error;
542 if (!strcmp(optval, "yes")) {
547 WARN("rlm_unbound (%s): Overriding syslog settings.", inst->name);
548 strcpy(k, "use-syslog:");
550 res = ub_ctx_set_option(inst->ub, k, v);
553 if (log_dst == L_DST_FILES) {
556 /* Reinstate the log file name JIC */
557 strcpy(k, "logfile:");
558 /* 3rd argument isn't const'd in libunbounds API */
559 memcpy(&log_file, &main_config.log_file, sizeof(log_file));
560 res = ub_ctx_set_option(inst->ub, k, log_file);
565 if (optval) free(optval);
566 strcpy(k, "logfile");
568 res = ub_ctx_get_option(inst->ub, k, &optval);
571 if (optval && strlen(optval)) {
572 log_dst = L_DST_FILES;
574 } else if (!debug_flag) {
575 log_dst = L_DST_NULL;
578 if (optval) free(optval);
584 * We have an fd to log to. And we've already attempted to
585 * dup it so libunbound doesn't close it on us.
588 ERROR("rlm_unbound (%s): Could not dup fd", inst->name);
592 inst->log_stream = fdopen(log_fd, "w");
593 if (!inst->log_stream) {
594 ERROR("rlm_unbound (%s): error setting up log stream", inst->name);
598 res = ub_ctx_debugout(inst->ub, inst->log_stream);
603 /* We gave libunbound a filename. It is on its own now. */
607 /* We tell libunbound not to log at all. */
608 res = ub_ctx_debugout(inst->ub, NULL);
613 #ifdef HAVE_PTHREAD_H
615 * Currently this wreaks havoc when running threaded, so just
616 * turn logging off until that gets figured out.
618 res = ub_ctx_debugout(inst->ub, NULL);
623 * We need to create a pipe, because libunbound does not
624 * share syslog nicely. Or the core added some new logsink.
626 if (pipe(inst->log_pipe)) {
628 ERROR("rlm_unbound (%s): Error setting up log pipes", inst->name);
632 if ((fcntl(inst->log_pipe[0], F_SETFL, O_NONBLOCK) < 0) ||
633 (fcntl(inst->log_pipe[0], F_SETFD, FD_CLOEXEC) < 0)) {
637 /* Opaque to us when this can be closed, so we do not. */
638 if (fcntl(inst->log_pipe[1], F_SETFL, O_NONBLOCK) < 0) {
642 inst->log_pipe_stream[0] = fdopen(inst->log_pipe[0], "r");
643 inst->log_pipe_stream[1] = fdopen(inst->log_pipe[1], "w");
645 if (!inst->log_pipe_stream[0] || !inst->log_pipe_stream[1]) {
646 if (!inst->log_pipe_stream[1]) {
647 close(inst->log_pipe[1]);
650 if (!inst->log_pipe_stream[0]) {
651 close(inst->log_pipe[0]);
653 ERROR("rlm_unbound (%s): Error setting up log stream", inst->name);
657 res = ub_ctx_debugout(inst->ub, inst->log_pipe_stream[1]);
660 if (!fr_event_fd_insert(inst->el, 0, inst->log_pipe[0], log_spew, inst)) {
661 ERROR("rlm_unbound (%s): could not insert log fd", inst->name);
665 inst->log_pipe_in_use = true;
672 * Now we need to finalize the context.
674 * There's no clean API to just finalize the context made public
675 * in libunbound. But we can trick it by trying to delete data
676 * which as it happens fails quickly and quietly even though the
677 * data did not exist.
679 strcpy(k, "notar33lsite.foo123.nottld A 127.0.0.1");
680 ub_ctx_data_remove(inst->ub, k);
682 inst->log_fd = ub_fd(inst->ub);
683 if (inst->log_fd >= 0) {
684 if (!fr_event_fd_insert(inst->el, 0, inst->log_fd, ub_fd_handler, inst)) {
685 ERROR("rlm_unbound (%s): could not insert async fd", inst->name);
692 MEM(inst->xlat_a_name = talloc_typed_asprintf(inst, "%s-a", inst->name));
693 MEM(inst->xlat_aaaa_name = talloc_typed_asprintf(inst, "%s-aaaa", inst->name));
694 MEM(inst->xlat_ptr_name = talloc_typed_asprintf(inst, "%s-ptr", inst->name));
696 if (xlat_register(inst->xlat_a_name, xlat_a, NULL, inst) ||
697 xlat_register(inst->xlat_aaaa_name, xlat_aaaa, NULL, inst) ||
698 xlat_register(inst->xlat_ptr_name, xlat_ptr, NULL, inst)) {
699 ERROR("rlm_unbound (%s): Failed registering xlats", inst->name);
700 xlat_unregister(inst->xlat_a_name, xlat_a, inst);
701 xlat_unregister(inst->xlat_aaaa_name, xlat_aaaa, inst);
702 xlat_unregister(inst->xlat_ptr_name, xlat_ptr, inst);
708 ERROR("rlm_unbound (%s): %s", inst->name, ub_strerror(res));
711 if (log_fd > -1) close(log_fd);
716 static int mod_detach(UNUSED void *instance)
718 rlm_unbound_t *inst = instance;
720 xlat_unregister(inst->xlat_a_name, xlat_a, inst);
721 xlat_unregister(inst->xlat_aaaa_name, xlat_aaaa, inst);
722 xlat_unregister(inst->xlat_ptr_name, xlat_ptr, inst);
724 if (inst->log_fd >= 0) {
725 fr_event_fd_delete(inst->el, 0, inst->log_fd);
727 ub_process(inst->ub);
728 /* This can hang/leave zombies currently
729 * see upstream bug #519
730 * ...so expect valgrind to complain with -m
733 ub_ctx_delete(inst->ub);
738 if (inst->log_pipe_stream[1]) {
739 fclose(inst->log_pipe_stream[1]);
742 if (inst->log_pipe_stream[0]) {
743 if (inst->log_pipe_in_use) {
744 fr_event_fd_delete(inst->el, 0, inst->log_pipe[0]);
746 fclose(inst->log_pipe_stream[0]);
749 if (inst->log_stream) {
750 fclose(inst->log_stream);
756 module_t rlm_unbound = {
759 RLM_TYPE_THREAD_SAFE, /* type */
760 sizeof(rlm_unbound_t),
762 mod_instantiate, /* instantiation */
763 mod_detach, /* detach */
764 /* This module does not directly interact with requests */
765 { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },