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 
69 typedef 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 
dict_dbm_lookup(DICT * dict,const char * name)81 static 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 
dict_dbm_update(DICT * dict,const char * name,const char * value)153 static 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 
dict_dbm_delete(DICT * dict,const char * name)237 static 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 
dict_dbm_sequence(DICT * dict,int function,const char ** key,const char ** value)314 static 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 
dict_dbm_close(DICT * dict)398 static 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 
dict_dbm_open(const char * path,int open_flags,int dict_flags)414 DICT   *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