Home | History | Annotate | Line # | Download | only in check
      1  1.11  christos /*	$NetBSD: check-tool.c,v 1.12 2026/01/29 18:36:26 christos Exp $	*/
      2   1.1  christos 
      3   1.1  christos /*
      4   1.1  christos  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      5   1.1  christos  *
      6   1.8  christos  * SPDX-License-Identifier: MPL-2.0
      7   1.8  christos  *
      8   1.1  christos  * This Source Code Form is subject to the terms of the Mozilla Public
      9   1.1  christos  * License, v. 2.0. If a copy of the MPL was not distributed with this
     10   1.6  christos  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
     11   1.1  christos  *
     12   1.1  christos  * See the COPYRIGHT file distributed with this work for additional
     13   1.1  christos  * information regarding copyright ownership.
     14   1.1  christos  */
     15   1.1  christos 
     16   1.1  christos /*! \file */
     17   1.1  christos 
     18   1.5  christos #include <inttypes.h>
     19  1.11  christos #include <netdb.h>
     20   1.3  christos #include <stdbool.h>
     21   1.1  christos #include <stdio.h>
     22   1.1  christos 
     23   1.1  christos #include <isc/buffer.h>
     24   1.1  christos #include <isc/log.h>
     25   1.1  christos #include <isc/mem.h>
     26   1.5  christos #include <isc/net.h>
     27   1.1  christos #include <isc/region.h>
     28  1.10  christos #include <isc/result.h>
     29   1.1  christos #include <isc/stdio.h>
     30   1.1  christos #include <isc/string.h>
     31   1.1  christos #include <isc/symtab.h>
     32   1.1  christos #include <isc/types.h>
     33   1.1  christos #include <isc/util.h>
     34   1.1  christos 
     35   1.1  christos #include <dns/db.h>
     36   1.1  christos #include <dns/dbiterator.h>
     37   1.1  christos #include <dns/fixedname.h>
     38   1.1  christos #include <dns/log.h>
     39   1.1  christos #include <dns/name.h>
     40   1.1  christos #include <dns/rdata.h>
     41   1.1  christos #include <dns/rdataclass.h>
     42   1.1  christos #include <dns/rdataset.h>
     43   1.1  christos #include <dns/rdatasetiter.h>
     44   1.1  christos #include <dns/rdatatype.h>
     45   1.1  christos #include <dns/types.h>
     46   1.1  christos #include <dns/zone.h>
     47   1.1  christos 
     48   1.1  christos #include <isccfg/log.h>
     49   1.1  christos 
     50   1.1  christos #include <ns/log.h>
     51   1.1  christos 
     52   1.5  christos #include "check-tool.h"
     53   1.5  christos 
     54   1.1  christos #ifndef CHECK_SIBLING
     55   1.1  christos #define CHECK_SIBLING 1
     56   1.5  christos #endif /* ifndef CHECK_SIBLING */
     57   1.1  christos 
     58   1.1  christos #ifndef CHECK_LOCAL
     59   1.1  christos #define CHECK_LOCAL 1
     60   1.5  christos #endif /* ifndef CHECK_LOCAL */
     61   1.1  christos 
     62   1.5  christos #define ERR_IS_CNAME	   1
     63   1.5  christos #define ERR_NO_ADDRESSES   2
     64   1.1  christos #define ERR_LOOKUP_FAILURE 3
     65   1.5  christos #define ERR_EXTRA_A	   4
     66   1.5  christos #define ERR_EXTRA_AAAA	   5
     67   1.5  christos #define ERR_MISSING_GLUE   5
     68   1.5  christos #define ERR_IS_MXCNAME	   6
     69   1.5  christos #define ERR_IS_SRVCNAME	   7
     70   1.1  christos 
     71  1.11  christos static const char *dbtype[] = { ZONEDB_DEFAULT };
     72   1.1  christos 
     73   1.1  christos int debug = 0;
     74   1.1  christos const char *journal = NULL;
     75   1.3  christos bool nomerge = true;
     76   1.1  christos #if CHECK_LOCAL
     77   1.3  christos bool docheckmx = true;
     78   1.3  christos bool dochecksrv = true;
     79   1.3  christos bool docheckns = true;
     80   1.5  christos #else  /* if CHECK_LOCAL */
     81   1.3  christos bool docheckmx = false;
     82   1.3  christos bool dochecksrv = false;
     83   1.3  christos bool docheckns = false;
     84   1.5  christos #endif /* if CHECK_LOCAL */
     85   1.5  christos dns_zoneopt_t zone_options = DNS_ZONEOPT_CHECKNS | DNS_ZONEOPT_CHECKMX |
     86  1.11  christos 			     DNS_ZONEOPT_CHECKDUPRR | DNS_ZONEOPT_CHECKSPF |
     87   1.5  christos 			     DNS_ZONEOPT_MANYERRORS | DNS_ZONEOPT_CHECKNAMES |
     88   1.3  christos 			     DNS_ZONEOPT_CHECKINTEGRITY |
     89   1.1  christos #if CHECK_SIBLING
     90   1.3  christos 			     DNS_ZONEOPT_CHECKSIBLING |
     91   1.5  christos #endif /* if CHECK_SIBLING */
     92  1.11  christos 			     DNS_ZONEOPT_CHECKSVCB | DNS_ZONEOPT_CHECKWILDCARD |
     93   1.5  christos 			     DNS_ZONEOPT_WARNMXCNAME | DNS_ZONEOPT_WARNSRVCNAME;
     94   1.1  christos 
     95   1.1  christos /*
     96   1.1  christos  * This needs to match the list in bin/named/log.c.
     97   1.1  christos  */
     98   1.5  christos static isc_logcategory_t categories[] = { { "", 0 },
     99   1.5  christos 					  { "unmatched", 0 },
    100   1.5  christos 					  { NULL, 0 } };
    101   1.1  christos 
    102   1.1  christos static isc_symtab_t *symtab = NULL;
    103   1.1  christos static isc_mem_t *sym_mctx;
    104   1.1  christos 
    105   1.1  christos static void
    106   1.1  christos freekey(char *key, unsigned int type, isc_symvalue_t value, void *userarg) {
    107   1.1  christos 	UNUSED(type);
    108   1.1  christos 	UNUSED(value);
    109   1.1  christos 	isc_mem_free(userarg, key);
    110   1.1  christos }
    111   1.1  christos 
    112   1.1  christos static void
    113   1.1  christos add(char *key, int value) {
    114   1.1  christos 	isc_result_t result;
    115   1.1  christos 	isc_symvalue_t symvalue;
    116   1.1  christos 
    117   1.1  christos 	if (sym_mctx == NULL) {
    118   1.5  christos 		isc_mem_create(&sym_mctx);
    119   1.1  christos 	}
    120   1.1  christos 
    121   1.1  christos 	if (symtab == NULL) {
    122   1.1  christos 		result = isc_symtab_create(sym_mctx, 100, freekey, sym_mctx,
    123   1.3  christos 					   false, &symtab);
    124   1.5  christos 		if (result != ISC_R_SUCCESS) {
    125   1.1  christos 			return;
    126   1.5  christos 		}
    127   1.1  christos 	}
    128   1.1  christos 
    129   1.1  christos 	key = isc_mem_strdup(sym_mctx, key);
    130   1.1  christos 
    131   1.1  christos 	symvalue.as_pointer = NULL;
    132   1.1  christos 	result = isc_symtab_define(symtab, key, value, symvalue,
    133   1.1  christos 				   isc_symexists_reject);
    134   1.5  christos 	if (result != ISC_R_SUCCESS) {
    135   1.1  christos 		isc_mem_free(sym_mctx, key);
    136   1.5  christos 	}
    137   1.1  christos }
    138   1.1  christos 
    139   1.3  christos static bool
    140   1.1  christos logged(char *key, int value) {
    141   1.1  christos 	isc_result_t result;
    142   1.1  christos 
    143   1.5  christos 	if (symtab == NULL) {
    144  1.11  christos 		return false;
    145   1.5  christos 	}
    146   1.1  christos 
    147   1.1  christos 	result = isc_symtab_lookup(symtab, key, value, NULL);
    148   1.5  christos 	if (result == ISC_R_SUCCESS) {
    149  1.11  christos 		return true;
    150   1.5  christos 	}
    151  1.11  christos 	return false;
    152   1.1  christos }
    153   1.1  christos 
    154   1.3  christos static bool
    155   1.1  christos checkns(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner,
    156   1.5  christos 	dns_rdataset_t *a, dns_rdataset_t *aaaa) {
    157   1.1  christos 	dns_rdataset_t *rdataset;
    158   1.1  christos 	dns_rdata_t rdata = DNS_RDATA_INIT;
    159   1.1  christos 	struct addrinfo hints, *ai, *cur;
    160   1.1  christos 	char namebuf[DNS_NAME_FORMATSIZE + 1];
    161   1.1  christos 	char ownerbuf[DNS_NAME_FORMATSIZE];
    162   1.1  christos 	char addrbuf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:123.123.123.123")];
    163   1.3  christos 	bool answer = true;
    164   1.3  christos 	bool match;
    165   1.1  christos 	const char *type;
    166   1.1  christos 	void *ptr = NULL;
    167   1.1  christos 	int result;
    168   1.1  christos 
    169   1.1  christos 	REQUIRE(a == NULL || !dns_rdataset_isassociated(a) ||
    170   1.1  christos 		a->type == dns_rdatatype_a);
    171   1.1  christos 	REQUIRE(aaaa == NULL || !dns_rdataset_isassociated(aaaa) ||
    172   1.1  christos 		aaaa->type == dns_rdatatype_aaaa);
    173   1.1  christos 
    174   1.5  christos 	if (a == NULL || aaaa == NULL) {
    175  1.11  christos 		return answer;
    176   1.5  christos 	}
    177   1.1  christos 
    178   1.1  christos 	memset(&hints, 0, sizeof(hints));
    179   1.1  christos 	hints.ai_flags = AI_CANONNAME;
    180   1.1  christos 	hints.ai_family = PF_UNSPEC;
    181   1.1  christos 	hints.ai_socktype = SOCK_STREAM;
    182   1.1  christos 	hints.ai_protocol = IPPROTO_TCP;
    183   1.1  christos 
    184   1.1  christos 	dns_name_format(name, namebuf, sizeof(namebuf) - 1);
    185   1.1  christos 	/*
    186   1.1  christos 	 * Turn off search.
    187   1.1  christos 	 */
    188   1.1  christos 	if (dns_name_countlabels(name) > 1U) {
    189   1.1  christos 		strlcat(namebuf, ".", sizeof(namebuf));
    190   1.1  christos 	}
    191   1.1  christos 	dns_name_format(owner, ownerbuf, sizeof(ownerbuf));
    192   1.1  christos 
    193   1.1  christos 	result = getaddrinfo(namebuf, NULL, &hints, &ai);
    194   1.1  christos 	dns_name_format(name, namebuf, sizeof(namebuf) - 1);
    195   1.1  christos 	switch (result) {
    196   1.1  christos 	case 0:
    197   1.1  christos 		/*
    198   1.1  christos 		 * Work around broken getaddrinfo() implementations that
    199   1.1  christos 		 * fail to set ai_canonname on first entry.
    200   1.1  christos 		 */
    201   1.1  christos 		cur = ai;
    202   1.1  christos 		while (cur != NULL && cur->ai_canonname == NULL &&
    203   1.9  christos 		       cur->ai_next != NULL)
    204   1.9  christos 		{
    205   1.1  christos 			cur = cur->ai_next;
    206   1.5  christos 		}
    207   1.1  christos 		if (cur != NULL && cur->ai_canonname != NULL &&
    208   1.1  christos 		    strcasecmp(cur->ai_canonname, namebuf) != 0 &&
    209   1.5  christos 		    !logged(namebuf, ERR_IS_CNAME))
    210   1.5  christos 		{
    211   1.1  christos 			dns_zone_log(zone, ISC_LOG_ERROR,
    212   1.1  christos 				     "%s/NS '%s' (out of zone) "
    213   1.1  christos 				     "is a CNAME '%s' (illegal)",
    214   1.5  christos 				     ownerbuf, namebuf, cur->ai_canonname);
    215   1.1  christos 			/* XXX950 make fatal for 9.5.0 */
    216   1.3  christos 			/* answer = false; */
    217   1.1  christos 			add(namebuf, ERR_IS_CNAME);
    218   1.1  christos 		}
    219   1.1  christos 		break;
    220   1.1  christos 	case EAI_NONAME:
    221   1.1  christos #if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME)
    222   1.1  christos 	case EAI_NODATA:
    223   1.5  christos #endif /* if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) */
    224   1.1  christos 		if (!logged(namebuf, ERR_NO_ADDRESSES)) {
    225   1.1  christos 			dns_zone_log(zone, ISC_LOG_ERROR,
    226   1.1  christos 				     "%s/NS '%s' (out of zone) "
    227   1.1  christos 				     "has no addresses records (A or AAAA)",
    228   1.1  christos 				     ownerbuf, namebuf);
    229   1.1  christos 			add(namebuf, ERR_NO_ADDRESSES);
    230   1.1  christos 		}
    231   1.1  christos 		/* XXX950 make fatal for 9.5.0 */
    232  1.11  christos 		return true;
    233   1.1  christos 
    234   1.1  christos 	default:
    235   1.1  christos 		if (!logged(namebuf, ERR_LOOKUP_FAILURE)) {
    236   1.1  christos 			dns_zone_log(zone, ISC_LOG_WARNING,
    237   1.5  christos 				     "getaddrinfo(%s) failed: %s", namebuf,
    238   1.5  christos 				     gai_strerror(result));
    239   1.1  christos 			add(namebuf, ERR_LOOKUP_FAILURE);
    240   1.1  christos 		}
    241  1.11  christos 		return true;
    242   1.1  christos 	}
    243   1.1  christos 
    244   1.1  christos 	/*
    245   1.1  christos 	 * Check that all glue records really exist.
    246   1.1  christos 	 */
    247   1.5  christos 	if (!dns_rdataset_isassociated(a)) {
    248   1.1  christos 		goto checkaaaa;
    249   1.5  christos 	}
    250   1.1  christos 	result = dns_rdataset_first(a);
    251   1.1  christos 	while (result == ISC_R_SUCCESS) {
    252   1.1  christos 		dns_rdataset_current(a, &rdata);
    253   1.3  christos 		match = false;
    254   1.1  christos 		for (cur = ai; cur != NULL; cur = cur->ai_next) {
    255   1.5  christos 			if (cur->ai_family != AF_INET) {
    256   1.1  christos 				continue;
    257   1.5  christos 			}
    258   1.1  christos 			ptr = &((struct sockaddr_in *)(cur->ai_addr))->sin_addr;
    259   1.1  christos 			if (memcmp(ptr, rdata.data, rdata.length) == 0) {
    260   1.3  christos 				match = true;
    261   1.1  christos 				break;
    262   1.1  christos 			}
    263   1.1  christos 		}
    264   1.1  christos 		if (!match && !logged(namebuf, ERR_EXTRA_A)) {
    265   1.5  christos 			dns_zone_log(zone, ISC_LOG_ERROR,
    266   1.5  christos 				     "%s/NS '%s' "
    267   1.1  christos 				     "extra GLUE A record (%s)",
    268   1.1  christos 				     ownerbuf, namebuf,
    269   1.5  christos 				     inet_ntop(AF_INET, rdata.data, addrbuf,
    270   1.5  christos 					       sizeof(addrbuf)));
    271   1.1  christos 			add(namebuf, ERR_EXTRA_A);
    272   1.1  christos 			/* XXX950 make fatal for 9.5.0 */
    273   1.3  christos 			/* answer = false; */
    274   1.1  christos 		}
    275   1.1  christos 		dns_rdata_reset(&rdata);
    276   1.1  christos 		result = dns_rdataset_next(a);
    277   1.1  christos 	}
    278   1.1  christos 
    279   1.5  christos checkaaaa:
    280   1.5  christos 	if (!dns_rdataset_isassociated(aaaa)) {
    281   1.1  christos 		goto checkmissing;
    282   1.5  christos 	}
    283   1.1  christos 	result = dns_rdataset_first(aaaa);
    284   1.1  christos 	while (result == ISC_R_SUCCESS) {
    285   1.1  christos 		dns_rdataset_current(aaaa, &rdata);
    286   1.3  christos 		match = false;
    287   1.1  christos 		for (cur = ai; cur != NULL; cur = cur->ai_next) {
    288   1.5  christos 			if (cur->ai_family != AF_INET6) {
    289   1.1  christos 				continue;
    290   1.5  christos 			}
    291   1.5  christos 			ptr = &((struct sockaddr_in6 *)(cur->ai_addr))
    292   1.5  christos 				       ->sin6_addr;
    293   1.1  christos 			if (memcmp(ptr, rdata.data, rdata.length) == 0) {
    294   1.3  christos 				match = true;
    295   1.1  christos 				break;
    296   1.1  christos 			}
    297   1.1  christos 		}
    298   1.1  christos 		if (!match && !logged(namebuf, ERR_EXTRA_AAAA)) {
    299   1.5  christos 			dns_zone_log(zone, ISC_LOG_ERROR,
    300   1.5  christos 				     "%s/NS '%s' "
    301   1.1  christos 				     "extra GLUE AAAA record (%s)",
    302   1.1  christos 				     ownerbuf, namebuf,
    303   1.5  christos 				     inet_ntop(AF_INET6, rdata.data, addrbuf,
    304   1.5  christos 					       sizeof(addrbuf)));
    305   1.1  christos 			add(namebuf, ERR_EXTRA_AAAA);
    306   1.1  christos 			/* XXX950 make fatal for 9.5.0. */
    307   1.3  christos 			/* answer = false; */
    308   1.1  christos 		}
    309   1.1  christos 		dns_rdata_reset(&rdata);
    310   1.1  christos 		result = dns_rdataset_next(aaaa);
    311   1.1  christos 	}
    312   1.1  christos 
    313   1.5  christos checkmissing:
    314   1.1  christos 	/*
    315   1.1  christos 	 * Check that all addresses appear in the glue.
    316   1.1  christos 	 */
    317   1.1  christos 	if (!logged(namebuf, ERR_MISSING_GLUE)) {
    318   1.3  christos 		bool missing_glue = false;
    319   1.1  christos 		for (cur = ai; cur != NULL; cur = cur->ai_next) {
    320   1.1  christos 			switch (cur->ai_family) {
    321   1.1  christos 			case AF_INET:
    322   1.1  christos 				rdataset = a;
    323   1.5  christos 				ptr = &((struct sockaddr_in *)(cur->ai_addr))
    324   1.5  christos 					       ->sin_addr;
    325   1.1  christos 				type = "A";
    326   1.1  christos 				break;
    327   1.1  christos 			case AF_INET6:
    328   1.1  christos 				rdataset = aaaa;
    329   1.5  christos 				ptr = &((struct sockaddr_in6 *)(cur->ai_addr))
    330   1.5  christos 					       ->sin6_addr;
    331   1.1  christos 				type = "AAAA";
    332   1.1  christos 				break;
    333   1.1  christos 			default:
    334   1.5  christos 				continue;
    335   1.1  christos 			}
    336   1.3  christos 			match = false;
    337   1.5  christos 			if (dns_rdataset_isassociated(rdataset)) {
    338   1.1  christos 				result = dns_rdataset_first(rdataset);
    339   1.5  christos 			} else {
    340   1.1  christos 				result = ISC_R_FAILURE;
    341   1.5  christos 			}
    342   1.1  christos 			while (result == ISC_R_SUCCESS && !match) {
    343   1.1  christos 				dns_rdataset_current(rdataset, &rdata);
    344   1.1  christos 				if (memcmp(ptr, rdata.data, rdata.length) == 0)
    345   1.5  christos 				{
    346   1.3  christos 					match = true;
    347   1.5  christos 				}
    348   1.1  christos 				dns_rdata_reset(&rdata);
    349   1.1  christos 				result = dns_rdataset_next(rdataset);
    350   1.1  christos 			}
    351   1.1  christos 			if (!match) {
    352   1.5  christos 				dns_zone_log(zone, ISC_LOG_ERROR,
    353   1.5  christos 					     "%s/NS '%s' "
    354   1.1  christos 					     "missing GLUE %s record (%s)",
    355   1.1  christos 					     ownerbuf, namebuf, type,
    356   1.1  christos 					     inet_ntop(cur->ai_family, ptr,
    357   1.5  christos 						       addrbuf,
    358   1.5  christos 						       sizeof(addrbuf)));
    359   1.1  christos 				/* XXX950 make fatal for 9.5.0. */
    360   1.3  christos 				/* answer = false; */
    361   1.3  christos 				missing_glue = true;
    362   1.1  christos 			}
    363   1.1  christos 		}
    364   1.5  christos 		if (missing_glue) {
    365   1.1  christos 			add(namebuf, ERR_MISSING_GLUE);
    366   1.5  christos 		}
    367   1.1  christos 	}
    368   1.1  christos 	freeaddrinfo(ai);
    369  1.11  christos 	return answer;
    370   1.1  christos }
    371   1.1  christos 
    372   1.3  christos static bool
    373   1.1  christos checkmx(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner) {
    374   1.1  christos 	struct addrinfo hints, *ai, *cur;
    375   1.1  christos 	char namebuf[DNS_NAME_FORMATSIZE + 1];
    376   1.1  christos 	char ownerbuf[DNS_NAME_FORMATSIZE];
    377   1.1  christos 	int result;
    378   1.1  christos 	int level = ISC_LOG_ERROR;
    379   1.3  christos 	bool answer = true;
    380   1.1  christos 
    381   1.1  christos 	memset(&hints, 0, sizeof(hints));
    382   1.1  christos 	hints.ai_flags = AI_CANONNAME;
    383   1.1  christos 	hints.ai_family = PF_UNSPEC;
    384   1.1  christos 	hints.ai_socktype = SOCK_STREAM;
    385   1.1  christos 	hints.ai_protocol = IPPROTO_TCP;
    386   1.1  christos 
    387   1.1  christos 	dns_name_format(name, namebuf, sizeof(namebuf) - 1);
    388   1.1  christos 	/*
    389   1.1  christos 	 * Turn off search.
    390   1.1  christos 	 */
    391   1.1  christos 	if (dns_name_countlabels(name) > 1U) {
    392   1.1  christos 		strlcat(namebuf, ".", sizeof(namebuf));
    393   1.1  christos 	}
    394   1.1  christos 	dns_name_format(owner, ownerbuf, sizeof(ownerbuf));
    395   1.1  christos 
    396   1.1  christos 	result = getaddrinfo(namebuf, NULL, &hints, &ai);
    397   1.1  christos 	dns_name_format(name, namebuf, sizeof(namebuf) - 1);
    398   1.1  christos 	switch (result) {
    399   1.1  christos 	case 0:
    400   1.1  christos 		/*
    401   1.1  christos 		 * Work around broken getaddrinfo() implementations that
    402   1.1  christos 		 * fail to set ai_canonname on first entry.
    403   1.1  christos 		 */
    404   1.1  christos 		cur = ai;
    405   1.1  christos 		while (cur != NULL && cur->ai_canonname == NULL &&
    406   1.9  christos 		       cur->ai_next != NULL)
    407   1.9  christos 		{
    408   1.1  christos 			cur = cur->ai_next;
    409   1.5  christos 		}
    410   1.1  christos 		if (cur != NULL && cur->ai_canonname != NULL &&
    411   1.5  christos 		    strcasecmp(cur->ai_canonname, namebuf) != 0)
    412   1.5  christos 		{
    413   1.5  christos 			if ((zone_options & DNS_ZONEOPT_WARNMXCNAME) != 0) {
    414   1.1  christos 				level = ISC_LOG_WARNING;
    415   1.5  christos 			}
    416   1.1  christos 			if ((zone_options & DNS_ZONEOPT_IGNOREMXCNAME) == 0) {
    417   1.1  christos 				if (!logged(namebuf, ERR_IS_MXCNAME)) {
    418   1.1  christos 					dns_zone_log(zone, level,
    419   1.1  christos 						     "%s/MX '%s' (out of zone)"
    420   1.1  christos 						     " is a CNAME '%s' "
    421   1.1  christos 						     "(illegal)",
    422   1.1  christos 						     ownerbuf, namebuf,
    423   1.1  christos 						     cur->ai_canonname);
    424   1.1  christos 					add(namebuf, ERR_IS_MXCNAME);
    425   1.1  christos 				}
    426   1.5  christos 				if (level == ISC_LOG_ERROR) {
    427   1.3  christos 					answer = false;
    428   1.5  christos 				}
    429   1.1  christos 			}
    430   1.1  christos 		}
    431   1.1  christos 		freeaddrinfo(ai);
    432  1.11  christos 		return answer;
    433   1.1  christos 
    434   1.1  christos 	case EAI_NONAME:
    435   1.1  christos #if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME)
    436   1.1  christos 	case EAI_NODATA:
    437   1.5  christos #endif /* if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) */
    438   1.1  christos 		if (!logged(namebuf, ERR_NO_ADDRESSES)) {
    439   1.1  christos 			dns_zone_log(zone, ISC_LOG_ERROR,
    440   1.1  christos 				     "%s/MX '%s' (out of zone) "
    441   1.1  christos 				     "has no addresses records (A or AAAA)",
    442   1.1  christos 				     ownerbuf, namebuf);
    443   1.1  christos 			add(namebuf, ERR_NO_ADDRESSES);
    444   1.1  christos 		}
    445   1.1  christos 		/* XXX950 make fatal for 9.5.0. */
    446  1.11  christos 		return true;
    447   1.1  christos 
    448   1.1  christos 	default:
    449   1.1  christos 		if (!logged(namebuf, ERR_LOOKUP_FAILURE)) {
    450   1.1  christos 			dns_zone_log(zone, ISC_LOG_WARNING,
    451   1.5  christos 				     "getaddrinfo(%s) failed: %s", namebuf,
    452   1.5  christos 				     gai_strerror(result));
    453   1.1  christos 			add(namebuf, ERR_LOOKUP_FAILURE);
    454   1.1  christos 		}
    455  1.11  christos 		return true;
    456   1.1  christos 	}
    457   1.1  christos }
    458   1.1  christos 
    459   1.3  christos static bool
    460   1.1  christos checksrv(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner) {
    461   1.1  christos 	struct addrinfo hints, *ai, *cur;
    462   1.1  christos 	char namebuf[DNS_NAME_FORMATSIZE + 1];
    463   1.1  christos 	char ownerbuf[DNS_NAME_FORMATSIZE];
    464   1.1  christos 	int result;
    465   1.1  christos 	int level = ISC_LOG_ERROR;
    466   1.3  christos 	bool answer = true;
    467   1.1  christos 
    468   1.1  christos 	memset(&hints, 0, sizeof(hints));
    469   1.1  christos 	hints.ai_flags = AI_CANONNAME;
    470   1.1  christos 	hints.ai_family = PF_UNSPEC;
    471   1.1  christos 	hints.ai_socktype = SOCK_STREAM;
    472   1.1  christos 	hints.ai_protocol = IPPROTO_TCP;
    473   1.1  christos 
    474   1.1  christos 	dns_name_format(name, namebuf, sizeof(namebuf) - 1);
    475   1.1  christos 	/*
    476   1.1  christos 	 * Turn off search.
    477   1.1  christos 	 */
    478   1.1  christos 	if (dns_name_countlabels(name) > 1U) {
    479   1.1  christos 		strlcat(namebuf, ".", sizeof(namebuf));
    480   1.1  christos 	}
    481   1.1  christos 	dns_name_format(owner, ownerbuf, sizeof(ownerbuf));
    482   1.1  christos 
    483   1.1  christos 	result = getaddrinfo(namebuf, NULL, &hints, &ai);
    484   1.1  christos 	dns_name_format(name, namebuf, sizeof(namebuf) - 1);
    485   1.1  christos 	switch (result) {
    486   1.1  christos 	case 0:
    487   1.1  christos 		/*
    488   1.1  christos 		 * Work around broken getaddrinfo() implementations that
    489   1.1  christos 		 * fail to set ai_canonname on first entry.
    490   1.1  christos 		 */
    491   1.1  christos 		cur = ai;
    492   1.1  christos 		while (cur != NULL && cur->ai_canonname == NULL &&
    493   1.9  christos 		       cur->ai_next != NULL)
    494   1.9  christos 		{
    495   1.1  christos 			cur = cur->ai_next;
    496   1.5  christos 		}
    497   1.1  christos 		if (cur != NULL && cur->ai_canonname != NULL &&
    498   1.5  christos 		    strcasecmp(cur->ai_canonname, namebuf) != 0)
    499   1.5  christos 		{
    500   1.5  christos 			if ((zone_options & DNS_ZONEOPT_WARNSRVCNAME) != 0) {
    501   1.1  christos 				level = ISC_LOG_WARNING;
    502   1.5  christos 			}
    503   1.1  christos 			if ((zone_options & DNS_ZONEOPT_IGNORESRVCNAME) == 0) {
    504   1.1  christos 				if (!logged(namebuf, ERR_IS_SRVCNAME)) {
    505   1.5  christos 					dns_zone_log(zone, level,
    506   1.5  christos 						     "%s/SRV '%s'"
    507   1.1  christos 						     " (out of zone) is a "
    508   1.1  christos 						     "CNAME '%s' (illegal)",
    509   1.1  christos 						     ownerbuf, namebuf,
    510   1.1  christos 						     cur->ai_canonname);
    511   1.1  christos 					add(namebuf, ERR_IS_SRVCNAME);
    512   1.1  christos 				}
    513   1.5  christos 				if (level == ISC_LOG_ERROR) {
    514   1.3  christos 					answer = false;
    515   1.5  christos 				}
    516   1.1  christos 			}
    517   1.1  christos 		}
    518   1.1  christos 		freeaddrinfo(ai);
    519  1.11  christos 		return answer;
    520   1.1  christos 
    521   1.1  christos 	case EAI_NONAME:
    522   1.1  christos #if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME)
    523   1.1  christos 	case EAI_NODATA:
    524   1.5  christos #endif /* if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) */
    525   1.1  christos 		if (!logged(namebuf, ERR_NO_ADDRESSES)) {
    526   1.1  christos 			dns_zone_log(zone, ISC_LOG_ERROR,
    527   1.1  christos 				     "%s/SRV '%s' (out of zone) "
    528   1.1  christos 				     "has no addresses records (A or AAAA)",
    529   1.1  christos 				     ownerbuf, namebuf);
    530   1.1  christos 			add(namebuf, ERR_NO_ADDRESSES);
    531   1.1  christos 		}
    532   1.1  christos 		/* XXX950 make fatal for 9.5.0. */
    533  1.11  christos 		return true;
    534   1.1  christos 
    535   1.1  christos 	default:
    536   1.1  christos 		if (!logged(namebuf, ERR_LOOKUP_FAILURE)) {
    537   1.1  christos 			dns_zone_log(zone, ISC_LOG_WARNING,
    538   1.5  christos 				     "getaddrinfo(%s) failed: %s", namebuf,
    539   1.5  christos 				     gai_strerror(result));
    540   1.1  christos 			add(namebuf, ERR_LOOKUP_FAILURE);
    541   1.1  christos 		}
    542  1.11  christos 		return true;
    543   1.1  christos 	}
    544   1.1  christos }
    545   1.1  christos 
    546   1.1  christos isc_result_t
    547   1.1  christos setup_logging(isc_mem_t *mctx, FILE *errout, isc_log_t **logp) {
    548   1.1  christos 	isc_logdestination_t destination;
    549   1.1  christos 	isc_logconfig_t *logconfig = NULL;
    550   1.1  christos 	isc_log_t *log = NULL;
    551   1.1  christos 
    552   1.5  christos 	isc_log_create(mctx, &log, &logconfig);
    553   1.1  christos 	isc_log_registercategories(log, categories);
    554   1.1  christos 	isc_log_setcontext(log);
    555   1.1  christos 	dns_log_init(log);
    556   1.1  christos 	dns_log_setcontext(log);
    557   1.1  christos 	cfg_log_init(log);
    558   1.1  christos 	ns_log_init(log);
    559   1.1  christos 
    560   1.1  christos 	destination.file.stream = errout;
    561   1.1  christos 	destination.file.name = NULL;
    562   1.1  christos 	destination.file.versions = ISC_LOG_ROLLNEVER;
    563   1.1  christos 	destination.file.maximum_size = 0;
    564   1.5  christos 	isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC,
    565   1.5  christos 			      ISC_LOG_DYNAMIC, &destination, 0);
    566   1.5  christos 
    567   1.5  christos 	RUNTIME_CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL) ==
    568   1.5  christos 		      ISC_R_SUCCESS);
    569   1.1  christos 
    570   1.1  christos 	*logp = log;
    571  1.11  christos 	return ISC_R_SUCCESS;
    572   1.1  christos }
    573   1.1  christos 
    574   1.1  christos /*% load the zone */
    575   1.1  christos isc_result_t
    576   1.1  christos load_zone(isc_mem_t *mctx, const char *zonename, const char *filename,
    577   1.1  christos 	  dns_masterformat_t fileformat, const char *classname,
    578   1.5  christos 	  dns_ttl_t maxttl, dns_zone_t **zonep) {
    579   1.1  christos 	isc_result_t result;
    580   1.1  christos 	dns_rdataclass_t rdclass;
    581   1.1  christos 	isc_textregion_t region;
    582   1.1  christos 	isc_buffer_t buffer;
    583   1.1  christos 	dns_fixedname_t fixorigin;
    584   1.1  christos 	dns_name_t *origin;
    585   1.1  christos 	dns_zone_t *zone = NULL;
    586   1.1  christos 
    587   1.1  christos 	REQUIRE(zonep == NULL || *zonep == NULL);
    588   1.1  christos 
    589   1.5  christos 	if (debug) {
    590   1.1  christos 		fprintf(stderr, "loading \"%s\" from \"%s\" class \"%s\"\n",
    591   1.1  christos 			zonename, filename, classname);
    592   1.5  christos 	}
    593   1.1  christos 
    594  1.11  christos 	dns_zone_create(&zone, mctx, 0);
    595   1.1  christos 
    596   1.8  christos 	dns_zone_settype(zone, dns_zone_primary);
    597   1.1  christos 
    598   1.1  christos 	isc_buffer_constinit(&buffer, zonename, strlen(zonename));
    599   1.1  christos 	isc_buffer_add(&buffer, strlen(zonename));
    600   1.1  christos 	origin = dns_fixedname_initname(&fixorigin);
    601   1.1  christos 	CHECK(dns_name_fromtext(origin, &buffer, dns_rootname, 0, NULL));
    602   1.1  christos 	CHECK(dns_zone_setorigin(zone, origin));
    603   1.5  christos 	dns_zone_setdbtype(zone, 1, (const char *const *)dbtype);
    604  1.10  christos 	if (strcmp(filename, "-") == 0) {
    605  1.10  christos 		CHECK(dns_zone_setstream(zone, stdin, fileformat,
    606  1.10  christos 					 &dns_master_style_default));
    607  1.10  christos 	} else {
    608  1.10  christos 		CHECK(dns_zone_setfile(zone, filename, fileformat,
    609  1.10  christos 				       &dns_master_style_default));
    610  1.10  christos 	}
    611   1.5  christos 	if (journal != NULL) {
    612   1.1  christos 		CHECK(dns_zone_setjournal(zone, journal));
    613   1.5  christos 	}
    614   1.1  christos 
    615  1.11  christos 	region.base = UNCONST(classname);
    616   1.1  christos 	region.length = strlen(classname);
    617   1.1  christos 	CHECK(dns_rdataclass_fromtext(&rdclass, &region));
    618   1.1  christos 
    619   1.1  christos 	dns_zone_setclass(zone, rdclass);
    620   1.3  christos 	dns_zone_setoption(zone, zone_options, true);
    621   1.1  christos 	dns_zone_setoption(zone, DNS_ZONEOPT_NOMERGE, nomerge);
    622   1.1  christos 
    623   1.1  christos 	dns_zone_setmaxttl(zone, maxttl);
    624   1.1  christos 
    625   1.5  christos 	if (docheckmx) {
    626   1.1  christos 		dns_zone_setcheckmx(zone, checkmx);
    627   1.5  christos 	}
    628   1.5  christos 	if (docheckns) {
    629   1.1  christos 		dns_zone_setcheckns(zone, checkns);
    630   1.5  christos 	}
    631   1.5  christos 	if (dochecksrv) {
    632   1.1  christos 		dns_zone_setchecksrv(zone, checksrv);
    633   1.5  christos 	}
    634   1.1  christos 
    635   1.3  christos 	CHECK(dns_zone_load(zone, false));
    636   1.1  christos 
    637   1.1  christos 	if (zonep != NULL) {
    638   1.1  christos 		*zonep = zone;
    639   1.1  christos 		zone = NULL;
    640   1.1  christos 	}
    641   1.1  christos 
    642   1.5  christos cleanup:
    643   1.5  christos 	if (zone != NULL) {
    644   1.1  christos 		dns_zone_detach(&zone);
    645   1.5  christos 	}
    646  1.11  christos 	return result;
    647   1.1  christos }
    648   1.1  christos 
    649   1.1  christos /*% dump the zone */
    650   1.1  christos isc_result_t
    651   1.1  christos dump_zone(const char *zonename, dns_zone_t *zone, const char *filename,
    652   1.1  christos 	  dns_masterformat_t fileformat, const dns_master_style_t *style,
    653   1.5  christos 	  const uint32_t rawversion) {
    654   1.1  christos 	isc_result_t result;
    655   1.1  christos 	FILE *output = stdout;
    656   1.1  christos 	const char *flags;
    657   1.1  christos 
    658   1.4  christos 	flags = (fileformat == dns_masterformat_text) ? "w" : "wb";
    659   1.1  christos 
    660   1.1  christos 	if (debug) {
    661   1.5  christos 		if (filename != NULL && strcmp(filename, "-") != 0) {
    662   1.5  christos 			fprintf(stderr, "dumping \"%s\" to \"%s\"\n", zonename,
    663   1.5  christos 				filename);
    664   1.5  christos 		} else {
    665   1.1  christos 			fprintf(stderr, "dumping \"%s\"\n", zonename);
    666   1.5  christos 		}
    667   1.1  christos 	}
    668   1.1  christos 
    669   1.1  christos 	if (filename != NULL && strcmp(filename, "-") != 0) {
    670   1.1  christos 		result = isc_stdio_open(filename, flags, &output);
    671   1.1  christos 
    672   1.1  christos 		if (result != ISC_R_SUCCESS) {
    673   1.5  christos 			fprintf(stderr,
    674   1.5  christos 				"could not open output "
    675   1.5  christos 				"file \"%s\" for writing\n",
    676   1.5  christos 				filename);
    677  1.11  christos 			return ISC_R_FAILURE;
    678   1.1  christos 		}
    679   1.1  christos 	}
    680   1.1  christos 
    681   1.3  christos 	result = dns_zone_dumptostream(zone, output, fileformat, style,
    682   1.3  christos 				       rawversion);
    683   1.5  christos 	if (output != stdout) {
    684   1.1  christos 		(void)isc_stdio_close(output);
    685   1.5  christos 	}
    686   1.1  christos 
    687  1.11  christos 	return result;
    688   1.1  christos }
    689