/* $Id: sqlite3_drv.c,v 1.187 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 . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #ifndef _WIN32 # include # include #endif #ifdef HAVE_UNISTD_H # include #endif #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 "sqlite3_drv.h" #include "libdspam.h" #include "config.h" #include "error.h" #include "language.h" #include "util.h" #include "config_shared.h" #ifdef _WIN32 # include # include "dir_win32.h" #endif int dspam_init_driver (DRIVER_CTX *DTX) { return 0; } int dspam_shutdown_driver (DRIVER_CTX *DTX) { return 0; } int _sqlite_drv_get_spamtotals (DSPAM_CTX * CTX) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; char query[1024]; char *err=NULL, **row; int nrow, ncolumn; int rc; if (s->dbh == NULL) { LOGDEBUG ("_sqlite_drv_get_spamtotals: invalid database handle (NULL)"); return EINVAL; } memset(&s->control_totals, 0, sizeof(struct _ds_spam_totals)); memset(&CTX->totals, 0, sizeof(struct _ds_spam_totals)); snprintf (query, sizeof (query), "SELECT spam_learned,innocent_learned," "spam_misclassified,innocent_misclassified," "spam_corpusfed,innocent_corpusfed," "spam_classified,innocent_classified" " FROM dspam_stats"); if ((sqlite3_get_table(s->dbh, query, &row, &nrow, &ncolumn, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, query); return EFAILURE; } if (nrow>0 && row != NULL) { CTX->totals.spam_learned = strtoul (row[ncolumn], NULL, 0); if (CTX->totals.spam_learned == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_sqlite_drv_get_spamtotals: failed converting %s to CTX->totals.spam_learned", row[ncolumn]); rc = EFAILURE; goto FAIL; } CTX->totals.innocent_learned = strtoul (row[ncolumn+1], NULL, 0); if (CTX->totals.innocent_learned == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_sqlite_drv_get_spamtotals: failed converting %s to CTX->totals.innocent_learned", row[ncolumn+1]); rc = EFAILURE; goto FAIL; } CTX->totals.spam_misclassified = strtoul (row[ncolumn+2], NULL, 0); if (CTX->totals.spam_misclassified == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_sqlite_drv_get_spamtotals: failed converting %s to CTX->totals.spam_misclassified", row[ncolumn+2]); rc = EFAILURE; goto FAIL; } CTX->totals.innocent_misclassified = strtoul (row[ncolumn+3], NULL, 0); if (CTX->totals.innocent_misclassified == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_sqlite_drv_get_spamtotals: failed converting %s to CTX->totals.innocent_misclassified", row[ncolumn+3]); rc = EFAILURE; goto FAIL; } CTX->totals.spam_corpusfed = strtoul (row[ncolumn+4], NULL, 0); if (CTX->totals.spam_corpusfed == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_sqlite_drv_get_spamtotals: failed converting %s to CTX->totals.spam_corpusfed", row[ncolumn+4]); rc = EFAILURE; goto FAIL; } CTX->totals.innocent_corpusfed = strtoul (row[ncolumn+5], NULL, 0); if (CTX->totals.innocent_corpusfed == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_sqlite_drv_get_spamtotals: failed converting %s to CTX->totals.innocent_corpusfed", row[ncolumn+5]); rc = EFAILURE; goto FAIL; } if (row[ncolumn+6] != NULL && row[ncolumn+7] != NULL) { CTX->totals.spam_classified = strtoul (row[ncolumn+6], NULL, 0); if (CTX->totals.spam_classified == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_sqlite_drv_get_spamtotals: failed converting %s to CTX->totals.spam_classified", row[ncolumn+6]); rc = EFAILURE; goto FAIL; } CTX->totals.innocent_classified = strtoul (row[ncolumn+7], NULL, 0); if (CTX->totals.innocent_classified == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_sqlite_drv_get_spamtotals: failed converting %s to CTX->totals.innocent_classified", row[ncolumn+7]); rc = EFAILURE; goto FAIL; } } else { CTX->totals.spam_classified = 0; CTX->totals.innocent_classified = 0; } rc = 0; } else { rc = EFAILURE; } FAIL: sqlite3_free_table(row); if ( !rc ) memcpy(&s->control_totals, &CTX->totals, sizeof(struct _ds_spam_totals)); return rc; } int _sqlite_drv_set_spamtotals (DSPAM_CTX * CTX) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; char query[1024]; char *err=NULL; int result = SQLITE_OK; if (s->dbh == NULL) { LOGDEBUG ("_sqlite_drv_set_spamtotals: invalid database handle (NULL)"); return EINVAL; } if (CTX->operating_mode == DSM_CLASSIFY) { _sqlite_drv_get_spamtotals (CTX); /* undo changes to in memory totals */ return 0; } /* dspam_stat_id insures only one stats record */ if (s->control_totals.innocent_learned == 0) { snprintf (query, sizeof (query), "INSERT INTO dspam_stats (dspam_stat_id,spam_learned," "innocent_learned,spam_misclassified,innocent_misclassified," "spam_corpusfed,innocent_corpusfed," "spam_classified,innocent_classified)" " VALUES (%d,%lu,%lu,%lu,%lu,%lu,%lu,%lu,%lu)", 0, CTX->totals.spam_learned, CTX->totals.innocent_learned, CTX->totals.spam_misclassified, CTX->totals.innocent_misclassified, CTX->totals.spam_corpusfed, CTX->totals.innocent_corpusfed, CTX->totals.spam_classified, CTX->totals.innocent_classified); result = sqlite3_exec(s->dbh, query, NULL, NULL, NULL); } if (s->control_totals.innocent_learned != 0 || result != SQLITE_OK) { snprintf (query, sizeof (query), "UPDATE dspam_stats SET spam_learned=spam_learned%s%d," "innocent_learned=innocent_learned%s%d," "spam_misclassified=spam_misclassified%s%d," "innocent_misclassified=innocent_misclassified%s%d," "spam_corpusfed=spam_corpusfed%s%d," "innocent_corpusfed=innocent_corpusfed%s%d," "spam_classified=spam_classified%s%d," "innocent_classified=innocent_classified%s%d", (CTX->totals.spam_learned > s->control_totals.spam_learned) ? "+" : "-", abs (CTX->totals.spam_learned - s->control_totals.spam_learned), (CTX->totals.innocent_learned > s->control_totals.innocent_learned) ? "+" : "-", abs (CTX->totals.innocent_learned - s->control_totals.innocent_learned), (CTX->totals.spam_misclassified > s->control_totals.spam_misclassified) ? "+" : "-", abs (CTX->totals.spam_misclassified - s->control_totals.spam_misclassified), (CTX->totals.innocent_misclassified > s->control_totals.innocent_misclassified) ? "+" : "-", abs (CTX->totals.innocent_misclassified - s->control_totals.innocent_misclassified), (CTX->totals.spam_corpusfed > s->control_totals.spam_corpusfed) ? "+" : "-", abs (CTX->totals.spam_corpusfed - s->control_totals.spam_corpusfed), (CTX->totals.innocent_corpusfed > s->control_totals.innocent_corpusfed) ? "+" : "-", abs (CTX->totals.innocent_corpusfed - s->control_totals.innocent_corpusfed), (CTX->totals.spam_classified > s->control_totals.spam_classified) ? "+" : "-", abs (CTX->totals.spam_classified - s->control_totals.spam_classified), (CTX->totals.innocent_classified > s->control_totals.innocent_classified) ? "+" : "-", abs (CTX->totals.innocent_classified - s->control_totals.innocent_classified)); if ((sqlite3_exec(s->dbh, query, NULL, NULL, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, query); return EFAILURE; } } return 0; } int _ds_getall_spamrecords (DSPAM_CTX * CTX, ds_diction_t diction) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; buffer *query; ds_term_t ds_term; ds_cursor_t ds_c; char scratch[1024]; char queryhead[1024]; struct _ds_spam_stat stat; unsigned long long token = 0; char *err=NULL, **row=NULL; int nrow, ncolumn, i; if (diction->items < 1) return 0; if (s->dbh == NULL) { LOGDEBUG ("_ds_getall_spamrecords: invalid database handle (NULL)"); return EINVAL; } stat.spam_hits = 0; stat.innocent_hits = 0; stat.probability = 0.00000; query = buffer_create (NULL); if (query == NULL) { LOG (LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } snprintf (queryhead, sizeof (queryhead), "SELECT token,spam_hits,innocent_hits" " FROM dspam_token_data WHERE token IN ("); ds_c = ds_diction_cursor(diction); ds_term = ds_diction_next(ds_c); while (ds_term) { scratch[0] = 0; buffer_copy(query, queryhead); while (ds_term) { snprintf (scratch, sizeof (scratch), "'%" LLU_FMT_SPEC "'", ds_term->key); buffer_cat (query, scratch); ds_term->s.innocent_hits = 0; ds_term->s.spam_hits = 0; ds_term->s.probability = 0.00000; ds_term->s.status = 0; if((query->used + 1024) > 1000000) { LOGDEBUG("_ds_getall_spamrecords: Splitting query at %ld characters", query->used); break; } ds_term = ds_diction_next(ds_c); if (ds_term) buffer_cat (query, ","); } buffer_cat (query, ")"); #ifdef VERBOSE LOGDEBUG ("SQLite query length: %ld\n", query->used); _sqlite_drv_query_error (strdup("VERBOSE DEBUG (INFO ONLY - NOT AN ERROR)"), query->data); #endif if ((sqlite3_get_table(s->dbh, query->data, &row, &nrow, &ncolumn, &err)) !=SQLITE_OK) { _sqlite_drv_query_error (err, query->data); LOGDEBUG ("_ds_getall_spamrecords: unable to run query: %s", query->data); buffer_destroy(query); ds_diction_close(ds_c); return EFAILURE; } if (nrow < 1) { sqlite3_free_table(row); buffer_destroy(query); ds_diction_close(ds_c); return 0; } if (row == NULL) { buffer_destroy(query); ds_diction_close(ds_c); return 0; } for(i=1;i<=nrow;i++) { token = strtoull (row[(i*ncolumn)], NULL, 0); stat.spam_hits = strtoul (row[1+(i*ncolumn)], NULL, 0); if (stat.spam_hits == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_ds_getall_spamrecords: failed converting %s to stat.spam_hits", row[1+(i*ncolumn)]); sqlite3_free_table(row); return EFAILURE; } stat.innocent_hits = strtoul (row[2+(i*ncolumn)], NULL, 0); if (stat.innocent_hits == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_ds_getall_spamrecords: failed converting %s to stat.innocent_hits", row[2+(i*ncolumn)]); sqlite3_free_table(row); return EFAILURE; } stat.status = 0; stat.status |= TST_DISK; if (stat.spam_hits < 0) stat.spam_hits = 0; if (stat.innocent_hits < 0) stat.innocent_hits = 0; ds_diction_addstat(diction, token, &stat); } if (row != NULL) sqlite3_free_table(row); row = NULL; ds_term = ds_diction_next(ds_c); } ds_diction_close(ds_c); buffer_destroy (query); if (row != NULL) sqlite3_free_table(row); row = NULL; /* Control token */ stat.spam_hits = 10; stat.innocent_hits = 10; stat.status = 0; ds_diction_touch(diction, CONTROL_TOKEN, "$$CONTROL$$", 0); ds_diction_addstat(diction, CONTROL_TOKEN, &stat); s->control_token = CONTROL_TOKEN; s->control_ih = 10; s->control_sh = 10; return 0; } int _ds_setall_spamrecords (DSPAM_CTX * CTX, ds_diction_t diction) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; struct _ds_spam_stat control, stat; ds_term_t ds_term; ds_cursor_t ds_c; char queryhead[1024]; buffer *query; char scratch[1024]; char *err=NULL; int update_any = 0; if (diction->items < 1) return 0; if (s->dbh == NULL) { LOGDEBUG ("_ds_setall_spamrecords: invalid database handle (NULL)"); return EINVAL; } if (CTX->operating_mode == DSM_CLASSIFY && (CTX->training_mode != DST_TOE || (diction->whitelist_token == 0 && (!(CTX->flags & DSF_NOISE))))) return 0; query = buffer_create (NULL); if (query == NULL) { LOG (LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } ds_diction_getstat(diction, s->control_token, &control); snprintf (queryhead, sizeof (queryhead), "UPDATE dspam_token_data SET last_hit=date('now')," "spam_hits=max(0,spam_hits%s%d)," "innocent_hits=max(0,innocent_hits%s%d)" " WHERE token IN (", (control.spam_hits > s->control_sh) ? "+" : "-", abs (control.spam_hits - s->control_sh), (control.innocent_hits > s->control_ih) ? "+" : "-", abs (control.innocent_hits - s->control_ih)); buffer_copy (query, queryhead); /* * Add each token in the diction to either an update or an insert queue */ ds_c = ds_diction_cursor(diction); ds_term = ds_diction_next(ds_c); while(ds_term) { int use_comma = 0; if (ds_term->key == s->control_token) { ds_term = ds_diction_next(ds_c); continue; } /* Don't write lexical tokens if we're in TOE mode classifying */ if (CTX->training_mode == DST_TOE && CTX->operating_mode == DSM_CLASSIFY && ds_term->key != diction->whitelist_token && (!ds_term->name || strncmp(ds_term->name, "bnr.", 4))) { ds_term = ds_diction_next(ds_c); continue; } ds_diction_getstat(diction, ds_term->key, &stat); /* Changed tokens are marked as "dirty" by libdspam */ if (!(stat.status & TST_DIRTY)) { ds_term = ds_diction_next(ds_c); continue; } else { stat.status &= ~TST_DIRTY; } /* This token wasn't originally loaded from disk, so try an insert */ if (!(stat.status & TST_DISK)) { char ins[1024]; snprintf(ins, sizeof (ins), "INSERT INTO dspam_token_data (token,spam_hits," "innocent_hits,last_hit) VALUES ('%" LLU_FMT_SPEC "',%d,%d," "date('now'))", ds_term->key, stat.spam_hits > 0 ? 1 : 0, stat.innocent_hits > 0 ? 1 : 0); if ((sqlite3_exec(s->dbh, ins, NULL, NULL, NULL)) != SQLITE_OK) stat.status |= TST_DISK; } if (stat.status & TST_DISK) { snprintf (scratch, sizeof (scratch), "'%" LLU_FMT_SPEC "'", ds_term->key); buffer_cat (query, scratch); update_any = 1; use_comma = 1; } ds_term->s.status |= TST_DISK; ds_term = ds_diction_next(ds_c); if((query->used + 1024) > 1000000) { LOGDEBUG("_ds_setall_spamrecords: Splitting update query at %ld characters", query->used); buffer_cat (query, ")"); if (update_any) { if ((sqlite3_exec(s->dbh, query->data, NULL, NULL, &err)) != SQLITE_OK) { _sqlite_drv_query_error (err, query->data); LOGDEBUG ("_ds_setall_spamrecords: unable to run query: %s", query->data); ds_diction_close(ds_c); buffer_destroy(query); return EFAILURE; } } buffer_copy (query, queryhead); } else if (ds_term && use_comma) buffer_cat (query, ","); } ds_diction_close(ds_c); /* Just incase */ if (query->used && query->data[strlen (query->data) - 1] == ',') { query->used--; query->data[strlen (query->data) - 1] = 0; } buffer_cat (query, ")"); LOGDEBUG("Control: [%ld %ld] [%lu %lu] Delta: [%lu %lu]", s->control_sh, s->control_ih, control.spam_hits, control.innocent_hits, control.spam_hits - s->control_sh, control.innocent_hits - s->control_ih); if (update_any) { if ((sqlite3_exec(s->dbh, query->data, NULL, NULL, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, query->data); LOGDEBUG ("_ds_setall_spamrecords: unable to run query: %s", query->data); buffer_destroy(query); return EFAILURE; } } buffer_destroy (query); return 0; } int _ds_get_spamrecord (DSPAM_CTX * CTX, unsigned long long token, struct _ds_spam_stat *stat) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; char query[1024]; char *err=NULL, **row; int nrow, ncolumn; if (s->dbh == NULL) { LOGDEBUG ("_ds_get_spamrecord: invalid database handle (NULL)"); return EINVAL; } snprintf (query, sizeof (query), "SELECT spam_hits,innocent_hits FROM dspam_token_data" " WHERE token='%" LLU_FMT_SPEC "'", token); stat->probability = 0.00000; stat->spam_hits = 0; stat->innocent_hits = 0; stat->status &= ~TST_DISK; if ((sqlite3_get_table(s->dbh, query, &row, &nrow, &ncolumn, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, query); LOGDEBUG ("_ds_get_spamrecord: unable to run query: %s", query); return EFAILURE; } if (nrow < 1) sqlite3_free_table(row); if (nrow < 1 || row == NULL) return 0; stat->spam_hits = strtoul (row[0], NULL, 0); if (stat->spam_hits == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_ds_get_spamrecord: failed converting %s to stat->spam_hits", row[0]); sqlite3_free_table(row); return EFAILURE; } stat->innocent_hits = strtoul (row[1], NULL, 0); if (stat->innocent_hits == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_ds_get_spamrecord: failed converting %s to stat->innocent_hits", row[1]); sqlite3_free_table(row); return EFAILURE; } stat->status |= TST_DISK; sqlite3_free_table(row); return 0; } int _ds_set_spamrecord (DSPAM_CTX * CTX, unsigned long long token, struct _ds_spam_stat *stat) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; char query[1024]; char *err=NULL; int result = 0; if (s->dbh == NULL) { LOGDEBUG ("_ds_set_spamrecord: invalid database handle (NULL)"); return EINVAL; } if (CTX->operating_mode == DSM_CLASSIFY) return 0; /* It's either not on disk or the caller isn't using stat.disk */ if (!(stat->status & TST_DISK)) { snprintf (query, sizeof (query), "INSERT INTO dspam_token_data (token,spam_hits,innocent_hits,last_hit)" " VALUES ('%" LLU_FMT_SPEC "',%lu,%lu,date('now'))", token, stat->spam_hits > 0 ? stat->spam_hits : 0, stat->innocent_hits > 0 ? stat->innocent_hits : 0); result = sqlite3_exec(s->dbh, query, NULL, NULL, NULL); } if ((stat->status & TST_DISK) || result) { /* insert failed; try updating instead */ snprintf (query, sizeof (query), "UPDATE dspam_token_data" " SET spam_hits=%lu," "innocent_hits=%lu" " WHERE token='%" LLU_FMT_SPEC "'", stat->spam_hits > 0 ? stat->spam_hits : 0, stat->innocent_hits > 0 ? stat->innocent_hits : 0, token); if ((sqlite3_exec(s->dbh, query, NULL, NULL, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, query); LOGDEBUG ("_ds_set_spamrecord: unable to run query: %s", query); return EFAILURE; } } return 0; } int _ds_init_storage (DSPAM_CTX * CTX, void *dbh) { struct _sqlite_drv_storage *s; FILE *file; char buff[1024]; char filename[MAX_FILENAME_LENGTH]; char *err=NULL; struct stat st; int noexist; buff[0] = 0; if (CTX == NULL) return EINVAL; if (CTX->flags & DSF_MERGED) { LOG(LOG_ERR, ERR_DRV_NO_MERGED); return EINVAL; } /* don't init if we're already initted */ if (CTX->storage != NULL) { LOGDEBUG ("_ds_init_storage: storage already initialized"); return EINVAL; } s = calloc (1, sizeof (struct _sqlite_drv_storage)); if (s == NULL) { LOG (LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } s->dbh = NULL; s->control_token = 0; s->iter_token = NULL; s->iter_sig = NULL; s->control_token = 0; s->control_sh = 0; s->control_ih = 0; s->dbh_attached = (dbh) ? 1 : 0; if (CTX->group == NULL || CTX->group[0] == 0) _ds_userdir_path (filename, CTX->home, CTX->username, "sdb"); else _ds_userdir_path (filename, CTX->home, CTX->group, "sdb"); _ds_prepare_path_for (filename); noexist = stat(filename, &st); if (dbh) s->dbh = dbh; else if ((sqlite3_open(filename, &s->dbh))!=SQLITE_OK) s->dbh = NULL; if (s->dbh == NULL) { free(s); LOGDEBUG ("_ds_init_storage: unable to initialize database: %s", filename); return EFAILURE; } /* Commit timeout of 20 minutes */ sqlite3_busy_timeout(s->dbh, 1000 * 60 * 20); /* Create database objects */ if (noexist) { LOGDEBUG ("_ds_init_storage: Creating object structure in database: %s", filename); buff[0] = 0; snprintf (buff, sizeof (buff), "CREATE TABLE dspam_token_data (token CHAR(20) PRIMARY KEY," "spam_hits INT,innocent_hits INT,last_hit DATE)"); if ((sqlite3_exec(s->dbh, buff, NULL, NULL, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, buff); free(s); return EFAILURE; } buff[0] = 0; snprintf (buff, sizeof (buff), "CREATE INDEX id_token_data_02 ON dspam_token_data" "(innocent_hits)"); if ((sqlite3_exec(s->dbh, buff, NULL, NULL, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, buff); free(s); return EFAILURE; } buff[0] = 0; snprintf (buff, sizeof (buff), "CREATE TABLE dspam_signature_data (" "signature CHAR(128) PRIMARY KEY,data BLOB,created_on DATE)"); if ((sqlite3_exec(s->dbh, buff, NULL, NULL, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, buff); free(s); return EFAILURE; } buff[0] = 0; snprintf (buff, sizeof (buff), "CREATE TABLE dspam_stats (dspam_stat_id INT PRIMARY KEY," "spam_learned INT,innocent_learned INT," "spam_misclassified INT,innocent_misclassified INT," "spam_corpusfed INT,innocent_corpusfed INT," "spam_classified INT,innocent_classified INT)"); if ((sqlite3_exec(s->dbh, buff, NULL, NULL, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, buff); free(s); return EFAILURE; } buff[0] = 0; } if (_ds_read_attribute(CTX->config->attributes, "SQLitePragma")) { char pragma[1024]; attribute_t t = _ds_find_attribute(CTX->config->attributes, "SQLitePragma"); while(t != NULL) { snprintf(pragma, sizeof(pragma), "PRAGMA %s", t->value); if ((sqlite3_exec(s->dbh, pragma, NULL, NULL, &err))!=SQLITE_OK) { LOG(LOG_WARNING, "sqlite.pragma function error: %s: %s", err, pragma); _sqlite_drv_query_error (err, pragma); } t = t->next; } } else { snprintf(filename, MAX_FILENAME_LENGTH, "%s/sqlite.pragma", CTX->home); file = fopen(filename, "r"); if (file != NULL) { while((fgets(buff, sizeof(buff), file))!=NULL) { chomp(buff); if ((sqlite3_exec(s->dbh, buff, NULL, NULL, &err))!=SQLITE_OK) { LOG(LOG_WARNING, "sqlite.pragma function error: %s: %s", err, buff); _sqlite_drv_query_error (err, buff); } } fclose(file); } } CTX->storage = s; s->dir_handles = nt_create (NT_INDEX); s->control_token = 0; s->control_sh = 0; s->control_ih = 0; /* get spam totals on successful init */ if (CTX->username != NULL) { if (_sqlite_drv_get_spamtotals (CTX)) { LOGDEBUG ("_ds_init_storage: unable to load totals. Using zero values."); } } else { memset (&CTX->totals, 0, sizeof (struct _ds_spam_totals)); memset (&s->control_totals, 0, sizeof (struct _ds_spam_totals)); } return 0; } int _ds_shutdown_storage (DSPAM_CTX * CTX) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; struct nt_node *node_nt; struct nt_c c_nt; if (s->dbh == NULL) { LOGDEBUG ("_ds_shutdown_storage: invalid database handle (NULL)"); return EINVAL; } 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); /* Store spam totals on shutdown */ if (CTX->username != NULL && CTX->operating_mode != DSM_CLASSIFY) { _sqlite_drv_set_spamtotals (CTX); } if (!s->dbh_attached) sqlite3_close(s->dbh); s->dbh = NULL; free(s); CTX->storage = NULL; return 0; } int _ds_create_signature_id (DSPAM_CTX * CTX, char *buf, size_t len) { char session[64]; char digit[6]; int pid, j; 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_get_signature (DSPAM_CTX * CTX, struct _ds_spam_signature *SIG, const char *signature) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; char query[128]; char *err=NULL; const char *query_tail; sqlite3_stmt *stmt; if (s->dbh == NULL) { LOGDEBUG ("_ds_get_signature: invalid database handle (NULL)"); return EINVAL; } snprintf (query, sizeof (query), "SELECT data FROM dspam_signature_data WHERE signature=\"%s\"", signature); if ((sqlite3_prepare(s->dbh, query, -1, &stmt, &query_tail)) !=SQLITE_OK) { _sqlite_drv_query_error (err, query); return EFAILURE; } if ((sqlite3_step(stmt))!=SQLITE_ROW) { sqlite3_finalize(stmt); return EFAILURE; } SIG->length = sqlite3_column_bytes(stmt, 0); SIG->data = malloc(SIG->length); if (SIG->data == NULL) { sqlite3_finalize(stmt); LOG(LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } memcpy(SIG->data, sqlite3_column_blob(stmt, 0), SIG->length); if ((sqlite3_finalize(stmt)!=SQLITE_OK)) LOGDEBUG("_ds_get_signature: sqlite3_finalize() failed: %s", strerror(errno)); return 0; } int _ds_set_signature (DSPAM_CTX * CTX, struct _ds_spam_signature *SIG, const char *signature) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; char scratch[1024]; char *err=NULL; const char *query_tail=NULL; sqlite3_stmt *stmt; if (s->dbh == NULL) { LOGDEBUG ("_ds_set_signature: invalid database handle (NULL)"); return EINVAL; } snprintf (scratch, sizeof (scratch), "INSERT INTO dspam_signature_data (signature,created_on,data)" " VALUES (\"%s\",date('now'),?)", signature); if ((sqlite3_prepare(s->dbh, scratch, -1, &stmt, &query_tail))!=SQLITE_OK) { _sqlite_drv_query_error ("_ds_set_signature: sqlite3_prepare() failed", scratch); return EFAILURE; } sqlite3_bind_blob(stmt, 1, SIG->data, SIG->length, SQLITE_STATIC); if ((sqlite3_step(stmt))!=SQLITE_DONE) { _sqlite_drv_query_error (err, scratch); return EFAILURE; } sqlite3_finalize(stmt); return 0; } int _ds_delete_signature (DSPAM_CTX * CTX, const char *signature) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; char query[128]; char *err=NULL; if (s->dbh == NULL) { LOGDEBUG ("_ds_delete_signature: invalid database handle (NULL)"); return EINVAL; } snprintf (query, sizeof (query), "DELETE FROM dspam_signature_data WHERE signature=\"%s\"", signature); if ((sqlite3_exec(s->dbh, query, NULL, NULL, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, query); return EFAILURE; } return 0; } int _ds_verify_signature (DSPAM_CTX * CTX, const char *signature) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; char query[128]; char *err=NULL, **row; int nrow, ncolumn; if (s->dbh == NULL) { LOGDEBUG ("_ds_verify_signature: invalid database handle (NULL)"); return EINVAL; } snprintf (query, sizeof (query), "SELECT signature FROM dspam_signature_data WHERE signature=\"%s\"", signature); if ((sqlite3_get_table(s->dbh, query, &row, &nrow, &ncolumn, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, query); return EFAILURE; } sqlite3_free_table(row); if (nrow<1) { return -1; } return 0; } char * _ds_get_nextuser (DSPAM_CTX * CTX) { static char user[MAX_FILENAME_LENGTH]; static char path[MAX_FILENAME_LENGTH]; struct _sqlite_drv_storage *s = (struct _sqlite_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, "_ds_get_nextuser: 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 (!strncmp (entry->d_name + strlen (entry->d_name) - 4, ".sdb", 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); /* done */ user[0] = 0; return NULL; } struct _ds_storage_record * _ds_get_nexttoken (DSPAM_CTX * CTX) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; struct _ds_storage_record *st; char query[128]; char *err=NULL; const char *query_tail=NULL; int x; if (s->dbh == NULL) { LOGDEBUG ("_ds_get_nexttoken: invalid database handle (NULL)"); return NULL; } st = calloc (1, sizeof (struct _ds_storage_record)); if (st == NULL) { LOG (LOG_CRIT, ERR_MEM_ALLOC); return NULL; } if (s->iter_token == NULL) { snprintf (query, sizeof (query), "SELECT token,spam_hits,innocent_hits,strftime('%%s'," "last_hit) FROM dspam_token_data"); if ((sqlite3_prepare(s->dbh, query, -1, &s->iter_token, &query_tail)) !=SQLITE_OK) { _sqlite_drv_query_error (err, query); free(st); return NULL; } } if ((x = sqlite3_step(s->iter_token)) !=SQLITE_ROW) { if (x != SQLITE_DONE) { _sqlite_drv_query_error (err, query); s->iter_token = NULL; free(st); return NULL; } sqlite3_finalize((struct sqlite3_stmt *) s->iter_token); s->iter_token = NULL; free(st); return NULL; } st->token = strtoull ((const char *) sqlite3_column_text(s->iter_token, 0), NULL, 0); st->spam_hits = strtoul ((const char *) sqlite3_column_text(s->iter_token, 1), NULL, 0); if (st->spam_hits == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_ds_get_nexttoken: failed converting %s to st->spam_hits", (const char *) sqlite3_column_text(s->iter_token, 1)); s->iter_token = NULL; free(st); return NULL; } st->innocent_hits = strtoul ((const char *) sqlite3_column_text(s->iter_token, 2), NULL, 0); if (st->innocent_hits == ULONG_MAX && errno == ERANGE) { LOGDEBUG("_ds_get_nexttoken: failed converting %s to st->innocent_hits", (const char *) sqlite3_column_text(s->iter_token, 2)); s->iter_token = NULL; free(st); return NULL; } st->last_hit = (time_t) strtol ((const char *) sqlite3_column_text(s->iter_token, 3), NULL, 0); return st; } struct _ds_storage_signature * _ds_get_nextsignature (DSPAM_CTX * CTX) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; struct _ds_storage_signature *st; unsigned long length; char query[128]; char *mem; char *err=NULL; const char *query_tail=NULL; int x; if (s->dbh == NULL) { LOGDEBUG ("_ds_get_nextsignature: invalid database handle (NULL)"); return NULL; } st = calloc (1, sizeof (struct _ds_storage_signature)); if (st == NULL) { LOG (LOG_CRIT, ERR_MEM_ALLOC); return NULL; } if (s->iter_sig == NULL) { snprintf (query, sizeof (query), "SELECT data,signature,strftime('%%s',created_on)" " FROM dspam_signature_data"); if ((sqlite3_prepare(s->dbh, query, -1, &s->iter_sig, &query_tail)) !=SQLITE_OK) { _sqlite_drv_query_error (err, query); free(st); return NULL; } } if ((x = sqlite3_step(s->iter_sig)) !=SQLITE_ROW) { if (x != SQLITE_DONE) { _sqlite_drv_query_error (err, query); s->iter_sig = NULL; free(st); return NULL; } sqlite3_finalize((struct sqlite3_stmt *) s->iter_sig); s->iter_sig = NULL; free(st); return NULL; } length = sqlite3_column_bytes(s->iter_sig, 0); mem = malloc (length); if (mem == NULL) { LOG (LOG_CRIT, ERR_MEM_ALLOC); sqlite3_finalize(s->iter_sig); s->iter_sig = NULL; free(st); return NULL; } memcpy(mem, sqlite3_column_blob(s->iter_sig, 0), length); st->data = mem; strlcpy(st->signature, (const char *) sqlite3_column_text(s->iter_sig, 1), sizeof(st->signature)); st->length = length; st->created_on = (time_t) strtol( (const char *) sqlite3_column_text(s->iter_sig, 2), NULL, 0); return st; } void _sqlite_drv_query_error (const char *error, const char *query) { FILE *file; time_t tm = time (NULL); char ct[128]; char fn[MAX_FILENAME_LENGTH]; LOG (LOG_WARNING, "query error: %s: see sql.errors for more details", error); snprintf (fn, sizeof (fn), "%s/sql.errors", LOGDIR); snprintf (ct, sizeof (ct), "%s", ctime (&tm)); chomp (ct); file = fopen (fn, "a"); if (file == NULL) { LOG(LOG_ERR, ERR_IO_FILE_WRITE, fn, strerror (errno)); } else { fprintf (file, "[%s] %d: %s: %s\n", ct, (int) getpid (), error, query); fclose (file); } free((char *)error); return; } int _ds_del_spamrecord (DSPAM_CTX * CTX, unsigned long long token) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; char query[128]; char *err=NULL; if (s->dbh == NULL) { LOGDEBUG ("_ds_del_spamrecord: invalid database handle (NULL)"); return EINVAL; } snprintf (query, sizeof (query), "DELETE FROM dspam_token_data WHERE token='%" LLU_FMT_SPEC "'", token); if ((sqlite3_exec(s->dbh, query, NULL, NULL, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, query); return EFAILURE; } return 0; } int _ds_delall_spamrecords (DSPAM_CTX * CTX, ds_diction_t diction) { struct _sqlite_drv_storage *s = (struct _sqlite_drv_storage *) CTX->storage; ds_term_t ds_term; ds_cursor_t ds_c; buffer *query; char *err=NULL; char scratch[1024]; char queryhead[1024]; int writes = 0; if (diction->items < 1) return 0; if (s->dbh == NULL) { LOGDEBUG ("_ds_delall_spamrecords: invalid database handle (NULL)"); return EINVAL; } query = buffer_create (NULL); if (query == NULL) { LOG (LOG_CRIT, ERR_MEM_ALLOC); return EUNKNOWN; } snprintf (queryhead, sizeof(queryhead), "DELETE FROM dspam_token_data" " WHERE token IN ("); buffer_cat (query, queryhead); ds_c = ds_diction_cursor(diction); ds_term = ds_diction_next(ds_c); while (ds_term) { snprintf (scratch, sizeof (scratch), "'%" LLU_FMT_SPEC "'", ds_term->key); buffer_cat (query, scratch); ds_term = ds_diction_next(ds_c); if (writes > 2500 || ds_term == NULL) { buffer_cat (query, ")"); if ((sqlite3_exec(s->dbh, query->data, NULL, NULL, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, query->data); buffer_destroy(query); return EFAILURE; } buffer_copy(query, queryhead); writes = 0; } else { writes++; if (ds_term) buffer_cat (query, ","); } } ds_diction_close(ds_c); if (writes) { buffer_cat (query, ")"); if ((sqlite3_exec(s->dbh, query->data, NULL, NULL, &err))!=SQLITE_OK) { _sqlite_drv_query_error (err, query->data); buffer_destroy(query); return EFAILURE; } } buffer_destroy (query); return 0; } void *_ds_connect (DSPAM_CTX *CTX) { return NULL; } /* 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); }