1/*	$NetBSD$	*/
2
3/*
4 * Copyright (C) 2004-2007, 2009-2011  Internet Systems Consortium, Inc. ("ISC")
5 * Copyright (C) 1999-2002  Internet Software Consortium.
6 *
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
12 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
14 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
16 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17 * PERFORMANCE OF THIS SOFTWARE.
18 */
19
20/* Id: named-checkconf.c,v 1.56 2011/03/12 04:59:46 tbox Exp  */
21
22/*! \file */
23
24#include <config.h>
25
26#include <errno.h>
27#include <stdlib.h>
28#include <stdio.h>
29
30#include <isc/commandline.h>
31#include <isc/dir.h>
32#include <isc/entropy.h>
33#include <isc/hash.h>
34#include <isc/log.h>
35#include <isc/mem.h>
36#include <isc/result.h>
37#include <isc/string.h>
38#include <isc/util.h>
39
40#include <isccfg/namedconf.h>
41
42#include <bind9/check.h>
43
44#include <dns/fixedname.h>
45#include <dns/log.h>
46#include <dns/name.h>
47#include <dns/result.h>
48#include <dns/zone.h>
49
50#include "check-tool.h"
51
52static const char *program = "named-checkconf";
53
54isc_log_t *logc = NULL;
55
56#define CHECK(r)\
57	do { \
58		result = (r); \
59		if (result != ISC_R_SUCCESS) \
60			goto cleanup; \
61	} while (/*CONSTCOND*/0)
62
63/*% usage */
64ISC_PLATFORM_NORETURN_PRE static void
65usage(void) ISC_PLATFORM_NORETURN_POST;
66
67static void
68usage(void) {
69	fprintf(stderr, "usage: %s [-h] [-j] [-p] [-v] [-z] [-t directory] "
70		"[named.conf]\n", program);
71	exit(1);
72}
73
74/*% directory callback */
75static isc_result_t
76directory_callback(const char *clausename, const cfg_obj_t *obj, void *arg) {
77	isc_result_t result;
78	const char *directory;
79
80	REQUIRE(strcasecmp("directory", clausename) == 0);
81
82	UNUSED(arg);
83	UNUSED(clausename);
84
85	/*
86	 * Change directory.
87	 */
88	directory = cfg_obj_asstring(obj);
89	result = isc_dir_chdir(directory);
90	if (result != ISC_R_SUCCESS) {
91		cfg_obj_log(obj, logc, ISC_LOG_ERROR,
92			    "change directory to '%s' failed: %s\n",
93			    directory, isc_result_totext(result));
94		return (result);
95	}
96
97	return (ISC_R_SUCCESS);
98}
99
100static isc_boolean_t
101get_maps(const cfg_obj_t **maps, const char *name, const cfg_obj_t **obj) {
102	int i;
103	for (i = 0;; i++) {
104		if (maps[i] == NULL)
105			return (ISC_FALSE);
106		if (cfg_map_get(maps[i], name, obj) == ISC_R_SUCCESS)
107			return (ISC_TRUE);
108	}
109}
110
111static isc_boolean_t
112get_checknames(const cfg_obj_t **maps, const cfg_obj_t **obj) {
113	const cfg_listelt_t *element;
114	const cfg_obj_t *checknames;
115	const cfg_obj_t *type;
116	const cfg_obj_t *value;
117	isc_result_t result;
118	int i;
119
120	for (i = 0;; i++) {
121		if (maps[i] == NULL)
122			return (ISC_FALSE);
123		checknames = NULL;
124		result = cfg_map_get(maps[i], "check-names", &checknames);
125		if (result != ISC_R_SUCCESS)
126			continue;
127		if (checknames != NULL && !cfg_obj_islist(checknames)) {
128			*obj = checknames;
129			return (ISC_TRUE);
130		}
131		for (element = cfg_list_first(checknames);
132		     element != NULL;
133		     element = cfg_list_next(element)) {
134			value = cfg_listelt_value(element);
135			type = cfg_tuple_get(value, "type");
136			if (strcasecmp(cfg_obj_asstring(type), "master") != 0)
137				continue;
138			*obj = cfg_tuple_get(value, "mode");
139			return (ISC_TRUE);
140		}
141	}
142}
143
144static isc_result_t
145config_get(const cfg_obj_t **maps, const char *name, const cfg_obj_t **obj) {
146	int i;
147
148	for (i = 0;; i++) {
149		if (maps[i] == NULL)
150			return (ISC_R_NOTFOUND);
151		if (cfg_map_get(maps[i], name, obj) == ISC_R_SUCCESS)
152			return (ISC_R_SUCCESS);
153	}
154}
155
156/*% configure the zone */
157static isc_result_t
158configure_zone(const char *vclass, const char *view,
159	       const cfg_obj_t *zconfig, const cfg_obj_t *vconfig,
160	       const cfg_obj_t *config, isc_mem_t *mctx)
161{
162	int i = 0;
163	isc_result_t result;
164	const char *zclass;
165	const char *zname;
166	const char *zfile;
167	const cfg_obj_t *maps[4];
168	const cfg_obj_t *zoptions = NULL;
169	const cfg_obj_t *classobj = NULL;
170	const cfg_obj_t *typeobj = NULL;
171	const cfg_obj_t *fileobj = NULL;
172	const cfg_obj_t *dbobj = NULL;
173	const cfg_obj_t *obj = NULL;
174	const cfg_obj_t *fmtobj = NULL;
175	dns_masterformat_t masterformat;
176
177	zone_options = DNS_ZONEOPT_CHECKNS | DNS_ZONEOPT_MANYERRORS;
178
179	zname = cfg_obj_asstring(cfg_tuple_get(zconfig, "name"));
180	classobj = cfg_tuple_get(zconfig, "class");
181	if (!cfg_obj_isstring(classobj))
182		zclass = vclass;
183	else
184		zclass = cfg_obj_asstring(classobj);
185
186	zoptions = cfg_tuple_get(zconfig, "options");
187	maps[i++] = zoptions;
188	if (vconfig != NULL)
189		maps[i++] = cfg_tuple_get(vconfig, "options");
190	if (config != NULL) {
191		cfg_map_get(config, "options", &obj);
192		if (obj != NULL)
193			maps[i++] = obj;
194	}
195	maps[i] = NULL;
196
197	cfg_map_get(zoptions, "type", &typeobj);
198	if (typeobj == NULL)
199		return (ISC_R_FAILURE);
200	if (strcasecmp(cfg_obj_asstring(typeobj), "master") != 0)
201		return (ISC_R_SUCCESS);
202	cfg_map_get(zoptions, "database", &dbobj);
203	if (dbobj != NULL)
204		return (ISC_R_SUCCESS);
205	cfg_map_get(zoptions, "file", &fileobj);
206	if (fileobj == NULL)
207		return (ISC_R_FAILURE);
208	zfile = cfg_obj_asstring(fileobj);
209
210	obj = NULL;
211	if (get_maps(maps, "check-dup-records", &obj)) {
212		if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) {
213			zone_options |= DNS_ZONEOPT_CHECKDUPRR;
214			zone_options &= ~DNS_ZONEOPT_CHECKDUPRRFAIL;
215		} else if (strcasecmp(cfg_obj_asstring(obj), "fail") == 0) {
216			zone_options |= DNS_ZONEOPT_CHECKDUPRR;
217			zone_options |= DNS_ZONEOPT_CHECKDUPRRFAIL;
218		} else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) {
219			zone_options &= ~DNS_ZONEOPT_CHECKDUPRR;
220			zone_options &= ~DNS_ZONEOPT_CHECKDUPRRFAIL;
221		} else
222			INSIST(0);
223	} else {
224		zone_options |= DNS_ZONEOPT_CHECKDUPRR;
225		zone_options &= ~DNS_ZONEOPT_CHECKDUPRRFAIL;
226	}
227
228	obj = NULL;
229	if (get_maps(maps, "check-mx", &obj)) {
230		if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) {
231			zone_options |= DNS_ZONEOPT_CHECKMX;
232			zone_options &= ~DNS_ZONEOPT_CHECKMXFAIL;
233		} else if (strcasecmp(cfg_obj_asstring(obj), "fail") == 0) {
234			zone_options |= DNS_ZONEOPT_CHECKMX;
235			zone_options |= DNS_ZONEOPT_CHECKMXFAIL;
236		} else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) {
237			zone_options &= ~DNS_ZONEOPT_CHECKMX;
238			zone_options &= ~DNS_ZONEOPT_CHECKMXFAIL;
239		} else
240			INSIST(0);
241	} else {
242		zone_options |= DNS_ZONEOPT_CHECKMX;
243		zone_options &= ~DNS_ZONEOPT_CHECKMXFAIL;
244	}
245
246	obj = NULL;
247	if (get_maps(maps, "check-integrity", &obj)) {
248		if (cfg_obj_asboolean(obj))
249			zone_options |= DNS_ZONEOPT_CHECKINTEGRITY;
250		else
251			zone_options &= ~DNS_ZONEOPT_CHECKINTEGRITY;
252	} else
253		zone_options |= DNS_ZONEOPT_CHECKINTEGRITY;
254
255	obj = NULL;
256	if (get_maps(maps, "check-mx-cname", &obj)) {
257		if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) {
258			zone_options |= DNS_ZONEOPT_WARNMXCNAME;
259			zone_options &= ~DNS_ZONEOPT_IGNOREMXCNAME;
260		} else if (strcasecmp(cfg_obj_asstring(obj), "fail") == 0) {
261			zone_options &= ~DNS_ZONEOPT_WARNMXCNAME;
262			zone_options &= ~DNS_ZONEOPT_IGNOREMXCNAME;
263		} else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) {
264			zone_options |= DNS_ZONEOPT_WARNMXCNAME;
265			zone_options |= DNS_ZONEOPT_IGNOREMXCNAME;
266		} else
267			INSIST(0);
268	} else {
269		zone_options |= DNS_ZONEOPT_WARNMXCNAME;
270		zone_options &= ~DNS_ZONEOPT_IGNOREMXCNAME;
271	}
272
273	obj = NULL;
274	if (get_maps(maps, "check-srv-cname", &obj)) {
275		if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) {
276			zone_options |= DNS_ZONEOPT_WARNSRVCNAME;
277			zone_options &= ~DNS_ZONEOPT_IGNORESRVCNAME;
278		} else if (strcasecmp(cfg_obj_asstring(obj), "fail") == 0) {
279			zone_options &= ~DNS_ZONEOPT_WARNSRVCNAME;
280			zone_options &= ~DNS_ZONEOPT_IGNORESRVCNAME;
281		} else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) {
282			zone_options |= DNS_ZONEOPT_WARNSRVCNAME;
283			zone_options |= DNS_ZONEOPT_IGNORESRVCNAME;
284		} else
285			INSIST(0);
286	} else {
287		zone_options |= DNS_ZONEOPT_WARNSRVCNAME;
288		zone_options &= ~DNS_ZONEOPT_IGNORESRVCNAME;
289	}
290
291	obj = NULL;
292	if (get_maps(maps, "check-sibling", &obj)) {
293		if (cfg_obj_asboolean(obj))
294			zone_options |= DNS_ZONEOPT_CHECKSIBLING;
295		else
296			zone_options &= ~DNS_ZONEOPT_CHECKSIBLING;
297	}
298
299	obj = NULL;
300	if (get_checknames(maps, &obj)) {
301		if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) {
302			zone_options |= DNS_ZONEOPT_CHECKNAMES;
303			zone_options &= ~DNS_ZONEOPT_CHECKNAMESFAIL;
304		} else if (strcasecmp(cfg_obj_asstring(obj), "fail") == 0) {
305			zone_options |= DNS_ZONEOPT_CHECKNAMES;
306			zone_options |= DNS_ZONEOPT_CHECKNAMESFAIL;
307		} else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) {
308			zone_options &= ~DNS_ZONEOPT_CHECKNAMES;
309			zone_options &= ~DNS_ZONEOPT_CHECKNAMESFAIL;
310		} else
311			INSIST(0);
312	} else {
313	       zone_options |= DNS_ZONEOPT_CHECKNAMES;
314	       zone_options |= DNS_ZONEOPT_CHECKNAMESFAIL;
315	}
316
317	masterformat = dns_masterformat_text;
318	fmtobj = NULL;
319	result = config_get(maps, "masterfile-format", &fmtobj);
320	if (result == ISC_R_SUCCESS) {
321		const char *masterformatstr = cfg_obj_asstring(fmtobj);
322		if (strcasecmp(masterformatstr, "text") == 0)
323			masterformat = dns_masterformat_text;
324		else if (strcasecmp(masterformatstr, "raw") == 0)
325			masterformat = dns_masterformat_raw;
326		else
327			INSIST(0);
328	}
329
330	result = load_zone(mctx, zname, zfile, masterformat, zclass, NULL);
331	if (result != ISC_R_SUCCESS)
332		fprintf(stderr, "%s/%s/%s: %s\n", view, zname, zclass,
333			dns_result_totext(result));
334	return(result);
335}
336
337/*% configure a view */
338static isc_result_t
339configure_view(const char *vclass, const char *view, const cfg_obj_t *config,
340	       const cfg_obj_t *vconfig, isc_mem_t *mctx)
341{
342	const cfg_listelt_t *element;
343	const cfg_obj_t *voptions;
344	const cfg_obj_t *zonelist;
345	isc_result_t result = ISC_R_SUCCESS;
346	isc_result_t tresult;
347
348	voptions = NULL;
349	if (vconfig != NULL)
350		voptions = cfg_tuple_get(vconfig, "options");
351
352	zonelist = NULL;
353	if (voptions != NULL)
354		(void)cfg_map_get(voptions, "zone", &zonelist);
355	else
356		(void)cfg_map_get(config, "zone", &zonelist);
357
358	for (element = cfg_list_first(zonelist);
359	     element != NULL;
360	     element = cfg_list_next(element))
361	{
362		const cfg_obj_t *zconfig = cfg_listelt_value(element);
363		tresult = configure_zone(vclass, view, zconfig, vconfig,
364					 config, mctx);
365		if (tresult != ISC_R_SUCCESS)
366			result = tresult;
367	}
368	return (result);
369}
370
371
372/*% load zones from the configuration */
373static isc_result_t
374load_zones_fromconfig(const cfg_obj_t *config, isc_mem_t *mctx) {
375	const cfg_listelt_t *element;
376	const cfg_obj_t *classobj;
377	const cfg_obj_t *views;
378	const cfg_obj_t *vconfig;
379	const char *vclass;
380	isc_result_t result = ISC_R_SUCCESS;
381	isc_result_t tresult;
382
383	views = NULL;
384
385	(void)cfg_map_get(config, "view", &views);
386	for (element = cfg_list_first(views);
387	     element != NULL;
388	     element = cfg_list_next(element))
389	{
390		const char *vname;
391
392		vclass = "IN";
393		vconfig = cfg_listelt_value(element);
394		if (vconfig != NULL) {
395			classobj = cfg_tuple_get(vconfig, "class");
396			if (cfg_obj_isstring(classobj))
397				vclass = cfg_obj_asstring(classobj);
398		}
399		vname = cfg_obj_asstring(cfg_tuple_get(vconfig, "name"));
400		tresult = configure_view(vclass, vname, config, vconfig, mctx);
401		if (tresult != ISC_R_SUCCESS)
402			result = tresult;
403	}
404
405	if (views == NULL) {
406		tresult = configure_view("IN", "_default", config, NULL, mctx);
407		if (tresult != ISC_R_SUCCESS)
408			result = tresult;
409	}
410	return (result);
411}
412
413static void
414output(void *closure, const char *text, int textlen) {
415	UNUSED(closure);
416	if (fwrite(text, 1, textlen, stdout) != (size_t)textlen) {
417		perror("fwrite");
418		exit(1);
419	}
420}
421
422/*% The main processing routine */
423int
424main(int argc, char **argv) {
425	int c;
426	cfg_parser_t *parser = NULL;
427	cfg_obj_t *config = NULL;
428	const char *conffile = NULL;
429	isc_mem_t *mctx = NULL;
430	isc_result_t result;
431	int exit_status = 0;
432	isc_entropy_t *ectx = NULL;
433	isc_boolean_t load_zones = ISC_FALSE;
434	isc_boolean_t print = ISC_FALSE;
435
436	isc_commandline_errprint = ISC_FALSE;
437
438	while ((c = isc_commandline_parse(argc, argv, "dhjt:pvz")) != EOF) {
439		switch (c) {
440		case 'd':
441			debug++;
442			break;
443
444		case 'j':
445			nomerge = ISC_FALSE;
446			break;
447
448		case 't':
449			result = isc_dir_chroot(isc_commandline_argument);
450			if (result != ISC_R_SUCCESS) {
451				fprintf(stderr, "isc_dir_chroot: %s\n",
452					isc_result_totext(result));
453				exit(1);
454			}
455			break;
456
457		case 'p':
458			print = ISC_TRUE;
459			break;
460
461		case 'v':
462			printf(VERSION "\n");
463			exit(0);
464
465		case 'z':
466			load_zones = ISC_TRUE;
467			docheckmx = ISC_FALSE;
468			docheckns = ISC_FALSE;
469			dochecksrv = ISC_FALSE;
470			break;
471
472		case '?':
473			if (isc_commandline_option != '?')
474				fprintf(stderr, "%s: invalid argument -%c\n",
475					program, isc_commandline_option);
476		case 'h':
477			usage();
478
479		default:
480			fprintf(stderr, "%s: unhandled option -%c\n",
481				program, isc_commandline_option);
482			exit(1);
483		}
484	}
485
486	if (isc_commandline_index + 1 < argc)
487		usage();
488	if (argv[isc_commandline_index] != NULL)
489		conffile = argv[isc_commandline_index];
490	if (conffile == NULL || conffile[0] == '\0')
491		conffile = NAMED_CONFFILE;
492
493#ifdef _WIN32
494	InitSockets();
495#endif
496
497	RUNTIME_CHECK(isc_mem_create(0, 0, &mctx) == ISC_R_SUCCESS);
498
499	RUNTIME_CHECK(setup_logging(mctx, stdout, &logc) == ISC_R_SUCCESS);
500
501	RUNTIME_CHECK(isc_entropy_create(mctx, &ectx) == ISC_R_SUCCESS);
502	RUNTIME_CHECK(isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE)
503		      == ISC_R_SUCCESS);
504
505	dns_result_register();
506
507	RUNTIME_CHECK(cfg_parser_create(mctx, logc, &parser) == ISC_R_SUCCESS);
508
509	cfg_parser_setcallback(parser, directory_callback, NULL);
510
511	if (cfg_parse_file(parser, conffile, &cfg_type_namedconf, &config) !=
512	    ISC_R_SUCCESS)
513		exit(1);
514
515	result = bind9_check_namedconf(config, logc, mctx);
516	if (result != ISC_R_SUCCESS)
517		exit_status = 1;
518
519	if (result == ISC_R_SUCCESS && load_zones) {
520		result = load_zones_fromconfig(config, mctx);
521		if (result != ISC_R_SUCCESS)
522			exit_status = 1;
523	}
524
525	if (print && exit_status == 0)
526		cfg_print(config, output, NULL);
527	cfg_obj_destroy(parser, &config);
528
529	cfg_parser_destroy(&parser);
530
531	dns_name_destroy();
532
533	isc_log_destroy(&logc);
534
535	isc_hash_destroy();
536	isc_entropy_detach(&ectx);
537
538	isc_mem_destroy(&mctx);
539
540#ifdef _WIN32
541	DestroySockets();
542#endif
543
544	return (exit_status);
545}
546