1 /* Pluto Asynchronous DNS Helper Program -- for internal use only!
2 * Copyright (C) 2002 D. Hugh Redelmeier.
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * RCSID $Id: adns.c,v 1.5 2002/03/23 03:33:19 dhr Exp $
17 /* This program executes as multiple processes. The Master process
18 * receives queries (struct adns_query messages) from Pluto and distributes
19 * them amongst Worker processes. These Worker processes are created
20 * by the Master whenever a query arrives and no existing Worker is free.
21 * At most MAX_WORKERS will be created; after that, the Master will queue
22 * queries until a Worker becomes free. When a Worker has an answer from
23 * the resolver, it sends the answer as a struct adns_answer message to the
24 * Master. The Master then forwards the answer to Pluto, noting that
25 * the Worker is free to accept another query.
27 * The protocol is simple: Pluto sends a sequence of queries and receives
28 * a sequence of answers. select(2) is used by Pluto and by the Master
29 * process to decide when to read, but writes are done without checking
30 * for readiness. Communications is via pipes. Since only one process
31 * can write to each pipe, messages will not be interleaved. Fixed length
32 * records are used for simplicity.
34 * Pluto needs a way to indicate to the Master when to shut down
35 * and the Master needs to indicate this to each worker. EOF on the pipe
38 * The interfaces between these components are considered private to
39 * Pluto. This allows us to get away with less checking. This is a
40 * reason to use pipes instead of TCP/IP.
42 * Although the code uses plain old UNIX processes, it could be modified
43 * to use threads. That might reduce resource requirements. It would
44 * preclude running on systems without thread-safe resolvers.
54 #include <sys/types.h>
56 #include <netinet/in.h>
57 #include <arpa/nameser.h>
59 #include <netdb.h> /* ??? for h_errno */
65 # define UNUSED __attribute__ ((unused))
67 # define UNUSED /* ignore */
70 #include "adns.h" /* needs <resolv.h> */
78 /* shared by all processes */
80 static const char *name; /* program name, for messages */
82 static bool debug = FALSE;
84 /* Read a variable-length record from a pipe (and no more!).
85 * First bytes must be a size_t containing the length.
86 * HES_CONTINUE if record read
88 * HES_IO_ERROR_IN if errno tells the tale.
91 static enum helper_exit_status
92 read_pipe(int fd, unsigned char *stuff, size_t minlen, size_t maxlen)
98 ssize_t m = read(fd, stuff + n, goal - n);
104 syslog(LOG_ERR, "Input error on pipe: %s", strerror(errno));
105 return HES_IO_ERROR_IN;
110 return HES_OK; /* treat empty message as EOF */
115 if (n >= sizeof(size_t))
117 goal = *(size_t *)(void *)stuff;
118 if (goal < minlen || maxlen < goal)
121 fprintf(stderr, "%lu : [%lu, %lu]\n"
122 , (unsigned long)goal
123 , (unsigned long)minlen, (unsigned long)maxlen);
133 /* Write a variable-length record to a pipe.
134 * First bytes must be a size_t containing the length.
135 * HES_CONTINUE if record written
138 static enum helper_exit_status
139 write_pipe(int fd, const unsigned char *stuff)
141 size_t len = *(const size_t *)(const void *)stuff;
145 ssize_t m = write(fd, stuff + n, len - n);
149 /* error, but ignore and retry if EINTR */
152 syslog(LOG_ERR, "Output error from master: %s", strerror(errno));
153 return HES_IO_ERROR_OUT;
164 /**************** worker process ****************/
166 /* The interface in RHL6.x and BIND distribution 8.2.2 are different,
167 * so we build some of our own :-(
170 /* Support deprecated interface to allow for older releases of the resolver.
171 * Fake new interface!
172 * See resolver(3) bind distribution (should be in RHL6.1, but isn't).
173 * __RES was 19960801 in RHL6.2, an old resolver.
176 #if (__RES) <= 19960801
177 # define OLD_RESOLVER 1
182 # define res_ninit(statp) res_init()
183 # define res_nquery(statp, dname, class, type, answer, anslen) \
184 res_query(dname, class, type, answer, anslen)
185 # define res_nclose(statp) res_close()
187 static struct __res_state *statp = &_res;
189 #else /* !OLD_RESOLVER */
191 static struct __res_state my_res_state /* = { 0 } */;
192 static res_state statp = &my_res_state;
194 #endif /* !OLD_RESOLVER */
197 worker(int qfd, int afd)
200 int r = res_ninit(statp);
204 syslog(LOG_ERR, "cannot initialize resolver");
208 statp->options |= RES_ROTATE;
210 statp->options |= RES_DEBUG;
216 struct adns_answer a;
218 enum helper_exit_status r = read_pipe(qfd, (unsigned char *)&q
219 , sizeof(q), sizeof(q));
221 if (r != HES_CONTINUE)
222 return r; /* some kind of exit */
224 if (q.qmagic != ADNS_Q_MAGIC)
226 syslog(LOG_ERR, "error in input from master: bad magic");
227 return HES_BAD_MAGIC;
230 a.amagic = ADNS_A_MAGIC;
231 a.continuation = q.continuation;
233 a.result = res_nquery(statp, q.name_buf, C_IN, q.type, a.ans, sizeof(a.ans));
234 a.h_errno_val = h_errno;
236 a.len = offsetof(struct adns_answer, ans) + (a.result < 0? 0 : a.result);
238 /* write answer, possibly a bit at a time */
239 r = write_pipe(afd, (const unsigned char *)&a);
241 if (r != HES_CONTINUE)
242 return r; /* some kind of exit */
246 /**************** master process ****************/
248 bool eof_from_pluto = FALSE;
249 static int pluto_qfd = NULL_FD; /* fd for queries from Pluto */
250 static int pluto_afd = NULL_FD; /* fd for answers for Pluto */
253 # define MAX_WORKERS 10 /* number of in-flight queries */
257 int qfd; /* query pipe's file descriptor */
258 int afd; /* answer pipe's file descriptor */
261 void *continuation; /* of outstanding request */
264 static struct worker_info wi[MAX_WORKERS];
265 static struct worker_info *wi_roof = wi;
270 struct query_list *next;
271 struct adns_query aq;
274 static struct query_list *oldest_query = NULL;
275 static struct query_list *newest_query; /* undefined when oldest == NULL */
276 static struct query_list *free_queries = NULL;
285 if (pipe(qfds) != 0 || pipe(afds) != 0)
287 syslog(LOG_ERR, "pipe(2) failed: %s", strerror(errno));
291 wi_roof->qfd = qfds[1]; /* write end of query pipe */
292 wi_roof->afd = afds[0]; /* read end of answer pipe */
297 /* fork failed: ignore if at least one worker exists */
300 syslog(LOG_ERR, "for(2) error creating first worker: %s", strerror(errno));
312 struct worker_info *w;
316 /* close all master pipes, including ours */
317 for (w = wi; w <= wi_roof; w++)
322 exit(worker(qfds[0], afds[1]));
327 struct worker_info *w = wi_roof++;
338 send_eof(struct worker_info *w)
350 p = waitpid(w->pid, &status, 0);
351 /* ignore result -- what could we do with it? */
355 forward_query(struct worker_info *w)
357 struct query_list *q = oldest_query;
366 enum helper_exit_status r
367 = write_pipe(w->qfd, (const unsigned char *) &q->aq);
369 if (r != HES_CONTINUE)
372 w->continuation = q->aq.continuation;
375 oldest_query = q->next;
376 q->next = free_queries;
384 struct query_list *q = free_queries;
385 enum helper_exit_status r;
387 /* find an unused queue entry */
390 q = malloc(sizeof(*q));
393 syslog(LOG_ERR, "malloc(3) failed");
399 free_queries = q->next;
402 r = read_pipe(pluto_qfd, (unsigned char *)&q->aq
403 , sizeof(q->aq), sizeof(q->aq));
407 /* EOF: we're done, except for unanswered queries */
408 struct worker_info *w;
410 eof_from_pluto = TRUE;
411 q->next = free_queries;
414 /* Send bye-bye to unbusy processes.
415 * Note that if there are queued queries, there won't be
416 * any non-busy workers.
418 for (w = wi; w != wi_roof; w++)
422 else if (r != HES_CONTINUE)
426 else if (q->aq.qmagic != ADNS_Q_MAGIC)
428 syslog(LOG_ERR, "error in query from Pluto: bad magic");
433 struct worker_info *w;
439 if (oldest_query == NULL)
442 newest_query->next = q;
445 /* See if any worker available */
451 if (w == wi + MAX_WORKERS)
452 break; /* no more to be created */
455 break; /* cannot create one at this time */
459 /* assign first to free worker */
469 answer(struct worker_info *w)
471 struct adns_answer a;
472 enum helper_exit_status r = read_pipe(w->afd, (unsigned char *)&a
473 , offsetof(struct adns_answer, ans), sizeof(a));
478 syslog(LOG_ERR, "unexpected EOF from worker");
479 exit(HES_IO_ERROR_IN);
481 else if (r != HES_CONTINUE)
485 else if (a.amagic != ADNS_A_MAGIC)
487 syslog(LOG_ERR, "Input from worker error: bad magic");
490 else if (a.continuation != w->continuation)
492 /* answer doesn't match query */
493 syslog(LOG_ERR, "Input from worker error: continuation mismatch");
498 /* pass the answer on to Pluto */
499 enum helper_exit_status r
500 = write_pipe(pluto_afd, (const unsigned char *) &a);
502 if (r != HES_CONTINUE)
509 /* assumption: input limited; accept blocking on output */
516 int maxfd = pluto_qfd; /* approximate lower bound */
518 struct worker_info *w;
523 FD_SET(pluto_qfd, &readfds);
526 for (w = wi; w != wi_roof; w++)
530 FD_SET(w->afd, &readfds);
538 return HES_OK; /* done! */
541 ndes = select(maxfd + 1, &readfds, NULL, NULL, NULL);
542 } while (ndes == -1 && errno == EINTR);
545 syslog(LOG_ERR, "select(2) error: %s", strerror(errno));
546 exit(HES_IO_ERROR_SELECT);
550 if (FD_ISSET(pluto_qfd, &readfds))
555 for (w = wi; ndes > 0 && w != wi_roof; w++)
557 if (w->busy && FD_ISSET(w->afd, &readfds))
567 /* Not to be invoked by strangers -- user hostile.
568 * Mandatory args: query-fd answer-fd
569 * Optional arg: -d, signifying "debug".
573 adns_usage(const char *fmt, const char *arg)
575 const char **sp = ipsec_copyright_notice();
577 fprintf(stderr, "INTERNAL TO PLUTO: DO NOT EXECUTE\n");
579 fprintf(stderr, fmt, arg);
580 fprintf(stderr, "\n%s\n", ipsec_version_string());
582 for (; *sp != NULL; sp++)
583 fprintf(stderr, "%s\n", *sp);
585 syslog(LOG_ERR, fmt, arg);
586 exit(HES_INVOCATION);
590 main(int argc UNUSED, char **argv)
598 for (a = argv + 1; *a != NULL; a++)
602 if (strcmp(*a, "-d") == 0)
605 adns_usage("unrecognized flag: %s", *a);
609 fd = strtoul(*a, &endptr, 0);
611 if (*endptr != 0 || fd <= 2 || (unsigned long)(int)fd != fd)
612 adns_usage("unacceptable file descriptor arg: %s", *a);
614 if (pluto_qfd == NULL_FD)
616 else if (pluto_afd == NULL_FD)
619 adns_usage("too many file descriptor arguments", NULL);
623 if (pluto_afd == NULL_FD)
624 adns_usage("too few file descriptor arguments", NULL);