diff dcclib/dnsbl.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/dnsbl.c	Tue Mar 10 13:49:58 2009 +0100
@@ -0,0 +1,1824 @@
+/* Distributed Checksum Clearinghouse
+ *
+ * reject messages contain URLs that resolve to DNS blacklisted IP addresses
+ *
+ * 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.100 $Revision$
+ */
+
+#include "helper.h"
+#include "dcc_heap_debug.h"
+#ifndef DCC_WIN32
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_RESOLV_H
+#include <resolv.h>
+#endif
+#ifdef HAVE_ARPA_NAMESER_H
+#include <arpa/nameser.h>
+#endif
+
+/* can check MX and NS addresses only with a standard resolver library */
+#define MXNS_DNSBL
+#if !defined(HAVE__RES) || !defined(HAVE_RES_INIT)
+#undef HAVE__RES
+#undef HAVE_RES_INIT
+#undef MXNS_DNSBL
+#endif
+#if !defined (HAVE_RES_QUERY) || !defined(HAVE_DN_EXPAND)
+#undef MXNS_DNSBL
+#endif
+#if !defined(HAVE_HSTRERROR)
+#undef MXNS_DNSBL			/* MX lookups need raw hsterror() */
+#endif
+#if !defined(T_MX) || !defined(T_A) || !defined(T_AAAA) || !defined(T_NS)
+#undef MXNS_DNSBL
+#endif
+#if !defined(C_IN) || !defined(C_IN) || !defined(PACKETSZ)
+#undef MXNS_DNSBL
+#endif
+#if !defined(RES_DEFNAMES) || !defined(RES_DNSRCH) || !defined(RES_NOALIASES)
+#undef MXNS_DNSBL
+#endif
+
+
+DNSBL *dnsbls;
+static DNSBL_UNHIT dnsbl_groups;
+u_char have_dnsbl_groups;
+
+HELPER helper;
+u_char have_helpers;
+DCC_PATH dnsbl_progpath;
+
+static u_char is_helper;
+static const char *helper_str = "";
+
+static u_char have_ipv4_dnsbl;
+static u_char have_ipv6_dnsbl;
+static u_char have_name_dnsbl;
+
+#define MAX_MSG_SECS 1000
+#ifndef RES_TIMEOUT
+#define RES_TIMEOUT 3
+#endif
+static int msg_secs = 25;		/* total seconds/mail message */
+static time_t msg_us;
+static int url_secs = 11;		/* total seconds/host name */
+static time_t url_us;
+
+
+/* Parse a string of the form:
+ *	    "domain[,[IPaddr][,name|ipv4|ipv6]]
+ *	or of one of the forms:
+ *	    set:progpath	path of helper program
+ *	    set:debug=X		more logging
+ *	    set:msg-secs=S	total seconds checking blacklists/message
+ *	    set:url-secs=S	total seconds per host name
+ *	    set:[no-]client	client IP address checks
+ *	    set:[no-]mail_host	envelope mail_from checks
+ *	    set:[no-]URL	body URL checks
+ *	    set:[no-]MX		MX checks
+ *	    set:[no-]NS		NS checks
+ *	    set:defaults	restore defaults
+ *	    set:group=X		start group of DNSBLs
+ *	    set:[no-]temp-fail	timeout temporarily fails SMTP transaction
+ *	    set:max_helpers=X	override dccm or dccifd max_work
+ *
+ *	    set:[no-]envelope	 obsolete mail host & client
+ *
+ *	    set:helper=soc,fd,X	start DNS resolver process
+ */
+u_char					/* 0=bad */
+dcc_parse_dnsbl(DCC_EMSG emsg, const char *entry, const char *progpath,
+		u_char tfail)
+{
+	static DNSBL_FGS cur_fgs = DNSBL_FG_DEFAULT;
+	static const REPLY_TPLT *cur_reply = 0;
+	static int bl_num = 0;
+	static int cur_group = 0;
+	DNSBL_GBITS gbit;
+	DNSBL *dp, *dp1, **dpp;
+	const char *tgt_ip;		/* "hit" IP address of this blacklist */
+	DNSBL_DOM tgt_ip_buf;
+	DNSBL_TYPE bl_type;
+	struct in6_addr tgt, tgt_mask;
+	int tgt_bits;
+	u_char tgt_use_ipv6;
+	DCC_EMSG addr_emsg;
+	int error, bl_dom_len;
+	int val;
+	char *p;
+#ifdef HAVE_HELPERS
+	int soc;
+	int fd;
+	int total_helpers;
+#	define SAVE_ARG(arg) helper_save_arg("-B", arg)
+#else
+#	define SAVE_ARG(arg)
+#endif
+
+	if (progpath && dnsbl_progpath[0] == '\0') {
+		const char *slash = strrchr(progpath, '/');
+		if (slash)
+			++slash;
+		else
+			slash = progpath;
+		snprintf(dnsbl_progpath, sizeof(dnsbl_progpath),
+			 "%.*sdns-helper", (int)(slash-progpath), progpath);
+	}
+
+	/* pretend to turn on temp-fail for dccproc to get
+	 * DNSBLx(timeout) messages */
+	if (tfail)
+		cur_fgs |= DNSBL_FG_TFAIL;
+
+	/* handle parameter settings */
+	if (!CLITCMP(entry, "set:")) {
+		const char *parm = entry+LITZ("set:");
+#ifdef HAVE_HELPERS
+		/* start running a helper process on the last, magic -B */
+		if (3 == sscanf(parm, HELPER_PAT, &soc, &fd, &total_helpers)) {
+			helper_child(soc, fd, total_helpers);
+		}
+#endif
+		if (!CLITCMP(parm, "progpath=")) {
+			BUFCPY(dnsbl_progpath, parm+LITZ("progpath="));
+			return 1;
+		}
+		if (!strcasecmp(parm, "debug")) {
+			++helper.debug;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (1 == sscanf(parm, "debug=%d", &val)) {
+			helper.debug = val;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "envelope")) {	/* obsolete */
+			cur_fgs |= (DNSBL_FG_CLIENT | DNSBL_FG_MAIL_HOST);
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "no-envelope")) {	/* obsolete */
+			cur_fgs &= ~(DNSBL_FG_CLIENT | DNSBL_FG_MAIL_HOST);
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "client")) {
+			/* obsolete */
+			cur_fgs |= DNSBL_FG_CLIENT;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "no-client")) {
+			/* obsolete */
+			cur_fgs &= ~DNSBL_FG_CLIENT;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "mail_host")) {
+			/* obsolete */
+			cur_fgs |= DNSBL_FG_MAIL_HOST;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "no-mail_host")) {
+			/* obsolete */
+			cur_fgs &= ~DNSBL_FG_MAIL_HOST;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "body")) {    /* obsolete */
+			cur_fgs |= DNSBL_FG_URL;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "url")) {
+			cur_fgs |= DNSBL_FG_URL;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "no-body")) { /* obsolete */
+			cur_fgs &= ~DNSBL_FG_URL;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "no-URL")) {
+			cur_fgs &= ~DNSBL_FG_URL;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "mx")) {
+#ifdef MXNS_DNSBL
+			cur_fgs |= DNSBL_FG_MX;
+			SAVE_ARG(entry);
+			return 1;
+#else
+			dcc_pemsg(EX_USAGE, emsg,
+				  "MX DNS blacklist checks not supported");
+			return 0;
+#endif
+		}
+		if (!strcasecmp(parm, "no-mx")) {
+			cur_fgs &= ~DNSBL_FG_MX;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "ns")) {
+#ifdef MXNS_DNSBL
+			cur_fgs |= DNSBL_FG_MX;
+			SAVE_ARG(entry);
+			return 1;
+#else
+			dcc_pemsg(EX_USAGE, emsg,
+				  "NS DNS blacklist checks not supported");
+			return 0;
+#endif
+		}
+		if (!strcasecmp(parm, "no-ns")) {
+			cur_fgs &= ~DNSBL_FG_NS;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "defaults")) {
+			cur_fgs = DNSBL_FG_DEFAULT;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!CLITCMP(parm, "rej-msg=")) {
+			parm += LITZ("rej-msg=");
+			if (*parm == '\0')
+				cur_reply = 0;
+			else
+				cur_reply = dnsbl_parse_reply(parm);
+			/* do not save for helpers */
+			return 1;
+		}
+		if (!CLITCMP(parm, "msg-secs=")) {
+			parm += LITZ("msg-secs=");
+			val = strtoul(parm, &p, 10);
+			if (*p != '\0' || val < 1 || val > MAX_MSG_SECS) {
+				dcc_pemsg(EX_USAGE, emsg,
+					  "bad number of seconds in \"-B %s\"",
+					  entry);
+				return 0;
+			}
+			if (msg_secs != val) {
+				msg_secs = val;
+				SAVE_ARG(entry);
+			}
+			return 1;
+		}
+		if (!CLITCMP(parm, "url-secs=")) {
+			parm += LITZ("url-secs=");
+			val = strtoul(parm, &p, 10);
+			if (*p != '\0' || val < 1 || val > MAX_MSG_SECS) {
+				dcc_pemsg(EX_USAGE, emsg,
+					  "bad number of seconds in \"-B %s\"",
+					  entry);
+				return 0;
+			}
+			if (url_secs != val) {
+				url_secs = val;
+				SAVE_ARG(entry);
+			}
+			return 1;
+		}
+		if (1 == sscanf(parm, "group=%d", &val)
+		    && val >= 1 && val <= MAX_DNSBL_GROUPS) {
+			cur_group = val-1;
+			have_dnsbl_groups = 1;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "temp-fail")) {
+			cur_fgs |= DNSBL_FG_TFAIL;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (!strcasecmp(parm, "no-temp-fail")) {
+			cur_fgs &= ~DNSBL_FG_TFAIL;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		if (1 == sscanf(parm, "max_helpers=%d", &val)
+		    && val >= 1 && val < 1000) {
+			helper.max_helpers = val;
+			SAVE_ARG(entry);
+			return 1;
+		}
+		dcc_pemsg(EX_USAGE, emsg, "unrecognized  \"-B %s\"",
+			  entry);
+		return 0;
+	}
+
+	/* we must have a DNSBL specification */
+	bl_type = DNSBL_TYPE_IPV4;	/* assume it is a simple IPv4 DNSBL */
+	tgt_ip = strchr(entry, ',');
+	if (!tgt_ip) {
+		bl_dom_len = strlen(entry);
+		have_ipv4_dnsbl = 1;
+	} else {
+		bl_dom_len = tgt_ip - entry;
+		++tgt_ip;
+
+		/* notice trailing ",name" or ",IPv4" */
+		p = strchr(tgt_ip, ',');
+		if (!p) {
+			have_ipv4_dnsbl = 1;
+		} else {
+			++p;
+			if (!strcasecmp(p, "name")) {
+				bl_type = DNSBL_TYPE_NAME;
+				have_name_dnsbl = 1;
+			} else if (!strcasecmp(p, "IPV4")) {
+				bl_type = DNSBL_TYPE_IPV4;
+				have_ipv4_dnsbl = 1;
+			} else if (!strcasecmp(p, "IPV6")) {
+				bl_type = DNSBL_TYPE_IPV6;
+				have_ipv6_dnsbl = 1;
+			} else {
+				dcc_pemsg(EX_NOHOST, emsg,
+					  "unknown DNSBL type in \"%s\"",
+					  entry);
+				return 0;
+			}
+			STRLCPY(tgt_ip_buf.c, tgt_ip,
+				min(ISZ(tgt_ip_buf), p-tgt_ip));
+			tgt_ip = tgt_ip_buf.c;
+		}
+	}
+
+	if (entry[0] == '.') {
+		++entry;
+		--bl_dom_len;
+	}
+	if (bl_dom_len < 1) {
+		dcc_pemsg(EX_NOHOST, emsg,
+			  "invalid DNS blacklist \"%s\"", entry);
+		return 0;
+	}
+
+	/* assume 127.0.0.2 if the target address is missing */
+	if (!tgt_ip || *tgt_ip == '\0')
+		tgt_ip = "127.0.0.2";
+
+	if (!strcasecmp(tgt_ip, "any")) {
+		/* That we get a result when we lookup up an address in the
+		 * in the DNSBL is all that matters in this case.
+		 * Whether it is IPv6 or IPv4 does not */
+		tgt_use_ipv6 = 2;
+		memset(&tgt, 0, sizeof(tgt));
+		memset(&tgt_mask, 0, sizeof(tgt_mask));
+
+	} else if (0 != (tgt_bits = dcc_str2cidr(emsg, &tgt, &tgt_mask,
+						 &tgt_use_ipv6, tgt_ip,
+						 0, 0))) {
+		if (tgt_bits < 0) {
+			dcc_pemsg(EX_NOHOST, addr_emsg,
+				  "invalid DNS blacklist %s \"%s\"",
+				  addr_emsg, entry);
+			return 0;
+		}
+
+	} else {
+		dcc_host_lock();
+		if (dcc_get_host(tgt_ip, 3, &error)) {
+			/* prefer an IPv4 target address */
+			if (dcc_hostaddrs[0].sa.sa_family == AF_INET) {
+				tgt_use_ipv6 = 0;
+				dcc_ipv4toipv6(&tgt,
+					       dcc_hostaddrs[0].ipv4.sin_addr);
+			} else {
+				tgt_use_ipv6 = 1;
+				tgt = dcc_hostaddrs[0].ipv6.sin6_addr;
+			}
+			dcc_host_unlock();
+			dcc_bits2mask(&tgt_mask, 128);
+
+		} else {
+			dcc_host_unlock();
+			dcc_pemsg(EX_NOHOST, emsg,
+				  "invalid DNSBL target IP address \"%s\": %s",
+				  tgt_ip, DCC_HSTRERROR(error));
+			return 0;
+		}
+	}
+
+	if (bl_dom_len >= ISZ(DNSBL_DOM) - INET6_ADDRSTRLEN*2) {
+		dcc_host_unlock();
+		/* we cannot fit the DNSBL base and the target name or address
+		 * into blw->probe.  We need to do DNS lookups of names
+		 * like 33.22.11.10.dnsbl.example.com or
+		 * domain.dom.bl.example.com */
+		dcc_pemsg(EX_NOHOST, emsg, "DNSBL name \"%s\" too long", entry);
+		return 0;
+	}
+
+	dp = dcc_malloc(sizeof(*dp));
+	memset(dp, 0, sizeof(*dp));
+
+	dp->tgt_use_ipv6 = tgt_use_ipv6;
+	dp->tgt = tgt;
+	dp->tgt_mask = tgt_mask;
+
+	dp->bl_type = bl_type;
+	dp->fgs = cur_fgs;
+	dp->reply = cur_reply;
+	memcpy(&dp->bl_dom.c, entry, bl_dom_len);
+	dp->bl_dom_len = bl_dom_len+1;	/* count trailing '\0' */
+	dp->bl_num = ++bl_num;
+	dp->group = cur_group;
+	gbit = DNSBL_G2B(dp->group);
+	dnsbl_groups.all |= gbit;
+	if (cur_fgs & DNSBL_FG_CLIENT)
+		dnsbl_groups.client |= gbit;
+	if (cur_fgs & DNSBL_FG_MAIL_HOST) {
+		dnsbl_groups.mail_host |= gbit;
+		if (cur_fgs & DNSBL_FG_MX)
+			dnsbl_groups.mail_host_mx |= gbit;
+		if (cur_fgs & DNSBL_FG_NS)
+			dnsbl_groups.mail_host_ns |= gbit;
+	}
+	if (cur_fgs & DNSBL_FG_URL) {
+		dnsbl_groups.url |= gbit;
+		if (cur_fgs & DNSBL_FG_MX)
+			dnsbl_groups.url_mx |= gbit;
+		if (cur_fgs & DNSBL_FG_NS)
+			dnsbl_groups.url_ns |= gbit;
+	}
+
+	/* link the DNSBLs in the order they are declared so that they
+	 * can be prioritized */
+	dpp = &dnsbls;
+	for (;;) {
+		dp1 = *dpp;
+		if (!dp1) {
+			*dpp = dp;
+			break;
+		}
+		/* notice sufficiently duplicate DNSBLS */
+		if (!(dp->fgs & DNSBL_FG_DUP) && !dp1->dup
+		    && dp->tgt_use_ipv6 == dp1->tgt_use_ipv6
+		    && dp->bl_type == dp1->bl_type
+		    && dp->bl_dom_len == dp1->bl_dom_len
+		    && !memcmp(dp->bl_dom.c, dp1->bl_dom.c, dp->bl_dom_len)) {
+			dp->fgs |= DNSBL_FG_DUP;
+			dp1->dup = dp;
+		}
+		dpp = &dp1->fwd;
+	}
+
+	SAVE_ARG(entry);
+	return 1;
+#undef SAVE_ARG
+}
+
+
+
+/* resolve inconsistencies among the -B parameters */
+static inline void
+fix_url_secs(void)
+{
+	if (url_secs > msg_secs)
+		url_secs =  msg_secs;
+	msg_us = msg_secs * DCC_US;
+	url_us = url_secs * DCC_US;
+}
+
+
+
+static char *
+type_str(char *buf, int buf_len, const DNSBL_WORK *blw, DNSBL_FGS fgs)
+{
+	const char *type;
+	char sustr[DCC_SU2STR_SIZE];
+	int i;
+
+	switch (fgs & DNSBL_FG_TYPES) {
+	case DNSBL_FG_CLIENT:
+		type = "SMTP client";
+		if (blw->tgt.dom.c[0] == '\0') {
+			snprintf(buf, buf_len, "%s %s",
+				 type, dcc_ipv6tostr2(sustr, sizeof(sustr),
+						      &blw->tgt.addr));
+			return buf;
+		}
+		break;
+	case DNSBL_FG_MAIL_HOST:
+		type = "mail_host";
+		break;
+	case DNSBL_FG_URL:
+		type = "URL";
+		break;
+	case DNSBL_FG_MX | DNSBL_FG_MAIL_HOST :
+		type = "mail_host MX";
+		break;
+	case DNSBL_FG_MX | DNSBL_FG_URL:
+		type = "URL MX";
+		break;
+	case DNSBL_FG_NS | DNSBL_FG_MAIL_HOST:
+		type = "mail_host NS";
+		break;
+	case DNSBL_FG_NS | DNSBL_FG_URL:
+		type = "URL NS";
+		break;
+
+	default:
+		dcc_logbad(EX_SOFTWARE, "impossible DNSBL hit type %#x", fgs);
+		break;
+	}
+
+	i = snprintf(buf, buf_len, "%s %s", type, blw->tgt.dom.c);
+	if (i >= buf_len && buf_len > 4)
+		strcpy(&buf[buf_len-4], "...");
+	return buf;
+}
+
+
+
+static void PATTRIB(4,5)
+dnsbl_log(const DNSBL_WORK *blw, const DNSBL_GROUP *blg, DNSBL_FGS fgs,
+	  const char *pat, ...)
+{
+	char type_buf[sizeof(blg->btype_buf)];
+	const char *type0, *type1;
+	char gbuf[8];
+	const char *gnum;
+	char msg[256];
+	va_list args;
+
+	va_start(args, pat);
+	vsnprintf(msg, sizeof(msg), pat, args);
+	va_end(args);
+
+	if (fgs) {
+		type0 = " ";
+		type1 = type_str(type_buf, sizeof(type_buf), blw, fgs);
+	} else {
+		type0 = "";
+		type1 = "";
+	}
+	if (!have_dnsbl_groups) {
+		gnum = "";
+	} else if (!blg) {
+		gnum = "*";
+	} else {
+		snprintf(gbuf, sizeof(gbuf), "%d",
+			 (int)(blg - blw->groups)+1);
+		gnum = gbuf;
+	}
+
+	if (helper.debug) {
+		if (dcc_no_syslog)
+			thr_trace_msg(blw->log_ctxt, "DNSBL%s%s%s%s %s",
+				      gnum, helper_str, type0, type1, msg);
+		else
+			thr_trace_msg(blw->log_ctxt, "%s DNSBL%s%s%s%s %s",
+				      blw->id,
+				      gnum, helper_str, type0, type1, msg);
+	} else {
+		thr_log_print(blw->log_ctxt, 1, "DNSBL%s%s%s%s %s\n",
+			      helper_str, gnum, type0, type1, msg);
+	}
+}
+
+
+
+void
+dcc_dnsbl_result(ASK_ST *ask_stp, DNSBL_WORK *blw)
+{
+	DNSBL *dp;
+	DNSBL_GROUP *blg;
+	int gnum;
+
+	if (!blw)
+		return;
+
+	for (gnum = 0, blg = blw->groups;
+	     gnum < MAX_DNSBL_GROUPS;
+	     ++gnum, ++blg) {
+		if (blg->fgs & DNSBL_FG_HITS) {
+			*ask_stp |= (ASK_ST_DNSBL_HIT(gnum) | ASK_ST_LOGIT);
+			dnsbl_log(blw, blg, 0, "%s %s=%s",
+				  blg->btype, blg->probe.c, blg->result);
+		} else if (blg->fgs & DNSBL_FG_TIMEO) {
+			*ask_stp |= ASK_ST_DNSBL_TIMEO(gnum);
+			for (dp = dnsbls; dp; dp = dp->fwd) {
+				if (dp->group != gnum)
+					continue;
+				if (dp->fgs & DNSBL_FG_TFAIL) {
+					*ask_stp |= ASK_ST_DNSBL_TFAIL(gnum);
+					break;
+				}
+			}
+		}
+	}
+}
+
+
+
+/* There are several timing requirements:
+ *	- Do not spend too much time on any single URL or envelope value.
+ *	- At helper.debug >=1, log the first DNSBL check in a group
+ *	    unfinished for lack of time.  Also log checks for single URLs that
+ *	    are partially unfinished.
+ *	- At helper.debug >=2, log things that take a long time
+ *	- Minimize log messages, because the noise can be deafening.
+ *	- Mark unfinished groups */
+
+
+/* A check has been abandoned or timed out. */
+static void
+set_timeo(DNSBL_WORK *blw, DNSBL_FGS fgs)
+{
+	DNSBL *dp;
+	DNSBL_GROUP *blg;
+	int gnum;
+
+	/* groups that are marked as having suffered timeouts can be
+	 * hit by later URLs */
+	if (fgs == 0) {
+		/* mark all groups if we don't know the type of request */
+		for (gnum = 0, blg = blw->groups;
+		     gnum < MAX_DNSBL_GROUPS;
+		     ++gnum, ++blg) {
+			/* ignore groups that have hit or timed out */
+			if (!(blw->unhit.all & DNSBL_G2B(gnum))
+			    || (blg->fgs & DNSBL_FG_TIMEO))
+				continue;
+			blg->fgs |= DNSBL_FG_TIMEO;
+		}
+
+	} else {
+		/* mark only groups that still care about this type */
+		for (dp = dnsbls; dp; dp = dp->fwd) {
+			/* this DNSBL does not care about this type
+			 * or the group to which it belongs has been hit */
+			if (!(dp->fgs & fgs & DNSBL_FG_HITS)
+			    || !(blw->unhit.all & DNSBL_G2B(dp->group)))
+				continue;
+			blw->groups[dp->group].fgs |= DNSBL_FG_TIMEO;
+		}
+	}
+
+	/* no complaint if time remains */
+	if (blw->url_us >= blw->url_us_used)
+		return;
+
+	/* no more log messages if everything has been complained about */
+	if (blw->url_us < 0 || blw->msg_us < 0)
+		return;
+
+	/* only one final message/URL */
+	blw->url_us = -1;
+	if (helper.debug < 2)
+		return;
+
+	if (is_helper) {
+		/* Messages from the helper process go to the system
+		 * log but not the per-message log file.
+		 * The helper works on a single URL or envelope value
+		 * and so does not know about the time limit for the
+		 * entire message. */
+		dnsbl_log(blw, 0, fgs, "failed after %.1f url_secs used",
+			  blw->url_us_used / (DCC_US*1.0));
+
+	} else if (blw->msg_us > blw->url_us_used) {
+		/* time remains for later URLs */
+		dnsbl_log(blw, 0, fgs,
+			  "failed after using %.1f url_secs;"
+			  " %.1f msg-secs remain",
+			  blw->url_us_used / (DCC_US*1.0),
+			  (blw->msg_us - blw->url_us_used) / (DCC_US*1.0));
+
+	} else {
+		dnsbl_log(blw, 0, fgs, "failed after using %.1f sec",
+			  blw->url_us_used / (DCC_US*1.0));
+	}
+}
+
+
+
+/* see if we are out of time before doing something */
+static inline u_char			/* 0=too late */
+time_ck_pre(DNSBL_WORK *blw, DNSBL_FGS fgs)
+{
+	/* don't worry if there is plenty of time */
+	if (blw->url_us >= blw->url_us_used)
+		return 1;
+
+	/* There is no more time. Ether previous operations succeeded slowly
+	 * or failed and were logged.
+	 * In the first case, log this operation and mark the groups. */
+	set_timeo(blw, fgs);
+	return 0;
+}
+
+
+
+/* see if we ran out of time after doing something */
+static u_char				/* 0=out of time */
+time_ck_post(DNSBL_WORK *blw, DNSBL_FGS fgs,
+	     u_char timedout)		/* 1=operation timed out */
+{
+	struct timeval now;
+	time_t used_us;
+
+	if (blw->url_us <= 0)
+		return 0;			/* previously out of time */
+
+	gettimeofday(&now, 0);
+	used_us = tv_diff2us(&now, &blw->url_start);
+
+	if (blw->url_us >= used_us && !timedout) {
+		if (helper.debug > 1
+		    && (used_us-blw->url_us_used) > url_us/4)
+			dnsbl_log(blw, 0, fgs, "%s after using %.1f url_secs",
+				  timedout ? "failed" : "succeeded",
+				  (used_us - blw->url_us_used) / (DCC_US*1.0));
+		blw->url_us_used = used_us;
+		return 1;
+	}
+
+	blw->url_us_used = used_us;
+	set_timeo(blw, fgs);
+	return 0;
+}
+
+
+
+/* start timer before we start to check something in the DNS blacklists
+ *	give up if we are already out of time */
+static u_char				/* 0=already too much time spent */
+msg_secs_start(DNSBL_WORK *blw, DNSBL_FGS fgs)
+{
+	blw->url_us = url_us;
+	blw->url_us_used = 0;
+
+	if (blw->msg_us <= 0) {
+		if (blw->msg_us == 0) {
+			/* out of time for next URL before we start it */
+			if (helper.debug)
+				dnsbl_log(blw, 0, fgs,
+					  "%d msg-secs already exhausted",
+					  msg_secs);
+			blw->msg_us = -1;   /* only one log message */
+			blw->url_us = -1;
+		}
+		/* mark the groups but do not log anything */
+		set_timeo(blw, fgs);
+		return 0;
+	}
+
+	gettimeofday(&blw->url_start, 0);
+	return 1;
+}
+
+
+
+/* account for time used */
+static void
+msg_secs_fin(DNSBL_WORK *blw)
+{
+	if (blw->msg_us < 0)
+		return;			/* prevously out of time */
+
+	blw->msg_us -= blw->url_us_used;
+	if (blw->msg_us > 0)
+		return;
+
+	if (blw->url_us >= blw->url_us_used) {
+		/* The clock had no time for more DNS work for this
+		 * name or address, but we finished and so it might
+		 * not matter.
+		 * Ensure a log message on the next check, if any */
+		blw->msg_us = 0;
+	}
+}
+
+
+
+#ifndef HAVE__RES
+static void
+dnsbl_res_delays(const DNSBL_WORK *blw UATTRIB)
+{
+	return;
+}
+#else
+/* Limit resolver delays to as much as we are willing to wait
+ *	We should be talking to a local caching resolver.  If it does not answer
+ *	immediately, it is unlikely to later.  If it does eventually get an
+ *	answer, the answer will probably ready the next time we ask.
+ *  dcc_host_lock() must be held. */
+static void
+dnsbl_res_delays(const DNSBL_WORK *blw)
+{
+	static int init_res_retrans;
+	static int init_res_retry;
+	int budget;			/* seconds we can afford */
+	int res_retrans;		/* retransmition delay */
+	int res_retry;			/* # of retransmissions */
+	int total;			/* retrans*retry = worst case delay */
+
+	/* get the current value */
+	if (!_res.options & RES_INIT) {
+		res_init();
+		init_res_retry = _res.retry;
+		if (!init_res_retry)
+			init_res_retry = 4;
+		init_res_retrans = _res.retrans;
+		if (!init_res_retrans)
+			init_res_retrans = RES_TIMEOUT;
+	}
+	res_retry = init_res_retry;
+	res_retrans = init_res_retrans;
+
+	/* assume binary exponential backoffs as in the BIND resolver */
+	total = ((1<<res_retry) -1) * res_retrans;
+
+	/* If the default values could take too long, then try 2 seconds.
+	 * If that is still too long, go to 1 retransmission and an initial
+	 * retransmission delay of 1/3 of the total allowed delay.
+	 * 1/3 from one exponential backoff for a total of 2**2-1=3 times
+	 * the initial delay.
+	 * We should be using a local caching DNS server and so should not
+	 * see many lost packets */
+	budget = (blw->url_us - blw->url_us_used + DCC_US/2) / DCC_US;
+	if (budget < 1)
+		budget = 1;
+
+	if (total >= budget) {
+		res_retry = 2;		/* 2 retries are often few enough */
+		total = ((1<<res_retry) -1) * res_retrans;
+
+		/* if that is not few enough,
+		 * then reduce the retransmission delay to fit */
+		if (total >= budget) {
+			res_retrans = budget/3;
+			if (res_retrans == 0)
+				res_retrans = 1;
+		}
+	}
+
+	if (_res.retry != res_retry
+	    || _res.retrans != res_retrans) {
+		_res.retry = res_retry;
+		_res.retrans = res_retrans;
+		if (helper.debug > 4)
+			dnsbl_log(blw, 0, 0,
+				  "budget=%d  _res.retry=%d"
+				  " _res.retrans=%d seconds",
+				  budget, res_retry, res_retrans);
+	}
+}
+#endif /* !HAVE__RES */
+
+
+
+static inline void
+blw_clear(DNSBL_WORK *blw)
+{
+	DNSBL_GROUP *blg;
+
+	blw->tgt.dom.c[0] = '\0';
+	blw->tgt_dom_len = 0;
+	for (blg = blw->groups; blg <= LAST(blw->groups); ++blg) {
+		blg->fgs = 0;
+		blg->btype = 0;
+		blg->result[0] = '\0';
+		blg->tgt.c[0] = '\0';
+		blg->probe.c[0] = '\0';
+	}
+}
+
+
+
+/* get ready to handle a mail message */
+void
+dcc_dnsbl_init(DCC_GOT_CKS *cks,
+	       DCC_CLNT_CTXT *dcc_ctxt, void *log_ctxt, const char *id)
+{
+	DNSBL_WORK *blw;
+	int i;
+
+	if (!dnsbls)
+		return;
+
+	blw = cks->dnsbl;
+	if (!blw) {
+		blw = dcc_malloc(sizeof(*blw));
+		memset(blw, 0, sizeof(*blw));
+		cks->dnsbl = blw;
+
+		/* general initializations on the first use of DNS blacklists */
+		fix_url_secs();
+	}
+
+	blw_clear(blw);
+	blw->msg_us = msg_us;
+	blw->id = id;
+	blw->dcc_ctxt = dcc_ctxt;
+	blw->log_ctxt = log_ctxt;
+	blw->unhit = dnsbl_groups;
+	for (i = 0; i < DIM(blw->tgt_cache); ++i)
+		blw->tgt_cache[i].dom.c[0] = '\0';
+	blw->tgt_cache_pos = 0;
+}
+
+
+
+/* look for a host name or IP address in a DNS blacklist and its duplicates
+ *	and any hit groups of DNSBLs.
+ *	These DNS operations should be done with local default values for
+ *	RES_DEFNAMES, RES_DNSRCH, and RES_NOALIASES because the blacklist
+ *	might be something local and strange. */
+static u_char				/* 0=no more time or blacklists */
+lookup(DNSBL_WORK *blw,
+       const DNSBL_DOM *probe,		/* look for this */
+       const DNSBL *dp,			/* check this blacklist & its dups */
+       DNSBL_FGS fgs,			/* type of lookup */
+       const void *tgt)			/* IP address or whatever for tracing */
+{
+#	define NUM_SUSTRS 3
+	char sustrs[DCC_SU2STR_SIZE*NUM_SUSTRS+1+sizeof("...")];
+	const DCC_SOCKU *hap;
+	struct in6_addr addr6;
+	const struct in6_addr *addr6p;
+	int error;
+	DNSBL_GROUP *blg;
+	DNSBL_GBITS gbit;
+	u_char hit;
+	char *p;
+	int i;
+
+	if (!time_ck_pre(blw, fgs))
+		return 0;
+
+	/* resolve a list of IP addresses for the probe in the DNSBL */
+	dcc_host_lock();
+	dnsbl_res_delays(blw);
+	if (!dcc_get_host(probe->c, dp->tgt_use_ipv6, &error)) {
+		dcc_host_unlock();
+		if (helper.debug > 1)
+			dnsbl_log(blw, 0, fgs, "gethostbyname(%s): %s",
+				  probe->c, DCC_HSTRERROR(error));
+		return time_ck_post(blw, fgs, error == TRY_AGAIN);
+	}
+
+	/* check each address obtained for the probe in the DNSBL
+	 * for a hit in the list of duplicate references to this DNSBL */
+	hit = 0;
+	do {
+		/* skip this reference if it is for the wrong type of DNSBL */
+		if ((dp->fgs & fgs) != fgs)
+			continue;
+		/* skip this reference if the containing group has a hit */
+		blg = &blw->groups[dp->group];
+		if (blg->fgs & DNSBL_FG_HITS)
+			continue;
+		/* check all of the addresses for a hit with this reference */
+		for (hap = dcc_hostaddrs; hap < dcc_hostaddrs_end; ++hap) {
+			/* finished if any answer is good enough
+			 * or if we get a match */
+			if (hap->sa.sa_family == AF_INET6) {
+				addr6p = &hap->ipv6.sin6_addr;
+			} else {
+				addr6p = &addr6;
+				dcc_ipv4toipv6(&addr6, hap->ipv4.sin_addr);
+			}
+			if (!DCC_IN_BLOCK(*addr6p, dp->tgt, dp->tgt_mask))
+				continue;
+
+			/* got a hit */
+			blg->dnsbl = dp;
+			blg->fgs = fgs;
+			if (dp->bl_type == DNSBL_TYPE_IPV4)
+				dcc_ipv4tostr(blg->tgt.c, sizeof(blg->tgt),
+					      tgt);
+			else if (dp->bl_type == DNSBL_TYPE_IPV6)
+				dcc_ipv6tostr(blg->tgt.c, sizeof(blg->tgt),
+					      tgt);
+			else if (dp->bl_type == DNSBL_TYPE_NAME)
+				BUFCPY(blg->tgt.c, tgt);
+			blg->btype = type_str(blg->btype_buf,
+					      sizeof(blg->btype_buf),
+					      blw, blg->fgs);
+			BUFCPY(blg->probe.c, probe->c);
+			dcc_su2str2(blg->result, sizeof(blg->result), hap);
+			if (helper.debug > 1 && (is_helper || !have_helpers))
+				dnsbl_log(blw, blg, fgs, "hit %s=%s",
+					  probe->c,
+					  dcc_su2str2(sustrs, sizeof(sustrs),
+						      hap));
+			gbit = DNSBL_G2B(dp->group);
+			if (fgs & DNSBL_FG_CLIENT)
+				blw->unhit.client &= ~gbit;
+			if (fgs & DNSBL_FG_MAIL_HOST) {
+				blw->unhit.mail_host &= ~gbit;
+				if (fgs & DNSBL_FG_MX)
+					blw->unhit.mail_host_mx &= ~gbit;
+				if (fgs & DNSBL_FG_NS)
+					blw->unhit.mail_host_ns &= ~gbit;
+			}
+			if (fgs & DNSBL_FG_URL) {
+				blw->unhit.url &= ~gbit;
+				if (fgs & DNSBL_FG_MX)
+					blw->unhit.url_mx &= ~gbit;
+				if (fgs & DNSBL_FG_NS)
+					blw->unhit.url_ns &= ~gbit;
+			}
+			blw->unhit.all &= ~gbit;
+			if (!blw->unhit.all) {
+				dcc_host_unlock();
+				time_ck_post(blw, fgs, 0);
+				return 0;
+			}
+			hit = 1;
+			break;
+		}
+
+		/* check the results in duplicate references to the DNSBL */
+	} while ((dp = dp->dup) != 0);
+
+	if (hit || helper.debug < 2) {
+		dcc_host_unlock();
+		return time_ck_post(blw, fgs, 0);
+	}
+
+	p = sustrs;
+	i = 0;
+	do {
+		dcc_su2str2(p, DCC_SU2STR_SIZE, &dcc_hostaddrs[i]);
+		p += strlen(p);
+		*p++ = ' ';
+		*p = '\0';
+		if (p >= &sustrs[DCC_SU2STR_SIZE*NUM_SUSTRS]) {
+			strcpy(p, "...");
+			break;
+		}
+	} while (dcc_hostaddrs_end > &dcc_hostaddrs[++i]);
+	dcc_host_unlock();
+	dnsbl_log(blw, 0, fgs, "miss; gethostbyname(%s)=%s", probe->c, sustrs);
+	return time_ck_post(blw, fgs, 0);
+
+#undef NUM_SUSTRS
+}
+
+
+
+/* check one IP address against the DNS blacklists */
+static u_char				/* 0=no more time or blacklists */
+ip_lookup(DNSBL_WORK *blw,
+	  DNSBL_TYPE tgt_type,
+	  const void *tgt,		/* in_addr* or in6_addr*, not aligned */
+	  DNSBL_FGS fgs)		/* type of lookup */
+{
+	const DNSBL *dp;
+	const u_char *bp;
+	DNSBL_DOM probe;
+
+	/* check all DNSBLs for a group without a hit */
+	for (dp = dnsbls; dp; dp = dp->fwd) {
+		if (dp->bl_type != tgt_type)
+			continue;
+		if ((dp->fgs & fgs) != fgs)
+			continue;
+		if (dp->fgs & DNSBL_FG_DUP)
+			continue;
+
+		if (!(blw->unhit.all & DNSBL_G2B(dp->group)))
+			continue;
+
+		bp = (u_char *)tgt;
+		if (tgt_type == DNSBL_TYPE_IPV4)
+			snprintf(probe.c, sizeof(probe.c),
+				 "%d.%d.%d.%d.%s",
+				 bp[3], bp[2], bp[1], bp[0],
+				 dp->bl_dom.c);
+		else
+			snprintf(probe.c, sizeof(probe.c),
+				 "%d.%d.%d.%d.%d.%d.%d.%d"
+				 ".%d.%d.%d.%d.%d.%d.%d.%d.%s",
+				 bp[15], bp[14], bp[13], bp[12],
+				 bp[11], bp[10], bp[9], bp[8],
+				 bp[7], bp[6], bp[5], bp[4],
+				 bp[3], bp[2], bp[1], bp[0],
+				 dp->bl_dom.c);
+
+		if (!lookup(blw, &probe, dp, fgs, tgt))
+			return 0;
+	}
+	return 1;
+}
+
+
+
+/* convert a name to one or more IP addresses to be checked in a DNS blacklist.
+ *  dcc_host_lock() must be held.
+ *	These DNS operations need RES_DEFNAMES and RES_DNSRCH off and
+ *	RES_NOALIASES on when the name is an MX or NS server name.
+ */
+static u_char				/* 0=failed */
+dnsbl_get_host(DNSBL_WORK *blw,
+	       const DNSBL_DOM *dom,
+	       u_char use_ipv6,
+	       int *errorp,		/* put error number here */
+	       DNSBL_FGS fgs		/* 0, DNSBL_FG_MX, or DNSBL_FG_NS */
+#ifndef MXNS_DNSBL
+	       UATTRIB
+#endif
+	       )
+{
+#ifdef MXNS_DNSBL
+	u_long save_options;
+#endif
+	u_char result;
+
+#ifdef MXNS_DNSBL
+	save_options = _res.options;
+	if (fgs & (DNSBL_FG_MX | DNSBL_FG_NS)) {
+		_res.options &= ~(RES_DEFNAMES | RES_DNSRCH);
+		_res.options |= RES_NOALIASES;
+	}
+#endif
+	dnsbl_res_delays(blw);
+	result = dcc_get_host(dom->c, use_ipv6, errorp);
+#ifdef MXNS_DNSBL
+	if (fgs & (DNSBL_FG_MX | DNSBL_FG_NS))
+		_res.options = save_options;
+#endif
+	return result;
+}
+
+
+
+/* look for a domain name in the DNS blacklists */
+static u_char				/* 0=no more time or blacklists */
+name_lookup(DNSBL_WORK *blw,
+	    const DNSBL_DOM *tgt,
+	    DNSBL_FGS fgs)		/* type of lookup */
+{
+	const DNSBL *dp;
+	DNSBL_DOM probe;
+	const char *p;
+	int tgt_len, i;
+
+	if (!have_name_dnsbl)
+		return 1;
+
+	for (dp = dnsbls; dp; dp = dp->fwd) {
+		if (dp->bl_type != DNSBL_TYPE_NAME)
+			continue;
+		if ((dp->fgs & fgs) != fgs)
+			continue;
+		if (dp->fgs & DNSBL_FG_DUP)
+			continue;
+
+		if (!(blw->unhit.all & DNSBL_G2B(dp->group)))
+			continue;
+
+		/* trim trailing '.' from names */
+		p = tgt->c;
+		tgt_len = strlen(p);
+		if (tgt_len > 0 && p[tgt_len-1] == '.')
+			--tgt_len;
+
+		if (tgt_len != 0) {	/* handle empty name */
+			/* truncate long names on the left and complain */
+			i = (tgt_len + dp->bl_dom_len) - (sizeof(probe.c) - 1);
+			if (i > 0) {
+				if (helper.debug)
+					dnsbl_log(blw, 0, 0,
+						  "target \"%s\""
+						  " is %d bytes too long",
+						  tgt->c, i);
+				p += i;
+				tgt_len -= i;
+			}
+			memcpy(&probe.c[0], p, tgt_len);
+			probe.c[tgt_len++] = '.';
+		}
+		memcpy(&probe.c[tgt_len], dp->bl_dom.c, dp->bl_dom_len);
+
+		if (!lookup(blw, &probe, dp, fgs, tgt))
+			return 0;
+	}
+
+	return 1;
+}
+
+
+
+/* look for a domain name and its IP addresses in the DNS blacklists */
+static u_char				/* 0=no more time or blacklists */
+name_ip_lookup(DNSBL_WORK *blw,
+	       const DNSBL_DOM *tgt,
+	       DNSBL_FGS fgs)		/* type of lookup */
+{
+	const DCC_SOCKU *sup;
+	struct in_addr ipv4[8];
+	struct in6_addr ipv6[4];
+	int i, error;
+
+	/* check the name */
+	if (!name_lookup(blw, tgt, fgs))
+		return 0;
+
+	/* cannot resolve a null name into an address to try in the DNSBLs */
+	if (tgt->c[0] == '\0')
+		return 1;
+
+	if (!time_ck_pre(blw, fgs))
+		return 0;
+
+	/* check IPv4 addresses for the name in IPv4 DNSBLs */
+	if (have_ipv4_dnsbl) {
+		dcc_host_lock();
+		/* first resolve IPv4 addresses for the URL or client name */
+		if (!dnsbl_get_host(blw, tgt, 0, &error, fgs)) {
+			dcc_host_unlock();
+			if (helper.debug > 1)
+				dnsbl_log(blw, 0, fgs, "gethostbyname(%s): %s",
+					  tgt->c, DCC_HSTRERROR(error));
+			if (!time_ck_post(blw, fgs, error == TRY_AGAIN))
+				return 0;
+		} else {
+			/* Try several of the IP addresses for the domain.
+			 * Save any IP addresses we want to check before we
+			 * check them, because checking changes the array
+			 * of addresses. */
+			for (sup = dcc_hostaddrs, i = 0;
+			     sup < dcc_hostaddrs_end && i < DIM(ipv4);
+			     ++sup, ++i) {
+				ipv4[i] = sup->ipv4.sin_addr;
+			}
+			dcc_host_unlock();
+			if (!time_ck_post(blw, fgs, 0))
+				return 0;
+			/* check the addresses in all of the DNS blacklists */
+			do {
+				if (!ip_lookup(blw, DNSBL_TYPE_IPV4,
+					      &ipv4[--i], fgs))
+					return 0;
+			} while (i > 0);
+		}
+	}
+
+	/* finally try IPv6 addresses for the name */
+	if (have_ipv6_dnsbl) {
+		dcc_host_lock();
+		if (!dnsbl_get_host(blw, tgt, 1, &error, fgs)) {
+			dcc_host_unlock();
+			if (helper.debug > 1)
+				dnsbl_log(blw, 0, fgs, "gethostbyname(%s): %s",
+					  tgt->c, DCC_HSTRERROR(error));
+			if (!time_ck_post(blw, fgs, error == TRY_AGAIN))
+				return 0;
+		} else {
+			for (sup = dcc_hostaddrs, i = 0;
+			     sup < dcc_hostaddrs_end && i < DIM(ipv6);
+			     ++sup, ++i) {
+				ipv6[i] = sup->ipv6.sin6_addr;
+			}
+			dcc_host_unlock();
+			if (!time_ck_post(blw, fgs, 0))
+				return 0;
+			do {
+				if (!ip_lookup(blw, DNSBL_TYPE_IPV6,
+					       &ipv4[--i], fgs))
+					return 0;
+			} while (i > 0);
+		}
+	}
+
+	return 1;
+}
+
+
+
+/* check an SMTP client */
+static void
+dnsbl_client(DNSBL_WORK *blw)
+{
+	struct in_addr ipv4;
+
+	if (blw->tgt.dom.c[0] != '\0'
+	    && blw->tgt.dom.c[0] != '[')
+		name_lookup(blw, &blw->tgt.dom, DNSBL_FG_CLIENT);
+
+	if (have_ipv4_dnsbl
+	    && dcc_ipv6toipv4(&ipv4, &blw->tgt.addr)) {
+		if (!ip_lookup(blw, DNSBL_TYPE_IPV4,
+			       &ipv4, DNSBL_FG_CLIENT))
+			return;
+	}
+
+	if (have_ipv6_dnsbl
+	    && !ip_lookup(blw, DNSBL_TYPE_IPV6,
+			  &blw->tgt.addr, DNSBL_FG_CLIENT))
+		return;
+}
+
+
+
+
+#ifdef MXNS_DNSBL
+/* look at DNS resource records */
+static u_char				/* 1=continue 0=stop looking */
+rr_lookup(DNSBL_WORK *blw,
+	  const char *name,		/* see if this domain is blacklisted */
+	  DNSBL_FGS fgs,
+	  u_short req_type)		/* T_A, T_AAAA, or T_MX */
+{
+	union {
+	    u_char  buf[PACKETSZ+20];
+	    HEADER  hdr;
+	} answer;
+	DNSBL_DOM dom;
+	const u_char *ap, *ap1, *eom;
+	int cnt, skip;
+	u_short resp_type;
+	u_short resp_class, rdlength;
+	int i;
+
+	if (!time_ck_pre(blw, fgs))
+		return 0;
+
+	/* resolve a set of RRs of the desired type for a name */
+	dnsbl_res_delays(blw);
+	dcc_host_lock();
+	i = res_query(name, C_IN, req_type, answer.buf, sizeof(answer.buf));
+	dcc_host_unlock();
+	if (i < 0) {
+		/* use raw hstrerror() here because we are using the
+		 * raw resolver */
+		if (helper.debug > 1)
+			dnsbl_log(blw, 0, fgs, "res_query(%s): %s",
+				  name, hstrerror(h_errno));
+		/* stop looking after too much time or NXDOMAIN */
+		return (time_ck_post(blw, fgs, h_errno == TRY_AGAIN)
+			&& h_errno != HOST_NOT_FOUND);
+	}
+	if (!time_ck_post(blw, fgs, 0))
+		return 0;
+
+	ap = &answer.buf[HFIXEDSZ];
+	if (i > ISZ(answer.buf))
+		i = ISZ(answer.buf);
+	eom = &answer.buf[i];
+
+	/* skip the question */
+	cnt = ntohs(answer.hdr.qdcount);
+	while (--cnt >= 0) {
+		skip = dn_skipname(ap, eom);
+		if (skip < 0) {
+			if (helper.debug > 1)
+				dnsbl_log(blw, 0, fgs, "dn_skipname(%s)=%d",
+					  name, skip);
+			/* look for other RRs */
+			return 1;
+		}
+		ap += skip+QFIXEDSZ;
+	}
+
+
+	/* check each name or address RR in the answer section */
+	for (cnt = ntohs(answer.hdr.ancount);
+	     --cnt >= 0 && ap < eom;
+	     ap += rdlength) {
+		/* get the name */
+		skip = dn_expand(answer.buf, eom, ap, dom.c, sizeof(dom.c));
+		if (skip < 0) {
+			if (helper.debug > 1)
+				dnsbl_log(blw, 0, fgs,
+					  "answer dn_expand(%s)=%d",
+					  name, skip);
+			return 1;
+		}
+		ap += skip;
+
+		/* get RR type and class and skip strange RRs */
+		GETSHORT(resp_type, ap);
+		GETSHORT(resp_class, ap);
+		ap += 4;		/* skip TTL */
+		GETSHORT(rdlength, ap);	/* get rdlength */
+
+		/* we care only about relevant answers */
+		if (resp_type != req_type
+		    || resp_class != C_IN)
+			continue;
+
+		if (req_type == T_MX) {
+			/* check MX name */
+			ap1 = ap + 2;	/* skip MX preference */
+			skip = dn_expand(answer.buf, eom, ap1,
+					 dom.c, sizeof(dom.c));
+			if (skip < 0) {
+				if (helper.debug > 1)
+					dnsbl_log(blw, 0, fgs,
+						  "MX dn_expand(%s)=%d",
+						  name, skip);
+				return 1;
+			}
+
+			if (dom.c[0] == '\0'
+			    && helper.debug > 1)
+				dnsbl_log(blw, 0, fgs, "null MX name");
+
+			if (!name_ip_lookup(blw, &dom, fgs))
+				return 0;
+
+		} else if (req_type == T_A) {
+			if (!ip_lookup(blw, DNSBL_TYPE_IPV4, ap, fgs))
+				return 0;
+#ifndef NO_IPV6
+		} else if (req_type == T_AAAA) {
+			if (!ip_lookup(blw, DNSBL_TYPE_IPV6, ap, fgs))
+				return 0;
+#endif
+		}
+	}
+
+	/* check the authority section only if we care about name servers
+	 * and we are not looking for MX servers */
+	if ((fgs & DNSBL_FG_MX)
+	    || 0 == ((fgs & DNSBL_FG_URL) ? blw->unhit.url_ns
+		     : blw->unhit.mail_host_ns))
+		return 1;
+
+	/* we could look at the additional section, but it can be incomplete */
+	fgs |= DNSBL_FG_NS;
+	for (cnt = ntohs(answer.hdr.nscount);
+	     --cnt >= 0 && ap < eom;
+	     ap += rdlength) {
+		/* get the name */
+		skip = dn_expand(answer.buf, eom, ap, dom.c, sizeof(dom.c));
+		if (skip < 0) {
+			if (helper.debug > 1)
+				dnsbl_log(blw, 0, fgs,
+					  "ns dn_expand(%s)=%d",
+					  name, skip);
+			return 1;
+		}
+		ap += skip;
+
+		/* get RR type and class */
+		GETSHORT(resp_type, ap);
+		GETSHORT(resp_class, ap);
+		ap += 4;		/* skip TTL */
+		GETSHORT(rdlength, ap);	/* get rdlength */
+
+		/* we care only about NS RRs */
+		if (resp_type != T_NS
+		    || resp_class != C_IN)
+			continue;
+
+		skip = dn_expand(answer.buf, eom, ap, dom.c, sizeof(dom.c));
+		if (skip < 0) {
+			if (helper.debug > 1)
+				dnsbl_log(blw, 0, fgs,
+					  "ns answer dn_expand(%s)=%d",
+					  name, skip);
+			return 1;
+		}
+
+		if (dom.c[0] == '\0'
+		    && helper.debug > 1)
+			dnsbl_log(blw, 0, fgs, "null NS name");
+
+		if (!name_ip_lookup(blw, &dom, fgs))
+			return 0;
+	}
+
+	return 1;
+}
+#endif /* MXNS_DNSBL */
+
+
+
+/* look for a domain in the DNS blacklists, including its MX & NS servers */
+static void
+dnsbl_name_host_url(DNSBL_WORK *blw,
+		    DNSBL_FGS fg)
+{
+	struct in_addr ipv4;
+#ifndef NO_IPV6
+	struct dcc_in6_addr ipv6;
+#endif
+
+	/* recognize an ASCII IP address */
+	ipv4.s_addr = inet_addr(blw->tgt.dom.c);
+	if (INADDR_NONE != ipv4.s_addr) {
+		ip_lookup(blw, DNSBL_TYPE_IPV4, &ipv4, fg);
+		return;
+	}
+#ifndef NO_IPV6
+	if (0 < inet_pton(AF_INET6, blw->tgt.dom.c, &ipv6)) {
+		ip_lookup(blw, DNSBL_TYPE_IPV6, &ipv6, fg);
+		return;
+	}
+#endif
+
+#ifndef MXNS_DNSBL
+	/* check simple name and its addresses if we do not have a
+	 * resolver library */
+	name_ip_lookup(blw, &blw->tgt.dom, fg);
+
+#else /* MXNS_DNSBL */
+	/* If we have a resolver library, check first for the name itself */
+	if (!name_lookup(blw, &blw->tgt.dom, fg))
+		return;
+
+	/* Look for name servers in the authority section after asking for
+	 * A RRs.  You cannot rely on the bad guys' DNS servers to answer an
+	 * ANY request */
+	if (have_ipv4_dnsbl && !rr_lookup(blw, blw->tgt.dom.c, fg, T_A))
+		return;
+#ifndef NO_IPV6
+	if (have_ipv6_dnsbl && !rr_lookup(blw, blw->tgt.dom.c, fg, T_AAAA))
+		return;
+#endif
+
+	/* Check MX servers if allowed by at least one DNS blacklist.
+	 * To ape the fall back to A RRs when MX RRs are missing, we need to
+	 * check A RR for evil.  However, we've already done that */
+	if (0 != ((fg & DNSBL_FG_URL) ? blw->unhit.url_mx
+		  : blw->unhit.mail_host_mx))
+		rr_lookup(blw, blw->tgt.dom.c, fg | DNSBL_FG_MX, T_MX);
+#endif /* MXNS_DNSBL */
+}
+
+
+
+#ifdef HAVE_HELPERS
+/* do some DNSBL work in a helper process */
+u_char					/* 1=try to send the response */
+dnsbl_work(const DNSBL_REQ *req, DNSBL_RESP *resp)
+{
+	DNSBL_WORK blw;
+	DNSBL_RESP_GROUP *rst;
+	DNSBL_GROUP *blg;
+	int gnum, i;
+
+	blw_clear(&blw);
+	blw.url_start = req->hdr.start;
+	blw.msg_us = MAX_MSG_SECS*DCC_US*2;
+	blw.url_us = req->hdr.avail_us;
+	blw.url_us_used = 0;
+	blw.id = req->hdr.id;
+	blw.dcc_ctxt = 0;
+	blw.log_ctxt = 0;
+
+	if (!is_helper) {
+		/* this must be the first job for this helper process */
+		is_helper = 1;
+		helper_str = " helper";
+		fix_url_secs();
+	}
+
+	blw.unhit = req->unhit;
+	for (i = 0, blg = blw.groups; i < MAX_DNSBL_GROUPS; ++i, ++blg)
+		blg->fgs = req->fgs[i];
+	switch (req->fg) {
+	case DNSBL_FG_CLIENT:		/* SMTP client IP address */
+		blw.tgt.addr = req->tgt.addr;
+		BUFCPY(blw.tgt.dom.c, req->tgt.dom.c);
+		dnsbl_client(&blw);
+		break;
+	case DNSBL_FG_MAIL_HOST:	/* envelope mail_from */
+	case DNSBL_FG_URL:		/* URL in body */
+		BUFCPY(blw.tgt.dom.c, req->tgt.dom.c);
+		dnsbl_name_host_url(&blw, req->fg);
+		break;
+	default:
+		dcc_logbad(EX_SOFTWARE, "%s DNSBL%s unknown type %d",
+			   req->hdr.id, helper_str, req->fg);
+	}
+
+	resp->unhit = blw.unhit;
+	for (gnum = 0, blg = blw.groups, rst = resp->groups;
+	     gnum < MAX_DNSBL_GROUPS;
+	     ++gnum, ++rst, ++blg) {
+		rst->fgs = blg->fgs;	/* copy hit & timeout flags */
+		if (0 == ((resp->unhit.all ^ req->unhit.all)
+			  & DNSBL_G2B(gnum))) {
+			/* no new hit here in the helper
+			 * or had a hit in the parent */
+			rst->bl_num = -1;
+			rst->probe.c[0] = '\0';
+			rst->result[0] = '\0';
+		} else {
+			rst->bl_num = blg->dnsbl->bl_num;
+			BUFCPY(rst->tgt.c, blg->tgt.c);
+			BUFCPY(rst->probe.c, blg->probe.c);
+			BUFCPY(rst->result, blg->result);
+		}
+	}
+
+	return 1;
+}
+
+
+
+/* ask a helper process to check for something in the DNS blacklists */
+static void
+use_helper(DNSBL_WORK *blw, DNSBL_FGS fg)
+{
+	DNSBL_REQ req;
+	DNSBL_RESP resp;
+	DNSBL_RESP_GROUP *rst;
+	DNSBL_GROUP *blg;
+	const DNSBL *dp;
+	int gnum, i;
+
+	memset(&req, 0, sizeof(req));
+
+	BUFCPY(req.hdr.id, blw->id);
+	req.unhit = blw->unhit;
+	for (gnum = 0, blg = blw->groups;
+	     gnum < MAX_DNSBL_GROUPS;
+	     ++gnum, ++blg)
+		req.fgs[gnum] = blg->fgs;
+	req.fg = fg;
+	switch (fg) {
+	case DNSBL_FG_CLIENT:
+		BUFCPY(req.tgt.dom.c, blw->tgt.dom.c);
+		req.tgt.addr = blw->tgt.addr;
+		break;
+	case DNSBL_FG_MAIL_HOST:
+	case DNSBL_FG_URL:
+		BUFCPY(req.tgt.dom.c, blw->tgt.dom.c);
+		break;
+	default:
+		dcc_logbad(EX_SOFTWARE, "unknown DNSBL type %d", fg);
+	}
+
+	if (!ask_helper(blw->dcc_ctxt, blw->log_ctxt, url_us,
+			&req.hdr, sizeof(req), &resp.hdr, sizeof(resp))) {
+		time_ck_post(blw, fg, 1);
+		msg_secs_fin(blw);
+		return;
+	}
+
+	blw->unhit = resp.unhit;
+	/* record hits and timeouts seen by the helper */
+	for (gnum = 0, blg = blw->groups, rst = resp.groups;
+	     gnum < MAX_DNSBL_GROUPS;
+	     ++gnum, ++rst, ++blg) {
+		blg->fgs = rst->fgs;
+		if (0 == ((resp.unhit.all ^ req.unhit.all) & DNSBL_G2B(gnum)))
+			continue;
+
+		/* This group has a new hit.
+		 * Discover the right DNSBL so we can use the right
+		 * template for the SMTP status message */
+		for (i = rst->bl_num, dp = dnsbls; i > 1; --i, dp = dp->fwd) {
+			if (!dp)
+				dcc_logbad(EX_SOFTWARE,
+					   "bad helper DNSBL #%d",
+					   rst->bl_num);
+		}
+		blg->dnsbl = dp;
+		BUFCPY(blg->tgt.c, rst->tgt.c);
+		BUFCPY(blg->probe.c, rst->probe.c);
+		BUFCPY(blg->result, rst->result);
+		blg->btype = type_str(blg->btype_buf, sizeof(blg->btype_buf),
+				      blw, blg->fgs);
+	}
+
+	msg_secs_fin(blw);
+}
+#endif /* HAVE_HELPERS */
+
+
+void
+dcc_client_dnsbl(DNSBL_WORK *blw, const struct in6_addr *addr, const char *name)
+{
+	/* nothing to do if no client DNS blacklists have been configured */
+	if (!blw || !blw->unhit.client)
+		return;
+
+	/* or if we have already spent too much time checking blacklists */
+	if (!msg_secs_start(blw, DNSBL_FG_CLIENT))
+		return;
+
+	BUFCPY(blw->tgt.dom.c, name);
+	blw->tgt.addr = *addr;
+
+#ifdef HAVE_HELPERS
+	if (have_helpers) {
+		use_helper(blw, DNSBL_FG_CLIENT);
+		return;
+	}
+#endif
+
+	dnsbl_client(blw);
+	msg_secs_fin(blw);
+}
+
+
+
+void
+dcc_mail_host_dnsbl(DNSBL_WORK *blw, const char *host)
+{
+	/* nothing to do if no DNS blacklists have been configured
+	 * or we do not have an envelope Mail_From host name
+	 * or no recipients care about mail_host hits */
+	if (!blw || !blw->unhit.mail_host
+	    || !host || *host == '\0')
+		return;
+
+	/* or no still active DNSBLs care about the Mail_From host name */
+
+	/* or if we have already spent too much time checking blacklists */
+	if (!msg_secs_start(blw, DNSBL_FG_MAIL_HOST))
+		return;
+
+	BUFCPY(blw->tgt.dom.c, host);
+
+#ifdef HAVE_HELPERS
+	if (have_helpers) {
+		use_helper(blw, DNSBL_FG_MAIL_HOST);
+		return;
+	}
+#endif
+
+	dnsbl_name_host_url(blw, DNSBL_FG_MAIL_HOST);
+	msg_secs_fin(blw);
+}
+
+
+
+void
+url_dnsbl(DNSBL_WORK *blw)
+{
+	int i;
+
+	/* nothing to do if no body URL DNSBLs have been configured
+	 * or if we already have hits for all DNSBLs */
+	if (!blw || !blw->unhit.url)
+		return;
+
+	/* or if we have already spent too much time checking blacklists */
+	if (!msg_secs_start(blw, DNSBL_FG_URL))
+		return;
+
+	/* avoid checking the same names */
+	for (i = 0; i < DIM(blw->tgt_cache); ++i) {
+		if (!strcmp(blw->tgt.dom.c, blw->tgt_cache[i].dom.c)) {
+			if (helper.debug > 1)
+				dnsbl_log(blw, 0, DNSBL_FG_URL,
+					  "tgt_cache hit");
+			blw->tgt_cache_pos = i;
+			return;
+		}
+	}
+	BUFCPY(blw->tgt_cache[blw->tgt_cache_pos].dom.c, blw->tgt.dom.c);
+	blw->tgt_cache_pos = (blw->tgt_cache_pos + 1) % DIM(blw->tgt_cache);
+
+#ifdef HAVE_HELPERS
+	if (have_helpers) {
+		use_helper(blw, DNSBL_FG_URL);
+		return;
+	}
+#endif
+
+	dnsbl_name_host_url(blw, DNSBL_FG_URL);
+	msg_secs_fin(blw);
+}