delv.c revision 0c27b3fe77ac1d5094ba3521e8142d9e7973133f
/*
* Copyright (C) 2014-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 <bind.keys.h>
#ifndef WIN32
#include <signal.h>
#include <netdb.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef WIN32
#endif
#include <isc/parseint.h>
#include <isc/sockaddr.h>
#include <isccfg/namedconf.h>
#include <dns/fixedname.h>
#include <dns/keytable.h>
#include <dns/keyvalues.h>
#include <dns/masterdump.h>
#include <dns/rdataclass.h>
#include <dns/rdataset.h>
#include <dns/rdatastruct.h>
#include <dns/rdatatype.h>
#define CHECK(r) \
do { \
result = (r); \
if (result != ISC_R_SUCCESS) \
goto cleanup; \
} while (0)
/* Variables used internally by delv. */
char *progname;
/* Configurables */
static const char *port = "53";
static unsigned int styleflags = 0;
static isc_boolean_t
static isc_boolean_t
static isc_boolean_t
static isc_boolean_t
static char *anchorfile = NULL;
static char *trust_anchor = NULL;
static char *dlv_anchor = NULL;
static int trusted_keys = 0;
/* Default bind.keys contents */
static char anchortext[] = MANAGED_KEYS;
/*
* Static function prototypes
*/
static isc_result_t
static isc_result_t
const char *desc);
static void
usage(void) {
"Usage: delv [@server] {q-opt} {d-opt} [domain] [q-type] [q-class]\n"
"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"
" q-opt is one of:\n"
" -x dot-notation (shortcut for reverse lookups)\n"
" -d level (set debugging level)\n"
" -a anchor-file (specify root and dlv trust anchors)\n"
" -p port (specify port number)\n"
" -q name (specify query name)\n"
" -t type (specify query type)\n"
" -c class (option included for compatibility;\n"
" only IN is supported)\n"
" -4 (use IPv4 query transport only)\n"
" -6 (use IPv6 query transport only)\n"
" -i (disable DNSSEC validation)\n"
" -m (enable memory usage debugging)\n"
" d-opt is of the form +keyword[=value], where keyword is:\n"
" +[no]all (Set or clear all display flags)\n"
" +[no]class (Control display of class)\n"
" +[no]crypto (Control display of cryptographic\n"
" fields in records)\n"
" +[no]multiline (Print records in an expanded format)\n"
" +[no]comments (Control display of comment lines)\n"
" +[no]rrcomments (Control display of per-record "
"comments)\n"
" +[no]unknownformat (Print RDATA in RFC 3597 \"unknown\" format)\n"
" +[no]short (Short form answer)\n"
" +[no]tcp (TCP mode)\n"
" +[no]ttl (Control display of ttls in records)\n"
" +[no]trust (Control display of trust level)\n"
" +[no]rtrace (Trace resolver fetches)\n"
" +[no]mtrace (Trace messages received)\n"
" +[no]vtrace (Trace validation process)\n"
" +[no]dlv (DNSSEC lookaside validation anchor)\n"
" +[no]root (DNSSEC validation trust anchor)\n"
" +[no]dnssec (Display DNSSEC records)\n"
" -h (print help and exit)\n"
" -v (print version and exit)\n",
stderr);
exit(1);
}
ISC_PLATFORM_NORETURN_PRE static void
static void
exit(1);
}
static void
static void
}
static isc_logcategory_t categories[] = {
{ "delv", 0 },
{ NULL, 0 }
};
#define LOGCATEGORY_DEFAULT (&categories[0])
#define LOGMODULE_DEFAULT (&modules[0])
static isc_logmodule_t modules[] = {
{ "delv", 0 },
{ NULL, 0 }
};
static void
static void
char msgbuf[2048];
return;
}
static int loglevel = 0;
static void
if (result != ISC_R_SUCCESS)
fatal("Couldn't set up logging");
if (result != ISC_R_SUCCESS)
fatal("Couldn't set up log channel 'stderr'");
if (result != ISC_R_SUCCESS)
fatal("Couldn't set log tag");
if (result != ISC_R_SUCCESS)
fatal("Couldn't attach to log channel 'stderr'");
ISC_LOG_DEBUG(1),
if (result != ISC_R_SUCCESS)
fatal("Couldn't set up log channel 'resolver'");
if (result != ISC_R_SUCCESS)
fatal("Couldn't attach to log channel 'resolver'");
}
ISC_LOG_DEBUG(3),
if (result != ISC_R_SUCCESS)
fatal("Couldn't set up log channel 'validator'");
if (result != ISC_R_SUCCESS)
fatal("Couldn't attach to log channel 'validator'");
}
ISC_LOG_DEBUG(10),
if (result != ISC_R_SUCCESS)
fatal("Couldn't set up log channel 'messages'");
if (result != ISC_R_SUCCESS)
fatal("Couldn't attach to log channel 'messagse'");
}
}
static void
return;
astr = "negative response, ";
case dns_trust_none:
tstr = "untrusted";
break;
tstr = "signed additional data, pending validation";
break;
case dns_trust_pending_answer:
tstr = "signed answer, pending validation";
break;
case dns_trust_additional:
tstr = "unsigned additional data";
break;
case dns_trust_glue:
tstr = "glue data";
break;
case dns_trust_answer:
if (root_validation || dlv_validation)
tstr = "unsigned answer";
else
tstr = "answer not validated";
break;
case dns_trust_authauthority:
tstr = "authority data";
break;
case dns_trust_authanswer:
tstr = "authoritative";
break;
case dns_trust_secure:
tstr = "fully validated";
break;
case dns_trust_ultimate:
tstr = "ultimate trust";
break;
}
}
static isc_result_t
{
static dns_trust_t trust;
isc_region_t r;
char *t = NULL;
int len = 2048;
if (!dns_rdataset_isassociated(rdataset)) {
char namebuf[DNS_NAME_FORMATSIZE];
"WARN: empty rdataset %s", namebuf);
return (ISC_R_SUCCESS);
}
return (ISC_R_SUCCESS);
putchar('\n');
}
do {
if (t == NULL)
return (ISC_R_NOMEMORY);
if (short_form) {
result == ISC_R_SUCCESS;
{
if ((rdataset->attributes &
DNS_RDATASETATTR_NEGATIVE) != 0)
continue;
styleflags, 0,
splitwidth, " ",
&target);
if (result != ISC_R_SUCCESS)
break;
break;
}
}
} else {
if ((rdataset->attributes &
DNS_RDATASETATTR_NEGATIVE) != 0)
}
if (result == ISC_R_NOSPACE) {
len += 1024;
} else if (result == ISC_R_NOMORE)
else
} while (result == ISC_R_NOSPACE);
isc_buffer_usedregion(&target, &r);
if (t != NULL)
return (ISC_R_SUCCESS);
}
static isc_result_t
if (showcomments)
if (print_unknown_format)
if (rrcomments)
if (nottl)
if (noclass)
if (nocrypto)
if (multiline) {
}
24, 24, 24, 32, 80, 8,
splitwidth, mctx);
24, 24, 32, 40, 80, 8,
splitwidth, mctx);
else
24, 32, 40, 48, 80, 8,
splitwidth, mctx);
if (result == ISC_R_SUCCESS)
return (result);
}
static isc_result_t
isc_buffer_t b;
dns_name_t *n;
unsigned int len;
isc_buffer_add(&b, len);
n = dns_fixedname_name(fn);
if (result != ISC_R_SUCCESS) {
return (result);
}
*name = n;
return (ISC_R_SUCCESS);
}
static isc_result_t
const char *keystr, *keynamestr;
unsigned char keydata[4096];
unsigned char rrdata[4096];
isc_region_t r;
if (!root_validation && !dlv_validation)
return (ISC_R_SUCCESS);
if (!match_root && !match_dlv)
return (ISC_R_SUCCESS);
return (ISC_R_SUCCESS);
if (match_root)
if (match_dlv)
/*
* The key data in keystruct is not dynamically allocated.
*/
if (flags > 0xffff)
if (proto > 0xff)
if (alg > 0xff)
isc_buffer_usedregion(&keydatabuf, &r);
trusted_keys++;
if (result == DST_R_NOCRYPTO)
else if (result == DST_R_UNSUPPORTEDALG) {
"skipping trusted key '%s': %s",
} else if (result != ISC_R_SUCCESS) {
"failed to add trusted key '%s': %s",
}
return (result);
}
static isc_result_t
{
{
}
}
if (result == DST_R_NOCRYPTO)
return (result);
}
static isc_result_t
const char *filename = anchorfile;
if (!root_validation && !dlv_validation)
return (ISC_R_SUCCESS);
#ifndef WIN32
#else
#endif
}
if (trust_anchor == NULL) {
if (trust_anchor == NULL)
fatal("out of memory");
}
if (dlv_anchor == NULL) {
if (dlv_anchor == NULL)
fatal("out of memory");
}
if (anchorfile != NULL)
} else {
if (result != ISC_R_SUCCESS)
if (anchorfile != NULL)
fatal("Unable to load keys from '%s'",
}
isc_buffer_t b;
&bindkeys);
if (result != ISC_R_SUCCESS)
fatal("Unable to parse built-in keys");
}
if (managed_keys != NULL)
if (trusted_keys == 0)
fatal("No trusted keys were loaded");
if (dlv_validation)
if (result != ISC_R_SUCCESS)
return (result);
}
static isc_result_t
int gai_error;
if (result != ISC_R_SUCCESS)
fatal("Couldn't parse port number");
return (ISC_R_NOMEMORY);
return (ISC_R_NOMEMORY);
} else {
if (!use_ipv6)
else if (!use_ipv4)
else
if (gai_error != 0) {
"getaddrinfo failed: %s",
return (ISC_R_FAILURE);
}
continue;
break;
}
}
}
while (!ISC_LIST_EMPTY(servers)) {
}
if (result != ISC_R_SUCCESS)
return (result);
}
static isc_result_t
if (result != ISC_R_SUCCESS)
fatal("Couldn't parse port number");
goto cleanup;
}
/* Get nameservers from resolv.conf */
/* Set destination port */
continue;
}
continue;
}
/* Incompatible protocol family */
}
/* None found, use localhost */
if (ISC_LIST_EMPTY(*nameservers)) {
if (use_ipv4) {
goto cleanup;
}
}
if (use_ipv6) {
goto cleanup;
}
}
}
if (result != ISC_R_SUCCESS)
return (result);
}
static char *
char *res;
do {
break;
} while (*res == '\0');
return (res);
}
static isc_result_t
const char *desc) {
isc_uint32_t n;
if (result != ISC_R_SUCCESS) {
return (result);
}
*uip = n;
return (ISC_R_SUCCESS);
}
static void
plus_option(char *option) {
char option_store[256];
ptr = option_store;
return;
}
cmd += 2;
}
#define FULLCHECK(A) \
do { \
goto invalid_option; \
} while (0)
switch (cmd[0]) {
case 'a': /* all */
FULLCHECK("all");
rrcomments = state;
break;
case 'c':
switch (cmd[1]) {
case 'd': /* cdflag */
FULLCHECK("cdflag");
break;
case 'l': /* class */
FULLCHECK("class");
break;
case 'o': /* comments */
FULLCHECK("comments");
break;
case 'r': /* crypto */
FULLCHECK("crypto");
break;
default:
goto invalid_option;
}
break;
case 'd':
switch (cmd[1]) {
case 'l': /* dlv */
FULLCHECK("dlv");
break;
if (dlv_anchor == NULL)
fatal("out of memory");
}
break;
case 'n': /* dnssec */
FULLCHECK("dnssec");
showdnssec = state;
break;
default:
goto invalid_option;
}
break;
case 'm':
switch (cmd[1]) {
case 't': /* mtrace */
if (state)
break;
case 'u': /* multiline */
FULLCHECK("multiline");
break;
default:
goto invalid_option;
}
break;
case 'r':
switch (cmd[1]) {
case 'o': /* root */
FULLCHECK("root");
break;
if (trust_anchor == NULL)
fatal("out of memory");
}
break;
case 'r': /* rrcomments */
FULLCHECK("rrcomments");
rrcomments = state;
break;
case 't': /* rtrace */
FULLCHECK("rtrace");
break;
default:
goto invalid_option;
}
break;
case 's':
switch (cmd[1]) {
case 'h': /* short */
FULLCHECK("short");
short_form = state;
if (short_form) {
}
break;
case 'p': /* split */
FULLCHECK("split");
goto invalid_option;
if (!state) {
splitwidth = 0;
break;
break;
1023, "split");
if (splitwidth % 4 != 0) {
warn("split must be a multiple of 4; "
"adjusting to %d", 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)
fatal("Couldn't parse split");
break;
default:
goto invalid_option;
}
break;
case 'u':
FULLCHECK("unknownformat");
break;
case 't':
switch (cmd[1]) {
case 'c': /* tcp */
FULLCHECK("tcp");
break;
case 'r': /* trust */
FULLCHECK("trust");
break;
case 't': /* ttl */
FULLCHECK("ttl");
break;
default:
goto invalid_option;
}
break;
case 'v': /* vtrace */
FULLCHECK("vtrace");
if (state)
break;
default:
/*
* We can also add a "need_value:" case here if we ever
* add a plus-option that requires a specified value
*/
usage();
}
return;
}
/*
* options: "46a:b:c:d:himp:q:t:vx:";
*/
static const char *single_dash_opts = "46himv";
static isc_boolean_t
char *hash;
/*
* Since the -[46himv] options do not take an argument,
* if they appear as the first character(s) of a q-opt.
*/
switch (opt) {
case '4':
if (isc_net_probeipv4() != ISC_R_SUCCESS)
fatal("IPv4 networking not available");
if (use_ipv6) {
}
break;
case '6':
if (isc_net_probeipv6() != ISC_R_SUCCESS)
fatal("IPv6 networking not available");
if (use_ipv4) {
}
break;
case 'h':
usage();
exit(0);
/* NOTREACHED */
case 'i':
break;
case 'm':
/* handled in preparse_args() */
break;
case 'v':
exit(0);
/* NOTREACHED */
default:
INSIST(0);
}
else
return (ISC_FALSE);
}
} else {
}
goto invalid_option;
switch (opt) {
case 'a':
if (anchorfile == NULL)
fatal("out of memory");
return (value_from_next);
case 'b':
if (result != ISC_R_SUCCESS)
fatal("Couldn't parse port number");
*hash = '\0';
} else
srcport = 0;
fatal("Only one local address per family "
"can be specified\n");
fatal("Only one local address per family "
"can be specified\n");
} else {
*hash = '#';
}
*hash = '#';
return (value_from_next);
case 'c':
if (classset)
warn("extra query class");
(isc_textregion_t *)&tr);
if (result == ISC_R_SUCCESS)
else if (rdclass != dns_rdataclass_in)
warn("ignoring non-IN query class");
else
warn("ignoring invalid class");
return (value_from_next);
case 'd':
if (result != ISC_R_SUCCESS)
fatal("Couldn't parse debug level");
return (value_from_next);
case 'p':
return (value_from_next);
case 'q':
warn("extra query name");
}
fatal("out of memory");
return (value_from_next);
case 't':
(isc_textregion_t *)&tr);
if (result == ISC_R_SUCCESS) {
if (typeset)
warn("extra query type");
if (rdtype == dns_rdatatype_ixfr ||
fatal("Transfer not supported");
} else
warn("ignoring invalid type");
return (value_from_next);
case 'x':
if (result == ISC_R_SUCCESS) {
warn("extra query name");
}
fatal("out of memory");
if (typeset)
warn("extra query type");
} else {
exit(1);
}
return (value_from_next);
default:
usage();
}
/* NOTREACHED */
return (ISC_FALSE);
}
/*
* Check for -m first to determine whether to enable
* memory debugging when setting up the memory context.
*/
static void
char *option;
if (argv[0][0] != '-')
continue;
if (option[0] == 'm') {
return;
}
}
}
}
/*
* Argument parsing is based on dig, but simplified: only one
* been removed that aren't applicable to delv. The interface
* should be familiar to dig users, however.
*/
static void
if (argv[0][0] == '@') {
} else if (argv[0][0] == '+') {
} else if (argv[0][0] == '-') {
if (argc <= 1) {
{
argc--;
argv++;
}
} else {
{
argc--;
argv++;
}
}
} else {
/*
* Anything which isn't an option
*/
if (open_type_class) {
(isc_textregion_t *)&tr);
if (result == ISC_R_SUCCESS) {
if (typeset)
warn("extra query type");
if (rdtype == dns_rdatatype_ixfr ||
fatal("Transfer not supported");
continue;
}
(isc_textregion_t *)&tr);
if (result == ISC_R_SUCCESS) {
if (classset)
warn("extra query class");
else if (rdclass != dns_rdataclass_in)
warn("ignoring non-IN "
"query class");
continue;
}
}
fatal("out of memory");
}
}
}
/*
* If no qtype specified, use A
*/
if (!typeset)
fatal("out of memory");
if (!typeset)
} else
}
static isc_result_t
return (ISC_R_NOSPACE);
*p += len;
return (ISC_R_SUCCESS);
}
static isc_result_t
int len;
if (result != ISC_R_SUCCESS)
return (result);
if (result != ISC_R_SUCCESS)
return (result);
} else
}
static isc_result_t
int r;
if (r > 0) {
/* This is a valid IPv6 address. */
unsigned int options = 0;
if (result != ISC_R_SUCCESS)
return (result);
return (ISC_R_SUCCESS);
} else {
/*
* Not a valid IPv6 address. Assume IPv4.
* If 'strict' is not set, 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;
return (DNS_R_BADDOTTEDQUAD);
if (result != ISC_R_SUCCESS)
return (result);
if (result != ISC_R_SUCCESS)
return (result);
return (ISC_R_SUCCESS);
}
}
int
#ifndef WIN32
#endif
argc--;
argv++;
result = dns_lib_init();
if (result != ISC_R_SUCCESS)
if (result != ISC_R_SUCCESS)
fatal("failed to create mctx");
#ifndef WIN32
/* Unblock SIGINT if it's been blocked by isc_app_ctxstart() */
fatal("Couldn't set up signal handler");
#endif
/* Create client */
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
/* Set the nameserver */
else
/* Construct QNAME */
/* Set up resolution options */
if (no_sigs)
if (!root_validation && !dlv_validation)
if (cdflag)
if (use_tcp)
/* Perform resolution */
if (result != ISC_R_SUCCESS)
response_name != NULL;
if (result != ISC_R_SUCCESS)
}
}
if (dlv_anchor != NULL)
if (trust_anchor != NULL)
if (anchorfile != NULL)
return (0);
}