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