diff options
Diffstat (limited to 'src/lib/kdb/kdb5.c')
| -rw-r--r-- | src/lib/kdb/kdb5.c | 2738 |
1 files changed, 2738 insertions, 0 deletions
diff --git a/src/lib/kdb/kdb5.c b/src/lib/kdb/kdb5.c new file mode 100644 index 000000000000..4adf0fcbb201 --- /dev/null +++ b/src/lib/kdb/kdb5.c @@ -0,0 +1,2738 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * Copyright 2006, 2009, 2010, 2016 by the Massachusetts Institute of + * Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * This code was based on code donated to MIT by Novell for + * distribution under the MIT license. + */ + +/* + * Include files + */ + +#include <k5-int.h> +#include "kdb5.h" +#include "kdb_log.h" +#include "kdb5int.h" + +/* Currently DB2 policy related errors are exported from DAL. But + other databases should set_err function to return string. */ +#include "adb_err.h" + +/* + * internal static variable + */ + +static k5_mutex_t db_lock = K5_MUTEX_PARTIAL_INITIALIZER; + +static db_library lib_list; + +/* + * Helper Functions + */ + +MAKE_INIT_FUNCTION(kdb_init_lock_list); +MAKE_FINI_FUNCTION(kdb_fini_lock_list); + +static void +free_mkey_list(krb5_context context, krb5_keylist_node *mkey_list) +{ + krb5_keylist_node *cur, *next; + + for (cur = mkey_list; cur != NULL; cur = next) { + next = cur->next; + krb5_free_keyblock_contents(context, &cur->keyblock); + free(cur); + } +} + +int +kdb_init_lock_list() +{ + return k5_mutex_finish_init(&db_lock); +} + +static int +kdb_lock_list() +{ + int err; + err = CALL_INIT_FUNCTION (kdb_init_lock_list); + if (err) + return err; + k5_mutex_lock(&db_lock); + return 0; +} + +void +kdb_fini_lock_list() +{ + if (INITIALIZER_RAN(kdb_init_lock_list)) + k5_mutex_destroy(&db_lock); +} + +static void +kdb_unlock_list() +{ + k5_mutex_unlock(&db_lock); +} + +/* Return true if the ulog is mapped in the master role. */ +static inline krb5_boolean +logging(krb5_context context) +{ + kdb_log_context *log_ctx = context->kdblog_context; + + return log_ctx != NULL && log_ctx->iproprole == IPROP_MASTER && + log_ctx->ulog != NULL; +} + +void +krb5_dbe_free_key_data_contents(krb5_context context, krb5_key_data *key) +{ + int i, idx; + + if (key) { + idx = (key->key_data_ver == 1 ? 1 : 2); + for (i = 0; i < idx; i++) { + if (key->key_data_contents[i]) { + zap(key->key_data_contents[i], key->key_data_length[i]); + free(key->key_data_contents[i]); + } + } + } + return; +} + +void +krb5_dbe_free_key_list(krb5_context context, krb5_keylist_node *val) +{ + krb5_keylist_node *temp = val, *prev; + + while (temp != NULL) { + prev = temp; + temp = temp->next; + krb5_free_keyblock_contents(context, &(prev->keyblock)); + free(prev); + } +} + +void +krb5_dbe_free_actkvno_list(krb5_context context, krb5_actkvno_node *val) +{ + krb5_actkvno_node *temp = val, *prev; + + while (temp != NULL) { + prev = temp; + temp = temp->next; + free(prev); + } +} + +void +krb5_dbe_free_mkey_aux_list(krb5_context context, krb5_mkey_aux_node *val) +{ + krb5_mkey_aux_node *temp = val, *prev; + + while (temp != NULL) { + prev = temp; + temp = temp->next; + krb5_dbe_free_key_data_contents(context, &prev->latest_mkey); + free(prev); + } +} + +void +krb5_dbe_free_tl_data(krb5_context context, krb5_tl_data *tl_data) +{ + if (tl_data) { + if (tl_data->tl_data_contents) + free(tl_data->tl_data_contents); + free(tl_data); + } +} + +void +krb5_dbe_free_strings(krb5_context context, krb5_string_attr *strings, + int count) +{ + int i; + + if (strings == NULL) + return; + for (i = 0; i < count; i++) { + free(strings[i].key); + free(strings[i].value); + } + free(strings); +} + +void +krb5_dbe_free_string(krb5_context context, char *string) +{ + free(string); +} + +/* Set *section to the appropriate section to use for a database module's + * profile queries. The caller must free the result. */ +static krb5_error_code +get_conf_section(krb5_context context, char **section) +{ + krb5_error_code status; + char *result = NULL, *value = NULL, *defrealm; + + *section = NULL; + + status = krb5_get_default_realm(context, &defrealm); + if (status) { + k5_setmsg(context, KRB5_KDB_SERVER_INTERNAL_ERR, + _("No default realm set; cannot initialize KDB")); + return KRB5_KDB_SERVER_INTERNAL_ERR; + } + status = profile_get_string(context->profile, + /* realms */ + KDB_REALM_SECTION, + defrealm, + /* under the realm name, database_module */ + KDB_MODULE_POINTER, + /* default value is the realm name itself */ + defrealm, + &value); + krb5_free_default_realm(context, defrealm); + if (status) + return status; + result = strdup(value); + profile_release_string(value); + if (result == NULL) + return ENOMEM; + *section = result; + return 0; +} + +static krb5_error_code +kdb_get_library_name(krb5_context kcontext, char **libname_out) +{ + krb5_error_code status = 0; + char *value = NULL, *lib = NULL, *defrealm = NULL; + + *libname_out = NULL; + + status = krb5_get_default_realm(kcontext, &defrealm); + if (status) + goto clean_n_exit; + status = profile_get_string(kcontext->profile, + /* realms */ + KDB_REALM_SECTION, + defrealm, + /* under the realm name, database_module */ + KDB_MODULE_POINTER, + /* default value is the realm name itself */ + defrealm, + &value); + if (status) + goto clean_n_exit; + +#define DB2_NAME "db2" + /* we got the module section. Get the library name from the module */ + status = profile_get_string(kcontext->profile, KDB_MODULE_SECTION, value, + KDB_LIB_POINTER, + /* default to db2 */ + DB2_NAME, + &lib); + + if (status) { + goto clean_n_exit; + } + + *libname_out = strdup(lib); + if (*libname_out == NULL) + status = ENOMEM; + +clean_n_exit: + krb5_free_default_realm(kcontext, defrealm); + profile_release_string(value); + profile_release_string(lib); + return status; +} + +static void +copy_vtable(const kdb_vftabl *in, kdb_vftabl *out) +{ + /* Copy fields for minor version 0. */ + out->maj_ver = in->maj_ver; + out->min_ver = in->min_ver; + out->init_library = in->init_library; + out->fini_library = in->fini_library; + out->init_module = in->init_module; + out->fini_module = in->fini_module; + out->create = in->create; + out->destroy = in->destroy; + out->get_age = in->get_age; + out->lock = in->lock; + out->unlock = in->unlock; + out->get_principal = in->get_principal; + out->put_principal = in->put_principal; + out->delete_principal = in->delete_principal; + out->rename_principal = in->rename_principal; + out->iterate = in->iterate; + out->create_policy = in->create_policy; + out->get_policy = in->get_policy; + out->put_policy = in->put_policy; + out->iter_policy = in->iter_policy; + out->delete_policy = in->delete_policy; + out->fetch_master_key = in->fetch_master_key; + out->fetch_master_key_list = in->fetch_master_key_list; + out->store_master_key_list = in->store_master_key_list; + out->dbe_search_enctype = in->dbe_search_enctype; + out->change_pwd = in->change_pwd; + out->promote_db = in->promote_db; + out->decrypt_key_data = in->decrypt_key_data; + out->encrypt_key_data = in->encrypt_key_data; + out->sign_authdata = in->sign_authdata; + out->check_transited_realms = in->check_transited_realms; + out->check_policy_as = in->check_policy_as; + out->check_policy_tgs = in->check_policy_tgs; + out->audit_as_req = in->audit_as_req; + out->refresh_config = in->refresh_config; + out->check_allowed_to_delegate = in->check_allowed_to_delegate; + + /* Copy fields for minor version 1 (major version 6). */ + assert(KRB5_KDB_DAL_MAJOR_VERSION == 6); + out->free_principal_e_data = NULL; + if (in->min_ver >= 1) + out->free_principal_e_data = in->free_principal_e_data; + + /* Set defaults for optional fields. */ + if (out->fetch_master_key == NULL) + out->fetch_master_key = krb5_db_def_fetch_mkey; + if (out->fetch_master_key_list == NULL) + out->fetch_master_key_list = krb5_def_fetch_mkey_list; + if (out->store_master_key_list == NULL) + out->store_master_key_list = krb5_def_store_mkey_list; + if (out->dbe_search_enctype == NULL) + out->dbe_search_enctype = krb5_dbe_def_search_enctype; + if (out->change_pwd == NULL) + out->change_pwd = krb5_dbe_def_cpw; + if (out->decrypt_key_data == NULL) + out->decrypt_key_data = krb5_dbe_def_decrypt_key_data; + if (out->encrypt_key_data == NULL) + out->encrypt_key_data = krb5_dbe_def_encrypt_key_data; + if (out->rename_principal == NULL) + out->rename_principal = krb5_db_def_rename_principal; +} + +#ifdef STATIC_PLUGINS + +extern kdb_vftabl krb5_db2_kdb_function_table; +#ifdef ENABLE_LDAP +extern kdb_vftabl krb5_ldap_kdb_function_table; +#endif + +static krb5_error_code +kdb_load_library(krb5_context kcontext, char *lib_name, db_library *libptr) +{ + krb5_error_code status; + db_library lib; + kdb_vftabl *vftabl_addr = NULL; + + if (strcmp(lib_name, "db2") == 0) + vftabl_addr = &krb5_db2_kdb_function_table; +#ifdef ENABLE_LDAP + if (strcmp(lib_name, "kldap") == 0) + vftabl_addr = &krb5_ldap_kdb_function_table; +#endif + if (!vftabl_addr) { + k5_setmsg(kcontext, KRB5_KDB_DBTYPE_NOTFOUND, + _("Unable to find requested database type: %s"), lib_name); + return KRB5_PLUGIN_OP_NOTSUPP; + } + + lib = calloc(1, sizeof(*lib)); + if (lib == NULL) + return ENOMEM; + + strlcpy(lib->name, lib_name, sizeof(lib->name)); + copy_vtable(vftabl_addr, &lib->vftabl); + + status = lib->vftabl.init_library(); + if (status) + goto cleanup; + + *libptr = lib; + return 0; + +cleanup: + free(lib); + return status; +} + +#else /* KDB5_STATIC_LINK*/ + +static char *db_dl_location[] = DEFAULT_KDB_LIB_PATH; +#define db_dl_n_locations (sizeof(db_dl_location) / sizeof(db_dl_location[0])) + +static krb5_error_code +kdb_load_library(krb5_context kcontext, char *lib_name, db_library *lib) +{ + krb5_error_code status = 0; + int ndx; + void **vftabl_addrs = NULL; + /* N.B.: If this is "const" but not "static", the Solaris 10 + native compiler has trouble building the library because of + absolute relocations needed in read-only section ".rodata". + When it's static, it goes into ".picdata", which is + read-write. */ + static const char *const dbpath_names[] = { + KDB_MODULE_SECTION, KRB5_CONF_DB_MODULE_DIR, NULL, + }; + const char *filebases[2]; + char **profpath = NULL; + char **path = NULL; + + filebases[0] = lib_name; + filebases[1] = NULL; + + *lib = calloc((size_t) 1, sizeof(**lib)); + if (*lib == NULL) + return ENOMEM; + + strlcpy((*lib)->name, lib_name, sizeof((*lib)->name)); + + /* Fetch the list of directories specified in the config + file(s) first. */ + status = profile_get_values(kcontext->profile, dbpath_names, &profpath); + if (status != 0 && status != PROF_NO_RELATION) + goto clean_n_exit; + ndx = 0; + if (profpath) + while (profpath[ndx] != NULL) + ndx++; + + path = calloc(ndx + db_dl_n_locations, sizeof (char *)); + if (path == NULL) { + status = ENOMEM; + goto clean_n_exit; + } + if (ndx) + memcpy(path, profpath, ndx * sizeof(profpath[0])); + memcpy(path + ndx, db_dl_location, db_dl_n_locations * sizeof(char *)); + status = 0; + + if ((status = krb5int_open_plugin_dirs ((const char **) path, + filebases, + &(*lib)->dl_dir_handle, &kcontext->err))) { + status = KRB5_KDB_DBTYPE_NOTFOUND; + k5_prependmsg(kcontext, status, + _("Unable to find requested database type")); + goto clean_n_exit; + } + + if ((status = krb5int_get_plugin_dir_data (&(*lib)->dl_dir_handle, "kdb_function_table", + &vftabl_addrs, &kcontext->err))) { + status = KRB5_KDB_DBTYPE_INIT; + k5_prependmsg(kcontext, status, + _("plugin symbol 'kdb_function_table' lookup failed")); + goto clean_n_exit; + } + + if (vftabl_addrs[0] == NULL) { + /* No plugins! */ + status = KRB5_KDB_DBTYPE_NOTFOUND; + k5_setmsg(kcontext, status, + _("Unable to load requested database module '%s': plugin " + "symbol 'kdb_function_table' not found"), lib_name); + goto clean_n_exit; + } + + if (((kdb_vftabl *)vftabl_addrs[0])->maj_ver != + KRB5_KDB_DAL_MAJOR_VERSION) { + status = KRB5_KDB_DBTYPE_MISMATCH; + goto clean_n_exit; + } + + copy_vtable(vftabl_addrs[0], &(*lib)->vftabl); + + if ((status = (*lib)->vftabl.init_library())) + goto clean_n_exit; + +clean_n_exit: + krb5int_free_plugin_dir_data(vftabl_addrs); + /* Both of these DTRT with NULL. */ + profile_free_list(profpath); + free(path); + if (status && *lib) { + if (PLUGIN_DIR_OPEN((&(*lib)->dl_dir_handle))) + krb5int_close_plugin_dirs (&(*lib)->dl_dir_handle); + free(*lib); + *lib = NULL; + } + return status; +} + +#endif /* end of _KDB5_STATIC_LINK */ + +static krb5_error_code +kdb_find_library(krb5_context kcontext, char *lib_name, db_library *lib) +{ + /* lock here so that no two threads try to do the same at the same time */ + krb5_error_code status = 0; + int locked = 0; + db_library curr_elt, prev_elt = NULL; + static int kdb_db2_pol_err_loaded = 0; + + if (!strcmp(DB2_NAME, lib_name) && (kdb_db2_pol_err_loaded == 0)) { + initialize_adb_error_table(); + kdb_db2_pol_err_loaded = 1; + } + + if ((status = kdb_lock_list()) != 0) + goto clean_n_exit; + locked = 1; + + curr_elt = lib_list; + while (curr_elt != NULL) { + if (strcmp(lib_name, curr_elt->name) == 0) { + *lib = curr_elt; + goto clean_n_exit; + } + prev_elt = curr_elt; + curr_elt = curr_elt->next; + } + + /* module not found. create and add to list */ + status = kdb_load_library(kcontext, lib_name, lib); + if (status) + goto clean_n_exit; + + if (prev_elt) { + /* prev_elt points to the last element in the list */ + prev_elt->next = *lib; + (*lib)->prev = prev_elt; + } else { + lib_list = *lib; + } + +clean_n_exit: + if (*lib) + (*lib)->reference_cnt++; + + if (locked) + kdb_unlock_list(); + + return status; +} + +static krb5_error_code +kdb_free_library(db_library lib) +{ + krb5_error_code status = 0; + int locked = 0; + + if ((status = kdb_lock_list()) != 0) + goto clean_n_exit; + locked = 1; + + lib->reference_cnt--; + + if (lib->reference_cnt == 0) { + status = lib->vftabl.fini_library(); + if (status) + goto clean_n_exit; + + /* close the library */ + if (PLUGIN_DIR_OPEN((&lib->dl_dir_handle))) + krb5int_close_plugin_dirs (&lib->dl_dir_handle); + + if (lib->prev == NULL) + lib_list = lib->next; /* first element in the list */ + else + lib->prev->next = lib->next; + + if (lib->next) + lib->next->prev = lib->prev; + free(lib); + } + +clean_n_exit: + if (locked) + kdb_unlock_list(); + + return status; +} + +krb5_error_code +krb5_db_setup_lib_handle(krb5_context kcontext) +{ + char *library = NULL; + krb5_error_code status = 0; + db_library lib = NULL; + kdb5_dal_handle *dal_handle = NULL; + + dal_handle = calloc((size_t) 1, sizeof(kdb5_dal_handle)); + if (dal_handle == NULL) { + status = ENOMEM; + goto clean_n_exit; + } + + status = kdb_get_library_name(kcontext, &library); + if (library == NULL) { + k5_prependmsg(kcontext, status, + _("Cannot initialize database library")); + goto clean_n_exit; + } + + status = kdb_find_library(kcontext, library, &lib); + if (status) + goto clean_n_exit; + + dal_handle->lib_handle = lib; + kcontext->dal_handle = dal_handle; + +clean_n_exit: + free(library); + + if (status) { + free(dal_handle); + if (lib) + kdb_free_library(lib); + } + + return status; +} + +static krb5_error_code +kdb_free_lib_handle(krb5_context kcontext) +{ + krb5_error_code status = 0; + + status = kdb_free_library(kcontext->dal_handle->lib_handle); + if (status) + return status; + + free_mkey_list(kcontext, kcontext->dal_handle->master_keylist); + krb5_free_principal(kcontext, kcontext->dal_handle->master_princ); + free(kcontext->dal_handle); + kcontext->dal_handle = NULL; + return 0; +} + +static krb5_error_code +get_vftabl(krb5_context kcontext, kdb_vftabl **vftabl_ptr) +{ + krb5_error_code status; + + *vftabl_ptr = NULL; + if (kcontext->dal_handle == NULL) { + status = krb5_db_setup_lib_handle(kcontext); + if (status) + return status; + } + *vftabl_ptr = &kcontext->dal_handle->lib_handle->vftabl; + return 0; +} + +/* + * External functions... DAL API + */ +krb5_error_code +krb5_db_open(krb5_context kcontext, char **db_args, int mode) +{ + krb5_error_code status; + char *section; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + status = get_conf_section(kcontext, §ion); + if (status) + return status; + status = v->init_module(kcontext, section, db_args, mode); + free(section); + return status; +} + +krb5_error_code +krb5_db_inited(krb5_context kcontext) +{ + return !(kcontext && kcontext->dal_handle && + kcontext->dal_handle->db_context); +} + +krb5_error_code +krb5_db_create(krb5_context kcontext, char **db_args) +{ + krb5_error_code status; + char *section; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->create == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + status = get_conf_section(kcontext, §ion); + if (status) + return status; + status = v->create(kcontext, section, db_args); + free(section); + return status; +} + +krb5_error_code +krb5_db_fini(krb5_context kcontext) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + /* Do nothing if module was never loaded. */ + if (kcontext->dal_handle == NULL) + return 0; + + v = &kcontext->dal_handle->lib_handle->vftabl; + status = v->fini_module(kcontext); + + if (status) + return status; + + return kdb_free_lib_handle(kcontext); +} + +krb5_error_code +krb5_db_destroy(krb5_context kcontext, char **db_args) +{ + krb5_error_code status; + char *section; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->destroy == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + status = get_conf_section(kcontext, §ion); + if (status) + return status; + status = v->destroy(kcontext, section, db_args); + free(section); + return status; +} + +krb5_error_code +krb5_db_get_age(krb5_context kcontext, char *db_name, time_t *t) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->get_age == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + return v->get_age(kcontext, db_name, t); +} + +krb5_error_code +krb5_db_lock(krb5_context kcontext, int lock_mode) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->lock == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + return v->lock(kcontext, lock_mode); +} + +krb5_error_code +krb5_db_unlock(krb5_context kcontext) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->unlock == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + return v->unlock(kcontext); +} + +krb5_error_code +krb5_db_get_principal(krb5_context kcontext, krb5_const_principal search_for, + unsigned int flags, krb5_db_entry **entry) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + *entry = NULL; + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->get_principal == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + status = v->get_principal(kcontext, search_for, flags, entry); + if (status) + return status; + + /* Sort the keys in the db entry as some parts of krb5 expect it to be. */ + if ((*entry)->key_data != NULL) + krb5_dbe_sort_key_data((*entry)->key_data, (*entry)->n_key_data); + + return 0; +} + +static void +free_tl_data(krb5_tl_data *list) +{ + krb5_tl_data *next; + + for (; list != NULL; list = next) { + next = list->tl_data_next; + free(list->tl_data_contents); + free(list); + } +} + +void +krb5_db_free_principal(krb5_context kcontext, krb5_db_entry *entry) +{ + kdb_vftabl *v; + int i; + + if (entry == NULL) + return; + if (entry->e_data != NULL) { + if (get_vftabl(kcontext, &v) == 0 && v->free_principal_e_data != NULL) + v->free_principal_e_data(kcontext, entry->e_data); + else + free(entry->e_data); + } + krb5_free_principal(kcontext, entry->princ); + free_tl_data(entry->tl_data); + for (i = 0; i < entry->n_key_data; i++) + krb5_dbe_free_key_data_contents(kcontext, &entry->key_data[i]); + free(entry->key_data); + free(entry); +} + +static void +free_db_args(char **db_args) +{ + int i; + if (db_args) { + for (i = 0; db_args[i]; i++) + free(db_args[i]); + free(db_args); + } +} + +static krb5_error_code +extract_db_args_from_tl_data(krb5_context kcontext, krb5_tl_data **start, + krb5_int16 *count, char ***db_argsp) +{ + char **db_args = NULL; + int db_args_size = 0; + krb5_tl_data *prev, *curr, *next; + krb5_error_code status; + + /* Giving db_args as part of tl data causes db2 to store the + tl_data as such. To prevent this, tl_data is collated and + passed as a separate argument. Currently supports only one + principal, but passing it as a separate argument makes it + difficult for kadmin remote to pass arguments to server. */ + prev = NULL, curr = *start; + while (curr) { + if (curr->tl_data_type == KRB5_TL_DB_ARGS) { + char **t; + /* Since this is expected to be NULL terminated string and + this could come from any client, do a check before + passing it to db. */ + if (((char *) curr->tl_data_contents)[curr->tl_data_length - 1] != + '\0') { + /* Not null terminated. Dangerous input. */ + status = EINVAL; + goto clean_n_exit; + } + + db_args_size++; + t = realloc(db_args, sizeof(char *) * (db_args_size + 1)); /* 1 for NULL */ + if (t == NULL) { + status = ENOMEM; + goto clean_n_exit; + } + + db_args = t; + db_args[db_args_size - 1] = (char *) curr->tl_data_contents; + db_args[db_args_size] = NULL; + + next = curr->tl_data_next; + if (prev == NULL) { + /* current node is the first in the linked list. remove it */ + *start = curr->tl_data_next; + } else { + prev->tl_data_next = curr->tl_data_next; + } + (*count)--; + free(curr); + + /* previous does not change */ + curr = next; + } else { + prev = curr; + curr = curr->tl_data_next; + } + } + status = 0; +clean_n_exit: + if (status != 0) { + free_db_args(db_args); + db_args = NULL; + } + *db_argsp = db_args; + return status; +} + +krb5_error_code +krb5int_put_principal_no_log(krb5_context kcontext, krb5_db_entry *entry) +{ + kdb_vftabl *v; + krb5_error_code status; + char **db_args; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->put_principal == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + status = extract_db_args_from_tl_data(kcontext, &entry->tl_data, + &entry->n_tl_data, + &db_args); + if (status) + return status; + status = v->put_principal(kcontext, entry, db_args); + free_db_args(db_args); + return status; +} + +krb5_error_code +krb5_db_put_principal(krb5_context kcontext, krb5_db_entry *entry) +{ + krb5_error_code status = 0; + kdb_incr_update_t *upd = NULL; + char *princ_name = NULL; + + if (logging(kcontext)) { + upd = k5alloc(sizeof(*upd), &status); + if (upd == NULL) + goto cleanup; + if ((status = ulog_conv_2logentry(kcontext, entry, upd))) + goto cleanup; + + status = krb5_unparse_name(kcontext, entry->princ, &princ_name); + if (status != 0) + goto cleanup; + + upd->kdb_princ_name.utf8str_t_val = princ_name; + upd->kdb_princ_name.utf8str_t_len = strlen(princ_name); + } + + status = krb5int_put_principal_no_log(kcontext, entry); + if (status) + goto cleanup; + + if (logging(kcontext)) + status = ulog_add_update(kcontext, upd); + +cleanup: + ulog_free_entries(upd, 1); + return status; +} + +krb5_error_code +krb5int_delete_principal_no_log(krb5_context kcontext, + krb5_principal search_for) +{ + kdb_vftabl *v; + krb5_error_code status; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->delete_principal == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + return v->delete_principal(kcontext, search_for); +} + +krb5_error_code +krb5_db_delete_principal(krb5_context kcontext, krb5_principal search_for) +{ + krb5_error_code status = 0; + kdb_incr_update_t upd; + char *princ_name = NULL; + + status = krb5int_delete_principal_no_log(kcontext, search_for); + if (status || !logging(kcontext)) + return status; + + status = krb5_unparse_name(kcontext, search_for, &princ_name); + if (status) + return status; + + memset(&upd, 0, sizeof(kdb_incr_update_t)); + upd.kdb_princ_name.utf8str_t_val = princ_name; + upd.kdb_princ_name.utf8str_t_len = strlen(princ_name); + upd.kdb_deleted = TRUE; + + status = ulog_add_update(kcontext, &upd); + free(princ_name); + return status; +} + +krb5_error_code +krb5_db_rename_principal(krb5_context kcontext, krb5_principal source, + krb5_principal target) +{ + kdb_vftabl *v; + krb5_error_code status; + krb5_db_entry *entry; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + + /* + * If the default rename function isn't used and logging is enabled, iprop + * would fail since it doesn't formally support renaming. In that case + * return KRB5_PLUGIN_OP_NOTSUPP. + */ + if (v->rename_principal != krb5_db_def_rename_principal && + logging(kcontext)) + return KRB5_PLUGIN_OP_NOTSUPP; + + status = krb5_db_get_principal(kcontext, target, KRB5_KDB_FLAG_ALIAS_OK, + &entry); + if (status == 0) { + krb5_db_free_principal(kcontext, entry); + return KRB5_KDB_INUSE; + } + + return v->rename_principal(kcontext, source, target); +} + +/* + * Use a proxy function for iterate so that we can sort the keys before sending + * them to the callback. + */ +struct callback_proxy_args { + int (*func)(krb5_pointer, krb5_db_entry *); + krb5_pointer func_arg; +}; + +static int +sort_entry_callback_proxy(krb5_pointer func_arg, krb5_db_entry *entry) +{ + struct callback_proxy_args *args = (struct callback_proxy_args *)func_arg; + + /* Sort the keys in the db entry as some parts of krb5 expect it to be. */ + if (entry && entry->key_data) + krb5_dbe_sort_key_data(entry->key_data, entry->n_key_data); + return args->func(args->func_arg, entry); +} + +krb5_error_code +krb5_db_iterate(krb5_context kcontext, char *match_entry, + int (*func)(krb5_pointer, krb5_db_entry *), + krb5_pointer func_arg, krb5_flags iterflags) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + struct callback_proxy_args proxy_args; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->iterate == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + + /* Use the proxy function to sort key data before passing entries to + * callback. */ + proxy_args.func = func; + proxy_args.func_arg = func_arg; + return v->iterate(kcontext, match_entry, sort_entry_callback_proxy, + &proxy_args, iterflags); +} + +/* Return a read only pointer alias to mkey list. Do not free this! */ +krb5_keylist_node * +krb5_db_mkey_list_alias(krb5_context kcontext) +{ + return kcontext->dal_handle->master_keylist; +} + +krb5_error_code +krb5_db_fetch_mkey_list(krb5_context context, krb5_principal mname, + const krb5_keyblock *mkey) +{ + kdb_vftabl *v; + krb5_error_code status = 0; + krb5_keylist_node *local_keylist; + + status = get_vftabl(context, &v); + if (status) + return status; + + if (!context->dal_handle->master_princ) { + status = krb5_copy_principal(context, mname, + &context->dal_handle->master_princ); + if (status) + return status; + } + + status = v->fetch_master_key_list(context, mname, mkey, &local_keylist); + if (status == 0) { + free_mkey_list(context, context->dal_handle->master_keylist); + context->dal_handle->master_keylist = local_keylist; + } + return status; +} + +krb5_error_code +krb5_db_store_master_key(krb5_context kcontext, char *keyfile, + krb5_principal mname, krb5_kvno kvno, + krb5_keyblock * key, char *master_pwd) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + krb5_keylist_node list; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + + if (v->store_master_key_list == NULL) + return KRB5_KDB_DBTYPE_NOSUP; + + list.kvno = kvno; + list.keyblock = *key; + list.next = NULL; + + return v->store_master_key_list(kcontext, keyfile, mname, + &list, master_pwd); +} + +krb5_error_code +krb5_db_store_master_key_list(krb5_context kcontext, char *keyfile, + krb5_principal mname, char *master_pwd) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + + if (v->store_master_key_list == NULL) + return KRB5_KDB_DBTYPE_NOSUP; + + if (kcontext->dal_handle->master_keylist == NULL) + return KRB5_KDB_DBNOTINITED; + + return v->store_master_key_list(kcontext, keyfile, mname, + kcontext->dal_handle->master_keylist, + master_pwd); +} + +char *krb5_mkey_pwd_prompt1 = KRB5_KDC_MKEY_1; +char *krb5_mkey_pwd_prompt2 = KRB5_KDC_MKEY_2; + +krb5_error_code +krb5_db_fetch_mkey(krb5_context context, krb5_principal mname, + krb5_enctype etype, krb5_boolean fromkeyboard, + krb5_boolean twice, char *db_args, krb5_kvno *kvno, + krb5_data *salt, krb5_keyblock *key) +{ + krb5_error_code retval; + char password[BUFSIZ]; + krb5_data pwd; + unsigned int size = sizeof(password); + krb5_keyblock tmp_key; + + memset(&tmp_key, 0, sizeof(tmp_key)); + + if (fromkeyboard) { + krb5_data scratch; + + if ((retval = krb5_read_password(context, krb5_mkey_pwd_prompt1, + twice ? krb5_mkey_pwd_prompt2 : 0, + password, &size))) { + goto clean_n_exit; + } + + pwd.data = password; + pwd.length = size; + if (!salt) { + retval = krb5_principal2salt(context, mname, &scratch); + if (retval) + goto clean_n_exit; + } + retval = + krb5_c_string_to_key(context, etype, &pwd, salt ? salt : &scratch, + key); + /* + * If a kvno pointer was passed in and it dereferences the IGNORE_VNO + * value then it should be assigned the value of the kvno associated + * with the current mkey princ key if that princ entry is available + * otherwise assign 1 which is the default kvno value for the mkey + * princ. + */ + if (kvno != NULL && *kvno == IGNORE_VNO) { + krb5_error_code rc; + krb5_db_entry *master_entry; + + rc = krb5_db_get_principal(context, mname, 0, &master_entry); + if (rc == 0) { + *kvno = (krb5_kvno) master_entry->key_data->key_data_kvno; + krb5_db_free_principal(context, master_entry); + } else + *kvno = 1; + } + + if (!salt) + free(scratch.data); + zap(password, sizeof(password)); /* erase it */ + + } else { + kdb_vftabl *v; + + if (context->dal_handle == NULL) { + retval = krb5_db_setup_lib_handle(context); + if (retval) + goto clean_n_exit; + } + + /* get the enctype from the stash */ + tmp_key.enctype = ENCTYPE_UNKNOWN; + + v = &context->dal_handle->lib_handle->vftabl; + retval = v->fetch_master_key(context, mname, &tmp_key, kvno, db_args); + + if (retval) + goto clean_n_exit; + + key->contents = k5memdup(tmp_key.contents, tmp_key.length, &retval); + if (key->contents == NULL) + goto clean_n_exit; + + key->magic = tmp_key.magic; + key->enctype = tmp_key.enctype; + key->length = tmp_key.length; + } + +clean_n_exit: + zapfree(tmp_key.contents, tmp_key.length); + return retval; +} + +krb5_error_code +krb5_dbe_fetch_act_key_list(krb5_context context, krb5_principal princ, + krb5_actkvno_node **act_key_list) +{ + krb5_error_code retval = 0; + krb5_db_entry *entry; + + if (act_key_list == NULL) + return (EINVAL); + + retval = krb5_db_get_principal(context, princ, 0, &entry); + if (retval == KRB5_KDB_NOENTRY) + return KRB5_KDB_NOMASTERKEY; + else if (retval) + return retval; + + retval = krb5_dbe_lookup_actkvno(context, entry, act_key_list); + krb5_db_free_principal(context, entry); + return retval; +} + +/* Find the most recent entry in list (which must not be empty) for the given + * timestamp, and return its kvno. */ +static krb5_kvno +find_actkvno(krb5_actkvno_node *list, krb5_timestamp now) +{ + /* + * The list is sorted in ascending order of time. Return the kvno of the + * predecessor of the first entry whose time is in the future. If + * (contrary to the safety checks in kdb5_util use_mkey) all of the entries + * are in the future, we will return the first node; if all are in the + * past, we will return the last node. + */ + while (list->next != NULL && list->next->act_time <= now) + list = list->next; + return list->act_kvno; +} + +/* Search the master keylist for the master key with the specified kvno. + * Return the keyblock of the matching entry or NULL if it does not exist. */ +static krb5_keyblock * +find_master_key(krb5_context context, krb5_kvno kvno) +{ + krb5_keylist_node *n; + + for (n = context->dal_handle->master_keylist; n != NULL; n = n->next) { + if (n->kvno == kvno) + return &n->keyblock; + } + return NULL; +} + +/* + * Locates the "active" mkey used when encrypting a princ's keys. Note, the + * caller must NOT free the output act_mkey. + */ + +krb5_error_code +krb5_dbe_find_act_mkey(krb5_context context, krb5_actkvno_node *act_mkey_list, + krb5_kvno *act_kvno, krb5_keyblock **act_mkey) +{ + krb5_kvno kvno; + krb5_error_code retval; + krb5_keyblock *mkey, *cur_mkey; + krb5_timestamp now; + + if (act_mkey_list == NULL) { + *act_kvno = 0; + *act_mkey = NULL; + return 0; + } + + if (context->dal_handle->master_keylist == NULL) + return KRB5_KDB_DBNOTINITED; + + /* Find the currently active master key version. */ + if ((retval = krb5_timeofday(context, &now))) + return (retval); + kvno = find_actkvno(act_mkey_list, now); + + /* Find the corresponding master key. */ + mkey = find_master_key(context, kvno); + if (mkey == NULL) { + /* Reload the master key list and try again. */ + cur_mkey = &context->dal_handle->master_keylist->keyblock; + if (krb5_db_fetch_mkey_list(context, context->dal_handle->master_princ, + cur_mkey) == 0) + mkey = find_master_key(context, kvno); + } + if (mkey == NULL) + return KRB5_KDB_NO_MATCHING_KEY; + + *act_mkey = mkey; + if (act_kvno != NULL) + *act_kvno = kvno; + return 0; +} + +/* + * Locates the mkey used to protect a princ's keys. Note, the caller must not + * free the output key. + */ +krb5_error_code +krb5_dbe_find_mkey(krb5_context context, krb5_db_entry *entry, + krb5_keyblock **mkey) +{ + krb5_kvno mkvno; + krb5_error_code retval; + krb5_keylist_node *cur_keyblock = context->dal_handle->master_keylist; + + if (!cur_keyblock) + return KRB5_KDB_DBNOTINITED; + + retval = krb5_dbe_get_mkvno(context, entry, &mkvno); + if (retval) + return (retval); + + while (cur_keyblock && cur_keyblock->kvno != mkvno) + cur_keyblock = cur_keyblock->next; + + if (cur_keyblock) { + *mkey = &cur_keyblock->keyblock; + return (0); + } else { + return KRB5_KDB_NO_MATCHING_KEY; + } +} + +void * +krb5_db_alloc(krb5_context kcontext, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +void +krb5_db_free(krb5_context kcontext, void *ptr) +{ + free(ptr); +} + +/* has to be modified */ + +krb5_error_code +krb5_dbe_find_enctype(krb5_context kcontext, krb5_db_entry *dbentp, + krb5_int32 ktype, krb5_int32 stype, krb5_int32 kvno, + krb5_key_data **kdatap) +{ + krb5_int32 start = 0; + return krb5_dbe_search_enctype(kcontext, dbentp, &start, ktype, stype, + kvno, kdatap); +} + +krb5_error_code +krb5_dbe_search_enctype(krb5_context kcontext, krb5_db_entry *dbentp, + krb5_int32 *start, krb5_int32 ktype, krb5_int32 stype, + krb5_int32 kvno, krb5_key_data ** kdatap) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + return v->dbe_search_enctype(kcontext, dbentp, start, ktype, stype, kvno, + kdatap); +} + +#define REALM_SEP_STRING "@" + +krb5_error_code +krb5_db_setup_mkey_name(krb5_context context, const char *keyname, + const char *realm, char **fullname, + krb5_principal *principal) +{ + krb5_error_code retval; + char *fname; + + if (!keyname) + keyname = KRB5_KDB_M_NAME; /* XXX external? */ + + if (asprintf(&fname, "%s%s%s", keyname, REALM_SEP_STRING, realm) < 0) + return ENOMEM; + + if ((retval = krb5_parse_name(context, fname, principal))) + return retval; + if (fullname) + *fullname = fname; + else + free(fname); + return 0; +} + +krb5_error_code +krb5_dbe_lookup_last_pwd_change(krb5_context context, krb5_db_entry *entry, + krb5_timestamp *stamp) +{ + krb5_tl_data tl_data; + krb5_error_code code; + krb5_int32 tmp; + + tl_data.tl_data_type = KRB5_TL_LAST_PWD_CHANGE; + + if ((code = krb5_dbe_lookup_tl_data(context, entry, &tl_data))) + return (code); + + if (tl_data.tl_data_length != 4) { + *stamp = 0; + return (0); + } + + krb5_kdb_decode_int32(tl_data.tl_data_contents, tmp); + + *stamp = (krb5_timestamp) tmp; + + return (0); +} + +krb5_error_code +krb5_dbe_lookup_last_admin_unlock(krb5_context context, krb5_db_entry *entry, + krb5_timestamp *stamp) +{ + krb5_tl_data tl_data; + krb5_error_code code; + krb5_int32 tmp; + + tl_data.tl_data_type = KRB5_TL_LAST_ADMIN_UNLOCK; + + if ((code = krb5_dbe_lookup_tl_data(context, entry, &tl_data))) + return (code); + + if (tl_data.tl_data_length != 4) { + *stamp = 0; + return (0); + } + + krb5_kdb_decode_int32(tl_data.tl_data_contents, tmp); + + *stamp = (krb5_timestamp) tmp; + + return (0); +} + +krb5_error_code +krb5_dbe_lookup_tl_data(krb5_context context, krb5_db_entry *entry, + krb5_tl_data *ret_tl_data) +{ + krb5_tl_data *tl_data; + + for (tl_data = entry->tl_data; tl_data; tl_data = tl_data->tl_data_next) { + if (tl_data->tl_data_type == ret_tl_data->tl_data_type) { + *ret_tl_data = *tl_data; + return (0); + } + } + + /* + * If the requested record isn't found, return zero bytes. If it + * ever means something to have a zero-length tl_data, this code + * and its callers will have to be changed. + */ + + ret_tl_data->tl_data_length = 0; + ret_tl_data->tl_data_contents = NULL; + return (0); +} + +krb5_error_code +krb5_dbe_create_key_data(krb5_context context, krb5_db_entry *entry) +{ + krb5_key_data *newptr; + + newptr = realloc(entry->key_data, + (entry->n_key_data + 1) * sizeof(*entry->key_data)); + if (newptr == NULL) + return ENOMEM; + entry->key_data = newptr; + + memset(entry->key_data + entry->n_key_data, 0, sizeof(krb5_key_data)); + entry->n_key_data++; + + return 0; +} + +krb5_error_code +krb5_dbe_update_mod_princ_data(krb5_context context, krb5_db_entry *entry, + krb5_timestamp mod_date, + krb5_const_principal mod_princ) +{ + krb5_tl_data tl_data; + + krb5_error_code retval = 0; + krb5_octet *nextloc = 0; + char *unparse_mod_princ = 0; + unsigned int unparse_mod_princ_size; + + if ((retval = krb5_unparse_name(context, mod_princ, &unparse_mod_princ))) + return (retval); + + unparse_mod_princ_size = strlen(unparse_mod_princ) + 1; + + if ((nextloc = (krb5_octet *) malloc(unparse_mod_princ_size + 4)) + == NULL) { + free(unparse_mod_princ); + return (ENOMEM); + } + + tl_data.tl_data_type = KRB5_TL_MOD_PRINC; + tl_data.tl_data_length = unparse_mod_princ_size + 4; + tl_data.tl_data_contents = nextloc; + + /* Mod Date */ + krb5_kdb_encode_int32(mod_date, nextloc); + + /* Mod Princ */ + memcpy(nextloc + 4, unparse_mod_princ, unparse_mod_princ_size); + + retval = krb5_dbe_update_tl_data(context, entry, &tl_data); + + free(unparse_mod_princ); + free(nextloc); + + return (retval); +} + +krb5_error_code +krb5_dbe_lookup_mod_princ_data(krb5_context context, krb5_db_entry *entry, + krb5_timestamp *mod_time, + krb5_principal *mod_princ) +{ + krb5_tl_data tl_data; + krb5_error_code code; + + *mod_princ = NULL; + *mod_time = 0; + + tl_data.tl_data_type = KRB5_TL_MOD_PRINC; + + if ((code = krb5_dbe_lookup_tl_data(context, entry, &tl_data))) + return (code); + + if ((tl_data.tl_data_length < 5) || + (tl_data.tl_data_contents[tl_data.tl_data_length - 1] != '\0')) + return (KRB5_KDB_TRUNCATED_RECORD); + + /* Mod Date */ + krb5_kdb_decode_int32(tl_data.tl_data_contents, *mod_time); + + /* Mod Princ */ + if ((code = krb5_parse_name(context, + (const char *) (tl_data.tl_data_contents + 4), + mod_princ))) + return (code); + + return (0); +} + +krb5_error_code +krb5_dbe_lookup_mkvno(krb5_context context, krb5_db_entry *entry, + krb5_kvno *mkvno) +{ + krb5_tl_data tl_data; + krb5_error_code code; + krb5_int16 tmp; + + tl_data.tl_data_type = KRB5_TL_MKVNO; + + if ((code = krb5_dbe_lookup_tl_data(context, entry, &tl_data))) + return (code); + + if (tl_data.tl_data_length == 0) { + *mkvno = 0; /* Indicates KRB5_TL_MKVNO data not present */ + return (0); + } else if (tl_data.tl_data_length != 2) { + return (KRB5_KDB_TRUNCATED_RECORD); + } + + krb5_kdb_decode_int16(tl_data.tl_data_contents, tmp); + *mkvno = (krb5_kvno) tmp; + return (0); +} + +krb5_error_code +krb5_dbe_get_mkvno(krb5_context context, krb5_db_entry *entry, + krb5_kvno *mkvno) +{ + krb5_error_code code; + krb5_kvno kvno; + krb5_keylist_node *mkey_list = context->dal_handle->master_keylist; + + if (mkey_list == NULL) + return KRB5_KDB_DBNOTINITED; + + /* Output the value from entry tl_data if present. */ + code = krb5_dbe_lookup_mkvno(context, entry, &kvno); + if (code != 0) + return code; + if (kvno != 0) { + *mkvno = kvno; + return 0; + } + + /* Determine the minimum kvno in mkey_list and output that. */ + kvno = (krb5_kvno) -1; + while (mkey_list != NULL) { + if (mkey_list->kvno < kvno) + kvno = mkey_list->kvno; + mkey_list = mkey_list->next; + } + *mkvno = kvno; + return 0; +} + +krb5_error_code +krb5_dbe_update_mkvno(krb5_context context, krb5_db_entry *entry, + krb5_kvno mkvno) +{ + krb5_tl_data tl_data; + krb5_octet buf[2]; /* this is the encoded size of an int16 */ + krb5_int16 tmp_kvno = (krb5_int16) mkvno; + + tl_data.tl_data_type = KRB5_TL_MKVNO; + tl_data.tl_data_length = sizeof(buf); + krb5_kdb_encode_int16(tmp_kvno, buf); + tl_data.tl_data_contents = buf; + + return (krb5_dbe_update_tl_data(context, entry, &tl_data)); +} + +krb5_error_code +krb5_dbe_lookup_mkey_aux(krb5_context context, krb5_db_entry *entry, + krb5_mkey_aux_node **mkey_aux_data_list) +{ + krb5_tl_data tl_data; + krb5_int16 version, mkey_kvno; + krb5_mkey_aux_node *head_data = NULL, *new_data = NULL, + *prev_data = NULL; + krb5_octet *curloc; /* current location pointer */ + krb5_error_code code; + + tl_data.tl_data_type = KRB5_TL_MKEY_AUX; + if ((code = krb5_dbe_lookup_tl_data(context, entry, &tl_data))) + return (code); + + if (tl_data.tl_data_contents == NULL) { + *mkey_aux_data_list = NULL; + return (0); + } else { + /* get version to determine how to parse the data */ + krb5_kdb_decode_int16(tl_data.tl_data_contents, version); + if (version == 1) { + /* variable size, must be at least 10 bytes */ + if (tl_data.tl_data_length < 10) + return (KRB5_KDB_TRUNCATED_RECORD); + + /* curloc points to first tuple entry in the tl_data_contents */ + curloc = tl_data.tl_data_contents + sizeof(version); + + while (curloc < (tl_data.tl_data_contents + tl_data.tl_data_length)) { + + new_data = (krb5_mkey_aux_node *) malloc(sizeof(krb5_mkey_aux_node)); + if (new_data == NULL) { + krb5_dbe_free_mkey_aux_list(context, head_data); + return (ENOMEM); + } + memset(new_data, 0, sizeof(krb5_mkey_aux_node)); + + krb5_kdb_decode_int16(curloc, mkey_kvno); + new_data->mkey_kvno = mkey_kvno; + curloc += sizeof(krb5_ui_2); + krb5_kdb_decode_int16(curloc, new_data->latest_mkey.key_data_kvno); + curloc += sizeof(krb5_ui_2); + krb5_kdb_decode_int16(curloc, new_data->latest_mkey.key_data_type[0]); + curloc += sizeof(krb5_ui_2); + krb5_kdb_decode_int16(curloc, new_data->latest_mkey.key_data_length[0]); + curloc += sizeof(krb5_ui_2); + + new_data->latest_mkey.key_data_contents[0] = (krb5_octet *) + malloc(new_data->latest_mkey.key_data_length[0]); + + if (new_data->latest_mkey.key_data_contents[0] == NULL) { + krb5_dbe_free_mkey_aux_list(context, head_data); + free(new_data); + return (ENOMEM); + } + memcpy(new_data->latest_mkey.key_data_contents[0], curloc, + new_data->latest_mkey.key_data_length[0]); + curloc += new_data->latest_mkey.key_data_length[0]; + + /* always using key data ver 1 for mkeys */ + new_data->latest_mkey.key_data_ver = 1; + + new_data->next = NULL; + if (prev_data != NULL) + prev_data->next = new_data; + else + head_data = new_data; + prev_data = new_data; + } + } else { + k5_setmsg(context, KRB5_KDB_BAD_VERSION, + _("Illegal version number for KRB5_TL_MKEY_AUX %d\n"), + version); + return (KRB5_KDB_BAD_VERSION); + } + } + *mkey_aux_data_list = head_data; + return (0); +} + +#if KRB5_TL_MKEY_AUX_VER == 1 +krb5_error_code +krb5_dbe_update_mkey_aux(krb5_context context, krb5_db_entry *entry, + krb5_mkey_aux_node *mkey_aux_data_list) +{ + krb5_error_code status; + krb5_tl_data tl_data; + krb5_int16 version, tmp_kvno; + unsigned char *nextloc; + krb5_mkey_aux_node *aux_data_entry; + + if (!mkey_aux_data_list) { + /* delete the KRB5_TL_MKEY_AUX from the entry */ + krb5_dbe_delete_tl_data(context, entry, KRB5_TL_MKEY_AUX); + return (0); + } + + memset(&tl_data, 0, sizeof(tl_data)); + tl_data.tl_data_type = KRB5_TL_MKEY_AUX; + /* + * determine out how much space to allocate. Note key_data_ver not stored + * as this is hard coded to one and is accounted for in + * krb5_dbe_lookup_mkey_aux. + */ + tl_data.tl_data_length = sizeof(version); /* version */ + for (aux_data_entry = mkey_aux_data_list; aux_data_entry != NULL; + aux_data_entry = aux_data_entry->next) { + + tl_data.tl_data_length += (sizeof(krb5_ui_2) + /* mkey_kvno */ + sizeof(krb5_ui_2) + /* latest_mkey kvno */ + sizeof(krb5_ui_2) + /* latest_mkey enctype */ + sizeof(krb5_ui_2) + /* latest_mkey length */ + aux_data_entry->latest_mkey.key_data_length[0]); + } + + tl_data.tl_data_contents = (krb5_octet *) malloc(tl_data.tl_data_length); + if (tl_data.tl_data_contents == NULL) + return (ENOMEM); + + nextloc = tl_data.tl_data_contents; + version = KRB5_TL_MKEY_AUX_VER; + krb5_kdb_encode_int16(version, nextloc); + nextloc += sizeof(krb5_ui_2); + + for (aux_data_entry = mkey_aux_data_list; aux_data_entry != NULL; + aux_data_entry = aux_data_entry->next) { + + tmp_kvno = (krb5_int16) aux_data_entry->mkey_kvno; + krb5_kdb_encode_int16(tmp_kvno, nextloc); + nextloc += sizeof(krb5_ui_2); + + krb5_kdb_encode_int16(aux_data_entry->latest_mkey.key_data_kvno, + nextloc); + nextloc += sizeof(krb5_ui_2); + + krb5_kdb_encode_int16(aux_data_entry->latest_mkey.key_data_type[0], + nextloc); + nextloc += sizeof(krb5_ui_2); + + krb5_kdb_encode_int16(aux_data_entry->latest_mkey.key_data_length[0], + nextloc); + nextloc += sizeof(krb5_ui_2); + + if (aux_data_entry->latest_mkey.key_data_length[0] > 0) { + memcpy(nextloc, aux_data_entry->latest_mkey.key_data_contents[0], + aux_data_entry->latest_mkey.key_data_length[0]); + nextloc += aux_data_entry->latest_mkey.key_data_length[0]; + } + } + + status = krb5_dbe_update_tl_data(context, entry, &tl_data); + free(tl_data.tl_data_contents); + return status; +} +#endif /* KRB5_TL_MKEY_AUX_VER == 1 */ + +#if KRB5_TL_ACTKVNO_VER == 1 +/* + * If version of the KRB5_TL_ACTKVNO data is KRB5_TL_ACTKVNO_VER == 1 then size of + * a actkvno tuple {act_kvno, act_time} entry is: + */ +#define ACTKVNO_TUPLE_SIZE (sizeof(krb5_int16) + sizeof(krb5_int32)) +#define act_kvno(cp) (cp) /* return pointer to start of act_kvno data */ +#define act_time(cp) ((cp) + sizeof(krb5_int16)) /* return pointer to start of act_time data */ +#endif + +krb5_error_code +krb5_dbe_lookup_actkvno(krb5_context context, krb5_db_entry *entry, + krb5_actkvno_node **actkvno_list) +{ + krb5_tl_data tl_data; + krb5_error_code code; + krb5_int16 version, tmp_kvno; + krb5_actkvno_node *head_data = NULL, *new_data = NULL, *prev_data = NULL; + unsigned int num_actkvno, i; + krb5_octet *next_tuple; + krb5_kvno earliest_kvno; + + memset(&tl_data, 0, sizeof(tl_data)); + tl_data.tl_data_type = KRB5_TL_ACTKVNO; + + if ((code = krb5_dbe_lookup_tl_data(context, entry, &tl_data))) + return (code); + + if (tl_data.tl_data_contents == NULL) { + /* + * If there is no KRB5_TL_ACTKVNO data (likely because the KDB was + * created prior to 1.7), synthesize the list which should have been + * created at KDB initialization, making the earliest master key + * active. + */ + + /* Get the earliest master key version. */ + if (entry->n_key_data == 0) + return KRB5_KDB_NOMASTERKEY; + earliest_kvno = entry->key_data[entry->n_key_data - 1].key_data_kvno; + + head_data = malloc(sizeof(*head_data)); + if (head_data == NULL) + return ENOMEM; + memset(head_data, 0, sizeof(*head_data)); + head_data->act_time = 0; /* earliest time possible */ + head_data->act_kvno = earliest_kvno; + } else { + /* get version to determine how to parse the data */ + krb5_kdb_decode_int16(tl_data.tl_data_contents, version); + if (version == 1) { + + /* variable size, must be at least 8 bytes */ + if (tl_data.tl_data_length < 8) + return (KRB5_KDB_TRUNCATED_RECORD); + + /* + * Find number of tuple entries, remembering to account for version + * field. + */ + num_actkvno = (tl_data.tl_data_length - sizeof(version)) / + ACTKVNO_TUPLE_SIZE; + prev_data = NULL; + /* next_tuple points to first tuple entry in the tl_data_contents */ + next_tuple = tl_data.tl_data_contents + sizeof(version); + for (i = 0; i < num_actkvno; i++) { + new_data = (krb5_actkvno_node *) malloc(sizeof(krb5_actkvno_node)); + if (new_data == NULL) { + krb5_dbe_free_actkvno_list(context, head_data); + return (ENOMEM); + } + memset(new_data, 0, sizeof(krb5_actkvno_node)); + + /* using tmp_kvno to avoid type mismatch */ + krb5_kdb_decode_int16(act_kvno(next_tuple), tmp_kvno); + new_data->act_kvno = (krb5_kvno) tmp_kvno; + krb5_kdb_decode_int32(act_time(next_tuple), new_data->act_time); + + if (prev_data != NULL) + prev_data->next = new_data; + else + head_data = new_data; + prev_data = new_data; + next_tuple += ACTKVNO_TUPLE_SIZE; + } + } else { + k5_setmsg(context, KRB5_KDB_BAD_VERSION, + _("Illegal version number for KRB5_TL_ACTKVNO %d\n"), + version); + return (KRB5_KDB_BAD_VERSION); + } + } + *actkvno_list = head_data; + return (0); +} + +/* + * Add KRB5_TL_ACTKVNO TL data entries to krb5_db_entry *entry + */ +#if KRB5_TL_ACTKVNO_VER == 1 +krb5_error_code +krb5_dbe_update_actkvno(krb5_context context, krb5_db_entry *entry, + const krb5_actkvno_node *actkvno_list) +{ + krb5_error_code retval = 0; + krb5_int16 version, tmp_kvno; + krb5_tl_data new_tl_data; + unsigned char *nextloc; + const krb5_actkvno_node *cur_actkvno; + krb5_octet *tmpptr; + + if (actkvno_list == NULL) + return EINVAL; + + memset(&new_tl_data, 0, sizeof(new_tl_data)); + /* allocate initial KRB5_TL_ACTKVNO tl_data entry */ + new_tl_data.tl_data_length = sizeof(version); + new_tl_data.tl_data_contents = (krb5_octet *) malloc(new_tl_data.tl_data_length); + if (new_tl_data.tl_data_contents == NULL) + return ENOMEM; + + /* add the current version # for the data format used for KRB5_TL_ACTKVNO */ + version = KRB5_TL_ACTKVNO_VER; + krb5_kdb_encode_int16(version, (unsigned char *) new_tl_data.tl_data_contents); + + for (cur_actkvno = actkvno_list; cur_actkvno != NULL; + cur_actkvno = cur_actkvno->next) { + + new_tl_data.tl_data_length += ACTKVNO_TUPLE_SIZE; + tmpptr = realloc(new_tl_data.tl_data_contents, new_tl_data.tl_data_length); + if (tmpptr == NULL) { + free(new_tl_data.tl_data_contents); + return ENOMEM; + } else { + new_tl_data.tl_data_contents = tmpptr; + } + + /* + * Using realloc so tl_data_contents is required to correctly calculate + * next location to store new tuple. + */ + nextloc = new_tl_data.tl_data_contents + new_tl_data.tl_data_length - ACTKVNO_TUPLE_SIZE; + /* using tmp_kvno to avoid type mismatch issues */ + tmp_kvno = (krb5_int16) cur_actkvno->act_kvno; + krb5_kdb_encode_int16(tmp_kvno, nextloc); + nextloc += sizeof(krb5_ui_2); + krb5_kdb_encode_int32((krb5_ui_4)cur_actkvno->act_time, nextloc); + } + + new_tl_data.tl_data_type = KRB5_TL_ACTKVNO; + retval = krb5_dbe_update_tl_data(context, entry, &new_tl_data); + free(new_tl_data.tl_data_contents); + + return (retval); +} +#endif /* KRB5_TL_ACTKVNO_VER == 1 */ + +krb5_error_code +krb5_dbe_update_last_pwd_change(krb5_context context, krb5_db_entry *entry, + krb5_timestamp stamp) +{ + krb5_tl_data tl_data; + krb5_octet buf[4]; /* this is the encoded size of an int32 */ + + tl_data.tl_data_type = KRB5_TL_LAST_PWD_CHANGE; + tl_data.tl_data_length = sizeof(buf); + krb5_kdb_encode_int32((krb5_int32) stamp, buf); + tl_data.tl_data_contents = buf; + + return (krb5_dbe_update_tl_data(context, entry, &tl_data)); +} + +krb5_error_code +krb5_dbe_update_last_admin_unlock(krb5_context context, krb5_db_entry *entry, + krb5_timestamp stamp) +{ + krb5_tl_data tl_data; + krb5_octet buf[4]; /* this is the encoded size of an int32 */ + + tl_data.tl_data_type = KRB5_TL_LAST_ADMIN_UNLOCK; + tl_data.tl_data_length = sizeof(buf); + krb5_kdb_encode_int32((krb5_int32) stamp, buf); + tl_data.tl_data_contents = buf; + + return (krb5_dbe_update_tl_data(context, entry, &tl_data)); +} + +/* + * Prepare to iterate over the string attributes of entry. The returned + * pointers are aliases into entry's tl_data (or into an empty string literal) + * and remain valid until the entry's tl_data is changed. + */ +static krb5_error_code +begin_attrs(krb5_context context, krb5_db_entry *entry, const char **pos_out, + const char **end_out) +{ + krb5_error_code code; + krb5_tl_data tl_data; + + *pos_out = *end_out = NULL; + tl_data.tl_data_type = KRB5_TL_STRING_ATTRS; + code = krb5_dbe_lookup_tl_data(context, entry, &tl_data); + if (code) + return code; + + /* Copy the current mapping to buf, updating key with value if found. */ + *pos_out = (const char *)tl_data.tl_data_contents; + *end_out = *pos_out + tl_data.tl_data_length; + return 0; +} + +/* Find the next key and value pair in *pos and update *pos. */ +static krb5_boolean +next_attr(const char **pos, const char *end, const char **key_out, + const char **val_out) +{ + const char *key, *key_end, *val, *val_end; + + *key_out = *val_out = NULL; + if (*pos == end) + return FALSE; + key = *pos; + key_end = memchr(key, '\0', end - key); + if (key_end == NULL) /* Malformed representation; give up. */ + return FALSE; + val = key_end + 1; + val_end = memchr(val, '\0', end - val); + if (val_end == NULL) /* Malformed representation; give up. */ + return FALSE; + + *key_out = key; + *val_out = val; + *pos = val_end + 1; + return TRUE; +} + +krb5_error_code +krb5_dbe_get_strings(krb5_context context, krb5_db_entry *entry, + krb5_string_attr **strings_out, int *count_out) +{ + krb5_error_code code; + const char *pos, *end, *mapkey, *mapval; + char *key = NULL, *val = NULL; + krb5_string_attr *strings = NULL, *newstrings; + int count = 0; + + *strings_out = NULL; + *count_out = 0; + code = begin_attrs(context, entry, &pos, &end); + if (code) + return code; + + while (next_attr(&pos, end, &mapkey, &mapval)) { + /* Add a copy of mapkey and mapvalue to strings. */ + newstrings = realloc(strings, (count + 1) * sizeof(*strings)); + if (newstrings == NULL) + goto oom; + strings = newstrings; + key = strdup(mapkey); + val = strdup(mapval); + if (key == NULL || val == NULL) + goto oom; + strings[count].key = key; + strings[count].value = val; + count++; + } + + *strings_out = strings; + *count_out = count; + return 0; + +oom: + free(key); + free(val); + krb5_dbe_free_strings(context, strings, count); + return ENOMEM; +} + +krb5_error_code +krb5_dbe_get_string(krb5_context context, krb5_db_entry *entry, + const char *key, char **value_out) +{ + krb5_error_code code; + const char *pos, *end, *mapkey, *mapval; + + *value_out = NULL; + code = begin_attrs(context, entry, &pos, &end); + if (code) + return code; + while (next_attr(&pos, end, &mapkey, &mapval)) { + if (strcmp(mapkey, key) == 0) { + *value_out = strdup(mapval); + return (*value_out == NULL) ? ENOMEM : 0; + } + } + + return 0; +} + +krb5_error_code +krb5_dbe_set_string(krb5_context context, krb5_db_entry *entry, + const char *key, const char *value) +{ + krb5_error_code code; + const char *pos, *end, *mapkey, *mapval; + struct k5buf buf = EMPTY_K5BUF; + krb5_boolean found = FALSE; + krb5_tl_data tl_data; + + /* Copy the current mapping to buf, updating key with value if found. */ + code = begin_attrs(context, entry, &pos, &end); + if (code) + return code; + k5_buf_init_dynamic(&buf); + while (next_attr(&pos, end, &mapkey, &mapval)) { + if (strcmp(mapkey, key) == 0) { + if (value != NULL) { + k5_buf_add_len(&buf, mapkey, strlen(mapkey) + 1); + k5_buf_add_len(&buf, value, strlen(value) + 1); + } + found = TRUE; + } else { + k5_buf_add_len(&buf, mapkey, strlen(mapkey) + 1); + k5_buf_add_len(&buf, mapval, strlen(mapval) + 1); + } + } + + /* If key wasn't found in the map, add a new entry for it. */ + if (!found && value != NULL) { + k5_buf_add_len(&buf, key, strlen(key) + 1); + k5_buf_add_len(&buf, value, strlen(value) + 1); + } + + if (k5_buf_status(&buf) != 0) + return ENOMEM; + if (buf.len > 65535) { + code = KRB5_KDB_STRINGS_TOOLONG; + goto cleanup; + } + tl_data.tl_data_type = KRB5_TL_STRING_ATTRS; + tl_data.tl_data_contents = buf.data; + tl_data.tl_data_length = buf.len; + + code = krb5_dbe_update_tl_data(context, entry, &tl_data); + +cleanup: + k5_buf_free(&buf); + return code; +} + +krb5_error_code +krb5_dbe_delete_tl_data(krb5_context context, krb5_db_entry *entry, + krb5_int16 tl_data_type) +{ + krb5_tl_data *tl_data, *prev_tl_data, *free_tl_data; + + /* + * Find existing entries of the specified type and remove them from the + * entry's tl_data list. + */ + + for (prev_tl_data = tl_data = entry->tl_data; tl_data != NULL;) { + if (tl_data->tl_data_type == tl_data_type) { + if (tl_data == entry->tl_data) { + /* remove from head */ + entry->tl_data = tl_data->tl_data_next; + prev_tl_data = entry->tl_data; + } else if (tl_data->tl_data_next == NULL) { + /* remove from tail */ + prev_tl_data->tl_data_next = NULL; + } else { + /* remove in between */ + prev_tl_data->tl_data_next = tl_data->tl_data_next; + } + free_tl_data = tl_data; + tl_data = tl_data->tl_data_next; + krb5_dbe_free_tl_data(context, free_tl_data); + entry->n_tl_data--; + } else { + prev_tl_data = tl_data; + tl_data = tl_data->tl_data_next; + } + } + + return (0); +} + +krb5_error_code +krb5_db_update_tl_data(krb5_context context, krb5_int16 *n_tl_datap, + krb5_tl_data **tl_datap, krb5_tl_data *new_tl_data) +{ + krb5_tl_data *tl_data = NULL; + krb5_octet *tmp; + + /* + * Copy the new data first, so we can fail cleanly if malloc() + * fails. + */ + tmp = malloc(new_tl_data->tl_data_length); + if (tmp == NULL) + return (ENOMEM); + + /* + * Find an existing entry of the specified type and point at + * it, or NULL if not found. + */ + + if (new_tl_data->tl_data_type != KRB5_TL_DB_ARGS) { /* db_args can be multiple */ + for (tl_data = *tl_datap; tl_data; + tl_data = tl_data->tl_data_next) + if (tl_data->tl_data_type == new_tl_data->tl_data_type) + break; + } + + /* If necessary, chain a new record in the beginning and point at it. */ + + if (!tl_data) { + tl_data = calloc(1, sizeof(*tl_data)); + if (tl_data == NULL) { + free(tmp); + return (ENOMEM); + } + tl_data->tl_data_next = *tl_datap; + *tl_datap = tl_data; + (*n_tl_datap)++; + } + + /* fill in the record */ + + free(tl_data->tl_data_contents); + + tl_data->tl_data_type = new_tl_data->tl_data_type; + tl_data->tl_data_length = new_tl_data->tl_data_length; + tl_data->tl_data_contents = tmp; + memcpy(tmp, new_tl_data->tl_data_contents, tl_data->tl_data_length); + + return (0); +} + +krb5_error_code +krb5_dbe_update_tl_data(krb5_context context, krb5_db_entry *entry, + krb5_tl_data *new_tl_data) +{ + return krb5_db_update_tl_data(context, &entry->n_tl_data, &entry->tl_data, + new_tl_data); +} + +krb5_error_code +krb5_dbe_compute_salt(krb5_context context, const krb5_key_data *key, + krb5_const_principal princ, krb5_int16 *salttype_out, + krb5_data **salt_out) +{ + krb5_error_code retval; + krb5_int16 stype; + krb5_data *salt, sdata; + + stype = (key->key_data_ver < 2) ? KRB5_KDB_SALTTYPE_NORMAL : + key->key_data_type[1]; + *salttype_out = stype; + *salt_out = NULL; + + /* Place computed salt into sdata, or directly into salt_out and return. */ + switch (stype) { + case KRB5_KDB_SALTTYPE_NORMAL: + retval = krb5_principal2salt(context, princ, &sdata); + if (retval) + return retval; + break; + case KRB5_KDB_SALTTYPE_V4: + sdata = empty_data(); + break; + case KRB5_KDB_SALTTYPE_NOREALM: + retval = krb5_principal2salt_norealm(context, princ, &sdata); + if (retval) + return retval; + break; + case KRB5_KDB_SALTTYPE_AFS3: + case KRB5_KDB_SALTTYPE_ONLYREALM: + return krb5_copy_data(context, &princ->realm, salt_out); + case KRB5_KDB_SALTTYPE_SPECIAL: + sdata = make_data(key->key_data_contents[1], key->key_data_length[1]); + return krb5_copy_data(context, &sdata, salt_out); + default: + return KRB5_KDB_BAD_SALTTYPE; + } + + /* Make a container for sdata. */ + salt = malloc(sizeof(*salt)); + if (salt == NULL) { + free(sdata.data); + return ENOMEM; + } + *salt = sdata; + *salt_out = salt; + return 0; +} + +krb5_error_code +krb5_dbe_specialize_salt(krb5_context context, krb5_db_entry *entry) +{ + krb5_int16 stype, i; + krb5_data *salt; + krb5_error_code ret; + + if (context == NULL || entry == NULL) + return EINVAL; + + /* + * Store salt values explicitly so that they don't depend on the principal + * name. + */ + for (i = 0; i < entry->n_key_data; i++) { + ret = krb5_dbe_compute_salt(context, &entry->key_data[i], entry->princ, + &stype, &salt); + if (ret) + return ret; + + /* Steal the data pointer from salt and free the container. */ + if (entry->key_data[i].key_data_ver >= 2) + free(entry->key_data[i].key_data_contents[1]); + entry->key_data[i].key_data_type[1] = KRB5_KDB_SALTTYPE_SPECIAL; + entry->key_data[i].key_data_contents[1] = (uint8_t *)salt->data; + entry->key_data[i].key_data_length[1] = salt->length; + entry->key_data[i].key_data_ver = 2; + free(salt); + } + + return 0; +} + +/* change password functions */ +krb5_error_code +krb5_dbe_cpw(krb5_context kcontext, krb5_keyblock *master_key, + krb5_key_salt_tuple *ks_tuple, int ks_tuple_count, char *passwd, + int new_kvno, krb5_boolean keepold, krb5_db_entry *db_entry) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + return v->change_pwd(kcontext, master_key, ks_tuple, ks_tuple_count, + passwd, new_kvno, keepold, db_entry); +} + +/* policy management functions */ +krb5_error_code +krb5_db_create_policy(krb5_context kcontext, osa_policy_ent_t policy) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->create_policy == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + + status = v->create_policy(kcontext, policy); + /* iprop does not support policy mods; force full resync. */ + if (!status && logging(kcontext)) + status = ulog_init_header(kcontext); + return status; +} + +krb5_error_code +krb5_db_get_policy(krb5_context kcontext, char *name, osa_policy_ent_t *policy) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->get_policy == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + return v->get_policy(kcontext, name, policy); +} + +krb5_error_code +krb5_db_put_policy(krb5_context kcontext, osa_policy_ent_t policy) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->put_policy == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + + status = v->put_policy(kcontext, policy); + /* iprop does not support policy mods; force full resync. */ + if (!status && logging(kcontext)) + status = ulog_init_header(kcontext); + return status; +} + +krb5_error_code +krb5_db_iter_policy(krb5_context kcontext, char *match_entry, + osa_adb_iter_policy_func func, void *data) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->iter_policy == NULL) + return 0; + return v->iter_policy(kcontext, match_entry, func, data); +} + +krb5_error_code +krb5_db_delete_policy(krb5_context kcontext, char *policy) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->delete_policy == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + + status = v->delete_policy(kcontext, policy); + /* iprop does not support policy mods; force full resync. */ + if (!status && logging(kcontext)) + status = ulog_init_header(kcontext); + return status; +} + +void +krb5_db_free_policy(krb5_context kcontext, osa_policy_ent_t policy) +{ + if (policy == NULL) + return; + free(policy->name); + free(policy->allowed_keysalts); + free_tl_data(policy->tl_data); + free(policy); +} + +krb5_error_code +krb5_db_promote(krb5_context kcontext, char **db_args) +{ + krb5_error_code status; + char *section; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->promote_db == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + status = get_conf_section(kcontext, §ion); + if (status) + return status; + status = v->promote_db(kcontext, section, db_args); + free(section); + return status; +} + +static krb5_error_code +decrypt_iterator(krb5_context kcontext, const krb5_key_data * key_data, + krb5_keyblock *dbkey, krb5_keysalt *keysalt) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + krb5_keylist_node *n = kcontext->dal_handle->master_keylist; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + for (; n; n = n->next) { + krb5_clear_error_message(kcontext); + status = v->decrypt_key_data(kcontext, &n->keyblock, key_data, dbkey, + keysalt); + if (status == 0) + return 0; + } + return status; +} + +krb5_error_code +krb5_dbe_decrypt_key_data(krb5_context kcontext, const krb5_keyblock *mkey, + const krb5_key_data *key_data, krb5_keyblock *dbkey, + krb5_keysalt *keysalt) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + krb5_keyblock *cur_mkey; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (mkey || kcontext->dal_handle->master_keylist == NULL) + return v->decrypt_key_data(kcontext, mkey, key_data, dbkey, keysalt); + status = decrypt_iterator(kcontext, key_data, dbkey, keysalt); + if (status == 0) + return 0; + if (kcontext->dal_handle->master_keylist) { + /* Try reloading master keys. */ + cur_mkey = &kcontext->dal_handle->master_keylist->keyblock; + if (krb5_db_fetch_mkey_list(kcontext, + kcontext->dal_handle->master_princ, + cur_mkey) == 0) + return decrypt_iterator(kcontext, key_data, dbkey, keysalt); + } + return status; +} + +krb5_error_code +krb5_dbe_encrypt_key_data(krb5_context kcontext, const krb5_keyblock *mkey, + const krb5_keyblock *dbkey, + const krb5_keysalt *keysalt, int keyver, + krb5_key_data *key_data) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + return v->encrypt_key_data(kcontext, mkey, dbkey, keysalt, keyver, + key_data); +} + +krb5_error_code +krb5_db_get_context(krb5_context context, void **db_context) +{ + *db_context = KRB5_DB_GET_DB_CONTEXT(context); + if (*db_context == NULL) + return KRB5_KDB_DBNOTINITED; + return 0; +} + +krb5_error_code +krb5_db_set_context(krb5_context context, void *db_context) +{ + KRB5_DB_GET_DB_CONTEXT(context) = db_context; + + return 0; +} + +krb5_error_code +krb5_db_sign_authdata(krb5_context kcontext, unsigned int flags, + krb5_const_principal client_princ, krb5_db_entry *client, + krb5_db_entry *server, krb5_db_entry *krbtgt, + krb5_keyblock *client_key, krb5_keyblock *server_key, + krb5_keyblock *krbtgt_key, krb5_keyblock *session_key, + krb5_timestamp authtime, krb5_authdata **tgt_auth_data, + krb5_authdata ***signed_auth_data) +{ + krb5_error_code status = 0; + kdb_vftabl *v; + + *signed_auth_data = NULL; + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->sign_authdata == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + return v->sign_authdata(kcontext, flags, client_princ, client, server, + krbtgt, client_key, server_key, krbtgt_key, + session_key, authtime, tgt_auth_data, + signed_auth_data); +} + +krb5_error_code +krb5_db_check_transited_realms(krb5_context kcontext, + const krb5_data *tr_contents, + const krb5_data *client_realm, + const krb5_data *server_realm) +{ + krb5_error_code status; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status) + return status; + if (v->check_transited_realms == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + return v->check_transited_realms(kcontext, tr_contents, client_realm, + server_realm); +} + +krb5_error_code +krb5_db_check_policy_as(krb5_context kcontext, krb5_kdc_req *request, + krb5_db_entry *client, krb5_db_entry *server, + krb5_timestamp kdc_time, const char **status, + krb5_pa_data ***e_data) +{ + krb5_error_code ret; + kdb_vftabl *v; + + *status = NULL; + *e_data = NULL; + ret = get_vftabl(kcontext, &v); + if (ret) + return ret; + if (v->check_policy_as == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + return v->check_policy_as(kcontext, request, client, server, kdc_time, + status, e_data); +} + +krb5_error_code +krb5_db_check_policy_tgs(krb5_context kcontext, krb5_kdc_req *request, + krb5_db_entry *server, krb5_ticket *ticket, + const char **status, krb5_pa_data ***e_data) +{ + krb5_error_code ret; + kdb_vftabl *v; + + *status = NULL; + *e_data = NULL; + ret = get_vftabl(kcontext, &v); + if (ret) + return ret; + if (v->check_policy_tgs == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + return v->check_policy_tgs(kcontext, request, server, ticket, status, + e_data); +} + +void +krb5_db_audit_as_req(krb5_context kcontext, krb5_kdc_req *request, + krb5_db_entry *client, krb5_db_entry *server, + krb5_timestamp authtime, krb5_error_code error_code) +{ + krb5_error_code status; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status || v->audit_as_req == NULL) + return; + v->audit_as_req(kcontext, request, client, server, authtime, error_code); +} + +void +krb5_db_refresh_config(krb5_context kcontext) +{ + krb5_error_code status; + kdb_vftabl *v; + + status = get_vftabl(kcontext, &v); + if (status || v->refresh_config == NULL) + return; + v->refresh_config(kcontext); +} + +krb5_error_code +krb5_db_check_allowed_to_delegate(krb5_context kcontext, + krb5_const_principal client, + const krb5_db_entry *server, + krb5_const_principal proxy) +{ + krb5_error_code ret; + kdb_vftabl *v; + + ret = get_vftabl(kcontext, &v); + if (ret) + return ret; + if (v->check_allowed_to_delegate == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + return v->check_allowed_to_delegate(kcontext, client, server, proxy); +} + +void +krb5_dbe_sort_key_data(krb5_key_data *key_data, size_t key_data_length) +{ + size_t i, j; + krb5_key_data tmp; + + /* Use insertion sort as a stable sort. */ + for (i = 1; i < key_data_length; i++) { + j = i; + while (j > 0 && + key_data[j - 1].key_data_kvno < key_data[j].key_data_kvno) { + tmp = key_data[j]; + key_data[j] = key_data[j - 1]; + key_data[j - 1] = tmp; + j--; + } + } +} |
