diff options
Diffstat (limited to 'src/util/dict_dbm.c')
-rw-r--r-- | src/util/dict_dbm.c | 503 |
1 files changed, 503 insertions, 0 deletions
diff --git a/src/util/dict_dbm.c b/src/util/dict_dbm.c new file mode 100644 index 0000000..e47b7ee --- /dev/null +++ b/src/util/dict_dbm.c @@ -0,0 +1,503 @@ +/*++ +/* NAME +/* dict_dbm 3 +/* SUMMARY +/* dictionary manager interface to DBM files +/* SYNOPSIS +/* #include <dict_dbm.h> +/* +/* DICT *dict_dbm_open(path, open_flags, dict_flags) +/* const char *name; +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_dbm_open() opens the named DBM database and makes it available +/* via the generic interface described in dict_open(3). +/* DIAGNOSTICS +/* Fatal errors: cannot open file, file write error, out of memory. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* ndbm(3) data base subroutines +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include "sys_defs.h" + +#ifdef HAS_DBM + +/* System library. */ + +#include <sys/stat.h> +#ifdef PATH_NDBM_H +#include PATH_NDBM_H +#else +#include <ndbm.h> +#endif +#ifdef R_FIRST +#error "Error: you are including the Berkeley DB version of ndbm.h" +#error "To build with Postfix NDBM support, delete the Berkeley DB ndbm.h file" +#endif +#include <string.h> +#include <unistd.h> + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "htable.h" +#include "iostuff.h" +#include "vstring.h" +#include "myflock.h" +#include "stringops.h" +#include "dict.h" +#include "dict_dbm.h" +#include "warn_stat.h" + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + DBM *dbm; /* open database */ + VSTRING *key_buf; /* key buffer */ + VSTRING *val_buf; /* result buffer */ +} DICT_DBM; + +#define SCOPY(buf, data, size) \ + vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) + +/* dict_dbm_lookup - find database entry */ + +static const char *dict_dbm_lookup(DICT *dict, const char *name) +{ + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + datum dbm_key; + datum dbm_value; + const char *result = 0; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_dbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); + + /* + * See if this DBM file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name) + 1; + dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); + if (dbm_value.dptr != 0) { + dict->flags &= ~DICT_FLAG_TRY0NULL; + result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); + } + } + + /* + * See if this DBM file was written with no null byte appended to key and + * value. + */ + if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name); + dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); + if (dbm_value.dptr != 0) { + dict->flags &= ~DICT_FLAG_TRY1NULL; + result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); + + return (result); +} + +/* dict_dbm_update - add or update database entry */ + +static int dict_dbm_update(DICT *dict, const char *name, const char *value) +{ + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + datum dbm_key; + datum dbm_value; + int status; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_dbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + dbm_key.dptr = (void *) name; + dbm_value.dptr = (void *) value; + dbm_key.dsize = strlen(name); + dbm_value.dsize = strlen(value); + + /* + * If undecided about appending a null byte to key and value, choose a + * default depending on the platform. + */ + if ((dict->flags & DICT_FLAG_TRY1NULL) + && (dict->flags & DICT_FLAG_TRY0NULL)) { +#ifdef DBM_NO_TRAILING_NULL + dict->flags &= ~DICT_FLAG_TRY1NULL; +#else + dict->flags &= ~DICT_FLAG_TRY0NULL; +#endif + } + + /* + * Optionally append a null byte to key and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dsize++; + dbm_value.dsize++; + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); + + /* + * Do the update. + */ + if ((status = dbm_store(dict_dbm->dbm, dbm_key, dbm_value, + (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0) + msg_fatal("error writing DBM database %s: %m", dict_dbm->dict.name); + if (status) { + if (dict->flags & DICT_FLAG_DUP_IGNORE) + /* void */ ; + else if (dict->flags & DICT_FLAG_DUP_WARN) + msg_warn("%s: duplicate entry: \"%s\"", dict_dbm->dict.name, name); + else + msg_fatal("%s: duplicate entry: \"%s\"", dict_dbm->dict.name, name); + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); + + return (status); +} + +/* dict_dbm_delete - delete one entry from the dictionary */ + +static int dict_dbm_delete(DICT *dict, const char *name) +{ + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + datum dbm_key; + int status = 1; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_dbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); + + /* + * See if this DBM file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name) + 1; + dbm_clearerr(dict_dbm->dbm); + if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) { + if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */ + msg_fatal("error deleting from %s: %m", dict_dbm->dict.name); + status = 1; /* not found */ + } else { + dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */ + } + } + + /* + * See if this DBM file was written with no null byte appended to key and + * value. + */ + if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name); + dbm_clearerr(dict_dbm->dbm); + if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) { + if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */ + msg_fatal("error deleting from %s: %m", dict_dbm->dict.name); + status = 1; /* not found */ + } else { + dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */ + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); + + return (status); +} + +/* traverse the dictionary */ + +static int dict_dbm_sequence(DICT *dict, int function, + const char **key, const char **value) +{ + const char *myname = "dict_dbm_sequence"; + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + datum dbm_key; + datum dbm_value; + int status; + + dict->error = 0; + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); + + /* + * Determine and execute the seek function. It returns the key. + */ + switch (function) { + case DICT_SEQ_FUN_FIRST: + dbm_key = dbm_firstkey(dict_dbm->dbm); + break; + case DICT_SEQ_FUN_NEXT: + dbm_key = dbm_nextkey(dict_dbm->dbm); + break; + default: + msg_panic("%s: invalid function: %d", myname, function); + } + + if (dbm_key.dptr != 0 && dbm_key.dsize > 0) { + + /* + * Copy the key so that it is guaranteed null terminated. + */ + *key = SCOPY(dict_dbm->key_buf, dbm_key.dptr, dbm_key.dsize); + + /* + * Fetch the corresponding value. + */ + dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); + + if (dbm_value.dptr != 0 && dbm_value.dsize > 0) { + + /* + * Copy the value so that it is guaranteed null terminated. + */ + *value = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); + status = 0; + } else { + + /* + * Determine if we have hit the last record or an error + * condition. + */ + if (dbm_error(dict_dbm->dbm)) + msg_fatal("error seeking %s: %m", dict_dbm->dict.name); + status = 1; /* no error: eof/not found + * (should not happen!) */ + } + } else { + + /* + * Determine if we have hit the last record or an error condition. + */ + if (dbm_error(dict_dbm->dbm)) + msg_fatal("error seeking %s: %m", dict_dbm->dict.name); + status = 1; /* no error: eof/not found */ + } + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); + + return (status); +} + +/* dict_dbm_close - disassociate from data base */ + +static void dict_dbm_close(DICT *dict) +{ + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + + dbm_close(dict_dbm->dbm); + if (dict_dbm->key_buf) + vstring_free(dict_dbm->key_buf); + if (dict_dbm->val_buf) + vstring_free(dict_dbm->val_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_dbm_open - open DBM data base */ + +DICT *dict_dbm_open(const char *path, int open_flags, int dict_flags) +{ + DICT_DBM *dict_dbm; + struct stat st; + DBM *dbm; + char *dbm_path = 0; + int lock_fd; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_DBM_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (dbm_path != 0) \ + myfree(dbm_path); \ + return (__d); \ + } while (0) + + /* + * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in + * the time domain) locking while accessing individual database records. + * + * Programs such as postmap/postalias use their own large-grained (in the + * time domain) locks while rewriting the entire file. + */ + if (dict_flags & DICT_FLAG_LOCK) { + dbm_path = concatenate(path, ".dir", (char *) 0); + if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0) + DICT_DBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_DBM, path, + open_flags, dict_flags, + "open database %s: %m", + dbm_path)); + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("shared-lock database %s for open: %m", dbm_path); + } + + /* + * XXX SunOS 5.x has no const in dbm_open() prototype. + */ + if ((dbm = dbm_open((char *) path, open_flags, 0644)) == 0) + DICT_DBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_DBM, path, + open_flags, dict_flags, + "open database %s.{dir,pag}: %m", + path)); + + if (dict_flags & DICT_FLAG_LOCK) { + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("unlock database %s for open: %m", dbm_path); + if (close(lock_fd) < 0) + msg_fatal("close database %s: %m", dbm_path); + } + dict_dbm = (DICT_DBM *) dict_alloc(DICT_TYPE_DBM, path, sizeof(*dict_dbm)); + dict_dbm->dict.lookup = dict_dbm_lookup; + dict_dbm->dict.update = dict_dbm_update; + dict_dbm->dict.delete = dict_dbm_delete; + dict_dbm->dict.sequence = dict_dbm_sequence; + dict_dbm->dict.close = dict_dbm_close; + dict_dbm->dict.lock_fd = dbm_dirfno(dbm); + dict_dbm->dict.stat_fd = dbm_pagfno(dbm); + if (dict_dbm->dict.lock_fd == dict_dbm->dict.stat_fd) + msg_fatal("open database %s: cannot support GDBM", path); + if (fstat(dict_dbm->dict.stat_fd, &st) < 0) + msg_fatal("dict_dbm_open: fstat: %m"); + dict_dbm->dict.mtime = st.st_mtime; + dict_dbm->dict.owner.uid = st.st_uid; + dict_dbm->dict.owner.status = (st.st_uid != 0); + + /* + * Warn if the source file is newer than the indexed file, except when + * the source file changed only seconds ago. + */ + if ((dict_flags & DICT_FLAG_LOCK) != 0 + && stat(path, &st) == 0 + && st.st_mtime > dict_dbm->dict.mtime + && st.st_mtime < time((time_t *) 0) - 100) + msg_warn("database %s is older than source file %s", dbm_path, path); + + close_on_exec(dbm_pagfno(dbm), CLOSE_ON_EXEC); + close_on_exec(dbm_dirfno(dbm), CLOSE_ON_EXEC); + dict_dbm->dict.flags = dict_flags | DICT_FLAG_FIXED; + if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) + dict_dbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_dbm->dict.fold_buf = vstring_alloc(10); + dict_dbm->dbm = dbm; + dict_dbm->key_buf = 0; + dict_dbm->val_buf = 0; + + DICT_DBM_OPEN_RETURN(DICT_DEBUG (&dict_dbm->dict)); +} + +#endif |