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