]>
Commit | Line | Data |
---|---|---|
89c4ed63 A |
1 | /* |
2 | * checkconf/unbound-checkconf.c - config file checker for unbound.conf file. | |
3 | * | |
4 | * Copyright (c) 2007, NLnet Labs. All rights reserved. | |
5 | * | |
6 | * This software is open source. | |
7 | * | |
8 | * Redistribution and use in source and binary forms, with or without | |
9 | * modification, are permitted provided that the following conditions | |
10 | * are met: | |
11 | * | |
12 | * Redistributions of source code must retain the above copyright notice, | |
13 | * this list of conditions and the following disclaimer. | |
14 | * | |
15 | * Redistributions in binary form must reproduce the above copyright notice, | |
16 | * this list of conditions and the following disclaimer in the documentation | |
17 | * and/or other materials provided with the distribution. | |
18 | * | |
19 | * Neither the name of the NLNET LABS nor the names of its contributors may | |
20 | * be used to endorse or promote products derived from this software without | |
21 | * specific prior written permission. | |
22 | * | |
23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
26 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
27 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
28 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | |
29 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
30 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
31 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
32 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
33 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
34 | */ | |
35 | ||
36 | /** | |
37 | * \file | |
38 | * | |
39 | * The config checker checks for syntax and other errors in the unbound.conf | |
40 | * file, and can be used to check for errors before the server is started | |
41 | * or sigHUPped. | |
42 | * Exit status 1 means an error. | |
43 | */ | |
44 | ||
45 | #include "config.h" | |
46 | #include "util/log.h" | |
47 | #include "util/config_file.h" | |
48 | #include "util/module.h" | |
49 | #include "util/net_help.h" | |
50 | #include "util/regional.h" | |
51 | #include "iterator/iterator.h" | |
52 | #include "iterator/iter_fwd.h" | |
53 | #include "iterator/iter_hints.h" | |
54 | #include "validator/validator.h" | |
55 | #include "services/localzone.h" | |
56 | #include "ldns/sbuffer.h" | |
57 | #ifdef HAVE_GETOPT_H | |
58 | #include <getopt.h> | |
59 | #endif | |
60 | #ifdef HAVE_PWD_H | |
61 | #include <pwd.h> | |
62 | #endif | |
63 | #ifdef HAVE_SYS_STAT_H | |
64 | #include <sys/stat.h> | |
65 | #endif | |
66 | #ifdef HAVE_GLOB_H | |
67 | #include <glob.h> | |
68 | #endif | |
69 | #ifdef WITH_PYTHONMODULE | |
70 | #include "pythonmod/pythonmod.h" | |
71 | #endif | |
72 | ||
73 | /** Give checkconf usage, and exit (1). */ | |
74 | static void | |
75 | usage() | |
76 | { | |
77 | printf("Usage: unbound-checkconf [file]\n"); | |
78 | printf(" Checks unbound configuration file for errors.\n"); | |
79 | printf("file if omitted %s is used.\n", CONFIGFILE); | |
80 | printf("-o option print value of option to stdout.\n"); | |
81 | printf("-h show this usage help.\n"); | |
82 | printf("Version %s\n", PACKAGE_VERSION); | |
83 | printf("BSD licensed, see LICENSE in source package for details.\n"); | |
84 | printf("Report bugs to %s\n", PACKAGE_BUGREPORT); | |
85 | exit(1); | |
86 | } | |
87 | ||
88 | /** | |
89 | * Print given option to stdout | |
90 | * @param cfg: config | |
91 | * @param opt: option name without trailing :. | |
92 | * This is different from config_set_option. | |
93 | */ | |
94 | static void | |
95 | print_option(struct config_file* cfg, const char* opt) | |
96 | { | |
97 | if(!config_get_option(cfg, opt, config_print_func, stdout)) | |
98 | fatal_exit("cannot print option '%s'", opt); | |
99 | } | |
100 | ||
101 | /** check if module works with config */ | |
102 | static void | |
103 | check_mod(struct config_file* cfg, struct module_func_block* fb) | |
104 | { | |
105 | struct module_env env; | |
106 | memset(&env, 0, sizeof(env)); | |
107 | env.cfg = cfg; | |
108 | env.scratch = regional_create(); | |
109 | env.scratch_buffer = sldns_buffer_new(BUFSIZ); | |
110 | if(!env.scratch || !env.scratch_buffer) | |
111 | fatal_exit("out of memory"); | |
112 | if(!(*fb->init)(&env, 0)) { | |
113 | fatal_exit("bad config for %s module", fb->name); | |
114 | } | |
115 | (*fb->deinit)(&env, 0); | |
116 | sldns_buffer_free(env.scratch_buffer); | |
117 | regional_destroy(env.scratch); | |
118 | } | |
119 | ||
120 | /** check localzones */ | |
121 | static void | |
122 | localzonechecks(struct config_file* cfg) | |
123 | { | |
124 | struct local_zones* zs; | |
125 | if(!(zs = local_zones_create())) | |
126 | fatal_exit("out of memory"); | |
127 | if(!local_zones_apply_cfg(zs, cfg)) | |
128 | fatal_exit("failed local-zone, local-data configuration"); | |
129 | local_zones_delete(zs); | |
130 | } | |
131 | ||
132 | /** emit warnings for IP in hosts */ | |
133 | static void | |
134 | warn_hosts(const char* typ, struct config_stub* list) | |
135 | { | |
136 | struct sockaddr_storage a; | |
137 | socklen_t alen; | |
138 | struct config_stub* s; | |
139 | struct config_strlist* h; | |
140 | for(s=list; s; s=s->next) { | |
141 | for(h=s->hosts; h; h=h->next) { | |
142 | if(extstrtoaddr(h->str, &a, &alen)) { | |
143 | fprintf(stderr, "unbound-checkconf: warning:" | |
144 | " %s %s: \"%s\" is an IP%s address, " | |
145 | "and when looked up as a host name " | |
146 | "during use may not resolve.\n", | |
147 | s->name, typ, h->str, | |
148 | addr_is_ip6(&a, alen)?"6":"4"); | |
149 | } | |
150 | } | |
151 | } | |
152 | } | |
153 | ||
154 | /** check interface strings */ | |
155 | static void | |
156 | interfacechecks(struct config_file* cfg) | |
157 | { | |
158 | struct sockaddr_storage a; | |
159 | socklen_t alen; | |
160 | int i, j; | |
161 | for(i=0; i<cfg->num_ifs; i++) { | |
162 | if(!extstrtoaddr(cfg->ifs[i], &a, &alen)) { | |
163 | fatal_exit("cannot parse interface specified as '%s'", | |
164 | cfg->ifs[i]); | |
165 | } | |
166 | for(j=0; j<cfg->num_ifs; j++) { | |
167 | if(i!=j && strcmp(cfg->ifs[i], cfg->ifs[j])==0) | |
168 | fatal_exit("interface: %s present twice, " | |
169 | "cannot bind same ports twice.", | |
170 | cfg->ifs[i]); | |
171 | } | |
172 | } | |
173 | for(i=0; i<cfg->num_out_ifs; i++) { | |
174 | if(!ipstrtoaddr(cfg->out_ifs[i], UNBOUND_DNS_PORT, | |
175 | &a, &alen)) { | |
176 | fatal_exit("cannot parse outgoing-interface " | |
177 | "specified as '%s'", cfg->out_ifs[i]); | |
178 | } | |
179 | for(j=0; j<cfg->num_out_ifs; j++) { | |
180 | if(i!=j && strcmp(cfg->out_ifs[i], cfg->out_ifs[j])==0) | |
181 | fatal_exit("outgoing-interface: %s present " | |
182 | "twice, cannot bind same ports twice.", | |
183 | cfg->out_ifs[i]); | |
184 | } | |
185 | } | |
186 | } | |
187 | ||
188 | /** check acl ips */ | |
189 | static void | |
190 | aclchecks(struct config_file* cfg) | |
191 | { | |
192 | int d; | |
193 | struct sockaddr_storage a; | |
194 | socklen_t alen; | |
195 | struct config_str2list* acl; | |
196 | for(acl=cfg->acls; acl; acl = acl->next) { | |
197 | if(!netblockstrtoaddr(acl->str, UNBOUND_DNS_PORT, &a, &alen, | |
198 | &d)) { | |
199 | fatal_exit("cannot parse access control address %s %s", | |
200 | acl->str, acl->str2); | |
201 | } | |
202 | } | |
203 | } | |
204 | ||
205 | /** true if fname is a file */ | |
206 | static int | |
207 | is_file(const char* fname) | |
208 | { | |
209 | struct stat buf; | |
210 | if(stat(fname, &buf) < 0) { | |
211 | if(errno==EACCES) { | |
212 | printf("warning: no search permission for one of the directories in path: %s\n", fname); | |
213 | return 1; | |
214 | } | |
215 | perror(fname); | |
216 | return 0; | |
217 | } | |
218 | if(S_ISDIR(buf.st_mode)) { | |
219 | printf("%s is not a file\n", fname); | |
220 | return 0; | |
221 | } | |
222 | return 1; | |
223 | } | |
224 | ||
225 | /** true if fname is a directory */ | |
226 | static int | |
227 | is_dir(const char* fname) | |
228 | { | |
229 | struct stat buf; | |
230 | if(stat(fname, &buf) < 0) { | |
231 | if(errno==EACCES) { | |
232 | printf("warning: no search permission for one of the directories in path: %s\n", fname); | |
233 | return 1; | |
234 | } | |
235 | perror(fname); | |
236 | return 0; | |
237 | } | |
238 | if(!(S_ISDIR(buf.st_mode))) { | |
239 | printf("%s is not a directory\n", fname); | |
240 | return 0; | |
241 | } | |
242 | return 1; | |
243 | } | |
244 | ||
245 | /** get base dir of a fname */ | |
246 | static char* | |
247 | basedir(char* fname) | |
248 | { | |
249 | char* rev; | |
250 | if(!fname) fatal_exit("out of memory"); | |
251 | rev = strrchr(fname, '/'); | |
252 | if(!rev) return NULL; | |
253 | if(fname == rev) return NULL; | |
254 | rev[0] = 0; | |
255 | return fname; | |
256 | } | |
257 | ||
258 | /** check chroot for a file string */ | |
259 | static void | |
260 | check_chroot_string(const char* desc, char** ss, | |
261 | const char* chrootdir, struct config_file* cfg) | |
262 | { | |
263 | char* str = *ss; | |
264 | if(str && str[0]) { | |
265 | *ss = fname_after_chroot(str, cfg, 1); | |
266 | if(!*ss) fatal_exit("out of memory"); | |
267 | if(!is_file(*ss)) { | |
268 | if(chrootdir && chrootdir[0]) | |
269 | fatal_exit("%s: \"%s\" does not exist in " | |
270 | "chrootdir %s", desc, str, chrootdir); | |
271 | else | |
272 | fatal_exit("%s: \"%s\" does not exist", | |
273 | desc, str); | |
274 | } | |
275 | /* put in a new full path for continued checking */ | |
276 | free(str); | |
277 | } | |
278 | } | |
279 | ||
280 | /** check file list, every file must be inside the chroot location */ | |
281 | static void | |
282 | check_chroot_filelist(const char* desc, struct config_strlist* list, | |
283 | const char* chrootdir, struct config_file* cfg) | |
284 | { | |
285 | struct config_strlist* p; | |
286 | for(p=list; p; p=p->next) { | |
287 | check_chroot_string(desc, &p->str, chrootdir, cfg); | |
288 | } | |
289 | } | |
290 | ||
291 | /** check file list, with wildcard processing */ | |
292 | static void | |
293 | check_chroot_filelist_wild(const char* desc, struct config_strlist* list, | |
294 | const char* chrootdir, struct config_file* cfg) | |
295 | { | |
296 | struct config_strlist* p; | |
297 | for(p=list; p; p=p->next) { | |
298 | #ifdef HAVE_GLOB | |
299 | if(strchr(p->str, '*') || strchr(p->str, '[') || | |
300 | strchr(p->str, '?') || strchr(p->str, '{') || | |
301 | strchr(p->str, '~')) { | |
302 | char* s = p->str; | |
303 | /* adjust whole pattern for chroot and check later */ | |
304 | p->str = fname_after_chroot(p->str, cfg, 1); | |
305 | free(s); | |
306 | } else | |
307 | #endif /* HAVE_GLOB */ | |
308 | check_chroot_string(desc, &p->str, chrootdir, cfg); | |
309 | } | |
310 | } | |
311 | ||
312 | /** check configuration for errors */ | |
313 | static void | |
314 | morechecks(struct config_file* cfg, const char* fname) | |
315 | { | |
316 | warn_hosts("stub-host", cfg->stubs); | |
317 | warn_hosts("forward-host", cfg->forwards); | |
318 | interfacechecks(cfg); | |
319 | aclchecks(cfg); | |
320 | ||
321 | if(cfg->verbosity < 0) | |
322 | fatal_exit("verbosity value < 0"); | |
323 | if(cfg->num_threads <= 0 || cfg->num_threads > 10000) | |
324 | fatal_exit("num_threads value weird"); | |
325 | if(!cfg->do_ip4 && !cfg->do_ip6) | |
326 | fatal_exit("ip4 and ip6 are both disabled, pointless"); | |
327 | if(!cfg->do_udp && !cfg->do_tcp) | |
328 | fatal_exit("udp and tcp are both disabled, pointless"); | |
329 | if(cfg->edns_buffer_size > cfg->msg_buffer_size) | |
330 | fatal_exit("edns-buffer-size larger than msg-buffer-size, " | |
331 | "answers will not fit in processing buffer"); | |
332 | ||
333 | if(cfg->chrootdir && cfg->chrootdir[0] && | |
334 | cfg->chrootdir[strlen(cfg->chrootdir)-1] == '/') | |
335 | fatal_exit("chootdir %s has trailing slash '/' please remove.", | |
336 | cfg->chrootdir); | |
337 | if(cfg->chrootdir && cfg->chrootdir[0] && | |
338 | !is_dir(cfg->chrootdir)) { | |
339 | fatal_exit("bad chroot directory"); | |
340 | } | |
341 | if(cfg->chrootdir && cfg->chrootdir[0]) { | |
342 | char buf[10240]; | |
343 | buf[0] = 0; | |
344 | if(fname[0] != '/') { | |
345 | if(getcwd(buf, sizeof(buf)) == NULL) | |
346 | fatal_exit("getcwd: %s", strerror(errno)); | |
347 | (void)strlcat(buf, "/", sizeof(buf)); | |
348 | } | |
349 | (void)strlcat(buf, fname, sizeof(buf)); | |
350 | if(strncmp(buf, cfg->chrootdir, strlen(cfg->chrootdir)) != 0) | |
351 | fatal_exit("config file %s is not inside chroot %s", | |
352 | buf, cfg->chrootdir); | |
353 | } | |
354 | if(cfg->directory && cfg->directory[0]) { | |
355 | char* ad = fname_after_chroot(cfg->directory, cfg, 0); | |
356 | if(!ad) fatal_exit("out of memory"); | |
357 | if(!is_dir(ad)) fatal_exit("bad chdir directory"); | |
358 | free(ad); | |
359 | } | |
360 | if( (cfg->chrootdir && cfg->chrootdir[0]) || | |
361 | (cfg->directory && cfg->directory[0])) { | |
362 | if(cfg->pidfile && cfg->pidfile[0]) { | |
363 | char* ad = (cfg->pidfile[0]=='/')?strdup(cfg->pidfile): | |
364 | fname_after_chroot(cfg->pidfile, cfg, 1); | |
365 | char* bd = basedir(ad); | |
366 | if(bd && !is_dir(bd)) | |
367 | fatal_exit("pidfile directory does not exist"); | |
368 | free(ad); | |
369 | } | |
370 | if(cfg->logfile && cfg->logfile[0]) { | |
371 | char* ad = fname_after_chroot(cfg->logfile, cfg, 1); | |
372 | char* bd = basedir(ad); | |
373 | if(bd && !is_dir(bd)) | |
374 | fatal_exit("logfile directory does not exist"); | |
375 | free(ad); | |
376 | } | |
377 | } | |
378 | ||
379 | check_chroot_filelist("file with root-hints", | |
380 | cfg->root_hints, cfg->chrootdir, cfg); | |
381 | check_chroot_filelist("trust-anchor-file", | |
382 | cfg->trust_anchor_file_list, cfg->chrootdir, cfg); | |
383 | check_chroot_filelist("auto-trust-anchor-file", | |
384 | cfg->auto_trust_anchor_file_list, cfg->chrootdir, cfg); | |
385 | check_chroot_filelist_wild("trusted-keys-file", | |
386 | cfg->trusted_keys_file_list, cfg->chrootdir, cfg); | |
387 | check_chroot_string("dlv-anchor-file", &cfg->dlv_anchor_file, | |
388 | cfg->chrootdir, cfg); | |
389 | /* remove chroot setting so that modules are not stripping pathnames*/ | |
390 | free(cfg->chrootdir); | |
391 | cfg->chrootdir = NULL; | |
392 | ||
393 | if(strcmp(cfg->module_conf, "iterator") != 0 | |
394 | && strcmp(cfg->module_conf, "validator iterator") != 0 | |
395 | && strcmp(cfg->module_conf, "dns64 validator iterator") != 0 | |
396 | && strcmp(cfg->module_conf, "dns64 iterator") != 0 | |
397 | #ifdef WITH_PYTHONMODULE | |
398 | && strcmp(cfg->module_conf, "python iterator") != 0 | |
399 | && strcmp(cfg->module_conf, "python validator iterator") != 0 | |
400 | && strcmp(cfg->module_conf, "validator python iterator") != 0 | |
401 | && strcmp(cfg->module_conf, "dns64 python iterator") != 0 | |
402 | && strcmp(cfg->module_conf, "dns64 python validator iterator") != 0 | |
403 | && strcmp(cfg->module_conf, "dns64 validator python iterator") != 0 | |
404 | && strcmp(cfg->module_conf, "python dns64 iterator") != 0 | |
405 | && strcmp(cfg->module_conf, "python dns64 validator iterator") != 0 | |
406 | #endif | |
407 | ) { | |
408 | fatal_exit("module conf '%s' is not known to work", | |
409 | cfg->module_conf); | |
410 | } | |
411 | ||
412 | #ifdef HAVE_GETPWNAM | |
413 | if(cfg->username && cfg->username[0]) { | |
414 | if(getpwnam(cfg->username) == NULL) | |
415 | fatal_exit("user '%s' does not exist.", cfg->username); | |
416 | endpwent(); | |
417 | } | |
418 | #endif | |
419 | if(cfg->remote_control_enable) { | |
420 | check_chroot_string("server-key-file", &cfg->server_key_file, | |
421 | cfg->chrootdir, cfg); | |
422 | check_chroot_string("server-cert-file", &cfg->server_cert_file, | |
423 | cfg->chrootdir, cfg); | |
424 | if(!is_file(cfg->control_key_file)) | |
425 | fatal_exit("control-key-file: \"%s\" does not exist", | |
426 | cfg->control_key_file); | |
427 | if(!is_file(cfg->control_cert_file)) | |
428 | fatal_exit("control-cert-file: \"%s\" does not exist", | |
429 | cfg->control_cert_file); | |
430 | } | |
431 | ||
432 | localzonechecks(cfg); | |
433 | } | |
434 | ||
435 | /** check forwards */ | |
436 | static void | |
437 | check_fwd(struct config_file* cfg) | |
438 | { | |
439 | struct iter_forwards* fwd = forwards_create(); | |
440 | if(!fwd || !forwards_apply_cfg(fwd, cfg)) { | |
441 | fatal_exit("Could not set forward zones"); | |
442 | } | |
443 | forwards_delete(fwd); | |
444 | } | |
445 | ||
446 | /** check hints */ | |
447 | static void | |
448 | check_hints(struct config_file* cfg) | |
449 | { | |
450 | struct iter_hints* hints = hints_create(); | |
451 | if(!hints || !hints_apply_cfg(hints, cfg)) { | |
452 | fatal_exit("Could not set root or stub hints"); | |
453 | } | |
454 | hints_delete(hints); | |
455 | } | |
456 | ||
457 | /** check config file */ | |
458 | static void | |
459 | checkconf(const char* cfgfile, const char* opt) | |
460 | { | |
461 | struct config_file* cfg = config_create(); | |
462 | if(!cfg) | |
463 | fatal_exit("out of memory"); | |
464 | if(!config_read(cfg, cfgfile, NULL)) { | |
465 | /* config_read prints messages to stderr */ | |
466 | config_delete(cfg); | |
467 | exit(1); | |
468 | } | |
469 | if(opt) { | |
470 | print_option(cfg, opt); | |
471 | config_delete(cfg); | |
472 | return; | |
473 | } | |
474 | morechecks(cfg, cfgfile); | |
475 | check_mod(cfg, iter_get_funcblock()); | |
476 | check_mod(cfg, val_get_funcblock()); | |
477 | #ifdef WITH_PYTHONMODULE | |
478 | if(strstr(cfg->module_conf, "python")) | |
479 | check_mod(cfg, pythonmod_get_funcblock()); | |
480 | #endif | |
481 | check_fwd(cfg); | |
482 | check_hints(cfg); | |
483 | printf("unbound-checkconf: no errors in %s\n", cfgfile); | |
484 | config_delete(cfg); | |
485 | } | |
486 | ||
487 | /** getopt global, in case header files fail to declare it. */ | |
488 | extern int optind; | |
489 | /** getopt global, in case header files fail to declare it. */ | |
490 | extern char* optarg; | |
491 | ||
492 | /** Main routine for checkconf */ | |
493 | int main(int argc, char* argv[]) | |
494 | { | |
495 | int c; | |
496 | const char* f; | |
497 | const char* opt = NULL; | |
498 | const char* cfgfile = CONFIGFILE; | |
499 | log_ident_set("unbound-checkconf"); | |
500 | log_init(NULL, 0, NULL); | |
501 | checklock_start(); | |
502 | #ifdef USE_WINSOCK | |
503 | /* use registry config file in preference to compiletime location */ | |
504 | if(!(cfgfile=w_lookup_reg_str("Software\\Unbound", "ConfigFile"))) | |
505 | cfgfile = CONFIGFILE; | |
506 | #endif /* USE_WINSOCK */ | |
507 | /* parse the options */ | |
508 | while( (c=getopt(argc, argv, "ho:")) != -1) { | |
509 | switch(c) { | |
510 | case 'o': | |
511 | opt = optarg; | |
512 | break; | |
513 | case '?': | |
514 | case 'h': | |
515 | default: | |
516 | usage(); | |
517 | } | |
518 | } | |
519 | argc -= optind; | |
520 | argv += optind; | |
521 | if(argc != 0 && argc != 1) | |
522 | usage(); | |
523 | if(argc == 1) | |
524 | f = argv[0]; | |
525 | else f = cfgfile; | |
526 | checkconf(f, opt); | |
527 | checklock_stop(); | |
528 | return 0; | |
529 | } |