Path: blob/main/crypto/krb5/src/plugins/kdb/lmdb/kdb_lmdb.c
34914 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/* plugins/kdb/lmdb/klmdb.c - KDB module using LMDB */2/*3* Copyright (C) 2018 by the Massachusetts Institute of Technology.4* All rights reserved.5*6* Redistribution and use in source and binary forms, with or without7* modification, are permitted provided that the following conditions8* are met:9*10* * Redistributions of source code must retain the above copyright11* notice, this list of conditions and the following disclaimer.12*13* * Redistributions in binary form must reproduce the above copyright14* notice, this list of conditions and the following disclaimer in15* the documentation and/or other materials provided with the16* distribution.17*18* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS19* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT20* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS21* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE22* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,23* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES24* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR25* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)26* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,27* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)28* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED29* OF THE POSSIBILITY OF SUCH DAMAGE.30*/3132/*33* Thread-safety note: unlike the other two in-tree KDB modules, this module34* performs no mutex locking to ensure thread safety. As the KDC and kadmind35* are single-threaded, and applications are not allowed to access the same36* krb5_context in multiple threads simultaneously, there is no current need37* for this code to be thread-safe. If a need arises in the future, mutex38* locking should be added around the read_txn and load_txn fields of39* lmdb_context to ensure that only one thread at a time accesses those40* transactions.41*/4243/*44* This KDB module stores principal and policy data using LMDB (Lightning45* Memory-Mapped Database). We use two LMDB environments, the first to hold46* the majority of principal and policy data (suffix ".mdb") in the "principal"47* and "policy" databases, and the second to hold the three non-replicated48* account lockout attributes (suffix ".lockout.mdb") in the "lockout"49* database. The KDC only needs to write to the lockout database.50*51* For iteration we create a read transaction in the main environment for the52* cursor. Because the iteration callback might need to create its own53* transactions for write operations (e.g. for kdb5_util54* update_princ_encryption), we set the MDB_NOTLS flag on the main environment,55* so that a thread can hold multiple transactions.56*57* To mitigate the overhead from MDB_NOTLS, we keep around a read_txn handle58* in the database context for get operations, using mdb_txn_reset() and59* mdb_txn_renew() between calls.60*61* For database loads, kdb5_util calls the create() method with the "temporary"62* db_arg, and then promotes the finished contents at the end with the63* promote_db() method. In this case we create or open the same LMDB64* environments as above, open a write_txn handle for the lifetime of the65* context, and empty out the principal and policy databases. On promote_db()66* we commit the transaction. We do not empty the lockout database and write67* to it non-transactionally during the load so that we don't block writes by68* the KDC; this isn't ideal if the load is aborted, but it shouldn't cause any69* practical issues.70*71* For iprop loads, kdb5_util also includes the "merge_nra" db_arg, signifying72* that the lockout attributes from existing principal entries should be73* preserved. This attribute is noted in the LMDB context, and put_principal74* operations will not write to the lockout database if an existing lockout75* entry is already present for the principal.76*/7778#include "k5-int.h"79#include <kadm5/admin.h>80#include "kdb5.h"81#include "klmdb-int.h"82#include <lmdb.h>8384/* The presence of any of these mask bits indicates a change to one of the85* three principal lockout attributes. */86#define LOCKOUT_MASK (KADM5_LAST_SUCCESS | KADM5_LAST_FAILED | \87KADM5_FAIL_AUTH_COUNT)8889/* The default map size (for both environments) in megabytes. */90#define DEFAULT_MAPSIZE 1289192#ifndef O_CLOEXEC93#define O_CLOEXEC 094#endif9596typedef struct {97char *path;98char *lockout_path;99krb5_boolean temporary; /* save changes until promote_db */100krb5_boolean merge_nra; /* preserve existing lockout attributes */101krb5_boolean disable_last_success;102krb5_boolean disable_lockout;103krb5_boolean nosync;104size_t mapsize;105unsigned int maxreaders;106107MDB_env *env;108MDB_env *lockout_env;109MDB_dbi princ_db;110MDB_dbi policy_db;111MDB_dbi lockout_db;112113/* Used for get operations; each transaction is short-lived but we save the114* handle between calls to reduce overhead from MDB_NOTLS. */115MDB_txn *read_txn;116117/* Write transaction for load operations (create() with the "temporary"118* db_arg). */119MDB_txn *load_txn;120} klmdb_context;121122static krb5_error_code123klerr(krb5_context context, int err, const char *msg)124{125krb5_error_code ret;126klmdb_context *dbc = context->dal_handle->db_context;127128/* Pass through system errors; map MDB errors to a com_err code. */129ret = (err > 0) ? err : KRB5_KDB_ACCESS_ERROR;130131k5_setmsg(context, ret, _("%s (path: %s): %s"), msg, dbc->path,132mdb_strerror(err));133return ret;134}135136/* Using db_args and the profile, create a DB context inside context and137* initialize its configurable parameters. */138static krb5_error_code139configure_context(krb5_context context, const char *conf_section,140char *const *db_args)141{142krb5_error_code ret;143klmdb_context *dbc;144char *pval = NULL;145const char *path = NULL;146profile_t profile = context->profile;147size_t i;148int bval, ival;149150dbc = k5alloc(sizeof(*dbc), &ret);151if (dbc == NULL)152return ret;153context->dal_handle->db_context = dbc;154155for (i = 0; db_args != NULL && db_args[i] != NULL; i++) {156if (strcmp(db_args[i], "temporary") == 0) {157dbc->temporary = TRUE;158} else if (strcmp(db_args[i], "merge_nra") == 0) {159dbc->merge_nra = TRUE;160} else if (strncmp(db_args[i], "dbname=", 7) == 0) {161path = db_args[i] + 7;162} else {163ret = EINVAL;164k5_setmsg(context, ret, _("Unsupported argument \"%s\" for LMDB"),165db_args[i]);166goto cleanup;167}168}169170if (path == NULL) {171/* Check for database_name in the db_module section. */172ret = profile_get_string(profile, KDB_MODULE_SECTION, conf_section,173KRB5_CONF_DATABASE_NAME, NULL, &pval);174if (!ret && pval == NULL) {175/* For compatibility, check for database_name in the realm. */176ret = profile_get_string(profile, KDB_REALM_SECTION,177KRB5_DB_GET_REALM(context),178KRB5_CONF_DATABASE_NAME, DEFAULT_KDB_FILE,179&pval);180}181if (ret)182goto cleanup;183path = pval;184}185186if (asprintf(&dbc->path, "%s.mdb", path) < 0) {187dbc->path = NULL;188ret = ENOMEM;189goto cleanup;190}191if (asprintf(&dbc->lockout_path, "%s.lockout.mdb", path) < 0) {192dbc->lockout_path = NULL;193ret = ENOMEM;194goto cleanup;195}196197ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,198KRB5_CONF_DISABLE_LAST_SUCCESS, FALSE, &bval);199if (ret)200goto cleanup;201dbc->disable_last_success = bval;202203ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,204KRB5_CONF_DISABLE_LOCKOUT, FALSE, &bval);205if (ret)206goto cleanup;207dbc->disable_lockout = bval;208209ret = profile_get_integer(profile, KDB_MODULE_SECTION, conf_section,210KRB5_CONF_MAPSIZE, DEFAULT_MAPSIZE, &ival);211if (ret)212goto cleanup;213dbc->mapsize = (size_t)ival * 1024 * 1024;214215ret = profile_get_integer(profile, KDB_MODULE_SECTION, conf_section,216KRB5_CONF_MAX_READERS, 0, &ival);217if (ret)218goto cleanup;219dbc->maxreaders = ival;220221ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,222KRB5_CONF_NOSYNC, FALSE, &bval);223if (ret)224goto cleanup;225dbc->nosync = bval;226227cleanup:228profile_release_string(pval);229return ret;230}231232static krb5_error_code233open_lmdb_env(krb5_context context, klmdb_context *dbc,234krb5_boolean is_lockout, krb5_boolean readonly,235MDB_env **env_out)236{237krb5_error_code ret;238const char *path = is_lockout ? dbc->lockout_path : dbc->path;239unsigned int flags;240MDB_env *env = NULL;241int err;242243*env_out = NULL;244245err = mdb_env_create(&env);246if (err)247goto lmdb_error;248249/* Use a pair of files instead of a subdirectory. */250flags = MDB_NOSUBDIR;251252/*253* For the primary database, tie read transaction locktable slots to the254* transaction and not the thread, so read transactions for iteration255* cursors can coexist with short-lived transactions for operations invoked256* by the iteration callback..257*/258if (!is_lockout)259flags |= MDB_NOTLS;260261if (readonly)262flags |= MDB_RDONLY;263264/* Durability for lockout records is never worth the performance penalty.265* For the primary environment it might be, so we make it configurable. */266if (is_lockout || dbc->nosync)267flags |= MDB_NOSYNC;268269/* We use one database in the lockout env, two in the primary env. */270err = mdb_env_set_maxdbs(env, is_lockout ? 1 : 2);271if (err)272goto lmdb_error;273274if (dbc->mapsize) {275err = mdb_env_set_mapsize(env, dbc->mapsize);276if (err)277goto lmdb_error;278}279280if (dbc->maxreaders) {281err = mdb_env_set_maxreaders(env, dbc->maxreaders);282if (err)283goto lmdb_error;284}285286err = mdb_env_open(env, path, flags, S_IRUSR | S_IWUSR);287if (err)288goto lmdb_error;289290*env_out = env;291return 0;292293lmdb_error:294ret = klerr(context, err, _("LMDB environment open failure"));295mdb_env_close(env);296return ret;297}298299/* Read a key from the primary environment, using a saved read transaction from300* the database context. Return KRB5_KDB_NOENTRY if the key is not found. */301static krb5_error_code302fetch(krb5_context context, MDB_dbi db, MDB_val *key, MDB_val *val_out)303{304krb5_error_code ret = 0;305klmdb_context *dbc = context->dal_handle->db_context;306int err;307308if (dbc->read_txn == NULL)309err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &dbc->read_txn);310else311err = mdb_txn_renew(dbc->read_txn);312313if (!err)314err = mdb_get(dbc->read_txn, db, key, val_out);315316if (err == MDB_NOTFOUND)317ret = KRB5_KDB_NOENTRY;318else if (err)319ret = klerr(context, err, _("LMDB read failure"));320321mdb_txn_reset(dbc->read_txn);322return ret;323}324325/* If we are using a lockout database, try to fetch the lockout attributes for326* key and set them in entry. */327static void328fetch_lockout(krb5_context context, MDB_val *key, krb5_db_entry *entry)329{330klmdb_context *dbc = context->dal_handle->db_context;331MDB_txn *txn = NULL;332MDB_val val;333int err;334335if (dbc->lockout_env == NULL)336return;337err = mdb_txn_begin(dbc->lockout_env, NULL, MDB_RDONLY, &txn);338if (!err)339err = mdb_get(txn, dbc->lockout_db, key, &val);340if (!err && val.mv_size >= LOCKOUT_RECORD_LEN)341klmdb_decode_princ_lockout(context, entry, val.mv_data);342mdb_txn_abort(txn);343}344345/*346* Store a value for key in the specified database within the primary347* environment. Use the saved load transaction if one is present, or a348* temporary write transaction if not. If no_overwrite is true and the key349* already exists, return KRB5_KDB_INUSE. If must_overwrite is true and the350* key does not already exist, return KRB5_KDB_NOENTRY.351*/352static krb5_error_code353put(krb5_context context, MDB_dbi db, char *keystr, uint8_t *bytes, size_t len,354krb5_boolean no_overwrite, krb5_boolean must_overwrite)355{356klmdb_context *dbc = context->dal_handle->db_context;357unsigned int putflags = no_overwrite ? MDB_NOOVERWRITE : 0;358MDB_txn *temp_txn = NULL, *txn;359MDB_val key = { strlen(keystr), keystr }, val = { len, bytes }, dummy;360int err;361362if (dbc->load_txn != NULL) {363txn = dbc->load_txn;364} else {365err = mdb_txn_begin(dbc->env, NULL, 0, &temp_txn);366if (err)367goto error;368txn = temp_txn;369}370371if (must_overwrite && mdb_get(txn, db, &key, &dummy) == MDB_NOTFOUND) {372mdb_txn_abort(temp_txn);373return KRB5_KDB_NOENTRY;374}375376err = mdb_put(txn, db, &key, &val, putflags);377if (err)378goto error;379380if (temp_txn != NULL) {381err = mdb_txn_commit(temp_txn);382temp_txn = NULL;383if (err)384goto error;385}386387return 0;388389error:390mdb_txn_abort(temp_txn);391if (err == MDB_KEYEXIST)392return KRB5_KDB_INUSE;393else394return klerr(context, err, _("LMDB write failure"));395}396397/* Delete an entry from the specified env and database, using a temporary write398* transaction. Return KRB5_KDB_NOENTRY if the key does not exist. */399static krb5_error_code400del(krb5_context context, MDB_env *env, MDB_dbi db, char *keystr)401{402krb5_error_code ret = 0;403MDB_txn *txn = NULL;404MDB_val key = { strlen(keystr), keystr };405int err;406407err = mdb_txn_begin(env, NULL, 0, &txn);408if (!err)409err = mdb_del(txn, db, &key, NULL);410if (!err) {411err = mdb_txn_commit(txn);412txn = NULL;413}414415if (err == MDB_NOTFOUND)416ret = KRB5_KDB_NOENTRY;417else if (err)418ret = klerr(context, err, _("LMDB delete failure"));419420mdb_txn_abort(txn);421return ret;422}423424/* Zero out and unlink filename. */425static krb5_error_code426destroy_file(const char *filename)427{428krb5_error_code ret;429struct stat st;430ssize_t len;431off_t pos;432uint8_t buf[BUFSIZ], zbuf[BUFSIZ] = { 0 };433int fd;434435fd = open(filename, O_RDWR | O_CLOEXEC, 0);436if (fd < 0)437return errno;438set_cloexec_fd(fd);439if (fstat(fd, &st) == -1)440goto error;441442memset(zbuf, 0, BUFSIZ);443pos = 0;444while (pos < st.st_size) {445len = read(fd, buf, BUFSIZ);446if (len < 0)447goto error;448/* Only rewrite the block if it's not already zeroed, in case the file449* is sparse. */450if (memcmp(buf, zbuf, len) != 0) {451(void)lseek(fd, pos, SEEK_SET);452len = write(fd, zbuf, len);453if (len < 0)454goto error;455}456pos += len;457}458close(fd);459460if (unlink(filename) != 0)461return errno;462return 0;463464error:465ret = errno;466close(fd);467return ret;468}469470static krb5_error_code471klmdb_lib_init(void)472{473return 0;474}475476static krb5_error_code477klmdb_lib_cleanup(void)478{479return 0;480}481482static krb5_error_code483klmdb_fini(krb5_context context)484{485klmdb_context *dbc;486487dbc = context->dal_handle->db_context;488if (dbc == NULL)489return 0;490mdb_txn_abort(dbc->read_txn);491mdb_txn_abort(dbc->load_txn);492mdb_env_close(dbc->env);493mdb_env_close(dbc->lockout_env);494free(dbc->path);495free(dbc->lockout_path);496free(dbc);497context->dal_handle->db_context = NULL;498return 0;499}500501static krb5_error_code502klmdb_open(krb5_context context, char *conf_section, char **db_args, int mode)503{504krb5_error_code ret;505klmdb_context *dbc;506krb5_boolean readonly;507MDB_txn *txn = NULL;508struct stat st;509int err;510511if (context->dal_handle->db_context != NULL)512return 0;513514ret = configure_context(context, conf_section, db_args);515if (ret)516return ret;517dbc = context->dal_handle->db_context;518519if (stat(dbc->path, &st) != 0) {520ret = ENOENT;521k5_setmsg(context, ret, _("LMDB file %s does not exist"), dbc->path);522goto error;523}524525/* Open the primary environment and databases. The KDC can open this526* environment read-only. */527readonly = (mode & KRB5_KDB_OPEN_RO) || (mode & KRB5_KDB_SRV_TYPE_KDC);528ret = open_lmdb_env(context, dbc, FALSE, readonly, &dbc->env);529if (ret)530goto error;531err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &txn);532if (err)533goto lmdb_error;534err = mdb_dbi_open(txn, "principal", 0, &dbc->princ_db);535if (err)536goto lmdb_error;537err = mdb_dbi_open(txn, "policy", 0, &dbc->policy_db);538if (err)539goto lmdb_error;540err = mdb_txn_commit(txn);541txn = NULL;542if (err)543goto lmdb_error;544545/* Open the lockout environment and database if we will need it. */546if (!dbc->disable_last_success || !dbc->disable_lockout) {547readonly = !!(mode & KRB5_KDB_OPEN_RO);548ret = open_lmdb_env(context, dbc, TRUE, readonly, &dbc->lockout_env);549if (ret)550goto error;551err = mdb_txn_begin(dbc->lockout_env, NULL, MDB_RDONLY, &txn);552if (err)553goto lmdb_error;554err = mdb_dbi_open(txn, "lockout", 0, &dbc->lockout_db);555if (err)556goto lmdb_error;557err = mdb_txn_commit(txn);558txn = NULL;559if (err)560goto lmdb_error;561}562563return 0;564565lmdb_error:566ret = klerr(context, err, _("LMDB open failure"));567error:568mdb_txn_abort(txn);569klmdb_fini(context);570return ret;571}572573static krb5_error_code574klmdb_create(krb5_context context, char *conf_section, char **db_args)575{576krb5_error_code ret;577klmdb_context *dbc;578MDB_txn *txn = NULL;579struct stat st;580int err;581582if (context->dal_handle->db_context != NULL)583return 0;584585ret = configure_context(context, conf_section, db_args);586if (ret)587return ret;588dbc = context->dal_handle->db_context;589590if (!dbc->temporary) {591if (stat(dbc->path, &st) == 0) {592ret = ENOENT;593k5_setmsg(context, ret, _("LMDB file %s already exists"),594dbc->path);595goto error;596}597}598599/* Open (and create if necessary) the LMDB environments. */600ret = open_lmdb_env(context, dbc, FALSE, FALSE, &dbc->env);601if (ret)602goto error;603ret = open_lmdb_env(context, dbc, TRUE, FALSE, &dbc->lockout_env);604if (ret)605goto error;606607/* Open the primary databases, creating them if they don't exist. */608err = mdb_txn_begin(dbc->env, NULL, 0, &txn);609if (err)610goto lmdb_error;611err = mdb_dbi_open(txn, "principal", MDB_CREATE, &dbc->princ_db);612if (err)613goto lmdb_error;614err = mdb_dbi_open(txn, "policy", MDB_CREATE, &dbc->policy_db);615if (err)616goto lmdb_error;617err = mdb_txn_commit(txn);618txn = NULL;619if (err)620goto lmdb_error;621622/* Create the lockout database if it doesn't exist. */623err = mdb_txn_begin(dbc->lockout_env, NULL, 0, &txn);624if (err)625goto lmdb_error;626err = mdb_dbi_open(txn, "lockout", MDB_CREATE, &dbc->lockout_db);627if (err)628goto lmdb_error;629err = mdb_txn_commit(txn);630txn = NULL;631if (err)632goto lmdb_error;633634if (dbc->temporary) {635/* Create a load transaction and empty the primary databases within636* it. */637err = mdb_txn_begin(dbc->env, NULL, 0, &dbc->load_txn);638if (err)639goto lmdb_error;640err = mdb_drop(dbc->load_txn, dbc->princ_db, 0);641if (err)642goto lmdb_error;643err = mdb_drop(dbc->load_txn, dbc->policy_db, 0);644if (err)645goto lmdb_error;646}647648/* Close the lockout environment if we won't need it. */649if (dbc->disable_last_success && dbc->disable_lockout) {650mdb_env_close(dbc->lockout_env);651dbc->lockout_env = NULL;652dbc->lockout_db = 0;653}654655return 0;656657lmdb_error:658ret = klerr(context, err, _("LMDB create error"));659error:660mdb_txn_abort(txn);661klmdb_fini(context);662return ret;663}664665/* Unlink the "-lock" extension of path. */666static krb5_error_code667unlink_lock_file(krb5_context context, const char *path)668{669char *lock_path;670int st;671672if (asprintf(&lock_path, "%s-lock", path) < 0)673return ENOMEM;674st = unlink(lock_path);675if (st)676k5_prependmsg(context, st, _("Could not unlink %s"), lock_path);677free(lock_path);678return st;679}680681static krb5_error_code682klmdb_destroy(krb5_context context, char *conf_section, char **db_args)683{684krb5_error_code ret;685klmdb_context *dbc;686687if (context->dal_handle->db_context != NULL)688klmdb_fini(context);689ret = configure_context(context, conf_section, db_args);690if (ret)691goto cleanup;692dbc = context->dal_handle->db_context;693694ret = destroy_file(dbc->path);695if (ret)696goto cleanup;697ret = unlink_lock_file(context, dbc->path);698if (ret)699goto cleanup;700701ret = destroy_file(dbc->lockout_path);702if (ret)703goto cleanup;704ret = unlink_lock_file(context, dbc->lockout_path);705706cleanup:707klmdb_fini(context);708return ret;709}710711static krb5_error_code712klmdb_get_principal(krb5_context context, krb5_const_principal searchfor,713unsigned int flags, krb5_db_entry **entry_out)714{715krb5_error_code ret;716klmdb_context *dbc = context->dal_handle->db_context;717MDB_val key, val;718char *name = NULL;719720*entry_out = NULL;721if (dbc == NULL)722return KRB5_KDB_DBNOTINITED;723724ret = krb5_unparse_name(context, searchfor, &name);725if (ret)726goto cleanup;727728key.mv_data = name;729key.mv_size = strlen(name);730ret = fetch(context, dbc->princ_db, &key, &val);731if (ret)732goto cleanup;733734ret = klmdb_decode_princ(context, name, strlen(name),735val.mv_data, val.mv_size, entry_out);736if (ret)737goto cleanup;738739fetch_lockout(context, &key, *entry_out);740741cleanup:742krb5_free_unparsed_name(context, name);743return ret;744}745746static krb5_error_code747klmdb_put_principal(krb5_context context, krb5_db_entry *entry, char **db_args)748{749krb5_error_code ret;750klmdb_context *dbc = context->dal_handle->db_context;751MDB_val key, val, dummy;752MDB_txn *txn = NULL;753uint8_t lockout[LOCKOUT_RECORD_LEN], *enc;754size_t len;755char *name = NULL;756int err;757758if (db_args != NULL) {759/* This module does not support DB arguments for put_principal. */760k5_setmsg(context, EINVAL, _("Unsupported argument \"%s\" for lmdb"),761db_args[0]);762return EINVAL;763}764765if (dbc == NULL)766return KRB5_KDB_DBNOTINITED;767768ret = krb5_unparse_name(context, entry->princ, &name);769if (ret)770goto cleanup;771772ret = klmdb_encode_princ(context, entry, &enc, &len);773if (ret)774goto cleanup;775ret = put(context, dbc->princ_db, name, enc, len, FALSE, FALSE);776free(enc);777if (ret)778goto cleanup;779780/*781* Write the lockout attributes to the lockout database if we are using782* one. During a load operation, changes to lockout attributes will become783* visible before the load is finished, which is an acceptable compromise784* on load atomicity.785*/786if (dbc->lockout_env != NULL &&787(entry->mask & (LOCKOUT_MASK | KADM5_PRINCIPAL))) {788key.mv_data = name;789key.mv_size = strlen(name);790klmdb_encode_princ_lockout(context, entry, lockout);791val.mv_data = lockout;792val.mv_size = sizeof(lockout);793err = mdb_txn_begin(dbc->lockout_env, NULL, 0, &txn);794if (!err && dbc->merge_nra) {795/* During an iprop load, do not change existing lockout entries. */796if (mdb_get(txn, dbc->lockout_db, &key, &dummy) == 0)797goto cleanup;798}799if (!err)800err = mdb_put(txn, dbc->lockout_db, &key, &val, 0);801if (!err) {802err = mdb_txn_commit(txn);803txn = NULL;804}805if (err) {806ret = klerr(context, err, _("LMDB lockout write failure"));807goto cleanup;808}809}810811cleanup:812mdb_txn_abort(txn);813krb5_free_unparsed_name(context, name);814return ret;815}816817static krb5_error_code818klmdb_delete_principal(krb5_context context, krb5_const_principal searchfor)819{820krb5_error_code ret;821klmdb_context *dbc = context->dal_handle->db_context;822char *name;823824if (dbc == NULL)825return KRB5_KDB_DBNOTINITED;826827ret = krb5_unparse_name(context, searchfor, &name);828if (ret)829return ret;830831ret = del(context, dbc->env, dbc->princ_db, name);832if (!ret && dbc->lockout_env != NULL)833(void)del(context, dbc->lockout_env, dbc->lockout_db, name);834835krb5_free_unparsed_name(context, name);836return ret;837}838839static krb5_error_code840klmdb_iterate(krb5_context context, char *match_expr,841krb5_error_code (*func)(void *, krb5_db_entry *), void *arg,842krb5_flags iterflags)843{844krb5_error_code ret;845klmdb_context *dbc = context->dal_handle->db_context;846krb5_db_entry *entry;847MDB_txn *txn = NULL;848MDB_cursor *cursor = NULL;849MDB_val key, val;850MDB_cursor_op op = (iterflags & KRB5_DB_ITER_REV) ? MDB_PREV : MDB_NEXT;851int err;852853if (dbc == NULL)854return KRB5_KDB_DBNOTINITED;855856err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &txn);857if (err)858goto lmdb_error;859err = mdb_cursor_open(txn, dbc->princ_db, &cursor);860if (err)861goto lmdb_error;862for (;;) {863err = mdb_cursor_get(cursor, &key, &val, op);864if (err == MDB_NOTFOUND)865break;866if (err)867goto lmdb_error;868ret = klmdb_decode_princ(context, key.mv_data, key.mv_size,869val.mv_data, val.mv_size, &entry);870if (ret)871goto cleanup;872fetch_lockout(context, &key, entry);873ret = (*func)(arg, entry);874krb5_db_free_principal(context, entry);875if (ret)876goto cleanup;877}878ret = 0;879goto cleanup;880881lmdb_error:882ret = klerr(context, err, _("LMDB principal iteration failure"));883cleanup:884mdb_cursor_close(cursor);885mdb_txn_abort(txn);886return ret;887}888889krb5_error_code890klmdb_get_policy(krb5_context context, char *name, osa_policy_ent_t *policy)891{892krb5_error_code ret;893klmdb_context *dbc = context->dal_handle->db_context;894MDB_val key, val;895896*policy = NULL;897if (dbc == NULL)898return KRB5_KDB_DBNOTINITED;899900key.mv_data = name;901key.mv_size = strlen(name);902ret = fetch(context, dbc->policy_db, &key, &val);903if (ret)904return ret;905return klmdb_decode_policy(context, name, strlen(name),906val.mv_data, val.mv_size, policy);907}908909static krb5_error_code910klmdb_create_policy(krb5_context context, osa_policy_ent_t policy)911{912krb5_error_code ret;913klmdb_context *dbc = context->dal_handle->db_context;914uint8_t *enc;915size_t len;916917if (dbc == NULL)918return KRB5_KDB_DBNOTINITED;919920ret = klmdb_encode_policy(context, policy, &enc, &len);921if (ret)922return ret;923ret = put(context, dbc->policy_db, policy->name, enc, len, TRUE, FALSE);924free(enc);925return ret;926}927928static krb5_error_code929klmdb_put_policy(krb5_context context, osa_policy_ent_t policy)930{931krb5_error_code ret;932klmdb_context *dbc = context->dal_handle->db_context;933uint8_t *enc;934size_t len;935936if (dbc == NULL)937return KRB5_KDB_DBNOTINITED;938939ret = klmdb_encode_policy(context, policy, &enc, &len);940if (ret)941return ret;942ret = put(context, dbc->policy_db, policy->name, enc, len, FALSE, TRUE);943free(enc);944return ret;945}946947static krb5_error_code948klmdb_iter_policy(krb5_context context, char *match_entry,949osa_adb_iter_policy_func func, void *arg)950{951krb5_error_code ret;952klmdb_context *dbc = context->dal_handle->db_context;953osa_policy_ent_t pol;954MDB_txn *txn = NULL;955MDB_cursor *cursor = NULL;956MDB_val key, val;957int err;958959if (dbc == NULL)960return KRB5_KDB_DBNOTINITED;961962err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &txn);963if (err)964goto lmdb_error;965err = mdb_cursor_open(txn, dbc->policy_db, &cursor);966if (err)967goto lmdb_error;968for (;;) {969err = mdb_cursor_get(cursor, &key, &val, MDB_NEXT);970if (err == MDB_NOTFOUND)971break;972if (err)973goto lmdb_error;974ret = klmdb_decode_policy(context, key.mv_data, key.mv_size,975val.mv_data, val.mv_size, &pol);976if (ret)977goto cleanup;978(*func)(arg, pol);979krb5_db_free_policy(context, pol);980}981ret = 0;982goto cleanup;983984lmdb_error:985ret = klerr(context, err, _("LMDB policy iteration failure"));986cleanup:987mdb_cursor_close(cursor);988mdb_txn_abort(txn);989return ret;990}991992static krb5_error_code993klmdb_delete_policy(krb5_context context, char *policy)994{995klmdb_context *dbc = context->dal_handle->db_context;996997if (dbc == NULL)998return KRB5_KDB_DBNOTINITED;999return del(context, dbc->env, dbc->policy_db, policy);1000}10011002static krb5_error_code1003klmdb_promote_db(krb5_context context, char *conf_section, char **db_args)1004{1005krb5_error_code ret = 0;1006klmdb_context *dbc = context->dal_handle->db_context;1007int err;10081009if (dbc == NULL)1010return KRB5_KDB_DBNOTINITED;1011if (dbc->load_txn == NULL)1012return EINVAL;1013err = mdb_txn_commit(dbc->load_txn);1014dbc->load_txn = NULL;1015if (err)1016ret = klerr(context, err, _("LMDB transaction commit failure"));1017klmdb_fini(context);1018return ret;1019}10201021static krb5_error_code1022klmdb_check_policy_as(krb5_context context, krb5_kdc_req *request,1023krb5_db_entry *client, krb5_db_entry *server,1024krb5_timestamp kdc_time, const char **status,1025krb5_pa_data ***e_data)1026{1027krb5_error_code ret;1028klmdb_context *dbc = context->dal_handle->db_context;10291030if (dbc->disable_lockout)1031return 0;10321033ret = klmdb_lockout_check_policy(context, client, kdc_time);1034if (ret == KRB5KDC_ERR_CLIENT_REVOKED)1035*status = "LOCKED_OUT";1036return ret;1037}10381039static void1040klmdb_audit_as_req(krb5_context context, krb5_kdc_req *request,1041const krb5_address *local_addr,1042const krb5_address *remote_addr, krb5_db_entry *client,1043krb5_db_entry *server, krb5_timestamp authtime,1044krb5_error_code status)1045{1046klmdb_context *dbc = context->dal_handle->db_context;10471048(void)klmdb_lockout_audit(context, client, authtime, status,1049dbc->disable_last_success, dbc->disable_lockout);1050}10511052krb5_error_code1053klmdb_update_lockout(krb5_context context, krb5_db_entry *entry,1054krb5_timestamp stamp, krb5_boolean zero_fail_count,1055krb5_boolean set_last_success,1056krb5_boolean set_last_failure)1057{1058krb5_error_code ret;1059klmdb_context *dbc = context->dal_handle->db_context;1060krb5_db_entry dummy = { 0 };1061uint8_t lockout[LOCKOUT_RECORD_LEN];1062MDB_txn *txn = NULL;1063MDB_val key, val;1064char *name = NULL;1065int err;10661067if (dbc == NULL)1068return KRB5_KDB_DBNOTINITED;1069if (dbc->lockout_env == NULL)1070return 0;1071if (!zero_fail_count && !set_last_success && !set_last_failure)1072return 0;10731074ret = krb5_unparse_name(context, entry->princ, &name);1075if (ret)1076goto cleanup;1077key.mv_data = name;1078key.mv_size = strlen(name);10791080err = mdb_txn_begin(dbc->lockout_env, NULL, 0, &txn);1081if (err)1082goto lmdb_error;1083/* Fetch base lockout info within txn so we update transactionally. */1084err = mdb_get(txn, dbc->lockout_db, &key, &val);1085if (!err && val.mv_size >= LOCKOUT_RECORD_LEN) {1086klmdb_decode_princ_lockout(context, &dummy, val.mv_data);1087} else {1088dummy.last_success = entry->last_success;1089dummy.last_failed = entry->last_failed;1090dummy.fail_auth_count = entry->fail_auth_count;1091}10921093if (zero_fail_count)1094dummy.fail_auth_count = 0;1095if (set_last_success)1096dummy.last_success = stamp;1097if (set_last_failure) {1098dummy.last_failed = stamp;1099dummy.fail_auth_count++;1100}11011102klmdb_encode_princ_lockout(context, &dummy, lockout);1103val.mv_data = lockout;1104val.mv_size = sizeof(lockout);1105err = mdb_put(txn, dbc->lockout_db, &key, &val, 0);1106if (err)1107goto lmdb_error;1108err = mdb_txn_commit(txn);1109txn = NULL;1110if (err)1111goto lmdb_error;1112goto cleanup;11131114lmdb_error:1115ret = klerr(context, err, _("LMDB lockout update failure"));1116cleanup:1117krb5_free_unparsed_name(context, name);1118mdb_txn_abort(txn);1119return 0;1120}11211122kdb_vftabl PLUGIN_SYMBOL_NAME(krb5_lmdb, kdb_function_table) = {1123.maj_ver = KRB5_KDB_DAL_MAJOR_VERSION,1124.min_ver = 0,1125.init_library = klmdb_lib_init,1126.fini_library = klmdb_lib_cleanup,1127.init_module = klmdb_open,1128.fini_module = klmdb_fini,1129.create = klmdb_create,1130.destroy = klmdb_destroy,1131.get_principal = klmdb_get_principal,1132.put_principal = klmdb_put_principal,1133.delete_principal = klmdb_delete_principal,1134.iterate = klmdb_iterate,1135.create_policy = klmdb_create_policy,1136.get_policy = klmdb_get_policy,1137.put_policy = klmdb_put_policy,1138.iter_policy = klmdb_iter_policy,1139.delete_policy = klmdb_delete_policy,1140.promote_db = klmdb_promote_db,1141.check_policy_as = klmdb_check_policy_as,1142.audit_as_req = klmdb_audit_as_req1143};114411451146