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