diff dcclib/helper.c @ 0:c7f6b056b673

First import of vendor version
author Peter Gervai <grin@grin.hu>
date Tue, 10 Mar 2009 13:49:58 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dcclib/helper.c	Tue Mar 10 13:49:58 2009 +0100
@@ -0,0 +1,935 @@
+/* Distributed Checksum Clearinghouse
+ *
+ * helper processes for DNS blacklists
+ *
+ * Copyright (c) 2008 by Rhyolite Software, LLC
+ *
+ * This agreement is not applicable to any entity which sells anti-spam
+ * solutions to others or provides an anti-spam solution as part of a
+ * security solution sold to other entities, or to a private network
+ * which employs the DCC or uses data provided by operation of the DCC
+ * but does not provide corresponding data to other users.
+ *
+ * Permission to use, copy, modify, and distribute this software without
+ * changes for any purpose with or without fee is hereby granted, provided
+ * that the above copyright notice and this permission notice appear in all
+ * copies and any distributed versions or copies are either unchanged
+ * or not called anything similar to "DCC" or "Distributed Checksum
+ * Clearinghouse".
+ *
+ * Parties not eligible to receive a license under this agreement can
+ * obtain a commercial license to use DCC by contacting Rhyolite Software
+ * at sales@rhyolite.com.
+ *
+ * A commercial license would be for Distributed Checksum and Reputation
+ * Clearinghouse software.  That software includes additional features.  This
+ * free license for Distributed ChecksumClearinghouse Software does not in any
+ * way grant permision to use Distributed Checksum and Reputation Clearinghouse
+ * software
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC
+ * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
+ * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ *
+ * Rhyolite Software DCC 1.3.103-1.42 $Revision$
+ */
+
+#include "helper.h"
+#include "dcc_heap_debug.h"
+#include <signal.h>
+#include <sys/wait.h>
+
+
+#ifdef HAVE_HELPERS
+
+#define HELPER_MAX_FAILURES 5		/* shutdown & restart these total */
+
+#define MAX_SLOW	    2		/* 50% excess for slow DNS resolver */
+
+
+/* add to the argv list for the helper processes */
+void
+helper_save_arg(const char *flag, const char *value)
+{
+	char const **new_arg;
+	int i;
+
+	if (helper.free_args <= 1) {
+		/* reserve space for the argv[0] and the null terminator */
+		helper.free_args += 5;
+		i = (helper.argc + 2*helper.free_args) * sizeof(char *);
+		new_arg = dcc_malloc(i);
+		memset(new_arg, 0, i);
+		if (helper.argv) {
+			for (i = 0; i < helper.argc; ++i)
+				new_arg[i] = helper.argv[i];
+			dcc_free(helper.argv);
+		} else {
+			++helper.argc;
+		}
+		helper.argv = new_arg;
+	}
+
+	helper.argv[helper.argc] = flag;
+	helper.argv[++helper.argc] = value;
+	helper.argv[++helper.argc] = 0;
+	--helper.free_args;
+}
+
+
+
+/* initialize things for the parent or one of the helping children */
+void
+helper_init(int max_helpers)
+{
+	helper.sn = getpid() + time(0);
+
+	helper.pipe_write = -1;
+	helper.pipe_read = -1;
+	helper.soc = INVALID_SOCKET;
+	helper.req_len = sizeof(DNSBL_REQ);
+
+	if (max_helpers) {
+		/* max_helpers=0 if we are starting a child,
+		 * but != 0 in the parent after parsing all args
+		 *
+		 * default to dccifd or dccm max_work */
+		if (!helper.max_helpers)
+			helper.max_helpers = max_helpers;
+
+		helper.pids = dcc_malloc(sizeof(pid_t) * helper.max_helpers);
+		memset(helper.pids, 0, sizeof(pid_t) * helper.max_helpers);
+	}
+
+	have_helpers = helper_lock_init();
+}
+
+
+
+/* collect zombies of helpers that died from boredom or otherwise */
+void
+reap_helpers(u_char locked)
+{
+	int status;
+	pid_t pid;
+	int pid_inx;
+
+	if (!locked)
+		helper_lock();
+
+	for (;;) {
+wait_again:;
+		pid = waitpid(0, &status, WNOHANG);
+		if (0 >= pid) {
+			if (!locked)
+				helper_unlock();
+			return;
+		}
+
+		for (pid_inx = 0; ; ++pid_inx) {
+			if (pid_inx >= helper.max_helpers) {
+				/* not an acknowledged child */
+				if (helper.debug >= 1)
+					dcc_trace_msg("not a DNSBL"
+						      " helper process reaped");
+				goto wait_again;
+			}
+
+			if (helper.pids[pid_inx] == pid)
+				break;
+		}
+
+		helper.pids[pid_inx] = 0;
+
+		/* races with dying helpers can confuse us */
+		if (--helper.total_helpers < 0) {
+			if (helper.debug)
+				dcc_trace_msg("DNSBL total helpers=%d",
+					      helper.total_helpers);
+			helper.total_helpers = 0;
+		}
+
+		if (helper.slow_helpers > helper.total_helpers/MAX_SLOW)
+			helper.slow_helpers = helper.total_helpers/MAX_SLOW;
+
+		if (--helper.idle_helpers < 0) {
+			/* We cannot be certain that a helper that quit was
+			 * idle, but it should have been. */
+			if (helper.debug)
+				dcc_trace_msg("DNSBL idle helpers=%d",
+					      helper.idle_helpers);
+			helper.idle_helpers = 0;
+		} else if (helper.idle_helpers > helper.total_helpers) {
+			/* The limit on the total_helpers can let us
+			 * drive idle_helpers<0
+			 * which can make the previous fix wrong */
+			if (helper.debug)
+				dcc_trace_msg("DNSBL idle helpers=%d"
+					      "  total helpers=%d",
+					      helper.idle_helpers,
+					      helper.total_helpers);
+			helper.idle_helpers = helper.total_helpers;
+		}
+
+		/* this is called from the "totals" thread
+		 * and so cannot use thr_error_msg() */
+
+#if defined(WIFEXITED) && defined(WEXITSTATUS)
+		if (WIFEXITED(status)) {
+			if (WEXITSTATUS(status) == 0) {
+				if (helper.debug > 1)
+					dcc_trace_msg("DNSBL helper %d quit,"
+						      " leaving %d helpers,"
+						      " %d idle",
+						      (int)pid,
+						      helper.total_helpers,
+						      helper.idle_helpers);
+				continue;
+			}
+			dcc_error_msg("DNSBL helper %d quit with exit(%d),"
+				      " leaving %d helpers, %d idle",
+				      (int)pid, WEXITSTATUS(status),
+				      helper.total_helpers,
+				      helper.idle_helpers);
+			continue;
+		}
+#endif
+#if defined(WTERMSIG) && defined(WIFSIGNALED)
+		if (WIFSIGNALED(status)) {
+			dcc_error_msg("DNSBL helper %d quit with signal %d,"
+				      " leaving %d helpers, %d idle",
+				      (int)pid, WTERMSIG(status),
+				      helper.total_helpers,
+				      helper.idle_helpers);
+			continue;
+		}
+#endif
+		dcc_error_msg("DNSBL helper %d quit with %d,"
+			      " leaving %d helpers, %d idle",
+			      (int)pid, status,
+			      helper.total_helpers, helper.idle_helpers);
+	}
+}
+
+
+
+/* must be called with the counter mutex */
+static void
+terminate_helpers(void)
+{
+	if (helper.pipe_write != -1) {
+		close(helper.pipe_write);
+		helper.pipe_write = -1;
+	}
+	if (helper.pipe_read != -1) {
+		close(helper.pipe_read);
+		helper.pipe_read = -1;
+	}
+	if (helper.soc != INVALID_SOCKET) {
+		closesocket(helper.soc);
+		helper.soc = INVALID_SOCKET;
+	}
+
+	reap_helpers(1);
+	++helper.gen;
+	memset(helper.pids, 0, sizeof(pid_t) * helper.max_helpers);
+	helper.total_helpers = 0;
+	helper.idle_helpers = 0;
+	helper.slow_helpers = 0;
+
+	helper.failures = 0;
+}
+
+
+
+static void
+help_finish(u_int gen, u_char ok, u_char counted, u_char locked)
+{
+	if (!locked)
+		helper_lock();
+
+	/* forget it if the children have been restarted */
+	if (gen == helper.gen) {
+		if (counted)
+			++helper.idle_helpers;
+
+		if (!ok) {
+			if (++helper.failures >= HELPER_MAX_FAILURES) {
+				if (helper.debug)
+					dcc_trace_msg("restart DNSBL helpers");
+				terminate_helpers();
+			} else {
+				reap_helpers(1);
+			}
+		}
+	}
+
+	if (!locked)
+		helper_unlock();
+}
+
+
+
+static u_char
+helper_soc_open(DCC_EMSG emsg, DCC_CLNT_CTXT *ctxt)
+{
+	u_char result;
+
+	if (ctxt->soc != INVALID_SOCKET)
+		return 1;
+
+	dcc_ctxts_lock();
+	if (!dcc_info_lock(emsg)) {
+		dcc_ctxts_unlock();
+		return 0;
+	}
+	result = dcc_clnt_soc_reopen(emsg, ctxt);
+	if (!dcc_info_unlock(emsg))
+		result = 0;
+	dcc_ctxts_unlock();
+	return result;
+}
+
+
+
+static u_char
+helper_soc_connect(DCC_EMSG emsg, DCC_CLNT_CTXT *ctxt, const DCC_SOCKU *su)
+{
+	u_char result;
+
+#ifdef linux
+	/* since at least some versions of Linux refuse to reconnect,
+	 * just disconnect */
+	su = 0;
+#endif
+
+	dcc_ctxts_lock();
+	if (!dcc_info_lock(emsg)) {
+		dcc_ctxts_unlock();
+		return 0;
+	}
+	result = dcc_clnt_connect(emsg, ctxt, su);
+	if (!dcc_info_unlock(emsg))
+		result = 0;
+	dcc_ctxts_unlock();
+
+	return result;
+}
+
+
+
+/* open the helper socket used to send requests to helper processes
+ *	must be called with the counter mutex */
+static u_char
+open_helper(DCC_EMSG emsg, DCC_CLNT_CTXT *ctxt, void *lp)
+{
+	struct in6_addr ipv6_loopback;
+	struct in_addr ipv4_loopback;
+	DCC_SOCKLEN_T soc_len;
+	static int rcvbuf = 32*1024;
+	static u_char rcvbuf_set = 0;
+
+	/* We want to create a new socket with the same choice of
+	 * IPv4 or IPv6 as the DCC client context's socket.  To do that,
+	 * we must ensure that the context's socket is healthy. */
+	if (!helper_soc_open(emsg, ctxt)) {
+		thr_error_msg(lp, "DNSBL helper soc_open(): %s", emsg);
+		return 0;
+	}
+
+	if (dcc_clnt_info->src.family != AF_UNSPEC) {
+		/*  bind to the send-from address if available */
+		dcc_mk_su(&helper.su, dcc_clnt_info->src.family,
+			  &dcc_clnt_info->src.u, 0);
+	} else if (ctxt->flags & DCC_CTXT_USING_IPV4) {
+		ipv4_loopback.s_addr = ntohl(0x7f000001);
+		dcc_mk_su(&helper.su, AF_INET, &ipv4_loopback, 0);
+	} else {
+		memset(&ipv6_loopback, 0, sizeof(ipv6_loopback));
+		ipv6_loopback.s6_addr32[3] = ntohl(1);
+		dcc_mk_su(&helper.su, AF_INET6, &ipv6_loopback, 0);
+	}
+	clean_stdio();
+	if (0 >= dcc_udp_bind(emsg, &helper.soc, &helper.su, 0)) {
+		thr_error_msg(lp, "DNSBL helper bind(%s): %s",
+			      dcc_su2str_err(&helper.su), emsg);
+		terminate_helpers();
+		return 0;
+	}
+	soc_len = sizeof(helper.su);
+	if (0 > getsockname(helper.soc, &helper.su.sa, &soc_len)) {
+		thr_error_msg(lp, "DNSBL helper getsockname(%d, %s): %s",
+			      helper.soc, dcc_su2str_err(&helper.su),
+			      ERROR_STR());
+		terminate_helpers();
+		return 0;
+	}
+	for (;;) {
+		if (!setsockopt(helper.soc, SOL_SOCKET, SO_RCVBUF,
+				&rcvbuf, sizeof(rcvbuf)))
+			break;
+		if (rcvbuf_set || rcvbuf <= 4096) {
+			thr_error_msg(lp,
+				      "DNSBL setsockopt(%s,SO_RCVBUF=%d): %s",
+				      dcc_su2str_err(&helper.su),
+				      rcvbuf, ERROR_STR());
+			break;
+		}
+		rcvbuf -= 4096;
+	}
+	rcvbuf_set = 1;
+	return 1;
+}
+
+
+
+/* Create the pipe used to awaken and terminate the helper processes
+ *	must be called with the counter mutex */
+static u_char
+ready_helpers(void *lp)
+{
+	int fds[2];
+
+	if (helper.pipe_write >= 0
+	    && helper.pipe_read >= 0)
+		return 1;
+
+	terminate_helpers();
+
+	/* give the helper child processes an FD that will go dead
+	 * if the parent dies or otherwise closes the other end */
+	clean_stdio();
+	if (0 > pipe(fds)) {
+		thr_error_msg(lp, "DNSBL parent helper pipe(): %s",
+			      ERROR_STR());
+		terminate_helpers();
+		return 0;
+	}
+	helper.pipe_read = fds[0];
+	helper.pipe_write = fds[1];
+	if (0 > fcntl(helper.pipe_write, F_SETFD, FD_CLOEXEC)) {
+		thr_error_msg(lp, "DNSBL helper fcntl(FD_CLOEXEC): %s",
+			      ERROR_STR());
+		terminate_helpers();
+		return 0;
+	}
+
+	return 1;
+}
+
+
+
+/* Start a new helper process.
+ *	The counter mutex must be locked */
+static u_char
+new_helper(DCC_EMSG emsg, DCC_CLNT_CTXT *ctxt, void *lp)
+{
+	pid_t pid;
+	char arg_buf[sizeof("set:")+sizeof(HELPER_PAT)+8+8+8];
+	char trace_buf[200];
+	char *bufp;
+	SOCKET soc;
+	int pid_inx, buf_len, i, j;
+
+	/* open the socket if necessary */
+	if (helper.soc == INVALID_SOCKET) {
+		if (!ready_helpers(lp))
+			return 0;
+		if (!open_helper(emsg, ctxt, lp))
+			return 0;
+	}
+
+	reap_helpers(1);
+	for (pid_inx = 0; ; ++pid_inx) {
+		if (pid_inx >= helper.max_helpers)
+			dcc_logbad(EX_SOFTWARE, "no free DNSBL pids[] entry");
+		if (helper.pids[pid_inx] == 0)
+			break;
+	}
+
+	fflush(stdout);
+	fflush(stderr);
+	pid = fork();
+	if (pid < 0) {
+		thr_error_msg(lp, "DNSBL helper fork(): %s", ERROR_STR());
+		return 0;
+	}
+
+	if (pid != 0) {
+		/* this is the parent */
+		helper.pids[pid_inx] = pid;
+		++helper.total_helpers;
+		return 1;
+	}
+
+	dcc_rel_priv();			/* no fun or games */
+	clean_stdio();
+
+	/* reset FD_CLOEXEC without affecting parent */
+	soc = dup(helper.soc);
+	if (soc == INVALID_SOCKET)
+		dcc_logbad(EX_UNAVAILABLE, "DNSBL helper soc dup(%d): %s",
+			   helper.soc, ERROR_STR());
+
+	snprintf(arg_buf, sizeof(arg_buf), "set:"HELPER_PAT,
+		 soc, helper.pipe_read, helper.total_helpers);
+	helper_save_arg("-B", arg_buf);
+	helper.argv[0] = dnsbl_progpath;
+	buf_len = sizeof(trace_buf);
+	bufp = trace_buf;
+	for (i = 0; i < helper.argc && buf_len > 2; ++i) {
+		j = snprintf(bufp, buf_len, "%s ", helper.argv[i]);
+		buf_len -= j;
+		bufp += j;
+	}
+	if (helper.debug >= 4)
+		dcc_trace_msg("DNSBL helper exec %s", trace_buf);
+
+	execv(helper.argv[0], (char * const *)helper.argv);
+	/* This process should continue at helper_child() */
+
+	dcc_logbad(EX_UNAVAILABLE, "exec(%s): %s",
+		   trace_buf, ERROR_STR());
+}
+
+
+
+static void NRATTRIB
+helper_exit(const char *reason)
+{
+	if (helper.debug > 1)
+		dcc_trace_msg("helper process on %s %s",
+			      dcc_su2str_err(&helper.su), reason);
+
+	exit(0);
+}
+
+
+
+static u_char helper_alarm_hit;
+static void
+helper_alarm(int s UATTRIB)
+{
+	helper_alarm_hit = 1;
+}
+
+
+
+/* helper processes start here via fork()/exec() in the parent  */
+void NRATTRIB
+helper_child(SOCKET soc, int fd, int total_helpers)
+{
+	sigset_t sigs;
+	DCC_SOCKLEN_T soc_len;
+	DNSBL_REQ req;
+	int req_len;
+	DNSBL_RESP resp;
+	DCC_SOCKU req_su;
+	DCC_SOCKLEN_T su_len;
+	struct timeval now;
+	u_char wake_buf;
+	int secs, i;
+
+	/* this process inherits via exec() by dccm or dccifd odd signal
+	 * blocking from some pthreads implementations including FreeBSD 5.* */
+	signal(SIGHUP, SIG_IGN);
+	signal(SIGINT, SIG_IGN);
+	signal(SIGTERM, SIG_IGN);
+	sigemptyset(&sigs);
+	sigaddset(&sigs, SIGALRM);
+	sigprocmask(SIG_UNBLOCK, &sigs, 0);
+
+	helper_init(0);
+	if (have_helpers)
+		dcc_logbad(EX_SOFTWARE, "no threads for DNSBL helpers");
+
+	helper.total_helpers = total_helpers;
+
+	helper.pipe_read = fd;
+	helper.soc = soc;
+	soc_len = sizeof(helper.su);
+	if (0 > getsockname(helper.soc, &helper.su.sa, &soc_len))
+		dcc_logbad(EX_IOERR, "DNSBL helper getsockname(%d): %s",
+			   helper.soc, ERROR_STR());
+
+	if (helper.debug > 1)
+		dcc_trace_msg("DNSBL helper process starting on %s",
+			      dcc_su2str_err(&helper.su));
+
+	for (;;) {
+		/* Use read() and SIGALRM to watch for a wake-up byte
+		 * from the parent, the parent ending and closing the pipe,
+		 * or enough idle time to require our retirement.  This
+		 * tactic awakens a single child for each wake-up call
+		 * from the parent.  Using select() or poll() on the main
+		 * socket awakens a thundering herd of children */
+		secs = HELPER_IDLE_STOP_SECS+1;
+		if (helper.total_helpers > 0)
+			secs /= helper.total_helpers+1;
+		if (secs < 5)
+			secs = 5;
+		signal(SIGALRM, helper_alarm);
+#ifdef HAVE_SIGINTERRUPT
+		siginterrupt(SIGALRM, 1);
+#endif
+		helper_alarm_hit = 0;
+		alarm(secs);
+		for (;;) {
+			su_len = sizeof(req_su);
+			req_len = recvfrom(helper.soc, &req, ISZ(req), 0,
+					   &req_su.sa, &su_len);
+
+			/* sleep until awakened if no work is ready */
+			if (req_len <= 0) {
+				if (req_len == 0)
+					dcc_logbad(EX_IOERR,
+						   "DNSBL helper recvfrom()=0");
+				if (!DCC_BLOCK_ERROR())
+					dcc_logbad(EX_IOERR,
+						   "DNSBL helper recvfrom():"
+						   " %s",
+						   ERROR_STR());
+				if (helper_alarm_hit)
+					helper_exit("idle helper exit");
+
+				i = read(helper.pipe_read, &wake_buf, 1);
+
+				/* The other end of the pipe can be marked
+				 * non-blocking by some pthreads
+				 * implementations.  That makes read() on this
+				 * end fail with EAGAIN.  When that happens,
+				 * fall back on select() or poll().
+				 * Even on such pthread implementations,
+				 * it rarely happens. */
+				if (i < 0 && DCC_BLOCK_ERROR()) {
+					DCC_EMSG emsg;
+					i = dcc_select_poll(emsg,
+							helper.pipe_read, 0,
+							-1);
+					if (i < 0)
+					    dcc_logbad(EX_IOERR,
+						       "dnsbl HELPER select():"
+						       " %s", emsg);
+				}
+
+				/* loof for work after a wake-up call */
+				if (i > 0)
+					continue;
+
+				if (helper_alarm_hit)
+					continue;
+				if (i == 0)
+					helper_exit("shutdown");
+				if (i < 0) {
+					dcc_logbad(EX_OSERR,
+						   "DNSBL read(terminate): %s",
+						   ERROR_STR());
+				}
+			}
+			if (req_len != helper.req_len) {
+				if (helper.debug)
+					dcc_trace_msg("DNSBL helper"
+						      " recvfrom(parent %s)=%d"
+						      " instead of %d",
+						      dcc_su2str_err(&req_su),
+						      req_len,
+						      helper.req_len);
+				continue;
+			}
+
+			/* we might get stray packets because we cannot
+			 * connect to a single port */
+			if (!DCC_SU_EQ(&helper.su, &helper.su)) {
+				if (helper.debug)
+					dcc_trace_msg("DNSBL helper"
+						    " request from"
+						    " %s instead of %s",
+						    dcc_su2str_err(&req_su),
+						    dcc_su2str_err(&helper.su));
+				continue;
+			}
+
+			if (req.hdr.magic != HELPER_MAGIC_REQ
+			    || req.hdr.version != HELPER_VERSION) {
+				if (helper.debug)
+					dcc_trace_msg("DNSBL helper"
+						      " recvfrom(parent %s)"
+						      " magic=%#08x",
+						      dcc_su2str_err(&req_su),
+						      req.hdr.magic);
+				continue;
+			}
+			break;
+		}
+		gettimeofday(&now, 0);
+		alarm(0);
+
+		/* do not bother working if it is already too late to answer,
+		 * perhaps because a previous helper died */
+		if (tv_diff2us(&now, &req.hdr.start) >= req.hdr.avail_us) {
+			if (helper.debug > 1)
+				dcc_trace_msg("%s DNSBL helper"
+					      " already too late to answer",
+					      req.hdr.id);
+			continue;
+		}
+
+		memset(&resp, 0, sizeof(resp));
+		resp.hdr.magic = HELPER_MAGIC_RESP;
+		resp.hdr.version = HELPER_VERSION;
+		resp.hdr.sn = req.hdr.sn;
+
+		/* do the work and send an answer if we have one */
+		if (!dnsbl_work(&req, &resp))
+			continue;
+
+		/* do not answer if it is too late */
+		gettimeofday(&now, 0);
+		if (tv_diff2us(&now, &req.hdr.start) > (req.hdr.avail_us
+							+ DCC_US/2)) {
+			if (helper.debug > 1)
+				dcc_trace_msg("%s DNSBL helper"
+					      " too late to answer",
+					      req.hdr.id);
+			continue;
+		}
+
+		i = sendto(helper.soc, &resp, sizeof(resp), 0,
+			   &req_su.sa, DCC_SU_LEN(&req_su));
+		if (i != sizeof(resp)) {
+			if (i < 0)
+				dcc_error_msg("%s helper sendto(%s): %s",
+					      req.hdr.id,
+					      dcc_su2str_err(&req_su),
+					      ERROR_STR());
+			else
+				dcc_error_msg("%s helper sendto(%s)=%d",
+					      req.hdr.id,
+					      dcc_su2str_err(&req_su), i);
+		}
+	}
+}
+
+
+
+/* ask a helper process to do some filtering */
+u_char					/* 1=got an answer */
+ask_helper(DCC_CLNT_CTXT *ctxt, void *log_ctxt,
+	   time_t avail_us,		/* spend at most this much time */
+	   HELPER_REQ_HDR *req,		/* request sent to helper */
+	   int req_len,
+	   HELPER_RESP_HDR *resp,	/* put answer here */
+	   int resp_len)
+{
+	DCC_EMSG emsg;
+	DCC_SOCKU send_su;
+	DCC_SOCKLEN_T su_len;
+	DCC_SOCKU recv_su;
+	char sustr[DCC_SU2STR_SIZE];
+	u_char counted;
+	u_int gen;
+	struct timeval now;
+	time_t us;
+	int i;
+
+	emsg[0] = '\0';
+
+	/* keep the lock until we have sent our request and wake-up call
+	 * to ensure that some other thread does not shut down all of
+	 * the helpers. */
+	helper_lock();
+	gettimeofday(&now, 0);
+
+	/* If it has been a long time since we used a helper, then the last
+	 * of them might be about to die of boredom.  Fix that race by
+	 * restarting all of them.
+	 * Most dying helpers should be reaped by the totals timer thread. */
+	if (helper.idle_helpers > 0
+	    && DCC_IS_TIME(now.tv_sec, helper.idle_restart,
+			   HELPER_IDLE_RESTART)) {
+		reap_helpers(1);
+		if (helper.idle_helpers > 0)
+			terminate_helpers();
+		gettimeofday(&now, 0);
+	}
+	helper.idle_restart = now.tv_sec + HELPER_IDLE_RESTART;
+
+	if (helper.idle_helpers - helper.slow_helpers > 0) {
+		/* avoid taking the last idle helper because there are
+		 * usually fewer truly idle helpers than we think because
+		 * we don't always wait for them to finish */
+		if (helper.idle_helpers > 2
+		    || helper.total_helpers >= helper.max_helpers
+		    || !new_helper(emsg, ctxt, log_ctxt))
+			--helper.idle_helpers;
+		counted = 1;
+	} else if (helper.total_helpers >= helper.max_helpers) {
+		if (helper.debug > 0)
+			thr_trace_msg(log_ctxt, "%s DNSBL %d idle, %d slow, and"
+				      " %d total DNSBL helpers", req->id,
+				      helper.idle_helpers, helper.slow_helpers,
+				      helper.total_helpers);
+		counted = 0;
+	} else {
+		if (!new_helper(emsg, ctxt, log_ctxt)) {
+			helper_unlock();
+			return 0;
+		}
+		counted = 1;
+	}
+
+	 /* The resolution of the BIND timeout limits is seconds, so even on
+	  * systems where the timeout limits work, the helper might delay
+	  * a second or two.  To keep the count of idle helpers as accurate
+	  * as possible, always wait at least 1 second for an answer
+	  * and 2 seconds for an answer to reach the parent. */
+	req->avail_us = avail_us;
+	avail_us += DCC_US;
+	req->start = now;
+	req->magic = HELPER_MAGIC_REQ;
+	req->version = HELPER_VERSION;
+
+	req->sn = ++helper.sn;
+	gen = helper.gen;
+
+	/* snapshot the address in case another thread restarts the helpers */
+	send_su = helper.su;
+
+	/* Use sendto() if the socket is not already conencted.
+	 * If it is already connected, then on many systems other than Linux,
+	 * it is possible and presumably cheap to reconnected it, so do so. */
+	if (!helper_soc_open(emsg, ctxt)) {
+		thr_trace_msg(log_ctxt, "DNSBL reopen(): %s", emsg);
+		help_finish(gen, 0, counted, 1);
+		helper_unlock();
+		return 0;
+	}
+	if (ctxt->conn_su.sa.sa_family != AF_UNSPEC
+	    && memcmp(&ctxt->conn_su, &send_su, sizeof(ctxt->conn_su))
+	    && !helper_soc_connect(emsg, ctxt, &send_su)) {
+		thr_trace_msg(log_ctxt, "DNSBL soc_connect(): %s", emsg);
+		help_finish(gen, 0, counted, 1);
+		helper_unlock();
+		return 0;
+	}
+	if (ctxt->conn_su.sa.sa_family == AF_UNSPEC) {
+		i = sendto(ctxt->soc, req, req_len, 0,
+			   &send_su.sa, DCC_SU_LEN(&send_su));
+	} else {
+		i = send(ctxt->soc, req, req_len, 0);
+	}
+	if (i != req_len) {
+		if (i < 0)
+			thr_trace_msg(log_ctxt, "%s DNSBL sendto(%s): %s",
+				      req->id, dcc_su2str(sustr, sizeof(sustr),
+							&send_su),
+				      ERROR_STR());
+		else
+			thr_trace_msg(log_ctxt, "%s DNSBL sendto(%s)=%d",
+				      req->id, dcc_su2str(sustr, sizeof(sustr),
+							&send_su),
+				      i);
+		help_finish(gen, 0, counted, 1);
+		helper_unlock();
+		return 0;
+	}
+
+	/* awaken a helper */
+	i = write(helper.pipe_write, "x", 1);
+	if (i != 1) {
+		if (i < 0)
+			thr_trace_msg(log_ctxt,
+				      "%s DNSBL write(pipe_write=%d): %s",
+				      req->id, helper.pipe_write, ERROR_STR());
+		else
+			thr_trace_msg(log_ctxt,
+				      "%s DNSBL write(pipe_write)=%d",
+				      req->id, i);
+		help_finish(gen, 0, counted, 1);
+		helper_unlock();
+		return 0;
+	}
+	helper_unlock();
+
+	for (;;) {
+		us = avail_us - tv_diff2us(&now, &req->start);
+		if (us < 0)
+			us = 0;
+		i = dcc_select_poll(0, ctxt->soc, 1, us);
+		if (i < 0) {
+			thr_error_msg(log_ctxt, "%s DNSBL select_poll: %s",
+				      req->id, ERROR_STR());
+			help_finish(gen, 0, counted, 0);
+			return 0;
+		}
+		gettimeofday(&now, 0);
+
+		if (i == 0) {
+			if (helper.debug)
+				thr_trace_msg(log_ctxt,
+					      "%s DNSBL no helper answer after"
+					      " %1.f sec", req->id,
+					      tv_diff2us(&now, &req->start)
+					      / (DCC_US*1.0));
+			helper_lock();
+			if (helper.slow_helpers<=helper.total_helpers/MAX_SLOW)
+				++helper.slow_helpers;
+			help_finish(gen, 0, counted, 1);
+			helper_unlock();
+			return 0;
+		}
+
+
+		su_len = sizeof(recv_su);
+		i = recvfrom(ctxt->soc, resp, resp_len,
+			     0, &recv_su.sa, &su_len);
+		/* because we are using UDP, we might get stray packets */
+		if (i != resp_len) {
+			if (i < 0) {
+				thr_trace_msg(log_ctxt,
+					      "%s DNSBL recvfrom(): %s",
+					      req->id, ERROR_STR());
+				if (DCC_BLOCK_ERROR())
+					continue;
+				help_finish(gen, 0, counted, 0);
+				return 0;
+			}
+			if (helper.debug > 1)
+				thr_trace_msg(log_ctxt,
+					      "%s DNSBL recvfrom(%s)=%d",
+					      req->id,
+					      dcc_su2str_err(&recv_su), i);
+			continue;
+		}
+		if (!DCC_SU_EQ(&send_su, &recv_su)) {
+			if (helper.debug != 0)
+				thr_trace_msg(log_ctxt,
+					      "%s DNSBL recvfrom(%s)"
+					      " instead of %s",
+					      req->id,
+					      dcc_su2str_err(&recv_su),
+					      dcc_su2str_err(&send_su));
+			continue;
+		}
+		if (resp->magic != HELPER_MAGIC_RESP
+		    || resp->version != HELPER_VERSION
+		    || resp->sn != req->sn) {
+			if (helper.debug >1 )
+				thr_trace_msg(log_ctxt,
+					      "%s DNSBL recvfrom(%s)"
+					      " magic=%#08x sn=%d",
+					      req->id, dcc_su2str_err(&recv_su),
+					      resp->magic, resp->sn);
+			continue;
+		}
+
+		help_finish(gen, 1, counted, 0);
+		return 1;
+	}
+}
+#endif /* HAVE_HELPERS */