1/*	$NetBSD$	*/
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
63typedef 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
75static 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
147static 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
231static 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
308static 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
393static 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
409DICT   *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    dict_sdbm->dict.mtime = st.st_mtime;
455    dict_sdbm->dict.owner.uid = st.st_uid;
456    dict_sdbm->dict.owner.status = (st.st_uid != 0);
457
458    /*
459     * Warn if the source file is newer than the indexed file, except when
460     * the source file changed only seconds ago.
461     */
462    if ((dict_flags & DICT_FLAG_LOCK) != 0
463	&& stat(path, &st) == 0
464	&& st.st_mtime > dict_sdbm->dict.mtime
465	&& st.st_mtime < time((time_t *) 0) - 100)
466	msg_warn("database %s is older than source file %s", dbm_path, path);
467
468    close_on_exec(sdbm_pagfno(dbm), CLOSE_ON_EXEC);
469    close_on_exec(sdbm_dirfno(dbm), CLOSE_ON_EXEC);
470    dict_sdbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
471    if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
472	dict_sdbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
473    if (dict_flags & DICT_FLAG_FOLD_FIX)
474	dict_sdbm->dict.fold_buf = vstring_alloc(10);
475    dict_sdbm->dbm = dbm;
476    dict_sdbm->key_buf = 0;
477    dict_sdbm->val_buf = 0;
478
479    if ((dict_flags & DICT_FLAG_LOCK))
480	myfree(dbm_path);
481
482    return (DICT_DEBUG (&dict_sdbm->dict));
483}
484
485#endif
486