diff options
-rw-r--r-- | Makefile | 23 | ||||
-rw-r--r-- | ldns-host.1 | 246 | ||||
-rw-r--r-- | ldns-host.c | 881 |
3 files changed, 1150 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9d07a58 --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +PROG=ldns-host +SRC=ldns-host.c +MAN=ldns-host.1 + +LOCALBASE?=/usr/local +PREFIX?=${LOCALBASE} +MANDIR?=${PREFIX}/man + +XCFLAGS=${CFLAGS} -I${LOCALBASE}/include +XLDFLAGS=${LDFLAGS} -L${LOCALBASE}/lib -lldns + +${PROG}: ${SRC} + ${CC} -o $@ ${XCFLAGS} ${XLDFLAGS} ${SRC} + +clean: + rm -f ${PROG} + +install: ${PROG} + cp ${PROG} ${PREFIX}/bin/ + cp ${MAN} ${MANDIR}/man1/ + +deinstall: + rm -f ${PREFIX}/bin/${PROG} ${MANDIR}/man1/${MAN} diff --git a/ldns-host.1 b/ldns-host.1 new file mode 100644 index 0000000..3a85ac4 --- /dev/null +++ b/ldns-host.1 @@ -0,0 +1,246 @@ +.\" (c) Magerya Vitaly +.\" +.\" Copying and distribution of this file, with or without modification, +.\" are permitted in any medium without royalty provided the copyright +.\" notice and this notice are preserved. This file is offered as-is, +.\" without any warranty. +.Dd Aug 27, 2012 +.Dt LDNS-HOST 1 +.Os +.Sh NAME +.Nm ldns-host +.Nd DNS lookup utility +.Sh SYNOPSIS +.Nm +.Op Fl aCdilrsTvw46 +.Op Fl c Ar class +.Op Fl N Ar ndots +.Op Fl R Ar number +.Op Fl t Ar type +.Op Fl W Ar wait +.Ar name +.Op Ar server +.Sh DESCRIPTION +.Nm +is a simple utility for performing DNS lookups. It is normally +used to convert names to IP addresses and vice versa. +.Pp +.Ar name +is the domain name that is to be looked up. It can also be a +dotted-decimal IPv4 address or a colon-delimited IPv6 address, +in which case +.Nm +will by default perform a reverse lookup for that address. +.Pp +When +.Ar name +is not provided, +.Nm +prints a short summary of it's usage. +.Pp +.Ar server +is an optional argument which is either a domain name or an IP +address of the name server that +.Nm +should query instead of the server or servers listed in +.Pa /etc/resolv.conf . +When +.Ar server +is a domain name, system resolver is used to obtain it's address. +.Pp +Supported options: +.Bl -tag -width indent +.It Fl a +Make a verbose query of type +.Cm ANY . +Equivalent to +.Fl v Fl t Cm ANY . +.It Fl C +Query for +.Cm SOA +records for zone +.Ar name +from all of it's authoritative name servers. The list of name +servers is obtained via +.Cm NS +query for +.Ar name . +.It Fl c Ar class +Perform DNS query of class +.Ar class . +Recognized classes are +.Cm IN Pq Internet , +.Cm CH Pq Chaosnet , +.Cm HS Pq Hesiod , +.Cm NONE , +.Cm ANY +and +.Cm CLASS Ns Ar N +(where +.Ar N +is a number from 1 to 255). Default is +.Cm IN . +.It Fl d +Produce verbose output. This is a synonym for +.Fl v , +and is provided for backward compatibility. +.It Fl i +Use IP6.INT domain for reverse lookups of IPv6 addresses (as +defined in RFC1886; note that RFC4159 deprecates IP6.INT). +By default IP6.ARPA is used. +.It Fl l +List all +.Cm NS, PTR, A +and +.Cm AAAA +records in zone +.Ar name +by performing a zone transfer +.Pq Cm AXFR . +You can combine this option with +.Fl a +to print all records, or with +.Fl t +to only print specific ones. +.It Fl N Ar ndots +Consider names with at least this many dots as absolute. That +is, try to resolve them directly before consulting +.Ic domain +or +.Ic search +options from +.Pa /etc/resolv.conf . +.It Fl r +Perform non-recursive query to the name server by clearing RD +.Pq Dq recursion desired +bit of the query. +.It Fl R Ar number +Retry this many times when a query does not receive an answer +in time. The default is 1 retry. If +.Ar number +is negative or zero, 1 is used instead. +.It Fl s +Report SERVFAIL responses as they are, do not ignore them. +.It Fl T +Query name server over TCP. By default UDP is used, except for +.Cm AXFR +and +.Cm IXFR +queries, which require TCP. +.Nm +will also retry UDP queries in TCP mode if the UDP response was +truncated (i.e. had TC bit set). +.It Fl t Ar type +Perform DNS query of type +.Ar type , +which can be any standard query type name +.Pq Cm A , CNAME , MX , TXT , No etc , +a wildcard query +.Pq Cm ANY , +or +.Cm TYPE Ns Ar N , +where +.Ar N +is a number from 1 to 65535. For +.Cm IXFR Pq incremental zone transfer +queries the starting serial number can be specified by appending +an equal sign followed by the number +.Pq e.g. Fl t Cm IXFR Ns =12345678 . +.Pp +The default is to query for +.Cm A , AAAA , No and Cm MX +records, unless +.Fl C +or +.Fl l +options are given (in which case +.Cm SOA +or +.Cm AXFR +queries are made) or +.Ar name +is a valid IP address +(in which case reverse lookup using +.Cm PTR +query is performed). +.It Fl v +Produce verbose output. +.It Fl w +Wait forever (or for a very long time) for response from the +name server. +.It Fl W Ar wait +Wait this many seconds for a reply from name server before timing +out. If +.Ar wait +is negative or zero, value of 1 is used. The default is to wait +10 seconds for TCP connections, and 5 seconds for UDP (both are +subject to retries, see option +.Fl R ) . +.It Fl 4 +Only use IPv4 transport. +.It Fl 6 +Only use IPv6 transport. +.El +.Sh FILES +.Pa /etc/resolv.conf +.Sh SEE ALSO +.Xr drill 1 , +.Xr resolv.conf 5 +.Sh COMPATIBILITY +.Nm +aims to be reasonably compatible with +.Sq host +utility from BIND9 distribution, both in supported options and +in produced output. Here is a list of known notable differences: +.Bl -bullet +.It +Debugging options +.Pq Fl D No and Fl m +are not supported. +.It +Query class +.Cm CLASS0 +and type +.Cm TYPE0 +are not supported. +.It +Backslashes in domain names are treated especially. +.It +The maximum of 255 retries (option +.Fl R ) +are supported. +.It +Some resource records are formatted differently. For example, +.Cm RRSIG +and +.Cm DNSKEY +records are displayed without spaces in them. +.It +When parsing +.Pa /etc/resolv.conf +commands +.Ic sortlist +and +.Ic options +are ignored. When multiple +.Ic search +and/or +.Ic domain +commands are present, +.Nm +first uses the last +.Ic domain +command, and then all of +.Ic search +commands, while +.Sq host +from BIND9 uses whatever command was specified last. +.It +Multi-packet zone transfers are not supported; only the first +response packet is printed. +.It +.Sq Pseudosection TSIG +is missing from verbose packet output. +.El +.Sh AUTHORS +.An Vitaly Magerya Aq magv@tx97.net diff --git a/ldns-host.c b/ldns-host.c new file mode 100644 index 0000000..d23e4ac --- /dev/null +++ b/ldns-host.c @@ -0,0 +1,881 @@ +/*- + * (c) Magerya Vitaly + * + * Copying and distribution of this file, with or without modification, + * are permitted in any medium without royalty provided the copyright + * notice and this notice are preserved. This file is offered as-is, + * without any warranty. + */ + +#include <ldns/ldns.h> +#include <limits.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +/* General utilities. + */ + +static char *progname; + +#define countof(array) (sizeof(array)/sizeof(*(array))) + +static void +die(int code, const char *fmt, ...) { + va_list args; + + va_start(args, fmt); + fprintf(stderr, "%s: ", progname); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + va_end(args); + exit(code); +} + +static int +ndots(const char *name) { + int n; + + for (n = 0; (name = strchr(name, '.')); n++, name++); + return n; +} + +/* General LDNS-specific utilities. + */ + +static ldns_status +ldns_resolver_new_default(ldns_resolver **res) { + if (ldns_resolver_new_frm_file(res, NULL) == LDNS_STATUS_OK || + (*res = ldns_resolver_new()) != NULL) + return LDNS_STATUS_OK; + return LDNS_STATUS_MEM_ERR; +} + +static ldns_status +ldns_resolver_push_default_servers(ldns_resolver *res) { + ldns_status status; + ldns_rdf *addr; + + if ((status = ldns_str2rdf_a(&addr, "127.0.0.1")) != LDNS_STATUS_OK || + (status = ldns_resolver_push_nameserver(res, addr)) != LDNS_STATUS_OK) + return ldns_rdf_deep_free(addr), status; + ldns_rdf_deep_free(addr); + if ((status = ldns_str2rdf_aaaa(&addr, "::1")) != LDNS_STATUS_OK || + (status = ldns_resolver_push_nameserver(res, addr)) != LDNS_STATUS_OK) + return ldns_rdf_deep_free(addr), status; + ldns_rdf_deep_free(addr); + return LDNS_STATUS_OK; +} + +static ldns_rdf * +ldns_rdf_new_addr_frm_str(const char *str) { + ldns_rdf *addr; + + if ((addr = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_A, str)) == NULL) + addr = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_AAAA, str); + return addr; +} + +static void +ldns_resolver_remove_nameservers(ldns_resolver *res) { + while (ldns_resolver_nameserver_count(res) > 0) + ldns_rdf_deep_free(ldns_resolver_pop_nameserver(res)); +} + +static ldns_rdf * +ldns_rdf_reverse_a(ldns_rdf *addr, const char *base) { + char *buf; + int i, len; + + len = strlen(base); + buf = alloca(LDNS_IP4ADDRLEN*4 + len + 1); + for (len = i = 0; i < LDNS_IP4ADDRLEN; i++) + len += sprintf(&buf[len], "%d.", + (int)ldns_rdf_data(addr)[LDNS_IP4ADDRLEN - i - 1]); + sprintf(&buf[len], "%s", base); + return ldns_dname_new_frm_str(buf); +} + +static ldns_rdf * +ldns_rdf_reverse_aaaa(ldns_rdf *addr, const char *base) { + char *buf; + int i, len; + + len = strlen(base); + buf = alloca(LDNS_IP6ADDRLEN*4 + len + 1); + for (i = 0; i < LDNS_IP6ADDRLEN; i++) { + uint8_t byte = ldns_rdf_data(addr)[LDNS_IP6ADDRLEN - i - 1]; + sprintf(&buf[i*4], "%x.%x.", byte & 0x0F, byte >> 4); + } + sprintf(&buf[LDNS_IP6ADDRLEN*4], "%s", base); + return ldns_dname_new_frm_str(buf); +} + +static ldns_status +ldns_pkt_push_rr_soa(ldns_pkt *pkt, ldns_pkt_section sec, + const ldns_rdf *name, ldns_rr_class c, uint32_t serial) { + ldns_rdf *rdf; + ldns_rr *rr; + uint32_t n; + + if ((rr = ldns_rr_new_frm_type(LDNS_RR_TYPE_SOA)) == NULL) + return LDNS_STATUS_MEM_ERR; + ldns_rr_set_class(rr, c); + ldns_rr_set_owner(rr, ldns_rdf_clone(name)); + ldns_rr_set_ttl(rr, 0); + + n = 0; + if ((rdf = ldns_rdf_new_frm_data(LDNS_RDF_TYPE_DNAME, 1, &n)) == NULL) + goto memerr; + ldns_rr_set_rdf(rr, rdf, 0); + ldns_rr_set_rdf(rr, ldns_rdf_clone(rdf), 1); + + n = htonl(serial); + if ((rdf = ldns_rdf_new_frm_data(LDNS_RDF_TYPE_INT32, 4, &n)) == NULL) + goto memerr; + ldns_rr_set_rdf(rr, rdf, 2); + + n = 0; + if ((rdf = ldns_rdf_new_frm_data(LDNS_RDF_TYPE_PERIOD, 4, &n)) == NULL) + goto memerr; + ldns_rr_set_rdf(rr, rdf, 3); + ldns_rr_set_rdf(rr, ldns_rdf_clone(rdf), 4); + ldns_rr_set_rdf(rr, ldns_rdf_clone(rdf), 5); + ldns_rr_set_rdf(rr, ldns_rdf_clone(rdf), 6); + + if (ldns_rr_rdf(rr, 1) == NULL || ldns_rr_rdf(rr, 4) == NULL || + ldns_rr_rdf(rr, 5) == NULL || ldns_rr_rdf(rr, 6) == NULL || + !ldns_pkt_push_rr(pkt, sec, rr)) + goto memerr; + return LDNS_STATUS_OK; + +memerr: + ldns_rr_free(rr); + return LDNS_STATUS_MEM_ERR; +} + +static ldns_status +ldns_resolver_send_to(ldns_pkt **answer, ldns_resolver *res, + const ldns_rdf *name, ldns_rr_type t, ldns_rr_class c, + uint16_t flags, uint32_t ixfr_serial, int nameserver) { + ldns_status status; + ldns_pkt *qpkt; + + int nscnt = ldns_resolver_nameserver_count(res); + ldns_rdf **ns = ldns_resolver_nameservers(res); + size_t *rtt = ldns_resolver_rtt(res); + + ldns_resolver_set_nameservers(res, &ns[nameserver]); + ldns_resolver_set_rtt(res, &rtt[nameserver]); + ldns_resolver_set_nameserver_count(res, 1); + + status = ldns_resolver_prepare_query_pkt(&qpkt, res, name, t, c, flags); + if (status == LDNS_STATUS_OK && t == LDNS_RR_TYPE_IXFR) + status = ldns_pkt_push_rr_soa(qpkt, + LDNS_SECTION_AUTHORITY, name, c, ixfr_serial); + if (status == LDNS_STATUS_OK) + status = ldns_resolver_send_pkt(answer, res, qpkt); + ldns_pkt_free(qpkt); + + ldns_resolver_set_nameservers(res, ns); + ldns_resolver_set_rtt(res, rtt); + ldns_resolver_set_nameserver_count(res, nscnt); + return status; +} + +static void +ldns_pkt_filter_answer(ldns_pkt *pkt, ldns_rr_type type) { + int i, j, cnt; + ldns_rr_list *rrlist; + ldns_rr *rr; + ldns_rr_type rrtype; + + rrlist = ldns_pkt_answer(pkt); + cnt = ldns_rr_list_rr_count(rrlist); + for (i = j = 0; i < cnt; i++) { + rr = ldns_rr_list_rr(rrlist, i); + rrtype = ldns_rr_get_type(rr); + if (type == LDNS_RR_TYPE_ANY || + type == rrtype || + type == LDNS_RR_TYPE_AXFR && + (rrtype == LDNS_RR_TYPE_A || + rrtype == LDNS_RR_TYPE_AAAA || + rrtype == LDNS_RR_TYPE_NS || + rrtype == LDNS_RR_TYPE_PTR)) + ldns_rr_list_set_rr(rrlist, rr, j++); + } + ldns_rr_list_set_rr_count(rrlist, j); +} + +/* Packet content printing. + */ + +static struct { + ldns_rr_type type; + const char *text; +} rr_types[] = { + {LDNS_RR_TYPE_A, "has address"}, + {LDNS_RR_TYPE_NS, "name server"}, + {LDNS_RR_TYPE_CNAME, "is an alias for"}, + {LDNS_RR_TYPE_WKS, "has well known services"}, + {LDNS_RR_TYPE_PTR, "domain name pointer"}, + {LDNS_RR_TYPE_HINFO, "host information"}, + {LDNS_RR_TYPE_MX, "mail is handled by"}, + {LDNS_RR_TYPE_TXT, "descriptive text"}, + {LDNS_RR_TYPE_X25, "x25 address"}, + {LDNS_RR_TYPE_ISDN, "ISDN address"}, + {LDNS_RR_TYPE_SIG, "has signature"}, + {LDNS_RR_TYPE_KEY, "has key"}, + {LDNS_RR_TYPE_AAAA, "has IPv6 address"}, + {LDNS_RR_TYPE_LOC, "location"}, +}; + +static void +print_opcode(ldns_pkt_opcode opcode) { + ldns_lookup_table *lt = ldns_lookup_by_id(ldns_opcodes, opcode); + + if (lt && lt->name) + printf("%s", lt->name); + else + printf("RESERVED%d", opcode); +} + +static void +print_rcode(uint8_t rcode) { + ldns_lookup_table *lt = ldns_lookup_by_id(ldns_rcodes, rcode); + + if (lt && lt->name) + printf("%s", lt->name); + else + printf("RESERVED%d", rcode); +} + +static int +print_rr_type(ldns_rr_type type) { + char *str; + int n; + + str = ldns_rr_type2str(type); + n = printf("%s", str); + free(str); + return n; +} + +static int +print_rr_class(ldns_rr_class cls) { + char *str; + int n; + + str = ldns_rr_class2str(cls); + n = printf("%s", str); + free(str); + return n; +} + +static int +print_rdf(ldns_rdf *rdf) { + char *str; + int n; + + str = ldns_rdf2str(rdf); + n = printf("%s", str); + free(str); + return n; +} + +static int +print_rdf_nodot(ldns_rdf *rdf) { + char *str; + int len, n; + + str = ldns_rdf2str(rdf); + len = strlen(str); + n = printf("%.*s", str[len-1] == '.' ? len-1 : len, str); + free(str); + return n; +} + +static int +print_padding(int fromcol, int tocol) { + int col = fromcol, nextcol = fromcol + 8 - fromcol%8; + + if (fromcol + 1 > tocol) tocol = fromcol + 1; + for (; nextcol <= tocol; col = nextcol, nextcol += 8) + printf("\t"); + for (; col < tocol; col++) + printf(" "); + return col - fromcol; +} + +static void +print_rr_verbose(ldns_rr *rr) { + bool isq = ldns_rr_is_question(rr); + int rdcnt = ldns_rr_rd_count(rr); + int i, n; + + /* bind9-host does not count the initial ';' here */ + n = isq ? printf(";") : 0; + n = print_rdf(ldns_rr_owner(rr)); + if (!isq) { + n += print_padding(n, 24); + n += printf("%d", ldns_rr_ttl(rr)); + } + n += print_padding(n, 32); + n += print_rr_class(ldns_rr_get_class(rr)); + n += print_padding(n, 40); + n += print_rr_type(ldns_rr_get_type(rr)); + for (i = 0; i < rdcnt; i++) { + if (i == 0) print_padding(n, 48); + else printf(" "); + print_rdf(ldns_rr_rdf(rr, i)); + } + printf("\n"); +} + +static void +print_pkt_section_verbose(const char *name, ldns_rr_list *rrlist) { + int i, cnt = ldns_rr_list_rr_count(rrlist); + + if (cnt == 0) + return; + printf(";; %s SECTION:\n", name); + for (i = 0; i < cnt; i++) + print_rr_verbose(ldns_rr_list_rr(rrlist, i)); + printf("\n"); +} + +static void +print_pkt_verbose(ldns_pkt *pkt) { + int got_flags = 0; + + printf(";; ->>HEADER<<- opcode: "); + print_opcode(ldns_pkt_get_opcode(pkt)); + printf(", status: "); + print_rcode(ldns_pkt_get_rcode(pkt)); + printf(", id: %u\n", ldns_pkt_id(pkt)); + printf(";; flags:"); + if (ldns_pkt_qr(pkt)) printf(" qr"), got_flags = 1; + if (ldns_pkt_aa(pkt)) printf(" aa"), got_flags = 1; + if (ldns_pkt_tc(pkt)) printf(" tc"), got_flags = 1; + if (ldns_pkt_rd(pkt)) printf(" rd"), got_flags = 1; + if (ldns_pkt_ra(pkt)) printf(" ra"), got_flags = 1; + if (ldns_pkt_ad(pkt)) printf(" ad"), got_flags = 1; + if (ldns_pkt_cd(pkt)) printf(" cd"), got_flags = 1; + if (!got_flags) printf(" "); + printf("; QUERY: %u, ANSWER: %u, AUTHORITY: %u, ADDITIONAL: %u\n", + ldns_pkt_qdcount(pkt), ldns_pkt_ancount(pkt), + ldns_pkt_nscount(pkt), ldns_pkt_arcount(pkt)); + if (ldns_pkt_edns(pkt)) + printf(";; EDNS: version: %u, udp=%u\n", + ldns_pkt_edns_version(pkt), ldns_pkt_edns_udp_size(pkt)); + printf("\n"); + print_pkt_section_verbose("QUESTION", ldns_pkt_question(pkt)); + print_pkt_section_verbose("ANSWER", ldns_pkt_answer(pkt)); + print_pkt_section_verbose("AUTHORITY", ldns_pkt_authority(pkt)); + print_pkt_section_verbose("ADDITIONAL", ldns_pkt_additional(pkt)); +} + +static void +print_rr_short(ldns_rr *rr) { + ldns_rr_type type = ldns_rr_get_type(rr); + size_t i, rdcnt = ldns_rr_rd_count(rr); + + print_rdf_nodot(ldns_rr_owner(rr)); + printf(" "); + for (i = 0; i < countof(rr_types); i++) { + if (rr_types[i].type == type) { + printf("%s", rr_types[i].text); + goto found; + } + } + + printf("has "); + print_rr_type(type); + printf(" record"); + +found: + for (i = 0; i < rdcnt; i++) { + printf(" "); + print_rdf(ldns_rr_rdf(rr, i)); + } + printf("\n"); +} + +static void +print_pkt_short(ldns_pkt *pkt, bool print_rr_server) { + ldns_rr_list *rrlist = ldns_pkt_answer(pkt); + size_t i; + + for (i = 0; i < ldns_rr_list_rr_count(rrlist); i++) { + if (print_rr_server) { + printf("Nameserver "); + print_rdf(ldns_pkt_answerfrom(pkt)); + printf(":\n\t"); + } + print_rr_short(ldns_rr_list_rr(rrlist, i)); + } +} + +static void +print_received_line(ldns_resolver *res, ldns_pkt *pkt) { + char *from = ldns_rdf2str(ldns_pkt_answerfrom(pkt)); + + printf("Received %zu bytes from %s#%d in %d ms\n", + ldns_pkt_size(pkt), from, ldns_resolver_port(res), + ldns_pkt_querytime(pkt)); + free(from); +} + +/* Main program. + * + * Note that no memory is freed below this line by intention. + */ + +#define DEFAULT_TCP_TIMEOUT 10 +#define DEFAULT_UDP_TIMEOUT 5 + +enum operation_mode { M_AXFR, M_DEFAULT_Q, M_SINGLE_Q, M_SOA }; + +static enum operation_mode o_mode = M_DEFAULT_Q; +static bool o_ignore_servfail = true; +static bool o_ip6_int = false; +static bool o_print_pkt_server = false; +static bool o_print_rr_server = false; +static bool o_recursive = true; +static bool o_tcp = false; +static bool o_verbose = false; +static char *o_name = NULL; +static char *o_server = NULL; +static int o_ipversion = LDNS_RESOLV_INETANY; +static int o_ndots = 1; +static int o_retries = 1; +static ldns_rr_class o_rrclass = LDNS_RR_CLASS_IN; +static ldns_rr_type o_rrtype = LDNS_RR_TYPE_A; +static time_t o_timeout = 0; +static uint32_t o_ixfr_serial = 0; + +static void +usage(void) { + fputs( + "Usage: ldns-host [-aCdilrsTvw46] [-c class] [-N ndots] [-R number]\n" + " [-t type] [-W wait] name [server]\n" + "\t-a same as -v -t ANY\n" + "\t-C query SOA records from all authoritative name servers\n" + "\t-c use this query class (IN, CH, HS, etc)\n" + "\t-d produce verbose output, same as -v\n" + "\t-i use IP6.INT for IPv6 reverse lookups\n" + "\t-l list records in a zone via AXFR\n" + "\t-N consider names with at least this many dots as absolute\n" + "\t-R retry UDP queries this many times\n" + "\t-r disable recursive query\n" + "\t-s do not ignore SERVFAIL responses\n" + "\t-T send query via TCP\n" + "\t-t use this query type (A, AAAA, MX, etc)\n" + "\t-v produce verbose output\n" + "\t-w wait forever for a server reply\n" + "\t-W wait this many seconds for a reply\n" + "\t-4 use IPv4 only\n" + "\t-6 use IPv6 only\n", + stderr); + exit(1); +} + +static void +parse_args(int argc, char *argv[]) { + int ch; + + progname = argv[0]; + while ((ch = getopt(argc, argv, "aCdilrsTvw46c:N:R:t:W:")) != -1) { + switch (ch) { + case 'a': + if (o_mode != M_AXFR) + o_mode = M_SINGLE_Q; + o_rrtype = LDNS_RR_TYPE_ANY; + o_verbose = true; + break; + case 'C': + o_mode = M_SOA; + o_print_rr_server = true; + o_rrclass = LDNS_RR_CLASS_IN; + o_rrtype = LDNS_RR_TYPE_NS; + break; + case 'c': + /* bind9-host sets o_mode to M_SINGLE_Q here */ + o_rrclass = ldns_get_rr_class_by_name(optarg); + if (o_rrclass <= 0) + die(2, "invalid class: %s\n", optarg); + break; + case 'd': o_verbose = true; break; + case 'i': o_ip6_int = true; break; + case 'l': + o_mode = M_AXFR; + o_rrtype = LDNS_RR_TYPE_AXFR; + o_tcp = true; + break; + case 'N': + o_ndots = atoi(optarg); + if (o_ndots < 0) o_ndots = 0; + break; + case 'n': + /* bind9-host accepts and ignores this option */ + break; + case 'r': o_recursive = 0; break; + case 'R': + o_retries = atoi(optarg); + if (o_retries <= 0) o_retries = 1; + if (o_retries > 255) o_retries = 255; + break; + case 's': o_ignore_servfail = false; break; + case 'T': o_tcp = true; break; + case 't': + if (o_mode != M_AXFR) + o_mode = M_SINGLE_Q; + if (strncasecmp(optarg, "ixfr=", 5) == 0) { + o_rrtype = LDNS_RR_TYPE_IXFR; + o_ixfr_serial = atol(optarg + 5); + } else { + o_rrtype = ldns_get_rr_type_by_name(optarg); + if (o_rrtype <= 0) + die(2, "invalid type: %s\n", optarg); + } + if (o_rrtype == LDNS_RR_TYPE_AXFR || o_rrtype == LDNS_RR_TYPE_IXFR) + o_tcp = true; + if (o_rrtype == LDNS_RR_TYPE_AXFR) { + o_mode = M_AXFR; + o_rrtype = LDNS_RR_TYPE_ANY; + o_verbose = true; + } + break; + case 'v': o_verbose = true; break; + case 'w': + o_timeout = (time_t)INT_MAX; + break; + case 'W': + o_timeout = atol(optarg); + if (o_timeout <= 0) o_timeout = 1; + break; + case '4': o_ipversion = LDNS_RESOLV_INET; break; + case '6': o_ipversion = LDNS_RESOLV_INET6; break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + /* bind9-host ignores arguments after the 2-nd one */ + if (argc < 1) + usage(); + o_name = argv[0]; + if (argc > 1) { + o_server = argv[1]; + o_print_pkt_server = true; + } +} + +static ldns_rdf* +safe_str2rdf_dname(const char *name) { + ldns_rdf *dname; + ldns_status status; + + if ((status = ldns_str2rdf_dname(&dname, name)) != LDNS_STATUS_OK) { + die(1, "'%s' is not a legal name (%s)", + name, ldns_get_errorstr_by_id(status)); + } + return dname; +} + +static ldns_rdf* +safe_dname_cat_clone(const ldns_rdf *rd1, const ldns_rdf *rd2) { + ldns_rdf *result = ldns_dname_cat_clone(rd1, rd2); + + if (!result) + die(1, "not enought memory for a domain name"); + /* Why doesn't ldns_dname_cat_clone check this condition? */ + if (ldns_rdf_size(result) > LDNS_MAX_DOMAINLEN) + die(1, "'%s' is not a legal name (%s)\n", ldns_rdf2str(result), + ldns_get_errorstr_by_id(LDNS_STATUS_DOMAINNAME_OVERFLOW)); + return result; +} + +static bool +query(ldns_resolver *res, ldns_rdf *domain, ldns_pkt **pkt) { + ldns_status status; + ldns_pkt_rcode rcode; + int i, cnt; + + if (o_verbose) { + printf("Trying \""); + print_rdf_nodot(domain); + printf("\"\n"); + } + for (cnt = ldns_resolver_nameserver_count(res), i = 0; i < cnt; i++) { + status = ldns_resolver_send_to(pkt, res, domain, o_rrtype, + o_rrclass, o_recursive ? LDNS_RD : 0, o_ixfr_serial, i); + if (status != LDNS_STATUS_OK) { + *pkt = NULL; + continue; + } + if (ldns_pkt_tc(*pkt) && !ldns_resolver_usevc(res)) { + if (o_verbose) + printf(";; Truncated, retrying in TCP mode.\n"); + ldns_resolver_set_usevc(res, true); + status = ldns_resolver_send_to(pkt, res, domain, o_rrtype, + o_rrclass, o_recursive ? LDNS_RD : 0, o_ixfr_serial, i); + ldns_resolver_set_usevc(res, false); + if (status != LDNS_STATUS_OK) + continue; + } + rcode = ldns_pkt_get_rcode(*pkt); + if (o_ignore_servfail && rcode == LDNS_RCODE_SERVFAIL && cnt > 1) + continue; + return rcode == LDNS_RCODE_NOERROR; + } + if (*pkt == NULL) { + printf(";; connection timed out; no servers could be reached\n"); + exit(1); + } + return false; +} + +static ldns_rdf * +search(ldns_resolver *res, ldns_rdf *domain, ldns_pkt **pkt, bool absolute) { + ldns_rdf *dname, **searchlist; + int i, n; + + if (absolute && query(res, domain, pkt)) + return domain; + + if ((dname = ldns_resolver_domain(res)) != NULL) { + dname = safe_dname_cat_clone(domain, dname); + if (query(res, dname, pkt)) + return dname; + } + + searchlist = ldns_resolver_searchlist(res); + n = ldns_resolver_searchlist_count(res); + for (i = 0; i < n; i++) { + dname = safe_dname_cat_clone(domain, searchlist[i]); + if (query(res, dname, pkt)) + return dname; + } + + if (!absolute && query(res, domain, pkt)) + return domain; + + return NULL; +} + +static void +report(ldns_resolver *res, ldns_rdf *domain, ldns_pkt *pkt) { + ldns_pkt_rcode rcode; + + if (o_print_pkt_server) { + printf("Using domain server:\nName: %s\nAddress: ", o_server); + print_rdf(ldns_pkt_answerfrom(pkt)); + printf("#%d\nAliases: \n\n", ldns_resolver_port(res)); + o_print_pkt_server = false; + } + rcode = ldns_pkt_get_rcode(pkt); + if (rcode != LDNS_RCODE_NOERROR) { + printf("Host "); + print_rdf_nodot(domain); + printf(" not found: %d(", rcode); + print_rcode(rcode); + printf(")\n"); + } else { + if (o_verbose) { + print_pkt_verbose(pkt); + } else { + print_pkt_short(pkt, o_print_rr_server); + if (o_mode != M_DEFAULT_Q && + ldns_rr_list_rr_count(ldns_pkt_answer(pkt)) == 0) { + print_rdf_nodot(domain); + printf(" has no "); + print_rr_type(o_rrtype); + printf(" record\n"); + } + } + } + if (o_verbose) + print_received_line(res, pkt); +} + +static bool +doquery(ldns_resolver *res, ldns_rdf *domain) { + ldns_pkt *pkt; + bool q; + + q = query(res, domain, &pkt); + report(res, domain, pkt); + return q; +} + +static bool +doquery_filtered(ldns_resolver *res, ldns_rdf *domain) { + ldns_pkt *pkt; + bool q; + + q = query(res, domain, &pkt); + ldns_pkt_filter_answer(pkt, o_rrtype); + report(res, domain, pkt); + return q; +} + +static bool +dosearch(ldns_resolver *res, ldns_rdf *domain, bool absolute) { + ldns_pkt *pkt; + ldns_rdf *dname; + + dname = search(res, domain, &pkt, absolute); + report(res, dname != NULL ? dname : domain, pkt); + return o_mode != M_DEFAULT_Q ? (dname != NULL) : + (dname != NULL) && + (o_rrtype = LDNS_RR_TYPE_AAAA, doquery_filtered(res, dname)) && + (o_rrtype = LDNS_RR_TYPE_MX, doquery_filtered(res, dname)); +} + +static bool +doaxfr(ldns_resolver *res, ldns_rdf *domain, bool absolute) { + ldns_pkt *pkt; + ldns_rdf *dname; + ldns_rr_type rrtype; + + rrtype = o_rrtype; + o_rrtype = LDNS_RR_TYPE_AXFR; + dname = search(res, domain, &pkt, absolute); + ldns_pkt_filter_answer(pkt, rrtype); + report(res, dname != NULL ? dname : domain, pkt); + return dname != NULL; +} + +static bool +dosoa(ldns_resolver *res, ldns_rdf *domain, bool absolute) { + ldns_rr_list *answer, **nsaddrs; + ldns_rdf *dname, *addr; + ldns_pkt *pkt; + ldns_rr *rr; + size_t i, j, n, cnt; + + if ((dname = search(res, domain, &pkt, absolute)) == NULL) + return false; + + answer = ldns_pkt_answer(pkt); + cnt = ldns_rr_list_rr_count(answer); + nsaddrs = alloca(cnt*sizeof(*nsaddrs)); + for (n = 0, i = 0; i < cnt; i++) + if ((addr = ldns_rr_ns_nsdname(ldns_rr_list_rr(answer, i))) != NULL) + nsaddrs[n++] = ldns_get_rr_list_addr_by_name(res, + addr, LDNS_RR_CLASS_IN, 0); + + o_print_pkt_server = false; + o_recursive = false; + o_rrtype = LDNS_RR_TYPE_SOA; + for (i = 0; i < n; i++) { + cnt = ldns_rr_list_rr_count(nsaddrs[i]); + for (j = 0; j < cnt; j++) { + ldns_resolver_remove_nameservers(res); + rr = ldns_rr_list_rr(nsaddrs[i], j); + if (ldns_resolver_ip6(res) == LDNS_RESOLV_INET && + ldns_rr_get_type(rr) == LDNS_RR_TYPE_AAAA || + ldns_resolver_ip6(res) == LDNS_RESOLV_INET6 && + ldns_rr_get_type(rr) == LDNS_RR_TYPE_A) + continue; + if (ldns_resolver_push_nameserver_rr(res, rr) == LDNS_STATUS_OK) + /* bind9-host queries for domain, not dname here */ + doquery(res, dname); + } + } + return 0; +} + +static void +resolver_set_nameserver_hostname(ldns_resolver *res, const char *server) { + struct addrinfo hints, *ailist, *ai; + ldns_status status; + ldns_rdf *rdf; + int err; + + memset(&hints, 0, sizeof hints); + switch (ldns_resolver_ip6(res)) { + case LDNS_RESOLV_INET: hints.ai_family = PF_INET; break; + case LDNS_RESOLV_INET6: hints.ai_family = PF_INET6; break; + default: hints.ai_family = PF_UNSPEC; break; + } + hints.ai_socktype = SOCK_STREAM; + do err = getaddrinfo(server, NULL, &hints, &ailist); + while (err == EAI_AGAIN); + if (err != 0) + die(1, "couldn't get address for '%s': %s", server, gai_strerror(err)); + for (ai = ailist; ai != NULL; ai = ai->ai_next) { + if ((rdf = ldns_sockaddr_storage2rdf((void*)ai->ai_addr, NULL)) == NULL) + die(1, "couldn't allocate an rdf: %s", + ldns_get_errorstr_by_id(LDNS_STATUS_MEM_ERR)); + status = ldns_resolver_push_nameserver(res, rdf); + if (status != LDNS_STATUS_OK) + die(1, "couldn't push a nameserver address: %s", + ldns_get_errorstr_by_id(status)); + } +} + +static void +resolver_set_nameserver_str(ldns_resolver *res, const char *server) { + ldns_rdf *addr; + + ldns_resolver_remove_nameservers(res); + addr = ldns_rdf_new_addr_frm_str(server); + if (addr) { + if (ldns_resolver_push_nameserver(res, addr) != LDNS_STATUS_OK) + die(1, "couldn't push a nameserver address"); + } else + resolver_set_nameserver_hostname(res, server); +} + +int +main(int argc, char *argv[]) { + ldns_rdf *addr, *dname; + ldns_resolver *res; + ldns_status status; + struct timeval restimeout; + + parse_args(argc, argv); + + status = ldns_resolver_new_default(&res); + if (status != LDNS_STATUS_OK) + die(1, "error creating resolver: %s", ldns_get_errorstr_by_id(status)); + if (ldns_resolver_nameserver_count(res) == 0) + ldns_resolver_push_default_servers(res); + + ldns_resolver_set_usevc(res, o_tcp); + restimeout.tv_sec = o_timeout > 0 ? o_timeout : + o_tcp ? DEFAULT_TCP_TIMEOUT : DEFAULT_UDP_TIMEOUT; + restimeout.tv_usec = 0; + ldns_resolver_set_timeout(res, restimeout); + ldns_resolver_set_retry(res, o_retries+1); + ldns_resolver_set_ip6(res, o_ipversion); + ldns_resolver_set_defnames(res, false); + ldns_resolver_set_fallback(res, false); + + if (o_server) + resolver_set_nameserver_str(res, o_server); + + if (ldns_str2rdf_a(&addr, o_name) == LDNS_STATUS_OK) { + dname = ldns_rdf_reverse_a(addr, "in-addr.arpa"); + if (dname == NULL) + die(1, "can't reverse '%s': %s", o_name, + ldns_get_errorstr_by_id(LDNS_STATUS_MEM_ERR)); + o_mode = M_SINGLE_Q; + o_rrtype = LDNS_RR_TYPE_PTR; + return !doquery(res, dname); + } else if (ldns_str2rdf_aaaa(&addr, o_name) == LDNS_STATUS_OK) { + dname = ldns_rdf_reverse_aaaa(addr, o_ip6_int ? "ip6.int" : "ip6.arpa"); + if (dname == NULL) + die(1, "can't reverse '%s': %s", o_name, + ldns_get_errorstr_by_id(LDNS_STATUS_MEM_ERR)); + o_mode = M_SINGLE_Q; + o_rrtype = LDNS_RR_TYPE_PTR; + return !doquery(res, dname); + } + return !(o_mode == M_SOA ? dosoa : o_mode == M_AXFR ? doaxfr : dosearch) + (res, safe_str2rdf_dname(o_name), ndots(o_name) >= o_ndots); +} |