1 /* Copyright (c) 2000
2         W. M. Shandruk <walt@erudition.net>.  All rights are reserved.
3 
4 Redistribution and use in source and binary forms, with or without
5 modification, are permitted provided that the following conditions
6 are met:
7 1. Redistributions of source code must retain the above copyright
8    notice, this list of conditions and the following disclaimer.
9 2. Redistributions in binary form must reproduce the above copyright
10    notice, this list of conditions and the following disclaimer in the
11    documentation and/or other materials provided with the distribution.
12 3. All advertising materials mentioning features or use of this software
13    must display the following acknowledgement:
14         This product includes software developed by W. M. Shandruk.
15 4. The name of W. M. Shandruk may not be used to endorse or promote
16    products derived from this software without specific prior written
17    permission.
18 
19 THIS SOFTWARE IS PROVIDED BY W. M. SHANDRUK ``AS IS'' AND ANY EXPRESS OR
20 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
22 NO EVENT SHALL W. M. SHANDRUK BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25 OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
27 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
28 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */
29 
30 #include "monitord.h"
31 #include "config.h"
32 
33 static int HUP;
34 
main(int argc,char * arga[])35 int main (int argc, char *arga[]) {
36 
37 	int i, num, interval;
38 	char *buf; /* All-purpose buffer */
39 	char *file [_MAXLINE];
40 	char *filename; /* config file name */
41 
42         buf = (char *) malloc ( (size_t) _BUFSIZE ); // init & zero the buffer
43         filename = (char *) malloc ( (size_t) _BUFSIZE ); // init & zero the buffer
44         bzero (buf, _BUFSIZE );
45         bzero (filename, _BUFSIZE );
46 
47 	// Set the HUP variable to zero because no SIGHUPs have been caught yet
48 	HUP = 0;
49 
50 	interval = _INTERVAL; /* Default interval period */
51 	strncpy (filename, _PATH_TO_CONFIG, strlen(_PATH_TO_CONFIG)); /* Default config file */
52 
53 	/* check if there are any arguments */
54 	if (argc > 1) {
55 		/* Loop through all arguments passed in */
56 		for (i = 0; i < argc; i++) {
57 			/* If interval is set, grab it */
58 			if (strstr (arga[i], "-t") && (argc > i)) {
59 				strncpy (buf, arga[i+1], _BUFSIZE);
60 				interval = atoi (buf);
61 			}
62 			/* If a custom config is set, grab it */
63 			if (strstr (arga[i], "-f") && (argc > i)) {
64 				strncpy (buf, arga[i+1], _BUFSIZE);
65 				bzero ( filename, _BUFSIZE );
66 				strncpy (filename, buf, _BUFSIZE);
67 			}
68 		}
69 	}
70 
71 	// init the *file[];
72 	for ( i = 0; i < _MAXLINE; i++ ) {
73 		file[i] = (char *) malloc ( (size_t) sizeof(char) * _BUFSIZE );
74 		bzero ( file[i], sizeof (*file[i]) );
75 	}
76 
77 //	file = (char **) calloc (1000, (size_t) sizeof(char) * _BUFSIZE );
78 
79 
80 	// Drop this daemon into the background
81 	switch (fork ()) {
82 		case -1: {
83 			warn ("couldn't fork()");
84 			exit(1);
85 		}
86 		case 0: {
87 			setsid ();
88 			break;
89 		}
90 		default: // The parent exits
91 		exit(0);
92 	}
93 
94 	/* Read the configuration file, saving it in *file[] and return the number of lines
95 	were read in the configuration file so we know how much we have to loop next time to
96 	check for processes we're responsible for. We're going to have a separate look in which
97 	we check for processes we're responsible for inside the Main loop() because we're going
98 	to be calling sleep() so we don't suck up processor time */
99 
100 	num = read_conf ((char ***) & file, filename );
101 
102 	/* Free some used up memory */
103 	free (buf);
104 	free (filename);
105 
106 	loop ((char **) & file, filename, num, interval );	// Start the main loop
107 
108 	exit (0); // exit if loop() breaks
109 }
110 
111 /* This is the function that reads our configuration file and returns the number of valid
112 lines that have been read. This line count is important because it will define how often the
113 loop inside the main loop() will go around for checking for processes we're responsible for.
114 There will be one look within the main loop() for each process we're responsible for, which will
115 simplify checking */
116 
read_conf(char *** file,char * filename)117 int read_conf ( char ***file, char *filename ) {
118 
119 	FILE *sfile;
120 	int line_count;
121 	char *buf;
122 
123         buf = (char *) malloc ( (size_t) _BUFSIZE ); // init & zero the buffer
124         bzero (buf, sizeof (*buf) );
125 
126 	line_count = 0;
127 
128 	// Open the configuration file for reading
129 	if ((sfile = fopen (filename, "r" )) == NULL) {
130 		syslog(LOG_ERR, "could not open %s, exiting", filename);
131 		printf ("monitord: couldn't open file %s, exiting\n", filename);
132 		exit(0);
133 	}
134 
135 	/* Start the main reading loop. We're going to only count valid lines with line_count.
136 	First thing we do is grab the first line; this allows us to manage the counter
137 	correctly; in other words, we can put the line grabber in the loop at the end of the
138 	loop so that when the feof() reaches the end of the file so does the loop and the
139 	counter gets incremented the correct number of times */
140 
141 	fgets ( buf, strlen(buf), sfile );
142 	while (!feof(sfile)) {
143 
144 		// If the line isn't a comment, proceed
145 		if (!strstr (buf, "#") && (strlen(buf) > 1)) {
146 
147 			// save the line into *file[]
148 			strncpy ( (char *) file[line_count], buf, _BUFSIZE );
149 //			realloc ( & file[1], (size_t) sizeof(char) * _BUFSIZE * (line_count + 1) );
150 			// zero out the buffer so we don't have it hold old garbage
151 			bzero (buf, sizeof (*buf));
152 
153 			line_count++; // Advance the counter
154 
155 		}
156 		fgets ( buf, _MAXLINE, sfile ); // Grab next line from the file
157 
158 	}
159 	fclose (sfile);
160 
161 	return (line_count);  // Return the number of valid lines in the configuration file
162 }
163 
loop(char ** file,char * filename,int max_proc,int interval)164 int loop ( char **file, char *filename, int max_proc, int interval ) {
165 
166 	int i, j, current_option, pid, FOUND;
167 
168 	int status;		// exit status of a child
169 
170 	// These are variables we will need for finding and executing the processes
171 	char uid[_BUFSIZE / 7]; 	/* user ID */
172 //	char gid[_BUFSIZE / 7]; 	/* group ID */
173 	char allopt[_BUFSIZE / 7];	/* entire options line */
174 	char opt[5][_BUFSIZE / 6];	/* options array */
175 	char delaytime[_BUFSIZE / 7];	/* delay time for a particular proc */
176 	char proc[_BUFSIZE / 7];	/* current process we're checking */
177 	char script[_BUFSIZE / 7];	/* start up script or process name */
178 	char script_path[_BUFSIZE / 7];	/* path to start up script or process name */
179 	char param[_BUFSIZE / 7];	/* parameters to the current process */
180 	char var[_BUFSIZE / 6];		/* generic variable from config file */
181 	char value[_BUFSIZE / 6];	/* generic value from config file */
182 	char email[_BUFSIZE / 6];	/* admin's email */
183 	char eserver[_BUFSIZE / 6];	/* admin's email server */
184 	signed char delay[max_proc];		/* array for tracking delay times for processes  */
185 
186 	char cmdline[_BUFSIZE];		/* entire process line from config file */
187 	char tmppath[_BUFSIZE];		/* temporary copy of script_path */
188 
189 	options_t options;
190 
191 	char *buf;		// all purpose buffer
192 	char *buf2;		// all purpose buffer
193 	char *token;		// all purpose buffer
194 	DIR *dirp;		// Directory Stream pointer
195 	struct dirent *dp; 	// Directory struct
196 	FILE *fp;		// File Stream pointer
197 	struct passwd *pw;	// Passwd DB struct
198 
199 	buf = (char *) malloc ( (size_t) _BUFSIZE ); // init the all purpose buffer
200 	buf2 = (char *) malloc ( (size_t) _BUFSIZE ); // init another all purpose buffer
201 	token = (char *) malloc ( (size_t) _BUFSIZE ); // init the token buffer
202 	bzero (buf, sizeof (*buf) );
203 	bzero (buf2, sizeof (*buf) );
204 	bzero (token, sizeof (*token) );
205 
206 	FOUND = 0;
207 
208 	memset(delay, 0, max_proc*sizeof(char));
209 
210 	// Main loop
211 	while(1) {
212 
213 		// Catch HUP signal to reread config file
214 		signal (SIGHUP, (void *) sig_catch);
215 		if (HUP) {
216 			// Reload the configuration file
217 			max_proc = read_conf ((char ***) file, filename);
218 			// Set HUP var back to zero because signal was already handled
219 			HUP = 0;
220 		}
221 
222 		// Reduce all delay times by interval seconds
223 		for ( j = 0; j < max_proc; j++ ) {
224 			if ( delay[j] > 0 )
225 				{ delay[j] -= interval; }
226 			else
227 				{ delay[j] = 0; }
228 		}
229 
230 		// Let's run through the maximum number of proceses that we are responsible for
231 		for ( i = 0; i < max_proc; i++ ) {
232 
233 			/*
234 			Breaks up the *file string to extract each bit we need, and save it into
235 			separate strings
236 			*/
237 
238 //			sscanf ( (char *) file[i], "%s %s %s", var, filler, value );
239 
240 
241 			/*
242 			We're first going to check for the general configuration lines
243 			that set the admin's email, status update time, and so on. If we
244 			pick these up, we "continue" so the later strtok() lines are
245 			skipped until we get past the generation configuration stuff
246 			*/
247 
248 			strncpy (buf, file[i], _BUFSIZE); /* copy temporary copy of line */
249 			strncpy (var, strtok(buf, " =\t"), sizeof(var));
250 			strncpy (value, strtok(NULL, " =\t"), sizeof(value));
251 
252 			/* Each line has a \n at the end which must be removed,
253 			so we set it to NULL */
254 			value[strlen(value) - 1] = '\0';
255 
256 			/* Grab admin's email */
257 			if (strncmp (var, "email", sizeof(var)) == 0) {
258 				strncpy (email, value, sizeof(email));
259 				continue;
260 			}
261 
262 			/* Grab admin's email server */
263 			if (strncmp (var, "smtp-server", sizeof(var)) == 0) {
264 				strncpy (eserver, value, sizeof(eserver));
265 				continue;
266 			}
267 
268 			/* Grab status update interval */
269 /*			if (strncmp (var, "interval", sizeof(var)) == 0) {
270 				interval = atoi (value) / interval;
271 				continue;
272 			}
273 */
274 			bzero (buf, _BUFSIZE);
275 
276 			/* grab daemon configuration lines */
277 			strncpy (cmdline, file[i], sizeof(cmdline)); /* copy temporary copy of line */
278 			strncpy (uid, strtok(cmdline, " \t"), sizeof(uid));
279 //			strncpy (gid, strtok(NULL, " \t"), sizeof(gid));
280 			strncpy (allopt, strtok(NULL, " \t"), sizeof(allopt));
281 			strncpy (delaytime, strtok(NULL, " \t"), sizeof(delaytime));
282 			strncpy (proc, strtok(NULL, " \t"), sizeof(proc));
283 			strncpy (script_path, strtok(NULL, " \t"), sizeof(script_path));
284 
285 
286 			/* The param element - the command parameters - must be constructed
287 			because they are composed of multiple tokens, so, they must be
288 			spliced into a single string. This is done with a loop that uses
289 			strncat() to concatenate the individuals tokens. However, we
290 			must, at the very first, zero out the param string because it
291 			being constructed by concatenation, it'll have its previous
292 			contents appended if we don't wipe it, unlike where with strncpy()
293 			the previous contents gets wiped */
294 
295 			bzero (param, sizeof(param));
296 			while ((token = strtok(NULL, " \t"))) {
297 				strncat (param, " ", sizeof(param));
298 				strncat (param, token, sizeof(param));
299 			}
300 
301 			/* Each line has a \n at the end which must be removed
302 			so we set it to NULL */
303 			param[strlen(param) - 1] = '\0';
304 
305 			/* The script is the last part of script_path and is extracted
306 			with a strtok() loop */
307 			strncpy (tmppath, script_path, sizeof(tmppath)); /* tmp path var */
308 			token = strtok (tmppath, "/");
309 			/* Loop and extract the script from the script_path */
310 			do {
311 				strncpy (script, token, sizeof(script));
312 			} while ((token = strtok (NULL, "/")));
313 
314 			/* parse the *opt[] array to grab all of the options */
315 			token = strtok (allopt, " \t,");
316 			current_option = 0;
317 			do {
318 				strncpy (opt[current_option], token, sizeof(opt[current_option]));
319 				current_option++;
320 			} while ((token = strtok (NULL, ",")));
321 
322 			/* Place the options into useful variables */
323 			options.isauto = FALSE;
324 			options.alert = FALSE;
325 			for (j = 0; j < current_option; j++) {
326 				if (strncmp (opt[j], "auto", sizeof(opt[j])) == 0) options.isauto = TRUE;
327 				if (strncmp (opt[j], "alert", sizeof(opt[j])) == 0) options.alert = TRUE;
328 /*				if (strncmp (opt[j], "status", sizeof(opt[j])) == 0) options.status = TRUE;
329 				else { options.status = FALSE; }
330 */
331 			}
332 
333 			/* If there are no parameters, then \n will end up on
334 			script_path, so when script is extracted, it needs to be
335 			replaced with \0 */
336 			if (!strlen(param)) script[strlen(script) - 1] = '\0';
337 
338 //			printf ("(%s) (%s) (%s) (%s) (%s) (%s) (%s)\n", uid, gid, allopt, proc, script_path, script, param);
339 
340 			// Open the /proc dir so we can run down the PID dirs
341 			dirp = (DIR *) opendir ("/proc");
342 
343 			/* Loop around until we reach the end of the /proc dir. readdir() will keep
344 			reading a new directory/file in /proc on each cycle and dump its info into
345 			the dp structure */
346 			while ( (dp = (struct dirent *) readdir(dirp)) != NULL ) {
347 				// Create the path of the status file in the PID dir
348 				sprintf ( buf, "/proc/%s/status",dp->d_name );
349 				// Open the status file
350 				if ((fp = fopen (buf, "r")) != NULL) {
351 					// Grab the line of status file
352 					fgets ( buf, _BUFSIZE, fp );
353 					// grab the first token on the line, which is the bin name
354 					strtok ( buf, " " );
355 //					printf ( "%s:%s\n", dp->d_name, buf );
356 					fclose (fp);
357 				}
358 				else {
359 //					printf("Couldn't open %s\n", buf);
360 				}
361 				// Set the FOUND flag if the process we're checking for is found
362 				if (!strncmp (buf, proc, MIN(strlen(buf),strlen(proc)))) FOUND = TRUE;
363 			}
364 			closedir (dirp);   // Close the /proc directory
365 
366 			// If the process wasn't found in the process listing then start it
367 			if (!FOUND && options.isauto && delay[i] < interval) {
368 
369 				/* Email admin that the service has died, if the "mail"
370 				option has been set in the options */
371 				if (options.alert) {
372 					bzero (buf, sizeof(*buf));
373 					bzero (buf2, sizeof(*buf2));
374 					sprintf (buf, "[%s] Service \"%s\" has died\n", getdate0(), proc);
375 					sprintf (buf2, "(monitord) SYSTEM ALERT, \"%s\" has died\n", proc);
376 					mail (email, eserver, buf2, buf);
377 				}
378 
379 				/* Set a delay for which not check a process to allow it to fully start up */
380 				delay[i] = atoi (delaytime);
381 
382 
383 				if ((pid = fork() ) < 0) {
384 //					printf ("Problem creating child process for system()\n");
385 				}
386 				if (pid > 0) {
387 //					Printf ("Start child process\n");
388 				}
389 				else if (pid == 0) {
390 					pw = getpwnam (uid);
391 					/* Set the UID/GID of calling fork() so it starts with the
392 					proper owner */
393 //					printf ("%s %s %s %s %s\n", uid, gid, allopt, script_path, param);
394 					seteuid (pw->pw_uid);
395 					setegid (pw->pw_gid);
396 					/* Format the execution string to include parameters that
397 					are specified to be passed to the daemon we are responsible
398 					for */
399 					if ((strlen (param) != 1)) {
400 						sprintf (buf, "%s %s", script_path, param);
401 					}
402 					else {
403 						sprintf (buf, "%s", script_path);
404 					}
405 					/* Actually restart the daemon */
406 					if (system (buf) != -1) {
407 						syslog(LOG_NOTICE, "restarted \"%s\" using \"%s %s\"\n", proc, script_path, param);
408 
409 						/* Email the admin that the service has been
410 						restarted if "mail" option is set */
411 						if (options.alert) {
412 							bzero (buf, sizeof(*buf));
413 							sprintf (buf, "[%s] restarted \"%s\" using \"%s %s\"\n", getdate0(), proc, script_path, param);
414 							sprintf (buf2, "(monitord) \"%s\" restarted\n", proc);
415 							mail (email, eserver, buf2, buf);
416 						}
417 						exit(0);
418 					} else {
419 						syslog(LOG_NOTICE, "unable to restart \"%s\"\n", proc);
420 
421 						/* Email the admin that the service has not
422 						been able to be	restarted if "mail" option
423 						is set */
424 						if (options.alert) {
425 							bzero (buf, sizeof(*buf));
426 							sprintf (buf, "[%s] unable to restart \"%s\"\n", getdate0(), proc);
427 							sprintf (buf2, "(monitord) SYSTEM ALERT: \"%s\" unable to restart\n", proc);
428 							mail (email, eserver, buf2, buf);
429 						}
430 						exit(0);
431 					}
432 				}
433 			} /* End of FOUND loop */
434 
435 			/* Reset the FOUND variable so that it can be used to locate
436 			the next process we are responsible for */
437 			FOUND = 0;
438 		} /* End of max_proc loop */
439 
440 		// Put the process to sleep for a bit so it doesn't suck up CPU cycles
441 		waitpid ( -1, &status, WNOHANG );
442 		sleep (interval);
443 	} /* End of main loop */
444 
445 	return (1);
446 }
447 
sig_catch()448 void sig_catch () {
449 
450 	syslog(LOG_NOTICE, "reloaded\n"); // Log the reload event
451 	printf ("[%s] monitord config reloaded\n", getdate0());
452 	HUP = 1;
453 	return;
454 
455 }
456 
getdate0()457 char *getdate0 () {
458 
459 	struct timeval *tp;
460 	struct timezone *tzp;
461 	time_t *time;
462 	char *buf;
463 
464 	buf = (char *) malloc ( (size_t) _BUFSIZE ); // init the time buffer
465 	tp = (struct timeval *) malloc ( (size_t) sizeof (struct timeval) ); // init the time buffer
466 	tzp = (struct timezone *) malloc ( (size_t) sizeof (struct timezone) ); // init the timezone buffer
467 	time = (time_t *) malloc ( (size_t) sizeof (time_t) ); // init the timezone buffer
468 
469 	/* Get time of day in seconds since Epoch */
470 	gettimeofday (tp, tzp);
471 
472 	/* Save time of day in pointer variable */
473 	*time = tp->tv_sec;
474 
475 	/* Convert the Epoch time to text and save in *buf */
476 	strncpy (buf, ctime(time), _BUFSIZE);
477 
478 	/* Chop off '\n' from the end of the *buf */
479 	*(buf + (strlen(buf) - 1)) = '\0';
480 
481 	/* Free everything you can */
482 	free ((struct timeval *) tp);
483 	free ((struct timzone *) tzp);
484 	free ((time_t *) time);
485 
486 	return( buf );
487 }
488