1/*	$NetBSD$	*/
2
3/*
4 * Copyright (C) 2004-2011  Internet Systems Consortium, Inc. ("ISC")
5 * Copyright (C) 1999-2003  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-checkzone.c,v 1.65 2011/12/22 17:29:22 each Exp  */
21
22/*! \file */
23
24#include <config.h>
25
26#include <stdlib.h>
27
28#include <isc/app.h>
29#include <isc/commandline.h>
30#include <isc/dir.h>
31#include <isc/entropy.h>
32#include <isc/hash.h>
33#include <isc/log.h>
34#include <isc/mem.h>
35#include <isc/socket.h>
36#include <isc/string.h>
37#include <isc/task.h>
38#include <isc/timer.h>
39#include <isc/util.h>
40
41#include <dns/db.h>
42#include <dns/fixedname.h>
43#include <dns/log.h>
44#include <dns/master.h>
45#include <dns/masterdump.h>
46#include <dns/name.h>
47#include <dns/rdataclass.h>
48#include <dns/rdataset.h>
49#include <dns/result.h>
50#include <dns/types.h>
51#include <dns/zone.h>
52
53#include "check-tool.h"
54
55static int quiet = 0;
56static isc_mem_t *mctx = NULL;
57static isc_entropy_t *ectx = NULL;
58dns_zone_t *zone = NULL;
59dns_zonetype_t zonetype = dns_zone_master;
60static int dumpzone = 0;
61static const char *output_filename;
62static char *prog_name = NULL;
63static const dns_master_style_t *outputstyle = NULL;
64static enum { progmode_check, progmode_compile } progmode;
65
66#define ERRRET(result, function) \
67	do { \
68		if (result != ISC_R_SUCCESS) { \
69			if (!quiet) \
70				fprintf(stderr, "%s() returned %s\n", \
71					function, dns_result_totext(result)); \
72			return (result); \
73		} \
74	} while (/*CONSTCOND*/0)
75
76ISC_PLATFORM_NORETURN_PRE static void
77usage(void) ISC_PLATFORM_NORETURN_POST;
78
79static void
80usage(void) {
81	fprintf(stderr,
82		"usage: %s [-djqvD] [-c class] "
83		"[-f inputformat] [-F outputformat] "
84		"[-t directory] [-w directory] [-k (ignore|warn|fail)] "
85		"[-n (ignore|warn|fail)] [-m (ignore|warn|fail)] "
86		"[-r (ignore|warn|fail)] "
87		"[-i (full|full-sibling|local|local-sibling|none)] "
88		"[-M (ignore|warn|fail)] [-S (ignore|warn|fail)] "
89		"[-W (ignore|warn)] "
90		"%s zonename filename\n",
91		prog_name,
92		progmode == progmode_check ? "[-o filename]" : "-o filename");
93	exit(1);
94}
95
96static void
97destroy(void) {
98	if (zone != NULL)
99		dns_zone_detach(&zone);
100	dns_name_destroy();
101}
102
103/*% main processing routine */
104int
105main(int argc, char **argv) {
106	int c;
107	char *origin = NULL;
108	char *filename = NULL;
109	isc_log_t *lctx = NULL;
110	isc_result_t result;
111	char classname_in[] = "IN";
112	char *classname = classname_in;
113	const char *workdir = NULL;
114	const char *inputformatstr = NULL;
115	const char *outputformatstr = NULL;
116	dns_masterformat_t inputformat = dns_masterformat_text;
117	dns_masterformat_t outputformat = dns_masterformat_text;
118	dns_masterrawheader_t header;
119	isc_uint32_t rawversion = 1, serialnum = 0;
120	isc_boolean_t snset = ISC_FALSE;
121	isc_boolean_t logdump = ISC_FALSE;
122	FILE *errout = stdout;
123	char *endp;
124
125	outputstyle = &dns_master_style_full;
126
127	prog_name = strrchr(argv[0], '/');
128	if (prog_name == NULL)
129		prog_name = strrchr(argv[0], '\\');
130	if (prog_name != NULL)
131		prog_name++;
132	else
133		prog_name = argv[0];
134	/*
135	 * Libtool doesn't preserve the program name prior to final
136	 * installation.  Remove the libtool prefix ("lt-").
137	 */
138	if (strncmp(prog_name, "lt-", 3) == 0)
139		prog_name += 3;
140
141#define PROGCMP(X) \
142	(strcasecmp(prog_name, X) == 0 || strcasecmp(prog_name, X ".exe") == 0)
143
144	if (PROGCMP("named-checkzone"))
145		progmode = progmode_check;
146	else if (PROGCMP("named-compilezone"))
147		progmode = progmode_compile;
148	else
149		INSIST(0);
150
151	/* Compilation specific defaults */
152	if (progmode == progmode_compile) {
153		zone_options |= (DNS_ZONEOPT_CHECKNS |
154				 DNS_ZONEOPT_FATALNS |
155				 DNS_ZONEOPT_CHECKDUPRR |
156				 DNS_ZONEOPT_CHECKNAMES |
157				 DNS_ZONEOPT_CHECKNAMESFAIL |
158				 DNS_ZONEOPT_CHECKWILDCARD);
159	} else
160		zone_options |= DNS_ZONEOPT_CHECKDUPRR;
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: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 'W':
385			if (ARGCMP("warn"))
386				zone_options |= DNS_ZONEOPT_CHECKWILDCARD;
387			else if (ARGCMP("ignore"))
388				zone_options &= ~DNS_ZONEOPT_CHECKWILDCARD;
389			break;
390
391		case '?':
392			if (isc_commandline_option != '?')
393				fprintf(stderr, "%s: invalid argument -%c\n",
394					prog_name, isc_commandline_option);
395		case 'h':
396			usage();
397
398		default:
399			fprintf(stderr, "%s: unhandled option -%c\n",
400				prog_name, isc_commandline_option);
401			exit(1);
402		}
403	}
404
405	if (workdir != NULL) {
406		result = isc_dir_chdir(workdir);
407		if (result != ISC_R_SUCCESS) {
408			fprintf(stderr, "isc_dir_chdir: %s: %s\n",
409				workdir, isc_result_totext(result));
410			exit(1);
411		}
412	}
413
414	if (inputformatstr != NULL) {
415		if (strcasecmp(inputformatstr, "text") == 0)
416			inputformat = dns_masterformat_text;
417		else if (strcasecmp(inputformatstr, "raw") == 0)
418			inputformat = dns_masterformat_raw;
419		else if (strncasecmp(inputformatstr, "raw=", 4) == 0) {
420			inputformat = dns_masterformat_raw;
421			fprintf(stderr,
422				"WARNING: input format raw, version ignored\n");
423		} else {
424			fprintf(stderr, "unknown file format: %s\n",
425			    inputformatstr);
426			exit(1);
427		}
428	}
429
430	if (outputformatstr != NULL) {
431		if (strcasecmp(outputformatstr, "text") == 0) {
432			outputformat = dns_masterformat_text;
433		} else if (strcasecmp(outputformatstr, "raw") == 0) {
434			outputformat = dns_masterformat_raw;
435		} else if (strncasecmp(outputformatstr, "raw=", 4) == 0) {
436			char *end;
437
438			outputformat = dns_masterformat_raw;
439			rawversion = strtol(outputformatstr + 4, &end, 10);
440			if (end == outputformatstr + 4 || *end != '\0' ||
441			    rawversion > 1U) {
442				fprintf(stderr,
443					"unknown raw format version\n");
444				exit(1);
445			}
446		} else {
447			fprintf(stderr, "unknown file format: %s\n",
448				outputformatstr);
449			exit(1);
450		}
451	}
452
453	if (progmode == progmode_compile) {
454		dumpzone = 1;	/* always dump */
455		logdump = !quiet;
456		if (output_filename == NULL) {
457			fprintf(stderr,
458				"output file required, but not specified\n");
459			usage();
460		}
461	}
462
463	if (output_filename != NULL)
464		dumpzone = 1;
465
466	/*
467	 * If we are outputing to stdout then send the informational
468	 * output to stderr.
469	 */
470	if (dumpzone &&
471	    (output_filename == NULL ||
472	     strcmp(output_filename, "-") == 0 ||
473	     strcmp(output_filename, "/dev/fd/1") == 0 ||
474	     strcmp(output_filename, "/dev/stdout") == 0)) {
475		errout = stderr;
476		logdump = ISC_FALSE;
477	}
478
479	if (isc_commandline_index + 2 != argc)
480		usage();
481
482#ifdef _WIN32
483	InitSockets();
484#endif
485
486	RUNTIME_CHECK(isc_mem_create(0, 0, &mctx) == ISC_R_SUCCESS);
487	if (!quiet)
488		RUNTIME_CHECK(setup_logging(mctx, errout, &lctx)
489			      == ISC_R_SUCCESS);
490	RUNTIME_CHECK(isc_entropy_create(mctx, &ectx) == ISC_R_SUCCESS);
491	RUNTIME_CHECK(isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE)
492		      == ISC_R_SUCCESS);
493
494	dns_result_register();
495
496	origin = argv[isc_commandline_index++];
497	filename = argv[isc_commandline_index++];
498	result = load_zone(mctx, origin, filename, inputformat, classname,
499			   &zone);
500
501	if (snset) {
502		dns_master_initrawheader(&header);
503		header.flags = DNS_MASTERRAW_SOURCESERIALSET;
504		header.sourceserial = serialnum;
505		dns_zone_setrawdata(zone, &header);
506	}
507
508	if (result == ISC_R_SUCCESS && dumpzone) {
509		if (logdump) {
510			fprintf(errout, "dump zone to %s...", output_filename);
511			fflush(errout);
512		}
513		result = dump_zone(origin, zone, output_filename,
514				   outputformat, outputstyle, rawversion);
515		if (logdump)
516			fprintf(errout, "done\n");
517	}
518
519	if (!quiet && result == ISC_R_SUCCESS)
520		fprintf(errout, "OK\n");
521	destroy();
522	if (lctx != NULL)
523		isc_log_destroy(&lctx);
524	isc_hash_destroy();
525	isc_entropy_detach(&ectx);
526	isc_mem_destroy(&mctx);
527#ifdef _WIN32
528	DestroySockets();
529#endif
530	return ((result == ISC_R_SUCCESS) ? 0 : 1);
531}
532