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