1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* plugins/kdb/lmdb/klmdb.c - KDB module using LMDB */ 3 /* 4 * Copyright (C) 2018 by the Massachusetts Institute of Technology. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 14 * * Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in 16 * the documentation and/or other materials provided with the 17 * distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 * OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 /* 34 * Thread-safety note: unlike the other two in-tree KDB modules, this module 35 * performs no mutex locking to ensure thread safety. As the KDC and kadmind 36 * are single-threaded, and applications are not allowed to access the same 37 * krb5_context in multiple threads simultaneously, there is no current need 38 * for this code to be thread-safe. If a need arises in the future, mutex 39 * locking should be added around the read_txn and load_txn fields of 40 * lmdb_context to ensure that only one thread at a time accesses those 41 * transactions. 42 */ 43 44 /* 45 * This KDB module stores principal and policy data using LMDB (Lightning 46 * Memory-Mapped Database). We use two LMDB environments, the first to hold 47 * the majority of principal and policy data (suffix ".mdb") in the "principal" 48 * and "policy" databases, and the second to hold the three non-replicated 49 * account lockout attributes (suffix ".lockout.mdb") in the "lockout" 50 * database. The KDC only needs to write to the lockout database. 51 * 52 * For iteration we create a read transaction in the main environment for the 53 * cursor. Because the iteration callback might need to create its own 54 * transactions for write operations (e.g. for kdb5_util 55 * update_princ_encryption), we set the MDB_NOTLS flag on the main environment, 56 * so that a thread can hold multiple transactions. 57 * 58 * To mitigate the overhead from MDB_NOTLS, we keep around a read_txn handle 59 * in the database context for get operations, using mdb_txn_reset() and 60 * mdb_txn_renew() between calls. 61 * 62 * For database loads, kdb5_util calls the create() method with the "temporary" 63 * db_arg, and then promotes the finished contents at the end with the 64 * promote_db() method. In this case we create or open the same LMDB 65 * environments as above, open a write_txn handle for the lifetime of the 66 * context, and empty out the principal and policy databases. On promote_db() 67 * we commit the transaction. We do not empty the lockout database and write 68 * to it non-transactionally during the load so that we don't block writes by 69 * the KDC; this isn't ideal if the load is aborted, but it shouldn't cause any 70 * practical issues. 71 * 72 * For iprop loads, kdb5_util also includes the "merge_nra" db_arg, signifying 73 * that the lockout attributes from existing principal entries should be 74 * preserved. This attribute is noted in the LMDB context, and put_principal 75 * operations will not write to the lockout database if an existing lockout 76 * entry is already present for the principal. 77 */ 78 79 #include "k5-int.h" 80 #include <kadm5/admin.h> 81 #include "kdb5.h" 82 #include "klmdb-int.h" 83 #include <lmdb.h> 84 85 /* The presence of any of these mask bits indicates a change to one of the 86 * three principal lockout attributes. */ 87 #define LOCKOUT_MASK (KADM5_LAST_SUCCESS | KADM5_LAST_FAILED | \ 88 KADM5_FAIL_AUTH_COUNT) 89 90 /* The default map size (for both environments) in megabytes. */ 91 #define DEFAULT_MAPSIZE 128 92 93 #ifndef O_CLOEXEC 94 #define O_CLOEXEC 0 95 #endif 96 97 typedef struct { 98 char *path; 99 char *lockout_path; 100 krb5_boolean temporary; /* save changes until promote_db */ 101 krb5_boolean merge_nra; /* preserve existing lockout attributes */ 102 krb5_boolean disable_last_success; 103 krb5_boolean disable_lockout; 104 krb5_boolean nosync; 105 size_t mapsize; 106 unsigned int maxreaders; 107 108 MDB_env *env; 109 MDB_env *lockout_env; 110 MDB_dbi princ_db; 111 MDB_dbi policy_db; 112 MDB_dbi lockout_db; 113 114 /* Used for get operations; each transaction is short-lived but we save the 115 * handle between calls to reduce overhead from MDB_NOTLS. */ 116 MDB_txn *read_txn; 117 118 /* Write transaction for load operations (create() with the "temporary" 119 * db_arg). */ 120 MDB_txn *load_txn; 121 } klmdb_context; 122 123 static krb5_error_code 124 klerr(krb5_context context, int err, const char *msg) 125 { 126 krb5_error_code ret; 127 klmdb_context *dbc = context->dal_handle->db_context; 128 129 /* Pass through system errors; map MDB errors to a com_err code. */ 130 ret = (err > 0) ? err : KRB5_KDB_ACCESS_ERROR; 131 132 k5_setmsg(context, ret, _("%s (path: %s): %s"), msg, dbc->path, 133 mdb_strerror(err)); 134 return ret; 135 } 136 137 /* Using db_args and the profile, create a DB context inside context and 138 * initialize its configurable parameters. */ 139 static krb5_error_code 140 configure_context(krb5_context context, const char *conf_section, 141 char *const *db_args) 142 { 143 krb5_error_code ret; 144 klmdb_context *dbc; 145 char *pval = NULL; 146 const char *path = NULL; 147 profile_t profile = context->profile; 148 size_t i; 149 int bval, ival; 150 151 dbc = k5alloc(sizeof(*dbc), &ret); 152 if (dbc == NULL) 153 return ret; 154 context->dal_handle->db_context = dbc; 155 156 for (i = 0; db_args != NULL && db_args[i] != NULL; i++) { 157 if (strcmp(db_args[i], "temporary") == 0) { 158 dbc->temporary = TRUE; 159 } else if (strcmp(db_args[i], "merge_nra") == 0) { 160 dbc->merge_nra = TRUE; 161 } else if (strncmp(db_args[i], "dbname=", 7) == 0) { 162 path = db_args[i] + 7; 163 } else { 164 ret = EINVAL; 165 k5_setmsg(context, ret, _("Unsupported argument \"%s\" for LMDB"), 166 db_args[i]); 167 goto cleanup; 168 } 169 } 170 171 if (path == NULL) { 172 /* Check for database_name in the db_module section. */ 173 ret = profile_get_string(profile, KDB_MODULE_SECTION, conf_section, 174 KRB5_CONF_DATABASE_NAME, NULL, &pval); 175 if (!ret && pval == NULL) { 176 /* For compatibility, check for database_name in the realm. */ 177 ret = profile_get_string(profile, KDB_REALM_SECTION, 178 KRB5_DB_GET_REALM(context), 179 KRB5_CONF_DATABASE_NAME, DEFAULT_KDB_FILE, 180 &pval); 181 } 182 if (ret) 183 goto cleanup; 184 path = pval; 185 } 186 187 if (asprintf(&dbc->path, "%s.mdb", path) < 0) { 188 dbc->path = NULL; 189 ret = ENOMEM; 190 goto cleanup; 191 } 192 if (asprintf(&dbc->lockout_path, "%s.lockout.mdb", path) < 0) { 193 dbc->lockout_path = NULL; 194 ret = ENOMEM; 195 goto cleanup; 196 } 197 198 ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section, 199 KRB5_CONF_DISABLE_LAST_SUCCESS, FALSE, &bval); 200 if (ret) 201 goto cleanup; 202 dbc->disable_last_success = bval; 203 204 ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section, 205 KRB5_CONF_DISABLE_LOCKOUT, FALSE, &bval); 206 if (ret) 207 goto cleanup; 208 dbc->disable_lockout = bval; 209 210 ret = profile_get_integer(profile, KDB_MODULE_SECTION, conf_section, 211 KRB5_CONF_MAPSIZE, DEFAULT_MAPSIZE, &ival); 212 if (ret) 213 goto cleanup; 214 dbc->mapsize = (size_t)ival * 1024 * 1024; 215 216 ret = profile_get_integer(profile, KDB_MODULE_SECTION, conf_section, 217 KRB5_CONF_MAX_READERS, 0, &ival); 218 if (ret) 219 goto cleanup; 220 dbc->maxreaders = ival; 221 222 ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section, 223 KRB5_CONF_NOSYNC, FALSE, &bval); 224 if (ret) 225 goto cleanup; 226 dbc->nosync = bval; 227 228 cleanup: 229 profile_release_string(pval); 230 return ret; 231 } 232 233 static krb5_error_code 234 open_lmdb_env(krb5_context context, klmdb_context *dbc, 235 krb5_boolean is_lockout, krb5_boolean readonly, 236 MDB_env **env_out) 237 { 238 krb5_error_code ret; 239 const char *path = is_lockout ? dbc->lockout_path : dbc->path; 240 unsigned int flags; 241 MDB_env *env = NULL; 242 int err; 243 244 *env_out = NULL; 245 246 err = mdb_env_create(&env); 247 if (err) 248 goto lmdb_error; 249 250 /* Use a pair of files instead of a subdirectory. */ 251 flags = MDB_NOSUBDIR; 252 253 /* 254 * For the primary database, tie read transaction locktable slots to the 255 * transaction and not the thread, so read transactions for iteration 256 * cursors can coexist with short-lived transactions for operations invoked 257 * by the iteration callback.. 258 */ 259 if (!is_lockout) 260 flags |= MDB_NOTLS; 261 262 if (readonly) 263 flags |= MDB_RDONLY; 264 265 /* Durability for lockout records is never worth the performance penalty. 266 * For the primary environment it might be, so we make it configurable. */ 267 if (is_lockout || dbc->nosync) 268 flags |= MDB_NOSYNC; 269 270 /* We use one database in the lockout env, two in the primary env. */ 271 err = mdb_env_set_maxdbs(env, is_lockout ? 1 : 2); 272 if (err) 273 goto lmdb_error; 274 275 if (dbc->mapsize) { 276 err = mdb_env_set_mapsize(env, dbc->mapsize); 277 if (err) 278 goto lmdb_error; 279 } 280 281 if (dbc->maxreaders) { 282 err = mdb_env_set_maxreaders(env, dbc->maxreaders); 283 if (err) 284 goto lmdb_error; 285 } 286 287 err = mdb_env_open(env, path, flags, S_IRUSR | S_IWUSR); 288 if (err) 289 goto lmdb_error; 290 291 *env_out = env; 292 return 0; 293 294 lmdb_error: 295 ret = klerr(context, err, _("LMDB environment open failure")); 296 mdb_env_close(env); 297 return ret; 298 } 299 300 /* Read a key from the primary environment, using a saved read transaction from 301 * the database context. Return KRB5_KDB_NOENTRY if the key is not found. */ 302 static krb5_error_code 303 fetch(krb5_context context, MDB_dbi db, MDB_val *key, MDB_val *val_out) 304 { 305 krb5_error_code ret = 0; 306 klmdb_context *dbc = context->dal_handle->db_context; 307 int err; 308 309 if (dbc->read_txn == NULL) 310 err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &dbc->read_txn); 311 else 312 err = mdb_txn_renew(dbc->read_txn); 313 314 if (!err) 315 err = mdb_get(dbc->read_txn, db, key, val_out); 316 317 if (err == MDB_NOTFOUND) 318 ret = KRB5_KDB_NOENTRY; 319 else if (err) 320 ret = klerr(context, err, _("LMDB read failure")); 321 322 mdb_txn_reset(dbc->read_txn); 323 return ret; 324 } 325 326 /* If we are using a lockout database, try to fetch the lockout attributes for 327 * key and set them in entry. */ 328 static void 329 fetch_lockout(krb5_context context, MDB_val *key, krb5_db_entry *entry) 330 { 331 klmdb_context *dbc = context->dal_handle->db_context; 332 MDB_txn *txn = NULL; 333 MDB_val val; 334 int err; 335 336 if (dbc->lockout_env == NULL) 337 return; 338 err = mdb_txn_begin(dbc->lockout_env, NULL, MDB_RDONLY, &txn); 339 if (!err) 340 err = mdb_get(txn, dbc->lockout_db, key, &val); 341 if (!err && val.mv_size >= LOCKOUT_RECORD_LEN) 342 klmdb_decode_princ_lockout(context, entry, val.mv_data); 343 mdb_txn_abort(txn); 344 } 345 346 /* 347 * Store a value for key in the specified database within the primary 348 * environment. Use the saved load transaction if one is present, or a 349 * temporary write transaction if not. If no_overwrite is true and the key 350 * already exists, return KRB5_KDB_INUSE. If must_overwrite is true and the 351 * key does not already exist, return KRB5_KDB_NOENTRY. 352 */ 353 static krb5_error_code 354 put(krb5_context context, MDB_dbi db, char *keystr, uint8_t *bytes, size_t len, 355 krb5_boolean no_overwrite, krb5_boolean must_overwrite) 356 { 357 klmdb_context *dbc = context->dal_handle->db_context; 358 unsigned int putflags = no_overwrite ? MDB_NOOVERWRITE : 0; 359 MDB_txn *temp_txn = NULL, *txn; 360 MDB_val key = { strlen(keystr), keystr }, val = { len, bytes }, dummy; 361 int err; 362 363 if (dbc->load_txn != NULL) { 364 txn = dbc->load_txn; 365 } else { 366 err = mdb_txn_begin(dbc->env, NULL, 0, &temp_txn); 367 if (err) 368 goto error; 369 txn = temp_txn; 370 } 371 372 if (must_overwrite && mdb_get(txn, db, &key, &dummy) == MDB_NOTFOUND) { 373 mdb_txn_abort(temp_txn); 374 return KRB5_KDB_NOENTRY; 375 } 376 377 err = mdb_put(txn, db, &key, &val, putflags); 378 if (err) 379 goto error; 380 381 if (temp_txn != NULL) { 382 err = mdb_txn_commit(temp_txn); 383 temp_txn = NULL; 384 if (err) 385 goto error; 386 } 387 388 return 0; 389 390 error: 391 mdb_txn_abort(temp_txn); 392 if (err == MDB_KEYEXIST) 393 return KRB5_KDB_INUSE; 394 else 395 return klerr(context, err, _("LMDB write failure")); 396 } 397 398 /* Delete an entry from the specified env and database, using a temporary write 399 * transaction. Return KRB5_KDB_NOENTRY if the key does not exist. */ 400 static krb5_error_code 401 del(krb5_context context, MDB_env *env, MDB_dbi db, char *keystr) 402 { 403 krb5_error_code ret = 0; 404 MDB_txn *txn = NULL; 405 MDB_val key = { strlen(keystr), keystr }; 406 int err; 407 408 err = mdb_txn_begin(env, NULL, 0, &txn); 409 if (!err) 410 err = mdb_del(txn, db, &key, NULL); 411 if (!err) { 412 err = mdb_txn_commit(txn); 413 txn = NULL; 414 } 415 416 if (err == MDB_NOTFOUND) 417 ret = KRB5_KDB_NOENTRY; 418 else if (err) 419 ret = klerr(context, err, _("LMDB delete failure")); 420 421 mdb_txn_abort(txn); 422 return ret; 423 } 424 425 /* Zero out and unlink filename. */ 426 static krb5_error_code 427 destroy_file(const char *filename) 428 { 429 krb5_error_code ret; 430 struct stat st; 431 ssize_t len; 432 off_t pos; 433 uint8_t buf[BUFSIZ], zbuf[BUFSIZ] = { 0 }; 434 int fd; 435 436 fd = open(filename, O_RDWR | O_CLOEXEC, 0); 437 if (fd < 0) 438 return errno; 439 set_cloexec_fd(fd); 440 if (fstat(fd, &st) == -1) 441 goto error; 442 443 memset(zbuf, 0, BUFSIZ); 444 pos = 0; 445 while (pos < st.st_size) { 446 len = read(fd, buf, BUFSIZ); 447 if (len < 0) 448 goto error; 449 /* Only rewrite the block if it's not already zeroed, in case the file 450 * is sparse. */ 451 if (memcmp(buf, zbuf, len) != 0) { 452 (void)lseek(fd, pos, SEEK_SET); 453 len = write(fd, zbuf, len); 454 if (len < 0) 455 goto error; 456 } 457 pos += len; 458 } 459 close(fd); 460 461 if (unlink(filename) != 0) 462 return errno; 463 return 0; 464 465 error: 466 ret = errno; 467 close(fd); 468 return ret; 469 } 470 471 static krb5_error_code 472 klmdb_lib_init(void) 473 { 474 return 0; 475 } 476 477 static krb5_error_code 478 klmdb_lib_cleanup(void) 479 { 480 return 0; 481 } 482 483 static krb5_error_code 484 klmdb_fini(krb5_context context) 485 { 486 klmdb_context *dbc; 487 488 dbc = context->dal_handle->db_context; 489 if (dbc == NULL) 490 return 0; 491 mdb_txn_abort(dbc->read_txn); 492 mdb_txn_abort(dbc->load_txn); 493 mdb_env_close(dbc->env); 494 mdb_env_close(dbc->lockout_env); 495 free(dbc->path); 496 free(dbc->lockout_path); 497 free(dbc); 498 context->dal_handle->db_context = NULL; 499 return 0; 500 } 501 502 static krb5_error_code 503 klmdb_open(krb5_context context, char *conf_section, char **db_args, int mode) 504 { 505 krb5_error_code ret; 506 klmdb_context *dbc; 507 krb5_boolean readonly; 508 MDB_txn *txn = NULL; 509 struct stat st; 510 int err; 511 512 if (context->dal_handle->db_context != NULL) 513 return 0; 514 515 ret = configure_context(context, conf_section, db_args); 516 if (ret) 517 return ret; 518 dbc = context->dal_handle->db_context; 519 520 if (stat(dbc->path, &st) != 0) { 521 ret = ENOENT; 522 k5_setmsg(context, ret, _("LMDB file %s does not exist"), dbc->path); 523 goto error; 524 } 525 526 /* Open the primary environment and databases. The KDC can open this 527 * environment read-only. */ 528 readonly = (mode & KRB5_KDB_OPEN_RO) || (mode & KRB5_KDB_SRV_TYPE_KDC); 529 ret = open_lmdb_env(context, dbc, FALSE, readonly, &dbc->env); 530 if (ret) 531 goto error; 532 err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &txn); 533 if (err) 534 goto lmdb_error; 535 err = mdb_dbi_open(txn, "principal", 0, &dbc->princ_db); 536 if (err) 537 goto lmdb_error; 538 err = mdb_dbi_open(txn, "policy", 0, &dbc->policy_db); 539 if (err) 540 goto lmdb_error; 541 err = mdb_txn_commit(txn); 542 txn = NULL; 543 if (err) 544 goto lmdb_error; 545 546 /* Open the lockout environment and database if we will need it. */ 547 if (!dbc->disable_last_success || !dbc->disable_lockout) { 548 readonly = !!(mode & KRB5_KDB_OPEN_RO); 549 ret = open_lmdb_env(context, dbc, TRUE, readonly, &dbc->lockout_env); 550 if (ret) 551 goto error; 552 err = mdb_txn_begin(dbc->lockout_env, NULL, MDB_RDONLY, &txn); 553 if (err) 554 goto lmdb_error; 555 err = mdb_dbi_open(txn, "lockout", 0, &dbc->lockout_db); 556 if (err) 557 goto lmdb_error; 558 err = mdb_txn_commit(txn); 559 txn = NULL; 560 if (err) 561 goto lmdb_error; 562 } 563 564 return 0; 565 566 lmdb_error: 567 ret = klerr(context, err, _("LMDB open failure")); 568 error: 569 mdb_txn_abort(txn); 570 klmdb_fini(context); 571 return ret; 572 } 573 574 static krb5_error_code 575 klmdb_create(krb5_context context, char *conf_section, char **db_args) 576 { 577 krb5_error_code ret; 578 klmdb_context *dbc; 579 MDB_txn *txn = NULL; 580 struct stat st; 581 int err; 582 583 if (context->dal_handle->db_context != NULL) 584 return 0; 585 586 ret = configure_context(context, conf_section, db_args); 587 if (ret) 588 return ret; 589 dbc = context->dal_handle->db_context; 590 591 if (!dbc->temporary) { 592 if (stat(dbc->path, &st) == 0) { 593 ret = ENOENT; 594 k5_setmsg(context, ret, _("LMDB file %s already exists"), 595 dbc->path); 596 goto error; 597 } 598 } 599 600 /* Open (and create if necessary) the LMDB environments. */ 601 ret = open_lmdb_env(context, dbc, FALSE, FALSE, &dbc->env); 602 if (ret) 603 goto error; 604 ret = open_lmdb_env(context, dbc, TRUE, FALSE, &dbc->lockout_env); 605 if (ret) 606 goto error; 607 608 /* Open the primary databases, creating them if they don't exist. */ 609 err = mdb_txn_begin(dbc->env, NULL, 0, &txn); 610 if (err) 611 goto lmdb_error; 612 err = mdb_dbi_open(txn, "principal", MDB_CREATE, &dbc->princ_db); 613 if (err) 614 goto lmdb_error; 615 err = mdb_dbi_open(txn, "policy", MDB_CREATE, &dbc->policy_db); 616 if (err) 617 goto lmdb_error; 618 err = mdb_txn_commit(txn); 619 txn = NULL; 620 if (err) 621 goto lmdb_error; 622 623 /* Create the lockout database if it doesn't exist. */ 624 err = mdb_txn_begin(dbc->lockout_env, NULL, 0, &txn); 625 if (err) 626 goto lmdb_error; 627 err = mdb_dbi_open(txn, "lockout", MDB_CREATE, &dbc->lockout_db); 628 if (err) 629 goto lmdb_error; 630 err = mdb_txn_commit(txn); 631 txn = NULL; 632 if (err) 633 goto lmdb_error; 634 635 if (dbc->temporary) { 636 /* Create a load transaction and empty the primary databases within 637 * it. */ 638 err = mdb_txn_begin(dbc->env, NULL, 0, &dbc->load_txn); 639 if (err) 640 goto lmdb_error; 641 err = mdb_drop(dbc->load_txn, dbc->princ_db, 0); 642 if (err) 643 goto lmdb_error; 644 err = mdb_drop(dbc->load_txn, dbc->policy_db, 0); 645 if (err) 646 goto lmdb_error; 647 } 648 649 /* Close the lockout environment if we won't need it. */ 650 if (dbc->disable_last_success && dbc->disable_lockout) { 651 mdb_env_close(dbc->lockout_env); 652 dbc->lockout_env = NULL; 653 dbc->lockout_db = 0; 654 } 655 656 return 0; 657 658 lmdb_error: 659 ret = klerr(context, err, _("LMDB create error")); 660 error: 661 mdb_txn_abort(txn); 662 klmdb_fini(context); 663 return ret; 664 } 665 666 /* Unlink the "-lock" extension of path. */ 667 static krb5_error_code 668 unlink_lock_file(krb5_context context, const char *path) 669 { 670 char *lock_path; 671 int st; 672 673 if (asprintf(&lock_path, "%s-lock", path) < 0) 674 return ENOMEM; 675 st = unlink(lock_path); 676 if (st) 677 k5_prependmsg(context, st, _("Could not unlink %s"), lock_path); 678 free(lock_path); 679 return st; 680 } 681 682 static krb5_error_code 683 klmdb_destroy(krb5_context context, char *conf_section, char **db_args) 684 { 685 krb5_error_code ret; 686 klmdb_context *dbc; 687 688 if (context->dal_handle->db_context != NULL) 689 klmdb_fini(context); 690 ret = configure_context(context, conf_section, db_args); 691 if (ret) 692 goto cleanup; 693 dbc = context->dal_handle->db_context; 694 695 ret = destroy_file(dbc->path); 696 if (ret) 697 goto cleanup; 698 ret = unlink_lock_file(context, dbc->path); 699 if (ret) 700 goto cleanup; 701 702 ret = destroy_file(dbc->lockout_path); 703 if (ret) 704 goto cleanup; 705 ret = unlink_lock_file(context, dbc->lockout_path); 706 707 cleanup: 708 klmdb_fini(context); 709 return ret; 710 } 711 712 static krb5_error_code 713 klmdb_get_principal(krb5_context context, krb5_const_principal searchfor, 714 unsigned int flags, krb5_db_entry **entry_out) 715 { 716 krb5_error_code ret; 717 klmdb_context *dbc = context->dal_handle->db_context; 718 MDB_val key, val; 719 char *name = NULL; 720 721 *entry_out = NULL; 722 if (dbc == NULL) 723 return KRB5_KDB_DBNOTINITED; 724 725 ret = krb5_unparse_name(context, searchfor, &name); 726 if (ret) 727 goto cleanup; 728 729 key.mv_data = name; 730 key.mv_size = strlen(name); 731 ret = fetch(context, dbc->princ_db, &key, &val); 732 if (ret) 733 goto cleanup; 734 735 ret = klmdb_decode_princ(context, name, strlen(name), 736 val.mv_data, val.mv_size, entry_out); 737 if (ret) 738 goto cleanup; 739 740 fetch_lockout(context, &key, *entry_out); 741 742 cleanup: 743 krb5_free_unparsed_name(context, name); 744 return ret; 745 } 746 747 static krb5_error_code 748 klmdb_put_principal(krb5_context context, krb5_db_entry *entry, char **db_args) 749 { 750 krb5_error_code ret; 751 klmdb_context *dbc = context->dal_handle->db_context; 752 MDB_val key, val, dummy; 753 MDB_txn *txn = NULL; 754 uint8_t lockout[LOCKOUT_RECORD_LEN], *enc; 755 size_t len; 756 char *name = NULL; 757 int err; 758 759 if (db_args != NULL) { 760 /* This module does not support DB arguments for put_principal. */ 761 k5_setmsg(context, EINVAL, _("Unsupported argument \"%s\" for lmdb"), 762 db_args[0]); 763 return EINVAL; 764 } 765 766 if (dbc == NULL) 767 return KRB5_KDB_DBNOTINITED; 768 769 ret = krb5_unparse_name(context, entry->princ, &name); 770 if (ret) 771 goto cleanup; 772 773 ret = klmdb_encode_princ(context, entry, &enc, &len); 774 if (ret) 775 goto cleanup; 776 ret = put(context, dbc->princ_db, name, enc, len, FALSE, FALSE); 777 free(enc); 778 if (ret) 779 goto cleanup; 780 781 /* 782 * Write the lockout attributes to the lockout database if we are using 783 * one. During a load operation, changes to lockout attributes will become 784 * visible before the load is finished, which is an acceptable compromise 785 * on load atomicity. 786 */ 787 if (dbc->lockout_env != NULL && 788 (entry->mask & (LOCKOUT_MASK | KADM5_PRINCIPAL))) { 789 key.mv_data = name; 790 key.mv_size = strlen(name); 791 klmdb_encode_princ_lockout(context, entry, lockout); 792 val.mv_data = lockout; 793 val.mv_size = sizeof(lockout); 794 err = mdb_txn_begin(dbc->lockout_env, NULL, 0, &txn); 795 if (!err && dbc->merge_nra) { 796 /* During an iprop load, do not change existing lockout entries. */ 797 if (mdb_get(txn, dbc->lockout_db, &key, &dummy) == 0) 798 goto cleanup; 799 } 800 if (!err) 801 err = mdb_put(txn, dbc->lockout_db, &key, &val, 0); 802 if (!err) { 803 err = mdb_txn_commit(txn); 804 txn = NULL; 805 } 806 if (err) { 807 ret = klerr(context, err, _("LMDB lockout write failure")); 808 goto cleanup; 809 } 810 } 811 812 cleanup: 813 mdb_txn_abort(txn); 814 krb5_free_unparsed_name(context, name); 815 return ret; 816 } 817 818 static krb5_error_code 819 klmdb_delete_principal(krb5_context context, krb5_const_principal searchfor) 820 { 821 krb5_error_code ret; 822 klmdb_context *dbc = context->dal_handle->db_context; 823 char *name; 824 825 if (dbc == NULL) 826 return KRB5_KDB_DBNOTINITED; 827 828 ret = krb5_unparse_name(context, searchfor, &name); 829 if (ret) 830 return ret; 831 832 ret = del(context, dbc->env, dbc->princ_db, name); 833 if (!ret && dbc->lockout_env != NULL) 834 (void)del(context, dbc->lockout_env, dbc->lockout_db, name); 835 836 krb5_free_unparsed_name(context, name); 837 return ret; 838 } 839 840 static krb5_error_code 841 klmdb_iterate(krb5_context context, char *match_expr, 842 krb5_error_code (*func)(void *, krb5_db_entry *), void *arg, 843 krb5_flags iterflags) 844 { 845 krb5_error_code ret; 846 klmdb_context *dbc = context->dal_handle->db_context; 847 krb5_db_entry *entry; 848 MDB_txn *txn = NULL; 849 MDB_cursor *cursor = NULL; 850 MDB_val key, val; 851 MDB_cursor_op op = (iterflags & KRB5_DB_ITER_REV) ? MDB_PREV : MDB_NEXT; 852 int err; 853 854 if (dbc == NULL) 855 return KRB5_KDB_DBNOTINITED; 856 857 err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &txn); 858 if (err) 859 goto lmdb_error; 860 err = mdb_cursor_open(txn, dbc->princ_db, &cursor); 861 if (err) 862 goto lmdb_error; 863 for (;;) { 864 err = mdb_cursor_get(cursor, &key, &val, op); 865 if (err == MDB_NOTFOUND) 866 break; 867 if (err) 868 goto lmdb_error; 869 ret = klmdb_decode_princ(context, key.mv_data, key.mv_size, 870 val.mv_data, val.mv_size, &entry); 871 if (ret) 872 goto cleanup; 873 fetch_lockout(context, &key, entry); 874 ret = (*func)(arg, entry); 875 krb5_db_free_principal(context, entry); 876 if (ret) 877 goto cleanup; 878 } 879 ret = 0; 880 goto cleanup; 881 882 lmdb_error: 883 ret = klerr(context, err, _("LMDB principal iteration failure")); 884 cleanup: 885 mdb_cursor_close(cursor); 886 mdb_txn_abort(txn); 887 return ret; 888 } 889 890 krb5_error_code 891 klmdb_get_policy(krb5_context context, char *name, osa_policy_ent_t *policy) 892 { 893 krb5_error_code ret; 894 klmdb_context *dbc = context->dal_handle->db_context; 895 MDB_val key, val; 896 897 *policy = NULL; 898 if (dbc == NULL) 899 return KRB5_KDB_DBNOTINITED; 900 901 key.mv_data = name; 902 key.mv_size = strlen(name); 903 ret = fetch(context, dbc->policy_db, &key, &val); 904 if (ret) 905 return ret; 906 return klmdb_decode_policy(context, name, strlen(name), 907 val.mv_data, val.mv_size, policy); 908 } 909 910 static krb5_error_code 911 klmdb_create_policy(krb5_context context, osa_policy_ent_t policy) 912 { 913 krb5_error_code ret; 914 klmdb_context *dbc = context->dal_handle->db_context; 915 uint8_t *enc; 916 size_t len; 917 918 if (dbc == NULL) 919 return KRB5_KDB_DBNOTINITED; 920 921 ret = klmdb_encode_policy(context, policy, &enc, &len); 922 if (ret) 923 return ret; 924 ret = put(context, dbc->policy_db, policy->name, enc, len, TRUE, FALSE); 925 free(enc); 926 return ret; 927 } 928 929 static krb5_error_code 930 klmdb_put_policy(krb5_context context, osa_policy_ent_t policy) 931 { 932 krb5_error_code ret; 933 klmdb_context *dbc = context->dal_handle->db_context; 934 uint8_t *enc; 935 size_t len; 936 937 if (dbc == NULL) 938 return KRB5_KDB_DBNOTINITED; 939 940 ret = klmdb_encode_policy(context, policy, &enc, &len); 941 if (ret) 942 return ret; 943 ret = put(context, dbc->policy_db, policy->name, enc, len, FALSE, TRUE); 944 free(enc); 945 return ret; 946 } 947 948 static krb5_error_code 949 klmdb_iter_policy(krb5_context context, char *match_entry, 950 osa_adb_iter_policy_func func, void *arg) 951 { 952 krb5_error_code ret; 953 klmdb_context *dbc = context->dal_handle->db_context; 954 osa_policy_ent_t pol; 955 MDB_txn *txn = NULL; 956 MDB_cursor *cursor = NULL; 957 MDB_val key, val; 958 int err; 959 960 if (dbc == NULL) 961 return KRB5_KDB_DBNOTINITED; 962 963 err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &txn); 964 if (err) 965 goto lmdb_error; 966 err = mdb_cursor_open(txn, dbc->policy_db, &cursor); 967 if (err) 968 goto lmdb_error; 969 for (;;) { 970 err = mdb_cursor_get(cursor, &key, &val, MDB_NEXT); 971 if (err == MDB_NOTFOUND) 972 break; 973 if (err) 974 goto lmdb_error; 975 ret = klmdb_decode_policy(context, key.mv_data, key.mv_size, 976 val.mv_data, val.mv_size, &pol); 977 if (ret) 978 goto cleanup; 979 (*func)(arg, pol); 980 krb5_db_free_policy(context, pol); 981 } 982 ret = 0; 983 goto cleanup; 984 985 lmdb_error: 986 ret = klerr(context, err, _("LMDB policy iteration failure")); 987 cleanup: 988 mdb_cursor_close(cursor); 989 mdb_txn_abort(txn); 990 return ret; 991 } 992 993 static krb5_error_code 994 klmdb_delete_policy(krb5_context context, char *policy) 995 { 996 klmdb_context *dbc = context->dal_handle->db_context; 997 998 if (dbc == NULL) 999 return KRB5_KDB_DBNOTINITED; 1000 return del(context, dbc->env, dbc->policy_db, policy); 1001 } 1002 1003 static krb5_error_code 1004 klmdb_promote_db(krb5_context context, char *conf_section, char **db_args) 1005 { 1006 krb5_error_code ret = 0; 1007 klmdb_context *dbc = context->dal_handle->db_context; 1008 int err; 1009 1010 if (dbc == NULL) 1011 return KRB5_KDB_DBNOTINITED; 1012 if (dbc->load_txn == NULL) 1013 return EINVAL; 1014 err = mdb_txn_commit(dbc->load_txn); 1015 dbc->load_txn = NULL; 1016 if (err) 1017 ret = klerr(context, err, _("LMDB transaction commit failure")); 1018 klmdb_fini(context); 1019 return ret; 1020 } 1021 1022 static krb5_error_code 1023 klmdb_check_policy_as(krb5_context context, krb5_kdc_req *request, 1024 krb5_db_entry *client, krb5_db_entry *server, 1025 krb5_timestamp kdc_time, const char **status, 1026 krb5_pa_data ***e_data) 1027 { 1028 krb5_error_code ret; 1029 klmdb_context *dbc = context->dal_handle->db_context; 1030 1031 if (dbc->disable_lockout) 1032 return 0; 1033 1034 ret = klmdb_lockout_check_policy(context, client, kdc_time); 1035 if (ret == KRB5KDC_ERR_CLIENT_REVOKED) 1036 *status = "LOCKED_OUT"; 1037 return ret; 1038 } 1039 1040 static void 1041 klmdb_audit_as_req(krb5_context context, krb5_kdc_req *request, 1042 const krb5_address *local_addr, 1043 const krb5_address *remote_addr, krb5_db_entry *client, 1044 krb5_db_entry *server, krb5_timestamp authtime, 1045 krb5_error_code status) 1046 { 1047 klmdb_context *dbc = context->dal_handle->db_context; 1048 1049 (void)klmdb_lockout_audit(context, client, authtime, status, 1050 dbc->disable_last_success, dbc->disable_lockout); 1051 } 1052 1053 krb5_error_code 1054 klmdb_update_lockout(krb5_context context, krb5_db_entry *entry, 1055 krb5_timestamp stamp, krb5_boolean zero_fail_count, 1056 krb5_boolean set_last_success, 1057 krb5_boolean set_last_failure) 1058 { 1059 krb5_error_code ret; 1060 klmdb_context *dbc = context->dal_handle->db_context; 1061 krb5_db_entry dummy = { 0 }; 1062 uint8_t lockout[LOCKOUT_RECORD_LEN]; 1063 MDB_txn *txn = NULL; 1064 MDB_val key, val; 1065 char *name = NULL; 1066 int err; 1067 1068 if (dbc == NULL) 1069 return KRB5_KDB_DBNOTINITED; 1070 if (dbc->lockout_env == NULL) 1071 return 0; 1072 if (!zero_fail_count && !set_last_success && !set_last_failure) 1073 return 0; 1074 1075 ret = krb5_unparse_name(context, entry->princ, &name); 1076 if (ret) 1077 goto cleanup; 1078 key.mv_data = name; 1079 key.mv_size = strlen(name); 1080 1081 err = mdb_txn_begin(dbc->lockout_env, NULL, 0, &txn); 1082 if (err) 1083 goto lmdb_error; 1084 /* Fetch base lockout info within txn so we update transactionally. */ 1085 err = mdb_get(txn, dbc->lockout_db, &key, &val); 1086 if (!err && val.mv_size >= LOCKOUT_RECORD_LEN) { 1087 klmdb_decode_princ_lockout(context, &dummy, val.mv_data); 1088 } else { 1089 dummy.last_success = entry->last_success; 1090 dummy.last_failed = entry->last_failed; 1091 dummy.fail_auth_count = entry->fail_auth_count; 1092 } 1093 1094 if (zero_fail_count) 1095 dummy.fail_auth_count = 0; 1096 if (set_last_success) 1097 dummy.last_success = stamp; 1098 if (set_last_failure) { 1099 dummy.last_failed = stamp; 1100 dummy.fail_auth_count++; 1101 } 1102 1103 klmdb_encode_princ_lockout(context, &dummy, lockout); 1104 val.mv_data = lockout; 1105 val.mv_size = sizeof(lockout); 1106 err = mdb_put(txn, dbc->lockout_db, &key, &val, 0); 1107 if (err) 1108 goto lmdb_error; 1109 err = mdb_txn_commit(txn); 1110 txn = NULL; 1111 if (err) 1112 goto lmdb_error; 1113 goto cleanup; 1114 1115 lmdb_error: 1116 ret = klerr(context, err, _("LMDB lockout update failure")); 1117 cleanup: 1118 krb5_free_unparsed_name(context, name); 1119 mdb_txn_abort(txn); 1120 return 0; 1121 } 1122 1123 kdb_vftabl PLUGIN_SYMBOL_NAME(krb5_lmdb, kdb_function_table) = { 1124 .maj_ver = KRB5_KDB_DAL_MAJOR_VERSION, 1125 .min_ver = 0, 1126 .init_library = klmdb_lib_init, 1127 .fini_library = klmdb_lib_cleanup, 1128 .init_module = klmdb_open, 1129 .fini_module = klmdb_fini, 1130 .create = klmdb_create, 1131 .destroy = klmdb_destroy, 1132 .get_principal = klmdb_get_principal, 1133 .put_principal = klmdb_put_principal, 1134 .delete_principal = klmdb_delete_principal, 1135 .iterate = klmdb_iterate, 1136 .create_policy = klmdb_create_policy, 1137 .get_policy = klmdb_get_policy, 1138 .put_policy = klmdb_put_policy, 1139 .iter_policy = klmdb_iter_policy, 1140 .delete_policy = klmdb_delete_policy, 1141 .promote_db = klmdb_promote_db, 1142 .check_policy_as = klmdb_check_policy_as, 1143 .audit_as_req = klmdb_audit_as_req 1144 }; 1145