1 /* $NetBSD: dict_dbm.c,v 1.1.1.3 2013/01/02 18:59:12 tron 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 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 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 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 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 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 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; 420 int lock_fd; 421 422 /* 423 * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in 424 * the time domain) locking while accessing individual database records. 425 * 426 * Programs such as postmap/postalias use their own large-grained (in the 427 * time domain) locks while rewriting the entire file. 428 */ 429 if (dict_flags & DICT_FLAG_LOCK) { 430 dbm_path = concatenate(path, ".dir", (char *) 0); 431 if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0) 432 return (dict_surrogate(DICT_TYPE_DBM, path, open_flags, dict_flags, 433 "open database %s: %m", dbm_path)); 434 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 435 msg_fatal("shared-lock database %s for open: %m", dbm_path); 436 } 437 438 /* 439 * XXX SunOS 5.x has no const in dbm_open() prototype. 440 */ 441 if ((dbm = dbm_open((char *) path, open_flags, 0644)) == 0) 442 return (dict_surrogate(DICT_TYPE_DBM, path, open_flags, dict_flags, 443 "open database %s.{dir,pag}: %m", path)); 444 445 if (dict_flags & DICT_FLAG_LOCK) { 446 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 447 msg_fatal("unlock database %s for open: %m", dbm_path); 448 if (close(lock_fd) < 0) 449 msg_fatal("close database %s: %m", dbm_path); 450 } 451 dict_dbm = (DICT_DBM *) dict_alloc(DICT_TYPE_DBM, path, sizeof(*dict_dbm)); 452 dict_dbm->dict.lookup = dict_dbm_lookup; 453 dict_dbm->dict.update = dict_dbm_update; 454 dict_dbm->dict.delete = dict_dbm_delete; 455 dict_dbm->dict.sequence = dict_dbm_sequence; 456 dict_dbm->dict.close = dict_dbm_close; 457 dict_dbm->dict.lock_fd = dbm_dirfno(dbm); 458 dict_dbm->dict.stat_fd = dbm_pagfno(dbm); 459 if (dict_dbm->dict.lock_fd == dict_dbm->dict.stat_fd) 460 msg_fatal("open database %s: cannot support GDBM", path); 461 if (fstat(dict_dbm->dict.stat_fd, &st) < 0) 462 msg_fatal("dict_dbm_open: fstat: %m"); 463 dict_dbm->dict.mtime = st.st_mtime; 464 dict_dbm->dict.owner.uid = st.st_uid; 465 dict_dbm->dict.owner.status = (st.st_uid != 0); 466 467 /* 468 * Warn if the source file is newer than the indexed file, except when 469 * the source file changed only seconds ago. 470 */ 471 if ((dict_flags & DICT_FLAG_LOCK) != 0 472 && stat(path, &st) == 0 473 && st.st_mtime > dict_dbm->dict.mtime 474 && st.st_mtime < time((time_t *) 0) - 100) 475 msg_warn("database %s is older than source file %s", dbm_path, path); 476 477 close_on_exec(dbm_pagfno(dbm), CLOSE_ON_EXEC); 478 close_on_exec(dbm_dirfno(dbm), CLOSE_ON_EXEC); 479 dict_dbm->dict.flags = dict_flags | DICT_FLAG_FIXED; 480 if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) 481 dict_dbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); 482 if (dict_flags & DICT_FLAG_FOLD_FIX) 483 dict_dbm->dict.fold_buf = vstring_alloc(10); 484 dict_dbm->dbm = dbm; 485 dict_dbm->key_buf = 0; 486 dict_dbm->val_buf = 0; 487 488 if ((dict_flags & DICT_FLAG_LOCK)) 489 myfree(dbm_path); 490 491 return (DICT_DEBUG (&dict_dbm->dict)); 492 } 493 494 #endif 495