/*
* Copyright 2000, 2001 Chip Norkus
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The names of the maintainers, developers and contributors may not be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE MAINTAINER, DEVELOPERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE DEVELOPERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "struct.h"
#include "common.h"
#include "sys.h"
#include "res.h"
#include "h.h"
#include "numeric.h"
#include "blalloc.h"
#include "memcount.h"
#include <sys/types.h>
#include <sys/socket.h>
#include "queue.h"
#include "throttle.h"
BlockHeap *hashent_freelist;
BlockHeap *throttle_freelist;
/*******************************************************************************
* hash code here. why isn't it in hash.c? see the license. :)
******************************************************************************/
hashent *hashent_alloc()
{
return BlockHeapALLOC(hashent_freelist, hashent);
}
void hashent_free(hashent *hp)
{
BlockHeapFree(hashent_freelist, hp);
}
/* hash_table creation function. given the user's paramters, allocate
* and empty a new hash table and return it. */
hash_table *
create_hash_table(int elems, size_t offset, size_t len, int flags,
int (*cmpfunc)(void *, void *))
{
hash_table *htp = MyMalloc(sizeof(hash_table));
htp->size = elems;
htp->keyoffset = offset;
htp->keylen = len;
htp->flags = flags;
htp->cmpfunc = cmpfunc;
htp->table = MyMalloc(sizeof(hashent_list) * htp->size);
memset(htp->table, 0, sizeof(hashent_list) * htp->size);
return htp;
}
/* hash_table destroyer. sweep through the given table and kill off every
* hashent */
void
destroy_hash_table(hash_table *table)
{
hashent *hep;
int i;
for (i = 0;i < table->size;i++)
{
while (!SLIST_EMPTY(&table->table[i]))
{
hep = SLIST_FIRST(&table->table[i]);
SLIST_REMOVE_HEAD(&table->table[i], lp);
hashent_free(hep);
}
}
MyFree(table->table);
MyFree(table);
}
/* this is an expensive function. it's not the sort of thing one should be
* calling a lot, however, in the right situations it can provide a lot of
* benefit */
void
resize_hash_table(hash_table *table, int elems)
{
hashent_list *oldtable;
int oldsize, i;
hashent *hep;
/* preserve the old table, then create a new one. */
oldtable = table->table;
oldsize = table->size;
table->size = elems;
table->table = MyMalloc(sizeof(hashent_list) * table->size);
memset(table->table, 0, sizeof(hashent_list) * table->size);
/* now walk each bucket in the old table, pulling off individual entries
* and re-adding them to the table as we go */
for (i = 0;i < oldsize;i++)
{
while (!SLIST_EMPTY(&oldtable[i]))
{
hep = SLIST_FIRST(&oldtable[i]);
hash_insert(table, hep->ent);
SLIST_REMOVE_HEAD(&oldtable[i], lp);
hashent_free(hep);
}
}
MyFree(oldtable);
}
/* get the hash of a given key. really only useful for insert/delete */
unsigned int
hash_get_key_hash(hash_table *table, void *key, size_t offset)
{
char *rkey = (char *)key + offset;
int len = table->keylen;
unsigned int hash = 0;
if (!len)
len = strlen(rkey);
else if (table->flags & HASH_FL_STRING)
{
len = strlen(rkey);
if (len > table->keylen)
len = table->keylen;
}
/* I borrowed this algorithm from perl5. Kudos to Larry Wall & co. */
if (table->flags & HASH_FL_NOCASE)
while (len--)
hash = hash * 33 + ToLower(*rkey++);
else
while (len--)
hash = hash * 33 + *rkey++;
return hash % table->size;
}
/* add the given item onto the hash */
int
hash_insert(hash_table *table, void *ent)
{
int hash = hash_get_key_hash(table, ent, table->keyoffset);
hashent *hep = hashent_alloc();
hep->ent = ent;
SLIST_INSERT_HEAD(&table->table[hash], hep, lp);
return 1;
}
/* delete the given item from the hash */
int
hash_delete(hash_table *table, void *ent)
{
int hash = hash_get_key_hash(table, ent, table->keyoffset);
hashent *hep;
SLIST_FOREACH(hep, &table->table[hash], lp)
{
if (hep->ent == ent)
break;
}
if (hep == NULL)
return 0;
SLIST_REMOVE(&table->table[hash], hep, hashent_t, lp);
hashent_free(hep);
return 1;
}
/* last, but not least, the find function. given the table and the key to
* look for, it hashes the key, and then calls the compare function in the
* given table slice until it finds the item, or reaches the end of the
* list. */
void *
hash_find(hash_table *table, void *key)
{
int hash = hash_get_key_hash(table, key, 0);
hashent *hep;
SLIST_FOREACH(hep, &table->table[hash], lp)
{
if (!table->cmpfunc(&((char *)hep->ent)[table->keyoffset], key))
return hep->ent;
}
return NULL; /* not found */
}
/*******************************************************************************
* actual throttle code here ;)
******************************************************************************/
LIST_HEAD(throttle_list_t, throttle_t) throttles;
typedef struct throttle_t
{
char addr[HOSTIPLEN + 1]; /* address of the throttle */
int conns; /* number of connections seen from this
address. */
time_t first; /* first time we saw this IP
* in this stage */
time_t last; /* last time we saw this IP */
time_t zline_start; /* time we placed a zline for this host,
or 0 if no zline */
int stage; /* how many times this host has been
* z-lined */
int re_zlines; /* just a statistic -- how many times has
* this host reconnected and had their
* ban reset */
LIST_ENTRY(throttle_t) lp;
} throttle;
/* variables for the throttler */
hash_table *throttle_hash;
int throttle_tcount = THROTTLE_TRIGCOUNT;
int throttle_ttime = THROTTLE_TRIGTIME;
int throttle_rtime = THROTTLE_RECORDTIME;
#ifdef THROTTLE_ENABLE
int throttle_enable = 1;
#else
int throttle_enable = 0;
#endif
int numthrottles = 0; /* number of throttles in existence */
#ifdef THROTTLE_ENABLE
void throttle_init(void)
{
hashent_freelist = BlockHeapCreate(sizeof(hashent), 1024);
throttle_freelist = BlockHeapCreate(sizeof(throttle), 1024);
/* create the throttle hash. */
throttle_hash = create_hash_table(THROTTLE_HASHSIZE,
offsetof(throttle, addr), HOSTIPLEN,
HASH_FL_STRING, (int (*)(void *, void *))strcmp);
}
throttle *throttle_alloc()
{
return BlockHeapALLOC(throttle_freelist, throttle);
}
void throttle_free(throttle *tp)
{
BlockHeapFree(throttle_freelist, tp);
}
/* returns the zline time, in seconds */
static int
throttle_get_zline_time(int stage)
{
switch(stage)
{
case -1:
return 0; /* no throttle */
case 0:
return 120; /* 2 minutes */
case 1:
return 300; /* 5 minutes */
case 2:
return 900; /* 15 minutes */
case 3:
return 1800; /* a half hour */
default:
return 3600; /* an hour */
}
return 0; /* dumb compiler */
}
void
throttle_remove(char *host)
{
throttle *tp = hash_find(throttle_hash, host);
if(tp)
{
LIST_REMOVE(tp, lp);
hash_delete(throttle_hash, tp);
throttle_free(tp);
numthrottles--;
}
}
void
throttle_force(char *host)
{
throttle *tp = hash_find(throttle_hash, host);
if (tp == NULL)
{
/* we haven't seen this one before, create a new throttle and add it to
* the hash. XXX: blockheap code should be used, but the blockheap
* allocator available in ircd is broken beyond repair as far as I'm
* concerned. -wd */
tp = throttle_alloc();;
strcpy(tp->addr, host);
tp->stage = -1; /* no zline stage yet */
tp->zline_start = 0;
tp->conns = 0;
tp->first = NOW;
tp->re_zlines = 0;
hash_insert(throttle_hash, tp);
LIST_INSERT_HEAD(&throttles, tp, lp);
numthrottles++;
}
/* now force them to be autothrottled if they reconnect. */
tp->conns = -1;
tp->last = tp->first = NOW;
}
/* fd is -1 for remote signons */
int
throttle_check(char *host, int fd, time_t sotime)
{
throttle *tp = hash_find(throttle_hash, host);
if (!throttle_enable)
return 1; /* always successful */
/* If this is an old remote signon, just ignore it */
if(fd == -1 && (NOW - sotime > throttle_ttime))
return 1;
/* If this user is signing on 'in the future', we need to
fix that. Someone has a bad remote TS, perhaps we should complain */
if(sotime > NOW)
sotime = NOW;
if (tp == NULL)
{
/* we haven't seen this one before, create a new throttle and add it to
* the hash. XXX: blockheap code should be used, but the blockheap
* allocator available in ircd is broken beyond repair as far as I'm
* concerned. -wd */
tp = throttle_alloc();;
strcpy(tp->addr, host);
tp->stage = -1; /* no zline stage yet */
tp->zline_start = 0;
tp->conns = 0;
tp->first = sotime;
tp->re_zlines = 0;
hash_insert(throttle_hash, tp);
LIST_INSERT_HEAD(&throttles, tp, lp);
numthrottles++;
}
else if(tp->zline_start)
{
time_t zlength = throttle_get_zline_time(tp->stage);
/* If they're zlined, drop them */
/* Also, reset the zline counter */
if(sotime - tp->zline_start < zlength)
{
/* don't reset throttle time for new remote signons */
if(fd == -1)
return 0;
/*
* Reset the z-line period to start now
* Mean, but should get the bots and help the humans
*/
tp->re_zlines++;
tp->zline_start = sotime;
return 0;
}
/* may look redundant, but it fixes it if
someone sets throttle_ttime to something insane */
tp->conns = 0;
tp->first = sotime;
tp->zline_start = 0;
}
/* got a throttle, up the conns */
if(tp->conns >= 0)
tp->conns++;
tp->last = sotime;
/* check the time bits, if they exceeded the throttle timeout, we should
* actually remove this structure from the hash and free it and create a
* new one, except that would be preposterously expensive, so we just
* re-set variables ;) -wd */
if (sotime - tp->first > throttle_ttime)
{
tp->conns = 1;
tp->first = sotime;
/* we can probably gaurantee they aren't going to be throttled, return
* success */
return 1;
}
if (tp->conns == -1)
{
/* This is a forced throttle, drop 'em! */
return 0;
}
if (tp->conns >= throttle_tcount)
{
/* mark them as z:lined (we do not actually add a Z:line as this would
* be wasteful) and let local +c ops know about this */
if (fd != -1)
{
char errbufr[512];
int zlength, elength;
tp->stage++;
zlength = throttle_get_zline_time(tp->stage);
/* let +c ops know */
sendto_realops_lev(REJ_LEV, "throttled connections from %s (%d in"
" %d seconds) for %d minutes (offense %d)",
tp->addr, tp->conns, sotime - tp->first,
zlength / 60, tp->stage + 1);
elength = ircsnprintf(errbufr, 512, ":%s NOTICE ZUSR :You have"
" been throttled for %d minutes for too"
" many connections in a short period of time."
" Further connections in this period will"
" reset your throttle and you will have to"
" wait longer.\r\n", me.name, zlength / 60);
send(fd, errbufr, elength, 0);
if(throttle_get_zline_time(tp->stage+1) != zlength)
{
elength = ircsnprintf(errbufr, 512, ":%s NOTICE ZUSR :When you"
" return, if you are throttled again, "
"your throttle will last longer.\r\n",
me.name);
send(fd, errbufr, elength, 0);
}
/* We steal this message from undernet, because mIRC detects it
* and doesn't try to autoreconnect */
elength = ircsnprintf(errbufr, 512, "ERROR :Your host is trying "
"to (re)connect too fast -- throttled.\r\n",
tp->addr);
send(fd, errbufr, elength, 0);
tp->zline_start = sotime;
}
else
{
/* it might be desireable at some point to let people know about
* these problems. for now, however, don't. */
}
return 0; /* drop 'em */
}
return 1; /* they're okay. */
}
/* walk through our list of throttles, expire any as necessary. in the case of
* Z:lines, expire them at the end of the Z:line timeout period. */
/* Expire at the end of the zline timeout period plus throttle_rtime */
void
throttle_timer(time_t now)
{
throttle *tp, *tp2;
time_t zlength;
if (!throttle_enable)
return;
tp = LIST_FIRST(&throttles);
while (tp != NULL)
{
zlength = throttle_get_zline_time(tp->stage);
tp2=LIST_NEXT(tp, lp);
if ((now == 0) || (tp->zline_start &&
(now - tp->zline_start) >= (zlength + throttle_rtime)) ||
(!tp->zline_start && (now - tp->last) >= throttle_rtime))
{
/* delete this item */
LIST_REMOVE(tp, lp);
hash_delete(throttle_hash, tp);
throttle_free(tp);
numthrottles--;
}
tp=tp2;
}
}
void throttle_rehash(void)
{
throttle_timer(0);
}
void throttle_resize(int size)
{
resize_hash_table(throttle_hash, size);
}
void throttle_stats(aClient *cptr, char *name)
{
int pending = 0, bans = 0;
throttle *tp;
unsigned int tcnt, tsz, hcnt, hsz;
tcnt = throttle_freelist->blocksAllocated *
throttle_freelist->elemsPerBlock;
tsz = tcnt * throttle_freelist->elemSize;
hcnt = hashent_freelist->blocksAllocated *
hashent_freelist->elemsPerBlock;
hsz = hcnt * hashent_freelist->elemSize;
sendto_one(cptr, ":%s %d %s :throttles: %d", me.name, RPL_STATSDEBUG, name,
numthrottles);
sendto_one(cptr, ":%s %d %s :alloc memory: %d throttles (%d bytes), "
"%d hashents (%d bytes)", me.name, RPL_STATSDEBUG, name,
tcnt, tsz, hcnt, hsz);
sendto_one(cptr, ":%s %d %s :throttle hash table size: %d", me.name,
RPL_STATSDEBUG, name, throttle_hash->size);
/* now count bans/pending */
LIST_FOREACH(tp, &throttles, lp)
{
if (tp->zline_start)
bans++;
else
pending++;
}
sendto_one(cptr, ":%s %d %s :throttles pending=%d bans=%d", me.name,
RPL_STATSDEBUG, name, pending, bans);
LIST_FOREACH(tp, &throttles, lp)
{
int ztime = throttle_get_zline_time(tp->stage);
if (tp->zline_start && tp->zline_start + ztime > NOW)
sendto_one(cptr, ":%s %d %s :throttled: %s [stage %d, %d secs"
" remain, %d futile retries]", me.name,
RPL_STATSDEBUG, name, tp->addr, tp->stage,
(tp->zline_start + ztime) - NOW, tp->re_zlines);
}
}
#else
/* ignore this -- required for drone modules and the like */
void throttle_force(char *host) {}
#endif
u_long
memcount_GenericHash(hash_table *ht, MCGenericHash *mc)
{
hashent *hep;
int i;
mc->file = __FILE__;
mc->hashtable.c = 1;
mc->hashtable.m = sizeof(*ht);
mc->buckets.c = ht->size;
mc->buckets.m = sizeof(hashent_list) * ht->size;
for (i = 0; i < ht->size; i++)
{
SLIST_FOREACH(hep, &ht->table[i], lp)
{
mc->e_hashents++;
}
}
mc->total.c += mc->hashtable.c + mc->buckets.c;
mc->total.m += mc->hashtable.m + mc->buckets.m;
return mc->total.m;
}
u_long
memcount_throttle(MCthrottle *mc)
{
#ifdef THROTTLE_ENABLE
throttle *tp;
#endif
mc->file = __FILE__;
#ifdef THROTTLE_ENABLE
LIST_FOREACH(tp, &throttles, lp)
{
mc->e_throttles++;
}
mc->e_throttle_heap = throttle_freelist;
mc->e_throttle_hash = throttle_hash;
#endif
mc->e_hashent_heap = hashent_freelist;
return 0;
}
/* vi:set ts=8 sts=4 sw=4 tw=79: */
syntax highlighted by Code2HTML, v. 0.9.1