diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /third_party/heimdal/lib/hdb | |
parent | Initial commit. (diff) | |
download | samba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz samba-4f5791ebd03eaec1c7da0865a383175b05102712.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')
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, ¤t); + 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 Binary files differnew file mode 100644 index 0000000..4278ed3 --- /dev/null +++ b/third_party/heimdal/lib/hdb/data-mkey.mit.des3.be diff --git a/third_party/heimdal/lib/hdb/data-mkey.mit.des3.le b/third_party/heimdal/lib/hdb/data-mkey.mit.des3.le Binary files differnew file mode 100644 index 0000000..19fdc93 --- /dev/null +++ b/third_party/heimdal/lib/hdb/data-mkey.mit.des3.le 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, "e); + 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, "e); + 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: + *; +}; |