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