view dcclib/ask.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 source

/* Distributed Checksum Clearinghouse
 *
 * ask about a batch of checksums
 *
 * 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.146 $Revision$
 */

#include "dcc_ck.h"
#include "dcc_heap_debug.h"
#include "dcc_xhdr.h"

static DCC_CKSUM_THOLDS dcc_tholds_log;
DCC_CKSUM_THOLDS dcc_tholds_rej;
static u_char dcc_honor_nospam[DCC_DIM_CKS];

static u_char trim_grey_ip_addr;	/* remove IP address from grey triple */
static struct in6_addr grey_ip_mask;

static void honor_cnt(const DCC_GOT_CKS *cks, u_int *, DCC_CK_TYPES, DCC_TGTS);



#ifdef DCC_PKT_VERSION5
/* figure old server's target count before our latest report */
static DCC_TGTS				/* return corrected current count */
save_p_tgts(DCC_GOT_SUM *g,		/* put previous count in g->tgts */
	    DCC_OPS op,
	    const DCC_TGTS local_tgts,	/* real local target count */
	    const DCC_TGTS gross_tgts,	/* local count adjusted by blacklist */
	    DCC_TGTS c_tgts)		/* what the old DCC server said */
{
	DCC_CK_TYPES type = g->type;

	if (op == DCC_OP_QUERY) {
		/* if we switched servers and converted a report
		 * to a query, then guess the total that the
		 * server would have produced for a report
		 * instead of the query we sent.
		 *
		 * Assume the server is not running with -K.
		 * If the server's current value is 0 for a body checksum
		 * then assume the report we sent to the other server has not
		 * been flooded.
		 * Assume other checksums will always be zero/unknown. */
		if (DB_GLOBAL_NOKEEP(0, type))
			return 0;

		/* Assume the current value is really the previous value
		 * because flooding has not happened */
		g->tgts = c_tgts;

		if (c_tgts < DCC_TGTS_TOO_MANY
		    && DCC_CK_IS_BODY(type)) {
			c_tgts += local_tgts;
			if (c_tgts > DCC_TGTS_TOO_MANY)
				c_tgts = DCC_TGTS_TOO_MANY;
		}
		return c_tgts;

	} else if (c_tgts >= gross_tgts
		   && gross_tgts < DCC_TGTS_TOO_MANY) {
		/* if possible infer server's value before our report */
		if (c_tgts >= DCC_TGTS_TOO_MANY)
			g->tgts = c_tgts;
		else
			g->tgts = c_tgts - gross_tgts;
	}

	return c_tgts;
}



#endif /* DCC_PKT_VERSION5 */
int					/* 1=ok, 0=no answer, -1=fatal */
ask_dcc(DCC_EMSG emsg,
	DCC_CLNT_CTXT *ctxt,
	DCC_CLNT_FGS clnt_fgs,		/* DCC_CLNT_FG_* */
	DCC_HEADER_BUF *hdr,		/* put results here */
	DCC_GOT_CKS *cks,		/*	and here */
	ASK_ST *ask_stp,		/*	and here */
	u_char spam,			/* spam==0 && local_tgts==0 --> query */
	DCC_TGTS local_tgts)		/* report these targets to DCC server */
{
	union {
	    DCC_HDR	hdr;
	    DCC_REPORT	r;
	} rpt;
	DCC_OP_RESP resp;
	DCC_OPS op;
	DCC_CK *ck;
	DCC_GOT_SUM *g;
	DCC_TGTS gross_tgts;
	DCC_TGTS c_tgts;		/* server's current, total count */
	DCC_CKS_WTGTS hdr_tgts;		/* values for X-DCC header */
	DCC_CK_TYPES type;
	DCC_SRVR_ID srvr_id;
	int pkt_len, recv_len, exp_len;
	int num_cks, ck_num, result;

	memset(hdr_tgts, 0, sizeof(hdr_tgts));

	/* prepare a report for the nearest DCC server */
	if (local_tgts == 0 && !spam) {
		/* because of greylisting, we can have a target count of 0
		 * but need to report spam discovered by a DNSBL */
		op = DCC_OP_QUERY;
		gross_tgts = 0;
		rpt.r.tgts = 0;
	} else {
		op = DCC_OP_REPORT;
		if (local_tgts == DCC_TGTS_TOO_MANY
		    || local_tgts == 0) {
			spam = 1;
			local_tgts = 1;
		}
		if (spam) {
			*ask_stp |= (ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT);
			gross_tgts = DCC_TGTS_TOO_MANY;
			rpt.r.tgts = htonl(local_tgts | DCC_TGTS_SPAM);
		} else {
			gross_tgts = local_tgts;
			rpt.r.tgts = htonl(local_tgts);
		}
	}

	ck = rpt.r.cks;
	num_cks = 0;
	for (g = cks->sums; g <= &cks->sums[DCC_CK_TYPE_LAST]; ++g) {
		/* never tell the DCC server about some headers */
		if (!g->rpt2srvr)
			continue;
		ck->len = sizeof(*ck);
		ck->type = g->type;
		memcpy(ck->sum, g->sum, sizeof(ck->sum));
		++ck;
		++num_cks;
	}
	if (num_cks == 0) {
		/* pretend we always have at least a basic body checksum
		 * guess the DCC would have answered 0 */
		xhdr_init(hdr, 0);
		xhdr_add_ck(hdr, DCC_CK_BODY, gross_tgts);
		honor_cnt(cks, ask_stp, DCC_CK_BODY, local_tgts);
		return 1;
	}

	/* send the report and see what the DCC has to say */
	pkt_len = (sizeof(rpt.r) - sizeof(rpt.r.cks)
		   + num_cks * sizeof(rpt.r.cks[0]));
	result = dcc_clnt_op(emsg, ctxt, clnt_fgs, 0, &srvr_id, 0,
			     &rpt.hdr, pkt_len, op, &resp, sizeof(resp));

	/* try a query to different server if the first failed
	 * but a second was found */
	if (!result && srvr_id != DCC_ID_INVALID) {
		if (dcc_clnt_debug) {
			if (emsg && *emsg != '\0') {
				dcc_trace_msg("retry with different server"
					      " after: %s", emsg);
				*emsg = '\0';
			} else {
				dcc_trace_msg("retry with different server");
			}
		}
		op = DCC_OP_QUERY;
		result = dcc_clnt_op(emsg, ctxt, clnt_fgs | DCC_CLNT_FG_RETRY,
				     0, &srvr_id, 0,
				     &rpt.hdr, pkt_len,
				     op, &resp, sizeof(resp));
	}
	if (!result) {
		*ask_stp |= ASK_ST_LOGIT;
	} else {
		/* forget about it if the DCC server responded too strangely */
		recv_len = ntohs(resp.hdr.len);
#ifdef DCC_PKT_VERSION5
		if (resp.hdr.pkt_vers <= DCC_PKT_VERSION5)
			exp_len = (sizeof(resp.ans5) - sizeof(resp.ans5.b)
				   + num_cks*sizeof(DCC_TGTS));
		else
#endif
			exp_len = (sizeof(resp.ans) - sizeof(resp.ans.b)
				   + num_cks*sizeof(resp.ans.b[0]));
		if (recv_len != exp_len) {
			dcc_pemsg(EX_UNAVAILABLE, emsg,
				  "DCC %s: answered with %d instead of %d bytes",
				  dcc_srvr_nm(0), recv_len, exp_len);
			*ask_stp |= ASK_ST_LOGIT;
			result = -1;
		}
	}

	/* check the server's response to see if we have spam */
	ck_num = 0;
	for (g = cks->sums; g <= &cks->sums[DCC_CK_TYPE_LAST]; ++g) {
		if (!g->rpt2srvr) {
			/* pretend we always have a basic body checksum */
			if (g == &cks->sums[DCC_CK_BODY])
				honor_cnt(cks, ask_stp,
					  DCC_CK_BODY, local_tgts);
			continue;
		}
		type = g->type;		/* g->type is valid only if rpt2srvr */

		if (result <= 0) {
			c_tgts = (DCC_CK_IS_BODY(type)) ? gross_tgts : 0;

#ifdef DCC_PKT_VERSION5
		} else if (resp.hdr.pkt_vers <= DCC_PKT_VERSION5) {
			c_tgts = save_p_tgts(g, op,
					     local_tgts, gross_tgts,
					     ntohl(resp.ans5.b[ck_num]));
		} else {
#endif /* DCC_PKT_VERSION5 */
			/* server's total before our report */
			g->tgts = ntohl(resp.ans.b[ck_num].p);
			/* new total */
			c_tgts = ntohl(resp.ans.b[ck_num].c);
#ifdef DCC_PKT_VERSION5
		}
#endif
		++ck_num;

		hdr_tgts[type] = c_tgts;

		/* notice DCC server's whitelist */
		if (dcc_honor_nospam[type]) {
			if (c_tgts == DCC_TGTS_OK) {
				*ask_stp |= ASK_ST_SRVR_NOTSPAM;

			} else if (c_tgts == DCC_TGTS_OK2) {
				/* if server says it is half ok,
				 * look for two halves */
				if (*ask_stp & ASK_ST_SRVR_OK2) {
					*ask_stp |= ASK_ST_SRVR_NOTSPAM;
				} else {
					*ask_stp |= ASK_ST_SRVR_OK2;
				}
			}
		}

		honor_cnt(cks, ask_stp, type, c_tgts);
	}

	/* honor server whitelist */
	if (*ask_stp & ASK_ST_SRVR_NOTSPAM)
		*ask_stp &= ~ASK_ST_SRVR_ISSPAM;

	/* generate the header line now that we have checked all of
	 * the counts against their thresholds and so know if we
	 * must add "bulk".  Add the header even if checking is turned off
	 * and we won't reject affected messages.  Say "many" for DNSBL
	 * or local blacklist spam even without an answer from the DCC server
	 * so that SpamAssassin gets the message. */
	xhdr_init(hdr, srvr_id);
	if (*ask_stp & ASK_ST_SRVR_ISSPAM) {
		xhdr_add_str(hdr, DCC_XHDR_BULK);
	} else if (*ask_stp & ASK_ST_CLNT_ISSPAM) {
		xhdr_add_str(hdr, DCC_XHDR_BULK);
		hdr_tgts[DCC_CK_BODY] = DCC_TGTS_TOO_MANY;
	} else if (*ask_stp & ASK_ST_REP_ISSPAM) {
		xhdr_add_str(hdr, DCC_XHDR_BULK_REP);
		hdr_tgts[DCC_CK_BODY] = DCC_TGTS_TOO_MANY;
	}

	for (g = cks->sums; g <= &cks->sums[DCC_CK_TYPE_LAST]; ++g) {
		if (!g->rpt2srvr) {
			/* pretend we always have a body checksum */
			if (g == &cks->sums[DCC_CK_BODY])
				xhdr_add_ck(hdr, DCC_CK_BODY,
					    hdr_tgts[DCC_CK_BODY]);
			continue;
		}
		/* Add interesing counts to the header.
		 * Body checksums are always interestig if we have them.
		 * Pretend we always have a basic body checksum. */
		type = g->type;
		if (DCC_CK_IS_BODY(type)) {
			xhdr_add_ck(hdr, type, hdr_tgts[type]);
			continue;
		}
		if (hdr_tgts[type] != 0)
			xhdr_add_ck(hdr, type, hdr_tgts[type]);
	}

	return result;
}



/* check message's checksums in whiteclnt for dccproc or dccsight */
u_char					/* 1=ok 0=something to complain about */
unthr_ask_white(DCC_EMSG emsg,
		ASK_ST *ask_stp,
		FLTR_SWS *swsp,
		const char *white_nm,
		DCC_GOT_CKS *cks,
		DCC_CKS_WTGTS wtgts)
{
	DCC_WHITE_LISTING listing;
	int retval;

	/* assume DNSBLs are on unless turned off, because there is no reason
	 * to use `dccproc -B` if you don't want to use them */
	*swsp |= FLTR_SW_DNSBL_M;

	/* fake whiteclnt if not specified */
	if (!white_nm) {
		dcc_merge_tholds(cks->tholds_rej, dcc_tholds_rej, 0);
		return 1;
	}

	/* don't filter if something is wrong with the file */
	if (!dcc_new_white_nm(emsg, &cmn_wf, white_nm)) {
		*ask_stp |= ASK_ST_WLIST_NOTSPAM | ASK_ST_LOGIT;
		return 0;
	}

	/* let whiteclnt file turn off the DCC and other filters */
	*swsp = wf2sws(*swsp, &cmn_wf);

	/* combine the command-line thresholds with the thresholds from
	 * from the common /var/dcc/whiteclnt file */
	dcc_merge_tholds(cks->tholds_rej, dcc_tholds_rej, cmn_wf.wtbl);

	retval = 1;
	switch (dcc_white_cks(emsg, &cmn_wf, cks, wtgts, &listing)) {
	case DCC_WHITE_OK:
	case DCC_WHITE_NOFILE:
		break;
	case DCC_WHITE_SILENT:
		*ask_stp |= ASK_ST_LOGIT;
		break;
	case DCC_WHITE_COMPLAIN:
	case DCC_WHITE_CONTINUE:
		retval = 0;
		*ask_stp |= ASK_ST_LOGIT;
		break;
	}

	switch (listing) {
	case DCC_WHITE_LISTED:
		/* do not send whitelisted checksums to DCC server */
		*ask_stp |= ASK_ST_WLIST_NOTSPAM;
		break;
	case DCC_WHITE_USE_DCC:
	case DCC_WHITE_UNLISTED:
		if (*swsp & FLTR_SW_TRAPS)
			*ask_stp |= (ASK_ST_CLNT_ISSPAM | ASK_ST_WLIST_ISSPAM
				     | ASK_ST_LOGIT);
		break;
	case DCC_WHITE_BLACK:
		*ask_stp |= (ASK_ST_WLIST_ISSPAM
			     | ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT);
		break;
	}

	if (*swsp & FLTR_SW_LOG_ALL)
		*ask_stp |= ASK_ST_LOGIT;

	return retval;
}



/* ask the DCC for dccproc or dccsight but not dccifd or dccm */
u_char					/* 1=ok 0=something to complain about */
unthr_ask_dcc(DCC_EMSG emsg,
	      DCC_CLNT_CTXT *ctxt,
	      DCC_HEADER_BUF *hdr,	/* put header here */
	      ASK_ST *ask_stp,		/* put state bites here */
	      DCC_GOT_CKS *cks,		/* these checksums */
	      u_char spam,		/* spam==0 && local_tgts==0 --> query */
	      DCC_TGTS local_tgts)	/* number of addressees */
{
	if (*ask_stp & ASK_ST_WLIST_NOTSPAM) {
		if (spam) {
			/* if dccproc says it is spam, then it is, even if
			 * the whiteclnt file says we cannot report it */
			*ask_stp |= (ASK_ST_CLNT_ISSPAM | ASK_ST_LOGIT);
			xhdr_init(hdr, 0);
			xhdr_add_ck(hdr, DCC_CK_BODY, DCC_TGTS_TOO_MANY);
		} else {
			xhdr_whitelist(hdr);
		}
		/* honor log threshold for whitelisted messages */
		dcc_honor_log_cnts(ask_stp, cks, local_tgts);
		return 1;

	} else {
		/* if allowed by whitelisting, report our checksums to the DCC
		 * and return with that result including setting logging */
		return (0 < ask_dcc(emsg, ctxt, DCC_CLNT_FG_NONE,
				    hdr, cks, ask_stp, spam,
				    local_tgts));
	}
}



/* parse -g for dccm and dccproc */
void
dcc_parse_honor(const char *arg0)
{
	const char *arg;
	DCC_CK_TYPES type, t2;
	int i;

	arg = arg0;
	if (!CLITCMP(arg, "not_") || !CLITCMP(arg, "not-")) {
		arg += LITZ("not_");
		i = 0;
	} else if (!CLITCMP(arg, "no_") || !CLITCMP(arg, "no-")) {
		arg += LITZ("no_");
		i = 0;
	} else {
		i = 1;
	}

	/* allow -g for ordinary checksums but not reputations or greylisting */
	type = dcc_str2type_thold(arg, -1);
	if (type == DCC_CK_INVALID) {
		dcc_error_msg("unrecognized checksum type in \"-g %s\"",
			      arg0);
		return;
	}
	for (t2 = DCC_CK_TYPE_FIRST; t2 <= DCC_CK_TYPE_LAST; ++t2) {
		if (t2 == type
		    || (type == SET_ALL_THOLDS && IS_ALL_CKSUM(t2))
		    || (type == SET_CMN_THOLDS && IS_CMN_CKSUM(t2)))
			dcc_honor_nospam[t2] = i;
	}
}



void
dcc_clear_tholds(void)
{
	DCC_CK_TYPES type;

	memset(dcc_honor_nospam, 0, sizeof(dcc_honor_nospam));
	dcc_honor_nospam[DCC_CK_IP] = 1;
	dcc_honor_nospam[DCC_CK_ENV_FROM] = 1;
	dcc_honor_nospam[DCC_CK_FROM] = 1;

	for (type = DCC_CK_TYPE_FIRST; type <= DCC_CK_TYPE_LAST; ++type) {
		dcc_tholds_log[type] = DCC_THOLD_UNSET;
		dcc_tholds_rej[type] = DCC_THOLD_UNSET;
	}
}



u_char					/* 1=merged from whiteclnt wtbl */
dcc_merge_tholds(DCC_CKSUM_THOLDS out,
		 const DCC_CKSUM_THOLDS in,
		 const DCC_WHITE_TBL *wtbl)
{
	DCC_CK_TYPES type;
	DCC_TGTS tgts;
	u_char result;

	if (in != out)
		memcpy(out, in, sizeof(DCC_CKSUM_THOLDS));
	if (!wtbl)
		return 0;

	result = 0;
	for (type = DCC_CK_TYPE_FIRST; type <= DCC_CK_TYPE_LAST; ++type) {
		tgts = wtbl->hdr.tholds_rej[type];
		if (tgts != DCC_THOLD_UNSET) {
			out[type] = tgts;
			result = 1;
		}
	}
	return result;
}



/* parse type,[log-thold,]rej-thold */
u_char					/* 1=need a log directory */
dcc_parse_tholds(const char *f,		/* "-c " or "-t " */
		 const char *arg)	/* optarg */
{
	DCC_CK_TYPES type;
	DCC_TGTS log_tgts, rej_tgts;
	char *thold_rej, *thold_log;
	u_char log_tgts_set, rej_tgts_set;

	thold_log = strchr(arg, ',');
	if (!thold_log) {
		dcc_error_msg("missing comma in \"%s%s\"", f, arg);
		return 0;
	}
	type = dcc_str2type_thold(arg, thold_log-arg);
	if (type == DCC_CK_INVALID) {
		dcc_error_msg("unrecognized checksum type in \"%s%s\"", f, arg);
		return 0;
	}

	thold_log = dcc_strdup(++thold_log);

	/* if there is only one threshold, take it as the spam threshold */
	thold_rej = strchr(thold_log, ',');
	if (!thold_rej) {
		thold_rej = thold_log;
		thold_log = 0;
	} else {
		*thold_rej++ = '\0';
	}

	log_tgts_set = log_tgts = 0;
	if (thold_log && *thold_log != '\0') {
		log_tgts = dcc_str2thold(type, thold_log);
		if (log_tgts == DCC_TGTS_INVALID)
			dcc_error_msg("unrecognized logging threshold"
				      " \"%s\" in \"%s%s\"",
				      thold_log, f, arg);
		else
			log_tgts_set = 1;
	}


	rej_tgts_set = rej_tgts = 0;
	if (!thold_rej || *thold_rej == '\0') {
		if (!thold_log || *thold_log == '\0')
			dcc_error_msg("no thresholds in \"%s%s\"", f, arg);
	} else {
		rej_tgts = dcc_str2thold(type, thold_rej);
		if (rej_tgts == DCC_TGTS_INVALID)
			dcc_error_msg("unrecognized rejection threshold"
				      " \"%s\" in \"%s%s\"",
				      thold_rej, f, arg);
		else
			rej_tgts_set = 1;
	}


	if (log_tgts_set || rej_tgts_set) {
		DCC_CK_TYPES t2;

		for (t2 = DCC_CK_TYPE_FIRST; t2 <= DCC_CK_TYPE_LAST; ++t2) {
			if (t2 == type
			    || (type == SET_ALL_THOLDS && IS_ALL_CKSUM(t2))
			    || (type == SET_CMN_THOLDS && IS_CMN_CKSUM(t2))) {
				if (log_tgts_set)
					dcc_tholds_log[t2] = log_tgts;
				if (rej_tgts_set)
					dcc_tholds_rej[t2] = rej_tgts;
			}
		}
	}

	dcc_free(thold_log);
	return log_tgts_set;
}



static void
honor_cnt(const DCC_GOT_CKS *cks,
	  ASK_ST *ask_stp,		/* previous flag bits */
	  DCC_CK_TYPES type,		/* which kind of checksum */
	  DCC_TGTS type_tgts)		/* total count for the checksum */
{
	if (type >= DIM(dcc_honor_nospam))
		return;

	/* reject and log spam */
	if (cks->tholds_rej[type] <= DCC_TGTS_TOO_MANY
	    && cks->tholds_rej[type] <= type_tgts
	    && type_tgts <= DCC_TGTS_TOO_MANY) {
		*ask_stp |= (ASK_ST_SRVR_ISSPAM | ASK_ST_LOGIT);
		return;
	}

	/* log messages that are bulkier than the log threshold */
	if (dcc_tholds_log[type] <= DCC_TGTS_TOO_MANY
	    && dcc_tholds_log[type] <= type_tgts)
		*ask_stp |= ASK_ST_LOGIT;
}



/* honor log threshold for local counts and white-/blacklists */
void
dcc_honor_log_cnts(ASK_ST *ask_stp,	/* previous flag bits */
		   const DCC_GOT_CKS *cks,  /* these server counts */
		   DCC_TGTS tgts)
{
	const DCC_GOT_SUM *g;
	DCC_CK_TYPES type;

	if (*ask_stp & ASK_ST_LOGIT)
		return;

	if (tgts == DCC_TGTS_TOO_MANY) {
		*ask_stp |= ASK_ST_LOGIT;
		return;
	}

	/* pretend we always have a body checksum for the log threshold */
	if (dcc_tholds_log[DCC_CK_BODY] <= DCC_TGTS_TOO_MANY
	    && dcc_tholds_log[DCC_CK_BODY] <= tgts) {
		*ask_stp |= ASK_ST_LOGIT;
		return;
	}

	for (g = cks->sums; g <= LAST(cks->sums); ++g) {
		type = g->type;
		if (type == DCC_CK_INVALID
		    || type == DCC_CK_ENV_TO)
			continue;
		if (dcc_tholds_log[type] > DCC_TGTS_TOO_MANY)
			continue;
		if (dcc_tholds_log[type] <= tgts) {
			*ask_stp |= ASK_ST_LOGIT;
			return;
		}
	}
}



/* compute switch settings from bits in a whiteclnt file */
FLTR_SWS
wf2sws(FLTR_SWS sws, const DCC_WF *wf)
{
	static time_t complained;
	time_t now;
	DCC_PATH abs_nm;
	int i;

	if (!grey_on
	    && (wf->wtbl_flags & (DCC_WHITE_FG_GREY_ON
				  | DCC_WHITE_FG_GREY_LOG_ON))
	    && (now = time(0)) > complained+24*60*60) {
		complained = now;
		dcc_error_msg("%s wants greylisting"
			      " but it is turned off",
			      fnm2abs_err(abs_nm, wf->ascii_nm));
	}

	/* compute switch values from whiteclnt bits */

	if (wf->wtbl_flags & DCC_WHITE_FG_NO_DISCARD)
		sws |= FLTR_SW_NO_DISCARD;
	else if (wf->wtbl_flags & DCC_WHITE_FG_DISCARD_OK)
		sws &= ~FLTR_SW_NO_DISCARD;

	if ((wf->wtbl_flags & DCC_WHITE_FG_DCC_OFF))
		sws |= FLTR_SW_DCC_OFF;
	else if (wf->wtbl_flags & DCC_WHITE_FG_DCC_ON)
		sws &= ~FLTR_SW_DCC_OFF;

	if (grey_on && (wf->wtbl_flags & DCC_WHITE_FG_GREY_ON)) {
		sws &= ~FLTR_SW_GREY_OFF;
	} else if (!grey_on || (wf->wtbl_flags & DCC_WHITE_FG_GREY_OFF)) {
		sws |= FLTR_SW_GREY_OFF;
	}

	if (wf->wtbl_flags & DCC_WHITE_FG_LOG_ALL) {
		sws |= FLTR_SW_LOG_ALL;
	} else if (wf->wtbl_flags & DCC_WHITE_FG_LOG_NORMAL) {
		sws &= ~FLTR_SW_LOG_ALL;
	}

	if (wf->wtbl_flags & DCC_WHITE_FG_GREY_LOG_ON) {
		sws &= ~FLTR_SW_GREY_LOG_OFF;
	} else if (wf->wtbl_flags & DCC_WHITE_FG_GREY_LOG_OFF) {
		sws |= FLTR_SW_GREY_LOG_OFF;
	}

	if (wf->wtbl_flags & DCC_WHITE_FG_LOG_M) {
		sws |= FLTR_SW_LOG_M;
	} else if (wf->wtbl_flags & DCC_WHITE_FG_LOG_H) {
		sws |= FLTR_SW_LOG_H;
	} else if (wf->wtbl_flags & DCC_WHITE_FG_LOG_D) {
		sws |= FLTR_SW_LOG_D;
	}

	if (wf->wtbl_flags & DCC_WHITE_FG_MTA_FIRST) {
		sws |= FLTR_SW_MTA_FIRST;
	} else if (wf->wtbl_flags & DCC_WHITE_FG_MTA_LAST) {
		sws &= ~FLTR_SW_MTA_FIRST;
	}

	for (i = 0; i < MAX_DNSBL_GROUPS; ++i) {
		if ((wf->wtbl_flags & DCC_WHITE_FG_DNSBL_ON(i))
		    && dnsbls) {
			sws |= FLTR_SW_DNSBL(i);
		} else if (wf->wtbl_flags & DCC_WHITE_FG_DNSBL_OFF(i)) {
			sws &= ~FLTR_SW_DNSBL(i);
		}
	}

	if (wf->wtbl_flags & DCC_WHITE_FG_TRAP_ACC) {
		sws |= FLTR_SW_TRAP_ACC;
	} else if (wf->wtbl_flags & DCC_WHITE_FG_TRAP_REJ) {
		sws |= FLTR_SW_TRAP_REJ;
	}

	return sws | FLTR_SW_SET;
}



#define LOG_ASK_ST_BLEN	    160
#define LOG_ASK_ST_OFF	    "(off)"
#define LOG_ASK_ST_OVF	    " ...\n\n"
static int
log_ask_st_sub(char *buf, int blen,
	       const char *s, int slen,
	       u_char off)
{
	int dlen, tlen;

	/* quit if no room at all in the log */
	if (blen >= LOG_ASK_ST_BLEN)
		return LOG_ASK_ST_BLEN;

	/* quit if nothing to say */
	if (!s || !slen)
		return blen;

	dlen = LOG_ASK_ST_BLEN - blen;
	tlen = LITZ(LOG_ASK_ST_OVF)+2+slen;
	if (off)			/* notice if we need to say "(off)" */
		tlen += LITZ(LOG_ASK_ST_OFF);
	if (dlen <= tlen) {
		/* show truncation of the message with "..." */
		memcpy(&buf[blen], LOG_ASK_ST_OVF, LITZ(LOG_ASK_ST_OVF));
		blen += LITZ(LOG_ASK_ST_OVF);
		if (blen < LOG_ASK_ST_BLEN)
			memset(&buf[blen], ' ', LOG_ASK_ST_BLEN-blen);
		return LOG_ASK_ST_BLEN;
	}

	if (blen > 0 && buf[blen-1] != '\n') {
		buf[blen++] = ' ';
		buf[blen++] = ' ';
	}
	memcpy(buf+blen, s, slen);
	blen += slen;
	if (off) {
		memcpy(buf+blen, LOG_ASK_ST_OFF, LITZ(LOG_ASK_ST_OFF));
		blen += LITZ(LOG_ASK_ST_OFF);
	}
	return blen;
}



/* generate log file line of results */
void
log_ask_st(LOG_WRITE_FNC fnc, void *cp, ASK_ST ask_st, FLTR_SWS sws,
	   u_char log_type,		/* 0="" 1="per-user" 2="global" */
	   const DCC_HEADER_BUF *hdr)
{
	char buf[LOG_ASK_ST_BLEN+3];
	char dnsbl_buf[24];
	int blen, len, i;
#define S(str,off) (blen = log_ask_st_sub(buf, blen, str, LITZ(str), off))
#define S0(bit,off,str) if (ask_st & bit) S(str,off)
#define S1(bit,s1off,str) S0(bit,(log_type != 2 && (s1off)),str)
#define S2(bit,str) S0(bit,0,str)

	blen = 0;
	S2(ASK_ST_QUERY, "query");

	/* the CGI scripts want to know why */
	if (sws & FLTR_SW_MTA_FIRST) {
		S2(ASK_ST_MTA_ISSPAM,	"MTA"DCC_XHDR_ISSPAM);
		S2(ASK_ST_MTA_NOTSPAM,	"MTA"DCC_XHDR_ISOK);
	}

	S2(ASK_ST_WLIST_NOTSPAM,	    "wlist"DCC_XHDR_ISOK);
	if (log_type != 2 && !(ask_st & ASK_ST_WLIST_NOTSPAM))
		S2(ASK_ST_WLIST_ISSPAM,	    "wlist"DCC_XHDR_ISSPAM);

	S1(ASK_ST_SRVR_ISSPAM,	(sws & FLTR_SW_DCC_OFF), "DCC"DCC_XHDR_ISSPAM);
	S1(ASK_ST_SRVR_NOTSPAM,	(sws & FLTR_SW_DCC_OFF), "DCC"DCC_XHDR_ISOK);
	S1(ASK_ST_REP_ISSPAM,  !(sws & FLTR_SW_REP_ON),	"Rep"DCC_XHDR_ISSPAM);

	for (i = 0; i < MAX_DNSBL_GROUPS; ++i) {
		if (ask_st & ASK_ST_DNSBL_HIT(i)) {
			if (have_dnsbl_groups)
				len = snprintf(dnsbl_buf, sizeof(dnsbl_buf),
					       "DNSBL%d"DCC_XHDR_ISSPAM, i+1);
			else
				len = snprintf(dnsbl_buf, sizeof(dnsbl_buf),
					       "DNSBL"DCC_XHDR_ISSPAM);
			/* log "DNSBLx-->spam" or "DNSBLx-->spam(off)" */
			blen = log_ask_st_sub(buf, blen,
					      dnsbl_buf, len,
					      log_type != 2
					      && !(sws & FLTR_SW_DNSBL(i)));
		} else if (ask_st & ASK_ST_DNSBL_TIMEO(i)) {
			if (have_dnsbl_groups)
				len = snprintf(dnsbl_buf, sizeof(dnsbl_buf),
					       "DNSBL%d(timeout)", i+1);
			else
				len = snprintf(dnsbl_buf, sizeof(dnsbl_buf),
					       "DNSBL(timeout)");
			blen = log_ask_st_sub(buf, blen,
					      dnsbl_buf, len, 0);
		}
	}

	if (!(sws & FLTR_SW_MTA_FIRST)) {
		S2(ASK_ST_MTA_ISSPAM,	"MTA"DCC_XHDR_ISSPAM);
		S2(ASK_ST_MTA_NOTSPAM,  "MTA"DCC_XHDR_ISOK);
	}
	blen = log_ask_st_sub(buf, blen, dcc_progname, dcc_progname_len, 0);
	if (log_type == 1) {
		blen = log_ask_st_sub(buf, blen,
				      "per-user", LITZ("per-user"), 0);
	} else if (log_type == 2) {
		blen = log_ask_st_sub(buf, blen,
				      "global", LITZ("global"), 0);
	}
	blen = log_ask_st_sub(buf, blen, "\n\n", 2, 0);
	fnc(cp, buf, blen);

	if (hdr->used != 0)
		xhdr_write(fnc, cp, hdr->buf, hdr->used, 0);
#undef S
#undef S0
#undef S1
#undef S2
}



/* parse -G options for DCC clients */
u_char					/* 0=bad */
dcc_parse_client_grey(const char *arg)
{
	int bits;
	const char *p;

	while (*arg != '\0') {
		if (dcc_ck_word_comma(&arg, "on")) {
			grey_on = 1;
			continue;
		}
		if (dcc_ck_word_comma(&arg, "off")) {
			grey_on = 0;
			continue;
		}
		if (dcc_ck_word_comma(&arg, "query")) {
			grey_query_only = 1;
			continue;
		}
		if (dcc_ck_word_comma(&arg, "noIP")) {
			grey_on = 1;
			trim_grey_ip_addr = 1;
			memset(&grey_ip_mask, 0, sizeof(grey_ip_mask));
			continue;
		}
		if (!CLITCMP(arg, "IPmask/")) {
			bits = 0;
			for (p = arg+LITZ("IPmask/");
			     *p >= '0' && *p <= '9';
			     ++p)
				bits = bits*10 + *p - '0';
			if (bits > 0 && bits < 128
			    && (*p == '\0' || *p == ',')) {
				arg = p;
				if (*p == ',')
					++arg;
				grey_on = 1;
				trim_grey_ip_addr = 1;
				/* assume giant blocks are really IPv4 */
				if (bits <= 32)
					bits += 128-32;
				dcc_bits2mask(&grey_ip_mask, bits);
				continue;
			}
		}
		return 0;
	}
	return 1;
}



/* sanity check the DCC server's answer */
u_char
dcc_ck_grey_answer(DCC_EMSG emsg, const DCC_OP_RESP *resp)
{
	int recv_len;

	recv_len = ntohs(resp->hdr.len);
	if (resp->hdr.op != DCC_OP_ANSWER) {
		dcc_pemsg(EX_UNAVAILABLE, emsg, "DCC %s: %s %*s",
			  dcc_srvr_nm(1),
			  dcc_hdr_op2str(0, 0, &resp->hdr),
			  (resp->hdr.op == DCC_OP_ERROR
			   ? (recv_len - (ISZ(resp->error)
					  - ISZ(resp->error.msg)))
			   : 0),
			  resp->error.msg);
		return 0;
	}

	if (recv_len != sizeof(DCC_GREY_ANSWER)) {
		dcc_pemsg(EX_UNAVAILABLE, emsg,
			  "greylist server %s answered with %d instead of"
			  " %d bytes",
			  dcc_srvr_nm(1), recv_len, ISZ(DCC_GREY_ANSWER));
		return 0;
	}

	return 1;
}



ASK_GREY_RESULT
ask_grey(DCC_EMSG emsg,
	 DCC_CLNT_CTXT *ctxt,
	 DCC_OPS op,			/* DCC_OP_GREY_{REPORT,QUERY,WHITE} */
	 DCC_SUM msg_sum,		/* put msg+sender+target cksum here */
	 DCC_SUM triple_sum,		/* put greylist triple checksum here */
	 const DCC_GOT_CKS *cks,
	 const DCC_SUM env_to_sum,
	 DCC_TGTS *pembargo_num,
	 DCC_TGTS *pearly_tgts,		/* ++ report to DCC even if embargoed */
	 DCC_TGTS *plate_tgts)		/* ++ don't report to DCC */
{
	MD5_CTX ctx;
	DCC_REPORT rpt;
	DCC_OP_RESP resp;
	DCC_CK *ck;
	DCC_CK_TYPES type;
	const DCC_GOT_SUM *g;
	DCC_TGTS result_tgts;
	int num_cks;

	if (cks->sums[DCC_CK_IP].type != DCC_CK_IP) {
		dcc_pemsg(EX_UNAVAILABLE, emsg,
			  "IP address not available for greylisting");
		memset(triple_sum, 0, sizeof(*triple_sum));
		memset(msg_sum, 0, sizeof(*msg_sum));
		return ASK_GREY_FAIL;
	}
	if (cks->sums[DCC_CK_ENV_FROM].type != DCC_CK_ENV_FROM) {
		dcc_pemsg(EX_UNAVAILABLE, emsg,
			  "env_From not available for greylisting");
		memset(triple_sum, 0, sizeof(*triple_sum));
		memset(msg_sum, 0, sizeof(*msg_sum));
		return ASK_GREY_FAIL;
	}

	/* Check the common checksums for whitelisting at the greylist server.
	 * This assumes DCC_CK_GREY_TRIPLE > DCC_CK_GREY_MSG > other types */
	ck = rpt.cks;
	num_cks = 0;
	for (type = 0, g = cks->sums;
	     type <= DCC_CK_TYPE_LAST;
	     ++type, ++g) {
		/* greylisting needs a body checksum, even if
		 * it is the fake checksum for a missing body */
		if (!g->rpt2srvr && type != DCC_CK_BODY)
			continue;
		ck->type = type;
		ck->len = sizeof(*ck);
		memcpy(ck->sum, g->sum, sizeof(ck->sum));
		++ck;
		++num_cks;
	}

	/* include in the request the grey message checksum as the checksum
	 * of the body, the env_From sender, and env_To target checksums */
	MD5Init(&ctx);
	MD5Update(&ctx, cks->sums[DCC_CK_BODY].sum, sizeof(DCC_SUM));
	MD5Update(&ctx, cks->sums[DCC_CK_ENV_FROM].sum, sizeof(DCC_SUM));
	MD5Update(&ctx, env_to_sum, sizeof(DCC_SUM));
	MD5Final(msg_sum, &ctx);
	ck->type = DCC_CK_GREY_MSG;
	ck->len = sizeof(*ck);
	memcpy(ck->sum, msg_sum, sizeof(ck->sum));
	++ck;
	++num_cks;

	/* include the triple checksum of the sender, the sender's IP
	 * address, and the target */
	MD5Init(&ctx);
	if (trim_grey_ip_addr) {
		struct in6_addr addr;
		DCC_SUM sum;
		int wno;

		for (wno = 0; wno < 4; ++wno) {
			addr.s6_addr32[wno] = (cks->ip_addr.s6_addr32[wno]
					       & grey_ip_mask.s6_addr32[wno]);
		}
		dcc_ck_ipv6(sum, &addr);
		MD5Update(&ctx, sum, sizeof(DCC_SUM));
	} else {
		MD5Update(&ctx, cks->sums[DCC_CK_IP].sum, sizeof(DCC_SUM));
	}
	MD5Update(&ctx, cks->sums[DCC_CK_ENV_FROM].sum, sizeof(DCC_SUM));
	MD5Update(&ctx, env_to_sum, sizeof(DCC_SUM));
	MD5Final(triple_sum, &ctx);
	ck->type = DCC_CK_GREY3;
	ck->len = sizeof(*ck);
	memcpy(ck->sum, triple_sum, sizeof(ck->sum));
	++num_cks;

	if (!dcc_clnt_op(emsg, ctxt, DCC_CLNT_FG_GREY, 0, 0, 0,
			 &rpt.hdr, (sizeof(rpt) - sizeof(rpt.cks)
				    + num_cks*sizeof(rpt.cks[0])),
			 op, &resp, sizeof(resp))) {
		return ASK_GREY_FAIL;
	}

	if (!dcc_ck_grey_answer(emsg, &resp))
		return ASK_GREY_FAIL;

	/* see what the greylist server had to say */
	result_tgts = ntohl(resp.gans.triple);
	switch (result_tgts) {
	case DCC_TGTS_OK:		/* embargo ended just now */
		/* if we have previously included this target in a count of
		 * targets sent to the DCC, then do not include it now */
		if (resp.gans.msg != 0 && plate_tgts)
			++*plate_tgts;
		if (pembargo_num)
			*pembargo_num = 0;
		return ASK_GREY_EMBARGO_END;

	case DCC_TGTS_TOO_MANY:		/* no current embargo */
		if (pembargo_num)
			*pembargo_num = 0;
		return ((resp.gans.msg != 0)
			? ASK_GREY_EMBARGO_END
			: ASK_GREY_PASS);

	case DCC_TGTS_GREY_WHITE:	/* whitelisted for greylisting */
		if (pembargo_num)
			*pembargo_num = 0;
		return ASK_GREY_WHITE;

	default:			/* embargoed */
		/* if this is a brand new embargo,
		 * then count this target in the DCC report */
		if (resp.gans.msg == 0 && pearly_tgts)
			++*pearly_tgts;
		if (pembargo_num)
			*pembargo_num = result_tgts+1;
		return ASK_GREY_EMBARGO;
	}
}