/* Copyright (c) 2000
        W. M. Shandruk <walt@erudition.net>.  All rights are reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software
   must display the following acknowledgement:
        This product includes software developed by W. M. Shandruk.
4. The name of W. M. Shandruk may not be used to endorse or promote
   products derived from this software without specific prior written
   permission.

THIS SOFTWARE IS PROVIDED BY W. M. SHANDRUK ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
NO EVENT SHALL W. M. SHANDRUK BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */

#include "monitord.h"
#include "config.h"

int main (int argc, char *arga[]) {

	int i, num, interval;
	char *buf; /* All-purpose buffer */
	char *file [_MAXLINE];
	char *filename; /* config file name */

        buf = (char *) malloc ( (size_t) _BUFSIZE ); // init & zero the buffer
        filename = (char *) malloc ( (size_t) _BUFSIZE ); // init & zero the buffer
        bzero (buf, _BUFSIZE );
        bzero (filename, _BUFSIZE );

	// Set the HUP variable to zero because no SIGHUPs have been caught yet
	HUP = 0;

	interval = _INTERVAL; /* Default interval period */
	strncpy (filename, _PATH_TO_CONFIG, strlen(_PATH_TO_CONFIG)); /* Default config file */
	
	/* check if there are any arguments */
	if (argc > 1) { 
		/* Loop through all arguments passed in */
		for (i = 0; i < argc; i++) {
			/* If interval is set, grab it */
			if (strstr (arga[i], "-t") && (argc > i)) {
				strncpy (buf, arga[i+1], _BUFSIZE);
				interval = atoi (buf);
			}
			/* If a custom config is set, grab it */
			if (strstr (arga[i], "-f") && (argc > i)) {
				strncpy (buf, arga[i+1], _BUFSIZE);
				bzero ( filename, _BUFSIZE );
				strncpy (filename, buf, _BUFSIZE); 
			}
		}
	}

	// init the *file[];
	for ( i = 0; i < _MAXLINE; i++ ) {		
		file[i] = (char *) malloc ( (size_t) sizeof(char) * _BUFSIZE );
		bzero ( file[i], sizeof (file[i]) );
	}

//	file = (char **) calloc (1000, (size_t) sizeof(char) * _BUFSIZE );


	// Drop this daemon into the background
	switch (fork ()) {
		case -1: {
			warn ("couldn't fork()");
			exit(1);
		}
		case 0: {
			setsid ();
			break;
		}
		default: 
		exit(1);
	}

	/* Read the configuration file, saving it in *file[] and return the number of lines
	were read in the configuration file so we know how much we have to loop next time to 
	check for processes we're responsible for. We're going to have a separate look in which 
	we check for processes we're responsible for inside the Main loop() because we're going
	to be calling sleep() so we don't suck up processor time */

	num = read_conf ((char ***) & file, filename );

	/* Free some used up memory */
	free (buf);
	free (filename);

	loop ((char **) & file, filename, num, interval );	// Start the main loop

	exit (0); // exit if loop() breaks
}

/* This is the function that reads our configuration file and returns the number of valid
lines that have been read. This line count is important because it will define how often the
loop inside the main loop() will go around for checking for processes we're responsible for.
There will be one look within the main loop() for each process we're responsible for, which will
simplify checking */

int read_conf ( char ***file, char *filename ) {

	FILE *sfile;
	int line_count;
	char *buf;

        buf = (char *) malloc ( (size_t) _BUFSIZE ); // init & zero the buffer
        bzero (buf, sizeof (buf) );

	line_count = 0;

	// Open the configuration file for reading
	if ((sfile = fopen (filename, "r" )) == NULL) {
		syslog(LOG_ERR, "could not open %s, exiting", filename);
		printf ("monitord: couldn't open file %s, exiting\n", filename);
		exit(0);
	}

	/* Start the main reading loop. We're going to only count valid lines with line_count.
	First thing we do is grab the first line; this allows us to manage the counter
	correctly; in other words, we can put the line grabber in the loop at the end of the
	loop so that when the feof() reaches the end of the file so does the loop and the
	counter gets incremented the correct number of times */

	fgets ( buf, strlen(buf), sfile );
	while (!feof(sfile)) {

		// If the line isn't a comment, proceed
		if (!strstr (buf, "#") && (strlen(buf) > 1)) {

			// save the line into *file[]
			strncpy ( (char *) file[line_count], buf, _BUFSIZE );
//			realloc ( & file[1], (size_t) sizeof(char) * _BUFSIZE * (line_count + 1) );
			// zero out the buffer so we don't have it hold old garbage
			bzero (buf, sizeof (buf));

			line_count++; // Advance the counter
	
		}
		fgets ( buf, _MAXLINE, sfile ); // Grab next line from the file

	}
	fclose (sfile);

	return (line_count);  // Return the number of valid lines in the configuration file
}

int loop ( char **file, char *filename, int max_proc, int interval ) {

	int i, j, current_option, pid, FOUND;	

	int status;		// exit status of a child

	// These are variables we will need for finding and executing the processes
	char uid[_BUFSIZE / 7]; 	/* user ID */
//	char gid[_BUFSIZE / 7]; 	/* group ID */
	char allopt[_BUFSIZE / 7];	/* entire options line */ 
	char opt[5][_BUFSIZE / 6];	/* options array */
	char delaytime[_BUFSIZE / 7];	/* delay time for a particular proc */
	char proc[_BUFSIZE / 7];	/* current process we're checking */
	char script[_BUFSIZE / 7];	/* start up script or process name */
	char script_path[_BUFSIZE / 7];	/* path to start up script or process name */
	char param[_BUFSIZE / 7];	/* parameters to the current process */
	char var[_BUFSIZE / 6];		/* generic variable from config file */
	char value[_BUFSIZE / 6];	/* generic value from config file */
	char email[_BUFSIZE / 6];	/* admin's email */
	char eserver[_BUFSIZE / 6];	/* admin's email server */
	char delay[max_proc];		/* array for tracking delay times for processes  */

	char cmdline[_BUFSIZE];		/* entire process line from config file */
	char tmppath[_BUFSIZE];		/* temporary copy of script_path */

	options_t options;

	char *buf;		// all purpose buffer
	char *buf2;		// all purpose buffer
	char *token;		// all purpose buffer
	DIR *dirp;		// Directory Stream pointer
	struct dirent *dp; 	// Directory struct
	FILE *fp;		// File Stream pointer
	struct passwd *pw;	// Passwd DB struct

	buf = (char *) malloc ( (size_t) _BUFSIZE ); // init the all purpose buffer
	buf2 = (char *) malloc ( (size_t) _BUFSIZE ); // init another all purpose buffer
	token = (char *) malloc ( (size_t) _BUFSIZE ); // init the token buffer
	bzero (buf, sizeof (buf) );
	bzero (buf2, sizeof (buf) );
	bzero (token, sizeof (token) );

	FOUND = 0;
	
	// Main loop
	while(1) {
	
		// Catch HUP signal to reread config file
		signal (SIGHUP, (void *) sig_catch);
		if (HUP) {
			// Reload the configuration file
			max_proc = read_conf ((char ***) file, filename);
			// Set HUP var back to zero because signal was already handled
			HUP = 0;
		}

		// Reduce all delay times by interval seconds
		for ( j = 0; j < max_proc; j++ ) {
			if ( delay[j] > 0 )
				{ delay[j] -= interval; }
			else
				{ delay[j] = 0; }
		}

		// Let's run through the maximum number of proceses that we are responsible for
		for ( i = 0; i < max_proc; i++ ) {		

			/*
			Breaks up the *file string to extract each bit we need, and save it into
			separate strings
			*/

//			sscanf ( (char *) file[i], "%s %s %s", var, filler, value );


			/*
			We're first going to check for the general configuration lines
			that set the admin's email, status update time, and so on. If we
			pick these up, we "continue" so the later strtok() lines are
			skipped until we get past the generation configuration stuff
			*/

			strncpy (buf, file[i], _BUFSIZE); /* copy temporary copy of line */
			strncpy (var, strtok(buf, " =\t"), sizeof(var));
			strncpy (value, strtok(NULL, " =\t"), sizeof(value));

			/* Each line has a \n at the end which must be removed,
			so we set it to NULL */
			value[strlen(value) - 1] = '\0';

			/* Grab admin's email */
			if (strncmp (var, "email", sizeof(var)) == 0) {
				strncpy (email, value, sizeof(email));
				continue;
			}

			/* Grab admin's email server */
			if (strncmp (var, "smtp-server", sizeof(var)) == 0) {
				strncpy (eserver, value, sizeof(eserver));
				continue;
			}

			/* Grab status update interval */
/*			if (strncmp (var, "interval", sizeof(var)) == 0) {
				interval = atoi (value) / interval;
				continue;
			}
*/
			bzero (buf, _BUFSIZE);

			/* grab daemon configuration lines */
			strncpy (cmdline, file[i], sizeof(cmdline)); /* copy temporary copy of line */
			strncpy (uid, strtok(cmdline, " \t"), sizeof(uid));
//			strncpy (gid, strtok(NULL, " \t"), sizeof(gid));
			strncpy (allopt, strtok(NULL, " \t"), sizeof(allopt));
			strncpy (delaytime, strtok(NULL, " \t"), sizeof(delaytime));
			strncpy (proc, strtok(NULL, " \t"), sizeof(proc));
			strncpy (script_path, strtok(NULL, " \t"), sizeof(script_path));


			/* The param element - the command parameters - must be constructed
			because they are composed of multiple tokens, so, they must be
			spliced into a single string. This is done with a loop that uses
			strncat() to concatenate the individuals tokens. However, we
			must, at the very first, zero out the param string because it
			being constructed by concatenation, it'll have its previous
			contents appended if we don't wipe it, unlike where with strncpy()
			the previous contents gets wiped */

			bzero (param, sizeof(param));
			while ((token = strtok(NULL, " \t"))) {
				strncat (param, " ", sizeof(param));
				strncat (param, token, sizeof(param));
			}

			/* Each line has a \n at the end which must be removed
			so we set it to NULL */
			param[strlen(param) - 1] = '\0';

			/* The script is the last part of script_path and is extracted
			with a strtok() loop */
			strncpy (tmppath, script_path, sizeof(tmppath)); /* tmp path var */
			token = strtok (tmppath, "/");
			/* Loop and extract the script from the script_path */
			do {
				strncpy (script, token, sizeof(script));
			} while ((token = strtok (NULL, "/")));

			/* parse the *opt[] array to grab all of the options */
			token = strtok (allopt, " \t,");
			current_option = 0;
			do {
				strncpy (opt[current_option], token, sizeof(opt[current_option]));
				current_option++;
			} while ((token = strtok (NULL, ",")));

			/* Place the options into useful variables */
			options.isauto = FALSE;
			options.alert = FALSE;
			for (j = 0; j < current_option; j++) {
				if (strncmp (opt[j], "auto", sizeof(opt[j])) == 0) options.isauto = TRUE;	
				if (strncmp (opt[j], "alert", sizeof(opt[j])) == 0) options.alert = TRUE;
/*				if (strncmp (opt[j], "status", sizeof(opt[j])) == 0) options.status = TRUE;
				else { options.status = FALSE; }
*/
			}

			/* If there are no parameters, then \n will end up on
			script_path, so when script is extracted, it needs to be
			replaced with \0 */
			if (!strlen(param)) script[strlen(script) - 1] = '\0';

//			printf ("(%s) (%s) (%s) (%s) (%s) (%s) (%s)\n", uid, gid, allopt, proc, script_path, script, param);

			// Open the /proc dir so we can run down the PID dirs
			dirp = (DIR *) opendir ("/proc");

			/* Loop around until we reach the end of the /proc dir. readdir() will keep
			reading a new directory/file in /proc on each cycle and dump its info into
			the dp structure */ 
			while ( (dp = (struct dirent *) readdir(dirp)) != NULL ) {
				// Create the path of the status file in the PID dir
				sprintf ( buf, "/proc/%s/status",dp->d_name );
				// Open the status file
				if ((fp = fopen (buf, "r")) != NULL) {
					// Grab the line of status file
					fgets ( buf, _BUFSIZE, fp );
					// grab the first token on the line, which is the bin name
					strtok ( buf, " " );					
//					printf ( "%s:%s\n", dp->d_name, buf );
					fclose (fp);
				}
				else {
//					printf("Couldn't open %s\n", buf);
				}
				// Set the FOUND flag if the process we're checking for is found
				if (!strncmp (buf, proc, sizeof(buf))) FOUND = TRUE;
			}
			closedir (dirp);   // Close the /proc directory

			// If the process wasn't found in the process listing then start it
			if (!FOUND && options.isauto && !delay[i]) {

				/* Email admin that the service has died, if the "mail"
				option has been set in the options */
				if (options.alert) {
					bzero (buf, sizeof(buf));
					bzero (buf2, sizeof(buf2));
					sprintf (buf, "[%s] Service \"%s\" has died\n", getdate(), proc);
					sprintf (buf2, "(monitord) SYSTEM ALERT, \"%s\" has died\n", proc);
					mail (email, eserver, buf2, buf);
				}

				/* Set a delay for which not check a process to allow it to fully start up */
				delay[i] = atoi (delaytime);
				

				if ((pid = fork() ) < 0) {
//					printf ("Problem creating child process for system()\n");
				}
				if (pid > 0) {
//					Printf ("Start child process\n");
				}
				else if (pid == 0) {
					pw = getpwnam (uid);
					/* Set the UID/GID of calling fork() so it starts with the
					proper owner */
//					printf ("%s %s %s %s %s\n", uid, gid, allopt, script_path, param);
					seteuid (pw->pw_uid);
					setegid (pw->pw_gid);
					/* Format the execution string to include parameters that
					are specified to be passed to the daemon we are responsible
					for */
					if ((strlen (param) != 1)) {
						sprintf (buf, "%s %s", script_path, param);
					}
					else {
						sprintf (buf, "%s", script_path);
					} 
					/* Actually restart the daemon */
					if (system (buf) != -1) {
						syslog(LOG_NOTICE, "restarted \"%s\" using \"%s %s\"\n", proc, script_path, param);
						
						/* Email the admin that the service has been
						restarted if "mail" option is set */
						if (options.alert) {
							bzero (buf, sizeof(buf));
							sprintf (buf, "[%s] restarted \"%s\" using \"%s %s\"\n", getdate(), proc, script_path, param);
							sprintf (buf2, "(monitord) \"%s\" restarted\n", proc);
							mail (email, eserver, buf2, buf);
						}
						exit(0);
					} else {
						syslog(LOG_NOTICE, "unable to restart \"%s\"\n", proc);
						
						/* Email the admin that the service has not
						been able to be	restarted if "mail" option
						is set */
						if (options.alert) {
							bzero (buf, sizeof(buf));
							sprintf (buf, "[%s] unable to restart \"%s\"\n", getdate(), proc);
							sprintf (buf2, "(monitord) SYSTEM ALERT: \"%s\" unable to restart\n", proc);
							mail (email, eserver, buf2, buf);
						}
						exit(0);
					}
				}
			} /* End of FOUND loop */

			/* Reset the FOUND variable so that it can be used to locate
			the next process we are responsible for */
			FOUND = 0;
		} /* End of max_proc loop */

		// Put the process to sleep for a bit so it doesn't suck up CPU cycles
		waitpid ( -1, &status, WNOHANG );
		sleep (interval);
	} /* End of main loop */
	
	return (1);
}

void sig_catch () {

	syslog(LOG_NOTICE, "reloaded\n"); // Log the reload event
	printf ("[%s] monitord config reloaded\n", getdate());
	HUP = 1;
	return;
	
}

char *getdate () {

	struct timeval *tp;
	struct timezone *tzp;
	time_t *time;
	char *buf;

	buf = (char *) malloc ( (size_t) _BUFSIZE ); // init the time buffer
	tp = (struct timeval *) malloc ( (size_t) sizeof (struct timeval) ); // init the time buffer
	tzp = (struct timezone *) malloc ( (size_t) sizeof (struct timezone) ); // init the timezone buffer
	time = (time_t *) malloc ( (size_t) sizeof (time_t) ); // init the timezone buffer

	/* Get time of day in seconds since Epoch */
	gettimeofday (tp, tzp);

	/* Save time of day in pointer variable */
	*time = tp->tv_sec;

	/* Convert the Epoch time to text and save in *buf */
	strncpy (buf, ctime(time), _BUFSIZE);

	/* Chop off '\n' from the end of the *buf */
	*(buf + (strlen(buf) - 1)) = '\0';

	/* Free everything you can */
	free ((struct timeval *) tp);
	free ((struct timzone *) tzp);
	free ((time_t *) time);	

	return( buf );
}


syntax highlighted by Code2HTML, v. 0.9.1