/*-
* 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 <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#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);
}
syntax highlighted by Code2HTML, v. 0.9.1