/*
 * **    spasm - The spasm anti-spam milter
 * **    Copyright (C) 2002-2004 Sean Hanson <aradia@theasylum.org>
 * **
 * **    This program is free software; you can redistribute it and/or modify
 * **    it under the terms of the GNU General Public License as published by
 * **    the Free Software Foundation; either version 2 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 General Public License for more details.
 * **
 * **    You should have received a copy of the GNU General Public License
 * **    along with this program; if not, write to the Free Software
 * **    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * */

#include <curses.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#include "spasm.h"
#include "dbfunc.h"

char *sp_strcat(char *a, char *b) {
    char *buf = NULL;

    if((buf = calloc(strlen(a) + strlen(b) + 2, 1)) == NULL) {
	spasm_error(LOG_ERR, "(sp_strcat) Error allocating memory: %m");
	return NULL;
    }
    sprintf(buf, "%s/%s", a, b);
    return buf;
}

#ifdef USE_BERKELEY_DB
/* DB4.1 and DB4.0 cross-compatibility issues */
int sp_db_open(DB *db, DB_TXN *txnid, const char *file, const char *database, DBTYPE type, u_int32_t flags, int mode) {

#if (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
    return db->open(db, txnid, file, database, type, flags, mode);
#else
    return db->open(db, file, database, type, flags, mode);
#endif

}

/* db_lookup_entry() - lookup a db4 hash entry, return the value */
char* db_lookup_entry(char *dbfile, char *key) {
    DB *db;
    DBT dbk, dbv;

    int rv;
    char *rp = NULL;

    if(dbfile == NULL || key == NULL) return NULL;

    memset(&dbk, '\0', sizeof(DBT));
    memset(&dbv, '\0', sizeof(DBT));

    if(db_create(&db, NULL, 0) == 0) {
	if((sp_db_open(db, NULL, dbfile, NULL, DB_HASH, DB_RDONLY, 0) == 0)) {
            dbk.data = key;
            dbk.size = strlen(key);
            rv = db->get(db, NULL, &dbk, &dbv, 0);
            if(rv || (dbv.data == NULL)) rp = NULL;
            else if((rp = calloc(dbv.size + 1, 1)) != NULL) {
                strncpy(rp, (char *)dbv.data, dbv.size);
            }
	}
	db->close(db, 0);
    }
    return rp;
}

/* db_insert_entry() - insert a db4 hash entry */
void db_insert_entry(char *dbfile, char *key, char *value) {
    DB *db;
    DBT dbk, dbv;
    int dbfd;
    struct flock lockinfo;
    int rc;

    if(dbfile == NULL || key == NULL || value == NULL) return;

    memset(&dbk, '\0', sizeof(DBT));
    memset(&dbv, '\0', sizeof(DBT));
    memset(&lockinfo, '\0', sizeof(lockinfo));

    lockinfo.l_type = F_WRLCK;

    if((rc = db_create(&db, NULL, 0)) != 0) {
	spasm_error(LOG_ERR, "(db_insert_entry) Error creating db: %s", db_strerror(rc));
	return;
    }
    if((rc = sp_db_open(db, NULL, dbfile, NULL, DB_HASH, DB_CREATE, 0644)) != 0) {
        spasm_error(LOG_ERR, "(db_insert_entry) Error opening database: %s", db_strerror(rc));
	db->close(db, 0);
        return;
    }
    if((rc = db->fd(db, &dbfd)) != 0) {
        spasm_error(LOG_ERR, "(db_insert_entry) Error reading file descriptor for database: %s", db_strerror(rc));
        db->close(db, 0);
        return;
    }
    if(fcntl(dbfd, F_SETLKW, &lockinfo) == -1) {
        spasm_error(LOG_ERR, "(db_insert_entry) Error locking database: %m");
        db->close(db, 0);
        return;
    }
    dbk.data = key;
    dbk.size = strlen(key);
    dbv.data = value;
    dbv.size = strlen(value);
    if((rc = db->put(db, NULL, &dbk, &dbv, 0)) != 0) {
        spasm_error(LOG_ERR, "(db_insert_entry) Error adding %s/%s to %s: %s", key, value, dbfile, db_strerror(rc));
    }
    if((rc = db->sync(db, 0)) != 0) {
        spasm_error(LOG_ERR, "(db_insert_entry) Error syncing database: %s", db_strerror(rc));
    }
    db->close(db, 0);
    return;
}

/* db_delete_entry() - delete a db4 entry */
void db_delete_entry(char *dbfile, char *key) {
    DB *db;
    DBT dbk;
    int dbfd;
    int rc;
    struct flock lockinfo;

    if(dbfile == NULL || key == NULL) return;
    memset(&dbk, '\0', sizeof(DBT));
    memset(&lockinfo, '\0', sizeof(lockinfo));
    lockinfo.l_type = F_WRLCK;
    if((rc = db_create(&db, NULL, 0)) != 0) {
	spasm_error(LOG_ERR, "(db_delete_entry) Error creating db: %s", db_strerror(rc));
	return;
    }
    if((rc = sp_db_open(db, NULL, dbfile, NULL, DB_HASH, DB_CREATE, 0644)) != 0) {
	spasm_error(LOG_ERR, "(db_delete_entry) Error opening database: %s", db_strerror(rc));
	db->close(db, 0);
	return;
    }
    if((rc = db->fd(db, &dbfd)) != 0) {
	spasm_error(LOG_ERR, "(db_delete_entry) Error reading file descriptor for database: %s", db_strerror(rc));
	db->close(db, 0);
	return;
    }
    if(fcntl(dbfd, F_SETLKW, &lockinfo) == -1) {
	spasm_error(LOG_ERR, "(db_delete_entry) Error locking database: %m");
	db->close(db, 0);
	return;
    }
    dbk.data = key;
    dbk.size = strlen(key);
    if((rc = db->del(db, NULL, &dbk, 0)) != 0) {
	spasm_error(LOG_ERR, "(db_delete_entry) Error deleting %s from %s: %s", key, dbfile, db_strerror(rc));
    }
    if((rc = db->sync(db, 0)) != 0) {
	spasm_error(LOG_ERR, "(db_delete_entry) Error syncing database: %s", db_strerror(rc));
    }
    db->close(db, 0);
}

/* db_dump_entries() - dump spasm_list of entries in db4 database */
struct spasm_list* db_dump_entries(char *dbfile, int vals) {
    struct spasm_list *entries = NULL;
    struct spasm_list *first = NULL;
    DB *db;
    DBT dbk, dbv;
    DBC *dbcp;
    int dbfd, rc;

    if(dbfile == NULL) return NULL;
    memset(&dbk, '\0', sizeof(DBT));
    memset(&dbv, '\0', sizeof(DBT));

    if((rc = db_create(&db, NULL, 0)) != 0) {
	spasm_error(LOG_ERR, "(db_dump_entries) Error creating db: %s", db_strerror(rc));
        return NULL;
    }
    if((rc = sp_db_open(db, NULL, dbfile, NULL, DB_HASH, DB_RDONLY, 0)) != 0) {
	if(rc != 2) spasm_error(LOG_ERR, "(db_dump_entries) Error opening database (%d): %s", rc, db_strerror(rc));
        db->close(db, 0);
        return NULL;
    }
    if((rc = db->fd(db, &dbfd)) != 0) {
	spasm_error(LOG_ERR, "(db_dump_entries) Error reading file descriptor for database: %s", db_strerror(rc));
        db->close(db, 0);
        return NULL;
    }
    if((rc = db->cursor(db, NULL, &dbcp, 0)) != 0) {
	spasm_error(LOG_ERR, "(db_dump_entries) Error retrieving db cursor: %s", db_strerror(rc));
        db->close(db, 0);
        return NULL;
    }
    while(dbcp->c_get(dbcp, &dbk, &dbv, DB_NEXT) == 0) {
        if(first == NULL) {
            if((first = calloc(sizeof(struct spasm_list), 1)) != NULL) {
                if((first->entry = calloc(dbk.size + 1, 1)) != NULL) {
                    entries = first;
                    entries->next = NULL;
                    strncpy(entries->entry, dbk.data, dbk.size);
		    if(vals) {
		        if((entries->strval = calloc(dbv.size + 1, 1)) != NULL) {
			    strncpy(entries->strval, dbv.data, dbv.size);
		        } else spasm_error(LOG_ERR, "(db_dump_entries) Error allocating memory for entries->strval: %m");
		    }
		} else spasm_error(LOG_ERR, "(db_dump_entries) Error allocating memory for first->entry: %m");
            } else spasm_error(LOG_ERR, "(db_dump_entries) Error allocating memory for first: %m");
        } else {
            if((entries->next = calloc(sizeof(struct spasm_list) + 1, 1)) != NULL) {
                entries = entries->next;
                entries->next = NULL;
                if((entries->entry = calloc(dbk.size + 1, 1)) != NULL) {
                    strncpy(entries->entry, dbk.data, dbk.size);
		    if(vals) {
		        if((entries->strval = calloc(dbv.size + 1, 1)) != NULL) {
			    strncpy(entries->strval, dbv.data, dbv.size);
		        } else spasm_error(LOG_ERR, "(db_dump_entries) Error allocating memory for entries->strval: %m");
		    }
                } else spasm_error(LOG_ERR, "(db_dump_entries) Error allocating memory for entries->entry: %m");
            } else spasm_error(LOG_ERR, "(db_dump_entries) Error allocating memory for entries->next: %m");
        }
    }
    dbcp->c_close(dbcp);
    db->close(db, 0);
    return first;
}
#endif /* USE_BERKELEY_DB */

/* Database Independent Functions */

int spasm_verify_filters(char *username) {
    int rv = 0;
    char *buf;

#if (FILTER_DB_TYPE == USE_BERKELEY_DB)
    DB *db;

    if(username == NULL) return 0;
    if((buf = sp_strcat(DB_FILTER_DIR, username)) == NULL) return 0;
    if((db_create(&db, NULL, 0) != 0)) {
	free(buf);
        return 0;
    }
    if((sp_db_open(db, NULL, buf, NULL, DB_HASH, DB_RDONLY, 0) == 0)) {
	rv = 1;
    } else {
	rv = 0;
    }
    db->close(db, 0);
    free(buf);
#endif

    return rv;
}

int spasm_lookup_filter(char *username, char *entry) {
    char *result;
    int rv = 0;
    char *buf;

#if (FILTER_DB_TYPE == USE_BERKELEY_DB)
    char *domain;
    if(username == NULL || entry == NULL) return 0;
    if((buf = sp_strcat(DB_FILTER_DIR, username)) == NULL) return 0;
    if(spasm_verify_filters(username)) {
	result = db_lookup_entry(buf, entry);
	if(result != NULL) {
            rv = atoi(result);
            free(result);
	}
    } else {
	if((domain = strchr(username, '@')) != NULL) {
	    domain++;
	    free(buf);
	    buf = sp_strcat(DB_FILTER_DIR, domain);
	    result = db_lookup_entry(buf, entry);
	    if(result != NULL) {
		rv = atoi(result);
		free(result);
	    }
	}
    }
    free(buf);
#endif

    return rv;
}

struct spasm_list* spasm_get_filters(char *username) {
    struct spasm_list *entries = NULL;
    struct spasm_list *first = NULL;

#if (FILTER_DB_TYPE == USE_BERKELEY_DB)
    DB *db;
    DBT dbk, dbv;
    DBC *dbcp;
    int dbfd;
    char *domain;
    char *buf;

    if(username == NULL) return NULL;
    memset(&dbk, '\0', sizeof(DBT));
    memset(&dbv, '\0', sizeof(DBT));

    if((buf = sp_strcat(DB_FILTER_DIR, username)) == NULL) return NULL;
    if((db_create(&db, NULL, 0) != 0)) {
	return NULL;
    }
    if((sp_db_open(db, NULL, buf, NULL, DB_HASH, DB_RDONLY, 0) != 0)) {
	if((domain = strchr(username, '@')) != NULL) {
	    domain++;
	    free(buf);
	    if((buf = sp_strcat(DB_FILTER_DIR, domain)) == NULL) {
		db->close(db, 0);
		return NULL;
	    }
	    if((sp_db_open(db, NULL, buf, NULL, DB_HASH, DB_RDONLY, 0) != 0)) {
		db->close(db, 0);
		free(buf);
		return NULL;
	    }
	} else {
	    db->close(db, 0);
	    free(buf);
	    return NULL;
	}
    }
    free(buf);
    if(db->fd(db, &dbfd) != 0) {
        db->close(db, 0);
        return NULL;
    }
    if(db->cursor(db, NULL, &dbcp, 0) != 0) {
        db->close(db, 0);
        return NULL;
    }
    while(dbcp->c_get(dbcp, &dbk, &dbv, DB_NEXT) == 0) {
        if(first == NULL) {
            if((first = calloc(sizeof(struct spasm_list), 1)) != NULL) {
                if((first->entry = calloc(dbk.size + 1, 1)) != NULL) {
                    entries = first;
                    entries->next = NULL;
                    strncpy(entries->entry, dbk.data, dbk.size);
		    entries->value = atoi(dbv.data);
                }
            }
        } else {
            if((entries->next = calloc(sizeof(struct spasm_list) + 1, 1)) != NULL) {
                entries = entries->next;
                entries->next = NULL;
                if((entries->entry = calloc(dbk.size + 1, 1)) != NULL) {
                    strncpy(entries->entry, dbk.data, dbk.size);
		    entries->value = atoi(dbv.data);
                }
            }
        }
    }
    dbcp->c_close(dbcp);
    db->close(db, 0);
#endif

    return first;
}

void spasm_set_filter(char *username, char *entry, int value) {

#if (FILTER_DB_TYPE == USE_BERKELEY_DB)
    char buf[128];
    char *buf2;

    if(username == NULL || entry == NULL) return;

    if((buf2 = sp_strcat(DB_FILTER_DIR, username)) == NULL) return;
    memset(buf, '\0', sizeof(buf));
    snprintf(buf, sizeof(buf)-1, "%d", value);
    db_insert_entry(buf2, entry, buf);
    free(buf2);
#endif

    return;
}

int spasm_lookup_bl(char *blacklist, char *entry) {
    int rv = 0;

#if (BLACKLIST_DB_TYPE == USE_BERKELEY_DB)
    char *result;
    char *buf;

    if(blacklist == NULL || entry == NULL) return 0;
    if((buf = sp_strcat(DB_BLACKLIST_DIR, blacklist)) == NULL) return 0;
    result = db_lookup_entry(buf, entry);
    if(result != NULL) {
	rv = 1;
	free(result);
    }
    free(buf);
#endif

    return rv;
}

struct spasm_list* spasm_get_bl(char *blacklist) {
    struct spasm_list *first = NULL;

#if (BLACKLIST_DB_TYPE == USE_BERKELEY_DB)
    char *buf;

    if(blacklist == NULL) return NULL;
    if((buf = sp_strcat(DB_BLACKLIST_DIR, blacklist)) == NULL) return NULL;
    first = db_dump_entries(buf, 0);
    free(buf);
#endif

    return first;
}

void spasm_insert_bl(char *blacklist, char *entry, char *val) {

#if (BLACKLIST_DB_TYPE == USE_BERKELEY_DB)
    char *buf;

    if(blacklist == NULL || entry == NULL || val == NULL) return;
    if((buf = sp_strcat(DB_BLACKLIST_DIR, blacklist)) == NULL) return;
    db_insert_entry(buf, entry, val);
    free(buf);
#endif

    return;
}

void spasm_delete_bl(char *blacklist, char *entry) {

#if (BLACKLIST_DB_TYPE == USE_BERKELEY_DB)
    char *buf;

    if(blacklist == NULL || entry == NULL) return;
    if((buf = sp_strcat(DB_BLACKLIST_DIR, blacklist)) == NULL) return;
    db_delete_entry(buf, entry);
    free(buf);
#endif

}

/* returns:  1 - all is well, 0 - failure */
int
spasm_create_bl(char *blacklist, struct spasm_list *entries)
{

#if (BLACKLIST_DB_TYPE == USE_BERKELEY_DB)
    char *tmpfile;
    char *buf;
    int rc;

    if(blacklist == NULL || entries == NULL) return 0;

    if((tmpfile = calloc(strlen(DB_BLACKLIST_DIR) + strlen(blacklist) + 5, 1)) == NULL) {
        spasm_error(LOG_ERR, "Error allocating memory for blacklist path: %m");
	return 0;
    }
    sprintf(tmpfile, "%s/%s.d00", DB_BLACKLIST_DIR, blacklist);
    if((buf = sp_strcat(DB_BLACKLIST_DIR, blacklist)) == NULL) {
	free(tmpfile);
	return 0;
    }
    while(entries != NULL) {
	db_insert_entry(tmpfile, entries->entry, "1");
	entries = entries->next;
    }
    rc = rename(tmpfile, buf);
    if (rc != 0) {
        spasm_error(LOG_ERR, "Error renaming tmpfile (%s): %m", tmpfile);
    }
    free(tmpfile);
    free(buf);
    return (rc == 0);
#endif

}

int spasm_lookup_wl(char *username, char *entry) {
    int rv = 0;

#if (WHITELIST_DB_TYPE == USE_BERKELEY_DB)
    char *domain;
    char *result;
    char *buf;

    if(username == NULL || entry == NULL) return 0;
    if((buf = sp_strcat(DB_WHITELIST_DIR, username)) == NULL) return 0;
    result = db_lookup_entry(buf, entry);
    if(result != NULL) {
	rv = 1;
	free(result);
    } else {
	if((domain = strchr(username, '@')) != NULL) {
	    domain++;
	    free(buf);
	    buf = sp_strcat(DB_WHITELIST_DIR, domain);
	    result = db_lookup_entry(buf, entry);
	    if(result != NULL) {
		rv = 1;
		free(result);
	    }
	}
    }
    free(buf);
#endif

    return rv;
}

struct spasm_list* spasm_get_wl(char *username) {
    struct spasm_list *first = NULL;

#if (WHITELIST_DB_TYPE == USE_BERKELEY_DB)
    char *buf;
    if(username == NULL) return NULL;
    if((buf = sp_strcat(DB_WHITELIST_DIR, username)) == NULL) return NULL;
    first = db_dump_entries(buf, 0);
    free(buf);
#endif

    return first;
}

void spasm_insert_wl(char *username, char *entry, char *val) {

#if (WHITELIST_DB_TYPE == USE_BERKELEY_DB)
    char *buf;
    if(username == NULL || entry == NULL || val == NULL) return;
    if((buf = sp_strcat(DB_WHITELIST_DIR, username)) == NULL) return;
    db_insert_entry(buf, entry, val);
    free(buf);
#endif

    return;
}

void spasm_delete_wl(char *username, char *entry) {

#if (WHITELIST_DB_TYPE == USE_BERKELEY_DB)
    char *buf;
    if(username == NULL || entry == NULL) return;
    if((buf = sp_strcat(DB_WHITELIST_DIR, username)) == NULL) return;
    db_delete_entry(buf, entry);
    free(buf);
#endif

}

struct spasm_list* spasm_get_re(char *dbname) {
    struct spasm_list *first = NULL;

#if (REGEX_DB_TYPE == USE_BERKELEY_DB)
    char *buf;
    if(dbname == NULL) return NULL;
    if((buf = sp_strcat(DB_REGEX_DIR, dbname)) == NULL) return NULL;
    first = db_dump_entries(buf, 0);
    free(buf);
#endif

    return first;
}

void spasm_insert_re(char *dbname, char *entry) {

#if (REGEX_DB_TYPE == USE_BERKELEY_DB)
    char *buf;
    if(dbname == NULL || entry == NULL) return;
    if((buf = sp_strcat(DB_REGEX_DIR, dbname)) == NULL) return;
    db_insert_entry(buf, entry, "1");
    free(buf);
#endif

    return;
}

void spasm_delete_re(char *dbname, char *entry) {
#if (REGEX_DB_TYPE == USE_BERKELEY_DB)
    char *buf;
    if(dbname == NULL || entry == NULL) return;
    if((buf = sp_strcat(DB_REGEX_DIR, dbname)) == NULL) return;
    db_delete_entry(buf, entry);
    free(buf);
#endif

}

/* END DB INDEPENDENT FUNCTIONS */
