/* Copyright (c) 2000 W. M. Shandruk . 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" static int HUP; 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: // The parent exits exit(0); } /* 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 */ signed 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; memset(delay, 0, max_proc*sizeof(char)); // 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, MIN(strlen(buf),strlen(proc)))) 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] < interval) { /* 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", getdate0(), 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", getdate0(), 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", getdate0(), 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", getdate0()); HUP = 1; return; } char *getdate0 () { 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 ); }