2 * copyright (c) 2001-2014 Emil Mikulic.
4 * dns.c: synchronous DNS in a child process.
6 * You may use, modify and redistribute this file under the terms of the
7 * GNU General Public License version 2. (see COPYING.GPL)
20 #include "bsd.h" /* for setproctitle, strlcpy */
22 #include <sys/param.h>
23 #include <sys/socket.h>
34 # define gethostbyaddr(addr, len, type) \
35 gethostbyaddr((const char *)(addr), len, type)
38 static void dns_main(void) _noreturn_
; /* the child process runs this */
40 #define CHILD 0 /* child process uses this socket */
42 static int dns_sock
[2];
43 static pid_t pid
= -1;
47 int error
; /* for gai_strerror(), or 0 if no error */
48 char name
[256]; /* http://tools.ietf.org/html/rfc1034#section-3.1 */
52 dns_init(const char *privdrop_user
)
54 if (socketpair(AF_UNIX
, SOCK_STREAM
, 0, dns_sock
) == -1)
62 /* We are the child. */
63 privdrop(NULL
/* don't chroot */, privdrop_user
);
64 close(dns_sock
[PARENT
]);
65 dns_sock
[PARENT
] = -1;
66 daemonize_finish(); /* drop our copy of the lifeline! */
67 if (signal(SIGUSR1
, SIG_IGN
) == SIG_ERR
)
68 errx(1, "signal(SIGUSR1, ignore) failed");
71 errx(1, "DNS child fell out of dns_main()");
73 /* We are the parent. */
74 close(dns_sock
[CHILD
]);
76 fd_set_nonblock(dns_sock
[PARENT
]);
77 verbosef("DNS child has PID %d", pid
);
85 return; /* no child was started */
86 close(dns_sock
[PARENT
]);
87 if (kill(pid
, SIGINT
) == -1)
89 verbosef("dns_stop() waiting for child");
90 if (waitpid(pid
, NULL
, 0) == -1)
92 verbosef("dns_stop() done waiting for child");
96 RB_ENTRY(tree_rec
) ptree
;
101 tree_cmp(struct tree_rec
*a
, struct tree_rec
*b
)
103 if (a
->ip
.family
!= b
->ip
.family
)
104 /* Sort IPv4 to the left of IPv6. */
105 return ((a
->ip
.family
== IPv4
) ? -1 : +1);
107 if (a
->ip
.family
== IPv4
)
108 return (memcmp(&a
->ip
.ip
.v4
, &b
->ip
.ip
.v4
, sizeof(a
->ip
.ip
.v4
)));
110 assert(a
->ip
.family
== IPv6
);
111 return (memcmp(&a
->ip
.ip
.v6
, &b
->ip
.ip
.v6
, sizeof(a
->ip
.ip
.v6
)));
115 static RB_HEAD(tree_t
, tree_rec
) ip_tree
= RB_INITIALIZER(&tree_rec
);
116 RB_GENERATE_STATIC(tree_t
, tree_rec
, ptree
, tree_cmp
)
119 dns_queue(const struct addr
*const ipaddr
)
121 struct tree_rec
*rec
;
125 return; /* no child was started - we're not doing any DNS */
127 if ((ipaddr
->family
!= IPv4
) && (ipaddr
->family
!= IPv6
)) {
128 verbosef("dns_queue() for unknown family %d", ipaddr
->family
);
132 rec
= xmalloc(sizeof(*rec
));
133 memcpy(&rec
->ip
, ipaddr
, sizeof(rec
->ip
));
135 if (RB_INSERT(tree_t
, &ip_tree
, rec
) != NULL
) {
136 /* Already queued - this happens seldom enough that we don't care about
137 * the performance hit of needlessly malloc()ing. */
138 verbosef("already queued %s", addr_to_str(ipaddr
));
143 num_w
= write(dns_sock
[PARENT
], ipaddr
, sizeof(*ipaddr
)); /* won't block */
145 warnx("dns_queue: write: ignoring end of file");
146 else if (num_w
== -1)
147 warn("dns_queue: ignoring write error");
148 else if (num_w
!= sizeof(*ipaddr
))
149 err(1, "dns_queue: wrote %zu instead of %zu", num_w
, sizeof(*ipaddr
));
153 dns_unqueue(const struct addr
*const ipaddr
)
155 struct tree_rec tmp
, *rec
;
157 memcpy(&tmp
.ip
, ipaddr
, sizeof(tmp
.ip
));
158 if ((rec
= RB_FIND(tree_t
, &ip_tree
, &tmp
)) != NULL
) {
159 RB_REMOVE(tree_t
, &ip_tree
, rec
);
163 verbosef("couldn't unqueue %s - not in queue!", addr_to_str(ipaddr
));
167 * Returns non-zero if result waiting, stores IP and name into given pointers
168 * (name buffer is allocated by dns_poll)
171 dns_get_result(struct addr
*ipaddr
, char **name
)
173 struct dns_reply reply
;
176 numread
= read(dns_sock
[PARENT
], &reply
, sizeof(reply
));
179 return (0); /* no input waiting */
184 goto error
; /* EOF */
185 if (numread
!= sizeof(reply
))
186 errx(1, "dns_get_result read got %zu, expected %zu",
187 numread
, sizeof(reply
));
189 /* Return successful reply. */
190 memcpy(ipaddr
, &reply
.addr
, sizeof(*ipaddr
));
191 if (reply
.error
!= 0) {
192 /* Identify common special cases. */
193 const char *type
= "none";
195 if (reply
.addr
.family
== IPv6
) {
196 if (IN6_IS_ADDR_LINKLOCAL(&reply
.addr
.ip
.v6
))
198 else if (IN6_IS_ADDR_SITELOCAL(&reply
.addr
.ip
.v6
))
200 else if (IN6_IS_ADDR_MULTICAST(&reply
.addr
.ip
.v6
))
203 assert(reply
.addr
.family
== IPv4
);
204 if (IN_MULTICAST(htonl(reply
.addr
.ip
.v4
)))
207 xasprintf(name
, "(%s)", type
);
209 else /* Correctly resolved name. */
210 *name
= xstrdup(reply
.name
);
212 dns_unqueue(&reply
.addr
);
216 warn("dns_get_result: ignoring read error");
217 /* FIXME: re-align to stream? restart dns child? */
228 return; /* no child was started - we're not doing any DNS */
230 while (dns_get_result(&ip
, &name
)) {
231 /* push into hosts_db */
232 struct bucket
*b
= host_find(&ip
);
235 verbosef("resolved %s to %s but it's not in the DB!",
236 addr_to_str(&ip
), name
);
239 if (b
->u
.host
.dns
!= NULL
) {
240 verbosef("resolved %s to %s but it's already in the DB!",
241 addr_to_str(&ip
), name
);
244 b
->u
.host
.dns
= name
;
248 /* ------------------------------------------------------------------------ */
251 STAILQ_ENTRY(qitem
) entries
;
255 static STAILQ_HEAD(qhead
, qitem
) queue
= STAILQ_HEAD_INITIALIZER(queue
);
258 enqueue(const struct addr
*const ip
)
262 i
= xmalloc(sizeof(*i
));
263 memcpy(&i
->ip
, ip
, sizeof(i
->ip
));
264 STAILQ_INSERT_TAIL(&queue
, i
, entries
);
265 verbosef("DNS: enqueued %s", addr_to_str(ip
));
268 /* Return non-zero and populate <ip> pointer if queue isn't empty. */
270 dequeue(struct addr
*ip
)
274 i
= STAILQ_FIRST(&queue
);
277 STAILQ_REMOVE_HEAD(&queue
, entries
);
278 memcpy(ip
, &i
->ip
, sizeof(*ip
));
280 verbosef("DNS: dequeued %s", addr_to_str(ip
));
285 xwrite(const int d
, const void *buf
, const size_t nbytes
)
287 ssize_t ret
= write(d
, buf
, nbytes
);
291 if (ret
!= (ssize_t
)nbytes
)
292 err(1, "wrote %d bytes instead of all %d bytes", (int)ret
, (int)nbytes
);
300 setproctitle("DNS child");
301 fd_set_nonblock(dns_sock
[CHILD
]);
302 verbosef("DNS child entering main DNS loop");
306 if (STAILQ_EMPTY(&queue
)) {
308 fd_set_block(dns_sock
[CHILD
]);
309 verbosef("entering blocking read loop");
312 fd_set_nonblock(dns_sock
[CHILD
]);
313 verbosef("non-blocking poll");
316 /* While we have input to process... */
317 ssize_t numread
= read(dns_sock
[CHILD
], &ip
, sizeof(ip
));
319 exit(0); /* end of file, nothing more to do here. */
321 if (!blocking
&& (errno
== EAGAIN
))
322 break; /* ran out of input */
324 err(1, "DNS: read failed");
326 if (numread
!= sizeof(ip
))
327 err(1, "DNS: read got %zu bytes, expecting %zu",
328 numread
, sizeof(ip
));
331 /* After one blocking read, become non-blocking so that when we
332 * run out of input we fall through to queue processing.
335 fd_set_nonblock(dns_sock
[CHILD
]);
341 struct dns_reply reply
;
342 struct sockaddr_in sin
;
343 struct sockaddr_in6 sin6
;
345 char host
[NI_MAXHOST
];
355 sin
.sin_family
= AF_INET
;
356 sin
.sin_addr
.s_addr
= ip
.ip
.v4
;
357 ret
= getnameinfo((struct sockaddr
*) &sin
, sizeof(sin
),
358 host
, sizeof(host
), NULL
, 0, flags
);
359 if (ret
== EAI_FAMILY
) {
360 verbosef("getnameinfo error %s, trying gethostbyname",
362 he
= gethostbyaddr(&sin
.sin_addr
.s_addr
,
363 sizeof(sin
.sin_addr
.s_addr
), sin
.sin_family
);
366 verbosef("gethostbyname error %s", hstrerror(h_errno
));
369 strlcpy(host
, he
->h_name
, sizeof(host
));
374 sin6
.sin6_family
= AF_INET6
;
375 memcpy(&sin6
.sin6_addr
, &ip
.ip
.v6
, sizeof(sin6
.sin6_addr
));
376 ret
= getnameinfo((struct sockaddr
*) &sin6
, sizeof(sin6
),
377 host
, sizeof(host
), NULL
, 0, flags
);
380 errx(1, "unexpected ip.family = %d", ip
.family
);
384 reply
.name
[0] = '\0';
387 assert(sizeof(reply
.name
) > sizeof(char *)); /* not just a ptr */
388 strlcpy(reply
.name
, host
, sizeof(reply
.name
));
391 fd_set_block(dns_sock
[CHILD
]);
392 xwrite(dns_sock
[CHILD
], &reply
, sizeof(reply
));
393 verbosef("DNS: %s is \"%s\".", addr_to_str(&reply
.addr
),
394 (ret
== 0) ? reply
.name
: gai_strerror(ret
));
399 /* vim:set ts=3 sw=3 tw=78 expandtab: */