xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_dbm.c (revision 27fd3f6531803adac12382d7643a9a492b576601)
1 /*	$NetBSD: dict_dbm.c,v 1.1.1.1 2009/06/23 10:08:59 tron 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 
66 /* Application-specific. */
67 
68 typedef struct {
69     DICT    dict;			/* generic members */
70     DBM    *dbm;			/* open database */
71     VSTRING *key_buf;			/* key buffer */
72     VSTRING *val_buf;			/* result buffer */
73 } DICT_DBM;
74 
75 #define SCOPY(buf, data, size) \
76     vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
77 
78 /* dict_dbm_lookup - find database entry */
79 
80 static const char *dict_dbm_lookup(DICT *dict, const char *name)
81 {
82     DICT_DBM *dict_dbm = (DICT_DBM *) dict;
83     datum   dbm_key;
84     datum   dbm_value;
85     const char *result = 0;
86 
87     /*
88      * Sanity check.
89      */
90     if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
91 	msg_panic("dict_dbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
92 
93     dict_errno = 0;
94 
95     /*
96      * Optionally fold the key.
97      */
98     if (dict->flags & DICT_FLAG_FOLD_FIX) {
99 	if (dict->fold_buf == 0)
100 	    dict->fold_buf = vstring_alloc(10);
101 	vstring_strcpy(dict->fold_buf, name);
102 	name = lowercase(vstring_str(dict->fold_buf));
103     }
104 
105     /*
106      * Acquire an exclusive lock.
107      */
108     if ((dict->flags & DICT_FLAG_LOCK)
109 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
110 	msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name);
111 
112     /*
113      * See if this DBM file was written with one null byte appended to key
114      * and value.
115      */
116     if (dict->flags & DICT_FLAG_TRY1NULL) {
117 	dbm_key.dptr = (void *) name;
118 	dbm_key.dsize = strlen(name) + 1;
119 	dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key);
120 	if (dbm_value.dptr != 0) {
121 	    dict->flags &= ~DICT_FLAG_TRY0NULL;
122 	    result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize);
123 	}
124     }
125 
126     /*
127      * See if this DBM file was written with no null byte appended to key and
128      * value.
129      */
130     if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
131 	dbm_key.dptr = (void *) name;
132 	dbm_key.dsize = strlen(name);
133 	dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key);
134 	if (dbm_value.dptr != 0) {
135 	    dict->flags &= ~DICT_FLAG_TRY1NULL;
136 	    result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize);
137 	}
138     }
139 
140     /*
141      * Release the exclusive lock.
142      */
143     if ((dict->flags & DICT_FLAG_LOCK)
144 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
145 	msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name);
146 
147     return (result);
148 }
149 
150 /* dict_dbm_update - add or update database entry */
151 
152 static void dict_dbm_update(DICT *dict, const char *name, const char *value)
153 {
154     DICT_DBM *dict_dbm = (DICT_DBM *) dict;
155     datum   dbm_key;
156     datum   dbm_value;
157     int     status;
158 
159     /*
160      * Sanity check.
161      */
162     if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
163 	msg_panic("dict_dbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
164 
165     /*
166      * Optionally fold the key.
167      */
168     if (dict->flags & DICT_FLAG_FOLD_FIX) {
169 	if (dict->fold_buf == 0)
170 	    dict->fold_buf = vstring_alloc(10);
171 	vstring_strcpy(dict->fold_buf, name);
172 	name = lowercase(vstring_str(dict->fold_buf));
173     }
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 
231 /* dict_dbm_delete - delete one entry from the dictionary */
232 
233 static int dict_dbm_delete(DICT *dict, const char *name)
234 {
235     DICT_DBM *dict_dbm = (DICT_DBM *) dict;
236     datum   dbm_key;
237     int     status = 1;
238 
239     /*
240      * Sanity check.
241      */
242     if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
243 	msg_panic("dict_dbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
244 
245     /*
246      * Optionally fold the key.
247      */
248     if (dict->flags & DICT_FLAG_FOLD_FIX) {
249 	if (dict->fold_buf == 0)
250 	    dict->fold_buf = vstring_alloc(10);
251 	vstring_strcpy(dict->fold_buf, name);
252 	name = lowercase(vstring_str(dict->fold_buf));
253     }
254 
255     /*
256      * Acquire an exclusive lock.
257      */
258     if ((dict->flags & DICT_FLAG_LOCK)
259 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
260 	msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name);
261 
262     /*
263      * See if this DBM file was written with one null byte appended to key
264      * and value.
265      */
266     if (dict->flags & DICT_FLAG_TRY1NULL) {
267 	dbm_key.dptr = (void *) name;
268 	dbm_key.dsize = strlen(name) + 1;
269 	dbm_clearerr(dict_dbm->dbm);
270 	if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) {
271 	    if (dbm_error(dict_dbm->dbm) != 0)	/* fatal error */
272 		msg_fatal("error deleting from %s: %m", dict_dbm->dict.name);
273 	    status = 1;				/* not found */
274 	} else {
275 	    dict->flags &= ~DICT_FLAG_TRY0NULL;	/* found */
276 	}
277     }
278 
279     /*
280      * See if this DBM file was written with no null byte appended to key and
281      * value.
282      */
283     if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
284 	dbm_key.dptr = (void *) name;
285 	dbm_key.dsize = strlen(name);
286 	dbm_clearerr(dict_dbm->dbm);
287 	if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) {
288 	    if (dbm_error(dict_dbm->dbm) != 0)	/* fatal error */
289 		msg_fatal("error deleting from %s: %m", dict_dbm->dict.name);
290 	    status = 1;				/* not found */
291 	} else {
292 	    dict->flags &= ~DICT_FLAG_TRY1NULL;	/* found */
293 	}
294     }
295 
296     /*
297      * Release the exclusive lock.
298      */
299     if ((dict->flags & DICT_FLAG_LOCK)
300 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
301 	msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name);
302 
303     return (status);
304 }
305 
306 /* traverse the dictionary */
307 
308 static int dict_dbm_sequence(DICT *dict, int function,
309 			             const char **key, const char **value)
310 {
311     const char *myname = "dict_dbm_sequence";
312     DICT_DBM *dict_dbm = (DICT_DBM *) dict;
313     datum   dbm_key;
314     datum   dbm_value;
315 
316     /*
317      * Acquire a shared lock.
318      */
319     if ((dict->flags & DICT_FLAG_LOCK)
320 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
321 	msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name);
322 
323     /*
324      * Determine and execute the seek function. It returns the key.
325      */
326     switch (function) {
327     case DICT_SEQ_FUN_FIRST:
328 	dbm_key = dbm_firstkey(dict_dbm->dbm);
329 	break;
330     case DICT_SEQ_FUN_NEXT:
331 	dbm_key = dbm_nextkey(dict_dbm->dbm);
332 	break;
333     default:
334 	msg_panic("%s: invalid function: %d", myname, function);
335     }
336 
337     if (dbm_key.dptr != 0 && dbm_key.dsize > 0) {
338 
339 	/*
340 	 * Copy the key so that it is guaranteed null terminated.
341 	 */
342 	*key = SCOPY(dict_dbm->key_buf, dbm_key.dptr, dbm_key.dsize);
343 
344 	/*
345 	 * Fetch the corresponding value.
346 	 */
347 	dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key);
348 
349 	if (dbm_value.dptr != 0 && dbm_value.dsize > 0) {
350 
351 	    /*
352 	     * Copy the value so that it is guaranteed null terminated.
353 	     */
354 	    *value = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize);
355 	} else {
356 
357 	    /*
358 	     * Determine if we have hit the last record or an error
359 	     * condition.
360 	     */
361 	    if (dbm_error(dict_dbm->dbm))
362 		msg_fatal("error seeking %s: %m", dict_dbm->dict.name);
363 	    return (1);				/* no error: eof/not found
364 						 * (should not happen!) */
365 	}
366     } else {
367 
368 	/*
369 	 * Determine if we have hit the last record or an error condition.
370 	 */
371 	if (dbm_error(dict_dbm->dbm))
372 	    msg_fatal("error seeking %s: %m", dict_dbm->dict.name);
373 	return (1);				/* no error: eof/not found */
374     }
375 
376     /*
377      * Release the shared lock.
378      */
379     if ((dict->flags & DICT_FLAG_LOCK)
380 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
381 	msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name);
382 
383     return (0);
384 }
385 
386 /* dict_dbm_close - disassociate from data base */
387 
388 static void dict_dbm_close(DICT *dict)
389 {
390     DICT_DBM *dict_dbm = (DICT_DBM *) dict;
391 
392     dbm_close(dict_dbm->dbm);
393     if (dict_dbm->key_buf)
394 	vstring_free(dict_dbm->key_buf);
395     if (dict_dbm->val_buf)
396 	vstring_free(dict_dbm->val_buf);
397     if (dict->fold_buf)
398 	vstring_free(dict->fold_buf);
399     dict_free(dict);
400 }
401 
402 /* dict_dbm_open - open DBM data base */
403 
404 DICT   *dict_dbm_open(const char *path, int open_flags, int dict_flags)
405 {
406     DICT_DBM *dict_dbm;
407     struct stat st;
408     DBM    *dbm;
409     char   *dbm_path;
410     int     lock_fd;
411 
412     /*
413      * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
414      * the time domain) locking while accessing individual database records.
415      *
416      * Programs such as postmap/postalias use their own large-grained (in the
417      * time domain) locks while rewriting the entire file.
418      */
419     if (dict_flags & DICT_FLAG_LOCK) {
420 	dbm_path = concatenate(path, ".dir", (char *) 0);
421 	if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0)
422 	    msg_fatal("open database %s: %m", dbm_path);
423 	if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
424 	    msg_fatal("shared-lock database %s for open: %m", dbm_path);
425     }
426 
427     /*
428      * XXX SunOS 5.x has no const in dbm_open() prototype.
429      */
430     if ((dbm = dbm_open((char *) path, open_flags, 0644)) == 0)
431 	msg_fatal("open database %s.{dir,pag}: %m", path);
432 
433     if (dict_flags & DICT_FLAG_LOCK) {
434 	if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
435 	    msg_fatal("unlock database %s for open: %m", dbm_path);
436 	if (close(lock_fd) < 0)
437 	    msg_fatal("close database %s: %m", dbm_path);
438     }
439     dict_dbm = (DICT_DBM *) dict_alloc(DICT_TYPE_DBM, path, sizeof(*dict_dbm));
440     dict_dbm->dict.lookup = dict_dbm_lookup;
441     dict_dbm->dict.update = dict_dbm_update;
442     dict_dbm->dict.delete = dict_dbm_delete;
443     dict_dbm->dict.sequence = dict_dbm_sequence;
444     dict_dbm->dict.close = dict_dbm_close;
445     dict_dbm->dict.lock_fd = dbm_dirfno(dbm);
446     dict_dbm->dict.stat_fd = dbm_pagfno(dbm);
447     if (dict_dbm->dict.lock_fd == dict_dbm->dict.stat_fd)
448 	msg_fatal("open database %s: cannot support GDBM", path);
449     if (fstat(dict_dbm->dict.stat_fd, &st) < 0)
450 	msg_fatal("dict_dbm_open: fstat: %m");
451     dict_dbm->dict.mtime = st.st_mtime;
452 
453     /*
454      * Warn if the source file is newer than the indexed file, except when
455      * the source file changed only seconds ago.
456      */
457     if ((dict_flags & DICT_FLAG_LOCK) != 0
458 	&& stat(path, &st) == 0
459 	&& st.st_mtime > dict_dbm->dict.mtime
460 	&& st.st_mtime < time((time_t *) 0) - 100)
461 	msg_warn("database %s is older than source file %s", dbm_path, path);
462 
463     close_on_exec(dbm_pagfno(dbm), CLOSE_ON_EXEC);
464     close_on_exec(dbm_dirfno(dbm), CLOSE_ON_EXEC);
465     dict_dbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
466     if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
467 	dict_dbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
468     if (dict_flags & DICT_FLAG_FOLD_FIX)
469 	dict_dbm->dict.fold_buf = vstring_alloc(10);
470     dict_dbm->dbm = dbm;
471     dict_dbm->key_buf = 0;
472     dict_dbm->val_buf = 0;
473 
474     if ((dict_flags & DICT_FLAG_LOCK))
475 	myfree(dbm_path);
476 
477     return (DICT_DEBUG (&dict_dbm->dict));
478 }
479 
480 #endif
481