diff options
Diffstat (limited to 'contrib/bind/named/ns_resp.c')
-rw-r--r-- | contrib/bind/named/ns_resp.c | 2743 |
1 files changed, 2743 insertions, 0 deletions
diff --git a/contrib/bind/named/ns_resp.c b/contrib/bind/named/ns_resp.c new file mode 100644 index 0000000..f64306e --- /dev/null +++ b/contrib/bind/named/ns_resp.c @@ -0,0 +1,2743 @@ +#if !defined(lint) && !defined(SABER) +static char sccsid[] = "@(#)ns_resp.c 4.65 (Berkeley) 3/3/91"; +static char rcsid[] = "$Id: ns_resp.c,v 8.27 1996/08/05 08:31:30 vixie Exp $"; +#endif /* not lint */ + +/* + * ++Copyright++ 1986, 1988, 1990 + * - + * Copyright (c) 1986, 1988, 1990 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * - + * Portions Copyright (c) 1993 by Digital Equipment Corporation. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies, and that + * the name of Digital Equipment Corporation not be used in advertising or + * publicity pertaining to distribution of the document or software without + * specific, written prior permission. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT + * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + * - + * --Copyright-- + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/file.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <arpa/inet.h> +#include <syslog.h> +#include <errno.h> +#include <stdio.h> +#include <resolv.h> + +#include "named.h" + +static void check_root __P((void)), + check_ns __P((void)); + +static u_int8_t norootlogged[MAXCLASS]; /* XXX- should be a bitmap */ + +static const char skipnameFailedAnswer[] = "skipname failed in answer", + skipnameFailedAuth[] = "skipname failed in authority", + skipnameFailedQuery[] = "skipname failed in query", + outofDataQuery[] = "ran out of data in query", + outofDataAnswer[] = "ran out of data in answer", + notSingleQuery[] = "not exactly one query", + expandFailedQuery[] = "dn_expand failed in query", + expandFailedAnswer[] = "dn_expand failed in answer", + expandFailedAuth[] = "dn_expand failed in authority", + outofDataAuth[] = "ran out of data in authority", + dlenOverrunAnswer[] = "dlen overrun in answer", + dlenOverrunAuth[] = "dlen overrun in authority", + dlenUnderrunAnswer[] = "dlen underrun in answer", + outofDataFinal[] = "out of data in final pass", + outofDataAFinal[] = "out of data after final pass", + badNameFound[] = "found an invalid domain name"; + +static char * +learntFrom(qp, server) + struct qinfo *qp; + struct sockaddr_in *server; +{ + static char *buf = NULL; + char *a, *ns, *na; + struct databuf *db; + char nsbuf[20]; + char abuf[20]; + int i; + + if (buf) { + free(buf); + buf = NULL; + } + + a = ns = na = "<Not Available>"; + + for (i = 0; i < (int)qp->q_naddr; i++) { + if (qp->q_addr[i].ns_addr.sin_addr.s_addr == + server->sin_addr.s_addr) { + db = qp->q_addr[i].ns; + if (db) { +#ifdef STATS + if (db->d_ns) { + strcpy(nsbuf, + inet_ntoa(db->d_ns->addr)); + ns = nsbuf; + } else { + ns = zones[db->d_zone].z_origin; + } +#endif + +#ifdef NCACHE + if (!db->d_rcode) +#endif + na = (char*)qp->q_addr[i].ns->d_data; + } + +#ifdef STATS + db = qp->q_addr[i].nsdata; + if (db) { + if (db->d_ns) { + strcpy(abuf, + inet_ntoa(db->d_ns->addr)); + a = abuf; + } else { + a = zones[db->d_zone].z_origin; + } + } +#endif + break; + } + } + + if ((a == ns) && (ns == na)) /* all "UNKNOWN" */ + return (""); + +#ifdef STATS +# define LEARNTFROM " '%s': learnt (A=%s,NS=%s)" +#else +# define LEARNTFROM " '%s'" +#endif + buf = malloc(strlen(a = (*a ? a : "\".\"")) + + strlen(ns = (*ns ? ns : "\".\"")) + + strlen(na = (*na ? na : "\".\"")) + + sizeof(LEARNTFROM)); + if (!buf) + return (""); + sprintf(buf, LEARNTFROM, na, a, ns); + return (buf); +} + +void +ns_resp(msg, msglen) + u_char *msg; + int msglen; +{ + register struct qinfo *qp; + register HEADER *hp; + register struct qserv *qs; + register struct databuf *ns, *ns2; + register u_char *cp; + u_char *eom = msg + msglen; + register u_char *tempcp; +#ifdef VALIDATE + struct sockaddr_in *server = &from_addr; + struct { char *name; int type, class; u_int cred; } defer_rm[99]; + int defer_rm_count; +#endif + struct sockaddr_in *nsa; + struct databuf *nsp[NSMAX]; + int i, c, n, qdcount, ancount, aucount, nscount, arcount; + int qtype, qclass, dbflags; + int restart; /* flag for processing cname response */ + int validanswer; + int cname; + int count, founddata, foundname; + int buflen; + int newmsglen; + char name[MAXDNAME], qname[MAXDNAME], msgbuf[MAXDNAME*2]; + char *dname; + const char *fname; + const char *formerrmsg = "brain damage"; + u_char newmsg[PACKETSZ]; + u_char **dpp, *tp; + time_t rtrip; + struct hashbuf *htp; + struct namebuf *np; + struct netinfo *lp; + struct fwdinfo *fwd; + + nameserIncr(from_addr.sin_addr, nssRcvdR); +#ifdef DATUMREFCNT + nsp[0] = NULL; +#endif + hp = (HEADER *) msg; + if ((qp = qfindid(hp->id)) == NULL ) { + dprintf(1, (ddt, "DUP? dropped (id %d)\n", ntohs(hp->id))); + nameserIncr(from_addr.sin_addr, nssRcvdDupR); + return; + } + + dprintf(2, (ddt, "Response (%s %s %s) nsid=%d id=%d\n", + (qp->q_flags & Q_SYSTEM) ?"SYSTEM" :"USER", + (qp->q_flags & Q_PRIMING) ?"PRIMING" :"NORMAL", + (qp->q_flags & Q_ZSERIAL) ?"ZSERIAL" :"-", + ntohs(qp->q_nsid), ntohs(qp->q_id))); + + /* + * Here we handle high level formatting problems by parsing the header. + */ + qdcount = ntohs(hp->qdcount); + ancount = ntohs(hp->ancount); + aucount = ntohs(hp->nscount); /* !!! */ + arcount = ntohs(hp->arcount); + free_addinfo(); /* sets addcount to zero */ + cp = msg + HFIXEDSZ; + dpp = dnptrs; + *dpp++ = msg; + if ((*cp & INDIR_MASK) == 0) + *dpp++ = cp; + *dpp = NULL; + if (qdcount == 1) { + n = dn_expand(msg, eom, cp, qname, sizeof(qname)); + if (n <= 0) { + formerrmsg = expandFailedQuery; + goto formerr; + } + cp += n; + GETSHORT(qtype, cp); + GETSHORT(qclass, cp); + if (!ns_nameok(qname, qclass, response_trans, + ns_ownercontext(qtype, response_trans))) { + formerrmsg = badNameFound; + goto formerr; + } + if (cp > eom) { + formerrmsg = outofDataQuery; + goto formerr; + } + if (qp->q_msg && qp->q_msglen && + !res_nameinquery(qname, qtype, qclass, + qp->q_msg, qp->q_msg + qp->q_msglen)) { + sprintf(msgbuf, + "query section mismatch (%s %s %s)", + qname, p_class(qclass), p_type(qtype)); + formerrmsg = msgbuf; + goto formerr; + } + } else { + /* Pedantic. */ + qname[0] = '\0'; + qtype = 0; + qclass = 0; + } + + /* cp now points after the query section. */ + + /* + * Here we handle bad responses from servers. + * Several possibilities come to mind: + * The server is sick and returns SERVFAIL + * The server returns some garbage opcode (it's sick) + * The server can't understand our query and return FORMERR + * In all these cases, we drop the packet, disable retries on + * this server and immediately force a retry. + */ + if ((hp->rcode != NOERROR && hp->rcode != NXDOMAIN) + || (hp->opcode != QUERY +#ifdef BIND_NOTIFY + && hp->opcode != NS_NOTIFY_OP +#endif + )) { + dprintf(2, (ddt, "resp: error (ret %d, op %d), dropped\n", + hp->rcode, hp->opcode)); + switch (hp->rcode) { + case SERVFAIL: + nameserIncr(from_addr.sin_addr, nssRcvdFail); + break; + case FORMERR: + nameserIncr(from_addr.sin_addr, nssRcvdFErr); + break; + default: + nameserIncr(from_addr.sin_addr, nssRcvdErr); + break; + } + /* mark server as bad */ + if (!qp->q_fwd) + for (i = 0; i < (int)qp->q_naddr; i++) + if (qp->q_addr[i].ns_addr.sin_addr.s_addr + == from_addr.sin_addr.s_addr) + qp->q_addr[i].nretry = MAXRETRY; + /* + * XXX: doesn't handle responses sent from the wrong + * interface on a multihomed server. + */ + if (qp->q_fwd || + qp->q_addr[qp->q_curaddr].ns_addr.sin_addr.s_addr + == from_addr.sin_addr.s_addr) + retry(qp); + return; + } + + if (qdcount != 1) { + /* We don't generate or forward these (yet). */ + formerrmsg = notSingleQuery; + goto formerr; + } + +#ifdef ALLOW_UPDATES + if ( (hp->rcode == NOERROR) && + (hp->opcode == UPDATEA || hp->opcode == UPDATED || + hp->opcode == UPDATEDA || hp->opcode == UPDATEM || + hp->opcode == UPDATEMA) ) { + /* + * Update the secondary's copy, now that the primary + * successfully completed the update. Zone doesn't matter + * for dyn. update -- doupdate calls findzone to find it + */ + /* XXX - DB_C_AUTH may be wrong */ + (void) doupdate(qp->q_msg, qp->q_msglen, qp->q_msg + HFIXEDSZ, + 0, (struct databuf *)0, 0, DB_C_AUTH); + dprintf(3, (ddt, "resp: leaving, UPDATE*\n")); + /* return code filled in by doupdate */ + goto return_msg; + } +#endif /* ALLOW_UPDATES */ + + /* + * Determine if the response came from a forwarder. Packets from + * anyplace not listed as a forwarder or as a server to whom we + * might have forwarded the query will be dropped. + */ + for (fwd = fwdtab; fwd != (struct fwdinfo *)NULL; fwd = fwd->next) { + if (fwd->fwdaddr.sin_addr.s_addr == + from_addr.sin_addr.s_addr) { + /* XXX - should put this in STATS somewhere. */ + break; + } + } + /* + * XXX: note bad ambiguity here. if one of our forwarders is also + * a delegated server for some domain, then we will not update + * the RTT information on any replies we get from those servers. + * Workaround: disable recursion on authoritative servers so that + * the ambiguity does not arise. + */ + /* + * If we weren't using a forwarder, find the qinfo pointer and update + * the rtt and fact that we have called on this server before. + */ + if (fwd == (struct fwdinfo *)NULL) { + struct timeval *stp; + + for (n = 0, qs = qp->q_addr; (u_int)n < qp->q_naddr; n++, qs++) + if (qs->ns_addr.sin_addr.s_addr == + from_addr.sin_addr.s_addr) + break; + if ((u_int)n >= qp->q_naddr) { + if (!haveComplained((char*)from_addr.sin_addr.s_addr, + "unexpected source")) { + syslog(LOG_INFO, + "Response from unexpected source (%s)", + sin_ntoa(&from_addr)); + } + /* + * We don't know who this response came from so it + * gets dropped on the floor. + */ + return; + } + stp = &qs->stime; + + /* Handle response from different (untried) interface */ + if ((qs->ns != NULL) && (stp->tv_sec == 0)) { + ns = qs->ns; + while (qs > qp->q_addr + && (qs->stime.tv_sec == 0 || qs->ns != ns)) + qs--; + *stp = qs->stime; + /* XXX - sometimes stp still ends up pointing to + * a zero timeval, in spite of the above attempt. + * Why? What should we do about it? + */ + dprintf(1, (ddt, + "Response from unused address %s, assuming %s\n", + sin_ntoa(&from_addr), + sin_ntoa(&qs->ns_addr))); + /* XXX - catch aliases here */ + } + + /* compute query round trip time */ + /* XXX - avoid integer overflow, which is quite likely if stp + * points to a zero timeval (see above). + * rtrip is of type time_t, which we assume is at least + * as big as an int. + */ + if ((tt.tv_sec - stp->tv_sec) > (INT_MAX-999)/1000) { + rtrip = INT_MAX; + } else { + rtrip = ((tt.tv_sec - stp->tv_sec) * 1000 + + (tt.tv_usec - stp->tv_usec) / 1000); + } + + dprintf(3, (ddt, "stime %lu/%lu now %lu/%lu rtt %ld\n", + (u_long)stp->tv_sec, (u_long)stp->tv_usec, + (u_long)tt.tv_sec, (u_long)tt.tv_usec, + (long)rtrip)); + + /* prevent floating point overflow, limit to 1000 sec */ + if (rtrip > 1000000) { + rtrip = 1000000; + } + ns = qs->nsdata; + /* + * Don't update nstime if this doesn't look + * like an address databuf now. XXX + */ + if (ns && (ns->d_type==T_A) && (ns->d_class==qs->ns->d_class)){ + if (ns->d_nstime == 0) + ns->d_nstime = (u_int32_t)rtrip; + else + ns->d_nstime = (u_int32_t) + (ns->d_nstime * ALPHA + + + (1-ALPHA) * (u_int32_t)rtrip); + /* prevent floating point overflow, + * limit to 1000 sec + */ + if (ns->d_nstime > 1000000) + ns->d_nstime = 1000000; + } + + /* + * Record the source so that we do not use this NS again. + */ + if (ns && qs->ns && (qp->q_nusedns < NSMAX)) { + qp->q_usedns[qp->q_nusedns++] = qs->ns; + dprintf(2, (ddt, "NS #%d addr %s used, rtt %d\n", + n, sin_ntoa(&qs->ns_addr), + ns->d_nstime)); + } + + /* + * Penalize those who had earlier chances but failed + * by multiplying round-trip times by BETA (>1). + * Improve nstime for unused addresses by applying GAMMA. + * The GAMMA factor makes unused entries slowly + * improve, so they eventually get tried again. + * GAMMA should be slightly less than 1. + * Watch out for records that may have timed out + * and are no longer the correct type. XXX + */ + + for (n = 0, qs = qp->q_addr; + (u_int)n < qp->q_naddr; + n++, qs++) { + ns2 = qs->nsdata; + if ((!ns2) || (ns2 == ns)) + continue; + if (ns2->d_type != T_A || + ns2->d_class != qs->ns->d_class) /* XXX */ + continue; + if (qs->stime.tv_sec) { + if (ns2->d_nstime == 0) + ns2->d_nstime = (u_int32_t)(rtrip * BETA); + else + ns2->d_nstime = (u_int32_t)( + ns2->d_nstime * BETA + (1-ALPHA) * rtrip + ); + if (ns2->d_nstime > 1000000) + ns2->d_nstime = 1000000; + } else + ns2->d_nstime = (u_int32_t)(ns2->d_nstime * GAMMA); + dprintf(2, (ddt, "NS #%d %s rtt now %d\n", n, + sin_ntoa(&qs->ns_addr), + ns2->d_nstime)); + } + } + +#ifdef BIND_NOTIFY + /* for now, NOTIFY isn't defined for ANCOUNT!=0, AUCOUNT!=0, + * or ADCOUNT!=0. therefore the only real work to be done for + * a NOTIFY-QR is to remove it from the query queue. + */ + if (hp->opcode == NS_NOTIFY_OP) { + qremove(qp); + return; + } +#endif + +#ifdef LAME_DELEGATION + /* + * Non-authoritative, no answer, no error + */ + if (qdcount == 1 && hp->rcode == NOERROR && !hp->aa && ancount == 0 + && aucount > 0 +#ifdef BIND_NOTIFY + && hp->opcode != NS_NOTIFY_OP +#endif + ) { + u_char *tp; + int type, class; +#ifdef DEBUG + if (debug > 0) + fp_nquery(msg, msglen, ddt); +#endif + /* + * Since there is no answer section (ancount == 0), + * we must be pointing at the authority section (aucount > 0). + */ + tp = cp; + n = dn_expand(msg, eom, tp, name, sizeof name); + if (n < 0) { + formerrmsg = expandFailedAuth; + goto formerr; + } + tp += n; + GETSHORT(type, tp); + if (tp >= eom) { + formerrmsg = outofDataAuth; + goto formerr; + } + GETSHORT(class, tp); + if (tp >= eom) { + formerrmsg = outofDataAuth; + goto formerr; + } + if (!ns_nameok(name, class, response_trans, + ns_ownercontext(type, response_trans))) { + formerrmsg = badNameFound; + goto formerr; + } + + /* + * If the answer delegates us either to the same level in + * the hierarchy or closer to the root, we consider this + * server lame. Note that for now we only log the message + * if the T_NS was C_IN, which is technically wrong (NS is + * visible in all classes) but necessary anyway (non-IN + * classes tend to not have good strong delegation graphs). + */ + + if (type == T_NS && samedomain(qp->q_domain, name)) { + nameserIncr(from_addr.sin_addr, nssRcvdLDel); + /* mark server as bad */ + if (!qp->q_fwd) + for (i = 0; i < (int)qp->q_naddr; i++) + if (qp->q_addr[i].ns_addr.sin_addr.s_addr + == from_addr.sin_addr.s_addr) + qp->q_addr[i].nretry = MAXRETRY; +#ifdef LAME_LOGGING + if (class == C_IN && + !haveComplained((char*)nhash(sin_ntoa(&from_addr)), + (char*)nhash(qp->q_domain))) + syslog(LAME_LOGGING, + "Lame server on '%s' (in '%s'?): %s%s\n", + qname, qp->q_domain, + sin_ntoa(&from_addr), + learntFrom(qp, &from_addr)); + +#endif /* LAME_LOGGING */ + /* XXX - doesn't handle responses sent from the wrong + * interface on a multihomed server + */ + if (qp->q_fwd || + qp->q_addr[qp->q_curaddr].ns_addr.sin_addr.s_addr + == from_addr.sin_addr.s_addr) + retry(qp); + return; + } + } +#endif /* LAME_DELEGATION */ + + if (qp->q_flags & Q_ZSERIAL) { + if (hp->aa && ancount > 0 && hp->rcode == NOERROR && + qtype == T_SOA && ((qclass == C_IN) || (qclass == C_HS))) { + int n; + u_int16_t type, class, dlen; + u_int32_t serial; + u_char *tp = cp; + + n = dn_expand(msg, eom, tp, name, sizeof name); + if (n < 0) { + formerrmsg = expandFailedAnswer; + goto formerr; + } + tp += n; /* name */ + GETSHORT(type, tp); /* type */ + GETSHORT(class, tp); /* class */ + tp += INT32SZ; /* ttl */ + GETSHORT(dlen, tp); /* dlen */ + if (tp >= eom) { + formerrmsg = outofDataAnswer; + goto formerr; + } + if (!ns_nameok(name, class, response_trans, + ns_ownercontext(type, response_trans))){ + formerrmsg = badNameFound; + goto formerr; + } + if (strcasecmp(qname, name) || + qtype != type || + qclass != class) { + sprintf(msgbuf, + "qserial answer mismatch (%s %s %s)", + name, p_class(class), p_type(type)); + formerrmsg = msgbuf; + goto formerr; + } + if ((u_int)dlen < (5 * INT32SZ)) { + formerrmsg = dlenUnderrunAnswer; + goto formerr; + } + + if (0 >= (n = dn_skipname(tp, eom))) { + formerrmsg = skipnameFailedAnswer; + goto formerr; + } + tp += n; /* mname */ + if (0 >= (n = dn_skipname(tp, eom))) { + formerrmsg = skipnameFailedAnswer; + goto formerr; + } + tp += n; /* rname */ + GETLONG(serial, tp); + + qserial_answer(qp, serial); + qremove(qp); + } else { + retry(qp); + } + return; + } + + /* + * Add the info received in the response to the data base. + */ + c = ancount + aucount + arcount; + + /* -ve $ing non-existence of record, must handle non-authoritative + * NOERRORs with c == 0. + */ + if (!hp->aa && hp->rcode == NOERROR && c == 0) + goto return_msg; + +#ifdef notdef + /* + * If the request was for a CNAME that doesn't exist, + * but the name is valid, fetch any other data for the name. + * DON'T do this now, as it will requery if data are already + * in the cache (maybe later with negative caching). + */ + if (type == T_CNAME && c == 0 && hp->rcode == NOERROR + && !(qp->q_flags & Q_SYSTEM)) { + dprintf(4, (ddt, "resp: leaving, no CNAME\n")); + + /* Cause us to put it in the cache later */ + prime(class, T_ANY, qp); + + /* Nothing to store, just give user the answer */ + goto return_msg; + } +#endif /* notdef */ + + if (qp->q_flags & Q_SYSTEM) + dbflags = DB_NOTAUTH | DB_NODATA; + else + dbflags = DB_NOTAUTH | DB_NODATA | DB_NOHINTS; + count = c; + if (qp->q_flags & Q_PRIMING) + dbflags |= DB_PRIMING; + if (hp->tc) { + count -= arcount; /* truncation had to affect this */ + if (!arcount) { + count -= aucount; /* guess it got this too */ + } + if (!(arcount || aucount)) { + count -= ancount; /* things are pretty grim */ + } + /* XXX - should retry this query with TCP */ + /* + * XXX - if this response is forwarded to the client + * the truncated section is included. We will not + * validate it, and if it somehow corrupt, we won't + * notice. + * + * XXX - if the answer section is truncated and we got + * this response after being redirected by a CNAME, we + * will not include any part of the final answer in our + * response to the client. This will make the client + * think that there are no RRs of the appropriate type. + */ + } + + tp = cp; + + restart = 0; + validanswer = 0; + nscount = 0; + cname = 0; +#ifdef VALIDATE + defer_rm_count = 0; +#endif + + for (i = 0; i < count; i++) { + struct databuf *ns3 = NULL; + u_char cred; + int VCode; + u_int16_t type, class; + + if (cp >= eom) { + formerrmsg = outofDataFinal; + goto formerr; + } + + /* Get the DNAME. */ + tempcp = cp; + n = dn_expand(msg, eom, tempcp, name, sizeof name); + if (n <= 0) { + formerrmsg = outofDataFinal; + goto formerr; + } + tempcp += n; + GETSHORT(type, tempcp); + GETSHORT(class, tempcp); + if (!ns_nameok(name, class, response_trans, + ns_ownercontext(type, response_trans))) { + formerrmsg = badNameFound; + goto formerr; + } + + /* + * See if there are any NS RRs in the authority section + * for the negative caching logic below. We'll count + * these before validation. + */ + if (type == T_NS && i >= ancount && i < ancount + aucount) + nscount++; + + /* Decide what credibility this ought to have in the cache. */ + if (i < ancount) + cred = (hp->aa && !strcasecmp(name, qname)) + ? DB_C_AUTH + : DB_C_ANSWER; + else + cred = (qp->q_flags & Q_PRIMING) + ? DB_C_ANSWER + : DB_C_ADDITIONAL; +#ifdef VALIDATE + if ((n = dovalidate(msg, msglen, cp, 0, + dbflags, qp->q_domain, server, + &VCode)) < 0) { + formerrmsg = outofDataFinal; + goto formerr; + } + if (VCode == INVALID && !(qp->q_flags & Q_SYSTEM)) { + /* + * If anything in the answer section fails + * validation this means that it definitely did + * not reside below the domain owning the NS RRs + * that we sent the query to. This means either + * that it was the target of a CNAME early in the + * response, in which case we will treat this the + * same as if the answer was incomplete and restart + * the query on the CNAME target, or that someone + * was trying to spoof us. + */ + if (i < ancount) + restart = 1; + /* + * Restart or no, if we're here it means we are not + * going to cache this RR. That being the case, we + * must burn down whatever partial RRset we've got + * in the cache now, lest we inadvertently answer + * with a truncated RRset in some future section. + */ + for (c = 0; c < defer_rm_count; c++) + if (!strcasecmp(defer_rm[c].name, name) && + defer_rm[c].class == class && + defer_rm[c].type == type) + break; + if (c < defer_rm_count) { + if (defer_rm[c].cred < cred) + defer_rm[c].cred = cred; + } else { + if (defer_rm_count+1 >= + (sizeof defer_rm / sizeof defer_rm[0])) { + formerrmsg = "too many RRs in ns_resp"; + goto formerr; + } + defer_rm[defer_rm_count].name = savestr(name); + defer_rm[defer_rm_count].type = type; + defer_rm[defer_rm_count].class = class; + defer_rm[defer_rm_count].cred = cred; + defer_rm_count++; + } + } else { +#endif + if (i < ancount) { + /* + * If there are any non-CNAME RRs (or + * CNAME RRs if they are an acceptable) + * then the query is complete unless an + * intermediate CNAME didn't pass validation, + * but that's OK. + */ + if (type != T_CNAME || qtype == T_CNAME || + qtype == T_ANY) + validanswer = 1; + else + cname = 1; + } + n = doupdate(msg, msglen, cp, 0, &ns3, dbflags, cred); +#ifdef VALIDATE + } +#endif + if (n < 0) { + dprintf(1, (ddt, "resp: leaving, doupdate failed\n")); + formerrmsg = outofDataFinal; + goto formerr; + } + cp += n; + } +#ifdef VALIDATE + if (defer_rm_count > 0) { + for (i = 0; i < defer_rm_count; i++) { + register struct databuf *db = NULL; + + fname = ""; + htp = hashtab; /* lookup relative to root */ + np = nlookup(defer_rm[i].name, &htp, &fname, 0); + if (np && fname == defer_rm[i].name && + defer_rm[i].class != C_ANY && + defer_rm[i].type != T_ANY) { + /* + * If doupdate() wouldn't have cached this + * RR anyway, there's no need to delete it. + */ + for (db = np->n_data; + db != NULL; + db = db->d_next) { + if (!db->d_zone && + match(db, defer_rm[i].class, + defer_rm[i].type) && + db->d_cred >= defer_rm[i].cred) { + break; + } + } + if (db == NULL) + delete_all(np, defer_rm[i].class, + defer_rm[i].type); + /* XXX: should delete name node if empty? */ + } + syslog(LOG_DEBUG, "defer_rm [%s %s %s] (np%#x, db%#x)", + defer_rm[i].name, + p_class(defer_rm[i].class), + p_type(defer_rm[i].type), + np, db); + free(defer_rm[i].name); + } + } +#endif + + if (cp > eom) { + formerrmsg = outofDataAFinal; + goto formerr; + } + + if ((qp->q_flags & Q_SYSTEM) && ancount) { + if (qp->q_flags & Q_PRIMING) + check_root(); + dprintf(3, (ddt, "resp: leaving, SYSQUERY ancount %d\n", + ancount)); +#ifdef BIND_NOTIFY + if (qp->q_notifyzone != DB_Z_CACHE) { + struct zoneinfo *zp = &zones[qp->q_notifyzone]; + + /* + * Clear this first since sysnotify() might set it. + */ + qp->q_notifyzone = DB_Z_CACHE; + sysnotify(zp->z_origin, zp->z_class, T_SOA); + } +#endif + qremove(qp); + return; + } + + if (ancount && count && !validanswer) + /* + * Everything passed validation but we didn't get the + * final answer. The response must have contained + * a dangling CNAME. Force a restart of the query. + * + * Don't set restart if count==0, since this means + * the response was truncated in the answer section, + * causing us to set count to 0 which will cause + * validanswer to be 0 as well even though the answer + * section probably contained valid RRs (just not + * a complete set). + * XXX - this works right if we can just forward this + * response to the client, but not if we found a CNAME + * in a prior response and restarted the query. + */ + restart = 1; + + /* + * If there are addresses and this is a local query, + * sort them appropriately for the local context. + */ +#ifdef SORT_RESPONSE + if (!restart && ancount > 1 && (lp = local(&qp->q_from)) != NULL) + sort_response(tp, ancount, lp, eom); +#endif + + /* + * An answer to a T_ANY query or a successful answer to a + * regular query with no indirection, then just return answer. + */ + if (!restart && ancount && (qtype == T_ANY || !qp->q_cmsglen)) { + dprintf(3, (ddt, "resp: got as much answer as there is\n")); + goto return_msg; + } + + /* + * We might want to cache this negative answer. + */ + if (!ancount && + (!nscount || hp->rcode == NXDOMAIN) && + (hp->aa || fwd || qclass == C_ANY)) { + /* we have an authoritative NO */ + dprintf(3, (ddt, "resp: leaving auth NO\n")); + if (qp->q_cmsglen) { + /* XXX - what about additional CNAMEs in the chain? */ + msg = qp->q_cmsg; + msglen = qp->q_cmsglen; + hp = (HEADER *)msg; + } +#ifdef NCACHE + /* answer was NO */ + if (hp->aa && + ((hp->rcode == NXDOMAIN) || (hp->rcode == NOERROR))) { + cache_n_resp(msg, msglen); + } +#endif /*NCACHE*/ + goto return_msg; + } + + /* + * All messages in here need further processing. i.e. they + * are either CNAMEs or we got referred again. + */ + count = 0; + founddata = 0; + foundname = 0; + dname = name; + /* + * Even with VALIDATE, if restart==0 and ancount > 0, we should + * have some valid data because because the data in the answer + * section is owned by the query name and that passes the + * validation test by definition + * + * XXX - the restart stuff doesn't work if any of the answer RRs + * is not cacheable (TTL==0 or unknown RR type), since all of the + * answer must pass through the cache and be re-assembled. + */ + if ((!restart || !cname) && qp->q_cmsglen && ancount) { + dprintf(1, (ddt, "Cname second pass\n")); + newmsglen = MIN(PACKETSZ, qp->q_cmsglen); + bcopy(qp->q_cmsg, newmsg, newmsglen); + } else { + newmsglen = MIN(PACKETSZ, msglen); + bcopy(msg, newmsg, newmsglen); + } + hp = (HEADER *) newmsg; + hp->ancount = htons(0); + hp->nscount = htons(0); + hp->arcount = htons(0); + dnptrs[0] = newmsg; + dnptrs[1] = NULL; + cp = newmsg + HFIXEDSZ; + /* + * Keep in mind that none of this code works when QDCOUNT>1. + * cp ends up pointed just past the query section in both cases. + */ + /* + * Arrange for dname to contain the query name. The query + * name can be either the original query name if restart==0 + * or the target of the last CNAME if we are following a + * CNAME chain and were referred. + */ + n = dn_expand(newmsg, newmsg + newmsglen, cp, dname, + sizeof name); + if (n < 0) { + dprintf(1, (ddt, "dn_expand failed\n")); + goto servfail; + } + if (!res_dnok(dname)) { + dprintf(1, (ddt, "bad name (%s)\n", dname)); + goto servfail; + } + cp += n + QFIXEDSZ; + buflen = sizeof(newmsg) - (cp - newmsg); + + cname = 0; + try_again: + dprintf(1, (ddt, "resp: nlookup(%s) qtype=%d\n", dname, qtype)); + fname = ""; + htp = hashtab; /* lookup relative to root */ + np = nlookup(dname, &htp, &fname, 0); + dprintf(1, (ddt, "resp: %s '%s' as '%s' (cname=%d)\n", + np == NULL ? "missed" : "found", dname, fname, cname)); + if (np == NULL || fname != dname) + goto fetch_ns; + + foundname++; + count = cp - newmsg; + n = finddata(np, qclass, qtype, hp, &dname, &buflen, &count); + if (n == 0) + goto fetch_ns; /* NO data available */ + cp += n; + buflen -= n; + hp->ancount = htons(ntohs(hp->ancount) + (u_int16_t)count); + if (fname != dname && qtype != T_CNAME && qtype != T_ANY) { + cname++; + goto try_again; + } + founddata = 1; + + dprintf(3, (ddt, + "resp: foundname=%d, count=%d, founddata=%d, cname=%d\n", + foundname, count, founddata, cname)); + + fetch_ns: + + if (hp->tc) + goto return_newmsg; + + /* + * Look for name servers to refer to and fill in the authority + * section or record the address for forwarding the query + * (recursion desired). + */ +#ifdef DATUMREFCNT + free_nsp(nsp); +#endif + switch (findns(&np, qclass, nsp, &count, 0)) { + case NXDOMAIN: /* shouldn't happen */ + dprintf(3, (ddt, "req: leaving (%s, rcode %d)\n", + dname, hp->rcode)); + if (!foundname) + hp->rcode = NXDOMAIN; + if (qclass != C_ANY) { + hp->aa = 1; + /* XXX: should return SOA if founddata == 0, + * but old named's are confused by an SOA + * in the auth. section if there's no error. + */ + if (foundname == 0 && np) { + n = doaddauth(hp, cp, buflen, np, nsp[0]); + cp += n; + buflen -= n; + } + } + goto return_newmsg; + + case SERVFAIL: + goto servfail; + } + + if (founddata) { + hp = (HEADER *)newmsg; + n = add_data(np, nsp, cp, buflen, &count); + if (n < 0) { + hp->tc = 1; + n = (-n); + } + cp += n; + buflen -= n; + hp->nscount = htons((u_int16_t)count); + goto return_newmsg; + } + + /* + * If we get here, we don't have the answer yet and are about + * to iterate to try and get it. First, infinite loop avoidance. + */ + if (qp->q_nqueries++ > MAXQUERIES) { + dprintf(1, (ddt, "resp: MAXQUERIES exceeded (%s %s %s)\n", + dname, p_class(qclass), p_type(qtype))); + syslog(LOG_INFO, + "MAXQUERIES exceeded, possible data loop in resolving (%s)", + dname); + goto servfail; + } + + /* Reset the query control structure */ +#ifdef DATUMREFCNT + /* XXX - this code should be shared with qfree()'s similar logic. */ + for (i = 0; (u_int)i < qp->q_naddr; i++) { + static const char freed[] = "freed", busy[] = "busy"; + const char *result; + + if (qp->q_addr[i].ns != NULL) { + if ((--(qp->q_addr[i].ns->d_rcnt))) + result = busy; + else + result = freed; + dprintf(1, (ddt, "ns_resp: ns %s rcnt %d (%s)\n", + qp->q_addr[i].ns->d_data, + qp->q_addr[i].ns->d_rcnt, + result)); + if (result == freed) + free((char*)qp->q_addr[i].ns); + } + if (qp->q_addr[i].nsdata != NULL) { + if ((--(qp->q_addr[i].nsdata->d_rcnt))) + result = busy; + else + result = freed; + dprintf(1, (ddt, + "ns_resp: nsdata %08.8X rcnt %d (%s)\n", + *(int32_t *)(qp->q_addr[i].nsdata->d_data), + qp->q_addr[i].nsdata->d_rcnt, + result)); + if (result == freed) + free((char*)qp->q_addr[i].nsdata); + } + } +#endif + qp->q_naddr = 0; + qp->q_curaddr = 0; + qp->q_fwd = fwdtab; +#if defined(LAME_DELEGATION) || defined(VALIDATE) + getname(np, qp->q_domain, sizeof(qp->q_domain)); +#endif /* LAME_DELEGATION */ + if ((n = nslookup(nsp, qp, dname, "ns_resp")) <= 0) { + if (n < 0) { + dprintf(3, (ddt, "resp: nslookup reports danger\n")); + if (cname) /* a remote CNAME that does not have data */ + goto return_newmsg; + goto servfail; + } else { + dprintf(3, (ddt, "resp: no addrs found for NS's\n")); + /* + * Timeout while sysquery looks up the NS addresses. + * + * Hopefully we'll have them when the client asks + * again. + * + * too bad we can't just wait for the sysquery + * response to restart this query (it's too hard). + * + * We could try to crawl back up the tree looking + * for reachable servers, but we may have just + * gotten delegated down here by a response with + * no A RRs for the servers. If we blindly tried + * this strategy, we bang on the same server forever. + */ + goto timeout; + } + } + for (n = 0; (u_int)n < qp->q_naddr; n++) + qp->q_addr[n].stime.tv_sec = 0; + if (!qp->q_fwd) + qp->q_addr[0].stime = tt; + if (cname) { + if (qp->q_cname++ == MAXCNAMES) { + dprintf(3, (ddt, + "resp: leaving, MAXCNAMES exceeded\n")); + goto servfail; + } + dprintf(1, (ddt, "q_cname = %d\n", qp->q_cname)); + dprintf(3, (ddt, + "resp: building recursive query; nslookup\n")); + if (!qp->q_cmsg) { + qp->q_cmsg = qp->q_msg; + qp->q_cmsglen = qp->q_msglen; + } else if (qp->q_msg) + (void) free(qp->q_msg); + if ((qp->q_msg = (u_char *)malloc(BUFSIZ)) == NULL) { + syslog(LOG_NOTICE, "resp: malloc error\n"); + goto servfail; + } + n = res_mkquery(QUERY, dname, qclass, qtype, + NULL, 0, NULL, qp->q_msg, BUFSIZ); + if (n < 0) { + syslog(LOG_INFO, "resp: res_mkquery(%s) failed", + dname); + goto servfail; + } + qp->q_msglen = n; + hp = (HEADER *) qp->q_msg; + hp->rd = 0; + } else + hp = (HEADER *) qp->q_msg; + hp->id = qp->q_nsid = htons(nsid_next()); + if (qp->q_fwd) + hp->rd = 1; + unsched(qp); + schedretry(qp, retrytime(qp)); + nsa = Q_NEXTADDR(qp, 0); + dprintf(1, (ddt, "resp: forw -> %s ds=%d nsid=%d id=%d %dms\n", + sin_ntoa(nsa), ds, + ntohs(qp->q_nsid), ntohs(qp->q_id), + (qp->q_addr[0].nsdata != NULL) + ? qp->q_addr[0].nsdata->d_nstime + : (-1))); +#ifdef DEBUG + if (debug >= 10) + fp_nquery(qp->q_msg, qp->q_msglen, ddt); +#endif + if (sendto(ds, (char*)qp->q_msg, qp->q_msglen, 0, + (struct sockaddr *)nsa, + sizeof(struct sockaddr_in)) < 0) { + if (!haveComplained((char*)nsa->sin_addr.s_addr, sendtoStr)) + syslog(LOG_INFO, "ns_resp: sendto(%s): %m", + sin_ntoa(nsa)); + nameserIncr(nsa->sin_addr, nssSendtoErr); + } + hp->rd = 0; /* leave set to 0 for dup detection */ +#ifdef XSTATS + nameserIncr(nsa->sin_addr, nssSentFwdR); +#endif + nameserIncr(qp->q_from.sin_addr, nssRcvdFwdR); + dprintf(3, (ddt, "resp: Query sent.\n")); +#ifdef DATUMREFCNT + free_nsp(nsp); +#endif + return; + + formerr: + if (!haveComplained((char*)from_addr.sin_addr.s_addr, + (char*)nhash(formerrmsg))) + syslog(LOG_INFO, "Malformed response from %s (%s)\n", + sin_ntoa(&from_addr), formerrmsg); +#ifdef XSTATS + nameserIncr(from_addr.sin_addr, nssSentFErr); +#endif +#ifdef DATUMREFCNT + free_nsp(nsp); +#endif + return; + + return_msg: + nameserIncr(from_addr.sin_addr, nssRcvdFwdR); +#ifdef XSTATS + nameserIncr(qp->q_from.sin_addr, nssSentFwdR); +#endif + /* The "standard" return code */ + hp->qr = 1; + hp->id = qp->q_id; + hp->rd = 1; + hp->ra = (NoRecurse == 0); + (void) send_msg(msg, msglen, qp); + qremove(qp); +#ifdef DATUMREFCNT + free_nsp(nsp); +#endif + return; + + return_newmsg: + nameserIncr(qp->q_from.sin_addr, nssSentAns); + +#ifdef XSTATS + if (!hp->aa) + nameserIncr(qp->q_from.sin_addr, nssSentNaAns); + if (hp->rcode == NXDOMAIN) + nameserIncr(qp->q_from.sin_addr, nssSentNXD); +#endif + n = doaddinfo(hp, cp, buflen); + cp += n; + buflen -= n; + hp->qr = 1; + hp->id = qp->q_id; + hp->rd = 1; + hp->ra = (NoRecurse == 0); + (void) send_msg(newmsg, cp - newmsg, qp); + qremove(qp); +#ifdef DATUMREFCNT + free_nsp(nsp); +#endif + return; + + servfail: +#ifdef XSTATS + nameserIncr(qp->q_from.sin_addr, nssSentFail); +#endif + hp = (HEADER *)(qp->q_cmsglen ? qp->q_cmsg : qp->q_msg); + hp->rcode = SERVFAIL; + hp->qr = 1; + hp->id = qp->q_id; + hp->rd = 1; + hp->ra = (NoRecurse == 0); + (void) send_msg((u_char *)hp, (qp->q_cmsglen ? qp->q_cmsglen : qp->q_msglen), + qp); + timeout: + qremove(qp); +#ifdef DATUMREFCNT + free_nsp(nsp); +#endif + return; +} + +/* + * Decode the resource record 'rrp' and update the database. + * If savens is non-nil, record pointer for forwarding queries a second time. + */ +int +doupdate(msg, msglen, rrp, zone, savens, flags, cred) + u_char *msg, *rrp; + struct databuf **savens; + int msglen, zone, flags; + u_int cred; +{ + register u_char *cp; + register int n; + int class, type, dlen, n1; + u_int32_t ttl; + struct databuf *dp; + char dname[MAXDNAME]; + u_char *cp1; + u_char data[BUFSIZ]; + register HEADER *hp = (HEADER *)msg; + enum context context; +#ifdef ALLOW_UPDATES + int zonenum; +#endif + + dprintf(3, (ddt, "doupdate(zone %d, savens %#lx, flags %#lx)\n", + zone, (u_long)savens, (u_long)flags)); + + cp = rrp; + if ((n = dn_expand(msg, msg + msglen, cp, dname, sizeof dname)) < 0) { + hp->rcode = FORMERR; + return (-1); + } + cp += n; + GETSHORT(type, cp); + GETSHORT(class, cp); + GETLONG(ttl, cp); + GETSHORT(dlen, cp); + if (!ns_nameok(dname, class, response_trans, + ns_ownercontext(type, response_trans))) { + hp->rcode = FORMERR; + return (-1); + } + dprintf(3, (ddt, "doupdate: dname %s type %d class %d ttl %d\n", + dname, type, class, ttl)); + /* + * Convert the resource record data into the internal + * database format. + */ + switch (type) { + case T_A: + if (dlen != INT32SZ) { + hp->rcode = FORMERR; + return (-1); + } + /*FALLTHROUGH*/ + case T_WKS: + case T_HINFO: + case T_UINFO: + case T_UID: + case T_GID: + case T_TXT: + case T_X25: + case T_ISDN: + case T_NSAP: + case T_AAAA: + case T_LOC: +#ifdef ALLOW_T_UNSPEC + case T_UNSPEC: +#endif + cp1 = cp; + n = dlen; + cp += n; + break; + + case T_CNAME: + case T_MB: + case T_MG: + case T_MR: + case T_NS: + case T_PTR: + n = dn_expand(msg, msg + msglen, cp, + (char *)data, sizeof data); + if (n < 0) { + hp->rcode = FORMERR; + return (-1); + } + if (!ns_nameok((char *)data, class, response_trans, + (type == T_PTR) + ? ns_ptrcontext(dname) + : domain_ctx)) { + hp->rcode = FORMERR; + return (-1); + } + cp += n; + cp1 = data; + n = strlen((char *)data) + 1; + break; + + case T_SOA: + context = hostname_ctx; + goto soa_rp_minfo; + case T_RP: + case T_MINFO: + context = mailname_ctx; + /* FALLTHROUGH */ + soa_rp_minfo: + n = dn_expand(msg, msg + msglen, cp, + (char *)data, sizeof data); + if (n < 0) { + hp->rcode = FORMERR; + return (-1); + } + if (!ns_nameok((char *)data, class, response_trans, context)) { + hp->rcode = FORMERR; + return (-1); + } + cp += n; + cp1 = data + (n = strlen((char *)data) + 1); + n1 = sizeof(data) - n; + if (type == T_SOA) + n1 -= 5 * INT32SZ; + n = dn_expand(msg, msg + msglen, cp, (char *)cp1, n1); + if (n < 0) { + hp->rcode = FORMERR; + return (-1); + } + if (type == T_RP) + context = domain_ctx; + else + context = mailname_ctx; + if (!ns_nameok((char *)cp1, class, response_trans, context)) { + hp->rcode = FORMERR; + return (-1); + } + cp += n; + cp1 += strlen((char *)cp1) + 1; + if (type == T_SOA) { + bcopy(cp, cp1, n = 5 * INT32SZ); + cp += n; + cp1 += n; + } + n = cp1 - data; + cp1 = data; + break; + + case T_MX: + case T_AFSDB: + case T_RT: + /* grab preference */ + bcopy(cp, data, INT16SZ); + cp1 = data + INT16SZ; + cp += INT16SZ; + + /* get name */ + n = dn_expand(msg, msg + msglen, cp, (char *)cp1, + sizeof data - INT16SZ); + if (n < 0) { + hp->rcode = FORMERR; + return (-1); + } + if (!ns_nameok((char *)cp1, class, response_trans, + hostname_ctx)) { + hp->rcode = FORMERR; + return (-1); + } + cp += n; + + /* compute end of data */ + cp1 += strlen((char *)cp1) + 1; + /* compute size of data */ + n = cp1 - data; + cp1 = data; + break; + + case T_PX: + /* grab preference */ + bcopy(cp, data, INT16SZ); + cp1 = data + INT16SZ; + cp += INT16SZ; + + /* get MAP822 name */ + n = dn_expand(msg, msg + msglen, cp, (char *)cp1, + sizeof data - INT16SZ); + if (n < 0) { + hp->rcode = FORMERR; + return (-1); + } + if (!ns_nameok((char *)cp1, class, response_trans, + domain_ctx)) { + hp->rcode = FORMERR; + return (-1); + } + cp += n; + cp1 += (n = strlen((char *)cp1) + 1); + n1 = sizeof(data) - n; + n = dn_expand(msg, msg + msglen, cp, (char *)cp1, n1); + if (n < 0) { + hp->rcode = FORMERR; + return (-1); + } + if (!ns_nameok((char *)cp1, class, response_trans, + domain_ctx)) { + hp->rcode = FORMERR; + return (-1); + } + cp += n; + cp1 += strlen((char *)cp1) + 1; + n = cp1 - data; + cp1 = data; + break; + + default: + dprintf(3, (ddt, "unknown type %d\n", type)); + return ((cp - rrp) + dlen); + } + if (n > MAXDATA) { + dprintf(1, (ddt, + "update type %d: %d bytes is too much data\n", + type, n)); + hp->rcode = FORMERR; + return (-1); + } + +#ifdef ALLOW_UPDATES + /* + * If this is a dynamic update request, process it specially; else, + * execute normal update code. + */ + switch(hp->opcode) { + + /* For UPDATEM and UPDATEMA, do UPDATED/UPDATEDA followed by UPDATEA */ + case UPDATEM: + case UPDATEMA: + + /* + * The named code for UPDATED and UPDATEDA is the same except that for + * UPDATEDA we we ignore any data that was passed: we just delete all + * RRs whose name, type, and class matches + */ + case UPDATED: + case UPDATEDA: + if (type == T_SOA) { /* Not allowed */ + dprintf(1, (ddt, "UDPATE: REFUSED - SOA delete\n")); + hp->rcode = REFUSED; + return (-1); + } + /* + * Don't check message length if doing UPDATEM/UPDATEMA, + * since the whole message wont have been demarshalled until + * we reach the code for UPDATEA + */ + if ( (hp->opcode == UPDATED) || (hp->opcode == UPDATEDA) ) { + if (cp != (u_char *)(msg + msglen)) { + dprintf(1, (ddt, + "FORMERR UPDATE message length off\n" + )); + hp->rcode = FORMERR; + return (-1); + } + } + if ((zonenum = findzone(dname, class)) == 0) { + hp->rcode = NXDOMAIN; + return (-1); + } + if (zones[zonenum].z_flags & Z_DYNADDONLY) { + hp->rcode = NXDOMAIN; + return (-1); + } + if ( (hp->opcode == UPDATED) || (hp->opcode == UPDATEM) ) { + /* Make a dp for use in db_update, as old dp */ + dp = savedata(class, type, 0, cp1, n); + dp->d_zone = zonenum; + dp->d_cred = cred; + dp->d_clev = db_getclev(zones[zonenum].z_origin); + n = db_update(dname, dp, NULL, DB_MEXIST | DB_DELETE, + hashtab); + if (n != OK) { + dprintf(1, (ddt, + "UPDATE: db_update failed\n")); + free((char*) dp); + hp->rcode = NOCHANGE; + return (-1); + } + } else { /* UPDATEDA or UPDATEMA */ + int DeletedOne = 0; + /* Make a dp for use in db_update, as old dp */ + dp = savedata(class, type, 0, NULL, 0); + dp->d_zone = zonenum; + dp->d_cred = cred; + dp->d_clev = db_getclev(zones[zonenum].z_origin); + do { /* Loop and delete all matching RR(s) */ + n = db_update(dname, dp, NULL, DB_DELETE, + hashtab); + if (n != OK) + break; + DeletedOne++; + } while (1); + free((char*) dp); + /* Ok for UPDATEMA not to have deleted any RRs */ + if (!DeletedOne && hp->opcode == UPDATEDA) { + dprintf(1, (ddt, + "UPDATE: db_update failed\n")); + hp->rcode = NOCHANGE; + return (-1); + } + } + if ( (hp->opcode == UPDATED) || (hp->opcode == UPDATEDA) ) + return (cp - rrp);; + /* + * Else unmarshal the RR to be added and continue on to + * UPDATEA code for UPDATEM/UPDATEMA + */ + if ((n = + dn_expand(msg, msg+msglen, cp, dname, sizeof(dname))) < 0) { + dprintf(1, (ddt, + "FORMERR UPDATE expand name failed\n")); + hp->rcode = FORMERR; + return (-1); + } + cp += n; + GETSHORT(type, cp); + GETSHORT(class, cp); + GETLONG(ttl, cp); + GETSHORT(n, cp); + cp1 = cp; +/**** XXX - need bounds checking here ****/ + cp += n; + + case UPDATEA: + if (n > MAXDATA) { + dprintf(1, (ddt, "UPDATE: too much data\n")); + hp->rcode = NOCHANGE; + return (-1); + } + if (cp != (u_char *)(msg + msglen)) { + dprintf(1, (ddt, + "FORMERR UPDATE message length off\n")); + hp->rcode = FORMERR; + return (-1); + } + if ((zonenum = findzone(dname, class)) == 0) { + hp->rcode = NXDOMAIN; + return (-1); + } + if (zones[zonenum].z_flags & Z_DYNADDONLY) { + struct hashbuf *htp = hashtab; + char *fname; + if (nlookup(dname, &htp, &fname, 0) && + !strcasecmp(dname, fname)) { + dprintf(1, (ddt, + "refusing add of existing name\n" + )); + hp->rcode = REFUSED; + return (-1); + } + } + dp = savedata(class, type, ttl, cp1, n); + dp->d_zone = zonenum; + dp->d_cred = cred; + dp->d_clev = db_getclev(zones[zonenum].z_origin); + if ((n = db_update(dname, NULL, dp, DB_NODATA, + hashtab)) != OK) { + dprintf(1, (ddt, "UPDATE: db_update failed\n")); + hp->rcode = NOCHANGE; + free((char*) dp); + return (-1); + } + else + return (cp - rrp); + } +#endif /* ALLOW_UPDATES */ + + if (zone == 0) + ttl += tt.tv_sec; +#if defined(TRACEROOT) || defined(BOGUSNS) + if ((type == T_NS) && (savens != NULL)) { + char *temp, qname[MAXDNAME]; + register int bogus = 0; + int bogusns = 0; +#ifdef BOGUSNS + if (addr_on_netlist(from_addr.sin_addr, boglist)) { + bogusns++; + bogus++; + } +#endif + if (!bogus && + ((temp = strrchr((char *)data, '.')) != NULL) && + !strcasecmp(temp, ".arpa") + ) + bogus++; + qname[0] = qname[1] = '\0'; + if (dn_expand(msg, msg + msglen, msg + HFIXEDSZ, + qname, sizeof(qname)) < 0) + qname[0] = '?'; + else if (qname[0] == '\0') + qname[0] = '.'; + if (bogus && ((dname[0] == '\0') && (zone == 0))) { + if (!haveComplained((char*)from_addr.sin_addr.s_addr, + "bogus root NS")) + syslog(LOG_NOTICE, + "bogus root NS %s rcvd from %s on query for \"%s\"", + data, sin_ntoa(&from_addr), qname); + return (cp - rrp); + } +#ifdef BOGUSNS + if (bogusns) { + if (!haveComplained((char*)from_addr.sin_addr.s_addr, + "bogus nonroot NS")) + syslog(LOG_INFO, + "bogus nonroot NS %s rcvd from %s on query for \"%s\"", + data, sin_ntoa(&from_addr), qname); + return (cp - rrp); + } +#endif + } +#endif /*TRACEROOT || BOGUSNS*/ + + dp = savedata(class, type, ttl, cp1, n); + dp->d_zone = zone; + dp->d_cred = cred; + dp->d_clev = 0; /* We trust what is on disk more, except root srvrs */ + if ((n = db_update(dname, dp, dp, flags, hashtab)) != OK) { +#ifdef DEBUG + if (debug && (n != DATAEXISTS)) + fprintf(ddt, "update failed (%d)\n", n); + else if (debug >= 3) + fprintf(ddt, "update failed (DATAEXISTS)\n"); +#endif + free((char *)dp); + } else if (type == T_NS && savens != NULL) + *savens = dp; + return (cp - rrp); +} + +int +send_msg(msg, msglen, qp) + u_char *msg; + int msglen; + struct qinfo *qp; +{ + if (qp->q_flags & Q_SYSTEM) + return (1); +#ifdef DEBUG + if (debug) { + fprintf(ddt,"send_msg -> %s (%s %d) id=%d\n", + sin_ntoa(&qp->q_from), + qp->q_stream == QSTREAM_NULL ? "UDP" : "TCP", + qp->q_stream == QSTREAM_NULL ? qp->q_dfd + : qp->q_stream->s_rfd, + ntohs(qp->q_id)); + } + if (debug > 4) { + struct qinfo *tqp; + + for (tqp = nsqhead; tqp!=QINFO_NULL; tqp = tqp->q_link) { + fprintf(ddt, + "qp %#lx q_id: %d q_nsid: %d q_msglen: %d ", + (u_long)tqp, tqp->q_id, + tqp->q_nsid, tqp->q_msglen); + fprintf(ddt, + "q_naddr: %d q_curaddr: %d\n", + tqp->q_naddr, tqp->q_curaddr); + fprintf(ddt, "q_next: %#lx q_link: %#lx\n", + (u_long)qp->q_next, (u_long)qp->q_link); + } + } + if (debug > 5) + fp_nquery(msg, msglen, ddt); +#endif /* DEBUG */ + if (qp->q_stream == QSTREAM_NULL) { + if (sendto(qp->q_dfd, (char*)msg, msglen, 0, + (struct sockaddr *)&qp->q_from, + sizeof(qp->q_from)) < 0) { + if (!haveComplained((char*)qp->q_from.sin_addr.s_addr, + sendtoStr)) +#if defined(SPURIOUS_ECONNREFUSED) + if (errno != ECONNREFUSED) +#endif + syslog(LOG_INFO, + "send_msg: sendto(%s): %m", + sin_ntoa(&qp->q_from)); + nameserIncr(qp->q_from.sin_addr, nssSendtoErr); + return (1); + } + } else { + (void) writemsg(qp->q_stream->s_rfd, (u_char*)msg, msglen); + sq_done(qp->q_stream); + } + return (0); +} + +#ifdef notdef +/* i don't quite understand this but the only ref to it is notdef'd --vix */ +prime(class, type, oqp) + int class, type; + register struct qinfo *oqp; +{ + char dname[BUFSIZ]; + + if (oqp->q_msg == NULL) + return; + if (dn_expand((u_char *)oqp->q_msg, + (u_char *)oqp->q_msg + oqp->q_msglen, + (u_char *)oqp->q_msg + HFIXEDSZ, (u_char *)dname, + sizeof(dname)) < 0) + return; + dprintf(2, (ddt, "prime: %s\n", dname)); + (void) sysquery(dname, class, type, NULL, 0, QUERY); +} +#endif + +void +prime_cache() +{ + register struct qinfo *qp; + + dprintf(1, (ddt, "prime_cache: priming = %d\n", priming)); + if (!priming && fcachetab->h_tab[0] != NULL && !forward_only) { + priming++; + if (!(qp = sysquery("", C_IN, T_NS, NULL, 0, QUERY))) + priming = 0; + else + qp->q_flags |= (Q_SYSTEM | Q_PRIMING); + } + needs_prime_cache = 0; + return; +} + +#ifdef BIND_NOTIFY +struct notify * +findNotifyPeer(zp, ina) + const struct zoneinfo *zp; + struct in_addr ina; +{ + register struct notify *ap; + + for (ap = zp->z_notifylist; ap; ap = ap->next) + if (ap->addr.s_addr == ina.s_addr) + break; + return (ap); +} + +/* sysnotify(dname, class, type) + * cause a NOTIFY request to be sysquery()'d to each secondary server + * of the zone that "dname" is within. + */ +void +sysnotify(dname, class, type) + const char *dname; + int class, type; +{ + char *soaname, *zname; + const char *fname; + register struct databuf *dp; + struct in_addr nss[NSMAX]; + int nns, na, zn, nsc; + struct hashbuf *htp; + struct zoneinfo *zp; + struct notify *ap; + struct namebuf *np; + + htp = hashtab; + np = nlookup(dname, &htp, &fname, 0); + if (!np) + panic(-1, "sysnotify: can't find name"); + zn = findMyZone(np, class); + if (zn == DB_Z_CACHE) + panic(-1, "sysnotify: not auth zone"); + zp = &zones[zn]; + if (zp->z_type != Z_PRIMARY && zp->z_type != Z_SECONDARY) + panic(-1, "sysnotify: not pri/sec"); + zname = zp->z_origin; +/* +**DBG** syslog(LOG_INFO, "sysnotify: found \"%s\" in \"%s\" (%s)", +**DBG** dname, zname, zoneTypeString(zp)); +*/ + nns = na = 0; + /* + * Send to recent AXFR peers. + */ + for (ap = zp->z_notifylist; ap; ap = ap->next) { + if (tt.tv_sec - ap->last >= zp->z_refresh) { + /* XXX - probably should do GC here. */ + continue; + } + nss[0] = ap->addr; + nsc = 1; + nns++; + na++; + sysquery(dname, class, T_SOA, nss, nsc, NS_NOTIFY_OP); + } + if (zp->z_type != Z_PRIMARY) + goto done; + /* + * Master. + */ + htp = hashtab; + np = nlookup(zname, &htp, &fname, 0); + if (!np) + panic(-1, "sysnotify: found name but not zone"); + soaname = NULL; + for (dp = np->n_data; dp; dp = dp->d_next) { + if (!dp->d_zone || !match(dp, class, T_SOA)) + continue; + if (soaname) { + syslog(LOG_NOTICE, "multiple SOA's for zone \"%s\"?", + zname); + return; + } + soaname = (char *) dp->d_data; + } + if (!soaname) { + syslog(LOG_NOTICE, "no SOA found for zone \"%s\"", zname); + return; + } + + for (dp = np->n_data; dp; dp = dp->d_next) { + register struct databuf *adp; + struct namebuf *anp; + + if (!dp->d_zone || !match(dp, class, T_NS)) + continue; + /* NS RDATA is server name. */ + if (strcasecmp((char*)dp->d_data, soaname) == 0) + continue; + htp = hashtab; + anp = nlookup((char*)dp->d_data, &htp, &fname, 0); + if (!anp) { + syslog(LOG_INFO, "sysnotify: can't nlookup(%s)?", + (char*)dp->d_data); + continue; + } + nsc = 0; + for (adp = anp->n_data; adp; adp = adp->d_next) { + struct in_addr ina; + if (!match(adp, class, T_A)) + continue; + ina = data_inaddr(adp->d_data); + /* Don't send to things we handled above. */ + ap = findNotifyPeer(zp, ina); + if (ap && tt.tv_sec - ap->last < zp->z_refresh) + goto nextns; + if (nsc < NSMAX) + nss[nsc++] = ina; + } /*next A*/ + if (nsc == 0) { + struct qinfo *qp; + + qp = sysquery((char*)dp->d_data, /*NS name*/ + class, /*XXX: C_IN?*/ + T_A, 0, 0, QUERY); + if (qp) + qp->q_notifyzone = zn; + continue; + } + (void) sysquery(dname, class, T_SOA, nss, nsc, NS_NOTIFY_OP); + nns++; + na += nsc; + nextns:; + } /*next NS*/ + done: + if (nns || na) { + char tmp[MAXDNAME*2]; + + /* Many syslog()'s only take 5 args. */ + sprintf(tmp, "%s %s %s", dname, p_class(class), p_type(type)); + syslog(LOG_INFO, "Sent NOTIFY for \"%s\" (%s); %d NS, %d A", + tmp, zname, nns, na); + } +} +#endif /*BIND_NOTIFY*/ + +struct qinfo * +sysquery(dname, class, type, nss, nsc, opcode) + const char *dname; + int class, type; + struct in_addr *nss; + int nsc, opcode; +{ + register struct qinfo *qp, *oqp; + register HEADER *hp; + struct namebuf *np; + struct databuf *nsp[NSMAX]; + struct hashbuf *htp; + struct sockaddr_in *nsa; + const char *fname; + int n, count; + +#ifdef DATUMREFCNT + nsp[0] = NULL; +#endif + dprintf(3, (ddt, "sysquery(%s, %d, %d, %#lx, %d)\n", + dname, class, type, (u_long)nss, nsc)); + qp = qnew(); + + if (nss && nsc) { + np = NULL; + } else { + htp = hashtab; + if (priming && dname[0] == '\0') { + np = NULL; + } else if ((np = nlookup(dname, &htp, &fname, 1)) == NULL) { + syslog(LOG_INFO, "sysquery: nlookup error on %s?", + dname); + err1: + qfree(qp); + return (NULL); + } + + n = findns(&np, class, nsp, &count, 0); + switch (n) { + case NXDOMAIN: + case SERVFAIL: + syslog(LOG_DEBUG, "sysquery: findns error (%d) on %s?", + n, dname); + err2: +#ifdef DATUMREFCNT + free_nsp(nsp); +#endif + goto err1; + } + } + + /* build new qinfo struct */ + qp->q_cmsg = qp->q_msg = NULL; + qp->q_dfd = ds; + if (nss && nsc) + qp->q_fwd = NULL; + else + qp->q_fwd = fwdtab; + qp->q_expire = tt.tv_sec + RETRY_TIMEOUT*2; + qp->q_flags |= Q_SYSTEM; +#if defined(LAME_DELEGATION) || defined(VALIDATE) + getname(np, qp->q_domain, sizeof(qp->q_domain)); +#endif /* LAME_DELEGATION */ + + if ((qp->q_msg = (u_char *)malloc(BUFSIZ)) == NULL) { + syslog(LOG_NOTICE, "sysquery: malloc failed"); + goto err2; + } + n = res_mkquery(opcode, dname, class, + type, NULL, 0, NULL, + qp->q_msg, BUFSIZ); + if (n < 0) { + syslog(LOG_INFO, "sysquery: res_mkquery(%s) failed", dname); + goto err2; + } + qp->q_msglen = n; + hp = (HEADER *) qp->q_msg; + hp->id = qp->q_nsid = htons(nsid_next()); + hp->rd = (qp->q_fwd ? 1 : 0); + + /* First check for an already pending query for this data */ + for (oqp = nsqhead; oqp != QINFO_NULL; oqp = oqp->q_link) { + if ((oqp != qp) + && (oqp->q_msglen == qp->q_msglen) + && bcmp((char *)oqp->q_msg+2, + qp->q_msg+2, + qp->q_msglen-2) == 0 + ) { +#ifdef BIND_NOTIFY + /* XXX - need fancier test to suppress duplicate + * NOTIFYs to the same server (compare nss?) + */ + if (opcode != NS_NOTIFY_OP) +#endif /*BIND_NOTIFY*/ + { + dprintf(3, (ddt, "sysquery: duplicate\n")); + goto err2; + } + } + } + + if (nss && nsc) { + int i; + struct qserv *qs; + + for (i = 0, qs = qp->q_addr; + i < nsc; + i++, qs++) { + qs->ns_addr.sin_family = AF_INET; + qs->ns_addr.sin_addr = nss[i]; + qs->ns_addr.sin_port = ns_port; + qs->ns = NULL; + qs->nsdata = NULL; + qs->stime = tt; + qs->nretry = 0; + } + qp->q_naddr = nsc; + } else { + fetch_a: + count = nslookup(nsp, qp, dname, "sysquery"); + if (count <= 0) { + if (count < 0) { + syslog(LOG_INFO, + "sysquery: nslookup reports danger (%s)", + dname); + goto err2; + } else if (np && NAME(*np)[0] == '\0') { + syslog(LOG_WARNING, + "sysquery: no addrs found for root NS (%s)", + dname); + if (class == C_IN && !priming) + needs_prime_cache = 1; + goto err2; + } + if (np) { +#ifdef DATUMREFCNT + free_nsp(nsp); + nsp[0] = NULL; +#endif + np = np_parent(np); + n = findns(&np, class, nsp, &count, 0); + switch (n) { + case NXDOMAIN: /*FALLTHROUGH*/ + case SERVFAIL: + syslog(LOG_DEBUG, + "sysquery: findns error (%d) on %s?", + n, dname); + goto err2; + } + goto fetch_a; + } + goto err2; + } + } + + schedretry(qp, retrytime(qp)); + if (qp->q_fwd == NULL) + qp->q_addr[0].stime = tt; /* XXX - why not every? */ + nsa = Q_NEXTADDR(qp, 0); + + dprintf(1, (ddt, + "sysquery: send -> %s dfd=%d nsid=%d id=%d retry=%ld\n", + sin_ntoa(nsa), qp->q_dfd, + ntohs(qp->q_nsid), ntohs(qp->q_id), + (long)qp->q_time)); +#ifdef DEBUG + if (debug >= 10) + fp_nquery(qp->q_msg, qp->q_msglen, ddt); +#endif + if (sendto(qp->q_dfd, (char*)qp->q_msg, qp->q_msglen, 0, + (struct sockaddr *)nsa, + sizeof(struct sockaddr_in)) < 0) { + if (!haveComplained((char*)nsa->sin_addr.s_addr, sendtoStr)) + syslog(LOG_INFO, "sysquery: sendto(%s): %m", + sin_ntoa(nsa)); + nameserIncr(nsa->sin_addr, nssSendtoErr); + } + nameserIncr(nsa->sin_addr, nssSentSysQ); +#ifdef DATUMREFCNT + free_nsp(nsp); +#endif + return (qp); +} + +/* + * Check the list of root servers after receiving a response + * to a query for the root servers. + */ +static void +check_root() +{ + register struct databuf *dp, *pdp; + register struct namebuf *np; + int count = 0; + + priming = 0; + for (np = hashtab->h_tab[0]; np != NULL; np = np->n_next) + if (NAME(*np)[0] == '\0') + break; + if (np == NULL) { + syslog(LOG_NOTICE, "check_root: Can't find root!\n"); + return; + } + for (dp = np->n_data; dp != NULL; dp = dp->d_next) + if (dp->d_type == T_NS) + count++; + dprintf(1, (ddt, "%d root servers\n", count)); + if (count < MINROOTS) { + syslog(LOG_NOTICE, + "check_root: %d root servers after query to root server < min", + count); + return; + } + pdp = NULL; + dp = np->n_data; + while (dp != NULL) { + if (dp->d_type == T_NS && dp->d_zone == 0 && + dp->d_ttl < tt.tv_sec) { + dprintf(1, (ddt, "deleting old root server '%s'\n", + dp->d_data)); + dp = rm_datum(dp, np, pdp); + /* SHOULD DELETE FROM HINTS ALSO */ + continue; + } + pdp = dp; + dp = dp->d_next; + } + check_ns(); +} + +/* + * Check the root to make sure that for each NS record we have a A RR + */ +static void +check_ns() +{ + register struct databuf *dp, *tdp; + register struct namebuf *np, *tnp; + struct hashbuf *htp; + char *dname; + int found_arr; + const char *fname; + time_t curtime; + + dprintf(2, (ddt, "check_ns()\n")); + + curtime = (u_int32_t) tt.tv_sec; + for (np = hashtab->h_tab[0]; np != NULL; np = np->n_next) { + if (NAME(*np)[0] != '\0') + continue; + for (dp = np->n_data; dp != NULL; dp = dp->d_next) { + if (dp->d_type != T_NS) + continue; + + /* look for A records */ + dname = (caddr_t) dp->d_data; + htp = hashtab; + tnp = nlookup(dname, &htp, &fname, 0); + if (tnp == NULL || fname != dname) { + dprintf(3, (ddt, + "check_ns: %s: not found %s %#lx\n", + dname, fname, (u_long)tnp)); + sysquery(dname, dp->d_class, T_A, NULL, + 0, QUERY); + continue; + } + /* look for name server addresses */ + found_arr = 0; + for (tdp=tnp->n_data; tdp != NULL; tdp=tdp->d_next) { + if (tdp->d_type != T_A || + tdp->d_class != dp->d_class) + continue; + if ((tdp->d_zone == 0) && + (tdp->d_ttl < curtime)) { + dprintf(3, (ddt, + "check_ns: stale entry '%s'\n", + NAME(*tnp))); + /* Cache invalidate the address RR's */ + delete_all(tnp, dp->d_class, T_A); + found_arr = 0; + break; + } + found_arr++; + } + if (!found_arr) + sysquery(dname, dp->d_class, T_A, NULL, + 0, QUERY); + } + } +} + +/* int findns(npp, class, nsp, countp, flag) + * Find NS' or an SOA + * npp, class: + * dname whose most enclosing NS is wanted + * nsp, countp: + * result array and count; array will also be NULL terminated + * flag: + * boolean: we're being called from ADDAUTH, bypass authority checks + * return value: + * NXDOMAIN: we are authoritative for this {dname,class} + * SERVFAIL: we are auth but zone isn't loaded; or, no root servers found + * OK: success (this is the only case where *countp and nsp[] are valid) + */ +int +findns(npp, class, nsp, countp, flag) + register struct namebuf **npp; + int class; + struct databuf **nsp; + int *countp; + int flag; +{ + register struct namebuf *np = *npp; + register struct databuf *dp; + register struct databuf **nspp; + struct hashbuf *htp; + +#ifdef DATUMREFCNT + nsp[0] = NULL; +#endif + + if (priming && (np == NULL || NAME(*np)[0] == '\0')) + htp = fcachetab; + else + htp = hashtab; + + try_again: + if (htp == fcachetab && class == C_IN && !priming) + needs_prime_cache = 1; + if (np == NULL) { + /* find the root */ + for (np = htp->h_tab[0]; np != NULL; np = np->n_next) + if (NAME(*np)[0] == '\0') + break; + } + while (np != NULL) { + dprintf(5, (ddt, "findns: np %#lx '%s'\n", + (u_long)np, NAME(*np))); + /* Look first for SOA records. */ +#ifdef ADDAUTH + if (!flag) +#endif + for (dp = np->n_data; dp != NULL; dp = dp->d_next) { + if (dp->d_zone != 0 && +#ifdef PURGE_ZONE + ((zones[dp->d_zone].z_type == Z_PRIMARY) || + (zones[dp->d_zone].z_type == Z_SECONDARY)) && +#endif + match(dp, class, T_SOA)) { + dprintf(3, (ddt, "findns: SOA found\n")); + if (zones[dp->d_zone].z_flags & Z_AUTH) { + *npp = np; + nsp[0] = dp; +#ifdef DATUMREFCNT + nsp[1] = NULL; + dp->d_rcnt++; +#endif + return (NXDOMAIN); + } else { + /* XXX: zone isn't loaded but we're + * primary or secondary for it. + * should we fwd this? + */ + return (SERVFAIL); + } + } + } + + /* If no SOA records, look for NS records. */ + nspp = &nsp[0]; + *nspp = NULL; + for (dp = np->n_data; dp != NULL; dp = dp->d_next) { + if (!match(dp, class, T_NS)) + continue; +#ifdef NCACHE + if (dp->d_rcode) + continue; +#endif + /* + * Don't use records that may become invalid to + * reference later when we do the rtt computation. + * Never delete our safety-belt information! + * + * XXX: this is horribly bogus. + */ + if ((dp->d_zone == 0) && +#ifdef DATUMREFCNT + (dp->d_ttl < tt.tv_sec) && +#else + (dp->d_ttl < (tt.tv_sec+900)) && +#endif + !(dp->d_flags & DB_F_HINT)) { + dprintf(1, (ddt, "findns: stale entry '%s'\n", + NAME(*np))); +#ifdef DATUMREFCNT + /* + * We may have already added NS databufs + * and are going to throw them away. Fix + * fix reference counts. We don't need + * free() them here as we just got them + * from the cache. + */ + while (nspp > &nsp[0]) + (*--nspp)->d_rcnt--; +#endif + /* Cache invalidate the NS RR's. */ +#ifndef DATUMREFCNT + if (dp->d_ttl < tt.tv_sec) +#endif + delete_all(np, class, T_NS); + nsp[0] = NULL; + goto try_parent; + } + if (nspp < &nsp[NSMAX-1]) { + *nspp++ = dp; +#ifdef DATUMREFCNT + dp->d_rcnt++; +#endif + } + } + + *countp = nspp - nsp; + if (*countp > 0) { + dprintf(3, (ddt, "findns: %d NS's added for '%s'\n", + *countp, NAME(*np))); + *nspp = NULL; + *npp = np; + return (OK); /* Success, got some NS's */ + } +try_parent: + np = np_parent(np); + } + if (htp == hashtab) { + htp = fcachetab; + goto try_again; + } + dprintf(1, (ddt, "findns: No root nameservers for class %s?\n", + p_class(class))); + if ((unsigned)class < MAXCLASS && norootlogged[class] == 0) { + norootlogged[class] = 1; + syslog(LOG_INFO, "No root nameservers for class %s\n", + p_class(class)); + } + return (SERVFAIL); +} + +/* + * Extract RR's from the given node that match class and type. + * Return number of bytes added to response. + * If no matching data is found, then 0 is returned. + */ +int +finddata(np, class, type, hp, dnamep, lenp, countp) + struct namebuf *np; + int class, type; + register HEADER *hp; + char **dnamep; + int *lenp, *countp; +{ + register struct databuf *dp; + register char *cp; + int buflen, n, count = 0, foundstale = 0; + +#ifdef ROUND_ROBIN + if (type != T_ANY && type != T_PTR) { + /* cycle order of RRs, for a load balancing effect... */ + + register struct databuf **dpp; + + for (dpp = &np->n_data; dp = *dpp; dpp = &dp->d_next) { + if (dp->d_next && wanted(dp, class, type)) { + register struct databuf *lp; + + *dpp = lp = dp->d_next; + dp->d_next = NULL; + + for (dpp = &lp->d_next; + *dpp; + dpp = &lp->d_next) + lp = *dpp; + *dpp = dp; + break; + } + } + } +#endif /*ROUND_ROBIN*/ + + buflen = *lenp; +#ifdef DEBUG + if (buflen > PACKETSZ) + dprintf(1, (ddt, "finddata(): buflen=%d\n", buflen)); +#endif + cp = ((char *)hp) + *countp; + for (dp = np->n_data; dp != NULL; dp = dp->d_next) { + if (!wanted(dp, class, type)) { +#ifndef NCACHE /*if no negative caching then cname => nothing else*/ + if (type == T_CNAME && class == dp->d_class) { + /* any data means no CNAME exists */ + *countp = 0; + return 0; + } +#endif /*NCACHE*/ + continue; + } + if (stale(dp)) { + /* + * Don't use stale data. + * Would like to call delete_all here + * and continue, but the data chain would get + * munged; can't restart, as make_rr has side + * effects (leaving pointers in dnptr). + * Just skip this entry for now + * and call delete_all at the end. + */ + dprintf(3, (ddt, + "finddata: stale entry '%s'\n", + NAME(*np))); + if (dp->d_zone == 0) + foundstale++; + continue; + } + if (dp->d_cred == DB_C_ADDITIONAL) { + /* we want to expire additional data very + * quickly. current strategy is to cut 5% + * off each time it is accessed. this makes + * stale(dp) true faster when this datum is + * used often. + */ + dp->d_ttl = tt.tv_sec + + + 0.95 * (int) (dp->d_ttl - tt.tv_sec); + } +#ifdef NCACHE + /* -ve $ing stuff, anant@isi.edu + * if we have a -ve $ed record, change the rcode on the + * header to reflect that + */ + if (dp->d_rcode == NOERROR_NODATA) { + if (count != 0) { + /* + * This should not happen, yet it does... + */ + syslog(LOG_INFO, + "NODATA & data for \"%s\" type %d class %d", + *dnamep, type, class); + continue; + } + if (type != T_ANY) { + hp->rcode = NOERROR_NODATA; + *countp = 0; + return 1; /* XXX - we have to report success */ + } + /* don't satisfy T_ANY queries from -$ info */ + continue; + } +#ifndef RETURNSOA + if (dp->d_rcode == NXDOMAIN) { + if (count != 0) { + /* + * This should not happen, yet it might... + */ + syslog(LOG_INFO, + "NXDOMAIN & data for \"%s\" type %d class %d", + *dnamep, type, class); + continue; + } + if (type != T_ANY) { + hp->rcode = NXDOMAIN; + *countp = 0; + return 1; /* XXX - we have to report success */ + } + /* don't satisfy T_ANY queries from -$ info */ + continue; + } +#endif +#endif /*NCACHE*/ + + if ((n = make_rr(*dnamep, dp, (u_char *)cp, buflen, 1)) < 0) { + hp->tc = 1; + *countp = count; + return (*lenp - buflen); + } + + cp += n; + buflen -= n; + count++; +#ifdef notdef + /* this isn't right for glue records, aa is set in ns_req */ + if (dp->d_zone && + (zones[dp->d_zone].z_flags & Z_AUTH) && + class != C_ANY) + hp->aa = 1; /* XXX */ +#endif + if (dp->d_type == T_CNAME) { + if (type != T_ANY) { /* or T_NS? */ + *dnamep = (caddr_t) dp->d_data; + if (dp->d_zone != DB_Z_CACHE && + (zones[dp->d_zone].z_flags & Z_AUTH) && + class != C_ANY) /* XXX */ + hp->aa = 1; /* XXX */ + } + break; + } + } + /* + * Cache invalidate the other RR's of same type + * if some have timed out + */ + if (foundstale) { + delete_all(np, class, type); + /* XXX this isn't right if 'type' is something special + * such as T_AXFR or T_MAILB, since the matching done + * by match() in delete_all() is different from that + * done by wanted() above. + */ + } + dprintf(3, (ddt, "finddata: added %d class %d type %d RRs\n", + count, class, type)); + *countp = count; + return (*lenp - buflen); +} + +/* + * Do we want this data record based on the class and type? + */ +int +wanted(dp, class, type) + struct databuf *dp; + int class, type; +{ + dprintf(3, (ddt, "wanted(%#lx, %d, %d) [%s %s]\n", + (u_long)dp, class, type, + p_class(dp->d_class), p_type(dp->d_type))); + + if (dp->d_class != class && class != C_ANY) + return (0); + if (type == dp->d_type) + return (1); +#ifdef NCACHE + /*-ve $ing stuff, for a T_ANY query, we do not want to return + * -ve $ed RRs. + */ + if (type == T_ANY && dp->d_rcode == NOERROR_NODATA) + return (0); +#endif + + switch (dp->d_type) { + case T_ANY: + return (1); + case T_CNAME: +#ifdef NCACHE + if (dp->d_rcode != NOERROR_NODATA) +#endif + return (1); +#ifdef NCACHE + else + break; +#endif + } + switch (type) { + case T_ANY: + return (1); + + case T_MAILB: + switch (dp->d_type) { + case T_MR: + case T_MB: + case T_MG: + case T_MINFO: + return (1); + } + break; + + case T_AXFR: + /* T_AXFR needs an authoritative SOA */ + if (dp->d_type == T_SOA && dp->d_zone != 0 + && (zones[dp->d_zone].z_flags & Z_AUTH)) + return (1); + break; + } + return (0); +} + +/* + * Add RR entries from dpp array to a query/response. + * Return the number of bytes added or negative the amount + * added if truncation occured. Typically you are + * adding NS records to a response. + */ +int +add_data(np, dpp, cp, buflen, countp) + struct namebuf *np; + struct databuf **dpp; + register u_char *cp; + int buflen, *countp; +{ + register struct databuf *dp; + char dname[MAXDNAME]; + register int n, bytes; + + bytes = *countp = 0; + getname(np, dname, sizeof(dname)); + for (dp = *dpp++; dp != NULL; dp = *dpp++) { + if (stale(dp)) + continue; /* ignore old cache entry */ +#ifdef NCACHE + if (dp->d_rcode) + continue; +#endif + if ((n = make_rr(dname, dp, cp, buflen, 1)) < 0) + return (-bytes); /* Truncation */ + cp += n; + buflen -= n; + bytes += n; + (*countp)++; + } + return (bytes); +} + +/* + * This is best thought of as a "cache invalidate" function. + * It is called whenever a piece of data is determined to have + * become invalid either through a timeout or a validation + * failure. It is better to have no information, than to + * have partial information you pass off as complete. + */ +void +delete_all(np, class, type) + register struct namebuf *np; + int class, type; +{ + register struct databuf *dp, *pdp; + + dprintf(3, (ddt, "delete_all(%#lx:\"%s\" %s %s)\n", + (u_long)np, NAME(*np), p_class(class), p_type(type))); + pdp = NULL; + dp = np->n_data; + while (dp != NULL) { + if ((dp->d_zone == 0) && !(dp->d_flags & DB_F_HINT) + && match(dp, class, type)) { + dp = rm_datum(dp, np, pdp); + continue; + } + pdp = dp; + dp = dp->d_next; + } +} |