Mercurial > notdcc
view dccd/rl.c @ 1:9b8d79ac0dc3
blindly adding debian patch for old version
author | Peter Gervai <grin@grin.hu> |
---|---|
date | Tue, 10 Mar 2009 14:25:08 +0100 |
parents | c7f6b056b673 |
children |
line wrap: on
line source
/* 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)); } }