1 /* Copyright (C) 2007-2009 Hendrik Baecker <andurin@process-zero.de>
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 as
5 * published by the Free Software Foundation;
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
15 */
16 
17 #include "../include/config.h"
18 #include "../include/pnp.h"
19 
20 typedef void (*sighandler_t)(int);
21 
22 void *processfile(void *);
23 static void *exit_handler_mem(void *);
24 extern int process_arguments(int, char **);
25 extern void process_configfile(char *);
26 extern void check_sig(int);
27 extern int prepare_vars(void);
28 extern int drop_privileges(char *, char *);
29 extern sighandler_t handle_signal(int, sighandler_t);
30 extern int is_file(const struct dirent *d);
31 extern int check_needed_config_options();
32 
33 double getload(int);
34 
35 static int thread_counter = 0;
36 int max_threads = 5;
37 int daemon_mode = FALSE;
38 int use_syslog = TRUE;
39 int loglevel = 0;
40 int max_logfile_size = 10485760; /* default 10Mbyte */
41 int use_load_threshold = FALSE;
42 int we_should_stop = FALSE;
43 int sleeptime = 15;
44 int identmyself = TRUE;
45 double load_threshold = 10.0;
46 
47 extern int sighup_detected;
48 
49 char *command, *command_args, *user, *group, *pidfile;
50 char *macro_x[CONFIG_OPT_COUNT];
51 char *log_file, *log_type;
52 char *config_file = NULL;
53 
54 const char *directory = NULL;
55 const char progname[5] = "npcd";
56 
start_daemon(const char * log_name,int facility)57 static void start_daemon(const char *log_name, int facility) {
58 	int i;
59 	pid_t pid;
60 
61 	/* Kill parent after for to get a waise */
62 	if ((pid = fork()) != 0)
63 		exit(EXIT_SUCCESS);
64 
65 	/* When dropping privileges from root, the `setgroups` call will
66 	* remove any extraneous groups. If we don't call this, then
67 	* even though our uid has dropped, we may still have groups
68 	* that enable us to do super-user things. This will fail if we
69 	* aren't root, so don't bother checking the return value, this
70 	* is just done as an optimistic privilege dropping function.
71 	*/
72 	setgroups(0, NULL);
73 
74 	/* Get this waise to sessionleader */
75 	if (setsid() < 0) {
76 		printf("%s could not get sessionleader\n", log_name);
77 		exit(EXIT_FAILURE);
78 	}
79 
80 	/* Ignore SIGHUP */
81 	handle_signal(SIGHUP, SIG_IGN);
82 
83 	/* terminate child */
84 	if ((pid = fork()) != 0)
85 		exit(EXIT_SUCCESS);
86 
87 	/* for core dump handling and better unmounting behavior */
88 	/* Its return should not be ignored.
89 	 * npcd.c:79: warning: ignoring return value of ‘chdir’ */
90 	if (chdir("/") != 0)
91 		exit(EXIT_FAILURE);
92 
93 	/* change umask to defined value - be independet from parent umask */
94 	umask(002);
95 
96 	/* close all possible file handles */
97 	for (i = sysconf(_SC_OPEN_MAX); i > 0; i--)
98 		close(i);
99 
100 	/* to hear the daemon you are calling... use syslog */
101 	if (use_syslog == TRUE && log_name != NULL)
102 		openlog(log_name, LOG_PID | LOG_CONS | LOG_NDELAY, facility);
103 
104         /* close existing stdin, stdout, stderr */
105         close(0);
106         close(1);
107         close(2);
108 
109         /* re-open stdin, stdout, stderr with known values */
110         open("/dev/null",O_RDONLY);
111         open("/dev/null",O_WRONLY);
112         open("/dev/null",O_WRONLY);
113 
114 }
115 
main(int argc,char ** argv)116 int main(int argc, char **argv) {
117 
118 	int i = 0;
119 	int filecounter = 0, pthread_ret = 0;
120 	double load;
121 	char buffer[MAX_LOGMESSAGE_SIZE];
122 
123 	FILE *fppid = NULL;
124 
125 	struct dirent **namelist;
126 
127 	load = 0.0;
128 
129 	if (process_arguments(argc, argv) == EXIT_FAILURE)
130 		exit(EXIT_FAILURE);
131 
132 	process_configfile(config_file);
133 
134 	if (loglevel == -1) {
135 		printf("DEBUG: Config File = %s\n", config_file);
136 		printf("CONFIG_OPT_LOGTYPE = %s\n", macro_x[CONFIG_OPT_LOGTYPE]);
137 		printf("CONFIG_OPT_LOGFILE = %s\n", macro_x[CONFIG_OPT_LOGFILE]);
138 		printf("CONFIG_OPT_LOGFILESIZE = %s\n", macro_x[CONFIG_OPT_LOGFILESIZE]);
139 		printf("CONFIG_OPT_LOGLEVEL = %s\n", macro_x[CONFIG_OPT_LOGLEVEL]);
140 		printf("CONFIG_OPT_SCANDIR = %s\n", macro_x[CONFIG_OPT_SCANDIR]);
141 		printf("CONFIG_OPT_RUNCMD = %s\n", macro_x[CONFIG_OPT_RUNCMD]);
142 		printf("CONFIG_OPT_RUNCMD_ARG = %s\n", macro_x[CONFIG_OPT_RUNCMD_ARG]);
143 		printf("CONFIG_OPT_MAXTHREADS = %s\n", macro_x[CONFIG_OPT_MAXTHREADS]);
144 		printf("CONFIG_OPT_LOAD = %s\n", macro_x[CONFIG_OPT_LOAD]);
145 		printf("CONFIG_OPT_USER = %s\n", macro_x[CONFIG_OPT_USER]);
146 		printf("CONFIG_OPT_GROUP = %s\n", macro_x[CONFIG_OPT_GROUP]);
147 		printf("CONFIG_OPT_PIDFILE = %s\n", macro_x[CONFIG_OPT_PIDFILE]);
148 		printf("CONFIG_OPT_SLEEPTIME = %s\n", macro_x[CONFIG_OPT_SLEEPTIME]);
149 		printf("CONFIG_OPT_IDENTMYSELF = %s\n", macro_x[CONFIG_OPT_IDENTMYSELF]);
150 		printf("---------------------------\n");
151 		if (check_needed_config_options() != 0) {
152 			printf("There is an Error! Exiting...\n");
153 			exit(EXIT_FAILURE);
154 		}
155 	}
156 
157 	if (prepare_vars() != 0)
158 		exit(EXIT_FAILURE);
159 	if (loglevel == -1)
160 		printf("DEBUG: load_threshold is %s - ('%f')\n",
161 				use_load_threshold ? "enabled" : "disabled", load_threshold);
162 
163 	pthread_t th[max_threads];
164 	for (i=0;i<max_threads;i++){
165 		th[i] = (pthread_t) NULL;
166 	}
167 	i = 0;
168 
169 	/* Nice point for another function to set
170 	 * the internal vars from macro_x[] */
171 
172 	/* Start in Daemon Mode or in foreground? */
173 	if (daemon_mode == TRUE)
174 		start_daemon("NPCD", LOG_LOCAL0);
175 
176 	else if (use_syslog)
177 		openlog("NPCD", LOG_PID | LOG_CONS | LOG_NDELAY, LOG_LOCAL0);
178 
179 	/* Create PID File or exit on failure */
180 	if (daemon_mode == TRUE && sighup_detected == FALSE) {
181 		fppid = fopen(pidfile, "w");
182 
183 		if (fppid == NULL) {
184 			printf("Could not open pidfile '%s': %s\n", pidfile,
185 					strerror(errno));
186 			exit(EXIT_FAILURE);
187 		} else {
188 			fprintf(fppid, "%d", getpid());
189 			fclose(fppid);
190 		}
191 	}
192 
193 	/* Try to drop the privileges */
194 	if (drop_privileges(user, group) == EXIT_FAILURE)
195 		exit(EXIT_FAILURE);
196 
197 	snprintf(buffer, sizeof(buffer) - 1,
198 			"%s Daemon (%s) started with PID=%d\n", progname, PACKAGE_VERSION,
199 			getpid());
200 	LOG(0, buffer);
201 	snprintf(buffer, sizeof(buffer) - 1,
202 			"Please have a look at '%s -V' to get license information\n",
203 			progname);
204 	LOG(0, buffer);
205 
206 	//sigemptyset();
207 	handle_signal(SIGINT, check_sig);
208 	handle_signal(SIGHUP, check_sig);
209 	handle_signal(SIGTERM, check_sig);
210 
211 	snprintf(buffer, sizeof(buffer) - 1,
212 			"HINT: load_threshold is %s - ('%f')\n",
213 			use_load_threshold ? "enabled" : "disabled", load_threshold);
214 	LOG(0, buffer);
215 
216 	/* beginn main loop */
217 	while (1) {
218 
219 		if (chdir(directory) != 0)
220 			exit(EXIT_FAILURE);
221 
222 		/* is_file() filter may cause trouble on some systems
223 		 * like Solaris or HP-UX that don't have a d-type
224 		 * member in struct dirent
225 		 */
226 		/* #ifdef HAVE_STRUCT_DIRENT_D_TYPE
227 		 if ( ( filecounter = scandir( directory, &namelist, is_file, alphasort ) ) < 0 ) {
228 		 #else */
229 		if ((filecounter = scandir(directory, &namelist, 0, alphasort)) < 0) {
230 			/* #endif */
231 			snprintf(buffer, sizeof(buffer) - 1,
232 					"Error while get file list from spooldir (%s) - %s\n",
233 					directory, strerror(errno));
234 			LOG(0, buffer);
235 			snprintf(buffer, sizeof(buffer) - 1, "Exiting...\n");
236 			LOG(0, buffer);
237 
238 			if (daemon_mode != TRUE)
239 				printf("Error while get file list from spooldir (%s) - %s\n",
240 						directory, strerror(errno));
241 			break;
242 		}
243 
244 		snprintf(buffer, sizeof(buffer) - 1, "Found %d files in %s\n",
245 				filecounter, directory);
246 		LOG(2, buffer);
247 
248 		for (i = 0, namelist; i < filecounter; i++) {
249 
250 #ifdef HAVE_GETLOADAVG
251 			if (use_load_threshold == TRUE) {
252 				load = getload(1);
253 				snprintf(buffer, sizeof(buffer) - 1, "DEBUG: load %f/%f\n",
254 						load, load_threshold);
255 				LOG(2, buffer);
256 			}
257 
258 			if (use_load_threshold && (load > load_threshold)) {
259 
260 				snprintf(buffer, sizeof(buffer) - 1,
261 						"WARN: MAX load reached: load %f/%f at i=%d", load,
262 						load_threshold, i);
263 				LOG(0, buffer);
264 
265 				if (i > 0)
266 					i--;
267 				sleep(sleeptime);
268 				continue;
269 			}
270 #endif
271 
272 			snprintf(buffer, sizeof(buffer) - 1,
273 					"ThreadCounter %d/%d File is %s\n", thread_counter,
274 					max_threads, namelist[i]->d_name);
275 			LOG(2, buffer);
276 
277 			struct stat attribute;
278 
279 			if (stat(namelist[i]->d_name, &attribute) == -1) {
280 				LOG(0, "Error while getting file status");
281 				break;
282 			}
283 
284 			if (strstr((namelist[i]->d_name), "-PID-") != NULL) {
285 				snprintf(
286 						buffer,
287 						sizeof(buffer) - 1,
288 						"File '%s' is an already in process PNP file. Leaving it untouched.\n",
289 						namelist[i]->d_name);
290 
291 				LOG(1, buffer);
292 				continue;
293 			}
294 
295 			if (S_ISREG(attribute.st_mode)) {
296 				snprintf(buffer, sizeof(buffer) - 1, "Regular File: %s\n",
297 						namelist[i]->d_name);
298 				LOG(2, buffer);
299 
300 				/* only start new threads if the max_thread config option is not reached */
301 				if (thread_counter < max_threads && we_should_stop == FALSE) {
302 
303 					if ((pthread_ret = pthread_create(&th[thread_counter],
304 							NULL, processfile, namelist[i]->d_name)) != 0) {
305 						snprintf(buffer, sizeof(buffer) - 1,
306 							"Could not create thread... exiting with error '%s'\n", strerror(errno));
307 						LOG(0, buffer);
308 						exit(EXIT_FAILURE);
309 					}
310 
311 					snprintf(buffer, sizeof(buffer) - 1,
312 							"A thread was started on thread_counter = %d\n",
313 							thread_counter);
314 					LOG(2, buffer);
315 
316 					thread_counter++;
317 
318 				}
319 
320 				else if (we_should_stop == TRUE)
321 					break;
322 
323 				else {
324 
325 					snprintf(
326 							buffer,
327 							sizeof(buffer) - 1,
328 							"WARN: MAX Thread reached: %s comes later with ThreadCounter: %d\n",
329 							namelist[i]->d_name, thread_counter);
330 					LOG(2, buffer);
331 
332 					i--;
333 
334 					for (thread_counter = thread_counter; thread_counter > 0; thread_counter--) {
335 						snprintf(buffer, sizeof(buffer) - 1,
336 								"DEBUG: Will wait for th['%d']\n",
337 								thread_counter - 1);
338 						LOG(2, buffer);
339 						pthread_join(th[thread_counter - 1], NULL);
340 					}
341 				}
342 			}
343 		}
344 
345 		if (thread_counter > 0) {
346 			/* Wait for open threads before working on the next run */
347 			snprintf(buffer, sizeof(buffer) - 1,
348 					"Have to wait: Filecounter = %d - thread_counter = %d\n",
349 					filecounter - 2, thread_counter);
350 			LOG(2, buffer);
351 
352 			for (thread_counter = thread_counter; thread_counter > 0; thread_counter--)
353 				pthread_join(th[thread_counter - 1], NULL);
354 		}
355 
356 		if (we_should_stop == TRUE)
357 			break;
358 
359 		for (i = 0, namelist; i < filecounter; i++) {
360 			free(namelist[i]);
361 		}
362 
363 		free(namelist);
364 
365 		snprintf(buffer, sizeof(buffer) - 1,
366 				"No more files to process... waiting for %d seconds\n",
367 				sleeptime);
368 		LOG(1, buffer);
369 
370 		sleep(sleeptime);
371 
372 	}
373 
374 	snprintf(buffer, sizeof(buffer) - 1, "Daemon ended. PID was '%d'\n",
375 			getpid());
376 	LOG(0, buffer);
377 
378 	if (use_syslog)
379 		closelog();
380 	return EXIT_SUCCESS;
381 }
382 
383 /* **************************************************************
384  *
385  * Function to parse and check the commandline arguments
386  *
387  * *************************************************************/
388 
process_arguments(int argc,char ** argv)389 int process_arguments(int argc, char **argv) {
390 	int c;
391 	int error = FALSE;
392 	int display_license = FALSE;
393 	int display_help = FALSE;
394 
395 #ifdef HAVE_GETOPT_H
396 	int option_index = 0;
397 	static struct option long_options[] = { { "help", no_argument, 0, 'h' }, {
398 			"version", no_argument, 0, 'V' },
399 			{ "license", no_argument, 0, 'V' },
400 			{ "daemon", no_argument, 0, 'd' }, { "config", required_argument,
401 					0, 'f' }, { 0, 0, 0, 0 } };
402 #endif
403 
404 	/* make sure we have the correct number of command line arguments */
405 	if (argc < 2)
406 		error = TRUE;
407 
408 	while (1) {
409 
410 #ifdef HAVE_GETOPT_H
411 		c = getopt_long(argc, argv, "+hVdf:", long_options, &option_index);
412 #else
413 		c = getopt(argc, argv, "+hVdf:");
414 #endif
415 
416 		if (c == -1 || c == EOF)
417 			break;
418 
419 		switch (c) {
420 
421 		case '?': /* usage */
422 		case 'h':
423 			display_help = TRUE;
424 			break;
425 
426 		case 'V': /* version */
427 			printf("%s %s - $Revision: 637 $\n\n", progname, PACKAGE_VERSION);
428 			display_license = TRUE;
429 			break;
430 
431 		case 'd': /* run in daemon mode */
432 			daemon_mode = TRUE;
433 			break;
434 
435 		case 'f': /* config file */
436 			if (optarg != NULL)
437 				config_file = optarg;
438 			break;
439 
440 		default:
441 			break;
442 		}
443 
444 	}
445 
446 	if (display_license == TRUE) {
447 
448 		printf(
449 				"This program is free software; you can redistribute it and/or modify\n");
450 		printf(
451 				"it under the terms of the GNU General Public License version 2 as\n");
452 		printf("published by the Free Software Foundation.\n\n");
453 		printf(
454 				"This program is distributed in the hope that it will be useful,\n");
455 		printf(
456 				"but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
457 		printf(
458 				"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n");
459 		printf("GNU General Public License for more details.\n\n");
460 		printf(
461 				"You should have received a copy of the GNU General Public License\n");
462 		printf("along with this program; if not, write to the Free Software\n");
463 		printf(
464 				"Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n\n");
465 
466 		exit(EXIT_SUCCESS);
467 	}
468 
469 	/* if there are no command line options (or if we encountered an error), print usage */
470 	if (error == TRUE || display_help == TRUE) {
471 		printf("\nUsage: %s -f <configfile> [-d] \n", argv[0]);
472 		printf("\n");
473 		printf("Options:\n");
474 		printf("\n");
475 		printf("  -d | --daemon \n");
476 		printf("\t\tRun as daemon in background\n");
477 		printf("\n");
478 		printf("  -f | --config \n");
479 		printf("\t\tPath to config file\n");
480 		printf("\n");
481 		printf(
482 				"Visit the Website at http://sourceforge.net/projects/pnp4nagios/ for bug fixes, \n");
483 		printf("new releases, online documentation, FAQs, Mailinglists.\n");
484 		printf("\n");
485 
486 		exit(EXIT_FAILURE);
487 	}
488 	return EXIT_SUCCESS;
489 }
490 
491 /********************************************************************
492  *								    *
493  * processfile - this is the function for each thread		    *
494  *								    *
495  ********************************************************************/
496 
processfile(void * filename)497 void * processfile(void *filename) {
498 
499 	char *file = (char *) filename;
500 	char command_line[MAX_COMMANDLINE_LENGTH];
501 	char buffer[MAX_LOGMESSAGE_SIZE];
502 	int result;
503 	FILE *proc;
504 
505 	/* npcd.c:493: warning: ‘result’ may be used uninitialized in this function */
506 	result = 0;
507 
508 	snprintf(command_line, sizeof(command_line), "%s %s %s %s/%s", command,
509 			identmyself ? "-n" : "\b", command_args, directory, file);
510 
511 	pthread_cleanup_push((void *) &exit_handler_mem, file);
512 
513 		snprintf(buffer, sizeof(buffer) - 1,
514 				"Processing file %s with ID %ld - going to exec %s\n", file,
515 				pthread_self(), command_line);
516 		LOG(2, buffer);
517 
518 		snprintf(buffer, sizeof(buffer) - 1, "Processing file '%s'\n", file);
519 		LOG(1, buffer);
520 
521 		if ((proc = popen(command_line, "r")) != NULL)
522 			result = pclose(proc);
523 
524 		result >>= 8;
525 
526 		if (result != 0) {
527 			snprintf(buffer, sizeof(buffer) - 1,
528 					"ERROR: Executed command exits with return code '%d'\n",
529 					result);
530 			LOG(0, buffer);
531 
532 			snprintf(buffer, sizeof(buffer) - 1,
533 					"ERROR: Command line was '%s'\n", command_line);
534 			LOG(0, buffer);
535 
536 			we_should_stop = FALSE;
537 		}
538 
539 		if (loglevel == -1)
540 			sleep(2);
541 
542 		pthread_cleanup_pop(1);
543 	pthread_exit((void *) pthread_self());
544 }
545 
546 /* ******************************************************************
547  *                                                                  *
548  * processfile - this is the function for each thread               *
549  *                                                                  *
550  * ******************************************************************/
551 
exit_handler_mem(void * arg)552 static void *exit_handler_mem(void * arg) {
553 	// syslog( LOG_NOTICE, "Will now clean up thread %ld\n",pthread_self());
554 	//if (thread_counter > 0)
555 	//thread_counter--;
556 	return 0;
557 }
558 
559 #ifdef HAVE_GETLOADAVG
getload(int which_sample)560 double getload(int which_sample) {
561 
562 	double loadavg[3];
563 	char buffer[MAX_LOGMESSAGE_SIZE];
564 
565 	if (which_sample == 1) {
566 		which_sample = 0;
567 	} else if (which_sample == 5) {
568 		which_sample = 1;
569 	} else if (which_sample == 15) {
570 		which_sample = 2;
571 	} else {
572 		snprintf(buffer, sizeof(buffer) - 1,
573 				"Invalid load sample %d - allowed is 1,5,15\n", which_sample);
574 		LOG(0, buffer);
575 
576 		return -1;
577 	}
578 	getloadavg(loadavg, 3);
579 	return loadavg[which_sample];
580 }
581 #endif
582