/* Distributed Checksum Clearinghouse
 *
 * work on a job in the server
 *
 * 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.137 $Revision$
 */

#include "dccd_defs.h"

static int prev_q_delay_us;
static float q_delays_us;
static int q_ops;

DCCD_STATS dccd_stats;

u_char query_only;			/* 1=treat reports as queries */

u_char grey_weak_body;			/* 1=ignore bodies for greylisting */
u_char grey_weak_ip;			/* 1=a good triple whitelists addr */

int grey_embargo = DEF_GREY_EMBARGO;	/* seconds to delay new traffic */
int grey_window = DEF_GREY_WINDOW;	/* wait as long as this */
int grey_white = DEF_GREY_WHITE;	/* remember this long */


/* report cache used to detect duplicate or retransmitted reports */
static RIDC *ridc_newest, *ridc_oldest;
static RIDC **ridc_hash;
static int ridc_hash_len;

static void send_resp(const QUEUE *, DCC_HDR *);
static void send_error(const QUEUE *, const char *, ...) PATTRIB(2,3);
static void send_emsg(const QUEUE *, int);
#define SEND_EMSG(q) send_emsg(q,__LINE__)
static void repeat_resp(QUEUE *);


static inline RIDC **
ridc_hash_fnc(DCC_HDR *hdr)
{
	u_int32_t sum;

	/* The client's (ID,RID,HID,PID) should be unique and constant for
	 * retransmissions of a single request.  It should
	 * make a reasonable hash value.  We cannot trust it entirely, if
	 * only because of anonymous clients */
	sum = (hdr->sender
	       + hdr->op_nums.h + hdr->op_nums.p + hdr->op_nums.r);

	return &ridc_hash[mhash(sum, ridc_hash_len)];
}



static void
ridc_ref(RIDC *ridc)
{
	ridc->last_used = db_time.tv_sec;
	if (ridc->newer)
		ridc->newer->older = ridc->older;
	else
		return;			/* it's already newest */
	if (ridc->older)
		ridc->older->newer = ridc->newer;
	else
		ridc_oldest = ridc->newer;
	ridc->older = ridc_newest;
	ridc->newer = 0;
	ridc_newest->newer = ridc;
	ridc_newest = ridc;
}



/* get a free report cache block */
static RIDC *
ridc_get_free(void)
{
	RIDC *ridc;
	time_t stale = db_time.tv_sec - DCC_MAX_DELAY_SEC;

	for (ridc = ridc_oldest; ridc != 0; ridc = ridc->newer) {
		if (ridc->last_used < stale) {
			/* found one, so recycle it */
			if (ridc->fwd)
				ridc->fwd->bak = ridc->bak;
			if (ridc->bak)
				ridc->bak->fwd = ridc->fwd;
			else if (ridc->hash)
				*ridc->hash = ridc->fwd;
			ridc->bak = 0;
			ridc_ref(ridc);
			return ridc;
		}
	}

	/* there are no free blocks that are old enough to recycle */
	return 0;
}



/* make some or some more RID blocks and (re)build the hash table */
static void
ridc_make(void)
{
	int new_len, old_len, j;
	RIDC *ridc, *ridc2, **ridch, **old_ridc_hash;

	new_len = queue_max;
	ridc = dcc_malloc(new_len*sizeof(*ridc));
	if (!ridc)
		dcc_logbad(EX_OSERR, "malloc(%d RIDC blocks) failed",
			   new_len);
	memset(ridc, 0, new_len*sizeof(*ridc));
	for (j = 0; j < new_len; ++j, ++ridc) {	/* make the new blocks oldest */
		if (!ridc_oldest) {
			ridc_oldest = ridc_newest = ridc;
		} else {
			ridc_oldest->older = ridc;
			ridc->newer = ridc_oldest;
			ridc_oldest = ridc;
		}
	}

	/* rebuild and expand the hash table */
	old_len = ridc_hash_len;
	ridc_hash_len += new_len;
	old_ridc_hash = ridc_hash;
	ridc_hash = dcc_malloc(ridc_hash_len*sizeof(*ridch));
	if (!ridc_hash)
		dcc_logbad(EX_OSERR, "malloc(%d RIDC hash table) failed",
			   ridc_hash_len);
	memset(ridc_hash, 0, ridc_hash_len*sizeof(*ridch));
	if (old_len != 0) {
		do {
			for (ridc = old_ridc_hash[--old_len];
			     ridc != 0;
			     ridc = ridc2) {
				ridch = ridc_hash_fnc(&ridc->hdr);
				ridc2 = ridc->fwd;
				ridc->bak = 0;
				ridc->hash = ridch;
				if ((ridc->fwd = *ridch) != 0)
					ridc->fwd->bak = ridc;
				*ridch = ridc;
			}
		} while (old_len != 0);
		dcc_free(old_ridc_hash);
	}
}



/* get the report cache block for an operation */
u_char					/* 0=new operation, 1=retransmission */
ridc_get(QUEUE *q)
{
	RIDC *ridc, **ridch;

	for (;;) {
		if (ridc_hash) {
			/* look for the existing report cache block */
			ridch = ridc_hash_fnc(&q->pkt.hdr);
			for (ridc = *ridch; ridc; ridc = ridc->fwd) {
				/* Reports are relatively small, so we
				 * can afford to not trust the client's
				 * RID to be unique.  Compare all but the
				 * client's transmission #. 
				 * Also check client's UDP port # because
				 * it should be unchanged regardless of
				 * multi-homing. */
				if (ridc->clnt_port == q->clnt_su.ipv4.sin_port
				    && !memcmp(&ridc->hdr, &q->pkt.hdr,
					       sizeof(ridc->hdr)
					       - sizeof(ridc->hdr.op_nums.t))) {
					/* found it, so make it newest */
					ridc_ref(ridc);
					q->ridc = ridc;
					return 1;
				}
			}

			/* the block does not already exist, so create it */
			ridc = ridc_get_free();
			if (ridc)
				break;
		}
		/* we are out of report cache blocks, so make more */
		ridc_make();

		/* re-hash because our previous pointer is invalid */
	}

	memcpy(&ridc->hdr, &q->pkt.hdr, sizeof(ridc->hdr));
	ridc->clnt_port = q->clnt_su.ipv4.sin_port;
	ridc->op = DCC_OP_INVALID;
	ridc->len = 0;
	ridc->hash = ridch;
	ridc->fwd = *ridch;
	if (ridc->fwd)
		ridc->fwd->bak = ridc;
	*ridch = ridc;

	q->ridc = ridc;
	return 0;
}



/* recompute the queue delay */
void
cycle_q_delay(void)
{
	if (q_ops == 0) {
		prev_q_delay_us = 0;
	} else {
		prev_q_delay_us = q_delays_us / q_ops;
	}
	q_delays_us = 0.0;
	q_ops = 0;
}


static int				/* microseconds of delay */
get_q_delay_us(QUEUE *q)
{
	int usecs;

	if (q_ops == 0) {
		usecs = 0;
	} else {
		usecs = q_delays_us / q_ops;
	}
	if (usecs < prev_q_delay_us)
		usecs = prev_q_delay_us;

	usecs += q->delay_us;
	return usecs;
}



/* get a unique timestamp */
static void
get_ts(DCC_TS ts)
{
	static struct timeval prev_time;

	gettimeofday(&db_time, 0);

	/* Try to make the next timestamp unique, but only as long
	 * as time itself marches forward.  This must work many times
	 * a second, or the resoltion of DCC timestaps.
	 * Worse, the increment can exhaust values from future seconds.
	 * Forget about it if the problem lasts for more than 5 minutes. */
	if (db_time.tv_sec > prev_time.tv_sec
	    || (db_time.tv_sec == prev_time.tv_sec
		&& db_time.tv_usec > prev_time.tv_usec)
	    || db_time.tv_sec < prev_time.tv_sec-5*60) {
		/* either the current time is good enough or we must
		 * give up and use it to make the timestamp */
		prev_time = db_time;

	} else {
		/* fudge the previous timestamp to make it good enough */
		prev_time.tv_usec += DCC_TS_USEC_MULT;
		if (prev_time.tv_usec > 1000*1000) {
			prev_time.tv_usec -= 1000*1000;
			++prev_time.tv_sec;
		}
	}

	dcc_timeval2ts(ts, &prev_time, 0);
}



/* see if a count just passed a multiple of a threshold and so is
 *	worth flooding or summarizing */
static inline u_char
summarize_thold(DCC_CK_TYPES type,
		DCC_TGTS rpt_tgts,	/* targets in this report */
		DCC_TGTS ck_tgts)	/* grand total */
{
	DCC_TGTS thold, q1, q2;

	thold = oflod_thold[type];
	if (thold == 0)
		return 1;
	q1 = (ck_tgts - rpt_tgts) / thold;
	q2 = ck_tgts/thold;
	return (q1 < 3 && q1 != q2);
}



/* generate a summary record of checksum counts
 *	db_sts.sumrcd points to the record */
static u_char
summarize_rcd(u_char dly)		/* 1=working on delayed records */
{
	DB_RCD new;
	DB_STATE *rcd_st;
	DCC_TGTS rcd_tgts, ck_tgts, new_tgts, sub_total;
	DCC_CK_TYPES type;
	DB_RCD_CK *cur_ck, *new_ck, *found_ck;
	DB_PTR oldest, prev;
	int cur_num_cks;
	int limit;
	u_char rcd_needed;		/* 1=have created rcd to add */
	u_char ck_needed;		/* 0=junk cksum, 2=needed in new rcd */
	u_char undelay_ok;		/* 1=ok to remove delay bit */
	u_char move_ok;

	/* For each checksum whose flooding was delayed but is now needed,
	 * generate a fake record that will be flooded */
	cur_num_cks = DB_NUM_CKS(db_sts.sumrcd.d.r);
	cur_ck = db_sts.sumrcd.d.r->cks;
	new_tgts = 0;
	undelay_ok = 1;
	move_ok = 1;
	rcd_needed = 0;
	new_ck = new.cks;
	do {
		/* Sum counts of all delayed reports for this checksum */
		type = DB_CK_TYPE(cur_ck);
		if (DB_TEST_NOKEEP(db_nokeep_cks, type))
			continue;

		if (dly) {
			switch (db_lookup(dcc_emsg, type, cur_ck->sum,
					  0, MAX_HASH_ENTRIES,
					  &db_sts.hash, &db_sts.rcd2,
					  &found_ck)) {
			case DB_FOUND_SYSERR:
			case DB_FOUND_LATER:
				db_broken(dcc_emsg);
				return 0;
			case DB_FOUND_EMPTY:
			case DB_FOUND_CHAIN:
			case DB_FOUND_INTRUDER:
				db_broken("missing hash entry for %s in "L_HPAT,
					  dcc_type2str_err(type, 0, 1),
					  db_sts.rcd2.s.rptr);
				return 0;
			case DB_FOUND_IT:
				break;
			}
			rcd_st = &db_sts.rcd2;

		} else {
			/* skip trudging through the hash table to find the
			 * most recent instance of the checksum if we
			 * are dealing with a new record */
			found_ck = cur_ck;
			rcd_st = &db_sts.sumrcd;
		}

		/* spam is handled immediately,
		 * but it is harmless to "undelay" it */
		ck_tgts = DB_TGTS_CK(found_ck);
		if (ck_tgts == DCC_TGTS_TOO_MANY)
			continue;
		if (DB_CK_OBS(found_ck))
			ck_needed = 0;
		else
			ck_needed = 1;

		oldest = DB_PTR_MAX;
		sub_total = 0;
		for (limit = 1000; limit >= 0; --limit) {
			/* stop adding reports at the first summary or
			 * compressed record in the hash chain */
			if (DB_RCD_SUMRY(rcd_st->d.r)
			    || DB_RCD_ID(rcd_st->d.r) == DCC_ID_COMP)
				break;

			/* honor deletions */
			rcd_tgts = DB_TGTS_RCD(rcd_st->d.r);
			if (rcd_tgts == 0)
				break;

			/* We can only summarize our own delayed reports
			 * to keep loops in the flooding topology from
			 * inflating totals. */
			if (DB_RCD_DELAY(rcd_st->d.r)
			    && DB_RCD_ID(rcd_st->d.r) == my_srvr_id) {
				oldest = rcd_st->s.rptr;
				sub_total = db_sum_ck(sub_total, rcd_tgts);
				/* if we summarize more than the record we
				 * just added, then we cannot simply
				 * convert that new record */
				if (db_sts.sumrcd.s.rptr != rcd_st->s.rptr)
					undelay_ok = 0;
			}
			prev = DB_PTR_EX(found_ck->prev);
			if (prev == DB_PTR_NULL)
				break;
			rcd_st = &db_sts.rcd2;
			found_ck = db_map_rcd_ck(dcc_emsg, rcd_st,
						 prev, type);
			if (!found_ck) {
				db_broken(dcc_emsg);
				return 0;
			}
		}
		/* honor deletions and summaries between our record
		 * and the start of the hash chain */
		if (sub_total == 0) {
			move_ok = 0;
			continue;
		}

		if (ck_needed == 1) {
			if (dly) {
				/* We are processing delayed reports.  Flood
				 * only one delayed summary per delay period */
				if (ck_tgts >= oflod_thold[type]
				    && (flod_mmaps == 0
					|| oldest <= flod_mmaps->delay_pos))
					ck_needed = 2;
			} else {
				/* We are considering a summary based on
				 * a report just received from a client
				 * or by flooding */
				if (summarize_thold(type, sub_total, ck_tgts))
					ck_needed = 2;
			}
		}

		if (new_ck != new.cks) {
			/* We have already begun a summary record.
			 *  Try to extend it. */
			if (sub_total == new_tgts) {
				new_ck->type_fgs = type;
				memcpy(new_ck->sum, cur_ck->sum,
				       sizeof(new_ck->sum));
				++new.fgs_num_cks;
				++new_ck;
				if (ck_needed == 2)
					rcd_needed = 1;
				continue;
			}
			/* We cannot extend the current summary record.
			 * If we don't really need the checksum, then
			 * forget the checksum */
			if (ck_needed != 2) {
				move_ok = 0;
				continue;
			}
			/* We really need to summarize the checksum.
			 * So add the current summary record
			 * to the database and start a new summary */
			if (rcd_needed
			    && !db_add_rcd(dcc_emsg, &new)) {
				db_broken(dcc_emsg);
				return 0;
			}
			rcd_needed = 0;
			undelay_ok = 0;
		}

		/* start a new summary record */
		new.srvr_id_auth = my_srvr_id;
		get_ts(new.ts);
		new_tgts = sub_total;
		DB_TGTS_RCD_SET(&new, new_tgts);
		new.fgs_num_cks = DB_RCD_FG_SUMRY+1;
		new_ck = new.cks;
		new_ck->type_fgs = type;
		memcpy(new_ck->sum, cur_ck->sum,
		       sizeof(new_ck->sum));
		new_ck->prev = DB_PTR_CP(DB_PTR_NULL);
		++new_ck;
		if (ck_needed == 2)
			rcd_needed = 1;
	}  while (++cur_ck, --cur_num_cks > 0);

	/* finished if nothing more to summarize */
	if (!rcd_needed)
		return 1;

	/* Add the last summary record */
	if (undelay_ok) {
		/* If possible, instead of adding a new record,
		 * change the preceding record to not be delayed
		 * That is possible if the preceding record has
		 * not yet been passed by the flooding */
		if (db_sts.sumrcd.s.rptr >= oflods_max_pos
		    && oflods_max_pos != 0) {
			db_sts.sumrcd.d.r->fgs_num_cks &= ~DB_RCD_FG_DELAY;
			db_sts.sumrcd.b->flags |= DB_BUF_FG_MSYNC;
			return 1;
		}

		/* failing that, try to move the record by making a new copy
		 * and deleting the original */
		if (move_ok) {
			memcpy(&new, db_sts.sumrcd.d.r,
			       (sizeof(new) - sizeof(new.cks)
				+ (DB_NUM_CKS(db_sts.sumrcd.d.r)
				   * sizeof(new.cks[0]))));
			new.fgs_num_cks &= ~DB_RCD_FG_DELAY;
			DB_TGTS_RCD_SET(db_sts.sumrcd.d.r, 0);
			rcd_tgts = DB_TGTS_RCD(&new);
			cur_num_cks = DB_NUM_CKS(db_sts.sumrcd.d.r);
			cur_ck = db_sts.sumrcd.d.r->cks;
			do {
				new_tgts = DB_TGTS_CK(cur_ck);
				if (new_tgts != DCC_TGTS_TOO_MANY) {
					new_tgts -= rcd_tgts;
					DB_TGTS_CK_SET(cur_ck, new_tgts);
				}
			}  while (++cur_ck, --cur_num_cks > 0);
			db_sts.sumrcd.b->flags |= DB_BUF_FG_MSYNC;
		}
	}

	if (!db_add_rcd(dcc_emsg, &new)) {
		db_broken(dcc_emsg);
		return 0;
	}
	return 1;
}



/* generate a delayed summary if necessary
 *	The target record is specified by db_sts.sumrcd.  It might be changed */
u_char
summarize_dly(void)
{
	DCC_CK_TYPES type;
	const DB_RCD_CK *cur_ck;
	DB_RCD_CK *found_ck;
	int cur_num_cks;
	DCC_TGTS ck_tgts;

	/* look for a checksum that could be summarized */
	cur_num_cks = DB_NUM_CKS(db_sts.sumrcd.d.r);
	cur_ck = db_sts.sumrcd.d.r->cks;
	do {
		type = DB_CK_TYPE(cur_ck);
		if (DB_TEST_NOKEEP(db_nokeep_cks, type))
			continue;

		switch (db_lookup(dcc_emsg, type, cur_ck->sum,
				  0, MAX_HASH_ENTRIES,
				  &db_sts.hash, &db_sts.rcd2,
				  &found_ck)) {
		case DB_FOUND_SYSERR:
		case DB_FOUND_LATER:
			db_broken(dcc_emsg);
			return 0;
		case DB_FOUND_EMPTY:
		case DB_FOUND_CHAIN:
		case DB_FOUND_INTRUDER:
			db_broken("missing hash entry for %s in "L_HPAT,
				  dcc_type2str_err(type, 0, 1),
				  db_sts.rcd2.s.rptr);
			return 0;
		case DB_FOUND_IT:
			break;
		}

		/* nothing to do if the checksum has already been summarized */
		if (DB_RCD_SUMRY(db_sts.rcd2.d.r))
			continue;

		/* spam reports are ignored or not delayed */
		ck_tgts = DB_TGTS_CK(found_ck);
		if (ck_tgts == DCC_TGTS_TOO_MANY)
			continue;

		/* generate a summary for a bulk checksum */
		if (ck_tgts >= oflod_thold[type]) {
			if (!summarize_rcd(1)) {
				db_broken(dcc_emsg);
				return 0;
			}
			return 1;
		}
	}  while (++cur_ck, --cur_num_cks > 0);

	return 1;
}



/* see if a incoming flooded checksum has been passed on recently
 *	db_sts.sumrcd points to the new record */
static u_char
flod_worth(DCC_EMSG emsg, u_char *pflod, DB_RCD_CK *ck, DCC_CK_TYPES type)
{
	DCC_TS past;
	DCC_TGTS thresh;
	int limit;
	DB_PTR prev;

	thresh = 3*oflod_thold[type];
	if (DB_TGTS_CK(ck) < thresh) {
		*pflod = 1;
		return 1;
	}

	dcc_timeval2ts(past, &db_time, -summarize_delay_secs);
	for (limit = 20; limit >= 0; --limit) {
		prev = DB_PTR_EX(ck->prev);
		if (prev == DB_PTR_NULL)
			break;
		ck = db_map_rcd_ck(emsg, &db_sts.rcd2, prev, type);
		if (!ck)
			return 0;

		/* Look for a recent report for this checksum that has been
		 * or will be flooded.  If we find one, and if the total
		 * including it is large enough, we may not need to flood
		 * the incoming report.  If the total is too small, we
		 * must flood the report. */
		if (DB_TGTS_CK(ck) < thresh) {
			*pflod = 1;
			return 1;
		}
		if (!DB_CK_OBS(ck)
		    && DCC_TS_NEWER_TS(db_sts.rcd2.d.r->ts, past))
			return 1;
	}

	/* if we can't find a recent preceding report,
	 * arrange to flood this one */
	*pflod = 1;
	return 1;
}



/* Add a record and deal with delaying its flooding.
 *	We will delay flooding it if its totals are not interesting.
 *	db_sts.sumrcd points to the new record on exit */
u_char
add_dly_rcd(DCC_EMSG emsg, DB_RCD *new_rcd)
{
	DB_PTR rcd_pos;
	int num_cks;
	DB_RCD_CK *new_ck;
	DCC_CK_TYPES type;
	DCC_TGTS rpt_tgts, ck_tgts;
	u_char flod, useful, summarize;

	/* put the record in the database */
	rcd_pos = db_add_rcd(emsg, new_rcd);
	if (!rcd_pos) {
		db_broken(dcc_emsg);
		return 0;
	}
	if (!db_map_rcd(emsg, &db_sts.sumrcd, rcd_pos, 0)) {
		db_broken(dcc_emsg);
		return 0;
	}
	new_rcd = db_sts.sumrcd.d.r;

	/* delete requests should not be delayed */
	rpt_tgts = DB_TGTS_RCD_RAW(new_rcd);
	if (rpt_tgts == DCC_TGTS_DEL)
		return 1;

	flod = (my_srvr_id == DB_RCD_ID(new_rcd));
	useful = 0;
	summarize = 0;
	for (num_cks = DB_NUM_CKS(new_rcd), new_ck = new_rcd->cks;
	     num_cks > 0;
	     ++new_ck, --num_cks) {
		if (DB_CK_OBS(new_ck))
			continue;

		type = DB_CK_TYPE(new_ck);
		if (DB_TEST_NOKEEP(db_nokeep_cks, type))
			continue;

		/* Reports of spam of either our own or others' are
		 * either immediately flooded or marked obsolete as they
		 * are linked in the database */
		ck_tgts = DB_TGTS_CK(new_ck);
		if (ck_tgts == DCC_TGTS_TOO_MANY) {
			flod = 1;
			continue;
		}

		/* This report has some potential value */
		useful = 1;

		/* Summarize all of our records for the checksums now
		 * if we just passed the threshold for one of them. */
		if (!summarize
		    && summarize_thold(type, rpt_tgts, ck_tgts))
			summarize = 1;

		/* If this is an incoming flooded checksum,
		 * then pass it on if it is novel (has a low total)
		 * or if we have not passed it on recently. */
		if (!flod
		    && !flod_worth(emsg, &flod, new_ck, type))
			return 0;
	}

	/* Forget reports that are either useless noise or reports of spam.
	 * They will be flooded or skipped by the flooder */
	if (!useful)
		return 1;

	if (DB_RCD_ID(new_rcd) == my_srvr_id) {
		/* Delay and sooner or later summarize our own reports */
		new_rcd->fgs_num_cks |= DB_RCD_FG_DELAY;

	} else if (!flod && !grey_on) {
		/* Don't delay reports that other servers that are
		 * important enough to flood, because we cannot summarize them.
		 * Summarizing other servers' reports would allow
		 * loops in the flooding topology to inflate the totals.
		 * However, another server's report might be reason
		 * for us to flood a summary of our own old reports. */
		for (num_cks = DB_NUM_CKS(new_rcd), new_ck = new_rcd->cks;
		     num_cks > 0;
		     ++new_ck, --num_cks) {
			new_ck->type_fgs |= DB_CK_FG_OBS;
		}
	}

	/* If this record pushed us past a threshold for at least one
	 * checksum, then try to generate a summary of our own,
	 * previously delayed reports even if this record was not our own. */
	if (summarize
	    && !summarize_rcd(0)) {
		db_broken(dcc_emsg);
		return 0;
	}
	return 1;
}



static u_char
add_del(const DCC_CK *del_ck)
{
	DB_RCD del_rcd;

	memset(&del_rcd, 0, sizeof(del_rcd));
	get_ts(del_rcd.ts);
	DB_TGTS_RCD_SET(&del_rcd, DCC_TGTS_DEL);
	del_rcd.srvr_id_auth = my_srvr_id | DCC_SRVR_ID_AUTH;
	del_rcd.fgs_num_cks = 1;
	del_rcd.cks[0].type_fgs = del_ck->type;
	memcpy(del_rcd.cks[0].sum, del_ck->sum, sizeof(del_rcd.cks[0].sum));
	del_rcd.cks[0].prev = DB_PTR_CP(DB_PTR_NULL);
	if (!db_add_rcd(dcc_emsg, &del_rcd)) {
		db_broken("add delete: %s", dcc_emsg);
		return 0;
	}

	return 1;
}



static const DCC_CK *
start_work(QUEUE *q, int *was_locked)
{
	const DCC_CK *ck, *ck_lim;
	DCC_CK_TYPE prev_type;
	int num_cks;

	num_cks = q->pkt_len - (sizeof(q->pkt.r) - sizeof(q->pkt.r.cks));
	if (num_cks < 0) {
		discard_error(q, "packet length %d too small for %s",
			      q->pkt_len, dcc_req_op2str(&q->pkt.a));
		return 0;
	}
	if (num_cks > ISZ(q->pkt.r.cks)) {
		discard_error(q, "packet length %d too large for %s",
			      q->pkt_len, dcc_req_op2str(&q->pkt.a));
		return 0;
	}
	if (num_cks % sizeof(DCC_CK) != 0) {
		discard_error(q, "odd packet length %d for %s",
			      q->pkt_len, dcc_req_op2str(&q->pkt.a));
		return 0;
	}
	num_cks /= sizeof(DCC_CK);

	/* send previous answer if this is a retransmission */
	if (ridc_get(q)) {
		repeat_resp(q);
		return 0;
	}

	/* check each checksum */
	ck_lim = &q->pkt.r.cks[num_cks];
	prev_type = DCC_CK_INVALID;
	for (ck = q->pkt.r.cks; ck < ck_lim; ++ck) {
		if (ck->len != sizeof(*ck)) {
			/* relax this someday if necessary */
			discard_error(q, "unknown checksum length %d", ck->len);
			return 0;
		}
		/* requiring that the checksums be ordered makes it easy
		 * to check for duplicates and for bogus long packets */
		if (prev_type >= ck->type) {
			discard_error(q, "out of order %s checksum",
				      dcc_type2str_err(ck->type, 0, 1));
			return 0;
		}
		prev_type = ck->type;
	}

	if ((*was_locked = db_lock(dcc_emsg)) < 0) {
		SEND_EMSG(q);
		return 0;
	}

	return ck_lim;
}



static void
fin_work(const QUEUE *q, DCC_QUERY_RESP *resp)
{
	int delay_us;

	/* send the response */
	resp->hdr.op = DCC_OP_QUERY_RESP;
	send_resp(q, &resp->hdr);

	/* update the average queue delay */
	gettimeofday(&db_time, 0);
	delay_us = ((db_time.tv_sec - q->answer.tv_sec)*DCC_USECS
		    + (db_time.tv_usec - q->answer.tv_usec));
	if (delay_us > 0)
		q_delays_us += delay_us;
	++q_ops;
}



/* use db_sts.hash and db_sts.rcd2 to look up answers
 *	while not touching db_sts.rcd */
static u_char
do_query(QUEUE *q, const DCC_CK *ck_lim, u_char report,
	 DCC_QUERY_RESP *resp, DCC_TGTS* max_tgts)
{
	const DB_RCD_CK *new_ck;
	const DCC_CK *ck;
	DB_RCD_CK *found_ck;
	DCC_TGTS tgts, *resp_tgts;
	int num_cks;

	/* To answer the query, look for each checksum
	 * except for checksums in a record we just added. */
	if (report) {
		new_ck = db_sts.sumrcd.d.r->cks;
		num_cks = DB_NUM_CKS(db_sts.sumrcd.d.r);
	} else {
		new_ck = 0;
		num_cks = 0;
	}
	*max_tgts = tgts = 0;
	resp_tgts = &resp->body.tgts[0];
	for (ck = q->pkt.r.cks; ck < ck_lim; ++ck, ++resp_tgts) {
		if (num_cks > 0 && ck->type == DB_CK_TYPE(new_ck)) {
			/* copy answer from the new record */
			tgts = DB_TGTS_CK(new_ck);
			++new_ck;
			--num_cks;

		} else if (!DCC_CK_OK_CLNT(ck->type, grey_on)) {
			/* ignore unknown checksums */
			tgts = 0;

		} else {
			switch (db_lookup(dcc_emsg, ck->type, ck->sum,
					  0, MAX_HASH_ENTRIES,
					  &db_sts.hash, &db_sts.rcd2,
					  &found_ck)) {
			case DB_FOUND_LATER:
			case DB_FOUND_SYSERR:
				db_broken(dcc_emsg);
				SEND_EMSG(q);
				return 0;
			case DB_FOUND_IT:
				tgts = DB_TGTS_CK(found_ck);
				break;
			case DB_FOUND_EMPTY:
			case DB_FOUND_CHAIN:
			case DB_FOUND_INTRUDER:
				tgts = 0;
				break;
			}
		}

		*resp_tgts = htonl(tgts);
		if (*max_tgts < tgts
		    && DCC_CK_IS_BODY(ck->type))
			*max_tgts = tgts;
	}

	resp->hdr.len = (sizeof(*resp) - sizeof(resp->body.tgts)
			 + ((char *)resp_tgts - (char *)resp->body.tgts));
	return 1;
}



static u_char
do_report(QUEUE *q, const DCC_CK *ck_lim, DCC_QUERY_RESP *resp,
	  DCC_TGTS* max_tgts)
{
	DCC_CK_TYPES type;
	const DCC_CK *ck;
	DB_RCD new;
	DB_RCD_CK *new_ck;
	DCC_TGTS tgts;
	char tgts_buf[DCC_XHDR_MAX_TGTS_LEN];

	/* Add the report to the database,
	 * and as a side effect, find the data to answer the query.
	 * Start by creating the record. */
	get_ts(new.ts);
	new.fgs_num_cks = 0;
	tgts = ntohl(q->pkt.r.tgts);

	if (tgts == 0) {
		discard_error(q, "reported zero target count");
		return 0;
	}

	if (tgts < 10) {
		;
	} else if (tgts == DCC_TGTS_TOO_MANY) {
		++dccd_stats.reportmany;
	} else if (tgts > DCC_TGTS_TOO_MANY) {
		discard_error(q, "bogus target count %s",
			      dcc_cnt2str(tgts_buf, sizeof(tgts_buf),
					  tgts, grey_on));
		return 0;
	} else if (tgts > 1000) {
		++dccd_stats.report1000;
	} else if (tgts > 100) {
		++dccd_stats.report100;
	} else if (tgts > 10) {
		++dccd_stats.report10;
	}
	DB_TGTS_RCD_SET(&new, tgts);
	new.srvr_id_auth = my_srvr_id;
	if (q->clnt_id != DCC_ID_ANON)
		new.srvr_id_auth |= DCC_SRVR_ID_AUTH;

	/* Check reported checksums as we copy them to the new record */
	new_ck = new.cks;
	for (ck = q->pkt.r.cks; ck < ck_lim; ++ck) {
		type = ck->type;

		/* ignore unknown checksums */
		if (!DCC_CK_OK_CLNT(type, 0)) {
			clnt_msg(q, "unknown checksum %s from %s",
				 dcc_type2str_err(type, 0, 1), Q_CIP(q));
			continue;
		}

		/* don't record uninteresting checksums */
		if (DB_TEST_NOKEEP(db_nokeep_cks, type))
			continue;

		new_ck->type_fgs = type;
		memcpy(new_ck->sum, ck->sum, sizeof(new_ck->sum));
		new_ck->prev = DB_PTR_CP(DB_PTR_NULL);
		++new_ck;
		++new.fgs_num_cks;
	}

	/* if we found no checksums to put into the record,
	 * treat the operation as if was a query */
	if (new.fgs_num_cks == 0)
		return do_query(q, ck_lim, 0, resp, max_tgts);

	/* Add the record to the database.
	 * That will update the totals for each checksum */
	if (!add_dly_rcd(dcc_emsg, &new)) {
		SEND_EMSG(q);
		return 0;
	}

	/* generate the response from the new record */
	return do_query(q, ck_lim, 1, resp, max_tgts);
}



/* process a single DCC_OP_REPORT or DCC_OP_QUERY */
void
do_work(QUEUE *q)
{
	const DCC_CK *ck_lim;
	DCC_QUERY_RESP resp;
	DCC_TGTS max_tgts;
	int was_locked;
	u_char result;

	ck_lim = start_work(q, &was_locked);
	if (!ck_lim)
		return;

	if (q->pkt.hdr.op == DCC_OP_REPORT) {
		if (!(q->flags & Q_FLG_RPT_OK)) {
			++dccd_stats.report_reject;
			clnt_msg(q, "treat report as query from %s", Q_CIP(q));
			result = do_query(q, ck_lim, 0, &resp, &max_tgts);
		} else {
			/* process the report */
			result = do_report(q, ck_lim, &resp, &max_tgts);
		}
	} else {
		result = do_query(q, ck_lim, 0, &resp, &max_tgts);
	}

	if (!result) {
		/* ensure that the clock ticks so rate limits don't stick */
		gettimeofday(&db_time, 0);
	} else {
		/* notice the size of our report */
		if (max_tgts == DCC_TGTS_OK || max_tgts == DCC_TGTS_OK2) {
			++dccd_stats.respwhite;
		} else if (max_tgts == DCC_TGTS_TOO_MANY) {
			++dccd_stats.respmany;
		} else if (max_tgts > 1000) {
			++dccd_stats.resp1000;
		} else if (max_tgts > 100) {
			++dccd_stats.resp100;
		} else if (max_tgts > 10) {
			++dccd_stats.resp10;
		}
		fin_work(q, &resp);
	}

	if (!was_locked)
		db_unlock(0);
}



/* return 0 for a new embargo,
 *	embargo count	for an existing embargo,
 *	DCC_TGTS_TOO_MANY   no embargo
 *	DCC_TGTS_OK	    a newly expired embargo
 *	DCC_TGTS_INVALID    broken database */
static DCC_TGTS
search_grey(DCC_EMSG emsg,
	    const DCC_CK *req_ck3,	/* triple checksum */
	    const DCC_CK *req_ckb,	/* body seen with it */
	    u_char body_known)
{
	DB_RCD_CK *ck3, *ckb;
	DB_PTR prev3;
	DCC_TS old_ts;
	DCC_TGTS result_tgts;
	int i;

	/* look for the triple checksum */
	switch (db_lookup(dcc_emsg, DCC_CK_GREY_TRIPLE, req_ck3->sum,
			  0, MAX_HASH_ENTRIES,
			  &db_sts.hash, &db_sts.rcd, &ck3)) {
	case DB_FOUND_EMPTY:
	case DB_FOUND_CHAIN:
	case DB_FOUND_INTRUDER:
		return 0;

	case DB_FOUND_IT:
		/* We found the triple checksum.
		 * If it is marked ok (MANY) or deleted,
		 * then we have our answer */
		result_tgts = DB_TGTS_CK(ck3);
		if (result_tgts == DCC_TGTS_TOO_MANY || result_tgts == 0)
			return result_tgts;

		/* Otherwise look for a report of the triple with
		 * the right body checksum that is old enough. */
		result_tgts = 0;
		dcc_timeval2ts(old_ts, &db_time, -grey_embargo);
		for (;;) {
			ckb = db_sts.rcd.d.r->cks;
			for (i = DB_NUM_CKS(db_sts.rcd.d.r);
			     i > 0;
			     --i, ++ckb) {
				/* try the next report in the database
				 * if it has the wrong body checksum
				 *
				 * If we are weak on bodies,
				 * act as if all reports of the triple checksums
				 * are with the right body checksum. */
				if (!grey_weak_body && req_ckb) {
					if (DB_CK_TYPE(ckb) != DCC_CK_BODY)
					    continue;
					if (memcmp(req_ckb->sum, ckb->sum,
						   sizeof(DCC_SUM)))
					    break;
				}

				/* We found the right body checksum in
				 * chain of the triple checksum
				 * or we don't care.
				 *
				 * If the report is old enough, then
				 * the embargo is over. */
				if (DCC_TS_NEWER_TS(old_ts, db_sts.rcd.d.r->ts))
					return DCC_TGTS_OK;

				/* If it is not old enough,
				 * then we know this is not a new embargo for
				 * this body (i.e. the reported target count
				 * will be >0) and we must keep looking for an
				 * old enough report with the body checksum. */
				++result_tgts;
				break;
			}

			/* If we know the body checksum is not in the database,
			 * then there is no profit in looking at other reports
			 * of the triple checksum to try to find an old enough
			 * report that is with the right body checksum.
			 * We know this is a new embargo. */
			if (!body_known)
				return 0;

			/* If we reach the end of the chain of the
			 * triple checksum without finding an old
			 * enough report for the right body,
			 * then the embargo is not over. */
			prev3 = DB_PTR_EX(ck3->prev);
			if (prev3 == DB_PTR_NULL)
				return result_tgts;

			/* examine the timestamp of the preceding report
			 * of the triple */
			ck3 = db_map_rcd_ck(emsg, &db_sts.rcd,
					    prev3, DCC_CK_GREY_TRIPLE);
			if (!ck3)
				return DCC_TGTS_INVALID;
		}
		break;

	case DB_FOUND_LATER:
	case DB_FOUND_SYSERR:
		db_broken(dcc_emsg);
		return DCC_TGTS_INVALID;
	}
	return DCC_TGTS_INVALID;
}



void
do_grey(QUEUE *q)
{
	DCC_OPS op;
	DB_RCD new;
	const DCC_CK *req, *req_lim;
	const DCC_CK *req_ck_ip, *req_ck_triple, *req_ck_msg, *req_ck_body;
	u_char body_known;
	DB_RCD_CK *new_ck, *found_ck;
	DCC_QUERY_RESP resp;
	DCC_TGTS tgts;
	DCC_TGTS ip_tgts;		/* existing count for DCC_CK_IP */
	DCC_TGTS triple_tgts;		/*   "   count for DCC_CK_GREY_TRIPLE */
	DCC_TGTS msg_tgts;		/*   "   count for DCC_CK_GREY_MSG */
	DCC_TGTS eff_msg_tgts;		/* effective value: 0=reported to DCC */
	DCC_TGTS new_msg_tgts;		/* value after this */
	DCC_TGTS result_tgts;		/* no embargo, ending, whitelist or # */
	int was_locked;

	TMSG3(QUERY, "received %s from %d at %s",
	      dcc_req_op2str(&q->pkt.a),
	      (DCC_CLNT_ID)ntohl(q->pkt.hdr.sender), Q_CIP(q));
	if (!ck_clnt_id(q))
		return;
	if (q->clnt_id == DCC_ID_ANON) {
		anon_msg("drop anomymous %s request from %s",
			 dcc_req_op2str(&q->pkt.a), Q_CIP(q));
		return;
	}

	/* an embargo of 0 seconds means we should only collect names */
	op = q->pkt.hdr.op;
	if (op == DCC_OP_GREY_REPORT && grey_embargo == 0)
		op = DCC_OP_GREY_WHITE;

	req_lim = start_work(q, &was_locked);
	if (!req_lim)
		return;

	/* Require the checksum of the (source,sender,target) triple
	 * the checksum of the (body,sender,target), and the body checksum.
	 * Allow other checksums for whitelisting. */
	ip_tgts = 0;
	body_known = grey_weak_body;
	req_ck_ip = 0;
	req_ck_body = 0;
	req_ck_triple = 0;
	req_ck_msg = 0;
	msg_tgts = eff_msg_tgts = 0;
	for (req = q->pkt.r.cks; req < req_lim; ++req) {
		/* Note our main checksums of the greylist triple and
		 * the message body.  Search the database for it later */
		if (req->type == DCC_CK_GREY_TRIPLE) {
			req_ck_triple = req;
			continue;
		}

		if (!DCC_CK_OK_CLNT(req->type, 1))
			continue;	/* ignore unknown checksums */
		switch (req->type) {
		case DCC_CK_IP:
			req_ck_ip = req;
			break;
		case DCC_CK_BODY:
			req_ck_body = req;
			break;
		case DCC_CK_GREY_MSG:
			req_ck_msg = req;
			break;
		}
		/* check for whitelisting */
		switch (db_lookup(dcc_emsg, req->type, req->sum,
				  0, MAX_HASH_ENTRIES,
				  &db_sts.hash, &db_sts.rcd, &found_ck)) {
		case DB_FOUND_LATER:
		case DB_FOUND_SYSERR:
			db_broken(dcc_emsg);
			SEND_EMSG(q);
			if (!was_locked)
				db_unlock(0);
			return;
		case DB_FOUND_IT:
			/* ignore deleted checksums */
			tgts = DB_TGTS_CK(found_ck);
			if (tgts == 0)
				continue;

			/* honor whitelisting */
			if (tgts == DCC_TGTS_OK2
			    && op != DCC_OP_GREY_WHITE) {
				op = DCC_OP_GREY_WHITE;
				++dccd_stats.respwhite;
			}

			switch (req->type) {
			case DCC_CK_BODY:
				/* notice if the target body exists at all */
				body_known = 1;
				break;
			case DCC_CK_GREY_MSG:
				msg_tgts = tgts;
				if (msg_tgts != DCC_TGTS_TOO_MANY) {
					/* already reported to DCC */
					eff_msg_tgts = 1;
				}
				break;
			case DCC_CK_IP:
				ip_tgts = tgts;
				break;
			default:
				break;
			}
			break;
		case DB_FOUND_EMPTY:
		case DB_FOUND_CHAIN:
		case DB_FOUND_INTRUDER:
			break;
		}
	}
	if (!req_ck_triple) {
		send_error(q, "missing DCC_CK_GREY_TRIPLE checksum for %s",
			   dcc_req_op2str(&q->pkt.a));
		if (!was_locked)
			db_unlock(0);
		return;
	}
	if (op == DCC_OP_GREY_REPORT && !grey_weak_body) {
		if (!req_ck_body) {
			send_error(q, "missing body checksum for %s",
				   dcc_req_op2str(&q->pkt.a));
			if (!was_locked)
				db_unlock(0);
			return;
		}
		if (!req_ck_msg) {
			send_error(q, "missing DCC_CK_GREY_MSG checksum for %s",
				   dcc_req_op2str(&q->pkt.a));
			if (!was_locked)
				db_unlock(0);
			return;
		}
	}

	/* decide if the embargo should end */
	triple_tgts = search_grey(dcc_emsg,
				  req_ck_triple, req_ck_body, body_known);
	if (triple_tgts == DCC_TGTS_INVALID) {
		SEND_EMSG(q);		/* broken database */
		if (!was_locked)
			db_unlock(0);
		return;
	}
	/* End existing embargo on a newly whitelisted sender so its
	 *	messages are logged.
	 * Quietly prevent future embargos of whitelisted senders that have
	 *	not been greylisted.
	 * Honor grey_weak_ip whitelisting even after it is turned off */
	if (triple_tgts == DCC_TGTS_TOO_MANY) {
		result_tgts = DCC_TGTS_TOO_MANY;
	} else if (op == DCC_OP_GREY_WHITE) {
		result_tgts = eff_msg_tgts ? DCC_TGTS_OK : DCC_TGTS_TOO_MANY;
	} else if (ip_tgts == DCC_TGTS_TOO_MANY) {
		result_tgts = DCC_TGTS_TOO_MANY;
	} else {
		result_tgts = triple_tgts;
	}

	if (op == DCC_OP_GREY_QUERY) {
		++dccd_stats.queries;

	} else if (!(q->flags & Q_FLG_RPT_OK)) {
		++dccd_stats.report_reject;
		clnt_msg(q, "treat report as query from %s", Q_CIP(q));
		++dccd_stats.queries;

	} else {
		/* add a report for this message */
		++dccd_stats.reports;
		new.srvr_id_auth = my_srvr_id | DCC_SRVR_ID_AUTH;
		new_ck = new.cks;
		new.fgs_num_cks = 0;
		if (result_tgts < DCC_TGTS_TOO_MANY) {
			if (req_ck_body) {
				new_ck->type_fgs = DCC_CK_BODY;
				memcpy(new_ck->sum, req_ck_body->sum,
				       sizeof(new_ck->sum));
				++new.fgs_num_cks;
				++new_ck;
			}
			new_msg_tgts = 1;
			DB_TGTS_RCD_SET(&new, 1);
		} else {
			/* embargo now ending (DCC_TGTS_TOO_OK)
			 * or no embargo (DCC_TGTS_TOO_MANY) */
			if (grey_weak_ip && req_ck_ip) {
				new_ck->type_fgs = DCC_CK_IP;
				memcpy(new_ck->sum, req_ck_ip->sum,
				       sizeof(new_ck->sum));
				++new.fgs_num_cks;
				++new_ck;
			}
			new_msg_tgts = 0;
			DB_TGTS_RCD_SET(&new, DCC_TGTS_TOO_MANY);
		}

		/* Include the DCC_CK_GREY_MSG checksum in the database
		 * record for a new embargo.
		 * The message checksum lets an SMTP server report an
		 * embargoed message to the DCC before the embargo is over,
		 * but not report it more than once even if more than one
		 * SMTP client retransmits the message.
		 *
		 * If the DCC_CK_GREY_MSG checksum does not exist in the
		 * database, then tell the DCC client the message is new
		 * and should be reported to the DCC server.  We must put the
		 * the DCC_CK_GREY_MSG into the database so we will recognize
		 * the message as not new when it is retransmitted.
		 *
		 * If the DCC_CK_GREY_MSG checksum exists and is not MANY,
		 * then we may have a retransmission of the message
		 * from another IP address.
		 * We need to tell the DCC client to not report to the
		 * DCC server. The new value for the DCC_CK_GREY_MSG checksum
		 * should be whatever we are using for the triple checksum.
		 *
		 * If the existing count for the DCC_CK_GREY_MSG checksum is
		 * MANY, and the new value for triple checksum is not MANY,
		 * then we have a new copy of the message and a new embargo.
		 * We have a spammer with multiple senders instead of a
		 * legitimate multihomed SMTP client.  We need to tell the
		 * DCC client to report to the DCC server.  To remember
		 * that we told the DCC client to report to the DCC server,
		 * we must first delete the existing MANY report of the
		 * DCC_CK_GREY_MSG checksum. */
		if (eff_msg_tgts != new_msg_tgts
		    && req_ck_msg) {
			if (msg_tgts == DCC_TGTS_TOO_MANY
			    && !add_del(req_ck_msg)) {
				SEND_EMSG(q);
				if (!was_locked)
					db_unlock(0);
				return;
			}
			new_ck->type_fgs = DCC_CK_GREY_MSG;
			memcpy(new_ck->sum, req_ck_msg->sum,
			       sizeof(new_ck->sum));
			++new.fgs_num_cks;
			++new_ck;
		}

		/* Add the triple checksum if we are not whitelisting
		 * the IP address */
		if (!(grey_weak_ip && req_ck_ip)
		    || result_tgts != DCC_TGTS_TOO_MANY) {
			new_ck->type_fgs = DCC_CK_GREY_TRIPLE;
			memcpy(new_ck->sum, req_ck_triple->sum,
			       sizeof(new_ck->sum));
			++new.fgs_num_cks;
		}

		get_ts(new.ts);
		if (!db_add_rcd(dcc_emsg, &new)) {
			db_broken(dcc_emsg);
			SEND_EMSG(q);
			if (!was_locked)
				db_unlock(0);
			return;
		}
	}

	/* In the result sent to the DCC client,
	 * the triple checksum is preceeded by the message checksum
	 * target with a count of 1 if it makes sense to report this
	 * target address to a DCC server despite a current or
	 * just ended embargo */
	resp.body.tgts[0] = htonl(eff_msg_tgts);

	/* Answer SMTP DATA command greylist operations with the target
	 * count of the triple checksum:
	 *	DCC_TGTS_OK if the embargo is just now being removed
	 *	DCC_TGTS_TOO_MANY if there is no current embargo
	 *	DCC_TGTS_OK2 if whitelisted.
	 *	embargo # otherwise */
	resp.body.tgts[1] = htonl(result_tgts);
	resp.hdr.len = (sizeof(resp) - sizeof(resp.body.tgts)
			+ 2*sizeof(resp.body.tgts[0]));

	fin_work(q, &resp);
	if (!was_locked)
		db_unlock(0);
}



static u_char				/* 0=refuse the bad guy, 1=continue */
picky_admn(QUEUE *q, int date)
{
	if (q->clnt_id != my_srvr_id) {
		discard_error(q, "unauthorized or wrong server-ID");
		return 0;
	}

	/* Demand a current timestamp to guard against replay attacks.
	 * This requires that administrators have clocks close to servers,
	 * and that network and server delays be reasonable */
	date = ntohl(date) - wake_time.tv_sec;
	if (date < -30 || date > 30) {
		send_error(q,
			   "rejected %s request; timestamp off by %d seconds",
			   dcc_req_op2str(&q->pkt.a), date);
		return 0;
	}

	return 1;
}



static u_char				/* 1=ok 0=error sent to client */
delete_sub(QUEUE *q, DCC_CK *del_ck,
	   int *was_locked,
	   u_char grey_spam)		/* 1=grey IP address, 2=triple */
{
	DB_RCD_CK *rcd_ck;
	char buf[80];
	DB_PTR prev;
	DCC_TGTS tgts;

	buf[0] = '\0';
	if (grey_spam < 2) {
		*was_locked = db_lock(dcc_emsg);
		if (*was_locked < 0) {
			SEND_EMSG(q);
			return 0;
		}
	}
	switch (db_lookup(dcc_emsg, del_ck->type, del_ck->sum,
			  0, MAX_HASH_ENTRIES,
			  &db_sts.hash, &db_sts.rcd, &rcd_ck)) {
	case DB_FOUND_EMPTY:
	case DB_FOUND_CHAIN:
	case DB_FOUND_INTRUDER:
		if (grey_spam) {
			/* finished if we have not greylisted the spammer */
			if (grey_spam != 1 && !*was_locked)
				db_unlock(0);
			return 1;
		} else {
			snprintf(buf, sizeof(buf),
				 "%s %s not found to delete",
				 dcc_type2str_err(del_ck->type, 0, 1),
				 dcc_ck2str_err(del_ck->type, del_ck->sum));
		}
		break;

	case DB_FOUND_IT:
		tgts = DB_TGTS_CK(rcd_ck);
		/* handle an ordinary delete request */
		if (!grey_spam) {
			if (tgts == 0)
				snprintf(buf, sizeof(buf),
					 "%s %s already deleted",
					 dcc_type2str_err(del_ck->type, 0, 1),
					 dcc_ck2str_err(del_ck->type,
							del_ck->sum));
			break;
		}
		/* Otherwise, we are deleting a greylist checksum.
		 * If we are deleting very new greylist records,
		 * we can cheat and avoid adding to the database
		 * by scribbling over the records.
		 * If there is an older record that might have been flooded,
		 * we must add a delete request to the database
		 * that will itself be flooded. */
		for (;;) {
			/* finished if the target has already been deleted */
			if (tgts == 0) {
				if (grey_spam != 1 && !*was_locked)
					db_unlock(0);
				return 1;
			}
			if (db_sts.rcd.s.rptr < oflods_max_pos
			    || oflods_max_pos == 0) {
				/* We need to add a delete request, because
				 * the record might have been flooded */
				break;
			}
			prev = DB_PTR_EX(rcd_ck->prev);
			/* try to delete the entire greylist entry
			 * starting with the target triple checksum */
			do {
				/* only if the embargo is not over */
				if (DB_TGTS_CK(rcd_ck) >= DCC_TGTS_TOO_MANY)
					goto need_rcd;
				DB_TGTS_CK_SET(rcd_ck, 0);
			} while (--rcd_ck >= db_sts.rcd.d.r->cks);
			DB_TGTS_RCD_SET(db_sts.rcd.d.r, 0);

			/* stop after the last record */
			if (prev == DB_PTR_NULL) {
				if (grey_spam != 1 && !*was_locked)
					db_unlock(0);
				return 1;
			}
			rcd_ck = db_map_rcd_ck(dcc_emsg, &db_sts.rcd,
					       prev, del_ck->type);
			if (!rcd_ck) {
				SEND_EMSG(q);
				if (!*was_locked)
					db_unlock(0);
				return 0;
			}
			tgts = DB_TGTS_CK(rcd_ck);
		}
need_rcd:;
		break;

	case DB_FOUND_LATER:
	case DB_FOUND_SYSERR:
		db_broken(dcc_emsg);
		if (!*was_locked)
			db_unlock(0);
		SEND_EMSG(q);
		return 0;
	}

	/* Add the delete request to the database even if the
	 * checksum seems deleted or absent so that we will
	 * flood the delete request.  This is required to ensure that
	 * records get deleted when they are created at one DCC server
	 * and deleted at another. */
	if (!add_del(del_ck))
		BUFCPY(buf, dcc_emsg);

	if (buf[0] != '\0') {
		if (!*was_locked)
			db_unlock(0);
		send_error(q, buf);
		return 0;
	}

	if (grey_spam != 1 && !*was_locked)
		db_unlock(0);

	TMSG3(ADMN, "deleted %s %s for %s",
	      dcc_type2str_err(del_ck->type, 0, 1),
	      dcc_ck2str_err(del_ck->type, del_ck->sum),
	      Q_CIP(q));
	return 1;
}



void
do_delete(QUEUE *q)
{
	int was_locked;

	TMSG3(ADMN, "received %s from client-ID %d at %s",
	      dcc_op2str(DCC_OP_DELETE, 0, 0),
	      (DCC_CLNT_ID)htonl(q->pkt.hdr.sender), Q_CIP(q));
	++dccd_stats.admin;

	if (!ck_clnt_srvr_id(q))
		return;
	if (!picky_admn(q, q->pkt.d.date))
		return;
	/* if we've already answered, then just repeat ourselves */
	if (ridc_get(q)) {
		repeat_resp(q);
		return;
	}

	if (q->pkt_len != sizeof(q->pkt.d)) {
		send_error(q, "wrong packet length %d for %s",
			   q->pkt_len, dcc_op2str(DCC_OP_DELETE, 0, 0));
		return;
	}
	if (q->pkt.d.ck.len != sizeof(q->pkt.d.ck)) {
		/* relax this someday if necessary */
		send_error(q, "unknown checksum length %d", q->pkt.d.ck.len);
		return;
	}
	if (!DCC_CK_OK_DB(q->pkt.d.ck.type)) {
		send_error(q, "unknown checkksum type %d", q->pkt.d.ck.type);
		return;
	}

	if (delete_sub(q, &q->pkt.d.ck, &was_locked, 0)) {
		send_ok(q);

		/* need to clean the database after a deletion
		 * to correct the totals of other checksums */
		need_del_dbclean = "checksum deleted";
	}
}



/* restore the embargo against a sender of spam */
void
do_grey_spam(QUEUE *q)
{
	int was_locked;

	TMSG3(QUERY, "received %s from %d at %s",
	      dcc_req_op2str(&q->pkt.a),
	      (DCC_CLNT_ID)ntohl(q->pkt.hdr.sender), Q_CIP(q));
	if (!ck_clnt_id(q))
		return;
	if (q->clnt_id == DCC_ID_ANON) {
		anon_msg("drop anomymous %s request from %s",
			 dcc_req_op2str(&q->pkt.a), Q_CIP(q));
		return;
	}

	/* require the checksum of the (source,sender,target) triple */
	if (q->pkt_len != sizeof(q->pkt.gs)) {
		send_error(q, "wrong packet length %d for %s",
			   q->pkt_len, dcc_op2str(DCC_OP_GREY_SPAM, 0, 0));
		return;
	}
	if (q->pkt.gs.triple.type != DCC_CK_GREY_TRIPLE) {
		send_error(q, "wrong checksum %s for %s",
			   dcc_type2str_err(q->pkt.gs.triple.type, 0, 1),
			   dcc_op2str(DCC_OP_GREY_SPAM, 0, 0));
		return;
	}
	if (q->pkt.gs.triple.len != sizeof(q->pkt.gs.triple)) {
		send_error(q, "unknown triple checksum length %d",
			   q->pkt.gs.ip.len);
		return;
	}
	if (q->pkt.gs.ip.type != DCC_CK_IP) {
		send_error(q, "wrong checksum %s for %s",
			   dcc_type2str_err(q->pkt.gs.ip.type, 0, 1),
			   dcc_op2str(DCC_OP_GREY_SPAM, 0, 0));
		return;
	}
	if (q->pkt.gs.ip.len != sizeof(q->pkt.gs.ip)) {
		send_error(q, "unknown IP checksum length %d",
			   q->pkt.gs.ip.len);
		return;
	}

	if (!delete_sub(q, &q->pkt.gs.ip, &was_locked, 1))
		return;
	if (!delete_sub(q, &q->pkt.gs.triple, &was_locked, 2))
		return;
	send_ok(q);
}



static void
do_flod(QUEUE *q)
{
	DCC_ADMN_RESP check;
	int print_len;
	u_int32_t val, arg;
	DCC_AOP_FLODS fop;
	FLOD_MMAP *mp;
	OFLOD_INFO *ofp;
	u_char mapped, found_it;

	val = ntohl(q->pkt.a.val1);
	fop = val % 256;
	arg = val / 256;

	if (fop != DCC_AOP_FLOD_LIST
	    && !picky_admn(q, q->pkt.a.date))
		return;

	switch (fop) {
	case DCC_AOP_FLOD_CHECK:
		if (host_id_last < db_time.tv_sec - DCC_SRVR_ID_SEC0)
			host_id_next = 0;
		next_flods_ck = 0;
		if (!check_load_ids(dcc_emsg, my_srvr_id)) {
			SEND_EMSG(q);
			return;
		}
		flod_stats_printf(check.val.string, sizeof(check.val.string),
				  (flods_off || flods_st == FLODS_ST_OFF
				   || !DB_IS_LOCKED())
				  ? 0
				  : (flods_st != FLODS_ST_ON) ? 1
				  : 2,
				  oflods.total, oflods.active, iflods.active);
		check.hdr.len = (strlen(check.val.string)
				 + sizeof(check)-sizeof(check.val));
		check.hdr.op = DCC_OP_ADMN;
		send_resp(q, &check.hdr);
		flods_ck(1);
		check_blacklist_file(1);
		return;

	case DCC_AOP_FLOD_SHUTDOWN:
		if (ridc_get(q)) {
			repeat_resp(q);
			return;
		}
		++flods_off;
		iflods_stop("shutdown flooding", 0);
		oflods_stop(0);
		send_ok(q);
		return;

	case DCC_AOP_FLOD_HALT:
		if (ridc_get(q)) {
			repeat_resp(q);
			return;
		}
		++flods_off;
		iflods_stop("stop flooding", 1);
		oflods_stop(1);
		send_ok(q);
		return;

	case DCC_AOP_FLOD_RESUME:
		if (ridc_get(q)) {
			repeat_resp(q);
			return;
		}
		if (!check_load_ids(dcc_emsg, my_srvr_id)) {
			SEND_EMSG(q);
			return;
		}
		flods_off -= flod_db_sick+1;
		flod_db_sick = 0;
		flods_restart("resume flooding");
		send_ok(q);
		flods_ck(0);
		return;

	case DCC_AOP_FLOD_REWIND:
		if (ridc_get(q)) {
			repeat_resp(q);
			return;
		}
		if (!flod_mmaps) {
			send_error(q, "flooding off or %s not available",
				   FLOD_MMAP_PATH(grey_on));
			return;
		}
		found_it = (arg == DCC_ID_INVALID);
		for (mp = flod_mmaps->mmaps;
		     mp <= LAST(flod_mmaps->mmaps);
		     ++mp) {
			if (arg == DCC_ID_INVALID
			    || mp->rem_id == arg) {
				mp->flags |= OFLOD_MMAP_FG_NEED_REWIND;
				mp->flags &= ~OFLOD_MMAP_FG_FFWD_IN;
				dcc_trace_msg("rewind flood from server-ID %d",
					      arg);
				found_it = 1;
			}
		}
		if (!found_it) {
			send_error(q, "unknown server-ID %d for %s",
				   arg, dcc_req_op2str(&q->pkt.a));
		} else {
			send_ok(q);
			flods_ck(0);
		}
		return;

	case DCC_AOP_FLOD_LIST:
		print_len = flods_list(check.val.string,
				       sizeof(check.val.string),
				       q->clnt_id == DCC_ID_ANON);
		check.hdr.len = print_len + sizeof(check)-sizeof(check.val);
		check.hdr.op = DCC_OP_ADMN;
		send_resp(q, &check.hdr);
		return;

	case DCC_AOP_FLOD_STATS:
	case DCC_AOP_FLOD_STATS_CLEAR:
		print_len = flod_stats(check.val.string,
				       sizeof(check.val.string),
				       arg,
				       fop == DCC_AOP_FLOD_STATS_CLEAR);
		check.hdr.len = print_len + sizeof(check)-sizeof(check.val);
		check.hdr.op = DCC_OP_ADMN;
		send_resp(q, &check.hdr);
		flods_ck(0);
		return;

	case DCC_AOP_FLOD_FFWD_IN:
	case DCC_AOP_FLOD_FFWD_OUT:
		if (ridc_get(q)) {
			repeat_resp(q);
			return;
		}
		if (flod_mmaps) {
			mapped = 0;
		} else {
			oflods_load();
			mapped = 1;
		}
		ofp = oflods.infos;
		for (;;) {
			if ((mp = ofp->mp) != 0
			    && mp->rem_id == arg) {
				/* found the target */
				if (fop == DCC_AOP_FLOD_FFWD_OUT) {
					ofp->cur_pos = db_csize;
					if (ofp->s < 0)
					    mp->confirm_pos = db_csize;
					dcc_trace_msg("fast forward flood to"
						      " server-ID %d",
						      arg);
				} else {
					mp->flags |= OFLOD_MMAP_FG_FFWD_IN;
					mp->flags &= ~OFLOD_MMAP_FG_NEED_REWIND;
				}
				send_ok(q);
				if (!mapped)
					flods_ck(0);
				break;
			}
			if (++ofp > LAST(oflods.infos)) {
				send_error(q, "unknown server-ID %d for %s",
					   arg, dcc_req_op2str(&q->pkt.a));
				break;
			}
		}
		if (mapped)
			oflods_unmap();
		return;

		if (ridc_get(q)) {
			repeat_resp(q);
			return;
		}
		if (!flod_mmaps) {
			send_error(q, "flooding off or %s not available",
				   FLOD_MMAP_PATH(grey_on));
			return;
		}
		ofp = oflods.infos;
		for (;;) {
			if ((mp = ofp->mp) != 0
			    && mp->rem_id == arg) {
				ofp->cur_pos = db_csize;
				if (ofp->s < 0)
					mp->confirm_pos = db_csize;
				dcc_trace_msg("fast forward flood to server-ID"
					      " %d",
					      arg);
				send_ok(q);
				flods_ck(0);
				return;
			}
			if (++ofp > LAST(oflods.infos)) {
				send_error(q, "unknown server-ID %d for %s",
					   arg, dcc_req_op2str(&q->pkt.a));
				return;
			}
		}
	}

	send_error(q, "unrecognized %s value %d",
		   dcc_req_op2str(&q->pkt.a), fop);
}



void
stats_clear(void)
{
	OFLOD_INFO *ofp;
	time_t secs;

	memset(&dccd_stats, 0, sizeof(dccd_stats));
	for (ofp = oflods.infos; ofp <= LAST(oflods.infos); ++ofp) {
		if (ofp->hostname[0] == '\0')
			continue;

		/* The counts reported to `cdcc stats` are sums
		 * of the dccd_stats and ofp->cnts values.  Bias
		 * the dccd_stats values by the current ofp->cnts values
		 * so the reported counts will be zero.  When the flooding
		 * connection is closed, the ofp->cnts values will be added
		 * to the dccd_stats values. */
		dccd_stats.iflod_total -= ofp->cnts.total;
		dccd_stats.iflod_accepted -= ofp->cnts.accepted;
		dccd_stats.iflod_stale -= ofp->cnts.stale.val;
		dccd_stats.iflod_dup -= ofp->cnts.dup.val;
		dccd_stats.iflod_ok2 -= ofp->cnts.ok2.val;
		dccd_stats.iflod_not_deleted -= ofp->cnts.not_deleted.val;
	}

	cycle_q_delay();
	secs = dccd_stats.reset.tv_sec - db_time.tv_sec;
	clients_clear(secs >= RL_AVG_DAY && secs <= RL_AVG_DAY + 60*60);

	memset(&db_stats, 0, sizeof(db_stats));
	dccd_stats.reset = db_time;
}



static u_char				/* 1=sent 0=something wrong */
stats_send(QUEUE *q)
{
	DCC_ADMN_RESP stats;
	char tbuf[80];
	OFLOD_INFO *ofp;
	IFLOD_INFO *ifp;
	int oflods_connecting, iflods_connecting;
	u_int iflod_total, iflod_accepted, iflod_stale;
	u_int iflod_dup, iflod_ok2, iflod_not_deleted;
	char flod_buf[60];
	char reset_buf1[30], reset_buf2[6], clients_reset[40], now_buf[20];
	int clients;
	const char *client_ovf;
	struct tm tm;
	int was_locked;
	int blen, plen, len;

	/* see if the database is locked so we can report that */
	was_locked = db_lock(dcc_emsg);
	if (was_locked < 0) {
		SEND_EMSG(q);
		return 0;
	} else if (!was_locked
		   && !db_unlock(dcc_emsg)) {
		SEND_EMSG(q);
		return 0;
	}

	tbuf[0] = '\0';
	if (dccd_tracemask & DCC_TRACE_ADMN_BIT)
		strcat(tbuf, "ADMN ");
	if (dccd_tracemask & DCC_TRACE_ANON_BIT)
		strcat(tbuf, "ANON ");
	if (dccd_tracemask & DCC_TRACE_CLNT_BIT)
		strcat(tbuf, "CLNT ");
	if (dccd_tracemask & DCC_TRACE_RLIM_BIT)
		strcat(tbuf, "RLIM ");
	if (dccd_tracemask & DCC_TRACE_QUERY_BIT)
		strcat(tbuf, "QUERY ");
	if (dccd_tracemask & DCC_TRACE_RIDC_BIT)
		strcat(tbuf, "RIDC ");
	if (dccd_tracemask & DCC_TRACE_FLOD_BIT)
		strcat(tbuf, "FLOOD ");
	if (dccd_tracemask & DCC_TRACE_FLOD2_BIT)
		strcat(tbuf, "FLOOD2 ");
	if (dccd_tracemask & DCC_TRACE_IDS_BIT)
		strcat(tbuf, "IDS ");
	if (dccd_tracemask & DCC_TRACE_BL_BIT)
		strcat(tbuf, "BL ");

	clients = clients_get(0, 0, 0, 0, 0);
	if (clients >= 0) {
		client_ovf = "";
	} else {
		client_ovf = ">";
		clients = -clients;
	}
	if (clients_cleared > db_time.tv_sec-CLIENTS_AGE) {
		strftime(clients_reset, sizeof(clients_reset),
			 "since %X", dcc_localtime(clients_cleared, &tm));
	} else {
		snprintf(clients_reset, sizeof(clients_reset),
			 "in %d hours", CLIENTS_AGE/(60*60));
	}

	oflods_connecting = 0;
	iflod_total = dccd_stats.iflod_total;
	iflod_accepted = dccd_stats.iflod_accepted;
	iflod_stale = dccd_stats.iflod_stale;
	iflod_dup = dccd_stats.iflod_dup;
	iflod_ok2 = dccd_stats.iflod_ok2;
	iflod_not_deleted = dccd_stats.iflod_not_deleted;
	for (ofp = oflods.infos; ofp <= LAST(oflods.infos); ++ofp) {
		if (ofp->s >= 0 && !(ofp->flags & OFLOD_FG_CONNECTED))
			++oflods_connecting;
		iflod_total += ofp->cnts.total;
		iflod_accepted += ofp->cnts.accepted;
		iflod_stale += ofp->cnts.stale.val;
		iflod_dup += ofp->cnts.dup.val;
		iflod_ok2 += ofp->cnts.ok2.val;
		iflod_not_deleted += ofp->cnts.not_deleted.val;
	}
	iflods_connecting = 0;
	for (ifp = iflods.infos; ifp <= LAST(iflods.infos); ++ifp) {
		if (ifp->s >= 0 && !(ifp->flags & IFLOD_FG_VERS_CK))
			++iflods_connecting;
	}
	strftime(now_buf, sizeof(now_buf), "%b %d %X",
		 dcc_localtime(db_time.tv_sec, &tm));
	strftime(reset_buf1, sizeof(reset_buf1),"%b %d %X",
		 dcc_localtime(dccd_stats.reset.tv_sec, &tm));
	strftime(reset_buf2, sizeof(reset_buf2), "%Z", &tm);

	blen = min(sizeof(stats.val.string), ntohl(q->pkt.a.val1));
	plen = snprintf(stats.val.string, blen,
	    "    version "DCC_VERSION"  %s%s%stracing %s\n"
	    "%7d hash entries %6d used "L_D9PAT" DB bytes\n"
	    "%5d ms delay  %d NOPs  %d ADMN  %d query  %s%d clients %s\n",

	    was_locked ? "" : "DB UNLOCKED  ",
	    query_only ? "Q-mode  " : "",
	    grey_on ? "Greylist  " : "",
	    tbuf[0] ? tbuf : "nothing",

	    HASH_LEN_EXT(db_hash_len), HASH_LEN_EXT(db_hash_used), db_csize,

	    get_q_delay_us(q) / 1000,

	    dccd_stats.nops, dccd_stats.admin, dccd_stats.queries,
	    client_ovf, clients, clients_reset);
	if (plen >= blen)
		plen = blen-1;
	blen -= plen;

	if (grey_on) {
		len = snprintf(&stats.val.string[plen], blen,
	    "%7d reports %2d whitelisted\n",

	    dccd_stats.reports,
	    dccd_stats.respwhite);

	} else {
		len = snprintf(&stats.val.string[plen], blen,
	    "%7d reports %7d>10 %7d>100 %7d>1000 %7d many\n"
	    "        answers %7d>10 %7d>100 %7d>1000 %7d many\n",

	    dccd_stats.reports,
	    (dccd_stats.report10 + dccd_stats.report100
	     + dccd_stats.report1000 + dccd_stats.reportmany),
	    (dccd_stats.report100 + dccd_stats.report1000
	     + dccd_stats.reportmany),
	    dccd_stats.report1000 + dccd_stats.reportmany,
	    dccd_stats.reportmany,

	    (dccd_stats.resp10 + dccd_stats.resp100
	     + dccd_stats.resp1000 + dccd_stats.respmany),
	    dccd_stats.resp100 + dccd_stats.resp1000 + dccd_stats.respmany,
	    dccd_stats.resp1000 + dccd_stats.respmany,
	    dccd_stats.respmany);
	}
	if (len >= blen)
		len = blen-1;
	blen -= len;
	plen += len;

	len = snprintf(&stats.val.string[plen], blen,
	    "%5d bad IDs %4d passwds %3d error responses %5d retransmitted\n",

	    dccd_stats.unknown_ids, dccd_stats.bad_passwd,
	    dccd_stats.send_errors,  dccd_stats.report_retrans);
	if (len >= blen)
		len = blen-1;
	blen -= len;
	plen += len;

	if (!grey_on) {
		len = snprintf(&stats.val.string[plen], blen,
	    "%5d answers rate-limited %3d anonymous"
	    "       %5d rejected reports\n",

	    dccd_stats.rl, dccd_stats.anon_rl,  dccd_stats.report_reject);
		if (len >= blen)
			len = blen-1;
		blen -= len;
		plen += len;
	}

	len = snprintf(&stats.val.string[plen], blen,
	    "    %s %6d total flooded in\n"
	    "%5d accepted %3d stale %5d dup  %5d white    %d delete\n"

	    "%5d reports added between %s.%06d %s and %s",

	    flod_stats_printf(flod_buf, sizeof(flod_buf),
			      (flods_off || flods_st == FLODS_ST_OFF
			       || !DB_IS_LOCKED())
			      ? 0
			      : (flods_st != FLODS_ST_ON) ? 1
			      : 2,
			      oflods.total,
			      oflods.active - oflods_connecting,
			      iflods.active - iflods_connecting),
	    iflod_total,
	    iflod_accepted, iflod_stale, iflod_dup,
	    iflod_ok2, iflod_not_deleted,

	    dccd_stats.adds+db_stats.adds,
	    reset_buf1, (int)dccd_stats.reset.tv_usec, reset_buf2,
	    now_buf);
	if (len >= blen)
		len = blen-1;
	blen -= len;
	plen += len;

	stats.hdr.len = plen + sizeof(stats)-sizeof(stats.val);
	stats.hdr.op = DCC_OP_ADMN;
	send_resp(q, &stats.hdr);
	return 1;
}



void
do_nop(QUEUE *q)
{
	DCC_OK buf;
	int usecs;

	/* respond immediately to even anonymous NOPs so that clients
	 * that are confused about passwords and whether they are anonymous
	 * do not retransmit unnecessarily */
	TMSG3(ADMN, "received %s from client-ID %d at %s",
	      dcc_req_op2str(&q->pkt.a),
	      (DCC_CLNT_ID)htonl(q->pkt.hdr.sender), Q_CIP(q));
	++dccd_stats.nops;

	if (!ck_clnt_srvr_id(q)) {
		if (q->rl)
			++q->rl->nops;
		free_q(q);
		return;
	}

	++q->rl->nops;

	if (anon_off && q->clnt_id == DCC_ID_ANON) {
		anon_msg("drop anonymous NOP from %s", Q_CIP(q));
		free_q(q);
		return;
	}

	usecs = get_q_delay_us(q);
	if (usecs >= DCC_MAX_RTT) {
		TMSG1(BL, "drop excessively delayed NOP from %s", Q_CIP(q));
		free_q(q);
		return;
	}

	buf.max_pkt_vers = DCC_PKT_VERSION_MAX;
	usecs /= 1000;
	buf.qdelay_ms = htons(usecs);
	strncpy(buf.brand, brand, sizeof(buf.brand));
	buf.hdr.op = DCC_OP_OK;
	buf.hdr.len = sizeof(buf);
	send_resp(q, &buf.hdr);
	free_q(q);
}



/* deal with an adminstative request */
void
do_admn(QUEUE *q)
{
	u_int32_t val1;
	DCC_ADMN_RESP resp;
	int len, offset, delay;

	val1 = ntohl(q->pkt.a.val1);
	TMSG6(ADMN, "received %s val1=%#x val2=%#x val3=%#x from client-ID"
	      " %d at %s",
	      dcc_req_op2str(&q->pkt.a), val1, q->pkt.a.val2, q->pkt.a.val3,
	      (DCC_CLNT_ID)htonl(q->pkt.hdr.sender), Q_CIP(q));
	++dccd_stats.admin;

	if (!ck_clnt_srvr_id(q))
		return;
	if (((q->pkt.a.aop != DCC_AOP_STATS
	      && q->pkt.a.aop != DCC_AOP_FLOD
	      && q->pkt.a.aop != DCC_AOP_ANON_DELAY
	      && q->pkt.a.aop != DCC_AOP_CLIENTS_ID)
	     || (anon_off && q->clnt_id == DCC_ID_ANON))
	    && !picky_admn(q, q->pkt.a.date))
		return;
	if (q->pkt_len != sizeof(DCC_ADMN_REQ)) {
		send_error(q, "%s size = %d",
			   dcc_req_op2str(&q->pkt.a), q->pkt_len);
		return;
	}

	switch (q->pkt.a.aop) {
	case DCC_AOP_STOP:		/* stop gracefully */
		if (ridc_get(q)) {
			repeat_resp(q);
			return;
		}
		if (!stopint) {
			stopint = -1;
			next_flods_ck = 0;
		}
		send_ok(q);
		db_unlock(0);
		db_unload(0);
		return;

	case DCC_AOP_NEW_IDS:		/* load ID's and passwords */
		/* if we've already answered, then just repeat ourselves */
		if (ridc_get(q)) {
			repeat_resp(q);
			return;
		}
		/* that the file name cannot be specified over the wire
		 * is a security feature not an oversight */
		switch (check_load_ids(dcc_emsg, my_srvr_id)) {
		case 0:
			SEND_EMSG(q);
			return;
		case 1:
			send_ok(q);
			dcc_trace_msg("reloaded ID's and keys");
			flods_restart("restart flooding with new IDs");
			return;
		case 2:
			send_ok(q);
			return;
		}
		return;

	case DCC_AOP_FLOD:		/* control flooding */
		do_flod(q);
		return;

	case DCC_AOP_DB_UNLOCK:		/* start switch to new database */
		/* repeat answer to identical question */
		if (ridc_get(q)) {
			repeat_resp(q);
			return;
		}
		if (!flods_off || iflods.active != 0 || oflods.active != 0) {
			send_error(q, "flooding not stopped before %s",
				   dcc_req_op2str(&q->pkt.a));
			return;
		}
		/* answer before working so dbclean does not time out */
		send_ok(q);
		dcc_trace_msg("database cleaning begun");
		db_unlock(0);
		db_unload(0);
		next_flods_ck = 0;
		/* don't start our own cleaning */
		del_dbclean_next = db_time.tv_sec + DEL_DBCLEAN_SECS;
		dbclean_limit = db_time.tv_sec + dbclean_limit_secs;
		/* Dbclean expects us to remove its separate hold on flooding
		 * so that it will not need to talk to us after telling us
		 * to close the old database.
		 * On some systems with lame mmap() support including BSD/OS,
		 * the daemon can stall for minutes in close(). */
		--flods_off;
		return;

	case DCC_AOP_DB_NEW:		/* finish switch to new database */
		if (ridc_get(q)) {
			repeat_resp(q);
			return;
		}
		if (DB_IS_LOCKED()) {
			send_error(q, "%s received before %s",
				   dcc_req_op2str(&q->pkt.a),
				   dcc_op2str(DCC_OP_ADMN, DCC_AOP_DB_NEW, 0));
			return;
		}
		/* send "ok" now because we may stall waiting to reopen */
		send_ok(q);
		db_close(0, 1);
		dccd_stats.adds += db_stats.adds;
		if (!dccd_db_open(DB_OPEN_LOCK_WAIT))
			dcc_logbad(dcc_ex_code,
				   "could not restart database %s: %s",
				   DCC_NM2PATH(db_nm), dcc_emsg);
		dcc_trace_msg(DCC_VERSION" database %s reopened with %s",
			      DCC_NM2PATH(db_nm), db_window_size);
		flods_off -= flod_db_sick;
		flod_db_sick = 0;
		flods_restart("database reopened");
		next_flods_ck = 0;	/* possibly reap dbclean child */
		return;

	case DCC_AOP_STATS:		/* return counters */
		/* we cannot just repeat ourselves for retransmissions,
		 * because the answer is too big to save */
		stats_send(q);
		return;

	case DCC_AOP_STATS_CLEAR:	/* return and then zero counters */
		/* we cannot just repeat ourselves for retransmissions,
		 * because the answer is too big to save */
		if (stats_send(q))
			stats_clear();
		return;

	case DCC_AOP_TRACE_ON:
	case DCC_AOP_TRACE_OFF:
		/* non-redundant message */
		if (!(DCC_TRACE_ADMN_BIT & dccd_tracemask))
			dcc_trace_msg("received %s %#x from client-ID %d at %s",
				      dcc_req_op2str(&q->pkt.a), val1,
				      (DCC_CLNT_ID)htonl(q->pkt.hdr.sender),
				      Q_CIP(q));
		if ((val1 & ~DCC_TRACE_OFF_BITS) != 0 || val1 == 0) {
			send_error(q, "invalid trace bits %#x", val1);
			return;
		}
		if (q->pkt.a.aop == DCC_AOP_TRACE_OFF)
			dccd_tracemask &= ~val1;
		else
			dccd_tracemask |= val1;
		send_ok(q);
		return;

	case DCC_AOP_CLIENTS:
		/* we cannot just repeat ourselves for retransmissions,
		 * because the answer is too big to save */
		offset = val1 >> 16;
		val1 &= 0xffff;
		len = q->pkt.a.val2 * sizeof(DCC_ADMN_RESP_CLIENTS);
		if (len > ISZ(resp.val))
			len = ISZ(resp.val);
		clients_get(&resp.val, &len, offset, val1, q->pkt.a.val3);
		resp.hdr.len = len + sizeof(resp)-sizeof(resp.val);
		resp.hdr.op = DCC_OP_ADMN;
		send_resp(q, &resp.hdr);
		return;

	case DCC_AOP_CLIENTS_ID:
		/* we cannot just repeat ourselves for retransmissions,
		 * because the answer is too big to save */
		offset = val1 >> 16;
		val1 &= 0xffff;
		len = q->pkt.a.val2 * sizeof(DCC_ADMN_RESP_CLIENTS);
		if (len > ISZ(resp.val))
			len = ISZ(resp.val);
		clients_get_id(&resp.val, &len, offset, val1, q->pkt.a.val3);
		resp.hdr.len = len + sizeof(resp)-sizeof(resp.val);
		resp.hdr.op = DCC_OP_ADMN;
		send_resp(q, &resp.hdr);
		return;

	case DCC_AOP_ANON_DELAY:
		/* repeat answer to identical question */
		if (ridc_get(q)) {
			repeat_resp(q);
			return;
		}
		if (anon_off)
			delay = DCC_ANON_DELAY_FOREVER;
		else
			delay = anon_delay_us/1000;
		resp.val.anon_delay.delay[0] = delay>>8;
		resp.val.anon_delay.delay[1] = delay;
		if (anon_delay_inflate== (u_int)-1) {
			resp.val.anon_delay.inflate[0] = 0;
			resp.val.anon_delay.inflate[1] = 0;
			resp.val.anon_delay.inflate[2] = 0;
			resp.val.anon_delay.inflate[3] = 0;
		} else {
			resp.val.anon_delay.inflate[0] = anon_delay_inflate>>24;
			resp.val.anon_delay.inflate[1] = anon_delay_inflate>>16;
			resp.val.anon_delay.inflate[2] = anon_delay_inflate>>8;
			resp.val.anon_delay.inflate[3] = anon_delay_inflate;
		}
		delay = (q->pkt.a.val2<<8) + q->pkt.a.val3;
		if (delay != DCC_NO_ANON_DELAY
		    && q->clnt_id == my_srvr_id) {
			if (delay == DCC_ANON_DELAY_FOREVER) {
				anon_off = 1;
			} else {
				anon_off = 0;
				if (delay > DCC_ANON_DELAY_MAX/1000)
					delay = DCC_ANON_DELAY_MAX/1000;
				anon_delay_us = delay*1000;
				if (val1 == 0)
					val1 = DCC_ANON_INFLATE_OFF;
				anon_delay_inflate = val1;
			}
		}
		resp.hdr.len = (sizeof(resp)-sizeof(resp.val)
				+sizeof(resp.val.anon_delay));
		resp.hdr.op = DCC_OP_ADMN;
		send_resp(q, &resp.hdr);
		return;
	}

	send_error(q, "invalid %s", dcc_req_op2str(&q->pkt.a));
}



static void
send_resp(const QUEUE *q, DCC_HDR *hdr)
{
	u_int save_len;
	int len, i;

	len = hdr->len;
	hdr->len = htons(hdr->len);
	/* callers must have dealt with the variations due to versions */
	if (q->pkt.hdr.pkt_vers < DCC_PKT_VERSION_MIN)
		hdr->pkt_vers = DCC_PKT_VERSION_MIN;
	else if (q->pkt.hdr.pkt_vers > DCC_PKT_VERSION_MAX)
		hdr->pkt_vers = DCC_PKT_VERSION_MAX;
	else
		hdr->pkt_vers = q->pkt.hdr.pkt_vers;
	hdr->sender = htonl(my_srvr_id);
	hdr->op_nums = q->pkt.hdr.op_nums;
	if (q->passwd[0] == '\0')
		dcc_sign((char *)&q->pkt.hdr.op_nums,
			 sizeof(q->pkt.hdr.op_nums),
			 hdr, len);
	else
		dcc_sign(q->passwd, sizeof(q->passwd),
			 hdr, len);

	if (q->ridc) {
		save_len = len-sizeof(*hdr)-sizeof(DCC_SIGNATURE);
		if (save_len > ISZ(q->ridc->result)) {
			if (hdr->op == DCC_OP_ERROR)
				save_len = sizeof(q->ridc->result);
			else
				dcc_logbad(EX_SOFTWARE, "RIDC buffer overflow");
		}
		q->ridc->len = save_len;
		memcpy(&q->ridc->result, hdr+1, save_len);
		q->ridc->op = hdr->op;
	}

	i = sendto(q->sp->udp, hdr, len, 0,
		   &q->clnt_su.sa, DCC_SU_LEN(&q->clnt_su));
	if (i < 0) {
		clnt_msg(q, "sendto(%s, client %d at %s: %s",
			 dcc_hdr_op2str(hdr), q->clnt_id, Q_CIP(q),
			 ERROR_STR());
	} else if (len != i) {
		clnt_msg(q, "sendto(%s, client %d at %s)=%d instead of %d",
			 dcc_hdr_op2str(hdr), q->clnt_id, Q_CIP(q), i, len);
	} else {
		TMSG4(ADMN, "sent %s to client-ID %d at %s for %s",
		      dcc_hdr_op2str(hdr),
		      (DCC_CLNT_ID)htonl(q->pkt.hdr.sender), Q_CIP(q),
		      dcc_req_op2str(&q->pkt.a));
	}

}



/* do not send an error response to a client */
void
discard_error(const QUEUE *q, const char *p, ...)
{
	char msg[DCC_ERROR_MSG_LEN];
	va_list args;

	/* log the message */
	va_start(args, p);
	vsnprintf(msg, sizeof(msg), p, args);
	va_end(args);
	clnt_msg(q, "\"%s\" not sent to client %d at %s",
		 msg, q->clnt_id, Q_CIP(q));

	if (q->ridc)
		q->ridc->op = DCC_OP_INVALID;

	++dccd_stats.send_errors;
}



/* send an error response to a client */
static void
send_error(const QUEUE *q, const char *p, ...)
{
	DCC_ERROR buf;
	int slen;
	va_list args;


	/* build and log the message */
	va_start(args, p);
	slen = vsnprintf(buf.msg, sizeof(buf.msg), p, args);
	if (slen > ISZ(buf.msg)-1)
		slen = ISZ(buf.msg)-1;
	va_end(args);
	clnt_msg(q, "\"%s\" sent to client %d at %s",
		 buf.msg, q->clnt_id, Q_CIP(q));

	/* send it */
	buf.hdr.len = sizeof(buf)-sizeof(buf.msg)+slen+1;
	buf.hdr.op = DCC_OP_ERROR;
	send_resp(q, &buf.hdr);

	++dccd_stats.send_errors;
}



static void
send_emsg(const QUEUE *q, int linenum)
{
	if (dcc_emsg[0] == '\0') {
		dcc_error_msg("error near line %d", linenum);
		discard_error(q, "error near line %d", linenum);
	} else {
		dcc_error_msg("%s", dcc_emsg);
		discard_error(q, dcc_emsg);
	}
}



/* say an administrative request was ok */
void
send_ok(QUEUE *q)
{
	DCC_OK buf;
	int delay_ms;

	buf.unused = 0;
	buf.max_pkt_vers = DCC_PKT_VERSION_MAX;
	delay_ms = get_q_delay_us(q) / 1000;
	buf.qdelay_ms = htons(delay_ms);
	strncpy(buf.brand, brand, sizeof(buf.brand));
	buf.hdr.op = DCC_OP_OK;
	buf.hdr.len = sizeof(buf);
	send_resp(q, &buf.hdr);
}



static void
repeat_resp(QUEUE *q)
{
	struct {
	    DCC_HDR hdr;
	    u_char  b[sizeof(q->ridc->result)];
	} buf;

	++dccd_stats.report_retrans;

	if (q->ridc->op == DCC_OP_INVALID) {
		TMSG2(RIDC, "repeat previous error silence for %s to %s",
		      dcc_req_op2str(&q->pkt.a),
		      Q_CIP(q));
		return;
	}

	TMSG3(RIDC, "repeat previous answer of %s for %s to %s",
	      dcc_op2str(q->ridc->op, 0, 0),
	      dcc_req_op2str(&q->pkt.a),
	      Q_CIP(q));

	buf.hdr.len = (q->ridc->len + sizeof(buf.hdr) + sizeof(DCC_SIGNATURE));
	buf.hdr.op = q->ridc->op;
	memcpy(&buf.hdr+1, &q->ridc->result, q->ridc->len);
	send_resp(q, &buf.hdr);
}
