diff options
author | peter <peter@FreeBSD.org> | 1998-05-03 04:11:49 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 1998-05-03 04:11:49 +0000 |
commit | 0666320b4eda500556d2c671c9527c9000057492 (patch) | |
tree | 759849259eae9f7cb0d3ddbd7a131081c6688068 /contrib/bind/bin/named/ns_resp.c | |
parent | 58ca52f41726d17758909ddafba7b6b6766c789c (diff) | |
download | FreeBSD-src-0666320b4eda500556d2c671c9527c9000057492.zip FreeBSD-src-0666320b4eda500556d2c671c9527c9000057492.tar.gz |
Import (trimmed) ISC bind-8.1.2-t3b. This will be updated to 8.1.2 on
final release.
Obtained from: ftp.isc.org
Diffstat (limited to 'contrib/bind/bin/named/ns_resp.c')
-rw-r--r-- | contrib/bind/bin/named/ns_resp.c | 3298 |
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 0000000..012f89e --- /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; +} |