xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_dbm.c (revision a5847cc334d9a7029f6352b847e9e8d71a0f9e0c)
1 /*	$NetBSD: dict_dbm.c,v 1.1.1.2 2010/06/17 18:07:12 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     int     status;
316 
317     /*
318      * Acquire a shared lock.
319      */
320     if ((dict->flags & DICT_FLAG_LOCK)
321 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
322 	msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name);
323 
324     /*
325      * Determine and execute the seek function. It returns the key.
326      */
327     switch (function) {
328     case DICT_SEQ_FUN_FIRST:
329 	dbm_key = dbm_firstkey(dict_dbm->dbm);
330 	break;
331     case DICT_SEQ_FUN_NEXT:
332 	dbm_key = dbm_nextkey(dict_dbm->dbm);
333 	break;
334     default:
335 	msg_panic("%s: invalid function: %d", myname, function);
336     }
337 
338     if (dbm_key.dptr != 0 && dbm_key.dsize > 0) {
339 
340 	/*
341 	 * Copy the key so that it is guaranteed null terminated.
342 	 */
343 	*key = SCOPY(dict_dbm->key_buf, dbm_key.dptr, dbm_key.dsize);
344 
345 	/*
346 	 * Fetch the corresponding value.
347 	 */
348 	dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key);
349 
350 	if (dbm_value.dptr != 0 && dbm_value.dsize > 0) {
351 
352 	    /*
353 	     * Copy the value so that it is guaranteed null terminated.
354 	     */
355 	    *value = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize);
356 	    status = 0;
357 	} else {
358 
359 	    /*
360 	     * Determine if we have hit the last record or an error
361 	     * condition.
362 	     */
363 	    if (dbm_error(dict_dbm->dbm))
364 		msg_fatal("error seeking %s: %m", dict_dbm->dict.name);
365 	    status = 1;				/* no error: eof/not found
366 						 * (should not happen!) */
367 	}
368     } else {
369 
370 	/*
371 	 * Determine if we have hit the last record or an error condition.
372 	 */
373 	if (dbm_error(dict_dbm->dbm))
374 	    msg_fatal("error seeking %s: %m", dict_dbm->dict.name);
375 	status = 1;				/* no error: eof/not found */
376     }
377 
378     /*
379      * Release the shared lock.
380      */
381     if ((dict->flags & DICT_FLAG_LOCK)
382 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
383 	msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name);
384 
385     return (status);
386 }
387 
388 /* dict_dbm_close - disassociate from data base */
389 
390 static void dict_dbm_close(DICT *dict)
391 {
392     DICT_DBM *dict_dbm = (DICT_DBM *) dict;
393 
394     dbm_close(dict_dbm->dbm);
395     if (dict_dbm->key_buf)
396 	vstring_free(dict_dbm->key_buf);
397     if (dict_dbm->val_buf)
398 	vstring_free(dict_dbm->val_buf);
399     if (dict->fold_buf)
400 	vstring_free(dict->fold_buf);
401     dict_free(dict);
402 }
403 
404 /* dict_dbm_open - open DBM data base */
405 
406 DICT   *dict_dbm_open(const char *path, int open_flags, int dict_flags)
407 {
408     DICT_DBM *dict_dbm;
409     struct stat st;
410     DBM    *dbm;
411     char   *dbm_path;
412     int     lock_fd;
413 
414     /*
415      * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
416      * the time domain) locking while accessing individual database records.
417      *
418      * Programs such as postmap/postalias use their own large-grained (in the
419      * time domain) locks while rewriting the entire file.
420      */
421     if (dict_flags & DICT_FLAG_LOCK) {
422 	dbm_path = concatenate(path, ".dir", (char *) 0);
423 	if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0)
424 	    msg_fatal("open database %s: %m", dbm_path);
425 	if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
426 	    msg_fatal("shared-lock database %s for open: %m", dbm_path);
427     }
428 
429     /*
430      * XXX SunOS 5.x has no const in dbm_open() prototype.
431      */
432     if ((dbm = dbm_open((char *) path, open_flags, 0644)) == 0)
433 	msg_fatal("open database %s.{dir,pag}: %m", path);
434 
435     if (dict_flags & DICT_FLAG_LOCK) {
436 	if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
437 	    msg_fatal("unlock database %s for open: %m", dbm_path);
438 	if (close(lock_fd) < 0)
439 	    msg_fatal("close database %s: %m", dbm_path);
440     }
441     dict_dbm = (DICT_DBM *) dict_alloc(DICT_TYPE_DBM, path, sizeof(*dict_dbm));
442     dict_dbm->dict.lookup = dict_dbm_lookup;
443     dict_dbm->dict.update = dict_dbm_update;
444     dict_dbm->dict.delete = dict_dbm_delete;
445     dict_dbm->dict.sequence = dict_dbm_sequence;
446     dict_dbm->dict.close = dict_dbm_close;
447     dict_dbm->dict.lock_fd = dbm_dirfno(dbm);
448     dict_dbm->dict.stat_fd = dbm_pagfno(dbm);
449     if (dict_dbm->dict.lock_fd == dict_dbm->dict.stat_fd)
450 	msg_fatal("open database %s: cannot support GDBM", path);
451     if (fstat(dict_dbm->dict.stat_fd, &st) < 0)
452 	msg_fatal("dict_dbm_open: fstat: %m");
453     dict_dbm->dict.mtime = st.st_mtime;
454 
455     /*
456      * Warn if the source file is newer than the indexed file, except when
457      * the source file changed only seconds ago.
458      */
459     if ((dict_flags & DICT_FLAG_LOCK) != 0
460 	&& stat(path, &st) == 0
461 	&& st.st_mtime > dict_dbm->dict.mtime
462 	&& st.st_mtime < time((time_t *) 0) - 100)
463 	msg_warn("database %s is older than source file %s", dbm_path, path);
464 
465     close_on_exec(dbm_pagfno(dbm), CLOSE_ON_EXEC);
466     close_on_exec(dbm_dirfno(dbm), CLOSE_ON_EXEC);
467     dict_dbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
468     if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
469 	dict_dbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
470     if (dict_flags & DICT_FLAG_FOLD_FIX)
471 	dict_dbm->dict.fold_buf = vstring_alloc(10);
472     dict_dbm->dbm = dbm;
473     dict_dbm->key_buf = 0;
474     dict_dbm->val_buf = 0;
475 
476     if ((dict_flags & DICT_FLAG_LOCK))
477 	myfree(dbm_path);
478 
479     return (DICT_DEBUG (&dict_dbm->dict));
480 }
481 
482 #endif
483