1/*
2 * Copyright (C) 2004-2013  Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (C) 1999-2003  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-checkzone.c,v 1.65 2011/12/22 17:29:22 each Exp $ */
19
20/*! \file */
21
22#include <config.h>
23
24#include <stdlib.h>
25
26#include <isc/app.h>
27#include <isc/commandline.h>
28#include <isc/dir.h>
29#include <isc/entropy.h>
30#include <isc/hash.h>
31#include <isc/log.h>
32#include <isc/mem.h>
33#include <isc/socket.h>
34#include <isc/string.h>
35#include <isc/task.h>
36#include <isc/timer.h>
37#include <isc/util.h>
38
39#include <dns/db.h>
40#include <dns/fixedname.h>
41#include <dns/log.h>
42#include <dns/master.h>
43#include <dns/masterdump.h>
44#include <dns/name.h>
45#include <dns/rdataclass.h>
46#include <dns/rdataset.h>
47#include <dns/result.h>
48#include <dns/types.h>
49#include <dns/zone.h>
50
51#include "check-tool.h"
52
53static int quiet = 0;
54static isc_mem_t *mctx = NULL;
55static isc_entropy_t *ectx = NULL;
56dns_zone_t *zone = NULL;
57dns_zonetype_t zonetype = dns_zone_master;
58static int dumpzone = 0;
59static const char *output_filename;
60static char *prog_name = NULL;
61static const dns_master_style_t *outputstyle = NULL;
62static enum { progmode_check, progmode_compile } progmode;
63
64#define ERRRET(result, function) \
65	do { \
66		if (result != ISC_R_SUCCESS) { \
67			if (!quiet) \
68				fprintf(stderr, "%s() returned %s\n", \
69					function, dns_result_totext(result)); \
70			return (result); \
71		} \
72	} while (0)
73
74ISC_PLATFORM_NORETURN_PRE static void
75usage(void) ISC_PLATFORM_NORETURN_POST;
76
77static void
78usage(void) {
79	fprintf(stderr,
80		"usage: %s [-djqvD] [-c class] "
81		"[-f inputformat] [-F outputformat] "
82		"[-t directory] [-w directory] [-k (ignore|warn|fail)] "
83		"[-n (ignore|warn|fail)] [-m (ignore|warn|fail)] "
84		"[-r (ignore|warn|fail)] "
85		"[-i (full|full-sibling|local|local-sibling|none)] "
86		"[-M (ignore|warn|fail)] [-S (ignore|warn|fail)] "
87		"[-W (ignore|warn)] "
88		"%s zonename filename\n",
89		prog_name,
90		progmode == progmode_check ? "[-o filename]" : "-o filename");
91	exit(1);
92}
93
94static void
95destroy(void) {
96	if (zone != NULL)
97		dns_zone_detach(&zone);
98	dns_name_destroy();
99}
100
101/*% main processing routine */
102int
103main(int argc, char **argv) {
104	int c;
105	char *origin = NULL;
106	char *filename = NULL;
107	isc_log_t *lctx = NULL;
108	isc_result_t result;
109	char classname_in[] = "IN";
110	char *classname = classname_in;
111	const char *workdir = NULL;
112	const char *inputformatstr = NULL;
113	const char *outputformatstr = NULL;
114	dns_masterformat_t inputformat = dns_masterformat_text;
115	dns_masterformat_t outputformat = dns_masterformat_text;
116	dns_masterrawheader_t header;
117	isc_uint32_t rawversion = 1, serialnum = 0;
118	isc_boolean_t snset = ISC_FALSE;
119	isc_boolean_t logdump = ISC_FALSE;
120	FILE *errout = stdout;
121	char *endp;
122
123	outputstyle = &dns_master_style_full;
124
125	prog_name = strrchr(argv[0], '/');
126	if (prog_name == NULL)
127		prog_name = strrchr(argv[0], '\\');
128	if (prog_name != NULL)
129		prog_name++;
130	else
131		prog_name = argv[0];
132	/*
133	 * Libtool doesn't preserve the program name prior to final
134	 * installation.  Remove the libtool prefix ("lt-").
135	 */
136	if (strncmp(prog_name, "lt-", 3) == 0)
137		prog_name += 3;
138
139#define PROGCMP(X) \
140	(strcasecmp(prog_name, X) == 0 || strcasecmp(prog_name, X ".exe") == 0)
141
142	if (PROGCMP("named-checkzone"))
143		progmode = progmode_check;
144	else if (PROGCMP("named-compilezone"))
145		progmode = progmode_compile;
146	else
147		INSIST(0);
148
149	/* Compilation specific defaults */
150	if (progmode == progmode_compile) {
151		zone_options |= (DNS_ZONEOPT_CHECKNS |
152				 DNS_ZONEOPT_FATALNS |
153				 DNS_ZONEOPT_CHECKSPF |
154				 DNS_ZONEOPT_CHECKDUPRR |
155				 DNS_ZONEOPT_CHECKNAMES |
156				 DNS_ZONEOPT_CHECKNAMESFAIL |
157				 DNS_ZONEOPT_CHECKWILDCARD);
158	} else
159		zone_options |= (DNS_ZONEOPT_CHECKDUPRR |
160				 DNS_ZONEOPT_CHECKSPF);
161
162#define ARGCMP(X) (strcmp(isc_commandline_argument, X) == 0)
163
164	isc_commandline_errprint = ISC_FALSE;
165
166	while ((c = isc_commandline_parse(argc, argv,
167			       "c:df:hi:jk:L:m:n:qr:s:t:o:vw:DF:M:S:T:W:"))
168	       != EOF) {
169		switch (c) {
170		case 'c':
171			classname = isc_commandline_argument;
172			break;
173
174		case 'd':
175			debug++;
176			break;
177
178		case 'i':
179			if (ARGCMP("full")) {
180				zone_options |= DNS_ZONEOPT_CHECKINTEGRITY |
181						DNS_ZONEOPT_CHECKSIBLING;
182				docheckmx = ISC_TRUE;
183				docheckns = ISC_TRUE;
184				dochecksrv = ISC_TRUE;
185			} else if (ARGCMP("full-sibling")) {
186				zone_options |= DNS_ZONEOPT_CHECKINTEGRITY;
187				zone_options &= ~DNS_ZONEOPT_CHECKSIBLING;
188				docheckmx = ISC_TRUE;
189				docheckns = ISC_TRUE;
190				dochecksrv = ISC_TRUE;
191			} else if (ARGCMP("local")) {
192				zone_options |= DNS_ZONEOPT_CHECKINTEGRITY;
193				zone_options |= DNS_ZONEOPT_CHECKSIBLING;
194				docheckmx = ISC_FALSE;
195				docheckns = ISC_FALSE;
196				dochecksrv = ISC_FALSE;
197			} else if (ARGCMP("local-sibling")) {
198				zone_options |= DNS_ZONEOPT_CHECKINTEGRITY;
199				zone_options &= ~DNS_ZONEOPT_CHECKSIBLING;
200				docheckmx = ISC_FALSE;
201				docheckns = ISC_FALSE;
202				dochecksrv = ISC_FALSE;
203			} else if (ARGCMP("none")) {
204				zone_options &= ~DNS_ZONEOPT_CHECKINTEGRITY;
205				zone_options &= ~DNS_ZONEOPT_CHECKSIBLING;
206				docheckmx = ISC_FALSE;
207				docheckns = ISC_FALSE;
208				dochecksrv = ISC_FALSE;
209			} else {
210				fprintf(stderr, "invalid argument to -i: %s\n",
211					isc_commandline_argument);
212				exit(1);
213			}
214			break;
215
216		case 'f':
217			inputformatstr = isc_commandline_argument;
218			break;
219
220		case 'F':
221			outputformatstr = isc_commandline_argument;
222			break;
223
224		case 'j':
225			nomerge = ISC_FALSE;
226			break;
227
228		case 'k':
229			if (ARGCMP("warn")) {
230				zone_options |= DNS_ZONEOPT_CHECKNAMES;
231				zone_options &= ~DNS_ZONEOPT_CHECKNAMESFAIL;
232			} else if (ARGCMP("fail")) {
233				zone_options |= DNS_ZONEOPT_CHECKNAMES |
234						DNS_ZONEOPT_CHECKNAMESFAIL;
235			} else if (ARGCMP("ignore")) {
236				zone_options &= ~(DNS_ZONEOPT_CHECKNAMES |
237						  DNS_ZONEOPT_CHECKNAMESFAIL);
238			} else {
239				fprintf(stderr, "invalid argument to -k: %s\n",
240					isc_commandline_argument);
241				exit(1);
242			}
243			break;
244
245		case 'L':
246			snset = ISC_TRUE;
247			endp = NULL;
248			serialnum = strtol(isc_commandline_argument, &endp, 0);
249			if (*endp != '\0') {
250				fprintf(stderr, "source serial number "
251						"must be numeric");
252				exit(1);
253			}
254			break;
255
256		case 'n':
257			if (ARGCMP("ignore")) {
258				zone_options &= ~(DNS_ZONEOPT_CHECKNS|
259						  DNS_ZONEOPT_FATALNS);
260			} else if (ARGCMP("warn")) {
261				zone_options |= DNS_ZONEOPT_CHECKNS;
262				zone_options &= ~DNS_ZONEOPT_FATALNS;
263			} else if (ARGCMP("fail")) {
264				zone_options |= DNS_ZONEOPT_CHECKNS|
265						DNS_ZONEOPT_FATALNS;
266			} else {
267				fprintf(stderr, "invalid argument to -n: %s\n",
268					isc_commandline_argument);
269				exit(1);
270			}
271			break;
272
273		case 'm':
274			if (ARGCMP("warn")) {
275				zone_options |= DNS_ZONEOPT_CHECKMX;
276				zone_options &= ~DNS_ZONEOPT_CHECKMXFAIL;
277			} else if (ARGCMP("fail")) {
278				zone_options |= DNS_ZONEOPT_CHECKMX |
279						DNS_ZONEOPT_CHECKMXFAIL;
280			} else if (ARGCMP("ignore")) {
281				zone_options &= ~(DNS_ZONEOPT_CHECKMX |
282						  DNS_ZONEOPT_CHECKMXFAIL);
283			} else {
284				fprintf(stderr, "invalid argument to -m: %s\n",
285					isc_commandline_argument);
286				exit(1);
287			}
288			break;
289
290		case 'o':
291			output_filename = isc_commandline_argument;
292			break;
293
294		case 'q':
295			quiet++;
296			break;
297
298		case 'r':
299			if (ARGCMP("warn")) {
300				zone_options |= DNS_ZONEOPT_CHECKDUPRR;
301				zone_options &= ~DNS_ZONEOPT_CHECKDUPRRFAIL;
302			} else if (ARGCMP("fail")) {
303				zone_options |= DNS_ZONEOPT_CHECKDUPRR |
304						DNS_ZONEOPT_CHECKDUPRRFAIL;
305			} else if (ARGCMP("ignore")) {
306				zone_options &= ~(DNS_ZONEOPT_CHECKDUPRR |
307						  DNS_ZONEOPT_CHECKDUPRRFAIL);
308			} else {
309				fprintf(stderr, "invalid argument to -r: %s\n",
310					isc_commandline_argument);
311				exit(1);
312			}
313			break;
314
315		case 's':
316			if (ARGCMP("full"))
317				outputstyle = &dns_master_style_full;
318			else if (ARGCMP("relative")) {
319				outputstyle = &dns_master_style_default;
320			} else {
321				fprintf(stderr,
322					"unknown or unsupported style: %s\n",
323					isc_commandline_argument);
324				exit(1);
325			}
326			break;
327
328		case 't':
329			result = isc_dir_chroot(isc_commandline_argument);
330			if (result != ISC_R_SUCCESS) {
331				fprintf(stderr, "isc_dir_chroot: %s: %s\n",
332					isc_commandline_argument,
333					isc_result_totext(result));
334				exit(1);
335			}
336			break;
337
338		case 'v':
339			printf(VERSION "\n");
340			exit(0);
341
342		case 'w':
343			workdir = isc_commandline_argument;
344			break;
345
346		case 'D':
347			dumpzone++;
348			break;
349
350		case 'M':
351			if (ARGCMP("fail")) {
352				zone_options &= ~DNS_ZONEOPT_WARNMXCNAME;
353				zone_options &= ~DNS_ZONEOPT_IGNOREMXCNAME;
354			} else if (ARGCMP("warn")) {
355				zone_options |= DNS_ZONEOPT_WARNMXCNAME;
356				zone_options &= ~DNS_ZONEOPT_IGNOREMXCNAME;
357			} else if (ARGCMP("ignore")) {
358				zone_options |= DNS_ZONEOPT_WARNMXCNAME;
359				zone_options |= DNS_ZONEOPT_IGNOREMXCNAME;
360			} else {
361				fprintf(stderr, "invalid argument to -M: %s\n",
362					isc_commandline_argument);
363				exit(1);
364			}
365			break;
366
367		case 'S':
368			if (ARGCMP("fail")) {
369				zone_options &= ~DNS_ZONEOPT_WARNSRVCNAME;
370				zone_options &= ~DNS_ZONEOPT_IGNORESRVCNAME;
371			} else if (ARGCMP("warn")) {
372				zone_options |= DNS_ZONEOPT_WARNSRVCNAME;
373				zone_options &= ~DNS_ZONEOPT_IGNORESRVCNAME;
374			} else if (ARGCMP("ignore")) {
375				zone_options |= DNS_ZONEOPT_WARNSRVCNAME;
376				zone_options |= DNS_ZONEOPT_IGNORESRVCNAME;
377			} else {
378				fprintf(stderr, "invalid argument to -S: %s\n",
379					isc_commandline_argument);
380				exit(1);
381			}
382			break;
383
384		case 'T':
385			if (ARGCMP("warn")) {
386				zone_options |= DNS_ZONEOPT_CHECKSPF;
387			} else if (ARGCMP("ignore")) {
388				zone_options &= ~DNS_ZONEOPT_CHECKSPF;
389			} else {
390				fprintf(stderr, "invalid argument to -T: %s\n",
391					isc_commandline_argument);
392				exit(1);
393			}
394			break;
395
396		case 'W':
397			if (ARGCMP("warn"))
398				zone_options |= DNS_ZONEOPT_CHECKWILDCARD;
399			else if (ARGCMP("ignore"))
400				zone_options &= ~DNS_ZONEOPT_CHECKWILDCARD;
401			break;
402
403		case '?':
404			if (isc_commandline_option != '?')
405				fprintf(stderr, "%s: invalid argument -%c\n",
406					prog_name, isc_commandline_option);
407			/* FALLTHROUGH */
408		case 'h':
409			usage();
410
411		default:
412			fprintf(stderr, "%s: unhandled option -%c\n",
413				prog_name, isc_commandline_option);
414			exit(1);
415		}
416	}
417
418	if (workdir != NULL) {
419		result = isc_dir_chdir(workdir);
420		if (result != ISC_R_SUCCESS) {
421			fprintf(stderr, "isc_dir_chdir: %s: %s\n",
422				workdir, isc_result_totext(result));
423			exit(1);
424		}
425	}
426
427	if (inputformatstr != NULL) {
428		if (strcasecmp(inputformatstr, "text") == 0)
429			inputformat = dns_masterformat_text;
430		else if (strcasecmp(inputformatstr, "raw") == 0)
431			inputformat = dns_masterformat_raw;
432		else if (strncasecmp(inputformatstr, "raw=", 4) == 0) {
433			inputformat = dns_masterformat_raw;
434			fprintf(stderr,
435				"WARNING: input format raw, version ignored\n");
436		} else {
437			fprintf(stderr, "unknown file format: %s\n",
438			    inputformatstr);
439			exit(1);
440		}
441	}
442
443	if (outputformatstr != NULL) {
444		if (strcasecmp(outputformatstr, "text") == 0) {
445			outputformat = dns_masterformat_text;
446		} else if (strcasecmp(outputformatstr, "raw") == 0) {
447			outputformat = dns_masterformat_raw;
448		} else if (strncasecmp(outputformatstr, "raw=", 4) == 0) {
449			char *end;
450
451			outputformat = dns_masterformat_raw;
452			rawversion = strtol(outputformatstr + 4, &end, 10);
453			if (end == outputformatstr + 4 || *end != '\0' ||
454			    rawversion > 1U) {
455				fprintf(stderr,
456					"unknown raw format version\n");
457				exit(1);
458			}
459		} else {
460			fprintf(stderr, "unknown file format: %s\n",
461				outputformatstr);
462			exit(1);
463		}
464	}
465
466	if (progmode == progmode_compile) {
467		dumpzone = 1;	/* always dump */
468		logdump = !quiet;
469		if (output_filename == NULL) {
470			fprintf(stderr,
471				"output file required, but not specified\n");
472			usage();
473		}
474	}
475
476	if (output_filename != NULL)
477		dumpzone = 1;
478
479	/*
480	 * If we are outputing to stdout then send the informational
481	 * output to stderr.
482	 */
483	if (dumpzone &&
484	    (output_filename == NULL ||
485	     strcmp(output_filename, "-") == 0 ||
486	     strcmp(output_filename, "/dev/fd/1") == 0 ||
487	     strcmp(output_filename, "/dev/stdout") == 0)) {
488		errout = stderr;
489		logdump = ISC_FALSE;
490	}
491
492	if (isc_commandline_index + 2 != argc)
493		usage();
494
495#ifdef _WIN32
496	InitSockets();
497#endif
498
499	RUNTIME_CHECK(isc_mem_create(0, 0, &mctx) == ISC_R_SUCCESS);
500	if (!quiet)
501		RUNTIME_CHECK(setup_logging(mctx, errout, &lctx)
502			      == ISC_R_SUCCESS);
503	RUNTIME_CHECK(isc_entropy_create(mctx, &ectx) == ISC_R_SUCCESS);
504	RUNTIME_CHECK(isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE)
505		      == ISC_R_SUCCESS);
506
507	dns_result_register();
508
509	origin = argv[isc_commandline_index++];
510	filename = argv[isc_commandline_index++];
511	result = load_zone(mctx, origin, filename, inputformat, classname,
512			   &zone);
513
514	if (snset) {
515		dns_master_initrawheader(&header);
516		header.flags = DNS_MASTERRAW_SOURCESERIALSET;
517		header.sourceserial = serialnum;
518		dns_zone_setrawdata(zone, &header);
519	}
520
521	if (result == ISC_R_SUCCESS && dumpzone) {
522		if (logdump) {
523			fprintf(errout, "dump zone to %s...", output_filename);
524			fflush(errout);
525		}
526		result = dump_zone(origin, zone, output_filename,
527				   outputformat, outputstyle, rawversion);
528		if (logdump)
529			fprintf(errout, "done\n");
530	}
531
532	if (!quiet && result == ISC_R_SUCCESS)
533		fprintf(errout, "OK\n");
534	destroy();
535	if (lctx != NULL)
536		isc_log_destroy(&lctx);
537	isc_hash_destroy();
538	isc_entropy_detach(&ectx);
539	isc_mem_destroy(&mctx);
540#ifdef _WIN32
541	DestroySockets();
542#endif
543	return ((result == ISC_R_SUCCESS) ? 0 : 1);
544}
545