/
githubmirror
/
c-ares
Обзор
Документация
Войти
/
githubmirror
/
c-ares
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/tools/adig.c
1 591 строка
48 KB
Brad House
clang-format: reformat src/ and include/, and stop changed-lines false positives on bulk reformats (#1238)
06 июл 2026, 18:19
Не верифицирован
06 июл 2026, 18:19
a13b89c
Код
Авторство
О чём код?
/* 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 #define RV_OK 0 /* Success */ #define RV_SYSERR 1 /* Internal system failure */ #define RV_MISUSE 2 /* Misuse (command line) */ #define RV_FAIL 3 /* Resolution failure */ static int final_rv = RV_OK; 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; case ARES_OPT_DATATYPE_UTF8_STR: /* True UTF8 handling not implemented here, but in practice this is * good enough. */ print_opt_binp(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 (status != ARES_SUCCESS) { final_rv = RV_FAIL; } 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 (dig_options[opt].prefix == '+' && 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; #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 RV_SYSERR; } config_defaults(); if (!read_cmdline(argc, (const char * const *)argv, 1)) { printf("\n** ERROR: %s\n\n", global_config.error); print_help(); final_rv = RV_MISUSE; 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(); final_rv = RV_MISUSE; 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)); final_rv = RV_SYSERR; 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); final_rv = RV_MISUSE; 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)); final_rv = RV_MISUSE; goto done; } /* Process events */ if (event_loop(channel) != 0) { final_rv = RV_SYSERR; } done: free_config(); ares_destroy(channel); ares_library_cleanup(); #ifdef USE_WINSOCK WSACleanup(); #endif return final_rv; }