Module: monitoring-plugins Branch: Decstasy-check_dig_flags_feature Commit: 052811414a7edcc6479895ddc169893e7740b28f Author: Dennis Ullrich <[email protected]> Date: Thu Nov 27 16:24:56 2025 +0100 URL: https://www.monitoring-plugins.org/repositories/monitoring-plugins/commit/?id=05281141
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. --- plugins/check_dig.c | 274 ++++++++++++++++++++++++++++++++++++++-------------- 1 file 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 @@ * Monitoring check_dig plugin * * License: GPL - * Copyright (c) 2002-2024 Monitoring Plugins Development Team + * Copyright (c) 2002-2025 Monitoring Plugins Development Team * * Description: * @@ -33,7 +33,7 @@ * because on some architectures those strings are in non-writable memory */ const char *progname = "check_dig"; -const char *copyright = "2002-2024"; +const char *copyright = "2002-2025"; const char *email = "[email protected]"; #include <ctype.h> @@ -58,10 +58,14 @@ void print_usage(void); static int verbose = 0; /* helpers for flag parsing */ -static bool parse_flags_line(const char *line, char ***out_flags, size_t *out_count); -static void free_flags(char **flags, size_t count); -static bool list_contains(char **flags, size_t count, const char *needle); -static void split_csv_trim(const char *csv, char ***out_items, size_t *out_count); +typedef struct { + char **items; + size_t count; +} flag_list; +static flag_list parse_flags_line(const char *line); +static flag_list split_csv_trim(const char *csv); +static bool flag_list_contains(const flag_list *list, const char *needle); +static void free_flag_list(flag_list *list); int main(int argc, char **argv) { setlocale(LC_ALL, ""); @@ -108,10 +112,9 @@ int main(int argc, char **argv) { output chld_out; output chld_err; char *msg = NULL; - char **dig_flags = NULL; - size_t dig_flags_cnt = 0; - + flag_list dig_flags = {.items = NULL, .count = 0}; mp_state_enum result = STATE_UNKNOWN; + /* run the command */ if (np_runcmd(command_line, &chld_out, &chld_err, 0) != 0) { result = STATE_WARNING; @@ -121,16 +124,21 @@ int main(int argc, char **argv) { /* extract ';; flags: ...' from stdout (first occurrence) */ for (size_t i = 0; i < chld_out.lines; i++) { if (strstr(chld_out.line[i], "flags:")) { - if (verbose) printf("Raw flags line: %s\n", chld_out.line[i]); - if (parse_flags_line(chld_out.line[i], &dig_flags, &dig_flags_cnt)) { - if (verbose) { - printf(_("Parsed flags:")); - for (size_t k = 0; k < dig_flags_cnt; k++) printf(" %s", dig_flags[k]); - printf("\n"); - } - } - break; - } + if (verbose) { + printf("Raw flags line: %s\n", chld_out.line[i]); + } + + dig_flags = parse_flags_line(chld_out.line[i]); + + if (verbose && dig_flags.count > 0) { + printf(_("Parsed flags:")); + for (size_t k = 0; k < dig_flags.count; k++) { + printf(" %s", dig_flags.items[k]); + } + printf("\n"); + } + break; + } } for (size_t i = 0; i < chld_out.lines; i++) { @@ -200,47 +208,55 @@ int main(int argc, char **argv) { } /* Optional: evaluate dig flags only if -E/-X were provided */ - if ((config.require_flags && *config.require_flags) || (config.forbid_flags && *config.forbid_flags)) { - if (dig_flags_cnt > 0) { + if ((config.require_flags && *config.require_flags) || + (config.forbid_flags && *config.forbid_flags)) { + + if (dig_flags.count > 0) { + if (config.require_flags && *config.require_flags) { - char **req = NULL; size_t reqn = 0; - split_csv_trim(config.require_flags, &req, &reqn); - for (size_t r = 0; r < reqn; r++) { - if (!list_contains(dig_flags, dig_flags_cnt, req[r])) { + flag_list req = split_csv_trim(config.require_flags); + + for (size_t r = 0; r < req.count; r++) { + if (!flag_list_contains(&dig_flags, req.items[r])) { result = STATE_CRITICAL; if (!msg) { - xasprintf(&msg, _("Missing required DNS flag: %s"), req[r]); + xasprintf(&msg, _("Missing required DNS flag: %s"), req.items[r]); } else { char *newmsg = NULL; - xasprintf(&newmsg, _("%s; missing required DNS flag: %s"), msg, req[r]); + xasprintf(&newmsg, _("%s; missing required DNS flag: %s"), msg, + req.items[r]); msg = newmsg; } } } - free_flags(req, reqn); + + free_flag_list(&req); } + if (config.forbid_flags && *config.forbid_flags) { - char **bad = NULL; size_t badn = 0; - split_csv_trim(config.forbid_flags, &bad, &badn); - for (size_t r = 0; r < badn; r++) { - if (list_contains(dig_flags, dig_flags_cnt, bad[r])) { + flag_list bad = split_csv_trim(config.forbid_flags); + + for (size_t r = 0; r < bad.count; r++) { + if (flag_list_contains(&dig_flags, bad.items[r])) { result = STATE_CRITICAL; if (!msg) { - xasprintf(&msg, _("Forbidden DNS flag present: %s"), bad[r]); + xasprintf(&msg, _("Forbidden DNS flag present: %s"), bad.items[r]); } else { char *newmsg = NULL; - xasprintf(&newmsg, _("%s; forbidden DNS flag present: %s"), msg, bad[r]); + xasprintf(&newmsg, _("%s; forbidden DNS flag present: %s"), msg, + bad.items[r]); msg = newmsg; } } } - free_flags(bad, badn); + + free_flag_list(&bad); } } } /* cleanup flags buffer */ - free_flags(dig_flags, dig_flags_cnt); + free_flag_list(&dig_flags); printf("DNS %s - %.3f seconds response time (%s)|%s\n", state_text(result), elapsed_time, msg ? msg : _("Probably a non-existent host/domain"), @@ -282,7 +298,8 @@ check_dig_config_wrapper process_arguments(int argc, char **argv) { int option = 0; while (true) { - int option_index = getopt_long(argc, argv, "hVvt:l:H:w:c:T:p:a:A:E:X:46", longopts, &option); + int option_index = + getopt_long(argc, argv, "hVvt:l:H:w:c:T:p:a:A:E:X:46", longopts, &option); if (option_index == -1 || option_index == EOF) { break; @@ -444,69 +461,180 @@ void print_usage(void) { /* helpers */ -static bool parse_flags_line(const char *line, char ***out_flags, size_t *out_count) { - if (!line || !out_flags || !out_count) return false; - *out_flags = NULL; *out_count = 0; +/** + * parse_flags_line - Parse a dig output line and extract DNS header flags. + * + * Input: + * line - NUL terminated dig output line, e.g. ";; flags: qr rd ra; ..." + * + * Returns: + * flag_list where: + * - items: array of NUL terminated flag strings (heap allocated) + * - count: number of entries in items + * On parse failure or if no flags were found, count is 0 and items is NULL. + */ +static flag_list parse_flags_line(const char *line) { + flag_list result = {.items = NULL, .count = 0}; + + if (!line) { + return result; + } + /* Locate start of DNS header flags in dig output */ const char *p = strstr(line, "flags:"); - if (!p) return false; - p += 6; + if (!p) { + return result; + } + p += 6; /* skip literal "flags:" */ + + /* Skip whitespace after "flags:" */ + while (*p && isspace((unsigned char)*p)) { + p++; + } - while (*p && isspace((unsigned char)*p)) p++; + /* Flags are terminated by the next semicolon e.g. "qr rd ra;" */ const char *q = strchr(p, ';'); - if (!q) return false; + if (!q) { + return result; + } + /* Extract substring containing the flag block */ size_t len = (size_t)(q - p); - if (len == 0) return false; + if (len == 0) { + return result; + } - char *buf = (char*)malloc(len + 1); - if (!buf) return false; - memcpy(buf, p, len); buf[len] = '\0'; + char *buf = (char *)malloc(len + 1); + if (!buf) { + return result; + } + memcpy(buf, p, len); + buf[len] = '\0'; - char **arr = NULL; size_t cnt = 0; + /* Tokenize flags separated by whitespace */ + char **arr = NULL; + size_t cnt = 0; char *saveptr = NULL; char *tok = strtok_r(buf, " \t", &saveptr); + while (tok) { - arr = (char**)realloc(arr, (cnt + 1) * sizeof(char*)); + /* Expand array for the next flag token */ + char **tmp = (char **)realloc(arr, (cnt + 1) * sizeof(char *)); + if (!tmp) { + /* On allocation failure keep what we have and return it */ + break; + } + arr = tmp; arr[cnt++] = strdup(tok); tok = strtok_r(NULL, " \t", &saveptr); } - free(buf); - *out_flags = arr; - *out_count = cnt; - return (cnt > 0); -} + free(buf); -static void free_flags(char **flags, size_t count) { - if (!flags) return; - for (size_t i = 0; i < count; i++) free(flags[i]); - free(flags); + result.items = arr; + result.count = cnt; + return result; } -static bool list_contains(char **flags, size_t count, const char *needle) { - if (!needle || !*needle) return false; - for (size_t i = 0; i < count; i++) { - if (strcasecmp(flags[i], needle) == 0) return true; +/** + * split_csv_trim - Split a comma separated string into trimmed tokens. + * + * Input: + * csv - NUL terminated string, e.g. "aa, qr , rd" + * + * Returns: + * flag_list where: + * - items: array of NUL terminated tokens (heap allocated, whitespace trimmed) + * - count: number of tokens + * On empty input, count is 0 and items is NULL + */ +static flag_list split_csv_trim(const char *csv) { + flag_list result = {.items = NULL, .count = 0}; + + if (!csv || !*csv) { + return result; } - return false; -} - -static void split_csv_trim(const char *csv, char ***out_items, size_t *out_count) { - *out_items = NULL; *out_count = 0; - if (!csv || !*csv) return; char *tmp = strdup(csv); + if (!tmp) { + return result; + } + char *s = tmp; char *token = NULL; + + /* Split CSV by commas, trimming whitespace on each token */ while ((token = strsep(&s, ",")) != NULL) { - while (*token && isspace((unsigned char)*token)) token++; + /* trim leading whitespace */ + while (*token && isspace((unsigned char)*token)) { + token++; + } + + /* trim trailing whitespace */ char *end = token + strlen(token); - while (end > token && isspace((unsigned char)end[-1])) *--end = '\0'; + while (end > token && isspace((unsigned char)end[-1])) { + *--end = '\0'; + } + if (*token) { - *out_items = (char**)realloc(*out_items, (*out_count + 1) * sizeof(char*)); - (*out_items)[(*out_count)++] = strdup(token); + /* Expand the items array and append the token */ + char **arr = (char **)realloc(result.items, (result.count + 1) * sizeof(char *)); + if (!arr) { + /* Allocation failed, stop and return what we have */ + break; + } + result.items = arr; + result.items[result.count++] = strdup(token); } } + free(tmp); + return result; +} + +/** + * flag_list_contains - Case-insensitive membership test in a flag_list. + * + * Input: + * list - pointer to a flag_list + * needle - NUL terminated string to search for + * + * Returns: + * true if needle is contained in list (strcasecmp) + * false otherwise + */ +static bool flag_list_contains(const flag_list *list, const char *needle) { + if (!list || !needle || !*needle) { + return false; + } + + for (size_t i = 0; i < list->count; i++) { + if (strcasecmp(list->items[i], needle) == 0) { + return true; + } + } + return false; +} + +/** + * free_flag_list - Release all heap allocations held by a flag_list. + * + * Input: + * list - pointer to a flag_list whose items were allocated by + * parse_flags_line() or split_csv_trim(). + * + * After this call list->items is NULL and list->count is 0. + */ +static void free_flag_list(flag_list *list) { + if (!list || !list->items) { + return; + } + + for (size_t i = 0; i < list->count; i++) { + free(list->items[i]); + } + free(list->items); + + list->items = NULL; + list->count = 0; }
