1 /*****************************************************************************
2 *
3 * utils_base.c
4 *
5 * License: GPL
6 * Copyright (c) 2006-2014 Nagios Plugins Development Team
7 *
8 * Library of useful functions for plugins
9 *
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 *
24 *
25 *****************************************************************************/
26
27 #include "common.h"
28 #include <stdarg.h>
29 #include "utils_base.h"
30 #include <ctype.h>
31 #include <fcntl.h>
32 #include <sys/stat.h>
33 #include <unistd.h>
34 #include <sys/types.h>
35
36 #define np_free(ptr) { if(ptr) { free(ptr); ptr = NULL; } }
37
38 nagios_plugin *this_nagios_plugin=NULL;
39
40 int _np_state_read_file(FILE *);
41
np_init(char * plugin_name,int argc,char ** argv)42 void np_init( char *plugin_name, int argc, char **argv ) {
43 if (!this_nagios_plugin) {
44 this_nagios_plugin = calloc(1, sizeof(nagios_plugin));
45 if (!this_nagios_plugin) {
46 die(STATE_UNKNOWN, "%s %s\n", _("Cannot allocate memory:"), strerror(errno));
47 }
48 this_nagios_plugin->plugin_name = strdup(plugin_name);
49 if (!this_nagios_plugin->plugin_name)
50 die(STATE_UNKNOWN, "%s %s\n", _("Cannot execute strdup:"), strerror(errno));
51 this_nagios_plugin->argc = argc;
52 this_nagios_plugin->argv = argv;
53 }
54 }
55
np_set_args(int argc,char ** argv)56 void np_set_args( int argc, char **argv ) {
57 if (!this_nagios_plugin)
58 die(STATE_UNKNOWN, "%s\n", _("This requires np_init to be called"));
59
60 this_nagios_plugin->argc = argc;
61 this_nagios_plugin->argv = argv;
62 }
63
64
np_cleanup()65 void np_cleanup() {
66 if (this_nagios_plugin) {
67 if(this_nagios_plugin->state) {
68 if(this_nagios_plugin->state->state_data) {
69 np_free(this_nagios_plugin->state->state_data->data);
70 np_free(this_nagios_plugin->state->state_data);
71 }
72 np_free(this_nagios_plugin->state->name);
73 np_free(this_nagios_plugin->state);
74 }
75 np_free(this_nagios_plugin->plugin_name);
76 np_free(this_nagios_plugin);
77 }
78 this_nagios_plugin=NULL;
79 }
80
81 /* Hidden function to get a pointer to this_nagios_plugin for testing */
_get_nagios_plugin(nagios_plugin ** pointer)82 void _get_nagios_plugin( nagios_plugin **pointer ){
83 *pointer = this_nagios_plugin;
84 }
85
86 void
die(int result,const char * fmt,...)87 die (int result, const char *fmt, ...)
88 {
89 va_list ap;
90 va_start (ap, fmt);
91 vprintf (fmt, ap);
92 va_end (ap);
93 if(this_nagios_plugin) {
94 np_cleanup();
95 }
96 exit (result);
97 }
98
set_range_start(range * this,double value)99 void set_range_start (range *this, double value) {
100 this->start = value;
101 this->start_infinity = FALSE;
102 }
103
set_range_end(range * this,double value)104 void set_range_end (range *this, double value) {
105 this->end = value;
106 this->end_infinity = FALSE;
107 }
108
109 range
parse_range_string(char * str)110 *parse_range_string (char *str) {
111 range *temp_range;
112 double start;
113 double end;
114 char *end_str;
115
116 temp_range = (range *) calloc(1, sizeof(range));
117
118 /* Set defaults */
119 temp_range->start = 0;
120 temp_range->start_infinity = FALSE;
121 temp_range->end = 0;
122 temp_range->end_infinity = TRUE;
123 temp_range->alert_on = OUTSIDE;
124
125 if (str[0] == '@') {
126 temp_range->alert_on = INSIDE;
127 str++;
128 }
129
130 end_str = index(str, ':');
131 if (end_str) {
132 if (str[0] == '~') {
133 temp_range->start_infinity = TRUE;
134 } else {
135 start = strtod(str, NULL); /* Will stop at the ':' */
136 set_range_start(temp_range, start);
137 }
138 end_str++; /* Move past the ':' */
139 } else {
140 end_str = str;
141 }
142 end = strtod(end_str, NULL);
143 if (strcmp(end_str, "")) {
144 set_range_end(temp_range, end);
145 }
146
147 if (temp_range->start_infinity ||
148 temp_range->end_infinity ||
149 temp_range->start <= temp_range->end) {
150 return temp_range;
151 }
152 free(temp_range);
153 return NULL;
154 }
155
156 /* returns 0 if okay, otherwise 1 */
157 int
_set_thresholds(thresholds ** my_thresholds,char * warn_string,char * critical_string)158 _set_thresholds(thresholds **my_thresholds, char *warn_string, char *critical_string)
159 {
160 thresholds *temp_thresholds = NULL;
161
162 if (!(temp_thresholds = calloc(1, sizeof(thresholds))))
163 die(STATE_UNKNOWN, "%s %s\n", _("Cannot allocate memory:"), strerror(errno));
164
165 temp_thresholds->warning = NULL;
166 temp_thresholds->critical = NULL;
167 temp_thresholds->warning_string = NULL;
168 temp_thresholds->critical_string = NULL;
169
170 if (warn_string) {
171 if (!(temp_thresholds->warning = parse_range_string(warn_string))) {
172 free(temp_thresholds);
173 return NP_RANGE_UNPARSEABLE;
174 }
175 temp_thresholds->warning_string = strdup(warn_string);
176 }
177 if (critical_string) {
178 if (!(temp_thresholds->critical = parse_range_string(critical_string))) {
179 free(temp_thresholds);
180 return NP_RANGE_UNPARSEABLE;
181 }
182 temp_thresholds->critical_string = strdup(critical_string);
183 }
184
185 *my_thresholds = temp_thresholds;
186
187 return 0;
188 }
189
190 void
set_thresholds(thresholds ** my_thresholds,char * warn_string,char * critical_string)191 set_thresholds(thresholds **my_thresholds, char *warn_string, char *critical_string)
192 {
193 switch (_set_thresholds(my_thresholds, warn_string, critical_string)) {
194 case 0:
195 return;
196 case NP_RANGE_UNPARSEABLE:
197 die(STATE_UNKNOWN, "%s\n", _("Range format incorrect"));
198 case NP_WARN_WITHIN_CRIT:
199 die(STATE_UNKNOWN, "%s\n", _("Warning level is a subset of critical and will not be alerted"));
200 break;
201 }
202 }
203
print_thresholds(const char * threshold_name,thresholds * my_threshold)204 void print_thresholds(const char *threshold_name, thresholds *my_threshold) {
205 printf("%s - ", threshold_name);
206 if (! my_threshold) {
207 printf("Threshold not set");
208 } else {
209 if (my_threshold->warning) {
210 printf("Warning: start=%g end=%g; ", my_threshold->warning->start, my_threshold->warning->end);
211 if (my_threshold->warning_string) {
212 printf("Warning String: %s; ", my_threshold->warning_string);
213 }
214 } else {
215 printf("Warning not set; ");
216 }
217 if (my_threshold->critical) {
218 printf("Critical: start=%g end=%g", my_threshold->critical->start, my_threshold->critical->end);
219 if (my_threshold->critical_string) {
220 printf("Critical String: %s; ", my_threshold->critical_string);
221 }
222 } else {
223 printf("Critical not set");
224 }
225 }
226 printf("\n");
227 }
228
229 /* Returns TRUE if alert should be raised based on the range */
230 int
check_range(double value,range * my_range)231 check_range(double value, range *my_range)
232 {
233 int no = FALSE;
234 int yes = TRUE;
235
236 if (my_range->alert_on == INSIDE) {
237 no = TRUE;
238 yes = FALSE;
239 }
240
241 if (!my_range->end_infinity && !my_range->start_infinity) {
242 if ((my_range->start <= value) && (value <= my_range->end)) {
243 return no;
244 } else {
245 return yes;
246 }
247 } else if (!my_range->start_infinity && my_range->end_infinity) {
248 if (my_range->start <= value) {
249 return no;
250 } else {
251 return yes;
252 }
253 } else if (my_range->start_infinity && !my_range->end_infinity) {
254 if (value <= my_range->end) {
255 return no;
256 } else {
257 return yes;
258 }
259 } else {
260 return no;
261 }
262 }
263
264 /* Returns status */
265 int
get_status(double value,thresholds * my_thresholds)266 get_status(double value, thresholds *my_thresholds)
267 {
268 if (my_thresholds->critical) {
269 if (check_range(value, my_thresholds->critical)) {
270 return STATE_CRITICAL;
271 }
272 }
273 if (my_thresholds->warning) {
274 if (check_range(value, my_thresholds->warning)) {
275 return STATE_WARNING;
276 }
277 }
278 return STATE_OK;
279 }
280
np_escaped_string(const char * string)281 char *np_escaped_string (const char *string) {
282 char *data;
283 int i, j=0;
284 data = strdup(string);
285 for (i=0; data[i]; i++) {
286 if (data[i] == '\\') {
287 switch(data[++i]) {
288 case 'n':
289 data[j++] = '\n';
290 break;
291 case 'r':
292 data[j++] = '\r';
293 break;
294 case 't':
295 data[j++] = '\t';
296 break;
297 case '\\':
298 data[j++] = '\\';
299 break;
300 default:
301 data[j++] = data[i];
302 }
303 } else {
304 data[j++] = data[i];
305 }
306 }
307 data[j] = '\0';
308 return data;
309 }
310
np_check_if_root(void)311 int np_check_if_root(void) { return (geteuid() == 0); }
312
np_warn_if_not_root(void)313 int np_warn_if_not_root(void) {
314 int status = np_check_if_root();
315 if(!status) {
316 printf("%s", _("Warning: "));
317 printf("%s\n", _("This plugin must be either run as root or setuid root."));
318 printf("%s\n", _("To run as root, you can use a tool like sudo."));
319 printf("%s\n", _("To set the setuid permissions, use the command:"));
320 /* XXX could we use something like progname? */
321 printf("\t%s\n", _("chmod u+s yourpluginfile"));
322 }
323 return status;
324 }
325
326 /*
327 * Extract the value from key/value pairs, or return NULL. The value returned
328 * can be free()ed.
329 * This function can be used to parse NTP control packet data and performance
330 * data strings.
331 */
np_extract_value(const char * varlist,const char * name,char sep)332 char *np_extract_value(const char *varlist, const char *name, char sep) {
333 char *tmp=NULL, *value=NULL;
334 int i;
335
336 while (1) {
337 /* Strip any leading space */
338 for (; isspace(varlist[0]); varlist++);
339
340 if (!strncmp(name, varlist, strlen(name))) {
341 varlist += strlen(name);
342 /* strip trailing spaces */
343 for (; isspace(varlist[0]); varlist++);
344
345 if (varlist[0] == '=') {
346 /* We matched the key, go past the = sign */
347 varlist++;
348 /* strip leading spaces */
349 for (; isspace(varlist[0]); varlist++);
350
351 if ((tmp = index(varlist, sep))) {
352 /* Value is delimited by a comma */
353 if (tmp-varlist == 0) continue;
354 value = (char *)calloc(1, tmp-varlist+1);
355 strncpy(value, varlist, tmp-varlist);
356 value[tmp-varlist] = '\0';
357 } else {
358 /* Value is delimited by a \0 */
359 if (!strlen(varlist)) continue;
360 value = (char *)calloc(1, strlen(varlist) + 1);
361 strncpy(value, varlist, strlen(varlist));
362 value[strlen(varlist)] = '\0';
363 }
364 break;
365 }
366 }
367 if ((tmp = index(varlist, sep))) {
368 /* More keys, keep going... */
369 varlist = tmp + 1;
370 } else {
371 /* We're done */
372 break;
373 }
374 }
375
376 /* Clean-up trailing spaces/newlines */
377 if (value) for (i=strlen(value)-1; isspace(value[i]); i--) value[i] = '\0';
378
379 return value;
380 }
381
382 /*
383 * Read a string representing a state (ok, warning... or numeric: 0, 1) and
384 * return the corresponding STATE_ value or ERROR)
385 */
translate_state(char * state_text)386 int translate_state (char *state_text) {
387 if (!strcasecmp(state_text,"OK") || !strcmp(state_text,"0"))
388 return STATE_OK;
389 if (!strcasecmp(state_text,"WARNING") || !strcmp(state_text,"1"))
390 return STATE_WARNING;
391 if (!strcasecmp(state_text,"CRITICAL") || !strcmp(state_text,"2"))
392 return STATE_CRITICAL;
393 if (!strcasecmp(state_text,"UNKNOWN") || !strcmp(state_text,"3"))
394 return STATE_UNKNOWN;
395 return ERROR;
396 }
397
398 /*
399 * Returns a string to use as a keyname, based on an md5 hash of argv, thus
400 * hopefully a unique key per service/plugin invocation. Use the extra-opts
401 * parse of argv, so that uniqueness in parameters are reflected there.
402 */
_np_state_generate_key()403 char *_np_state_generate_key() {
404 struct sha1_ctx ctx;
405 int i;
406 char **argv = this_nagios_plugin->argv;
407 unsigned char result[20];
408 char keyname[41];
409 char *p=NULL;
410
411 sha1_init_ctx(&ctx);
412
413 for(i=0; i<this_nagios_plugin->argc; i++) {
414 sha1_process_bytes(argv[i], strlen(argv[i]), &ctx);
415 }
416
417 sha1_finish_ctx(&ctx, &result);
418
419 for (i=0; i<20; ++i) {
420 sprintf(&keyname[2*i], "%02x", result[i]);
421 }
422 keyname[40]='\0';
423
424 p = strdup(keyname);
425 if(!p) {
426 die(STATE_UNKNOWN, "%s %s\n", _("Cannot execute strdup:"), strerror(errno));
427 }
428 return p;
429 }
430
_cleanup_state_data()431 void _cleanup_state_data() {
432 if (this_nagios_plugin->state->state_data) {
433 np_free(this_nagios_plugin->state->state_data->data);
434 np_free(this_nagios_plugin->state->state_data);
435 }
436 }
437
438 /*
439 * Internal function. Returns either:
440 * envvar NAGIOS_PLUGIN_STATE_DIRECTORY
441 * statically compiled shared state directory
442 */
_np_state_calculate_location_prefix()443 char* _np_state_calculate_location_prefix(){
444 char *env_dir;
445
446 /* Do not allow passing NP_STATE_DIRECTORY in setuid plugins
447 * for security reasons */
448
449 if (!np_suid()) {
450 env_dir = getenv("NAGIOS_PLUGIN_STATE_DIRECTORY");
451 if(env_dir && env_dir[0] != '\0')
452 return env_dir;
453 return NP_STATE_DIR_PREFIX;
454 }
455 }
456
457 /*
458 * Initiatializer for state routines.
459 * Sets variables. Generates filename. Returns np_state_key. die with
460 * UNKNOWN if exception
461 */
np_enable_state(char * keyname,int expected_data_version)462 void np_enable_state(char *keyname, int expected_data_version) {
463 state_key *this_state = NULL;
464 char *temp_filename = NULL;
465 char *temp_keyname = NULL;
466 char *p=NULL;
467 int ret;
468
469 if(!this_nagios_plugin)
470 die(STATE_UNKNOWN, "%s\n", _("This requires np_init to be called"));
471
472 this_state = (state_key *) calloc(1, sizeof(state_key));
473 if(!this_state)
474 die(STATE_UNKNOWN, "%s %s\n", _("Cannot allocate memory:"), strerror(errno));
475
476 if(!keyname) {
477 temp_keyname = _np_state_generate_key();
478 } else {
479 temp_keyname = strdup(keyname);
480 if(!temp_keyname)
481 die(STATE_UNKNOWN, "%s %s\n", _("Cannot execute strdup:"), strerror(errno));
482 }
483 /* Die if invalid characters used for keyname */
484 p = temp_keyname;
485 while(*p!='\0') {
486 if(! (isalnum(*p) || *p == '_'))
487 die(STATE_UNKNOWN, "%s\n", _("Invalid character for keyname - only alphanumerics or '_'"));
488 p++;
489 }
490 this_state->name=temp_keyname;
491 this_state->plugin_name=this_nagios_plugin->plugin_name;
492 this_state->data_version=expected_data_version;
493 this_state->state_data=NULL;
494
495 /* Calculate filename */
496 ret = asprintf(&temp_filename, "%s/%lu/%s/%s", _np_state_calculate_location_prefix(), (unsigned long)geteuid(), this_nagios_plugin->plugin_name, this_state->name);
497 if (ret < 0)
498 die(STATE_UNKNOWN, "%s %s\n", _("Cannot allocate memory:"), strerror(errno));
499 this_state->_filename=temp_filename;
500
501 this_nagios_plugin->state = this_state;
502 }
503
504 /*
505 * Will return NULL if no data is available (first run). If key currently
506 * exists, read data. If state file format version is not expected, return
507 * as if no data. Get state data version number and compares to expected.
508 * If numerically lower, then return as no previous state. die with UNKNOWN
509 * if exceptional error.
510 */
np_state_read()511 state_data *np_state_read() {
512 state_data *this_state_data=NULL;
513 FILE *statefile;
514 int rc = FALSE;
515
516 if(!this_nagios_plugin)
517 die(STATE_UNKNOWN, "%s\n", _("This requires np_init to be called"));
518
519 /* Open file. If this fails, no previous state found */
520 statefile = fopen( this_nagios_plugin->state->_filename, "r" );
521 if(statefile) {
522
523 this_state_data = (state_data *) calloc(1, sizeof(state_data));
524 if(!this_state_data)
525 die(STATE_UNKNOWN, "%s %s\n", _("Cannot allocate memory:"), strerror(errno));
526
527 this_state_data->data=NULL;
528 this_nagios_plugin->state->state_data = this_state_data;
529
530 rc = _np_state_read_file(statefile);
531
532 fclose(statefile);
533 }
534
535 if(!rc) {
536 _cleanup_state_data();
537 }
538
539 return this_nagios_plugin->state->state_data;
540 }
541
542 /*
543 * Read the state file
544 */
_np_state_read_file(FILE * f)545 int _np_state_read_file(FILE *f) {
546 int status=FALSE;
547 size_t pos;
548 char *line;
549 int i;
550 int failure=0;
551 time_t current_time, data_time;
552 enum { STATE_FILE_VERSION, STATE_DATA_VERSION, STATE_DATA_TIME, STATE_DATA_TEXT, STATE_DATA_END } expected=STATE_FILE_VERSION;
553
554 time(¤t_time);
555
556 /* Note: This introduces a limit of 1024 bytes in the string data */
557 line = (char *) calloc(1, 1024);
558 if(!line)
559 die(STATE_UNKNOWN, "%s %s\n", _("Cannot allocate memory:"), strerror(errno));
560
561 while(!failure && (fgets(line,1024,f))!=NULL){
562 pos=strlen(line);
563 if(line[pos-1]=='\n')
564 line[pos-1]='\0';
565
566 if(line[0] == '#') continue;
567
568 switch(expected) {
569 case STATE_FILE_VERSION:
570 i=atoi(line);
571 if(i!=NP_STATE_FORMAT_VERSION)
572 failure++;
573 else
574 expected=STATE_DATA_VERSION;
575 break;
576 case STATE_DATA_VERSION:
577 i=atoi(line);
578 if(i != this_nagios_plugin->state->data_version)
579 failure++;
580 else
581 expected=STATE_DATA_TIME;
582 break;
583 case STATE_DATA_TIME:
584 /* If time > now, error */
585 data_time=strtoul(line,NULL,10);
586 if(data_time > current_time)
587 failure++;
588 else {
589 this_nagios_plugin->state->state_data->time = data_time;
590 expected=STATE_DATA_TEXT;
591 }
592 break;
593 case STATE_DATA_TEXT:
594 this_nagios_plugin->state->state_data->data = strdup(line);
595 if(this_nagios_plugin->state->state_data->data==NULL)
596 die(STATE_UNKNOWN, "%s %s\n", _("Cannot execute strdup:"), strerror(errno));
597 expected=STATE_DATA_END;
598 status=TRUE;
599 break;
600 case STATE_DATA_END:
601 ;
602 }
603 }
604
605 np_free(line);
606 return status;
607 }
608
609 /*
610 * If time=NULL, use current time. Create state file, with state format
611 * version, default text. Writes version, time, and data. Avoid locking
612 * problems - use mv to write and then swap. Possible loss of state data if
613 * two things writing to same key at same time.
614 * Will die with UNKNOWN if errors
615 */
np_state_write_string(time_t data_time,char * data_string)616 void np_state_write_string(time_t data_time, char *data_string) {
617 FILE *fp;
618 char *temp_file=NULL;
619 int fd=0, result=0;
620 time_t current_time;
621 char *directories=NULL;
622 char *p=NULL;
623
624 if(!data_time)
625 time(¤t_time);
626 else
627 current_time=data_time;
628
629 /* If file doesn't currently exist, create directories */
630 if(access(this_nagios_plugin->state->_filename,F_OK)) {
631 result = asprintf(&directories, "%s", this_nagios_plugin->state->_filename);
632 if (result < 0)
633 die(STATE_UNKNOWN, "%s %s\n", _("Cannot allocate memory:"), strerror(errno));
634 if(!directories)
635 die(STATE_UNKNOWN, "%s %s\n", _("Cannot allocate memory:"), strerror(errno));
636
637 for(p=directories+1; *p; p++) {
638 if(*p=='/') {
639 *p='\0';
640 if(access(directories,F_OK) && mkdir(directories, S_IRWXU)) {
641 /* Can't free this! Otherwise error message is wrong! */
642 /* np_free(directories); */
643 die(STATE_UNKNOWN, "%s %s\n", _("Cannot create directory:"), directories);
644 }
645 *p='/';
646 }
647 }
648 np_free(directories);
649 }
650
651 result = asprintf(&temp_file,"%s.XXXXXX",this_nagios_plugin->state->_filename);
652 if (result < 0)
653 die(STATE_UNKNOWN, "%s %s\n", _("Cannot allocate memory:"), strerror(errno));
654 if(!temp_file)
655 die(STATE_UNKNOWN, "%s %s\n", _("Cannot allocate memory:"), strerror(errno));
656
657 if((fd=mkstemp(temp_file))==-1) {
658 np_free(temp_file);
659 die(STATE_UNKNOWN, "%s\n", _("Cannot create temporary filename"));
660 }
661
662 fp=(FILE *)fdopen(fd,"w");
663 if(!fp) {
664 close(fd);
665 unlink(temp_file);
666 np_free(temp_file);
667 die(STATE_UNKNOWN, "%s\n", _("Unable to open temporary state file"));
668 }
669
670 fprintf(fp,"# NP State file\n");
671 fprintf(fp,"%d\n",NP_STATE_FORMAT_VERSION);
672 fprintf(fp,"%d\n",this_nagios_plugin->state->data_version);
673 fprintf(fp,"%lu\n",current_time);
674 fprintf(fp,"%s\n",data_string);
675
676 fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP);
677
678 fflush(fp);
679
680 result=fclose(fp);
681
682 fsync(fd);
683
684 if(result) {
685 unlink(temp_file);
686 np_free(temp_file);
687 die(STATE_UNKNOWN, "%s\n", _("Error writing temp file"));
688 }
689
690 if(rename(temp_file, this_nagios_plugin->state->_filename)) {
691 unlink(temp_file);
692 np_free(temp_file);
693 die(STATE_UNKNOWN, "%s\n", _("Cannot rename state temp file"));
694 }
695
696 np_free(temp_file);
697 }
698