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