/* $Id: hash_drv.c,v 1.296 2011/06/28 00:13:48 sbajic Exp $ */
/*
DSPAM
COPYRIGHT (C) 2002-2012 DSPAM PROJECT
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
/*
* hash_drv.c - hash-based storage driver
* mmap'd flat-file storage for fast storage
* inspired by crm114 sparse spectra algorithm
*
* DESCRIPTION
* This driver uses a random access file for storage. It is exceptionally fast
* and does not require any third-party dependencies. The auto-extend
* functionality allows the file to grow as needed.
*/
#define READ_ATTRIB(A) _ds_read_attribute(CTX->config->attributes, A)
#define MATCH_ATTRIB(A, B) _ds_match_attribute(CTX->config->attributes, A, B)
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef TIME_WITH_SYS_TIME
# include
# include
#else
# ifdef HAVE_SYS_TIME_H
# include
# else
# include
# endif
#endif
#include "storage_driver.h"
#include "config_shared.h"
#include "hash_drv.h"
#include "libdspam.h"
#include "config.h"
#include "error.h"
#include "language.h"
#include "util.h"
int
dspam_init_driver (DRIVER_CTX *DTX)
{
DSPAM_CTX *CTX;
char *HashConcurrentUser;
#ifdef DAEMON
unsigned long connection_cache = 1;
#endif
if (DTX == NULL)
return 0;
CTX = DTX->CTX;
HashConcurrentUser = READ_ATTRIB("HashConcurrentUser");
#ifdef DAEMON
/*
* Stateful concurrent hash databases are preloaded into memory and
* shared using a reader-writer lock. At the present moment, only a single
* user can be loaded into any instance of the daemon, so it is only useful
* if you are running with a system-wide filtering user.
*/
if (DTX->flags & DRF_STATEFUL) {
char filename[MAX_FILENAME_LENGTH];
hash_drv_map_t map;
unsigned long hash_rec_max = HASH_REC_MAX;
unsigned long max_seek = HASH_SEEK_MAX;
unsigned long max_extents = 0;
unsigned long extent_size = HASH_EXTENT_MAX;
int pctincrease = 0;
int flags = HMAP_AUTOEXTEND;
int ret;
unsigned long i;
if (READ_ATTRIB("HashConnectionCache") && !HashConcurrentUser)
connection_cache = strtol(READ_ATTRIB("HashConnectionCache"), NULL, 0);
DTX->connection_cache = connection_cache;
if (READ_ATTRIB("HashRecMax"))
hash_rec_max = strtol(READ_ATTRIB("HashRecMax"), NULL, 0);
if (READ_ATTRIB("HashExtentSize"))
extent_size = strtol(READ_ATTRIB("HashExtentSize"), NULL, 0);
if (READ_ATTRIB("HashMaxExtents"))
max_extents = strtol(READ_ATTRIB("HashMaxExtents"), NULL, 0);
if (!MATCH_ATTRIB("HashAutoExtend", "on"))
flags = 0;
if (READ_ATTRIB("HashPctIncrease")) {
pctincrease = atoi(READ_ATTRIB("HashPctIncrease"));
if (pctincrease > 100) {
LOG(LOG_ERR, "HashPctIncrease out of range; ignoring");
pctincrease = 0;
}
}
if (READ_ATTRIB("HashMaxSeek"))
max_seek = strtol(READ_ATTRIB("HashMaxSeek"), NULL, 0);
/* Connection array (just one single connection for hash_drv) */
DTX->connections = calloc(1, sizeof(struct _ds_drv_connection *) * connection_cache);
if (DTX->connections == NULL)
goto memerr;
/* Initialize Connections */
for(i=0;iconnections[i] = calloc(1, sizeof(struct _ds_drv_connection));
if (DTX->connections[i] == NULL)
goto memerr;
/* Our connection's storage structure */
if (HashConcurrentUser) {
DTX->connections[i]->dbh = calloc(1, sizeof(struct _hash_drv_map));
if (DTX->connections[i]->dbh == NULL)
goto memerr;
pthread_rwlock_init(&DTX->connections[i]->rwlock, NULL);
} else {
DTX->connections[i]->dbh = NULL;
pthread_mutex_init(&DTX->connections[i]->lock, NULL);
}
}
/* Load concurrent database into resident memory */
if (HashConcurrentUser) {
map = (hash_drv_map_t) DTX->connections[0]->dbh;
/* Tell the server our connection lock will be reader/writer based */
if (!(DTX->flags & DRF_RWLOCK))
DTX->flags |= DRF_RWLOCK;
_ds_userdir_path(filename, DTX->CTX->home, HashConcurrentUser, "css");
_ds_prepare_path_for(filename);
LOGDEBUG("preloading %s into memory via mmap()", filename);
ret = _hash_drv_open(filename, map, hash_rec_max,
max_seek, max_extents, extent_size, pctincrease, flags);
if (ret) {
LOG(LOG_CRIT, "_hash_drv_open(%s) failed on error %d: %s",
filename, ret, strerror(errno));
free(DTX->connections[0]->dbh);
free(DTX->connections[0]);
free(DTX->connections);
return EFAILURE;
}
}
}
#endif
return 0;
#ifdef DAEMON
memerr:
if (DTX) {
if (DTX->connections) {
unsigned long i;
for(i=0;iconnections[i])
free(DTX->connections[i]->dbh);
free(DTX->connections[i]);
}
}
free(DTX->connections);
}
LOG(LOG_CRIT, ERR_MEM_ALLOC);
return EUNKNOWN;
#endif
}
int
dspam_shutdown_driver (DRIVER_CTX *DTX)
{
#ifdef DAEMON
DSPAM_CTX *CTX;
if (DTX && DTX->CTX) {
char *HashConcurrentUser;
CTX = DTX->CTX;
HashConcurrentUser = READ_ATTRIB("HashConcurrentUser");
if (DTX->flags & DRF_STATEFUL) {
int connection_cache = 1;
if (READ_ATTRIB("HashConnectionCache") && !HashConcurrentUser)
connection_cache = strtol(READ_ATTRIB("HashConnectionCache"), NULL, 0);
LOGDEBUG("unloading hash database from memory");
if (DTX->connections) {
int i;
for(i=0;iconnections[i]) {
if (!HashConcurrentUser) {
pthread_mutex_destroy(&DTX->connections[i]->lock);
}
else {
pthread_rwlock_destroy(&DTX->connections[i]->rwlock);
hash_drv_map_t map = (hash_drv_map_t) DTX->connections[i]->dbh;
if (map)
_hash_drv_close(map);
}
free(DTX->connections[i]->dbh);
free(DTX->connections[i]);
}
}
free(DTX->connections);
}
}
}
#endif
return 0;
}
int
_hash_drv_lock_get (
DSPAM_CTX *CTX,
struct _hash_drv_storage *s,
const char *username)
{
char filename[MAX_FILENAME_LENGTH];
int r;
_ds_userdir_path(filename, CTX->home, username, "lock");
_ds_prepare_path_for(filename);
s->lock = fopen(filename, "a");
if (s->lock == NULL) {
LOG(LOG_ERR, ERR_IO_FILE_WRITE, filename, strerror(errno));
return EFAILURE;
}
r = _ds_get_fcntl_lock(fileno(s->lock));
if (r) {
fclose(s->lock);
LOG(LOG_ERR, ERR_IO_LOCK, filename, r, strerror(errno));
}
return r;
}
int
_hash_drv_lock_free (
struct _hash_drv_storage *s,
const char *username)
{
int r;
if (username == NULL)
return 0;
r = _ds_free_fcntl_lock(fileno(s->lock));
if (!r) {
fclose(s->lock);
} else {
LOG(LOG_ERR, ERR_IO_LOCK_FREE, username, r, strerror(errno));
}
return r;
}
FILE*
_hash_tools_lock_get (const char *cssfilename)
{
char filename[MAX_FILENAME_LENGTH];
char *pPeriod;
int r;
FILE* lockfile = NULL;
if (cssfilename == NULL)
return NULL;
pPeriod = strrchr(cssfilename, '.');
if (pPeriod == NULL || strcmp(pPeriod + 1, "css") || (size_t)(pPeriod - cssfilename + 5) >= sizeof(filename))
return NULL;
strncpy(filename, cssfilename, pPeriod - cssfilename + 1);
strcpy(filename + (pPeriod - cssfilename + 1), "lock");
_ds_prepare_path_for(filename);
lockfile = fopen(filename, "a");
if (lockfile == NULL) {
LOG(LOG_ERR, ERR_IO_FILE_OPEN, filename, strerror(errno));
return NULL;
}
r = _ds_get_fcntl_lock(fileno(lockfile));
if (r) {
fclose(lockfile);
lockfile = NULL;
LOG(LOG_ERR, ERR_IO_LOCK, filename, r, strerror(errno));
}
return lockfile;
}
int
_hash_tools_lock_free (
const char *cssfilename,
FILE* lockfile)
{
int r;
if (cssfilename == NULL || lockfile == NULL)
return 0;
r = _ds_free_fcntl_lock(fileno(lockfile));
if (!r) {
fclose(lockfile);
} else {
LOG(LOG_ERR, ERR_IO_LOCK_FREE, cssfilename, r, strerror(errno));
}
return r;
}
int _hash_drv_open(
const char *filename,
hash_drv_map_t map,
unsigned long recmaxifnew,
unsigned long max_seek,
unsigned long max_extents,
unsigned long extent_size,
int pctincrease,
int flags)
{
struct _hash_drv_header header;
int open_flags = O_RDWR;
int mmap_flags = PROT_READ + PROT_WRITE;
FILE *f;
map->fd = open(filename, open_flags);
/*
* Create a new hash database if desired. The record count written in the
* first segment will be recmaxifnew. Once the file is created, it's then
* mmap()'d into memory as usual.
*/
if (map->fd < 0 && recmaxifnew) {
struct _hash_drv_spam_record rec;
unsigned long i;
memset(&header, 0, sizeof(struct _hash_drv_header));
memset(&rec, 0, sizeof(struct _hash_drv_spam_record));
header.hash_rec_max = recmaxifnew;
f = fopen(filename, "w");
if (!f) {
LOG(LOG_ERR, ERR_IO_FILE_WRITE, filename, strerror(errno));
return EFILE;
}
if(fwrite(&header, sizeof(struct _hash_drv_header), 1, f)!=1)
goto WRITE_ERROR;
for(i=0;ifd = open(filename, open_flags);
}
if (map->fd < 0) {
LOG(LOG_ERR, ERR_IO_FILE_WRITE, filename, strerror(errno));
return EFILE;
}
map->header = malloc(sizeof(struct _hash_drv_header));
if (map->header == NULL) {
LOG(LOG_CRIT, ERR_MEM_ALLOC);
close(map->fd);
map->addr = 0;
return EFAILURE;
}
if (read(map->fd, map->header, sizeof(struct _hash_drv_header))
!= sizeof(struct _hash_drv_header)) {
free(map->header);
close(map->fd);
return EFAILURE;
}
map->file_len = lseek(map->fd, 0, SEEK_END);
map->addr = mmap(NULL, map->file_len, mmap_flags, MAP_SHARED, map->fd, 0);
if (map->addr == MAP_FAILED) {
free(map->header);
close(map->fd);
map->addr = 0;
return EFAILURE;
}
strlcpy(map->filename, filename, MAX_FILENAME_LENGTH);
map->max_seek = max_seek;
map->max_extents = max_extents;
map->extent_size = extent_size;
map->pctincrease = pctincrease;
map->flags = flags;
return 0;
WRITE_ERROR:
fclose(f);
unlink(filename);
LOG(LOG_ERR, ERR_IO_FILE_WRITING, filename, strerror(errno));
return EFILE;
}
int
_hash_drv_close(hash_drv_map_t map) {
struct _hash_drv_header header;
int r;
if (!map->addr)
return EINVAL;
memcpy(&header, map->header, sizeof(struct _hash_drv_header));
r = munmap(map->addr, map->file_len);
if (r) {
LOG(LOG_WARNING, "munmap failed on error %d: %s", r, strerror(errno));
}
lseek (map->fd, 0, SEEK_SET);
r = write (map->fd, &header, sizeof(struct _hash_drv_header));
if (r < 0) {
LOG(LOG_WARNING, "write failed on error %d: %s", r, strerror(errno));
}
close(map->fd);
map->addr = 0;
free(map->header);
return r;
}
int
_ds_init_storage (DSPAM_CTX * CTX, void *dbh)
{
struct _hash_drv_storage *s = NULL;
hash_drv_map_t map = NULL;
if (CTX == NULL)
return EINVAL;
if (!CTX->home) {
LOG(LOG_ERR, ERR_AGENT_DSPAM_HOME);
return EINVAL;
}
if (CTX->flags & DSF_MERGED) {
LOG(LOG_ERR, ERR_DRV_NO_MERGED);
return EINVAL;
}
if (CTX->storage)
return EINVAL;
/* Persistent driver storage */
s = calloc (1, sizeof (struct _hash_drv_storage));
if (s == NULL)
{
LOG(LOG_CRIT, ERR_MEM_ALLOC);
return EUNKNOWN;
}
/* If running in HashConcurrentUser mode, use existing hash mapping */
if (dbh) {
map = dbh;
s->dbh_attached = 1;
} else {
map = calloc(1, sizeof(struct _hash_drv_map));
if (!map) {
LOG(LOG_CRIT, ERR_MEM_ALLOC);
free(s);
return EUNKNOWN;
}
s->dbh_attached = 0;
}
s->map = map;
/* Mapping defaults */
s->hash_rec_max = HASH_REC_MAX;
s->max_seek = HASH_SEEK_MAX;
s->max_extents = 0;
s->extent_size = HASH_EXTENT_MAX;
s->pctincrease = 0;
s->flags = HMAP_AUTOEXTEND;
if (READ_ATTRIB("HashRecMax"))
s->hash_rec_max = strtol(READ_ATTRIB("HashRecMax"), NULL, 0);
if (READ_ATTRIB("HashExtentSize"))
s->extent_size = strtol(READ_ATTRIB("HashExtentSize"), NULL, 0);
if (READ_ATTRIB("HashMaxExtents"))
s->max_extents = strtol(READ_ATTRIB("HashMaxExtents"), NULL, 0);
if (!MATCH_ATTRIB("HashAutoExtend", "on"))
s->flags = 0;
if (READ_ATTRIB("HashPctIncrease")) {
s->pctincrease = atoi(READ_ATTRIB("HashPctIncrease"));
if (s->pctincrease > 100) {
LOG(LOG_ERR, "HashPctIncrease out of range; ignoring");
s->pctincrease = 0;
}
}
if (READ_ATTRIB("HashMaxSeek"))
s->max_seek = strtol(READ_ATTRIB("HashMaxSeek"), NULL, 0);
if (!dbh && CTX->username != NULL)
{
char db[MAX_FILENAME_LENGTH];
int lock_result;
int ret;
if (CTX->group == NULL)
_ds_userdir_path(db, CTX->home, CTX->username, "css");
else
_ds_userdir_path(db, CTX->home, CTX->group, "css");
lock_result = _hash_drv_lock_get (CTX, s,
(CTX->group) ? CTX->group : CTX->username);
if (lock_result < 0)
goto BAIL;
ret = _hash_drv_open(db, s->map, s->hash_rec_max, s->max_seek,
s->max_extents, s->extent_size, s->pctincrease, s->flags);
if (ret) {
_hash_drv_close(s->map);
free(s);
return EFAILURE;
}
}
CTX->storage = s;
s->dir_handles = nt_create (NT_INDEX);
if (_hash_drv_get_spamtotals (CTX))
{
LOGDEBUG ("unable to load totals. using zero values.");
memset (&CTX->totals, 0, sizeof (struct _ds_spam_totals));
}
return 0;
BAIL:
free(s);
return EFAILURE;
}
int
_ds_shutdown_storage (DSPAM_CTX * CTX)
{
struct _hash_drv_storage *s;
struct nt_node *node_nt;
struct nt_c c_nt;
if (!CTX || !CTX->storage)
return EINVAL;
s = (struct _hash_drv_storage *) CTX->storage;
/* Close open file handles to directories (iteration functions) */
node_nt = c_nt_first (s->dir_handles, &c_nt);
while (node_nt != NULL)
{
DIR *dir;
dir = (DIR *) node_nt->ptr;
closedir (dir);
node_nt = c_nt_next (s->dir_handles, &c_nt);
}
nt_destroy (s->dir_handles);
if (CTX->operating_mode != DSM_CLASSIFY)
_hash_drv_set_spamtotals (CTX);
/* Close connection to hash database only if we're not concurrent */
if (!s->dbh_attached) {
_hash_drv_close(s->map);
free(s->map);
int lock_result =
_hash_drv_lock_free (s, (CTX->group) ? CTX->group : CTX->username);
if (lock_result < 0)
return EUNKNOWN;
}
free (CTX->storage);
CTX->storage = NULL;
return 0;
}
int
_hash_drv_get_spamtotals (DSPAM_CTX * CTX)
{
struct _hash_drv_storage *s = (struct _hash_drv_storage *) CTX->storage;
if (s->map->addr == 0)
return EINVAL;
/* Totals are loaded straight from the hash header */
memcpy(&CTX->totals, &s->map->header->totals, sizeof(struct _ds_spam_totals));
return 0;
}
int
_hash_drv_set_spamtotals (DSPAM_CTX * CTX)
{
struct _hash_drv_storage *s = (struct _hash_drv_storage *) CTX->storage;
if (s->map->addr == NULL)
return EINVAL;
/* Totals are stored into the hash header */
memcpy(&s->map->header->totals, &CTX->totals, sizeof(struct _ds_spam_totals));
return 0;
}
int
_ds_getall_spamrecords (DSPAM_CTX * CTX, ds_diction_t diction)
{
ds_term_t ds_term;
ds_cursor_t ds_c;
struct _ds_spam_stat stat;
struct _ds_spam_stat *p_stat = &stat;
int ret = 0, x = 0;
if (diction == NULL || CTX == NULL)
return EINVAL;
ds_c = ds_diction_cursor(diction);
ds_term = ds_diction_next(ds_c);
while(ds_term)
{
ds_term->s.spam_hits = 0;
ds_term->s.innocent_hits = 0;
ds_term->s.offset = 0;
x = _ds_get_spamrecord (CTX, ds_term->key, p_stat);
if (!x)
ds_diction_setstat(diction, ds_term->key, p_stat);
else if (x != EFAILURE)
ret = x;
ds_term = ds_diction_next(ds_c);
}
ds_diction_close(ds_c);
if (ret) {
LOGDEBUG("_ds_getall_spamtotals returning %d", ret);
}
return ret;
}
int
_ds_setall_spamrecords (DSPAM_CTX * CTX, ds_diction_t diction)
{
ds_term_t ds_term;
ds_cursor_t ds_c;
int ret = EUNKNOWN;
if (diction == NULL || CTX == NULL)
return EINVAL;
if (CTX->operating_mode == DSM_CLASSIFY &&
(CTX->training_mode != DST_TOE ||
(diction->whitelist_token == 0 && (!(CTX->flags & DSF_NOISE)))))
{
return 0;
}
ds_c = ds_diction_cursor(diction);
ds_term = ds_diction_next(ds_c);
while(ds_term)
{
if (!(ds_term->s.status & TST_DIRTY)) {
ds_term = ds_diction_next(ds_c);
continue;
}
if (CTX->training_mode == DST_TOE &&
CTX->classification == DSR_NONE &&
CTX->operating_mode == DSM_CLASSIFY &&
diction->whitelist_token != ds_term->key &&
(!ds_term->name || strncmp(ds_term->name, "bnr.", 4)))
{
ds_term = ds_diction_next(ds_c);
continue;
}
if (ds_term->s.spam_hits > CTX->totals.spam_learned)
ds_term->s.spam_hits = CTX->totals.spam_learned;
if (ds_term->s.innocent_hits > CTX->totals.innocent_learned)
ds_term->s.innocent_hits = CTX->totals.innocent_learned;
if (!_ds_set_spamrecord (CTX, ds_term->key, &ds_term->s))
ret = 0;
ds_term = ds_diction_next(ds_c);
}
ds_diction_close(ds_c);
return ret;
}
int
_ds_get_spamrecord (
DSPAM_CTX * CTX,
unsigned long long token,
struct _ds_spam_stat *stat)
{
struct _hash_drv_spam_record rec;
struct _hash_drv_storage *s = (struct _hash_drv_storage *) CTX->storage;
rec.spam = rec.nonspam = 0;
rec.hashcode = token;
stat->offset = _hash_drv_get_spamrecord(s->map, &rec);
if (!stat->offset)
return EFAILURE;
stat->probability = 0.00000;
stat->status = 0;
stat->innocent_hits = rec.nonspam & 0x0fffffff;
stat->spam_hits = rec.spam & 0x0fffffff;
return 0;
}
int
_ds_set_spamrecord (
DSPAM_CTX * CTX,
unsigned long long token,
struct _ds_spam_stat *stat)
{
struct _hash_drv_spam_record rec;
struct _hash_drv_storage *s = (struct _hash_drv_storage *) CTX->storage;
rec.hashcode = token;
rec.nonspam = (stat->innocent_hits > 0) ? stat->innocent_hits : 0;
rec.spam = (stat->spam_hits > 0) ? stat->spam_hits : 0;
if(rec.nonspam>0x0fffffff)rec.nonspam=0x0fffffff;
if(rec.spam>0x0fffffff)rec.spam=0x0fffffff;
return _hash_drv_set_spamrecord(s->map, &rec, stat->offset);
}
int
_ds_set_signature (DSPAM_CTX * CTX, struct _ds_spam_signature *SIG,
const char *signature)
{
char filename[MAX_FILENAME_LENGTH];
char scratch[128];
FILE *file;
_ds_userdir_path(filename,
CTX->home,
(CTX->group) ? CTX->group : CTX->username,
"sig");
snprintf(scratch, sizeof(scratch), "/%s.sig", signature);
strlcat(filename, scratch, sizeof(filename));
_ds_prepare_path_for(filename);
file = fopen(filename, "w");
if (!file) {
LOG(LOG_ERR, ERR_IO_FILE_WRITE, filename, strerror(errno));
return EFAILURE;
}
if(fwrite(SIG->data, SIG->length, 1, file)!=1) {
fclose(file);
unlink(filename);
LOG(LOG_ERR, ERR_IO_FILE_WRITING, filename, strerror(errno));
return(EFAILURE);
}
fclose(file);
return 0;
}
int
_ds_get_signature (DSPAM_CTX * CTX, struct _ds_spam_signature *SIG,
const char *signature)
{
char filename[MAX_FILENAME_LENGTH];
char scratch[128];
FILE *file;
struct stat statbuf;
_ds_userdir_path(filename,
CTX->home,
(CTX->group) ? CTX->group : CTX->username,
"sig");
snprintf(scratch, sizeof(scratch), "/%s.sig", signature);
strlcat(filename, scratch, sizeof(filename));
if (stat (filename, &statbuf)) {
LOG(LOG_ERR, ERR_IO_FILE_OPEN, filename, strerror(errno));
return EFAILURE;
};
SIG->data = malloc(statbuf.st_size);
if (!SIG->data) {
LOG(LOG_CRIT, ERR_MEM_ALLOC);
return EUNKNOWN;
}
file = fopen(filename, "r");
if (!file) {
LOG(LOG_ERR, ERR_IO_FILE_OPEN, filename, strerror(errno));
return EFAILURE;
}
if (fread(SIG->data, statbuf.st_size, 1, file) != 1) {
LOG(LOG_ERR, ERR_IO_FILE_READ, filename, strerror(errno));
fclose(file);
return EFAILURE;
}
SIG->length = statbuf.st_size;
fclose(file);
return 0;
}
void *_ds_connect (DSPAM_CTX *CTX)
{
CTX = CTX; /* Keep compiler happy */
return NULL;
}
int
_ds_create_signature_id (DSPAM_CTX * CTX, char *buf, size_t len)
{
char session[64];
char digit[6];
int pid, j;
CTX = CTX; /* Keep compiler happy */
pid = getpid ();
snprintf (session, sizeof (session), "%8lx%d", (long) time (NULL), pid);
for (j = 0; j < 2; j++)
{
snprintf (digit, 6, "%d", rand ());
strlcat (session, digit, 64);
}
strlcpy (buf, session, len);
return 0;
}
int
_ds_verify_signature (DSPAM_CTX * CTX, const char *signature)
{
char filename[MAX_FILENAME_LENGTH];
char scratch[128];
struct stat statbuf;
_ds_userdir_path(filename,
CTX->home,
(CTX->group) ? CTX->group : CTX->username,
"sig");
snprintf(scratch, sizeof(scratch), "/%s.sig", signature);
strlcat(filename, scratch, sizeof(filename));
if (stat (filename, &statbuf))
return 1;
return 0;
}
struct _ds_storage_record *
_ds_get_nexttoken (DSPAM_CTX * CTX)
{
struct _hash_drv_storage *s = (struct _hash_drv_storage *) CTX->storage;
struct _hash_drv_spam_record rec;
struct _ds_storage_record *sr;
struct _ds_spam_stat stat;
rec.hashcode = 0;
sr = calloc(1, sizeof(struct _ds_storage_record));
if (!sr) {
LOG(LOG_CRIT, ERR_MEM_ALLOC);
return NULL;
}
if (s->offset_nexttoken == 0) {
s->offset_header = s->map->addr;
s->offset_nexttoken = sizeof(struct _hash_drv_header);
memcpy(&rec,
(void *)((unsigned long) s->map->addr + s->offset_nexttoken),
sizeof(struct _hash_drv_spam_record));
if (rec.hashcode)
_ds_get_spamrecord (CTX, rec.hashcode, &stat);
}
while(rec.hashcode == 0 ||
((unsigned long) s->map->addr + s->offset_nexttoken ==
(unsigned long) s->offset_header + sizeof(struct _hash_drv_header) +
(s->offset_header->hash_rec_max * sizeof(struct _hash_drv_spam_record))))
{
s->offset_nexttoken += sizeof(struct _hash_drv_spam_record);
if ((unsigned long) s->map->addr + s->offset_nexttoken >
(unsigned long) s->offset_header + sizeof(struct _hash_drv_header) +
(s->offset_header->hash_rec_max * sizeof(struct _hash_drv_spam_record)))
{
if (s->offset_nexttoken < s->map->file_len) {
s->offset_header = (void *)((unsigned long) s->map->addr +
(s->offset_nexttoken - sizeof(struct _hash_drv_spam_record)));
s->offset_nexttoken += sizeof(struct _hash_drv_header);
s->offset_nexttoken -= sizeof(struct _hash_drv_spam_record);
} else {
free(sr);
return NULL;
}
}
memcpy(&rec,
(void *)((unsigned long) s->map->addr + s->offset_nexttoken),
sizeof(struct _hash_drv_spam_record));
_ds_get_spamrecord (CTX, rec.hashcode, &stat);
}
sr->token = rec.hashcode;
sr->spam_hits = stat.spam_hits;
sr->innocent_hits = stat.innocent_hits;
sr->last_hit = time(NULL);
return sr;
}
int
_ds_delete_signature (DSPAM_CTX * CTX, const char *signature)
{
char filename[MAX_FILENAME_LENGTH];
char scratch[128];
_ds_userdir_path(filename,
CTX->home,
(CTX->group) ? CTX->group : CTX->username,
"sig");
snprintf(scratch, sizeof(scratch), "/%s.sig", signature);
strlcat(filename, scratch, sizeof(filename));
return unlink(filename);
}
char *
_ds_get_nextuser (DSPAM_CTX * CTX)
{
static char user[MAX_FILENAME_LENGTH];
static char path[MAX_FILENAME_LENGTH];
struct _hash_drv_storage *s = (struct _hash_drv_storage *) CTX->storage;
struct nt_node *node_nt, *prev;
struct nt_c c_nt;
char *x = NULL, *y;
DIR *dir = NULL;
struct dirent *entry;
if (s->dir_handles->items == 0)
{
char filename[MAX_FILENAME_LENGTH];
snprintf(filename, MAX_FILENAME_LENGTH, "%s/data", CTX->home);
dir = opendir (filename);
if (dir == NULL)
{
LOG (LOG_WARNING,
"unable to open directory '%s' for reading: %s",
CTX->home, strerror (errno));
return NULL;
}
nt_add (s->dir_handles, (void *) dir);
strlcpy (path, filename, sizeof (path));
}
else
{
node_nt = c_nt_first (s->dir_handles, &c_nt);
while (node_nt != NULL)
{
if (node_nt->next == NULL)
dir = (DIR *) node_nt->ptr;
node_nt = c_nt_next (s->dir_handles, &c_nt);
}
}
if (dir != NULL) {
while ((entry = readdir (dir)) != NULL)
{
struct stat st;
char filename[MAX_FILENAME_LENGTH];
snprintf (filename, sizeof (filename), "%s/%s", path, entry->d_name);
if (!strcmp (entry->d_name, ".") || !strcmp (entry->d_name, ".."))
continue;
if (stat (filename, &st)) {
continue;
}
/* push a new directory */
if (st.st_mode & S_IFDIR)
{
DIR *ndir;
ndir = opendir (filename);
if (ndir == NULL)
continue;
strlcat (path, "/", sizeof (path));
strlcat (path, entry->d_name, sizeof (path));
nt_add (s->dir_handles, (void *) ndir);
return _ds_get_nextuser (CTX);
}
else if (strlen(entry->d_name)>4 &&
!strncmp ((entry->d_name + strlen (entry->d_name)) - 4, ".css", 4))
{
strlcpy (user, entry->d_name, sizeof (user));
user[strlen (user) - 4] = 0;
return user;
}
}
}
/* pop current directory */
y = strchr (path, '/');
while (y != NULL)
{
x = y;
y = strchr (x + 1, '/');
}
if (x)
x[0] = 0;
/* pop directory handle from list */
node_nt = c_nt_first (s->dir_handles, &c_nt);
prev = NULL;
while (node_nt != NULL)
{
if (node_nt->next == NULL)
{
dir = (DIR *) node_nt->ptr;
closedir (dir);
if (prev != NULL) {
prev->next = NULL;
s->dir_handles->insert = NULL;
}
else
s->dir_handles->first = NULL;
free (node_nt);
s->dir_handles->items--;
break;
}
prev = node_nt;
node_nt = c_nt_next (s->dir_handles, &c_nt);
}
if (s->dir_handles->items > 0)
return _ds_get_nextuser (CTX);
user[0] = 0;
return NULL;
}
struct _ds_storage_signature *
_ds_get_nextsignature (DSPAM_CTX * CTX)
{
CTX = CTX; /* Keep compiler happy */
return NULL;
}
int
_ds_delall_spamrecords (DSPAM_CTX * CTX, ds_diction_t diction)
{
CTX = CTX; /* Keep compiler happy */
diction = diction; /* Keep compiler happy */
return 0;
}
int _hash_drv_autoextend(
hash_drv_map_t map,
int extents,
unsigned long last_extent_size)
{
struct _hash_drv_header header;
struct _hash_drv_spam_record rec;
int lastsize;
unsigned long i;
_hash_drv_close(map);
map->fd = open(map->filename, O_RDWR);
if (map->fd < 0) {
LOG(LOG_WARNING, "unable to resize hash. open failed: %s", strerror(errno));
return EFAILURE;
}
memset(&header, 0, sizeof(struct _hash_drv_header));
memset(&rec, 0, sizeof(struct _hash_drv_spam_record));
if (extents == 0 || !map->pctincrease)
header.hash_rec_max = map->extent_size;
else
header.hash_rec_max = last_extent_size
+ (last_extent_size * (map->pctincrease/100.0));
LOGDEBUG("adding extent last: %d(%ld) new: %d(%ld) pctincrease: %1.2f", extents, last_extent_size, extents+1, header.hash_rec_max, (map->pctincrease/100.0));
lastsize=lseek (map->fd, 0, SEEK_END);
if(write (map->fd, &header, sizeof(struct _hash_drv_header))!=sizeof(struct _hash_drv_header)) {
if (ftruncate(map->fd, lastsize) < 0) {
LOG(LOG_WARNING, "unable to truncate hash file %s: %s",
map->filename, strerror(errno));
}
close(map->fd);
LOG(LOG_WARNING, "unable to resize hash. open failed: %s", strerror(errno));
return EFAILURE;
}
for(i=0;ifd, &rec, sizeof(struct _hash_drv_spam_record))!=sizeof(struct _hash_drv_spam_record)) {
if (ftruncate(map->fd, lastsize) < 0) {
LOG(LOG_WARNING, "unable to truncate hash file %s: %s",
map->filename, strerror(errno));
}
close(map->fd);
LOG(LOG_WARNING, "unable to resize hash. open failed: %s", strerror(errno));
return EFAILURE;
}
close(map->fd);
_hash_drv_open(map->filename, map, 0, map->max_seek,
map->max_extents, map->extent_size, map->pctincrease, map->flags);
return 0;
}
unsigned long _hash_drv_seek(
hash_drv_map_t map,
unsigned long offset,
unsigned long long hashcode,
int flags)
{
hash_drv_header_t header = (void *)((unsigned long) map->addr + offset);
hash_drv_spam_record_t rec;
unsigned long fpos;
unsigned long iterations = 0;
if (offset >= map->file_len)
return 0;
fpos = sizeof(struct _hash_drv_header) +
((hashcode % header->hash_rec_max) * sizeof(struct _hash_drv_spam_record));
rec = (void *)((unsigned long) map->addr + offset + fpos);
while(rec->hashcode != hashcode && /* Match token */
rec->hashcode != 0 && /* Insert on empty */
iterations < map->max_seek) /* Max Iterations */
{
iterations++;
fpos += sizeof(struct _hash_drv_spam_record);
if (fpos >= (header->hash_rec_max * sizeof(struct _hash_drv_spam_record)))
fpos = sizeof(struct _hash_drv_header);
rec = (void *)((unsigned long) map->addr + offset + fpos);
}
if (rec->hashcode == hashcode)
return fpos;
if (rec->hashcode == 0 && (flags & HSEEK_INSERT))
return fpos;
return 0;
}
int
_hash_drv_set_spamrecord (
hash_drv_map_t map,
hash_drv_spam_record_t wrec,
unsigned long map_offset)
{
hash_drv_spam_record_t rec;
unsigned long offset = 0, extents = 0, last_extent_size = 0, rec_offset = 0;
if (map->addr == NULL)
return EINVAL;
if (map_offset) {
rec = (void *)((unsigned long) map->addr + map_offset);
} else {
while(rec_offset <= 0 && offset < map->file_len)
{
rec_offset = _hash_drv_seek(map, offset, wrec->hashcode, HSEEK_INSERT);
if (rec_offset <= 0) {
hash_drv_header_t header = (void *)((unsigned long) map->addr + offset);
offset += sizeof(struct _hash_drv_header) +
(sizeof(struct _hash_drv_spam_record) * header->hash_rec_max);
last_extent_size = header->hash_rec_max;
extents++;
}
}
if (rec_offset <= 0) {
if (map->flags & HMAP_AUTOEXTEND) {
if (extents > map->max_extents && map->max_extents)
goto FULL;
if (!_hash_drv_autoextend(map, extents-1, last_extent_size))
return _hash_drv_set_spamrecord(map, wrec, map_offset);
else
return EFAILURE;
} else {
goto FULL;
}
}
rec = (void *)((unsigned long) map->addr + offset + rec_offset);
}
rec->hashcode = wrec->hashcode;
rec->nonspam = wrec->nonspam;
rec->spam = wrec->spam;
return 0;
FULL:
LOG(LOG_WARNING, "hash table %s full", map->filename);
return EFAILURE;
}
unsigned long
_hash_drv_get_spamrecord (
hash_drv_map_t map,
hash_drv_spam_record_t wrec)
{
hash_drv_spam_record_t rec;
unsigned long offset = 0, extents = 0, rec_offset = 0;
if (map->addr == NULL)
return 0;
while(rec_offset <= 0 && offset < map->file_len)
{
rec_offset = _hash_drv_seek(map, offset, wrec->hashcode, 0);
if (rec_offset <= 0) {
hash_drv_header_t header = (void *)((unsigned long) map->addr + offset);
offset += sizeof(struct _hash_drv_header) +
(sizeof(struct _hash_drv_spam_record) * header->hash_rec_max);
extents++;
}
}
if (rec_offset <= 0)
return 0;
offset += rec_offset;
rec = (void *)((unsigned long) map->addr + offset);
wrec->nonspam = rec->nonspam;
wrec->spam = rec->spam;
return offset;
}
/* Preference Stubs for Flat-File */
agent_pref_t _ds_pref_load(config_t config, const char *user,
const char *home, void *dbh)
{
return _ds_ff_pref_load(config, user, home, dbh);
}
int _ds_pref_set(config_t config, const char *user, const char *home,
const char *attrib, const char *value, void *dbh)
{
return _ds_ff_pref_set(config, user, home, attrib, value, dbh);
}
int _ds_pref_del(config_t config, const char *user, const char *home,
const char *attrib, void *dbh)
{
return _ds_ff_pref_del(config, user, home, attrib, dbh);
}