This commit is contained in:
@@ -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 */
|
||||
Reference in New Issue
Block a user