xref: /openbsd-src/usr.sbin/nsd/nsd-checkconf.c (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
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 		/* int */
278 		SERV_GET_INT(server_count, o);
279 		SERV_GET_INT(tcp_count, o);
280 		SERV_GET_INT(tcp_query_count, o);
281 		SERV_GET_INT(tcp_timeout, o);
282 		SERV_GET_INT(ipv4_edns_size, o);
283 		SERV_GET_INT(ipv6_edns_size, o);
284 		SERV_GET_INT(statistics, o);
285 		SERV_GET_INT(xfrd_reload_timeout, o);
286 		SERV_GET_INT(verbosity, o);
287 
288 		if(strcasecmp(o, "zones") == 0) {
289 			RBTREE_FOR(zone, zone_options_t*, opt->zone_options)
290 				quote(zone->name);
291 			return;
292 		}
293 		printf("Server option not handled: %s\n", o);
294 		exit(1);
295 	}
296 }
297 
298 void
299 config_test_print_server(nsd_options_t* opt)
300 {
301 	ip_address_option_t* ip;
302 	key_options_t* key;
303 	zone_options_t* zone;
304 
305 	printf("# Config settings.\n");
306 	printf("server:\n");
307 	printf("\tdebug-mode: %s\n", opt->debug_mode?"yes":"no");
308 	printf("\tip4-only: %s\n", opt->ip4_only?"yes":"no");
309 	printf("\tip6-only: %s\n", opt->ip6_only?"yes":"no");
310 	printf("\thide-version: %s\n", opt->hide_version?"yes":"no");
311 	print_string_var("database:", opt->database);
312 	print_string_var("identity:", opt->identity);
313 	print_string_var("nsid:", opt->nsid);
314 	print_string_var("logfile:", opt->logfile);
315 	printf("\tserver_count: %d\n", opt->server_count);
316 	printf("\ttcp_count: %d\n", opt->tcp_count);
317 	printf("\ttcp_query_count: %d\n", opt->tcp_query_count);
318 	printf("\ttcp_timeout: %d\n", opt->tcp_timeout);
319 	printf("\tipv4-edns-size: %d\n", (int) opt->ipv4_edns_size);
320 	printf("\tipv6-edns-size: %d\n", (int) opt->ipv6_edns_size);
321 	print_string_var("pidfile:", opt->pidfile);
322 	print_string_var("port:", opt->port);
323 	printf("\tstatistics: %d\n", opt->statistics);
324 	print_string_var("chroot:", opt->chroot);
325 	print_string_var("username:", opt->username);
326 	print_string_var("zonesdir:", opt->zonesdir);
327 	print_string_var("difffile:", opt->difffile);
328 	print_string_var("xfrdfile:", opt->xfrdfile);
329 	printf("\txfrd_reload_timeout: %d\n", opt->xfrd_reload_timeout);
330 	printf("\tverbosity: %d\n", opt->verbosity);
331 
332 	for(ip = opt->ip_addresses; ip; ip=ip->next)
333 	{
334 		print_string_var("ip-address:", ip->address);
335 	}
336 	for(key = opt->keys; key; key=key->next)
337 	{
338 		printf("\nkey:\n");
339 		print_string_var("name:", key->name);
340 		print_string_var("algorithm:", key->algorithm);
341 		print_string_var("secret:", key->secret);
342 	}
343 	RBTREE_FOR(zone, zone_options_t*, opt->zone_options)
344 	{
345 		printf("\nzone:\n");
346 		print_string_var("name:", zone->name);
347 		print_string_var("zonefile:", zone->zonefile);
348 		print_acl("allow-notify:", zone->allow_notify);
349 		print_acl("request-xfr:", zone->request_xfr);
350 		printf("\tnotify-retry: %d\n", zone->notify_retry);
351 		print_acl("notify:", zone->notify);
352 		print_acl("provide-xfr:", zone->provide_xfr);
353 		print_acl_ips("outgoing-interface:", zone->outgoing_interface);
354 		printf("\tallow-axfr-fallback: %s\n", zone->allow_axfr_fallback?"yes":"no");
355 	}
356 
357 }
358 
359 static int
360 additional_checks(nsd_options_t* opt, const char* filename)
361 {
362 	ip_address_option_t* ip = opt->ip_addresses;
363 	zone_options_t* zone;
364 	key_options_t* key;
365 	int num = 0;
366 	int errors = 0;
367 	while(ip) {
368 		num++;
369 		ip = ip->next;
370 	}
371 	if(num > MAX_INTERFACES) {
372 		fprintf(stderr, "%s: too many interfaces (ip-address:) specified.\n", filename);
373 		errors ++;
374 	}
375 
376 	RBTREE_FOR(zone, zone_options_t*, opt->zone_options)
377 	{
378 		const dname_type* dname = dname_parse(opt->region, zone->name); /* memory leak. */
379 		if(!dname) {
380 			fprintf(stderr, "%s: cannot parse zone name syntax for zone %s.\n", filename, zone->name);
381 			errors ++;
382 		}
383 		if(zone->allow_notify && !zone->request_xfr) {
384 			fprintf(stderr, "%s: zone %s has allow-notify but no request-xfr"
385 				" items. Where can it get a zone transfer when a notify "
386 				"is received?\n", filename, zone->name);
387 			errors ++;
388 		}
389 	}
390 
391 	for(key = opt->keys; key; key=key->next)
392 	{
393 		const dname_type* dname = dname_parse(opt->region, key->name); /* memory leak. */
394 		uint8_t data[4000];
395 		int size;
396 
397 		if(!dname) {
398 			fprintf(stderr, "%s: cannot parse tsig name syntax for key %s.\n", filename, key->name);
399 			errors ++;
400 		}
401 		size = b64_pton(key->secret, data, sizeof(data));
402 		if(size == -1) {
403 			fprintf(stderr, "%s: cannot base64 decode tsig secret: for key %s.\n", filename, key->name);
404 			errors ++;
405 		}
406 		if(tsig_get_algorithm_by_name(key->algorithm) != NULL)
407 		{
408 			fprintf(stderr, "%s: bad tsig algorithm %s: for key \
409 %s.\n", filename, key->algorithm, key->name);
410 			errors ++;
411 		}
412 	}
413 
414 #ifndef BIND8_STATS
415 	if(opt->statistics > 0)
416 	{
417 		fprintf(stderr, "%s: 'statistics: %d' but BIND 8 statistics feature not enabled.\n",
418 			filename, opt->statistics);
419 		errors ++;
420 	}
421 #endif
422 #ifndef HAVE_CHROOT
423 	if(opt->chroot != 0)
424 	{
425 		fprintf(stderr, "%s: chroot %s given. chroot not supported on this platform.\n",
426 			filename, opt->chroot);
427 		errors ++;
428 	}
429 #endif
430 	if (opt->identity && strlen(opt->identity) > UCHAR_MAX) {
431                 fprintf(stderr, "%s: server identity too long (%u characters)\n",
432                       filename, (unsigned) strlen(opt->identity));
433 		errors ++;
434         }
435 
436 	/* not done here: parsing of ip-address. parsing of username. */
437 
438         if (opt->chroot) {
439                 int l = strlen(opt->chroot);
440 
441                 if (strncmp(opt->chroot, opt->pidfile, l) != 0) {
442 			fprintf(stderr, "%s: pidfile %s is not relative to chroot %s.\n",
443 				filename, opt->pidfile, opt->chroot);
444 			errors ++;
445                 }
446 		if (strncmp(opt->chroot, opt->database, l) != 0) {
447 			fprintf(stderr, "%s: databasefile %s is not relative to chroot %s.\n",
448 				filename, opt->database, opt->chroot);
449 			errors ++;
450                 }
451 		if (strncmp(opt->chroot, opt->difffile, l) != 0) {
452 			fprintf(stderr, "%s: difffile %s is not relative to chroot %s.\n",
453 				filename, opt->difffile, opt->chroot);
454 			errors ++;
455                 }
456 		if (strncmp(opt->chroot, opt->xfrdfile, l) != 0) {
457 			fprintf(stderr, "%s: xfrdfile %s is not relative to chroot %s.\n",
458 				filename, opt->xfrdfile, opt->chroot);
459 			errors ++;
460                 }
461         }
462 	if (atoi(opt->port) <= 0) {
463 		fprintf(stderr, "%s: port number '%s' is not a positive number.\n",
464 			filename, opt->port);
465 		errors ++;
466 	}
467 	if(errors != 0) {
468 		fprintf(stderr, "%s: %d semantic errors in %d zones, %d keys.\n",
469 			filename, errors, (int)nsd_options_num_zones(opt),
470 			(int)opt->numkeys);
471 	}
472 
473 	return (errors == 0);
474 }
475 
476 int
477 main(int argc, char* argv[])
478 {
479 	int c;
480 	int verbose = 0;
481 	int key_sec = 0;
482 	const char * conf_opt = NULL; /* what option do you want? Can be NULL -> print all */
483 	const char * conf_zone = NULL; /* what zone are we talking about */
484 	const char * conf_key = NULL; /* what key is needed */
485 	const char* configfile;
486 	nsd_options_t *options;
487 
488 	log_init("nsd-checkconf");
489 
490 
491         /* Parse the command line... */
492         while ((c = getopt(argc, argv, "vo:a:s:z:")) != -1) {
493 		switch (c) {
494 		case 'v':
495 			verbose = 1;
496 			break;
497 		case 'o':
498 			conf_opt = optarg;
499 			break;
500 		case 'a':
501 			if (conf_key) {
502 				fprintf(stderr, "Error: cannot combine -a with -s or other -a.\n");
503 				exit(1);
504 			}
505 			conf_key = optarg;
506 			break;
507 		case 's':
508 			if (conf_key) {
509 				fprintf(stderr, "Error: cannot combine -s with -a or other -s.\n");
510 				exit(1);
511 			}
512 			conf_key = optarg;
513 			key_sec = 1;
514 			break;
515 		case 'z':
516 			conf_zone = optarg;
517 			break;
518 		default:
519 			usage();
520 		};
521 	}
522         argc -= optind;
523         argv += optind;
524         if (argc == 0 || argc>=2) {
525 		usage();
526 	}
527 	configfile = argv[0];
528 
529 	/* read config file */
530 	options = nsd_options_create(region_create(xalloc, free));
531 	if (!parse_options_file(options, configfile) ||
532 	   !additional_checks(options, configfile)) {
533 		exit(2);
534 	}
535 	if (conf_opt || conf_key) {
536 		config_print_zone(options, conf_key, key_sec, underscore(conf_opt), conf_zone);
537 	} else {
538 		if (verbose) {
539 			printf("# Read file %s: %d zones, %d keys.\n",
540 				configfile,
541 				(int)nsd_options_num_zones(options),
542 				(int)options->numkeys);
543 			config_test_print_server(options);
544 		}
545 	}
546 	return 0;
547 }
548