/*
 * 5799-WZQ (C) COPYRIGHT = NONE
 * LICENSED MATERIALS - PROPERTY OF IBM
 */
/* $Header:ns_resp.c 12.0$ */
/* $ACIS:ns_resp.c 12.0$ */
/* $Source: /ibm/acis/usr/src/etc/named/RCS/ns_resp.c,v $ */

#ifndef lint
static char *rcsid = "$Header:ns_resp.c 12.0$";
#endif

#ifndef lint
static char sccsid[] = "@(#)ns_resp.c	4.3 (Berkeley) 5/30/86";
#endif

/*
 * Copyright (c) 1986 Regents of the University of California
 *	All Rights Reserved
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <syslog.h>
#include <arpa/nameser.h>
#include "ns.h"
#include "db.h"

extern int errno;
extern char *dnptrs[];
extern char *malloc();
struct	databuf *nsp[MAXNS], **nspp;

/*
 * Handle a response from a forwarded query.
 */
ns_resp(msg, msglen)
	char *msg;
	int msglen;
{
	register struct qinfo *qp;
	register HEADER *hp;
	register char *cp, *tp;
	int i, c, n, ancount, nscount, arcount;
	int type;
	int qtype, qclass;
	int cname = 0; /* flag for processing cname response */
	int count, founddata;
	int buflen;
	int newmsglen;
	char name[MAXDNAME], *dname;
	char *fname;
	char *newmsg;

	struct hashbuf *htp;
	struct databuf *dp, *tmp, *pdp;
	struct namebuf *np;

#ifdef DEBUG
	if (debug >= 3)
		fprintf(ddt,"ns_resp(%x, %d)\n", msg, msglen);
#endif
	hp = (HEADER *) msg;
	if( (qp = qfindid(hp->id)) == NULL )
		return;

	if (hp->rcode != NOERROR || hp->opcode != QUERY)
		goto retmsg;
	/*
	 * Skip query section
	 */
	cp = msg + sizeof(HEADER);
	if (hp->qdcount) {
		if ((n = dn_skip(cp)) < 0) {
#ifdef DEBUG
			if (debug)
			    fprintf(ddt,"FORMERR ns_resp() dn_skip failed\n");
#endif
			hp->rcode = FORMERR;
			goto retmsg;
		}
		cp += n;
		qtype = getshort(cp);
		cp += sizeof(u_short);
		qclass = getshort(cp);
		cp += sizeof(u_short);
	}
	/*
	 * Save answers, name server, and additional records for future use.
	 */
	ancount = ntohs(hp->ancount);
	nscount = ntohs(hp->nscount);
	arcount = ntohs(hp->arcount);
	if (ancount == 1 || nscount) {
		/*
	 	 *  Check if it's a CNAME responce
	 	 */
		tp = cp;
		tp += dn_skip(tp); /* name */
		type = getshort(tp);
		tp += sizeof(u_short); /* type */
		if (type == T_CNAME) {
			tp += sizeof(u_short); /* class */
			tp += sizeof(u_long);  /* ttl */
			tp += sizeof(u_short); /* dlen */
			cname++;
#ifdef DEBUG
			if (debug) {
				fprintf(ddt,"CNAME - needs more processing\n");
			}
#endif
			if (!qp->q_cmsglen) {
				qp->q_cmsg = qp->q_msg;
				qp->q_cmsglen = qp->q_msglen;
			}
		}
	}
	/*
	 * Add the info recived in the responce to the Data Base
	 */
	c = ancount + nscount + arcount;
	nspp = nsp;
	for (i = 0; i < c; i++) {
		if (cp >= msg + msglen) {
#ifdef DEBUG
			if (debug)
			    fprintf(ddt, "FORMERR ns_resp() message bad count?\n");
#endif
			hp->rcode = FORMERR;
			goto retmsg;
		}
		if ((n = doupdate(msg, msglen, cp, 0,
		    !ancount && i < nscount)) < 0) {
#ifdef DEBUG
			if (debug)
			    fprintf(ddt,"FORMERR ns_resp() doupdate failed\n");
#endif
			hp->rcode = FORMERR;
			goto retmsg;
		}
		cp += n;
	}
	if (cp > msg + msglen) {
#ifdef DEBUG
		if (debug)
		    fprintf(ddt,"FORMERR ns_resp()  packet size err %d, %d\n",
		       cp-msg, msglen);
#endif
		hp->rcode = FORMERR;
		goto retmsg;
	}
	if ((qtype == C_ANY) && ancount)
		goto retmsg;
	if ((!cname && !qp->q_cmsglen) && (ancount || nscount == 0))
		goto retmsg;

	/*
	 * 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 ((newmsg = malloc(BUFSIZ)) == NULL) {
#if DEBUG
		if (debug)
			fprintf(ddt,"ns_resp: malloc error\n");
#endif
		syslog(LOG_ERR, "ns_resp: Out Of Memory");
		hp->rcode = SERVFAIL;
		goto retmsg;
	}
	buflen = BUFSIZ;
	if ((!cname && qp->q_cmsglen) && ancount) {
#if DEBUG
		if (debug) {
			fprintf(ddt,"Cname second pass\n");
		}
#endif
		newmsglen = qp->q_cmsglen;
		bcopy(qp->q_cmsg, newmsg, newmsglen);
	} else {
		newmsglen = msglen;
		bcopy(msg, newmsg, newmsglen);
	}
	buflen = buflen - newmsglen;
	hp = (HEADER *) newmsg;
	dnptrs[0] = newmsg;
	dnptrs[1] = NULL;
	cp = newmsg + sizeof(HEADER);
	if (cname)
		cp += dn_skip(cp) + QFIXEDSZ;
	if ((n = dn_expand(newmsg, newmsg + newmsglen,
		cp, dname, sizeof(name))) < 0) {
#ifdef DEBUG
		if (debug)
			fprintf(ddt,"dn_expand failed\n" );
#endif
		hp->rcode = SERVFAIL;
		free(newmsg);
		goto retmsg;
	}
	cp = newmsg + sizeof(HEADER);
	if (cname)
		cp += dn_skip(cp);
	else
		cp += n;
	cp += QFIXEDSZ;

again:
	htp = hashtab;		/* lookup relative to root */
#ifdef DEBUG
	if (debug)
		fprintf(ddt,"ns_resp() nlookup(%s)\n",dname);
#endif
	np = nlookup(dname, &htp, &fname, 0);
	if (np == (struct namebuf *)NULL)
		fname = "";
	if (fname != dname)
		goto findns;
#ifdef DEBUG
	if (debug)
		fprintf(ddt,"found '%s'\n", dname);
#endif
	pdp = NULL;
	dp = np->n_data;
	/* look for the data */
	while (dp != NULL) {
		if (!wanted(dp, qclass, qtype)) {
			pdp = dp;
			dp = dp->d_next;
			continue;
		}
		if ((n = make_rr(dname, dp, cp, buflen, 1)) < 0) {
			if (n == -1) {
				hp->tc = 1;
				break;
			}
			/* delete old cache entry */
#ifdef DEBUG
			if (debug >= 5)
				fprintf(ddt,"deleting cache entry\n");
#endif
			rminv(dp);
			tmp = dp->d_next;
			free((char *)dp);
			dp = tmp;
			if (pdp == NULL)
				np->n_data = dp;
			else
				pdp->d_next = dp;
			continue;
		}
		cp += n;
		buflen -= n;
		count++;
		if (dp->d_zone)
			hp->aa = 1;
		if (dp->d_type == T_CNAME) {
			if (type == T_ANY)
				break;
			dname = dp->d_data;
			goto again;
		}
		founddata++;
		pdp = dp;
		dp = dp->d_next;
	}
#ifdef DEBUG
	if (debug >= 5)
		fprintf(ddt,"count = %d, founddata = %d\n", count, founddata);
#endif
	if (count)
		hp->ancount = htons((u_short)count);

findns:

	/*
 	 * Look for name servers to refer to and fill in the authority
 	 * section or record the address for forwarding the query
 	 * (recursion desired).
 	 */
	for (count = 0;; np = np->n_parent) {
#ifdef DEBUG
		if (debug >= 5)
			fprintf(ddt, "fname = '%s'\n", fname);
#endif
		if (*fname == '\0') {
			for (np = hashtab->h_tab[0]; np != NULL;
			     np = np->n_next)
				if (np->n_dname[0] == '\0')
					goto foundns;
#ifdef DEBUG
			if (debug)
				fprintf(ddt, "No root nameserver?\n");
#endif
			syslog(LOG_ERR, "No root Nameserver\n");
			hp->rcode = SERVFAIL;
			break;
		}
foundns:
		nspp = nsp;
		pdp = NULL;
		dp = np->n_data;
		while (dp != NULL) {
			if (!match(dp, qclass, T_NS)) {
				pdp = dp;
				dp = dp->d_next;
				continue;
			}
			if (!founddata) {
				if (nspp < &nsp[MAXNS-1])
					*nspp++ = dp;
				pdp = dp;
				dp = dp->d_next;
				continue;
			}
			if ((n = make_rr(fname, dp, cp, buflen, 1)) < 0) {
				if (n == -1) {
					hp->tc = 1;
					break;
				}
				/* delete old cache entry */
#ifdef DEBUG
				if (debug)
					fprintf(ddt,"deleting cache entry\n");
#endif
				rminv(dp);
				tmp = dp->d_next;		
				free((char *)dp);
				dp = tmp;
				if (pdp == NULL)
					np->n_data = dp;
				else
					pdp->d_next = dp;
				continue;
			}
			cp += n;
			buflen -= n;
			count++;
			pdp = dp;
			dp = dp->d_next;
		}
		if ((*fname == '\0') || (count > 0))
			break;
		if (nspp != nsp)
			break;
		if ((fname = index(fname, '.')) == NULL)
			fname = "";
		else
			fname++;
	}

	if (count && founddata) {
		hp->nscount = htons((u_short)count);
		cp += doaddinfo(hp, cp, buflen);
		buflen = cp - newmsg;
		msg = newmsg;
		msglen = buflen;
		hp = (HEADER *) msg;
		goto retmsg;
	}

	*nspp = NULL;
	if (cname) {
		newmsglen = res_mkquery(QUERY, dname, C_ANY, T_A, (char *)NULL,
					0, NULL, newmsg, BUFSIZ);
		qp->q_msglen = newmsglen;
		hp->id = qp->q_nsid;
	} else {
		hp->ancount = 0;
		hp->nscount = 0;
		hp->arcount = 0;
		hp->qr = 0;
	}
	qp->q_naddr = 0;
	qp->q_curaddr = 0;
	n = nslookup(nsp, qp);

#ifdef DEBUG
		if (debug > 7) {
			int kjd;

			fprintf(ddt,"n = %d\n",n);
			for (kjd = 0; kjd < qp->q_naddr; kjd++ )
				fprintf(ddt,"list %d-> %s (%d)\n",  kjd,
					inet_ntoa(qp->q_addr[kjd].sin_addr),
					ntohs(qp->q_addr[kjd].sin_port));
		}
#endif DEBUG
	qp->q_msg = newmsg;
 	if (qp->q_cname++ == MAXCNAMES) {
 		hp->id = qp->q_id;
 		hp->rd = 1;
 		hp->ra = 1;		
		if (qp->q_stream != QSTREAM_NULL) {
			(void) writemsg(qp->q_stream->s_rfd, newmsg, newmsglen);
			qp->q_stream->s_time = tt.tv_sec;
			qp->q_stream->s_refcnt--;
		} else {
			if (sendto(ds, newmsg, newmsglen, 0, &qp->q_from,
				sizeof(qp->q_from)) < 0) {
#ifdef DEBUG
				if (debug)
					fprintf(ddt,"sendto failed\n");
#endif
			}
 		}
	 	qremove(qp);
 		return;
 	}
#ifdef DEBUG
 	if (debug)
 		fprintf(ddt,"q_cname = %d\n",qp->q_cname);
#endif
	unsched(qp);
	schedretry(qp, (time_t)RETRYTIME);

#ifdef DEBUG
	if (debug >= 3)
		fp_query(qp->q_msg, ddt);
	if (debug)
		fprintf(ddt,"try -> %s (%d)\n",
			inet_ntoa(qp->q_addr[0].sin_addr),
			ntohs(qp->q_addr[0].sin_port));
#endif
	if (sendto(ds, qp->q_msg, qp->q_msglen, 0,
		&qp->q_addr[0], sizeof(qp->q_addr[0])) < 0) {
#ifdef DEBUG
		if (debug)
			fprintf(ddt, "error returning msg errno=%d\n",errno);
#endif
	}
	return;

retmsg:
	/*
	 * Pass answer back to original requestor.
	 */
	hp->id = qp->q_id;
	hp->rd = 1;		/* restore Recursion Desired bit */
	hp->ra = 1;		/* Recursion is Available */
#ifdef DEBUG
	if (debug)
		fprintf(ddt,"respond -> %s (%d)\n",
			inet_ntoa(qp->q_from.sin_addr),
			ntohs(qp->q_from.sin_port));
	if (debug >= 10)
		fp_query(msg, ddt);
#endif
	if (qp->q_stream == QSTREAM_NULL) {
		if (sendto(ds, msg, msglen, 0,
		    &qp->q_from, sizeof(qp->q_from)) < 0) {
#ifdef DEBUG
			if (debug)
				fprintf(ddt,"sendto failed\n");
#endif
		}

	} else {
		(void) writemsg(qp->q_stream->s_rfd, msg, msglen);
		qp->q_stream->s_time = tt.tv_sec;
		qp->q_stream->s_refcnt--;
	}
	if (msg == newmsg)
		(void) free(newmsg);
	/*
	 * Remove from table
	 */
	qremove(qp);
}

/*
 * Decode the resource record 'rrp' and update the database.
 * If savens is true, record pointer for forwarding queries a second time.
 */
doupdate(msg, msglen, rrp, zone, savens)
	char *msg ;
	char *rrp;
	int  msglen, zone, savens;
{
	register char *cp;
	register int n;
	int class, type, dlen, n1;
	u_long ttl;
	struct databuf *dp;
	char dname[MAXDNAME];
	char data[BUFSIZ], *cp1;

#ifdef DEBUG
	if (debug >= 3)
		fprintf(ddt,"doupdate(%d, %d)\n", zone, savens);
#endif

	cp = rrp;
	if ((n = dn_expand(msg, msg + msglen, cp, dname, sizeof(dname))) < 0)
		return (-1);
	cp += n;
	type = getshort(cp);
	cp += sizeof(u_short);
	class = getshort(cp);
	cp += sizeof(u_short);
	ttl = getlong(cp);
	cp += sizeof(u_long);
	dlen = getshort(cp);
	cp += sizeof(u_short);
	if (zone == 0) {
		if (ttl == 0)
			ttl = 5 * 60;
		ttl += (u_long) tt.tv_sec;
	}
	/*
	 * Convert the resource record data into the internal
	 * database format.
	 */
	switch (type) {
	case T_A:
	case T_HINFO:
	case T_UINFO:
	case T_UID:
	case T_GID:
		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:
		if ((n = dn_expand(msg, msg + msglen, cp, data,
		   sizeof(data))) < 0)
			return (-1);
		cp += n;
		cp1 = data;
		n = strlen(data) + 1;
		break;

	case T_MINFO:
	case T_SOA:
		if ((n = dn_expand(msg, msg + msglen, cp, data,
		    sizeof(data))) < 0)
			return (-1);
		cp += n;
		cp1 = data + (n = strlen(data) + 1);
		n1 = sizeof(data) - n;
		if (type == T_SOA)
			n1 -= 5 * sizeof(u_long);
		if ((n = dn_expand(msg, msg + msglen, cp, cp1, n1)) < 0)
			return (-1);
		cp += n;
		cp1 += strlen(cp1) + 1;
		if (type == T_SOA) {
			bcopy(cp, cp1, n = 5 * sizeof(u_long));
			cp += n;
			cp1 += n;
		}
		n = cp1 - data;
		cp1 = data;
		break;

  	case T_MX:
 		/* grab preference */
 		bcopy(cp,data,sizeof(u_short));
 		cp1 = data + sizeof(u_short);
 		cp += sizeof(u_short);

 		/* get name */
  		if ((n = dn_expand(msg, msg + msglen, cp, cp1,
		    sizeof(data)-sizeof(u_short))) < 0)
  			return(-1);
  		cp += n;

 		/* compute end of data */
 		cp1 += strlen(cp1) + 1;
 		/* compute size of data */
  		n = cp1 - data;
  		cp1 = data;
  		break;

	default:
#ifdef DEBUG
		if (debug >= 3)
			fprintf(ddt,"unknown type %d\n", type);
#endif
		return ((cp - rrp) + dlen);
	}
	dp = savedata(class, type, ttl, cp1, n);
	dp->d_zone = zone;
	if ((n = db_update(dname, dp, dp, DB_NODATA)) < 0) {
#ifdef DEBUG
		if (debug && (n != DATAEXISTS))
			fprintf(ddt,"update failed (%d)\n", n);
		else if (debug >= 3)
			fprintf(ddt,"update failed (DATAEXISTS)\n");
#endif
		(void) free((char *)dp);
	} else if (savens && type == T_NS && nspp < &nsp[MAXNS-1])
		*nspp++ = dp;
	return (cp - rrp);
}
