Home | History | Annotate | Line # | Download | only in dns
      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