summaryrefslogtreecommitdiffstats
path: root/third_party/heimdal/lib/hdb
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
commit4f5791ebd03eaec1c7da0865a383175b05102712 (patch)
tree8ce7b00f7a76baa386372422adebbe64510812d4 /third_party/heimdal/lib/hdb
parentInitial commit. (diff)
downloadsamba-upstream.tar.xz
samba-upstream.zip
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/heimdal/lib/hdb')
-rw-r--r--third_party/heimdal/lib/hdb/Makefile.am168
-rw-r--r--third_party/heimdal/lib/hdb/NTMakefile189
-rw-r--r--third_party/heimdal/lib/hdb/common.c1745
-rw-r--r--third_party/heimdal/lib/hdb/data-mkey.mit.des3.bebin0 -> 46 bytes
-rw-r--r--third_party/heimdal/lib/hdb/data-mkey.mit.des3.lebin0 -> 30 bytes
-rw-r--r--third_party/heimdal/lib/hdb/db.c391
-rw-r--r--third_party/heimdal/lib/hdb/db3.c495
-rw-r--r--third_party/heimdal/lib/hdb/dbinfo.c291
-rw-r--r--third_party/heimdal/lib/hdb/ext.c786
-rw-r--r--third_party/heimdal/lib/hdb/hdb-keytab.c232
-rw-r--r--third_party/heimdal/lib/hdb/hdb-ldap.c2117
-rw-r--r--third_party/heimdal/lib/hdb/hdb-mdb.c692
-rw-r--r--third_party/heimdal/lib/hdb/hdb-mitdb.c1500
-rw-r--r--third_party/heimdal/lib/hdb/hdb-sqlite.c1075
-rw-r--r--third_party/heimdal/lib/hdb/hdb.asn1251
-rw-r--r--third_party/heimdal/lib/hdb/hdb.c848
-rw-r--r--third_party/heimdal/lib/hdb/hdb.h337
-rw-r--r--third_party/heimdal/lib/hdb/hdb.opt5
-rw-r--r--third_party/heimdal/lib/hdb/hdb.schema144
-rw-r--r--third_party/heimdal/lib/hdb/hdb_err.et33
-rw-r--r--third_party/heimdal/lib/hdb/hdb_locl.h73
-rw-r--r--third_party/heimdal/lib/hdb/keys.c855
-rw-r--r--third_party/heimdal/lib/hdb/keytab.c449
-rw-r--r--third_party/heimdal/lib/hdb/libhdb-exports.def180
-rw-r--r--third_party/heimdal/lib/hdb/libhdb-version.rc36
-rw-r--r--third_party/heimdal/lib/hdb/mkey.c769
-rw-r--r--third_party/heimdal/lib/hdb/ndbm.c406
-rw-r--r--third_party/heimdal/lib/hdb/print.c597
-rw-r--r--third_party/heimdal/lib/hdb/test_concurrency.c506
-rw-r--r--third_party/heimdal/lib/hdb/test_dbinfo.c156
-rw-r--r--third_party/heimdal/lib/hdb/test_hdbkeys.c124
-rw-r--r--third_party/heimdal/lib/hdb/test_mkey.c55
-rw-r--r--third_party/heimdal/lib/hdb/test_namespace.c941
-rw-r--r--third_party/heimdal/lib/hdb/version-script.map189
34 files changed, 16635 insertions, 0 deletions
diff --git a/third_party/heimdal/lib/hdb/Makefile.am b/third_party/heimdal/lib/hdb/Makefile.am
new file mode 100644
index 0000000..89ab15d
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/Makefile.am
@@ -0,0 +1,168 @@
+# $Id$
+
+include $(top_srcdir)/Makefile.am.common
+
+WFLAGS += $(WFLAGS_ENUM_CONV)
+
+AM_CPPFLAGS += -I../asn1 -I$(srcdir)/../asn1
+AM_CPPFLAGS += $(INCLUDE_openldap) -DHDB_DB_DIR=\"$(DIR_hdbdir)\"
+AM_CPPFLAGS += -I$(srcdir)/../krb5
+AM_CPPFLAGS += $(INCLUDE_sqlite3)
+AM_CPPFLAGS += $(INCLUDE_libintl)
+AM_CPPFLAGS += -DHDB_DEFAULT_DB_TYPE=\"$(db_type):\"
+if HAVE_DBHEADER
+AM_CPPFLAGS += -I$(DBHEADER)
+endif
+
+BUILT_SOURCES = \
+ $(gen_files_hdb) \
+ hdb_err.c \
+ hdb_err.h
+
+gen_files_hdb = \
+ asn1_Event.c \
+ asn1_GENERATION.c \
+ asn1_HDB_EncTypeList.c \
+ asn1_HDB_Ext_Aliases.c \
+ asn1_HDB_Ext_Constrained_delegation_acl.c \
+ asn1_HDB_Ext_KeyRotation.c \
+ asn1_HDB_Ext_KeySet.c \
+ asn1_HDB_Ext_Lan_Manager_OWF.c \
+ asn1_HDB_Ext_Password.c \
+ asn1_HDB_Ext_PKINIT_acl.c \
+ asn1_HDB_Ext_PKINIT_cert.c \
+ asn1_HDB_Ext_PKINIT_hash.c \
+ asn1_HDB_EntryOrAlias.c \
+ asn1_HDB_entry_alias.c \
+ asn1_HDB_entry.c \
+ asn1_HDB_extension.c \
+ asn1_HDB_extensions.c \
+ asn1_HDB_keyset.c \
+ asn1_HDBFlags.c \
+ asn1_Key.c \
+ asn1_KeyRotation.c \
+ asn1_KeyRotationFlags.c \
+ asn1_Keys.c \
+ asn1_Salt.c
+
+CLEANFILES = $(BUILT_SOURCES) $(gen_files_hdb) \
+ hdb_asn1{,-priv}.h hdb_asn1_files hdb_asn1-template.c \
+ hdb_asn1_syms.c hdb_asn1_oids.c hdb_asn1.json \
+ testhdb-*
+
+LDADD = libhdb.la \
+ ../krb5/libkrb5.la \
+ ../asn1/libasn1.la \
+ $(LIB_hcrypto) \
+ $(LIB_roken) \
+ $(LIB_openldap) \
+ $(LIB_libintl) \
+ $(LIB_ldopen)
+
+
+if OPENLDAP_MODULE
+
+ldap_so = hdb_ldap.la
+hdb_ldap_la_SOURCES = hdb-ldap.c
+hdb_ldap_la_LDFLAGS = -module -avoid-version
+hdb_ldap_la_LIBADD = $(LIB_openldap) libhdb.la
+
+else
+
+ldap = hdb-ldap.c
+ldap_lib = $(LIB_openldap)
+
+endif
+
+
+lib_LTLIBRARIES = libhdb.la $(ldap_so)
+libhdb_la_LDFLAGS = -version-info 11:0:2
+
+if versionscript
+libhdb_la_LDFLAGS += $(LDFLAGS_VERSION_SCRIPT)$(srcdir)/version-script.map
+endif
+
+# test_hdbkeys and test_mkey are not tests -- they are manual test utils
+noinst_PROGRAMS = test_dbinfo test_hdbkeys test_mkey test_namespace test_concurrency
+TESTS = test_dbinfo test_namespace test_concurrency
+
+dist_libhdb_la_SOURCES = \
+ common.c \
+ db.c \
+ db3.c \
+ ext.c \
+ $(ldap) \
+ hdb.c \
+ hdb-sqlite.c \
+ hdb-keytab.c \
+ hdb-mdb.c \
+ hdb-mitdb.c \
+ hdb_locl.h \
+ keys.c \
+ keytab.c \
+ dbinfo.c \
+ mkey.c \
+ ndbm.c \
+ print.c
+
+nodist_libhdb_la_SOURCES = $(BUILT_SOURCES)
+
+libhdb_la_DEPENDENCIES = version-script.map
+
+include_HEADERS = hdb.h $(srcdir)/hdb-protos.h
+nodist_include_HEADERS = hdb_err.h hdb_asn1.h
+
+noinst_HEADERS = $(srcdir)/hdb-private.h
+
+libhdb_la_LIBADD = \
+ $(LIB_com_err) \
+ ../krb5/libkrb5.la \
+ ../asn1/libasn1.la \
+ $(LIB_sqlite3) \
+ $(LIBADD_roken) \
+ $(ldap_lib) \
+ $(LIB_dlopen) \
+ $(DB3LIB) $(DB1LIB) $(LMDBLIB) $(NDBMLIB)
+
+HDB_PROTOS = $(srcdir)/hdb-protos.h $(srcdir)/hdb-private.h
+
+ALL_OBJECTS = $(libhdb_la_OBJECTS)
+ALL_OBJECTS += $(test_dbinfo_OBJECTS)
+ALL_OBJECTS += $(test_hdbkeys_OBJECTS)
+ALL_OBJECTS += $(test_mkey_OBJECTS)
+ALL_OBJECTS += $(test_namespace_OBJECTS)
+ALL_OBJECTS += $(test_concurrency_OBJECTS)
+
+$(ALL_OBJECTS): $(HDB_PROTOS) hdb_asn1.h hdb_asn1-priv.h hdb_err.h
+
+test_namespace_LDADD = $(LDADD) $(test_hdbkeys_LIBS) $(LIB_heimbase)
+
+$(srcdir)/hdb-protos.h: $(dist_libhdb_la_SOURCES)
+ cd $(srcdir); perl ../../cf/make-proto.pl -q -P comment -o hdb-protos.h $(dist_libhdb_la_SOURCES) || rm -f hdb-protos.h
+
+$(srcdir)/hdb-private.h: $(dist_libhdb_la_SOURCES)
+ cd $(srcdir); perl ../../cf/make-proto.pl -q -P comment -p hdb-private.h $(dist_libhdb_la_SOURCES) || rm -f hdb-private.h
+
+$(gen_files_hdb) hdb_asn1.h hdb_asn1-priv.h: hdb_asn1_files
+ for genfile in '$(gen_files_hdb)'; do \
+ $(CLANG_FORMAT) -style=$(CLANG_FORMAT_STYLE) -i $${genfile}; \
+ done
+
+hdb_asn1_files: $(ASN1_COMPILE_DEP) $(srcdir)/hdb.asn1
+ $(ASN1_COMPILE) --option-file=$(srcdir)/hdb.opt $(srcdir)/hdb.asn1 hdb_asn1
+ @$(CLANG_FORMAT) -style=$(CLANG_FORMAT_STYLE) -i $$(cat hdb_asn1_files)
+
+# to help stupid solaris make
+
+hdb_err.h: hdb_err.et
+
+EXTRA_DIST = \
+ NTMakefile \
+ libhdb-version.rc \
+ libhdb-exports.def \
+ hdb.asn1 \
+ hdb_err.et \
+ hdb.schema \
+ version-script.map \
+ data-mkey.mit.des3.le \
+ data-mkey.mit.des3.be
diff --git a/third_party/heimdal/lib/hdb/NTMakefile b/third_party/heimdal/lib/hdb/NTMakefile
new file mode 100644
index 0000000..f4801f7
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/NTMakefile
@@ -0,0 +1,189 @@
+########################################################################
+#
+# Copyright (c) 2009, Secure Endpoints Inc.
+# 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.
+#
+
+RELDIR=lib\hdb
+
+intcflags=-DASN1_LIB
+
+!include ../../windows/NTMakefile.w32
+
+$(OBJ)\asn1_hdb_asn1.c $(OBJ)\hdb_asn1.h $(OBJ)\hdb_asn1-priv.h: $(BINDIR)\asn1_compile.exe hdb.asn1
+ cd $(OBJ)
+ $(BINDIR)\asn1_compile.exe --one-code-file --option-file=$(SRCDIR)\hdb.opt $(SRCDIR)\hdb.asn1 hdb_asn1
+ cd $(SRCDIR)
+
+!ifdef OPENLDAP_MODULE
+
+ldap_dll = $(BINDIR)\hdb_ldap.dll
+ldap_lib = $(LIBDIR)\hdb_ldap.lib
+ldap_objs = $(OBJ)\hdb-ldap.obj
+
+$(ldap_dll): $(ldap_objs)
+ $(DLLGUILINK) -implib:$(ldap_lib)
+ $(DLLPREP)
+
+clean::
+ -$(RM) $(ldap_dll)
+ -$(RM) $(ldap_lib)
+
+!else
+
+ldap = $(OBJ)\hdb-ldap.obj
+ldap_c = hdb-ldap.c
+
+!endif
+
+dist_libhdb_la_SOURCES = \
+ common.c \
+ db.c \
+ db3.c \
+ ext.c \
+ $(ldap_c) \
+ hdb.c \
+ hdb-sqlite.c \
+ hdb-keytab.c \
+ hdb-mitdb.c \
+ hdb-mdb.c \
+ hdb_locl.h \
+ keys.c \
+ keytab.c \
+ dbinfo.c \
+ mkey.c \
+ ndbm.c \
+ print.c
+
+libhdb_OBJs = \
+ $(OBJ)\common.obj \
+ $(OBJ)\db.obj \
+ $(OBJ)\db3.obj \
+ $(OBJ)\ext.obj \
+ $(ldap) \
+ $(OBJ)\hdb.obj \
+ $(OBJ)\hdb-sqlite.obj \
+ $(OBJ)\hdb-keytab.obj \
+ $(OBJ)\hdb-mitdb.obj \
+ $(OBJ)\keys.obj \
+ $(OBJ)\keytab.obj \
+ $(OBJ)\dbinfo.obj \
+ $(OBJ)\mkey.obj \
+ $(OBJ)\ndbm.obj \
+ $(OBJ)\print.obj \
+ $(OBJ)\asn1_hdb_asn1.obj \
+ $(OBJ)\hdb_err.obj
+
+$(OBJ)\hdb_err.c $(OBJ)\hdb_err.h: hdb_err.et
+ cd $(OBJ)
+ $(BINDIR)\compile_et.exe $(SRCDIR)\hdb_err.et
+ cd $(SRCDIR)
+
+$(OBJ)\hdb-protos.h: $(dist_libhdb_la_SOURCES)
+ $(PERL) ../../cf/make-proto.pl -q -P remove -o $@ $(dist_libhdb_la_SOURCES) \
+ || $(RM) $@
+
+$(OBJ)\hdb-private.h: $(dist_libhdb_la_SOURCES)
+ $(PERL) ../../cf/make-proto.pl -q -P remote -p $@ $(dist_libhdb_la_SOURCES) \
+ || $(RM) $@
+
+INCFILES= \
+ $(INCDIR)\hdb.h \
+ $(INCDIR)\hdb-protos.h \
+ $(OBJ)\hdb-private.h \
+ $(INCDIR)\hdb_err.h \
+ $(INCDIR)\hdb_asn1.h \
+ $(INCDIR)\hdb_asn1-priv.h
+
+!ifndef STATICLIBS
+
+RES=$(OBJ)\libhdb-version.res
+
+$(LIBHDB): $(BINDIR)\libhdb.dll
+
+$(BINDIR)\libhdb.dll: $(libhdb_OBJs) $(ldap_lib) $(LIBHEIMBASE) $(LIBHEIMDAL) $(LIBSQLITE) $(LIBCOMERR) $(LIBROKEN) $(RES)
+ $(DLLGUILINK) -def:libhdb-exports.def -implib:$(LIBHDB)
+ $(DLLPREP_NODIST)
+
+clean::
+ -$(RM) $(BINDIR)\libhdb.*
+
+!else
+
+$(LIBHDB): $(libhdb_OBJs) $(ldap_lib)
+ $(LIBCON)
+
+!endif
+
+all:: $(INCFILES) $(LIBHDB)
+
+clean::
+ -$(RM) $(INCFILES)
+ -$(RM) $(LIBHDB)
+
+test:: test-binaries test-run
+
+test-binaries: $(OBJ)\test_dbinfo.exe $(OBJ)\test_hdbkeys.exe $(OBJ)\test_namespace.exe
+
+$(OBJ)\test_dbinfo.exe: $(OBJ)\test_dbinfo.obj $(LIBHDB) $(LIBHEIMDAL) $(LIBROKEN) $(LIBVERS)
+ $(EXECONLINK)
+ $(EXEPREP_NODIST)
+
+$(OBJ)\test_hdbkeys.exe: $(OBJ)\test_hdbkeys.obj $(LIBHDB) $(LIBHEIMDAL) $(LIBROKEN) $(LIBVERS)
+ $(EXECONLINK)
+ $(EXEPREP_NODIST)
+
+$(OBJ)\test_namespace.exe: $(OBJ)\test_namespace.obj $(LIBHDB) $(LIBHEIMDAL) $(LIBHEIMBASE) $(LIBROKEN) $(LIBVERS)
+ $(EXECONLINK)
+ $(EXEPREP_NODIST)
+
+test-run:
+ cd $(OBJ)
+ -test_dbinfo.exe
+ -test_hdbkeys.exe
+ -test_namespace.exe
+ cd $(SRCDIR)
+
+!ifdef OPENLDAP_INC
+openldap_inc_flag=-I$(OPENLDAP_INC)
+!else
+openldap_inc_flag=
+!endif
+
+hdb_cflags=$(openldap_inc_flag) -I$(OBJ)
+
+{}.c{$(OBJ)}.obj::
+ $(C2OBJ_P) $(hdb_cflags) -DASN1_LIB
+
+{$(OBJ)}.c{$(OBJ)}.obj::
+ $(C2OBJ_P) $(hdb_cflags)
+
+test-exports:
+ $(PERL) ..\..\cf\w32-check-exported-symbols.pl --vs version-script.map --def libhdb-exports.def
+
+test:: test-exports
diff --git a/third_party/heimdal/lib/hdb/common.c b/third_party/heimdal/lib/hdb/common.c
new file mode 100644
index 0000000..a92cc13
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/common.c
@@ -0,0 +1,1745 @@
+/*
+ * Copyright (c) 1997-2002 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "krb5_locl.h"
+#include "hdb_locl.h"
+
+int
+hdb_principal2key(krb5_context context, krb5_const_principal p, krb5_data *key)
+{
+ Principal new;
+ size_t len = 0;
+ int ret;
+
+ ret = copy_Principal(p, &new);
+ if(ret)
+ return ret;
+ new.name.name_type = 0;
+
+ ASN1_MALLOC_ENCODE(Principal, key->data, key->length, &new, &len, ret);
+ if (ret == 0 && key->length != len)
+ krb5_abortx(context, "internal asn.1 encoder error");
+ free_Principal(&new);
+ return ret;
+}
+
+int
+hdb_key2principal(krb5_context context, krb5_data *key, krb5_principal p)
+{
+ return decode_Principal(key->data, key->length, p, NULL);
+}
+
+int
+hdb_entry2value(krb5_context context, const hdb_entry *ent, krb5_data *value)
+{
+ size_t len = 0;
+ int ret;
+
+ ASN1_MALLOC_ENCODE(HDB_entry, value->data, value->length, ent, &len, ret);
+ if (ret == 0 && value->length != len)
+ krb5_abortx(context, "internal asn.1 encoder error");
+ return ret;
+}
+
+int
+hdb_value2entry(krb5_context context, krb5_data *value, hdb_entry *ent)
+{
+ return decode_HDB_entry(value->data, value->length, ent, NULL);
+}
+
+int
+hdb_entry_alias2value(krb5_context context,
+ const hdb_entry_alias *alias,
+ krb5_data *value)
+{
+ size_t len = 0;
+ int ret;
+
+ ASN1_MALLOC_ENCODE(HDB_entry_alias, value->data, value->length,
+ alias, &len, ret);
+ if (ret == 0 && value->length != len)
+ krb5_abortx(context, "internal asn.1 encoder error");
+ return ret;
+}
+
+int
+hdb_value2entry_alias(krb5_context context, krb5_data *value,
+ hdb_entry_alias *ent)
+{
+ return decode_HDB_entry_alias(value->data, value->length, ent, NULL);
+}
+
+/*
+ * Some old databases may not have stored the salt with each key, which will
+ * break clients when aliases or canonicalization are used. Generate a
+ * default salt based on the real principal name in the entry to handle
+ * this case.
+ */
+static krb5_error_code
+add_default_salts(krb5_context context, HDB *db, hdb_entry *entry)
+{
+ krb5_error_code ret;
+ size_t i;
+ krb5_salt pwsalt;
+
+ ret = krb5_get_pw_salt(context, entry->principal, &pwsalt);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < entry->keys.len; i++) {
+ Key *key = &entry->keys.val[i];
+
+ if (key->salt != NULL ||
+ _krb5_enctype_requires_random_salt(context, key->key.keytype))
+ continue;
+
+ key->salt = calloc(1, sizeof(*key->salt));
+ if (key->salt == NULL) {
+ ret = krb5_enomem(context);
+ break;
+ }
+
+ key->salt->type = KRB5_PADATA_PW_SALT;
+
+ ret = krb5_data_copy(&key->salt->salt,
+ pwsalt.saltvalue.data,
+ pwsalt.saltvalue.length);
+ if (ret)
+ break;
+ }
+
+ krb5_free_salt(context, pwsalt);
+
+ return ret;
+}
+
+static krb5_error_code
+fetch_entry_or_alias(krb5_context context,
+ HDB *db,
+ krb5_const_principal principal,
+ unsigned flags,
+ hdb_entry *entry)
+{
+ HDB_EntryOrAlias eoa;
+ krb5_principal enterprise_principal = NULL;
+ krb5_data key, value;
+ krb5_error_code ret;
+
+ value.length = 0;
+ value.data = 0;
+ key = value;
+
+ if (principal->name.name_type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
+ if (principal->name.name_string.len != 1) {
+ ret = KRB5_PARSE_MALFORMED;
+ krb5_set_error_message(context, ret, "malformed principal: "
+ "enterprise name with %d name components",
+ principal->name.name_string.len);
+ return ret;
+ }
+ ret = krb5_parse_name(context, principal->name.name_string.val[0],
+ &enterprise_principal);
+ if (ret)
+ return ret;
+ principal = enterprise_principal;
+ }
+
+ ret = hdb_principal2key(context, principal, &key);
+ if (ret == 0)
+ ret = db->hdb__get(context, db, key, &value);
+ if (ret == 0)
+ ret = decode_HDB_EntryOrAlias(value.data, value.length, &eoa, NULL);
+ if (ret == 0 && eoa.element == choice_HDB_EntryOrAlias_entry) {
+ *entry = eoa.u.entry;
+ } else if (ret == 0 && eoa.element == choice_HDB_EntryOrAlias_alias) {
+ krb5_data_free(&key);
+ ret = hdb_principal2key(context, eoa.u.alias.principal, &key);
+ if (ret == 0) {
+ krb5_data_free(&value);
+ ret = db->hdb__get(context, db, key, &value);
+ }
+ if (ret == 0)
+ /* No alias chaining */
+ ret = hdb_value2entry(context, &value, entry);
+ krb5_free_principal(context, eoa.u.alias.principal);
+ } else if (ret == 0)
+ ret = ENOTSUP;
+ if (ret == 0 && enterprise_principal) {
+ /*
+ * Whilst Windows does not canonicalize enterprise principal names if
+ * the canonicalize flag is unset, the original specification in
+ * draft-ietf-krb-wg-kerberos-referrals-03.txt says we should.
+ */
+ entry->flags.force_canonicalize = 1;
+ }
+
+ /* HDB_F_GET_ANY indicates request originated from KDC (not kadmin) */
+ if (ret == 0 && eoa.element == choice_HDB_EntryOrAlias_alias &&
+ (flags & (HDB_F_CANON|HDB_F_GET_ANY)) == 0) {
+
+ /* `principal' was alias but canon not req'd */
+ free_HDB_entry(entry);
+ ret = HDB_ERR_NOENTRY;
+ }
+
+ krb5_free_principal(context, enterprise_principal);
+ krb5_data_free(&value);
+ krb5_data_free(&key);
+ principal = enterprise_principal = NULL;
+ return ret;
+}
+
+krb5_error_code
+_hdb_fetch_kvno(krb5_context context, HDB *db, krb5_const_principal principal,
+ unsigned flags, krb5_kvno kvno, hdb_entry *entry)
+{
+ krb5_error_code ret;
+
+ ret = fetch_entry_or_alias(context, db, principal, flags, entry);
+ if (ret)
+ return ret;
+
+ if ((flags & HDB_F_DECRYPT) && (flags & HDB_F_ALL_KVNOS)) {
+ /* Decrypt the current keys */
+ ret = hdb_unseal_keys(context, db, entry);
+ if (ret) {
+ hdb_free_entry(context, db, entry);
+ return ret;
+ }
+ /* Decrypt the key history too */
+ ret = hdb_unseal_keys_kvno(context, db, 0, flags, entry);
+ if (ret) {
+ hdb_free_entry(context, db, entry);
+ return ret;
+ }
+ } else if ((flags & HDB_F_DECRYPT)) {
+ if ((flags & HDB_F_KVNO_SPECIFIED) == 0 || kvno == entry->kvno) {
+ /* Decrypt the current keys */
+ ret = hdb_unseal_keys(context, db, entry);
+ if (ret) {
+ hdb_free_entry(context, db, entry);
+ return ret;
+ }
+ } else {
+ if ((flags & HDB_F_ALL_KVNOS))
+ kvno = 0;
+ /*
+ * Find and decrypt the keys from the history that we want,
+ * and swap them with the current keys
+ */
+ ret = hdb_unseal_keys_kvno(context, db, kvno, flags, entry);
+ if (ret) {
+ hdb_free_entry(context, db, entry);
+ return ret;
+ }
+ }
+ }
+ if ((flags & HDB_F_FOR_AS_REQ) && (flags & HDB_F_GET_CLIENT)) {
+ /*
+ * Generate default salt for any principals missing one; note such
+ * principals could include those for which a random (non-password)
+ * key was generated, but given the salt will be ignored by a keytab
+ * client it doesn't hurt to include the default salt.
+ */
+ ret = add_default_salts(context, db, entry);
+ if (ret) {
+ hdb_free_entry(context, db, entry);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+hdb_remove_aliases(krb5_context context, HDB *db, krb5_data *key)
+{
+ const HDB_Ext_Aliases *aliases;
+ krb5_error_code code;
+ hdb_entry oldentry;
+ krb5_data value;
+ size_t i;
+
+ code = db->hdb__get(context, db, *key, &value);
+ if (code == HDB_ERR_NOENTRY)
+ return 0;
+ else if (code)
+ return code;
+
+ code = hdb_value2entry(context, &value, &oldentry);
+ krb5_data_free(&value);
+ if (code)
+ return code;
+
+ code = hdb_entry_get_aliases(&oldentry, &aliases);
+ if (code || aliases == NULL) {
+ free_HDB_entry(&oldentry);
+ return code;
+ }
+ for (i = 0; i < aliases->aliases.len; i++) {
+ krb5_data akey;
+
+ code = hdb_principal2key(context, &aliases->aliases.val[i], &akey);
+ if (code == 0) {
+ code = db->hdb__del(context, db, akey);
+ krb5_data_free(&akey);
+ }
+ if (code) {
+ free_HDB_entry(&oldentry);
+ return code;
+ }
+ }
+ free_HDB_entry(&oldentry);
+ return 0;
+}
+
+static krb5_error_code
+hdb_add_aliases(krb5_context context, HDB *db,
+ unsigned flags, hdb_entry *entry)
+{
+ const HDB_Ext_Aliases *aliases;
+ krb5_error_code code;
+ krb5_data key, value;
+ size_t i;
+
+ code = hdb_entry_get_aliases(entry, &aliases);
+ if (code || aliases == NULL)
+ return code;
+
+ for (i = 0; i < aliases->aliases.len; i++) {
+ hdb_entry_alias entryalias;
+ entryalias.principal = entry->principal;
+
+ code = hdb_entry_alias2value(context, &entryalias, &value);
+ if (code)
+ return code;
+
+ code = hdb_principal2key(context, &aliases->aliases.val[i], &key);
+ if (code == 0) {
+ code = db->hdb__put(context, db, flags, key, value);
+ krb5_data_free(&key);
+ }
+ krb5_data_free(&value);
+ if (code)
+ return code;
+ }
+ return 0;
+}
+
+/* Check if new aliases are already used for other entries */
+static krb5_error_code
+hdb_check_aliases(krb5_context context, HDB *db, hdb_entry *entry)
+{
+ const HDB_Ext_Aliases *aliases = NULL;
+ HDB_EntryOrAlias eoa;
+ krb5_data akey, value;
+ size_t i;
+ int ret;
+
+ memset(&eoa, 0, sizeof(eoa));
+ krb5_data_zero(&value);
+ akey = value;
+
+ ret = hdb_entry_get_aliases(entry, &aliases);
+ for (i = 0; ret == 0 && aliases && i < aliases->aliases.len; i++) {
+ ret = hdb_principal2key(context, &aliases->aliases.val[i], &akey);
+ if (ret == 0)
+ ret = db->hdb__get(context, db, akey, &value);
+ if (ret == 0)
+ ret = decode_HDB_EntryOrAlias(value.data, value.length, &eoa, NULL);
+ if (ret == 0 && eoa.element != choice_HDB_EntryOrAlias_entry &&
+ eoa.element != choice_HDB_EntryOrAlias_alias)
+ ret = ENOTSUP;
+ if (ret == 0 && eoa.element == choice_HDB_EntryOrAlias_entry)
+ /* New alias names an existing non-alias entry in the HDB */
+ ret = HDB_ERR_EXISTS;
+ if (ret == 0 && eoa.element == choice_HDB_EntryOrAlias_alias &&
+ !krb5_principal_compare(context, eoa.u.alias.principal,
+ entry->principal))
+ /* New alias names an existing alias of a different entry */
+ ret = HDB_ERR_EXISTS;
+ if (ret == HDB_ERR_NOENTRY) /* from db->hdb__get */
+ /* New alias is a name that doesn't exist in the HDB */
+ ret = 0;
+
+ free_HDB_EntryOrAlias(&eoa);
+ krb5_data_free(&value);
+ krb5_data_free(&akey);
+ }
+ return ret;
+}
+
+/*
+ * Many HDB entries don't have `etypes' setup. Historically we use the
+ * enctypes of the selected keyset as the entry's supported enctypes, but that
+ * is problematic. By doing this at store time and, if need be, at fetch time,
+ * we can make sure to stop deriving supported etypes from keys in the long
+ * run. We also need kadm5/kadmin support for etypes. We'll use this function
+ * there to derive etypes when using a kadm5_principal_ent_t that lacks the new
+ * TL data for etypes.
+ */
+krb5_error_code
+hdb_derive_etypes(krb5_context context, hdb_entry *e, HDB_Ext_KeySet *base_keys)
+{
+ krb5_error_code ret = 0;
+ size_t i, k, netypes;
+ HDB_extension *ext;
+
+ if (!base_keys &&
+ (ext = hdb_find_extension(e, choice_HDB_extension_data_hist_keys)))
+ base_keys = &ext->data.u.hist_keys;
+
+ netypes = e->keys.len;
+ if (netypes == 0 && base_keys) {
+ /* There's no way that base_keys->val[i].keys.len == 0, but hey */
+ for (i = 0; netypes == 0 && i < base_keys->len; i++)
+ netypes = base_keys->val[i].keys.len;
+ }
+
+ if (netypes == 0)
+ return 0;
+
+ if (e->etypes != NULL) {
+ free(e->etypes->val);
+ e->etypes->len = 0;
+ e->etypes->val = 0;
+ } else if ((e->etypes = calloc(1, sizeof(e->etypes[0]))) == NULL) {
+ ret = krb5_enomem(context);
+ }
+ if (ret == 0 &&
+ (e->etypes->val = calloc(netypes, sizeof(e->etypes->val[0]))) == NULL)
+ ret = krb5_enomem(context);
+ if (ret) {
+ free(e->etypes);
+ e->etypes = 0;
+ return ret;
+ }
+ e->etypes->len = netypes;
+ for (i = 0; i < e->keys.len && i < netypes; i++)
+ e->etypes->val[i] = e->keys.val[i].key.keytype;
+ if (!base_keys || i)
+ return 0;
+ for (k = 0; i == 0 && k < base_keys->len; k++) {
+ if (!base_keys->val[k].keys.len)
+ continue;
+ for (; i < base_keys->val[k].keys.len; i++)
+ e->etypes->val[i] = base_keys->val[k].keys.val[i].key.keytype;
+ }
+ return 0;
+}
+
+krb5_error_code
+_hdb_store(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
+{
+ krb5_data key, value;
+ int code;
+
+ if (entry->flags.do_not_store ||
+ entry->flags.force_canonicalize)
+ return HDB_ERR_MISUSE;
+ /* check if new aliases already is used */
+ code = hdb_check_aliases(context, db, entry);
+ if (code)
+ return code;
+
+ if ((flags & HDB_F_PRECHECK) && (flags & HDB_F_REPLACE))
+ return 0;
+
+ if ((flags & HDB_F_PRECHECK)) {
+ code = hdb_principal2key(context, entry->principal, &key);
+ if (code)
+ return code;
+ code = db->hdb__get(context, db, key, &value);
+ krb5_data_free(&key);
+ if (code == 0)
+ krb5_data_free(&value);
+ if (code == HDB_ERR_NOENTRY)
+ return 0;
+ return code ? code : HDB_ERR_EXISTS;
+ }
+
+ if ((entry->etypes == NULL || entry->etypes->len == 0) &&
+ (code = hdb_derive_etypes(context, entry, NULL)))
+ return code;
+
+ if (entry->generation == NULL) {
+ struct timeval t;
+ entry->generation = malloc(sizeof(*entry->generation));
+ if(entry->generation == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ gettimeofday(&t, NULL);
+ entry->generation->time = t.tv_sec;
+ entry->generation->usec = t.tv_usec;
+ entry->generation->gen = 0;
+ } else
+ entry->generation->gen++;
+
+ code = hdb_seal_keys(context, db, entry);
+ if (code)
+ return code;
+
+ code = hdb_principal2key(context, entry->principal, &key);
+ if (code)
+ return code;
+
+ /* remove aliases */
+ code = hdb_remove_aliases(context, db, &key);
+ if (code) {
+ krb5_data_free(&key);
+ return code;
+ }
+ code = hdb_entry2value(context, entry, &value);
+ if (code == 0)
+ code = db->hdb__put(context, db, flags & HDB_F_REPLACE, key, value);
+ krb5_data_free(&value);
+ krb5_data_free(&key);
+ if (code)
+ return code;
+
+ code = hdb_add_aliases(context, db, flags, entry);
+
+ return code;
+}
+
+krb5_error_code
+_hdb_remove(krb5_context context, HDB *db,
+ unsigned flags, krb5_const_principal principal)
+{
+ krb5_data key, value;
+ HDB_EntryOrAlias eoa;
+ int is_alias = -1;
+ int code;
+
+ /*
+ * We only allow deletion of entries by canonical name. To remove an
+ * alias use kadm5_modify_principal().
+ *
+ * We need to determine if this is an alias. We decode as a
+ * HDB_EntryOrAlias, which is expensive -- we could decode as a
+ * HDB_entry_alias instead and assume it's an entry if decoding fails...
+ */
+
+ code = hdb_principal2key(context, principal, &key);
+ if (code == 0)
+ code = db->hdb__get(context, db, key, &value);
+ if (code == 0) {
+ code = decode_HDB_EntryOrAlias(value.data, value.length, &eoa, NULL);
+ krb5_data_free(&value);
+ }
+ if (code == 0) {
+ is_alias = eoa.element == choice_HDB_EntryOrAlias_entry ? 0 : 1;
+ free_HDB_EntryOrAlias(&eoa);
+ }
+
+ if ((flags & HDB_F_PRECHECK)) {
+ if (code == 0 && is_alias)
+ krb5_set_error_message(context, code = HDB_ERR_NOENTRY,
+ "Cannot delete alias of principal");
+ krb5_data_free(&key);
+ return code;
+ }
+
+ if (code == 0)
+ code = hdb_remove_aliases(context, db, &key);
+ if (code == 0)
+ code = db->hdb__del(context, db, key);
+ krb5_data_free(&key);
+ return code;
+}
+
+/* PRF+(K_base, pad, keylen(etype)) */
+static krb5_error_code
+derive_Key1(krb5_context context,
+ krb5_data *pad,
+ EncryptionKey *base,
+ krb5int32 etype,
+ EncryptionKey *nk)
+{
+ krb5_error_code ret;
+ krb5_crypto crypto = NULL;
+ krb5_data out;
+ size_t len;
+
+ out.data = 0;
+ out.length = 0;
+
+ ret = krb5_enctype_keysize(context, base->keytype, &len);
+ if (ret == 0)
+ ret = krb5_crypto_init(context, base, 0, &crypto);
+ if (ret == 0)
+ ret = krb5_crypto_prfplus(context, crypto, pad, len, &out);
+ if (crypto)
+ krb5_crypto_destroy(context, crypto);
+ if (ret == 0)
+ ret = krb5_random_to_key(context, etype, out.data, out.length, nk);
+ krb5_data_free(&out);
+ return ret;
+}
+
+/* PRF+(PRF+(K_base, princ, keylen(etype)), kvno, keylen(etype)) */
+/* XXX Make it PRF+(PRF+(K_base, princ, keylen(K_base.etype)), and lift it, kvno, keylen(etype)) */
+static krb5_error_code
+derive_Key(krb5_context context,
+ const char *princ,
+ krb5uint32 kvno,
+ EncryptionKey *base,
+ krb5int32 etype,
+ Key *nk)
+{
+ krb5_error_code ret = 0;
+ EncryptionKey intermediate;
+ krb5_data pad;
+
+ nk->salt = NULL;
+ nk->mkvno = NULL;
+ nk->key.keytype = 0;
+ nk->key.keyvalue.data = 0;
+ nk->key.keyvalue.length = 0;
+
+ intermediate.keytype = 0;
+ intermediate.keyvalue.data = 0;
+ intermediate.keyvalue.length = 0;
+ if (princ) {
+ /* Derive intermediate key for the given principal */
+ /* XXX Lift to optimize? */
+ pad.data = (void *)(uintptr_t)princ;
+ pad.length = strlen(princ);
+ ret = derive_Key1(context, &pad, base, etype, &intermediate);
+ if (ret == 0)
+ base = &intermediate;
+ } /* else `base' is already an intermediate key for the desired princ */
+
+ /* Derive final key for `kvno' from intermediate key */
+ kvno = htonl(kvno);
+ pad.data = &kvno;
+ pad.length = sizeof(kvno);
+ if (ret == 0)
+ ret = derive_Key1(context, &pad, base, etype, &nk->key);
+ free_EncryptionKey(&intermediate);
+ return ret;
+}
+
+/*
+ * PRF+(PRF+(K_base, princ, keylen(etype)), kvno, keylen(etype)) for one
+ * enctype.
+ */
+static krb5_error_code
+derive_Keys(krb5_context context,
+ const char *princ,
+ krb5uint32 kvno,
+ krb5int32 etype,
+ const Keys *base,
+ Keys *dk)
+
+{
+ krb5_error_code ret = 0;
+ size_t i;
+ Key nk;
+
+ dk->len = 0;
+ dk->val = 0;
+
+ /*
+ * The enctypes of the base keys is the list of enctypes to derive keys
+ * for. Still, we derive all keys from the first base key.
+ */
+ for (i = 0; ret == 0 && i < base->len; i++) {
+ if (etype != KRB5_ENCTYPE_NULL && etype != base->val[i].key.keytype)
+ continue;
+ ret = derive_Key(context, princ, kvno, &base->val[0].key,
+ base->val[i].key.keytype, &nk);
+ if (ret)
+ break;
+ ret = add_Keys(dk, &nk);
+ free_Key(&nk);
+ /*
+ * FIXME We need to finish kdc/kadm5/kadmin support for the `etypes' so
+ * we can reduce the number of keys in keytabs to just those in current
+ * use and only of *one* enctype.
+ *
+ * What we could do is derive *one* key and for the others output a
+ * one-byte key of the intended enctype (which will never work).
+ *
+ * We'll never need any keys but the first one...
+ */
+ }
+
+ if (ret)
+ free_Keys(dk);
+ return ret;
+}
+
+/* Helper for derive_keys_for_kr() */
+static krb5_error_code
+derive_keyset(krb5_context context,
+ const Keys *base_keys,
+ const char *princ,
+ krb5int32 etype,
+ krb5uint32 kvno,
+ KerberosTime set_time, /* "now" */
+ hdb_keyset *dks)
+{
+ dks->kvno = kvno;
+ dks->keys.val = 0;
+ dks->set_time = malloc(sizeof(*(dks->set_time)));
+ if (dks->set_time == NULL)
+ return krb5_enomem(context);
+ *dks->set_time = set_time;
+ return derive_Keys(context, princ, kvno, etype, base_keys, &dks->keys);
+}
+
+/* Possibly derive and install in `h' a keyset identified by `t' */
+static krb5_error_code
+derive_keys_for_kr(krb5_context context,
+ hdb_entry *h,
+ HDB_Ext_KeySet *base_keys,
+ int is_current_keyset,
+ int rotation_period_offset,
+ const char *princ,
+ krb5int32 etype,
+ krb5uint32 kvno_wanted,
+ KerberosTime t,
+ struct KeyRotation *krp)
+{
+ krb5_error_code ret;
+ hdb_keyset dks;
+ KerberosTime set_time, n;
+ krb5uint32 kvno;
+ size_t i;
+
+ if (rotation_period_offset < -1 || rotation_period_offset > 1)
+ return EINVAL; /* wat */
+
+ /*
+ * Compute `kvno' and `set_time' given `t' and `krp'.
+ *
+ * There be signed 32-bit time_t dragons here.
+ *
+ * (t - krp->epoch < 0) is better than (krp->epoch < t), making us more
+ * tolerant of signed 32-bit time_t here near 2038. Of course, we have
+ * signed 32-bit time_t dragons elsewhere.
+ */
+ if (t - krp->epoch < 0)
+ return 0; /* This KR is not relevant yet */
+ n = (t - krp->epoch) / krp->period;
+ n += rotation_period_offset;
+ set_time = krp->epoch + krp->period * n;
+ kvno = krp->base_kvno + n;
+
+
+ /*
+ * Do not waste cycles computing keys not wanted or needed.
+ * A past kvno is too old if its set_time + rotation period is in the past
+ * by more than half a rotation period, since then no service ticket
+ * encrypted with keys of that kvno can still be extant.
+ *
+ * A future kvno is not coming up soon enough if we're more than a quarter
+ * of the rotation period away from it.
+ *
+ * Recall: the assumption for virtually-keyed principals is that services
+ * fetch their future keys frequently enough that they'll never miss having
+ * the keys they need.
+ */
+ if (!is_current_keyset || rotation_period_offset != 0) {
+ if ((kvno_wanted && kvno != kvno_wanted) ||
+ t - (set_time + krp->period + (krp->period >> 1)) > 0 ||
+ (set_time - t > 0 && (set_time - t) > (krp->period >> 2)))
+ return 0;
+ }
+
+ for (i = 0; i < base_keys->len; i++) {
+ if (base_keys->val[i].kvno == krp->base_key_kvno)
+ break;
+ }
+ if (i == base_keys->len) {
+ /* Base key not found! */
+ if (kvno_wanted || is_current_keyset) {
+ krb5_set_error_message(context, ret = HDB_ERR_KVNO_NOT_FOUND,
+ "Base key version %u not found for %s",
+ krp->base_key_kvno, princ);
+ return ret;
+ }
+ return 0;
+ }
+
+ ret = derive_keyset(context, &base_keys->val[i].keys, princ, etype, kvno,
+ set_time, &dks);
+ if (ret == 0)
+ ret = hdb_install_keyset(context, h, is_current_keyset, &dks);
+
+ free_HDB_keyset(&dks);
+ return ret;
+}
+
+/* Derive and install current keys, and possibly preceding or next keys */
+static krb5_error_code
+derive_keys_for_current_kr(krb5_context context,
+ hdb_entry *h,
+ HDB_Ext_KeySet *base_keys,
+ const char *princ,
+ unsigned int flags,
+ krb5int32 etype,
+ krb5uint32 kvno_wanted,
+ KerberosTime t,
+ struct KeyRotation *krp,
+ KerberosTime future_epoch)
+{
+ krb5_error_code ret;
+
+ /* derive_keys_for_kr() for current kvno and install as the current keys */
+ ret = derive_keys_for_kr(context, h, base_keys, 1, 0, princ, etype,
+ kvno_wanted, t, krp);
+ if (!(flags & HDB_F_ALL_KVNOS))
+ return ret;
+
+ /* */
+
+
+ /*
+ * derive_keys_for_kr() for prev kvno if still needed -- it can only be
+ * needed if the prev kvno's start time is within this KR's epoch.
+ *
+ * Note that derive_keys_for_kr() can return without doing anything if this
+ * is isn't the current keyset. So these conditions need not be
+ * sufficiently narrow.
+ */
+ if (ret == 0 && t - krp->epoch >= krp->period)
+ ret = derive_keys_for_kr(context, h, base_keys, 0, -1, princ, etype,
+ kvno_wanted, t, krp);
+ /*
+ * derive_keys_for_kr() for next kvno if near enough, but only if it
+ * doesn't start after the next KR's epoch.
+ */
+ if (future_epoch &&
+ t - krp->epoch >= 0 /* We know! Hint to the compiler */) {
+ KerberosTime next_kvno_start, n;
+
+ n = (t - krp->epoch) / krp->period;
+ next_kvno_start = krp->epoch + krp->period * (n + 1);
+ if (future_epoch - next_kvno_start <= 0)
+ return ret;
+ }
+ if (ret == 0)
+ ret = derive_keys_for_kr(context, h, base_keys, 0, 1, princ, etype,
+ kvno_wanted, t, krp);
+ return ret;
+}
+
+/*
+ * Derive and install all keysets in `h' that `princ' needs at time `now'.
+ *
+ * This mutates the entry `h' to
+ *
+ * a) not have base keys,
+ * b) have keys derived from the base keys according to
+ * c) the key rotation periods for the base principal (possibly the same
+ * principal if it's a concrete principal with virtual keys), and the
+ * requested time, enctype, and kvno (all of which are optional, with zero
+ * implying some default).
+ *
+ * Arguments:
+ *
+ * - `flags' is the flags passed to `hdb_fetch_kvno()'
+ * - `princ' is the name of the principal we'll end up with in `entry'
+ * - `h_is_namespace' indicates whether `h' is for a namespace or a concrete
+ * principal (that might nonetheless have virtual/derived keys)
+ * - `t' is the time such that the derived keys are for kvnos needed at `t'
+ * - `etype' indicates what enctype to derive keys for (0 for all enctypes in
+ * `entry->etypes')
+ * - `kvno' requests a particular kvno, or all if zero
+ *
+ * The caller doesn't know if the principal needs key derivation -- we make
+ * that determination in this function.
+ *
+ * Note that this function is fully deterministic for any given set of
+ * arguments and HDB contents.
+ *
+ * Definitions:
+ *
+ * - A keyset is a set of keys for a single kvno.
+ * - A keyset is relevant IFF:
+ * - it is the keyset for a time period identified by `t' in a
+ * corresponding KR
+ * - it is a keyset for a past time period for which there may be extant,
+ * not-yet-expired tickets that a service may need to decrypt
+ * - it is a keyset for an upcoming time period that a service will need to
+ * fetch before that time period becomes current, that way the service
+ * can have keytab entries for those keys in time for when the KDC starts
+ * encrypting service tickets to those keys
+ *
+ * This function derives the keyset(s) for the current KR first. The idea is
+ * to optimize the order of resulting keytabs so that the most likely keys to
+ * be used come first.
+ *
+ * Invariants:
+ *
+ * - KR metadata is sane because sanity is checked for when storing HDB
+ * entries
+ * - KRs are sorted by epoch in descending order; KR #0's epoch is the most
+ * recent
+ * - KR periods are non-zero (we divide by period)
+ * - kvnos are numerically ordered and correspond to time periods
+ * - within each KR, the kvnos for larger times are larger than (or equal
+ * to) the kvnos of earlier times
+ * - at KR boundaries, the first kvno of the newer boundary is larger than
+ * the kvno of the last time period of the previous KR
+ * - the time `t' must fall into exactly one KR period
+ * - the time `t' must fall into exactly one period within a KR period
+ * - at most two kvnos will be relevant from the KR that `t' falls into
+ * (the current kvno for `t', and possibly either the preceding, or the
+ * next)
+ * - at most one kvno from non-current KRs will be derived: possibly one for a
+ * preceding KR, and possibly one from an upcoming KR
+ *
+ * There can be:
+ *
+ * - no KR extension (not a namespace principal, and no virtual keys)
+ * - 1, 2, or 3 KRs (see above)
+ * - the newest KR may have the `deleted' flag, meaning "does not exist after
+ * this epoch"
+ *
+ * Note that the last time period in any older KR can be partial.
+ *
+ * Timeline diagram:
+ *
+ * .......|--+--+...+--|---+---+---+...+--|----+...
+ * T20 T10 T11 RT12 T1n T01
+ * ^ ^ ^ ^ ^ ^ ^ T00
+ * | | | T22 T2n | | ^
+ * ^ | T21 | | |
+ * princ | | epoch of | epoch of
+ * did | | middle KR | newest epoch
+ * not | | |
+ * exist! | start of Note that T1n
+ * | second kvno is shown as shorter
+ * | in 1st epoch than preceding periods
+ * |
+ * ^
+ * first KR's
+ * epoch, and start
+ * of its first kvno
+ *
+ * Tmn == the start of the Mth KR's Nth time period.
+ * (higher M -> older KR; lower M -> newer KR)
+ * (N is the reverse: lower N -> older time period in KR)
+ * T20 == start of oldest KR -- no keys before this time will be derived.
+ * T2n == last time period in oldest KR
+ * T10 == start of middle KR
+ * T1n == last time period in middle KR
+ * T00 == start of newest KR
+ * T0n == current time period in newest KR for wall clock time
+ */
+static krb5_error_code
+derive_keys(krb5_context context,
+ unsigned flags,
+ krb5_const_principal princ,
+ int h_is_namespace,
+ krb5_timestamp t,
+ krb5int32 etype,
+ krb5uint32 kvno,
+ hdb_entry *h)
+{
+ HDB_Ext_KeyRotation kr;
+ HDB_Ext_KeySet base_keys;
+ krb5_error_code ret = 0;
+ size_t current_kr, future_kr, past_kr, i;
+ char *p = NULL;
+ int valid = 1;
+
+ if (!h_is_namespace && !h->flags.virtual_keys)
+ return 0;
+ h->flags.virtual = 1;
+
+ kr.len = 0;
+ kr.val = 0;
+ if (ret == 0) {
+ const HDB_Ext_KeyRotation *ckr;
+
+ /* Installing keys invalidates `ckr', so we copy it */
+ ret = hdb_entry_get_key_rotation(context, h, &ckr);
+ if (!ckr)
+ return ret;
+ if (ret == 0)
+ ret = copy_HDB_Ext_KeyRotation(ckr, &kr);
+ }
+
+ /* Get the base keys from the entry, and remove them */
+ base_keys.val = 0;
+ base_keys.len = 0;
+ if (ret == 0)
+ ret = _hdb_remove_base_keys(context, h, &base_keys, &kr);
+
+ /* Make sure we have h->etypes */
+ if (ret == 0 && !h->etypes)
+ ret = hdb_derive_etypes(context, h, &base_keys);
+
+ /* Keys not desired? Don't derive them! */
+ if (ret || !(flags & HDB_F_DECRYPT)) {
+ free_HDB_Ext_KeyRotation(&kr);
+ free_HDB_Ext_KeySet(&base_keys);
+ return ret;
+ }
+
+ /* The principal name will be used in key derivation and error messages */
+ if (ret == 0)
+ ret = krb5_unparse_name(context, princ, &p);
+
+ /* Sanity check key rotations, determine current & last kr */
+ if (ret == 0 && kr.len < 1)
+ krb5_set_error_message(context, ret = HDB_ERR_NOENTRY,
+ "no key rotation periods for %s", p);
+ if (ret == 0)
+ current_kr = future_kr = past_kr = kr.len;
+ else
+ current_kr = future_kr = past_kr = 1;
+
+ /*
+ * Identify a current, next, and previous KRs if there are any.
+ *
+ * There can be up to three KRs, ordered by epoch, descending, making up a
+ * timeline like:
+ *
+ * ...|---------|--------|------>
+ * ^ | | |
+ * | | | |
+ * | | | Newest KR (kr.val[0])
+ * | | Middle KR (kr.val[1])
+ * | Oldest (last) KR (kr.val[2])
+ * |
+ * Before the begging of time for this namespace
+ *
+ * We step through these from future towards past looking for the best
+ * future, current, and past KRs. The best current KR is one that has its
+ * epoch nearest to `t' but in the past of `t'.
+ *
+ * We validate KRs before storing HDB entries with the KR extension, so we
+ * can assume they are valid here. However, we do some validity checking,
+ * and if they're not valid, we pick the best current KR and ignore the
+ * others.
+ *
+ * In principle there cannot be two future KRs, but this function is
+ * deterministic and takes a time value, so it should not enforce this just
+ * so we can test. Enforcement of such rules should be done at store time.
+ */
+ for (i = 0; ret == 0 && i < kr.len; i++) {
+ /* Minimal validation: order and period */
+ if (i && kr.val[i - 1].epoch - kr.val[i].epoch <= 0) {
+ future_kr = past_kr = kr.len;
+ valid = 0;
+ }
+ if (!kr.val[i].period) {
+ future_kr = past_kr = kr.len;
+ valid = 0;
+ continue;
+ }
+ if (t - kr.val[i].epoch >= 0) {
+ /*
+ * `t' is in the future of this KR's epoch, so it's a candidate for
+ * either current or past KR.
+ */
+ if (current_kr == kr.len)
+ current_kr = i; /* First curr KR candidate; should be best */
+ else if (kr.val[current_kr].epoch - kr.val[i].epoch < 0)
+ current_kr = i; /* Invalid KRs, but better curr KR cand. */
+ else if (valid && past_kr == kr.len)
+ past_kr = i;
+ } else if (valid) {
+ /* This KR is in the future of `t', a candidate for next KR */
+ future_kr = i;
+ }
+ }
+ if (ret == 0 && current_kr == kr.len)
+ /* No current KR -> too soon */
+ krb5_set_error_message(context, ret = HDB_ERR_NOENTRY,
+ "Too soon for virtual principal to exist");
+
+ /* Check that the principal has not been marked deleted */
+ if (ret == 0 && current_kr < kr.len && kr.val[current_kr].flags.deleted)
+ krb5_set_error_message(context, ret = HDB_ERR_NOENTRY,
+ "virtual principal %s does not exist "
+ "because last key rotation period "
+ "marks deletion", p);
+
+ /*
+ * Derive and set in `h' its current kvno and current keys.
+ *
+ * This will set h->kvno as well.
+ *
+ * This may set up to TWO keysets for the current key rotation period:
+ * - current keys (h->keys and h->kvno)
+ * - possibly one future
+ * OR
+ * possibly one past keyset in hist_keys for the current_kr
+ */
+ if (ret == 0 && current_kr < kr.len)
+ ret = derive_keys_for_current_kr(context, h, &base_keys, p, flags,
+ etype, kvno, t, &kr.val[current_kr],
+ current_kr ? kr.val[0].epoch : 0);
+
+ /*
+ * Derive and set in `h' its future keys for next KR if it is soon to be
+ * current.
+ *
+ * We want to derive keys for the first kvno of the next (future) KR if
+ * it's sufficiently close to `t', meaning within 1 period of the current
+ * KR, but we want these keys to be available sooner, so 1.5 of the current
+ * period.
+ */
+ if (ret == 0 && future_kr < kr.len && (flags & HDB_F_ALL_KVNOS))
+ ret = derive_keys_for_kr(context, h, &base_keys, 0, 0, p, etype, kvno,
+ kr.val[future_kr].epoch, &kr.val[future_kr]);
+
+ /*
+ * Derive and set in `h' its past keys for the previous KR if its last time
+ * period could still have extant, unexpired service tickets encrypted in
+ * its keys.
+ */
+ if (ret == 0 && past_kr < kr.len && (flags & HDB_F_ALL_KVNOS))
+ ret = derive_keys_for_kr(context, h, &base_keys, 0, 0, p, etype, kvno,
+ kr.val[current_kr].epoch - 1, &kr.val[past_kr]);
+
+ /*
+ * Impose a bound on h->max_life so that [when the KDC is the caller]
+ * the KDC won't issue tickets longer lived than this.
+ */
+ if (ret == 0 && !h->max_life &&
+ (h->max_life = calloc(1, sizeof(h->max_life[0]))) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0 && *h->max_life > kr.val[current_kr].period >> 1)
+ *h->max_life = kr.val[current_kr].period >> 1;
+
+ free_HDB_Ext_KeyRotation(&kr);
+ free_HDB_Ext_KeySet(&base_keys);
+ free(p);
+ return ret;
+}
+
+/*
+ * Pick a best kvno for the given principal at the given time.
+ *
+ * Implements the [hdb] new_service_key_delay configuration parameter.
+ *
+ * In order for disparate keytab provisioning systems such as OSKT and our own
+ * kadmin ext_keytab and httpkadmind's get-keys to coexist, we need to be able
+ * to force keys set by the former to not become current keys until users of
+ * the latter have had a chance to fetch those keys into their keytabs. To do
+ * this we have to search the list of keys in the entry looking for the newest
+ * keys older than `now - db->new_service_key_delay'.
+ *
+ * The context is that OSKT's krb5_keytab is very happy to change keys in a way
+ * that requires all members of a cluster to rekey together. If one also
+ * wishes to have cluster members that opt out of this and just fetch current,
+ * past, and future keys periodically, then the keys set by OSKT must not come
+ * into effect until all the opt-out members have had a chance to fetch the new
+ * keys.
+ *
+ * The assumption is that services will fetch new keys periodically, say, every
+ * four hours. Then one can set `[hdb] new_service_key_delay = 8h' in the
+ * configuration and new keys set by OSKT will not be used until 8h after they
+ * are set.
+ *
+ * Naturally, this applies only to concrete principals with concrete keys.
+ */
+static krb5_error_code
+pick_kvno(krb5_context context,
+ HDB *db,
+ unsigned flags,
+ krb5_timestamp now,
+ krb5uint32 kvno,
+ hdb_entry *h)
+{
+ HDB_extension *ext;
+ HDB_Ext_KeySet keys;
+ time_t current = 0;
+ time_t best;
+ size_t i;
+
+ /*
+ * If we want a specific kvno, or if the caller doesn't want new keys
+ * delayed, or if there's no new-key delay configured, or we're not
+ * fetching for use as a service principal, then we're out.
+ */
+ if (!(flags & HDB_F_DELAY_NEW_KEYS) || kvno || h->flags.virtual ||
+ h->flags.virtual_keys || db->new_service_key_delay <= 0)
+ return 0;
+
+ /* No history -> current keyset is the only one and therefore the best */
+ ext = hdb_find_extension(h, choice_HDB_extension_data_hist_keys);
+ if (!ext)
+ return 0;
+
+ /* Assume the current keyset is the best to start with */
+ (void) hdb_entry_get_pw_change_time(h, &current);
+ if (current == 0 && h->modified_by)
+ current = h->modified_by->time;
+ if (current == 0)
+ current = h->created_by.time;
+
+ /* Current keyset starts out as best */
+ best = current;
+ kvno = h->kvno;
+
+ /* Look for a better keyset in the history */
+ keys = ext->data.u.hist_keys;
+ for (i = 0; i < keys.len; i++) {
+ /* No set_time? Ignore. Too new? Ignore */
+ if (!keys.val[i].set_time ||
+ keys.val[i].set_time[0] + db->new_service_key_delay > now)
+ continue;
+
+ /*
+ * Ignore the keyset with kvno 1 when the entry has better kvnos
+ * because kadmin's `ank -r' command immediately changes the keys.
+ */
+ if (kvno > 1 && keys.val[i].kvno == 1)
+ continue;
+
+ /*
+ * This keyset's set_time older than the previous best? Ignore.
+ * However, if the current best is the entry's current and that one
+ * is too new, then don't ignore this one.
+ */
+ if (keys.val[i].set_time[0] < best &&
+ (best != current || current + db->new_service_key_delay < now))
+ continue;
+
+ /*
+ * If two good enough keysets have the same set_time, take the keyset
+ * with the highest kvno.
+ */
+ if (keys.val[i].set_time[0] == best && keys.val[i].kvno <= kvno)
+ continue;
+
+ /*
+ * This keyset is clearly more current than the previous best keyset
+ * but still old enough to use for encrypting tickets with.
+ */
+ best = keys.val[i].set_time[0];
+ kvno = keys.val[i].kvno;
+ }
+ return hdb_change_kvno(context, kvno, h);
+}
+
+/*
+ * Make a WELLKNOWN/HOSTBASED-NAMESPACE/${svc}/${hostname} or
+ * WELLKNOWN/HOSTBASED-NAMESPACE/${svc}/${hostname}/${domainname} principal
+ * object, with the service and hostname components take from `wanted', but if
+ * the service name is not in the list `db->virtual_hostbased_princ_svcs[]'
+ * then use "_" (wildcard) instead. This way we can have different attributes
+ * for different services in the same namespaces.
+ *
+ * For example, virtual hostbased service names for the "host" service might
+ * have ok-as-delegate set, but ones for the "HTTP" service might not.
+ */
+static krb5_error_code
+make_namespace_princ(krb5_context context,
+ HDB *db,
+ krb5_const_principal wanted,
+ krb5_principal *namespace)
+{
+ krb5_error_code ret = 0;
+ const char *realm = krb5_principal_get_realm(context, wanted);
+ const char *comp0 = krb5_principal_get_comp_string(context, wanted, 0);
+ const char *comp1 = krb5_principal_get_comp_string(context, wanted, 1);
+ const char *comp2 = krb5_principal_get_comp_string(context, wanted, 2);
+ char * const *svcs = db->virtual_hostbased_princ_svcs;
+ size_t i;
+
+ *namespace = NULL;
+ if (comp0 == NULL || comp1 == NULL)
+ return EINVAL;
+ if (strcmp(comp0, "krbtgt") == 0)
+ return 0;
+
+ for (i = 0; svcs && svcs[i]; i++) {
+ if (strcmp(comp0, svcs[i]) == 0) {
+ comp0 = svcs[i];
+ break;
+ }
+ }
+ if (!svcs || !svcs[i])
+ comp0 = "_";
+
+ /* First go around, need a namespace princ. Make it! */
+ ret = krb5_build_principal(context, namespace, strlen(realm),
+ realm, KRB5_WELLKNOWN_NAME,
+ HDB_WK_NAMESPACE, comp0, NULL);
+ if (ret == 0)
+ ret = krb5_principal_set_comp_string(context, *namespace, 3, comp1);
+ if (ret == 0 && comp2)
+ /* Support domain-based names */
+ ret = krb5_principal_set_comp_string(context, *namespace, 4, comp2);
+ /* Caller frees `*namespace' on error */
+ return ret;
+}
+
+static int
+is_namespace_princ_p(krb5_context context,
+ krb5_const_principal princ)
+{
+ return
+ krb5_principal_get_num_comp(context, princ) >= 4
+ && strcmp(krb5_principal_get_comp_string(context, princ, 0),
+ KRB5_WELLKNOWN_NAME) == 0
+ && strcmp(krb5_principal_get_comp_string(context, princ, 1),
+ HDB_WK_NAMESPACE) == 0;
+}
+
+/* See call site */
+static krb5_error_code
+rewrite_hostname(krb5_context context,
+ krb5_const_principal wanted_princ,
+ krb5_const_principal ns_princ,
+ krb5_const_principal found_ns_princ,
+ char **s)
+{
+ const char *ns_host_part, *wanted_host_part, *found_host_part;
+ const char *p, *r;
+ size_t ns_host_part_len, wanted_host_part_len;
+
+ wanted_host_part = krb5_principal_get_comp_string(context, wanted_princ, 1);
+ wanted_host_part_len = strlen(wanted_host_part);
+ if (wanted_host_part_len > 256) {
+ krb5_set_error_message(context, HDB_ERR_NOENTRY,
+ "Aliases of host-based principals longer than "
+ "256 bytes not supported");
+ return HDB_ERR_NOENTRY;
+ }
+
+ ns_host_part = krb5_principal_get_comp_string(context, ns_princ, 3);
+ ns_host_part_len = strlen(ns_host_part);
+
+ /* Find `ns_host_part' as the tail of `wanted_host_part' */
+ for (r = p = strstr(wanted_host_part, ns_host_part);
+ r && strnlen(r, ns_host_part_len + 1) > ns_host_part_len;
+ p = (r = strstr(r, ns_host_part)) ? r : p)
+ ;
+ if (!p || strnlen(p, ns_host_part_len + 1) != ns_host_part_len)
+ return HDB_ERR_NOENTRY; /* Can't happen */
+ if (p == wanted_host_part || p[-1] != '.')
+ return HDB_ERR_NOENTRY;
+
+ found_host_part =
+ krb5_principal_get_comp_string(context, found_ns_princ, 3);
+ return
+ asprintf(s, "%.*s%s", (int)(p - wanted_host_part), wanted_host_part,
+ found_host_part) < 0 ||
+ *s == NULL ? krb5_enomem(context) : 0;
+}
+
+/*
+ * Fix `h->principal' to match the desired `princ' in the namespace
+ * `nsprinc' (which is either the same as `h->principal' or an alias
+ * of it).
+ */
+static krb5_error_code
+fix_princ_name(krb5_context context,
+ krb5_const_principal princ,
+ krb5_const_principal nsprinc,
+ hdb_entry *h)
+{
+ krb5_error_code ret = 0;
+ char *s = NULL;
+
+ if (!nsprinc)
+ return 0;
+ if (krb5_principal_get_num_comp(context, princ) < 2)
+ return HDB_ERR_NOENTRY;
+
+ /* `nsprinc' must be a namespace principal */
+
+ if (krb5_principal_compare(context, nsprinc, h->principal)) {
+ /*
+ * `h' is the HDB entry for `nsprinc', and `nsprinc' is its canonical
+ * name.
+ *
+ * Set the entry's principal name to the desired name. The keys will
+ * be fixed next (upstairs, but don't forget to!).
+ */
+ free_Principal(h->principal);
+ return copy_Principal(princ, h->principal);
+ }
+
+ if (!is_namespace_princ_p(context, h->principal)) {
+ /*
+ * The alias is a namespace, but the canonical name is not. WAT.
+ *
+ * Well, the KDC will just issue a referral anyways, so we can leave
+ * `h->principal' as is...
+ *
+ * Remove all of `h's keys just in case, and leave
+ * `h->principal' as-is.
+ */
+ free_Keys(&h->keys);
+ (void) hdb_entry_clear_password(context, h);
+ return hdb_clear_extension(context, h,
+ choice_HDB_extension_data_hist_keys);
+ }
+
+ /*
+ * A namespace alias of a namespace entry.
+ *
+ * We'll want to rewrite the original principal accordingly.
+ *
+ * E.g., if the caller wanted host/foo.ns.test.h5l.se and we
+ * found WELLKNOWN/HOSTBASED-NAMESPACE/ns.test.h5l.se is an
+ * alias of WELLKNOWN/HOSTBASED-NAMESPACE/ns.example.org, then
+ * we'll want to treat host/foo.ns.test.h5l.se as an alias of
+ * host/foo.ns.example.org.
+ */
+ if (krb5_principal_get_num_comp(context, h->principal) !=
+ 2 + krb5_principal_get_num_comp(context, princ))
+ ret = HDB_ERR_NOENTRY; /* Only host-based services for now */
+ if (ret == 0)
+ ret = rewrite_hostname(context, princ, nsprinc, h->principal, &s);
+ if (ret == 0) {
+ krb5_free_principal(context, h->principal);
+ h->principal = NULL;
+ ret = krb5_make_principal(context, &h->principal,
+ krb5_principal_get_realm(context, princ),
+ krb5_principal_get_comp_string(context,
+ princ, 0),
+ s,
+ NULL);
+ }
+ return ret;
+}
+
+/* Wrapper around db->hdb_fetch_kvno() that implements virtual princs/keys */
+static krb5_error_code
+fetch_it(krb5_context context,
+ HDB *db,
+ krb5_const_principal princ,
+ unsigned flags,
+ krb5_timestamp t,
+ krb5int32 etype,
+ krb5uint32 kvno,
+ hdb_entry *ent)
+{
+ krb5_const_principal tmpprinc = princ;
+ krb5_principal nsprinc = NULL;
+ krb5_error_code ret = 0;
+ const char *comp0 = krb5_principal_get_comp_string(context, princ, 0);
+ const char *comp1 = krb5_principal_get_comp_string(context, princ, 1);
+ const char *tmp;
+ size_t mindots = db->virtual_hostbased_princ_ndots;
+ size_t maxdots = db->virtual_hostbased_princ_maxdots;
+ size_t hdots = 0;
+ char *host = NULL;
+ int do_search = 0;
+
+ if (!db->enable_virtual_hostbased_princs)
+ maxdots = mindots = 0;
+ if (db->enable_virtual_hostbased_princs && comp1 &&
+ strcmp("krbtgt", comp0) != 0 && strcmp(KRB5_WELLKNOWN_NAME, comp0) != 0) {
+ char *htmp;
+
+ if ((host = strdup(comp1)) == NULL)
+ return krb5_enomem(context);
+
+ /* Strip out any :port */
+ htmp = strchr(host, ':');
+ if (htmp) {
+ if (strchr(htmp + 1, ':')) {
+ /* Extra ':'s? No virtualization for you! */
+ free(host);
+ host = NULL;
+ htmp = NULL;
+ } else {
+ *htmp = '\0';
+ }
+ }
+ /* Count dots in `host' */
+ for (hdots = 0, htmp = host; htmp && *htmp; htmp++)
+ if (*htmp == '.')
+ hdots++;
+
+ do_search = 1;
+ }
+
+ tmp = host ? host : comp1;
+ for (ret = HDB_ERR_NOENTRY; ret == HDB_ERR_NOENTRY; tmpprinc = nsprinc) {
+ krb5_error_code ret2 = 0;
+
+ /*
+ * We break out of this loop with ret == 0 only if we found the HDB
+ * entry we were looking for or the HDB entry for a matching namespace.
+ *
+ * Otherwise we break out with ret != 0, typically HDB_ERR_NOENTRY.
+ *
+ * First time through we lookup the principal as given.
+ *
+ * Next we lookup a namespace principal, stripping off hostname labels
+ * from the left until we find one or get tired of looking or run out
+ * of labels.
+ */
+ ret = db->hdb_fetch_kvno(context, db, tmpprinc, flags, kvno, ent);
+ if (ret != HDB_ERR_NOENTRY || hdots == 0 || hdots < mindots || !tmp ||
+ !do_search)
+ break;
+
+ /*
+ * Breadcrumb:
+ *
+ * - if we found a concrete principal, but it's been marked
+ * as now-virtual, then we must keep going
+ *
+ * But this will be coded in the future.
+ *
+ * Maybe we can take attributes from the concrete principal...
+ */
+
+ /*
+ * The namespace's hostname will not have more labels than maxdots + 1.
+ * Thus we truncate immediately down to maxdots + 1 if we haven't yet.
+ *
+ * Example: with maxdots == 3,
+ * foo.bar.baz.app.blah.example -> baz.app.blah.example
+ */
+ while (maxdots && hdots > maxdots && tmp) {
+ tmp = strchr(tmp, '.');
+ /* tmp != NULL because maxdots > 0; we check to quiet linters */
+ if (tmp == NULL) {
+ ret = HDB_ERR_NOENTRY;
+ goto out;
+ }
+ tmp++;
+ hdots--;
+ }
+
+ if (nsprinc == NULL)
+ /* First go around, need a namespace princ. Make it! */
+ ret2 = make_namespace_princ(context, db, tmpprinc, &nsprinc);
+
+ /* Update the hostname component of the namespace principal */
+ if (ret2 == 0)
+ ret2 = krb5_principal_set_comp_string(context, nsprinc, 3, tmp);
+ if (ret2)
+ ret = ret2;
+
+ if (tmp) {
+ /* Strip off left-most label for the next go-around */
+ if ((tmp = strchr(tmp, '.')))
+ tmp++;
+ hdots--;
+ } /* else we'll break out after the next db->hdb_fetch_kvno() call */
+ }
+
+ /*
+ * If unencrypted keys were requested, derive them. There may not be any
+ * key derivation to do, but that's decided in derive_keys().
+ */
+ if (ret == 0) {
+ /* Fix the principal name if namespaced */
+ ret = fix_princ_name(context, princ, nsprinc, ent);
+
+ /* Derive keys if namespaced or virtual */
+ if (ret == 0)
+ ret = derive_keys(context, flags, princ, !!nsprinc, t, etype, kvno,
+ ent);
+ /* Pick the best kvno for this principal at the given time */
+ if (ret == 0)
+ ret = pick_kvno(context, db, flags, t, kvno, ent);
+ }
+
+out:
+ if (ret != 0 && ret != HDB_ERR_WRONG_REALM)
+ hdb_free_entry(context, db, ent);
+ krb5_free_principal(context, nsprinc);
+ free(host);
+ return ret;
+}
+
+/**
+ * Fetch a principal's HDB entry, possibly generating virtual keys from base
+ * keys according to strict key rotation schedules. If a time is given, other
+ * than HDB I/O, this function is pure, thus usable for testing.
+ *
+ * HDB writers should use `db->hdb_fetch_kvno()' to avoid materializing virtual
+ * principals.
+ *
+ * HDB readers should use this function rather than `db->hdb_fetch_kvno()'
+ * unless they only want to see concrete principals and not bother generating
+ * any virtual keys.
+ *
+ * @param context Context
+ * @param db HDB
+ * @param principal Principal name
+ * @param flags Fetch flags
+ * @param t For virtual keys, use this as the point in time (use zero to mean "now")
+ * @param etype Key enctype (use KRB5_ENCTYPE_NULL to mean "preferred")
+ * @param kvno Key version number (use zero to mean "current")
+ * @param h Output HDB entry
+ *
+ * @return Zero on success, an error code otherwise.
+ */
+krb5_error_code
+hdb_fetch_kvno(krb5_context context,
+ HDB *db,
+ krb5_const_principal principal,
+ unsigned int flags,
+ krb5_timestamp t,
+ krb5int32 etype,
+ krb5uint32 kvno,
+ hdb_entry *h)
+{
+ krb5_error_code ret = HDB_ERR_NOENTRY;
+
+ flags |= kvno ? HDB_F_KVNO_SPECIFIED : 0; /* XXX is this needed */
+ if (t == 0)
+ krb5_timeofday(context, &t);
+ ret = fetch_it(context, db, principal, flags, t, etype, kvno, h);
+ if (ret == HDB_ERR_NOENTRY)
+ krb5_set_error_message(context, ret, "no such entry found in hdb");
+
+ /*
+ * This check is to support aliases in HDB; the force_canonicalize
+ * check is to allow HDB backends to support realm name canon
+ * independently of principal aliases (used by Samba).
+ */
+ if (ret == 0 && !(flags & HDB_F_ADMIN_DATA) &&
+ !h->flags.force_canonicalize &&
+ !krb5_realm_compare(context, principal, h->principal))
+ ret = HDB_ERR_WRONG_REALM;
+ return ret;
+}
+
+size_t ASN1CALL
+length_hdb_keyset(HDB_keyset *data)
+{
+ return length_HDB_keyset(data);
+}
+
+size_t ASN1CALL
+length_hdb_entry(HDB_entry *data)
+{
+ return length_HDB_entry(data);
+}
+
+size_t ASN1CALL
+length_hdb_entry_alias(HDB_entry_alias *data)
+{
+ return length_HDB_entry_alias(data);
+}
+
+void ASN1CALL
+free_hdb_keyset(HDB_keyset *data)
+{
+ free_HDB_keyset(data);
+}
+
+void ASN1CALL
+free_hdb_entry(HDB_entry *data)
+{
+ free_HDB_entry(data);
+}
+
+void ASN1CALL
+free_hdb_entry_alias(HDB_entry_alias *data)
+{
+ free_HDB_entry_alias(data);
+}
+
+size_t ASN1CALL
+copy_hdb_keyset(const HDB_keyset *from, HDB_keyset *to)
+{
+ return copy_HDB_keyset(from, to);
+}
+
+size_t ASN1CALL
+copy_hdb_entry(const HDB_entry *from, HDB_entry *to)
+{
+ return copy_HDB_entry(from, to);
+}
+
+size_t ASN1CALL
+copy_hdb_entry_alias(const HDB_entry_alias *from, HDB_entry_alias *to)
+{
+ return copy_HDB_entry_alias(from, to);
+}
+
+int ASN1CALL
+decode_hdb_keyset(const unsigned char *p,
+ size_t len,
+ HDB_keyset *data,
+ size_t *size)
+{
+ return decode_HDB_keyset(p, len, data, size);
+}
+
+int ASN1CALL
+decode_hdb_entry(const unsigned char *p,
+ size_t len,
+ HDB_entry *data,
+ size_t *size)
+{
+ return decode_HDB_entry(p, len, data, size);
+}
+
+int ASN1CALL
+decode_hdb_entry_alias(const unsigned char *p,
+ size_t len,
+ HDB_entry_alias *data,
+ size_t *size)
+{
+ return decode_HDB_entry_alias(p, len, data, size);
+}
+
+int ASN1CALL
+encode_hdb_keyset(unsigned char *p,
+ size_t len,
+ const HDB_keyset *data,
+ size_t *size)
+{
+ return encode_HDB_keyset(p, len, data, size);
+}
+
+int ASN1CALL
+encode_hdb_entry(unsigned char *p,
+ size_t len,
+ const HDB_entry *data,
+ size_t *size)
+{
+ return encode_HDB_entry(p, len, data, size);
+}
+
+int ASN1CALL
+encode_hdb_entry_alias(unsigned char *p,
+ size_t len,
+ const HDB_entry_alias *data,
+ size_t *size)
+{
+ return encode_HDB_entry_alias(p, len, data, size);
+}
diff --git a/third_party/heimdal/lib/hdb/data-mkey.mit.des3.be b/third_party/heimdal/lib/hdb/data-mkey.mit.des3.be
new file mode 100644
index 0000000..4278ed3
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/data-mkey.mit.des3.be
Binary files differ
diff --git a/third_party/heimdal/lib/hdb/data-mkey.mit.des3.le b/third_party/heimdal/lib/hdb/data-mkey.mit.des3.le
new file mode 100644
index 0000000..19fdc93
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/data-mkey.mit.des3.le
Binary files differ
diff --git a/third_party/heimdal/lib/hdb/db.c b/third_party/heimdal/lib/hdb/db.c
new file mode 100644
index 0000000..5fcce7b
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/db.c
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 1997 - 2001 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "hdb_locl.h"
+
+#if defined(HAVE_DB1)
+
+#if defined(HAVE_DB_185_H)
+#include <db_185.h>
+#elif defined(HAVE_DB_H)
+#include <db.h>
+#endif
+
+typedef struct {
+ HDB hdb; /* generic members */
+ int lock_fd; /* DB-specific */
+ int do_sync; /* DB-specific */
+} DB1_HDB;
+
+static krb5_error_code
+DB_close(krb5_context context, HDB *db)
+{
+ DB1_HDB *db1 = (DB1_HDB *)db;
+ DB *d = (DB*)db->hdb_db;
+
+ heim_assert(d != 0, "Closing already closed HDB");
+
+ (*d->close)(d);
+ db->hdb_db = 0;
+
+ if (db1->lock_fd >= 0) {
+ close(db1->lock_fd);
+ db1->lock_fd = -1;
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+DB_destroy(krb5_context context, HDB *db)
+{
+ krb5_error_code ret;
+
+ ret = hdb_clear_master_key(context, db);
+ krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
+ free(db->hdb_name);
+ free(db);
+ return ret;
+}
+
+static krb5_error_code
+DB_set_sync(krb5_context context, HDB *db, int on)
+{
+ DB1_HDB *db1 = (DB1_HDB *)db;
+ DB *d = (DB*)db->hdb_db;
+ krb5_error_code ret = 0;
+
+ db1->do_sync = on;
+ if (on) {
+ ret = (*d->sync)(d, 0);
+ if (ret == -1) {
+ ret = errno;
+ krb5_set_error_message(context, ret, "Database %s put sync error: %s",
+ db->hdb_name, strerror(ret));
+ }
+ }
+ return ret;
+}
+
+static krb5_error_code
+DB_lock(krb5_context context, HDB *db, int operation)
+{
+
+ return 0;
+}
+
+static krb5_error_code
+DB_unlock(krb5_context context, HDB *db)
+{
+
+ return 0;
+}
+
+
+static krb5_error_code
+DB_seq(krb5_context context, HDB *db,
+ unsigned flags, hdb_entry *entry, int flag)
+{
+ DB *d = (DB*)db->hdb_db;
+ DBT key, value;
+ krb5_data key_data, data;
+ int code;
+
+ code = (*d->seq)(d, &key, &value, flag);
+ if(code == -1) {
+ code = errno;
+ krb5_set_error_message(context, code, "Database %s seq error: %s",
+ db->hdb_name, strerror(code));
+ return code;
+ }
+ if(code == 1) {
+ krb5_clear_error_message(context);
+ return HDB_ERR_NOENTRY;
+ }
+
+ key_data.data = key.data;
+ key_data.length = key.size;
+ data.data = value.data;
+ data.length = value.size;
+ memset(entry, 0, sizeof(*entry));
+ if (hdb_value2entry(context, &data, entry))
+ return DB_seq(context, db, flags, entry, R_NEXT);
+ if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
+ code = hdb_unseal_keys (context, db, entry);
+ if (code)
+ hdb_free_entry (context, db, entry);
+ }
+ if (code == 0 && entry->principal == NULL) {
+ entry->principal = malloc(sizeof(*entry->principal));
+ if (entry->principal == NULL) {
+ code = ENOMEM;
+ krb5_set_error_message(context, code, "malloc: out of memory");
+ hdb_free_entry (context, db, entry);
+ } else {
+ hdb_key2principal(context, &key_data, entry->principal);
+ }
+ }
+ return code;
+}
+
+
+static krb5_error_code
+DB_firstkey(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
+{
+ return DB_seq(context, db, flags, entry, R_FIRST);
+}
+
+
+static krb5_error_code
+DB_nextkey(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
+{
+ return DB_seq(context, db, flags, entry, R_NEXT);
+}
+
+static krb5_error_code
+DB_rename(krb5_context context, HDB *db, const char *new_name)
+{
+ int ret;
+ char *old, *new;
+
+ if (strncmp(new_name, "db:", sizeof("db:") - 1) == 0)
+ new_name += sizeof("db:") - 1;
+ else if (strncmp(new_name, "db1:", sizeof("db1:") - 1) == 0)
+ new_name += sizeof("db1:") - 1;
+ asprintf(&old, "%s.db", db->hdb_name);
+ asprintf(&new, "%s.db", new_name);
+ ret = rename(old, new);
+ free(old);
+ free(new);
+ if(ret)
+ return errno;
+
+ free(db->hdb_name);
+ db->hdb_name = strdup(new_name);
+ return 0;
+}
+
+static krb5_error_code
+DB__get(krb5_context context, HDB *db, krb5_data key, krb5_data *reply)
+{
+ DB *d = (DB*)db->hdb_db;
+ DBT k, v;
+ int code;
+
+ k.data = key.data;
+ k.size = key.length;
+ code = (*d->get)(d, &k, &v, 0);
+ if(code < 0) {
+ code = errno;
+ krb5_set_error_message(context, code, "Database %s get error: %s",
+ db->hdb_name, strerror(code));
+ return code;
+ }
+ if(code == 1) {
+ krb5_clear_error_message(context);
+ return HDB_ERR_NOENTRY;
+ }
+
+ krb5_data_copy(reply, v.data, v.size);
+ return 0;
+}
+
+static krb5_error_code
+DB__put(krb5_context context, HDB *db, int replace,
+ krb5_data key, krb5_data value)
+{
+ DB1_HDB *db1 = (DB1_HDB *)db;
+ DB *d = (DB*)db->hdb_db;
+ DBT k, v;
+ int code;
+
+ k.data = key.data;
+ k.size = key.length;
+ v.data = value.data;
+ v.size = value.length;
+ krb5_clear_error_message(context);
+ code = (*d->put)(d, &k, &v, replace ? 0 : R_NOOVERWRITE);
+ if(code < 0) {
+ code = errno;
+ krb5_set_error_message(context, code, "Database %s put error: %s",
+ db->hdb_name, strerror(code));
+ return code;
+ }
+ if(code == 1) {
+ return HDB_ERR_EXISTS;
+ }
+
+ return db->hdb_set_sync(context, db, db1->do_sync);
+}
+
+static krb5_error_code
+DB__del(krb5_context context, HDB *db, krb5_data key)
+{
+ DB1_HDB *db1 = (DB1_HDB *)db;
+ DB *d = (DB*)db->hdb_db;
+ DBT k;
+ krb5_error_code code;
+ k.data = key.data;
+ k.size = key.length;
+ krb5_clear_error_message(context);
+ code = (*d->del)(d, &k, 0);
+ if (code == 1)
+ return HDB_ERR_NOENTRY;
+ if (code < 0) {
+ code = errno;
+ krb5_set_error_message(context, code, "Database %s del error: %s",
+ db->hdb_name, strerror(code));
+ return code;
+ }
+ return db->hdb_set_sync(context, db, db1->do_sync);
+}
+
+static DB *
+_open_db(char *fn, int flags, int mode, int *fd)
+{
+#ifndef O_EXLOCK
+ int op;
+ int ret;
+
+ *fd = open(fn, flags, mode);
+ if (*fd == -1)
+ return NULL;
+
+ if ((flags & O_ACCMODE) == O_RDONLY)
+ op = LOCK_SH;
+ else
+ op = LOCK_EX;
+
+ ret = flock(*fd, op);
+ if (ret == -1) {
+ int saved_errno;
+
+ saved_errno = errno;
+ close(*fd);
+ errno = saved_errno;
+ return NULL;
+ }
+#else
+ if ((flags & O_ACCMODE) == O_RDONLY)
+ flags |= O_SHLOCK;
+ else
+ flags |= O_EXLOCK;
+#endif
+
+ return dbopen(fn, flags, mode, DB_BTREE, NULL);
+}
+
+static krb5_error_code
+DB_open(krb5_context context, HDB *db, int flags, mode_t mode)
+{
+ DB1_HDB *db1 = (DB1_HDB *)db;
+ char *fn;
+ krb5_error_code ret;
+
+ asprintf(&fn, "%s.db", db->hdb_name);
+ if (fn == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ db->hdb_db = _open_db(fn, flags, mode, &db1->lock_fd);
+ free(fn);
+ /* try to open without .db extension */
+ if(db->hdb_db == NULL && errno == ENOENT)
+ db->hdb_db = _open_db(db->hdb_name, flags, mode, &db1->lock_fd);
+ if(db->hdb_db == NULL) {
+ krb5_set_error_message(context, errno, "dbopen (%s): %s",
+ db->hdb_name, strerror(errno));
+ return errno;
+ }
+ if((flags & O_ACCMODE) == O_RDONLY)
+ ret = hdb_check_db_format(context, db);
+ else
+ ret = hdb_init_db(context, db);
+ if(ret == HDB_ERR_NOENTRY) {
+ krb5_clear_error_message(context);
+ return 0;
+ }
+ if (ret) {
+ DB_close(context, db);
+ krb5_set_error_message(context, ret, "hdb_open: failed %s database %s",
+ (flags & O_ACCMODE) == O_RDONLY ?
+ "checking format of" : "initialize",
+ db->hdb_name);
+ }
+ return ret;
+}
+
+krb5_error_code
+hdb_db1_create(krb5_context context, HDB **db,
+ const char *filename)
+{
+ DB1_HDB **db1 = (DB1_HDB **)db;
+ *db = calloc(1, sizeof(**db1)); /* Allocate space for the larger db1 */
+ if (*db == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ (*db)->hdb_db = NULL;
+ (*db)->hdb_name = strdup(filename);
+ if ((*db)->hdb_name == NULL) {
+ free(*db);
+ *db = NULL;
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ (*db)->hdb_master_key_set = 0;
+ (*db)->hdb_openp = 0;
+ (*db)->hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL;
+ (*db)->hdb_open = DB_open;
+ (*db)->hdb_close = DB_close;
+ (*db)->hdb_fetch_kvno = _hdb_fetch_kvno;
+ (*db)->hdb_store = _hdb_store;
+ (*db)->hdb_remove = _hdb_remove;
+ (*db)->hdb_firstkey = DB_firstkey;
+ (*db)->hdb_nextkey= DB_nextkey;
+ (*db)->hdb_lock = DB_lock;
+ (*db)->hdb_unlock = DB_unlock;
+ (*db)->hdb_rename = DB_rename;
+ (*db)->hdb__get = DB__get;
+ (*db)->hdb__put = DB__put;
+ (*db)->hdb__del = DB__del;
+ (*db)->hdb_destroy = DB_destroy;
+ (*db)->hdb_set_sync = DB_set_sync;
+
+ (*db1)->lock_fd = -1;
+ (*db1)->do_sync = 1;
+ return 0;
+}
+
+#endif /* defined(HAVE_DB1) */
diff --git a/third_party/heimdal/lib/hdb/db3.c b/third_party/heimdal/lib/hdb/db3.c
new file mode 100644
index 0000000..9d0c0a9
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/db3.c
@@ -0,0 +1,495 @@
+/*
+ * Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "hdb_locl.h"
+
+#include <fcntl.h>
+
+#if HAVE_DB3
+
+#ifdef HAVE_DBHEADER
+#include <db.h>
+#elif HAVE_DB6_DB_H
+#include <db6/db.h>
+#elif HAVE_DB5_DB_H
+#include <db5/db.h>
+#elif HAVE_DB4_DB_H
+#include <db4/db.h>
+#elif HAVE_DB3_DB_H
+#include <db3/db.h>
+#else
+#include <db.h>
+#endif
+
+typedef struct {
+ HDB hdb; /* generic members */
+ int lock_fd; /* DB3-specific */
+ int do_sync; /* DB3-specific */
+} DB3_HDB;
+
+
+static krb5_error_code
+DB_close(krb5_context context, HDB *db)
+{
+ DB3_HDB *db3 = (DB3_HDB *)db;
+ DB *d = (DB*)db->hdb_db;
+ DBC *dbcp = (DBC*)db->hdb_dbc;
+
+ heim_assert(d != 0, "Closing already closed HDB");
+
+ if (dbcp != NULL)
+ dbcp->c_close(dbcp);
+ if (d != NULL)
+ d->close(d, 0);
+ if (db3->lock_fd >= 0)
+ close(db3->lock_fd);
+
+ db3->lock_fd = -1;
+ db->hdb_dbc = 0;
+ db->hdb_db = 0;
+
+ return 0;
+}
+
+static krb5_error_code
+DB_destroy(krb5_context context, HDB *db)
+{
+ krb5_error_code ret;
+
+ ret = hdb_clear_master_key(context, db);
+ krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
+ free(db->hdb_name);
+ free(db);
+ return ret;
+}
+
+static krb5_error_code
+DB_set_sync(krb5_context context, HDB *db, int on)
+{
+ DB3_HDB *db3 = (DB3_HDB *)db;
+ DB *d = (DB*)db->hdb_db;
+ krb5_error_code ret = 0;
+
+ db3->do_sync = on;
+ if (on) {
+ ret = (*d->sync)(d, 0);
+ if (ret) {
+ if (ret == EACCES || ret == ENOSPC || ret == EINVAL) {
+ krb5_set_error_message(context, ret,
+ "Database %s put sync error: %s",
+ db->hdb_name, strerror(ret));
+ } else {
+ ret = HDB_ERR_UK_SERROR;
+ krb5_set_error_message(context, ret,
+ "Database %s put sync error: unknown (%d)",
+ db->hdb_name, ret);
+ }
+ }
+ }
+ return ret;
+}
+
+static krb5_error_code
+DB_lock(krb5_context context, HDB *db, int operation)
+{
+
+ return 0;
+}
+
+static krb5_error_code
+DB_unlock(krb5_context context, HDB *db)
+{
+
+ return 0;
+}
+
+
+static krb5_error_code
+DB_seq(krb5_context context, HDB *db,
+ unsigned flags, hdb_entry *entry, int flag)
+{
+ DBT key, value;
+ DBC *dbcp = db->hdb_dbc;
+ krb5_data key_data, data;
+ int code;
+
+ memset(&key, 0, sizeof(DBT));
+ memset(&value, 0, sizeof(DBT));
+ code = (*dbcp->c_get)(dbcp, &key, &value, flag);
+ if (code == DB_NOTFOUND)
+ return HDB_ERR_NOENTRY;
+ if (code)
+ return code;
+
+ key_data.data = key.data;
+ key_data.length = key.size;
+ data.data = value.data;
+ data.length = value.size;
+ memset(entry, 0, sizeof(*entry));
+ if (hdb_value2entry(context, &data, entry))
+ return DB_seq(context, db, flags, entry, DB_NEXT);
+ if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
+ code = hdb_unseal_keys (context, db, entry);
+ if (code)
+ hdb_free_entry (context, db, entry);
+ }
+ if (entry->principal == NULL) {
+ entry->principal = malloc(sizeof(*entry->principal));
+ if (entry->principal == NULL) {
+ hdb_free_entry (context, db, entry);
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ } else {
+ hdb_key2principal(context, &key_data, entry->principal);
+ }
+ }
+ return 0;
+}
+
+
+static krb5_error_code
+DB_firstkey(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
+{
+ return DB_seq(context, db, flags, entry, DB_FIRST);
+}
+
+
+static krb5_error_code
+DB_nextkey(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
+{
+ return DB_seq(context, db, flags, entry, DB_NEXT);
+}
+
+static krb5_error_code
+DB_rename(krb5_context context, HDB *db, const char *new_name)
+{
+ int ret;
+ char *old, *new;
+
+ if (strncmp(new_name, "db:", sizeof("db:") - 1) == 0)
+ new_name += sizeof("db:") - 1;
+ else if (strncmp(new_name, "db3:", sizeof("db3:") - 1) == 0)
+ new_name += sizeof("db3:") - 1;
+
+ ret = asprintf(&old, "%s.db", db->hdb_name);
+ if (ret == -1)
+ return ENOMEM;
+ ret = asprintf(&new, "%s.db", new_name);
+ if (ret == -1) {
+ free(old);
+ return ENOMEM;
+ }
+ ret = rename(old, new);
+ free(old);
+ if(ret) {
+ free(new);
+ return errno;
+ }
+
+ free(db->hdb_name);
+ new[strlen(new) - 3] = '\0';
+ db->hdb_name = new;
+ return 0;
+}
+
+static krb5_error_code
+DB__get(krb5_context context, HDB *db, krb5_data key, krb5_data *reply)
+{
+ DB *d = (DB*)db->hdb_db;
+ DBT k, v;
+ int code;
+
+ memset(&k, 0, sizeof(DBT));
+ memset(&v, 0, sizeof(DBT));
+ k.data = key.data;
+ k.size = key.length;
+ k.flags = 0;
+ code = (*d->get)(d, NULL, &k, &v, 0);
+ if(code == DB_NOTFOUND)
+ return HDB_ERR_NOENTRY;
+ if(code)
+ return code;
+
+ krb5_data_copy(reply, v.data, v.size);
+ return 0;
+}
+
+static krb5_error_code
+DB__put(krb5_context context, HDB *db, int replace,
+ krb5_data key, krb5_data value)
+{
+ DB3_HDB *db3 = (DB3_HDB *)db;
+ DB *d = (DB*)db->hdb_db;
+ DBT k, v;
+ int code;
+
+ memset(&k, 0, sizeof(DBT));
+ memset(&v, 0, sizeof(DBT));
+ k.data = key.data;
+ k.size = key.length;
+ k.flags = 0;
+ v.data = value.data;
+ v.size = value.length;
+ v.flags = 0;
+ code = (*d->put)(d, NULL, &k, &v, replace ? 0 : DB_NOOVERWRITE);
+ if(code == DB_KEYEXIST)
+ return HDB_ERR_EXISTS;
+ if (code) {
+ /*
+ * Berkeley DB 3 and up have a terrible error reporting
+ * interface...
+ *
+ * DB->err() doesn't output a string.
+ * DB->set_errcall()'s callback function doesn't have a void *
+ * argument that can be used to place the error somewhere.
+ *
+ * The only thing we could do is fopen()/fdopen() a file, set it
+ * with DB->set_errfile(), then call DB->err(), then read the
+ * message from the file, unset it with DB->set_errfile(), close
+ * it and delete it. That's a lot of work... so we don't do it.
+ */
+ if (code == EACCES || code == ENOSPC || code == EINVAL) {
+ krb5_set_error_message(context, code,
+ "Database %s put error: %s",
+ db->hdb_name, strerror(code));
+ } else {
+ code = HDB_ERR_UK_SERROR;
+ krb5_set_error_message(context, code,
+ "Database %s put error: unknown (%d)",
+ db->hdb_name, code);
+ }
+ return code;
+ }
+ return db->hdb_set_sync(context, db, db3->do_sync);
+}
+
+static krb5_error_code
+DB__del(krb5_context context, HDB *db, krb5_data key)
+{
+ DB3_HDB *db3 = (DB3_HDB *)db;
+ DB *d = (DB*)db->hdb_db;
+ DBT k;
+ krb5_error_code code;
+ memset(&k, 0, sizeof(DBT));
+ k.data = key.data;
+ k.size = key.length;
+ k.flags = 0;
+ code = (*d->del)(d, NULL, &k, 0);
+ if(code == DB_NOTFOUND)
+ return HDB_ERR_NOENTRY;
+ if (code) {
+ if (code == EACCES || code == ENOSPC || code == EINVAL) {
+ krb5_set_error_message(context, code,
+ "Database %s del error: %s",
+ db->hdb_name, strerror(code));
+ } else {
+ code = HDB_ERR_UK_SERROR;
+ krb5_set_error_message(context, code,
+ "Database %s del error: unknown (%d)",
+ db->hdb_name, code);
+ }
+ return code;
+ }
+ return db->hdb_set_sync(context, db, db3->do_sync);
+}
+
+#define RD_CACHE_SZ 0x8000 /* Minimal read cache size */
+#define WR_CACHE_SZ 0x8000 /* Minimal write cache size */
+
+static int
+_open_db(DB *d, char *fn, int myflags, int flags, mode_t mode, int *fd)
+{
+ int ret;
+ int cache_size = (myflags & DB_RDONLY) ? RD_CACHE_SZ : WR_CACHE_SZ;
+
+ *fd = open(fn, flags, mode);
+
+ if (*fd == -1)
+ return errno;
+
+ /*
+ * Without DB_FCNTL_LOCKING, the DB library complains when initializing
+ * a database in an empty file. Since the database is our lock file,
+ * we create it before Berkeley DB does, so a new DB always starts empty.
+ */
+ myflags |= DB_FCNTL_LOCKING;
+
+ ret = flock(*fd, (myflags&DB_RDONLY) ? LOCK_SH : LOCK_EX);
+ if (ret == -1) {
+ ret = errno;
+ close(*fd);
+ *fd = -1;
+ return ret;
+ }
+
+ d->set_cachesize(d, 0, cache_size, 0);
+
+#if (DB_VERSION_MAJOR > 4) || ((DB_VERSION_MAJOR == 4) && (DB_VERSION_MINOR >= 1))
+ ret = (*d->open)(d, NULL, fn, NULL, DB_BTREE, myflags, mode);
+#else
+ ret = (*d->open)(d, fn, NULL, DB_BTREE, myflags, mode);
+#endif
+
+ if (ret != 0) {
+ close(*fd);
+ *fd = -1;
+ }
+
+ return ret;
+}
+
+static krb5_error_code
+DB_open(krb5_context context, HDB *db, int flags, mode_t mode)
+{
+ DB3_HDB *db3 = (DB3_HDB *)db;
+ DBC *dbc = NULL;
+ char *fn;
+ krb5_error_code ret;
+ DB *d;
+ int myflags = 0;
+ int aret;
+
+ heim_assert(db->hdb_db == 0, "Opening already open HDB");
+
+ if (flags & O_CREAT)
+ myflags |= DB_CREATE;
+
+ if (flags & O_EXCL)
+ myflags |= DB_EXCL;
+
+ if((flags & O_ACCMODE) == O_RDONLY)
+ myflags |= DB_RDONLY;
+
+ if (flags & O_TRUNC)
+ myflags |= DB_TRUNCATE;
+
+ aret = asprintf(&fn, "%s.db", db->hdb_name);
+ if (aret == -1) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ if (db_create(&d, NULL, 0) != 0) {
+ free(fn);
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ db->hdb_db = d;
+
+ /* From here on out always DB_close() before returning on error */
+
+ ret = _open_db(d, fn, myflags, flags, mode, &db3->lock_fd);
+ free(fn);
+ if (ret == ENOENT) {
+ /* try to open without .db extension */
+ ret = _open_db(d, db->hdb_name, myflags, flags, mode, &db3->lock_fd);
+ }
+
+ if (ret) {
+ DB_close(context, db);
+ krb5_set_error_message(context, ret, "opening %s: %s",
+ db->hdb_name, strerror(ret));
+ return ret;
+ }
+
+#ifndef DB_CURSOR_BULK
+# define DB_CURSOR_BULK 0 /* Missing with DB < 4.8 */
+#endif
+ ret = (*d->cursor)(d, NULL, &dbc, DB_CURSOR_BULK);
+
+ if (ret) {
+ DB_close(context, db);
+ krb5_set_error_message(context, ret, "d->cursor: %s", strerror(ret));
+ return ret;
+ }
+ db->hdb_dbc = dbc;
+
+ if((flags & O_ACCMODE) == O_RDONLY)
+ ret = hdb_check_db_format(context, db);
+ else
+ ret = hdb_init_db(context, db);
+ if(ret == HDB_ERR_NOENTRY)
+ return 0;
+ if (ret) {
+ DB_close(context, db);
+ krb5_set_error_message(context, ret, "hdb_open: failed %s database %s",
+ (flags & O_ACCMODE) == O_RDONLY ?
+ "checking format of" : "initialize",
+ db->hdb_name);
+ }
+
+ return ret;
+}
+
+krb5_error_code
+hdb_db3_create(krb5_context context, HDB **db,
+ const char *filename)
+{
+ DB3_HDB **db3 = (DB3_HDB **)db;
+ *db3 = calloc(1, sizeof(**db3)); /* Allocate space for the larger db3 */
+ if (*db == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ (*db)->hdb_db = NULL;
+ (*db)->hdb_name = strdup(filename);
+ if ((*db)->hdb_name == NULL) {
+ free(*db);
+ *db = NULL;
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ (*db)->hdb_master_key_set = 0;
+ (*db)->hdb_openp = 0;
+ (*db)->hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL;
+ (*db)->hdb_open = DB_open;
+ (*db)->hdb_close = DB_close;
+ (*db)->hdb_fetch_kvno = _hdb_fetch_kvno;
+ (*db)->hdb_store = _hdb_store;
+ (*db)->hdb_remove = _hdb_remove;
+ (*db)->hdb_firstkey = DB_firstkey;
+ (*db)->hdb_nextkey= DB_nextkey;
+ (*db)->hdb_lock = DB_lock;
+ (*db)->hdb_unlock = DB_unlock;
+ (*db)->hdb_rename = DB_rename;
+ (*db)->hdb__get = DB__get;
+ (*db)->hdb__put = DB__put;
+ (*db)->hdb__del = DB__del;
+ (*db)->hdb_destroy = DB_destroy;
+ (*db)->hdb_set_sync = DB_set_sync;
+
+ (*db3)->lock_fd = -1;
+ return 0;
+}
+#endif /* HAVE_DB3 */
diff --git a/third_party/heimdal/lib/hdb/dbinfo.c b/third_party/heimdal/lib/hdb/dbinfo.c
new file mode 100644
index 0000000..60b11f8
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/dbinfo.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "hdb_locl.h"
+
+struct hdb_dbinfo {
+ char *label;
+ char *realm;
+ char *dbname;
+ char *mkey_file;
+ char *acl_file;
+ char *log_file;
+ const krb5_config_binding *binding;
+ struct hdb_dbinfo *next;
+};
+
+static int
+get_dbinfo(krb5_context context,
+ const krb5_config_binding *db_binding,
+ const char *label,
+ struct hdb_dbinfo **db)
+{
+ struct hdb_dbinfo *di;
+ const char *p;
+
+ *db = NULL;
+
+ p = krb5_config_get_string(context, db_binding, "dbname", NULL);
+ if(p == NULL)
+ return 0;
+
+ di = calloc(1, sizeof(*di));
+ if (di == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ di->label = strdup(label);
+ di->dbname = strdup(p);
+
+ p = krb5_config_get_string(context, db_binding, "realm", NULL);
+ if(p)
+ di->realm = strdup(p);
+ p = krb5_config_get_string(context, db_binding, "mkey_file", NULL);
+ if(p)
+ di->mkey_file = strdup(p);
+ p = krb5_config_get_string(context, db_binding, "acl_file", NULL);
+ if(p)
+ di->acl_file = strdup(p);
+ p = krb5_config_get_string(context, db_binding, "log_file", NULL);
+ if(p)
+ di->log_file = strdup(p);
+
+ di->binding = db_binding;
+
+ *db = di;
+ return 0;
+}
+
+
+int
+hdb_get_dbinfo(krb5_context context, struct hdb_dbinfo **dbp)
+{
+ const krb5_config_binding *db_binding;
+ struct hdb_dbinfo *di, **dt, *databases;
+ const char *default_dbname = HDB_DEFAULT_DB;
+ const char *default_mkey = HDB_DB_DIR "/m-key";
+ const char *default_acl = HDB_DB_DIR "/kadmind.acl";
+ const char *p;
+ int ret;
+
+ *dbp = NULL;
+ dt = NULL;
+ databases = NULL;
+
+ db_binding = krb5_config_get_list(context, NULL,
+ "kdc",
+ "database",
+ NULL);
+ if (db_binding) {
+
+ ret = get_dbinfo(context, db_binding, "default", &databases);
+ if (ret == 0 && databases != NULL)
+ dt = &databases->next;
+
+ for ( ; db_binding != NULL; db_binding = db_binding->next) {
+
+ if (db_binding->type != krb5_config_list)
+ continue;
+
+ ret = get_dbinfo(context, db_binding->u.list,
+ db_binding->name, &di);
+ if (ret)
+ krb5_err(context, 1, ret, "failed getting realm");
+
+ if (di == NULL)
+ continue;
+
+ if (dt)
+ *dt = di;
+ else {
+ hdb_free_dbinfo(context, &databases);
+ databases = di;
+ }
+ dt = &di->next;
+
+ }
+ }
+
+ if (databases == NULL) {
+ /* if there are none specified, create one and use defaults */
+ databases = calloc(1, sizeof(*databases));
+ databases->label = strdup("default");
+ }
+
+ for (di = databases; di; di = di->next) {
+ if (di->dbname == NULL) {
+ di->dbname = strdup(default_dbname);
+ if (di->mkey_file == NULL)
+ di->mkey_file = strdup(default_mkey);
+ }
+ if (di->mkey_file == NULL) {
+ p = strrchr(di->dbname, '.');
+ if(p == NULL || strchr(p, '/') != NULL)
+ /* final pathname component does not contain a . */
+ ret = asprintf(&di->mkey_file, "%s.mkey", di->dbname);
+ else
+ /* the filename is something.else, replace .else with
+ .mkey */
+ ret = asprintf(&di->mkey_file, "%.*s.mkey",
+ (int)(p - di->dbname), di->dbname);
+ if (ret == -1) {
+ hdb_free_dbinfo(context, &databases);
+ return ENOMEM;
+ }
+ }
+ if(di->acl_file == NULL)
+ di->acl_file = strdup(default_acl);
+ }
+ *dbp = databases;
+ return 0;
+}
+
+
+struct hdb_dbinfo *
+hdb_dbinfo_get_next(struct hdb_dbinfo *dbp, struct hdb_dbinfo *dbprevp)
+{
+ if (dbprevp == NULL)
+ return dbp;
+ else
+ return dbprevp->next;
+}
+
+const char *
+hdb_dbinfo_get_label(krb5_context context, struct hdb_dbinfo *dbp)
+{
+ return dbp->label;
+}
+
+const char *
+hdb_dbinfo_get_realm(krb5_context context, struct hdb_dbinfo *dbp)
+{
+ return dbp->realm;
+}
+
+const char *
+hdb_dbinfo_get_dbname(krb5_context context, struct hdb_dbinfo *dbp)
+{
+ return dbp->dbname;
+}
+
+const char *
+hdb_dbinfo_get_mkey_file(krb5_context context, struct hdb_dbinfo *dbp)
+{
+ return dbp->mkey_file;
+}
+
+const char *
+hdb_dbinfo_get_acl_file(krb5_context context, struct hdb_dbinfo *dbp)
+{
+ return dbp->acl_file;
+}
+
+const char *
+hdb_dbinfo_get_log_file(krb5_context context, struct hdb_dbinfo *dbp)
+{
+ return dbp->log_file;
+}
+
+const krb5_config_binding *
+hdb_dbinfo_get_binding(krb5_context context, struct hdb_dbinfo *dbp)
+{
+ return dbp->binding;
+}
+
+void
+hdb_free_dbinfo(krb5_context context, struct hdb_dbinfo **dbp)
+{
+ struct hdb_dbinfo *di, *ndi;
+
+ for(di = *dbp; di != NULL; di = ndi) {
+ ndi = di->next;
+ free (di->label);
+ free (di->realm);
+ free (di->dbname);
+ free (di->mkey_file);
+ free (di->acl_file);
+ free (di->log_file);
+ free(di);
+ }
+ *dbp = NULL;
+}
+
+/**
+ * Return the directory where the hdb database resides.
+ *
+ * @param context Kerberos 5 context.
+ *
+ * @return string pointing to directory.
+ */
+
+const char *
+hdb_db_dir(krb5_context context)
+{
+ const char *p;
+
+ p = krb5_config_get_string(context, NULL, "hdb", "db-dir", NULL);
+ if (p)
+ return p;
+
+ return HDB_DB_DIR;
+}
+
+/**
+ * Return the default hdb database resides.
+ *
+ * @param context Kerberos 5 context.
+ *
+ * @return string pointing to directory.
+ */
+
+const char *
+hdb_default_db(krb5_context context)
+{
+ static char *default_hdb = NULL;
+ struct hdb_dbinfo *dbinfo = NULL;
+ struct hdb_dbinfo *d = NULL;
+ const char *s;
+
+ if (default_hdb)
+ return default_hdb;
+
+ (void) hdb_get_dbinfo(context, &dbinfo);
+ while ((d = hdb_dbinfo_get_next(dbinfo, d)) != NULL) {
+ if ((s = hdb_dbinfo_get_dbname(context, d)) &&
+ (default_hdb = strdup(s)))
+ break;
+ }
+
+ hdb_free_dbinfo(context, &dbinfo);
+ return default_hdb ? default_hdb : HDB_DEFAULT_DB;
+}
diff --git a/third_party/heimdal/lib/hdb/ext.c b/third_party/heimdal/lib/hdb/ext.c
new file mode 100644
index 0000000..48683ef
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/ext.c
@@ -0,0 +1,786 @@
+/*
+ * Copyright (c) 2004 - 2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "hdb_locl.h"
+#include <der.h>
+
+krb5_error_code
+hdb_entry_check_mandatory(krb5_context context, const hdb_entry *ent)
+{
+ size_t i;
+
+ if (ent->extensions == NULL)
+ return 0;
+
+ /*
+ * check for unknown extensions and if they where tagged mandatory
+ */
+
+ for (i = 0; i < ent->extensions->len; i++) {
+ if (ent->extensions->val[i].data.element !=
+ choice_HDB_extension_data_asn1_ellipsis)
+ continue;
+ if (ent->extensions->val[i].mandatory) {
+ krb5_set_error_message(context, HDB_ERR_MANDATORY_OPTION,
+ "Principal have unknown "
+ "mandatory extension");
+ return HDB_ERR_MANDATORY_OPTION;
+ }
+ }
+ return 0;
+}
+
+HDB_extension *
+hdb_find_extension(const hdb_entry *entry, int type)
+{
+ size_t i;
+
+ if (entry->extensions == NULL)
+ return NULL;
+
+ for (i = 0; i < entry->extensions->len; i++)
+ if (entry->extensions->val[i].data.element == (unsigned)type)
+ return &entry->extensions->val[i];
+ return NULL;
+}
+
+/*
+ * Replace the extension `ext' in `entry'. Make a copy of the
+ * extension, so the caller must still free `ext' on both success and
+ * failure. Returns 0 or error code.
+ */
+
+krb5_error_code
+hdb_replace_extension(krb5_context context,
+ hdb_entry *entry,
+ const HDB_extension *ext)
+{
+ HDB_extension *ext2;
+ int ret;
+
+ ext2 = NULL;
+
+ if (entry->extensions == NULL) {
+ entry->extensions = calloc(1, sizeof(*entry->extensions));
+ if (entry->extensions == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ } else if (ext->data.element != choice_HDB_extension_data_asn1_ellipsis) {
+ ext2 = hdb_find_extension(entry, ext->data.element);
+ } else {
+ /*
+ * This is an unknown extension, and we are asked to replace a
+ * possible entry in `entry' that is of the same type. This
+ * might seem impossible, but ASN.1 CHOICE comes to our
+ * rescue. The first tag in each branch in the CHOICE is
+ * unique, so just find the element in the list that have the
+ * same tag was we are putting into the list.
+ */
+ Der_class replace_class, list_class;
+ Der_type replace_type, list_type;
+ unsigned int replace_tag, list_tag;
+ size_t size;
+ size_t i;
+
+ ret = der_get_tag(ext->data.u.asn1_ellipsis.data,
+ ext->data.u.asn1_ellipsis.length,
+ &replace_class, &replace_type, &replace_tag,
+ &size);
+ if (ret) {
+ krb5_set_error_message(context, ret, "hdb: failed to decode "
+ "replacement hdb extension");
+ return ret;
+ }
+
+ for (i = 0; i < entry->extensions->len; i++) {
+ HDB_extension *ext3 = &entry->extensions->val[i];
+
+ if (ext3->data.element != choice_HDB_extension_data_asn1_ellipsis)
+ continue;
+
+ ret = der_get_tag(ext3->data.u.asn1_ellipsis.data,
+ ext3->data.u.asn1_ellipsis.length,
+ &list_class, &list_type, &list_tag,
+ &size);
+ if (ret) {
+ krb5_set_error_message(context, ret, "hdb: failed to decode "
+ "present hdb extension");
+ return ret;
+ }
+
+ if (MAKE_TAG(replace_class,replace_type,replace_type) ==
+ MAKE_TAG(list_class,list_type,list_type)) {
+ ext2 = ext3;
+ break;
+ }
+ }
+ }
+
+ if (ext2) {
+ free_HDB_extension(ext2);
+ ret = copy_HDB_extension(ext, ext2);
+ if (ret)
+ krb5_set_error_message(context, ret, "hdb: failed to copy replacement "
+ "hdb extension");
+ return ret;
+ }
+
+ return add_HDB_extensions(entry->extensions, ext);
+}
+
+krb5_error_code
+hdb_clear_extension(krb5_context context,
+ hdb_entry *entry,
+ int type)
+{
+ size_t i;
+
+ if (entry->extensions == NULL)
+ return 0;
+
+ for (i = 0; i < entry->extensions->len; ) {
+ if (entry->extensions->val[i].data.element == (unsigned)type)
+ (void) remove_HDB_extensions(entry->extensions, i);
+ else
+ i++;
+ }
+ if (entry->extensions->len == 0) {
+ free(entry->extensions->val);
+ free(entry->extensions);
+ entry->extensions = NULL;
+ }
+
+ return 0;
+}
+
+
+krb5_error_code
+hdb_entry_get_pkinit_acl(const hdb_entry *entry, const HDB_Ext_PKINIT_acl **a)
+{
+ const HDB_extension *ext;
+
+ ext = hdb_find_extension(entry, choice_HDB_extension_data_pkinit_acl);
+ if (ext)
+ *a = &ext->data.u.pkinit_acl;
+ else
+ *a = NULL;
+
+ return 0;
+}
+
+krb5_error_code
+hdb_entry_get_pkinit_hash(const hdb_entry *entry, const HDB_Ext_PKINIT_hash **a)
+{
+ const HDB_extension *ext;
+
+ ext = hdb_find_extension(entry, choice_HDB_extension_data_pkinit_cert_hash);
+ if (ext)
+ *a = &ext->data.u.pkinit_cert_hash;
+ else
+ *a = NULL;
+
+ return 0;
+}
+
+krb5_error_code
+hdb_entry_get_pkinit_cert(const hdb_entry *entry, const HDB_Ext_PKINIT_cert **a)
+{
+ const HDB_extension *ext;
+
+ ext = hdb_find_extension(entry, choice_HDB_extension_data_pkinit_cert);
+ if (ext)
+ *a = &ext->data.u.pkinit_cert;
+ else
+ *a = NULL;
+
+ return 0;
+}
+
+krb5_error_code
+hdb_entry_get_krb5_config(const hdb_entry *entry, heim_octet_string *c)
+{
+ const HDB_extension *ext;
+
+ c->data = NULL;
+ c->length = 0;
+ ext = hdb_find_extension(entry, choice_HDB_extension_data_krb5_config);
+ if (ext)
+ *c = ext->data.u.krb5_config;
+ return 0;
+}
+
+krb5_error_code
+hdb_entry_set_krb5_config(krb5_context context,
+ hdb_entry *entry,
+ heim_octet_string *s)
+{
+ HDB_extension ext;
+
+ ext.mandatory = FALSE;
+ ext.data.element = choice_HDB_extension_data_last_pw_change;
+ /* hdb_replace_extension() copies this, so no need to copy it here */
+ ext.data.u.krb5_config = *s;
+ return hdb_replace_extension(context, entry, &ext);
+}
+
+krb5_error_code
+hdb_entry_get_pw_change_time(const hdb_entry *entry, time_t *t)
+{
+ const HDB_extension *ext;
+
+ ext = hdb_find_extension(entry, choice_HDB_extension_data_last_pw_change);
+ if (ext)
+ *t = ext->data.u.last_pw_change;
+ else
+ *t = 0;
+
+ return 0;
+}
+
+krb5_error_code
+hdb_entry_set_pw_change_time(krb5_context context,
+ hdb_entry *entry,
+ time_t t)
+{
+ HDB_extension ext;
+
+ ext.mandatory = FALSE;
+ ext.data.element = choice_HDB_extension_data_last_pw_change;
+ if (t == 0)
+ t = time(NULL);
+ ext.data.u.last_pw_change = t;
+
+ return hdb_replace_extension(context, entry, &ext);
+}
+
+int
+hdb_entry_get_password(krb5_context context, HDB *db,
+ const hdb_entry *entry, char **p)
+{
+ HDB_extension *ext;
+ char *str;
+ int ret;
+
+ ext = hdb_find_extension(entry, choice_HDB_extension_data_password);
+ if (ext) {
+ heim_utf8_string xstr;
+ heim_octet_string pw;
+
+ if (db->hdb_master_key_set && ext->data.u.password.mkvno) {
+ hdb_master_key key;
+
+ key = _hdb_find_master_key(ext->data.u.password.mkvno,
+ db->hdb_master_key);
+
+ if (key == NULL) {
+ krb5_set_error_message(context, HDB_ERR_NO_MKEY,
+ "master key %d missing",
+ *ext->data.u.password.mkvno);
+ return HDB_ERR_NO_MKEY;
+ }
+
+ ret = _hdb_mkey_decrypt(context, key, HDB_KU_MKEY,
+ ext->data.u.password.password.data,
+ ext->data.u.password.password.length,
+ &pw);
+ } else {
+ ret = der_copy_octet_string(&ext->data.u.password.password, &pw);
+ }
+ if (ret) {
+ krb5_clear_error_message(context);
+ return ret;
+ }
+
+ xstr = pw.data;
+ if (xstr[pw.length - 1] != '\0') {
+ krb5_set_error_message(context, EINVAL, "malformed password");
+ return EINVAL;
+ }
+
+ *p = strdup(xstr);
+
+ der_free_octet_string(&pw);
+ if (*p == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ return 0;
+ }
+
+ ret = krb5_unparse_name(context, entry->principal, &str);
+ if (ret == 0) {
+ krb5_set_error_message(context, ENOENT,
+ "no password attribute for %s", str);
+ free(str);
+ } else
+ krb5_clear_error_message(context);
+
+ return ENOENT;
+}
+
+int
+hdb_entry_set_password(krb5_context context, HDB *db,
+ hdb_entry *entry, const char *p)
+{
+ HDB_extension ext;
+ hdb_master_key key;
+ int ret;
+
+ ext.mandatory = FALSE;
+ ext.data.element = choice_HDB_extension_data_password;
+
+ if (db->hdb_master_key_set) {
+
+ key = _hdb_find_master_key(NULL, db->hdb_master_key);
+ if (key == NULL) {
+ krb5_set_error_message(context, HDB_ERR_NO_MKEY,
+ "hdb_entry_set_password: "
+ "failed to find masterkey");
+ return HDB_ERR_NO_MKEY;
+ }
+
+ ret = _hdb_mkey_encrypt(context, key, HDB_KU_MKEY,
+ p, strlen(p) + 1,
+ &ext.data.u.password.password);
+ if (ret)
+ return ret;
+
+ ext.data.u.password.mkvno =
+ malloc(sizeof(*ext.data.u.password.mkvno));
+ if (ext.data.u.password.mkvno == NULL) {
+ free_HDB_extension(&ext);
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ *ext.data.u.password.mkvno = _hdb_mkey_version(key);
+
+ } else {
+ ext.data.u.password.mkvno = NULL;
+
+ ret = krb5_data_copy(&ext.data.u.password.password,
+ p, strlen(p) + 1);
+ if (ret) {
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ free_HDB_extension(&ext);
+ return ret;
+ }
+ }
+
+ ret = hdb_replace_extension(context, entry, &ext);
+
+ free_HDB_extension(&ext);
+
+ return ret;
+}
+
+int
+hdb_entry_clear_password(krb5_context context, hdb_entry *entry)
+{
+ return hdb_clear_extension(context, entry,
+ choice_HDB_extension_data_password);
+}
+
+krb5_error_code
+hdb_entry_get_ConstrainedDelegACL(const hdb_entry *entry,
+ const HDB_Ext_Constrained_delegation_acl **a)
+{
+ const HDB_extension *ext;
+
+ ext = hdb_find_extension(entry,
+ choice_HDB_extension_data_allowed_to_delegate_to);
+ if (ext)
+ *a = &ext->data.u.allowed_to_delegate_to;
+ else
+ *a = NULL;
+
+ return 0;
+}
+
+krb5_error_code
+hdb_entry_get_aliases(const hdb_entry *entry, const HDB_Ext_Aliases **a)
+{
+ const HDB_extension *ext;
+
+ ext = hdb_find_extension(entry, choice_HDB_extension_data_aliases);
+ if (ext)
+ *a = &ext->data.u.aliases;
+ else
+ *a = NULL;
+
+ return 0;
+}
+
+unsigned int
+hdb_entry_get_kvno_diff_clnt(const hdb_entry *entry)
+{
+ const HDB_extension *ext;
+
+ ext = hdb_find_extension(entry,
+ choice_HDB_extension_data_hist_kvno_diff_clnt);
+ if (ext)
+ return ext->data.u.hist_kvno_diff_clnt;
+ return 1;
+}
+
+krb5_error_code
+hdb_entry_set_kvno_diff_clnt(krb5_context context, hdb_entry *entry,
+ unsigned int diff)
+{
+ HDB_extension ext;
+
+ if (diff > 16384)
+ return EINVAL;
+ ext.mandatory = FALSE;
+ ext.data.element = choice_HDB_extension_data_hist_kvno_diff_clnt;
+ ext.data.u.hist_kvno_diff_clnt = diff;
+ return hdb_replace_extension(context, entry, &ext);
+}
+
+krb5_error_code
+hdb_entry_clear_kvno_diff_clnt(krb5_context context, hdb_entry *entry)
+{
+ return hdb_clear_extension(context, entry,
+ choice_HDB_extension_data_hist_kvno_diff_clnt);
+}
+
+unsigned int
+hdb_entry_get_kvno_diff_svc(const hdb_entry *entry)
+{
+ const HDB_extension *ext;
+
+ ext = hdb_find_extension(entry,
+ choice_HDB_extension_data_hist_kvno_diff_svc);
+ if (ext)
+ return ext->data.u.hist_kvno_diff_svc;
+ return 1024; /* max_life effectively provides a better default */
+}
+
+krb5_error_code
+hdb_entry_set_kvno_diff_svc(krb5_context context, hdb_entry *entry,
+ unsigned int diff)
+{
+ HDB_extension ext;
+
+ if (diff > 16384)
+ return EINVAL;
+ ext.mandatory = FALSE;
+ ext.data.element = choice_HDB_extension_data_hist_kvno_diff_svc;
+ ext.data.u.hist_kvno_diff_svc = diff;
+ return hdb_replace_extension(context, entry, &ext);
+}
+
+krb5_error_code
+hdb_entry_clear_kvno_diff_svc(krb5_context context, hdb_entry *entry)
+{
+ return hdb_clear_extension(context, entry,
+ choice_HDB_extension_data_hist_kvno_diff_svc);
+}
+
+krb5_error_code
+hdb_set_last_modified_by(krb5_context context, hdb_entry *entry,
+ krb5_principal modby, time_t modtime)
+{
+ krb5_error_code ret;
+ Event *old_ev;
+ Event *ev;
+
+ old_ev = entry->modified_by;
+
+ ev = calloc(1, sizeof (*ev));
+ if (!ev)
+ return ENOMEM;
+ if (modby)
+ ret = krb5_copy_principal(context, modby, &ev->principal);
+ else
+ ret = krb5_parse_name(context, "root/admin", &ev->principal);
+ if (ret) {
+ free(ev);
+ return ret;
+ }
+ ev->time = modtime;
+ if (!ev->time)
+ time(&ev->time);
+
+ entry->modified_by = ev;
+ if (old_ev)
+ free_Event(old_ev);
+ return 0;
+}
+
+krb5_error_code
+hdb_entry_get_key_rotation(krb5_context context,
+ const hdb_entry *entry,
+ const HDB_Ext_KeyRotation **kr)
+{
+ HDB_extension *ext =
+ hdb_find_extension(entry, choice_HDB_extension_data_key_rotation);
+
+ *kr = ext ? &ext->data.u.key_rotation : NULL;
+ return 0;
+}
+
+krb5_error_code
+hdb_validate_key_rotation(krb5_context context,
+ const KeyRotation *past_kr,
+ const KeyRotation *new_kr)
+{
+ unsigned int last_kvno;
+
+ if (new_kr->period < 1) {
+ krb5_set_error_message(context, EINVAL,
+ "Key rotation periods must be non-zero "
+ "and positive");
+ return EINVAL;
+ }
+ if (new_kr->base_key_kvno < 1 || new_kr->base_kvno < 1) {
+ krb5_set_error_message(context, EINVAL,
+ "Key version number zero not allowed "
+ "for key rotation");
+ return EINVAL;
+ }
+ if (!past_kr)
+ return 0;
+
+ if (past_kr->base_key_kvno == new_kr->base_key_kvno) {
+ /*
+ * The new base keys can be the same as the old, but must have
+ * different kvnos. (Well, not must must. It's a convention for now.)
+ */
+ krb5_set_error_message(context, EINVAL,
+ "Base key version numbers for KRs must differ");
+ return EINVAL;
+ }
+ if (new_kr->epoch - past_kr->epoch <= 0) {
+ krb5_set_error_message(context, EINVAL,
+ "New key rotation periods must start later "
+ "than existing ones");
+ return EINVAL;
+ }
+
+ last_kvno = 1 + ((new_kr->epoch - past_kr->epoch) / past_kr->period);
+ if (new_kr->base_kvno <= last_kvno) {
+ krb5_set_error_message(context, EINVAL,
+ "New key rotation base kvno must be larger "
+ "the last kvno for the current key "
+ "rotation (%u)", last_kvno);
+ return EINVAL;
+ }
+ return 0;
+}
+
+static int
+kr_eq(const KeyRotation *a, const KeyRotation *b)
+{
+ return !!(
+ a->epoch == b->epoch &&
+ a->period == b->period &&
+ a->base_kvno == b->base_kvno &&
+ a->base_key_kvno == b->base_key_kvno &&
+ KeyRotationFlags2int(a->flags) == KeyRotationFlags2int(b->flags)
+ );
+}
+
+krb5_error_code
+hdb_validate_key_rotations(krb5_context context,
+ const HDB_Ext_KeyRotation *existing,
+ const HDB_Ext_KeyRotation *krs)
+{
+ krb5_error_code ret = 0;
+ size_t added = 0;
+ size_t i;
+
+ if ((!existing || !existing->len) && (!krs || !krs->len))
+ return 0; /* Nothing to do; weird */
+
+ /*
+ * HDB_Ext_KeyRotation has to have 1..3 elements, and this is enforced by
+ * the ASN.1 compiler and the code it generates. Nonetheless we'll check
+ * that there's not zero elements.
+ */
+ if ((!krs || !krs->len)) {
+ /*
+ * NOTE: We can clear this on concrete principals with virtual keys
+ * though. The caller can check for that case.
+ */
+ krb5_set_error_message(context, EINVAL,
+ "Cannot clear key rotation metadata on "
+ "virtual principal namespaces");
+ ret = EINVAL;
+ }
+
+ /* Validate the new KRs by themselves */
+ for (i = 0; ret == 0 && i < krs->len; i++) {
+ ret = hdb_validate_key_rotation(context,
+ i+1 < krs->len ? &krs->val[i+1] : 0,
+ &krs->val[i]);
+ }
+ if (ret || !existing || !existing->len)
+ return ret;
+
+ if (existing->len == krs->len) {
+ /* Check for no change */
+ for (i = 0; i < krs->len; i++)
+ if (!kr_eq(&existing->val[i], &krs->val[i]))
+ break;
+ if (i == krs->len)
+ return 0; /* No change */
+ }
+
+ /*
+ * Check that new KRs make sense in the context of the previous KRs.
+ *
+ * Permitted changes:
+ *
+ * - add one new KR in front
+ * - drop old KRs
+ *
+ * Start by checking if we're adding a KR, then go on to check for dropped
+ * KRs and/or last KR alteration.
+ */
+ if (existing->val[0].epoch == krs->val[0].epoch ||
+ existing->val[0].base_kvno == krs->val[0].base_kvno) {
+ if (!kr_eq(&existing->val[0], &krs->val[0])) {
+ krb5_set_error_message(context, EINVAL,
+ "Key rotation change not sensible");
+ ret = EINVAL;
+ }
+ /* Key rotation *not* added */
+ } else {
+ /* Key rotation added; check it first */
+ ret = hdb_validate_key_rotation(context,
+ &existing->val[0],
+ &krs->val[0]);
+ added = 1;
+ }
+ for (i = 0; ret == 0 && i < existing->len && i + added < krs->len; i++)
+ if (!kr_eq(&existing->val[i], &krs->val[i + added]))
+ krb5_set_error_message(context, ret = EINVAL,
+ "Only last key rotation may be truncated");
+ return ret;
+}
+
+/* XXX We need a function to "revoke" the past */
+
+/**
+ * This function adds a KeyRotation value to an entry, validating the
+ * change. One of `entry' and `krs' must be NULL, and the other non-NULL, and
+ * whichever is given will be altered.
+ *
+ * @param context Context
+ * @param entry An HDB entry
+ * @param krs A key rotation extension for hdb_entry
+ * @param kr A new KeyRotation value
+ *
+ * @return Zero on success, an error otherwise.
+ */
+krb5_error_code
+hdb_entry_add_key_rotation(krb5_context context,
+ hdb_entry *entry,
+ HDB_Ext_KeyRotation *krs,
+ const KeyRotation *kr)
+{
+ krb5_error_code ret;
+ HDB_extension new_ext;
+ HDB_extension *ext = &new_ext;
+ KeyRotation tmp;
+ size_t i, sz;
+
+ if (kr->period < 1) {
+ krb5_set_error_message(context, EINVAL,
+ "Key rotation period cannot be zero");
+ return EINVAL;
+ }
+
+ new_ext.mandatory = TRUE;
+ new_ext.data.element = choice_HDB_extension_data_key_rotation;
+ new_ext.data.u.key_rotation.len = 0;
+ new_ext.data.u.key_rotation.val = 0;
+
+ if (entry && krs)
+ return EINVAL;
+
+ if (entry) {
+ ext = hdb_find_extension(entry, choice_HDB_extension_data_key_rotation);
+ if (!ext)
+ ext = &new_ext;
+ } else {
+ const KeyRotation *prev_kr = &krs->val[0];
+ unsigned int last_kvno = 0;
+
+ if (kr->epoch - prev_kr->epoch <= 0) {
+ krb5_set_error_message(context, EINVAL,
+ "New key rotation periods must start later "
+ "than existing ones");
+ return EINVAL;
+ }
+
+ if (kr->base_kvno <= prev_kr->base_kvno ||
+ kr->base_kvno - prev_kr->base_kvno <=
+ (last_kvno = 1 +
+ ((kr->epoch - prev_kr->epoch) / prev_kr->period))) {
+ krb5_set_error_message(context, EINVAL,
+ "New key rotation base kvno must be larger "
+ "the last kvno for the current key "
+ "rotation (%u)", last_kvno);
+ return EINVAL;
+ }
+ }
+
+ /* First, append */
+ ret = add_HDB_Ext_KeyRotation(&ext->data.u.key_rotation, kr);
+ if (ret)
+ return ret;
+
+ /* Rotate new to front */
+ tmp = ext->data.u.key_rotation.val[ext->data.u.key_rotation.len - 1];
+ sz = sizeof(ext->data.u.key_rotation.val[0]);
+ memmove(&ext->data.u.key_rotation.val[1], &ext->data.u.key_rotation.val[0],
+ (ext->data.u.key_rotation.len - 1) * sz);
+ ext->data.u.key_rotation.val[0] = tmp;
+
+ /* Drop too old entries */
+ for (i = 3; i < ext->data.u.key_rotation.len; i++)
+ free_KeyRotation(&ext->data.u.key_rotation.val[i]);
+ ext->data.u.key_rotation.len =
+ ext->data.u.key_rotation.len > 3 ? 3 : ext->data.u.key_rotation.len;
+
+ if (ext != &new_ext)
+ return 0;
+
+ /* Install new extension */
+ if (ret == 0 && entry)
+ ret = hdb_replace_extension(context, entry, ext);
+ free_HDB_extension(&new_ext);
+ return ret;
+}
diff --git a/third_party/heimdal/lib/hdb/hdb-keytab.c b/third_party/heimdal/lib/hdb/hdb-keytab.c
new file mode 100644
index 0000000..c9b469c
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/hdb-keytab.c
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2009 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "hdb_locl.h"
+#include <assert.h>
+
+typedef struct {
+ char *path;
+ krb5_keytab keytab;
+} *hdb_keytab;
+
+/*
+ *
+ */
+
+static krb5_error_code
+hkt_close(krb5_context context, HDB *db)
+{
+ hdb_keytab k = (hdb_keytab)db->hdb_db;
+ krb5_error_code ret;
+
+ assert(k->keytab);
+
+ ret = krb5_kt_close(context, k->keytab);
+ k->keytab = NULL;
+
+ return ret;
+}
+
+static krb5_error_code
+hkt_destroy(krb5_context context, HDB *db)
+{
+ hdb_keytab k = (hdb_keytab)db->hdb_db;
+ krb5_error_code ret;
+
+ ret = hdb_clear_master_key(context, db);
+ krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
+
+ free(k->path);
+ free(k);
+
+ free(db->hdb_name);
+ free(db);
+ return ret;
+}
+
+static krb5_error_code
+hkt_lock(krb5_context context, HDB *db, int operation)
+{
+ return 0;
+}
+
+static krb5_error_code
+hkt_unlock(krb5_context context, HDB *db)
+{
+ return 0;
+}
+
+static krb5_error_code
+hkt_firstkey(krb5_context context, HDB *db,
+ unsigned flags, hdb_entry *entry)
+{
+ return HDB_ERR_DB_INUSE;
+}
+
+static krb5_error_code
+hkt_nextkey(krb5_context context, HDB * db, unsigned flags,
+ hdb_entry * entry)
+{
+ return HDB_ERR_DB_INUSE;
+}
+
+static krb5_error_code
+hkt_open(krb5_context context, HDB * db, int flags, mode_t mode)
+{
+ hdb_keytab k = (hdb_keytab)db->hdb_db;
+ krb5_error_code ret;
+
+ assert(k->keytab == NULL);
+
+ ret = krb5_kt_resolve(context, k->path, &k->keytab);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static krb5_error_code
+hkt_fetch_kvno(krb5_context context, HDB * db, krb5_const_principal principal,
+ unsigned flags, krb5_kvno kvno, hdb_entry * entry)
+{
+ hdb_keytab k = (hdb_keytab)db->hdb_db;
+ krb5_error_code ret;
+ krb5_keytab_entry ktentry;
+
+ if (!(flags & HDB_F_KVNO_SPECIFIED)) {
+ /* Preserve previous behaviour if no kvno specified */
+ kvno = 0;
+ }
+
+ memset(&ktentry, 0, sizeof(ktentry));
+
+ entry->flags.server = 1;
+ entry->flags.forwardable = 1;
+ entry->flags.renewable = 1;
+
+ /* Not recorded in the OD backend, make something up */
+ ret = krb5_parse_name(context, "hdb/keytab@WELL-KNOWN:KEYTAB-BACKEND",
+ &entry->created_by.principal);
+ if (ret)
+ goto out;
+
+ /*
+ * XXX really needs to try all enctypes and just not pick the
+ * first one, even if that happens to be des3-cbc-sha1 (ie best
+ * enctype) in the Apple case. A while loop over all known
+ * enctypes should work.
+ */
+
+ ret = krb5_kt_get_entry(context, k->keytab, principal, kvno, 0, &ktentry);
+ if (ret) {
+ ret = HDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ ret = krb5_copy_principal(context, principal, &entry->principal);
+ if (ret)
+ goto out;
+
+ ret = _hdb_keytab2hdb_entry(context, &ktentry, entry);
+
+ out:
+ if (ret) {
+ free_HDB_entry(entry);
+ memset(entry, 0, sizeof(*entry));
+ }
+ krb5_kt_free_entry(context, &ktentry);
+
+ return ret;
+}
+
+static krb5_error_code
+hkt_store(krb5_context context, HDB * db, unsigned flags,
+ hdb_entry * entry)
+{
+ return HDB_ERR_DB_INUSE;
+}
+
+
+krb5_error_code
+hdb_keytab_create(krb5_context context, HDB ** db, const char *arg)
+{
+ hdb_keytab k;
+
+ *db = calloc(1, sizeof(**db));
+ if (*db == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ memset(*db, 0, sizeof(**db));
+
+ k = calloc(1, sizeof(*k));
+ if (k == NULL) {
+ free(*db);
+ *db = NULL;
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ k->path = strdup(arg);
+ if (k->path == NULL) {
+ free(k);
+ free(*db);
+ *db = NULL;
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+
+ (*db)->hdb_db = k;
+
+ (*db)->hdb_master_key_set = 0;
+ (*db)->hdb_openp = 0;
+ (*db)->hdb_open = hkt_open;
+ (*db)->hdb_close = hkt_close;
+ (*db)->hdb_fetch_kvno = hkt_fetch_kvno;
+ (*db)->hdb_store = hkt_store;
+ (*db)->hdb_remove = NULL;
+ (*db)->hdb_firstkey = hkt_firstkey;
+ (*db)->hdb_nextkey = hkt_nextkey;
+ (*db)->hdb_lock = hkt_lock;
+ (*db)->hdb_unlock = hkt_unlock;
+ (*db)->hdb_rename = NULL;
+ (*db)->hdb__get = NULL;
+ (*db)->hdb__put = NULL;
+ (*db)->hdb__del = NULL;
+ (*db)->hdb_destroy = hkt_destroy;
+
+ return 0;
+}
diff --git a/third_party/heimdal/lib/hdb/hdb-ldap.c b/third_party/heimdal/lib/hdb/hdb-ldap.c
new file mode 100644
index 0000000..5cd097f
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/hdb-ldap.c
@@ -0,0 +1,2117 @@
+/*
+ * Copyright (c) 1999-2001, 2003, PADL Software Pty Ltd.
+ * Copyright (c) 2004, Andrew Bartlett.
+ * Copyright (c) 2003 - 2008, Kungliga Tekniska Högskolan.
+ * Copyright (c) 2015, Timothy Pearson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of PADL Software nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE 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 PADL SOFTWARE 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 "hdb_locl.h"
+
+#ifdef OPENLDAP
+
+#include <lber.h>
+#include <ldap.h>
+#include <sys/un.h>
+#include <hex.h>
+
+static krb5_error_code LDAP__connect(krb5_context context, HDB *);
+static krb5_error_code LDAP_close(krb5_context context, HDB *);
+
+static krb5_error_code
+LDAP_message2entry(krb5_context context, HDB * db, LDAPMessage * msg,
+ int flags, hdb_entry * ent);
+
+static const char *default_structural_object = "account";
+static char *structural_object;
+static const char *default_ldap_url = "ldapi:///";
+static krb5_boolean samba_forwardable;
+
+struct hdbldapdb {
+ LDAP *h_lp;
+ int h_msgid;
+ char *h_base;
+ char *h_url;
+ char *h_bind_dn;
+ char *h_bind_password;
+ krb5_boolean h_start_tls;
+ char *h_createbase;
+};
+
+#define HDB2LDAP(db) (((struct hdbldapdb *)(db)->hdb_db)->h_lp)
+#define HDB2MSGID(db) (((struct hdbldapdb *)(db)->hdb_db)->h_msgid)
+#define HDBSETMSGID(db,msgid) \
+ do { ((struct hdbldapdb *)(db)->hdb_db)->h_msgid = msgid; } while(0)
+#define HDB2BASE(dn) (((struct hdbldapdb *)(db)->hdb_db)->h_base)
+#define HDB2URL(dn) (((struct hdbldapdb *)(db)->hdb_db)->h_url)
+#define HDB2BINDDN(db) (((struct hdbldapdb *)(db)->hdb_db)->h_bind_dn)
+#define HDB2BINDPW(db) (((struct hdbldapdb *)(db)->hdb_db)->h_bind_password)
+#define HDB2CREATE(db) (((struct hdbldapdb *)(db)->hdb_db)->h_createbase)
+
+/*
+ *
+ */
+
+static char * krb5kdcentry_attrs[] = {
+ "cn",
+ "createTimestamp",
+ "creatorsName",
+ "krb5EncryptionType",
+ "krb5KDCFlags",
+ "krb5Key",
+ "krb5KeyVersionNumber",
+ "krb5MaxLife",
+ "krb5MaxRenew",
+ "krb5PasswordEnd",
+ "krb5PrincipalName",
+ "krb5PrincipalRealm",
+ "krb5ExtendedAttributes",
+ "krb5ValidEnd",
+ "krb5ValidStart",
+ "modifiersName",
+ "modifyTimestamp",
+ "objectClass",
+ "sambaAcctFlags",
+ "sambaKickoffTime",
+ "sambaNTPassword",
+ "sambaPwdLastSet",
+ "sambaPwdMustChange",
+ "uid",
+ NULL
+};
+
+static char *krb5principal_attrs[] = {
+ "cn",
+ "createTimestamp",
+ "creatorsName",
+ "krb5PrincipalName",
+ "krb5PrincipalRealm",
+ "modifiersName",
+ "modifyTimestamp",
+ "objectClass",
+ "uid",
+ NULL
+};
+
+static int
+LDAP_no_size_limit(krb5_context context, LDAP *lp)
+{
+ int ret, limit = LDAP_NO_LIMIT;
+
+ ret = ldap_set_option(lp, LDAP_OPT_SIZELIMIT, (const void *)&limit);
+ if (ret != LDAP_SUCCESS) {
+ krb5_set_error_message(context, HDB_ERR_BADVERSION,
+ "ldap_set_option: %s",
+ ldap_err2string(ret));
+ return HDB_ERR_BADVERSION;
+ }
+ return 0;
+}
+
+static int
+check_ldap(krb5_context context, HDB *db, int ret)
+{
+ switch (ret) {
+ case LDAP_SUCCESS:
+ return 0;
+ case LDAP_SERVER_DOWN:
+ LDAP_close(context, db);
+ return 1;
+ default:
+ return 1;
+ }
+}
+
+static krb5_error_code
+LDAP__setmod(LDAPMod *** modlist, int modop, const char *attribute,
+ int *pIndex)
+{
+ int cMods;
+
+ if (*modlist == NULL) {
+ *modlist = (LDAPMod **)ber_memcalloc(1, sizeof(LDAPMod *));
+ if (*modlist == NULL)
+ return ENOMEM;
+ }
+
+ for (cMods = 0; (*modlist)[cMods] != NULL; cMods++) {
+ if ((*modlist)[cMods]->mod_op == modop &&
+ strcasecmp((*modlist)[cMods]->mod_type, attribute) == 0) {
+ break;
+ }
+ }
+
+ *pIndex = cMods;
+
+ if ((*modlist)[cMods] == NULL) {
+ LDAPMod *mod;
+
+ *modlist = (LDAPMod **)ber_memrealloc(*modlist,
+ (cMods + 2) * sizeof(LDAPMod *));
+ if (*modlist == NULL)
+ return ENOMEM;
+
+ (*modlist)[cMods] = (LDAPMod *)ber_memalloc(sizeof(LDAPMod));
+ if ((*modlist)[cMods] == NULL)
+ return ENOMEM;
+
+ mod = (*modlist)[cMods];
+ mod->mod_op = modop;
+ mod->mod_type = ber_strdup(attribute);
+ if (mod->mod_type == NULL) {
+ ber_memfree(mod);
+ (*modlist)[cMods] = NULL;
+ return ENOMEM;
+ }
+
+ if (modop & LDAP_MOD_BVALUES) {
+ mod->mod_bvalues = NULL;
+ } else {
+ mod->mod_values = NULL;
+ }
+
+ (*modlist)[cMods + 1] = NULL;
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+LDAP_addmod_len(LDAPMod *** modlist, int modop, const char *attribute,
+ unsigned char *value, size_t len)
+{
+ krb5_error_code ret;
+ int cMods, i = 0;
+
+ ret = LDAP__setmod(modlist, modop | LDAP_MOD_BVALUES, attribute, &cMods);
+ if (ret)
+ return ret;
+
+ if (value != NULL) {
+ struct berval **bv;
+
+ bv = (*modlist)[cMods]->mod_bvalues;
+ if (bv != NULL) {
+ for (i = 0; bv[i] != NULL; i++)
+ ;
+ bv = ber_memrealloc(bv, (i + 2) * sizeof(*bv));
+ } else
+ bv = ber_memalloc(2 * sizeof(*bv));
+ if (bv == NULL)
+ return ENOMEM;
+
+ (*modlist)[cMods]->mod_bvalues = bv;
+
+ bv[i] = ber_memalloc(sizeof(**bv));;
+ if (bv[i] == NULL)
+ return ENOMEM;
+
+ bv[i]->bv_val = (void *)value;
+ bv[i]->bv_len = len;
+
+ bv[i + 1] = NULL;
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+LDAP_addmod(LDAPMod *** modlist, int modop, const char *attribute,
+ const char *value)
+{
+ int cMods, i = 0;
+ krb5_error_code ret;
+
+ ret = LDAP__setmod(modlist, modop, attribute, &cMods);
+ if (ret)
+ return ret;
+
+ if (value != NULL) {
+ char **bv;
+
+ bv = (*modlist)[cMods]->mod_values;
+ if (bv != NULL) {
+ for (i = 0; bv[i] != NULL; i++)
+ ;
+ bv = ber_memrealloc(bv, (i + 2) * sizeof(*bv));
+ } else
+ bv = ber_memalloc(2 * sizeof(*bv));
+ if (bv == NULL)
+ return ENOMEM;
+
+ (*modlist)[cMods]->mod_values = bv;
+
+ bv[i] = ber_strdup(value);
+ if (bv[i] == NULL)
+ return ENOMEM;
+
+ bv[i + 1] = NULL;
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+LDAP_addmod_generalized_time(LDAPMod *** mods, int modop,
+ const char *attribute, KerberosTime * time)
+{
+ char buf[22];
+ struct tm *tm;
+
+ /* XXX not threadsafe */
+ tm = gmtime(time);
+ strftime(buf, sizeof(buf), "%Y%m%d%H%M%SZ", tm);
+
+ return LDAP_addmod(mods, modop, attribute, buf);
+}
+
+static krb5_error_code
+LDAP_addmod_integer(krb5_context context,
+ LDAPMod *** mods, int modop,
+ const char *attribute, unsigned long l)
+{
+ krb5_error_code ret;
+ char *buf;
+
+ ret = asprintf(&buf, "%ld", l);
+ if (ret < 0) {
+ krb5_set_error_message(context, ENOMEM,
+ "asprintf: out of memory:");
+ return ENOMEM;
+ }
+ ret = LDAP_addmod(mods, modop, attribute, buf);
+ free (buf);
+ return ret;
+}
+
+static krb5_error_code
+LDAP_get_string_value(HDB * db, LDAPMessage * entry,
+ const char *attribute, char **ptr)
+{
+ struct berval **vals;
+
+ vals = ldap_get_values_len(HDB2LDAP(db), entry, attribute);
+ if (vals == NULL || vals[0] == NULL) {
+ *ptr = NULL;
+ return HDB_ERR_NOENTRY;
+ }
+
+ *ptr = malloc(vals[0]->bv_len + 1);
+ if (*ptr == NULL) {
+ ldap_value_free_len(vals);
+ return ENOMEM;
+ }
+
+ memcpy(*ptr, vals[0]->bv_val, vals[0]->bv_len);
+ (*ptr)[vals[0]->bv_len] = 0;
+
+ ldap_value_free_len(vals);
+
+ return 0;
+}
+
+static krb5_error_code
+LDAP_get_integer_value(HDB * db, LDAPMessage * entry,
+ const char *attribute, int *ptr)
+{
+ krb5_error_code ret;
+ char *val;
+
+ ret = LDAP_get_string_value(db, entry, attribute, &val);
+ if (ret)
+ return ret;
+ *ptr = atoi(val);
+ free(val);
+ return 0;
+}
+
+static krb5_error_code
+LDAP_get_generalized_time_value(HDB * db, LDAPMessage * entry,
+ const char *attribute, KerberosTime * kt)
+{
+ char *tmp, *gentime;
+ struct tm tm;
+ int ret;
+
+ *kt = 0;
+
+ ret = LDAP_get_string_value(db, entry, attribute, &gentime);
+ if (ret)
+ return ret;
+
+ tmp = strptime(gentime, "%Y%m%d%H%M%SZ", &tm);
+ if (tmp == NULL) {
+ free(gentime);
+ return HDB_ERR_NOENTRY;
+ }
+
+ free(gentime);
+
+ *kt = timegm(&tm);
+
+ return 0;
+}
+
+static int
+bervalstrcmp(struct berval *v, const char *str)
+{
+ size_t len = strlen(str);
+ return (v->bv_len == len) && strncasecmp(str, (char *)v->bv_val, len) == 0;
+}
+
+
+static krb5_error_code
+LDAP_entry2mods(krb5_context context, HDB * db, hdb_entry * ent,
+ LDAPMessage * msg, LDAPMod *** pmods, krb5_boolean *pis_new_entry)
+{
+ krb5_error_code ret;
+ krb5_boolean is_new_entry = FALSE;
+ char *tmp = NULL;
+ LDAPMod **mods = NULL;
+ hdb_entry orig;
+ unsigned long oflags, nflags;
+ int i;
+
+ krb5_boolean is_samba_account = FALSE;
+ krb5_boolean is_account = FALSE;
+ krb5_boolean is_heimdal_entry = FALSE;
+ krb5_boolean is_heimdal_principal = FALSE;
+
+ struct berval **vals;
+
+ *pmods = NULL;
+
+ if (msg != NULL) {
+
+ ret = LDAP_message2entry(context, db, msg, 0, &orig);
+ if (ret)
+ goto out;
+
+ vals = ldap_get_values_len(HDB2LDAP(db), msg, "objectClass");
+ if (vals) {
+ int num_objectclasses = ldap_count_values_len(vals);
+ for (i=0; i < num_objectclasses; i++) {
+ if (bervalstrcmp(vals[i], "sambaSamAccount"))
+ is_samba_account = TRUE;
+ else if (bervalstrcmp(vals[i], structural_object))
+ is_account = TRUE;
+ else if (bervalstrcmp(vals[i], "krb5Principal"))
+ is_heimdal_principal = TRUE;
+ else if (bervalstrcmp(vals[i], "krb5KDCEntry"))
+ is_heimdal_entry = TRUE;
+ }
+ ldap_value_free_len(vals);
+ }
+
+ /*
+ * If this is just a "account" entry and no other objectclass
+ * is hanging on this entry, it's really a new entry.
+ */
+ if (is_samba_account == FALSE && is_heimdal_principal == FALSE &&
+ is_heimdal_entry == FALSE) {
+ if (is_account == TRUE) {
+ is_new_entry = TRUE;
+ } else {
+ ret = HDB_ERR_NOENTRY;
+ goto out;
+ }
+ }
+ } else
+ is_new_entry = TRUE;
+
+ if (is_new_entry) {
+
+ /* to make it perfectly obvious we're depending on
+ * orig being intiialized to zero */
+ memset(&orig, 0, sizeof(orig));
+
+ /* account is the structural object class */
+ if (is_account == FALSE) {
+ ret = LDAP_addmod(&mods, LDAP_MOD_ADD, "objectClass", "top");
+ if (ret)
+ goto out;
+
+ ret = LDAP_addmod(&mods, LDAP_MOD_ADD, "objectClass",
+ structural_object);
+ is_account = TRUE;
+ if (ret)
+ goto out;
+ }
+
+ ret = LDAP_addmod(&mods, LDAP_MOD_ADD, "objectClass", "krb5Principal");
+ is_heimdal_principal = TRUE;
+ if (ret)
+ goto out;
+
+ ret = LDAP_addmod(&mods, LDAP_MOD_ADD, "objectClass", "krb5KDCEntry");
+ is_heimdal_entry = TRUE;
+ if (ret)
+ goto out;
+ }
+
+ if (is_new_entry ||
+ krb5_principal_compare(context, ent->principal, orig.principal)
+ == FALSE)
+ {
+ if (is_heimdal_principal || is_heimdal_entry) {
+
+ ret = krb5_unparse_name(context, ent->principal, &tmp);
+ if (ret)
+ goto out;
+
+ ret = LDAP_addmod(&mods, LDAP_MOD_REPLACE,
+ "krb5PrincipalName", tmp);
+ if (ret) {
+ free(tmp);
+ goto out;
+ }
+ free(tmp);
+ }
+
+ if (is_account || is_samba_account) {
+ ret = krb5_unparse_name_short(context, ent->principal, &tmp);
+ if (ret)
+ goto out;
+ ret = LDAP_addmod(&mods, LDAP_MOD_REPLACE, "uid", tmp);
+ if (ret) {
+ free(tmp);
+ goto out;
+ }
+ free(tmp);
+ }
+ }
+
+ if (is_heimdal_entry && (ent->kvno != orig.kvno || is_new_entry)) {
+ ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
+ "krb5KeyVersionNumber",
+ ent->kvno);
+ if (ret)
+ goto out;
+ }
+
+ if (is_heimdal_entry && ent->extensions) {
+ if (!is_new_entry) {
+ vals = ldap_get_values_len(HDB2LDAP(db), msg, "krb5ExtendedAttributes");
+ if (vals) {
+ ldap_value_free_len(vals);
+ ret = LDAP_addmod(&mods, LDAP_MOD_DELETE, "krb5ExtendedAttributes", NULL);
+ if (ret)
+ goto out;
+ }
+ }
+
+ for (i = 0; i < ent->extensions->len; i++) {
+ unsigned char *buf;
+ size_t size, sz = 0;
+
+ ASN1_MALLOC_ENCODE(HDB_extension, buf, size, &ent->extensions->val[i], &sz, ret);
+ if (ret)
+ goto out;
+ if (size != sz)
+ krb5_abortx(context, "internal error in ASN.1 encoder");
+
+ ret = LDAP_addmod_len(&mods, LDAP_MOD_ADD, "krb5ExtendedAttributes", buf, sz);
+ if (ret)
+ goto out;
+ }
+ }
+
+ if (is_heimdal_entry && ent->valid_start) {
+ if (orig.valid_end == NULL
+ || (*(ent->valid_start) != *(orig.valid_start))) {
+ ret = LDAP_addmod_generalized_time(&mods, LDAP_MOD_REPLACE,
+ "krb5ValidStart",
+ ent->valid_start);
+ if (ret)
+ goto out;
+ }
+ }
+
+ if (ent->valid_end) {
+ if (orig.valid_end == NULL || (*(ent->valid_end) != *(orig.valid_end))) {
+ if (is_heimdal_entry) {
+ ret = LDAP_addmod_generalized_time(&mods, LDAP_MOD_REPLACE,
+ "krb5ValidEnd",
+ ent->valid_end);
+ if (ret)
+ goto out;
+ }
+ if (is_samba_account) {
+ ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
+ "sambaKickoffTime",
+ *(ent->valid_end));
+ if (ret)
+ goto out;
+ }
+ }
+ }
+
+ if (ent->pw_end) {
+ if (orig.pw_end == NULL || (*(ent->pw_end) != *(orig.pw_end))) {
+ if (is_heimdal_entry) {
+ ret = LDAP_addmod_generalized_time(&mods, LDAP_MOD_REPLACE,
+ "krb5PasswordEnd",
+ ent->pw_end);
+ if (ret)
+ goto out;
+ }
+
+ if (is_samba_account) {
+ ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
+ "sambaPwdMustChange",
+ *(ent->pw_end));
+ if (ret)
+ goto out;
+ }
+ }
+ }
+
+
+#if 0 /* we we have last_pw_change */
+ if (is_samba_account && ent->last_pw_change) {
+ if (orig.last_pw_change == NULL || (*(ent->last_pw_change) != *(orig.last_pw_change))) {
+ ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
+ "sambaPwdLastSet",
+ *(ent->last_pw_change));
+ if (ret)
+ goto out;
+ }
+ }
+#endif
+
+ if (is_heimdal_entry && ent->max_life) {
+ if (orig.max_life == NULL
+ || (*(ent->max_life) != *(orig.max_life))) {
+
+ ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
+ "krb5MaxLife",
+ *(ent->max_life));
+ if (ret)
+ goto out;
+ }
+ }
+
+ if (is_heimdal_entry && ent->max_renew) {
+ if (orig.max_renew == NULL
+ || (*(ent->max_renew) != *(orig.max_renew))) {
+
+ ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
+ "krb5MaxRenew",
+ *(ent->max_renew));
+ if (ret)
+ goto out;
+ }
+ }
+
+ oflags = HDBFlags2int(orig.flags);
+ nflags = HDBFlags2int(ent->flags);
+
+ if (is_heimdal_entry && oflags != nflags) {
+
+ ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
+ "krb5KDCFlags",
+ nflags);
+ if (ret)
+ goto out;
+ }
+
+ /* Remove keys if they exists, and then replace keys. */
+ if (!is_new_entry && orig.keys.len > 0) {
+ vals = ldap_get_values_len(HDB2LDAP(db), msg, "krb5Key");
+ if (vals) {
+ ldap_value_free_len(vals);
+
+ ret = LDAP_addmod(&mods, LDAP_MOD_DELETE, "krb5Key", NULL);
+ if (ret)
+ goto out;
+ }
+ }
+
+ for (i = 0; i < ent->keys.len; i++) {
+
+ if (is_samba_account
+ && ent->keys.val[i].key.keytype == ETYPE_ARCFOUR_HMAC_MD5) {
+ char *ntHexPassword;
+ char *nt;
+ time_t now = time(NULL);
+
+ /* the key might have been 'sealed', but samba passwords
+ are clear in the directory */
+ ret = hdb_unseal_key(context, db, &ent->keys.val[i]);
+ if (ret)
+ goto out;
+
+ nt = ent->keys.val[i].key.keyvalue.data;
+ /* store in ntPassword, not krb5key */
+ ret = hex_encode(nt, 16, &ntHexPassword);
+ if (ret < 0) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "hdb-ldap: failed to "
+ "hex encode key");
+ goto out;
+ }
+ ret = LDAP_addmod(&mods, LDAP_MOD_REPLACE, "sambaNTPassword",
+ ntHexPassword);
+ free(ntHexPassword);
+ if (ret)
+ goto out;
+ ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_REPLACE,
+ "sambaPwdLastSet", now);
+ if (ret)
+ goto out;
+
+ /* have to kill the LM passwod if it exists */
+ vals = ldap_get_values_len(HDB2LDAP(db), msg, "sambaLMPassword");
+ if (vals) {
+ ldap_value_free_len(vals);
+ ret = LDAP_addmod(&mods, LDAP_MOD_DELETE,
+ "sambaLMPassword", NULL);
+ if (ret)
+ goto out;
+ }
+
+ } else if (is_heimdal_entry) {
+ unsigned char *buf;
+ size_t len, buf_size;
+
+ ASN1_MALLOC_ENCODE(Key, buf, buf_size, &ent->keys.val[i], &len, ret);
+ if (ret)
+ goto out;
+ if(buf_size != len)
+ krb5_abortx(context, "internal error in ASN.1 encoder");
+
+ /* addmod_len _owns_ the key, doesn't need to copy it */
+ ret = LDAP_addmod_len(&mods, LDAP_MOD_ADD, "krb5Key", buf, len);
+ if (ret)
+ goto out;
+ }
+ }
+
+ if (ent->etypes) {
+ int add_krb5EncryptionType = 0;
+
+ /*
+ * Only add/modify krb5EncryptionType if it's a new heimdal
+ * entry or krb5EncryptionType already exists on the entry.
+ */
+
+ if (!is_new_entry) {
+ vals = ldap_get_values_len(HDB2LDAP(db), msg, "krb5EncryptionType");
+ if (vals) {
+ ldap_value_free_len(vals);
+ ret = LDAP_addmod(&mods, LDAP_MOD_DELETE, "krb5EncryptionType",
+ NULL);
+ if (ret)
+ goto out;
+ add_krb5EncryptionType = 1;
+ }
+ } else if (is_heimdal_entry)
+ add_krb5EncryptionType = 1;
+
+ if (add_krb5EncryptionType) {
+ for (i = 0; i < ent->etypes->len; i++) {
+ if (is_samba_account &&
+ ent->keys.val[i].key.keytype == ETYPE_ARCFOUR_HMAC_MD5)
+ {
+ ;
+ } else if (is_heimdal_entry) {
+ ret = LDAP_addmod_integer(context, &mods, LDAP_MOD_ADD,
+ "krb5EncryptionType",
+ ent->etypes->val[i]);
+ if (ret)
+ goto out;
+ }
+ }
+ }
+ }
+
+ /* for clarity */
+ ret = 0;
+
+ out:
+
+ *pis_new_entry = is_new_entry;
+
+ if (ret == 0)
+ *pmods = mods;
+ else if (mods != NULL) {
+ ldap_mods_free(mods, 1);
+ *pmods = NULL;
+ }
+
+ if (msg)
+ hdb_free_entry(context, db, &orig);
+
+ return ret;
+}
+
+static krb5_error_code
+LDAP_dn2principal(krb5_context context, HDB * db, const char *dn,
+ krb5_principal * principal)
+{
+ krb5_error_code ret;
+ int rc;
+ const char *filter = "(objectClass=krb5Principal)";
+ LDAPMessage *res = NULL, *e;
+ char *p;
+
+ ret = LDAP_no_size_limit(context, HDB2LDAP(db));
+ if (ret)
+ goto out;
+
+ rc = ldap_search_ext_s(HDB2LDAP(db), dn, LDAP_SCOPE_SUBTREE,
+ filter, krb5principal_attrs, 0,
+ NULL, NULL, NULL,
+ 0, &res);
+ if (check_ldap(context, db, rc)) {
+ ret = HDB_ERR_NOENTRY;
+ krb5_set_error_message(context, ret, "ldap_search_ext_s: "
+ "filter: %s error: %s",
+ filter, ldap_err2string(rc));
+ goto out;
+ }
+
+ e = ldap_first_entry(HDB2LDAP(db), res);
+ if (e == NULL) {
+ ret = HDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ ret = LDAP_get_string_value(db, e, "krb5PrincipalName", &p);
+ if (ret) {
+ ret = HDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ ret = krb5_parse_name(context, p, principal);
+ free(p);
+
+ out:
+ if (res)
+ ldap_msgfree(res);
+
+ return ret;
+}
+
+static int
+need_quote(unsigned char c)
+{
+ return (c & 0x80) ||
+ (c < 32) ||
+ (c == '(') ||
+ (c == ')') ||
+ (c == '*') ||
+ (c == '\\') ||
+ (c == 0x7f);
+}
+
+static const char hexchar[] = "0123456789ABCDEF";
+
+static krb5_error_code
+escape_value(krb5_context context, const char *unquoted, char **quoted)
+{
+ size_t i, len;
+
+ for (i = 0, len = 0; unquoted[i] != '\0'; i++, len++) {
+ if (need_quote((unsigned char)unquoted[i]))
+ len += 2;
+ }
+
+ *quoted = malloc(len + 1);
+ if (*quoted == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ for (i = 0; unquoted[0] ; unquoted++) {
+ if (need_quote((unsigned char)unquoted[0])) {
+ (*quoted)[i++] = '\\';
+ (*quoted)[i++] = hexchar[(unquoted[0] >> 4) & 0xf];
+ (*quoted)[i++] = hexchar[(unquoted[0] ) & 0xf];
+ } else
+ (*quoted)[i++] = (char)unquoted[0];
+ }
+ (*quoted)[i] = '\0';
+ return 0;
+}
+
+
+static krb5_error_code
+LDAP__lookup_princ(krb5_context context,
+ HDB *db,
+ const char *princname,
+ const char *userid,
+ LDAPMessage **msg)
+{
+ krb5_error_code ret;
+ int rc;
+ char *quote, *filter = NULL;
+
+ ret = LDAP__connect(context, db);
+ if (ret)
+ return ret;
+
+ /*
+ * Quote searches that contain filter language, this quote
+ * searches for *@REALM, which takes very long time.
+ */
+
+ ret = escape_value(context, princname, &quote);
+ if (ret)
+ goto out;
+
+ rc = asprintf(&filter,
+ "(&(objectClass=krb5Principal)(krb5PrincipalName=%s))",
+ quote);
+ free(quote);
+
+ if (rc < 0) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+
+ ret = LDAP_no_size_limit(context, HDB2LDAP(db));
+ if (ret)
+ goto out;
+
+ rc = ldap_search_ext_s(HDB2LDAP(db), HDB2BASE(db),
+ LDAP_SCOPE_SUBTREE, filter,
+ krb5kdcentry_attrs, 0,
+ NULL, NULL, NULL,
+ 0, msg);
+ if (check_ldap(context, db, rc)) {
+ ret = HDB_ERR_NOENTRY;
+ krb5_set_error_message(context, ret, "ldap_search_ext_s: "
+ "filter: %s - error: %s",
+ filter, ldap_err2string(rc));
+ goto out;
+ }
+
+ if (userid && ldap_count_entries(HDB2LDAP(db), *msg) == 0) {
+ free(filter);
+ filter = NULL;
+ ldap_msgfree(*msg);
+ *msg = NULL;
+
+ ret = escape_value(context, userid, &quote);
+ if (ret)
+ goto out;
+
+ rc = asprintf(&filter,
+ "(&(|(objectClass=sambaSamAccount)(objectClass=%s))(uid=%s))",
+ structural_object, quote);
+ free(quote);
+ if (rc < 0) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "asprintf: out of memory");
+ goto out;
+ }
+
+ ret = LDAP_no_size_limit(context, HDB2LDAP(db));
+ if (ret)
+ goto out;
+
+ rc = ldap_search_ext_s(HDB2LDAP(db), HDB2BASE(db), LDAP_SCOPE_SUBTREE,
+ filter, krb5kdcentry_attrs, 0,
+ NULL, NULL, NULL,
+ 0, msg);
+ if (check_ldap(context, db, rc)) {
+ ret = HDB_ERR_NOENTRY;
+ krb5_set_error_message(context, ret,
+ "ldap_search_ext_s: filter: %s error: %s",
+ filter, ldap_err2string(rc));
+ goto out;
+ }
+ }
+
+ ret = 0;
+
+ out:
+ if (filter)
+ free(filter);
+
+ return ret;
+}
+
+static krb5_error_code
+LDAP_principal2message(krb5_context context, HDB * db,
+ krb5_const_principal princ, LDAPMessage ** msg)
+{
+ char *name, *name_short = NULL;
+ krb5_error_code ret;
+ krb5_realm *r, *r0;
+
+ *msg = NULL;
+
+ ret = krb5_unparse_name(context, princ, &name);
+ if (ret)
+ return ret;
+
+ ret = krb5_get_default_realms(context, &r0);
+ if(ret) {
+ free(name);
+ return ret;
+ }
+ for (r = r0; *r != NULL; r++) {
+ if(strcmp(krb5_principal_get_realm(context, princ), *r) == 0) {
+ ret = krb5_unparse_name_short(context, princ, &name_short);
+ if (ret) {
+ krb5_free_host_realm(context, r0);
+ free(name);
+ return ret;
+ }
+ break;
+ }
+ }
+ krb5_free_host_realm(context, r0);
+
+ ret = LDAP__lookup_princ(context, db, name, name_short, msg);
+ free(name);
+ free(name_short);
+
+ return ret;
+}
+
+/*
+ * Construct an hdb_entry from a directory entry.
+ */
+static krb5_error_code
+LDAP_message2entry(krb5_context context, HDB * db, LDAPMessage * msg,
+ int flags, hdb_entry * ent)
+{
+ char *unparsed_name = NULL, *dn = NULL, *ntPasswordIN = NULL;
+ char *samba_acct_flags = NULL;
+ struct berval **keys;
+ struct berval **extensions;
+ struct berval **vals;
+ int tmp, tmp_time, i, ret, have_arcfour = 0;
+
+ memset(ent, 0, sizeof(*ent));
+ ent->flags = int2HDBFlags(0);
+
+ ret = LDAP_get_string_value(db, msg, "krb5PrincipalName", &unparsed_name);
+ if (ret == 0) {
+ ret = krb5_parse_name(context, unparsed_name, &ent->principal);
+ if (ret)
+ goto out;
+ } else {
+ ret = LDAP_get_string_value(db, msg, "uid",
+ &unparsed_name);
+ if (ret == 0) {
+ ret = krb5_parse_name(context, unparsed_name, &ent->principal);
+ if (ret)
+ goto out;
+ } else {
+ krb5_set_error_message(context, HDB_ERR_NOENTRY,
+ "hdb-ldap: ldap entry missing"
+ "principal name");
+ return HDB_ERR_NOENTRY;
+ }
+ }
+
+ {
+ int integer;
+ ret = LDAP_get_integer_value(db, msg, "krb5KeyVersionNumber",
+ &integer);
+ if (ret)
+ ent->kvno = 0;
+ else
+ ent->kvno = integer;
+ }
+
+ keys = ldap_get_values_len(HDB2LDAP(db), msg, "krb5Key");
+ if (keys != NULL) {
+ size_t l;
+
+ ent->keys.len = ldap_count_values_len(keys);
+ ent->keys.val = (Key *) calloc(ent->keys.len, sizeof(Key));
+ if (ent->keys.val == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "calloc: out of memory");
+ goto out;
+ }
+ for (i = 0; i < ent->keys.len; i++) {
+ decode_Key((unsigned char *) keys[i]->bv_val,
+ (size_t) keys[i]->bv_len, &ent->keys.val[i], &l);
+ }
+ ber_bvecfree(keys);
+ } else {
+#if 1
+ /*
+ * This violates the ASN1 but it allows a principal to
+ * be related to a general directory entry without creating
+ * the keys. Hopefully it's OK.
+ */
+ ent->keys.len = 0;
+ ent->keys.val = NULL;
+#else
+ ret = HDB_ERR_NOENTRY;
+ goto out;
+#endif
+ }
+
+ extensions = ldap_get_values_len(HDB2LDAP(db), msg, "krb5ExtendedAttributes");
+ if (extensions != NULL) {
+ size_t l;
+
+ ent->extensions = calloc(1, sizeof(*(ent->extensions)));
+ if (ent->extensions == NULL) {
+ ret = krb5_enomem(context);
+ goto out;
+ }
+ ent->extensions->len = ldap_count_values_len(extensions);
+ ent->extensions->val = (HDB_extension *) calloc(ent->extensions->len, sizeof(HDB_extension));
+ if (ent->extensions->val == NULL) {
+ ent->extensions->len = 0;
+ ret = krb5_enomem(context);
+ goto out;
+ }
+ for (i = 0; i < ent->extensions->len; i++) {
+ ret = decode_HDB_extension((unsigned char *) extensions[i]->bv_val,
+ (size_t) extensions[i]->bv_len, &ent->extensions->val[i], &l);
+ if (ret)
+ krb5_set_error_message(context, ret, "decode_HDB_extension failed");
+ }
+ ber_bvecfree(extensions);
+ } else {
+ ent->extensions = NULL;
+ }
+
+ vals = ldap_get_values_len(HDB2LDAP(db), msg, "krb5EncryptionType");
+ if (vals != NULL) {
+ ent->etypes = malloc(sizeof(*(ent->etypes)));
+ if (ent->etypes == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret,"malloc: out of memory");
+ goto out;
+ }
+ ent->etypes->len = ldap_count_values_len(vals);
+ ent->etypes->val = calloc(ent->etypes->len,
+ sizeof(ent->etypes->val[0]));
+ if (ent->etypes->val == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ ent->etypes->len = 0;
+ goto out;
+ }
+ for (i = 0; i < ent->etypes->len; i++) {
+ char *buf;
+
+ buf = malloc(vals[i]->bv_len + 1);
+ if (buf == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ memcpy(buf, vals[i]->bv_val, vals[i]->bv_len);
+ buf[vals[i]->bv_len] = '\0';
+ ent->etypes->val[i] = atoi(buf);
+ free(buf);
+ }
+ ldap_value_free_len(vals);
+ }
+
+ for (i = 0; i < ent->keys.len; i++) {
+ if (ent->keys.val[i].key.keytype == ETYPE_ARCFOUR_HMAC_MD5) {
+ have_arcfour = 1;
+ break;
+ }
+ }
+
+ /* manually construct the NT (type 23) key */
+ ret = LDAP_get_string_value(db, msg, "sambaNTPassword", &ntPasswordIN);
+ if (ret == 0 && have_arcfour == 0) {
+ unsigned *etypes;
+ Key *ks;
+
+ ks = realloc(ent->keys.val,
+ (ent->keys.len + 1) *
+ sizeof(ent->keys.val[0]));
+ if (ks == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ ent->keys.val = ks;
+ memset(&ent->keys.val[ent->keys.len], 0, sizeof(Key));
+ ent->keys.val[ent->keys.len].key.keytype = ETYPE_ARCFOUR_HMAC_MD5;
+ ret = krb5_data_alloc (&ent->keys.val[ent->keys.len].key.keyvalue, 16);
+ if (ret) {
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ ret = ENOMEM;
+ goto out;
+ }
+ ret = hex_decode(ntPasswordIN,
+ ent->keys.val[ent->keys.len].key.keyvalue.data, 16);
+ ent->keys.len++;
+ if (ret == -1) {
+ krb5_set_error_message(context, ret = EINVAL,
+ "invalid hex encoding of password");
+ goto out;
+ }
+
+ if (ent->etypes == NULL) {
+ ent->etypes = malloc(sizeof(*(ent->etypes)));
+ if (ent->etypes == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ ent->etypes->val = NULL;
+ ent->etypes->len = 0;
+ }
+
+ for (i = 0; i < ent->etypes->len; i++)
+ if (ent->etypes->val[i] == ETYPE_ARCFOUR_HMAC_MD5)
+ break;
+ /* If there is no ARCFOUR enctype, add one */
+ if (i == ent->etypes->len) {
+ etypes = realloc(ent->etypes->val,
+ (ent->etypes->len + 1) *
+ sizeof(ent->etypes->val[0]));
+ if (etypes == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ ent->etypes->val = etypes;
+ ent->etypes->val[ent->etypes->len] =
+ ETYPE_ARCFOUR_HMAC_MD5;
+ ent->etypes->len++;
+ }
+ }
+
+ ret = LDAP_get_generalized_time_value(db, msg, "createTimestamp",
+ &ent->created_by.time);
+ if (ret)
+ ent->created_by.time = time(NULL);
+
+ ent->created_by.principal = NULL;
+
+ if (flags & HDB_F_ADMIN_DATA) {
+ ret = LDAP_get_string_value(db, msg, "creatorsName", &dn);
+ if (ret == 0) {
+ LDAP_dn2principal(context, db, dn, &ent->created_by.principal);
+ free(dn);
+ }
+
+ ent->modified_by = calloc(1, sizeof(*ent->modified_by));
+ if (ent->modified_by == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+
+ ret = LDAP_get_generalized_time_value(db, msg, "modifyTimestamp",
+ &ent->modified_by->time);
+ if (ret == 0) {
+ ret = LDAP_get_string_value(db, msg, "modifiersName", &dn);
+ if (ret == 0) {
+ LDAP_dn2principal(context, db, dn, &ent->modified_by->principal);
+ free(dn);
+ } else {
+ free(ent->modified_by);
+ ent->modified_by = NULL;
+ }
+ }
+ }
+
+ ent->valid_start = malloc(sizeof(*ent->valid_start));
+ if (ent->valid_start == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ ret = LDAP_get_generalized_time_value(db, msg, "krb5ValidStart",
+ ent->valid_start);
+ if (ret) {
+ /* OPTIONAL */
+ free(ent->valid_start);
+ ent->valid_start = NULL;
+ }
+
+ ent->valid_end = malloc(sizeof(*ent->valid_end));
+ if (ent->valid_end == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ ret = LDAP_get_generalized_time_value(db, msg, "krb5ValidEnd",
+ ent->valid_end);
+ if (ret) {
+ /* OPTIONAL */
+ free(ent->valid_end);
+ ent->valid_end = NULL;
+ }
+
+ ret = LDAP_get_integer_value(db, msg, "sambaKickoffTime", &tmp_time);
+ if (ret == 0) {
+ if (ent->valid_end == NULL) {
+ ent->valid_end = malloc(sizeof(*ent->valid_end));
+ if (ent->valid_end == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ }
+ *ent->valid_end = tmp_time;
+ }
+
+ ent->pw_end = malloc(sizeof(*ent->pw_end));
+ if (ent->pw_end == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ ret = LDAP_get_generalized_time_value(db, msg, "krb5PasswordEnd",
+ ent->pw_end);
+ if (ret) {
+ /* OPTIONAL */
+ free(ent->pw_end);
+ ent->pw_end = NULL;
+ }
+
+ ret = LDAP_get_integer_value(db, msg, "sambaPwdLastSet", &tmp_time);
+ if (ret == 0) {
+ time_t delta;
+
+ delta = krb5_config_get_time_default(context, NULL,
+ 0,
+ "kadmin",
+ "password_lifetime",
+ NULL);
+
+ if (delta) {
+ if (ent->pw_end == NULL) {
+ ent->pw_end = malloc(sizeof(*ent->pw_end));
+ if (ent->pw_end == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ }
+
+ *ent->pw_end = tmp_time + delta;
+ }
+ }
+
+ ret = LDAP_get_integer_value(db, msg, "sambaPwdMustChange", &tmp_time);
+ if (ret == 0) {
+ if (ent->pw_end == NULL) {
+ ent->pw_end = malloc(sizeof(*ent->pw_end));
+ if (ent->pw_end == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ }
+ *ent->pw_end = tmp_time;
+ }
+
+ /* OPTIONAL */
+ ret = LDAP_get_integer_value(db, msg, "sambaPwdLastSet", &tmp_time);
+ if (ret == 0)
+ hdb_entry_set_pw_change_time(context, ent, tmp_time);
+
+ {
+ int max_life;
+
+ ent->max_life = malloc(sizeof(*ent->max_life));
+ if (ent->max_life == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ ret = LDAP_get_integer_value(db, msg, "krb5MaxLife", &max_life);
+ if (ret) {
+ free(ent->max_life);
+ ent->max_life = NULL;
+ } else
+ *ent->max_life = max_life;
+ }
+
+ {
+ int max_renew;
+
+ ent->max_renew = malloc(sizeof(*ent->max_renew));
+ if (ent->max_renew == NULL) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ goto out;
+ }
+ ret = LDAP_get_integer_value(db, msg, "krb5MaxRenew", &max_renew);
+ if (ret) {
+ free(ent->max_renew);
+ ent->max_renew = NULL;
+ } else
+ *ent->max_renew = max_renew;
+ }
+
+ ret = LDAP_get_integer_value(db, msg, "krb5KDCFlags", &tmp);
+ if (ret)
+ tmp = 0;
+
+ ent->flags = int2HDBFlags(tmp);
+
+ /* Try and find Samba flags to put into the mix */
+ ret = LDAP_get_string_value(db, msg, "sambaAcctFlags", &samba_acct_flags);
+ if (ret == 0) {
+ /* parse the [UXW...] string:
+
+ 'N' No password
+ 'D' Disabled
+ 'H' Homedir required
+ 'T' Temp account.
+ 'U' User account (normal)
+ 'M' MNS logon user account - what is this ?
+ 'W' Workstation account
+ 'S' Server account
+ 'L' Locked account
+ 'X' No Xpiry on password
+ 'I' Interdomain trust account
+
+ */
+
+ int flags_len = strlen(samba_acct_flags);
+
+ if (flags_len < 2)
+ goto out2;
+
+ if (samba_acct_flags[0] != '['
+ || samba_acct_flags[flags_len - 1] != ']')
+ goto out2;
+
+ /* Allow forwarding */
+ if (samba_forwardable)
+ ent->flags.forwardable = TRUE;
+
+ for (i=0; i < flags_len; i++) {
+ switch (samba_acct_flags[i]) {
+ case ' ':
+ case '[':
+ case ']':
+ break;
+ case 'N':
+ /* how to handle no password in kerberos? */
+ break;
+ case 'D':
+ ent->flags.invalid = TRUE;
+ break;
+ case 'H':
+ break;
+ case 'T':
+ /* temp duplicate */
+ ent->flags.invalid = TRUE;
+ break;
+ case 'U':
+ ent->flags.client = TRUE;
+ break;
+ case 'M':
+ break;
+ case 'W':
+ case 'S':
+ ent->flags.server = TRUE;
+ ent->flags.client = TRUE;
+ break;
+ case 'L':
+ ent->flags.invalid = TRUE;
+ break;
+ case 'X':
+ if (ent->pw_end) {
+ free(ent->pw_end);
+ ent->pw_end = NULL;
+ }
+ break;
+ case 'I':
+ ent->flags.server = TRUE;
+ ent->flags.client = TRUE;
+ break;
+ }
+ }
+ out2:
+ free(samba_acct_flags);
+ }
+
+ ret = 0;
+
+out:
+ free(unparsed_name);
+ free(ntPasswordIN);
+
+ if (ret)
+ hdb_free_entry(context, db, ent);
+
+ return ret;
+}
+
+static krb5_error_code
+LDAP_close(krb5_context context, HDB * db)
+{
+ if (HDB2LDAP(db)) {
+ ldap_unbind_ext(HDB2LDAP(db), NULL, NULL);
+ ((struct hdbldapdb *)db->hdb_db)->h_lp = NULL;
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+LDAP_lock(krb5_context context, HDB * db, int operation)
+{
+ return 0;
+}
+
+static krb5_error_code
+LDAP_unlock(krb5_context context, HDB * db)
+{
+ return 0;
+}
+
+static krb5_error_code
+LDAP_seq(krb5_context context, HDB * db, unsigned flags, hdb_entry * entry)
+{
+ int msgid, rc, parserc;
+ krb5_error_code ret;
+ LDAPMessage *e;
+
+ msgid = HDB2MSGID(db);
+ if (msgid < 0)
+ return HDB_ERR_NOENTRY;
+
+ do {
+ rc = ldap_result(HDB2LDAP(db), msgid, LDAP_MSG_ONE, NULL, &e);
+ switch (rc) {
+ case LDAP_RES_SEARCH_REFERENCE:
+ ldap_msgfree(e);
+ ret = 0;
+ break;
+ case LDAP_RES_SEARCH_ENTRY:
+ /* We have an entry. Parse it. */
+ ret = LDAP_message2entry(context, db, e, flags, entry);
+ ldap_msgfree(e);
+ break;
+ case LDAP_RES_SEARCH_RESULT:
+ /* We're probably at the end of the results. If not, abandon. */
+ parserc =
+ ldap_parse_result(HDB2LDAP(db), e, NULL, NULL, NULL,
+ NULL, NULL, 1);
+ ret = HDB_ERR_NOENTRY;
+ if (parserc != LDAP_SUCCESS
+ && parserc != LDAP_MORE_RESULTS_TO_RETURN) {
+ krb5_set_error_message(context, ret, "ldap_parse_result: %s",
+ ldap_err2string(parserc));
+ ldap_abandon_ext(HDB2LDAP(db), msgid, NULL, NULL);
+ }
+ HDBSETMSGID(db, -1);
+ break;
+ case LDAP_SERVER_DOWN:
+ ldap_msgfree(e);
+ LDAP_close(context, db);
+ HDBSETMSGID(db, -1);
+ ret = ENETDOWN;
+ break;
+ default:
+ /* Some unspecified error (timeout?). Abandon. */
+ ldap_msgfree(e);
+ ldap_abandon_ext(HDB2LDAP(db), msgid, NULL, NULL);
+ ret = HDB_ERR_NOENTRY;
+ HDBSETMSGID(db, -1);
+ break;
+ }
+ } while (rc == LDAP_RES_SEARCH_REFERENCE);
+
+ if (ret == 0) {
+ if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
+ ret = hdb_unseal_keys(context, db, entry);
+ if (ret)
+ hdb_free_entry(context, db, entry);
+ }
+ }
+
+ return ret;
+}
+
+static krb5_error_code
+LDAP_firstkey(krb5_context context, HDB *db, unsigned flags,
+ hdb_entry *entry)
+{
+ krb5_error_code ret;
+ int msgid;
+
+ ret = LDAP__connect(context, db);
+ if (ret)
+ return ret;
+
+ ret = LDAP_no_size_limit(context, HDB2LDAP(db));
+ if (ret)
+ return ret;
+
+ ret = ldap_search_ext(HDB2LDAP(db), HDB2BASE(db),
+ LDAP_SCOPE_SUBTREE,
+ "(|(objectClass=krb5Principal)(objectClass=sambaSamAccount))",
+ krb5kdcentry_attrs, 0,
+ NULL, NULL, NULL, 0, &msgid);
+ if (ret != LDAP_SUCCESS || msgid < 0)
+ return HDB_ERR_NOENTRY;
+
+ HDBSETMSGID(db, msgid);
+
+ return LDAP_seq(context, db, flags, entry);
+}
+
+static krb5_error_code
+LDAP_nextkey(krb5_context context, HDB * db, unsigned flags,
+ hdb_entry * entry)
+{
+ return LDAP_seq(context, db, flags, entry);
+}
+
+static krb5_error_code
+LDAP__connect(krb5_context context, HDB * db)
+{
+ int rc, version = LDAP_VERSION3;
+ /*
+ * Empty credentials to do a SASL bind with LDAP. Note that empty
+ * different from NULL credentials. If you provide NULL
+ * credentials instead of empty credentials you will get a SASL
+ * bind in progress message.
+ */
+ struct berval bv = { 0, "" };
+ const char *sasl_method = "EXTERNAL";
+ const char *bind_dn = NULL;
+
+ if (HDB2BINDDN(db) != NULL && HDB2BINDPW(db) != NULL) {
+ /* A bind DN was specified; use SASL SIMPLE */
+ bind_dn = HDB2BINDDN(db);
+ sasl_method = LDAP_SASL_SIMPLE;
+ bv.bv_val = HDB2BINDPW(db);
+ bv.bv_len = strlen(bv.bv_val);
+ }
+
+ if (HDB2LDAP(db)) {
+ /* connection has been opened. ping server. */
+ struct sockaddr_un addr;
+ socklen_t len = sizeof(addr);
+ int sd;
+
+ if (ldap_get_option(HDB2LDAP(db), LDAP_OPT_DESC, &sd) == 0 &&
+ getpeername(sd, (struct sockaddr *) &addr, &len) < 0) {
+ /* the other end has died. reopen. */
+ LDAP_close(context, db);
+ }
+ }
+
+ if (HDB2LDAP(db) != NULL) /* server is UP */
+ return 0;
+
+ rc = ldap_initialize(&((struct hdbldapdb *)db->hdb_db)->h_lp, HDB2URL(db));
+ if (rc != LDAP_SUCCESS) {
+ krb5_set_error_message(context, HDB_ERR_NOENTRY, "ldap_initialize: %s",
+ ldap_err2string(rc));
+ return HDB_ERR_NOENTRY;
+ }
+
+ rc = ldap_set_option(HDB2LDAP(db), LDAP_OPT_PROTOCOL_VERSION,
+ (const void *)&version);
+ if (rc != LDAP_SUCCESS) {
+ krb5_set_error_message(context, HDB_ERR_BADVERSION,
+ "ldap_set_option: %s", ldap_err2string(rc));
+ LDAP_close(context, db);
+ return HDB_ERR_BADVERSION;
+ }
+
+ if (((struct hdbldapdb *)db->hdb_db)->h_start_tls) {
+ rc = ldap_start_tls_s(HDB2LDAP(db), NULL, NULL);
+
+ if (rc != LDAP_SUCCESS) {
+ krb5_set_error_message(context, HDB_ERR_BADVERSION,
+ "ldap_start_tls_s: %s", ldap_err2string(rc));
+ LDAP_close(context, db);
+ return HDB_ERR_BADVERSION;
+ }
+ }
+
+ rc = ldap_sasl_bind_s(HDB2LDAP(db), bind_dn, sasl_method, &bv,
+ NULL, NULL, NULL);
+ if (rc != LDAP_SUCCESS) {
+ krb5_set_error_message(context, HDB_ERR_BADVERSION,
+ "ldap_sasl_bind_s: %s", ldap_err2string(rc));
+ LDAP_close(context, db);
+ return HDB_ERR_BADVERSION;
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+LDAP_open(krb5_context context, HDB * db, int flags, mode_t mode)
+{
+ /* Not the right place for this. */
+#ifdef HAVE_SIGACTION
+ struct sigaction sa;
+
+ sa.sa_flags = 0;
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+
+ sigaction(SIGPIPE, &sa, NULL);
+#else
+ signal(SIGPIPE, SIG_IGN);
+#endif /* HAVE_SIGACTION */
+
+ return LDAP__connect(context, db);
+}
+
+static krb5_error_code
+LDAP_fetch_kvno(krb5_context context, HDB * db, krb5_const_principal principal,
+ unsigned flags, krb5_kvno kvno, hdb_entry * entry)
+{
+ LDAPMessage *msg, *e;
+ krb5_error_code ret;
+
+ ret = LDAP_principal2message(context, db, principal, &msg);
+ if (ret)
+ return ret;
+
+ e = ldap_first_entry(HDB2LDAP(db), msg);
+ if (e == NULL) {
+ ret = HDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ ret = LDAP_message2entry(context, db, e, flags, entry);
+ if (ret == 0) {
+ if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
+ ret = hdb_unseal_keys(context, db, entry);
+ if (ret)
+ hdb_free_entry(context, db, entry);
+ }
+ }
+
+ out:
+ ldap_msgfree(msg);
+
+ return ret;
+}
+
+#if 0
+static krb5_error_code
+LDAP_fetch(krb5_context context, HDB * db, krb5_const_principal principal,
+ unsigned flags, hdb_entry * entry)
+{
+ return LDAP_fetch_kvno(context, db, principal,
+ flags & (~HDB_F_KVNO_SPECIFIED), 0, entry);
+}
+#endif
+
+static krb5_error_code
+LDAP_store(krb5_context context, HDB * db, unsigned flags,
+ hdb_entry * entry)
+{
+ LDAPMod **mods = NULL;
+ krb5_error_code ret;
+ const char *errfn;
+ int rc;
+ LDAPMessage *msg = NULL, *e = NULL;
+ char *dn = NULL, *name = NULL;
+ krb5_boolean is_new_entry;
+
+ if ((flags & HDB_F_PRECHECK))
+ return 0; /* we can't guarantee whether we'll be able to perform it */
+
+ ret = LDAP_principal2message(context, db, entry->principal, &msg);
+ if (ret == 0)
+ e = ldap_first_entry(HDB2LDAP(db), msg);
+
+ ret = krb5_unparse_name(context, entry->principal, &name);
+ if (ret) {
+ free(name);
+ return ret;
+ }
+
+ ret = hdb_seal_keys(context, db, entry);
+ if (ret)
+ goto out;
+
+ /* turn new entry into LDAPMod array */
+ ret = LDAP_entry2mods(context, db, entry, e, &mods, &is_new_entry);
+ if (ret)
+ goto out;
+
+ if (e == NULL) {
+ ret = asprintf(&dn, "krb5PrincipalName=%s,%s", name, HDB2CREATE(db));
+ if (ret < 0) {
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "asprintf: out of memory");
+ goto out;
+ }
+ } else if ((flags & HDB_F_REPLACE) || (is_new_entry)) {
+ /* Entry exists, and we're allowed to replace it. */
+ /* Entry may also exist but need to be modified to create a new principal. */
+ dn = ldap_get_dn(HDB2LDAP(db), e);
+ } else {
+ /* Entry exists, but we're not allowed to replace it. Bail. */
+ ret = HDB_ERR_EXISTS;
+ goto out;
+ }
+
+ /* write entry into directory */
+ if (e == NULL) {
+ /* didn't exist before */
+ rc = ldap_add_ext_s(HDB2LDAP(db), dn, mods, NULL, NULL );
+ errfn = "ldap_add_ext_s";
+ } else {
+ /* already existed, send deltas only */
+ rc = ldap_modify_ext_s(HDB2LDAP(db), dn, mods, NULL, NULL );
+ errfn = "ldap_modify_ext_s";
+ }
+
+ if (check_ldap(context, db, rc)) {
+ char *ld_error = NULL;
+ ldap_get_option(HDB2LDAP(db), LDAP_OPT_ERROR_STRING,
+ &ld_error);
+ ret = HDB_ERR_CANT_LOCK_DB;
+ krb5_set_error_message(context, ret, "%s: %s (DN=%s) %s: %s",
+ errfn, name, dn, ldap_err2string(rc), ld_error);
+ } else
+ ret = 0;
+
+ out:
+ /* free stuff */
+ if (dn)
+ free(dn);
+ if (msg)
+ ldap_msgfree(msg);
+ if (mods)
+ ldap_mods_free(mods, 1);
+ if (name)
+ free(name);
+
+ return ret;
+}
+
+static krb5_error_code
+LDAP_remove(krb5_context context, HDB *db,
+ unsigned flags, krb5_const_principal principal)
+{
+ krb5_error_code ret;
+ LDAPMessage *msg, *e;
+ char *dn = NULL;
+ int rc, limit = LDAP_NO_LIMIT;
+
+ if ((flags & HDB_F_PRECHECK))
+ return 0; /* we can't guarantee whether we'll be able to perform it */
+
+ ret = LDAP_principal2message(context, db, principal, &msg);
+ if (ret)
+ goto out;
+
+ e = ldap_first_entry(HDB2LDAP(db), msg);
+ if (e == NULL) {
+ ret = HDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ dn = ldap_get_dn(HDB2LDAP(db), e);
+ if (dn == NULL) {
+ ret = HDB_ERR_NOENTRY;
+ goto out;
+ }
+
+ rc = ldap_set_option(HDB2LDAP(db), LDAP_OPT_SIZELIMIT, (const void *)&limit);
+ if (rc != LDAP_SUCCESS) {
+ ret = HDB_ERR_BADVERSION;
+ krb5_set_error_message(context, ret, "ldap_set_option: %s",
+ ldap_err2string(rc));
+ goto out;
+ }
+
+ /* HACK: This should check if we need to delete the object or just some attributes */
+
+ rc = ldap_delete_ext_s(HDB2LDAP(db), dn, NULL, NULL );
+ if (check_ldap(context, db, rc)) {
+ ret = HDB_ERR_CANT_LOCK_DB;
+ krb5_set_error_message(context, ret, "ldap_delete_ext_s: %s",
+ ldap_err2string(rc));
+ } else
+ ret = 0;
+
+ out:
+ if (dn != NULL)
+ free(dn);
+ if (msg != NULL)
+ ldap_msgfree(msg);
+
+ return ret;
+}
+
+static krb5_error_code
+LDAP_destroy(krb5_context context, HDB * db)
+{
+ krb5_error_code ret;
+
+ LDAP_close(context, db);
+
+ ret = hdb_clear_master_key(context, db);
+ if (HDB2BASE(db))
+ free(HDB2BASE(db));
+ if (HDB2CREATE(db))
+ free(HDB2CREATE(db));
+ if (HDB2URL(db))
+ free(HDB2URL(db));
+ krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
+ if (db->hdb_name)
+ free(db->hdb_name);
+ free(db->hdb_db);
+ free(db);
+
+ return ret;
+}
+
+static krb5_error_code
+LDAP_set_sync(krb5_context context, HDB * db, int on)
+{
+ (void)on;
+ return 0;
+}
+
+static krb5_error_code
+hdb_ldap_common(krb5_context context,
+ HDB ** db,
+ const char *search_base,
+ const char *url)
+{
+ struct hdbldapdb *h;
+ const char *create_base = NULL;
+ const char *ldap_secret_file = NULL;
+
+ if (url == NULL || url[0] == '\0') {
+ const char *p;
+ p = krb5_config_get_string(context, NULL, "kdc",
+ "hdb-ldap-url", NULL);
+ if (p == NULL)
+ p = default_ldap_url;
+
+ url = p;
+ }
+
+ if (search_base == NULL || search_base[0] == '\0') {
+ krb5_set_error_message(context, ENOMEM, "ldap search base not configured");
+ return ENOMEM; /* XXX */
+ }
+
+ if (structural_object == NULL) {
+ const char *p;
+
+ p = krb5_config_get_string(context, NULL, "kdc",
+ "hdb-ldap-structural-object", NULL);
+ if (p == NULL)
+ p = default_structural_object;
+ structural_object = strdup(p);
+ if (structural_object == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ }
+
+ samba_forwardable =
+ krb5_config_get_bool_default(context, NULL, TRUE,
+ "kdc", "hdb-samba-forwardable", NULL);
+
+ *db = calloc(1, sizeof(**db));
+ if (*db == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ memset(*db, 0, sizeof(**db));
+
+ h = calloc(1, sizeof(*h));
+ if (h == NULL) {
+ free(*db);
+ *db = NULL;
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ (*db)->hdb_db = h;
+
+ /* XXX */
+ if (asprintf(&(*db)->hdb_name, "ldap:%s", search_base) == -1) {
+ LDAP_destroy(context, *db);
+ *db = NULL;
+ krb5_set_error_message(context, ENOMEM, "strdup: out of memory");
+ return ENOMEM;
+ }
+
+ h->h_url = strdup(url);
+ h->h_base = strdup(search_base);
+ if (h->h_url == NULL || h->h_base == NULL) {
+ LDAP_destroy(context, *db);
+ *db = NULL;
+ krb5_set_error_message(context, ENOMEM, "strdup: out of memory");
+ return ENOMEM;
+ }
+
+ ldap_secret_file = krb5_config_get_string(context, NULL, "kdc",
+ "hdb-ldap-secret-file", NULL);
+ if (ldap_secret_file != NULL) {
+ krb5_config_binding *tmp;
+ krb5_error_code ret;
+ const char *p;
+
+ ret = krb5_config_parse_file(context, ldap_secret_file, &tmp);
+ if (ret)
+ return ret;
+
+ p = krb5_config_get_string(context, tmp, "kdc",
+ "hdb-ldap-bind-dn", NULL);
+ if (p != NULL)
+ h->h_bind_dn = strdup(p);
+
+ p = krb5_config_get_string(context, tmp, "kdc",
+ "hdb-ldap-bind-password", NULL);
+ if (p != NULL)
+ h->h_bind_password = strdup(p);
+
+ krb5_config_file_free(context, tmp);
+ }
+
+ h->h_start_tls =
+ krb5_config_get_bool_default(context, NULL, FALSE,
+ "kdc", "hdb-ldap-start-tls", NULL);
+
+ create_base = krb5_config_get_string(context, NULL, "kdc",
+ "hdb-ldap-create-base", NULL);
+ if (create_base == NULL)
+ create_base = h->h_base;
+
+ h->h_createbase = strdup(create_base);
+ if (h->h_createbase == NULL) {
+ LDAP_destroy(context, *db);
+ *db = NULL;
+ krb5_set_error_message(context, ENOMEM, "strdup: out of memory");
+ return ENOMEM;
+ }
+
+ (*db)->hdb_master_key_set = 0;
+ (*db)->hdb_openp = 0;
+ (*db)->hdb_capability_flags = HDB_CAP_F_SHARED_DIRECTORY;
+ (*db)->hdb_open = LDAP_open;
+ (*db)->hdb_close = LDAP_close;
+ (*db)->hdb_fetch_kvno = LDAP_fetch_kvno;
+ (*db)->hdb_store = LDAP_store;
+ (*db)->hdb_remove = LDAP_remove;
+ (*db)->hdb_firstkey = LDAP_firstkey;
+ (*db)->hdb_nextkey = LDAP_nextkey;
+ (*db)->hdb_lock = LDAP_lock;
+ (*db)->hdb_unlock = LDAP_unlock;
+ (*db)->hdb_rename = NULL;
+ (*db)->hdb__get = NULL;
+ (*db)->hdb__put = NULL;
+ (*db)->hdb__del = NULL;
+ (*db)->hdb_destroy = LDAP_destroy;
+ (*db)->hdb_set_sync = LDAP_set_sync;
+
+ return 0;
+}
+
+#ifdef OPENLDAP_MODULE
+static
+#endif
+
+krb5_error_code
+hdb_ldap_create(krb5_context context, HDB ** db, const char *arg)
+{
+ return hdb_ldap_common(context, db, arg, NULL);
+}
+
+#ifdef OPENLDAP_MODULE
+static
+#endif
+
+krb5_error_code
+hdb_ldapi_create(krb5_context context, HDB ** db, const char *arg)
+{
+ krb5_error_code ret;
+ char *search_base, *p;
+
+ if (asprintf(&p, "ldapi:%s", arg) == -1 || p == NULL) {
+ *db = NULL;
+ krb5_set_error_message(context, ENOMEM, "out of memory");
+ return ENOMEM;
+ }
+ search_base = strchr(p + strlen("ldapi://"), ':');
+ if (search_base == NULL) {
+ *db = NULL;
+ krb5_set_error_message(context, HDB_ERR_BADVERSION,
+ "search base missing");
+ return HDB_ERR_BADVERSION;
+ }
+ *search_base = '\0';
+ search_base++;
+
+ ret = hdb_ldap_common(context, db, search_base, p);
+ free(p);
+ return ret;
+}
+
+#ifdef OPENLDAP_MODULE
+static krb5_error_code
+init(krb5_context context, void **ctx)
+{
+ *ctx = NULL;
+ return 0;
+}
+
+static void
+fini(void *ctx)
+{
+}
+
+struct hdb_method hdb_ldap_interface = {
+ HDB_INTERFACE_VERSION,
+ init,
+ fini,
+ 0 /*is_file_based*/, 0 /*can_taste*/,
+ "ldap",
+ hdb_ldap_create
+};
+
+struct hdb_method hdb_ldapi_interface = {
+ HDB_INTERFACE_VERSION,
+ init,
+ fini,
+ 0 /*is_file_based*/, 0 /*can_taste*/,
+ "ldapi",
+ hdb_ldapi_create
+};
+#endif
+
+#endif /* OPENLDAP */
diff --git a/third_party/heimdal/lib/hdb/hdb-mdb.c b/third_party/heimdal/lib/hdb/hdb-mdb.c
new file mode 100644
index 0000000..6aa5201
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/hdb-mdb.c
@@ -0,0 +1,692 @@
+/*
+ * Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * Copyright (c) 2011 - Howard Chu, Symas Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "hdb_locl.h"
+
+#if HAVE_LMDB
+
+/* LMDB */
+
+#include <lmdb.h>
+
+#define KILO 1024
+
+#define E(sym, kret) case sym: ret = kret; ename = #sym; break
+
+/* Note: calls krb5_set_error_message() */
+static krb5_error_code
+mdb2krb5_code(krb5_context context, int code)
+{
+ krb5_error_code ret = 0;
+ const char *ename = "UNKNOWN";
+ const char *estr = mdb_strerror(code);
+
+ switch (code) {
+ case MDB_SUCCESS: return 0;
+ E(MDB_KEYEXIST, HDB_ERR_EXISTS);
+ E(MDB_NOTFOUND, HDB_ERR_NOENTRY);
+ E(MDB_PAGE_NOTFOUND, HDB_ERR_UK_SERROR);
+ E(MDB_CORRUPTED, HDB_ERR_UK_SERROR);
+ E(MDB_PANIC, HDB_ERR_UK_SERROR);
+ E(MDB_VERSION_MISMATCH, HDB_ERR_UK_SERROR);
+ E(MDB_INVALID, HDB_ERR_UK_SERROR);
+ E(MDB_MAP_FULL, HDB_ERR_UK_SERROR);
+ E(MDB_DBS_FULL, HDB_ERR_UK_SERROR);
+ E(MDB_READERS_FULL, HDB_ERR_UK_SERROR);
+ E(MDB_TLS_FULL, HDB_ERR_UK_SERROR);
+ E(MDB_TXN_FULL, HDB_ERR_UK_SERROR);
+ E(MDB_CURSOR_FULL, HDB_ERR_UK_SERROR);
+ E(MDB_PAGE_FULL, HDB_ERR_UK_SERROR);
+ E(MDB_MAP_RESIZED, HDB_ERR_UK_SERROR);
+ E(MDB_INCOMPATIBLE, HDB_ERR_UK_SERROR);
+ E(MDB_BAD_RSLOT, HDB_ERR_UK_SERROR);
+ E(MDB_BAD_TXN, HDB_ERR_UK_SERROR);
+ E(MDB_BAD_VALSIZE, HDB_ERR_UK_SERROR);
+ E(MDB_BAD_DBI, HDB_ERR_UK_SERROR);
+ default:
+ if (code > 0 && code < 100)
+ ret = code;
+ else
+ ret = HDB_ERR_UK_SERROR;
+ break;
+ }
+ if (ret)
+ krb5_set_error_message(context, ret, "MDB error %s (%d): %s",
+ ename, code, estr);
+ return ret;
+}
+
+typedef struct mdb_info {
+ MDB_env *e;
+ MDB_txn *t;
+ MDB_dbi d;
+ MDB_cursor *c;
+ int oflags;
+ mode_t mode;
+ size_t mapsize;
+ unsigned int in_tx:1;
+} mdb_info;
+
+/* See below */
+struct keep_it_open {
+ char *path;
+ MDB_env *env;
+ MDB_dbi d;
+ unsigned int oflags;
+ size_t refs;
+ size_t mapsize;
+ unsigned int valid:1;
+ struct keep_it_open *next;
+} *keep_them_open;
+HEIMDAL_MUTEX keep_them_open_lock = HEIMDAL_MUTEX_INITIALIZER;
+
+/*
+ * On Unix LMDB uses fcntl() byte-range locks, and unlike SQLite3 (which also
+ * uses fcntl() byte-range locks) LMDB takes no precautions to avoid early
+ * first-close()s that cause other threads' locks to get dropped. No, LMDB
+ * requires the caller to take such precautions. For us that means opening one
+ * mdb env per-{HDB, mode} (where mode is read-write or read-only), never
+ * closing it, and sharing it with all threads.
+ *
+ * Sharing an MDB_env * across multiple threads is documented to be safe, and
+ * internally LMDB uses pread(2), pwrite(2), and mmap(2) for I/O, using
+ * read(2)/write(2) only in the DB copy routines that we don't use.
+ *
+ * On WIN32 we don't have to do any of this, however, to avoid ifdef spaghetti,
+ * we share this code on all platforms, even if it isn't strictly needed.
+ *
+ * Also, one must call mdb_open() (aka mdb_dbi_open()) only once per call to
+ * mdb_env_open() and per B-tree. We only use one B-tree in each LMDB: the
+ * main one.
+ *
+ * On success this outputs an `MDB_env *' (the handle for the LMDB) and an
+ * `MDB_dbi' (the handle for the main B-tree in the LMDB).
+ *
+ * ALSO, LMDB requires that we re-open the `MDB_env' when the database grows
+ * larger than the mmap size. We handle this by finding in `keep_them_open'
+ * the env we already have, marking it unusable, and the finding some other
+ * better one or opening a new one and adding it to the list.
+ */
+static krb5_error_code
+my_mdb_env_create_and_open(krb5_context context,
+ mdb_info *mi,
+ const char *path,
+ int mapfull)
+{
+ struct keep_it_open *p, *n;
+ MDB_txn *txn = NULL;
+ unsigned int flags = MDB_NOSUBDIR;
+ struct stat st;
+ size_t mapsize = 0;
+ int max_readers;
+ int locked = 0;
+ int code = 0;
+
+ mi->oflags &= O_ACCMODE;
+ flags |= (mi->oflags == O_RDONLY) ? MDB_RDONLY : 0;
+
+ mi->e = NULL;
+
+ /*
+ * Allocate a new object, in case we don't already have one in
+ * `keep_them_open'; if we don't need it, we'll free it. This way we do
+ * some of the work of creating one while not holding a lock.
+ */
+ if ((n = calloc(1, sizeof(*n))) == NULL ||
+ (n->path = strdup(path)) == NULL) {
+ free(n);
+ return krb5_enomem(context);
+ }
+ n->oflags = mi->oflags;
+
+ max_readers = krb5_config_get_int_default(context, NULL, 0, "kdc",
+ "hdb-mdb-maxreaders", NULL);
+ mapsize = krb5_config_get_int_default(context, NULL, 0, "kdc", "hdb-mdb-mapsize",
+ NULL);
+ if (mapsize > INT_MAX)
+ mapsize = 0;
+
+ memset(&st, 0, sizeof(st));
+ if (stat(path, &st) == 0 && st.st_size > mapsize * KILO)
+ mapsize += (st.st_size + (st.st_size >> 2)) / KILO;
+ if (mapsize < 100 * 1024)
+ mapsize = 100 * 1024; /* 100MB */
+ if (mapsize < mi->mapsize)
+ mapsize = mi->mapsize;
+ if (mapfull)
+ mapsize += 10 * 1024;
+ if ((code = mdb_env_create(&n->env)) ||
+ (max_readers && (code = mdb_env_set_maxreaders(n->env, max_readers))))
+ goto out;
+
+ /* Look for an existing env */
+ HEIMDAL_MUTEX_lock(&keep_them_open_lock);
+ locked = 1;
+ for (p = keep_them_open; p; p = p->next) {
+ if (strcmp(p->path, path) != 0)
+ continue;
+ if (p->mapsize > mapsize)
+ /* Always increase mapsize */
+ mapsize = p->mapsize + (p->mapsize >> 1);
+ if (!p->valid || p->oflags != mi->oflags)
+ continue;
+ /* Found one; output it and get out */
+ mi->e = p->env;
+ mi->d = p->d;
+ p->refs++;
+ goto out;
+ }
+
+ /* Did not find one, so open and add this one to the list */
+
+ /* Open the LMDB itself */
+ n->refs = 1;
+ n->valid = 1;
+ krb5_debug(context, 5, "Opening HDB LMDB %s with mapsize %llu",
+ path, (unsigned long long)mapsize * KILO);
+ code = mdb_env_set_mapsize(n->env, mapsize * KILO);
+ if (code == 0)
+ code = mdb_env_open(n->env, path, flags, mi->mode);
+ if (code == 0)
+ /* Open a transaction so we can resolve the main B-tree */
+ code = mdb_txn_begin(n->env, NULL, MDB_RDONLY, &txn);
+ if (code == 0)
+ /* Resolve the main B-tree */
+ code = mdb_open(txn, NULL, 0, &n->d);
+ if (code)
+ goto out;
+
+ /* Successfully opened the LMDB; output the two handles */
+ mi->mapsize = n->mapsize = mapsize;
+ mi->e = n->env;
+ mi->d = n->d;
+
+ /* Add this keep_it_open to the front of the list */
+ n->next = keep_them_open;
+ keep_them_open = n;
+ n = NULL;
+
+out:
+ if (locked)
+ HEIMDAL_MUTEX_unlock(&keep_them_open_lock);
+ if (n) {
+ if (n->env)
+ mdb_env_close(n->env);
+ free(n->path);
+ free(n);
+ }
+ (void) mdb_txn_commit(txn); /* Safe when `txn == NULL' */
+ return mdb2krb5_code(context, code);
+}
+
+static void
+my_mdb_env_close(krb5_context context,
+ const char *db_name,
+ MDB_env **envp)
+{
+ struct keep_it_open **prev;
+ struct keep_it_open *p, *old;
+ size_t refs_seen = 0;
+ size_t slen = strlen(db_name);
+ MDB_env *env = *envp;
+
+ if (env == NULL)
+ return;
+
+ HEIMDAL_MUTEX_lock(&keep_them_open_lock);
+ for (p = keep_them_open; p; p = p->next) {
+ /*
+ * We can have multiple open ones and we need to know if this is the
+ * last one, so we can't break out early.
+ */
+ if (p->env == env)
+ refs_seen += (--(p->refs));
+ else if (strncmp(db_name, p->path, slen) == 0 &&
+ strcmp(p->path + slen, ".mdb") == 0)
+ refs_seen += p->refs;
+ }
+ krb5_debug(context, 6, "Closing HDB LMDB %s / %p; refs %llu", db_name, env,
+ (unsigned long long)refs_seen);
+ prev = &keep_them_open;
+ for (p = keep_them_open; !refs_seen && p; ) {
+ /* We're the last close */
+ if (p->refs ||
+ strncmp(db_name, p->path, slen) != 0 ||
+ strcmp(p->path + slen, ".mdb") != 0) {
+
+ /* Not us; this keep_it_open stays */
+ prev = &p->next;
+ p = p->next;
+ continue;
+ }
+
+ /* Close and remove this one */
+ krb5_debug(context, 6, "Closing HDB LMDB %s (mapsize was %llu)",
+ db_name, (unsigned long long)p->mapsize * KILO);
+ old = p;
+ *prev = (p = p->next); /* prev stays */
+ mdb_env_close(old->env);
+ free(old->path);
+ free(old);
+ }
+ HEIMDAL_MUTEX_unlock(&keep_them_open_lock);
+}
+
+/*
+ * This is a wrapper around my_mdb_env_create_and_open(). It may close an
+ * existing MDB_env in mi->e if it's there. If we need to reopen because the
+ * MDB grew too much, then we call this.
+ */
+static krb5_error_code
+my_reopen_mdb(krb5_context context, HDB *db, int mapfull)
+{
+ mdb_info *mi = (mdb_info *)db->hdb_db;
+ char *fn;
+ krb5_error_code ret = 0;
+
+ /* No-op if we don't have an open one */
+ my_mdb_env_close(context, db->hdb_name, &mi->e);
+ if (asprintf(&fn, "%s.mdb", db->hdb_name) == -1)
+ ret = krb5_enomem(context);
+ if (ret == 0)
+ ret = my_mdb_env_create_and_open(context, mi, fn, mapfull);
+ free(fn);
+ return ret;
+}
+
+static krb5_error_code
+DB_close(krb5_context context, HDB *db)
+{
+ mdb_info *mi = (mdb_info *)db->hdb_db;
+
+ mdb_cursor_close(mi->c);
+ mdb_txn_abort(mi->t);
+ my_mdb_env_close(context, db->hdb_name, &mi->e);
+ mi->c = 0;
+ mi->t = 0;
+ mi->e = 0;
+ return 0;
+}
+
+static krb5_error_code
+DB_destroy(krb5_context context, HDB *db)
+{
+ krb5_error_code ret;
+
+ ret = hdb_clear_master_key(context, db);
+ krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
+ free(db->hdb_name);
+ free(db->hdb_db);
+ free(db);
+ return ret;
+}
+
+static krb5_error_code
+DB_set_sync(krb5_context context, HDB *db, int on)
+{
+ mdb_info *mi = (mdb_info *)db->hdb_db;
+
+ mdb_env_set_flags(mi->e, MDB_NOSYNC, !on);
+ return mdb_env_sync(mi->e, 0);
+}
+
+static krb5_error_code
+DB_lock(krb5_context context, HDB *db, int operation)
+{
+ db->lock_count++;
+ return 0;
+}
+
+static krb5_error_code
+DB_unlock(krb5_context context, HDB *db)
+{
+ if (db->lock_count > 1) {
+ db->lock_count--;
+ return 0;
+ }
+ heim_assert(db->lock_count == 1, "HDB lock/unlock sequence does not match");
+ db->lock_count--;
+ return 0;
+}
+
+
+static krb5_error_code
+DB_seq(krb5_context context, HDB *db,
+ unsigned flags, hdb_entry *entry, int flag)
+{
+ mdb_info *mi = db->hdb_db;
+ MDB_val key, value;
+ krb5_data key_data, data;
+ int code;
+
+ /*
+ * No need to worry about MDB_MAP_FULL when we're scanning the DB since we
+ * have snapshot semantics, and any DB growth from other transactions
+ * should not affect us.
+ */
+ key.mv_size = 0;
+ value.mv_size = 0;
+ code = mdb_cursor_get(mi->c, &key, &value, flag);
+ if (code)
+ return mdb2krb5_code(context, code);
+
+ key_data.data = key.mv_data;
+ key_data.length = key.mv_size;
+ data.data = value.mv_data;
+ data.length = value.mv_size;
+ memset(entry, 0, sizeof(*entry));
+ if (hdb_value2entry(context, &data, entry))
+ return DB_seq(context, db, flags, entry, MDB_NEXT);
+ if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
+ code = hdb_unseal_keys (context, db, entry);
+ if (code)
+ hdb_free_entry (context, db, entry);
+ }
+ if (entry->principal == NULL) {
+ entry->principal = malloc(sizeof(*entry->principal));
+ if (entry->principal == NULL) {
+ hdb_free_entry (context, db, entry);
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ } else {
+ hdb_key2principal(context, &key_data, entry->principal);
+ }
+ }
+ return 0;
+}
+
+
+static krb5_error_code
+DB_firstkey(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
+{
+ krb5_error_code ret = 0;
+ mdb_info *mi = db->hdb_db;
+ int tries = 3;
+ int code = 0;
+
+ /* Always start with a fresh cursor to pick up latest DB state */
+
+ do {
+ if (mi->t)
+ mdb_txn_abort(mi->t);
+ mi->t = NULL;
+ if (code)
+ code = my_reopen_mdb(context, db, 1);
+ if (code == 0)
+ code = mdb_txn_begin(mi->e, NULL, MDB_RDONLY, &mi->t);
+ if (code == 0)
+ code = mdb_cursor_open(mi->t, mi->d, &mi->c);
+ if (code == 0) {
+ ret = DB_seq(context, db, flags, entry, MDB_FIRST);
+ break;
+ }
+ } while (code == MDB_MAP_FULL && --tries > 0);
+
+ if (code || ret) {
+ mdb_txn_abort(mi->t);
+ mi->t = NULL;
+ }
+ return ret ? ret : mdb2krb5_code(context, code);
+}
+
+
+static krb5_error_code
+DB_nextkey(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
+{
+ return DB_seq(context, db, flags, entry, MDB_NEXT);
+}
+
+static krb5_error_code
+DB_rename(krb5_context context, HDB *db, const char *new_name)
+{
+ int ret;
+ char *old, *new;
+
+ if (strncmp(new_name, "mdb:", sizeof("mdb:") - 1) == 0)
+ new_name += sizeof("mdb:") - 1;
+ else if (strncmp(new_name, "lmdb:", sizeof("lmdb:") - 1) == 0)
+ new_name += sizeof("lmdb:") - 1;
+ if (asprintf(&old, "%s.mdb", db->hdb_name) == -1)
+ return ENOMEM;
+ if (asprintf(&new, "%s.mdb", new_name) == -1) {
+ free(old);
+ return ENOMEM;
+ }
+ ret = rename(old, new);
+ free(old);
+ free(new);
+ if(ret)
+ return errno;
+
+ free(db->hdb_name);
+ db->hdb_name = strdup(new_name);
+ return 0;
+}
+
+static krb5_error_code
+DB__get(krb5_context context, HDB *db, krb5_data key, krb5_data *reply)
+{
+ mdb_info *mi = (mdb_info*)db->hdb_db;
+ MDB_txn *txn = NULL;
+ MDB_val k, v;
+ int tries = 3;
+ int code = 0;
+
+ k.mv_data = key.data;
+ k.mv_size = key.length;
+
+ do {
+ if (txn) {
+ mdb_txn_abort(txn);
+ txn = NULL;
+ }
+ if (code)
+ code = my_reopen_mdb(context, db, 1);
+ if (code == 0)
+ code = mdb_txn_begin(mi->e, NULL, MDB_RDONLY, &txn);
+ if (code == 0)
+ code = mdb_get(txn, mi->d, &k, &v);
+ if (code == 0)
+ krb5_data_copy(reply, v.mv_data, v.mv_size);
+ } while (code == MDB_MAP_FULL && --tries > 0);
+
+ if (code)
+ mdb_txn_abort(txn);
+ else
+ (void) mdb_txn_commit(txn); /* Empty transaction? -> commit */
+ return mdb2krb5_code(context, code);
+}
+
+static krb5_error_code
+DB__put(krb5_context context, HDB *db, int replace,
+ krb5_data key, krb5_data value)
+{
+ mdb_info *mi = (mdb_info*)db->hdb_db;
+ MDB_txn *txn = NULL;
+ MDB_val k, v;
+ int tries = 3;
+ int code = 0;
+
+ k.mv_data = key.data;
+ k.mv_size = key.length;
+ v.mv_data = value.data;
+ v.mv_size = value.length;
+
+ do {
+ if (txn) {
+ mdb_txn_abort(txn);
+ txn = NULL;
+ }
+ if (code)
+ code = my_reopen_mdb(context, db, 1);
+ if (code == 0)
+ code = mdb_txn_begin(mi->e, NULL, 0, &txn);
+ if (code == 0)
+ code = mdb_put(txn, mi->d, &k, &v, replace ? 0 : MDB_NOOVERWRITE);
+ if (code == 0) {
+ /*
+ * No need to call mdb_env_sync(); it's done automatically if
+ * MDB_NOSYNC is not set.
+ */
+ code = mdb_txn_commit(txn);
+ txn = NULL;
+ }
+ } while (code == MDB_MAP_FULL && --tries > 0);
+ if (txn)
+ mdb_txn_abort(txn);
+ return mdb2krb5_code(context, code);
+}
+
+static krb5_error_code
+DB__del(krb5_context context, HDB *db, krb5_data key)
+{
+ mdb_info *mi = (mdb_info*)db->hdb_db;
+ MDB_txn *txn = NULL;
+ MDB_val k;
+ int tries = 3;
+ int code = 0;
+
+ k.mv_data = key.data;
+ k.mv_size = key.length;
+
+ do {
+ if (txn) {
+ mdb_txn_abort(txn);
+ txn = NULL;
+ }
+ if (code)
+ code = my_reopen_mdb(context, db, 1);
+ if (code == 0)
+ code = mdb_txn_begin(mi->e, NULL, 0, &txn);
+ if (code == 0)
+ code = mdb_del(txn, mi->d, &k, NULL);
+ if (code == 0) {
+ /*
+ * No need to call mdb_env_sync(); it's done automatically if
+ * MDB_NOSYNC is not set.
+ */
+ code = mdb_txn_commit(txn);
+ txn = NULL;
+ }
+ } while (code == MDB_MAP_FULL && --tries > 0);
+
+ if (txn)
+ mdb_txn_abort(txn);
+ return mdb2krb5_code(context, code);
+}
+
+static krb5_error_code
+DB_open(krb5_context context, HDB *db, int oflags, mode_t mode)
+{
+ mdb_info *mi = (mdb_info *)db->hdb_db;
+ krb5_error_code ret;
+
+ mi->e = NULL;
+ mi->mode = mode;
+ mi->oflags = oflags & O_ACCMODE;
+ ret = my_reopen_mdb(context, db, 0);
+ if (ret) {
+ krb5_prepend_error_message(context, ret, "opening %s:", db->hdb_name);
+ return ret;
+ }
+
+ if ((oflags & O_ACCMODE) == O_RDONLY) {
+ ret = hdb_check_db_format(context, db);
+ /*
+ * Dubious: if the DB is not initialized, shouldn't we tell the
+ * caller??
+ */
+ if (ret == HDB_ERR_NOENTRY)
+ return 0;
+ } else {
+ /* hdb_init_db() calls hdb_check_db_format() */
+ ret = hdb_init_db(context, db);
+ }
+ if (ret) {
+ DB_close(context, db);
+ krb5_set_error_message(context, ret, "hdb_open: failed %s database %s",
+ (oflags & O_ACCMODE) == O_RDONLY ?
+ "checking format of" : "initialize",
+ db->hdb_name);
+ }
+
+ return ret;
+}
+
+krb5_error_code
+hdb_mdb_create(krb5_context context, HDB **db,
+ const char *filename)
+{
+ *db = calloc(1, sizeof(**db));
+ if (*db == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ (*db)->hdb_db = calloc(1, sizeof(mdb_info));
+ if ((*db)->hdb_db == NULL) {
+ free(*db);
+ *db = NULL;
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ (*db)->hdb_name = strdup(filename);
+ if ((*db)->hdb_name == NULL) {
+ free((*db)->hdb_db);
+ free(*db);
+ *db = NULL;
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ (*db)->hdb_master_key_set = 0;
+ (*db)->hdb_openp = 0;
+ (*db)->hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL;
+ (*db)->hdb_open = DB_open;
+ (*db)->hdb_close = DB_close;
+ (*db)->hdb_fetch_kvno = _hdb_fetch_kvno;
+ (*db)->hdb_store = _hdb_store;
+ (*db)->hdb_remove = _hdb_remove;
+ (*db)->hdb_firstkey = DB_firstkey;
+ (*db)->hdb_nextkey= DB_nextkey;
+ (*db)->hdb_lock = DB_lock;
+ (*db)->hdb_unlock = DB_unlock;
+ (*db)->hdb_rename = DB_rename;
+ (*db)->hdb__get = DB__get;
+ (*db)->hdb__put = DB__put;
+ (*db)->hdb__del = DB__del;
+ (*db)->hdb_destroy = DB_destroy;
+ (*db)->hdb_set_sync = DB_set_sync;
+ return 0;
+}
+#endif /* HAVE_LMDB */
diff --git a/third_party/heimdal/lib/hdb/hdb-mitdb.c b/third_party/heimdal/lib/hdb/hdb-mitdb.c
new file mode 100644
index 0000000..7436f39
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/hdb-mitdb.c
@@ -0,0 +1,1500 @@
+/*
+ * Copyright (c) 1997 - 2017 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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.
+ */
+
+#define KRB5_KDB_DISALLOW_POSTDATED 0x00000001
+#define KRB5_KDB_DISALLOW_FORWARDABLE 0x00000002
+#define KRB5_KDB_DISALLOW_TGT_BASED 0x00000004
+#define KRB5_KDB_DISALLOW_RENEWABLE 0x00000008
+#define KRB5_KDB_DISALLOW_PROXIABLE 0x00000010
+#define KRB5_KDB_DISALLOW_DUP_SKEY 0x00000020
+#define KRB5_KDB_DISALLOW_ALL_TIX 0x00000040
+#define KRB5_KDB_REQUIRES_PRE_AUTH 0x00000080
+#define KRB5_KDB_REQUIRES_HW_AUTH 0x00000100
+#define KRB5_KDB_REQUIRES_PWCHANGE 0x00000200
+#define KRB5_KDB_DISALLOW_SVR 0x00001000
+#define KRB5_KDB_PWCHANGE_SERVICE 0x00002000
+#define KRB5_KDB_SUPPORT_DESMD5 0x00004000
+#define KRB5_KDB_NEW_PRINC 0x00008000
+
+/*
+
+key: krb5_unparse_name + NUL
+
+ 16: baselength
+ 32: attributes
+ 32: max time
+ 32: max renewable time
+ 32: client expire
+ 32: passwd expire
+ 32: last successful passwd
+ 32: last failed attempt
+ 32: num of failed attempts
+ 16: num tl data
+ 16: num data data
+ 16: principal length
+ length: principal
+ for num tl data times
+ 16: tl data type
+ 16: tl data length
+ length: length
+ for num key data times
+ 16: version (num keyblocks)
+ 16: kvno
+ for version times:
+ 16: type
+ 16: length
+ length: keydata
+
+
+key_data_contents[0]
+
+ int16: length
+ read-of-data: key-encrypted, key-usage 0, master-key
+
+salt:
+ version2 = salt in key_data->key_data_contents[1]
+ else default salt.
+
+*/
+
+#include "hdb_locl.h"
+
+typedef struct MITDB {
+ HDB db; /* Generic */
+ int do_sync; /* MITDB-specific */
+} MITDB;
+
+static void
+attr_to_flags(unsigned attr, HDBFlags *flags)
+{
+ flags->postdate = !(attr & KRB5_KDB_DISALLOW_POSTDATED);
+ flags->forwardable = !(attr & KRB5_KDB_DISALLOW_FORWARDABLE);
+ flags->initial = !!(attr & KRB5_KDB_DISALLOW_TGT_BASED);
+ flags->renewable = !(attr & KRB5_KDB_DISALLOW_RENEWABLE);
+ flags->proxiable = !(attr & KRB5_KDB_DISALLOW_PROXIABLE);
+ /* DUP_SKEY */
+ flags->invalid = !!(attr & KRB5_KDB_DISALLOW_ALL_TIX);
+ flags->require_preauth = !!(attr & KRB5_KDB_REQUIRES_PRE_AUTH);
+ flags->require_hwauth = !!(attr & KRB5_KDB_REQUIRES_HW_AUTH);
+ flags->require_pwchange = !!(attr & KRB5_KDB_REQUIRES_PWCHANGE);
+ flags->server = !(attr & KRB5_KDB_DISALLOW_SVR);
+ flags->change_pw = !!(attr & KRB5_KDB_PWCHANGE_SERVICE);
+ flags->client = 1; /* XXX */
+}
+
+#define KDB_V1_BASE_LENGTH 38
+
+#define CHECK(x) do { if ((x)) goto out; } while(0)
+
+#ifdef HAVE_DB1
+static krb5_error_code
+mdb_principal2key(krb5_context context,
+ krb5_const_principal principal,
+ krb5_data *key)
+{
+ krb5_error_code ret;
+ char *str;
+
+ ret = krb5_unparse_name(context, principal, &str);
+ if (ret)
+ return ret;
+ key->data = str;
+ key->length = strlen(str) + 1;
+ return 0;
+}
+#endif /* HAVE_DB1 */
+
+#define KRB5_KDB_SALTTYPE_NORMAL 0
+#define KRB5_KDB_SALTTYPE_V4 1
+#define KRB5_KDB_SALTTYPE_NOREALM 2
+#define KRB5_KDB_SALTTYPE_ONLYREALM 3
+#define KRB5_KDB_SALTTYPE_SPECIAL 4
+#define KRB5_KDB_SALTTYPE_AFS3 5
+#define KRB5_KDB_SALTTYPE_CERTHASH 6
+
+static krb5_error_code
+fix_salt(krb5_context context, hdb_entry *ent, Key *k)
+{
+ krb5_error_code ret;
+ Salt *salt = k->salt;
+ /* fix salt type */
+ switch((int)salt->type) {
+ case KRB5_KDB_SALTTYPE_NORMAL:
+ salt->type = KRB5_PADATA_PW_SALT;
+ break;
+ case KRB5_KDB_SALTTYPE_V4:
+ krb5_data_free(&salt->salt);
+ salt->type = KRB5_PADATA_PW_SALT;
+ break;
+ case KRB5_KDB_SALTTYPE_NOREALM:
+ {
+ size_t len;
+ size_t i;
+ char *p;
+
+ len = 0;
+ for (i = 0; i < ent->principal->name.name_string.len; ++i)
+ len += strlen(ent->principal->name.name_string.val[i]);
+ ret = krb5_data_alloc (&salt->salt, len);
+ if (ret)
+ return ret;
+ p = salt->salt.data;
+ for (i = 0; i < ent->principal->name.name_string.len; ++i) {
+ memcpy (p,
+ ent->principal->name.name_string.val[i],
+ strlen(ent->principal->name.name_string.val[i]));
+ p += strlen(ent->principal->name.name_string.val[i]);
+ }
+
+ salt->type = KRB5_PADATA_PW_SALT;
+ break;
+ }
+ case KRB5_KDB_SALTTYPE_ONLYREALM:
+ krb5_data_free(&salt->salt);
+ ret = krb5_data_copy(&salt->salt,
+ ent->principal->realm,
+ strlen(ent->principal->realm));
+ if(ret)
+ return ret;
+ salt->type = KRB5_PADATA_PW_SALT;
+ break;
+ case KRB5_KDB_SALTTYPE_SPECIAL:
+ salt->type = KRB5_PADATA_PW_SALT;
+ break;
+ case KRB5_KDB_SALTTYPE_AFS3:
+ krb5_data_free(&salt->salt);
+ ret = krb5_data_copy(&salt->salt,
+ ent->principal->realm,
+ strlen(ent->principal->realm));
+ if(ret)
+ return ret;
+ salt->type = KRB5_PADATA_AFS3_SALT;
+ break;
+ case KRB5_KDB_SALTTYPE_CERTHASH:
+ krb5_data_free(&salt->salt);
+ free(k->salt);
+ k->salt = NULL;
+ break;
+ default:
+ abort();
+ }
+ return 0;
+}
+
+
+/**
+ * This function takes a key from a krb5_storage from an MIT KDB encoded
+ * entry and places it in the given Key object.
+ *
+ * @param context Context
+ * @param entry HDB entry
+ * @param sp krb5_storage with current offset set to the beginning of a
+ * key
+ * @param version See comments in caller body for the backstory on this
+ * @param k Key * to load the key into
+ */
+static krb5_error_code
+mdb_keyvalue2key(krb5_context context, hdb_entry *entry, krb5_storage *sp, uint16_t version, Key *k)
+{
+ size_t i;
+ uint16_t u16, type;
+ krb5_error_code ret;
+
+ k->mkvno = malloc(sizeof(*k->mkvno));
+ if (k->mkvno == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+ *k->mkvno = 1;
+
+ for (i = 0; i < version; i++) {
+ CHECK(ret = krb5_ret_uint16(sp, &type));
+ CHECK(ret = krb5_ret_uint16(sp, &u16));
+ if (i == 0) {
+ /* This "version" means we have a key */
+ k->key.keytype = type;
+ /*
+ * MIT stores keys encrypted keys as {16-bit length
+ * of plaintext key, {encrypted key}}. The reason
+ * for this is that the Kerberos cryptosystem is not
+ * length-preserving. Heimdal's approach is to
+ * truncate the plaintext to the expected length of
+ * the key given its enctype, so we ignore this
+ * 16-bit length-of-plaintext-key field.
+ */
+ if (u16 > 2) {
+ krb5_storage_seek(sp, 2, SEEK_CUR); /* skip real length */
+ k->key.keyvalue.length = u16 - 2; /* adjust cipher len */
+ k->key.keyvalue.data = malloc(k->key.keyvalue.length);
+ krb5_storage_read(sp, k->key.keyvalue.data,
+ k->key.keyvalue.length);
+ } else {
+ /* We'll ignore this key; see our caller */
+ k->key.keyvalue.length = 0;
+ k->key.keyvalue.data = NULL;
+ krb5_storage_seek(sp, u16, SEEK_CUR); /* skip real length */
+ }
+ } else if (i == 1) {
+ /* This "version" means we have a salt */
+ k->salt = calloc(1, sizeof(*k->salt));
+ if (k->salt == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+ k->salt->type = type;
+ if (u16 != 0) {
+ k->salt->salt.data = malloc(u16);
+ if (k->salt->salt.data == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+ k->salt->salt.length = u16;
+ krb5_storage_read(sp, k->salt->salt.data, k->salt->salt.length);
+ }
+ fix_salt(context, entry, k);
+ } else {
+ /*
+ * Whatever this "version" might be, we skip it
+ *
+ * XXX A krb5.conf parameter requesting that we log
+ * about strangeness like this, or return an error
+ * from here, might be nice.
+ */
+ krb5_storage_seek(sp, u16, SEEK_CUR);
+ }
+ }
+
+ return 0;
+
+out:
+ free_Key(k);
+ return ret;
+}
+
+
+static krb5_error_code
+add_1des_dup(krb5_context context, Keys *keys, Key *key, krb5_keytype keytype)
+{
+ key->key.keytype = keytype;
+ return add_Keys(keys, key);
+}
+
+/*
+ * This monstrosity is here so we can avoid having to do enctype
+ * similarity checking in the KDC. This helper function dups 1DES keys
+ * in a keyset for all the similar 1DES enctypes for which keys are
+ * missing. And, of course, we do this only if there's any 1DES keys in
+ * the keyset to begin with.
+ */
+static krb5_error_code
+dup_similar_keys_in_keyset(krb5_context context, Keys *keys)
+{
+ krb5_error_code ret;
+ size_t i, k;
+ Key key;
+ int keyset_has_1des_crc = 0;
+ int keyset_has_1des_md4 = 0;
+ int keyset_has_1des_md5 = 0;
+
+ memset(&key, 0, sizeof (key));
+ k = keys->len;
+ for (i = 0; i < keys->len; i++) {
+ if (keys->val[i].key.keytype == ETYPE_DES_CBC_CRC) {
+ keyset_has_1des_crc = 1;
+ if (k == keys->len)
+ k = i;
+ } else if (keys->val[i].key.keytype == ETYPE_DES_CBC_MD4) {
+ keyset_has_1des_crc = 1;
+ if (k == keys->len)
+ k = i;
+ } else if (keys->val[i].key.keytype == ETYPE_DES_CBC_MD5) {
+ keyset_has_1des_crc = 1;
+ if (k == keys->len)
+ k = i;
+ }
+ }
+ if (k == keys->len)
+ return 0;
+
+ ret = copy_Key(&keys->val[k], &key);
+ if (ret)
+ return ret;
+ if (!keyset_has_1des_crc) {
+ ret = add_1des_dup(context, keys, &key, ETYPE_DES_CBC_CRC);
+ if (ret)
+ goto out;
+ }
+ if (!keyset_has_1des_md4) {
+ ret = add_1des_dup(context, keys, &key, ETYPE_DES_CBC_MD4);
+ if (ret)
+ goto out;
+ }
+ if (!keyset_has_1des_md5) {
+ ret = add_1des_dup(context, keys, &key, ETYPE_DES_CBC_MD5);
+ if (ret)
+ goto out;
+ }
+
+out:
+ free_Key(&key);
+ return ret;
+}
+
+
+static krb5_error_code
+dup_similar_keys(krb5_context context, hdb_entry *entry)
+{
+ krb5_error_code ret;
+ HDB_Ext_KeySet *hist_keys;
+ HDB_extension *extp;
+ size_t i;
+
+ ret = dup_similar_keys_in_keyset(context, &entry->keys);
+ if (ret)
+ return ret;
+ extp = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys);
+ if (extp == NULL)
+ return 0;
+
+ hist_keys = &extp->data.u.hist_keys;
+ for (i = 0; i < hist_keys->len; i++) {
+ ret = dup_similar_keys_in_keyset(context, &hist_keys->val[i].keys);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+
+/**
+ * This function parses an MIT krb5 encoded KDB entry and fills in the
+ * given HDB entry with it.
+ *
+ * @param context krb5_context
+ * @param data Encoded MIT KDB entry
+ * @param target_kvno Desired kvno, or 0 for the entry's current kvno
+ * @param entry Desired kvno, or 0 for the entry's current kvno
+ */
+krb5_error_code
+_hdb_mdb_value2entry(krb5_context context, krb5_data *data,
+ krb5_kvno target_kvno, hdb_entry *entry)
+{
+ krb5_error_code ret;
+ krb5_storage *sp;
+ Key k;
+ krb5_kvno key_kvno;
+ uint32_t u32;
+ uint16_t u16, num_keys, num_tl;
+ ssize_t sz;
+ size_t i;
+ char *p;
+
+ memset(&k, 0, sizeof (k));
+ memset(entry, 0, sizeof(*entry));
+
+ sp = krb5_storage_from_data(data);
+ if (sp == NULL) {
+ krb5_set_error_message(context, ENOMEM, "out of memory");
+ return ENOMEM;
+ }
+
+ krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_LE);
+
+ /*
+ * 16: baselength
+ *
+ * The story here is that these 16 bits have to be a constant:
+ * KDB_V1_BASE_LENGTH. Once upon a time a different value here
+ * would have been used to indicate the presence of "extra data"
+ * between the "base" contents and the {principal name, TL data,
+ * keys} that follow it. Nothing supports such "extra data"
+ * nowadays, so neither do we here.
+ *
+ * XXX But... surely we ought to log about this extra data, or skip
+ * it, or something, in case anyone has MIT KDBs with ancient
+ * entries in them... Logging would allow the admin to know which
+ * entries to dump with MIT krb5's kdb5_util. But logging would be
+ * noisy. For now we do nothing.
+ */
+ CHECK(ret = krb5_ret_uint16(sp, &u16));
+ if (u16 != KDB_V1_BASE_LENGTH) { ret = EINVAL; goto out; }
+ /* 32: attributes */
+ CHECK(ret = krb5_ret_uint32(sp, &u32));
+ attr_to_flags(u32, &entry->flags);
+
+ /* 32: max time */
+ CHECK(ret = krb5_ret_uint32(sp, &u32));
+ if (u32) {
+ entry->max_life = malloc(sizeof(*entry->max_life));
+ *entry->max_life = u32;
+ }
+ /* 32: max renewable time */
+ CHECK(ret = krb5_ret_uint32(sp, &u32));
+ if (u32) {
+ entry->max_renew = malloc(sizeof(*entry->max_renew));
+ *entry->max_renew = u32;
+ }
+ /* 32: client expire */
+ CHECK(ret = krb5_ret_uint32(sp, &u32));
+ if (u32) {
+ entry->valid_end = malloc(sizeof(*entry->valid_end));
+ *entry->valid_end = u32;
+ }
+ /* 32: passwd expire */
+ CHECK(ret = krb5_ret_uint32(sp, &u32));
+ if (u32) {
+ entry->pw_end = malloc(sizeof(*entry->pw_end));
+ *entry->pw_end = u32;
+ }
+ /* 32: last successful passwd */
+ CHECK(ret = krb5_ret_uint32(sp, &u32));
+ /* 32: last failed attempt */
+ CHECK(ret = krb5_ret_uint32(sp, &u32));
+ /* 32: num of failed attempts */
+ CHECK(ret = krb5_ret_uint32(sp, &u32));
+ /* 16: num tl data */
+ CHECK(ret = krb5_ret_uint16(sp, &u16));
+ num_tl = u16;
+ /* 16: num key data */
+ CHECK(ret = krb5_ret_uint16(sp, &u16));
+ num_keys = u16;
+ /* 16: principal length */
+ CHECK(ret = krb5_ret_uint16(sp, &u16));
+ /* length: principal */
+ {
+ /*
+ * Note that the principal name includes the NUL in the entry,
+ * but we don't want to take chances, so we add an extra NUL.
+ */
+ p = malloc(u16 + 1);
+ if (p == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+ sz = krb5_storage_read(sp, p, u16);
+ if (sz != u16) {
+ ret = EINVAL; /* XXX */
+ goto out;
+ }
+ p[u16] = '\0';
+ CHECK(ret = krb5_parse_name(context, p, &entry->principal));
+ free(p);
+ }
+ /* for num tl data times
+ 16: tl data type
+ 16: tl data length
+ length: length */
+#define mit_KRB5_TL_LAST_PWD_CHANGE 1
+#define mit_KRB5_TL_MOD_PRINC 2
+ for (i = 0; i < num_tl; i++) {
+ int tl_type;
+ krb5_principal modby;
+ /* 16: TL data type */
+ CHECK(ret = krb5_ret_uint16(sp, &u16));
+ tl_type = u16;
+ /* 16: TL data length */
+ CHECK(ret = krb5_ret_uint16(sp, &u16));
+ /*
+ * For rollback to MIT purposes we really must understand some
+ * TL data!
+ *
+ * XXX Move all this to separate functions, one per-TL type.
+ */
+ switch (tl_type) {
+ case mit_KRB5_TL_LAST_PWD_CHANGE:
+ CHECK(ret = krb5_ret_uint32(sp, &u32));
+ CHECK(ret = hdb_entry_set_pw_change_time(context, entry, u32));
+ break;
+ case mit_KRB5_TL_MOD_PRINC:
+ if (u16 < 5) {
+ ret = EINVAL; /* XXX */
+ goto out;
+ }
+ CHECK(ret = krb5_ret_uint32(sp, &u32)); /* mod time */
+ p = malloc(u16 - 4 + 1);
+ if (!p) {
+ ret = ENOMEM;
+ goto out;
+ }
+ p[u16 - 4] = '\0';
+ sz = krb5_storage_read(sp, p, u16 - 4);
+ if (sz != u16 - 4) {
+ ret = EINVAL; /* XXX */
+ goto out;
+ }
+ CHECK(ret = krb5_parse_name(context, p, &modby));
+ CHECK(ret = hdb_set_last_modified_by(context, entry, modby, u32));
+ krb5_free_principal(context, modby);
+ free(p);
+ break;
+ default:
+ krb5_storage_seek(sp, u16, SEEK_CUR);
+ break;
+ }
+ }
+ /*
+ * for num key data times
+ * 16: "version"
+ * 16: kvno
+ * for version times:
+ * 16: type
+ * 16: length
+ * length: keydata
+ *
+ * "version" here is really 1 or 2, the first meaning there's only
+ * keys for this kvno, the second meaning there's keys and salt[s?].
+ * That's right... hold that gag reflex, you can do it.
+ */
+ for (i = 0; i < num_keys; i++) {
+ uint16_t version;
+
+ CHECK(ret = krb5_ret_uint16(sp, &u16));
+ version = u16;
+ CHECK(ret = krb5_ret_uint16(sp, &u16));
+ key_kvno = u16;
+
+ ret = mdb_keyvalue2key(context, entry, sp, version, &k);
+ if (ret)
+ goto out;
+ if (k.key.keytype == 0 || k.key.keyvalue.length == 0) {
+ /*
+ * Older MIT KDBs may have enctype 0 / length 0 keys. We
+ * ignore these.
+ */
+ free_Key(&k);
+ continue;
+ }
+
+ if ((target_kvno == 0 && entry->kvno < key_kvno) ||
+ (target_kvno == key_kvno && entry->kvno != target_kvno)) {
+ /*
+ * MIT's KDB doesn't keep track of kvno. The highest kvno
+ * is the current kvno, and we just found a new highest
+ * kvno or the desired kvno.
+ *
+ * Note that there's no guarantee of any key ordering, but
+ * generally MIT KDB entries have keys in strictly
+ * descending kvno order.
+ *
+ * XXX We do assume that keys are clustered by kvno. If
+ * not, then bad. It might be possible to construct
+ * non-clustered keys via the kadm5 API. It wouldn't be
+ * hard to cope with this, since if it happens the worst
+ * that will happen is that some of the current keys can be
+ * found in the history extension, and we could just pull
+ * them back out in that case.
+ */
+ ret = hdb_add_current_keys_to_history(context, entry);
+ if (ret)
+ goto out;
+ free_Keys(&entry->keys);
+ ret = add_Keys(&entry->keys, &k);
+ free_Key(&k);
+ if (ret)
+ goto out;
+ entry->kvno = key_kvno;
+ continue;
+ }
+
+ if (entry->kvno == key_kvno) {
+ /*
+ * Note that if key_kvno == 0 and target_kvno == 0 then we
+ * end up adding those keys here. Yeah, kvno 0 is very
+ * special for us, but just in case, we keep such keys.
+ */
+ ret = add_Keys(&entry->keys, &k);
+ free_Key(&k);
+ if (ret)
+ goto out;
+ entry->kvno = key_kvno;
+ } else {
+ ret = hdb_add_history_key(context, entry, key_kvno, &k);
+ if (ret)
+ goto out;
+ free_Key(&k);
+ }
+ }
+
+ if (target_kvno != 0 && entry->kvno != target_kvno) {
+ ret = HDB_ERR_KVNO_NOT_FOUND;
+ goto out;
+ }
+
+ krb5_storage_free(sp);
+
+ return dup_similar_keys(context, entry);
+
+out:
+ krb5_storage_free(sp);
+
+ if (ret == HEIM_ERR_EOF)
+ /* Better error code than "end of file" */
+ ret = HEIM_ERR_BAD_HDBENT_ENCODING;
+ free_HDB_entry(entry);
+ free_Key(&k);
+ return ret;
+}
+
+#if 0
+static krb5_error_code
+mdb_entry2value(krb5_context context, hdb_entry *entry, krb5_data *data)
+{
+ return EINVAL;
+}
+#endif
+
+#if HAVE_DB1
+
+#if defined(HAVE_DB_185_H)
+#include <db_185.h>
+#elif defined(HAVE_DB_H)
+#include <db.h>
+#endif
+
+
+static krb5_error_code
+mdb_close(krb5_context context, HDB *db)
+{
+ DB *d = (DB*)db->hdb_db;
+ (*d->close)(d);
+ return 0;
+}
+
+static krb5_error_code
+mdb_destroy(krb5_context context, HDB *db)
+{
+ krb5_error_code ret;
+
+ ret = hdb_clear_master_key(context, db);
+ krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
+ free(db->hdb_name);
+ free(db);
+ return ret;
+}
+
+static krb5_error_code
+mdb_set_sync(krb5_context context, HDB *db, int on)
+{
+ MITDB *mdb = (MITDB *)db;
+ DB *d = (DB*)db->hdb_db;
+
+ mdb->do_sync = on;
+ if (on)
+ return fsync((*d->fd)(d));
+ return 0;
+}
+
+static krb5_error_code
+mdb_lock(krb5_context context, HDB *db, int operation)
+{
+ DB *d = (DB*)db->hdb_db;
+ int fd = (*d->fd)(d);
+ krb5_error_code ret;
+
+ if (db->lock_count > 1) {
+ db->lock_count++;
+ if (db->lock_type == HDB_WLOCK || db->lock_count == operation)
+ return 0;
+ }
+
+ if(fd < 0) {
+ krb5_set_error_message(context, HDB_ERR_CANT_LOCK_DB,
+ "Can't lock database: %s", db->hdb_name);
+ return HDB_ERR_CANT_LOCK_DB;
+ }
+ ret = hdb_lock(fd, operation);
+ if (ret)
+ return ret;
+ db->lock_count++;
+ return 0;
+}
+
+static krb5_error_code
+mdb_unlock(krb5_context context, HDB *db)
+{
+ DB *d = (DB*)db->hdb_db;
+ int fd = (*d->fd)(d);
+
+ if (db->lock_count > 1) {
+ db->lock_count--;
+ return 0;
+ }
+ heim_assert(db->lock_count == 1, "HDB lock/unlock sequence does not match");
+ db->lock_count--;
+
+ if(fd < 0) {
+ krb5_set_error_message(context, HDB_ERR_CANT_LOCK_DB,
+ "Can't unlock database: %s", db->hdb_name);
+ return HDB_ERR_CANT_LOCK_DB;
+ }
+ return hdb_unlock(fd);
+}
+
+
+static krb5_error_code
+mdb_seq(krb5_context context, HDB *db,
+ unsigned flags, hdb_entry *entry, int flag)
+{
+ DB *d = (DB*)db->hdb_db;
+ DBT key, value;
+ krb5_data key_data, data;
+ int code;
+
+ code = db->hdb_lock(context, db, HDB_RLOCK);
+ if(code == -1) {
+ krb5_set_error_message(context, HDB_ERR_DB_INUSE, "Database %s in use", db->hdb_name);
+ return HDB_ERR_DB_INUSE;
+ }
+ code = (*d->seq)(d, &key, &value, flag);
+ db->hdb_unlock(context, db); /* XXX check value */
+ if(code == -1) {
+ code = errno;
+ krb5_set_error_message(context, code, "Database %s seq error: %s",
+ db->hdb_name, strerror(code));
+ return code;
+ }
+ if(code == 1) {
+ krb5_clear_error_message(context);
+ return HDB_ERR_NOENTRY;
+ }
+
+ key_data.data = key.data;
+ key_data.length = key.size;
+ data.data = value.data;
+ data.length = value.size;
+ memset(entry, 0, sizeof(*entry));
+
+ if (_hdb_mdb_value2entry(context, &data, 0, entry))
+ return mdb_seq(context, db, flags, entry, R_NEXT);
+
+ if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
+ code = hdb_unseal_keys (context, db, entry);
+ if (code)
+ hdb_free_entry (context, db, entry);
+ }
+
+ return code;
+}
+
+
+static krb5_error_code
+mdb_firstkey(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
+{
+ return mdb_seq(context, db, flags, entry, R_FIRST);
+}
+
+
+static krb5_error_code
+mdb_nextkey(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
+{
+ return mdb_seq(context, db, flags, entry, R_NEXT);
+}
+
+static krb5_error_code
+mdb_rename(krb5_context context, HDB *db, const char *new_name)
+{
+ int ret;
+ char *old = NULL;
+ char *new = NULL;
+
+ if (asprintf(&old, "%s.db", db->hdb_name) < 0)
+ goto out;
+ if (asprintf(&new, "%s.db", new_name) < 0)
+ goto out;
+ ret = rename(old, new);
+ if(ret)
+ goto out;
+
+ free(db->hdb_name);
+ db->hdb_name = strdup(new_name);
+ errno = 0;
+
+out:
+ free(old);
+ free(new);
+ return errno;
+}
+
+static krb5_error_code
+mdb__get(krb5_context context, HDB *db, krb5_data key, krb5_data *reply)
+{
+ DB *d = (DB*)db->hdb_db;
+ DBT k, v;
+ int code;
+
+ k.data = key.data;
+ k.size = key.length;
+ code = db->hdb_lock(context, db, HDB_RLOCK);
+ if(code)
+ return code;
+ code = (*d->get)(d, &k, &v, 0);
+ db->hdb_unlock(context, db);
+ if(code < 0) {
+ code = errno;
+ krb5_set_error_message(context, code, "Database %s get error: %s",
+ db->hdb_name, strerror(code));
+ return code;
+ }
+ if(code == 1) {
+ krb5_clear_error_message(context);
+ return HDB_ERR_NOENTRY;
+ }
+
+ krb5_data_copy(reply, v.data, v.size);
+ return 0;
+}
+
+static krb5_error_code
+mdb__put(krb5_context context, HDB *db, int replace,
+ krb5_data key, krb5_data value)
+{
+ MITDB *mdb = (MITDB *)db;
+ DB *d = (DB*)db->hdb_db;
+ DBT k, v;
+ int code;
+
+ k.data = key.data;
+ k.size = key.length;
+ v.data = value.data;
+ v.size = value.length;
+ code = db->hdb_lock(context, db, HDB_WLOCK);
+ if(code)
+ return code;
+ code = (*d->put)(d, &k, &v, replace ? 0 : R_NOOVERWRITE);
+ if (code == 0) {
+ code = mdb_set_sync(context, db, mdb->do_sync);
+ db->hdb_unlock(context, db);
+ return code;
+ }
+ db->hdb_unlock(context, db);
+ if(code < 0) {
+ code = errno;
+ krb5_set_error_message(context, code, "Database %s put error: %s",
+ db->hdb_name, strerror(code));
+ return code;
+ }
+ krb5_clear_error_message(context);
+ return HDB_ERR_EXISTS;
+}
+
+static krb5_error_code
+mdb__del(krb5_context context, HDB *db, krb5_data key)
+{
+ MITDB *mdb = (MITDB *)db;
+ DB *d = (DB*)db->hdb_db;
+ DBT k;
+ krb5_error_code code;
+ k.data = key.data;
+ k.size = key.length;
+ code = db->hdb_lock(context, db, HDB_WLOCK);
+ if(code)
+ return code;
+ code = (*d->del)(d, &k, 0);
+ if (code == 0) {
+ code = mdb_set_sync(context, db, mdb->do_sync);
+ db->hdb_unlock(context, db);
+ return code;
+ }
+ db->hdb_unlock(context, db);
+ if(code == 1) {
+ code = errno;
+ krb5_set_error_message(context, code, "Database %s put error: %s",
+ db->hdb_name, strerror(code));
+ return code;
+ }
+ if(code < 0)
+ return errno;
+ return 0;
+}
+
+static krb5_error_code
+mdb_fetch_kvno(krb5_context context, HDB *db, krb5_const_principal principal,
+ unsigned flags, krb5_kvno kvno, hdb_entry *entry)
+{
+ krb5_data key, value;
+ krb5_error_code ret;
+
+ ret = mdb_principal2key(context, principal, &key);
+ if (ret)
+ return ret;
+ ret = db->hdb__get(context, db, key, &value);
+ krb5_data_free(&key);
+ if(ret)
+ return ret;
+ ret = _hdb_mdb_value2entry(context, &value, kvno, entry);
+ krb5_data_free(&value);
+ if (ret)
+ return ret;
+
+ if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
+ ret = hdb_unseal_keys (context, db, entry);
+ if (ret) {
+ hdb_free_entry(context, db, entry);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+mdb_store(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
+{
+ krb5_error_code ret;
+ krb5_storage *sp = NULL;
+ krb5_storage *spent = NULL;
+ krb5_data line = { 0, 0 };
+ krb5_data kdb_ent = { 0, 0 };
+ krb5_data key = { 0, 0 };
+ krb5_data value = { 0, 0 };
+ krb5_ssize_t sz;
+
+ if ((flags & HDB_F_PRECHECK) && (flags & HDB_F_REPLACE))
+ return 0;
+
+ if ((flags & HDB_F_PRECHECK)) {
+ ret = mdb_principal2key(context, entry->principal, &key);
+ if (ret) return ret;
+ ret = db->hdb__get(context, db, key, &value);
+ krb5_data_free(&key);
+ if (ret == 0)
+ krb5_data_free(&value);
+ if (ret == HDB_ERR_NOENTRY)
+ return 0;
+ return ret ? ret : HDB_ERR_EXISTS;
+ }
+
+ sp = krb5_storage_emem();
+ if (!sp) return ENOMEM;
+ ret = _hdb_set_master_key_usage(context, db, 0); /* MIT KDB uses KU 0 */
+ ret = hdb_seal_keys(context, db, entry);
+ if (ret) return ret;
+ ret = entry2mit_string_int(context, sp, entry);
+ if (ret) goto out;
+ sz = krb5_storage_write(sp, "\n", 2); /* NUL-terminate */
+ ret = ENOMEM;
+ if (sz != 2) goto out;
+ ret = krb5_storage_to_data(sp, &line);
+ if (ret) goto out;
+
+ ret = ENOMEM;
+ spent = krb5_storage_emem();
+ if (!spent) goto out;
+ ret = _hdb_mit_dump2mitdb_entry(context, line.data, spent);
+ if (ret) goto out;
+ ret = krb5_storage_to_data(spent, &kdb_ent);
+ if (ret) goto out;
+ ret = mdb_principal2key(context, entry->principal, &key);
+ if (ret) goto out;
+ ret = mdb__put(context, db, 1, key, kdb_ent);
+
+out:
+ if (sp)
+ krb5_storage_free(sp);
+ if (spent)
+ krb5_storage_free(spent);
+ krb5_data_free(&line);
+ krb5_data_free(&kdb_ent);
+ krb5_data_free(&key);
+
+ return ret;
+}
+
+static krb5_error_code
+mdb_remove(krb5_context context, HDB *db,
+ unsigned flags, krb5_const_principal principal)
+{
+ krb5_error_code code;
+ krb5_data key;
+ krb5_data value = { 0, 0 };
+
+ mdb_principal2key(context, principal, &key);
+
+ if ((flags & HDB_F_PRECHECK)) {
+ code = db->hdb__get(context, db, key, &value);
+ krb5_data_free(&key);
+ if (code == 0) {
+ krb5_data_free(&value);
+ return 0;
+ }
+ return code;
+ }
+
+ code = db->hdb__del(context, db, key);
+ krb5_data_free(&key);
+ return code;
+}
+
+static krb5_error_code
+mdb_open(krb5_context context, HDB *db, int flags, mode_t mode)
+{
+ char *fn;
+ char *actual_fn;
+ krb5_error_code ret;
+ struct stat st;
+
+ if (asprintf(&fn, "%s.db", db->hdb_name) < 0) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ if (stat(fn, &st) == 0)
+ actual_fn = fn;
+ else
+ actual_fn = db->hdb_name;
+ db->hdb_db = dbopen(actual_fn, flags, mode, DB_BTREE, NULL);
+ if (db->hdb_db == NULL) {
+ switch (errno) {
+#ifdef EFTYPE
+ case EFTYPE:
+#endif
+ case EINVAL:
+ db->hdb_db = dbopen(actual_fn, flags, mode, DB_HASH, NULL);
+ }
+ }
+ free(fn);
+
+ if (db->hdb_db == NULL) {
+ ret = errno;
+ krb5_set_error_message(context, ret, "dbopen (%s): %s",
+ db->hdb_name, strerror(ret));
+ return ret;
+ }
+#if 0
+ /*
+ * Don't do this -- MIT won't be able to handle the
+ * HDB_DB_FORMAT_ENTRY key.
+ */
+ if ((flags & O_ACCMODE) != O_RDONLY)
+ ret = hdb_init_db(context, db);
+#endif
+ ret = hdb_check_db_format(context, db);
+ if (ret == HDB_ERR_NOENTRY) {
+ krb5_clear_error_message(context);
+ return 0;
+ }
+ if (ret) {
+ mdb_close(context, db);
+ krb5_set_error_message(context, ret, "hdb_open: failed %s database %s",
+ (flags & O_ACCMODE) == O_RDONLY ?
+ "checking format of" : "initialize",
+ db->hdb_name);
+ }
+ return ret;
+}
+
+krb5_error_code
+hdb_mitdb_create(krb5_context context, HDB **db,
+ const char *filename)
+{
+ MITDB **mdb = (MITDB **)db;
+ *mdb = calloc(1, sizeof(**mdb));
+ if (*mdb == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ (*db)->hdb_db = NULL;
+ (*db)->hdb_name = strdup(filename);
+ if ((*db)->hdb_name == NULL) {
+ free(*db);
+ *db = NULL;
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ (*mdb)->do_sync = 1;
+ (*db)->hdb_master_key_set = 0;
+ (*db)->hdb_openp = 0;
+ (*db)->hdb_capability_flags = 0;
+ (*db)->hdb_open = mdb_open;
+ (*db)->hdb_close = mdb_close;
+ (*db)->hdb_fetch_kvno = mdb_fetch_kvno;
+ (*db)->hdb_store = mdb_store;
+ (*db)->hdb_remove = mdb_remove;
+ (*db)->hdb_firstkey = mdb_firstkey;
+ (*db)->hdb_nextkey= mdb_nextkey;
+ (*db)->hdb_lock = mdb_lock;
+ (*db)->hdb_unlock = mdb_unlock;
+ (*db)->hdb_rename = mdb_rename;
+ (*db)->hdb__get = mdb__get;
+ (*db)->hdb__put = mdb__put;
+ (*db)->hdb__del = mdb__del;
+ (*db)->hdb_destroy = mdb_destroy;
+ (*db)->hdb_set_sync = mdb_set_sync;
+ return 0;
+}
+
+#endif /* HAVE_DB1 */
+
+/*
+can have any number of princ stanzas.
+format is as follows (only \n indicates newlines)
+princ\t%d\t (%d is KRB5_KDB_V1_BASE_LENGTH, always 38)
+%d\t (strlen of principal e.g. shadow/foo@ANDREW.CMU.EDU)
+%d\t (number of tl_data)
+%d\t (number of key data, e.g. how many keys for this user)
+%d\t (extra data length)
+%s\t (principal name)
+%d\t (attributes)
+%d\t (max lifetime, seconds)
+%d\t (max renewable life, seconds)
+%d\t (expiration, seconds since epoch or 2145830400 for never)
+%d\t (password expiration, seconds, 0 for never)
+%d\t (last successful auth, seconds since epoch)
+%d\t (last failed auth, per above)
+%d\t (failed auth count)
+foreach tl_data 0 to number of tl_data - 1 as above
+ %d\t%d\t (data type, data length)
+ foreach tl_data 0 to length-1
+ %02x (tl data contents[element n])
+ except if tl_data length is 0
+ %d (always -1)
+ \t
+foreach key 0 to number of keys - 1 as above
+ %d\t%d\t (key data version, kvno)
+ foreach version 0 to key data version - 1 (a key or a salt)
+ %d\t%d\t(data type for this key, data length for this key)
+ foreach key data length 0 to length-1
+ %02x (key data contents[element n])
+ except if key_data length is 0
+ %d (always -1)
+ \t
+foreach extra data length 0 to length - 1
+ %02x (extra data part)
+unless no extra data
+ %d (always -1)
+;\n
+
+*/
+
+#if 0
+/* Why ever did we loop? */
+static char *
+nexttoken(char **p)
+{
+ char *q;
+ do {
+ q = strsep(p, " \t");
+ } while(q && *q == '\0');
+ return q;
+}
+#endif
+
+static char *
+nexttoken(char **p, size_t len, const char *what)
+{
+ char *q;
+
+ if (*p == NULL)
+ return NULL;
+
+ q = *p;
+ *p += len;
+ /* Must be followed by a delimiter (right?) */
+ if (strsep(p, " \t") != q + len) {
+ warnx("No tokens left in dump entry while looking for %s", what);
+ return NULL;
+ }
+ if (*q == '\0')
+ warnx("Empty last token in dump entry while looking for %s", what);
+ return q;
+}
+
+static size_t
+getdata(char **p, unsigned char *buf, size_t len, const char *what)
+{
+ size_t i;
+ int v;
+ char *q = nexttoken(p, 0, what);
+ if (q == NULL) {
+ warnx("Failed to find hex-encoded binary data (%s) in dump", what);
+ return 0;
+ }
+ i = 0;
+ while (*q && i < len) {
+ if (sscanf(q, "%02x", &v) != 1)
+ break;
+ buf[i++] = v;
+ q += 2;
+ }
+ return i;
+}
+
+static int
+getint(char **p, const char *what, int *val)
+{
+ char *q = nexttoken(p, 0, what);
+ if (!q) {
+ warnx("Failed to find a signed integer (%s) in dump", what);
+ return 1;
+ }
+ if (sscanf(q, "%d", val) != 1)
+ return 1;
+ return 0;
+}
+
+static unsigned int
+getuint(char **p, const char *what)
+{
+ int val;
+ char *q = nexttoken(p, 0, what);
+ if (!q) {
+ warnx("Failed to find an unsigned integer (%s) in dump", what);
+ return 0;
+ }
+ if (sscanf(q, "%u", &val) != 1)
+ return 0;
+ return val;
+}
+
+#define KRB5_KDB_SALTTYPE_NORMAL 0
+#define KRB5_KDB_SALTTYPE_V4 1
+#define KRB5_KDB_SALTTYPE_NOREALM 2
+#define KRB5_KDB_SALTTYPE_ONLYREALM 3
+#define KRB5_KDB_SALTTYPE_SPECIAL 4
+#define KRB5_KDB_SALTTYPE_AFS3 5
+
+#define CHECK_UINT(num) \
+ if ((num) < 0 || (num) > INT_MAX) return EINVAL
+#define CHECK_UINT16(num) \
+ if ((num) < 0 || (num) > 1<<15) return EINVAL
+#define CHECK_NUM(num, maxv) \
+ if ((num) > (maxv)) return EINVAL
+
+/*
+ * This utility function converts an MIT dump entry to an MIT on-disk
+ * encoded entry, which can then be decoded with _hdb_mdb_value2entry().
+ * This allows us to have a single decoding function (_hdb_mdb_value2entry),
+ * which makes the code cleaner (less code duplication), if a bit less
+ * efficient. It also will allow us to have a function to dump an HDB
+ * entry in MIT format so we can dump HDB into MIT format for rollback
+ * purposes. And that will allow us to write to MIT KDBs, again
+ * somewhat inefficiently, also for migration/rollback purposes.
+ */
+int
+_hdb_mit_dump2mitdb_entry(krb5_context context, char *line, krb5_storage *sp)
+{
+ krb5_error_code ret = EINVAL;
+ char *p = line, *q;
+ char *princ;
+ krb5_ssize_t sz;
+ size_t i;
+ size_t princ_len;
+ unsigned int num_tl_data;
+ size_t num_key_data;
+ unsigned int attributes;
+ int tmp;
+
+ krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_LE);
+
+ q = nexttoken(&p, 0, "record type (princ or policy)");
+ if (strcmp(q, "kdb5_util") == 0 || strcmp(q, "policy") == 0 ||
+ strcmp(q, "princ") != 0) {
+ warnx("Supposed MIT dump entry does not start with 'kdb5_util', "
+ "'policy', nor 'princ'");
+ return -1;
+ }
+ if (getint(&p, "constant '38'", &tmp) || tmp != 38) {
+ warnx("Dump entry does not start with '38<TAB>'");
+ return EINVAL;
+ }
+#define KDB_V1_BASE_LENGTH 38
+ ret = krb5_store_int16(sp, KDB_V1_BASE_LENGTH);
+ if (ret) return ret;
+
+ princ_len = getuint(&p, "principal name length");
+ if (princ_len > (1<<15) - 1) {
+ warnx("Principal name in dump entry too long (%llu)",
+ (unsigned long long)princ_len);
+ return EINVAL;
+ }
+ num_tl_data = getuint(&p, "number of TL data");
+ num_key_data = getuint(&p, "number of key data");
+ (void) getint(&p, "5th field, length of 'extra data'", &tmp);
+ princ = nexttoken(&p, (int)princ_len, "principal name");
+ if (princ == NULL) {
+ warnx("Failed to read principal name (expected length %llu)",
+ (unsigned long long)princ_len);
+ return -1;
+ }
+
+ attributes = getuint(&p, "attributes");
+ ret = krb5_store_uint32(sp, attributes);
+ if (ret) return ret;
+
+ if (getint(&p, "max life", &tmp)) return EINVAL;
+ ret = krb5_store_uint32(sp, tmp);
+ if (ret) return ret;
+
+ if (getint(&p, "max renewable life", &tmp)) return EINVAL;
+ ret = krb5_store_uint32(sp, tmp);
+ if (ret) return ret;
+
+ if (getint(&p, "expiration", &tmp)) return EINVAL;
+ ret = krb5_store_uint32(sp, tmp);
+ if (ret) return ret;
+
+ if (getint(&p, "pw expiration", &tmp)) return EINVAL;
+ ret = krb5_store_uint32(sp, tmp);
+ if (ret) return ret;
+
+ if (getint(&p, "last auth", &tmp)) return EINVAL;
+ ret = krb5_store_uint32(sp, tmp);
+ if (ret) return ret;
+
+ if (getint(&p, "last failed auth", &tmp)) return EINVAL;
+ ret = krb5_store_uint32(sp, tmp);
+ if (ret) return ret;
+
+ if (getint(&p,"fail auth count", &tmp)) return EINVAL;
+ ret = krb5_store_uint32(sp, tmp);
+ if (ret) return ret;
+
+ /* add TL data count */
+ CHECK_NUM(num_tl_data, 1023);
+ ret = krb5_store_uint16(sp, num_tl_data);
+ if (ret) return ret;
+
+ /* add key count */
+ CHECK_NUM(num_key_data, 1023);
+ ret = krb5_store_uint16(sp, num_key_data);
+ if (ret) return ret;
+
+ /* add principal unparsed name length and unparsed name */
+ princ_len = strlen(princ);
+ princ_len++; /* must count and write the NUL in the on-disk encoding */
+ ret = krb5_store_uint16(sp, princ_len);
+ if (ret) return ret;
+ sz = krb5_storage_write(sp, princ, princ_len);
+ if (sz != princ_len) return ENOMEM;
+
+ /* scan and write TL data */
+ for (i = 0; i < num_tl_data; i++) {
+ char *reading_what;
+ int tl_type, tl_length;
+ unsigned char *buf;
+
+ if (getint(&p, "TL data type", &tl_type) ||
+ getint(&p, "data length", &tl_length))
+ return EINVAL;
+
+ if (asprintf(&reading_what, "TL data type %d (length %d)",
+ tl_type, tl_length) < 0)
+ return ENOMEM;
+
+ /*
+ * XXX Leaking reading_what, but only on ENOMEM cases anyways,
+ * so we don't care.
+ */
+ CHECK_UINT16(tl_type);
+ ret = krb5_store_uint16(sp, tl_type);
+ if (ret) return ret;
+ CHECK_UINT16(tl_length);
+ ret = krb5_store_uint16(sp, tl_length);
+ if (ret) return ret;
+
+ if (tl_length) {
+ buf = malloc(tl_length);
+ if (!buf) return ENOMEM;
+ if (getdata(&p, buf, tl_length, reading_what) != tl_length) {
+ free(buf);
+ return EINVAL;
+ }
+ sz = krb5_storage_write(sp, buf, tl_length);
+ free(buf);
+ if (sz != tl_length) return ENOMEM;
+ } else {
+ if (strcmp(nexttoken(&p, 0, "'-1' field"), "-1") != 0) return EINVAL;
+ }
+ free(reading_what);
+ }
+
+ for (i = 0; i < num_key_data; i++) {
+ unsigned char *buf;
+ int key_versions;
+ int kvno;
+ int keytype;
+ int keylen;
+ size_t k;
+
+ if (getint(&p, "key data 'version'", &key_versions)) return EINVAL;
+ CHECK_UINT16(key_versions);
+ ret = krb5_store_int16(sp, key_versions);
+ if (ret) return ret;
+
+ if (getint(&p, "kvno", &kvno)) return EINVAL;
+ CHECK_UINT16(kvno);
+ ret = krb5_store_int16(sp, kvno);
+ if (ret) return ret;
+
+ for (k = 0; k < key_versions; k++) {
+ if (getint(&p, "enctype", &keytype)) return EINVAL;
+ CHECK_UINT16(keytype);
+ ret = krb5_store_int16(sp, keytype);
+ if (ret) return ret;
+
+ if (getint(&p, "encrypted key length", &keylen)) return EINVAL;
+ CHECK_UINT16(keylen);
+ ret = krb5_store_int16(sp, keylen);
+ if (ret) return ret;
+
+ if (keylen) {
+ buf = malloc(keylen);
+ if (!buf) return ENOMEM;
+ if (getdata(&p, buf, keylen, "key (or salt) data") != keylen) {
+ free(buf);
+ return EINVAL;
+ }
+ sz = krb5_storage_write(sp, buf, keylen);
+ free(buf);
+ if (sz != keylen) return ENOMEM;
+ } else {
+ if (strcmp(nexttoken(&p, 0,
+ "'-1' zero-length key/salt field"),
+ "-1") != 0) {
+ warnx("Expected '-1' field because key/salt length is 0");
+ return -1;
+ }
+ }
+ }
+ }
+ /*
+ * The rest is "extra data", but there's never any and we wouldn't
+ * know what to do with it.
+ */
+ /* nexttoken(&p, 0, "extra data"); */
+ return 0;
+}
+
diff --git a/third_party/heimdal/lib/hdb/hdb-sqlite.c b/third_party/heimdal/lib/hdb/hdb-sqlite.c
new file mode 100644
index 0000000..4bb2f8e
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/hdb-sqlite.c
@@ -0,0 +1,1075 @@
+/*
+ * Copyright (c) 2009 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "hdb_locl.h"
+#include "sqlite3.h"
+
+#define MAX_RETRIES 10
+
+typedef struct hdb_sqlite_db {
+ double version;
+ sqlite3 *db;
+ char *db_file;
+
+ sqlite3_stmt *connect;
+ sqlite3_stmt *get_version;
+ sqlite3_stmt *fetch;
+ sqlite3_stmt *get_ids;
+ sqlite3_stmt *add_entry;
+ sqlite3_stmt *add_principal;
+ sqlite3_stmt *add_alias;
+ sqlite3_stmt *delete_aliases;
+ sqlite3_stmt *update_entry;
+ sqlite3_stmt *remove;
+ sqlite3_stmt *get_all_entries;
+
+} hdb_sqlite_db;
+
+/* This should be used to mark updates which make the code incompatible
+ * with databases created with previous versions. Don't update it if
+ * compatibility is not broken. */
+#define HDBSQLITE_VERSION 0.1
+
+#define _HDBSQLITE_STRINGIFY(x) #x
+#define HDBSQLITE_STRINGIFY(x) _HDBSQLITE_STRINGIFY(x)
+
+#define HDBSQLITE_CREATE_TABLES \
+ " BEGIN TRANSACTION;" \
+ " CREATE TABLE Version (number REAL);" \
+ " INSERT INTO Version (number)" \
+ " VALUES (" HDBSQLITE_STRINGIFY(HDBSQLITE_VERSION) ");" \
+ " CREATE TABLE Principal" \
+ " (id INTEGER PRIMARY KEY," \
+ " principal TEXT UNIQUE NOT NULL," \
+ " canonical INTEGER," \
+ " entry INTEGER);" \
+ " CREATE TABLE Entry" \
+ " (id INTEGER PRIMARY KEY," \
+ " data BLOB);" \
+ " COMMIT"
+#define HDBSQLITE_CREATE_TRIGGERS \
+ " CREATE TRIGGER remove_principals AFTER DELETE ON Entry" \
+ " BEGIN" \
+ " DELETE FROM Principal" \
+ " WHERE entry = OLD.id;" \
+ " END"
+#define HDBSQLITE_CONNECT \
+ " PRAGMA journal_mode = WAL"
+#define HDBSQLITE_GET_VERSION \
+ " SELECT number FROM Version"
+#define HDBSQLITE_FETCH \
+ " SELECT Entry.data FROM Principal, Entry" \
+ " WHERE Principal.principal = ? AND" \
+ " Entry.id = Principal.entry"
+#define HDBSQLITE_GET_IDS \
+ " SELECT id, entry FROM Principal" \
+ " WHERE principal = ?"
+#define HDBSQLITE_ADD_ENTRY \
+ " INSERT INTO Entry (data) VALUES (?)"
+#define HDBSQLITE_ADD_PRINCIPAL \
+ " INSERT INTO Principal (principal, entry, canonical)" \
+ " VALUES (?, last_insert_rowid(), 1)"
+#define HDBSQLITE_ADD_ALIAS \
+ " INSERT INTO Principal (principal, entry, canonical)" \
+ " VALUES(?, ?, 0)"
+#define HDBSQLITE_DELETE_ALIASES \
+ " DELETE FROM Principal" \
+ " WHERE entry = ? AND canonical = 0"
+#define HDBSQLITE_UPDATE_ENTRY \
+ " UPDATE Entry SET data = ?" \
+ " WHERE id = ?"
+#define HDBSQLITE_REMOVE \
+ " DELETE FROM ENTRY WHERE id = " \
+ " (SELECT entry FROM Principal" \
+ " WHERE principal = ?)"
+#define HDBSQLITE_GET_ALL_ENTRIES \
+ " SELECT data FROM Entry"
+
+/**
+ * Wrapper around sqlite3_prepare_v2.
+ *
+ * @param context The current krb5 context
+ * @param statement Where to store the pointer to the statement
+ * after preparing it
+ * @param str SQL code for the statement
+ *
+ * @return 0 if OK, an error code if not
+ */
+static krb5_error_code
+hdb_sqlite_prepare_stmt(krb5_context context,
+ sqlite3 *db,
+ sqlite3_stmt **statement,
+ const char *str)
+{
+ int ret, tries = 0;
+
+ ret = sqlite3_prepare_v2(db, str, -1, statement, NULL);
+ while((tries++ < MAX_RETRIES) &&
+ ((ret == SQLITE_BUSY) ||
+ (ret == SQLITE_IOERR_BLOCKED) ||
+ (ret == SQLITE_LOCKED))) {
+ krb5_warnx(context, "hdb-sqlite: prepare busy");
+ sleep(1);
+ ret = sqlite3_prepare_v2(db, str, -1, statement, NULL);
+ }
+
+ if (ret != SQLITE_OK) {
+ krb5_set_error_message(context, HDB_ERR_UK_RERROR,
+ "Failed to prepare stmt %s: %s",
+ str, sqlite3_errmsg(db));
+ return HDB_ERR_UK_RERROR;
+ }
+
+ return 0;
+}
+
+static krb5_error_code
+prep_stmts(krb5_context context, hdb_sqlite_db *hsdb)
+{
+ int ret;
+
+ ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
+ &hsdb->connect,
+ HDBSQLITE_CONNECT);
+ if (ret)
+ return ret;
+ ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
+ &hsdb->get_version,
+ HDBSQLITE_GET_VERSION);
+ if (ret)
+ return ret;
+ ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
+ &hsdb->fetch,
+ HDBSQLITE_FETCH);
+ if (ret)
+ return ret;
+ ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
+ &hsdb->get_ids,
+ HDBSQLITE_GET_IDS);
+ if (ret)
+ return ret;
+ ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
+ &hsdb->add_entry,
+ HDBSQLITE_ADD_ENTRY);
+ if (ret)
+ return ret;
+ ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
+ &hsdb->add_principal,
+ HDBSQLITE_ADD_PRINCIPAL);
+ if (ret)
+ return ret;
+ ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
+ &hsdb->add_alias,
+ HDBSQLITE_ADD_ALIAS);
+ if (ret)
+ return ret;
+ ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
+ &hsdb->delete_aliases,
+ HDBSQLITE_DELETE_ALIASES);
+ if (ret)
+ return ret;
+ ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
+ &hsdb->update_entry,
+ HDBSQLITE_UPDATE_ENTRY);
+ if (ret)
+ return ret;
+ ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
+ &hsdb->remove,
+ HDBSQLITE_REMOVE);
+ if (ret)
+ return ret;
+ ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
+ &hsdb->get_all_entries,
+ HDBSQLITE_GET_ALL_ENTRIES);
+ return ret;
+}
+
+static void
+finalize_stmts(krb5_context context, hdb_sqlite_db *hsdb)
+{
+ if (hsdb->connect != NULL)
+ sqlite3_finalize(hsdb->connect);
+ hsdb->connect = NULL;
+
+ if (hsdb->get_version != NULL)
+ sqlite3_finalize(hsdb->get_version);
+ hsdb->get_version = NULL;
+
+ if (hsdb->fetch != NULL)
+ sqlite3_finalize(hsdb->fetch);
+ hsdb->fetch = NULL;
+
+ if (hsdb->get_ids != NULL)
+ sqlite3_finalize(hsdb->get_ids);
+ hsdb->get_ids = NULL;
+
+ if (hsdb->add_entry != NULL)
+ sqlite3_finalize(hsdb->add_entry);
+ hsdb->add_entry = NULL;
+
+ if (hsdb->add_principal != NULL)
+ sqlite3_finalize(hsdb->add_principal);
+ hsdb->add_principal = NULL;
+
+ if (hsdb->add_alias != NULL)
+ sqlite3_finalize(hsdb->add_alias);
+ hsdb->add_alias = NULL;
+
+ if (hsdb->delete_aliases != NULL)
+ sqlite3_finalize(hsdb->delete_aliases);
+ hsdb->delete_aliases = NULL;
+
+ if (hsdb->update_entry != NULL)
+ sqlite3_finalize(hsdb->update_entry);
+ hsdb->update_entry = NULL;
+
+ if (hsdb->remove != NULL)
+ sqlite3_finalize(hsdb->remove);
+ hsdb->remove = NULL;
+
+ if (hsdb->get_all_entries != NULL)
+ sqlite3_finalize(hsdb->get_all_entries);
+ hsdb->get_all_entries = NULL;
+}
+
+/**
+ * A wrapper around sqlite3_exec.
+ *
+ * @param context The current krb5 context
+ * @param database An open sqlite3 database handle
+ * @param statement SQL code to execute
+ * @param error_code What to return if the statement fails
+ *
+ * @return 0 if OK, else error_code
+ */
+static krb5_error_code
+hdb_sqlite_exec_stmt(krb5_context context,
+ hdb_sqlite_db *hsdb,
+ const char *statement,
+ krb5_error_code error_code)
+{
+ int ret;
+ int reinit_stmts = 0;
+ sqlite3 *database = hsdb->db;
+
+ ret = sqlite3_exec(database, statement, NULL, NULL, NULL);
+
+ while(((ret == SQLITE_BUSY) ||
+ (ret == SQLITE_IOERR_BLOCKED) ||
+ (ret == SQLITE_LOCKED))) {
+ if (reinit_stmts == 0 && ret == SQLITE_BUSY) {
+ finalize_stmts(context, hsdb);
+ reinit_stmts = 1;
+ }
+ krb5_warnx(context, "hdb-sqlite: exec busy: %d", (int)getpid());
+ sleep(1);
+ ret = sqlite3_exec(database, statement, NULL, NULL, NULL);
+ }
+
+ if (ret != SQLITE_OK && error_code) {
+ krb5_set_error_message(context, error_code,
+ "Execute %s: %s", statement,
+ sqlite3_errmsg(database));
+ return error_code;
+ }
+
+ if (reinit_stmts)
+ return prep_stmts(context, hsdb);
+
+ return 0;
+}
+
+/**
+ *
+ */
+
+static krb5_error_code
+bind_principal(krb5_context context, krb5_const_principal principal, sqlite3_stmt *stmt, int key)
+{
+ krb5_error_code ret;
+ char *str = NULL;
+
+ ret = krb5_unparse_name(context, principal, &str);
+ if (ret)
+ return ret;
+
+ sqlite3_bind_text(stmt, key, str, -1, SQLITE_TRANSIENT);
+ free(str);
+ return 0;
+}
+
+static int hdb_sqlite_step(krb5_context, sqlite3 *, sqlite3_stmt *);
+
+/**
+ * Opens an sqlite3 database handle to a file, may create the
+ * database file depending on flags.
+ *
+ * @param context The current krb5 context
+ * @param db Heimdal database handle
+ * @param flags Controls whether or not the file may be created,
+ * may be 0 or SQLITE_OPEN_CREATE
+ */
+static krb5_error_code
+hdb_sqlite_open_database(krb5_context context, HDB *db, int flags)
+{
+ int ret;
+ hdb_sqlite_db *hsdb = (hdb_sqlite_db*) db->hdb_db;
+
+ ret = sqlite3_open_v2(hsdb->db_file, &hsdb->db,
+ SQLITE_OPEN_READWRITE | flags, NULL);
+
+ if (ret) {
+ if (hsdb->db) {
+ ret = ENOENT;
+ krb5_set_error_message(context, ret,
+ "Error opening sqlite database %s: %s",
+ hsdb->db_file, sqlite3_errmsg(hsdb->db));
+ sqlite3_close(hsdb->db);
+ hsdb->db = NULL;
+ } else
+ ret = krb5_enomem(context);
+ return ret;
+ }
+ return 0;
+}
+
+static int
+hdb_sqlite_step(krb5_context context, sqlite3 *db, sqlite3_stmt *stmt)
+{
+ int ret;
+
+ ret = sqlite3_step(stmt);
+ while(((ret == SQLITE_BUSY) ||
+ (ret == SQLITE_IOERR_BLOCKED) ||
+ (ret == SQLITE_LOCKED))) {
+ krb5_warnx(context, "hdb-sqlite: step busy: %d", (int)getpid());
+ sleep(1);
+ ret = sqlite3_step(stmt);
+ }
+ return ret;
+}
+
+/**
+ * Closes the database and frees memory allocated for statements.
+ *
+ * @param context The current krb5 context
+ * @param db Heimdal database handle
+ */
+static krb5_error_code
+hdb_sqlite_close_database(krb5_context context, HDB *db)
+{
+ hdb_sqlite_db *hsdb = (hdb_sqlite_db *) db->hdb_db;
+
+ finalize_stmts(context, hsdb);
+
+ /* XXX Use sqlite3_close_v2() when we upgrade SQLite3 */
+ if (sqlite3_close(hsdb->db) != SQLITE_OK) {
+ krb5_set_error_message(context, HDB_ERR_UK_SERROR,
+ "SQLite BEGIN TRANSACTION failed: %s",
+ sqlite3_errmsg(hsdb->db));
+ return HDB_ERR_UK_SERROR;
+ }
+
+ return 0;
+}
+
+/**
+ * Opens an sqlite database file and prepares it for use.
+ * If the file does not exist it will be created.
+ *
+ * @param context The current krb5_context
+ * @param db The heimdal database handle
+ * @param filename Where to store the database file
+ *
+ * @return 0 if everything worked, an error code if not
+ */
+static krb5_error_code
+hdb_sqlite_make_database(krb5_context context, HDB *db, const char *filename)
+{
+ int ret;
+ int created_file = 0;
+ hdb_sqlite_db *hsdb = (hdb_sqlite_db *) db->hdb_db;
+
+ hsdb->db_file = strdup(filename);
+ if(hsdb->db_file == NULL)
+ return ENOMEM;
+
+ ret = hdb_sqlite_open_database(context, db, 0);
+ if (ret) {
+ ret = hdb_sqlite_open_database(context, db, SQLITE_OPEN_CREATE);
+ if (ret) goto out;
+
+ created_file = 1;
+
+ hdb_sqlite_exec_stmt(context, hsdb,
+ "PRAGMA main.page_size = 8192",
+ HDB_ERR_UK_SERROR);
+
+ ret = hdb_sqlite_exec_stmt(context, hsdb,
+ HDBSQLITE_CREATE_TABLES,
+ HDB_ERR_UK_SERROR);
+ if (ret) goto out;
+
+ ret = hdb_sqlite_exec_stmt(context, hsdb,
+ HDBSQLITE_CREATE_TRIGGERS,
+ HDB_ERR_UK_SERROR);
+ if (ret) goto out;
+ }
+
+ ret = prep_stmts(context, hsdb);
+ if (ret) goto out;
+
+ sqlite3_reset(hsdb->connect);
+ (void) hdb_sqlite_step(context, hsdb->db, hsdb->connect);
+ sqlite3_reset(hsdb->connect);
+
+ ret = hdb_sqlite_step(context, hsdb->db, hsdb->get_version);
+ if(ret == SQLITE_ROW) {
+ hsdb->version = sqlite3_column_double(hsdb->get_version, 0);
+ }
+ sqlite3_reset(hsdb->get_version);
+ ret = 0;
+
+ if(hsdb->version != HDBSQLITE_VERSION) {
+ ret = HDB_ERR_UK_SERROR;
+ krb5_set_error_message(context, ret, "HDBSQLITE_VERSION mismatch");
+ }
+
+ if(ret) goto out;
+
+ return 0;
+
+ out:
+ if (hsdb->db)
+ sqlite3_close(hsdb->db);
+ if (created_file)
+ unlink(hsdb->db_file);
+ free(hsdb->db_file);
+ hsdb->db_file = NULL;
+
+ return ret;
+}
+
+/**
+ * Retrieves an entry by searching for the given
+ * principal in the Principal database table, both
+ * for canonical principals and aliases.
+ *
+ * @param context The current krb5_context
+ * @param db Heimdal database handle
+ * @param principal The principal whose entry to search for
+ * @param flags Currently only for HDB_F_DECRYPT
+ * @param kvno kvno to fetch is HDB_F_KVNO_SPECIFIED use used
+ *
+ * @return 0 if everything worked, an error code if not
+ */
+static krb5_error_code
+hdb_sqlite_fetch_kvno(krb5_context context, HDB *db, krb5_const_principal principal,
+ unsigned flags, krb5_kvno kvno, hdb_entry *entry)
+{
+ int sqlite_error;
+ krb5_error_code ret;
+ hdb_sqlite_db *hsdb = (hdb_sqlite_db*)(db->hdb_db);
+ sqlite3_stmt *fetch = hsdb->fetch;
+ krb5_data value;
+ krb5_principal enterprise_principal = NULL;
+
+ if (principal->name.name_type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
+ if (principal->name.name_string.len != 1) {
+ ret = KRB5_PARSE_MALFORMED;
+ krb5_set_error_message(context, ret, "malformed principal: "
+ "enterprise name with %d name components",
+ principal->name.name_string.len);
+ return ret;
+ }
+ ret = krb5_parse_name(context, principal->name.name_string.val[0],
+ &enterprise_principal);
+ if (ret)
+ return ret;
+ principal = enterprise_principal;
+ }
+
+ ret = bind_principal(context, principal, fetch, 1);
+ krb5_free_principal(context, enterprise_principal);
+ if (ret)
+ return ret;
+
+ sqlite_error = hdb_sqlite_step(context, hsdb->db, fetch);
+ if (sqlite_error != SQLITE_ROW) {
+ if(sqlite_error == SQLITE_DONE) {
+ ret = HDB_ERR_NOENTRY;
+ goto out;
+ } else {
+ ret = HDB_ERR_UK_RERROR;
+ krb5_set_error_message(context, ret,
+ "sqlite fetch failed: %d",
+ sqlite_error);
+ goto out;
+ }
+ }
+
+ value.length = sqlite3_column_bytes(fetch, 0);
+ value.data = (void *) sqlite3_column_blob(fetch, 0);
+
+ ret = hdb_value2entry(context, &value, entry);
+ if(ret)
+ goto out;
+
+ if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
+ ret = hdb_unseal_keys(context, db, entry);
+ if(ret) {
+ hdb_free_entry(context, db, entry);
+ goto out;
+ }
+ }
+
+ ret = 0;
+
+out:
+
+ sqlite3_clear_bindings(fetch);
+ sqlite3_reset(fetch);
+
+
+ return ret;
+}
+
+/**
+ * Convenience function to step a prepared statement with no
+ * value once.
+ *
+ * @param context The current krb5_context
+ * @param statement A prepared sqlite3 statement
+ *
+ * @return 0 if everything worked, an error code if not
+ */
+static krb5_error_code
+hdb_sqlite_step_once(krb5_context context, HDB *db, sqlite3_stmt *statement)
+{
+ int ret;
+ hdb_sqlite_db *hsdb = (hdb_sqlite_db *) db->hdb_db;
+
+ ret = hdb_sqlite_step(context, hsdb->db, statement);
+ sqlite3_clear_bindings(statement);
+ sqlite3_reset(statement);
+
+ return ret;
+}
+
+
+/**
+ * Stores an hdb_entry in the database. If flags contains HDB_F_REPLACE
+ * a previous entry may be replaced.
+ *
+ * @param context The current krb5_context
+ * @param db Heimdal database handle
+ * @param flags May currently only contain HDB_F_REPLACE
+ * @param entry The data to store
+ *
+ * @return 0 if everything worked, an error code if not
+ */
+static krb5_error_code
+hdb_sqlite_store(krb5_context context, HDB *db, unsigned flags,
+ hdb_entry *entry)
+{
+ int ret;
+ int i;
+ sqlite_int64 entry_id;
+ const HDB_Ext_Aliases *aliases;
+
+ hdb_sqlite_db *hsdb = (hdb_sqlite_db *)(db->hdb_db);
+ krb5_data value;
+ sqlite3_stmt *get_ids = hsdb->get_ids;
+
+ krb5_data_zero(&value);
+
+ ret = hdb_sqlite_exec_stmt(context, hsdb,
+ "BEGIN IMMEDIATE TRANSACTION",
+ HDB_ERR_UK_SERROR);
+ if(ret != SQLITE_OK) {
+ ret = HDB_ERR_UK_SERROR;
+ krb5_set_error_message(context, ret,
+ "SQLite BEGIN TRANSACTION failed: %s",
+ sqlite3_errmsg(hsdb->db));
+ goto rollback;
+ }
+
+ ret = hdb_seal_keys(context, db, entry);
+ if(ret) {
+ goto rollback;
+ }
+
+ ret = hdb_entry2value(context, entry, &value);
+ if(ret) {
+ goto rollback;
+ }
+
+ ret = bind_principal(context, entry->principal, get_ids, 1);
+ if (ret)
+ goto rollback;
+
+ ret = hdb_sqlite_step(context, hsdb->db, get_ids);
+
+ if(ret == SQLITE_DONE) { /* No such principal */
+
+ sqlite3_bind_blob(hsdb->add_entry, 1,
+ value.data, value.length, SQLITE_STATIC);
+ ret = hdb_sqlite_step(context, hsdb->db, hsdb->add_entry);
+ sqlite3_clear_bindings(hsdb->add_entry);
+ sqlite3_reset(hsdb->add_entry);
+ if (ret != SQLITE_DONE && ret != SQLITE_CONSTRAINT) {
+ ret = HDB_ERR_UK_SERROR;
+ goto rollback;
+ }
+ if (ret == SQLITE_CONSTRAINT) {
+ ret = HDB_ERR_EXISTS;
+ goto rollback;
+ }
+
+ ret = bind_principal(context, entry->principal, hsdb->add_principal, 1);
+ if (ret)
+ goto rollback;
+
+ ret = hdb_sqlite_step(context, hsdb->db, hsdb->add_principal);
+ sqlite3_clear_bindings(hsdb->add_principal);
+ sqlite3_reset(hsdb->add_principal);
+ if (ret != SQLITE_DONE && ret != SQLITE_CONSTRAINT) {
+ ret = HDB_ERR_UK_SERROR;
+ goto rollback;
+ }
+ if (ret == SQLITE_CONSTRAINT) {
+ ret = HDB_ERR_EXISTS;
+ goto rollback;
+ }
+
+ /* Now let's learn what Entry ID we got for the new principal */
+ sqlite3_reset(get_ids);
+ ret = hdb_sqlite_step(context, hsdb->db, get_ids);
+ if (ret != SQLITE_ROW) {
+ ret = HDB_ERR_UK_SERROR;
+ goto rollback;
+ }
+
+ entry_id = sqlite3_column_int64(get_ids, 1);
+
+ } else if(ret == SQLITE_ROW) { /* Found a principal */
+
+ if(!(flags & HDB_F_REPLACE)) {
+ ret = HDB_ERR_EXISTS;
+ goto rollback;
+ }
+
+ entry_id = sqlite3_column_int64(get_ids, 1);
+
+ sqlite3_bind_int64(hsdb->delete_aliases, 1, entry_id);
+ ret = hdb_sqlite_step_once(context, db, hsdb->delete_aliases);
+ if (ret != SQLITE_DONE) {
+ ret = HDB_ERR_UK_SERROR;
+ goto rollback;
+ }
+
+ sqlite3_bind_blob(hsdb->update_entry, 1,
+ value.data, value.length, SQLITE_STATIC);
+ sqlite3_bind_int64(hsdb->update_entry, 2, entry_id);
+ ret = hdb_sqlite_step_once(context, db, hsdb->update_entry);
+ if (ret != SQLITE_DONE) {
+ ret = HDB_ERR_UK_SERROR;
+ goto rollback;
+ }
+
+ } else {
+ /* Error! */
+ ret = HDB_ERR_UK_SERROR;
+ goto rollback;
+ }
+
+ ret = hdb_entry_get_aliases(entry, &aliases);
+ if(ret || aliases == NULL)
+ goto commit;
+
+ for(i = 0; i < aliases->aliases.len; i++) {
+
+ ret = bind_principal(context, &aliases->aliases.val[i], hsdb->add_alias, 1);
+ if (ret)
+ goto rollback;
+
+ sqlite3_bind_int64(hsdb->add_alias, 2, entry_id);
+ ret = hdb_sqlite_step_once(context, db, hsdb->add_alias);
+ if (ret == SQLITE_CONSTRAINT) {
+ ret = HDB_ERR_EXISTS;
+ goto rollback;
+ }
+ if (ret != SQLITE_DONE) {
+ ret = HDB_ERR_UK_SERROR;
+ goto rollback;
+ }
+ }
+
+commit:
+ krb5_data_free(&value);
+ sqlite3_clear_bindings(get_ids);
+ sqlite3_reset(get_ids);
+
+ if ((flags & HDB_F_PRECHECK)) {
+ (void) hdb_sqlite_exec_stmt(context, hsdb, "ROLLBACK", 0);
+ return 0;
+ }
+
+ ret = hdb_sqlite_exec_stmt(context, hsdb, "COMMIT", HDB_ERR_UK_SERROR);
+ if(ret != SQLITE_OK)
+ krb5_warnx(context, "hdb-sqlite: COMMIT problem: %ld: %s",
+ (long)HDB_ERR_UK_SERROR, sqlite3_errmsg(hsdb->db));
+
+ return ret == SQLITE_OK ? 0 : HDB_ERR_UK_SERROR;
+
+rollback:
+ krb5_data_free(&value);
+ sqlite3_clear_bindings(get_ids);
+ sqlite3_reset(get_ids);
+ krb5_warnx(context, "hdb-sqlite: store rollback problem: %d: %s",
+ ret, sqlite3_errmsg(hsdb->db));
+
+ (void) hdb_sqlite_exec_stmt(context, hsdb, "ROLLBACK", 0);
+ return ret;
+}
+
+/**
+ * This may be called often by other code, since the BDB backends
+ * can not have several open connections. SQLite can handle
+ * many processes with open handles to the database file
+ * and closing/opening the handle is an expensive operation.
+ * Hence, this function does nothing.
+ *
+ * @param context The current krb5 context
+ * @param db Heimdal database handle
+ *
+ * @return Always returns 0
+ */
+static krb5_error_code
+hdb_sqlite_close(krb5_context context, HDB *db)
+{
+ return 0;
+}
+
+/**
+ * The opposite of hdb_sqlite_close. Since SQLite accepts
+ * many open handles to the database file the handle does not
+ * need to be closed, or reopened.
+ *
+ * @param context The current krb5 context
+ * @param db Heimdal database handle
+ * @param flags
+ * @param mode_t
+ *
+ * @return Always returns 0
+ */
+static krb5_error_code
+hdb_sqlite_open(krb5_context context, HDB *db, int flags, mode_t mode)
+{
+ return 0;
+}
+
+/**
+ * Closes the databse and frees all resources.
+ *
+ * @param context The current krb5 context
+ * @param db Heimdal database handle
+ *
+ * @return 0 on success, an error code if not
+ */
+static krb5_error_code
+hdb_sqlite_destroy(krb5_context context, HDB *db)
+{
+ int ret, ret2;
+ hdb_sqlite_db *hsdb;
+
+ ret = hdb_clear_master_key(context, db);
+
+ ret2 = hdb_sqlite_close_database(context, db);
+
+ hsdb = (hdb_sqlite_db*)(db->hdb_db);
+
+ krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
+ free(hsdb->db_file);
+ free(db->hdb_name);
+ free(db->hdb_db);
+ free(db);
+
+ return ret ? ret : ret2;
+}
+
+static krb5_error_code
+hdb_sqlite_set_sync(krb5_context context, HDB *db, int on)
+{
+ return hdb_sqlite_exec_stmt(context, (hdb_sqlite_db*)(db->hdb_db),
+ on ? "PRAGMA main.synchronous = NORMAL" :
+ "PRAGMA main.synchronous = OFF",
+ HDB_ERR_UK_SERROR);
+}
+
+/*
+ * Not sure if this is needed.
+ */
+static krb5_error_code
+hdb_sqlite_lock(krb5_context context, HDB *db, int operation)
+{
+ krb5_set_error_message(context, HDB_ERR_CANT_LOCK_DB,
+ "lock not implemented");
+ return HDB_ERR_CANT_LOCK_DB;
+}
+
+/*
+ * Not sure if this is needed.
+ */
+static krb5_error_code
+hdb_sqlite_unlock(krb5_context context, HDB *db)
+{
+ krb5_set_error_message(context, HDB_ERR_CANT_LOCK_DB,
+ "unlock not implemented");
+ return HDB_ERR_CANT_LOCK_DB;
+}
+
+/*
+ * Should get the next entry, to allow iteration over all entries.
+ */
+static krb5_error_code
+hdb_sqlite_nextkey(krb5_context context, HDB *db, unsigned flags,
+ hdb_entry *entry)
+{
+ krb5_error_code ret = 0;
+ int sqlite_error;
+ krb5_data value;
+
+ hdb_sqlite_db *hsdb = (hdb_sqlite_db *) db->hdb_db;
+
+ sqlite_error = hdb_sqlite_step(context, hsdb->db, hsdb->get_all_entries);
+ if(sqlite_error == SQLITE_ROW) {
+ /* Found an entry */
+ value.length = sqlite3_column_bytes(hsdb->get_all_entries, 0);
+ value.data = (void *) sqlite3_column_blob(hsdb->get_all_entries, 0);
+ memset(entry, 0, sizeof(*entry));
+ ret = hdb_value2entry(context, &value, entry);
+ }
+ else if(sqlite_error == SQLITE_DONE) {
+ /* No more entries */
+ ret = HDB_ERR_NOENTRY;
+ sqlite3_reset(hsdb->get_all_entries);
+ }
+ else {
+ ret = HDB_ERR_UK_RERROR;
+ krb5_set_error_message(context, HDB_ERR_UK_RERROR,
+ "SELECT failed after returning one or "
+ "more rows: %s", sqlite3_errmsg(hsdb->db));
+
+ }
+
+ return ret;
+}
+
+/*
+ * Should get the first entry in the database.
+ * What is flags used for?
+ */
+static krb5_error_code
+hdb_sqlite_firstkey(krb5_context context, HDB *db, unsigned flags,
+ hdb_entry *entry)
+{
+ hdb_sqlite_db *hsdb = (hdb_sqlite_db *) db->hdb_db;
+ krb5_error_code ret;
+
+ sqlite3_reset(hsdb->get_all_entries);
+
+ ret = hdb_sqlite_nextkey(context, db, flags, entry);
+ if(ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * Renames the database file.
+ */
+static krb5_error_code
+hdb_sqlite_rename(krb5_context context, HDB *db, const char *new_name)
+{
+ krb5_error_code ret, ret2;
+ hdb_sqlite_db *hsdb = (hdb_sqlite_db *) db->hdb_db;
+
+ krb5_warnx(context, "hdb_sqlite_rename");
+
+ if (strncasecmp(new_name, "sqlite:", 7) == 0)
+ new_name += 7;
+
+ ret = hdb_sqlite_close_database(context, db);
+
+ if (rename(hsdb->db_file, new_name) == -1)
+ return errno;
+
+ free(hsdb->db_file);
+ ret2 = hdb_sqlite_make_database(context, db, new_name);
+ return ret ? ret : ret2;
+}
+
+/*
+ * Removes a principal, including aliases and associated entry.
+ */
+static krb5_error_code
+hdb_sqlite_remove(krb5_context context, HDB *db,
+ unsigned flags, krb5_const_principal principal)
+{
+ krb5_error_code ret;
+ hdb_sqlite_db *hsdb = (hdb_sqlite_db*)(db->hdb_db);
+ sqlite3_stmt *get_ids = hsdb->get_ids;
+ sqlite3_stmt *rm = hsdb->remove;
+
+ ret = bind_principal(context, principal, rm, 1);
+
+ if (ret == 0)
+ ret = hdb_sqlite_exec_stmt(context, hsdb,
+ "BEGIN IMMEDIATE TRANSACTION",
+ HDB_ERR_UK_SERROR);
+ if (ret != SQLITE_OK) {
+ ret = HDB_ERR_UK_SERROR;
+ (void) hdb_sqlite_exec_stmt(context, hsdb, "ROLLBACK", 0);
+ krb5_set_error_message(context, ret,
+ "SQLite BEGIN TRANSACTION failed: %s",
+ sqlite3_errmsg(hsdb->db));
+ return ret;
+ }
+
+ if ((flags & HDB_F_PRECHECK)) {
+ ret = bind_principal(context, principal, get_ids, 1);
+ if (ret)
+ return ret;
+
+ ret = hdb_sqlite_step(context, hsdb->db, get_ids);
+ sqlite3_clear_bindings(get_ids);
+ sqlite3_reset(get_ids);
+ if (ret == SQLITE_DONE) {
+ (void) hdb_sqlite_exec_stmt(context, hsdb, "ROLLBACK", 0);
+ return HDB_ERR_NOENTRY;
+ }
+ }
+
+ ret = hdb_sqlite_step(context, hsdb->db, rm);
+ sqlite3_clear_bindings(rm);
+ sqlite3_reset(rm);
+ if (ret != SQLITE_DONE) {
+ (void) hdb_sqlite_exec_stmt(context, hsdb, "ROLLBACK", 0);
+ ret = HDB_ERR_UK_SERROR;
+ krb5_set_error_message(context, ret, "sqlite remove failed: %d", ret);
+ return ret;
+ }
+
+ if ((flags & HDB_F_PRECHECK)) {
+ (void) hdb_sqlite_exec_stmt(context, hsdb, "ROLLBACK", 0);
+ return 0;
+ }
+
+ ret = hdb_sqlite_exec_stmt(context, hsdb, "COMMIT", HDB_ERR_UK_SERROR);
+ if (ret != SQLITE_OK)
+ krb5_warnx(context, "hdb-sqlite: COMMIT problem: %ld: %s",
+ (long)HDB_ERR_UK_SERROR, sqlite3_errmsg(hsdb->db));
+
+ return 0;
+}
+
+/**
+ * Create SQLITE object, and creates the on disk database if its doesn't exists.
+ *
+ * @param context A Kerberos 5 context.
+ * @param db a returned database handle.
+ * @param filename filename
+ *
+ * @return 0 on success, an error code if not
+ */
+
+krb5_error_code
+hdb_sqlite_create(krb5_context context, HDB **db, const char *filename)
+{
+ krb5_error_code ret;
+ hdb_sqlite_db *hsdb;
+
+ *db = calloc(1, sizeof (**db));
+ if (*db == NULL)
+ return krb5_enomem(context);
+
+ (*db)->hdb_name = strdup(filename);
+ if ((*db)->hdb_name == NULL) {
+ free(*db);
+ *db = NULL;
+ return krb5_enomem(context);
+ }
+
+ hsdb = (hdb_sqlite_db*) calloc(1, sizeof (*hsdb));
+ if (hsdb == NULL) {
+ free((*db)->hdb_name);
+ free(*db);
+ *db = NULL;
+ return krb5_enomem(context);
+ }
+
+ (*db)->hdb_db = hsdb;
+
+ /* XXX make_database should make sure everything else is freed on error */
+ ret = hdb_sqlite_make_database(context, *db, filename);
+ if (ret) {
+ free((*db)->hdb_db);
+ free(*db);
+ *db = NULL;
+ return ret;
+ }
+
+ (*db)->hdb_master_key_set = 0;
+ (*db)->hdb_openp = 0;
+ (*db)->hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL;
+
+ (*db)->hdb_open = hdb_sqlite_open;
+ (*db)->hdb_close = hdb_sqlite_close;
+
+ (*db)->hdb_lock = hdb_sqlite_lock;
+ (*db)->hdb_unlock = hdb_sqlite_unlock;
+ (*db)->hdb_firstkey = hdb_sqlite_firstkey;
+ (*db)->hdb_nextkey = hdb_sqlite_nextkey;
+ (*db)->hdb_fetch_kvno = hdb_sqlite_fetch_kvno;
+ (*db)->hdb_store = hdb_sqlite_store;
+ (*db)->hdb_remove = hdb_sqlite_remove;
+ (*db)->hdb_destroy = hdb_sqlite_destroy;
+ (*db)->hdb_rename = hdb_sqlite_rename;
+ (*db)->hdb_set_sync = hdb_sqlite_set_sync;
+ (*db)->hdb__get = NULL;
+ (*db)->hdb__put = NULL;
+ (*db)->hdb__del = NULL;
+
+ return 0;
+}
diff --git a/third_party/heimdal/lib/hdb/hdb.asn1 b/third_party/heimdal/lib/hdb/hdb.asn1
new file mode 100644
index 0000000..b0bfbf9
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/hdb.asn1
@@ -0,0 +1,251 @@
+-- $Id$
+HDB DEFINITIONS ::=
+BEGIN
+
+IMPORTS EncryptionKey, KerberosTime, Principal FROM krb5;
+
+hdb_db_format INTEGER ::= 2 -- format of database,
+ -- update when making changes
+
+-- these must have the same value as the pa-* counterparts
+hdb-pw-salt INTEGER ::= 3
+hdb-afs3-salt INTEGER ::= 10
+
+Salt ::= SEQUENCE {
+ type[0] INTEGER (0..4294967295),
+ salt[1] OCTET STRING,
+ opaque[2] OCTET STRING OPTIONAL
+}
+
+Key ::= SEQUENCE {
+ mkvno[0] INTEGER (0..4294967295) OPTIONAL, -- master key version number
+ key[1] EncryptionKey,
+ salt[2] Salt OPTIONAL
+}
+
+Event ::= SEQUENCE {
+ time[0] KerberosTime,
+ principal[1] Principal OPTIONAL
+}
+
+HDBFlags ::= BIT STRING {
+ initial(0), -- require as-req
+ forwardable(1), -- may issue forwardable
+ proxiable(2), -- may issue proxiable
+ renewable(3), -- may issue renewable
+ postdate(4), -- may issue postdatable
+ server(5), -- may be server
+ client(6), -- may be client
+ invalid(7), -- entry is invalid
+ require-preauth(8), -- must use preauth
+ change-pw(9), -- change password service
+ require-hwauth(10), -- must use hwauth
+ ok-as-delegate(11), -- as in TicketFlags
+ user-to-user(12), -- may use user-to-user auth
+ immutable(13), -- may not be deleted
+ trusted-for-delegation(14), -- Trusted to print forwardabled tickets
+ allow-kerberos4(15), -- Allow Kerberos 4 requests
+ allow-digest(16), -- Allow digest requests
+ locked-out(17), -- Account is locked out,
+ -- authentication will be denied
+ require-pwchange(18), -- require a passwd change
+
+ materialize(19), -- store even if within virtual namespace
+ virtual-keys(20), -- entry stored; keys mostly derived
+ virtual(21), -- entry not stored; keys always derived
+ synthetic(22), -- entry not stored; for PKINIT
+ no-auth-data-reqd(23), -- omit PAC from service tickets
+
+ force-canonicalize(30), -- force the KDC to return the canonical
+ -- principal irrespective of the setting
+ -- of the canonicalize KDC option
+ do-not-store(31) -- Not to be modified and stored in HDB
+}
+
+GENERATION ::= SEQUENCE {
+ time[0] KerberosTime, -- timestamp
+ usec[1] INTEGER (0..4294967295), -- microseconds
+ gen[2] INTEGER (0..4294967295) -- generation number
+}
+
+HDB-Ext-PKINIT-acl ::= SEQUENCE OF SEQUENCE {
+ subject[0] UTF8String,
+ issuer[1] UTF8String OPTIONAL,
+ anchor[2] UTF8String OPTIONAL
+}
+
+HDB-Ext-PKINIT-hash ::= SEQUENCE OF SEQUENCE {
+ digest-type[0] OBJECT IDENTIFIER,
+ digest[1] OCTET STRING
+}
+
+HDB-Ext-PKINIT-cert ::= SEQUENCE OF SEQUENCE {
+ cert[0] OCTET STRING
+}
+
+HDB-Ext-Constrained-delegation-acl ::= SEQUENCE OF Principal
+
+-- hdb-ext-referrals ::= PA-SERVER-REFERRAL-DATA
+
+HDB-Ext-Lan-Manager-OWF ::= OCTET STRING
+
+HDB-Ext-Password ::= SEQUENCE {
+ mkvno[0] INTEGER (0..4294967295) OPTIONAL, -- master key version number
+ password OCTET STRING
+}
+
+HDB-Ext-Aliases ::= SEQUENCE {
+ case-insensitive[0] BOOLEAN, -- case insensitive name allowed
+ aliases[1] SEQUENCE OF Principal -- all names, inc primary
+}
+
+Keys ::= SEQUENCE OF Key
+
+HDB_keyset ::= SEQUENCE {
+ kvno[0] INTEGER (0..4294967295),
+ keys[1] Keys,
+ set-time[2] KerberosTime OPTIONAL, -- time this keyset was created/set
+ ...
+}
+
+HDB-Ext-KeySet ::= SEQUENCE OF HDB_keyset
+
+--
+-- We need a function of current (or given, but it will always be current) time
+-- and a base hdb_entry or its HDB-Ext-KeyRotation and service ticket lifetime,
+-- that outputs a sequence of {kvno, set_time, max_life} representing past keys
+-- (up to one per past and current KeyRotation), current keys (for the current
+-- KeyRotation), up to one future key for the current KeyRotation, and up to
+-- one future key for the _next_ (future) KeyRotation if there is one.
+--
+-- We have to impose constraints on new KeyRotation elements of
+-- HDB-Ext-KeyRotation.
+--
+-- So virtual keysets (keytabs) will contain:
+--
+-- - up to one past keyset for all KeyRotation periods that are "applicable"
+-- - the current keyset for all KeyRotation periods that are "applicable"
+-- - up to one future keyset for all KeyRotation periods that are "applicable"
+--
+-- An applicable KeyRotation period is:
+--
+-- - the KeyRotation whose `epoch` is a) in the past and b) nearest to the
+-- current time - we call this the current KeyRotation
+-- - a KeyRotation whose `epoch` is nearest but in the past of the current
+-- one
+-- - a KeyRotation whose `epoch` is nearest but in the future of the current
+-- one
+--
+-- A service principal's max ticket life will be bounded by half the current
+-- key rotation period.
+--
+-- Note: There can be more than one applicable past KeyRotation, and more than
+-- one applicable KeyRotation. We might not want to permit this.
+-- However, it's probably easier to permit it, though we might not test
+-- end-to-end.
+--
+-- Testing:
+--
+-- - We should have standalone unit tests for all these pure functions.
+--
+-- - We should have a test that uses kadm5 and GSS to test against a KDC using
+-- small key rotation periods on the order of seconds, with back-off in case
+-- of losing a race condition.
+--
+KeyRotationFlags ::= BIT STRING {
+ deleted(0), -- if set on a materialized principal, this will mean
+ -- the principal does not exist
+ -- if set on a namespace, this will mean that
+ -- only materialized principal below it exist
+ parent(1) -- if set on a materialized principal, this will mean
+ -- that the keys for kvnos in this KeyRotation spec
+ -- will be derived from the parent's base keys and
+ -- corresponding KeyRotation spec
+ -- if set on a namespace, this flag will be ignored
+ -- (or we could support nested namespaces?)
+}
+KeyRotation ::= SEQUENCE {
+ -- base-kvno is always computed at set time and set for the principal,
+ -- and is never subject to admin choice. The base-kvno is that of the
+ -- current kvno at that period's `from` given the previous period.
+ --
+ -- Also, insertion of KeyRotation elements before existing ones (in
+ -- time) is never permitted, and all new KeyRotation elements must be
+ -- in the future relative to existing ones.
+ --
+ -- HDB-Ext-KeyRotation will always be sorted (as stored) by `from`, in
+ -- descending order.
+ --
+ -- Max service ticket lifetime will be constrained to no more than half
+ -- the period of the the applicable KeyRotation elements.
+ --
+ flags[0] KeyRotationFlags,
+ epoch[1] KerberosTime, -- start of this period
+ period[2] INTEGER(0..4294967295), -- key rotation seconds
+ base-kvno[3] INTEGER(0..4294967295), -- starting from this kvno
+ base-key-kvno[4]INTEGER(0..4294967295), -- kvno of base-key
+ ...
+}
+
+HDB-Ext-KeyRotation ::= SEQUENCE SIZE (1..3) OF KeyRotation
+
+HDB-extension ::= SEQUENCE {
+ mandatory[0] BOOLEAN, -- kdc MUST understand this extension,
+ -- if not the whole entry must
+ -- be rejected
+ data[1] CHOICE {
+ pkinit-acl[0] HDB-Ext-PKINIT-acl,
+ pkinit-cert-hash[1] HDB-Ext-PKINIT-hash,
+ allowed-to-delegate-to[2] HDB-Ext-Constrained-delegation-acl,
+-- referral-info[3] HDB-Ext-Referrals,
+ lm-owf[4] HDB-Ext-Lan-Manager-OWF,
+ password[5] HDB-Ext-Password,
+ aliases[6] HDB-Ext-Aliases,
+ last-pw-change[7] KerberosTime,
+ pkinit-cert[8] HDB-Ext-PKINIT-cert,
+ hist-keys[9] HDB-Ext-KeySet,
+ hist-kvno-diff-clnt[10] INTEGER (0..4294967295),
+ hist-kvno-diff-svc[11] INTEGER (0..4294967295),
+ policy[12] UTF8String,
+ principal-id[13] INTEGER(-9223372036854775808..9223372036854775807),
+ key-rotation[14] HDB-Ext-KeyRotation,
+ krb5-config[15] OCTET STRING,
+ ...
+ },
+ ...
+}
+
+HDB-extensions ::= SEQUENCE OF HDB-extension
+
+-- Just for convenience, for encoding this as TL data in lib/kadm5
+HDB-EncTypeList ::= SEQUENCE OF INTEGER (0..4294967295)
+
+HDB_entry ::= SEQUENCE {
+ principal[0] Principal OPTIONAL, -- this is optional only
+ -- for compatibility with libkrb5
+ kvno[1] INTEGER (0..4294967295),
+ keys[2] Keys,
+ created-by[3] Event,
+ modified-by[4] Event OPTIONAL,
+ valid-start[5] KerberosTime OPTIONAL,
+ valid-end[6] KerberosTime OPTIONAL,
+ pw-end[7] KerberosTime OPTIONAL,
+ max-life[8] INTEGER (0..4294967295) OPTIONAL,
+ max-renew[9] INTEGER (0..4294967295) OPTIONAL,
+ flags[10] HDBFlags,
+ etypes[11] HDB-EncTypeList OPTIONAL,
+ generation[12] GENERATION OPTIONAL,
+ extensions[13] HDB-extensions OPTIONAL,
+ session-etypes[14] HDB-EncTypeList OPTIONAL
+}
+
+HDB_entry_alias ::= [APPLICATION 0] SEQUENCE {
+ principal[0] Principal OPTIONAL
+}
+
+HDB-EntryOrAlias ::= CHOICE {
+ entry HDB_entry,
+ alias HDB_entry_alias
+}
+
+END
diff --git a/third_party/heimdal/lib/hdb/hdb.c b/third_party/heimdal/lib/hdb/hdb.c
new file mode 100644
index 0000000..171ba9e
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/hdb.c
@@ -0,0 +1,848 @@
+/*
+ * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "krb5_locl.h"
+#include "hdb_locl.h"
+
+#ifdef HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+/*! @mainpage Heimdal database backend library
+ *
+ * @section intro Introduction
+ *
+ * Heimdal libhdb library provides the backend support for Heimdal kdc
+ * and kadmind. Its here where plugins for diffrent database engines
+ * can be pluged in and extend support for here Heimdal get the
+ * principal and policy data from.
+ *
+ * Example of Heimdal backend are:
+ * - Berkeley DB 1.85
+ * - Berkeley DB 3.0
+ * - Berkeley DB 4.0
+ * - New Berkeley DB
+ * - LDAP
+ *
+ *
+ * The project web page: http://www.h5l.org/
+ *
+ */
+
+const int hdb_interface_version = HDB_INTERFACE_VERSION;
+
+static struct hdb_method methods[] = {
+ /* "db:" should be db3 if we have db3, or db1 if we have db1 */
+#if HAVE_DB3
+ { HDB_INTERFACE_VERSION, NULL, NULL, 1 /*is_file_based*/, 1 /*can_taste*/,
+ "db:", hdb_db3_create},
+#elif HAVE_DB1
+ { HDB_INTERFACE_VERSION, NULL, NULL, 1, 1, "db:", hdb_db1_create},
+#endif
+#if HAVE_DB1
+ { HDB_INTERFACE_VERSION, NULL, NULL, 1, 1, "db1:", hdb_db1_create},
+#endif
+#if HAVE_DB3
+ { HDB_INTERFACE_VERSION, NULL, NULL, 1, 1, "db3:", hdb_db3_create},
+#endif
+#if HAVE_DB1
+ { HDB_INTERFACE_VERSION, NULL, NULL, 1, 1, "mit-db:", hdb_mitdb_create},
+#endif
+#if HAVE_LMDB
+ { HDB_INTERFACE_VERSION, NULL, NULL, 1, 1, "mdb:", hdb_mdb_create},
+ { HDB_INTERFACE_VERSION, NULL, NULL, 1, 1, "lmdb:", hdb_mdb_create},
+#endif
+#if HAVE_NDBM
+ { HDB_INTERFACE_VERSION, NULL, NULL, 1, 0, "ndbm:", hdb_ndbm_create},
+#endif
+#ifdef HAVE_SQLITE3
+ { HDB_INTERFACE_VERSION, NULL, NULL, 1, 1, "sqlite:", hdb_sqlite_create},
+#endif
+ /* The keytab interface can't use its hdb_open() method to "taste" a DB */
+ { HDB_INTERFACE_VERSION, NULL, NULL, 1, 0, "keytab:", hdb_keytab_create},
+ /* The rest are not file-based */
+#if defined(OPENLDAP) && !defined(OPENLDAP_MODULE)
+ { HDB_INTERFACE_VERSION, NULL, NULL, 0, 0, "ldap:", hdb_ldap_create},
+ { HDB_INTERFACE_VERSION, NULL, NULL, 0, 0, "ldapi:", hdb_ldapi_create},
+#elif defined(OPENLDAP)
+ { HDB_INTERFACE_VERSION, NULL, NULL, 0, 0, "ldap:", NULL},
+ { HDB_INTERFACE_VERSION, NULL, NULL, 0, 0, "ldapi:", NULL},
+#endif
+ { 0, NULL, NULL, 0, 0, NULL, NULL}
+};
+
+/**
+ * Returns the Keys of `e' for `kvno', or NULL if not found. The Keys will
+ * remain valid provided that the entry is not mutated.
+ *
+ * @param context Context
+ * @param e The HDB entry
+ * @param kvno The kvno
+ *
+ * @return A pointer to the Keys for the requested kvno.
+ */
+const Keys *
+hdb_kvno2keys(krb5_context context,
+ const hdb_entry *e,
+ krb5_kvno kvno)
+{
+ HDB_Ext_KeySet *hist_keys;
+ HDB_extension *extp;
+ size_t i;
+
+ if (kvno == 0 || e->kvno == kvno)
+ return &e->keys;
+
+ extp = hdb_find_extension(e, choice_HDB_extension_data_hist_keys);
+ if (extp == NULL)
+ return 0;
+
+ hist_keys = &extp->data.u.hist_keys;
+ for (i = 0; i < hist_keys->len; i++) {
+ if (hist_keys->val[i].kvno == kvno)
+ return &hist_keys->val[i].keys;
+ }
+
+ return NULL;
+}
+
+/* Based on remove_HDB_Ext_KeySet(), generated by the ASN.1 compiler */
+static int
+dequeue_HDB_Ext_KeySet(HDB_Ext_KeySet *data, unsigned int element, hdb_keyset *ks)
+{
+ if (element >= data->len) {
+ ks->kvno = 0;
+ ks->keys.len = 0;
+ ks->keys.val = 0;
+ ks->set_time = 0;
+ return ASN1_OVERRUN;
+ }
+ *ks = data->val[element];
+ data->len--;
+ /* Swap instead of memmove()... changes the order of elements */
+ if (element < data->len)
+ data->val[element] = data->val[data->len];
+ if (data->len == 0) {
+ free(data->val);
+ data->val = 0;
+ }
+ return 0;
+}
+
+
+/**
+ * Removes from `e' and optionally outputs the keyset for the requested `kvno'.
+ *
+ * @param context Context
+ * @param e The HDB entry
+ * @param kvno The key version number
+ * @param ks A pointer to a variable of type hdb_keyset (may be NULL)
+ *
+ * @return Zero on success, an error code otherwise.
+ */
+krb5_error_code
+hdb_remove_keys(krb5_context context,
+ hdb_entry *e,
+ krb5_kvno kvno,
+ hdb_keyset *ks)
+{
+ HDB_Ext_KeySet *hist_keys;
+ HDB_extension *extp;
+ size_t i;
+
+ if (kvno == 0 || e->kvno == kvno) {
+ if (ks) {
+ KerberosTime t;
+
+ (void) hdb_entry_get_pw_change_time(e, &t);
+ if (t) {
+ if ((ks->set_time = malloc(sizeof(*ks->set_time))) == NULL)
+ return krb5_enomem(context);
+ *ks->set_time = t;
+ }
+ ks->kvno = e->kvno;
+ ks->keys = e->keys;
+ e->keys.len = 0;
+ e->keys.val = NULL;
+ e->kvno = 0;
+ } else {
+ free_Keys(&e->keys);
+ }
+ return 0;
+ }
+
+ if (ks) {
+ ks->kvno = 0;
+ ks->keys.len = 0;
+ ks->keys.val = 0;
+ ks->set_time = 0;
+ }
+
+ extp = hdb_find_extension(e, choice_HDB_extension_data_hist_keys);
+ if (extp == NULL)
+ return 0;
+
+ hist_keys = &extp->data.u.hist_keys;
+ for (i = 0; i < hist_keys->len; i++) {
+ if (hist_keys->val[i].kvno != kvno)
+ continue;
+ if (ks)
+ return dequeue_HDB_Ext_KeySet(hist_keys, i, ks);
+ return remove_HDB_Ext_KeySet(hist_keys, i);
+ }
+ return HDB_ERR_NOENTRY;
+}
+
+/**
+ * Removes from `e' and outputs all the base keys for virtual principal and/or
+ * key derivation.
+ *
+ * @param context Context
+ * @param e The HDB entry
+ * @param ks A pointer to a variable of type HDB_Ext_KeySet
+ * @param ckr A pointer to stable (copied) HDB_Ext_KeyRotation
+ *
+ * @return Zero on success, an error code otherwise.
+ */
+krb5_error_code
+_hdb_remove_base_keys(krb5_context context,
+ hdb_entry *e,
+ HDB_Ext_KeySet *base_keys,
+ const HDB_Ext_KeyRotation *ckr)
+{
+ krb5_error_code ret = 0;
+ size_t i, k;
+
+ base_keys->len = 0;
+ if ((base_keys->val = calloc(ckr->len, sizeof(base_keys->val[0]))) == NULL)
+ ret = krb5_enomem(context);
+
+ for (k = i = 0; ret == 0 && i < ckr->len; i++) {
+ const KeyRotation *krp = &ckr->val[i];
+
+ /*
+ * WARNING: O(N * M) where M is number of keysets and N is the number
+ * of base keysets.
+ *
+ * In practice N will never be > 3 because the ASN.1 module imposes
+ * that as a constraint, and M will generally be the same as N, so this
+ * will be O(1) after all.
+ */
+ ret = hdb_remove_keys(context, e, krp->base_key_kvno,
+ &base_keys->val[k]);
+ if (ret == 0)
+ k++;
+ else if (ret == HDB_ERR_NOENTRY)
+ ret = 0;
+ }
+ if (ret == 0)
+ base_keys->len = k;
+ else
+ free_HDB_Ext_KeySet(base_keys);
+ return 0;
+}
+
+/**
+ * Removes from `e' and outputs all the base keys for virtual principal and/or
+ * key derivation.
+ *
+ * @param context Context
+ * @param e The HDB entry
+ * @param is_current_keyset Whether to make the keys the current keys for `e'
+ * @param ks A pointer to an hdb_keyset containing the keys to set
+ *
+ * @return Zero on success, an error code otherwise.
+ */
+krb5_error_code
+hdb_install_keyset(krb5_context context,
+ hdb_entry *e,
+ int is_current_keyset,
+ const hdb_keyset *ks)
+{
+ krb5_error_code ret = 0;
+
+ if (is_current_keyset) {
+ if (e->keys.len &&
+ (ret = hdb_add_current_keys_to_history(context, e)))
+ return ret;
+ free_Keys(&e->keys);
+ e->kvno = ks->kvno;
+ if (ret == 0)
+ ret = copy_Keys(&ks->keys, &e->keys);
+ if (ret == 0 && ks->set_time)
+ ret = hdb_entry_set_pw_change_time(context, e, *ks->set_time);
+ return ret;
+ }
+ return hdb_add_history_keyset(context, e, ks);
+}
+
+
+krb5_error_code
+hdb_next_enctype2key(krb5_context context,
+ const hdb_entry *e,
+ const Keys *keyset,
+ krb5_enctype enctype,
+ Key **key)
+{
+ const Keys *keys = keyset ? keyset : &e->keys;
+ Key *k;
+
+ for (k = *key ? (*key) + 1 : keys->val; k < keys->val + keys->len; k++) {
+ if(k->key.keytype == enctype){
+ *key = k;
+ return 0;
+ }
+ }
+ krb5_set_error_message(context, KRB5_PROG_ETYPE_NOSUPP,
+ "No next enctype %d for hdb-entry",
+ (int)enctype);
+ return KRB5_PROG_ETYPE_NOSUPP; /* XXX */
+}
+
+krb5_error_code
+hdb_enctype2key(krb5_context context,
+ hdb_entry *e,
+ const Keys *keyset,
+ krb5_enctype enctype,
+ Key **key)
+{
+ *key = NULL;
+ return hdb_next_enctype2key(context, e, keyset, enctype, key);
+}
+
+void
+hdb_free_key(Key *key)
+{
+ memset_s(key->key.keyvalue.data,
+ key->key.keyvalue.length,
+ 0,
+ key->key.keyvalue.length);
+ free_Key(key);
+ free(key);
+}
+
+
+krb5_error_code
+hdb_lock(int fd, int operation)
+{
+ int i, code = 0;
+
+ for(i = 0; i < 3; i++){
+ code = flock(fd, (operation == HDB_RLOCK ? LOCK_SH : LOCK_EX) | LOCK_NB);
+ if(code == 0 || errno != EWOULDBLOCK)
+ break;
+ sleep(1);
+ }
+ if(code == 0)
+ return 0;
+ if(errno == EWOULDBLOCK)
+ return HDB_ERR_DB_INUSE;
+ return HDB_ERR_CANT_LOCK_DB;
+}
+
+krb5_error_code
+hdb_unlock(int fd)
+{
+ int code;
+ code = flock(fd, LOCK_UN);
+ if(code)
+ return 4711 /* XXX */;
+ return 0;
+}
+
+void
+hdb_free_entry(krb5_context context, HDB *db, hdb_entry *ent)
+{
+ Key *k;
+ size_t i;
+
+ if (db && db->hdb_free_entry_context)
+ db->hdb_free_entry_context(context, db, ent);
+
+ for(i = 0; i < ent->keys.len; i++) {
+ k = &ent->keys.val[i];
+
+ memset_s(k->key.keyvalue.data,
+ k->key.keyvalue.length,
+ 0,
+ k->key.keyvalue.length);
+ }
+ free_HDB_entry(ent);
+}
+
+krb5_error_code
+hdb_foreach(krb5_context context,
+ HDB *db,
+ unsigned flags,
+ hdb_foreach_func_t func,
+ void *data)
+{
+ krb5_error_code ret;
+ hdb_entry entry;
+ ret = db->hdb_firstkey(context, db, flags, &entry);
+ if (ret == 0)
+ krb5_clear_error_message(context);
+ while(ret == 0){
+ ret = (*func)(context, db, &entry, data);
+ hdb_free_entry(context, db, &entry);
+ if(ret == 0)
+ ret = db->hdb_nextkey(context, db, flags, &entry);
+ }
+ if(ret == HDB_ERR_NOENTRY)
+ ret = 0;
+ return ret;
+}
+
+krb5_error_code
+hdb_check_db_format(krb5_context context, HDB *db)
+{
+ krb5_data tag;
+ krb5_data version;
+ krb5_error_code ret, ret2;
+ unsigned ver;
+ int foo;
+
+ ret = db->hdb_lock(context, db, HDB_RLOCK);
+ if (ret)
+ return ret;
+
+ tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY;
+ tag.length = strlen(tag.data);
+ ret = (*db->hdb__get)(context, db, tag, &version);
+ ret2 = db->hdb_unlock(context, db);
+ if(ret)
+ return ret;
+ if (ret2)
+ return ret2;
+ foo = sscanf(version.data, "%u", &ver);
+ krb5_data_free (&version);
+ if (foo != 1)
+ return HDB_ERR_BADVERSION;
+ if(ver != HDB_DB_FORMAT)
+ return HDB_ERR_BADVERSION;
+ return 0;
+}
+
+krb5_error_code
+hdb_init_db(krb5_context context, HDB *db)
+{
+ krb5_error_code ret, ret2;
+ krb5_data tag;
+ krb5_data version;
+ char ver[32];
+
+ ret = hdb_check_db_format(context, db);
+ if(ret != HDB_ERR_NOENTRY)
+ return ret;
+
+ ret = db->hdb_lock(context, db, HDB_WLOCK);
+ if (ret)
+ return ret;
+
+ tag.data = (void *)(intptr_t)HDB_DB_FORMAT_ENTRY;
+ tag.length = strlen(tag.data);
+ snprintf(ver, sizeof(ver), "%u", HDB_DB_FORMAT);
+ version.data = ver;
+ version.length = strlen(version.data) + 1; /* zero terminated */
+ ret = (*db->hdb__put)(context, db, 0, tag, version);
+ ret2 = db->hdb_unlock(context, db);
+ if (ret) {
+ if (ret2)
+ krb5_clear_error_message(context);
+ return ret;
+ }
+ return ret2;
+}
+
+/*
+ * `default_dbmethod' is the last resort default.
+ *
+ * In hdb_create() we may try all the `methods[]' until one succeeds or all
+ * fail.
+ */
+#if defined(HAVE_LMDB)
+static struct hdb_method default_dbmethod =
+ { HDB_INTERFACE_VERSION, NULL, NULL, 1, 1, "", hdb_mdb_create };
+#elif defined(HAVE_DB3)
+static struct hdb_method default_dbmethod =
+ { HDB_INTERFACE_VERSION, NULL, NULL, 1, 1, "", hdb_db3_create };
+#elif defined(HAVE_DB1)
+static struct hdb_method default_dbmethod =
+ { HDB_INTERFACE_VERSION, NULL, NULL, 1, 1, "", hdb_db1_create };
+#elif defined(HAVE_NDBM)
+static struct hdb_method default_dbmethod =
+ { HDB_INTERFACE_VERSION, NULL, NULL, 0, 1, "", hdb_ndbm_create };
+#else
+static struct hdb_method default_dbmethod =
+ { 0, NULL, NULL, 0, 0, NULL, NULL};
+#endif
+
+static int
+is_pathish(const char *s)
+{
+ if (s[0] == '/' ||
+ strncmp(s, "./", sizeof("./") - 1) == 0 ||
+ strncmp(s, "../", sizeof("../") - 1) == 0)
+ return 1;
+#ifdef WIN32
+ if (s[0] == '\\' || (isalpha(s[0]) && s[0] == ':') ||
+ strncmp(s, ".\\", sizeof(".\\") - 1) == 0 ||
+ strncmp(s, "\\\\", sizeof("\\\\") - 1) == 0)
+ return 1;
+#endif
+ return 0;
+}
+
+static const struct hdb_method *
+has_method_prefix(const char *filename)
+{
+ const struct hdb_method *h;
+
+ for (h = methods; h->prefix != NULL; ++h)
+ if (strncmp(filename, h->prefix, strlen(h->prefix)) == 0)
+ return h;
+ return NULL;
+}
+
+/*
+ * find the relevant method for `filename', returning a pointer to the
+ * rest in `rest'.
+ * return NULL if there's no such method.
+ */
+
+static const struct hdb_method *
+find_method(const char *filename, const char **rest)
+{
+ const struct hdb_method *h = has_method_prefix(filename);
+
+ *rest = h ? filename + strlen(h->prefix) : filename;
+ return h;
+}
+
+struct cb_s {
+ const char *residual;
+ const char *filename;
+ const struct hdb_method *h;
+};
+
+static krb5_error_code KRB5_LIB_CALL
+callback(krb5_context context, const void *plug, void *plugctx, void *userctx)
+{
+ const struct hdb_method *h = (const struct hdb_method *)plug;
+ struct cb_s *cb_ctx = (struct cb_s *)userctx;
+
+ if (strncmp(cb_ctx->filename, h->prefix, strlen(h->prefix)) == 0) {
+ cb_ctx->residual = cb_ctx->filename + strlen(h->prefix) + 1;
+ cb_ctx->h = h;
+ return 0;
+ }
+ return KRB5_PLUGIN_NO_HANDLE;
+}
+
+static char *
+make_sym(const char *prefix)
+{
+ char *s, *sym;
+
+ errno = 0;
+ if (prefix == NULL || prefix[0] == '\0')
+ return NULL;
+ if ((s = strdup(prefix)) == NULL)
+ return NULL;
+ if (strchr(s, ':') != NULL)
+ *strchr(s, ':') = '\0';
+ if (asprintf(&sym, "hdb_%s_interface", s) == -1)
+ sym = NULL;
+ free(s);
+ return sym;
+}
+
+static const char *hdb_plugin_deps[] = { "hdb", "krb5", NULL };
+
+krb5_error_code
+hdb_list_builtin(krb5_context context, char **list)
+{
+ const struct hdb_method *h;
+ size_t len = 0;
+ char *buf = NULL;
+
+ for (h = methods; h->prefix != NULL; ++h) {
+ if (h->prefix[0] == '\0')
+ continue;
+ len += strlen(h->prefix) + 2;
+ }
+
+ len += 1;
+ buf = malloc(len);
+ if (buf == NULL) {
+ return krb5_enomem(context);
+ }
+ buf[0] = '\0';
+
+ for (h = methods; h->prefix != NULL; ++h) {
+ if (h->create == NULL) {
+ struct cb_s cb_ctx;
+ char *f;
+ struct heim_plugin_data hdb_plugin_data;
+
+ hdb_plugin_data.module = "krb5";
+ hdb_plugin_data.min_version = HDB_INTERFACE_VERSION;
+ hdb_plugin_data.deps = hdb_plugin_deps;
+ hdb_plugin_data.get_instance = hdb_get_instance;
+
+ /* Try loading the plugin */
+ if (asprintf(&f, "%sfoo", h->prefix) == -1)
+ f = NULL;
+ if ((hdb_plugin_data.name = make_sym(h->prefix)) == NULL) {
+ free(buf);
+ free(f);
+ return krb5_enomem(context);
+ }
+ cb_ctx.filename = f;
+ cb_ctx.residual = NULL;
+ cb_ctx.h = NULL;
+ (void)_krb5_plugin_run_f(context, &hdb_plugin_data, 0,
+ &cb_ctx, callback);
+ free(f);
+ free(rk_UNCONST(hdb_plugin_data.name));
+ if (cb_ctx.h == NULL || cb_ctx.h->create == NULL)
+ continue;
+ }
+ if (h != methods)
+ strlcat(buf, ", ", len);
+ strlcat(buf, h->prefix, len);
+ }
+ *list = buf;
+ return 0;
+}
+
+krb5_error_code
+_hdb_keytab2hdb_entry(krb5_context context,
+ const krb5_keytab_entry *ktentry,
+ hdb_entry *entry)
+{
+ entry->kvno = ktentry->vno;
+ entry->created_by.time = ktentry->timestamp;
+
+ entry->keys.val = calloc(1, sizeof(entry->keys.val[0]));
+ if (entry->keys.val == NULL)
+ return ENOMEM;
+ entry->keys.len = 1;
+
+ entry->keys.val[0].mkvno = NULL;
+ entry->keys.val[0].salt = NULL;
+
+ return krb5_copy_keyblock_contents(context,
+ &ktentry->keyblock,
+ &entry->keys.val[0].key);
+}
+
+static krb5_error_code
+load_config(krb5_context context, HDB *db)
+{
+ db->enable_virtual_hostbased_princs =
+ krb5_config_get_bool_default(context, NULL, FALSE, "hdb",
+ "enable_virtual_hostbased_princs",
+ NULL);
+ db->virtual_hostbased_princ_ndots =
+ krb5_config_get_int_default(context, NULL, 1, "hdb",
+ "virtual_hostbased_princ_mindots",
+ NULL);
+ db->virtual_hostbased_princ_maxdots =
+ krb5_config_get_int_default(context, NULL, 0, "hdb",
+ "virtual_hostbased_princ_maxdots",
+ NULL);
+ db->new_service_key_delay =
+ krb5_config_get_time_default(context, NULL, 0, "hdb",
+ "new_service_key_delay", NULL);
+ /*
+ * XXX Needs freeing in the HDB backends because we don't have a
+ * first-class hdb_close() :(
+ */
+ db->virtual_hostbased_princ_svcs =
+ krb5_config_get_strings(context, NULL, "hdb",
+ "virtual_hostbased_princ_svcs", NULL);
+ /* Check for ENOMEM */
+ if (db->virtual_hostbased_princ_svcs == NULL
+ && krb5_config_get_string(context, NULL, "hdb",
+ "virtual_hostbased_princ_svcs", NULL)) {
+ return krb5_enomem(context);
+ }
+ return 0;
+}
+
+/**
+ * Create a handle for a Kerberos database
+ *
+ * Create a handle for a Kerberos database backend specified by a
+ * filename. Doesn't actually create or even open an HDB file(s);
+ * you have to call the hdb_open() open method of the resulting HDB
+ * to open the database, and you have to use O_CREAT to create it.
+ *
+ * If `filename' does not have a backend type prefix, all file-based
+ * backends will be tried until one succeeds or all fail, and if the
+ * HDB exists for some backend, that will be used. A build-time
+ * default backend type will be used if the `filename' does not exist.
+ *
+ * Note that the actual filename may have a suffix added, such as
+ * ".db". Also, for backends such as "ldap:" and "ldapi:" the
+ * `filename' is more like a URI.
+ *
+ * @param [in] context Context
+ * @param [out] db HDB handle output
+ * @param [in] filename The name of the HDB
+ *
+ * @return Zero on success else a krb5 error code.
+ */
+
+krb5_error_code
+hdb_create(krb5_context context, HDB **db, const char *filename)
+{
+ krb5_error_code ret = ENOTSUP;
+ struct cb_s cb_ctx;
+
+ *db = NULL;
+ if (filename == NULL)
+ filename = hdb_default_db(context);
+
+ cb_ctx.h = find_method(filename, &cb_ctx.residual);
+ cb_ctx.filename = filename;
+
+ if (cb_ctx.h == NULL || cb_ctx.h->create == NULL) {
+ struct heim_plugin_data hdb_plugin_data;
+
+ /*
+ * `filename' does not start with a known HDB backend prefix.
+ *
+ * Try plugins.
+ */
+ hdb_plugin_data.module = "krb5";
+ hdb_plugin_data.min_version = HDB_INTERFACE_VERSION;
+ hdb_plugin_data.deps = hdb_plugin_deps;
+ hdb_plugin_data.get_instance = hdb_get_instance;
+
+ if ((hdb_plugin_data.name = make_sym(filename)) == NULL)
+ return krb5_enomem(context);
+
+ (void)_krb5_plugin_run_f(context, &hdb_plugin_data, 0 /* flags */,
+ &cb_ctx, callback);
+
+ free(rk_UNCONST(hdb_plugin_data.name));
+ }
+
+ if (cb_ctx.h == NULL || cb_ctx.h->create == NULL) {
+ int pathish = is_pathish(filename);
+ /*
+ * `filename' does not start with a known HDB backend prefix and it
+ * wasn't handled by any plugin.
+ *
+ * If it's "filename-ish", try all builtin HDB backends that are
+ * local-file-ish, but use hdb_open() to see if the HDB exists and stop
+ * when a backend is found for which the HDB exists.
+ */
+ if (!pathish) {
+ krb5_set_error_message(context, ret = ENOTSUP,
+ "No database support for %s",
+ cb_ctx.filename);
+ return ret;
+ }
+ for (cb_ctx.h = methods; cb_ctx.h->prefix != NULL; cb_ctx.h++) {
+ if (cb_ctx.h->is_file_based)
+ continue;
+ if (!cb_ctx.h->can_taste)
+ continue;
+ /* Taste the file */
+ ret = (*cb_ctx.h->create)(context, db, filename);
+ if (ret == 0)
+ ret = (*db)->hdb_open(context, *db, O_RDONLY, 0);
+ if (ret == 0) {
+ (void) (*db)->hdb_close(context, *db);
+ break;
+ }
+ if (*db)
+ (*db)->hdb_destroy(context, *db);
+ *db = NULL;
+ }
+ if (cb_ctx.h->prefix == NULL)
+ cb_ctx.h = NULL;
+ }
+#ifdef HDB_DEFAULT_DB_TYPE
+ if (cb_ctx.h == NULL) {
+ /*
+ * If still we've not picked a backend, use a build configuration time
+ * default.
+ */
+ for (cb_ctx.h = methods; cb_ctx.h->prefix != NULL; cb_ctx.h++)
+ if (strcmp(cb_ctx.h->prefix, HDB_DEFAULT_DB_TYPE) == 0)
+ break;
+ if (cb_ctx.h->prefix == NULL)
+ cb_ctx.h = NULL;
+ }
+#endif
+ if (cb_ctx.h == NULL)
+ /* Last resort default */
+ cb_ctx.h = &default_dbmethod;
+ if (cb_ctx.h->prefix == NULL) {
+ krb5_set_error_message(context, ENOTSUP,
+ "Could not determine default DB backend for %s",
+ filename);
+ return ENOTSUP;
+ }
+ if (!*db) {
+ ret = (*cb_ctx.h->create)(context, db, cb_ctx.residual);
+ if (ret == 0)
+ (*db)->hdb_method_name = cb_ctx.h->prefix;
+ }
+ if (ret == 0 && *db)
+ ret = load_config(context, *db);
+ if (ret && *db) {
+ (*db)->hdb_destroy(context, *db);
+ *db = NULL;
+ }
+ return ret;
+}
+
+uintptr_t KRB5_CALLCONV
+hdb_get_instance(const char *libname)
+{
+ static const char *instance = "libhdb";
+
+ if (strcmp(libname, "hdb") == 0)
+ return (uintptr_t)instance;
+ else if (strcmp(libname, "krb5") == 0)
+ return krb5_get_instance(libname);
+
+ return 0;
+}
diff --git a/third_party/heimdal/lib/hdb/hdb.h b/third_party/heimdal/lib/hdb/hdb.h
new file mode 100644
index 0000000..2a3d1c4
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/hdb.h
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) 1997 - 2007 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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.
+ */
+
+/* $Id$ */
+
+#ifndef __HDB_H__
+#define __HDB_H__
+
+#include <stdio.h>
+
+#include <krb5.h>
+
+#include <hdb_err.h>
+
+#include <heimbase-svc.h>
+#include <heim_asn1.h>
+#include <hdb_asn1.h>
+
+#define HDB_DB_FORMAT hdb_db_format
+
+typedef HDB_keyset hdb_keyset;
+typedef HDB_entry hdb_entry;
+typedef HDB_entry_alias hdb_entry_alias;
+
+struct hdb_dbinfo;
+
+enum hdb_lockop{ HDB_RLOCK, HDB_WLOCK };
+
+/* flags for various functions */
+#define HDB_F_DECRYPT 0x00001 /* decrypt keys */
+#define HDB_F_REPLACE 0x00002 /* replace entry */
+#define HDB_F_GET_CLIENT 0x00004 /* fetch client */
+#define HDB_F_GET_SERVER 0x00008 /* fetch server */
+#define HDB_F_GET_KRBTGT 0x00010 /* fetch krbtgt */
+#define HDB_F_GET_ANY ( HDB_F_GET_CLIENT | \
+ HDB_F_GET_SERVER | \
+ HDB_F_GET_KRBTGT ) /* fetch any of client,server,krbtgt */
+#define HDB_F_CANON 0x00020 /* want canonicalition */
+#define HDB_F_ADMIN_DATA 0x00040 /* want data that kdc don't use */
+#define HDB_F_KVNO_SPECIFIED 0x00080 /* we want a particular KVNO */
+#define HDB_F_LIVE_CLNT_KVNOS 0x00200 /* we want all live keys for pre-auth */
+#define HDB_F_LIVE_SVC_KVNOS 0x00400 /* we want all live keys for tix */
+#define HDB_F_ALL_KVNOS 0x00800 /* we want all the keys, live or not */
+#define HDB_F_FOR_AS_REQ 0x01000 /* fetch is for a AS REQ */
+#define HDB_F_FOR_TGS_REQ 0x02000 /* fetch is for a TGS REQ */
+#define HDB_F_PRECHECK 0x04000 /* check that the operation would succeed */
+#define HDB_F_DELAY_NEW_KEYS 0x08000 /* apply [hdb] new_service_key_delay */
+#define HDB_F_SYNTHETIC_OK 0x10000 /* synthetic principal for PKINIT or GSS preauth OK */
+#define HDB_F_GET_FAST_COOKIE 0x20000 /* fetch the FX-COOKIE key (not a normal principal) */
+
+/* hdb_capability_flags */
+#define HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL 1
+#define HDB_CAP_F_HANDLE_PASSWORDS 2
+#define HDB_CAP_F_PASSWORD_UPDATE_KEYS 4
+#define HDB_CAP_F_SHARED_DIRECTORY 8
+
+#define heim_pcontext krb5_context
+#define heim_pconfig void *
+
+typedef struct hdb_request_desc {
+ HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS;
+} *hdb_request_t;
+
+#undef heim_pcontext
+#undef heim_pconfig
+
+/* key usage for master key */
+#define HDB_KU_MKEY 0x484442
+
+/*
+ * Second component of WELLKNOWN namespace principals, the third component is
+ * the common DNS suffix of the implied virtual hosts.
+ */
+#define HDB_WK_NAMESPACE "HOSTBASED-NAMESPACE"
+
+typedef struct hdb_master_key_data *hdb_master_key;
+
+/**
+ * HDB backend function pointer structure
+ *
+ * The HDB structure is what the KDC and kadmind framework uses to
+ * query the backend database when talking about principals.
+ */
+
+typedef struct HDB {
+ void *hdb_db;
+ void *hdb_dbc; /** don't use, only for DB3 */
+ const char *hdb_method_name;
+ char *hdb_name;
+ int hdb_master_key_set;
+ hdb_master_key hdb_master_key;
+ int hdb_openp;
+ int hdb_capability_flags;
+ int lock_count;
+ int lock_type;
+ /*
+ * These fields cache config values.
+ *
+ * XXX Move these into a structure that we point to so that we
+ * don't need to break the ABI every time we add a field.
+ */
+ int enable_virtual_hostbased_princs;
+ size_t virtual_hostbased_princ_ndots; /* Min. # of .s in hostname */
+ size_t virtual_hostbased_princ_maxdots; /* Max. # of .s in namespace */
+ char **virtual_hostbased_princ_svcs; /* Which svcs are not wildcarded */
+ time_t new_service_key_delay; /* Delay for new keys */
+ /**
+ * Open (or create) the a Kerberos database.
+ *
+ * Open (or create) the a Kerberos database that was resolved with
+ * hdb_create(). The third and fourth flag to the function are the
+ * same as open(), thus passing O_CREAT will create the data base
+ * if it doesn't exists.
+ *
+ * Then done the caller should call hdb_close(), and to release
+ * all resources hdb_destroy().
+ */
+ krb5_error_code (*hdb_open)(krb5_context, struct HDB*, int, mode_t);
+ /**
+ * Close the database for transaction
+ *
+ * Closes the database for further transactions, wont release any
+ * permanant resources. the database can be ->hdb_open-ed again.
+ */
+ krb5_error_code (*hdb_close)(krb5_context, struct HDB*);
+ /**
+ * Free backend-specific entry context.
+ */
+ void (*hdb_free_entry_context)(krb5_context, struct HDB*, hdb_entry*);
+ /**
+ * Fetch an entry from the backend
+ *
+ * Fetch an entry from the backend, flags are what type of entry
+ * should be fetch: client, server, krbtgt.
+ * knvo (if specified and flags HDB_F_KVNO_SPECIFIED set) is the kvno to get
+ */
+ krb5_error_code (*hdb_fetch_kvno)(krb5_context, struct HDB*,
+ krb5_const_principal, unsigned, krb5_kvno,
+ hdb_entry*);
+ /**
+ * Store an entry to database
+ */
+ krb5_error_code (*hdb_store)(krb5_context, struct HDB*,
+ unsigned, hdb_entry*);
+ /**
+ * Remove an entry from the database.
+ */
+ krb5_error_code (*hdb_remove)(krb5_context, struct HDB*,
+ unsigned, krb5_const_principal);
+ /**
+ * As part of iteration, fetch one entry
+ */
+ krb5_error_code (*hdb_firstkey)(krb5_context, struct HDB*,
+ unsigned, hdb_entry*);
+ /**
+ * As part of iteration, fetch next entry
+ */
+ krb5_error_code (*hdb_nextkey)(krb5_context, struct HDB*,
+ unsigned, hdb_entry*);
+ /**
+ * Lock database
+ *
+ * A lock can only be held by one consumers. Transaction can still
+ * happen on the database while the lock is held, so the entry is
+ * only useful for syncroning creation of the database and renaming of the database.
+ */
+ krb5_error_code (*hdb_lock)(krb5_context, struct HDB*, int);
+ /**
+ * Unlock database
+ */
+ krb5_error_code (*hdb_unlock)(krb5_context, struct HDB*);
+ /**
+ * Rename the data base.
+ *
+ * Assume that the database is not hdb_open'ed and not locked.
+ */
+ krb5_error_code (*hdb_rename)(krb5_context, struct HDB*, const char*);
+ /**
+ * Get an hdb_entry from a classical DB backend
+ *
+ * This function takes a principal key (krb5_data) and returns all
+ * data related to principal in the return krb5_data. The returned
+ * encoded entry is of type hdb_entry or hdb_entry_alias.
+ */
+ krb5_error_code (*hdb__get)(krb5_context, struct HDB*,
+ krb5_data, krb5_data*);
+ /**
+ * Store an hdb_entry from a classical DB backend
+ *
+ * This function takes a principal key (krb5_data) and encoded
+ * hdb_entry or hdb_entry_alias as the data to store.
+ *
+ * For a file-based DB, this must synchronize to disk when done.
+ * This is sub-optimal for kadm5_s_rename_principal(), and for
+ * kadm5_s_modify_principal() when using principal aliases; to
+ * improve this so that only one fsync() need be done
+ * per-transaction will require HDB API extensions.
+ */
+ krb5_error_code (*hdb__put)(krb5_context, struct HDB*, int,
+ krb5_data, krb5_data);
+ /**
+ * Delete and hdb_entry from a classical DB backend
+ *
+ * This function takes a principal key (krb5_data) naming the record
+ * to delete.
+ *
+ * Same discussion as in @ref HDB::hdb__put
+ */
+ krb5_error_code (*hdb__del)(krb5_context, struct HDB*, krb5_data);
+ /**
+ * Destroy the handle to the database.
+ *
+ * Destroy the handle to the database, deallocate all memory and
+ * related resources. Does not remove any permanent data. Its the
+ * logical reverse of hdb_create() function that is the entry
+ * point for the module.
+ */
+ krb5_error_code (*hdb_destroy)(krb5_context, struct HDB*);
+ /**
+ * Get the list of realms this backend handles.
+ * This call is optional to support. The returned realms are used
+ * for announcing the realms over bonjour. Free returned array
+ * with krb5_free_host_realm().
+ */
+ krb5_error_code (*hdb_get_realms)(krb5_context, struct HDB *, krb5_realm **);
+ /**
+ * Change password.
+ *
+ * Will update keys for the entry when given password. The new
+ * keys must be written into the entry and will then later be
+ * ->hdb_store() into the database. The backend will still perform
+ * all other operations, increasing the kvno, and update
+ * modification timestamp.
+ *
+ * The backend needs to call _kadm5_set_keys() and perform password
+ * quality checks.
+ */
+ krb5_error_code (*hdb_password)(krb5_context, struct HDB*, hdb_entry*, const char *, int);
+
+ /**
+ * Authentication auditing. Note that this function is called by
+ * both the AS and TGS, but currently only the AS sets the auth
+ * event type. This may change in a future version.
+ *
+ * Event details are available by querying the request using
+ * heim_audit_getkv(HDB_REQUEST_KV_...).
+ *
+ * In case the entry is locked out, the backend should set the
+ * hdb_entry.flags.locked-out flag.
+ */
+ krb5_error_code (*hdb_audit)(krb5_context, struct HDB *, hdb_entry *, hdb_request_t);
+
+ /**
+ * Check if delegation is allowed.
+ */
+ krb5_error_code (*hdb_check_constrained_delegation)(krb5_context, struct HDB *, hdb_entry *, krb5_const_principal);
+
+ /**
+ * Check if this name is an alias for the supplied client for PKINIT userPrinicpalName logins
+ */
+ krb5_error_code (*hdb_check_pkinit_ms_upn_match)(krb5_context, struct HDB *, hdb_entry *, krb5_const_principal);
+
+ /**
+ * Check if s4u2self is allowed from this client to this server or the SPN is a valid SPN of this client (for user2user)
+ */
+ krb5_error_code (*hdb_check_client_matches_target_service)(krb5_context, struct HDB *, hdb_entry *, hdb_entry *);
+
+ /**
+ * Enable/disable synchronous updates
+ *
+ * Calling this with 0 disables sync. Calling it with non-zero enables
+ * sync and does an fsync().
+ */
+ krb5_error_code (*hdb_set_sync)(krb5_context, struct HDB *, int);
+}HDB;
+
+#define HDB_INTERFACE_VERSION 11
+
+struct hdb_method {
+ HEIM_PLUGIN_FTABLE_COMMON_ELEMENTS(krb5_context);
+ unsigned int is_file_based:1;
+ unsigned int can_taste:1;
+ const char *prefix;
+ krb5_error_code (*create)(krb5_context, HDB **, const char *filename);
+};
+
+/* dump entry format, for hdb_print_entry() */
+typedef enum hdb_dump_format {
+ HDB_DUMP_HEIMDAL = 0,
+ HDB_DUMP_MIT = 1,
+} hdb_dump_format_t;
+
+struct hdb_print_entry_arg {
+ FILE *out;
+ hdb_dump_format_t fmt;
+};
+
+typedef krb5_error_code (*hdb_foreach_func_t)(krb5_context, HDB*,
+ hdb_entry*, void*);
+extern krb5_kt_ops hdb_kt_ops;
+extern krb5_kt_ops hdb_get_kt_ops;
+
+extern const int hdb_interface_version;
+
+#include <hdb-protos.h>
+
+#endif /* __HDB_H__ */
diff --git a/third_party/heimdal/lib/hdb/hdb.opt b/third_party/heimdal/lib/hdb/hdb.opt
new file mode 100644
index 0000000..626f8c7
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/hdb.opt
@@ -0,0 +1,5 @@
+--sequence=HDB-extensions
+--sequence=HDB-Ext-KeyRotation
+--sequence=HDB-Ext-KeySet
+--sequence=Keys
+--decorate=HDB_entry:void:context?:::
diff --git a/third_party/heimdal/lib/hdb/hdb.schema b/third_party/heimdal/lib/hdb/hdb.schema
new file mode 100644
index 0000000..f9fb080
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/hdb.schema
@@ -0,0 +1,144 @@
+# Definitions for a Kerberos V KDC schema
+#
+# $Id$
+#
+# This version is compatible with OpenLDAP 1.8
+#
+# OID Base is iso(1) org(3) dod(6) internet(1) private(4) enterprise(1) padl(5322) kdcSchema(10)
+#
+# Syntaxes are under 1.3.6.1.4.1.5322.10.0
+# Attributes types are under 1.3.6.1.4.1.5322.10.1
+# Object classes are under 1.3.6.1.4.1.5322.10.2
+
+# Syntax definitions
+
+#krb5KDCFlagsSyntax SYNTAX ::= {
+# WITH SYNTAX INTEGER
+#-- initial(0), -- require as-req
+#-- forwardable(1), -- may issue forwardable
+#-- proxiable(2), -- may issue proxiable
+#-- renewable(3), -- may issue renewable
+#-- postdate(4), -- may issue postdatable
+#-- server(5), -- may be server
+#-- client(6), -- may be client
+#-- invalid(7), -- entry is invalid
+#-- require-preauth(8), -- must use preauth
+#-- change-pw(9), -- change password service
+#-- require-hwauth(10), -- must use hwauth
+#-- ok-as-delegate(11), -- as in TicketFlags
+#-- user-to-user(12), -- may use user-to-user auth
+#-- immutable(13) -- may not be deleted
+# ID { 1.3.6.1.4.1.5322.10.0.1 }
+#}
+
+#krb5PrincipalNameSyntax SYNTAX ::= {
+# WITH SYNTAX OCTET STRING
+#-- String representations of distinguished names as per RFC1510
+# ID { 1.3.6.1.4.1.5322.10.0.2 }
+#}
+
+# Attribute type definitions
+
+attributetype ( 1.3.6.1.4.1.5322.10.1.1
+ NAME 'krb5PrincipalName'
+ DESC 'The unparsed Kerberos principal name'
+ EQUALITY caseExactIA5Match
+ SINGLE-VALUE
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+
+attributetype ( 1.3.6.1.4.1.5322.10.1.2
+ NAME 'krb5KeyVersionNumber'
+ EQUALITY integerMatch
+ SINGLE-VALUE
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )
+
+attributetype ( 1.3.6.1.4.1.5322.10.1.3
+ NAME 'krb5MaxLife'
+ EQUALITY integerMatch
+ SINGLE-VALUE
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )
+
+attributetype ( 1.3.6.1.4.1.5322.10.1.4
+ NAME 'krb5MaxRenew'
+ EQUALITY integerMatch
+ SINGLE-VALUE
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )
+
+attributetype ( 1.3.6.1.4.1.5322.10.1.5
+ NAME 'krb5KDCFlags'
+ EQUALITY integerMatch
+ SINGLE-VALUE
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )
+
+attributetype ( 1.3.6.1.4.1.5322.10.1.6
+ NAME 'krb5EncryptionType'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )
+
+attributetype ( 1.3.6.1.4.1.5322.10.1.7
+ NAME 'krb5ValidStart'
+ EQUALITY generalizedTimeMatch
+ ORDERING generalizedTimeOrderingMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.5322.10.1.8
+ NAME 'krb5ValidEnd'
+ EQUALITY generalizedTimeMatch
+ ORDERING generalizedTimeOrderingMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+ SINGLE-VALUE )
+
+attributetype ( 1.3.6.1.4.1.5322.10.1.9
+ NAME 'krb5PasswordEnd'
+ EQUALITY generalizedTimeMatch
+ ORDERING generalizedTimeOrderingMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+ SINGLE-VALUE )
+
+# this is temporary; keys will eventually
+# be child entries or compound attributes.
+attributetype ( 1.3.6.1.4.1.5322.10.1.10
+ NAME 'krb5Key'
+ DESC 'Encoded ASN1 Key as an octet string'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 )
+
+attributetype ( 1.3.6.1.4.1.5322.10.1.11
+ NAME 'krb5PrincipalRealm'
+ DESC 'Distinguished name of krb5Realm entry'
+ SUP distinguishedName )
+
+attributetype ( 1.3.6.1.4.1.5322.10.1.12
+ NAME 'krb5RealmName'
+ EQUALITY octetStringMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{128} )
+
+attributetype ( 1.3.6.1.4.1.5322.10.1.13
+ NAME 'krb5ExtendedAttributes'
+ DESC 'Encoded ASN1 HDB Extension Attributes as an octet string'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 )
+
+# Object class definitions
+
+objectclass ( 1.3.6.1.4.1.5322.10.2.1
+ NAME 'krb5Principal'
+ SUP top
+ AUXILIARY
+ MUST ( krb5PrincipalName )
+ MAY ( cn $ krb5PrincipalRealm ) )
+
+objectclass ( 1.3.6.1.4.1.5322.10.2.2
+ NAME 'krb5KDCEntry'
+ SUP krb5Principal
+ AUXILIARY
+ MUST ( krb5KeyVersionNumber )
+ MAY ( krb5ValidStart $ krb5ValidEnd $ krb5PasswordEnd $
+ krb5MaxLife $ krb5MaxRenew $ krb5KDCFlags $
+ krb5EncryptionType $ krb5Key $ krb5ExtendedAttributes ) )
+
+objectclass ( 1.3.6.1.4.1.5322.10.2.3
+ NAME 'krb5Realm'
+ SUP top
+ AUXILIARY
+ MUST ( krb5RealmName ) )
+
diff --git a/third_party/heimdal/lib/hdb/hdb_err.et b/third_party/heimdal/lib/hdb/hdb_err.et
new file mode 100644
index 0000000..6a79ffa
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/hdb_err.et
@@ -0,0 +1,33 @@
+#
+# Error messages for the hdb library
+#
+# This might look like a com_err file, but is not
+#
+id "$Id$"
+
+error_table hdb
+
+prefix HDB_ERR
+
+index 1
+#error_code INUSE, "Entry already exists in database"
+error_code UK_SERROR, "Database store error"
+error_code UK_RERROR, "Database read error"
+error_code NOENTRY, "No such entry in the database"
+error_code DB_INUSE, "Database is locked or in use--try again later"
+error_code DB_CHANGED, "Database was modified during read"
+error_code RECURSIVELOCK, "Attempt to lock database twice"
+error_code NOTLOCKED, "Attempt to unlock database when not locked"
+error_code BADLOCKMODE, "Invalid kdb lock mode"
+error_code CANT_LOCK_DB, "Insufficient access to lock database"
+error_code EXISTS, "Entry already exists in database"
+error_code BADVERSION, "Wrong database version"
+error_code NO_MKEY, "No correct master key"
+error_code MANDATORY_OPTION, "Entry contains unknown mandatory extension"
+error_code NO_WRITE_SUPPORT, "HDB backend doesn't contain write support"
+error_code NOT_FOUND_HERE, "The secret for this entry is not replicated to this database"
+error_code MISUSE, "Incorrect use of the API"
+error_code KVNO_NOT_FOUND, "Entry key version number not found"
+error_code WRONG_REALM, "The principal exists in another realm."
+
+end
diff --git a/third_party/heimdal/lib/hdb/hdb_locl.h b/third_party/heimdal/lib/hdb/hdb_locl.h
new file mode 100644
index 0000000..fd7b184
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/hdb_locl.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 1997-2001 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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.
+ */
+
+/* $Id$ */
+
+#ifndef __HDB_LOCL_H__
+#define __HDB_LOCL_H__
+
+#include <config.h>
+
+#include <assert.h>
+#include <heimbase.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#include <roken.h>
+
+#include "crypto-headers.h"
+#include <krb5.h>
+#include <hdb.h>
+#include <hdb-private.h>
+
+#define HDB_DEFAULT_DB HDB_DB_DIR "/heimdal"
+#define HDB_DB_FORMAT_ENTRY "hdb/db-format"
+
+#endif /* __HDB_LOCL_H__ */
diff --git a/third_party/heimdal/lib/hdb/keys.c b/third_party/heimdal/lib/hdb/keys.c
new file mode 100644
index 0000000..457e5da
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/keys.c
@@ -0,0 +1,855 @@
+/*
+ * Copyright (c) 1997 - 2011 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "krb5_locl.h"
+#include "hdb_locl.h"
+
+#include <pkinit_asn1.h>
+#include <base64.h>
+
+/*
+ * free all the memory used by (len, keys)
+ */
+
+void
+hdb_free_keys(krb5_context context, int len, Key *keys)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ free(keys[i].mkvno);
+ keys[i].mkvno = NULL;
+ if (keys[i].salt != NULL) {
+ free_Salt(keys[i].salt);
+ free(keys[i].salt);
+ keys[i].salt = NULL;
+ }
+ krb5_free_keyblock_contents(context, &keys[i].key);
+ }
+ free (keys);
+}
+
+/*
+ * for each entry in `default_keys' try to parse it as a sequence
+ * of etype:salttype:salt, syntax of this if something like:
+ * [(des|des3|etype):](pw-salt|afs3)[:string], if etype is omitted it
+ * means all etypes, and if string is omitted is means the default
+ * string (for that principal). Additional special values:
+ * v5 == pw-salt, and
+ * v4 == des:pw-salt:
+ * afs or afs3 == des:afs3-salt
+ */
+
+static const krb5_enctype des_etypes[] = {
+ KRB5_ENCTYPE_DES_CBC_MD5,
+ KRB5_ENCTYPE_DES_CBC_MD4,
+ KRB5_ENCTYPE_DES_CBC_CRC
+};
+
+static const krb5_enctype all_etypes[] = {
+ KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+ KRB5_ENCTYPE_DES3_CBC_SHA1,
+ KRB5_ENCTYPE_ARCFOUR_HMAC_MD5
+};
+
+static krb5_error_code
+parse_key_set(krb5_context context, const char *key,
+ krb5_enctype **ret_enctypes, size_t *ret_num_enctypes,
+ krb5_salt *salt, krb5_principal principal)
+{
+ const char *p;
+ char buf[3][256];
+ int num_buf = 0;
+ int i, num_enctypes = 0;
+ krb5_enctype e;
+ const krb5_enctype *enctypes = NULL;
+ krb5_error_code ret;
+
+ p = key;
+
+ *ret_enctypes = NULL;
+ *ret_num_enctypes = 0;
+
+ /* split p in a list of :-separated strings */
+ for(num_buf = 0; num_buf < 3; num_buf++)
+ if(strsep_copy(&p, ":", buf[num_buf], sizeof(buf[num_buf])) == -1)
+ break;
+
+ salt->saltvalue.data = NULL;
+ salt->saltvalue.length = 0;
+
+ for(i = 0; i < num_buf; i++) {
+ if(enctypes == NULL && num_buf > 1) {
+ /* this might be a etype specifier */
+ /* XXX there should be a string_to_etypes handling
+ special cases like `des' and `all' */
+ if(strcmp(buf[i], "des") == 0) {
+ enctypes = des_etypes;
+ num_enctypes = sizeof(des_etypes)/sizeof(des_etypes[0]);
+ } else if(strcmp(buf[i], "des3") == 0) {
+ e = KRB5_ENCTYPE_DES3_CBC_SHA1;
+ enctypes = &e;
+ num_enctypes = 1;
+ } else {
+ ret = krb5_string_to_enctype(context, buf[i], &e);
+ if (ret == 0) {
+ enctypes = &e;
+ num_enctypes = 1;
+ } else
+ return ret;
+ }
+ continue;
+ }
+ if(salt->salttype == 0) {
+ /* interpret string as a salt specifier, if no etype
+ is set, this sets default values */
+ /* XXX should perhaps use string_to_salttype, but that
+ interface sucks */
+ if(strcmp(buf[i], "pw-salt") == 0) {
+ if(enctypes == NULL) {
+ enctypes = all_etypes;
+ num_enctypes = sizeof(all_etypes)/sizeof(all_etypes[0]);
+ }
+ salt->salttype = KRB5_PW_SALT;
+ } else if(strcmp(buf[i], "afs3-salt") == 0) {
+ if(enctypes == NULL) {
+ enctypes = des_etypes;
+ num_enctypes = sizeof(des_etypes)/sizeof(des_etypes[0]);
+ }
+ salt->salttype = KRB5_AFS3_SALT;
+ }
+ continue;
+ }
+
+ if (salt->saltvalue.data != NULL)
+ free(salt->saltvalue.data);
+ /* if there is a final string, use it as the string to
+ salt with, this is mostly useful with null salt for
+ v4 compat, and a cell name for afs compat */
+ salt->saltvalue.data = strdup(buf[i]);
+ if (salt->saltvalue.data == NULL)
+ return krb5_enomem(context);
+ salt->saltvalue.length = strlen(buf[i]);
+ }
+
+ if(enctypes == NULL || salt->salttype == 0) {
+ krb5_free_salt(context, *salt);
+ krb5_set_error_message(context, EINVAL, "bad value for default_keys `%s'", key);
+ return EINVAL;
+ }
+
+ /* if no salt was specified make up default salt */
+ if(salt->saltvalue.data == NULL) {
+ if(salt->salttype == KRB5_PW_SALT) {
+ ret = krb5_get_pw_salt(context, principal, salt);
+ if (ret)
+ return ret;
+ } else if(salt->salttype == KRB5_AFS3_SALT) {
+ krb5_const_realm realm = krb5_principal_get_realm(context, principal);
+ salt->saltvalue.data = strdup(realm);
+ if(salt->saltvalue.data == NULL) {
+ krb5_set_error_message(context, ENOMEM,
+ "out of memory while "
+ "parsing salt specifiers");
+ return ENOMEM;
+ }
+ strlwr(salt->saltvalue.data);
+ salt->saltvalue.length = strlen(realm);
+ }
+ }
+
+ *ret_enctypes = malloc(sizeof(enctypes[0]) * num_enctypes);
+ if (*ret_enctypes == NULL) {
+ krb5_free_salt(context, *salt);
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ memcpy(*ret_enctypes, enctypes, sizeof(enctypes[0]) * num_enctypes);
+ *ret_num_enctypes = num_enctypes;
+
+ return 0;
+}
+
+/**
+ * This function prunes an HDB entry's historic keys by kvno.
+ *
+ * @param context Context
+ * @param entry HDB entry
+ * @param kvno Keyset kvno to prune, or zero to prune all too-old keys
+ */
+krb5_error_code
+hdb_prune_keys_kvno(krb5_context context, hdb_entry *entry, int kvno)
+{
+ HDB_extension *ext;
+ HDB_Ext_KeySet *keys;
+ hdb_keyset *elem;
+ time_t keep_time = 0;
+ size_t nelem;
+ size_t i;
+
+ /*
+ * XXX Pruning old keys for namespace principals may not be desirable, but!
+ * as long as the `set_time's of the base keys for a namespace principal
+ * match the `epoch's of the corresponding KeyRotation periods, it will be
+ * perfectly acceptable to prune old [base] keys for namespace principals
+ * just as for any other principal. Therefore, we may not need to make any
+ * changes here w.r.t. namespace principals.
+ */
+
+ ext = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys);
+ if (ext == NULL)
+ return 0;
+ keys = &ext->data.u.hist_keys;
+ nelem = keys->len;
+
+ /*
+ * Optionally drop key history for keys older than now - max_life, which is
+ * all the keys no longer needed to decrypt extant tickets.
+ */
+ if (kvno == 0 && entry->max_life != NULL && nelem > 0) {
+ time_t ceiling = time(NULL) - *entry->max_life;
+
+ /*
+ * Compute most recent key timestamp that predates the current time
+ * by at least the entry's maximum ticket lifetime.
+ */
+ for (i = 0; i < nelem; ++i) {
+ elem = &keys->val[i];
+ if (elem->set_time && *elem->set_time < ceiling
+ && (keep_time == 0 || *elem->set_time > keep_time))
+ keep_time = *elem->set_time;
+ }
+ }
+
+ if (kvno == 0 && keep_time == 0)
+ return 0;
+
+ for (i = 0; i < nelem; /* see below */) {
+ elem = &keys->val[i];
+ if ((kvno && kvno == elem->kvno) ||
+ (keep_time && elem->set_time && *elem->set_time < keep_time)) {
+ remove_HDB_Ext_KeySet(keys, i);
+ /*
+ * Removing the i'th element shifts the tail down, continue
+ * at same index with reduced upper bound.
+ */
+ --nelem;
+ continue;
+ }
+ ++i;
+ }
+
+ return 0;
+}
+
+/**
+ * This function prunes an HDB entry's keys that are too old to have been used
+ * to mint still valid tickets (based on the entry's maximum ticket lifetime).
+ *
+ * @param context Context
+ * @param entry HDB entry
+ */
+krb5_error_code
+hdb_prune_keys(krb5_context context, hdb_entry *entry)
+{
+ if (!krb5_config_get_bool_default(context, NULL, FALSE,
+ "kadmin", "prune-key-history", NULL))
+ return 0;
+ return hdb_prune_keys_kvno(context, entry, 0);
+}
+
+/**
+ * This function adds a keyset to an HDB entry's key history.
+ *
+ * @param context Context
+ * @param entry HDB entry
+ * @param kvno Key version number of the key to add to the history
+ * @param key The Key to add
+ */
+krb5_error_code
+hdb_add_history_keyset(krb5_context context,
+ hdb_entry *entry,
+ const hdb_keyset *ks)
+{
+ size_t i;
+ HDB_Ext_KeySet *hist_keys;
+ HDB_extension ext;
+ HDB_extension *extp;
+ krb5_error_code ret = 0;
+
+ memset(&ext, 0, sizeof (ext));
+
+ extp = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys);
+ if (extp == NULL) {
+ ext.mandatory = FALSE;
+ ext.data.element = choice_HDB_extension_data_hist_keys;
+ ext.data.u.hist_keys.len = 0;
+ ext.data.u.hist_keys.val = 0;
+ extp = &ext;
+ }
+ hist_keys = &extp->data.u.hist_keys;
+
+ for (i = 0; i < hist_keys->len; i++) {
+ if (hist_keys->val[i].kvno == ks->kvno) {
+ /* Replace existing */
+ free_HDB_keyset(&hist_keys->val[i]);
+ ret = copy_HDB_keyset(ks, &hist_keys->val[i]);
+ break;
+ }
+ }
+ if (i >= hist_keys->len)
+ ret = add_HDB_Ext_KeySet(hist_keys, ks); /* Append new */
+ if (ret == 0 && extp == &ext)
+ ret = hdb_replace_extension(context, entry, &ext);
+ free_HDB_extension(&ext);
+ return ret;
+}
+
+/**
+ * This function adds an HDB entry's current keyset to the entry's key
+ * history. The current keyset is left alone; the caller is responsible
+ * for freeing it.
+ *
+ * @param context Context
+ * @param entry HDB entry
+ *
+ * @return Zero on success, or an error code otherwise.
+ */
+krb5_error_code
+hdb_add_current_keys_to_history(krb5_context context, hdb_entry *entry)
+{
+ krb5_error_code ret;
+ hdb_keyset ks;
+ time_t newtime;
+
+ if (entry->keys.len == 0)
+ return 0; /* nothing to do */
+
+ ret = hdb_entry_get_pw_change_time(entry, &newtime);
+ if (ret)
+ return ret;
+
+ ks.keys = entry->keys;
+ ks.kvno = entry->kvno;
+ ks.set_time = &newtime;
+
+ ret = hdb_add_history_keyset(context, entry, &ks);
+ if (ret == 0)
+ ret = hdb_prune_keys(context, entry);
+ return ret;
+}
+
+/**
+ * This function adds a key to an HDB entry's key history.
+ *
+ * @param context Context
+ * @param entry HDB entry
+ * @param kvno Key version number of the key to add to the history
+ * @param key The Key to add
+ *
+ * @return Zero on success, or an error code otherwise.
+ */
+krb5_error_code
+hdb_add_history_key(krb5_context context, hdb_entry *entry, krb5_kvno kvno, Key *key)
+{
+ size_t i;
+ hdb_keyset keyset;
+ HDB_Ext_KeySet *hist_keys;
+ HDB_extension ext;
+ HDB_extension *extp;
+ krb5_error_code ret;
+
+ memset(&keyset, 0, sizeof (keyset));
+ memset(&ext, 0, sizeof (ext));
+
+ extp = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys);
+ if (extp == NULL) {
+ ext.data.element = choice_HDB_extension_data_hist_keys;
+ extp = &ext;
+ }
+
+ extp->mandatory = FALSE;
+ hist_keys = &extp->data.u.hist_keys;
+
+ for (i = 0; i < hist_keys->len; i++) {
+ if (hist_keys->val[i].kvno == kvno) {
+ ret = add_Keys(&hist_keys->val[i].keys, key);
+ goto out;
+ }
+ }
+
+ keyset.kvno = kvno;
+ ret = add_Keys(&keyset.keys, key);
+ if (ret)
+ goto out;
+ ret = add_HDB_Ext_KeySet(hist_keys, &keyset);
+ if (ret)
+ goto out;
+ if (extp == &ext) {
+ ret = hdb_replace_extension(context, entry, &ext);
+ if (ret)
+ goto out;
+ }
+
+out:
+ free_HDB_keyset(&keyset);
+ free_HDB_extension(&ext);
+ return ret;
+}
+
+/**
+ * This function changes an hdb_entry's kvno, swapping the current key
+ * set with a historical keyset. If no historical keys are found then
+ * an error is returned (the caller can still set entry->kvno directly).
+ *
+ * @param context krb5_context
+ * @param new_kvno New kvno for the entry
+ * @param entry hdb_entry to modify
+ */
+krb5_error_code
+hdb_change_kvno(krb5_context context, krb5_kvno new_kvno, hdb_entry *entry)
+{
+ HDB_extension ext;
+ HDB_extension *extp;
+ hdb_keyset keyset;
+ HDB_Ext_KeySet *hist_keys;
+ size_t i;
+ int found = 0;
+ krb5_error_code ret;
+
+ if (entry->kvno == new_kvno)
+ return 0;
+
+ extp = hdb_find_extension(entry, choice_HDB_extension_data_hist_keys);
+ if (extp == NULL) {
+ memset(&ext, 0, sizeof (ext));
+ ext.data.element = choice_HDB_extension_data_hist_keys;
+ extp = &ext;
+ }
+
+ memset(&keyset, 0, sizeof (keyset));
+ hist_keys = &extp->data.u.hist_keys;
+ for (i = 0; i < hist_keys->len; i++) {
+ if (hist_keys->val[i].kvno == new_kvno) {
+ found = 1;
+ ret = copy_HDB_keyset(&hist_keys->val[i], &keyset);
+ if (ret)
+ goto out;
+ ret = remove_HDB_Ext_KeySet(hist_keys, i);
+ if (ret)
+ goto out;
+ break;
+ }
+ }
+
+ if (!found)
+ return HDB_ERR_KVNO_NOT_FOUND;
+
+ ret = hdb_add_current_keys_to_history(context, entry);
+ if (ret)
+ goto out;
+
+ /* Note: we do nothing with keyset.set_time */
+ entry->kvno = new_kvno;
+ entry->keys = keyset.keys; /* shortcut */
+ memset(&keyset.keys, 0, sizeof (keyset.keys));
+
+out:
+ free_HDB_keyset(&keyset);
+ return ret;
+}
+
+
+static krb5_error_code
+add_enctype_to_key_set(Key **key_set, size_t *nkeyset,
+ krb5_enctype enctype, krb5_salt *salt)
+{
+ krb5_error_code ret;
+ Key key, *tmp;
+
+ memset(&key, 0, sizeof(key));
+
+ tmp = realloc(*key_set, (*nkeyset + 1) * sizeof((*key_set)[0]));
+ if (tmp == NULL)
+ return ENOMEM;
+
+ *key_set = tmp;
+
+ key.key.keytype = enctype;
+ key.key.keyvalue.length = 0;
+ key.key.keyvalue.data = NULL;
+
+ if (salt) {
+ key.salt = calloc(1, sizeof(*key.salt));
+ if (key.salt == NULL) {
+ free_Key(&key);
+ return ENOMEM;
+ }
+
+ key.salt->type = salt->salttype;
+ krb5_data_zero (&key.salt->salt);
+
+ ret = krb5_data_copy(&key.salt->salt,
+ salt->saltvalue.data,
+ salt->saltvalue.length);
+ if (ret) {
+ free_Key(&key);
+ return ret;
+ }
+ } else
+ key.salt = NULL;
+
+ (*key_set)[*nkeyset] = key;
+
+ *nkeyset += 1;
+
+ return 0;
+}
+
+
+static
+krb5_error_code
+ks_tuple2str(krb5_context context, int n_ks_tuple,
+ krb5_key_salt_tuple *ks_tuple, char ***ks_tuple_strs)
+{
+ size_t i;
+ char **ksnames;
+ krb5_error_code rc = KRB5_PROG_ETYPE_NOSUPP;
+
+ *ks_tuple_strs = NULL;
+ if (n_ks_tuple < 1)
+ return 0;
+
+ if ((ksnames = calloc(n_ks_tuple + 1, sizeof (*ksnames))) == NULL)
+ return (errno);
+
+ for (i = 0; i < n_ks_tuple; i++) {
+ char *ename, *sname;
+
+ if (krb5_enctype_to_string(context, ks_tuple[i].ks_enctype, &ename))
+ goto out;
+ if (krb5_salttype_to_string(context, ks_tuple[i].ks_enctype,
+ ks_tuple[i].ks_salttype, &sname)) {
+ free(ename);
+ goto out;
+ }
+
+ if (asprintf(&ksnames[i], "%s:%s", ename, sname) == -1) {
+ rc = errno;
+ free(ename);
+ free(sname);
+ goto out;
+ }
+ free(ename);
+ free(sname);
+ }
+
+ ksnames[i] = NULL;
+ *ks_tuple_strs = ksnames;
+ return 0;
+
+out:
+ for (i = 0; i < n_ks_tuple; i++)
+ free(ksnames[i]);
+ free(ksnames);
+ return (rc);
+}
+
+/*
+ *
+ */
+
+static char **
+glob_rules_keys(krb5_context context, krb5_const_principal principal)
+{
+ const krb5_config_binding *list;
+ krb5_principal pattern;
+ krb5_error_code ret;
+
+ list = krb5_config_get_list(context, NULL, "kadmin",
+ "default_key_rules", NULL);
+ if (list == NULL)
+ return NULL;
+
+ while (list) {
+ if (list->type == krb5_config_string) {
+ ret = krb5_parse_name(context, list->name, &pattern);
+ if (ret == 0) {
+ ret = krb5_principal_match(context, principal, pattern);
+ krb5_free_principal(context, pattern);
+ if (ret) {
+ return krb5_config_get_strings(context, list,
+ list->name, NULL);
+ }
+ }
+ }
+ list = list->next;
+ }
+ return NULL;
+}
+
+/*
+ * NIST guidance in Section 5.1 of [SP800-132] requires that a portion
+ * of the salt of at least 128 bits shall be randomly generated.
+ */
+static krb5_error_code
+add_random_to_salt(krb5_context context, krb5_salt *in, krb5_salt *out)
+{
+ krb5_error_code ret;
+ char *p;
+ unsigned char random[16];
+ char *s;
+ int slen;
+
+ krb5_generate_random_block(random, sizeof(random));
+
+ slen = rk_base64_encode(random, sizeof(random), &s);
+ if (slen < 0)
+ return ENOMEM;
+
+ ret = krb5_data_alloc(&out->saltvalue, slen + in->saltvalue.length);
+ if (ret) {
+ free(s);
+ return ret;
+ }
+
+ p = out->saltvalue.data;
+ memcpy(p, s, slen);
+ memcpy(&p[slen], in->saltvalue.data, in->saltvalue.length);
+
+ out->salttype = in->salttype;
+ free(s);
+
+ return 0;
+}
+
+/*
+ * Generate the `key_set' from the [kadmin]default_keys statement. If
+ * `no_salt' is set, salt is not important (and will not be set) since
+ * it's random keys that is going to be created.
+ */
+
+krb5_error_code
+hdb_generate_key_set(krb5_context context, krb5_principal principal,
+ krb5_key_salt_tuple *ks_tuple, int n_ks_tuple,
+ Key **ret_key_set, size_t *nkeyset, int no_salt)
+{
+ char **ktypes = NULL;
+ char **kp;
+ krb5_error_code ret;
+ Key *k, *key_set;
+ size_t i, j;
+ char **ks_tuple_strs;
+ char **config_ktypes = NULL;
+ static const char *default_keytypes[] = {
+ "aes256-cts-hmac-sha1-96:pw-salt",
+ "des3-cbc-sha1:pw-salt",
+ "arcfour-hmac-md5:pw-salt",
+ NULL
+ };
+
+ if ((ret = ks_tuple2str(context, n_ks_tuple, ks_tuple, &ks_tuple_strs)))
+ return ret;
+
+ ktypes = ks_tuple_strs;
+ if (ktypes == NULL) {
+ config_ktypes = glob_rules_keys(context, principal);
+ ktypes = config_ktypes;
+ }
+ if (ktypes == NULL) {
+ config_ktypes = krb5_config_get_strings(context, NULL, "kadmin",
+ "default_keys", NULL);
+ ktypes = config_ktypes;
+ }
+ if (ktypes == NULL)
+ ktypes = (char **)(intptr_t)default_keytypes;
+
+ *ret_key_set = key_set = NULL;
+ *nkeyset = 0;
+
+ for(kp = ktypes; kp && *kp; kp++) {
+ const char *p;
+ krb5_salt salt;
+ krb5_enctype *enctypes;
+ size_t num_enctypes;
+
+ p = *kp;
+ /* check alias */
+ if(strcmp(p, "v5") == 0)
+ p = "pw-salt";
+ else if(strcmp(p, "v4") == 0)
+ p = "des:pw-salt:";
+ else if(strcmp(p, "afs") == 0 || strcmp(p, "afs3") == 0)
+ p = "des:afs3-salt";
+ else if (strcmp(p, "arcfour-hmac-md5") == 0)
+ p = "arcfour-hmac-md5:pw-salt";
+
+ memset(&salt, 0, sizeof(salt));
+
+ ret = parse_key_set(context, p,
+ &enctypes, &num_enctypes, &salt, principal);
+ if (ret) {
+ krb5_warn(context, ret, "bad value for default_keys `%s'", *kp);
+ ret = 0;
+ krb5_free_salt(context, salt);
+ continue;
+ }
+
+ for (i = 0; i < num_enctypes; i++) {
+ krb5_salt *saltp = no_salt ? NULL : &salt;
+ krb5_salt rsalt;
+
+ /* find duplicates */
+ for (j = 0; j < *nkeyset; j++) {
+
+ k = &key_set[j];
+
+ if (k->key.keytype == enctypes[i]) {
+ if (no_salt)
+ break;
+ if (k->salt == NULL && salt.salttype == KRB5_PW_SALT)
+ break;
+ if (k->salt->type == salt.salttype &&
+ k->salt->salt.length == salt.saltvalue.length &&
+ memcmp(k->salt->salt.data, salt.saltvalue.data,
+ salt.saltvalue.length) == 0)
+ break;
+ }
+ }
+ /* not a duplicate, lets add it */
+ if (j < *nkeyset)
+ continue;
+
+ memset(&rsalt, 0, sizeof(rsalt));
+
+ /* prepend salt with randomness if required */
+ if (!no_salt &&
+ _krb5_enctype_requires_random_salt(context, enctypes[i])) {
+ saltp = &rsalt;
+ ret = add_random_to_salt(context, &salt, &rsalt);
+ }
+
+ if (ret == 0)
+ ret = add_enctype_to_key_set(&key_set, nkeyset, enctypes[i],
+ saltp);
+ krb5_free_salt(context, rsalt);
+
+ if (ret) {
+ free(enctypes);
+ krb5_free_salt(context, salt);
+ goto out;
+ }
+ }
+ free(enctypes);
+ krb5_free_salt(context, salt);
+ }
+
+ *ret_key_set = key_set;
+
+ out:
+ if (config_ktypes != NULL)
+ krb5_config_free_strings(config_ktypes);
+
+ for(kp = ks_tuple_strs; kp && *kp; kp++)
+ free(*kp);
+ free(ks_tuple_strs);
+
+ if (ret) {
+ krb5_warn(context, ret,
+ "failed to parse the [kadmin]default_keys values");
+
+ for (i = 0; i < *nkeyset; i++)
+ free_Key(&key_set[i]);
+ free(key_set);
+ } else if (*nkeyset == 0) {
+ krb5_warnx(context,
+ "failed to parse any of the [kadmin]default_keys values");
+ ret = EINVAL; /* XXX */
+ }
+
+ return ret;
+}
+
+
+krb5_error_code
+hdb_generate_key_set_password_with_ks_tuple(krb5_context context,
+ krb5_principal principal,
+ const char *password,
+ krb5_key_salt_tuple *ks_tuple,
+ int n_ks_tuple,
+ Key **keys, size_t *num_keys)
+{
+ krb5_error_code ret;
+ size_t i;
+
+ ret = hdb_generate_key_set(context, principal, ks_tuple, n_ks_tuple,
+ keys, num_keys, 0);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < (*num_keys); i++) {
+ krb5_salt salt;
+ Key *key = &(*keys)[i];
+
+ salt.salttype = key->salt->type;
+ salt.saltvalue.length = key->salt->salt.length;
+ salt.saltvalue.data = key->salt->salt.data;
+
+ ret = krb5_string_to_key_salt (context,
+ key->key.keytype,
+ password,
+ salt,
+ &key->key);
+ if(ret)
+ break;
+ }
+
+ if(ret) {
+ hdb_free_keys (context, *num_keys, *keys);
+ return ret;
+ }
+ return ret;
+}
+
+
+krb5_error_code
+hdb_generate_key_set_password(krb5_context context,
+ krb5_principal principal,
+ const char *password,
+ Key **keys, size_t *num_keys)
+{
+
+ return hdb_generate_key_set_password_with_ks_tuple(context, principal,
+ password, NULL, 0,
+ keys, num_keys);
+}
diff --git a/third_party/heimdal/lib/hdb/keytab.c b/third_party/heimdal/lib/hdb/keytab.c
new file mode 100644
index 0000000..b1aa020
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/keytab.c
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 1999 - 2002 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "hdb_locl.h"
+
+/* keytab backend for HDB databases */
+
+struct hdb_data {
+ char *dbname;
+ char *mkey;
+};
+
+struct hdb_cursor {
+ HDB *db;
+ hdb_entry hdb_entry;
+ int first, next;
+ int key_idx;
+};
+
+/*
+ * the format for HDB keytabs is:
+ * HDB:[HDBFORMAT:database-specific-data[:mkey=mkey-file]]
+ */
+
+static krb5_error_code KRB5_CALLCONV
+hdb_resolve(krb5_context context, const char *name, krb5_keytab id)
+{
+ struct hdb_data *d;
+ const char *db, *mkey;
+
+ d = malloc(sizeof(*d));
+ if(d == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ db = name;
+ mkey = strstr(name, ":mkey=");
+ if(mkey == NULL || mkey[6] == '\0') {
+ if(*name == '\0')
+ d->dbname = NULL;
+ else {
+ d->dbname = strdup(name);
+ if(d->dbname == NULL) {
+ free(d);
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ }
+ d->mkey = NULL;
+ } else {
+ d->dbname = malloc(mkey - db + 1);
+ if(d->dbname == NULL) {
+ free(d);
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ memmove(d->dbname, db, mkey - db);
+ d->dbname[mkey - db] = '\0';
+
+ d->mkey = strdup(mkey + 6);
+ if(d->mkey == NULL) {
+ free(d->dbname);
+ free(d);
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ }
+ id->data = d;
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+hdb_close(krb5_context context, krb5_keytab id)
+{
+ struct hdb_data *d = id->data;
+
+ free(d->dbname);
+ free(d->mkey);
+ free(d);
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+hdb_get_name(krb5_context context,
+ krb5_keytab id,
+ char *name,
+ size_t namesize)
+{
+ struct hdb_data *d = id->data;
+
+ snprintf(name, namesize, "%s%s%s",
+ d->dbname ? d->dbname : "",
+ (d->dbname || d->mkey) ? ":" : "",
+ d->mkey ? d->mkey : "");
+ return 0;
+}
+
+/*
+ * try to figure out the database (`dbname') and master-key (`mkey')
+ * that should be used for `principal'.
+ */
+
+static krb5_error_code
+find_db (krb5_context context,
+ char **dbname,
+ char **mkey,
+ krb5_const_principal principal)
+{
+ krb5_const_realm realm = krb5_principal_get_realm(context, principal);
+ krb5_error_code ret;
+ struct hdb_dbinfo *head, *dbinfo = NULL;
+
+ *dbname = *mkey = NULL;
+
+ ret = hdb_get_dbinfo(context, &head);
+ if (ret)
+ return ret;
+
+ while ((dbinfo = hdb_dbinfo_get_next(head, dbinfo)) != NULL) {
+ const char *p = hdb_dbinfo_get_realm(context, dbinfo);
+ if (p && strcmp (realm, p) == 0) {
+ p = hdb_dbinfo_get_dbname(context, dbinfo);
+ if (p)
+ *dbname = strdup(p);
+ p = hdb_dbinfo_get_mkey_file(context, dbinfo);
+ if (p)
+ *mkey = strdup(p);
+ break;
+ }
+ }
+ hdb_free_dbinfo(context, &head);
+ if (*dbname == NULL &&
+ (*dbname = strdup(hdb_default_db(context))) == NULL) {
+ free(*mkey);
+ *mkey = NULL;
+ return krb5_enomem(context);
+ }
+ return 0;
+}
+
+/*
+ * find the keytab entry in `id' for `principal, kvno, enctype' and return
+ * it in `entry'. return 0 or an error code
+ */
+
+static krb5_error_code KRB5_CALLCONV
+hdb_get_entry(krb5_context context,
+ krb5_keytab id,
+ krb5_const_principal principal,
+ krb5_kvno kvno,
+ krb5_enctype enctype,
+ krb5_keytab_entry *entry)
+{
+ hdb_entry ent;
+ krb5_error_code ret;
+ struct hdb_data *d = id->data;
+ const char *dbname = d->dbname;
+ const char *mkey = d->mkey;
+ char *fdbname = NULL, *fmkey = NULL;
+ HDB *db;
+ size_t i;
+
+ if (!principal)
+ return KRB5_KT_NOTFOUND;
+
+ memset(&ent, 0, sizeof(ent));
+
+ if (dbname == NULL) {
+ ret = find_db(context, &fdbname, &fmkey, principal);
+ if (ret)
+ return ret;
+ dbname = fdbname;
+ mkey = fmkey;
+ }
+
+ ret = hdb_create (context, &db, dbname);
+ if (ret)
+ goto out2;
+ ret = hdb_set_master_keyfile (context, db, mkey);
+ if (ret) {
+ (*db->hdb_destroy)(context, db);
+ goto out2;
+ }
+
+ ret = (*db->hdb_open)(context, db, O_RDONLY, 0);
+ if (ret) {
+ (*db->hdb_destroy)(context, db);
+ goto out2;
+ }
+
+ ret = hdb_fetch_kvno(context, db, principal,
+ HDB_F_DECRYPT|HDB_F_KVNO_SPECIFIED|
+ HDB_F_GET_CLIENT|HDB_F_GET_SERVER|HDB_F_GET_KRBTGT,
+ 0, 0, kvno, &ent);
+
+ if(ret == HDB_ERR_NOENTRY) {
+ ret = KRB5_KT_NOTFOUND;
+ goto out;
+ }else if(ret)
+ goto out;
+
+ if(kvno && (krb5_kvno)ent.kvno != kvno) {
+ hdb_free_entry(context, db, &ent);
+ ret = KRB5_KT_NOTFOUND;
+ goto out;
+ }
+ if(enctype == 0)
+ if(ent.keys.len > 0)
+ enctype = ent.keys.val[0].key.keytype;
+ ret = KRB5_KT_NOTFOUND;
+ for(i = 0; i < ent.keys.len; i++) {
+ if(ent.keys.val[i].key.keytype == enctype) {
+ krb5_copy_principal(context, principal, &entry->principal);
+ entry->vno = ent.kvno;
+ krb5_copy_keyblock_contents(context,
+ &ent.keys.val[i].key,
+ &entry->keyblock);
+ ret = 0;
+ break;
+ }
+ }
+ hdb_free_entry(context, db, &ent);
+ out:
+ (*db->hdb_close)(context, db);
+ (*db->hdb_destroy)(context, db);
+ out2:
+ free(fdbname);
+ free(fmkey);
+ return ret;
+}
+
+/*
+ * find the keytab entry in `id' for `principal, kvno, enctype' and return
+ * it in `entry'. return 0 or an error code
+ */
+
+static krb5_error_code KRB5_CALLCONV
+hdb_start_seq_get(krb5_context context,
+ krb5_keytab id,
+ krb5_kt_cursor *cursor)
+{
+ krb5_error_code ret;
+ struct hdb_cursor *c;
+ struct hdb_data *d = id->data;
+ const char *dbname = d->dbname;
+ const char *mkey = d->mkey;
+ HDB *db;
+
+ if (dbname == NULL) {
+ /*
+ * We don't support enumerating without being told what
+ * backend to enumerate on
+ */
+ ret = KRB5_KT_NOTFOUND;
+ return ret;
+ }
+
+ ret = hdb_create (context, &db, dbname);
+ if (ret)
+ return ret;
+ ret = hdb_set_master_keyfile (context, db, mkey);
+ if (ret) {
+ (*db->hdb_destroy)(context, db);
+ return ret;
+ }
+
+ ret = (*db->hdb_open)(context, db, O_RDONLY, 0);
+ if (ret) {
+ (*db->hdb_destroy)(context, db);
+ return ret;
+ }
+
+ cursor->data = c = malloc (sizeof(*c));
+ if(c == NULL){
+ (*db->hdb_close)(context, db);
+ (*db->hdb_destroy)(context, db);
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ c->db = db;
+ c->first = TRUE;
+ c->next = TRUE;
+ c->key_idx = 0;
+
+ cursor->data = c;
+ return ret;
+}
+
+static int KRB5_CALLCONV
+hdb_next_entry(krb5_context context,
+ krb5_keytab id,
+ krb5_keytab_entry *entry,
+ krb5_kt_cursor *cursor)
+{
+ struct hdb_cursor *c = cursor->data;
+ krb5_error_code ret;
+
+ memset(entry, 0, sizeof(*entry));
+
+ if (c->first) {
+ c->first = FALSE;
+ ret = (c->db->hdb_firstkey)(context, c->db,
+ HDB_F_DECRYPT|
+ HDB_F_GET_CLIENT|HDB_F_GET_SERVER|HDB_F_GET_KRBTGT,
+ &c->hdb_entry);
+ if (ret == HDB_ERR_NOENTRY)
+ return KRB5_KT_END;
+ else if (ret)
+ return ret;
+
+ if (c->hdb_entry.keys.len == 0)
+ hdb_free_entry(context, c->db, &c->hdb_entry);
+ else
+ c->next = FALSE;
+ }
+
+ while (c->next) {
+ ret = (c->db->hdb_nextkey)(context, c->db,
+ HDB_F_DECRYPT|
+ HDB_F_GET_CLIENT|HDB_F_GET_SERVER|HDB_F_GET_KRBTGT,
+ &c->hdb_entry);
+ if (ret == HDB_ERR_NOENTRY)
+ return KRB5_KT_END;
+ else if (ret)
+ return ret;
+
+ /* If no keys on this entry, try again */
+ if (c->hdb_entry.keys.len == 0)
+ hdb_free_entry(context, c->db, &c->hdb_entry);
+ else
+ c->next = FALSE;
+ }
+
+ /*
+ * Return next enc type (keytabs are one slot per key, while
+ * hdb is one record per principal.
+ */
+
+ ret = krb5_copy_principal(context,
+ c->hdb_entry.principal,
+ &entry->principal);
+ if (ret)
+ return ret;
+
+ entry->vno = c->hdb_entry.kvno;
+ ret = krb5_copy_keyblock_contents(context,
+ &c->hdb_entry.keys.val[c->key_idx].key,
+ &entry->keyblock);
+ if (ret) {
+ krb5_free_principal(context, entry->principal);
+ memset(entry, 0, sizeof(*entry));
+ return ret;
+ }
+ c->key_idx++;
+
+ /*
+ * Once we get to the end of the list, signal that we want the
+ * next entry
+ */
+
+ if ((size_t)c->key_idx == c->hdb_entry.keys.len) {
+ hdb_free_entry(context, c->db, &c->hdb_entry);
+ c->next = TRUE;
+ c->key_idx = 0;
+ }
+
+ return 0;
+}
+
+
+static int KRB5_CALLCONV
+hdb_end_seq_get(krb5_context context,
+ krb5_keytab id,
+ krb5_kt_cursor *cursor)
+{
+ struct hdb_cursor *c = cursor->data;
+
+ if (!c->next)
+ hdb_free_entry(context, c->db, &c->hdb_entry);
+
+ (c->db->hdb_close)(context, c->db);
+ (c->db->hdb_destroy)(context, c->db);
+
+ free(c);
+ return 0;
+}
+
+krb5_kt_ops hdb_kt_ops = {
+ "HDB",
+ hdb_resolve,
+ hdb_get_name,
+ hdb_close,
+ NULL, /* destroy */
+ hdb_get_entry,
+ hdb_start_seq_get,
+ hdb_next_entry,
+ hdb_end_seq_get,
+ NULL, /* add */
+ NULL, /* remove */
+ NULL,
+ 0
+};
+
+krb5_kt_ops hdb_get_kt_ops = {
+ "HDBGET",
+ hdb_resolve,
+ hdb_get_name,
+ hdb_close,
+ NULL,
+ hdb_get_entry,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0
+};
diff --git a/third_party/heimdal/lib/hdb/libhdb-exports.def b/third_party/heimdal/lib/hdb/libhdb-exports.def
new file mode 100644
index 0000000..72a7fb7
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/libhdb-exports.def
@@ -0,0 +1,180 @@
+EXPORTS
+ encode_HDB_keyset
+ _hdb_fetch_kvno
+ _hdb_remove
+ _hdb_store
+ hdb_add_current_keys_to_history
+ hdb_add_history_key
+ hdb_add_history_keyset
+ hdb_add_master_key
+ hdb_change_kvno
+ hdb_check_db_format
+ hdb_clear_extension
+ hdb_clear_master_key
+ hdb_create
+ hdb_db_dir
+ hdb_dbinfo_get_acl_file
+ hdb_dbinfo_get_binding
+ hdb_dbinfo_get_dbname
+ hdb_dbinfo_get_label
+ hdb_dbinfo_get_log_file
+ hdb_dbinfo_get_mkey_file
+ hdb_dbinfo_get_next
+ hdb_dbinfo_get_realm
+ hdb_derive_etypes
+ hdb_default_db
+ hdb_enctype2key
+ hdb_entry2string
+ hdb_entry2value
+ hdb_entry_add_key_rotation
+ hdb_entry_alias2value
+ hdb_entry_check_mandatory
+ hdb_entry_clear_password
+ hdb_entry_get_ConstrainedDelegACL
+ hdb_entry_get_aliases
+ hdb_entry_get_key_rotation
+ hdb_entry_get_krb5_config
+ hdb_entry_get_password
+ hdb_entry_get_pkinit_acl
+ hdb_entry_get_pkinit_cert
+ hdb_entry_get_pkinit_hash
+ hdb_entry_get_pw_change_time
+ hdb_entry_set_krb5_config
+ hdb_entry_set_password
+ hdb_entry_set_pw_change_time
+ hdb_fetch_kvno
+ hdb_find_extension
+ hdb_foreach
+ hdb_free_dbinfo
+ hdb_free_entry
+ hdb_free_key
+ hdb_free_keys
+ hdb_free_master_key
+ hdb_generate_key_set
+ hdb_generate_key_set_password
+ hdb_generate_key_set_password_with_ks_tuple
+ hdb_get_dbinfo
+ hdb_get_instance
+ hdb_init_db
+ hdb_install_keyset
+ hdb_interface_version DATA
+ hdb_key2principal
+ hdb_kvno2keys
+ hdb_list_builtin
+ hdb_lock
+ hdb_next_enctype2key
+ hdb_principal2key
+ hdb_print_entry
+ hdb_process_master_key
+ hdb_prune_keys
+ hdb_prune_keys_kvno
+ hdb_read_master_key
+ hdb_remove_keys
+ hdb_replace_extension
+ hdb_seal_key
+ hdb_seal_key_mkey
+ hdb_seal_keys
+ hdb_seal_keys_mkey
+ hdb_set_last_modified_by
+ hdb_set_master_key
+ hdb_set_master_keyfile
+ hdb_unlock
+ hdb_unseal_key
+ hdb_unseal_key_mkey
+ hdb_unseal_keys
+ hdb_unseal_keys_kvno
+ hdb_unseal_keys_mkey
+ hdb_validate_key_rotation
+ hdb_validate_key_rotations
+ hdb_value2entry
+ hdb_value2entry_alias
+ hdb_write_master_key
+ length_HDB_keyset
+ initialize_hdb_error_table_r
+
+ hdb_kt_ops
+ hdb_get_kt_ops
+
+; MIT KDB related entries
+ _hdb_mdb_value2entry
+ _hdb_mit_dump2mitdb_entry
+
+; some random bits needed for libkadm
+ add_HDB_Ext_KeyRotation
+ add_HDB_Ext_KeySet
+ add_Keys
+ asn1_HDBFlags_units
+ copy_Event
+ copy_HDB_EncTypeList
+ copy_hdb_entry
+ copy_HDB_entry
+ copy_hdb_entry_alias
+ copy_HDB_entry_alias
+ copy_HDB_EntryOrAlias
+ copy_HDB_extensions
+ copy_HDB_Ext_KeyRotation
+ copy_Key
+ copy_Keys
+ copy_Salt
+ decode_HDB_EncTypeList
+ decode_hdb_entry
+ decode_HDB_entry
+ decode_hdb_entry_alias
+ decode_HDB_entry_alias
+ decode_HDB_EntryOrAlias
+ decode_HDB_Ext_Aliases
+ decode_HDB_extension
+ decode_HDB_Ext_KeyRotation
+ decode_HDB_Ext_PKINIT_acl
+ decode_Key
+ decode_Keys
+ encode_HDB_EncTypeList
+ encode_hdb_entry
+ encode_HDB_entry
+ encode_hdb_entry_alias
+ encode_HDB_entry_alias
+ encode_HDB_EntryOrAlias
+ encode_HDB_Ext_Aliases
+ encode_HDB_extension
+ encode_HDB_Ext_KeyRotation
+ encode_HDB_Ext_PKINIT_acl
+ encode_hdb_keyset
+ encode_Key
+ encode_Keys
+ free_Event
+ free_HDB_EncTypeList
+ free_hdb_entry
+ free_HDB_entry
+ free_hdb_entry_alias
+ free_HDB_entry_alias
+ free_HDB_EntryOrAlias
+ free_HDB_Ext_Aliases
+ free_HDB_extension
+ free_HDB_extensions
+ free_HDB_Ext_KeyRotation
+ free_HDB_Ext_KeySet
+ free_HDB_Ext_PKINIT_acl
+ free_hdb_keyset
+ free_HDB_keyset
+ free_Key
+ free_Keys
+ free_Salt
+ HDBFlags2int
+ int2HDBFlags
+ int2KeyRotationFlags
+ KeyRotationFlags2int
+ length_HDB_EncTypeList
+ length_hdb_entry
+ length_HDB_entry
+ length_hdb_entry_alias
+ length_HDB_entry_alias
+ length_HDB_EntryOrAlias
+ length_HDB_Ext_Aliases
+ length_HDB_extension
+ length_HDB_Ext_KeyRotation
+ length_HDB_Ext_PKINIT_acl
+ length_hdb_keyset
+ length_Key
+ length_Keys
+ remove_HDB_Ext_KeyRotation
+ remove_Keys
diff --git a/third_party/heimdal/lib/hdb/libhdb-version.rc b/third_party/heimdal/lib/hdb/libhdb-version.rc
new file mode 100644
index 0000000..b0d417b
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/libhdb-version.rc
@@ -0,0 +1,36 @@
+/***********************************************************************
+ * Copyright (c) 2010, Secure Endpoints Inc.
+ * 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.
+ *
+ **********************************************************************/
+
+#define RC_FILE_TYPE VFT_DLL
+#define RC_FILE_DESC_0409 "Heimdal DB Library"
+#define RC_FILE_ORIG_0409 "libhdb.dll"
+
+#include "../../windows/version.rc"
diff --git a/third_party/heimdal/lib/hdb/mkey.c b/third_party/heimdal/lib/hdb/mkey.c
new file mode 100644
index 0000000..cfc27d4
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/mkey.c
@@ -0,0 +1,769 @@
+/*
+ * Copyright (c) 2000 - 2004 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "hdb_locl.h"
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+struct hdb_master_key_data {
+ krb5_keytab_entry keytab;
+ krb5_crypto crypto;
+ struct hdb_master_key_data *next;
+ unsigned int key_usage;
+};
+
+void
+hdb_free_master_key(krb5_context context, hdb_master_key mkey)
+{
+ struct hdb_master_key_data *ptr;
+ while(mkey) {
+ krb5_kt_free_entry(context, &mkey->keytab);
+ if (mkey->crypto)
+ krb5_crypto_destroy(context, mkey->crypto);
+ ptr = mkey;
+ mkey = mkey->next;
+ free(ptr);
+ }
+}
+
+krb5_error_code
+hdb_process_master_key(krb5_context context,
+ int kvno, krb5_keyblock *key, krb5_enctype etype,
+ hdb_master_key *mkey)
+{
+ krb5_error_code ret;
+
+ *mkey = calloc(1, sizeof(**mkey));
+ if(*mkey == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ (*mkey)->key_usage = HDB_KU_MKEY;
+ (*mkey)->keytab.vno = kvno;
+ ret = krb5_parse_name(context, "K/M", &(*mkey)->keytab.principal);
+ if(ret)
+ goto fail;
+ ret = krb5_copy_keyblock_contents(context, key, &(*mkey)->keytab.keyblock);
+ if(ret)
+ goto fail;
+ if(etype != 0)
+ (*mkey)->keytab.keyblock.keytype = etype;
+ (*mkey)->keytab.timestamp = time(NULL);
+ ret = krb5_crypto_init(context, key, etype, &(*mkey)->crypto);
+ if(ret)
+ goto fail;
+ return 0;
+ fail:
+ hdb_free_master_key(context, *mkey);
+ *mkey = NULL;
+ return ret;
+}
+
+krb5_error_code
+hdb_add_master_key(krb5_context context, krb5_keyblock *key,
+ hdb_master_key *inout)
+{
+ int vno = 0;
+ hdb_master_key p;
+ krb5_error_code ret;
+
+ for(p = *inout; p; p = p->next)
+ vno = max(vno, p->keytab.vno);
+ vno++;
+ ret = hdb_process_master_key(context, vno, key, 0, &p);
+ if(ret)
+ return ret;
+ p->next = *inout;
+ *inout = p;
+ return 0;
+}
+
+static krb5_error_code
+read_master_keytab(krb5_context context, const char *filename,
+ hdb_master_key *mkey)
+{
+ krb5_error_code ret;
+ krb5_keytab id;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry entry;
+ hdb_master_key p;
+
+ *mkey = NULL;
+ ret = krb5_kt_resolve(context, filename, &id);
+ if(ret)
+ return ret;
+
+ ret = krb5_kt_start_seq_get(context, id, &cursor);
+ if(ret)
+ goto out;
+ while(krb5_kt_next_entry(context, id, &entry, &cursor) == 0) {
+ p = calloc(1, sizeof(*p));
+ if (p == NULL) {
+ ret = ENOMEM;
+ break;
+ }
+ p->keytab = entry;
+ p->next = *mkey;
+ *mkey = p;
+ ret = krb5_crypto_init(context, &p->keytab.keyblock, 0, &p->crypto);
+ if (ret)
+ break;
+ }
+ krb5_kt_end_seq_get(context, id, &cursor);
+ out:
+ krb5_kt_close(context, id);
+ if (ret) {
+ hdb_free_master_key(context, *mkey);
+ *mkey = NULL;
+ }
+ return ret;
+}
+
+/* read a MIT master keyfile */
+static krb5_error_code
+read_master_mit(krb5_context context, const char *filename,
+ int byteorder, hdb_master_key *mkey)
+{
+ int fd;
+ krb5_error_code ret;
+ krb5_storage *sp;
+ int16_t enctype;
+ krb5_keyblock key;
+
+ fd = open(filename, O_RDONLY | O_BINARY);
+ if(fd < 0) {
+ int save_errno = errno;
+ krb5_set_error_message(context, save_errno, "failed to open %s: %s",
+ filename, strerror(save_errno));
+ return save_errno;
+ }
+ sp = krb5_storage_from_fd(fd);
+ if(sp == NULL) {
+ close(fd);
+ return errno;
+ }
+ krb5_storage_set_flags(sp, byteorder);
+ /* could possibly use ret_keyblock here, but do it with more
+ checks for now */
+ {
+ ret = krb5_ret_int16(sp, &enctype);
+ if (ret)
+ goto out;
+ ret = krb5_enctype_valid(context, enctype);
+ if (ret)
+ goto out;
+ key.keytype = enctype;
+ ret = krb5_ret_data(sp, &key.keyvalue);
+ if(ret)
+ goto out;
+ }
+ ret = hdb_process_master_key(context, 1, &key, 0, mkey);
+ krb5_free_keyblock_contents(context, &key);
+ out:
+ krb5_storage_free(sp);
+ close(fd);
+ return ret;
+}
+
+/* read an old master key file */
+static krb5_error_code
+read_master_encryptionkey(krb5_context context, const char *filename,
+ hdb_master_key *mkey)
+{
+ int fd;
+ krb5_keyblock key;
+ krb5_error_code ret;
+ unsigned char buf[256];
+ ssize_t len;
+ size_t ret_len;
+
+ fd = open(filename, O_RDONLY | O_BINARY);
+ if(fd < 0) {
+ int save_errno = errno;
+ krb5_set_error_message(context, save_errno, "failed to open %s: %s",
+ filename, strerror(save_errno));
+ return save_errno;
+ }
+
+ len = read(fd, buf, sizeof(buf));
+ close(fd);
+ if(len < 0) {
+ int save_errno = errno;
+ krb5_set_error_message(context, save_errno, "error reading %s: %s",
+ filename, strerror(save_errno));
+ return save_errno;
+ }
+
+ ret = decode_EncryptionKey(buf, len, &key, &ret_len);
+ memset_s(buf, sizeof(buf), 0, sizeof(buf));
+ if(ret)
+ return ret;
+
+ /* Originally, the keytype was just that, and later it got changed
+ to des-cbc-md5, but we always used des in cfb64 mode. This
+ should cover all cases, but will break if someone has hacked
+ this code to really use des-cbc-md5 -- but then that's not my
+ problem. */
+ if(key.keytype == ETYPE_DES_CBC_CRC || key.keytype == ETYPE_DES_CBC_MD5)
+ key.keytype = ETYPE_DES_CFB64_NONE;
+
+ ret = hdb_process_master_key(context, 0, &key, 0, mkey);
+ krb5_free_keyblock_contents(context, &key);
+ return ret;
+}
+
+/* read a krb4 /.k style file */
+static krb5_error_code
+read_master_krb4(krb5_context context, const char *filename,
+ hdb_master_key *mkey)
+{
+ int fd;
+ krb5_keyblock key;
+ krb5_error_code ret;
+ unsigned char buf[256];
+ ssize_t len;
+
+ fd = open(filename, O_RDONLY | O_BINARY);
+ if(fd < 0) {
+ int save_errno = errno;
+ krb5_set_error_message(context, save_errno, "failed to open %s: %s",
+ filename, strerror(save_errno));
+ return save_errno;
+ }
+
+ len = read(fd, buf, sizeof(buf));
+ close(fd);
+ if(len < 0) {
+ int save_errno = errno;
+ krb5_set_error_message(context, save_errno, "error reading %s: %s",
+ filename, strerror(save_errno));
+ return save_errno;
+ }
+ if(len != 8) {
+ krb5_set_error_message(context, HEIM_ERR_EOF,
+ "bad contents of %s", filename);
+ return HEIM_ERR_EOF; /* XXX file might be too large */
+ }
+
+ memset(&key, 0, sizeof(key));
+ key.keytype = ETYPE_DES_PCBC_NONE;
+ ret = krb5_data_copy(&key.keyvalue, buf, len);
+ memset_s(buf, sizeof(buf), 0, sizeof(buf));
+ if(ret)
+ return ret;
+
+ ret = hdb_process_master_key(context, 0, &key, 0, mkey);
+ krb5_free_keyblock_contents(context, &key);
+ return ret;
+}
+
+krb5_error_code
+hdb_read_master_key(krb5_context context, const char *filename,
+ hdb_master_key *mkey)
+{
+ FILE *f;
+ unsigned char buf[16];
+ krb5_error_code ret;
+
+ off_t len;
+
+ *mkey = NULL;
+
+ if(filename == NULL)
+ filename = HDB_DB_DIR "/m-key";
+
+ f = fopen(filename, "r");
+ if(f == NULL) {
+ int save_errno = errno;
+ krb5_set_error_message(context, save_errno, "failed to open %s: %s",
+ filename, strerror(save_errno));
+ return save_errno;
+ }
+
+ if(fread(buf, 1, 2, f) != 2) {
+ fclose(f);
+ krb5_set_error_message(context, HEIM_ERR_EOF, "end of file reading %s", filename);
+ return HEIM_ERR_EOF;
+ }
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+
+ if(fclose(f) != 0)
+ return errno;
+
+ if(len < 0)
+ return errno;
+
+ if(len == 8) {
+ ret = read_master_krb4(context, filename, mkey);
+ } else if(buf[0] == 0x30 && len <= 127 && buf[1] == len - 2) {
+ ret = read_master_encryptionkey(context, filename, mkey);
+ } else if(buf[0] == 5 && buf[1] >= 1 && buf[1] <= 2) {
+ ret = read_master_keytab(context, filename, mkey);
+ } else {
+ /*
+ * Check both LittleEndian and BigEndian since they key file
+ * might be moved from a machine with diffrent byte order, or
+ * its running on MacOS X that always uses BE master keys.
+ */
+ ret = read_master_mit(context, filename, KRB5_STORAGE_BYTEORDER_LE, mkey);
+ if (ret)
+ ret = read_master_mit(context, filename, KRB5_STORAGE_BYTEORDER_BE, mkey);
+ }
+ return ret;
+}
+
+krb5_error_code
+hdb_write_master_key(krb5_context context, const char *filename,
+ hdb_master_key mkey)
+{
+ krb5_error_code ret;
+ hdb_master_key p;
+ krb5_keytab kt;
+
+ if(filename == NULL)
+ filename = HDB_DB_DIR "/m-key";
+
+ ret = krb5_kt_resolve(context, filename, &kt);
+ if(ret)
+ return ret;
+
+ for(p = mkey; p; p = p->next) {
+ ret = krb5_kt_add_entry(context, kt, &p->keytab);
+ }
+
+ krb5_kt_close(context, kt);
+
+ return ret;
+}
+
+krb5_error_code
+_hdb_set_master_key_usage(krb5_context context, HDB *db, unsigned int key_usage)
+{
+ if (db->hdb_master_key_set == 0)
+ return HDB_ERR_NO_MKEY;
+ db->hdb_master_key->key_usage = key_usage;
+ return 0;
+}
+
+hdb_master_key
+_hdb_find_master_key(unsigned int *mkvno, hdb_master_key mkey)
+{
+ hdb_master_key ret = NULL;
+ while(mkey) {
+ if(ret == NULL && mkey->keytab.vno == 0)
+ ret = mkey;
+ if(mkvno == NULL) {
+ if(ret == NULL || mkey->keytab.vno > ret->keytab.vno)
+ ret = mkey;
+ } else if((uint32_t)mkey->keytab.vno == *mkvno)
+ return mkey;
+ mkey = mkey->next;
+ }
+ return ret;
+}
+
+int
+_hdb_mkey_version(hdb_master_key mkey)
+{
+ return mkey->keytab.vno;
+}
+
+int
+_hdb_mkey_decrypt(krb5_context context, hdb_master_key key,
+ krb5_key_usage usage,
+ void *ptr, size_t size, krb5_data *res)
+{
+ return krb5_decrypt(context, key->crypto, usage,
+ ptr, size, res);
+}
+
+int
+_hdb_mkey_encrypt(krb5_context context, hdb_master_key key,
+ krb5_key_usage usage,
+ const void *ptr, size_t size, krb5_data *res)
+{
+ return krb5_encrypt(context, key->crypto, usage,
+ ptr, size, res);
+}
+
+krb5_error_code
+hdb_unseal_key_mkey(krb5_context context, Key *k, hdb_master_key mkey)
+{
+
+ krb5_error_code ret;
+ krb5_data res;
+ size_t keysize;
+
+ hdb_master_key key;
+
+ if(k->mkvno == NULL)
+ return 0;
+
+ key = _hdb_find_master_key(k->mkvno, mkey);
+
+ if (key == NULL)
+ return HDB_ERR_NO_MKEY;
+
+ ret = _hdb_mkey_decrypt(context, key, HDB_KU_MKEY,
+ k->key.keyvalue.data,
+ k->key.keyvalue.length,
+ &res);
+ if(ret == KRB5KRB_AP_ERR_BAD_INTEGRITY) {
+ /* try to decrypt with MIT key usage */
+ ret = _hdb_mkey_decrypt(context, key, 0,
+ k->key.keyvalue.data,
+ k->key.keyvalue.length,
+ &res);
+ }
+ if (ret)
+ return ret;
+
+ /* fixup keylength if the key got padded when encrypting it */
+ ret = krb5_enctype_keysize(context, k->key.keytype, &keysize);
+ if (ret) {
+ krb5_data_free(&res);
+ return ret;
+ }
+ if (keysize > res.length) {
+ krb5_data_free(&res);
+ return KRB5_BAD_KEYSIZE;
+ }
+
+ memset(k->key.keyvalue.data, 0, k->key.keyvalue.length);
+ free(k->key.keyvalue.data);
+ k->key.keyvalue = res;
+ k->key.keyvalue.length = keysize;
+ free(k->mkvno);
+ k->mkvno = NULL;
+
+ return 0;
+}
+
+krb5_error_code
+hdb_unseal_keys_mkey(krb5_context context, hdb_entry *ent, hdb_master_key mkey)
+{
+ size_t i;
+
+ for(i = 0; i < ent->keys.len; i++){
+ krb5_error_code ret;
+
+ ret = hdb_unseal_key_mkey(context, &ent->keys.val[i], mkey);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+krb5_error_code
+hdb_unseal_keys(krb5_context context, HDB *db, hdb_entry *ent)
+{
+ if (db->hdb_master_key_set == 0)
+ return 0;
+ return hdb_unseal_keys_mkey(context, ent, db->hdb_master_key);
+}
+
+/*
+ * Unseal the keys for the given kvno (or all of them) of entry.
+ *
+ * If kvno == 0 -> unseal all.
+ * if kvno != 0 -> unseal the requested kvno and make sure it's the one listed
+ * as the current keyset for the entry (swapping it with a
+ * historical keyset if need be).
+ */
+krb5_error_code
+hdb_unseal_keys_kvno(krb5_context context, HDB *db, krb5_kvno kvno,
+ unsigned flags, hdb_entry *ent)
+{
+ krb5_error_code ret = HDB_ERR_NOENTRY;
+ HDB_extension *ext;
+ HDB_Ext_KeySet *hist_keys;
+ Key *tmp_val;
+ time_t tmp_set_time;
+ unsigned int tmp_len;
+ unsigned int kvno_diff = 0;
+ krb5_kvno tmp_kvno;
+ size_t i, k;
+ int exclude_dead = 0;
+ KerberosTime now = 0;
+
+ if (kvno == 0)
+ ret = 0;
+
+ if ((flags & HDB_F_LIVE_CLNT_KVNOS) || (flags & HDB_F_LIVE_SVC_KVNOS)) {
+ exclude_dead = 1;
+ now = time(NULL);
+ if (HDB_F_LIVE_CLNT_KVNOS)
+ kvno_diff = hdb_entry_get_kvno_diff_clnt(ent);
+ else
+ kvno_diff = hdb_entry_get_kvno_diff_svc(ent);
+ }
+
+ ext = hdb_find_extension(ent, choice_HDB_extension_data_hist_keys);
+ if (ext == NULL || (&ext->data.u.hist_keys)->len == 0)
+ return hdb_unseal_keys_mkey(context, ent, db->hdb_master_key);
+
+ /* For swapping; see below */
+ tmp_len = ent->keys.len;
+ tmp_val = ent->keys.val;
+ tmp_kvno = ent->kvno;
+ (void) hdb_entry_get_pw_change_time(ent, &tmp_set_time);
+
+ hist_keys = &ext->data.u.hist_keys;
+
+ for (i = 0; i < hist_keys->len; i++) {
+ if (kvno != 0 && hist_keys->val[i].kvno != kvno)
+ continue;
+
+ if (exclude_dead &&
+ ((ent->max_life != NULL &&
+ hist_keys->val[i].set_time != NULL &&
+ (*hist_keys->val[i].set_time) < (now - (*ent->max_life))) ||
+ (hist_keys->val[i].kvno < kvno &&
+ (kvno - hist_keys->val[i].kvno) > kvno_diff)))
+ /*
+ * The KDC may want to to check for this keyset's set_time
+ * is within the TGS principal's max_life, say. But we stop
+ * here.
+ */
+ continue;
+
+ /* Either the keys we want, or all the keys */
+ for (k = 0; k < hist_keys->val[i].keys.len; k++) {
+ ret = hdb_unseal_key_mkey(context,
+ &hist_keys->val[i].keys.val[k],
+ db->hdb_master_key);
+ /*
+ * If kvno == 0 we might not want to bail here! E.g., if we
+ * no longer have the right master key, so just ignore this.
+ *
+ * We could filter out keys that we can't decrypt here
+ * because of HDB_ERR_NO_MKEY. However, it seems safest to
+ * filter them out only where necessary, say, in kadm5.
+ */
+ if (ret && kvno != 0)
+ return ret;
+ if (ret && ret != HDB_ERR_NO_MKEY)
+ return (ret);
+ }
+
+ if (kvno == 0)
+ continue;
+
+ /*
+ * What follows is a bit of a hack.
+ *
+ * This is the keyset we're being asked for, but it's not the
+ * current keyset. So we add the current keyset to the history,
+ * leave the one we were asked for in the history, and pretend
+ * the one we were asked for is also the current keyset.
+ *
+ * This is a bit of a defensive hack in case an entry fetched
+ * this way ever gets modified then stored: if the keyset is not
+ * changed we can detect this and put things back, else we won't
+ * drop any keysets from history by accident.
+ *
+ * Note too that we only ever get called with a non-zero kvno
+ * either in the KDC or in cases where we aren't changing the
+ * HDB entry anyways, which is why this is just a defensive
+ * hack. We also don't fetch specific kvnos in the dump case,
+ * so there's no danger that we'll dump this entry and load it
+ * again, repeatedly causing the history to grow boundelessly.
+ */
+
+ /* Swap key sets */
+ ent->kvno = hist_keys->val[i].kvno;
+ ent->keys.val = hist_keys->val[i].keys.val;
+ ent->keys.len = hist_keys->val[i].keys.len;
+ if (hist_keys->val[i].set_time != NULL)
+ /* Sloppy, but the callers we expect won't care */
+ (void) hdb_entry_set_pw_change_time(context, ent,
+ *hist_keys->val[i].set_time);
+ hist_keys->val[i].kvno = tmp_kvno;
+ hist_keys->val[i].keys.val = tmp_val;
+ hist_keys->val[i].keys.len = tmp_len;
+ if (hist_keys->val[i].set_time != NULL)
+ /* Sloppy, but the callers we expect won't care */
+ *hist_keys->val[i].set_time = tmp_set_time;
+
+ return 0;
+ }
+
+ return (ret);
+}
+
+krb5_error_code
+hdb_unseal_key(krb5_context context, HDB *db, Key *k)
+{
+ if (db->hdb_master_key_set == 0)
+ return 0;
+ return hdb_unseal_key_mkey(context, k, db->hdb_master_key);
+}
+
+krb5_error_code
+hdb_seal_key_mkey(krb5_context context, Key *k, hdb_master_key mkey)
+{
+ krb5_error_code ret;
+ krb5_data res;
+ hdb_master_key key;
+
+ if(k->mkvno != NULL)
+ return 0;
+
+ key = _hdb_find_master_key(k->mkvno, mkey);
+
+ if (key == NULL)
+ return HDB_ERR_NO_MKEY;
+
+ ret = _hdb_mkey_encrypt(context, key, HDB_KU_MKEY,
+ k->key.keyvalue.data,
+ k->key.keyvalue.length,
+ &res);
+ if (ret)
+ return ret;
+
+ memset(k->key.keyvalue.data, 0, k->key.keyvalue.length);
+ free(k->key.keyvalue.data);
+ k->key.keyvalue = res;
+
+ if (k->mkvno == NULL) {
+ k->mkvno = malloc(sizeof(*k->mkvno));
+ if (k->mkvno == NULL)
+ return ENOMEM;
+ }
+ *k->mkvno = key->keytab.vno;
+
+ return 0;
+}
+
+krb5_error_code
+hdb_seal_keys_mkey(krb5_context context, hdb_entry *ent, hdb_master_key mkey)
+{
+ HDB_extension *ext;
+ HDB_Ext_KeySet *hist_keys;
+ size_t i, k;
+ krb5_error_code ret;
+
+ for(i = 0; i < ent->keys.len; i++){
+ ret = hdb_seal_key_mkey(context, &ent->keys.val[i], mkey);
+ if (ret)
+ return ret;
+ }
+
+ ext = hdb_find_extension(ent, choice_HDB_extension_data_hist_keys);
+ if (ext == NULL)
+ return 0;
+ hist_keys = &ext->data.u.hist_keys;
+
+ for (i = 0; i < hist_keys->len; i++) {
+ for (k = 0; k < hist_keys->val[i].keys.len; k++) {
+ ret = hdb_seal_key_mkey(context, &hist_keys->val[i].keys.val[k],
+ mkey);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+krb5_error_code
+hdb_seal_keys(krb5_context context, HDB *db, hdb_entry *ent)
+{
+ if (db->hdb_master_key_set == 0)
+ return 0;
+
+ return hdb_seal_keys_mkey(context, ent, db->hdb_master_key);
+}
+
+krb5_error_code
+hdb_seal_key(krb5_context context, HDB *db, Key *k)
+{
+ if (db->hdb_master_key_set == 0)
+ return 0;
+
+ return hdb_seal_key_mkey(context, k, db->hdb_master_key);
+}
+
+krb5_error_code
+hdb_set_master_key(krb5_context context,
+ HDB *db,
+ krb5_keyblock *key)
+{
+ krb5_error_code ret;
+ hdb_master_key mkey;
+
+ ret = hdb_process_master_key(context, 0, key, 0, &mkey);
+ if (ret)
+ return ret;
+ db->hdb_master_key = mkey;
+#if 0 /* XXX - why? */
+ des_set_random_generator_seed(key.keyvalue.data);
+#endif
+ db->hdb_master_key_set = 1;
+ db->hdb_master_key->key_usage = HDB_KU_MKEY;
+ return 0;
+}
+
+krb5_error_code
+hdb_set_master_keyfile (krb5_context context,
+ HDB *db,
+ const char *keyfile)
+{
+ hdb_master_key key;
+ krb5_error_code ret;
+
+ ret = hdb_read_master_key(context, keyfile, &key);
+ if (ret) {
+ if (ret != ENOENT)
+ return ret;
+ krb5_clear_error_message(context);
+ return 0;
+ }
+ db->hdb_master_key = key;
+ db->hdb_master_key_set = 1;
+ return ret;
+}
+
+krb5_error_code
+hdb_clear_master_key (krb5_context context,
+ HDB *db)
+{
+ if (db->hdb_master_key_set) {
+ hdb_free_master_key(context, db->hdb_master_key);
+ db->hdb_master_key_set = 0;
+ }
+ return 0;
+}
diff --git a/third_party/heimdal/lib/hdb/ndbm.c b/third_party/heimdal/lib/hdb/ndbm.c
new file mode 100644
index 0000000..52c52c8
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/ndbm.c
@@ -0,0 +1,406 @@
+/*
+ * Copyright (c) 1997 - 2001 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "hdb_locl.h"
+
+#if HAVE_NDBM
+
+#if defined(HAVE_GDBM_NDBM_H)
+#include <gdbm/ndbm.h>
+#define WRITE_SUPPORT 1
+#elif defined(HAVE_NDBM_H)
+#include <ndbm.h>
+#elif defined(HAVE_DBM_H)
+#define WRITE_SUPPORT 1
+#include <dbm.h>
+#endif
+
+struct ndbm_db {
+ DBM *db;
+ int lock_fd;
+};
+
+static krb5_error_code
+NDBM_destroy(krb5_context context, HDB *db)
+{
+ hdb_clear_master_key(context, db);
+ krb5_config_free_strings(db->virtual_hostbased_princ_svcs);
+ free(db->hdb_name);
+ free(db);
+ return 0;
+}
+
+static krb5_error_code
+NDBM_lock(krb5_context context, HDB *db, int operation)
+{
+ struct ndbm_db *d = db->hdb_db;
+ return hdb_lock(d->lock_fd, operation);
+}
+
+static krb5_error_code
+NDBM_unlock(krb5_context context, HDB *db)
+{
+ struct ndbm_db *d = db->hdb_db;
+ return hdb_unlock(d->lock_fd);
+}
+
+static krb5_error_code
+NDBM_seq(krb5_context context, HDB *db,
+ unsigned flags, hdb_entry *entry, int first)
+
+{
+ struct ndbm_db *d = (struct ndbm_db *)db->hdb_db;
+ datum key, value;
+ krb5_data key_data, data;
+ krb5_error_code ret = 0;
+
+ if(first)
+ key = dbm_firstkey(d->db);
+ else
+ key = dbm_nextkey(d->db);
+ if(key.dptr == NULL)
+ return HDB_ERR_NOENTRY;
+ key_data.data = key.dptr;
+ key_data.length = key.dsize;
+ ret = db->hdb_lock(context, db, HDB_RLOCK);
+ if(ret) return ret;
+ value = dbm_fetch(d->db, key);
+ db->hdb_unlock(context, db);
+ data.data = value.dptr;
+ data.length = value.dsize;
+ memset(entry, 0, sizeof(*entry));
+ if(hdb_value2entry(context, &data, entry))
+ return NDBM_seq(context, db, flags, entry, 0);
+ if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
+ ret = hdb_unseal_keys (context, db, entry);
+ if (ret)
+ hdb_free_entry (context, db, entry);
+ }
+ if (ret == 0 && entry->principal == NULL) {
+ entry->principal = malloc (sizeof(*entry->principal));
+ if (entry->principal == NULL) {
+ hdb_free_entry (context, db, entry);
+ ret = ENOMEM;
+ krb5_set_error_message(context, ret, "malloc: out of memory");
+ } else {
+ hdb_key2principal (context, &key_data, entry->principal);
+ }
+ }
+ return ret;
+}
+
+
+static krb5_error_code
+NDBM_firstkey(krb5_context context, HDB *db,unsigned flags,hdb_entry *entry)
+{
+ return NDBM_seq(context, db, flags, entry, 1);
+}
+
+
+static krb5_error_code
+NDBM_nextkey(krb5_context context, HDB *db, unsigned flags,hdb_entry *entry)
+{
+ return NDBM_seq(context, db, flags, entry, 0);
+}
+
+static krb5_error_code
+open_lock_file(krb5_context context, const char *db_name, int *fd)
+{
+ char *lock_file;
+ int ret = 0;
+
+ /* lock old and new databases */
+ if (asprintf(&lock_file, "%s.lock", db_name) == -1) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ *fd = open(lock_file, O_RDWR | O_CREAT, 0600);
+ if(*fd < 0) {
+ ret = errno;
+ krb5_set_error_message(context, ret, "open(%s): %s", lock_file,
+ strerror(ret));
+ }
+ free(lock_file);
+ return ret;
+}
+
+
+static krb5_error_code
+NDBM_rename(krb5_context context, HDB *db, const char *new_name)
+{
+ int ret;
+ char *old_dir = NULL, *old_pag = NULL;
+ char *new_dir = NULL, *new_pag = NULL;
+ int old_lock_fd, new_lock_fd;
+
+ /* lock old and new databases */
+ ret = open_lock_file(context, db->hdb_name, &old_lock_fd);
+ if (ret)
+ return ret;
+
+ ret = hdb_lock(old_lock_fd, HDB_WLOCK);
+ if(ret) {
+ close(old_lock_fd);
+ return ret;
+ }
+
+ ret = open_lock_file(context, new_name, &new_lock_fd);
+ if (ret) {
+ hdb_unlock(old_lock_fd);
+ close(old_lock_fd);
+ return ret;
+ }
+
+ ret = hdb_lock(new_lock_fd, HDB_WLOCK);
+ if(ret) {
+ hdb_unlock(old_lock_fd);
+ close(old_lock_fd);
+ close(new_lock_fd);
+ return ret;
+ }
+
+ if (asprintf(&old_dir, "%s.dir", db->hdb_name) == -1) {
+ old_dir = NULL;
+ ret = ENOMEM;
+ goto out;
+ }
+ if (asprintf(&old_pag, "%s.pag", db->hdb_name) == -1) {
+ old_pag = NULL;
+ ret = ENOMEM;
+ goto out;
+ }
+ if (asprintf(&new_dir, "%s.dir", new_name) == -1) {
+ new_dir = NULL;
+ ret = ENOMEM;
+ goto out;
+ }
+ if (asprintf(&new_pag, "%s.pag", new_name) == -1) {
+ new_pag = NULL;
+ ret = ENOMEM;
+ goto out;
+ }
+
+ ret = rename(old_dir, new_dir) || rename(old_pag, new_pag);
+ if (ret) {
+ ret = errno;
+ if (ret == 0)
+ ret = EPERM;
+ krb5_set_error_message(context, ret, "rename: %s", strerror(ret));
+ }
+
+ out:
+ free(old_dir);
+ free(old_pag);
+ free(new_dir);
+ free(new_pag);
+
+ hdb_unlock(new_lock_fd);
+ hdb_unlock(old_lock_fd);
+ close(new_lock_fd);
+ close(old_lock_fd);
+
+ if(ret)
+ return ret;
+
+ free(db->hdb_name);
+ db->hdb_name = strdup(new_name);
+ return 0;
+}
+
+static krb5_error_code
+NDBM__get(krb5_context context, HDB *db, krb5_data key, krb5_data *reply)
+{
+ struct ndbm_db *d = (struct ndbm_db *)db->hdb_db;
+ datum k, v;
+ int code;
+
+ k.dptr = key.data;
+ k.dsize = key.length;
+ code = db->hdb_lock(context, db, HDB_RLOCK);
+ if(code)
+ return code;
+ v = dbm_fetch(d->db, k);
+ db->hdb_unlock(context, db);
+ if(v.dptr == NULL)
+ return HDB_ERR_NOENTRY;
+
+ krb5_data_copy(reply, v.dptr, v.dsize);
+ return 0;
+}
+
+static krb5_error_code
+NDBM__put(krb5_context context, HDB *db, int replace,
+ krb5_data key, krb5_data value)
+{
+#ifdef WRITE_SUPPORT
+ struct ndbm_db *d = (struct ndbm_db *)db->hdb_db;
+ datum k, v;
+ int code;
+
+ k.dptr = key.data;
+ k.dsize = key.length;
+ v.dptr = value.data;
+ v.dsize = value.length;
+
+ code = db->hdb_lock(context, db, HDB_WLOCK);
+ if(code)
+ return code;
+ code = dbm_store(d->db, k, v, replace ? DBM_REPLACE : DBM_INSERT);
+ db->hdb_unlock(context, db);
+ if(code == 1)
+ return HDB_ERR_EXISTS;
+ if (code < 0)
+ return code;
+ return 0;
+#else
+ return HDB_ERR_NO_WRITE_SUPPORT;
+#endif
+}
+
+static krb5_error_code
+NDBM__del(krb5_context context, HDB *db, krb5_data key)
+{
+ struct ndbm_db *d = (struct ndbm_db *)db->hdb_db;
+ datum k;
+ int code;
+ krb5_error_code ret;
+
+ k.dptr = key.data;
+ k.dsize = key.length;
+ ret = db->hdb_lock(context, db, HDB_WLOCK);
+ if(ret) return ret;
+ code = dbm_delete(d->db, k);
+ db->hdb_unlock(context, db);
+ if(code < 0)
+ return errno;
+ return 0;
+}
+
+
+static krb5_error_code
+NDBM_close(krb5_context context, HDB *db)
+{
+ struct ndbm_db *d = db->hdb_db;
+ dbm_close(d->db);
+ close(d->lock_fd);
+ free(d);
+ return 0;
+}
+
+static krb5_error_code
+NDBM_open(krb5_context context, HDB *db, int flags, mode_t mode)
+{
+ krb5_error_code ret;
+ struct ndbm_db *d = malloc(sizeof(*d));
+
+ if(d == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ d->db = dbm_open((char*)db->hdb_name, flags, mode);
+ if(d->db == NULL){
+ ret = errno;
+ free(d);
+ krb5_set_error_message(context, ret, "dbm_open(%s): %s", db->hdb_name,
+ strerror(ret));
+ return ret;
+ }
+
+ ret = open_lock_file(context, db->hdb_name, &d->lock_fd);
+ if (ret) {
+ ret = errno;
+ dbm_close(d->db);
+ free(d);
+ krb5_set_error_message(context, ret, "open(lock file): %s",
+ strerror(ret));
+ return ret;
+ }
+
+ db->hdb_db = d;
+ if((flags & O_ACCMODE) == O_RDONLY)
+ ret = hdb_check_db_format(context, db);
+ else
+ ret = hdb_init_db(context, db);
+ if(ret == HDB_ERR_NOENTRY)
+ return 0;
+ if (ret) {
+ NDBM_close(context, db);
+ krb5_set_error_message(context, ret, "hdb_open: failed %s database %s",
+ (flags & O_ACCMODE) == O_RDONLY ?
+ "checking format of" : "initialize",
+ db->hdb_name);
+ }
+ return ret;
+}
+
+krb5_error_code
+hdb_ndbm_create(krb5_context context, HDB **db,
+ const char *filename)
+{
+ *db = calloc(1, sizeof(**db));
+ if (*db == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ (*db)->hdb_db = NULL;
+ (*db)->hdb_name = strdup(filename);
+ if ((*db)->hdb_name == NULL) {
+ free(*db);
+ *db = NULL;
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+ (*db)->hdb_master_key_set = 0;
+ (*db)->hdb_openp = 0;
+ (*db)->hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL;
+ (*db)->hdb_open = NDBM_open;
+ (*db)->hdb_close = NDBM_close;
+ (*db)->hdb_fetch_kvno = _hdb_fetch_kvno;
+ (*db)->hdb_store = _hdb_store;
+ (*db)->hdb_remove = _hdb_remove;
+ (*db)->hdb_firstkey = NDBM_firstkey;
+ (*db)->hdb_nextkey= NDBM_nextkey;
+ (*db)->hdb_lock = NDBM_lock;
+ (*db)->hdb_unlock = NDBM_unlock;
+ (*db)->hdb_rename = NDBM_rename;
+ (*db)->hdb__get = NDBM__get;
+ (*db)->hdb__put = NDBM__put;
+ (*db)->hdb__del = NDBM__del;
+ (*db)->hdb_destroy = NDBM_destroy;
+ return 0;
+}
+
+#endif /* HAVE_NDBM */
diff --git a/third_party/heimdal/lib/hdb/print.c b/third_party/heimdal/lib/hdb/print.c
new file mode 100644
index 0000000..7f25358
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/print.c
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 1999-2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of KTH nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY KTH AND ITS 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 KTH OR ITS 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 "hdb_locl.h"
+#include <hex.h>
+#include <ctype.h>
+
+/*
+ This is the present contents of a dump line. This might change at
+ any time. Fields are separated by white space.
+
+ principal
+ keyblock
+ kvno
+ keys...
+ mkvno
+ enctype
+ keyvalue
+ salt (- means use normal salt)
+ creation date and principal
+ modification date and principal
+ principal valid from date (not used)
+ principal valid end date (not used)
+ principal key expires (not used)
+ max ticket life
+ max renewable life
+ flags
+ generation number
+ */
+
+/*
+ * These utility functions return the number of bytes written or -1, and
+ * they set an error in the context.
+ */
+static ssize_t
+append_string(krb5_context context, krb5_storage *sp, const char *fmt, ...)
+{
+ ssize_t sz;
+ char *s;
+ int rc;
+ va_list ap;
+ va_start(ap, fmt);
+ rc = vasprintf(&s, fmt, ap);
+ va_end(ap);
+ if(rc < 0) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return -1;
+ }
+ sz = krb5_storage_write(sp, s, strlen(s));
+ free(s);
+ return sz;
+}
+
+static krb5_error_code
+append_hex(krb5_context context, krb5_storage *sp,
+ int always_encode, int lower, krb5_data *data)
+{
+ ssize_t sz;
+ int printable = 1;
+ size_t i;
+ char *p;
+
+ p = data->data;
+ if (!always_encode) {
+ for (i = 0; i < data->length; i++) {
+ if (!isalnum((unsigned char)p[i]) && p[i] != '.'){
+ printable = 0;
+ break;
+ }
+ }
+ }
+ if (printable && !always_encode)
+ return append_string(context, sp, "\"%.*s\"",
+ data->length, data->data);
+ sz = hex_encode(data->data, data->length, &p);
+ if (sz == -1) return sz;
+ if (lower)
+ strlwr(p);
+ sz = append_string(context, sp, "%s", p);
+ free(p);
+ return sz;
+}
+
+static char *
+time2str(time_t t)
+{
+ static char buf[128];
+ strftime(buf, sizeof(buf), "%Y%m%d%H%M%S", gmtime(&t));
+ return buf;
+}
+
+static ssize_t
+append_event(krb5_context context, krb5_storage *sp, Event *ev)
+{
+ krb5_error_code ret;
+ ssize_t sz;
+ char *pr = NULL;
+ if(ev == NULL)
+ return append_string(context, sp, "- ");
+ if (ev->principal != NULL) {
+ ret = krb5_unparse_name(context, ev->principal, &pr);
+ if (ret) return -1; /* krb5_unparse_name() sets error info */
+ }
+ sz = append_string(context, sp, "%s:%s ", time2str(ev->time),
+ pr ? pr : "UNKNOWN");
+ free(pr);
+ return sz;
+}
+
+#define KRB5_KDB_SALTTYPE_NORMAL 0
+#define KRB5_KDB_SALTTYPE_V4 1
+#define KRB5_KDB_SALTTYPE_NOREALM 2
+#define KRB5_KDB_SALTTYPE_ONLYREALM 3
+#define KRB5_KDB_SALTTYPE_SPECIAL 4
+#define KRB5_KDB_SALTTYPE_AFS3 5
+
+static ssize_t
+append_mit_key(krb5_context context, krb5_storage *sp,
+ krb5_const_principal princ,
+ unsigned int kvno, Key *key)
+{
+ krb5_error_code ret;
+ krb5_salt k5salt;
+ ssize_t sz;
+ size_t key_versions = key->salt ? 2 : 1;
+ size_t decrypted_key_length;
+ char buf[2];
+ krb5_data keylenbytes;
+ unsigned int salttype;
+
+ sz = append_string(context, sp, "\t%u\t%u\t%d\t%d\t", key_versions, kvno,
+ key->key.keytype, key->key.keyvalue.length + 2);
+ if (sz == -1) return sz;
+ ret = krb5_enctype_keysize(context, key->key.keytype, &decrypted_key_length);
+ if (ret) return -1; /* XXX we lose the error code */
+ buf[0] = decrypted_key_length & 0xff;
+ buf[1] = (decrypted_key_length & 0xff00) >> 8;
+ keylenbytes.data = buf;
+ keylenbytes.length = sizeof (buf);
+ sz = append_hex(context, sp, 1, 1, &keylenbytes);
+ if (sz == -1) return sz;
+ sz = append_hex(context, sp, 1, 1, &key->key.keyvalue);
+ if (!key->salt)
+ return sz;
+
+ /* Map salt to MIT KDB style */
+ switch (key->salt->type) {
+ case KRB5_PADATA_PW_SALT:
+
+ /*
+ * Compute normal salt and then see whether it matches the stored one
+ */
+ ret = krb5_get_pw_salt(context, princ, &k5salt);
+ if (ret) return -1;
+ if (k5salt.saltvalue.length == key->salt->salt.length &&
+ memcmp(k5salt.saltvalue.data, key->salt->salt.data,
+ k5salt.saltvalue.length) == 0)
+ salttype = KRB5_KDB_SALTTYPE_NORMAL; /* matches */
+ else if (key->salt->salt.length == strlen(princ->realm) &&
+ memcmp(key->salt->salt.data, princ->realm,
+ key->salt->salt.length) == 0)
+ salttype = KRB5_KDB_SALTTYPE_ONLYREALM; /* matches realm */
+ else if (key->salt->salt.length ==
+ k5salt.saltvalue.length - strlen(princ->realm) &&
+ memcmp((char *)k5salt.saltvalue.data + strlen(princ->realm),
+ key->salt->salt.data, key->salt->salt.length) == 0)
+ salttype = KRB5_KDB_SALTTYPE_NOREALM; /* matches w/o realm */
+ else
+ salttype = KRB5_KDB_SALTTYPE_NORMAL; /* hope for best */
+
+ break;
+
+ case KRB5_PADATA_AFS3_SALT:
+ salttype = KRB5_KDB_SALTTYPE_AFS3;
+ break;
+
+ default:
+ return -1;
+ }
+
+ sz = append_string(context, sp, "\t%u\t%u\t", salttype,
+ key->salt->salt.length);
+ if (sz == -1) return sz;
+ return append_hex(context, sp, 1, 1, &key->salt->salt);
+}
+
+static krb5_error_code
+entry2string_int (krb5_context context, krb5_storage *sp, hdb_entry *ent)
+{
+ char *p;
+ size_t i;
+ krb5_error_code ret;
+
+ /* --- principal */
+ ret = krb5_unparse_name(context, ent->principal, &p);
+ if(ret)
+ return ret;
+ append_string(context, sp, "%s ", p);
+ free(p);
+ /* --- kvno */
+ append_string(context, sp, "%d", ent->kvno);
+ /* --- keys */
+ for(i = 0; i < ent->keys.len; i++){
+ /* --- mkvno, keytype */
+ if(ent->keys.val[i].mkvno)
+ append_string(context, sp, ":%d:%d:",
+ *ent->keys.val[i].mkvno,
+ ent->keys.val[i].key.keytype);
+ else
+ append_string(context, sp, "::%d:",
+ ent->keys.val[i].key.keytype);
+ /* --- keydata */
+ append_hex(context, sp, 0, 0, &ent->keys.val[i].key.keyvalue);
+ append_string(context, sp, ":");
+ /* --- salt */
+ if(ent->keys.val[i].salt){
+ append_string(context, sp, "%u/", ent->keys.val[i].salt->type);
+ append_hex(context, sp, 0, 0, &ent->keys.val[i].salt->salt);
+ }else
+ append_string(context, sp, "-");
+ }
+ append_string(context, sp, " ");
+ /* --- created by */
+ append_event(context, sp, &ent->created_by);
+ /* --- modified by */
+ append_event(context, sp, ent->modified_by);
+
+ /* --- valid start */
+ if(ent->valid_start)
+ append_string(context, sp, "%s ", time2str(*ent->valid_start));
+ else
+ append_string(context, sp, "- ");
+
+ /* --- valid end */
+ if(ent->valid_end)
+ append_string(context, sp, "%s ", time2str(*ent->valid_end));
+ else
+ append_string(context, sp, "- ");
+
+ /* --- password ends */
+ if(ent->pw_end)
+ append_string(context, sp, "%s ", time2str(*ent->pw_end));
+ else
+ append_string(context, sp, "- ");
+
+ /* --- max life */
+ if(ent->max_life)
+ append_string(context, sp, "%d ", *ent->max_life);
+ else
+ append_string(context, sp, "- ");
+
+ /* --- max renewable life */
+ if(ent->max_renew)
+ append_string(context, sp, "%d ", *ent->max_renew);
+ else
+ append_string(context, sp, "- ");
+
+ /* --- flags */
+ append_string(context, sp, "%d ", HDBFlags2int(ent->flags));
+
+ /* --- generation number */
+ if(ent->generation) {
+ append_string(context, sp, "%s:%d:%d ", time2str(ent->generation->time),
+ ent->generation->usec,
+ ent->generation->gen);
+ } else
+ append_string(context, sp, "- ");
+
+ /* --- extensions */
+ if(ent->extensions && ent->extensions->len > 0) {
+ for(i = 0; i < ent->extensions->len; i++) {
+ void *d;
+ size_t size, sz = 0;
+
+ ASN1_MALLOC_ENCODE(HDB_extension, d, size,
+ &ent->extensions->val[i], &sz, ret);
+ if (ret) {
+ krb5_clear_error_message(context);
+ return ret;
+ }
+ if(size != sz)
+ krb5_abortx(context, "internal asn.1 encoder error");
+
+ if (hex_encode(d, size, &p) < 0) {
+ free(d);
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ free(d);
+ append_string(context, sp, "%s%s", p,
+ ent->extensions->len - 1 != i ? ":" : "");
+ free(p);
+ }
+ } else
+ append_string(context, sp, "-");
+
+ return 0;
+}
+
+#define KRB5_KDB_DISALLOW_POSTDATED 0x00000001
+#define KRB5_KDB_DISALLOW_FORWARDABLE 0x00000002
+#define KRB5_KDB_DISALLOW_TGT_BASED 0x00000004
+#define KRB5_KDB_DISALLOW_RENEWABLE 0x00000008
+#define KRB5_KDB_DISALLOW_PROXIABLE 0x00000010
+#define KRB5_KDB_DISALLOW_DUP_SKEY 0x00000020
+#define KRB5_KDB_DISALLOW_ALL_TIX 0x00000040
+#define KRB5_KDB_REQUIRES_PRE_AUTH 0x00000080
+#define KRB5_KDB_REQUIRES_HW_AUTH 0x00000100
+#define KRB5_KDB_REQUIRES_PWCHANGE 0x00000200
+#define KRB5_KDB_DISALLOW_SVR 0x00001000
+#define KRB5_KDB_PWCHANGE_SERVICE 0x00002000
+#define KRB5_KDB_SUPPORT_DESMD5 0x00004000
+#define KRB5_KDB_NEW_PRINC 0x00008000
+
+static int
+flags_to_attr(HDBFlags flags)
+{
+ int a = 0;
+
+ if (!flags.postdate)
+ a |= KRB5_KDB_DISALLOW_POSTDATED;
+ if (!flags.forwardable)
+ a |= KRB5_KDB_DISALLOW_FORWARDABLE;
+ if (flags.initial)
+ a |= KRB5_KDB_DISALLOW_TGT_BASED;
+ if (!flags.renewable)
+ a |= KRB5_KDB_DISALLOW_RENEWABLE;
+ if (!flags.proxiable)
+ a |= KRB5_KDB_DISALLOW_PROXIABLE;
+ if (flags.invalid)
+ a |= KRB5_KDB_DISALLOW_ALL_TIX;
+ if (flags.require_preauth)
+ a |= KRB5_KDB_REQUIRES_PRE_AUTH;
+ if (flags.require_hwauth)
+ a |= KRB5_KDB_REQUIRES_HW_AUTH;
+ if (!flags.server)
+ a |= KRB5_KDB_DISALLOW_SVR;
+ if (flags.change_pw)
+ a |= KRB5_KDB_PWCHANGE_SERVICE;
+ return a;
+}
+
+krb5_error_code
+entry2mit_string_int(krb5_context context, krb5_storage *sp, hdb_entry *ent)
+{
+ krb5_error_code ret;
+ ssize_t sz;
+ size_t i, k;
+ size_t num_tl_data = 0;
+ size_t num_key_data = 0;
+ char *p;
+ HDB_Ext_KeySet *hist_keys = NULL;
+ HDB_extension *extp;
+ time_t last_pw_chg = 0;
+ time_t exp = 0;
+ time_t pwexp = 0;
+ unsigned int max_life = 0;
+ unsigned int max_renew = 0;
+
+ if (ent->modified_by)
+ num_tl_data++;
+
+ ret = hdb_entry_get_pw_change_time(ent, &last_pw_chg);
+ if (ret) return ret;
+ if (last_pw_chg)
+ num_tl_data++;
+
+ extp = hdb_find_extension(ent, choice_HDB_extension_data_hist_keys);
+ if (extp)
+ hist_keys = &extp->data.u.hist_keys;
+
+ for (i = 0; i < ent->keys.len;i++) {
+ if (ent->keys.val[i].key.keytype == ETYPE_DES_CBC_MD4 ||
+ ent->keys.val[i].key.keytype == ETYPE_DES_CBC_MD5)
+ continue;
+ num_key_data++;
+ }
+ if (hist_keys) {
+ for (i = 0; i < hist_keys->len; i++) {
+ /*
+ * MIT uses the highest kvno as the current kvno instead of
+ * tracking kvno separately, so we can't dump keysets with kvno
+ * higher than the entry's kvno.
+ */
+ if (hist_keys->val[i].kvno >= ent->kvno)
+ continue;
+ for (k = 0; k < hist_keys->val[i].keys.len; k++) {
+ if (ent->keys.val[k].key.keytype == ETYPE_DES_CBC_MD4 ||
+ ent->keys.val[k].key.keytype == ETYPE_DES_CBC_MD5)
+ continue;
+ num_key_data++;
+ }
+ }
+ }
+
+ ret = krb5_unparse_name(context, ent->principal, &p);
+ if (ret) return ret;
+ sz = append_string(context, sp, "princ\t38\t%u\t%u\t%u\t0\t%s\t%d",
+ strlen(p), num_tl_data, num_key_data, p,
+ flags_to_attr(ent->flags));
+ free(p);
+ if (sz == -1) return ENOMEM;
+
+ if (ent->max_life)
+ max_life = *ent->max_life;
+ if (ent->max_renew)
+ max_renew = *ent->max_renew;
+ if (ent->valid_end)
+ exp = *ent->valid_end;
+ if (ent->pw_end)
+ pwexp = *ent->pw_end;
+
+ sz = append_string(context, sp, "\t%u\t%u\t%u\t%u\t0\t0\t0",
+ max_life, max_renew, exp, pwexp);
+ if (sz == -1) return ENOMEM;
+
+ /* Dump TL data we know: last pw chg and modified_by */
+#define mit_KRB5_TL_LAST_PWD_CHANGE 1
+#define mit_KRB5_TL_MOD_PRINC 2
+ if (last_pw_chg) {
+ krb5_data d;
+ time_t val;
+ unsigned char *ptr;
+
+ ptr = (unsigned char *)&last_pw_chg;
+ val = ((unsigned long)ptr[3] << 24) | (ptr[2] << 16)
+ | (ptr[1] << 8) | ptr[0];
+ d.data = &val;
+ d.length = sizeof (last_pw_chg);
+ sz = append_string(context, sp, "\t%u\t%u\t",
+ mit_KRB5_TL_LAST_PWD_CHANGE, d.length);
+ if (sz == -1) return ENOMEM;
+ sz = append_hex(context, sp, 1, 1, &d);
+ if (sz == -1) return ENOMEM;
+ }
+ if (ent->modified_by) {
+ krb5_data d;
+ unsigned int val;
+ size_t plen;
+ unsigned char *ptr;
+ char *modby_p;
+
+ ptr = (unsigned char *)&ent->modified_by->time;
+ val = ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24);
+ d.data = &val;
+ d.length = sizeof (ent->modified_by->time);
+ ret = krb5_unparse_name(context, ent->modified_by->principal, &modby_p);
+ if (ret)
+ return ret;
+ plen = strlen(modby_p);
+ sz = append_string(context, sp, "\t%u\t%u\t",
+ mit_KRB5_TL_MOD_PRINC,
+ d.length + plen + 1 /* NULL counted */);
+ if (sz == -1) {
+ free(modby_p);
+ return ENOMEM;
+ }
+ sz = append_hex(context, sp, 1, 1, &d);
+ if (sz == -1) {
+ free(modby_p);
+ return ENOMEM;
+ }
+ d.data = modby_p;
+ d.length = plen + 1;
+ sz = append_hex(context, sp, 1, 1, &d);
+ free(modby_p);
+ if (sz == -1)
+ return ENOMEM;
+ }
+ /*
+ * Dump keys (remembering to not include any with kvno higher than
+ * the entry's because MIT doesn't track entry kvno separately from
+ * the entry's keys -- max kvno is it)
+ */
+ for (i = 0; i < ent->keys.len; i++) {
+ if (ent->keys.val[i].key.keytype == ETYPE_DES_CBC_MD4 ||
+ ent->keys.val[i].key.keytype == ETYPE_DES_CBC_MD5)
+ continue;
+ sz = append_mit_key(context, sp, ent->principal, ent->kvno,
+ &ent->keys.val[i]);
+ if (sz == -1) return ENOMEM;
+ }
+ for (i = 0; hist_keys && i < ent->kvno; i++) {
+ size_t m;
+
+ /* dump historical keys */
+ for (k = 0; k < hist_keys->len; k++) {
+ if (hist_keys->val[k].kvno != ent->kvno - i)
+ continue;
+ for (m = 0; m < hist_keys->val[k].keys.len; m++) {
+ if (ent->keys.val[k].key.keytype == ETYPE_DES_CBC_MD4 ||
+ ent->keys.val[k].key.keytype == ETYPE_DES_CBC_MD5)
+ continue;
+ sz = append_mit_key(context, sp, ent->principal,
+ hist_keys->val[k].kvno,
+ &hist_keys->val[k].keys.val[m]);
+ if (sz == -1) return ENOMEM;
+ }
+ }
+ }
+ sz = append_string(context, sp, "\t-1;"); /* "extra data" */
+ if (sz == -1) return ENOMEM;
+ return 0;
+}
+
+krb5_error_code
+hdb_entry2string(krb5_context context, hdb_entry *ent, char **str)
+{
+ krb5_error_code ret;
+ krb5_data data;
+ krb5_storage *sp;
+
+ sp = krb5_storage_emem();
+ if (sp == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ ret = entry2string_int(context, sp, ent);
+ if (ret) {
+ krb5_storage_free(sp);
+ return ret;
+ }
+
+ krb5_storage_write(sp, "\0", 1);
+ krb5_storage_to_data(sp, &data);
+ krb5_storage_free(sp);
+ *str = data.data;
+ return 0;
+}
+
+/* print a hdb_entry to (FILE*)data; suitable for hdb_foreach */
+
+krb5_error_code
+hdb_print_entry(krb5_context context, HDB *db, hdb_entry *entry,
+ void *data)
+{
+ struct hdb_print_entry_arg *parg = data;
+ krb5_error_code ret;
+ krb5_storage *sp;
+
+ fflush(parg->out);
+ sp = krb5_storage_from_fd(fileno(parg->out));
+ if (sp == NULL) {
+ krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
+ return ENOMEM;
+ }
+
+ switch (parg->fmt) {
+ case HDB_DUMP_HEIMDAL:
+ ret = entry2string_int(context, sp, entry);
+ break;
+ case HDB_DUMP_MIT:
+ ret = entry2mit_string_int(context, sp, entry);
+ break;
+ default:
+ heim_abort("Only two dump formats supported: Heimdal and MIT");
+ }
+ if (ret) {
+ krb5_storage_free(sp);
+ return ret;
+ }
+
+ krb5_storage_write(sp, "\n", 1);
+ krb5_storage_free(sp);
+ return 0;
+}
diff --git a/third_party/heimdal/lib/hdb/test_concurrency.c b/third_party/heimdal/lib/hdb/test_concurrency.c
new file mode 100644
index 0000000..35c01f5
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/test_concurrency.c
@@ -0,0 +1,506 @@
+/*
+ * Copyright (c) 2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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.
+ */
+
+/*
+ * This test tries to test reader/writer concurrency for the SQLite3 and LMDB
+ * HDB backends. We're hoping to find that one thread or process can dump the
+ * HDB while another writes -- this way backups and ipropd-master need not
+ * block write transactions when dumping a huge HDB.
+ *
+ * It has two modes: threaded, and forked.
+ *
+ * Apparently, neither LMDB nor SQLite3 give us the desired level of
+ * concurrency in threaded mode, with this test not making progress. This is
+ * surprising, at least for SQLite3, which is supposed to support N readers, 1
+ * writer and be thread-safe. LMDB also is supposed to support N readers, 1
+ * writers, but perhaps not all in one process?
+ */
+
+#include "hdb_locl.h"
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <pthread.h>
+#include <getarg.h>
+
+struct tsync {
+ pthread_mutex_t lock;
+ pthread_cond_t rcv;
+ pthread_cond_t wcv;
+ const char *hdb_name;
+ const char *fname;
+ volatile int writer_go;
+ volatile int reader_go;
+ int writer_go_pipe[2];
+ int reader_go_pipe[2];
+};
+
+static void *
+threaded_reader(void *d)
+{
+ krb5_error_code ret;
+ krb5_context context;
+ struct tsync *s = d;
+ hdb_entry entr;
+ HDB *dbr = NULL;
+
+ printf("Reader thread opening HDB\n");
+
+ if ((krb5_init_context(&context)))
+ errx(1, "krb5_init_context failed");
+
+ printf("Reader thread waiting for writer to create the HDB\n");
+ (void) pthread_mutex_lock(&s->lock);
+ s->writer_go = 1;
+ (void) pthread_cond_signal(&s->wcv);
+ while (!s->reader_go)
+ (void) pthread_cond_wait(&s->rcv, &s->lock);
+ s->reader_go = 0;
+ (void) pthread_mutex_unlock(&s->lock);
+
+ /* Open a new HDB handle to read */
+ if ((ret = hdb_create(context, &dbr, s->hdb_name))) {
+ //(void) unlink(s->fname);
+ krb5_err(context, 1, ret, "Could not get a handle for HDB %s (read)",
+ s->hdb_name);
+ }
+ if ((ret = dbr->hdb_open(context, dbr, O_RDONLY, 0))) {
+ //(void) unlink(s->fname);
+ krb5_err(context, 1, ret, "Could not open HDB %s", s->hdb_name);
+ }
+ if ((ret = dbr->hdb_firstkey(context, dbr, 0, &entr))) {
+ //(void) unlink(s->fname);
+ krb5_err(context, 1, ret, "Could not iterate HDB %s", s->hdb_name);
+ }
+ free_HDB_entry(&entr);
+
+ /* Tell the writer to go ahead and write */
+ printf("Reader thread iterated one entry; telling writer to write more\n");
+ s->writer_go = 1;
+ (void) pthread_mutex_lock(&s->lock);
+ (void) pthread_cond_signal(&s->wcv);
+
+ /* Wait for the writer to have written one more entry to the HDB */
+ printf("Reader thread waiting for writer\n");
+ while (!s->reader_go)
+ (void) pthread_cond_wait(&s->rcv, &s->lock);
+ s->reader_go = 0;
+ (void) pthread_mutex_unlock(&s->lock);
+
+ /* Iterate the rest */
+ printf("Reader thread iterating another entry\n");
+ if ((ret = dbr->hdb_nextkey(context, dbr, 0, &entr))) {
+ //(void) unlink(s->fname);
+ krb5_err(context, 1, ret,
+ "Could not iterate while writing to HDB %s", s->hdb_name);
+ }
+ printf("Reader thread iterated another entry\n");
+ free_HDB_entry(&entr);
+ if ((ret = dbr->hdb_nextkey(context, dbr, 0, &entr)) == 0) {
+ //(void) unlink(s->fname);
+ krb5_warn(context, ret,
+ "HDB %s sees writes committed since starting iteration",
+ s->hdb_name);
+ } else if (ret != HDB_ERR_NOENTRY) {
+ //(void) unlink(s->fname);
+ krb5_err(context, 1, ret,
+ "Could not iterate while writing to HDB %s (2)", s->hdb_name);
+ }
+
+ /* Tell the writer we're done */
+ printf("Reader thread telling writer to go\n");
+ s->writer_go = 1;
+ (void) pthread_cond_signal(&s->wcv);
+ (void) pthread_mutex_unlock(&s->lock);
+
+ dbr->hdb_close(context, dbr);
+ dbr->hdb_destroy(context, dbr);
+ krb5_free_context(context);
+ printf("Reader thread exiting\n");
+ return 0;
+}
+
+static void
+forked_reader(struct tsync *s)
+{
+ krb5_error_code ret;
+ krb5_context context;
+ hdb_entry entr;
+ ssize_t bytes;
+ char b[1];
+ HDB *dbr = NULL;
+
+ printf("Reader process opening HDB\n");
+
+ (void) close(s->writer_go_pipe[0]);
+ (void) close(s->reader_go_pipe[1]);
+ s->writer_go_pipe[0] = -1;
+ s->reader_go_pipe[1] = -1;
+ if ((krb5_init_context(&context)))
+ errx(1, "krb5_init_context failed");
+
+ printf("Reader process waiting for writer\n");
+ while ((bytes = read(s->reader_go_pipe[0], b, sizeof(b))) == -1 &&
+ errno == EINTR)
+ ;
+ if (bytes == -1)
+ err(1, "Could not read from reader-go pipe (error)");
+
+ /* Open a new HDB handle to read */
+ if ((ret = hdb_create(context, &dbr, s->hdb_name))) {
+ //(void) unlink(s->fname);
+ krb5_err(context, 1, ret, "Could not get a handle for HDB %s (read)",
+ s->hdb_name);
+ }
+ if ((ret = dbr->hdb_open(context, dbr, O_RDONLY, 0))) {
+ //(void) unlink(s->fname);
+ krb5_err(context, 1, ret, "Could not open HDB %s", s->hdb_name);
+ }
+ if ((ret = dbr->hdb_firstkey(context, dbr, 0, &entr))) {
+ //(void) unlink(s->fname);
+ krb5_err(context, 1, ret, "Could not iterate HDB %s", s->hdb_name);
+ }
+ printf("Reader process iterated one entry\n");
+ free_HDB_entry(&entr);
+
+ /* Tell the writer to go ahead and write */
+ printf("Reader process iterated one entry; telling writer to write more\n");
+ while ((bytes = write(s->writer_go_pipe[1], "", sizeof(""))) == -1 &&
+ errno == EINTR)
+ ;
+ if (bytes == -1)
+ err(1, "Could not write to writer-go pipe (error)");
+
+
+ /* Wait for the writer to have written one more entry to the HDB */
+ printf("Reader process waiting for writer\n");
+ while ((bytes = read(s->reader_go_pipe[0], b, sizeof(b))) == -1 &&
+ errno == EINTR)
+ ;
+ if (bytes == -1)
+ err(1, "Could not read from reader-go pipe (error)");
+ if (bytes == 0)
+ errx(1, "Could not read from reader-go pipe (EOF)");
+
+ /* Iterate the rest */
+ if ((ret = dbr->hdb_nextkey(context, dbr, 0, &entr))) {
+ //(void) unlink(s->fname);
+ krb5_err(context, 1, ret,
+ "Could not iterate while writing to HDB %s", s->hdb_name);
+ }
+ free_HDB_entry(&entr);
+ printf("Reader process iterated another entry\n");
+ if ((ret = dbr->hdb_nextkey(context, dbr, 0, &entr)) == 0) {
+ //(void) unlink(s->fname);
+ krb5_warn(context, ret,
+ "HDB %s sees writes committed since starting iteration (%s)",
+ s->hdb_name, entr.principal->name.name_string.val[0]);
+ } else if (ret != HDB_ERR_NOENTRY) {
+ //(void) unlink(s->fname);
+ krb5_err(context, 1, ret,
+ "Could not iterate while writing to HDB %s (2)", s->hdb_name);
+ }
+
+ /* Tell the writer we're done */
+ printf("Reader process done; telling writer to go\n");
+ while ((bytes = write(s->writer_go_pipe[1], "", sizeof(""))) == -1 &&
+ errno == EINTR)
+ ;
+ if (bytes == -1)
+ err(1, "Could not write to writer-go pipe (error)");
+
+ dbr->hdb_close(context, dbr);
+ dbr->hdb_destroy(context, dbr);
+ krb5_free_context(context);
+ (void) close(s->writer_go_pipe[1]);
+ (void) close(s->reader_go_pipe[0]);
+ printf("Reader process exiting\n");
+ _exit(0);
+}
+
+static krb5_error_code
+make_entry(krb5_context context, hdb_entry *entry, const char *name)
+{
+ krb5_error_code ret;
+
+ memset(entry, 0, sizeof(*entry));
+ entry->kvno = 2;
+ entry->keys.len = 0;
+ entry->keys.val = NULL;
+ entry->created_by.time = time(NULL);
+ entry->modified_by = NULL;
+ entry->valid_start = NULL;
+ entry->valid_end = NULL;
+ entry->max_life = NULL;
+ entry->max_renew = NULL;
+ entry->etypes = NULL;
+ entry->generation = NULL;
+ entry->extensions = NULL;
+ if ((ret = krb5_make_principal(context, &entry->principal,
+ "TEST.H5L.SE", name, NULL)))
+ return ret;
+ if ((ret = krb5_make_principal(context, &entry->created_by.principal,
+ "TEST.H5L.SE", "tester", NULL)))
+ return ret;
+ return 0;
+}
+
+static void
+readers_turn(struct tsync *s, pid_t child, int threaded)
+{
+ if (threaded) {
+ (void) pthread_mutex_lock(&s->lock);
+ s->reader_go = 1;
+ (void) pthread_cond_signal(&s->rcv);
+
+ while (!s->writer_go)
+ (void) pthread_cond_wait(&s->wcv, &s->lock);
+ s->writer_go = 0;
+ (void) pthread_mutex_unlock(&s->lock);
+ } else {
+ ssize_t bytes;
+ char b[1];
+
+ while ((bytes = write(s->reader_go_pipe[1], "", sizeof(""))) == -1 &&
+ errno == EINTR)
+ ;
+ if (bytes == -1) {
+ kill(child, SIGKILL);
+ err(1, "Could not write to reader-go pipe (error)");
+ }
+ if (bytes == 0) {
+ kill(child, SIGKILL);
+ err(1, "Could not write to reader-go pipe (EOF?)");
+ }
+
+ while ((bytes = read(s->writer_go_pipe[0], b, sizeof(b))) == -1 &&
+ errno == EINTR)
+ ;
+ if (bytes == -1) {
+ kill(child, SIGKILL);
+ err(1, "Could not read from writer-go pipe");
+ }
+ if (bytes == 0) {
+ kill(child, SIGKILL);
+ errx(1, "Child errored");
+ }
+ s->writer_go = 0;
+ }
+}
+
+static void
+test_hdb_concurrency(char *name, const char *ext, int threaded)
+{
+ krb5_error_code ret;
+ krb5_context context;
+ char *fname = strchr(name, ':') + 1;
+ char *fname_ext = NULL;
+ pthread_t reader_thread;
+ struct tsync ts;
+ hdb_entry entw;
+ pid_t child = getpid();
+ HDB *dbw = NULL;
+ int status;
+ int fd;
+
+ memset(&ts, 0, sizeof(ts));
+ (void) pthread_cond_init(&ts.rcv, NULL);
+ (void) pthread_cond_init(&ts.wcv, NULL);
+ (void) pthread_mutex_init(&ts.lock, NULL);
+
+ if ((krb5_init_context(&context)))
+ errx(1, "krb5_init_context failed");
+
+ /* Use mkstemp() then unlink() to avoid warnings about mktemp(); ugh */
+ if ((fd = mkstemp(fname)) == -1)
+ err(1, "mkstemp(%s)", fname);
+ (void) close(fd);
+ (void) unlink(fname);
+ if (asprintf(&fname_ext, "%s%s", fname, ext ? ext : "") == -1 ||
+ fname_ext == NULL)
+ err(1, "Out of memory");
+ ts.hdb_name = name;
+ ts.fname = fname_ext;
+
+ if (threaded) {
+ printf("Starting reader thread\n");
+ (void) pthread_mutex_lock(&ts.lock);
+ if ((errno = pthread_create(&reader_thread, NULL, threaded_reader, &ts))) {
+ (void) unlink(fname_ext);
+ krb5_err(context, 1, errno, "Could not create a thread to read HDB");
+ }
+
+ /* Wait for reader */
+ while (!ts.writer_go)
+ (void) pthread_cond_wait(&ts.wcv, &ts.lock);
+ (void) pthread_mutex_unlock(&ts.lock);
+ } else {
+ printf("Starting reader process\n");
+ if (pipe(ts.writer_go_pipe) == -1)
+ err(1, "Could not create a pipe");
+ if (pipe(ts.reader_go_pipe) == -1)
+ err(1, "Could not create a pipe");
+ switch ((child = fork())) {
+ case -1: err(1, "Could not fork a child");
+ case 0: forked_reader(&ts); _exit(0);
+ default: break;
+ }
+ (void) close(ts.writer_go_pipe[1]);
+ ts.writer_go_pipe[1] = -1;
+ }
+
+ printf("Writing two entries into HDB\n");
+ if ((ret = hdb_create(context, &dbw, name)))
+ krb5_err(context, 1, ret, "Could not get a handle for HDB %s (write)",
+ name);
+ if ((ret = dbw->hdb_open(context, dbw, O_RDWR | O_CREAT, 0600)))
+ krb5_err(context, 1, ret, "Could not create HDB %s", name);
+
+ /* Add two entries */
+ memset(&entw, 0, sizeof(entw));
+ if ((ret = make_entry(context, &entw, "foo")) ||
+ (ret = dbw->hdb_store(context, dbw, 0, &entw))) {
+ (void) unlink(fname_ext);
+ krb5_err(context, 1, ret,
+ "Could not store entry for \"foo\" in HDB %s", name);
+ }
+ free_HDB_entry(&entw);
+ if ((ret = make_entry(context, &entw, "bar")) ||
+ (ret = dbw->hdb_store(context, dbw, 0, &entw))) {
+ (void) unlink(fname_ext);
+ krb5_err(context, 1, ret,
+ "Could not store entry for \"foo\" in HDB %s", name);
+ }
+ free_HDB_entry(&entw);
+
+ /* Tell the reader to start reading */
+ readers_turn(&ts, child, threaded);
+
+ /* Store one more entry */
+ if ((ret = make_entry(context, &entw, "foobar")) ||
+ (ret = dbw->hdb_store(context, dbw, 0, &entw))) {
+ (void) unlink(fname_ext);
+ krb5_err(context, 1, ret,
+ "Could not store entry for \"foobar\" in HDB %s "
+ "while iterating it", name);
+ }
+ free_HDB_entry(&entw);
+
+ /* Tell the reader to go again */
+ readers_turn(&ts, child, threaded);
+
+ dbw->hdb_close(context, dbw);
+ dbw->hdb_destroy(context, dbw);
+ if (threaded) {
+ (void) pthread_join(reader_thread, NULL);
+ } else {
+ (void) close(ts.writer_go_pipe[1]);
+ (void) close(ts.reader_go_pipe[0]);
+ (void) close(ts.reader_go_pipe[1]);
+ while (wait(&status) == -1 && errno == EINTR)
+ ;
+ (void) close(ts.writer_go_pipe[0]);
+ if (!WIFEXITED(status))
+ errx(1, "Child reader died");
+ if (WEXITSTATUS(status) != 0)
+ errx(1, "Child reader errored");
+ }
+ (void) unlink(fname_ext);
+ krb5_free_context(context);
+}
+
+static int use_fork;
+static int use_threads;
+static int help_flag;
+static int version_flag;
+
+struct getargs args[] = {
+ { "use-fork", 'f', arg_flag, &use_fork, NULL, NULL },
+ { "use-threads", 't', arg_flag, &use_threads, NULL, NULL },
+ { "help", 'h', arg_flag, &help_flag, NULL, NULL },
+ { "version", 0, arg_flag, &version_flag, NULL, NULL }
+};
+
+static int num_args = sizeof(args) / sizeof(args[0]);
+
+int
+main(int argc, char **argv)
+{
+ char stemplate[sizeof("sqlite:testhdb-XXXXXX")];
+#ifdef HAVE_LMDB
+ char ltemplate[sizeof("lmdb:testhdb-XXXXXX")];
+#endif
+ int o = 0;
+
+ setprogname(argv[0]);
+
+ if (getarg(args, num_args, argc, argv, &o))
+ krb5_std_usage(1, args, num_args);
+
+ if (help_flag)
+ krb5_std_usage(0, args, num_args);
+
+ if (version_flag){
+ print_version(NULL);
+ return 0;
+ }
+
+ if (!use_fork && !use_threads)
+ use_threads = use_fork = 1;
+
+#ifdef HAVE_FORK
+ if (use_fork) {
+ printf("Testing SQLite3 HDB backend (multi-process)\n");
+ memcpy(stemplate, "sqlite:testhdb-XXXXXX", sizeof("sqlite:testhdb-XXXXXX"));
+ test_hdb_concurrency(stemplate, "", 0);
+
+#ifdef HAVE_LMDB
+ printf("Testing LMDB HDB backend (multi-process)\n");
+ memcpy(ltemplate, "lmdb:testhdb-XXXXXX", sizeof("lmdb:testhdb-XXXXXX"));
+ test_hdb_concurrency(ltemplate, ".lmdb", 0);
+#endif
+ }
+#endif
+
+ if (use_threads) {
+ printf("Testing SQLite3 HDB backend (multi-process)\n");
+ memcpy(stemplate, "sqlite:testhdb-XXXXXX", sizeof("sqlite:testhdb-XXXXXX"));
+ test_hdb_concurrency(stemplate, "", 1);
+
+#ifdef HAVE_LMDB
+ printf("Testing LMDB HDB backend (multi-process)\n");
+ memcpy(ltemplate, "lmdb:testhdb-XXXXXX", sizeof("lmdb:testhdb-XXXXXX"));
+ test_hdb_concurrency(ltemplate, ".lmdb", 1);
+#endif
+ }
+ return 0;
+}
diff --git a/third_party/heimdal/lib/hdb/test_dbinfo.c b/third_party/heimdal/lib/hdb/test_dbinfo.c
new file mode 100644
index 0000000..195fd41
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/test_dbinfo.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "hdb_locl.h"
+#include <getarg.h>
+
+static int help_flag;
+static int version_flag;
+
+struct getargs args[] = {
+ { "help", 'h', arg_flag, &help_flag, NULL, NULL },
+ { "version", 0, arg_flag, &version_flag, NULL, NULL }
+};
+
+static int num_args = sizeof(args) / sizeof(args[0]);
+
+/*
+ * Prove that HDB_EntryOrAlias being a CHOICE of hdb_entry or hdb_entry_alias
+ * adds nothing to the encoding of those types.
+ */
+static
+void
+check_HDB_EntryOrAlias(krb5_context context)
+{
+ HDB_EntryOrAlias eoa;
+ hdb_entry entry;
+ hdb_entry_alias alias;
+ krb5_data v;
+ size_t len;
+ int ret;
+
+ memset(&entry, 0, sizeof(entry));
+ memset(&alias, 0, sizeof(alias));
+ memset(&eoa, 0, sizeof(eoa));
+ krb5_data_zero(&v);
+
+ ret = krb5_make_principal(context, &alias.principal, "KTH.SE", "foo",
+ NULL);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_make_principal");
+ ASN1_MALLOC_ENCODE(HDB_entry_alias, v.data, v.length, &alias, &len, ret);
+ if (ret)
+ krb5_err(context, 1, ret, "encode_HDB_EntryOrAlias");
+ if (v.length != len)
+ abort();
+ ret = decode_HDB_EntryOrAlias(v.data, v.length, &eoa, &len);
+ if (ret)
+ krb5_err(context, 1, ret, "decode_HDB_EntryOrAlias");
+ if (v.length != len)
+ abort();
+ free_HDB_EntryOrAlias(&eoa);
+ free_HDB_entry_alias(&alias);
+ krb5_data_free(&v);
+
+ ret = krb5_make_principal(context, &entry.principal, "KTH.SE", "foo",
+ NULL);
+ if (ret)
+ krb5_err(context, 1, ret, "krb5_make_principal");
+ entry.kvno = 5;
+ entry.flags.initial = 1;
+ ASN1_MALLOC_ENCODE(HDB_entry, v.data, v.length, &entry, &len, ret);
+ if (ret)
+ krb5_err(context, 1, ret, "encode_HDB_EntryOrAlias");
+ if (v.length != len)
+ abort();
+ ret = decode_HDB_EntryOrAlias(v.data, v.length, &eoa, &len);
+ if (ret)
+ krb5_err(context, 1, ret, "decode_HDB_EntryOrAlias");
+ if (v.length != len)
+ abort();
+ free_HDB_EntryOrAlias(&eoa);
+ free_HDB_entry(&entry);
+ krb5_data_free(&v);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct hdb_dbinfo *info, *d;
+ krb5_context context;
+ int ret, o = 0;
+
+ setprogname(argv[0]);
+
+ if(getarg(args, num_args, argc, argv, &o))
+ krb5_std_usage(1, args, num_args);
+
+ if(help_flag)
+ krb5_std_usage(0, args, num_args);
+
+ if(version_flag){
+ print_version(NULL);
+ exit(0);
+ }
+
+ ret = krb5_init_context(&context);
+ if (ret)
+ errx (1, "krb5_init_context failed: %d", ret);
+
+ check_HDB_EntryOrAlias(context);
+
+ ret = hdb_get_dbinfo(context, &info);
+ if (ret)
+ krb5_err(context, 1, ret, "hdb_get_dbinfo");
+
+ d = NULL;
+ while ((d = hdb_dbinfo_get_next(info, d)) != NULL) {
+ const char *s;
+ s = hdb_dbinfo_get_label(context, d);
+ printf("label: %s\n", s ? s : "no label");
+ s = hdb_dbinfo_get_realm(context, d);
+ printf("\trealm: %s\n", s ? s : "no realm");
+ s = hdb_dbinfo_get_dbname(context, d);
+ printf("\tdbname: %s\n", s ? s : "no dbname");
+ s = hdb_dbinfo_get_mkey_file(context, d);
+ printf("\tmkey_file: %s\n", s ? s : "no mkey file");
+ s = hdb_dbinfo_get_acl_file(context, d);
+ printf("\tacl_file: %s\n", s ? s : "no acl file");
+ }
+
+ hdb_free_dbinfo(context, &info);
+
+ krb5_free_context(context);
+
+ return 0;
+}
diff --git a/third_party/heimdal/lib/hdb/test_hdbkeys.c b/third_party/heimdal/lib/hdb/test_hdbkeys.c
new file mode 100644
index 0000000..d6bc31d
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/test_hdbkeys.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "hdb_locl.h"
+#include <getarg.h>
+#include <base64.h>
+
+static int help_flag;
+static int version_flag;
+static int kvno_integer = 1;
+
+struct getargs args[] = {
+ { "kvno", 'd', arg_integer, &kvno_integer, NULL, NULL },
+ { "help", 'h', arg_flag, &help_flag, NULL, NULL },
+ { "version", 0, arg_flag, &version_flag, NULL, NULL }
+};
+
+static int num_args = sizeof(args) / sizeof(args[0]);
+
+int
+main(int argc, char **argv)
+{
+ krb5_principal principal;
+ krb5_context context;
+ char *principal_str, *password_str, *str;
+ int ret, o = 0;
+ hdb_keyset keyset;
+ size_t length, len;
+ void *data;
+
+ setprogname(argv[0]);
+
+ if(getarg(args, num_args, argc, argv, &o))
+ krb5_std_usage(1, args, num_args);
+
+ if(help_flag)
+ krb5_std_usage(0, args, num_args);
+
+ if(version_flag){
+ print_version(NULL);
+ exit(0);
+ }
+
+ ret = krb5_init_context(&context);
+ if (ret)
+ errx (1, "krb5_init_context failed: %d", ret);
+
+ if (argc != 3)
+ errx(1, "username and password missing");
+
+ principal_str = argv[1];
+ password_str = argv[2];
+
+ ret = krb5_parse_name (context, principal_str, &principal);
+ if (ret)
+ krb5_err (context, 1, ret, "krb5_parse_name %s", principal_str);
+
+ memset(&keyset, 0, sizeof(keyset));
+
+ keyset.kvno = kvno_integer;
+ keyset.set_time = malloc(sizeof (*keyset.set_time));
+ if (keyset.set_time == NULL)
+ errx(1, "couldn't allocate set_time field of keyset");
+ *keyset.set_time = time(NULL);
+
+ ret = hdb_generate_key_set_password(context, principal, password_str,
+ &keyset.keys.val, &len);
+ if (ret)
+ krb5_err(context, 1, ret, "hdb_generate_key_set_password");
+ keyset.keys.len = len;
+
+ if (keyset.keys.len == 0)
+ krb5_errx (context, 1, "hdb_generate_key_set_password length 0");
+
+ krb5_free_principal (context, principal);
+
+ ASN1_MALLOC_ENCODE(HDB_keyset, data, length, &keyset, &len, ret);
+ if (ret)
+ krb5_errx(context, 1, "encode keyset");
+ if (len != length)
+ krb5_abortx(context, "foo");
+
+ krb5_free_context(context);
+
+ ret = rk_base64_encode(data, length, &str);
+ if (ret < 0)
+ errx(1, "base64_encode");
+
+ printf("keyset: %s\n", str);
+
+ free(data);
+
+ return 0;
+}
diff --git a/third_party/heimdal/lib/hdb/test_mkey.c b/third_party/heimdal/lib/hdb/test_mkey.c
new file mode 100644
index 0000000..97399c6
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/test_mkey.c
@@ -0,0 +1,55 @@
+
+#include "hdb_locl.h"
+#include <getarg.h>
+#include <base64.h>
+
+static char *mkey_file;
+static int help_flag;
+static int version_flag;
+
+struct getargs args[] = {
+ { "mkey-file", 0, arg_string, &mkey_file, NULL, NULL },
+ { "help", 'h', arg_flag, &help_flag, NULL, NULL },
+ { "version", 0, arg_flag, &version_flag, NULL, NULL }
+};
+
+static int num_args = sizeof(args) / sizeof(args[0]);
+
+int
+main(int argc, char **argv)
+{
+ krb5_context context;
+ int ret, o = 0;
+
+ setprogname(argv[0]);
+
+ if(getarg(args, num_args, argc, argv, &o))
+ krb5_std_usage(1, args, num_args);
+
+ if(help_flag)
+ krb5_std_usage(0, args, num_args);
+
+ if(version_flag){
+ print_version(NULL);
+ exit(0);
+ }
+
+ ret = krb5_init_context(&context);
+ if (ret)
+ errx(1, "krb5_init_context failed: %d", ret);
+
+ if (mkey_file) {
+ hdb_master_key mkey;
+
+ ret = hdb_read_master_key(context, mkey_file, &mkey);
+ if (ret)
+ krb5_err(context, 1, ret, "failed to read master key %s", mkey_file);
+
+ hdb_free_master_key(context, mkey);
+ } else
+ krb5_errx(context, 1, "no command option given");
+
+ krb5_free_context(context);
+
+ return 0;
+}
diff --git a/third_party/heimdal/lib/hdb/test_namespace.c b/third_party/heimdal/lib/hdb/test_namespace.c
new file mode 100644
index 0000000..f9b4cdb
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/test_namespace.c
@@ -0,0 +1,941 @@
+/*
+ * Copyright (c) 2020 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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.
+ */
+
+/*
+ * This program implements an ephemeral, memory-based HDB backend, stores into
+ * it just one HDB entry -one for a namespace- then checks that virtual
+ * principals are returned below that namespace by hdb_fetch_kvno(), and that
+ * the logic for automatic key rotation of virtual principals is correct.
+ */
+
+#include "hdb_locl.h"
+#include <hex.h>
+
+static KeyRotation krs[2];
+static const char *base_pw[2] = { "Testing123...", "Tested123..." };
+
+typedef struct {
+ HDB hdb; /* generic members */
+ /*
+ * Make this dict a global, add a mutex lock around it, and a .finit and/or
+ * atexit() handler to free it, and we'd have a first-class MEMORY HDB.
+ *
+ * What would a first-class MEMORY HDB be good for though, besides testing?
+ *
+ * However, we could move this dict into `HDB' and then have _hdb_store()
+ * and friends support it as a cache for frequently-used & seldom-changing
+ * entries, such as: K/M, namespaces, and krbtgt principals. That would
+ * speed up lookups, especially for backends with poor reader-writer
+ * concurrency (DB, LMDB) and LDAP. Such entries could be cached for a
+ * minute or three at a time.
+ */
+ heim_dict_t dict;
+} TEST_HDB;
+
+struct hdb_called {
+ int create;
+ int init;
+ int fini;
+};
+
+static krb5_error_code
+TDB_close(krb5_context context, HDB *db)
+{
+ return 0;
+}
+
+static krb5_error_code
+TDB_destroy(krb5_context context, HDB *db)
+{
+ TEST_HDB *tdb = (void *)db;
+
+ heim_release(tdb->dict);
+ free(tdb->hdb.hdb_name);
+ free(tdb);
+ return 0;
+}
+
+static krb5_error_code
+TDB_set_sync(krb5_context context, HDB *db, int on)
+{
+ return 0;
+}
+
+static krb5_error_code
+TDB_lock(krb5_context context, HDB *db, int operation)
+{
+
+ return 0;
+}
+
+static krb5_error_code
+TDB_unlock(krb5_context context, HDB *db)
+{
+
+ return 0;
+}
+
+static krb5_error_code
+TDB_firstkey(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
+{
+ /* XXX Implement */
+ /* Tricky thing: heim_dict_iterate_f() is inconvenient here */
+ /* We need this to check that virtual principals aren't created */
+ return 0;
+}
+
+static krb5_error_code
+TDB_nextkey(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
+{
+ /* XXX Implement */
+ /* Tricky thing: heim_dict_iterate_f() is inconvenient here */
+ /* We need this to check that virtual principals aren't created */
+ return 0;
+}
+
+static krb5_error_code
+TDB_rename(krb5_context context, HDB *db, const char *new_name)
+{
+ return EEXIST;
+}
+
+static krb5_error_code
+TDB__get(krb5_context context, HDB *db, krb5_data key, krb5_data *reply)
+{
+ krb5_error_code ret = 0;
+ TEST_HDB *tdb = (void *)db;
+ heim_object_t k, v = NULL;
+
+ if ((k = heim_data_create(key.data, key.length)) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0 && (v = heim_dict_get_value(tdb->dict, k)) == NULL)
+ ret = HDB_ERR_NOENTRY;
+ if (ret == 0)
+ ret = krb5_data_copy(reply, heim_data_get_ptr(v), heim_data_get_length(v));
+ heim_release(k);
+ return ret;
+}
+
+static krb5_error_code
+TDB__put(krb5_context context, HDB *db, int rplc, krb5_data kd, krb5_data vd)
+{
+ krb5_error_code ret = 0;
+ TEST_HDB *tdb = (void *)db;
+ heim_object_t k = NULL;
+ heim_object_t v = NULL;
+
+ if ((k = heim_data_create(kd.data, kd.length)) == NULL ||
+ (v = heim_data_create(vd.data, vd.length)) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0 && !rplc && heim_dict_get_value(tdb->dict, k) != NULL)
+ ret = HDB_ERR_EXISTS;
+ if (ret == 0 && heim_dict_set_value(tdb->dict, k, v))
+ ret = krb5_enomem(context);
+ heim_release(k);
+ heim_release(v);
+ return ret;
+}
+
+static krb5_error_code
+TDB__del(krb5_context context, HDB *db, krb5_data key)
+{
+ krb5_error_code ret = 0;
+ TEST_HDB *tdb = (void *)db;
+ heim_object_t k;
+
+ if ((k = heim_data_create(key.data, key.length)) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0 && heim_dict_get_value(tdb->dict, k) == NULL)
+ ret = HDB_ERR_NOENTRY;
+ if (ret == 0)
+ heim_dict_delete_key(tdb->dict, k);
+ heim_release(k);
+ return ret;
+}
+
+static krb5_error_code
+TDB_open(krb5_context context, HDB *db, int flags, mode_t mode)
+{
+ return 0;
+}
+
+static krb5_error_code
+hdb_test_create(krb5_context context, struct HDB **db, const char *arg)
+{
+ TEST_HDB *tdb;
+
+ if ((tdb = calloc(1, sizeof(tdb[0]))) == NULL ||
+ (tdb->hdb.hdb_name = strdup(arg)) == NULL ||
+ (tdb->dict = heim_dict_create(10)) == NULL) {
+ if (tdb)
+ free(tdb->hdb.hdb_name);
+ free(tdb);
+ return krb5_enomem(context);
+ }
+
+ tdb->hdb.hdb_db = NULL;
+ tdb->hdb.hdb_master_key_set = 0;
+ tdb->hdb.hdb_openp = 0;
+ tdb->hdb.hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL;
+ tdb->hdb.hdb_open = TDB_open;
+ tdb->hdb.hdb_close = TDB_close;
+ tdb->hdb.hdb_fetch_kvno = _hdb_fetch_kvno;
+ tdb->hdb.hdb_store = _hdb_store;
+ tdb->hdb.hdb_remove = _hdb_remove;
+ tdb->hdb.hdb_firstkey = TDB_firstkey;
+ tdb->hdb.hdb_nextkey= TDB_nextkey;
+ tdb->hdb.hdb_lock = TDB_lock;
+ tdb->hdb.hdb_unlock = TDB_unlock;
+ tdb->hdb.hdb_rename = TDB_rename;
+ tdb->hdb.hdb__get = TDB__get;
+ tdb->hdb.hdb__put = TDB__put;
+ tdb->hdb.hdb__del = TDB__del;
+ tdb->hdb.hdb_destroy = TDB_destroy;
+ tdb->hdb.hdb_set_sync = TDB_set_sync;
+ *db = &tdb->hdb;
+
+ return 0;
+}
+
+static krb5_error_code
+hdb_test_init(krb5_context context, void **ctx)
+{
+ *ctx = NULL;
+ return 0;
+}
+
+static void hdb_test_fini(void *ctx)
+{
+}
+
+struct hdb_method hdb_test =
+{
+#ifdef WIN32
+ /* Not c99 */
+ HDB_INTERFACE_VERSION,
+ hdb_test_init,
+ hdb_test_fini,
+ 1 /*is_file_based*/, 1 /*can_taste*/,
+ "test",
+ hdb_test_create
+#else
+ .minor_version = HDB_INTERFACE_VERSION,
+ .init = hdb_test_init,
+ .fini = hdb_test_fini,
+ .is_file_based = 1,
+ .can_taste = 1,
+ .prefix = "test",
+ .create = hdb_test_create
+#endif
+};
+
+static krb5_error_code
+make_base_key(krb5_context context,
+ krb5_const_principal p,
+ const char *pw,
+ krb5_keyblock *k)
+{
+ return krb5_string_to_key(context, KRB5_ENCTYPE_AES128_CTS_HMAC_SHA256_128,
+ pw, p, k);
+}
+
+static krb5_error_code
+tderive_key(krb5_context context,
+ const char *p,
+ KeyRotation *kr,
+ int toffset,
+ krb5_keyblock *base,
+ krb5int32 etype,
+ krb5_keyblock *k,
+ uint32_t *kvno,
+ time_t *set_time)
+{
+ krb5_error_code ret = 0;
+ krb5_crypto crypto = NULL;
+ EncryptionKey intermediate;
+ krb5_data pad, out;
+ size_t len;
+ int n;
+
+ n = toffset / kr->period;
+ *set_time = kr->epoch + kr->period * n;
+ *kvno = kr->base_kvno + n;
+
+ out.data = 0;
+ out.length = 0;
+
+ /* Derive intermediate key */
+ pad.data = (void *)(uintptr_t)p;
+ pad.length = strlen(p);
+ ret = krb5_enctype_keysize(context, base->keytype, &len);
+ if (ret == 0)
+ ret = krb5_crypto_init(context, base, 0, &crypto);
+ if (ret == 0)
+ ret = krb5_crypto_prfplus(context, crypto, &pad, len, &out);
+ if (crypto)
+ krb5_crypto_destroy(context, crypto);
+ crypto = NULL;
+ if (ret == 0)
+ ret = krb5_random_to_key(context, etype, out.data, out.length,
+ &intermediate);
+ krb5_data_free(&out);
+
+ /* Derive final key */
+ pad.data = kvno;
+ pad.length = sizeof(*kvno);
+ if (ret == 0)
+ ret = krb5_enctype_keysize(context, etype, &len);
+ if (ret == 0)
+ ret = krb5_crypto_init(context, &intermediate, 0, &crypto);
+ if (ret == 0) {
+ *kvno = htonl(*kvno);
+ ret = krb5_crypto_prfplus(context, crypto, &pad, len, &out);
+ *kvno = ntohl(*kvno);
+ }
+ if (crypto)
+ krb5_crypto_destroy(context, crypto);
+ if (ret == 0)
+ ret = krb5_random_to_key(context, etype, out.data, out.length, k);
+ krb5_data_free(&out);
+
+ free_EncryptionKey(&intermediate);
+ return ret;
+}
+
+/* Create a namespace principal */
+static void
+make_namespace(krb5_context context, HDB *db, const char *name)
+{
+ krb5_error_code ret = 0;
+ hdb_entry e;
+ Key k;
+
+ memset(&k, 0, sizeof(k));
+ k.mkvno = 0;
+ k.salt = 0;
+
+ /* Setup the HDB entry */
+ memset(&e, 0, sizeof(e));
+ e.created_by.time = krs[0].epoch;
+ e.valid_start = e.valid_end = e.pw_end = 0;
+ e.generation = 0;
+ e.flags = int2HDBFlags(0);
+ e.flags.server = e.flags.client = 1;
+ e.flags.virtual = 1;
+
+ /* Setup etypes */
+ if (ret == 0 &&
+ (e.etypes = malloc(sizeof(*e.etypes))) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0)
+ e.etypes->len = 3;
+ if (ret == 0 &&
+ (e.etypes->val = calloc(e.etypes->len,
+ sizeof(e.etypes->val[0]))) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0) {
+ e.etypes->val[0] = KRB5_ENCTYPE_AES128_CTS_HMAC_SHA256_128;
+ e.etypes->val[1] = KRB5_ENCTYPE_AES256_CTS_HMAC_SHA384_192;
+ e.etypes->val[2] = KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1_96;
+ }
+
+ /* Setup max_life and max_renew */
+ if (ret == 0 &&
+ (e.max_life = malloc(sizeof(*e.max_life))) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0 &&
+ (e.max_renew = malloc(sizeof(*e.max_renew))) == NULL)
+ ret = krb5_enomem(context);
+ if (ret == 0)
+ /* Make it long, so we see the clamped max */
+ *e.max_renew = 2 * ((*e.max_life = 15 * 24 * 3600));
+
+ /* Setup principal name and created_by */
+ if (ret == 0)
+ ret = krb5_parse_name(context, name, &e.principal);
+ if (ret == 0)
+ ret = krb5_parse_name(context, "admin@BAR.EXAMPLE",
+ &e.created_by.principal);
+
+ /* Make base keys for first epoch */
+ if (ret == 0)
+ ret = make_base_key(context, e.principal, base_pw[0], &k.key);
+ if (ret == 0)
+ add_Keys(&e.keys, &k);
+ if (ret == 0)
+ ret = hdb_entry_set_pw_change_time(context, &e, krs[0].epoch);
+ free_Key(&k);
+ e.kvno = krs[0].base_key_kvno;
+
+ /* Move them to history */
+ if (ret == 0)
+ ret = hdb_add_current_keys_to_history(context, &e);
+ free_Keys(&e.keys);
+
+ /* Make base keys for second epoch */
+ if (ret == 0)
+ ret = make_base_key(context, e.principal, base_pw[1], &k.key);
+ if (ret == 0)
+ add_Keys(&e.keys, &k);
+ e.kvno = krs[1].base_key_kvno;
+ if (ret == 0)
+ ret = hdb_entry_set_pw_change_time(context, &e, krs[1].epoch);
+
+ /* Add the key rotation metadata */
+ if (ret == 0)
+ ret = hdb_entry_add_key_rotation(context, &e, 0, &krs[0]);
+ if (ret == 0)
+ ret = hdb_entry_add_key_rotation(context, &e, 0, &krs[1]);
+
+ if (ret == 0)
+ ret = db->hdb_store(context, db, 0, &e);
+ if (ret)
+ krb5_err(context, 1, ret, "failed to setup a namespace principal");
+ free_Key(&k);
+ hdb_free_entry(context, db, &e);
+}
+
+#define WK_PREFIX "WELLKNOWN/" HDB_WK_NAMESPACE "/"
+
+static const char *expected[] = {
+ WK_PREFIX "_/bar.example@BAR.EXAMPLE",
+ "HTTP/bar.example@BAR.EXAMPLE",
+ "HTTP/foo.bar.example@BAR.EXAMPLE",
+ "host/foo.bar.example@BAR.EXAMPLE",
+ "HTTP/blah.foo.bar.example@BAR.EXAMPLE",
+};
+static const char *unexpected[] = {
+ WK_PREFIX "_/no.example@BAZ.EXAMPLE",
+ "HTTP/no.example@BAR.EXAMPLE",
+ "HTTP/foo.no.example@BAR.EXAMPLE",
+ "HTTP/blah.foo.no.example@BAR.EXAMPLE",
+};
+
+/*
+ * We'll fetch as many entries as we have principal names in `expected[]', for
+ * as many KeyRotation periods as we have (between 1 and 3), and for up to 5
+ * different time offsets in each period.
+ */
+#define NUM_OFFSETS 5
+static hdb_entry e[
+ (sizeof(expected) / sizeof(expected[0])) *
+ (sizeof(krs) / sizeof(krs[0])) *
+ NUM_OFFSETS
+];
+
+static int
+hist_key_compar(const void *va, const void *vb)
+{
+ const hdb_keyset *a = va;
+ const hdb_keyset *b = vb;
+
+ return a->kvno - b->kvno;
+}
+
+/*
+ * Fetch keys for some decent time in the given kr.
+ *
+ * `kr' is an index into the global `krs[]'.
+ * `t' is a number 0..4 inclusive that identifies a time period relative to the
+ * epoch of `krs[kr]' (see code below).
+ */
+static void
+fetch_entries(krb5_context context,
+ HDB *db,
+ size_t kr,
+ size_t t,
+ int must_fail)
+{
+ krb5_error_code ret = 0;
+ krb5_principal p = NULL;
+ krb5_keyblock base_key, dk;
+ hdb_entry *ep;
+ hdb_entry no;
+ size_t i, b;
+ int toffset = 0;
+
+ memset(&base_key, 0, sizeof(base_key));
+
+ /* Work out offset of first entry in `e[]' */
+ assert(kr < sizeof(krs) / sizeof(krs[0]));
+ assert(t < NUM_OFFSETS);
+ b = (kr * NUM_OFFSETS + t) * (sizeof(expected) / sizeof(expected[0]));
+ assert(b < sizeof(e) / sizeof(e[0]));
+ assert(sizeof(e) / sizeof(e[0]) - b >=
+ (sizeof(expected) / sizeof(expected[0])));
+
+ switch (t) {
+ case 0: toffset = 1; break; /* epoch + 1s */
+ case 1: toffset = 1 + (krs[kr].period >> 1); break; /* epoch + period/2 */
+ case 2: toffset = 1 + (krs[kr].period >> 2); break; /* epoch + period/4 */
+ case 3: toffset = 1 + (krs[kr].period >> 3); break; /* epoch + period/8 */
+ case 4: toffset = 1 - (krs[kr].period >> 3); break; /* epoch - period/8 */
+ }
+
+ for (i = 0; ret == 0 && i < sizeof(expected) / sizeof(expected[0]); i++) {
+ ep = &e[b + i];
+ memset(ep, 0, sizeof(*ep));
+ if (ret == 0)
+ ret = krb5_parse_name(context, expected[i], &p);
+ if (ret == 0 && i == 0) {
+ if (toffset < 0 && kr)
+ ret = make_base_key(context, p, base_pw[kr - 1], &base_key);
+ else
+ ret = make_base_key(context, p, base_pw[kr], &base_key);
+ }
+ if (ret == 0)
+ ret = hdb_fetch_kvno(context, db, p,
+ HDB_F_DECRYPT | HDB_F_ALL_KVNOS,
+ krs[kr].epoch + toffset, 0, 0, ep);
+ if (i && must_fail && ret == 0)
+ krb5_errx(context, 1,
+ "virtual principal that shouldn't exist does");
+ if (kr == 0 && toffset < 0 && ret == HDB_ERR_NOENTRY)
+ continue;
+ if (kr == 0 && toffset < 0) {
+ /*
+ * Virtual principals don't exist before their earliest key
+ * rotation epoch's start time.
+ */
+ if (i == 0) {
+ if (ret)
+ krb5_errx(context, 1,
+ "namespace principal does not exist before its time");
+ } else if (i != 0) {
+ if (ret == 0)
+ krb5_errx(context, 1,
+ "virtual principal exists before its time");
+ if (ret != HDB_ERR_NOENTRY)
+ krb5_errx(context, 1, "wrong error code");
+ ret = 0;
+ }
+ } else {
+ if (ret == 0 &&
+ !krb5_principal_compare(context, p, ep->principal))
+ krb5_errx(context, 1, "wrong principal in fetched entry");
+ }
+
+ {
+ HDB_Ext_KeySet *hist_keys;
+ HDB_extension *ext;
+ ext = hdb_find_extension(ep,
+ choice_HDB_extension_data_hist_keys);
+ if (ext) {
+ /* Sort key history by kvno, why not */
+ hist_keys = &ext->data.u.hist_keys;
+ qsort(hist_keys->val, hist_keys->len,
+ sizeof(hist_keys->val[0]), hist_key_compar);
+ }
+ }
+
+ krb5_free_principal(context, p);
+ }
+ if (ret && must_fail) {
+ free_EncryptionKey(&base_key);
+ return;
+ }
+ if (ret)
+ krb5_err(context, 1, ret, "virtual principal test failed");
+
+ for (i = 0; i < sizeof(unexpected) / sizeof(unexpected[0]); i++) {
+ memset(&no, 0, sizeof(no));
+ if (ret == 0)
+ ret = krb5_parse_name(context, unexpected[i], &p);
+ if (ret == 0)
+ ret = hdb_fetch_kvno(context, db, p, HDB_F_DECRYPT,
+ krs[kr].epoch + toffset, 0, 0, &no);
+ if (ret == 0)
+ krb5_errx(context, 1, "bogus principal exists, wat");
+ krb5_free_principal(context, p);
+ ret = 0;
+ }
+
+ if (kr == 0 && toffset < 0)
+ return;
+
+ /*
+ * XXX
+ *
+ * Add check that derived keys are a) different, b) as expected, using a
+ * set of test vectors or else by computing the expected keys here with
+ * code that's not shared with lib/hdb/common.c.
+ *
+ * Add check that we get expected past and/or future keys, not just current
+ * keys.
+ */
+ for (i = 1; ret == 0 && i < sizeof(expected) / sizeof(expected[0]); i++) {
+ uint32_t kvno;
+ time_t set_time, chg_time;
+
+ ep = &e[b + i];
+ if (toffset > 0) {
+ ret = tderive_key(context, expected[i], &krs[kr], toffset,
+ &base_key, base_key.keytype, &dk, &kvno, &set_time);
+ } else /* XXX */{
+ /* XXX */
+ assert(kr);
+ ret = tderive_key(context, expected[i], &krs[kr - 1],
+ krs[kr].epoch - krs[kr - 1].epoch + toffset,
+ &base_key, base_key.keytype, &dk, &kvno, &set_time);
+ }
+ if (ret)
+ krb5_err(context, 1, ret, "deriving keys for comparison");
+
+ if (kvno != ep->kvno)
+ krb5_errx(context, 1, "kvno mismatch (%u != %u)", kvno, ep->kvno);
+ (void) hdb_entry_get_pw_change_time(ep, &chg_time);
+ if (set_time != chg_time)
+ krb5_errx(context, 1, "key change time mismatch");
+ if (ep->keys.len == 0)
+ krb5_errx(context, 1, "no keys!");
+ if (ep->keys.val[0].key.keytype != dk.keytype)
+ krb5_errx(context, 1, "enctype mismatch!");
+ if (ep->keys.val[0].key.keyvalue.length !=
+ dk.keyvalue.length)
+ krb5_errx(context, 1, "key length mismatch!");
+ if (memcmp(ep->keys.val[0].key.keyvalue.data,
+ dk.keyvalue.data, dk.keyvalue.length) != 0)
+ krb5_errx(context, 1, "key mismatch!");
+ if (memcmp(ep->keys.val[0].key.keyvalue.data,
+ e[b + i - 1].keys.val[0].key.keyvalue.data,
+ dk.keyvalue.length) == 0)
+ krb5_errx(context, 1, "different virtual principals have the same keys!");
+ /* XXX Add check that we have the expected number of history keys */
+ free_EncryptionKey(&dk);
+ }
+ free_EncryptionKey(&base_key);
+}
+
+static void
+check_kvnos(krb5_context context)
+{
+ HDB_Ext_KeySet keysets;
+ size_t i, k, m, p; /* iterator indices */
+
+ keysets.len = 0;
+ keysets.val = 0;
+
+ /* For every principal name */
+ for (i = 0; i < sizeof(expected)/sizeof(expected[0]); i++) {
+ free_HDB_Ext_KeySet(&keysets);
+
+ /* For every entry we've fetched for it */
+ for (k = 0; k < sizeof(e)/sizeof(e[0]); k++) {
+ HDB_Ext_KeySet *hist_keys;
+ HDB_extension *ext;
+ hdb_entry *ep;
+ int match = 0;
+
+ if ((k % NUM_OFFSETS) != i)
+ continue;
+
+ ep = &e[k];
+ if (ep->principal == NULL)
+ continue; /* Didn't fetch this one */
+
+ /*
+ * Check that the current keys for it match what we've seen already
+ * or else add them to `keysets'.
+ */
+ for (m = 0; m < keysets.len; m++) {
+ if (ep->kvno == keysets.val[m].kvno) {
+ /* Check the key is the same */
+ if (ep->keys.val[0].key.keytype !=
+ keysets.val[m].keys.val[0].key.keytype ||
+ ep->keys.val[0].key.keyvalue.length !=
+ keysets.val[m].keys.val[0].key.keyvalue.length ||
+ memcmp(ep->keys.val[0].key.keyvalue.data,
+ keysets.val[m].keys.val[0].key.keyvalue.data,
+ ep->keys.val[0].key.keyvalue.length) != 0)
+ krb5_errx(context, 1,
+ "key mismatch for same princ & kvno");
+ match = 1;
+ }
+ }
+ if (m == keysets.len) {
+ hdb_keyset ks;
+
+ ks.kvno = ep->kvno;
+ ks.keys = ep->keys;
+ ks.set_time = 0;
+ if (add_HDB_Ext_KeySet(&keysets, &ks))
+ krb5_err(context, 1, ENOMEM, "out of memory");
+ match = 1;
+ }
+ if (match)
+ continue;
+
+ /* For all non-current keysets, repeat the above */
+ ext = hdb_find_extension(ep,
+ choice_HDB_extension_data_hist_keys);
+ if (!ext)
+ continue;
+ hist_keys = &ext->data.u.hist_keys;
+ for (p = 0; p < hist_keys->len; p++) {
+ for (m = 0; m < keysets.len; m++) {
+ if (keysets.val[m].kvno == hist_keys->val[p].kvno)
+ if (ep->keys.val[0].key.keytype !=
+ keysets.val[m].keys.val[0].key.keytype ||
+ ep->keys.val[0].key.keyvalue.length !=
+ keysets.val[m].keys.val[0].key.keyvalue.length ||
+ memcmp(ep->keys.val[0].key.keyvalue.data,
+ keysets.val[m].keys.val[0].key.keyvalue.data,
+ ep->keys.val[0].key.keyvalue.length) != 0)
+ krb5_errx(context, 1,
+ "key mismatch for same princ & kvno");
+ }
+ if (m == keysets.len) {
+ hdb_keyset ks;
+ ks.kvno = ep->kvno;
+ ks.keys = ep->keys;
+ ks.set_time = 0;
+ if (add_HDB_Ext_KeySet(&keysets, &ks))
+ krb5_err(context, 1, ENOMEM, "out of memory");
+ }
+ }
+ }
+ }
+ free_HDB_Ext_KeySet(&keysets);
+}
+
+static void
+print_em(krb5_context context)
+{
+ HDB_Ext_KeySet *hist_keys;
+ HDB_extension *ext;
+ size_t i, p;
+
+ for (i = 0; i < sizeof(e)/sizeof(e[0]); i++) {
+ const char *name = expected[i % (sizeof(expected)/sizeof(expected[0]))];
+ char *x;
+
+ if (0 == i % (sizeof(expected)/sizeof(expected[0])))
+ continue;
+ if (e[i].principal == NULL)
+ continue;
+ hex_encode(e[i].keys.val[0].key.keyvalue.data,
+ e[i].keys.val[0].key.keyvalue.length, &x);
+ printf("%s %u %s\n", x, e[i].kvno, name);
+ free(x);
+
+ ext = hdb_find_extension(&e[i], choice_HDB_extension_data_hist_keys);
+ if (!ext)
+ continue;
+ hist_keys = &ext->data.u.hist_keys;
+ for (p = 0; p < hist_keys->len; p++) {
+ hex_encode(hist_keys->val[p].keys.val[0].key.keyvalue.data,
+ hist_keys->val[p].keys.val[0].key.keyvalue.length, &x);
+ printf("%s %u %s\n", x, hist_keys->val[p].kvno, name);
+ free(x);
+ }
+ }
+}
+
+#if 0
+static void
+check_expected_kvnos(krb5_context context)
+{
+ HDB_Ext_KeySet *hist_keys;
+ HDB_extension *ext;
+ size_t i, k, m, p;
+
+ for (i = 0; i < sizeof(expected)/sizeof(expected[0]); i++) {
+ for (k = 0; k < sizeof(krs)/sizeof(krs[0]); k++) {
+ hdb_entry *ep = &e[k * sizeof(expected)/sizeof(expected[0]) + i];
+
+ if (ep->principal == NULL)
+ continue;
+ for (m = 0; m < NUM_OFFSETS; m++) {
+ ext = hdb_find_extension(ep,
+ choice_HDB_extension_data_hist_keys);
+ if (!ext)
+ continue;
+ hist_keys = &ext->data.u.hist_keys;
+ for (p = 0; p < hist_keys->len; p++) {
+ fprintf(stderr, "%s at %lu, %lu: history kvno %u\n",
+ expected[i], k, m, hist_keys->val[p].kvno);
+ }
+ }
+ fprintf(stderr, "%s at %lu: kvno %u\n", expected[i], k,
+ ep->kvno);
+ }
+ }
+}
+#endif
+
+#define SOME_TIME 1596318329
+#define SOME_BASE_KVNO 150
+#define SOME_EPOCH (SOME_TIME - (7 * 24 * 3600) - (SOME_TIME % (7 * 24 * 3600)))
+#define SOME_PERIOD 3600
+
+#define CONF \
+ "[hdb]\n" \
+ "\tenable_virtual_hostbased_princs = true\n" \
+ "\tvirtual_hostbased_princ_mindots = 1\n" \
+ "\tvirtual_hostbased_princ_maxdots = 3\n" \
+
+int
+main(int argc, char **argv)
+{
+ krb5_error_code ret;
+ krb5_context context;
+ size_t i;
+ HDB *db = NULL;
+
+ setprogname(argv[0]);
+ memset(e, 0, sizeof(e));
+ ret = krb5_init_context(&context);
+ if (ret == 0)
+ ret = krb5_set_config(context, CONF);
+ if (ret == 0)
+ ret = krb5_plugin_register(context, PLUGIN_TYPE_DATA, "hdb_test_interface",
+ &hdb_test);
+ if (ret == 0)
+ ret = hdb_create(context, &db, "test:mem");
+ if (ret)
+ krb5_err(context, 1, ret, "failed to setup HDB driver and test");
+
+ assert(db->enable_virtual_hostbased_princs);
+ assert(db->virtual_hostbased_princ_ndots == 1);
+ assert(db->virtual_hostbased_princ_maxdots == 3);
+
+ /* Setup key rotation metadata in a convenient way */
+ /*
+ * FIXME Reorder these two KRs to match how we store them to avoid
+ * confusion. #0 should be future-most, #1 should past-post.
+ */
+ krs[0].flags = krs[1].flags = int2KeyRotationFlags(0);
+ krs[0].epoch = SOME_EPOCH - 20 * 24 * 3600;
+ krs[0].period = SOME_PERIOD >> 1;
+ krs[0].base_kvno = 150;
+ krs[0].base_key_kvno = 1;
+ krs[1].epoch = SOME_TIME;
+ krs[1].period = SOME_PERIOD;
+ krs[1].base_kvno = krs[0].base_kvno + 1 + (krs[1].epoch + (krs[0].period - 1) - krs[0].epoch) / krs[0].period;
+ krs[1].base_key_kvno = 2;
+
+ {
+ HDB_Ext_KeyRotation existing_krs, new_krs;
+ KeyRotation ordered_krs[2];
+
+ ordered_krs[0] = krs[1];
+ ordered_krs[1] = krs[0];
+ existing_krs.len = 0;
+ existing_krs.val = 0;
+ new_krs.len = 1;
+ new_krs.val = &ordered_krs[1];
+ if ((ret = hdb_validate_key_rotations(context, NULL, &new_krs)) ||
+ (ret = hdb_validate_key_rotations(context, &existing_krs,
+ &new_krs)))
+ krb5_err(context, 1, ret, "Valid KeyRotation thought invalid");
+ new_krs.len = 1;
+ new_krs.val = &ordered_krs[0];
+ if ((ret = hdb_validate_key_rotations(context, NULL, &new_krs)) ||
+ (ret = hdb_validate_key_rotations(context, &existing_krs,
+ &new_krs)))
+ krb5_err(context, 1, ret, "Valid KeyRotation thought invalid");
+ new_krs.len = 2;
+ new_krs.val = &ordered_krs[0];
+ if ((ret = hdb_validate_key_rotations(context, NULL, &new_krs)) ||
+ (ret = hdb_validate_key_rotations(context, &existing_krs,
+ &new_krs)))
+ krb5_err(context, 1, ret, "Valid KeyRotation thought invalid");
+ existing_krs.len = 1;
+ existing_krs.val = &ordered_krs[1];
+ if ((ret = hdb_validate_key_rotations(context, &existing_krs,
+ &new_krs)))
+ krb5_err(context, 1, ret, "Valid KeyRotation thought invalid");
+ existing_krs.len = 2;
+ existing_krs.val = &ordered_krs[0];
+ if ((ret = hdb_validate_key_rotations(context, &existing_krs,
+ &new_krs)))
+ krb5_err(context, 1, ret, "Valid KeyRotation thought invalid");
+
+ new_krs.len = 2;
+ new_krs.val = &krs[0];
+ if ((ret = hdb_validate_key_rotations(context, &existing_krs,
+ &new_krs)) == 0)
+ krb5_errx(context, 1, "Invalid KeyRotation thought valid");
+ }
+
+ make_namespace(context, db, WK_PREFIX "_/bar.example@BAR.EXAMPLE");
+
+ fetch_entries(context, db, 1, 0, 0);
+ fetch_entries(context, db, 1, 1, 0);
+ fetch_entries(context, db, 1, 2, 0);
+ fetch_entries(context, db, 1, 3, 0);
+ fetch_entries(context, db, 1, 4, 0); /* Just before newest KR */
+
+ fetch_entries(context, db, 0, 0, 0);
+ fetch_entries(context, db, 0, 1, 0);
+ fetch_entries(context, db, 0, 2, 0);
+ fetch_entries(context, db, 0, 3, 0);
+ fetch_entries(context, db, 0, 4, 1); /* Must fail: just before 1st KR */
+
+ /*
+ * Check that for every virtual principal in `expected[]', all the keysets
+ * with the same kvno, in all the entries fetched for different times,
+ * match.
+ */
+ check_kvnos(context);
+
+#if 0
+ /*
+ * Check that for every virtual principal in `expected[]' we have the
+ * expected key history.
+ */
+ check_expected_kvnos(context);
+#endif
+
+ /*
+ * XXX Add various tests here, checking `e[]':
+ *
+ * - Extract all {principal, kvno, key} for all keys, current and
+ * otherwise, then sort by {key, kvno, principal}, then check that the
+ * only time we have matching keys is when the kvno and principal also
+ * match.
+ */
+
+ print_em(context);
+
+ /*
+ * XXX Test adding a third KR, a 4th KR, dropping KRs...
+ */
+
+ /* Cleanup */
+ for (i = 0; ret == 0 && i < sizeof(e) / sizeof(e[0]); i++)
+ hdb_free_entry(context, db, &e[i]);
+ db->hdb_destroy(context, db);
+ krb5_free_context(context);
+ return 0;
+}
diff --git a/third_party/heimdal/lib/hdb/version-script.map b/third_party/heimdal/lib/hdb/version-script.map
new file mode 100644
index 0000000..058060d
--- /dev/null
+++ b/third_party/heimdal/lib/hdb/version-script.map
@@ -0,0 +1,189 @@
+# $Id$
+
+HEIMDAL_HDB_1.0 {
+ global:
+ _hdb_fetch_kvno;
+ _hdb_remove;
+ _hdb_store;
+ hdb_add_current_keys_to_history;
+ hdb_add_history_key;
+ hdb_add_history_keyset;
+ hdb_add_master_key;
+ hdb_change_kvno;
+ hdb_check_db_format;
+ hdb_clear_extension;
+ hdb_clear_master_key;
+ hdb_create;
+ hdb_db_dir;
+ hdb_dbinfo_get_acl_file;
+ hdb_dbinfo_get_binding;
+ hdb_dbinfo_get_dbname;
+ hdb_dbinfo_get_label;
+ hdb_dbinfo_get_log_file;
+ hdb_dbinfo_get_mkey_file;
+ hdb_dbinfo_get_next;
+ hdb_dbinfo_get_realm;
+ hdb_default_db;
+ hdb_derive_etypes;
+ hdb_enctype2key;
+ hdb_entry2string;
+ hdb_entry2value;
+ hdb_entry_add_key_rotation;
+ hdb_entry_alias2value;
+ hdb_entry_check_mandatory;
+ hdb_entry_clear_password;
+ hdb_entry_get_ConstrainedDelegACL;
+ hdb_entry_get_aliases;
+ hdb_entry_get_key_rotation;
+ hdb_entry_get_krb5_config;
+ hdb_entry_get_password;
+ hdb_entry_get_pkinit_acl;
+ hdb_entry_get_pkinit_cert;
+ hdb_entry_get_pkinit_hash;
+ hdb_entry_get_pw_change_time;
+ hdb_entry_set_krb5_config;
+ hdb_entry_set_password;
+ hdb_entry_set_pw_change_time;
+ hdb_fetch_kvno;
+ hdb_find_extension;
+ hdb_foreach;
+ hdb_free_dbinfo;
+ hdb_free_entry;
+ hdb_free_key;
+ hdb_free_keys;
+ hdb_free_master_key;
+ hdb_generate_key_set;
+ hdb_generate_key_set_password;
+ hdb_generate_key_set_password_with_ks_tuple;
+ hdb_get_dbinfo;
+ hdb_get_instance;
+ hdb_init_db;
+ hdb_install_keyset;
+ hdb_key2principal;
+ hdb_kvno2keys;
+ hdb_list_builtin;
+ hdb_lock;
+ hdb_next_enctype2key;
+ hdb_principal2key;
+ hdb_print_entry;
+ hdb_process_master_key;
+ hdb_prune_keys;
+ hdb_prune_keys_kvno;
+ hdb_read_master_key;
+ hdb_remove_keys;
+ hdb_replace_extension;
+ hdb_seal_key;
+ hdb_seal_key_mkey;
+ hdb_seal_keys;
+ hdb_seal_keys_mkey;
+ hdb_set_last_modified_by;
+ hdb_set_master_key;
+ hdb_set_master_keyfile;
+ hdb_unlock;
+ hdb_unseal_key;
+ hdb_unseal_key_mkey;
+ hdb_unseal_keys;
+ hdb_unseal_keys_kvno;
+ hdb_unseal_keys_mkey;
+ hdb_validate_key_rotation;
+ hdb_validate_key_rotations;
+ hdb_value2entry;
+ hdb_value2entry_alias;
+ hdb_write_master_key;
+ length_hdb_keyset;
+ length_HDB_keyset;
+ hdb_interface_version;
+ initialize_hdb_error_table_r;
+
+ # MIT KDB related entries
+ _hdb_mdb_value2entry;
+ _hdb_mit_dump2mitdb_entry;
+
+ hdb_kt_ops;
+ hdb_get_kt_ops;
+
+ # some random bits needed for libkadm
+ add_HDB_Ext_KeyRotation;
+ add_HDB_Ext_KeySet;
+ add_HDB_Ext_KeySet;
+ add_Keys;
+ add_Keys;
+ asn1_HDBFlags_units;
+ copy_Event;
+ copy_HDB_EncTypeList;
+ copy_hdb_entry;
+ copy_hdb_entry_alias;
+ copy_HDB_entry;
+ copy_HDB_entry_alias;
+ copy_HDB_EntryOrAlias;
+ copy_HDB_extensions;
+ copy_HDB_Ext_KeyRotation;
+ copy_Key;
+ copy_Keys;
+ copy_Salt;
+ decode_HDB_EncTypeList;
+ decode_hdb_entry;
+ decode_hdb_entry_alias;
+ decode_HDB_entry;
+ decode_HDB_entry_alias;
+ decode_HDB_EntryOrAlias;
+ decode_HDB_Ext_Aliases;
+ decode_HDB_extension;
+ decode_HDB_Ext_KeyRotation;
+ decode_HDB_Ext_PKINIT_acl;
+ decode_Key;
+ decode_Keys;
+ encode_HDB_EncTypeList;
+ encode_hdb_entry;
+ encode_hdb_entry_alias;
+ encode_HDB_entry;
+ encode_HDB_entry_alias;
+ encode_HDB_EntryOrAlias;
+ encode_HDB_Ext_Aliases;
+ encode_HDB_extension;
+ encode_HDB_Ext_KeyRotation;
+ encode_HDB_Ext_PKINIT_acl;
+ encode_hdb_keyset;
+ encode_HDB_keyset;
+ encode_Key;
+ encode_Keys;
+ free_Event;
+ free_HDB_EncTypeList;
+ free_hdb_entry;
+ free_hdb_entry_alias;
+ free_HDB_entry;
+ free_HDB_entry_alias;
+ free_HDB_EntryOrAlias;
+ free_HDB_Ext_Aliases;
+ free_HDB_extension;
+ free_HDB_extensions;
+ free_HDB_Ext_KeyRotation;
+ free_HDB_Ext_KeySet;
+ free_HDB_Ext_PKINIT_acl;
+ free_hdb_keyset;
+ free_HDB_keyset;
+ free_Key;
+ free_Keys;
+ free_Salt;
+ HDBFlags2int;
+ int2HDBFlags;
+ int2KeyRotationFlags;
+ KeyRotationFlags2int;
+ length_HDB_EncTypeList;
+ length_hdb_entry;
+ length_hdb_entry_alias;
+ length_HDB_entry;
+ length_HDB_entry_alias;
+ length_HDB_EntryOrAlias;
+ length_HDB_Ext_Aliases;
+ length_HDB_extension;
+ length_HDB_Ext_KeyRotation;
+ length_HDB_Ext_PKINIT_acl;
+ length_Key;
+ length_Keys;
+ remove_HDB_Ext_KeyRotation;
+ remove_Keys;
+
+ local:
+ *;
+};