master xplshn/aruu / cmd / net / host.c
  1/* See LICENSE file for copyright and license details. */
  2
  3
  4#include "util.h"
  5#include "arg.h"
  6
  7#include <arpa/inet.h>
  8#include <ctype.h>
  9#include <errno.h>
 10#include <netdb.h>
 11#include <netinet/in.h>
 12#include <resolv.h>
 13#include <stdio.h>
 14#include <stdlib.h>
 15#include <string.h>
 16#include <sys/socket.h>
 17#include <sys/time.h>
 18#include <sys/types.h>
 19#include <unistd.h>
 20
 21struct RRType {
 22	const char *name;
 23	const char *msg;
 24	int type;
 25};
 26
 27static const struct RRType rrtypes[] = {
 28	{ "A", "has address", 1 },
 29	{ "NS", "name server", 2 },
 30	{ "CNAME", "is a nickname for", 5 },
 31	{ "SOA", "start of authority", 6 },
 32	{ "PTR", "domain name pointer", 12 },
 33	{ "MX", "mail is handled by", 15 },
 34	{ "TXT", "descriptive text", 16 },
 35	{ "AAAA", "has IPv6 address", 28 },
 36	{ "SRV", "has SRV record", 33 },
 37	{ "ANY", "has ANY record", 255 }
 38};
 39
 40static void
 41usage(void)
 42{
 43	eprintf("usage: %s [-t type] name [server]\n", argv0);
 44}
 45
 46static int
 47load_nameservers(char **ns, int max_ns)
 48{
 49	FILE *fp = fopen("/etc/resolv.conf", "r");
 50	char line[256];
 51	char *p, *end;
 52	int count = 0;
 53
 54	if (!fp)
 55		return 0;
 56
 57	while (fgets(line, sizeof(line), fp) && count < max_ns) {
 58		if (strncmp(line, "nameserver", 10) == 0 && isspace(line[10])) {
 59			p = line + 11;
 60			while (isspace(*p))
 61				p++;
 62			end = p;
 63			while (*end && !isspace(*end) && *end != '#')
 64				end++;
 65			*end = '\0';
 66			if (*p) {
 67				ns[count++] = estrdup(p);
 68			}
 69		}
 70	}
 71	fclose(fp);
 72	return count;
 73}
 74
 75static char *
 76reverse_ip(const char *ip_str, int *type)
 77{
 78	struct in_addr addr4;
 79	struct in6_addr addr6;
 80	unsigned char *p;
 81	char *buf = emalloc(256);
 82	int i, j;
 83
 84	if (inet_pton(AF_INET, ip_str, &addr4) > 0) {
 85		p = (unsigned char *)&addr4.s_addr;
 86		sprintf(buf, "%d.%d.%d.%d.in-addr.arpa", p[3], p[2], p[1], p[0]);
 87		*type = 12;
 88		return buf;
 89	} else if (inet_pton(AF_INET6, ip_str, &addr6) > 0) {
 90		p = (unsigned char *)&addr6.s6_addr;
 91		j = 0;
 92		for (i = 15; i >= 0; i--) {
 93			j += sprintf(buf + j, "%x.%x.", p[i] & 15, p[i] >> 4);
 94		}
 95		strcpy(buf + j, "ip6.arpa");
 96		*type = 12;
 97		return buf;
 98	}
 99
100	free(buf);
101	return NULL;
102}
103
104static int
105send_query(const char *ns, const char *name, int type, unsigned char *resp, int resp_len)
106{
107	struct addrinfo hints, *res;
108	unsigned char query[512];
109	struct timeval tv;
110	int qlen;
111	int sock = -1;
112	int r;
113
114	qlen = res_mkquery(0, name, 1, type, NULL, 0, NULL, query, sizeof(query));
115	if (qlen < 0) {
116		weprintf("res_mkquery failed for %s\n", name);
117		return -1;
118	}
119
120	memset(&hints, 0, sizeof(hints));
121	hints.ai_family = AF_UNSPEC;
122	hints.ai_socktype = SOCK_DGRAM;
123
124	r = getaddrinfo(ns, "53", &hints, &res);
125	if (r != 0) {
126		weprintf("getaddrinfo %s: %s\n", ns, gai_strerror(r));
127		return -1;
128	}
129
130	sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
131	if (sock < 0) {
132		freeaddrinfo(res);
133		return -1;
134	}
135
136	tv.tv_sec = 5;
137	tv.tv_usec = 0;
138	setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
139
140	if (connect(sock, res->ai_addr, res->ai_addrlen) < 0) {
141		close(sock);
142		freeaddrinfo(res);
143		return -1;
144	}
145
146	if (send(sock, query, qlen, 0) < 0) {
147		close(sock);
148		freeaddrinfo(res);
149		return -1;
150	}
151
152	r = recv(sock, resp, resp_len, 0);
153	close(sock);
154	freeaddrinfo(res);
155
156	return r;
157}
158
159static unsigned short
160peek_be(const unsigned char *p)
161{
162	return (p[0] << 8) | p[1];
163}
164
165static int
166parse_response(const unsigned char *resp, int resp_len, const char *query_name)
167{
168	const unsigned char *p;
169	char name[256];
170	char target[256];
171	char ipv6[64];
172	char mname[256], rname[256];
173	const char *err;
174	const char *msg;
175	struct in_addr a;
176	int rcode, qdcount, ancount;
177	int i, n, type, rdlen, pref, n1, n2;
178	unsigned int ttl;
179
180	if (resp_len < 12) {
181		weprintf("response too short: %d\n", resp_len);
182		return -1;
183	}
184
185	rcode = resp[3] & 0x0f;
186	if (rcode != 0) {
187		err = "Unknown error";
188		switch (rcode) {
189		case 1: err = "Format error"; break;
190		case 2: err = "Server failure"; break;
191		case 3: err = "Non-existent domain"; break;
192		case 4: err = "Not implemented"; break;
193		case 5: err = "Refused"; break;
194		}
195		eprintf("Host not found: %s\n", err);
196	}
197
198	qdcount = peek_be(resp + 4);
199	ancount = peek_be(resp + 6);
200
201	p = resp + 12;
202	for (i = 0; i < qdcount; i++) {
203		n = dn_expand(resp, resp + resp_len, p, name, sizeof(name));
204		if (n < 0)
205			return -1;
206		p += n + 4;
207	}
208
209	if (ancount == 0) {
210		printf("%s has no records\n", query_name);
211		return 0;
212	}
213
214	for (i = 0; i < ancount; i++) {
215		n = dn_expand(resp, resp + resp_len, p, name, sizeof(name));
216		if (n < 0)
217			return -1;
218		p += n;
219		type = peek_be(p);
220		ttl = (p[4] << 24) | (p[5] << 16) | (p[6] << 8) | p[7];
221		rdlen = peek_be(p + 8);
222		p += 10;
223
224		(void)ttl;
225
226		if (type == 1) {
227			memcpy(&a, p, 4);
228			printf("%s has address %s\n", name, inet_ntoa(a));
229		} else if (type == 28) {
230			inet_ntop(AF_INET6, p, ipv6, sizeof(ipv6));
231			printf("%s has IPv6 address %s\n", name, ipv6);
232		} else if (type == 2 || type == 5 || type == 12) {
233			dn_expand(resp, resp + resp_len, p, target, sizeof(target));
234			msg = "has record";
235			if (type == 2) msg = "name server";
236			else if (type == 5) msg = "is a nickname for";
237			else if (type == 12) msg = "domain name pointer";
238			printf("%s %s %s\n", name, msg, target);
239		} else if (type == 15) {
240			pref = peek_be(p);
241			dn_expand(resp, resp + resp_len, p + 2, target, sizeof(target));
242			printf("%s mail is handled by %d %s\n", name, pref, target);
243		} else if (type == 16) {
244			printf("%s descriptive text \"%.*s\"\n", name, (int)p[0], (char *)(p + 1));
245		} else if (type == 6) {
246			n1 = dn_expand(resp, resp + resp_len, p, mname, sizeof(mname));
247			n2 = dn_expand(resp, resp + resp_len, p + n1, rname, sizeof(rname));
248			(void)n2;
249			printf("%s start of authority %s %s\n", name, mname, rname);
250		} else {
251			printf("%s has unsupported record type %d\n", name, type);
252		}
253
254		p += rdlen;
255	}
256
257	return 0;
258}
259
260// ?man host: dns lookup utility
261// ?man arguments: name [server
262// ?man look up hostnames and IP addresses using dns
263int
264main(int argc, char *argv[])
265{
266	unsigned char resp[65536];
267	char *ns[8];
268	char *tflag = NULL;
269	char *name;
270	char *query_name;
271	char *rev;
272	int ns_count = 0;
273	int type = 1;
274	int i, r;
275
276	ARGBEGIN {
277	// ?man -t:str: sort or specify timestamp
278	case 't':
279		tflag = EARGF(usage());
280		break;
281	default:
282		usage();
283	} ARGEND
284
285	if (argc < 1)
286		usage();
287
288	query_name = argv[0];
289
290	if (argc > 1) {
291		ns[0] = argv[1];
292		ns_count = 1;
293	} else {
294		ns_count = load_nameservers(ns, 8);
295		if (ns_count == 0)
296			eprintf("no nameservers found in /etc/resolv.conf\n");
297	}
298
299	rev = reverse_ip(query_name, &type);
300	if (rev) {
301		name = rev;
302	} else {
303		name = query_name;
304		if (tflag) {
305			type = -1;
306			for (i = 0; i < (int)LEN(rrtypes); i++) {
307				if (strcasecmp(tflag, rrtypes[i].name) == 0) {
308					type = rrtypes[i].type;
309					break;
310				}
311			}
312			if (type == -1)
313				eprintf("invalid record type: %s\n", tflag);
314		}
315	}
316
317	for (i = 0; i < ns_count; i++) {
318		r = send_query(ns[i], name, type, resp, sizeof(resp));
319		if (r > 0) {
320			parse_response(resp, r, query_name);
321			break;
322		}
323	}
324
325	if (rev)
326		free(rev);
327
328	if (argc == 1) {
329		for (i = 0; i < ns_count; i++) {
330			free(ns[i]);
331		}
332	}
333
334	return r > 0 ? 0 : 1;
335}