/* This file is part of mailfromd. -*- c -*- Copyright (C) 2006, 2007 Sergey Poznyakoff 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 3, 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, see . */ #define LOOKUP_NULL_BYTE 0x1 #define LOOKUP_TEST_ONLY 0x2 static int dbmap_lookup(eval_environ_t env, char *dbname, const char *keystr, const char *defval, int flags) { int rc; DBM_FILE db; DBM_DATUM key; DBM_DATUM contents; if (!defval) defval = ""; if (mu_dbm_open(dbname, &db, MU_STREAM_READ, 0, NULL)) MF_THROW(mf_dbfailure, _("mu_dbm_open(%s) failed: %s"), dbname, mu_dbm_strerror()); memset(&key, 0, sizeof key); memset(&contents, 0, sizeof contents); MU_DATUM_PTR(key) = (void*) keystr; MU_DATUM_SIZE(key) = strlen(keystr); if (flags & LOOKUP_NULL_BYTE) MU_DATUM_SIZE(key)++; rc = mu_dbm_fetch(&db, key, &contents) == 0; debug2(10, "Looking up %s: %s", keystr, rc ? "true" : "false"); if (flags & LOOKUP_TEST_ONLY) push(env, (STKVAL)rc); else { if (!rc) { if (defval) pushs(env, (STKVAL)defval); else push(env, 0); } else if (((char*)MU_DATUM_PTR(contents))[MU_DATUM_SIZE(contents)-1]) { size_t off; size_t len = MU_DATUM_SIZE(contents); char *s = MF_ALLOC_HEAP(off, len + 1); memcpy(s, MU_DATUM_PTR(contents), len); s[len] = 0; push(env, (STKVAL) off); } else pushs(env, MU_DATUM_PTR(contents)); } mu_dbm_datum_free(&contents); mu_dbm_close(&db); return rc; } MF_DEFUN(dbmap, NUMBER, STRING dbname, STRING key, OPTIONAL, NUMBER null) { dbmap_lookup(env, dbname, key, NULL, LOOKUP_TEST_ONLY | (MF_OPTVAL(null) ? LOOKUP_NULL_BYTE : 0)); } END MF_DEFUN(dbget, STRING, STRING dbname, STRING key, OPTIONAL, STRING defval, NUMBER null) { dbmap_lookup(env, dbname, key, MF_OPTVAL(defval), (MF_OPTVAL(null) ? LOOKUP_NULL_BYTE : 0)); } END MF_DEFUN(dbput, VOID, STRING dbname, STRING keystr, STRING value, OPTIONAL, NUMBER null) { int rc; DBM_FILE db; DBM_DATUM key; DBM_DATUM contents; if (mu_dbm_open(dbname, &db, MU_STREAM_RDWR, 0640, NULL)) MF_THROW(mf_dbfailure, _("mu_dbm_open(%s) failed: %s"), dbname, mu_dbm_strerror()); memset(&key, 0, sizeof key); MU_DATUM_PTR(key) = keystr; MU_DATUM_SIZE(key) = strlen(keystr); if (MF_OPTVAL(null)) MU_DATUM_SIZE(key)++; memset(&contents, 0, sizeof contents); MU_DATUM_PTR(contents) = value; MU_DATUM_SIZE(contents) = strlen(value) + 1; rc = mu_dbm_insert(&db, key, contents, 1); mu_dbm_close(&db); MF_ASSERT(rc == 0, mf_dbfailure, _("Failed to insert data to %s: %s %s: %s"), dbname, keystr, value, mu_dbm_strerror()); } END MF_DEFUN(dbdel, VOID, STRING dbname, STRING keystr, OPTIONAL, NUMBER null) { DBM_FILE db; DBM_DATUM key; int rc; if (mu_dbm_open(dbname, &db, MU_STREAM_RDWR, 0640, NULL)) MF_THROW(mf_dbfailure, _("mu_dbm_open(%s) failed: %s"), dbname, mu_dbm_strerror()); memset(&key, 0, sizeof key); MU_DATUM_PTR(key) = keystr; MU_DATUM_SIZE(key) = strlen(keystr); if (MF_OPTVAL(null)) MU_DATUM_SIZE(key)++; rc = mu_dbm_delete(&db, key); mu_dbm_close(&db); MF_ASSERT(rc == 0, mf_dbfailure, _("Failed to delete data `%s' from `%s': %s"), key, dbname, mu_dbm_strerror()); } END static void greylist_print_item(const char *key, size_t size, const void *content) { time_t timestamp = *(time_t*) content; size--; /* Size includes the trailing nul */ printf("%*.*s ", size, size, key); format_time_str(stdout, timestamp); putchar('\n'); } static int greylist_expire_item(const void *content) { time_t timestamp = *(time_t*) content; return greylist_format->expire_interval && time(NULL) - timestamp > greylist_format->expire_interval; } static struct db_format greylist_format_struct = { "greylist", DEFAULT_GREYLIST_DATABASE, 1, DEFAULT_EXPIRE_INTERVAL, greylist_print_item, greylist_expire_item }; struct db_format *greylist_format = &greylist_format_struct; MF_VAR(greylist_seconds_left, NUMBER); /* greylist(key, interval) Returns true if the key is greylisted, false if it's OK to deliver mail. */ MF_DEFUN(greylist, NUMBER, STRING email, NUMBER interval) { int rc; DBM_FILE db; DBM_DATUM key; DBM_DATUM contents; int readonly; time_t now; rc = mu_dbm_open(greylist_format->dbname, &db, MU_STREAM_RDWR, 0600, &readonly); MF_ASSERT(rc == 0, mf_dbfailure, _("mu_dbm_open(%s) failed: %s"), greylist_format->dbname, mu_dbm_strerror()); memset(&key, 0, sizeof key); memset(&contents, 0, sizeof contents); MU_DATUM_PTR(key) = email; MU_DATUM_SIZE(key) = strlen(email)+1; time(&now); if (mu_dbm_fetch(&db, key, &contents) == 0) { time_t timestamp, diff; MF_ASSERT(MU_DATUM_SIZE(contents) == sizeof timestamp, mf_dbfailure, _("Greylist database %s has wrong data size"), greylist_format->dbname); timestamp = *(time_t*) MU_DATUM_PTR(contents); diff = now - timestamp; __DBG(20) { char timebuf[32]; debug_log("%s entered greylist database on %s, " "%ld seconds ago", email, mailfromd_timestr(timestamp, timebuf, sizeof timebuf), (long) diff); } if (diff < interval) { diff = interval - diff; MF_VAR_REF(greylist_seconds_left, diff); debug2(20, "%s still greylisted (for %lu sec.)", email, (unsigned long) diff); rc = 1; } else if (diff > greylist_format->expire_interval) { debug1(20, "greylist record for %s expired", email); if (!readonly) { memcpy(MU_DATUM_PTR(contents), &now, sizeof now); if (mu_dbm_insert(&db, key, contents, 1)) mu_error(_("Cannot insert datum `%s' in " "greylist database %s: %s"), key, greylist_format->dbname, mu_dbm_strerror()); } else debug(20, "database opened in readonly mode: " "not updating"); rc = 1; } else { debug1(20, "%s finished greylisting period", email); rc = 0; } mu_dbm_datum_free(&contents); } else if (!readonly) { debug1(20, "greylisting %s", email); MF_VAR_REF(greylist_seconds_left, interval); MU_DATUM_PTR(contents) = (void*)&now; MU_DATUM_SIZE(contents) = sizeof now; if (mu_dbm_insert(&db, key, contents, 1)) mu_error(_("Cannot insert datum `%s' in greylist " "database %s: %s"), key, greylist_format->dbname, mu_dbm_strerror()); rc = 1; } else rc = 0; mu_dbm_close(&db); MF_RETURN(rc); } END MF_DEFUN(db_name, STRING, STRING fmtid) { struct db_format *fmt = db_format_lookup(fmtid); MF_ASSERT(fmt != NULL, mf_not_found, _("No such db format: %s"), fmtid); MF_RETURN_STRING(fmt->dbname); } END MF_DEFUN(db_get_active, NUMBER, STRING fmtid) { struct db_format *fmt = db_format_lookup(fmtid); MF_ASSERT(fmt != NULL, mf_not_found, _("No such db format: %s"), fmtid); MF_RETURN(fmt->enabled); } END MF_DEFUN(db_set_active, VOID, STRING fmtid, NUMBER active) { struct db_format *fmt = db_format_lookup(fmtid); MF_ASSERT(fmt != NULL, mf_not_found, _("No such db format: %s"), fmtid); fmt->enabled = active; } END MF_DEFUN(db_expire_interval, NUMBER, STRING fmtid) { struct db_format *fmt = db_format_lookup(fmtid); MF_ASSERT(fmt != NULL, mf_not_found, _("No such db format: %s"), fmtid); MF_RETURN(fmt->expire_interval); } END MF_INIT