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