krb5 commit: Add LMDB KDB module

Greg Hudson ghudson at mit.edu
Tue May 29 15:00:04 EDT 2018


https://github.com/krb5/krb5/commit/03e3115222e7f8ce61b2daec7bcbb2365f3190f9
commit 03e3115222e7f8ce61b2daec7bcbb2365f3190f9
Author: Greg Hudson <ghudson at mit.edu>
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
+
+ at libnover_frag@
+ at 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;
+}


More information about the cvs-krb5 mailing list