xref: /netbsd-src/external/ibm-public/postfix/dist/src/dns/dns_lookup.c (revision aef5eb5f59cdfe8314f1b5f78ac04eb144e44010)
1 /*	$NetBSD: dns_lookup.c,v 1.7 2022/10/08 16:12:45 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	dns_lookup 3
6 /* SUMMARY
7 /*	domain name service lookup
8 /* SYNOPSIS
9 /*	#include <dns.h>
10 /*
11 /*	int	dns_lookup(name, type, rflags, list, fqdn, why)
12 /*	const char *name;
13 /*	unsigned type;
14 /*	unsigned rflags;
15 /*	DNS_RR	**list;
16 /*	VSTRING *fqdn;
17 /*	VSTRING *why;
18 /*
19 /*	int	dns_lookup_l(name, rflags, list, fqdn, why, lflags, ltype, ...)
20 /*	const char *name;
21 /*	unsigned rflags;
22 /*	DNS_RR	**list;
23 /*	VSTRING *fqdn;
24 /*	VSTRING *why;
25 /*	int	lflags;
26 /*	unsigned ltype;
27 /*
28 /*	int	dns_lookup_v(name, rflags, list, fqdn, why, lflags, ltype)
29 /*	const char *name;
30 /*	unsigned rflags;
31 /*	DNS_RR	**list;
32 /*	VSTRING *fqdn;
33 /*	VSTRING *why;
34 /*	int	lflags;
35 /*	unsigned *ltype;
36 /*
37 /*	int	dns_get_h_errno()
38 /* AUXILIARY FUNCTIONS
39 /*	extern int var_dns_ncache_ttl_fix;
40 /*
41 /*	int	dns_lookup_r(name, type, rflags, list, fqdn, why, rcode)
42 /*	const char *name;
43 /*	unsigned type;
44 /*	unsigned rflags;
45 /*	DNS_RR	**list;
46 /*	VSTRING *fqdn;
47 /*	VSTRING *why;
48 /*	int	*rcode;
49 /*
50 /*	int	dns_lookup_rl(name, rflags, list, fqdn, why, rcode, lflags,
51 /*				ltype, ...)
52 /*	const char *name;
53 /*	unsigned rflags;
54 /*	DNS_RR	**list;
55 /*	VSTRING *fqdn;
56 /*	VSTRING *why;
57 /*	int	*rcode;
58 /*	int	lflags;
59 /*	unsigned ltype;
60 /*
61 /*	int	dns_lookup_rv(name, rflags, list, fqdn, why, rcode, lflags,
62 /*				ltype)
63 /*	const char *name;
64 /*	unsigned rflags;
65 /*	DNS_RR	**list;
66 /*	VSTRING *fqdn;
67 /*	VSTRING *why;
68 /*	int	*rcode;
69 /*	int	lflags;
70 /*	unsigned *ltype;
71 /*
72 /*	int	dns_lookup_x(name, type, rflags, list, fqdn, why, rcode, lflags)
73 /*	const char *name;
74 /*	unsigned type;
75 /*	unsigned rflags;
76 /*	DNS_RR	**list;
77 /*	VSTRING *fqdn;
78 /*	VSTRING *why;
79 /*	int	*rcode;
80 /*	unsigned lflags;
81 /* DESCRIPTION
82 /*	dns_lookup() looks up DNS resource records. When requested to
83 /*	look up data other than type CNAME, it will follow a limited
84 /*	number of CNAME indirections. All result names (including
85 /*	null terminator) will fit a buffer of size DNS_NAME_LEN.
86 /*	All name results are validated by \fIvalid_hostname\fR();
87 /*	an invalid name is reported as a DNS_INVAL result, while
88 /*	malformed replies are reported as transient errors.
89 /*
90 /*	dns_get_h_errno() returns the last error. This deprecates
91 /*	usage of the global h_errno variable. We should not rely
92 /*	on that being updated.
93 /*
94 /*	dns_lookup_l() and dns_lookup_v() allow the user to specify
95 /*	a list of resource types.
96 /*
97 /*	dns_lookup_x, dns_lookup_r(), dns_lookup_rl() and dns_lookup_rv()
98 /*	accept or return additional information.
99 /*
100 /*	The var_dns_ncache_ttl_fix variable controls a workaround
101 /*	for res_search(3) implementations that break the
102 /*	DNS_REQ_FLAG_NCACHE_TTL feature. The workaround does not
103 /*	support EDNS0 or DNSSEC, but it should be sufficient for
104 /*	DNSBL/DNSWL lookups.
105 /* INPUTS
106 /* .ad
107 /* .fi
108 /* .IP name
109 /*	The name to be looked up in the domain name system.
110 /*	This name must pass the valid_hostname() test; it
111 /*	must not be an IP address.
112 /* .IP type
113 /*	The resource record type to be looked up (T_A, T_MX etc.).
114 /* .IP rflags
115 /*	Resolver flags. These are a bitwise OR of:
116 /* .RS
117 /* .IP RES_DEBUG
118 /*	Print debugging information.
119 /* .IP RES_DNSRCH
120 /*	Search local domain and parent domains.
121 /* .IP RES_DEFNAMES
122 /*	Append local domain to unqualified names.
123 /* .IP RES_USE_DNSSEC
124 /*	Request DNSSEC validation. This flag is silently ignored
125 /*	when the system stub resolver API, resolver(3), does not
126 /*	implement DNSSEC.
127 /*	Automatically turns on the RES_TRUSTAD flag on systems that
128 /*	support this flag (this behavior will be more configurable
129 /*	in a later release).
130 /* .RE
131 /* .IP lflags
132 /*	Flags that control the operation of the dns_lookup*()
133 /*	functions.  DNS_REQ_FLAG_NONE requests no special processing.
134 /*	Otherwise, specify one or more of the following:
135 /* .RS
136 /* .IP DNS_REQ_FLAG_STOP_INVAL
137 /*	This flag is used by dns_lookup_l() and dns_lookup_v().
138 /*	Invoke dns_lookup() for the resource types in the order as
139 /*	specified, and return when dns_lookup() returns DNS_INVAL.
140 /* .IP DNS_REQ_FLAG_STOP_NULLMX
141 /*	This flag is used by dns_lookup_l() and dns_lookup_v().
142 /*	Invoke dns_lookup() for the resource types in the order as
143 /*	specified, and return when dns_lookup() returns DNS_NULLMX.
144 /* .IP DNS_REQ_FLAG_STOP_MX_POLICY
145 /*	This flag is used by dns_lookup_l() and dns_lookup_v().
146 /*	Invoke dns_lookup() for the resource types in the order as
147 /*	specified, and return when dns_lookup() returns DNS_POLICY
148 /*	for an MX query.
149 /* .IP DNS_REQ_FLAG_STOP_OK
150 /*	This flag is used by dns_lookup_l() and dns_lookup_v().
151 /*	Invoke dns_lookup() for the resource types in the order as
152 /*	specified, and return when dns_lookup() returns DNS_OK.
153 /* .IP DNS_REQ_FLAG_NCACHE_TTL
154 /*	When the lookup result status is DNS_NOTFOUND, return the
155 /*	SOA record(s) from the authority section in the reply, if
156 /*	available. The per-record reply TTL specifies how long the
157 /*	DNS_NOTFOUND answer is valid. The caller should pass the
158 /*	record(s) to dns_rr_free().
159 /*	Logs a warning if the RES_DNSRCH or RES_DEFNAMES resolver
160 /*	flags are set, and disables those flags.
161 /* .RE
162 /* .IP ltype
163 /*	The resource record types to be looked up. In the case of
164 /*	dns_lookup_l(), this is a null-terminated argument list.
165 /*	In the case of dns_lookup_v(), this is a null-terminated
166 /*	integer array.
167 /* OUTPUTS
168 /* .ad
169 /* .fi
170 /* .IP list
171 /*	A null pointer, or a pointer to a variable that receives a
172 /*	list of requested resource records.
173 /* .IP fqdn
174 /*	A null pointer, or storage for the fully-qualified domain
175 /*	name found for \fIname\fR.
176 /* .IP why
177 /*	A null pointer, or storage for the reason for failure.
178 /* .IP rcode
179 /*	Pointer to storage for the reply RCODE value. This gives
180 /*	more detailed information than DNS_FAIL, DNS_RETRY, etc.
181 /* DIAGNOSTICS
182 /*	If DNSSEC validation is requested but the response is not
183 /*	DNSSEC validated, dns_lookup() will send a one-time probe
184 /*	query as configured with the \fBdnssec_probe\fR configuration
185 /*	parameter, and will log a warning when the probe response
186 /*	was not DNSSEC validated.
187 /* .PP
188 /*	dns_lookup() returns one of the following codes and sets the
189 /*	\fIwhy\fR argument accordingly:
190 /* .IP DNS_OK
191 /*	The DNS query succeeded.
192 /* .IP DNS_POLICY
193 /*	The DNS query succeeded, but the answer did not pass the
194 /*	policy filter.
195 /* .IP DNS_NOTFOUND
196 /*	The DNS query succeeded; the requested information was not found.
197 /* .IP DNS_NULLMX
198 /*	The DNS query succeeded; the requested service is unavailable.
199 /*	This is returned when the list argument is not a null
200 /*	pointer, and an MX lookup result contains a null server
201 /*	name (so-called "nullmx" record).
202 /* .IP DNS_INVAL
203 /*	The DNS query succeeded; the result failed the valid_hostname() test.
204 /*
205 /*	NOTE: the valid_hostname() test is skipped for results that
206 /*	the caller suppresses explicitly.  For example, when the
207 /*	caller requests MX record lookup but specifies a null
208 /*	resource record list argument, no syntax check will be done
209 /*	for MX server names.
210 /* .IP DNS_RETRY
211 /*	The query failed, or the reply was malformed.
212 /*	The problem is considered transient.
213 /* .IP DNS_FAIL
214 /*	The query failed.
215 /* BUGS
216 /*	dns_lookup() implements a subset of all possible resource types:
217 /*	CNAME, MX, A, and some records with similar formatting requirements.
218 /*	It is unwise to specify the T_ANY wildcard resource type.
219 /*
220 /*	It takes a surprising amount of code to accomplish what appears
221 /*	to be a simple task. Later versions of the mail system may implement
222 /*	their own DNS client software.
223 /* SEE ALSO
224 /*	dns_rr(3) resource record memory and list management
225 /* LICENSE
226 /* .ad
227 /* .fi
228 /*	The Secure Mailer license must be distributed with this software.
229 /* AUTHOR(S)
230 /*	Wietse Venema
231 /*	IBM T.J. Watson Research
232 /*	P.O. Box 704
233 /*	Yorktown Heights, NY 10598, USA
234 /*
235 /*	Wietse Venema
236 /*	Google, Inc.
237 /*	111 8th Avenue
238 /*	New York, NY 10011, USA
239 /*--*/
240 
241 /* System library. */
242 
243 #include <sys_defs.h>
244 #include <netdb.h>
245 #include <string.h>
246 #include <ctype.h>
247 
248 /* Utility library. */
249 
250 #include <mymalloc.h>
251 #include <vstring.h>
252 #include <msg.h>
253 #include <valid_hostname.h>
254 #include <stringops.h>
255 
256 /* Global library. */
257 
258 #include <mail_params.h>
259 
260 /* DNS library. */
261 
262 #define LIBDNS_INTERNAL
263 #include "dns.h"
264 
265 /* Local stuff. */
266 
267  /*
268   * Structure to keep track of things while decoding a name server reply.
269   */
270 #define DEF_DNS_REPLY_SIZE	4096	/* in case we're using TCP */
271 #define MAX_DNS_REPLY_SIZE	65536	/* in case we're using TCP */
272 #define MAX_DNS_QUERY_SIZE	2048	/* XXX */
273 
274 typedef struct DNS_REPLY {
275     unsigned char *buf;			/* raw reply data */
276     size_t  buf_len;			/* reply buffer length */
277     int     rcode;			/* unfiltered reply code */
278     int     dnssec_ad;			/* DNSSEC AD bit */
279     int     query_count;		/* number of queries */
280     int     answer_count;		/* number of answers */
281     int     auth_count;			/* number of authority records */
282     unsigned char *query_start;		/* start of query data */
283     unsigned char *answer_start;	/* start of answer data */
284     unsigned char *end;			/* first byte past reply */
285 } DNS_REPLY;
286 
287  /*
288   * Test/set primitives to determine if the reply buffer contains a server
289   * response. We use this when the caller requests DNS_REQ_FLAG_NCACHE_TTL,
290   * and the DNS server replies that the requested record does not exist.
291   */
292 #define TEST_HAVE_DNS_REPLY_PACKET(r)	((r)->end > (r)->buf)
293 #define SET_HAVE_DNS_REPLY_PACKET(r, l)	((r)->end = (r)->buf + (l))
294 #define SET_NO_DNS_REPLY_PACKET(r)	((r)->end = (r)->buf)
295 
296 #define INET_ADDR_LEN	4		/* XXX */
297 #define INET6_ADDR_LEN	16		/* XXX */
298 
299  /*
300   * Use the threadsafe resolver API if available, not because it is theadsafe,
301   * but because it has more functionality.
302   */
303 #ifdef USE_RES_NCALLS
304 static struct __res_state dns_res_state;
305 
306 #define DNS_RES_NINIT		res_ninit
307 #define DNS_RES_NMKQUERY	res_nmkquery
308 #define DNS_RES_NSEARCH		res_nsearch
309 #define DNS_RES_NSEND		res_nsend
310 #define DNS_GET_H_ERRNO(statp)	((statp)->res_h_errno)
311 
312  /*
313   * Alias new resolver API calls to the legacy resolver API which stores
314   * resolver and error state in global variables.
315   */
316 #else
317 #define dns_res_state		_res
318 #define DNS_RES_NINIT(statp)	res_init()
319 #define DNS_RES_NMKQUERY(statp, op, dname, class, type, data, datalen, \
320 		newrr, buf, buflen) \
321 	res_mkquery((op), (dname), (class), (type), (data), (datalen), \
322 		(newrr), (buf), (buflen))
323 #define DNS_RES_NSEARCH(statp, dname, class, type, answer, anslen) \
324 	res_search((dname), (class), (type), (answer), (anslen))
325 #define DNS_RES_NSEND(statp, msg, msglen, answer, anslen) \
326 	res_send((msg), (msglen), (answer), (anslen))
327 #define DNS_GET_H_ERRNO(statp)	(h_errno)
328 #endif
329 
330 #ifdef USE_SET_H_ERRNO
331 #define DNS_SET_H_ERRNO(statp, err)	(set_h_errno(err))
332 #else
333 #define DNS_SET_H_ERRNO(statp, err)	(DNS_GET_H_ERRNO(statp) = (err))
334 #endif
335 
336  /*
337   * To improve postscreen's allowlisting support, we need to know how long a
338   * DNSBL "not found" answer is valid. The 2010 implementation assumed it was
339   * valid for 3600 seconds. That is too long by 2015 standards.
340   *
341   * Instead of guessing, Postfix 3.1 and later implement RFC 2308 (DNS NCACHE),
342   * where a DNS server provides the TTL of a "not found" response as the TTL
343   * of an SOA record in the authority section.
344   *
345   * Unfortunately, the res_search() and res_query() API gets in the way. These
346   * functions overload their result value, the server reply length, and
347   * return -1 when the requested record does not exist. With libbind-based
348   * implementations, the server response is still available in an application
349   * buffer, thanks to the promise that res_query() and res_search() invoke
350   * res_send(), which returns the full server response even if the requested
351   * record does not exist.
352   *
353   * If this promise is broken (for example, res_search() does not call
354   * res_send(), but some non-libbind implementation that updates the
355   * application buffer only when the requested record exists), then we have a
356   * way out by setting the var_dns_ncache_ttl_fix variable. This enables a
357   * limited res_query() clone that should be sufficient for DNSBL / DNSWL
358   * lookups.
359   *
360   * The libunbound API does not comingle the reply length and reply status
361   * information, but that will have to wait until it is safe to make
362   * libunbound a mandatory dependency for Postfix.
363   */
364 #ifdef HAVE_RES_SEND
365 
366 /* dns_neg_query - a res_query() clone that can return negative replies */
367 
368 static int dns_neg_query(const char *name, int class, int type,
369 			         unsigned char *answer, int anslen)
370 {
371     unsigned char msg_buf[MAX_DNS_QUERY_SIZE];
372     HEADER *reply_header = (HEADER *) answer;
373     int     len;
374 
375     /*
376      * Differences with res_query() from libbind:
377      *
378      * - This function returns a positive server reply length not only in case
379      * of success, but in all cases where a server reply is available that
380      * passes the preliminary checks in res_send().
381      *
382      * - This function clears h_errno in case of success. The caller must use
383      * h_errno instead of the return value to decide if the lookup was
384      * successful.
385      *
386      * - No support for EDNS0 and DNSSEC (including turning off EDNS0 after
387      * error). That should be sufficient for DNS reputation lookups where the
388      * reply contains a small number of IP addresses.  TXT records are out of
389      * scope for this workaround.
390      */
391     reply_header->rcode = NOERROR;
392 
393 #define NO_MKQUERY_DATA_BUF     ((unsigned char *) 0)
394 #define NO_MKQUERY_DATA_LEN     ((int) 0)
395 #define NO_MKQUERY_NEWRR        ((unsigned char *) 0)
396 
397     if ((len = DNS_RES_NMKQUERY(&dns_res_state,
398 			      QUERY, name, class, type, NO_MKQUERY_DATA_BUF,
399 				NO_MKQUERY_DATA_LEN, NO_MKQUERY_NEWRR,
400 				msg_buf, sizeof(msg_buf))) < 0) {
401 	DNS_SET_H_ERRNO(&dns_res_state, NO_RECOVERY);
402 	if (msg_verbose)
403 	    msg_info("res_nmkquery() failed");
404 	return (len);
405     } else if ((len = DNS_RES_NSEND(&dns_res_state,
406 				    msg_buf, len, answer, anslen)) < 0) {
407 	DNS_SET_H_ERRNO(&dns_res_state, TRY_AGAIN);
408 	if (msg_verbose)
409 	    msg_info("res_nsend() failed");
410 	return (len);
411     } else {
412 	switch (reply_header->rcode) {
413 	case NXDOMAIN:
414 	    DNS_SET_H_ERRNO(&dns_res_state, HOST_NOT_FOUND);
415 	    break;
416 	case NOERROR:
417 	    if (reply_header->ancount != 0)
418 		DNS_SET_H_ERRNO(&dns_res_state, 0);
419 	    else
420 		DNS_SET_H_ERRNO(&dns_res_state, NO_DATA);
421 	    break;
422 	case SERVFAIL:
423 	    DNS_SET_H_ERRNO(&dns_res_state, TRY_AGAIN);
424 	    break;
425 	default:
426 	    DNS_SET_H_ERRNO(&dns_res_state, NO_RECOVERY);
427 	    break;
428 	}
429 	return (len);
430     }
431 }
432 
433 #endif
434 
435 /* dns_neg_search - res_search() that can return negative replies */
436 
437 static int dns_neg_search(const char *name, int class, int type,
438 	               unsigned char *answer, int anslen, int keep_notfound)
439 {
440     int     len;
441 
442     /*
443      * Differences with res_search() from libbind:
444      *
445      * - With a non-zero keep_notfound argument, this function returns a
446      * positive server reply length not only in case of success, but also in
447      * case of a "notfound" reply status. The keep_notfound argument is
448      * usually zero, which allows us to avoid an unnecessary memset() call in
449      * the most common use case.
450      *
451      * - This function clears h_errno in case of success. The caller must use
452      * h_errno instead of the return value to decide if a lookup was
453      * successful.
454      */
455 #define NOT_FOUND_H_ERRNO(he) ((he) == HOST_NOT_FOUND || (he) == NO_DATA)
456 
457     if (keep_notfound)
458 	/* Prepare for returning a null-padded server reply. */
459 	memset(answer, 0, anslen);
460     len = DNS_RES_NSEARCH(&dns_res_state, name, class, type, answer, anslen);
461     /* Begin API creep workaround. */
462     if (len < 0 && DNS_GET_H_ERRNO(&dns_res_state) == 0) {
463 	DNS_SET_H_ERRNO(&dns_res_state, TRY_AGAIN);
464 	msg_warn("res_nsearch(state, \"%s\", %d, %d, %p, %d) returns %d"
465 		 " with h_errno==0 -- setting h_errno=TRY_AGAIN",
466 		 name, class, type, answer, anslen, len);
467     }
468     /* End API creep workaround. */
469     if (len > 0) {
470 	DNS_SET_H_ERRNO(&dns_res_state, 0);
471     } else if (keep_notfound
472 	       && NOT_FOUND_H_ERRNO(DNS_GET_H_ERRNO(&dns_res_state))) {
473 	/* Expect to return a null-padded server reply. */
474 	len = anslen;
475     }
476     return (len);
477 }
478 
479 /* dns_query - query name server and pre-parse the reply */
480 
481 static int dns_query(const char *name, int type, unsigned flags,
482 		             DNS_REPLY *reply, VSTRING *why, unsigned lflags)
483 {
484     HEADER *reply_header;
485     int     len;
486     unsigned long saved_options;
487     int     keep_notfound = (lflags & DNS_REQ_FLAG_NCACHE_TTL);
488 
489     /*
490      * Initialize the reply buffer.
491      */
492     if (reply->buf == 0) {
493 	reply->buf = (unsigned char *) mymalloc(DEF_DNS_REPLY_SIZE);
494 	reply->buf_len = DEF_DNS_REPLY_SIZE;
495     }
496 
497     /*
498      * Initialize the name service.
499      */
500     if ((dns_res_state.options & RES_INIT) == 0
501 	&& DNS_RES_NINIT(&dns_res_state) < 0) {
502 	if (why)
503 	    vstring_strcpy(why, "Name service initialization failure");
504 	return (DNS_FAIL);
505     }
506 
507     /*
508      * Set search options: debugging, parent domain search, append local
509      * domain. Do not allow the user to control other features.
510      */
511 #define USER_FLAGS (RES_DEBUG | RES_DNSRCH | RES_DEFNAMES | RES_USE_DNSSEC)
512 
513     if ((flags & USER_FLAGS) != flags)
514 	msg_panic("dns_query: bad flags: %d", flags);
515 
516     /*
517      * Set extra options that aren't exposed to the application.
518      */
519 #define XTRA_FLAGS (RES_USE_EDNS0 | RES_TRUSTAD)
520 
521     if (DNS_WANT_DNSSEC_VALIDATION(flags))
522 	flags |= (RES_USE_EDNS0 | RES_TRUSTAD);
523 
524     /*
525      * Can't append domains: we need the right SOA TTL.
526      */
527 #define APPEND_DOMAIN_FLAGS (RES_DNSRCH | RES_DEFNAMES)
528 
529     if (keep_notfound && (flags & APPEND_DOMAIN_FLAGS)) {
530 	msg_warn("negative caching disables RES_DEFNAMES and RES_DNSRCH");
531 	flags &= ~APPEND_DOMAIN_FLAGS;
532     }
533 
534     /*
535      * Save and restore resolver options that we overwrite, to avoid
536      * surprising behavior in other code that also invokes the resolver.
537      */
538 #define SAVE_FLAGS (USER_FLAGS | XTRA_FLAGS)
539 
540     saved_options = (dns_res_state.options & SAVE_FLAGS);
541 
542     /*
543      * Perform the lookup. Claim that the information cannot be found if and
544      * only if the name server told us so.
545      */
546     for (;;) {
547 	dns_res_state.options &= ~saved_options;
548 	dns_res_state.options |= flags;
549 	if (keep_notfound && var_dns_ncache_ttl_fix) {
550 #ifdef HAVE_RES_SEND
551 	    len = dns_neg_query((char *) name, C_IN, type, reply->buf,
552 				reply->buf_len);
553 #else
554 	    var_dns_ncache_ttl_fix = 0;
555 	    msg_warn("system library does not support %s=yes"
556 		     " -- ignoring this setting", VAR_DNS_NCACHE_TTL_FIX);
557 	    len = dns_neg_search((char *) name, C_IN, type, reply->buf,
558 				 reply->buf_len, keep_notfound);
559 #endif
560 	} else {
561 	    len = dns_neg_search((char *) name, C_IN, type, reply->buf,
562 				 reply->buf_len, keep_notfound);
563 	}
564 	dns_res_state.options &= ~flags;
565 	dns_res_state.options |= saved_options;
566 	reply_header = (HEADER *) reply->buf;
567 	reply->rcode = reply_header->rcode;
568 	if ((reply->dnssec_ad = !!reply_header->ad) != 0)
569 	    DNS_SEC_STATS_SET(DNS_SEC_FLAG_AVAILABLE);
570 	if (DNS_GET_H_ERRNO(&dns_res_state) != 0) {
571 	    if (why)
572 		vstring_sprintf(why, "Host or domain name not found. "
573 				"Name service error for name=%s type=%s: %s",
574 				name, dns_strtype(type),
575 			     dns_strerror(DNS_GET_H_ERRNO(&dns_res_state)));
576 	    if (msg_verbose)
577 		msg_info("dns_query: %s (%s): %s",
578 			 name, dns_strtype(type),
579 			 dns_strerror(DNS_GET_H_ERRNO(&dns_res_state)));
580 	    switch (DNS_GET_H_ERRNO(&dns_res_state)) {
581 	    case NO_RECOVERY:
582 		return (DNS_FAIL);
583 	    case HOST_NOT_FOUND:
584 	    case NO_DATA:
585 		if (keep_notfound)
586 		    break;
587 		SET_NO_DNS_REPLY_PACKET(reply);
588 		return (DNS_NOTFOUND);
589 	    default:
590 		return (DNS_RETRY);
591 	    }
592 	} else {
593 	    if (msg_verbose)
594 		msg_info("dns_query: %s (%s): OK", name, dns_strtype(type));
595 	}
596 
597 	if (reply_header->tc == 0 || reply->buf_len >= MAX_DNS_REPLY_SIZE)
598 	    break;
599 	reply->buf = (unsigned char *)
600 	    myrealloc((void *) reply->buf, 2 * reply->buf_len);
601 	reply->buf_len *= 2;
602     }
603 
604     /*
605      * Future proofing. If this reaches the panic call, then some code change
606      * introduced a bug.
607      */
608     if (len < 0)
609 	msg_panic("dns_query: bad length %d (h_errno=%s)",
610 		  len, dns_strerror(DNS_GET_H_ERRNO(&dns_res_state)));
611 
612     /*
613      * Paranoia.
614      */
615     if (len > reply->buf_len) {
616 	msg_warn("reply length %d > buffer length %d for name=%s type=%s",
617 		 len, (int) reply->buf_len, name, dns_strtype(type));
618 	len = reply->buf_len;
619     }
620 
621     /*
622      * Initialize the reply structure. Some structure members are filled on
623      * the fly while the reply is being parsed.
624      */
625     SET_HAVE_DNS_REPLY_PACKET(reply, len);
626     reply->query_start = reply->buf + sizeof(HEADER);
627     reply->answer_start = 0;
628     reply->query_count = ntohs(reply_header->qdcount);
629     reply->answer_count = ntohs(reply_header->ancount);
630     reply->auth_count = ntohs(reply_header->nscount);
631     if (msg_verbose > 1)
632 	msg_info("dns_query: reply len=%d ancount=%d nscount=%d",
633 		 len, reply->answer_count, reply->auth_count);
634 
635     /*
636      * Future proofing. If this reaches the panic call, then some code change
637      * introduced a bug.
638      */
639     if (DNS_GET_H_ERRNO(&dns_res_state) == 0) {
640 	return (DNS_OK);
641     } else if (keep_notfound) {
642 	return (DNS_NOTFOUND);
643     } else {
644 	msg_panic("dns_query: unexpected reply status: %s",
645 		  dns_strerror(DNS_GET_H_ERRNO(&dns_res_state)));
646     }
647 }
648 
649 /* dns_skip_query - skip query data in name server reply */
650 
651 static int dns_skip_query(DNS_REPLY *reply)
652 {
653     int     query_count = reply->query_count;
654     unsigned char *pos = reply->query_start;
655     int     len;
656 
657     /*
658      * For each query, skip over the domain name and over the fixed query
659      * data.
660      */
661     while (query_count-- > 0) {
662 	if (pos >= reply->end)
663 	    return DNS_RETRY;
664 	len = dn_skipname(pos, reply->end);
665 	if (len < 0)
666 	    return (DNS_RETRY);
667 	pos += len + QFIXEDSZ;
668     }
669     reply->answer_start = pos;
670     return (DNS_OK);
671 }
672 
673 /* dns_get_fixed - extract fixed data from resource record */
674 
675 static int dns_get_fixed(unsigned char *pos, DNS_FIXED *fixed)
676 {
677     GETSHORT(fixed->type, pos);
678     GETSHORT(fixed->class, pos);
679     GETLONG(fixed->ttl, pos);
680     GETSHORT(fixed->length, pos);
681 
682     if (fixed->class != C_IN) {
683 	msg_warn("dns_get_fixed: bad class: %u", fixed->class);
684 	return (DNS_RETRY);
685     }
686     return (DNS_OK);
687 }
688 
689 /* valid_rr_name - validate hostname in resource record */
690 
691 static int valid_rr_name(const char *name, const char *location,
692 			         unsigned type, DNS_REPLY *reply)
693 {
694     char    temp[DNS_NAME_LEN];
695     char   *query_name;
696     int     len;
697     char   *gripe;
698     int     result;
699 
700     /*
701      * People aren't supposed to specify numeric names where domain names are
702      * required, but it "works" with some mailers anyway, so people complain
703      * when software doesn't bend over backwards.
704      */
705 #define PASS_NAME	1
706 #define REJECT_NAME	0
707 
708     if (valid_hostaddr(name, DONT_GRIPE)) {
709 	result = PASS_NAME;
710 	gripe = "numeric domain name";
711     } else if (!valid_hostname(name, DO_GRIPE)) {
712 	result = REJECT_NAME;
713 	gripe = "malformed domain name";
714     } else {
715 	result = PASS_NAME;
716 	gripe = 0;
717     }
718 
719     /*
720      * If we have a gripe, show some context, including the name used in the
721      * query and the type of reply that we're looking at.
722      */
723     if (gripe) {
724 	len = dn_expand(reply->buf, reply->end, reply->query_start,
725 			temp, DNS_NAME_LEN);
726 	query_name = (len < 0 ? "*unparsable*" : temp);
727 	msg_warn("%s in %s of %s record for %s: %.100s",
728 		 gripe, location, dns_strtype(type), query_name, name);
729     }
730     return (result);
731 }
732 
733 /* dns_get_rr - extract resource record from name server reply */
734 
735 static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply,
736 		              unsigned char *pos, char *rr_name,
737 		              DNS_FIXED *fixed)
738 {
739     char    temp[DNS_NAME_LEN];
740     char   *tempbuf = temp;
741     UINT32_TYPE soa_buf[5];
742     int     comp_len;
743     ssize_t data_len;
744     unsigned pref = 0;
745     unsigned char *src;
746     unsigned char *dst;
747     int     ch;
748 
749 #define MIN2(a, b)	((unsigned)(a) < (unsigned)(b) ? (a) : (b))
750 
751     *list = 0;
752 
753     switch (fixed->type) {
754     default:
755 	msg_panic("dns_get_rr: don't know how to extract resource type %s",
756 		  dns_strtype(fixed->type));
757     case T_CNAME:
758     case T_DNAME:
759     case T_MB:
760     case T_MG:
761     case T_MR:
762     case T_NS:
763     case T_PTR:
764 	if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
765 	    return (DNS_RETRY);
766 	if (!valid_rr_name(temp, "resource data", fixed->type, reply))
767 	    return (DNS_INVAL);
768 	data_len = strlen(temp) + 1;
769 	break;
770     case T_MX:
771 	GETSHORT(pref, pos);
772 	if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
773 	    return (DNS_RETRY);
774 	/* Don't even think of returning an invalid hostname to the caller. */
775 	if (*temp == 0)
776 	    return (DNS_NULLMX);		/* TODO: descriptive text */
777 	if (!valid_rr_name(temp, "resource data", fixed->type, reply))
778 	    return (DNS_INVAL);
779 	data_len = strlen(temp) + 1;
780 	break;
781     case T_A:
782 	if (fixed->length != INET_ADDR_LEN) {
783 	    msg_warn("extract_answer: bad address length: %d", fixed->length);
784 	    return (DNS_RETRY);
785 	}
786 	if (fixed->length > sizeof(temp))
787 	    msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
788 		      fixed->length);
789 	memcpy(temp, pos, fixed->length);
790 	data_len = fixed->length;
791 	break;
792 #ifdef T_AAAA
793     case T_AAAA:
794 	if (fixed->length != INET6_ADDR_LEN) {
795 	    msg_warn("extract_answer: bad address length: %d", fixed->length);
796 	    return (DNS_RETRY);
797 	}
798 	if (fixed->length > sizeof(temp))
799 	    msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
800 		      fixed->length);
801 	memcpy(temp, pos, fixed->length);
802 	data_len = fixed->length;
803 	break;
804 #endif
805 
806 	/*
807 	 * We impose the same length limit here as for DNS names. However,
808 	 * see T_TLSA discussion below.
809 	 */
810     case T_TXT:
811 	data_len = MIN2(pos[0] + 1, MIN2(fixed->length + 1, sizeof(temp)));
812 	for (src = pos + 1, dst = (unsigned char *) (temp);
813 	     dst < (unsigned char *) (temp) + data_len - 1; /* */ ) {
814 	    ch = *src++;
815 	    *dst++ = (ISPRINT(ch) ? ch : ' ');
816 	}
817 	*dst = 0;
818 	break;
819 
820 	/*
821 	 * For a full certificate, fixed->length may be longer than
822 	 * sizeof(tmpbuf) == DNS_NAME_LEN.  Since we don't need a decode
823 	 * buffer, just copy the raw data into the rr.
824 	 *
825 	 * XXX Reject replies with bogus length < 3.
826 	 *
827 	 * XXX What about enforcing a sane upper bound? The RFC 1035 hard
828 	 * protocol limit is the RRDATA length limit of 65535.
829 	 */
830     case T_TLSA:
831 	data_len = fixed->length;
832 	tempbuf = (char *) pos;
833 	break;
834 
835 	/*
836 	 * We use the SOA record TTL to determine the negative reply TTL. We
837 	 * save the time fields in the SOA record for debugging, but for now
838 	 * we don't bother saving the source host and mailbox information, as
839 	 * that would require changes to the DNS_RR structure and APIs. See
840 	 * also code in dns_strrecord().
841 	 */
842     case T_SOA:
843 	comp_len = dn_skipname(pos, reply->end);
844 	if (comp_len < 0)
845 	    return (DNS_RETRY);
846 	pos += comp_len;
847 	comp_len = dn_skipname(pos, reply->end);
848 	if (comp_len < 0)
849 	    return (DNS_RETRY);
850 	pos += comp_len;
851 	if (reply->end - pos < sizeof(soa_buf)) {
852 	    msg_warn("extract_answer: bad SOA length: %d", fixed->length);
853 	    return (DNS_RETRY);
854 	}
855 	GETLONG(soa_buf[0], pos);		/* Serial */
856 	GETLONG(soa_buf[1], pos);		/* Refresh */
857 	GETLONG(soa_buf[2], pos);		/* Retry */
858 	GETLONG(soa_buf[3], pos);		/* Expire */
859 	GETLONG(soa_buf[4], pos);		/* Ncache TTL */
860 	tempbuf = (char *) soa_buf;
861 	data_len = sizeof(soa_buf);
862 	break;
863     }
864     *list = dns_rr_create(orig_name, rr_name, fixed->type, fixed->class,
865 			  fixed->ttl, pref, tempbuf, data_len);
866     return (DNS_OK);
867 }
868 
869 /* dns_get_alias - extract CNAME from name server reply */
870 
871 static int dns_get_alias(DNS_REPLY *reply, unsigned char *pos,
872 			         DNS_FIXED *fixed, char *cname, int c_len)
873 {
874     if (fixed->type != T_CNAME)
875 	msg_panic("dns_get_alias: bad type %s", dns_strtype(fixed->type));
876     if (dn_expand(reply->buf, reply->end, pos, cname, c_len) < 0)
877 	return (DNS_RETRY);
878     if (!valid_rr_name(cname, "resource data", fixed->type, reply))
879 	return (DNS_INVAL);
880     return (DNS_OK);
881 }
882 
883 /* dns_get_answer - extract answers from name server reply */
884 
885 static int dns_get_answer(const char *orig_name, DNS_REPLY *reply, int type,
886 	             DNS_RR **rrlist, VSTRING *fqdn, char *cname, int c_len,
887 			          int *maybe_secure)
888 {
889     char    rr_name[DNS_NAME_LEN];
890     unsigned char *pos;
891     int     answer_count = reply->answer_count;
892     int     len;
893     DNS_FIXED fixed;
894     DNS_RR *rr;
895     int     resource_found = 0;
896     int     cname_found = 0;
897     int     not_found_status = DNS_NOTFOUND;	/* can't happen */
898     int     status;
899 
900     /*
901      * Initialize. Skip over the name server query if we haven't yet.
902      */
903     if (reply->answer_start == 0)
904 	if ((status = dns_skip_query(reply)) < 0)
905 	    return (status);
906     pos = reply->answer_start;
907 
908     /*
909      * Either this, or use a GOTO for emergency exits. The purpose is to
910      * prevent incomplete answers from being passed back to the caller.
911      */
912 #define CORRUPT(status) { \
913 	if (rrlist && *rrlist) { \
914 	    dns_rr_free(*rrlist); \
915 	    *rrlist = 0; \
916 	} \
917 	return (status); \
918     }
919 
920     /*
921      * Iterate over all answers.
922      */
923     while (answer_count-- > 0) {
924 
925 	/*
926 	 * Optionally extract the fully-qualified domain name.
927 	 */
928 	if (pos >= reply->end)
929 	    CORRUPT(DNS_RETRY);
930 	len = dn_expand(reply->buf, reply->end, pos, rr_name, DNS_NAME_LEN);
931 	if (len < 0)
932 	    CORRUPT(DNS_RETRY);
933 	pos += len;
934 
935 	/*
936 	 * Extract the fixed reply data: type, class, ttl, length.
937 	 */
938 	if (pos + RRFIXEDSZ > reply->end)
939 	    CORRUPT(DNS_RETRY);
940 	if ((status = dns_get_fixed(pos, &fixed)) != DNS_OK)
941 	    CORRUPT(status);
942 	if (strcmp(orig_name, ".") == 0 && *rr_name == 0)
943 	     /* Allow empty response name for root queries. */ ;
944 	else if (!valid_rr_name(rr_name, "resource name", fixed.type, reply))
945 	    CORRUPT(DNS_INVAL);
946 	if (fqdn)
947 	    vstring_strcpy(fqdn, rr_name);
948 	if (msg_verbose)
949 	    msg_info("dns_get_answer: type %s for %s",
950 		     dns_strtype(fixed.type), rr_name);
951 	pos += RRFIXEDSZ;
952 
953 	/*
954 	 * Optionally extract the requested resource or CNAME data.
955 	 */
956 	if (pos + fixed.length > reply->end)
957 	    CORRUPT(DNS_RETRY);
958 	if (type == fixed.type || type == T_ANY) {	/* requested type */
959 	    if (rrlist) {
960 		if ((status = dns_get_rr(&rr, orig_name, reply, pos, rr_name,
961 					 &fixed)) == DNS_OK) {
962 		    resource_found++;
963 		    rr->dnssec_valid = *maybe_secure ? reply->dnssec_ad : 0;
964 		    *rrlist = dns_rr_append(*rrlist, rr);
965 		} else if (status == DNS_NULLMX) {
966 		    CORRUPT(status);		/* TODO: use better name */
967 		} else if (not_found_status != DNS_RETRY)
968 		    not_found_status = status;
969 	    } else
970 		resource_found++;
971 	} else if (fixed.type == T_CNAME) {	/* cname resource */
972 	    cname_found++;
973 	    if (cname && c_len > 0)
974 		if ((status = dns_get_alias(reply, pos, &fixed, cname, c_len)) != DNS_OK)
975 		    CORRUPT(status);
976 	    if (!reply->dnssec_ad)
977 		*maybe_secure = 0;
978 	}
979 	pos += fixed.length;
980     }
981 
982     /*
983      * See what answer we came up with. Report success when the requested
984      * information was found. Otherwise, when a CNAME was found, report that
985      * more recursion is needed. Otherwise report failure.
986      */
987     if (resource_found)
988 	return (DNS_OK);
989     if (cname_found)
990 	return (DNS_RECURSE);
991     return (not_found_status);
992 }
993 
994 /* dns_lookup_x - DNS lookup user interface */
995 
996 int     dns_lookup_x(const char *name, unsigned type, unsigned flags,
997 		             DNS_RR **rrlist, VSTRING *fqdn, VSTRING *why,
998 		             int *rcode, unsigned lflags)
999 {
1000     char    cname[DNS_NAME_LEN];
1001     int     c_len = sizeof(cname);
1002     static DNS_REPLY reply;
1003     int     count;
1004     int     status;
1005     int     maybe_secure = 1;		/* Query name presumed secure */
1006     const char *orig_name = name;
1007 
1008     /*
1009      * Reset results early. DNS_OK is not the only status that returns
1010      * resource records; DNS_NOTFOUND will do that too, if requested.
1011      */
1012     if (rrlist)
1013 	*rrlist = 0;
1014 
1015     /*
1016      * DJBDNS produces a bogus A record when given a numerical hostname.
1017      */
1018     if (valid_hostaddr(name, DONT_GRIPE)) {
1019 	if (why)
1020 	    vstring_sprintf(why,
1021 		   "Name service error for %s: invalid host or domain name",
1022 			    name);
1023 	if (rcode)
1024 	    *rcode = NXDOMAIN;
1025 	DNS_SET_H_ERRNO(&dns_res_state, HOST_NOT_FOUND);
1026 	return (DNS_NOTFOUND);
1027     }
1028 
1029     /*
1030      * The Linux resolver misbehaves when given an invalid domain name.
1031      */
1032     if (strcmp(name, ".") && !valid_hostname(name, DONT_GRIPE)) {
1033 	if (why)
1034 	    vstring_sprintf(why,
1035 		   "Name service error for %s: invalid host or domain name",
1036 			    name);
1037 	if (rcode)
1038 	    *rcode = NXDOMAIN;
1039 	DNS_SET_H_ERRNO(&dns_res_state, HOST_NOT_FOUND);
1040 	return (DNS_NOTFOUND);
1041     }
1042 
1043     /*
1044      * Perform the lookup. Follow CNAME chains, but only up to a
1045      * pre-determined maximum.
1046      */
1047     for (count = 0; count < 10; count++) {
1048 
1049 	/*
1050 	 * Perform the DNS lookup, and pre-parse the name server reply.
1051 	 */
1052 	status = dns_query(name, type, flags, &reply, why, lflags);
1053 	if (rcode)
1054 	    *rcode = reply.rcode;
1055 	if (status != DNS_OK) {
1056 
1057 	    /*
1058 	     * If the record does not exist, and we have a copy of the server
1059 	     * response, try to extract the negative caching TTL for the SOA
1060 	     * record in the authority section. DO NOT return an error if an
1061 	     * SOA record is malformed.
1062 	     */
1063 	    if (status == DNS_NOTFOUND && TEST_HAVE_DNS_REPLY_PACKET(&reply)
1064 		&& reply.auth_count > 0) {
1065 		reply.answer_count = reply.auth_count;	/* XXX TODO: Fix API */
1066 		(void) dns_get_answer(orig_name, &reply, T_SOA, rrlist, fqdn,
1067 				      cname, c_len, &maybe_secure);
1068 	    }
1069 	    if (DNS_WANT_DNSSEC_VALIDATION(flags)
1070 		&& !DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE | \
1071 				       DNS_SEC_FLAG_DONT_PROBE))
1072 		dns_sec_probe(flags);		/* XXX Clobbers 'reply' */
1073 	    return (status);
1074 	}
1075 
1076 	/*
1077 	 * Extract resource records of the requested type. Pick up CNAME
1078 	 * information just in case the requested data is not found.
1079 	 */
1080 	status = dns_get_answer(orig_name, &reply, type, rrlist, fqdn,
1081 				cname, c_len, &maybe_secure);
1082 	if (DNS_WANT_DNSSEC_VALIDATION(flags)
1083 	    && !DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE | \
1084 				   DNS_SEC_FLAG_DONT_PROBE))
1085 	    dns_sec_probe(flags);		/* XXX Clobbers 'reply' */
1086 	switch (status) {
1087 	default:
1088 	    if (why)
1089 		vstring_sprintf(why, "Name service error for name=%s type=%s: "
1090 				"Malformed or unexpected name server reply",
1091 				name, dns_strtype(type));
1092 	    return (status);
1093 	case DNS_NULLMX:
1094 	    if (why)
1095 		vstring_sprintf(why, "Domain %s does not accept mail (nullMX)",
1096 				name);
1097 	    DNS_SET_H_ERRNO(&dns_res_state, NO_DATA);
1098 	    return (status);
1099 	case DNS_OK:
1100 	    if (rrlist && dns_rr_filter_maps) {
1101 		if (dns_rr_filter_execute(rrlist) < 0) {
1102 		    if (why)
1103 			vstring_sprintf(why,
1104 					"Error looking up name=%s type=%s: "
1105 					"Invalid DNS reply filter syntax",
1106 					name, dns_strtype(type));
1107 		    dns_rr_free(*rrlist);
1108 		    *rrlist = 0;
1109 		    status = DNS_RETRY;
1110 		} else if (*rrlist == 0) {
1111 		    if (why)
1112 			vstring_sprintf(why,
1113 					"Error looking up name=%s type=%s: "
1114 					"DNS reply filter drops all results",
1115 					name, dns_strtype(type));
1116 		    status = DNS_POLICY;
1117 		}
1118 	    }
1119 	    return (status);
1120 	case DNS_RECURSE:
1121 	    if (msg_verbose)
1122 		msg_info("dns_lookup: %s aliased to %s", name, cname);
1123 #if RES_USE_DNSSEC
1124 
1125 	    /*
1126 	     * Once an intermediate CNAME reply is not validated, all
1127 	     * consequent RRs are deemed not validated, so we don't ask for
1128 	     * further DNSSEC replies.
1129 	     */
1130 	    if (maybe_secure == 0)
1131 		flags &= ~RES_USE_DNSSEC;
1132 #endif
1133 	    name = cname;
1134 	}
1135     }
1136     if (why)
1137 	vstring_sprintf(why, "Name server loop for %s", name);
1138     msg_warn("dns_lookup: Name server loop for %s", name);
1139     return (DNS_NOTFOUND);
1140 }
1141 
1142 /* dns_lookup_rl - DNS lookup interface with types list */
1143 
1144 int     dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist,
1145 		              VSTRING *fqdn, VSTRING *why, int *rcode,
1146 		              int lflags,...)
1147 {
1148     va_list ap;
1149     unsigned type, next;
1150     int     status = DNS_NOTFOUND;
1151     int     hpref_status = INT_MIN;
1152     VSTRING *hpref_rtext = 0;
1153     int     hpref_rcode;
1154     int     hpref_h_errno;
1155     DNS_RR *rr;
1156 
1157     /* Save intermediate highest-priority result. */
1158 #define SAVE_HPREF_STATUS() do { \
1159 	hpref_status = status; \
1160 	if (rcode) \
1161 	    hpref_rcode = *rcode; \
1162 	if (why && status != DNS_OK) \
1163 	    vstring_strcpy(hpref_rtext ? hpref_rtext : \
1164 			   (hpref_rtext = vstring_alloc(VSTRING_LEN(why))), \
1165 			   vstring_str(why)); \
1166 	hpref_h_errno = DNS_GET_H_ERRNO(&dns_res_state); \
1167     } while (0)
1168 
1169     /* Restore intermediate highest-priority result. */
1170 #define RESTORE_HPREF_STATUS() do { \
1171 	status = hpref_status; \
1172 	if (rcode) \
1173 	    *rcode = hpref_rcode; \
1174 	if (why && status != DNS_OK) \
1175 	    vstring_strcpy(why, vstring_str(hpref_rtext)); \
1176 	DNS_SET_H_ERRNO(&dns_res_state, hpref_h_errno); \
1177     } while (0)
1178 
1179     if (rrlist)
1180 	*rrlist = 0;
1181     va_start(ap, lflags);
1182     for (type = va_arg(ap, unsigned); type != 0; type = next) {
1183 	next = va_arg(ap, unsigned);
1184 	if (msg_verbose)
1185 	    msg_info("lookup %s type %s flags %s",
1186 		     name, dns_strtype(type), dns_str_resflags(flags));
1187 	status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
1188 			      fqdn, why, rcode, lflags);
1189 	if (rrlist && rr)
1190 	    *rrlist = dns_rr_append(*rrlist, rr);
1191 	if (status == DNS_OK) {
1192 	    if (lflags & DNS_REQ_FLAG_STOP_OK)
1193 		break;
1194 	} else if (status == DNS_INVAL) {
1195 	    if (lflags & DNS_REQ_FLAG_STOP_INVAL)
1196 		break;
1197 	} else if (status == DNS_POLICY) {
1198 	    if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY))
1199 		break;
1200 	} else if (status == DNS_NULLMX) {
1201 	    if (lflags & DNS_REQ_FLAG_STOP_NULLMX)
1202 		break;
1203 	}
1204 	/* XXX Stop after NXDOMAIN error. */
1205 	if (next == 0)
1206 	    break;
1207 	if (status >= hpref_status)
1208 	    SAVE_HPREF_STATUS();		/* save last info */
1209     }
1210     va_end(ap);
1211     if (status < hpref_status)
1212 	RESTORE_HPREF_STATUS();			/* else report last info */
1213     if (hpref_rtext)
1214 	vstring_free(hpref_rtext);
1215     return (status);
1216 }
1217 
1218 /* dns_lookup_rv - DNS lookup interface with types vector */
1219 
1220 int     dns_lookup_rv(const char *name, unsigned flags, DNS_RR **rrlist,
1221 		              VSTRING *fqdn, VSTRING *why, int *rcode,
1222 		              int lflags, unsigned *types)
1223 {
1224     unsigned type, next;
1225     int     status = DNS_NOTFOUND;
1226     int     hpref_status = INT_MIN;
1227     VSTRING *hpref_rtext = 0;
1228     int     hpref_rcode;
1229     int     hpref_h_errno;
1230     DNS_RR *rr;
1231 
1232     if (rrlist)
1233 	*rrlist = 0;
1234     for (type = *types++; type != 0; type = next) {
1235 	next = *types++;
1236 	if (msg_verbose)
1237 	    msg_info("lookup %s type %s flags %s",
1238 		     name, dns_strtype(type), dns_str_resflags(flags));
1239 	status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
1240 			      fqdn, why, rcode, lflags);
1241 	if (rrlist && rr)
1242 	    *rrlist = dns_rr_append(*rrlist, rr);
1243 	if (status == DNS_OK) {
1244 	    if (lflags & DNS_REQ_FLAG_STOP_OK)
1245 		break;
1246 	} else if (status == DNS_INVAL) {
1247 	    if (lflags & DNS_REQ_FLAG_STOP_INVAL)
1248 		break;
1249 	} else if (status == DNS_POLICY) {
1250 	    if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY))
1251 		break;
1252 	} else if (status == DNS_NULLMX) {
1253 	    if (lflags & DNS_REQ_FLAG_STOP_NULLMX)
1254 		break;
1255 	}
1256 	/* XXX Stop after NXDOMAIN error. */
1257 	if (next == 0)
1258 	    break;
1259 	if (status >= hpref_status)
1260 	    SAVE_HPREF_STATUS();		/* save last info */
1261     }
1262     if (status < hpref_status)
1263 	RESTORE_HPREF_STATUS();			/* else report last info */
1264     if (hpref_rtext)
1265 	vstring_free(hpref_rtext);
1266     return (status);
1267 }
1268 
1269 /* dns_get_h_errno - get the last lookup status */
1270 
1271 int     dns_get_h_errno(void)
1272 {
1273     return (DNS_GET_H_ERRNO(&dns_res_state));
1274 }
1275