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