/* Distributed Checksum Clearinghouse
 *
 * ask about a batch of checksums
 *
 * Copyright (c) 2005 by Rhyolite Software
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE
 * 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.2.74-1.63 $Revision$
 */

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

#define SET_ALL_THOLDS	DCC_CK_INVALID
#define ALL_CKSUMS_STR	"ALL"
#define SET_CMN_THOLDS	(DCC_CK_TYPE_LAST+1)
#define CMN_CKSUMS_STR	"CMN"
#define IS_CMN_CKSUM(t)	((t) == DCC_CK_BODY	\
			 || (t) == DCC_CK_FUZ1	\
			 || (t) == DCC_CK_FUZ2)

static DCC_TGTS dcc_tholds_spam[DCC_DIM_CKS];
static DCC_TGTS dcc_tholds_log[DCC_DIM_CKS];
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 dcc_honor_cnt(u_int *, DCC_CK_TYPES, DCC_TGTS);


int					/* 1=ok, 0=no answer, -1=failed */
dcc_ask(DCC_EMSG emsg, DCC_CLNT_CTXT *ctxt,
	u_char flags,			/* DCC_CLNT_FG_* */
	DCC_HEADER_BUF *header,		/* put results here */
	DCC_GOT_CKS *cks,		/*	and here */
	u_int *honorp,			/*	and here */
	DCC_TGTS local_tgts)		/* report these targets to DCC server */
{
	/* report and response buffers */
	DCC_QUERY_REPORT rpt;
	union {
	    DCC_HDR	hdr;
	    DCC_QUERY_RESP r;
	    DCC_ERROR	error;
	} resp;
	DCC_OPS op;
	DCC_CK *ck;
	DCC_GOT_SUM *g;
	DCC_TGTS srvr_tgts;
	DCC_CKS_WTGTS hdr_tgts;
	u_int num_cks, recv_len, exp_len;
	DCC_CK_TYPES type;
	DCC_SRVR_ID srvr_id;
	int resp_num, i;

	/* prepare a report for the nearest DCC server */
	memset(&rpt, 0, sizeof(rpt));
	rpt.tgts = htonl(local_tgts);
	ck = rpt.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->rpt)
			continue;
		ck->type = g->type;
		ck->len = sizeof(*ck);
		memcpy(ck->sum, g->sum, sizeof(ck->sum));
		++ck;
		++num_cks;
	}

	/* send the report and see what the DCC has to say */
	op = (local_tgts == 0) ? DCC_OP_QUERY : DCC_OP_REPORT;
	if (num_cks != 0) {
		i = dcc_clnt_op(emsg, ctxt, flags, 0, &srvr_id,
				&rpt.hdr, (sizeof(rpt) - sizeof(rpt.cks)
					   + num_cks*sizeof(rpt.cks[0])),
				op, &resp.hdr, sizeof(resp), 0);
		/* try a query to different server if the first failed
		 * but a second was found */
		if (!i && srvr_id != DCC_ID_INVALID) {
			if (dcc_clnt_debug) {
				if (emsg && *emsg != '\0') {
					dcc_trace_msg("retry after: %s", emsg);
					*emsg = '\0';
				}
				dcc_trace_msg("retry with different server");
			}
			if (local_tgts != DCC_TGTS_TOO_MANY)
				op = DCC_OP_QUERY;
			i = dcc_clnt_op(emsg, ctxt, flags, 0, &srvr_id,
					&rpt.hdr,
					(sizeof(rpt) - sizeof(rpt.cks)
					 + num_cks*sizeof(rpt.cks[0])),
					op, &resp.hdr, sizeof(resp), 0);
		}
		if (!i) {
			*honorp |= DCC_HONOR_LOGIT;
			header->buf[0] = '\0';
			return 0;
		}

		/* forget about it if the DCC server responded strangely */
		recv_len = htons(resp.r.hdr.len);
		if (resp.hdr.op != DCC_OP_QUERY_RESP) {
			dcc_pemsg(EX_UNAVAILABLE, emsg, "DCC %s: %s %*s",
				  dcc_srvr_nm(),
				  dcc_hdr_op2str(&resp.hdr),
				  (resp.hdr.op == DCC_OP_ERROR
				   ? (recv_len - (ISZ(resp.error)
						  - ISZ(resp.error.msg)))
				   : 0),
				  resp.error.msg);
			*honorp |= DCC_HONOR_LOGIT;
			header->buf[0] = '\0';
			return -1;
		}
		exp_len = (sizeof(resp.r) - sizeof(resp.r.body.tgts)
			   + num_cks*sizeof(resp.r.body.tgts[0]));
		if (recv_len != exp_len) {
			dcc_pemsg(EX_UNAVAILABLE, emsg,
				  "DCC %s: answered with %d"
				  " instead of %d bytes",
				  dcc_srvr_nm(), recv_len, exp_len);
			*honorp |= DCC_HONOR_LOGIT;
			header->buf[0] = '\0';
			return -1;
		}
	}

	/* check the server's response to see if we have spam */
	resp_num = 0;
	for (g = cks->sums; g <= &cks->sums[DCC_CK_TYPE_LAST]; ++g) {
		if (!g->rpt) {
			/* pretend we always have a basic body checksum */
			if (g == &cks->sums[DCC_CK_BODY])
				dcc_honor_cnt(honorp, DCC_CK_BODY, 0);
			continue;
		}
		type = g->type;
		srvr_tgts = ntohl(resp.r.body.tgts[resp_num++]);

		/* if we switched servers and converted a report
		 * to a query, then guess the total that the
		 * server would have produced for a report.
		 * Assume the server is not running with -K and so
		 * its value would have changed from zero only
		 * for a body checksum */
		if (op == DCC_OP_QUERY
		    && local_tgts != 0
		    && (srvr_tgts != 0 || DCC_CK_IS_BODY(type))
		    && srvr_tgts < DCC_TGTS_TOO_MANY) {
			srvr_tgts += local_tgts;
			if (srvr_tgts > DCC_TGTS_TOO_MANY)
				srvr_tgts = DCC_TGTS_TOO_MANY;
		}
		hdr_tgts[type] = srvr_tgts;
		if (srvr_tgts >= local_tgts
		    && local_tgts != DCC_TGTS_TOO_MANY) {
			/* infer server's value before our report */
			if (srvr_tgts >= DCC_TGTS_TOO_MANY)
				g->tgts = srvr_tgts;
			else
				g->tgts = srvr_tgts - local_tgts;
			/* honor the total */
			dcc_honor_cnt(honorp, type, srvr_tgts);
		}
	}

	/* generate the header line now that we have checked all of
	 * the counts against their thresholds and so know if we
	 * must add "bulk". */
	dcc_header_init(header, srvr_id);
	if (*honorp & (DCC_HONOR_SRVR_ISSPAM
		       | DCC_HONOR_LOCAL_ISSPAM
		       | DCC_HONOR_MTA_ISSPAM))
		dcc_add_header(header, DCC_XHDR_BULK);

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

	return 1;
}



/* Check the local whitelist for dccproc or dccsight but not dccifd or dccm.
 * If the whitelist does not give an unambiguous answer and allows the
 *	the use of the DCC,,
 * ask the DCC server about the checksums of a message */
u_char					/* 1=ok 0=something to complain about */
dcc_white_ask(DCC_EMSG emsg,
	      DCC_WF *wf,
	      DCC_CLNT_CTXT *ctxt,
	      DCC_HEADER_BUF *header,	/* put header here */
	      u_int *honorp,		/* put state bites here */
	      const char *white_nm,	/* must name whitelist every time */
	      DCC_GOT_CKS *cks,		/* these checksums */
	      DCC_CKS_WTGTS wtgts,	/* whitelist targets */
	      DCC_TGTS local_tgts)	/* number of addressees */
{
	header->buf[0] = '\0';
	*honorp &= ~(DCC_HONOR_SRVR_OK2
		     | DCC_HONOR_SRVR_NOTSPAM
		     | DCC_HONOR_SRVR_ISSPAM
		     | DCC_HONOR_LOCAL_ISSPAM
		     | DCC_HONOR_LOCAL_NOTSPAM);

	if (wtgts)
		memset(wtgts, 0, sizeof(*wtgts));

	/* check whitelist
	 * don't filter if something is wrong with it */
	if (white_nm) {
		switch (dcc_white_cks(emsg, wf, cks, wtgts, white_nm, 0, 0)) {
		case DCC_WHITE_ERROR:
			*honorp |= DCC_HONOR_LOGIT;
			return 0;
		case DCC_WHITE_LISTED:
			*honorp |= DCC_HONOR_LOCAL_NOTSPAM;
			cks->flags &= ~DCC_CKS_HAVE_SUM;
			break;
		case DCC_WHITE_HALF_LISTED:
		case DCC_WHITE_UNLISTED:
			break;
		case DCC_WHITE_BLACK:
			*honorp |= (DCC_HONOR_LOGIT | DCC_HONOR_LOCAL_ISSPAM);
			local_tgts = DCC_TGTS_TOO_MANY;
			break;
		}
		if (wf->info_flags & DCC_WHITE_FG_LOG_ALL)
			*honorp |= DCC_HONOR_LOGIT;
		dcc_wf_unlock(wf);
	}

	/* pay attention to DNS blacklist if we do not already have an answer */
	if (cks->dnsbl
	    && cks->dnsbl->hit != DNSBL_HIT_NONE
	    && !(*honorp & (DCC_HONOR_SRVR_NOTSPAM
			    | DCC_HONOR_SRVR_ISSPAM
			    | DCC_HONOR_LOCAL_ISSPAM
			    | DCC_HONOR_LOCAL_NOTSPAM))) {
		*honorp |= (DCC_HONOR_LOGIT | DCC_HONOR_LOCAL_ISSPAM);
		local_tgts = DCC_TGTS_TOO_MANY;
	}


	/* if allowed by whitelisting, report our checksums to the DCC
	 * and return with that result including setting logging */
	if ((cks->flags & DCC_CKS_HAVE_SUM)
	    && !(wf->flags & DCC_WHITE_FG_DCC_OFF))
		return (0 < dcc_ask(emsg, ctxt, DCC_CLNT_FG_NONE,
				    header, cks, honorp, local_tgts));

	/* else honor log threshold for local counts and white-/blacklists */
	dcc_honor_log_cnts(honorp, cks, local_tgts);
	return 1;
}



void
dcc_init_tholds(void)
{
	DCC_TGTS *tgtsp;

	dcc_honor_nospam[DCC_CK_IP] = 1;
	dcc_honor_nospam[DCC_CK_ENV_FROM] = 1;
	dcc_honor_nospam[DCC_CK_FROM] = 1;
	for (tgtsp = dcc_tholds_spam; tgtsp <= LAST(dcc_tholds_spam); ++tgtsp)
		*tgtsp = DCC_TGTS_INVALID;
	for (tgtsp = dcc_tholds_log; tgtsp <= LAST(dcc_tholds_log); ++tgtsp)
		*tgtsp = DCC_TGTS_INVALID;
}



/* 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 (!CSTRCMP(arg, "not_") || !CSTRCMP(arg, "not-")) {
		arg += STRZ("not_");
		i = 0;
	} else if (!CSTRCMP(arg, "no_") || !CSTRCMP(arg, "no-")) {
		arg += STRZ("no_");
		i = 0;
	} else {
		i = 1;
	}

	type = dcc_str2type(arg);
	if (type == DCC_CK_INVALID
	    || type >= DIM(dcc_honor_nospam)) {
		if (!strcasecmp(arg, CMN_CKSUMS_STR)) {
			type = SET_CMN_THOLDS;
		} else if (!strcasecmp(arg, ALL_CKSUMS_STR)) {
			type = SET_ALL_THOLDS;
		} else {
			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
		    || (type == SET_CMN_THOLDS
			&& IS_CMN_CKSUM(t2)))
			dcc_honor_nospam[t2] = i;
	}
}



static void
set_tholds(DCC_TGTS tholds[DCC_DIM_CKS],
	   DCC_CK_TYPES type,		/* specified type, DCC_CK_INVALID=all,
					 * SET_CMN_THOLDS=default set */
	   DCC_TGTS tgts)
{
	DCC_CK_TYPES t2;

	for (t2 = DCC_CK_TYPE_FIRST; t2 <= DCC_CK_TYPE_LAST; ++t2) {
		if (t2 == type
		    || type == SET_ALL_THOLDS
		    || (type == SET_CMN_THOLDS
			&& IS_CMN_CKSUM(t2)))
			tholds[t2] = tgts;
	}
}



/* parse type,[log-thold,]rej-thold */
u_char					/* 1=need a log directory */
dcc_parse_tholds(char a,
		 const char *arg)   /* optarg */
{
	DCC_CK_TYPES type;
	DCC_TGTS log_tgts, spam_tgts;
	char *duparg, *thold_spam, *thold_log;
	u_char log_tgts_set, spam_tgts_set;

	duparg = dcc_strdup(arg);
	thold_log = strchr(duparg, ',');
	if (!thold_log) {
		dcc_error_msg("missing comma in \"-%c %s\"", a, arg);
		dcc_free(duparg);
		return 0;
	}
	*thold_log++ = '\0';

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

	if (!strcasecmp(duparg, ALL_CKSUMS_STR)) {
		type = SET_ALL_THOLDS;
	} else if (!strcasecmp(duparg, CMN_CKSUMS_STR)) {
		type = SET_CMN_THOLDS;
	} else {
		type = dcc_str2type(duparg);
		if (type == DCC_CK_INVALID) {
			dcc_error_msg("unrecognized checksum type"
				      " \"%s\" in \"-%c %s\"",
				      duparg, a, arg);
			dcc_free(duparg);
			return 0;
		}
	}

	log_tgts = DCC_TGTS_INVALID;
	log_tgts_set = 0;
	if (thold_log && *thold_log != '\0') {
		log_tgts = dcc_str2cnt(thold_log);
		if (log_tgts == DCC_TGTS_INVALID
		    && strcasecmp(thold_log, "never"))
			dcc_error_msg("unrecognized count \"%s\" in \"-%c %s\"",
				      thold_log, a, arg);
		else
			log_tgts_set = 1;
	}


	spam_tgts = DCC_TGTS_INVALID;
	spam_tgts_set = 0;
	if (!thold_spam || *thold_spam == '\0') {
		if (!thold_log || *thold_log == '\0')
			dcc_error_msg("no thresholds in \"-%c %s\"", a, arg);
	} else {
		spam_tgts = dcc_str2cnt(thold_spam);
		if (spam_tgts == DCC_TGTS_INVALID
		    && strcasecmp(thold_spam, "never"))
			dcc_error_msg("unrecognized count \"%s\" in \"-%c %s\"",
				      thold_spam, a, arg);
		else
			spam_tgts_set = 1;
	}

	if (log_tgts_set)
		set_tholds(dcc_tholds_log, type, log_tgts);
	if (spam_tgts_set)
		set_tholds(dcc_tholds_spam, type, spam_tgts);

	dcc_free(duparg);
	return log_tgts_set;
}



static void
dcc_honor_cnt(u_int *honorp,		/* previous flag bits */
	      DCC_CK_TYPES type,	/* which kind of checksum */
	      DCC_TGTS srvr_tgts)	/* total count for the checksum */
{
	if (type >= DIM(dcc_honor_nospam))
		return;

	/* decide whether to notice DCC server's whitelist */
	if (dcc_honor_nospam[type]) {
		if (srvr_tgts == DCC_TGTS_OK) {
			/* if server says it is not spam then it's not */
			*honorp |= DCC_HONOR_SRVR_NOTSPAM;
			*honorp &= ~DCC_HONOR_SRVR_ISSPAM;

		} else if (srvr_tgts == DCC_TGTS_OK2) {
			/* if server says it is half ok, look for two halves */
			if (*honorp & DCC_HONOR_SRVR_OK2) {
				*honorp |= DCC_HONOR_SRVR_NOTSPAM;
				*honorp &= ~DCC_HONOR_SRVR_ISSPAM;
			} else {
				*honorp |= DCC_HONOR_SRVR_OK2;
			}
		}
	}

	/* reject and log spam */
	if (dcc_tholds_spam[type] != DCC_TGTS_INVALID
	    && dcc_tholds_spam[type] <= srvr_tgts
	    && srvr_tgts <= DCC_TGTS_TOO_MANY
	    && !(*honorp & DCC_HONOR_SRVR_NOTSPAM))
		*honorp |= DCC_HONOR_SRVR_ISSPAM;

	/* log bulky messages */
	if ((*honorp & DCC_HONOR_SRVR_ISSPAM)
	    || (dcc_tholds_log[type] != DCC_TGTS_INVALID
		&& dcc_tholds_log[type] <= srvr_tgts))
		*honorp |= DCC_HONOR_LOGIT;
}



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

	if (tgts == DCC_TGTS_TOO_MANY) {
		if (!(*honorp & DCC_HONOR_LOCAL_NOTSPAM))
			*honorp |= DCC_HONOR_LOCAL_ISSPAM;
		*honorp |= DCC_HONOR_LOGIT;
		return;
	}

	if (*honorp & DCC_HONOR_LOGIT)
		return;

	/* pretend we always have a body checksum */
	if (dcc_tholds_log[DCC_CK_BODY] != DCC_TGTS_INVALID
	    && dcc_tholds_log[DCC_CK_BODY] <= tgts) {
		*honorp |= DCC_HONOR_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_INVALID)
			continue;
		if (dcc_tholds_log[type] <= tgts) {
			*honorp |= DCC_HONOR_LOGIT;
			return;
		}
	}
}



/* parse -G options for DCC clients */
u_char					/* 0=bad */
dcc_parse_client_grey(char *arg)
{
	int bits;
	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 (!CSTRCMP(arg, "IPmask/")) {
			bits = 0;
			for (p = arg+STRZ("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;
}



DCC_ASK_GREY_RESULT
dcc_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_QUERY_REPORT rpt;
	union {
	    DCC_HDR	hdr;
	    DCC_QUERY_RESP r;
	    DCC_ERROR	error;
	} resp;
	DCC_CK *ck;
	DCC_CK_TYPES type;
	const DCC_GOT_SUM *g;
	DCC_TGTS result_tgts;
	int num_cks, recv_len;

	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 DCC_GREY_ASK_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 DCC_GREY_ASK_FAIL;
	}

	/* Check the common checksums for whitelisting at the greylist server.
	 * This assumes DCC_CK_GREY_TRIPLE > DCC_CK_GREY_MSG > other types */
	memset(&rpt, 0, sizeof(rpt));
	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->rpt && type != DCC_CK_BODY)
			continue;
		ck->type = type;
		ck->len = sizeof(*ck);
		memcpy(ck->sum, g->sum, sizeof(ck->sum));
		++ck;
		++num_cks;
	}

	/* include the grey message checksum of
	 * the body, the sender, and 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_GREY_TRIPLE;
	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,
			 &rpt.hdr, (sizeof(rpt) - sizeof(rpt.cks)
				    + num_cks*sizeof(rpt.cks[0])),
			 op, &resp.hdr, sizeof(resp), 0)) {
		return DCC_GREY_ASK_FAIL;
	}

	recv_len = htons(resp.r.hdr.len);
	if (resp.hdr.op != DCC_OP_QUERY_RESP) {
		dcc_pemsg(EX_UNAVAILABLE, emsg, "DCC %s: %s %*s",
			  dcc_srvr_nm(),
			  dcc_hdr_op2str(&resp.hdr),
			  (resp.hdr.op == DCC_OP_ERROR
			   ? (recv_len - (ISZ(resp.error)
					  - ISZ(resp.error.msg)))
			   : 0),
			  resp.error.msg);
		return DCC_GREY_ASK_FAIL;
	}
	if (recv_len != (sizeof(resp.r) - sizeof(resp.r.body.tgts)
			 + 2*sizeof(resp.r.body.tgts[0]))) {
		dcc_pemsg(EX_UNAVAILABLE, emsg,
			  "DCC %s: answered with %d bytes",
			  dcc_srvr_nm(), recv_len);
		return DCC_GREY_ASK_FAIL;
	}

	/* see what the greylist server had to say */
	result_tgts = ntohl(resp.r.body.tgts[1]);
	switch (result_tgts) {
	case DCC_TGTS_OK:		/* embargo ended just now */
		if (resp.r.body.tgts[0] != 0)
			++*plate_tgts;
		*pembargo_num = 0;
		return DCC_GREY_ASK_EMBARGO_END;

	case DCC_TGTS_TOO_MANY:		/* no current embargo */
		*pembargo_num = 0;
		return ((resp.r.body.tgts[0] != 0)
			? DCC_GREY_ASK_EMBARGO_END
			: DCC_GREY_ASK_PASS);

	case DCC_TGTS_OK2:		/* whitelisted for greylisting */
		*pembargo_num = 0;
		return DCC_GREY_ASK_WHITE;

	default:			/* embargoed */
		if (resp.r.body.tgts[0] == 0)
			++*pearly_tgts;	/* count this target in DCC report */
		*pembargo_num = result_tgts+1;
		return DCC_GREY_ASK_EMBARGO;
	}
}



/* tell the grey list to restore the embargo on a triple */
u_char
dcc_grey_spam(DCC_EMSG emsg,
	      DCC_CLNT_CTXT *ctxt,
	      const DCC_GOT_CKS *cks,
	      const DCC_SUM triple_sum)	/* delete this cksum from database */
{
	DCC_GREY_SPAM gs;
	union {
	    DCC_HDR	hdr;
	    DCC_ERROR	error;
	} resp;

	memset(&gs, 0, sizeof(gs));

	if (cks->sums[DCC_CK_IP].type != DCC_CK_IP) {
		dcc_pemsg(EX_SOFTWARE, emsg,
			  "missing IP checksum for dcc_grey_spam()");
		return 0;
	}

	gs.ip.type = DCC_CK_IP;
	gs.ip.len = sizeof(gs.ip);
	memcpy(&gs.ip.sum, &cks->sums[DCC_CK_IP].sum, sizeof(DCC_SUM));

	gs.triple.type = DCC_CK_GREY_TRIPLE;
	gs.triple.len = sizeof(gs.triple);
	memcpy(&gs.triple.sum, triple_sum, sizeof(DCC_SUM));

	return dcc_clnt_op(emsg, ctxt, DCC_CLNT_FG_GREY,
			   0, 0, &gs.hdr, sizeof(gs),
			   DCC_OP_GREY_SPAM, &resp.hdr, sizeof(resp), 0);
}
