Patrick Welche <prlw1@cam.ac.uk>
[netbsd-mini2440.git] / external / ibm-public / postfix / dist / src / dns / dns_lookup.c
blobc6970caba3ad82533e691f0d12e4a19500e79fef
1 /* $NetBSD$ */
3 /*++
4 /* NAME
5 /* dns_lookup 3
6 /* SUMMARY
7 /* domain name service lookup
8 /* SYNOPSIS
9 /* #include <dns.h>
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;
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;
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.
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.
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.
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 /*--*/
137 /* System library. */
139 #include <sys_defs.h>
140 #include <netdb.h>
141 #include <string.h>
142 #include <ctype.h>
144 /* Utility library. */
146 #include <mymalloc.h>
147 #include <vstring.h>
148 #include <msg.h>
149 #include <valid_hostname.h>
150 #include <stringops.h>
152 /* DNS library. */
154 #include "dns.h"
156 /* Local stuff. */
159 * Structure to keep track of things while decoding a name server reply.
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 */
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;
174 #define INET_ADDR_LEN 4 /* XXX */
175 #define INET6_ADDR_LEN 16 /* XXX */
177 /* dns_query - query name server and pre-parse the reply */
179 static int dns_query(const char *name, int type, int flags,
180 DNS_REPLY *reply, VSTRING *why)
182 HEADER *reply_header;
183 int len;
184 unsigned long saved_options;
187 * Initialize the reply buffer.
189 if (reply->buf == 0) {
190 reply->buf = (unsigned char *) mymalloc(DEF_DNS_REPLY_SIZE);
191 reply->buf_len = DEF_DNS_REPLY_SIZE;
195 * Initialize the name service.
197 if ((_res.options & RES_INIT) == 0 && res_init() < 0) {
198 if (why)
199 vstring_strcpy(why, "Name service initialization failure");
200 return (DNS_FAIL);
204 * Set search options: debugging, parent domain search, append local
205 * domain. Do not allow the user to control other features.
207 #define USER_FLAGS (RES_DEBUG | RES_DNSRCH | RES_DEFNAMES)
209 if ((flags & USER_FLAGS) != flags)
210 msg_panic("dns_query: bad flags: %d", flags);
211 saved_options = (_res.options & USER_FLAGS);
214 * Perform the lookup. Claim that the information cannot be found if and
215 * only if the name server told us so.
217 for (;;) {
218 _res.options &= ~saved_options;
219 _res.options |= flags;
220 len = res_search((char *) name, C_IN, type, reply->buf, reply->buf_len);
221 _res.options &= ~flags;
222 _res.options |= saved_options;
223 if (len < 0) {
224 if (why)
225 vstring_sprintf(why, "Host or domain name not found. "
226 "Name service error for name=%s type=%s: %s",
227 name, dns_strtype(type), dns_strerror(h_errno));
228 if (msg_verbose)
229 msg_info("dns_query: %s (%s): %s",
230 name, dns_strtype(type), dns_strerror(h_errno));
231 switch (h_errno) {
232 case NO_RECOVERY:
233 return (DNS_FAIL);
234 case HOST_NOT_FOUND:
235 case NO_DATA:
236 return (DNS_NOTFOUND);
237 default:
238 return (DNS_RETRY);
241 if (msg_verbose)
242 msg_info("dns_query: %s (%s): OK", name, dns_strtype(type));
244 reply_header = (HEADER *) reply->buf;
245 if (reply_header->tc == 0 || reply->buf_len >= MAX_DNS_REPLY_SIZE)
246 break;
247 reply->buf = (unsigned char *)
248 myrealloc((char *) reply->buf, 2 * reply->buf_len);
249 reply->buf_len *= 2;
253 * Paranoia.
255 if (len > reply->buf_len) {
256 msg_warn("reply length %d > buffer length %d for name=%s type=%s",
257 len, (int) reply->buf_len, name, dns_strtype(type));
258 len = reply->buf_len;
262 * Initialize the reply structure. Some structure members are filled on
263 * the fly while the reply is being parsed.
265 reply->end = reply->buf + len;
266 reply->query_start = reply->buf + sizeof(HEADER);
267 reply->answer_start = 0;
268 reply->query_count = ntohs(reply_header->qdcount);
269 reply->answer_count = ntohs(reply_header->ancount);
270 return (DNS_OK);
273 /* dns_skip_query - skip query data in name server reply */
275 static int dns_skip_query(DNS_REPLY *reply)
277 int query_count = reply->query_count;
278 unsigned char *pos = reply->query_start;
279 char temp[DNS_NAME_LEN];
280 int len;
283 * For each query, skip over the domain name and over the fixed query
284 * data.
286 while (query_count-- > 0) {
287 if (pos >= reply->end)
288 return DNS_RETRY;
289 len = dn_expand(reply->buf, reply->end, pos, temp, DNS_NAME_LEN);
290 if (len < 0)
291 return (DNS_RETRY);
292 pos += len + QFIXEDSZ;
294 reply->answer_start = pos;
295 return (DNS_OK);
298 /* dns_get_fixed - extract fixed data from resource record */
300 static int dns_get_fixed(unsigned char *pos, DNS_FIXED *fixed)
302 GETSHORT(fixed->type, pos);
303 GETSHORT(fixed->class, pos);
304 GETLONG(fixed->ttl, pos);
305 GETSHORT(fixed->length, pos);
307 if (fixed->class != C_IN) {
308 msg_warn("dns_get_fixed: bad class: %u", fixed->class);
309 return (DNS_RETRY);
311 return (DNS_OK);
314 /* valid_rr_name - validate hostname in resource record */
316 static int valid_rr_name(const char *name, const char *location,
317 unsigned type, DNS_REPLY *reply)
319 char temp[DNS_NAME_LEN];
320 char *query_name;
321 int len;
322 char *gripe;
323 int result;
326 * People aren't supposed to specify numeric names where domain names are
327 * required, but it "works" with some mailers anyway, so people complain
328 * when software doesn't bend over backwards.
330 #define PASS_NAME 1
331 #define REJECT_NAME 0
333 if (valid_hostaddr(name, DONT_GRIPE)) {
334 result = PASS_NAME;
335 gripe = "numeric domain name";
336 } else if (!valid_hostname(name, DO_GRIPE)) {
337 result = REJECT_NAME;
338 gripe = "malformed domain name";
339 } else {
340 result = PASS_NAME;
341 gripe = 0;
345 * If we have a gripe, show some context, including the name used in the
346 * query and the type of reply that we're looking at.
348 if (gripe) {
349 len = dn_expand(reply->buf, reply->end, reply->query_start,
350 temp, DNS_NAME_LEN);
351 query_name = (len < 0 ? "*unparsable*" : temp);
352 msg_warn("%s in %s of %s record for %s: %.100s",
353 gripe, location, dns_strtype(type), query_name, name);
355 return (result);
358 /* dns_get_rr - extract resource record from name server reply */
360 static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply,
361 unsigned char *pos, char *rr_name,
362 DNS_FIXED *fixed)
364 char temp[DNS_NAME_LEN];
365 ssize_t data_len;
366 unsigned pref = 0;
367 unsigned char *src;
368 unsigned char *dst;
369 int ch;
371 #define MIN2(a, b) ((unsigned)(a) < (unsigned)(b) ? (a) : (b))
373 *list = 0;
375 switch (fixed->type) {
376 default:
377 msg_panic("dns_get_rr: don't know how to extract resource type %s",
378 dns_strtype(fixed->type));
379 case T_CNAME:
380 case T_MB:
381 case T_MG:
382 case T_MR:
383 case T_NS:
384 case T_PTR:
385 if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
386 return (DNS_RETRY);
387 if (!valid_rr_name(temp, "resource data", fixed->type, reply))
388 return (DNS_INVAL);
389 data_len = strlen(temp) + 1;
390 break;
391 case T_MX:
392 GETSHORT(pref, pos);
393 if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
394 return (DNS_RETRY);
395 if (!valid_rr_name(temp, "resource data", fixed->type, reply))
396 return (DNS_INVAL);
397 data_len = strlen(temp) + 1;
398 break;
399 case T_A:
400 if (fixed->length != INET_ADDR_LEN) {
401 msg_warn("extract_answer: bad address length: %d", fixed->length);
402 return (DNS_RETRY);
404 if (fixed->length > sizeof(temp))
405 msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
406 fixed->length);
407 memcpy(temp, pos, fixed->length);
408 data_len = fixed->length;
409 break;
410 #ifdef T_AAAA
411 case T_AAAA:
412 if (fixed->length != INET6_ADDR_LEN) {
413 msg_warn("extract_answer: bad address length: %d", fixed->length);
414 return (DNS_RETRY);
416 if (fixed->length > sizeof(temp))
417 msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
418 fixed->length);
419 memcpy(temp, pos, fixed->length);
420 data_len = fixed->length;
421 break;
422 #endif
423 case T_TXT:
424 data_len = MIN2(pos[0] + 1, MIN2(fixed->length + 1, sizeof(temp)));
425 for (src = pos + 1, dst = (unsigned char *) (temp);
426 dst < (unsigned char *) (temp) + data_len - 1; /* */ ) {
427 ch = *src++;
428 *dst++ = (ISPRINT(ch) ? ch : ' ');
430 *dst = 0;
431 break;
433 *list = dns_rr_create(orig_name, rr_name, fixed->type, fixed->class,
434 fixed->ttl, pref, temp, data_len);
435 return (DNS_OK);
438 /* dns_get_alias - extract CNAME from name server reply */
440 static int dns_get_alias(DNS_REPLY *reply, unsigned char *pos,
441 DNS_FIXED *fixed, char *cname, int c_len)
443 if (fixed->type != T_CNAME)
444 msg_panic("dns_get_alias: bad type %s", dns_strtype(fixed->type));
445 if (dn_expand(reply->buf, reply->end, pos, cname, c_len) < 0)
446 return (DNS_RETRY);
447 if (!valid_rr_name(cname, "resource data", fixed->type, reply))
448 return (DNS_INVAL);
449 return (DNS_OK);
452 /* dns_get_answer - extract answers from name server reply */
454 static int dns_get_answer(const char *orig_name, DNS_REPLY *reply, int type,
455 DNS_RR **rrlist, VSTRING *fqdn, char *cname, int c_len)
457 char rr_name[DNS_NAME_LEN];
458 unsigned char *pos;
459 int answer_count = reply->answer_count;
460 int len;
461 DNS_FIXED fixed;
462 DNS_RR *rr;
463 int resource_found = 0;
464 int cname_found = 0;
465 int not_found_status = DNS_NOTFOUND; /* can't happen */
466 int status;
469 * Initialize. Skip over the name server query if we haven't yet.
471 if (reply->answer_start == 0)
472 if ((status = dns_skip_query(reply)) < 0)
473 return (status);
474 pos = reply->answer_start;
475 if (rrlist)
476 *rrlist = 0;
479 * Either this, or use a GOTO for emergency exits. The purpose is to
480 * prevent incomplete answers from being passed back to the caller.
482 #define CORRUPT(status) { \
483 if (rrlist && *rrlist) { \
484 dns_rr_free(*rrlist); \
485 *rrlist = 0; \
487 return (status); \
491 * Iterate over all answers.
493 while (answer_count-- > 0) {
496 * Optionally extract the fully-qualified domain name.
498 if (pos >= reply->end)
499 CORRUPT(DNS_RETRY);
500 len = dn_expand(reply->buf, reply->end, pos, rr_name, DNS_NAME_LEN);
501 if (len < 0)
502 CORRUPT(DNS_RETRY);
503 pos += len;
506 * Extract the fixed reply data: type, class, ttl, length.
508 if (pos + RRFIXEDSZ > reply->end)
509 CORRUPT(DNS_RETRY);
510 if ((status = dns_get_fixed(pos, &fixed)) != DNS_OK)
511 CORRUPT(status);
512 if (!valid_rr_name(rr_name, "resource name", fixed.type, reply))
513 CORRUPT(DNS_INVAL);
514 if (fqdn)
515 vstring_strcpy(fqdn, rr_name);
516 if (msg_verbose)
517 msg_info("dns_get_answer: type %s for %s",
518 dns_strtype(fixed.type), rr_name);
519 pos += RRFIXEDSZ;
522 * Optionally extract the requested resource or CNAME data.
524 if (pos + fixed.length > reply->end)
525 CORRUPT(DNS_RETRY);
526 if (type == fixed.type || type == T_ANY) { /* requested type */
527 if (rrlist) {
528 if ((status = dns_get_rr(&rr, orig_name, reply, pos, rr_name,
529 &fixed)) == DNS_OK) {
530 resource_found++;
531 *rrlist = dns_rr_append(*rrlist, rr);
532 } else if (not_found_status != DNS_RETRY)
533 not_found_status = status;
534 } else
535 resource_found++;
536 } else if (fixed.type == T_CNAME) { /* cname resource */
537 cname_found++;
538 if (cname && c_len > 0)
539 if ((status = dns_get_alias(reply, pos, &fixed, cname, c_len)) != DNS_OK)
540 CORRUPT(status);
542 pos += fixed.length;
546 * See what answer we came up with. Report success when the requested
547 * information was found. Otherwise, when a CNAME was found, report that
548 * more recursion is needed. Otherwise report failure.
550 if (resource_found)
551 return (DNS_OK);
552 if (cname_found)
553 return (DNS_RECURSE);
554 return (not_found_status);
557 /* dns_lookup - DNS lookup user interface */
559 int dns_lookup(const char *name, unsigned type, unsigned flags,
560 DNS_RR **rrlist, VSTRING *fqdn, VSTRING *why)
562 char cname[DNS_NAME_LEN];
563 int c_len = sizeof(cname);
564 static DNS_REPLY reply;
565 int count;
566 int status;
567 const char *orig_name = name;
570 * DJBDNS produces a bogus A record when given a numerical hostname.
572 if (valid_hostaddr(name, DONT_GRIPE)) {
573 if (why)
574 vstring_sprintf(why,
575 "Name service error for %s: invalid host or domain name",
576 name);
577 SET_H_ERRNO(HOST_NOT_FOUND);
578 return (DNS_NOTFOUND);
582 * The Linux resolver misbehaves when given an invalid domain name.
584 if (!valid_hostname(name, DONT_GRIPE)) {
585 if (why)
586 vstring_sprintf(why,
587 "Name service error for %s: invalid host or domain name",
588 name);
589 SET_H_ERRNO(HOST_NOT_FOUND);
590 return (DNS_NOTFOUND);
594 * Perform the lookup. Follow CNAME chains, but only up to a
595 * pre-determined maximum.
597 for (count = 0; count < 10; count++) {
600 * Perform the DNS lookup, and pre-parse the name server reply.
602 if ((status = dns_query(name, type, flags, &reply, why)) != DNS_OK)
603 return (status);
606 * Extract resource records of the requested type. Pick up CNAME
607 * information just in case the requested data is not found.
609 status = dns_get_answer(orig_name, &reply, type, rrlist, fqdn,
610 cname, c_len);
611 switch (status) {
612 default:
613 if (why)
614 vstring_sprintf(why, "Name service error for name=%s type=%s: "
615 "Malformed or unexpected name server reply",
616 name, dns_strtype(type));
617 /* FALLTHROUGH */
618 case DNS_OK:
619 return (status);
620 case DNS_RECURSE:
621 if (msg_verbose)
622 msg_info("dns_lookup: %s aliased to %s", name, cname);
623 name = cname;
626 if (why)
627 vstring_sprintf(why, "Name server loop for %s", name);
628 msg_warn("dns_lookup: Name server loop for %s", name);
629 return (DNS_NOTFOUND);
632 /* dns_lookup_l - DNS lookup interface with types list */
634 int dns_lookup_l(const char *name, unsigned flags, DNS_RR **rrlist,
635 VSTRING *fqdn, VSTRING *why, int lflags,...)
637 va_list ap;
638 unsigned type;
639 int status = DNS_NOTFOUND;
640 DNS_RR *rr;
641 int non_err = 0;
642 int soft_err = 0;
644 if (rrlist)
645 *rrlist = 0;
646 va_start(ap, lflags);
647 while ((type = va_arg(ap, unsigned)) != 0) {
648 if (msg_verbose)
649 msg_info("lookup %s type %s flags %d",
650 name, dns_strtype(type), flags);
651 status = dns_lookup(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
652 fqdn, why);
653 if (status == DNS_OK) {
654 non_err = 1;
655 if (rrlist)
656 *rrlist = dns_rr_append(*rrlist, rr);
657 if (lflags & DNS_REQ_FLAG_STOP_OK)
658 break;
659 } else if (status == DNS_INVAL) {
660 if (lflags & DNS_REQ_FLAG_STOP_INVAL)
661 break;
662 } else if (status == DNS_RETRY) {
663 soft_err = 1;
666 va_end(ap);
667 return (non_err ? DNS_OK : soft_err ? DNS_RETRY : status);
670 /* dns_lookup_v - DNS lookup interface with types vector */
672 int dns_lookup_v(const char *name, unsigned flags, DNS_RR **rrlist,
673 VSTRING *fqdn, VSTRING *why, int lflags,
674 unsigned *types)
676 unsigned type;
677 int status = DNS_NOTFOUND;
678 DNS_RR *rr;
679 int non_err = 0;
680 int soft_err = 0;
682 if (rrlist)
683 *rrlist = 0;
684 while ((type = *types++) != 0) {
685 if (msg_verbose)
686 msg_info("lookup %s type %s flags %d",
687 name, dns_strtype(type), flags);
688 status = dns_lookup(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
689 fqdn, why);
690 if (status == DNS_OK) {
691 non_err = 1;
692 if (rrlist)
693 *rrlist = dns_rr_append(*rrlist, rr);
694 if (lflags & DNS_REQ_FLAG_STOP_OK)
695 break;
696 } else if (status == DNS_INVAL) {
697 if (lflags & DNS_REQ_FLAG_STOP_INVAL)
698 break;
699 } else if (status == DNS_RETRY) {
700 soft_err = 1;
703 return (non_err ? DNS_OK : soft_err ? DNS_RETRY : status);