diff options
| author | Dennis Ullrich <dennis.ullrich@plusserver.com> | 2025-11-27 16:24:56 +0100 |
|---|---|---|
| committer | Dennis Ullrich <dennis.ullrich@plusserver.com> | 2025-11-27 16:43:56 +0100 |
| commit | 052811414a7edcc6479895ddc169893e7740b28f (patch) | |
| tree | 4e7f459a342d49752932eea4051c11ec63614b9b /plugins/check_dig.c | |
| parent | cedfc166d42f4e89dddc1caa44e0655157d35a0a (diff) | |
| download | monitoring-plugins-052811414a7edcc6479895ddc169893e7740b28f.tar.gz | |
Refactor DNS flag handling: replace output pointer pattern with structured return types and add documentation.
This refactors all helper functions related to DNS flag extraction and validation:
- Introduce a new `flag_list` struct used as unified return type for functions producing multiple output values
- Replace all functions using output pointers (`char ***`, `size_t *`) with functions returning `flag_list`
- Update callers in `main()` and the flag validation logic
- Add documentation comments describing purpose, inputs and outputs for all new helper functions
- Consolidate memory handling through a single `free_flag_list()` helper
- Apply clang-format
This brings more clarity, avoids hidden output and aligns with the review request for cleaner functions input/output.
Diffstat (limited to 'plugins/check_dig.c')
| -rw-r--r-- | plugins/check_dig.c | 274 |
1 files changed, 201 insertions, 73 deletions
diff --git a/plugins/check_dig.c b/plugins/check_dig.c index b3f4c878..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,7 +33,7 @@ | |||
| 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 <ctype.h> |
| @@ -58,10 +58,14 @@ void print_usage(void); | |||
| 58 | static int verbose = 0; | 58 | static int verbose = 0; |
| 59 | 59 | ||
| 60 | /* helpers for flag parsing */ | 60 | /* helpers for flag parsing */ |
| 61 | static bool parse_flags_line(const char *line, char ***out_flags, size_t *out_count); | 61 | typedef struct { |
| 62 | static void free_flags(char **flags, size_t count); | 62 | char **items; |
| 63 | static bool list_contains(char **flags, size_t count, const char *needle); | 63 | size_t count; |
| 64 | static void split_csv_trim(const char *csv, char ***out_items, size_t *out_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); | ||
| 65 | 69 | ||
| 66 | int main(int argc, char **argv) { | 70 | int main(int argc, char **argv) { |
| 67 | setlocale(LC_ALL, ""); | 71 | setlocale(LC_ALL, ""); |
| @@ -108,10 +112,9 @@ int main(int argc, char **argv) { | |||
| 108 | output chld_out; | 112 | output chld_out; |
| 109 | output chld_err; | 113 | output chld_err; |
| 110 | char *msg = NULL; | 114 | char *msg = NULL; |
| 111 | char **dig_flags = NULL; | 115 | flag_list dig_flags = {.items = NULL, .count = 0}; |
| 112 | size_t dig_flags_cnt = 0; | ||
| 113 | |||
| 114 | mp_state_enum result = STATE_UNKNOWN; | 116 | mp_state_enum result = STATE_UNKNOWN; |
| 117 | |||
| 115 | /* run the command */ | 118 | /* run the command */ |
| 116 | if (np_runcmd(command_line, &chld_out, &chld_err, 0) != 0) { | 119 | if (np_runcmd(command_line, &chld_out, &chld_err, 0) != 0) { |
| 117 | result = STATE_WARNING; | 120 | result = STATE_WARNING; |
| @@ -121,16 +124,21 @@ int main(int argc, char **argv) { | |||
| 121 | /* extract ';; flags: ...' from stdout (first occurrence) */ | 124 | /* extract ';; flags: ...' from stdout (first occurrence) */ |
| 122 | for (size_t i = 0; i < chld_out.lines; i++) { | 125 | for (size_t i = 0; i < chld_out.lines; i++) { |
| 123 | if (strstr(chld_out.line[i], "flags:")) { | 126 | if (strstr(chld_out.line[i], "flags:")) { |
| 124 | if (verbose) printf("Raw flags line: %s\n", chld_out.line[i]); | 127 | if (verbose) { |
| 125 | if (parse_flags_line(chld_out.line[i], &dig_flags, &dig_flags_cnt)) { | 128 | printf("Raw flags line: %s\n", chld_out.line[i]); |
| 126 | if (verbose) { | 129 | } |
| 127 | printf(_("Parsed flags:")); | 130 | |
| 128 | for (size_t k = 0; k < dig_flags_cnt; k++) printf(" %s", dig_flags[k]); | 131 | dig_flags = parse_flags_line(chld_out.line[i]); |
| 129 | printf("\n"); | 132 | |
| 130 | } | 133 | if (verbose && dig_flags.count > 0) { |
| 131 | } | 134 | printf(_("Parsed flags:")); |
| 132 | break; | 135 | for (size_t k = 0; k < dig_flags.count; k++) { |
| 133 | } | 136 | printf(" %s", dig_flags.items[k]); |
| 137 | } | ||
| 138 | printf("\n"); | ||
| 139 | } | ||
| 140 | break; | ||
| 141 | } | ||
| 134 | } | 142 | } |
| 135 | 143 | ||
| 136 | for (size_t i = 0; i < chld_out.lines; i++) { | 144 | for (size_t i = 0; i < chld_out.lines; i++) { |
| @@ -200,47 +208,55 @@ int main(int argc, char **argv) { | |||
| 200 | } | 208 | } |
| 201 | 209 | ||
| 202 | /* Optional: evaluate dig flags only if -E/-X were provided */ | 210 | /* Optional: evaluate dig flags only if -E/-X were provided */ |
| 203 | if ((config.require_flags && *config.require_flags) || (config.forbid_flags && *config.forbid_flags)) { | 211 | if ((config.require_flags && *config.require_flags) || |
| 204 | if (dig_flags_cnt > 0) { | 212 | (config.forbid_flags && *config.forbid_flags)) { |
| 213 | |||
| 214 | if (dig_flags.count > 0) { | ||
| 215 | |||
| 205 | if (config.require_flags && *config.require_flags) { | 216 | if (config.require_flags && *config.require_flags) { |
| 206 | char **req = NULL; size_t reqn = 0; | 217 | flag_list req = split_csv_trim(config.require_flags); |
| 207 | split_csv_trim(config.require_flags, &req, &reqn); | 218 | |
| 208 | for (size_t r = 0; r < reqn; r++) { | 219 | for (size_t r = 0; r < req.count; r++) { |
| 209 | if (!list_contains(dig_flags, dig_flags_cnt, req[r])) { | 220 | if (!flag_list_contains(&dig_flags, req.items[r])) { |
| 210 | result = STATE_CRITICAL; | 221 | result = STATE_CRITICAL; |
| 211 | if (!msg) { | 222 | if (!msg) { |
| 212 | xasprintf(&msg, _("Missing required DNS flag: %s"), req[r]); | 223 | xasprintf(&msg, _("Missing required DNS flag: %s"), req.items[r]); |
| 213 | } else { | 224 | } else { |
| 214 | char *newmsg = NULL; | 225 | char *newmsg = NULL; |
| 215 | xasprintf(&newmsg, _("%s; missing required DNS flag: %s"), msg, req[r]); | 226 | xasprintf(&newmsg, _("%s; missing required DNS flag: %s"), msg, |
| 227 | req.items[r]); | ||
| 216 | msg = newmsg; | 228 | msg = newmsg; |
| 217 | } | 229 | } |
| 218 | } | 230 | } |
| 219 | } | 231 | } |
| 220 | free_flags(req, reqn); | 232 | |
| 233 | free_flag_list(&req); | ||
| 221 | } | 234 | } |
| 235 | |||
| 222 | if (config.forbid_flags && *config.forbid_flags) { | 236 | if (config.forbid_flags && *config.forbid_flags) { |
| 223 | char **bad = NULL; size_t badn = 0; | 237 | flag_list bad = split_csv_trim(config.forbid_flags); |
| 224 | split_csv_trim(config.forbid_flags, &bad, &badn); | 238 | |
| 225 | for (size_t r = 0; r < badn; r++) { | 239 | for (size_t r = 0; r < bad.count; r++) { |
| 226 | if (list_contains(dig_flags, dig_flags_cnt, bad[r])) { | 240 | if (flag_list_contains(&dig_flags, bad.items[r])) { |
| 227 | result = STATE_CRITICAL; | 241 | result = STATE_CRITICAL; |
| 228 | if (!msg) { | 242 | if (!msg) { |
| 229 | xasprintf(&msg, _("Forbidden DNS flag present: %s"), bad[r]); | 243 | xasprintf(&msg, _("Forbidden DNS flag present: %s"), bad.items[r]); |
| 230 | } else { | 244 | } else { |
| 231 | char *newmsg = NULL; | 245 | char *newmsg = NULL; |
| 232 | xasprintf(&newmsg, _("%s; forbidden DNS flag present: %s"), msg, bad[r]); | 246 | xasprintf(&newmsg, _("%s; forbidden DNS flag present: %s"), msg, |
| 247 | bad.items[r]); | ||
| 233 | msg = newmsg; | 248 | msg = newmsg; |
| 234 | } | 249 | } |
| 235 | } | 250 | } |
| 236 | } | 251 | } |
| 237 | free_flags(bad, badn); | 252 | |
| 253 | free_flag_list(&bad); | ||
| 238 | } | 254 | } |
| 239 | } | 255 | } |
| 240 | } | 256 | } |
| 241 | 257 | ||
| 242 | /* cleanup flags buffer */ | 258 | /* cleanup flags buffer */ |
| 243 | free_flags(dig_flags, dig_flags_cnt); | 259 | free_flag_list(&dig_flags); |
| 244 | 260 | ||
| 245 | 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, |
| 246 | msg ? msg : _("Probably a non-existent host/domain"), | 262 | msg ? msg : _("Probably a non-existent host/domain"), |
| @@ -282,7 +298,8 @@ check_dig_config_wrapper process_arguments(int argc, char **argv) { | |||
| 282 | 298 | ||
| 283 | int option = 0; | 299 | int option = 0; |
| 284 | while (true) { | 300 | while (true) { |
| 285 | int option_index = getopt_long(argc, argv, "hVvt:l:H:w:c:T:p:a:A:E:X: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); | ||
| 286 | 303 | ||
| 287 | if (option_index == -1 || option_index == EOF) { | 304 | if (option_index == -1 || option_index == EOF) { |
| 288 | break; | 305 | break; |
| @@ -444,69 +461,180 @@ void print_usage(void) { | |||
| 444 | 461 | ||
| 445 | /* helpers */ | 462 | /* helpers */ |
| 446 | 463 | ||
| 447 | static bool parse_flags_line(const char *line, char ***out_flags, size_t *out_count) { | 464 | /** |
| 448 | if (!line || !out_flags || !out_count) return false; | 465 | * parse_flags_line - Parse a dig output line and extract DNS header flags. |
| 449 | *out_flags = NULL; *out_count = 0; | 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 | } | ||
| 450 | 482 | ||
| 483 | /* Locate start of DNS header flags in dig output */ | ||
| 451 | const char *p = strstr(line, "flags:"); | 484 | const char *p = strstr(line, "flags:"); |
| 452 | if (!p) return false; | 485 | if (!p) { |
| 453 | p += 6; | 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 | } | ||
| 454 | 494 | ||
| 455 | while (*p && isspace((unsigned char)*p)) p++; | 495 | /* Flags are terminated by the next semicolon e.g. "qr rd ra;" */ |
| 456 | const char *q = strchr(p, ';'); | 496 | const char *q = strchr(p, ';'); |
| 457 | if (!q) return false; | 497 | if (!q) { |
| 498 | return result; | ||
| 499 | } | ||
| 458 | 500 | ||
| 501 | /* Extract substring containing the flag block */ | ||
| 459 | size_t len = (size_t)(q - p); | 502 | size_t len = (size_t)(q - p); |
| 460 | if (len == 0) return false; | 503 | if (len == 0) { |
| 504 | return result; | ||
| 505 | } | ||
| 461 | 506 | ||
| 462 | char *buf = (char*)malloc(len + 1); | 507 | char *buf = (char *)malloc(len + 1); |
| 463 | if (!buf) return false; | 508 | if (!buf) { |
| 464 | memcpy(buf, p, len); buf[len] = '\0'; | 509 | return result; |
| 510 | } | ||
| 511 | memcpy(buf, p, len); | ||
| 512 | buf[len] = '\0'; | ||
| 465 | 513 | ||
| 466 | char **arr = NULL; size_t cnt = 0; | 514 | /* Tokenize flags separated by whitespace */ |
| 515 | char **arr = NULL; | ||
| 516 | size_t cnt = 0; | ||
| 467 | char *saveptr = NULL; | 517 | char *saveptr = NULL; |
| 468 | char *tok = strtok_r(buf, " \t", &saveptr); | 518 | char *tok = strtok_r(buf, " \t", &saveptr); |
| 519 | |||
| 469 | while (tok) { | 520 | while (tok) { |
| 470 | arr = (char**)realloc(arr, (cnt + 1) * sizeof(char*)); | 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; | ||
| 471 | arr[cnt++] = strdup(tok); | 528 | arr[cnt++] = strdup(tok); |
| 472 | tok = strtok_r(NULL, " \t", &saveptr); | 529 | tok = strtok_r(NULL, " \t", &saveptr); |
| 473 | } | 530 | } |
| 474 | free(buf); | ||
| 475 | 531 | ||
| 476 | *out_flags = arr; | 532 | free(buf); |
| 477 | *out_count = cnt; | ||
| 478 | return (cnt > 0); | ||
| 479 | } | ||
| 480 | 533 | ||
| 481 | static void free_flags(char **flags, size_t count) { | 534 | result.items = arr; |
| 482 | if (!flags) return; | 535 | result.count = cnt; |
| 483 | for (size_t i = 0; i < count; i++) free(flags[i]); | 536 | return result; |
| 484 | free(flags); | ||
| 485 | } | 537 | } |
| 486 | 538 | ||
| 487 | static bool list_contains(char **flags, size_t count, const char *needle) { | 539 | /** |
| 488 | if (!needle || !*needle) return false; | 540 | * split_csv_trim - Split a comma separated string into trimmed tokens. |
| 489 | for (size_t i = 0; i < count; i++) { | 541 | * |
| 490 | if (strcasecmp(flags[i], needle) == 0) return true; | 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; | ||
| 491 | } | 556 | } |
| 492 | return false; | ||
| 493 | } | ||
| 494 | |||
| 495 | static void split_csv_trim(const char *csv, char ***out_items, size_t *out_count) { | ||
| 496 | *out_items = NULL; *out_count = 0; | ||
| 497 | if (!csv || !*csv) return; | ||
| 498 | 557 | ||
| 499 | char *tmp = strdup(csv); | 558 | char *tmp = strdup(csv); |
| 559 | if (!tmp) { | ||
| 560 | return result; | ||
| 561 | } | ||
| 562 | |||
| 500 | char *s = tmp; | 563 | char *s = tmp; |
| 501 | char *token = NULL; | 564 | char *token = NULL; |
| 565 | |||
| 566 | /* Split CSV by commas, trimming whitespace on each token */ | ||
| 502 | while ((token = strsep(&s, ",")) != NULL) { | 567 | while ((token = strsep(&s, ",")) != NULL) { |
| 503 | while (*token && isspace((unsigned char)*token)) token++; | 568 | /* trim leading whitespace */ |
| 569 | while (*token && isspace((unsigned char)*token)) { | ||
| 570 | token++; | ||
| 571 | } | ||
| 572 | |||
| 573 | /* trim trailing whitespace */ | ||
| 504 | char *end = token + strlen(token); | 574 | char *end = token + strlen(token); |
| 505 | while (end > token && isspace((unsigned char)end[-1])) *--end = '\0'; | 575 | while (end > token && isspace((unsigned char)end[-1])) { |
| 576 | *--end = '\0'; | ||
| 577 | } | ||
| 578 | |||
| 506 | if (*token) { | 579 | if (*token) { |
| 507 | *out_items = (char**)realloc(*out_items, (*out_count + 1) * sizeof(char*)); | 580 | /* Expand the items array and append the token */ |
| 508 | (*out_items)[(*out_count)++] = strdup(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); | ||
| 509 | } | 588 | } |
| 510 | } | 589 | } |
| 590 | |||
| 511 | free(tmp); | 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; | ||
| 512 | } | 640 | } |
