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}