named-checkconf.c revision 254897
1/*
2 * Copyright (C) 2004-2007, 2009-2013  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/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_maps(maps, "check-spf", &obj)) {
299		if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) {
300			zone_options |= DNS_ZONEOPT_CHECKSPF;
301		} else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) {
302			zone_options &= ~DNS_ZONEOPT_CHECKSPF;
303		} else
304			INSIST(0);
305	} else {
306		zone_options |= DNS_ZONEOPT_CHECKSPF;
307	}
308
309	obj = NULL;
310	if (get_checknames(maps, &obj)) {
311		if (strcasecmp(cfg_obj_asstring(obj), "warn") == 0) {
312			zone_options |= DNS_ZONEOPT_CHECKNAMES;
313			zone_options &= ~DNS_ZONEOPT_CHECKNAMESFAIL;
314		} else if (strcasecmp(cfg_obj_asstring(obj), "fail") == 0) {
315			zone_options |= DNS_ZONEOPT_CHECKNAMES;
316			zone_options |= DNS_ZONEOPT_CHECKNAMESFAIL;
317		} else if (strcasecmp(cfg_obj_asstring(obj), "ignore") == 0) {
318			zone_options &= ~DNS_ZONEOPT_CHECKNAMES;
319			zone_options &= ~DNS_ZONEOPT_CHECKNAMESFAIL;
320		} else
321			INSIST(0);
322	} else {
323	       zone_options |= DNS_ZONEOPT_CHECKNAMES;
324	       zone_options |= DNS_ZONEOPT_CHECKNAMESFAIL;
325	}
326
327	masterformat = dns_masterformat_text;
328	fmtobj = NULL;
329	result = config_get(maps, "masterfile-format", &fmtobj);
330	if (result == ISC_R_SUCCESS) {
331		const char *masterformatstr = cfg_obj_asstring(fmtobj);
332		if (strcasecmp(masterformatstr, "text") == 0)
333			masterformat = dns_masterformat_text;
334		else if (strcasecmp(masterformatstr, "raw") == 0)
335			masterformat = dns_masterformat_raw;
336		else
337			INSIST(0);
338	}
339
340	result = load_zone(mctx, zname, zfile, masterformat, zclass, NULL);
341	if (result != ISC_R_SUCCESS)
342		fprintf(stderr, "%s/%s/%s: %s\n", view, zname, zclass,
343			dns_result_totext(result));
344	return(result);
345}
346
347/*% configure a view */
348static isc_result_t
349configure_view(const char *vclass, const char *view, const cfg_obj_t *config,
350	       const cfg_obj_t *vconfig, isc_mem_t *mctx)
351{
352	const cfg_listelt_t *element;
353	const cfg_obj_t *voptions;
354	const cfg_obj_t *zonelist;
355	isc_result_t result = ISC_R_SUCCESS;
356	isc_result_t tresult;
357
358	voptions = NULL;
359	if (vconfig != NULL)
360		voptions = cfg_tuple_get(vconfig, "options");
361
362	zonelist = NULL;
363	if (voptions != NULL)
364		(void)cfg_map_get(voptions, "zone", &zonelist);
365	else
366		(void)cfg_map_get(config, "zone", &zonelist);
367
368	for (element = cfg_list_first(zonelist);
369	     element != NULL;
370	     element = cfg_list_next(element))
371	{
372		const cfg_obj_t *zconfig = cfg_listelt_value(element);
373		tresult = configure_zone(vclass, view, zconfig, vconfig,
374					 config, mctx);
375		if (tresult != ISC_R_SUCCESS)
376			result = tresult;
377	}
378	return (result);
379}
380
381
382/*% load zones from the configuration */
383static isc_result_t
384load_zones_fromconfig(const cfg_obj_t *config, isc_mem_t *mctx) {
385	const cfg_listelt_t *element;
386	const cfg_obj_t *classobj;
387	const cfg_obj_t *views;
388	const cfg_obj_t *vconfig;
389	const char *vclass;
390	isc_result_t result = ISC_R_SUCCESS;
391	isc_result_t tresult;
392
393	views = NULL;
394
395	(void)cfg_map_get(config, "view", &views);
396	for (element = cfg_list_first(views);
397	     element != NULL;
398	     element = cfg_list_next(element))
399	{
400		const char *vname;
401
402		vclass = "IN";
403		vconfig = cfg_listelt_value(element);
404		if (vconfig != NULL) {
405			classobj = cfg_tuple_get(vconfig, "class");
406			if (cfg_obj_isstring(classobj))
407				vclass = cfg_obj_asstring(classobj);
408		}
409		vname = cfg_obj_asstring(cfg_tuple_get(vconfig, "name"));
410		tresult = configure_view(vclass, vname, config, vconfig, mctx);
411		if (tresult != ISC_R_SUCCESS)
412			result = tresult;
413	}
414
415	if (views == NULL) {
416		tresult = configure_view("IN", "_default", config, NULL, mctx);
417		if (tresult != ISC_R_SUCCESS)
418			result = tresult;
419	}
420	return (result);
421}
422
423static void
424output(void *closure, const char *text, int textlen) {
425	UNUSED(closure);
426	if (fwrite(text, 1, textlen, stdout) != (size_t)textlen) {
427		perror("fwrite");
428		exit(1);
429	}
430}
431
432/*% The main processing routine */
433int
434main(int argc, char **argv) {
435	int c;
436	cfg_parser_t *parser = NULL;
437	cfg_obj_t *config = NULL;
438	const char *conffile = NULL;
439	isc_mem_t *mctx = NULL;
440	isc_result_t result;
441	int exit_status = 0;
442	isc_entropy_t *ectx = NULL;
443	isc_boolean_t load_zones = ISC_FALSE;
444	isc_boolean_t print = ISC_FALSE;
445
446	isc_commandline_errprint = ISC_FALSE;
447
448	while ((c = isc_commandline_parse(argc, argv, "dhjt:pvz")) != EOF) {
449		switch (c) {
450		case 'd':
451			debug++;
452			break;
453
454		case 'j':
455			nomerge = ISC_FALSE;
456			break;
457
458		case 't':
459			result = isc_dir_chroot(isc_commandline_argument);
460			if (result != ISC_R_SUCCESS) {
461				fprintf(stderr, "isc_dir_chroot: %s\n",
462					isc_result_totext(result));
463				exit(1);
464			}
465			break;
466
467		case 'p':
468			print = ISC_TRUE;
469			break;
470
471		case 'v':
472			printf(VERSION "\n");
473			exit(0);
474
475		case 'z':
476			load_zones = ISC_TRUE;
477			docheckmx = ISC_FALSE;
478			docheckns = ISC_FALSE;
479			dochecksrv = ISC_FALSE;
480			break;
481
482		case '?':
483			if (isc_commandline_option != '?')
484				fprintf(stderr, "%s: invalid argument -%c\n",
485					program, isc_commandline_option);
486			/* FALLTHROUGH */
487		case 'h':
488			usage();
489
490		default:
491			fprintf(stderr, "%s: unhandled option -%c\n",
492				program, isc_commandline_option);
493			exit(1);
494		}
495	}
496
497	if (isc_commandline_index + 1 < argc)
498		usage();
499	if (argv[isc_commandline_index] != NULL)
500		conffile = argv[isc_commandline_index];
501	if (conffile == NULL || conffile[0] == '\0')
502		conffile = NAMED_CONFFILE;
503
504#ifdef _WIN32
505	InitSockets();
506#endif
507
508	RUNTIME_CHECK(isc_mem_create(0, 0, &mctx) == ISC_R_SUCCESS);
509
510	RUNTIME_CHECK(setup_logging(mctx, stdout, &logc) == ISC_R_SUCCESS);
511
512	RUNTIME_CHECK(isc_entropy_create(mctx, &ectx) == ISC_R_SUCCESS);
513	RUNTIME_CHECK(isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE)
514		      == ISC_R_SUCCESS);
515
516	dns_result_register();
517
518	RUNTIME_CHECK(cfg_parser_create(mctx, logc, &parser) == ISC_R_SUCCESS);
519
520	cfg_parser_setcallback(parser, directory_callback, NULL);
521
522	if (cfg_parse_file(parser, conffile, &cfg_type_namedconf, &config) !=
523	    ISC_R_SUCCESS)
524		exit(1);
525
526	result = bind9_check_namedconf(config, logc, mctx);
527	if (result != ISC_R_SUCCESS)
528		exit_status = 1;
529
530	if (result == ISC_R_SUCCESS && load_zones) {
531		result = load_zones_fromconfig(config, mctx);
532		if (result != ISC_R_SUCCESS)
533			exit_status = 1;
534	}
535
536	if (print && exit_status == 0)
537		cfg_print(config, output, NULL);
538	cfg_obj_destroy(parser, &config);
539
540	cfg_parser_destroy(&parser);
541
542	dns_name_destroy();
543
544	isc_log_destroy(&logc);
545
546	isc_hash_destroy();
547	isc_entropy_detach(&ectx);
548
549	isc_mem_destroy(&mctx);
550
551#ifdef _WIN32
552	DestroySockets();
553#endif
554
555	return (exit_status);
556}
557