Home | History | Annotate | Line # | Download | only in libresolv
      1 /*	$NetBSD: res_findzonecut.c,v 1.3 2022/04/19 20:32:17 rillig Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
      5  * Copyright (c) 1999 by Internet Software Consortium.
      6  *
      7  * Permission to use, copy, modify, and 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
     12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     13  * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
     14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
     17  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     18  */
     19 #include <sys/cdefs.h>
     20 #if 0
     21 static const char rcsid[] = "Id: res_findzonecut.c,v 1.10 2005/10/11 00:10:16 marka Exp ";
     22 #else
     23 __RCSID("$NetBSD: res_findzonecut.c,v 1.3 2022/04/19 20:32:17 rillig Exp $");
     24 #endif
     25 
     26 
     27 /* Import. */
     28 
     29 #include "port_before.h"
     30 
     31 #include <sys/param.h>
     32 #include <sys/socket.h>
     33 #include <sys/time.h>
     34 #include <sys/queue.h>
     35 
     36 #include <netinet/in.h>
     37 #include <arpa/inet.h>
     38 #include <arpa/nameser.h>
     39 
     40 #include <errno.h>
     41 #include <limits.h>
     42 #include <netdb.h>
     43 #include <stdarg.h>
     44 #include <stdio.h>
     45 #include <stdlib.h>
     46 #include <string.h>
     47 
     48 #include "port_after.h"
     49 
     50 #include <resolv.h>
     51 
     52 /* Data structures. */
     53 
     54 typedef struct rr_a {
     55 	TAILQ_ENTRY(rr_a)	link;
     56 	union res_sockaddr_union addr;
     57 } rr_a;
     58 typedef TAILQ_HEAD(rrset_a, rr_a) rrset_a;
     59 
     60 typedef struct rr_ns {
     61 	TAILQ_ENTRY(rr_ns)	link;
     62 	const char *		name;
     63 	unsigned int		flags;
     64 	rrset_a			addrs;
     65 } rr_ns;
     66 typedef TAILQ_HEAD(rrset_ns, rr_ns) rrset_ns;
     67 
     68 #define	RR_NS_HAVE_V4		0x01
     69 #define	RR_NS_HAVE_V6		0x02
     70 
     71 /* Forward. */
     72 
     73 static int	satisfy(res_state, const char *, rrset_ns *,
     74 			union res_sockaddr_union *, int);
     75 static int	add_addrs(res_state, rr_ns *,
     76 			  union res_sockaddr_union *, int);
     77 static int	get_soa(res_state, const char *, ns_class, int,
     78 			char *, size_t, char *, size_t,
     79 			rrset_ns *);
     80 static int	get_ns(res_state, const char *, ns_class, int, rrset_ns *);
     81 static int	get_glue(res_state, ns_class, int, rrset_ns *);
     82 static int	save_ns(res_state, ns_msg *, ns_sect,
     83 			const char *, ns_class, int, rrset_ns *);
     84 static int	save_a(res_state, ns_msg *, ns_sect,
     85 		       const char *, ns_class, int, rr_ns *);
     86 static void	free_nsrrset(rrset_ns *);
     87 static void	free_nsrr(rrset_ns *, rr_ns *);
     88 static rr_ns *	find_ns(rrset_ns *, const char *);
     89 static int	do_query(res_state, const char *, ns_class, ns_type,
     90 			 u_char *, ns_msg *);
     91 static void	res_dprintf(const char *, ...) ISC_FORMAT_PRINTF(1, 2);
     92 
     93 /* Macros. */
     94 
     95 #define DPRINTF(x) do {\
     96 		int save_errno = errno; \
     97 		if ((statp->options & RES_DEBUG) != 0U) res_dprintf x; \
     98 		errno = save_errno; \
     99 	} while (0)
    100 
    101 /* Public. */
    102 
    103 /*%
    104  *	find enclosing zone for a <dname,class>, and some server addresses
    105  *
    106  * parameters:
    107  *\li	res - resolver context to work within (is modified)
    108  *\li	dname - domain name whose enclosing zone is desired
    109  *\li	class - class of dname (and its enclosing zone)
    110  *\li	zname - found zone name
    111  *\li	zsize - allocated size of zname
    112  *\li	addrs - found server addresses
    113  *\li	naddrs - max number of addrs
    114  *
    115  * return values:
    116  *\li	< 0 - an error occurred (check errno)
    117  *\li	= 0 - zname is now valid, but addrs[] wasn't changed
    118  *\li	> 0 - zname is now valid, and return value is number of addrs[] found
    119  *
    120  * notes:
    121  *\li	this function calls res_nsend() which means it depends on correctly
    122  *	functioning recursive nameservers (usually defined in /etc/resolv.conf
    123  *	or its local equivalent).
    124  *
    125  *\li	we start by asking for an SOA<dname,class>.  if we get one as an
    126  *	answer, that just means <dname,class> is a zone top, which is fine.
    127  *	more than likely we'll be told to go pound sand, in the form of a
    128  *	negative answer.
    129  *
    130  *\li	note that we are not prepared to deal with referrals since that would
    131  *	only come from authority servers and our correctly functioning local
    132  *	recursive server would have followed the referral and got us something
    133  *	more definite.
    134  *
    135  *\li	if the authority section contains an SOA, this SOA should also be the
    136  *	closest enclosing zone, since any intermediary zone cuts would've been
    137  *	returned as referrals and dealt with by our correctly functioning local
    138  *	recursive name server.  but an SOA in the authority section should NOT
    139  *	match our dname (since that would have been returned in the answer
    140  *	section).  an authority section SOA has to be "above" our dname.
    141  *
    142  *\li	however, since authority section SOA's were once optional, it's
    143  *	possible that we'll have to go hunting for the enclosing SOA by
    144  *	ripping labels off the front of our dname -- this is known as "doing
    145  *	it the hard way."
    146  *
    147  *\li	ultimately we want some server addresses, which are ideally the ones
    148  *	pertaining to the SOA.MNAME, but only if there is a matching NS RR.
    149  *	so the second phase (after we find an SOA) is to go looking for the
    150  *	NS RRset for that SOA's zone.
    151  *
    152  *\li	no answer section processed by this code is allowed to contain CNAME
    153  *	or DNAME RR's.  for the SOA query this means we strip a label and
    154  *	keep going.  for the NS and A queries this means we just give up.
    155  */
    156 
    157 int
    158 res_findzonecut(res_state statp, const char *dname, ns_class class, int opts,
    159 		char *zname, size_t zsize, struct in_addr *addrs, int naddrs)
    160 {
    161 	int result, i;
    162 	union res_sockaddr_union *u;
    163 
    164 
    165 	opts |= RES_IPV4ONLY;
    166 	opts &= ~RES_IPV6ONLY;
    167 
    168 	u = calloc(naddrs, sizeof(*u));
    169 	if (u == NULL)
    170 		return(-1);
    171 
    172 	result = res_findzonecut2(statp, dname, class, opts, zname, zsize,
    173 				  u, naddrs);
    174 
    175 	for (i = 0; i < result; i++) {
    176 		addrs[i] = u[i].sin.sin_addr;
    177 	}
    178 	free(u);
    179 	return (result);
    180 }
    181 
    182 int
    183 res_findzonecut2(res_state statp, const char *dname, ns_class class, int opts,
    184 		 char *zname, size_t zsize, union res_sockaddr_union *addrs,
    185 		 int naddrs)
    186 {
    187 	char mname[NS_MAXDNAME];
    188 	u_long save_pfcode;
    189 	rrset_ns nsrrs;
    190 	int n;
    191 
    192 	DPRINTF(("START dname='%s' class=%s, zsize=%ld, naddrs=%d",
    193 		 dname, p_class(class), (long)zsize, naddrs));
    194 	save_pfcode = statp->pfcode;
    195 	statp->pfcode |= RES_PRF_HEAD2 | RES_PRF_HEAD1 | RES_PRF_HEADX |
    196 			 RES_PRF_QUES | RES_PRF_ANS |
    197 			 RES_PRF_AUTH | RES_PRF_ADD;
    198 	TAILQ_INIT(&nsrrs);
    199 
    200 	DPRINTF(("get the soa, and see if it has enough glue"));
    201 	if ((n = get_soa(statp, dname, class, opts, zname, zsize,
    202 			 mname, sizeof mname, &nsrrs)) < 0 ||
    203 	    ((opts & RES_EXHAUSTIVE) == 0 &&
    204 	     (n = satisfy(statp, mname, &nsrrs, addrs, naddrs)) > 0))
    205 		goto done;
    206 
    207 	DPRINTF(("get the ns rrset and see if it has enough glue"));
    208 	if ((n = get_ns(statp, zname, class, opts, &nsrrs)) < 0 ||
    209 	    ((opts & RES_EXHAUSTIVE) == 0 &&
    210 	     (n = satisfy(statp, mname, &nsrrs, addrs, naddrs)) > 0))
    211 		goto done;
    212 
    213 	DPRINTF(("get the missing glue and see if it's finally enough"));
    214 	if ((n = get_glue(statp, class, opts, &nsrrs)) >= 0)
    215 		n = satisfy(statp, mname, &nsrrs, addrs, naddrs);
    216 
    217  done:
    218 	DPRINTF(("FINISH n=%d (%s)", n, (n < 0) ? strerror(errno) : "OK"));
    219 	free_nsrrset(&nsrrs);
    220 	statp->pfcode = save_pfcode;
    221 	return (n);
    222 }
    223 
    224 /* Private. */
    225 
    226 static int
    227 satisfy(res_state statp, const char *mname, rrset_ns *nsrrsp,
    228 	union res_sockaddr_union *addrs, int naddrs)
    229 {
    230 	rr_ns *nsrr;
    231 	int n, x;
    232 
    233 	n = 0;
    234 	nsrr = find_ns(nsrrsp, mname);
    235 	if (nsrr != NULL) {
    236 		x = add_addrs(statp, nsrr, addrs, naddrs);
    237 		addrs += x;
    238 		naddrs -= x;
    239 		n += x;
    240 	}
    241 	TAILQ_FOREACH(nsrr, nsrrsp, link) {
    242 		if (naddrs <= 0)
    243 			break;
    244 		if (ns_samename(nsrr->name, mname) != 1) {
    245 			x = add_addrs(statp, nsrr, addrs, naddrs);
    246 			addrs += x;
    247 			naddrs -= x;
    248 			n += x;
    249 		}
    250 	}
    251 	DPRINTF(("satisfy(%s): %d", mname, n));
    252 	return (n);
    253 }
    254 
    255 static int
    256 add_addrs(res_state statp, rr_ns *nsrr,
    257 	  union res_sockaddr_union *addrs, int naddrs)
    258 {
    259 	rr_a *arr;
    260 	int n = 0;
    261 
    262 	TAILQ_FOREACH(arr, &nsrr->addrs, link) {
    263 		if (naddrs <= 0)
    264 			return (0);
    265 		*addrs++ = arr->addr;
    266 		naddrs--;
    267 		n++;
    268 	}
    269 	DPRINTF(("add_addrs: %d", n));
    270 	return (n);
    271 }
    272 
    273 static int
    274 get_soa(res_state statp, const char *dname, ns_class class, int opts,
    275 	char *zname, size_t zsize, char *mname, size_t msize,
    276 	rrset_ns *nsrrsp)
    277 {
    278 	char tname[NS_MAXDNAME];
    279 	u_char *resp = NULL;
    280 	int n, i, ancount, nscount;
    281 	ns_sect sect;
    282 	ns_msg msg;
    283 	u_int rcode;
    284 
    285 	/*
    286 	 * Find closest enclosing SOA, even if it's for the root zone.
    287 	 */
    288 
    289 	/* First canonicalize dname (exactly one unescaped trailing "."). */
    290 	if (ns_makecanon(dname, tname, sizeof tname) < 0)
    291 		goto cleanup;
    292 	dname = tname;
    293 
    294 	resp = malloc(NS_MAXMSG);
    295 	if (resp == NULL)
    296 		goto cleanup;
    297 
    298 	/* Now grovel the subdomains, hunting for an SOA answer or auth. */
    299 	for (;;) {
    300 		/* Leading or inter-label '.' are skipped here. */
    301 		while (*dname == '.')
    302 			dname++;
    303 
    304 		/* Is there an SOA? */
    305 		n = do_query(statp, dname, class, ns_t_soa, resp, &msg);
    306 		if (n < 0) {
    307 			DPRINTF(("get_soa: do_query('%s', %s) failed (%d)",
    308 				 dname, p_class(class), n));
    309 			goto cleanup;
    310 		}
    311 		if (n > 0) {
    312 			DPRINTF(("get_soa: CNAME or DNAME found"));
    313 			sect = ns_s_max, n = 0;
    314 		} else {
    315 			rcode = ns_msg_getflag(msg, ns_f_rcode);
    316 			ancount = ns_msg_count(msg, ns_s_an);
    317 			nscount = ns_msg_count(msg, ns_s_ns);
    318 			if (ancount > 0 && rcode == ns_r_noerror)
    319 				sect = ns_s_an, n = ancount;
    320 			else if (nscount > 0)
    321 				sect = ns_s_ns, n = nscount;
    322 			else
    323 				sect = ns_s_max, n = 0;
    324 		}
    325 		for (i = 0; i < n; i++) {
    326 			const char *t;
    327 			const u_char *rdata;
    328 			ns_rr rr;
    329 
    330 			if (ns_parserr(&msg, sect, i, &rr) < 0) {
    331 				DPRINTF(("get_soa: ns_parserr(%s, %d) failed",
    332 					 p_section(sect, ns_o_query), i));
    333 				goto cleanup;
    334 			}
    335 			if (ns_rr_type(rr) == ns_t_cname ||
    336 			    ns_rr_type(rr) == ns_t_dname)
    337 				break;
    338 			if (ns_rr_type(rr) != ns_t_soa ||
    339 			    ns_rr_class(rr) != class)
    340 				continue;
    341 			t = ns_rr_name(rr);
    342 			switch (sect) {
    343 			case ns_s_an:
    344 				if (ns_samedomain(dname, t) == 0) {
    345 					DPRINTF(
    346 				    ("get_soa: ns_samedomain('%s', '%s') == 0",
    347 						dname, t)
    348 						);
    349 					errno = EPROTOTYPE;
    350 					goto cleanup;
    351 				}
    352 				break;
    353 			case ns_s_ns:
    354 				if (ns_samename(dname, t) == 1 ||
    355 				    ns_samedomain(dname, t) == 0) {
    356 					DPRINTF(
    357 		       ("get_soa: ns_samename() || !ns_samedomain('%s', '%s')",
    358 						dname, t)
    359 						);
    360 					errno = EPROTOTYPE;
    361 					goto cleanup;
    362 				}
    363 				break;
    364 			default:
    365 				abort();
    366 			}
    367 			if (strlen(t) + 1 > zsize) {
    368 				DPRINTF(("get_soa: zname(%lu) too small (%lu)",
    369 					 (unsigned long)zsize,
    370 					 (unsigned long)strlen(t) + 1));
    371 				errno = EMSGSIZE;
    372 				goto cleanup;
    373 			}
    374 			strcpy(zname, t);
    375 			rdata = ns_rr_rdata(rr);
    376 			if (ns_name_uncompress(resp, ns_msg_end(msg), rdata,
    377 					       mname, msize) < 0) {
    378 				DPRINTF(("get_soa: ns_name_uncompress failed")
    379 					);
    380 				goto cleanup;
    381 			}
    382 			if (save_ns(statp, &msg, ns_s_ns,
    383 				    zname, class, opts, nsrrsp) < 0) {
    384 				DPRINTF(("get_soa: save_ns failed"));
    385 				goto cleanup;
    386 			}
    387 			free(resp);
    388 			return (0);
    389 		}
    390 
    391 		/* If we're out of labels, then not even "." has an SOA! */
    392 		if (*dname == '\0')
    393 			break;
    394 
    395 		/* Find label-terminating "."; top of loop will skip it. */
    396 		while (*dname != '.') {
    397 			if (*dname == '\\')
    398 				if (*++dname == '\0') {
    399 					errno = EMSGSIZE;
    400 					goto cleanup;
    401 				}
    402 			dname++;
    403 		}
    404 	}
    405 	DPRINTF(("get_soa: out of labels"));
    406 	errno = EDESTADDRREQ;
    407  cleanup:
    408 	if (resp != NULL)
    409 		free(resp);
    410 	return (-1);
    411 }
    412 
    413 static int
    414 get_ns(res_state statp, const char *zname, ns_class class, int opts,
    415       rrset_ns *nsrrsp)
    416 {
    417 	u_char *resp;
    418 	ns_msg msg;
    419 	int n;
    420 
    421 	resp = malloc(NS_MAXMSG);
    422 	if (resp == NULL)
    423 		return (-1);
    424 
    425 	/* Go and get the NS RRs for this zone. */
    426 	n = do_query(statp, zname, class, ns_t_ns, resp, &msg);
    427 	if (n != 0) {
    428 		DPRINTF(("get_ns: do_query('%s', %s) failed (%d)",
    429 			 zname, p_class(class), n));
    430 		free(resp);
    431 		return (-1);
    432 	}
    433 
    434 	/* Remember the NS RRs and associated A RRs that came back. */
    435 	if (save_ns(statp, &msg, ns_s_an, zname, class, opts, nsrrsp) < 0) {
    436 		DPRINTF(("get_ns save_ns('%s', %s) failed",
    437 			 zname, p_class(class)));
    438 		free(resp);
    439 		return (-1);
    440 	}
    441 
    442 	free(resp);
    443 	return (0);
    444 }
    445 
    446 static int
    447 get_glue(res_state statp, ns_class class, int opts, rrset_ns *nsrrsp) {
    448 	rr_ns *nsrr, *nsrr_n;
    449 	u_char *resp;
    450 
    451 	resp = malloc(NS_MAXMSG);
    452 	if (resp == NULL)
    453 		return(-1);
    454 
    455 	/* Go and get the A RRs for each empty NS RR on our list. */
    456 	TAILQ_FOREACH_SAFE(nsrr, nsrrsp, link, nsrr_n) {
    457 		ns_msg msg;
    458 		int n;
    459 
    460 		if ((nsrr->flags & RR_NS_HAVE_V4) == 0) {
    461 			n = do_query(statp, nsrr->name, class, ns_t_a,
    462 				     resp, &msg);
    463 			if (n < 0) {
    464 				DPRINTF(
    465 				       ("get_glue: do_query('%s', %s') failed",
    466 					nsrr->name, p_class(class)));
    467 				goto cleanup;
    468 			}
    469 			if (n > 0) {
    470 				DPRINTF((
    471 			"get_glue: do_query('%s', %s') CNAME or DNAME found",
    472 					 nsrr->name, p_class(class)));
    473 			}
    474 			if (save_a(statp, &msg, ns_s_an, nsrr->name, class,
    475 				   opts, nsrr) < 0) {
    476 				DPRINTF(("get_glue: save_r('%s', %s) failed",
    477 					 nsrr->name, p_class(class)));
    478 				goto cleanup;
    479 			}
    480 		}
    481 
    482 		if ((nsrr->flags & RR_NS_HAVE_V6) == 0) {
    483 			n = do_query(statp, nsrr->name, class, ns_t_aaaa,
    484 				     resp, &msg);
    485 			if (n < 0) {
    486 				DPRINTF(
    487 				       ("get_glue: do_query('%s', %s') failed",
    488 					nsrr->name, p_class(class)));
    489 				goto cleanup;
    490 			}
    491 			if (n > 0) {
    492 				DPRINTF((
    493 			"get_glue: do_query('%s', %s') CNAME or DNAME found",
    494 					 nsrr->name, p_class(class)));
    495 			}
    496 			if (save_a(statp, &msg, ns_s_an, nsrr->name, class,
    497 				   opts, nsrr) < 0) {
    498 				DPRINTF(("get_glue: save_r('%s', %s) failed",
    499 					 nsrr->name, p_class(class)));
    500 				goto cleanup;
    501 			}
    502 		}
    503 
    504 		/* If it's still empty, it's just chaff. */
    505 		if (TAILQ_EMPTY(&nsrr->addrs)) {
    506 			DPRINTF(("get_glue: removing empty '%s' NS",
    507 				 nsrr->name));
    508 			free_nsrr(nsrrsp, nsrr);
    509 		}
    510 	}
    511 	free(resp);
    512 	return (0);
    513 
    514  cleanup:
    515 	free(resp);
    516 	return (-1);
    517 }
    518 
    519 static int
    520 save_ns(res_state statp, ns_msg *msg, ns_sect sect,
    521 	const char *owner, ns_class class, int opts,
    522 	rrset_ns *nsrrsp)
    523 {
    524 	int i;
    525 
    526 	for (i = 0; i < ns_msg_count(*msg, sect); i++) {
    527 		char tname[MAXDNAME];
    528 		const u_char *rdata;
    529 		rr_ns *nsrr;
    530 		ns_rr rr;
    531 
    532 		if (ns_parserr(msg, sect, i, &rr) < 0) {
    533 			DPRINTF(("save_ns: ns_parserr(%s, %d) failed",
    534 				 p_section(sect, ns_o_query), i));
    535 			return (-1);
    536 		}
    537 		if (ns_rr_type(rr) != ns_t_ns ||
    538 		    ns_rr_class(rr) != class ||
    539 		    ns_samename(ns_rr_name(rr), owner) != 1)
    540 			continue;
    541 		nsrr = find_ns(nsrrsp, ns_rr_name(rr));
    542 		if (nsrr == NULL) {
    543 			nsrr = malloc(sizeof *nsrr);
    544 			if (nsrr == NULL) {
    545 				DPRINTF(("save_ns: malloc failed"));
    546 				return (-1);
    547 			}
    548 			rdata = ns_rr_rdata(rr);
    549 			if (ns_name_uncompress(ns_msg_base(*msg),
    550 					       ns_msg_end(*msg), rdata,
    551 					       tname, sizeof tname) < 0) {
    552 				DPRINTF(("save_ns: ns_name_uncompress failed")
    553 					);
    554 				free(nsrr);
    555 				return (-1);
    556 			}
    557 			nsrr->name = strdup(tname);
    558 			if (nsrr->name == NULL) {
    559 				DPRINTF(("save_ns: strdup failed"));
    560 				free(nsrr);
    561 				return (-1);
    562 			}
    563 			TAILQ_INIT(&nsrr->addrs);
    564 			nsrr->flags = 0;
    565 			TAILQ_INSERT_TAIL(nsrrsp, nsrr, link);
    566 		}
    567 		if (save_a(statp, msg, ns_s_ar,
    568 			   nsrr->name, class, opts, nsrr) < 0) {
    569 			DPRINTF(("save_ns: save_r('%s', %s) failed",
    570 				 nsrr->name, p_class(class)));
    571 			return (-1);
    572 		}
    573 	}
    574 	return (0);
    575 }
    576 
    577 static int
    578 save_a(res_state statp, ns_msg *msg, ns_sect sect,
    579        const char *owner, ns_class class, int opts,
    580        rr_ns *nsrr)
    581 {
    582 	int i;
    583 
    584 	for (i = 0; i < ns_msg_count(*msg, sect); i++) {
    585 		ns_rr rr;
    586 		rr_a *arr;
    587 
    588 		if (ns_parserr(msg, sect, i, &rr) < 0) {
    589 			DPRINTF(("save_a: ns_parserr(%s, %d) failed",
    590 				 p_section(sect, ns_o_query), i));
    591 			return (-1);
    592 		}
    593 		if ((ns_rr_type(rr) != ns_t_a &&
    594 		     ns_rr_type(rr) != ns_t_aaaa) ||
    595 		    ns_rr_class(rr) != class ||
    596 		    ns_samename(ns_rr_name(rr), owner) != 1 ||
    597 		    ns_rr_rdlen(rr) != NS_INADDRSZ)
    598 			continue;
    599 		if ((opts & RES_IPV6ONLY) != 0 && ns_rr_type(rr) != ns_t_aaaa)
    600 			continue;
    601 		if ((opts & RES_IPV4ONLY) != 0 && ns_rr_type(rr) != ns_t_a)
    602 			continue;
    603 		arr = malloc(sizeof *arr);
    604 		if (arr == NULL) {
    605 			DPRINTF(("save_a: malloc failed"));
    606 			return (-1);
    607 		}
    608 		memset(&arr->addr, 0, sizeof(arr->addr));
    609 		switch (ns_rr_type(rr)) {
    610 		case ns_t_a:
    611 			arr->addr.sin.sin_family = AF_INET;
    612 #ifdef HAVE_SA_LEN
    613 			arr->addr.sin.sin_len = sizeof(arr->addr.sin);
    614 #endif
    615 			memcpy(&arr->addr.sin.sin_addr, ns_rr_rdata(rr),
    616 			       NS_INADDRSZ);
    617 			arr->addr.sin.sin_port = htons(NAMESERVER_PORT);
    618 			nsrr->flags |= RR_NS_HAVE_V4;
    619 			break;
    620 		case ns_t_aaaa:
    621 			arr->addr.sin6.sin6_family = AF_INET6;
    622 #ifdef HAVE_SA_LEN
    623 			arr->addr.sin6.sin6_len = sizeof(arr->addr.sin6);
    624 #endif
    625 			memcpy(&arr->addr.sin6.sin6_addr, ns_rr_rdata(rr), 16);
    626 			arr->addr.sin.sin_port = htons(NAMESERVER_PORT);
    627 			nsrr->flags |= RR_NS_HAVE_V6;
    628 			break;
    629 		default:
    630 			abort();
    631 		}
    632 		TAILQ_INSERT_TAIL(&nsrr->addrs, arr, link);
    633 	}
    634 	return (0);
    635 }
    636 
    637 static void
    638 free_nsrrset(rrset_ns *nsrrsp) {
    639 	rr_ns *nsrr, *tmp;
    640 
    641 	TAILQ_FOREACH_SAFE(nsrr, nsrrsp, link, tmp)
    642 		free_nsrr(nsrrsp, nsrr);
    643 }
    644 
    645 static void
    646 free_nsrr(rrset_ns *nsrrsp, rr_ns *nsrr) {
    647 	rr_a *arr, *n_arr;
    648 	char *tmp;
    649 
    650 	TAILQ_FOREACH_SAFE(arr, &nsrr->addrs, link, n_arr) {
    651 		TAILQ_REMOVE(&nsrr->addrs, arr, link);
    652 		free(arr);
    653 	}
    654 	DE_CONST(nsrr->name, tmp);
    655 	free(tmp);
    656 	TAILQ_REMOVE(nsrrsp, nsrr, link);
    657 	free(nsrr);
    658 }
    659 
    660 static rr_ns *
    661 find_ns(rrset_ns *nsrrsp, const char *dname) {
    662 	rr_ns *nsrr;
    663 
    664 	TAILQ_FOREACH(nsrr, nsrrsp, link)
    665 		if (ns_samename(nsrr->name, dname) == 1)
    666 			return (nsrr);
    667 	return (NULL);
    668 }
    669 
    670 static int
    671 do_query(res_state statp, const char *dname, ns_class class, ns_type qtype,
    672 	 u_char *resp, ns_msg *msg)
    673 {
    674 	u_char req[NS_PACKETSZ];
    675 	int i, n;
    676 
    677 	n = res_nmkquery(statp, ns_o_query, dname, class, qtype,
    678 			 NULL, 0, NULL, req, NS_PACKETSZ);
    679 	if (n < 0) {
    680 		DPRINTF(("do_query: res_nmkquery failed"));
    681 		return (-1);
    682 	}
    683 	n = res_nsend(statp, req, n, resp, NS_MAXMSG);
    684 	if (n < 0) {
    685 		DPRINTF(("do_query: res_nsend failed"));
    686 		return (-1);
    687 	}
    688 	if (n == 0) {
    689 		DPRINTF(("do_query: res_nsend returned 0"));
    690 		errno = EMSGSIZE;
    691 		return (-1);
    692 	}
    693 	if (ns_initparse(resp, n, msg) < 0) {
    694 		DPRINTF(("do_query: ns_initparse failed"));
    695 		return (-1);
    696 	}
    697 	n = 0;
    698 	for (i = 0; i < ns_msg_count(*msg, ns_s_an); i++) {
    699 		ns_rr rr;
    700 
    701 		if (ns_parserr(msg, ns_s_an, i, &rr) < 0) {
    702 			DPRINTF(("do_query: ns_parserr failed"));
    703 			return (-1);
    704 		}
    705 		n += (ns_rr_class(rr) == class &&
    706 		      (ns_rr_type(rr) == ns_t_cname ||
    707 		       ns_rr_type(rr) == ns_t_dname));
    708 	}
    709 	return (n);
    710 }
    711 
    712 static void
    713 res_dprintf(const char *fmt, ...) {
    714 	va_list ap;
    715 
    716 	va_start(ap, fmt);
    717 	fputs(";; res_findzonecut: ", stderr);
    718 	vfprintf(stderr, fmt, ap);
    719 	fputc('\n', stderr);
    720 	va_end(ap);
    721 }
    722 
    723 /*! \file */
    724