diff options
Diffstat (limited to 'plugins')
| -rw-r--r-- | plugins/check_dig.c | 268 | ||||
| -rw-r--r-- | plugins/check_dig.d/config.h | 10 |
2 files changed, 273 insertions, 5 deletions
diff --git a/plugins/check_dig.c b/plugins/check_dig.c index c27e5f13..9ea19e6a 100644 --- a/plugins/check_dig.c +++ b/plugins/check_dig.c | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | * Monitoring check_dig plugin | 3 | * Monitoring check_dig plugin |
| 4 | * | 4 | * |
| 5 | * License: GPL | 5 | * License: GPL |
| 6 | * Copyright (c) 2002-2024 Monitoring Plugins Development Team | 6 | * Copyright (c) 2002-2025 Monitoring Plugins Development Team |
| 7 | * | 7 | * |
| 8 | * Description: | 8 | * Description: |
| 9 | * | 9 | * |
| @@ -33,9 +33,10 @@ | |||
| 33 | * because on some architectures those strings are in non-writable memory */ | 33 | * because on some architectures those strings are in non-writable memory */ |
| 34 | 34 | ||
| 35 | const char *progname = "check_dig"; | 35 | const char *progname = "check_dig"; |
| 36 | const char *copyright = "2002-2024"; | 36 | const char *copyright = "2002-2025"; |
| 37 | const char *email = "devel@monitoring-plugins.org"; | 37 | const char *email = "devel@monitoring-plugins.org"; |
| 38 | 38 | ||
| 39 | #include <ctype.h> | ||
| 39 | #include "common.h" | 40 | #include "common.h" |
| 40 | #include "netutils.h" | 41 | #include "netutils.h" |
| 41 | #include "utils.h" | 42 | #include "utils.h" |
| @@ -56,6 +57,12 @@ void print_usage(void); | |||
| 56 | 57 | ||
| 57 | static int verbose = 0; | 58 | static int verbose = 0; |
| 58 | 59 | ||
| 60 | /* helpers for flag parsing */ | ||
| 61 | static flag_list parse_flags_line(const char *line); | ||
| 62 | static flag_list split_csv_trim(const char *csv); | ||
| 63 | static bool flag_list_contains(const flag_list *list, const char *needle); | ||
| 64 | static void free_flag_list(flag_list *list); | ||
| 65 | |||
| 59 | int main(int argc, char **argv) { | 66 | int main(int argc, char **argv) { |
| 60 | setlocale(LC_ALL, ""); | 67 | setlocale(LC_ALL, ""); |
| 61 | bindtextdomain(PACKAGE, LOCALEDIR); | 68 | bindtextdomain(PACKAGE, LOCALEDIR); |
| @@ -101,13 +108,35 @@ int main(int argc, char **argv) { | |||
| 101 | output chld_out; | 108 | output chld_out; |
| 102 | output chld_err; | 109 | output chld_err; |
| 103 | char *msg = NULL; | 110 | char *msg = NULL; |
| 111 | flag_list dig_flags = {.items = NULL, .count = 0}; | ||
| 104 | mp_state_enum result = STATE_UNKNOWN; | 112 | mp_state_enum result = STATE_UNKNOWN; |
| 113 | |||
| 105 | /* run the command */ | 114 | /* run the command */ |
| 106 | if (np_runcmd(command_line, &chld_out, &chld_err, 0) != 0) { | 115 | if (np_runcmd(command_line, &chld_out, &chld_err, 0) != 0) { |
| 107 | result = STATE_WARNING; | 116 | result = STATE_WARNING; |
| 108 | msg = (char *)_("dig returned an error status"); | 117 | msg = (char *)_("dig returned an error status"); |
| 109 | } | 118 | } |
| 110 | 119 | ||
| 120 | /* extract ';; flags: ...' from stdout (first occurrence) */ | ||
| 121 | for (size_t i = 0; i < chld_out.lines; i++) { | ||
| 122 | if (strstr(chld_out.line[i], "flags:")) { | ||
| 123 | if (verbose) { | ||
| 124 | printf("Raw flags line: %s\n", chld_out.line[i]); | ||
| 125 | } | ||
| 126 | |||
| 127 | dig_flags = parse_flags_line(chld_out.line[i]); | ||
| 128 | |||
| 129 | if (verbose && dig_flags.count > 0) { | ||
| 130 | printf(_("Parsed flags:")); | ||
| 131 | for (size_t k = 0; k < dig_flags.count; k++) { | ||
| 132 | printf(" %s", dig_flags.items[k]); | ||
| 133 | } | ||
| 134 | printf("\n"); | ||
| 135 | } | ||
| 136 | break; | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 111 | for (size_t i = 0; i < chld_out.lines; i++) { | 140 | for (size_t i = 0; i < chld_out.lines; i++) { |
| 112 | /* the server is responding, we just got the host name... */ | 141 | /* the server is responding, we just got the host name... */ |
| 113 | if (strstr(chld_out.line[i], ";; ANSWER SECTION:")) { | 142 | if (strstr(chld_out.line[i], ";; ANSWER SECTION:")) { |
| @@ -174,6 +203,44 @@ int main(int argc, char **argv) { | |||
| 174 | result = STATE_WARNING; | 203 | result = STATE_WARNING; |
| 175 | } | 204 | } |
| 176 | 205 | ||
| 206 | /* Optional: evaluate dig flags only if -E/-X were provided */ | ||
| 207 | if ((config.require_flags.count > 0) || (config.forbid_flags.count > 0)) { | ||
| 208 | if (dig_flags.count > 0) { | ||
| 209 | for (size_t r = 0; r < config.require_flags.count; r++) { | ||
| 210 | if (!flag_list_contains(&dig_flags, config.require_flags.items[r])) { | ||
| 211 | result = STATE_CRITICAL; | ||
| 212 | if (!msg) { | ||
| 213 | xasprintf(&msg, _("Missing required DNS flag: %s"), | ||
| 214 | config.require_flags.items[r]); | ||
| 215 | } else { | ||
| 216 | char *newmsg = NULL; | ||
| 217 | xasprintf(&newmsg, _("%s; missing required DNS flag: %s"), msg, | ||
| 218 | config.require_flags.items[r]); | ||
| 219 | msg = newmsg; | ||
| 220 | } | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | for (size_t r = 0; r < config.forbid_flags.count; r++) { | ||
| 225 | if (flag_list_contains(&dig_flags, config.forbid_flags.items[r])) { | ||
| 226 | result = STATE_CRITICAL; | ||
| 227 | if (!msg) { | ||
| 228 | xasprintf(&msg, _("Forbidden DNS flag present: %s"), | ||
| 229 | config.forbid_flags.items[r]); | ||
| 230 | } else { | ||
| 231 | char *newmsg = NULL; | ||
| 232 | xasprintf(&newmsg, _("%s; forbidden DNS flag present: %s"), msg, | ||
| 233 | config.forbid_flags.items[r]); | ||
| 234 | msg = newmsg; | ||
| 235 | } | ||
| 236 | } | ||
| 237 | } | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | /* cleanup flags buffer */ | ||
| 242 | free_flag_list(&dig_flags); | ||
| 243 | |||
| 177 | printf("DNS %s - %.3f seconds response time (%s)|%s\n", state_text(result), elapsed_time, | 244 | printf("DNS %s - %.3f seconds response time (%s)|%s\n", state_text(result), elapsed_time, |
| 178 | msg ? msg : _("Probably a non-existent host/domain"), | 245 | msg ? msg : _("Probably a non-existent host/domain"), |
| 179 | fperfdata("time", elapsed_time, "s", (config.warning_interval > UNDEFINED), | 246 | fperfdata("time", elapsed_time, "s", (config.warning_interval > UNDEFINED), |
| @@ -190,6 +257,8 @@ check_dig_config_wrapper process_arguments(int argc, char **argv) { | |||
| 190 | {"critical", required_argument, 0, 'c'}, | 257 | {"critical", required_argument, 0, 'c'}, |
| 191 | {"timeout", required_argument, 0, 't'}, | 258 | {"timeout", required_argument, 0, 't'}, |
| 192 | {"dig-arguments", required_argument, 0, 'A'}, | 259 | {"dig-arguments", required_argument, 0, 'A'}, |
| 260 | {"require-flags", required_argument, 0, 'E'}, | ||
| 261 | {"forbid-flags", required_argument, 0, 'X'}, | ||
| 193 | {"verbose", no_argument, 0, 'v'}, | 262 | {"verbose", no_argument, 0, 'v'}, |
| 194 | {"version", no_argument, 0, 'V'}, | 263 | {"version", no_argument, 0, 'V'}, |
| 195 | {"help", no_argument, 0, 'h'}, | 264 | {"help", no_argument, 0, 'h'}, |
| @@ -212,7 +281,8 @@ check_dig_config_wrapper process_arguments(int argc, char **argv) { | |||
| 212 | 281 | ||
| 213 | int option = 0; | 282 | int option = 0; |
| 214 | while (true) { | 283 | while (true) { |
| 215 | int option_index = getopt_long(argc, argv, "hVvt:l:H:w:c:T:p:a:A:46", longopts, &option); | 284 | int option_index = |
| 285 | getopt_long(argc, argv, "hVvt:l:H:w:c:T:p:a:A:E:X:46", longopts, &option); | ||
| 216 | 286 | ||
| 217 | if (option_index == -1 || option_index == EOF) { | 287 | if (option_index == -1 || option_index == EOF) { |
| 218 | break; | 288 | break; |
| @@ -263,6 +333,12 @@ check_dig_config_wrapper process_arguments(int argc, char **argv) { | |||
| 263 | case 'A': /* dig arguments */ | 333 | case 'A': /* dig arguments */ |
| 264 | result.config.dig_args = strdup(optarg); | 334 | result.config.dig_args = strdup(optarg); |
| 265 | break; | 335 | break; |
| 336 | case 'E': /* require flags */ | ||
| 337 | result.config.require_flags = split_csv_trim(optarg); | ||
| 338 | break; | ||
| 339 | case 'X': /* forbid flags */ | ||
| 340 | result.config.forbid_flags = split_csv_trim(optarg); | ||
| 341 | break; | ||
| 266 | case 'v': /* verbose */ | 342 | case 'v': /* verbose */ |
| 267 | verbose++; | 343 | verbose++; |
| 268 | break; | 344 | break; |
| @@ -343,6 +419,10 @@ void print_help(void) { | |||
| 343 | printf(" %s\n", _("was in -l")); | 419 | printf(" %s\n", _("was in -l")); |
| 344 | printf(" %s\n", "-A, --dig-arguments=STRING"); | 420 | printf(" %s\n", "-A, --dig-arguments=STRING"); |
| 345 | printf(" %s\n", _("Pass STRING as argument(s) to dig")); | 421 | printf(" %s\n", _("Pass STRING as argument(s) to dig")); |
| 422 | printf(" %s\n", "-E, --require-flags=LIST"); | ||
| 423 | printf(" %s\n", _("Comma-separated dig flags that must be present (e.g. 'aa,qr')")); | ||
| 424 | printf(" %s\n", "-X, --forbid-flags=LIST"); | ||
| 425 | printf(" %s\n", _("Comma-separated dig flags that must NOT be present")); | ||
| 346 | printf(UT_WARN_CRIT); | 426 | printf(UT_WARN_CRIT); |
| 347 | printf(UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT); | 427 | printf(UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT); |
| 348 | printf(UT_VERBOSE); | 428 | printf(UT_VERBOSE); |
| @@ -359,5 +439,185 @@ void print_usage(void) { | |||
| 359 | printf("%s\n", _("Usage:")); | 439 | printf("%s\n", _("Usage:")); |
| 360 | printf("%s -l <query_address> [-H <host>] [-p <server port>]\n", progname); | 440 | printf("%s -l <query_address> [-H <host>] [-p <server port>]\n", progname); |
| 361 | printf(" [-T <query type>] [-w <warning interval>] [-c <critical interval>]\n"); | 441 | printf(" [-T <query type>] [-w <warning interval>] [-c <critical interval>]\n"); |
| 362 | printf(" [-t <timeout>] [-a <expected answer address>] [-v]\n"); | 442 | printf(" [-t <timeout>] [-a <expected answer address>] [-E <flags>] [-X <flags>] [-v]\n"); |
| 443 | } | ||
| 444 | |||
| 445 | /* helpers */ | ||
| 446 | |||
| 447 | /** | ||
| 448 | * parse_flags_line - Parse a dig output line and extract DNS header flags. | ||
| 449 | * | ||
| 450 | * Input: | ||
| 451 | * line - NUL terminated dig output line, e.g. ";; flags: qr rd ra; ..." | ||
| 452 | * | ||
| 453 | * Returns: | ||
| 454 | * flag_list where: | ||
| 455 | * - items: array of NUL terminated flag strings (heap allocated) | ||
| 456 | * - count: number of entries in items | ||
| 457 | * On parse failure or if no flags were found, count is 0 and items is NULL. | ||
| 458 | */ | ||
| 459 | static flag_list parse_flags_line(const char *line) { | ||
| 460 | flag_list result = {.items = NULL, .count = 0}; | ||
| 461 | |||
| 462 | if (!line) { | ||
| 463 | return result; | ||
| 464 | } | ||
| 465 | |||
| 466 | /* Locate start of DNS header flags in dig output */ | ||
| 467 | const char *p = strstr(line, "flags:"); | ||
| 468 | if (!p) { | ||
| 469 | return result; | ||
| 470 | } | ||
| 471 | p += 6; /* skip literal "flags:" */ | ||
| 472 | |||
| 473 | /* Skip whitespace after "flags:" */ | ||
| 474 | while (*p && isspace((unsigned char)*p)) { | ||
| 475 | p++; | ||
| 476 | } | ||
| 477 | |||
| 478 | /* Flags are terminated by the next semicolon e.g. "qr rd ra;" */ | ||
| 479 | const char *q = strchr(p, ';'); | ||
| 480 | if (!q) { | ||
| 481 | return result; | ||
| 482 | } | ||
| 483 | |||
| 484 | /* Extract substring containing the flag block */ | ||
| 485 | size_t len = (size_t)(q - p); | ||
| 486 | if (len == 0) { | ||
| 487 | return result; | ||
| 488 | } | ||
| 489 | |||
| 490 | char *buf = (char *)malloc(len + 1); | ||
| 491 | if (!buf) { | ||
| 492 | return result; | ||
| 493 | } | ||
| 494 | memcpy(buf, p, len); | ||
| 495 | buf[len] = '\0'; | ||
| 496 | |||
| 497 | /* Tokenize flags separated by whitespace */ | ||
| 498 | char **arr = NULL; | ||
| 499 | size_t cnt = 0; | ||
| 500 | char *saveptr = NULL; | ||
| 501 | char *tok = strtok_r(buf, " \t", &saveptr); | ||
| 502 | |||
| 503 | while (tok) { | ||
| 504 | /* Expand array for the next flag token */ | ||
| 505 | char **tmp = (char **)realloc(arr, (cnt + 1) * sizeof(char *)); | ||
| 506 | if (!tmp) { | ||
| 507 | /* On allocation failure keep what we have and return it */ | ||
| 508 | break; | ||
| 509 | } | ||
| 510 | arr = tmp; | ||
| 511 | arr[cnt++] = strdup(tok); | ||
| 512 | tok = strtok_r(NULL, " \t", &saveptr); | ||
| 513 | } | ||
| 514 | |||
| 515 | free(buf); | ||
| 516 | |||
| 517 | result.items = arr; | ||
| 518 | result.count = cnt; | ||
| 519 | return result; | ||
| 520 | } | ||
| 521 | |||
| 522 | /** | ||
| 523 | * split_csv_trim - Split a comma separated string into trimmed tokens. | ||
| 524 | * | ||
| 525 | * Input: | ||
| 526 | * csv - NUL terminated string, e.g. "aa, qr , rd" | ||
| 527 | * | ||
| 528 | * Returns: | ||
| 529 | * flag_list where: | ||
| 530 | * - items: array of NUL terminated tokens (heap allocated, whitespace trimmed) | ||
| 531 | * - count: number of tokens | ||
| 532 | * On empty input, count is 0 and items is NULL | ||
| 533 | */ | ||
| 534 | static flag_list split_csv_trim(const char *csv) { | ||
| 535 | flag_list result = {.items = NULL, .count = 0}; | ||
| 536 | |||
| 537 | if (!csv || !*csv) { | ||
| 538 | return result; | ||
| 539 | } | ||
| 540 | |||
| 541 | char *tmp = strdup(csv); | ||
| 542 | if (!tmp) { | ||
| 543 | return result; | ||
| 544 | } | ||
| 545 | |||
| 546 | char *s = tmp; | ||
| 547 | char *token = NULL; | ||
| 548 | |||
| 549 | /* Split CSV by commas, trimming whitespace on each token */ | ||
| 550 | while ((token = strsep(&s, ",")) != NULL) { | ||
| 551 | /* trim leading whitespace */ | ||
| 552 | while (*token && isspace((unsigned char)*token)) { | ||
| 553 | token++; | ||
| 554 | } | ||
| 555 | |||
| 556 | /* trim trailing whitespace */ | ||
| 557 | char *end = token + strlen(token); | ||
| 558 | while (end > token && isspace((unsigned char)end[-1])) { | ||
| 559 | *--end = '\0'; | ||
| 560 | } | ||
| 561 | |||
| 562 | if (*token) { | ||
| 563 | /* Expand the items array and append the token */ | ||
| 564 | char **arr = (char **)realloc(result.items, (result.count + 1) * sizeof(char *)); | ||
| 565 | if (!arr) { | ||
| 566 | /* Allocation failed, stop and return what we have */ | ||
| 567 | break; | ||
| 568 | } | ||
| 569 | result.items = arr; | ||
| 570 | result.items[result.count++] = strdup(token); | ||
| 571 | } | ||
| 572 | } | ||
| 573 | |||
| 574 | free(tmp); | ||
| 575 | return result; | ||
| 576 | } | ||
| 577 | |||
| 578 | /** | ||
| 579 | * flag_list_contains - Case-insensitive membership test in a flag_list. | ||
| 580 | * | ||
| 581 | * Input: | ||
| 582 | * list - pointer to a flag_list | ||
| 583 | * needle - NUL terminated string to search for | ||
| 584 | * | ||
| 585 | * Returns: | ||
| 586 | * true if needle is contained in list (strcasecmp) | ||
| 587 | * false otherwise | ||
| 588 | */ | ||
| 589 | static bool flag_list_contains(const flag_list *list, const char *needle) { | ||
| 590 | if (!list || !needle || !*needle) { | ||
| 591 | return false; | ||
| 592 | } | ||
| 593 | |||
| 594 | for (size_t i = 0; i < list->count; i++) { | ||
| 595 | if (strcasecmp(list->items[i], needle) == 0) { | ||
| 596 | return true; | ||
| 597 | } | ||
| 598 | } | ||
| 599 | return false; | ||
| 600 | } | ||
| 601 | |||
| 602 | /** | ||
| 603 | * free_flag_list - Release all heap allocations held by a flag_list. | ||
| 604 | * | ||
| 605 | * Input: | ||
| 606 | * list - pointer to a flag_list whose items were allocated by | ||
| 607 | * parse_flags_line() or split_csv_trim(). | ||
| 608 | * | ||
| 609 | * After this call list->items is NULL and list->count is 0. | ||
| 610 | */ | ||
| 611 | static void free_flag_list(flag_list *list) { | ||
| 612 | if (!list || !list->items) { | ||
| 613 | return; | ||
| 614 | } | ||
| 615 | |||
| 616 | for (size_t i = 0; i < list->count; i++) { | ||
| 617 | free(list->items[i]); | ||
| 618 | } | ||
| 619 | free(list->items); | ||
| 620 | |||
| 621 | list->items = NULL; | ||
| 622 | list->count = 0; | ||
| 363 | } | 623 | } |
diff --git a/plugins/check_dig.d/config.h b/plugins/check_dig.d/config.h index a570b633..dd1f58b5 100644 --- a/plugins/check_dig.d/config.h +++ b/plugins/check_dig.d/config.h | |||
| @@ -8,6 +8,11 @@ | |||
| 8 | #define DEFAULT_TRIES 2 | 8 | #define DEFAULT_TRIES 2 |
| 9 | 9 | ||
| 10 | typedef struct { | 10 | typedef struct { |
| 11 | char **items; | ||
| 12 | size_t count; | ||
| 13 | } flag_list; | ||
| 14 | |||
| 15 | typedef struct { | ||
| 11 | char *query_address; | 16 | char *query_address; |
| 12 | char *record_type; | 17 | char *record_type; |
| 13 | char *expected_address; | 18 | char *expected_address; |
| @@ -19,6 +24,8 @@ typedef struct { | |||
| 19 | 24 | ||
| 20 | double warning_interval; | 25 | double warning_interval; |
| 21 | double critical_interval; | 26 | double critical_interval; |
| 27 | flag_list require_flags; | ||
| 28 | flag_list forbid_flags; | ||
| 22 | } check_dig_config; | 29 | } check_dig_config; |
| 23 | 30 | ||
| 24 | check_dig_config check_dig_config_init() { | 31 | check_dig_config check_dig_config_init() { |
| @@ -34,7 +41,8 @@ check_dig_config check_dig_config_init() { | |||
| 34 | 41 | ||
| 35 | .warning_interval = UNDEFINED, | 42 | .warning_interval = UNDEFINED, |
| 36 | .critical_interval = UNDEFINED, | 43 | .critical_interval = UNDEFINED, |
| 37 | 44 | .require_flags = {.count = 0, .items = NULL}, | |
| 45 | .forbid_flags = {.count = 0, .items = NULL}, | ||
| 38 | }; | 46 | }; |
| 39 | return tmp; | 47 | return tmp; |
| 40 | } | 48 | } |
