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