1 /* $NetBSD: dict_dbm.c,v 1.1.1.2 2010/06/17 18:07: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 66 /* Application-specific. */ 67 68 typedef struct { 69 DICT dict; /* generic members */ 70 DBM *dbm; /* open database */ 71 VSTRING *key_buf; /* key buffer */ 72 VSTRING *val_buf; /* result buffer */ 73 } DICT_DBM; 74 75 #define SCOPY(buf, data, size) \ 76 vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) 77 78 /* dict_dbm_lookup - find database entry */ 79 80 static const char *dict_dbm_lookup(DICT *dict, const char *name) 81 { 82 DICT_DBM *dict_dbm = (DICT_DBM *) dict; 83 datum dbm_key; 84 datum dbm_value; 85 const char *result = 0; 86 87 /* 88 * Sanity check. 89 */ 90 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 91 msg_panic("dict_dbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); 92 93 dict_errno = 0; 94 95 /* 96 * Optionally fold the key. 97 */ 98 if (dict->flags & DICT_FLAG_FOLD_FIX) { 99 if (dict->fold_buf == 0) 100 dict->fold_buf = vstring_alloc(10); 101 vstring_strcpy(dict->fold_buf, name); 102 name = lowercase(vstring_str(dict->fold_buf)); 103 } 104 105 /* 106 * Acquire an exclusive lock. 107 */ 108 if ((dict->flags & DICT_FLAG_LOCK) 109 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 110 msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); 111 112 /* 113 * See if this DBM file was written with one null byte appended to key 114 * and value. 115 */ 116 if (dict->flags & DICT_FLAG_TRY1NULL) { 117 dbm_key.dptr = (void *) name; 118 dbm_key.dsize = strlen(name) + 1; 119 dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); 120 if (dbm_value.dptr != 0) { 121 dict->flags &= ~DICT_FLAG_TRY0NULL; 122 result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); 123 } 124 } 125 126 /* 127 * See if this DBM file was written with no null byte appended to key and 128 * value. 129 */ 130 if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { 131 dbm_key.dptr = (void *) name; 132 dbm_key.dsize = strlen(name); 133 dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); 134 if (dbm_value.dptr != 0) { 135 dict->flags &= ~DICT_FLAG_TRY1NULL; 136 result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); 137 } 138 } 139 140 /* 141 * Release the exclusive lock. 142 */ 143 if ((dict->flags & DICT_FLAG_LOCK) 144 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 145 msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); 146 147 return (result); 148 } 149 150 /* dict_dbm_update - add or update database entry */ 151 152 static void dict_dbm_update(DICT *dict, const char *name, const char *value) 153 { 154 DICT_DBM *dict_dbm = (DICT_DBM *) dict; 155 datum dbm_key; 156 datum dbm_value; 157 int status; 158 159 /* 160 * Sanity check. 161 */ 162 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 163 msg_panic("dict_dbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); 164 165 /* 166 * Optionally fold the key. 167 */ 168 if (dict->flags & DICT_FLAG_FOLD_FIX) { 169 if (dict->fold_buf == 0) 170 dict->fold_buf = vstring_alloc(10); 171 vstring_strcpy(dict->fold_buf, name); 172 name = lowercase(vstring_str(dict->fold_buf)); 173 } 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 231 /* dict_dbm_delete - delete one entry from the dictionary */ 232 233 static int dict_dbm_delete(DICT *dict, const char *name) 234 { 235 DICT_DBM *dict_dbm = (DICT_DBM *) dict; 236 datum dbm_key; 237 int status = 1; 238 239 /* 240 * Sanity check. 241 */ 242 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 243 msg_panic("dict_dbm_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_dbm->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 dbm_clearerr(dict_dbm->dbm); 270 if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) { 271 if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */ 272 msg_fatal("error deleting from %s: %m", dict_dbm->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 dbm_clearerr(dict_dbm->dbm); 287 if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) { 288 if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */ 289 msg_fatal("error deleting from %s: %m", dict_dbm->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_dbm->dict.name); 302 303 return (status); 304 } 305 306 /* traverse the dictionary */ 307 308 static int dict_dbm_sequence(DICT *dict, int function, 309 const char **key, const char **value) 310 { 311 const char *myname = "dict_dbm_sequence"; 312 DICT_DBM *dict_dbm = (DICT_DBM *) dict; 313 datum dbm_key; 314 datum dbm_value; 315 int status; 316 317 /* 318 * Acquire a shared lock. 319 */ 320 if ((dict->flags & DICT_FLAG_LOCK) 321 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 322 msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); 323 324 /* 325 * Determine and execute the seek function. It returns the key. 326 */ 327 switch (function) { 328 case DICT_SEQ_FUN_FIRST: 329 dbm_key = dbm_firstkey(dict_dbm->dbm); 330 break; 331 case DICT_SEQ_FUN_NEXT: 332 dbm_key = dbm_nextkey(dict_dbm->dbm); 333 break; 334 default: 335 msg_panic("%s: invalid function: %d", myname, function); 336 } 337 338 if (dbm_key.dptr != 0 && dbm_key.dsize > 0) { 339 340 /* 341 * Copy the key so that it is guaranteed null terminated. 342 */ 343 *key = SCOPY(dict_dbm->key_buf, dbm_key.dptr, dbm_key.dsize); 344 345 /* 346 * Fetch the corresponding value. 347 */ 348 dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); 349 350 if (dbm_value.dptr != 0 && dbm_value.dsize > 0) { 351 352 /* 353 * Copy the value so that it is guaranteed null terminated. 354 */ 355 *value = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); 356 status = 0; 357 } else { 358 359 /* 360 * Determine if we have hit the last record or an error 361 * condition. 362 */ 363 if (dbm_error(dict_dbm->dbm)) 364 msg_fatal("error seeking %s: %m", dict_dbm->dict.name); 365 status = 1; /* no error: eof/not found 366 * (should not happen!) */ 367 } 368 } else { 369 370 /* 371 * Determine if we have hit the last record or an error condition. 372 */ 373 if (dbm_error(dict_dbm->dbm)) 374 msg_fatal("error seeking %s: %m", dict_dbm->dict.name); 375 status = 1; /* no error: eof/not found */ 376 } 377 378 /* 379 * Release the shared lock. 380 */ 381 if ((dict->flags & DICT_FLAG_LOCK) 382 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 383 msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); 384 385 return (status); 386 } 387 388 /* dict_dbm_close - disassociate from data base */ 389 390 static void dict_dbm_close(DICT *dict) 391 { 392 DICT_DBM *dict_dbm = (DICT_DBM *) dict; 393 394 dbm_close(dict_dbm->dbm); 395 if (dict_dbm->key_buf) 396 vstring_free(dict_dbm->key_buf); 397 if (dict_dbm->val_buf) 398 vstring_free(dict_dbm->val_buf); 399 if (dict->fold_buf) 400 vstring_free(dict->fold_buf); 401 dict_free(dict); 402 } 403 404 /* dict_dbm_open - open DBM data base */ 405 406 DICT *dict_dbm_open(const char *path, int open_flags, int dict_flags) 407 { 408 DICT_DBM *dict_dbm; 409 struct stat st; 410 DBM *dbm; 411 char *dbm_path; 412 int lock_fd; 413 414 /* 415 * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in 416 * the time domain) locking while accessing individual database records. 417 * 418 * Programs such as postmap/postalias use their own large-grained (in the 419 * time domain) locks while rewriting the entire file. 420 */ 421 if (dict_flags & DICT_FLAG_LOCK) { 422 dbm_path = concatenate(path, ".dir", (char *) 0); 423 if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0) 424 msg_fatal("open database %s: %m", dbm_path); 425 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 426 msg_fatal("shared-lock database %s for open: %m", dbm_path); 427 } 428 429 /* 430 * XXX SunOS 5.x has no const in dbm_open() prototype. 431 */ 432 if ((dbm = dbm_open((char *) path, open_flags, 0644)) == 0) 433 msg_fatal("open database %s.{dir,pag}: %m", path); 434 435 if (dict_flags & DICT_FLAG_LOCK) { 436 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 437 msg_fatal("unlock database %s for open: %m", dbm_path); 438 if (close(lock_fd) < 0) 439 msg_fatal("close database %s: %m", dbm_path); 440 } 441 dict_dbm = (DICT_DBM *) dict_alloc(DICT_TYPE_DBM, path, sizeof(*dict_dbm)); 442 dict_dbm->dict.lookup = dict_dbm_lookup; 443 dict_dbm->dict.update = dict_dbm_update; 444 dict_dbm->dict.delete = dict_dbm_delete; 445 dict_dbm->dict.sequence = dict_dbm_sequence; 446 dict_dbm->dict.close = dict_dbm_close; 447 dict_dbm->dict.lock_fd = dbm_dirfno(dbm); 448 dict_dbm->dict.stat_fd = dbm_pagfno(dbm); 449 if (dict_dbm->dict.lock_fd == dict_dbm->dict.stat_fd) 450 msg_fatal("open database %s: cannot support GDBM", path); 451 if (fstat(dict_dbm->dict.stat_fd, &st) < 0) 452 msg_fatal("dict_dbm_open: fstat: %m"); 453 dict_dbm->dict.mtime = st.st_mtime; 454 455 /* 456 * Warn if the source file is newer than the indexed file, except when 457 * the source file changed only seconds ago. 458 */ 459 if ((dict_flags & DICT_FLAG_LOCK) != 0 460 && stat(path, &st) == 0 461 && st.st_mtime > dict_dbm->dict.mtime 462 && st.st_mtime < time((time_t *) 0) - 100) 463 msg_warn("database %s is older than source file %s", dbm_path, path); 464 465 close_on_exec(dbm_pagfno(dbm), CLOSE_ON_EXEC); 466 close_on_exec(dbm_dirfno(dbm), CLOSE_ON_EXEC); 467 dict_dbm->dict.flags = dict_flags | DICT_FLAG_FIXED; 468 if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) 469 dict_dbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); 470 if (dict_flags & DICT_FLAG_FOLD_FIX) 471 dict_dbm->dict.fold_buf = vstring_alloc(10); 472 dict_dbm->dbm = dbm; 473 dict_dbm->key_buf = 0; 474 dict_dbm->val_buf = 0; 475 476 if ((dict_flags & DICT_FLAG_LOCK)) 477 myfree(dbm_path); 478 479 return (DICT_DEBUG (&dict_dbm->dict)); 480 } 481 482 #endif 483