busybox/networking/nslookup.c

1042 lines
26 KiB
C
Raw Normal View History

/* vi: set sw=4 ts=4: */
//config:config NSLOOKUP
//config: bool "nslookup (9.7 kb)"
//config: default y
//config: help
//config: nslookup is a tool to query Internet name servers.
//config:
//config:config FEATURE_NSLOOKUP_BIG
//config: bool "Use internal resolver code instead of libc"
//config: depends on NSLOOKUP
//config: default y
//config:
//config:config FEATURE_NSLOOKUP_LONG_OPTIONS
//config: bool "Enable long options"
//config: default y
//config: depends on FEATURE_NSLOOKUP_BIG && LONG_OPTS
//applet:IF_NSLOOKUP(APPLET(nslookup, BB_DIR_USR_BIN, BB_SUID_DROP))
//kbuild:lib-$(CONFIG_NSLOOKUP) += nslookup.o
2000-01-29 18:29:01 +05:30
//usage:#define nslookup_trivial_usage
//usage: IF_FEATURE_NSLOOKUP_BIG("[-type=QUERY_TYPE] [-debug] ") "HOST [DNS_SERVER]"
//usage:#define nslookup_full_usage "\n\n"
//usage: "Query DNS about HOST"
//usage: IF_FEATURE_NSLOOKUP_BIG("\n")
//usage: IF_FEATURE_NSLOOKUP_BIG("\nQUERY_TYPE: soa,ns,a,"IF_FEATURE_IPV6("aaaa,")"cname,mx,txt,ptr,srv,any")
//usage:#define nslookup_example_usage
//usage: "$ nslookup localhost\n"
//usage: "Server: default\n"
//usage: "Address: default\n"
//usage: "\n"
//usage: "Name: debian\n"
//usage: "Address: 127.0.0.1\n"
#include <resolv.h>
#include <net/if.h> /* for IFNAMSIZ */
//#include <arpa/inet.h>
//#include <netdb.h>
#include "libbb.h"
#include "common_bufsiz.h"
#if !ENABLE_FEATURE_NSLOOKUP_BIG
/*
* Mini nslookup implementation for busybox
*
* Copyright (C) 1999,2000 by Lineo, inc. and John Beppu
* Copyright (C) 1999,2000,2001 by John Beppu <beppu@codepoet.org>
*
* Correct default name server display and explicit name server option
* added by Ben Zeckel <bzeckel@hmc.edu> June 2001
*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
2000-01-29 18:29:01 +05:30
/*
* I'm only implementing non-interactive mode;
* I totally forgot nslookup even had an interactive mode.
*
* This applet is the only user of res_init(). Without it,
* you may avoid pulling in _res global from libc.
*/
2000-01-29 18:29:01 +05:30
2006-10-26 22:47:59 +05:30
/* Examples of 'standard' nslookup output
* $ nslookup yahoo.com
* Server: 128.193.0.10
* Address: 128.193.0.10#53
*
2006-10-26 22:47:59 +05:30
* Non-authoritative answer:
* Name: yahoo.com
* Address: 216.109.112.135
* Name: yahoo.com
* Address: 66.94.234.13
*
* $ nslookup 204.152.191.37
* Server: 128.193.4.20
* Address: 128.193.4.20#53
*
2006-10-26 22:47:59 +05:30
* Non-authoritative answer:
* 37.191.152.204.in-addr.arpa canonical name = 37.32-27.191.152.204.in-addr.arpa.
* 37.32-27.191.152.204.in-addr.arpa name = zeus-pub2.kernel.org.
*
2006-10-26 22:47:59 +05:30
* Authoritative answers can be found from:
* 32-27.191.152.204.in-addr.arpa nameserver = ns1.kernel.org.
* 32-27.191.152.204.in-addr.arpa nameserver = ns2.kernel.org.
* 32-27.191.152.204.in-addr.arpa nameserver = ns3.kernel.org.
* ns1.kernel.org internet address = 140.211.167.34
* ns2.kernel.org internet address = 204.152.191.4
* ns3.kernel.org internet address = 204.152.191.36
*/
2006-10-26 22:47:59 +05:30
static int print_host(const char *hostname, const char *header)
2000-01-29 18:29:01 +05:30
{
2007-02-04 08:09:08 +05:30
/* We can't use xhost2sockaddr() - we want to get ALL addresses,
* not just one */
struct addrinfo *result = NULL;
int rc;
struct addrinfo hint;
memset(&hint, 0 , sizeof(hint));
/* hint.ai_family = AF_UNSPEC; - zero anyway */
/* Needed. Or else we will get each address thrice (or more)
* for each possible socket type (tcp,udp,raw...): */
hint.ai_socktype = SOCK_STREAM;
// hint.ai_flags = AI_CANONNAME;
rc = getaddrinfo(hostname, NULL /*service*/, &hint, &result);
if (rc == 0) {
struct addrinfo *cur = result;
unsigned cnt = 0;
printf("%-10s %s\n", header, hostname);
2007-10-01 17:35:12 +05:30
// puts(cur->ai_canonname); ?
while (cur) {
char *dotted, *revhost;
dotted = xmalloc_sockaddr2dotted_noport(cur->ai_addr);
revhost = xmalloc_sockaddr2hostonly_noport(cur->ai_addr);
printf("Address %u: %s%c", ++cnt, dotted, revhost ? ' ' : '\n');
if (revhost) {
puts(revhost);
if (ENABLE_FEATURE_CLEAN_UP)
free(revhost);
}
if (ENABLE_FEATURE_CLEAN_UP)
free(dotted);
cur = cur->ai_next;
}
} else {
#if ENABLE_VERBOSE_RESOLUTION_ERRORS
2007-01-23 04:16:04 +05:30
bb_error_msg("can't resolve '%s': %s", hostname, gai_strerror(rc));
#else
bb_error_msg("can't resolve '%s'", hostname);
#endif
}
if (ENABLE_FEATURE_CLEAN_UP && result)
freeaddrinfo(result);
return (rc != 0);
}
/* lookup the default nameserver and display it */
static void server_print(void)
{
char *server;
struct sockaddr *sa;
#if ENABLE_FEATURE_IPV6
sa = (struct sockaddr*)_res._u._ext.nsaddrs[0];
if (!sa)
#endif
sa = (struct sockaddr*)&_res.nsaddr_list[0];
server = xmalloc_sockaddr2dotted_noport(sa);
print_host(server, "Server:");
if (ENABLE_FEATURE_CLEAN_UP)
free(server);
bb_putchar('\n');
}
/* alter the global _res nameserver structure to use
an explicit dns server instead of what is in /etc/resolv.conf */
static void set_default_dns(const char *server)
{
len_and_sockaddr *lsa;
if (!server)
return;
/* NB: this works even with, say, "[::1]:5353"! :) */
lsa = xhost2sockaddr(server, 53);
if (lsa->u.sa.sa_family == AF_INET) {
_res.nscount = 1;
/* struct copy */
_res.nsaddr_list[0] = lsa->u.sin;
}
#if ENABLE_FEATURE_IPV6
/* Hoped libc can cope with IPv4 address there too.
* No such luck, glibc 2.4 segfaults even with IPv6,
* maybe I misunderstand how to make glibc use IPv6 addr?
* (uclibc 0.9.31+ should work) */
if (lsa->u.sa.sa_family == AF_INET6) {
// glibc neither SEGVs nor sends any dgrams with this
// (strace shows no socket ops):
//_res.nscount = 0;
_res._u._ext.nscount = 1;
/* store a pointer to part of malloc'ed lsa */
_res._u._ext.nsaddrs[0] = &lsa->u.sin6;
/* must not free(lsa)! */
}
#endif
2004-01-31 04:10:05 +05:30
}
int nslookup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int nslookup_main(int argc, char **argv)
2000-01-29 18:29:01 +05:30
{
/* We allow 1 or 2 arguments.
* The first is the name to be looked up and the second is an
* optional DNS server with which to do the lookup.
* More than 3 arguments is an error to follow the pattern of the
* standard nslookup */
if (!argv[1] || argv[1][0] == '-' || argc > 3)
2003-03-19 14:43:01 +05:30
bb_show_usage();
/* initialize DNS structure _res used in printing the default
* name server and in the explicit name server option feature. */
res_init();
/* rfc2133 says this enables IPv6 lookups */
/* (but it also says "may be enabled in /etc/resolv.conf") */
/*_res.options |= RES_USE_INET6;*/
set_default_dns(argv[2]);
2001-01-21 03:21:21 +05:30
server_print();
/* getaddrinfo and friends are free to request a resolver
* reinitialization. Just in case, set_default_dns() again
* after getaddrinfo (in server_print). This reportedly helps
* with bug 675 "nslookup does not properly use second argument"
* at least on Debian Wheezy and Openwrt AA (eglibc based).
*/
set_default_dns(argv[2]);
return print_host(argv[1], "Name:");
2000-01-29 18:29:01 +05:30
}
#else /****** A version from LEDE / OpenWRT ******/
/*
* musl compatible nslookup
*
* Copyright (C) 2017 Jo-Philipp Wich <jo@mein.io>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#if 0
# define dbg(...) fprintf(stderr, __VA_ARGS__)
#else
# define dbg(...) ((void)0)
#endif
struct ns {
const char *name;
len_and_sockaddr *lsa;
//UNUSED: int failures;
int replies;
};
struct query {
const char *name;
unsigned qlen;
// unsigned latency;
// uint8_t rcode;
unsigned char query[512];
// unsigned char reply[512];
};
static const struct {
unsigned char type;
char name[7];
} qtypes[] ALIGN1 = {
{ ns_t_soa, "SOA" },
{ ns_t_ns, "NS" },
{ ns_t_a, "A" },
#if ENABLE_FEATURE_IPV6
{ ns_t_aaaa, "AAAA" },
#endif
{ ns_t_cname, "CNAME" },
{ ns_t_mx, "MX" },
{ ns_t_txt, "TXT" },
{ ns_t_srv, "SRV" },
{ ns_t_ptr, "PTR" },
{ ns_t_any, "ANY" },
};
static const char *const rcodes[] ALIGN_PTR = {
"NOERROR", // 0
"FORMERR", // 1
"SERVFAIL", // 2
"NXDOMAIN", // 3
"NOTIMP", // 4
"REFUSED", // 5
"YXDOMAIN", // 6
"YXRRSET", // 7
"NXRRSET", // 8
"NOTAUTH", // 9
"NOTZONE", // 10
"11", // 11 not assigned
"12", // 12 not assigned
"13", // 13 not assigned
"14", // 14 not assigned
"15", // 15 not assigned
};
#if ENABLE_FEATURE_IPV6
static const char v4_mapped[12] = { 0,0,0,0, 0,0,0,0, 0,0,0xff,0xff };
#endif
struct globals {
unsigned default_port;
unsigned default_retry;
unsigned default_timeout;
unsigned query_count;
unsigned serv_count;
struct ns *server;
struct query *query;
char *search;
smalluint have_search_directive;
smalluint exitcode;
} FIX_ALIASING;
#define G (*(struct globals*)bb_common_bufsiz1)
#define INIT_G() do { \
setup_common_bufsiz(); \
G.default_port = 53; \
G.default_retry = 2; \
G.default_timeout = 5; \
} while (0)
enum {
OPT_debug = (1 << 0),
};
static NOINLINE int parse_reply(const unsigned char *msg, size_t len)
{
HEADER *header;
ns_msg handle;
ns_rr rr;
int i, n, rdlen;
const char *format = NULL;
char astr[INET6_ADDRSTRLEN], dname[MAXDNAME];
const unsigned char *cp;
header = (HEADER *)msg;
if (!header->aa)
printf("Non-authoritative answer:\n");
else if (option_mask32 & OPT_debug)
printf("Non-authoritative answer:\n" + 4);
if (ns_initparse(msg, len, &handle) != 0) {
//printf("Unable to parse reply: %s\n", strerror(errno));
return -1;
}
for (i = 0; i < ns_msg_count(handle, ns_s_an); i++) {
if (ns_parserr(&handle, ns_s_an, i, &rr) != 0) {
//printf("Unable to parse resource record: %s\n", strerror(errno));
return -1;
}
rdlen = ns_rr_rdlen(rr);
switch (ns_rr_type(rr))
{
case ns_t_a:
if (rdlen != 4) {
dbg("unexpected A record length %d\n", rdlen);
return -1;
}
inet_ntop(AF_INET, ns_rr_rdata(rr), astr, sizeof(astr));
printf("Name:\t%s\nAddress: %s\n", ns_rr_name(rr), astr);
break;
#if ENABLE_FEATURE_IPV6
case ns_t_aaaa:
if (rdlen != 16) {
dbg("unexpected AAAA record length %d\n", rdlen);
return -1;
}
inet_ntop(AF_INET6, ns_rr_rdata(rr), astr, sizeof(astr));
/* bind-utils 9.11.3 uses the same format for A and AAAA answers */
printf("Name:\t%s\nAddress: %s\n", ns_rr_name(rr), astr);
break;
#endif
case ns_t_ns:
if (!format)
format = "%s\tnameserver = %s\n";
/* fall through */
case ns_t_cname:
if (!format)
format = "%s\tcanonical name = %s\n";
/* fall through */
case ns_t_ptr:
if (!format)
format = "%s\tname = %s\n";
if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle),
ns_rr_rdata(rr), dname, sizeof(dname)) < 0
) {
//printf("Unable to uncompress domain: %s\n", strerror(errno));
return -1;
}
printf(format, ns_rr_name(rr), dname);
break;
case ns_t_mx:
if (rdlen < 2) {
printf("MX record too short\n");
return -1;
}
n = ns_get16(ns_rr_rdata(rr));
if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle),
ns_rr_rdata(rr) + 2, dname, sizeof(dname)) < 0
) {
//printf("Cannot uncompress MX domain: %s\n", strerror(errno));
return -1;
}
printf("%s\tmail exchanger = %d %s\n", ns_rr_name(rr), n, dname);
break;
case ns_t_txt:
if (rdlen < 1) {
//printf("TXT record too short\n");
return -1;
}
n = *(unsigned char *)ns_rr_rdata(rr);
if (n > 0) {
memset(dname, 0, sizeof(dname));
memcpy(dname, ns_rr_rdata(rr) + 1, n);
printf("%s\ttext = \"%s\"\n", ns_rr_name(rr), dname);
}
break;
case ns_t_srv:
if (rdlen < 6) {
//printf("SRV record too short\n");
return -1;
}
cp = ns_rr_rdata(rr);
n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle),
cp + 6, dname, sizeof(dname));
if (n < 0) {
//printf("Unable to uncompress domain: %s\n", strerror(errno));
return -1;
}
printf("%s\tservice = %u %u %u %s\n", ns_rr_name(rr),
ns_get16(cp), ns_get16(cp + 2), ns_get16(cp + 4), dname);
break;
case ns_t_soa:
if (rdlen < 20) {
dbg("SOA record too short:%d\n", rdlen);
return -1;
}
printf("%s\n", ns_rr_name(rr));
cp = ns_rr_rdata(rr);
n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle),
cp, dname, sizeof(dname));
if (n < 0) {
//printf("Unable to uncompress domain: %s\n", strerror(errno));
return -1;
}
printf("\torigin = %s\n", dname);
cp += n;
n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle),
cp, dname, sizeof(dname));
if (n < 0) {
//printf("Unable to uncompress domain: %s\n", strerror(errno));
return -1;
}
printf("\tmail addr = %s\n", dname);
cp += n;
printf("\tserial = %lu\n", ns_get32(cp));
cp += 4;
printf("\trefresh = %lu\n", ns_get32(cp));
cp += 4;
printf("\tretry = %lu\n", ns_get32(cp));
cp += 4;
printf("\texpire = %lu\n", ns_get32(cp));
cp += 4;
printf("\tminimum = %lu\n", ns_get32(cp));
break;
default:
break;
}
}
return i;
}
/*
* Function logic borrowed & modified from musl libc, res_msend.c
* G.query_count is always > 0.
*/
static int send_queries(struct ns *ns)
{
unsigned char reply[512];
uint8_t rcode;
len_and_sockaddr *local_lsa;
struct pollfd pfd;
int servfail_retry = 0;
int n_replies = 0;
// int save_idx = 0;
unsigned retry_interval;
unsigned timeout = G.default_timeout * 1000;
unsigned tstart, tsent, tcur;
pfd.events = POLLIN;
pfd.fd = xsocket_type(&local_lsa, ns->lsa->u.sa.sa_family, SOCK_DGRAM);
/*
* local_lsa has "null" address and port 0 now.
* bind() ensures we have a *particular port* selected by kernel
* and remembered in fd, thus later recv(fd)
* receives only packets sent to this port.
*/
xbind(pfd.fd, &local_lsa->u.sa, local_lsa->len);
free(local_lsa);
/* Make read/writes know the destination */
xconnect(pfd.fd, &ns->lsa->u.sa, ns->lsa->len);
ndelay_on(pfd.fd);
retry_interval = timeout / G.default_retry;
tstart = tcur = monotonic_ms();
goto send;
while (tcur - tstart < timeout) {
int qn;
int recvlen;
if (tcur - tsent >= retry_interval) {
send:
for (qn = 0; qn < G.query_count; qn++) {
if (G.query[qn].qlen == 0)
continue; /* this one was replied already */
if (write(pfd.fd, G.query[qn].query, G.query[qn].qlen) < 0) {
bb_perror_msg("write to '%s'", ns->name);
n_replies = -1; /* "no go, try next server" */
goto ret;
}
dbg("query %u sent\n", qn);
}
tsent = tcur;
servfail_retry = 2 * G.query_count;
}
/* Wait for a response, or until time to retry */
if (poll(&pfd, 1, retry_interval - (tcur - tsent)) <= 0)
goto next;
recvlen = read(pfd.fd, reply, sizeof(reply));
if (recvlen < 0) {
libbb: reduce the overhead of single parameter bb_error_msg() calls Back in 2007, commit 0c97c9d43707 ("'simple' error message functions by Loic Grenie") introduced bb_simple_perror_msg() to allow for a lower overhead call to bb_perror_msg() when only a string was being printed with no parameters. This saves space for some CPU architectures because it avoids the overhead of a call to a variadic function. However there has never been a simple version of bb_error_msg(), and since 2007 many new calls to bb_perror_msg() have been added that only take a single parameter and so could have been using bb_simple_perror_message(). This changeset introduces 'simple' versions of bb_info_msg(), bb_error_msg(), bb_error_msg_and_die(), bb_herror_msg() and bb_herror_msg_and_die(), and replaces all calls that only take a single parameter, or use something like ("%s", arg), with calls to the corresponding 'simple' version. Since it is likely that single parameter calls to the variadic functions may be accidentally reintroduced in the future a new debugging config option WARN_SIMPLE_MSG has been introduced. This uses some macro magic which will cause any such calls to generate a warning, but this is turned off by default to avoid use of the unpleasant macros in normal circumstances. This is a large changeset due to the number of calls that have been replaced. The only files that contain changes other than simple substitution of function calls are libbb.h, libbb/herror_msg.c, libbb/verror_msg.c and libbb/xfuncs_printf.c. In miscutils/devfsd.c, networking/udhcp/common.h and util-linux/mdev.c additonal macros have been added for logging so that single parameter and multiple parameter logging variants exist. The amount of space saved varies considerably by architecture, and was found to be as follows (for 'defconfig' using GCC 7.4): Arm: -92 bytes MIPS: -52 bytes PPC: -1836 bytes x86_64: -938 bytes Note that for the MIPS architecture only an exception had to be made disabling the 'simple' calls for 'udhcp' (in networking/udhcp/common.h) because it made these files larger on MIPS. Signed-off-by: James Byrne <james.byrne@origamienergy.com> Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2019-07-02 15:05:03 +05:30
bb_simple_perror_msg("read");
next:
tcur = monotonic_ms();
continue;
}
if (ns->replies++ == 0) {
printf("Server:\t\t%s\n", ns->name);
printf("Address:\t%s\n\n",
auto_string(xmalloc_sockaddr2dotted(&ns->lsa->u.sa))
);
/* In "Address", bind-utils 9.11.3 show port after a hash: "1.2.3.4#53" */
/* Should we do the same? */
}
/* Non-identifiable packet */
if (recvlen < 4) {
dbg("read is too short:%d\n", recvlen);
goto next;
}
/* Find which query this answer goes with, if any */
// qn = save_idx;
qn = 0;
for (;;) {
if (memcmp(reply, G.query[qn].query, 2) == 0) {
dbg("response matches query %u\n", qn);
break;
}
if (++qn >= G.query_count) {
dbg("response does not match any query\n");
goto next;
}
}
if (G.query[qn].qlen == 0) {
dbg("dropped duplicate response to query %u\n", qn);
goto next;
}
rcode = reply[3] & 0x0f;
dbg("query %u rcode:%s\n", qn, rcodes[rcode]);
/* Retry immediately on SERVFAIL */
if (rcode == 2) {
//UNUSED: ns->failures++;
if (servfail_retry) {
servfail_retry--;
write(pfd.fd, G.query[qn].query, G.query[qn].qlen);
dbg("query %u resent\n", qn);
goto next;
}
}
/* Process reply */
G.query[qn].qlen = 0; /* flag: "reply received" */
tcur = monotonic_ms();
#if 1
if (option_mask32 & OPT_debug) {
printf("Query #%d completed in %ums:\n", qn, tcur - tstart);
}
if (rcode != 0) {
printf("** server can't find %s: %s\n",
G.query[qn].name, rcodes[rcode]);
G.exitcode = EXIT_FAILURE;
} else {
switch (parse_reply(reply, recvlen)) {
case -1:
printf("*** Can't find %s: Parse error\n", G.query[qn].name);
G.exitcode = EXIT_FAILURE;
break;
/* bind-utils 9.11.25 just says nothing in this case */
//case 0:
// break;
}
}
/* NB: in case of authoritative, empty answer (NODATA), IOW: one with
* ns_msg_count() == 0, bind-utils 9.11.25 shows no trace of this answer
* (unless -debug, where it says:
* ------------
* QUESTIONS:
* host.com, type = AAAA, class = IN
* ANSWERS:
* AUTHORITY RECORDS:
* ADDITIONAL RECORDS:
* ------------
* ). Due to printing of below '\n', we do show an additional empty line.
* This is better than not showing any indication of this reply at all,
* yet maintains "compatibility". I wonder whether it's better to break compat
* and emit something more meaningful, e.g. print "Empty answer (NODATA)"?
*/
bb_putchar('\n');
n_replies++;
if (n_replies >= G.query_count)
goto ret;
#else
//used to store replies and process them later
G.query[qn].latency = tcur - tstart;
n_replies++;
if (qn != save_idx) {
/* "wrong" receive buffer, move to correct one */
memcpy(G.query[qn].reply, G.query[save_idx].reply, recvlen);
continue;
}
/* G.query[0..save_idx] have replies, move to next one, if exists */
for (;;) {
save_idx++;
if (save_idx >= G.query_count)
goto ret; /* all are full: we have all results */
if (!G.query[save_idx].rlen)
break; /* this one is empty */
}
#endif
} /* while() */
ret:
close(pfd.fd);
return n_replies;
}
static void add_ns(const char *addr)
{
struct ns *ns;
unsigned count;
dbg("%s: addr:'%s'\n", __func__, addr);
count = G.serv_count++;
G.server = xrealloc_vector(G.server, /*8=2^3:*/ 3, count);
ns = &G.server[count];
ns->name = addr;
ns->lsa = xhost2sockaddr(addr, G.default_port);
/*ns->replies = 0; - already is */
/*ns->failures = 0; - already is */
}
static void parse_resolvconf(void)
{
FILE *resolv;
resolv = fopen_for_read("/etc/resolv.conf");
if (resolv) {
char line[512]; /* "search" is defined to be up to 256 chars */
while (fgets(line, sizeof(line), resolv)) {
char *p, *arg;
char *tokstate;
p = strtok_r(line, " \t\n", &tokstate);
if (!p)
continue;
dbg("resolv_key:'%s'\n", p);
arg = strtok_r(NULL, "\n", &tokstate);
dbg("resolv_arg:'%s'\n", arg);
if (!arg)
continue;
if (strcmp(p, "domain") == 0) {
/* domain DOM */
if (!G.have_search_directive)
goto set_search;
continue;
}
if (strcmp(p, "search") == 0) {
/* search DOM1 DOM2... */
G.have_search_directive = 1;
set_search:
free(G.search);
G.search = xstrdup(arg);
dbg("search='%s'\n", G.search);
continue;
}
if (strcmp(p, "nameserver") != 0)
continue;
/* nameserver DNS */
add_ns(xstrdup(arg));
}
fclose(resolv);
}
if (!G.search) {
/* default search domain is domain part of hostname */
char *h = safe_gethostname();
char *d = strchr(h, '.');
if (d) {
G.search = d + 1;
dbg("search='%s' (from hostname)\n", G.search);
}
/* else free(h); */
}
/* Cater for case of "domain ." in resolv.conf */
if (G.search && LONE_CHAR(G.search, '.'))
G.search = NULL;
}
static void add_query(int type, const char *dname)
{
struct query *new_q;
unsigned count;
ssize_t qlen;
count = G.query_count++;
G.query = xrealloc_vector(G.query, /*4=2^2:*/ 2, count);
new_q = &G.query[count];
dbg("new query#%u type %u for '%s'\n", count, type, dname);
new_q->name = dname;
qlen = res_mkquery(QUERY, dname, C_IN, type,
/*data:*/ NULL, /*datalen:*/ 0,
/*newrr:*/ NULL,
new_q->query, sizeof(new_q->query)
);
new_q->qlen = qlen;
}
static void add_query_with_search(int type, const char *dname)
{
char *s;
if (type == T_PTR || !G.search || strchr(dname, '.')) {
add_query(type, dname);
return;
}
s = G.search;
for (;;) {
char *fullname, *e;
e = skip_non_whitespace(s);
fullname = xasprintf("%s.%.*s", dname, (int)(e - s), s);
add_query(type, fullname);
s = skip_whitespace(e);
if (!*s)
break;
}
}
static char *make_ptr(const char *addrstr)
{
unsigned char addr[16];
#if ENABLE_FEATURE_IPV6
if (inet_pton(AF_INET6, addrstr, addr)) {
if (memcmp(addr, v4_mapped, 12) != 0) {
int i;
char resbuf[80];
char *ptr = resbuf;
for (i = 0; i < 16; i++) {
*ptr++ = 0x20 | bb_hexdigits_upcase[(unsigned char)addr[15 - i] & 0xf];
*ptr++ = '.';
*ptr++ = 0x20 | bb_hexdigits_upcase[(unsigned char)addr[15 - i] >> 4];
*ptr++ = '.';
}
strcpy(ptr, "ip6.arpa");
return xstrdup(resbuf);
}
return xasprintf("%u.%u.%u.%u.in-addr.arpa",
addr[15], addr[14], addr[13], addr[12]);
}
#endif
if (inet_pton(AF_INET, addrstr, addr)) {
return xasprintf("%u.%u.%u.%u.in-addr.arpa",
addr[3], addr[2], addr[1], addr[0]);
}
return NULL;
}
int nslookup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int nslookup_main(int argc UNUSED_PARAM, char **argv)
{
unsigned types;
int rc;
int err;
INIT_G();
/* manpage: "Options can also be specified on the command line
* if they precede the arguments and are prefixed with a hyphen."
*/
types = 0;
argv++;
for (;;) {
const char *options =
// bind-utils-9.11.3 accept these:
// class= cl=
// type= ty= querytype= query= qu= q=
// domain= do=
// port= po=
// timeout= t=
// retry= ret=
// ndots=
// recurse
// norecurse
// defname
// nodefname
// vc
// novc
// debug
// nodebug
// d2
// nod2
// search
// nosearch
// sil
// fail
// nofail
// ver (prints version and exits)
"type\0" /* 0 */
"querytype\0" /* 1 */
"port\0" /* 2 */
"retry\0" /* 3 */
"debug\0" /* 4 */
"t\0" /* disambiguate with "type": else -t=2 fails */
"timeout\0" /* 6 */
"";
int i;
char *arg;
char *val;
if (!*argv)
bb_show_usage();
if (argv[0][0] != '-')
break;
/* Separate out "=val" part */
arg = (*argv++) + 1;
val = strchrnul(arg, '=');
if (*val)
*val++ = '\0';
i = index_in_substrings(options, arg);
//bb_error_msg("i:%d arg:'%s' val:'%s'", i, arg, val);
if (i < 0)
bb_show_usage();
if (i <= 1) {
for (i = 0;; i++) {
if (i == ARRAY_SIZE(qtypes))
bb_error_msg_and_die("invalid query type \"%s\"", val);
if (strcasecmp(qtypes[i].name, val) == 0)
break;
}
types |= (1 << i);
continue;
}
if (i == 2) {
G.default_port = xatou_range(val, 1, 0xffff);
}
if (i == 3) {
G.default_retry = xatou_range(val, 1, INT_MAX);
}
if (i == 4) {
option_mask32 |= OPT_debug;
}
if (i > 4) {
G.default_timeout = xatou_range(val, 1, INT_MAX / 1000);
}
}
/* Use given DNS server if present */
if (argv[1]) {
if (argv[2])
bb_show_usage();
add_ns(argv[1]);
} else {
parse_resolvconf();
/* Fall back to localhost if we could not find NS in resolv.conf */
if (G.serv_count == 0)
add_ns("127.0.0.1");
}
if (types == 0) {
/* No explicit type given, guess query type.
* If we can convert the domain argument into a ptr (means that
* inet_pton() could read it) we assume a PTR request, else
* we issue A+AAAA queries and switch to an output format
* mimicking the one of the traditional nslookup applet.
*/
char *ptr;
ptr = make_ptr(argv[0]);
if (ptr) {
add_query(T_PTR, ptr);
} else {
add_query_with_search(T_A, argv[0]);
#if ENABLE_FEATURE_IPV6
add_query_with_search(T_AAAA, argv[0]);
#endif
}
} else {
int c;
for (c = 0; c < ARRAY_SIZE(qtypes); c++) {
if (types & (1 << c))
add_query_with_search(qtypes[c].type, argv[0]);
}
}
for (rc = 0; rc < G.serv_count;) {
int c;
c = send_queries(&G.server[rc]);
if (c > 0) {
/* more than zero replies received */
#if 0 /* which version does this? */
if (option_mask32 & OPT_debug) {
printf("Replies:\t%d\n", G.server[rc].replies);
printf("Failures:\t%d\n\n", G.server[rc].failures);
}
#endif
break;
//FIXME: we "break" even though some queries may still be not answered, and other servers may know them?
}
/* c = 0: timed out waiting for replies */
/* c < 0: error (message already printed) */
rc++;
if (rc >= G.serv_count) {
//
// NB: bind-utils-9.11.3 behavior (all to stdout, not stderr):
//
// $ nslookup gmail.com 8.8.8.8
// ;; connection timed out; no servers could be reached
//
// Using TCP mode:
// $ nslookup -vc gmail.com 8.8.8.8; echo EXITCODE:$?
// <~10 sec>
// ;; Connection to 8.8.8.8#53(8.8.8.8) for gmail.com failed: timed out.
// <~10 sec>
// ;; Connection to 8.8.8.8#53(8.8.8.8) for gmail.com failed: timed out.
// <~10 sec>
// ;; connection timed out; no servers could be reached
// ;; Connection to 8.8.8.8#53(8.8.8.8) for gmail.com failed: timed out.
// <empty line>
// EXITCODE:1
// $ _
printf(";; connection timed out; no servers could be reached\n\n");
return EXIT_FAILURE;
}
}
err = 0;
for (rc = 0; rc < G.query_count; rc++) {
if (G.query[rc].qlen) {
printf("*** Can't find %s: No answer\n", G.query[rc].name);
err = 1;
}
}
if (err) /* should this affect exicode too? */
bb_putchar('\n');
if (ENABLE_FEATURE_CLEAN_UP) {
free(G.server);
free(G.query);
}
return G.exitcode;
}
#endif