#include <sys_defs.h>
#include <netdb.h>
#include <string.h>
#include <ctype.h>
#include <mymalloc.h>
#include <vstring.h>
#include <msg.h>
#include <valid_hostname.h>
#include <stringops.h>
#include "dns.h"
#define DEF_DNS_REPLY_SIZE 4096
#define MAX_DNS_REPLY_SIZE 32768
typedef struct DNS_REPLY {
unsigned char *buf;
size_t buf_len;
int query_count;
int answer_count;
unsigned char *query_start;
unsigned char *answer_start;
unsigned char *end;
} DNS_REPLY;
#define INET_ADDR_LEN 4
#define INET6_ADDR_LEN 16
static int dns_query(const char *name, int type, int flags,
DNS_REPLY *reply, VSTRING *why)
{
HEADER *reply_header;
int len;
unsigned long saved_options;
if (reply->buf == 0) {
reply->buf = (unsigned char *) mymalloc(DEF_DNS_REPLY_SIZE);
reply->buf_len = DEF_DNS_REPLY_SIZE;
}
if ((_res.options & RES_INIT) == 0 && res_init() < 0) {
if (why)
vstring_strcpy(why, "Name service initialization failure");
return (DNS_FAIL);
}
#define USER_FLAGS (RES_DEBUG | RES_DNSRCH | RES_DEFNAMES)
if ((flags & USER_FLAGS) != flags)
msg_panic("dns_query: bad flags: %d", flags);
saved_options = (_res.options & USER_FLAGS);
for (;;) {
_res.options &= ~saved_options;
_res.options |= flags;
len = res_search((char *) name, C_IN, type, reply->buf, reply->buf_len);
_res.options &= ~flags;
_res.options |= saved_options;
if (len < 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:
return (DNS_NOTFOUND);
default:
return (DNS_RETRY);
}
}
if (msg_verbose)
msg_info("dns_query: %s (%s): OK", name, dns_strtype(type));
reply_header = (HEADER *) reply->buf;
if (reply_header->tc == 0 || reply->buf_len >= MAX_DNS_REPLY_SIZE)
break;
reply->buf = (unsigned char *)
myrealloc((char *) reply->buf, 2 * reply->buf_len);
reply->buf_len *= 2;
}
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;
}
reply->end = reply->buf + 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);
return (DNS_OK);
}
static int dns_skip_query(DNS_REPLY *reply)
{
int query_count = reply->query_count;
unsigned char *pos = reply->query_start;
char temp[DNS_NAME_LEN];
int len;
while (query_count-- > 0) {
if (pos >= reply->end)
return DNS_RETRY;
len = dn_expand(reply->buf, reply->end, pos, temp, DNS_NAME_LEN);
if (len < 0)
return (DNS_RETRY);
pos += len + QFIXEDSZ;
}
reply->answer_start = pos;
return (DNS_OK);
}
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);
}
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;
#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 (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);
}
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];
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_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);
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
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;
}
*list = dns_rr_create(orig_name, rr_name, fixed->type, fixed->class,
fixed->ttl, pref, temp, data_len);
return (DNS_OK);
}
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);
}
static int dns_get_answer(const char *orig_name, DNS_REPLY *reply, int type,
DNS_RR **rrlist, VSTRING *fqdn, char *cname, int c_len)
{
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;
int status;
if (reply->answer_start == 0)
if ((status = dns_skip_query(reply)) < 0)
return (status);
pos = reply->answer_start;
if (rrlist)
*rrlist = 0;
#define CORRUPT(status) { \
if (rrlist && *rrlist) { \
dns_rr_free(*rrlist); \
*rrlist = 0; \
} \
return (status); \
}
while (answer_count-- > 0) {
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;
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;
if (pos + fixed.length > reply->end)
CORRUPT(DNS_RETRY);
if (type == fixed.type || type == T_ANY) {
if (rrlist) {
if ((status = dns_get_rr(&rr, orig_name, reply, pos, rr_name,
&fixed)) == DNS_OK) {
resource_found++;
*rrlist = dns_rr_append(*rrlist, rr);
} else if (not_found_status != DNS_RETRY)
not_found_status = status;
} else
resource_found++;
} else if (fixed.type == T_CNAME) {
cname_found++;
if (cname && c_len > 0)
if ((status = dns_get_alias(reply, pos, &fixed, cname, c_len)) != DNS_OK)
CORRUPT(status);
}
pos += fixed.length;
}
if (resource_found)
return (DNS_OK);
if (cname_found)
return (DNS_RECURSE);
return (not_found_status);
}
int dns_lookup(const char *name, unsigned type, unsigned flags,
DNS_RR **rrlist, VSTRING *fqdn, VSTRING *why)
{
char cname[DNS_NAME_LEN];
int c_len = sizeof(cname);
static DNS_REPLY reply;
int count;
int status;
const char *orig_name = name;
if (valid_hostaddr(name, DONT_GRIPE)) {
if (why)
vstring_sprintf(why,
"Name service error for %s: invalid host or domain name",
name);
SET_H_ERRNO(HOST_NOT_FOUND);
return (DNS_NOTFOUND);
}
if (!valid_hostname(name, DONT_GRIPE)) {
if (why)
vstring_sprintf(why,
"Name service error for %s: invalid host or domain name",
name);
SET_H_ERRNO(HOST_NOT_FOUND);
return (DNS_NOTFOUND);
}
for (count = 0; count < 10; count++) {
if ((status = dns_query(name, type, flags, &reply, why)) != DNS_OK)
return (status);
status = dns_get_answer(orig_name, &reply, type, rrlist, fqdn,
cname, c_len);
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));
case DNS_OK:
return (status);
case DNS_RECURSE:
if (msg_verbose)
msg_info("dns_lookup: %s aliased to %s", name, cname);
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);
}
int dns_lookup_l(const char *name, unsigned flags, DNS_RR **rrlist,
VSTRING *fqdn, VSTRING *why, int lflags,...)
{
va_list ap;
unsigned type;
int status = DNS_NOTFOUND;
DNS_RR *rr;
int non_err = 0;
int soft_err = 0;
if (rrlist)
*rrlist = 0;
va_start(ap, lflags);
while ((type = va_arg(ap, unsigned)) != 0) {
if (msg_verbose)
msg_info("lookup %s type %s flags %d",
name, dns_strtype(type), flags);
status = dns_lookup(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
fqdn, why);
if (status == DNS_OK) {
non_err = 1;
if (rrlist)
*rrlist = dns_rr_append(*rrlist, rr);
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_RETRY) {
soft_err = 1;
}
}
va_end(ap);
return (non_err ? DNS_OK : soft_err ? DNS_RETRY : status);
}
int dns_lookup_v(const char *name, unsigned flags, DNS_RR **rrlist,
VSTRING *fqdn, VSTRING *why, int lflags,
unsigned *types)
{
unsigned type;
int status = DNS_NOTFOUND;
DNS_RR *rr;
int non_err = 0;
int soft_err = 0;
if (rrlist)
*rrlist = 0;
while ((type = *types++) != 0) {
if (msg_verbose)
msg_info("lookup %s type %s flags %d",
name, dns_strtype(type), flags);
status = dns_lookup(name, type, flags, rrlist ? &rr : (DNS_RR **) 0,
fqdn, why);
if (status == DNS_OK) {
non_err = 1;
if (rrlist)
*rrlist = dns_rr_append(*rrlist, rr);
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_RETRY) {
soft_err = 1;
}
}
return (non_err ? DNS_OK : soft_err ? DNS_RETRY : status);
}