xref: /netbsd-src/external/mpl/bind/dist/bin/dig/dig.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: dig.c,v 1.12 2025/01/26 16:24:32 christos Exp $	*/
2 
3 /*
4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5  *
6  * SPDX-License-Identifier: MPL-2.0
7  *
8  * This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11  *
12  * See the COPYRIGHT file distributed with this work for additional
13  * information regarding copyright ownership.
14  */
15 
16 /*! \file */
17 
18 #include <ctype.h>
19 #include <inttypes.h>
20 #include <stdbool.h>
21 #include <stdlib.h>
22 #include <time.h>
23 
24 #include <isc/attributes.h>
25 #include <isc/dir.h>
26 #include <isc/loop.h>
27 #include <isc/netaddr.h>
28 #include <isc/parseint.h>
29 #include <isc/result.h>
30 #include <isc/string.h>
31 #include <isc/time.h>
32 #include <isc/util.h>
33 
34 #include <dns/byaddr.h>
35 #include <dns/dns64.h>
36 #include <dns/fixedname.h>
37 #include <dns/masterdump.h>
38 #include <dns/message.h>
39 #include <dns/name.h>
40 #include <dns/rcode.h>
41 #include <dns/rdata.h>
42 #include <dns/rdataclass.h>
43 #include <dns/rdataset.h>
44 #include <dns/rdatatype.h>
45 #include <dns/tsig.h>
46 
47 #include "dighost.h"
48 
49 #define ADD_STRING(b, s)                                          \
50 	{                                                         \
51 		if (strlen(s) >= isc_buffer_availablelength(b)) { \
52 			return ((((ISC_R_NOSPACE))));             \
53 		} else {                                          \
54 			isc_buffer_putstr(b, s);                  \
55 		}                                                 \
56 	}
57 
58 #define DIG_MAX_ADDRESSES 20
59 
60 dig_lookup_t *default_lookup = NULL;
61 
62 static char *batchname = NULL;
63 static FILE *batchfp = NULL;
64 static char *argv0;
65 static int addresscount = 0;
66 
67 static char domainopt[DNS_NAME_MAXTEXT];
68 static char hexcookie[81];
69 
70 static bool short_form = false, printcmd = true, plusquest = false,
71 	    pluscomm = false, ipv4only = false, ipv6only = false, digrc = true;
72 static uint32_t splitwidth = 0xffffffff;
73 
74 /*% opcode text */
75 static const char *const opcodetext[] = {
76 	"QUERY",      "IQUERY",	    "STATUS",	  "RESERVED3",
77 	"NOTIFY",     "UPDATE",	    "RESERVED6",  "RESERVED7",
78 	"RESERVED8",  "RESERVED9",  "RESERVED10", "RESERVED11",
79 	"RESERVED12", "RESERVED13", "RESERVED14", "RESERVED15"
80 };
81 
82 static const char *
83 rcode_totext(dns_rcode_t rcode) {
84 	static char buf[64];
85 	isc_buffer_t b;
86 	isc_result_t result;
87 
88 	memset(buf, 0, sizeof(buf));
89 	isc_buffer_init(&b, buf + 1, sizeof(buf) - 2);
90 	result = dns_rcode_totext(rcode, &b);
91 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
92 	if (strspn(buf + 1, "0123456789") == strlen(buf + 1)) {
93 		buf[0] = '?';
94 		return buf;
95 	}
96 	return buf + 1;
97 }
98 
99 /*% print usage */
100 static void
101 print_usage(FILE *fp) {
102 	fprintf(fp,
103 		"Usage:  dig [@global-server] [domain] [q-type] [q-class] "
104 		"{q-opt}\n"
105 		"            {global-d-opt} host [@local-server] "
106 		"{local-d-opt}\n"
107 		"            [ host [@local-server] {local-d-opt} [...]]\n");
108 }
109 
110 #if TARGET_OS_IPHONE
111 static void
112 usage(void) {
113 	fprintf(stderr, "Press <Help> for complete list of options\n");
114 }
115 #else  /* if TARGET_OS_IPHONE */
116 noreturn static void
117 usage(void);
118 
119 static void
120 usage(void) {
121 	print_usage(stderr);
122 	fprintf(stderr, "\nUse \"dig -h\" (or \"dig -h | more\") "
123 			"for complete list of options\n");
124 	exit(EXIT_FAILURE);
125 }
126 #endif /* if TARGET_OS_IPHONE */
127 
128 /*% help */
129 static void
130 help(void) {
131 	print_usage(stdout);
132 	printf("Where:  domain	  is in the Domain Name System\n"
133 	       "        q-class  is one of (in,hs,ch,...) [default: in]\n"
134 	       "        q-type   is one of "
135 	       "(a,any,mx,ns,soa,hinfo,axfr,txt,...) "
136 	       "[default:a]\n"
137 	       "                 (Use ixfr=version for type ixfr)\n"
138 	       "        q-opt    is one of:\n"
139 	       "                 -4                  (use IPv4 query transport "
140 	       "only)\n"
141 	       "                 -6                  (use IPv6 query transport "
142 	       "only)\n"
143 	       "                 -b address[#port]   (bind to source "
144 	       "address/port)\n"
145 	       "                 -c class            (specify query class)\n"
146 	       "                 -f filename         (batch mode)\n"
147 	       "                 -k keyfile          (specify tsig key file)\n"
148 	       "                 -m                  (enable memory usage "
149 	       "debugging)\n"
150 	       "                 -p port             (specify port number)\n"
151 	       "                 -q name             (specify query name)\n"
152 	       "                 -r                  (do not read ~/.digrc)\n"
153 	       "                 -t type             (specify query type)\n"
154 	       "                 -u                  (display times in usec "
155 	       "instead of msec)\n"
156 	       "                 -x dot-notation     (shortcut for reverse "
157 	       "lookups)\n"
158 	       "                 -y [hmac:]name:key  (specify named base64 "
159 	       "tsig "
160 	       "key)\n"
161 	       "        d-opt    is of the form +keyword[=value], where "
162 	       "keyword "
163 	       "is:\n"
164 	       "                 +[no]aaflag         (Set AA flag in query "
165 	       "(+[no]aaflag))\n"
166 	       "                 +[no]aaonly         (Set AA flag in query "
167 	       "(+[no]aaflag))\n"
168 	       "                 +[no]additional     (Control display of "
169 	       "additional section)\n"
170 	       "                 +[no]adflag         (Set AD flag in query "
171 	       "(default on))\n"
172 	       "                 +[no]all            (Set or clear all display "
173 	       "flags)\n"
174 	       "                 +[no]answer         (Control display of "
175 	       "answer "
176 	       "section)\n"
177 	       "                 +[no]authority      (Control display of "
178 	       "authority section)\n"
179 	       "                 +[no]badcookie      (Retry BADCOOKIE "
180 	       "responses)\n"
181 	       "                 +[no]besteffort     (Try to parse even "
182 	       "illegal "
183 	       "messages)\n"
184 	       "                 +bufsize[=###]      (Set EDNS0 Max UDP packet "
185 	       "size)\n"
186 	       "                 +[no]cdflag         (Set checking disabled "
187 	       "flag in query)\n"
188 	       "                 +[no]class          (Control display of class "
189 	       "in records)\n"
190 	       "                 +[no]cmd            (Control display of "
191 	       "command line -\n"
192 	       "                                      global option)\n"
193 	       "                 +[no]comments       (Control display of "
194 	       "packet "
195 	       "header\n"
196 	       "                                      and section name "
197 	       "comments)\n"
198 	       "                 +[no]cookie         (Add a COOKIE option to "
199 	       "the request)\n"
200 	       "                 +[no]crypto         (Control display of "
201 	       "cryptographic\n"
202 	       "                                      fields in records)\n"
203 	       "                 +[no]defname        (Use search list "
204 	       "(+[no]search))\n"
205 	       "                 +[no]dns64prefix    (Get the DNS64 prefixes "
206 	       "from ipv4only.arpa)\n"
207 	       "                 +[no]dnssec         (Request DNSSEC records)\n"
208 	       "                 +domain=###         (Set default domainname)\n"
209 	       "                 +[no]edns[=###]     (Set EDNS version) [0]\n"
210 	       "                 +ednsflags=###      (Set EDNS flag bits)\n"
211 	       "                 +[no]ednsnegotiation (Set EDNS version "
212 	       "negotiation)\n"
213 	       "                 +ednsopt=###[:value] (Send specified EDNS "
214 	       "option)\n"
215 	       "                 +noednsopt          (Clear list of +ednsopt "
216 	       "options)\n"
217 	       "                 +[no]expandaaaa     (Expand AAAA records)\n"
218 	       "                 +[no]expire         (Request time to expire)\n"
219 	       "                 +[no]fail           (Don't try next server on "
220 	       "SERVFAIL)\n"
221 	       "                 +[no]header-only    (Send query without a "
222 	       "question section)\n"
223 	       "                 +[no]https[=###]    (DNS-over-HTTPS mode) "
224 	       "[/]\n"
225 	       "                 +[no]https-get      (Use GET instead of "
226 	       "default POST method while using HTTPS)\n"
227 	       "                 +[no]http-plain[=###]    (DNS over plain HTTP "
228 	       "mode) "
229 	       "[/]\n"
230 	       "                 +[no]http-plain-get      (Use GET instead of "
231 	       "default POST method while using plain HTTP)\n"
232 	       "                 +[no]identify       (ID responders in short "
233 	       "answers)\n"
234 #ifdef HAVE_LIBIDN2
235 	       "                 +[no]idn            (convert international "
236 	       "domain names)\n"
237 #endif /* ifdef HAVE_LIBIDN2 */
238 	       "                 +[no]ignore         (Don't revert to TCP for "
239 	       "TC responses.)\n"
240 	       "                 +[no]keepalive      (Request EDNS TCP "
241 	       "keepalive)\n"
242 	       "                 +[no]keepopen       (Keep the TCP socket open "
243 	       "between "
244 	       "queries)\n"
245 	       "                 +[no]multiline      (Print records in an "
246 	       "expanded format)\n"
247 	       "                 +ndots=###          (Set search NDOTS value)\n"
248 	       "                 +[no]nsid           (Request Name Server ID)\n"
249 	       "                 +[no]nssearch       (Search all authoritative "
250 	       "nameservers)\n"
251 	       "                 +[no]onesoa         (AXFR prints only one soa "
252 	       "record)\n"
253 	       "                 +[no]opcode=###     (Set the opcode of the "
254 	       "request)\n"
255 	       "                 +padding=###        (Set padding block size "
256 	       "[0])\n"
257 	       "                 "
258 	       "+[no]proxy[=src_addr[#src_port]-dst_addr[#dst_port]]        "
259 	       "(Add PROXYv2 headers to the queries. If addresses are omitted, "
260 	       "LOCAL PROXYv2 headers are added)\n"
261 	       "                 "
262 	       "+[no]proxy-plain[=src_addr[#src_port]-dst_addr[#dst_port]]  "
263 	       "(The same as '+[no]proxy', but send PROXYv2 headers ahead of "
264 	       "any encryption if an encrypted transport is used)\n"
265 	       "                 +qid=###            (Specify the query ID to "
266 	       "use when sending queries)\n"
267 	       "                 +[no]qr             (Print question before "
268 	       "sending)\n"
269 	       "                 +[no]question       (Control display of "
270 	       "question section)\n"
271 	       "                 +[no]raflag         (Set RA flag in query "
272 	       "(+[no]raflag))\n"
273 	       "                 +[no]rdflag         (Recursive mode "
274 	       "(+[no]recurse))\n"
275 	       "                 +[no]recurse        (Recursive mode "
276 	       "(+[no]rdflag))\n"
277 	       "                 +retry=###          (Set number of UDP "
278 	       "retries) [2]\n"
279 	       "                 +[no]rrcomments     (Control display of "
280 	       "per-record "
281 	       "comments)\n"
282 	       "                 +[no]search         (Set whether to use "
283 	       "searchlist)\n"
284 	       "                 +[no]short          (Display nothing except "
285 	       "short\n"
286 	       "                                      form of answers - global "
287 	       "option)\n"
288 	       "                 +[no]showbadcookie  (Show BADCOOKIE message)\n"
289 	       "                 +[no]showsearch     (Search with intermediate "
290 	       "results)\n"
291 	       "                 +[no]split=##       (Split hex/base64 fields "
292 	       "into chunks)\n"
293 	       "                 +[no]stats          (Control display of "
294 	       "statistics)\n"
295 	       "                 +subnet=addr        (Set edns-client-subnet "
296 	       "option)\n"
297 	       "                 +[no]tcflag         (Set TC flag in query "
298 	       "(+[no]tcflag))\n"
299 	       "                 +[no]tcp            (TCP mode (+[no]vc))\n"
300 	       "                 +timeout=###        (Set query timeout) [5]\n"
301 	       "                 +[no]tls            (DNS-over-TLS mode)\n"
302 	       "                 +[no]tls-ca[=file]  (Enable remote server's "
303 	       "TLS certificate validation)\n"
304 	       "                 +[no]tls-hostname=hostname (Explicitly set "
305 	       "the expected TLS hostname)\n"
306 	       "                 +[no]tls-certfile=file (Load client TLS "
307 	       "certificate chain from file)\n"
308 	       "                 +[no]tls-keyfile=file (Load client TLS "
309 	       "private key from file)\n"
310 	       "                 +[no]trace          (Trace delegation down "
311 	       "from root [implies +dnssec])\n"
312 	       "                 +tries=###          (Set number of UDP "
313 	       "attempts) [3]\n"
314 	       "                 +[no]ttlid          (Control display of ttls "
315 	       "in records)\n"
316 	       "                 +[no]ttlunits       (Display TTLs in "
317 	       "human-readable units)\n"
318 	       "                 +[no]unknownformat  (Print RDATA in RFC 3597 "
319 	       "\"unknown\" "
320 	       "format)\n"
321 	       "                 +[no]vc             (TCP mode (+[no]tcp))\n"
322 	       "                 +[no]yaml           (Present the results as "
323 	       "YAML)\n"
324 	       "                 +[no]zflag          (Set Z flag in query)\n"
325 	       "        global d-opts and servers (before host name) affect "
326 	       "all "
327 	       "queries.\n"
328 	       "        local d-opts and servers (after host name) affect only "
329 	       "that lookup.\n"
330 	       "        -h                           (print help and exit)\n"
331 	       "        -v                           (print version "
332 	       "and exit)\n");
333 }
334 
335 /*%
336  * Callback from dighost.c to print the received message.
337  */
338 static void
339 received(unsigned int bytes, isc_sockaddr_t *from, dig_query_t *query) {
340 	uint64_t diff;
341 	time_t tnow;
342 	struct tm tmnow;
343 	char time_str[100];
344 	char fromtext[ISC_SOCKADDR_FORMATSIZE];
345 
346 	isc_sockaddr_format(from, fromtext, sizeof(fromtext));
347 
348 	if (short_form || yaml) {
349 		return;
350 	}
351 
352 	if (query->lookup->stats) {
353 		const char *proto;
354 		diff = isc_time_microdiff(&query->time_recv, &query->time_sent);
355 		if (query->lookup->use_usec) {
356 			printf(";; Query time: %ld usec\n", (long)diff);
357 		} else {
358 			printf(";; Query time: %ld msec\n", (long)diff / 1000);
359 		}
360 		if (dig_lookup_is_tls(query->lookup)) {
361 			proto = "TLS";
362 		} else if (query->lookup->https_mode) {
363 			if (query->lookup->http_plain) {
364 				proto = query->lookup->https_get ? "HTTP-GET"
365 								 : "HTTP";
366 			} else {
367 				proto = query->lookup->https_get ? "HTTPS-GET"
368 								 : "HTTPS";
369 			}
370 		} else if (query->lookup->tcp_mode) {
371 			proto = "TCP";
372 		} else {
373 			proto = "UDP";
374 		}
375 		printf(";; SERVER: %s(%s) (%s)\n", fromtext, query->userarg,
376 		       proto);
377 
378 		if (query->lookup->proxy_mode) {
379 			printf(";; CLIENT PROXY HEADER");
380 
381 			if ((dig_lookup_is_tls(query->lookup) ||
382 			     (query->lookup->https_mode &&
383 			      !query->lookup->http_plain)) &&
384 			    query->lookup->proxy_plain)
385 			{
386 				printf(" (plain)");
387 			}
388 
389 			printf(": ");
390 
391 			if (!query->lookup->proxy_local) {
392 				char src_buf[ISC_SOCKADDR_FORMATSIZE] = { 0 };
393 				char dst_buf[ISC_SOCKADDR_FORMATSIZE] = { 0 };
394 
395 				isc_sockaddr_format(
396 					&query->lookup->proxy_src_addr, src_buf,
397 					sizeof(src_buf));
398 
399 				isc_sockaddr_format(
400 					&query->lookup->proxy_dst_addr, dst_buf,
401 					sizeof(dst_buf));
402 				printf("source: %s, destination: %s", src_buf,
403 				       dst_buf);
404 			} else {
405 				printf("LOCAL");
406 			}
407 
408 			printf("\n");
409 		}
410 		time(&tnow);
411 		(void)localtime_r(&tnow, &tmnow);
412 
413 		if (strftime(time_str, sizeof(time_str),
414 			     "%a %b %d %H:%M:%S %Z %Y", &tmnow) > 0U)
415 		{
416 			printf(";; WHEN: %s\n", time_str);
417 		}
418 		if (query->lookup->doing_xfr) {
419 			printf(";; XFR size: %u records (messages %u, "
420 			       "bytes %" PRIu64 ")\n",
421 			       query->rr_count, query->msg_count,
422 			       query->byte_count);
423 		} else {
424 			printf(";; MSG SIZE  rcvd: %u\n", bytes);
425 		}
426 		if (tsigkey != NULL) {
427 			if (!validated) {
428 				puts(";; WARNING -- Some TSIG could not "
429 				     "be validated");
430 			}
431 		}
432 		if ((tsigkey == NULL) && (keysecret[0] != 0)) {
433 			puts(";; WARNING -- TSIG key was not used.");
434 		}
435 		puts("");
436 	} else if (query->lookup->identify) {
437 		diff = isc_time_microdiff(&query->time_recv, &query->time_sent);
438 		if (query->lookup->use_usec) {
439 			printf(";; Received %" PRIu64 " bytes "
440 			       "from %s(%s) in %ld us\n\n",
441 			       query->lookup->doing_xfr ? query->byte_count
442 							: (uint64_t)bytes,
443 			       fromtext, query->userarg, (long)diff);
444 		} else {
445 			printf(";; Received %" PRIu64 " bytes "
446 			       "from %s(%s) in %ld ms\n\n",
447 			       query->lookup->doing_xfr ? query->byte_count
448 							: (uint64_t)bytes,
449 			       fromtext, query->userarg, (long)diff / 1000);
450 		}
451 	}
452 }
453 
454 /*
455  * Callback from dighost.c to print that it is trying a server.
456  * Not used in dig.
457  * XXX print_trying
458  */
459 static void
460 trying(char *frm, dig_lookup_t *lookup) {
461 	UNUSED(frm);
462 	UNUSED(lookup);
463 }
464 
465 /*%
466  * Internal print routine used to print short form replies.
467  */
468 static isc_result_t
469 say_message(dns_rdata_t *rdata, dig_query_t *query, isc_buffer_t *buf) {
470 	isc_result_t result;
471 	uint64_t diff;
472 	char store[sizeof(" in 18446744073709551616 us.")];
473 	unsigned int styleflags = 0;
474 
475 	if (query->lookup->trace || query->lookup->ns_search_only) {
476 		result = dns_rdatatype_totext(rdata->type, buf);
477 		if (result != ISC_R_SUCCESS) {
478 			return result;
479 		}
480 		ADD_STRING(buf, " ");
481 	}
482 
483 	/* Turn on rrcomments if explicitly enabled */
484 	if (query->lookup->rrcomments > 0) {
485 		styleflags |= DNS_STYLEFLAG_RRCOMMENT;
486 	}
487 	if (query->lookup->nocrypto) {
488 		styleflags |= DNS_STYLEFLAG_NOCRYPTO;
489 	}
490 	if (query->lookup->print_unknown_format) {
491 		styleflags |= DNS_STYLEFLAG_UNKNOWNFORMAT;
492 	}
493 	if (query->lookup->expandaaaa) {
494 		styleflags |= DNS_STYLEFLAG_EXPANDAAAA;
495 	}
496 	result = dns_rdata_tofmttext(rdata, NULL, styleflags, 0, splitwidth,
497 				     " ", buf);
498 	if (result == ISC_R_NOSPACE) {
499 		return result;
500 	}
501 	check_result(result, "dns_rdata_totext");
502 	if (query->lookup->identify) {
503 		diff = isc_time_microdiff(&query->time_recv, &query->time_sent);
504 		ADD_STRING(buf, " from server ");
505 		ADD_STRING(buf, query->servname);
506 		if (query->lookup->use_usec) {
507 			snprintf(store, sizeof(store), " in %" PRIu64 " us.",
508 				 diff);
509 		} else {
510 			snprintf(store, sizeof(store), " in %" PRIu64 " ms.",
511 				 diff / 1000);
512 		}
513 		ADD_STRING(buf, store);
514 	}
515 	ADD_STRING(buf, "\n");
516 	return ISC_R_SUCCESS;
517 }
518 
519 /*%
520  * short_form message print handler.  Calls above say_message()
521  */
522 static isc_result_t
523 dns64prefix_answer(dns_message_t *msg, isc_buffer_t *buf) {
524 	dns_rdataset_t *rdataset = NULL;
525 	dns_fixedname_t fixed;
526 	dns_name_t *name;
527 	isc_result_t result;
528 	isc_netprefix_t prefix[10];
529 	size_t i, count = 10;
530 
531 	name = dns_fixedname_initname(&fixed);
532 	result = dns_name_fromstring(name, "ipv4only.arpa", dns_rootname, 0,
533 				     NULL);
534 	check_result(result, "dns_name_fromstring");
535 
536 	result = dns_message_findname(msg, DNS_SECTION_ANSWER, name,
537 				      dns_rdatatype_aaaa, dns_rdatatype_none,
538 				      NULL, &rdataset);
539 	if (result == DNS_R_NXDOMAIN || result == DNS_R_NXRRSET) {
540 		return ISC_R_SUCCESS;
541 	} else if (result != ISC_R_SUCCESS) {
542 		return result;
543 	}
544 
545 	result = dns_dns64_findprefix(rdataset, prefix, &count);
546 	if (result == ISC_R_NOTFOUND) {
547 		return ISC_R_SUCCESS;
548 	}
549 	if (count > 10) {
550 		count = 10;
551 	}
552 	for (i = 0; i < count; i++) {
553 		result = isc_netaddr_totext(&prefix[i].addr, buf);
554 		if (result != ISC_R_SUCCESS) {
555 			return result;
556 		}
557 		result = isc_buffer_printf(buf, "/%u\n", prefix[i].prefixlen);
558 		if (result != ISC_R_SUCCESS) {
559 			return result;
560 		}
561 	}
562 
563 	return ISC_R_SUCCESS;
564 }
565 
566 /*%
567  * short_form message print handler.  Calls above say_message()
568  */
569 static isc_result_t
570 short_answer(dns_message_t *msg, dns_messagetextflag_t flags, isc_buffer_t *buf,
571 	     dig_query_t *query) {
572 	dns_name_t *name;
573 	dns_rdataset_t *rdataset;
574 	isc_result_t result, loopresult;
575 	dns_name_t empty_name;
576 	dns_rdata_t rdata = DNS_RDATA_INIT;
577 
578 	UNUSED(flags);
579 
580 	dns_name_init(&empty_name, NULL);
581 	result = dns_message_firstname(msg, DNS_SECTION_ANSWER);
582 	if (result == ISC_R_NOMORE) {
583 		return ISC_R_SUCCESS;
584 	} else if (result != ISC_R_SUCCESS) {
585 		return result;
586 	}
587 
588 	for (;;) {
589 		name = NULL;
590 		dns_message_currentname(msg, DNS_SECTION_ANSWER, &name);
591 
592 		for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
593 		     rdataset = ISC_LIST_NEXT(rdataset, link))
594 		{
595 			loopresult = dns_rdataset_first(rdataset);
596 			while (loopresult == ISC_R_SUCCESS) {
597 				dns_rdataset_current(rdataset, &rdata);
598 				result = say_message(&rdata, query, buf);
599 				if (result == ISC_R_NOSPACE) {
600 					return result;
601 				}
602 				check_result(result, "say_message");
603 				loopresult = dns_rdataset_next(rdataset);
604 				dns_rdata_reset(&rdata);
605 			}
606 		}
607 		result = dns_message_nextname(msg, DNS_SECTION_ANSWER);
608 		if (result == ISC_R_NOMORE) {
609 			break;
610 		} else if (result != ISC_R_SUCCESS) {
611 			return result;
612 		}
613 	}
614 
615 	return ISC_R_SUCCESS;
616 }
617 
618 static bool
619 isdotlocal(dns_message_t *msg) {
620 	isc_result_t result;
621 	static unsigned char local_ndata[] = { "\005local" };
622 	static unsigned char local_offsets[] = { 0, 6 };
623 	static dns_name_t local = DNS_NAME_INITABSOLUTE(local_ndata,
624 							local_offsets);
625 
626 	for (result = dns_message_firstname(msg, DNS_SECTION_QUESTION);
627 	     result == ISC_R_SUCCESS;
628 	     result = dns_message_nextname(msg, DNS_SECTION_QUESTION))
629 	{
630 		dns_name_t *name = NULL;
631 		dns_message_currentname(msg, DNS_SECTION_QUESTION, &name);
632 		if (dns_name_issubdomain(name, &local)) {
633 			return true;
634 		}
635 	}
636 	return false;
637 }
638 
639 /*
640  * Callback from dighost.c to print the reply from a server
641  */
642 static isc_result_t
643 printmessage(dig_query_t *query, const isc_buffer_t *msgbuf, dns_message_t *msg,
644 	     bool headers) {
645 	isc_result_t result;
646 	dns_messagetextflag_t flags;
647 	isc_buffer_t *buf = NULL;
648 	unsigned int len = OUTPUTBUF;
649 	dns_master_style_t *style = NULL;
650 	unsigned int styleflags = 0;
651 	bool isquery = (msg == query->lookup->sendmsg);
652 	bool dns64prefix = query->lookup->dns64prefix;
653 
654 	UNUSED(msgbuf);
655 
656 	dig_idnsetup(query->lookup, true);
657 
658 	styleflags |= DNS_STYLEFLAG_REL_OWNER;
659 	if (yaml) {
660 		msg->indent.string = "  ";
661 		msg->indent.count = 3;
662 		styleflags |= DNS_STYLEFLAG_YAML;
663 	} else {
664 		if (query->lookup->comments) {
665 			styleflags |= DNS_STYLEFLAG_COMMENT;
666 		}
667 		if (query->lookup->print_unknown_format) {
668 			styleflags |= DNS_STYLEFLAG_UNKNOWNFORMAT;
669 		}
670 		/* Turn on rrcomments if explicitly enabled */
671 		if (query->lookup->rrcomments > 0) {
672 			styleflags |= DNS_STYLEFLAG_RRCOMMENT;
673 		}
674 		if (query->lookup->ttlunits) {
675 			styleflags |= DNS_STYLEFLAG_TTL_UNITS;
676 		}
677 		if (query->lookup->nottl) {
678 			styleflags |= DNS_STYLEFLAG_NO_TTL;
679 		}
680 		if (query->lookup->noclass) {
681 			styleflags |= DNS_STYLEFLAG_NO_CLASS;
682 		}
683 		if (query->lookup->nocrypto) {
684 			styleflags |= DNS_STYLEFLAG_NOCRYPTO;
685 		}
686 		if (query->lookup->expandaaaa) {
687 			styleflags |= DNS_STYLEFLAG_EXPANDAAAA;
688 		}
689 		if (query->lookup->multiline) {
690 			styleflags |= DNS_STYLEFLAG_OMIT_OWNER;
691 			styleflags |= DNS_STYLEFLAG_OMIT_CLASS;
692 			styleflags |= DNS_STYLEFLAG_REL_DATA;
693 			styleflags |= DNS_STYLEFLAG_OMIT_TTL;
694 			styleflags |= DNS_STYLEFLAG_TTL;
695 			styleflags |= DNS_STYLEFLAG_MULTILINE;
696 			/* Turn on rrcomments unless explicitly disabled */
697 			if (query->lookup->rrcomments >= 0) {
698 				styleflags |= DNS_STYLEFLAG_RRCOMMENT;
699 			}
700 		}
701 	}
702 	if (query->lookup->multiline ||
703 	    (query->lookup->nottl && query->lookup->noclass))
704 	{
705 		result = dns_master_stylecreate(&style, styleflags, 24, 24, 24,
706 						32, 80, 8, splitwidth, mctx);
707 	} else if (query->lookup->nottl || query->lookup->noclass) {
708 		result = dns_master_stylecreate(&style, styleflags, 24, 24, 32,
709 						40, 80, 8, splitwidth, mctx);
710 	} else {
711 		result = dns_master_stylecreate(&style, styleflags, 24, 32, 40,
712 						48, 80, 8, splitwidth, mctx);
713 	}
714 	check_result(result, "dns_master_stylecreate");
715 
716 	if (query->lookup->cmdline[0] != 0) {
717 		if (!short_form && !dns64prefix && printcmd) {
718 			printf("%s", query->lookup->cmdline);
719 		}
720 		query->lookup->cmdline[0] = '\0';
721 	}
722 	debug("printmessage(%s %s %s)", headers ? "headers" : "noheaders",
723 	      query->lookup->comments ? "comments" : "nocomments",
724 	      short_form    ? "short_form"
725 	      : dns64prefix ? "dns64prefix_form"
726 			    : "long_form");
727 
728 	flags = 0;
729 	if (!headers) {
730 		flags |= DNS_MESSAGETEXTFLAG_NOHEADERS;
731 		flags |= DNS_MESSAGETEXTFLAG_NOCOMMENTS;
732 	}
733 	if (query->lookup->onesoa &&
734 	    query->lookup->rdtype == dns_rdatatype_axfr)
735 	{
736 		flags |= (query->msg_count == 0) ? DNS_MESSAGETEXTFLAG_ONESOA
737 						 : DNS_MESSAGETEXTFLAG_OMITSOA;
738 	}
739 	if (!query->lookup->comments) {
740 		flags |= DNS_MESSAGETEXTFLAG_NOCOMMENTS;
741 	}
742 
743 	isc_buffer_allocate(mctx, &buf, len);
744 
745 	if (yaml) {
746 		enum { Q = 0x1, R = 0x2 }; /* Q:query; R:ecursive */
747 		unsigned int tflag = 0;
748 		char sockstr[ISC_SOCKADDR_FORMATSIZE];
749 		uint16_t sport;
750 		char *hash;
751 		int pf;
752 
753 		printf("- type: MESSAGE\n");
754 		printf("  message:\n");
755 
756 		if (isquery) {
757 			tflag |= Q;
758 			if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0) {
759 				tflag |= R;
760 			}
761 		} else if (((msg->flags & DNS_MESSAGEFLAG_RD) != 0) &&
762 			   ((msg->flags & DNS_MESSAGEFLAG_RA) != 0))
763 		{
764 			tflag |= R;
765 		}
766 
767 		if (tflag == (Q | R)) {
768 			printf("    type: RECURSIVE_QUERY\n");
769 		} else if (tflag == Q) {
770 			printf("    type: AUTH_QUERY\n");
771 		} else if (tflag == R) {
772 			printf("    type: RECURSIVE_RESPONSE\n");
773 		} else {
774 			printf("    type: AUTH_RESPONSE\n");
775 		}
776 
777 		if (!isc_time_isepoch(&query->time_sent)) {
778 			char tbuf[100];
779 			if (query->lookup->use_usec) {
780 				isc_time_formatISO8601us(&query->time_sent,
781 							 tbuf, sizeof(tbuf));
782 			} else {
783 				isc_time_formatISO8601ms(&query->time_sent,
784 							 tbuf, sizeof(tbuf));
785 			}
786 			printf("    query_time: !!timestamp %s\n", tbuf);
787 		}
788 
789 		if (!isquery && !isc_time_isepoch(&query->time_recv)) {
790 			char tbuf[100];
791 			if (query->lookup->use_usec) {
792 				isc_time_formatISO8601us(&query->time_recv,
793 							 tbuf, sizeof(tbuf));
794 			} else {
795 				isc_time_formatISO8601ms(&query->time_recv,
796 							 tbuf, sizeof(tbuf));
797 			}
798 			printf("    response_time: !!timestamp %s\n", tbuf);
799 		}
800 
801 		printf("    message_size: %ub\n",
802 		       isc_buffer_usedlength(msgbuf));
803 
804 		pf = isc_sockaddr_pf(&query->sockaddr);
805 		if (pf == PF_INET || pf == PF_INET6) {
806 			printf("    socket_family: %s\n",
807 			       pf == PF_INET ? "INET" : "INET6");
808 
809 			printf("    socket_protocol: %s\n",
810 			       query->lookup->tcp_mode ? "TCP" : "UDP");
811 
812 			sport = isc_sockaddr_getport(&query->sockaddr);
813 			isc_sockaddr_format(&query->sockaddr, sockstr,
814 					    sizeof(sockstr));
815 			hash = strchr(sockstr, '#');
816 			if (hash != NULL) {
817 				*hash = '\0';
818 			}
819 			if (strcmp(sockstr, "::") == 0) {
820 				strlcat(sockstr, "0", sizeof(sockstr));
821 			}
822 
823 			printf("    response_address: \"%s\"\n", sockstr);
824 			printf("    response_port: %u\n", sport);
825 		}
826 
827 		if (query->handle != NULL) {
828 			isc_sockaddr_t saddr =
829 				isc_nmhandle_localaddr(query->handle);
830 			sport = isc_sockaddr_getport(&saddr);
831 			isc_sockaddr_format(&saddr, sockstr, sizeof(sockstr));
832 			hash = strchr(sockstr, '#');
833 			if (hash != NULL) {
834 				*hash = '\0';
835 			}
836 			if (strcmp(sockstr, "::") == 0) {
837 				strlcat(sockstr, "0", sizeof(sockstr));
838 			}
839 
840 			printf("    query_address: \"%s\"\n", sockstr);
841 			printf("    query_port: %u\n", sport);
842 		}
843 
844 		printf("    %s:\n", isquery ? "query_message_data"
845 					    : "response_message_data");
846 		result = dns_message_headertotext(msg, style, flags, buf);
847 	} else if (query->lookup->comments && !short_form && !dns64prefix) {
848 		if (query->lookup->cmdline[0] != '\0' && printcmd) {
849 			printf("; %s\n", query->lookup->cmdline);
850 		}
851 		if (msg == query->lookup->sendmsg) {
852 			printf(";; Sending:\n");
853 		} else {
854 			printf(";; Got answer:\n");
855 		}
856 
857 		if (headers) {
858 			if (isdotlocal(msg)) {
859 				printf(";; WARNING: .local is reserved for "
860 				       "Multicast DNS\n;; You are currently "
861 				       "testing what happens when an mDNS "
862 				       "query is leaked to DNS\n");
863 			}
864 			printf(";; ->>HEADER<<- opcode: %s, status: %s, "
865 			       "id: %u\n",
866 			       opcodetext[msg->opcode],
867 			       rcode_totext(msg->rcode), msg->id);
868 			printf(";; flags:");
869 			if ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) {
870 				printf(" qr");
871 			}
872 			if ((msg->flags & DNS_MESSAGEFLAG_AA) != 0) {
873 				printf(" aa");
874 			}
875 			if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) {
876 				printf(" tc");
877 			}
878 			if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0) {
879 				printf(" rd");
880 			}
881 			if ((msg->flags & DNS_MESSAGEFLAG_RA) != 0) {
882 				printf(" ra");
883 			}
884 			if ((msg->flags & DNS_MESSAGEFLAG_AD) != 0) {
885 				printf(" ad");
886 			}
887 			if ((msg->flags & DNS_MESSAGEFLAG_CD) != 0) {
888 				printf(" cd");
889 			}
890 			if ((msg->flags & 0x0040U) != 0) {
891 				printf("; MBZ: 0x4");
892 			}
893 
894 			printf("; QUERY: %u, ANSWER: %u, "
895 			       "AUTHORITY: %u, ADDITIONAL: %u\n",
896 			       msg->counts[DNS_SECTION_QUESTION],
897 			       msg->counts[DNS_SECTION_ANSWER],
898 			       msg->counts[DNS_SECTION_AUTHORITY],
899 			       msg->counts[DNS_SECTION_ADDITIONAL]);
900 
901 			if (msg != query->lookup->sendmsg &&
902 			    (msg->flags & DNS_MESSAGEFLAG_RD) != 0 &&
903 			    (msg->flags & DNS_MESSAGEFLAG_RA) == 0)
904 			{
905 				printf(";; WARNING: recursion requested "
906 				       "but not available\n");
907 			}
908 		}
909 		if (msg != query->lookup->sendmsg &&
910 		    query->lookup->edns != -1 && msg->opt == NULL &&
911 		    (msg->rcode == dns_rcode_formerr ||
912 		     msg->rcode == dns_rcode_notimp))
913 		{
914 			printf("\n;; WARNING: EDNS query returned status "
915 			       "%s - retry with '%s+noedns'\n",
916 			       rcode_totext(msg->rcode),
917 			       query->lookup->dnssec ? "+nodnssec " : "");
918 		}
919 		if (msg != query->lookup->sendmsg && extrabytes != 0U) {
920 			printf(";; WARNING: Message has %u extra byte%s at "
921 			       "end\n",
922 			       extrabytes, extrabytes != 0 ? "s" : "");
923 		}
924 	}
925 
926 repopulate_buffer:
927 
928 	if (query->lookup->comments && headers && !short_form && !dns64prefix) {
929 		result = dns_message_pseudosectiontotext(
930 			msg, DNS_PSEUDOSECTION_OPT, style, flags, buf);
931 		if (result == ISC_R_NOSPACE) {
932 		buftoosmall:
933 			len += OUTPUTBUF;
934 			isc_buffer_free(&buf);
935 			isc_buffer_allocate(mctx, &buf, len);
936 			goto repopulate_buffer;
937 		}
938 		check_result(result, "dns_message_pseudosectiontotext");
939 	}
940 
941 	if (query->lookup->section_question && headers) {
942 		if (!short_form && !dns64prefix) {
943 			result = dns_message_sectiontotext(
944 				msg, DNS_SECTION_QUESTION, style, flags, buf);
945 			if (result == ISC_R_NOSPACE) {
946 				goto buftoosmall;
947 			}
948 			check_result(result, "dns_message_sectiontotext");
949 		}
950 	}
951 	if (query->lookup->section_answer) {
952 		if (!short_form && !dns64prefix) {
953 			result = dns_message_sectiontotext(
954 				msg, DNS_SECTION_ANSWER, style, flags, buf);
955 			if (result == ISC_R_NOSPACE) {
956 				goto buftoosmall;
957 			}
958 			check_result(result, "dns_message_sectiontotext");
959 		} else if (dns64prefix) {
960 			result = dns64prefix_answer(msg, buf);
961 			if (result == ISC_R_NOSPACE) {
962 				goto buftoosmall;
963 			}
964 			check_result(result, "dns64prefix_answer");
965 		} else {
966 			result = short_answer(msg, flags, buf, query);
967 			if (result == ISC_R_NOSPACE) {
968 				goto buftoosmall;
969 			}
970 			check_result(result, "short_answer");
971 		}
972 	}
973 	if (query->lookup->section_authority) {
974 		if (!short_form && !dns64prefix) {
975 			result = dns_message_sectiontotext(
976 				msg, DNS_SECTION_AUTHORITY, style, flags, buf);
977 			if (result == ISC_R_NOSPACE) {
978 				goto buftoosmall;
979 			}
980 			check_result(result, "dns_message_sectiontotext");
981 		}
982 	}
983 	if (query->lookup->section_additional) {
984 		if (!short_form && !dns64prefix) {
985 			result = dns_message_sectiontotext(
986 				msg, DNS_SECTION_ADDITIONAL, style, flags, buf);
987 			if (result == ISC_R_NOSPACE) {
988 				goto buftoosmall;
989 			}
990 			check_result(result, "dns_message_sectiontotext");
991 			/*
992 			 * Only print the signature on the first record.
993 			 */
994 			if (headers) {
995 				result = dns_message_pseudosectiontotext(
996 					msg, DNS_PSEUDOSECTION_TSIG, style,
997 					flags, buf);
998 				if (result == ISC_R_NOSPACE) {
999 					goto buftoosmall;
1000 				}
1001 				check_result(result, "dns_message_"
1002 						     "pseudosectiontotext");
1003 				result = dns_message_pseudosectiontotext(
1004 					msg, DNS_PSEUDOSECTION_SIG0, style,
1005 					flags, buf);
1006 				if (result == ISC_R_NOSPACE) {
1007 					goto buftoosmall;
1008 				}
1009 				check_result(result, "dns_message_"
1010 						     "pseudosectiontotext");
1011 			}
1012 		}
1013 	}
1014 
1015 	if (headers && query->lookup->comments && !short_form && !yaml) {
1016 		printf("\n");
1017 	}
1018 
1019 	printf("%.*s", (int)isc_buffer_usedlength(buf),
1020 	       (char *)isc_buffer_base(buf));
1021 	isc_buffer_free(&buf);
1022 
1023 	if (style != NULL) {
1024 		dns_master_styledestroy(&style, mctx);
1025 	}
1026 
1027 	dig_idnsetup(query->lookup, false);
1028 
1029 	return result;
1030 }
1031 
1032 /*%
1033  * print the greeting message when the program first starts up.
1034  */
1035 static void
1036 printgreeting(int argc, char **argv, dig_lookup_t *lookup) {
1037 	int i;
1038 	static bool first = true;
1039 	char append[MXNAME];
1040 
1041 	if (printcmd) {
1042 		snprintf(lookup->cmdline, sizeof(lookup->cmdline),
1043 			 "%s; <<>> DiG %s <<>>", first ? "\n" : "",
1044 			 PACKAGE_VERSION);
1045 		i = 1;
1046 		while (i < argc) {
1047 			snprintf(append, sizeof(append), " %s", argv[i++]);
1048 			strlcat(lookup->cmdline, append,
1049 				sizeof(lookup->cmdline));
1050 		}
1051 		strlcat(lookup->cmdline, "\n", sizeof(lookup->cmdline));
1052 		if (first && addresscount != 0) {
1053 			snprintf(append, sizeof(append),
1054 				 "; (%d server%s found)\n", addresscount,
1055 				 addresscount > 1 ? "s" : "");
1056 			strlcat(lookup->cmdline, append,
1057 				sizeof(lookup->cmdline));
1058 		}
1059 		if (first) {
1060 			snprintf(append, sizeof(append),
1061 				 ";; global options:%s%s\n",
1062 				 short_form ? " +short" : "",
1063 				 printcmd ? " +cmd" : "");
1064 			first = false;
1065 			strlcat(lookup->cmdline, append,
1066 				sizeof(lookup->cmdline));
1067 		}
1068 	}
1069 }
1070 
1071 #define FULLCHECK(A)                                                 \
1072 	do {                                                         \
1073 		size_t _l = strlen(cmd);                             \
1074 		if (_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) \
1075 			goto invalid_option;                         \
1076 	} while (0)
1077 #define FULLCHECK2(A, B)                                                 \
1078 	do {                                                             \
1079 		size_t _l = strlen(cmd);                                 \
1080 		if ((_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) && \
1081 		    (_l >= sizeof(B) || strncasecmp(cmd, B, _l) != 0))   \
1082 			goto invalid_option;                             \
1083 	} while (0)
1084 #define FULLCHECK6(A, B, C, D, E, F)                                     \
1085 	do {                                                             \
1086 		size_t _l = strlen(cmd);                                 \
1087 		if ((_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) && \
1088 		    (_l >= sizeof(B) || strncasecmp(cmd, B, _l) != 0) && \
1089 		    (_l >= sizeof(C) || strncasecmp(cmd, C, _l) != 0) && \
1090 		    (_l >= sizeof(D) || strncasecmp(cmd, D, _l) != 0) && \
1091 		    (_l >= sizeof(E) || strncasecmp(cmd, E, _l) != 0) && \
1092 		    (_l >= sizeof(F) || strncasecmp(cmd, F, _l) != 0))   \
1093 			goto invalid_option;                             \
1094 	} while (0)
1095 
1096 /*
1097  * Parse source and destination addresses in the same format as used by "kdig":
1098  *
1099  * SRC_ADDR[#SRC_PORT]-DST_ADDR[#DST_PORT]
1100  *
1101  * This can be described (pretty closely for our purpose) using the
1102  * following EBNF grammar:
1103  *
1104  * S = proxy-addrs. (* start rule *)
1105  * proxy-addrs = addr "-" addr EOF.
1106  * addr = addr-char { addr-char } ["#" port ].
1107  * port = digit { digit }.
1108  * addr-char = <aby but "#", "-", EOF >.
1109  * EOF = '\0'.
1110  */
1111 #define MATCH(ch)     (st->str[0] == (ch))
1112 #define MATCH_DIGIT() isdigit((unsigned char)(st->str[0]))
1113 #define ADVANCE()     st->str++
1114 #define GETP()	      (st->str)
1115 
1116 typedef struct isc_proxy_addrs_parser_state {
1117 	const char *str;
1118 
1119 	const char *last_addr_start;
1120 	size_t last_addr_len;
1121 
1122 	const char *last_port_start;
1123 	size_t last_port_len;
1124 
1125 	const char *src_addr_start;
1126 	size_t src_addr_len;
1127 
1128 	const char *src_port_start;
1129 	size_t src_port_len;
1130 
1131 	const char *dst_addr_start;
1132 	size_t dst_addr_len;
1133 
1134 	const char *dst_port_start;
1135 	size_t dst_port_len;
1136 } isc_proxy_addrs_parser_state_t;
1137 
1138 static bool
1139 rule_proxy_addrs(isc_proxy_addrs_parser_state_t *st);
1140 
1141 static bool
1142 rule_addr(isc_proxy_addrs_parser_state_t *st);
1143 
1144 static bool
1145 rule_port(isc_proxy_addrs_parser_state_t *st);
1146 
1147 static bool
1148 rule_addr_char(isc_proxy_addrs_parser_state_t *st);
1149 
1150 static void
1151 proxy_handle_port_string(const char *port_start, const size_t port_len,
1152 			 in_port_t *pport) {
1153 	char buf[512] = { 0 }; /* max */
1154 	size_t string_size = 0, max_string_bytes = 0;
1155 	unsigned int tmp;
1156 	isc_result_t result;
1157 
1158 	string_size = port_len + 1;
1159 	max_string_bytes = string_size > sizeof(buf) ? sizeof(buf)
1160 						     : string_size;
1161 
1162 	(void)strlcpy(buf, port_start, max_string_bytes);
1163 	result = parse_uint(&tmp, buf, MAXPORT, "port number");
1164 	if (result != ISC_R_SUCCESS) {
1165 		fatal("Couldn't parse port number");
1166 	}
1167 	*pport = tmp;
1168 }
1169 
1170 static isc_result_t
1171 proxy_handle_addr_string(const char *addr_start, const size_t addr_len,
1172 			 const in_port_t addr_port, isc_sockaddr_t *addr) {
1173 	isc_result_t result = ISC_R_FAILURE;
1174 	char buf[512] = { 0 }; /* max */
1175 	size_t string_size = 0, max_string_bytes = 0;
1176 	struct in_addr ipv4 = { 0 };
1177 	struct in6_addr ipv6 = { 0 };
1178 	int ret = 0;
1179 
1180 	string_size = addr_len + 1;
1181 	max_string_bytes = string_size > sizeof(buf) ? sizeof(buf)
1182 						     : string_size;
1183 
1184 	(void)strlcpy(buf, addr_start, max_string_bytes);
1185 
1186 	ret = inet_pton(AF_INET, buf, &ipv4);
1187 	if (ret == 1) {
1188 		isc_sockaddr_fromin(addr, &ipv4, addr_port);
1189 		result = ISC_R_SUCCESS;
1190 	} else {
1191 		ret = inet_pton(AF_INET6, buf, &ipv6);
1192 		if (ret == 1) {
1193 			isc_sockaddr_fromin6(addr, &ipv6, addr_port);
1194 			result = ISC_R_SUCCESS;
1195 		}
1196 	}
1197 
1198 	return result;
1199 }
1200 
1201 static bool
1202 parse_proxy_addresses(const char *addrs, isc_sockaddr_t *psrc,
1203 		      isc_sockaddr_t *pdst) {
1204 	isc_result_t result = ISC_R_FAILURE;
1205 	isc_sockaddr_t src = { 0 }, dst = { 0 };
1206 	isc_proxy_addrs_parser_state_t st = { 0 };
1207 	in_port_t src_port = 0, dst_port = 53; /* Follow kdig footsteps */
1208 
1209 	REQUIRE(addrs != NULL && *addrs != '\0');
1210 	REQUIRE(psrc != NULL);
1211 	REQUIRE(pdst != NULL);
1212 
1213 	st.str = addrs;
1214 
1215 	/* start syntax analysis and verification */
1216 	if (!rule_proxy_addrs(&st)) {
1217 		warn("PROXY source and destination addresses cannot be parsed");
1218 		return false;
1219 	}
1220 
1221 	/* get port numeric values */
1222 	if (st.src_port_len > 0) {
1223 		INSIST(st.src_port_start != NULL);
1224 		proxy_handle_port_string(st.src_port_start, st.src_port_len,
1225 					 &src_port);
1226 	}
1227 
1228 	if (st.dst_port_len > 0) {
1229 		INSIST(st.dst_port_start != NULL);
1230 		proxy_handle_port_string(st.dst_port_start, st.dst_port_len,
1231 					 &dst_port);
1232 	}
1233 
1234 	/* get addresses */
1235 	INSIST(st.src_addr_len > 0);
1236 	INSIST(st.src_addr_start != NULL);
1237 	INSIST(st.dst_addr_len > 0);
1238 	INSIST(st.dst_addr_start != NULL);
1239 
1240 	result = proxy_handle_addr_string(st.src_addr_start, st.src_addr_len,
1241 					  src_port, &src);
1242 	if (result != ISC_R_SUCCESS) {
1243 		warn("Cannot get PROXY source address: %s",
1244 		     isc_result_totext(result));
1245 		return false;
1246 	}
1247 
1248 	result = proxy_handle_addr_string(st.dst_addr_start, st.dst_addr_len,
1249 					  dst_port, &dst);
1250 	if (result != ISC_R_SUCCESS) {
1251 		warn("Cannot get PROXY destination address: %s",
1252 		     isc_result_totext(result));
1253 		return false;
1254 	}
1255 
1256 	/* addresses should be of the same type */
1257 	if (isc_sockaddr_pf(&src) != isc_sockaddr_pf(&dst)) {
1258 		warn("PROXY source and destination addresses must be of the "
1259 		     "same type");
1260 		return false;
1261 	}
1262 
1263 	*psrc = src;
1264 	*pdst = dst;
1265 
1266 	return true;
1267 }
1268 
1269 static bool
1270 rule_proxy_addrs(isc_proxy_addrs_parser_state_t *st) {
1271 	if (!rule_addr(st)) {
1272 		return false;
1273 	}
1274 
1275 	st->src_addr_start = st->last_addr_start;
1276 	st->src_addr_len = st->last_addr_len;
1277 	st->src_port_start = st->last_port_start;
1278 	st->src_port_len = st->last_port_len;
1279 
1280 	if (!MATCH('-')) {
1281 		return false;
1282 	}
1283 
1284 	ADVANCE();
1285 
1286 	if (!rule_addr(st)) {
1287 		return false;
1288 	}
1289 
1290 	st->dst_addr_start = st->last_addr_start;
1291 	st->dst_addr_len = st->last_addr_len;
1292 	st->dst_port_start = st->last_port_start;
1293 	st->dst_port_len = st->last_port_len;
1294 
1295 	if (!MATCH('\0')) {
1296 		return false;
1297 	}
1298 
1299 	return true;
1300 }
1301 
1302 static bool
1303 rule_addr(isc_proxy_addrs_parser_state_t *st) {
1304 	const char *start = GETP();
1305 	if (!rule_addr_char(st)) {
1306 		return false;
1307 	}
1308 
1309 	while (rule_addr_char(st)) {
1310 		/* skip */
1311 	}
1312 
1313 	st->last_addr_start = start;
1314 	st->last_addr_len = GETP() - start;
1315 
1316 	if (MATCH('#')) {
1317 		ADVANCE();
1318 
1319 		if (!rule_port(st)) {
1320 			return false;
1321 		}
1322 	}
1323 
1324 	return true;
1325 }
1326 
1327 static bool
1328 rule_port(isc_proxy_addrs_parser_state_t *st) {
1329 	const char *start = GETP();
1330 	if (!MATCH_DIGIT()) {
1331 		return false;
1332 	}
1333 
1334 	ADVANCE();
1335 
1336 	while (MATCH_DIGIT()) {
1337 		ADVANCE();
1338 	}
1339 
1340 	st->last_port_start = start;
1341 	st->last_port_len = GETP() - start;
1342 
1343 	return true;
1344 }
1345 
1346 static bool
1347 rule_addr_char(isc_proxy_addrs_parser_state_t *st) {
1348 	if (MATCH('#') || MATCH('-') || MATCH('\0')) {
1349 		return false;
1350 	}
1351 
1352 	ADVANCE();
1353 
1354 	return true;
1355 }
1356 
1357 #undef GETP
1358 #undef ADVANCE
1359 #undef MATCH_DIGIT
1360 #undef MATCH
1361 
1362 static bool
1363 plus_proxy_handle_addresses(const char *value, const bool state,
1364 			    dig_lookup_t *lookup) {
1365 	lookup->proxy_mode = state;
1366 	if (!state) {
1367 		/*
1368 		 * We are not interested in the option value in that
1369 		 * case
1370 		 */
1371 		return true;
1372 	}
1373 
1374 	if (value == NULL || *value == '\0') {
1375 		lookup->proxy_local = true;
1376 		return true;
1377 	}
1378 
1379 	if (!parse_proxy_addresses(value, &lookup->proxy_src_addr,
1380 				   &lookup->proxy_dst_addr))
1381 	{
1382 		return false;
1383 	}
1384 	return true;
1385 }
1386 
1387 static bool
1388 plus_proxy_options(const char *cmd, const char *value, const bool state,
1389 		   dig_lookup_t *lookup) {
1390 	switch (cmd[5]) {
1391 	case '-':
1392 		FULLCHECK("proxy-plain");
1393 		lookup->proxy_plain = state;
1394 		if (!plus_proxy_handle_addresses(value, state, lookup)) {
1395 			goto invalid_option;
1396 		}
1397 		break;
1398 	case '\0':
1399 		FULLCHECK("proxy");
1400 		if (!plus_proxy_handle_addresses(value, state, lookup)) {
1401 			goto invalid_option;
1402 		}
1403 		break;
1404 	default:
1405 		goto invalid_option;
1406 	}
1407 	return true;
1408 
1409 invalid_option:
1410 	return false;
1411 }
1412 
1413 static bool
1414 plus_tls_options(const char *cmd, const char *value, const bool state,
1415 		 dig_lookup_t *lookup) {
1416 	/*
1417 	 * Using TLS implies "TCP-like" mode.
1418 	 */
1419 	if (!lookup->tcp_mode_set) {
1420 		lookup->tcp_mode = state;
1421 	}
1422 	switch (cmd[3]) {
1423 	case '-':
1424 		/*
1425 		 * Assume that if any of the +tls-* options are set, then we
1426 		 * need to verify the remote certificate (compatibility with
1427 		 * kdig).
1428 		 */
1429 		if (state) {
1430 			lookup->tls_ca_set = state;
1431 		}
1432 		switch (cmd[4]) {
1433 		case 'c':
1434 			switch (cmd[5]) {
1435 			case 'a':
1436 				FULLCHECK("tls-ca");
1437 				lookup->tls_ca_set = state;
1438 				if (state && value != NULL) {
1439 					lookup->tls_ca_file =
1440 						isc_mem_strdup(mctx, value);
1441 				}
1442 				break;
1443 			case 'e':
1444 				FULLCHECK("tls-certfile");
1445 				lookup->tls_cert_file_set = state;
1446 				if (state) {
1447 					if (value != NULL && *value != '\0') {
1448 						lookup->tls_cert_file =
1449 							isc_mem_strdup(mctx,
1450 								       value);
1451 					} else {
1452 						fprintf(stderr,
1453 							";; TLS certificate "
1454 							"file is "
1455 							"not specified\n");
1456 						goto invalid_option;
1457 					}
1458 				}
1459 				break;
1460 			default:
1461 				goto invalid_option;
1462 			}
1463 			break;
1464 		case 'h':
1465 			FULLCHECK("tls-hostname");
1466 			lookup->tls_hostname_set = state;
1467 			if (state) {
1468 				if (value != NULL && *value != '\0') {
1469 					lookup->tls_hostname =
1470 						isc_mem_strdup(mctx, value);
1471 				} else {
1472 					fprintf(stderr, ";; TLS hostname is "
1473 							"not specified\n");
1474 					goto invalid_option;
1475 				}
1476 			}
1477 			break;
1478 		case 'k':
1479 			FULLCHECK("tls-keyfile");
1480 			lookup->tls_key_file_set = state;
1481 			if (state) {
1482 				if (value != NULL && *value != '\0') {
1483 					lookup->tls_key_file =
1484 						isc_mem_strdup(mctx, value);
1485 				} else {
1486 					fprintf(stderr,
1487 						";; TLS private key file is "
1488 						"not specified\n");
1489 					goto invalid_option;
1490 				}
1491 			}
1492 			break;
1493 		default:
1494 			goto invalid_option;
1495 		}
1496 		break;
1497 	case '\0':
1498 		FULLCHECK("tls");
1499 		lookup->tls_mode = state;
1500 		break;
1501 	default:
1502 		goto invalid_option;
1503 	}
1504 
1505 	return true;
1506 invalid_option:
1507 	return false;
1508 }
1509 
1510 /*%
1511  * We're not using isc_commandline_parse() here since the command line
1512  * syntax of dig is quite a bit different from that which can be described
1513  * by that routine.
1514  * XXX doc options
1515  */
1516 
1517 static dig_lookup_t *
1518 plus_option(char *option, bool is_batchfile, bool *need_clone,
1519 	    dig_lookup_t *lookup) {
1520 	isc_result_t result;
1521 	char *cmd, *value, *last = NULL, *code, *extra;
1522 	uint32_t num;
1523 	bool state = true;
1524 	size_t n;
1525 
1526 	INSIST(option != NULL);
1527 
1528 	if ((cmd = strtok_r(option, "=", &last)) == NULL) {
1529 		printf(";; Invalid option %s\n", option);
1530 		return lookup;
1531 	}
1532 	if (strncasecmp(cmd, "no", 2) == 0) {
1533 		cmd += 2;
1534 		state = false;
1535 	}
1536 	/* parse the rest of the string */
1537 	value = strtok_r(NULL, "", &last);
1538 
1539 	switch (cmd[0]) {
1540 	case 'a':
1541 		switch (cmd[1]) {
1542 		case 'a': /* aaonly / aaflag */
1543 			FULLCHECK2("aaonly", "aaflag");
1544 			lookup->aaonly = state;
1545 			break;
1546 		case 'd':
1547 			switch (cmd[2]) {
1548 			case 'd': /* additional */
1549 				FULLCHECK("additional");
1550 				lookup->section_additional = state;
1551 				break;
1552 			case 'f':  /* adflag */
1553 			case '\0': /* +ad is a synonym for +adflag */
1554 				FULLCHECK("adflag");
1555 				lookup->adflag = state;
1556 				break;
1557 			default:
1558 				goto invalid_option;
1559 			}
1560 			break;
1561 		case 'l': /* all */
1562 			FULLCHECK("all");
1563 			lookup->section_question = state;
1564 			lookup->section_authority = state;
1565 			lookup->section_answer = state;
1566 			lookup->section_additional = state;
1567 			lookup->comments = state;
1568 			lookup->stats = state;
1569 			printcmd = state;
1570 			break;
1571 		case 'n': /* answer */
1572 			FULLCHECK("answer");
1573 			lookup->section_answer = state;
1574 			break;
1575 		case 'u': /* authority */
1576 			FULLCHECK("authority");
1577 			lookup->section_authority = state;
1578 			break;
1579 		default:
1580 			goto invalid_option;
1581 		}
1582 		break;
1583 	case 'b':
1584 		switch (cmd[1]) {
1585 		case 'a': /* badcookie */
1586 			FULLCHECK("badcookie");
1587 			lookup->badcookie = state;
1588 			break;
1589 		case 'e': /* besteffort */
1590 			FULLCHECK("besteffort");
1591 			lookup->besteffort = state;
1592 			break;
1593 		case 'u': /* bufsize */
1594 			FULLCHECK("bufsize");
1595 			if (!state) {
1596 				goto invalid_option;
1597 			}
1598 			if (value == NULL) {
1599 				lookup->udpsize = DEFAULT_EDNS_BUFSIZE;
1600 				break;
1601 			}
1602 			result = parse_uint(&num, value, COMMSIZE,
1603 					    "buffer size");
1604 			if (result != ISC_R_SUCCESS) {
1605 				warn("Couldn't parse buffer size");
1606 				goto exit_or_usage;
1607 			}
1608 			lookup->udpsize = num;
1609 			break;
1610 		default:
1611 			goto invalid_option;
1612 		}
1613 		break;
1614 	case 'c':
1615 		switch (cmd[1]) {
1616 		case 'd': /* cdflag */
1617 			switch (cmd[2]) {
1618 			case 'f':  /* cdflag */
1619 			case '\0': /* +cd is a synonym for +cdflag */
1620 				FULLCHECK("cdflag");
1621 				lookup->cdflag = state;
1622 				break;
1623 			default:
1624 				goto invalid_option;
1625 			}
1626 			break;
1627 		case 'l': /* class */
1628 			/* keep +cl for backwards compatibility */
1629 			FULLCHECK2("cl", "class");
1630 			lookup->noclass = !state;
1631 			break;
1632 		case 'm': /* cmd */
1633 			FULLCHECK("cmd");
1634 			printcmd = state;
1635 			break;
1636 		case 'o': /* comments */
1637 			switch (cmd[2]) {
1638 			case 'm':
1639 				FULLCHECK("comments");
1640 				lookup->comments = state;
1641 				if (lookup == default_lookup) {
1642 					pluscomm = state;
1643 				}
1644 				break;
1645 			case 'o': /* cookie */
1646 				FULLCHECK("cookie");
1647 				if (state && lookup->edns == -1) {
1648 					lookup->edns = DEFAULT_EDNS_VERSION;
1649 				}
1650 				lookup->sendcookie = state;
1651 				if (value != NULL) {
1652 					n = strlcpy(hexcookie, value,
1653 						    sizeof(hexcookie));
1654 					if (n >= sizeof(hexcookie)) {
1655 						warn("COOKIE data too large");
1656 						goto exit_or_usage;
1657 					}
1658 					lookup->cookie = hexcookie;
1659 				} else {
1660 					lookup->cookie = NULL;
1661 				}
1662 				break;
1663 			default:
1664 				goto invalid_option;
1665 			}
1666 			break;
1667 		case 'r':
1668 			FULLCHECK("crypto");
1669 			lookup->nocrypto = !state;
1670 			break;
1671 		default:
1672 			goto invalid_option;
1673 		}
1674 		break;
1675 	case 'd':
1676 		switch (cmd[1]) {
1677 		case 'e': /* defname */
1678 			FULLCHECK("defname");
1679 			fprintf(stderr, ";; +[no]defname option is "
1680 					"deprecated; use +[no]search\n");
1681 			if (!lookup->trace) {
1682 				usesearch = state;
1683 			}
1684 			break;
1685 		case 'n':
1686 			switch (cmd[2]) {
1687 			case 's':
1688 				switch (cmd[3]) {
1689 				case '6': /* dns64prefix */
1690 					FULLCHECK("dns64prefix");
1691 					if (state) {
1692 						if (*need_clone) {
1693 							lookup = clone_lookup(
1694 								default_lookup,
1695 								true);
1696 						}
1697 						*need_clone = true;
1698 						lookup->dns64prefix = state;
1699 						strlcpy(lookup->textname,
1700 							"ipv4only.arpa",
1701 							sizeof(lookup->textname));
1702 						printcmd = false;
1703 						lookup->section_additional =
1704 							false;
1705 						lookup->section_answer = true;
1706 						lookup->section_authority =
1707 							false;
1708 						lookup->section_question =
1709 							false;
1710 						lookup->comments = false;
1711 						lookup->stats = false;
1712 						lookup->rrcomments = -1;
1713 						lookup->rdtype =
1714 							dns_rdatatype_aaaa;
1715 						lookup->rdtypeset = true;
1716 						ISC_LIST_APPEND(lookup_list,
1717 								lookup, link);
1718 					}
1719 					break;
1720 				case 's': /* dnssec */
1721 					FULLCHECK("dnssec");
1722 				dnssec:
1723 					if (state && lookup->edns == -1) {
1724 						lookup->edns =
1725 							DEFAULT_EDNS_VERSION;
1726 					}
1727 					lookup->dnssec = state;
1728 					break;
1729 				default:
1730 					goto invalid_option;
1731 				}
1732 				break;
1733 			default:
1734 				goto invalid_option;
1735 			}
1736 			break;
1737 		case 'o': /* domain ... but treat "do" as synonym for dnssec */
1738 			if (cmd[2] == '\0') {
1739 				goto dnssec;
1740 			}
1741 			FULLCHECK("domain");
1742 			if (value == NULL) {
1743 				goto need_value;
1744 			}
1745 			if (!state) {
1746 				goto invalid_option;
1747 			}
1748 			strlcpy(domainopt, value, sizeof(domainopt));
1749 			break;
1750 		default:
1751 			goto invalid_option;
1752 		}
1753 		break;
1754 	case 'e':
1755 		switch (cmd[1]) {
1756 		case 'd':
1757 			switch (cmd[2]) {
1758 			case 'n':
1759 				switch (cmd[3]) {
1760 				case 's':
1761 					switch (cmd[4]) {
1762 					case 0:
1763 						FULLCHECK("edns");
1764 						if (!state) {
1765 							lookup->edns = -1;
1766 							break;
1767 						}
1768 						if (value == NULL) {
1769 							lookup->edns =
1770 								DEFAULT_EDNS_VERSION;
1771 							break;
1772 						}
1773 						result = parse_uint(&num, value,
1774 								    255,
1775 								    "edns");
1776 						if (result != ISC_R_SUCCESS) {
1777 							warn("Couldn't parse "
1778 							     "edns");
1779 							goto exit_or_usage;
1780 						}
1781 						lookup->edns = num;
1782 						break;
1783 					case 'f':
1784 						FULLCHECK("ednsflags");
1785 						if (!state) {
1786 							lookup->ednsflags = 0;
1787 							break;
1788 						}
1789 						if (value == NULL) {
1790 							lookup->ednsflags = 0;
1791 							break;
1792 						}
1793 						result = parse_xint(
1794 							&num, value, 0xffff,
1795 							"ednsflags");
1796 						if (result != ISC_R_SUCCESS) {
1797 							warn("Couldn't parse "
1798 							     "ednsflags");
1799 							goto exit_or_usage;
1800 						}
1801 						if (lookup->edns == -1) {
1802 							lookup->edns =
1803 								DEFAULT_EDNS_VERSION;
1804 						}
1805 						lookup->ednsflags = num;
1806 						break;
1807 					case 'n':
1808 						FULLCHECK("ednsnegotiation");
1809 						lookup->ednsneg = state;
1810 						break;
1811 					case 'o':
1812 						FULLCHECK("ednsopt");
1813 						if (!state) {
1814 							lookup->ednsoptscnt = 0;
1815 							break;
1816 						}
1817 						code = NULL;
1818 						if (value != NULL) {
1819 							code = strtok_r(value,
1820 									":",
1821 									&last);
1822 						}
1823 						if (code == NULL) {
1824 							warn("ednsopt no "
1825 							     "code point "
1826 							     "specified");
1827 							goto exit_or_usage;
1828 						}
1829 						extra = strtok_r(NULL, "",
1830 								 &last);
1831 						save_opt(lookup, code, extra);
1832 						if (extra != NULL) {
1833 							extra[-1] = ':';
1834 						}
1835 						break;
1836 					default:
1837 						goto invalid_option;
1838 					}
1839 					break;
1840 				default:
1841 					goto invalid_option;
1842 				}
1843 				break;
1844 			default:
1845 				goto invalid_option;
1846 			}
1847 			break;
1848 		case 'x':
1849 			switch (cmd[2]) {
1850 			case 'p':
1851 				switch (cmd[3]) {
1852 				case 'a':
1853 					FULLCHECK("expandaaaa");
1854 					lookup->expandaaaa = state;
1855 					break;
1856 				case 'i':
1857 					FULLCHECK("expire");
1858 					lookup->expire = state;
1859 					break;
1860 				default:
1861 					goto invalid_option;
1862 				}
1863 				break;
1864 			default:
1865 				goto invalid_option;
1866 			}
1867 			break;
1868 		default:
1869 			goto invalid_option;
1870 		}
1871 		break;
1872 	case 'f': /* fail */
1873 		switch (cmd[1]) {
1874 		case 'a':
1875 			FULLCHECK("fail");
1876 			lookup->servfail_stops = state;
1877 			break;
1878 		case 'u':
1879 			FULLCHECK("fuzztime");
1880 			lookup->fuzzing = state;
1881 			if (lookup->fuzzing) {
1882 				if (value == NULL) {
1883 					lookup->fuzztime = 0x622acce1;
1884 					break;
1885 				}
1886 				result = parse_uint(&num, value, 0xffffffff,
1887 						    "fuzztime");
1888 				if (result != ISC_R_SUCCESS) {
1889 					warn("Couldn't parse fuzztime");
1890 					goto exit_or_usage;
1891 				}
1892 				lookup->fuzztime = num;
1893 			}
1894 			break;
1895 		default:
1896 			goto invalid_option;
1897 		}
1898 		break;
1899 	case 'h':
1900 		switch (cmd[1]) {
1901 		case 'e': /* header-only */
1902 			FULLCHECK("header-only");
1903 			lookup->header_only = state;
1904 			break;
1905 		case 't':
1906 			FULLCHECK6("https", "https-get", "https-post",
1907 				   "http-plain", "http-plain-get",
1908 				   "http-plain-post");
1909 #if HAVE_LIBNGHTTP2
1910 			if (lookup->https_path != NULL) {
1911 				isc_mem_free(mctx, lookup->https_path);
1912 				lookup->https_path = NULL;
1913 			}
1914 			if (!state) {
1915 				lookup->https_mode = false;
1916 				break;
1917 			}
1918 			lookup->https_mode = true;
1919 			if (cmd[4] == '-') {
1920 				lookup->http_plain = true;
1921 				switch (cmd[10]) {
1922 				case '\0':
1923 					FULLCHECK("http-plain");
1924 					break;
1925 				case '-':
1926 					switch (cmd[11]) {
1927 					case 'p':
1928 						FULLCHECK("http-plain-post");
1929 						break;
1930 					case 'g':
1931 						FULLCHECK("http-plain-get");
1932 						lookup->https_get = true;
1933 						break;
1934 					}
1935 					break;
1936 				default:
1937 					goto invalid_option;
1938 				}
1939 			} else {
1940 				switch (cmd[5]) {
1941 				case '\0':
1942 					FULLCHECK("https");
1943 					break;
1944 				case '-':
1945 					switch (cmd[6]) {
1946 					case 'p':
1947 						FULLCHECK("https-post");
1948 						break;
1949 					case 'g':
1950 						FULLCHECK("https-get");
1951 						lookup->https_get = true;
1952 						break;
1953 					}
1954 					break;
1955 				default:
1956 					goto invalid_option;
1957 				}
1958 			}
1959 			if (!lookup->tcp_mode_set) {
1960 				lookup->tcp_mode = state;
1961 			}
1962 			if (value == NULL) {
1963 				lookup->https_path = isc_mem_strdup(
1964 					mctx, ISC_NM_HTTP_DEFAULT_PATH);
1965 			} else {
1966 				if (!isc_nm_http_path_isvalid(value)) {
1967 					fprintf(stderr,
1968 						";; The given HTTP path \"%s\" "
1969 						"is not "
1970 						"a valid absolute path\n",
1971 						value);
1972 					goto invalid_option;
1973 				}
1974 				lookup->https_path = isc_mem_strdup(mctx,
1975 								    value);
1976 			}
1977 #else
1978 			fprintf(stderr, ";; DoH support not enabled\n");
1979 #endif
1980 			break;
1981 		default:
1982 			goto invalid_option;
1983 		}
1984 		break;
1985 	case 'i':
1986 		switch (cmd[1]) {
1987 		case 'd':
1988 			switch (cmd[2]) {
1989 			case 'e':
1990 				FULLCHECK("identify");
1991 				lookup->identify = state;
1992 				break;
1993 			case 'n':
1994 				switch (cmd[3]) {
1995 				case '\0':
1996 					FULLCHECK("idn");
1997 					lookup->idnin = state;
1998 					lookup->idnout = state;
1999 					break;
2000 				case 'i': /* (compat) */
2001 					FULLCHECK("idnin");
2002 					lookup->idnin = state;
2003 					break;
2004 				case 'o': /* (compat) */
2005 					FULLCHECK("idnout");
2006 					lookup->idnout = state;
2007 					break;
2008 				default:
2009 					goto invalid_option;
2010 				}
2011 #ifndef HAVE_LIBIDN2
2012 				if (state) {
2013 					printf(";; IDN support "
2014 					       "is not available\n");
2015 				}
2016 #endif /* ifndef HAVE_LIBIDN2 */
2017 				break;
2018 			default:
2019 				goto invalid_option;
2020 			}
2021 			break;
2022 		case 'g': /* ignore */
2023 		default:  /*
2024 			   * Inherits default for compatibility (+[no]i*).
2025 			   */
2026 			FULLCHECK("ignore");
2027 			lookup->ignore = state;
2028 		}
2029 		break;
2030 	case 'k':
2031 		switch (cmd[1]) {
2032 		case 'e':
2033 			switch (cmd[2]) {
2034 			case 'e':
2035 				switch (cmd[3]) {
2036 				case 'p':
2037 					switch (cmd[4]) {
2038 					case 'a':
2039 						FULLCHECK("keepalive");
2040 						lookup->tcp_keepalive = state;
2041 						break;
2042 					case 'o':
2043 						FULLCHECK("keepopen");
2044 						keep_open = state;
2045 						break;
2046 					default:
2047 						goto invalid_option;
2048 					}
2049 					break;
2050 				default:
2051 					goto invalid_option;
2052 				}
2053 				break;
2054 			default:
2055 				goto invalid_option;
2056 			}
2057 			break;
2058 		default:
2059 			goto invalid_option;
2060 		}
2061 		break;
2062 	case 'm':
2063 		switch (cmd[1]) {
2064 		case 'a':
2065 			FULLCHECK("mapped");
2066 			fatal("+mapped option no longer supported");
2067 		case 'u':
2068 			FULLCHECK("multiline");
2069 			lookup->multiline = state;
2070 			break;
2071 		default:
2072 			goto invalid_option;
2073 		}
2074 		break;
2075 	case 'n':
2076 		switch (cmd[1]) {
2077 		case 'd': /* ndots */
2078 			FULLCHECK("ndots");
2079 			if (value == NULL) {
2080 				goto need_value;
2081 			}
2082 			if (!state) {
2083 				goto invalid_option;
2084 			}
2085 			result = parse_uint(&num, value, MAXNDOTS, "ndots");
2086 			if (result != ISC_R_SUCCESS) {
2087 				warn("Couldn't parse ndots");
2088 				goto exit_or_usage;
2089 			}
2090 			ndots = num;
2091 			break;
2092 		case 's':
2093 			switch (cmd[2]) {
2094 			case 'i': /* nsid */
2095 				FULLCHECK("nsid");
2096 				if (state && lookup->edns == -1) {
2097 					lookup->edns = DEFAULT_EDNS_VERSION;
2098 				}
2099 				lookup->nsid = state;
2100 				break;
2101 			case 's': /* nssearch */
2102 				FULLCHECK("nssearch");
2103 				lookup->ns_search_only = state;
2104 				if (state) {
2105 					lookup->trace_root = true;
2106 					lookup->recurse = true;
2107 					lookup->identify = true;
2108 					lookup->stats = false;
2109 					lookup->comments = false;
2110 					lookup->section_additional = false;
2111 					lookup->section_authority = false;
2112 					lookup->section_question = false;
2113 					lookup->rdtype = dns_rdatatype_ns;
2114 					lookup->rdtypeset = true;
2115 					short_form = true;
2116 					lookup->rrcomments = 0;
2117 				}
2118 				break;
2119 			default:
2120 				goto invalid_option;
2121 			}
2122 			break;
2123 		default:
2124 			goto invalid_option;
2125 		}
2126 		break;
2127 	case 'o':
2128 		switch (cmd[1]) {
2129 		case 'n':
2130 			FULLCHECK("onesoa");
2131 			lookup->onesoa = state;
2132 			break;
2133 		case 'p':
2134 			FULLCHECK("opcode");
2135 			if (!state) {
2136 				lookup->opcode = 0; /* default - query */
2137 				break;
2138 			}
2139 			if (value == NULL) {
2140 				goto need_value;
2141 			}
2142 			for (num = 0;
2143 			     num < sizeof(opcodetext) / sizeof(opcodetext[0]);
2144 			     num++)
2145 			{
2146 				if (strcasecmp(opcodetext[num], value) == 0) {
2147 					break;
2148 				}
2149 			}
2150 			if (num < 16) {
2151 				lookup->opcode = (dns_opcode_t)num;
2152 				break;
2153 			}
2154 			result = parse_uint(&num, value, 15, "opcode");
2155 			if (result != ISC_R_SUCCESS) {
2156 				warn("Couldn't parse opcode");
2157 				goto exit_or_usage;
2158 			}
2159 			lookup->opcode = (dns_opcode_t)num;
2160 			break;
2161 		default:
2162 			goto invalid_option;
2163 		}
2164 		break;
2165 	case 'p':
2166 		switch (cmd[1]) {
2167 		case 'a':
2168 			FULLCHECK("padding");
2169 			if (state && lookup->edns == -1) {
2170 				lookup->edns = DEFAULT_EDNS_VERSION;
2171 			}
2172 			if (value == NULL) {
2173 				goto need_value;
2174 			}
2175 			result = parse_uint(&num, value, 512, "padding");
2176 			if (result != ISC_R_SUCCESS) {
2177 				warn("Couldn't parse padding");
2178 				goto exit_or_usage;
2179 			}
2180 			lookup->padding = (uint16_t)num;
2181 			break;
2182 		case 'r':
2183 			if (!plus_proxy_options(cmd, value, state, lookup)) {
2184 				goto invalid_option;
2185 			}
2186 			break;
2187 		default:
2188 			goto invalid_option;
2189 		}
2190 		break;
2191 	case 'q':
2192 		switch (cmd[1]) {
2193 		case 'i': /* qid */
2194 			FULLCHECK("qid");
2195 			if (!state) {
2196 				lookup->setqid = false;
2197 				lookup->qid = 0;
2198 				break;
2199 			}
2200 			if (value == NULL) {
2201 				goto need_value;
2202 			}
2203 			result = parse_uint(&num, value, MAXQID, "qid");
2204 			if (result != ISC_R_SUCCESS) {
2205 				warn("Couldn't parse qid");
2206 				goto exit_or_usage;
2207 			}
2208 			lookup->setqid = true;
2209 			lookup->qid = num;
2210 			break;
2211 		case 'r': /* qr */
2212 			FULLCHECK("qr");
2213 			lookup->qr = state;
2214 			break;
2215 		case 'u': /* question */
2216 			FULLCHECK("question");
2217 			lookup->section_question = state;
2218 			if (lookup == default_lookup) {
2219 				plusquest = state;
2220 			}
2221 			break;
2222 		default:
2223 			goto invalid_option;
2224 		}
2225 		break;
2226 	case 'r':
2227 		switch (cmd[1]) {
2228 		case 'a': /* raflag */
2229 			FULLCHECK("raflag");
2230 			lookup->raflag = state;
2231 			break;
2232 		case 'd': /* rdflag */
2233 			FULLCHECK("rdflag");
2234 			lookup->recurse = state;
2235 			break;
2236 		case 'e':
2237 			switch (cmd[2]) {
2238 			case 'c': /* recurse */
2239 				FULLCHECK("recurse");
2240 				lookup->recurse = state;
2241 				break;
2242 			case 't': /* retry / retries */
2243 				FULLCHECK2("retry", "retries");
2244 				if (value == NULL) {
2245 					goto need_value;
2246 				}
2247 				if (!state) {
2248 					goto invalid_option;
2249 				}
2250 				result = parse_uint(&lookup->retries, value,
2251 						    MAXTRIES - 1, "retries");
2252 				if (result != ISC_R_SUCCESS) {
2253 					warn("Couldn't parse retries");
2254 					goto exit_or_usage;
2255 				}
2256 				lookup->retries++;
2257 				break;
2258 			default:
2259 				goto invalid_option;
2260 			}
2261 			break;
2262 		case 'r': /* rrcomments */
2263 			FULLCHECK("rrcomments");
2264 			lookup->rrcomments = state ? 1 : -1;
2265 			break;
2266 		default:
2267 			goto invalid_option;
2268 		}
2269 		break;
2270 	case 's':
2271 		switch (cmd[1]) {
2272 		case 'e': /* search */
2273 			FULLCHECK("search");
2274 			if (!lookup->trace) {
2275 				usesearch = state;
2276 			}
2277 			break;
2278 		case 'h':
2279 			if (cmd[2] != 'o') {
2280 				goto invalid_option;
2281 			}
2282 			switch (cmd[3]) {
2283 			case 'r': /* short */
2284 				FULLCHECK("short");
2285 				short_form = state;
2286 				if (state) {
2287 					printcmd = false;
2288 					lookup->section_additional = false;
2289 					lookup->section_answer = true;
2290 					lookup->section_authority = false;
2291 					lookup->section_question = false;
2292 					lookup->comments = false;
2293 					lookup->stats = false;
2294 					lookup->rrcomments = -1;
2295 				}
2296 				break;
2297 			case 'w': /* showsearch */
2298 				switch (cmd[4]) {
2299 				case 'b':
2300 					FULLCHECK("showbadcookie");
2301 					lookup->showbadcookie = state;
2302 					break;
2303 				case 's':
2304 					FULLCHECK("showsearch");
2305 					if (!lookup->trace) {
2306 						showsearch = state;
2307 						usesearch = state;
2308 					}
2309 					break;
2310 				default:
2311 					goto invalid_option;
2312 				}
2313 				break;
2314 			default:
2315 				goto invalid_option;
2316 			}
2317 			break;
2318 		case 'i': /* sigchase */
2319 			FULLCHECK("sigchase");
2320 			fatal("+sigchase option no longer supported");
2321 		case 'p': /* split */
2322 			FULLCHECK("split");
2323 			if (value != NULL && !state) {
2324 				goto invalid_option;
2325 			}
2326 			if (!state) {
2327 				splitwidth = 0;
2328 				break;
2329 			} else if (value == NULL) {
2330 				break;
2331 			}
2332 
2333 			result = parse_uint(&splitwidth, value, 1023, "split");
2334 			if ((splitwidth % 4) != 0U) {
2335 				splitwidth = ((splitwidth + 3) / 4) * 4;
2336 				fprintf(stderr,
2337 					";; Warning, split must be "
2338 					"a multiple of 4; adjusting "
2339 					"to %u\n",
2340 					splitwidth);
2341 			}
2342 			/*
2343 			 * There is an adjustment done in the
2344 			 * totext_<rrtype>() functions which causes
2345 			 * splitwidth to shrink.  This is okay when we're
2346 			 * using the default width but incorrect in this
2347 			 * case, so we correct for it
2348 			 */
2349 			if (splitwidth) {
2350 				splitwidth += 3;
2351 			}
2352 			if (result != ISC_R_SUCCESS) {
2353 				warn("Couldn't parse split");
2354 				goto exit_or_usage;
2355 			}
2356 			break;
2357 		case 't': /* stats */
2358 			FULLCHECK("stats");
2359 			lookup->stats = state;
2360 			break;
2361 		case 'u': /* subnet */
2362 			FULLCHECK("subnet");
2363 			if (state && value == NULL) {
2364 				goto need_value;
2365 			}
2366 			if (!state) {
2367 				if (lookup->ecs_addr != NULL) {
2368 					isc_mem_put(mctx, lookup->ecs_addr,
2369 						    sizeof(*lookup->ecs_addr));
2370 					lookup->ecs_addr = NULL;
2371 				}
2372 				break;
2373 			}
2374 			if (lookup->edns == -1) {
2375 				lookup->edns = DEFAULT_EDNS_VERSION;
2376 			}
2377 			if (lookup->ecs_addr != NULL) {
2378 				isc_mem_put(mctx, lookup->ecs_addr,
2379 					    sizeof(*lookup->ecs_addr));
2380 				lookup->ecs_addr = NULL;
2381 			}
2382 			result = parse_netprefix(&lookup->ecs_addr, value);
2383 			if (result != ISC_R_SUCCESS) {
2384 				warn("Couldn't parse client");
2385 				goto exit_or_usage;
2386 			}
2387 			break;
2388 		default:
2389 			goto invalid_option;
2390 		}
2391 		break;
2392 	case 't':
2393 		switch (cmd[1]) {
2394 		case 'c': /* tcp */
2395 			switch (cmd[2]) {
2396 			case 'f':
2397 				FULLCHECK("tcflag");
2398 				lookup->tcflag = state;
2399 				break;
2400 			case 'p':
2401 				FULLCHECK("tcp");
2402 				if (!is_batchfile) {
2403 					lookup->tcp_mode = state;
2404 					lookup->tcp_mode_set = true;
2405 				}
2406 				break;
2407 			default:
2408 				goto invalid_option;
2409 			}
2410 			break;
2411 		case 'i': /* timeout */
2412 			FULLCHECK("timeout");
2413 			if (value == NULL) {
2414 				goto need_value;
2415 			}
2416 			if (!state) {
2417 				goto invalid_option;
2418 			}
2419 			result = parse_uint(&timeout, value, MAXTIMEOUT,
2420 					    "timeout");
2421 			if (result != ISC_R_SUCCESS) {
2422 				warn("Couldn't parse timeout");
2423 				goto exit_or_usage;
2424 			}
2425 			if (timeout == 0) {
2426 				timeout = 1;
2427 			}
2428 			break;
2429 		case 'l':
2430 			switch (cmd[2]) {
2431 			case 's':
2432 				if (!plus_tls_options(cmd, value, state,
2433 						      lookup))
2434 				{
2435 					goto invalid_option;
2436 				}
2437 				break;
2438 			default:
2439 				goto invalid_option;
2440 			}
2441 			break;
2442 		case 'o':
2443 			FULLCHECK("topdown");
2444 			fatal("+topdown option no longer supported");
2445 		case 'r':
2446 			switch (cmd[2]) {
2447 			case 'a': /* trace */
2448 				FULLCHECK("trace");
2449 				lookup->trace = state;
2450 				lookup->trace_root = state;
2451 				if (state) {
2452 					lookup->recurse = true;
2453 					lookup->identify = true;
2454 					lookup->comments = false;
2455 					lookup->rrcomments = 0;
2456 					lookup->stats = false;
2457 					lookup->section_additional = false;
2458 					lookup->section_authority = true;
2459 					lookup->section_question = false;
2460 					lookup->dnssec = true;
2461 					lookup->sendcookie = true;
2462 					usesearch = false;
2463 				}
2464 				break;
2465 			case 'i': /* tries */
2466 				FULLCHECK("tries");
2467 				if (value == NULL) {
2468 					goto need_value;
2469 				}
2470 				if (!state) {
2471 					goto invalid_option;
2472 				}
2473 				result = parse_uint(&lookup->retries, value,
2474 						    MAXTRIES, "tries");
2475 				if (result != ISC_R_SUCCESS) {
2476 					warn("Couldn't parse tries");
2477 					goto exit_or_usage;
2478 				}
2479 				if (lookup->retries == 0) {
2480 					lookup->retries = 1;
2481 				}
2482 				break;
2483 			case 'u': /* trusted-key */
2484 				FULLCHECK("trusted-key");
2485 				fatal("+trusted-key option "
2486 				      "no longer supported");
2487 			default:
2488 				goto invalid_option;
2489 			}
2490 			break;
2491 		case 't':
2492 			switch (cmd[2]) {
2493 			case 'l':
2494 				switch (cmd[3]) {
2495 				case 0:
2496 				case 'i': /* ttlid */
2497 					FULLCHECK2("ttl", "ttlid");
2498 					lookup->nottl = !state;
2499 					break;
2500 				case 'u': /* ttlunits */
2501 					FULLCHECK("ttlunits");
2502 					lookup->nottl = false;
2503 					lookup->ttlunits = state;
2504 					break;
2505 				default:
2506 					goto invalid_option;
2507 				}
2508 				break;
2509 			default:
2510 				goto invalid_option;
2511 			}
2512 			break;
2513 		default:
2514 			goto invalid_option;
2515 		}
2516 		break;
2517 	case 'u':
2518 		switch (cmd[1]) {
2519 		case 'n':
2520 			switch (cmd[2]) {
2521 			case 'e':
2522 				FULLCHECK("unexpected");
2523 				fatal("+unexpected option "
2524 				      "no longer supported");
2525 			case 'k':
2526 				FULLCHECK("unknownformat");
2527 				lookup->print_unknown_format = state;
2528 				break;
2529 			default:
2530 				goto invalid_option;
2531 			}
2532 		}
2533 		break;
2534 	case 'v':
2535 		FULLCHECK("vc");
2536 		if (!is_batchfile) {
2537 			lookup->tcp_mode = state;
2538 			lookup->tcp_mode_set = true;
2539 		}
2540 		break;
2541 	case 'y': /* yaml */
2542 		FULLCHECK("yaml");
2543 		yaml = state;
2544 		if (state) {
2545 			printcmd = false;
2546 			lookup->stats = false;
2547 			lookup->rrcomments = -1;
2548 		}
2549 		break;
2550 	case 'z': /* zflag */
2551 		FULLCHECK("zflag");
2552 		lookup->zflag = state;
2553 		break;
2554 	default:
2555 	invalid_option:
2556 	need_value:
2557 #if TARGET_OS_IPHONE
2558 	exit_or_usage:
2559 #endif /* if TARGET_OS_IPHONE */
2560 		fprintf(stderr, "Invalid option: +%s\n", option);
2561 		usage();
2562 	}
2563 	if (value != NULL) {
2564 		value[-1] = '=';
2565 	}
2566 	return lookup;
2567 
2568 #if !TARGET_OS_IPHONE
2569 exit_or_usage:
2570 	cleanup_openssl_refs();
2571 	digexit();
2572 #endif /* if !TARGET_OS_IPHONE */
2573 }
2574 
2575 /*%
2576  * #true returned if value was used
2577  */
2578 static const char *single_dash_opts = "46dhimnruv";
2579 static const char *dash_opts = "46bcdfhikmnpqrtvyx";
2580 static bool
2581 dash_option(char *option, char *next, dig_lookup_t **lookup,
2582 	    bool *open_type_class, bool *need_clone, bool config_only, int argc,
2583 	    char **argv, bool *firstarg) {
2584 	char opt, *value, *ptr, *ptr2, *ptr3, *last;
2585 	isc_result_t result;
2586 	bool value_from_next;
2587 	isc_textregion_t tr;
2588 	dns_rdatatype_t rdtype;
2589 	dns_rdataclass_t rdclass;
2590 	char textname[MXNAME];
2591 	struct in_addr in4;
2592 	struct in6_addr in6;
2593 	in_port_t srcport;
2594 	char *hash, *cmd;
2595 	uint32_t num;
2596 
2597 	while (strpbrk(option, single_dash_opts) == &option[0]) {
2598 		/*
2599 		 * Since the -[46dhimnuv] options do not take an argument,
2600 		 * account for them (in any number and/or combination)
2601 		 * if they appear as the first character(s) of a q-opt.
2602 		 */
2603 		opt = option[0];
2604 		switch (opt) {
2605 		case '4':
2606 			if (have_ipv4) {
2607 				isc_net_disableipv6();
2608 				have_ipv6 = false;
2609 			} else {
2610 				fatal("can't find IPv4 networking");
2611 				UNREACHABLE();
2612 				return false;
2613 			}
2614 			break;
2615 		case '6':
2616 			if (have_ipv6) {
2617 				isc_net_disableipv4();
2618 				have_ipv4 = false;
2619 			} else {
2620 				fatal("can't find IPv6 networking");
2621 				UNREACHABLE();
2622 				return false;
2623 			}
2624 			break;
2625 		case 'd':
2626 			ptr = strpbrk(&option[1], dash_opts);
2627 			if (ptr != &option[1]) {
2628 				cmd = option;
2629 				FULLCHECK("debug");
2630 				debugging = true;
2631 				return false;
2632 			} else {
2633 				debugging = true;
2634 			}
2635 			break;
2636 		case 'h':
2637 			help();
2638 			exit(EXIT_SUCCESS);
2639 			break;
2640 		case 'i':
2641 			fatal("-%c removed", option[0]);
2642 		case 'm': /* memdebug */
2643 			/* memdebug is handled in preparse_args() */
2644 			break;
2645 		case 'n':
2646 			fatal("-%c removed", option[0]);
2647 		case 'r':
2648 			debug("digrc (late)");
2649 			digrc = false;
2650 			break;
2651 		case 'u':
2652 			(*lookup)->use_usec = true;
2653 			break;
2654 		case 'v':
2655 			printf("DiG %s\n", PACKAGE_VERSION);
2656 			exit(EXIT_SUCCESS);
2657 			break;
2658 		}
2659 		if (strlen(option) > 1U) {
2660 			option = &option[1];
2661 		} else {
2662 			return false;
2663 		}
2664 	}
2665 	opt = option[0];
2666 	if (strlen(option) > 1U) {
2667 		value_from_next = false;
2668 		value = &option[1];
2669 	} else {
2670 		value_from_next = true;
2671 		value = next;
2672 	}
2673 	if (value == NULL) {
2674 		goto invalid_option;
2675 	}
2676 	switch (opt) {
2677 	case 'b':
2678 		hash = strchr(value, '#');
2679 		if (hash != NULL) {
2680 			result = parse_uint(&num, hash + 1, MAXPORT,
2681 					    "port number");
2682 			if (result != ISC_R_SUCCESS) {
2683 				fatal("Couldn't parse port number");
2684 			}
2685 			srcport = num;
2686 			*hash = '\0';
2687 		} else {
2688 			srcport = 0;
2689 		}
2690 		if (have_ipv6 && inet_pton(AF_INET6, value, &in6) == 1) {
2691 			isc_sockaddr_fromin6(&localaddr, &in6, srcport);
2692 			isc_net_disableipv4();
2693 		} else if (have_ipv4 && inet_pton(AF_INET, value, &in4) == 1) {
2694 			isc_sockaddr_fromin(&localaddr, &in4, srcport);
2695 			isc_net_disableipv6();
2696 		} else {
2697 			if (hash != NULL) {
2698 				*hash = '#';
2699 			}
2700 			fatal("invalid address %s", value);
2701 		}
2702 		if (hash != NULL) {
2703 			*hash = '#';
2704 		}
2705 		specified_source = true;
2706 		return value_from_next;
2707 	case 'c':
2708 		if ((*lookup)->rdclassset) {
2709 			fprintf(stderr, ";; Warning, extra class option\n");
2710 		}
2711 		*open_type_class = false;
2712 		tr.base = value;
2713 		tr.length = (unsigned int)strlen(value);
2714 		result = dns_rdataclass_fromtext(&rdclass,
2715 						 (isc_textregion_t *)&tr);
2716 		if (result == ISC_R_SUCCESS) {
2717 			(*lookup)->rdclass = rdclass;
2718 			(*lookup)->rdclassset = true;
2719 		} else {
2720 			fprintf(stderr,
2721 				";; Warning, ignoring "
2722 				"invalid class %s\n",
2723 				value);
2724 		}
2725 		return value_from_next;
2726 	case 'f':
2727 		batchname = value;
2728 		return value_from_next;
2729 	case 'k':
2730 		strlcpy(keyfile, value, sizeof(keyfile));
2731 		return value_from_next;
2732 	case 'p':
2733 		result = parse_uint(&num, value, MAXPORT, "port number");
2734 		if (result != ISC_R_SUCCESS) {
2735 			fatal("Couldn't parse port number");
2736 		}
2737 		port = num;
2738 		port_set = true;
2739 		return value_from_next;
2740 	case 'q':
2741 		if (!config_only) {
2742 			if (*need_clone) {
2743 				(*lookup) = clone_lookup(default_lookup, true);
2744 			}
2745 			*need_clone = true;
2746 			strlcpy((*lookup)->textname, value,
2747 				sizeof((*lookup)->textname));
2748 			(*lookup)->trace_root = ((*lookup)->trace ||
2749 						 (*lookup)->ns_search_only);
2750 			(*lookup)->new_search = true;
2751 			if (*firstarg) {
2752 				printgreeting(argc, argv, *lookup);
2753 				*firstarg = false;
2754 			}
2755 			ISC_LIST_APPEND(lookup_list, (*lookup), link);
2756 			debug("looking up %s", (*lookup)->textname);
2757 		}
2758 		return value_from_next;
2759 	case 't':
2760 		*open_type_class = false;
2761 		if (strncasecmp(value, "ixfr=", 5) == 0) {
2762 			rdtype = dns_rdatatype_ixfr;
2763 			result = ISC_R_SUCCESS;
2764 		} else {
2765 			tr.base = value;
2766 			tr.length = (unsigned int)strlen(value);
2767 			result = dns_rdatatype_fromtext(
2768 				&rdtype, (isc_textregion_t *)&tr);
2769 			if (result == ISC_R_SUCCESS &&
2770 			    rdtype == dns_rdatatype_ixfr)
2771 			{
2772 				result = DNS_R_UNKNOWN;
2773 			}
2774 		}
2775 		if (result == ISC_R_SUCCESS) {
2776 			if ((*lookup)->rdtypeset) {
2777 				fprintf(stderr, ";; Warning, "
2778 						"extra type option\n");
2779 			}
2780 			if (rdtype == dns_rdatatype_ixfr) {
2781 				uint32_t serial;
2782 				(*lookup)->rdtype = dns_rdatatype_ixfr;
2783 				(*lookup)->rdtypeset = true;
2784 				result = parse_uint(&serial, &value[5],
2785 						    MAXSERIAL, "serial number");
2786 				if (result != ISC_R_SUCCESS) {
2787 					fatal("Couldn't parse serial number");
2788 				}
2789 				(*lookup)->ixfr_serial = serial;
2790 				(*lookup)->section_question = plusquest;
2791 				(*lookup)->comments = pluscomm;
2792 				if (!(*lookup)->tcp_mode_set) {
2793 					(*lookup)->tcp_mode = true;
2794 				}
2795 			} else {
2796 				(*lookup)->rdtype = rdtype;
2797 				if (!config_only) {
2798 					(*lookup)->rdtypeset = true;
2799 				}
2800 				if (rdtype == dns_rdatatype_axfr) {
2801 					(*lookup)->section_question = plusquest;
2802 					(*lookup)->comments = pluscomm;
2803 				} else if (rdtype == dns_rdatatype_any) {
2804 					if (!(*lookup)->tcp_mode_set) {
2805 						(*lookup)->tcp_mode = true;
2806 					}
2807 				}
2808 				(*lookup)->ixfr_serial = false;
2809 			}
2810 		} else {
2811 			fprintf(stderr,
2812 				";; Warning, ignoring "
2813 				"invalid type %s\n",
2814 				value);
2815 		}
2816 		return value_from_next;
2817 	case 'y':
2818 		if ((ptr = strtok_r(value, ":", &last)) == NULL) {
2819 			usage();
2820 		}
2821 		if ((ptr2 = strtok_r(NULL, ":", &last)) == NULL) { /* name or
2822 								    * secret */
2823 			usage();
2824 		}
2825 		if ((ptr3 = strtok_r(NULL, "", &last)) != NULL) { /* secret or
2826 								   * NULL */
2827 			parse_hmac(ptr);
2828 			ptr = ptr2;
2829 			ptr2 = ptr3;
2830 		} else {
2831 			hmac_alg = DST_ALG_HMACMD5;
2832 			digestbits = 0;
2833 		}
2834 		/* XXXONDREJ: FIXME */
2835 		strlcpy(keynametext, ptr, sizeof(keynametext));
2836 		strlcpy(keysecret, ptr2, sizeof(keysecret));
2837 		if (ptr3 != NULL) {
2838 			ptr[-1] = ':';
2839 		}
2840 		ptr2[-1] = ':';
2841 		return value_from_next;
2842 	case 'x':
2843 		if (*need_clone) {
2844 			*lookup = clone_lookup(default_lookup, true);
2845 		}
2846 		*need_clone = true;
2847 		if (get_reverse(textname, sizeof(textname), value, false) ==
2848 		    ISC_R_SUCCESS)
2849 		{
2850 			strlcpy((*lookup)->textname, textname,
2851 				sizeof((*lookup)->textname));
2852 			debug("looking up %s", (*lookup)->textname);
2853 			(*lookup)->trace_root = ((*lookup)->trace ||
2854 						 (*lookup)->ns_search_only);
2855 			if (!(*lookup)->rdtypeset) {
2856 				(*lookup)->rdtype = dns_rdatatype_ptr;
2857 			}
2858 			if (!(*lookup)->rdclassset) {
2859 				(*lookup)->rdclass = dns_rdataclass_in;
2860 			}
2861 			(*lookup)->new_search = true;
2862 			if (*firstarg) {
2863 				printgreeting(argc, argv, *lookup);
2864 				*firstarg = false;
2865 			}
2866 			ISC_LIST_APPEND(lookup_list, *lookup, link);
2867 		} else {
2868 			fprintf(stderr, "Invalid IP address %s\n", value);
2869 			exit(EXIT_FAILURE);
2870 		}
2871 		return value_from_next;
2872 	invalid_option:
2873 	default:
2874 		fprintf(stderr, "Invalid option: -%s\n", option);
2875 		usage();
2876 	}
2877 	UNREACHABLE();
2878 	return false;
2879 }
2880 
2881 /*%
2882  * Because we may be trying to do memory allocation recording, we're going
2883  * to need to parse the arguments for the -m *before* we start the main
2884  * argument parsing routine.
2885  *
2886  * I'd prefer not to have to do this, but I am not quite sure how else to
2887  * fix the problem.  Argument parsing in dig involves memory allocation
2888  * by its nature, so it can't be done in the main argument parser.
2889  */
2890 static void
2891 preparse_args(int argc, char **argv) {
2892 	int rc;
2893 	char **rv;
2894 	char *option;
2895 
2896 	rc = argc;
2897 	rv = argv;
2898 	for (rc--, rv++; rc > 0; rc--, rv++) {
2899 		if (rv[0][0] != '-') {
2900 			continue;
2901 		}
2902 		option = &rv[0][1];
2903 		while (strpbrk(option, single_dash_opts) == &option[0]) {
2904 			switch (option[0]) {
2905 			case 'd':
2906 				/* For debugging early startup */
2907 				debugging = true;
2908 				break;
2909 			case 'm':
2910 				memdebugging = true;
2911 				isc_mem_debugging = ISC_MEM_DEBUGTRACE |
2912 						    ISC_MEM_DEBUGRECORD;
2913 				break;
2914 			case 'r':
2915 				/*
2916 				 * Must be done early, because ~/.digrc
2917 				 * is read before command line parsing
2918 				 */
2919 				debug("digrc (early)");
2920 				digrc = false;
2921 				break;
2922 			case '4':
2923 				if (ipv6only) {
2924 					fatal("only one of -4 and -6 allowed");
2925 				}
2926 				ipv4only = true;
2927 				break;
2928 			case '6':
2929 				if (ipv4only) {
2930 					fatal("only one of -4 and -6 allowed");
2931 				}
2932 				ipv6only = true;
2933 				break;
2934 			}
2935 			option = &option[1];
2936 		}
2937 		if (strlen(option) == 0U) {
2938 			continue;
2939 		}
2940 		/* Look for dash value option. */
2941 		if (strpbrk(option, dash_opts) != &option[0]) {
2942 			goto invalid_option;
2943 		}
2944 		if (strlen(option) > 1U) {
2945 			/* value in option. */
2946 			continue;
2947 		}
2948 		/* Dash value is next argument so we need to skip it. */
2949 		rc--, rv++;
2950 		/* Handle missing argument */
2951 		if (rc == 0) {
2952 		invalid_option:
2953 			fprintf(stderr, "Invalid option: -%s\n", option);
2954 			usage();
2955 		}
2956 	}
2957 }
2958 
2959 static int
2960 split_batchline(char *batchline, char **bargv, int len, const char *msg) {
2961 	int bargc;
2962 	char *last = NULL;
2963 
2964 	REQUIRE(batchline != NULL);
2965 
2966 	for (bargc = 1, bargv[bargc] = strtok_r(batchline, " \t\r\n", &last);
2967 	     bargc < len && bargv[bargc];
2968 	     bargv[++bargc] = strtok_r(NULL, " \t\r\n", &last))
2969 	{
2970 		debug("%s %d: %s", msg, bargc, bargv[bargc]);
2971 	}
2972 	return bargc;
2973 }
2974 
2975 static void
2976 parse_args(bool is_batchfile, bool config_only, int argc, char **argv) {
2977 	isc_result_t result;
2978 	isc_textregion_t tr;
2979 	bool firstarg = true;
2980 	dig_lookup_t *lookup = NULL;
2981 	dns_rdatatype_t rdtype;
2982 	dns_rdataclass_t rdclass;
2983 	bool open_type_class = true;
2984 	char batchline[MXNAME];
2985 	int bargc;
2986 	char *bargv[64];
2987 	int rc;
2988 	char **rv;
2989 #ifndef NOPOSIX
2990 	char *homedir;
2991 	char rcfile[PATH_MAX];
2992 #endif /* ifndef NOPOSIX */
2993 	bool need_clone = true;
2994 
2995 	/*
2996 	 * The semantics for parsing the args is a bit complex; if
2997 	 * we don't have a host yet, make the arg apply globally,
2998 	 * otherwise make it apply to the latest host.  This is
2999 	 * a bit different than the previous versions, but should
3000 	 * form a consistent user interface.
3001 	 *
3002 	 * First, create a "default lookup" which won't actually be used
3003 	 * anywhere, except for cloning into new lookups
3004 	 */
3005 
3006 	debug("parse_args()");
3007 	if (!is_batchfile) {
3008 		debug("making new lookup");
3009 		default_lookup = make_empty_lookup();
3010 		default_lookup->adflag = true;
3011 		default_lookup->edns = DEFAULT_EDNS_VERSION;
3012 		default_lookup->sendcookie = true;
3013 
3014 #ifndef NOPOSIX
3015 		/*
3016 		 * Treat ${HOME}/.digrc as a special batchfile
3017 		 */
3018 		INSIST(batchfp == NULL);
3019 		homedir = getenv("HOME");
3020 		if (homedir != NULL && digrc) {
3021 			unsigned int n;
3022 			debug("digrc (open)");
3023 			n = snprintf(rcfile, sizeof(rcfile), "%s/.digrc",
3024 				     homedir);
3025 			if (n < sizeof(rcfile)) {
3026 				batchfp = fopen(rcfile, "r");
3027 			}
3028 		}
3029 		if (batchfp != NULL) {
3030 			while (fgets(batchline, sizeof(batchline), batchfp) !=
3031 			       0)
3032 			{
3033 				debug("config line %s", batchline);
3034 				bargc = split_batchline(batchline, bargv, 62,
3035 							".digrc argv");
3036 				bargv[0] = argv[0];
3037 				argv0 = argv[0];
3038 				parse_args(true, true, bargc, (char **)bargv);
3039 			}
3040 			fclose(batchfp);
3041 		}
3042 #endif /* ifndef NOPOSIX */
3043 	}
3044 
3045 	if (is_batchfile && !config_only) {
3046 		/* Processing '-f batchfile'. */
3047 		lookup = clone_lookup(default_lookup, true);
3048 		need_clone = false;
3049 	} else {
3050 		lookup = default_lookup;
3051 	}
3052 
3053 	rc = argc;
3054 	rv = argv;
3055 	for (rc--, rv++; rc > 0; rc--, rv++) {
3056 		debug("main parsing %s", rv[0]);
3057 		if (strncmp(rv[0], "%", 1) == 0) {
3058 			break;
3059 		}
3060 		if (rv[0][0] == '@') {
3061 			if (is_batchfile && !config_only) {
3062 				addresscount = getaddresses(lookup, &rv[0][1],
3063 							    &result);
3064 				if (addresscount == 0) {
3065 					fprintf(stderr,
3066 						"couldn't get address "
3067 						"for '%s': %s: skipping "
3068 						"lookup\n",
3069 						&rv[0][1],
3070 						isc_result_totext(result));
3071 					if (ISC_LINK_LINKED(lookup, link)) {
3072 						ISC_LIST_DEQUEUE(lookup_list,
3073 								 lookup, link);
3074 					}
3075 					destroy_lookup(lookup);
3076 					return;
3077 				}
3078 			} else {
3079 				addresscount = getaddresses(lookup, &rv[0][1],
3080 							    NULL);
3081 				if (addresscount == 0) {
3082 					fatal("no valid addresses for '%s'\n",
3083 					      &rv[0][1]);
3084 				}
3085 			}
3086 		} else if (rv[0][0] == '+') {
3087 			lookup = plus_option(&rv[0][1], is_batchfile,
3088 					     &need_clone, lookup);
3089 		} else if (rv[0][0] == '-') {
3090 			if (rc <= 1) {
3091 				if (dash_option(&rv[0][1], NULL, &lookup,
3092 						&open_type_class, &need_clone,
3093 						config_only, argc, argv,
3094 						&firstarg))
3095 				{
3096 					rc--;
3097 					rv++;
3098 				}
3099 			} else {
3100 				if (dash_option(&rv[0][1], rv[1], &lookup,
3101 						&open_type_class, &need_clone,
3102 						config_only, argc, argv,
3103 						&firstarg))
3104 				{
3105 					rc--;
3106 					rv++;
3107 				}
3108 			}
3109 		} else {
3110 			/*
3111 			 * Anything which isn't an option
3112 			 */
3113 			if (open_type_class) {
3114 				if (strncasecmp(rv[0], "ixfr=", 5) == 0) {
3115 					rdtype = dns_rdatatype_ixfr;
3116 					result = ISC_R_SUCCESS;
3117 				} else {
3118 					tr.base = rv[0];
3119 					tr.length = (unsigned int)strlen(rv[0]);
3120 					result = dns_rdatatype_fromtext(
3121 						&rdtype,
3122 						(isc_textregion_t *)&tr);
3123 					if (result == ISC_R_SUCCESS &&
3124 					    rdtype == dns_rdatatype_ixfr)
3125 					{
3126 						fprintf(stderr, ";; Warning, "
3127 								"ixfr requires "
3128 								"a "
3129 								"serial "
3130 								"number\n");
3131 						continue;
3132 					}
3133 				}
3134 				if (result == ISC_R_SUCCESS) {
3135 					if (lookup->rdtypeset) {
3136 						fprintf(stderr, ";; Warning, "
3137 								"extra type "
3138 								"option\n");
3139 					}
3140 					if (rdtype == dns_rdatatype_ixfr) {
3141 						uint32_t serial;
3142 						lookup->rdtype =
3143 							dns_rdatatype_ixfr;
3144 						lookup->rdtypeset = true;
3145 						result = parse_uint(&serial,
3146 								    &rv[0][5],
3147 								    MAXSERIAL,
3148 								    "serial "
3149 								    "number");
3150 						if (result != ISC_R_SUCCESS) {
3151 							fatal("Couldn't parse "
3152 							      "serial number");
3153 						}
3154 						lookup->ixfr_serial = serial;
3155 						lookup->section_question =
3156 							plusquest;
3157 						lookup->comments = pluscomm;
3158 						if (!lookup->tcp_mode_set) {
3159 							lookup->tcp_mode = true;
3160 						}
3161 					} else {
3162 						lookup->rdtype = rdtype;
3163 						lookup->rdtypeset = true;
3164 						if (rdtype ==
3165 						    dns_rdatatype_axfr)
3166 						{
3167 							lookup->section_question =
3168 								plusquest;
3169 							lookup->comments =
3170 								pluscomm;
3171 						}
3172 						if (rdtype ==
3173 							    dns_rdatatype_any &&
3174 						    !lookup->tcp_mode_set)
3175 						{
3176 							lookup->tcp_mode = true;
3177 						}
3178 						lookup->ixfr_serial = false;
3179 					}
3180 					continue;
3181 				}
3182 				result = dns_rdataclass_fromtext(
3183 					&rdclass, (isc_textregion_t *)&tr);
3184 				if (result == ISC_R_SUCCESS) {
3185 					if (lookup->rdclassset) {
3186 						fprintf(stderr, ";; Warning, "
3187 								"extra class "
3188 								"option\n");
3189 					}
3190 					lookup->rdclass = rdclass;
3191 					lookup->rdclassset = true;
3192 					continue;
3193 				}
3194 			}
3195 
3196 			if (!config_only) {
3197 				if (need_clone) {
3198 					lookup = clone_lookup(default_lookup,
3199 							      true);
3200 				}
3201 				need_clone = true;
3202 				strlcpy(lookup->textname, rv[0],
3203 					sizeof(lookup->textname));
3204 				lookup->trace_root = (lookup->trace ||
3205 						      lookup->ns_search_only);
3206 				lookup->new_search = true;
3207 				if (firstarg) {
3208 					printgreeting(argc, argv, lookup);
3209 					firstarg = false;
3210 				}
3211 				ISC_LIST_APPEND(lookup_list, lookup, link);
3212 				debug("looking up %s", lookup->textname);
3213 			}
3214 			/* XXX Error message */
3215 		}
3216 	}
3217 
3218 	/*
3219 	 * If we have a batchfile, seed the lookup list with the
3220 	 * first entry, then trust the callback in dighost_shutdown
3221 	 * to get the rest
3222 	 */
3223 	char *filename = batchname;
3224 	if ((filename != NULL) && !(is_batchfile)) {
3225 		if (strcmp(filename, "-") == 0) {
3226 			batchfp = stdin;
3227 		} else {
3228 			batchfp = fopen(filename, "r");
3229 		}
3230 		if (batchfp == NULL) {
3231 			perror(filename);
3232 			if (exitcode < 8) {
3233 				exitcode = 8;
3234 			}
3235 			fatal("couldn't open specified batch file");
3236 		}
3237 		/* XXX Remove code dup from shutdown code */
3238 	next_line:
3239 		if (fgets(batchline, sizeof(batchline), batchfp) != 0) {
3240 			debug("batch line %s", batchline);
3241 			if (batchline[0] == '\r' || batchline[0] == '\n' ||
3242 			    batchline[0] == '#' || batchline[0] == ';')
3243 			{
3244 				goto next_line;
3245 			}
3246 			bargc = split_batchline(batchline, bargv, 14,
3247 						"batch argv");
3248 			bargv[0] = argv[0];
3249 			argv0 = argv[0];
3250 			parse_args(true, false, bargc, (char **)bargv);
3251 			return;
3252 		}
3253 		return;
3254 	}
3255 	/*
3256 	 * If no lookup specified, search for root
3257 	 */
3258 	if ((lookup_list.head == NULL) && !config_only) {
3259 		if (need_clone) {
3260 			lookup = clone_lookup(default_lookup, true);
3261 		}
3262 		need_clone = true;
3263 		lookup->trace_root = (lookup->trace || lookup->ns_search_only);
3264 		lookup->new_search = true;
3265 		strlcpy(lookup->textname, ".", sizeof(lookup->textname));
3266 		lookup->rdtype = dns_rdatatype_ns;
3267 		lookup->rdtypeset = true;
3268 		if (firstarg) {
3269 			printgreeting(argc, argv, lookup);
3270 			firstarg = false;
3271 		}
3272 		ISC_LIST_APPEND(lookup_list, lookup, link);
3273 	}
3274 	if (!need_clone) {
3275 		destroy_lookup(lookup);
3276 	}
3277 }
3278 
3279 /*
3280  * Callback from dighost.c to allow program-specific shutdown code.
3281  * Here, we're possibly reading from a batch file, then shutting down
3282  * for real if there's nothing in the batch file to read.
3283  */
3284 static void
3285 query_finished(void) {
3286 	char batchline[MXNAME];
3287 
3288 	fflush(stdout);
3289 
3290 	if (batchname != NULL && !feof(batchfp) &&
3291 	    fgets(batchline, sizeof(batchline), batchfp) != NULL)
3292 	{
3293 		int bargc;
3294 		char *bargv[16];
3295 		debug("batch line %s", batchline);
3296 		bargc = split_batchline(batchline, bargv, 14, "batch argv");
3297 		bargv[0] = argv0;
3298 		parse_args(true, false, bargc, (char **)bargv);
3299 		start_lookup();
3300 		return;
3301 	}
3302 
3303 	debug("shutdown");
3304 
3305 	/* We are done */
3306 	if (batchname != NULL) {
3307 		if (batchfp != stdin) {
3308 			fclose(batchfp);
3309 		}
3310 		batchname = NULL;
3311 	}
3312 	isc_loopmgr_shutdown(loopmgr);
3313 }
3314 
3315 static void
3316 dig_error(const char *format, ...) {
3317 	va_list args;
3318 
3319 	if (yaml) {
3320 		printf("- type: DIG_ERROR\n");
3321 
3322 		/*
3323 		 * Print an indent before a literal block quote.
3324 		 * Note: this will break if used to print more than
3325 		 * one line of text as only the first line would be
3326 		 * indented.
3327 		 */
3328 		printf("  message: |\n");
3329 		printf("    ");
3330 	} else {
3331 		printf(";; ");
3332 	}
3333 
3334 	va_start(args, format);
3335 	vprintf(format, args);
3336 	va_end(args);
3337 	printf("\n"); /* We get the error without a newline */
3338 }
3339 
3340 static void
3341 dig_warning(const char *format, ...) {
3342 	va_list args;
3343 
3344 	if (!yaml) {
3345 		printf(";; ");
3346 
3347 		va_start(args, format);
3348 		vprintf(format, args);
3349 		va_end(args);
3350 
3351 		printf("\n");
3352 	}
3353 }
3354 
3355 static void
3356 dig_comments(dig_lookup_t *lookup, const char *format, ...) {
3357 	va_list args;
3358 
3359 	if (lookup->comments && !yaml) {
3360 		printf(";; ");
3361 
3362 		va_start(args, format);
3363 		vprintf(format, args);
3364 		va_end(args);
3365 
3366 		printf("\n");
3367 	}
3368 }
3369 
3370 void
3371 dig_setup(int argc, char **argv) {
3372 	ISC_LIST_INIT(lookup_list);
3373 	ISC_LIST_INIT(server_list);
3374 	ISC_LIST_INIT(search_list);
3375 
3376 	debug("dig_setup()");
3377 
3378 	/* setup dighost callbacks */
3379 	dighost_printmessage = printmessage;
3380 	dighost_received = received;
3381 	dighost_trying = trying;
3382 	dighost_shutdown = query_finished;
3383 	dighost_error = dig_error;
3384 	dighost_warning = dig_warning;
3385 	dighost_comments = dig_comments;
3386 
3387 	progname = argv[0];
3388 	preparse_args(argc, argv);
3389 
3390 	setup_libs();
3391 	setup_system(ipv4only, ipv6only);
3392 }
3393 
3394 void
3395 dig_query_setup(bool is_batchfile, bool config_only, int argc, char **argv) {
3396 	debug("dig_query_setup");
3397 
3398 	parse_args(is_batchfile, config_only, argc, argv);
3399 	if (keyfile[0] != 0) {
3400 		setup_file_key();
3401 	} else if (keysecret[0] != 0) {
3402 		setup_text_key();
3403 	}
3404 	if (domainopt[0] != '\0') {
3405 		set_search_domain(domainopt);
3406 		usesearch = true;
3407 	}
3408 }
3409 
3410 void
3411 dig_startup(void) {
3412 	debug("dig_startup()");
3413 
3414 	isc_loopmgr_setup(loopmgr, run_loop, NULL);
3415 	isc_loopmgr_run(loopmgr);
3416 }
3417 
3418 void
3419 dig_shutdown(void) {
3420 	destroy_lookup(default_lookup);
3421 	cancel_all();
3422 	destroy_libs();
3423 }
3424 
3425 /*% Main processing routine for dig */
3426 int
3427 main(int argc, char **argv) {
3428 	dig_setup(argc, argv);
3429 	dig_query_setup(false, false, argc, argv);
3430 	dig_startup();
3431 	dig_shutdown();
3432 
3433 	return exitcode;
3434 }
3435