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 69typedef 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 81static 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 153static 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 237static 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 314static 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 398static 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 414DICT *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