mdig.c revision 0c27b3fe77ac1d5094ba3521e8142d9e7973133f
/*
* Copyright (C) 2015, 2016 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 http://mozilla.org/MPL/2.0/.
*/
#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <isc/parseint.h>
#include <isc/sockaddr.h>
#include <dns/dispatch.h>
#include <dns/fixedname.h>
#include <dns/rdataset.h>
#include <dns/rdatatype.h>
#include <dns/rdataclass.h>
#include <dns/rdataset.h>
#include <dns/resolver.h>
#include <bind9/getaddresses.h>
if ((x) != ISC_R_SUCCESS) { \
(str), isc_result_totext(x)); \
exit(-1); \
} \
}
#define ADD_STRING(b, s) { \
if (strlen(s) >= isc_buffer_availablelength(b)) \
return (ISC_R_NOSPACE); \
else \
isc_buffer_putstr(b, s); \
}
#define COMMSIZE 0xffff
#define OUTPUTBUF 32767
#define MAXPORT 0xffff
#define PORT 53
#define MAXTIMEOUT 0xffff
#define TCPTIMEOUT 10
#define UDPTIMEOUT 5
#define MAXTRIES 0xffffffff
static dns_requestmgr_t *requestmgr;
static const char *batchname;
static isc_sockaddr_t srcaddr;
static char *server;
static isc_sockaddr_t dstaddr;
static unsigned char cookie_secret[33];
static int onfly = 0;
static char hexcookie[81];
struct query {
char *cookie;
unsigned int ednsoptscnt;
unsigned int ednsflags;
unsigned int timeout;
unsigned int udptimeout;
unsigned int udpretries;
};
static struct query default_query;
#define EDNSOPTS 100U
/*% opcode text */
static const char * const opcodetext[] = {
"QUERY",
"IQUERY",
"STATUS",
"RESERVED3",
"NOTIFY",
"UPDATE",
"RESERVED6",
"RESERVED7",
"RESERVED8",
"RESERVED9",
"RESERVED10",
"RESERVED11",
"RESERVED12",
"RESERVED13",
"RESERVED14",
"RESERVED15"
};
/*% return code text */
static const char * const rcodetext[] = {
"NOERROR",
"FORMERR",
"SERVFAIL",
"NXDOMAIN",
"NOTIMP",
"REFUSED",
"YXDOMAIN",
"YXRRSET",
"NXRRSET",
"NOTAUTH",
"NOTZONE",
"RESERVED11",
"RESERVED12",
"RESERVED13",
"RESERVED14",
"RESERVED15",
"BADVERS"
};
/*% safe rcodetext[] */
static char *
{
static char buf[sizeof("?65535")];
union {
const char *consttext;
char *deconsttext;
} totext;
} else
return totext.deconsttext;
}
/* receive response event handler */
static void
unsigned int parseflags = 0;
unsigned int styleflags = 0;
exit(-1);
}
if (besteffort) {
}
if (display_comments)
if (display_rrcomments)
if (display_ttlunits)
if (!display_ttl)
if (!display_class)
if (!display_crypto)
if (display_multiline) {
}
24, 24, 24, 32, 80, 8,
else if (!display_ttl || !display_class)
24, 24, 32, 40, 80, 8,
else
24, 32, 40, 48, 80, 8,
flags = 0;
if (!display_headers) {
}
if (!display_comments)
if (display_comments && !display_short_form) {
printf(";; Got answer:\n");
if (display_headers) {
printf(";; ->>HEADER<<- opcode: %s, status: %s, "
"id: %u\n",
printf(";; flags:");
printf(" qr");
printf(" aa");
printf(" tc");
printf(" rd");
printf(" ra");
printf(" ad");
printf(" cd");
printf("; MBZ: 0x4");
printf("; QUERY: %u, ANSWER: %u, "
"AUTHORITY: %u, ADDITIONAL: %u\n",
printf(";; WARNING: recursion requested "
"but not available\n");
}
}
if (result == ISC_R_NOSPACE) {
if (result == ISC_R_SUCCESS)
goto repopulate_buffer;
else
goto cleanup;
}
}
if (result == ISC_R_NOSPACE)
goto buftoosmall;
}
if (display_answer && !display_short_form) {
if (result == ISC_R_NOSPACE)
goto buftoosmall;
} else if (display_answer) {
unsigned int answerstyleflags = 0;
if (!display_crypto)
if (result != ISC_R_NOMORE)
for (;;) {
if (result == ISC_R_NOMORE)
break;
&name);
while (loopresult == ISC_R_SUCCESS) {
&rdata,
NULL,
if (result == ISC_R_NOSPACE)
goto buftoosmall;
if (strlen("\n") >=
goto buftoosmall;
}
}
}
}
if (display_authority && !display_short_form) {
if (result == ISC_R_NOSPACE)
goto buftoosmall;
}
if (display_additional && !display_short_form) {
if (result == ISC_R_NOSPACE)
goto buftoosmall;
}
/*
* Only print the signature on the first record.
*/
result =
if (result == ISC_R_NOSPACE)
goto buftoosmall;
result =
if (result == ISC_R_NOSPACE)
goto buftoosmall;
}
printf("\n");
(char *)isc_buffer_base(buf));
if (--onfly == 0)
return;
}
/*%
* Add EDNS0 option record to a message. Currently, the only supported
* options are UDP buffer size, the DO bit, and EDNS options
* (e.g., NSID, COOKIE, client-subnet)
*/
static void
{
}
static void
/* XXXMPA need to fix, should be per server. */
}
static isc_result_t
{
unsigned int options;
onfly++;
dns_rootname, 0, NULL);
if (query->have_aaonly)
if (query->have_adflag)
if (query->have_cdflag)
if (query->have_zflag)
{
unsigned int flags;
int i = 0;
char ecsbuf[20];
unsigned char cookie[40];
INSIST(i < DNS_EDNSOPTIONS);
i++;
}
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
isc_buffer_t b;
/* Round up prefix len to a multiple of 8 */
INSIST(i < DNS_EDNSOPTIONS);
family = 1;
if ((plen % 8) != 0)
} else {
family = 2;
}
/* Mask off last address byte */
/* family */
isc_buffer_putuint16(&b, family);
/* source prefix-length */
isc_buffer_putuint8(&b, plen);
/* scope prefix-length */
isc_buffer_putuint8(&b, 0);
/* address */
if (addrl > 0)
isc_buffer_putmem(&b, addr,
(unsigned)addrl);
i++;
}
if (query->send_cookie) {
INSIST(i < DNS_EDNSOPTIONS);
isc_buffer_t b;
&b);
} else {
}
i++;
}
INSIST(i < DNS_EDNSOPTIONS);
i++;
}
if (query->ednsoptscnt != 0) {
i += query->ednsoptscnt;
}
}
options = 0;
if (tcp_mode)
return ISC_R_SUCCESS;
}
static void
{
}
if (onfly == 0)
return;
}
ISC_PLATFORM_NORETURN_PRE static void
usage(void) ISC_PLATFORM_NORETURN_POST;
static void
usage(void) {
fputs("Usage: mdig @server {global-opt} host\n"
" {local-opt} [ host {local-opt} [...]]\n",
stderr);
fputs("\nUse \"mdig -h\" (or \"mdig -h | more\") "
"for complete list of options\n",
stderr);
exit(1);
}
/*% help */
static void
help(void) {
fputs("Usage: mdig @server {global-opt} host\n"
" {local-opt} [ host {local-opt} [...]]\n",
stdout);
"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"
" -p port (specify port number)\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"
" local opt is one of:\n"
" -c class (specify query class)\n"
" -t type (specify query type)\n"
" -i (use IP6.INT for IPv6 reverse lookups)\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",
stdout);
}
static char *
char *res;
do {
break;
} while (*res == '\0');
return (res);
}
ISC_PLATFORM_NORETURN_PRE static void
static void
exit(-2);
}
static isc_result_t
isc_uint32_t n;
if (result != ISC_R_SUCCESS) {
return (result);
}
*uip = n;
return (ISC_R_SUCCESS);
}
static isc_result_t
const char *desc) {
}
static isc_result_t
const char *desc) {
}
static unsigned char ednsoptscnt = 0;
static void
isc_buffer_t b;
if (ednsoptscnt == EDNSOPTS)
fatal("too many ednsopts");
if (result != ISC_R_SUCCESS)
char *buf;
fatal("out of memory");
}
if (query->ednsoptscnt == 0)
query->ednsoptscnt++;
ednsoptscnt++;
}
static isc_result_t
char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:XXX.XXX.XXX.XXX/128")];
*slash = '\0';
if (result != ISC_R_SUCCESS) {
fatal("invalid prefix length in '%s': %s\n",
}
netmask = 0;
}
fatal("out of memory");
if (netmask > 128)
netmask = 128;
if (netmask > 32)
netmask = 32;
} else if (netmask != 0xffffffff) {
int i;
break;
}
}
if (netmask > 32)
netmask = 32;
}
if (!parsed)
return (ISC_R_SUCCESS);
}
/*%
* Append 'len' bytes of 'text' at '*p', failing with
* ISC_R_NOSPACE if that would advance p past 'end'.
*/
static isc_result_t
return (ISC_R_NOSPACE);
*p += len;
return (ISC_R_SUCCESS);
}
static isc_result_t
int len;
} else {
}
}
static void
{
int r;
if (r > 0) {
/* This is a valid IPv6 address. */
unsigned int options = 0;
if (ip6_int)
return;
} else {
/*
* Not a valid IPv6 address. Assume IPv4.
* Construct the in-addr.arpa name by blindly
* reversing octets whether or not they look like
* integers, so that this can be used for RFC2317
* names and such.
*/
char *p = reverse;
/* Append .in-addr.arpa. and a terminating NUL. */
return;
}
}
/*%
* We're not using isc_commandline_parse() here since the command line
* syntax of mdig is quite a bit different from that which can be described
* by that routine.
* XXX doc options
*/
static void
{
char option_store[256];
size_t n;
ptr = option_store;
return;
}
cmd += 2;
}
#define FULLCHECK(A) \
do { \
goto invalid_option; \
} while (0)
#define FULLCHECK2(A, B) \
do { \
goto invalid_option; \
} while (0)
#define GLOBAL() \
do { \
if (!global) \
goto global_option; \
} while (0)
switch (cmd[0]) {
case 'a':
switch (cmd[1]) {
case 'a': /* aaonly / aaflag */
break;
case 'd':
switch (cmd[2]) {
case 'd': /* additional */
FULLCHECK("additional");
break;
case 'f': /* adflag */
case '\0': /* +ad is a synonym for +adflag */
FULLCHECK("adflag");
break;
default:
goto invalid_option;
}
break;
case 'l': /* all */
FULLCHECK("all");
GLOBAL();
break;
case 'n': /* answer */
FULLCHECK("answer");
GLOBAL();
break;
case 'u': /* authority */
FULLCHECK("authority");
GLOBAL();
break;
default:
goto invalid_option;
}
break;
case 'b':
switch (cmd[1]) {
case 'e':/* besteffort */
FULLCHECK("besteffort");
GLOBAL();
besteffort = state;
break;
case 'u':/* bufsize */
FULLCHECK("bufsize");
goto need_value;
if (!state)
goto invalid_option;
"buffer size");
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");
break;
default:
goto invalid_option;
}
break;
case 'l': /* cl */
FULLCHECK("cl");
GLOBAL();
break;
case 'o': /* comments */
switch (cmd[2]) {
case 'm':
FULLCHECK("comments");
GLOBAL();
break;
case 'o':
FULLCHECK("cookie");
sizeof(hexcookie));
if (n >= sizeof(hexcookie))
fatal("COOKIE data too large");
} else
break;
default:
goto invalid_option;
}
break;
case 'r':
FULLCHECK("crypto");
GLOBAL();
break;
default:
goto invalid_option;
}
break;
case 'd':
switch (cmd[1]) {
case 'n': /* dnssec */
FULLCHECK("dnssec");
break;
case 's': /* dscp */
FULLCHECK("dscp");
GLOBAL();
if (!state) {
dscp = -1;
break;
}
goto need_value;
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) {
break;
}
break;
}
255,
"edns");
CHECK("parse_uint(edns)",
result);
break;
case 'f':
FULLCHECK("ednsflags");
if (!state) {
break;
}
break;
}
0xffff,
"ednsflags");
CHECK("parse_xint(ednsflags)",
result);
break;
case 'o':
FULLCHECK("ednsopt");
if (!state) {
query->ednsoptscnt = 0;
break;
}
fatal("ednsopt no "
"code point "
"specified");
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
case 'x':
FULLCHECK("expire");
break;
default:
goto invalid_option;
}
break;
case 'm': /* multiline */
FULLCHECK("multiline");
GLOBAL();
break;
case 'n':
FULLCHECK("nsid");
break;
case 'q':
FULLCHECK("question");
GLOBAL();
break;
case 'r':
switch (cmd[1]) {
case 'e':
switch (cmd[2]) {
case 'c': /* recurse */
FULLCHECK("recurse");
break;
case 't': /* retry / retries */
goto need_value;
if (!state)
goto invalid_option;
MAXTRIES - 1,
"udpretries");
query->udpretries++;
break;
default:
goto invalid_option;
}
break;
case 'r':
FULLCHECK("rrcomments");
GLOBAL();
break;
default:
goto invalid_option;
}
break;
case 's':
switch (cmd[1]) {
case 'h':
FULLCHECK("short");
GLOBAL();
if (state) {
}
break;
case 'p': /* split */
FULLCHECK("split");
GLOBAL();
goto invalid_option;
if (!state) {
display_splitwidth = 0;
break;
break;
1023, "split");
if (display_splitwidth % 4 != 0) {
"a multiple of 4; adjusting "
"to %d\n",
}
/*
* 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 (display_splitwidth)
display_splitwidth += 3;
break;
case 'u': /* subnet */
FULLCHECK("subnet");
goto need_value;
if (!state) {
}
break;
}
break;
default:
goto invalid_option;
}
break;
case 't':
switch (cmd[1]) {
case 'c': /* tcp */
FULLCHECK("tcp");
GLOBAL();
break;
case 'i': /* timeout */
FULLCHECK("timeout");
goto need_value;
if (!state)
goto invalid_option;
MAXTIMEOUT, "timeout");
break;
case 'r':
FULLCHECK("tries");
goto need_value;
if (!state)
goto invalid_option;
MAXTRIES, "udpretries");
if (query->udpretries == 0)
break;
case 't':
switch (cmd[2]) {
case 'l':
switch (cmd[3]) {
case 0:
case 'i': /* ttlid */
GLOBAL();
display_ttl = state;
break;
case 'u': /* ttlunits */
FULLCHECK("ttlunits");
GLOBAL();
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
default:
goto invalid_option;
}
break;
case 'u':
switch (cmd[1]) {
case 'd':
FULLCHECK("udptimeout");
goto need_value;
if (!state)
goto invalid_option;
MAXTIMEOUT, "udptimeout");
break;
case 'n':
FULLCHECK("unknownformat");
break;
default:
goto invalid_option;
}
break;
case 'v':
FULLCHECK("vc");
GLOBAL();
break;
case 'z': /* zflag */
FULLCHECK("zflag");
break;
break;
default:
usage();
}
return;
}
/*%
* #ISC_TRUE returned if value was used
*/
static const char *single_dash_opts = "46hiv";
/*static const char *dash_opts = "46bcfhiptvx";*/
static isc_boolean_t
{
char opt;
const char *value;
char *hash;
/*
* Since the -[46hiv] options do not take an argument,
* if they appear as the first character(s) of an opt.
*/
switch (opt) {
case '4':
GLOBAL();
if (have_ipv4) {
} else {
fatal("can't find IPv4 networking");
/* NOTREACHED */
return (ISC_FALSE);
}
break;
case '6':
GLOBAL();
if (have_ipv6) {
} else {
fatal("can't find IPv6 networking");
/* NOTREACHED */
return (ISC_FALSE);
}
break;
case 'h':
help();
exit(0);
break;
case 'i':
break;
case 'v':
exit(0);
break;
}
else
return (ISC_FALSE);
}
} else {
}
goto invalid_option;
switch (opt) {
case 'b':
GLOBAL();
"port number");
*hash = '\0';
} else
srcport = 0;
} else {
*hash = '#';
}
*hash = '#';
return (value_from_next);
case 'c':
(isc_textregion_t *)&tr);
return (value_from_next);
case 'f':
return (value_from_next);
case 'p':
GLOBAL();
return (value_from_next);
case 't':
(isc_textregion_t *)&tr);
return (value_from_next);
case 'x':
return (value_from_next);
usage();
default:
usage();
}
/* NOTREACHED */
return (ISC_FALSE);
}
static struct query *
fatal("memory allocation failure in %s:%d",
fatal("memory allocation failure in %s:%d",
}
return query;
}
static void
{
int bargc;
char *bargv[64];
int rc;
char **rv;
char *input;
/*
* 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 query" which won't actually be used
* anywhere, except for cloning into new queries
*/
if (!is_batchfile) {
default_query.textname[0] = 0;
default_query.udpsize = 0;
default_query.ednsoptscnt = 0;
default_query.ednsflags = 0;
default_query.timeout = 0;
default_query.udptimeout = 0;
}
if (is_batchfile) {
/* Processing '-f batchfile'. */
query = clone_default_query();
} else
query = &default_query;
break;
if (rv[0][0] == '@') {
} else if (rv[0][0] == '+') {
} else if (rv[0][0] == '-') {
if (rc <= 1) {
rc--;
rv++;
}
} else {
rc--;
rv++;
}
}
if (setname) {
if (query == &default_query)
query = clone_default_query();
default_query.textname[0] = 0;
query = clone_default_query();
}
} else {
/*
* Anything which isn't an option
*/
if (query == &default_query)
query = clone_default_query();
query = clone_default_query();
/* XXX Error message */
}
}
/*
* If we have a batchfile, read the query list from it.
*/
else
}
bargc = 1;
continue;
bargc++;
}
}
}
if (query != &default_query) {
}
}
/*% Main processing routine for mdig */
int
int ns;
if (isc_net_probeipv4() == ISC_R_SUCCESS)
if (isc_net_probeipv6() == ISC_R_SUCCESS)
fatal("could not find either IPv4 or IPv6");
sizeof(cookie_secret), NULL, 0));
fatal("a server '@xxx' is required");
ns = 0;
}
fatal("can't choose between IPv4 and IPv6");
dispatchmgr = NULL;
if (have_ipv4) {
} else {
}
dispatchvx = NULL;
4096, 100, 100, 17, 19,
requestmgr = NULL;
&requestmgr));
(void)isc_app_run();
}
}
return (0);
}