/* $Id: syswatch.c,v 1.69 2000/06/13 05:54:03 jared Exp $ */
#include "config.h"

/* Normal global vars */
unsigned char *ident_hash = NULL;
bool gotsighup = FALSE;   /* quit all things we're doing asap, and then
				cleanup and shuffle config files, etc.. */
int lvl = 0; /* internal counter for the braces */
int ln = 0; /* line number */
int numqueued = 0;   /* number of elements in the queue */
time_t boottime = 0; /* boot time for uptime */
bool debug = 0; /* debugging flag - 0 = off 1 = on */
bool mallocdebug = FALSE; /* 0 = off, 1 = on */
char *myname; /* myname */
struct hostinfo *currenthead = NULL; /* something */
struct dnscache *dnshead = NULL;
struct clientstatus *clienthead;
struct monitorent *queuehead = NULL;
struct protoent *icmpproto;
bool statuschanged = TRUE;
bool do_count = FALSE;
unsigned long elements_to_monitor = 0; /* gets updated with how many
						elements we monitor */
unsigned short int killed = 0;
unsigned short int killafter = 45; /* kill a check after this many seconds */
unsigned short int warnlog = 0; /* default = off */
unsigned short disable_icmp = 0;

/* command line specified vars & args */
char configfile[256]; /* config file path */
bool donotify = 1; /* Do notifies - a value of 0 doesn't notify contacts */
int dofork = 0; /* 0 = fork, 1 = don't */
bool ckconfigonly = FALSE; /* 0 = normal, 1 = just check config then exit */
bool reload = FALSE; /* false = no send sighup to pid listed in /etc/sysmond.pid */
bool paused = FALSE; /* false = do not suspend monitoring */
bool send_pause_sig = FALSE; /* false = do not send pause signal */
bool badconfig = FALSE; /* false = config parsed ok */

/* defaults set in set_defaults */
int numfailures; /* Number of failures before mailing contact */
int inactivetime; /* timeout for client inactivity */
int globtimeout; /* amount of time it takes to time out internal tests 
			(in seconds) */
int globtimeoutlen; /* when to dequeue the request (in seconds) */
int pageinterval; /* no re-page interval for hosts when they're down,
			otherwise the number of minutes to re-page in */
bool showupalso; /* show up also in textfile */
int queuetime; /* How often to queue an individual request */
int facility; /* facility to log into */
int glob_icmp_fd = -1; /* Global ICMP file des */
int maxqueued;   /* number of elements that can be in the queue at
			once - delay queueing until it comes down,
			default = 100, 0 = Unlimited (Dangerous) */
char *pmesg = NULL;
char *subject = NULL;
char *sender = NULL;
int dnslog = 900;
int dnsexpire = 3600;
int heartbeat = 1;
char *errorsto = NULL;
char *replyto = NULL;
int html = -1;
char *statusfilename = NULL;
char *upcolor = UPCOLOR;
char *downcolor = DOWNCOLOR;
char *recentcolor = RECENTCOLOR;
char *globhdr = NULL;
char *globhdrval = NULL;
char *authkey = NULL;
char *statefile = NULL;


/* set defaults */
void set_defaults()
{
	/* Set global variables to some sort of defaults */

	numfailures = 4;
	inactivetime = 300;
	globtimeout = 30;
	globtimeoutlen = 35;
	pageinterval = 0;
	showupalso = FALSE;
	queuetime = 60;
	facility = LOG_DAEMON;
	maxqueued = 100;
	dnslog = 900;
	dnsexpire = 3600;
	heartbeat = 1;
	if (errorsto != NULL)
	{
		free(errorsto);
		errorsto = NULL;
	}
	if (replyto != NULL)
	{
		free(replyto);
		replyto = NULL;
	}
	if (globhdr != NULL)
	{
		free(globhdr);
		globhdr = NULL;
	}
	if (globhdrval != NULL)
	{
		free(globhdrval);
		globhdrval = NULL;
	}
	if (statusfilename != NULL)
	{
		free(statusfilename);
		statusfilename = NULL;
	}
	if (pmesg != PMESG)
	{
		free(pmesg);
		pmesg = PMESG;
	}
	if (statefile != NULL)
	{
		free(statefile);
		statefile = NULL;
	}
	if (subject != SUBJECT)
	{
		free (subject);
		subject = SUBJECT;
	}
	if (sender != NULL)
	{
		free(sender);
		sender = NULL;
	}
	if (upcolor != UPCOLOR)
	{
		free(upcolor);
		upcolor = UPCOLOR;
	}
        if (downcolor != DOWNCOLOR)
	{
		free(downcolor);
	        downcolor = DOWNCOLOR;
	}
        if (recentcolor != RECENTCOLOR)
	{
		free(recentcolor);
	        recentcolor = RECENTCOLOR;
	}
}

/* 
 * This function counts total hosts, those up currently, down currently
 * and stores them in long ints, for showing in textfiles
 */

void recurs_count(struct hostinfo *here, unsigned long *total)
{
	if (here == NULL)
		return;
	*total = *total+1;

	recurs_count(here->child, total);
	recurs_count(here->sibling, total);
	
}

void update_count()
{
	unsigned long total = 0;
	struct hostinfo *here;
	here = currenthead;
	recurs_count(here, &total);
	print_err(0, "total entries to be monitored = %d\n", total);
	elements_to_monitor = total;
	return;
}

void usage ()
{
	/* Print out the real way to use the program, morons! */
	fprintf(stderr,"Usage: %s [ -f config-file ] [ -n ] [ -d ] [ -v ] [ -t ] \n\t [ -p port ] [ reload ] \n", myname);
	fprintf(stderr,"  -f config-file : Alternate config file location\n");
	fprintf(stderr,"  -n             : Don't do notifies\n");
	fprintf(stderr,"  -d             : Don't fork\n");
	fprintf(stderr,"  -v             : Print version then exit\n");
	fprintf(stderr,"  -t             : Test/check config file then exit\n");
	fprintf(stderr,"  -p #           : Change port number listening on\n");
	fprintf(stderr,"  reload         : Test/check config file, and if it passes, reload config\n");
	exit(1);
}

/*
 * Toggle warning logging
 */
void toggle_warnlog()
{
	if (warnlog)
		warnlog = 0;
	else
		warnlog = 1;
        signal(SIGUSR1, toggle_warnlog);
}

/*
 * Toggle pause
 */
void toggle_pause()
{
	if (paused)
	{
		paused = 0;
		print_err(0, "monitoring unpaused");
	} else {
		paused = 1;
		print_err(0, "monitoring PAUSED");
	}
	signal(SIGUSR2, toggle_pause);

}

/* parse the command line arguments */
void cmdline(int argc, char **argv, char *conf_file, int *listenport)
{
	int er = 0, x;

	for (x=1; argc > x ; x++)
	{
		if (argv[x][0]=='-')
		{
			switch (argv[x][1]) 
			{
				case 'f':
				{
					if (x+1 == argc)
						usage();
					strcpy(conf_file,argv[x]+2);
					if (strlen(conf_file) == 0)
					{
						strcpy(conf_file,argv[x+1]);
						x++;
					}
					break;
				}
				case 'p': /* alternate port number */
				{
					if (x+1 == argc)
						usage();
					*listenport = atoi(argv[x+1]);
					x++; /* skip it as the next arg */
					break;
				}
				case 'i':
					disable_icmp = TRUE;
					print_err(1, "ICMP disabled");
					break;
				case 'j':
					do_count = TRUE;
					break;
				case 'n': /* don't do notifies */
				{
					donotify = 0;
					break;
				}
				case 'd': /* don't fork */
				{
					dofork = 1;
					break;
				}
				case 't': /* verify config file */
				{
					ckconfigonly = 1;
					break;
				}
				case 'M': /* malloc debug on */
				{
					mallocdebug= TRUE;
					break;
				}
				case 'w': /* warning log */
				{
					toggle_warnlog();
					break;
				}
				case 'D': /* debug on */
				{
					debug = 1;
					print_err(1, "Debug On");
					break;
				}
				case 'v': /* print version */
				{
					fprintf(stdout, "%s\n", VERS);
					exit(0);
				}
				case '-': /* gnu style args */
				{
					if (strcmp(argv[x]+2, "version") == 0)
					{
						fprintf(stdout, "%s\n", VERS);
						exit(0);
					}
					if (strcmp(argv[x]+2, "help") == 0)
						usage();
				}
				default:
					er++;
			}
		} else {
			if (strcmp(argv[x], "reload") == 0)
			{
				reload = TRUE;
				ckconfigonly = TRUE;
			} else if (strcmp(argv[x], "pause") == 0)
			{
				send_pause_sig = TRUE;
			}
		}
	} /* end for */
	if (er > 0)
		usage();
}

void stop_daemon()
{

	print_err(1, "Shutting down... Please remain seated as your ride comes to a complete stop");
	/* insert cleanup code here */

	/* dump current network status to a file */
	if (html != -1)
		dump_to_file(statusfilename, html);

	/* timeout old clients */
	inactivetime = 0;
	timeout_clients();

	/* free dead client memory */
	dead_client_cleanup();

	if (clienthead != NULL)
	{
		/* un bind the listen socket */
		close(clienthead->filedes); 
	}
	/* Free the memory */
	FREE(ident_hash);

	dnsexpire = 0;
	expire_dns();

	/* free the memory in use */
	free_tree(currenthead);

	exit(0);
}

void setup_client_listen(int listenport)
{
	clienthead = MALLOC(sizeof(struct clientstatus), "clienthead");

	/* listen on port PORTNUM */
	clienthead->filedes = init_socket(listenport);

	/* master client socket */
	clienthead->clientver = -1;
}

void recurs_print_q(int filedes, struct hostinfo *here)
{
	char buffer[256];

	if (here == NULL)
		return;

	if (here->queued == 1)
	{
		snprintf(buffer, 256, "treeQ: %s:%d", here->hostname, here->type);
	       	sendline(filedes, buffer);
	}
	recurs_print_q(filedes, here->child);
	recurs_print_q(filedes, here->sibling);
}

void print_queue(int filedes)
{
	struct monitorent *here;
	char buffer[256];

	/* Print out the currently queued entries for debugging */
	for (here = queuehead; here!= NULL; here = here->next)
	{
		snprintf(buffer, 256, "Queue: %s:%s:%d", here->checkent->hostname,
			type_to_name(here->checkent->type),
			here->checkent->port);
		sendline(filedes, buffer);
	}
	sendline(filedes, "End of Queue based on queuehead");

	recurs_print_q(filedes, currenthead);

	snprintf(buffer, 256, "Number of elements in queue count: %d", numqueued);
	sendline(filedes, buffer);

	return;
}

void
queue_check(struct hostinfo *entry)
{
	struct monitorent *newentry = NULL;

	/* put a pointer on the stack for this entry */

	/* stick it at the top for easy access */

	if (entry == NULL)
	{
		print_err(1, "queue_check called w/ NULL");
		return;
	}
	if (entry->queued == 1)
	{
		print_err(1, "queue_check: Attempt to queue check already in q");
		return;
	}
	newentry = MALLOC(sizeof(struct monitorent), "new entry - monitorent in queue_check");

	newentry->checkent = entry;
	newentry->started = 0;
	newentry->monitordata = NULL;
	gettimeofday(&newentry->queueat, NULL);
	newentry->retval = -1;
	newentry->filedes = -1;
	newentry->fd_state = 0;
        newentry->checkent->queued = 1;

	/* insert it at the top of the queue */
        newentry->next = queuehead;
	queuehead = newentry;

	numqueued++;

	/* entry is queued for checking */
	if (debug)
	{
		print_err(0, "queue_check:Queued %s:%s:%p", 
			entry->hostname, type_to_name(entry->type), entry);
	}

	return;
}

/*      Routine used to do recursive walking of the n-ary
	tree to check entries for their queue status */
void recurs_queue_checks(struct hostinfo *entry, time_t now)
{
	if (entry == NULL)
	{
		print_err(1, "BUG! recurs_queue_checks called w/ NULL arg");
		return;
	}
	if (entry->queued == 0) 
	{
		if ((entry->lchecktime+queuetime) < now)
		{
			if ((maxqueued == 0) || (numqueued <= maxqueued))
			{
				queue_check(entry);
			}
		} else if ((entry->lastcheck == 0) && 
				(entry->child != NULL) && (entry->reverse == 0)) 
		{
			recurs_queue_checks(entry->child, now);
		} else if ((entry->lastcheck != 0) &&
				(entry->child != NULL) && (entry->reverse == 1))
		{
			recurs_queue_checks(entry->child, now);

		}
	} else {
		/* we are queued, and were last checked as up */
		if ((entry->lastcheck == SYSM_OK) && (entry->reverse==0) &&
			(entry->child != NULL))
		{
			recurs_queue_checks(entry->child, now);
		}
		/* we are queued, and were last checked as DOWN, and
			invert is on */
		if ((entry->lastcheck != SYSM_OK) && (entry->reverse) &&
			(entry->child != NULL))
		{
			recurs_queue_checks(entry->child, now);
		}
	}

	if (entry->sibling != NULL)
		recurs_queue_checks(entry->sibling, now);
}

void queue_checks()
{
	time_t now;

	/* Get the current time */
	time(&now);

	/* walk n-ary tree and queue checks that need it */
	recurs_queue_checks(currenthead, now);
}

void needssleep()
{
	fd_set rd, wr, except;
	struct timeval local_timeout;
	struct clientstatus *clienthere = NULL;
	struct monitorent *here = NULL;
	int mincalctime = 1, maxfd = 1;

	/* determine if we need sleep */

	/* set up all the data structures */
	FD_ZERO(&wr);
	FD_ZERO(&except);
	FD_ZERO(&rd);

	/* Service clients */
	client_poll();

	/* Watch the clients for data to be read */
	for (clienthere = clienthead; clienthere != NULL;
		clienthere = clienthere->next)
	{
		if (clienthere->filedes == -1)
		{
			continue;
		}
		FD_SET(clienthere->filedes, &rd);
		if (clienthere->filedes > maxfd)
			maxfd = clienthere->filedes;
	}

	/*
	 *
	 */
	for (here=queuehead;here!= NULL;here=here->next)
	{
		if (here->filedes == -1)
			continue;
		if (here->retval != -1)
			continue;
		if (here->fd_state)
		{
			FD_SET(here->filedes, &rd);
		} else {
			FD_SET(here->filedes, &wr);
		}
		if (here->filedes > maxfd)
			maxfd = here->filedes;
	}
	
	if (glob_icmp_fd > maxfd && (!disable_icmp))
		maxfd = glob_icmp_fd;
	
	/* Need a way to calculate this (really!) */

	/* create new flags in monitorent for fd, and if
	   we're pending read or write? */

	/* watch for data to read on this fd */
	/*              FD_SET(fd, &rd); */

	if (!disable_icmp)
	{
		FD_SET(glob_icmp_fd, &rd);
	}

	/* watch for the ability to write on this fd */
	/*              FD_SET(fd, &wr); */

	local_timeout.tv_sec = mincalctime;
	local_timeout.tv_usec = 0;

	select(maxfd+1, &rd, &wr, &except, &local_timeout);
	if (!disable_icmp)
	{
		if (FD_ISSET(glob_icmp_fd, &rd))
		{
			handle_icmp_responses();
		}
	}

        /*
         *
         */
        for (here=queuehead;here!= NULL;here=here->next)
        {
                if (here->filedes == -1)
                        continue;
		if (here->retval != -1)
			continue;
                if (FD_ISSET(here->filedes, &rd))
		{
			service_this(here);
		}
        }


	/* basically, set up all the fd's we need to watch
		in a fd_set with select timeout set for the
		minimum time for any check in the tree for
		the time it needs to wake up, include client
		fd's in this also */
}

void stop_this(struct monitorent *here)
{
	switch (here->checkent->type)
	{
		case SYSM_TYPE_TCP:
		       	stop_test_tcp(here);
		       	break;
		case SYSM_TYPE_UDP:
		       	stop_test_udp(here);
		       	break;
		case SYSM_TYPE_PING:
		       	stop_test_ping(here);
		       	break;
#ifdef IMPLEMENT
		case SYSM_TYPE_SNMP:
		       	stop_test_snmp(here);
		       	break;
#endif /* IMPLEMENT */
		case SYSM_TYPE_NNTP:
		       	stop_test_nntp(here);
		       	break;
		case SYSM_TYPE_SMTP:
		       	stop_test_smtp(here);
		       	break;
		case SYSM_TYPE_IMAP:
		       	stop_test_imap(here);
		       	break;
		case SYSM_TYPE_POP3:
		       	stop_test_pop3(here);
		       	break;
		case SYSM_TYPE_X500:
		       	stop_test_x500(here);
		       	break;
#ifdef IMPLEMENT
		case SYSM_TYPE_POP2:
		       	stop_test_pop2(here);
		       	break;
		case SYSM_TYPE_BOOTP:
		       	stop_test_bootp(here);
		       	break;
		case SYSM_TYPE_DNS:
		       	stop_test_dns(here);
		       	break;
#endif /* IMPLEMENT */
		case SYSM_TYPE_WWW:
		       	stop_test_www(here);
		       	break;
		case SYSM_TYPE_RADIUS:
		       	stop_check_radius(here);
		       	break;
#ifdef IMPLEMENT
		case SYSM_TYPE_HTTPS:
		       	stop_test_https(here);
		       	break;
#endif /* IMPLEMENT */
		case SYSM_TYPE_SYSM:
		       	stop_check_sysmon(here);
		       	break;
#ifdef IMPLEMENT
		case SYSM_TYPE_SSHD:
		       	stop_test_sshd(here);
		       	break;
#endif /* IMPLEMENT */
		default:
			print_err(1, "BUG! shouldn't have reached here");
			break;
       	}
}

void service_this(struct monitorent *here)
{
	if (here == NULL)
	{
		print_err(1, "BUG!  service_this() called with here== NULL");
		return;
	}
	if (debug)
	{
		print_err(0, "Servicing entry in queue of %s:%s",
			here->checkent->hostname,
			type_to_name(here->checkent->type));
	}

	if (here->started)
	{
		/* service a check already started */
		switch (here->checkent->type)
		{
			case SYSM_TYPE_TCP: service_test_tcp(here);
				break;
			case SYSM_TYPE_UDP: service_test_udp(here);
				break;
			case SYSM_TYPE_PING: service_test_ping(here);
				break;
#ifdef IMPLEMENT
                        case SYSM_TYPE_SNMP: service_test_snmp(here);
                                break;
#endif /* IMPLEMENT */
                        case SYSM_TYPE_NNTP: service_test_nntp(here);
                                break;
                        case SYSM_TYPE_SMTP: service_test_smtp(here);
                                break;
                        case SYSM_TYPE_IMAP: service_test_imap(here);
                                break;
                        case SYSM_TYPE_POP3: service_test_pop3(here);
                                break;
                        case SYSM_TYPE_X500: service_test_x500(here);
                                break;
#ifdef IMPLEMENT
                        case SYSM_TYPE_POP2: service_test_pop2(here);
                                break;
                        case SYSM_TYPE_BOOTP: service_test_bootp(here);
                                break;
                        case SYSM_TYPE_DNS: service_test_dns(here);
                                break;
#endif /* IMPLEMENT */
                        case SYSM_TYPE_WWW: service_test_www(here);
                                break;
                        case SYSM_TYPE_RADIUS: service_check_radius(here);
                                break;
#ifdef HAVE_SSL
                        case SYSM_TYPE_HTTPS: service_test_https(here);
                                break;
#endif /* HAVE_SSL */
			case SYSM_TYPE_SYSM: service_check_sysmon(here);
				break;

			default: print_err(1, "service_this:BUG, should not have reached here");
				break;
                } /* end switch */
	}
	if (!here->started)
	{
		switch (here->checkent->type)
		{
			case SYSM_TYPE_TCP: 
				start_test_tcp(here);
				break;
			case SYSM_TYPE_UDP: 
				start_test_udp(here);
				break;
			case SYSM_TYPE_PING: 
				start_test_ping(here);
				break;
#ifdef IMPLEMENT
			case SYSM_TYPE_SNMP: 
				start_test_snmp(here);
				break;
#endif /* IMPLEMENT */
			case SYSM_TYPE_NNTP: 
				start_test_nntp(here);
				break;
			case SYSM_TYPE_SMTP: start_test_smtp(here);
				break;
			case SYSM_TYPE_IMAP: start_test_imap(here);
				break;
			case SYSM_TYPE_POP3: start_test_pop3(here);
				break;
			case SYSM_TYPE_X500: start_test_x500(here);
				break;
#ifdef IMPLEMENT
			case SYSM_TYPE_POP2: start_test_pop2(here);
				break;
			case SYSM_TYPE_BOOTP: start_test_bootp(here);
				break;
			case SYSM_TYPE_DNS: start_test_dns(here);
				break;
#endif /* IMPLEMENT */
			case SYSM_TYPE_WWW: start_test_www(here);
				break;
			case SYSM_TYPE_RADIUS: start_check_radius(here);
				break;
#ifdef HAVE_SSL
			case SYSM_TYPE_HTTPS: start_test_https(here);
				break;
#endif /* HAVE_SSL */
			case SYSM_TYPE_SYSM: start_check_sysmon(here);
				break;

			default: print_err(1, "service_this:BUG, should not have reached here");
				break;
		} /* end switch */
		here->started = TRUE;
	}
}

void handle_retval(struct monitorent *handle_this)
{
	if (debug)
	{
		print_err(0, "handle_retval: dequeued %s:%s:%p",
			handle_this->checkent->hostname,
			type_to_name(handle_this->checkent->type),
			handle_this);

		print_err(0, "handle_retval:checking %s type %s got us %s",
			handle_this->checkent->hostname,
			type_to_name(handle_this->checkent->type),
			errtostr(handle_this->retval));
	} /* endif debug */
        handle_this->checkent->totalchecked++;

	/* Do necessary post-check status stuff */

	/* check to see if the service came up */
	if ((handle_this->retval == 0) &&
		(handle_this->checkent->contacted == TRUE) &&
		(handle_this->checkent->lastcheck != handle_this->retval))
	{
		/* if so, set the state */
		handle_this->checkent->lastcheck = handle_this->retval;
		handle_this->checkent->totaldown++;
		/* determine if we need to page */
		if (donotify)
		{
			/* page */
			page_someone(handle_this->checkent);
		}
		time(&handle_this->checkent->last_up);
		/* reset contacted */
		handle_this->checkent->contacted = FALSE; /* init it */
		statuschanged= TRUE;
	} /* endif some long ass thing */

	/* if service is down, and state is
	   same as last time */
	if ((handle_this->retval != 0) &&
		(handle_this->checkent->lastcheck == handle_this->retval))
	{
		/* increment times service has failed */
		handle_this->checkent->downct++;
		handle_this->checkent->totaldown++;

		/* if it's failed too many times and we
			haven't paged */

		if ((handle_this->checkent->downct >=
			handle_this->checkent->max_down) &&
			(handle_this->checkent->contacted == FALSE))
		{
			/* page someone */
			page_someone(handle_this->checkent);
		}
		statuschanged=TRUE;
	} /* endif some other retval check thing */

	/* if state changed but still failing */
	if (handle_this->retval != handle_this->checkent->lastcheck)
	{
		/* reset stuff */
		handle_this->checkent->downct = 1;
		handle_this->checkent->lastcheck = handle_this->retval;
		time(&handle_this->checkent->deathtime);
                handle_this->checkent->totaldown++;

                /* if it's failed too many times and we
                        haven't paged */

                if ((handle_this->checkent->downct >=
                        handle_this->checkent->max_down) &&
                        (handle_this->checkent->contacted == FALSE))
                {
                        /* page someone */
                        page_someone(handle_this->checkent);
                }
                statuschanged=TRUE;

		statuschanged=TRUE;
	} /*endif handle_this->retval!=handle_this->checkent->lastcheck*/

	/* If we came up, clear this counter */
	if (handle_this->retval == SYSM_OK)
	{
		handle_this->checkent->downct = 0;
	}


	return;
}

void service_checks()
{
	struct monitorent *here, *last, *freeit;
	struct timeval start,end;
	int freed = 0;

	if (queuehead == NULL)
	{
		return;
	}

	/* setup checks that need to be, service current checks */
	last = NULL;
	freeit = NULL;
	here = queuehead;
	while (here != NULL)
	{
		/* if no return value */
		if (here->retval == -1)
		{
			gettimeofday(&start, NULL);
			service_this(here); /* service the check */
			gettimeofday(&end, NULL);
			if (check_runtime(start, end, "service_checks:service_this", 1))
			{
				print_err(0, "service_this:here->checkent->hostname = %s",
					here->checkent->hostname);
				print_err(0, "service_this:here->checkent->type = %s",
					type_to_name(here->checkent->type));
			}
			last = here;
		} else {
                        /* Handle the return value */
                        handle_retval(here);

			/* set the current element to be free'ed */
			freeit = here;

			/* Bug detection */
			if (here->checkent == NULL)
			{
				print_err(1, "BUG! service_checks: here->checkent == NULL");
				ABORT();
			}
			/*
			  Tag it as not queued in the check entry
			  otherwise it won't ever get checked again
			 */
                        here->checkent->queued=0;

			/* 
			   Decrease counter saying how many elements
			   are in the queue.  This is important if we
			   are doing max-queue-size stuff
			 */
			numqueued--; 	/* decrease queue counter */

			/* Save the time of the completion of our check */
			time(&(here->checkent->lchecktime));

			if (last == NULL)
			{ /* If we intend to free the head of the queue */
				queuehead = here->next;
			} else {
				last->next = here->next;
			}
		}
		 
		here = here->next;

	        if (freeit != NULL)
	        {
	                FREE(freeit);
			freed++;
			freeit = NULL;
	        }
	} /* end while */

	/* dump current network status to a file */
	if ((html != -1) && (statuschanged || showupalso))
	{
		dump_to_file(statusfilename, html);
	}
}

void fast_cleanup_checks()
{
	struct monitorent *here, *next;
	/* cleanup checks, and shut them down asap */

	if (numqueued == 0)
	{
		if (debug)
		{
			print_err(0, "fast_cleanup_checks w/ no checks in queue");
		}
		return;
	} 

	/* walk the current queue */
        for (here = queuehead ; here != NULL ; here=here->next)
	{
		if (debug) 
		{
			print_err(0, "in q @ sighup - %s:%d", here->checkent->hostname,
				here->checkent->type);
		}
		if (!here->started)
		{
			here->checkent->queued = 0;
			here->retval = here->checkent->lastcheck;
		} else {
			if (here->monitordata != NULL)
			{
				stop_this(here);
			}
			
		}
	}

	/* Free everything in the queue */
        for (here = queuehead ; here != NULL ; )
        {
		if (here == NULL)
			break;

		next = here->next;
		FREE(here);
		numqueued--;
		here = next;

	}
	queuehead = NULL;
}

void wakeup_checks()
{
	/* wake up checks that need timeout */
        struct monitorent *here = NULL;
	struct timeval now;
	unsigned int count;

	count = 0;
	gettimeofday(&now, NULL); /* get the current time */

	if (debug)
	{
		print_err(0, "wakeup_checks() waking up checks");
	}
        /* walk the current queue */
	here = queuehead;
        while (here != NULL)
        {
		if (count > (numqueued +10))
		{
			print_err(1, "wakeup_checks() BUG! bailing out of while() as count is too high");
			break;
		}
		if (debug)
		{
			print_err(0, "wakeup_checks() looking at %s:%s:%d", 
                                here->checkent->hostname,
                                type_to_name(here->checkent->type),
                                here->checkent->port);
		}
		if (mydifftime(here->queueat, now) >= killafter)
		{
                        print_err(0, "Killing stale check %s:%s:%d lasting %10.6fsecs",
                                here->checkent->hostname,
                                type_to_name(here->checkent->type),
                                here->checkent->port,
                                mydifftime(here->queueat, now));

			if (here->checkent->type == SYSM_TYPE_PING)
			{
				print_err(1, "BUG: Ping should never be killed!");
			} else {
				stop_this(here);
				here->retval = SYSM_KILLED;
				killed++;
				print_err(1, "possible BUG numkilled = %d -- normally should never exceed zero", killed);
			}
		} else if ((mydifftime(here->queueat,now) >= 31.0) && warnlog) {
                        print_err(0, "Possibly stale check of %s:%s:%d lasting %10.6f",
				here->checkent->hostname,
				type_to_name(here->checkent->type),
				here->checkent->port,
				mydifftime(here->queueat, now) );
		}
		here = here->next;
		count++;
		if (debug)
		{
			print_err(0, "wakeup_checks() - just moved here");
		}
	}
	if (debug)
	{
		print_err(0, "wakeup_checks() exiting");
	}
}

void write_pid_file()
{
	pid_t mypid = getpid();
	FILE *fh;
	
	if (getuid() != 0)
	{
		return;
	}
	fh = fopen(PIDFILE, "w");
	if (fh == NULL)
	{
		perror("write_pid_file:fopen");
		return;
	}
	fprintf(fh, "%d\n", mypid);
	if (fclose(fh) != 0)
	{
		perror("write_pid_file:fclose");
		return;
	}
}

/*
 * We should revoke root if it's not necessary anymore.
 * This would be done by doing a setuid(nobody) or something
 * that sort.  We should write pid file as superuser
 * or whatever is required.  Basically we only have to be
 * root to write /etc/sysmond.pid, and to create the
 * inital icmp file descriptor that we will be sending
 * our pings out.
 *
 * We should also be able to run as non-root if nothing icmp
 * is being monitored.  If we catch sighup as non-root, and 
 * reload conf file to have something imcp in it, we should
 * basically disable icmp totally, and reject those parts
 * of config file
 */
void revoke_root_if_necessary()
{
	/* getuid() if uid != 0, icmp_enabled = 0 */
	/* setuid(nobody) */
	/* seteuid(nobody) */
	
}

void 
do_watch(char *cmdname, int listenport)
{
	struct timeval one, two;
	struct hostinfo *hupdata = NULL;

	setup_client_listen(listenport);
	if (!disable_icmp)
	{
		setup_icmp_fd();
	}
	revoke_root_if_necessary();

	write_pid_file();
	while (1)
	{
		gettimeofday(&one, NULL);
		if (currenthead != NULL) 
		/* Nothing in the tree to monitor */
		{
			queue_checks();   /* queue checks ready for us */
		}
		gettimeofday(&two, NULL);
		check_runtime(one, two, "queue_checks", 1);

		gettimeofday(&one, NULL);
		wakeup_checks();  /* wakeup any "stale" checks */
		gettimeofday(&two, NULL);
		check_runtime(one, two, "wakeup_checks", 1);

		gettimeofday(&one, NULL);
		service_checks(); /* do checks that are ready for us */
		gettimeofday(&two, NULL);
		check_runtime(one, two, "service_checks", 1);

		gettimeofday(&one, NULL);
		needssleep();     /* if we need a sleep, do it here */
		gettimeofday(&two, NULL);
		check_runtime(one, two, "needssleep", 1);

		gettimeofday(&one, NULL);
		do_tree_periodic(); /* Do periodic checks/tests */
		gettimeofday(&two, NULL);
		check_runtime(one, two, "do_tree_periodic", 1);

		while (paused)
		{
			service_checks();
			wakeup_checks();
			do_tree_periodic();
			needssleep();
		}
		if (gotsighup)
		{
			/* Kill all checks currently pending */
			fast_cleanup_checks();

			/* do all the fun stuff */
		        if (debug)
			{
				print_err(0, "calling sync_after_sighup");
			}
		        hupdata = sync_after_sighup(currenthead, configfile);

		        currenthead = hupdata;

		        if (debug)
			{
				print_err(0, "resetting gotsighup");
			}
		        gotsighup = FALSE;
			statuschanged = TRUE;
		}

	}
}

/*
 */
void periodic_rewalk(struct hostinfo *here, time_t right_now, int down)
{
	if (here == NULL)
		return;
	
	if (((right_now - here->lchecktime) > (3 * queuetime)) && (warnlog))
	{
		if ((here->lchecktime != 0) && (!down))
		print_err(1, 
			"WARNING: check of %s:%s:%d not checked in %d seconds", 
			here->hostname, type_to_name(here->type), here->port, 
			(right_now-here->lchecktime));
	}
	if (here->lastcheck == SYSM_NODNS)
	{
		/* redo dns query */
		if (my_gethostbyname(here->hostname) != NULL)
			here->lastcheck = 0;
	}

	if ((here->lastcheck != SYSM_OK) || down)
	{
		periodic_rewalk(here->child, right_now, 1);
	} else {
		periodic_rewalk(here->child, right_now, 0);
	}
	periodic_rewalk(here->sibling, right_now, 0);
}

/*
 *
 */
void periodic_page(struct hostinfo  *here)
{
	if (here == NULL)
		return;
	if (here->contacted)
	{
		if ((time(NULL) - here->lastcontacted) > (pageinterval*60))
			page_someone(here);
	}
	periodic_page(here->child);
	periodic_page(here->sibling);
}

void send_sig_usr2()
{
        pid_t that_pid;
        FILE *fh;
	char buffer[256];

        fh = fopen(PIDFILE, "r");
        if (fh == NULL)
        {
                perror("open_pid_file:fopen");
                return;
        }
	
	memset(buffer,0,256);
        if (fgets(buffer, 250, fh) == NULL)
	{
		perror("send_sig_usr2:open_pid_file:fgets()");
	} else {
		that_pid = atoi(buffer);
		printf("sending (un)pause signal to sysmond process %d\n", that_pid);
		if(kill(that_pid, SIGUSR2) == -1)
		{
			perror("send_sig_usr2:kill");
		}
	}
	

        if (fclose(fh) != 0)
        {
                perror("open_pid_file:fclose");
                return;
        }
}
void send_sig_hup()
{
        pid_t that_pid;
        FILE *fh;
	char buffer[256];

        fh = fopen(PIDFILE, "r");
        if (fh == NULL)
        {
                perror("open_pid_file:fopen");
                return;
        }
	
	memset(buffer,0,256);
        if (fgets(buffer, 250, fh) == NULL)
	{
		perror("send_sig_hup:open_pid_file:fgets()");
	} else {
		that_pid = atoi(buffer);
		printf("sending reload signal to sysmond process %d\n", that_pid);
		if(kill(that_pid, SIGHUP) == -1)
		{
			perror("send_sig_hup:kill");
		}
	}
	

        if (fclose(fh) != 0)
        {
                perror("open_pid_file:fclose");
                return;
        }
}

/*
 *
 */
static void zombiecollect(void)
{ 
	/* Collect any zombies */
	while(waitpid((pid_t)-1,(int*)0,WNOHANG)>0);
}

/*
 *
 */
void do_tree_periodic()
{
	time_t now;
	
	time(&now);

	zombiecollect();

	/* walk tree and check for now valid dns entries */
	periodic_rewalk(currenthead, now, 0);

	/* walk tree and check for time to repage about sites */
	if (pageinterval != 0)
	{
		periodic_page(currenthead);
	}
}


/* The main part of the program */
int main(int argc, char **argv)
{
	int pid = 0; /* pid of child */
	int listenport = SYSMON_PORTNUM; /* (default) port to listen on */
	char myhostname[80]; /* my hostname */
        struct rlimit thislimit; /* used for resetting fd limits */
        struct timeval tv;

        gettimeofday(&tv, NULL);
        srand(tv.tv_sec+tv.tv_usec);

	/* setup the global defaults */
	set_defaults();

	ident_hash = MALLOC(0xffff, "ident_hash");
	boottime = time(NULL);

	myname = argv[0];
	time(&dnslog_last_log);

	currenthead = NULL; /* initalize it */

	gethostname(myhostname, 80); /* get my hostname */

	/* trap those signals that tell us to stop and do proper
	   cleanup */
	signal(SIGHUP, reload_config);
	signal(SIGUSR1, toggle_warnlog);
	signal(SIGUSR2, toggle_pause);
	signal(SIGINT, stop_daemon);
	signal(SIGQUIT, stop_daemon);
	signal(SIGTERM, stop_daemon);

        thislimit.rlim_cur = 1024;
        thislimit.rlim_max = 1024;

#if (defined(__FreeBSD) || defined(FreeBSD) || defined(__FreeBSD__) || \
	defined(sun))

        setrlimit(RLIMIT_NOFILE, &thislimit);
#else
        setrlimit(RLIMIT_OFILE, &thislimit);
#endif

	/* do some setting up for icmp stuff */
        if (!(icmpproto = getprotobyname("icmp")))
        {
		/* huh, what's icmp? */
		print_err(1, "sysmond:syswatch.c: unknown protocol icmp.");
		exit(1);
        }


	strcpy(configfile, CFILE);

	if (argc > 1)
	{
		cmdline(argc, argv, configfile, &listenport);
	}

	/* check if we should pause/unpause monitoring */
	if (send_pause_sig)
	{
		send_sig_usr2();
		exit(0);
	}
	badconfig = FALSE;
	currenthead = loadconfig(configfile);

	update_count();

	if (ckconfigonly)
	{
		if (badconfig)
		{
			print_err(1, "Configuration file failed parsing, will not reload config file\n");
			exit(1);
		}
		if (reload)
		{
			send_sig_hup();
		}
		exit(0);
	}

        if (currenthead == NULL)
        {
                print_err(1, "main: currenthead == NULL - nothing to monitor, exiting");
                exit(1);
        }

	/* if we're syslogging stuff, log a bootup message in syslog
	 */
	print_err(1, "booting of version %s", VERS);

	fprintf(stdout, "%s\n", VERS);
	fprintf(stdout, "%s started on %s\n", myname, myhostname);

	if (heartbeat)
	{
		send_heartbeat(myhostname);
	}

	if (dofork == 0)
	{
		pid = fork();
	}

	if ((pid == 0) || (dofork == 1))
	{
		do_watch(argv[0], listenport);
	} else {
		fprintf(stdout, "forked process as pid %d\n", pid);
	}
	exit(0);
}



int
init_socket(int listenport) {

	struct sockaddr_in name;
	int sock;
	int len;

	int on = 1;
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		perror("opening datagram socket");
		exit(2);
	}

	len = sizeof(on);
	/* use keepalives to force us to die when the remote dies */
	if ((setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&on, (int)len) == -1) ||
		(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&on, (int)len) == -1))
	{
	  	perror("init_socket:setsockopt: cannot set socket option");
	}

	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
	name.sin_port = htons(listenport);

	while (1)
	{
		/* bind it */
		if (bind(sock, (struct sockaddr*)&name, 
			sizeof(struct sockaddr_in)) < 0) 
		{
			perror("bind()");
			exit(1);
		} else  {
			break;
		}
	}
	listen(sock,2);
	return sock;

}


