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