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

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

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

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

/*
 * Internet Name server (see rfc883 & others).
 */

#include <sys/param.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <syslog.h>
#include <errno.h>
#include <signal.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>
#include "ns.h"
#include "db.h"

#ifdef BOOTFILE 			/* default boot file */
char	*bootfile = BOOTFILE;
#else
char	*bootfile = "/etc/named.boot";
#endif

#ifdef DEBUGFILE 			/* default debug output file */
char	*debugfile = DEBUGFILE;
#else
char	*debugfile = "/usr/tmp/named.run";
#endif

#ifdef PIDFILE 				/* file to store current named PID */
char	*PidFile = PIDFILE;
#else
char	*PidFile = "/etc/named.pid";	
#endif

#ifndef FD_SET
#define	NFDBITS		32
#define	FD_SETSIZE	32
#define	FD_SET(n, p)	((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS)))
#define	FD_CLR(n, p)	((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS)))
#define	FD_ISSET(n, p)	((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS)))
#define FD_ZERO(p)	bzero((char *)(p), sizeof(*(p)))
#endif

FILE	*fp;  				/* file descriptor for pid file */

#ifdef DEBUG
FILE	*ddt;
#endif

int	debug = 0;			/* debugging flag */
int	ds;				/* datagram socket */
int	read_interrupted = 0;		/* flag for read timer */
int	needreload = 0;			/* received SIGHUP, need to reload db */
int	needmaint = 0;			/* need to call ns_maint()*/
int	rbufsize = 8 * 1024;		/* UDP recive buffer size */

struct	qstream *streamq = QSTREAM_NULL; /* list of open streams */
struct	sockaddr_in nsaddr;
struct	timeval tt;
short	ns_port;

char		**Argv = NULL;		/* pointer to argument vector */
char		*LastArg = NULL;	/* end of argv */

extern char *malloc(), *realloc(), *calloc();

extern int errno;



main(argc, argv, envp)
	int argc;
	char *argv[], *envp[];
{
	register int n, udpcnt;
	register char *arg;
	register struct qstream *sp;
	int vs, len;
	int nfds;
	int on = 1;
	int rfd, size;
	u_long lasttime, maxctime;
	char buf[BUFSIZ];

	fd_set mask, tmpmask;

	struct timeval t, *tp;
	struct sockaddr_in from;
	struct qstream *candidate = QSTREAM_NULL;
	extern int onintr(), maint_alarm(), reapchild(), doadump(), onhup();
	extern int sigsetdebug(), signodebug(), sigprof();
	extern struct qstream *sqadd();
	extern char Version[];
	struct	sigvec sv;

	ns_port = htons(NAMESERVER_PORT);

	/*
	**  Save start and extent of argv for setproctitle.
	*/

	Argv = argv;
	if (envp == 0 || *envp == 0)
		envp = argv;
	while (*envp)
		envp++;
	LastArg = envp[-1] + strlen(envp[-1]);

	while (--argc > 0) {
		arg = *++argv;
		if (*arg == '-') {
			while (*++arg)
				switch (*arg) {
				case 'b':
					if (--argc <= 0)
						usage();
					bootfile = *++argv;
					break;

  				case 'd':
 					++argv;

 					if (*argv != 0) {
 					    if (**argv == '-') {
 						argv--;
 						break;
 					    }
 					    debug = atoi(*argv);
 					    --argc;
 					}
					if (debug <= 0)
						debug = 1;
					setdebug(1);
					break;

				case 'p':
					if (--argc <= 0)
						usage();
					ns_port = htons((u_short)atoi(*++argv));
					break;

				default:
					usage();
				}
		} else
			bootfile = *argv;
	}

	if (!debug) {
		if (fork())
			exit(0);
		for (n = getdtablesize() - 1; n >= 0; n--)
			(void) close(n);
		(void) open("/dev/null", O_RDONLY);
		(void) dup2(0, 1);
		(void) dup2(0, 2);
		n = open("/dev/tty", O_RDWR);
		if (n > 0) {
			(void) ioctl(n, TIOCNOTTY, (char *)NULL);
			(void) close(n);
		}
	}
#ifdef DEBUG
	else {
		fprintf(ddt,"Debug turned ON, Level %d\n",debug);
		fprintf(ddt,"Version = %s\t",Version);
		fprintf(ddt,"bootfile = %s\n",bootfile);
	}		
#endif

#ifdef BSD4_3
	openlog("named", LOG_PID|LOG_CONS|LOG_NDELAY, LOG_DAEMON);
#else
	openlog("named", LOG_PID);
#endif

	nsaddr.sin_family = AF_INET;
	nsaddr.sin_addr.s_addr = INADDR_ANY;
	nsaddr.sin_port = ns_port;
	/*
	** Initialize and load database.
	*/
	ns_init(bootfile);

	/* Block signals during maintenance */
	sv.sv_handler = maint_alarm;
	sv.sv_onstack = 0;
	sv.sv_mask = ~0;

	(void) sigvec(SIGALRM, &sv, (struct sigvec *)0);

	(void) signal(SIGHUP, onhup);
	(void) signal(SIGCHLD, reapchild);
	(void) signal(SIGPIPE, SIG_IGN);
	(void) signal(SIGSYS, sigprof);

#if	BSD >= 43
	/* flames to mckusick@monet.Berkeley.EDU - I lost the battle -KJD */
	(void) signal(SIGINT, doadump);
	(void) signal(SIGUSR1, sigsetdebug);
	(void) signal(SIGUSR2, signodebug);
#else	BSD
	(void) signal(SIGQUIT, doadump);
	(void) signal(SIGEMT, sigsetdebug);
	(void) signal(SIGFPE, signodebug);
#endif BSD

#ifdef DEBUG
	if (debug) {
		fprintf(ddt,"database initialized\n");
	}
#endif
	/*
	** Open stream port.
	*/
	if ((vs = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		syslog(LOG_ERR, "socket(SOCK_STREAM): %m");
		exit(1);
	}	
	(void)setsockopt(vs, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
	if (bind(vs, &nsaddr, sizeof(nsaddr))) {
		syslog(LOG_ERR, "bind(vs): %m");
		exit(1);
	}
	(void) listen(vs, 5);
	/*
	** Open datagram port.
	*/
	if ((ds = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		syslog(LOG_ERR, "socket(SOCK_DGRAM): %m");
		exit(1);
	}	
	(void)setsockopt(ds, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
#ifdef BSD4_3
	(void)setsockopt(ds, SOL_SOCKET, SO_RCVBUF, (char *)&rbufsize,
	    sizeof(rbufsize));
#endif
	(void) fcntl(ds, F_SETFL, FNDELAY);
	if (bind(ds, &nsaddr, sizeof(nsaddr))) {
		syslog(LOG_ERR, "bind(ds): %m");
		exit(1);
	}
	/* tuck my process id away */
	fp = fopen(PidFile, "w");
	if (fp != NULL) {
		fprintf(fp, "%d\n", getpid());
		(void) fclose(fp);
	}

	t.tv_usec = 0;

#ifdef DEBUG
	if (debug)
		fprintf(ddt,"Ready to answer queries.\n");
#endif
	nfds = getdtablesize();       /* get the number of file descriptors */
	if (nfds > FD_SETSIZE) {
		syslog(LOG_ERR, "Return from getdtablesize() > FD_SETSIZE");
#ifdef DEBUG
		if (debug)
		      fprintf(ddt,"Return from getdtablesize() > FD_SETSIZE\n");
#endif
	}
	FD_ZERO(&mask);
	FD_SET(vs, &mask);
	FD_SET(ds, &mask);
	for (;;) {
		/*
		** Wait until a query arrives; can be interrupted by maintenance
		*/
		if (retryqp != NULL) {
			if (gettimeofday(&tt, (struct timezone *)0) < 0)
				syslog(LOG_ERR, "gettimeofday failed: %m");
			t.tv_sec = (long) retryqp->q_time - tt.tv_sec;
			if (t.tv_sec <= 0) {
				retry(retryqp);
				continue;
			}
			tp = &t;
		} else
			tp = NULL;
		if(needreload) {
			needreload = 0;
			db_reload();
		}
		if(needmaint) {
			needmaint = 0;
			ns_maint();
		}
		tmpmask = mask;
		n = select(nfds, &tmpmask, (fd_set *)NULL, (fd_set *)NULL, tp);
		if (n < 0) {
			if (errno == EINTR)
				continue;
			syslog(LOG_ERR, "select: %m");
			break;
		}
		if (n == 0) {
			retry(retryqp);
			continue;
		}
		if (gettimeofday(&tt, (struct timezone *)0) < 0) {
			syslog(LOG_ERR, "gettimeofday failed: %m");
			break;
		}
		/*
		** Process datagram
		*/
		if (FD_ISSET(ds, &tmpmask))
		    for(udpcnt = 0; udpcnt < 25; udpcnt++) {
			len = sizeof(from);
			if ((n = recvfrom(ds, buf, sizeof(buf), 0,
				&from, &len)) < 0)
			{
				if ((n == -1) && (errno == EWOULDBLOCK))
					break;
				syslog(LOG_WARNING, "recvfrom: %m");
				break;
			}
#ifdef DEBUG
			if (debug)
				fprintf(ddt,"datagram from %s, %d (%d)\n",
					inet_ntoa(from.sin_addr),
					ntohs(from.sin_port), n);
			if (debug >= 10)
				fp_query(buf, ddt);
#endif
			/*
			 * Consult database to get the answer.
			 */
			if (gettimeofday(&tt, (struct timezone *)0) < 0) {
				syslog(LOG_ERR, "gettimeofday failed: %m");
				break;
			}
			ns_req(buf, n, PACKETSZ, QSTREAM_NULL, &from);
		    }
		/*
		** Process stream connection
		*/
		if (FD_ISSET(vs, &tmpmask)) {
			len = sizeof(from);
			rfd = accept(vs, &from, &len);
			if (gettimeofday(&tt, (struct timezone *)0) < 0) {
				syslog(LOG_ERR, "gettimeofday failed: %m");
				break;
			}
			if (rfd < 0) {
			    if (errno == EMFILE) {
				if (streamq != NULL) {
				    maxctime = 0;
				    candidate = QSTREAM_NULL;
				    for (sp = streamq; sp != QSTREAM_NULL;
				       sp = sp->s_next)
				    {
					if (sp->s_refcnt != 0)
					    continue;
					lasttime = tt.tv_sec - sp->s_time;
					if (lasttime >= 900)
					    sqrm(sp, &tmpmask);
					else if (lasttime > maxctime) {
					    candidate = sp;
					    maxctime = lasttime;
					}
				    }
				    rfd = accept(vs, &from, &len);
				    if ((rfd < 0) && (errno == EMFILE))
					if (candidate != QSTREAM_NULL) {
					    sqrm(candidate, &tmpmask);
				    	    rfd = accept(vs, &from, &len);
				   	    if (rfd < 0)
					        syslog(LOG_WARNING,
						    "accept: %m");
						continue;
					}
				} else {
					syslog(LOG_WARNING, "accept: %m");
					continue;
				}
			    } else {
				syslog(LOG_WARNING, "accept: %m");
				continue;
			    }
			}
			(void) fcntl(rfd, F_SETFL, FNDELAY);
			(void) setsockopt(rfd, SOL_SOCKET, SO_KEEPALIVE,
				(char *)&on, sizeof(on));
			if ((sp = sqadd()) == QSTREAM_NULL)
				(void) close(rfd);
			sp->s_rfd = rfd;	/* stream file descriptor */
			sp->s_size = -1;	/* amount of data to recive */
			if (gettimeofday(&tt, (struct timezone *)0) < 0) {
				syslog(LOG_ERR, "gettimeofday failed: %m");
				break;
			}
			sp->s_time = tt.tv_sec;	/* last transaction time */
			sp->s_from = from;	/* address to respond to */
			sp->s_bufsize = 0;
			sp->s_bufp = (char *)&sp->s_tempsize;
			sp->s_refcnt = 0;
			FD_SET(rfd, &mask);
			FD_SET(rfd, &tmpmask);
#ifdef DEBUG
			if (debug)
			{
				fprintf(ddt,"stream from %s, %d (%d)\n",
					inet_ntoa(sp->s_from.sin_addr),
					ntohs(sp->s_from.sin_port), n);
			}
#endif
		}
#ifdef DEBUG
		if (debug > 2)
			fprintf(ddt,"streamq  = x%x\n",streamq);
#endif
		if (streamq != NULL) {
			for (sp = streamq; sp != QSTREAM_NULL; sp = sp->s_next)
			    if (FD_ISSET(sp->s_rfd, &tmpmask)) {
#ifdef DEBUG
				if (debug > 5) {
				    fprintf(ddt,
					"sp x%x rfd %d size %d time %d ",
					sp, sp->s_rfd, sp->s_size,
					sp->s_time );
				    fprintf(ddt," next x%x \n", sp->s_next );
				    fprintf(ddt,"\tbufsize %d",sp->s_bufsize);
				    fprintf(ddt," buf x%x%d ",sp->s_buf);
				    fprintf(ddt," bufp x%x%d\n",sp->s_bufp);
				}
#endif DEBUG
			    if (sp->s_size < 0) {
			        size = sizeof(u_short) -
				(sp->s_bufp - (char *)&sp->s_tempsize);
			        while (size > 0 &&
			           (n = read(sp->s_rfd, sp->s_bufp, size)) > 0){
					    sp->s_bufp += n;
					    size -= n;
			        }
			        if ((n == -1) && (errno == EWOULDBLOCK))
					    continue;
			        if (n <= 0) {
					    sp->s_refcnt = 0;
					    sqrm(sp, &mask);
					    continue;
			        }
			        if ((sp->s_bufp - (char *)&sp->s_tempsize) ==
					sizeof(u_short)) {
					sp->s_size = htons(sp->s_tempsize);
					if (sp->s_bufsize == 0) {
					    if ( (sp->s_buf = malloc(BUFSIZ))
						== NULL) {
						    sp->s_buf = buf;
						    sp->s_size  = sizeof(buf);
					    } else {
						    sp->s_bufsize = BUFSIZ;
					    }
					}
					if (sp->s_size > sp->s_bufsize &&
					  sp->s_bufsize != 0) {
					    if ((sp->s_buf = realloc(
						(char *)sp->s_buf,
						(unsigned)sp->s_size)) == NULL){
						    sp->s_buf = buf;
						    sp->s_bufsize = 0;
						    sp->s_size  = sizeof(buf);
					   } else {
						    sp->s_bufsize = sp->s_size;
					   }
					}
					sp->s_bufp = sp->s_buf;	
				    }
			    }
			    if (gettimeofday(&tt, (struct timezone *)0) < 0) {
				    syslog(LOG_ERR, "gettimeofday failed: %m");
				    break;
			    }
			    sp->s_time = tt.tv_sec;
			    while (sp->s_size > 0 &&
			      (n = read(sp->s_rfd, sp->s_buf, sp->s_size)) > 0)
			    {
				    sp->s_bufp += n;
				    sp->s_size -= n;
			    }
			    /*
			     * we don't have enough memory for the query.
			     * if we have a query id, then we will send an
			     * error back to the user.
			     */
			    if (sp->s_bufsize == 0 &&
				(sp->s_bufp - sp->s_buf > sizeof(u_short))) {
				    HEADER *hp;

				    hp = (HEADER *)sp->s_buf;
				    hp->qr = 1;
				    hp->ra = 1;
				    hp->ancount = 0;
				    hp->qdcount = 0;
				    hp->nscount = 0;
				    hp->arcount = 0;
				    hp->rcode = SERVFAIL;
				    (void) writemsg(sp->s_rfd, sp->s_buf,
					sizeof(HEADER));
				    continue;
			    }
			    if ((n == -1) && (errno == EWOULDBLOCK))
				    continue;
			    if (n <= 0) {
				    sp->s_refcnt = 0;
				    sqrm(sp, &mask);
				    continue;
			    }
			    /*
			     * Consult database to get the answer.
			     */
			    if (sp->s_size == 0) {
				    sp->s_refcnt++;
				    ns_req(sp->s_buf,
					sp->s_bufp - sp->s_buf,
					sp->s_bufsize, sp,
					&sp->s_from);
				    sp->s_bufp = (char *)&sp->s_tempsize;
				    sp->s_size = -1;
				    continue;
			    }
		    }
		}
	}
	(void) close(vs);
	(void) close(ds);
	return (0);
}

usage()
{
	fprintf(stderr, "Usage: named [-d #] [-p port] [{-b} bootfile]\n");
	exit(1);
}

/*
** Set flag saying to reload database upon receiving SIGHUP.
** Must make sure that someone isn't walking through a data
** structure at the time.
*/

onhup()
{
	needreload = 1;
}

/*
** Set flag saying to call ns_maint()
** Must make sure that someone isn't walking through a data
** structure at the time.
*/

maint_alarm()
{
	needmaint = 1;
}

/*
** Set flag saying to read was interrupted
** used for a read timer
*/

read_alarm()
{
	extern int read_interrupted;
	read_interrupted = 1;
}

reapchild()
{
	union wait status;

	while (wait3(&status, WNOHANG, (struct rusage *)NULL) > 0)
		;
}

/*
** Turn on or off debuging by open or closeing the debug file
*/

setdebug(code)
int code;
{
#if defined(lint) && !defined(DEBUG)
	code = code;
#endif
#ifdef DEBUG

	if (code) {
		ddt = freopen(debugfile, "w+", stderr);
		if ( ddt == NULL)
			syslog(LOG_WARNING, "can't open debug file: %m");
		else
			setlinebuf(ddt);
	}
	else {
		fprintf(ddt,"Debug turned OFF, Level %d\n",debug);
		(void) fclose(ddt);
		debug = 0;
	}
#endif
}

/*
** Catch a special signal  SIGEMT and set debug level
**
**  SIGEMT - if debuging is off then turn on debuging else incremnt the level
**
** Handy for looking in on long running name servers.
*/

sigsetdebug()
{

#ifdef DEBUG
	if (debug == 0) {
		debug++;
		setdebug(1);
	}
	else {
		debug++;
	}
	fprintf(ddt,"Debug turned ON, Level %d\n",debug);
#endif
}

/*
** Catch a special signal's SIGFPE and turn off debugging
**
**  SIGFPE - turn off debugging
*/

signodebug()
{
	setdebug(0);
}


/*
** Catch a special signal SIGSYS
**
**  this is setup to fork and exit to drop to /usr/tmp/gmon.out
**   and keep the server running
*/

sigprof()
{
#ifdef DEBUG
	if (debug)
		fprintf(ddt,"sigprof()\n");
#endif
	if ( fork() == 0)
	{
		(void) chdir("/usr/tmp");
		exit(1);
	}
}

/*
** Routines for managing stream queue
*/

struct qstream *
sqadd()
{
	register struct qstream *sqp;

	if ((sqp = (struct qstream *)calloc(1, sizeof(struct qstream)))
	    == NULL ) {
#ifdef DEBUG
		if (debug >= 5)
			fprintf(ddt,"sqadd: malloc error\n");
#endif
		syslog(LOG_ERR, "sqadd: Out Of Memory");
		return(QSTREAM_NULL);
	}
#ifdef DEBUG
	if (debug > 3)
		fprintf(ddt,"sqadd(x%x)\n", sqp);
#endif

	sqp->s_next = streamq;
	streamq = sqp;
	return(sqp);
}

sqrm(qp, mask)
	register struct qstream *qp;
	fd_set *mask;
{
	register struct qstream *qsp;

#ifdef DEBUG
	if (debug > 1) {
		fprintf(ddt,"sqrm(%#x, %d ) rfcnt=%d\n",
		    qp, qp->s_rfd, qp->s_refcnt);
	}
#endif
	if (qp->s_refcnt != 0)
		return;

	if (qp->s_bufsize != 0)
		(void) free(qp->s_buf);
	FD_CLR(qp->s_rfd, mask);
	(void) close(qp->s_rfd);
	if (qp == streamq) {
		streamq = qp->s_next;
	} else {
		for (qsp = streamq; qsp->s_next != qp; qsp = qsp->s_next)
			;
		qsp->s_next = qp->s_next;
	}
	(void)free((char *)qp);
}

setproctitle(a, s)
	char *a;
	int s;
{
	int size;
	register char *cp;
	struct sockaddr_in sin;
	char buf[80];

	cp = Argv[0];
	size = sizeof(sin);
	if (getpeername(s, &sin, &size) == 0)
		(void) sprintf(buf, "-%s [%s]", a, inet_ntoa(sin.sin_addr));
	else
		(void) sprintf(buf, "-%s", a);
	(void) strncpy(cp, buf, LastArg - cp);
	cp += strlen(cp);
	while (cp < LastArg)
		*cp++ = ' ';
}
