summaryrefslogtreecommitdiffstats
path: root/plugins/check_snmp.d
diff options
context:
space:
mode:
authorLorenz Kästle <12514511+RincewindsHat@users.noreply.github.com>2025-09-08 15:57:06 +0200
committerLorenz Kästle <12514511+RincewindsHat@users.noreply.github.com>2025-09-08 15:57:06 +0200
commit87195f5511bf18db2a64f71ea9783ebbfb33c3a5 (patch)
tree491157b89647d73ed6acb0e4e2ae7cdf7fffb01c /plugins/check_snmp.d
parent1aefb1f9df5268ccbcd3ce38f5527ebca3896db6 (diff)
downloadmonitoring-plugins-87195f5511bf18db2a64f71ea9783ebbfb33c3a5.tar.gz
check_snmp: refactoring + fixes
This commit moves the state retention logic to check_snmp as it is only used there and I do not want it to be used at all, so it doesn't get a place in the lib. Otherwise this adapts tests and fixes the rate computing in the refactored version of check_snmp. Also fixes some bugs detected with the tests
Diffstat (limited to 'plugins/check_snmp.d')
-rw-r--r--plugins/check_snmp.d/check_snmp_helpers.c861
-rw-r--r--plugins/check_snmp.d/check_snmp_helpers.h64
-rw-r--r--plugins/check_snmp.d/config.h25
3 files changed, 922 insertions, 28 deletions
diff --git a/plugins/check_snmp.d/check_snmp_helpers.c b/plugins/check_snmp.d/check_snmp_helpers.c
index 8f4bcb9c..9fa396d8 100644
--- a/plugins/check_snmp.d/check_snmp_helpers.c
+++ b/plugins/check_snmp.d/check_snmp_helpers.c
@@ -1,6 +1,15 @@
1#include "./check_snmp_helpers.h" 1#include "./check_snmp_helpers.h"
2#include <string.h> 2#include <string.h>
3#include "../../lib/utils_base.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;
4 13
5check_snmp_test_unit check_snmp_test_unit_init() { 14check_snmp_test_unit check_snmp_test_unit_init() {
6 check_snmp_test_unit tmp = { 15 check_snmp_test_unit tmp = {
@@ -78,38 +87,848 @@ int check_snmp_set_thresholds(const char *threshold_string, check_snmp_test_unit
78const int DEFAULT_PROTOCOL = SNMP_VERSION_1; 87const int DEFAULT_PROTOCOL = SNMP_VERSION_1;
79const char DEFAULT_OUTPUT_DELIMITER[] = " "; 88const char DEFAULT_OUTPUT_DELIMITER[] = " ";
80 89
81const int RANDOM_STATE_DATA_LENGTH_PREDICTION = 1024; 90const int RANDOM_STATE_DATA_LENGTH_PREDICTION = 8192;
82 91
83check_snmp_config check_snmp_config_init() { 92check_snmp_config check_snmp_config_init() {
84 check_snmp_config tmp = { 93 check_snmp_config tmp = {
85 .use_getnext = false, 94 .snmp_params =
95 {
96 .use_getnext = false,
97
98 .ignore_mib_parsing_errors = false,
99 .need_mibs = false,
86 100
87 .ignore_mib_parsing_errors = false, 101 .test_units = NULL,
88 .need_mibs = false, 102 .num_of_test_units = 0,
103 },
89 104
90 .test_units = NULL, 105 .evaluation_params =
91 .num_of_test_units = 0, 106 {
107 .nulloid_result = STATE_UNKNOWN, // state to return if no result for query
92 108
93 .nulloid_result = STATE_UNKNOWN, // state to return if no result for query 109 .invert_search = true,
110 .regex_cmp_value = {},
111 .string_cmp_value = "",
94 112
95 .invert_search = true, 113 .multiplier = 1.0,
96 .regex_cmp_value = {}, 114 .multiplier_set = false,
97 .string_cmp_value = "", 115 .offset = 0,
116 .offset_set = false,
98 117
99 .multiplier = 1.0, 118 .use_oid_as_perf_data_label = false,
100 .multiplier_set = false,
101 .offset = 0,
102 .offset_set = false,
103 119
104 .use_perf_data_labels_from_input = false, 120 .calculate_rate = false,
121 .rate_multiplier = 1,
122 },
105 }; 123 };
106 124
107 snmp_sess_init(&tmp.snmp_session); 125 snmp_sess_init(&tmp.snmp_params.snmp_session);
108 126
109 tmp.snmp_session.retries = DEFAULT_RETRIES; 127 tmp.snmp_params.snmp_session.retries = DEFAULT_RETRIES;
110 tmp.snmp_session.version = DEFAULT_SNMP_VERSION; 128 tmp.snmp_params.snmp_session.version = DEFAULT_SNMP_VERSION;
111 tmp.snmp_session.securityLevel = SNMP_SEC_LEVEL_NOAUTH; 129 tmp.snmp_params.snmp_session.securityLevel = SNMP_SEC_LEVEL_NOAUTH;
112 tmp.snmp_session.community = (unsigned char *)"public"; 130 tmp.snmp_params.snmp_session.community = (unsigned char *)"public";
113 tmp.snmp_session.community_len = strlen("public"); 131 tmp.snmp_params.snmp_session.community_len = strlen("public");
114 return tmp; 132 return tmp;
115} 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 }
278 case ASN_IPADDRESS:
279 if (verbose) {
280 printf("Debug: Got an IP address\n");
281 }
282 result.response_values[loop_index].type = vars->type;
283
284 // TODO: print address here, state always ok? or regex match?
285 continue;
286 default:
287 if (verbose) {
288 printf("Debug: Got a unmatched result type: %hhu\n", vars->type);
289 }
290 // TODO: Error here?
291 continue;
292 }
293 }
294
295 return result;
296}
297
298check_snmp_evaluation evaluate_single_unit(response_value response,
299 check_snmp_evaluation_parameters eval_params,
300 check_snmp_test_unit test_unit, time_t query_timestamp,
301 check_snmp_state_entry prev_state,
302 bool have_previous_state) {
303 mp_subcheck sc_oid_test = mp_subcheck_init();
304
305 if ((test_unit.label != NULL) && (strcmp(test_unit.label, "") != 0)) {
306 xasprintf(&sc_oid_test.output, "%s - ", test_unit.label);
307 } else {
308 sc_oid_test.output = strdup("");
309 }
310
311 char oid_string[(MAX_OID_LEN * 2) + 1] = {};
312
313 int oid_string_result =
314 snprint_objid(oid_string, (MAX_OID_LEN * 2) + 1, response.oid, response.oid_length);
315 if (oid_string_result <= 0) {
316 // TODO error here
317 die(STATE_UNKNOWN, "snprint_objid failed\n");
318 }
319
320 xasprintf(&sc_oid_test.output, "%sOID: %s", sc_oid_test.output, oid_string);
321 sc_oid_test = mp_set_subcheck_default_state(sc_oid_test, STATE_OK);
322
323 if (verbose > 2) {
324 printf("Processing oid %s\n", oid_string);
325 }
326
327 bool got_a_numerical_value = false;
328 mp_perfdata_value pd_result_val = {0};
329
330 check_snmp_state_entry result_state = {
331 .timestamp = query_timestamp,
332 .oid_length = response.oid_length,
333 .type = response.type,
334 };
335
336 for (size_t i = 0; i < response.oid_length; i++) {
337 result_state.oid[i] = response.oid[i];
338 }
339
340 if (have_previous_state) {
341 if (query_timestamp == prev_state.timestamp) {
342 // somehow we have the same timestamp again, that can't be good
343 sc_oid_test = mp_set_subcheck_state(sc_oid_test, STATE_UNKNOWN);
344 xasprintf(&sc_oid_test.output, "Time duration between plugin calls is invalid");
345
346 check_snmp_evaluation result = {
347 .sc = sc_oid_test,
348 .state = result_state,
349 };
350
351 return result;
352 }
353 }
354 // compute rate time difference
355 double timeDiff = 0;
356 if (have_previous_state) {
357 if (verbose) {
358 printf("Previous timestamp: %s", ctime(&prev_state.timestamp));
359 printf("Current timestamp: %s", ctime(&query_timestamp));
360 }
361 timeDiff = difftime(query_timestamp, prev_state.timestamp) / eval_params.rate_multiplier;
362 }
363
364 mp_perfdata pd_num_val = {};
365
366 switch (response.type) {
367 case ASN_OCTET_STR: {
368 char *tmp = response.string_response;
369 if (strchr(tmp, '"') != NULL) {
370 // got double quote in the string
371 if (strchr(tmp, '\'') != NULL) {
372 // got single quote in the string too
373 // dont quote that at all to avoid even more confusion
374 xasprintf(&sc_oid_test.output, "%s - Value: %s", sc_oid_test.output, tmp);
375 } else {
376 // quote with single quotes
377 xasprintf(&sc_oid_test.output, "%s - Value: '%s'", sc_oid_test.output, tmp);
378 }
379 } else {
380 // quote with double quotes
381 xasprintf(&sc_oid_test.output, "%s - Value: \"%s\"", sc_oid_test.output, tmp);
382 }
383
384 if (strlen(tmp) == 0) {
385 sc_oid_test = mp_set_subcheck_state(sc_oid_test, eval_params.nulloid_result);
386 }
387
388 // String matching test
389 if ((test_unit.eval_mthd.crit_string)) {
390 if (strcmp(tmp, eval_params.string_cmp_value)) {
391 sc_oid_test = mp_set_subcheck_state(
392 sc_oid_test, (eval_params.invert_search) ? STATE_CRITICAL : STATE_OK);
393 } else {
394 sc_oid_test = mp_set_subcheck_state(
395 sc_oid_test, (eval_params.invert_search) ? STATE_OK : STATE_CRITICAL);
396 }
397 } else if (test_unit.eval_mthd.crit_regex) {
398 const size_t nmatch = eval_params.regex_cmp_value.re_nsub + 1;
399 regmatch_t pmatch[nmatch];
400 memset(pmatch, '\0', sizeof(regmatch_t) * nmatch);
401
402 int excode = regexec(&eval_params.regex_cmp_value, tmp, nmatch, pmatch, 0);
403 if (excode == 0) {
404 sc_oid_test = mp_set_subcheck_state(
405 sc_oid_test, (eval_params.invert_search) ? STATE_OK : STATE_CRITICAL);
406 } else if (excode != REG_NOMATCH) {
407 char errbuf[MAX_INPUT_BUFFER] = "";
408 regerror(excode, &eval_params.regex_cmp_value, errbuf, MAX_INPUT_BUFFER);
409 printf(_("Execute Error: %s\n"), errbuf);
410 exit(STATE_CRITICAL);
411 } else { // REG_NOMATCH
412 sc_oid_test = mp_set_subcheck_state(
413 sc_oid_test, eval_params.invert_search ? STATE_CRITICAL : STATE_OK);
414 }
415 }
416 } break;
417 case ASN_COUNTER64:
418 got_a_numerical_value = true;
419
420 result_state.value.uIntVal = response.value.uIntVal;
421 result_state.type = response.type;
422
423 // TODO: perfdata unit counter
424 if (eval_params.calculate_rate && have_previous_state) {
425 if (prev_state.value.uIntVal > response.value.uIntVal) {
426 // overflow
427 unsigned long long tmp =
428 (UINT64_MAX - prev_state.value.uIntVal) + response.value.uIntVal;
429
430 tmp /= timeDiff;
431 pd_result_val = mp_create_pd_value(tmp);
432 } else {
433 pd_result_val = mp_create_pd_value(
434 (response.value.uIntVal - prev_state.value.uIntVal) / timeDiff);
435 }
436 } else {
437 // It's only a counter if we cont compute rate
438 pd_num_val.uom = "c";
439 pd_result_val = mp_create_pd_value(response.value.uIntVal);
440 }
441 break;
442 case ASN_GAUGE: // same as ASN_UNSIGNED
443 case ASN_TIMETICKS:
444 case ASN_COUNTER:
445 case ASN_UINTEGER: {
446 got_a_numerical_value = true;
447 long long treated_value = (long long)response.value.uIntVal;
448
449 if (eval_params.multiplier_set || eval_params.offset_set) {
450 double processed = 0;
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 = 0;
485 if (eval_params.multiplier_set) {
486 processed = (double)response.value.intVal * eval_params.multiplier;
487 }
488
489 if (eval_params.offset_set) {
490 processed += eval_params.offset;
491 }
492
493 result_state.value.doubleVal = processed;
494
495 if (eval_params.calculate_rate && have_previous_state) {
496 pd_result_val =
497 mp_create_pd_value((processed - prev_state.value.doubleVal) / timeDiff);
498 } else {
499 pd_result_val = mp_create_pd_value(processed);
500 }
501 } else {
502 result_state.value.intVal = response.value.intVal;
503
504 if (eval_params.calculate_rate && have_previous_state) {
505 pd_result_val = mp_create_pd_value(
506 (response.value.intVal - prev_state.value.intVal) / timeDiff);
507 } else {
508 pd_result_val = mp_create_pd_value(response.value.intVal);
509 }
510 }
511
512 got_a_numerical_value = true;
513 } break;
514 case ASN_FLOAT: // fallthrough
515 case ASN_DOUBLE: {
516 got_a_numerical_value = true;
517 double tmp = response.value.doubleVal;
518 if (eval_params.offset_set) {
519 tmp += eval_params.offset;
520 }
521
522 if (eval_params.multiplier_set) {
523 tmp *= eval_params.multiplier;
524 }
525
526 if (eval_params.calculate_rate && have_previous_state) {
527 pd_result_val = mp_create_pd_value((tmp - prev_state.value.doubleVal) / timeDiff);
528 } else {
529 pd_result_val = mp_create_pd_value(tmp);
530 }
531 got_a_numerical_value = true;
532
533 result_state.value.doubleVal = tmp;
534 } break;
535 case ASN_IPADDRESS:
536 // TODO
537 }
538
539 if (got_a_numerical_value) {
540 if (eval_params.use_oid_as_perf_data_label) {
541 // Use oid for perdata label
542 pd_num_val.label = strdup(oid_string);
543 // TODO strdup error checking
544 } else if (test_unit.label != NULL && strcmp(test_unit.label, "") != 0) {
545 pd_num_val.label = strdup(test_unit.label);
546 } else {
547 pd_num_val.label = strdup(test_unit.oid);
548 }
549
550 if (!(eval_params.calculate_rate && !have_previous_state)) {
551 // some kind of numerical value
552 if (test_unit.unit_value != NULL && strcmp(test_unit.unit_value, "") != 0) {
553 pd_num_val.uom = test_unit.unit_value;
554 }
555
556 pd_num_val.value = pd_result_val;
557
558 xasprintf(&sc_oid_test.output, "%s Value: %s", sc_oid_test.output,
559 pd_value_to_string(pd_result_val));
560
561 if (test_unit.unit_value != NULL && strcmp(test_unit.unit_value, "") != 0) {
562 xasprintf(&sc_oid_test.output, "%s%s", sc_oid_test.output, test_unit.unit_value);
563 }
564
565 if (test_unit.threshold.warning_is_set || test_unit.threshold.critical_is_set) {
566 pd_num_val = mp_pd_set_thresholds(pd_num_val, test_unit.threshold);
567 mp_state_enum tmp_state = mp_get_pd_status(pd_num_val);
568
569 if (tmp_state == STATE_WARNING) {
570 sc_oid_test = mp_set_subcheck_state(sc_oid_test, STATE_WARNING);
571 xasprintf(&sc_oid_test.output, "%s - number violates warning threshold",
572 sc_oid_test.output);
573 } else if (tmp_state == STATE_CRITICAL) {
574 sc_oid_test = mp_set_subcheck_state(sc_oid_test, STATE_CRITICAL);
575 xasprintf(&sc_oid_test.output, "%s - number violates critical threshold",
576 sc_oid_test.output);
577 }
578 }
579
580 mp_add_perfdata_to_subcheck(&sc_oid_test, pd_num_val);
581 } else {
582 // should calculate rate, but there is no previous state, so first run
583 // exit with ok now
584 sc_oid_test = mp_set_subcheck_state(sc_oid_test, STATE_OK);
585 xasprintf(&sc_oid_test.output, "%s - No previous data to calculate rate - assume okay",
586 sc_oid_test.output);
587 }
588 }
589
590 check_snmp_evaluation result = {
591 .sc = sc_oid_test,
592 .state = result_state,
593 };
594
595 return result;
596}
597
598char *_np_state_generate_key(int argc, char **argv);
599
600/*
601 * If time=NULL, use current time. Create state file, with state format
602 * version, default text. Writes version, time, and data. Avoid locking
603 * problems - use mv to write and then swap. Possible loss of state data if
604 * two things writing to same key at same time.
605 * Will die with UNKNOWN if errors
606 */
607void np_state_write_string(state_key stateKey, time_t timestamp, char *stringToStore) {
608 time_t current_time;
609 if (timestamp == 0) {
610 time(&current_time);
611 } else {
612 current_time = timestamp;
613 }
614
615 int result = 0;
616
617 /* If file doesn't currently exist, create directories */
618 if (access(stateKey._filename, F_OK) != 0) {
619 char *directories = NULL;
620 result = asprintf(&directories, "%s", stateKey._filename);
621 if (result < 0) {
622 die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
623 }
624
625 for (char *p = directories + 1; *p; p++) {
626 if (*p == '/') {
627 *p = '\0';
628 if ((access(directories, F_OK) != 0) && (mkdir(directories, S_IRWXU) != 0)) {
629 /* Can't free this! Otherwise error message is wrong! */
630 /* np_free(directories); */
631 die(STATE_UNKNOWN, _("Cannot create directory: %s"), directories);
632 }
633 *p = '/';
634 }
635 }
636
637 if (directories) {
638 free(directories);
639 }
640 }
641
642 char *temp_file = NULL;
643 result = asprintf(&temp_file, "%s.XXXXXX", stateKey._filename);
644 if (result < 0) {
645 die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
646 }
647
648 int temp_file_desc = 0;
649 if ((temp_file_desc = mkstemp(temp_file)) == -1) {
650 if (temp_file) {
651 free(temp_file);
652 }
653 die(STATE_UNKNOWN, _("Cannot create temporary filename"));
654 }
655
656 FILE *temp_file_pointer = fdopen(temp_file_desc, "w");
657 if (temp_file_pointer == NULL) {
658 close(temp_file_desc);
659 unlink(temp_file);
660 if (temp_file) {
661 free(temp_file);
662 }
663 die(STATE_UNKNOWN, _("Unable to open temporary state file"));
664 }
665
666 fprintf(temp_file_pointer, "# NP State file\n");
667 fprintf(temp_file_pointer, "%d\n", NP_STATE_FORMAT_VERSION);
668 fprintf(temp_file_pointer, "%d\n", stateKey.data_version);
669 fprintf(temp_file_pointer, "%lu\n", current_time);
670 fprintf(temp_file_pointer, "%s\n", stringToStore);
671
672 fchmod(temp_file_desc, S_IRUSR | S_IWUSR | S_IRGRP);
673
674 fflush(temp_file_pointer);
675
676 result = fclose(temp_file_pointer);
677
678 fsync(temp_file_desc);
679
680 if (result != 0) {
681 unlink(temp_file);
682 if (temp_file) {
683 free(temp_file);
684 }
685 die(STATE_UNKNOWN, _("Error writing temp file"));
686 }
687
688 if (rename(temp_file, stateKey._filename) != 0) {
689 unlink(temp_file);
690 if (temp_file) {
691 free(temp_file);
692 }
693 die(STATE_UNKNOWN, _("Cannot rename state temp file"));
694 }
695
696 if (temp_file) {
697 free(temp_file);
698 }
699}
700
701/*
702 * Read the state file
703 */
704bool _np_state_read_file(FILE *state_file, state_key stateKey) {
705 time_t current_time;
706 time(&current_time);
707
708 /* Note: This introduces a limit of 8192 bytes in the string data */
709 char *line = (char *)calloc(1, 8192);
710 if (line == NULL) {
711 die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
712 }
713
714 bool status = false;
715 enum {
716 STATE_FILE_VERSION,
717 STATE_DATA_VERSION,
718 STATE_DATA_TIME,
719 STATE_DATA_TEXT,
720 STATE_DATA_END
721 } expected = STATE_FILE_VERSION;
722
723 int failure = 0;
724 while (!failure && (fgets(line, 8192, state_file)) != NULL) {
725 size_t pos = strlen(line);
726 if (line[pos - 1] == '\n') {
727 line[pos - 1] = '\0';
728 }
729
730 if (line[0] == '#') {
731 continue;
732 }
733
734 switch (expected) {
735 case STATE_FILE_VERSION: {
736 int i = atoi(line);
737 if (i != NP_STATE_FORMAT_VERSION) {
738 failure++;
739 } else {
740 expected = STATE_DATA_VERSION;
741 }
742 } break;
743 case STATE_DATA_VERSION: {
744 int i = atoi(line);
745 if (i != stateKey.data_version) {
746 failure++;
747 } else {
748 expected = STATE_DATA_TIME;
749 }
750 } break;
751 case STATE_DATA_TIME: {
752 /* If time > now, error */
753 time_t data_time = strtoul(line, NULL, 10);
754 if (data_time > current_time) {
755 failure++;
756 } else {
757 stateKey.state_data->time = data_time;
758 expected = STATE_DATA_TEXT;
759 }
760 } break;
761 case STATE_DATA_TEXT:
762 stateKey.state_data->data = strdup(line);
763 if (stateKey.state_data->data == NULL) {
764 die(STATE_UNKNOWN, _("Cannot execute strdup: %s"), strerror(errno));
765 }
766 stateKey.state_data->length = strlen(line);
767 expected = STATE_DATA_END;
768 status = true;
769 break;
770 case STATE_DATA_END:;
771 }
772 }
773
774 if (line) {
775 free(line);
776 }
777 return status;
778}
779/*
780 * Will return NULL if no data is available (first run). If key currently
781 * exists, read data. If state file format version is not expected, return
782 * as if no data. Get state data version number and compares to expected.
783 * If numerically lower, then return as no previous state. die with UNKNOWN
784 * if exceptional error.
785 */
786state_data *np_state_read(state_key stateKey) {
787 /* Open file. If this fails, no previous state found */
788 FILE *statefile = fopen(stateKey._filename, "r");
789 state_data *this_state_data = (state_data *)calloc(1, sizeof(state_data));
790 if (statefile != NULL) {
791
792 if (this_state_data == NULL) {
793 die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
794 }
795
796 this_state_data->data = NULL;
797 stateKey.state_data = this_state_data;
798
799 if (_np_state_read_file(statefile, stateKey)) {
800 this_state_data->errorcode = OK;
801 } else {
802 this_state_data->errorcode = ERROR;
803 }
804
805 fclose(statefile);
806 } else {
807 // Failed to open state file
808 this_state_data->errorcode = ERROR;
809 }
810
811 return stateKey.state_data;
812}
813
814/*
815 * Internal function. Returns either:
816 * envvar NAGIOS_PLUGIN_STATE_DIRECTORY
817 * statically compiled shared state directory
818 */
819char *_np_state_calculate_location_prefix(void) {
820 char *env_dir;
821
822 /* Do not allow passing MP_STATE_PATH in setuid plugins
823 * for security reasons */
824 if (!mp_suid()) {
825 env_dir = getenv("MP_STATE_PATH");
826 if (env_dir && env_dir[0] != '\0') {
827 return env_dir;
828 }
829 /* This is the former ENV, for backward-compatibility */
830 env_dir = getenv("NAGIOS_PLUGIN_STATE_DIRECTORY");
831 if (env_dir && env_dir[0] != '\0') {
832 return env_dir;
833 }
834 }
835
836 return NP_STATE_DIR_PREFIX;
837}
838
839/*
840 * Initiatializer for state routines.
841 * Sets variables. Generates filename. Returns np_state_key. die with
842 * UNKNOWN if exception
843 */
844state_key np_enable_state(char *keyname, int expected_data_version, char *plugin_name, int argc,
845 char **argv) {
846 state_key *this_state = (state_key *)calloc(1, sizeof(state_key));
847 if (this_state == NULL) {
848 die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
849 }
850
851 char *temp_keyname = NULL;
852 if (keyname == NULL) {
853 temp_keyname = _np_state_generate_key(argc, argv);
854 } else {
855 temp_keyname = strdup(keyname);
856 if (temp_keyname == NULL) {
857 die(STATE_UNKNOWN, _("Cannot execute strdup: %s"), strerror(errno));
858 }
859 }
860
861 /* Die if invalid characters used for keyname */
862 char *tmp_char = temp_keyname;
863 while (*tmp_char != '\0') {
864 if (!(isalnum(*tmp_char) || *tmp_char == '_')) {
865 die(STATE_UNKNOWN, _("Invalid character for keyname - only alphanumerics or '_'"));
866 }
867 tmp_char++;
868 }
869 this_state->name = temp_keyname;
870 this_state->plugin_name = plugin_name;
871 this_state->data_version = expected_data_version;
872 this_state->state_data = NULL;
873
874 /* Calculate filename */
875 char *temp_filename = NULL;
876 int error = asprintf(&temp_filename, "%s/%lu/%s/%s", _np_state_calculate_location_prefix(),
877 (unsigned long)geteuid(), plugin_name, this_state->name);
878 if (error < 0) {
879 die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno));
880 }
881
882 this_state->_filename = temp_filename;
883
884 return *this_state;
885}
886
887/*
888 * Returns a string to use as a keyname, based on an md5 hash of argv, thus
889 * hopefully a unique key per service/plugin invocation. Use the extra-opts
890 * parse of argv, so that uniqueness in parameters are reflected there.
891 */
892char *_np_state_generate_key(int argc, char **argv) {
893 unsigned char result[256];
894
895#ifdef USE_OPENSSL
896 /*
897 * This code path is chosen if openssl is available (which should be the most common
898 * scenario). Alternatively, the gnulib implementation/
899 *
900 */
901 EVP_MD_CTX *ctx = EVP_MD_CTX_new();
902
903 EVP_DigestInit(ctx, EVP_sha256());
904
905 for (int i = 0; i < argc; i++) {
906 EVP_DigestUpdate(ctx, argv[i], strlen(argv[i]));
907 }
908
909 EVP_DigestFinal(ctx, result, NULL);
910#else
911
912 struct sha256_ctx ctx;
913
914 for (int i = 0; i < this_monitoring_plugin->argc; i++) {
915 sha256_process_bytes(argv[i], strlen(argv[i]), &ctx);
916 }
917
918 sha256_finish_ctx(&ctx, result);
919#endif // FOUNDOPENSSL
920
921 char keyname[41];
922 for (int i = 0; i < 20; ++i) {
923 sprintf(&keyname[2 * i], "%02x", result[i]);
924 }
925
926 keyname[40] = '\0';
927
928 char *keyname_copy = strdup(keyname);
929 if (keyname_copy == NULL) {
930 die(STATE_UNKNOWN, _("Cannot execute strdup: %s"), strerror(errno));
931 }
932
933 return keyname_copy;
934}
diff --git a/plugins/check_snmp.d/check_snmp_helpers.h b/plugins/check_snmp.d/check_snmp_helpers.h
index 28e3c4e3..0f7780b1 100644
--- a/plugins/check_snmp.d/check_snmp_helpers.h
+++ b/plugins/check_snmp.d/check_snmp_helpers.h
@@ -1,7 +1,71 @@
1#pragma once 1#pragma once
2 2
3#include "./config.h" 3#include "./config.h"
4#include <net-snmp/library/asn1.h>
4 5
5check_snmp_test_unit check_snmp_test_unit_init(); 6check_snmp_test_unit check_snmp_test_unit_init();
6int check_snmp_set_thresholds(const char *, check_snmp_test_unit[], size_t, bool); 7int check_snmp_set_thresholds(const char *, check_snmp_test_unit[], size_t, bool);
7check_snmp_config check_snmp_config_init(); 8check_snmp_config check_snmp_config_init();
9
10typedef struct {
11 oid oid[MAX_OID_LEN];
12 size_t oid_length;
13 unsigned char type;
14 union {
15 uint64_t uIntVal;
16 int64_t intVal;
17 double doubleVal;
18 } value;
19 char *string_response;
20} response_value;
21
22typedef struct {
23 int errorcode;
24 response_value *response_values;
25} snmp_responces;
26snmp_responces do_snmp_query(check_snmp_config_snmp_parameters parameters);
27
28// state is similar to response, but only numerics and a timestamp
29typedef struct {
30 time_t timestamp;
31 oid oid[MAX_OID_LEN];
32 size_t oid_length;
33 unsigned char type;
34 union {
35 unsigned long long uIntVal;
36 long long intVal;
37 double doubleVal;
38 } value;
39} check_snmp_state_entry;
40
41typedef struct {
42 check_snmp_state_entry state;
43 mp_subcheck sc;
44} check_snmp_evaluation;
45check_snmp_evaluation evaluate_single_unit(response_value response,
46 check_snmp_evaluation_parameters eval_params,
47 check_snmp_test_unit test_unit, time_t query_timestamp,
48 check_snmp_state_entry prev_state,
49 bool have_previous_state);
50
51#define NP_STATE_FORMAT_VERSION 1
52
53typedef struct state_data_struct {
54 time_t time;
55 void *data;
56 size_t length; /* Of binary data */
57 int errorcode;
58} state_data;
59
60typedef struct state_key_struct {
61 char *name;
62 char *plugin_name;
63 int data_version;
64 char *_filename;
65 state_data *state_data;
66} state_key;
67
68state_data *np_state_read(state_key stateKey);
69state_key np_enable_state(char *keyname, int expected_data_version, char *plugin_name, int argc,
70 char **argv);
71void np_state_write_string(state_key stateKey, time_t timestamp, char *stringToStore);
diff --git a/plugins/check_snmp.d/config.h b/plugins/check_snmp.d/config.h
index e68986e2..a5d5aa52 100644
--- a/plugins/check_snmp.d/config.h
+++ b/plugins/check_snmp.d/config.h
@@ -1,7 +1,7 @@
1#pragma once 1#pragma once
2 2
3#include "thresholds.h" 3#include "../../lib/thresholds.h"
4#include "states.h" 4#include "../../lib/states.h"
5#include <stdlib.h> 5#include <stdlib.h>
6#include <stdbool.h> 6#include <stdbool.h>
7#include <regex.h> 7#include <regex.h>
@@ -18,7 +18,7 @@
18#include <net-snmp/library/snmp.h> 18#include <net-snmp/library/snmp.h>
19#include <net-snmp/session_api.h> 19#include <net-snmp/session_api.h>
20 20
21#define DEFAULT_PORT "161" 21#define DEFAULT_PORT "161"
22#define DEFAULT_RETRIES 5 22#define DEFAULT_RETRIES 5
23 23
24typedef struct eval_method { 24typedef struct eval_method {
@@ -34,10 +34,8 @@ typedef struct check_snmp_test_unit {
34 mp_thresholds threshold; 34 mp_thresholds threshold;
35} check_snmp_test_unit; 35} check_snmp_test_unit;
36 36
37typedef struct check_snmp_config { 37typedef struct {
38 // SNMP session to use
39 struct snmp_session snmp_session; 38 struct snmp_session snmp_session;
40
41 // use getnet instead of get 39 // use getnet instead of get
42 bool use_getnext; 40 bool use_getnext;
43 41
@@ -47,7 +45,9 @@ typedef struct check_snmp_config {
47 45
48 check_snmp_test_unit *test_units; 46 check_snmp_test_unit *test_units;
49 size_t num_of_test_units; 47 size_t num_of_test_units;
48} check_snmp_config_snmp_parameters;
50 49
50typedef struct {
51 // State if an empty value is encountered 51 // State if an empty value is encountered
52 mp_state_enum nulloid_result; 52 mp_state_enum nulloid_result;
53 53
@@ -63,7 +63,18 @@ typedef struct check_snmp_config {
63 bool offset_set; 63 bool offset_set;
64 64
65 // Modify output 65 // Modify output
66 bool use_perf_data_labels_from_input; 66 bool use_oid_as_perf_data_label;
67
68 // activate rate calucation
69 bool calculate_rate;
70 unsigned int rate_multiplier;
71} check_snmp_evaluation_parameters;
72
73typedef struct check_snmp_config {
74 // SNMP session to use
75 check_snmp_config_snmp_parameters snmp_params;
76
77 check_snmp_evaluation_parameters evaluation_params;
67 78
68 mp_output_format output_format; 79 mp_output_format output_format;
69 bool output_format_is_set; 80 bool output_format_is_set;