1/*++
2/* NAME
3/*	dict_db 3
4/* SUMMARY
5/*	dictionary manager interface to DB files
6/* SYNOPSIS
7/*	#include <dict_db.h>
8/*
9/*	int	dict_db_cache_size;
10/*
11/*	DICT	*dict_hash_open(path, open_flags, dict_flags)
12/*	const char *path;
13/*	int	open_flags;
14/*	int	dict_flags;
15/*
16/*	DICT	*dict_btree_open(path, open_flags, dict_flags)
17/*	const char *path;
18/*	int	open_flags;
19/*	int	dict_flags;
20/* DESCRIPTION
21/*	dict_XXX_open() opens the specified DB database.  The result is
22/*	a pointer to a structure that can be used to access the dictionary
23/*	using the generic methods documented in dict_open(3).
24/*
25/*	The dict_db_cache_size variable specifies a non-default per-table
26/*	I/O buffer size.  The default buffer size is adequate for reading.
27/*	For better performance while creating a large table, specify a large
28/*	buffer size before opening the file.
29/*
30/*	Arguments:
31/* .IP path
32/*	The database pathname, not including the ".db" suffix.
33/* .IP open_flags
34/*	Flags passed to dbopen().
35/* .IP dict_flags
36/*	Flags used by the dictionary interface.
37/* SEE ALSO
38/*	dict(3) generic dictionary manager
39/* DIAGNOSTICS
40/*	Fatal errors: cannot open file, write error, out of memory.
41/* LICENSE
42/* .ad
43/* .fi
44/*	The Secure Mailer license must be distributed with this software.
45/* AUTHOR(S)
46/*	Wietse Venema
47/*	IBM T.J. Watson Research
48/*	P.O. Box 704
49/*	Yorktown Heights, NY 10598, USA
50/*--*/
51
52#include "sys_defs.h"
53
54#ifdef HAS_DB
55
56/* System library. */
57
58#include <sys/stat.h>
59#include <limits.h>
60#ifdef PATH_DB_H
61#include PATH_DB_H
62#else
63#include <db.h>
64#endif
65#include <string.h>
66#include <unistd.h>
67#include <errno.h>
68
69#if defined(_DB_185_H_) && defined(USE_FCNTL_LOCK)
70#error "Error: this system must not use the db 1.85 compatibility interface"
71#endif
72
73#ifndef DB_VERSION_MAJOR
74#define DB_VERSION_MAJOR 1
75#define DICT_DB_GET(db, key, val, flag)	db->get(db, key, val, flag)
76#define DICT_DB_PUT(db, key, val, flag)	db->put(db, key, val, flag)
77#define DICT_DB_DEL(db, key, flag)	db->del(db, key, flag)
78#define DICT_DB_SYNC(db, flag)		db->sync(db, flag)
79#define DICT_DB_CLOSE(db)		db->close(db)
80#define DONT_CLOBBER			R_NOOVERWRITE
81#endif
82
83#if DB_VERSION_MAJOR > 1
84#define DICT_DB_GET(db, key, val, flag)	sanitize(db->get(db, 0, key, val, flag))
85#define DICT_DB_PUT(db, key, val, flag)	sanitize(db->put(db, 0, key, val, flag))
86#define DICT_DB_DEL(db, key, flag)	sanitize(db->del(db, 0, key, flag))
87#define DICT_DB_SYNC(db, flag)		((errno = db->sync(db, flag)) ? -1 : 0)
88#define DICT_DB_CLOSE(db)		((errno = db->close(db, 0)) ? -1 : 0)
89#define DONT_CLOBBER			DB_NOOVERWRITE
90#endif
91
92#if (DB_VERSION_MAJOR == 2 && DB_VERSION_MINOR < 6)
93#define DICT_DB_CURSOR(db, curs)	(db)->cursor((db), NULL, (curs))
94#else
95#define DICT_DB_CURSOR(db, curs)	(db)->cursor((db), NULL, (curs), 0)
96#endif
97
98#ifndef DB_FCNTL_LOCKING
99#define DB_FCNTL_LOCKING		0
100#endif
101
102/* Utility library. */
103
104#include "msg.h"
105#include "mymalloc.h"
106#include "vstring.h"
107#include "stringops.h"
108#include "iostuff.h"
109#include "myflock.h"
110#include "dict.h"
111#include "dict_db.h"
112#include "warn_stat.h"
113
114/* Application-specific. */
115
116typedef struct {
117    DICT    dict;			/* generic members */
118    DB     *db;				/* open db file */
119#if DB_VERSION_MAJOR > 1
120    DBC    *cursor;			/* dict_db_sequence() */
121#endif
122    VSTRING *key_buf;			/* key result */
123    VSTRING *val_buf;			/* value result */
124} DICT_DB;
125
126#define SCOPY(buf, data, size) \
127    vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
128
129 /*
130  * You can override the default dict_db_cache_size setting before calling
131  * dict_hash_open() or dict_btree_open(). This is done in mkmap_db_open() to
132  * set a larger memory pool for database (re)builds.
133  *
134  * XXX This should be specified via the DICT interface so that it becomes an
135  * object property, instead of being specified by poking a global variable
136  * so that it becomes a class property.
137  */
138int     dict_db_cache_size = (128 * 1024);	/* 128K default memory pool */
139
140#define DICT_DB_NELM		4096
141
142#if DB_VERSION_MAJOR > 1
143
144/* sanitize - sanitize db_get/put/del result */
145
146static int sanitize(int status)
147{
148
149    /*
150     * XXX This is unclean but avoids a lot of clutter elsewhere. Categorize
151     * results into non-fatal errors (i.e., errors that we can deal with),
152     * success, or fatal error (i.e., all other errors).
153     */
154    switch (status) {
155
156	case DB_NOTFOUND:		/* get, del */
157	case DB_KEYEXIST:		/* put */
158	return (1);			/* non-fatal */
159
160    case 0:
161	return (0);				/* success */
162
163    case DB_KEYEMPTY:				/* get, others? */
164	status = EINVAL;
165	/* FALLTHROUGH */
166    default:
167	errno = status;
168	return (-1);				/* fatal */
169    }
170}
171
172#endif
173
174/* dict_db_lookup - find database entry */
175
176static const char *dict_db_lookup(DICT *dict, const char *name)
177{
178    DICT_DB *dict_db = (DICT_DB *) dict;
179    DB     *db = dict_db->db;
180    DBT     db_key;
181    DBT     db_value;
182    int     status;
183    const char *result = 0;
184
185    dict->error = 0;
186
187    /*
188     * Sanity check.
189     */
190    if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
191	msg_panic("dict_db_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
192
193    memset(&db_key, 0, sizeof(db_key));
194    memset(&db_value, 0, sizeof(db_value));
195
196    /*
197     * Optionally fold the key.
198     */
199    if (dict->flags & DICT_FLAG_FOLD_FIX) {
200	if (dict->fold_buf == 0)
201	    dict->fold_buf = vstring_alloc(10);
202	vstring_strcpy(dict->fold_buf, name);
203	name = lowercase(vstring_str(dict->fold_buf));
204    }
205
206    /*
207     * Acquire a shared lock.
208     */
209    if ((dict->flags & DICT_FLAG_LOCK)
210	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
211	msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
212
213    /*
214     * See if this DB file was written with one null byte appended to key and
215     * value.
216     */
217    if (dict->flags & DICT_FLAG_TRY1NULL) {
218	db_key.data = (void *) name;
219	db_key.size = strlen(name) + 1;
220	if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0)
221	    msg_fatal("error reading %s: %m", dict_db->dict.name);
222	if (status == 0) {
223	    dict->flags &= ~DICT_FLAG_TRY0NULL;
224	    result = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
225	}
226    }
227
228    /*
229     * See if this DB file was written with no null byte appended to key and
230     * value.
231     */
232    if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
233	db_key.data = (void *) name;
234	db_key.size = strlen(name);
235	if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0)
236	    msg_fatal("error reading %s: %m", dict_db->dict.name);
237	if (status == 0) {
238	    dict->flags &= ~DICT_FLAG_TRY1NULL;
239	    result = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
240	}
241    }
242
243    /*
244     * Release the shared lock.
245     */
246    if ((dict->flags & DICT_FLAG_LOCK)
247	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
248	msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
249
250    return (result);
251}
252
253/* dict_db_update - add or update database entry */
254
255static int dict_db_update(DICT *dict, const char *name, const char *value)
256{
257    DICT_DB *dict_db = (DICT_DB *) dict;
258    DB     *db = dict_db->db;
259    DBT     db_key;
260    DBT     db_value;
261    int     status;
262
263    dict->error = 0;
264
265    /*
266     * Sanity check.
267     */
268    if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
269	msg_panic("dict_db_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
270
271    /*
272     * Optionally fold the key.
273     */
274    if (dict->flags & DICT_FLAG_FOLD_FIX) {
275	if (dict->fold_buf == 0)
276	    dict->fold_buf = vstring_alloc(10);
277	vstring_strcpy(dict->fold_buf, name);
278	name = lowercase(vstring_str(dict->fold_buf));
279    }
280    memset(&db_key, 0, sizeof(db_key));
281    memset(&db_value, 0, sizeof(db_value));
282    db_key.data = (void *) name;
283    db_value.data = (void *) value;
284    db_key.size = strlen(name);
285    db_value.size = strlen(value);
286
287    /*
288     * If undecided about appending a null byte to key and value, choose a
289     * default depending on the platform.
290     */
291    if ((dict->flags & DICT_FLAG_TRY1NULL)
292	&& (dict->flags & DICT_FLAG_TRY0NULL)) {
293#ifdef DB_NO_TRAILING_NULL
294	dict->flags &= ~DICT_FLAG_TRY1NULL;
295#else
296	dict->flags &= ~DICT_FLAG_TRY0NULL;
297#endif
298    }
299
300    /*
301     * Optionally append a null byte to key and value.
302     */
303    if (dict->flags & DICT_FLAG_TRY1NULL) {
304	db_key.size++;
305	db_value.size++;
306    }
307
308    /*
309     * Acquire an exclusive lock.
310     */
311    if ((dict->flags & DICT_FLAG_LOCK)
312	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
313	msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
314
315    /*
316     * Do the update.
317     */
318    if ((status = DICT_DB_PUT(db, &db_key, &db_value,
319	     (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : DONT_CLOBBER)) < 0)
320	msg_fatal("error writing %s: %m", dict_db->dict.name);
321    if (status) {
322	if (dict->flags & DICT_FLAG_DUP_IGNORE)
323	     /* void */ ;
324	else if (dict->flags & DICT_FLAG_DUP_WARN)
325	    msg_warn("%s: duplicate entry: \"%s\"", dict_db->dict.name, name);
326	else
327	    msg_fatal("%s: duplicate entry: \"%s\"", dict_db->dict.name, name);
328    }
329    if (dict->flags & DICT_FLAG_SYNC_UPDATE)
330	if (DICT_DB_SYNC(db, 0) < 0)
331	    msg_fatal("%s: flush dictionary: %m", dict_db->dict.name);
332
333    /*
334     * Release the exclusive lock.
335     */
336    if ((dict->flags & DICT_FLAG_LOCK)
337	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
338	msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
339
340    return (status);
341}
342
343/* delete one entry from the dictionary */
344
345static int dict_db_delete(DICT *dict, const char *name)
346{
347    DICT_DB *dict_db = (DICT_DB *) dict;
348    DB     *db = dict_db->db;
349    DBT     db_key;
350    int     status = 1;
351    int     flags = 0;
352
353    dict->error = 0;
354
355    /*
356     * Sanity check.
357     */
358    if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
359	msg_panic("dict_db_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
360
361    /*
362     * Optionally fold the key.
363     */
364    if (dict->flags & DICT_FLAG_FOLD_FIX) {
365	if (dict->fold_buf == 0)
366	    dict->fold_buf = vstring_alloc(10);
367	vstring_strcpy(dict->fold_buf, name);
368	name = lowercase(vstring_str(dict->fold_buf));
369    }
370    memset(&db_key, 0, sizeof(db_key));
371
372    /*
373     * Acquire an exclusive lock.
374     */
375    if ((dict->flags & DICT_FLAG_LOCK)
376	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
377	msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
378
379    /*
380     * See if this DB file was written with one null byte appended to key and
381     * value.
382     */
383    if (dict->flags & DICT_FLAG_TRY1NULL) {
384	db_key.data = (void *) name;
385	db_key.size = strlen(name) + 1;
386	if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0)
387	    msg_fatal("error deleting from %s: %m", dict_db->dict.name);
388	if (status == 0)
389	    dict->flags &= ~DICT_FLAG_TRY0NULL;
390    }
391
392    /*
393     * See if this DB file was written with no null byte appended to key and
394     * value.
395     */
396    if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
397	db_key.data = (void *) name;
398	db_key.size = strlen(name);
399	if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0)
400	    msg_fatal("error deleting from %s: %m", dict_db->dict.name);
401	if (status == 0)
402	    dict->flags &= ~DICT_FLAG_TRY1NULL;
403    }
404    if (dict->flags & DICT_FLAG_SYNC_UPDATE)
405	if (DICT_DB_SYNC(db, 0) < 0)
406	    msg_fatal("%s: flush dictionary: %m", dict_db->dict.name);
407
408    /*
409     * Release the exclusive lock.
410     */
411    if ((dict->flags & DICT_FLAG_LOCK)
412	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
413	msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
414
415    return status;
416}
417
418/* dict_db_sequence - traverse the dictionary */
419
420static int dict_db_sequence(DICT *dict, int function,
421			            const char **key, const char **value)
422{
423    const char *myname = "dict_db_sequence";
424    DICT_DB *dict_db = (DICT_DB *) dict;
425    DB     *db = dict_db->db;
426    DBT     db_key;
427    DBT     db_value;
428    int     status = 0;
429    int     db_function;
430
431    dict->error = 0;
432
433#if DB_VERSION_MAJOR > 1
434
435    /*
436     * Initialize.
437     */
438    memset(&db_key, 0, sizeof(db_key));
439    memset(&db_value, 0, sizeof(db_value));
440
441    /*
442     * Determine the function.
443     */
444    switch (function) {
445    case DICT_SEQ_FUN_FIRST:
446	if (dict_db->cursor == 0)
447	    DICT_DB_CURSOR(db, &(dict_db->cursor));
448	db_function = DB_FIRST;
449	break;
450    case DICT_SEQ_FUN_NEXT:
451	if (dict_db->cursor == 0)
452	    msg_panic("%s: no cursor", myname);
453	db_function = DB_NEXT;
454	break;
455    default:
456	msg_panic("%s: invalid function %d", myname, function);
457    }
458
459    /*
460     * Acquire a shared lock.
461     */
462    if ((dict->flags & DICT_FLAG_LOCK)
463	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
464	msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
465
466    /*
467     * Database lookup.
468     */
469    status =
470	dict_db->cursor->c_get(dict_db->cursor, &db_key, &db_value, db_function);
471    if (status != 0 && status != DB_NOTFOUND)
472	msg_fatal("error [%d] seeking %s: %m", status, dict_db->dict.name);
473
474    /*
475     * Release the shared lock.
476     */
477    if ((dict->flags & DICT_FLAG_LOCK)
478	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
479	msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
480
481    if (status == 0) {
482
483	/*
484	 * Copy the result so it is guaranteed null terminated.
485	 */
486	*key = SCOPY(dict_db->key_buf, db_key.data, db_key.size);
487	*value = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
488    }
489    return (status);
490#else
491
492    /*
493     * determine the function
494     */
495    switch (function) {
496    case DICT_SEQ_FUN_FIRST:
497	db_function = R_FIRST;
498	break;
499    case DICT_SEQ_FUN_NEXT:
500	db_function = R_NEXT;
501	break;
502    default:
503	msg_panic("%s: invalid function %d", myname, function);
504    }
505
506    /*
507     * Acquire a shared lock.
508     */
509    if ((dict->flags & DICT_FLAG_LOCK)
510	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
511	msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
512
513    if ((status = db->seq(db, &db_key, &db_value, db_function)) < 0)
514	msg_fatal("error seeking %s: %m", dict_db->dict.name);
515
516    /*
517     * Release the shared lock.
518     */
519    if ((dict->flags & DICT_FLAG_LOCK)
520	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
521	msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
522
523    if (status == 0) {
524
525	/*
526	 * Copy the result so that it is guaranteed null terminated.
527	 */
528	*key = SCOPY(dict_db->key_buf, db_key.data, db_key.size);
529	*value = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
530    }
531    return status;
532#endif
533}
534
535/* dict_db_close - close data base */
536
537static void dict_db_close(DICT *dict)
538{
539    DICT_DB *dict_db = (DICT_DB *) dict;
540
541#if DB_VERSION_MAJOR > 1
542    if (dict_db->cursor)
543	dict_db->cursor->c_close(dict_db->cursor);
544#endif
545    if (DICT_DB_SYNC(dict_db->db, 0) < 0)
546	msg_fatal("flush database %s: %m", dict_db->dict.name);
547
548    /*
549     * With some Berkeley DB implementations, close fails with a bogus ENOENT
550     * error, while it reports no errors with put+sync, no errors with
551     * del+sync, and no errors with the sync operation just before this
552     * comment. This happens in programs that never fork and that never share
553     * the database with other processes. The bogus close error has been
554     * reported for programs that use the first/next iterator. Instead of
555     * making Postfix look bad because it reports errors that other programs
556     * ignore, I'm going to report the bogus error as a non-error.
557     */
558    if (DICT_DB_CLOSE(dict_db->db) < 0)
559	msg_info("close database %s: %m (possible Berkeley DB bug)",
560		 dict_db->dict.name);
561    if (dict_db->key_buf)
562	vstring_free(dict_db->key_buf);
563    if (dict_db->val_buf)
564	vstring_free(dict_db->val_buf);
565    if (dict->fold_buf)
566	vstring_free(dict->fold_buf);
567    dict_free(dict);
568}
569
570/* dict_db_open - open data base */
571
572static DICT *dict_db_open(const char *class, const char *path, int open_flags,
573			          int type, void *tweak, int dict_flags)
574{
575    DICT_DB *dict_db;
576    struct stat st;
577    DB     *db = 0;
578    char   *db_path = 0;
579    int     lock_fd = -1;
580    int     dbfd;
581
582#if DB_VERSION_MAJOR > 1
583    int     db_flags;
584
585#endif
586
587    /*
588     * Mismatches between #include file and library are a common cause for
589     * trouble.
590     */
591#if DB_VERSION_MAJOR > 1
592    int     major_version;
593    int     minor_version;
594    int     patch_version;
595
596    (void) db_version(&major_version, &minor_version, &patch_version);
597    if (major_version != DB_VERSION_MAJOR || minor_version != DB_VERSION_MINOR)
598	return (dict_surrogate(class, path, open_flags, dict_flags,
599			       "incorrect version of Berkeley DB: "
600	      "compiled against %d.%d.%d, run-time linked against %d.%d.%d",
601		       DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH,
602			       major_version, minor_version, patch_version));
603    if (msg_verbose) {
604	msg_info("Compiled against Berkeley DB: %d.%d.%d\n",
605		 DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH);
606	msg_info("Run-time linked against Berkeley DB: %d.%d.%d\n",
607		 major_version, minor_version, patch_version);
608    }
609#else
610    if (msg_verbose)
611	msg_info("Compiled against Berkeley DB version 1");
612#endif
613
614    db_path = concatenate(path, ".db", (char *) 0);
615
616    /*
617     * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
618     * the time domain) locking while accessing individual database records.
619     *
620     * Programs such as postmap/postalias use their own large-grained (in the
621     * time domain) locks while rewriting the entire file.
622     *
623     * XXX DB version 4.1 will not open a zero-length file. This means we must
624     * open an existing file without O_CREAT|O_TRUNC, and that we must let
625     * db_open() create a non-existent file for us.
626     */
627#define LOCK_OPEN_FLAGS(f) ((f) & ~(O_CREAT|O_TRUNC))
628#define FREE_RETURN(e) do { \
629	DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \
630	if (db_path) myfree(db_path); return (_dict); \
631    } while (0)
632
633    if (dict_flags & DICT_FLAG_LOCK) {
634	if ((lock_fd = open(db_path, LOCK_OPEN_FLAGS(open_flags), 0644)) < 0) {
635	    if (errno != ENOENT)
636		FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
637					   "open database %s: %m", db_path));
638	} else {
639	    if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
640		msg_fatal("shared-lock database %s for open: %m", db_path);
641	}
642    }
643
644    /*
645     * Use the DB 1.x programming interface. This is the default interface
646     * with 4.4BSD systems. It is also available via the db_185 compatibility
647     * interface, but that interface does not have the undocumented feature
648     * that we need to make file locking safe with POSIX fcntl() locking.
649     */
650#if DB_VERSION_MAJOR < 2
651    if ((db = dbopen(db_path, open_flags, 0644, type, tweak)) == 0)
652	FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
653				   "open database %s: %m", db_path));
654    dbfd = db->fd(db);
655#endif
656
657    /*
658     * Use the DB 2.x programming interface. Jump a couple extra hoops.
659     */
660#if DB_VERSION_MAJOR == 2
661    db_flags = DB_FCNTL_LOCKING;
662    if (open_flags == O_RDONLY)
663	db_flags |= DB_RDONLY;
664    if (open_flags & O_CREAT)
665	db_flags |= DB_CREATE;
666    if (open_flags & O_TRUNC)
667	db_flags |= DB_TRUNCATE;
668    if ((errno = db_open(db_path, type, db_flags, 0644, 0, tweak, &db)) != 0)
669	FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
670				   "open database %s: %m", db_path));
671    if (db == 0)
672	msg_panic("db_open null result");
673    if ((errno = db->fd(db, &dbfd)) != 0)
674	msg_fatal("get database file descriptor: %m");
675#endif
676
677    /*
678     * Use the DB 3.x programming interface. Jump even more hoops.
679     */
680#if DB_VERSION_MAJOR > 2
681    db_flags = DB_FCNTL_LOCKING;
682    if (open_flags == O_RDONLY)
683	db_flags |= DB_RDONLY;
684    if (open_flags & O_CREAT)
685	db_flags |= DB_CREATE;
686    if (open_flags & O_TRUNC)
687	db_flags |= DB_TRUNCATE;
688    if ((errno = db_create(&db, 0, 0)) != 0)
689	msg_fatal("create DB database: %m");
690    if (db == 0)
691	msg_panic("db_create null result");
692    if ((errno = db->set_cachesize(db, 0, dict_db_cache_size, 0)) != 0)
693	msg_fatal("set DB cache size %d: %m", dict_db_cache_size);
694    if (type == DB_HASH && db->set_h_nelem(db, DICT_DB_NELM) != 0)
695	msg_fatal("set DB hash element count %d: %m", DICT_DB_NELM);
696#if DB_VERSION_MAJOR == 5 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0)
697    if ((errno = db->open(db, 0, db_path, 0, type, db_flags, 0644)) != 0)
698	FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
699				   "open database %s: %m", db_path));
700#elif (DB_VERSION_MAJOR == 3 || DB_VERSION_MAJOR == 4)
701    if ((errno = db->open(db, db_path, 0, type, db_flags, 0644)) != 0)
702	FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
703				   "open database %s: %m", db_path));
704#else
705#error "Unsupported Berkeley DB version"
706#endif
707    if ((errno = db->fd(db, &dbfd)) != 0)
708	msg_fatal("get database file descriptor: %m");
709#endif
710    if ((dict_flags & DICT_FLAG_LOCK) && lock_fd >= 0) {
711	if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
712	    msg_fatal("unlock database %s for open: %m", db_path);
713	if (close(lock_fd) < 0)
714	    msg_fatal("close database %s: %m", db_path);
715    }
716    dict_db = (DICT_DB *) dict_alloc(class, db_path, sizeof(*dict_db));
717    dict_db->dict.lookup = dict_db_lookup;
718    dict_db->dict.update = dict_db_update;
719    dict_db->dict.delete = dict_db_delete;
720    dict_db->dict.sequence = dict_db_sequence;
721    dict_db->dict.close = dict_db_close;
722    dict_db->dict.lock_fd = dbfd;
723    dict_db->dict.stat_fd = dbfd;
724    if (fstat(dict_db->dict.stat_fd, &st) < 0)
725	msg_fatal("dict_db_open: fstat: %m");
726    dict_db->dict.mtime = st.st_mtime;
727    dict_db->dict.owner.uid = st.st_uid;
728    dict_db->dict.owner.status = (st.st_uid != 0);
729
730    /*
731     * Warn if the source file is newer than the indexed file, except when
732     * the source file changed only seconds ago.
733     */
734    if ((dict_flags & DICT_FLAG_LOCK) != 0
735	&& stat(path, &st) == 0
736	&& st.st_mtime > dict_db->dict.mtime
737	&& st.st_mtime < time((time_t *) 0) - 100)
738	msg_warn("database %s is older than source file %s", db_path, path);
739
740    close_on_exec(dict_db->dict.lock_fd, CLOSE_ON_EXEC);
741    close_on_exec(dict_db->dict.stat_fd, CLOSE_ON_EXEC);
742    dict_db->dict.flags = dict_flags | DICT_FLAG_FIXED;
743    if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
744	dict_db->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL);
745    if (dict_flags & DICT_FLAG_FOLD_FIX)
746	dict_db->dict.fold_buf = vstring_alloc(10);
747    dict_db->db = db;
748#if DB_VERSION_MAJOR > 1
749    dict_db->cursor = 0;
750#endif
751    dict_db->key_buf = 0;
752    dict_db->val_buf = 0;
753
754    myfree(db_path);
755    return (DICT_DEBUG (&dict_db->dict));
756}
757
758/* dict_hash_open - create association with data base */
759
760DICT   *dict_hash_open(const char *path, int open_flags, int dict_flags)
761{
762#if DB_VERSION_MAJOR < 2
763    HASHINFO tweak;
764
765    memset((char *) &tweak, 0, sizeof(tweak));
766    tweak.nelem = DICT_DB_NELM;
767    tweak.cachesize = dict_db_cache_size;
768#endif
769#if DB_VERSION_MAJOR == 2
770    DB_INFO tweak;
771
772    memset((char *) &tweak, 0, sizeof(tweak));
773    tweak.h_nelem = DICT_DB_NELM;
774    tweak.db_cachesize = dict_db_cache_size;
775#endif
776#if DB_VERSION_MAJOR > 2
777    void   *tweak;
778
779    tweak = 0;
780#endif
781    return (dict_db_open(DICT_TYPE_HASH, path, open_flags, DB_HASH,
782			 (void *) &tweak, dict_flags));
783}
784
785/* dict_btree_open - create association with data base */
786
787DICT   *dict_btree_open(const char *path, int open_flags, int dict_flags)
788{
789#if DB_VERSION_MAJOR < 2
790    BTREEINFO tweak;
791
792    memset((char *) &tweak, 0, sizeof(tweak));
793    tweak.cachesize = dict_db_cache_size;
794#endif
795#if DB_VERSION_MAJOR == 2
796    DB_INFO tweak;
797
798    memset((char *) &tweak, 0, sizeof(tweak));
799    tweak.db_cachesize = dict_db_cache_size;
800#endif
801#if DB_VERSION_MAJOR > 2
802    void   *tweak;
803
804    tweak = 0;
805#endif
806
807    return (dict_db_open(DICT_TYPE_BTREE, path, open_flags, DB_BTREE,
808			 (void *) &tweak, dict_flags));
809}
810
811#endif
812