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