1/*++ 2/* NAME 3/* dict_dbm 3 4/* SUMMARY 5/* dictionary manager interface to DBM files 6/* SYNOPSIS 7/* #include <dict_dbm.h> 8/* 9/* DICT *dict_dbm_open(path, open_flags, dict_flags) 10/* const char *name; 11/* const char *path; 12/* int open_flags; 13/* int dict_flags; 14/* DESCRIPTION 15/* dict_dbm_open() opens the named DBM database and makes it available 16/* via the generic interface described in dict_open(3). 17/* DIAGNOSTICS 18/* Fatal errors: cannot open file, file write error, out of memory. 19/* SEE ALSO 20/* dict(3) generic dictionary manager 21/* ndbm(3) data base subroutines 22/* LICENSE 23/* .ad 24/* .fi 25/* The Secure Mailer license must be distributed with this software. 26/* AUTHOR(S) 27/* Wietse Venema 28/* IBM T.J. Watson Research 29/* P.O. Box 704 30/* Yorktown Heights, NY 10598, USA 31/*--*/ 32 33#include "sys_defs.h" 34 35#ifdef HAS_DBM 36 37/* System library. */ 38 39#include <sys/stat.h> 40#ifdef PATH_NDBM_H 41#include PATH_NDBM_H 42#else 43#include <ndbm.h> 44#endif 45#ifdef R_FIRST 46#error "Error: you are including the Berkeley DB version of ndbm.h" 47#error "To build with Postfix NDBM support, delete the Berkeley DB ndbm.h file" 48#endif 49#include <string.h> 50#include <unistd.h> 51 52/* Utility library. */ 53 54#include "msg.h" 55#include "mymalloc.h" 56#include "htable.h" 57#include "iostuff.h" 58#include "vstring.h" 59#include "myflock.h" 60#include "stringops.h" 61#include "dict.h" 62#include "dict_dbm.h" 63#include "warn_stat.h" 64 65/* Application-specific. */ 66 67typedef struct { 68 DICT dict; /* generic members */ 69 DBM *dbm; /* open database */ 70 VSTRING *key_buf; /* key buffer */ 71 VSTRING *val_buf; /* result buffer */ 72} DICT_DBM; 73 74#define SCOPY(buf, data, size) \ 75 vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) 76 77/* dict_dbm_lookup - find database entry */ 78 79static const char *dict_dbm_lookup(DICT *dict, const char *name) 80{ 81 DICT_DBM *dict_dbm = (DICT_DBM *) dict; 82 datum dbm_key; 83 datum dbm_value; 84 const char *result = 0; 85 86 dict->error = 0; 87 88 /* 89 * Sanity check. 90 */ 91 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 92 msg_panic("dict_dbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); 93 94 /* 95 * Optionally fold the key. 96 */ 97 if (dict->flags & DICT_FLAG_FOLD_FIX) { 98 if (dict->fold_buf == 0) 99 dict->fold_buf = vstring_alloc(10); 100 vstring_strcpy(dict->fold_buf, name); 101 name = lowercase(vstring_str(dict->fold_buf)); 102 } 103 104 /* 105 * Acquire an exclusive lock. 106 */ 107 if ((dict->flags & DICT_FLAG_LOCK) 108 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 109 msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); 110 111 /* 112 * See if this DBM file was written with one null byte appended to key 113 * and value. 114 */ 115 if (dict->flags & DICT_FLAG_TRY1NULL) { 116 dbm_key.dptr = (void *) name; 117 dbm_key.dsize = strlen(name) + 1; 118 dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); 119 if (dbm_value.dptr != 0) { 120 dict->flags &= ~DICT_FLAG_TRY0NULL; 121 result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); 122 } 123 } 124 125 /* 126 * See if this DBM file was written with no null byte appended to key and 127 * value. 128 */ 129 if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { 130 dbm_key.dptr = (void *) name; 131 dbm_key.dsize = strlen(name); 132 dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); 133 if (dbm_value.dptr != 0) { 134 dict->flags &= ~DICT_FLAG_TRY1NULL; 135 result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); 136 } 137 } 138 139 /* 140 * Release the exclusive lock. 141 */ 142 if ((dict->flags & DICT_FLAG_LOCK) 143 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 144 msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); 145 146 return (result); 147} 148 149/* dict_dbm_update - add or update database entry */ 150 151static int dict_dbm_update(DICT *dict, const char *name, const char *value) 152{ 153 DICT_DBM *dict_dbm = (DICT_DBM *) dict; 154 datum dbm_key; 155 datum dbm_value; 156 int status; 157 158 dict->error = 0; 159 160 /* 161 * Sanity check. 162 */ 163 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 164 msg_panic("dict_dbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); 165 166 /* 167 * Optionally fold the key. 168 */ 169 if (dict->flags & DICT_FLAG_FOLD_FIX) { 170 if (dict->fold_buf == 0) 171 dict->fold_buf = vstring_alloc(10); 172 vstring_strcpy(dict->fold_buf, name); 173 name = lowercase(vstring_str(dict->fold_buf)); 174 } 175 dbm_key.dptr = (void *) name; 176 dbm_value.dptr = (void *) value; 177 dbm_key.dsize = strlen(name); 178 dbm_value.dsize = strlen(value); 179 180 /* 181 * If undecided about appending a null byte to key and value, choose a 182 * default depending on the platform. 183 */ 184 if ((dict->flags & DICT_FLAG_TRY1NULL) 185 && (dict->flags & DICT_FLAG_TRY0NULL)) { 186#ifdef DBM_NO_TRAILING_NULL 187 dict->flags &= ~DICT_FLAG_TRY1NULL; 188#else 189 dict->flags &= ~DICT_FLAG_TRY0NULL; 190#endif 191 } 192 193 /* 194 * Optionally append a null byte to key and value. 195 */ 196 if (dict->flags & DICT_FLAG_TRY1NULL) { 197 dbm_key.dsize++; 198 dbm_value.dsize++; 199 } 200 201 /* 202 * Acquire an exclusive lock. 203 */ 204 if ((dict->flags & DICT_FLAG_LOCK) 205 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) 206 msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); 207 208 /* 209 * Do the update. 210 */ 211 if ((status = dbm_store(dict_dbm->dbm, dbm_key, dbm_value, 212 (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0) 213 msg_fatal("error writing DBM database %s: %m", dict_dbm->dict.name); 214 if (status) { 215 if (dict->flags & DICT_FLAG_DUP_IGNORE) 216 /* void */ ; 217 else if (dict->flags & DICT_FLAG_DUP_WARN) 218 msg_warn("%s: duplicate entry: \"%s\"", dict_dbm->dict.name, name); 219 else 220 msg_fatal("%s: duplicate entry: \"%s\"", dict_dbm->dict.name, name); 221 } 222 223 /* 224 * Release the exclusive lock. 225 */ 226 if ((dict->flags & DICT_FLAG_LOCK) 227 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 228 msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); 229 230 return (status); 231} 232 233/* dict_dbm_delete - delete one entry from the dictionary */ 234 235static int dict_dbm_delete(DICT *dict, const char *name) 236{ 237 DICT_DBM *dict_dbm = (DICT_DBM *) dict; 238 datum dbm_key; 239 int status = 1; 240 241 dict->error = 0; 242 243 /* 244 * Sanity check. 245 */ 246 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 247 msg_panic("dict_dbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); 248 249 /* 250 * Optionally fold the key. 251 */ 252 if (dict->flags & DICT_FLAG_FOLD_FIX) { 253 if (dict->fold_buf == 0) 254 dict->fold_buf = vstring_alloc(10); 255 vstring_strcpy(dict->fold_buf, name); 256 name = lowercase(vstring_str(dict->fold_buf)); 257 } 258 259 /* 260 * Acquire an exclusive lock. 261 */ 262 if ((dict->flags & DICT_FLAG_LOCK) 263 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) 264 msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); 265 266 /* 267 * See if this DBM file was written with one null byte appended to key 268 * and value. 269 */ 270 if (dict->flags & DICT_FLAG_TRY1NULL) { 271 dbm_key.dptr = (void *) name; 272 dbm_key.dsize = strlen(name) + 1; 273 dbm_clearerr(dict_dbm->dbm); 274 if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) { 275 if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */ 276 msg_fatal("error deleting from %s: %m", dict_dbm->dict.name); 277 status = 1; /* not found */ 278 } else { 279 dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */ 280 } 281 } 282 283 /* 284 * See if this DBM file was written with no null byte appended to key and 285 * value. 286 */ 287 if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { 288 dbm_key.dptr = (void *) name; 289 dbm_key.dsize = strlen(name); 290 dbm_clearerr(dict_dbm->dbm); 291 if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) { 292 if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */ 293 msg_fatal("error deleting from %s: %m", dict_dbm->dict.name); 294 status = 1; /* not found */ 295 } else { 296 dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */ 297 } 298 } 299 300 /* 301 * Release the exclusive lock. 302 */ 303 if ((dict->flags & DICT_FLAG_LOCK) 304 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 305 msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); 306 307 return (status); 308} 309 310/* traverse the dictionary */ 311 312static int dict_dbm_sequence(DICT *dict, int function, 313 const char **key, const char **value) 314{ 315 const char *myname = "dict_dbm_sequence"; 316 DICT_DBM *dict_dbm = (DICT_DBM *) dict; 317 datum dbm_key; 318 datum dbm_value; 319 int status; 320 321 dict->error = 0; 322 323 /* 324 * Acquire a shared lock. 325 */ 326 if ((dict->flags & DICT_FLAG_LOCK) 327 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 328 msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); 329 330 /* 331 * Determine and execute the seek function. It returns the key. 332 */ 333 switch (function) { 334 case DICT_SEQ_FUN_FIRST: 335 dbm_key = dbm_firstkey(dict_dbm->dbm); 336 break; 337 case DICT_SEQ_FUN_NEXT: 338 dbm_key = dbm_nextkey(dict_dbm->dbm); 339 break; 340 default: 341 msg_panic("%s: invalid function: %d", myname, function); 342 } 343 344 if (dbm_key.dptr != 0 && dbm_key.dsize > 0) { 345 346 /* 347 * Copy the key so that it is guaranteed null terminated. 348 */ 349 *key = SCOPY(dict_dbm->key_buf, dbm_key.dptr, dbm_key.dsize); 350 351 /* 352 * Fetch the corresponding value. 353 */ 354 dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); 355 356 if (dbm_value.dptr != 0 && dbm_value.dsize > 0) { 357 358 /* 359 * Copy the value so that it is guaranteed null terminated. 360 */ 361 *value = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); 362 status = 0; 363 } else { 364 365 /* 366 * Determine if we have hit the last record or an error 367 * condition. 368 */ 369 if (dbm_error(dict_dbm->dbm)) 370 msg_fatal("error seeking %s: %m", dict_dbm->dict.name); 371 status = 1; /* no error: eof/not found 372 * (should not happen!) */ 373 } 374 } else { 375 376 /* 377 * Determine if we have hit the last record or an error condition. 378 */ 379 if (dbm_error(dict_dbm->dbm)) 380 msg_fatal("error seeking %s: %m", dict_dbm->dict.name); 381 status = 1; /* no error: eof/not found */ 382 } 383 384 /* 385 * Release the shared lock. 386 */ 387 if ((dict->flags & DICT_FLAG_LOCK) 388 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 389 msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); 390 391 return (status); 392} 393 394/* dict_dbm_close - disassociate from data base */ 395 396static void dict_dbm_close(DICT *dict) 397{ 398 DICT_DBM *dict_dbm = (DICT_DBM *) dict; 399 400 dbm_close(dict_dbm->dbm); 401 if (dict_dbm->key_buf) 402 vstring_free(dict_dbm->key_buf); 403 if (dict_dbm->val_buf) 404 vstring_free(dict_dbm->val_buf); 405 if (dict->fold_buf) 406 vstring_free(dict->fold_buf); 407 dict_free(dict); 408} 409 410/* dict_dbm_open - open DBM data base */ 411 412DICT *dict_dbm_open(const char *path, int open_flags, int dict_flags) 413{ 414 DICT_DBM *dict_dbm; 415 struct stat st; 416 DBM *dbm; 417 char *dbm_path = 0; 418 int lock_fd; 419 420 /* 421 * Let the optimizer worry about eliminating redundant code. 422 */ 423#define DICT_DBM_OPEN_RETURN(d) { \ 424 DICT *__d = (d); \ 425 if (dbm_path != 0) \ 426 myfree(dbm_path); \ 427 return (__d); \ 428 } while (0) 429 430 /* 431 * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in 432 * the time domain) locking while accessing individual database records. 433 * 434 * Programs such as postmap/postalias use their own large-grained (in the 435 * time domain) locks while rewriting the entire file. 436 */ 437 if (dict_flags & DICT_FLAG_LOCK) { 438 dbm_path = concatenate(path, ".dir", (char *) 0); 439 if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0) 440 DICT_DBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_DBM, path, 441 open_flags, dict_flags, 442 "open database %s: %m", 443 dbm_path)); 444 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 445 msg_fatal("shared-lock database %s for open: %m", dbm_path); 446 } 447 448 /* 449 * XXX SunOS 5.x has no const in dbm_open() prototype. 450 */ 451 if ((dbm = dbm_open((char *) path, open_flags, 0644)) == 0) 452 DICT_DBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_DBM, path, 453 open_flags, dict_flags, 454 "open database %s.{dir,pag}: %m", 455 path)); 456 457 if (dict_flags & DICT_FLAG_LOCK) { 458 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 459 msg_fatal("unlock database %s for open: %m", dbm_path); 460 if (close(lock_fd) < 0) 461 msg_fatal("close database %s: %m", dbm_path); 462 } 463 dict_dbm = (DICT_DBM *) dict_alloc(DICT_TYPE_DBM, path, sizeof(*dict_dbm)); 464 dict_dbm->dict.lookup = dict_dbm_lookup; 465 dict_dbm->dict.update = dict_dbm_update; 466 dict_dbm->dict.delete = dict_dbm_delete; 467 dict_dbm->dict.sequence = dict_dbm_sequence; 468 dict_dbm->dict.close = dict_dbm_close; 469 dict_dbm->dict.lock_fd = dbm_dirfno(dbm); 470 dict_dbm->dict.stat_fd = dbm_pagfno(dbm); 471 if (dict_dbm->dict.lock_fd == dict_dbm->dict.stat_fd) 472 msg_fatal("open database %s: cannot support GDBM", path); 473 if (fstat(dict_dbm->dict.stat_fd, &st) < 0) 474 msg_fatal("dict_dbm_open: fstat: %m"); 475 dict_dbm->dict.mtime = st.st_mtime; 476 dict_dbm->dict.owner.uid = st.st_uid; 477 dict_dbm->dict.owner.status = (st.st_uid != 0); 478 479 /* 480 * Warn if the source file is newer than the indexed file, except when 481 * the source file changed only seconds ago. 482 */ 483 if ((dict_flags & DICT_FLAG_LOCK) != 0 484 && stat(path, &st) == 0 485 && st.st_mtime > dict_dbm->dict.mtime 486 && st.st_mtime < time((time_t *) 0) - 100) 487 msg_warn("database %s is older than source file %s", dbm_path, path); 488 489 close_on_exec(dbm_pagfno(dbm), CLOSE_ON_EXEC); 490 close_on_exec(dbm_dirfno(dbm), CLOSE_ON_EXEC); 491 dict_dbm->dict.flags = dict_flags | DICT_FLAG_FIXED; 492 if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) 493 dict_dbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); 494 if (dict_flags & DICT_FLAG_FOLD_FIX) 495 dict_dbm->dict.fold_buf = vstring_alloc(10); 496 dict_dbm->dbm = dbm; 497 dict_dbm->key_buf = 0; 498 dict_dbm->val_buf = 0; 499 500 DICT_DBM_OPEN_RETURN(DICT_DEBUG (&dict_dbm->dict)); 501} 502 503#endif 504