diff dccd/rl.c @ 0:c7f6b056b673

First import of vendor version
author Peter Gervai <grin@grin.hu>
date Tue, 10 Mar 2009 13:49:58 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dccd/rl.c	Tue Mar 10 13:49:58 2009 +0100
@@ -0,0 +1,1700 @@
+/* Distributed Checksum Clearinghouse
+ *
+ * server rate limiting
+ *
+ * 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.142 $Revision$
+ */
+
+#include "dccd_defs.h"
+
+
+RL_RATE rl_sub_rate;
+RL_RATE rl_anon_rate;
+RL_RATE rl_all_anon_rate;
+RL_RATE rl_bugs_rate;
+#ifdef RL_MIN_MAX
+static int rl_min_max = RL_MIN_MAX;
+#else
+static int rl_min_max = 0;
+#endif
+
+static RL rl_all_anon;			/* limit all or anonymous clients */
+
+static RL *rl_newest, *rl_oldest;
+static RL **rl_hash;
+static int rl_hash_len;
+static u_char rl_too_many;
+
+time_t clients_cleared;
+
+time_t need_clients_save;
+
+typedef struct ip_bl {
+    struct ip_bl *fwd;
+    struct in6_addr addr;
+    struct in6_addr mask;
+    RL_DATA_FG  flags;			/* subset of RL_FG_* */
+#    define  IP_BL_ADDR	    RL_FG_BL_ADDR
+#    define  IP_BL_TRACE    RL_FG_TRACE
+} IP_BL;
+static IP_BL *ip_bl;
+
+static u_char rl_get(RL **, DCC_CLNT_ID, const struct in6_addr *);
+
+
+
+/* See if an IP address is evil
+ *	The blacklist is a simple linear list.  Entries from the file
+ *	are added to the front of the file, so that the last matching
+ *	entry determines the result.
+ *  This should be sped up if there are ever more than a very few entries */
+u_char					/* 1=blacklisted */
+ck_ip_bl(RL **rlp, DCC_CLNT_ID clnt_id, const struct in6_addr *pap)
+{
+	const IP_BL *bl;
+	RL *rl;
+
+	for (bl = ip_bl; bl; bl = bl->fwd) {
+		if (DCC_IN_BLOCK(*pap, bl->addr, bl->mask)) {
+			/* all blacklisted addresses including flooding peers
+			 * should have rate limit blocks so that they are
+			 * noticed */
+			rl = *rlp;
+			if (!rl) {
+				rl_get(rlp, clnt_id, pap);
+				rl = *rlp;
+			}
+			rl->d.flags &= ~(RL_FG_BL_ADDR | RL_FG_TRACE);
+			rl->d.flags |= bl->flags;
+			rl->d.flags |= RL_FG_CK_BL;
+			return (rl->d.flags & RL_FG_BL_ADDR) != 0;
+		}
+	}
+
+	rl = *rlp;
+	if (rl) {
+		rl->d.flags &= ~(RL_FG_BL_ADDR | RL_FG_TRACE);
+		rl->d.flags |= RL_FG_CK_BL;
+	}
+	return 0;
+}
+
+
+
+static void
+clear_bl(void)
+{
+	IP_BL *bl;
+	RL *rl;
+
+	while ((bl = ip_bl) != 0) {
+		ip_bl = ip_bl->fwd;
+		dcc_free(bl);
+	}
+
+	for (rl = rl_newest; rl != 0; rl = rl->older) {
+		rl->d.flags &= ~RL_FG_CK_BL;
+	}
+}
+
+
+
+void
+check_blacklist_file(void)
+{
+#define BL_NM "blacklist"
+	DCC_FNM_LNO_BUF fnm_buf;
+	static time_t prev_mtime, next_msg;
+#define BL_RECHECK (2*60*60)
+	static int serrno;
+	struct stat sb;
+	FILE *f;
+	char buf[120];
+	IP_BL *bl;
+	struct in6_addr addr, mask;
+	u_char flags;
+	char *p;
+	int lno, entries, i;
+
+	/* see if the file has changed */
+	if (0 > stat(BL_NM, &sb)) {
+		if (errno != ENOENT) {
+			prev_mtime = 1;
+			if (serrno != errno
+			    || DB_IS_TIME(next_msg, BL_RECHECK)) {
+				serrno = errno;
+				next_msg = db_time.tv_sec + BL_RECHECK;
+				dcc_error_msg("stat(%s): %s",
+					      DB_NM2PATH_ERR(BL_NM),
+					      ERROR_STR());
+			}
+
+		} else if (prev_mtime != 0) {
+			prev_mtime = 0;
+			next_msg = 0;
+			dcc_error_msg("%s disappeared", DB_NM2PATH_ERR(BL_NM));
+		}
+		clear_bl();
+		return;
+	}
+	if (prev_mtime == sb.st_mtime
+	    && (next_msg == 0 || !DB_IS_TIME(next_msg, BL_RECHECK)))
+		return;
+
+	/* the file has changed, so parse it */
+	clear_bl();
+	prev_mtime = 1;
+	next_msg = 0;
+	f = fopen(BL_NM, "r");
+	if (!f) {
+		if (serrno != errno) {
+			serrno = errno;
+			next_msg = db_time.tv_sec + BL_RECHECK;
+			dcc_error_msg("fopen(%s): %s",
+				      DB_NM2PATH_ERR(BL_NM), ERROR_STR());
+		}
+		return;
+	}
+	if (0 > fstat(fileno(f), &sb)) {
+		if (serrno != errno) {
+			serrno = errno;
+			next_msg = db_time.tv_sec + BL_RECHECK;
+			dcc_error_msg("fstat(%s): %s",
+				      DB_NM2PATH_ERR(BL_NM), ERROR_STR());
+		}
+		return;
+	}
+	prev_mtime = sb.st_mtime;
+
+	entries = 0;
+	for (lno = 1; ; ++lno) {
+		if (!fgets(buf, sizeof(buf), f)) {
+			if (ferror(f) && serrno != errno) {
+				serrno = errno;
+				next_msg = db_time.tv_sec + BL_RECHECK;
+				dcc_error_msg("fgets(%s): %s",
+					      DB_NM2PATH_ERR(BL_NM),
+					      ERROR_STR());
+			}
+			break;
+		}
+		/* reject lines that are too long */
+		i = strlen(buf);
+		if (buf[i-1] != '\n') {
+			next_msg = db_time.tv_sec + BL_RECHECK;
+			dcc_error_msg("syntax error%s",
+				      fnm_lno(&fnm_buf,
+					      DB_NM2PATH_ERR(BL_NM), lno));
+			break;
+		}
+
+		/* ignore leading blanks, comments, and blank lines */
+		p = strchr(buf, '#');
+		if (p)
+			*p = '\0';
+		p = buf;
+		p += strspn(p, DCC_WHITESPACE);
+		if (*p == '\0')
+			continue;
+
+		flags = IP_BL_ADDR;
+		for (;;) {
+			i = strspn(p, " \t");
+			if (i != 0) {
+				p += i;
+				continue;
+			}
+			if (!CLITCMP(p, "trace")) {
+				p += LITZ("trace");
+				if (*p == ',')
+					++p;
+				flags |= IP_BL_TRACE;
+				continue;
+			}
+			if (!CLITCMP(p, "ok")) {
+				p += LITZ("ok");
+				if (*p == ',')
+					++p;
+				flags &= ~IP_BL_ADDR;
+				continue;
+			}
+			if (!CLITCMP(p, "bad")) {
+				p += LITZ("bad");
+				if (*p == ',')
+					++p;
+				flags |= IP_BL_ADDR;
+				continue;
+			}
+			break;
+		}
+
+		i = dcc_str2cidr(dcc_emsg, &addr, &mask, 0, p, BL_NM, lno);
+		if (i <= 0) {
+			if (i < 0)
+				dcc_error_msg("%s", dcc_emsg);
+			else
+				dcc_error_msg("syntax error%s",
+					      fnm_lno(&fnm_buf,
+						      DB_NM2PATH_ERR(BL_NM),
+						      lno));
+			next_msg = db_time.tv_sec + BL_RECHECK;
+			continue;
+		}
+
+		bl = dcc_malloc(sizeof(*bl));
+		bl->addr = addr;
+		bl->mask = mask;
+		bl->flags = flags;
+		bl->fwd = ip_bl;
+		ip_bl = bl;
+		if (++entries > 100) {
+			dcc_error_msg("too many entries in %s",
+				      DB_NM2PATH_ERR(BL_NM));
+			next_msg = db_time.tv_sec + BL_RECHECK;
+			break;
+		}
+	}
+	fclose(f);
+
+	if (entries)
+		dcc_trace_msg("read %d entries from %s",
+			      entries, DB_NM2PATH_ERR(BL_NM));
+
+#undef BL_NM
+#undef BL_RECHECK
+}
+
+
+
+static inline RL **
+rl_hash_fnc(DCC_CLNT_ID	clnt_id, const struct in6_addr *addr)
+{
+	u_int32_t sum;
+
+	sum = clnt_id;
+	sum += addr->s6_addr32[0];
+	sum += addr->s6_addr32[1];
+	sum += addr->s6_addr32[2];
+	sum += addr->s6_addr32[3];
+	return &rl_hash[mhash(sum, rl_hash_len)];
+}
+
+
+
+/* link a rate limit block into its hash bin */
+static inline void
+rl_hash_link(RL *rl,			/* new block */
+	     RL **bin)			/* its bin */
+{
+	RL *rl2;
+
+	rl->bin = bin;
+	rl2 = *bin;
+	rl->hfwd = rl2;
+	if (rl2)
+		rl2->hbak = rl;
+	*bin = rl;
+}
+
+
+
+static int
+set_rl_min_max(void)
+{
+	int i;
+
+	if (rl_min_max == 0) {
+		/* allow RL_MIN_MAX_DEF for 512 MByte
+		 *	RL_MIN_MAX_DEF*2 for 1 GByte
+		 *	...
+		 *	RL_MIN_MAX_DEF*16 for 8 GByte
+		 */
+		i = (db_max_rss + 512*1024*1024-1) / (512*1024*1024);
+		if (i <= 0)
+			i = 1;
+		rl_min_max = RL_MIN_MAX_DEF * i;
+	}
+	if (rl_min_max > RL_MIN_MAX_MAX)
+		rl_min_max = RL_MIN_MAX_MAX;
+
+	return rl_min_max;
+}
+
+
+
+static void
+rl_expand(int new_len)
+{
+	RL *rl;
+	int j;
+
+	/* create a bunch of rate limit blocks */
+	if (new_len < 64)
+		new_len = 64;
+
+	if (rl_hash_len+new_len > set_rl_min_max()) {
+		if (rl_hash_len > rl_min_max)
+			dcc_logbad(EX_SOFTWARE, "impossible # of RL blocks");
+		new_len = rl_min_max - rl_hash_len;
+	}
+
+	if (rl_hash_len != 0)
+		dcc_trace_msg("increase from %d to %d RL blocks",
+			      rl_hash_len, rl_hash_len+new_len);
+
+	rl = dcc_malloc(new_len*sizeof(*rl));
+	if (!rl)
+		dcc_logbad(EX_OSERR, "malloc(%d RL's) failed", new_len);
+	memset(rl, 0, new_len*sizeof(*rl));
+	j = 0;
+	if (!rl_oldest) {
+		rl_oldest = rl;
+		rl_newest = rl;
+		++rl;
+		++j;
+	}
+	while (j < new_len) {	/* make the new blocks oldest */
+		rl_oldest->older = rl;
+		rl->newer = rl_oldest;
+		rl_oldest = rl;
+		 ++rl;
+		 ++j;
+	}
+
+	/* Rebuild and expand the hash table.
+	 *	The hash table is an array of pointers to rate limit blocks. */
+	if (rl_hash)
+		dcc_free(rl_hash);
+	rl_hash_len += new_len;
+	rl_hash = dcc_malloc(rl_hash_len*sizeof(*rl_hash));
+	memset(rl_hash, 0, rl_hash_len*sizeof(RL *));
+
+	/* copy the active blocks to the new hash table */
+	for (rl = rl_newest; rl; rl = rl->older) {
+		if (!rl->bin)
+			continue;
+		rl->hbak = 0;
+		rl_hash_link(rl,
+			     rl_hash_fnc(rl->d.clnt_id, &rl->d.clnt_addr));
+	}
+}
+
+
+
+/* age a rate limit */
+static void
+rl_age(RL *rl, const RL_RATE *credits)
+{
+	time_t secs;
+
+	secs = (db_time.tv_sec - rl->d.last_used);
+
+	/* only prevent overflow if no time has passed */
+	if (secs <= 0) {
+		if (rl->d.request_credits < credits->lo)
+			rl->d.request_credits = credits->lo;
+		if (rl->d.bug_credits < rl_bugs_rate.lo)
+			rl->d.bug_credits = rl_bugs_rate.lo;
+	}
+
+	rl->d.last_used = db_time.tv_sec;
+
+	/* reset idle counters */
+	if (secs >= RL_AVG_SECS || secs < 0) {
+		rl->d.bug_credits = rl_bugs_rate.hi;
+		rl->d.request_credits = credits->hi;
+		return;
+	}
+
+	rl->d.request_credits += secs * credits->per_sec;
+	if (rl->d.request_credits > credits->hi)
+		rl->d.request_credits = credits->hi;
+
+	rl->d.bug_credits += secs * rl_bugs_rate.per_sec;
+	if (rl->d.bug_credits > rl_bugs_rate.hi)
+		rl->d.bug_credits = rl_bugs_rate.hi;
+}
+
+
+
+/* age and increment a rate limit */
+void
+rl_inc(RL *rl, const RL_RATE *credits)
+{
+	rl_age(rl, credits);
+	++rl->d.requests;		/* increase total count of requests */
+	rl->d.request_credits -= RL_SCALE;  /* charge for this request */
+}
+
+
+
+/* update the request rate */
+static void
+rl_avg_requests_age(RL *rl, u_char force)
+{
+	time_t secs;
+	double trim;
+
+	if (rl->d.requests_avg_aged > db_time.tv_sec) {
+		/* clear things if time has jumped backwards */
+		rl->d.requests_avg_start = 0;
+
+	} else if (rl->d.requests_avg_aged + RL_AVG_UPDATE > db_time.tv_sec
+		   && !force) {
+		/* do nothing if it is not yet time and we are not forced */
+		return;
+	}
+
+	secs = db_time.tv_sec - rl->d.requests_avg_start;
+	if (secs > RL_AVG_TERM*2) {
+		/* clear if too long since the average was updated */
+		rl->d.requests_old = rl->d.requests;
+		rl->d.requests_avg_total = 0;
+		rl->d.nops_old = rl->d.nops;
+		rl->d.nops_avg_total = 0;
+		rl->d.requests_avg_start = db_time.tv_sec;
+		trim = 1.0;
+
+	} else if (secs < 24*60*60) {
+		/* we have no average for the first day */
+		trim = 1.0;
+
+	} else if (secs <= RL_AVG_TERM) {
+		trim = (60*60*24 * 1.0) / secs;
+
+	} else {
+		/* trim old counts if we have been averaging long enough */
+		trim = secs - RL_AVG_TERM;
+		trim /= (RL_AVG_TERM*2);
+		rl->d.requests_avg_total -= (rl->d.requests_avg_total * trim);
+		rl->d.nops_avg_total -= (rl->d.nops_avg_total * trim);
+		rl->d.requests_avg_start = db_time.tv_sec - RL_AVG_TERM;
+		trim = (60*60*24 * 1.0) / RL_AVG_TERM;
+	}
+
+	rl->d.requests_avg_total += rl->d.requests - rl->d.requests_old;
+	rl->d.requests_old = rl->d.requests;
+	rl->d.nops_avg_total += rl->d.nops - rl->d.nops_old;
+	rl->d.nops_old = rl->d.nops;
+
+	rl->d.requests_avg = rl->d.requests_avg_total * trim;
+	rl->d.nops_avg = rl->d.nops_avg_total * trim;
+
+	rl->d.requests_avg_aged = db_time.tv_sec;
+}
+
+
+
+static void
+rl_unref(RL *rl)
+{
+	if (rl->newer) {
+		rl->newer->older = rl->older;
+	} else if (rl_newest == rl) {
+		rl_newest = rl->older;
+	}
+
+	if (rl->older) {
+		rl->older->newer = rl->newer;
+	} else if (rl_oldest == rl) {
+		rl_oldest = rl->newer;
+	}
+
+	rl->newer = 0;
+	rl->older = 0;
+}
+
+
+
+static void
+rl_ref(RL *rl)
+{
+	RL **bin, *bin_head;
+
+	if (rl_newest == rl)
+		return;
+
+	/* make it the most recently used block */
+	rl_unref(rl);
+	rl->older = rl_newest;
+	rl_newest->newer = rl;
+	rl_newest = rl;
+
+	/* move it to the head of its hash bin if it is not already there */
+	bin = rl->bin;
+	bin_head = *bin;
+	if (bin_head == rl)
+		return;
+	*bin = rl;
+	if (rl->hfwd)
+		rl->hfwd->hbak = rl->hbak;
+	rl->hbak->hfwd = rl->hfwd;	/* we know there is a predecessor */
+	rl->hbak = 0;
+	rl->hfwd = bin_head;
+	bin_head->hbak = rl;
+}
+
+
+
+/* get a free rate limit block, recycling the oldest block if necessary */
+static RL *				/* block to use or 0 to make more */
+rl_get_free(void)
+{
+	RL *rl, *rl2;
+	time_t stale;
+
+	for (rl = rl_oldest; rl != 0; rl = rl->newer) {
+		if (rl->ref_cnt)
+			continue;
+
+		/* Found oldest free block */
+		if (rl->d.last_used != 0) {
+			/* oldest free block has been used and so we
+			 * must recycle something */
+			stale = db_time.tv_sec;
+			/* keep many blocks until we get enough to
+			 * worry about a denial of service attack */
+			if (rl_hash_len < set_rl_min_max())
+				stale -= CLIENTS_SAVE_AGE;
+			else
+				stale -= RL_LIFE_SECS;
+			/* make more if the oldest is new
+			 * and we don't have too many */
+			if (rl->d.last_used >= stale
+			    && rl_hash_len < rl_min_max)
+				return 0;
+
+			/* We cannot make more because we have too many blocks.
+			 *
+			 * Notice if we are about to recycle a block that is
+			 * not obsolete.
+			 * Try to find an old, little used block, so that we
+			 * do not recycle a block used by a busy client that
+			 * has only paused */
+			if (rl->d.last_used > clients_cleared) {
+				rl_too_many = 1;
+				stale = db_time.tv_sec - CLIENTS_AGE/2;
+				for (rl2 = rl;
+				     rl2 && rl2->d.last_used <= stale;
+				     rl2 = rl2->newer) {
+					/* avoid forgeting bad guys */
+					if (rl->d.flags & RL_FG_BLS)
+					    continue;
+					/* take the first tiny client found */
+					if (RL_REQUESTS_AVG(&rl2->d) < 100) {
+					    rl = rl2;
+					    break;
+					}
+				}
+			}
+		}
+
+		/* Recycle a block by first removing it from its hash chain */
+		if (rl->hfwd)
+			rl->hfwd->hbak = rl->hbak;
+		if (rl->hbak)
+			rl->hbak->hfwd = rl->hfwd;
+		else if (rl->bin)	/* It will not be on a hash chain */
+			*rl->bin = rl->hfwd;	/* if it has never been used */
+		rl_unref(rl);
+		memset(rl, 0, sizeof(*rl));
+		rl_avg_requests_age(rl, 1);
+		return rl;
+	}
+
+	/* There are no free blocks that are old enough to recycle,
+	 * so tell the caller to make more */
+	return 0;
+}
+
+
+
+/* get a rate limit block based on the IP address of the sender */
+static u_char				/* 1=existing block */
+rl_get(RL **rlp, DCC_CLNT_ID clnt_id, const struct in6_addr *cap)
+{
+	RL *rl, **bin;
+
+	if (!rl_hash_len)
+		rl_expand(queue_max);
+
+	bin = rl_hash_fnc(clnt_id, cap);
+	for (rl = *bin; rl; rl = rl->hfwd) {
+		if (rl->d.clnt_id != clnt_id
+		    || memcmp(&rl->d.clnt_addr, cap, sizeof(rl->d.clnt_addr)))
+			continue;
+		rl_ref(rl);		/* found it, so make it newest */
+		rl_avg_requests_age(rl, 0);
+		*rlp = rl;
+		return 1;
+	}
+
+	rl = rl_get_free();
+	if (!rl) {
+		/* when we are out of rate limiting blocks, make more */
+		rl_expand(queue_max);
+		/* which makes a new rate limit hash table which makes our
+		 * pointer into the old hash table bogus */
+		rl = rl_get_free();
+		if (!rl)
+			dcc_logbad(EX_SOFTWARE, "no available RL blocks");
+		bin = rl_hash_fnc(clnt_id, cap);
+	}
+	rl_hash_link(rl, bin);
+	rl_ref(rl);
+	rl->d.clnt_addr = *cap;
+	rl->d.clnt_id = clnt_id;
+	*rlp = rl;
+	return 0;
+}
+
+
+
+/* get a rate limit block for a job */
+static RL *
+rl_get_q(QUEUE *q, DCC_CLNT_ID id)
+{
+	struct in6_addr clnt_addr;
+	const struct in6_addr *cap;
+	RL *rl;
+
+	if (q->clnt_su.sa.sa_family == AF_INET6) {
+		cap = &q->clnt_su.ipv6.sin6_addr;
+	} else {
+		dcc_ipv4toipv6(&clnt_addr, q->clnt_su.ipv4.sin_addr);
+		cap = &clnt_addr;
+	}
+	rl_get(&q->rl, id, cap);
+	rl = q->rl;
+	++rl->ref_cnt;
+
+	return rl;
+}
+
+
+
+static u_char
+clients_write(FILE *f, const void *buf, int buf_len)
+{
+	if (1 == fwrite(buf, buf_len, 1, f))
+		return 1;
+	dcc_error_msg("fwrite(%s): %s",
+		      DB_NM2PATH_ERR(CLIENTS_NM()), ERROR_STR());
+	fclose(f);
+	return 0;
+}
+
+
+
+/* dump the rate limit blocks into a file */
+void
+clients_save(void)
+{
+	int fd;
+	FILE *f;
+	CLIENTS_HEADER header;
+	RL *rl;
+
+	need_clients_save = db_time.tv_sec + CLIENTS_SAVE_SECS;
+
+	/* prevent evil games with symbolic links */
+	unlink(CLIENTS_NM());
+	fd = open(CLIENTS_NM(), O_WRONLY|O_CREAT|O_EXCL, 0644);
+	if (fd < 0) {
+		dcc_error_msg("open(%s): %s",
+			      DB_NM2PATH_ERR(CLIENTS_NM()), ERROR_STR());
+		return;
+	}
+	f = fdopen(fd, "w");
+
+	memset(&header, 0, sizeof(header));
+	BUFCPY(header.magic, CLIENTS_MAGIC(grey_on));
+	header.now = db_time.tv_sec;
+	header.cleared = clients_cleared;
+	if (anon_off) {
+		header.anon_delay_us = DCC_ANON_DELAY_FOREVER;
+	} else {
+		header.anon_delay_us = anon_delay_us;
+		header.anon_delay_inflate = anon_delay_inflate;
+	}
+	if (clients_cleared > db_time.tv_sec)   /* fix time jump */
+		clients_cleared = db_time.tv_sec;
+
+	for (rl = rl_oldest; rl != 0; rl = rl->newer) {
+		if (rl->d.last_used == 0)
+			continue;
+		if (rl->d.last_used > db_time.tv_sec)   /* fix time jump */
+			rl->d.last_used = db_time.tv_sec;
+		++header.hash_len;
+	}
+	if (!clients_write(f, &header, sizeof(header)))
+		return;
+
+	for (rl = rl_oldest; rl != 0; rl = rl->newer) {
+		if (rl->d.last_used == 0)
+			continue;
+		if (!clients_write(f, &rl->d, sizeof(rl->d)))
+			return;
+	}
+	fclose(f);
+}
+
+
+
+static u_char
+clients_read(FILE *f, void *buf, int buf_len)
+{
+	int i;
+
+	i = fread(buf, buf_len, 1, f);
+	if (i == 1)
+		return 1;
+
+	if (feof(f))
+		return 0;
+
+	dcc_error_msg("fread(%s): %s",
+		      DB_NM2PATH_ERR(CLIENTS_NM()), ERROR_STR());
+	fclose(f);
+	return 0;
+}
+
+
+
+/* load the rate limit blocks from a previous instance */
+void
+clients_load(void)
+{
+	struct stat sb;
+	int fd;
+	FILE *f;
+	CLIENTS_HEADER header;
+	RL_DATA data;
+	RL *rl;
+	int rl_size, total, used, anon;
+#	define BAD_FILE() ((0 > rename(CLIENTS_NM(), BAD_CLIENTS_NM()))	    \
+			   ? unlink(CLIENTS_NM()) : 0)
+
+	clients_cleared = db_time.tv_sec;
+
+	fd = open(CLIENTS_NM(), O_RDONLY, 0);
+	if (fd < 0) {
+		if (errno != ENOENT)
+			dcc_error_msg("open(%s): %s",
+				      DB_NM2PATH_ERR(CLIENTS_NM()),
+				      ERROR_STR());
+		else if (db_debug)
+			dcc_trace_msg("open(%s): %s",
+				      DB_NM2PATH_ERR(CLIENTS_NM()),
+				      ERROR_STR());
+		BAD_FILE();
+		return;
+	}
+	f = fdopen(fd, "r");
+	if (0 > fstat(fd, &sb)) {
+		dcc_error_msg("stat(%s): %s",
+			      DB_NM2PATH_ERR(CLIENTS_NM()), ERROR_STR());
+		BAD_FILE();
+		fclose(f);
+		return;
+	}
+	if ((int)sb.st_size < ISZ(header)) {
+		dcc_error_msg("%s has invalid size %d",
+			      DB_NM2PATH_ERR(CLIENTS_NM()), (int)sb.st_size);
+		BAD_FILE();
+		fclose(f);
+		return;
+	}
+
+	if (!clients_read(f, &header, sizeof(header)))
+		return;
+
+	/* try to save strange files but quietly ignore old files */
+	if (strcmp(header.magic, CLIENTS_MAGIC(grey_on))) {
+		if (strncmp(header.magic, CLIENTS_MAGIC_BASE(grey_on),
+			    strlen(CLIENTS_MAGIC_BASE(grey_on)))) {
+			dcc_error_msg("unrecognized magic in %s",
+				      DB_NM2PATH_ERR(CLIENTS_NM()));
+			BAD_FILE();
+		}
+		fclose(f);
+		return;
+	}
+
+	if (((sb.st_size - ISZ(header)) % ISZ(RL_DATA)) != 0) {
+		dcc_error_msg("%s has invalid size %d",
+			      DB_NM2PATH_ERR(CLIENTS_NM()), (int)sb.st_size);
+		BAD_FILE();
+		fclose(f);
+		return;
+	}
+
+	if (header.hash_len > RL_MIN_MAX_MAX) {
+		dcc_trace_msg("unrecognized hash_len=%d in %s",
+			      header.hash_len, DB_NM2PATH_ERR(CLIENTS_NM()));
+		fclose(f);
+		return;
+	}
+	if (header.cleared > db_time.tv_sec) {
+		dcc_trace_msg("bad time %d in %s",
+			      (int)header.cleared,
+			      DB_NM2PATH_ERR(CLIENTS_NM()));
+		BAD_FILE();
+		fclose(f);
+		return;
+	}
+	clients_cleared = header.cleared;
+	rl_size = header.hash_len;
+	rl_size += queue_max - (rl_size % queue_max);
+	rl_size = min(rl_size, set_rl_min_max());
+	rl_expand(rl_size);
+
+	for (total = 0, used = 0, anon = 0; ; ++total) {
+		if (!clients_read(f, &data, sizeof(data)))
+			break;
+		if (data.last_used > db_time.tv_sec) {
+			dcc_error_msg("badly timestamped entry in %s",
+				      DB_NM2PATH_ERR(CLIENTS_NM()));
+			break;
+		}
+
+		if (rl_get(&rl, data.clnt_id, &data.clnt_addr)) {
+			dcc_error_msg("duplicate entry in %s",
+				      DB_NM2PATH_ERR(CLIENTS_NM()));
+			break;
+		}
+
+		rl->d = data;
+		rl->d.flags &= ~RL_FG_CK_BL;	/* check current blacklist */
+		++used;
+		if ((rl->d.flags & RL_FG_ANON)
+		    && rl->d.last_used >= db_time.tv_sec-2*24*60*60)
+			++anon;
+	}
+	fclose(f);
+
+	if (used != total
+	    || total > 500
+	    || anon != 0
+	    || db_debug) {
+		if (anon != 0)
+			dcc_trace_msg("used %d of %d RL blocks,"
+				      " %d for recent anonymous clients"
+				      " in %s",
+				      used, total, anon,
+				      DB_NM2PATH_ERR(CLIENTS_NM()));
+		else
+			dcc_trace_msg("used %d of %d RL blocks in %s",
+				      used, total,
+				      DB_NM2PATH_ERR(CLIENTS_NM()));
+	}
+}
+
+
+
+/* pack up a 32 bit int as part of an answer to a `cdcc clients` request */
+static u_char *
+client_pack4(u_char *cp,
+	     u_int32_t v)
+{
+	while (v > 0x7f) {
+		*cp++ = v | 0x80;
+		v >>= 7;
+	}
+	*cp++ = v;
+	return cp;
+}
+
+
+
+static int
+client_pack(u_char *cp0,		/* pack into this byte first */
+	    u_char **flagsp,		/* pointer to packed flags */
+	    u_char flags,		/* DCC_ADMN_RESP_CLIENTS_* */
+	    DCC_CLNT_ID clnt_id,
+	    time_t last_used,		/* skip count if ..._CLIENTS_SKIP */
+	    int requests,
+	    int nops,
+	    u_char vers,
+	    const struct in6_addr *clnt_addr)
+{
+	u_char *cp;
+
+	cp = cp0;
+	*flagsp = cp0;			/* announce location of these flags */
+	if (vers != 0)
+		flags |= DCC_ADMN_RESP_CLIENTS_VERS;
+	*cp++ = flags;
+	if (flags & DCC_ADMN_RESP_CLIENTS_VERS)
+		*cp++ = vers;
+	*cp++ = last_used >> 24;
+	*cp++ = last_used >> 16;
+	*cp++ = last_used >> 8;
+	*cp++ = last_used;
+	if (clnt_id == DCC_ID_ANON) {
+		*cp0 |= DCC_ADMN_RESP_CLIENTS_ID1;
+	} else {
+		cp = client_pack4(cp, clnt_id);
+	}
+	cp = client_pack4(cp, requests);
+	cp = client_pack4(cp, nops);
+	if (!clnt_addr) {
+		memset(cp, 0, 4);
+		cp += 4;
+	} else if (DCC_IN6_ADDR_V4MAPPED(clnt_addr)) {
+		memcpy(cp, &clnt_addr->s6_addr32[3], 4);
+		cp += 4;
+	} else {
+		*cp0 |= DCC_ADMN_RESP_CLIENTS_IPV6;
+		memcpy(cp, clnt_addr->s6_addr32, 16);
+		cp += 16;
+	}
+	return cp-cp0;
+}
+
+
+
+static int
+client_pack_skip(u_char *cp, u_char **prev_flags, u_int skipped)
+{
+	return client_pack(cp, prev_flags, DCC_ADMN_RESP_CLIENTS_SKIP,
+			   DCC_ID_ANON, skipped, 0, 0, 0, 0);
+}
+
+
+
+/* get list of clients,
+ *	treating clients with the same ID as if they were all the same system */
+void
+clients_get_id(DCC_ADMN_RESP_VAL *val,
+	       int *lenp,		/* buffer length */
+	       u_int offset,		/* skip this many newer entries */
+	       int thold,		/* skip clients with fewer requests */
+	       u_char req_flags,
+	       const struct in6_addr *addr6,
+	       const struct in6_addr *mask6)
+{
+	RL *rl, *rl2;
+	u_char *skip;
+	int requests, nops;
+	u_char *prev_flags;		/* flags in previous record */
+	u_int skipped;
+	int len, len_lim;
+
+	if (offset == 0)
+		need_clients_save = 0;
+
+	prev_flags = 0;
+	len_lim = *lenp;
+	len_lim <<= DCC_ADMIN_RESP_CLIENTS_SHIFT;
+	if (len_lim > ISZ(*val))
+		len_lim = ISZ(*val);
+	skip = 0;
+	skipped = 0;
+	len = 0;
+	if (!thold)
+		thold = 1;
+	for (rl = rl_newest; rl != 0; rl = rl->older)
+		rl->d.flags &= ~RL_FG_MARKED;
+
+	for (rl = rl_newest; ; rl = rl->older) {
+		if (!rl) {
+			/* there are no more, so
+			 * mark the previous record as the last */
+			if (prev_flags)
+				*prev_flags |= DCC_ADMN_RESP_CLIENTS_LAST;
+			break;
+		}
+
+		if (rl->d.flags & RL_FG_MARKED)
+			continue;
+
+		if (addr6 && !DCC_IN_BLOCK(rl->d.clnt_addr, *addr6, *mask6)) {
+			rl->d.flags |= RL_FG_MARKED;
+			continue;
+		}
+
+		if (rl->d.last_used == 0) {
+			if (!(req_flags & DCC_AOP_CLIENTS_AVG))
+				continue;
+			/* report clients that have been quiet today if
+			 * they were active yesterday */
+			rl_avg_requests_age(rl, 0);
+			if (RL_REQUESTS_AVG(&rl->d) == 0
+			    && RL_NOPS_AVG(&rl->d) == 0)
+				continue;
+		}
+
+		requests = 0;
+		nops = 0;
+		for (rl2 = rl; rl2 != 0; rl2 = rl2->older) {
+			if (rl2->d.clnt_id != rl->d.clnt_id)
+				continue;
+			rl2->d.flags |= RL_FG_MARKED;
+			if (addr6
+			    && !DCC_IN_BLOCK(rl->d.clnt_addr, *addr6, *mask6))
+				continue;
+			if (req_flags & DCC_AOP_CLIENTS_AVG) {
+				rl_avg_requests_age(rl2, 0);
+				requests += RL_REQUESTS_AVG(&rl2->d);
+				nops += RL_NOPS_AVG(&rl2->d);
+			} else {
+				requests += rl2->d.requests;
+				nops += rl2->d.nops;
+			}
+		}
+
+		/* get the part of the list that cdcc wants */
+		if (offset != 0) {
+			--offset;
+			continue;
+		}
+
+		if (requests < thold) {
+			/* The threshold might be larger on the next request
+			 * from cdcc, so tell cdcc the number skipped for
+			 * the threshold this time.
+			 * Tell cdcc by insertint a fake entry. */
+			if (!skip) {
+				skip = &val->clients[len];
+				len += client_pack_skip(skip, &prev_flags, 0);
+			}
+			++skipped;
+			continue;
+		}
+
+		/* stop at end of buffer
+		 * check only after skipping boring records for the common
+		 * case of the last 10,000 records missing the threshold */
+		if (len+DCC_ADMN_RESP_CLIENTS_MAX_SIZE > len_lim)
+			break;
+
+		len += client_pack(&val->clients[len], &prev_flags, 0,
+				   rl->d.clnt_id, rl->d.last_used,
+				   requests, nops, 0, 0);
+	}
+	if (skipped)
+		client_pack_skip(skip, &prev_flags, skipped);
+	*lenp = len;
+}
+
+
+
+/* get a list of the most recent clients */
+int					/* +/- number of clients */
+clients_get(DCC_ADMN_RESP_VAL *val,
+	    int *lenp,			/* buffer length */
+	    u_int offset,		/* skip this many newer entries */
+	    int thold,			/* skip clients with fewer requests */
+	    u_char req_flags,
+	    const struct in6_addr *addr6,
+	    const struct in6_addr *mask6)
+{
+	RL *rl;
+	u_char *skip;
+	int requests, nops;
+	u_char prev_vers, vers;
+	u_char *prev_flags;		/* flags in previous record */
+	int skipped, total, len, len_lim;
+
+	if (offset == 0)
+		need_clients_save = 0;
+
+	prev_flags = 0;
+	if (!val || !lenp) {
+		len_lim = 0;
+	} else {
+		len_lim = *lenp;
+		len_lim <<= DCC_ADMIN_RESP_CLIENTS_SHIFT;
+		if (len_lim > ISZ(*val))
+			len_lim = ISZ(*val);
+	}
+	if (!thold)
+		thold = 1;
+	prev_vers = 0;
+	skip = 0;
+	skipped = 0;
+	total = 0;
+	len = 0;
+	for (rl = rl_newest; ; rl = rl->older) {
+		if (!rl) {
+			/* there are no more, so
+			 * mark the previous record as the last */
+			if (prev_flags)
+				*prev_flags |= DCC_ADMN_RESP_CLIENTS_LAST;
+			break;
+		}
+
+		if (rl->d.last_used == 0) {
+			if (!(req_flags & DCC_AOP_CLIENTS_AVG))
+				continue;
+			/* report clients that have been quiet today if
+			 * they were active yesterday
+			 * and we are reporting averages */
+			rl_avg_requests_age(rl, 0);
+			if (RL_REQUESTS_AVG(&rl->d) == 0
+			    && RL_NOPS_AVG(&rl->d) == 0)
+				continue;
+		}
+
+		if (rl->d.requests != 0)
+			++total;
+
+		/* compute only the total for `cdcc stats` if buffer is null */
+		if (len_lim == 0)
+			continue;
+
+		/* respect client IP address limitations */
+		if (addr6 && !DCC_IN_BLOCK(rl->d.clnt_addr, *addr6, *mask6))
+			continue;
+
+		/* always report blacklisted clients
+		 * report only (non-)anonymous clients if asked, error
+		 *	toward reporting clients that were treated as
+		 *	anonymous but used a real ID */
+		if (!(rl->d.flags & RL_FG_BLS)) {
+			if ((req_flags & DCC_AOP_CLIENTS_ANON)
+			    && !(rl->d.flags & RL_FG_ANON))
+				continue;
+			if ((req_flags & DCC_AOP_CLIENTS_NON_ANON)
+			    && rl->d.clnt_id == DCC_ID_ANON)
+				continue;
+		}
+
+		if (offset != 0) {
+			--offset;
+			continue;
+		}
+
+		if (req_flags & DCC_AOP_CLIENTS_AVG) {
+			rl_avg_requests_age(rl, 0);
+			requests = RL_REQUESTS_AVG(&rl->d);
+			nops = RL_NOPS_AVG(&rl->d);
+		} else {
+			requests = rl->d.requests;
+			nops = rl->d.nops;
+		}
+
+		/* skip uninteresting records and insert a fake
+		 * entry in the output to the client with the total
+		 * skipped in the entire response to the client */
+		if (requests < thold
+		    && (!(rl->d.flags & RL_FG_BLS)
+			|| rl->d.requests == 0)) {
+			if (!skip) {
+				/* start a new fake record */
+				skip = &val->clients[len];
+				len += client_pack_skip(skip, &prev_flags, 0);
+				prev_vers = 0;
+			}
+			++skipped;
+			continue;
+		}
+
+		/* stop at end of buffer
+		 * check only after skipping boring records for the common
+		 * case of ignoring the last 10,000 records */
+		if (len + DCC_ADMN_RESP_CLIENTS_MAX_SIZE*2 > len_lim)
+				break;
+
+		/* send the version number if it is wanted and differs
+		 * from the previous value */
+		if ((req_flags & DCC_AOP_CLIENTS_VERS)
+		    && rl->d.pkt_vers != prev_vers) {
+			vers = rl->d.pkt_vers;
+			prev_vers = vers;
+		} else {
+			vers = 0;
+		}
+
+		len += client_pack(&val->clients[len], &prev_flags,
+				   (rl->d.flags & (RL_FG_BL_ADDR | RL_FG_BL_ID))
+				   ? DCC_ADMN_RESP_CLIENTS_BL
+				   : (rl->d.flags & RL_FG_BL_BAD)
+				   ? DCC_ADMN_RESP_CLIENTS_BAD
+				   : 0,
+				   rl->d.clnt_id, rl->d.last_used,
+				   requests, nops, vers, &rl->d.clnt_addr);
+	}
+	/* put final total number of skipped records in the output */
+	if (skipped)
+		client_pack_skip(skip, &prev_flags, skipped);
+
+	if (lenp)
+		*lenp = len;
+
+	/* return negative total if the oldest block is not very old and
+	 * we have had to recycle a recent block */
+	if (rl_too_many)
+		return -total;
+	return total;
+}
+
+
+
+/* forget old clients */
+void
+clients_clear()
+{
+	RL *rl;
+
+	for (rl = rl_oldest; rl != 0; rl = rl->newer) {
+		rl_avg_requests_age(rl, 1);
+		rl->d.requests = 0;
+		rl->d.requests_old = 0;
+		rl->d.nops = 0;
+		rl->d.nops_old = 0;
+	}
+
+	clients_cleared = db_time.tv_sec;
+	rl_too_many = 0;
+}
+
+
+
+u_char					/* 0=bad passwd, 1=1st passwd, 2=2nd */
+ck_sign(const ID_TBL **tpp,		/* return ID table entry here */
+	DCC_PASSWD passwd,		/* return matching password here */
+	DCC_CLNT_ID id,
+	const void *buf, u_int buf_len)
+{
+	ID_TBL *tp;
+
+	tp = find_id_tbl(id);
+	if (tpp)
+		*tpp = tp;
+	if (!tp)
+		return 0;
+
+	if (tp->cur_passwd[0] != '\0'
+	    && dcc_ck_signature(tp->cur_passwd, sizeof(tp->cur_passwd),
+				buf, buf_len)) {
+		if (passwd)
+			memcpy(passwd, tp->cur_passwd, sizeof(DCC_PASSWD));
+		return 1;
+	}
+	if (tp->next_passwd[0] != '\0'
+	    && dcc_ck_signature(tp->next_passwd, sizeof(tp->next_passwd),
+				buf, buf_len)) {
+		if (passwd)
+			memcpy(passwd, tp->next_passwd, sizeof(DCC_PASSWD));
+		return 2;
+	}
+	return 0;
+}
+
+
+
+/* decide how long a request should be delayed */
+static u_char				/* 0=delay >= maximum */
+tp2delay(QUEUE *q, time_t delay_us, u_int delay_inflate)
+{
+	int inflate;
+
+	if (delay_us != 0) {
+		inflate = 1 + RL_REQUESTS_AVG(&q->rl->d)/delay_inflate;
+		if (inflate > DCC_ANON_DELAY_MAX / delay_us /* no overflow */
+		    || (delay_us *= inflate) >= DCC_ANON_DELAY_MAX) {
+			q->rl->d.flags |= RL_FG_BL_BAD;
+			q->delay_us = DCC_ANON_DELAY_MAX;
+			return 0;
+		}
+	}
+
+	/* do not answer anonymous clients while we are cleaning
+	 * the database */
+	if ((db_minimum_map || anon_off) && (q->rl->d.flags & RL_FG_ANON)) {
+		q->delay_us = DCC_ANON_DELAY_MAX;
+		return 0;
+	}
+
+	if (flods_off > 0
+	    || flods_st != FLODS_ST_ON
+	    || (iflods.open == 0 && (oflods.total != 0 || !grey_on))
+	    || !DB_IS_TIME(iflods_ok_timer, IFLODS_OK_SECS)) {
+		/* Increase the delay when flooding is off, broken,
+		 * or just starting after being off
+		 * Greylist servers without any flooding peers can be isolated
+		 * but not DCC servers */
+		delay_us += 400*1000;
+
+	} else if (dbclean_wfix_state == WFIX_QUIET
+		   || dbclean_wfix_state == WFIX_CHECK) {
+		/* increase the delay if we are testing to see if clients
+		 * have a alternative so we can do a quick window fix. */
+		delay_us += 1000*1000;
+	}
+
+	if (delay_us >= DCC_ANON_DELAY_MAX) {
+		q->delay_us = DCC_ANON_DELAY_MAX;
+		return 0;
+	}
+	q->delay_us = delay_us;
+	return 1;
+}
+
+
+
+/* check the message authentication code and rate limit requests */
+static u_char				/* 0=forget request, 1=go ahead */
+ck_id(QUEUE *q,
+      DCC_CLNT_ID id,			/* what the client claimed */
+      DCC_CLNT_ID min_id)		/* allowed */
+{
+#define RL_CNT2AVG(cur,lims) ((lims.hi - cur) / (RL_SCALE*RL_AVG_SECS*1.0))
+	RL_DATA_FG id_fgs = 0;
+	const ID_TBL *tp;
+	RL *rl;
+	int result;			/* 1=ok & no msg; 0=msg; -1=no msg */
+
+	q->clnt_id = id;
+	if (id == DCC_ID_ANON) {
+		tp = 0;
+		q->flags |= Q_FG_UNTRUSTED;
+		id_fgs = RL_FG_ANON;
+		result = 1;
+
+	} else if (id < min_id) {
+		tp = 0;
+		q->flags |= (Q_FG_UNTRUSTED | Q_FG_UKN_ID);
+		id_fgs = (RL_FG_UKN_ID | RL_FG_ANON);
+		result = 0;
+
+	} else if (!ck_sign(&tp, q->passwd, id, &q->pkt, q->pkt_len)) {
+		/* failed to authenticate the ID */
+		if (!tp) {
+			q->flags |= Q_FG_UKN_ID;
+			id_fgs = (RL_FG_UKN_ID | RL_FG_ANON);
+		} else {
+			if (tp->delay_us >= DCC_ANON_DELAY_US_BLACKLIST)
+				id_fgs |= RL_FG_BL_ID;
+			tp = 0;
+			q->flags |= Q_FG_BAD_PASSWD;
+			id_fgs = (RL_FG_PASSWD | RL_FG_ANON);
+		}
+		q->flags |= Q_FG_UNTRUSTED;
+		result = 0;
+
+	} else if (tp->delay_us >= DCC_ANON_DELAY_US_BLACKLIST) {
+		q->flags |= Q_FG_UNTRUSTED;
+		id_fgs = RL_FG_BL_ID;
+		result = -1;
+
+	} else {
+		id_fgs = 0;
+		result = 1;
+	}
+
+
+	rl = rl_get_q(q, id);
+
+	rl->d.pkt_vers = q->pkt.hdr.pkt_vers;
+
+	/* See if this client IP address is blacklisted,
+	 * if we have not checked it since (re)loading the blacklist */
+	rl->d.flags &= ~(RL_FG_BL_ID | RL_FG_BL_BAD
+			 | RL_FG_PASSWD | RL_FG_UKN_ID | RL_FG_ANON);
+	rl->d.flags |= id_fgs;
+	if (!(rl->d.flags & RL_FG_CK_BL))
+		ck_ip_bl(&rl, q->clnt_id, &rl->d.clnt_addr);
+	if (rl->d.flags & (RL_FG_BL_ADDR | RL_FG_BL_ID))
+		result = -1;
+
+	if (tp) {
+		if (!query_only || (tp->flags & ID_FLG_RPT_OK))
+			q->flags |= Q_FG_RPT_OK;
+
+		rl_inc(rl, &rl_sub_rate);
+
+		if (!tp2delay(q, tp->delay_us, tp->delay_inflate))
+			result = -1;
+
+		if (rl->d.request_credits <= 0 && result >= 0) {
+			clnt_msg(q, "%.1f requests/sec are too many%s",
+				 RL_CNT2AVG(rl->d.request_credits, rl_sub_rate),
+				 from_id_ip(q, 0));
+			++dccd_stats.rl;
+			rl->d.flags |= RL_FG_BL_BAD;
+			result = -1;
+		}
+
+	} else {
+		if (!query_only)
+			q->flags |= Q_FG_RPT_OK;
+
+		rl_inc(rl, &rl_anon_rate);
+		rl_inc(&rl_all_anon, &rl_all_anon_rate);
+
+		if (!tp2delay(q, anon_delay_us, anon_delay_inflate))
+			result = -1;
+
+		if (rl->d.request_credits <= 0 && result >= 0) {
+			anon_msg("%.1f requests/sec are too many%s",
+				 RL_CNT2AVG(rl->d.request_credits,
+					    rl_anon_rate),
+				 from_id_ip(q, 0));
+			rl->d.flags |= RL_FG_BL_BAD;
+			++dccd_stats.anon_rl;
+			result = -1;
+		} else if (rl_all_anon.d.request_credits <= 0 && result >= 0) {
+			anon_msg("%s contributed to %.1f"
+				 " anonymous requests/sec",
+				 Q_CIP(q),
+				 RL_CNT2AVG(rl_all_anon.d.request_credits,
+					    rl_all_anon_rate));
+			++dccd_stats.anon_rl;
+			result = -1;
+		}
+	}
+
+	if (q->flags & (Q_FG_BAD_PASSWD | Q_FG_UKN_ID))
+		++dccd_stats.bad_passwd;
+	else if (rl->d.flags & RL_FG_BL_BAD)
+		++dccd_stats.bad_op;
+	else if (rl->d.flags & RL_FG_BLS)
+		++dccd_stats.blist;
+
+	/* complain if tracing all blacklisted clients
+	 * or if tracing this client
+	 * or if this client is not blacklisted
+	 *	but uses a bad password or ID */
+	if (((rl->d.flags & RL_FG_BLS) && (DCC_TRACE_BL_BIT & dccd_tracemask))
+	    || (rl->d.flags & RL_FG_TRACE)
+	    || result == 0) {
+		char result_buf[30];
+		const char *result_str, *bl_str;
+
+		if (result >= 0) {
+			if (q->delay_us == 0) {
+				result_str = "";
+			} else {
+				snprintf(result_buf, sizeof(result_buf),
+					 "delay %.3f ",
+					 q->delay_us/(DCC_US*1.0));
+				result_str = result_buf;
+			}
+		} else {
+			result_str = "drop ";
+		}
+		bl_str = ((rl->d.flags & RL_FG_BL_ADDR) ? "blacklisted "
+			  : (rl->d.flags & RL_FG_BL_ID) ? "ID blacklisted "
+			  : "");
+		dcc_trace_msg("%s%s%s",
+			      result_str, bl_str, from_id_ip(q, 1));
+	}
+
+	return (result >= 0);
+}
+
+
+
+/* check the message authentication code for a client of our server-ID
+ * and rate limit its messages */
+u_char					/* 0=forget it, 1=go ahead */
+ck_clnt_srvr_id(QUEUE *q)
+{
+	/* require a client-ID, our server-ID, or the anonymous client-ID
+	 * to consider allowing an administrative request */
+	return ck_id(q, ntohl(q->pkt.hdr.sender), DCC_SRVR_ID_MIN);
+}
+
+
+
+/* check the message authentication code of a request,
+ * and rate limit the source */
+u_char					/* 0=forget it, 1=go ahead */
+ck_clnt_id(QUEUE *q)
+{
+	/* require a client-ID and not a server-ID to discourage server
+	 * operators from leaking server-ID's */
+	return ck_id(q, ntohl(q->pkt.hdr.sender), DCC_CLNT_ID_MIN);
+}
+
+
+
+const char *
+qop2str(const QUEUE *q)
+{
+	static int bufno;
+	static struct {
+	    char    str[DCC_OPBUF];
+	} bufs[4];
+	char *s;
+
+	s = bufs[bufno].str;
+	bufno = (bufno+1) % DIM(bufs);
+
+	return dcc_hdr_op2str(s, DCC_OPBUF, &q->pkt.hdr);
+}
+
+
+
+const char *
+from_id_ip(const QUEUE *q,
+	   u_char print_op)		/* 1=include operation */
+{
+	static char buf[DCC_OPBUF
+			+ISZ(" from ")+40+ISZ(" at ")+DCC_SU2STR_SIZE];
+	char ob[DCC_OPBUF];
+	const char *op;
+
+	if (print_op) {
+		op = dcc_hdr_op2str(ob, sizeof(ob), &q->pkt.hdr);
+	} else {
+		op = "";
+	}
+
+	if (q->clnt_id == DCC_ID_ANON)
+		snprintf(buf, sizeof(buf),
+			 "%s from anonymous at %s",
+			 op, dcc_su2str_err(&q->clnt_su));
+	else if (q->flags & Q_FG_UKN_ID)
+		snprintf(buf, sizeof(buf),
+			 "%s from unknown ID %d at %s",
+			 op, q->clnt_id, dcc_su2str_err(&q->clnt_su));
+	else if (q->flags & Q_FG_BAD_PASSWD)
+		snprintf(buf, sizeof(buf),
+			 "%s from ID %d at %s with bad password ",
+			 op, q->clnt_id, dcc_su2str_err(&q->clnt_su));
+	else if (q->flags & Q_FG_UNTRUSTED)
+		snprintf(buf, sizeof(buf),
+			 "%s from bad ID %d at %s",
+			 op, q->clnt_id, dcc_su2str_err(&q->clnt_su));
+	else
+		snprintf(buf, sizeof(buf),
+			 "%s from ID %d at %s",
+			 op, q->clnt_id, dcc_su2str_err(&q->clnt_su));
+
+	return buf;
+}
+
+
+
+const char *
+op_id_ip(const QUEUE *q)
+{
+	static char buf[3+14+4+DCC_SU2STR_SIZE];
+	char ob[DCC_OPBUF];
+
+	snprintf(buf, sizeof(buf), "%s from ID %d at %s",
+		 dcc_hdr_op2str(ob, sizeof(ob), &q->pkt.hdr),
+		 (DCC_CLNT_ID)ntohl(q->pkt.hdr.sender), Q_CIP(q));
+	return buf;
+}
+
+
+
+/* complain about an anonymous, non-paying client */
+void
+vanon_msg(const char *p, va_list args)
+{
+	rl_age(&rl_all_anon, &rl_all_anon_rate);
+	if ((DCC_TRACE_ANON_BIT & dccd_tracemask)
+	    && (rl_all_anon.d.bug_credits > 0
+		|| (DCC_TRACE_RLIM_BIT & dccd_tracemask))) {
+		rl_all_anon.d.bug_credits -= RL_SCALE;
+		dcc_vtrace_msg(p, args);
+	}
+}
+
+
+
+void
+anon_msg(const char *p, ...)
+{
+	va_list args;
+
+	va_start(args, p);
+	vanon_msg(p, args);
+	va_end(args);
+}
+
+
+
+/* complain about a client */
+void
+vclnt_msg(const QUEUE *q, const char *p, va_list args)
+{
+	if ((q->flags & Q_FG_UNTRUSTED) || !q->rl) {
+		vanon_msg(p, args);
+		return;
+	}
+
+	if (DCC_TRACE_CLNT_BIT & dccd_tracemask) {
+		rl_age(q->rl, &rl_sub_rate);
+		if (q->rl->d.bug_credits > 0
+		    || (DCC_TRACE_RLIM_BIT & dccd_tracemask)) {
+			q->rl->d.bug_credits -= RL_SCALE;
+			dcc_vtrace_msg(p, args);
+		}
+	}
+}
+
+
+
+/* complain about a client */
+void
+clnt_msg(const QUEUE *q, const char *p, ...)
+{
+	va_list args;
+
+	va_start(args, p);
+	vclnt_msg(q, p, args);
+	va_end(args);
+}
+
+
+
+void
+drop_msg(QUEUE *q, const char *p, ...)
+{
+	RL *rl;
+	char buf[80];
+	va_list args;
+
+	rl = rl_get_q(q, DCC_ID_ANON);
+	rl->d.flags |= RL_FG_BL_BAD;
+
+	rl_inc(rl, &rl_anon_rate);
+	rl_inc(&rl_all_anon, &rl_all_anon_rate);
+	if (!(rl->d.flags & RL_FG_BL_ADDR)) {
+		va_start(args, p);
+		vsnprintf(buf, sizeof(buf), p, args);
+		va_end(args);
+		anon_msg("drop %s from %s", buf, Q_CIP(q));
+	}
+}