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