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