view dcclib/dnsbl.c @ 3:b689077d4918

Ignore old patches
author Peter Gervai <grin@grin.hu>
date Tue, 10 Mar 2009 14:31:24 +0100
parents c7f6b056b673
children
line wrap: on
line source

/* 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);
}