Mercurial > notdcc
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)); + } +}