Use *= 2 instead of += $self for doubling
[freeradius.git] / src / modules / rlm_unbound / rlm_unbound.c
1 /*
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.
5  *
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.
10  *
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
14  */
15
16 /**
17  * $Id$
18  * @file rlm_unbound.c
19  * @brief DNS services via libunbound.
20  *
21  * @copyright 2013 The FreeRADIUS server project
22  * @copyright 2013 Brian S. Julin <bjulin@clarku.edu>
23  */
24 RCSID("$Id$")
25
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <freeradius-devel/log.h>
29 #include <fcntl.h>
30 #include <unbound.h>
31
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. */
35
36         char const      *name;
37         char const      *xlat_a_name;
38         char const      *xlat_aaaa_name;
39         char const      *xlat_ptr_name;
40
41         uint32_t        timeout;
42
43         char const      *filename;
44
45         int             log_fd;
46         FILE            *log_stream;
47
48         int             log_pipe[2];
49         FILE            *log_pipe_stream[2];
50         bool            log_pipe_in_use;
51 } rlm_unbound_t;
52
53 /*
54  *      A mapping of configuration file names to internal variables.
55  */
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 */
60 };
61
62 /*
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.
66  */
67 static void link_ubres(void* my_arg, int err, struct ub_result* result)
68 {
69         struct ub_result **ubres = (struct ub_result **)my_arg;
70
71         /*
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.
75          */
76         if (err) {
77                 ERROR("rlm_unbound: %s", ub_strerror(err));
78                 *ubres = NULL;
79         } else {
80                 *ubres = result;
81         }
82 }
83
84 /*
85  *      Convert labels as found in a DNS result to a NULL terminated string.
86  *
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.
92  */
93 static int rrlabels_tostr(char *out, char *rr, size_t left)
94 {
95         int offset = 0;
96
97         /*
98          * TODO: verify that unbound results (will) always use this label
99          * format, and review the specs on this label format for nuances.
100          */
101
102         if (!left) {
103                 return -1;
104         }
105         if (left > 253) {
106                 left = 253; /* DNS length limit */
107         }
108         /* As a whole this should be "NULL terminated" by the 0-length label */
109         if (strnlen(rr, left) > left - 1) {
110                 return -1;
111         }
112
113         /* It will fit, but does it it look well formed? */
114         while (1) {
115                 size_t count;
116
117                 count = *((unsigned char *)(rr + offset));
118                 if (!count) break;
119
120                 offset++;
121                 if (count > 63 || strlen(rr + offset) < count) {
122                         return -1;
123                 }
124                 offset += count;
125         }
126
127         /* Data is valid and fits.  Copy it. */
128         offset = 0;
129         while (1) {
130                 int count;
131
132                 count = *((unsigned char *)(rr));
133                 if (!count) break;
134
135                 if (offset) {
136                         *(out + offset) = '.';
137                         offset++;
138                 }
139
140                 rr++;
141                 memcpy(out + offset, rr, count);
142                 rr += count;
143                 offset += count;
144         }
145
146         *(out + offset) = '\0';
147         return offset;
148 }
149
150 static int ub_common_wait(rlm_unbound_t *inst, REQUEST *request, char const *tag, struct ub_result **ub, int async_id)
151 {
152         useconds_t iv, waited;
153
154         iv = inst->timeout > 64 ? 64000 : inst->timeout * 1000;
155         ub_process(inst->ub);
156
157         for (waited = 0; (void*)*ub == (void *)inst; waited += iv, iv *= 2) {
158
159                 if (waited + iv > (useconds_t)inst->timeout * 1000) {
160                         usleep(inst->timeout * 1000 - waited);
161                         ub_process(inst->ub);
162                         break;
163                 }
164
165                 usleep(iv);
166
167                 /* Check if already handled by event loop */
168                 if ((void *)*ub != (void *)inst) {
169                         break;
170                 }
171
172                 /* In case we are running single threaded */
173                 ub_process(inst->ub);
174         }
175
176         if ((void *)*ub == (void *)inst) {
177                 int res;
178
179                 RDEBUG("rlm_unbound (%s): DNS took too long", tag);
180
181                 res = ub_cancel(inst->ub, async_id);
182                 if (res) {
183                         REDEBUG("rlm_unbound (%s): ub_cancel: %s",
184                                 tag, ub_strerror(res));
185                 }
186                 return -1;
187         }
188
189         return 0;
190 }
191
192 static int ub_common_fail(REQUEST *request, char const *tag, struct ub_result *ub)
193 {
194         if (ub->bogus) {
195                 RWDEBUG("rlm_unbound (%s): Bogus DNS response", tag);
196                 return -1;
197         }
198
199         if (ub->nxdomain) {
200                 RDEBUG("rlm_unbound (%s): NXDOMAIN", tag);
201                 return -1;
202         }
203
204         if (!ub->havedata) {
205                 RDEBUG("rlm_unbound (%s): empty result", tag);
206                 return -1;
207         }
208
209         return 0;
210 }
211
212 static ssize_t xlat_a(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
213 {
214         rlm_unbound_t *inst = instance;
215         struct ub_result **ubres;
216         int async_id;
217         char *fmt2; /* For const warnings.  Keep till new libunbound ships. */
218
219         /* This has to be on the heap, because threads. */
220         ubres = talloc(inst, struct ub_result *);
221
222         /* Used and thus impossible value from heap to designate incomplete */
223         *ubres = (void *)instance;
224
225         fmt2 = talloc_typed_strdup(inst, fmt);
226         ub_resolve_async(inst->ub, fmt2, 1, 1, ubres, link_ubres, &async_id);
227         talloc_free(fmt2);
228
229         if (ub_common_wait(inst, request, inst->xlat_a_name, ubres, async_id)) {
230                 goto error0;
231         }
232
233         if (*ubres) {
234                 if (ub_common_fail(request, inst->xlat_a_name, *ubres)) {
235                         goto error1;
236                 }
237
238                 if (!inet_ntop(AF_INET, (*ubres)->data[0], out, freespace)) {
239                         goto error1;
240                 };
241
242                 ub_resolve_free(*ubres);
243                 talloc_free(ubres);
244                 return strlen(out);
245         }
246
247         RWDEBUG("rlm_unbound (%s): no result", inst->xlat_a_name);
248
249  error1:
250         ub_resolve_free(*ubres); /* Handles NULL gracefully */
251
252  error0:
253         talloc_free(ubres);
254         return -1;
255 }
256
257 static ssize_t xlat_aaaa(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
258 {
259         rlm_unbound_t *inst = instance;
260         struct ub_result **ubres;
261         int async_id;
262         char *fmt2; /* For const warnings.  Keep till new libunbound ships. */
263
264         /* This has to be on the heap, because threads. */
265         ubres = talloc(inst, struct ub_result *);
266
267         /* Used and thus impossible value from heap to designate incomplete */
268         *ubres = (void *)instance;
269
270         fmt2 = talloc_typed_strdup(inst, fmt);
271         ub_resolve_async(inst->ub, fmt2, 28, 1, ubres, link_ubres, &async_id);
272         talloc_free(fmt2);
273
274         if (ub_common_wait(inst, request, inst->xlat_aaaa_name, ubres, async_id)) {
275                 goto error0;
276         }
277
278         if (*ubres) {
279                 if (ub_common_fail(request, inst->xlat_aaaa_name, *ubres)) {
280                         goto error1;
281                 }
282                 if (!inet_ntop(AF_INET6, (*ubres)->data[0], out, freespace)) {
283                         goto error1;
284                 };
285                 ub_resolve_free(*ubres);
286                 talloc_free(ubres);
287                 return strlen(out);
288         }
289
290         RWDEBUG("rlm_unbound (%s): no result", inst->xlat_aaaa_name);
291
292 error1:
293         ub_resolve_free(*ubres); /* Handles NULL gracefully */
294
295 error0:
296         talloc_free(ubres);
297         return -1;
298 }
299
300 static ssize_t xlat_ptr(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
301 {
302         rlm_unbound_t *inst = instance;
303         struct ub_result **ubres;
304         int async_id;
305         char *fmt2; /* For const warnings.  Keep till new libunbound ships. */
306
307         /* This has to be on the heap, because threads. */
308         ubres = talloc(inst, struct ub_result *);
309
310         /* Used and thus impossible value from heap to designate incomplete */
311         *ubres = (void *)instance;
312
313         fmt2 = talloc_typed_strdup(inst, fmt);
314         ub_resolve_async(inst->ub, fmt2, 12, 1, ubres, link_ubres, &async_id);
315         talloc_free(fmt2);
316
317         if (ub_common_wait(inst, request, inst->xlat_ptr_name,
318                            ubres, async_id)) {
319                 goto error0;
320         }
321
322         if (*ubres) {
323                 if (ub_common_fail(request, inst->xlat_ptr_name, *ubres)) {
324                         goto error1;
325                 }
326                 if (rrlabels_tostr(out, (*ubres)->data[0], freespace) < 0) {
327                         goto error1;
328                 }
329                 ub_resolve_free(*ubres);
330                 talloc_free(ubres);
331                 return strlen(out);
332         }
333
334         RWDEBUG("rlm_unbound (%s): no result", inst->xlat_ptr_name);
335
336 error1:
337         ub_resolve_free(*ubres);  /* Handles NULL gracefully */
338
339 error0:
340         talloc_free(ubres);
341         return -1;
342 }
343
344 /*
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.
350  */
351 static void ub_fd_handler(UNUSED fr_event_list_t *el, UNUSED int sock, void *ctx)
352 {
353         rlm_unbound_t *inst = ctx;
354         int err;
355
356         err = ub_process(inst->ub);
357         if (err) {
358                 ERROR("rlm_unbound (%s) async ub_process: %s",
359                       inst->name, ub_strerror(err));
360         }
361 }
362
363 #ifndef HAVE_PTHREAD_H
364
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)
367 {
368         rlm_unbound_t *inst = ctx;
369         char line[1024];
370
371         /*
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.
382          */
383         while (fgets(line, 1024, inst->log_pipe_stream[0])) {
384                 DEBUG("rlm_unbound (%s): %s", inst->name, line);
385         }
386 }
387
388 #endif
389
390 static int mod_instantiate(CONF_SECTION *conf, void *instance)
391 {
392         rlm_unbound_t *inst = instance;
393         int res;
394         char *optval;
395
396         log_dst_t log_dst;
397         int log_level;
398         int log_fd = -1;
399
400         char k[64]; /* To silence const warns until newer unbound in distros */
401
402         inst->el = radius_event_list_corral(EVENT_CORRAL_AUX);
403         inst->log_pipe_stream[0] = NULL;
404         inst->log_pipe_stream[1] = NULL;
405         inst->log_fd = -1;
406         inst->log_pipe_in_use = false;
407
408         inst->name = cf_section_name2(conf);
409         if (!inst->name) {
410                 inst->name = cf_section_name1(conf);
411         }
412
413         if (inst->timeout > 10000) {
414                 ERROR("rlm_unbound (%s): timeout must be 0 to 10000", inst->name);
415                 return -1;
416         }
417
418         inst->ub = ub_ctx_create();
419         if (!inst->ub) {
420                 ERROR("rlm_unbound (%s): ub_ctx_create failed", inst->name);
421                 return -1;
422         }
423
424 #ifdef HAVE_PTHREAD_H
425         /*
426          *      Note unbound threads WILL happen with -s option, if it matters.
427          *      We cannot tell from here whether that option is in effect.
428          */
429         res = ub_ctx_async(inst->ub, 1);
430 #else
431         /*
432          *      Uses forked subprocesses instead.
433          */
434         res = ub_ctx_async(inst->ub, 0);
435 #endif
436
437         if (res) goto error;
438
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. */
442         log_level = 0;
443
444         if (debug_flag > 0) {
445                 log_level = debug_flag;
446
447         } else if (main_config.debug_level > 0) {
448                 log_level = main_config.debug_level;
449         }
450
451         switch (log_level) {
452         /* TODO: This will need some tweaking */
453         case 0:
454         case 1:
455                 break;
456
457         case 2:
458                 log_level = 1;
459                 break;
460
461         case 3:
462         case 4:
463                 log_level = 2; /* mid-to-heavy levels of output */
464                 break;
465
466         case 5:
467         case 6:
468         case 7:
469         case 8:
470                 log_level = 3; /* Pretty crazy amounts of output */
471                 break;
472
473         default:
474                 log_level = 4; /* Insane amounts of output including crypts */
475                 break;
476         }
477
478         res = ub_ctx_debuglevel(inst->ub, log_level);
479         if (res) goto error;
480
481         switch(default_log.dst) {
482         case L_DST_STDOUT:
483                 if (!debug_flag) {
484                         log_dst = L_DST_NULL;
485                         break;
486                 }
487                 log_dst = L_DST_STDOUT;
488                 log_fd = dup(STDOUT_FILENO);
489                 break;
490
491         case L_DST_STDERR:
492                 if (!debug_flag) {
493                         log_dst = L_DST_NULL;
494                         break;
495                 }
496                 log_dst = L_DST_STDOUT;
497                 log_fd = dup(STDERR_FILENO);
498                 break;
499
500         case L_DST_FILES:
501                 if (main_config.log_file) {
502                         char *log_file;
503
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);
508                         if (res) {
509                                 goto error;
510                         }
511                         log_dst = L_DST_FILES;
512                         break;
513                 }
514                 /* FALL-THROUGH */
515
516         case L_DST_NULL:
517                 log_dst = L_DST_NULL;
518                 break;
519
520         default:
521                 log_dst = L_DST_SYSLOG;
522                 break;
523         }
524
525         /* Now load the config file, which can override gleaned settings. */
526         {
527                 char *file;
528
529                 memcpy(&file, &inst->filename, sizeof(file));
530                 res = ub_ctx_config(inst->ub, file);
531                 if (res) goto error;
532         }
533
534         /*
535          *      Check if the config file tried to use syslog.  Unbound
536          *      does not share syslog gracefully.
537          */
538         strcpy(k, "use-syslog");
539         res = ub_ctx_get_option(inst->ub, k, &optval);
540         if (res || !optval) goto error;
541
542         if (!strcmp(optval, "yes")) {
543                 char v[3];
544
545                 free(optval);
546
547                 WARN("rlm_unbound (%s): Overriding syslog settings.", inst->name);
548                 strcpy(k, "use-syslog:");
549                 strcpy(v, "no");
550                 res = ub_ctx_set_option(inst->ub, k, v);
551                 if (res) goto error;
552
553                 if (log_dst == L_DST_FILES) {
554                         char *log_file;
555
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);
561                         if (res) goto error;
562                 }
563
564         } else {
565                 if (optval) free(optval);
566                 strcpy(k, "logfile");
567
568                 res = ub_ctx_get_option(inst->ub, k, &optval);
569                 if (res) goto error;
570
571                 if (optval && strlen(optval)) {
572                         log_dst = L_DST_FILES;
573
574                 } else if (!debug_flag) {
575                         log_dst = L_DST_NULL;
576                 }
577
578                 if (optval) free(optval);
579         }
580
581         switch (log_dst) {
582         case L_DST_STDOUT:
583                 /*
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.
586                  */
587                 if (log_fd == -1) {
588                         ERROR("rlm_unbound (%s): Could not dup fd", inst->name);
589                         goto error_nores;
590                 }
591
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);
595                         goto error_nores;
596                 }
597
598                 res = ub_ctx_debugout(inst->ub, inst->log_stream);
599                 if (res) goto error;
600                 break;
601
602         case L_DST_FILES:
603                 /* We gave libunbound a filename.  It is on its own now. */
604                 break;
605
606         case L_DST_NULL:
607                 /* We tell libunbound not to log at all. */
608                 res = ub_ctx_debugout(inst->ub, NULL);
609                 if (res) goto error;
610                 break;
611
612         case L_DST_SYSLOG:
613 #ifdef HAVE_PTHREAD_H
614                 /*
615                  *  Currently this wreaks havoc when running threaded, so just
616                  *  turn logging off until that gets figured out.
617                  */
618                 res = ub_ctx_debugout(inst->ub, NULL);
619                 if (res) goto error;
620                 break;
621 #else
622                 /*
623                  *  We need to create a pipe, because libunbound does not
624                  *  share syslog nicely.  Or the core added some new logsink.
625                  */
626                 if (pipe(inst->log_pipe)) {
627                 error_pipe:
628                         ERROR("rlm_unbound (%s): Error setting up log pipes", inst->name);
629                         goto error_nores;
630                 }
631
632                 if ((fcntl(inst->log_pipe[0], F_SETFL, O_NONBLOCK) < 0) ||
633                     (fcntl(inst->log_pipe[0], F_SETFD, FD_CLOEXEC) < 0)) {
634                         goto error_pipe;
635                 }
636
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) {
639                         goto error_pipe;
640                 }
641
642                 inst->log_pipe_stream[0] = fdopen(inst->log_pipe[0], "r");
643                 inst->log_pipe_stream[1] = fdopen(inst->log_pipe[1], "w");
644
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]);
648                         }
649
650                         if (!inst->log_pipe_stream[0]) {
651                                 close(inst->log_pipe[0]);
652                         }
653                         ERROR("rlm_unbound (%s): Error setting up log stream", inst->name);
654                         goto error_nores;
655                 }
656
657                 res = ub_ctx_debugout(inst->ub, inst->log_pipe_stream[1]);
658                 if (res) goto error;
659
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);
662                         goto error_nores;
663                 }
664
665                 inst->log_pipe_in_use = true;
666 #endif
667         default:
668                 break;
669         }
670
671         /*
672          *  Now we need to finalize the context.
673          *
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.
678          */
679         strcpy(k, "notar33lsite.foo123.nottld A 127.0.0.1");
680         ub_ctx_data_remove(inst->ub, k);
681
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);
686                         inst->log_fd = -1;
687                         goto error_nores;
688                 }
689
690         }
691
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));
695
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);
703                 goto error_nores;
704         }
705         return 0;
706
707  error:
708         ERROR("rlm_unbound (%s): %s", inst->name, ub_strerror(res));
709
710  error_nores:
711         if (log_fd > -1) close(log_fd);
712
713         return -1;
714 }
715
716 static int mod_detach(UNUSED void *instance)
717 {
718         rlm_unbound_t *inst = instance;
719
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);
723
724         if (inst->log_fd >= 0) {
725                 fr_event_fd_delete(inst->el, 0, inst->log_fd);
726                 if (inst->ub) {
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
731                          */
732 #if 0
733                         ub_ctx_delete(inst->ub);
734 #endif
735                 }
736         }
737
738         if (inst->log_pipe_stream[1]) {
739                 fclose(inst->log_pipe_stream[1]);
740         }
741
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]);
745                 }
746                 fclose(inst->log_pipe_stream[0]);
747         }
748
749         if (inst->log_stream) {
750                 fclose(inst->log_stream);
751         }
752
753         return 0;
754 }
755
756 module_t rlm_unbound = {
757         RLM_MODULE_INIT,
758         "unbound",
759         RLM_TYPE_THREAD_SAFE,           /* type */
760         sizeof(rlm_unbound_t),
761         module_config,
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 },
766 };