1 /*	$NetBSD: dict_sdbm.c,v 1.1.1.2 2013/01/02 18:59:12 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	dict_sdbm 3
6 /* SUMMARY
7 /*	dictionary manager interface to SDBM files
8 /* SYNOPSIS
9 /*	#include <dict_sdbm.h>
10 /*
11 /*	DICT	*dict_sdbm_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_sdbm_open() opens the named SDBM 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 /*	sdbm(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 /* System library. */
38 
39 #include <sys/stat.h>
40 #include <string.h>
41 #include <unistd.h>
42 #ifdef HAS_SDBM
43 #include <sdbm.h>
44 #endif
45 
46 /* Utility library. */
47 
48 #include <msg.h>
49 #include <mymalloc.h>
50 #include <htable.h>
51 #include <iostuff.h>
52 #include <vstring.h>
53 #include <myflock.h>
54 #include <stringops.h>
55 #include <dict.h>
56 #include <dict_sdbm.h>
57 #include <warn_stat.h>
58 
59 #ifdef HAS_SDBM
60 
61 /* Application-specific. */
62 
63 typedef struct {
64     DICT    dict;			/* generic members */
65     SDBM   *dbm;			/* open database */
66     VSTRING *key_buf;			/* key buffer */
67     VSTRING *val_buf;			/* result buffer */
68 } DICT_SDBM;
69 
70 #define SCOPY(buf, data, size) \
71     vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
72 
73 /* dict_sdbm_lookup - find database entry */
74 
dict_sdbm_lookup(DICT * dict,const char * name)75 static const char *dict_sdbm_lookup(DICT *dict, const char *name)
76 {
77     DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
78     datum   dbm_key;
79     datum   dbm_value;
80     const char *result = 0;
81 
82     dict->error = 0;
83 
84     /*
85      * Sanity check.
86      */
87     if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
88 	msg_panic("dict_sdbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
89 
90     /*
91      * Optionally fold the key.
92      */
93     if (dict->flags & DICT_FLAG_FOLD_FIX) {
94 	if (dict->fold_buf == 0)
95 	    dict->fold_buf = vstring_alloc(10);
96 	vstring_strcpy(dict->fold_buf, name);
97 	name = lowercase(vstring_str(dict->fold_buf));
98     }
99 
100     /*
101      * Acquire an exclusive lock.
102      */
103     if ((dict->flags & DICT_FLAG_LOCK)
104 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
105 	msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
106 
107     /*
108      * See if this DBM file was written with one null byte appended to key
109      * and value.
110      */
111     if (dict->flags & DICT_FLAG_TRY1NULL) {
112 	dbm_key.dptr = (void *) name;
113 	dbm_key.dsize = strlen(name) + 1;
114 	dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
115 	if (dbm_value.dptr != 0) {
116 	    dict->flags &= ~DICT_FLAG_TRY0NULL;
117 	    result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
118 	}
119     }
120 
121     /*
122      * See if this DBM file was written with no null byte appended to key and
123      * value.
124      */
125     if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
126 	dbm_key.dptr = (void *) name;
127 	dbm_key.dsize = strlen(name);
128 	dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
129 	if (dbm_value.dptr != 0) {
130 	    dict->flags &= ~DICT_FLAG_TRY1NULL;
131 	    result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
132 	}
133     }
134 
135     /*
136      * Release the exclusive lock.
137      */
138     if ((dict->flags & DICT_FLAG_LOCK)
139 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
140 	msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
141 
142     return (result);
143 }
144 
145 /* dict_sdbm_update - add or update database entry */
146 
dict_sdbm_update(DICT * dict,const char * name,const char * value)147 static int dict_sdbm_update(DICT *dict, const char *name, const char *value)
148 {
149     DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
150     datum   dbm_key;
151     datum   dbm_value;
152     int     status;
153 
154     dict->error = 0;
155 
156     /*
157      * Sanity check.
158      */
159     if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
160 	msg_panic("dict_sdbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
161 
162     /*
163      * Optionally fold the key.
164      */
165     if (dict->flags & DICT_FLAG_FOLD_FIX) {
166 	if (dict->fold_buf == 0)
167 	    dict->fold_buf = vstring_alloc(10);
168 	vstring_strcpy(dict->fold_buf, name);
169 	name = lowercase(vstring_str(dict->fold_buf));
170     }
171     dbm_key.dptr = (void *) name;
172     dbm_value.dptr = (void *) value;
173     dbm_key.dsize = strlen(name);
174     dbm_value.dsize = strlen(value);
175 
176     /*
177      * If undecided about appending a null byte to key and value, choose a
178      * default depending on the platform.
179      */
180     if ((dict->flags & DICT_FLAG_TRY1NULL)
181 	&& (dict->flags & DICT_FLAG_TRY0NULL)) {
182 #ifdef DBM_NO_TRAILING_NULL
183 	dict->flags &= ~DICT_FLAG_TRY1NULL;
184 #else
185 	dict->flags &= ~DICT_FLAG_TRY0NULL;
186 #endif
187     }
188 
189     /*
190      * Optionally append a null byte to key and value.
191      */
192     if (dict->flags & DICT_FLAG_TRY1NULL) {
193 	dbm_key.dsize++;
194 	dbm_value.dsize++;
195     }
196 
197     /*
198      * Acquire an exclusive lock.
199      */
200     if ((dict->flags & DICT_FLAG_LOCK)
201 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
202 	msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
203 
204     /*
205      * Do the update.
206      */
207     if ((status = sdbm_store(dict_sdbm->dbm, dbm_key, dbm_value,
208      (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0)
209 	msg_fatal("error writing SDBM database %s: %m", dict_sdbm->dict.name);
210     if (status) {
211 	if (dict->flags & DICT_FLAG_DUP_IGNORE)
212 	     /* void */ ;
213 	else if (dict->flags & DICT_FLAG_DUP_WARN)
214 	    msg_warn("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name);
215 	else
216 	    msg_fatal("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name);
217     }
218 
219     /*
220      * Release the exclusive lock.
221      */
222     if ((dict->flags & DICT_FLAG_LOCK)
223 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
224 	msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
225 
226     return (status);
227 }
228 
229 /* dict_sdbm_delete - delete one entry from the dictionary */
230 
dict_sdbm_delete(DICT * dict,const char * name)231 static int dict_sdbm_delete(DICT *dict, const char *name)
232 {
233     DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
234     datum   dbm_key;
235     int     status = 1;
236 
237     dict->error = 0;
238 
239     /*
240      * Sanity check.
241      */
242     if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
243 	msg_panic("dict_sdbm_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_sdbm->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 	sdbm_clearerr(dict_sdbm->dbm);
270 	if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) {
271 	    if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */
272 		msg_fatal("error deleting from %s: %m", dict_sdbm->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 	sdbm_clearerr(dict_sdbm->dbm);
287 	if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) {
288 	    if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */
289 		msg_fatal("error deleting from %s: %m", dict_sdbm->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_sdbm->dict.name);
302 
303     return (status);
304 }
305 
306 /* traverse the dictionary */
307 
dict_sdbm_sequence(DICT * dict,const int function,const char ** key,const char ** value)308 static int dict_sdbm_sequence(DICT *dict, const int function,
309 			              const char **key, const char **value)
310 {
311     const char *myname = "dict_sdbm_sequence";
312     DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
313     datum   dbm_key;
314     datum   dbm_value;
315     int     status;
316 
317     dict->error = 0;
318 
319     /*
320      * Acquire a shared lock.
321      */
322     if ((dict->flags & DICT_FLAG_LOCK)
323 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
324 	msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
325 
326     /*
327      * Determine and execute the seek function. It returns the key.
328      */
329     sdbm_clearerr(dict_sdbm->dbm);
330     switch (function) {
331     case DICT_SEQ_FUN_FIRST:
332 	dbm_key = sdbm_firstkey(dict_sdbm->dbm);
333 	break;
334     case DICT_SEQ_FUN_NEXT:
335 	dbm_key = sdbm_nextkey(dict_sdbm->dbm);
336 	break;
337     default:
338 	msg_panic("%s: invalid function: %d", myname, function);
339     }
340 
341     if (dbm_key.dptr != 0 && dbm_key.dsize > 0) {
342 
343 	/*
344 	 * Copy the key so that it is guaranteed null terminated.
345 	 */
346 	*key = SCOPY(dict_sdbm->key_buf, dbm_key.dptr, dbm_key.dsize);
347 
348 	/*
349 	 * Fetch the corresponding value.
350 	 */
351 	dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
352 
353 	if (dbm_value.dptr != 0 && dbm_value.dsize > 0) {
354 
355 	    /*
356 	     * Copy the value so that it is guaranteed null terminated.
357 	     */
358 	    *value = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
359 	    status = 0;
360 	} else {
361 
362 	    /*
363 	     * Determine if we have hit the last record or an error
364 	     * condition.
365 	     */
366 	    if (sdbm_error(dict_sdbm->dbm))
367 		msg_fatal("error seeking %s: %m", dict_sdbm->dict.name);
368 	    status = 1;				/* no error: eof/not found
369 						 * (should not happen!) */
370 	}
371     } else {
372 
373 	/*
374 	 * Determine if we have hit the last record or an error condition.
375 	 */
376 	if (sdbm_error(dict_sdbm->dbm))
377 	    msg_fatal("error seeking %s: %m", dict_sdbm->dict.name);
378 	status = 1;				/* no error: eof/not found */
379     }
380 
381     /*
382      * Release the shared lock.
383      */
384     if ((dict->flags & DICT_FLAG_LOCK)
385 	&& myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
386 	msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
387 
388     return (status);
389 }
390 
391 /* dict_sdbm_close - disassociate from data base */
392 
dict_sdbm_close(DICT * dict)393 static void dict_sdbm_close(DICT *dict)
394 {
395     DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
396 
397     sdbm_close(dict_sdbm->dbm);
398     if (dict_sdbm->key_buf)
399 	vstring_free(dict_sdbm->key_buf);
400     if (dict_sdbm->val_buf)
401 	vstring_free(dict_sdbm->val_buf);
402     if (dict->fold_buf)
403 	vstring_free(dict->fold_buf);
404     dict_free(dict);
405 }
406 
407 /* dict_sdbm_open - open SDBM data base */
408 
dict_sdbm_open(const char * path,int open_flags,int dict_flags)409 DICT   *dict_sdbm_open(const char *path, int open_flags, int dict_flags)
410 {
411     DICT_SDBM *dict_sdbm;
412     struct stat st;
413     SDBM   *dbm;
414     char   *dbm_path;
415     int     lock_fd;
416 
417     /*
418      * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
419      * the time domain) locking while accessing individual database records.
420      *
421      * Programs such as postmap/postalias use their own large-grained (in the
422      * time domain) locks while rewriting the entire file.
423      */
424     if (dict_flags & DICT_FLAG_LOCK) {
425 	dbm_path = concatenate(path, ".dir", (char *) 0);
426 	if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0)
427 	    msg_fatal("open database %s: %m", dbm_path);
428 	if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
429 	    msg_fatal("shared-lock database %s for open: %m", dbm_path);
430     }
431 
432     /*
433      * XXX sdbm_open() has no const in prototype.
434      */
435     if ((dbm = sdbm_open((char *) path, open_flags, 0644)) == 0)
436 	msg_fatal("open database %s.{dir,pag}: %m", path);
437 
438     if (dict_flags & DICT_FLAG_LOCK) {
439 	if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
440 	    msg_fatal("unlock database %s for open: %m", dbm_path);
441 	if (close(lock_fd) < 0)
442 	    msg_fatal("close database %s: %m", dbm_path);
443     }
444     dict_sdbm = (DICT_SDBM *) dict_alloc(DICT_TYPE_SDBM, path, sizeof(*dict_sdbm));
445     dict_sdbm->dict.lookup = dict_sdbm_lookup;
446     dict_sdbm->dict.update = dict_sdbm_update;
447     dict_sdbm->dict.delete = dict_sdbm_delete;
448     dict_sdbm->dict.sequence = dict_sdbm_sequence;
449     dict_sdbm->dict.close = dict_sdbm_close;
450     dict_sdbm->dict.lock_fd = sdbm_dirfno(dbm);
451     dict_sdbm->dict.stat_fd = sdbm_pagfno(dbm);
452     if (fstat(dict_sdbm->dict.stat_fd, &st) < 0)
453 	msg_fatal("dict_sdbm_open: fstat: %m");
454     dict_sdbm->dict.mtime = st.st_mtime;
455     dict_sdbm->dict.owner.uid = st.st_uid;
456     dict_sdbm->dict.owner.status = (st.st_uid != 0);
457 
458     /*
459      * Warn if the source file is newer than the indexed file, except when
460      * the source file changed only seconds ago.
461      */
462     if ((dict_flags & DICT_FLAG_LOCK) != 0
463 	&& stat(path, &st) == 0
464 	&& st.st_mtime > dict_sdbm->dict.mtime
465 	&& st.st_mtime < time((time_t *) 0) - 100)
466 	msg_warn("database %s is older than source file %s", dbm_path, path);
467 
468     close_on_exec(sdbm_pagfno(dbm), CLOSE_ON_EXEC);
469     close_on_exec(sdbm_dirfno(dbm), CLOSE_ON_EXEC);
470     dict_sdbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
471     if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
472 	dict_sdbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
473     if (dict_flags & DICT_FLAG_FOLD_FIX)
474 	dict_sdbm->dict.fold_buf = vstring_alloc(10);
475     dict_sdbm->dbm = dbm;
476     dict_sdbm->key_buf = 0;
477     dict_sdbm->val_buf = 0;
478 
479     if ((dict_flags & DICT_FLAG_LOCK))
480 	myfree(dbm_path);
481 
482     return (DICT_DEBUG (&dict_sdbm->dict));
483 }
484 
485 #endif
486