/* 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; }