/* 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.
 */

#ifndef USE_UNBOUND     /* 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 <resolv.h>
#include <netdb.h>      /* ??? for h_errno */

#include <libreswan.h>

/* GCC magic! */
#ifdef GCC_LINT
# define UNUSED __attribute__ ((unused))
#else
# define UNUSED /* ignore */
#endif

#include "constants.h"
#include "adns.h"       /* needs <resolv.h> */
#include "lsw_select.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.
 */

#undef OLD_RESOLVER

#if (__RES) <= 19960801
# define OLD_RESOLVER   1
#endif

#ifdef __UCLIBC__
#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()

#define statp  ((struct __res_state *)(&_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, ns_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);

		if (((q.debugging & IMPAIR_DELAY_ADNS_KEY_ANSWER) && q.type ==
		     ns_t_key) ||
		    ((q.debugging & IMPAIR_DELAY_ADNS_TXT_ANSWER) && q.type ==
		     ns_t_txt))
			sleep(30); /* delay the answer */

		/* 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 ****************/

static 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? */
	if (p == -1)
		syslog(LOG_ERR, "waitpid(2) failed, ignored");
}

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 rs =
			write_pipe(PLUTO_AFD, (const unsigned char *) &a);

		if (rs != HES_CONTINUE)
			exit(rs);
		w->busy = FALSE;
		forward_query(w);
	}
}

/* assumption: input limited; accept blocking on output */
static int master(void)
{
	for (;; ) {
		lsw_fd_set readfds;
		int maxfd = PLUTO_QFD;  /* approximate lower bound */
		int ndes = 0;
		struct worker_info *w;

		LSW_FD_ZERO(&readfds);
		if (!eof_from_pluto) {
			LSW_FD_SET(PLUTO_QFD, &readfds);
			ndes++;
		}
		for (w = wi; w != wi_roof; w++) {
			if (w->busy) {
				LSW_FD_SET(w->afd, &readfds);
				ndes++;
				if (maxfd < w->afd)
					maxfd = w->afd;
			}
		}

		if (ndes == 0)
			return HES_OK; /* done! */

		do
			ndes =
				lsw_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 (LSW_FD_ISSET(PLUTO_QFD, &readfds)) {
				query();
				ndes--;
			}
			for (w = wi; ndes > 0 && w != wi_roof; w++) {
				if (w->busy &&
				    LSW_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 unexpected_arg_exit(const char *arg)
{
	fprintf(stderr, "%s INTERNAL TO PLUTO: DO NOT EXECUTE\n", name);

	fprintf(stderr, "unexpected argument \"%s\"\n", arg);
	fprintf(stderr, "%s\n", ipsec_version_string());

	syslog(LOG_ERR, "%s: unexpected argument \"%s\"", name, 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 {
			unexpected_arg_exit(argv[i]);
			/*NOTREACHED*/
		}
	}

	return master();
}

#endif /* !USE_UNBOUND */
