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