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

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

#ifndef lint
static char sccsid[] = "@(#)ns_init.c	4.3 (Berkeley) 6/4/86";
#endif

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

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

struct	zoneinfo zones[MAXZONES];	/* zone information */
int	nzones;				/* number of zones in use */

/*
 * Read boot file for configuration info.
 */
ns_init(bootfile)
	char *bootfile;
{
	register struct zoneinfo *zp;
	char buf[BUFSIZ];
	char *tm;
	FILE *fp;
	int type, first = 1;
	time_t next_refresh;
	struct itimerval ival;

#ifdef DEBUG
	if (debug >= 3)
		fprintf(ddt,"ns_init(%s)\n", bootfile);
#endif

	if ((fp = fopen(bootfile, "r")) == NULL) {
		syslog(LOG_ERR, "%s: %m", bootfile);
		exit(1);
	}

	/* allocate root hash table */
	hashtab = savehash((struct hashbuf *)NULL);

	/* init zone data */
	nzones = 1;		/* zone zero is cache data */
	while (getword(buf, sizeof(buf), fp)) {
		/* read 'primary', 'secondary', 'cache' or 'domain' */
	top:	
		if (cistrcmp(buf, "cache") == 0) {
			type = Z_CACHE;
			zp = zones;
		} else {
			if (cistrcmp(buf, "primary") == 0)
				type = Z_PRIMARY;
			else if (cistrcmp(buf, "secondary") == 0)
				type = Z_SECONDARY;
			else if (cistrcmp(buf, "domain") == 0)
				type = Z_DOMAIN;
			else {
				syslog(LOG_ERR, "%s: unknown type", buf);
				exit(1);
			}
			if (nzones >= MAXZONES) {
				syslog(LOG_ERR, "too many zones");
				exit(1);
			}
			zp = &zones[nzones++];
		}
		zp->z_type = type;
		zp->z_addrcnt = 0;
		/*
		 * read zone origin
		 */
		(void) getword(buf, sizeof(buf), fp);
		if (buf[0] == '.')
			buf[0] = '\0';
		zp->z_origin = savestr(buf);
		/*
		 * read source file or host address
		 */
		if (type != Z_DOMAIN) {
			(void) getword(buf, sizeof(buf), fp);
			zp->z_source = savestr(buf);
#ifdef DEBUG
			if (debug) {
				fprintf(ddt,"zone found (%d): ", zp->z_type);
				if (zp->z_origin[0] == '\0')
					fprintf(ddt,"'.'");
				else
					fprintf(ddt,"'%s'", zp->z_origin);
				fprintf(ddt,", source = %s", zp->z_source);
			}
#endif
		}
#ifdef DEBUG
		else if (debug)
			fprintf(ddt,"zone found (%d): domain name = '%s'",
				zp->z_type, zp->z_origin);
#endif
		switch (zp->z_type) {
		case Z_PRIMARY:
		case Z_CACHE:
#ifdef DEBUG
			if (debug)
				fprintf(ddt,"\n");
#endif
			(void) db_load(zp->z_source, zp->z_origin, zp - zones);
			break;

		case Z_SECONDARY:
			zp->z_addr[zp->z_addrcnt].s_addr =
				inet_addr(zp->z_source);
			if (zp->z_addr[zp->z_addrcnt].s_addr != (unsigned)-1)
					zp->z_addrcnt++;
			while (getword(buf, sizeof(buf), fp)) {
				tm = savestr(buf);
				zp->z_addr[zp->z_addrcnt].s_addr =
					inet_addr(tm);
				if (zp->z_addr[zp->z_addrcnt].s_addr ==
						(unsigned)-1) {
#ifdef DEBUG
					if (debug)
						fprintf(ddt," (addrcnt) = %d\n",
							zp->z_addrcnt);
#endif
					zoneinit(zp);
					if (first) {
					    next_refresh = zp->z_time;
					    first = 0;
					} else
					    if (next_refresh > zp->z_time)
						next_refresh = zp->z_time;
					goto top;
				}
#ifdef DEBUG
				if (debug)
					fprintf(ddt,", %s",buf);
#endif
				if (++zp->z_addrcnt >= MAXNS) {
					zp->z_addrcnt = MAXNS;
#ifdef DEBUG
					if (debug)
					    fprintf(ddt,
						"\nns.h MAXNS reached\n");
#endif
					break;
				}
			}
#ifdef DEBUG
			if (debug)
				fprintf(ddt," addrcnt = %d\n", zp->z_addrcnt);
#endif
			zoneinit(zp);
			if (first) {
				next_refresh = zp->z_time;
				first = 0;
			} else
				if (next_refresh > zp->z_time)
					next_refresh = zp->z_time;
			break;

		case Z_DOMAIN:
			while (getword(buf, sizeof(buf), fp)) {
				tm = savestr(buf);
				zp->z_addr[zp->z_addrcnt].s_addr =
					inet_addr(tm);
				if (zp->z_addr[zp->z_addrcnt].s_addr ==
						(unsigned)-1) {
#ifdef DEBUG
					if (debug)
						fprintf(ddt," addrcnt = %d\n",
							zp->z_addrcnt);
#endif
					goto top;
				}
#ifdef DEBUG
				if (debug)
					fprintf(ddt,", %s",buf);
#endif
				if (++zp->z_addrcnt >= MAXNS) {
				    zp->z_addrcnt = MAXNS;
#ifdef DEBUG
				    if (debug)
				       fprintf(ddt,"\nns.h MAXNS reached\n");
#endif
				    break;
				}
			}
#ifdef DEBUG
			if (debug)
				fprintf(ddt," addrcnt = %d\n",
					zp->z_addrcnt);
#endif
			break;

		}
	}
	(void) fclose(fp);
	if (!first) {
		if (gettimeofday(&tt, (struct timezone *)0) < 0) {
			syslog(LOG_ERR, "gettimeofday failed: %m");
			ival.it_value.tv_sec = 5 * 60;
		} else {
			bzero((char *)&ival, sizeof(ival));
			ival.it_value.tv_sec = next_refresh - tt.tv_sec;
			if (ival.it_value.tv_sec < 0)
				ival.it_value.tv_sec = 60;
		}
	} else
		ival.it_value.tv_sec = 0;
	(void) setitimer(ITIMER_REAL, &ival, (struct itimerval *)NULL);
#if DEBUG
	if (debug)
		fprintf(ddt,"exit ns_init() Next interupt in %d sec\n",
			ival.it_value.tv_sec);	
#endif
}

zoneinit(zp)
	struct zoneinfo *zp;
{
	struct sockaddr_in sin;
	HEADER *hp;
	char buf[PACKETSZ];
	u_short len;
	char *cp;
	char *tmp;
	int s, n, l;
	int cnt, soacnt, error = 0;
	int zone = zp - zones;
	u_long serial;
	struct itimerval ival;
	struct itimerval zeroival;
	extern struct sockaddr_in nsaddr;
	extern int errno;
	extern int read_alarm();
	struct sigvec sv, osv;
	extern int read_interrupted;
	
#ifdef DEBUG
	if (debug)
		fprintf(ddt,"zoneinit()\n");
#endif

	bzero((char *)&zeroival, sizeof(zeroival));
	ival = zeroival;
	ival.it_value.tv_sec = 2 * 60;
	sv.sv_handler = read_alarm;
	sv.sv_onstack = 0;
	sv.sv_mask = ~0;
	(void) sigvec(SIGALRM, &sv, &osv);

	for( cnt = 0; cnt < zp->z_addrcnt; cnt++) {
		error = 0;
		bzero((char *)&sin, sizeof(sin));
		sin.sin_family = AF_INET;
		sin.sin_port = nsaddr.sin_port;
		sin.sin_addr = zp->z_addr[cnt];
		if ((n = res_mkquery(QUERY, zp->z_origin, C_IN, T_AXFR,
	    	(char *)NULL, 0, NULL, buf, sizeof(buf))) < 0) {
			syslog(LOG_ERR, "zoneinit: res_mkquery failed");
			(void) sigvec(SIGALRM, &osv, (struct sigvec *)0);
			return;
		}
		if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
			syslog(LOG_ERR, "zoneref: socket: %m");
			exit(1);
		}	
#ifdef DEBUG
		if (debug >= 2) {
			fprintf(ddt,"connecting to server #%d %s, %d (%d)\n",
				cnt+1, inet_ntoa(sin.sin_addr),
				ntohs(sin.sin_port), n);
		}
#endif
		if (connect(s, &sin, sizeof(sin)) < 0) {
			(void) close(s);
			error++;
#ifdef DEBUG
			if (debug >= 2)
				fprintf(ddt,"connect failed\n");
#endif
			continue;
		}	
		/*
	 	 * Send length & message for zone transfer
	 	 */
		if (writemsg(s, buf, n) < 0) {
			(void) close(s);
			error++;
#ifdef DEBUG
			if (debug >= 2)
				fprintf(ddt,"writemsg failed\n");
#endif
			continue;	
		}
	
		hp = (HEADER *) buf;
		soacnt = 0;

		for (;;) {
			/*
			 * Receive length & response
			 */
			cp = buf;
			l = sizeof(u_short);
			read_interrupted = 0;
			while (l > 0) {
				(void) setitimer(ITIMER_REAL, &ival,
				    (struct itimerval *)NULL);
				if ((n = recv(s, cp, l, 0)) > 0) {
					cp += n;
					l -= n;
				} else {
					if (errno == EINTR && !read_interrupted)
						continue;
					error++;
					break;
				}
			}
			(void) setitimer(ITIMER_REAL, &zeroival,
			    (struct itimerval *)NULL);
			if (error)
				break;
			if ((len = htons(*(u_short *)buf)) == 0)
				break;
			l = len;
			cp = buf;
			while (l > 0) {
				(void) setitimer(ITIMER_REAL, &ival,
				    (struct itimerval *)NULL);
				if ((n = recv(s, cp, l, 0)) > 0) {
					cp += n;
					l -= n;
				} else {
					if (errno == EINTR && !read_interrupted)
						continue;
					error++;
					break;
				}
			}
			(void) setitimer(ITIMER_REAL, &zeroival,
			    (struct itimerval *)NULL);
			if (error)
				break;
#ifdef DEBUG
			if (debug >= 3) {
				fprintf(ddt,"len = %d\n", len);
				fp_query(buf, ddt);
			}
#endif
			cp = buf + sizeof(HEADER);
			if (hp->qdcount)
				cp += dn_skip(cp) + QFIXEDSZ;
			tmp = cp + dn_skip(cp);
			n = doupdate(buf, sizeof(buf), cp, zone, 0);
			if ((cp - buf) + n != len) {
#ifdef DEBUG
			   if (debug)
	                     fprintf(ddt,"zoneinit: doupdate failed (%d, %d)\n",
					cp - buf, n);
#endif
				error++;
				break;
			}
			if ((getshort(tmp)) == T_SOA) {
				if (soacnt == 0) {
					soacnt++;
					tmp += 3 * sizeof(u_short)
						+ sizeof(u_long);
					tmp += dn_skip(tmp);
					tmp += dn_skip(tmp);
					serial = getlong(tmp);
					continue;
				}
				soa_zinfo(zp, tmp);
				if (serial != zp->z_serial)
					 soacnt = 0;
				else {
					break;
				}
			}
		}
		(void) close(s);
		if ( error == 0) {
			(void) sigvec(SIGALRM, &osv, (struct sigvec *)0);
			return;
		}
#ifdef DEBUG
		if (debug >= 2)
			fprintf(ddt,"error reciving zone transfer\n");
#endif
	}
	/*
	 *     Freedom at last!!
	 *
	 *  The land where all repressed slaves dream of.
	 *
	 *  Can't find a master to talk to.
	 *  syslog it and hope we can find a master during maintenance
	 */
	if (error)
	    syslog(LOG_ERR, "zoneinit: Can't find Master for secondary zone %s",
		zp->z_origin);
	/*
	 * Set zone to be refreshed in 5 min.
	 *	maybe by then we can refresh it.
	 */
	zp->z_refresh = 300; /* 300 seconds = 5 Min. */
	zp->z_retry = 300;
	if (gettimeofday(&tt, (struct timezone *)0) < 0)
		syslog(LOG_ERR, "gettimeofday failed: %m");
	zp->z_time = tt.tv_sec + 300;
	(void) sigvec(SIGALRM, &osv, (struct sigvec *)0);
	return;
}
#ifdef notdef
/*
 * Look for an authoritative zone that matches the RHS of dname
 * and return is zone # or zero if not found.
 */
findzone(dname, class)
	char *dname;
	int class;
{
	register struct zoneinfo *zp;
	register char *d1, *d2;
	register int c;
	char *end;

	end = dname + strlen(dname);
	for (zp = &zones[1]; zp < &zones[nzones]; zp++) {
		d1 = end;
		d2 = zp->z_origin + strlen(zp->z_origin);
		while (d2 > zp->z_origin) {
			if ((c*--
		}
		return (zp - zones);
	}
	return (0);
}
#endif

soa_zinfo(zp, cp)
	struct zoneinfo *zp;
	char *cp;
{
	cp += 3 * sizeof(u_short);
	cp += sizeof(u_long);
	cp += dn_skip(cp);
	cp += dn_skip(cp);
	zp->z_serial = getlong(cp);
	cp += sizeof(u_long);
	zp->z_refresh = getlong(cp);
	if (gettimeofday(&tt, (struct timezone *)0) < 0)
		syslog(LOG_ERR, "gettimeofday failed: %m");
	zp->z_time = tt.tv_sec + zp->z_refresh;
	cp += sizeof(u_long);
	zp->z_retry = getlong(cp);
	cp += sizeof(u_long);
	zp->z_expire = getlong(cp);
	cp += sizeof(u_long);
	zp->z_minimum = getlong(cp);
	cp += sizeof(u_long);
}
