/* Distributed Checksum Clearinghouse * * server rate limiting * * Copyright (c) 2006 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 for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * Parties not eligible to receive a license under this agreement can * obtain a commercial license to use DCC and permission to use * U.S. Patent 6,330,590 by contacting Commtouch at http://www.commtouch.com/ * or by email to nospam@commtouch.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.50-1.82 $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 u_char rl_too_many; time_t clients_cleared; typedef struct ip_bl { struct ip_bl *fwd; struct in6_addr addr; struct in6_addr mask; u_char flags; /* RL_FG_BL, RL_FG_TRACE */ } IP_BL; static IP_BL *ip_bl; /* see if IP address is evil */ u_char ck_ip_bl(const struct in6_addr *pap) { const IP_BL *bl; for (bl = ip_bl; bl; bl = bl->fwd) { if (DCC_IN_BLOCK(*pap, bl->addr, bl->mask)) return bl->flags; } return 0; } /* See if this client IP address should still be blacklisted * This should be sped up if there are ever more than a very few entries */ static void ck_client_bl(RL *rl) { if (rl->flags & RL_FG_CK_BL) return; rl->flags |= RL_FG_CK_BL; rl->flags &= ~(RL_FG_BL | RL_FG_TRACE); rl->flags |= ck_ip_bl(&rl->clnt_addr); } 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_FG_CK_BL; } } void check_blacklist_file(void) { #define BL_NM "blacklist" DCC_FNM_LNO_BUF fnm_buf; static time_t prev_mtime; 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) { if (serrno != errno) { serrno = errno; dcc_error_msg("stat(%s): %s", DB_NM2PATH_ERR(BL_NM), ERROR_STR()); } } else if (prev_mtime != 0) { dcc_trace_msg("%s disappeared", DB_NM2PATH_ERR(BL_NM)); } clear_bl(); prev_mtime = 0; return; } if (prev_mtime == sb.st_mtime) return; /* it has changed, so parse it */ clear_bl(); prev_mtime = 0; f = fopen(BL_NM, "r"); if (!f) { if (serrno != errno) { serrno = errno; dcc_error_msg("fopen(%s): %s", DB_NM2PATH_ERR(BL_NM), ERROR_STR()); } return; } if (0 > fstat(fileno(f), &sb)) { 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; 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') { 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 = RL_FG_BL; for (;;) { if (!CSTRCMP(p, "trace") && 0 != (i = strspn(p+STRZ("trace"), " \t"))) { p += i+STRZ("trace"); flags |= RL_FG_TRACE; continue; } if (!CSTRCMP(p, "ok") && 0 != (i = strspn(p+STRZ("ok"), " \t"))) { p += i+STRZ("ok"); flags &= ~RL_FG_BL; continue; } break; } i = dcc_str2cidr(dcc_emsg, &addr, &mask, 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)); break; } bl = dcc_malloc(sizeof(*bl)); bl->addr = addr; bl->mask = mask; bl->flags = flags; bl->fwd = ip_bl; ip_bl = bl; ++entries; } fclose(f); if (entries) dcc_trace_msg("read %d entries from %s", entries, DB_NM2PATH_ERR(BL_NM)); } 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) { 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; } 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; } 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, *rl2; time_t stale; for (rl = rl_oldest; rl != 0; rl = rl->newer) { if (rl->ref_cnt) continue; /* 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; /* 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 just paused */ if (rl->last_used > clients_cleared) { rl_too_many = 1; stale = db_time.tv_sec - (CLIENTS_AGE*3)/4; for (rl2 = rl; rl2 && rl2->last_used <= stale; rl2 = rl2->newer) { /* avoid forgeting bad guys */ if (rl2->requests < 100 && RL_REQUESTS_AVG(rl) < 100 && !(rl->flags & RL_FG_BL)) { rl = rl2; break; } } } } /* recycle a 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_client_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_client_bl(rl); return rl; } 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, u_char **pflags, #ifdef DCC_PKT_VERSION6 const QUEUE *q, #endif u_char flags, 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; #ifdef DCC_PKT_VERSION6 if (q && q->pkt.hdr.pkt_vers <= DCC_PKT_VERSION6) { #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) DCC_ADMN_RESP_CLIENTSv6 *cl; int addr_len; cl = (DCC_ADMN_RESP_CLIENTSv6 *)cp0; *pflags = 0; cl->flags = flags & (DCC_ADMN_RESP_CLIENTS_BL | DCC_ADMN_RESP_CLIENTS_IPV6 | DCC_ADMN_RESP_CLIENTS_SKIP); if (flags & DCC_ADMN_RESP_CLIENTS_SKIP) { CPY4(cl->last_used, 0); CPY3(cl->requests, last_used); } else { CPY4(cl->last_used, last_used); CPY3(cl->requests, requests); } CPY2(cl->nops, nops); CPY4(cl->clnt_id, clnt_id); if (!clnt_addr) { addr_len = 4; memset(&cl->addr, 0, addr_len); } else if (DCC_IN6_ADDR_V4MAPPED(clnt_addr)) { addr_len = 4; memcpy(&cl->addr, &clnt_addr->s6_addr32[3], addr_len); } else { cl->flags |= DCC_ADMN_RESP_CLIENTS_IPV6; addr_len = 16; memcpy(&cl->addr, clnt_addr, addr_len); } return sizeof(*cl) - sizeof(cl->addr) + addr_len; #undef CPY2 #undef CPY3 #undef CPY4 } #endif cp = cp0; *pflags = cp0; 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 **pflags, const QUEUE *q, u_int skipped) { return client_pack(cp, pflags, q, DCC_ADMN_RESP_CLIENTS_SKIP, DCC_ID_ANON, skipped, 0, 0, 0, 0); } void clients_get_id(DCC_ADMN_RESP_VAL *val, int *lenp, /* buffer length */ const QUEUE *q, 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 *pflags; u_int skipped; int len, len_lim; pflags = 0; len_lim = *lenp; #ifdef DCC_PKT_VERSION6 if (q->pkt.hdr.pkt_vers <= DCC_PKT_VERSION6) len_lim *= sizeof(DCC_ADMN_RESP_CLIENTSv6); else #endif 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->flags &= ~RL_FG_MARKED; for (rl = rl_newest; ; rl = rl->older) { if (!rl) { if (pflags) *pflags |= DCC_ADMN_RESP_CLIENTS_LAST; break; } if (rl->last_used == 0) continue; if (rl->flags & RL_FG_MARKED) continue; if (addr6 && !DCC_IN_BLOCK(rl->clnt_addr, *addr6, *mask6)) { rl->flags |= RL_FG_MARKED; continue; } requests = 0; nops = 0; for (rl2 = rl; rl2 != 0; rl2 = rl2->older) { if (rl2->clnt_id != rl->clnt_id) continue; rl2->flags |= RL_FG_MARKED; if (addr6 && !DCC_IN_BLOCK(rl->clnt_addr, *addr6, *mask6)) continue; if (req_flags & DCC_AOP_CLIENTS_AVG) { rl_avg_requests_age(rl2, 0); requests += RL_REQUESTS_AVG(rl2); nops += RL_NOPS_AVG(rl2); } else { requests += rl2->requests; nops += rl2->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, &pflags, q, 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], &pflags, q, 0, rl->clnt_id, rl->last_used, requests, nops, 0, 0); } if (skipped) client_pack_skip(skip, &pflags, q, skipped); *lenp = len; } /* get the most recent clients */ int /* +/- number of clients */ clients_get(DCC_ADMN_RESP_VAL *val, int *lenp, /* buffer length */ #ifdef DCC_PKT_VERSION6 const QUEUE *q, #endif 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, *pflags; int skipped, total, len, len_lim; pflags = 0; if (!val || !lenp) { len_lim = 0; } else { len_lim = *lenp; #ifdef DCC_PKT_VERSION6 if (q && q->pkt.hdr.pkt_vers <= DCC_PKT_VERSION6) len_lim *= sizeof(DCC_ADMN_RESP_CLIENTSv6); else #endif 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) { if (pflags) *pflags |= DCC_ADMN_RESP_CLIENTS_LAST; break; } if (rl->last_used == 0) continue; if (rl->requests != 0) ++total; /* compute only the total for `cdcc stats` if buffer is null */ if (len_lim == 0) continue; if (addr6 && !DCC_IN_BLOCK(rl->clnt_addr, *addr6, *mask6)) continue; if (offset != 0) { --offset; continue; } if (req_flags & DCC_AOP_CLIENTS_AVG) { rl_avg_requests_age(rl, 0); requests = RL_REQUESTS_AVG(rl); nops = RL_NOPS_AVG(rl); } else { requests = rl->requests; nops = rl->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->flags & RL_FG_BL) || rl->requests == 0)) { if (!skip) { /* start a new fake record */ skip = &val->clients[len]; len += client_pack_skip(skip, &pflags, q, 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->vers != prev_vers) { vers = rl->vers; prev_vers = vers; } else { vers = 0; } len += client_pack(&val->clients[len], &pflags, q, (rl->flags & RL_FG_BL) ? DCC_ADMN_RESP_CLIENTS_BL : 0, rl->clnt_id, rl->last_used, requests, nops, vers, &rl->clnt_addr); } /* put final total number of skipped records in the output */ if (skipped) client_pack_skip(skip, &pflags, q, skipped); if (lenp) *lenp = len; /* return negative total if we have more clients than RL blocks */ if (rl_too_many) return -total; return total; } /* 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; rl_too_many = 0; } 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 += 600*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->vers = q->pkt.hdr.pkt_vers; ++rl->requests; rl_age(rl, &rl_sub_rate); rl->request_credits -= RL_SCALE; if (rl->flags & RL_FG_TRACE) dcc_trace_msg("%d %s%s %s", id, Q_CIP(q), (rl->flags & RL_FG_BL) ? " blacklisted" : "", dcc_hdr_op2str(0, 0, &q->pkt.hdr)); if (rl->flags & RL_FG_BL) { if (!(rl->flags & RL_FG_TRACE)) TMSG3(BL, "%d %s blacklisted %s", id, Q_CIP(q), dcc_hdr_op2str(0, 0, &q->pkt.hdr)); 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->vers = q->pkt.hdr.pkt_vers; ++rl->requests; rl_age(rl, &rl_anon_rate); rl->request_credits -= RL_SCALE; if (rl->flags & RL_FG_TRACE) dcc_trace_msg("anonymous %s%s %s", Q_CIP(q), (rl->flags & RL_FG_BL) ? " blacklisted" : "", dcc_hdr_op2str(0, 0, &q->pkt.hdr)); if (rl->flags & RL_FG_BL) { if (!(rl->flags & RL_FG_TRACE)) TMSG2(BL, "anonymous %s blacklisted %s", Q_CIP(q), dcc_hdr_op2str(0, 0, &q->pkt.hdr)); 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; char ob[DCC_OPBUF]; /* require a client-ID, our server-ID, or the anonymous client-ID * to consider allowing an administrative request */ 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_hdr_op2str(ob, sizeof(ob), &q->pkt.hdr)); 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; char ob[DCC_OPBUF]; /* 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_hdr_op2str(ob, sizeof(ob), &q->pkt.hdr)); 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); } } }