xref: /netbsd-src/external/mpl/bind/dist/tests/dns/master_test.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: master_test.c,v 1.3 2025/01/26 16:25:47 christos Exp $	*/
2 
3 /*
4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5  *
6  * SPDX-License-Identifier: MPL-2.0
7  *
8  * This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11  *
12  * See the COPYRIGHT file distributed with this work for additional
13  * information regarding copyright ownership.
14  */
15 
16 #include <inttypes.h>
17 #include <sched.h> /* IWYU pragma: keep */
18 #include <setjmp.h>
19 #include <stdarg.h>
20 #include <stdbool.h>
21 #include <stddef.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 
26 #define UNIT_TESTING
27 #include <cmocka.h>
28 
29 #include <isc/dir.h>
30 #include <isc/string.h>
31 #include <isc/util.h>
32 
33 #include <dns/cache.h>
34 #include <dns/callbacks.h>
35 #include <dns/db.h>
36 #include <dns/master.h>
37 #include <dns/masterdump.h>
38 #include <dns/name.h>
39 #include <dns/rdata.h>
40 #include <dns/rdatalist.h>
41 #include <dns/rdataset.h>
42 
43 #include <tests/dns.h>
44 
45 static void
46 nullmsg(dns_rdatacallbacks_t *cb, const char *fmt, ...) {
47 	UNUSED(cb);
48 	UNUSED(fmt);
49 }
50 
51 #define BUFLEN	    255
52 #define BIGBUFLEN   (70 * 1024)
53 #define TEST_ORIGIN "test"
54 
55 static dns_masterrawheader_t header;
56 static bool headerset;
57 
58 dns_name_t dns_origin;
59 char origin[sizeof(TEST_ORIGIN)];
60 unsigned char name_buf[BUFLEN];
61 dns_rdatacallbacks_t callbacks;
62 char *include_file = NULL;
63 
64 static void
65 rawdata_callback(dns_zone_t *zone, dns_masterrawheader_t *header);
66 
67 static isc_result_t
68 add_callback(void *arg, const dns_name_t *owner,
69 	     dns_rdataset_t *dataset DNS__DB_FLARG) {
70 	char buf[BIGBUFLEN];
71 	isc_buffer_t target;
72 	isc_result_t result;
73 
74 	UNUSED(arg);
75 
76 	isc_buffer_init(&target, buf, BIGBUFLEN);
77 	result = dns_rdataset_totext(dataset, owner, false, false, &target);
78 	return result;
79 }
80 
81 static void
82 rawdata_callback(dns_zone_t *zone, dns_masterrawheader_t *h) {
83 	UNUSED(zone);
84 	header = *h;
85 	headerset = true;
86 }
87 
88 static isc_result_t
89 setup_master(void (*warn)(struct dns_rdatacallbacks *, const char *, ...),
90 	     void (*error)(struct dns_rdatacallbacks *, const char *, ...)) {
91 	isc_result_t result;
92 	int len;
93 	isc_buffer_t source;
94 	isc_buffer_t target;
95 
96 	strlcpy(origin, TEST_ORIGIN, sizeof(origin));
97 	len = strlen(origin);
98 	isc_buffer_init(&source, origin, len);
99 	isc_buffer_add(&source, len);
100 	isc_buffer_setactive(&source, len);
101 	isc_buffer_init(&target, name_buf, BUFLEN);
102 	dns_name_init(&dns_origin, NULL);
103 	dns_master_initrawheader(&header);
104 
105 	result = dns_name_fromtext(&dns_origin, &source, dns_rootname, 0,
106 				   &target);
107 	if (result != ISC_R_SUCCESS) {
108 		return result;
109 	}
110 
111 	dns_rdatacallbacks_init_stdio(&callbacks);
112 	callbacks.add = add_callback;
113 	callbacks.rawdata = rawdata_callback;
114 	callbacks.zone = NULL;
115 	if (warn != NULL) {
116 		callbacks.warn = warn;
117 	}
118 	if (error != NULL) {
119 		callbacks.error = error;
120 	}
121 	headerset = false;
122 	return result;
123 }
124 
125 static isc_result_t
126 test_master(const char *workdir, const char *testfile,
127 	    dns_masterformat_t format,
128 	    void (*warn)(struct dns_rdatacallbacks *, const char *, ...),
129 	    void (*error)(struct dns_rdatacallbacks *, const char *, ...)) {
130 	isc_result_t result;
131 
132 	result = setup_master(warn, error);
133 	if (result != ISC_R_SUCCESS) {
134 		return result;
135 	}
136 
137 	dns_rdatacallbacks_init_stdio(&callbacks);
138 	callbacks.add = add_callback;
139 	callbacks.rawdata = rawdata_callback;
140 	callbacks.zone = NULL;
141 	if (warn != NULL) {
142 		callbacks.warn = warn;
143 	}
144 	if (error != NULL) {
145 		callbacks.error = error;
146 	}
147 
148 	if (workdir != NULL) {
149 		result = isc_dir_chdir(workdir);
150 		if (result != ISC_R_SUCCESS) {
151 			return result;
152 		}
153 	}
154 
155 	result = dns_master_loadfile(testfile, &dns_origin, &dns_origin,
156 				     dns_rdataclass_in, true, 0, &callbacks,
157 				     NULL, NULL, mctx, format, 0);
158 
159 	return result;
160 }
161 
162 static void
163 include_callback(const char *filename, void *arg) {
164 	char **argp = (char **)arg;
165 	*argp = isc_mem_strdup(mctx, filename);
166 }
167 
168 /*
169  * Successful load test:
170  * dns_master_loadfile() loads a valid master file and returns success
171  */
172 ISC_RUN_TEST_IMPL(load) {
173 	isc_result_t result;
174 
175 	UNUSED(state);
176 
177 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master1.data",
178 			     dns_masterformat_text, nullmsg, nullmsg);
179 	assert_int_equal(result, ISC_R_SUCCESS);
180 }
181 
182 /*
183  * Unexpected end of file test:
184  * dns_master_loadfile() returns DNS_R_UNEXPECTED when file ends too soon
185  */
186 ISC_RUN_TEST_IMPL(unexpected) {
187 	isc_result_t result;
188 
189 	UNUSED(state);
190 
191 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master2.data",
192 			     dns_masterformat_text, nullmsg, nullmsg);
193 	assert_int_equal(result, ISC_R_UNEXPECTEDEND);
194 }
195 
196 /*
197  * No owner test:
198  * dns_master_loadfile() accepts broken zones with no TTL for first record
199  * if it is an SOA
200  */
201 ISC_RUN_TEST_IMPL(noowner) {
202 	isc_result_t result;
203 
204 	UNUSED(state);
205 
206 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master3.data",
207 			     dns_masterformat_text, nullmsg, nullmsg);
208 	assert_int_equal(result, DNS_R_NOOWNER);
209 }
210 
211 /*
212  * No TTL test:
213  * dns_master_loadfile() returns DNS_R_NOOWNER when no owner name is
214  * specified
215  */
216 ISC_RUN_TEST_IMPL(nottl) {
217 	isc_result_t result;
218 
219 	UNUSED(state);
220 
221 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master4.data",
222 			     dns_masterformat_text, nullmsg, nullmsg);
223 	assert_int_equal(result, ISC_R_SUCCESS);
224 }
225 
226 /*
227  * Bad class test:
228  * dns_master_loadfile() returns DNS_R_BADCLASS when record class doesn't
229  * match zone class
230  */
231 ISC_RUN_TEST_IMPL(badclass) {
232 	isc_result_t result;
233 
234 	UNUSED(state);
235 
236 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master5.data",
237 			     dns_masterformat_text, nullmsg, nullmsg);
238 	assert_int_equal(result, DNS_R_BADCLASS);
239 }
240 
241 /*
242  * Too big rdata test:
243  * dns_master_loadfile() returns ISC_R_NOSPACE when record is too big
244  */
245 ISC_RUN_TEST_IMPL(toobig) {
246 	isc_result_t result;
247 
248 	UNUSED(state);
249 
250 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master15.data",
251 			     dns_masterformat_text, nullmsg, nullmsg);
252 	assert_int_equal(result, ISC_R_NOSPACE);
253 }
254 
255 /*
256  * Maximum rdata test:
257  * dns_master_loadfile() returns ISC_R_SUCCESS when record is maximum size
258  */
259 ISC_RUN_TEST_IMPL(maxrdata) {
260 	isc_result_t result;
261 
262 	UNUSED(state);
263 
264 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master16.data",
265 			     dns_masterformat_text, nullmsg, nullmsg);
266 	assert_int_equal(result, ISC_R_SUCCESS);
267 }
268 
269 /*
270  * DNSKEY test:
271  * dns_master_loadfile() understands DNSKEY with key material
272  */
273 ISC_RUN_TEST_IMPL(dnskey) {
274 	isc_result_t result;
275 
276 	UNUSED(state);
277 
278 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master6.data",
279 			     dns_masterformat_text, nullmsg, nullmsg);
280 	assert_int_equal(result, ISC_R_SUCCESS);
281 }
282 
283 /*
284  * DNSKEY with no key material test:
285  * dns_master_loadfile() understands DNSKEY with no key material
286  *
287  * RFC 4034 removed the ability to signal NOKEY, so empty key material should
288  * be rejected.
289  */
290 ISC_RUN_TEST_IMPL(dnsnokey) {
291 	isc_result_t result;
292 
293 	UNUSED(state);
294 
295 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master7.data",
296 			     dns_masterformat_text, nullmsg, nullmsg);
297 	assert_int_equal(result, ISC_R_UNEXPECTEDEND);
298 }
299 
300 /*
301  * Include test:
302  * dns_master_loadfile() understands $INCLUDE
303  */
304 ISC_RUN_TEST_IMPL(include) {
305 	isc_result_t result;
306 
307 	UNUSED(state);
308 
309 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master8.data",
310 			     dns_masterformat_text, nullmsg, nullmsg);
311 	assert_int_equal(result, DNS_R_SEENINCLUDE);
312 }
313 
314 /*
315  * Include file list test:
316  * dns_master_loadfile4() returns names of included file
317  */
318 ISC_RUN_TEST_IMPL(master_includelist) {
319 	isc_result_t result;
320 	char *filename = NULL;
321 
322 	UNUSED(state);
323 
324 	result = setup_master(nullmsg, nullmsg);
325 	assert_int_equal(result, ISC_R_SUCCESS);
326 
327 	result = isc_dir_chdir(SRCDIR);
328 	assert_int_equal(result, ISC_R_SUCCESS);
329 
330 	result = dns_master_loadfile(
331 		TESTS_DIR "/testdata/master/master8.data", &dns_origin,
332 		&dns_origin, dns_rdataclass_in, 0, true, &callbacks,
333 		include_callback, &filename, mctx, dns_masterformat_text, 0);
334 	assert_int_equal(result, DNS_R_SEENINCLUDE);
335 	assert_non_null(filename);
336 	if (filename != NULL) {
337 		assert_string_equal(filename, "testdata/master/master6.data");
338 		isc_mem_free(mctx, filename);
339 	}
340 }
341 
342 /*
343  * Include failure test:
344  * dns_master_loadfile() understands $INCLUDE failures
345  */
346 ISC_RUN_TEST_IMPL(includefail) {
347 	isc_result_t result;
348 
349 	UNUSED(state);
350 
351 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master9.data",
352 			     dns_masterformat_text, nullmsg, nullmsg);
353 	assert_int_equal(result, DNS_R_BADCLASS);
354 }
355 
356 /*
357  * Non-empty blank lines test:
358  * dns_master_loadfile() handles non-empty blank lines
359  */
360 ISC_RUN_TEST_IMPL(blanklines) {
361 	isc_result_t result;
362 
363 	UNUSED(state);
364 
365 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master10.data",
366 			     dns_masterformat_text, nullmsg, nullmsg);
367 	assert_int_equal(result, ISC_R_SUCCESS);
368 }
369 
370 /*
371  * SOA leading zeroes test:
372  * dns_master_loadfile() allows leading zeroes in SOA
373  */
374 
375 ISC_RUN_TEST_IMPL(leadingzero) {
376 	isc_result_t result;
377 
378 	UNUSED(state);
379 
380 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master11.data",
381 			     dns_masterformat_text, nullmsg, nullmsg);
382 	assert_int_equal(result, ISC_R_SUCCESS);
383 }
384 
385 /* masterfile totext tests */
386 ISC_RUN_TEST_IMPL(totext) {
387 	isc_result_t result;
388 	dns_rdataset_t rdataset;
389 	dns_rdatalist_t rdatalist;
390 	isc_buffer_t target;
391 	unsigned char buf[BIGBUFLEN];
392 
393 	UNUSED(state);
394 
395 	/* First, test with an empty rdataset */
396 	dns_rdatalist_init(&rdatalist);
397 	rdatalist.rdclass = dns_rdataclass_in;
398 	rdatalist.type = dns_rdatatype_none;
399 	rdatalist.covers = dns_rdatatype_none;
400 
401 	dns_rdataset_init(&rdataset);
402 	dns_rdatalist_tordataset(&rdatalist, &rdataset);
403 
404 	isc_buffer_init(&target, buf, BIGBUFLEN);
405 	result = dns_master_rdatasettotext(dns_rootname, &rdataset,
406 					   &dns_master_style_debug, NULL,
407 					   &target);
408 	assert_int_equal(result, ISC_R_SUCCESS);
409 	assert_int_equal(isc_buffer_usedlength(&target), 0);
410 
411 	/*
412 	 * XXX: We will also need to add tests for dumping various
413 	 * rdata types, classes, etc, and comparing the results against
414 	 * known-good output.
415 	 */
416 }
417 
418 /*
419  * Raw load test:
420  * dns_master_loadfile() loads a valid raw file and returns success
421  */
422 ISC_RUN_TEST_IMPL(loadraw) {
423 	isc_result_t result;
424 
425 	UNUSED(state);
426 
427 	/* Raw format version 0 */
428 	result = test_master(BUILDDIR, "testdata/master/master12.data",
429 			     dns_masterformat_raw, nullmsg, nullmsg);
430 	assert_string_equal(isc_result_totext(result), "success");
431 	assert_true(headerset);
432 	assert_int_equal(header.flags, 0);
433 
434 	/* Raw format version 1, no source serial  */
435 	result = test_master(BUILDDIR, "testdata/master/master13.data",
436 			     dns_masterformat_raw, nullmsg, nullmsg);
437 	assert_string_equal(isc_result_totext(result), "success");
438 	assert_true(headerset);
439 	assert_int_equal(header.flags, 0);
440 
441 	/* Raw format version 1, source serial == 2011120101 */
442 	result = test_master(BUILDDIR, "testdata/master/master14.data",
443 			     dns_masterformat_raw, nullmsg, nullmsg);
444 	assert_string_equal(isc_result_totext(result), "success");
445 	assert_true(headerset);
446 	assert_true((header.flags & DNS_MASTERRAW_SOURCESERIALSET) != 0);
447 	assert_int_equal(header.sourceserial, 2011120101);
448 }
449 
450 /*
451  * Raw dump test:
452  * dns_master_dump*() functions dump valid raw files
453  */
454 ISC_RUN_TEST_IMPL(dumpraw) {
455 	isc_result_t result;
456 	dns_db_t *db = NULL;
457 	dns_dbversion_t *version = NULL;
458 	char myorigin[sizeof(TEST_ORIGIN)];
459 	dns_name_t dnsorigin;
460 	isc_buffer_t source, target;
461 	unsigned char namebuf[BUFLEN];
462 	int len;
463 
464 	UNUSED(state);
465 
466 	strlcpy(myorigin, TEST_ORIGIN, sizeof(myorigin));
467 	len = strlen(myorigin);
468 	isc_buffer_init(&source, myorigin, len);
469 	isc_buffer_add(&source, len);
470 	isc_buffer_setactive(&source, len);
471 	isc_buffer_init(&target, namebuf, BUFLEN);
472 	dns_name_init(&dnsorigin, NULL);
473 	result = dns_name_fromtext(&dnsorigin, &source, dns_rootname, 0,
474 				   &target);
475 	assert_int_equal(result, ISC_R_SUCCESS);
476 
477 	result = dns_db_create(mctx, ZONEDB_DEFAULT, &dnsorigin,
478 			       dns_dbtype_zone, dns_rdataclass_in, 0, NULL,
479 			       &db);
480 	assert_int_equal(result, ISC_R_SUCCESS);
481 
482 	result = isc_dir_chdir(SRCDIR);
483 	assert_int_equal(result, ISC_R_SUCCESS);
484 
485 	result = dns_db_load(db, TESTS_DIR "/testdata/master/master1.data",
486 			     dns_masterformat_text, 0);
487 	assert_int_equal(result, ISC_R_SUCCESS);
488 
489 	result = isc_dir_chdir(BUILDDIR);
490 	assert_int_equal(result, ISC_R_SUCCESS);
491 
492 	dns_db_currentversion(db, &version);
493 
494 	result = dns_master_dump(mctx, db, version, &dns_master_style_default,
495 				 "test.dump", dns_masterformat_raw, NULL);
496 	assert_int_equal(result, ISC_R_SUCCESS);
497 
498 	result = test_master(NULL, "test.dump", dns_masterformat_raw, nullmsg,
499 			     nullmsg);
500 	assert_string_equal(isc_result_totext(result), "success");
501 	assert_true(headerset);
502 	assert_int_equal(header.flags, 0);
503 
504 	dns_master_initrawheader(&header);
505 	header.sourceserial = 12345;
506 	header.flags |= DNS_MASTERRAW_SOURCESERIALSET;
507 
508 	unlink("test.dump");
509 	result = dns_master_dump(mctx, db, version, &dns_master_style_default,
510 				 "test.dump", dns_masterformat_raw, &header);
511 	assert_int_equal(result, ISC_R_SUCCESS);
512 
513 	result = test_master(NULL, "test.dump", dns_masterformat_raw, nullmsg,
514 			     nullmsg);
515 	assert_string_equal(isc_result_totext(result), "success");
516 	assert_true(headerset);
517 	assert_true((header.flags & DNS_MASTERRAW_SOURCESERIALSET) != 0);
518 	assert_int_equal(header.sourceserial, 12345);
519 
520 	unlink("test.dump");
521 	dns_db_closeversion(db, &version, false);
522 	dns_db_detach(&db);
523 }
524 
525 static const char *warn_expect_value;
526 static bool warn_expect_result;
527 
528 static void
529 warn_expect(struct dns_rdatacallbacks *mycallbacks, const char *fmt, ...) {
530 	char buf[4096];
531 	va_list ap;
532 
533 	UNUSED(mycallbacks);
534 
535 	warn_expect_result = false;
536 
537 	va_start(ap, fmt);
538 	vsnprintf(buf, sizeof(buf), fmt, ap);
539 	va_end(ap);
540 
541 	if (warn_expect_value != NULL && strstr(buf, warn_expect_value) != NULL)
542 	{
543 		warn_expect_result = true;
544 	}
545 }
546 
547 /*
548  * Origin change test:
549  * dns_master_loadfile() rejects zones with inherited name following $ORIGIN
550  */
551 ISC_RUN_TEST_IMPL(neworigin) {
552 	isc_result_t result;
553 
554 	UNUSED(state);
555 
556 	warn_expect_value = "record with inherited owner";
557 	result = test_master(SRCDIR, TESTS_DIR "/testdata/master/master17.data",
558 			     dns_masterformat_text, warn_expect, nullmsg);
559 	assert_int_equal(result, ISC_R_SUCCESS);
560 	assert_true(warn_expect_result);
561 }
562 
563 ISC_TEST_LIST_START
564 ISC_TEST_ENTRY(load)
565 ISC_TEST_ENTRY(unexpected)
566 ISC_TEST_ENTRY(noowner)
567 ISC_TEST_ENTRY(nottl)
568 ISC_TEST_ENTRY(badclass)
569 ISC_TEST_ENTRY(dnskey)
570 ISC_TEST_ENTRY(dnsnokey)
571 ISC_TEST_ENTRY(include)
572 ISC_TEST_ENTRY(master_includelist)
573 ISC_TEST_ENTRY(includefail)
574 ISC_TEST_ENTRY(blanklines)
575 ISC_TEST_ENTRY(leadingzero)
576 ISC_TEST_ENTRY(totext)
577 ISC_TEST_ENTRY(loadraw)
578 ISC_TEST_ENTRY(dumpraw)
579 ISC_TEST_ENTRY(toobig)
580 ISC_TEST_ENTRY(maxrdata)
581 ISC_TEST_ENTRY(neworigin)
582 ISC_TEST_LIST_END
583 
584 ISC_TEST_MAIN
585