/*
* Copyright (c) 2003-2008 Hypertriton, Inc.
* 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 COPYRIGHT HOLDERS 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.
*/
/*
* Lookup table for spam checker statistics per IPv4/IPv6 address.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mailblockd.h"
#define TBL_FIELD_SEP " \t"
/* Initialize a table. */
void
TBL_Init(TBL *t, const char *path, u_int nBuckets)
{
int i;
if ((t->path = strdup(path)) == NULL) {
MBD_Fatal("Out of memory");
}
t->nbuckets = nBuckets;
t->buckets = Malloc(nBuckets*sizeof(TBL_Bucket));
t->count = 0;
for (i = 0; i < nBuckets; i++)
SLIST_INIT(&t->buckets[i].ents);
}
/* Release the resources allocated by a table. */
void
TBL_Destroy(TBL *t)
{
TBL_Entry *ent, *nent;
int i;
for (i = 0; i < t->nbuckets; i++) {
for (ent = SLIST_FIRST(&t->buckets[i].ents);
ent != SLIST_END(&t->buckets[i].ents);
ent = nent) {
nent = SLIST_NEXT(ent, bents);
free(ent->key);
free(ent);
}
}
free(t->buckets);
free(t->path);
free(t);
}
#if 0
int
TBL_Rehash(TBL **t, u_int nbuckets)
{
char path[FILENAME_MAX];
char tempPath[16];
int fd;
Debug("Rehashing: %d -> %d buckets", (*t)->nbuckets, nbuckets);
Strlcpy(path, (*t)->path, sizeof(path));
Strlcpy(tempPath, "/tmp/mbdXXXXXX", sizeof(tempPath));
if ((fd = mkstemp(tempPath)) == -1) {
MBD_SetError("mkstemp: %s", strerror(errno));
return (-1);
}
if (TBL_SaveFD(*t, fd) == -1) {
close(fd);
return (-1);
}
close(fd);
TBL_Destroy(*t);
if ((*t = TBL_Load(tempPath, nbuckets)) == NULL) {
return (-1);
}
unlink(tempPath);
return (0);
}
#endif
/* Insert a new table entry. */
TBL_Entry *
TBL_Insert(TBL *ta, u_int h, const char *key)
{
TBL_Entry *ent;
if (ta->nbuckets == 0) {
MBD_SetError("Zero-sized table");
return (NULL);
}
if ((ent = malloc(sizeof(TBL_Entry))) == NULL) {
goto outofmem;
}
if ((ent->key = strdup(key)) == NULL) {
free(ent);
goto outofmem;
}
SLIST_INSERT_HEAD(&ta->buckets[h].ents, ent, bents);
ta->buckets[h].nents++;
ta->count++;
return (ent);
outofmem:
MBD_SetError("Out of memory");
return (NULL);
}
void
TBL_DeleteEntry(TBL *ta, u_int h, TBL_Entry *ent)
{
SLIST_REMOVE(&ta->buckets[h].ents, ent, tbl_entry, bents);
free(ent->key);
free(ent);
if (ta->count > 0) {
ta->count--;
} else {
syslog(LOG_ERR, "Bogus table count!");
}
}
int
TBL_Delete(TBL *ta, const char *key)
{
TBL_Entry *ent;
u_int h = TBL_Hash(ta,key);
if ((ent = TBL_LookupHash(ta, key, h)) == NULL) {
MBD_SetError("Element not found in table: %s", key);
return (-1);
}
TBL_DeleteEntry(ta, h, ent);
return (0);
}
static int
ParseLine(TBL *ta, char *s)
{
TBL_Entry *nent;
char *key, *p;
u_int h;
int i;
if ((key = strsep(&s, TBL_FIELD_SEP)) == NULL || key[0] == '\0') {
MBD_SetError("Bad key");
return (-1);
}
if ((nent = malloc(sizeof(TBL_Entry))) == NULL) {
goto outofmem;
}
if ((nent->key = strdup(key)) == NULL) {
free(nent);
goto outofmem;
}
/* Read the count */
if ((p = strsep(&s, TBL_FIELD_SEP)) == NULL) {
nent->count = 1;
} else {
if ((nent->count = (unsigned int)strtoul(p, NULL, 10)) < 1)
nent->count = 1;
}
/* Read the average score (redundant but saves us some CPU) */
if ((p = strsep(&s, TBL_FIELD_SEP)) == NULL) {
nent->scoreAvg = 0.0;
} else {
nent->scoreAvg = (unsigned int)strtoul(p, NULL, 10);
}
/* Read the total score */
if ((p = strsep(&s, TBL_FIELD_SEP)) == NULL) {
nent->scoreTot = 0.0;
} else {
nent->scoreTot = (unsigned int)strtoul(p, NULL, 10);
}
/* Read the timestamp */
if ((p = strsep(&s, TBL_FIELD_SEP)) == NULL) {
nent->stamp = 0;
} else {
nent->stamp = strtol(p, NULL, 10);
}
/* Insert into the table */
h = TBL_Hash(ta, nent->key);
SLIST_INSERT_HEAD(&ta->buckets[h].ents, nent, bents);
ta->buckets[h].nents++;
ta->count++;
return (0);
fail:
free(nent->key);
free(nent);
return (-1);
outofmem:
MBD_SetError("Out of memory");
return (-1);
}
/*
* Load a table from the given file. Leading spacing characters and characters
* between '#' and end of line are ignored.
*/
TBL *
TBL_Load(const char *path, u_int nBuckets)
{
TBL *ta;
char *data, *p, *eol;
size_t len, nRead;
ssize_t rv;
u_int nLines;
int fd;
if ((fd = open(path, O_RDONLY)) == -1) {
MBD_SetError("%s: %s", path, strerror(errno));
return (NULL);
}
len = (size_t)lseek(fd, 0, SEEK_END);
(void)lseek(fd, 0, SEEK_SET);
if (len == 0) {
ta = Malloc(sizeof(TBL));
TBL_Init(ta, path, (nBuckets == -1) ? 0 : nBuckets);
close(fd);
return (ta);
}
if ((data = malloc(len+1)) == NULL) {
MBD_SetError("Out of memory for data");
close(fd);
return (NULL);
}
for (nRead = 0; nRead < len; ) {
rv = read(fd, &data[nRead], len-nRead);
if (rv == -1) {
if (errno == EINTR) { continue; }
goto fail_read;
} else if (rv == 0) {
goto fail_read;
}
nRead += rv;
}
close(fd);
data[len] = '\0';
ta = Malloc(sizeof(TBL));
if (nBuckets == -1) { /* Auto-size */
nLines = 0;
for (p = &data[0]; *p != '\0'; p++) {
if (*p == '\n')
nLines++;
}
TBL_Init(ta, path, nLines);
} else {
TBL_Init(ta, path, nBuckets);
}
nLines = 0;
for (p = &data[0]; *p != '\0'; p++) {
while (isspace(*p)) {
p++;
}
if (*p == '#') {
while (*(++p) != '\n')
;;
}
for (eol = p; *eol != '\n' && *eol != '\0'; eol++) {
if (*eol == '#') {
for (eol--; isspace(*eol); eol--)
;;
eol++;
break;
}
}
*eol = '\0';
if (p < eol) {
char *sp;
for (sp = &eol[-1]; isspace(*sp); sp--)
;;
sp[1] = '\0';
if (ParseLine(ta, p) == -1) {
Debug("%s:%u: %s", path, nLines,
MBD_GetError());
syslog(LOG_ERR, "%s:%u: %s", path, nLines,
MBD_GetError());
}
}
p = eol;
nLines++;
}
free(data);
return (ta);
fail_read:
free(data);
close(fd);
return (NULL);
}
/* Save the contents of the given table to the named file. */
int
TBL_Save(TBL *t, const char *path, mode_t mode)
{
TBL_Entry *ent;
int fd, rv;
if ((fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0600)) == -1) {
MBD_SetError("%s: %s", path, strerror(errno));
return (-1);
}
rv = TBL_SaveFD(t, fd);
close(fd);
return (rv);
}
/* Save the contents of the given table to an open fd. */
int
TBL_SaveFD(TBL *ta, int fd)
{
char buf[80];
TBL_Entry *ent;
size_t nWrote, len;
ssize_t rv;
u_int i;
for (i = 0; i < ta->nbuckets; i++) {
SLIST_FOREACH(ent, &ta->buckets[i].ents, bents) {
snprintf(buf, sizeof(buf), "%s %ld %f %f %ld\n",
ent->key, ent->count, ent->scoreAvg, ent->scoreTot,
ent->stamp);
len = strlen(buf);
for (nWrote = 0; nWrote < len; ) {
rv = write(fd, &buf[nWrote], len-nWrote);
if (rv == -1) {
if (errno == EINTR) { continue; }
else { goto fail_write; }
} else if (rv == 0) {
goto fail_write;
}
nWrote += rv;
}
}
}
return (0);
fail_write:
MBD_SetError("Write error");
return (-1);
}