#if defined(LIBC_SCCS) && !defined(lint)
static const char sccsid[] = "@(#)res_query.c 8.1 (Berkeley) 6/4/93";
static const char rcsid[] = "$Id: res_query.c,v 1.1 2006/03/01 19:01:38 majka Exp $";
#endif
#ifndef __APPLE__
#include "port_before.h"
#endif
#include <sys/types.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <resolv.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "res_private.h"
#include <dns_sd.h>
#include <sys/event.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#ifndef __APPLE__
#include "port_after.h"
#endif
#define DEBUG
#if PACKETSZ > 1024
#define MAXPACKET PACKETSZ
#else
#define MAXPACKET 1024
#endif
#define BILLION 1000000000
#define IPv6_REVERSE_LEN 72
#define IPv6_REVERSE_LINK_LOCAL_TRAILING_CHAR 58
#define IPv6_REVERSE_LINK_LOCAL_SCOPE_ID_LOW 48
const static uint8_t hexval[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
struct res_query_context
{
u_char *answer;
size_t anslen;
size_t ansmaxlen;
uint32_t ifnum;
DNSServiceFlags flags;
DNSServiceErrorType error;
};
static void
res_query_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t ifIndex, DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *ctx)
{
struct res_query_context *context;
int n;
size_t buflen;
u_char *dnlist[2], *cp;
HEADER *ans;
struct in6_addr a6;
context = (struct res_query_context *)ctx;
context->flags = flags;
context->error = errorCode;
if (errorCode != kDNSServiceErr_NoError) return;
buflen = context->ansmaxlen - context->anslen;
if (buflen < NS_HFIXEDSZ) return;
dnlist[0] = context->answer + NS_HFIXEDSZ;
dnlist[1] = NULL;
cp = context->answer + context->anslen;
n = dn_comp((char *)fullname, cp, buflen, dnlist, &dnlist[1]);
if (n < 0) return;
if (buflen < n + rdlen + 10) return;
cp += n;
buflen -= n;
putshort(rrtype, cp);
cp += sizeof(uint16_t);
putshort(rrclass, cp);
cp += sizeof(uint16_t);
putlong(ttl, cp);
cp += sizeof(uint32_t);
putshort(rdlen, cp);
cp += sizeof(uint16_t);
memcpy(cp, rdata, rdlen);
cp += rdlen;
ans = (HEADER *)context->answer;
ans->ancount = htons(ntohs(ans->ancount) + 1);
context->anslen = (size_t)(cp - context->answer);
if ((context->ifnum == 0) && (rrtype == ns_t_aaaa))
{
memset(&a6, 0, sizeof(struct in6_addr));
memcpy(&a6, rdata, sizeof(struct in6_addr));
if (IN6_IS_ADDR_LINKLOCAL(&a6)) context->ifnum = ifIndex;
}
}
static void
h_errno_for_dnssd_err(DNSServiceErrorType dnssd_err, int *h_errno_err)
{
switch (dnssd_err)
{
case kDNSServiceErr_NoError:
*h_errno_err = NETDB_SUCCESS;
break;
case kDNSServiceErr_Unknown:
*h_errno_err = NO_RECOVERY;
break;
case kDNSServiceErr_NoSuchRecord:
*h_errno_err = NO_DATA;
break;
case kDNSServiceErr_NoSuchName:
*h_errno_err = HOST_NOT_FOUND;
break;
case kDNSServiceErr_NoMemory:
default:
*h_errno_err = NETDB_INTERNAL;
break;
}
}
static int
_is_rev_link_local(const char *name)
{
int len, i;
if (name == NULL) return 0;
len = strlen(name);
if (len == 0) return 0;
if (name[len - 1] == '.') len--;
if (len != IPv6_REVERSE_LEN) return 0;
i = IPv6_REVERSE_LINK_LOCAL_TRAILING_CHAR;
if ((name[i] != '8') && (name[i] != '9') && (name[i] != 'A') && (name[i] != 'a') && (name[i] != 'B') && (name[i] != 'b')) return 0;
i = IPv6_REVERSE_LINK_LOCAL_TRAILING_CHAR + 1;
if (strncasecmp(name + i, ".e.f.ip6.arpa", 13)) return 0;
for (i = 0; i < IPv6_REVERSE_LINK_LOCAL_TRAILING_CHAR; i += 2)
{
if (name[i] < '0') return 0;
if ((name[i] > '9') && (name[i] < 'A')) return 0;
if ((name[i] > 'F') && (name[i] < 'a')) return 0;
if (name[i] > 'f') return 0;
if (name[i + 1] != '.') return 0;
}
return 1;
}
__private_extern__ int
res_query_mDNSResponder(res_state statp, const char *name, int class, int type, u_char *answer, int anslen, struct sockaddr *from, uint32_t *fromlen)
{
DNSServiceRef sdRef;
DNSServiceErrorType result;
struct res_query_context context;
int i, kq, n, wait;
struct kevent kv;
struct timeval ctv;
struct timespec now, finish, timeout;
HEADER *ans;
uint32_t iface;
uint16_t nibble;
char *qname;
result = 0;
kq = -1;
ans = (HEADER *)answer;
ans->rcode = 0;
memset(&context, 0, sizeof(struct res_query_context));
context.answer = answer;
context.ansmaxlen = anslen;
context.anslen = res_nmkquery(statp, ns_o_query, name, class, type, NULL, 0, NULL, answer, anslen);
if (context.anslen <= 0) return 0;
ans->qr = 1;
ans->qr = htons(ans->qr);
qname = (char *)name;
iface = 0;
if (_is_rev_link_local(name))
{
i = IPv6_REVERSE_LINK_LOCAL_SCOPE_ID_LOW;
nibble = hexval[(uint32_t)name[i]];
iface = nibble;
i += 2;
nibble = hexval[(uint32_t)name[i]];
iface += (nibble << 4);
i += 2;
nibble = hexval[(uint32_t)name[i]];
iface += (nibble << 8);
i += 2;
nibble = hexval[(uint32_t)name[i]];
iface += (nibble << 12);
if (iface != 0)
{
qname = strdup(name);
if (qname == NULL)
{
h_errno = NO_RECOVERY;
errno = ENOMEM;
return -1;
}
i = IPv6_REVERSE_LINK_LOCAL_SCOPE_ID_LOW;
qname[i] = '0';
qname[i + 2] = '0';
qname[i + 4] = '0';
qname[i + 6] = '0';
}
}
result = DNSServiceQueryRecord(&sdRef, kDNSServiceFlagsReturnIntermediates, iface, qname, type, class, res_query_callback, &context);
if (iface != 0) free(qname);
if (result != 0) return 0;
kq = kqueue();
gettimeofday(&ctv, NULL);
timeout.tv_sec = statp->retrans;
timeout.tv_nsec = 0;
finish.tv_sec = ctv.tv_sec + statp->retrans;
finish.tv_nsec = ctv.tv_usec * 1000;
EV_SET(&kv, DNSServiceRefSockFD(sdRef), EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, 0);
wait = 1;
while (wait == 1)
{
n = kevent(kq, &kv, 1, &kv, 1, &timeout);
if (n < 0)
{
if (errno == EINTR) goto keep_waiting;
h_errno = NO_RECOVERY;
wait = 0;
}
else if ((n == 0) && (ans->ancount == 0))
{
h_errno = TRY_AGAIN;
wait = 0;
}
else
{
result = DNSServiceProcessResult(sdRef);
if ((result != 0) || (context.error != 0))
{
if (result == 0) result = context.error;
h_errno_for_dnssd_err(result, &h_errno);
wait = 0;
}
if ((ans->ancount > 0) && ((context.flags & kDNSServiceFlagsMoreComing) == 0)) wait = 0;
}
keep_waiting:
if (wait == 1)
{
gettimeofday(&ctv, NULL);
now.tv_sec = ctv.tv_sec;
now.tv_nsec = ctv.tv_usec * 1000;
timeout.tv_sec = finish.tv_sec - now.tv_sec;
if (finish.tv_nsec >= now.tv_nsec)
{
timeout.tv_nsec = finish.tv_nsec - now.tv_nsec;
}
else
{
timeout.tv_nsec = BILLION - now.tv_nsec + finish.tv_nsec;
timeout.tv_sec--;
}
}
}
DNSServiceRefDeallocate(sdRef);
close(kq);
if (ans->ancount == 0) context.anslen = -1;
if ((from != NULL) && (fromlen != NULL) && (context.ifnum != 0))
{
((struct sockaddr_in6 *)from)->sin6_len = sizeof(struct sockaddr_in6);
((struct sockaddr_in6 *)from)->sin6_family = AF_INET6;
((struct sockaddr_in6 *)from)->sin6_addr.__u6_addr.__u6_addr8[15] = 1;
((struct sockaddr_in6 *)from)->sin6_scope_id = context.ifnum;
*fromlen = sizeof(struct sockaddr_in6);
}
return context.anslen;
}
static int
res_soa_minimum(const u_char *msg, int len)
{
ns_msg handle;
const u_char *eom;
uint32_t i, b;
int min, soa_min;
eom = msg + len;
handle._msg = msg;
handle._eom = eom;
if (msg + NS_INT16SZ > eom) return -1;
NS_GET16(handle._id, msg);
if (msg + NS_INT16SZ > eom) return -1;
NS_GET16(handle._flags, msg);
for (i = 0; i < ns_s_max; i++)
{
if (msg + NS_INT16SZ > eom) return -1;
NS_GET16(handle._counts[i], msg);
}
if (handle._counts[ns_s_ns] == 0) return -1;
for (i = 0; i < ns_s_ns; i++)
{
if (handle._counts[i] == 0) handle._sections[i] = NULL;
else
{
b = ns_skiprr(msg, eom, (ns_sect)i, handle._counts[i]);
if (b < 0) return -1;
handle._sections[i] = msg;
msg += b;
}
}
min = -1;
for (i = 0; i < handle._counts[ns_s_ns]; i++)
{
b = ns_skiprr(msg, eom, ns_s_ns, 1);
if (b < 0) return -1;
memcpy(&soa_min, msg + b - sizeof(int32_t), sizeof(int32_t));
soa_min = ntohl(soa_min);
if ((i == 0) || (soa_min < min)) min = soa_min;
msg += b;
}
return min;
}
__private_extern__ int
res_nquery_soa_min(res_state statp, const char *name, int class, int type, u_char *answer, int anslen, struct sockaddr *from, int *fromlen, int *min)
{
u_char buf[MAXPACKET];
HEADER *hp = (HEADER *) answer;
int n;
u_int oflags;
if (min != NULL) *min = -1;
oflags = statp->_flags;
again:
__h_errno_set(statp, 0);
hp->rcode = ns_r_noerror;
#ifdef DEBUG
if (statp->options & RES_DEBUG) printf(";; res_query(%s, %d, %d)\n", name, class, type);
#endif
n = res_nmkquery(statp, ns_o_query, name, class, type, NULL, 0, NULL, buf, sizeof(buf));
#ifdef RES_USE_EDNS0
if (n > 0 && (statp->_flags & RES_F_EDNS0ERR) == 0 && (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0)
n = res_nopt(statp, n, buf, sizeof(buf), anslen);
#endif
if (n <= 0)
{
#ifdef DEBUG
if (statp->options & RES_DEBUG) printf(";; res_query: mkquery failed\n");
#endif
__h_errno_set(statp, NO_RECOVERY);
return (n);
}
n = res_nsend_2(statp, buf, n, answer, anslen, from, fromlen);
if (n < 0)
{
#ifdef RES_USE_EDNS0
if ((statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0 && ((oflags ^ statp->_flags) & RES_F_EDNS0ERR) != 0)
{
statp->_flags |= RES_F_EDNS0ERR;
if (statp->options & RES_DEBUG) printf(";; res_nquery: retry without EDNS0\n");
goto again;
}
#endif
#ifdef DEBUG
if (statp->options & RES_DEBUG) printf(";; res_query: send error\n");
#endif
__h_errno_set(statp, TRY_AGAIN);
return (n);
}
if ((hp->rcode == ns_r_nxdomain) || ((hp->rcode == ns_r_noerror) && (ntohs(hp->ancount) == 0)))
{
if (min != NULL)
{
*min = res_soa_minimum(answer, anslen);
if (statp->options & RES_DEBUG) printf(";; res_nquery: SOA minimum TTL = %d\n", *min);
}
}
if (hp->rcode != ns_r_noerror || ntohs(hp->ancount) == 0)
{
#ifdef DEBUG
if (statp->options & RES_DEBUG) printf(";; rcode = %d, ancount=%d\n", hp->rcode, ntohs(hp->ancount));
#endif
switch (hp->rcode)
{
case ns_r_nxdomain:
__h_errno_set(statp, HOST_NOT_FOUND);
break;
case ns_r_servfail:
__h_errno_set(statp, TRY_AGAIN);
break;
case ns_r_noerror:
__h_errno_set(statp, NO_DATA);
break;
case ns_r_formerr:
case ns_r_notimpl:
case ns_r_refused:
default:
__h_errno_set(statp, NO_RECOVERY);
break;
}
return (-1);
}
return (n);
}
int
res_nquery_2(res_state statp, const char *name, int class, int type, u_char *answer, int anslen, struct sockaddr *from, int *fromlen)
{
int unused = 0;
return res_nquery_soa_min(statp, name, class, type, answer, anslen, from, fromlen, &unused);
}
int
res_nquery(res_state statp, const char *name, int class, int type, u_char *answer, int anslen)
{
struct sockaddr_storage f;
int l;
l = sizeof(struct sockaddr_storage);
return res_nquery_2(statp, name, class, type, answer, anslen, (struct sockaddr *)&f, &l);
}
int
res_nquerydomain_2(res_state statp, const char *name, const char *domain, int class, int type, u_char *answer, int anslen, struct sockaddr *from, int *fromlen)
{
char nbuf[NS_MAXDNAME];
const char *longname = nbuf;
int n, d;
#ifdef DEBUG
if (statp->options & RES_DEBUG) printf(";; res_nquerydomain(%s, %s, %d, %d)\n", name, domain?domain:"<Nil>", class, type);
#endif
if (domain == NULL)
{
n = strlen(name);
if (n >= NS_MAXDNAME)
{
__h_errno_set(statp, NO_RECOVERY);
return (-1);
}
n--;
if (n >= 0 && name[n] == '.')
{
strncpy(nbuf, name, n);
nbuf[n] = '\0';
}
else
{
longname = name;
}
}
else
{
n = strlen(name);
d = strlen(domain);
if (n + d + 1 >= NS_MAXDNAME)
{
__h_errno_set(statp, NO_RECOVERY);
return (-1);
}
sprintf(nbuf, "%s.%s", name, domain);
}
return (res_nquery_2(statp, longname, class, type, answer, anslen, from, fromlen));
}
int
res_nquerydomain(res_state statp, const char *name, const char *domain, int class, int type, u_char *answer, int anslen)
{
struct sockaddr_storage f;
int l;
l = sizeof(struct sockaddr_storage);
return res_nquerydomain_2(statp, name, domain, class, type, answer, anslen, (struct sockaddr *)&f, &l);
}
int
res_nsearch_2(res_state statp, const char *name, int class, int type, u_char *answer, int anslen, struct sockaddr *from, int *fromlen)
{
const char *cp, * const *domain;
HEADER *hp = (HEADER *) answer;
char tmp[NS_MAXDNAME];
u_int dots;
int trailing_dot, ret, saved_herrno;
int got_nodata = 0, got_servfail = 0, root_on_list = 0;
int tried_as_is = 0;
int searched = 0;
errno = 0;
__h_errno_set(statp, HOST_NOT_FOUND);
dots = 0;
for (cp = name; *cp != '\0'; cp++) dots += (*cp == '.');
trailing_dot = 0;
if (cp > name && *--cp == '.') trailing_dot++;
if (!dots && (cp = res_hostalias(statp, name, tmp, sizeof tmp))!= NULL)
return (res_nquery(statp, cp, class, type, answer, anslen));
saved_herrno = -1;
if (dots >= statp->ndots || trailing_dot)
{
ret = res_nquerydomain_2(statp, name, NULL, class, type, answer, anslen, from, fromlen);
if (ret > 0 || trailing_dot) return (ret);
saved_herrno = h_errno;
tried_as_is++;
}
if ((!dots && (statp->options & RES_DEFNAMES) != 0) || (dots && !trailing_dot && (statp->options & RES_DNSRCH) != 0))
{
int done = 0;
for (domain = (const char * const *)statp->dnsrch; *domain && !done; domain++)
{
searched = 1;
if (domain[0][0] == '\0' || (domain[0][0] == '.' && domain[0][1] == '\0')) root_on_list++;
ret = res_nquerydomain_2(statp, name, *domain, class, type, answer, anslen, from, fromlen);
if (ret > 0) return (ret);
if (errno == ECONNREFUSED)
{
__h_errno_set(statp, TRY_AGAIN);
return (-1);
}
switch (statp->res_h_errno)
{
case NO_DATA:
got_nodata++;
case HOST_NOT_FOUND:
break;
case TRY_AGAIN:
if (hp->rcode == ns_r_refused)
{
got_servfail++;
break;
}
default:
done++;
}
if ((statp->options & RES_DNSRCH) == 0) done++;
}
}
if ((dots || !searched || (statp->options & RES_NOTLDQUERY) == 0) && !(tried_as_is || root_on_list))
{
ret = res_nquerydomain_2(statp, name, NULL, class, type, answer, anslen, from, fromlen);
if (ret > 0) return (ret);
}
if (saved_herrno != -1)
__h_errno_set(statp, saved_herrno);
else if (got_nodata)
__h_errno_set(statp, NO_DATA);
else if (got_servfail)
__h_errno_set(statp, TRY_AGAIN);
return (-1);
}
int
__res_nsearch_list_2(res_state statp, const char *name, int class, int type, u_char *answer, int anslen, struct sockaddr *from, int *fromlen, int nsearch, char **search)
{
const char *cp, *domain;
HEADER *hp = (HEADER *) answer;
char tmp[NS_MAXDNAME];
u_int dots;
int trailing_dot, ret, saved_herrno, i;
int got_nodata = 0, got_servfail = 0, root_on_list = 0;
int tried_as_is = 0;
int searched = 0;
errno = 0;
__h_errno_set(statp, HOST_NOT_FOUND);
dots = 0;
for (cp = name; *cp != '\0'; cp++) dots += (*cp == '.');
trailing_dot = 0;
if (cp > name && *--cp == '.') trailing_dot++;
if (!dots && (cp = res_hostalias(statp, name, tmp, sizeof tmp)) != NULL)
return (res_nquery(statp, cp, class, type, answer, anslen));
saved_herrno = -1;
if (dots >= statp->ndots || trailing_dot)
{
ret = res_nquerydomain(statp, name, NULL, class, type, answer, anslen);
if (ret > 0 || trailing_dot) return ret;
saved_herrno = h_errno;
tried_as_is++;
}
if ((!dots && (statp->options & RES_DEFNAMES) != 0) || (dots && !trailing_dot && (statp->options & RES_DNSRCH) != 0))
{
int done = 0;
for (i = 0; i < nsearch; i++)
{
domain = search[i];
searched = 1;
if (domain[0] == '\0' || (domain[0] == '.' && domain[1] == '\0')) root_on_list++;
ret = res_nquerydomain_2(statp, name, domain, class, type, answer, anslen, from, fromlen);
if (ret > 0) return ret;
if (errno == ECONNREFUSED)
{
__h_errno_set(statp, TRY_AGAIN);
return -1;
}
switch (statp->res_h_errno)
{
case NO_DATA:
got_nodata++;
case HOST_NOT_FOUND:
break;
case TRY_AGAIN:
if (hp->rcode == ns_r_refused)
{
got_servfail++;
break;
}
default:
done++;
}
if ((statp->options & RES_DNSRCH) == 0) done++;
}
}
if ((dots || !searched || (statp->options & RES_NOTLDQUERY) == 0) && !(tried_as_is || root_on_list))
{
ret = res_nquerydomain_2(statp, name, NULL, class, type, answer, anslen, from, fromlen);
if (ret > 0) return ret;
}
if (saved_herrno != -1) __h_errno_set(statp, saved_herrno);
else if (got_nodata) __h_errno_set(statp, NO_DATA);
else if (got_servfail) __h_errno_set(statp, TRY_AGAIN);
return -1;
}
int
res_nsearch(res_state statp, const char *name, int class, int type, u_char *answer, int anslen)
{
struct sockaddr_storage f;
int l;
l = sizeof(struct sockaddr_storage);
return res_nsearch_2(statp, name, class, type, answer, anslen, (struct sockaddr *)&f, &l);
}
const char *
res_hostalias(const res_state statp, const char *name, char *dst, size_t siz)
{
char *file, *cp1, *cp2;
char buf[BUFSIZ];
FILE *fp;
if (statp->options & RES_NOALIASES) return (NULL);
file = getenv("HOSTALIASES");
if (file == NULL || (fp = fopen(file, "r")) == NULL) return (NULL);
setbuf(fp, NULL);
buf[sizeof(buf) - 1] = '\0';
while (fgets(buf, sizeof(buf), fp))
{
for (cp1 = buf; *cp1 && !isspace((unsigned char)*cp1); ++cp1) ;
if (!*cp1) break;
*cp1 = '\0';
if (ns_samename(buf, name) == 1)
{
while (isspace((unsigned char)*++cp1)) ;
if (!*cp1) break;
for (cp2 = cp1 + 1; *cp2 && !isspace((unsigned char)*cp2); ++cp2) ;
*cp2 = '\0';
strncpy(dst, cp1, siz - 1);
dst[siz - 1] = '\0';
fclose(fp);
return (dst);
}
}
fclose(fp);
return (NULL);
}