/* This file is part of Mailfromd. -*- c -*-
Copyright (C) 2006-2020 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 . */
MF_BUILTIN_MODULE
#define DEFAULT_DB_MODE 0640
#include
struct db_prop { /* Database properties */
char *pat; /* Database name pattern */
mode_t mode; /* File mode */
int null; /* Null byte */
mu_url_t hint; /* Hint to use instead of the default one */
};
static mu_list_t db_prop_list;
static int
db_prop_compare(const void *item, const void *data)
{
const struct db_prop *prop = item;
const char *name = data;
return strcmp(prop->pat, name);
}
static int
strtomode(char *str, mode_t *pmode)
{
mode_t mode = 0;
struct { char c; unsigned mask; } modetab[] = {
{ 'r', S_IRUSR },
{ 'w', S_IWUSR },
{ 'x', S_IXUSR },
{ 'r', S_IRGRP },
{ 'w', S_IWGRP },
{ 'x', S_IXGRP },
{ 'r', S_IROTH },
{ 'w', S_IWOTH },
{ 'x', S_IXOTH },
{ 0 }
};
int i;
for (i = 0; modetab[i].c; i++) {
if (!str[i])
return i + 1;
if (str[i] == modetab[i].c)
mode |= modetab[i].mask;
else if (str[i] != '-')
return i + 1;
}
if (str[i])
return i + 1;
*pmode = mode;
return 0;
}
static int
is_url(const char *p)
{
for (; *p && mu_isalnum(*p); p++)
;
return *p == ':';
}
/* #pragma dbprop [null] [mode] [hint]
At least one of the bracketed parameters must be present. Two or more
parameters can be given in arbitrary order.
*/
MF_PRAGMA(dbprop, 3, 5)
{
int null = 0;
mode_t mode = DEFAULT_DB_MODE;
struct db_prop *prop;
int rc;
char *pat;
mu_url_t hint = NULL;
--argc;
pat = *++argv;
while (--argc) {
char *p = *++argv;
if (strcmp(p, "null") == 0)
null = 1;
else if (mu_isdigit(*p)) {
unsigned long n = strtoul(p, &p, 8);
if (*p || (mode = n) != n) {
parse_error(_("bad numeric file mode"));
return;
}
} else if (is_url(p)) {
rc = mu_url_create(&hint, p);
if (rc) {
parse_error(_("not a valid URL: %s"),
mu_strerror(rc));
return;
}
} else if (rc = strtomode(p, &mode)) {
parse_error(_("bad symbolic file mode (near %s)"),
p + rc - 1);
return;
}
}
if (!db_prop_list) {
rc = mu_list_create(&db_prop_list);
if (rc) {
parse_error(_("cannot create list: %s"),
mu_strerror(rc));
return;
}
mu_list_set_comparator(db_prop_list, db_prop_compare);
}
if (mu_list_locate(db_prop_list, pat, (void**) &prop)) {
prop = mu_alloc(sizeof(*prop));
prop->pat = mu_strdup(pat);
rc = mu_list_append(db_prop_list, prop);
if (rc) {
parse_error(_("Cannot create list: %s"),
mu_strerror(rc));
return;
}
}
prop->mode = mode;
prop->null = null;
prop->hint = hint;
}
const struct db_prop *
db_prop_lookup(const char *name)
{
mu_iterator_t itr;
const struct db_prop *found = NULL;
if (db_prop_list
&& mu_list_get_iterator(db_prop_list, &itr) == 0) {
for (mu_iterator_first(itr);
!mu_iterator_is_done(itr);
mu_iterator_next(itr)) {
const struct db_prop *p;
mu_iterator_current(itr, (void**)&p);
if (strcmp(p->pat, name) == 0) {
found = p;
break;
} else if (fnmatch(p->pat, name, 0) == 0)
found = p;
}
mu_iterator_destroy(&itr);
}
return found;
}
int
_open_dbm(mu_dbm_file_t *pdb, char *dbname, int access, int mode,
mu_url_t hint)
{
mu_dbm_file_t db;
int rc;
mu_url_t url;
if (!hint)
hint = mu_dbm_get_hint();
rc = mu_url_create_hint(&url, dbname, 0, hint);
if (rc) {
mu_error(_("cannot create database URL for %s: %s"),
dbname, mu_strerror(rc));
return rc;
}
rc = mu_dbm_create_from_url(url, &db,
mf_file_mode_to_safety_criteria(mode));
mu_url_destroy(&url);
if (rc)
return rc;
rc = mu_dbm_safety_check(db);
if (rc && rc != ENOENT) {
mu_error(_("%s fails safety check: %s"),
dbname, mu_strerror(rc));
mu_dbm_destroy(&db);
return rc;
}
rc = mu_dbm_open(db, access, mode);
if (rc) {
mu_dbm_destroy(&db);
return rc;
}
*pdb = db;
return rc;
}
#define LOOKUP_NULL_BYTE 0x1
#define LOOKUP_TEST_ONLY 0x2
MF_DSEXP_SUPPRESS([],[<
static int
dbmap_lookup(eval_environ_t env, char *dbname, const char *keystr,
const char *defval, const struct db_prop *prop, int flags)
{
int rc;
mu_dbm_file_t db;
struct mu_dbm_datum key;
struct mu_dbm_datum contents;
if (!defval)
defval = "";
rc = _open_dbm(&db, dbname, MU_STREAM_READ,
prop ? prop->mode : DEFAULT_DB_MODE,
prop ? prop->hint : NULL);
if (rc)
MF_THROW(mfe_dbfailure,
_("mf_dbm_open(%s) failed: %s"),
dbname,
mu_strerror(rc));
memset(&key, 0, sizeof key);
memset(&contents, 0, sizeof contents);
key.mu_dptr = (void*) keystr;
key.mu_dsize = strlen(keystr);
if (flags & LOOKUP_NULL_BYTE)
key.mu_dsize++;
rc = mu_dbm_fetch(db, &key, &contents);
if (rc && rc != MU_ERR_NOENT) {
mu_dbm_destroy(&db);
MF_THROW(mfe_dbfailure,
_("cannot fetch data: %s"),
mu_dbm_strerror(db));
}
MF_DEBUG(MU_DEBUG_TRACE5,
("Looking up %s: %s", keystr, rc ? "false" : "true"));
if (flags & LOOKUP_TEST_ONLY)
push(env, (STKVAL) (rc == 0));
else {
if (rc) {
if (defval)
pushs(env, defval);
else
push(env, (STKVAL) 0L);
} else if (((char*)contents.mu_dptr)[contents.mu_dsize-1]) {
size_t off;
size_t len = contents.mu_dsize;
char *s = MF_ALLOC_HEAP(off, len + 1);
memcpy(s, contents.mu_dptr, len);
s[len] = 0;
push(env, (STKVAL) off);
} else
pushs(env, contents.mu_dptr);
}
mu_dbm_datum_free(&contents);
mu_dbm_destroy(&db);
return rc;
}
>])
MF_DSEXP
MF_DEFUN(dbmap, NUMBER, STRING dbname, STRING key, OPTIONAL, NUMBER null)
{
const struct db_prop *prop = db_prop_lookup(dbname);
dbmap_lookup(env, dbname, key, NULL,
prop,
LOOKUP_TEST_ONLY |
(MF_OPTVAL(null, prop && prop->null)
? LOOKUP_NULL_BYTE : 0));
}
END
MF_DEFUN(dbget, STRING, STRING dbname, STRING key, OPTIONAL,
STRING defval, NUMBER null)
{
const struct db_prop *prop = db_prop_lookup(dbname);
dbmap_lookup(env, dbname, key,
MF_OPTVAL(defval),
prop,
MF_OPTVAL(null, prop && prop->null)
? LOOKUP_NULL_BYTE : 0);
}
END
MF_DEFUN(dbput, VOID, STRING dbname, STRING keystr, STRING value,
OPTIONAL, NUMBER null, NUMBER mode)
{
int rc;
mu_dbm_file_t db;
struct mu_dbm_datum key;
struct mu_dbm_datum contents;
const struct db_prop *prop = db_prop_lookup(dbname);
rc = _open_dbm(&db, dbname, MU_STREAM_RDWR,
MF_OPTVAL(mode,
(prop ? prop->mode : DEFAULT_DB_MODE)),
prop ? prop->hint : NULL);
if (rc)
MF_THROW(mfe_dbfailure,
_("mf_dbm_open(%s) failed: %s"),
dbname,
mu_strerror(rc));
memset(&key, 0, sizeof key);
key.mu_dptr = keystr;
key.mu_dsize = strlen(keystr);
if (MF_OPTVAL(null, prop && prop->null))
key.mu_dsize++;
memset(&contents, 0, sizeof contents);
contents.mu_dptr = value;
contents.mu_dsize = strlen(value) + 1;
rc = mu_dbm_store(db, &key, &contents, 1);
if (rc) {
const char *errstr = mu_dbm_strerror(db);
mu_dbm_destroy(&db);
MF_THROW(mfe_dbfailure,
_("failed to insert data to %s: %s %s: %s"),
dbname,
keystr,
value,
errstr);
}
mu_dbm_destroy(&db);
}
END
MF_DEFUN(dbinsert, VOID, STRING dbname, STRING keystr, STRING value,
OPTIONAL, NUMBER replace, NUMBER null, NUMBER mode)
{
int rc;
mu_dbm_file_t db;
struct mu_dbm_datum key;
struct mu_dbm_datum contents;
const struct db_prop *prop = db_prop_lookup(dbname);
const char *errstr;
rc = _open_dbm(&db, dbname, MU_STREAM_RDWR,
MF_OPTVAL(mode,
(prop ? prop->mode : DEFAULT_DB_MODE)),
prop ? prop->hint : NULL);
if (rc)
MF_THROW(mfe_dbfailure,
_("mf_dbm_open(%s) failed: %s"),
dbname,
mu_strerror(rc));
memset(&key, 0, sizeof key);
key.mu_dptr = keystr;
key.mu_dsize = strlen(keystr);
if (MF_OPTVAL(null, prop && prop->null))
key.mu_dsize++;
memset(&contents, 0, sizeof contents);
contents.mu_dptr = value;
contents.mu_dsize = strlen(value) + 1;
rc = mu_dbm_store(db, &key, &contents, MF_OPTVAL(replace));
if (rc && rc != MU_ERR_EXISTS)
errstr = mu_dbm_strerror(db);
mu_dbm_destroy(&db);
if (rc == MU_ERR_EXISTS)
MF_THROW(mfe_exists, _("record already exists"));
MF_ASSERT(rc == 0,
mfe_dbfailure,
_("failed to insert data to %s: %s %s: %s"),
dbname,
keystr,
value,
errstr);
}
END
MF_DEFUN(dbdel, VOID, STRING dbname, STRING keystr, OPTIONAL, NUMBER null,
NUMBER mode)
{
mu_dbm_file_t db;
struct mu_dbm_datum key;
int rc;
const struct db_prop *prop = db_prop_lookup(dbname);
rc = _open_dbm(&db, dbname, MU_STREAM_RDWR,
MF_OPTVAL(mode,
(prop ? prop->mode : DEFAULT_DB_MODE)),
prop ? prop->hint : NULL);
MF_ASSERT(rc == 0,
mfe_dbfailure,
_("mf_dbm_open(%s) failed: %s"),
dbname,
mu_strerror(rc));
memset(&key, 0, sizeof key);
key.mu_dptr = keystr;
key.mu_dsize = strlen(keystr);
if (MF_OPTVAL(null, prop && prop->null))
key.mu_dsize++;
rc = mu_dbm_delete(db, &key);
mu_dbm_destroy(&db);
MF_ASSERT(rc == 0 || rc == MU_ERR_NOENT,
mfe_dbfailure,
_("failed to delete data `%s' from `%s': %s"),
keystr,
dbname,
mu_strerror(rc));
}
END
#define NUMDB 128
struct db_tab {
int used;
mu_dbm_file_t db;
struct mu_dbm_datum key;
};
static void *
alloc_db_tab()
{
return mu_calloc(NUMDB, sizeof(struct db_tab));
}
static void
close_db_tab(struct db_tab *dbt)
{
if (dbt->used) {
mu_dbm_datum_free(&dbt->key);
mu_dbm_destroy(&dbt->db);
dbt->used = 0;
}
}
static void
destroy_db_tab(void *data)
{
int i;
struct db_tab *db = data;
for (i = 0; i < NUMDB; i++)
close_db_tab(db + i);
free(db);
}
MF_DECLARE_DATA(DBTAB, alloc_db_tab, destroy_db_tab);
static int
new_db_tab(struct db_tab *dbt)
{
int i;
for (i = 0; i < NUMDB; i++)
if (!dbt[i].used) {
dbt[i].used = 1;
return i;
}
return -1;
}
MF_DEFUN(dbfirst, NUMBER, STRING dbname)
{
int rc;
int n;
struct db_tab *dbt = MF_GET_DATA;
mu_dbm_file_t db;
struct mu_dbm_datum key;
const struct db_prop *prop = db_prop_lookup(dbname);
rc = _open_dbm(&db, dbname, MU_STREAM_READ,
prop ? prop->mode : DEFAULT_DB_MODE,
prop ? prop->hint : NULL);
MF_ASSERT(rc == 0,
mfe_dbfailure,
_("mf_dbm_open(%s) failed: %s"),
dbname,
mu_strerror(rc));
memset(&key, 0, sizeof key);
rc = mu_dbm_firstkey(db, &key);
if (rc) {
if (rc == MU_ERR_NOENT) {
mu_dbm_destroy(&db);
MF_RETURN(0);
} else if (rc) {
mu_dbm_destroy(&db);
MF_THROW(mfe_dbfailure,
_("mf_dbm_firstkey failed: %s"),
mu_strerror(rc));
}
}
n = new_db_tab(dbt);
MF_ASSERT(n >= 0,
mfe_failure,
_("no more database entries available"));
dbt += n;
dbt->db = db;
dbt->key = key;
MF_RETURN(n);
}
END
MF_DEFUN(dbnext, NUMBER, NUMBER dn)
{
struct db_tab *dbt = MF_GET_DATA + dn;
struct mu_dbm_datum nextkey;
int rc;
MF_ASSERT(dn >= 0 && dn < NUMDB && dbt->used,
mfe_range,
_("invalid database descriptor"));
memset (&nextkey, 0, sizeof nextkey);
rc = mu_dbm_nextkey(dbt->db, &nextkey);
if (rc) {
if (rc == MU_ERR_FAILURE)
mu_error(_("mu_dbm_nextkey: %s"),
mu_dbm_strerror(dbt->db));
close_db_tab(dbt);
MF_RETURN(0);
}
mu_dbm_datum_free(&nextkey);
dbt->key = nextkey;
MF_RETURN(1);
}
END
MF_DEFUN(dbkey, STRING, NUMBER dn)
{
size_t off, len;
char *s;
struct db_tab *dbt = MF_GET_DATA + dn;
MF_ASSERT(dn >= 0 && dn < NUMDB && dbt->used,
mfe_range,
_("invalid database descriptor"));
len = dbt->key.mu_dsize;
s = MF_ALLOC_HEAP(off, len + 1);
memcpy(s, dbt->key.mu_dptr, len);
s[len] = 0;
MF_RETURN(off, size);
}
END
MF_DEFUN(dbvalue, STRING, NUMBER dn)
{
int rc;
size_t off, len;
char *s;
struct db_tab *dbt = MF_GET_DATA + dn;
struct mu_dbm_datum contents;
MF_ASSERT(dn >= 0 && dn < NUMDB && dbt->used,
mfe_range,
_("invalid database descriptor"));
memset(&contents, 0, sizeof contents);
rc = mu_dbm_fetch(dbt->db, &dbt->key, &contents);
MF_ASSERT(rc == 0,
mfe_dbfailure,
_("cannot fetch key: %s"),
rc == MU_ERR_FAILURE ?
mu_dbm_strerror(dbt->db) : mu_strerror (rc));
len = contents.mu_dsize;
s = MF_ALLOC_HEAP(off, len + 1);
memcpy(s, contents.mu_dptr, len);
s[len] = 0;
mu_dbm_datum_free(&contents);
MF_RETURN(off, size);
}
END
enum greylist_semantics
{
greylist_traditional,
greylist_ct
};
static enum greylist_semantics greylist_semantics = greylist_traditional;
/* #pragma greylist {traditional|gray|ct|con-tassios}*/
MF_PRAGMA(greylist, 2, 2)
{
if (strcmp(argv[1], "traditional") == 0
|| strcmp(argv[1], "gray") == 0)
greylist_semantics = greylist_traditional;
else if (strcmp(argv[1], "ct") == 0
|| strcmp(argv[1], "con-tassios") == 0)
greylist_semantics = greylist_ct;
else
/* TRANSLATORS: Do not translate keywords:
traditional, gray, ct, con-tassios */
parse_error(_("unknown semantics; allowed values are: "
"traditional (or gray) and "
"ct (or con-tassios)"));
}
/* FIXME: Duplicated in lib/cache.c */
static char *
format_timestr(time_t timestamp, char *timebuf, size_t bufsize)
{
struct tm tm;
gmtime_r(×tamp, &tm);
strftime(timebuf, bufsize, "%c", &tm);
return timebuf;
}
MF_VAR(greylist_seconds_left, NUMBER);
/* The traditional (aka gray's) greylist implementation: the greylist
database keeps the time the greylisting was activated.
*/
static int
do_greylist_traditional(eval_environ_t env, char *email, long interval)
{
int rc;
mu_dbm_file_t db;
struct mu_dbm_datum key;
struct mu_dbm_datum contents;
int readonly = 0;
time_t now;
rc = _open_dbm(&db, greylist_format->dbname, MU_STREAM_RDWR, 0600,
NULL);
if (rc) {
rc = _open_dbm(&db, greylist_format->dbname,
MU_STREAM_READ, 0600, NULL);
readonly = 1;
}
MF_ASSERT(rc == 0, mfe_dbfailure, _("mf_dbm_open(%s) failed: %s"),
greylist_format->dbname, mu_strerror(rc));
memset(&key, 0, sizeof key);
memset(&contents, 0, sizeof contents);
key.mu_dptr = email;
key.mu_dsize = strlen(email)+1;
time(&now);
rc = mu_dbm_fetch(db, &key, &contents);
if (rc == 0) {
time_t timestamp, diff;
MF_ASSERT(contents.mu_dsize == sizeof timestamp,
mfe_dbfailure,
_("greylist database %s has wrong data size"),
greylist_format->dbname);
timestamp = *(time_t*) contents.mu_dptr;
diff = now - timestamp;
if (mu_debug_level_p(debug_handle, MU_DEBUG_TRACE5)) {
char timebuf[32];
mu_debug_log("%s entered greylist database on %s, "
"%ld seconds ago",
email,
format_timestr(timestamp, timebuf,
sizeof timebuf),
(long) diff);
}
if (diff < interval) {
diff = interval - diff;
MF_VAR_REF(greylist_seconds_left, ulong, diff);//FIXME
MF_DEBUG(MU_DEBUG_TRACE6,
("%s still greylisted (for %lu sec.)",
email,
(unsigned long) diff));
rc = 1;
} else if (diff > greylist_format->expire_interval) {
MF_DEBUG(MU_DEBUG_TRACE6,
("greylist record for %s expired", email));
MF_VAR_REF(greylist_seconds_left, long, interval);
if (!readonly) {
memcpy(contents.mu_dptr, &now, sizeof now);
rc = mu_dbm_store(db, &key, &contents, 1);
if (rc)
mu_error(_("cannot insert datum `%-.*s' in "
"greylist database %s: %s"),
(int)key.mu_dsize,
(char*)key.mu_dptr,
greylist_format->dbname,
rc == MU_ERR_FAILURE ?
mu_dbm_strerror(db) :
mu_strerror(rc));
} else
MF_DEBUG(MU_DEBUG_TRACE6,
("database opened in readonly mode: "
"not updating"));
rc = 1;
} else {
MF_DEBUG(MU_DEBUG_TRACE6,
("%s finished greylisting period", email));
rc = 0;
}
mu_dbm_datum_free(&contents);
} else if (!readonly) {
MF_DEBUG(MU_DEBUG_TRACE6, ("greylisting %s", email));
MF_VAR_REF(greylist_seconds_left, long, interval);
contents.mu_dptr = (void*)&now;
contents.mu_dsize = sizeof now;
rc = mu_dbm_store(db, &key, &contents, 1);
if (rc)
mu_error(_("Cannot insert datum `%-.*s' in greylist "
"database %s: %s"),
(int)key.mu_dsize, (char*)key.mu_dptr,
greylist_format->dbname,
rc == MU_ERR_FAILURE ?
mu_dbm_strerror(db) : mu_strerror(rc));
rc = 1;
} else
rc = 0;
mu_dbm_destroy(&db);
return rc;
}
/* Implementation of the is_greylisted predicate has no sense for
traditional greylist databases, because greylisting interval is
not known beforehand.
FIXME: keep the reference below up to date.
*/
static int
is_greylisted_traditional(eval_environ_t env, char *email)
{
MF_THROW(mfe_failure,
_("is_greylisted is not implemented for traditional greylist databases; "
"see documentation, chapter %s %s for more info"),
"4.12.1.23", "Greylisting functions");
return 0;
}
/* New greylist implementation (by Con Tassios): the database keeps
the time the greylisting period is set to expire (`interval' seconds
from now)
*/
static int
do_greylist_ct(eval_environ_t env, char *email, long interval)
{
int rc;
mu_dbm_file_t db;
struct mu_dbm_datum key;
struct mu_dbm_datum contents;
int readonly = 0;
time_t now;
rc = _open_dbm(&db, greylist_format->dbname, MU_STREAM_RDWR, 0600,
NULL);
if (rc) {
rc = _open_dbm(&db, greylist_format->dbname,
MU_STREAM_READ, 0600, NULL);
readonly = 1;
}
MF_ASSERT(rc == 0, mfe_dbfailure, _("mf_dbm_open(%s) failed: %s"),
greylist_format->dbname, mu_strerror(rc));
memset(&key, 0, sizeof key);
memset(&contents, 0, sizeof contents);
key.mu_dptr = email;
key.mu_dsize = strlen(email) + 1;
time(&now);
rc = mu_dbm_fetch(db, &key, &contents);
if (rc == 0) {
time_t timestamp;
MF_ASSERT(contents.mu_dsize == sizeof timestamp,
mfe_dbfailure,
_("greylist database %s has wrong data size"),
greylist_format->dbname);
timestamp = *(time_t*) contents.mu_dptr;
if (now < timestamp) {
time_t diff = timestamp - now;
MF_VAR_REF(greylist_seconds_left, long, diff);
MF_DEBUG(MU_DEBUG_TRACE6,
("%s still greylisted (for %lu sec.)",
email,
(unsigned long) diff));
rc = 1;
} else if (now - timestamp >
greylist_format->expire_interval) {
MF_DEBUG(MU_DEBUG_TRACE6,
("greylist record for %s expired", email));
MF_VAR_REF(greylist_seconds_left, long, interval);
if (!readonly) {
now += interval;
memcpy(contents.mu_dptr, &now, sizeof now);
rc = mu_dbm_store(db, &key, &contents, 1);
if (rc)
mu_error(_("Cannot insert datum "
"`%-.*s' in greylist "
"database %s: %s"),
(int)key.mu_dsize,
(char*)key.mu_dptr,
greylist_format->dbname,
rc == MU_ERR_FAILURE ?
mu_dbm_strerror(db) :
mu_strerror(rc));
} else
MF_DEBUG(MU_DEBUG_TRACE6,
("database opened in readonly mode: "
"not updating"));
rc = 1;
} else {
MF_DEBUG(MU_DEBUG_TRACE6,
("%s finished greylisting period", email));
rc = 0;
}
mu_dbm_datum_free(&contents);
} else if (!readonly) {
MF_DEBUG(MU_DEBUG_TRACE6, ("greylisting %s", email));
MF_VAR_REF(greylist_seconds_left, long, interval);
now += interval;
contents.mu_dptr = (void*)&now;
contents.mu_dsize = sizeof now;
rc = mu_dbm_store(db, &key, &contents, 1);
if (rc)
mu_error(_("Cannot insert datum `%-.*s' in greylist "
"database %s: %s"),
(int)key.mu_dsize, (char*)key.mu_dptr,
greylist_format->dbname,
rc == MU_ERR_FAILURE ?
mu_dbm_strerror(db) : mu_strerror(rc));
rc = 1;
} else
rc = 0;
mu_dbm_destroy(&db);
return rc;
}
/* The `is_greylisted' predicate for new databases */
static int
is_greylisted_ct(eval_environ_t env, char *email)
{
int rc;
mu_dbm_file_t db;
struct mu_dbm_datum key;
struct mu_dbm_datum contents;
time_t now;
rc = _open_dbm(&db, greylist_format->dbname, MU_STREAM_RDWR, 0600,
NULL);
if (rc)
rc = _open_dbm(&db, greylist_format->dbname,
MU_STREAM_READ, 0600, NULL);
MF_ASSERT(rc == 0, mfe_dbfailure, _("mf_dbm_open(%s) failed: %s"),
greylist_format->dbname, mu_strerror(rc));
memset(&key, 0, sizeof key);
memset(&contents, 0, sizeof contents);
key.mu_dptr = email;
key.mu_dsize = strlen(email) + 1;
time(&now);
rc = mu_dbm_fetch(db, &key, &contents);
if (rc == 0) {
time_t timestamp;
MF_ASSERT(contents.mu_dsize == sizeof timestamp,
mfe_dbfailure,
_("greylist database %s has wrong data size"),
greylist_format->dbname);
timestamp = *(time_t*) contents.mu_dptr;
rc = timestamp > now;
if (rc)
MF_VAR_REF(greylist_seconds_left, long, timestamp - now);
mu_dbm_datum_free(&contents);
} else
rc = 0;
mu_dbm_destroy(&db);
return rc;
}
struct greylist_class {
int (*gl_fun)(eval_environ_t, char *, long);
int (*gl_pred)(eval_environ_t, char *);
};
struct greylist_class greylist_class[] = {
{ do_greylist_traditional, is_greylisted_traditional },
{ do_greylist_ct, is_greylisted_ct }
};
/* 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)
{
MF_RETURN(greylist_class[greylist_semantics].gl_fun(env, email,
interval));
}
END
/* is_greylisted(key)
Returns true if the key is greylisted, otherwise false
*/
MF_DEFUN(is_greylisted, NUMBER, STRING email)
{
MF_RETURN(greylist_class[greylist_semantics].gl_pred(env, email));
}
END
MF_DEFUN(db_name, STRING, STRING fmtid)
{
struct db_format *fmt = db_format_lookup(fmtid);
MF_ASSERT(fmt != NULL,
mfe_not_found,
_("no such db format: %s"), fmtid);
MF_RETURN(fmt->dbname);
}
END
MF_DEFUN(db_get_active, NUMBER, STRING fmtid)
{
struct db_format *fmt = db_format_lookup(fmtid);
MF_ASSERT(fmt != NULL,
mfe_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,
mfe_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,
mfe_not_found,
_("no such db format: %s"), fmtid);
MF_RETURN(fmt->expire_interval);
}
END