Use macro for terminating CONF_PARSER arrays
[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 as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /**
18  * $Id$
19  * @file rlm_unbound.c
20  * @brief DNS services via libunbound.
21  *
22  * @copyright 2013 The FreeRADIUS server project
23  * @copyright 2013 Brian S. Julin <bjulin@clarku.edu>
24  */
25 RCSID("$Id$")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29 #include <freeradius-devel/log.h>
30 #include <fcntl.h>
31 #include <unbound.h>
32
33 typedef struct rlm_unbound_t {
34         struct ub_ctx   *ub;   /* This must come first.  Do not move */
35         fr_event_list_t *el; /* This must come second.  Do not move. */
36
37         char const      *name;
38         char const      *xlat_a_name;
39         char const      *xlat_aaaa_name;
40         char const      *xlat_ptr_name;
41
42         uint32_t        timeout;
43
44         char const      *filename;
45
46         int             log_fd;
47         FILE            *log_stream;
48
49         int             log_pipe[2];
50         FILE            *log_pipe_stream[2];
51         bool            log_pipe_in_use;
52 } rlm_unbound_t;
53
54 /*
55  *      A mapping of configuration file names to internal variables.
56  */
57 static const CONF_PARSER module_config[] = {
58         { "filename", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_REQUIRED, rlm_unbound_t, filename), "${modconfdir}/unbound/default.conf"  },
59         { "timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_unbound_t, timeout), "3000" },
60         CONF_PARSER_TERMINATOR
61 };
62
63 /*
64  *      Callback sent to libunbound for xlat functions.  Simply links the
65  *      new ub_result via a pointer that has been allocated from the heap.
66  *      This pointer has been pre-initialized to a magic value.
67  */
68 static void link_ubres(void* my_arg, int err, struct ub_result* result)
69 {
70         struct ub_result **ubres = (struct ub_result **)my_arg;
71
72         /*
73          *      Note that while result will be NULL on error, we are explicit
74          *      here because that is actually a behavior that is suboptimal
75          *      and only documented in the examples.  It could change.
76          */
77         if (err) {
78                 ERROR("rlm_unbound: %s", ub_strerror(err));
79                 *ubres = NULL;
80         } else {
81                 *ubres = result;
82         }
83 }
84
85 /*
86  *      Convert labels as found in a DNS result to a NULL terminated string.
87  *
88  *      Result is written to memory pointed to by "out" but no result will
89  *      be written unless it and its terminating NULL character fit in "left"
90  *      bytes.  Returns the number of bytes written excluding the terminating
91  *      NULL, or -1 if nothing was written because it would not fit or due
92  *      to a violation in the labels format.
93  */
94 static int rrlabels_tostr(char *out, char *rr, size_t left)
95 {
96         int offset = 0;
97
98         /*
99          * TODO: verify that unbound results (will) always use this label
100          * format, and review the specs on this label format for nuances.
101          */
102
103         if (!left) {
104                 return -1;
105         }
106         if (left > 253) {
107                 left = 253; /* DNS length limit */
108         }
109         /* As a whole this should be "NULL terminated" by the 0-length label */
110         if (strnlen(rr, left) > left - 1) {
111                 return -1;
112         }
113
114         /* It will fit, but does it it look well formed? */
115         while (1) {
116                 size_t count;
117
118                 count = *((unsigned char *)(rr + offset));
119                 if (!count) break;
120
121                 offset++;
122                 if (count > 63 || strlen(rr + offset) < count) {
123                         return -1;
124                 }
125                 offset += count;
126         }
127
128         /* Data is valid and fits.  Copy it. */
129         offset = 0;
130         while (1) {
131                 int count;
132
133                 count = *((unsigned char *)(rr));
134                 if (!count) break;
135
136                 if (offset) {
137                         *(out + offset) = '.';
138                         offset++;
139                 }
140
141                 rr++;
142                 memcpy(out + offset, rr, count);
143                 rr += count;
144                 offset += count;
145         }
146
147         *(out + offset) = '\0';
148         return offset;
149 }
150
151 static int ub_common_wait(rlm_unbound_t *inst, REQUEST *request, char const *tag, struct ub_result **ub, int async_id)
152 {
153         useconds_t iv, waited;
154
155         iv = inst->timeout > 64 ? 64000 : inst->timeout * 1000;
156         ub_process(inst->ub);
157
158         for (waited = 0; (void*)*ub == (void *)inst; waited += iv, iv *= 2) {
159
160                 if (waited + iv > (useconds_t)inst->timeout * 1000) {
161                         usleep(inst->timeout * 1000 - waited);
162                         ub_process(inst->ub);
163                         break;
164                 }
165
166                 usleep(iv);
167
168                 /* Check if already handled by event loop */
169                 if ((void *)*ub != (void *)inst) {
170                         break;
171                 }
172
173                 /* In case we are running single threaded */
174                 ub_process(inst->ub);
175         }
176
177         if ((void *)*ub == (void *)inst) {
178                 int res;
179
180                 RDEBUG("rlm_unbound (%s): DNS took too long", tag);
181
182                 res = ub_cancel(inst->ub, async_id);
183                 if (res) {
184                         REDEBUG("rlm_unbound (%s): ub_cancel: %s",
185                                 tag, ub_strerror(res));
186                 }
187                 return -1;
188         }
189
190         return 0;
191 }
192
193 static int ub_common_fail(REQUEST *request, char const *tag, struct ub_result *ub)
194 {
195         if (ub->bogus) {
196                 RWDEBUG("rlm_unbound (%s): Bogus DNS response", tag);
197                 return -1;
198         }
199
200         if (ub->nxdomain) {
201                 RDEBUG("rlm_unbound (%s): NXDOMAIN", tag);
202                 return -1;
203         }
204
205         if (!ub->havedata) {
206                 RDEBUG("rlm_unbound (%s): empty result", tag);
207                 return -1;
208         }
209
210         return 0;
211 }
212
213 static ssize_t xlat_a(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
214 {
215         rlm_unbound_t *inst = instance;
216         struct ub_result **ubres;
217         int async_id;
218         char *fmt2; /* For const warnings.  Keep till new libunbound ships. */
219
220         /* This has to be on the heap, because threads. */
221         ubres = talloc(inst, struct ub_result *);
222
223         /* Used and thus impossible value from heap to designate incomplete */
224         *ubres = (void *)instance;
225
226         fmt2 = talloc_typed_strdup(inst, fmt);
227         ub_resolve_async(inst->ub, fmt2, 1, 1, ubres, link_ubres, &async_id);
228         talloc_free(fmt2);
229
230         if (ub_common_wait(inst, request, inst->xlat_a_name, ubres, async_id)) {
231                 goto error0;
232         }
233
234         if (*ubres) {
235                 if (ub_common_fail(request, inst->xlat_a_name, *ubres)) {
236                         goto error1;
237                 }
238
239                 if (!inet_ntop(AF_INET, (*ubres)->data[0], out, freespace)) {
240                         goto error1;
241                 };
242
243                 ub_resolve_free(*ubres);
244                 talloc_free(ubres);
245                 return strlen(out);
246         }
247
248         RWDEBUG("rlm_unbound (%s): no result", inst->xlat_a_name);
249
250  error1:
251         ub_resolve_free(*ubres); /* Handles NULL gracefully */
252
253  error0:
254         talloc_free(ubres);
255         return -1;
256 }
257
258 static ssize_t xlat_aaaa(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
259 {
260         rlm_unbound_t *inst = instance;
261         struct ub_result **ubres;
262         int async_id;
263         char *fmt2; /* For const warnings.  Keep till new libunbound ships. */
264
265         /* This has to be on the heap, because threads. */
266         ubres = talloc(inst, struct ub_result *);
267
268         /* Used and thus impossible value from heap to designate incomplete */
269         *ubres = (void *)instance;
270
271         fmt2 = talloc_typed_strdup(inst, fmt);
272         ub_resolve_async(inst->ub, fmt2, 28, 1, ubres, link_ubres, &async_id);
273         talloc_free(fmt2);
274
275         if (ub_common_wait(inst, request, inst->xlat_aaaa_name, ubres, async_id)) {
276                 goto error0;
277         }
278
279         if (*ubres) {
280                 if (ub_common_fail(request, inst->xlat_aaaa_name, *ubres)) {
281                         goto error1;
282                 }
283                 if (!inet_ntop(AF_INET6, (*ubres)->data[0], out, freespace)) {
284                         goto error1;
285                 };
286                 ub_resolve_free(*ubres);
287                 talloc_free(ubres);
288                 return strlen(out);
289         }
290
291         RWDEBUG("rlm_unbound (%s): no result", inst->xlat_aaaa_name);
292
293 error1:
294         ub_resolve_free(*ubres); /* Handles NULL gracefully */
295
296 error0:
297         talloc_free(ubres);
298         return -1;
299 }
300
301 static ssize_t xlat_ptr(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
302 {
303         rlm_unbound_t *inst = instance;
304         struct ub_result **ubres;
305         int async_id;
306         char *fmt2; /* For const warnings.  Keep till new libunbound ships. */
307
308         /* This has to be on the heap, because threads. */
309         ubres = talloc(inst, struct ub_result *);
310
311         /* Used and thus impossible value from heap to designate incomplete */
312         *ubres = (void *)instance;
313
314         fmt2 = talloc_typed_strdup(inst, fmt);
315         ub_resolve_async(inst->ub, fmt2, 12, 1, ubres, link_ubres, &async_id);
316         talloc_free(fmt2);
317
318         if (ub_common_wait(inst, request, inst->xlat_ptr_name,
319                            ubres, async_id)) {
320                 goto error0;
321         }
322
323         if (*ubres) {
324                 if (ub_common_fail(request, inst->xlat_ptr_name, *ubres)) {
325                         goto error1;
326                 }
327                 if (rrlabels_tostr(out, (*ubres)->data[0], freespace) < 0) {
328                         goto error1;
329                 }
330                 ub_resolve_free(*ubres);
331                 talloc_free(ubres);
332                 return strlen(out);
333         }
334
335         RWDEBUG("rlm_unbound (%s): no result", inst->xlat_ptr_name);
336
337 error1:
338         ub_resolve_free(*ubres);  /* Handles NULL gracefully */
339
340 error0:
341         talloc_free(ubres);
342         return -1;
343 }
344
345 /*
346  *      Even when run in asyncronous mode, callbacks sent to libunbound still
347  *      must be run in an application-side thread (via ub_process.)  This is
348  *      probably to keep the API usage consistent across threaded and forked
349  *      embedded client modes.  This callback function lets an event loop call
350  *      ub_process when the instance's file descriptor becomes ready.
351  */
352 static void ub_fd_handler(UNUSED fr_event_list_t *el, UNUSED int sock, void *ctx)
353 {
354         rlm_unbound_t *inst = ctx;
355         int err;
356
357         err = ub_process(inst->ub);
358         if (err) {
359                 ERROR("rlm_unbound (%s) async ub_process: %s",
360                       inst->name, ub_strerror(err));
361         }
362 }
363
364 #ifndef HAVE_PTHREAD_H
365
366 /* If we have to use a pipe to redirect logging, this does the work. */
367 static void log_spew(UNUSED fr_event_list_t *el, UNUSED int sock, void *ctx)
368 {
369         rlm_unbound_t *inst = ctx;
370         char line[1024];
371
372         /*
373          *  This works for pipes from processes, but not from threads
374          *  right now.  The latter is hinky and will require some fancy
375          *  blocking/nonblocking trickery which is not figured out yet,
376          *  since selecting on a pipe from a thread in the same process
377          *  seems to behave differently.  It will likely preclude the use
378          *  of fgets and streams.  Left for now since some unbound logging
379          *  infrastructure is still global across multiple contexts.  Maybe
380          *  we can get unbound folks to provide a ub_ctx_debugout_async that
381          *  takes a function hook instead to just bypass the piping when
382          *  used in threaded mode.
383          */
384         while (fgets(line, 1024, inst->log_pipe_stream[0])) {
385                 DEBUG("rlm_unbound (%s): %s", inst->name, line);
386         }
387 }
388
389 #endif
390
391 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
392 {
393         rlm_unbound_t *inst = instance;
394
395         inst->name = cf_section_name2(conf);
396         if (!inst->name) {
397                 inst->name = cf_section_name1(conf);
398         }
399
400         if (inst->timeout > 10000) {
401                 cf_log_err_cs(conf, "timeout must be 0 to 10000");
402                 return -1;
403         }
404
405         MEM(inst->xlat_a_name = talloc_typed_asprintf(inst, "%s-a", inst->name));
406         MEM(inst->xlat_aaaa_name = talloc_typed_asprintf(inst, "%s-aaaa", inst->name));
407         MEM(inst->xlat_ptr_name = talloc_typed_asprintf(inst, "%s-ptr", inst->name));
408
409         if (xlat_register(inst->xlat_a_name, xlat_a, NULL, inst) ||
410             xlat_register(inst->xlat_aaaa_name, xlat_aaaa, NULL, inst) ||
411             xlat_register(inst->xlat_ptr_name, xlat_ptr, NULL, inst)) {
412                 cf_log_err_cs(conf, "Failed registering xlats");
413                 return -1;
414         }
415
416         return 0;
417 }
418
419 static int mod_instantiate(CONF_SECTION *conf, void *instance)
420 {
421         rlm_unbound_t *inst = instance;
422         int res;
423         char *optval;
424
425         log_dst_t log_dst;
426         int log_level;
427         int log_fd = -1;
428
429         char k[64]; /* To silence const warns until newer unbound in distros */
430
431         inst->el = radius_event_list_corral(EVENT_CORRAL_AUX);
432         inst->log_pipe_stream[0] = NULL;
433         inst->log_pipe_stream[1] = NULL;
434         inst->log_fd = -1;
435         inst->log_pipe_in_use = false;
436
437         inst->ub = ub_ctx_create();
438         if (!inst->ub) {
439                 cf_log_err_cs(conf, "ub_ctx_create failed");
440                 return -1;
441         }
442
443 #ifdef HAVE_PTHREAD_H
444         /*
445          *      Note unbound threads WILL happen with -s option, if it matters.
446          *      We cannot tell from here whether that option is in effect.
447          */
448         res = ub_ctx_async(inst->ub, 1);
449 #else
450         /*
451          *      Uses forked subprocesses instead.
452          */
453         res = ub_ctx_async(inst->ub, 0);
454 #endif
455
456         if (res) goto error;
457
458         /*      Glean some default settings to match the main server.   */
459         /*      TODO: debug_level can be changed at runtime. */
460         /*      TODO: log until fork when stdout or stderr and !rad_debug_lvl. */
461         log_level = 0;
462
463         if (rad_debug_lvl > 0) {
464                 log_level = rad_debug_lvl;
465
466         } else if (main_config.debug_level > 0) {
467                 log_level = main_config.debug_level;
468         }
469
470         switch (log_level) {
471         /* TODO: This will need some tweaking */
472         case 0:
473         case 1:
474                 break;
475
476         case 2:
477                 log_level = 1;
478                 break;
479
480         case 3:
481         case 4:
482                 log_level = 2; /* mid-to-heavy levels of output */
483                 break;
484
485         case 5:
486         case 6:
487         case 7:
488         case 8:
489                 log_level = 3; /* Pretty crazy amounts of output */
490                 break;
491
492         default:
493                 log_level = 4; /* Insane amounts of output including crypts */
494                 break;
495         }
496
497         res = ub_ctx_debuglevel(inst->ub, log_level);
498         if (res) goto error;
499
500         switch (default_log.dst) {
501         case L_DST_STDOUT:
502                 if (!rad_debug_lvl) {
503                         log_dst = L_DST_NULL;
504                         break;
505                 }
506                 log_dst = L_DST_STDOUT;
507                 log_fd = dup(STDOUT_FILENO);
508                 break;
509
510         case L_DST_STDERR:
511                 if (!rad_debug_lvl) {
512                         log_dst = L_DST_NULL;
513                         break;
514                 }
515                 log_dst = L_DST_STDOUT;
516                 log_fd = dup(STDERR_FILENO);
517                 break;
518
519         case L_DST_FILES:
520                 if (main_config.log_file) {
521                         char *log_file;
522
523                         strcpy(k, "logfile:");
524                         /* 3rd argument isn't const'd in libunbounds API */
525                         memcpy(&log_file, &main_config.log_file, sizeof(log_file));
526                         res = ub_ctx_set_option(inst->ub, k, log_file);
527                         if (res) {
528                                 goto error;
529                         }
530                         log_dst = L_DST_FILES;
531                         break;
532                 }
533                 /* FALL-THROUGH */
534
535         case L_DST_NULL:
536                 log_dst = L_DST_NULL;
537                 break;
538
539         default:
540                 log_dst = L_DST_SYSLOG;
541                 break;
542         }
543
544         /* Now load the config file, which can override gleaned settings. */
545         {
546                 char *file;
547
548                 memcpy(&file, &inst->filename, sizeof(file));
549                 res = ub_ctx_config(inst->ub, file);
550                 if (res) goto error;
551         }
552
553         /*
554          *      Check if the config file tried to use syslog.  Unbound
555          *      does not share syslog gracefully.
556          */
557         strcpy(k, "use-syslog");
558         res = ub_ctx_get_option(inst->ub, k, &optval);
559         if (res || !optval) goto error;
560
561         if (!strcmp(optval, "yes")) {
562                 char v[3];
563
564                 free(optval);
565
566                 WARN("rlm_unbound (%s): Overriding syslog settings.", inst->name);
567                 strcpy(k, "use-syslog:");
568                 strcpy(v, "no");
569                 res = ub_ctx_set_option(inst->ub, k, v);
570                 if (res) goto error;
571
572                 if (log_dst == L_DST_FILES) {
573                         char *log_file;
574
575                         /* Reinstate the log file name JIC */
576                         strcpy(k, "logfile:");
577                         /* 3rd argument isn't const'd in libunbounds API */
578                         memcpy(&log_file, &main_config.log_file, sizeof(log_file));
579                         res = ub_ctx_set_option(inst->ub, k, log_file);
580                         if (res) goto error;
581                 }
582
583         } else {
584                 if (optval) free(optval);
585                 strcpy(k, "logfile");
586
587                 res = ub_ctx_get_option(inst->ub, k, &optval);
588                 if (res) goto error;
589
590                 if (optval && strlen(optval)) {
591                         log_dst = L_DST_FILES;
592
593                 } else if (!rad_debug_lvl) {
594                         log_dst = L_DST_NULL;
595                 }
596
597                 if (optval) free(optval);
598         }
599
600         switch (log_dst) {
601         case L_DST_STDOUT:
602                 /*
603                  * We have an fd to log to.  And we've already attempted to
604                  * dup it so libunbound doesn't close it on us.
605                  */
606                 if (log_fd == -1) {
607                         cf_log_err_cs(conf, "Could not dup fd");
608                         goto error_nores;
609                 }
610
611                 inst->log_stream = fdopen(log_fd, "w");
612                 if (!inst->log_stream) {
613                         cf_log_err_cs(conf, "error setting up log stream");
614                         goto error_nores;
615                 }
616
617                 res = ub_ctx_debugout(inst->ub, inst->log_stream);
618                 if (res) goto error;
619                 break;
620
621         case L_DST_FILES:
622                 /* We gave libunbound a filename.  It is on its own now. */
623                 break;
624
625         case L_DST_NULL:
626                 /* We tell libunbound not to log at all. */
627                 res = ub_ctx_debugout(inst->ub, NULL);
628                 if (res) goto error;
629                 break;
630
631         case L_DST_SYSLOG:
632 #ifdef HAVE_PTHREAD_H
633                 /*
634                  *  Currently this wreaks havoc when running threaded, so just
635                  *  turn logging off until that gets figured out.
636                  */
637                 res = ub_ctx_debugout(inst->ub, NULL);
638                 if (res) goto error;
639                 break;
640 #else
641                 /*
642                  *  We need to create a pipe, because libunbound does not
643                  *  share syslog nicely.  Or the core added some new logsink.
644                  */
645                 if (pipe(inst->log_pipe)) {
646                 error_pipe:
647                         cf_log_err_cs(conf, "Error setting up log pipes");
648                         goto error_nores;
649                 }
650
651                 if ((fcntl(inst->log_pipe[0], F_SETFL, O_NONBLOCK) < 0) ||
652                     (fcntl(inst->log_pipe[0], F_SETFD, FD_CLOEXEC) < 0)) {
653                         goto error_pipe;
654                 }
655
656                 /* Opaque to us when this can be closed, so we do not. */
657                 if (fcntl(inst->log_pipe[1], F_SETFL, O_NONBLOCK) < 0) {
658                         goto error_pipe;
659                 }
660
661                 inst->log_pipe_stream[0] = fdopen(inst->log_pipe[0], "r");
662                 inst->log_pipe_stream[1] = fdopen(inst->log_pipe[1], "w");
663
664                 if (!inst->log_pipe_stream[0] || !inst->log_pipe_stream[1]) {
665                         if (!inst->log_pipe_stream[1]) {
666                                 close(inst->log_pipe[1]);
667                         }
668
669                         if (!inst->log_pipe_stream[0]) {
670                                 close(inst->log_pipe[0]);
671                         }
672                         cf_log_err_cs(conf, "Error setting up log stream");
673                         goto error_nores;
674                 }
675
676                 res = ub_ctx_debugout(inst->ub, inst->log_pipe_stream[1]);
677                 if (res) goto error;
678
679                 if (!fr_event_fd_insert(inst->el, 0, inst->log_pipe[0], log_spew, inst)) {
680                         cf_log_err_cs(conf, "could not insert log fd");
681                         goto error_nores;
682                 }
683
684                 inst->log_pipe_in_use = true;
685 #endif
686         default:
687                 break;
688         }
689
690         /*
691          *  Now we need to finalize the context.
692          *
693          *  There's no clean API to just finalize the context made public
694          *  in libunbound.  But we can trick it by trying to delete data
695          *  which as it happens fails quickly and quietly even though the
696          *  data did not exist.
697          */
698         strcpy(k, "notar33lsite.foo123.nottld A 127.0.0.1");
699         ub_ctx_data_remove(inst->ub, k);
700
701         inst->log_fd = ub_fd(inst->ub);
702         if (inst->log_fd >= 0) {
703                 if (!fr_event_fd_insert(inst->el, 0, inst->log_fd, ub_fd_handler, inst)) {
704                         cf_log_err_cs(conf, "could not insert async fd");
705                         inst->log_fd = -1;
706                         goto error_nores;
707                 }
708
709         }
710
711         return 0;
712
713  error:
714         cf_log_err_cs(conf, "%s", ub_strerror(res));
715
716  error_nores:
717         if (log_fd > -1) close(log_fd);
718
719         return -1;
720 }
721
722 static int mod_detach(UNUSED void *instance)
723 {
724         rlm_unbound_t *inst = instance;
725
726         if (inst->log_fd >= 0) {
727                 fr_event_fd_delete(inst->el, 0, inst->log_fd);
728                 if (inst->ub) {
729                         ub_process(inst->ub);
730                         /* This can hang/leave zombies currently
731                          * see upstream bug #519
732                          * ...so expect valgrind to complain with -m
733                          */
734 #if 0
735                         ub_ctx_delete(inst->ub);
736 #endif
737                 }
738         }
739
740         if (inst->log_pipe_stream[1]) {
741                 fclose(inst->log_pipe_stream[1]);
742         }
743
744         if (inst->log_pipe_stream[0]) {
745                 if (inst->log_pipe_in_use) {
746                         fr_event_fd_delete(inst->el, 0, inst->log_pipe[0]);
747                 }
748                 fclose(inst->log_pipe_stream[0]);
749         }
750
751         if (inst->log_stream) {
752                 fclose(inst->log_stream);
753         }
754
755         return 0;
756 }
757
758 extern module_t rlm_unbound;
759 module_t rlm_unbound = {
760         .magic          = RLM_MODULE_INIT,
761         .name           = "unbound",
762         .type           = RLM_TYPE_THREAD_SAFE,
763         .inst_size      = sizeof(rlm_unbound_t),
764         .config         = module_config,
765         .bootstrap      = mod_bootstrap,
766         .instantiate    = mod_instantiate,
767         .detach         = mod_detach
768 };