The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

/* MIT License
*
* Copyright (c) 1998 Massachusetts Institute of Technology
* Copyright (c) The c-ares project and its contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#include "ares_setup.h"
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
#ifdef HAVE_NETDB_H
# include <netdb.h>
#endif
#include "ares_nameser.h"
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include "ares.h"
#include "ares_array.h"
#include "ares_buf.h"
#include "ares_dns.h"
#include "ares_getopt.h"
#include "ares_mem.h"
#include "ares_str.h"
#include "limits.h"
#ifndef PATH_MAX
# define PATH_MAX 1024
#endif
typedef struct {
unsigned short port;
size_t tries;
size_t ndots;
ares_bool_t tcp;
ares_bool_t ignore_tc;
char *search;
ares_bool_t do_search;
ares_bool_t aa_flag;
ares_bool_t ad_flag;
ares_bool_t cd_flag;
ares_bool_t rd_flag;
/* ares_bool_t do_flag; */
ares_bool_t edns;
size_t udp_size;
ares_bool_t primary;
ares_bool_t aliases;
ares_bool_t stayopen;
ares_bool_t dns0x20;
ares_bool_t display_class;
ares_bool_t display_ttl;
ares_bool_t display_command;
ares_bool_t display_stats;
ares_bool_t display_query;
ares_bool_t display_question;
ares_bool_t display_answer;
ares_bool_t display_authority;
ares_bool_t display_additional;
ares_bool_t display_comments;
} dns_options_t;
typedef struct {
dns_options_t opts;
ares_bool_t is_help;
ares_bool_t no_rcfile;
struct ares_options options;
int optmask;
ares_dns_class_t qclass;
ares_dns_rec_type_t qtype;
char *name;
char *servers;
char error[256];
} adig_config_t;
static adig_config_t global_config;
static const char *helpstr[] = {
"usage: adig [@server] [-c class] [-p port#] [-q name] [-t type] [-x addr]",
" [name] [type] [class] [queryopt...]",
"",
"@server: server ip address. May specify multiple in comma delimited "
"format.",
" may be specified in URI format",
"name: name of the resource record that is to be looked up",
"type: what type of query is required. e.g. - A, AAAA, MX, TXT, etc. If",
" no specified, A will be used.",
"class: Sets the query class, defaults to IN. May also be HS or CH.",
"",
"FLAGS",
"-c class: Sets the query class, defaults to IN. May also be HS or CH.",
"-h: Prints this help.",
"-p port: Sends query to a port other than 53. Often recommended to set",
" the port using @server instead.",
"-q name: Specifies the domain name to query. Useful to distinguish name",
" from other arguments",
"-r: Skip adigrc processing",
"-s: Server (alias for @server syntax), compatibility with old cmdline",
"-t type: Indicates resource record type to query. Useful to distinguish",
" type from other arguments",
"-x addr: Simplified reverse lookups. Sets the type to PTR and forms a",
" valid in-arpa query string",
"",
"QUERY OPTIONS",
"+[no]aaonly: Sets the aa flag in the query. Default is off.",
"+[no]aaflag: Alias for +[no]aaonly",
"+[no]additional: Toggles printing the additional section. On by default.",
"+[no]adflag: Sets the ad (authentic data) bit in the query. Default is",
" off.",
"+[no]aliases: Whether or not to honor the HOSTALIASES file. Default is",
" on.",
"+[no]all: Toggles all of +[no]cmd, +[no]stats, +[no]question,",
" +[no]answer, +[no]authority, +[no]additional, "
"+[no]comments",
"+[no]answer: Toggles printing the answer. On by default.",
"+[no]authority: Toggles printing the authority. On by default.",
"+bufsize=#: UDP EDNS 0 packet size allowed. Defaults to 1232.",
"+[no]cdflag: Sets the CD (checking disabled) bit in the query. Default",
" is off.",
"+[no]class: Display the class when printing the record. On by "
"default.",
"+[no]cmd: Toggles printing the command requested. On by default.",
"+[no]comments: Toggles printing the comments. On by default",
"+[no]defname: Alias for +[no]search",
"+domain=somename: Sets the search list to a single domain.",
"+[no]dns0x20: Whether or not to use DNS 0x20 case randomization when",
" sending queries. Default is off.",
"+[no]edns[=#]: Enable or disable EDNS. Only allows a value of 0 if",
" specified. Default is to enable EDNS.",
"+[no]ignore: Ignore truncation on UDP, by default retried on TCP.",
"+[no]keepopen: Whether or not the server connection should be "
"persistent.",
" Default is off.",
"+ndots=#: Sets the number of dots that must appear before being",
" considered absolute. Defaults to 1.",
"+[no]primary: Whether or not to only use a single server if more than "
"one",
" server is available. Defaults to using all servers.",
"+[no]qr: Toggles printing the request query. Off by default.",
"+[no]question: Toggles printing the question. On by default.",
"+[no]recurse: Toggles the RD (Recursion Desired) bit. On by default.",
"+retry=#: Same as +tries but does not include the initial attempt.",
"+[no]search: To use or not use the search list. Search list is not "
"used",
" by default.",
"+[no]stats: Toggles printing the statistics. On by default.",
"+[no]tcp: Whether to use TCP when querying name servers. Default is",
" UDP.",
"+tries=#: Number of query tries. Defaults to 3.",
"+[no]ttlid: Display the TTL when printing the record. On by default.",
"+[no]vc: Alias for +[no]tcp",
"",
NULL
};
static void free_config(void)
{
free(global_config.servers);
free(global_config.name);
free(global_config.opts.search);
memset(&global_config, 0, sizeof(global_config));
}
static void print_help(void)
{
size_t i;
printf("adig version %s\n\n", ares_version(NULL));
for (i = 0; helpstr[i] != NULL; i++) {
printf("%s\n", helpstr[i]);
}
}
static void print_flags(ares_dns_flags_t flags)
{
if (flags & ARES_FLAG_QR) {
printf(" qr");
}
if (flags & ARES_FLAG_AA) {
printf(" aa");
}
if (flags & ARES_FLAG_TC) {
printf(" tc");
}
if (flags & ARES_FLAG_RD) {
printf(" rd");
}
if (flags & ARES_FLAG_RA) {
printf(" ra");
}
if (flags & ARES_FLAG_AD) {
printf(" ad");
}
if (flags & ARES_FLAG_CD) {
printf(" cd");
}
}
static void print_header(const ares_dns_record_t *dnsrec)
{
printf(";; ->>HEADER<<- opcode: %s, status: %s, id: %u\n",
ares_dns_opcode_tostr(ares_dns_record_get_opcode(dnsrec)),
ares_dns_rcode_tostr(ares_dns_record_get_rcode(dnsrec)),
ares_dns_record_get_id(dnsrec));
printf(";; flags:");
print_flags(ares_dns_record_get_flags(dnsrec));
printf("; QUERY: %u, ANSWER: %u, AUTHORITY: %u, ADDITIONAL: %u\n\n",
(unsigned int)ares_dns_record_query_cnt(dnsrec),
(unsigned int)ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER),
(unsigned int)ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_AUTHORITY),
(unsigned int)ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ADDITIONAL));
}
static void print_question(const ares_dns_record_t *dnsrec)
{
size_t i;
if (global_config.opts.display_comments) {
printf(";; QUESTION SECTION:\n");
}
for (i = 0; i < ares_dns_record_query_cnt(dnsrec); i++) {
const char *name;
ares_dns_rec_type_t qtype;
ares_dns_class_t qclass;
size_t len;
if (ares_dns_record_query_get(dnsrec, i, &name, &qtype, &qclass) !=
ARES_SUCCESS) {
return;
}
if (name == NULL) {
return;
}
len = strlen(name);
printf(";%s.\t", name);
if (len + 1 < 24) {
printf("\t");
}
if (len + 1 < 16) {
printf("\t");
}
if (global_config.opts.display_class) {
printf("%s\t", ares_dns_class_tostr(qclass));
}
printf("%s\n", ares_dns_rec_type_tostr(qtype));
}
if (global_config.opts.display_comments) {
printf("\n");
}
}
static void print_opt_none(const unsigned char *val, size_t val_len)
{
(void)val;
if (val_len != 0) {
printf("INVALID!");
}
}
static void print_opt_addr_list(const unsigned char *val, size_t val_len)
{
size_t i;
if (val_len % 4 != 0) {
printf("INVALID!");
return;
}
for (i = 0; i < val_len; i += 4) {
char buf[256] = "";
ares_inet_ntop(AF_INET, val + i, buf, sizeof(buf));
if (i != 0) {
printf(",");
}
printf("%s", buf);
}
}
static void print_opt_addr6_list(const unsigned char *val, size_t val_len)
{
size_t i;
if (val_len % 16 != 0) {
printf("INVALID!");
return;
}
for (i = 0; i < val_len; i += 16) {
char buf[256] = "";
ares_inet_ntop(AF_INET6, val + i, buf, sizeof(buf));
if (i != 0) {
printf(",");
}
printf("%s", buf);
}
}
static void print_opt_u8_list(const unsigned char *val, size_t val_len)
{
size_t i;
for (i = 0; i < val_len; i++) {
if (i != 0) {
printf(",");
}
printf("%u", (unsigned int)val[i]);
}
}
static void print_opt_u16_list(const unsigned char *val, size_t val_len)
{
size_t i;
if (val_len < 2 || val_len % 2 != 0) {
printf("INVALID!");
return;
}
for (i = 0; i < val_len; i += 2) {
unsigned short u16 = 0;
unsigned short c;
/* Jumping over backwards to try to avoid odd compiler warnings */
c = (unsigned short)val[i];
u16 |= (unsigned short)((c << 8) & 0xFFFF);
c = (unsigned short)val[i + 1];
u16 |= c;
if (i != 0) {
printf(",");
}
printf("%u", (unsigned int)u16);
}
}
static void print_opt_u32_list(const unsigned char *val, size_t val_len)
{
size_t i;
if (val_len < 4 || val_len % 4 != 0) {
printf("INVALID!");
return;
}
for (i = 0; i < val_len; i += 4) {
unsigned int u32 = 0;
u32 |= (unsigned int)(val[i] << 24);
u32 |= (unsigned int)(val[i + 1] << 16);
u32 |= (unsigned int)(val[i + 2] << 8);
u32 |= (unsigned int)(val[i + 3]);
if (i != 0) {
printf(",");
}
printf("%u", u32);
}
}
static void print_opt_str_list(const unsigned char *val, size_t val_len)
{
size_t cnt = 0;
printf("\"");
while (val_len) {
long read_len = 0;
unsigned char *str = NULL;
ares_status_t status;
if (cnt) {
printf(",");
}
status = (ares_status_t)ares_expand_string(val, val, (int)val_len, &str,
&read_len);
if (status != ARES_SUCCESS) {
printf("INVALID");
break;
}
printf("%s", str);
ares_free_string(str);
val_len -= (size_t)read_len;
val += read_len;
cnt++;
}
printf("\"");
}
static void print_opt_name(const unsigned char *val, size_t val_len)
{
char *str = NULL;
long read_len = 0;
if (ares_expand_name(val, val, (int)val_len, &str, &read_len) !=
ARES_SUCCESS) {
printf("INVALID!");
return;
}
printf("%s.", str);
ares_free_string(str);
}
static void print_opt_bin(const unsigned char *val, size_t val_len)
{
size_t i;
for (i = 0; i < val_len; i++) {
printf("%02x", (unsigned int)val[i]);
}
}
static ares_bool_t adig_isprint(int ch)
{
if (ch >= 0x20 && ch <= 0x7E) {
return ARES_TRUE;
}
return ARES_FALSE;
}
static void print_opt_binp(const unsigned char *val, size_t val_len)
{
size_t i;
printf("\"");
for (i = 0; i < val_len; i++) {
if (adig_isprint(val[i])) {
printf("%c", val[i]);
} else {
printf("\\%03d", val[i]);
}
}
printf("\"");
}
static void print_opts(const ares_dns_rr_t *rr, ares_dns_rr_key_t key)
{
size_t i;
for (i = 0; i < ares_dns_rr_get_opt_cnt(rr, key); i++) {
size_t val_len = 0;
const unsigned char *val = NULL;
unsigned short opt;
const char *name;
if (i != 0) {
printf(" ");
}
opt = ares_dns_rr_get_opt(rr, key, i, &val, &val_len);
name = ares_dns_opt_get_name(key, opt);
if (name == NULL) {
printf("key%u", (unsigned int)opt);
} else {
printf("%s", name);
}
if (val_len == 0) {
return;
}
printf("=");
switch (ares_dns_opt_get_datatype(key, opt)) {
case ARES_OPT_DATATYPE_NONE:
print_opt_none(val, val_len);
break;
case ARES_OPT_DATATYPE_U8_LIST:
print_opt_u8_list(val, val_len);
break;
case ARES_OPT_DATATYPE_INADDR4_LIST:
print_opt_addr_list(val, val_len);
break;
case ARES_OPT_DATATYPE_INADDR6_LIST:
print_opt_addr6_list(val, val_len);
break;
case ARES_OPT_DATATYPE_U16:
case ARES_OPT_DATATYPE_U16_LIST:
print_opt_u16_list(val, val_len);
break;
case ARES_OPT_DATATYPE_U32:
case ARES_OPT_DATATYPE_U32_LIST:
print_opt_u32_list(val, val_len);
break;
case ARES_OPT_DATATYPE_STR_LIST:
print_opt_str_list(val, val_len);
break;
case ARES_OPT_DATATYPE_BIN:
print_opt_bin(val, val_len);
break;
case ARES_OPT_DATATYPE_NAME:
print_opt_name(val, val_len);
break;
}
}
}
static void print_addr(const ares_dns_rr_t *rr, ares_dns_rr_key_t key)
{
const struct in_addr *addr = ares_dns_rr_get_addr(rr, key);
char buf[256] = "";
ares_inet_ntop(AF_INET, addr, buf, sizeof(buf));
printf("%s", buf);
}
static void print_addr6(const ares_dns_rr_t *rr, ares_dns_rr_key_t key)
{
const struct ares_in6_addr *addr = ares_dns_rr_get_addr6(rr, key);
char buf[256] = "";
ares_inet_ntop(AF_INET6, addr, buf, sizeof(buf));
printf("%s", buf);
}
static void print_u8(const ares_dns_rr_t *rr, ares_dns_rr_key_t key)
{
unsigned char u8 = ares_dns_rr_get_u8(rr, key);
printf("%u", (unsigned int)u8);
}
static void print_u16(const ares_dns_rr_t *rr, ares_dns_rr_key_t key)
{
unsigned short u16 = ares_dns_rr_get_u16(rr, key);
printf("%u", (unsigned int)u16);
}
static void print_u32(const ares_dns_rr_t *rr, ares_dns_rr_key_t key)
{
unsigned int u32 = ares_dns_rr_get_u32(rr, key);
printf("%u", u32);
}
static void print_name(const ares_dns_rr_t *rr, ares_dns_rr_key_t key)
{
const char *str = ares_dns_rr_get_str(rr, key);
printf("%s.", str);
}
static void print_str(const ares_dns_rr_t *rr, ares_dns_rr_key_t key)
{
const char *str = ares_dns_rr_get_str(rr, key);
printf("\"%s\"", str);
}
static void print_bin(const ares_dns_rr_t *rr, ares_dns_rr_key_t key)
{
size_t len = 0;
const unsigned char *binp = ares_dns_rr_get_bin(rr, key, &len);
print_opt_bin(binp, len);
}
static void print_binp(const ares_dns_rr_t *rr, ares_dns_rr_key_t key)
{
size_t len;
const unsigned char *binp = ares_dns_rr_get_bin(rr, key, &len);
print_opt_binp(binp, len);
}
static void print_abinp(const ares_dns_rr_t *rr, ares_dns_rr_key_t key)
{
size_t i;
size_t cnt = ares_dns_rr_get_abin_cnt(rr, key);
for (i = 0; i < cnt; i++) {
size_t len;
const unsigned char *binp = ares_dns_rr_get_abin(rr, key, i, &len);
if (i != 0) {
printf(" ");
}
print_opt_binp(binp, len);
}
}
static void print_rr(const ares_dns_rr_t *rr)
{
const char *name = ares_dns_rr_get_name(rr);
size_t len = 0;
size_t keys_cnt = 0;
ares_dns_rec_type_t rtype = ares_dns_rr_get_type(rr);
const ares_dns_rr_key_t *keys = ares_dns_rr_get_keys(rtype, &keys_cnt);
size_t i;
if (name == NULL) {
return;
}
len = strlen(name);
printf("%s.\t", name);
if (len < 24) {
printf("\t");
}
if (global_config.opts.display_ttl) {
printf("%u\t", ares_dns_rr_get_ttl(rr));
}
if (global_config.opts.display_class) {
printf("%s\t", ares_dns_class_tostr(ares_dns_rr_get_class(rr)));
}
printf("%s\t", ares_dns_rec_type_tostr(rtype));
/* Output params here */
for (i = 0; i < keys_cnt; i++) {
ares_dns_datatype_t datatype = ares_dns_rr_key_datatype(keys[i]);
if (i != 0) {
printf(" ");
}
switch (datatype) {
case ARES_DATATYPE_INADDR:
print_addr(rr, keys[i]);
break;
case ARES_DATATYPE_INADDR6:
print_addr6(rr, keys[i]);
break;
case ARES_DATATYPE_U8:
print_u8(rr, keys[i]);
break;
case ARES_DATATYPE_U16:
print_u16(rr, keys[i]);
break;
case ARES_DATATYPE_U32:
print_u32(rr, keys[i]);
break;
case ARES_DATATYPE_NAME:
print_name(rr, keys[i]);
break;
case ARES_DATATYPE_STR:
print_str(rr, keys[i]);
break;
case ARES_DATATYPE_BIN:
print_bin(rr, keys[i]);
break;
case ARES_DATATYPE_BINP:
print_binp(rr, keys[i]);
break;
case ARES_DATATYPE_ABINP:
print_abinp(rr, keys[i]);
break;
case ARES_DATATYPE_OPT:
print_opts(rr, keys[i]);
break;
}
}
printf("\n");
}
static const ares_dns_rr_t *has_opt(const ares_dns_record_t *dnsrec,
ares_dns_section_t section)
{
size_t i;
for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, section); i++) {
const ares_dns_rr_t *rr = ares_dns_record_rr_get_const(dnsrec, section, i);
if (ares_dns_rr_get_type(rr) == ARES_REC_TYPE_OPT) {
return rr;
}
}
return NULL;
}
static void print_section(const ares_dns_record_t *dnsrec,
ares_dns_section_t section)
{
size_t i;
if (ares_dns_record_rr_cnt(dnsrec, section) == 0 ||
(ares_dns_record_rr_cnt(dnsrec, section) == 1 &&
has_opt(dnsrec, section) != NULL)) {
return;
}
if (global_config.opts.display_comments) {
printf(";; %s SECTION:\n", ares_dns_section_tostr(section));
}
for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, section); i++) {
const ares_dns_rr_t *rr = ares_dns_record_rr_get_const(dnsrec, section, i);
if (ares_dns_rr_get_type(rr) == ARES_REC_TYPE_OPT) {
continue;
}
print_rr(rr);
}
if (global_config.opts.display_comments) {
printf("\n");
}
}
static void print_opt_psuedosection(const ares_dns_record_t *dnsrec)
{
const ares_dns_rr_t *rr = has_opt(dnsrec, ARES_SECTION_ADDITIONAL);
const unsigned char *cookie = NULL;
size_t cookie_len = 0;
if (rr == NULL) {
return;
}
if (!ares_dns_rr_get_opt_byid(rr, ARES_RR_OPT_OPTIONS, ARES_OPT_PARAM_COOKIE,
&cookie, &cookie_len)) {
cookie = NULL;
}
printf(";; OPT PSEUDOSECTION:\n");
printf("; EDNS: version: %u, flags: %u; udp: %u\n",
(unsigned int)ares_dns_rr_get_u8(rr, ARES_RR_OPT_VERSION),
(unsigned int)ares_dns_rr_get_u16(rr, ARES_RR_OPT_FLAGS),
(unsigned int)ares_dns_rr_get_u16(rr, ARES_RR_OPT_UDP_SIZE));
if (cookie) {
printf("; COOKIE: ");
print_opt_bin(cookie, cookie_len);
printf(" (good)\n");
}
}
static void print_record(const ares_dns_record_t *dnsrec)
{
if (global_config.opts.display_comments) {
print_header(dnsrec);
print_opt_psuedosection(dnsrec);
}
if (global_config.opts.display_question) {
print_question(dnsrec);
}
if (global_config.opts.display_answer) {
print_section(dnsrec, ARES_SECTION_ANSWER);
}
if (global_config.opts.display_additional) {
print_section(dnsrec, ARES_SECTION_ADDITIONAL);
}
if (global_config.opts.display_authority) {
print_section(dnsrec, ARES_SECTION_AUTHORITY);
}
if (global_config.opts.display_stats) {
unsigned char *abuf = NULL;
size_t alen = 0;
ares_dns_write(dnsrec, &abuf, &alen);
printf(";; MSG SIZE rcvd: %d\n\n", (int)alen);
ares_free_string(abuf);
}
}
static void callback(void *arg, ares_status_t status, size_t timeouts,
const ares_dns_record_t *dnsrec)
{
(void)arg;
(void)timeouts;
if (global_config.opts.display_comments) {
/* We got a "Server status" */
if (status >= ARES_SUCCESS && status <= ARES_EREFUSED) {
printf(";; Got answer:");
} else {
printf(";;");
}
if (status != ARES_SUCCESS) {
printf(" %s", ares_strerror((int)status));
}
printf("\n");
}
print_record(dnsrec);
}
static ares_status_t enqueue_query(ares_channel_t *channel)
{
ares_dns_record_t *dnsrec = NULL;
ares_dns_rr_t *rr = NULL;
ares_status_t status;
unsigned short flags = 0;
char *nametemp = NULL;
const char *name = global_config.name;
if (global_config.opts.aa_flag) {
flags |= ARES_FLAG_AA;
}
if (global_config.opts.ad_flag) {
flags |= ARES_FLAG_AD;
}
if (global_config.opts.cd_flag) {
flags |= ARES_FLAG_CD;
}
if (global_config.opts.rd_flag) {
flags |= ARES_FLAG_RD;
}
status = ares_dns_record_create(&dnsrec, 0, flags, ARES_OPCODE_QUERY,
ARES_RCODE_NOERROR);
if (status != ARES_SUCCESS) {
goto done;
}
/* If it is a PTR record, convert from ip address into in-arpa form
* automatically */
if (global_config.qtype == ARES_REC_TYPE_PTR) {
struct ares_addr addr;
size_t len;
addr.family = AF_UNSPEC;
if (ares_dns_pton(name, &addr, &len) != NULL) {
nametemp = ares_dns_addr_to_ptr(&addr);
name = nametemp;
}
}
status = ares_dns_record_query_add(dnsrec, name, global_config.qtype,
global_config.qclass);
if (status != ARES_SUCCESS) {
goto done;
}
if (global_config.opts.edns) {
status = ares_dns_record_rr_add(&rr, dnsrec, ARES_SECTION_ADDITIONAL, "",
ARES_REC_TYPE_OPT, ARES_CLASS_IN, 0);
if (status != ARES_SUCCESS) {
goto done;
}
ares_dns_rr_set_u16(rr, ARES_RR_OPT_UDP_SIZE,
(unsigned short)global_config.opts.udp_size);
ares_dns_rr_set_u8(rr, ARES_RR_OPT_VERSION, 0);
}
if (global_config.opts.display_query) {
printf(";; Sending:\n");
print_record(dnsrec);
}
if (global_config.opts.do_search) {
status = ares_search_dnsrec(channel, dnsrec, callback, NULL);
} else {
status = ares_send_dnsrec(channel, dnsrec, callback, NULL, NULL);
}
done:
ares_free_string(nametemp);
ares_dns_record_destroy(dnsrec);
return status;
}
static int event_loop(ares_channel_t *channel)
{
while (1) {
fd_set read_fds;
fd_set write_fds;
int nfds;
struct timeval tv;
struct timeval *tvp;
int count;
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
memset(&tv, 0, sizeof(tv));
nfds = ares_fds(channel, &read_fds, &write_fds);
if (nfds == 0) {
break;
}
tvp = ares_timeout(channel, NULL, &tv);
if (tvp == NULL) {
break;
}
count = select(nfds, &read_fds, &write_fds, NULL, tvp);
if (count < 0) {
#ifdef USE_WINSOCK
int err = WSAGetLastError();
#else
int err = errno;
#endif
if (err != EAGAIN && err != EINTR) {
fprintf(stderr, "select fail: %d", err);
return 1;
}
}
ares_process(channel, &read_fds, &write_fds);
}
return 0;
}
typedef enum {
OPT_TYPE_BOOL,
OPT_TYPE_STRING,
OPT_TYPE_SIZE_T,
OPT_TYPE_U16,
OPT_TYPE_FUNC
} opt_type_t;
/* Callback called with OPT_TYPE_FUNC when processing options.
* \param[in] prefix prefix character for option
* \param[in] name name for option
* \param[in] is_true ARES_TRUE unless option was prefixed with 'no'
* \param[in] value value for option
* \return ARES_TRUE on success, ARES_FALSE on failure. Should fill in
* global_config.error on error */
typedef ares_bool_t (*dig_opt_cb_t)(char prefix, const char *name,
ares_bool_t is_true, const char *value);
static ares_bool_t opt_class_cb(char prefix, const char *name,
ares_bool_t is_true, const char *value)
{
(void)prefix;
(void)name;
(void)is_true;
if (!ares_dns_class_fromstr(&global_config.qclass, value)) {
snprintf(global_config.error, sizeof(global_config.error),
"unrecognized class %s", value);
return ARES_FALSE;
}
return ARES_TRUE;
}
static ares_bool_t opt_type_cb(char prefix, const char *name,
ares_bool_t is_true, const char *value)
{
(void)prefix;
(void)name;
(void)is_true;
if (!ares_dns_rec_type_fromstr(&global_config.qtype, value)) {
snprintf(global_config.error, sizeof(global_config.error),
"unrecognized record type %s", value);
return ARES_FALSE;
}
return ARES_TRUE;
}
static ares_bool_t opt_ptr_cb(char prefix, const char *name,
ares_bool_t is_true, const char *value)
{
(void)prefix;
(void)name;
(void)is_true;
global_config.qtype = ARES_REC_TYPE_PTR;
ares_free(global_config.name);
global_config.name = strdup(value);
return ARES_TRUE;
}
static ares_bool_t opt_all_cb(char prefix, const char *name,
ares_bool_t is_true, const char *value)
{
(void)prefix;
(void)name;
(void)value;
global_config.opts.display_command = is_true;
global_config.opts.display_stats = is_true;
global_config.opts.display_question = is_true;
global_config.opts.display_answer = is_true;
global_config.opts.display_authority = is_true;
global_config.opts.display_additional = is_true;
global_config.opts.display_comments = is_true;
return ARES_TRUE;
}
static ares_bool_t opt_edns_cb(char prefix, const char *name,
ares_bool_t is_true, const char *value)
{
(void)prefix;
(void)name;
global_config.opts.edns = is_true;
if (is_true && value != NULL && atoi(value) > 0) {
snprintf(global_config.error, sizeof(global_config.error),
"edns 0 only supported");
return ARES_FALSE;
}
return ARES_TRUE;
}
static ares_bool_t opt_retry_cb(char prefix, const char *name,
ares_bool_t is_true, const char *value)
{
(void)prefix;
(void)name;
(void)is_true;
if (!ares_str_isnum(value)) {
snprintf(global_config.error, sizeof(global_config.error),
"value not numeric");
return ARES_FALSE;
}
global_config.opts.tries = strtoul(value, NULL, 10) + 1;
return ARES_TRUE;
}
static ares_bool_t opt_dig_bare_cb(char prefix, const char *name,
ares_bool_t is_true, const char *value)
{
(void)prefix;
(void)name;
(void)is_true;
/* Handle @servers */
if (*value == '@') {
free(global_config.servers);
global_config.servers = strdup(value + 1);
return ARES_TRUE;
}
/* Make sure we don't pass options */
if (*value == '-' || *value == '+') {
snprintf(global_config.error, sizeof(global_config.error),
"unrecognized argument %s", value);
return ARES_FALSE;
}
/* See if it is a DNS class */
if (ares_dns_class_fromstr(&global_config.qclass, value)) {
return ARES_TRUE;
}
/* See if it is a DNS record type */
if (ares_dns_rec_type_fromstr(&global_config.qtype, value)) {
return ARES_TRUE;
}
/* See if it is a domain name */
if (ares_is_hostname(value)) {
free(global_config.name);
global_config.name = strdup(value);
return ARES_TRUE;
}
snprintf(global_config.error, sizeof(global_config.error),
"unrecognized argument %s", value);
return ARES_FALSE;
}
static const struct {
/* Prefix for option. If 0 then this param is a non-option and type must be
* OPT_TYPE_FUNC where the entire value for the param will be passed */
char prefix;
/* Name of option. If null, there is none and the value is expected to be
* immediately after the prefix character */
const char *name;
/* Separator between key and value. If 0 then uses the next argument as the
* value, otherwise splits on the separator. BOOL types won't ever use a
* separator and is ignored.*/
char separator;
/* Type of parameter passed in. If it is OPT_TYPE_FUNC, then it calls the
* dig_opt_cb_t callback */
opt_type_t type;
/* Pointer to argument to fill in */
void *opt;
/* Callback if OPT_TYPE_FUNC */
dig_opt_cb_t cb;
} dig_options[] = {
/* -4 (ipv4 only) */
/* -6 (ipv6 only) */
/* { '-', "b", 0, OPT_TYPE_FUNC, NULL, opt_bind_address_cb },
*/
{ '-', "c", 0, OPT_TYPE_FUNC, NULL, opt_class_cb },
/* -f file */
{ '-', "h", 0, OPT_TYPE_BOOL, &global_config.is_help, NULL },
/* -k keyfile */
/* -m (memory usage debugging) */
{ '-', "p", 0, OPT_TYPE_U16, &global_config.opts.port, NULL },
{ '-', "q", 0, OPT_TYPE_STRING, &global_config.name, NULL },
{ '-', "r", 0, OPT_TYPE_BOOL, &global_config.no_rcfile, NULL },
{ '-', "s", 0, OPT_TYPE_STRING, &global_config.servers, NULL },
{ '-', "t", 0, OPT_TYPE_FUNC, NULL, opt_type_cb },
/* -u (print microseconds instead of milliseconds) */
{ '-', "x", 0, OPT_TYPE_FUNC, NULL, opt_ptr_cb },
/* -y [hmac:]keynam:secret */
{ '+', "aaflag", 0, OPT_TYPE_BOOL, &global_config.opts.aa_flag, NULL },
{ '+', "aaonly", 0, OPT_TYPE_BOOL, &global_config.opts.aa_flag, NULL },
{ '+', "additional", 0, OPT_TYPE_BOOL, &global_config.opts.display_additional,
NULL },
{ '+', "adflag", 0, OPT_TYPE_BOOL, &global_config.opts.ad_flag, NULL },
{ '+', "aliases", 0, OPT_TYPE_BOOL, &global_config.opts.aliases, NULL },
{ '+', "all", '=', OPT_TYPE_FUNC, NULL, opt_all_cb },
{ '+', "answer", 0, OPT_TYPE_BOOL, &global_config.opts.display_answer, NULL },
{ '+', "authority", 0, OPT_TYPE_BOOL, &global_config.opts.display_authority,
NULL },
{ '+', "bufsize", '=', OPT_TYPE_SIZE_T, &global_config.opts.udp_size, NULL },
{ '+', "cdflag", 0, OPT_TYPE_BOOL, &global_config.opts.cd_flag, NULL },
{ '+', "class", 0, OPT_TYPE_BOOL, &global_config.opts.display_class, NULL },
{ '+', "cmd", 0, OPT_TYPE_BOOL, &global_config.opts.display_command, NULL },
{ '+', "comments", 0, OPT_TYPE_BOOL, &global_config.opts.display_comments,
NULL },
{ '+', "defname", 0, OPT_TYPE_BOOL, &global_config.opts.do_search, NULL },
{ '+', "dns0x20", 0, OPT_TYPE_BOOL, &global_config.opts.dns0x20, NULL },
{ '+', "domain", '=', OPT_TYPE_STRING, &global_config.opts.search, NULL },
{ '+', "edns", '=', OPT_TYPE_FUNC, NULL, opt_edns_cb },
{ '+', "keepopen", 0, OPT_TYPE_BOOL, &global_config.opts.stayopen, NULL },
{ '+', "ignore", 0, OPT_TYPE_BOOL, &global_config.opts.ignore_tc, NULL },
{ '+', "ndots", '=', OPT_TYPE_SIZE_T, &global_config.opts.ndots, NULL },
{ '+', "primary", 0, OPT_TYPE_BOOL, &global_config.opts.primary, NULL },
{ '+', "qr", 0, OPT_TYPE_BOOL, &global_config.opts.display_query, NULL },
{ '+', "question", 0, OPT_TYPE_BOOL, &global_config.opts.display_question,
NULL },
{ '+', "recurse", 0, OPT_TYPE_BOOL, &global_config.opts.rd_flag, NULL },
{ '+', "retry", '=', OPT_TYPE_FUNC, NULL, opt_retry_cb },
{ '+', "search", 0, OPT_TYPE_BOOL, &global_config.opts.do_search, NULL },
{ '+', "stats", 0, OPT_TYPE_BOOL, &global_config.opts.display_stats, NULL },
{ '+', "tcp", 0, OPT_TYPE_BOOL, &global_config.opts.tcp, NULL },
{ '+', "tries", '=', OPT_TYPE_SIZE_T, &global_config.opts.tries, NULL },
{ '+', "ttlid", 0, OPT_TYPE_BOOL, &global_config.opts.display_ttl, NULL },
{ '+', "vc", 0, OPT_TYPE_BOOL, &global_config.opts.tcp, NULL },
{ 0, NULL, 0, OPT_TYPE_FUNC, NULL, opt_dig_bare_cb },
{ 0, NULL, 0, 0, NULL, NULL }
};
static ares_bool_t read_cmdline(int argc, const char * const *argv,
int start_idx)
{
int arg;
size_t opt;
for (arg = start_idx; arg < argc; arg++) {
ares_bool_t option_handled = ARES_FALSE;
for (opt = 0; !option_handled &&
(dig_options[opt].opt != NULL || dig_options[opt].cb != NULL);
opt++) {
ares_bool_t is_true = ARES_TRUE;
const char *value = NULL;
const char *nameptr = NULL;
size_t namelen;
/* Match prefix character */
if (dig_options[opt].prefix != 0 &&
dig_options[opt].prefix != *(argv[arg])) {
continue;
}
nameptr = argv[arg];
/* skip prefix */
if (dig_options[opt].prefix != 0) {
nameptr++;
}
/* Negated option if it has a 'no' prefix */
if (ares_streq_max(nameptr, "no", 2)) {
is_true = ARES_FALSE;
nameptr += 2;
}
if (dig_options[opt].separator != 0) {
const char *ptr = strchr(nameptr, dig_options[opt].separator);
if (ptr == NULL) {
namelen = ares_strlen(nameptr);
} else {
namelen = (size_t)(ptr - nameptr);
value = ptr + 1;
}
} else {
namelen = ares_strlen(nameptr);
}
/* Match name */
if (dig_options[opt].name != NULL &&
!ares_streq_max(nameptr, dig_options[opt].name, namelen)) {
continue;
}
if (dig_options[opt].name == NULL) {
value = nameptr;
}
/* We need another argument for the value */
if (dig_options[opt].type != OPT_TYPE_BOOL &&
dig_options[opt].prefix != 0 && dig_options[opt].separator == 0) {
if (arg == argc - 1) {
snprintf(global_config.error, sizeof(global_config.error),
"insufficient arguments for %c%s", dig_options[opt].prefix,
dig_options[opt].name);
return ARES_FALSE;
}
arg++;
value = argv[arg];
}
switch (dig_options[opt].type) {
case OPT_TYPE_BOOL:
{
ares_bool_t *b = dig_options[opt].opt;
if (b == NULL) {
snprintf(global_config.error, sizeof(global_config.error),
"invalid use for %c%s", dig_options[opt].prefix,
dig_options[opt].name);
return ARES_FALSE;
}
*b = is_true;
}
break;
case OPT_TYPE_STRING:
{
char **str = dig_options[opt].opt;
if (str == NULL) {
snprintf(global_config.error, sizeof(global_config.error),
"invalid use for %c%s", dig_options[opt].prefix,
dig_options[opt].name);
return ARES_FALSE;
}
if (value == NULL) {
snprintf(global_config.error, sizeof(global_config.error),
"missing value for %c%s", dig_options[opt].prefix,
dig_options[opt].name);
return ARES_FALSE;
}
if (*str != NULL) {
free(*str);
}
*str = strdup(value);
break;
}
case OPT_TYPE_SIZE_T:
{
size_t *s = dig_options[opt].opt;
if (s == NULL) {
snprintf(global_config.error, sizeof(global_config.error),
"invalid use for %c%s", dig_options[opt].prefix,
dig_options[opt].name);
return ARES_FALSE;
}
if (value == NULL) {
snprintf(global_config.error, sizeof(global_config.error),
"missing value for %c%s", dig_options[opt].prefix,
dig_options[opt].name);
return ARES_FALSE;
}
if (!ares_str_isnum(value)) {
snprintf(global_config.error, sizeof(global_config.error),
"%c%s is not a numeric value", dig_options[opt].prefix,
dig_options[opt].name);
return ARES_FALSE;
}
*s = strtoul(value, NULL, 10);
break;
}
case OPT_TYPE_U16:
{
unsigned short *s = dig_options[opt].opt;
if (s == NULL) {
snprintf(global_config.error, sizeof(global_config.error),
"invalid use for %c%s", dig_options[opt].prefix,
dig_options[opt].name);
return ARES_FALSE;
}
if (value == NULL) {
snprintf(global_config.error, sizeof(global_config.error),
"missing value for %c%s", dig_options[opt].prefix,
dig_options[opt].name);
return ARES_FALSE;
}
if (!ares_str_isnum(value)) {
snprintf(global_config.error, sizeof(global_config.error),
"%c%s is not a numeric value", dig_options[opt].prefix,
dig_options[opt].name);
return ARES_FALSE;
}
*s = (unsigned short)strtoul(value, NULL, 10);
break;
}
case OPT_TYPE_FUNC:
if (dig_options[opt].cb == NULL) {
snprintf(global_config.error, sizeof(global_config.error),
"missing callback");
return ARES_FALSE;
}
if (!dig_options[opt].cb(dig_options[opt].prefix,
dig_options[opt].name, is_true, value)) {
return ARES_FALSE;
}
break;
}
option_handled = ARES_TRUE;
}
if (!option_handled) {
snprintf(global_config.error, sizeof(global_config.error),
"unrecognized option %s", argv[arg]);
return ARES_FALSE;
}
}
return ARES_TRUE;
}
static ares_bool_t read_rcfile(void)
{
char configdir[PATH_MAX];
unsigned int cdlen = 0;
#if !defined(WIN32)
# if !defined(__APPLE__)
char *configdir_xdg;
# endif
char *homedir;
#endif
char rcfile[PATH_MAX];
unsigned int rclen;
size_t rcargc;
char **rcargv;
ares_buf_t *rcbuf;
ares_status_t rcstatus;
#if defined(WIN32)
cdlen = (unsigned int)snprintf(configdir, sizeof(configdir), "%s/%s",
getenv("APPDATA"), "c-ares");
#elif defined(__APPLE__)
homedir = getenv("HOME");
if (homedir != NULL) {
cdlen = (unsigned int)snprintf(configdir, sizeof(configdir), "%s/%s/%s/%s",
homedir, "Library", "Application Support",
"c-ares");
}
#else
configdir_xdg = getenv("XDG_CONFIG_HOME");
if (configdir_xdg == NULL) {
homedir = getenv("HOME");
if (homedir != NULL) {
cdlen = (unsigned int)snprintf(configdir, sizeof(configdir), "%s/%s",
homedir, ".config");
}
} else {
cdlen =
(unsigned int)snprintf(configdir, sizeof(configdir), "%s", configdir_xdg);
}
#endif
DEBUGF(fprintf(stderr, "read_cmdline() configdir: %s\n", configdir));
if (cdlen == 0 || cdlen > sizeof(configdir)) {
DEBUGF(
fprintf(stderr, "read_cmdline() skipping rcfile parsing on directory\n"));
return ARES_TRUE;
}
rclen =
(unsigned int)snprintf(rcfile, sizeof(rcfile), "%s/adigrc", configdir);
if (rclen > sizeof(rcfile)) {
DEBUGF(fprintf(stderr, "read_cmdline() skipping rcfile parsing on file\n"));
return ARES_TRUE;
}
rcbuf = ares_buf_create();
if (ares_buf_load_file(rcfile, rcbuf) == ARES_SUCCESS) {
rcstatus = ares_buf_split_str(rcbuf, (const unsigned char *)"\n ", 2,
ARES_BUF_SPLIT_TRIM, 0, &rcargv, &rcargc);
if (rcstatus == ARES_SUCCESS) {
read_cmdline((int)rcargc, (const char * const *)rcargv, 0);
} else {
snprintf(global_config.error, sizeof(global_config.error),
"rcfile is invalid: %s", ares_strerror((int)rcstatus));
}
ares_free_array(rcargv, rcargc, ares_free);
if (rcstatus != ARES_SUCCESS) {
ares_buf_destroy(rcbuf);
return ARES_FALSE;
}
} else {
DEBUGF(fprintf(stderr, "read_cmdline() failed to load rcfile"));
}
ares_buf_destroy(rcbuf);
return ARES_TRUE;
}
static void config_defaults(void)
{
memset(&global_config, 0, sizeof(global_config));
global_config.opts.tries = 3;
global_config.opts.ndots = 1;
global_config.opts.rd_flag = ARES_TRUE;
global_config.opts.edns = ARES_TRUE;
global_config.opts.udp_size = 1232;
global_config.opts.aliases = ARES_TRUE;
global_config.opts.display_class = ARES_TRUE;
global_config.opts.display_ttl = ARES_TRUE;
global_config.opts.display_command = ARES_TRUE;
global_config.opts.display_stats = ARES_TRUE;
global_config.opts.display_question = ARES_TRUE;
global_config.opts.display_answer = ARES_TRUE;
global_config.opts.display_authority = ARES_TRUE;
global_config.opts.display_additional = ARES_TRUE;
global_config.opts.display_comments = ARES_TRUE;
global_config.qclass = ARES_CLASS_IN;
global_config.qtype = ARES_REC_TYPE_A;
}
static void config_opts(void)
{
global_config.optmask = ARES_OPT_FLAGS;
if (global_config.opts.tcp) {
global_config.options.flags |= ARES_FLAG_USEVC;
}
if (global_config.opts.primary) {
global_config.options.flags |= ARES_FLAG_PRIMARY;
}
if (global_config.opts.edns) {
global_config.options.flags |= ARES_FLAG_EDNS;
}
if (global_config.opts.stayopen) {
global_config.options.flags |= ARES_FLAG_STAYOPEN;
}
if (global_config.opts.dns0x20) {
global_config.options.flags |= ARES_FLAG_DNS0x20;
}
if (!global_config.opts.aliases) {
global_config.options.flags |= ARES_FLAG_NOALIASES;
}
if (!global_config.opts.rd_flag) {
global_config.options.flags |= ARES_FLAG_NORECURSE;
}
if (!global_config.opts.do_search) {
global_config.options.flags |= ARES_FLAG_NOSEARCH;
}
if (global_config.opts.ignore_tc) {
global_config.options.flags |= ARES_FLAG_IGNTC;
}
if (global_config.opts.port) {
global_config.optmask |= ARES_OPT_UDP_PORT;
global_config.optmask |= ARES_OPT_TCP_PORT;
global_config.options.udp_port = global_config.opts.port;
global_config.options.tcp_port = global_config.opts.port;
}
global_config.optmask |= ARES_OPT_TRIES;
global_config.options.tries = (int)global_config.opts.tries;
global_config.optmask |= ARES_OPT_NDOTS;
global_config.options.ndots = (int)global_config.opts.ndots;
global_config.optmask |= ARES_OPT_EDNSPSZ;
global_config.options.ednspsz = (int)global_config.opts.udp_size;
if (global_config.opts.search != NULL) {
global_config.optmask |= ARES_OPT_DOMAINS;
global_config.options.domains = &global_config.opts.search;
global_config.options.ndomains = 1;
}
}
int main(int argc, char **argv)
{
ares_channel_t *channel = NULL;
ares_status_t status;
int rv = 0;
#ifdef USE_WINSOCK
WORD wVersionRequested = MAKEWORD(USE_WINSOCK, USE_WINSOCK);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);
#endif
status = (ares_status_t)ares_library_init(ARES_LIB_INIT_ALL);
if (status != ARES_SUCCESS) {
fprintf(stderr, "ares_library_init: %s\n", ares_strerror((int)status));
return 1;
}
config_defaults();
if (!read_cmdline(argc, (const char * const *)argv, 1)) {
printf("\n** ERROR: %s\n\n", global_config.error);
print_help();
rv = 1;
goto done;
}
if (global_config.no_rcfile && !read_rcfile()) {
fprintf(stderr, "\n** ERROR: %s\n", global_config.error);
}
if (global_config.is_help) {
print_help();
goto done;
}
if (global_config.name == NULL) {
printf("missing query name\n");
print_help();
rv = 1;
goto done;
}
config_opts();
status = (ares_status_t)ares_init_options(&channel, &global_config.options,
global_config.optmask);
if (status != ARES_SUCCESS) {
fprintf(stderr, "ares_init_options: %s\n", ares_strerror((int)status));
rv = 1;
goto done;
}
if (global_config.servers) {
status =
(ares_status_t)ares_set_servers_ports_csv(channel, global_config.servers);
if (status != ARES_SUCCESS) {
fprintf(stderr, "ares_set_servers_ports_csv: %s: %s\n",
ares_strerror((int)status), global_config.servers);
rv = 1;
goto done;
}
}
/* Debug */
if (global_config.opts.display_command) {
printf("\n; <<>> c-ares DiG %s <<>>", ares_version(NULL));
printf(" %s", global_config.name);
printf("\n");
}
/* Enqueue a query for each separate name */
status = enqueue_query(channel);
if (status != ARES_SUCCESS) {
fprintf(stderr, "Failed to create query for %s: %s\n", global_config.name,
ares_strerror((int)status));
rv = 1;
goto done;
}
/* Process events */
rv = event_loop(channel);
done:
free_config();
ares_destroy(channel);
ares_library_cleanup();
#ifdef USE_WINSOCK
WSACleanup();
#endif
return rv;
}