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