/*- * Copyright (c) 2004 Free (Olivier Beyssac) * All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 #include #include #include #include #include #include #include #include #include "ipinfo.h" #include "iptree.h" #include "options.h" #define get_pos(x, y) (x + y * 256) extern struct options opt; #define IPTREE_M_IPLIST (0) #define IPTREE_M_BLACKLIST (1) /* dlist is a common double linked list made of dl_nodes */ typedef struct dlist *dlist; struct dl_node { struct ipinfo *p; struct dl_node *prev; struct dl_node *next; }; struct dlist { struct dl_node *first; struct dl_node *last; size_t count; }; /* iptree stores IP addresses IP x.y.z.t is in the tree at position iptree->[x + 256 * y]->c[z]->d[t] */ struct ipt_leaf { size_t count; struct ipinfo *d[256]; }; struct ipt_node { size_t count; struct ipt_leaf *c[256]; }; struct iptree { struct ipt_node *ab[256 * 256]; dlist ipl; /* Whole IPs list */ dlist bl; /* Blacklisted IPs */ }; static void iptree_free(const iptree ipt, struct ipinfo *val); static void *xcalloc(size_t nmemb, size_t size) { void *tmp; if ((tmp = calloc(nmemb, size)) == NULL) syslog(LOG_ERR, "unable to calloc()"); return tmp; } /* Dump dlist to file */ static void dlist_dump2file(const dlist dl, FILE *f) { struct dl_node *p; for (p = dl->first; p; p = p->next) fwrite(p->p, sizeof(struct ipinfo), 1, f); } /* Remove the first element of dlist and return its address */ static struct dl_node *dlist_clean(const iptree ipt, const int mode) { dlist l; struct dl_node *p; if (mode == IPTREE_M_IPLIST) { l = ipt->ipl; l->first->p->iplp = NULL; } else { l = ipt->bl; l->first->p->blp = NULL; } if (l->first == NULL) return NULL; p = l->first; /* Free only if - we are in the IP list and entry is not blacklisted, - we want to explicitly delete from blacklist and entry is not in IP list */ if ((mode == IPTREE_M_IPLIST && !p->p->blp) || (mode == IPTREE_M_BLACKLIST && !p->p->iplp)) { if (opt.log_level >= 3) syslog(LOG_INFO, "Going to free %p (last=%lu - mode=%d)", (void *)p->p, (unsigned long)p->p->last, mode); iptree_free(ipt, p->p); } l->first = p->next; if (l->first) l->first->prev = NULL; l->count--; p->prev = NULL; p->next = NULL; return p; } /* Return a newly allocated dlist */ static dlist dlist_init(void) { dlist tmp; tmp = xcalloc(sizeof(struct dlist), 1); return tmp; } /* Add an ipinfo record at the end of the wanted dlist (mode is IPTREE_M_IPLIST or M_BLACKLIST) Return 1 if success, 0 if allocation failed */ static int dlist_add(const iptree ipt, struct ipinfo *val, const int mode, const time_t req_time) { dlist l; unsigned int list_size; struct dl_node *p; time_t delta; if (val->blp) { if (opt.log_level >= 3) syslog(LOG_INFO, "%u.%u.%u.%u already in bl", val->iptbl[3], val->iptbl[2], val->iptbl[1], val->iptbl[0]); return 1; } if (mode == IPTREE_M_IPLIST) { l = ipt->ipl; p = val->iplp; list_size = opt.list_size; } else { l = ipt->bl; p = val->blp; list_size = opt.blacklist_size; } if (p == NULL) { /* Add entry to list */ l->count++; /* See if we can get an expirable, already allocated node, otherwise alloc a brand new one */ if (l->count >= list_size + 1) p = dlist_clean(ipt, mode); else if ((p = xcalloc(sizeof(struct dl_node), 1)) == NULL) { syslog(LOG_ERR, "dlist_add failed to allocate %d bytes", sizeof(struct dl_node)); return 0; } if (mode == IPTREE_M_IPLIST) val->iplp = p; else val->blp = p; p->p = val; if (opt.log_level >= 3) syslog(LOG_INFO, "%s list size: %u/%u", mode == IPTREE_M_IPLIST?"IP":"black", l->count, list_size); if (l->first == NULL) { l->first = p; l->last = p; return 1; } } else { /* Already existing entry, see if we have to blacklist */ delta = val->last - val->start; if (mode == IPTREE_M_IPLIST && !val->blp && delta >= opt.interval && val->count * opt.interval / delta >= opt.max_req) { /* Blacklist We will not remove the entry from the IP list and just wait until the entry expires (no more updates once it's blacklisted) */ dlist_add(ipt, val, IPTREE_M_BLACKLIST, req_time); if (opt.log_level >= 1) syslog(LOG_INFO, "%u.%u.%u.%u put in bl: %u reqs / %lu secs", val->iptbl[3], val->iptbl[2], val->iptbl[1], val->iptbl[0], val->count, (unsigned long)delta); } if (l->last == p) return 1; if (l->first == p) l->first = p->next; } /* Do the generic moving stuff */ l->last->next = p; if (p->prev) p->prev->next = p->next; if (p->next) p->next->prev = p->prev; p->next = NULL; p->prev = l->last; l->last = p; l->first->prev = NULL; return 1; } #if 0 /* Call for debugging purposes only: brute force dump */ static void iptree_dump(const iptree ipt) { size_t i, j, k; for (i = 0; i < 256 * 256; i++) if (ipt->ab[i]) for (j = 0; j < 256; j++) if (ipt->ab[i]->c[j]) for (k = 0; k < 256; k++) if (ipt->ab[i]->c[j]->d[k]) syslog(LOG_INFO, "iptree: %u.%u.%u.%u: %u %lu %lu", i%256, i/256, j, k, ipt->ab[i]->c[j]->d[k]->count, ipt->ab[i]->c[j]->d[k]->start, ipt->ab[i]->c[j]->d[k]->last); } #endif /* Read dlist from file */ static void iptree_getfromfile(const iptree ipt, FILE *f) { struct ipinfo ipi; int svlog_level; unsigned int val; svlog_level = opt.log_level; opt.log_level = 0; while (fread(&ipi, sizeof(struct ipinfo), 1, f)) { val = (ipi.iptbl[3]<<24) + (ipi.iptbl[2]<<16) + (ipi.iptbl[1]<<8) + ipi.iptbl[0]; iptree_add(ipt, val, ipi.start, ipi.last, ipi.count, 0); } opt.log_level = svlog_level; } /* Return a newly allocated iptree */ extern iptree iptree_init(void) { iptree tmp; struct stat sb; FILE *f; /* FIXME: should add a test */ tmp = malloc(sizeof(struct iptree)); memset(tmp->ab, 0, 256 * 256); tmp->ipl = dlist_init(); tmp->bl = dlist_init(); if (stat(opt.wl_filename, &sb) == 0 && S_ISREG(sb.st_mode)) { double records = (double)sb.st_size / (double)sizeof(struct ipinfo); int check = sb.st_size / sizeof(struct ipinfo); /* Sanity check */ if ((double)check != records) return tmp; if ((f = fopen(opt.wl_filename, "r")) != NULL) { syslog(LOG_INFO, "loading information from previous dump"); iptree_getfromfile(tmp, f); fclose(f); } } return tmp; } /* Add an IP address to a tree */ extern int iptree_add(const iptree ipt, const unsigned int val, const time_t cr_time, const time_t req_time, const int count, const int options) { union { long l; unsigned char c[sizeof(val)]; } u; unsigned char *iptbl; unsigned long pos; size_t i; u.l = val; iptbl = u.c; pos = get_pos(iptbl[3], iptbl[2]); if (!ipt->ab[pos]) { ipt->ab[pos] = xcalloc(sizeof(struct ipt_node), 1); if (ipt->ab[pos] == NULL) { syslog(LOG_ERR, "iptree_add failed to alloc %d bytes for %u.%u.%u.%u 1", sizeof(struct ipt_node), iptbl[3], iptbl[2], iptbl[1], iptbl[0]); return 0; } } if (!ipt->ab[pos]->c[iptbl[1]]) { ipt->ab[pos]->c[iptbl[1]] = xcalloc(sizeof(struct ipt_leaf), 1); if (ipt->ab[pos]->c[iptbl[1]] == NULL) { syslog(LOG_ERR, "iptree_add failed to alloc %d bytes for %u.%u.%u.%u 2", sizeof(struct ipt_node), iptbl[3], iptbl[2], iptbl[1], iptbl[0]); if (!ipt->ab[pos]->count) { syslog(LOG_ERR, "iptree_add freed block %u.%u.", iptbl[3], iptbl[2]); free(ipt->ab[pos]); ipt->ab[pos] = NULL; } return 0; } } if (!ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]) { ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]] = xcalloc(sizeof(struct ipinfo), 1); if (ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]] == NULL) { syslog(LOG_ERR, "iptree_add failed to alloc %d bytes for %u.%u.%u.%u 3", sizeof(struct ipt_node), iptbl[3], iptbl[2], iptbl[1], iptbl[0]); if (!ipt->ab[pos]->c[iptbl[1]]->count) { syslog(LOG_ERR, "freed block %u.%u.%u", iptbl[3], iptbl[2], iptbl[1]); free(ipt->ab[pos]->c[iptbl[1]]); ipt->ab[pos]->c[iptbl[1]] = NULL; } if (!ipt->ab[pos]->count) { syslog(LOG_ERR, "freed block %u.", iptbl[3]); free(ipt->ab[pos]); ipt->ab[pos] = NULL; } return 0; } ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->start = cr_time; ipt->ab[pos]->c[iptbl[1]]->count++; ipt->ab[pos]->count++; } for (i = 0; i < sizeof(long); i++) ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->iptbl[i] = iptbl[i]; ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->options |= options; /* Decrement only if IP count is greater than zero */ if (count > 0 || ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->count > 0) ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->count += count; ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->last = req_time; if ((options & IPINFO_OPT_FORCED)) { if (dlist_add(ipt, ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]], IPTREE_M_BLACKLIST, req_time) == 0) { syslog(LOG_INFO, "iptree_add cleaning last added bl entry %u.%u.%u.%u (%p)", iptbl[3], iptbl[2], iptbl[1], iptbl[0], (void *)ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]); iptree_free(ipt, ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]); return 0; } else return 1; } if (dlist_add(ipt, ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]], IPTREE_M_IPLIST, req_time) == 0 && ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->count == 1 && ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->blp == NULL) { syslog(LOG_INFO, "iptree_add cleaning last added entry %u.%u.%u.%u (%p)", iptbl[3], iptbl[2], iptbl[1], iptbl[0], (void *)ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]); iptree_free(ipt, ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]); return 0; } return 1; } /* Remove an ipinfo record from the tree and any unused node above */ static void iptree_free(const iptree ipt, struct ipinfo *val) { unsigned long pos; unsigned char *iptbl; /* Shortcut */ iptbl = val->iptbl; pos = get_pos(iptbl[3], iptbl[2]); if (opt.log_level >= 3) syslog(LOG_INFO, "free IP %u.%u.%u.%u (%p)", iptbl[3], iptbl[2], iptbl[1], iptbl[0], (void *)val); free(val); ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]] = NULL; ipt->ab[pos]->c[iptbl[1]]->count--; if (!ipt->ab[pos]->c[iptbl[1]]->count) { free(ipt->ab[pos]->c[iptbl[1]]); ipt->ab[pos]->c[iptbl[1]] = NULL; if (opt.log_level >= 3) syslog(LOG_INFO, "freed block %u.%u.%u.", iptbl[3], iptbl[2], iptbl[1]); } ipt->ab[pos]->count--; if (!ipt->ab[pos]->count) { free(ipt->ab[pos]); ipt->ab[pos] = NULL; if (opt.log_level >= 3) syslog(LOG_INFO, "freed block %u.%u.", iptbl[3], iptbl[2]); } return; } /* Dump all iptree lists to files */ extern void iptree_dump2files(const iptree ipt) { FILE *f; if ((f = fopen(opt.wl_filename, "w+")) == NULL) { syslog(LOG_ERR, "fopen: %s", strerror(errno)); return; } dlist_dump2file(ipt->ipl, f); if (opt.log_level >= 3) syslog(LOG_INFO, "IP list dumped to %s", opt.wl_filename); fclose(f); if ((f = fopen(opt.bl_filename, "w+")) == NULL) { syslog(LOG_ERR, "fopen: %s", strerror(errno)); return; } dlist_dump2file(ipt->bl, f); if (opt.log_level >= 3) syslog(LOG_INFO, "black list dumped to %s", opt.bl_filename); fclose(f); } /* Return 1 if entry is blacklisted, 0 otherwise */ extern int iptree_get_bl(const iptree ipt, const unsigned long val, const time_t req_time) { unsigned char *iptbl; unsigned long pos; time_t t, delta; iptbl = (unsigned char *)&val; pos = get_pos(iptbl[3], iptbl[2]); if (ipt->ab[pos] && ipt->ab[pos]->c[iptbl[1]] && ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]] && ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->blp) { t = req_time - ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->last; if (t <= opt.blacklist_expiration) { delta = ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->last - ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->start; if (ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->options & IPINFO_OPT_FORCED || (delta >= opt.interval && (ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->count * opt.interval / delta >= opt.max_req))) { if (opt.log_level >= 3) syslog(LOG_INFO, "request for a blacklisted IP: %u.%u.%u.%u", iptbl[3], iptbl[2], iptbl[1], iptbl[0]); return 1; } } else { /* Redemption */ ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->count = 0; ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->options = 0; ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->start = req_time; ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->last = req_time; /* if entry is still in IP list, update it */ if (ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]->iplp) { if (opt.log_level >= 3) syslog(LOG_INFO, "re-adding %u.%u.%u.%u (%p)", iptbl[3], iptbl[2], iptbl[1], iptbl[0], (void *)ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]]); dlist_add(ipt, ipt->ab[pos]->c[iptbl[1]]->d[iptbl[0]], IPTREE_M_IPLIST, req_time); } } } return 0; } /* Show some stats via syslog */ extern void iptree_show_stats(const iptree ipt, const time_t t) { if (ipt->ipl->first == NULL) syslog(LOG_INFO, "IP list: %u/%u", ipt->ipl->count, opt.list_size); else syslog(LOG_INFO, "IP list: %u/%u (oldest: %lu secs)", ipt->ipl->count, opt.list_size, (unsigned long)t - ipt->ipl->first->p->last); if (ipt->bl->first == NULL) syslog(LOG_INFO, "Blacklist: %u/%u", ipt->bl->count, opt.blacklist_size); else syslog(LOG_INFO, "Blacklist: %u/%u (oldest: %lu secs)", ipt->bl->count, opt.blacklist_size, (unsigned long)t - ipt->bl->first->p->last); }