1/* $NetBSD$ */ 2 3/*++ 4/* NAME 5/* dict_sdbm 3 6/* SUMMARY 7/* dictionary manager interface to SDBM files 8/* SYNOPSIS 9/* #include <dict_sdbm.h> 10/* 11/* DICT *dict_sdbm_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_sdbm_open() opens the named SDBM 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/* sdbm(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/* System library. */ 38 39#include <sys/stat.h> 40#include <string.h> 41#include <unistd.h> 42#ifdef HAS_SDBM 43#include <sdbm.h> 44#endif 45 46/* Utility library. */ 47 48#include <msg.h> 49#include <mymalloc.h> 50#include <htable.h> 51#include <iostuff.h> 52#include <vstring.h> 53#include <myflock.h> 54#include <stringops.h> 55#include <dict.h> 56#include <dict_sdbm.h> 57#include <warn_stat.h> 58 59#ifdef HAS_SDBM 60 61/* Application-specific. */ 62 63typedef struct { 64 DICT dict; /* generic members */ 65 SDBM *dbm; /* open database */ 66 VSTRING *key_buf; /* key buffer */ 67 VSTRING *val_buf; /* result buffer */ 68} DICT_SDBM; 69 70#define SCOPY(buf, data, size) \ 71 vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) 72 73/* dict_sdbm_lookup - find database entry */ 74 75static const char *dict_sdbm_lookup(DICT *dict, const char *name) 76{ 77 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; 78 datum dbm_key; 79 datum dbm_value; 80 const char *result = 0; 81 82 dict->error = 0; 83 84 /* 85 * Sanity check. 86 */ 87 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 88 msg_panic("dict_sdbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); 89 90 /* 91 * Optionally fold the key. 92 */ 93 if (dict->flags & DICT_FLAG_FOLD_FIX) { 94 if (dict->fold_buf == 0) 95 dict->fold_buf = vstring_alloc(10); 96 vstring_strcpy(dict->fold_buf, name); 97 name = lowercase(vstring_str(dict->fold_buf)); 98 } 99 100 /* 101 * Acquire an exclusive lock. 102 */ 103 if ((dict->flags & DICT_FLAG_LOCK) 104 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 105 msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); 106 107 /* 108 * See if this DBM file was written with one null byte appended to key 109 * and value. 110 */ 111 if (dict->flags & DICT_FLAG_TRY1NULL) { 112 dbm_key.dptr = (void *) name; 113 dbm_key.dsize = strlen(name) + 1; 114 dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); 115 if (dbm_value.dptr != 0) { 116 dict->flags &= ~DICT_FLAG_TRY0NULL; 117 result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize); 118 } 119 } 120 121 /* 122 * See if this DBM file was written with no null byte appended to key and 123 * value. 124 */ 125 if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { 126 dbm_key.dptr = (void *) name; 127 dbm_key.dsize = strlen(name); 128 dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); 129 if (dbm_value.dptr != 0) { 130 dict->flags &= ~DICT_FLAG_TRY1NULL; 131 result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize); 132 } 133 } 134 135 /* 136 * Release the exclusive lock. 137 */ 138 if ((dict->flags & DICT_FLAG_LOCK) 139 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 140 msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); 141 142 return (result); 143} 144 145/* dict_sdbm_update - add or update database entry */ 146 147static int dict_sdbm_update(DICT *dict, const char *name, const char *value) 148{ 149 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; 150 datum dbm_key; 151 datum dbm_value; 152 int status; 153 154 dict->error = 0; 155 156 /* 157 * Sanity check. 158 */ 159 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 160 msg_panic("dict_sdbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); 161 162 /* 163 * Optionally fold the key. 164 */ 165 if (dict->flags & DICT_FLAG_FOLD_FIX) { 166 if (dict->fold_buf == 0) 167 dict->fold_buf = vstring_alloc(10); 168 vstring_strcpy(dict->fold_buf, name); 169 name = lowercase(vstring_str(dict->fold_buf)); 170 } 171 dbm_key.dptr = (void *) name; 172 dbm_value.dptr = (void *) value; 173 dbm_key.dsize = strlen(name); 174 dbm_value.dsize = strlen(value); 175 176 /* 177 * If undecided about appending a null byte to key and value, choose a 178 * default depending on the platform. 179 */ 180 if ((dict->flags & DICT_FLAG_TRY1NULL) 181 && (dict->flags & DICT_FLAG_TRY0NULL)) { 182#ifdef DBM_NO_TRAILING_NULL 183 dict->flags &= ~DICT_FLAG_TRY1NULL; 184#else 185 dict->flags &= ~DICT_FLAG_TRY0NULL; 186#endif 187 } 188 189 /* 190 * Optionally append a null byte to key and value. 191 */ 192 if (dict->flags & DICT_FLAG_TRY1NULL) { 193 dbm_key.dsize++; 194 dbm_value.dsize++; 195 } 196 197 /* 198 * Acquire an exclusive lock. 199 */ 200 if ((dict->flags & DICT_FLAG_LOCK) 201 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) 202 msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); 203 204 /* 205 * Do the update. 206 */ 207 if ((status = sdbm_store(dict_sdbm->dbm, dbm_key, dbm_value, 208 (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0) 209 msg_fatal("error writing SDBM database %s: %m", dict_sdbm->dict.name); 210 if (status) { 211 if (dict->flags & DICT_FLAG_DUP_IGNORE) 212 /* void */ ; 213 else if (dict->flags & DICT_FLAG_DUP_WARN) 214 msg_warn("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name); 215 else 216 msg_fatal("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name); 217 } 218 219 /* 220 * Release the exclusive lock. 221 */ 222 if ((dict->flags & DICT_FLAG_LOCK) 223 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 224 msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); 225 226 return (status); 227} 228 229/* dict_sdbm_delete - delete one entry from the dictionary */ 230 231static int dict_sdbm_delete(DICT *dict, const char *name) 232{ 233 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; 234 datum dbm_key; 235 int status = 1; 236 237 dict->error = 0; 238 239 /* 240 * Sanity check. 241 */ 242 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 243 msg_panic("dict_sdbm_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_sdbm->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 sdbm_clearerr(dict_sdbm->dbm); 270 if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) { 271 if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */ 272 msg_fatal("error deleting from %s: %m", dict_sdbm->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 sdbm_clearerr(dict_sdbm->dbm); 287 if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) { 288 if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */ 289 msg_fatal("error deleting from %s: %m", dict_sdbm->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_sdbm->dict.name); 302 303 return (status); 304} 305 306/* traverse the dictionary */ 307 308static int dict_sdbm_sequence(DICT *dict, const int function, 309 const char **key, const char **value) 310{ 311 const char *myname = "dict_sdbm_sequence"; 312 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; 313 datum dbm_key; 314 datum dbm_value; 315 int status; 316 317 dict->error = 0; 318 319 /* 320 * Acquire a shared lock. 321 */ 322 if ((dict->flags & DICT_FLAG_LOCK) 323 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 324 msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); 325 326 /* 327 * Determine and execute the seek function. It returns the key. 328 */ 329 sdbm_clearerr(dict_sdbm->dbm); 330 switch (function) { 331 case DICT_SEQ_FUN_FIRST: 332 dbm_key = sdbm_firstkey(dict_sdbm->dbm); 333 break; 334 case DICT_SEQ_FUN_NEXT: 335 dbm_key = sdbm_nextkey(dict_sdbm->dbm); 336 break; 337 default: 338 msg_panic("%s: invalid function: %d", myname, function); 339 } 340 341 if (dbm_key.dptr != 0 && dbm_key.dsize > 0) { 342 343 /* 344 * Copy the key so that it is guaranteed null terminated. 345 */ 346 *key = SCOPY(dict_sdbm->key_buf, dbm_key.dptr, dbm_key.dsize); 347 348 /* 349 * Fetch the corresponding value. 350 */ 351 dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); 352 353 if (dbm_value.dptr != 0 && dbm_value.dsize > 0) { 354 355 /* 356 * Copy the value so that it is guaranteed null terminated. 357 */ 358 *value = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize); 359 status = 0; 360 } else { 361 362 /* 363 * Determine if we have hit the last record or an error 364 * condition. 365 */ 366 if (sdbm_error(dict_sdbm->dbm)) 367 msg_fatal("error seeking %s: %m", dict_sdbm->dict.name); 368 status = 1; /* no error: eof/not found 369 * (should not happen!) */ 370 } 371 } else { 372 373 /* 374 * Determine if we have hit the last record or an error condition. 375 */ 376 if (sdbm_error(dict_sdbm->dbm)) 377 msg_fatal("error seeking %s: %m", dict_sdbm->dict.name); 378 status = 1; /* no error: eof/not found */ 379 } 380 381 /* 382 * Release the shared lock. 383 */ 384 if ((dict->flags & DICT_FLAG_LOCK) 385 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 386 msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); 387 388 return (status); 389} 390 391/* dict_sdbm_close - disassociate from data base */ 392 393static void dict_sdbm_close(DICT *dict) 394{ 395 DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; 396 397 sdbm_close(dict_sdbm->dbm); 398 if (dict_sdbm->key_buf) 399 vstring_free(dict_sdbm->key_buf); 400 if (dict_sdbm->val_buf) 401 vstring_free(dict_sdbm->val_buf); 402 if (dict->fold_buf) 403 vstring_free(dict->fold_buf); 404 dict_free(dict); 405} 406 407/* dict_sdbm_open - open SDBM data base */ 408 409DICT *dict_sdbm_open(const char *path, int open_flags, int dict_flags) 410{ 411 DICT_SDBM *dict_sdbm; 412 struct stat st; 413 SDBM *dbm; 414 char *dbm_path; 415 int lock_fd; 416 417 /* 418 * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in 419 * the time domain) locking while accessing individual database records. 420 * 421 * Programs such as postmap/postalias use their own large-grained (in the 422 * time domain) locks while rewriting the entire file. 423 */ 424 if (dict_flags & DICT_FLAG_LOCK) { 425 dbm_path = concatenate(path, ".dir", (char *) 0); 426 if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0) 427 msg_fatal("open database %s: %m", dbm_path); 428 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 429 msg_fatal("shared-lock database %s for open: %m", dbm_path); 430 } 431 432 /* 433 * XXX sdbm_open() has no const in prototype. 434 */ 435 if ((dbm = sdbm_open((char *) path, open_flags, 0644)) == 0) 436 msg_fatal("open database %s.{dir,pag}: %m", path); 437 438 if (dict_flags & DICT_FLAG_LOCK) { 439 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 440 msg_fatal("unlock database %s for open: %m", dbm_path); 441 if (close(lock_fd) < 0) 442 msg_fatal("close database %s: %m", dbm_path); 443 } 444 dict_sdbm = (DICT_SDBM *) dict_alloc(DICT_TYPE_SDBM, path, sizeof(*dict_sdbm)); 445 dict_sdbm->dict.lookup = dict_sdbm_lookup; 446 dict_sdbm->dict.update = dict_sdbm_update; 447 dict_sdbm->dict.delete = dict_sdbm_delete; 448 dict_sdbm->dict.sequence = dict_sdbm_sequence; 449 dict_sdbm->dict.close = dict_sdbm_close; 450 dict_sdbm->dict.lock_fd = sdbm_dirfno(dbm); 451 dict_sdbm->dict.stat_fd = sdbm_pagfno(dbm); 452 if (fstat(dict_sdbm->dict.stat_fd, &st) < 0) 453 msg_fatal("dict_sdbm_open: fstat: %m"); 454 dict_sdbm->dict.mtime = st.st_mtime; 455 dict_sdbm->dict.owner.uid = st.st_uid; 456 dict_sdbm->dict.owner.status = (st.st_uid != 0); 457 458 /* 459 * Warn if the source file is newer than the indexed file, except when 460 * the source file changed only seconds ago. 461 */ 462 if ((dict_flags & DICT_FLAG_LOCK) != 0 463 && stat(path, &st) == 0 464 && st.st_mtime > dict_sdbm->dict.mtime 465 && st.st_mtime < time((time_t *) 0) - 100) 466 msg_warn("database %s is older than source file %s", dbm_path, path); 467 468 close_on_exec(sdbm_pagfno(dbm), CLOSE_ON_EXEC); 469 close_on_exec(sdbm_dirfno(dbm), CLOSE_ON_EXEC); 470 dict_sdbm->dict.flags = dict_flags | DICT_FLAG_FIXED; 471 if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) 472 dict_sdbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); 473 if (dict_flags & DICT_FLAG_FOLD_FIX) 474 dict_sdbm->dict.fold_buf = vstring_alloc(10); 475 dict_sdbm->dbm = dbm; 476 dict_sdbm->key_buf = 0; 477 dict_sdbm->val_buf = 0; 478 479 if ((dict_flags & DICT_FLAG_LOCK)) 480 myfree(dbm_path); 481 482 return (DICT_DEBUG (&dict_sdbm->dict)); 483} 484 485#endif 486