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