Home | History | Annotate | Line # | Download | only in util
      1 /*	$NetBSD: dict_sdbm.c,v 1.2 2026/05/09 18:49:22 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	dict_sdbm 3
      6 /* SUMMARY
      7 /*	dictionary manager interface to SDBM files
      8 /* SYNOPSIS
      9 /*	#include <dict_sdbm.h>
     10 /*
     11 /*	DICT	*dict_sdbm_open(path, open_flags, dict_flags)
     12 /*	const char *name;
     13 /*	const char *path;
     14 /*	int	open_flags;
     15 /*	int	dict_flags;
     16 /* DESCRIPTION
     17 /*	dict_sdbm_open() opens the named SDBM database and makes it available
     18 /*	via the generic interface described in dict_open(3).
     19 /* DIAGNOSTICS
     20 /*	Fatal errors: cannot open file, file write error, out of memory.
     21 /* SEE ALSO
     22 /*	dict(3) generic dictionary manager
     23 /*	sdbm(3) data base subroutines
     24 /* LICENSE
     25 /* .ad
     26 /* .fi
     27 /*	The Secure Mailer license must be distributed with this software.
     28 /* AUTHOR(S)
     29 /*	Wietse Venema
     30 /*	IBM T.J. Watson Research
     31 /*	P.O. Box 704
     32 /*	Yorktown Heights, NY 10598, USA
     33 /*--*/
     34 
     35 #include "sys_defs.h"
     36 
     37 /* System library. */
     38 
     39 #include <sys/stat.h>
     40 #include <string.h>
     41 #include <unistd.h>
     42 #ifdef HAS_SDBM
     43 #include <sdbm.h>
     44 #endif
     45 
     46 /* Utility library. */
     47 
     48 #include <msg.h>
     49 #include <mymalloc.h>
     50 #include <htable.h>
     51 #include <iostuff.h>
     52 #include <vstring.h>
     53 #include <myflock.h>
     54 #include <stringops.h>
     55 #include <dict.h>
     56 #include <dict_sdbm.h>
     57 #include <warn_stat.h>
     58 
     59 #ifdef HAS_SDBM
     60 
     61 /* Application-specific. */
     62 
     63 typedef struct {
     64     DICT    dict;			/* generic members */
     65     SDBM   *dbm;			/* open database */
     66     VSTRING *key_buf;			/* key buffer */
     67     VSTRING *val_buf;			/* result buffer */
     68 } DICT_SDBM;
     69 
     70 #define SCOPY(buf, data, size) \
     71     vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
     72 
     73 /* dict_sdbm_lookup - find database entry */
     74 
     75 static const char *dict_sdbm_lookup(DICT *dict, const char *name)
     76 {
     77     DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
     78     datum   dbm_key;
     79     datum   dbm_value;
     80     const char *result = 0;
     81 
     82     dict->error = 0;
     83 
     84     /*
     85      * Sanity check.
     86      */
     87     if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
     88 	msg_panic("dict_sdbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
     89 
     90     /*
     91      * Optionally fold the key.
     92      */
     93     if (dict->flags & DICT_FLAG_FOLD_FIX) {
     94 	if (dict->fold_buf == 0)
     95 	    dict->fold_buf = vstring_alloc(10);
     96 	vstring_strcpy(dict->fold_buf, name);
     97 	name = lowercase(vstring_str(dict->fold_buf));
     98     }
     99 
    100     /*
    101      * Acquire an exclusive lock.
    102      */
    103     if ((dict->flags & DICT_FLAG_LOCK)
    104 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
    105 	msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
    106 
    107     /*
    108      * See if this DBM file was written with one null byte appended to key
    109      * and value.
    110      */
    111     if (dict->flags & DICT_FLAG_TRY1NULL) {
    112 	dbm_key.dptr = (void *) name;
    113 	dbm_key.dsize = strlen(name) + 1;
    114 	dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
    115 	if (dbm_value.dptr != 0) {
    116 	    dict->flags &= ~DICT_FLAG_TRY0NULL;
    117 	    result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
    118 	}
    119     }
    120 
    121     /*
    122      * See if this DBM file was written with no null byte appended to key and
    123      * value.
    124      */
    125     if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
    126 	dbm_key.dptr = (void *) name;
    127 	dbm_key.dsize = strlen(name);
    128 	dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
    129 	if (dbm_value.dptr != 0) {
    130 	    dict->flags &= ~DICT_FLAG_TRY1NULL;
    131 	    result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
    132 	}
    133     }
    134 
    135     /*
    136      * Release the exclusive lock.
    137      */
    138     if ((dict->flags & DICT_FLAG_LOCK)
    139 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
    140 	msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
    141 
    142     return (result);
    143 }
    144 
    145 /* dict_sdbm_update - add or update database entry */
    146 
    147 static int dict_sdbm_update(DICT *dict, const char *name, const char *value)
    148 {
    149     DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
    150     datum   dbm_key;
    151     datum   dbm_value;
    152     int     status;
    153 
    154     dict->error = 0;
    155 
    156     /*
    157      * Sanity check.
    158      */
    159     if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
    160 	msg_panic("dict_sdbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
    161 
    162     /*
    163      * Optionally fold the key.
    164      */
    165     if (dict->flags & DICT_FLAG_FOLD_FIX) {
    166 	if (dict->fold_buf == 0)
    167 	    dict->fold_buf = vstring_alloc(10);
    168 	vstring_strcpy(dict->fold_buf, name);
    169 	name = lowercase(vstring_str(dict->fold_buf));
    170     }
    171     dbm_key.dptr = (void *) name;
    172     dbm_value.dptr = (void *) value;
    173     dbm_key.dsize = strlen(name);
    174     dbm_value.dsize = strlen(value);
    175 
    176     /*
    177      * If undecided about appending a null byte to key and value, choose a
    178      * default depending on the platform.
    179      */
    180     if ((dict->flags & DICT_FLAG_TRY1NULL)
    181 	&& (dict->flags & DICT_FLAG_TRY0NULL)) {
    182 #ifdef DBM_NO_TRAILING_NULL
    183 	dict->flags &= ~DICT_FLAG_TRY1NULL;
    184 #else
    185 	dict->flags &= ~DICT_FLAG_TRY0NULL;
    186 #endif
    187     }
    188 
    189     /*
    190      * Optionally append a null byte to key and value.
    191      */
    192     if (dict->flags & DICT_FLAG_TRY1NULL) {
    193 	dbm_key.dsize++;
    194 	dbm_value.dsize++;
    195     }
    196 
    197     /*
    198      * Acquire an exclusive lock.
    199      */
    200     if ((dict->flags & DICT_FLAG_LOCK)
    201 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
    202 	msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
    203 
    204     /*
    205      * Do the update.
    206      */
    207     if ((status = sdbm_store(dict_sdbm->dbm, dbm_key, dbm_value,
    208      (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0)
    209 	msg_fatal("error writing SDBM database %s: %m", dict_sdbm->dict.name);
    210     if (status) {
    211 	if (dict->flags & DICT_FLAG_DUP_IGNORE)
    212 	     /* void */ ;
    213 	else if (dict->flags & DICT_FLAG_DUP_WARN)
    214 	    msg_warn("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name);
    215 	else
    216 	    msg_fatal("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name);
    217     }
    218 
    219     /*
    220      * Release the exclusive lock.
    221      */
    222     if ((dict->flags & DICT_FLAG_LOCK)
    223 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
    224 	msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
    225 
    226     return (status);
    227 }
    228 
    229 /* dict_sdbm_delete - delete one entry from the dictionary */
    230 
    231 static int dict_sdbm_delete(DICT *dict, const char *name)
    232 {
    233     DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
    234     datum   dbm_key;
    235     int     status = 1;
    236 
    237     dict->error = 0;
    238 
    239     /*
    240      * Sanity check.
    241      */
    242     if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
    243 	msg_panic("dict_sdbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
    244 
    245     /*
    246      * Optionally fold the key.
    247      */
    248     if (dict->flags & DICT_FLAG_FOLD_FIX) {
    249 	if (dict->fold_buf == 0)
    250 	    dict->fold_buf = vstring_alloc(10);
    251 	vstring_strcpy(dict->fold_buf, name);
    252 	name = lowercase(vstring_str(dict->fold_buf));
    253     }
    254 
    255     /*
    256      * Acquire an exclusive lock.
    257      */
    258     if ((dict->flags & DICT_FLAG_LOCK)
    259 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
    260 	msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
    261 
    262     /*
    263      * See if this DBM file was written with one null byte appended to key
    264      * and value.
    265      */
    266     if (dict->flags & DICT_FLAG_TRY1NULL) {
    267 	dbm_key.dptr = (void *) name;
    268 	dbm_key.dsize = strlen(name) + 1;
    269 	sdbm_clearerr(dict_sdbm->dbm);
    270 	if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) {
    271 	    if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */
    272 		msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name);
    273 	    status = 1;				/* not found */
    274 	} else {
    275 	    dict->flags &= ~DICT_FLAG_TRY0NULL;	/* found */
    276 	}
    277     }
    278 
    279     /*
    280      * See if this DBM file was written with no null byte appended to key and
    281      * value.
    282      */
    283     if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
    284 	dbm_key.dptr = (void *) name;
    285 	dbm_key.dsize = strlen(name);
    286 	sdbm_clearerr(dict_sdbm->dbm);
    287 	if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) {
    288 	    if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */
    289 		msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name);
    290 	    status = 1;				/* not found */
    291 	} else {
    292 	    dict->flags &= ~DICT_FLAG_TRY1NULL;	/* found */
    293 	}
    294     }
    295 
    296     /*
    297      * Release the exclusive lock.
    298      */
    299     if ((dict->flags & DICT_FLAG_LOCK)
    300 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
    301 	msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
    302 
    303     return (status);
    304 }
    305 
    306 /* traverse the dictionary */
    307 
    308 static int dict_sdbm_sequence(DICT *dict, const int function,
    309 			              const char **key, const char **value)
    310 {
    311     const char *myname = "dict_sdbm_sequence";
    312     DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
    313     datum   dbm_key;
    314     datum   dbm_value;
    315     int     status;
    316 
    317     dict->error = 0;
    318 
    319     /*
    320      * Acquire a shared lock.
    321      */
    322     if ((dict->flags & DICT_FLAG_LOCK)
    323 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
    324 	msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
    325 
    326     /*
    327      * Determine and execute the seek function. It returns the key.
    328      */
    329     sdbm_clearerr(dict_sdbm->dbm);
    330     switch (function) {
    331     case DICT_SEQ_FUN_FIRST:
    332 	dbm_key = sdbm_firstkey(dict_sdbm->dbm);
    333 	break;
    334     case DICT_SEQ_FUN_NEXT:
    335 	dbm_key = sdbm_nextkey(dict_sdbm->dbm);
    336 	break;
    337     default:
    338 	msg_panic("%s: invalid function: %d", myname, function);
    339     }
    340 
    341     if (dbm_key.dptr != 0 && dbm_key.dsize > 0) {
    342 
    343 	/*
    344 	 * Copy the key so that it is guaranteed null terminated.
    345 	 */
    346 	*key = SCOPY(dict_sdbm->key_buf, dbm_key.dptr, dbm_key.dsize);
    347 
    348 	/*
    349 	 * Fetch the corresponding value.
    350 	 */
    351 	dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
    352 
    353 	if (dbm_value.dptr != 0 && dbm_value.dsize > 0) {
    354 
    355 	    /*
    356 	     * Copy the value so that it is guaranteed null terminated.
    357 	     */
    358 	    *value = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
    359 	    status = 0;
    360 	} else {
    361 
    362 	    /*
    363 	     * Determine if we have hit the last record or an error
    364 	     * condition.
    365 	     */
    366 	    if (sdbm_error(dict_sdbm->dbm))
    367 		msg_fatal("error seeking %s: %m", dict_sdbm->dict.name);
    368 	    status = 1;				/* no error: eof/not found
    369 						 * (should not happen!) */
    370 	}
    371     } else {
    372 
    373 	/*
    374 	 * Determine if we have hit the last record or an error condition.
    375 	 */
    376 	if (sdbm_error(dict_sdbm->dbm))
    377 	    msg_fatal("error seeking %s: %m", dict_sdbm->dict.name);
    378 	status = 1;				/* no error: eof/not found */
    379     }
    380 
    381     /*
    382      * Release the shared lock.
    383      */
    384     if ((dict->flags & DICT_FLAG_LOCK)
    385 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
    386 	msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
    387 
    388     return (status);
    389 }
    390 
    391 /* dict_sdbm_close - disassociate from data base */
    392 
    393 static void dict_sdbm_close(DICT *dict)
    394 {
    395     DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
    396 
    397     sdbm_close(dict_sdbm->dbm);
    398     if (dict_sdbm->key_buf)
    399 	vstring_free(dict_sdbm->key_buf);
    400     if (dict_sdbm->val_buf)
    401 	vstring_free(dict_sdbm->val_buf);
    402     if (dict->fold_buf)
    403 	vstring_free(dict->fold_buf);
    404     dict_free(dict);
    405 }
    406 
    407 /* dict_sdbm_open - open SDBM data base */
    408 
    409 DICT   *dict_sdbm_open(const char *path, int open_flags, int dict_flags)
    410 {
    411     DICT_SDBM *dict_sdbm;
    412     struct stat st;
    413     SDBM   *dbm;
    414     char   *dbm_path;
    415     int     lock_fd;
    416 
    417     /*
    418      * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
    419      * the time domain) locking while accessing individual database records.
    420      *
    421      * Programs such as postmap/postalias use their own large-grained (in the
    422      * time domain) locks while rewriting the entire file.
    423      */
    424     if (dict_flags & DICT_FLAG_LOCK) {
    425 	dbm_path = concatenate(path, ".dir", (char *) 0);
    426 	if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0)
    427 	    msg_fatal("open database %s: %m", dbm_path);
    428 	if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
    429 	    msg_fatal("shared-lock database %s for open: %m", dbm_path);
    430     }
    431 
    432     /*
    433      * XXX sdbm_open() has no const in prototype.
    434      */
    435     if ((dbm = sdbm_open((char *) path, open_flags, 0644)) == 0)
    436 	msg_fatal("open database %s.{dir,pag}: %m", path);
    437 
    438     if (dict_flags & DICT_FLAG_LOCK) {
    439 	if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
    440 	    msg_fatal("unlock database %s for open: %m", dbm_path);
    441 	if (close(lock_fd) < 0)
    442 	    msg_fatal("close database %s: %m", dbm_path);
    443     }
    444     dict_sdbm = (DICT_SDBM *) dict_alloc(DICT_TYPE_SDBM, path, sizeof(*dict_sdbm));
    445     dict_sdbm->dict.lookup = dict_sdbm_lookup;
    446     dict_sdbm->dict.update = dict_sdbm_update;
    447     dict_sdbm->dict.delete = dict_sdbm_delete;
    448     dict_sdbm->dict.sequence = dict_sdbm_sequence;
    449     dict_sdbm->dict.close = dict_sdbm_close;
    450     dict_sdbm->dict.lock_fd = sdbm_dirfno(dbm);
    451     dict_sdbm->dict.stat_fd = sdbm_pagfno(dbm);
    452     if (fstat(dict_sdbm->dict.stat_fd, &st) < 0)
    453 	msg_fatal("dict_sdbm_open: fstat: %m");
    454     if (open_flags == O_RDONLY)
    455 	dict_sdbm->dict.mtime = st.st_mtime;
    456     dict_sdbm->dict.owner.uid = st.st_uid;
    457     dict_sdbm->dict.owner.status = (st.st_uid != 0);
    458 
    459     /*
    460      * Warn if the source file is newer than the indexed file, except when
    461      * the source file changed only seconds ago.
    462      */
    463     if ((dict_flags & DICT_FLAG_LOCK) != 0
    464 	&& open_flags == O_RDONLY
    465 	&& stat(path, &st) == 0
    466 	&& st.st_mtime > dict_sdbm->dict.mtime
    467 	&& st.st_mtime < time((time_t *) 0) - 100)
    468 	msg_warn("database %s is older than source file %s", dbm_path, path);
    469 
    470     close_on_exec(sdbm_pagfno(dbm), CLOSE_ON_EXEC);
    471     close_on_exec(sdbm_dirfno(dbm), CLOSE_ON_EXEC);
    472     dict_sdbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
    473     if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
    474 	dict_sdbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
    475     if (dict_flags & DICT_FLAG_FOLD_FIX)
    476 	dict_sdbm->dict.fold_buf = vstring_alloc(10);
    477     dict_sdbm->dbm = dbm;
    478     dict_sdbm->key_buf = 0;
    479     dict_sdbm->val_buf = 0;
    480 
    481     if ((dict_flags & DICT_FLAG_LOCK))
    482 	myfree(dbm_path);
    483 
    484     return (&dict_sdbm->dict);
    485 }
    486 
    487 #endif
    488