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.c919
1 files changed, 581 insertions, 338 deletions
diff --git a/plugins/check_dig.c b/plugins/check_dig.c
index be7a6101..9ea19e6a 100644
--- a/plugins/check_dig.c
+++ b/plugins/check_dig.c
@@ -1,30 +1,30 @@
1/***************************************************************************** 1/*****************************************************************************
2* 2 *
3* Monitoring check_dig plugin 3 * Monitoring check_dig plugin
4* 4 *
5* License: GPL 5 * License: GPL
6* Copyright (c) 2002-2008 Monitoring Plugins Development Team 6 * Copyright (c) 2002-2025 Monitoring Plugins Development Team
7* 7 *
8* Description: 8 * Description:
9* 9 *
10* This file contains the check_dig plugin 10 * This file contains the check_dig plugin
11* 11 *
12* 12 *
13* This program is free software: you can redistribute it and/or modify 13 * This program is free software: you can redistribute it and/or modify
14* it under the terms of the GNU General Public License as published by 14 * it under the terms of the GNU General Public License as published by
15* the Free Software Foundation, either version 3 of the License, or 15 * the Free Software Foundation, either version 3 of the License, or
16* (at your option) any later version. 16 * (at your option) any later version.
17* 17 *
18* This program is distributed in the hope that it will be useful, 18 * This program is distributed in the hope that it will be useful,
19* but WITHOUT ANY WARRANTY; without even the implied warranty of 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21* GNU General Public License for more details. 21 * GNU General Public License for more details.
22* 22 *
23* You should have received a copy of the GNU General Public License 23 * You should have received a copy of the GNU General Public License
24* along with this program. If not, see <http://www.gnu.org/licenses/>. 24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25* 25 *
26* 26 *
27*****************************************************************************/ 27 *****************************************************************************/
28 28
29/* Hackers note: 29/* Hackers note:
30 * There are typecasts to (char *) from _("foo bar") in this file. 30 * There are typecasts to (char *) from _("foo bar") in this file.
@@ -33,348 +33,591 @@
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-2008"; 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"
42#include "runcmd.h" 43#include "runcmd.h"
43 44
44int process_arguments (int, char **); 45#include "check_dig.d/config.h"
45int validate_arguments (void); 46#include "states.h"
46void print_help (void); 47
47void print_usage (void); 48typedef struct {
48 49 int errorcode;
49#define UNDEFINED 0 50 check_dig_config config;
50#define DEFAULT_PORT 53 51} check_dig_config_wrapper;
51#define DEFAULT_TRIES 2 52static check_dig_config_wrapper process_arguments(int /*argc*/, char ** /*argv*/);
52 53static check_dig_config_wrapper validate_arguments(check_dig_config_wrapper /*config_wrapper*/);
53char *query_address = NULL; 54
54char *record_type = "A"; 55static void print_help(void);
55char *expected_address = NULL; 56void print_usage(void);
56char *dns_server = NULL; 57
57char *dig_args = ""; 58static int verbose = 0;
58char *query_transport = ""; 59
59bool verbose = false; 60/* helpers for flag parsing */
60int server_port = DEFAULT_PORT; 61static flag_list parse_flags_line(const char *line);
61int number_tries = DEFAULT_TRIES; 62static flag_list split_csv_trim(const char *csv);
62double warning_interval = UNDEFINED; 63static bool flag_list_contains(const flag_list *list, const char *needle);
63double critical_interval = UNDEFINED; 64static void free_flag_list(flag_list *list);
64struct timeval tv; 65
65 66int main(int argc, char **argv) {
66int 67 setlocale(LC_ALL, "");
67main (int argc, char **argv) 68 bindtextdomain(PACKAGE, LOCALEDIR);
68{ 69 textdomain(PACKAGE);
69 char *command_line; 70
70 output chld_out, chld_err; 71 /* Set signal handling and alarm */
71 char *msg = NULL; 72 if (signal(SIGALRM, runcmd_timeout_alarm_handler) == SIG_ERR) {
72 size_t i; 73 usage_va(_("Cannot catch SIGALRM"));
73 char *t; 74 }
74 long microsec; 75
75 double elapsed_time; 76 /* Parse extra opts if any */
76 int result = STATE_UNKNOWN; 77 argv = np_extra_opts(&argc, argv, progname);
77 int timeout_interval_dig; 78
78 79 check_dig_config_wrapper tmp_config = process_arguments(argc, argv);
79 setlocale (LC_ALL, ""); 80 if (tmp_config.errorcode == ERROR) {
80 bindtextdomain (PACKAGE, LOCALEDIR); 81 usage_va(_("Could not parse arguments"));
81 textdomain (PACKAGE); 82 }
82 83
83 /* Set signal handling and alarm */ 84 const check_dig_config config = tmp_config.config;
84 if (signal (SIGALRM, runcmd_timeout_alarm_handler) == SIG_ERR) 85
85 usage_va(_("Cannot catch SIGALRM")); 86 /* dig applies the timeout to each try, so we need to work around this */
86 87 int timeout_interval_dig = ((int)timeout_interval / config.number_tries) + config.number_tries;
87 /* Parse extra opts if any */ 88
88 argv=np_extra_opts (&argc, argv, progname); 89 char *command_line;
89 90 /* get the command to run */
90 if (process_arguments (argc, argv) == ERROR) 91 xasprintf(&command_line, "%s %s %s -p %d @%s %s %s +retry=%d +time=%d", PATH_TO_DIG,
91 usage_va(_("Could not parse arguments")); 92 config.dig_args, config.query_transport, config.server_port, config.dns_server,
92 93 config.query_address, config.record_type, config.number_tries, timeout_interval_dig);
93 /* dig applies the timeout to each try, so we need to work around this */ 94
94 timeout_interval_dig = timeout_interval / number_tries + number_tries; 95 alarm(timeout_interval);
95 96 struct timeval start_time;
96 /* get the command to run */ 97 gettimeofday(&start_time, NULL);
97 xasprintf (&command_line, "%s %s %s -p %d @%s %s %s +retry=%d +time=%d", 98
98 PATH_TO_DIG, dig_args, query_transport, server_port, dns_server, query_address, record_type, number_tries, timeout_interval_dig); 99 if (verbose) {
99 100 printf("%s\n", command_line);
100 alarm (timeout_interval); 101 if (config.expected_address != NULL) {
101 gettimeofday (&tv, NULL); 102 printf(_("Looking for: '%s'\n"), config.expected_address);
102 103 } else {
103 if (verbose) { 104 printf(_("Looking for: '%s'\n"), config.query_address);
104 printf ("%s\n", command_line); 105 }
105 if(expected_address != NULL) { 106 }
106 printf (_("Looking for: '%s'\n"), expected_address); 107
107 } else { 108 output chld_out;
108 printf (_("Looking for: '%s'\n"), query_address); 109 output chld_err;
109 } 110 char *msg = NULL;
110 } 111 flag_list dig_flags = {.items = NULL, .count = 0};
111 112 mp_state_enum result = STATE_UNKNOWN;
112 /* run the command */ 113
113 if(np_runcmd(command_line, &chld_out, &chld_err, 0) != 0) { 114 /* run the command */
114 result = STATE_WARNING; 115 if (np_runcmd(command_line, &chld_out, &chld_err, 0) != 0) {
115 msg = (char *)_("dig returned an error status"); 116 result = STATE_WARNING;
116 } 117 msg = (char *)_("dig returned an error status");
117 118 }
118 for(i = 0; i < chld_out.lines; i++) { 119
119 /* the server is responding, we just got the host name... */ 120 /* extract ';; flags: ...' from stdout (first occurrence) */
120 if (strstr (chld_out.line[i], ";; ANSWER SECTION:")) { 121 for (size_t i = 0; i < chld_out.lines; i++) {
121 122 if (strstr(chld_out.line[i], "flags:")) {
122 /* loop through the whole 'ANSWER SECTION' */ 123 if (verbose) {
123 for(; i < chld_out.lines; i++) { 124 printf("Raw flags line: %s\n", chld_out.line[i]);
124 /* get the host address */ 125 }
125 if (verbose) 126
126 printf ("%s\n", chld_out.line[i]); 127 dig_flags = parse_flags_line(chld_out.line[i]);
127 128
128 if (strcasestr (chld_out.line[i], (expected_address == NULL ? query_address : expected_address)) != NULL) { 129 if (verbose && dig_flags.count > 0) {
129 msg = chld_out.line[i]; 130 printf(_("Parsed flags:"));
130 result = STATE_OK; 131 for (size_t k = 0; k < dig_flags.count; k++) {
131 132 printf(" %s", dig_flags.items[k]);
132 /* Translate output TAB -> SPACE */ 133 }
133 t = msg; 134 printf("\n");
134 while ((t = strchr(t, '\t')) != NULL) *t = ' '; 135 }
135 break; 136 break;
136 } 137 }
137 } 138 }
138 139
139 if (result == STATE_UNKNOWN) { 140 for (size_t i = 0; i < chld_out.lines; i++) {
140 msg = (char *)_("Server not found in ANSWER SECTION"); 141 /* the server is responding, we just got the host name... */
141 result = STATE_WARNING; 142 if (strstr(chld_out.line[i], ";; ANSWER SECTION:")) {
142 } 143
143 144 /* loop through the whole 'ANSWER SECTION' */
144 /* we found the answer section, so break out of the loop */ 145 for (; i < chld_out.lines; i++) {
145 break; 146 /* get the host address */
146 } 147 if (verbose) {
147 } 148 printf("%s\n", chld_out.line[i]);
148 149 }
149 if (result == STATE_UNKNOWN) { 150
150 msg = (char *)_("No ANSWER SECTION found"); 151 if (strcasestr(chld_out.line[i], (config.expected_address == NULL
151 result = STATE_CRITICAL; 152 ? config.query_address
152 } 153 : config.expected_address)) != NULL) {
153 154 msg = chld_out.line[i];
154 /* If we get anything on STDERR, at least set warning */ 155 result = STATE_OK;
155 if(chld_err.buflen > 0) { 156
156 result = max_state(result, STATE_WARNING); 157 /* Translate output TAB -> SPACE */
157 if(!msg) for(i = 0; i < chld_err.lines; i++) { 158 char *temp = msg;
158 msg = strchr(chld_err.line[0], ':'); 159 while ((temp = strchr(temp, '\t')) != NULL) {
159 if(msg) { 160 *temp = ' ';
160 msg++; 161 }
161 break; 162 break;
162 } 163 }
163 } 164 }
164 } 165
165 166 if (result == STATE_UNKNOWN) {
166 microsec = deltime (tv); 167 msg = (char *)_("Server not found in ANSWER SECTION");
167 elapsed_time = (double)microsec / 1.0e6; 168 result = STATE_WARNING;
168 169 }
169 if (critical_interval > UNDEFINED && elapsed_time > critical_interval) 170
170 result = STATE_CRITICAL; 171 /* we found the answer section, so break out of the loop */
171 172 break;
172 else if (warning_interval > UNDEFINED && elapsed_time > warning_interval) 173 }
173 result = STATE_WARNING; 174 }
174 175
175 printf ("DNS %s - %.3f seconds response time (%s)|%s\n", 176 if (result == STATE_UNKNOWN) {
176 state_text (result), elapsed_time, 177 msg = (char *)_("No ANSWER SECTION found");
177 msg ? msg : _("Probably a non-existent host/domain"), 178 result = STATE_CRITICAL;
178 fperfdata("time", elapsed_time, "s", 179 }
179 (warning_interval>UNDEFINED ? true:false), 180
180 warning_interval, 181 /* If we get anything on STDERR, at least set warning */
181 (critical_interval>UNDEFINED ? true:false), 182 if (chld_err.buflen > 0) {
182 critical_interval, 183 result = max_state(result, STATE_WARNING);
183 true, 0, false, 0)); 184 if (!msg) {
184 return result; 185 for (size_t i = 0; i < chld_err.lines; i++) {
186 msg = strchr(chld_err.line[0], ':');
187 if (msg) {
188 msg++;
189 break;
190 }
191 }
192 }
193 }
194
195 long microsec = deltime(start_time);
196 double elapsed_time = (double)microsec / 1.0e6;
197
198 if (config.critical_interval > UNDEFINED && elapsed_time > config.critical_interval) {
199 result = STATE_CRITICAL;
200 }
201
202 else if (config.warning_interval > UNDEFINED && elapsed_time > config.warning_interval) {
203 result = STATE_WARNING;
204 }
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
244 printf("DNS %s - %.3f seconds response time (%s)|%s\n", state_text(result), elapsed_time,
245 msg ? msg : _("Probably a non-existent host/domain"),
246 fperfdata("time", elapsed_time, "s", (config.warning_interval > UNDEFINED),
247 config.warning_interval, (config.critical_interval > UNDEFINED),
248 config.critical_interval, true, 0, false, 0));
249 exit(result);
185} 250}
186 251
187
188
189/* process command-line arguments */ 252/* process command-line arguments */
190int 253check_dig_config_wrapper process_arguments(int argc, char **argv) {
191process_arguments (int argc, char **argv) 254 static struct option longopts[] = {{"hostname", required_argument, 0, 'H'},
192{ 255 {"query_address", required_argument, 0, 'l'},
193 int c; 256 {"warning", required_argument, 0, 'w'},
194 257 {"critical", required_argument, 0, 'c'},
195 int option = 0; 258 {"timeout", required_argument, 0, 't'},
196 static struct option longopts[] = { 259 {"dig-arguments", required_argument, 0, 'A'},
197 {"hostname", required_argument, 0, 'H'}, 260 {"require-flags", required_argument, 0, 'E'},
198 {"query_address", required_argument, 0, 'l'}, 261 {"forbid-flags", required_argument, 0, 'X'},
199 {"warning", required_argument, 0, 'w'}, 262 {"verbose", no_argument, 0, 'v'},
200 {"critical", required_argument, 0, 'c'}, 263 {"version", no_argument, 0, 'V'},
201 {"timeout", required_argument, 0, 't'}, 264 {"help", no_argument, 0, 'h'},
202 {"dig-arguments", required_argument, 0, 'A'}, 265 {"record_type", required_argument, 0, 'T'},
203 {"verbose", no_argument, 0, 'v'}, 266 {"expected_address", required_argument, 0, 'a'},
204 {"version", no_argument, 0, 'V'}, 267 {"port", required_argument, 0, 'p'},
205 {"help", no_argument, 0, 'h'}, 268 {"use-ipv4", no_argument, 0, '4'},
206 {"record_type", required_argument, 0, 'T'}, 269 {"use-ipv6", no_argument, 0, '6'},
207 {"expected_address", required_argument, 0, 'a'}, 270 {0, 0, 0, 0}};
208 {"port", required_argument, 0, 'p'}, 271
209 {"use-ipv4", no_argument, 0, '4'}, 272 check_dig_config_wrapper result = {
210 {"use-ipv6", no_argument, 0, '6'}, 273 .errorcode = OK,
211 {0, 0, 0, 0} 274 .config = check_dig_config_init(),
212 }; 275 };
213 276
214 if (argc < 2) 277 if (argc < 2) {
215 return ERROR; 278 result.errorcode = ERROR;
216 279 return result;
217 while (1) { 280 }
218 c = getopt_long (argc, argv, "hVvt:l:H:w:c:T:p:a:A:46", longopts, &option); 281
219 282 int option = 0;
220 if (c == -1 || c == EOF) 283 while (true) {
221 break; 284 int option_index =
222 285 getopt_long(argc, argv, "hVvt:l:H:w:c:T:p:a:A:E:X:46", longopts, &option);
223 switch (c) { 286
224 case 'h': /* help */ 287 if (option_index == -1 || option_index == EOF) {
225 print_help (); 288 break;
226 exit (STATE_UNKNOWN); 289 }
227 case 'V': /* version */ 290
228 print_revision (progname, NP_VERSION); 291 switch (option_index) {
229 exit (STATE_UNKNOWN); 292 case 'h': /* help */
230 case 'H': /* hostname */ 293 print_help();
231 host_or_die(optarg); 294 exit(STATE_UNKNOWN);
232 dns_server = optarg; 295 case 'V': /* version */
233 break; 296 print_revision(progname, NP_VERSION);
234 case 'p': /* server port */ 297 exit(STATE_UNKNOWN);
235 if (is_intpos (optarg)) { 298 case 'H': /* hostname */
236 server_port = atoi (optarg); 299 host_or_die(optarg);
237 } 300 result.config.dns_server = optarg;
238 else { 301 break;
239 usage_va(_("Port must be a positive integer - %s"), optarg); 302 case 'p': /* server port */
240 } 303 if (is_intpos(optarg)) {
241 break; 304 result.config.server_port = atoi(optarg);
242 case 'l': /* address to lookup */ 305 } else {
243 query_address = optarg; 306 usage_va(_("Port must be a positive integer - %s"), optarg);
244 break; 307 }
245 case 'w': /* warning */ 308 break;
246 if (is_nonnegative (optarg)) { 309 case 'l': /* address to lookup */
247 warning_interval = strtod (optarg, NULL); 310 result.config.query_address = optarg;
248 } 311 break;
249 else { 312 case 'w': /* warning */
250 usage_va(_("Warning interval must be a positive integer - %s"), optarg); 313 if (is_nonnegative(optarg)) {
251 } 314 result.config.warning_interval = strtod(optarg, NULL);
252 break; 315 } else {
253 case 'c': /* critical */ 316 usage_va(_("Warning interval must be a positive integer - %s"), optarg);
254 if (is_nonnegative (optarg)) { 317 }
255 critical_interval = strtod (optarg, NULL); 318 break;
256 } 319 case 'c': /* critical */
257 else { 320 if (is_nonnegative(optarg)) {
258 usage_va(_("Critical interval must be a positive integer - %s"), optarg); 321 result.config.critical_interval = strtod(optarg, NULL);
259 } 322 } else {
260 break; 323 usage_va(_("Critical interval must be a positive integer - %s"), optarg);
261 case 't': /* timeout */ 324 }
262 if (is_intnonneg (optarg)) { 325 break;
263 timeout_interval = atoi (optarg); 326 case 't': /* timeout */
264 } 327 if (is_intnonneg(optarg)) {
265 else { 328 timeout_interval = atoi(optarg);
266 usage_va(_("Timeout interval must be a positive integer - %s"), optarg); 329 } else {
267 } 330 usage_va(_("Timeout interval must be a positive integer - %s"), optarg);
268 break; 331 }
269 case 'A': /* dig arguments */ 332 break;
270 dig_args = strdup(optarg); 333 case 'A': /* dig arguments */
271 break; 334 result.config.dig_args = strdup(optarg);
272 case 'v': /* verbose */ 335 break;
273 verbose = true; 336 case 'E': /* require flags */
274 break; 337 result.config.require_flags = split_csv_trim(optarg);
275 case 'T': 338 break;
276 record_type = optarg; 339 case 'X': /* forbid flags */
277 break; 340 result.config.forbid_flags = split_csv_trim(optarg);
278 case 'a': 341 break;
279 expected_address = optarg; 342 case 'v': /* verbose */
280 break; 343 verbose++;
281 case '4': 344 break;
282 query_transport = "-4"; 345 case 'T':
283 break; 346 result.config.record_type = optarg;
284 case '6': 347 break;
285 query_transport = "-6"; 348 case 'a':
286 break; 349 result.config.expected_address = optarg;
287 default: /* usage5 */ 350 break;
288 usage5(); 351 case '4':
289 } 352 result.config.query_transport = "-4";
290 } 353 break;
291 354 case '6':
292 c = optind; 355 result.config.query_transport = "-6";
293 if (dns_server == NULL) { 356 break;
294 if (c < argc) { 357 default: /* usage5 */
295 host_or_die(argv[c]); 358 usage5();
296 dns_server = argv[c]; 359 }
297 } 360 }
298 else { 361
299 if (strcmp(query_transport,"-6") == 0) 362 int index = optind;
300 dns_server = strdup("::1"); 363 if (result.config.dns_server == NULL) {
301 else 364 if (index < argc) {
302 dns_server = strdup ("127.0.0.1"); 365 host_or_die(argv[index]);
303 } 366 result.config.dns_server = argv[index];
304 } 367 } else {
305 368 if (strcmp(result.config.query_transport, "-6") == 0) {
306 return validate_arguments (); 369 result.config.dns_server = strdup("::1");
370 } else {
371 result.config.dns_server = strdup("127.0.0.1");
372 }
373 }
374 }
375
376 return validate_arguments(result);
307} 377}
308 378
309 379check_dig_config_wrapper validate_arguments(check_dig_config_wrapper config_wrapper) {
310 380 if (config_wrapper.config.query_address == NULL) {
311int 381 config_wrapper.errorcode = ERROR;
312validate_arguments (void) 382 }
313{ 383 return config_wrapper;
314 if (query_address != NULL)
315 return OK;
316 else
317 return ERROR;
318} 384}
319 385
386void print_help(void) {
387 char *myport;
320 388
389 xasprintf(&myport, "%d", DEFAULT_PORT);
321 390
322void 391 print_revision(progname, NP_VERSION);
323print_help (void)
324{
325 char *myport;
326 392
327 xasprintf (&myport, "%d", DEFAULT_PORT); 393 printf("Copyright (c) 2000 Karl DeBisschop <kdebisschop@users.sourceforge.net>\n");
394 printf(COPYRIGHT, copyright, email);
328 395
329 print_revision (progname, NP_VERSION); 396 printf(_("This plugin tests the DNS service on the specified host using dig"));
330 397
331 printf ("Copyright (c) 2000 Karl DeBisschop <kdebisschop@users.sourceforge.net>\n"); 398 printf("\n\n");
332 printf (COPYRIGHT, copyright, email);
333 399
334 printf (_("This plugin tests the DNS service on the specified host using dig")); 400 print_usage();
335 401
336 printf ("\n\n"); 402 printf(UT_HELP_VRSN);
337 403
338 print_usage (); 404 printf(UT_EXTRA_OPTS);
339 405
340 printf (UT_HELP_VRSN); 406 printf(UT_HOST_PORT, 'p', myport);
341 407
342 printf (UT_EXTRA_OPTS); 408 printf(" %s\n", "-4, --use-ipv4");
409 printf(" %s\n", _("Force dig to only use IPv4 query transport"));
410 printf(" %s\n", "-6, --use-ipv6");
411 printf(" %s\n", _("Force dig to only use IPv6 query transport"));
412 printf(" %s\n", "-l, --query_address=STRING");
413 printf(" %s\n", _("Machine name to lookup"));
414 printf(" %s\n", "-T, --record_type=STRING");
415 printf(" %s\n", _("Record type to lookup (default: A)"));
416 printf(" %s\n", "-a, --expected_address=STRING");
417 printf(" %s\n",
418 _("An address expected to be in the answer section. If not set, uses whatever"));
419 printf(" %s\n", _("was in -l"));
420 printf(" %s\n", "-A, --dig-arguments=STRING");
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"));
426 printf(UT_WARN_CRIT);
427 printf(UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
428 printf(UT_VERBOSE);
343 429
344 printf (UT_HOST_PORT, 'p', myport); 430 printf("\n");
431 printf("%s\n", _("Examples:"));
432 printf(" %s\n", "check_dig -H DNSSERVER -l www.example.com -A \"+tcp\"");
433 printf(" %s\n", "This will send a tcp query to DNSSERVER for www.example.com");
345 434
346 printf (" %s\n","-4, --use-ipv4"); 435 printf(UT_SUPPORT);
347 printf (" %s\n",_("Force dig to only use IPv4 query transport")); 436}
348 printf (" %s\n","-6, --use-ipv6");
349 printf (" %s\n",_("Force dig to only use IPv6 query transport"));
350 printf (" %s\n","-l, --query_address=STRING");
351 printf (" %s\n",_("Machine name to lookup"));
352 printf (" %s\n","-T, --record_type=STRING");
353 printf (" %s\n",_("Record type to lookup (default: A)"));
354 printf (" %s\n","-a, --expected_address=STRING");
355 printf (" %s\n",_("An address expected to be in the answer section. If not set, uses whatever"));
356 printf (" %s\n",_("was in -l"));
357 printf (" %s\n","-A, --dig-arguments=STRING");
358 printf (" %s\n",_("Pass STRING as argument(s) to dig"));
359 printf (UT_WARN_CRIT);
360 printf (UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
361 printf (UT_VERBOSE);
362 437
363 printf ("\n"); 438void print_usage(void) {
364 printf ("%s\n", _("Examples:")); 439 printf("%s\n", _("Usage:"));
365 printf (" %s\n", "check_dig -H DNSSERVER -l www.example.com -A \"+tcp\""); 440 printf("%s -l <query_address> [-H <host>] [-p <server port>]\n", progname);
366 printf (" %s\n", "This will send a tcp query to DNSSERVER for www.example.com"); 441 printf(" [-T <query type>] [-w <warning interval>] [-c <critical interval>]\n");
442 printf(" [-t <timeout>] [-a <expected answer address>] [-E <flags>] [-X <flags>] [-v]\n");
443}
367 444
368 printf (UT_SUPPORT); 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;
369} 520}
370 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}
371 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}
372 601
373void 602/**
374print_usage (void) 603 * free_flag_list - Release all heap allocations held by a flag_list.
375{ 604 *
376 printf ("%s\n", _("Usage:")); 605 * Input:
377 printf ("%s -l <query_address> [-H <host>] [-p <server port>]\n", progname); 606 * list - pointer to a flag_list whose items were allocated by
378 printf (" [-T <query type>] [-w <warning interval>] [-c <critical interval>]\n"); 607 * parse_flags_line() or split_csv_trim().
379 printf (" [-t <timeout>] [-a <expected answer address>] [-v]\n"); 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;
380} 623}