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