#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 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;
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));
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) {
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
(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;
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;
if
(*value ==
'@'
) {
free
(global_config.servers);
global_config.servers = strdup(value + 1);
return
ARES_TRUE;
}
if
(*value ==
'-'
|| *value ==
'+'
) {
snprintf(global_config.error,
sizeof
(global_config.error),
"unrecognized argument %s"
, value);
return
ARES_FALSE;
}
if
(ares_dns_class_fromstr(&global_config.qclass, value)) {
return
ARES_TRUE;
}
if
(ares_dns_rec_type_fromstr(&global_config.qtype, value)) {
return
ARES_TRUE;
}
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
{
char
prefix;
const
char
*name;
char
separator;
opt_type_t type;
void
*opt;
dig_opt_cb_t cb;
} dig_options[] = {
{
'-'
,
"c"
, 0, OPT_TYPE_FUNC, NULL, opt_class_cb },
{
'-'
,
"h"
, 0, OPT_TYPE_BOOL, &global_config.is_help, NULL },
{
'-'
,
"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 },
{
'-'
,
"x"
, 0, OPT_TYPE_FUNC, NULL, opt_ptr_cb },
{
'+'
,
"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;
if
(dig_options[opt].prefix != 0 &&
dig_options[opt].prefix != *(argv[arg])) {
continue
;
}
nameptr = argv[arg];
if
(dig_options[opt].prefix != 0) {
nameptr++;
}
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);
}
if
(dig_options[opt].name != NULL &&
!ares_streq_max(nameptr, dig_options[opt].name, namelen)) {
continue
;
}
if
(dig_options[opt].name == NULL) {
value = nameptr;
}
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;
}
}
if
(global_config.opts.display_command) {
printf
(
"\n; <<>> c-ares DiG %s <<>>"
, ares_version(NULL));
printf
(
" %s"
, global_config.name);
printf
(
"\n"
);
}
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;
}
rv = event_loop(channel);
done:
free_config();
ares_destroy(channel);
ares_library_cleanup();
#ifdef USE_WINSOCK
WSACleanup();
#endif
return
rv;
}