ftp.nice.ch/pub/next/unix/network/system/bind-4.9.3pl1.NIHS.bd.tar.gz#/bind-4.9_3-REL/named/ns_resp.c

This is ns_resp.c in view mode; [Download] [Up]

#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.19 1996/01/09 20:23:55 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/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",
			editFailed[] =		"edit of response failed";

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];
	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 (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)) {
			char msgbuf[MAXDNAME*2];

			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 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 (strcasecmp(qname, name) ||
			    qtype != type ||
			    qclass != class) {
				char msgbuf[MAXDNAME*2];

				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);
		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 */
	}

	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);

		/*
		 * 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 && !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.
		 */
		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;
	}
	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"));
		} else {
			dprintf(3, (ddt, "resp: no addrs found for NS's\n"));
		}
		if (cname)	/* a remote CNAME that does not have data */
			goto return_newmsg;
		goto servfail;
	}
	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 */
	nameserIncr(nsa->sin_addr, nssSentFwdR);
	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);
	nameserIncr(from_addr.sin_addr, nssSentFErr);
#ifdef	DATUMREFCNT
	free_nsp(nsp);
#endif
	return;

 return_msg:
	nameserIncr(from_addr.sin_addr, nssRcvdFwdR);
	nameserIncr(qp->q_from.sin_addr, nssSentFwdR);
	/* 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:
	nameserIncr(qp->q_from.sin_addr, nssSentFail);
	hp = (HEADER *)(cname ? 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, (cname ? qp->q_cmsglen : qp->q_msglen),
			qp);
	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;
#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);
	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_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);
		}
		cp += n;
		cp1 = data;
		n = strlen((char *)data) + 1;
		break;

	case T_MINFO:
	case T_SOA:
	case T_RP:
		n = dn_expand(msg, msg + msglen, cp,
			      (char *)data, sizeof data);
		if (n < 0) {
			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);
		}
		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);
		}
		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);
		}
		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);
		}
		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 {
		count = nslookup(nsp, qp, dname, "sysquery");
		if (count <= 0) {
			if (count < 0)
				syslog(LOG_INFO,
				      "sysquery: nslookup reports danger (%s)",
				       dname);
			else
				/* "." domain gets LOG_WARNING here. */
				syslog(dname[0] ? LOG_INFO : LOG_WARNING,
				       "sysquery: no addrs found for NS (%s)",
				       dname);
			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),
		    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 (np->n_dname[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 (np->n_dname[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",
						    tnp->n_dname));
					/* 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 || np->n_dname[0] == '\0'))
		htp = fcachetab;
	else
		htp = hashtab;

 try_again:
	if (htp == fcachetab)
		needs_prime_cache = 1;
	while (np == NULL && htp != NULL) {
		dprintf(3, (ddt, "findns: using %s\n",
			    htp == hashtab ? "cache" : "hints"));
		for (np = htp->h_tab[0]; np != NULL; np = np->n_next)
			if (np->n_dname[0] == '\0')
				break;
		htp = (htp == hashtab ? fcachetab : NULL);	/* Fallback */
	}
	while (np != NULL) {
		dprintf(5, (ddt, "findns: np %#lx '%s'\n",
			    (u_long)np, np->n_dname));
		/* 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",
					    np->n_dname));
				/* Cache invalidate the NS RR's. */
#ifndef DATUMREFCNT
				if (dp->d_ttl < tt.tv_sec)
#endif
					delete_all(np, class, T_NS);
				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, np->n_dname));
			*nspp = NULL;
			*npp = np;
			return (OK);	/* Success, got some NS's */
		}
try_parent:
		np = np->n_parent;
	}
	if (htp)
		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",
				    np->n_dname));
			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, np->n_dname, 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;
	}
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.