summaryrefslogtreecommitdiffstats
path: root/plugins/check_snmp.d/check_snmp_helpers.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/check_snmp.d/check_snmp_helpers.c')
-rw-r--r--plugins/check_snmp.d/check_snmp_helpers.c938
1 files changed, 938 insertions, 0 deletions
diff --git a/plugins/check_snmp.d/check_snmp_helpers.c b/plugins/check_snmp.d/check_snmp_helpers.c
new file mode 100644
index 00000000..2dfc88b5
--- /dev/null
+++ b/plugins/check_snmp.d/check_snmp_helpers.c
@@ -0,0 +1,938 @@
1#include "./check_snmp_helpers.h"
2#include <string.h>
3#include "../../lib/utils_base.h"
4#include "config.h"
5#include <assert.h>
6#include "../utils.h"
7#include "output.h"
8#include "states.h"
9#include <sys/stat.h>
10#include <ctype.h>
11
12extern int verbose;
13
14check_snmp_test_unit check_snmp_test_unit_init() {
15 check_snmp_test_unit tmp = {
16 .threshold = mp_thresholds_init(),
17 };
18 return tmp;
19}
20
21int check_snmp_set_thresholds(const char *threshold_string, check_snmp_test_unit test_units[],
22 size_t max_test_units, bool is_critical) {
23
24 if (threshold_string == NULL || strlen(threshold_string) == 0) {
25 // No input, do nothing
26 return 0;
27 }
28
29 if (strchr(threshold_string, ',') != NULL) {
30 // Got a comma in the string, should be multiple values
31 size_t tu_index = 0;
32
33 while (threshold_string[0] == ',') {
34 // got commas at the beginning, so skip some values
35 tu_index++;
36 threshold_string++;
37 }
38
39 char *thr_string_copy = strdup(threshold_string);
40 for (char *ptr = strtok(thr_string_copy, ", "); ptr != NULL;
41 ptr = strtok(NULL, ", "), tu_index++) {
42
43 if (tu_index > max_test_units) {
44 // More thresholds then values, just ignore them
45 return 0;
46 }
47
48 // edge case: maybe we got `,,` to skip a value
49 if (strlen(ptr) == 0) {
50 // no threshold given, do not set it then
51 continue;
52 }
53
54 mp_range_parsed tmp = mp_parse_range_string(ptr);
55 if (tmp.error != MP_PARSING_SUCCES) {
56 die(STATE_UNKNOWN, "Unable to parse critical threshold range: %s", ptr);
57 }
58
59 if (is_critical) {
60 test_units[tu_index].threshold.critical = tmp.range;
61 test_units[tu_index].threshold.critical_is_set = true;
62 } else {
63 test_units[tu_index].threshold.warning = tmp.range;
64 test_units[tu_index].threshold.warning_is_set = true;
65 }
66 }
67
68 free(thr_string_copy);
69 } else {
70 // Single value
71 // only valid for the first test unit
72 mp_range_parsed tmp = mp_parse_range_string(threshold_string);
73 if (tmp.error != MP_PARSING_SUCCES) {
74 die(STATE_UNKNOWN, "Unable to parse critical threshold range: %s", threshold_string);
75 }
76
77 if (is_critical) {
78 test_units[0].threshold.critical = tmp.range;
79 test_units[0].threshold.critical_is_set = true;
80 } else {
81 test_units[0].threshold.warning = tmp.range;
82 test_units[0].threshold.warning_is_set = true;
83 }
84 }
85
86 return 0;
87}
88
89const int DEFAULT_PROTOCOL = SNMP_VERSION_1;
90const char DEFAULT_OUTPUT_DELIMITER[] = " ";
91
92const int RANDOM_STATE_DATA_LENGTH_PREDICTION = 8192;
93
94check_snmp_config check_snmp_config_init() {
95 check_snmp_config tmp = {
96 .snmp_params =
97 {
98 .use_getnext = false,
99
100 .ignore_mib_parsing_errors = false,
101 .need_mibs = false,
102
103 .test_units = NULL,
104 .num_of_test_units = 0,
105 },
106
107 .evaluation_params =
108 {
109 .nulloid_result = STATE_UNKNOWN, // state to return if no result for query
110
111 .invert_search = true,
112 .regex_cmp_value = {},
113 .string_cmp_value = "",
114
115 .multiplier = 1.0,
116 .multiplier_set = false,
117 .offset = 0,
118 .offset_set = false,
119
120 .use_oid_as_perf_data_label = false,
121
122 .calculate_rate = false,
123 .rate_multiplier = 1,
124 },
125 };
126
127 snmp_sess_init(&tmp.snmp_params.snmp_session);
128
129 tmp.snmp_params.snmp_session.retries = DEFAULT_RETRIES;
130 tmp.snmp_params.snmp_session.version = DEFAULT_SNMP_VERSION;
131 tmp.snmp_params.snmp_session.securityLevel = SNMP_SEC_LEVEL_NOAUTH;
132 tmp.snmp_params.snmp_session.community = (unsigned char *)"public";
133 tmp.snmp_params.snmp_session.community_len = strlen("public");
134 return tmp;
135}
136
137snmp_responces do_snmp_query(check_snmp_config_snmp_parameters parameters) {
138 if (parameters.ignore_mib_parsing_errors) {
139 char *opt_toggle_res = snmp_mib_toggle_options("e");
140 if (opt_toggle_res != NULL) {
141 die(STATE_UNKNOWN, "Unable to disable MIB parsing errors");
142 }
143 }
144
145 struct snmp_pdu *pdu = NULL;
146 if (parameters.use_getnext) {
147 pdu = snmp_pdu_create(SNMP_MSG_GETNEXT);
148 } else {
149 pdu = snmp_pdu_create(SNMP_MSG_GET);
150 }
151
152 for (size_t i = 0; i < parameters.num_of_test_units; i++) {
153 assert(parameters.test_units[i].oid != NULL);
154 if (verbose > 0) {
155 printf("OID %zu to parse: %s\n", i, parameters.test_units[i].oid);
156 }
157
158 oid tmp_OID[MAX_OID_LEN];
159 size_t tmp_OID_len = MAX_OID_LEN;
160 if (snmp_parse_oid(parameters.test_units[i].oid, tmp_OID, &tmp_OID_len) != NULL) {
161 // success
162 snmp_add_null_var(pdu, tmp_OID, tmp_OID_len);
163 } else {
164 // failed
165 snmp_perror("Parsing failure");
166 die(STATE_UNKNOWN, "Failed to parse OID\n");
167 }
168 }
169
170 const int timeout_safety_tolerance = 5;
171 alarm((timeout_interval * (unsigned int)parameters.snmp_session.retries) +
172 timeout_safety_tolerance);
173
174 struct snmp_session *active_session = snmp_open(&parameters.snmp_session);
175 if (active_session == NULL) {
176 int pcliberr = 0;
177 int psnmperr = 0;
178 char *pperrstring = NULL;
179 snmp_error(&parameters.snmp_session, &pcliberr, &psnmperr, &pperrstring);
180 die(STATE_UNKNOWN, "Failed to open SNMP session: %s\n", pperrstring);
181 }
182
183 struct snmp_pdu *response = NULL;
184 int snmp_query_status = snmp_synch_response(active_session, pdu, &response);
185
186 if (!(snmp_query_status == STAT_SUCCESS && response->errstat == SNMP_ERR_NOERROR)) {
187 int pcliberr = 0;
188 int psnmperr = 0;
189 char *pperrstring = NULL;
190 snmp_error(active_session, &pcliberr, &psnmperr, &pperrstring);
191
192 if (psnmperr == SNMPERR_TIMEOUT) {
193 // We exit with critical here for some historical reason
194 die(STATE_CRITICAL, "SNMP query ran into a timeout\n");
195 }
196 die(STATE_UNKNOWN, "SNMP query failed: %s\n", pperrstring);
197 }
198
199 snmp_close(active_session);
200
201 /* disable alarm again */
202 alarm(0);
203
204 snmp_responces result = {
205 .errorcode = OK,
206 .response_values = calloc(parameters.num_of_test_units, sizeof(response_value)),
207 };
208
209 if (result.response_values == NULL) {
210 result.errorcode = ERROR;
211 return result;
212 }
213
214 // We got the the query results, now process them
215 size_t loop_index = 0;
216 for (netsnmp_variable_list *vars = response->variables; vars;
217 vars = vars->next_variable, loop_index++) {
218
219 for (size_t jdx = 0; jdx < vars->name_length; jdx++) {
220 result.response_values[loop_index].oid[jdx] = vars->name[jdx];
221 }
222 result.response_values[loop_index].oid_length = vars->name_length;
223
224 switch (vars->type) {
225 case ASN_OCTET_STR: {
226 result.response_values[loop_index].string_response = strdup((char *)vars->val.string);
227 result.response_values[loop_index].type = vars->type;
228 if (verbose) {
229 printf("Debug: Got a string as response: %s\n", vars->val.string);
230 }
231 }
232 continue;
233 case ASN_OPAQUE:
234 if (verbose) {
235 printf("Debug: Got OPAQUE\n");
236 }
237 break;
238 /* Numerical values */
239 case ASN_COUNTER64: {
240 if (verbose) {
241 printf("Debug: Got counter64\n");
242 }
243 struct counter64 tmp = *(vars->val.counter64);
244 uint64_t counter = (tmp.high << 32) + tmp.low;
245 result.response_values[loop_index].value.uIntVal = counter;
246 result.response_values[loop_index].type = vars->type;
247 } break;
248 case ASN_GAUGE: // same as ASN_UNSIGNED
249 case ASN_TIMETICKS:
250 case ASN_COUNTER:
251 case ASN_UINTEGER: {
252 if (verbose) {
253 printf("Debug: Got a Integer like\n");
254 }
255 result.response_values[loop_index].value.uIntVal = (unsigned long)*(vars->val.integer);
256 result.response_values[loop_index].type = vars->type;
257 } break;
258 case ASN_INTEGER: {
259 if (verbose) {
260 printf("Debug: Got a Integer\n");
261 }
262 result.response_values[loop_index].value.intVal = *(vars->val.integer);
263 result.response_values[loop_index].type = vars->type;
264 } break;
265 case ASN_FLOAT: {
266 if (verbose) {
267 printf("Debug: Got a float\n");
268 }
269 result.response_values[loop_index].value.doubleVal = *(vars->val.floatVal);
270 result.response_values[loop_index].type = vars->type;
271 } break;
272 case ASN_DOUBLE: {
273 if (verbose) {
274 printf("Debug: Got a double\n");
275 }
276 result.response_values[loop_index].value.doubleVal = *(vars->val.doubleVal);
277 result.response_values[loop_index].type = vars->type;
278 } break;
279 case ASN_IPADDRESS:
280 if (verbose) {
281 printf("Debug: Got an IP address\n");
282 }
283 result.response_values[loop_index].type = vars->type;
284
285 // TODO: print address here, state always ok? or regex match?
286 break;
287 default:
288 if (verbose) {
289 printf("Debug: Got a unmatched result type: %hhu\n", vars->type);
290 }
291 // TODO: Error here?
292 break;
293 }
294 }
295
296 return result;
297}
298
299check_snmp_evaluation evaluate_single_unit(response_value response,
300 check_snmp_evaluation_parameters eval_params,
301 check_snmp_test_unit test_unit, time_t query_timestamp,
302 check_snmp_state_entry prev_state,
303 bool have_previous_state) {
304 mp_subcheck sc_oid_test = mp_subcheck_init();
305
306 if ((test_unit.label != NULL) && (strcmp(test_unit.label, "") != 0)) {
307 xasprintf(&sc_oid_test.output, "%s - ", test_unit.label);
308 } else {
309 sc_oid_test.output = strdup("");
310 }
311
312 char oid_string[(MAX_OID_LEN * 2) + 1] = {};
313
314 int oid_string_result =
315 snprint_objid(oid_string, (MAX_OID_LEN * 2) + 1, response.oid, response.oid_length);
316 if (oid_string_result <= 0) {
317 // TODO error here
318 die(STATE_UNKNOWN, "snprint_objid failed\n");
319 }
320
321 xasprintf(&sc_oid_test.output, "%sOID: %s", sc_oid_test.output, oid_string);
322 sc_oid_test = mp_set_subcheck_default_state(sc_oid_test, STATE_OK);
323
324 if (verbose > 2) {
325 printf("Processing oid %s\n", oid_string);
326 }
327
328 bool got_a_numerical_value = false;
329 mp_perfdata_value pd_result_val = {0};
330
331 check_snmp_state_entry result_state = {
332 .timestamp = query_timestamp,
333 .oid_length = response.oid_length,
334 .type = response.type,
335 };
336
337 for (size_t i = 0; i < response.oid_length; i++) {
338 result_state.oid[i] = response.oid[i];
339 }
340
341 if (have_previous_state) {
342 if (query_timestamp == prev_state.timestamp) {
343 // somehow we have the same timestamp again, that can't be good
344 sc_oid_test = mp_set_subcheck_state(sc_oid_test, STATE_UNKNOWN);
345 xasprintf(&sc_oid_test.output, "Time duration between plugin calls is invalid");
346
347 check_snmp_evaluation result = {
348 .sc = sc_oid_test,
349 .state = result_state,
350 };
351
352 return result;
353 }
354 }
355 // compute rate time difference
356 double timeDiff = 0;
357 if (have_previous_state) {
358 if (verbose) {
359 printf("Previous timestamp: %s", ctime(&prev_state.timestamp));
360 printf("Current timestamp: %s", ctime(&query_timestamp));
361 }
362 timeDiff = difftime(query_timestamp, prev_state.timestamp) / eval_params.rate_multiplier;
363 }
364
365 mp_perfdata pd_num_val = {};
366
367 switch (response.type) {
368 case ASN_OCTET_STR: {
369 char *tmp = response.string_response;
370 if (strchr(tmp, '"') != NULL) {
371 // got double quote in the string
372 if (strchr(tmp, '\'') != NULL) {
373 // got single quote in the string too
374 // dont quote that at all to avoid even more confusion
375 xasprintf(&sc_oid_test.output, "%s - Value: %s", sc_oid_test.output, tmp);
376 } else {
377 // quote with single quotes
378 xasprintf(&sc_oid_test.output, "%s - Value: '%s'", sc_oid_test.output, tmp);
379 }
380 } else {
381 // quote with double quotes
382 xasprintf(&sc_oid_test.output, "%s - Value: \"%s\"", sc_oid_test.output, tmp);
383 }
384
385 if (strlen(tmp) == 0) {
386 sc_oid_test = mp_set_subcheck_state(sc_oid_test, eval_params.nulloid_result);
387 }
388
389 // String matching test
390 if ((test_unit.eval_mthd.crit_string)) {
391 if (strcmp(tmp, eval_params.string_cmp_value)) {
392 sc_oid_test = mp_set_subcheck_state(
393 sc_oid_test, (eval_params.invert_search) ? STATE_CRITICAL : STATE_OK);
394 } else {
395 sc_oid_test = mp_set_subcheck_state(
396 sc_oid_test, (eval_params.invert_search) ? STATE_OK : STATE_CRITICAL);
397 }
398 } else if (test_unit.eval_mthd.crit_regex) {
399 const size_t nmatch = eval_params.regex_cmp_value.re_nsub + 1;
400 regmatch_t pmatch[nmatch];
401 memset(pmatch, '\0', sizeof(regmatch_t) * nmatch);
402
403 int excode = regexec(&eval_params.regex_cmp_value, tmp, nmatch, pmatch, 0);
404 if (excode == 0) {
405 sc_oid_test = mp_set_subcheck_state(
406 sc_oid_test, (eval_params.invert_search) ? STATE_OK : STATE_CRITICAL);
407 } else if (excode != REG_NOMATCH) {
408 char errbuf[MAX_INPUT_BUFFER] = "";
409 regerror(excode, &eval_params.regex_cmp_value, errbuf, MAX_INPUT_BUFFER);
410 printf(_("Execute Error: %s\n"), errbuf);
411 exit(STATE_CRITICAL);
412 } else { // REG_NOMATCH
413 sc_oid_test = mp_set_subcheck_state(
414 sc_oid_test, eval_params.invert_search ? STATE_CRITICAL : STATE_OK);
415 }
416 }
417 } break;
418 case ASN_COUNTER64:
419 got_a_numerical_value = true;
420
421 result_state.value.uIntVal = response.value.uIntVal;
422 result_state.type = response.type;
423
424 // TODO: perfdata unit counter
425 if (eval_params.calculate_rate && have_previous_state) {
426 if (prev_state.value.uIntVal > response.value.uIntVal) {
427 // overflow
428 unsigned long long tmp =
429 (UINT64_MAX - prev_state.value.uIntVal) + response.value.uIntVal;
430
431 tmp /= timeDiff;
432 pd_result_val = mp_create_pd_value(tmp);
433 } else {
434 pd_result_val = mp_create_pd_value(
435 (response.value.uIntVal - prev_state.value.uIntVal) / timeDiff);
436 }
437 } else {
438 // It's only a counter if we cont compute rate
439 pd_num_val.uom = "c";
440 pd_result_val = mp_create_pd_value(response.value.uIntVal);
441 }
442 break;
443 case ASN_GAUGE: // same as ASN_UNSIGNED
444 case ASN_TIMETICKS:
445 case ASN_COUNTER:
446 case ASN_UINTEGER: {
447 got_a_numerical_value = true;
448 long long treated_value = (long long)response.value.uIntVal;
449
450 if (eval_params.multiplier_set || eval_params.offset_set) {
451 double processed = (double)response.value.uIntVal;
452
453 if (eval_params.offset_set) {
454 processed += eval_params.offset;
455 }
456
457 if (eval_params.multiplier_set) {
458 processed = processed * eval_params.multiplier;
459 }
460
461 treated_value = lround(processed);
462 }
463
464 result_state.value.intVal = treated_value;
465
466 if (eval_params.calculate_rate && have_previous_state) {
467 if (verbose > 2) {
468 printf("%s: Rate calculation (int/counter/gauge): prev: %lli\n", __FUNCTION__,
469 prev_state.value.intVal);
470 printf("%s: Rate calculation (int/counter/gauge): current: %lli\n", __FUNCTION__,
471 treated_value);
472 }
473 double rate = (treated_value - prev_state.value.intVal) / timeDiff;
474 pd_result_val = mp_create_pd_value(rate);
475 } else {
476 pd_result_val = mp_create_pd_value(treated_value);
477
478 if (response.type == ASN_COUNTER) {
479 pd_num_val.uom = "c";
480 }
481 }
482
483 } break;
484 case ASN_INTEGER: {
485 if (eval_params.multiplier_set || eval_params.offset_set) {
486 double processed = (double)response.value.intVal;
487
488 if (eval_params.offset_set) {
489 processed += eval_params.offset;
490 }
491
492 if (eval_params.multiplier_set) {
493 processed *= eval_params.multiplier;
494 }
495
496 result_state.value.doubleVal = processed;
497
498 if (eval_params.calculate_rate && have_previous_state) {
499 pd_result_val =
500 mp_create_pd_value((processed - prev_state.value.doubleVal) / timeDiff);
501 } else {
502 pd_result_val = mp_create_pd_value(processed);
503 }
504 } else {
505 result_state.value.intVal = response.value.intVal;
506
507 if (eval_params.calculate_rate && have_previous_state) {
508 pd_result_val = mp_create_pd_value(
509 (response.value.intVal - prev_state.value.intVal) / timeDiff);
510 } else {
511 pd_result_val = mp_create_pd_value(response.value.intVal);
512 }
513 }
514
515 got_a_numerical_value = true;
516 } break;
517 case ASN_FLOAT: // fallthrough
518 case ASN_DOUBLE: {
519 got_a_numerical_value = true;
520 double tmp = response.value.doubleVal;
521 if (eval_params.offset_set) {
522 tmp += eval_params.offset;
523 }
524
525 if (eval_params.multiplier_set) {
526 tmp *= eval_params.multiplier;
527 }
528
529 if (eval_params.calculate_rate && have_previous_state) {
530 pd_result_val = mp_create_pd_value((tmp - prev_state.value.doubleVal) / timeDiff);
531 } else {
532 pd_result_val = mp_create_pd_value(tmp);
533 }
534 got_a_numerical_value = true;
535
536 result_state.value.doubleVal = tmp;
537 } break;
538 case ASN_IPADDRESS:
539 // TODO
540 break;
541 }
542
543 if (got_a_numerical_value) {
544 if (eval_params.use_oid_as_perf_data_label) {
545 // Use oid for perdata label
546 pd_num_val.label = strdup(oid_string);
547 // TODO strdup error checking
548 } else if (test_unit.label != NULL && strcmp(test_unit.label, "") != 0) {
549 pd_num_val.label = strdup(test_unit.label);
550 } else {
551 pd_num_val.label = strdup(test_unit.oid);
552 }
553
554 if (!(eval_params.calculate_rate && !have_previous_state)) {
555 // some kind of numerical value
556 if (test_unit.unit_value != NULL && strcmp(test_unit.unit_value, "") != 0) {
557 pd_num_val.uom = test_unit.unit_value;
558 }
559
560 pd_num_val.value = pd_result_val;
561
562 xasprintf(&sc_oid_test.output, "%s Value: %s", sc_oid_test.output,
563 pd_value_to_string(pd_result_val));
564
565 if (test_unit.unit_value != NULL && strcmp(test_unit.unit_value, "") != 0) {
566 xasprintf(&sc_oid_test.output, "%s%s", sc_oid_test.output, test_unit.unit_value);
567 }
568
569 if (test_unit.threshold.warning_is_set || test_unit.threshold.critical_is_set) {
570 pd_num_val = mp_pd_set_thresholds(pd_num_val, test_unit.threshold);
571 mp_state_enum tmp_state = mp_get_pd_status(pd_num_val);
572
573 if (tmp_state == STATE_WARNING) {
574 sc_oid_test = mp_set_subcheck_state(sc_oid_test, STATE_WARNING);
575 xasprintf(&sc_oid_test.output, "%s - number violates warning threshold",
576 sc_oid_test.output);
577 } else if (tmp_state == STATE_CRITICAL) {
578 sc_oid_test = mp_set_subcheck_state(sc_oid_test, STATE_CRITICAL);
579 xasprintf(&sc_oid_test.output, "%s - number violates critical threshold",
580 sc_oid_test.output);
581 }
582 }
583
584 mp_add_perfdata_to_subcheck(&sc_oid_test, pd_num_val);
585 } else {
586 // should calculate rate, but there is no previous state, so first run
587 // exit with ok now
588 sc_oid_test = mp_set_subcheck_state(sc_oid_test, STATE_OK);
589 xasprintf(&sc_oid_test.output, "%s - No previous data to calculate rate - assume okay",
590 sc_oid_test.output);
591 }
592 }
593
594 check_snmp_evaluation result = {
595 .sc = sc_oid_test,
596 .state = result_state,
597 };
598
599 return result;
600}
601
602char *_np_state_generate_key(int argc, char **argv);
603
604/*
605 * If time=NULL, use current time. Create state file, with state format
606 * version, default text. Writes version, time, and data. Avoid locking
607 * problems - use mv to write and then swap. Possible loss of state data if
608 * two things writing to same key at same time.
609 * Will die with UNKNOWN if errors
610 */
611void np_state_write_string(state_key stateKey, time_t timestamp, char *stringToStore) {
612 time_t current_time;
613 if (timestamp == 0) {
614 time(&current_time);
615 } else {
616 current_time = timestamp;
617 }
618
619 int result = 0;
620
621 /* If file doesn't currently exist, create directories */
622 if (access(stateKey._filename, F_OK) != 0) {
623 char *directories = NULL;
624 result = asprintf(&directories, "%s", stateKey._filename);
625 if (result < 0) {
626 die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
627 }
628
629 for (char *p = directories + 1; *p; p++) {
630 if (*p == '/') {
631 *p = '\0';
632 if ((access(directories, F_OK) != 0) && (mkdir(directories, S_IRWXU) != 0)) {
633 /* Can't free this! Otherwise error message is wrong! */
634 /* np_free(directories); */
635 die(STATE_UNKNOWN, _("Cannot create directory: %s"), directories);
636 }
637 *p = '/';
638 }
639 }
640
641 if (directories) {
642 free(directories);
643 }
644 }
645
646 char *temp_file = NULL;
647 result = asprintf(&temp_file, "%s.XXXXXX", stateKey._filename);
648 if (result < 0) {
649 die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
650 }
651
652 int temp_file_desc = 0;
653 if ((temp_file_desc = mkstemp(temp_file)) == -1) {
654 if (temp_file) {
655 free(temp_file);
656 }
657 die(STATE_UNKNOWN, _("Cannot create temporary filename"));
658 }
659
660 FILE *temp_file_pointer = fdopen(temp_file_desc, "w");
661 if (temp_file_pointer == NULL) {
662 close(temp_file_desc);
663 unlink(temp_file);
664 if (temp_file) {
665 free(temp_file);
666 }
667 die(STATE_UNKNOWN, _("Unable to open temporary state file"));
668 }
669
670 fprintf(temp_file_pointer, "# NP State file\n");
671 fprintf(temp_file_pointer, "%d\n", NP_STATE_FORMAT_VERSION);
672 fprintf(temp_file_pointer, "%d\n", stateKey.data_version);
673 fprintf(temp_file_pointer, "%lu\n", current_time);
674 fprintf(temp_file_pointer, "%s\n", stringToStore);
675
676 fchmod(temp_file_desc, S_IRUSR | S_IWUSR | S_IRGRP);
677
678 fflush(temp_file_pointer);
679
680 result = fclose(temp_file_pointer);
681
682 fsync(temp_file_desc);
683
684 if (result != 0) {
685 unlink(temp_file);
686 if (temp_file) {
687 free(temp_file);
688 }
689 die(STATE_UNKNOWN, _("Error writing temp file"));
690 }
691
692 if (rename(temp_file, stateKey._filename) != 0) {
693 unlink(temp_file);
694 if (temp_file) {
695 free(temp_file);
696 }
697 die(STATE_UNKNOWN, _("Cannot rename state temp file"));
698 }
699
700 if (temp_file) {
701 free(temp_file);
702 }
703}
704
705/*
706 * Read the state file
707 */
708bool _np_state_read_file(FILE *state_file, state_key stateKey) {
709 time_t current_time;
710 time(&current_time);
711
712 /* Note: This introduces a limit of 8192 bytes in the string data */
713 char *line = (char *)calloc(1, 8192);
714 if (line == NULL) {
715 die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
716 }
717
718 bool status = false;
719 enum {
720 STATE_FILE_VERSION,
721 STATE_DATA_VERSION,
722 STATE_DATA_TIME,
723 STATE_DATA_TEXT,
724 STATE_DATA_END
725 } expected = STATE_FILE_VERSION;
726
727 int failure = 0;
728 while (!failure && (fgets(line, 8192, state_file)) != NULL) {
729 size_t pos = strlen(line);
730 if (line[pos - 1] == '\n') {
731 line[pos - 1] = '\0';
732 }
733
734 if (line[0] == '#') {
735 continue;
736 }
737
738 switch (expected) {
739 case STATE_FILE_VERSION: {
740 int i = atoi(line);
741 if (i != NP_STATE_FORMAT_VERSION) {
742 failure++;
743 } else {
744 expected = STATE_DATA_VERSION;
745 }
746 } break;
747 case STATE_DATA_VERSION: {
748 int i = atoi(line);
749 if (i != stateKey.data_version) {
750 failure++;
751 } else {
752 expected = STATE_DATA_TIME;
753 }
754 } break;
755 case STATE_DATA_TIME: {
756 /* If time > now, error */
757 time_t data_time = strtoul(line, NULL, 10);
758 if (data_time > current_time) {
759 failure++;
760 } else {
761 stateKey.state_data->time = data_time;
762 expected = STATE_DATA_TEXT;
763 }
764 } break;
765 case STATE_DATA_TEXT:
766 stateKey.state_data->data = strdup(line);
767 if (stateKey.state_data->data == NULL) {
768 die(STATE_UNKNOWN, _("Cannot execute strdup: %s"), strerror(errno));
769 }
770 stateKey.state_data->length = strlen(line);
771 expected = STATE_DATA_END;
772 status = true;
773 break;
774 case STATE_DATA_END:;
775 }
776 }
777
778 if (line) {
779 free(line);
780 }
781 return status;
782}
783/*
784 * Will return NULL if no data is available (first run). If key currently
785 * exists, read data. If state file format version is not expected, return
786 * as if no data. Get state data version number and compares to expected.
787 * If numerically lower, then return as no previous state. die with UNKNOWN
788 * if exceptional error.
789 */
790state_data *np_state_read(state_key stateKey) {
791 /* Open file. If this fails, no previous state found */
792 FILE *statefile = fopen(stateKey._filename, "r");
793 state_data *this_state_data = (state_data *)calloc(1, sizeof(state_data));
794 if (statefile != NULL) {
795
796 if (this_state_data == NULL) {
797 die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
798 }
799
800 this_state_data->data = NULL;
801 stateKey.state_data = this_state_data;
802
803 if (_np_state_read_file(statefile, stateKey)) {
804 this_state_data->errorcode = OK;
805 } else {
806 this_state_data->errorcode = ERROR;
807 }
808
809 fclose(statefile);
810 } else {
811 // Failed to open state file
812 this_state_data->errorcode = ERROR;
813 }
814
815 return stateKey.state_data;
816}
817
818/*
819 * Internal function. Returns either:
820 * envvar NAGIOS_PLUGIN_STATE_DIRECTORY
821 * statically compiled shared state directory
822 */
823char *_np_state_calculate_location_prefix(void) {
824 char *env_dir;
825
826 /* Do not allow passing MP_STATE_PATH in setuid plugins
827 * for security reasons */
828 if (!mp_suid()) {
829 env_dir = getenv("MP_STATE_PATH");
830 if (env_dir && env_dir[0] != '\0') {
831 return env_dir;
832 }
833 /* This is the former ENV, for backward-compatibility */
834 env_dir = getenv("NAGIOS_PLUGIN_STATE_DIRECTORY");
835 if (env_dir && env_dir[0] != '\0') {
836 return env_dir;
837 }
838 }
839
840 return NP_STATE_DIR_PREFIX;
841}
842
843/*
844 * Initiatializer for state routines.
845 * Sets variables. Generates filename. Returns np_state_key. die with
846 * UNKNOWN if exception
847 */
848state_key np_enable_state(char *keyname, int expected_data_version, const char *plugin_name,
849 int argc, char **argv) {
850 state_key *this_state = (state_key *)calloc(1, sizeof(state_key));
851 if (this_state == NULL) {
852 die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
853 }
854
855 char *temp_keyname = NULL;
856 if (keyname == NULL) {
857 temp_keyname = _np_state_generate_key(argc, argv);
858 } else {
859 temp_keyname = strdup(keyname);
860 if (temp_keyname == NULL) {
861 die(STATE_UNKNOWN, _("Cannot execute strdup: %s"), strerror(errno));
862 }
863 }
864
865 /* Die if invalid characters used for keyname */
866 char *tmp_char = temp_keyname;
867 while (*tmp_char != '\0') {
868 if (!(isalnum(*tmp_char) || *tmp_char == '_')) {
869 die(STATE_UNKNOWN, _("Invalid character for keyname - only alphanumerics or '_'"));
870 }
871 tmp_char++;
872 }
873 this_state->name = temp_keyname;
874 this_state->plugin_name = (char *)plugin_name;
875 this_state->data_version = expected_data_version;
876 this_state->state_data = NULL;
877
878 /* Calculate filename */
879 char *temp_filename = NULL;
880 int error = asprintf(&temp_filename, "%s/%lu/%s/%s", _np_state_calculate_location_prefix(),
881 (unsigned long)geteuid(), plugin_name, this_state->name);
882 if (error < 0) {
883 die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
884 }
885
886 this_state->_filename = temp_filename;
887
888 return *this_state;
889}
890
891/*
892 * Returns a string to use as a keyname, based on an md5 hash of argv, thus
893 * hopefully a unique key per service/plugin invocation. Use the extra-opts
894 * parse of argv, so that uniqueness in parameters are reflected there.
895 */
896char *_np_state_generate_key(int argc, char **argv) {
897 unsigned char result[256];
898
899#ifdef USE_OPENSSL
900 /*
901 * This code path is chosen if openssl is available (which should be the most common
902 * scenario). Alternatively, the gnulib implementation/
903 *
904 */
905 EVP_MD_CTX *ctx = EVP_MD_CTX_new();
906
907 EVP_DigestInit(ctx, EVP_sha256());
908
909 for (int i = 0; i < argc; i++) {
910 EVP_DigestUpdate(ctx, argv[i], strlen(argv[i]));
911 }
912
913 EVP_DigestFinal(ctx, result, NULL);
914#else
915
916 struct sha256_ctx ctx;
917
918 for (int i = 0; i < this_monitoring_plugin->argc; i++) {
919 sha256_process_bytes(argv[i], strlen(argv[i]), &ctx);
920 }
921
922 sha256_finish_ctx(&ctx, result);
923#endif // FOUNDOPENSSL
924
925 char keyname[41];
926 for (int i = 0; i < 20; ++i) {
927 sprintf(&keyname[2 * i], "%02x", result[i]);
928 }
929
930 keyname[40] = '\0';
931
932 char *keyname_copy = strdup(keyname);
933 if (keyname_copy == NULL) {
934 die(STATE_UNKNOWN, _("Cannot execute strdup: %s"), strerror(errno));
935 }
936
937 return keyname_copy;
938}