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