Compatibility fixes for NetBSD 5.1.2
[darkstat] / dns.c
1 /* darkstat 3
2 * copyright (c) 2001-2011 Emil Mikulic.
3 *
4 * dns.c: synchronous DNS in a child process.
5 *
6 * You may use, modify and redistribute this file under the terms of the
7 * GNU General Public License version 2. (see COPYING.GPL)
8 */
9
10 #include "cdefs.h"
11 #include "conv.h"
12 #include "decode.h"
13 #include "dns.h"
14 #include "err.h"
15 #include "hosts_db.h"
16 #include "queue.h"
17 #include "str.h"
18 #include "tree.h"
19 #include "bsd.h" /* for setproctitle, strlcpy */
20
21 #include <sys/param.h>
22 #include <sys/socket.h>
23 #include <sys/wait.h>
24 #include <assert.h>
25 #include <errno.h>
26 #include <netdb.h>
27 #include <signal.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 #ifdef __NetBSD__
33 # define gethostbyaddr(addr, len, type) \
34 gethostbyaddr((const char *)(addr), len, type)
35 #endif
36
37 static void dns_main(void) _noreturn_; /* the child process runs this */
38
39 #define CHILD 0 /* child process uses this socket */
40 #define PARENT 1
41 static int sock[2];
42 static pid_t pid = -1;
43
44 struct dns_reply {
45 struct addr addr;
46 int error; /* for gai_strerror(), or 0 if no error */
47 char name[256]; /* http://tools.ietf.org/html/rfc1034#section-3.1 */
48 };
49
50 void
51 dns_init(const char *privdrop_user)
52 {
53 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) == -1)
54 err(1, "socketpair");
55
56 pid = fork();
57 if (pid == -1)
58 err(1, "fork");
59
60 if (pid == 0) {
61 /* We are the child. */
62 privdrop(NULL /* don't chroot */, privdrop_user);
63 close(sock[PARENT]);
64 sock[PARENT] = -1;
65 daemonize_finish(); /* drop our copy of the lifeline! */
66 if (signal(SIGUSR1, SIG_IGN) == SIG_ERR)
67 errx(1, "signal(SIGUSR1, ignore) failed");
68 dns_main();
69 verbosef("fell out of dns_main()");
70 exit(0);
71 } else {
72 /* We are the parent. */
73 close(sock[CHILD]);
74 sock[CHILD] = -1;
75 fd_set_nonblock(sock[PARENT]);
76 verbosef("DNS child has PID %d", pid);
77 }
78 }
79
80 void
81 dns_stop(void)
82 {
83 if (pid == -1)
84 return; /* no child was started */
85 close(sock[PARENT]);
86 if (kill(pid, SIGINT) == -1)
87 err(1, "kill");
88 verbosef("dns_stop() waiting for child");
89 if (waitpid(pid, NULL, 0) == -1)
90 err(1, "waitpid");
91 verbosef("dns_stop() done waiting for child");
92 }
93
94 struct tree_rec {
95 RB_ENTRY(tree_rec) ptree;
96 struct addr ip;
97 };
98
99 static int
100 tree_cmp(struct tree_rec *a, struct tree_rec *b)
101 {
102 if (a->ip.family != b->ip.family)
103 /* Sort IPv4 to the left of IPv6. */
104 return ((a->ip.family == IPv4) ? -1 : +1);
105
106 if (a->ip.family == IPv4)
107 return (memcmp(&a->ip.ip.v4, &b->ip.ip.v4, sizeof(a->ip.ip.v4)));
108 else {
109 assert(a->ip.family == IPv6);
110 return (memcmp(&a->ip.ip.v6, &b->ip.ip.v6, sizeof(a->ip.ip.v6)));
111 }
112 }
113
114 static RB_HEAD(tree_t, tree_rec) ip_tree = RB_INITIALIZER(&tree_rec);
115 /* Quiet warnings. */
116 static struct tree_rec * tree_t_RB_NEXT(struct tree_rec *elm)
117 _unused_;
118 static struct tree_rec * tree_t_RB_MINMAX(struct tree_t *head, int val)
119 _unused_;
120 RB_GENERATE(tree_t, tree_rec, ptree, tree_cmp)
121
122 void
123 dns_queue(const struct addr *const ipaddr)
124 {
125 struct tree_rec *rec;
126 ssize_t num_w;
127
128 if (pid == -1)
129 return; /* no child was started - we're not doing any DNS */
130
131 if ((ipaddr->family != IPv4) && (ipaddr->family != IPv6)) {
132 verbosef("dns_queue() for unknown family %d", ipaddr->family);
133 return;
134 }
135
136 rec = xmalloc(sizeof(*rec));
137 memcpy(&rec->ip, ipaddr, sizeof(rec->ip));
138
139 if (RB_INSERT(tree_t, &ip_tree, rec) != NULL) {
140 /* Already queued - this happens seldom enough that we don't care about
141 * the performance hit of needlessly malloc()ing. */
142 verbosef("already queued %s", addr_to_str(ipaddr));
143 free(rec);
144 return;
145 }
146
147 num_w = write(sock[PARENT], ipaddr, sizeof(*ipaddr)); /* won't block */
148 if (num_w == 0)
149 warnx("dns_queue: write: ignoring end of file");
150 else if (num_w == -1)
151 warn("dns_queue: ignoring write error");
152 else if (num_w != sizeof(*ipaddr))
153 err(1, "dns_queue: wrote %zu instead of %zu", num_w, sizeof(*ipaddr));
154 }
155
156 static void
157 dns_unqueue(const struct addr *const ipaddr)
158 {
159 struct tree_rec tmp, *rec;
160
161 memcpy(&tmp.ip, ipaddr, sizeof(tmp.ip));
162 if ((rec = RB_FIND(tree_t, &ip_tree, &tmp)) != NULL) {
163 RB_REMOVE(tree_t, &ip_tree, rec);
164 free(rec);
165 }
166 else
167 verbosef("couldn't unqueue %s - not in queue!", addr_to_str(ipaddr));
168 }
169
170 /*
171 * Returns non-zero if result waiting, stores IP and name into given pointers
172 * (name buffer is allocated by dns_poll)
173 */
174 static int
175 dns_get_result(struct addr *ipaddr, char **name)
176 {
177 struct dns_reply reply;
178 ssize_t numread;
179
180 numread = read(sock[PARENT], &reply, sizeof(reply));
181 if (numread == -1) {
182 if (errno == EAGAIN)
183 return (0); /* no input waiting */
184 else
185 goto error;
186 }
187 if (numread == 0)
188 goto error; /* EOF */
189 if (numread != sizeof(reply))
190 errx(1, "dns_get_result read got %zu, expected %zu",
191 numread, sizeof(reply));
192
193 /* Return successful reply. */
194 memcpy(ipaddr, &reply.addr, sizeof(*ipaddr));
195 if (reply.error != 0) {
196 /* Identify common special cases. */
197 const char *type = "none";
198
199 if (reply.addr.family == IPv6) {
200 if (IN6_IS_ADDR_LINKLOCAL(&reply.addr.ip.v6))
201 type = "link-local";
202 else if (IN6_IS_ADDR_SITELOCAL(&reply.addr.ip.v6))
203 type = "site-local";
204 else if (IN6_IS_ADDR_MULTICAST(&reply.addr.ip.v6))
205 type = "multicast";
206 } else {
207 assert(reply.addr.family == IPv4);
208 if (IN_MULTICAST(htonl(reply.addr.ip.v4)))
209 type = "multicast";
210 }
211 xasprintf(name, "(%s)", type);
212 }
213 else /* Correctly resolved name. */
214 *name = xstrdup(reply.name);
215
216 dns_unqueue(&reply.addr);
217 return (1);
218
219 error:
220 warn("dns_get_result: ignoring read error");
221 /* FIXME: re-align to stream? restart dns child? */
222 return (0);
223 }
224
225 void
226 dns_poll(void)
227 {
228 struct addr ip;
229 char *name;
230
231 if (pid == -1)
232 return; /* no child was started - we're not doing any DNS */
233
234 while (dns_get_result(&ip, &name)) {
235 /* push into hosts_db */
236 struct bucket *b = host_find(&ip);
237
238 if (b == NULL) {
239 verbosef("resolved %s to %s but it's not in the DB!",
240 addr_to_str(&ip), name);
241 return;
242 }
243 if (b->u.host.dns != NULL) {
244 verbosef("resolved %s to %s but it's already in the DB!",
245 addr_to_str(&ip), name);
246 return;
247 }
248 b->u.host.dns = name;
249 }
250 }
251
252 /* ------------------------------------------------------------------------ */
253
254 struct qitem {
255 STAILQ_ENTRY(qitem) entries;
256 struct addr ip;
257 };
258
259 STAILQ_HEAD(qhead, qitem) queue = STAILQ_HEAD_INITIALIZER(queue);
260
261 static void
262 enqueue(const struct addr *const ip)
263 {
264 struct qitem *i;
265
266 i = xmalloc(sizeof(*i));
267 memcpy(&i->ip, ip, sizeof(i->ip));
268 STAILQ_INSERT_TAIL(&queue, i, entries);
269 verbosef("DNS: enqueued %s", addr_to_str(ip));
270 }
271
272 /* Return non-zero and populate <ip> pointer if queue isn't empty. */
273 static int
274 dequeue(struct addr *ip)
275 {
276 struct qitem *i;
277
278 i = STAILQ_FIRST(&queue);
279 if (i == NULL)
280 return (0);
281 STAILQ_REMOVE_HEAD(&queue, entries);
282 memcpy(ip, &i->ip, sizeof(*ip));
283 free(i);
284 verbosef("DNS: dequeued %s", addr_to_str(ip));
285 return 1;
286 }
287
288 static void
289 xwrite(const int d, const void *buf, const size_t nbytes)
290 {
291 ssize_t ret = write(d, buf, nbytes);
292
293 if (ret == -1)
294 err(1, "write");
295 if (ret != (ssize_t)nbytes)
296 err(1, "wrote %d bytes instead of all %d bytes", (int)ret, (int)nbytes);
297 }
298
299 static void
300 dns_main(void)
301 {
302 struct addr ip;
303
304 setproctitle("DNS child");
305 fd_set_nonblock(sock[CHILD]);
306 verbosef("DNS child entering main DNS loop");
307 for (;;) {
308 int blocking;
309
310 if (STAILQ_EMPTY(&queue)) {
311 blocking = 1;
312 fd_set_block(sock[CHILD]);
313 verbosef("entering blocking read loop");
314 } else {
315 blocking = 0;
316 fd_set_nonblock(sock[CHILD]);
317 verbosef("non-blocking poll");
318 }
319 for (;;) {
320 /* While we have input to process... */
321 ssize_t numread = read(sock[CHILD], &ip, sizeof(ip));
322 if (numread == 0)
323 exit(0); /* end of file, nothing more to do here. */
324 if (numread == -1) {
325 if (!blocking && (errno == EAGAIN))
326 break; /* ran out of input */
327 /* else error */
328 err(1, "DNS: read failed");
329 }
330 if (numread != sizeof(ip))
331 err(1, "DNS: read got %zu bytes, expecting %zu",
332 numread, sizeof(ip));
333 enqueue(&ip);
334 if (blocking) {
335 /* After one blocking read, become non-blocking so that when we
336 * run out of input we fall through to queue processing.
337 */
338 blocking = 0;
339 fd_set_nonblock(sock[CHILD]);
340 }
341 }
342
343 /* Process queue. */
344 if (dequeue(&ip)) {
345 struct dns_reply reply;
346 struct sockaddr_in sin;
347 struct sockaddr_in6 sin6;
348 struct hostent *he;
349 char host[NI_MAXHOST];
350 int ret, flags;
351
352 reply.addr = ip;
353 flags = NI_NAMEREQD;
354 # ifdef NI_IDN
355 flags |= NI_IDN;
356 # endif
357 switch (ip.family) {
358 case IPv4:
359 sin.sin_family = AF_INET;
360 sin.sin_addr.s_addr = ip.ip.v4;
361 ret = getnameinfo((struct sockaddr *) &sin, sizeof(sin),
362 host, sizeof(host), NULL, 0, flags);
363 if (ret == EAI_FAMILY) {
364 verbosef("getnameinfo error %s, trying gethostbyname",
365 gai_strerror(ret));
366 he = gethostbyaddr(&sin.sin_addr.s_addr,
367 sizeof(sin.sin_addr.s_addr), sin.sin_family);
368 if (he == NULL) {
369 ret = EAI_FAIL;
370 verbosef("gethostbyname error %s", hstrerror(h_errno));
371 } else {
372 ret = 0;
373 strlcpy(host, he->h_name, sizeof(host));
374 }
375 }
376 break;
377 case IPv6:
378 sin6.sin6_family = AF_INET6;
379 memcpy(&sin6.sin6_addr, &ip.ip.v6, sizeof(sin6.sin6_addr));
380 ret = getnameinfo((struct sockaddr *) &sin6, sizeof(sin6),
381 host, sizeof(host), NULL, 0, flags);
382 break;
383 default:
384 ret = EAI_FAMILY;
385 }
386
387 if (ret != 0) {
388 reply.name[0] = '\0';
389 reply.error = ret;
390 } else {
391 assert(sizeof(reply.name) > sizeof(char *)); /* not just a ptr */
392 strlcpy(reply.name, host, sizeof(reply.name));
393 reply.error = 0;
394 }
395 fd_set_block(sock[CHILD]);
396 xwrite(sock[CHILD], &reply, sizeof(reply));
397 verbosef("DNS: %s is \"%s\".", addr_to_str(&reply.addr),
398 (ret == 0) ? reply.name : gai_strerror(ret));
399 }
400 }
401 }
402
403 /* vim:set ts=3 sw=3 tw=78 expandtab: */