/* ARISA - Ignore List * Copyright (C) 2004 Carl Ritson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include "arisa.h" #include #include /** defines **/ #define INIT_SIZE 4 #define SHRINK_RATIO 4 #define CHAIN_LIMIT 16 #define SIZE_CAP 8192 /** structures **/ typedef struct hh_bucket_t { unsigned long hash; char *host; time_t last_hit; float hits; struct hh_bucket_t *next; } hh_bucket_t; typedef struct hosthash_t { // lock_t lock; hh_bucket_t **table; unsigned int size; } hosthash_t; /** hosthash functions */ static unsigned long hosthash_hash(const char *host) { unsigned long hash = 0; int i; assert(host != NULL); for(i = 0; host[i] != '\0'; ++i) { // irc_tolower as host maybe be a hostmask with a nick in it hash = hash * 37UL + (unsigned long) irc_tolower(host[i]); } return hash; } static hosthash_t *hosthash_init(void) { hosthash_t *ht = xalloc(sizeof(hosthash_t)); unsigned int i; // LOCK_INIT(&(ht->lock)); ht->table = xalloc(sizeof(hh_bucket_t *) * INIT_SIZE); ht->size = INIT_SIZE; for(i = 0; i < ht->size; ++i) { ht->table[i] = NULL; } return ht; } static hh_bucket_t *init_bucket(unsigned long hash, const char *host) { hh_bucket_t *b = xalloc(sizeof(hh_bucket_t)); b->hash = hash; b->host = xstrdup(host); b->hits = 0.0; b->last_hit = 0; b->next = NULL; return b; } static void free_bucket(hh_bucket_t *b) { xfree(b->host); xfree(b); } static void flush(hosthash_t *ht) { hh_bucket_t *b = NULL, *p = NULL; unsigned int i; // LOCK(ht); for(i = 0; i < ht->size; ++i) { for(p = ht->table[i]; p != NULL; p = b) { b = p->next; free_bucket(p); } ht->table[i] = NULL; } // UNLOCK(ht); } void hosthash_free(hosthash_t *ht) { flush(ht); // LOCK_FREE(&(ht->lock)); xfree(ht->table); xfree(ht); } /* lock assumed */ static int place(hosthash_t *ht, hh_bucket_t *b, hh_bucket_t **dup) { hh_bucket_t *p = ht->table[b->hash % ht->size]; int depth = 0; b->next = NULL; if(p == NULL) { ht->table[b->hash % ht->size] = b; } else { for(;;) { depth++; if(irc_strcasecmp(p->host,b->host) == 0) { if(dup != NULL) *dup = p; return -1; } if(p->next != NULL) p = p->next; else break; } p->next = b; } return depth; } /* lock assumed */ static void resize(hosthash_t *ht, unsigned int new_size) { hh_bucket_t **old_table = ht->table; unsigned int old_size = ht->size; unsigned int i; hh_bucket_t *b = NULL, *p = NULL; if(new_size > SIZE_CAP || new_size < INIT_SIZE || new_size == old_size) return; ht->size = new_size; ht->table = xalloc(sizeof(hh_bucket_t *)*ht->size); for(i = 0; i < ht->size; ++i) ht->table[i] = NULL; for(i = 0; i < old_size; ++i) { if(old_table[i] == NULL) continue; for(p = old_table[i]; p != NULL; p = b) { b = p->next; if(place(ht,p,NULL) == -1) free_bucket(p); } } xfree(old_table); } static inline void decay_bucket(hh_bucket_t *bkt, time_t now, int trigger, int decay) { if(bkt->hits > 0.0) { bkt->hits -= ((float)(now - bkt->last_hit) / (float) decay) * (float) trigger; if(bkt->hits < 0.0) bkt->hits = 0.0; bkt->last_hit = now; } } static int hosthash_trigger(hosthash_t *ht, const char *host, time_t now, int trigger, int decay) { hh_bucket_t *lbkt, *bkt, *nbkt; unsigned long hash; int ret = 0, depth = 0; assert(ht != NULL); assert(host != NULL); hash = hosthash_hash(host); // LOCK(ht); lbkt = NULL; bkt = ht->table[hash % ht->size]; while(bkt != NULL) { depth++; decay_bucket(bkt,now,trigger,decay); if(irc_strcasecmp(bkt->host,host) == 0) break; if(bkt->hits <= 0.0) { nbkt = bkt->next; if(lbkt != NULL) lbkt->next = bkt->next; else ht->table[hash % ht->size] = bkt->next; free_bucket(bkt); bkt = nbkt; } else { lbkt = bkt; bkt = bkt->next; } } if(bkt == NULL) { bkt = init_bucket(hash,host); if(lbkt != NULL) lbkt->next = bkt; else ht->table[hash % ht->size] = bkt; if((depth+1) > CHAIN_LIMIT) resize(ht,ht->size * 2); } if(ceil(bkt->hits) >= (float) trigger) { ret = 1; } else { bkt->hits += 1.0; if(ceil(bkt->hits) > (float) trigger) ret = 1; } bkt->last_hit = now; // this isn't really required but anyway... //D("bkt: %p, last_hit: %ld, hits: %.2f",bkt,bkt->last_hit,bkt->hits); // UNLOCK(ht); return ret; } static void prune(hosthash_t *ht, time_t now, int trigger, int decay) { hh_bucket_t *lbkt, *bkt, *nbkt; int i,chain_length,max_chain_length = 0; assert(ht != NULL); // LOCK(ht); for(i = 0; i < ht->size; ++i) { lbkt = NULL; bkt = ht->table[i]; for(chain_length = 0; bkt != NULL; ++chain_length) { decay_bucket(bkt,now,trigger,decay); nbkt = bkt->next; if(bkt->hits <= 0.0) { free_bucket(bkt); if(lbkt != NULL) lbkt->next = nbkt; else ht->table[i] = nbkt; chain_length--; } else lbkt = bkt; bkt = nbkt; } if(chain_length > max_chain_length) max_chain_length = chain_length; } if(max_chain_length < (CHAIN_LIMIT / SHRINK_RATIO)) resize(ht,ht->size / 2); // UNLOCK(ht); } static void hosthash_reset(hosthash_t *ht, const char *host) { hh_bucket_t *bkt; unsigned long hash; assert(ht != NULL); assert(host != NULL); hash = hosthash_hash(host); // LOCK(ht); bkt = ht->table[hash % ht->size]; while(bkt != NULL) { if(irc_strcasecmp(bkt->host,host) == 0) { bkt->hits = 0.0; break; } else bkt = bkt->next; } // UNLOCK(ht); } /** ignore list functions **/ ignore_entry_t *alloc_ignore_entry(void) { ignore_entry_t *ent = xalloc(sizeof(ignore_entry_t)); ent->flags = 0; ent->mask = NULL; ent->expires = 0; return ent; } void free_ignore_entry(ignore_entry_t *ent) { if(ent->mask != NULL) xfree(ent->mask); xfree(ent); } ignore_list_t *alloc_ignore_list(void) { ignore_list_t *l = xalloc(sizeof(ignore_list_t)); LOCK_INIT(&(l->lock)); l->entries = NULL; l->no_entries = 0; l->last_prune = 0; l->trigger = 0; l->period = 300; l->log = 0; l->data = hosthash_init(); return l; } void free_ignore_list(ignore_list_t *l) { LOCK_FREE(&(l->lock)); if(l->entries != NULL) { int i; for(i = 0; i < l->no_entries; ++i) free_ignore_entry(l->entries[i]); xfree(l->entries); } hosthash_free((hosthash_t *)l->data); xfree(l); } enum { IGNORE_YES = 1, IGNORE_NO = 0, IGNORE_MAYBE = -1}; static int test_list(ignore_list_t *l, time_t now, const char *hostmask) { char buffer[512]; int i, expired = 0, ret = IGNORE_MAYBE; for(i = 0; hostmask[i] != '\0' && i < (sizeof(buffer)-1); ++i) buffer[i] = irc_tolower(hostmask[i]); buffer[i] = '\0'; //D("hostmask: \"%s\", buffer: \"%s\"",hostmask,buffer); for(i = 0; i < l->no_entries && ret == IGNORE_MAYBE; ++i) { ignore_entry_t *e = l->entries[i]; if(e->expires > now || (e->flags & IGNORE_FOREVER)) { if(wild_match(buffer,e->mask)) { //D("yes: %s",e->mask); ret = e->flags & IGNORE_INVERSION ? IGNORE_NO : IGNORE_YES; } //else //D("no: %s",e->mask); } else { if(l->log) LOGP(L_INF,"Removed ignore entry for %s", e->mask); free_ignore_entry(e); l->entries[i] = NULL; expired++; } } if(expired) PTRARR_DEL(&(l->entries),&(l->no_entries),NULL); return ret; } static int add_entry(ignore_list_t *l, const char *mask, int flags, time_t expires) { ignore_entry_t *e = alloc_ignore_entry(); int i; for(i = 0; i < l->no_entries; ++i) { if(strcasecmp(l->entries[i]->mask,mask) == 0) return -1; } e->flags = flags; e->expires = expires; e->mask = xstrdup(mask); for(i = 0; e->mask[i] != '\0'; ++i) e->mask[i] = irc_tolower(e->mask[i]); PTRARR_ADD(&(l->entries),&(l->no_entries),e); if(l->log) { char tbuf[32]; LOGP(L_INF,"Added %signore entry for %s, %s%s", e->flags & IGNORE_INVERSION ? "un" : "", e->mask, e->flags & IGNORE_FOREVER ? "Forever" : "Till: ", e->flags & IGNORE_FOREVER ? "" : timestr(e->expires,tbuf,sizeof(tbuf))); } return 0; } int ignore_test(ignore_list_t *l, const char *hostmask) { time_t now = xtime(); int ret; LOCK(l); if((l->last_prune + 600) < now) { prune((hosthash_t *)l->data,now,l->trigger,l->period); l->last_prune = now; } ret = test_list(l,now,hostmask); if(ret != IGNORE_MAYBE) { UNLOCK(l); return ret; } else ret = IGNORE_NO; if(l->trigger != 0 && l->period > 0) { if(hosthash_trigger((hosthash_t *)l->data,hostmask,now, l->trigger,l->period)) { char *ptr = strchr(hostmask,'@'); if(ptr != NULL) { char buffer[512]; snprintf(buffer,sizeof(buffer),"*%s",ptr); add_entry(l,buffer,0,now + l->period); } else { add_entry(l,hostmask,0,now + l->period); } hosthash_reset((hosthash_t *)l->data,hostmask); ret = IGNORE_YES; } } UNLOCK(l); return ret; } int ignore_add(ignore_list_t *l, const char *hostmask, int flags, time_t expires) { int ret; LOCK(l); ret = add_entry(l,hostmask,flags,expires); UNLOCK(l); return 0; } int ignore_del(ignore_list_t *l, int pos, const char *hostmask) { int i,ret = -1; LOCK(l); if(hostmask == NULL && pos >= 0 && pos < l->no_entries) { l->entries[pos]->flags = 0; l->entries[pos]->expires = 0; ret = 0; } else if(hostmask != NULL) { for(i = 0; i < l->no_entries && ret == -1; ++i) { if(irc_strcasecmp(l->entries[i]->mask,hostmask) == 0) { l->entries[i]->flags = 0; l->entries[i]->expires = 0; ret = 0; } } } UNLOCK(l); return ret; } void ignore_wipe(ignore_list_t *l) { int i; LOCK(l); if(l->entries != NULL) { for(i = 0; i < l->no_entries; ++i) free_ignore_entry(l->entries[i]); xfree(l->entries); l->no_entries = 0; } flush((hosthash_t *)l->data); if(l->log) LOGP(L_INF,"Wiped Ignore List"); UNLOCK(l); }