mdig.c 54.4 KB
Newer Older
Mark Andrews's avatar
Mark Andrews committed
1
/*
2
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
Mark Andrews's avatar
Mark Andrews committed
3
 *
4 5
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
6
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
7 8 9
 *
 * See the COPYRIGHT file distributed with this work for additional
 * information regarding copyright ownership.
Mark Andrews's avatar
Mark Andrews committed
10 11
 */

12
#include <inttypes.h>
13
#include <stdbool.h>
Mark Andrews's avatar
Mark Andrews committed
14 15 16 17 18
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <isc/app.h>
19
#include <isc/attributes.h>
Mark Andrews's avatar
Mark Andrews committed
20 21 22 23 24 25
#include <isc/base64.h>
#include <isc/hash.h>
#include <isc/hex.h>
#include <isc/log.h>
#include <isc/mem.h>
#include <isc/net.h>
26
#include <isc/nonce.h>
Mark Andrews's avatar
Mark Andrews committed
27
#include <isc/parseint.h>
Francis Dupont's avatar
Francis Dupont committed
28
#include <isc/print.h>
29
#include <isc/random.h>
Mark Andrews's avatar
Mark Andrews committed
30 31 32 33 34 35 36 37 38
#include <isc/sockaddr.h>
#include <isc/socket.h>
#include <isc/string.h>
#include <isc/task.h>
#include <isc/timer.h>
#include <isc/util.h>

#include <dns/byaddr.h>
#include <dns/dispatch.h>
39
#include <dns/events.h>
Mark Andrews's avatar
Mark Andrews committed
40 41 42 43
#include <dns/fixedname.h>
#include <dns/message.h>
#include <dns/name.h>
#include <dns/rdata.h>
44
#include <dns/rdataclass.h>
Mark Andrews's avatar
Mark Andrews committed
45 46 47 48
#include <dns/rdataset.h>
#include <dns/rdatatype.h>
#include <dns/request.h>
#include <dns/resolver.h>
49
#include <dns/result.h>
Mark Andrews's avatar
Mark Andrews committed
50
#include <dns/types.h>
51
#include <dns/view.h>
Mark Andrews's avatar
Mark Andrews committed
52

53
#include <dst/result.h>
Mark Andrews's avatar
Mark Andrews committed
54

55 56
#include <bind9/getaddresses.h>

57 58 59 60 61 62 63 64
#define CHECK(str, x)                                                       \
	{                                                                   \
		if ((x) != ISC_R_SUCCESS) {                                 \
			fprintf(stderr, "mdig: %s failed with %s\n", (str), \
				isc_result_totext(x));                      \
			exit(-1);                                           \
		}                                                           \
	}
Mark Andrews's avatar
Mark Andrews committed
65 66 67

#define RUNCHECK(x) RUNTIME_CHECK((x) == ISC_R_SUCCESS)

68 69 70
#define ADD_STRING(b, s)                                        \
	{                                                       \
		if (strlen(s) >= isc_buffer_availablelength(b)) \
71
			return ((ISC_R_NOSPACE));               \
72 73 74
		else                                            \
			isc_buffer_putstr(b, s);                \
	}
Mark Andrews's avatar
Mark Andrews committed
75

Evan Hunt's avatar
Evan Hunt committed
76 77 78 79 80
#define MXNAME	   (DNS_NAME_MAXTEXT + 1)
#define COMMSIZE   0xffff
#define OUTPUTBUF  32767
#define MAXPORT	   0xffff
#define PORT	   53
Mark Andrews's avatar
Mark Andrews committed
81 82 83
#define MAXTIMEOUT 0xffff
#define TCPTIMEOUT 10
#define UDPTIMEOUT 5
Evan Hunt's avatar
Evan Hunt committed
84
#define MAXTRIES   0xffffffff
Mark Andrews's avatar
Mark Andrews committed
85

Evan Hunt's avatar
Evan Hunt committed
86
static isc_mem_t *mctx;
Mark Andrews's avatar
Mark Andrews committed
87
static dns_requestmgr_t *requestmgr;
Evan Hunt's avatar
Evan Hunt committed
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
static const char *batchname;
static FILE *batchfp;
static bool have_ipv4 = false;
static bool have_ipv6 = false;
static bool have_src = false;
static bool tcp_mode = false;
static bool besteffort = true;
static bool display_short_form = false;
static bool display_headers = true;
static bool display_comments = true;
static int display_rrcomments = 0;
static bool display_ttlunits = true;
static bool display_ttl = true;
static bool display_class = true;
static bool display_crypto = true;
static bool display_multiline = false;
static bool display_question = true;
static bool display_answer = true;
static bool display_authority = true;
static bool display_additional = true;
static bool display_unknown_format = false;
static bool yaml = false;
static bool continue_on_error = false;
static uint32_t display_splitwidth = 0xffffffff;
static isc_sockaddr_t srcaddr;
static char *server;
static isc_sockaddr_t dstaddr;
static in_port_t port = 53;
static isc_dscp_t dscp = -1;
static unsigned char cookie_secret[33];
static int onfly = 0;
static char hexcookie[81];
Mark Andrews's avatar
Mark Andrews committed
120 121

struct query {
122 123
	char textname[MXNAME]; /*% Name we're going to be
				* looking up */
Evan Hunt's avatar
Evan Hunt committed
124 125 126 127 128 129 130 131 132 133 134
	bool recurse;
	bool have_aaonly;
	bool have_adflag;
	bool have_cdflag;
	bool have_zflag;
	bool dnssec;
	bool expire;
	bool send_cookie;
	char *cookie;
	bool nsid;
	dns_rdatatype_t rdtype;
Mark Andrews's avatar
Mark Andrews committed
135
	dns_rdataclass_t rdclass;
Evan Hunt's avatar
Evan Hunt committed
136 137 138 139 140 141 142 143 144
	uint16_t udpsize;
	int16_t edns;
	dns_ednsopt_t *ednsopts;
	unsigned int ednsoptscnt;
	unsigned int ednsflags;
	isc_sockaddr_t *ecs_addr;
	unsigned int timeout;
	unsigned int udptimeout;
	unsigned int udpretries;
Mark Andrews's avatar
Mark Andrews committed
145 146 147 148 149 150 151
	ISC_LINK(struct query) link;
};
static struct query default_query;
static ISC_LIST(struct query) queries;

#define EDNSOPTS 100U
/*% opcode text */
152 153 154 155 156
static const char *const opcodetext[] = {
	"QUERY",      "IQUERY",	    "STATUS",	  "RESERVED3",
	"NOTIFY",     "UPDATE",	    "RESERVED6",  "RESERVED7",
	"RESERVED8",  "RESERVED9",  "RESERVED10", "RESERVED11",
	"RESERVED12", "RESERVED13", "RESERVED14", "RESERVED15"
Mark Andrews's avatar
Mark Andrews committed
157 158 159
};

/*% return code text */
160 161 162 163 164
static const char *const rcodetext[] = {
	"NOERROR",    "FORMERR",    "SERVFAIL",	  "NXDOMAIN",	"NOTIMP",
	"REFUSED",    "YXDOMAIN",   "YXRRSET",	  "NXRRSET",	"NOTAUTH",
	"NOTZONE",    "RESERVED11", "RESERVED12", "RESERVED13", "RESERVED14",
	"RESERVED15", "BADVERS"
Mark Andrews's avatar
Mark Andrews committed
165 166 167 168
};

/*% safe rcodetext[] */
static char *
Evan Hunt's avatar
Evan Hunt committed
169
rcode_totext(dns_rcode_t rcode) {
Mark Andrews's avatar
Mark Andrews committed
170 171 172
	static char buf[sizeof("?65535")];
	union {
		const char *consttext;
Evan Hunt's avatar
Evan Hunt committed
173
		char *deconsttext;
Mark Andrews's avatar
Mark Andrews committed
174 175
	} totext;

176
	if (rcode >= (sizeof(rcodetext) / sizeof(rcodetext[0]))) {
Mark Andrews's avatar
Mark Andrews committed
177 178
		snprintf(buf, sizeof(buf), "?%u", rcode);
		totext.deconsttext = buf;
179
	} else {
Mark Andrews's avatar
Mark Andrews committed
180
		totext.consttext = rcodetext[rcode];
181 182
	}
	return (totext.deconsttext);
Mark Andrews's avatar
Mark Andrews committed
183 184 185 186
}

/* receive response event handler */
static void
Evan Hunt's avatar
Evan Hunt committed
187 188 189 190 191 192 193 194 195
recvresponse(isc_task_t *task, isc_event_t *event) {
	dns_requestevent_t *reqev = (dns_requestevent_t *)event;
	isc_result_t result;
	dns_message_t *query = NULL, *response = NULL;
	unsigned int parseflags = 0;
	isc_buffer_t *msgbuf = NULL, *buf = NULL;
	unsigned int len = OUTPUTBUF;
	dns_master_style_t *style = NULL;
	unsigned int styleflags = 0;
Mark Andrews's avatar
Mark Andrews committed
196 197 198 199 200
	dns_messagetextflag_t flags;

	UNUSED(task);

	REQUIRE(reqev != NULL);
201
	query = reqev->ev_arg;
Mark Andrews's avatar
Mark Andrews committed
202 203 204 205

	if (reqev->result != ISC_R_SUCCESS) {
		fprintf(stderr, "response failed with %s\n",
			isc_result_totext(reqev->result));
206
		if (continue_on_error) {
207
			goto cleanup;
208
		} else {
209
			exit(-1);
210
		}
Mark Andrews's avatar
Mark Andrews committed
211 212
	}

213
	dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &response);
Mark Andrews's avatar
Mark Andrews committed
214 215 216 217 218 219

	parseflags |= DNS_MESSAGEPARSE_PRESERVEORDER;
	if (besteffort) {
		parseflags |= DNS_MESSAGEPARSE_BESTEFFORT;
		parseflags |= DNS_MESSAGEPARSE_IGNORETRUNCATION;
	}
Evan Hunt's avatar
Evan Hunt committed
220 221

	msgbuf = dns_request_getanswer(reqev->request);
Mark Andrews's avatar
Mark Andrews committed
222 223 224 225
	result = dns_request_getresponse(reqev->request, response, parseflags);
	CHECK("dns_request_getresponse", result);

	styleflags |= DNS_STYLEFLAG_REL_OWNER;
Evan Hunt's avatar
Evan Hunt committed
226 227
	if (yaml) {
		styleflags |= DNS_STYLEFLAG_YAML;
228 229
		response->indent.string = "  ";
		response->indent.count = 3;
Evan Hunt's avatar
Evan Hunt committed
230 231 232 233 234 235 236 237
	} else {
		if (display_comments) {
			styleflags |= DNS_STYLEFLAG_COMMENT;
		}
		if (display_unknown_format) {
			styleflags |= DNS_STYLEFLAG_UNKNOWNFORMAT;
		}
		if (display_rrcomments > 0) {
238 239
			styleflags |= DNS_STYLEFLAG_RRCOMMENT;
		}
Evan Hunt's avatar
Evan Hunt committed
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
		if (display_ttlunits) {
			styleflags |= DNS_STYLEFLAG_TTL_UNITS;
		}
		if (!display_ttl) {
			styleflags |= DNS_STYLEFLAG_NO_TTL;
		}
		if (!display_class) {
			styleflags |= DNS_STYLEFLAG_NO_CLASS;
		}
		if (!display_crypto) {
			styleflags |= DNS_STYLEFLAG_NOCRYPTO;
		}
		if (display_multiline) {
			styleflags |= DNS_STYLEFLAG_OMIT_OWNER;
			styleflags |= DNS_STYLEFLAG_OMIT_CLASS;
			styleflags |= DNS_STYLEFLAG_REL_DATA;
			styleflags |= DNS_STYLEFLAG_OMIT_TTL;
			styleflags |= DNS_STYLEFLAG_TTL;
			styleflags |= DNS_STYLEFLAG_MULTILINE;
			styleflags |= DNS_STYLEFLAG_COMMENT;
			/* Turn on rrcomments unless explicitly disabled */
			if (display_rrcomments >= 0) {
				styleflags |= DNS_STYLEFLAG_RRCOMMENT;
			}
		}
Mark Andrews's avatar
Mark Andrews committed
265
	}
266
	if (display_multiline || (!display_ttl && !display_class)) {
267 268 269
		result = dns_master_stylecreate(&style, styleflags, 24, 24, 24,
						32, 80, 8, display_splitwidth,
						mctx);
270
	} else if (!display_ttl || !display_class) {
271 272 273
		result = dns_master_stylecreate(&style, styleflags, 24, 24, 32,
						40, 80, 8, display_splitwidth,
						mctx);
274
	} else {
275 276 277
		result = dns_master_stylecreate(&style, styleflags, 24, 32, 40,
						48, 80, 8, display_splitwidth,
						mctx);
278
	}
Mark Andrews's avatar
Mark Andrews committed
279 280 281 282 283 284 285
	CHECK("dns_master_stylecreate2", result);

	flags = 0;
	if (!display_headers) {
		flags |= DNS_MESSAGETEXTFLAG_NOHEADERS;
		flags |= DNS_MESSAGETEXTFLAG_NOCOMMENTS;
	}
286
	if (!display_comments) {
Mark Andrews's avatar
Mark Andrews committed
287
		flags |= DNS_MESSAGETEXTFLAG_NOCOMMENTS;
288
	}
Mark Andrews's avatar
Mark Andrews committed
289

290
	isc_buffer_allocate(mctx, &buf, len);
Mark Andrews's avatar
Mark Andrews committed
291

Evan Hunt's avatar
Evan Hunt committed
292
	if (yaml) {
Evan Hunt's avatar
Evan Hunt committed
293
		char sockstr[ISC_SOCKADDR_FORMATSIZE];
Evan Hunt's avatar
Evan Hunt committed
294
		uint16_t sport;
Evan Hunt's avatar
Evan Hunt committed
295 296
		char *hash;
		int pf;
Evan Hunt's avatar
Evan Hunt committed
297 298 299 300 301 302

		printf("-\n");
		printf("  type: MESSAGE\n");
		printf("  message:\n");

		if (((response->flags & DNS_MESSAGEFLAG_RD) != 0) &&
Evan Hunt's avatar
Evan Hunt committed
303 304
		    ((response->flags & DNS_MESSAGEFLAG_RA) != 0))
		{
Evan Hunt's avatar
Evan Hunt committed
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
			printf("    type: RECURSIVE_RESPONSE\n");
		} else {
			printf("    type: AUTH_RESPONSE\n");
		}

		printf("    message_size: %ub\n",
		       isc_buffer_usedlength(msgbuf));

		pf = isc_sockaddr_pf(&dstaddr);
		if (pf == PF_INET || pf == PF_INET6) {
			printf("    socket_family: %s\n",
			       pf == PF_INET ? "INET" : "INET6");

			printf("    socket_protocol: %s\n",
			       tcp_mode ? "TCP" : "UDP");

			sport = isc_sockaddr_getport(&dstaddr);
			isc_sockaddr_format(&dstaddr, sockstr, sizeof(sockstr));
			hash = strchr(sockstr, '#');
			if (hash != NULL) {
				*hash = '\0';
			}
			printf("    response_address: %s\n", sockstr);
			printf("    response_port: %u\n", sport);
		}

		if (have_src) {
			sport = isc_sockaddr_getport(&srcaddr);
			isc_sockaddr_format(&srcaddr, sockstr, sizeof(sockstr));
			hash = strchr(sockstr, '#');
			if (hash != NULL) {
				*hash = '\0';
			}
			printf("    query_address: %s\n", sockstr);
			printf("    query_port: %u\n", sport);
		}

		printf("    %s:\n", "response_message_data");
		result = dns_message_headertotext(response, style, flags, buf);
344
		CHECK("dns_message_headertotext", result);
Evan Hunt's avatar
Evan Hunt committed
345
	} else if (display_comments && !display_short_form) {
Mark Andrews's avatar
Mark Andrews committed
346
		printf(";; Got answer:\n");
Francis Dupont's avatar
Francis Dupont committed
347

Mark Andrews's avatar
Mark Andrews committed
348 349 350 351
		if (display_headers) {
			printf(";; ->>HEADER<<- opcode: %s, status: %s, "
			       "id: %u\n",
			       opcodetext[response->opcode],
352
			       rcode_totext(response->rcode), response->id);
Mark Andrews's avatar
Mark Andrews committed
353
			printf(";; flags:");
354
			if ((response->flags & DNS_MESSAGEFLAG_QR) != 0) {
Mark Andrews's avatar
Mark Andrews committed
355
				printf(" qr");
356 357
			}
			if ((response->flags & DNS_MESSAGEFLAG_AA) != 0) {
Mark Andrews's avatar
Mark Andrews committed
358
				printf(" aa");
359 360
			}
			if ((response->flags & DNS_MESSAGEFLAG_TC) != 0) {
Mark Andrews's avatar
Mark Andrews committed
361
				printf(" tc");
362 363
			}
			if ((response->flags & DNS_MESSAGEFLAG_RD) != 0) {
Mark Andrews's avatar
Mark Andrews committed
364
				printf(" rd");
365 366
			}
			if ((response->flags & DNS_MESSAGEFLAG_RA) != 0) {
Mark Andrews's avatar
Mark Andrews committed
367
				printf(" ra");
368 369
			}
			if ((response->flags & DNS_MESSAGEFLAG_AD) != 0) {
Mark Andrews's avatar
Mark Andrews committed
370
				printf(" ad");
371 372
			}
			if ((response->flags & DNS_MESSAGEFLAG_CD) != 0) {
Mark Andrews's avatar
Mark Andrews committed
373
				printf(" cd");
374 375
			}
			if ((response->flags & 0x0040U) != 0) {
Mark Andrews's avatar
Mark Andrews committed
376
				printf("; MBZ: 0x4");
377
			}
Mark Andrews's avatar
Mark Andrews committed
378 379 380 381 382 383 384 385 386

			printf("; QUERY: %u, ANSWER: %u, "
			       "AUTHORITY: %u, ADDITIONAL: %u\n",
			       response->counts[DNS_SECTION_QUESTION],
			       response->counts[DNS_SECTION_ANSWER],
			       response->counts[DNS_SECTION_AUTHORITY],
			       response->counts[DNS_SECTION_ADDITIONAL]);

			if ((response->flags & DNS_MESSAGEFLAG_RD) != 0 &&
Evan Hunt's avatar
Evan Hunt committed
387 388
			    (response->flags & DNS_MESSAGEFLAG_RA) == 0)
			{
Mark Andrews's avatar
Mark Andrews committed
389 390
				printf(";; WARNING: recursion requested "
				       "but not available\n");
391
			}
Mark Andrews's avatar
Mark Andrews committed
392 393 394 395 396 397
		}
	}

repopulate_buffer:

	if (display_comments && display_headers && !display_short_form) {
398 399
		result = dns_message_pseudosectiontotext(
			response, DNS_PSEUDOSECTION_OPT, style, flags, buf);
Mark Andrews's avatar
Mark Andrews committed
400
		if (result == ISC_R_NOSPACE) {
401
		buftoosmall:
Mark Andrews's avatar
Mark Andrews committed
402 403
			len += OUTPUTBUF;
			isc_buffer_free(&buf);
404 405
			isc_buffer_allocate(mctx, &buf, len);
			goto repopulate_buffer;
Mark Andrews's avatar
Mark Andrews committed
406 407 408 409 410
		}
		CHECK("dns_message_pseudosectiontotext", result);
	}

	if (display_question && display_headers && !display_short_form) {
411 412
		result = dns_message_sectiontotext(
			response, DNS_SECTION_QUESTION, style, flags, buf);
413
		if (result == ISC_R_NOSPACE) {
Mark Andrews's avatar
Mark Andrews committed
414
			goto buftoosmall;
415
		}
Mark Andrews's avatar
Mark Andrews committed
416 417 418 419
		CHECK("dns_message_sectiontotext", result);
	}

	if (display_answer && !display_short_form) {
420
		result = dns_message_sectiontotext(response, DNS_SECTION_ANSWER,
Mark Andrews's avatar
Mark Andrews committed
421
						   style, flags, buf);
422
		if (result == ISC_R_NOSPACE) {
Mark Andrews's avatar
Mark Andrews committed
423
			goto buftoosmall;
424
		}
Mark Andrews's avatar
Mark Andrews committed
425 426
		CHECK("dns_message_sectiontotext", result);
	} else if (display_answer) {
Evan Hunt's avatar
Evan Hunt committed
427
		dns_name_t *name;
Mark Andrews's avatar
Mark Andrews committed
428
		dns_rdataset_t *rdataset;
Evan Hunt's avatar
Evan Hunt committed
429 430 431 432
		isc_result_t loopresult;
		dns_name_t empty_name;
		dns_rdata_t rdata = DNS_RDATA_INIT;
		unsigned int answerstyleflags = 0;
Mark Andrews's avatar
Mark Andrews committed
433

434
		if (!display_crypto) {
Mark Andrews's avatar
Mark Andrews committed
435
			answerstyleflags |= DNS_STYLEFLAG_NOCRYPTO;
436 437
		}
		if (display_unknown_format) {
438
			answerstyleflags |= DNS_STYLEFLAG_UNKNOWNFORMAT;
439
		}
Mark Andrews's avatar
Mark Andrews committed
440 441 442

		dns_name_init(&empty_name, NULL);
		result = dns_message_firstname(response, DNS_SECTION_ANSWER);
443
		if (result != ISC_R_NOMORE) {
Mark Andrews's avatar
Mark Andrews committed
444
			CHECK("dns_message_firstname", result);
445
		}
Mark Andrews's avatar
Mark Andrews committed
446 447

		for (;;) {
448
			if (result == ISC_R_NOMORE) {
Mark Andrews's avatar
Mark Andrews committed
449
				break;
450
			}
Mark Andrews's avatar
Mark Andrews committed
451 452
			CHECK("dns_message_nextname", result);
			name = NULL;
453
			dns_message_currentname(response, DNS_SECTION_ANSWER,
Mark Andrews's avatar
Mark Andrews committed
454 455 456 457
						&name);

			for (rdataset = ISC_LIST_HEAD(name->list);
			     rdataset != NULL;
Evan Hunt's avatar
Evan Hunt committed
458 459
			     rdataset = ISC_LIST_NEXT(rdataset, link))
			{
Mark Andrews's avatar
Mark Andrews committed
460 461 462 463
				loopresult = dns_rdataset_first(rdataset);
				while (loopresult == ISC_R_SUCCESS) {
					dns_rdataset_current(rdataset, &rdata);
					result = dns_rdata_tofmttext(
464 465
						&rdata, NULL, answerstyleflags,
						0, 60, " ", buf);
466
					if (result == ISC_R_NOSPACE) {
Mark Andrews's avatar
Mark Andrews committed
467
						goto buftoosmall;
468
					}
Mark Andrews's avatar
Mark Andrews committed
469 470
					CHECK("dns_rdata_tofmttext", result);
					loopresult =
471
						dns_rdataset_next(rdataset);
Mark Andrews's avatar
Mark Andrews committed
472 473
					dns_rdata_reset(&rdata);
					if (strlen("\n") >=
474
					    isc_buffer_availablelength(buf)) {
Mark Andrews's avatar
Mark Andrews committed
475
						goto buftoosmall;
476
					}
Mark Andrews's avatar
Mark Andrews committed
477 478 479 480 481 482 483 484 485
					isc_buffer_putstr(buf, "\n");
				}
			}
			result = dns_message_nextname(response,
						      DNS_SECTION_ANSWER);
		}
	}

	if (display_authority && !display_short_form) {
486 487
		result = dns_message_sectiontotext(
			response, DNS_SECTION_AUTHORITY, style, flags, buf);
488
		if (result == ISC_R_NOSPACE) {
Mark Andrews's avatar
Mark Andrews committed
489
			goto buftoosmall;
490
		}
Mark Andrews's avatar
Mark Andrews committed
491 492 493 494
		CHECK("dns_message_sectiontotext", result);
	}

	if (display_additional && !display_short_form) {
495 496
		result = dns_message_sectiontotext(
			response, DNS_SECTION_ADDITIONAL, style, flags, buf);
497
		if (result == ISC_R_NOSPACE) {
Mark Andrews's avatar
Mark Andrews committed
498
			goto buftoosmall;
499
		}
Mark Andrews's avatar
Mark Andrews committed
500 501 502 503 504 505 506
		CHECK("dns_message_sectiontotext", result);
	}

	if (display_additional && !display_short_form && display_headers) {
		/*
		 * Only print the signature on the first record.
		 */
507 508
		result = dns_message_pseudosectiontotext(
			response, DNS_PSEUDOSECTION_TSIG, style, flags, buf);
509
		if (result == ISC_R_NOSPACE) {
Mark Andrews's avatar
Mark Andrews committed
510
			goto buftoosmall;
511
		}
Mark Andrews's avatar
Mark Andrews committed
512
		CHECK("dns_message_pseudosectiontotext", result);
513 514
		result = dns_message_pseudosectiontotext(
			response, DNS_PSEUDOSECTION_SIG0, style, flags, buf);
515
		if (result == ISC_R_NOSPACE) {
Mark Andrews's avatar
Mark Andrews committed
516
			goto buftoosmall;
517
		}
Mark Andrews's avatar
Mark Andrews committed
518 519 520
		CHECK("dns_message_pseudosectiontotext", result);
	}

Evan Hunt's avatar
Evan Hunt committed
521 522
	if (display_headers && display_comments && !display_short_form && !yaml)
	{
Mark Andrews's avatar
Mark Andrews committed
523
		printf("\n");
Evan Hunt's avatar
Evan Hunt committed
524
	}
Mark Andrews's avatar
Mark Andrews committed
525 526 527 528 529 530 531

	printf("%.*s", (int)isc_buffer_usedlength(buf),
	       (char *)isc_buffer_base(buf));
	isc_buffer_free(&buf);

cleanup:
	fflush(stdout);
532
	if (style != NULL) {
Mark Andrews's avatar
Mark Andrews committed
533
		dns_master_styledestroy(&style, mctx);
534 535
	}
	if (query != NULL) {
536
		dns_message_detach(&query);
537 538
	}
	if (response != NULL) {
539
		dns_message_detach(&response);
540
	}
Mark Andrews's avatar
Mark Andrews committed
541 542 543
	dns_request_destroy(&reqev->request);
	isc_event_free(&event);

544
	if (--onfly == 0) {
Mark Andrews's avatar
Mark Andrews committed
545
		isc_app_shutdown();
546
	}
Mark Andrews's avatar
Mark Andrews committed
547 548 549 550 551 552
	return;
}

/*%
 * Add EDNS0 option record to a message.  Currently, the only supported
 * options are UDP buffer size, the DO bit, and EDNS options
553
 * (e.g., NSID, COOKIE, client-subnet)
Mark Andrews's avatar
Mark Andrews committed
554 555
 */
static void
556
add_opt(dns_message_t *msg, uint16_t udpsize, uint16_t edns, unsigned int flags,
Evan Hunt's avatar
Evan Hunt committed
557
	dns_ednsopt_t *opts, size_t count) {
Mark Andrews's avatar
Mark Andrews committed
558
	dns_rdataset_t *rdataset = NULL;
Evan Hunt's avatar
Evan Hunt committed
559
	isc_result_t result;
Mark Andrews's avatar
Mark Andrews committed
560 561

	result = dns_message_buildopt(msg, &rdataset, edns, udpsize, flags,
Evan Hunt's avatar
Evan Hunt committed
562
				      opts, count);
Mark Andrews's avatar
Mark Andrews committed
563 564 565 566 567 568
	CHECK("dns_message_buildopt", result);
	result = dns_message_setopt(msg, rdataset);
	CHECK("dns_message_setopt", result);
}

static void
Evan Hunt's avatar
Evan Hunt committed
569
compute_cookie(unsigned char *cookie, size_t len) {
Mark Andrews's avatar
Mark Andrews committed
570 571 572 573 574 575
	/* XXXMPA need to fix, should be per server. */
	INSIST(len >= 8U);
	memmove(cookie, cookie_secret, 8);
}

static isc_result_t
Evan Hunt's avatar
Evan Hunt committed
576 577 578 579
sendquery(struct query *query, isc_task_t *task) {
	dns_request_t *request;
	dns_message_t *message;
	dns_name_t *qname;
Mark Andrews's avatar
Mark Andrews committed
580
	dns_rdataset_t *qrdataset;
Evan Hunt's avatar
Evan Hunt committed
581
	isc_result_t result;
Mark Andrews's avatar
Mark Andrews committed
582
	dns_fixedname_t queryname;
Evan Hunt's avatar
Evan Hunt committed
583 584
	isc_buffer_t buf;
	unsigned int options;
Mark Andrews's avatar
Mark Andrews committed
585 586 587 588 589 590 591 592 593 594 595

	onfly++;

	dns_fixedname_init(&queryname);
	isc_buffer_init(&buf, query->textname, strlen(query->textname));
	isc_buffer_add(&buf, strlen(query->textname));
	result = dns_name_fromtext(dns_fixedname_name(&queryname), &buf,
				   dns_rootname, 0, NULL);
	CHECK("dns_name_fromtext", result);

	message = NULL;
596
	dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER, &message);
Mark Andrews's avatar
Mark Andrews committed
597 598

	message->opcode = dns_opcode_query;
599
	if (query->recurse) {
Mark Andrews's avatar
Mark Andrews committed
600
		message->flags |= DNS_MESSAGEFLAG_RD;
601 602
	}
	if (query->have_aaonly) {
Mark Andrews's avatar
Mark Andrews committed
603
		message->flags |= DNS_MESSAGEFLAG_AA;
604 605
	}
	if (query->have_adflag) {
Mark Andrews's avatar
Mark Andrews committed
606
		message->flags |= DNS_MESSAGEFLAG_AD;
607 608
	}
	if (query->have_cdflag) {
Mark Andrews's avatar
Mark Andrews committed
609
		message->flags |= DNS_MESSAGEFLAG_CD;
610 611
	}
	if (query->have_zflag) {
Mark Andrews's avatar
Mark Andrews committed
612
		message->flags |= 0x0040U;
613
	}
Mark Andrews's avatar
Mark Andrews committed
614 615 616 617 618 619 620 621 622 623 624 625 626
	message->rdclass = query->rdclass;
	message->id = (unsigned short)(random() & 0xFFFF);

	qname = NULL;
	result = dns_message_gettempname(message, &qname);
	CHECK("dns_message_gettempname", result);

	qrdataset = NULL;
	result = dns_message_gettemprdataset(message, &qrdataset);
	CHECK("dns_message_gettemprdataset", result);

	dns_name_init(qname, NULL);
	dns_name_clone(dns_fixedname_name(&queryname), qname);
627
	dns_rdataset_makequestion(qrdataset, query->rdclass, query->rdtype);
Mark Andrews's avatar
Mark Andrews committed
628 629 630
	ISC_LIST_APPEND(qname->list, qrdataset, link);
	dns_message_addname(message, qname, DNS_SECTION_QUESTION);

631
	if (query->udpsize > 0 || query->dnssec || query->edns > -1 ||
Evan Hunt's avatar
Evan Hunt committed
632 633
	    query->ecs_addr != NULL)
	{
Mark Andrews's avatar
Mark Andrews committed
634
		dns_ednsopt_t opts[EDNSOPTS + DNS_EDNSOPTIONS];
Evan Hunt's avatar
Evan Hunt committed
635 636 637
		unsigned int flags;
		int i = 0;
		char ecsbuf[20];
638
		unsigned char cookie[40];
Mark Andrews's avatar
Mark Andrews committed
639

640
		if (query->udpsize == 0) {
641
			query->udpsize = 1232;
642 643
		}
		if (query->edns < 0) {
Mark Andrews's avatar
Mark Andrews committed
644
			query->edns = 0;
645
		}
Mark Andrews's avatar
Mark Andrews committed
646 647 648 649 650 651 652 653 654 655

		if (query->nsid) {
			INSIST(i < DNS_EDNSOPTIONS);
			opts[i].code = DNS_OPT_NSID;
			opts[i].length = 0;
			opts[i].value = NULL;
			i++;
		}

		if (query->ecs_addr != NULL) {
Evan Hunt's avatar
Evan Hunt committed
656 657 658 659
			uint8_t addr[16], family;
			uint32_t plen;
			struct sockaddr *sa;
			struct sockaddr_in *sin;
Mark Andrews's avatar
Mark Andrews committed
660
			struct sockaddr_in6 *sin6;
Evan Hunt's avatar
Evan Hunt committed
661 662
			size_t addrl;
			isc_buffer_t b;
Mark Andrews's avatar
Mark Andrews committed
663 664

			sa = &query->ecs_addr->type.sa;
665
			plen = query->ecs_addr->length;
Mark Andrews's avatar
Mark Andrews committed
666 667

			/* Round up prefix len to a multiple of 8 */
668
			addrl = (plen + 7) / 8;
Mark Andrews's avatar
Mark Andrews committed
669 670 671

			INSIST(i < DNS_EDNSOPTIONS);
			opts[i].code = DNS_OPT_CLIENT_SUBNET;
672
			opts[i].length = (uint16_t)addrl + 4;
Mark Andrews's avatar
Mark Andrews committed
673 674 675
			CHECK("isc_buffer_allocate", result);
			isc_buffer_init(&b, ecsbuf, sizeof(ecsbuf));
			if (sa->sa_family == AF_INET) {
676
				family = 1;
677
				sin = (struct sockaddr_in *)sa;
Evan Hunt's avatar
Evan Hunt committed
678
				memmove(addr, &sin->sin_addr, 4);
679
				if ((plen % 8) != 0) {
680 681
					addr[addrl - 1] &= ~0U
							   << (8 - (plen % 8));
682
				}
Mark Andrews's avatar
Mark Andrews committed
683
			} else {
684
				family = 2;
685
				sin6 = (struct sockaddr_in6 *)sa;
Evan Hunt's avatar
Evan Hunt committed
686
				memmove(addr, &sin6->sin6_addr, 16);
Mark Andrews's avatar
Mark Andrews committed
687 688
			}

689
			/* Mask off last address byte */
690
			if (addrl > 0 && (plen % 8) != 0) {
691
				addr[addrl - 1] &= ~0U << (8 - (plen % 8));
692
			}
693 694 695 696 697 698 699 700

			/* family */
			isc_buffer_putuint16(&b, family);
			/* source prefix-length */
			isc_buffer_putuint8(&b, plen);
			/* scope prefix-length */
			isc_buffer_putuint8(&b, 0);
			/* address */
701
			if (addrl > 0) {
702
				isc_buffer_putmem(&b, addr, (unsigned)addrl);
703
			}
704

705
			opts[i].value = (uint8_t *)ecsbuf;
Mark Andrews's avatar
Mark Andrews committed
706 707 708
			i++;
		}

709
		if (query->send_cookie) {
Mark Andrews's avatar
Mark Andrews committed
710
			INSIST(i < DNS_EDNSOPTIONS);
711 712
			opts[i].code = DNS_OPT_COOKIE;
			if (query->cookie != NULL) {
Mark Andrews's avatar
Mark Andrews committed
713 714
				isc_buffer_t b;

715
				isc_buffer_init(&b, cookie, sizeof(cookie));
Evan Hunt's avatar
Evan Hunt committed
716 717
				result = isc_hex_decodestring(query->cookie,
							      &b);
Mark Andrews's avatar
Mark Andrews committed
718 719 720 721
				CHECK("isc_hex_decodestring", result);
				opts[i].value = isc_buffer_base(&b);
				opts[i].length = isc_buffer_usedlength(&b);
			} else {
722
				compute_cookie(cookie, 8);
Mark Andrews's avatar
Mark Andrews committed
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744
				opts[i].length = 8;
				opts[i].value = cookie;
			}
			i++;
		}

		if (query->expire) {
			INSIST(i < DNS_EDNSOPTIONS);
			opts[i].code = DNS_OPT_EXPIRE;
			opts[i].length = 0;
			opts[i].value = NULL;
			i++;
		}

		if (query->ednsoptscnt != 0) {
			memmove(&opts[i], query->ednsopts,
				sizeof(dns_ednsopt_t) * query->ednsoptscnt);
			i += query->ednsoptscnt;
		}

		flags = query->ednsflags;
		flags &= ~DNS_MESSAGEEXTFLAG_DO;
745
		if (query->dnssec) {
Mark Andrews's avatar
Mark Andrews committed
746
			flags |= DNS_MESSAGEEXTFLAG_DO;
747
		}
Mark Andrews's avatar
Mark Andrews committed
748 749 750 751
		add_opt(message, query->udpsize, query->edns, flags, opts, i);
	}

	options = 0;
752
	if (tcp_mode) {
Mark Andrews's avatar
Mark Andrews committed
753
		options |= DNS_REQUESTOPT_TCP | DNS_REQUESTOPT_SHARE;
754
	}
Mark Andrews's avatar
Mark Andrews committed
755
	request = NULL;
756 757 758 759
	result = dns_request_createvia(
		requestmgr, message, have_src ? &srcaddr : NULL, &dstaddr, dscp,
		options, NULL, query->timeout, query->udptimeout,
		query->udpretries, task, recvresponse, message, &request);
Mark Andrews's avatar
Mark Andrews committed
760 761
	CHECK("dns_request_createvia4", result);

762
	return (ISC_R_SUCCESS);
Mark Andrews's avatar
Mark Andrews committed
763 764 765
}

static void
Evan Hunt's avatar
Evan Hunt committed
766
sendqueries(isc_task_t *task, isc_event_t *event) {
Mark Andrews's avatar
Mark Andrews committed
767 768 769 770 771 772 773 774 775 776 777
	struct query *query = (struct query *)event->ev_arg;

	isc_event_free(&event);

	while (query != NULL) {
		struct query *next = ISC_LIST_NEXT(query, link);

		sendquery(query, task);
		query = next;
	}

778
	if (onfly == 0) {
Mark Andrews's avatar
Mark Andrews committed
779
		isc_app_shutdown();
780
	}
Mark Andrews's avatar
Mark Andrews committed
781 782 783
	return;
}

784 785
ISC_NORETURN static void
usage(void);
Mark Andrews's avatar
Mark Andrews committed
786 787

static void
Evan Hunt's avatar
Evan Hunt committed
788
usage(void) {
Evan Hunt's avatar
Evan Hunt committed
789 790 791 792
	fprintf(stderr, "Usage: mdig @server {global-opt} host\n"
			"           {local-opt} [ host {local-opt} [...]]\n"
			"\nUse \"mdig -h\" (or \"mdig -h | more\") "
			"for complete list of options\n");
Mark Andrews's avatar
Mark Andrews committed
793 794 795 796 797
	exit(1);
}

/*% help */
static void
Evan Hunt's avatar
Evan Hunt committed
798
help(void) {
Evan Hunt's avatar
Evan Hunt committed
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892
	printf("Usage: mdig @server {global-opt} host\n"
	       "           {local-opt} [ host {local-opt} [...]]\n"
	       "Where:\n"
	       " anywhere opt    is one of:\n"
	       "                 -f filename         (batch mode)\n"
	       "                 -h                  (print help and exit)\n"
	       "                 -v                  (print version and exit)\n"
	       " global opt      is one of:\n"
	       "                 -4                  (use IPv4 query transport "
	       "only)\n"
	       "                 -6                  (use IPv6 query transport "
	       "only)\n"
	       "                 -b address[#port]   (bind to source "
	       "address/port)\n"
	       "                 -p port             (specify port number)\n"
	       "                 -m                  (enable memory usage "
	       "debugging)\n"
	       "                 +[no]dscp[=###]     (Set the DSCP value to "
	       "### "
	       "[0..63])\n"
	       "                 +[no]vc             (TCP mode)\n"
	       "                 +[no]tcp            (TCP mode, alternate "
	       "syntax)\n"
	       "                 +[no]besteffort     (Try to parse even "
	       "illegal "
	       "messages)\n"
	       "                 +[no]cl             (Control display of class "
	       "in records)\n"
	       "                 +[no]comments       (Control display of "
	       "comment lines)\n"
	       "                 +[no]rrcomments     (Control display of "
	       "per-record "
	       "comments)\n"
	       "                 +[no]crypto         (Control display of "
	       "cryptographic "
	       "fields in records)\n"
	       "                 +[no]question       (Control display of "
	       "question)\n"
	       "                 +[no]answer         (Control display of "
	       "answer)\n"
	       "                 +[no]authority      (Control display of "
	       "authority)\n"
	       "                 +[no]additional     (Control display of "
	       "additional)\n"
	       "                 +[no]short          (Disable everything "
	       "except "
	       "short\n"
	       "                                      form of answer)\n"
	       "                 +[no]ttlid          (Control display of ttls "
	       "in records)\n"
	       "                 +[no]ttlunits       (Display TTLs in "
	       "human-readable units)\n"
	       "                 +[no]unknownformat  (Print RDATA in RFC 3597 "
	       "\"unknown\" format)\n"
	       "                 +[no]all            (Set or clear all display "
	       "flags)\n"
	       "                 +[no]multiline      (Print records in an "
	       "expanded format)\n"
	       "                 +[no]split=##       (Split hex/base64 fields "
	       "into chunks)\n"
	       " local opt       is one of:\n"
	       "                 -c class            (specify query class)\n"
	       "                 -t type             (specify query type)\n"
	       "                 -x dot-notation     (shortcut for reverse "
	       "lookups)\n"
	       "                 +timeout=###        (Set query timeout) "
	       "[UDP=5,TCP=10]\n"
	       "                 +udptimeout=###     (Set timeout before UDP "
	       "retry)\n"
	       "                 +tries=###          (Set number of UDP "
	       "attempts) [3]\n"
	       "                 +retry=###          (Set number of UDP "
	       "retries) [2]\n"
	       "                 +bufsize=###        (Set EDNS0 Max UDP packet "
	       "size)\n"
	       "                 +subnet=addr        (Set edns-client-subnet "
	       "option)\n"
	       "                 +[no]edns[=###]     (Set EDNS version) [0]\n"
	       "                 +ednsflags=###      (Set EDNS flag bits)\n"
	       "                 +ednsopt=###[:value] (Send specified EDNS "
	       "option)\n"
	       "                 +noednsopt          (Clear list of +ednsopt "
	       "options)\n"
	       "                 +[no]recurse        (Recursive mode)\n"
	       "                 +[no]aaonly         (Set AA flag in query "
	       "(+[no]aaflag))\n"
	       "                 +[no]adflag         (Set AD flag in query)\n"
	       "                 +[no]cdflag         (Set CD flag in query)\n"
	       "                 +[no]zflag          (Set Z flag in query)\n"
	       "                 +[no]dnssec         (Request DNSSEC records)\n"
	       "                 +[no]expire         (Request time to expire)\n"
	       "                 +[no]cookie[=###]   (Send a COOKIE option)\n"
	       "                 +[no]nsid           (Request Name "
	       "Server ID)\n");
Mark Andrews's avatar
Mark Andrews committed
893 894
}

895 896
ISC_NORETURN static void
fatal(const char *format, ...) ISC_FORMAT_PRINTF(1, 2);
Mark Andrews's avatar
Mark Andrews committed
897 898

static void
Evan Hunt's avatar
Evan Hunt committed
899
fatal(const char *format, ...) {
Mark Andrews's avatar
Mark Andrews committed
900 901 902 903 904 905 906 907 908 909 910 911
	va_list args;

	fflush(stdout);
	fprintf(stderr, "mdig: ");
	va_start(args, format);
	vfprintf(stderr, format, args);
	va_end(args);
	fprintf(stderr, "\n");
	exit(-2);
}

static isc_result_t
912
parse_uint_helper(uint32_t *uip, const char *value, uint32_t max,
Evan Hunt's avatar
Evan Hunt committed
913 914
		  const char *desc, int base) {
	uint32_t n;
Mark Andrews's avatar
Mark Andrews committed
915
	isc_result_t result = isc_parse_uint32(&n, value, base);
916
	if (result == ISC_R_SUCCESS && n > max) {
Mark Andrews's avatar
Mark Andrews committed
917
		result = ISC_R_RANGE;
918
	}
Mark Andrews's avatar
Mark Andrews committed
919
	if (result != ISC_R_SUCCESS) {
920 921
		printf("invalid %s '%s': %s\n", desc, value,
		       isc_result_totext(result));
Mark Andrews's avatar
Mark Andrews committed
922 923 924 925 926 927 928
		return (result);
	}
	*uip = n;
	return (ISC_R_SUCCESS);
}

static isc_result_t
Evan Hunt's avatar
Evan Hunt committed
929
parse_uint(uint32_t *uip, const char *value, uint32_t max, const char *desc) {
Mark Andrews's avatar
Mark Andrews committed
930 931 932 933
	return (parse_uint_helper(uip, value, max, desc, 10));
}

static isc_result_t
Evan Hunt's avatar
Evan Hunt committed
934
parse_xint(uint32_t *uip, const char *value, uint32_t max, const char *desc) {
Mark Andrews's avatar
Mark Andrews committed
935 936 937
	return (parse_uint_helper(uip, value, max, desc, 0));
}

938
static void
Evan Hunt's avatar
Evan Hunt committed
939
newopts(struct query *query) {
Ondřej Surý's avatar
Ondřej Surý committed
940 941 942 943 944 945 946 947 948 949
	size_t len = sizeof(query->ednsopts[0]) * EDNSOPTS;
	size_t i;

	query->ednsopts = isc_mem_allocate(mctx, len);

	for (i = 0; i < EDNSOPTS; i++) {
		query->ednsopts[i].code = 0;
		query->ednsopts[i].length = 0;
		query->ednsopts[i].value = NULL;
	}
950
}
Mark Andrews's avatar
Mark Andrews committed
951 952

static void
Evan Hunt's avatar
Evan Hunt committed
953 954
save_opt(struct query *query, char *code, char *value) {
	uint32_t num;
Mark Andrews's avatar
Mark Andrews committed
955 956 957
	isc_buffer_t b;
	isc_result_t result;

958 959 960 961 962
	if (query->ednsopts == NULL) {
		newopts(query);
	}

	if (query->ednsoptscnt == EDNSOPTS) {
Mark Andrews's avatar
Mark Andrews committed
963
		fatal("too many ednsopts");
964
	}
Mark Andrews's avatar
Mark Andrews committed
965 966

	result = parse_uint(&num, code, 65535, "ednsopt");
967
	if (result != ISC_R_SUCCESS) {
Mark Andrews's avatar
Mark Andrews committed
968
		fatal("bad edns code point: %s", code);
969
	}
Mark Andrews's avatar
Mark Andrews committed
970

971 972 973
	query->ednsopts[query->ednsoptscnt].code = num;
	query->ednsopts[query->ednsoptscnt].length = 0;
	query->ednsopts[query->ednsoptscnt].value = NULL;
Mark Andrews's avatar
Mark Andrews committed
974 975 976

	if (value != NULL) {
		char *buf;
977
		buf = isc_mem_allocate(mctx, strlen(value) / 2 + 1);
978
		isc_buffer_init(&b, buf, strlen(value) / 2 + 1);
Mark Andrews's avatar