1 /*        $NetBSD: dns_lookup.c,v 1.9 2025/02/25 19:15:44 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 
dns_neg_query(const char * name,int class,int type,unsigned char * answer,int anslen)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 
dns_neg_search(const char * name,int class,int type,unsigned char * answer,int anslen,int keep_notfound)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 
dns_query(const char * name,int type,unsigned flags,DNS_REPLY * reply,VSTRING * why,unsigned lflags)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 
dns_skip_query(DNS_REPLY * reply)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 
dns_get_fixed(unsigned char * pos,DNS_FIXED * fixed)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 
valid_rr_name(const char * name,const char * location,unsigned type,DNS_REPLY * reply)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 
dns_get_rr(DNS_RR ** list,const char * orig_name,DNS_REPLY * reply,unsigned char * pos,char * rr_name,DNS_FIXED * fixed)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               if (msg_verbose)
840                     msg_info("frag_len=%d text=\"%.*s\"",
841                                (int) frag_len, (int) frag_len, (char *) src);
842               if (frag_len > reply->end - src
843               || frag_len >= ((unsigned char *) ltemp + sizeof(ltemp)) - dst) {
844                     msg_warn("extract_answer: bad TXT string length: %d", frag_len);
845                     return (DNS_RETRY);
846               }
847               while (frag_len-- > 0) {
848                     ch = *src++;
849                     *dst++ = (ISPRINT(ch) ? ch : ' ');
850               }
851           }
852           *dst++ = 0;
853           tempbuf = ltemp;
854           data_len = dst - (unsigned char *) tempbuf;
855           break;
856 
857           /*
858            * For a full certificate, fixed->length may be longer than
859            * sizeof(tmpbuf) == DNS_NAME_LEN.  Since we don't need a decode
860            * buffer, just copy the raw data into the rr.
861            *
862            * XXX Reject replies with bogus length < 3.
863            *
864            * XXX What about enforcing a sane upper bound? The RFC 1035 hard
865            * protocol limit is the RRDATA length limit of 65535.
866            */
867     case T_TLSA:
868           data_len = fixed->length;
869           tempbuf = (char *) pos;
870           break;
871 
872           /*
873            * We use the SOA record TTL to determine the negative reply TTL. We
874            * save the time fields in the SOA record for debugging, but for now
875            * we don't bother saving the source host and mailbox information, as
876            * that would require changes to the DNS_RR structure and APIs. See
877            * also code in dns_strrecord().
878            */
879     case T_SOA:
880           comp_len = dn_skipname(pos, reply->end);
881           if (comp_len < 0)
882               return (DNS_RETRY);
883           pos += comp_len;
884           comp_len = dn_skipname(pos, reply->end);
885           if (comp_len < 0)
886               return (DNS_RETRY);
887           pos += comp_len;
888           if (reply->end - pos < sizeof(soa_buf)) {
889               msg_warn("extract_answer: bad SOA length: %d", fixed->length);
890               return (DNS_RETRY);
891           }
892           GETLONG(soa_buf[0], pos);               /* Serial */
893           GETLONG(soa_buf[1], pos);               /* Refresh */
894           GETLONG(soa_buf[2], pos);               /* Retry */
895           GETLONG(soa_buf[3], pos);               /* Expire */
896           GETLONG(soa_buf[4], pos);               /* Ncache TTL */
897           tempbuf = (char *) soa_buf;
898           data_len = sizeof(soa_buf);
899           break;
900     }
901     *list = dns_rr_create(orig_name, rr_name, fixed->type, fixed->class,
902                                 fixed->ttl, pref, weight, port, tempbuf, data_len);
903     return (DNS_OK);
904 }
905 
906 /* dns_get_alias - extract CNAME from name server reply */
907 
dns_get_alias(DNS_REPLY * reply,unsigned char * pos,DNS_FIXED * fixed,char * cname,int c_len)908 static int dns_get_alias(DNS_REPLY *reply, unsigned char *pos,
909                                        DNS_FIXED *fixed, char *cname, int c_len)
910 {
911     if (fixed->type != T_CNAME)
912           msg_panic("dns_get_alias: bad type %s", dns_strtype(fixed->type));
913     if (dn_expand(reply->buf, reply->end, pos, cname, c_len) < 0)
914           return (DNS_RETRY);
915     if (!valid_rr_name(cname, "resource data", fixed->type, reply))
916           return (DNS_INVAL);
917     return (DNS_OK);
918 }
919 
920 /* dns_get_answer - extract answers from name server reply */
921 
dns_get_answer(const char * orig_name,DNS_REPLY * reply,int type,DNS_RR ** rrlist,VSTRING * fqdn,char * cname,int c_len,int * maybe_secure)922 static int dns_get_answer(const char *orig_name, DNS_REPLY *reply, int type,
923                        DNS_RR **rrlist, VSTRING *fqdn, char *cname, int c_len,
924                                         int *maybe_secure)
925 {
926     char    rr_name[DNS_NAME_LEN];
927     unsigned char *pos;
928     int     answer_count = reply->answer_count;
929     int     len;
930     DNS_FIXED fixed;
931     DNS_RR *rr;
932     int     resource_found = 0;
933     int     cname_found = 0;
934     int     not_found_status = DNS_NOTFOUND;      /* can't happen */
935     int     status;
936 
937     /*
938      * Initialize. Skip over the name server query if we haven't yet.
939      */
940     if (reply->answer_start == 0)
941           if ((status = dns_skip_query(reply)) < 0)
942               return (status);
943     pos = reply->answer_start;
944 
945     /*
946      * Either this, or use a GOTO for emergency exits. The purpose is to
947      * prevent incomplete answers from being passed back to the caller.
948      */
949 #define CORRUPT(status) { \
950           if (rrlist && *rrlist) { \
951               dns_rr_free(*rrlist); \
952               *rrlist = 0; \
953           } \
954           return (status); \
955     }
956 
957     /*
958      * Iterate over all answers.
959      */
960     while (answer_count-- > 0) {
961 
962           /*
963            * Optionally extract the fully-qualified domain name.
964            */
965           if (pos >= reply->end)
966               CORRUPT(DNS_RETRY);
967           len = dn_expand(reply->buf, reply->end, pos, rr_name, DNS_NAME_LEN);
968           if (len < 0)
969               CORRUPT(DNS_RETRY);
970           pos += len;
971 
972           /*
973            * Extract the fixed reply data: type, class, ttl, length.
974            */
975           if (pos + RRFIXEDSZ > reply->end)
976               CORRUPT(DNS_RETRY);
977           if ((status = dns_get_fixed(pos, &fixed)) != DNS_OK)
978               CORRUPT(status);
979           if (strcmp(orig_name, ".") == 0 && *rr_name == 0)
980                /* Allow empty response name for root queries. */ ;
981           else if (!valid_rr_name(rr_name, "resource name", fixed.type, reply))
982               CORRUPT(DNS_INVAL);
983           if (fqdn)
984               vstring_strcpy(fqdn, rr_name);
985           if (msg_verbose)
986               msg_info("dns_get_answer: type %s for %s",
987                          dns_strtype(fixed.type), rr_name);
988           pos += RRFIXEDSZ;
989 
990           /*
991            * Optionally extract the requested resource or CNAME data.
992            */
993           if (pos + fixed.length > reply->end)
994               CORRUPT(DNS_RETRY);
995           if (type == fixed.type || type == T_ANY) {        /* requested type */
996               if (rrlist) {
997                     if ((status = dns_get_rr(&rr, orig_name, reply, pos, rr_name,
998                                                    &fixed)) == DNS_OK) {
999                         resource_found++;
1000                         rr->dnssec_valid = *maybe_secure ? reply->dnssec_ad : 0;
1001                         *rrlist = dns_rr_append(*rrlist, rr);
1002                         if (DNS_RR_IS_TRUNCATED(*rrlist))
1003                               break;
1004                     } else if (status == DNS_NULLMX || status == DNS_NULLSRV) {
1005                         CORRUPT(status);                    /* TODO: use better name */
1006                     } else if (not_found_status != DNS_RETRY)
1007                         not_found_status = status;
1008               } else
1009                     resource_found++;
1010           } else if (fixed.type == T_CNAME) {     /* cname resource */
1011               cname_found++;
1012               if (cname && c_len > 0)
1013                     if ((status = dns_get_alias(reply, pos, &fixed, cname, c_len)) != DNS_OK)
1014                         CORRUPT(status);
1015               if (!reply->dnssec_ad)
1016                     *maybe_secure = 0;
1017           }
1018           pos += fixed.length;
1019     }
1020 
1021     /*
1022      * See what answer we came up with. Report success when the requested
1023      * information was found. Otherwise, when a CNAME was found, report that
1024      * more recursion is needed. Otherwise report failure.
1025      */
1026     if (resource_found)
1027           return (DNS_OK);
1028     if (cname_found)
1029           return (DNS_RECURSE);
1030     return (not_found_status);
1031 }
1032 
1033 /* dns_lookup_x - DNS lookup user interface */
1034 
dns_lookup_x(const char * name,unsigned type,unsigned flags,DNS_RR ** rrlist,VSTRING * fqdn,VSTRING * why,int * rcode,unsigned lflags)1035 int     dns_lookup_x(const char *name, unsigned type, unsigned flags,
1036                                  DNS_RR **rrlist, VSTRING *fqdn, VSTRING *why,
1037                                  int *rcode, unsigned lflags)
1038 {
1039     char    cname[DNS_NAME_LEN];
1040     int     c_len = sizeof(cname);
1041     static DNS_REPLY reply;
1042     int     count;
1043     int     status;
1044     int     maybe_secure = 1;           /* Query name presumed secure */
1045     const char *orig_name = name;
1046 
1047     /*
1048      * Reset results early. DNS_OK is not the only status that returns
1049      * resource records; DNS_NOTFOUND will do that too, if requested.
1050      */
1051     if (rrlist)
1052           *rrlist = 0;
1053 
1054     /*
1055      * DJBDNS produces a bogus A record when given a numerical hostname.
1056      */
1057     if (valid_hostaddr(name, DONT_GRIPE)) {
1058           if (why)
1059               vstring_sprintf(why,
1060                        "Name service error for %s: invalid host or domain name",
1061                                   name);
1062           if (rcode)
1063               *rcode = NXDOMAIN;
1064           DNS_SET_H_ERRNO(&dns_res_state, HOST_NOT_FOUND);
1065           return (DNS_NOTFOUND);
1066     }
1067 
1068     /*
1069      * The Linux resolver misbehaves when given an invalid domain name.
1070      */
1071     if (strcmp(name, ".") && !valid_hostname(name, DONT_GRIPE | DO_WILDCARD)) {
1072           if (why)
1073               vstring_sprintf(why,
1074                        "Name service error for %s: invalid host or domain name",
1075                                   name);
1076           if (rcode)
1077               *rcode = NXDOMAIN;
1078           DNS_SET_H_ERRNO(&dns_res_state, HOST_NOT_FOUND);
1079           return (DNS_NOTFOUND);
1080     }
1081 
1082     /*
1083      * Perform the lookup. Follow CNAME chains, but only up to a
1084      * pre-determined maximum.
1085      */
1086     for (count = 0; count < 10; count++) {
1087 
1088           /*
1089            * Perform the DNS lookup, and pre-parse the name server reply.
1090            */
1091           status = dns_query(name, type, flags, &reply, why, lflags);
1092           if (rcode)
1093               *rcode = reply.rcode;
1094           if (status != DNS_OK) {
1095 
1096               /*
1097                * If the record does not exist, and we have a copy of the server
1098                * response, try to extract the negative caching TTL for the SOA
1099                * record in the authority section. DO NOT return an error if an
1100                * SOA record is malformed.
1101                */
1102               if (status == DNS_NOTFOUND && TEST_HAVE_DNS_REPLY_PACKET(&reply)
1103                     && reply.auth_count > 0) {
1104                     reply.answer_count = reply.auth_count;  /* XXX TODO: Fix API */
1105                     (void) dns_get_answer(orig_name, &reply, T_SOA, rrlist, fqdn,
1106                                               cname, c_len, &maybe_secure);
1107               }
1108               if (DNS_WANT_DNSSEC_VALIDATION(flags)
1109                     && !DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE | \
1110                                                DNS_SEC_FLAG_DONT_PROBE))
1111                     dns_sec_probe(flags);                   /* XXX Clobbers 'reply' */
1112               return (status);
1113           }
1114 
1115           /*
1116            * Extract resource records of the requested type. Pick up CNAME
1117            * information just in case the requested data is not found.
1118            */
1119           status = dns_get_answer(orig_name, &reply, type, rrlist, fqdn,
1120                                         cname, c_len, &maybe_secure);
1121           if (DNS_WANT_DNSSEC_VALIDATION(flags)
1122               && !DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE | \
1123                                            DNS_SEC_FLAG_DONT_PROBE))
1124               dns_sec_probe(flags);               /* XXX Clobbers 'reply' */
1125           switch (status) {
1126           default:
1127               if (why)
1128                     vstring_sprintf(why, "Name service error for name=%s type=%s: "
1129                                         "Malformed or unexpected name server reply",
1130                                         name, dns_strtype(type));
1131               return (status);
1132           case DNS_NULLMX:
1133               if (why)
1134                     vstring_sprintf(why, "Domain %s does not accept mail (nullMX)",
1135                                         name);
1136               DNS_SET_H_ERRNO(&dns_res_state, NO_DATA);
1137               return (status);
1138           case DNS_NULLSRV:
1139               if (why)
1140                     vstring_sprintf(why, "Domain %s does not support SRV requests",
1141                                         name);
1142               DNS_SET_H_ERRNO(&dns_res_state, NO_DATA);
1143               return (status);
1144           case DNS_OK:
1145               if (rrlist && dns_rr_filter_maps) {
1146                     if (dns_rr_filter_execute(rrlist) < 0) {
1147                         if (why)
1148                               vstring_sprintf(why,
1149                                                   "Error looking up name=%s type=%s: "
1150                                                   "Invalid DNS reply filter syntax",
1151                                                   name, dns_strtype(type));
1152                         dns_rr_free(*rrlist);
1153                         *rrlist = 0;
1154                         status = DNS_RETRY;
1155                     } else if (*rrlist == 0) {
1156                         if (why)
1157                               vstring_sprintf(why,
1158                                                   "Error looking up name=%s type=%s: "
1159                                                   "DNS reply filter drops all results",
1160                                                   name, dns_strtype(type));
1161                         status = DNS_POLICY;
1162                     }
1163               }
1164               return (status);
1165           case DNS_RECURSE:
1166               if (msg_verbose)
1167                     msg_info("dns_lookup: %s aliased to %s", name, cname);
1168 #if RES_USE_DNSSEC
1169 
1170               /*
1171                * Once an intermediate CNAME reply is not validated, all
1172                * consequent RRs are deemed not validated, so we don't ask for
1173                * further DNSSEC replies.
1174                */
1175               if (maybe_secure == 0)
1176                     flags &= ~RES_USE_DNSSEC;
1177 #endif
1178               name = cname;
1179           }
1180     }
1181     if (why)
1182           vstring_sprintf(why, "Name server loop for %s", name);
1183     msg_warn("dns_lookup: Name server loop for %s", name);
1184     return (DNS_NOTFOUND);
1185 }
1186 
1187 /* dns_lookup_rl - DNS lookup interface with types list */
1188 
dns_lookup_rl(const char * name,unsigned flags,DNS_RR ** rrlist,VSTRING * fqdn,VSTRING * why,int * rcode,int lflags,...)1189 int     dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist,
1190                                   VSTRING *fqdn, VSTRING *why, int *rcode,
1191                                   int lflags,...)
1192 {
1193     va_list ap;
1194     unsigned type, next;
1195     int     status = DNS_NOTFOUND;
1196     int     hpref_status = INT_MIN;
1197     VSTRING *hpref_rtext = 0;
1198     int     hpref_rcode;
1199     int     hpref_h_errno;
1200     DNS_RR *rr;
1201 
1202     /* Save intermediate highest-priority result. */
1203 #define SAVE_HPREF_STATUS() do { \
1204           hpref_status = status; \
1205           if (rcode) \
1206               hpref_rcode = *rcode; \
1207           if (why && status != DNS_OK) \
1208               vstring_strcpy(hpref_rtext ? hpref_rtext : \
1209                                  (hpref_rtext = vstring_alloc(VSTRING_LEN(why))), \
1210                                  vstring_str(why)); \
1211           hpref_h_errno = DNS_GET_H_ERRNO(&dns_res_state); \
1212     } while (0)
1213 
1214     /* Restore intermediate highest-priority result. */
1215 #define RESTORE_HPREF_STATUS() do { \
1216           status = hpref_status; \
1217           if (rcode) \
1218               *rcode = hpref_rcode; \
1219           if (why && status != DNS_OK) \
1220               vstring_strcpy(why, vstring_str(hpref_rtext)); \
1221           DNS_SET_H_ERRNO(&dns_res_state, hpref_h_errno); \
1222     } while (0)
1223 
1224     if (rrlist)
1225           *rrlist = 0;
1226     va_start(ap, lflags);
1227     for (type = va_arg(ap, unsigned); type != 0; type = next) {
1228           next = va_arg(ap, unsigned);
1229           if (msg_verbose)
1230               msg_info("lookup %s type %s flags %s",
1231                          name, dns_strtype(type), dns_str_resflags(flags));
1232           status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
1233                                     fqdn, why, rcode, lflags);
1234           if (rrlist && rr) {
1235               *rrlist = dns_rr_append(*rrlist, rr);
1236               if (DNS_RR_IS_TRUNCATED(*rrlist))
1237                     break;
1238           }
1239           if (status == DNS_OK) {
1240               if (lflags & DNS_REQ_FLAG_STOP_OK)
1241                     break;
1242           } else if (status == DNS_INVAL) {
1243               if (lflags & DNS_REQ_FLAG_STOP_INVAL)
1244                     break;
1245           } else if (status == DNS_POLICY) {
1246               if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY))
1247                     break;
1248           } else if (status == DNS_NULLMX) {
1249               if (lflags & DNS_REQ_FLAG_STOP_NULLMX)
1250                     break;
1251           }
1252           /* XXX Stop after NXDOMAIN error. */
1253           if (next == 0)
1254               break;
1255           if (status >= hpref_status)
1256               SAVE_HPREF_STATUS();                /* save last info */
1257     }
1258     va_end(ap);
1259     if (status < hpref_status)
1260           RESTORE_HPREF_STATUS();                           /* else report last info */
1261     if (hpref_rtext)
1262           vstring_free(hpref_rtext);
1263     return (status);
1264 }
1265 
1266 /* dns_lookup_rv - DNS lookup interface with types vector */
1267 
dns_lookup_rv(const char * name,unsigned flags,DNS_RR ** rrlist,VSTRING * fqdn,VSTRING * why,int * rcode,int lflags,unsigned * types)1268 int     dns_lookup_rv(const char *name, unsigned flags, DNS_RR **rrlist,
1269                                   VSTRING *fqdn, VSTRING *why, int *rcode,
1270                                   int lflags, unsigned *types)
1271 {
1272     unsigned type, next;
1273     int     status = DNS_NOTFOUND;
1274     int     hpref_status = INT_MIN;
1275     VSTRING *hpref_rtext = 0;
1276     int     hpref_rcode;
1277     int     hpref_h_errno;
1278     DNS_RR *rr;
1279 
1280     if (rrlist)
1281           *rrlist = 0;
1282     for (type = *types++; type != 0; type = next) {
1283           next = *types++;
1284           if (msg_verbose)
1285               msg_info("lookup %s type %s flags %s",
1286                          name, dns_strtype(type), dns_str_resflags(flags));
1287           status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
1288                                     fqdn, why, rcode, lflags);
1289           if (rrlist && rr) {
1290               *rrlist = dns_rr_append(*rrlist, rr);
1291               if (DNS_RR_IS_TRUNCATED(*rrlist))
1292                     break;
1293           }
1294           if (status == DNS_OK) {
1295               if (lflags & DNS_REQ_FLAG_STOP_OK)
1296                     break;
1297           } else if (status == DNS_INVAL) {
1298               if (lflags & DNS_REQ_FLAG_STOP_INVAL)
1299                     break;
1300           } else if (status == DNS_POLICY) {
1301               if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY))
1302                     break;
1303           } else if (status == DNS_NULLMX) {
1304               if (lflags & DNS_REQ_FLAG_STOP_NULLMX)
1305                     break;
1306           }
1307           /* XXX Stop after NXDOMAIN error. */
1308           if (next == 0)
1309               break;
1310           if (status >= hpref_status)
1311               SAVE_HPREF_STATUS();                /* save last info */
1312     }
1313     if (status < hpref_status)
1314           RESTORE_HPREF_STATUS();                           /* else report last info */
1315     if (hpref_rtext)
1316           vstring_free(hpref_rtext);
1317     return (status);
1318 }
1319 
1320 /* dns_get_h_errno - get the last lookup status */
1321 
dns_get_h_errno(void)1322 int     dns_get_h_errno(void)
1323 {
1324     return (DNS_GET_H_ERRNO(&dns_res_state));
1325 }
1326