7 /* domain name service lookup
11 /* int dns_lookup(name, type, rflags, list, fqdn, why)
19 /* int dns_lookup_l(name, rflags, list, fqdn, why, lflags, ltype, ...)
28 /* int dns_lookup_v(name, rflags, list, fqdn, why, lflags, ltype)
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.
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.
55 /* The resource record type to be looked up (T_A, T_MX etc.).
57 /* Resolver flags. These are a bitwise OR of:
60 /* Print debugging information.
62 /* Search local domain and parent domains.
64 /* Append local domain to unqualified names.
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:
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.
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
89 /* A null pointer, or a pointer to a variable that receives a
90 /* list of requested resource records.
92 /* A null pointer, or storage for the fully-qualified domain
93 /* name found for \fIname\fR.
95 /* A null pointer, or storage for the reason for failure.
97 /* dns_lookup() returns one of the following codes and sets the
98 /* \fIwhy\fR argument accordingly:
100 /* The DNS query succeeded.
102 /* The DNS query succeeded; the requested information was not found.
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.
112 /* The query failed, or the reply was malformed.
113 /* The problem is considered transient.
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.
125 /* dns_rr(3) resource record memory and list management
129 /* The Secure Mailer license must be distributed with this software.
132 /* IBM T.J. Watson Research
134 /* Yorktown Heights, NY 10598, USA
137 /* System library. */
139 #include <sys_defs.h>
144 /* Utility library. */
146 #include <mymalloc.h>
149 #include <valid_hostname.h>
150 #include <stringops.h>
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 */
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
;
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) {
199 vstring_strcpy(why
, "Name service initialization failure");
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.
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
;
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
));
229 msg_info("dns_query: %s (%s): %s",
230 name
, dns_strtype(type
), dns_strerror(h_errno
));
236 return (DNS_NOTFOUND
);
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
)
247 reply
->buf
= (unsigned char *)
248 myrealloc((char *) reply
->buf
, 2 * reply
->buf_len
);
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
);
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
];
283 * For each query, skip over the domain name and over the fixed query
286 while (query_count
-- > 0) {
287 if (pos
>= reply
->end
)
289 len
= dn_expand(reply
->buf
, reply
->end
, pos
, temp
, DNS_NAME_LEN
);
292 pos
+= len
+ QFIXEDSZ
;
294 reply
->answer_start
= pos
;
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);
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
];
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.
331 #define REJECT_NAME 0
333 if (valid_hostaddr(name
, DONT_GRIPE
)) {
335 gripe
= "numeric domain name";
336 } else if (!valid_hostname(name
, DO_GRIPE
)) {
337 result
= REJECT_NAME
;
338 gripe
= "malformed domain name";
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.
349 len
= dn_expand(reply
->buf
, reply
->end
, reply
->query_start
,
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
);
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
,
364 char temp
[DNS_NAME_LEN
];
371 #define MIN2(a, b) ((unsigned)(a) < (unsigned)(b) ? (a) : (b))
375 switch (fixed
->type
) {
377 msg_panic("dns_get_rr: don't know how to extract resource type %s",
378 dns_strtype(fixed
->type
));
385 if (dn_expand(reply
->buf
, reply
->end
, pos
, temp
, sizeof(temp
)) < 0)
387 if (!valid_rr_name(temp
, "resource data", fixed
->type
, reply
))
389 data_len
= strlen(temp
) + 1;
393 if (dn_expand(reply
->buf
, reply
->end
, pos
, temp
, sizeof(temp
)) < 0)
395 if (!valid_rr_name(temp
, "resource data", fixed
->type
, reply
))
397 data_len
= strlen(temp
) + 1;
400 if (fixed
->length
!= INET_ADDR_LEN
) {
401 msg_warn("extract_answer: bad address length: %d", fixed
->length
);
404 if (fixed
->length
> sizeof(temp
))
405 msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
407 memcpy(temp
, pos
, fixed
->length
);
408 data_len
= fixed
->length
;
412 if (fixed
->length
!= INET6_ADDR_LEN
) {
413 msg_warn("extract_answer: bad address length: %d", fixed
->length
);
416 if (fixed
->length
> sizeof(temp
))
417 msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
419 memcpy(temp
, pos
, fixed
->length
);
420 data_len
= fixed
->length
;
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; /* */ ) {
428 *dst
++ = (ISPRINT(ch
) ? ch
: ' ');
433 *list
= dns_rr_create(orig_name
, rr_name
, fixed
->type
, fixed
->class,
434 fixed
->ttl
, pref
, temp
, data_len
);
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)
447 if (!valid_rr_name(cname
, "resource data", fixed
->type
, reply
))
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
];
459 int answer_count
= reply
->answer_count
;
463 int resource_found
= 0;
465 int not_found_status
= DNS_NOTFOUND
; /* can't happen */
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)
474 pos
= reply
->answer_start
;
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); \
491 * Iterate over all answers.
493 while (answer_count
-- > 0) {
496 * Optionally extract the fully-qualified domain name.
498 if (pos
>= reply
->end
)
500 len
= dn_expand(reply
->buf
, reply
->end
, pos
, rr_name
, DNS_NAME_LEN
);
506 * Extract the fixed reply data: type, class, ttl, length.
508 if (pos
+ RRFIXEDSZ
> reply
->end
)
510 if ((status
= dns_get_fixed(pos
, &fixed
)) != DNS_OK
)
512 if (!valid_rr_name(rr_name
, "resource name", fixed
.type
, reply
))
515 vstring_strcpy(fqdn
, rr_name
);
517 msg_info("dns_get_answer: type %s for %s",
518 dns_strtype(fixed
.type
), rr_name
);
522 * Optionally extract the requested resource or CNAME data.
524 if (pos
+ fixed
.length
> reply
->end
)
526 if (type
== fixed
.type
|| type
== T_ANY
) { /* requested type */
528 if ((status
= dns_get_rr(&rr
, orig_name
, reply
, pos
, rr_name
,
529 &fixed
)) == DNS_OK
) {
531 *rrlist
= dns_rr_append(*rrlist
, rr
);
532 } else if (not_found_status
!= DNS_RETRY
)
533 not_found_status
= status
;
536 } else if (fixed
.type
== T_CNAME
) { /* cname resource */
538 if (cname
&& c_len
> 0)
539 if ((status
= dns_get_alias(reply
, pos
, &fixed
, cname
, c_len
)) != DNS_OK
)
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.
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
;
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
)) {
575 "Name service error for %s: invalid host or domain 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
)) {
587 "Name service error for %s: invalid host or domain 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
)
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
,
614 vstring_sprintf(why
, "Name service error for name=%s type=%s: "
615 "Malformed or unexpected name server reply",
616 name
, dns_strtype(type
));
622 msg_info("dns_lookup: %s aliased to %s", name
, cname
);
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
,...)
639 int status
= DNS_NOTFOUND
;
646 va_start(ap
, lflags
);
647 while ((type
= va_arg(ap
, unsigned)) != 0) {
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,
653 if (status
== DNS_OK
) {
656 *rrlist
= dns_rr_append(*rrlist
, rr
);
657 if (lflags
& DNS_REQ_FLAG_STOP_OK
)
659 } else if (status
== DNS_INVAL
) {
660 if (lflags
& DNS_REQ_FLAG_STOP_INVAL
)
662 } else if (status
== DNS_RETRY
) {
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
,
677 int status
= DNS_NOTFOUND
;
684 while ((type
= *types
++) != 0) {
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,
690 if (status
== DNS_OK
) {
693 *rrlist
= dns_rr_append(*rrlist
, rr
);
694 if (lflags
& DNS_REQ_FLAG_STOP_OK
)
696 } else if (status
== DNS_INVAL
) {
697 if (lflags
& DNS_REQ_FLAG_STOP_INVAL
)
699 } else if (status
== DNS_RETRY
) {
703 return (non_err
? DNS_OK
: soft_err
? DNS_RETRY
: status
);