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