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