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