Current File : //root/postfix-3.2.0/src/dns/dns_lookup.c |
/*++
/* NAME
/* dns_lookup 3
/* SUMMARY
/* domain name service lookup
/* SYNOPSIS
/* #include <dns.h>
/*
/* int dns_lookup(name, type, rflags, list, fqdn, why)
/* const char *name;
/* unsigned type;
/* unsigned rflags;
/* DNS_RR **list;
/* VSTRING *fqdn;
/* VSTRING *why;
/*
/* int dns_lookup_l(name, rflags, list, fqdn, why, lflags, ltype, ...)
/* const char *name;
/* unsigned rflags;
/* DNS_RR **list;
/* VSTRING *fqdn;
/* VSTRING *why;
/* int lflags;
/* unsigned ltype;
/*
/* int dns_lookup_v(name, rflags, list, fqdn, why, lflags, ltype)
/* const char *name;
/* unsigned rflags;
/* DNS_RR **list;
/* VSTRING *fqdn;
/* VSTRING *why;
/* int lflags;
/* unsigned *ltype;
/* AUXILIARY FUNCTIONS
/* extern int var_dns_ncache_ttl_fix;
/*
/* int dns_lookup_r(name, type, rflags, list, fqdn, why, rcode)
/* const char *name;
/* unsigned type;
/* unsigned rflags;
/* DNS_RR **list;
/* VSTRING *fqdn;
/* VSTRING *why;
/* int *rcode;
/*
/* int dns_lookup_rl(name, rflags, list, fqdn, why, rcode, lflags,
/* ltype, ...)
/* const char *name;
/* unsigned rflags;
/* DNS_RR **list;
/* VSTRING *fqdn;
/* VSTRING *why;
/* int *rcode;
/* int lflags;
/* unsigned ltype;
/*
/* int dns_lookup_rv(name, rflags, list, fqdn, why, rcode, lflags,
/* ltype)
/* const char *name;
/* unsigned rflags;
/* DNS_RR **list;
/* VSTRING *fqdn;
/* VSTRING *why;
/* int *rcode;
/* int lflags;
/* unsigned *ltype;
/*
/* int dns_lookup_x(name, type, rflags, list, fqdn, why, rcode, lflags)
/* const char *name;
/* unsigned type;
/* unsigned rflags;
/* DNS_RR **list;
/* VSTRING *fqdn;
/* VSTRING *why;
/* int *rcode;
/* unsigned lflags;
/* DESCRIPTION
/* dns_lookup() looks up DNS resource records. When requested to
/* look up data other than type CNAME, it will follow a limited
/* number of CNAME indirections. All result names (including
/* null terminator) will fit a buffer of size DNS_NAME_LEN.
/* All name results are validated by \fIvalid_hostname\fR();
/* an invalid name is reported as a DNS_INVAL result, while
/* malformed replies are reported as transient errors.
/*
/* dns_lookup_l() and dns_lookup_v() allow the user to specify
/* a list of resource types.
/*
/* dns_lookup_x, dns_lookup_r(), dns_lookup_rl() and dns_lookup_rv()
/* accept or return additional information.
/*
/* The var_dns_ncache_ttl_fix variable controls a workaround
/* for res_search(3) implementations that break the
/* DNS_REQ_FLAG_NCACHE_TTL feature. The workaround does not
/* support EDNS0 or DNSSEC, but it should be sufficient for
/* DNSBL/DNSWL lookups.
/* INPUTS
/* .ad
/* .fi
/* .IP name
/* The name to be looked up in the domain name system.
/* This name must pass the valid_hostname() test; it
/* must not be an IP address.
/* .IP type
/* The resource record type to be looked up (T_A, T_MX etc.).
/* .IP rflags
/* Resolver flags. These are a bitwise OR of:
/* .RS
/* .IP RES_DEBUG
/* Print debugging information.
/* .IP RES_DNSRCH
/* Search local domain and parent domains.
/* .IP RES_DEFNAMES
/* Append local domain to unqualified names.
/* .IP RES_USE_DNSSEC
/* Request DNSSEC validation. This flag is silently ignored
/* when the system stub resolver API, resolver(3), does not
/* implement DNSSEC.
/* .RE
/* .IP lflags
/* Flags that control the operation of the dns_lookup*()
/* functions. DNS_REQ_FLAG_NONE requests no special processing.
/* Otherwise, specify one or more of the following:
/* .RS
/* .IP DNS_REQ_FLAG_STOP_INVAL
/* This flag is used by dns_lookup_l() and dns_lookup_v().
/* Invoke dns_lookup() for the resource types in the order as
/* specified, and return when dns_lookup() returns DNS_INVAL.
/* .IP DNS_REQ_FLAG_STOP_NULLMX
/* This flag is used by dns_lookup_l() and dns_lookup_v().
/* Invoke dns_lookup() for the resource types in the order as
/* specified, and return when dns_lookup() returns DNS_NULLMX.
/* .IP DNS_REQ_FLAG_STOP_MX_POLICY
/* This flag is used by dns_lookup_l() and dns_lookup_v().
/* Invoke dns_lookup() for the resource types in the order as
/* specified, and return when dns_lookup() returns DNS_POLICY
/* for an MX query.
/* .IP DNS_REQ_FLAG_STOP_OK
/* This flag is used by dns_lookup_l() and dns_lookup_v().
/* Invoke dns_lookup() for the resource types in the order as
/* specified, and return when dns_lookup() returns DNS_OK.
/* .IP DNS_REQ_FLAG_NCACHE_TTL
/* When the lookup result status is DNS_NOTFOUND, return the
/* SOA record(s) from the authority section in the reply, if
/* available. The per-record reply TTL specifies how long the
/* DNS_NOTFOUND answer is valid. The caller should pass the
/* record(s) to dns_rr_free().
/* .RE
/* .IP ltype
/* The resource record types to be looked up. In the case of
/* dns_lookup_l(), this is a null-terminated argument list.
/* In the case of dns_lookup_v(), this is a null-terminated
/* integer array.
/* OUTPUTS
/* .ad
/* .fi
/* .IP list
/* A null pointer, or a pointer to a variable that receives a
/* list of requested resource records.
/* .IP fqdn
/* A null pointer, or storage for the fully-qualified domain
/* name found for \fIname\fR.
/* .IP why
/* A null pointer, or storage for the reason for failure.
/* .IP rcode
/* Pointer to storage for the reply RCODE value. This gives
/* more detailed information than DNS_FAIL, DNS_RETRY, etc.
/* DIAGNOSTICS
/* dns_lookup() returns one of the following codes and sets the
/* \fIwhy\fR argument accordingly:
/* .IP DNS_OK
/* The DNS query succeeded.
/* .IP DNS_POLICY
/* The DNS query succeeded, but the answer did not pass the
/* policy filter.
/* .IP DNS_NOTFOUND
/* The DNS query succeeded; the requested information was not found.
/* .IP DNS_NULLMX
/* The DNS query succeeded; the requested service is unavailable.
/* This is returned when the list argument is not a null
/* pointer, and an MX lookup result contains a null server
/* name (so-called "nullmx" record).
/* .IP DNS_INVAL
/* The DNS query succeeded; the result failed the valid_hostname() test.
/*
/* NOTE: the valid_hostname() test is skipped for results that
/* the caller suppresses explicitly. For example, when the
/* caller requests MX record lookup but specifies a null
/* resource record list argument, no syntax check will be done
/* for MX server names.
/* .IP DNS_RETRY
/* The query failed, or the reply was malformed.
/* The problem is considered transient.
/* .IP DNS_FAIL
/* The query failed.
/* BUGS
/* dns_lookup() implements a subset of all possible resource types:
/* CNAME, MX, A, and some records with similar formatting requirements.
/* It is unwise to specify the T_ANY wildcard resource type.
/*
/* It takes a surprising amount of code to accomplish what appears
/* to be a simple task. Later versions of the mail system may implement
/* their own DNS client software.
/* SEE ALSO
/* dns_rr(3) resource record memory and list management
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Wietse Venema
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
/*--*/
/* System library. */
#include <sys_defs.h>
#include <netdb.h>
#include <string.h>
#include <ctype.h>
/* Utility library. */
#include <mymalloc.h>
#include <vstring.h>
#include <msg.h>
#include <valid_hostname.h>
#include <stringops.h>
/* Global library. */
#include <mail_params.h>
/* DNS library. */
#define LIBDNS_INTERNAL
#include "dns.h"
/* Local stuff. */
/*
* Structure to keep track of things while decoding a name server reply.
*/
#define DEF_DNS_REPLY_SIZE 4096 /* in case we're using TCP */
#define MAX_DNS_REPLY_SIZE 65536 /* in case we're using TCP */
#define MAX_DNS_QUERY_SIZE 2048 /* XXX */
typedef struct DNS_REPLY {
unsigned char *buf; /* raw reply data */
size_t buf_len; /* reply buffer length */
int rcode; /* unfiltered reply code */
int dnssec_ad; /* DNSSEC AD bit */
int query_count; /* number of queries */
int answer_count; /* number of answers */
int auth_count; /* number of authority records */
unsigned char *query_start; /* start of query data */
unsigned char *answer_start; /* start of answer data */
unsigned char *end; /* first byte past reply */
} DNS_REPLY;
/*
* Test/set primitives to determine if the reply buffer contains a server
* response. We use this when the caller requests DNS_REQ_FLAG_NCACHE_TTL,
* and the DNS server replies that the requested record does not exist.
*/
#define TEST_HAVE_DNS_REPLY_PACKET(r) ((r)->end > (r)->buf)
#define SET_HAVE_DNS_REPLY_PACKET(r, l) ((r)->end = (r)->buf + (l))
#define SET_NO_DNS_REPLY_PACKET(r) ((r)->end = (r)->buf)
#define INET_ADDR_LEN 4 /* XXX */
#define INET6_ADDR_LEN 16 /* XXX */
/*
* To improve postscreen's whitelisting support, we need to know how long a
* DNSBL "not found" answer is valid. The 2010 implementation assumed it was
* valid for 3600 seconds. That is too long by 2015 standards.
*
* Instead of guessing, Postfix 3.1 and later implement RFC 2308 (DNS NCACHE),
* where a DNS server provides the TTL of a "not found" response as the TTL
* of an SOA record in the authority section.
*
* Unfortunately, the res_search() and res_query() API gets in the way. These
* functions overload their result value, the server reply length, and
* return -1 when the requested record does not exist. With libbind-based
* implementations, the server response is still available in an application
* buffer, thanks to the promise that res_query() and res_search() invoke
* res_send(), which returns the full server response even if the requested
* record does not exist.
*
* If this promise is broken (for example, res_search() does not call
* res_send(), but some non-libbind implementation that updates the
* application buffer only when the requested record exists), then we have a
* way out by setting the var_dns_ncache_ttl_fix variable. This enables a
* limited res_query() clone that should be sufficient for DNSBL / DNSWL
* lookups.
*
* The libunbound API does not comingle the reply length and reply status
* information, but that will have to wait until it is safe to make
* libunbound a mandatory dependency for Postfix.
*/
/* dns_res_query - a res_query() clone that can return negative replies */
static int dns_res_query(const char *name, int class, int type,
unsigned char *answer, int anslen)
{
unsigned char msg_buf[MAX_DNS_QUERY_SIZE];
HEADER *reply_header = (HEADER *) answer;
int len;
/*
* Differences with res_query() from libbind:
*
* - This function returns a positive server reply length not only in case
* of success, but in all cases where a server reply is available that
* passes the preliminary checks in res_send().
*
* - This function clears h_errno in case of success. The caller must use
* h_errno instead of the return value to decide if the lookup was
* successful.
*
* - No support for EDNS0 and DNSSEC (including turning off EDNS0 after
* error). That should be sufficient for DNS reputation lookups where the
* reply contains a small number of IP addresses. TXT records are out of
* scope for this workaround.
*/
reply_header->rcode = NOERROR;
#define NO_MKQUERY_DATA_BUF ((unsigned char *) 0)
#define NO_MKQUERY_DATA_LEN ((int) 0)
#define NO_MKQUERY_NEWRR ((unsigned char *) 0)
if ((len = res_mkquery(QUERY, name, class, type, NO_MKQUERY_DATA_BUF,
NO_MKQUERY_DATA_LEN, NO_MKQUERY_NEWRR,
msg_buf, sizeof(msg_buf))) < 0) {
SET_H_ERRNO(NO_RECOVERY);
if (msg_verbose)
msg_info("res_mkquery() failed");
return (len);
} else if ((len = res_send(msg_buf, len, answer, anslen)) < 0) {
SET_H_ERRNO(TRY_AGAIN);
if (msg_verbose)
msg_info("res_send() failed");
return (len);
} else {
switch (reply_header->rcode) {
case NXDOMAIN:
SET_H_ERRNO(HOST_NOT_FOUND);
break;
case NOERROR:
if (reply_header->ancount != 0)
SET_H_ERRNO(0);
else
SET_H_ERRNO(NO_DATA);
break;
case SERVFAIL:
SET_H_ERRNO(TRY_AGAIN);
break;
default:
SET_H_ERRNO(NO_RECOVERY);
break;
}
return (len);
}
}
/* dns_res_search - res_search() that can return negative replies */
static int dns_res_search(const char *name, int class, int type,
unsigned char *answer, int anslen, int keep_notfound)
{
int len;
/*
* Differences with res_search() from libbind:
*
* - With a non-zero keep_notfound argument, this function returns a
* positive server reply length not only in case of success, but also in
* case of a "notfound" reply status. The keep_notfound argument is
* usually zero, which allows us to avoid an unnecessary memset() call in
* the most common use case.
*
* - This function clears h_errno in case of success. The caller must use
* h_errno instead of the return value to decide if a lookup was
* successful.
*/
#define NOT_FOUND_H_ERRNO(he) ((he) == HOST_NOT_FOUND || (he) == NO_DATA)
if (keep_notfound)
/* Prepare for returning a null-padded server reply. */
memset(answer, 0, anslen);
len = res_query(name, class, type, answer, anslen);
if (len > 0) {
SET_H_ERRNO(0);
} else if (keep_notfound && NOT_FOUND_H_ERRNO(h_errno)) {
/* Expect to return a null-padded server reply. */
len = anslen;
}
return (len);
}
/* dns_query - query name server and pre-parse the reply */
static int dns_query(const char *name, int type, unsigned flags,
DNS_REPLY *reply, VSTRING *why, unsigned lflags)
{
HEADER *reply_header;
int len;
unsigned long saved_options;
int keep_notfound = (lflags & DNS_REQ_FLAG_NCACHE_TTL);
/*
* Initialize the reply buffer.
*/
if (reply->buf == 0) {
reply->buf = (unsigned char *) mymalloc(DEF_DNS_REPLY_SIZE);
reply->buf_len = DEF_DNS_REPLY_SIZE;
}
/*
* Initialize the name service.
*/
if ((_res.options & RES_INIT) == 0 && res_init() < 0) {
if (why)
vstring_strcpy(why, "Name service initialization failure");
return (DNS_FAIL);
}
/*
* Set search options: debugging, parent domain search, append local
* domain. Do not allow the user to control other features.
*/
#define USER_FLAGS (RES_DEBUG | RES_DNSRCH | RES_DEFNAMES | RES_USE_DNSSEC)
if ((flags & USER_FLAGS) != flags)
msg_panic("dns_query: bad flags: %d", flags);
/*
* Set extra options that aren't exposed to the application.
*/
#define XTRA_FLAGS (RES_USE_EDNS0)
if (flags & RES_USE_DNSSEC)
flags |= RES_USE_EDNS0;
/*
* Save and restore resolver options that we overwrite, to avoid
* surprising behavior in other code that also invokes the resolver.
*/
#define SAVE_FLAGS (USER_FLAGS | XTRA_FLAGS)
saved_options = (_res.options & SAVE_FLAGS);
/*
* Perform the lookup. Claim that the information cannot be found if and
* only if the name server told us so.
*/
for (;;) {
_res.options &= ~saved_options;
_res.options |= flags;
if (keep_notfound && var_dns_ncache_ttl_fix) {
len = dns_res_query((char *) name, C_IN, type, reply->buf,
reply->buf_len);
} else {
len = dns_res_search((char *) name, C_IN, type, reply->buf,
reply->buf_len, keep_notfound);
}
_res.options &= ~flags;
_res.options |= saved_options;
reply_header = (HEADER *) reply->buf;
reply->rcode = reply_header->rcode;
if (h_errno != 0) {
if (why)
vstring_sprintf(why, "Host or domain name not found. "
"Name service error for name=%s type=%s: %s",
name, dns_strtype(type), dns_strerror(h_errno));
if (msg_verbose)
msg_info("dns_query: %s (%s): %s",
name, dns_strtype(type), dns_strerror(h_errno));
switch (h_errno) {
case NO_RECOVERY:
return (DNS_FAIL);
case HOST_NOT_FOUND:
case NO_DATA:
if (keep_notfound)
break;
SET_NO_DNS_REPLY_PACKET(reply);
return (DNS_NOTFOUND);
default:
return (DNS_RETRY);
}
} else {
if (msg_verbose)
msg_info("dns_query: %s (%s): OK", name, dns_strtype(type));
}
if (reply_header->tc == 0 || reply->buf_len >= MAX_DNS_REPLY_SIZE)
break;
reply->buf = (unsigned char *)
myrealloc((void *) reply->buf, 2 * reply->buf_len);
reply->buf_len *= 2;
}
/*
* Future proofing. If this reaches the panic call, then some code change
* introduced a bug.
*/
if (len < 0)
msg_panic("dns_query: bad length %d (h_errno=%s)",
len, dns_strerror(h_errno));
/*
* Paranoia.
*/
if (len > reply->buf_len) {
msg_warn("reply length %d > buffer length %d for name=%s type=%s",
len, (int) reply->buf_len, name, dns_strtype(type));
len = reply->buf_len;
}
/*
* Initialize the reply structure. Some structure members are filled on
* the fly while the reply is being parsed. Coerce AD bit to boolean.
*/
#if RES_USE_DNSSEC != 0
reply->dnssec_ad = (flags & RES_USE_DNSSEC) ? !!reply_header->ad : 0;
#else
reply->dnssec_ad = 0;
#endif
SET_HAVE_DNS_REPLY_PACKET(reply, len);
reply->query_start = reply->buf + sizeof(HEADER);
reply->answer_start = 0;
reply->query_count = ntohs(reply_header->qdcount);
reply->answer_count = ntohs(reply_header->ancount);
reply->auth_count = ntohs(reply_header->nscount);
if (msg_verbose > 1)
msg_info("dns_query: reply len=%d ancount=%d nscount=%d",
len, reply->answer_count, reply->auth_count);
/*
* Future proofing. If this reaches the panic call, then some code change
* introduced a bug.
*/
if (h_errno == 0) {
return (DNS_OK);
} else if (keep_notfound) {
return (DNS_NOTFOUND);
} else {
msg_panic("dns_query: unexpected reply status: %s",
dns_strerror(h_errno));
}
}
/* dns_skip_query - skip query data in name server reply */
static int dns_skip_query(DNS_REPLY *reply)
{
int query_count = reply->query_count;
unsigned char *pos = reply->query_start;
int len;
/*
* For each query, skip over the domain name and over the fixed query
* data.
*/
while (query_count-- > 0) {
if (pos >= reply->end)
return DNS_RETRY;
len = dn_skipname(pos, reply->end);
if (len < 0)
return (DNS_RETRY);
pos += len + QFIXEDSZ;
}
reply->answer_start = pos;
return (DNS_OK);
}
/* dns_get_fixed - extract fixed data from resource record */
static int dns_get_fixed(unsigned char *pos, DNS_FIXED *fixed)
{
GETSHORT(fixed->type, pos);
GETSHORT(fixed->class, pos);
GETLONG(fixed->ttl, pos);
GETSHORT(fixed->length, pos);
if (fixed->class != C_IN) {
msg_warn("dns_get_fixed: bad class: %u", fixed->class);
return (DNS_RETRY);
}
return (DNS_OK);
}
/* valid_rr_name - validate hostname in resource record */
static int valid_rr_name(const char *name, const char *location,
unsigned type, DNS_REPLY *reply)
{
char temp[DNS_NAME_LEN];
char *query_name;
int len;
char *gripe;
int result;
/*
* People aren't supposed to specify numeric names where domain names are
* required, but it "works" with some mailers anyway, so people complain
* when software doesn't bend over backwards.
*/
#define PASS_NAME 1
#define REJECT_NAME 0
if (valid_hostaddr(name, DONT_GRIPE)) {
result = PASS_NAME;
gripe = "numeric domain name";
} else if (!valid_hostname(name, DO_GRIPE)) {
result = REJECT_NAME;
gripe = "malformed domain name";
} else {
result = PASS_NAME;
gripe = 0;
}
/*
* If we have a gripe, show some context, including the name used in the
* query and the type of reply that we're looking at.
*/
if (gripe) {
len = dn_expand(reply->buf, reply->end, reply->query_start,
temp, DNS_NAME_LEN);
query_name = (len < 0 ? "*unparsable*" : temp);
msg_warn("%s in %s of %s record for %s: %.100s",
gripe, location, dns_strtype(type), query_name, name);
}
return (result);
}
/* dns_get_rr - extract resource record from name server reply */
static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply,
unsigned char *pos, char *rr_name,
DNS_FIXED *fixed)
{
char temp[DNS_NAME_LEN];
char *tempbuf = temp;
UINT32_TYPE soa_buf[5];
int comp_len;
ssize_t data_len;
unsigned pref = 0;
unsigned char *src;
unsigned char *dst;
int ch;
#define MIN2(a, b) ((unsigned)(a) < (unsigned)(b) ? (a) : (b))
*list = 0;
switch (fixed->type) {
default:
msg_panic("dns_get_rr: don't know how to extract resource type %s",
dns_strtype(fixed->type));
case T_CNAME:
case T_DNAME:
case T_MB:
case T_MG:
case T_MR:
case T_NS:
case T_PTR:
if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
return (DNS_RETRY);
if (!valid_rr_name(temp, "resource data", fixed->type, reply))
return (DNS_INVAL);
data_len = strlen(temp) + 1;
break;
case T_MX:
GETSHORT(pref, pos);
if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0)
return (DNS_RETRY);
/* Don't even think of returning an invalid hostname to the caller. */
if (*temp == 0)
return (DNS_NULLMX); /* TODO: descriptive text */
if (!valid_rr_name(temp, "resource data", fixed->type, reply))
return (DNS_INVAL);
data_len = strlen(temp) + 1;
break;
case T_A:
if (fixed->length != INET_ADDR_LEN) {
msg_warn("extract_answer: bad address length: %d", fixed->length);
return (DNS_RETRY);
}
if (fixed->length > sizeof(temp))
msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
fixed->length);
memcpy(temp, pos, fixed->length);
data_len = fixed->length;
break;
#ifdef T_AAAA
case T_AAAA:
if (fixed->length != INET6_ADDR_LEN) {
msg_warn("extract_answer: bad address length: %d", fixed->length);
return (DNS_RETRY);
}
if (fixed->length > sizeof(temp))
msg_panic("dns_get_rr: length %d > DNS_NAME_LEN",
fixed->length);
memcpy(temp, pos, fixed->length);
data_len = fixed->length;
break;
#endif
/*
* We impose the same length limit here as for DNS names. However,
* see T_TLSA discussion below.
*/
case T_TXT:
data_len = MIN2(pos[0] + 1, MIN2(fixed->length + 1, sizeof(temp)));
for (src = pos + 1, dst = (unsigned char *) (temp);
dst < (unsigned char *) (temp) + data_len - 1; /* */ ) {
ch = *src++;
*dst++ = (ISPRINT(ch) ? ch : ' ');
}
*dst = 0;
break;
/*
* For a full certificate, fixed->length may be longer than
* sizeof(tmpbuf) == DNS_NAME_LEN. Since we don't need a decode
* buffer, just copy the raw data into the rr.
*
* XXX Reject replies with bogus length < 3.
*
* XXX What about enforcing a sane upper bound? The RFC 1035 hard
* protocol limit is the RRDATA length limit of 65535.
*/
case T_TLSA:
data_len = fixed->length;
tempbuf = (char *) pos;
break;
/*
* We use the SOA record TTL to determine the negative reply TTL. We
* save the time fields in the SOA record for debugging, but for now
* we don't bother saving the source host and mailbox information, as
* that would require changes to the DNS_RR structure and APIs. See
* also code in dns_strrecord().
*/
case T_SOA:
comp_len = dn_skipname(pos, reply->end);
if (comp_len < 0)
return (DNS_RETRY);
pos += comp_len;
comp_len = dn_skipname(pos, reply->end);
if (comp_len < 0)
return (DNS_RETRY);
pos += comp_len;
if (reply->end - pos < sizeof(soa_buf)) {
msg_warn("extract_answer: bad SOA length: %d", fixed->length);
return (DNS_RETRY);
}
GETLONG(soa_buf[0], pos); /* Serial */
GETLONG(soa_buf[1], pos); /* Refresh */
GETLONG(soa_buf[2], pos); /* Retry */
GETLONG(soa_buf[3], pos); /* Expire */
GETLONG(soa_buf[4], pos); /* Ncache TTL */
tempbuf = (char *) soa_buf;
data_len = sizeof(soa_buf);
break;
}
*list = dns_rr_create(orig_name, rr_name, fixed->type, fixed->class,
fixed->ttl, pref, tempbuf, data_len);
return (DNS_OK);
}
/* dns_get_alias - extract CNAME from name server reply */
static int dns_get_alias(DNS_REPLY *reply, unsigned char *pos,
DNS_FIXED *fixed, char *cname, int c_len)
{
if (fixed->type != T_CNAME)
msg_panic("dns_get_alias: bad type %s", dns_strtype(fixed->type));
if (dn_expand(reply->buf, reply->end, pos, cname, c_len) < 0)
return (DNS_RETRY);
if (!valid_rr_name(cname, "resource data", fixed->type, reply))
return (DNS_INVAL);
return (DNS_OK);
}
/* dns_get_answer - extract answers from name server reply */
static int 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)
{
char rr_name[DNS_NAME_LEN];
unsigned char *pos;
int answer_count = reply->answer_count;
int len;
DNS_FIXED fixed;
DNS_RR *rr;
int resource_found = 0;
int cname_found = 0;
int not_found_status = DNS_NOTFOUND; /* can't happen */
int status;
/*
* Initialize. Skip over the name server query if we haven't yet.
*/
if (reply->answer_start == 0)
if ((status = dns_skip_query(reply)) < 0)
return (status);
pos = reply->answer_start;
/*
* Either this, or use a GOTO for emergency exits. The purpose is to
* prevent incomplete answers from being passed back to the caller.
*/
#define CORRUPT(status) { \
if (rrlist && *rrlist) { \
dns_rr_free(*rrlist); \
*rrlist = 0; \
} \
return (status); \
}
/*
* Iterate over all answers.
*/
while (answer_count-- > 0) {
/*
* Optionally extract the fully-qualified domain name.
*/
if (pos >= reply->end)
CORRUPT(DNS_RETRY);
len = dn_expand(reply->buf, reply->end, pos, rr_name, DNS_NAME_LEN);
if (len < 0)
CORRUPT(DNS_RETRY);
pos += len;
/*
* Extract the fixed reply data: type, class, ttl, length.
*/
if (pos + RRFIXEDSZ > reply->end)
CORRUPT(DNS_RETRY);
if ((status = dns_get_fixed(pos, &fixed)) != DNS_OK)
CORRUPT(status);
if (!valid_rr_name(rr_name, "resource name", fixed.type, reply))
CORRUPT(DNS_INVAL);
if (fqdn)
vstring_strcpy(fqdn, rr_name);
if (msg_verbose)
msg_info("dns_get_answer: type %s for %s",
dns_strtype(fixed.type), rr_name);
pos += RRFIXEDSZ;
/*
* Optionally extract the requested resource or CNAME data.
*/
if (pos + fixed.length > reply->end)
CORRUPT(DNS_RETRY);
if (type == fixed.type || type == T_ANY) { /* requested type */
if (rrlist) {
if ((status = dns_get_rr(&rr, orig_name, reply, pos, rr_name,
&fixed)) == DNS_OK) {
resource_found++;
rr->dnssec_valid = *maybe_secure ? reply->dnssec_ad : 0;
*rrlist = dns_rr_append(*rrlist, rr);
} else if (status == DNS_NULLMX) {
CORRUPT(status); /* TODO: use better name */
} else if (not_found_status != DNS_RETRY)
not_found_status = status;
} else
resource_found++;
} else if (fixed.type == T_CNAME) { /* cname resource */
cname_found++;
if (cname && c_len > 0)
if ((status = dns_get_alias(reply, pos, &fixed, cname, c_len)) != DNS_OK)
CORRUPT(status);
if (!reply->dnssec_ad)
*maybe_secure = 0;
}
pos += fixed.length;
}
/*
* See what answer we came up with. Report success when the requested
* information was found. Otherwise, when a CNAME was found, report that
* more recursion is needed. Otherwise report failure.
*/
if (resource_found)
return (DNS_OK);
if (cname_found)
return (DNS_RECURSE);
return (not_found_status);
}
/* dns_lookup_x - DNS lookup user interface */
int dns_lookup_x(const char *name, unsigned type, unsigned flags,
DNS_RR **rrlist, VSTRING *fqdn, VSTRING *why,
int *rcode, unsigned lflags)
{
char cname[DNS_NAME_LEN];
int c_len = sizeof(cname);
static DNS_REPLY reply;
int count;
int status;
int maybe_secure = 1; /* Query name presumed secure */
const char *orig_name = name;
/*
* Reset results early. DNS_OK is not the only status that returns
* resource records; DNS_NOTFOUND will do that too, if requested.
*/
if (rrlist)
*rrlist = 0;
/*
* DJBDNS produces a bogus A record when given a numerical hostname.
*/
if (valid_hostaddr(name, DONT_GRIPE)) {
if (why)
vstring_sprintf(why,
"Name service error for %s: invalid host or domain name",
name);
if (rcode)
*rcode = NXDOMAIN;
SET_H_ERRNO(HOST_NOT_FOUND);
return (DNS_NOTFOUND);
}
/*
* The Linux resolver misbehaves when given an invalid domain name.
*/
if (!valid_hostname(name, DONT_GRIPE)) {
if (why)
vstring_sprintf(why,
"Name service error for %s: invalid host or domain name",
name);
if (rcode)
*rcode = NXDOMAIN;
SET_H_ERRNO(HOST_NOT_FOUND);
return (DNS_NOTFOUND);
}
/*
* Perform the lookup. Follow CNAME chains, but only up to a
* pre-determined maximum.
*/
for (count = 0; count < 10; count++) {
/*
* Perform the DNS lookup, and pre-parse the name server reply.
*/
status = dns_query(name, type, flags, &reply, why, lflags);
if (rcode)
*rcode = reply.rcode;
if (status != DNS_OK) {
/*
* If the record does not exist, and we have a copy of the server
* response, try to extract the negative caching TTL for the SOA
* record in the authority section. DO NOT return an error if an
* SOA record is malformed.
*/
if (status == DNS_NOTFOUND && TEST_HAVE_DNS_REPLY_PACKET(&reply)
&& reply.auth_count > 0) {
reply.answer_count = reply.auth_count; /* XXX TODO: Fix API */
(void) dns_get_answer(orig_name, &reply, T_SOA, rrlist, fqdn,
cname, c_len, &maybe_secure);
}
return (status);
}
/*
* Extract resource records of the requested type. Pick up CNAME
* information just in case the requested data is not found.
*/
status = dns_get_answer(orig_name, &reply, type, rrlist, fqdn,
cname, c_len, &maybe_secure);
switch (status) {
default:
if (why)
vstring_sprintf(why, "Name service error for name=%s type=%s: "
"Malformed or unexpected name server reply",
name, dns_strtype(type));
return (status);
case DNS_NULLMX:
if (why)
vstring_sprintf(why, "Domain %s does not accept mail (nullMX)",
name);
SET_H_ERRNO(NO_DATA);
return (status);
case DNS_OK:
if (rrlist && dns_rr_filter_maps) {
if (dns_rr_filter_execute(rrlist) < 0) {
if (why)
vstring_sprintf(why,
"Error looking up name=%s type=%s: "
"Invalid DNS reply filter syntax",
name, dns_strtype(type));
dns_rr_free(*rrlist);
*rrlist = 0;
status = DNS_RETRY;
} else if (*rrlist == 0) {
if (why)
vstring_sprintf(why,
"Error looking up name=%s type=%s: "
"DNS reply filter drops all results",
name, dns_strtype(type));
status = DNS_POLICY;
}
}
return (status);
case DNS_RECURSE:
if (msg_verbose)
msg_info("dns_lookup: %s aliased to %s", name, cname);
#if RES_USE_DNSSEC
/*
* Once an intermediate CNAME reply is not validated, all
* consequent RRs are deemed not validated, so we don't ask for
* further DNSSEC replies.
*/
if (maybe_secure == 0)
flags &= ~RES_USE_DNSSEC;
#endif
name = cname;
}
}
if (why)
vstring_sprintf(why, "Name server loop for %s", name);
msg_warn("dns_lookup: Name server loop for %s", name);
return (DNS_NOTFOUND);
}
/* dns_lookup_rl - DNS lookup interface with types list */
int dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist,
VSTRING *fqdn, VSTRING *why, int *rcode,
int lflags,...)
{
va_list ap;
unsigned type, next;
int status = DNS_NOTFOUND;
int hpref_status = INT_MIN;
VSTRING *hpref_rtext = 0;
int hpref_rcode;
int hpref_h_errno;
DNS_RR *rr;
/* Save intermediate highest-priority result. */
#define SAVE_HPREF_STATUS() do { \
hpref_status = status; \
if (rcode) \
hpref_rcode = *rcode; \
if (why && status != DNS_OK) \
vstring_strcpy(hpref_rtext ? hpref_rtext : \
(hpref_rtext = vstring_alloc(VSTRING_LEN(why))), \
vstring_str(why)); \
hpref_h_errno = h_errno; \
} while (0)
/* Restore intermediate highest-priority result. */
#define RESTORE_HPREF_STATUS() do { \
status = hpref_status; \
if (rcode) \
*rcode = hpref_rcode; \
if (why && status != DNS_OK) \
vstring_strcpy(why, vstring_str(hpref_rtext)); \
SET_H_ERRNO(hpref_h_errno); \
} while (0)
if (rrlist)
*rrlist = 0;
va_start(ap, lflags);
for (type = va_arg(ap, unsigned); type != 0; type = next) {
next = va_arg(ap, unsigned);
if (msg_verbose)
msg_info("lookup %s type %s flags %s",
name, dns_strtype(type), dns_str_resflags(flags));
status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
fqdn, why, rcode, lflags);
if (rrlist && rr)
*rrlist = dns_rr_append(*rrlist, rr);
if (status == DNS_OK) {
if (lflags & DNS_REQ_FLAG_STOP_OK)
break;
} else if (status == DNS_INVAL) {
if (lflags & DNS_REQ_FLAG_STOP_INVAL)
break;
} else if (status == DNS_POLICY) {
if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY))
break;
} else if (status == DNS_NULLMX) {
if (lflags & DNS_REQ_FLAG_STOP_NULLMX)
break;
}
/* XXX Stop after NXDOMAIN error. */
if (next == 0)
break;
if (status >= hpref_status)
SAVE_HPREF_STATUS(); /* save last info */
}
va_end(ap);
if (status < hpref_status)
RESTORE_HPREF_STATUS(); /* else report last info */
if (hpref_rtext)
vstring_free(hpref_rtext);
return (status);
}
/* dns_lookup_rv - DNS lookup interface with types vector */
int dns_lookup_rv(const char *name, unsigned flags, DNS_RR **rrlist,
VSTRING *fqdn, VSTRING *why, int *rcode,
int lflags, unsigned *types)
{
unsigned type, next;
int status = DNS_NOTFOUND;
int hpref_status = INT_MIN;
VSTRING *hpref_rtext = 0;
int hpref_rcode;
int hpref_h_errno;
DNS_RR *rr;
if (rrlist)
*rrlist = 0;
for (type = *types++; type != 0; type = next) {
next = *types++;
if (msg_verbose)
msg_info("lookup %s type %s flags %s",
name, dns_strtype(type), dns_str_resflags(flags));
status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
fqdn, why, rcode, lflags);
if (rrlist && rr)
*rrlist = dns_rr_append(*rrlist, rr);
if (status == DNS_OK) {
if (lflags & DNS_REQ_FLAG_STOP_OK)
break;
} else if (status == DNS_INVAL) {
if (lflags & DNS_REQ_FLAG_STOP_INVAL)
break;
} else if (status == DNS_POLICY) {
if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY))
break;
} else if (status == DNS_NULLMX) {
if (lflags & DNS_REQ_FLAG_STOP_NULLMX)
break;
}
/* XXX Stop after NXDOMAIN error. */
if (next == 0)
break;
if (status >= hpref_status)
SAVE_HPREF_STATUS(); /* save last info */
}
if (status < hpref_status)
RESTORE_HPREF_STATUS(); /* else report last info */
if (hpref_rtext)
vstring_free(hpref_rtext);
return (status);
}