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