/*
 * Named init --- sits around and restarts named when necessary
 *
 * Written by Theodore Ts'o.  Copyright 1991.
 * 
 * Use this code however you want, as long as you don't try to make
 * money off of it and as long as you don't claim it's yours.
 *
 *	$Header: /cvs/Darwin/Services/bind/bind/contrib/ninit/ninit.c,v 1.1.1.2 2001/01/31 03:59:06 zarzycki Exp $
 * 	$Source: /cvs/Darwin/Services/bind/bind/contrib/ninit/ninit.c,v $
 *
 * Note that ninit requires that named be modified so that it accepts
 * the -n option.  This option to named asks named not to fork into
 * the background when it is started up.  This is necessary because
 * ninit wants to be notified when the named process exits.
 *
 * Usage: ninit [options] [named boot file]
 *
 * Options:
 * 	-n <negative niceness>	Run the named process with the nice level
 * 				*reduced* argument level.  This is useful
 * 				on mailhub, when you have 30+ sendmail
 * 				processes and one named process, and you
 * 				want to give the named process a
 * 				higher priority.
 *
 * 	-s <stats interval>	This option controls how often (in
 * 				seconds) named should be strobed to
 * 				update the named.stats file.
 *
 * 	-c <check interval>	This option controls how often (in
 * 				seconds) ninit should check to make
 * 				sure named is still working.
 *
 * 	-f <syslog facility>	This option controls which syslog facility
 * 				ninit should use.
 *
 * 	-d <debug level>	This option is passed to named to turn
 * 				on nameserver debugging.
 *
 * 	-N			This option tells ninit not fork and go
 * 				into the background when it is started
 * 				up.
 *
 */

#include <stdio.h>
#include <sys/types.h>
#include <syslog.h>
#include <errno.h>
#include <signal.h>
#include <netdb.h>
#include <ctype.h>
#include <sys/socket.h>
#include <arpa/nameser.h>
#include <netinet/in.h>
#include <resolv.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/resource.h>

/*
 * Configuration section.  It should be fairly obvious.....
 *
 * FORKING_NAMED is intended for site who do not have source code to
 * their named, and so cannot add the -n option to named.
 *
 * TEST_HOST specifies the host which ninit should try to resolve when
 * checking to see if named is working.
 */

#define TEST_HOST "MIT.EDU"
#define NAMED "/etc/named"
#define NAMED_PID_FILE "/etc/named.pid"
#define PID_FILE "/etc/ninit.pid"
#define DEFAULT_FACILITY LOG_LOCAL4
/* #define FORKING_NAMED */

extern int h_errno;

int	named_pid = -1;
int	terminating_named = 0;
int	named_restarting = 0;
int	child_died = 0;

int	stats_timer = 0;
int	check_timer = 0;

char	*named_file = 0;
int	named_nice = 0;
int	debug = 0;
int	syslog_facility = DEFAULT_FACILITY;
char	*progname = 0;
int	nofork = 0;
int	stats_interval = 300;
int	check_interval = 60;

struct in_addr local_addr;
	
void	start_named(), PRS(), usage();
int	decode_facility();

struct code {
        char    *name;
        int     facility;
};

struct code     FacNames[] = {
        "kern",         LOG_KERN,
        "user",         LOG_USER,
        "mail",         LOG_MAIL,
        "daemon",       LOG_DAEMON,
        "auth",         LOG_AUTH,
        "syslog",       LOG_SYSLOG,
        "lpr",          LOG_LPR,
#ifdef LOG_NEWS
        "news",         LOG_NEWS,
#endif
#ifdef LOG_UUCP
        "uucp",         LOG_UUCP,
#endif
        "local0",       LOG_LOCAL0,
        "local1",       LOG_LOCAL1,
        "local2",       LOG_LOCAL2,
        "local3",       LOG_LOCAL3,
        "local4",       LOG_LOCAL4,
        "local5",       LOG_LOCAL5,
        "local6",       LOG_LOCAL6,
        "local7",       LOG_LOCAL7,
        "security",     LOG_AUTH,
#ifdef LOG_MARK
        "mark",         LOG_MARK,
#endif
        NULL,           -1
};

int alarm_chk()
{
	int	named_active = 1;
	int	do_check = 0;
	int	next_alarm;
	
	if ((named_pid <= 0) || named_restarting || terminating_named) {
		named_active = 0;
	}
	/*
	 * We could put something here to increment the timers by alarm(0),
	 * but we won't bother for now.
	 *
	 * Check to see if the timers have expired; if so, run the events
	 */
	if (named_active && (stats_timer <= 0)) {
		(void) rename("/usr/tmp/named.stats",
			      "/usr/tmp/named.stats.old");
		kill(named_pid, SIGIOT);
		stats_timer = stats_interval;
	}
	if (named_active && (check_timer <= 0)) {
		/*
		 * We run this one after restarting the alarm because
		 * it might take a while
		 */
		do_check++;
		check_timer = check_interval;
	}
	/*
	 * Now figure out when the next time we need to run.
	 */
	if (stats_timer < check_timer)
		next_alarm = stats_timer;
	else
		next_alarm = check_timer;
	stats_timer -= next_alarm;
	check_timer -= next_alarm;
	alarm(next_alarm);
	/*
	 * Now, perform the named check if necessary
	 */
	if (do_check) {
		(void) gethostbyname(TEST_HOST);
		if (h_errno == TRY_AGAIN) {
			syslog(LOG_ERR, "Named hosed!  Restarting...");
			restart_named();
		}
	}
}

int restart_named()
{
	if ((named_pid <= 0) || named_restarting || terminating_named)
		return;
	terminating_named++;
	kill(named_pid, SIGTERM);
}

int die()
{
	syslog(LOG_NOTICE, "/etc/ninit received SIGTERM, exiting");
	if (named_pid > 0) {
		kill(named_pid, SIGTERM);
		syslog(LOG_NOTICE, "named killed as part of ninit shutdown");
	}
	(void) unlink(NAMED_PID_FILE);
	(void) unlink(PID_FILE);
	exit(0);
}

int mourner()
{
	child_died++;
}

main(argc, argv)
	int	argc;
	char	**argv;
{
	int	pid;
	union wait status;
	
	PRS(argc, argv);
	daemon_setup();
	openlog("ninit", LOG_PID|LOG_CONS, syslog_facility);
	syslog(LOG_INFO, "ninit started, pid = %d", getpid());
	signal(SIGALRM, alarm_chk);
	signal(SIGHUP, restart_named);
	signal(SIGTERM, die);
	signal(SIGCHLD, mourner);
	start_named();
	alarm_chk();
	
	while (1) {
		pid = wait(&status);

		if (pid == -1) {
			if (errno == ECHILD) {
				named_pid = -1;
				start_named();
				continue;
			} else {
				syslog(LOG_CRIT,
				       "Error in wait: %s",
				       sys_errlist[errno]);
				sleep(120);
				continue;
			}
		}
		named_pid = -1;
		if (terminating_named || (status.w_termsig == SIGTERM)) {
			terminating_named = 0;
		} else if (status.w_termsig) {
			syslog(LOG_ERR, "Named terinated with signal %d%s",
			       status.w_termsig,
			       status.w_coredump ? " (core dumped)" : "");
		} else {
			syslog(status.w_retcode ? LOG_ERR : LOG_NOTICE,
			       "named termined with exit status %d",
			       status.w_retcode);
		}
		start_named();
	}
	
}

void start_named()
{
	int	pid;
	int	tries = 3;
	FILE	*f;
	char	debug_buf[24];
	char	*argv[10];
	int	argc = 0;

	if (f = fopen(NAMED_PID_FILE, "r")) {
		fscanf(f, "%d", &pid);
		fclose(f);
		if ((pid > 0) && !kill(pid, 0)) {
			/* There is a running named already, kill it. */
			syslog(LOG_WARNING,
			       "Killing already existing named, pid = %d",
			       pid);
			kill(pid, 15);
		}
	}

	if (named_restarting++ > 3) {
		syslog(LOG_ALERT,
		       "Couldn't start named after three tries, sleeping");
		sleep(180);
		named_restarting = 1;
	}
	child_died = 0;
	if ((pid = vfork()) == 0) {
		argv[argc++]  = "named";
		if (debug) {
			argv[argc++] = "-d";
			sprintf(debug_buf, "%d", debug);
			argv[argc++] = debug_buf;
		}
#ifdef FORKING_NAMED
		else
			argv[argc++] = "-d";
#else
		argv[argc++] = "-n";
#endif
		argv[argc++] = named_file;
		argv[argc++] = 0;
		execv(NAMED, argv);
		syslog(LOG_ERR, "Couldn't start named: %s",
		       sys_errlist[errno]);
		exit(1);
	} else if (pid == -1) {
		syslog(LOG_ERR, "Couldn't fork to start named: %s",
		       sys_errlist[errno]);
		sleep(180);
		return;
	} else {
		syslog(LOG_DEBUG, "Named process started, pid = %d", pid);
		named_pid = pid;
		setpriority(PRIO_PROCESS, pid, named_nice);
		named_restarting++;
		/* Stall until the named is really working */
		do {
			if (child_died)
				return;
			(void) gethostbyname(TEST_HOST);
		} while (h_errno == TRY_AGAIN);
		syslog(LOG_INFO, "Named successfully started, pid = %d\n",
		       pid);
		named_restarting = 0;
#ifdef FORKING_NAMED
		/* Turn off debugging */
		if (!debug)
			kill(named_pid, SIGUSR2);
#endif
		/*
		 * Reset timers...
		 */
		stats_timer = stats_interval;
		check_timer = check_interval;
		alarm_chk();
	}
}

void PRS(argc, argv)
	int	argc;
	char	**argv;
{
	register char	*word, ch;
	char	*buf;
	char	*fac = NULL;
	
	local_addr.s_addr = inet_addr("127.0.0.1");
	res_init();
	_res.nscount = 1;	/* One nameserver --- the local one */
	_res.nsaddr.sin_addr = local_addr;
	_res.retry = 2;
	_res.retrans = RES_TIMEOUT;
	progname = *argv++;
	while (word = *argv++) {
		if (*word == '-') {
			word++;
			while (word && (ch = *word++)) {
				switch(ch){
				case 'n': 	/* Niceness */
					if (*word)
						buf = word;
					else
						buf = *argv++;
					if (!buf)
						usage();
					named_nice = 0 - atoi(buf);
					word = 0;
					break;
				case 's': 	/* Stats int. */
					if (*word)
						buf = word;
					else
						buf = *argv++;
					if (!buf)
						usage();
					stats_interval = atoi(buf);
					word = 0;
					break;
				case 'c': 	/* Check int. */
					if (*word)
						buf = word;
					else
						buf = *argv++;
					if (!buf)
						usage();
					check_interval = atoi(buf);
					word = 0;
					break;
				case 'f': 	/* Syslog facility */
					if (*word)
						fac = word;
					else
						fac = *argv++;
					if (!fac)
						usage();
					word = 0;
					break;
				case 'd': 	/* Debug */
					if (*word)
						buf = word;
					else
						buf = *argv++;
					if (!buf)
						usage();
					debug = atoi(buf);
					word = 0;
					break;
				case 'N': 	/* Nofork */
					nofork++;
					break;
				default:
					usage();
				}
				
			}
		} else {
			if (named_file)
				usage();
			else
				named_file = word;
		}
	}
	if (fac) {
		syslog_facility = decode_facility(fac);
		if (syslog_facility < 0) {
			fprintf(stderr, "%s is not a valid facility\n", fac);
			exit(1);
		}
	}
}

void usage()
{
	fprintf(stderr,
		"Usage: %s [OPTIONS] named_boot_file\n\n", progname);
	fprintf(stderr, "Where the options can be: \n");
	fprintf(stderr, "\t-d debug_level\tTurns on debugging\n");
	fprintf(stderr, "\t-n nice arg\tRenices the named by -nice arg\n");
	fprintf(stderr, "\t-f facility\tUses the specified syslog facility\n");
	fprintf(stderr, "\t-s stats_interval\tSpecifies the statistics polling interval\n");
	fprintf(stderr, "\t-c check_interval\tSpecifies the named check interval\n");
	fprintf(stderr, "\t-N\t\tCauses ninit not to fork on startup.\n");
	exit(1);
}

daemon_setup()
{
	int	n, pid;
	FILE	*f;

	if (f = fopen(PID_FILE, "r")) {
		fscanf(f, "%d", &pid);
		fclose(f);
		if (!kill(pid, 0)) {
			fprintf(stderr,
				"ninit is already running on pid = %d\n",
				pid);
			exit(1);
		}
	}
	
	if (!nofork) {
		if (fork() > 0)
			exit(0);
		n = open("/dev/null", O_RDONLY);
		(void) dup2(n, 0);
		(void) dup2(n, 1);
		(void) dup2(n, 2);
		if (n > 2)
			(void) close(n);
	}
	
	if ((f = fopen(PID_FILE, "w")) == NULL)
		syslog(LOG_ERR, "Couldn't open pid file for write, %s",
		       sys_errlist[errno]);
	fprintf(f, "%d\n", getpid());
	fclose(f);
}

int decode_facility(name)
	char	*name;
{
	char	buf[40], *src, *dest;
	struct code	*p;
	int	i;

	for (src = name, dest = buf, i = 0; *src && i < 40; src++, dest++, i++)
		*dest = (isupper(*src)) ? tolower(*src) : *src;
	for (p = FacNames; p->name; p++)
		if (!strcmp(buf, p->name))
			return(p->facility);
	return(-1);
}

		
	
	
