xref: /openbsd-src/usr.sbin/nsd/nsd-checkconf.c (revision 4c1e55dc91edd6e69ccc60ce855900fbc12cf34f)
1 /*
2  * checkconf - Read and repeat configuration file to output.
3  *
4  * Copyright (c) 2001-2011, NLnet Labs. All rights reserved.
5  *
6  * See LICENSE for the license.
7  *
8  */
9 #include <config.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <unistd.h>
13 #include <string.h>
14 #include <limits.h>
15 #include "tsig.h"
16 #include "options.h"
17 #include "util.h"
18 #include "dname.h"
19 
20 extern char *optarg;
21 extern int optind;
22 
23 #define ZONE_GET_ACL(NAME, VAR) 		\
24 	if (strcasecmp(#NAME, (VAR)) == 0) { 	\
25 		quote_acl((zone->NAME)); 	\
26 		return; 			\
27 	}
28 
29 #define ZONE_GET_OUTGOING(NAME, VAR)			\
30 	if (strcasecmp(#NAME, (VAR)) == 0) {		\
31 		acl_options_t* acl; 			\
32 		for(acl=zone->NAME; acl; acl=acl->next)	\
33 			quote(acl->ip_address_spec);	\
34 		return; 				\
35 	}
36 
37 #define ZONE_GET_STR(NAME, VAR) 		\
38 	if (strcasecmp(#NAME, (VAR)) == 0) { 	\
39 		quote(zone->NAME); 		\
40 		return; 			\
41 	}
42 
43 #define ZONE_GET_BIN(NAME, VAR) 			\
44 	if (strcasecmp(#NAME, (VAR)) == 0) { 		\
45 		printf("%s\n", zone->NAME?"yes":"no"); 	\
46 	}
47 
48 #define SERV_GET_BIN(NAME, VAR) 			\
49 	if (strcasecmp(#NAME, (VAR)) == 0) { 		\
50 		printf("%s\n", opt->NAME?"yes":"no"); 	\
51 	}
52 
53 #define SERV_GET_STR(NAME, VAR) 		\
54 	if (strcasecmp(#NAME, (VAR)) == 0) { 	\
55 		quote(opt->NAME); 		\
56 		return; 			\
57 	}
58 
59 #define SERV_GET_INT(NAME, VAR) 		\
60 	if (strcasecmp(#NAME, (VAR)) == 0) { 	\
61 		printf("%d\n", (int) opt->NAME); 	\
62 		return; 			\
63 	}
64 
65 #define SERV_GET_IP(NAME, VAR) 				\
66 	if (strcasecmp(#NAME, (VAR)) == 0) { 		\
67 		for(ip = opt->ip_addresses; ip; ip=ip->next)	\
68 		{						\
69 			quote(ip->address);			\
70 		}						\
71 		return;						\
72 	}
73 
74 static char buf[BUFSIZ];
75 
76 static char *
77 underscore(const char *s) {
78 	const char *j = s;
79 	size_t i = 0;
80 
81 	while(j && *j) {
82 		if (*j == '-') {
83 			buf[i++] = '_';
84 		} else {
85 			buf[i++] = *j;
86 		}
87 		j++;
88 		if (i >= BUFSIZ) {
89 			return NULL;
90 		}
91 	}
92 	buf[i] = '\0';
93 	return buf;
94 }
95 
96 static void
97 usage(void)
98 {
99 	fprintf(stderr, "usage: nsd-checkconf [-v|-h] [-o option] [-z zonename]\n");
100 	fprintf(stderr, "                     [-s keyname] <configfilename>\n");
101 	fprintf(stderr, "       Checks NSD configuration file for errors.\n");
102 	fprintf(stderr, "       Version %s. Report bugs to <%s>.\n\n",
103 		PACKAGE_VERSION, PACKAGE_BUGREPORT);
104 	fprintf(stderr, "Use with a configfile as argument to check syntax.\n");
105 	fprintf(stderr, "Use with -o, -z or -s options to query the configuration.\n\n");
106 	fprintf(stderr, "-v		Verbose, echo settings that take effect to std output.\n");
107 	fprintf(stderr, "-h		Print this help information.\n");
108 	fprintf(stderr, "-o option	Print value of the option specified to stdout.\n");
109 	fprintf(stderr, "-z zonename	Print option value for the zone given.\n");
110 	fprintf(stderr, "-a keyname	Print algorithm name for the TSIG key.\n");
111 	fprintf(stderr, "-s keyname	Print base64 secret blob for the TSIG key.\n");
112 	exit(1);
113 }
114 
115 static void
116 print_string_var(const char* varname, const char* value)
117 {
118 	if (!value) {
119 		printf("\t#%s\n", varname);
120 	} else {
121 		printf("\t%s \"%s\"\n", varname, value);
122 	}
123 }
124 
125 static void
126 quote(const char *v)
127 {
128 	if(v==NULL)
129 		printf("\n");
130 	else
131 		printf("%s\n", v);
132 }
133 
134 static void
135 quote_acl(acl_options_t* acl)
136 {
137 	while(acl)
138 	{
139 		printf("%s %s\n", acl->ip_address_spec,
140 			acl->nokey?"NOKEY":(acl->blocked?"BLOCKED":
141 			(acl->key_name?acl->key_name:"(null)")));
142 		acl=acl->next;
143 	}
144 }
145 
146 static void
147 print_acl(const char* varname, acl_options_t* acl)
148 {
149 	while(acl)
150 	{
151 		printf("\t%s ", varname);
152 		if(acl->use_axfr_only)
153 			printf("AXFR ");
154 		if(acl->allow_udp)
155 			printf("UDP ");
156 		printf("%s %s\n", acl->ip_address_spec,
157 			acl->nokey?"NOKEY":(acl->blocked?"BLOCKED":
158 			(acl->key_name?acl->key_name:"(null)")));
159 		if(1) {
160 			printf("\t# %s", acl->is_ipv6?"ip6":"ip4");
161 			if(acl->port == 0) printf(" noport");
162 			else printf(" port=%d", acl->port);
163 			if(acl->rangetype == acl_range_single) printf(" single");
164 			if(acl->rangetype == acl_range_mask)   printf(" masked");
165 			if(acl->rangetype == acl_range_subnet) printf(" subnet");
166 			if(acl->rangetype == acl_range_minmax) printf(" minmax");
167 			if(acl->is_ipv6) {
168 #ifdef INET6
169 				char dest[128];
170 				inet_ntop(AF_INET6, &acl->addr.addr6, dest, sizeof(dest));
171 				printf(" addr=%s", dest);
172 				if(acl->rangetype != acl_range_single) {
173 					inet_ntop(AF_INET6, &acl->range_mask.addr6, dest, sizeof(dest));
174 					printf(" rangemask=%s", dest);
175 				}
176 #else
177 				printf(" ip6addr-noip6defined");
178 #endif
179 			} else {
180 				char dest[128];
181 				inet_ntop(AF_INET, &acl->addr.addr, dest, sizeof(dest));
182 				printf(" addr=%s", dest);
183 				if(acl->rangetype != acl_range_single) {
184 					inet_ntop(AF_INET, &acl->range_mask.addr, dest, sizeof(dest));
185 					printf(" rangemask=%s", dest);
186 				}
187 			}
188 			printf("\n");
189 		}
190 		acl=acl->next;
191 	}
192 }
193 
194 static void
195 print_acl_ips(const char* varname, acl_options_t* acl)
196 {
197 	while(acl)
198 	{
199 		printf("\t%s %s\n", varname, acl->ip_address_spec);
200 		acl=acl->next;
201 	}
202 }
203 
204 void
205 config_print_zone(nsd_options_t* opt, const char* k, int s, const char *o, const char *z)
206 {
207 	zone_options_t* zone;
208 	ip_address_option_t* ip;
209 
210 	if (k) {
211 		/* find key */
212 		key_options_t* key = opt->keys;
213 		for( ; key ; key=key->next) {
214 			if(strcmp(key->name, k) == 0) {
215 				if (s) {
216 					quote(key->secret);
217 				} else {
218 					quote(key->algorithm);
219 				}
220 				return;
221 			}
222 		}
223 		printf("Could not find key %s\n", k);
224 		return;
225 	}
226 
227 	if (!o) {
228 		return;
229 	}
230 
231 	if (z) {
232 		const dname_type *dname = dname_parse(opt->region, z);
233 		if(!dname) {
234 			printf("Could not parse zone name %s\n", z);
235 			exit(1);
236 		}
237 		/* look per zone */
238 		RBTREE_FOR(zone, zone_options_t*, opt->zone_options)
239 		{
240 			if (dname_compare(dname, zone->node.key) == 0) {
241 				/* -z matches, return are in the defines */
242 				ZONE_GET_STR(name, o);
243 				ZONE_GET_STR(zonefile, o);
244 				ZONE_GET_ACL(request_xfr, o);
245 				ZONE_GET_ACL(provide_xfr, o);
246 				ZONE_GET_ACL(allow_notify, o);
247 				ZONE_GET_ACL(notify, o);
248 				ZONE_GET_BIN(notify_retry, o);
249 				ZONE_GET_OUTGOING(outgoing_interface, o);
250 				ZONE_GET_BIN(allow_axfr_fallback, o);
251 				printf("Zone option not handled: %s %s\n", z, o);
252 				exit(1);
253 			}
254 		}
255 		printf("Zone does not exist: %s\n", z);
256 		exit(1);
257 	} else {
258 		/* look in the server section */
259 		SERV_GET_IP(ip_address, o);
260 		/* bin */
261 		SERV_GET_BIN(debug_mode, o);
262 		SERV_GET_BIN(ip4_only, o);
263 		SERV_GET_BIN(ip6_only, o);
264 		SERV_GET_BIN(hide_version, o);
265 		/* str */
266 		SERV_GET_STR(database, o);
267 		SERV_GET_STR(identity, o);
268 		SERV_GET_STR(nsid, o);
269 		SERV_GET_STR(logfile, o);
270 		SERV_GET_STR(pidfile, o);
271 		SERV_GET_STR(chroot, o);
272 		SERV_GET_STR(username, o);
273 		SERV_GET_STR(zonesdir, o);
274 		SERV_GET_STR(difffile, o);
275 		SERV_GET_STR(xfrdfile, o);
276 		SERV_GET_STR(port, o);
277 #if defined(BIND8_STATS) && defined(USE_ZONE_STATS)
278 		SERV_GET_STR(zonestatsfile, o);
279 #endif
280 		/* int */
281 		SERV_GET_INT(server_count, o);
282 		SERV_GET_INT(tcp_count, o);
283 		SERV_GET_INT(tcp_query_count, o);
284 		SERV_GET_INT(tcp_timeout, o);
285 		SERV_GET_INT(ipv4_edns_size, o);
286 		SERV_GET_INT(ipv6_edns_size, o);
287 		SERV_GET_INT(statistics, o);
288 		SERV_GET_INT(xfrd_reload_timeout, o);
289 		SERV_GET_INT(verbosity, o);
290 
291 		if(strcasecmp(o, "zones") == 0) {
292 			RBTREE_FOR(zone, zone_options_t*, opt->zone_options)
293 				quote(zone->name);
294 			return;
295 		}
296 		printf("Server option not handled: %s\n", o);
297 		exit(1);
298 	}
299 }
300 
301 void
302 config_test_print_server(nsd_options_t* opt)
303 {
304 	ip_address_option_t* ip;
305 	key_options_t* key;
306 	zone_options_t* zone;
307 
308 	printf("# Config settings.\n");
309 	printf("server:\n");
310 	printf("\tdebug-mode: %s\n", opt->debug_mode?"yes":"no");
311 	printf("\tip4-only: %s\n", opt->ip4_only?"yes":"no");
312 	printf("\tip6-only: %s\n", opt->ip6_only?"yes":"no");
313 	printf("\thide-version: %s\n", opt->hide_version?"yes":"no");
314 	print_string_var("database:", opt->database);
315 	print_string_var("identity:", opt->identity);
316 	print_string_var("nsid:", opt->nsid);
317 	print_string_var("logfile:", opt->logfile);
318 	printf("\tserver_count: %d\n", opt->server_count);
319 	printf("\ttcp_count: %d\n", opt->tcp_count);
320 	printf("\ttcp_query_count: %d\n", opt->tcp_query_count);
321 	printf("\ttcp_timeout: %d\n", opt->tcp_timeout);
322 	printf("\tipv4-edns-size: %d\n", (int) opt->ipv4_edns_size);
323 	printf("\tipv6-edns-size: %d\n", (int) opt->ipv6_edns_size);
324 	print_string_var("pidfile:", opt->pidfile);
325 	print_string_var("port:", opt->port);
326 	printf("\tstatistics: %d\n", opt->statistics);
327 #if defined(BIND8_STATS) && defined(USE_ZONE_STATS)
328 	printf("\tzone-stats-file: %s\n", opt->zonestatsfile);
329 #endif
330 	print_string_var("chroot:", opt->chroot);
331 	print_string_var("username:", opt->username);
332 	print_string_var("zonesdir:", opt->zonesdir);
333 	print_string_var("difffile:", opt->difffile);
334 	print_string_var("xfrdfile:", opt->xfrdfile);
335 	printf("\txfrd_reload_timeout: %d\n", opt->xfrd_reload_timeout);
336 	printf("\tverbosity: %d\n", opt->verbosity);
337 
338 	for(ip = opt->ip_addresses; ip; ip=ip->next)
339 	{
340 		print_string_var("ip-address:", ip->address);
341 	}
342 	for(key = opt->keys; key; key=key->next)
343 	{
344 		printf("\nkey:\n");
345 		print_string_var("name:", key->name);
346 		print_string_var("algorithm:", key->algorithm);
347 		print_string_var("secret:", key->secret);
348 	}
349 	RBTREE_FOR(zone, zone_options_t*, opt->zone_options)
350 	{
351 		printf("\nzone:\n");
352 		print_string_var("name:", zone->name);
353 		print_string_var("zonefile:", zone->zonefile);
354 		print_acl("allow-notify:", zone->allow_notify);
355 		print_acl("request-xfr:", zone->request_xfr);
356 		printf("\tnotify-retry: %d\n", zone->notify_retry);
357 		print_acl("notify:", zone->notify);
358 		print_acl("provide-xfr:", zone->provide_xfr);
359 		print_acl_ips("outgoing-interface:", zone->outgoing_interface);
360 		printf("\tallow-axfr-fallback: %s\n", zone->allow_axfr_fallback?"yes":"no");
361 	}
362 
363 }
364 
365 static int
366 additional_checks(nsd_options_t* opt, const char* filename)
367 {
368 	ip_address_option_t* ip = opt->ip_addresses;
369 	zone_options_t* zone;
370 	key_options_t* key;
371 	int num = 0;
372 	int errors = 0;
373 	while(ip) {
374 		num++;
375 		ip = ip->next;
376 	}
377 	if(num > MAX_INTERFACES) {
378 		fprintf(stderr, "%s: too many interfaces (ip-address:) specified.\n", filename);
379 		errors ++;
380 	}
381 
382 	RBTREE_FOR(zone, zone_options_t*, opt->zone_options)
383 	{
384 		const dname_type* dname = dname_parse(opt->region, zone->name); /* memory leak. */
385 		if(!dname) {
386 			fprintf(stderr, "%s: cannot parse zone name syntax for zone %s.\n", filename, zone->name);
387 			errors ++;
388 		}
389 		if(zone->allow_notify && !zone->request_xfr) {
390 			fprintf(stderr, "%s: zone %s has allow-notify but no request-xfr"
391 				" items. Where can it get a zone transfer when a notify "
392 				"is received?\n", filename, zone->name);
393 			errors ++;
394 		}
395 	}
396 
397 	for(key = opt->keys; key; key=key->next)
398 	{
399 		const dname_type* dname = dname_parse(opt->region, key->name); /* memory leak. */
400 		uint8_t data[4000];
401 		int size;
402 
403 		if(!dname) {
404 			fprintf(stderr, "%s: cannot parse tsig name syntax for key %s.\n", filename, key->name);
405 			errors ++;
406 		}
407 		size = b64_pton(key->secret, data, sizeof(data));
408 		if(size == -1) {
409 			fprintf(stderr, "%s: cannot base64 decode tsig secret: for key %s.\n", filename, key->name);
410 			errors ++;
411 		}
412 		if(tsig_get_algorithm_by_name(key->algorithm) == NULL)
413 		{
414 			fprintf(stderr, "%s: bad tsig algorithm %s: for key \
415 %s.\n", filename, key->algorithm, key->name);
416 			errors ++;
417 		}
418 	}
419 
420 #ifndef BIND8_STATS
421 	if(opt->statistics > 0)
422 	{
423 		fprintf(stderr, "%s: 'statistics: %d' but BIND 8 statistics feature not enabled.\n",
424 			filename, opt->statistics);
425 		errors ++;
426 	}
427 #  ifndef USE_ZONE_STATS
428 	if(opt->zonestatsfile)
429 	{
430 		fprintf(stderr, "%s: 'zone-stats-file: %s' but per zone BIND 8 statistics feature not enabled.\n",
431 			filename, opt->zonestatsfile);
432 		errors ++;
433 	}
434 #  endif
435 #endif
436 
437 #ifndef HAVE_CHROOT
438 	if(opt->chroot != 0)
439 	{
440 		fprintf(stderr, "%s: chroot %s given. chroot not supported on this platform.\n",
441 			filename, opt->chroot);
442 		errors ++;
443 	}
444 #endif
445 	if (opt->identity && strlen(opt->identity) > UCHAR_MAX) {
446                 fprintf(stderr, "%s: server identity too long (%u characters)\n",
447                       filename, (unsigned) strlen(opt->identity));
448 		errors ++;
449         }
450 
451 	/* not done here: parsing of ip-address. parsing of username. */
452 
453         if (opt->chroot) {
454                 int l = strlen(opt->chroot);
455 
456                 if (strncmp(opt->chroot, opt->pidfile, l) != 0) {
457 			fprintf(stderr, "%s: pidfile %s is not relative to chroot %s.\n",
458 				filename, opt->pidfile, opt->chroot);
459 			errors ++;
460                 }
461 		if (strncmp(opt->chroot, opt->database, l) != 0) {
462 			fprintf(stderr, "%s: databasefile %s is not relative to chroot %s.\n",
463 				filename, opt->database, opt->chroot);
464 			errors ++;
465                 }
466 		if (strncmp(opt->chroot, opt->difffile, l) != 0) {
467 			fprintf(stderr, "%s: difffile %s is not relative to chroot %s.\n",
468 				filename, opt->difffile, opt->chroot);
469 			errors ++;
470                 }
471 		if (strncmp(opt->chroot, opt->xfrdfile, l) != 0) {
472 			fprintf(stderr, "%s: xfrdfile %s is not relative to chroot %s.\n",
473 				filename, opt->xfrdfile, opt->chroot);
474 			errors ++;
475                 }
476         }
477 	if (atoi(opt->port) <= 0) {
478 		fprintf(stderr, "%s: port number '%s' is not a positive number.\n",
479 			filename, opt->port);
480 		errors ++;
481 	}
482 	if(errors != 0) {
483 		fprintf(stderr, "%s: %d semantic errors in %d zones, %d keys.\n",
484 			filename, errors, (int)nsd_options_num_zones(opt),
485 			(int)opt->numkeys);
486 	}
487 
488 	return (errors == 0);
489 }
490 
491 int
492 main(int argc, char* argv[])
493 {
494 	int c;
495 	int verbose = 0;
496 	int key_sec = 0;
497 	const char * conf_opt = NULL; /* what option do you want? Can be NULL -> print all */
498 	const char * conf_zone = NULL; /* what zone are we talking about */
499 	const char * conf_key = NULL; /* what key is needed */
500 	const char* configfile;
501 	nsd_options_t *options;
502 
503 	log_init("nsd-checkconf");
504 
505 
506         /* Parse the command line... */
507         while ((c = getopt(argc, argv, "vo:a:s:z:")) != -1) {
508 		switch (c) {
509 		case 'v':
510 			verbose = 1;
511 			break;
512 		case 'o':
513 			conf_opt = optarg;
514 			break;
515 		case 'a':
516 			if (conf_key) {
517 				fprintf(stderr, "Error: cannot combine -a with -s or other -a.\n");
518 				exit(1);
519 			}
520 			conf_key = optarg;
521 			break;
522 		case 's':
523 			if (conf_key) {
524 				fprintf(stderr, "Error: cannot combine -s with -a or other -s.\n");
525 				exit(1);
526 			}
527 			conf_key = optarg;
528 			key_sec = 1;
529 			break;
530 		case 'z':
531 			conf_zone = optarg;
532 			break;
533 		default:
534 			usage();
535 		};
536 	}
537         argc -= optind;
538         argv += optind;
539         if (argc == 0 || argc>=2) {
540 		usage();
541 	}
542 	configfile = argv[0];
543 
544 	/* read config file */
545 	options = nsd_options_create(region_create(xalloc, free));
546 	tsig_init(options->region);
547 	if (!parse_options_file(options, configfile) ||
548 	   !additional_checks(options, configfile)) {
549 		exit(2);
550 	}
551 	if (conf_opt || conf_key) {
552 		config_print_zone(options, conf_key, key_sec, underscore(conf_opt), conf_zone);
553 	} else {
554 		if (verbose) {
555 			printf("# Read file %s: %d zones, %d keys.\n",
556 				configfile,
557 				(int)nsd_options_num_zones(options),
558 				(int)options->numkeys);
559 			config_test_print_server(options);
560 		}
561 	}
562 	return 0;
563 }
564