krb5 commit: Add LMDB KDB module

Greg Hudson <[email protected]>
Newsgroups gmane.comp.encryption.kerberos.cvs
Message-ID <[email protected]>
https://github.com/krb5/krb5/commit/03e3115222e7f8ce61b2daec7bcbb2365f3190f9
commit 03e3115222e7f8ce61b2daec7bcbb2365f3190f9
Author: Greg Hudson <[email protected]>
Date:   Mon Apr 2 13:34:54 2018 -0400

    Add LMDB KDB module
    
    Add a new KDB module using LMDB.  For this module, combine policy and
    principal databases into one environment with two databases, but split
    out principal lockout fields into a separate environment so that
    nothing blocks KDC writes for more than a trivial amount of time.
    
    ticket: 8674 (new)

 src/Makefile.in                    |    2 +
 src/config/pre.in                  |    4 +
 src/configure.in                   |   21 +
 src/include/k5-int.h               |    3 +
 src/plugins/kdb/lmdb/Makefile.in   |   27 +
 src/plugins/kdb/lmdb/deps          |   53 ++
 src/plugins/kdb/lmdb/kdb_lmdb.c    | 1143 ++++++++++++++++++++++++++++++++++++
 src/plugins/kdb/lmdb/klmdb-int.h   |   78 +++
 src/plugins/kdb/lmdb/klmdb.exports |    1 +
 src/plugins/kdb/lmdb/lockout.c     |  180 ++++++
 src/plugins/kdb/lmdb/marshal.c     |  339 +++++++++++
 11 files changed, 1851 insertions(+), 0 deletions(-)

diff --git a/src/Makefile.in b/src/Makefile.in
index db6c0df..e2c178f 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -21,6 +21,7 @@ SUBDIRS=util include lib \
 	plugins/certauth/test \
 	plugins/kdb/db2 \
 	@ldap_plugin_dir@ \
+	@lmdb_plugin_dir@ \
 	plugins/kdb/test \
 	plugins/kdcpolicy/test \
 	plugins/preauth/otp \
@@ -517,6 +518,7 @@ pyrunenv.vals: Makefile
 	echo "tls_impl = '$(TLS_IMPL)'" >> $@
 	echo "have_sasl = '$(HAVE_SASL)'" >> $@
 	echo "have_spake_openssl = '$(HAVE_SPAKE_OPENSSL)'" >> $@
+	echo "have_lmdb = '$(HAVE_LMDB)'" >> $@
 	echo "sizeof_time_t = $(SIZEOF_TIME_T)" >> $@
 
 runenv.py: pyrunenv.vals
diff --git a/src/config/pre.in b/src/config/pre.in
index 0306c45..b7e4045 100644
--- a/src/config/pre.in
+++ b/src/config/pre.in
@@ -389,6 +389,7 @@ DL_LIB		= @DL_LIB@
 
 CMOCKA_LIBS	= @CMOCKA_LIBS@
 LDAP_LIBS	= @LDAP_LIBS@
+LMDB_LIBS	= @LMDB_LIBS@
 
 KRB5_LIB			= -lkrb5
 K5CRYPTO_LIB			= -lk5crypto
@@ -449,6 +450,9 @@ HAVE_SASL = @HAVE_SASL@
 # Whether we are building support for NIST SPAKE groups using OpenSSL
 HAVE_SPAKE_OPENSSL = @HAVE_SPAKE_OPENSSL@
 
+# Whether we are building the LMDB KDB module
+HAVE_LMDB = @HAVE_LMDB@
+
 # Whether we have libresolv 1.1.5 for URI discovery tests
 HAVE_RESOLV_WRAPPER = @HAVE_RESOLV_WRAPPER@
 
diff --git a/src/configure.in b/src/configure.in
index c2ae7bd..9f0c0f2 100644
--- a/src/configure.in
+++ b/src/configure.in
@@ -1251,6 +1251,27 @@ AC_CHECK_LIB(aceclnt, SD_Init, [
 AC_SUBST(sam2_plugin)
 CFLAGS=$old_CFLAGS
 
+lmdb_plugin_dir=""
+HAVE_LMDB=no
+AC_ARG_WITH([lmdb],
+AC_HELP_STRING([--with-lmdb],
+	    [compile LMDB database backend module @<:@auto@:>@]),,
+	    [withval=auto])
+if test "$withval" = auto -o "$withval" = yes; then
+  AC_CHECK_LIB([lmdb],[mdb_env_create],[have_lmdb=true],[have_lmdb=false])
+  if test "$have_lmdb" = true; then
+    LMDB_LIBS=-llmdb
+    HAVE_LMDB=yes
+    lmdb_plugin_dir='plugins/kdb/lmdb'
+    K5_GEN_MAKEFILE(plugins/kdb/lmdb)
+  elif test "$withval" = yes; then
+    AC_MSG_ERROR([liblmdb not found])
+  fi
+fi
+AC_SUBST(HAVE_LMDB)
+AC_SUBST(LMDB_LIBS)
+AC_SUBST(lmdb_plugin_dir)
+
 # Kludge for simple server --- FIXME is this the best way to do this?
 
 if test "$ac_cv_lib_socket" = "yes" -a "$ac_cv_lib_nsl" = "yes"; then
diff --git a/src/include/k5-int.h b/src/include/k5-int.h
index 3eca3aa..5d84985 100644
--- a/src/include/k5-int.h
+++ b/src/include/k5-int.h
@@ -264,13 +264,16 @@ typedef unsigned char   u_char;
 #define KRB5_CONF_LDAP_SERVICE_PASSWORD_FILE   "ldap_service_password_file"
 #define KRB5_CONF_LIBDEFAULTS                  "libdefaults"
 #define KRB5_CONF_LOGGING                      "logging"
+#define KRB5_CONF_MAPSIZE                      "mapsize"
 #define KRB5_CONF_MASTER_KDC                   "master_kdc"
 #define KRB5_CONF_MASTER_KEY_NAME              "master_key_name"
 #define KRB5_CONF_MASTER_KEY_TYPE              "master_key_type"
 #define KRB5_CONF_MAX_LIFE                     "max_life"
+#define KRB5_CONF_MAX_READERS                  "max_readers"
 #define KRB5_CONF_MAX_RENEWABLE_LIFE           "max_renewable_life"
 #define KRB5_CONF_MODULE                       "module"
 #define KRB5_CONF_NOADDRESSES                  "noaddresses"
+#define KRB5_CONF_NOSYNC                       "nosync"
 #define KRB5_CONF_NO_HOST_REFERRAL             "no_host_referral"
 #define KRB5_CONF_PERMITTED_ENCTYPES           "permitted_enctypes"
 #define KRB5_CONF_PLUGINS                      "plugins"
diff --git a/src/plugins/kdb/lmdb/Makefile.in b/src/plugins/kdb/lmdb/Makefile.in
new file mode 100644
index 0000000..8e68b17
--- /dev/null
+++ b/src/plugins/kdb/lmdb/Makefile.in
@@ -0,0 +1,27 @@
+mydir=plugins$(S)kdb$(S)lmdb
+BUILDTOP=$(REL)..$(S)..$(S)..
+MODULE_INSTALL_DIR = $(KRB5_DB_MODULE_DIR)
+
+LOCALINCLUDES = -I$(srcdir)/../../../lib/kdb
+
+LIBBASE=klmdb
+LIBMAJOR=0
+LIBMINOR=0
+RELDIR=../plugins/kdb/lmdb
+# Depends on libk5crypto and libkrb5
+# Also on gssrpc, for xdr stuff.
+SHLIB_EXPDEPS = $(KADMSRV_DEPLIBS) $(KDB5_DEPLIBS) $(KRB5_BASE_DEPLIBS)
+SHLIB_EXPLIBS = $(KADMSRV_LIBS) $(KRB5_BASE_LIBS) $(LMDB_LIBS)
+
+DBDIR = liblmdb
+
+SRCS=$(srcdir)/kdb_lmdb.c $(srcdir)/lockout.c $(srcdir)/marshal.c
+
+STLIBOBJS=kdb_lmdb.o lockout.o marshal.o
+
+all-unix: all-liblinks
+install-unix: install-libs
+clean-unix:: clean-liblinks clean-libs clean-libobjs
+
+@libnover_frag@
+@libobj_frag@
diff --git a/src/plugins/kdb/lmdb/deps b/src/plugins/kdb/lmdb/deps
new file mode 100644
index 0000000..e4212f7
--- /dev/null
+++ b/src/plugins/kdb/lmdb/deps
@@ -0,0 +1,53 @@
+#
+# Generated makefile dependencies follow.
+#
+kdb_lmdb.so kdb_lmdb.po $(OUTPRE)kdb_lmdb.$(OBJEXT): \
+  $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/gssapi/gssapi.h \
+  $(BUILDTOP)/include/gssrpc/types.h $(BUILDTOP)/include/kadm5/admin.h \
+  $(BUILDTOP)/include/kadm5/chpass_util_strings.h $(BUILDTOP)/include/kadm5/kadm_err.h \
+  $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+  $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(srcdir)/../../../lib/kdb/kdb5.h \
+  $(top_srcdir)/include/gssrpc/auth.h $(top_srcdir)/include/gssrpc/auth_gss.h \
+  $(top_srcdir)/include/gssrpc/auth_unix.h $(top_srcdir)/include/gssrpc/clnt.h \
+  $(top_srcdir)/include/gssrpc/rename.h $(top_srcdir)/include/gssrpc/rpc.h \
+  $(top_srcdir)/include/gssrpc/rpc_msg.h $(top_srcdir)/include/gssrpc/svc.h \
+  $(top_srcdir)/include/gssrpc/svc_auth.h $(top_srcdir)/include/gssrpc/xdr.h \
+  $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+  $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+  $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+  $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+  $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/kdb.h \
+  $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+  $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+  $(top_srcdir)/include/socket-utils.h kdb_lmdb.c klmdb-int.h
+lockout.so lockout.po $(OUTPRE)lockout.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+  $(BUILDTOP)/include/gssapi/gssapi.h $(BUILDTOP)/include/gssrpc/types.h \
+  $(BUILDTOP)/include/kadm5/admin.h $(BUILDTOP)/include/kadm5/admin_internal.h \
+  $(BUILDTOP)/include/kadm5/chpass_util_strings.h $(BUILDTOP)/include/kadm5/kadm_err.h \
+  $(BUILDTOP)/include/kadm5/server_internal.h $(BUILDTOP)/include/krb5/krb5.h \
+  $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+  $(COM_ERR_DEPS) $(srcdir)/../../../lib/kdb/kdb5.h $(top_srcdir)/include/gssrpc/auth.h \
+  $(top_srcdir)/include/gssrpc/auth_gss.h $(top_srcdir)/include/gssrpc/auth_unix.h \
+  $(top_srcdir)/include/gssrpc/clnt.h $(top_srcdir)/include/gssrpc/rename.h \
+  $(top_srcdir)/include/gssrpc/rpc.h $(top_srcdir)/include/gssrpc/rpc_msg.h \
+  $(top_srcdir)/include/gssrpc/svc.h $(top_srcdir)/include/gssrpc/svc_auth.h \
+  $(top_srcdir)/include/gssrpc/xdr.h $(top_srcdir)/include/k5-buf.h \
+  $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+  $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+  $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+  $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+  $(top_srcdir)/include/kdb.h $(top_srcdir)/include/krb5.h \
+  $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+  $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+  klmdb-int.h lockout.c
+marshal.so marshal.po $(OUTPRE)marshal.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+  $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+  $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
+  $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+  $(top_srcdir)/include/k5-input.h $(top_srcdir)/include/k5-int-pkinit.h \
+  $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+  $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+  $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/kdb.h \
+  $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+  $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+  $(top_srcdir)/include/socket-utils.h klmdb-int.h marshal.c
diff --git a/src/plugins/kdb/lmdb/kdb_lmdb.c b/src/plugins/kdb/lmdb/kdb_lmdb.c
new file mode 100644
index 0000000..bd288e2
--- /dev/null
+++ b/src/plugins/kdb/lmdb/kdb_lmdb.c
@@ -0,0 +1,1143 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* plugins/kdb/lmdb/klmdb.c - KDB module using LMDB */
+/*
+ * Copyright (C) 2018 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Thread-safety note: unlike the other two in-tree KDB modules, this module
+ * performs no mutex locking to ensure thread safety.  As the KDC and kadmind
+ * are single-threaded, and applications are not allowed to access the same
+ * krb5_context in multiple threads simultaneously, there is no current need
+ * for this code to be thread-safe.  If a need arises in the future, mutex
+ * locking should be added around the read_txn and load_txn fields of
+ * lmdb_context to ensure that only one thread at a time accesses those
+ * transactions.
+ */
+
+/*
+ * This KDB module stores principal and policy data using LMDB (Lightning
+ * Memory-Mapped Database).  We use two LMDB environments, the first to hold
+ * the majority of principal and policy data (suffix ".mdb") in the "principal"
+ * and "policy" databases, and the second to hold the three non-replicated
+ * account lockout attributes (suffix ".lockout.mdb") in the "lockout"
+ * database.  The KDC only needs to write to the lockout database.
+ *
+ * For iteration we create a read transaction in the main environment for the
+ * cursor.  Because the iteration callback might need to create its own
+ * transactions for write operations (e.g. for kdb5_util
+ * update_princ_encryption), we set the MDB_NOTLS flag on the main environment,
+ * so that a thread can hold multiple transactions.
+ *
+ * To mitigate the overhead from MDB_NOTLS, we keep around a read_txn handle
+ * in the database context for get operations, using mdb_txn_reset() and
+ * mdb_txn_renew() between calls.
+ *
+ * For database loads, kdb5_util calls the create() method with the "temporary"
+ * db_arg, and then promotes the finished contents at the end with the
+ * promote_db() method.  In this case we create or open the same LMDB
+ * environments as above, open a write_txn handle for the lifetime of the
+ * context, and empty out the principal and policy databases.  On promote_db()
+ * we commit the transaction.  We do not empty the lockout database and write
+ * to it non-transactionally during the load so that we don't block writes by
+ * the KDC; this isn't ideal if the load is aborted, but it shouldn't cause any
+ * practical issues.
+ *
+ * For iprop loads, kdb5_util also includes the "merge_nra" db_arg, signifying
+ * that the lockout attributes from existing principal entries should be
+ * preserved.  This attribute is noted in the LMDB context, and put_principal
+ * operations will not write to the lockout database if an existing lockout
+ * entry is already present for the principal.
+ */
+
+#include "k5-int.h"
+#include <kadm5/admin.h>
+#include "kdb5.h"
+#include "klmdb-int.h"
+#include <lmdb.h>
+
+/* The presence of any of these mask bits indicates a change to one of the
+ * three principal lockout attributes. */
+#define LOCKOUT_MASK (KADM5_LAST_SUCCESS | KADM5_LAST_FAILED |  \
+                      KADM5_FAIL_AUTH_COUNT)
+
+/* The default map size (for both environments) in megabytes. */
+#define DEFAULT_MAPSIZE 128
+
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+
+typedef struct {
+    char *path;
+    char *lockout_path;
+    krb5_boolean temporary;     /* save changes until promote_db */
+    krb5_boolean merge_nra;     /* preserve existing lockout attributes */
+    krb5_boolean disable_last_success;
+    krb5_boolean disable_lockout;
+    krb5_boolean nosync;
+    size_t mapsize;
+    unsigned int maxreaders;
+
+    MDB_env *env;
+    MDB_env *lockout_env;
+    MDB_dbi princ_db;
+    MDB_dbi policy_db;
+    MDB_dbi lockout_db;
+
+    /* Used for get operations; each transaction is short-lived but we save the
+     * handle between calls to reduce overhead from MDB_NOTLS. */
+    MDB_txn *read_txn;
+
+    /* Write transaction for load operations (create() with the "temporary"
+     * db_arg).  */
+    MDB_txn *load_txn;
+} klmdb_context;
+
+static krb5_error_code
+klerr(krb5_context context, int err, const char *msg)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc = context->dal_handle->db_context;
+
+    /* Pass through system errors; map MDB errors to a com_err code. */
+    ret = (err > 0) ? err : KRB5_KDB_ACCESS_ERROR;
+
+    k5_setmsg(context, ret, _("%s (path: %s): %s"), msg, dbc->path,
+              mdb_strerror(err));
+    return ret;
+}
+
+/* Using db_args and the profile, create a DB context inside context and
+ * initialize its configurable parameters. */
+static krb5_error_code
+configure_context(krb5_context context, const char *conf_section,
+                  char *const *db_args)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc;
+    char *pval = NULL;
+    const char *path = NULL;
+    profile_t profile = context->profile;
+    int i, bval, ival;
+
+    dbc = k5alloc(sizeof(*dbc), &ret);
+    if (dbc == NULL)
+        return ret;
+    context->dal_handle->db_context = dbc;
+
+    for (i = 0; db_args != NULL && db_args[i] != NULL; i++) {
+        if (strcmp(db_args[i], "temporary") == 0) {
+            dbc->temporary = TRUE;
+        } else if (strcmp(db_args[i], "merge_nra") == 0) {
+            dbc->merge_nra = TRUE;
+        } else if (strncmp(db_args[i], "dbname=", 7) == 0) {
+            path = db_args[i] + 7;
+        } else {
+            ret = EINVAL;
+            k5_setmsg(context, ret, _("Unsupported argument \"%s\" for LMDB"),
+                      db_args[i]);
+            goto cleanup;
+        }
+    }
+
+    if (path == NULL) {
+        /* Check for database_name in the db_module section. */
+        ret = profile_get_string(profile, KDB_MODULE_SECTION, conf_section,
+                                 KRB5_CONF_DATABASE_NAME, NULL, &pval);
+        if (!ret && pval == NULL) {
+            /* For compatibility, check for database_name in the realm. */
+            ret = profile_get_string(profile, KDB_REALM_SECTION,
+                                     KRB5_DB_GET_REALM(context),
+                                     KRB5_CONF_DATABASE_NAME, DEFAULT_KDB_FILE,
+                                     &pval);
+        }
+        if (ret)
+            goto cleanup;
+        path = pval;
+    }
+
+    if (asprintf(&dbc->path, "%s.mdb", path) < 0) {
+        dbc->path = NULL;
+        ret = ENOMEM;
+        goto cleanup;
+    }
+    if (asprintf(&dbc->lockout_path, "%s.lockout.mdb", path) < 0) {
+        dbc->lockout_path = NULL;
+        ret = ENOMEM;
+        goto cleanup;
+    }
+
+    ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,
+                              KRB5_CONF_DISABLE_LAST_SUCCESS, FALSE, &bval);
+    if (ret)
+        goto cleanup;
+    dbc->disable_last_success = bval;
+
+    ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,
+                              KRB5_CONF_DISABLE_LOCKOUT, FALSE, &bval);
+    if (ret)
+        goto cleanup;
+    dbc->disable_lockout = bval;
+
+    ret = profile_get_integer(profile, KDB_MODULE_SECTION, conf_section,
+                              KRB5_CONF_MAPSIZE, DEFAULT_MAPSIZE, &ival);
+    if (ret)
+        goto cleanup;
+    dbc->mapsize = (size_t)ival * 1024 * 1024;
+
+    ret = profile_get_integer(profile, KDB_MODULE_SECTION, conf_section,
+                              KRB5_CONF_MAX_READERS, 0, &ival);
+    if (ret)
+        goto cleanup;
+    dbc->maxreaders = ival;
+
+    ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,
+                              KRB5_CONF_NOSYNC, FALSE, &bval);
+    if (ret)
+        goto cleanup;
+    dbc->nosync = bval;
+
+cleanup:
+    profile_release_string(pval);
+    return ret;
+}
+
+static krb5_error_code
+open_lmdb_env(krb5_context context, klmdb_context *dbc,
+              krb5_boolean is_lockout, krb5_boolean readonly,
+              MDB_env **env_out)
+{
+    krb5_error_code ret;
+    const char *path = is_lockout ? dbc->lockout_path : dbc->path;
+    unsigned int flags;
+    MDB_env *env = NULL;
+    int err;
+
+    *env_out = NULL;
+
+    err = mdb_env_create(&env);
+    if (err)
+        goto lmdb_error;
+
+    /* Use a pair of files instead of a subdirectory. */
+    flags = MDB_NOSUBDIR;
+
+    /*
+     * For the primary database, tie read transaction locktable slots to the
+     * transaction and not the thread, so read transactions for iteration
+     * cursors can coexist with short-lived transactions for operations invoked
+     * by the iteration callback..
+     */
+    if (!is_lockout)
+        flags |= MDB_NOTLS;
+
+    if (readonly)
+        flags |= MDB_RDONLY;
+
+    /* Durability for lockout records is never worth the performance penalty.
+     * For the primary environment it might be, so we make it configurable. */
+    if (is_lockout || dbc->nosync)
+        flags |= MDB_NOSYNC;
+
+    /* We use one database in the lockout env, two in the primary env. */
+    err = mdb_env_set_maxdbs(env, is_lockout ? 1 : 2);
+    if (err)
+        goto lmdb_error;
+
+    if (dbc->mapsize) {
+        err = mdb_env_set_mapsize(env, dbc->mapsize);
+        if (err)
+            goto lmdb_error;
+    }
+
+    if (dbc->maxreaders) {
+        err = mdb_env_set_maxreaders(env, dbc->maxreaders);
+        if (err)
+            goto lmdb_error;
+    }
+
+    err = mdb_env_open(env, path, flags, S_IRUSR | S_IWUSR);
+    if (err)
+        goto lmdb_error;
+
+    *env_out = env;
+    return 0;
+
+lmdb_error:
+    ret = klerr(context, err, _("LMDB environment open failure"));
+    mdb_env_close(env);
+    return ret;
+}
+
+/* Read a key from the primary environment, using a saved read transaction from
+ * the database context.  Return KRB5_KDB_NOENTRY if the key is not found. */
+static krb5_error_code
+fetch(krb5_context context, MDB_dbi db, MDB_val *key, MDB_val *val_out)
+{
+    krb5_error_code ret = 0;
+    klmdb_context *dbc = context->dal_handle->db_context;
+    int err;
+
+    if (dbc->read_txn == NULL)
+        err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &dbc->read_txn);
+    else
+        err = mdb_txn_renew(dbc->read_txn);
+
+    if (!err)
+        err = mdb_get(dbc->read_txn, db, key, val_out);
+
+    if (err == MDB_NOTFOUND)
+        ret = KRB5_KDB_NOENTRY;
+    else if (err)
+        ret = klerr(context, err, _("LMDB read failure"));
+
+    mdb_txn_reset(dbc->read_txn);
+    return ret;
+}
+
+/* If we are using a lockout database, try to fetch the lockout attributes for
+ * key and set them in entry. */
+static void
+fetch_lockout(krb5_context context, MDB_val *key, krb5_db_entry *entry)
+{
+    klmdb_context *dbc = context->dal_handle->db_context;
+    MDB_txn *txn = NULL;
+    MDB_val val;
+    int err;
+
+    if (dbc->lockout_env == NULL)
+        return;
+    err = mdb_txn_begin(dbc->lockout_env, NULL, MDB_RDONLY, &txn);
+    if (!err)
+        err = mdb_get(txn, dbc->lockout_db, key, &val);
+    if (!err && val.mv_size >= LOCKOUT_RECORD_LEN)
+        klmdb_decode_princ_lockout(context, entry, val.mv_data);
+    mdb_txn_abort(txn);
+}
+
+/*
+ * Store a value for key in the specified database within the primary
+ * environment.  Use the saved load transaction if one is present, or a
+ * temporary write transaction if not.  If no_overwrite is true and the key
+ * already exists, return KRB5_KDB_INUSE.  If must_overwrite is true and the
+ * key does not already exist, return KRB5_KDB_NOENTRY.
+ */
+static krb5_error_code
+put(krb5_context context, MDB_dbi db, char *keystr, uint8_t *bytes, size_t len,
+    krb5_boolean no_overwrite, krb5_boolean must_overwrite)
+{
+    klmdb_context *dbc = context->dal_handle->db_context;
+    unsigned int putflags = no_overwrite ? MDB_NOOVERWRITE : 0;
+    MDB_txn *temp_txn = NULL, *txn;
+    MDB_val key = { strlen(keystr), keystr }, val = { len, bytes }, dummy;
+    int err;
+
+    if (dbc->load_txn != NULL) {
+        txn = dbc->load_txn;
+    } else {
+        err = mdb_txn_begin(dbc->env, NULL, 0, &temp_txn);
+        if (err)
+            goto error;
+        txn = temp_txn;
+    }
+
+    if (must_overwrite && mdb_get(txn, db, &key, &dummy) == MDB_NOTFOUND) {
+        mdb_txn_abort(temp_txn);
+        return KRB5_KDB_NOENTRY;
+    }
+
+    err = mdb_put(txn, db, &key, &val, putflags);
+    if (err)
+        goto error;
+
+    if (temp_txn != NULL) {
+        err = mdb_txn_commit(temp_txn);
+        temp_txn = NULL;
+        if (err)
+            goto error;
+    }
+
+    return 0;
+
+error:
+    mdb_txn_abort(temp_txn);
+    if (err == MDB_KEYEXIST)
+        return KRB5_KDB_INUSE;
+    else
+        return klerr(context, err, _("LMDB write failure"));
+}
+
+/* Delete an entry from the specified env and database, using a temporary write
+ * transaction.  Return KRB5_KDB_NOENTRY if the key does not exist. */
+static krb5_error_code
+del(krb5_context context, MDB_env *env, MDB_dbi db, char *keystr)
+{
+    krb5_error_code ret = 0;
+    MDB_txn *txn = NULL;
+    MDB_val key = { strlen(keystr), keystr };
+    int err;
+
+    err = mdb_txn_begin(env, NULL, 0, &txn);
+    if (!err)
+        err = mdb_del(txn, db, &key, NULL);
+    if (!err) {
+        err = mdb_txn_commit(txn);
+        txn = NULL;
+    }
+
+    if (err == MDB_NOTFOUND)
+        ret = KRB5_KDB_NOENTRY;
+    else if (err)
+        ret = klerr(context, err, _("LMDB delete failure"));
+
+    mdb_txn_abort(txn);
+    return ret;
+}
+
+/* Zero out and unlink filename. */
+static krb5_error_code
+destroy_file(const char *filename)
+{
+    krb5_error_code ret;
+    struct stat st;
+    ssize_t len;
+    off_t pos;
+    uint8_t buf[BUFSIZ], zbuf[BUFSIZ] = { 0 };
+    int fd;
+
+    fd = open(filename, O_RDWR | O_CLOEXEC, 0);
+    if (fd < 0)
+        return errno;
+    set_cloexec_fd(fd);
+    if (fstat(fd, &st) == -1)
+        goto error;
+
+    memset(zbuf, 0, BUFSIZ);
+    pos = 0;
+    while (pos < st.st_size) {
+        len = read(fd, buf, BUFSIZ);
+        if (len < 0)
+            goto error;
+        /* Only rewrite the block if it's not already zeroed, in case the file
+         * is sparse. */
+        if (memcmp(buf, zbuf, len) != 0) {
+            (void)lseek(fd, pos, SEEK_SET);
+            len = write(fd, zbuf, len);
+            if (len < 0)
+                goto error;
+        }
+        pos += len;
+    }
+    close(fd);
+
+    if (unlink(filename) != 0)
+        return errno;
+    return 0;
+
+error:
+    ret = errno;
+    close(fd);
+    return ret;
+}
+
+static krb5_error_code
+klmdb_lib_init()
+{
+    return 0;
+}
+
+static krb5_error_code
+klmdb_lib_cleanup()
+{
+    return 0;
+}
+
+static krb5_error_code
+klmdb_fini(krb5_context context)
+{
+    klmdb_context *dbc;
+
+    dbc = context->dal_handle->db_context;
+    if (dbc == NULL)
+        return 0;
+    mdb_txn_abort(dbc->read_txn);
+    mdb_txn_abort(dbc->load_txn);
+    mdb_env_close(dbc->env);
+    mdb_env_close(dbc->lockout_env);
+    free(dbc->path);
+    free(dbc->lockout_path);
+    free(dbc);
+    context->dal_handle->db_context = NULL;
+    return 0;
+}
+
+static krb5_error_code
+klmdb_open(krb5_context context, char *conf_section, char **db_args, int mode)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc;
+    krb5_boolean readonly;
+    MDB_txn *txn = NULL;
+    struct stat st;
+    int err;
+
+    if (context->dal_handle->db_context != NULL)
+        return 0;
+
+    ret = configure_context(context, conf_section, db_args);
+    if (ret)
+        return ret;
+    dbc = context->dal_handle->db_context;
+
+    if (stat(dbc->path, &st) != 0) {
+        ret = ENOENT;
+        k5_setmsg(context, ret, _("LMDB file %s does not exist"), dbc->path);
+        goto error;
+    }
+
+    /* Open the primary environment and databases.  The KDC can open this
+     * environment read-only. */
+    readonly = (mode & KRB5_KDB_OPEN_RO) || (mode & KRB5_KDB_SRV_TYPE_KDC);
+    ret = open_lmdb_env(context, dbc, FALSE, readonly, &dbc->env);
+    if (ret)
+        goto error;
+    err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &txn);
+    if (err)
+        goto lmdb_error;
+    err = mdb_dbi_open(txn, "principal", 0, &dbc->princ_db);
+    if (err)
+        goto lmdb_error;
+    err = mdb_dbi_open(txn, "policy", 0, &dbc->policy_db);
+    if (err)
+        goto lmdb_error;
+    err = mdb_txn_commit(txn);
+    txn = NULL;
+    if (err)
+        goto lmdb_error;
+
+    /* Open the lockout environment and database if we will need it. */
+    if (!dbc->disable_last_success || !dbc->disable_lockout) {
+        readonly = !!(mode & KRB5_KDB_OPEN_RO);
+        ret = open_lmdb_env(context, dbc, TRUE, readonly, &dbc->lockout_env);
+        if (ret)
+            goto error;
+        err = mdb_txn_begin(dbc->lockout_env, NULL, MDB_RDONLY, &txn);
+        if (err)
+            goto lmdb_error;
+        err = mdb_dbi_open(txn, "lockout", 0, &dbc->lockout_db);
+        if (err)
+            goto lmdb_error;
+        err = mdb_txn_commit(txn);
+        txn = NULL;
+        if (err)
+            goto lmdb_error;
+    }
+
+    return 0;
+
+lmdb_error:
+    ret = klerr(context, err, _("LMDB open failure"));
+error:
+    mdb_txn_abort(txn);
+    klmdb_fini(context);
+    return ret;
+}
+
+static krb5_error_code
+klmdb_create(krb5_context context, char *conf_section, char **db_args)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc;
+    MDB_txn *txn = NULL;
+    struct stat st;
+    int err;
+
+    if (context->dal_handle->db_context != NULL)
+        return 0;
+
+    ret = configure_context(context, conf_section, db_args);
+    if (ret)
+        return ret;
+    dbc = context->dal_handle->db_context;
+
+    if (!dbc->temporary) {
+        if (stat(dbc->path, &st) == 0) {
+            ret = ENOENT;
+            k5_setmsg(context, ret, _("LMDB file %s already exists"),
+                      dbc->path);
+            goto error;
+        }
+    }
+
+    /* Open (and create if necessary) the LMDB environments. */
+    ret = open_lmdb_env(context, dbc, FALSE, FALSE, &dbc->env);
+    if (ret)
+        goto error;
+    ret = open_lmdb_env(context, dbc, TRUE, FALSE, &dbc->lockout_env);
+    if (ret)
+        goto error;
+
+    /* Open the primary databases, creating them if they don't exist. */
+    err = mdb_txn_begin(dbc->env, NULL, 0, &txn);
+    if (err)
+        goto lmdb_error;
+    err = mdb_dbi_open(txn, "principal", MDB_CREATE, &dbc->princ_db);
+    if (err)
+        goto lmdb_error;
+    err = mdb_dbi_open(txn, "policy", MDB_CREATE, &dbc->policy_db);
+    if (err)
+        goto lmdb_error;
+    err = mdb_txn_commit(txn);
+    txn = NULL;
+    if (err)
+        goto lmdb_error;
+
+    /* Create the lockout database if it doesn't exist. */
+    err = mdb_txn_begin(dbc->lockout_env, NULL, 0, &txn);
+    if (err)
+        goto lmdb_error;
+    err = mdb_dbi_open(txn, "lockout", MDB_CREATE, &dbc->lockout_db);
+    if (err)
+        goto lmdb_error;
+    err = mdb_txn_commit(txn);
+    txn = NULL;
+    if (err)
+        goto lmdb_error;
+
+    if (dbc->temporary) {
+        /* Create a load transaction and empty the primary databases within
+         * it. */
+        err = mdb_txn_begin(dbc->env, NULL, 0, &dbc->load_txn);
+        if (err)
+            goto lmdb_error;
+        err = mdb_drop(dbc->load_txn, dbc->princ_db, 0);
+        if (err)
+            goto lmdb_error;
+        err = mdb_drop(dbc->load_txn, dbc->policy_db, 0);
+        if (err)
+            goto lmdb_error;
+    }
+
+    /* Close the lockout environment if we won't need it. */
+    if (dbc->disable_last_success && dbc->disable_lockout) {
+        mdb_env_close(dbc->lockout_env);
+        dbc->lockout_env = NULL;
+        dbc->lockout_db = 0;
+    }
+
+    return 0;
+
+lmdb_error:
+    ret = klerr(context, err, _("LMDB create error"));
+error:
+    mdb_txn_abort(txn);
+    klmdb_fini(context);
+    return ret;
+}
+
+/* Unlink the "-lock" extension of path. */
+static krb5_error_code
+unlink_lock_file(krb5_context context, const char *path)
+{
+    char *lock_path;
+    int st;
+
+    if (asprintf(&lock_path, "%s-lock", path) < 0)
+        return ENOMEM;
+    st = unlink(lock_path);
+    if (st)
+        k5_prependmsg(context, st, _("Could not unlink %s"), lock_path);
+    free(lock_path);
+    return st;
+}
+
+static krb5_error_code
+klmdb_destroy(krb5_context context, char *conf_section, char **db_args)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc;
+
+    if (context->dal_handle->db_context != NULL)
+        klmdb_fini(context);
+    ret = configure_context(context, conf_section, db_args);
+    if (ret)
+        goto cleanup;
+    dbc = context->dal_handle->db_context;
+
+    ret = destroy_file(dbc->path);
+    if (ret)
+        goto cleanup;
+    ret = unlink_lock_file(context, dbc->path);
+    if (ret)
+        goto cleanup;
+
+    ret = destroy_file(dbc->lockout_path);
+    if (ret)
+        goto cleanup;
+    ret = unlink_lock_file(context, dbc->lockout_path);
+
+cleanup:
+    klmdb_fini(context);
+    return ret;
+}
+
+static krb5_error_code
+klmdb_get_principal(krb5_context context, krb5_const_principal searchfor,
+                    unsigned int flags, krb5_db_entry **entry_out)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc = context->dal_handle->db_context;
+    MDB_val key, val;
+    char *name = NULL;
+
+    *entry_out = NULL;
+    if (dbc == NULL)
+        return KRB5_KDB_DBNOTINITED;
+
+    ret = krb5_unparse_name(context, searchfor, &name);
+    if (ret)
+        goto cleanup;
+
+    key.mv_data = name;
+    key.mv_size = strlen(name);
+    ret = fetch(context, dbc->princ_db, &key, &val);
+    if (ret)
+        goto cleanup;
+
+    ret = klmdb_decode_princ(context, name, strlen(name),
+                             val.mv_data, val.mv_size, entry_out);
+    if (ret)
+        goto cleanup;
+
+    fetch_lockout(context, &key, *entry_out);
+
+cleanup:
+    krb5_free_unparsed_name(context, name);
+    return ret;
+}
+
+static krb5_error_code
+klmdb_put_principal(krb5_context context, krb5_db_entry *entry, char **db_args)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc = context->dal_handle->db_context;
+    MDB_val key, val, dummy;
+    MDB_txn *txn = NULL;
+    uint8_t lockout[LOCKOUT_RECORD_LEN], *enc;
+    size_t len;
+    char *name = NULL;
+    int err;
+
+    if (db_args != NULL) {
+        /* This module does not support DB arguments for put_principal. */
+        k5_setmsg(context, EINVAL, _("Unsupported argument \"%s\" for lmdb"),
+                  db_args[0]);
+        return EINVAL;
+    }
+
+    if (dbc == NULL)
+        return KRB5_KDB_DBNOTINITED;
+
+    ret = krb5_unparse_name(context, entry->princ, &name);
+    if (ret)
+        goto cleanup;
+
+    ret = klmdb_encode_princ(context, entry, &enc, &len);
+    if (ret)
+        goto cleanup;
+    ret = put(context, dbc->princ_db, name, enc, len, FALSE, FALSE);
+    free(enc);
+    if (ret)
+        goto cleanup;
+
+    /*
+     * Write the lockout attributes to the lockout database if we are using
+     * one.  During a load operation, changes to lockout attributes will become
+     * visible before the load is finished, which is an acceptable compromise
+     * on load atomicity.
+     */
+    if (dbc->lockout_env != NULL &&
+        (entry->mask & (LOCKOUT_MASK | KADM5_PRINCIPAL))) {
+        key.mv_data = name;
+        key.mv_size = strlen(name);
+        klmdb_encode_princ_lockout(context, entry, lockout);
+        val.mv_data = lockout;
+        val.mv_size = sizeof(lockout);
+        err = mdb_txn_begin(dbc->lockout_env, NULL, 0, &txn);
+        if (!err && dbc->merge_nra) {
+            /* During an iprop load, do not change existing lockout entries. */
+            if (mdb_get(txn, dbc->lockout_db, &key, &dummy) == 0)
+                goto cleanup;
+        }
+        if (!err)
+            err = mdb_put(txn, dbc->lockout_db, &key, &val, 0);
+        if (!err) {
+            err = mdb_txn_commit(txn);
+            txn = NULL;
+        }
+        if (err) {
+            ret = klerr(context, err, _("LMDB lockout write failure"));
+            goto cleanup;
+        }
+    }
+
+cleanup:
+    mdb_txn_abort(txn);
+    krb5_free_unparsed_name(context, name);
+    return ret;
+}
+
+static krb5_error_code
+klmdb_delete_principal(krb5_context context, krb5_const_principal searchfor)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc = context->dal_handle->db_context;
+    char *name;
+
+    if (dbc == NULL)
+        return KRB5_KDB_DBNOTINITED;
+
+    ret = krb5_unparse_name(context, searchfor, &name);
+    if (ret)
+        return ret;
+
+    ret = del(context, dbc->env, dbc->princ_db, name);
+    if (!ret && dbc->lockout_env != NULL)
+        (void)del(context, dbc->lockout_env, dbc->lockout_db, name);
+
+    krb5_free_unparsed_name(context, name);
+    return ret;
+}
+
+static krb5_error_code
+klmdb_iterate(krb5_context context, char *match_expr,
+              krb5_error_code (*func)(void *, krb5_db_entry *), void *arg,
+              krb5_flags iterflags)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc = context->dal_handle->db_context;
+    krb5_db_entry *entry;
+    MDB_txn *txn = NULL;
+    MDB_cursor *cursor = NULL;
+    MDB_val key, val;
+    MDB_cursor_op op = (iterflags & KRB5_DB_ITER_REV) ? MDB_PREV : MDB_NEXT;
+    int err;
+
+    if (dbc == NULL)
+        return KRB5_KDB_DBNOTINITED;
+
+    err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &txn);
+    if (err)
+        goto lmdb_error;
+    err = mdb_cursor_open(txn, dbc->princ_db, &cursor);
+    if (err)
+        goto lmdb_error;
+    for (;;) {
+        err = mdb_cursor_get(cursor, &key, &val, op);
+        if (err == MDB_NOTFOUND)
+            break;
+        if (err)
+            goto lmdb_error;
+        ret = klmdb_decode_princ(context, key.mv_data, key.mv_size,
+                                 val.mv_data, val.mv_size, &entry);
+        if (ret)
+            goto cleanup;
+        fetch_lockout(context, &key, entry);
+        ret = (*func)(arg, entry);
+        krb5_db_free_principal(context, entry);
+        if (ret)
+            goto cleanup;
+    }
+    ret = 0;
+    goto cleanup;
+
+lmdb_error:
+    ret = klerr(context, err, _("LMDB principal iteration failure"));
+cleanup:
+    mdb_cursor_close(cursor);
+    mdb_txn_abort(txn);
+    return ret;
+}
+
+krb5_error_code
+klmdb_get_policy(krb5_context context, char *name, osa_policy_ent_t *policy)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc = context->dal_handle->db_context;
+    MDB_val key, val;
+
+    *policy = NULL;
+    if (dbc == NULL)
+        return KRB5_KDB_DBNOTINITED;
+
+    key.mv_data = name;
+    key.mv_size = strlen(name);
+    ret = fetch(context, dbc->policy_db, &key, &val);
+    if (ret)
+        return ret;
+    return klmdb_decode_policy(context, name, strlen(name),
+                               val.mv_data, val.mv_size, policy);
+}
+
+static krb5_error_code
+klmdb_create_policy(krb5_context context, osa_policy_ent_t policy)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc = context->dal_handle->db_context;
+    uint8_t *enc;
+    size_t len;
+
+    if (dbc == NULL)
+        return KRB5_KDB_DBNOTINITED;
+
+    ret = klmdb_encode_policy(context, policy, &enc, &len);
+    if (ret)
+        return ret;
+    ret = put(context, dbc->policy_db, policy->name, enc, len, TRUE, FALSE);
+    free(enc);
+    return ret;
+}
+
+static krb5_error_code
+klmdb_put_policy(krb5_context context, osa_policy_ent_t policy)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc = context->dal_handle->db_context;
+    uint8_t *enc;
+    size_t len;
+
+    if (dbc == NULL)
+        return KRB5_KDB_DBNOTINITED;
+
+    ret = klmdb_encode_policy(context, policy, &enc, &len);
+    if (ret)
+        return ret;
+    ret = put(context, dbc->policy_db, policy->name, enc, len, FALSE, TRUE);
+    free(enc);
+    return ret;
+}
+
+static krb5_error_code
+klmdb_iter_policy(krb5_context context, char *match_entry,
+                  osa_adb_iter_policy_func func, void *arg)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc = context->dal_handle->db_context;
+    osa_policy_ent_t pol;
+    MDB_txn *txn = NULL;
+    MDB_cursor *cursor = NULL;
+    MDB_val key, val;
+    int err;
+
+    if (dbc == NULL)
+        return KRB5_KDB_DBNOTINITED;
+
+    err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &txn);
+    if (err)
+        goto lmdb_error;
+    err = mdb_cursor_open(txn, dbc->policy_db, &cursor);
+    if (err)
+        goto lmdb_error;
+    for (;;) {
+        err = mdb_cursor_get(cursor, &key, &val, MDB_NEXT);
+        if (err == MDB_NOTFOUND)
+            break;
+        if (err)
+            goto lmdb_error;
+        ret = klmdb_decode_policy(context, key.mv_data, key.mv_size,
+                                  val.mv_data, val.mv_size, &pol);
+        if (ret)
+            goto cleanup;
+        (*func)(arg, pol);
+        krb5_db_free_policy(context, pol);
+    }
+    ret = 0;
+    goto cleanup;
+
+lmdb_error:
+    ret = klerr(context, err, _("LMDB policy iteration failure"));
+cleanup:
+    mdb_cursor_close(cursor);
+    mdb_txn_abort(txn);
+    return ret;
+}
+
+static krb5_error_code
+klmdb_delete_policy(krb5_context context, char *policy)
+{
+    klmdb_context *dbc = context->dal_handle->db_context;
+
+    if (dbc == NULL)
+        return KRB5_KDB_DBNOTINITED;
+    return del(context, dbc->env, dbc->policy_db, policy);
+}
+
+static krb5_error_code
+klmdb_promote_db(krb5_context context, char *conf_section, char **db_args)
+{
+    krb5_error_code ret = 0;
+    klmdb_context *dbc = context->dal_handle->db_context;
+    int err;
+
+    if (dbc == NULL)
+        return KRB5_KDB_DBNOTINITED;
+    if (dbc->load_txn == NULL)
+        return EINVAL;
+    err = mdb_txn_commit(dbc->load_txn);
+    dbc->load_txn = NULL;
+    if (err)
+        ret = klerr(context, err, _("LMDB transaction commit failure"));
+    klmdb_fini(context);
+    return ret;
+}
+
+static krb5_error_code
+klmdb_check_policy_as(krb5_context context, krb5_kdc_req *request,
+                      krb5_db_entry *client, krb5_db_entry *server,
+                      krb5_timestamp kdc_time, const char **status,
+                      krb5_pa_data ***e_data)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc = context->dal_handle->db_context;
+
+    if (dbc->disable_lockout)
+        return 0;
+
+    ret = klmdb_lockout_check_policy(context, client, kdc_time);
+    if (ret == KRB5KDC_ERR_CLIENT_REVOKED)
+        *status = "LOCKED_OUT";
+    return ret;
+}
+
+static void
+klmdb_audit_as_req(krb5_context context, krb5_kdc_req *request,
+                   const krb5_address *local_addr,
+                   const krb5_address *remote_addr, krb5_db_entry *client,
+                   krb5_db_entry *server, krb5_timestamp authtime,
+                   krb5_error_code status)
+{
+    klmdb_context *dbc = context->dal_handle->db_context;
+
+    (void)klmdb_lockout_audit(context, client, authtime, status,
+                              dbc->disable_last_success, dbc->disable_lockout);
+}
+
+krb5_error_code
+klmdb_update_lockout(krb5_context context, krb5_db_entry *entry,
+                     krb5_timestamp stamp, krb5_boolean zero_fail_count,
+                     krb5_boolean set_last_success,
+                     krb5_boolean set_last_failure)
+{
+    krb5_error_code ret;
+    klmdb_context *dbc = context->dal_handle->db_context;
+    krb5_db_entry dummy = { 0 };
+    uint8_t lockout[LOCKOUT_RECORD_LEN];
+    MDB_txn *txn = NULL;
+    MDB_val key, val;
+    char *name = NULL;
+    int err;
+
+    if (dbc == NULL)
+        return KRB5_KDB_DBNOTINITED;
+    if (dbc->lockout_env == NULL)
+        return 0;
+    if (!zero_fail_count && !set_last_success && !set_last_failure)
+        return 0;
+
+    ret = krb5_unparse_name(context, entry->princ, &name);
+    if (ret)
+        goto cleanup;
+    key.mv_data = name;
+    key.mv_size = strlen(name);
+
+    err = mdb_txn_begin(dbc->lockout_env, NULL, 0, &txn);
+    if (err)
+        goto lmdb_error;
+    /* Fetch base lockout info within txn so we update transactionally. */
+    err = mdb_get(txn, dbc->lockout_db, &key, &val);
+    if (!err && val.mv_size >= LOCKOUT_RECORD_LEN) {
+        klmdb_decode_princ_lockout(context, &dummy, val.mv_data);
+    } else {
+        dummy.last_success = entry->last_success;
+        dummy.last_failed = entry->last_failed;
+        dummy.fail_auth_count = entry->fail_auth_count;
+    }
+
+    if (zero_fail_count)
+        dummy.fail_auth_count = 0;
+    if (set_last_success)
+        dummy.last_success = stamp;
+    if (set_last_failure) {
+        dummy.last_failed = stamp;
+        dummy.fail_auth_count++;
+    }
+
+    klmdb_encode_princ_lockout(context, &dummy, lockout);
+    val.mv_data = lockout;
+    val.mv_size = sizeof(lockout);
+    err = mdb_put(txn, dbc->lockout_db, &key, &val, 0);
+    if (err)
+        goto lmdb_error;
+    err = mdb_txn_commit(txn);
+    txn = NULL;
+    if (err)
+        goto lmdb_error;
+    goto cleanup;
+
+lmdb_error:
+    ret = klerr(context, err, _("LMDB lockout update failure"));
+cleanup:
+    krb5_free_unparsed_name(context, name);
+    mdb_txn_abort(txn);
+    return 0;
+}
+
+kdb_vftabl PLUGIN_SYMBOL_NAME(krb5_lmdb, kdb_function_table) = {
+    .maj_ver = KRB5_KDB_DAL_MAJOR_VERSION,
+    .min_ver = 0,
+    .init_library = klmdb_lib_init,
+    .fini_library = klmdb_lib_cleanup,
+    .init_module = klmdb_open,
+    .fini_module = klmdb_fini,
+    .create = klmdb_create,
+    .destroy = klmdb_destroy,
+    .get_principal = klmdb_get_principal,
+    .put_principal = klmdb_put_principal,
+    .delete_principal = klmdb_delete_principal,
+    .iterate = klmdb_iterate,
+    .create_policy = klmdb_create_policy,
+    .get_policy = klmdb_get_policy,
+    .put_policy = klmdb_put_policy,
+    .iter_policy = klmdb_iter_policy,
+    .delete_policy = klmdb_delete_policy,
+    .promote_db = klmdb_promote_db,
+    .check_policy_as = klmdb_check_policy_as,
+    .audit_as_req = klmdb_audit_as_req
+};
diff --git a/src/plugins/kdb/lmdb/klmdb-int.h b/src/plugins/kdb/lmdb/klmdb-int.h
new file mode 100644
index 0000000..29bceae
--- /dev/null
+++ b/src/plugins/kdb/lmdb/klmdb-int.h
@@ -0,0 +1,78 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* plugins/kdb/lmdb/klmdb-int.h - internal declarations for LMDB KDB module */
+/*
+ * Copyright (C) 2018 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LMDB_INT_H
+#define LMDB_INT_H
+
+/* Length of a principal lockout record (three 32-bit fields) */
+#define LOCKOUT_RECORD_LEN 12
+
+krb5_error_code klmdb_encode_princ(krb5_context context,
+                                   const krb5_db_entry *entry,
+                                   uint8_t **enc_out, size_t *len_out);
+void klmdb_encode_princ_lockout(krb5_context context,
+                                const krb5_db_entry *entry,
+                                uint8_t buf[LOCKOUT_RECORD_LEN]);
+krb5_error_code klmdb_encode_policy(krb5_context context,
+                                    const osa_policy_ent_rec *pol,
+                                    uint8_t **enc_out, size_t *len_out);
+
+krb5_error_code klmdb_decode_princ(krb5_context context,
+                                   const void *key, size_t key_len,
+                                   const void *enc, size_t enc_len,
+                                   krb5_db_entry **entry_out);
+void klmdb_decode_princ_lockout(krb5_context context, krb5_db_entry *entry,
+                                const uint8_t buf[LOCKOUT_RECORD_LEN]);
+krb5_error_code klmdb_decode_policy(krb5_context context,
+                                    const void *key, size_t key_len,
+                                    const void *enc, size_t enc_len,
+                                    osa_policy_ent_t *pol_out);
+
+krb5_error_code klmdb_lockout_check_policy(krb5_context context,
+                                           krb5_db_entry *entry,
+                                           krb5_timestamp stamp);
+krb5_error_code klmdb_lockout_audit(krb5_context context, krb5_db_entry *entry,
+                                    krb5_timestamp stamp,
+                                    krb5_error_code status,
+                                    krb5_boolean disable_last_success,
+                                    krb5_boolean disable_lockout);
+krb5_error_code klmdb_update_lockout(krb5_context context,
+                                     krb5_db_entry *entry,
+                                     krb5_timestamp stamp,
+                                     krb5_boolean zero_fail_count,
+                                     krb5_boolean set_last_success,
+                                     krb5_boolean set_last_failure);
+
+krb5_error_code klmdb_get_policy(krb5_context context, char *name,
+                                 osa_policy_ent_t *policy);
+
+#endif /* LMDB_INT_H */
diff --git a/src/plugins/kdb/lmdb/klmdb.exports b/src/plugins/kdb/lmdb/klmdb.exports
new file mode 100644
index 0000000..f2b7c11
--- /dev/null
+++ b/src/plugins/kdb/lmdb/klmdb.exports
@@ -0,0 +1 @@
+kdb_function_table
diff --git a/src/plugins/kdb/lmdb/lockout.c b/src/plugins/kdb/lmdb/lockout.c
new file mode 100644
index 0000000..380d8b3
--- /dev/null
+++ b/src/plugins/kdb/lmdb/lockout.c
@@ -0,0 +1,180 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* plugins/kdb/lmdb/lockout.c */
+/*
+ * Copyright (C) 2009, 2018 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+#include "kdb.h"
+#include <kadm5/server_internal.h>
+#include "kdb5.h"
+#include "klmdb-int.h"
+
+static krb5_error_code
+lookup_lockout_policy(krb5_context context, krb5_db_entry *entry,
+                      krb5_kvno *pw_max_fail, krb5_deltat *pw_failcnt_interval,
+                      krb5_deltat *pw_lockout_duration)
+{
+    krb5_tl_data tl_data;
+    krb5_error_code code;
+    osa_princ_ent_rec adb;
+    XDR xdrs;
+
+    *pw_max_fail = 0;
+    *pw_failcnt_interval = 0;
+    *pw_lockout_duration = 0;
+
+    tl_data.tl_data_type = KRB5_TL_KADM_DATA;
+
+    code = krb5_dbe_lookup_tl_data(context, entry, &tl_data);
+    if (code != 0 || tl_data.tl_data_length == 0)
+        return code;
+
+    memset(&adb, 0, sizeof(adb));
+    xdrmem_create(&xdrs, (char *)tl_data.tl_data_contents,
+                  tl_data.tl_data_length, XDR_DECODE);
+    if (!xdr_osa_princ_ent_rec(&xdrs, &adb)) {
+        xdr_destroy(&xdrs);
+        return KADM5_XDR_FAILURE;
+    }
+
+    if (adb.policy != NULL) {
+        osa_policy_ent_t policy = NULL;
+
+        code = klmdb_get_policy(context, adb.policy, &policy);
+        if (code == 0) {
+            *pw_max_fail = policy->pw_max_fail;
+            *pw_failcnt_interval = policy->pw_failcnt_interval;
+            *pw_lockout_duration = policy->pw_lockout_duration;
+            krb5_db_free_policy(context, policy);
+        }
+    }
+
+    xdr_destroy(&xdrs);
+
+    xdrmem_create(&xdrs, NULL, 0, XDR_FREE);
+    xdr_osa_princ_ent_rec(&xdrs, &adb);
+    xdr_destroy(&xdrs);
+
+    return 0;
+}
+
+/* draft-behera-ldap-password-policy-10.txt 7.1 */
+static krb5_boolean
+locked_check_p(krb5_context context, krb5_timestamp stamp, krb5_kvno max_fail,
+               krb5_timestamp lockout_duration, krb5_db_entry *entry)
+{
+    krb5_timestamp unlock_time;
+
+    /* If the entry was unlocked since the last failure, it's not locked. */
+    if (krb5_dbe_lookup_last_admin_unlock(context, entry, &unlock_time) == 0 &&
+        !ts_after(entry->last_failed, unlock_time))
+        return FALSE;
+
+    if (max_fail == 0 || entry->fail_auth_count < max_fail)
+        return FALSE;
+
+    if (lockout_duration == 0)
+        return TRUE; /* principal permanently locked */
+
+    return ts_after(ts_incr(entry->last_failed, lockout_duration), stamp);
+}
+
+krb5_error_code
+klmdb_lockout_check_policy(krb5_context context, krb5_db_entry *entry,
+                           krb5_timestamp stamp)
+{
+    krb5_error_code code;
+    krb5_kvno max_fail = 0;
+    krb5_deltat failcnt_interval = 0;
+    krb5_deltat lockout_duration = 0;
+
+    code = lookup_lockout_policy(context, entry, &max_fail, &failcnt_interval,
+                                 &lockout_duration);
+    if (code != 0)
+        return code;
+
+    if (locked_check_p(context, stamp, max_fail, lockout_duration, entry))
+        return KRB5KDC_ERR_CLIENT_REVOKED;
+
+    return 0;
+}
+
+krb5_error_code
+klmdb_lockout_audit(krb5_context context, krb5_db_entry *entry,
+                    krb5_timestamp stamp, krb5_error_code status,
+                    krb5_boolean disable_last_success,
+                    krb5_boolean disable_lockout)
+{
+    krb5_error_code ret;
+    krb5_kvno max_fail = 0;
+    krb5_deltat failcnt_interval = 0, lockout_duration = 0;
+    krb5_boolean zero_fail_count = FALSE;
+    krb5_boolean set_last_success = FALSE, set_last_failure = FALSE;
+    krb5_timestamp unlock_time;
+
+    if (status != 0 && status != KRB5KDC_ERR_PREAUTH_FAILED &&
+        status != KRB5KRB_AP_ERR_BAD_INTEGRITY)
+        return 0;
+
+    if (!disable_lockout) {
+        ret = lookup_lockout_policy(context, entry, &max_fail,
+                                    &failcnt_interval, &lockout_duration);
+        if (ret)
+            return ret;
+    }
+
+    /*
+     * Don't continue to modify the DB for an already locked account.
+     * (In most cases, status will be KRB5KDC_ERR_CLIENT_REVOKED, and
+     * this check is unneeded, but in rare cases, we can fail with an
+     * integrity error or preauth failure before a policy check.)
+     */
+    if (locked_check_p(context, stamp, max_fail, lockout_duration, entry))
+        return 0;
+
+    /* Only mark the authentication as successful if the entry
+     * required preauthentication; otherwise we have no idea. */
+    if (status == 0 && (entry->attributes & KRB5_KDB_REQUIRES_PRE_AUTH)) {
+        if (!disable_lockout && entry->fail_auth_count != 0)
+            zero_fail_count = TRUE;
+        if (!disable_last_success)
+            set_last_success = TRUE;
+    } else if (status != 0 && !disable_lockout) {
+        /* Reset the failure counter after an administrative unlock. */
+        if (krb5_dbe_lookup_last_admin_unlock(context, entry,
+                                              &unlock_time) == 0 &&
+            !ts_after(entry->last_failed, unlock_time))
+            zero_fail_count = TRUE;
+
+        /* Reset the failure counter after failcnt_interval. */
+        if (failcnt_interval != 0 &&
+            ts_after(stamp, ts_incr(entry->last_failed, failcnt_interval)))
+            zero_fail_count = TRUE;
+
+        set_last_failure = TRUE;
+    }
+
+    return klmdb_update_lockout(context, entry, stamp, zero_fail_count,
+                                set_last_success, set_last_failure);
+}
diff --git a/src/plugins/kdb/lmdb/marshal.c b/src/plugins/kdb/lmdb/marshal.c
new file mode 100644
index 0000000..f49a2cb
--- /dev/null
+++ b/src/plugins/kdb/lmdb/marshal.c
@@ -0,0 +1,339 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/kdb/kdb_xdr.c */
+/*
+ * Copyright (C) 2018 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "k5-int.h"
+#include "k5-input.h"
+#include <kdb.h>
+#include "klmdb-int.h"
+
+static void
+put16(struct k5buf *buf, uint16_t num)
+{
+    uint8_t n[2];
+
+    store_16_le(num, n);
+    k5_buf_add_len(buf, n, 2);
+}
+
+static void
+put32(struct k5buf *buf, uint32_t num)
+{
+    uint8_t n[4];
+
+    store_32_le(num, n);
+    k5_buf_add_len(buf, n, 4);
+}
+
+static void
+put_tl_data(struct k5buf *buf, const krb5_tl_data *tl)
+{
+    for (; tl != NULL; tl = tl->tl_data_next) {
+        put16(buf, tl->tl_data_type);
+        put16(buf, tl->tl_data_length);
+        k5_buf_add_len(buf, tl->tl_data_contents, tl->tl_data_length);
+    }
+}
+
+krb5_error_code
+klmdb_encode_princ(krb5_context context, const krb5_db_entry *entry,
+                   uint8_t **enc_out, size_t *len_out)
+{
+    struct k5buf buf;
+    const krb5_key_data *kd;
+    int i, j;
+
+    *enc_out = NULL;
+    *len_out = 0;
+
+    k5_buf_init_dynamic(&buf);
+
+    put32(&buf, entry->attributes);
+    put32(&buf, entry->max_life);
+    put32(&buf, entry->max_renewable_life);
+    put32(&buf, entry->expiration);
+    put32(&buf, entry->pw_expiration);
+    put16(&buf, entry->n_tl_data);
+    put16(&buf, entry->n_key_data);
+    put_tl_data(&buf, entry->tl_data);
+    for (i = 0; i < entry->n_key_data; i++) {
+        kd = &entry->key_data[i];
+        put16(&buf, kd->key_data_ver);
+        put16(&buf, kd->key_data_kvno);
+        for (j = 0; j < kd->key_data_ver; j++) {
+            put16(&buf, kd->key_data_type[j]);
+            put16(&buf, kd->key_data_length[j]);
+            if (kd->key_data_length[j] > 0) {
+                k5_buf_add_len(&buf, kd->key_data_contents[j],
+                               kd->key_data_length[j]);
+            }
+        }
+    }
+
+    if (k5_buf_status(&buf) != 0)
+        return ENOMEM;
+
+    *enc_out = buf.data;
+    *len_out = buf.len;
+    return 0;
+}
+
+void
+klmdb_encode_princ_lockout(krb5_context context, const krb5_db_entry *entry,
+                           uint8_t buf[LOCKOUT_RECORD_LEN])
+{
+    store_32_le(entry->last_success, buf);
+    store_32_le(entry->last_failed, buf + 4);
+    store_32_le(entry->fail_auth_count, buf + 8);
+}
+
+krb5_error_code
+klmdb_encode_policy(krb5_context context, const osa_policy_ent_rec *pol,
+                    uint8_t **enc_out, size_t *len_out)
+{
+    struct k5buf buf;
+
+    *enc_out = NULL;
+    *len_out = 0;
+
+    k5_buf_init_dynamic(&buf);
+    put32(&buf, pol->pw_min_life);
+    put32(&buf, pol->pw_max_life);
+    put32(&buf, pol->pw_min_length);
+    put32(&buf, pol->pw_min_classes);
+    put32(&buf, pol->pw_history_num);
+    put32(&buf, pol->pw_max_fail);
+    put32(&buf, pol->pw_failcnt_interval);
+    put32(&buf, pol->pw_lockout_duration);
+    put32(&buf, pol->attributes);
+    put32(&buf, pol->max_life);
+    put32(&buf, pol->max_renewable_life);
+
+    if (pol->allowed_keysalts == NULL) {
+        put32(&buf, 0);
+    } else {
+        put32(&buf, strlen(pol->allowed_keysalts));
+        k5_buf_add(&buf, pol->allowed_keysalts);
+    }
+
+    put16(&buf, pol->n_tl_data);
+    put_tl_data(&buf, pol->tl_data);
+
+    if (k5_buf_status(&buf) != 0)
+        return ENOMEM;
+
+    *enc_out = buf.data;
+    *len_out = buf.len;
+    return 0;
+}
+
+static krb5_error_code
+get_tl_data(struct k5input *in, size_t count, krb5_tl_data **tl)
+{
+    krb5_error_code ret;
+    const uint8_t *contents;
+    size_t i, len;
+
+    for (i = 0; i < count; i++) {
+        *tl = k5alloc(sizeof(**tl), &ret);
+        if (*tl == NULL)
+            return ret;
+        (*tl)->tl_data_type = k5_input_get_uint16_le(in);
+        len = (*tl)->tl_data_length = k5_input_get_uint16_le(in);
+        contents = k5_input_get_bytes(in, len);
+        if (contents == NULL)
+            return KRB5_KDB_TRUNCATED_RECORD;
+        (*tl)->tl_data_contents = k5memdup(contents, len, &ret);
+        if ((*tl)->tl_data_contents == NULL)
+            return ret;
+        tl = &(*tl)->tl_data_next;
+    }
+
+    return 0;
+}
+
+krb5_error_code
+klmdb_decode_princ(krb5_context context, const void *key, size_t key_len,
+                   const void *enc, size_t enc_len, krb5_db_entry **entry_out)
+{
+    krb5_error_code ret;
+    struct k5input in;
+    krb5_db_entry *entry = NULL;
+    char *princname = NULL;
+    const uint8_t *contents;
+    int i, j;
+    size_t len;
+    krb5_key_data *kd;
+
+    *entry_out = NULL;
+
+    entry = k5alloc(sizeof(*entry), &ret);
+    if (entry == NULL)
+        goto cleanup;
+
+    princname = k5memdup0(key, key_len, &ret);
+    if (princname == NULL)
+        goto cleanup;
+    ret = krb5_parse_name(context, princname, &entry->princ);
+    if (ret)
+        goto cleanup;
+
+    k5_input_init(&in, enc, enc_len);
+    entry->attributes = k5_input_get_uint32_le(&in);
+    entry->max_life = k5_input_get_uint32_le(&in);
+    entry->max_renewable_life = k5_input_get_uint32_le(&in);
+    entry->expiration = k5_input_get_uint32_le(&in);
+    entry->pw_expiration = k5_input_get_uint32_le(&in);
+    entry->n_tl_data = k5_input_get_uint16_le(&in);
+    entry->n_key_data = k5_input_get_uint16_le(&in);
+    if (entry->n_tl_data < 0 || entry->n_key_data < 0) {
+        ret = KRB5_KDB_TRUNCATED_RECORD;
+        goto cleanup;
+    }
+
+    ret = get_tl_data(&in, entry->n_tl_data, &entry->tl_data);
+    if (ret)
+        goto cleanup;
+
+    if (entry->n_key_data > 0) {
+        entry->key_data = k5calloc(entry->n_key_data, sizeof(*entry->key_data),
+                                   &ret);
+        if (entry->key_data == NULL)
+            goto cleanup;
+    }
+    for (i = 0; i < entry->n_key_data; i++) {
+        kd = &entry->key_data[i];
+        kd->key_data_ver = k5_input_get_uint16_le(&in);
+        kd->key_data_kvno = k5_input_get_uint16_le(&in);
+        if (kd->key_data_ver < 0 &&
+            kd->key_data_ver > KRB5_KDB_V1_KEY_DATA_ARRAY) {
+            ret = KRB5_KDB_BAD_VERSION;
+            goto cleanup;
+        }
+        for (j = 0; j < kd->key_data_ver; j++) {
+            kd->key_data_type[j] = k5_input_get_uint16_le(&in);
+            len = kd->key_data_length[j] = k5_input_get_uint16_le(&in);
+            contents = k5_input_get_bytes(&in, len);
+            if (contents == NULL) {
+                ret = KRB5_KDB_TRUNCATED_RECORD;
+                goto cleanup;
+            }
+            if (len > 0) {
+                kd->key_data_contents[j] = k5memdup(contents, len, &ret);
+                if (kd->key_data_contents[j] == NULL)
+                    goto cleanup;
+            }
+        }
+    }
+
+    ret = in.status;
+    if (ret)
+        goto cleanup;
+
+    entry->len = KRB5_KDB_V1_BASE_LENGTH;
+    *entry_out = entry;
+    entry = NULL;
+
+cleanup:
+    free(princname);
+    krb5_db_free_principal(context, entry);
+    return ret;
+}
+
+void
+klmdb_decode_princ_lockout(krb5_context context, krb5_db_entry *entry,
+                           const uint8_t buf[LOCKOUT_RECORD_LEN])
+{
+    entry->last_success = load_32_le(buf);
+    entry->last_failed = load_32_le(buf + 4);
+    entry->fail_auth_count = load_32_le(buf + 8);
+}
+
+krb5_error_code
+klmdb_decode_policy(krb5_context context, const void *key, size_t key_len,
+                    const void *enc, size_t enc_len, osa_policy_ent_t *pol_out)
+{
+    krb5_error_code ret;
+    osa_policy_ent_t pol = NULL;
+    struct k5input in;
+    const char *str;
+    size_t len;
+
+    *pol_out = NULL;
+    pol = k5alloc(sizeof(*pol), &ret);
+    if (pol == NULL)
+        goto error;
+
+    pol->name = k5memdup0(key, key_len, &ret);
+    if (pol->name == NULL)
+        goto error;
+
+    k5_input_init(&in, enc, enc_len);
+    pol->pw_min_life = k5_input_get_uint32_le(&in);
+    pol->pw_max_life = k5_input_get_uint32_le(&in);
+    pol->pw_min_length = k5_input_get_uint32_le(&in);
+    pol->pw_min_classes = k5_input_get_uint32_le(&in);
+    pol->pw_history_num = k5_input_get_uint32_le(&in);
+    pol->pw_max_fail = k5_input_get_uint32_le(&in);
+    pol->pw_failcnt_interval = k5_input_get_uint32_le(&in);
+    pol->pw_lockout_duration = k5_input_get_uint32_le(&in);
+    pol->attributes = k5_input_get_uint32_le(&in);
+    pol->max_life = k5_input_get_uint32_le(&in);
+    pol->max_renewable_life = k5_input_get_uint32_le(&in);
+
+    len = k5_input_get_uint32_le(&in);
+    if (len > 0) {
+        str = (char *)k5_input_get_bytes(&in, len);
+        if (str == NULL) {
+            ret = KRB5_KDB_TRUNCATED_RECORD;
+            goto error;
+        }
+        pol->allowed_keysalts = k5memdup0(str, len, &ret);
+        if (pol->allowed_keysalts == NULL)
+            goto error;
+    }
+
+    pol->n_tl_data = k5_input_get_uint16_le(&in);
+    ret = get_tl_data(&in, pol->n_tl_data, &pol->tl_data);
+    if (ret)
+        goto error;
+
+    ret = in.status;
+    if (ret)
+        goto error;
+
+    *pol_out = pol;
+    return 0;
+
+error:
+    krb5_db_free_policy(context, pol);
+    return ret;
+}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.