2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-22 18:19:42 +00:00
bind/bin/dig/dig.c
Evan Hunt e12dc1faa2 dig: setup IDN whenever printing a message
because dig now uses the netmgr, printing of response messages
happens in a different thread than setup. the IDN output filtering
procedure, which set using dns_name_settotextfilter(), is stored as
thread-local data, and so if it's set during setup, it won't be
accessible when printing. we now set it immediately before printing,
in the same thread, and clear it immedately afterward.
2020-11-07 20:49:53 +01:00

2741 lines
68 KiB
C

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* 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
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
/*! \file */
#include <ctype.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <isc/app.h>
#include <isc/attributes.h>
#include <isc/netaddr.h>
#include <isc/parseint.h>
#include <isc/platform.h>
#include <isc/print.h>
#include <isc/string.h>
#include <isc/task.h>
#include <isc/util.h>
#include <pk11/site.h>
#include <dns/byaddr.h>
#include <dns/fixedname.h>
#include <dns/masterdump.h>
#include <dns/message.h>
#include <dns/name.h>
#include <dns/rcode.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rdataset.h>
#include <dns/rdatatype.h>
#include <dns/result.h>
#include <dns/tsig.h>
#include "dighost.h"
#define ADD_STRING(b, s) \
{ \
if (strlen(s) >= isc_buffer_availablelength(b)) { \
return ((((ISC_R_NOSPACE)))); \
} else { \
isc_buffer_putstr(b, s); \
} \
}
#define DIG_MAX_ADDRESSES 20
dig_lookup_t *default_lookup = NULL;
static atomic_uintptr_t batchname = ATOMIC_VAR_INIT(0);
static FILE *batchfp = NULL;
static char *argv0;
static int addresscount = 0;
static char domainopt[DNS_NAME_MAXTEXT];
static char hexcookie[81];
static bool short_form = false, printcmd = true, plusquest = false,
pluscomm = false, ipv4only = false, ipv6only = false, digrc = true;
static uint32_t splitwidth = 0xffffffff;
/*% opcode text */
static const char *const opcodetext[] = {
"QUERY", "IQUERY", "STATUS", "RESERVED3",
"NOTIFY", "UPDATE", "RESERVED6", "RESERVED7",
"RESERVED8", "RESERVED9", "RESERVED10", "RESERVED11",
"RESERVED12", "RESERVED13", "RESERVED14", "RESERVED15"
};
static const char *
rcode_totext(dns_rcode_t rcode) {
static char buf[64];
isc_buffer_t b;
isc_result_t result;
memset(buf, 0, sizeof(buf));
isc_buffer_init(&b, buf + 1, sizeof(buf) - 2);
result = dns_rcode_totext(rcode, &b);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
if (strspn(buf + 1, "0123456789") == strlen(buf + 1)) {
buf[0] = '?';
return (buf);
}
return (buf + 1);
}
/*% print usage */
static void
print_usage(FILE *fp) {
fprintf(fp,
"Usage: dig [@global-server] [domain] [q-type] [q-class] "
"{q-opt}\n"
" {global-d-opt} host [@local-server] "
"{local-d-opt}\n"
" [ host [@local-server] {local-d-opt} [...]]\n");
}
#if TARGET_OS_IPHONE
static void
usage(void) {
fprintf(stderr, "Press <Help> for complete list of options\n");
}
#else /* if TARGET_OS_IPHONE */
ISC_NORETURN static void
usage(void);
static void
usage(void) {
print_usage(stderr);
fprintf(stderr, "\nUse \"dig -h\" (or \"dig -h | more\") "
"for complete list of options\n");
exit(1);
}
#endif /* if TARGET_OS_IPHONE */
/*% version */
static void
version(void) {
fprintf(stderr, "DiG %s\n", PACKAGE_VERSION);
}
/*% help */
static void
help(void) {
print_usage(stdout);
printf("Where: domain is in the Domain Name System\n"
" q-class is one of (in,hs,ch,...) [default: in]\n"
" q-type is one of "
"(a,any,mx,ns,soa,hinfo,axfr,txt,...) "
"[default:a]\n"
" (Use ixfr=version for type ixfr)\n"
" q-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"
" -c class (specify query class)\n"
" -f filename (batch mode)\n"
" -k keyfile (specify tsig key file)\n"
" -m (enable memory usage "
"debugging)\n"
" -p port (specify port number)\n"
" -q name (specify query name)\n"
" -r (do not read ~/.digrc)\n"
" -t type (specify query type)\n"
" -u (display times in usec "
"instead of msec)\n"
" -x dot-notation (shortcut for reverse "
"lookups)\n"
" -y [hmac:]name:key (specify named base64 "
"tsig "
"key)\n"
" d-opt is of the form +keyword[=value], where "
"keyword "
"is:\n"
" +[no]aaflag (Set AA flag in query "
"(+[no]aaflag))\n"
" +[no]aaonly (Set AA flag in query "
"(+[no]aaflag))\n"
" +[no]additional (Control display of "
"additional section)\n"
" +[no]adflag (Set AD flag in query "
"(default on))\n"
" +[no]all (Set or clear all display "
"flags)\n"
" +[no]answer (Control display of "
"answer "
"section)\n"
" +[no]authority (Control display of "
"authority section)\n"
" +[no]badcookie (Retry BADCOOKIE "
"responses)\n"
" +[no]besteffort (Try to parse even "
"illegal "
"messages)\n"
" +bufsize[=###] (Set EDNS0 Max UDP packet "
"size)\n"
" +[no]cdflag (Set checking disabled "
"flag in query)\n"
" +[no]class (Control display of class "
"in records)\n"
" +[no]cmd (Control display of "
"command line -\n"
" global option)\n"
" +[no]comments (Control display of "
"packet "
"header\n"
" and section name "
"comments)\n"
" +[no]cookie (Add a COOKIE option to "
"the request)\n"
" +[no]crypto (Control display of "
"cryptographic\n"
" fields in records)\n"
" +[no]defname (Use search list "
"(+[no]search))\n"
" +[no]dnssec (Request DNSSEC records)\n"
" +domain=### (Set default domainname)\n"
" +[no]dscp[=###] (Set the DSCP value to "
"### "
"[0..63])\n"
" +[no]edns[=###] (Set EDNS version) [0]\n"
" +ednsflags=### (Set EDNS flag bits)\n"
" +[no]ednsnegotiation (Set EDNS version "
"negotiation)\n"
" +ednsopt=###[:value] (Send specified EDNS "
"option)\n"
" +noednsopt (Clear list of +ednsopt "
"options)\n"
" +[no]expandaaaa (Expand AAAA records)\n"
" +[no]expire (Request time to expire)\n"
" +[no]fail (Don't try next server on "
"SERVFAIL)\n"
" +[no]header-only (Send query without a "
"question section)\n"
" +[no]identify (ID responders in short "
"answers)\n"
#ifdef HAVE_LIBIDN2
" +[no]idnin (Parse IDN names "
"[default=on on tty])\n"
" +[no]idnout (Convert IDN response "
"[default=on on tty])\n"
#endif /* ifdef HAVE_LIBIDN2 */
" +[no]ignore (Don't revert to TCP for "
"TC responses.)\n"
" +[no]keepalive (Request EDNS TCP "
"keepalive)\n"
" +[no]keepopen (Keep the TCP socket open "
"between "
"queries)\n"
" +[no]mapped (Allow mapped IPv4 over "
"IPv6)\n"
" +[no]multiline (Print records in an "
"expanded format)\n"
" +ndots=### (Set search NDOTS value)\n"
" +[no]nsid (Request Name Server ID)\n"
" +[no]nssearch (Search all authoritative "
"nameservers)\n"
" +[no]onesoa (AXFR prints only one soa "
"record)\n"
" +[no]opcode=### (Set the opcode of the "
"request)\n"
" +padding=### (Set padding block size "
"[0])\n"
" +[no]qr (Print question before "
"sending)\n"
" +[no]question (Control display of "
"question section)\n"
" +[no]raflag (Set RA flag in query "
"(+[no]raflag))\n"
" +[no]rdflag (Recursive mode "
"(+[no]recurse))\n"
" +[no]recurse (Recursive mode "
"(+[no]rdflag))\n"
" +retry=### (Set number of UDP "
"retries) [2]\n"
" +[no]rrcomments (Control display of "
"per-record "
"comments)\n"
" +[no]search (Set whether to use "
"searchlist)\n"
" +[no]short (Display nothing except "
"short\n"
" form of answers - global "
"option)\n"
" +[no]showsearch (Search with intermediate "
"results)\n"
" +[no]split=## (Split hex/base64 fields "
"into chunks)\n"
" +[no]stats (Control display of "
"statistics)\n"
" +subnet=addr (Set edns-client-subnet "
"option)\n"
" +[no]tcflag (Set TC flag in query "
"(+[no]tcflag))\n"
" +[no]tcp (TCP mode (+[no]vc))\n"
" +timeout=### (Set query timeout) [5]\n"
" +[no]trace (Trace delegation down "
"from root "
"[+dnssec])\n"
" +tries=### (Set number of UDP "
"attempts) [3]\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]vc (TCP mode (+[no]tcp))\n"
" +[no]yaml (Present the results as "
"YAML)\n"
" +[no]zflag (Set Z flag in query)\n"
" global d-opts and servers (before host name) affect "
"all "
"queries.\n"
" local d-opts and servers (after host name) affect only "
"that lookup.\n"
" -h (print help and exit)\n"
" -v (print version "
"and exit)\n");
}
/*%
* Callback from dighost.c to print the received message.
*/
static void
received(unsigned int bytes, isc_sockaddr_t *from, dig_query_t *query) {
uint64_t diff;
time_t tnow;
struct tm tmnow;
#ifdef WIN32
wchar_t time_str[100];
#else /* ifdef WIN32 */
char time_str[100];
#endif /* ifdef WIN32 */
char fromtext[ISC_SOCKADDR_FORMATSIZE];
isc_sockaddr_format(from, fromtext, sizeof(fromtext));
if (short_form || yaml) {
return;
}
if (query->lookup->stats) {
diff = isc_time_microdiff(&query->time_recv, &query->time_sent);
if (query->lookup->use_usec) {
printf(";; Query time: %ld usec\n", (long)diff);
} else {
printf(";; Query time: %ld msec\n", (long)diff / 1000);
}
printf(";; SERVER: %s(%s)\n", fromtext, query->servname);
time(&tnow);
(void)localtime_r(&tnow, &tmnow);
#ifdef WIN32
/*
* On Windows, time zone name ("%Z") may be a localized
* wide-character string, which strftime() handles incorrectly.
*/
if (wcsftime(time_str, sizeof(time_str) / sizeof(time_str[0]),
L"%a %b %d %H:%M:%S %Z %Y", &tmnow) > 0U)
{
printf(";; WHEN: %ls\n", time_str);
}
#else /* ifdef WIN32 */
if (strftime(time_str, sizeof(time_str),
"%a %b %d %H:%M:%S %Z %Y", &tmnow) > 0U)
{
printf(";; WHEN: %s\n", time_str);
}
#endif /* ifdef WIN32 */
if (query->lookup->doing_xfr) {
printf(";; XFR size: %u records (messages %u, "
"bytes %" PRIu64 ")\n",
query->rr_count, query->msg_count,
query->byte_count);
} else {
printf(";; MSG SIZE rcvd: %u\n", bytes);
}
if (tsigkey != NULL) {
if (!validated) {
puts(";; WARNING -- Some TSIG could not "
"be validated");
}
}
if ((tsigkey == NULL) && (keysecret[0] != 0)) {
puts(";; WARNING -- TSIG key was not used.");
}
puts("");
} else if (query->lookup->identify) {
diff = isc_time_microdiff(&query->time_recv, &query->time_sent);
if (query->lookup->use_usec) {
printf(";; Received %" PRIu64 " bytes "
"from %s(%s) in %ld us\n\n",
query->lookup->doing_xfr ? query->byte_count
: (uint64_t)bytes,
fromtext, query->userarg, (long)diff);
} else {
printf(";; Received %" PRIu64 " bytes "
"from %s(%s) in %ld ms\n\n",
query->lookup->doing_xfr ? query->byte_count
: (uint64_t)bytes,
fromtext, query->userarg, (long)diff / 1000);
}
}
}
/*
* Callback from dighost.c to print that it is trying a server.
* Not used in dig.
* XXX print_trying
*/
static void
trying(char *frm, dig_lookup_t *lookup) {
UNUSED(frm);
UNUSED(lookup);
}
/*%
* Internal print routine used to print short form replies.
*/
static isc_result_t
say_message(dns_rdata_t *rdata, dig_query_t *query, isc_buffer_t *buf) {
isc_result_t result;
uint64_t diff;
char store[sizeof(" in 18446744073709551616 us.")];
unsigned int styleflags = 0;
if (query->lookup->trace || query->lookup->ns_search_only) {
result = dns_rdatatype_totext(rdata->type, buf);
if (result != ISC_R_SUCCESS) {
return (result);
}
ADD_STRING(buf, " ");
}
/* Turn on rrcomments if explicitly enabled */
if (query->lookup->rrcomments > 0) {
styleflags |= DNS_STYLEFLAG_RRCOMMENT;
}
if (query->lookup->nocrypto) {
styleflags |= DNS_STYLEFLAG_NOCRYPTO;
}
if (query->lookup->print_unknown_format) {
styleflags |= DNS_STYLEFLAG_UNKNOWNFORMAT;
}
if (query->lookup->expandaaaa) {
styleflags |= DNS_STYLEFLAG_EXPANDAAAA;
}
result = dns_rdata_tofmttext(rdata, NULL, styleflags, 0, splitwidth,
" ", buf);
if (result == ISC_R_NOSPACE) {
return (result);
}
check_result(result, "dns_rdata_totext");
if (query->lookup->identify) {
diff = isc_time_microdiff(&query->time_recv, &query->time_sent);
ADD_STRING(buf, " from server ");
ADD_STRING(buf, query->servname);
if (query->lookup->use_usec) {
snprintf(store, sizeof(store), " in %" PRIu64 " us.",
diff);
} else {
snprintf(store, sizeof(store), " in %" PRIu64 " ms.",
diff / 1000);
}
ADD_STRING(buf, store);
}
ADD_STRING(buf, "\n");
return (ISC_R_SUCCESS);
}
/*%
* short_form message print handler. Calls above say_message()
*/
static isc_result_t
short_answer(dns_message_t *msg, dns_messagetextflag_t flags, isc_buffer_t *buf,
dig_query_t *query) {
dns_name_t *name;
dns_rdataset_t *rdataset;
isc_result_t result, loopresult;
dns_name_t empty_name;
dns_rdata_t rdata = DNS_RDATA_INIT;
UNUSED(flags);
dns_name_init(&empty_name, NULL);
result = dns_message_firstname(msg, DNS_SECTION_ANSWER);
if (result == ISC_R_NOMORE) {
return (ISC_R_SUCCESS);
} else if (result != ISC_R_SUCCESS) {
return (result);
}
for (;;) {
name = NULL;
dns_message_currentname(msg, DNS_SECTION_ANSWER, &name);
for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link))
{
loopresult = dns_rdataset_first(rdataset);
while (loopresult == ISC_R_SUCCESS) {
dns_rdataset_current(rdataset, &rdata);
result = say_message(&rdata, query, buf);
if (result == ISC_R_NOSPACE) {
return (result);
}
check_result(result, "say_message");
loopresult = dns_rdataset_next(rdataset);
dns_rdata_reset(&rdata);
}
}
result = dns_message_nextname(msg, DNS_SECTION_ANSWER);
if (result == ISC_R_NOMORE) {
break;
} else if (result != ISC_R_SUCCESS) {
return (result);
}
}
return (ISC_R_SUCCESS);
}
static bool
isdotlocal(dns_message_t *msg) {
isc_result_t result;
static unsigned char local_ndata[] = { "\005local\0" };
static unsigned char local_offsets[] = { 0, 6 };
static dns_name_t local = DNS_NAME_INITABSOLUTE(local_ndata,
local_offsets);
for (result = dns_message_firstname(msg, DNS_SECTION_QUESTION);
result == ISC_R_SUCCESS;
result = dns_message_nextname(msg, DNS_SECTION_QUESTION))
{
dns_name_t *name = NULL;
dns_message_currentname(msg, DNS_SECTION_QUESTION, &name);
if (dns_name_issubdomain(name, &local)) {
return (true);
}
}
return (false);
}
/*
* Callback from dighost.c to print the reply from a server
*/
static isc_result_t
printmessage(dig_query_t *query, const isc_buffer_t *msgbuf, dns_message_t *msg,
bool headers) {
isc_result_t result;
dns_messagetextflag_t flags;
isc_buffer_t *buf = NULL;
unsigned int len = OUTPUTBUF;
dns_master_style_t *style = NULL;
unsigned int styleflags = 0;
bool isquery = (msg == query->lookup->sendmsg);
UNUSED(msgbuf);
dig_idnsetup(query->lookup, true);
styleflags |= DNS_STYLEFLAG_REL_OWNER;
if (yaml) {
msg->indent.string = " ";
msg->indent.count = 3;
styleflags |= DNS_STYLEFLAG_YAML;
} else {
if (query->lookup->comments) {
styleflags |= DNS_STYLEFLAG_COMMENT;
}
if (query->lookup->print_unknown_format) {
styleflags |= DNS_STYLEFLAG_UNKNOWNFORMAT;
}
/* Turn on rrcomments if explicitly enabled */
if (query->lookup->rrcomments > 0) {
styleflags |= DNS_STYLEFLAG_RRCOMMENT;
}
if (query->lookup->ttlunits) {
styleflags |= DNS_STYLEFLAG_TTL_UNITS;
}
if (query->lookup->nottl) {
styleflags |= DNS_STYLEFLAG_NO_TTL;
}
if (query->lookup->noclass) {
styleflags |= DNS_STYLEFLAG_NO_CLASS;
}
if (query->lookup->nocrypto) {
styleflags |= DNS_STYLEFLAG_NOCRYPTO;
}
if (query->lookup->expandaaaa) {
styleflags |= DNS_STYLEFLAG_EXPANDAAAA;
}
if (query->lookup->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;
/* Turn on rrcomments unless explicitly disabled */
if (query->lookup->rrcomments >= 0) {
styleflags |= DNS_STYLEFLAG_RRCOMMENT;
}
}
}
if (query->lookup->multiline ||
(query->lookup->nottl && query->lookup->noclass))
{
result = dns_master_stylecreate(&style, styleflags, 24, 24, 24,
32, 80, 8, splitwidth, mctx);
} else if (query->lookup->nottl || query->lookup->noclass) {
result = dns_master_stylecreate(&style, styleflags, 24, 24, 32,
40, 80, 8, splitwidth, mctx);
} else {
result = dns_master_stylecreate(&style, styleflags, 24, 32, 40,
48, 80, 8, splitwidth, mctx);
}
check_result(result, "dns_master_stylecreate");
if (query->lookup->cmdline[0] != 0) {
if (!short_form && printcmd) {
printf("%s", query->lookup->cmdline);
}
query->lookup->cmdline[0] = '\0';
}
debug("printmessage(%s %s %s)", headers ? "headers" : "noheaders",
query->lookup->comments ? "comments" : "nocomments",
short_form ? "short_form" : "long_form");
flags = 0;
if (!headers) {
flags |= DNS_MESSAGETEXTFLAG_NOHEADERS;
flags |= DNS_MESSAGETEXTFLAG_NOCOMMENTS;
}
if (query->lookup->onesoa &&
query->lookup->rdtype == dns_rdatatype_axfr) {
flags |= (query->msg_count == 0) ? DNS_MESSAGETEXTFLAG_ONESOA
: DNS_MESSAGETEXTFLAG_OMITSOA;
}
if (!query->lookup->comments) {
flags |= DNS_MESSAGETEXTFLAG_NOCOMMENTS;
}
isc_buffer_allocate(mctx, &buf, len);
if (yaml) {
enum { Q = 0x1, R = 0x2 }; /* Q:query; R:ecursive */
unsigned int tflag = 0;
char sockstr[ISC_SOCKADDR_FORMATSIZE];
uint16_t sport;
char *hash;
int pf;
printf("-\n");
printf(" type: MESSAGE\n");
printf(" message:\n");
if (isquery) {
tflag |= Q;
if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0) {
tflag |= R;
}
} else if (((msg->flags & DNS_MESSAGEFLAG_RD) != 0) &&
((msg->flags & DNS_MESSAGEFLAG_RA) != 0))
{
tflag |= R;
}
if (tflag == (Q | R)) {
printf(" type: RECURSIVE_QUERY\n");
} else if (tflag == Q) {
printf(" type: AUTH_QUERY\n");
} else if (tflag == R) {
printf(" type: RECURSIVE_RESPONSE\n");
} else {
printf(" type: AUTH_RESPONSE\n");
}
if (!isc_time_isepoch(&query->time_sent)) {
char tbuf[100];
if (query->lookup->use_usec) {
isc_time_formatISO8601us(&query->time_sent,
tbuf, sizeof(tbuf));
} else {
isc_time_formatISO8601ms(&query->time_sent,
tbuf, sizeof(tbuf));
}
printf(" query_time: !!timestamp %s\n", tbuf);
}
if (!isquery && !isc_time_isepoch(&query->time_recv)) {
char tbuf[100];
if (query->lookup->use_usec) {
isc_time_formatISO8601us(&query->time_recv,
tbuf, sizeof(tbuf));
} else {
isc_time_formatISO8601ms(&query->time_recv,
tbuf, sizeof(tbuf));
}
printf(" response_time: !!timestamp %s\n", tbuf);
}
printf(" message_size: %ub\n",
isc_buffer_usedlength(msgbuf));
pf = isc_sockaddr_pf(&query->sockaddr);
if (pf == PF_INET || pf == PF_INET6) {
printf(" socket_family: %s\n",
pf == PF_INET ? "INET" : "INET6");
printf(" socket_protocol: %s\n",
query->lookup->tcp_mode ? "TCP" : "UDP");
sport = isc_sockaddr_getport(&query->sockaddr);
isc_sockaddr_format(&query->sockaddr, sockstr,
sizeof(sockstr));
hash = strchr(sockstr, '#');
if (hash != NULL) {
*hash = '\0';
}
if (strcmp(sockstr, "::") == 0) {
strlcat(sockstr, "0", sizeof(sockstr));
}
printf(" response_address: %s\n", sockstr);
printf(" response_port: %u\n", sport);
}
if (query->handle != NULL) {
isc_sockaddr_t saddr =
isc_nmhandle_localaddr(query->handle);
sport = isc_sockaddr_getport(&saddr);
isc_sockaddr_format(&saddr, sockstr, sizeof(sockstr));
hash = strchr(sockstr, '#');
if (hash != NULL) {
*hash = '\0';
}
if (strcmp(sockstr, "::") == 0) {
strlcat(sockstr, "0", sizeof(sockstr));
}
printf(" query_address: %s\n", sockstr);
printf(" query_port: %u\n", sport);
}
printf(" %s:\n", isquery ? "query_message_data"
: "response_message_data");
result = dns_message_headertotext(msg, style, flags, buf);
} else if (query->lookup->comments && !short_form) {
if (query->lookup->cmdline[0] != '\0' && printcmd) {
printf("; %s\n", query->lookup->cmdline);
}
if (msg == query->lookup->sendmsg) {
printf(";; Sending:\n");
} else {
printf(";; Got answer:\n");
}
if (headers) {
if (isdotlocal(msg)) {
printf(";; WARNING: .local is reserved for "
"Multicast DNS\n;; You are currently "
"testing what happens when an mDNS "
"query is leaked to DNS\n");
}
printf(";; ->>HEADER<<- opcode: %s, status: %s, "
"id: %u\n",
opcodetext[msg->opcode],
rcode_totext(msg->rcode), msg->id);
printf(";; flags:");
if ((msg->flags & DNS_MESSAGEFLAG_QR) != 0) {
printf(" qr");
}
if ((msg->flags & DNS_MESSAGEFLAG_AA) != 0) {
printf(" aa");
}
if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) {
printf(" tc");
}
if ((msg->flags & DNS_MESSAGEFLAG_RD) != 0) {
printf(" rd");
}
if ((msg->flags & DNS_MESSAGEFLAG_RA) != 0) {
printf(" ra");
}
if ((msg->flags & DNS_MESSAGEFLAG_AD) != 0) {
printf(" ad");
}
if ((msg->flags & DNS_MESSAGEFLAG_CD) != 0) {
printf(" cd");
}
if ((msg->flags & 0x0040U) != 0) {
printf("; MBZ: 0x4");
}
printf("; QUERY: %u, ANSWER: %u, "
"AUTHORITY: %u, ADDITIONAL: %u\n",
msg->counts[DNS_SECTION_QUESTION],
msg->counts[DNS_SECTION_ANSWER],
msg->counts[DNS_SECTION_AUTHORITY],
msg->counts[DNS_SECTION_ADDITIONAL]);
if (msg != query->lookup->sendmsg &&
(msg->flags & DNS_MESSAGEFLAG_RD) != 0 &&
(msg->flags & DNS_MESSAGEFLAG_RA) == 0)
{
printf(";; WARNING: recursion requested "
"but not available\n");
}
}
if (msg != query->lookup->sendmsg &&
query->lookup->edns != -1 && msg->opt == NULL &&
(msg->rcode == dns_rcode_formerr ||
msg->rcode == dns_rcode_notimp))
{
printf("\n;; WARNING: EDNS query returned status "
"%s - retry with '%s+noedns'\n",
rcode_totext(msg->rcode),
query->lookup->dnssec ? "+nodnssec " : "");
}
if (msg != query->lookup->sendmsg && extrabytes != 0U) {
printf(";; WARNING: Message has %u extra byte%s at "
"end\n",
extrabytes, extrabytes != 0 ? "s" : "");
}
}
repopulate_buffer:
if (query->lookup->comments && headers && !short_form) {
result = dns_message_pseudosectiontotext(
msg, DNS_PSEUDOSECTION_OPT, style, flags, buf);
if (result == ISC_R_NOSPACE) {
buftoosmall:
len += OUTPUTBUF;
isc_buffer_free(&buf);
isc_buffer_allocate(mctx, &buf, len);
goto repopulate_buffer;
}
check_result(result, "dns_message_pseudosectiontotext");
}
if (query->lookup->section_question && headers) {
if (!short_form) {
result = dns_message_sectiontotext(
msg, DNS_SECTION_QUESTION, style, flags, buf);
if (result == ISC_R_NOSPACE) {
goto buftoosmall;
}
check_result(result, "dns_message_sectiontotext");
}
}
if (query->lookup->section_answer) {
if (!short_form) {
result = dns_message_sectiontotext(
msg, DNS_SECTION_ANSWER, style, flags, buf);
if (result == ISC_R_NOSPACE) {
goto buftoosmall;
}
check_result(result, "dns_message_sectiontotext");
} else {
result = short_answer(msg, flags, buf, query);
if (result == ISC_R_NOSPACE) {
goto buftoosmall;
}
check_result(result, "short_answer");
}
}
if (query->lookup->section_authority) {
if (!short_form) {
result = dns_message_sectiontotext(
msg, DNS_SECTION_AUTHORITY, style, flags, buf);
if (result == ISC_R_NOSPACE) {
goto buftoosmall;
}
check_result(result, "dns_message_sectiontotext");
}
}
if (query->lookup->section_additional) {
if (!short_form) {
result = dns_message_sectiontotext(
msg, DNS_SECTION_ADDITIONAL, style, flags, buf);
if (result == ISC_R_NOSPACE) {
goto buftoosmall;
}
check_result(result, "dns_message_sectiontotext");
/*
* Only print the signature on the first record.
*/
if (headers) {
result = dns_message_pseudosectiontotext(
msg, DNS_PSEUDOSECTION_TSIG, style,
flags, buf);
if (result == ISC_R_NOSPACE) {
goto buftoosmall;
}
check_result(result, "dns_message_"
"pseudosectiontotext");
result = dns_message_pseudosectiontotext(
msg, DNS_PSEUDOSECTION_SIG0, style,
flags, buf);
if (result == ISC_R_NOSPACE) {
goto buftoosmall;
}
check_result(result, "dns_message_"
"pseudosectiontotext");
}
}
}
if (headers && query->lookup->comments && !short_form && !yaml) {
printf("\n");
}
printf("%.*s", (int)isc_buffer_usedlength(buf),
(char *)isc_buffer_base(buf));
isc_buffer_free(&buf);
if (style != NULL) {
dns_master_styledestroy(&style, mctx);
}
dig_idnsetup(query->lookup, false);
return (result);
}
/*%
* print the greeting message when the program first starts up.
*/
static void
printgreeting(int argc, char **argv, dig_lookup_t *lookup) {
int i;
static bool first = true;
char append[MXNAME];
if (printcmd) {
snprintf(lookup->cmdline, sizeof(lookup->cmdline),
"%s; <<>> DiG %s <<>>", first ? "\n" : "",
PACKAGE_VERSION);
i = 1;
while (i < argc) {
snprintf(append, sizeof(append), " %s", argv[i++]);
strlcat(lookup->cmdline, append,
sizeof(lookup->cmdline));
}
strlcat(lookup->cmdline, "\n", sizeof(lookup->cmdline));
if (first && addresscount != 0) {
snprintf(append, sizeof(append),
"; (%d server%s found)\n", addresscount,
addresscount > 1 ? "s" : "");
strlcat(lookup->cmdline, append,
sizeof(lookup->cmdline));
}
if (first) {
snprintf(append, sizeof(append),
";; global options:%s%s\n",
short_form ? " +short" : "",
printcmd ? " +cmd" : "");
first = false;
strlcat(lookup->cmdline, append,
sizeof(lookup->cmdline));
}
}
}
/*%
* We're not using isc_commandline_parse() here since the command line
* syntax of dig is quite a bit different from that which can be described
* by that routine.
* XXX doc options
*/
static void
plus_option(char *option, bool is_batchfile, dig_lookup_t *lookup) {
isc_result_t result;
char *cmd, *value, *last = NULL, *code, *extra;
uint32_t num;
bool state = true;
size_t n;
INSIST(option != NULL);
if ((cmd = strtok_r(option, "=", &last)) == NULL) {
printf(";; Invalid option %s\n", option);
return;
}
if (strncasecmp(cmd, "no", 2) == 0) {
cmd += 2;
state = false;
}
/* parse the rest of the string */
value = strtok_r(NULL, "", &last);
#define FULLCHECK(A) \
do { \
size_t _l = strlen(cmd); \
if (_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) \
goto invalid_option; \
} while (0)
#define FULLCHECK2(A, B) \
do { \
size_t _l = strlen(cmd); \
if ((_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) && \
(_l >= sizeof(B) || strncasecmp(cmd, B, _l) != 0)) \
goto invalid_option; \
} while (0)
switch (cmd[0]) {
case 'a':
switch (cmd[1]) {
case 'a': /* aaonly / aaflag */
FULLCHECK2("aaonly", "aaflag");
lookup->aaonly = state;
break;
case 'd':
switch (cmd[2]) {
case 'd': /* additional */
FULLCHECK("additional");
lookup->section_additional = state;
break;
case 'f': /* adflag */
case '\0': /* +ad is a synonym for +adflag */
FULLCHECK("adflag");
lookup->adflag = state;
break;
default:
goto invalid_option;
}
break;
case 'l': /* all */
FULLCHECK("all");
lookup->section_question = state;
lookup->section_authority = state;
lookup->section_answer = state;
lookup->section_additional = state;
lookup->comments = state;
lookup->stats = state;
printcmd = state;
break;
case 'n': /* answer */
FULLCHECK("answer");
lookup->section_answer = state;
break;
case 'u': /* authority */
FULLCHECK("authority");
lookup->section_authority = state;
break;
default:
goto invalid_option;
}
break;
case 'b':
switch (cmd[1]) {
case 'a': /* badcookie */
FULLCHECK("badcookie");
lookup->badcookie = state;
break;
case 'e': /* besteffort */
FULLCHECK("besteffort");
lookup->besteffort = state;
break;
case 'u': /* bufsize */
FULLCHECK("bufsize");
if (!state) {
goto invalid_option;
}
if (value == NULL) {
lookup->udpsize = DEFAULT_EDNS_BUFSIZE;
break;
}
result = parse_uint(&num, value, COMMSIZE,
"buffer size");
if (result != ISC_R_SUCCESS) {
warn("Couldn't parse buffer size");
goto exit_or_usage;
}
lookup->udpsize = num;
break;
default:
goto invalid_option;
}
break;
case 'c':
switch (cmd[1]) {
case 'd': /* cdflag */
switch (cmd[2]) {
case 'f': /* cdflag */
case '\0': /* +cd is a synonym for +cdflag */
FULLCHECK("cdflag");
lookup->cdflag = state;
break;
default:
goto invalid_option;
}
break;
case 'l': /* class */
/* keep +cl for backwards compatibility */
FULLCHECK2("cl", "class");
lookup->noclass = !state;
break;
case 'm': /* cmd */
FULLCHECK("cmd");
printcmd = state;
break;
case 'o': /* comments */
switch (cmd[2]) {
case 'm':
FULLCHECK("comments");
lookup->comments = state;
if (lookup == default_lookup) {
pluscomm = state;
}
break;
case 'o': /* cookie */
FULLCHECK("cookie");
if (state && lookup->edns == -1) {
lookup->edns = DEFAULT_EDNS_VERSION;
}
lookup->sendcookie = state;
if (value != NULL) {
n = strlcpy(hexcookie, value,
sizeof(hexcookie));
if (n >= sizeof(hexcookie)) {
warn("COOKIE data too large");
goto exit_or_usage;
}
lookup->cookie = hexcookie;
} else {
lookup->cookie = NULL;
}
break;
default:
goto invalid_option;
}
break;
case 'r':
FULLCHECK("crypto");
lookup->nocrypto = !state;
break;
default:
goto invalid_option;
}
break;
case 'd':
switch (cmd[1]) {
case 'e': /* defname */
FULLCHECK("defname");
if (!lookup->trace) {
usesearch = state;
}
break;
case 'n': /* dnssec */
FULLCHECK("dnssec");
dnssec:
if (state && lookup->edns == -1) {
lookup->edns = DEFAULT_EDNS_VERSION;
}
lookup->dnssec = state;
break;
case 'o': /* domain ... but treat "do" as synonym for dnssec */
if (cmd[2] == '\0') {
goto dnssec;
}
FULLCHECK("domain");
if (value == NULL) {
goto need_value;
}
if (!state) {
goto invalid_option;
}
strlcpy(domainopt, value, sizeof(domainopt));
break;
case 's': /* dscp */
FULLCHECK("dscp");
if (!state) {
lookup->dscp = -1;
break;
}
if (value == NULL) {
goto need_value;
}
result = parse_uint(&num, value, 0x3f, "DSCP");
if (result != ISC_R_SUCCESS) {
warn("Couldn't parse DSCP value");
goto exit_or_usage;
}
lookup->dscp = num;
break;
default:
goto invalid_option;
}
break;
case 'e':
switch (cmd[1]) {
case 'd':
switch (cmd[2]) {
case 'n':
switch (cmd[3]) {
case 's':
switch (cmd[4]) {
case 0:
FULLCHECK("edns");
if (!state) {
lookup->edns = -1;
break;
}
if (value == NULL) {
lookup->edns =
DEFAULT_EDNS_VERSION;
break;
}
result = parse_uint(&num, value,
255,
"edns");
if (result != ISC_R_SUCCESS) {
warn("Couldn't parse "
"edns");
goto exit_or_usage;
}
lookup->edns = num;
break;
case 'f':
FULLCHECK("ednsflags");
if (!state) {
lookup->ednsflags = 0;
break;
}
if (value == NULL) {
lookup->ednsflags = 0;
break;
}
result = parse_xint(
&num, value, 0xffff,
"ednsflags");
if (result != ISC_R_SUCCESS) {
warn("Couldn't parse "
"ednsflags");
goto exit_or_usage;
}
lookup->ednsflags = num;
break;
case 'n':
FULLCHECK("ednsnegotiation");
lookup->ednsneg = state;
break;
case 'o':
FULLCHECK("ednsopt");
if (!state) {
lookup->ednsoptscnt = 0;
break;
}
code = NULL;
if (value != NULL) {
code = strtok_r(value,
":",
&last);
}
if (code == NULL) {
warn("ednsopt no "
"code point "
"specified");
goto exit_or_usage;
}
extra = strtok_r(NULL, "\0",
&last);
save_opt(lookup, code, extra);
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
case 'x':
switch (cmd[2]) {
case 'p':
switch (cmd[3]) {
case 'a':
FULLCHECK("expandaaaa");
lookup->expandaaaa = state;
break;
case 'i':
FULLCHECK("expire");
lookup->expire = state;
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
case 'f': /* fail */
FULLCHECK("fail");
lookup->servfail_stops = state;
break;
case 'h':
FULLCHECK("header-only");
lookup->header_only = state;
break;
case 'i':
switch (cmd[1]) {
case 'd': /* identify */
switch (cmd[2]) {
case 'e':
FULLCHECK("identify");
lookup->identify = state;
break;
case 'n':
switch (cmd[3]) {
case 'i':
FULLCHECK("idnin");
#ifndef HAVE_LIBIDN2
fprintf(stderr, ";; IDN input support"
" not enabled\n");
#else /* ifndef HAVE_LIBIDN2 */
lookup->idnin = state;
#endif /* ifndef HAVE_LIBIDN2 */
break;
case 'o':
FULLCHECK("idnout");
#ifndef HAVE_LIBIDN2
fprintf(stderr, ";; IDN output support"
" not enabled\n");
#else /* ifndef HAVE_LIBIDN2 */
lookup->idnout = state;
#endif /* ifndef HAVE_LIBIDN2 */
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
case 'g': /* ignore */
default: /*
* Inherits default for compatibility (+[no]i*).
*/
FULLCHECK("ignore");
lookup->ignore = state;
}
break;
case 'k':
switch (cmd[1]) {
case 'e':
switch (cmd[2]) {
case 'e':
switch (cmd[3]) {
case 'p':
switch (cmd[4]) {
case 'a':
FULLCHECK("keepalive");
lookup->tcp_keepalive = state;
break;
case 'o':
FULLCHECK("keepopen");
keep_open = state;
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
case 'm': /* multiline */
switch (cmd[1]) {
case 'a':
FULLCHECK("mapped");
lookup->mapped = state;
break;
case 'u':
FULLCHECK("multiline");
lookup->multiline = state;
break;
default:
goto invalid_option;
}
break;
case 'n':
switch (cmd[1]) {
case 'd': /* ndots */
FULLCHECK("ndots");
if (value == NULL) {
goto need_value;
}
if (!state) {
goto invalid_option;
}
result = parse_uint(&num, value, MAXNDOTS, "ndots");
if (result != ISC_R_SUCCESS) {
warn("Couldn't parse ndots");
goto exit_or_usage;
}
ndots = num;
break;
case 's':
switch (cmd[2]) {
case 'i': /* nsid */
FULLCHECK("nsid");
if (state && lookup->edns == -1) {
lookup->edns = DEFAULT_EDNS_VERSION;
}
lookup->nsid = state;
break;
case 's': /* nssearch */
FULLCHECK("nssearch");
lookup->ns_search_only = state;
if (state) {
lookup->trace_root = true;
lookup->recurse = true;
lookup->identify = true;
lookup->stats = false;
lookup->comments = false;
lookup->section_additional = false;
lookup->section_authority = false;
lookup->section_question = false;
lookup->rdtype = dns_rdatatype_ns;
lookup->rdtypeset = true;
short_form = true;
lookup->rrcomments = 0;
}
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
case 'o':
switch (cmd[1]) {
case 'n':
FULLCHECK("onesoa");
lookup->onesoa = state;
break;
case 'p':
FULLCHECK("opcode");
if (!state) {
lookup->opcode = 0; /* default - query */
break;
}
if (value == NULL) {
goto need_value;
}
for (num = 0;
num < sizeof(opcodetext) / sizeof(opcodetext[0]);
num++) {
if (strcasecmp(opcodetext[num], value) == 0) {
break;
}
}
if (num < 16) {
lookup->opcode = (dns_opcode_t)num;
break;
}
result = parse_uint(&num, value, 15, "opcode");
if (result != ISC_R_SUCCESS) {
warn("Couldn't parse opcode");
goto exit_or_usage;
}
lookup->opcode = (dns_opcode_t)num;
break;
default:
goto invalid_option;
}
break;
case 'p':
FULLCHECK("padding");
if (state && lookup->edns == -1) {
lookup->edns = DEFAULT_EDNS_VERSION;
}
if (value == NULL) {
goto need_value;
}
result = parse_uint(&num, value, 512, "padding");
if (result != ISC_R_SUCCESS) {
warn("Couldn't parse padding");
goto exit_or_usage;
}
lookup->padding = (uint16_t)num;
break;
case 'q':
switch (cmd[1]) {
case 'i': /* qid */
FULLCHECK("qid");
if (!state) {
lookup->setqid = false;
lookup->qid = 0;
break;
}
if (value == NULL) {
goto need_value;
}
result = parse_uint(&num, value, MAXQID, "qid");
if (result != ISC_R_SUCCESS) {
warn("Couldn't parse qid");
goto exit_or_usage;
}
lookup->setqid = true;
lookup->qid = num;
break;
case 'r': /* qr */
FULLCHECK("qr");
lookup->qr = state;
break;
case 'u': /* question */
FULLCHECK("question");
lookup->section_question = state;
if (lookup == default_lookup) {
plusquest = state;
}
break;
default:
goto invalid_option;
}
break;
case 'r':
switch (cmd[1]) {
case 'a': /* raflag */
FULLCHECK("raflag");
lookup->raflag = state;
break;
case 'd': /* rdflag */
FULLCHECK("rdflag");
lookup->recurse = state;
break;
case 'e':
switch (cmd[2]) {
case 'c': /* recurse */
FULLCHECK("recurse");
lookup->recurse = state;
break;
case 't': /* retry / retries */
FULLCHECK2("retry", "retries");
if (value == NULL) {
goto need_value;
}
if (!state) {
goto invalid_option;
}
result = parse_uint(&lookup->retries, value,
MAXTRIES - 1, "retries");
if (result != ISC_R_SUCCESS) {
warn("Couldn't parse retries");
goto exit_or_usage;
}
lookup->retries++;
break;
default:
goto invalid_option;
}
break;
case 'r': /* rrcomments */
FULLCHECK("rrcomments");
lookup->rrcomments = state ? 1 : -1;
break;
default:
goto invalid_option;
}
break;
case 's':
switch (cmd[1]) {
case 'e': /* search */
FULLCHECK("search");
if (!lookup->trace) {
usesearch = state;
}
break;
case 'h':
if (cmd[2] != 'o') {
goto invalid_option;
}
switch (cmd[3]) {
case 'r': /* short */
FULLCHECK("short");
short_form = state;
if (state) {
printcmd = false;
lookup->section_additional = false;
lookup->section_answer = true;
lookup->section_authority = false;
lookup->section_question = false;
lookup->comments = false;
lookup->stats = false;
lookup->rrcomments = -1;
}
break;
case 'w': /* showsearch */
FULLCHECK("showsearch");
if (!lookup->trace) {
showsearch = state;
usesearch = state;
}
break;
default:
goto invalid_option;
}
break;
case 'i': /* sigchase */
FULLCHECK("sigchase");
fprintf(stderr, ";; +sigchase option is deprecated");
break;
case 'p': /* split */
FULLCHECK("split");
if (value != NULL && !state) {
goto invalid_option;
}
if (!state) {
splitwidth = 0;
break;
} else if (value == NULL) {
break;
}
result = parse_uint(&splitwidth, value, 1023, "split");
if ((splitwidth % 4) != 0U) {
splitwidth = ((splitwidth + 3) / 4) * 4;
fprintf(stderr,
";; Warning, split must be "
"a multiple of 4; adjusting "
"to %u\n",
splitwidth);
}
/*
* There is an adjustment done in the
* totext_<rrtype>() functions which causes
* splitwidth to shrink. This is okay when we're
* using the default width but incorrect in this
* case, so we correct for it
*/
if (splitwidth) {
splitwidth += 3;
}
if (result != ISC_R_SUCCESS) {
warn("Couldn't parse split");
goto exit_or_usage;
}
break;
case 't': /* stats */
FULLCHECK("stats");
lookup->stats = state;
break;
case 'u': /* subnet */
FULLCHECK("subnet");
if (state && value == NULL) {
goto need_value;
}
if (!state) {
if (lookup->ecs_addr != NULL) {
isc_mem_free(mctx, lookup->ecs_addr);
lookup->ecs_addr = NULL;
}
break;
}
if (lookup->edns == -1) {
lookup->edns = DEFAULT_EDNS_VERSION;
}
if (lookup->ecs_addr != NULL) {
isc_mem_free(mctx, lookup->ecs_addr);
lookup->ecs_addr = NULL;
}
result = parse_netprefix(&lookup->ecs_addr, value);
if (result != ISC_R_SUCCESS) {
warn("Couldn't parse client");
goto exit_or_usage;
}
break;
default:
goto invalid_option;
}
break;
case 't':
switch (cmd[1]) {
case 'c': /* tcp */
switch (cmd[2]) {
case 'f':
FULLCHECK("tcflag");
lookup->tcflag = state;
break;
case 'p':
FULLCHECK("tcp");
if (!is_batchfile) {
lookup->tcp_mode = state;
lookup->tcp_mode_set = true;
}
break;
default:
goto invalid_option;
}
break;
case 'i': /* timeout */
FULLCHECK("timeout");
if (value == NULL) {
goto need_value;
}
if (!state) {
goto invalid_option;
}
result = parse_uint(&timeout, value, MAXTIMEOUT,
"timeout");
if (result != ISC_R_SUCCESS) {
warn("Couldn't parse timeout");
goto exit_or_usage;
}
if (timeout == 0) {
timeout = 1;
}
break;
case 'o':
FULLCHECK("topdown");
fprintf(stderr, ";; +topdown option is deprecated");
break;
case 'r':
switch (cmd[2]) {
case 'a': /* trace */
FULLCHECK("trace");
lookup->trace = state;
lookup->trace_root = state;
if (state) {
lookup->recurse = true;
lookup->identify = true;
lookup->comments = false;
lookup->rrcomments = 0;
lookup->stats = false;
lookup->section_additional = false;
lookup->section_authority = true;
lookup->section_question = false;
lookup->dnssec = true;
lookup->sendcookie = true;
usesearch = false;
}
break;
case 'i': /* tries */
FULLCHECK("tries");
if (value == NULL) {
goto need_value;
}
if (!state) {
goto invalid_option;
}
result = parse_uint(&lookup->retries, value,
MAXTRIES, "tries");
if (result != ISC_R_SUCCESS) {
warn("Couldn't parse tries");
goto exit_or_usage;
}
if (lookup->retries == 0) {
lookup->retries = 1;
}
break;
case 'u': /* trusted-key */
FULLCHECK("trusted-key");
fprintf(stderr, ";; +trusted-key option is "
"deprecated");
break;
default:
goto invalid_option;
}
break;
case 't':
switch (cmd[2]) {
case 'l':
switch (cmd[3]) {
case 0:
case 'i': /* ttlid */
FULLCHECK2("ttl", "ttlid");
lookup->nottl = !state;
break;
case 'u': /* ttlunits */
FULLCHECK("ttlunits");
lookup->nottl = false;
lookup->ttlunits = state;
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
case 'u':
switch (cmd[1]) {
case 'n':
switch (cmd[2]) {
case 'e':
FULLCHECK("unexpected");
fprintf(stderr, ";; +unexpected option "
"is deprecated");
break;
case 'k':
FULLCHECK("unknownformat");
lookup->print_unknown_format = state;
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
case 'v':
FULLCHECK("vc");
if (!is_batchfile) {
lookup->tcp_mode = state;
lookup->tcp_mode_set = true;
}
break;
case 'y': /* yaml */
FULLCHECK("yaml");
yaml = state;
if (state) {
printcmd = false;
lookup->stats = false;
lookup->rrcomments = -1;
}
break;
case 'z': /* zflag */
FULLCHECK("zflag");
lookup->zflag = state;
break;
default:
invalid_option:
need_value:
#if TARGET_OS_IPHONE
exit_or_usage:
#endif /* if TARGET_OS_IPHONE */
fprintf(stderr, "Invalid option: +%s\n", option);
usage();
}
return;
#if !TARGET_OS_IPHONE
exit_or_usage:
digexit();
#endif /* if !TARGET_OS_IPHONE */
}
/*%
* #true returned if value was used
*/
static const char *single_dash_opts = "46dhimnruv";
static const char *dash_opts = "46bcdfhikmnpqrtvyx";
static bool
dash_option(char *option, char *next, dig_lookup_t **lookup,
bool *open_type_class, bool *need_clone, bool config_only, int argc,
char **argv, bool *firstarg) {
char opt, *value, *ptr, *ptr2, *ptr3, *last;
isc_result_t result;
bool value_from_next;
isc_textregion_t tr;
dns_rdatatype_t rdtype;
dns_rdataclass_t rdclass;
char textname[MXNAME];
struct in_addr in4;
struct in6_addr in6;
in_port_t srcport;
char *hash, *cmd;
uint32_t num;
while (strpbrk(option, single_dash_opts) == &option[0]) {
/*
* Since the -[46dhimnuv] options do not take an argument,
* account for them (in any number and/or combination)
* if they appear as the first character(s) of a q-opt.
*/
opt = option[0];
switch (opt) {
case '4':
if (have_ipv4) {
isc_net_disableipv6();
have_ipv6 = false;
} else {
fatal("can't find IPv4 networking");
/* NOTREACHED */
return (false);
}
break;
case '6':
if (have_ipv6) {
isc_net_disableipv4();
have_ipv4 = false;
} else {
fatal("can't find IPv6 networking");
/* NOTREACHED */
return (false);
}
break;
case 'd':
ptr = strpbrk(&option[1], dash_opts);
if (ptr != &option[1]) {
cmd = option;
FULLCHECK("debug");
debugging = true;
return (false);
} else {
debugging = true;
}
break;
case 'h':
help();
exit(0);
break;
case 'i':
/* deprecated */
break;
case 'm': /* memdebug */
/* memdebug is handled in preparse_args() */
break;
case 'n':
/* deprecated */
break;
case 'r':
debug("digrc (late)");
digrc = false;
break;
case 'u':
(*lookup)->use_usec = true;
break;
case 'v':
version();
exit(0);
break;
}
if (strlen(option) > 1U) {
option = &option[1];
} else {
return (false);
}
}
opt = option[0];
if (strlen(option) > 1U) {
value_from_next = false;
value = &option[1];
} else {
value_from_next = true;
value = next;
}
if (value == NULL) {
goto invalid_option;
}
switch (opt) {
case 'b':
hash = strchr(value, '#');
if (hash != NULL) {
result = parse_uint(&num, hash + 1, MAXPORT,
"port number");
if (result != ISC_R_SUCCESS) {
fatal("Couldn't parse port number");
}
srcport = num;
*hash = '\0';
} else {
srcport = 0;
}
if (have_ipv6 && inet_pton(AF_INET6, value, &in6) == 1) {
isc_sockaddr_fromin6(&localaddr, &in6, srcport);
isc_net_disableipv4();
} else if (have_ipv4 && inet_pton(AF_INET, value, &in4) == 1) {
isc_sockaddr_fromin(&localaddr, &in4, srcport);
isc_net_disableipv6();
} else {
if (hash != NULL) {
*hash = '#';
}
fatal("invalid address %s", value);
}
if (hash != NULL) {
*hash = '#';
}
specified_source = true;
return (value_from_next);
case 'c':
if ((*lookup)->rdclassset) {
fprintf(stderr, ";; Warning, extra class option\n");
}
*open_type_class = false;
tr.base = value;
tr.length = (unsigned int)strlen(value);
result = dns_rdataclass_fromtext(&rdclass,
(isc_textregion_t *)&tr);
if (result == ISC_R_SUCCESS) {
(*lookup)->rdclass = rdclass;
(*lookup)->rdclassset = true;
} else {
fprintf(stderr,
";; Warning, ignoring "
"invalid class %s\n",
value);
}
return (value_from_next);
case 'f':
atomic_store(&batchname, (uintptr_t)value);
return (value_from_next);
case 'k':
strlcpy(keyfile, value, sizeof(keyfile));
return (value_from_next);
case 'p':
result = parse_uint(&num, value, MAXPORT, "port number");
if (result != ISC_R_SUCCESS) {
fatal("Couldn't parse port number");
}
port = num;
return (value_from_next);
case 'q':
if (!config_only) {
if (*need_clone) {
(*lookup) = clone_lookup(default_lookup, true);
}
*need_clone = true;
strlcpy((*lookup)->textname, value,
sizeof((*lookup)->textname));
(*lookup)->trace_root = ((*lookup)->trace ||
(*lookup)->ns_search_only);
(*lookup)->new_search = true;
if (*firstarg) {
printgreeting(argc, argv, *lookup);
*firstarg = false;
}
ISC_LIST_APPEND(lookup_list, (*lookup), link);
debug("looking up %s", (*lookup)->textname);
}
return (value_from_next);
case 't':
*open_type_class = false;
if (strncasecmp(value, "ixfr=", 5) == 0) {
rdtype = dns_rdatatype_ixfr;
result = ISC_R_SUCCESS;
} else {
tr.base = value;
tr.length = (unsigned int)strlen(value);
result = dns_rdatatype_fromtext(
&rdtype, (isc_textregion_t *)&tr);
if (result == ISC_R_SUCCESS &&
rdtype == dns_rdatatype_ixfr) {
result = DNS_R_UNKNOWN;
}
}
if (result == ISC_R_SUCCESS) {
if ((*lookup)->rdtypeset) {
fprintf(stderr, ";; Warning, "
"extra type option\n");
}
if (rdtype == dns_rdatatype_ixfr) {
uint32_t serial;
(*lookup)->rdtype = dns_rdatatype_ixfr;
(*lookup)->rdtypeset = true;
result = parse_uint(&serial, &value[5],
MAXSERIAL, "serial number");
if (result != ISC_R_SUCCESS) {
fatal("Couldn't parse serial number");
}
(*lookup)->ixfr_serial = serial;
(*lookup)->section_question = plusquest;
(*lookup)->comments = pluscomm;
if (!(*lookup)->tcp_mode_set) {
(*lookup)->tcp_mode = true;
}
} else {
(*lookup)->rdtype = rdtype;
if (!config_only) {
(*lookup)->rdtypeset = true;
}
if (rdtype == dns_rdatatype_axfr) {
(*lookup)->section_question = plusquest;
(*lookup)->comments = pluscomm;
} else if (rdtype == dns_rdatatype_any) {
if (!(*lookup)->tcp_mode_set) {
(*lookup)->tcp_mode = true;
}
}
(*lookup)->ixfr_serial = false;
}
} else {
fprintf(stderr,
";; Warning, ignoring "
"invalid type %s\n",
value);
}
return (value_from_next);
case 'y':
if ((ptr = strtok_r(value, ":", &last)) == NULL) {
usage();
}
if ((ptr2 = strtok_r(NULL, ":", &last)) == NULL) { /* name or
* secret */
usage();
}
if ((ptr3 = strtok_r(NULL, ":", &last)) != NULL) { /* secret or
* NULL */
parse_hmac(ptr);
ptr = ptr2;
ptr2 = ptr3;
} else {
hmacname = DNS_TSIG_HMACMD5_NAME;
digestbits = 0;
}
/* XXXONDREJ: FIXME */
strlcpy(keynametext, ptr, sizeof(keynametext));
strlcpy(keysecret, ptr2, sizeof(keysecret));
return (value_from_next);
case 'x':
if (*need_clone) {
*lookup = clone_lookup(default_lookup, true);
}
*need_clone = true;
if (get_reverse(textname, sizeof(textname), value, false) ==
ISC_R_SUCCESS) {
strlcpy((*lookup)->textname, textname,
sizeof((*lookup)->textname));
debug("looking up %s", (*lookup)->textname);
(*lookup)->trace_root = ((*lookup)->trace ||
(*lookup)->ns_search_only);
if (!(*lookup)->rdtypeset) {
(*lookup)->rdtype = dns_rdatatype_ptr;
}
if (!(*lookup)->rdclassset) {
(*lookup)->rdclass = dns_rdataclass_in;
}
(*lookup)->new_search = true;
if (*firstarg) {
printgreeting(argc, argv, *lookup);
*firstarg = false;
}
ISC_LIST_APPEND(lookup_list, *lookup, link);
} else {
fprintf(stderr, "Invalid IP address %s\n", value);
exit(1);
}
return (value_from_next);
invalid_option:
default:
fprintf(stderr, "Invalid option: -%s\n", option);
usage();
}
/* NOTREACHED */
return (false);
}
/*%
* Because we may be trying to do memory allocation recording, we're going
* to need to parse the arguments for the -m *before* we start the main
* argument parsing routine.
*
* I'd prefer not to have to do this, but I am not quite sure how else to
* fix the problem. Argument parsing in dig involves memory allocation
* by its nature, so it can't be done in the main argument parser.
*/
static void
preparse_args(int argc, char **argv) {
int rc;
char **rv;
char *option;
rc = argc;
rv = argv;
for (rc--, rv++; rc > 0; rc--, rv++) {
if (rv[0][0] != '-') {
continue;
}
option = &rv[0][1];
while (strpbrk(option, single_dash_opts) == &option[0]) {
switch (option[0]) {
case 'd':
/* For debugging early startup */
debugging = true;
break;
case 'm':
memdebugging = true;
isc_mem_debugging = ISC_MEM_DEBUGTRACE |
ISC_MEM_DEBUGRECORD;
break;
case 'r':
/*
* Must be done early, because ~/.digrc
* is read before command line parsing
*/
debug("digrc (early)");
digrc = false;
break;
case '4':
if (ipv6only) {
fatal("only one of -4 and -6 allowed");
}
ipv4only = true;
break;
case '6':
if (ipv4only) {
fatal("only one of -4 and -6 allowed");
}
ipv6only = true;
break;
}
option = &option[1];
}
if (strlen(option) == 0U) {
continue;
}
/* Look for dash value option. */
if (strpbrk(option, dash_opts) != &option[0] ||
strlen(option) > 1U) {
/* Error or value in option. */
continue;
}
/* Dash value is next argument so we need to skip it. */
rc--, rv++;
/* Handle missing argument */
if (rc == 0) {
break;
}
}
}
static int
split_batchline(char *batchline, char **bargv, int len, const char *msg) {
int bargc;
char *last = NULL;
REQUIRE(batchline != NULL);
for (bargc = 1, bargv[bargc] = strtok_r(batchline, " \t\r\n", &last);
bargc < len && bargv[bargc];
bargv[++bargc] = strtok_r(NULL, " \t\r\n", &last))
{
debug("%s %d: %s", msg, bargc, bargv[bargc]);
}
return (bargc);
}
static void
parse_args(bool is_batchfile, bool config_only, int argc, char **argv) {
isc_result_t result;
isc_textregion_t tr;
bool firstarg = true;
dig_lookup_t *lookup = NULL;
dns_rdatatype_t rdtype;
dns_rdataclass_t rdclass;
bool open_type_class = true;
char batchline[MXNAME];
int bargc;
char *bargv[64];
int rc;
char **rv;
#ifndef NOPOSIX
char *homedir;
char rcfile[PATH_MAX];
#endif /* ifndef NOPOSIX */
bool need_clone = true;
/*
* The semantics for parsing the args is a bit complex; if
* we don't have a host yet, make the arg apply globally,
* otherwise make it apply to the latest host. This is
* a bit different than the previous versions, but should
* form a consistent user interface.
*
* First, create a "default lookup" which won't actually be used
* anywhere, except for cloning into new lookups
*/
debug("parse_args()");
if (!is_batchfile) {
debug("making new lookup");
default_lookup = make_empty_lookup();
default_lookup->adflag = true;
default_lookup->edns = DEFAULT_EDNS_VERSION;
default_lookup->sendcookie = true;
#ifndef NOPOSIX
/*
* Treat ${HOME}/.digrc as a special batchfile
*/
INSIST(batchfp == NULL);
homedir = getenv("HOME");
if (homedir != NULL && digrc) {
unsigned int n;
debug("digrc (open)");
n = snprintf(rcfile, sizeof(rcfile), "%s/.digrc",
homedir);
if (n < sizeof(rcfile)) {
batchfp = fopen(rcfile, "r");
}
}
if (batchfp != NULL) {
while (fgets(batchline, sizeof(batchline), batchfp) !=
0) {
debug("config line %s", batchline);
bargc = split_batchline(batchline, bargv, 62,
".digrc argv");
bargv[0] = argv[0];
argv0 = argv[0];
parse_args(true, true, bargc, (char **)bargv);
}
fclose(batchfp);
}
#endif /* ifndef NOPOSIX */
}
if (is_batchfile && !config_only) {
/* Processing '-f batchfile'. */
lookup = clone_lookup(default_lookup, true);
need_clone = false;
} else {
lookup = default_lookup;
}
rc = argc;
rv = argv;
for (rc--, rv++; rc > 0; rc--, rv++) {
debug("main parsing %s", rv[0]);
if (strncmp(rv[0], "%", 1) == 0) {
break;
}
if (rv[0][0] == '@') {
if (is_batchfile && !config_only) {
addresscount = getaddresses(lookup, &rv[0][1],
&result);
if (addresscount == 0) {
fprintf(stderr,
"couldn't get address "
"for '%s': %s: skipping "
"lookup\n",
&rv[0][1],
isc_result_totext(result));
if (ISC_LINK_LINKED(lookup, link)) {
ISC_LIST_DEQUEUE(lookup_list,
lookup, link);
}
destroy_lookup(lookup);
return;
}
} else {
addresscount = getaddresses(lookup, &rv[0][1],
NULL);
if (addresscount == 0) {
fatal("no valid addresses for '%s'\n",
&rv[0][1]);
}
}
} else if (rv[0][0] == '+') {
plus_option(&rv[0][1], is_batchfile, lookup);
} else if (rv[0][0] == '-') {
if (rc <= 1) {
if (dash_option(&rv[0][1], NULL, &lookup,
&open_type_class, &need_clone,
config_only, argc, argv,
&firstarg))
{
rc--;
rv++;
}
} else {
if (dash_option(&rv[0][1], rv[1], &lookup,
&open_type_class, &need_clone,
config_only, argc, argv,
&firstarg))
{
rc--;
rv++;
}
}
} else {
/*
* Anything which isn't an option
*/
if (open_type_class) {
if (strncasecmp(rv[0], "ixfr=", 5) == 0) {
rdtype = dns_rdatatype_ixfr;
result = ISC_R_SUCCESS;
} else {
tr.base = rv[0];
tr.length = (unsigned int)strlen(rv[0]);
result = dns_rdatatype_fromtext(
&rdtype,
(isc_textregion_t *)&tr);
if (result == ISC_R_SUCCESS &&
rdtype == dns_rdatatype_ixfr) {
fprintf(stderr, ";; Warning, "
"ixfr requires "
"a "
"serial "
"number\n");
continue;
}
}
if (result == ISC_R_SUCCESS) {
if (lookup->rdtypeset) {
fprintf(stderr, ";; Warning, "
"extra type "
"option\n");
}
if (rdtype == dns_rdatatype_ixfr) {
uint32_t serial;
lookup->rdtype =
dns_rdatatype_ixfr;
lookup->rdtypeset = true;
result = parse_uint(&serial,
&rv[0][5],
MAXSERIAL,
"serial "
"number");
if (result != ISC_R_SUCCESS) {
fatal("Couldn't parse "
"serial number");
}
lookup->ixfr_serial = serial;
lookup->section_question =
plusquest;
lookup->comments = pluscomm;
if (!lookup->tcp_mode_set) {
lookup->tcp_mode = true;
}
} else {
lookup->rdtype = rdtype;
lookup->rdtypeset = true;
if (rdtype ==
dns_rdatatype_axfr) {
lookup->section_question =
plusquest;
lookup->comments =
pluscomm;
}
if (rdtype ==
dns_rdatatype_any &&
!lookup->tcp_mode_set) {
lookup->tcp_mode = true;
}
lookup->ixfr_serial = false;
}
continue;
}
result = dns_rdataclass_fromtext(
&rdclass, (isc_textregion_t *)&tr);
if (result == ISC_R_SUCCESS) {
if (lookup->rdclassset) {
fprintf(stderr, ";; Warning, "
"extra class "
"option\n");
}
lookup->rdclass = rdclass;
lookup->rdclassset = true;
continue;
}
}
if (!config_only) {
if (need_clone) {
lookup = clone_lookup(default_lookup,
true);
}
need_clone = true;
strlcpy(lookup->textname, rv[0],
sizeof(lookup->textname));
lookup->trace_root = (lookup->trace ||
lookup->ns_search_only);
lookup->new_search = true;
if (firstarg) {
printgreeting(argc, argv, lookup);
firstarg = false;
}
ISC_LIST_APPEND(lookup_list, lookup, link);
debug("looking up %s", lookup->textname);
}
/* XXX Error message */
}
}
/*
* If we have a batchfile, seed the lookup list with the
* first entry, then trust the callback in dighost_shutdown
* to get the rest
*/
char *filename = (char *)atomic_load(&batchname);
if ((filename != NULL) && !(is_batchfile)) {
if (strcmp(filename, "-") == 0) {
batchfp = stdin;
} else {
batchfp = fopen(filename, "r");
}
if (batchfp == NULL) {
perror(filename);
if (exitcode < 8) {
exitcode = 8;
}
fatal("couldn't open specified batch file");
}
/* XXX Remove code dup from shutdown code */
next_line:
if (fgets(batchline, sizeof(batchline), batchfp) != 0) {
debug("batch line %s", batchline);
if (batchline[0] == '\r' || batchline[0] == '\n' ||
batchline[0] == '#' || batchline[0] == ';')
{
goto next_line;
}
bargc = split_batchline(batchline, bargv, 14,
"batch argv");
bargv[0] = argv[0];
argv0 = argv[0];
parse_args(true, false, bargc, (char **)bargv);
return;
}
return;
}
/*
* If no lookup specified, search for root
*/
if ((lookup_list.head == NULL) && !config_only) {
if (need_clone) {
lookup = clone_lookup(default_lookup, true);
}
need_clone = true;
lookup->trace_root = (lookup->trace || lookup->ns_search_only);
lookup->new_search = true;
strlcpy(lookup->textname, ".", sizeof(lookup->textname));
lookup->rdtype = dns_rdatatype_ns;
lookup->rdtypeset = true;
if (firstarg) {
printgreeting(argc, argv, lookup);
firstarg = false;
}
ISC_LIST_APPEND(lookup_list, lookup, link);
}
if (!need_clone) {
destroy_lookup(lookup);
}
}
/*
* Callback from dighost.c to allow program-specific shutdown code.
* Here, we're possibly reading from a batch file, then shutting down
* for real if there's nothing in the batch file to read.
*/
static void
query_finished(void) {
char batchline[MXNAME];
int bargc;
char *bargv[16];
if (atomic_load(&batchname) == 0) {
isc_app_shutdown();
return;
}
fflush(stdout);
if (feof(batchfp)) {
atomic_store(&batchname, 0);
isc_app_shutdown();
if (batchfp != stdin) {
fclose(batchfp);
}
return;
}
if (fgets(batchline, sizeof(batchline), batchfp) != 0) {
debug("batch line %s", batchline);
bargc = split_batchline(batchline, bargv, 14, "batch argv");
bargv[0] = argv0;
parse_args(true, false, bargc, (char **)bargv);
start_lookup();
} else {
atomic_store(&batchname, 0);
if (batchfp != stdin) {
fclose(batchfp);
}
isc_app_shutdown();
return;
}
}
static void
dig_error(const char *format, ...) {
va_list args;
if (yaml) {
printf("-\n");
printf(" type: DIG_ERROR\n");
/*
* Print an indent before a literal block quote.
* Note: this will break if used to print more than
* one line of text as only the first line would be
* indented.
*/
printf(" message: |\n");
printf(" ");
} else {
printf(";; ");
}
va_start(args, format);
vprintf(format, args);
va_end(args);
if (!yaml) {
printf("\n");
}
}
static void
dig_warning(const char *format, ...) {
va_list args;
if (!yaml) {
printf(";; ");
va_start(args, format);
vprintf(format, args);
va_end(args);
printf("\n");
}
}
static void
dig_comments(dig_lookup_t *lookup, const char *format, ...) {
va_list args;
if (lookup->comments && !yaml) {
printf(";; ");
va_start(args, format);
vprintf(format, args);
va_end(args);
printf("\n");
}
}
void
dig_setup(int argc, char **argv) {
isc_result_t result;
ISC_LIST_INIT(lookup_list);
ISC_LIST_INIT(server_list);
ISC_LIST_INIT(search_list);
debug("dig_setup()");
/* setup dighost callbacks */
dighost_printmessage = printmessage;
dighost_received = received;
dighost_trying = trying;
dighost_shutdown = query_finished;
dighost_error = dig_error;
dighost_warning = dig_warning;
dighost_comments = dig_comments;
progname = argv[0];
preparse_args(argc, argv);
result = isc_app_start();
check_result(result, "isc_app_start");
setup_libs();
setup_system(ipv4only, ipv6only);
}
void
dig_query_setup(bool is_batchfile, bool config_only, int argc, char **argv) {
debug("dig_query_setup");
parse_args(is_batchfile, config_only, argc, argv);
if (keyfile[0] != 0) {
setup_file_key();
} else if (keysecret[0] != 0) {
setup_text_key();
}
if (domainopt[0] != '\0') {
set_search_domain(domainopt);
usesearch = true;
}
}
void
dig_startup(void) {
isc_result_t result;
debug("dig_startup()");
result = isc_app_onrun(mctx, global_task, onrun_callback, NULL);
check_result(result, "isc_app_onrun");
isc_app_run();
}
void
dig_query_start(void) {
start_lookup();
}
void
dig_shutdown(void) {
destroy_lookup(default_lookup);
if (atomic_load(&batchname) != 0) {
if (batchfp != stdin) {
fclose(batchfp);
}
atomic_store(&batchname, 0);
}
cancel_all();
destroy_libs();
isc_app_finish();
}
/*% Main processing routine for dig */
int
main(int argc, char **argv) {
dig_setup(argc, argv);
dig_query_setup(false, false, argc, argv);
dig_startup();
dig_shutdown();
return (exitcode);
}