summaryrefslogtreecommitdiffstats
path: root/plugins/check_dig.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/check_dig.c')
-rw-r--r--plugins/check_dig.c286
1 files changed, 275 insertions, 11 deletions
diff --git a/plugins/check_dig.c b/plugins/check_dig.c
index d0903be2..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
35const char *progname = "check_dig"; 35const char *progname = "check_dig";
36const char *copyright = "2002-2024"; 36const char *copyright = "2002-2025";
37const char *email = "devel@monitoring-plugins.org"; 37const 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
57static int verbose = 0; 58static int verbose = 0;
58 59
60/* helpers for flag parsing */
61static flag_list parse_flags_line(const char *line);
62static flag_list split_csv_trim(const char *csv);
63static bool flag_list_contains(const flag_list *list, const char *needle);
64static void free_flag_list(flag_list *list);
65
59int main(int argc, char **argv) { 66int main(int argc, char **argv) {
60 setlocale(LC_ALL, ""); 67 setlocale(LC_ALL, "");
61 bindtextdomain(PACKAGE, LOCALEDIR); 68 bindtextdomain(PACKAGE, LOCALEDIR);
@@ -81,8 +88,9 @@ int main(int argc, char **argv) {
81 88
82 char *command_line; 89 char *command_line;
83 /* get the command to run */ 90 /* get the command to run */
84 xasprintf(&command_line, "%s %s %s -p %d @%s %s %s +retry=%d +time=%d", PATH_TO_DIG, config.dig_args, config.query_transport, 91 xasprintf(&command_line, "%s %s %s -p %d @%s %s %s +retry=%d +time=%d", PATH_TO_DIG,
85 config.server_port, config.dns_server, config.query_address, config.record_type, config.number_tries, timeout_interval_dig); 92 config.dig_args, config.query_transport, config.server_port, config.dns_server,
93 config.query_address, config.record_type, config.number_tries, timeout_interval_dig);
86 94
87 alarm(timeout_interval); 95 alarm(timeout_interval);
88 struct timeval start_time; 96 struct timeval start_time;
@@ -100,13 +108,35 @@ int main(int argc, char **argv) {
100 output chld_out; 108 output chld_out;
101 output chld_err; 109 output chld_err;
102 char *msg = NULL; 110 char *msg = NULL;
111 flag_list dig_flags = {.items = NULL, .count = 0};
103 mp_state_enum result = STATE_UNKNOWN; 112 mp_state_enum result = STATE_UNKNOWN;
113
104 /* run the command */ 114 /* run the command */
105 if (np_runcmd(command_line, &chld_out, &chld_err, 0) != 0) { 115 if (np_runcmd(command_line, &chld_out, &chld_err, 0) != 0) {
106 result = STATE_WARNING; 116 result = STATE_WARNING;
107 msg = (char *)_("dig returned an error status"); 117 msg = (char *)_("dig returned an error status");
108 } 118 }
109 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
110 for (size_t i = 0; i < chld_out.lines; i++) { 140 for (size_t i = 0; i < chld_out.lines; i++) {
111 /* the server is responding, we just got the host name... */ 141 /* the server is responding, we just got the host name... */
112 if (strstr(chld_out.line[i], ";; ANSWER SECTION:")) { 142 if (strstr(chld_out.line[i], ";; ANSWER SECTION:")) {
@@ -118,8 +148,9 @@ int main(int argc, char **argv) {
118 printf("%s\n", chld_out.line[i]); 148 printf("%s\n", chld_out.line[i]);
119 } 149 }
120 150
121 if (strcasestr(chld_out.line[i], (config.expected_address == NULL ? config.query_address : config.expected_address)) != 151 if (strcasestr(chld_out.line[i], (config.expected_address == NULL
122 NULL) { 152 ? config.query_address
153 : config.expected_address)) != NULL) {
123 msg = chld_out.line[i]; 154 msg = chld_out.line[i];
124 result = STATE_OK; 155 result = STATE_OK;
125 156
@@ -172,10 +203,49 @@ int main(int argc, char **argv) {
172 result = STATE_WARNING; 203 result = STATE_WARNING;
173 } 204 }
174 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
175 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,
176 msg ? msg : _("Probably a non-existent host/domain"), 245 msg ? msg : _("Probably a non-existent host/domain"),
177 fperfdata("time", elapsed_time, "s", (config.warning_interval > UNDEFINED), config.warning_interval, 246 fperfdata("time", elapsed_time, "s", (config.warning_interval > UNDEFINED),
178 (config.critical_interval > UNDEFINED), config.critical_interval, true, 0, false, 0)); 247 config.warning_interval, (config.critical_interval > UNDEFINED),
248 config.critical_interval, true, 0, false, 0));
179 exit(result); 249 exit(result);
180} 250}
181 251
@@ -187,6 +257,8 @@ check_dig_config_wrapper process_arguments(int argc, char **argv) {
187 {"critical", required_argument, 0, 'c'}, 257 {"critical", required_argument, 0, 'c'},
188 {"timeout", required_argument, 0, 't'}, 258 {"timeout", required_argument, 0, 't'},
189 {"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'},
190 {"verbose", no_argument, 0, 'v'}, 262 {"verbose", no_argument, 0, 'v'},
191 {"version", no_argument, 0, 'V'}, 263 {"version", no_argument, 0, 'V'},
192 {"help", no_argument, 0, 'h'}, 264 {"help", no_argument, 0, 'h'},
@@ -209,7 +281,8 @@ check_dig_config_wrapper process_arguments(int argc, char **argv) {
209 281
210 int option = 0; 282 int option = 0;
211 while (true) { 283 while (true) {
212 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);
213 286
214 if (option_index == -1 || option_index == EOF) { 287 if (option_index == -1 || option_index == EOF) {
215 break; 288 break;
@@ -260,6 +333,12 @@ check_dig_config_wrapper process_arguments(int argc, char **argv) {
260 case 'A': /* dig arguments */ 333 case 'A': /* dig arguments */
261 result.config.dig_args = strdup(optarg); 334 result.config.dig_args = strdup(optarg);
262 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;
263 case 'v': /* verbose */ 342 case 'v': /* verbose */
264 verbose++; 343 verbose++;
265 break; 344 break;
@@ -335,10 +414,15 @@ void print_help(void) {
335 printf(" %s\n", "-T, --record_type=STRING"); 414 printf(" %s\n", "-T, --record_type=STRING");
336 printf(" %s\n", _("Record type to lookup (default: A)")); 415 printf(" %s\n", _("Record type to lookup (default: A)"));
337 printf(" %s\n", "-a, --expected_address=STRING"); 416 printf(" %s\n", "-a, --expected_address=STRING");
338 printf(" %s\n", _("An address expected to be in the answer section. If not set, uses whatever")); 417 printf(" %s\n",
418 _("An address expected to be in the answer section. If not set, uses whatever"));
339 printf(" %s\n", _("was in -l")); 419 printf(" %s\n", _("was in -l"));
340 printf(" %s\n", "-A, --dig-arguments=STRING"); 420 printf(" %s\n", "-A, --dig-arguments=STRING");
341 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"));
342 printf(UT_WARN_CRIT); 426 printf(UT_WARN_CRIT);
343 printf(UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT); 427 printf(UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
344 printf(UT_VERBOSE); 428 printf(UT_VERBOSE);
@@ -355,5 +439,185 @@ void print_usage(void) {
355 printf("%s\n", _("Usage:")); 439 printf("%s\n", _("Usage:"));
356 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);
357 printf(" [-T <query type>] [-w <warning interval>] [-c <critical interval>]\n"); 441 printf(" [-T <query type>] [-w <warning interval>] [-c <critical interval>]\n");
358 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 */
459static 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 */
534static 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 */
589static 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 */
611static 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;
359} 623}