aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/bind/bin/named/ns_resp.c
diff options
context:
space:
mode:
authorPeter Wemm <peter@FreeBSD.org>1998-05-03 04:11:49 +0000
committerPeter Wemm <peter@FreeBSD.org>1998-05-03 04:11:49 +0000
commit4e0ffe0baef832be2a8161229e9e4f7cb18a586a (patch)
tree759849259eae9f7cb0d3ddbd7a131081c6688068 /contrib/bind/bin/named/ns_resp.c
parente2f1a81473c07cf46c3104b8dcfdd992a2fe7ca8 (diff)
downloadsrc-4e0ffe0baef832be2a8161229e9e4f7cb18a586a.tar.gz
src-4e0ffe0baef832be2a8161229e9e4f7cb18a586a.zip
Import (trimmed) ISC bind-8.1.2-t3b. This will be updated to 8.1.2 on
final release. Obtained from: ftp.isc.org
Notes
Notes: svn path=/vendor/bind/dist/; revision=35629
Diffstat (limited to 'contrib/bind/bin/named/ns_resp.c')
-rw-r--r--contrib/bind/bin/named/ns_resp.c3298
1 files changed, 3298 insertions, 0 deletions
diff --git a/contrib/bind/bin/named/ns_resp.c b/contrib/bind/bin/named/ns_resp.c
new file mode 100644
index 000000000000..012f89e22413
--- /dev/null
+++ b/contrib/bind/bin/named/ns_resp.c
@@ -0,0 +1,3298 @@
+#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.56 1998/03/16 19:40:07 halley Exp $";
+#endif /* not lint */
+
+/*
+ * 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.
+ */
+
+/*
+ * Portions Copyright (c) 1995 by International Business Machines, Inc.
+ *
+ * International Business Machines, Inc. (hereinafter called IBM) grants
+ * permission under its copyrights to use, copy, modify, and distribute this
+ * Software with or without fee, provided that the above copyright notice and
+ * all paragraphs of this notice appear in all copies, and that the name of IBM
+ * not be used in connection with the marketing of any product incorporating
+ * the Software or modifications thereof, without specific, written prior
+ * permission.
+ *
+ * To the extent it has a right to do so, IBM grants an immunity from suit
+ * under its patents, if any, for the use, sale or manufacture of products to
+ * the extent that such products are used for performing Domain Name System
+ * dynamic updates in TCP/IP networks by means of the Software. No immunity is
+ * granted for any product per se or for any other function of any product.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL,
+ * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN
+ * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ */
+
+/*
+ * Portions Copyright (c) 1996, 1997 by Internet Software Consortium.
+ *
+ * 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.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM 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.
+ */
+
+#include "port_before.h"
+
+#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 <errno.h>
+#include <limits.h>
+#include <resolv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+
+#include <isc/eventlib.h>
+#include <isc/logging.h>
+#include <isc/memcluster.h>
+
+#include "port_after.h"
+
+#include "named.h"
+
+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",
+ wrongQuestion[] = "answer to wrong question",
+ danglingCname[] = "dangling CNAME pointer";
+
+struct db_list {
+ struct db_list *db_next;
+ struct databuf *db_dp;
+};
+
+struct flush_set {
+ char * fs_name;
+ int fs_type;
+ int fs_class;
+ u_int fs_cred;
+ struct db_list *fs_list;
+ struct db_list *fs_last;
+};
+
+static void rrsetadd(struct flush_set *, const char *,
+ struct databuf *),
+ rrsetupdate(struct flush_set *, int flags,
+ struct sockaddr_in),
+ flushrrset(struct flush_set *, struct sockaddr_in),
+ free_flushset(struct flush_set *, int);
+static int rrsetcmp(char *, struct db_list *),
+ check_root(void),
+ check_ns(void),
+ rrextract(u_char *, int, u_char *,
+ struct databuf **, char *, int,
+ struct sockaddr_in, char **);
+static void sysnotify_slaves(const char *, const char *,
+ int, int, int *, int *);
+static void sysnotify_ns(const char *, const char *,
+ int, int, int *, int *);
+static void add_related_additional(char *);
+static void free_related_additional(void);
+static int related_additional(char *);
+static void freestr_maybe(char **);
+
+#define MAX_RELATED 100
+
+static int num_related = 0;
+static char *related[MAX_RELATED];
+
+static char *
+learntFrom(struct qinfo *qp, struct sockaddr_in *server) {
+ static char *buf = NULL;
+ char *a, *ns, *na;
+ struct databuf *db;
+ int i;
+
+ a = ns = na = "<Not Available>";
+
+ for (i = 0; (u_int)i < qp->q_naddr; i++) {
+ if (ina_equal(qp->q_addr[i].ns_addr.sin_addr,
+ server->sin_addr)) {
+ db = qp->q_addr[i].ns;
+ if (db != NULL) {
+ if (NS_OPTION_P(OPTION_HOSTSTATS)) {
+ char nsbuf[20];
+
+ if (db->d_ns != NULL) {
+ strcpy(nsbuf,
+ inet_ntoa(db->d_ns->addr));
+ ns = nsbuf;
+ } else {
+ ns = zones[db->d_zone]
+ .z_origin;
+ }
+ }
+ if (db->d_rcode == 0)
+ na = (char*)qp->q_addr[i].ns->d_data;
+ }
+
+ if (NS_OPTION_P(OPTION_HOSTSTATS)) {
+ char abuf[20];
+
+ db = qp->q_addr[i].nsdata;
+ if (db != NULL) {
+ if (db->d_ns != NULL) {
+ strcpy(abuf,
+ inet_ntoa(db->d_ns->addr));
+ a = abuf;
+ } else {
+ a = zones[db->d_zone].z_origin;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ if (a == ns && ns == na) /* all "UNKNOWN" */
+ return (NULL);
+
+ if (*a == '\0')
+ a = "\".\"";
+ if (*ns == '\0')
+ ns = "\".\"";
+ if (*na == '\0')
+ na = "\".\"";
+
+ if (NS_OPTION_P(OPTION_HOSTSTATS)) {
+ static const char fmt[] = " '%s': learnt (A=%s,NS=%s)";
+
+ buf = newstr(sizeof fmt + strlen(na) + strlen(a) + strlen(ns),
+ 0);
+ if (buf == NULL)
+ return (NULL);
+ sprintf(buf, fmt, na, a, ns);
+ } else {
+ static const char fmt[] = " '%s'";
+
+ buf = newstr(sizeof fmt + strlen(na), 0);
+ if (buf == NULL)
+ return (NULL);
+ sprintf(buf, fmt, na);
+ }
+
+ return (buf);
+}
+
+void
+ns_resp(u_char *msg, int msglen, struct sockaddr_in from, struct qstream *qsp) {
+ struct qinfo *qp;
+ HEADER *hp;
+ struct qserv *qs;
+ struct databuf *ns, *ns2;
+ u_char *cp;
+ u_char *eom = msg + msglen;
+ struct flush_set *flushset = NULL;
+ int flushset_size = 0;
+ struct sockaddr_in *nsa;
+ struct databuf *nsp[NSMAX];
+ int i, c, n, qdcount, ancount, aucount, nscount, arcount, arfirst;
+ u_int qtype, qclass;
+ int restart; /* flag for processing cname response */
+ int validanswer, dbflags;
+ int cname, lastwascname, externalcname;
+ int count, founddata, foundname;
+ int buflen;
+ int newmsglen;
+ char name[MAXDNAME], qname[MAXDNAME], aname[MAXDNAME];
+ char msgbuf[MAXDNAME];
+ char *dname, tmpdomain[MAXDNAME];
+ 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 fwdinfo *fwd;
+ struct databuf *dp;
+ int forcecmsg = 0;
+ char *tname = NULL;
+
+ nameserIncr(from.sin_addr, nssRcvdR);
+ nsp[0] = NULL;
+ hp = (HEADER *) msg;
+ if ((qp = qfindid(hp->id)) == NULL ) {
+ ns_debug(ns_log_default, 1, "DUP? dropped (id %d)",
+ ntohs(hp->id));
+ nameserIncr(from.sin_addr, nssRcvdDupR);
+ return;
+ }
+
+ ns_debug(ns_log_default, 2, "Response (%s %s %s) nsid=%d id=%d",
+ (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;
+ if (cp + 2 * INT16SZ > eom) {
+ formerrmsg = outofDataQuery;
+ goto formerr;
+ }
+ GETSHORT(qtype, cp);
+ GETSHORT(qclass, cp);
+ if (!ns_nameok(qname, qclass, NULL, response_trans,
+ ns_ownercontext(qtype, response_trans),
+ qname, from.sin_addr)) {
+ formerrmsg = badNameFound;
+ goto refused;
+ }
+ 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;
+ }
+ if (strcasecmp(qp->q_name, qname) != 0 ||
+ qp->q_class != qclass ||
+ qp->q_type != qtype) {
+ formerrmsg = wrongQuestion;
+ goto formerr;
+ }
+ } else {
+ strcpy(qname, qp->q_name);
+ qclass = qp->q_class;
+ qtype = qp->q_type;
+ }
+
+ /* 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
+ )) {
+ ns_debug(ns_log_default, 2,
+ "resp: error (ret %d, op %d), dropped",
+ hp->rcode, hp->opcode);
+ switch (hp->rcode) {
+ case SERVFAIL:
+ nameserIncr(from.sin_addr, nssRcvdFail);
+ break;
+ case FORMERR:
+ nameserIncr(from.sin_addr, nssRcvdFErr);
+ break;
+ default:
+ nameserIncr(from.sin_addr, nssRcvdErr);
+ break;
+ }
+ /* mark server as bad */
+ if (!qp->q_fwd)
+ for (i = 0; i < (int)qp->q_naddr; i++)
+ if (ina_equal(qp->q_addr[i].ns_addr.sin_addr,
+ from.sin_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 ||
+ ina_equal(qp->q_addr[qp->q_curaddr].ns_addr.sin_addr,
+ from.sin_addr))
+ retry(qp);
+ return;
+ }
+
+ if (qdcount != 1) {
+ /* We don't generate or forward these (yet). */
+ formerrmsg = notSingleQuery;
+ goto formerr;
+ }
+
+ /*
+ * 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.
+ * XXX - should put this in STATS somewhere.
+ */
+ for (fwd = server_options->fwdtab; fwd; fwd = fwd->next)
+ if (ina_equal(fwd->fwdaddr.sin_addr, from.sin_addr))
+ 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 == NULL) {
+ struct timeval *stp;
+
+ for (n = 0, qs = qp->q_addr;
+ (u_int)n < qp->q_naddr;
+ n++, qs++)
+ if (ina_equal(qs->ns_addr.sin_addr, from.sin_addr))
+ break;
+ if ((u_int)n >= qp->q_naddr) {
+ if (!haveComplained(ina_ulong(from.sin_addr),
+ (u_long)"unexpected source")) {
+ ns_info(ns_log_default,
+ "Response from unexpected source (%s)",
+ sin_ntoa(from));
+ }
+ /*
+ * 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?
+ */
+ /* 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);
+ }
+
+ ns_debug(ns_log_default, 3,
+ "stime %lu/%lu now %lu/%lu rtt %ld",
+ (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) {
+ u_long t;
+
+ if (ns->d_nstime == 0)
+ t = rtrip;
+ else
+ t = ns->d_nstime * ALPHA
+ +
+ (1 - ALPHA) * rtrip;
+ if (t > 65535)
+ t = 65535;
+ ns->d_nstime = (u_int16_t)t;
+ }
+
+ /*
+ * 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;
+ ns_debug(ns_log_default, 2,
+ "NS #%d addr %s used, rtt %d",
+ 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++) {
+ u_long t;
+
+ 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)
+ t = (rtrip * BETA);
+ else
+ t = ns2->d_nstime * BETA
+ +
+ (1 - ALPHA) * rtrip;
+ } else
+ t = ns2->d_nstime * GAMMA;
+ if (t > 65535)
+ t = 65535;
+ ns2->d_nstime = (u_int16_t)t;
+ ns_debug(ns_log_default, 2, "NS #%d %s rtt now %d", 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
+
+ /*
+ * Non-authoritative, no answer, no error, with referral.
+ */
+ if (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, log_get_stream(packet_channel));
+#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;
+ if (tp + 2 * INT16SZ > eom) {
+ formerrmsg = outofDataAuth;
+ goto formerr;
+ }
+ GETSHORT(type, tp);
+ GETSHORT(class, tp);
+ if (!ns_nameok(name, class, NULL, response_trans,
+ ns_ownercontext(type, response_trans),
+ name, from.sin_addr)) {
+ formerrmsg = badNameFound;
+ goto refused;
+ }
+
+ /*
+ * 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.sin_addr, nssRcvdLDel);
+ /* mark server as bad */
+ if (!qp->q_fwd)
+ for (i = 0; i < (int)qp->q_naddr; i++)
+ if (ina_equal(qp->q_addr[i].ns_addr.sin_addr,
+ from.sin_addr))
+ qp->q_addr[i].nretry = MAXRETRY;
+ if (class == C_IN &&
+ !haveComplained(ina_ulong(from.sin_addr),
+ nhash(qp->q_domain))) {
+ char *learnt_from = learntFrom(qp, &from);
+
+ ns_info(ns_log_lame_servers,
+ "Lame server on '%s' (in '%s'?): %s%s",
+ qname, qp->q_domain,
+ sin_ntoa(from),
+ (learnt_from == NULL) ? "" :
+ learnt_from);
+ if (learnt_from != NULL)
+ freestr(learnt_from);
+ }
+
+ /* XXX - doesn't handle responses sent from the wrong
+ * interface on a multihomed server
+ */
+ if (qp->q_fwd ||
+ ina_equal(qp->q_addr[qp->q_curaddr].ns_addr.sin_addr,
+ from.sin_addr))
+ retry(qp);
+ return;
+ }
+ }
+
+ 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_int type, class, dlen;
+ u_int32_t serial;
+ u_char *tp = cp;
+ u_char *rdatap;
+
+ n = dn_expand(msg, eom, tp, name, sizeof name);
+ if (n < 0) {
+ formerrmsg = expandFailedAnswer;
+ goto formerr;
+ }
+ tp += n; /* name */
+ if (tp + 3 * INT16SZ + INT32SZ > eom) {
+ formerrmsg = outofDataAnswer;
+ goto formerr;
+ }
+ GETSHORT(type, tp); /* type */
+ GETSHORT(class, tp); /* class */
+ tp += INT32SZ; /* ttl */
+ GETSHORT(dlen, tp); /* dlen */
+ rdatap = tp; /* start of rdata */
+ if (!ns_nameok(name, class, NULL, response_trans,
+ ns_ownercontext(type, response_trans),
+ name, from.sin_addr)) {
+ formerrmsg = badNameFound;
+ goto refused;
+ }
+ 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 (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 */
+ if (tp + 5 * INT32SZ > eom) {
+ formerrmsg = dlenUnderrunAnswer;
+ goto formerr;
+ }
+ GETLONG(serial, tp);
+ tp += 4 * INT32SZ; /* Skip rest of SOA. */
+ if ((u_int)(tp - rdatap) != dlen) {
+ formerrmsg = dlenOverrunAnswer;
+ goto formerr;
+ }
+
+ qserial_answer(qp, serial, from);
+ qremove(qp);
+ } else {
+ retry(qp);
+ }
+ return;
+ }
+
+ /*
+ * Add the info received in the response to the data base.
+ */
+ arfirst = ancount + aucount;
+ c = arfirst + 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;
+
+ 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 */
+ }
+
+ /* retry using tcp provided this was not a tcp query */
+ if (!(qp->q_flags & Q_USEVC)) {
+ qp->q_flags |= Q_USEVC;
+ unsched(qp);
+ schedretry(qp, 60);
+ if (tcp_send(qp) != NOERROR)
+ /*
+ * We're probably in trouble if tcp_send
+ * failed, but we'll try to press on because
+ * there isn't anything else to do.
+ */
+ retry(qp);
+ return;
+ } else if (!qsp) {
+ /* outstanding udp response */
+ return;
+ }
+
+ /* XXX truncated tcp response */
+ ns_error(ns_log_default,
+ "ns_resp: TCP truncated: \"%s\" %s %s",
+ qname, p_class(qclass), p_type(qtype));
+ /* mark this server as bad */
+ if (!qp->q_fwd)
+ for (i = 0; i < (int)qp->q_naddr; i++)
+ if (ina_equal(qp->q_addr[i].ns_addr.sin_addr,
+ from.sin_addr))
+ qp->q_addr[i].nretry = MAXRETRY;
+
+ /* try another server, it may have a bigger write buffer */
+ retry(qp);
+ return;
+ }
+
+ tp = cp;
+
+ restart = 0;
+ validanswer = 0;
+ nscount = 0;
+ cname = 0;
+ lastwascname = 0;
+ externalcname = 0;
+ strcpy(aname, qname);
+
+ if (count) {
+ /* allocate 1 extra record for end of set detection */
+ flushset_size = (count + 1) * sizeof *flushset;
+ flushset = memget(flushset_size);
+ if (flushset == NULL)
+ panic("flushset: out of memory", NULL);
+ memset(flushset, 0, flushset_size);
+ } else
+ flushset = NULL;
+
+ for (i = 0; i < count; i++) {
+ struct databuf *dp;
+ int type;
+
+ freestr_maybe(&tname);
+ if (cp >= eom) {
+ free_related_additional();
+ if (flushset != NULL)
+ free_flushset(flushset, flushset_size);
+ formerrmsg = outofDataFinal;
+ goto formerr;
+ }
+ n = rrextract(msg, msglen, cp, &dp, name, sizeof name, from,
+ &tname);
+ if (n < 0) {
+ free_related_additional();
+ freestr_maybe(&tname);
+ if (flushset != NULL)
+ free_flushset(flushset, flushset_size);
+ formerrmsg = outofDataFinal;
+ if (hp->rcode == REFUSED)
+ goto refused;
+ else
+ goto formerr;
+ }
+ cp += n;
+ if (!dp)
+ continue;
+ type = dp->d_type;
+ if (i < ancount) {
+ /* Answer section. */
+ if (externalcname || strcasecmp(name, aname) != 0) {
+ if (!externalcname)
+ ns_info(ns_log_resp_checks,
+ "wrong ans. name (%s != %s)",
+ name[0] ? name : ".",
+ aname[0] ? aname : ".");
+ else
+ ns_debug(ns_log_resp_checks, 3,
+ "ignoring answer '%s' after external cname",
+ name);
+ db_freedata(dp);
+ continue;
+ }
+ if (type == T_CNAME &&
+ qtype != T_CNAME && qtype != T_ANY) {
+ strcpy(aname, (char *)dp->d_data);
+ if (!samedomain(aname, qp->q_domain))
+ externalcname = 1;
+ cname++;
+ lastwascname = 1;
+ } else {
+ validanswer = 1;
+ lastwascname = 0;
+ }
+
+ if (tname != NULL) {
+ add_related_additional(tname);
+ tname = NULL;
+ }
+
+ dp->d_cred = (hp->aa && !strcasecmp(name, qname))
+ ? DB_C_AUTH
+ : DB_C_ANSWER;
+ } else {
+ /* After answer section. */
+ if (lastwascname) {
+ ns_debug(ns_log_resp_checks, 3,
+ "last was cname, ignoring auth. and add.");
+ db_freedata(dp);
+ break;
+ }
+ if (i < arfirst) {
+ /* Authority section. */
+ switch (type) {
+ case T_NS:
+ case T_SOA:
+ if (!samedomain(aname, name)){
+ ns_info(ns_log_resp_checks,
+ "bad referral (%s !< %s)",
+ aname[0] ? aname : ".",
+ name[0] ? name : ".");
+ db_freedata(dp);
+ continue;
+ } else if (!samedomain(name,
+ qp->q_domain)) {
+ if (!externalcname)
+ ns_info(ns_log_resp_checks,
+ "bad referral (%s !< %s)",
+ name[0] ? name : ".",
+ qp->q_domain[0] ?
+ qp->q_domain : ".");
+ db_freedata(dp);
+ continue;
+ }
+ if (type == T_NS) {
+ nscount++;
+ add_related_additional(tname);
+ tname = NULL;
+ }
+ break;
+ case T_NXT:
+ /* XXX check */
+ break;
+ case T_SIG:
+ /* XXX check that it relates to an
+ NS or SOA or NXT */
+ break;
+ default:
+ ns_info(ns_log_resp_checks,
+ "invalid RR type '%s' in authority section (name = '%s') from %s",
+ p_type(type), name,
+ sin_ntoa(from));
+ db_freedata(dp);
+ continue;
+ }
+ } else {
+ /* Additional section. */
+ switch (type) {
+ case T_A:
+ case T_AAAA:
+ if (externalcname ||
+ !samedomain(name, qp->q_domain)) {
+ ns_debug(ns_log_resp_checks, 3,
+ "ignoring additional info '%s' type %s",
+ name, p_type(type));
+ db_freedata(dp);
+ continue;
+ }
+ if (!related_additional(name)) {
+ ns_info(ns_log_resp_checks,
+ "unrelated additional info '%s' type %s from %s",
+ name, p_type(type),
+ sin_ntoa(from));
+ db_freedata(dp);
+ continue;
+ }
+ break;
+ case T_KEY:
+ /* XXX check? */
+ break;
+ case T_SIG:
+ /*
+ * XXX a SIG RR should relate
+ * to some other RR in this section,
+ * although if it's the last RR
+ * it might be a transaction signature.
+ */
+ break;
+ default:
+ ns_info(ns_log_resp_checks,
+ "invalid RR type '%s' in additional section (name = '%s') from %s",
+ p_type(type), name,
+ sin_ntoa(from));
+ db_freedata(dp);
+ continue;
+ }
+ }
+ dp->d_cred = (qp->q_flags & Q_PRIMING)
+ ? DB_C_ANSWER
+ : DB_C_ADDITIONAL;
+ }
+ rrsetadd(flushset, name, dp);
+ }
+ free_related_additional();
+ freestr_maybe(&tname);
+ if (flushset != NULL) {
+ rrsetupdate(flushset, dbflags, from);
+ free_flushset(flushset, flushset_size);
+ }
+ if (lastwascname && !externalcname)
+ ns_info(ns_log_cname, "%s (%s)", danglingCname, aname);
+
+ if (cp > eom) {
+ formerrmsg = outofDataAFinal;
+ goto formerr;
+ }
+
+ if ((qp->q_flags & Q_SYSTEM) && ancount) {
+ if ((qp->q_flags & Q_PRIMING) && !check_root()) {
+ /* mark server as bad */
+ if (!qp->q_fwd)
+ for (i = 0; i < (int)qp->q_naddr; i++)
+ if (ina_equal(qp->q_addr[i].ns_addr.sin_addr,
+ from.sin_addr))
+ qp->q_addr[i].nretry = MAXRETRY;
+ /* XXX - doesn't handle responses sent from
+ * the wronginterface on a multihomed server
+ */
+ if (qp->q_fwd ||
+ qp->q_addr[qp->q_curaddr].ns_addr.sin_addr.s_addr
+ == from.sin_addr.s_addr)
+ retry(qp);
+ return;
+ }
+ ns_debug(ns_log_default, 3,
+ "resp: leaving, SYSQUERY ancount %d", 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, ns_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;
+ }
+
+ /*
+ * 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)) {
+ ns_debug(ns_log_default, 3,
+ "resp: got as much answer as there is");
+ goto return_msg;
+ }
+
+ /*
+ * We might want to cache this negative answer.
+ *
+ * if ancount != 0 and rcode == NOERROR we cannot determine if the
+ * CNAME chain has been processed to completion or not, so just
+ * restart the query. DNS needs a NODATA return code!
+ */
+ if (((hp->rcode == NXDOMAIN) && (cname == ancount)) ||
+ ((hp->rcode == NOERROR) && (ancount == 0) && (nscount == 0)))
+ {
+ cache_n_resp(msg, msglen, from);
+
+ if (!qp->q_cmsglen) {
+ ns_debug(ns_log_default, 3,
+ "resp: leaving NO: auth = %d", hp->aa);
+ goto return_msg;
+ }
+ forcecmsg = 1;
+ }
+
+ /*
+ * All messages in here need further processing. i.e. they
+ * are either CNAMEs or we got referred again.
+ */
+ count = 0;
+ founddata = 0;
+ dname = name;
+ /*
+ * 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 ((forcecmsg && qp->q_cmsglen) ||
+ ((!restart || !cname) && qp->q_cmsglen && ancount)) {
+ ns_debug(ns_log_default, 1, "Cname second pass");
+ newmsglen = MIN(PACKETSZ, qp->q_cmsglen);
+ memcpy(newmsg, qp->q_cmsg, newmsglen);
+ } else {
+ newmsglen = MIN(PACKETSZ, msglen);
+ memcpy(newmsg, msg, newmsglen);
+ }
+ hp = (HEADER *) newmsg;
+ hp->ancount = htons(0);
+ hp->nscount = htons(0);
+ hp->arcount = htons(0);
+ hp->rcode = NOERROR;
+ 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) {
+ ns_debug(ns_log_default, 1, "dn_expand failed");
+ goto servfail;
+ }
+ if (!res_dnok(dname)) {
+ ns_debug(ns_log_default, 1, "bad name (%s)", dname);
+ goto servfail;
+ }
+ cp += n + QFIXEDSZ;
+ buflen = sizeof(newmsg) - (cp - newmsg);
+
+ cname = 0;
+
+ try_again:
+ ns_debug(ns_log_default, 1, "resp: nlookup(%s) qtype=%d", dname,
+ qtype);
+ foundname = 0;
+ fname = "";
+ htp = hashtab; /* lookup relative to root */
+ np = nlookup(dname, &htp, &fname, 0);
+ ns_debug(ns_log_default, 1, "resp: %s '%s' as '%s' (cname=%d)",
+ np == NULL ? "missed" : "found", dname, fname, cname);
+ if (np == NULL || fname != dname)
+ goto fetch_ns;
+
+ foundname++;
+ count = cp - newmsg;
+ /*
+ * Look for NXDOMAIN record.
+ */
+ for (dp = np->n_data; dp; dp = dp->d_next) {
+ if (!stale(dp) && (dp->d_rcode == NXDOMAIN) &&
+ (dp->d_class == (int)qclass)) {
+#ifdef RETURNSOA
+ n = finddata(np, qclass, T_SOA, hp, &dname,
+ &buflen, &count);
+ if ( n != 0) {
+ if (count) {
+ cp += n;
+ buflen -= n;
+ newmsglen += n;
+ hp->nscount = htons((u_int16_t)count);
+ }
+ if (hp->rcode == NOERROR_NODATA) {
+ hp->rcode = NOERROR;
+ goto return_newmsg;
+ }
+ }
+#endif
+ hp->rcode = NXDOMAIN;
+ /*
+ * XXX forcing AA all the time isn't right, but
+ * we have to work that way by default
+ * for compatibility with older servers.
+ */
+ if (!NS_OPTION_P(OPTION_NONAUTH_NXDOMAIN))
+ hp->aa = 1;
+ ns_debug(ns_log_default, 3, "resp: NXDOMAIN aa = %d",
+ hp->aa);
+ goto return_newmsg;
+ }
+ }
+ n = finddata(np, qclass, qtype, hp, &dname, &buflen, &count);
+ if (n == 0)
+ goto fetch_ns; /* NO data available */
+ if (hp->rcode) {
+ if (hp->rcode == NOERROR_NODATA)
+ hp->rcode = NOERROR;
+#ifdef RETURNSOA
+ if (count) {
+ cp += n;
+ buflen -= n;
+ hp->nscount = htons((u_int16_t)count);
+ }
+#endif
+ goto return_newmsg;
+ }
+ 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;
+
+ ns_debug(ns_log_default, 3,
+ "resp: foundname=%d, count=%d, founddata=%d, cname=%d",
+ 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).
+ */
+ free_nsp(nsp);
+ switch (findns(&np, qclass, nsp, &count, 0)) {
+ case NXDOMAIN: /* shouldn't happen */
+ ns_debug(ns_log_default, 3, "req: leaving (%s, rcode %d)",
+ dname, hp->rcode);
+ if (!foundname)
+ hp->rcode = NXDOMAIN;
+ if (qclass != C_ANY) {
+ hp->aa = 1;
+ if (np && (!foundname || !founddata)) {
+ 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) {
+ ns_debug(ns_log_default, 1,
+ "resp: MAXQUERIES exceeded (%s %s %s)",
+ dname, p_class(qclass), p_type(qtype));
+ ns_info(ns_log_default,
+ "MAXQUERIES exceeded, possible data loop in resolving (%s)",
+ dname);
+ goto servfail;
+ }
+
+ /* Reset the query control structure */
+
+ ns_freeqns(qp, "ns_resp");
+ qp->q_naddr = 0;
+ qp->q_curaddr = 0;
+ qp->q_fwd = server_options->fwdtab;
+
+ if (qp->q_domain != NULL)
+ freestr(qp->q_domain);
+ getname(np, tmpdomain, sizeof tmpdomain);
+ qp->q_domain = savestr(tmpdomain, 1);
+
+ if ((n = nslookup(nsp, qp, dname, "ns_resp")) <= 0) {
+ if (n < 0) {
+ ns_debug(ns_log_default, 3,
+ "resp: nslookup reports danger");
+ if (cname) /* a remote CNAME that does not have data */
+ goto return_newmsg;
+ goto servfail;
+ } else {
+ ns_debug(ns_log_default, 3,
+ "resp: no addrs found for NS's");
+ /*
+ * 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) {
+ ns_debug(ns_log_default, 3,
+ "resp: leaving, MAXCNAMES exceeded");
+ goto servfail;
+ }
+ ns_debug(ns_log_default, 1, "q_cname = %d", qp->q_cname);
+ ns_debug(ns_log_default, 3,
+ "resp: building recursive query; nslookup");
+ if (qp->q_cmsg == NULL) {
+ qp->q_cmsg = qp->q_msg;
+ qp->q_cmsglen = qp->q_msglen;
+ qp->q_cmsgsize = qp->q_msgsize;
+ } else if (qp->q_msg != NULL)
+ memput(qp->q_msg, qp->q_msgsize);
+ qp->q_msg = (u_char *)memget(PACKETSZ);
+ if (qp->q_msg == NULL) {
+ ns_notice(ns_log_default, "resp: memget error");
+ goto servfail;
+ }
+ qp->q_msgsize = PACKETSZ;
+ n = res_mkquery(QUERY, dname, qclass, qtype,
+ NULL, 0, NULL, qp->q_msg, PACKETSZ);
+ if (n < 0) {
+ ns_info(ns_log_default, "resp: res_mkquery(%s) failed",
+ dname);
+ goto servfail;
+ }
+ if (qp->q_name != NULL)
+ freestr(qp->q_name);
+ qp->q_name = savestr(dname, 1);
+ 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);
+ ns_debug(ns_log_default, 1,
+ "resp: forw -> %s ds=%d nsid=%d id=%d %dms",
+ 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,
+ log_get_stream(packet_channel));
+#endif
+ if (qp->q_flags & Q_USEVC) {
+ if (tcp_send(qp) != NOERROR) {
+ if (!haveComplained(ina_ulong(nsa->sin_addr),
+ (u_long)tcpsendStr))
+ ns_info(ns_log_default,
+ "ns_forw: tcp_send(%s) failed: %s",
+ sin_ntoa(*nsa), strerror(errno));
+ }
+ } else if (sendto(ds, (char*)qp->q_msg, qp->q_msglen, 0,
+ (struct sockaddr *)nsa,
+ sizeof(struct sockaddr_in)) < 0)
+ {
+ if (!haveComplained(ina_ulong(nsa->sin_addr),
+ (u_long)sendtoStr))
+ ns_info(ns_log_default, "ns_resp: sendto(%s): %s",
+ sin_ntoa(*nsa), strerror(errno));
+ 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);
+ ns_debug(ns_log_default, 3, "resp: Query sent.");
+ free_nsp(nsp);
+ return;
+
+ formerr:
+ if (!haveComplained(ina_ulong(from.sin_addr), (u_long)formerrmsg))
+ ns_info(ns_log_resp_checks, "Malformed response from %s (%s)",
+ sin_ntoa(from), formerrmsg);
+ free_nsp(nsp);
+ return;
+
+ return_msg:
+ nameserIncr(from.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 = (NS_OPTION_P(OPTION_NORECURSE) == 0);
+ (void) send_msg(msg, msglen, qp);
+ qremove(qp);
+ free_nsp(nsp);
+ return;
+
+ return_newmsg:
+ nameserIncr(qp->q_from.sin_addr, nssSentAns);
+
+ if (!hp->aa)
+ nameserIncr(qp->q_from.sin_addr, nssSentNaAns);
+ if (hp->rcode == NXDOMAIN)
+ nameserIncr(qp->q_from.sin_addr, nssSentNXD);
+ n = doaddinfo(hp, cp, buflen);
+ cp += n;
+ buflen -= n;
+ hp->qr = 1;
+ hp->id = qp->q_id;
+ hp->rd = 1;
+ hp->ra = (NS_OPTION_P(OPTION_NORECURSE) == 0);
+ (void) send_msg(newmsg, cp - newmsg, qp);
+ qremove(qp);
+ free_nsp(nsp);
+ return;
+
+ refused:
+ hp = (HEADER *)(qp->q_cmsglen ? qp->q_cmsg : qp->q_msg);
+ hp->rcode = REFUSED;
+ hp->qr = 1;
+ hp->id = qp->q_id;
+ hp->rd = 1;
+ hp->ra = (NS_OPTION_P(OPTION_NORECURSE) == 0);
+ (void) send_msg((u_char *)hp,
+ (qp->q_cmsglen ? qp->q_cmsglen : qp->q_msglen),
+ qp);
+ qremove(qp);
+ free_nsp(nsp);
+ return;
+
+ servfail:
+ nameserIncr(qp->q_from.sin_addr, nssSentFail);
+ 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 = (NS_OPTION_P(OPTION_NORECURSE) == 0);
+ (void) send_msg((u_char *)hp,
+ (qp->q_cmsglen ? qp->q_cmsglen : qp->q_msglen),
+ qp);
+ qremove(qp);
+ free_nsp(nsp);
+ return;
+
+ timeout:
+ if (qp->q_stream)
+ sq_remove(qp->q_stream);
+ qremove(qp);
+ free_nsp(nsp);
+ return;
+}
+
+#define BOUNDS_CHECK(ptr, count) \
+ do { \
+ if ((ptr) + (count) > eom) { \
+ hp->rcode = FORMERR; \
+ return (-1); \
+ } \
+ } while (0)
+
+static int
+rrextract(u_char *msg, int msglen, u_char *rrp, struct databuf **dpp,
+ char *dname, int namelen, struct sockaddr_in from, char **tnamep)
+{
+ u_char *cp, *eom, *rdatap;
+ u_int class, type, dlen;
+ int n, n1;
+ u_int32_t ttl;
+ u_char *cp1, data[MAXDATA*2];
+ HEADER *hp = (HEADER *)msg;
+ enum context context;
+
+ if (tnamep != NULL)
+ *tnamep = NULL;
+
+ *dpp = NULL;
+ cp = rrp;
+ eom = msg + msglen;
+ if ((n = dn_expand(msg, eom, cp, dname, namelen)) < 0) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ cp += n;
+ BOUNDS_CHECK(cp, 2*INT16SZ + INT32SZ + INT16SZ);
+ GETSHORT(type, cp);
+ GETSHORT(class, cp);
+ GETLONG(ttl, cp);
+ if (ttl > MAXIMUM_TTL) {
+ ns_debug(ns_log_default, 5, "%s: converted TTL > %u to 0",
+ dname, MAXIMUM_TTL);
+ ttl = 0;
+ }
+ GETSHORT(dlen, cp);
+ BOUNDS_CHECK(cp, dlen);
+ rdatap = cp;
+ if (!ns_nameok(dname, class, NULL, response_trans,
+ ns_ownercontext(type, response_trans),
+ dname, from.sin_addr)) {
+ hp->rcode = REFUSED;
+ return (-1);
+ }
+ ns_debug(ns_log_default, 3,
+ "rrextract: dname %s type %d class %d ttl %d",
+ dname, type, class, ttl);
+ /*
+ * Convert the resource record data into the internal
+ * database format.
+ *
+ * On entry to the switch:
+ * CP points to the RDATA section of the wire-format RR.
+ * DLEN is its length.
+ * The memory area at DATA is available for processing.
+ *
+ * On exit from the switch:
+ * CP has been incremented past the RR.
+ * CP1 points to the RDATA section of the database-format RR.
+ * N contains the length of the RDATA section of the dbase-format RR.
+ *
+ * The new data at CP1 for length N will be copied into the database,
+ * so it need not be in any particular storage location.
+ */
+ switch (type) {
+ case T_A:
+ if (dlen != INT32SZ) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ /*FALLTHROUGH*/
+ case T_WKS:
+ case T_HINFO:
+ case T_TXT:
+ case T_X25:
+ case T_ISDN:
+ case T_NSAP:
+ case T_AAAA:
+ case T_LOC:
+ case T_KEY:
+ 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, eom, cp, (char *)data, sizeof data);
+ if (n < 0) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ if (!ns_nameok((char *)data, class, NULL, response_trans,
+ type == T_PTR ?ns_ptrcontext(dname) :domain_ctx,
+ dname, from.sin_addr)) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ cp += n;
+ cp1 = data;
+ n = strlen((char *)data) + 1;
+ if (tnamep != NULL && (type == T_NS || type == T_MB))
+ *tnamep = savestr((char *)cp1, 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, eom, cp, (char *)data, sizeof data);
+ if (n < 0) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ if (!ns_nameok((char *)data, class, NULL, response_trans,
+ context, dname, from.sin_addr)) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ cp += n;
+ /*
+ * The next use of 'cp' is dn_expand(), so we don't have
+ * to BOUNDS_CHECK() here.
+ */
+ cp1 = data + (n = strlen((char *)data) + 1);
+ n1 = sizeof(data) - n;
+ if (type == T_SOA)
+ n1 -= 5 * INT32SZ;
+ n = dn_expand(msg, eom, 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, NULL, response_trans,
+ context, dname, from.sin_addr)) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ cp += n;
+ cp1 += strlen((char *)cp1) + 1;
+ if (type == T_SOA) {
+ n = 5 * INT32SZ;
+ BOUNDS_CHECK(cp, n);
+ memcpy(cp1, cp, n);
+ cp += n;
+ cp1 += n;
+ }
+ n = cp1 - data;
+ cp1 = data;
+ break;
+
+ case T_NAPTR:
+ /* Grab weight and port. */
+ BOUNDS_CHECK(cp, INT16SZ*2);
+ memcpy(data, cp, INT16SZ*2);
+ cp1 = data + INT16SZ*2;
+ cp += INT16SZ*2;
+
+ /* Flags */
+ BOUNDS_CHECK(cp, 1);
+ n = *cp++;
+ BOUNDS_CHECK(cp, n);
+ *cp1++ = n;
+ memcpy(cp1, cp, n);
+ cp += n; cp1 += n;
+
+ /* Service */
+ BOUNDS_CHECK(cp, 1);
+ n = *cp++;
+ BOUNDS_CHECK(cp, n);
+ *cp1++ = n;
+ memcpy(cp1, cp, n);
+ cp += n; cp1 += n;
+
+ /* Regexp */
+ BOUNDS_CHECK(cp, 1);
+ n = *cp++;
+ BOUNDS_CHECK(cp, n);
+ *cp1++ = n;
+ memcpy(cp1, cp, n);
+ cp += n; cp1 += n;
+
+ /* Replacement */
+ n = dn_expand(msg, eom, cp, (char *)cp1,
+ sizeof data - (cp1 - data));
+ if (n < 0) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ if (!ns_nameok((char *)cp1, class, NULL, response_trans,
+ hostname_ctx, dname, from.sin_addr)) {
+ 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_MX:
+ case T_AFSDB:
+ case T_RT:
+ case T_SRV:
+ /* grab preference */
+ BOUNDS_CHECK(cp, INT16SZ);
+ memcpy(data, cp, INT16SZ);
+ cp1 = data + INT16SZ;
+ cp += INT16SZ;
+
+ if (type == T_SRV) {
+ /* Grab weight and port. */
+ BOUNDS_CHECK(cp, INT16SZ*2);
+ memcpy(cp1, cp, INT16SZ*2);
+ cp1 += INT16SZ*2;
+ cp += INT16SZ*2;
+ }
+
+ /* get name */
+ n = dn_expand(msg, eom, cp, (char *)cp1,
+ sizeof data - (cp1 - data));
+ if (n < 0) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ if (!ns_nameok((char *)cp1, class, NULL, response_trans,
+ hostname_ctx, dname, from.sin_addr)) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ cp += n;
+
+ if (tnamep != NULL)
+ *tnamep = savestr((char *)cp1, 1);
+
+ /* compute end of data */
+ cp1 += strlen((char *)cp1) + 1;
+ /* compute size of data */
+ n = cp1 - data;
+ cp1 = data;
+ break;
+
+ case T_PX:
+ /* grab preference */
+ BOUNDS_CHECK(cp, INT16SZ);
+ memcpy(data, cp, INT16SZ);
+ cp1 = data + INT16SZ;
+ cp += INT16SZ;
+
+ /* get MAP822 name */
+ n = dn_expand(msg, eom, cp, (char *)cp1,
+ sizeof data - INT16SZ);
+ if (n < 0) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ if (!ns_nameok((char *)cp1, class, NULL, response_trans,
+ domain_ctx, dname, from.sin_addr)) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ cp += n;
+ /*
+ * The next use of 'cp' is dn_expand(), so we don't have
+ * to BOUNDS_CHECK() here.
+ */
+ cp1 += (n = strlen((char *)cp1) + 1);
+ n1 = sizeof(data) - n;
+ n = dn_expand(msg, eom, cp, (char *)cp1, n1);
+ if (n < 0) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ if (!ns_nameok((char *)cp1, class, NULL, response_trans,
+ domain_ctx, dname, from.sin_addr)) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ cp += n;
+ cp1 += strlen((char *)cp1) + 1;
+ n = cp1 - data;
+ cp1 = data;
+ break;
+
+ case T_SIG: {
+ u_long origTTL, exptime, signtime, timetilexp, now;
+
+ /* Check signature time, expiration, and adjust TTL. */
+ /* This code is similar to that in db_load.c. */
+
+ /* Skip coveredType, alg, labels */
+ BOUNDS_CHECK(cp, INT16SZ + 1 + 1 + 3*INT32SZ);
+ cp1 = cp + INT16SZ + 1 + 1;
+ GETLONG(origTTL, cp1);
+ GETLONG(exptime, cp1);
+ GETLONG(signtime, cp1);
+ now = time(NULL); /* Get current time in GMT/UTC */
+
+ /* Don't let bogus name servers increase the signed TTL */
+ if (ttl > origTTL) {
+ ns_debug(ns_log_default, 3,
+ "shrinking SIG TTL from %d to origTTL %d",
+ ttl, origTTL);
+ ttl = origTTL;
+ }
+
+ /* Don't let bogus signers "sign" in the future. */
+ if (signtime > now) {
+ ns_debug(ns_log_default, 3,
+ "ignoring SIG: signature date %s is in the future",
+ p_secstodate (signtime));
+ return ((cp - rrp) + dlen);
+ }
+
+ /* Ignore received SIG RR's that are already expired. */
+ if (exptime <= now) {
+ ns_debug(ns_log_default, 3,
+ "ignoring SIG: expiration %s is in the past",
+ p_secstodate (exptime));
+ return ((cp - rrp) + dlen);
+ }
+
+ /* Lop off the TTL at the expiration time. */
+ timetilexp = exptime - now;
+ if (timetilexp < ttl) {
+ ns_debug(ns_log_default, 3,
+ "shrinking expiring %s SIG TTL from %d to %d",
+ p_secstodate (exptime), ttl, timetilexp);
+ ttl = timetilexp;
+ }
+
+ /* The following code is copied from named-xfer.c. */
+ cp1 = (u_char *)data;
+
+ /* first just copy over the type_covered, algorithm, */
+ /* labels, orig ttl, two timestamps, and the footprint */
+ BOUNDS_CHECK(cp, 18);
+ memcpy(cp1, cp, 18);
+ cp += 18;
+ cp1 += 18;
+
+ /* then the signer's name */
+ n = dn_expand(msg, eom, cp, (char *)cp1, (sizeof data) - 18);
+ if (n < 0) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ cp += n;
+ cp1 += strlen((char*)cp1)+1;
+
+ /* finally, we copy over the variable-length signature.
+ Its size is the total data length, minus what we copied. */
+ if (18 + (u_int)n > dlen) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ n = dlen - (18 + n);
+ if (n > ((int)(sizeof data) - (int)(cp1 - (u_char *)data))) {
+ hp->rcode = FORMERR;
+ return (-1); /* out of room! */
+ }
+ memcpy(cp1, cp, n);
+ cp += n;
+ cp1 += n;
+
+ /* compute size of data */
+ n = cp1 - (u_char *)data;
+ cp1 = (u_char *)data;
+ break;
+ }
+
+ default:
+ ns_debug(ns_log_default, 3, "unknown type %d", type);
+ return ((cp - rrp) + dlen);
+ }
+
+ if (cp > eom) {
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ if ((u_int)(cp - rdatap) != dlen) {
+ ns_debug(ns_log_default, 3,
+ "encoded rdata length is %u, but actual length was %u",
+ dlen, (u_int)(cp - rdatap));
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+ if (n > MAXDATA) {
+ ns_debug(ns_log_default, 1,
+ "update type %d: %d bytes is too much data",
+ type, n);
+ hp->rcode = FORMERR;
+ return (-1);
+ }
+
+ ttl += tt.tv_sec;
+ *dpp = savedata(class, type, ttl, cp1, n);
+ return (cp - rrp);
+}
+
+int
+send_msg(u_char *msg, int msglen, struct qinfo *qp) {
+ if (qp->q_flags & Q_SYSTEM)
+ return (1);
+ if (!qp->q_stream && (msglen > PACKETSZ))
+ msglen = trunc_adjust(msg, msglen, PACKETSZ);
+ ns_debug(ns_log_default, 1, "send_msg -> %s (%s %d) id=%d",
+ sin_ntoa(qp->q_from),
+ qp->q_stream == NULL ? "UDP" : "TCP",
+ qp->q_stream == NULL ? qp->q_dfd : qp->q_stream->s_rfd,
+ ntohs(qp->q_id));
+#ifdef DEBUG
+ if (debug > 4) {
+ struct qinfo *tqp;
+
+ for (tqp = nsqhead; tqp != NULL; tqp = tqp->q_link) {
+ ns_debug(ns_log_default, 4,
+ "qp %#lx q_id: %d q_nsid: %d q_msglen: %d",
+ (u_long)tqp, tqp->q_id,
+ tqp->q_nsid, tqp->q_msglen);
+ ns_debug(ns_log_default, 4,
+ "\tq_naddr: %d q_curaddr: %d",
+ tqp->q_naddr, tqp->q_curaddr);
+ ns_debug(ns_log_default, 4,
+ "\tq_next: %#lx q_link: %#lx",
+ (u_long)qp->q_next, (u_long)qp->q_link);
+ }
+ }
+ if (debug >= 6)
+ fp_nquery(msg, msglen, log_get_stream(packet_channel));
+#endif /* DEBUG */
+ if (qp->q_stream == NULL) {
+ if (sendto(qp->q_dfd, (char*)msg, msglen, 0,
+ (struct sockaddr *)&qp->q_from,
+ sizeof(qp->q_from)) < 0) {
+ if (!haveComplained(ina_ulong(qp->q_from.sin_addr),
+ (u_long)sendtoStr))
+#if defined(SPURIOUS_ECONNREFUSED)
+ if (errno != ECONNREFUSED)
+#endif
+ ns_info(ns_log_default,
+ "send_msg: sendto(%s): %s",
+ sin_ntoa(qp->q_from),
+ strerror(errno));
+ nameserIncr(qp->q_from.sin_addr, nssSendtoErr);
+ return (1);
+ }
+ } else
+ writestream(qp->q_stream, (u_char*)msg, msglen);
+ 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;
+ struct qinfo *oqp;
+{
+ char dname[MAXDNAME];
+
+ 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;
+ ns_debug(ns_log_default, 2, "prime: %s", dname);
+ (void) sysquery(dname, class, type, NULL, 0, QUERY);
+}
+#endif
+
+void
+prime_cache() {
+ struct qinfo *qp;
+
+ /*
+ * XXX - should this always be skipped if OPTION_FORWARD_ONLY
+ * or should it be another option? What about when we are
+ * doing selective forwarding?
+ */
+ if (!NS_OPTION_P(OPTION_FORWARD_ONLY)) {
+ ns_debug(ns_log_default, 1, "prime_cache: priming = %d",
+ priming);
+ if (!priming && fcachetab->h_tab[0] != NULL) {
+ 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
+/*
+ * 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(const char *dname, int class, int type) {
+ const char *zname, *fname;
+ int nns, na, zn, n;
+ struct zoneinfo *zp;
+ struct hashbuf *htp;
+ struct namebuf *np;
+ struct databuf *dp;
+
+ ns_debug(ns_log_notify, 3, "sysnotify(%s, %s, %s)",
+ dname, p_class(class), p_type(type));
+ htp = hashtab;
+ np = nlookup(dname, &htp, &fname, 0);
+ if (np == NULL) {
+ ns_warning(ns_log_notify, "sysnotify: can't find \"%s\"",
+ dname);
+ return;
+ }
+ zn = findMyZone(np, class);
+ if (zn == DB_Z_CACHE) {
+ ns_warning(ns_log_notify, "sysnotify: not auth for zone %s",
+ dname);
+ return;
+ }
+ zp = &zones[zn];
+ if (zp->z_notify == znotify_no ||
+ (zp->z_notify == znotify_use_default &&
+ NS_OPTION_P(OPTION_NONOTIFY)))
+ return;
+ if (zp->z_type != z_master && zp->z_type != z_slave) {
+ ns_warning(ns_log_notify, "sysnotify: %s not master or slave",
+ dname);
+ return;
+ }
+ zname = zp->z_origin;
+ nns = na = 0;
+ if (zp->z_type == z_master)
+ sysnotify_slaves(dname, zname, class, zn, &nns, &na);
+ if (zp->z_notify_count != 0) {
+ struct in_addr *also_addr = zp->z_also_notify;
+ int i;
+
+ for (i = 0; i < zp->z_notify_count; i++) {
+ sysquery(dname, class, T_SOA, also_addr, 1,
+ NS_NOTIFY_OP);
+ also_addr++;
+ }
+ nns += zp->z_notify_count;
+ na += zp->z_notify_count;
+ }
+ if (nns != 0 || na != 0)
+ ns_info(ns_log_notify,
+ "Sent NOTIFY for \"%s %s %s\" (%s); %d NS, %d A",
+ dname, p_class(class), p_type(type), zname, nns, na);
+}
+
+static void
+sysnotify_slaves(const char *dname, const char *zname, int class, int zn,
+ int *nns, int *na)
+{
+ const char *mname, *fname;
+ struct hashbuf *htp;
+ struct namebuf *np;
+ struct databuf *dp;
+
+ /*
+ * Master.
+ */
+ htp = hashtab;
+ np = nlookup(zname, &htp, &fname, 0);
+ if (!np) {
+ ns_warning(ns_log_notify,
+ "sysnotify: found name \"%s\" but not zone",
+ dname);
+ return;
+ }
+ mname = NULL;
+ for (dp = np->n_data; dp; dp = dp->d_next) {
+ if (dp->d_zone == DB_Z_CACHE || !match(dp, class, T_SOA))
+ continue;
+ if (mname) {
+ ns_notice(ns_log_notify,
+ "multiple SOA's for zone \"%s\"?",
+ zname);
+ return;
+ }
+ mname = (char *) dp->d_data;
+ }
+ if (mname == NULL) {
+ ns_notice(ns_log_notify, "no SOA found for zone \"%s\"",
+ zname);
+ return;
+ }
+ for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
+ if (dp->d_zone == DB_Z_CACHE || !match(dp, class, T_NS))
+ continue;
+ if (strcasecmp((char*)dp->d_data, mname) == 0)
+ continue;
+ sysnotify_ns(dname, (char *)dp->d_data, class, zn, nns, na);
+ }
+}
+
+static void
+sysnotify_ns(const char *dname, const char *aname,
+ int class, int zn, int *nns, int *na)
+{
+ struct databuf *adp;
+ struct namebuf *anp;
+ const char *fname;
+ struct in_addr nss[NSMAX];
+ struct hashbuf *htp;
+ int is_us, nsc;
+
+ htp = hashtab;
+ anp = nlookup(aname, &htp, &fname, 0);
+ nsc = 0;
+ is_us = 0;
+ if (anp != NULL)
+ for (adp = anp->n_data; adp; adp = adp->d_next) {
+ struct in_addr ina;
+
+ if (!match(adp, class, T_A))
+ continue;
+ ina = ina_get(adp->d_data);
+ if (aIsUs(ina)) {
+ is_us = 1;
+ continue;
+ }
+ if (nsc < NSMAX)
+ nss[nsc++] = ina;
+ } /*next A*/
+ if (nsc == 0) {
+ if (!is_us) {
+ struct qinfo *qp;
+
+ qp = sysquery(aname, class, T_A, 0, 0, QUERY);
+ if (qp != NULL)
+ qp->q_notifyzone = zn;
+ }
+ return;
+ }
+ (void) sysquery(dname, class, T_SOA, nss, nsc, NS_NOTIFY_OP);
+ (*nns)++;
+ *na += nsc;
+}
+#endif /*BIND_NOTIFY*/
+
+struct qinfo *
+sysquery(const char *dname, int class, int type,
+ struct in_addr *nss, int nsc, int opcode)
+{
+ struct qinfo *qp, *oqp;
+ HEADER *hp;
+ char tmpdomain[MAXDNAME];
+ struct namebuf *np;
+ struct databuf *nsp[NSMAX];
+ struct hashbuf *htp;
+ struct sockaddr_in *nsa;
+ const char *fname;
+ int n, count;
+
+ nsp[0] = NULL;
+ ns_debug(ns_log_default, 3, "sysquery(%s, %d, %d, %#x, %d)",
+ dname, class, type, nss, nsc);
+ qp = qnew(dname, class, type);
+
+ if (nss && nsc)
+ np = NULL;
+ else {
+ htp = hashtab;
+ if (priming && dname[0] == '\0') {
+ np = NULL;
+ } else if ((np = nlookup(dname, &htp, &fname, 1)) == NULL) {
+ ns_info(ns_log_default,
+ "sysquery: nlookup error on %s?",
+ dname);
+ err1:
+ ns_freeqry(qp);
+ return (NULL);
+ }
+
+ n = findns(&np, class, nsp, &count, 0);
+ switch (n) {
+ case NXDOMAIN:
+ case SERVFAIL:
+ ns_info(ns_log_default,
+ "sysquery: findns error (%s) on %s?",
+ n == NXDOMAIN ? "NXDOMAIN" : "SERVFAIL",
+ dname);
+ err2:
+ free_nsp(nsp);
+ 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 = server_options->fwdtab;
+ qp->q_expire = tt.tv_sec + RETRY_TIMEOUT*2;
+ qp->q_flags |= Q_SYSTEM;
+
+ getname(np, tmpdomain, sizeof tmpdomain);
+ qp->q_domain = savestr(tmpdomain, 1);
+
+ if ((qp->q_msg = (u_char *)memget(PACKETSZ)) == NULL) {
+ ns_notice(ns_log_default, "sysquery: memget failed");
+ goto err2;
+ }
+ qp->q_msgsize = PACKETSZ;
+ n = res_mkquery(opcode, dname, class,
+ type, NULL, 0, NULL,
+ qp->q_msg, PACKETSZ);
+ if (n < 0) {
+ ns_info(ns_log_default,
+ "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 != NULL; oqp = oqp->q_link) {
+ if ((oqp != qp)
+ && (oqp->q_msglen == qp->q_msglen)
+ && memcmp(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*/
+ {
+ ns_debug(ns_log_default, 3,
+ "sysquery: duplicate");
+ 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) {
+ ns_info(ns_log_default,
+ "sysquery: nslookup reports danger (%s)",
+ dname);
+ goto err2;
+ } else if (np && NAME(*np)[0] == '\0') {
+ /*
+ * It's not too serious if we don't have
+ * the root server addresses if we have to
+ * go through a forwarder anyway. Don't
+ * bother to log it, since prime_cache()
+ * won't do anything about it as currently
+ * implemented.
+ *
+ * XXX - should we skip setting
+ * needs_prime_cache as well?
+ *
+ * XXX - what happens when we implement
+ * selective forwarding?
+ */
+ if (!NS_OPTION_P(OPTION_FORWARD_ONLY))
+ ns_warning(ns_log_default,
+ "sysquery: no addrs found for root NS (%s)",
+ dname);
+ if (class == C_IN && !priming)
+ needs_prime_cache = 1;
+ goto err2;
+ }
+ if (np) {
+ free_nsp(nsp);
+ nsp[0] = NULL;
+ np = np_parent(np);
+ n = findns(&np, class, nsp, &count, 0);
+ switch (n) {
+ case NXDOMAIN: /*FALLTHROUGH*/
+ case SERVFAIL:
+ ns_info(ns_log_default,
+ "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);
+
+ ns_debug(ns_log_default, 1,
+ "sysquery: send -> %s dfd=%d nsid=%d id=%d retry=%ld",
+ 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,
+ log_get_stream(packet_channel));
+#endif
+ if (sendto(qp->q_dfd, (char*)qp->q_msg, qp->q_msglen, 0,
+ (struct sockaddr *)nsa,
+ sizeof(struct sockaddr_in)) < 0) {
+ if (!haveComplained(ina_ulong(nsa->sin_addr),
+ (u_long)sendtoStr))
+ ns_info(ns_log_default, "sysquery: sendto(%s): %s",
+ sin_ntoa(*nsa), strerror(errno));
+ nameserIncr(nsa->sin_addr, nssSendtoErr);
+ }
+ nameserIncr(nsa->sin_addr, nssSentSysQ);
+ free_nsp(nsp);
+ return (qp);
+}
+
+/*
+ * Check the list of root servers after receiving a response
+ * to a query for the root servers.
+ */
+static int
+check_root() {
+ struct databuf *dp, *pdp;
+ 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) {
+ ns_notice(ns_log_default, "check_root: Can't find root!");
+ return (0);
+ }
+ for (dp = np->n_data; dp != NULL; dp = dp->d_next)
+ if (dp->d_type == T_NS)
+ count++;
+ ns_debug(ns_log_default, 1, "%d root servers", count);
+ if (count < MINROOTS) {
+ ns_notice(ns_log_default,
+ "check_root: %d root servers after query to root server < min",
+ count);
+ return (0);
+ }
+ pdp = NULL;
+ dp = np->n_data;
+ while (dp != NULL) {
+ if (dp->d_type == T_NS && dp->d_zone == DB_Z_CACHE &&
+ dp->d_ttl < (u_int32_t)tt.tv_sec) {
+ ns_debug(ns_log_default, 1,
+ "deleting old root server '%s'",
+ dp->d_data);
+ dp = rm_datum(dp, np, pdp, NULL);
+ /* SHOULD DELETE FROM HINTS ALSO */
+ continue;
+ }
+ pdp = dp;
+ dp = dp->d_next;
+ }
+ if (check_ns())
+ return (1);
+ else {
+ priming = 1;
+ return (0);
+ }
+}
+
+/*
+ * Check the root to make sure that for each NS record we have a A RR
+ */
+static int
+check_ns() {
+ struct databuf *dp, *tdp;
+ struct namebuf *np, *tnp;
+ struct hashbuf *htp;
+ char *dname;
+ int found_arr;
+ const char *fname;
+ time_t curtime;
+ int servers = 0, rrsets = 0;
+
+ ns_debug(ns_log_default, 2, "check_ns()");
+
+ 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) {
+ int cnames = 0;
+
+ if (dp->d_rcode)
+ continue;
+
+ if (dp->d_type != T_NS)
+ continue;
+
+ servers++;
+
+ /* look for A records */
+ dname = (caddr_t) dp->d_data;
+ htp = hashtab;
+ tnp = nlookup(dname, &htp, &fname, 0);
+ if (tnp == NULL || fname != dname) {
+ ns_debug(ns_log_default, 3,
+ "check_ns: %s: not found %s %#lx",
+ dname, fname, (u_long)tnp);
+ sysquery(dname, dp->d_class, T_A, NULL,
+ 0, QUERY);
+ continue;
+ }
+ /* look for name server addresses */
+ found_arr = 0;
+ delete_stale(tnp);
+ for (tdp = tnp->n_data;
+ tdp != NULL;
+ tdp = tdp->d_next) {
+ if (tdp->d_rcode)
+ continue;
+ if (tdp->d_type == T_CNAME)
+ cnames++;
+ if (tdp->d_type != T_A ||
+ tdp->d_class != dp->d_class)
+ continue;
+ if ((tdp->d_zone == DB_Z_CACHE) &&
+ (tdp->d_ttl < (u_int32_t)curtime)) {
+ ns_debug(ns_log_default, 3,
+ "check_ns: stale entry '%s'",
+ NAME(*tnp));
+ found_arr = 0;
+ break;
+ }
+ found_arr++;
+ }
+ if (found_arr)
+ rrsets++;
+ else if (cnames > 0)
+ ns_info(ns_log_default,
+ "Root NS %s -> CNAME %s",
+ NAME(*np), NAME(*tnp));
+ else
+ sysquery(dname, dp->d_class, T_A, NULL,
+ 0, QUERY);
+ }
+ }
+
+ ns_debug(ns_log_default, 2, "check_ns: %d %d", servers, rrsets);
+ return ((servers <= 2)
+ ? (rrsets == servers)
+ : ((rrsets * 2) >= servers)
+ );
+}
+
+/* int findns(npp, class, nsp, countp, flag)
+ * Find NS's 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}
+ * *countp is bogus, but nsp[] has a single SOA returned in it.
+ * SERVFAIL: we are auth but zone isn't loaded; or, no root servers found
+ * *countp and nsp[] are bogus.
+ * OK: we are not authoritative, and here are the NS records we found.
+ * *countp and nsp[] return NS records of interest.
+ */
+int
+findns(struct namebuf **npp, int class,
+ struct databuf **nsp, int *countp, int flag)
+{
+ struct namebuf *np = *npp;
+ struct databuf *dp;
+ struct databuf **nspp;
+ struct hashbuf *htp;
+
+ nsp[0] = NULL;
+
+ if (priming && (np == NULL || NAME(*np)[0] == '\0'))
+ htp = fcachetab;
+ else
+ htp = hashtab;
+
+ try_again:
+ if (htp == fcachetab && class == C_IN && !priming)
+ /*
+ * XXX - do we want to set needs_prime_cache if
+ * OPTION_FORWARD_ONLY?
+ */
+ 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) {
+ ns_debug(ns_log_default, 5, "findns: np %#x '%s'", 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 != DB_Z_CACHE &&
+ ((zones[dp->d_zone].z_type == Z_PRIMARY) ||
+ (zones[dp->d_zone].z_type == Z_SECONDARY)) &&
+ match(dp, class, T_SOA)) {
+ ns_debug(ns_log_default, 3,
+ "findns: SOA found");
+ if (zones[dp->d_zone].z_flags & Z_AUTH) {
+ *npp = np;
+ nsp[0] = dp;
+ nsp[1] = NULL;
+ DRCNTINC(dp);
+ 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;
+ delete_stale(np);
+ for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
+ if (!match(dp, class, T_NS))
+ continue;
+ if (dp->d_rcode)
+ continue;
+ /*
+ * 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 == DB_Z_CACHE) &&
+ (dp->d_ttl < (u_int32_t)tt.tv_sec) &&
+ !(dp->d_flags & DB_F_HINT)) {
+ ns_debug(ns_log_default, 1,
+ "findns: stale entry '%s'",
+ NAME(*np));
+ /*
+ * We may have already added NS databufs
+ * and are going to throw them away. Fix
+ * reference counts. We don't need to free
+ * them here as we just got them from the
+ * cache.
+ */
+ while (nspp > &nsp[0]) {
+ nspp--;
+ DRCNTDEC(*nspp);
+ }
+ nsp[0] = NULL;
+ goto try_parent;
+ }
+ if (nspp < &nsp[NSMAX-1]) {
+ *nspp++ = dp;
+ DRCNTINC(dp);
+ }
+ }
+
+ *countp = nspp - nsp;
+ if (*countp > 0) {
+ ns_debug(ns_log_default, 3,
+ "findns: %d NS's added for '%s'",
+ *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;
+ }
+ ns_debug(ns_log_default, 1,
+ "findns: No root nameservers for class %s?", p_class(class));
+ if ((unsigned)class < MAXCLASS && norootlogged[class] == 0) {
+ norootlogged[class] = 1;
+ ns_info(ns_log_default, "No root nameservers for class %s",
+ 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(struct namebuf *np, int class, int type,
+ HEADER *hp, char **dnamep, int *lenp, int *countp)
+{
+ struct databuf *dp;
+ char *cp;
+ int buflen, n, count = 0;
+
+ delete_stale(np);
+
+#ifdef ROUND_ROBIN
+ if (type != T_ANY && type != T_PTR) {
+ /* cycle order of RRs, for a load balancing effect... */
+
+ struct databuf **dpp;
+
+ for (dpp = &np->n_data; (dp = *dpp) != NULL;
+ dpp = &dp->d_next) {
+ if (dp->d_next && wanted(dp, class, type)) {
+ 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)
+ ns_debug(ns_log_default, 1, "finddata(): buflen=%d", buflen);
+#endif
+ cp = ((char *)hp) + *countp;
+ for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
+ if (!wanted(dp, class, type)) {
+ if (type == T_CNAME && class == dp->d_class) {
+ /* any data means no CNAME exists */
+ *countp = 0;
+ return 0;
+ }
+ continue;
+ }
+ if (dp->d_cred == DB_C_ADDITIONAL) {
+#ifdef NOADDITIONAL
+ continue;
+#else
+ /* 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 earlier when this datum is
+ * used often.
+ */
+ dp->d_ttl = tt.tv_sec
+ +
+ 0.95 * (int) (dp->d_ttl - tt.tv_sec);
+#endif
+ }
+ /* -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...
+ */
+ ns_info(ns_log_default,
+ "NODATA & data for \"%s\" type %d class %d",
+ *dnamep, type, class);
+ continue;
+ }
+ if (type == T_ANY)
+ continue;
+ hp->rcode = NOERROR_NODATA;
+ if (dp->d_size == 0) { /* !RETURNSOA */
+ *countp = 0;
+ return 1; /* XXX - we have to report success */
+ }
+ }
+ if (dp->d_rcode == NXDOMAIN) {
+ if (count != 0) {
+ /*
+ * This should not happen, yet it might...
+ */
+ ns_info(ns_log_default,
+ "NXDOMAIN & data for \"%s\" type %d class %d",
+ *dnamep, type, class);
+ continue;
+ }
+ hp->rcode = NXDOMAIN;
+ if (dp->d_size == 0) { /* !RETURNSOA */
+ *countp = 0;
+ return 1; /* XXX - we have to report success */
+ }
+ }
+
+ /* Don't put anything but key or sig RR's in response to
+ requests for key or sig */
+ if (((type == T_SIG) || (type == T_KEY)) &&
+ (!((dp->d_type == T_SIG) || (dp->d_type == T_KEY))) )
+ continue;
+
+ if ((n = make_rr(*dnamep, dp, (u_char *)cp, buflen, 1,
+ dnptrs, dnptrs_end)) < 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 != DB_Z_CACHE &&
+ (zones[dp->d_zone].z_flags & Z_AUTH) != 0 &&
+ class != C_ANY)
+ hp->aa = 1; /* XXX */
+#endif
+ if (dp->d_type == T_CNAME) {
+ /* don't alias if querying for key, sig, nxt, or any */
+ if ((type != T_KEY) &&
+ (type != T_SIG) &&
+ (type != T_NXT) &&
+ (type != T_ANY)) { /* or T_NS? */
+ *dnamep = (caddr_t) dp->d_data;
+ }
+ break;
+ }
+ }
+ /*
+ * Cache invalidate the other RR's of same type
+ * if some have timed out
+ */
+ ns_debug(ns_log_default, 3, "finddata: added %d class %d type %d RRs",
+ count, class, type);
+ *countp = count;
+ return (*lenp - buflen);
+}
+
+/*
+ * Do we want this data record based on the class and type?
+ * (We always return found unexpired SIG RR's that cover the wanted rrtype.)
+ */
+int
+wanted(const struct databuf *dp, int class, int type) {
+ const u_char *cp;
+ int coveredType;
+ time_t expiration;
+#ifdef DEBUG
+ char pclass[15], ptype[15];
+#endif
+
+#ifdef DEBUG
+ strcpy(pclass, p_class(class));
+ strcpy(ptype, p_type(type));
+ ns_debug(ns_log_default, 3, "wanted(%#x, %s %s) [%s %s]",
+ dp, pclass, ptype,
+ p_class(dp->d_class), p_type(dp->d_type));
+#endif
+
+ if (dp->d_class != class && class != C_ANY)
+ return (0);
+ /*
+ * Must check SIG for expiration below, other matches
+ * return OK here.
+ */
+ if (type == dp->d_type && (type != T_SIG))
+ return (1);
+ /* 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);
+
+ /* First, look at the type of RR. */
+ switch (dp->d_type) {
+
+ /* Cases to deal with:
+ T_ANY search, return all unexpired SIGs.
+ T_SIG search, return all unexpired SIGs.
+ T_<foo> search, return all unexp SIG <FOO>s.
+ */
+ case T_SIG:
+ cp = dp->d_data;
+ GETSHORT(coveredType, cp);
+ cp += INT16SZ + INT32SZ; /* skip alg, labels, & orig TTL */
+ GETLONG(expiration,cp);
+
+ if (type == T_ANY || type == T_SIG || type == coveredType) {
+ if (expiration > time(0))
+ return (1); /* Unexpired matching SIG */
+ }
+ return (0); /* We don't return this SIG. */
+
+ case T_ANY:
+ return (1);
+ case T_CNAME:
+ if (dp->d_rcode != NOERROR_NODATA)
+ return (1);
+ else
+ break;
+ }
+ /* OK, now look at the type of query. */
+ 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 != DB_Z_CACHE
+ && (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(struct namebuf *np, struct databuf **dpp,
+ u_char *cp, int buflen, int *countp)
+{
+ struct databuf *dp;
+ char dname[MAXDNAME];
+ 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 */
+ if (dp->d_rcode)
+ continue;
+ if ((n = make_rr(dname, dp, cp, buflen, 1,
+ dnptrs, dnptrs_end)) < 0)
+ return (-bytes); /* Truncation */
+ cp += n;
+ buflen -= n;
+ bytes += n;
+ (*countp)++;
+ }
+ return (bytes);
+}
+
+static void
+rrsetadd(struct flush_set *flushset, const char *name, struct databuf *dp) {
+ struct flush_set *fs = flushset;
+ struct db_list *dbl;
+
+ while (fs->fs_name && (
+ strcasecmp(fs->fs_name,name) ||
+ (fs->fs_class != dp->d_class) ||
+ (fs->fs_type != dp->d_type) ||
+ (fs->fs_cred != dp->d_cred))) {
+ fs++;
+ }
+ if (!fs->fs_name) {
+ fs->fs_name = savestr(name, 1);
+ fs->fs_class = dp->d_class;
+ fs->fs_type = dp->d_type;
+ fs->fs_cred = dp->d_cred;
+ fs->fs_list = NULL;
+ fs->fs_last = NULL;
+ }
+ dbl = (struct db_list *)memget(sizeof(struct db_list));
+ if (!dbl)
+ panic("rrsetadd: out of memory", NULL);
+ dbl->db_next = NULL;
+ dbl->db_dp = dp;
+ if (fs->fs_last == NULL)
+ fs->fs_list = dbl;
+ else
+ fs->fs_last->db_next = dbl;
+ fs->fs_last = dbl;
+}
+
+static int
+ttlcheck(const char *name, struct db_list *dbl, int update) {
+ int type = dbl->db_dp->d_type;
+ int class = dbl->db_dp->d_class;
+ struct hashbuf *htp = hashtab;
+ const char *fname;
+ struct namebuf *np;
+ struct db_list *dbp = dbl;
+ struct databuf *dp;
+ u_int32_t ttl = 0; /* Make gcc happy. */
+ int first;
+
+
+ np = nlookup(name, &htp, &fname, 0);
+ if (np == NULL || fname != name || ns_wildcard(NAME(*np)))
+ return (1);
+
+ /* check that all the ttl's we have are the same, if not return 1 */
+ first = 1;
+ for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
+ if (!match(dp, class, type))
+ continue;
+ if (first) {
+ /* we can't update zone data so return early */
+ if (dp->d_zone != DB_Z_CACHE)
+ return (0);
+ ttl = dp->d_ttl;
+ first = 0;
+ } else if (ttl != dp->d_ttl)
+ return (1);
+ }
+
+ /* there are no records of this type in the cache */
+ if (first)
+ return(1);
+
+ /*
+ * the ttls of all records we have in the cache are the same
+ * if the ttls differ in the new set we don't want it.
+ */
+
+ /* check that all the ttl's we have are the same, if not return 0 */
+ first = 1;
+ while (dbp) {
+ if (first) {
+ ttl = dbp->db_dp->d_ttl;
+ first = 0;
+ } else if (ttl != dbp->db_dp->d_ttl) {
+ return(0);
+ }
+ dbp = dbp->db_next;
+ }
+
+ /* update ttl if required */
+ if (update) {
+ for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
+ if (!match(dp, class, type))
+ continue;
+ if (dp->d_ttl > ttl)
+ break;
+ dp->d_ttl = ttl;
+ fixttl(dp);
+ }
+ }
+
+ return(1);
+}
+
+static int
+rrsetcmp(name, dbl)
+ char *name;
+ struct db_list *dbl;
+{
+ int type = dbl->db_dp->d_type;
+ int class = dbl->db_dp->d_class;
+ struct hashbuf *htp = hashtab;
+ const char *fname;
+ struct namebuf *np;
+ struct db_list *dbp = dbl;
+ struct databuf *dp;
+ int exists = 0;
+
+
+ np = nlookup(name, &htp, &fname, 0);
+ if (np == NULL || fname != name || ns_wildcard(NAME(*np))) {
+ ns_debug(ns_log_default, 3, "rrsetcmp: name not in database");
+ return (-1);
+ }
+
+ /* check that all entries in dbl are in the cache */
+ while (dbp) {
+ for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
+ if (match(dp, class, type))
+ exists++;
+ if (!db_cmp(dp, dbp->db_dp)
+#ifdef NOADDITIONAL
+ && ((dp->d_cred == dbp->db_dp->d_cred) ||
+ (dp->d_cred != DB_C_ADDITIONAL))
+#endif
+ )
+ break;
+ }
+ if (!dp) {
+ ns_debug(ns_log_default, 3,
+ "rrsetcmp: %srecord%s in database",
+ exists ? "" : "no ", exists ? " not" : "s");
+ return (exists ? 1 : -1);
+ }
+ dbp = dbp->db_next;
+ }
+
+ /* Check that all cache entries are in the list. */
+ for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
+ if (!match(dp, class, type))
+ continue;
+#ifdef NCACHE
+ if (dp->d_rcode)
+ return (1);
+#endif
+ dbp = dbl;
+ while (dbp) {
+ if (!db_cmp(dp, dbp->db_dp))
+ break;
+ dbp = dbp->db_next;
+ }
+ if (!dbp) {
+ ns_debug(ns_log_default, 3,
+ "rrsetcmp: record not in rrset");
+ return (1);
+ }
+ }
+ ns_debug(ns_log_default, 3, "rrsetcmp: rrsets matched");
+ return (0);
+}
+
+static void
+rrsetupdate(struct flush_set * flushset, int flags, struct sockaddr_in from) {
+ struct flush_set *fs = flushset;
+ struct db_list *dbp, *odbp;
+ int n;
+
+ while (fs->fs_name) {
+ ns_debug(ns_log_default, 2, "rrsetupdate: %s",
+ fs->fs_name[0] ? fs->fs_name : ".");
+ if ((n = rrsetcmp(fs->fs_name, fs->fs_list)) &&
+ ttlcheck(fs->fs_name, fs->fs_list, 0)) {
+ if (n > 0)
+ flushrrset(fs, from);
+
+ dbp = fs->fs_list;
+ while (dbp) {
+ n = db_update(fs->fs_name, dbp->db_dp,
+ dbp->db_dp, NULL, flags,
+ hashtab, from);
+ ns_debug(ns_log_default, 3,
+ "rrsetupdate: %s %d",
+ fs->fs_name[0] ? fs->fs_name : ".",
+ n);
+ if (n != OK)
+ db_freedata(dbp->db_dp);
+ odbp = dbp;
+ dbp = dbp->db_next;
+ memput(odbp, sizeof *odbp);
+ }
+ } else {
+ if (n == 0)
+ (void)ttlcheck(fs->fs_name,fs->fs_list, 1);
+ dbp = fs->fs_list;
+ while (dbp) {
+ db_freedata(dbp->db_dp);
+ odbp = dbp;
+ dbp = dbp->db_next;
+ memput(odbp, sizeof *odbp);
+ }
+ }
+ fs->fs_list = NULL;
+ fs++;
+ }
+}
+
+static void
+flushrrset(struct flush_set * fs, struct sockaddr_in from) {
+ struct databuf *dp;
+ int n;
+
+ ns_debug(ns_log_default, 2, "flushrrset(%s, %s, %s, %d)",
+ fs->fs_name[0]?fs->fs_name:".", p_type(fs->fs_type),
+ p_class(fs->fs_class), fs->fs_cred);
+ dp = savedata(fs->fs_class, fs->fs_type, 0, NULL, 0);
+ dp->d_zone = DB_Z_CACHE;
+ dp->d_cred = fs->fs_cred;
+ dp->d_clev = 0;
+ do {
+ n = db_update(fs->fs_name, dp, NULL, NULL, DB_DELETE, hashtab,
+ from);
+ ns_debug(ns_log_default, 3, "flushrrset: %d", n);
+ } while (n == OK);
+ db_freedata(dp);
+}
+
+static void
+free_flushset(struct flush_set *flushset, int flushset_size) {
+ struct flush_set *fs;
+
+ for (fs = flushset; fs->fs_name != NULL; fs++)
+ freestr(fs->fs_name);
+ memput(flushset, flushset_size);
+}
+
+/*
+ * 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(struct namebuf *np, int class, int type) {
+ struct databuf *dp, *pdp;
+
+ ns_debug(ns_log_default, 3, "delete_all(%#x:\"%s\" %s %s)",
+ np, NAME(*np), p_class(class), p_type(type));
+ pdp = NULL;
+ dp = np->n_data;
+ while (dp != NULL) {
+ if (dp->d_zone == DB_Z_CACHE && (dp->d_flags & DB_F_HINT) == 0
+ && match(dp, class, type)) {
+ dp = rm_datum(dp, np, pdp, NULL);
+ continue;
+ }
+ pdp = dp;
+ dp = dp->d_next;
+ }
+}
+
+/* delete_stale(np)
+ * for all RRs associated with this name, check for staleness (& delete)
+ * arguments:
+ * np = pointer to namebuf to be cleaned.
+ * returns:
+ * void.
+ * side effects:
+ * delete_all() can be called, freeing memory and relinking chains.
+ */
+void
+delete_stale(np)
+ struct namebuf *np;
+{
+ struct databuf *dp;
+ again:
+ for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
+ if (dp->d_zone == DB_Z_CACHE && stale(dp)) {
+ delete_all(np, dp->d_class, dp->d_type);
+ goto again;
+ }
+ }
+}
+
+
+/*
+ * Adjust answer message so that it fits in outlen. Set tc if required.
+ *
+ * If outlen = msglen, can be used to verify qdcount, ancount, nscount
+ * and arcount.
+ *
+ * return new length
+ */
+
+int
+trunc_adjust(u_char *msg, int msglen, int outlen) {
+ register HEADER *hp;
+ u_int qdcount, ancount, nscount, arcount, dlen;
+ u_char *cp = msg, *cp1, *eom_in, *eom_out;
+ int n;
+
+ eom_in = msg + msglen;
+ eom_out = msg + outlen;
+
+ hp = (HEADER *)msg;
+ qdcount = ntohs(hp->qdcount);
+ ancount = ntohs(hp->ancount);
+ nscount = ntohs(hp->nscount);
+ arcount = ntohs(hp->arcount);
+ cp += HFIXEDSZ;
+
+ while ((qdcount || ancount || nscount || arcount) &&
+ cp < eom_in && cp < eom_out) {
+
+ cp1 = cp; /* use temporary in case we break */
+
+ n = dn_skipname(cp1, eom_in);
+ if (n < 0)
+ break;
+ cp1 += n + 2 * INT16SZ; /* type, class */
+
+ if (!qdcount) {
+ cp1 += INT32SZ; /* ttl */
+ if (cp1 + INT16SZ > eom_in)
+ break;
+ GETSHORT(dlen, cp1);
+ cp1 += dlen;
+ }
+
+ if (cp1 > eom_in || cp1 > eom_out)
+ break;
+
+ cp = cp1;
+
+ if (qdcount)
+ qdcount--;
+ else if (ancount)
+ ancount--;
+ else if (nscount)
+ nscount--;
+ else
+ arcount--;
+ }
+
+ if (qdcount || ancount || nscount || arcount) {
+ ns_debug(ns_log_default, 1,
+ "trunc_adjust:%s %d %d %d %d %d, %d %d %d %d %d",
+ hp->tc?" tc":"", msglen,
+ ntohs(hp->qdcount), ntohs(hp->ancount),
+ ntohs(hp->nscount), ntohs(hp->arcount),
+ cp-msg, qdcount, ancount, nscount, arcount);
+ hp->tc = 1;
+ hp->qdcount = htons(ntohs(hp->qdcount) - qdcount);
+ hp->ancount = htons(ntohs(hp->ancount) - ancount);
+ hp->nscount = htons(ntohs(hp->nscount) - nscount);
+ hp->arcount = htons(ntohs(hp->arcount) - arcount);
+ }
+ ENSURE(cp <= eom_out);
+ return (cp - msg);
+}
+
+static void
+add_related_additional(char *name) {
+ int i;
+
+ if (num_related >= MAX_RELATED - 1)
+ return;
+ for (i = 0; i < num_related; i++)
+ if (strcasecmp(name, related[i]) == 0) {
+ freestr(name);
+ return;
+ }
+ related[num_related++] = name;
+}
+
+static void
+free_related_additional() {
+ int i;
+
+ for (i = 0; i < num_related; i++)
+ freestr(related[i]);
+ num_related = 0;
+}
+
+static int
+related_additional(char *name) {
+ int i;
+
+ for (i = 0; i < num_related; i++)
+ if (strcasecmp(name, related[i]) == 0)
+ return (1);
+ return (0);
+}
+
+static void
+freestr_maybe(char **tname) {
+ if (tname == NULL || *tname == NULL)
+ return;
+ freestr(*tname);
+ *tname = NULL;
+}