tor-android/tor-android-binary/src/main/jni/pdnsd/src/dns_query.c

3799 lines
113 KiB
C

/* dns_query.c - Execute outgoing dns queries and write entries to cache
Copyright (C) 2000, 2001 Thomas Moestl
Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011, 2012 Paul A. Rombouts
This file is part of the pdnsd package.
pdnsd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
pdnsd is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pdnsd; see the file COPYING. If not, see
<http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <sys/types.h>
#ifdef HAVE_SYS_POLL_H
#include <sys/poll.h>
#endif
#include <stdlib.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include "list.h"
#include "consts.h"
#include "ipvers.h"
#include "dns_query.h"
#include "cache.h"
#include "dns.h"
#include "conff.h"
#include "servers.h"
#include "helpers.h"
#include "netdev.h"
#include "error.h"
#include "debug.h"
#if defined(NO_TCP_QUERIES) && M_PRESET!=UDP_ONLY
# error "You may not define NO_TCP_QUERIES when M_PRESET is not set to UDP_ONLY"
#endif
#if defined(NO_UDP_QUERIES) && M_PRESET!=TCP_ONLY
# error "You may not define NO_UDP_QUERIES when M_PRESET is not set to TCP_ONLY"
#endif
/* data type to hold lists of IP addresses (both v4 and v6)
The allocated size should be:
sizeof(rejectlist_t) + na4*sizeof(addr4maskpair_t) + na6*sizeof(addr6maskpair_t)
*/
typedef struct rejectlist_s {
struct rejectlist_s *next;
short policy;
short inherit;
int na4;
#if ALLOW_LOCAL_AAAA
int na6;
addr6maskpair_t rdata[0]; /* dummy array for alignment */
#else
addr4maskpair_t rdata[0];
#endif
} rejectlist_t;
/* --- structures and state constants for parallel query */
typedef struct {
union {
#ifdef ENABLE_IPV4
struct sockaddr_in sin4;
#endif
#ifdef ENABLE_IPV6
struct sockaddr_in6 sin6;
#endif
} a;
#ifdef ENABLE_IPV6
struct in_addr a4fallback;
#endif
time_t timeout;
unsigned short flags;
short state;
short qm;
char nocache;
char auth_serv;
char lean_query;
char edns_query;
char needs_testing;
char trusted;
char aa;
char tc;
char ra;
char failed;
const unsigned char *nsdomain;
rejectlist_t *rejectlist;
/* internal state for p_exec_query */
int sock;
#if 0
dns_cent_t nent;
dns_cent_t servent;
#endif
unsigned short transl;
unsigned short recvl;
#ifndef NO_TCP_QUERIES
int iolen; /* number of bytes written or read up to now */
#endif
dns_msg_t *msg;
dns_hdr_t *recvbuf;
unsigned short myrid;
int s_errno;
} query_stat_t;
typedef DYNAMIC_ARRAY(query_stat_t) *query_stat_array;
/* Some macros for handling data in reject lists
Perhaps we should use inline functions instead of macros.
*/
#define have_rejectlist(st) ((st)->rejectlist!=NULL)
#define inherit_rejectlist(st) ((st)->rejectlist && (st)->rejectlist->inherit)
#define reject_policy(st) ((st)->rejectlist->policy)
#define nreject_a4(st) ((st)->rejectlist->na4)
#if ALLOW_LOCAL_AAAA
#define nreject_a6(st) ((st)->rejectlist->na6)
#define rejectlist_a6(st) ((addr6maskpair_t *)(st)->rejectlist->rdata)
#define rejectlist_a4(st) ((addr4maskpair_t *)(rejectlist_a6(st)+nreject_a6(st)))
#else
#define rejectlist_a4(st) ((addr4maskpair_t *)(st)->rejectlist->rdata)
#endif
#define QS_INITIAL 0 /* This is the initial state. Set this before starting. */
#define QS_TCPINITIAL 1 /* Start a TCP query. */
#define QS_TCPWRITE 2 /* Waiting to write data. */
#define QS_TCPREAD 3 /* Waiting to read data. */
#define QS_UDPINITIAL 4 /* Start a UDP query */
#define QS_UDPRECEIVE 5 /* UDP query transmitted, waiting for response. */
#define QS_QUERY_CASES case QS_TCPINITIAL: case QS_TCPWRITE: case QS_TCPREAD: case QS_UDPINITIAL: case QS_UDPRECEIVE
#define QS_CANCELED 7 /* query was started, but canceled before completion */
#define QS_DONE 8 /* done, resources freed, result is in stat_t */
/* Events to be polled/selected for */
#define QS_WRITE_CASES case QS_TCPWRITE
#define QS_READ_CASES case QS_TCPREAD: case QS_UDPRECEIVE
/*
* This is for error handling to prevent spewing the log files.
* Races do not really matter here, so no locks.
*/
#define MAXPOLLERRS 10
static volatile unsigned long poll_errs=0;
#define SOCK_ADDR(p) ((struct sockaddr *) &(p)->a)
#ifdef SIN_LEN
#undef SIN_LEN
#endif
#define SIN_LEN SEL_IPVER(sizeof(struct sockaddr_in),sizeof(struct sockaddr_in6))
#define PDNSD_A(p) SEL_IPVER(((pdnsd_a *) &(p)->a.sin4.sin_addr),((pdnsd_a *) &(p)->a.sin6.sin6_addr))
#ifndef EWOULDBLOCK
#define EWOULDBLOCK EAGAIN
#endif
typedef DYNAMIC_ARRAY(dns_cent_t) *dns_cent_array;
/*
* Take the data from an RR and add it to an array of cache entries.
* The return value will be RC_OK in case of success,
* RC_SERVFAIL in case there is a problem with inconsistent ttl timestamps
* or RC_FATALERR in case of a memory allocation failure.
*/
static int rr_to_cache(dns_cent_array *centa, unsigned char *oname, int tp, time_t ttl,
unsigned dlen, void *data, unsigned flags, time_t queryts)
{
int i,n;
dns_cent_t *cent;
n=DA_NEL(*centa);
for(i=0;i<n;++i) {
cent=&DA_INDEX(*centa,i);
if (rhnicmp(cent->qname,oname)) {
int retval=RC_OK;
/* We already have an entry in the array for this name. add_cent_rr is sufficient.
However, make sure there are no double records. This is done by add_cent_rr */
#ifdef RFC2181_ME_HARDER
rr_set_t *rrset= getrrset(cent,tp);
if (rrset && rrset->ttl!=ttl)
retval= RC_SERVFAIL;
#endif
return add_cent_rr(cent,tp,ttl,queryts,flags,dlen,data DBG1)? retval: RC_FATALERR;
}
}
/* Add a new entry to the array for this name. */
if (!(*centa=DA_GROW1_F(*centa,free_cent0)))
return RC_FATALERR;
cent=&DA_LAST(*centa);
if (!init_cent(cent,oname, 0, 0, 0 DBG1)) {
*centa=DA_RESIZE(*centa,n);
return RC_FATALERR;
}
return add_cent_rr(cent,tp,ttl,queryts,flags,dlen,data DBG1)? RC_OK: RC_FATALERR;
}
/*
* Takes a pointer (ptr) to a buffer with recnum rrs,decodes them and enters
* them into an array of cache entries. *ptr is modified to point after the last
* rr, and *lcnt is decremented by the size of the rrs.
*
* *numopt is incremented with the number of OPT pseudo RRs found (should be at most one).
* The structure pointed to by ep is filled with the information of the first OPT pseudo RR found,
* but only if *numopt was set to zero before the call.
*
* The return value will be either RC_OK (which indicates success),
* or one of the failure codes RC_FORMAT, RC_TRUNC, RC_SERVFAIL or RC_FATALERR
* (the latter indicates a memory allocation failure).
*/
static int rrs2cent(unsigned char *msg, size_t msgsz, unsigned char **ptr, size_t *lcnt, int recnum,
unsigned flags, time_t queryts, dns_cent_array *centa, int *numopt, edns_info_t *ep)
{
int rc, retval=RC_OK;
int i;
uint16_t type,class; uint32_t ttl; uint16_t rdlength;
for (i=0;i<recnum;i++) {
unsigned char oname[DNSNAMEBUFSIZE], *ttlp;
unsigned int len;
if ((rc=decompress_name(msg, msgsz, ptr, lcnt, oname, &len))!=RC_OK) {
return rc;
}
if (*lcnt<sizeof_rr_hdr_t) {
return RC_TRUNC;
}
*lcnt -= sizeof_rr_hdr_t;
GETINT16(type,*ptr);
GETINT16(class,*ptr);
ttlp= *ptr; /* Remember pointer to ttl field. */
GETINT32(ttl,*ptr);
GETINT16(rdlength,*ptr);
if (*lcnt<rdlength) {
return RC_TRUNC;
}
if(type==T_OPT) {
/* Found OPT pseudo-RR */
if((*numopt)++ == 0) {
#if DEBUG>0
if(oname[0]!=0) {
DEBUG_MSG("rrs2cent: name in OPT record not empty!\n");
}
#endif
ep->udpsize= class;
ep->rcode= ((uint16_t)ttlp[0]<<4) | ((dns_hdr_t *)msg)->rcode;
ep->version= ttlp[1];
ep->do_flg= (ttlp[2]>>7)&1;
#if DEBUG>0
if(debug_p) {
unsigned int Zflags= ((uint16_t)ttlp[2]<<8) | ttlp[3];
if(Zflags & 0x7fff) {
DEBUG_MSG("rrs2cent: Z field contains unknown nonzero bits (%04x).\n",
Zflags);
}
}
if(rdlength) {
DEBUG_MSG("rrs2cent: RDATA field in OPT record not empty!\n");
}
#endif
}
else {
DEBUG_MSG("rrs2cent: ingnoring surplus OPT record.\n");
}
}
else if (!(PDNSD_NOT_CACHED_TYPE(type) || class!=C_IN)) {
/* Some types contain names that may be compressed, so these need to be processed.
* The other records are taken as they are. */
size_t blcnt=rdlength;
unsigned char *bptr=*ptr; /* make backup for decompression, because rdlength is the
authoritative record length and pointer and size will be
modified by decompress_name. */
unsigned char *nptr;
unsigned int slen;
switch (type) {
case T_A:
/* Validate types we use internally */
if(rdlength!=4) goto invalid_length;
goto default_case;
case T_CNAME:
case T_MB:
case T_MD:
case T_MF:
case T_MG:
case T_MR:
case T_NS:
case T_PTR:
{
unsigned char db[DNSNAMEBUFSIZE];
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, db, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
if (blcnt!=0)
goto trailing_junk;
if ((rc=rr_to_cache(centa, oname, type, ttl, len, db, flags,queryts))!=RC_OK) {
if(rc==RC_FATALERR)
return rc;
retval=rc;
}
}
break;
#if IS_CACHED_MINFO || IS_CACHED_RP
#if IS_CACHED_MINFO
case T_MINFO:
#endif
#if IS_CACHED_RP
case T_RP:
#endif
{
unsigned char db[DNSNAMEBUFSIZE+DNSNAMEBUFSIZE];
nptr=db;
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nptr, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
/* PDNSD_ASSERT(len + DNSNAMEBUFSIZE <= sizeof(db), "T_MINFO/T_RP: buffer limit reached"); */
nptr+=len;
slen=len;
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nptr, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
/*nptr+=len;*/
slen+=len;
if (blcnt!=0)
goto trailing_junk;
if ((rc=rr_to_cache(centa, oname, type, ttl, slen, db, flags,queryts))!=RC_OK) {
if(rc==RC_FATALERR)
return rc;
retval=rc;
}
}
break;
#endif
case T_MX:
#if IS_CACHED_AFSDB
case T_AFSDB:
#endif
#if IS_CACHED_RT
case T_RT:
#endif
#if IS_CACHED_KX
case T_KX:
#endif
{
unsigned char db[2+DNSNAMEBUFSIZE];
if (blcnt<2)
goto record_too_short;
memcpy(db,bptr,2); /* copy the preference field*/
blcnt-=2;
bptr+=2;
nptr=db+2;
slen=2;
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nptr, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
/*nptr+=len;*/
slen+=len;
if (blcnt!=0)
goto trailing_junk;
if ((rc=rr_to_cache(centa, oname, type, ttl, slen, db, flags,queryts))!=RC_OK) {
if(rc==RC_FATALERR)
return rc;
retval=rc;
}
}
break;
case T_SOA:
{
unsigned char db[DNSNAMEBUFSIZE+DNSNAMEBUFSIZE+20];
nptr=db;
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nptr, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
/* PDNSD_ASSERT(len + DNSNAMEBUFSIZE <= sizeof(db), "T_SOA: buffer limit reached"); */
nptr+=len;
slen=len;
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nptr, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
nptr+=len;
slen+=len;
/* PDNSD_ASSERT(slen + 20 <= sizeof(db), "T_SOA: buffer limit reached"); */
if (blcnt<20)
goto record_too_short;
memcpy(nptr,bptr,20); /*copy the rest of the SOA record*/
blcnt-=20;
slen+=20;
if (blcnt!=0)
goto trailing_junk;
if ((rc=rr_to_cache(centa, oname, type, ttl, slen, db, flags,queryts))!=RC_OK) {
if(rc==RC_FATALERR)
return rc;
retval=rc;
}
}
break;
#if IS_CACHED_AAAA
case T_AAAA:
/* Validate types we use internally */
if(rdlength!=16) goto invalid_length;
goto default_case;
#endif
#if IS_CACHED_PX
case T_PX:
{
unsigned char db[2+DNSNAMEBUFSIZE+DNSNAMEBUFSIZE];
if (blcnt<2)
goto record_too_short;
memcpy(db,bptr,2); /* copy the preference field*/
blcnt-=2;
bptr+=2;
nptr=db+2;
slen=2;
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nptr, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
/* PDNSD_ASSERT(len + DNSNAMEBUFSIZE <= sizeof(db), "T_PX: buffer limit reached"); */
nptr+=len;
slen+=len;
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nptr, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
/* nptr+=len; */
slen+=len;
if (blcnt!=0)
goto trailing_junk;
if ((rc=rr_to_cache(centa, oname, type, ttl, slen, db, flags,queryts))!=RC_OK) {
if(rc==RC_FATALERR)
return rc;
retval=rc;
}
}
break;
#endif
#if IS_CACHED_SRV
case T_SRV:
{
unsigned char db[6+DNSNAMEBUFSIZE];
if (blcnt<6)
goto record_too_short;
memcpy(db,bptr,6);
blcnt-=6;
bptr+=6;
nptr=db+6;
slen=6;
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nptr, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
/*nptr+=len;*/
slen+=len;
if (blcnt!=0)
goto trailing_junk;
if ((rc=rr_to_cache(centa, oname, type, ttl, slen, db, flags,queryts))!=RC_OK) {
if(rc==RC_FATALERR)
return rc;
retval=rc;
}
}
break;
#endif
#if IS_CACHED_NXT
case T_NXT:
{
unsigned char db[1040];
nptr=db;
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nptr, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
nptr+=len;
slen=len+blcnt;
if (slen > sizeof(db))
goto buffer_overflow;
memcpy(nptr,bptr,blcnt);
if ((rc=rr_to_cache(centa, oname, type, ttl, slen, db, flags,queryts))!=RC_OK) {
if(rc==RC_FATALERR)
return rc;
retval=rc;
}
}
break;
#endif
#if IS_CACHED_NAPTR
case T_NAPTR:
{
int j;
unsigned char db[4 + 3*256 + DNSNAMEBUFSIZE];
nptr=db;
/*
* After the preference field, three text strings follow, the maximum length being 255
* characters for each (this is ensured by the type of *bptr), plus one length byte for
* each, so 3 * 256 = 786 in total. In addition, the name below is up to DNSNAMEBUFSIZE characters
* in size, and the preference field is another 4 bytes in size, so the total length
* that can be taken up is 1028 characters. This means that the whole record will always
* fit into db.
*/
len=4; /* also copy the preference field*/
for (j=0;j<3;j++) {
if (len>=blcnt)
goto record_too_short;
len += ((unsigned)bptr[len])+1;
}
if(len>blcnt)
goto record_too_short;
memcpy(nptr,bptr,len);
blcnt-=len;
bptr+=len;
nptr+=len;
slen=len;
/* PDNSD_ASSERT(slen+DNSNAMEBUFSIZE <= sizeof(db), "T_NAPTR: buffer limit reached (name)"); */
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nptr, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
/*nptr+=len;*/
slen+=len;
if (blcnt!=0)
goto trailing_junk;
if ((rc=rr_to_cache(centa, oname, type, ttl, slen, db, flags,queryts))!=RC_OK) {
if(rc==RC_FATALERR)
return rc;
retval=rc;
}
}
break;
#endif
#if IS_CACHED_IPSECKEY
case T_IPSECKEY:
{
unsigned gwtp;
/* An IPSECKEY record can contain a domain name, so we do some sanity checks just to be sure. */
if(blcnt<3) goto record_too_short;
gwtp= bptr[1];
blcnt -= 3;
bptr += 3;
switch(gwtp) {
case 0: goto default_case;
case 1: /* There should be enough room for IPv4 address. */
if(blcnt<4) goto record_too_short;
goto default_case;
case 2: /* There should be enough room for IPv6 address. */
if(blcnt<16) goto record_too_short;
goto default_case;
case 3: /* Check that domain name is not compressed. */
if(isnormalencdomname(bptr,blcnt)) goto default_case;
/* It appears the name is compressed even though RFC 4025
says it shouldn't be. For the sake of flexibility, we
try to decompress it anyway. */
{
unsigned char *rbuf, nmbuf[DNSNAMEBUFSIZE];
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nmbuf, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
slen=3+len+blcnt;
rbuf=malloc(slen);
if(!rbuf) return RC_FATALERR;
nptr=mempcpy(rbuf,*ptr,3);
nptr=mempcpy(nptr,nmbuf,len);
memcpy(nptr,bptr,blcnt);
rc=rr_to_cache(centa, oname, type, ttl, slen, rbuf, flags,queryts);
free(rbuf);
if(rc!=RC_OK) {
if(rc==RC_FATALERR)
return rc;
retval=rc;
}
}
break;
default:
DEBUG_MSG("rrs2cent: %s record contains unsupported gateway type (%u).\n",getrrtpname(type),gwtp);
return RC_FORMAT;
}
}
break;
#endif
#if IS_CACHED_RRSIG
case T_RRSIG:
/* An RRSIG record contains a domain name, so we do some sanity checks just to be sure. */
if(blcnt<18) goto record_too_short;
blcnt -= 18;
bptr += 18;
if(isnormalencdomname(bptr,blcnt)) goto default_case;
/* It appears the name is compressed even though RFC 4034
says it shouldn't be. For the sake of flexibility, we
try to decompress it anyway. */
{
unsigned char *rbuf, nmbuf[DNSNAMEBUFSIZE];
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nmbuf, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
slen=18+len+blcnt;
rbuf=malloc(slen);
if(!rbuf) return RC_FATALERR;
nptr=mempcpy(rbuf,*ptr,18);
nptr=mempcpy(nptr,nmbuf,len);
memcpy(nptr,bptr,blcnt);
rc=rr_to_cache(centa, oname, type, ttl, slen, rbuf, flags,queryts);
free(rbuf);
if(rc!=RC_OK) {
if(rc==RC_FATALERR)
return rc;
retval=rc;
}
}
break;
#endif
#if IS_CACHED_NSEC
case T_NSEC:
/* An NSEC record contains a domain name, so we do some sanity checks just to be sure. */
if(isnormalencdomname(bptr,blcnt)) goto default_case;
/* It appears the name is compressed even though RFC 4034
says it shouldn't be. For the sake of flexibility, we
try to decompress it anyway. */
{
unsigned char *rbuf, nmbuf[DNSNAMEBUFSIZE];
if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nmbuf, &len))!=RC_OK)
return rc==RC_TRUNC?RC_FORMAT:rc;
slen=len+blcnt;
rbuf=malloc(slen);
if(!rbuf) return RC_FATALERR;
nptr=mempcpy(rbuf,nmbuf,len);
memcpy(nptr,bptr,blcnt);
rc=rr_to_cache(centa, oname, type, ttl, slen, rbuf, flags,queryts);
free(rbuf);
if(rc!=RC_OK) {
if(rc==RC_FATALERR)
return rc;
retval=rc;
}
}
break;
#endif
default:
default_case:
if ((rc=rr_to_cache(centa, oname, type, ttl, rdlength, *ptr, flags,queryts))!=RC_OK) {
if(rc==RC_FATALERR)
return rc;
retval=rc;
}
}
}
else {
/* skip otherwise */
DEBUG_MSG("rrs2cent: ignoring record of type %s (%d), class %s (%d).\n",
getrrtpname(type), type,
class==C_IN?"IN":"[unknown]", class);
}
*lcnt -= rdlength;
*ptr += rdlength;
}
return retval;
trailing_junk:
DEBUG_MSG("rrs2cent: %s record has trailing junk.\n",getrrtpname(type));
return RC_FORMAT;
record_too_short:
DEBUG_MSG("rrs2cent: %s record too short.\n",getrrtpname(type));
return RC_FORMAT;
buffer_overflow:
DEBUG_MSG("rrs2cent: buffer too small to process %s record.\n",getrrtpname(type));
return RC_FORMAT;
invalid_length:
DEBUG_MSG("rrs2cent: %s record has length %u.\n",getrrtpname(type),rdlength);
return RC_FORMAT;
}
/*
* Try to bind the socket to a port in the given port range. Returns 1 on success, or 0 on failure.
*/
static int bind_socket(int s)
{
int query_port_start=global.query_port_start,query_port_end=global.query_port_end;
/*
* -1, as a special value for query_port_start, denotes that we let the kernel select
* a port when we first use the socket, which used to be the default.
*/
if (query_port_start >= 0) {
union {
#ifdef ENABLE_IPV4
struct sockaddr_in sin4;
#endif
#ifdef ENABLE_IPV6
struct sockaddr_in6 sin6;
#endif
} sin;
socklen_t sinl;
int prt, pstart, range = query_port_end-query_port_start+1, m=0xffff;
unsigned try1,try2, maxtry2;
if (range<=0 || range>0x10000) {
log_warn("Illegal port range in %s line %d, dropping query!\n",__FILE__,__LINE__);
return 0;
}
if(range<=0x8000) {
/* Find the smallest power of 2 >= range. */
for(m=1; m<range; m <<= 1);
/* Convert into a bit mask. */
--m;
}
for (try2=0,maxtry2=range*2;;) {
/* Get a random number < range, by rejecting those >= range. */
for(try1=0;;) {
prt= get_rand16()&m;
if(prt<range) break;
if(++try1>=0x10000) {
log_warn("Cannot get random number < range"
" after %d tries in %s line %d,"
" bad random number generator?\n",
try1,__FILE__,__LINE__);
return 0;
}
}
prt += query_port_start;
for(pstart=prt;;) {
#ifdef ENABLE_IPV4
if (run_ipv4) {
memset(&sin.sin4,0,sizeof(struct sockaddr_in));
sin.sin4.sin_family=AF_INET;
sin.sin4.sin_port=htons(prt);
sin.sin4.sin_addr=global.out_a.ipv4;
SET_SOCKA_LEN4(sin.sin4);
sinl=sizeof(struct sockaddr_in);
}
#endif
#ifdef ENABLE_IPV6
ELSE_IPV6 {
memset(&sin.sin6,0,sizeof(struct sockaddr_in6));
sin.sin6.sin6_family=AF_INET6;
sin.sin6.sin6_port=htons(prt);
sin.sin6.sin6_flowinfo=IPV6_FLOWINFO;
sin.sin6.sin6_addr=global.out_a.ipv6;
SET_SOCKA_LEN6(sin.sin6);
sinl=sizeof(struct sockaddr_in6);
}
#endif
if (bind(s,(struct sockaddr *)&sin,sinl)==-1) {
if (errno!=EADDRINUSE &&
errno!=EADDRNOTAVAIL) { /* EADDRNOTAVAIL should not happen here... */
log_warn("Could not bind to socket: %s\n", strerror(errno));
return 0;
}
/* If the address is in use, we continue. */
} else
goto done;
if(++try2>=maxtry2) {
/* It is possible we missed the free ports by chance,
try scanning the whole range. */
if (++prt>query_port_end)
prt=query_port_start;
if (prt==pstart) {
/* Wrapped around, scanned the whole range. Give up. */
log_warn("Out of ports in the range"
" %d-%d, dropping query!\n",
query_port_start,query_port_end);
return 0;
}
}
else /* Try new random number */
break;
}
}
}
done:
return 1;
}
inline static void *realloc_or_cleanup(void *ptr,size_t size)
{
void *retval=pdnsd_realloc(ptr,size);
if(!retval)
pdnsd_free(ptr);
return retval;
}
#if defined(NO_TCP_QUERIES)
# define USE_UDP(st) 1
#elif defined(NO_UDP_QUERIES)
# define USE_UDP(st) 0
#else /* !defined(NO_TCP_QUERIES) && !defined(NO_UDP_QUERIES) */
# define USE_UDP(st) ((st)->qm==UDP_ONLY || (st)->qm==UDP_TCP)
/* These functions will be used in case a TCP query might fail and we want to try again using UDP. */
# define tentative_tcp_query(st) ((st)->qm==TCP_UDP && ((st)->state==QS_TCPWRITE || ((st)->state==QS_TCPREAD && (st)->iolen==0)))
inline static void switch_to_udp(query_stat_t *st)
{
st->qm=UDP_ONLY;
st->myrid=get_rand16();
st->msg->hdr.id=htons(st->myrid);
st->state=QS_UDPINITIAL;
/* st->failed=0; */
}
/* This function will be used in case a UDP reply was truncated and we want to try again using TCP. */
inline static void switch_to_tcp(query_stat_t *st)
{
/* PDNSD_ASSERT(st->state==QS_INITIAL || st->state==QS_DONE || st->state==QS_CANCELED,
"Attempt to switch to TCP while a query is in progress."); */
st->qm=TCP_ONLY;
st->state=QS_INITIAL;
st->failed=0;
}
#endif
/* ------ following is the parallel query code.
* It has been observed that a whole lot of name servers are just damn lame, with response time
* of about 1 min. If that slow one is by chance the first server we try, serializing the tries is quite
* sub-optimal. Also when doing serial queries, the timeout values given in the config will add up, which
* is not the Right Thing. Now that serial queries are in place, this is still true for CNAME recursion,
* and for recursion in quest for the holy AA, but not totally for querying multiple servers.
* The impact on network bandwith should be only marginal (given todays bandwith).
*
* The actual strategy is to do (max) PAR_QUERIES parallel queries, and, if these time out or fail, do again
* that number of queries, until we are successful or there are no more servers to query.
* Since the memory footprint of a thread is considerably large on some systems, and because we have better
* control, we will do the parallel queries multiplexed in one thread.
*/
/* The query state machine that is called from p_exec_query. This is called once for initialization (state
* QS_TCPINITIAL or QS_UDPINITIAL is preset), and the state that it gives back may either be state QS_DONE,
* in which case it must return a return code other than -1 and is called no more for this server
* (except perhaps in UDP mode if TCP failed). If p_query_sm returns -1, then the state machine is in a read
* or write state, and a function higher up the calling chain can setup a poll() or select() together with st->sock.
* If that poll/select is succesful for that socket, p_exec_query is called again and will hand over to p_query_sm.
* So, you can assume that read(), write() and recvfrom() will not block at the start of a state handling when you
* have returned -1 (which means "call again") as last step of the last state handling. */
static int p_query_sm(query_stat_t *st)
{
int retval=RC_SERVFAIL,rv;
#if !defined(NO_TCP_QUERIES) && !defined(NO_UDP_QUERIES)
tryagain:
#endif
switch (st->state){
/* TCP query code */
#ifndef NO_TCP_QUERIES
case QS_TCPINITIAL:
if ((st->sock=socket(PDNSD_PF_INET,SOCK_STREAM,IPPROTO_TCP))==-1) {
DEBUG_MSG("Could not open socket: %s\n", strerror(errno));
break;
}
/* sin4 or sin6 is intialized, hopefully. */
/* maybe bind */
if (!bind_socket(st->sock)) {
close(st->sock);
break;
}
/* transmit query by tcp*/
/* make the socket non-blocking */
{
int oldflags = fcntl(st->sock, F_GETFL, 0);
if (oldflags == -1 || fcntl(st->sock,F_SETFL,oldflags|O_NONBLOCK)==-1) {
DEBUG_PDNSDA_MSG("fcntl error while trying to make socket to %s non-blocking: %s\n", PDNSDA2STR(PDNSD_A(st)),strerror(errno));
close(st->sock);
break;
}
}
st->iolen=0;
#ifdef ENABLE_IPV6
retry_tcp_connect:
#endif
if (connect(st->sock,SOCK_ADDR(st),SIN_LEN)==-1) {
if (errno==EINPROGRESS || errno==EPIPE) {
st->state=QS_TCPWRITE;
/* st->event=QEV_WRITE; */ /* wait for writability; the connect is then done */
return -1;
} else if (errno==ECONNREFUSED) {
st->s_errno=errno;
DEBUG_PDNSDA_MSG("TCP connection refused by %s\n", PDNSDA2STR(PDNSD_A(st)));
close(st->sock);
goto tcp_failed; /* We may want to try again using UDP */
} else {
/* Since immediate connect() errors do not cost any time, we do not try to switch the
* server status to offline */
#ifdef ENABLE_IPV6
/* if IPv6 connectivity is for some reason unavailable, perhaps the
IPv4 fallback address can still be reached. */
if(!run_ipv4 && (errno==ENETUNREACH || errno==ENETDOWN)
&& st->a4fallback.s_addr!=INADDR_ANY)
{
#if DEBUG>0
char abuf[ADDRSTR_MAXLEN];
DEBUG_PDNSDA_MSG("Connecting to %s failed: %s, retrying with IPv4 address %s\n",
PDNSDA2STR(PDNSD_A(st)),strerror(errno),
inet_ntop(AF_INET,&st->a4fallback,abuf,sizeof(abuf)));
#endif
IPV6_MAPIPV4(&st->a4fallback,&st->a.sin6.sin6_addr);
st->a4fallback.s_addr=INADDR_ANY;
goto retry_tcp_connect;
}
#endif
DEBUG_PDNSDA_MSG("Error while connecting to %s: %s\n", PDNSDA2STR(PDNSD_A(st)),strerror(errno));
close(st->sock);
break;
}
}
st->state=QS_TCPWRITE;
/* st->event=QEV_WRITE; */
/* fall through in case of not EINPROGRESS */
case QS_TCPWRITE:
{
int rem= dnsmsghdroffset + st->transl - st->iolen;
if(rem>0) {
rv=write(st->sock,((unsigned char*)st->msg)+st->iolen,rem);
if(rv==-1) {
if(errno==EWOULDBLOCK)
return -1;
st->s_errno=errno;
close(st->sock);
if (st->iolen==0 &&
(st->s_errno==ECONNREFUSED || st->s_errno==ECONNRESET ||
st->s_errno==EPIPE))
{
/* This error may be delayed from connect() */
DEBUG_PDNSDA_MSG("TCP connection to %s failed: %s\n", PDNSDA2STR(PDNSD_A(st)),strerror(st->s_errno));
goto tcp_failed; /* We may want to try again using UDP */
}
DEBUG_PDNSDA_MSG("Error while sending data to %s: %s\n", PDNSDA2STR(PDNSD_A(st)),strerror(st->s_errno));
break;
}
st->iolen += rv;
if(rv<rem)
return -1;
}
}
st->state=QS_TCPREAD;
st->iolen=0;
/* st->event=QEV_READ; */
/* fall through */
case QS_TCPREAD:
if(st->iolen==0) {
uint16_t recvl_net;
rv=read(st->sock,&recvl_net,sizeof(recvl_net));
if(rv==-1 && errno==EWOULDBLOCK)
return -1;
if(rv!=sizeof(recvl_net))
goto error_receiv_data;
st->iolen=rv;
st->recvl=ntohs(recvl_net);
if(!(st->recvbuf=(dns_hdr_t *)realloc_or_cleanup(st->recvbuf,st->recvl))) {
close(st->sock);
DEBUG_MSG("Out of memory in query.\n");
retval=RC_FATALERR;
break;
}
}
{
int offset=st->iolen-sizeof(uint16_t);
int rem=st->recvl-offset;
if(rem>0) {
rv=read(st->sock,((unsigned char*)st->recvbuf)+offset,rem);
if(rv==-1) {
if(errno==EWOULDBLOCK)
return -1;
goto error_receiv_data;
}
if(rv==0)
goto error_receiv_data; /* unexpected EOF */
st->iolen += rv;
if(rv<rem)
return -1;
}
}
close(st->sock);
st->state=QS_DONE;
return RC_OK;
error_receiv_data:
if(rv==-1) st->s_errno=errno;
DEBUG_PDNSDA_MSG("Error while receiving data from %s: %s\n", PDNSDA2STR(PDNSD_A(st)),
rv==-1?strerror(errno):(rv==0 && st->iolen==0)?"no data":"incomplete data");
close(st->sock);
tcp_failed:
#if !defined(NO_TCP_QUERIES) && !defined(NO_UDP_QUERIES)
if(st->qm==TCP_UDP) {
switch_to_udp(st);
DEBUG_PDNSDA_MSG("TCP query to %s failed. Trying to use UDP.\n", PDNSDA2STR(PDNSD_A(st)));
goto tryagain;
}
#endif
break;
#endif
#ifndef NO_UDP_QUERIES
/* UDP query code */
case QS_UDPINITIAL:
if ((st->sock=socket(PDNSD_PF_INET,SOCK_DGRAM,IPPROTO_UDP))==-1) {
DEBUG_MSG("Could not open socket: %s\n", strerror(errno));
break;
}
/* maybe bind */
if (!bind_socket(st->sock)) {
close(st->sock);
break;
}
/* connect */
#ifdef ENABLE_IPV6
retry_udp_connect:
#endif
if (connect(st->sock,SOCK_ADDR(st),SIN_LEN)==-1) {
if (errno==ECONNREFUSED) st->s_errno=errno;
#ifdef ENABLE_IPV6
/* if IPv6 connectivity is for some reason unavailable, perhaps the
IPv4 fallback address can still be reached. */
else if(!run_ipv4 && (errno==ENETUNREACH || errno==ENETDOWN)
&& st->a4fallback.s_addr!=INADDR_ANY)
{
#if DEBUG>0
char abuf[ADDRSTR_MAXLEN];
DEBUG_PDNSDA_MSG("Connecting to %s failed: %s, retrying with IPv4 address %s\n",
PDNSDA2STR(PDNSD_A(st)),strerror(errno),
inet_ntop(AF_INET,&st->a4fallback,abuf,sizeof(abuf)));
#endif
IPV6_MAPIPV4(&st->a4fallback,&st->a.sin6.sin6_addr);
st->a4fallback.s_addr=INADDR_ANY;
goto retry_udp_connect;
}
#endif
DEBUG_PDNSDA_MSG("Error while connecting to %s: %s\n", PDNSDA2STR(PDNSD_A(st)),strerror(errno));
close(st->sock);
break;
}
/* transmit query by udp*/
/* send will hopefully not block on a freshly opened socket (the buffer
* must be empty) */
if (send(st->sock,&st->msg->hdr,st->transl,0)==-1) {
st->s_errno=errno;
DEBUG_PDNSDA_MSG("Error while sending data to %s: %s\n", PDNSDA2STR(PDNSD_A(st)),strerror(errno));
close(st->sock);
break;
}
st->state=QS_UDPRECEIVE;
/* st->event=QEV_READ; */
return -1;
case QS_UDPRECEIVE:
{
int udpbufsize= (st->edns_query?global.udpbufsize:UDP_BUFSIZE);
if(!(st->recvbuf=(dns_hdr_t *)realloc_or_cleanup(st->recvbuf,udpbufsize))) {
close(st->sock);
DEBUG_MSG("Out of memory in query.\n");
retval=RC_FATALERR;
break;
}
if ((rv=recv(st->sock,st->recvbuf,udpbufsize,0))==-1) {
st->s_errno=errno;
DEBUG_PDNSDA_MSG("Error while receiving data from %s: %s\n", PDNSDA2STR(PDNSD_A(st)),strerror(errno));
close(st->sock);
break;
}
st->recvl=rv;
if (st->recvl<sizeof(dns_hdr_t) || ntohs(st->recvbuf->id)!=st->myrid) {
DEBUG_MSG("Bad answer received. Ignoring it.\n");
/* no need to care about timeouts here. That is done at an upper layer. */
st->state=QS_UDPRECEIVE;
/* st->event=QEV_READ; */
return -1;
}
close(st->sock);
st->state=QS_DONE;
return RC_OK;
}
#endif
}
/* If we get here, something has gone wrong. */
st->state=QS_DONE;
return retval; /* should be either RC_SERVFAIL or RC_FATALERR */
}
static dns_cent_t *lookup_cent_array(dns_cent_array ca, const unsigned char *nm)
{
int i,n=DA_NEL(ca);
for(i=0;i<n;++i) {
dns_cent_t *ce=&DA_INDEX(ca,i);
if(rhnicmp(ce->qname,nm))
return ce;
}
return NULL;
}
/* Extract the minimum ttl field from the SOA record stored in an rr bucket. */
static time_t soa_minimum(rr_bucket_t *rrs)
{
uint32_t minimum;
unsigned char *p=(unsigned char *)(rrs->data);
/* Skip owner and maintainer. Lengths are validated in cache. */
p=skiprhn(skiprhn(p));
/* Skip serial, refresh, retry, expire fields. */
p += 4*sizeof(uint32_t);
GETINT32(minimum,p);
return minimum;
}
/*
* The function that will actually execute a query. It takes a state structure in st.
* st->state must be set to QS_INITIAL before calling.
* This may return one of the RC_* codes, where RC_OK indicates success, the other
* RC codes indicate the appropriate errors. -1 is the return value that indicates that
* you should call p_exec_query again with the same state for the result until you get
* a return value >0. Alternatively, call p_cancel_query to cancel it.
* Timeouts are already handled by this function.
* Any records that the query has yielded and that are not a direct answer to the query
* (i.e. are records for other domains) are added to the cache, while the direct answers
* are returned in ent.
* All ns records, to whomever they might belong, are additionally returned in the ns list.
* Free it when done.
* This function calls another query state machine function that supports TCP and UDP.
*
* If you want to tell me that this function has a truly ugly coding style, ah, well...
* You are right, somehow, but I feel it is conceptually elegant ;-)
*/
static int p_exec_query(dns_cent_t **entp, const unsigned char *name, int thint,
query_stat_t *st, dlist *ns, unsigned char *c_soa)
{
int rv,rcode;
unsigned short rd;
switch (st->state){
case QS_INITIAL: {
size_t transl,allocsz;
unsigned int rrnlen=0;
allocsz= sizeof(dns_msg_t);
if(name) {
rrnlen=rhnlen(name);
allocsz += rrnlen+4;
if(st->edns_query)
allocsz += sizeof_opt_pseudo_rr;
}
st->msg=(dns_msg_t *)pdnsd_malloc(allocsz);
if (!st->msg) {
st->state=QS_DONE;
return RC_FATALERR; /* unrecoverable error */
}
st->myrid=get_rand16();
st->msg->hdr.id=htons(st->myrid);
st->msg->hdr.qr=QR_QUERY;
st->msg->hdr.opcode=OP_QUERY;
st->msg->hdr.aa=0;
st->msg->hdr.tc=0;
st->msg->hdr.rd=(name && st->trusted);
st->msg->hdr.ra=0;
st->msg->hdr.z=0;
st->msg->hdr.ad=0;
st->msg->hdr.cd=0;
st->msg->hdr.rcode=RC_OK;
st->msg->hdr.qdcount=htons(name!=NULL);
st->msg->hdr.ancount=0;
st->msg->hdr.nscount=0;
st->msg->hdr.arcount=0;
transl= sizeof(dns_hdr_t);
if(name) {
unsigned char *p = mempcpy((unsigned char *)(&st->msg->hdr+1),name,rrnlen);
unsigned short qtype=(st->lean_query?thint:QT_ALL);
PUTINT16(qtype,p);
PUTINT16(C_IN,p);
transl += rrnlen+4;
if(st->edns_query)
add_opt_pseudo_rr(&st->msg,&transl,&allocsz,
global.udpbufsize,RC_OK,0,0);
}
st->transl=transl;
#ifndef NO_TCP_QUERIES
st->msg->len=htons(st->transl);
#endif
st->recvbuf=NULL;
st->state=(USE_UDP(st)?QS_UDPINITIAL:QS_TCPINITIAL);
/* fall through */
}
QS_QUERY_CASES:
tryagain:
rv=p_query_sm(st);
if (rv==-1) {
return -1;
}
if (rv!=RC_OK) {
pdnsd_free(st->msg);
pdnsd_free(st->recvbuf);
st->state=QS_DONE;
if(st->needs_testing) {
switch(st->s_errno) {
case ENETUNREACH: /* network unreachable */
case EHOSTUNREACH: /* host unreachable */
case ENOPROTOOPT: /* protocol unreachable */
case ECONNREFUSED: /* port unreachable */
case ENETDOWN: /* network down */
case EHOSTDOWN: /* host down */
#ifdef ENONET
case ENONET: /* machine not on the network */
#endif
/* Mark this server as down for a period of time */
sched_server_test(PDNSD_A(st),1,0);
st->needs_testing=0;
}
}
return rv;
}
/* rv==RC_OK */
DEBUG_PDNSDA_MSG("Received reply from %s (msg len=%u).\n", PDNSDA2STR(PDNSD_A(st)), st->recvl);
DEBUG_DUMP_DNS_MSG(st->recvbuf, st->recvl);
/* Basic sanity checks */
if (st->recvl<sizeof(dns_hdr_t)) {
DEBUG_MSG("Message too short!\n");
goto discard_reply;
}
{
uint16_t recvid=ntohs(st->recvbuf->id);
if (recvid!=st->myrid) {
DEBUG_MSG("ID mismatch: expected %04x, got %04x!\n", st->myrid, recvid);
goto discard_reply;
}
}
if (st->recvbuf->qr!=QR_RESP) {
DEBUG_MSG("The QR bit indicates this is a query, not a response!\n");
goto discard_reply;
}
if (st->recvbuf->opcode!=OP_QUERY) {
DEBUG_MSG("Not a reply to a standard query (opcode=%u).\n",st->recvbuf->opcode);
goto discard_reply;
}
rcode=st->recvbuf->rcode;
#if DEBUG>0
{
char flgsbuf[DNSFLAGSMAXSTRSIZE];
DEBUG_MSG("rcode=%u (%s), flags:%s\n", rcode, get_ename(rcode), dnsflags2str(st->recvbuf, flgsbuf));
}
#endif
if (st->recvbuf->z!=0) {
DEBUG_MSG("Malformed response (nonzero Z bit).\n");
goto discard_reply;
}
if(st->needs_testing) {
/* We got an answer from this server, so don't bother with up tests for a while. */
sched_server_test(PDNSD_A(st),1,1);
st->needs_testing=0;
}
rv=rcode;
if(rcode==RC_OK || rcode==RC_NAMEERR) {
/* success or at least no requery is needed */
st->state=QS_DONE;
break;
}
else if (entp) {
if(rcode==RC_SERVFAIL || rcode==RC_NOTSUPP || rcode==RC_REFUSED) {
if (st->msg->hdr.rd && !st->recvbuf->ra) {
/* seems as if we have got no recursion available.
We will have to do it by ourselves (sigh...) */
DEBUG_PDNSDA_MSG("Server %s returned error code: %s."
" Maybe does not support recursive query?"
" Querying non-recursively.\n",
PDNSDA2STR(PDNSD_A(st)),get_ename(rcode));
st->msg->hdr.rd=0;
goto resetstate_tryagain;
}
else if(rcode!=RC_SERVFAIL && st->edns_query && st->msg->hdr.arcount)
goto try_withoutedns;
else if (st->recvbuf->ancount && st->auth_serv==2) {
/* The name server returned a failure code,
but the answer section is not empty,
and the answer is from a server lower down the call chain.
Use this answer tentatively (it may be the
best we can get), but remember the failure. */
DEBUG_PDNSDA_MSG("Server %s returned error code: %s,"
" but the answer section is not empty."
" Using the answer tentatively.\n",
PDNSDA2STR(PDNSD_A(st)),get_ename(rcode));
st->failed=3;
st->state=QS_DONE;
break;
}
}
else if(rcode==RC_FORMAT && st->edns_query && st->msg->hdr.arcount)
try_withoutedns: {
size_t transl;
/* Perhaps the remote server barfs when the query
contains an OPT RR in the additional section.
Try again with an empty addtional section. */
DEBUG_PDNSDA_MSG("Server %s returned error code: %s."
" Maybe cannot handle EDNS?"
" Querying with empty additional section.\n",
PDNSDA2STR(PDNSD_A(st)),get_ename(rcode));
transl=remove_opt_pseudo_rr(st->msg,st->transl);
if(transl!=0 && st->msg->hdr.arcount==0) {
st->transl=transl;
#ifndef NO_TCP_QUERIES
st->msg->len=htons(st->transl);
#endif
st->edns_query=0;
resetstate_tryagain:
st->myrid=get_rand16();
st->msg->hdr.id=htons(st->myrid);
st->state=(USE_UDP(st)?QS_UDPINITIAL:QS_TCPINITIAL);
goto tryagain;
}
else {
DEBUG_PDNSDA_MSG("Internal error: could not remove additional section from query"
" to server %s\n", PDNSDA2STR(PDNSD_A(st)));
}
}
}
discard_reply:
/* report failure */
pdnsd_free(st->msg);
pdnsd_free(st->recvbuf);
/*close(st->sock);*/
st->state=QS_DONE;
#if DEBUG>0
if(entp) {
DEBUG_PDNSDA_MSG("Discarding reply from server %s\n", PDNSDA2STR(PDNSD_A(st)));
}
#endif
if (rv!=RC_OK)
return rv;
return RC_SERVFAIL; /* mock error code */
default: /* we shouldn't get here */
st->state=QS_DONE;
return RC_SERVFAIL; /* mock error code */
}
/* If we reach this code, we have successfully received an answer,
* because we have returned error codes on errors or -1 on AGAIN conditions.
* So we *should* have a usable dns record in recvbuf by now.
*/
rd= st->msg->hdr.rd; /* Save the 'Recursion Desired' bit of the query. */
pdnsd_free(st->msg);
if(entp) {
time_t queryts=time(NULL);
size_t lcnt= ((size_t)st->recvl) - sizeof(dns_hdr_t);
unsigned char *rrp=(unsigned char *)(st->recvbuf+1);
dns_cent_array secs[3]={NULL,NULL,NULL};
# define ans_sec secs[0]
# define auth_sec secs[1]
# define add_sec secs[2]
unsigned short qtype,flags,aa,neg_ans=0,reject_ans=0,num_ns=0;
int numoptrr;
edns_info_t ednsinfo= {0};
if (ntohs(st->recvbuf->qdcount)!=1) {
DEBUG_PDNSDA_MSG("Bad number of query records in answer from %s\n",
PDNSDA2STR(PDNSD_A(st)));
rv=RC_SERVFAIL;
goto free_recvbuf_return;
}
/* check & skip the query record. */
{
unsigned char nbuf[DNSNAMEBUFSIZE];
if ((rv=decompress_name((unsigned char *)st->recvbuf, st->recvl, &rrp, &lcnt, nbuf, NULL))!=RC_OK) {
DEBUG_PDNSDA_MSG("Cannot decompress QNAME in answer from %s\n",
PDNSDA2STR(PDNSD_A(st)));
rv=RC_SERVFAIL;
goto free_recvbuf_return;
}
if(!rhnicmp(nbuf,name)) {
DEBUG_PDNSDA_MSG("Answer from %s does not match query.\n",
PDNSDA2STR(PDNSD_A(st)));
rv=RC_SERVFAIL;
goto free_recvbuf_return;
}
}
qtype=(st->lean_query?thint:QT_ALL);
if (lcnt<4) {
DEBUG_PDNSDA_MSG("Format error in reply from %s (message truncated in qtype or qclass).\n",
PDNSDA2STR(PDNSD_A(st)));
rv=RC_SERVFAIL; /* mock error code */
goto free_recvbuf_return;
}
{
unsigned short qt,qc;
GETINT16(qt,rrp);
GETINT16(qc,rrp);
if(qt!=qtype) {
DEBUG_PDNSDA_MSG("qtype in answer (%u) from %s does not match expected qtype (%u).\n",
qt,PDNSDA2STR(PDNSD_A(st)),qtype);
rv=RC_SERVFAIL;
goto free_recvbuf_return;
}
}
lcnt-=4;
st->aa= (st->recvbuf->aa && !st->failed);
st->tc= st->recvbuf->tc;
st->ra= (rd && st->recvbuf->ra);
/* Don't flag cache entries from a truncated reply as authoritative. */
aa= (st->aa && !st->tc);
flags=st->flags;
if (aa) flags|=CF_AUTH;
/* Initialize a dns_cent_t in the array for the answer section */
if (!(ans_sec=DA_GROW1(ans_sec))) {
rv=RC_FATALERR; /* unrecoverable error */
goto free_recvbuf_return;
}
/* By marking DF_AUTH, we mean authoritative AND complete. */
if (!init_cent(&DA_INDEX(ans_sec,0), name, 0, 0, (aa && qtype==QT_ALL)?DF_AUTH:0 DBG1)) {
rv=RC_FATALERR; /* unrecoverable error */
goto free_centarrays_recvbuf_return;
}
/* Now read the answer, authority and additional sections,
storing the results in the arrays ans_sec,auth_sec and add_sec.
*/
numoptrr=0;
rv=rrs2cent((unsigned char *)st->recvbuf, st->recvl, &rrp, &lcnt, ntohs(st->recvbuf->ancount),
flags, queryts, &ans_sec, &numoptrr, &ednsinfo);
#if DEBUG>0
if(numoptrr!=0) {
DEBUG_MSG("Answer section in reply contains %d OPT pseudo-RRs!\n", numoptrr);
}
#endif
numoptrr=0;
if(rv==RC_OK) {
uint16_t nscount=ntohs(st->recvbuf->nscount);
if (nscount) {
rv=rrs2cent((unsigned char *)st->recvbuf, st->recvl, &rrp, &lcnt, nscount,
flags|CF_ADDITIONAL, queryts, &auth_sec, &numoptrr, &ednsinfo);
#if DEBUG>0
if(numoptrr!=0) {
DEBUG_MSG("Authority section in reply contains %d OPT pseudo-RRs!\n", numoptrr);
}
#endif
}
}
numoptrr=0;
if(rv==RC_OK) {
uint16_t arcount=ntohs(st->recvbuf->arcount);
if (arcount) {
rv=rrs2cent((unsigned char *)st->recvbuf, st->recvl, &rrp, &lcnt, arcount,
flags|CF_ADDITIONAL, queryts, &add_sec, &numoptrr, &ednsinfo);
if(numoptrr!=0) {
#if DEBUG>0
if(numoptrr!=1) {
DEBUG_MSG("Additional section in reply contains %d OPT pseudo-RRs!\n", numoptrr);
}
DEBUG_PDNSDA_MSG("Reply from %s contains OPT pseudosection: EDNS version = %u, udp size = %u, flag DO=%u\n",
PDNSDA2STR(PDNSD_A(st)), ednsinfo.version, ednsinfo.udpsize, ednsinfo.do_flg);
#endif
if(rcode!=ednsinfo.rcode) {
DEBUG_PDNSDA_MSG("Reply from %s contains unexpected EDNS rcode %u (%s)!\n",
PDNSDA2STR(PDNSD_A(st)), ednsinfo.rcode, get_ename(ednsinfo.rcode));
rcode=ednsinfo.rcode;
/* Mark as failed, but use answer tentatively. */
if(!st->failed) st->failed=1;
}
}
}
}
if(!(rv==RC_OK || (rv==RC_TRUNC && st->recvbuf->tc))) {
DEBUG_PDNSDA_MSG(rv==RC_FORMAT?"Format error in reply from %s.\n":
rv==RC_TRUNC?"Format error in reply from %s (message unexpectedly truncated).\n":
rv==RC_SERVFAIL?"Inconsistent timestamps in reply from %s.\n":
"Out of memory while processing reply from %s.\n",
PDNSDA2STR(PDNSD_A(st)));
if(rv==RC_SERVFAIL) {
/* Inconsistent ttl timestamps and we are
enforcing strict RFC 2181 compliance.
Mark as failed, but use answer tentatively. */
if(!st->failed) st->failed=1;
}
else {
if(rv!=RC_FATALERR) rv=RC_SERVFAIL;
goto free_ent_centarrays_recvbuf_return;
}
}
{
/* Remember references to NS and SOA records in the answer or authority section
so that we can add this information to our own reply. */
int i,n=DA_NEL(ans_sec);
for(i=0;i<n;++i) {
dns_cent_t *cent=&DA_INDEX(ans_sec,i);
unsigned scnt=rhnsegcnt(cent->qname);
if(getrrset_NS(cent))
cent->c_ns=scnt;
if(getrrset_SOA(cent))
cent->c_soa=scnt;
if((qtype>=QT_MIN && qtype<=QT_MAX) ||
(/* (qtype>=T_MIN && qtype<=T_MAX) && */ getrrset(cent,qtype)) ||
(n==1 && cent->num_rrs==0))
{
/* Match this name with names in the authority section */
int j,m=DA_NEL(auth_sec);
for(j=0;j<m;++j) {
dns_cent_t *ce=&DA_INDEX(auth_sec,j);
unsigned int ml,rem;
ml=domain_match(ce->qname,cent->qname, &rem, NULL);
if(rem==0 &&
/* Don't accept records for the root domain from name servers
that were not listed in the configuration file. */
(ml || st->auth_serv!=2)) {
if(getrrset_NS(ce)) {
if(cent->c_ns==cundef || cent->c_ns<ml)
cent->c_ns=ml;
}
if(getrrset_SOA(ce)) {
if(cent->c_soa==cundef || cent->c_soa<ml)
cent->c_soa=ml;
}
}
}
}
}
}
/* Check whether the answer contains an IP address that should be rejected. */
if(have_rejectlist(st)) {
int i;
int na4=nreject_a4(st);
addr4maskpair_t *a4arr=rejectlist_a4(st);
#if ALLOW_LOCAL_AAAA
int na6=nreject_a6(st);
addr6maskpair_t *a6arr=rejectlist_a6(st);
#endif
/* Check addresses in the answer, authority and additional sections. */
for(i=0;i<3;++i) {
dns_cent_array sec=secs[i];
int j,nce=DA_NEL(sec);
for(j=0;j<nce;++j) {
dns_cent_t *cent=&DA_INDEX(sec,j);
rr_set_t *rrset=getrrset_A(cent);
if(rrset && na4) {
/* This is far from the world's most efficient matching algorithm,
but it should work OK as long as the numbers involved are small.
*/
rr_bucket_t *rr;
for(rr=rrset->rrs; rr; rr=rr->next) {
struct in_addr *a=(struct in_addr *)(rr->data);
int k;
for(k=0;k<na4;++k) {
addr4maskpair_t *am = &a4arr[k];
if(ADDR4MASK_EQUIV(a,&am->a,&am->mask)) {
#if DEBUG>0
unsigned char nmbuf[DNSNAMEBUFSIZE]; char abuf[ADDRSTR_MAXLEN];
DEBUG_PDNSDA_MSG("Rejecting answer from server %s because it contains an A record"
" for \"%s\" with an address in the reject list: %s\n",
PDNSDA2STR(PDNSD_A(st)),
rhn2str(cent->qname,nmbuf,sizeof(nmbuf)),
inet_ntop(AF_INET,a,abuf,sizeof(abuf)));
#endif
reject_ans=1; goto rejectlist_scan_done;
}
}
}
}
#if ALLOW_LOCAL_AAAA
rrset=getrrset_AAAA(cent);
if(rrset && na6) {
rr_bucket_t *rr;
for(rr=rrset->rrs; rr; rr=rr->next) {
struct in6_addr *a=(struct in6_addr *)(rr->data);
int k;
for(k=0;k<na6;++k) {
addr6maskpair_t *am = &a6arr[k];
if(ADDR6MASK_EQUIV(a,&am->a,&am->mask)) {
#if DEBUG>0
unsigned char nmbuf[DNSNAMEBUFSIZE]; char abuf[INET6_ADDRSTRLEN];
DEBUG_PDNSDA_MSG("Rejecting answer from server %s because it contains an AAAA record"
" for \"%s\" with an address in the reject list: %s\n",
PDNSDA2STR(PDNSD_A(st)),
rhn2str(cent->qname,nmbuf,sizeof(nmbuf)),
inet_ntop(AF_INET6,a,abuf,sizeof(abuf)));
#endif
reject_ans=1; goto rejectlist_scan_done;
}
}
}
}
#endif
}
}
rejectlist_scan_done:;
}
/* negative caching for domains */
if (rcode==RC_NAMEERR) {
DEBUG_PDNSDA_MSG("Server %s returned error code: %s\n", PDNSDA2STR(PDNSD_A(st)),get_ename(rcode));
name_error:
neg_ans=1;
{
/* We did not get what we wanted. Cache according to policy */
dns_cent_t *ent=&DA_INDEX(ans_sec,0);
int neg_domain_pol=global.neg_domain_pol;
if (neg_domain_pol==C_ON || (neg_domain_pol==C_AUTH && st->aa)) {
time_t ttl=global.neg_ttl;
/* Try to find a SOA record that came with the reply.
*/
if(ent->c_soa!=cundef) {
unsigned scnt=rhnsegcnt(name);
dns_cent_t *cent;
if(ent->c_soa<scnt && (cent=lookup_cent_array(auth_sec,skipsegs(name,scnt-ent->c_soa)))) {
rr_set_t *rrset=getrrset_SOA(cent);
if (rrset && rrset->rrs) {
time_t min=soa_minimum(rrset->rrs);
ttl=rrset->ttl;
if(ttl>min)
ttl=min;
}
}
}
DEBUG_RHN_MSG("Caching domain %s negative with ttl %li\n",RHN2STR(name),(long)ttl);
negate_cent(ent,ttl,queryts);
if(st->nocache) ent->flags |= DF_NOCACHE;
goto cleanup_return_OK;
} else {
if(c_soa) *c_soa=ent->c_soa;
free_cent(ent DBG1);
rv=RC_NAMEERR;
goto add_additional;
}
}
}
if(reject_ans) {
if(reject_policy(st)==C_NEGATE && st->failed<=1)
goto name_error;
else {
rv=RC_SERVFAIL;
goto free_ent_centarrays_recvbuf_return;
}
}
if(global.deleg_only_zones && st->auth_serv<3) { /* st->auth_serv==3 means this server is a root-server. */
int missingdelegation,authcnt;
/* The deleg_only_zones data may change due to runtime reconfiguration,
therefore use locks. */
lock_server_data();
missingdelegation=0; authcnt=0;
{
int i,n=DA_NEL(global.deleg_only_zones); unsigned rem,zrem;
for(i=0;i<n;++i) {
if(domain_match(name,DA_INDEX(global.deleg_only_zones,i),&rem,&zrem) && zrem==0)
goto zone_match;
}
goto delegation_OK;
zone_match:
/* The name queried matches a delegation-only zone. */
if(rem) {
/* Check if we can find delegation in the answer or authority section. */
/* dns_cent_array secs[2]={ans_sec,auth_sec}; */
int j;
for(j=0;j<2;++j) {
dns_cent_array sec=secs[j];
int k,m=DA_NEL(sec);
for(k=0;k<m;++k) {
dns_cent_t *ce=&DA_INDEX(sec,k);
if(getrrset_NS(ce) || getrrset_SOA(ce)) {
/* Found a NS or SOA record in the answer or authority section. */
int l;
++authcnt;
for(l=0;l<n;++l) {
if(domain_match(ce->qname,DA_INDEX(global.deleg_only_zones,l),&rem,&zrem) && zrem==0) {
if(rem) break;
else goto try_next_auth;
}
}
goto delegation_OK;
}
try_next_auth:;
}
}
#if DEBUG>0
{
unsigned char nmbuf[DNSNAMEBUFSIZE],zbuf[DNSNAMEBUFSIZE];
DEBUG_PDNSDA_MSG(authcnt?"%s is in %s zone, but no delegation found in answer returned by server %s\n"
:"%s is in %s zone, but no authority information provided by server %s\n",
rhn2str(name,nmbuf,sizeof(nmbuf)), rhn2str(DA_INDEX(global.deleg_only_zones,i),zbuf,sizeof(zbuf)),
PDNSDA2STR(PDNSD_A(st)));
}
#endif
missingdelegation=1;
}
delegation_OK:;
}
unlock_server_data();
if(missingdelegation) {
if(authcnt && st->failed<=1) {
/* Treat this as a nonexistant name. */
goto name_error;
}
else if(st->auth_serv<2) {
/* If this is one of the servers obtained from the list
pdnsd was configured with, treat this as a failure.
Hopefully one of the other servers in the list will
return a non-empty authority section.
*/
rv=RC_SERVFAIL;
goto free_ent_centarrays_recvbuf_return;
}
}
}
{
/* Negative caching of rr sets */
dns_cent_t *ent=&DA_INDEX(ans_sec,0);
if(!ent->num_rrs) neg_ans=1;
if (thint>=T_MIN && thint<=T_MAX && !getrrset(ent,thint) && !st->tc && st->failed<=1) {
/* We did not get what we wanted. Cache according to policy */
int neg_rrs_pol=global.neg_rrs_pol;
if (neg_rrs_pol==C_ON || (neg_rrs_pol==C_AUTH && aa) ||
(neg_rrs_pol==C_DEFAULT && (aa || st->ra)))
{
time_t ttl=global.neg_ttl;
rr_set_t *rrset=getrrset_SOA(ent);
dns_cent_t *cent;
unsigned scnt;
/* If we received a SOA, we should take the ttl of that record. */
if ((rrset && rrset->rrs) ||
/* Try to find a SOA record higher up the hierarchy that came with the reply. */
((cent=lookup_cent_array(auth_sec,
(ent->c_soa!=cundef && ent->c_soa<(scnt=rhnsegcnt(name)))?
skipsegs(name,scnt-ent->c_soa):
name)) &&
(rrset=getrrset_SOA(cent)) && rrset->rrs))
{
time_t min=soa_minimum(rrset->rrs);
ttl=rrset->ttl;
if(ttl>min)
ttl=min;
}
DEBUG_RHN_MSG("Caching type %s for domain %s negative with ttl %li\n",getrrtpname(thint),RHN2STR(name),(long)ttl);
if (!add_cent_rrset_by_type(ent, thint, ttl, queryts, CF_NEGATIVE|flags DBG1)) {
rv=RC_FATALERR;
goto free_ent_centarrays_recvbuf_return;
}
}
}
}
if (st->failed<=1) {
/* The domain names of all name servers found in the answer and authority sections are placed in *ns,
which is automatically grown. */
/* dns_cent_array secs[2]={ans_sec,auth_sec}; */
int i;
for(i=0;i<2;++i) {
dns_cent_array sec=secs[i];
int j,n=DA_NEL(sec);
for(j=0;j<n;++j) {
dns_cent_t *cent=&DA_INDEX(sec,j);
unsigned int rem;
/* Don't accept records for the root domain from name servers
that were not listed in the configuration file. */
if((*(cent->qname) || st->auth_serv!=2) &&
/* Don't accept possibly poisoning nameserver entries in paranoid mode */
(st->trusted || !st->nsdomain || (domain_match(st->nsdomain, cent->qname, &rem,NULL),rem==0)) &&
/* The following test is actually redundant and should never fail. */
*(cent->qname)!=0xff)
{
/* Some nameservers obviously choose to send SOA records instead of NS ones.
* Although I think that this is poor behaviour, we'll have to work around that. */
static const unsigned short nstypes[2]={T_NS,T_SOA};
int k;
for(k=0;k<2;++k) {
rr_set_t *rrset=getrrset(cent,nstypes[k]);
if(rrset) {
rr_bucket_t *rr;
unsigned short first=1;
for(rr=rrset->rrs; rr; rr=rr->next) {
size_t sz1,sz2;
unsigned char *p;
/* Skip duplicate records */
for(p=dlist_first(*ns); p; p=dlist_next(p)) {
if(rhnicmp(*p==0xff?p+1:skiprhn(p),(unsigned char *)(rr->data)))
goto next_nsr;
}
/* add to the nameserver list.
Here we use a little compression trick: if
the first byte of a name is 0xff, this means
repeat the previous name.
*/
sz1= (first?rhnlen(cent->qname):1);
sz2=rhnlen((unsigned char *)(rr->data));
if (!(*ns=dlist_grow(*ns,sz1+sz2))) {
rv=RC_FATALERR;
goto free_ent_centarrays_recvbuf_return;
}
p=dlist_last(*ns);
if(first) {
first=0;
p=mempcpy(p,cent->qname,sz1);
}
else
*p++ = 0xff; /* 0xff means 'idem' */
/* This will only copy the first name, which is the NS */
memcpy(p,(unsigned char *)(rr->data),sz2);
++num_ns;
next_nsr:;
}
}
}
}
}
}
}
cleanup_return_OK:
if(st->failed && neg_ans && num_ns==0) {
DEBUG_PDNSDA_MSG("Answer from server %s does not contain usable records.\n",
PDNSDA2STR(PDNSD_A(st)));
rv=RC_SERVFAIL;
goto free_ns_ent_centarrays_recvbuf_return;
}
if(!(*entp=malloc(sizeof(dns_cent_t)))) {
rv=RC_FATALERR;
goto free_ns_ent_centarrays_recvbuf_return;
}
**entp=DA_INDEX(ans_sec,0);
rv=RC_OK;
add_additional:
if (!st->failed && !reject_ans) {
/* Add the additional RRs to the cache. */
/* dns_cent_array secs[3]={ans_sec,auth_sec,add_sec}; */
int i;
#if DEBUG>0
if(debug_p && neg_ans) {
int j,n=DA_NEL(ans_sec);
for(j=1; j<n; ++j) {
unsigned char nmbuf[DNSNAMEBUFSIZE],nmbuf2[DNSNAMEBUFSIZE];
DEBUG_PDNSDA_MSG("Reply from %s is negative for %s, dropping record(s) for %s in answer section.\n",
PDNSDA2STR(PDNSD_A(st)),
rhn2str(name,nmbuf,sizeof(nmbuf)),
rhn2str(DA_INDEX(ans_sec,j).qname,nmbuf2,sizeof(nmbuf2)));
}
}
#endif
for(i=neg_ans; i<3; ++i) {
dns_cent_array sec=secs[i];
int j,n=DA_NEL(sec);
/* The first entry in the answer section is treated separately, so skip that one. */
for(j= !i; j<n; ++j) {
dns_cent_t *cent=&DA_INDEX(sec,j);
if(*(cent->qname) || st->auth_serv!=2) {
unsigned int rem;
if(st->trusted || !st->nsdomain || (domain_match(st->nsdomain, cent->qname, &rem, NULL),rem==0))
add_cache(cent);
else {
#if DEBUG>0
unsigned char nmbuf[DNSNAMEBUFSIZE],nsbuf[DNSNAMEBUFSIZE];
DEBUG_MSG("Record for %s not in nsdomain %s; dropped.\n",
rhn2str(cent->qname,nmbuf,sizeof(nmbuf)),rhn2str(st->nsdomain,nsbuf,sizeof(nsbuf)));
#endif
}
}
else {
#if DEBUG>0
static const char *const secname[3]={"answer","authority","additional"};
DEBUG_PDNSDA_MSG("Record(s) for root domain in %s section from %s dropped.\n", secname[i],PDNSDA2STR(PDNSD_A(st)));
#endif
}
}
}
}
goto free_centarrays_recvbuf_return;
free_ns_ent_centarrays_recvbuf_return:
dlist_free(*ns); *ns=NULL;
free_ent_centarrays_recvbuf_return:
if(DA_NEL(ans_sec)>=1) free_cent(&DA_INDEX(ans_sec,0) DBG1);
free_centarrays_recvbuf_return:
{
/* dns_cent_array secs[3]={ans_sec,auth_sec,add_sec}; */
int i;
for(i=0;i<3;++i) {
dns_cent_array sec=secs[i];
int j,n=DA_NEL(sec);
/* The first entry in the answer section is treated separately, so skip that one. */
for(j= !i; j<n; ++j)
free_cent(&DA_INDEX(sec,j) DBG1);
da_free(sec);
}
}
#undef ans_sec
#undef auth_sec
#undef add_sec
}
free_recvbuf_return:
pdnsd_free(st->recvbuf);
return rv;
}
/*
* Cancel a query, freeing all resources. Any query state is valid as input (this may even be called
* if a call to p_exec_query already returned error or success)
*/
static void p_cancel_query(query_stat_t *st)
{
switch (st->state) {
QS_WRITE_CASES:
QS_READ_CASES:
close(st->sock);
/* fall through */
case QS_TCPINITIAL:
case QS_UDPINITIAL:
pdnsd_free(st->recvbuf);
pdnsd_free(st->msg);
}
if(st->state!=QS_INITIAL && st->state!=QS_DONE)
st->state=QS_CANCELED;
}
#if 0
/*
* Initialize a query_serv_t (server list for parallel query)
* This is there for historical reasons only.
*/
inline static void init_qserv(query_stat_array *q)
{
*q=NULL;
}
#endif
/*
* Add a server entry to a query_serv_t
* Note: only a reference to nsdomain is copied, not the name itself.
* Be sure to free the q-list before freeing the name.
*/
static int add_qserv(query_stat_array *q, pdnsd_a2 *a, int port, time_t timeout, unsigned flags,
char nocache, char lean_query, char edns_query, char auth_s, char needs_testing, char trusted,
const unsigned char *nsdomain, rejectlist_t *rejectlist)
{
query_stat_t *qs;
if ((*q=DA_GROW1(*q))==NULL) {
DEBUG_MSG("Out of memory in add_qserv()\n");
return 0;
}
qs=&DA_LAST(*q);
#ifdef ENABLE_IPV4
if (run_ipv4) {
memset(&qs->a.sin4,0,sizeof(qs->a.sin4));
qs->a.sin4.sin_family=AF_INET;
qs->a.sin4.sin_port=htons(port);
qs->a.sin4.sin_addr=a->ipv4;
SET_SOCKA_LEN4(qs->a.sin4);
}
#endif
#ifdef ENABLE_IPV6
ELSE_IPV6 {
memset(&qs->a.sin6,0,sizeof(qs->a.sin6));
qs->a.sin6.sin6_family=AF_INET6;
qs->a.sin6.sin6_port=htons(port);
qs->a.sin6.sin6_flowinfo=IPV6_FLOWINFO;
qs->a.sin6.sin6_addr=a->ipv6;
SET_SOCKA_LEN6(qs->a.sin6);
qs->a4fallback=a->ipv4;
}
#endif
qs->timeout=timeout;
qs->flags=flags;
qs->nocache=nocache;
qs->auth_serv=auth_s;
qs->lean_query=lean_query;
qs->edns_query=edns_query;
qs->needs_testing=needs_testing;
qs->trusted=trusted;
qs->aa=0;
qs->tc=0;
qs->ra=0;
qs->failed=0;
qs->nsdomain=nsdomain; /* Note: only a reference is copied, not the name itself! */
qs->rejectlist=rejectlist;
qs->state=QS_INITIAL;
qs->qm=global.query_method;
qs->s_errno=0;
return 1;
}
/* Test whether two pdnsd_a2 addresses are the same. */
inline __attribute__((always_inline))
static int same_inaddr2_2(pdnsd_a2 *a, pdnsd_a2 *b)
{
return SEL_IPVER( a->ipv4.s_addr==b->ipv4.s_addr,
IN6_ARE_ADDR_EQUAL(&a->ipv6,&b->ipv6) &&
a->ipv4.s_addr==b->ipv4.s_addr );
}
/* This can be used to check whether a server address was already used in a
previous query_stat_t entry. */
inline static int query_stat_same_inaddr2(query_stat_t *qs, pdnsd_a2 *b)
{
return SEL_IPVER( qs->a.sin4.sin_addr.s_addr==b->ipv4.s_addr,
IN6_ARE_ADDR_EQUAL(&qs->a.sin6.sin6_addr,&b->ipv6) &&
qs->a4fallback.s_addr==b->ipv4.s_addr );
}
/*
* Free resources used by a query_serv_t
* There for historical reasons only.
*/
inline static void del_qserv(query_stat_array q)
{
da_free(q);
}
struct qstatnode_s {
query_stat_array qa;
struct qstatnode_s *next;
};
typedef struct qstatnode_s qstatnode_t;
struct qhintnode_s {
const unsigned char *nm;
int tp;
struct qhintnode_s *next;
};
/* typedef struct qhintnode_s qhintnode_t; */ /* Already defined in dns_query.h */
static int auth_ok(query_stat_array q, const unsigned char *name, int thint, dns_cent_t *ent,
int hops, qstatnode_t *qslist, qhintnode_t *qhlist,
query_stat_t *qse, dlist ns, query_stat_array *serv);
static int p_dns_cached_resolve(query_stat_array q, const unsigned char *name, int thint, dns_cent_t **cachedp,
int hops, qstatnode_t *qslist, qhintnode_t *qhlist, time_t queryts,
unsigned char *c_soa);
static int simple_dns_cached_resolve(atup_array atup_a, int port, char edns_query, time_t timeout,
const unsigned char *name, int thint, dns_cent_t **cachedp);
/*
* Performs a semi-parallel query on the servers in q. PAR_QUERIES are executed parallel at a time.
* name is the query name in dns protocol format (number.string etc),
* ent is the dns_cent_t that will be filled.
* hops is the number of recursions left.
* qslist should refer to a list of server arrays used higher up in the calling chain. This way we can
* avoid name servers that have already been tried for this name.
* qhlist should refer to a list of names that we are trying to resolve higher up in the calling chain.
* These names should be avoided further down the chain, or we risk getting caught in a wasteful cycle.
* thint is a hint on the requested query type used to decide whether an aa record must be fetched
* or a non-authoritative answer will be enough.
*
* nocache is needed because we add AA records to the cache. If the nocache flag is set, we do not
* take the original values for the record, but flags=0 and ttl=0 (but only if we do not already have
* a cached record for that set). These settings cause the record be purged on the next cache addition.
* It will also not be used again.
*
* The return value of p_recursive_query() has the same meaning as that of p_dns_cached_resolve()
* (see below).
*/
static int p_recursive_query(query_stat_array q, const unsigned char *name, int thint, dns_cent_t **entp,
int *nocache, int hops, qstatnode_t *qslist, qhintnode_t *qhlist,
unsigned char *c_soa)
{
dns_cent_t *ent,*entsave=NULL;
int i,j,k;
int rv=RC_SERVFAIL;
int qualval=0;
query_stat_t *qse=NULL; /* Initialized to inhibit compiler warning */
dlist ns=NULL,nssave=NULL;
query_stat_array serv=NULL,servsave=NULL;
# define W_AUTHOK 8
# define W_NOTFAILED 2
# define W_NOTTRUNC 1
# define NOTFAILMASK 6
# define GOODQUAL (W_AUTHOK+3*W_NOTFAILED)
# define save_query_result(ent,qs,ns,serv,authok) \
{ \
int qval = authok*W_AUTHOK + (3-qs->failed)*W_NOTFAILED + (!qs->tc)*W_NOTTRUNC; \
if(entsave && qval>qualval) { \
/* Free the old copy, because the new result is better. */ \
free_cent(entsave DBG1); \
pdnsd_free(entsave); \
entsave=NULL; \
del_qserv(servsave); \
dlist_free(nssave); \
} \
if(!entsave) { \
entsave=ent; \
servsave=serv; \
/* The serv array contains references to data within the ns list, \
so we need to save a copy of the ns list as well! */ \
if(DA_NEL(serv)>0) nssave=ns; else {nssave=NULL;dlist_free(ns);} \
qualval=qval; \
qse=qs; \
} \
else { \
/* We already have a copy, free the present one. */ \
free_cent(ent DBG1); \
pdnsd_free(ent); \
del_qserv(serv); \
dlist_free(ns); \
} \
serv=NULL; \
ns=NULL; \
}
{
time_t ts0=time(NULL),global_timeout=global.timeout;
int dc=0,mc=0,nq=DA_NEL(q),parqueries=global.par_queries;
for (j=0; j<nq; j += parqueries) {
mc=j+parqueries;
if (mc>nq) mc=nq;
/* First, call p_exec_query once for each parallel set to initialize.
* Then, as long as not all have the state QS_DONE or we have a timeout,
* build a poll/select set for all active queries and call them accordingly. */
for (i=dc;i<mc;i++) {
query_stat_t *qs=&DA_INDEX(q,i);
if(i>=j) {
/* The below should not happen any more, but may once again
* (immediate success) */
DEBUG_PDNSDA_MSG("Sending query to %s\n", PDNSDA2STR(PDNSD_A(qs)));
retryquery:
rv=p_exec_query(&ent, name, thint, qs,&ns,c_soa);
if (rv==RC_OK) {
int authok;
DEBUG_PDNSDA_MSG("Query to %s succeeded.\n", PDNSDA2STR(PDNSD_A(qs)));
if((authok=auth_ok(q, name, thint, ent, hops, qslist, qhlist, qs, ns, &serv))) {
if(authok>=0) {
if(!qs->failed
#if !defined(NO_TCP_QUERIES) && !defined(NO_UDP_QUERIES)
&& !(qs->qm==UDP_TCP && qs->tc)
#endif
)
{
qse=qs;
mc=i; /* No need to cancel queries beyond i */
goto done;
}
}
else {
mc=i; /* No need to cancel queries beyond i */
goto free_ent_return_failed;
}
}
/* We do not have a satisfactory answer.
However, we will save a copy in case none of the other
servers in the q list give a satisfactory answer either.
*/
save_query_result(ent,qs,ns,serv,authok);
#if !defined(NO_TCP_QUERIES) && !defined(NO_UDP_QUERIES)
if(qs->qm==UDP_TCP && qs->tc) {
switch_to_tcp(qs);
DEBUG_PDNSDA_MSG("Reply from %s was truncated. Trying again using TCP.\n",
PDNSDA2STR(PDNSD_A(qs)));
goto retryquery;
}
#endif
}
else if (rv==RC_NAMEERR || rv==RC_FATALERR) {
mc=i; /* No need to cancel queries beyond i */
goto done;
}
}
if (qs->state==QS_DONE && i==dc)
dc++;
}
if (dc<mc) {
time_t ts,maxto,now;
int pc,nevents;
#ifdef NO_POLL
int maxfd;
fd_set reads;
fd_set writes;
struct timeval tv;
#else
int ic;
struct pollfd polls[mc-dc]; /* Variable length array, may cause portability problems */
#endif
/* we do time keeping by hand, because poll/select might be interrupted and
* the returned times are not always to be trusted upon */
ts=time(NULL);
do {
/* build poll/select sets, maintain time.
* If you do parallel queries, the highest timeout will be honored
* also for the other servers when their timeout is exceeded and
* the highest is not.
* Changed by Paul Rombouts: queries are not canceled until we receive
* a useful reply or everything has failed or timed out (also taking into
* account the global timeout option).
* Thus in the worst case all the queries in the q list will be active
* simultaneously. The downside is that we may be wasting more resources
* this way. The advantage is that we have a greater chance of catching a
* reply. After all, if we wait longer anyway, why not for more servers. */
maxto=0;
pc=0;
rv=RC_SERVFAIL;
#ifdef NO_POLL
FD_ZERO(&reads);
FD_ZERO(&writes);
maxfd=0;
#endif
for (i=dc;i<mc;i++) {
query_stat_t *qs=&DA_INDEX(q,i);
if (qs->state!=QS_DONE) {
if (i>=j && qs->timeout>maxto)
maxto=qs->timeout;
#ifdef NO_POLL
if (qs->sock>maxfd) {
maxfd=qs->sock;
PDNSD_ASSERT(maxfd<FD_SETSIZE,"socket file descriptor exceeds FD_SETSIZE.");
}
switch (qs->state) {
QS_READ_CASES:
FD_SET(qs->sock,&reads);
break;
QS_WRITE_CASES:
FD_SET(qs->sock,&writes);
break;
}
#else
polls[pc].fd=qs->sock;
switch (qs->state) {
QS_READ_CASES:
polls[pc].events=POLLIN;
break;
QS_WRITE_CASES:
polls[pc].events=POLLOUT;
break;
default:
polls[pc].events=0;
}
#endif
pc++;
}
}
if (pc==0) {
/* In this case, ALL are done and we do not need to cancel any
* query. */
dc=mc;
break;
}
now=time(NULL);
maxto -= now-ts;
if (mc==nq) {
#if !defined(NO_TCP_QUERIES) && !defined(NO_UDP_QUERIES)
/* Don't use the global timeout if there are TCP queries
we might want to retry using UDP. */
for (i=j;i<mc;i++) {
query_stat_t *qs=&DA_INDEX(q,i);
if(tentative_tcp_query(qs))
goto skip_globto;
}
#endif
{
time_t globto=global_timeout-(now-ts0);
if(globto>maxto) maxto=globto;
}
#if !defined(NO_TCP_QUERIES) && !defined(NO_UDP_QUERIES)
skip_globto:;
#endif
}
#ifdef NO_POLL
tv.tv_sec=(maxto>0)?maxto:0;
tv.tv_usec=0;
nevents=select(maxfd+1,&reads,&writes,NULL,&tv);
#else
nevents=poll(polls,pc,(maxto>0)?(maxto*1000):0);
#endif
if (nevents<0) {
/* if(errno==EINTR)
continue; */
log_warn("poll/select failed: %s",strerror(errno));
goto done;
}
if (nevents==0) {
/* We have timed out. Mark the unresponsive servers so that we can consider
them for retesting later on. We will continue to listen for replies from
these servers as long as we have additional servers to try. */
for (i=j;i<mc;i++) {
query_stat_t *qs=&DA_INDEX(q,i);
if (qs->state!=QS_DONE && qs->needs_testing)
qs->needs_testing=2;
#if !defined(NO_TCP_QUERIES) && !defined(NO_UDP_QUERIES)
if (tentative_tcp_query(qs)) {
/* We timed out while waiting for a TCP connection.
Try again using UDP.
*/
close(qs->sock);
switch_to_udp(qs);
DEBUG_PDNSDA_MSG("TCP connection to %s timed out. Trying to use UDP.\n",
PDNSDA2STR(PDNSD_A(qs)));
rv=p_exec_query(&ent, name, thint, qs,&ns,c_soa);
/* In the unlikely case of immediate success */
if (rv==RC_OK) {
int authok;
DEBUG_PDNSDA_MSG("Query to %s succeeded.\n", PDNSDA2STR(PDNSD_A(qs)));
if((authok=auth_ok(q, name, thint, ent, hops, qslist, qhlist, qs, ns, &serv))) {
if(authok>=0) {
if(!qs->failed) {
qse=qs;
goto done;
}
}
else
goto free_ent_return_failed;
}
save_query_result(ent,qs,ns,serv,authok);
}
else if (rv==RC_NAMEERR || rv==RC_FATALERR) {
goto done;
}
++nevents;
}
#endif
}
#if !defined(NO_TCP_QUERIES) && !defined(NO_UDP_QUERIES)
if (mc==nq) {
/* We will not try additional servers, but we might want to try again
using UDP instead of TCP
*/
if(nevents && (time(NULL)-ts0)<global_timeout)
continue;
}
#endif
break;
}
#ifndef NO_POLL
ic=0;
#endif
for (i=dc;i<mc;i++) {
query_stat_t *qs=&DA_INDEX(q,i);
/* Check if we got a poll/select event */
if (qs->state!=QS_DONE) {
int srv_event=0;
/* This detection may seem suboptimal, but normally, we have at most 2-3 parallel
* queries, and anything else would be higher overhead, */
#ifdef NO_POLL
switch (qs->state) {
QS_READ_CASES:
srv_event=FD_ISSET(qs->sock,&reads);
break;
QS_WRITE_CASES:
srv_event=FD_ISSET(qs->sock,&writes);
break;
}
#else
do {
PDNSD_ASSERT(ic<pc, "file descriptor not found in poll() array");
k=ic++;
} while(polls[k].fd!=qs->sock);
/*
* In case of an error, reenter the state machine
* to catch it.
*/
switch (qs->state) {
QS_READ_CASES:
srv_event=polls[k].revents&(POLLIN|POLLERR|POLLHUP|POLLNVAL);
break;
QS_WRITE_CASES:
srv_event=polls[k].revents&(POLLOUT|POLLERR|POLLHUP|POLLNVAL);
break;
}
#endif
if (srv_event) {
--nevents;
retryquery2:
rv=p_exec_query(&ent, name, thint, qs,&ns,c_soa);
if (rv==RC_OK) {
int authok;
DEBUG_PDNSDA_MSG("Query to %s succeeded.\n", PDNSDA2STR(PDNSD_A(qs)));
if((authok=auth_ok(q, name, thint, ent, hops, qslist, qhlist, qs, ns, &serv))) {
if(authok>=0) {
if(!qs->failed
#if !defined(NO_TCP_QUERIES) && !defined(NO_UDP_QUERIES)
&& !(qs->qm==UDP_TCP && qs->tc)
#endif
)
{
qse=qs;
goto done;
}
}
else
goto free_ent_return_failed;
}
save_query_result(ent,qs,ns,serv,authok);
#if !defined(NO_TCP_QUERIES) && !defined(NO_UDP_QUERIES)
if(qs->qm==UDP_TCP && qs->tc) {
switch_to_tcp(qs);
DEBUG_PDNSDA_MSG("Reply from %s was truncated. Trying again using TCP.\n",
PDNSDA2STR(PDNSD_A(qs)));
goto retryquery2;
}
#endif
}
else if (rv==RC_NAMEERR || rv==RC_FATALERR) {
goto done;
}
}
}
/* recheck, this might have changed after the last p_exec_query */
if (qs->state==QS_DONE && i==dc)
dc++;
}
if(nevents>0) {
/* We have not managed to handle all the events reported by poll/select.
Better call it quits, or we risk getting caught in a wasteful cycle.
*/
if(++poll_errs<=MAXPOLLERRS)
log_error("%d unhandled poll/select event(s) in p_recursive_query() at %s, line %d.",nevents,__FILE__,__LINE__);
rv=RC_SERVFAIL;
goto done;
}
} while (dc<mc);
}
}
goto cancel_queries;
free_ent_return_failed:
free_cent(ent DBG1);
pdnsd_free(ent);
rv=RC_FATALERR;
done:
if (entsave) {
/* We have or will get an authoritative answer, or we have encountered an error.
Free the non-authoritative answer. */
free_cent(entsave DBG1);
pdnsd_free(entsave);
entsave=NULL;
del_qserv(servsave);
dlist_free(nssave);
}
cancel_queries:
/* Cancel any remaining queries. */
for (i=dc;i<mc;i++)
p_cancel_query(&DA_INDEX(q,i));
{
/* See if any servers need to be retested for availability.
We build up a list of addresses rather than call
sched_server_test() separately for each address to
reduce the overhead caused by locking and signaling */
int n=0;
for (i=0;i<mc;i++)
if (DA_INDEX(q,i).needs_testing > 1)
++n;
if(n>0) {
pdnsd_a addrs[n]; /* variable length array */
k=0;
for (i=0;i<mc;i++) {
query_stat_t *qs=&DA_INDEX(q,i);
if (qs->needs_testing > 1)
addrs[k++]= *PDNSD_A(qs);
}
sched_server_test(addrs,n,-1);
}
}
}
if(entsave) {
/*
* If we didn't get rrs from any of the authoritative servers, or the answers were
* unsatisfactory for another reason, take the one we had.
* However, raise the CF_NOCACHE flag, so that it won't be used again (outside the
* cache latency period).
*/
DEBUG_PDNSDA_MSG("Using %s reply from %s.\n",
!(qualval&NOTFAILMASK)? "reportedly failed":
!(qualval&W_NOTFAILED)? "inconsistent":
!(qualval&W_NOTTRUNC)? "truncated":
!(qualval&W_AUTHOK)? "non-authoritative": "good",
PDNSDA2STR(PDNSD_A(qse)));
ent=entsave;
serv=servsave;
ns=nssave;
if(qualval<GOODQUAL) {
if(!(ent->flags&DF_NEGATIVE)) {
int jlim= RRARR_LEN(ent);
for (j=0; j<jlim; ++j) {
rr_set_t *rrs= RRARR_INDEX(ent,j);
if (rrs)
rrs->flags |= CF_NOCACHE;
}
}
else /* Very unlikely, but not impossible. */
ent->flags |= DF_NOCACHE;
}
rv=RC_OK;
}
else if (rv!=RC_OK) {
if(rv==RC_FATALERR) {
DEBUG_MSG("Unrecoverable error encountered while processing query.\n");
rv=RC_SERVFAIL;
}
DEBUG_MSG("%sReturning error code \"%s\"\n",
rv!=RC_NAMEERR? "No query succeeded. ": "",
get_ename(rv));
goto clean_up_return;
}
if(nocache) *nocache=qse->nocache;
if (DA_NEL(serv)>0) {
/* Authority records present. Ask them, because the answer was non-authoritative. */
qstatnode_t qsn={q,qslist};
unsigned char save_ns=ent->c_ns,save_soa=ent->c_soa;
if(qse->aa || qse->ra) {
/* The server claimed to be authoritative or have recursion available,
yet we did not completely trust the answer for some reason.
We will try to ask the servers in the authority records,
but in case we fail, we will save a copy of the answer. */
entsave=ent;
}
else {
free_cent(ent DBG1);
pdnsd_free(ent);
entsave=NULL;
}
rv=p_dns_cached_resolve(serv, name, thint,&ent,hops-1,&qsn,qhlist,time(NULL),c_soa);
if(rv==RC_OK || rv==RC_CACHED || (rv==RC_STALE && !entsave)) {
if(save_ns!=cundef && (ent->c_ns==cundef || ent->c_ns<save_ns))
ent->c_ns=save_ns;
if(save_soa!=cundef && (ent->c_soa==cundef || ent->c_soa<save_soa))
ent->c_soa=save_soa;
goto free_entsave;
}
else if(rv==RC_NAMEERR) {
if(c_soa && save_soa!=cundef && (*c_soa==cundef || *c_soa<save_soa))
*c_soa=save_soa;
free_entsave:
if(entsave) {
free_cent(entsave DBG1);
pdnsd_free(entsave);
}
}
else if(entsave) {
if(rv==RC_STALE) {
free_cent(ent DBG1);
pdnsd_free(ent);
}
DEBUG_PDNSDA_MSG("Using saved reply from %s that claims to %s.\n",
PDNSDA2STR(PDNSD_A(qse)),
qse->aa? "be authoritative": "have recursion available");
ent=entsave;
rv=RC_OK;
}
}
clean_up_return:
/* Always free the serv array before freeing the ns list,
because the serv array contains references to data within the ns list! */
del_qserv(serv);
dlist_free(ns);
if(rv==RC_OK || rv==RC_CACHED || rv==RC_STALE) *entp=ent;
return rv;
# undef save_query_result
}
/* auth_ok returns 1 if we don't need an authoritative answer or
if we can find servers to ask for an authoritative answer.
In the latter case these servers will be added to the *serv list.
A return value of 0 means the answer is not satisfactory in the
previous sense.
A return value of -1 indicates an error.
*/
static int auth_ok(query_stat_array q, const unsigned char *name, int thint, dns_cent_t *ent,
int hops, qstatnode_t *qslist, qhintnode_t *qhlist,
query_stat_t *qse, dlist ns, query_stat_array *serv)
{
int retval=0;
/* If the answer was obtained from a name server which returned a failure code,
the answer is never satisfactory. */
if(qse->failed > 1) return 0;
/*
Look into the query type hint. If it is a wildcard (QT_*), we need an authoritative answer.
Same if there is no record that answers the query.
This test will also succeed if we have a negative cached record. This is done purposely.
*/
#define aa_needed ((thint>=QT_MIN && thint<=QT_MAX) || \
((thint>=T_MIN && thint<=T_MAX) && \
(!have_rr(ent,thint) && !have_rr_CNAME(ent))))
/* We will want to query authoritative servers if all of the following conditions apply:
1) The server from which we got the answer was not configured as "proxy only".
2) The answer is not a negatively cached domain (i.e. the server did not reply with NXDOMAIN).
3) The query type is a wild card (QT_*), or no record answers the query.
4) The answer that we have is non-authoritative.
*/
if(!(qse->auth_serv && !(ent->flags&DF_NEGATIVE) && aa_needed))
return 1;
if(qse->aa) {
/* The reply we have claims to be authoritative.
However, I have seen cases where name servers raise the authority flag incorrectly (groan...),
so as a work-around, we will check whether the domains for which the servers in the ns
list are responsible, match the queried name better than the domain for which the
last server was responsible. */
unsigned char *nsdomain;
if(!qse->nsdomain)
return 1;
nsdomain=dlist_first(ns);
if(!nsdomain)
return 1;
for(;;) {
unsigned int rem,crem;
domain_match(nsdomain,qse->nsdomain,&rem,&crem);
if(!(rem>0 && crem==0))
return 1;
domain_match(nsdomain,name,&rem,NULL);
if(rem!=0)
return 1;
do {
nsdomain=dlist_next(nsdomain);
if(!nsdomain)
goto done_checkauth;
} while(*nsdomain==0xff); /* Skip repeats. */
}
done_checkauth:;
/* The name servers in the ns list are a better match for the queried name than
the server from which we got the last reply, so ignore the aa flag.
*/
#if DEBUG>0
if(debug_p) {
unsigned char dbuf[DNSNAMEBUFSIZE],sdbuf[DNSNAMEBUFSIZE];
nsdomain=dlist_first(ns);
DEBUG_PDNSDA_MSG("The name server %s which is responsible for the %s domain, raised the aa flag, but appears to delegate to the sub-domain %s\n",
PDNSDA2STR(PDNSD_A(qse)),
rhn2str(qse->nsdomain,dbuf,sizeof(dbuf)),
rhn2str(nsdomain,sdbuf,sizeof(sdbuf)));
}
#endif
}
/* The answer was non-authoritative. Try to build a list of addresses of authoritative servers. */
if (hops>0) {
unsigned char *nsdomp, *nsdomain=NULL;
rr_set_t *localrrset=NULL;
rr_bucket_t *localrr=NULL;
for (nsdomp=dlist_first(ns);;) {
unsigned char *nsname=NULL; /* Initialize to inhibit compiler warning. */
int nserva, ia, n;
pdnsd_a2 serva[MAXNAMESERVIPS];
/* Get next name server. */
if(localrr) {
/* Use next locally defined NS record. */
nsname=(unsigned char *)(localrr->data);
localrr= localrr->next;
}
else {
if(localrrset) {
/* clean up rrset */
del_rrset(localrrset DBG1);
localrrset=NULL;
}
if(!nsdomp)
break;
else if(*nsdomp!=0xff) {
/* New domain. */
nsdomain=nsdomp;
if (global.paranoid) {
unsigned int rem;
/* paranoia mode: don't query name servers that are not responsible */
domain_match(nsdomain,name,&rem,NULL);
if (rem!=0) {
#if DEBUG>0
unsigned char nmbuf[DNSNAMEBUFSIZE],dbuf[DNSNAMEBUFSIZE],nsbuf[DNSNAMEBUFSIZE];
DEBUG_MSG("The name server %s is responsible for the %s domain, which does not match %s\n",
rhn2str(nsname,nsbuf,sizeof(nsbuf)),
rhn2str(nsdomain,dbuf,sizeof(dbuf)),
rhn2str(name,nmbuf,sizeof(nmbuf)));
#endif
/* Skip records in ns list for the same domain. */
do {
nsdomp=dlist_next(nsdomp);
} while (nsdomp && *nsdomp==0xff);
continue;
}
}
/* Check if we have locally defined NS records, because
they will override the ones provided by remote servers.
*/
localrrset=lookup_cache_local_rrset(nsdomain,T_NS);
if(localrrset) {
/* Skip records in ns list for the same domain. */
do {
nsdomp=dlist_next(nsdomp);
} while (nsdomp && *nsdomp==0xff);
localrr=localrrset->rrs;
if(!localrr) continue;
nsname=(unsigned char *)(localrr->data);
localrr= localrr->next;
}
else {
nsname=skiprhn(nsdomp);
nsdomp=dlist_next(nsdomp);
}
}
else {
/* domain repeated. */
nsname= nsdomp+1;
nsdomp=dlist_next(nsdomp);
}
}
/* look it up in the cache or resolve it if needed.
The records received should be in the cache now, so it's ok.
*/
nserva=0;
{
const unsigned char *nm=name;
int tp=thint;
qhintnode_t *ql=qhlist;
for(;;) {
if(rhnicmp(nm,nsname) && tp==T_A) {
DEBUG_RHN_MSG("Not looking up address for name server \"%s\": "
"risk of infinite recursion.\n",RHN2STR(nsname));
goto skip_server;
}
if(!ql) break;
nm=ql->nm;
tp=ql->tp;
ql=ql->next;
}
{
qhintnode_t qhn={name,thint,qhlist};
dns_cent_t *servent;
if (r_dns_cached_resolve(nsname,T_A, &servent, hops-1, &qhn,time(NULL),NULL)==RC_OK) {
#ifdef ENABLE_IPV4
if (run_ipv4) {
rr_set_t *rrset=getrrset_A(servent);
rr_bucket_t *rrs;
if (rrset)
for(rrs=rrset->rrs; rrs && nserva<MAXNAMESERVIPS; rrs=rrs->next)
serva[nserva++].ipv4 = *((struct in_addr *)rrs->data);
}
#endif
#ifdef ENABLE_IPV6
ELSE_IPV6 {
rr_set_t *rrset6=getrrset_AAAA(servent);
rr_bucket_t *rrs6= (rrset6? rrset6->rrs: NULL);
rr_set_t *rrset4=getrrset_A(servent);
rr_bucket_t *rrs4= (rrset4? rrset4->rrs: NULL);
while(nserva<MAXNAMESERVIPS) {
if(rrs6) {
serva[nserva].ipv6 = *((struct in6_addr *)rrs6->data);
rrs6=rrs6->next;
if (rrs4) {
/* Store IPv4 address as fallback. */
serva[nserva].ipv4 = *((struct in_addr *)rrs4->data);
rrs4=rrs4->next;
}
else
serva[nserva].ipv4.s_addr=INADDR_ANY;
}
else if (rrs4) {
struct in_addr *ina = (struct in_addr *)rrs4->data;
struct in6_addr *in6a = &serva[nserva].ipv6;
IPV6_MAPIPV4(ina,in6a);
serva[nserva].ipv4.s_addr=INADDR_ANY;
rrs4=rrs4->next;
}
else
break;
++nserva;
}
}
#endif
free_cent(servent DBG1);
pdnsd_free(servent);
}
}
}
#if DEBUG>0
if(nserva==0) {
DEBUG_RHN_MSG("Looking up address for name server \"%s\" failed.\n",RHN2STR(nsname));
}
#endif
n=DA_NEL(*serv);
for(ia=0; ia<nserva; ++ia) {
pdnsd_a2 *pserva= &serva[ia];
int i;
if(is_local_addr(PDNSD_A2_TO_A(pserva)))
continue; /* Do not use local address (as defined in netdev.c). */
/* Skip duplicate addresses. */
for (i=0; i<n; ++i) {
query_stat_t *qs=&DA_INDEX(*serv,i);
if (query_stat_same_inaddr2(qs,pserva))
goto skip_server_addr;
}
{ /* We've got an address. Add it to the list if it wasn't one of the servers we queried. */
query_stat_array qa=q;
qstatnode_t *ql=qslist;
for(;;) {
int i,n=DA_NEL(qa);
for (i=0; i<n; ++i) {
/* If qa[i].state == QS_DONE, then p_exec_query() has been called,
and we should not query this server again */
query_stat_t *qs=&DA_INDEX(qa,i);
if (qs->state==QS_DONE && equiv_inaddr2(PDNSD_A(qs),pserva)) {
DEBUG_PDNSDA_MSG("Not trying name server %s, already queried.\n", PDNSDA2STR(PDNSD_A2_TO_A(pserva)));
goto skip_server_addr;
}
}
if(!ql) break;
qa=ql->qa;
ql=ql->next;
}
}
/* lean query mode is inherited. CF_AUTH and CF_ADDITIONAL are not (as specified
* in CFF_NOINHERIT). */
if (!add_qserv(serv, pserva, 53, qse->timeout, qse->flags&~CFF_NOINHERIT, 0,
qse->lean_query,qse->edns_query,2,0,!global.paranoid,nsdomain,
inherit_rejectlist(qse)?qse->rejectlist:NULL))
{
return -1;
}
retval=1;
skip_server_addr:;
}
skip_server:;
}
#if DEBUG>0
if(!retval) {
DEBUG_PDNSDA_MSG("No remaining authoritative name servers to try in authority section from %s.\n", PDNSDA2STR(PDNSD_A(qse)));
}
#endif
}
else {
DEBUG_MSG("Maximum hops count reached; not trying any more name servers.\n");
}
return retval;
#undef aa_needed
}
/*
* This checks the given name to resolve against the access list given for the server using the
* include=, exclude= and policy= parameters.
*/
static int use_server(servparm_t *s, const unsigned char *name)
{
int i,n=DA_NEL(s->alist);
for (i=0;i<n;i++) {
slist_t *sl=&DA_INDEX(s->alist,i);
unsigned int nrem,lrem;
domain_match(name,sl->domain,&nrem,&lrem);
if(!lrem && (!sl->exact || !nrem))
return sl->rule==C_INCLUDED;
}
if (s->policy==C_SIMPLE_ONLY || s->policy==C_FQDN_ONLY) {
if(rhnsegcnt(name)<=1)
return s->policy==C_SIMPLE_ONLY;
else
return s->policy==C_FQDN_ONLY;
}
return s->policy==C_INCLUDED;
}
#if ALLOW_LOCAL_AAAA
#define serv_has_rejectlist(s) ((s)->reject_a4!=NULL || (s)->reject_a6!=NULL)
#else
#define serv_has_rejectlist(s) ((s)->reject_a4!=NULL)
#endif
/* Take the lists of IP addresses from a server section sp and
convert them into a form that can be used by p_exec_query().
If successful, add_rejectlist returns a new list which is added to the old list rl,
otherwise the return value is NULL.
*/
static rejectlist_t *add_rejectlist(rejectlist_t *rl, servparm_t *sp)
{
int i,na4=DA_NEL(sp->reject_a4);
addr4maskpair_t *a4p;
#if ALLOW_LOCAL_AAAA
int na6=DA_NEL(sp->reject_a6);
addr6maskpair_t *a6p;
#endif
rejectlist_t *rlist = malloc(sizeof(rejectlist_t) + na4*sizeof(addr4maskpair_t)
#if ALLOW_LOCAL_AAAA
+ na6*sizeof(addr6maskpair_t)
#endif
);
if(rlist) {
#if ALLOW_LOCAL_AAAA
/* Store the larger IPv6 addresses first to avoid possible alignment problems. */
rlist->na6 = na6;
a6p = (addr6maskpair_t *)rlist->rdata;
for(i=0;i<na6;++i)
*a6p++ = DA_INDEX(sp->reject_a6,i);
#endif
rlist->na4 = na4;
#if ALLOW_LOCAL_AAAA
a4p = (addr4maskpair_t *)a6p;
#else
a4p = (addr4maskpair_t *)rlist->rdata;
#endif
for(i=0;i<na4;++i)
*a4p++ = DA_INDEX(sp->reject_a4,i);
rlist->policy = sp->rejectpolicy;
rlist->inherit = sp->rejectrecursively;
rlist->next = rl;
}
else {
DEBUG_MSG("Out of memory in add_rejectlist()\n");
}
return rlist;
}
inline static void free_rejectlist(rejectlist_t *rl)
{
while(rl) {
rejectlist_t *next = rl->next;
free(rl);
rl=next;
}
}
/* Lookup addresses of nameservers provided by root servers for a given domain in the cache.
Returns NULL if unsuccessful (or the cache entries have timed out).
*/
static addr2_array lookup_ns(const unsigned char *domain)
{
addr2_array res=NULL;
dns_cent_t *cent=lookup_cache(domain,NULL);
if(cent) {
rr_set_t *rrset=getrrset_NS(cent);
if(rrset && (rrset->flags&CF_ROOTSERV) && !timedout(rrset)) {
rr_bucket_t *rr;
for(rr=rrset->rrs; rr; rr=rr->next) {
dns_cent_t *servent=lookup_cache((unsigned char*)(rr->data),NULL);
int nserva=0;
pdnsd_a2 serva[MAXNAMESERVIPS];
if(servent) {
#ifdef ENABLE_IPV4
if (run_ipv4) {
rr_set_t *rrset=getrrset_A(servent);
rr_bucket_t *rrs;
if (rrset && !timedout(rrset))
for(rrs=rrset->rrs; rrs && nserva<MAXNAMESERVIPS; rrs=rrs->next)
serva[nserva++].ipv4 = *((struct in_addr *)rrs->data);
}
#endif
#ifdef ENABLE_IPV6
ELSE_IPV6 {
rr_set_t *rrset6=getrrset_AAAA(servent);
rr_set_t *rrset4=getrrset_A(servent);
rr_bucket_t *rrs6=NULL, *rrs4=NULL;
if (rrset6 && !(rrset6->flags&CF_NEGATIVE)) {
if(!timedout(rrset6)) {
rrs6= rrset6->rrs;
if (rrs6 && rrset4 && !(rrset4->flags&CF_NEGATIVE)) {
if(timedout(rrset4) || !(rrs4=rrset4->rrs))
/* Treat this as a failure. */
rrs6=NULL;
}
}
}
else if (rrset4 && !timedout(rrset4))
rrs4= rrset4->rrs;
while(nserva<MAXNAMESERVIPS) {
if(rrs6) {
serva[nserva].ipv6 = *((struct in6_addr *)rrs6->data);
rrs6=rrs6->next;
if (rrs4) {
/* Store IPv4 address as fallback. */
serva[nserva].ipv4 = *((struct in_addr *)rrs4->data);
rrs4=rrs4->next;
}
else
serva[nserva].ipv4.s_addr=INADDR_ANY;
}
else if (rrs4) {
struct in_addr *ina = (struct in_addr *)rrs4->data;
struct in6_addr *in6a = &serva[nserva].ipv6;
IPV6_MAPIPV4(ina,in6a);
serva[nserva].ipv4.s_addr=INADDR_ANY;
rrs4=rrs4->next;
}
else
break;
++nserva;
}
}
#endif
free_cent(servent DBG1);
pdnsd_free(servent);
}
if(nserva==0) {
/* Address lookup failed. */
da_free(res); res=NULL;
break;
}
else {
int i, j, n=DA_NEL(res);
for(i=0; i<nserva; ++i) {
pdnsd_a2 *pserva= &serva[i];
/* Skip duplicates */
for (j=0; j<n; ++j) {
pdnsd_a2 *pa= &DA_INDEX(res,j);
if (same_inaddr2_2(pa,pserva))
goto skip_address;
}
if(!(res=DA_GROW1(res))) {
DEBUG_MSG("Out of memory in lookup_ns()\n");
goto free_cent_return;
}
DA_LAST(res)= *pserva;
skip_address:;
}
}
}
}
free_cent_return:
free_cent(cent DBG1);
pdnsd_free(cent);
}
return res;
}
/* Find addresses of root servers by looking them up in the cache or querying (non-recursively)
the name servers in the list provided.
Returns NULL if unsuccessful (or the cache entries have timed out).
*/
addr2_array dns_rootserver_resolv(atup_array atup_a, int port, char edns_query, time_t timeout)
{
addr2_array res=NULL;
dns_cent_t *cent;
static const unsigned char rdomain[1]={0}; /* root-domain name. */
int rc;
rc=simple_dns_cached_resolve(atup_a,port,edns_query,timeout,rdomain,T_NS,&cent);
if(rc==RC_OK) {
rr_set_t *rrset=getrrset_NS(cent);
if(rrset) {
rr_bucket_t *rr;
unsigned nfail=0;
for(rr=rrset->rrs; rr; rr=rr->next) {
dns_cent_t *servent;
int nserva=0;
pdnsd_a2 serva[MAXNAMESERVIPS];
rc=simple_dns_cached_resolve(atup_a,port,edns_query,timeout,
(const unsigned char *)(rr->data),T_A,&servent);
if(rc==RC_OK) {
#ifdef ENABLE_IPV4
if (run_ipv4) {
rr_set_t *rrset=getrrset_A(servent);
rr_bucket_t *rrs;
if (rrset)
for(rrs=rrset->rrs; rrs && nserva<MAXNAMESERVIPS; rrs=rrs->next)
serva[nserva++].ipv4 = *((struct in_addr *)rrs->data);
}
#endif
#ifdef ENABLE_IPV6
ELSE_IPV6 {
rr_set_t *rrset6=getrrset_AAAA(servent);
rr_bucket_t *rrs6= (rrset6? rrset6->rrs: NULL);
rr_set_t *rrset4=getrrset_A(servent);
rr_bucket_t *rrs4= (rrset4? rrset4->rrs: NULL);
while(nserva<MAXNAMESERVIPS) {
if(rrs6) {
serva[nserva].ipv6 = *((struct in6_addr *)rrs6->data);
rrs6=rrs6->next;
if (rrs4) {
/* Store IPv4 address as fallback. */
serva[nserva].ipv4 = *((struct in_addr *)rrs4->data);
rrs4=rrs4->next;
}
else
serva[nserva].ipv4.s_addr=INADDR_ANY;
}
else if (rrs4) {
struct in_addr *ina = (struct in_addr *)rrs4->data;
struct in6_addr *in6a = &serva[nserva].ipv6;
IPV6_MAPIPV4(ina,in6a);
serva[nserva].ipv4.s_addr=INADDR_ANY;
rrs4=rrs4->next;
}
else
break;
++nserva;
}
}
#endif
free_cent(servent DBG1);
pdnsd_free(servent);
}
else {
DEBUG_RHN_MSG("Simple query for %s type A failed (rc: %s)\n",
RHN2STR((const unsigned char *)(rr->data)),get_ename(rc));
}
if(nserva==0) {
/* Address lookup failed. */
DEBUG_RHN_MSG("Failed to obtain address of root server %s in dns_rootserver_resolv()\n",
RHN2STR((const unsigned char *)(rr->data)));
++nfail;
}
else {
int i, j, n=DA_NEL(res);
for(i=0; i<nserva; ++i) {
pdnsd_a2 *pserva= &serva[i];
/* Skip duplicates */
for (j=0; j<n; ++j) {
pdnsd_a2 *pa= &DA_INDEX(res,j);
if (same_inaddr2_2(pa,pserva))
goto skip_address;
}
if(!(res=DA_GROW1(res))) {
DEBUG_MSG("Out of memory in dns_rootserver_resolv()\n");
goto free_cent_return;
}
DA_LAST(res)= *pserva;
skip_address:;
}
}
}
/* At least half of the names should resolve, otherwise we reject the result. */
if(nfail>DA_NEL(res)) {
DEBUG_MSG("Too many root-server resolve failures (%u succeeded, %u failed),"
" rejecting the result.\n", DA_NEL(res),nfail);
da_free(res); res=NULL;
}
}
free_cent_return:
free_cent(cent DBG1);
pdnsd_free(cent);
}
else {
DEBUG_MSG("Simple query for root domain type NS failed (rc: %s)\n",get_ename(rc));
}
return res;
}
static int p_dns_resolve(const unsigned char *name, int thint, dns_cent_t **cachedp, int hops, qhintnode_t *qhlist,
unsigned char *c_soa)
{
int i,n,rc;
int one_up=0,seenrootserv=0;
query_stat_array serv=NULL;
rejectlist_t *rejectlist=NULL;
/* try the servers in the order of their definition */
lock_server_data();
n=DA_NEL(servers);
for (i=0;i<n;++i) {
servparm_t *sp=&DA_INDEX(servers,i);
if(sp->rootserver<=1 && use_server(sp,name)) {
int m=DA_NEL(sp->atup_a);
if(m>0) {
rejectlist_t *rjl=NULL;
int j=0, jstart=0;
if(sp->rand_servers) j=jstart=random()%m;
do {
atup_t *at=&DA_INDEX(sp->atup_a,j);
if (at->is_up) {
if(sp->rootserver) {
if(!seenrootserv) {
int nseg,mseg=1,l=0;
const unsigned char *topdomain=NULL;
addr2_array adrs=NULL;
seenrootserv=1;
nseg=rhnsegcnt(name);
if(nseg>=2) {
static const unsigned char rhn_arpa[6]= {4,'a','r','p','a',0};
unsigned int rem;
/* Check if the queried name ends in "arpa" */
domain_match(rhn_arpa, name, &rem,NULL);
if(rem==0) mseg=3;
}
if(nseg<=mseg) {
if(nseg>0) mseg=nseg-1; else mseg=0;
}
for(;mseg>=1; --mseg) {
topdomain=skipsegs(name,nseg-mseg);
adrs=lookup_ns(topdomain);
l=DA_NEL(adrs);
if(l>0) break;
if(adrs) da_free(adrs);
}
if(l>0) {
/* The name servers for this top level domain have been found in the cache.
Instead of asking the root server, we will use this cached information.
*/
int k=0, kstart=0;
if(sp->rand_servers) k=kstart=random()%l;
if(serv_has_rejectlist(sp) && sp->rejectrecursively && !rjl) {
rjl=add_rejectlist(rejectlist,sp);
if(!rjl) {one_up=0; da_free(adrs); goto done;}
rejectlist=rjl;
}
do {
one_up=add_qserv(&serv, &DA_INDEX(adrs,k), 53, sp->timeout,
mk_flag_val(sp)&~CFF_NOINHERIT, sp->nocache,
sp->lean_query, sp->edns_query, 2, 0,
!global.paranoid, topdomain, rjl);
if(!one_up) {
da_free(adrs);
goto done;
}
if(++k==l) k=0;
} while(k!=kstart);
da_free(adrs);
DEBUG_PDNSDA_MSG("Not querying root-server %s, using cached information instead.\n",
PDNSDA2STR(PDNSD_A2_TO_A(&at->a)));
seenrootserv=2;
break;
}
}
else if(seenrootserv==2)
break;
}
if(serv_has_rejectlist(sp) && !rjl) {
rjl=add_rejectlist(rejectlist,sp);
if(!rjl) {one_up=0; goto done;}
rejectlist=rjl;
}
one_up=add_qserv(&serv, &at->a, sp->port, sp->timeout,
mk_flag_val(sp), sp->nocache, sp->lean_query, sp->edns_query,
sp->rootserver?3:(!sp->is_proxy),
needs_testing(sp), 1, NULL, rjl);
if(!one_up)
goto done;
}
if(++j==m) j=0;
} while(j!=jstart);
}
}
}
done:
unlock_server_data();
if (one_up) {
dns_cent_t *cached;
int nocache;
rc=p_recursive_query(serv, name, thint, &cached, &nocache, hops, NULL, qhlist, c_soa);
if (rc==RC_OK) {
if (!nocache) {
dns_cent_t *tc;
add_cache(cached);
if ((tc=lookup_cache(name,NULL))) {
/* The cache may hold more information than the recent query yielded.
* try to get the merged record. If that fails, revert to the new one. */
free_cent(cached DBG1);
pdnsd_free(cached);
cached=tc;
/* rc=RC_CACHED; */
} else
DEBUG_MSG("p_dns_resolve: merging answer with cache failed, using local cent copy.\n");
} else
DEBUG_MSG("p_dns_resolve: nocache.\n");
*cachedp=cached;
}
else if(rc==RC_CACHED || rc==RC_STALE)
*cachedp=cached;
}
else {
DEBUG_MSG("No server is marked up and allowed for this domain.\n");
rc=RC_SERVFAIL; /* No server up */
}
del_qserv(serv);
free_rejectlist(rejectlist);
return rc;
}
static int set_flags_ttl(unsigned short *flags, time_t *ttl, dns_cent_t *cached, int tp)
{
rr_set_t *rrset=getrrset(cached,tp);
if (rrset) {
time_t t;
*flags|=rrset->flags;
t=rrset->ts+CLAT_ADJ(rrset->ttl);
if (!*ttl || *ttl>t)
*ttl=t;
return 1;
}
return 0;
}
static void set_all_flags_ttl(unsigned short *flags, time_t *ttl, dns_cent_t *cached)
{
int i, ilim= RRARR_LEN(cached);
for(i=0; i<ilim; ++i) {
rr_set_t *rrset= RRARR_INDEX(cached,i);
if (rrset) {
time_t t;
*flags|=rrset->flags;
t=rrset->ts+CLAT_ADJ(rrset->ttl);
if (!*ttl || *ttl>t)
*ttl=t;
}
}
}
/*
Lookup name in the cache, and if records of type thint are found, check whether a requery is needed.
Possible returns values are:
RC_OK: the name is locally defined.
RC_NAMEERR: the name is locally negatively cached.
RC_CACHED: name was found in the cache, requery not needed.
RC_STALE: name was found in the cache, but requery is needed.
RC_NOTCACHED: name was not found in the cache.
*/
static int lookup_cache_status(const unsigned char *name, int thint, dns_cent_t **cachedp, unsigned short *flagsp,
time_t queryts, unsigned char *c_soa)
{
dns_cent_t *cached;
int rc=RC_NOTCACHED;
int wild=0;
unsigned short flags=0;
if ((cached=lookup_cache(name,&wild))) {
short int neg=0,timed=0,need_req=0;
time_t ttl=0;
if (cached->flags&DF_LOCAL) {
#if DEBUG>0
{
char dflagstr[DFLAGSTRLEN];
DEBUG_RHN_MSG("Entry found in cache for '%s' with dflags=%s.\n",
RHN2STR(cached->qname),dflags2str(cached->flags,dflagstr));
}
#endif
if((cached->flags&DF_NEGATIVE) || wild==w_locnerr) {
if(c_soa) {
if(cached->c_soa!=cundef)
*c_soa=cached->c_soa;
else if(have_rr_SOA(cached))
*c_soa=rhnsegcnt(cached->qname);
else {
unsigned char *owner=getlocalowner(cached->qname,T_SOA);
if(owner)
*c_soa=rhnsegcnt(owner);
}
}
free_cent(cached DBG1);
pdnsd_free(cached);
rc= RC_NAMEERR;
goto return_rc;
}
else {
rc= RC_OK;
goto return_rc_cent;
}
}
DEBUG_RHN_MSG("Record found in cache for %s\n",RHN2STR(cached->qname));
if (cached->flags&DF_NEGATIVE) {
if ((ttl=cached->neg.ts+CLAT_ADJ(cached->neg.ttl))>=queryts)
neg=1;
else
timed=1;
} else {
if (thint==QT_ALL) {
set_all_flags_ttl(&flags, &ttl, cached);
}
else if (!set_flags_ttl(&flags, &ttl, cached, T_CNAME) || (getrrset_CNAME(cached)->flags&CF_NEGATIVE)) {
flags=0; ttl=0;
if (thint>=T_MIN && thint<=T_MAX) {
if (set_flags_ttl(&flags, &ttl, cached, thint))
neg=getrrset(cached,thint)->flags&CF_NEGATIVE && ttl>=queryts;
}
else if (thint==QT_MAILB) {
set_flags_ttl(&flags, &ttl, cached, T_MB);
set_flags_ttl(&flags, &ttl, cached, T_MG);
set_flags_ttl(&flags, &ttl, cached, T_MR);
}
else if (thint==QT_MAILA) {
set_flags_ttl(&flags, &ttl, cached, T_MD);
set_flags_ttl(&flags, &ttl, cached, T_MF);
}
}
if(!(flags&CF_LOCAL)) {
if (thint==QT_ALL) {
if(!(cached->flags&DF_AUTH))
need_req=1;
}
else if (thint>=QT_MIN && thint<=QT_MAX) {
if(!(flags&CF_AUTH && !(flags&CF_ADDITIONAL)))
need_req=1;
}
if (ttl<queryts)
timed=1;
}
}
#if DEBUG>0
{
char dflagstr[DFLAGSTRLEN],cflagstr[CFLAGSTRLEN];
DEBUG_MSG("Requery decision: dflags=%s, cflags=%s, req=%i, neg=%i, timed=%i, %s=%li\n",
dflags2str(cached->flags,dflagstr),cflags2str(flags,cflagstr),need_req,neg,timed,
ttl?"ttl":"timestamp",(long)(ttl?(ttl-queryts):ttl));
}
#endif
rc = (!neg && (need_req || timed))? RC_STALE: RC_CACHED;
return_rc_cent:
*cachedp=cached;
}
return_rc:
if(flagsp) *flagsp=flags;
return rc;
}
/*
* Resolve records for name into dns_cent_t, type thint.
* q is the set of servers to query from. Set q to NULL if you want to ask the servers registered with pdnsd.
* qslist should refer to a list of server arrays already used higher up the calling chain (may be NULL).
* p_dns_cached_resolve() returns one of the following values:
* RC_OK means that the name was successfully resolved by querying other servers.
* RC_CACHED or RC_STALE means that the name was found in the cache.
* RC_NAMEERR or RC_SERVFAIL indicates a resolve error.
*/
static int p_dns_cached_resolve(query_stat_array q, const unsigned char *name, int thint, dns_cent_t **cachedp,
int hops, qstatnode_t *qslist, qhintnode_t *qhlist, time_t queryts,
unsigned char *c_soa)
{
dns_cent_t *cached=NULL;
int rc;
unsigned short flags=0;
DEBUG_RHN_MSG("Starting cached resolve for: %s, query %s\n",RHN2STR(name),get_tname(thint));
rc= lookup_cache_status(name, thint, &cached, &flags,queryts,c_soa);
if(rc==RC_OK) {
/* Locally defined record. */
*cachedp=cached;
return RC_CACHED;
}
else if(rc==RC_NAMEERR) /* Locally negated name. */
return RC_NAMEERR;
/* update server records set onquery */
if(global.onquery) test_onquery();
if (global.lndown_kluge && !(flags&CF_LOCAL)) {
int i,n,linkdown=1;
lock_server_data();
n=DA_NEL(servers);
for(i=0;i<n;++i) {
servparm_t *sp=&DA_INDEX(servers,i);
if(sp->rootserver<=1) {
int j,m=DA_NEL(sp->atup_a);
for(j=0;j<m;++j) {
if (DA_INDEX(sp->atup_a,j).is_up) {
linkdown=0;
goto done;
}
}
}
}
done:
unlock_server_data();
if (linkdown) {
DEBUG_MSG("Link is down.\n");
rc=RC_SERVFAIL;
goto cleanup_return;
}
}
if (rc!=RC_CACHED) {
dns_cent_t *ent;
DEBUG_MSG("Trying name servers.\n");
if (q)
rc=p_recursive_query(q,name,thint, &ent,NULL,hops,qslist,qhlist,c_soa);
else
rc=p_dns_resolve(name,thint, &ent,hops,qhlist,c_soa);
if(rc==RC_OK || rc==RC_CACHED || rc==RC_STALE) {
if (cached) {
free_cent(cached DBG1);
pdnsd_free(cached);
}
cached=ent;
}
else if (rc==RC_SERVFAIL && cached && (flags&CF_NOPURGE)) {
/* We could not get a new record, but we have a timed-out cached one
with the nopurge flag set. This means that we shall use it even
if timed out when no new one is available*/
DEBUG_MSG("Falling back to cached record.\n");
rc=RC_STALE;
}
else
goto cleanup_return;
} else {
DEBUG_MSG("Using cached record.\n");
}
*cachedp=cached;
return rc;
cleanup_return:
if(cached) {
free_cent(cached DBG1);
pdnsd_free(cached);
}
return rc;
}
/* r_dns_cached_resolve() is like p_dns_cached_resolve(), except that r_dns_cached_resolve()
will not return negatively cached entries, but returns RC_NAMEERR instead.
It also does not return RC_CACHED or RC_STALE, but RC_OK instead.
*/
int r_dns_cached_resolve(unsigned char *name, int thint, dns_cent_t **cachedp,
int hops, qhintnode_t *qhlist, time_t queryts,
unsigned char *c_soa)
{
dns_cent_t *cached;
int rc=p_dns_cached_resolve(NULL,name,thint,&cached,hops,NULL,qhlist,queryts,c_soa);
if(rc==RC_OK || rc==RC_CACHED || rc==RC_STALE) {
if(cached->flags&DF_NEGATIVE) {
if(c_soa)
*c_soa=cached->c_soa;
free_cent(cached DBG1);
pdnsd_free(cached);
return RC_NAMEERR;
}
else {
*cachedp=cached;
return RC_OK;
}
}
return rc;
}
static int simple_dns_cached_resolve(atup_array atup_a, int port, char edns_query, time_t timeout,
const unsigned char *name, int thint, dns_cent_t **cachedp)
{
dns_cent_t *cached=NULL;
int rc;
DEBUG_RHN_MSG("Starting simple cached resolve for: %s, query %s\n",RHN2STR(name),get_tname(thint));
rc= lookup_cache_status(name, thint, &cached, NULL, time(NULL), NULL);
if(rc==RC_OK) {
/* Locally defined record. */
*cachedp=cached;
return RC_OK;
}
else if(rc==RC_NAMEERR) /* Locally negated name. */
return RC_NAMEERR;
if (rc!=RC_CACHED) {
query_stat_array qserv;
int j,m;
if (cached) {
free_cent(cached DBG1);
pdnsd_free(cached);
cached=NULL;
}
DEBUG_MSG("Trying name servers.\n");
qserv=NULL;
m=DA_NEL(atup_a);
for(j=0; j<m; ++j) {
if(!add_qserv(&qserv, &DA_INDEX(atup_a,j).a, port, timeout, 0, 0, 1, edns_query, 0, 0, 1, NULL, NULL)) {
/* Note: qserv array already cleaned up by add_qserv() */
return RC_SERVFAIL;
}
}
rc=p_recursive_query(qserv,name,thint, &cached,NULL,0,NULL,NULL,NULL);
del_qserv(qserv);
if (rc==RC_OK) {
dns_cent_t *tc;
add_cache(cached);
if ((tc=lookup_cache(name,NULL))) {
/* The cache may hold more information than the recent query yielded.
* try to get the merged record. If that fails, revert to the new one. */
free_cent(cached DBG1);
pdnsd_free(cached);
cached=tc;
} else
DEBUG_MSG("simple_dns_cached_resolve: merging answer with cache failed, using local cent copy.\n");
}
else if(!(rc==RC_CACHED || rc==RC_STALE)) /* RC_CACHED and RC_STALE should not be possible. */
return rc;
} else {
DEBUG_MSG("Using cached record.\n");
}
if(cached->flags&DF_NEGATIVE) {
free_cent(cached DBG1);
pdnsd_free(cached);
return RC_NAMEERR;
}
*cachedp=cached;
return RC_OK;
}
/* Check whether a server is responsive by sending it an (empty) query.
rep is the number of times this is tried in case of no reply.
*/
int query_uptest(pdnsd_a *addr, int port, const unsigned char *name, time_t timeout, int rep)
{
query_stat_t qs;
int iter=0,rv;
#ifdef ENABLE_IPV4
if (run_ipv4) {
memset(&qs.a.sin4,0,sizeof(qs.a.sin4));
qs.a.sin4.sin_family=AF_INET;
qs.a.sin4.sin_port=htons(port);
qs.a.sin4.sin_addr=addr->ipv4;
SET_SOCKA_LEN4(qs.a.sin4);
}
#endif
#ifdef ENABLE_IPV6
ELSE_IPV6 {
memset(&qs.a.sin6,0,sizeof(qs.a.sin6));
qs.a.sin6.sin6_family=AF_INET6;
qs.a.sin6.sin6_port=htons(port);
qs.a.sin6.sin6_flowinfo=IPV6_FLOWINFO;
qs.a.sin6.sin6_addr=addr->ipv6;
SET_SOCKA_LEN6(qs.a.sin6);
qs.a4fallback.s_addr=INADDR_ANY;
}
#endif
qs.timeout=timeout;
qs.flags=0;
qs.nocache=0;
qs.auth_serv=0;
qs.lean_query=1;
qs.edns_query=0;
qs.needs_testing=0;
qs.trusted=1;
qs.aa=0;
qs.tc=0;
qs.ra=0;
qs.failed=0;
qs.nsdomain=NULL;
qs.rejectlist=NULL;
try_again:
qs.state=QS_INITIAL;
qs.qm=global.query_method;
qs.s_errno=0;
rv=p_exec_query(NULL, name, T_A, &qs, NULL, NULL);
if(rv==-1) {
time_t ts, tpassed;
for(ts=time(NULL), tpassed=0;; tpassed=time(NULL)-ts) {
int event;
#ifdef NO_POLL
fd_set reads;
fd_set writes;
struct timeval tv;
FD_ZERO(&reads);
FD_ZERO(&writes);
PDNSD_ASSERT(qs.sock<FD_SETSIZE,"socket file descriptor exceeds FD_SETSIZE.");
switch (qs.state) {
QS_READ_CASES:
FD_SET(qs.sock,&reads);
break;
QS_WRITE_CASES:
FD_SET(qs.sock,&writes);
break;
}
tv.tv_sec=timeout>tpassed?timeout-tpassed:0;
tv.tv_usec=0;
/* There is a possible race condition with the arrival of a signal here,
but it is so unlikely to be a problem in practice that doing
this properly is not worth the trouble.
*/
if(is_interrupted_servstat_thread()) {
DEBUG_MSG("server status thread interrupted.\n");
p_cancel_query(&qs);
return 0;
}
event=select(qs.sock+1,&reads,&writes,NULL,&tv);
#else
struct pollfd pfd;
pfd.fd=qs.sock;
switch (qs.state) {
QS_READ_CASES:
pfd.events=POLLIN;
break;
QS_WRITE_CASES:
pfd.events=POLLOUT;
break;
default:
pfd.events=0;
}
/* There is a possible race condition with the arrival of a signal here,
but it is so unlikely to be a problem in practice that doing
this properly is not worth the trouble.
*/
if(is_interrupted_servstat_thread()) {
DEBUG_MSG("server status thread interrupted.\n");
p_cancel_query(&qs);
return 0;
}
event=poll(&pfd,1,timeout>tpassed?(timeout-tpassed)*1000:0);
#endif
if (event<0) {
if(errno==EINTR && is_interrupted_servstat_thread()) {
DEBUG_MSG("poll/select interrupted in server status thread.\n");
}
else
log_warn("poll/select failed: %s",strerror(errno));
p_cancel_query(&qs);
return 0;
}
if(event==0) {
/* timed out */
p_cancel_query(&qs);
if(++iter<rep) goto try_again;
return 0;
}
event=0;
#ifdef NO_POLL
switch (qs.state) {
QS_READ_CASES:
event=FD_ISSET(qs.sock,&reads);
break;
QS_WRITE_CASES:
event=FD_ISSET(qs.sock,&writes);
break;
}
#else
switch (qs.state) {
QS_READ_CASES:
event=pfd.revents&(POLLIN|POLLERR|POLLHUP|POLLNVAL);
break;
QS_WRITE_CASES:
event=pfd.revents&(POLLOUT|POLLERR|POLLHUP|POLLNVAL);
break;
}
#endif
if(event) {
rv=p_exec_query(NULL, name, T_A, &qs, NULL, NULL);
if(rv!=-1) break;
}
else {
if(++poll_errs<=MAXPOLLERRS)
log_error("Unhandled poll/select event in query_uptest() at %s, line %d.",__FILE__,__LINE__);
p_cancel_query(&qs);
return 0;
}
}
}
return (rv!=RC_SERVFAIL && rv!=RC_FATALERR);
}