1/*++
2/* NAME
3/*	dns_lookup 3
4/* SUMMARY
5/*	domain name service lookup
6/* SYNOPSIS
7/*	#include <dns.h>
8/*
9/*	int	dns_lookup(name, type, rflags, list, fqdn, why)
10/*	const char *name;
11/*	unsigned type;
12/*	unsigned rflags;
13/*	DNS_RR	**list;
14/*	VSTRING *fqdn;
15/*	VSTRING *why;
16/*
17/*	int	dns_lookup_l(name, rflags, list, fqdn, why, lflags, ltype, ...)
18/*	const char *name;
19/*	unsigned rflags;
20/*	DNS_RR	**list;
21/*	VSTRING *fqdn;
22/*	VSTRING *why;
23/*	int	lflags;
24/*	unsigned ltype;
25/*
26/*	int	dns_lookup_v(name, rflags, list, fqdn, why, lflags, ltype)
27/*	const char *name;
28/*	unsigned rflags;
29/*	DNS_RR	**list;
30/*	VSTRING *fqdn;
31/*	VSTRING *why;
32/*	int	lflags;
33/*	unsigned *ltype;
34/* AUXILIARY FUNCTIONS
35/*	int	dns_lookup_r(name, type, rflags, list, fqdn, why, rcode)
36/*	const char *name;
37/*	unsigned type;
38/*	unsigned rflags;
39/*	DNS_RR	**list;
40/*	VSTRING *fqdn;
41/*	VSTRING *why;
42/*	int	*rcode;
43/*
44/*	int	dns_lookup_rl(name, rflags, list, fqdn, why, rcode, lflags,
45/*				ltype, ...)
46/*	const char *name;
47/*	unsigned rflags;
48/*	DNS_RR	**list;
49/*	VSTRING *fqdn;
50/*	VSTRING *why;
51/*	int	*rcode;
52/*	int	lflags;
53/*	unsigned ltype;
54/*
55/*	int	dns_lookup_rv(name, rflags, list, fqdn, why, rcode, lflags,
56/*				ltype)
57/*	const char *name;
58/*	unsigned rflags;
59/*	DNS_RR	**list;
60/*	VSTRING *fqdn;
61/*	VSTRING *why;
62/*	int	*rcode;
63/*	int	lflags;
64/*	unsigned *ltype;
65/* DESCRIPTION
66/*	dns_lookup() looks up DNS resource records. When requested to
67/*	look up data other than type CNAME, it will follow a limited
68/*	number of CNAME indirections. All result names (including
69/*	null terminator) will fit a buffer of size DNS_NAME_LEN.
70/*	All name results are validated by \fIvalid_hostname\fR();
71/*	an invalid name is reported as a DNS_INVAL result, while
72/*	malformed replies are reported as transient errors.
73/*
74/*	dns_lookup_l() and dns_lookup_v() allow the user to specify
75/*	a list of resource types.
76/*
77/*	dns_lookup_r(), dns_lookup_rl() and dns_lookup_rv() provide
78/*	additional information.
79/* INPUTS
80/* .ad
81/* .fi
82/* .IP name
83/*	The name to be looked up in the domain name system.
84/*	This name must pass the valid_hostname() test; it
85/*	must not be an IP address.
86/* .IP type
87/*	The resource record type to be looked up (T_A, T_MX etc.).
88/* .IP rflags
89/*	Resolver flags. These are a bitwise OR of:
90/* .RS
91/* .IP RES_DEBUG
92/*	Print debugging information.
93/* .IP RES_DNSRCH
94/*	Search local domain and parent domains.
95/* .IP RES_DEFNAMES
96/*	Append local domain to unqualified names.
97/* .IP RES_USE_DNSSEC
98/*	Request DNSSEC validation. This flag is silently ignored
99/*	when the system stub resolver API, resolver(3), does not
100/*	implement DNSSEC.
101/* .RE
102/* .IP lflags
103/*	Multi-type request control for dns_lookup_l() and dns_lookup_v().
104/*	For convenience, DNS_REQ_FLAG_NONE requests no special
105/*	processing. Invoke dns_lookup() for all specified resource
106/*	record types in the specified order, and merge their results.
107/*	Otherwise, specify one or more of the following:
108/* .RS
109/* .IP DNS_REQ_FLAG_STOP_INVAL
110/*	Invoke dns_lookup() for the resource types in the order as
111/*	specified, and return when dns_lookup() returns DNS_INVAL.
112/* .IP DNS_REQ_FLAG_STOP_OK
113/*	Invoke dns_lookup() for the resource types in the order as
114/*	specified, and return when dns_lookup() returns DNS_OK.
115/* .RE
116/* .IP ltype
117/*	The resource record types to be looked up. In the case of
118/*	dns_lookup_l(), this is a null-terminated argument list.
119/*	In the case of dns_lookup_v(), this is a null-terminated
120/*	integer array.
121/* OUTPUTS
122/* .ad
123/* .fi
124/* .IP list
125/*	A null pointer, or a pointer to a variable that receives a
126/*	list of requested resource records.
127/* .IP fqdn
128/*	A null pointer, or storage for the fully-qualified domain
129/*	name found for \fIname\fR.
130/* .IP why
131/*	A null pointer, or storage for the reason for failure.
132/* .IP rcode
133/*	Pointer to storage for the reply RCODE value. This gives
134/*	more detailed information than DNS_FAIL, DNS_RETRY, etc.
135/* DIAGNOSTICS
136/*	dns_lookup() returns one of the following codes and sets the
137/*	\fIwhy\fR argument accordingly:
138/* .IP DNS_OK
139/*	The DNS query succeeded.
140/* .IP DNS_NOTFOUND
141/*	The DNS query succeeded; the requested information was not found.
142/* .IP DNS_INVAL
143/*	The DNS query succeeded; the result failed the valid_hostname() test.
144/*
145/*	NOTE: the valid_hostname() test is skipped for results that
146/*	the caller suppresses explicitly.  For example, when the
147/*	caller requests MX record lookup but specifies a null
148/*	resource record list argument, no syntax check will be done
149/*	for MX server names.
150/* .IP DNS_RETRY
151/*	The query failed, or the reply was malformed.
152/*	The problem is considered transient.
153/* .IP DNS_FAIL
154/*	The query failed.
155/* BUGS
156/*	dns_lookup() implements a subset of all possible resource types:
157/*	CNAME, MX, A, and some records with similar formatting requirements.
158/*	It is unwise to specify the T_ANY wildcard resource type.
159/*
160/*	It takes a surprising amount of code to accomplish what appears
161/*	to be a simple task. Later versions of the mail system may implement
162/*	their own DNS client software.
163/* SEE ALSO
164/*	dns_rr(3) resource record memory and list management
165/* LICENSE
166/* .ad
167/* .fi
168/*	The Secure Mailer license must be distributed with this software.
169/* AUTHOR(S)
170/*	Wietse Venema
171/*	IBM T.J. Watson Research
172/*	P.O. Box 704
173/*	Yorktown Heights, NY 10598, USA
174/*--*/
175
176/* System library. */
177
178#include <sys_defs.h>
179#include <netdb.h>
180#include <string.h>
181#include <ctype.h>
182
183/* Utility library. */
184
185#include <mymalloc.h>
186#include <vstring.h>
187#include <msg.h>
188#include <valid_hostname.h>
189#include <stringops.h>
190
191/* DNS library. */
192
193#include "dns.h"
194
195/* Local stuff. */
196
197 /*
198  * Structure to keep track of things while decoding a name server reply.
199  */
200#define DEF_DNS_REPLY_SIZE	4096	/* in case we're using TCP */
201#define MAX_DNS_REPLY_SIZE	65536	/* in case we're using TCP */
202
203typedef struct DNS_REPLY {
204    unsigned char *buf;			/* raw reply data */
205    size_t  buf_len;			/* reply buffer length */
206    int     rcode;			/* unfiltered reply code */
207    int     dnssec_ad;			/* DNSSEC AD bit */
208    int     query_count;		/* number of queries */
209    int     answer_count;		/* number of answers */
210    unsigned char *query_start;		/* start of query data */
211    unsigned char *answer_start;	/* start of answer data */
212    unsigned char *end;			/* first byte past reply */
213} DNS_REPLY;
214
215#define INET_ADDR_LEN	4		/* XXX */
216#define INET6_ADDR_LEN	16		/* XXX */
217
218/* dns_query - query name server and pre-parse the reply */
219
220static int dns_query(const char *name, int type, int flags,
221		             DNS_REPLY *reply, VSTRING *why)
222{
223    HEADER *reply_header;
224    int     len;
225    unsigned long saved_options;
226
227    /*
228     * Initialize the reply buffer.
229     */
230    if (reply->buf == 0) {
231	reply->buf = (unsigned char *) mymalloc(DEF_DNS_REPLY_SIZE);
232	reply->buf_len = DEF_DNS_REPLY_SIZE;
233    }
234
235    /*
236     * Initialize the name service.
237     */
238    if ((_res.options & RES_INIT) == 0 && res_init() < 0) {
239	if (why)
240	    vstring_strcpy(why, "Name service initialization failure");
241	return (DNS_FAIL);
242    }
243
244    /*
245     * Set search options: debugging, parent domain search, append local
246     * domain. Do not allow the user to control other features.
247     */
248#define USER_FLAGS (RES_DEBUG | RES_DNSRCH | RES_DEFNAMES | RES_USE_DNSSEC)
249
250    if ((flags & USER_FLAGS) != flags)
251	msg_panic("dns_query: bad flags: %d", flags);
252
253    /*
254     * Set extra options that aren't exposed to the application.
255     */
256#define XTRA_FLAGS (RES_USE_EDNS0)
257
258    if (flags & RES_USE_DNSSEC)
259	flags |= RES_USE_EDNS0;
260
261    /*
262     * Save and restore resolver options that we overwrite, to avoid
263     * surprising behavior in other code that also invokes the resolver.
264     */
265#define SAVE_FLAGS (USER_FLAGS | XTRA_FLAGS)
266
267    saved_options = (_res.options & SAVE_FLAGS);
268
269    /*
270     * Perform the lookup. Claim that the information cannot be found if and
271     * only if the name server told us so.
272     */
273    for (;;) {
274	_res.options &= ~saved_options;
275	_res.options |= flags;
276	len = res_search((char *) name, C_IN, type, reply->buf, reply->buf_len);
277	_res.options &= ~flags;
278	_res.options |= saved_options;
279	reply_header = (HEADER *) reply->buf;
280	reply->rcode = reply_header->rcode;
281	if (len < 0) {
282	    if (why)
283		vstring_sprintf(why, "Host or domain name not found. "
284				"Name service error for name=%s type=%s: %s",
285			    name, dns_strtype(type), dns_strerror(h_errno));
286	    if (msg_verbose)
287		msg_info("dns_query: %s (%s): %s",
288			 name, dns_strtype(type), dns_strerror(h_errno));
289	    switch (h_errno) {
290	    case NO_RECOVERY:
291		return (DNS_FAIL);
292	    case HOST_NOT_FOUND:
293	    case NO_DATA:
294		return (DNS_NOTFOUND);
295	    default:
296		return (DNS_RETRY);
297	    }
298	}
299	if (msg_verbose)
300	    msg_info("dns_query: %s (%s): OK", name, dns_strtype(type));
301
302	if (reply_header->tc == 0 || reply->buf_len >= MAX_DNS_REPLY_SIZE)
303	    break;
304	reply->buf = (unsigned char *)
305	    myrealloc((char *) reply->buf, 2 * reply->buf_len);
306	reply->buf_len *= 2;
307    }
308
309    /*
310     * Paranoia.
311     */
312    if (len > reply->buf_len) {
313	msg_warn("reply length %d > buffer length %d for name=%s type=%s",
314		 len, (int) reply->buf_len, name, dns_strtype(type));
315	len = reply->buf_len;
316    }
317
318    /*
319     * Initialize the reply structure. Some structure members are filled on
320     * the fly while the reply is being parsed.  Coerce AD bit to boolean.
321     */
322#if RES_USE_DNSSEC != 0
323    reply->dnssec_ad = (flags & RES_USE_DNSSEC) ? !!reply_header->ad : 0;
324#else
325    reply->dnssec_ad = 0;
326#endif
327    reply->end = reply->buf + len;
328    reply->query_start = reply->buf + sizeof(HEADER);
329    reply->answer_start = 0;
330    reply->query_count = ntohs(reply_header->qdcount);
331    reply->answer_count = ntohs(reply_header->ancount);
332    return (DNS_OK);
333}
334
335/* dns_skip_query - skip query data in name server reply */
336
337static int dns_skip_query(DNS_REPLY *reply)
338{
339    int     query_count = reply->query_count;
340    unsigned char *pos = reply->query_start;
341    char    temp[DNS_NAME_LEN];
342    int     len;
343
344    /*
345     * For each query, skip over the domain name and over the fixed query
346     * data.
347     */
348    while (query_count-- > 0) {
349	if (pos >= reply->end)
350	    return DNS_RETRY;
351	len = dn_expand(reply->buf, reply->end, pos, temp, DNS_NAME_LEN);
352	if (len < 0)
353	    return (DNS_RETRY);
354	pos += len + QFIXEDSZ;
355    }
356    reply->answer_start = pos;
357    return (DNS_OK);
358}
359
360/* dns_get_fixed - extract fixed data from resource record */
361
362static int dns_get_fixed(unsigned char *pos, DNS_FIXED *fixed)
363{
364    GETSHORT(fixed->type, pos);
365    GETSHORT(fixed->class, pos);
366    GETLONG(fixed->ttl, pos);
367    GETSHORT(fixed->length, pos);
368
369    if (fixed->class != C_IN) {
370	msg_warn("dns_get_fixed: bad class: %u", fixed->class);
371	return (DNS_RETRY);
372    }
373    return (DNS_OK);
374}
375
376/* valid_rr_name - validate hostname in resource record */
377
378static int valid_rr_name(const char *name, const char *location,
379			         unsigned type, DNS_REPLY *reply)
380{
381    char    temp[DNS_NAME_LEN];
382    char   *query_name;
383    int     len;
384    char   *gripe;
385    int     result;
386
387    /*
388     * People aren't supposed to specify numeric names where domain names are
389     * required, but it "works" with some mailers anyway, so people complain
390     * when software doesn't bend over backwards.
391     */
392#define PASS_NAME	1
393#define REJECT_NAME	0
394
395    if (valid_hostaddr(name, DONT_GRIPE)) {
396	result = PASS_NAME;
397	gripe = "numeric domain name";
398    } else if (!valid_hostname(name, DO_GRIPE)) {
399	result = REJECT_NAME;
400	gripe = "malformed domain name";
401    } else {
402	result = PASS_NAME;
403	gripe = 0;
404    }
405
406    /*
407     * If we have a gripe, show some context, including the name used in the
408     * query and the type of reply that we're looking at.
409     */
410    if (gripe) {
411	len = dn_expand(reply->buf, reply->end, reply->query_start,
412			temp, DNS_NAME_LEN);
413	query_name = (len < 0 ? "*unparsable*" : temp);
414	msg_warn("%s in %s of %s record for %s: %.100s",
415		 gripe, location, dns_strtype(type), query_name, name);
416    }
417    return (result);
418}
419
420/* dns_get_rr - extract resource record from name server reply */
421
422static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply,
423		              unsigned char *pos, char *rr_name,
424		              DNS_FIXED *fixed)
425{
426    char    temp[DNS_NAME_LEN];
427    char   *tempbuf = temp;
428    ssize_t data_len;
429    unsigned pref = 0;
430    unsigned char *src;
431    unsigned char *dst;
432    int     ch;
433
434#define MIN2(a, b)	((unsigned)(a) < (unsigned)(b) ? (a) : (b))
435
436    *list = 0;
437
438    switch (fixed->type) {
439    default:
440	msg_panic("dns_get_rr: don't know how to extract resource type %s",
441		  dns_strtype(fixed->type));
442    case T_CNAME:
443    case T_DNAME:
444    case T_MB:
445    case T_MG:
446    case T_MR:
447    case T_NS:
448    case T_PTR:
449	if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
450	    return (DNS_RETRY);
451	if (!valid_rr_name(temp, "resource data", fixed->type, reply))
452	    return (DNS_INVAL);
453	data_len = strlen(temp) + 1;
454	break;
455    case T_MX:
456	GETSHORT(pref, pos);
457	if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
458	    return (DNS_RETRY);
459	if (!valid_rr_name(temp, "resource data", fixed->type, reply))
460	    return (DNS_INVAL);
461	data_len = strlen(temp) + 1;
462	break;
463    case T_A:
464	if (fixed->length != INET_ADDR_LEN) {
465	    msg_warn("extract_answer: bad address length: %d", fixed->length);
466	    return (DNS_RETRY);
467	}
468	if (fixed->length > sizeof(temp))
469	    msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
470		      fixed->length);
471	memcpy(temp, pos, fixed->length);
472	data_len = fixed->length;
473	break;
474#ifdef T_AAAA
475    case T_AAAA:
476	if (fixed->length != INET6_ADDR_LEN) {
477	    msg_warn("extract_answer: bad address length: %d", fixed->length);
478	    return (DNS_RETRY);
479	}
480	if (fixed->length > sizeof(temp))
481	    msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
482		      fixed->length);
483	memcpy(temp, pos, fixed->length);
484	data_len = fixed->length;
485	break;
486#endif
487
488	/*
489	 * We impose the same length limit here as for DNS names. However,
490	 * see T_TLSA discussion below.
491	 */
492    case T_TXT:
493	data_len = MIN2(pos[0] + 1, MIN2(fixed->length + 1, sizeof(temp)));
494	for (src = pos + 1, dst = (unsigned char *) (temp);
495	     dst < (unsigned char *) (temp) + data_len - 1; /* */ ) {
496	    ch = *src++;
497	    *dst++ = (ISPRINT(ch) ? ch : ' ');
498	}
499	*dst = 0;
500	break;
501
502	/*
503	 * For a full certificate, fixed->length may be longer than
504	 * sizeof(tmpbuf) == DNS_NAME_LEN.  Since we don't need a decode
505	 * buffer, just copy the raw data into the rr.
506	 *
507	 * XXX Reject replies with bogus length < 3.
508	 *
509	 * XXX What about enforcing a sane upper bound? The RFC 1035 hard
510	 * protocol limit is the RRDATA length limit of 65535.
511	 */
512    case T_TLSA:
513	data_len = fixed->length;
514	tempbuf = (char *) pos;
515	break;
516    }
517    *list = dns_rr_create(orig_name, rr_name, fixed->type, fixed->class,
518			  fixed->ttl, pref, tempbuf, data_len);
519    return (DNS_OK);
520}
521
522/* dns_get_alias - extract CNAME from name server reply */
523
524static int dns_get_alias(DNS_REPLY *reply, unsigned char *pos,
525			         DNS_FIXED *fixed, char *cname, int c_len)
526{
527    if (fixed->type != T_CNAME)
528	msg_panic("dns_get_alias: bad type %s", dns_strtype(fixed->type));
529    if (dn_expand(reply->buf, reply->end, pos, cname, c_len) < 0)
530	return (DNS_RETRY);
531    if (!valid_rr_name(cname, "resource data", fixed->type, reply))
532	return (DNS_INVAL);
533    return (DNS_OK);
534}
535
536/* dns_get_answer - extract answers from name server reply */
537
538static int dns_get_answer(const char *orig_name, DNS_REPLY *reply, int type,
539	             DNS_RR **rrlist, VSTRING *fqdn, char *cname, int c_len,
540			          int *maybe_secure)
541{
542    char    rr_name[DNS_NAME_LEN];
543    unsigned char *pos;
544    int     answer_count = reply->answer_count;
545    int     len;
546    DNS_FIXED fixed;
547    DNS_RR *rr;
548    int     resource_found = 0;
549    int     cname_found = 0;
550    int     not_found_status = DNS_NOTFOUND;	/* can't happen */
551    int     status;
552
553    /*
554     * Initialize. Skip over the name server query if we haven't yet.
555     */
556    if (reply->answer_start == 0)
557	if ((status = dns_skip_query(reply)) < 0)
558	    return (status);
559    pos = reply->answer_start;
560    if (rrlist)
561	*rrlist = 0;
562
563    /*
564     * Either this, or use a GOTO for emergency exits. The purpose is to
565     * prevent incomplete answers from being passed back to the caller.
566     */
567#define CORRUPT(status) { \
568	if (rrlist && *rrlist) { \
569	    dns_rr_free(*rrlist); \
570	    *rrlist = 0; \
571	} \
572	return (status); \
573    }
574
575    /*
576     * Iterate over all answers.
577     */
578    while (answer_count-- > 0) {
579
580	/*
581	 * Optionally extract the fully-qualified domain name.
582	 */
583	if (pos >= reply->end)
584	    CORRUPT(DNS_RETRY);
585	len = dn_expand(reply->buf, reply->end, pos, rr_name, DNS_NAME_LEN);
586	if (len < 0)
587	    CORRUPT(DNS_RETRY);
588	pos += len;
589
590	/*
591	 * Extract the fixed reply data: type, class, ttl, length.
592	 */
593	if (pos + RRFIXEDSZ > reply->end)
594	    CORRUPT(DNS_RETRY);
595	if ((status = dns_get_fixed(pos, &fixed)) != DNS_OK)
596	    CORRUPT(status);
597	if (!valid_rr_name(rr_name, "resource name", fixed.type, reply))
598	    CORRUPT(DNS_INVAL);
599	if (fqdn)
600	    vstring_strcpy(fqdn, rr_name);
601	if (msg_verbose)
602	    msg_info("dns_get_answer: type %s for %s",
603		     dns_strtype(fixed.type), rr_name);
604	pos += RRFIXEDSZ;
605
606	/*
607	 * Optionally extract the requested resource or CNAME data.
608	 */
609	if (pos + fixed.length > reply->end)
610	    CORRUPT(DNS_RETRY);
611	if (type == fixed.type || type == T_ANY) {	/* requested type */
612	    if (rrlist) {
613		if ((status = dns_get_rr(&rr, orig_name, reply, pos, rr_name,
614					 &fixed)) == DNS_OK) {
615		    resource_found++;
616		    rr->dnssec_valid = *maybe_secure ? reply->dnssec_ad : 0;
617		    *rrlist = dns_rr_append(*rrlist, rr);
618		} else if (not_found_status != DNS_RETRY)
619		    not_found_status = status;
620	    } else
621		resource_found++;
622	} else if (fixed.type == T_CNAME) {	/* cname resource */
623	    cname_found++;
624	    if (cname && c_len > 0)
625		if ((status = dns_get_alias(reply, pos, &fixed, cname, c_len)) != DNS_OK)
626		    CORRUPT(status);
627	    if (!reply->dnssec_ad)
628		*maybe_secure = 0;
629	}
630	pos += fixed.length;
631    }
632
633    /*
634     * See what answer we came up with. Report success when the requested
635     * information was found. Otherwise, when a CNAME was found, report that
636     * more recursion is needed. Otherwise report failure.
637     */
638    if (resource_found)
639	return (DNS_OK);
640    if (cname_found)
641	return (DNS_RECURSE);
642    return (not_found_status);
643}
644
645/* dns_lookup_r - DNS lookup user interface */
646
647int     dns_lookup_r(const char *name, unsigned type, unsigned flags,
648		             DNS_RR **rrlist, VSTRING *fqdn, VSTRING *why,
649		             int *rcode)
650{
651    char    cname[DNS_NAME_LEN];
652    int     c_len = sizeof(cname);
653    static DNS_REPLY reply;
654    int     count;
655    int     status;
656    int     maybe_secure = 1;		/* Query name presumed secure */
657    const char *orig_name = name;
658
659    /*
660     * DJBDNS produces a bogus A record when given a numerical hostname.
661     */
662    if (valid_hostaddr(name, DONT_GRIPE)) {
663	if (why)
664	    vstring_sprintf(why,
665		   "Name service error for %s: invalid host or domain name",
666			    name);
667	SET_H_ERRNO(HOST_NOT_FOUND);
668	return (DNS_NOTFOUND);
669    }
670
671    /*
672     * The Linux resolver misbehaves when given an invalid domain name.
673     */
674    if (!valid_hostname(name, DONT_GRIPE)) {
675	if (why)
676	    vstring_sprintf(why,
677		   "Name service error for %s: invalid host or domain name",
678			    name);
679	SET_H_ERRNO(HOST_NOT_FOUND);
680	return (DNS_NOTFOUND);
681    }
682
683    /*
684     * Perform the lookup. Follow CNAME chains, but only up to a
685     * pre-determined maximum.
686     */
687    for (count = 0; count < 10; count++) {
688
689	/*
690	 * Perform the DNS lookup, and pre-parse the name server reply.
691	 */
692	status = dns_query(name, type, flags, &reply, why);
693	if (rcode)
694	    *rcode = reply.rcode;
695	if (status != DNS_OK)
696	    return (status);
697
698	/*
699	 * Extract resource records of the requested type. Pick up CNAME
700	 * information just in case the requested data is not found.
701	 */
702	status = dns_get_answer(orig_name, &reply, type, rrlist, fqdn,
703				cname, c_len, &maybe_secure);
704	switch (status) {
705	default:
706	    if (why)
707		vstring_sprintf(why, "Name service error for name=%s type=%s: "
708				"Malformed or unexpected name server reply",
709				name, dns_strtype(type));
710	    /* FALLTHROUGH */
711	case DNS_OK:
712	    return (status);
713	case DNS_RECURSE:
714	    if (msg_verbose)
715		msg_info("dns_lookup: %s aliased to %s", name, cname);
716#if RES_USE_DNSSEC
717
718	    /*
719	     * Once an intermediate CNAME reply is not validated, all
720	     * consequent RRs are deemed not validated, so we don't ask for
721	     * further DNSSEC replies.
722	     */
723	    if (maybe_secure == 0)
724		flags &= ~RES_USE_DNSSEC;
725#endif
726	    name = cname;
727	}
728    }
729    if (why)
730	vstring_sprintf(why, "Name server loop for %s", name);
731    msg_warn("dns_lookup: Name server loop for %s", name);
732    return (DNS_NOTFOUND);
733}
734
735/* dns_lookup_rl - DNS lookup interface with types list */
736
737int     dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist,
738		              VSTRING *fqdn, VSTRING *why, int *rcode,
739		              int lflags,...)
740{
741    va_list ap;
742    unsigned type;
743    int     status = DNS_NOTFOUND;
744    DNS_RR *rr;
745    int     non_err = 0;
746    int     soft_err = 0;
747
748    if (rrlist)
749	*rrlist = 0;
750    va_start(ap, lflags);
751    while ((type = va_arg(ap, unsigned)) != 0) {
752	if (msg_verbose)
753	    msg_info("lookup %s type %s flags %d",
754		     name, dns_strtype(type), flags);
755	status = dns_lookup_r(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
756			      fqdn, why, rcode);
757	if (status == DNS_OK) {
758	    non_err = 1;
759	    if (rrlist)
760		*rrlist = dns_rr_append(*rrlist, rr);
761	    if (lflags & DNS_REQ_FLAG_STOP_OK)
762		break;
763	} else if (status == DNS_INVAL) {
764	    if (lflags & DNS_REQ_FLAG_STOP_INVAL)
765		break;
766	} else if (status == DNS_RETRY) {
767	    soft_err = 1;
768	}
769	/* XXX Stop after NXDOMAIN error. */
770    }
771    va_end(ap);
772    return (non_err ? DNS_OK : soft_err ? DNS_RETRY : status);
773}
774
775/* dns_lookup_rv - DNS lookup interface with types vector */
776
777int     dns_lookup_rv(const char *name, unsigned flags, DNS_RR **rrlist,
778		              VSTRING *fqdn, VSTRING *why, int *rcode,
779		              int lflags, unsigned *types)
780{
781    unsigned type;
782    int     status = DNS_NOTFOUND;
783    DNS_RR *rr;
784    int     non_err = 0;
785    int     soft_err = 0;
786
787    if (rrlist)
788	*rrlist = 0;
789    while ((type = *types++) != 0) {
790	if (msg_verbose)
791	    msg_info("lookup %s type %s flags %d",
792		     name, dns_strtype(type), flags);
793	status = dns_lookup_r(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
794			      fqdn, why, rcode);
795	if (status == DNS_OK) {
796	    non_err = 1;
797	    if (rrlist)
798		*rrlist = dns_rr_append(*rrlist, rr);
799	    if (lflags & DNS_REQ_FLAG_STOP_OK)
800		break;
801	} else if (status == DNS_INVAL) {
802	    if (lflags & DNS_REQ_FLAG_STOP_INVAL)
803		break;
804	} else if (status == DNS_RETRY) {
805	    soft_err = 1;
806	}
807	/* XXX Stop after NXDOMAIN error. */
808    }
809    return (non_err ? DNS_OK : soft_err ? DNS_RETRY : status);
810}
811