diff options
Diffstat (limited to 'src/pluto/adns.c')
-rw-r--r-- | src/pluto/adns.c | 615 |
1 files changed, 615 insertions, 0 deletions
diff --git a/src/pluto/adns.c b/src/pluto/adns.c new file mode 100644 index 000000000..c5977d23c --- /dev/null +++ b/src/pluto/adns.c @@ -0,0 +1,615 @@ +/* Pluto Asynchronous DNS Helper Program -- for internal use only! + * Copyright (C) 2002 D. Hugh Redelmeier. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * RCSID $Id: adns.c,v 1.1 2004/03/15 20:35:28 as Exp $ + */ + +#ifndef USE_LWRES /* whole file! */ + +/* This program executes as multiple processes. The Master process + * receives queries (struct adns_query messages) from Pluto and distributes + * them amongst Worker processes. These Worker processes are created + * by the Master whenever a query arrives and no existing Worker is free. + * At most MAX_WORKERS will be created; after that, the Master will queue + * queries until a Worker becomes free. When a Worker has an answer from + * the resolver, it sends the answer as a struct adns_answer message to the + * Master. The Master then forwards the answer to Pluto, noting that + * the Worker is free to accept another query. + * + * The protocol is simple: Pluto sends a sequence of queries and receives + * a sequence of answers. select(2) is used by Pluto and by the Master + * process to decide when to read, but writes are done without checking + * for readiness. Communications is via pipes. Since only one process + * can write to each pipe, messages will not be interleaved. Fixed length + * records are used for simplicity. + * + * Pluto needs a way to indicate to the Master when to shut down + * and the Master needs to indicate this to each worker. EOF on the pipe + * signifies this. + * + * The interfaces between these components are considered private to + * Pluto. This allows us to get away with less checking. This is a + * reason to use pipes instead of TCP/IP. + * + * Although the code uses plain old UNIX processes, it could be modified + * to use threads. That might reduce resource requirements. It would + * preclude running on systems without thread-safe resolvers. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <syslog.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <resolv.h> +#include <netdb.h> /* ??? for h_errno */ + +#include <freeswan.h> + +/* GCC magic! */ +#ifdef GCC_LINT +# define UNUSED __attribute__ ((unused)) +#else +# define UNUSED /* ignore */ +#endif + +#include "constants.h" +#include "adns.h" /* needs <resolv.h> */ + +/* shared by all processes */ + +static const char *name; /* program name, for messages */ + +static bool debug = FALSE; + +/* Read a variable-length record from a pipe (and no more!). + * First bytes must be a size_t containing the length. + * HES_CONTINUE if record read + * HES_OK if EOF + * HES_IO_ERROR_IN if errno tells the tale. + * Others are errors. + */ +static enum helper_exit_status +read_pipe(int fd, unsigned char *stuff, size_t minlen, size_t maxlen) +{ + size_t n = 0; + size_t goal = minlen; + + do { + ssize_t m = read(fd, stuff + n, goal - n); + + if (m == -1) + { + if (errno != EINTR) + { + syslog(LOG_ERR, "Input error on pipe: %s", strerror(errno)); + return HES_IO_ERROR_IN; + } + } + else if (m == 0) + { + return HES_OK; /* treat empty message as EOF */ + } + else + { + n += m; + if (n >= sizeof(size_t)) + { + goal = *(size_t *)(void *)stuff; + if (goal < minlen || maxlen < goal) + { + if (debug) + fprintf(stderr, "%lu : [%lu, %lu]\n" + , (unsigned long)goal + , (unsigned long)minlen, (unsigned long)maxlen); + return HES_BAD_LEN; + } + } + } + } while (n < goal); + + return HES_CONTINUE; +} + +/* Write a variable-length record to a pipe. + * First bytes must be a size_t containing the length. + * HES_CONTINUE if record written + * Others are errors. + */ +static enum helper_exit_status +write_pipe(int fd, const unsigned char *stuff) +{ + size_t len = *(const size_t *)(const void *)stuff; + size_t n = 0; + + do { + ssize_t m = write(fd, stuff + n, len - n); + + if (m == -1) + { + /* error, but ignore and retry if EINTR */ + if (errno != EINTR) + { + syslog(LOG_ERR, "Output error from master: %s", strerror(errno)); + return HES_IO_ERROR_OUT; + } + } + else + { + n += m; + } + } while (n != len); + return HES_CONTINUE; +} + +/**************** worker process ****************/ + +/* The interface in RHL6.x and BIND distribution 8.2.2 are different, + * so we build some of our own :-( + */ + +/* Support deprecated interface to allow for older releases of the resolver. + * Fake new interface! + * See resolver(3) bind distribution (should be in RHL6.1, but isn't). + * __RES was 19960801 in RHL6.2, an old resolver. + */ + +#if (__RES) <= 19960801 +# define OLD_RESOLVER 1 +#endif + +#ifdef OLD_RESOLVER + +# define res_ninit(statp) res_init() +# define res_nquery(statp, dname, class, type, answer, anslen) \ + res_query(dname, class, type, answer, anslen) +# define res_nclose(statp) res_close() + +static struct __res_state *statp = &_res; + +#else /* !OLD_RESOLVER */ + +static struct __res_state my_res_state /* = { 0 } */; +static res_state statp = &my_res_state; + +#endif /* !OLD_RESOLVER */ + +static int +worker(int qfd, int afd) +{ + { + int r = res_ninit(statp); + + if (r != 0) + { + syslog(LOG_ERR, "cannot initialize resolver"); + return HES_RES_INIT; + } +#ifndef OLD_RESOLVER + statp->options |= RES_ROTATE; +#endif + statp->options |= RES_DEBUG; + } + + for (;;) + { + struct adns_query q; + struct adns_answer a; + + enum helper_exit_status r = read_pipe(qfd, (unsigned char *)&q + , sizeof(q), sizeof(q)); + + if (r != HES_CONTINUE) + return r; /* some kind of exit */ + + if (q.qmagic != ADNS_Q_MAGIC) + { + syslog(LOG_ERR, "error in input from master: bad magic"); + return HES_BAD_MAGIC; + } + + a.amagic = ADNS_A_MAGIC; + a.serial = q.serial; + + a.result = res_nquery(statp, q.name_buf, C_IN, q.type, a.ans, sizeof(a.ans)); + a.h_errno_val = h_errno; + + a.len = offsetof(struct adns_answer, ans) + (a.result < 0? 0 : a.result); + +#ifdef DEBUG + if (((q.debugging & IMPAIR_DELAY_ADNS_KEY_ANSWER) && q.type == T_KEY) + || ((q.debugging & IMPAIR_DELAY_ADNS_TXT_ANSWER) && q.type == T_TXT)) + sleep(30); /* delay the answer */ +#endif + + /* write answer, possibly a bit at a time */ + r = write_pipe(afd, (const unsigned char *)&a); + + if (r != HES_CONTINUE) + return r; /* some kind of exit */ + } +} + +/**************** master process ****************/ + +bool eof_from_pluto = FALSE; +#define PLUTO_QFD 0 /* queries come on stdin */ +#define PLUTO_AFD 1 /* answers go out on stdout */ + +#ifndef MAX_WORKERS +# define MAX_WORKERS 10 /* number of in-flight queries */ +#endif + +struct worker_info { + int qfd; /* query pipe's file descriptor */ + int afd; /* answer pipe's file descriptor */ + pid_t pid; + bool busy; + void *continuation; /* of outstanding request */ +}; + +static struct worker_info wi[MAX_WORKERS]; +static struct worker_info *wi_roof = wi; + +/* request FIFO */ + +struct query_list { + struct query_list *next; + struct adns_query aq; +}; + +static struct query_list *oldest_query = NULL; +static struct query_list *newest_query; /* undefined when oldest == NULL */ +static struct query_list *free_queries = NULL; + +static bool +spawn_worker(void) +{ + int qfds[2]; + int afds[2]; + pid_t p; + + if (pipe(qfds) != 0 || pipe(afds) != 0) + { + syslog(LOG_ERR, "pipe(2) failed: %s", strerror(errno)); + exit(HES_PIPE); + } + + wi_roof->qfd = qfds[1]; /* write end of query pipe */ + wi_roof->afd = afds[0]; /* read end of answer pipe */ + + p = fork(); + if (p == -1) + { + /* fork failed: ignore if at least one worker exists */ + if (wi_roof == wi) + { + syslog(LOG_ERR, "fork(2) error creating first worker: %s", strerror(errno)); + exit(HES_FORK); + } + close(qfds[0]); + close(qfds[1]); + close(afds[0]); + close(afds[1]); + return FALSE; + } + else if (p == 0) + { + /* child */ + struct worker_info *w; + + close(PLUTO_QFD); + close(PLUTO_AFD); + /* close all master pipes, including ours */ + for (w = wi; w <= wi_roof; w++) + { + close(w->qfd); + close(w->afd); + } + exit(worker(qfds[0], afds[1])); + } + else + { + /* parent */ + struct worker_info *w = wi_roof++; + + w->pid = p; + w->busy = FALSE; + close(qfds[0]); + close(afds[1]); + return TRUE; + } +} + +static void +send_eof(struct worker_info *w) +{ + pid_t p; + int status; + + close(w->qfd); + w->qfd = NULL_FD; + + close(w->afd); + w->afd = NULL_FD; + + /* reap child */ + p = waitpid(w->pid, &status, 0); + /* ignore result -- what could we do with it? */ +} + +static void +forward_query(struct worker_info *w) +{ + struct query_list *q = oldest_query; + + if (q == NULL) + { + if (eof_from_pluto) + send_eof(w); + } + else + { + enum helper_exit_status r + = write_pipe(w->qfd, (const unsigned char *) &q->aq); + + if (r != HES_CONTINUE) + exit(r); + + w->busy = TRUE; + + oldest_query = q->next; + q->next = free_queries; + free_queries = q; + } +} + +static void +query(void) +{ + struct query_list *q = free_queries; + enum helper_exit_status r; + + /* find an unused queue entry */ + if (q == NULL) + { + q = malloc(sizeof(*q)); + if (q == NULL) + { + syslog(LOG_ERR, "malloc(3) failed"); + exit(HES_MALLOC); + } + } + else + { + free_queries = q->next; + } + + r = read_pipe(PLUTO_QFD, (unsigned char *)&q->aq + , sizeof(q->aq), sizeof(q->aq)); + + if (r == HES_OK) + { + /* EOF: we're done, except for unanswered queries */ + struct worker_info *w; + + eof_from_pluto = TRUE; + q->next = free_queries; + free_queries = q; + + /* Send bye-bye to unbusy processes. + * Note that if there are queued queries, there won't be + * any non-busy workers. + */ + for (w = wi; w != wi_roof; w++) + if (!w->busy) + send_eof(w); + } + else if (r != HES_CONTINUE) + { + exit(r); + } + else if (q->aq.qmagic != ADNS_Q_MAGIC) + { + syslog(LOG_ERR, "error in query from Pluto: bad magic"); + exit(HES_BAD_MAGIC); + } + else + { + struct worker_info *w; + + /* got a query */ + + /* add it to FIFO */ + q->next = NULL; + if (oldest_query == NULL) + oldest_query = q; + else + newest_query->next = q; + newest_query = q; + + /* See if any worker available */ + for (w = wi; ; w++) + { + if (w == wi_roof) + { + /* no free worker */ + if (w == wi + MAX_WORKERS) + break; /* no more to be created */ + /* make a new one */ + if (!spawn_worker()) + break; /* cannot create one at this time */ + } + if (!w->busy) + { + /* assign first to free worker */ + forward_query(w); + break; + } + } + } + return; +} + +static void +answer(struct worker_info *w) +{ + struct adns_answer a; + enum helper_exit_status r = read_pipe(w->afd, (unsigned char *)&a + , offsetof(struct adns_answer, ans), sizeof(a)); + + if (r == HES_OK) + { + /* unexpected EOF */ + syslog(LOG_ERR, "unexpected EOF from worker"); + exit(HES_IO_ERROR_IN); + } + else if (r != HES_CONTINUE) + { + exit(r); + } + else if (a.amagic != ADNS_A_MAGIC) + { + syslog(LOG_ERR, "Input from worker error: bad magic"); + exit(HES_BAD_MAGIC); + } + else if (a.continuation != w->continuation) + { + /* answer doesn't match query */ + syslog(LOG_ERR, "Input from worker error: continuation mismatch"); + exit(HES_SYNC); + } + else + { + /* pass the answer on to Pluto */ + enum helper_exit_status r + = write_pipe(PLUTO_AFD, (const unsigned char *) &a); + + if (r != HES_CONTINUE) + exit(r); + w->busy = FALSE; + forward_query(w); + } +} + +/* assumption: input limited; accept blocking on output */ +static int +master(void) +{ + for (;;) + { + fd_set readfds; + int maxfd = PLUTO_QFD; /* approximate lower bound */ + int ndes = 0; + struct worker_info *w; + + FD_ZERO(&readfds); + if (!eof_from_pluto) + { + FD_SET(PLUTO_QFD, &readfds); + ndes++; + } + for (w = wi; w != wi_roof; w++) + { + if (w->busy) + { + FD_SET(w->afd, &readfds); + ndes++; + if (maxfd < w->afd) + maxfd = w->afd; + } + } + + if (ndes == 0) + return HES_OK; /* done! */ + + do { + ndes = select(maxfd + 1, &readfds, NULL, NULL, NULL); + } while (ndes == -1 && errno == EINTR); + if (ndes == -1) + { + syslog(LOG_ERR, "select(2) error: %s", strerror(errno)); + exit(HES_IO_ERROR_SELECT); + } + else if (ndes > 0) + { + if (FD_ISSET(PLUTO_QFD, &readfds)) + { + query(); + ndes--; + } + for (w = wi; ndes > 0 && w != wi_roof; w++) + { + if (w->busy && FD_ISSET(w->afd, &readfds)) + { + answer(w); + ndes--; + } + } + } + } +} + +/* Not to be invoked by strangers -- user hostile. + * Mandatory args: query-fd answer-fd + * Optional arg: -d, signifying "debug". + */ + +static void +adns_usage(const char *fmt, const char *arg) +{ + const char **sp = ipsec_copyright_notice(); + + fprintf(stderr, "INTERNAL TO PLUTO: DO NOT EXECUTE\n"); + + fprintf(stderr, fmt, arg); + fprintf(stderr, "\n%s\n", ipsec_version_string()); + + for (; *sp != NULL; sp++) + fprintf(stderr, "%s\n", *sp); + + syslog(LOG_ERR, fmt, arg); + exit(HES_INVOCATION); +} + +int +main(int argc UNUSED, char **argv) +{ + int i = 1; + + name = argv[0]; + + while (i < argc) + { + if (streq(argv[i], "-d")) + { + i++; + debug = TRUE; + } + else + { + adns_usage("unexpected argument \"%s\"", argv[i]); + /*NOTREACHED*/ + } + } + + return master(); +} + +#endif /* !USE_LWRES */ |