/* This file is part of Mailfromd.
Copyright (C) 2005-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 . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include "libmf.h"
#include "mfdb.h"
int mf_database_mode = 0600;
int ignore_failed_reads_option;//FIXME;
struct db_fmt_list {
struct db_fmt_list *next;
struct db_format fmt;
};
static struct db_fmt_list *db_fmt_list;
static mu_debug_handle_t debug_handle;
void
db_format_enumerate(dbfmt_enumerator_t fp, void *data)
{
struct db_fmt_list *p;
for (p = db_fmt_list; p; p = p->next)
fp(&p->fmt, data);
}
struct db_format *
db_format_install(struct db_format *fmt)
{
struct db_fmt_list *p;
if (!fmt)
return NULL;
p = mu_alloc(sizeof *p);
p->fmt = *fmt;
mf_namefixup_register(&p->fmt.dbname, p->fmt.dbname);
/* FIXME: not used so far */
p->fmt.debug_handle = mu_debug_register_category(p->fmt.name);
p->next = db_fmt_list;
db_fmt_list = p;
return &p->fmt;
}
struct db_format *
db_format_lookup(const char *name)
{
struct db_fmt_list *p;
for (p = db_fmt_list; p; p = p->next)
if (strcmp(p->fmt.name, name) == 0)
return &p->fmt;
return NULL;
}
void
db_format_setup()
{
debug_handle = mu_debug_register_category("db");
cache_format = db_format_install(cache_format);
rate_format = db_format_install(rate_format);
tbf_rate_format = db_format_install(tbf_rate_format);
greylist_format = db_format_install(greylist_format);
}
int
mf_file_mode_to_safety_criteria(int mode)
{
int fl = 0;
if (!(mode & 0002))
fl |= MU_FILE_SAFETY_WORLD_WRITABLE;
if (!(mode & 0004))
fl |= MU_FILE_SAFETY_WORLD_READABLE;
if (!(mode & 0020))
fl |= MU_FILE_SAFETY_GROUP_WRITABLE;
if (!(mode & 0040))
fl |= MU_FILE_SAFETY_GROUP_READABLE;
return fl;
}
mu_dbm_file_t
mf_dbm_open(char *dbname, int access)
{
mu_dbm_file_t db;
int rc;
rc = mu_dbm_create(dbname, &db, MU_FILE_SAFETY_LINKED_WRDIR |
MU_FILE_SAFETY_DIR_IWOTH|
mf_file_mode_to_safety_criteria(mf_database_mode));
if (rc) {
mu_error(_("unable to create database %s: %s"),
dbname, mu_strerror (rc));
return NULL;
}
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 NULL;
}
rc = mu_dbm_open(db, access, mf_database_mode);
if (rc) {
mu_error(_("mu_dbm_open(%s) failed: %s (%s)"), dbname,
mu_strerror (rc), mu_strerror (errno));
mu_dbm_destroy(&db);
return NULL;
}
return db;
}
int
db_list_item(char *dbname, char *email, db_item_printer_t fun)
{
mu_dbm_file_t db;
struct mu_dbm_datum key, contents;
int rc = 1;
db = mf_dbm_open(dbname, MU_STREAM_READ);
if (!db)
return 1;
memset(&contents, 0, sizeof contents);
memset(&key, 0, sizeof key);
key.mu_dptr = email;
key.mu_dsize = strlen (email) + 1;
rc = mu_dbm_fetch(db, &key, &contents);
if (rc == 0) {
fun(&key, &contents);
} else if (rc != MU_ERR_NOENT) {
mu_error(_("cannot fetch record `%s' from `%s': %s"),
email, dbname, mu_dbm_strerror(db));
rc = 0;
}
mu_dbm_destroy(&db);
return rc;
}
typedef int (*db_enum_func)(struct mu_dbm_datum *key,
struct mu_dbm_datum *cnt, void *data);
int
db_enumerate(mu_dbm_file_t db, db_enum_func fun, void *data)
{
int rc = 0;
int ret = 0;
struct mu_dbm_datum key, contents;
const char *name = "";
mu_dbm_get_name(db, &name);
memset(&key, 0, sizeof key);
memset(&contents, 0, sizeof contents);
for (rc = mu_dbm_firstkey(db, &key); rc == 0;
rc = mu_dbm_nextkey(db, &key)) {
int res = mu_dbm_fetch(db, &key, &contents);
if (res == 0) {
res = fun(&key, &contents, data);
if (res) {
mu_dbm_datum_free(&key);
break;
}
mu_dbm_datum_free(&contents);
} else {
mu_error(_("cannot fetch data `%*.*s' from `%s': %s"),
(int) key.mu_dsize, (int) key.mu_dsize,
key.mu_dptr,
name,
mu_dbm_strerror(db));
ret = 1;
}
}
if (rc != MU_ERR_NOENT) {
mu_error(_("unexpected error scanning database %s: %s"),
name, mu_dbm_strerror(db));
ret = 1;
}
return ret;
}
static int
db_list_func(struct mu_dbm_datum *key, struct mu_dbm_datum *contents,
void *data)
{
db_item_printer_t fun = data;
fun(key, contents);
return 0;
}
int
db_list(char *dbname, db_item_printer_t fun)
{
mu_dbm_file_t db = mf_dbm_open(dbname, MU_STREAM_READ);
if (!db)
return 1;
db_enumerate(db, db_list_func, fun);
mu_dbm_destroy(&db);
return 1;
}
struct expire_data {
db_expire_t fun;
mu_opool_t pool;
size_t key_count;
};
static int
db_expire_func(struct mu_dbm_datum *key, struct mu_dbm_datum *contents,
void *data)
{
struct expire_data *dp = data;
if (dp->fun(contents)) {
mu_opool_append(dp->pool, &key->mu_dsize,
sizeof key->mu_dsize);
mu_opool_append(dp->pool, key->mu_dptr, key->mu_dsize);
dp->key_count++;
}
return 0;
}
int
db_expire(char *dbname, db_expire_t fun)
{
mu_dbm_file_t db;
struct mu_dbm_datum key;
struct expire_data ed;
size_t size;
char *base, *p;
size_t i;
int rc = 0;
db = mf_dbm_open(dbname, MU_STREAM_RDWR);
if (!db)
return 1;
ed.fun = fun;
mu_opool_create(&ed.pool, MU_OPOOL_ENOMEMABRT);
ed.key_count = 0;
mu_debug(debug_handle, MU_DEBUG_TRACE0,
("Expiring %s: mark phase", dbname));
if (db_enumerate(db, db_expire_func, &ed))
rc = !ignore_failed_reads_option;
size = 0;
mu_opool_append(ed.pool, &size, sizeof size);
mu_debug(debug_handle, MU_DEBUG_TRACE0,
("Number of keys to expire: %lu, memory used: %u",
(unsigned long) ed.key_count,
(unsigned) mu_opool_size(ed.pool)));
mu_debug(debug_handle, MU_DEBUG_TRACE0,
("Expiring %s: sweep phase", dbname));
base = mu_opool_finish(ed.pool, NULL);
memset(&key, 0, sizeof key);
for (i = 0, p = base; i < ed.key_count; i++) {
size = *(size_t*)p;
p += sizeof(size_t);
mu_debug(debug_handle, MU_DEBUG_TRACE5,
("Remove: %*.*s", (int)size, (int)size, p));
key.mu_dptr = p;
key.mu_dsize = size;
rc = mu_dbm_delete(db, &key);
if (rc && rc != MU_ERR_NOENT) {
mu_error (_("cannot remove record `%*.*s' from `%s': %s"),
(int)size, (int)size,
p, dbname, mu_dbm_strerror(db));
rc = 1;
}
p += size;
}
mu_debug(debug_handle, MU_DEBUG_TRACE0,
("Finished expiring %s", dbname));
mu_opool_destroy(&ed.pool);
mu_dbm_destroy(&db);
return rc;
}
int
db_delete(char *dbname, char *id)
{
mu_dbm_file_t db;
struct mu_dbm_datum key;
int rc;
db = mf_dbm_open(dbname, MU_STREAM_RDWR);
if (!db)
return 1;
memset(&key, 0, sizeof key);
key.mu_dptr = id;
key.mu_dsize = strlen (id) + 1;
rc = mu_dbm_delete(db, &key);
if (rc) {
mu_error(_("cannot remove record `%s' from `%s': %s"), id,
dbname, mu_dbm_strerror(db));
}
mu_dbm_destroy(&db);
return rc;
}
static char *
make_tmp_name(const char *dbname)
{
char *suf = strrchr(dbname, '.');
char *p = strrchr(dbname, '/');
size_t len;
char *newname;
char buf[80];
if (suf)
len = suf - dbname;
else if (p)
len = p - dbname;
else
len = strlen(dbname);
snprintf(buf, sizeof buf, "%lu", (unsigned long) getpid());
newname = mu_alloc(len + 1 + strlen(buf) + 1);
memcpy(newname, dbname, len);
newname[len] = '.';
strcpy(newname + len + 1, buf);
return newname;
}
struct compact_data {
int rc;
db_expire_t fun;
mu_dbm_file_t ndb;
};
static int
db_compact_func(struct mu_dbm_datum *key, struct mu_dbm_datum *contents,
void *data)
{
struct compact_data *dp = data;
int rc;
const char *name;
mu_dbm_get_name(dp->ndb, &name);
if (!dp->fun || dp->fun(contents) == 0) {
if (((char*)key->mu_dptr)[key->mu_dsize - 1]) {
/* Old database format. Convert. */
char *p;
struct mu_dbm_datum newkey;
p = malloc(key->mu_dsize + 1);
if (!p) {
mu_error(_("not enough memory"));
return 1;
}
memcpy(p, key->mu_dptr, key->mu_dsize);
p[key->mu_dsize] = 0;
memset(&newkey, 0, sizeof newkey);
newkey.mu_dptr = p;
newkey.mu_dsize = key->mu_dsize + 1;
rc = mu_dbm_store(dp->ndb, &newkey, contents, 1);
if (rc) {
mu_error(_("cannot insert datum `%s' into `%s': %s"),
p, name,
rc == MU_ERR_FAILURE ?
mu_dbm_strerror(dp->ndb) :
mu_strerror(rc));
dp->rc = 1;
}
free(p);
} else if ((rc = mu_dbm_store(dp->ndb, key, contents, 1))) {
mu_error(_("cannot insert datum `%*.*s' into `%s': %s"),
(int)key->mu_dsize, (int)key->mu_dsize,
(char*) key->mu_dsize,
name,
rc == MU_ERR_FAILURE ?
mu_dbm_strerror(dp->ndb) :
mu_strerror(rc));
dp->rc = 1;
}
}
return 0;
}
int
db_compact(char *dbname, db_expire_t fun)
{
mu_dbm_file_t odb;
char *tmpname;
struct compact_data dat;
const char *file_name;
int flags, rc;
int fd;
struct stat st;
mu_debug(debug_handle, MU_DEBUG_TRACE0,
("Compacting database `%s'", dbname));
odb = mf_dbm_open(dbname, MU_STREAM_READ);
if (!odb)
return 1;
rc = mu_dbm_get_fd(odb, &fd, NULL);
if (rc) {
mu_dbm_destroy(&odb);
mu_diag_funcall(MU_DIAG_ERROR, "mu_dbm_get_fd", NULL, rc);
return 1;
}
if (fstat(fd, &st)) {
mu_diag_funcall(MU_DIAG_ERROR, "fstat", NULL, errno);
mu_dbm_destroy(&odb);
return 1;
}
if (getuid() != st.st_uid && getgid() != st.st_gid) {
if (switch_to_privs(st.st_uid, st.st_gid, NULL))
exit(EX_SOFTWARE);
}
umask(0777 & ~ st.st_mode);
mu_dbm_get_name(odb, &file_name);
tmpname = make_tmp_name(file_name);
mu_dbm_safety_get_flags(odb, &flags);
rc = mu_dbm_create(tmpname, &dat.ndb, flags);
if (rc) {
mu_error(_("unable to create database %s: %s"),
tmpname, mu_strerror (rc));
return 1;
}
mu_dbm_safety_set_owner(dat.ndb, st.st_uid);
rc = mu_dbm_open(dat.ndb, MU_STREAM_CREAT, mf_database_mode);
if (rc) {
mu_error(_("unable to open database %s: %s"),
tmpname, mu_strerror (rc));
mu_dbm_destroy(&odb);
return 1;
}
dat.rc = 0;
dat.fun = fun;
db_enumerate(odb, db_compact_func, &dat);
mu_dbm_close(dat.ndb);
mu_dbm_close(odb);
if (dat.rc == 0) {
if (rename(tmpname, file_name)) {
mu_error(_("cannot rename %s to %s: %s"),
tmpname, file_name, mu_strerror(errno));
dat.rc = 1;
}
}
mu_dbm_destroy(&dat.ndb);
mu_dbm_destroy(&odb);
free(tmpname);
return dat.rc;
}