/* Distributed Checksum Clearinghouse
 *
 * server rate limiting
 *
 * Copyright (c) 2005 by Rhyolite Software
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE
 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 * Rhyolite Software DCC 1.2.74-1.63 $Revision$
 */

#include "dccd_defs.h"


RL_RATE rl_sub_rate;
RL_RATE rl_anon_rate;
RL_RATE rl_all_anon_rate;
RL_RATE rl_bugs_rate;

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 time_t rl_last_recycled;

time_t clients_cleared;

typedef struct ip_bl {
    struct ip_bl *fwd;
    struct in6_addr addr;
    struct in6_addr mask;
} IP_BL;
IP_BL *ip_bl;



/* See if this IP address should still be blacklisted
 * This should be sped up if there are ever more than a very few entries */
static inline void
ck_bl(RL *rl)
{
	const IP_BL *bl;

	if (!(rl->flags & RL_CK_BLACKLIST)) {
		rl->flags |= RL_CK_BLACKLIST;
		for (bl = ip_bl; bl; bl = bl->fwd) {
			if (DCC_IN_BLOCK(rl->clnt_addr, bl->addr, bl->mask)) {
				rl->flags |= RL_BLACKLISTED;
				return;
			}
		}
		rl->flags &= ~RL_BLACKLISTED;
	}
}



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->flags  &= ~RL_CK_BLACKLIST;
	}
}



void
check_blacklist_file(u_char force)
{
#define BL_NM "blacklist"
	static time_t prev_mtime;
	static int serrno, syntax;
	struct stat sb;
	FILE *f;
	char buf[120];
	IP_BL *bl;
	struct in6_addr addr, mask;
	char *p;
	int lineno, i;

	/* see if the file has changed */
	if (0 > stat(BL_NM, &sb)) {
		if (errno != ENOENT) {
			if (serrno != errno) {
				serrno = errno;
				syntax = 0;
				dcc_error_msg("stat(%s): %s",
					      DCC_NM2PATH(BL_NM), ERROR_STR());
			}
		} else if (ip_bl) {
			prev_mtime = 0;
			clear_bl();
		}
		return;
	}
	if (!force && prev_mtime == sb.st_mtime)
		return;

	/* it has changed, so parse it */
	f = fopen(BL_NM, "r");
	if (!f) {
		if (serrno != errno) {
			serrno = errno;
			syntax = 0;
			dcc_error_msg("fopen(%s): %s",
				      DCC_NM2PATH(BL_NM), ERROR_STR());
		}
		if (ip_bl) {
			clear_bl();
		}
	}
	if (0 > fstat(fileno(f), &sb)) {
		dcc_error_msg("fstat(%s): %s",
			      DCC_NM2PATH(BL_NM), ERROR_STR());
		return;
	}
	prev_mtime = sb.st_mtime;

	clear_bl();
	for (lineno = 1; ; ++lineno) {
		if (!fgets(buf, sizeof(buf), f)) {
			if (ferror(f)
			    && serrno != errno) {
				serrno = errno;
				syntax = 0;
				dcc_error_msg("fgets(%s): %s",
					      DCC_NM2PATH(BL_NM),
					      ERROR_STR());
			}
			break;
		}
		/* reject lines that are too long */
		i = strlen(buf);
		if (buf[i-1] != '\n') {
			if (syntax != lineno) {
				serrno = 0;
				syntax = lineno;
				dcc_error_msg("syntax error%s",
					      fnm_lineno(BL_NM, lineno));
			}
			break;
		}

		/* ignore leading blanks, comments, and blank lines */
		p = strchr(buf, '#');
		if (p)
			*p = '\0';
		i = strspn(buf, DCC_WHITESPACE);
		if (buf[i] == '\0')
			continue;

		i = dcc_str2cidr(dcc_emsg, &addr, &mask, &buf[i],
				 BL_NM, lineno);
		if (i <= 0) {
			if (syntax != lineno) {
				serrno = 0;
				syntax = lineno;
				if (i < 0)
					dcc_error_msg("%s", dcc_emsg);
				else
					dcc_error_msg("syntax error%s",
						      fnm_lineno(BL_NM,lineno));
			}
			break;
		}

		bl = dcc_malloc(sizeof(*bl));
		bl->addr = addr;
		bl->mask = mask;
		bl->fwd = ip_bl;
		ip_bl = bl;
	}
	fclose(f);
}



static RL **
rl_hash_fnc(DCC_CLNT_ID	id, const struct in6_addr *addr)
{
	u_int sum;

	sum = 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)];
}



static void
rl_expand(void)
{
	RL *rl, *rl2, **rlh, **rl_hash_old;
	int old_len, new_len, j;

	old_len = rl_hash_len;
	new_len = max(64, queue_max);

	if (old_len != 0)
		dcc_trace_msg("increase from %d to %d RL blocks",
			      old_len, old_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 */
	rl_hash_len += new_len;
	rl_hash_old = rl_hash;
	rl_hash = dcc_malloc(rl_hash_len*sizeof(*rl_hash));
	if (!rl_hash)
		dcc_logbad(EX_OSERR, "malloc(%d RL hash table) failed",
			   rl_hash_len);
	memset(rl_hash, 0, rl_hash_len*sizeof(*rlh));
	if (old_len != 0) {
		do {
			for (rl = rl_hash_old[--old_len];
			     rl != 0;
			     rl = rl2) {
				rlh = rl_hash_fnc(rl->clnt_id, &rl->clnt_addr);
				rl2 = rl->fwd;
				rl->bak = 0;
				rl->hash = rlh;
				if ((rl->fwd = *rlh) != 0)
					rl->fwd->bak = rl;
				*rlh = rl;
			}
		} while (old_len > 0);
		dcc_free(rl_hash_old);
	}
}



/* age a rate limit */
static void
rl_age(RL *rl, const RL_RATE *credits)
{
	time_t secs;

	secs = (db_time.tv_sec - rl->last_used);

	/* only prevent overflow if no time has passed */
	if (secs != 0) {
		rl->last_used = db_time.tv_sec;

		/* reset idle counters */
		if (secs >= RL_AVG_SECS || secs < 0) {
			rl->bug_credits = rl_bugs_rate.hi;
			rl->request_credits = credits->hi;
			return;
		}

		rl->request_credits += secs*credits->sec;
		if (rl->request_credits > credits->hi)
			rl->request_credits = credits->hi;

		rl->bug_credits += secs*rl_bugs_rate.sec;
		if (rl->bug_credits > rl_bugs_rate.hi)
			rl->bug_credits = rl_bugs_rate.hi;
	}
	if (rl->request_credits < credits->lo)
		rl->request_credits = credits->lo;
	if (rl->bug_credits < rl_bugs_rate.lo)
		rl->bug_credits = rl_bugs_rate.lo;
}




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)
{
	if (rl_newest == rl)
		return;

	rl_unref(rl);
	rl->older = rl_newest;
	rl_newest->newer = rl;
	rl_newest = rl;
}



/* get a free rate limit block */
static RL *
rl_get_free(void)
{
	RL *rl;
	time_t stale;

	for (rl = rl_oldest; rl != 0; rl = rl->newer) {
		if (!rl->ref_cnt) {
			/* Found oldest free block */
			if (rl->last_used != 0) {
				/* it has been used */
				stale = db_time.tv_sec;
				/* keep a day's worth of blocks until
				 * we get enough to worry about a denial
				 * of service attack */
				if (rl_hash_len < RL_MIN_MAX)
					stale -= CLIENTS_AGE;
				else
					stale -= RL_LIFE_SECS;
				/* make more if the oldest is new */
				if (rl->last_used >= stale
				    && rl_hash_len <= RL_MAX_MAX)
					return 0;
				/* Remember how old it was */
				rl_last_recycled = rl->last_used;
			}

			/* recycle the oldest free block */
			if (rl->fwd)
				rl->fwd->bak = rl->bak;
			if (rl->bak)
				rl->bak->fwd = rl->fwd;
			else if (rl->hash)
				*rl->hash = rl->fwd;
			rl_unref(rl);
			memset(rl, 0, sizeof(*rl));

			rl->requests_avg_date = db_time.tv_sec + RL_AVG_DAY;
			rl_ref(rl);
			return rl;
		}
	}

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



/* update the number of requests made yesterday if it is time */
static inline void
rl_avg_requests_age(RL *rl,
		    u_char force)	/* sync to daily `cdcc "stats clear"` */
{
	if (rl->requests_avg_date <= db_time.tv_sec
	    || rl->requests_avg_date + RL_AVG_DAY < db_time.tv_sec
	    || force) {
		rl->requests_cur_avg = rl->requests_next_avg + rl->requests;
		rl->nops_cur_avg = rl->nops_next_avg + rl->nops;

		/* these biases effectively zero tomorrow's averages */
		rl->requests_next_avg = -rl->requests;
		rl->nops_next_avg = -rl->nops;

		rl->requests_avg_date = db_time.tv_sec + RL_AVG_DAY;
	}
}



/* get a rate limit block based on the IP address of the sender */
static RL *
rl_get(QUEUE *q)
{
	RL *rl, **rlh;
	struct in6_addr clnt_addr;
	const struct in6_addr *cap;

	if (!rl_hash_len)
		rl_expand();

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

	rlh = rl_hash_fnc(q->clnt_id, cap);
	for (rl = *rlh; rl; rl = rl->fwd) {
		if (rl->clnt_id != q->clnt_id
		    || memcmp(&rl->clnt_addr, cap, sizeof(rl->clnt_addr)))
			continue;
		rl_ref(rl);		/* found it, so make it newest */
		rl_avg_requests_age(rl, 0);
		q->rl = rl;
		++rl->ref_cnt;
		ck_bl(rl);
		return rl;
	}

	rl = rl_get_free();
	if (!rl) {
		/* when we are out of rate limiting blocks, make more */
		rl_expand();
		/* which makes a new rate limit hash table */
		rlh = rl_hash_fnc(q->clnt_id, cap);
		rl = rl_get_free();
	}
	rl->clnt_addr = *cap;
	rl->clnt_id = q->clnt_id;
	rl->hash = rlh;
	rl->fwd = *rlh;
	if (rl->fwd)
		rl->fwd->bak = rl;
	*rlh = rl;

	q->rl = rl;
	++rl->ref_cnt;
	ck_bl(rl);
	return rl;
}



/* get 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 avg)			/* 1=get average requests */
{
#define CPY2(t,s) (t[0] = s>>8, t[1] = s)
#define CPY3(t,s) (t[0] = s>>16, t[1] = s>>8, t[2] = s)
#define CPY4(t,s) (t[0] = s>>24, t[1] = s>>16, t[2] = s>>8, t[3] = s)
	RL *rl;
	DCC_ADMN_RESP_CLIENTS *cl, *cl_skip;
	int requests, nops;
	int skipped, total, len, len_lim;

	if (!val || !lenp) {
		len_lim = 0;
	} else {
		len_lim = *lenp;
	}
	if (!thold)
		thold = 1;
	cl_skip = 0;
	skipped = 0;
	total = 0;
	len = 0;
	for (rl = rl_newest; rl != 0; rl = rl->older) {
		if (rl->last_used == 0)
			continue;

		if (rl->requests != 0)
			++total;
		/* get the part of the list that cdcc wants */
		if (offset != 0) {
			--offset;
			continue;
		}

		if (len+ISZ(*cl) > len_lim) {
			if (len_lim != 0)
				break;
			continue;
		}

		if (avg) {
			rl_avg_requests_age(rl, 0);
			requests = RL_REQUESTS_AVG(rl);
			nops = RL_NOPS_AVG(rl);
		} else {
			requests = rl->requests;
			nops = rl->nops;
		}
		if (requests < thold
		    && !(rl->flags & RL_BLACKLISTED)) {
			/* insert a fake entry for skipped records */
			if (!cl_skip) {
				cl_skip = (DCC_ADMN_RESP_CLIENTS *
					   )((char *)val->clients + len);
				memset(cl_skip, 0, sizeof(*cl_skip));
				cl_skip->flags = DCC_ADMN_RESP_CLIENTS_SKIP;
				len += (sizeof(*cl_skip)
					- sizeof(cl_skip->addr)
					+ sizeof(cl_skip->addr.ipv4));
			}
			++skipped;
			CPY3(cl_skip->requests, skipped);
			continue;
		}

		cl = (DCC_ADMN_RESP_CLIENTS *)((char *)val->clients + len);
		cl->flags = 0;
		CPY4(cl->last_used, rl->last_used);
		CPY3(cl->requests, requests);
		CPY2(cl->nops, nops);
		CPY4(cl->id, rl->clnt_id);
		if (rl->flags & RL_BLACKLISTED)
			cl->flags |= DCC_ADMN_RESP_CLIENTS_BL;
		if (DCC_IN6_ADDR_V4MAPPED(&rl->clnt_addr)) {
			memcpy(&cl->addr.ipv4,
			       &rl->clnt_addr.s6_addr32[3],
			       sizeof(cl->addr.ipv4));
			len += (sizeof(*cl) - sizeof(cl->addr)
				+ sizeof(cl->addr.ipv4));
		} else {
			cl->flags |= DCC_ADMN_RESP_CLIENTS_IPV6;
			memcpy(&cl->addr.ipv6,
			       rl->clnt_addr.s6_addr32,
			       sizeof(cl->addr.ipv6));
			len += (sizeof(*cl) - sizeof(cl->addr)
				+ sizeof(cl->addr.ipv6));
		}
	}
	if (lenp)
		*lenp = len;

	/* return negative total if we have more clients than RL blocks */
	if (rl_last_recycled > clients_cleared)
		return -total;
	return total;
#undef CPY2
#undef CPY3
#undef CPY4
}



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 avg)		/* 1=get average requests */
{
#define CPY2(t,s) (t[0] = s>>8, t[1] = s)
#define CPY3(t,s) (t[0] = s>>16, t[1] = s>>8, t[2] = s)
#define CPY4(t,s) (t[0] = s>>24, t[1] = s>>16, t[2] = s>>8, t[3] = s)
	RL *rl, *rl2;
	DCC_ADMN_RESP_CLIENTS *cl, *cl_skip;
	int requests, nops;
	u_int skipped;
	int len, len_lim;

	len_lim = *lenp;
	cl_skip = 0;
	skipped = 0;
	len = 0;
	if (!thold)
		thold = 1;
	for (rl = rl_newest; rl != 0; rl = rl->older) {
		rl->flags &= ~RL_MARKED;
	}
	for (rl = rl_newest; rl != 0; rl = rl->older) {
		if (rl->last_used == 0)
			continue;

		/* get the part of the list that cdcc wants */
		if (offset != 0) {
			--offset;
			continue;
		}

		requests = 0;
		nops = 0;
		if (!(rl->flags & RL_MARKED)) {
			for (rl2 = rl; rl2 != 0; rl2 = rl2->older) {
				if (rl2->clnt_id == rl->clnt_id) {
					if (avg) {
					    rl_avg_requests_age(rl2, 0);
					    requests += RL_REQUESTS_AVG(rl2);
					    nops += RL_NOPS_AVG(rl2);
					} else {
					    requests += rl2->requests;
					    nops += rl2->nops;
					}
					rl2->flags |= RL_MARKED;
				}
			}
		}
		if (requests < thold) {
			/* insert a fake entry for skipped records */
			if (!cl_skip) {
				cl_skip = (DCC_ADMN_RESP_CLIENTS *
					   )((char *)val->clients + len);
				memset(cl_skip, 0, sizeof(*cl_skip));
				cl_skip->flags = DCC_ADMN_RESP_CLIENTS_SKIP;
				len += (sizeof(*cl_skip)
					- sizeof(cl_skip->addr)
					+ sizeof(cl_skip->addr.ipv4));
			}
			++skipped;
			CPY3(cl_skip->requests, skipped);
			continue;
		}

		cl = (DCC_ADMN_RESP_CLIENTS *)((char *)val->clients + len);
		cl->flags = 0;
		CPY4(cl->last_used, rl->last_used);
		CPY3(cl->requests, requests);
		CPY2(cl->nops, nops);
		CPY4(cl->id, rl->clnt_id);
		memset(&cl->addr.ipv4, 0, sizeof(cl->addr.ipv4));
		len += (sizeof(*cl) - sizeof(cl->addr)
			+ sizeof(cl->addr.ipv4));

		if (len+ISZ(*cl) > len_lim)
			break;
	}
	*lenp = len;
#undef CPY2
#undef CPY3
#undef CPY4
}



/* forget old clients */
void
clients_clear(u_char force_rl_age)
{
	RL *rl;

	for (rl = rl_oldest; rl != 0; rl = rl->newer) {
		rl->requests_next_avg += rl->requests;
		rl->requests = 0;
		rl->nops_next_avg += rl->nops;
		rl->nops = 0;
		rl_avg_requests_age(rl, force_rl_age);
	}

	clients_cleared = db_time.tv_sec;
}



u_char					/* 0=bad passwd, 1=ok */
ck_sign(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 1;
	}
	return 0;
}



static void
tp2delay(QUEUE *q, time_t delay_us, u_int delay_inflate)
{
	if (delay_us == 0) {
		q->delay_us = 0;
	} else {
		q->delay_us = RL_REQUESTS_AVG(q->rl);
		q->delay_us = 1 + q->delay_us/delay_inflate;
		if (q->delay_us > DCC_ANON_DELAY_MAX / delay_us) {
			/* prevent overflow */
			q->delay_us = DCC_ANON_DELAY_MAX;
			return;
		}
		q->delay_us *= delay_us;
	}

	/* increase the delay when flooding is off or broken */
	if (flods_st != FLODS_ST_ON
	    || (iflods.active == 0 && oflods.total != 0))
		q->delay_us += 400*1000;
	if (q->delay_us > DCC_ANON_DELAY_MAX)
		q->delay_us = DCC_ANON_DELAY_MAX;
}



/* check the message authentication code and rate limit requests */
u_char					/* 0=forget DOS attack, 1=go ahead */
ck_id(QUEUE *q, DCC_CLNT_ID id)
{
#define RL_CNT2AVG(cur,lims) ((lims.hi - cur) / (RL_SCALE*RL_AVG_SECS*1.0))
	ID_TBL *tp;
	RL *rl;

	if (id == DCC_ID_ANON) {
		tp = 0;
	} else {
		/* authenticate the ID */
		if (!ck_sign(&tp, q->passwd, id, &q->pkt, q->pkt_len)) {
			if (!tp) {
				++dccd_stats.unknown_ids;
				anon_msg("unknown client-ID %d from %s",
					 id, Q_CIP(q));
			} else {
				++dccd_stats.bad_passwd;
				anon_msg("bad authentication for client-ID"
					 " %d from %s",
					 id, Q_CIP(q));
				tp = 0;
			}
		}
	}

	if (tp) {
		q->clnt_id = id;
		if (!query_only || (tp->flags & ID_FLG_RPT_OK))
			q->flags |= Q_FLG_RPT_OK;

		rl = rl_get(q);
		++rl->requests;
		rl_age(rl, &rl_sub_rate);
		rl->request_credits -= RL_SCALE;

		if (rl->flags & RL_BLACKLISTED) {
			TMSG2(BL, "%d %s blacklisted request",
			      id, Q_CIP(q));
			return 0;
		}

		if (rl->request_credits <= 0) {
			clnt_msg(q, "%.1f requests/sec are too many from %d %s",
				 RL_CNT2AVG(rl->request_credits, rl_sub_rate),
				 id, Q_CIP(q));
			++dccd_stats.rl;
			return 0;
		}

		tp2delay(q, tp->delay_us, tp->delay_inflate);

	} else {
		q->clnt_id = DCC_ID_ANON;
		if (!query_only)
			q->flags |= Q_FLG_RPT_OK;

		rl_age(&rl_all_anon, &rl_all_anon_rate);
		rl_all_anon.request_credits -= RL_SCALE;
		rl = rl_get(q);
		++rl->requests;
		rl_age(rl, &rl_anon_rate);
		rl->request_credits -= RL_SCALE;

		if (rl->flags & RL_BLACKLISTED) {
			TMSG1(BL, " anonymous %s blacklisted request",
			      Q_CIP(q));
			return 0;
		}

		if (rl->request_credits <= 0) {
			anon_msg("%.1f requests/sec are too many from anonymous"
				 " %s",
				 RL_CNT2AVG(rl->request_credits, rl_anon_rate),
				 Q_CIP(q));
			++dccd_stats.anon_rl;
			return 0;
		}
		if (rl_all_anon.request_credits <= 0) {
			anon_msg("%s contributed to %.1f"
				 " anonymous requests/sec",
				 Q_CIP(q),
				 RL_CNT2AVG(rl_all_anon.request_credits,
					    rl_all_anon_rate));
			++dccd_stats.anon_rl;
			return 0;
		}

		tp2delay(q, anon_delay_us, anon_delay_inflate);
	}

	return 1;
}



/* check the message authentication code for a client of our server-ID
 * and rate limit its messages */
u_char					/* 0=forget DOS attack, 1=go ahead */
ck_clnt_srvr_id(QUEUE *q)
{
	DCC_CLNT_ID id;
	u_char result;

	/* require a client-ID, our server-ID, or the anonymous client-ID
	 * to allow anonymous `cdcc stats` */
	id = ntohl(q->pkt.hdr.sender);
	if ((id < DCC_SRVR_ID_MIN || id > DCC_CLNT_ID_MAX)
	    && id != DCC_ID_ANON) {
		++dccd_stats.unknown_ids;
		result = ck_id(q, DCC_ID_ANON);
		if (result)
			anon_msg("bad client or server-ID %d from %s for %s",
				 id, Q_CIP(q), dcc_req_op2str(&q->pkt.a));
		return result;
	}
	return ck_id(q, id);
}



/* check the message authentication code of a request,
 * and rate limit the source */
u_char					/* 0=forget DOS attack, 1=go ahead */
ck_clnt_id(QUEUE *q)
{
	DCC_CLNT_ID id;
	u_char result;

	/* require a client-ID instead of a server-ID to discourage server
	 * operators from leaking server-ID's */
	id = ntohl(q->pkt.hdr.sender);
	if (id < DCC_CLNT_ID_MIN
	    && id != DCC_ID_ANON) {
		++dccd_stats.unknown_ids;
		result = ck_id(q, DCC_ID_ANON);
		if (result)
			anon_msg("bad client-ID %d from %s for %s",
				 id, Q_CIP(q), dcc_req_op2str(&q->pkt.a));
		return result;
	}
	return ck_id(q, id);
}



/* 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.bug_credits > 0
		|| (DCC_TRACE_RLIM_BIT & dccd_tracemask))) {
		rl_all_anon.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 an authenticated client */
void
clnt_msg(const QUEUE *q, const char *p, ...)
{
	va_list args;

	if (q->clnt_id == DCC_ID_ANON || !q->rl) {
		va_start(args, p);
		vanon_msg(p, args);
		va_end(args);
		return;
	}

	if (DCC_TRACE_CLNT_BIT & dccd_tracemask) {
		rl_age(q->rl, &rl_sub_rate);
		if (q->rl->bug_credits > 0
		    || (DCC_TRACE_RLIM_BIT & dccd_tracemask)) {
			q->rl->bug_credits -= RL_SCALE;
			va_start(args, p);
			dcc_vtrace_msg(p, args);
			va_end(args);
		}
	}
}
