/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* * Copyright 2006, 2009, 2010 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 #include #include #include #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; } /* * XXX eventually this should be consolidated with krb5_free_key_data_contents * so there is only a single version. */ 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; char *value = NULL; *section = NULL; if (context->default_realm == NULL) { krb5_set_error_message(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, context->default_realm, /* under the realm name, database_module */ KDB_MODULE_POINTER, /* default value is the realm name itself */ context->default_realm, &value); if (status) return status; result = strdup(value); profile_release_string(value); if (result == NULL) return ENOMEM; *section = result; return 0; } static char * kdb_get_library_name(krb5_context kcontext) { krb5_error_code status = 0; char *result = NULL; char *value = NULL; char *lib = NULL; status = profile_get_string(kcontext->profile, /* realms */ KDB_REALM_SECTION, kcontext->default_realm, /* under the realm name, database_module */ KDB_MODULE_POINTER, /* default value is the realm name itself */ kcontext->default_realm, &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; } result = strdup(lib); clean_n_exit: profile_release_string(value); profile_release_string(lib); return result; } static void kdb_setup_opt_functions(db_library lib) { if (lib->vftabl.fetch_master_key == NULL) lib->vftabl.fetch_master_key = krb5_db_def_fetch_mkey; if (lib->vftabl.fetch_master_key_list == NULL) lib->vftabl.fetch_master_key_list = krb5_def_fetch_mkey_list; if (lib->vftabl.store_master_key_list == NULL) lib->vftabl.store_master_key_list = krb5_def_store_mkey_list; if (lib->vftabl.dbe_search_enctype == NULL) lib->vftabl.dbe_search_enctype = krb5_dbe_def_search_enctype; if (lib->vftabl.change_pwd == NULL) lib->vftabl.change_pwd = krb5_dbe_def_cpw; if (lib->vftabl.decrypt_key_data == NULL) lib->vftabl.decrypt_key_data = krb5_dbe_def_decrypt_key_data; if (lib->vftabl.encrypt_key_data == NULL) lib->vftabl.encrypt_key_data = krb5_dbe_def_encrypt_key_data; } #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) { krb5_set_error_message(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)); memcpy(&lib->vftabl, vftabl_addr, sizeof(kdb_vftabl)); kdb_setup_opt_functions(lib); 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))) { const char *err_str = krb5_get_error_message(kcontext, status); status = KRB5_KDB_DBTYPE_NOTFOUND; krb5_set_error_message(kcontext, status, _("Unable to find requested database type: %s"), err_str); krb5_free_error_message(kcontext, err_str); goto clean_n_exit; } if ((status = krb5int_get_plugin_dir_data (&(*lib)->dl_dir_handle, "kdb_function_table", &vftabl_addrs, &kcontext->err))) { const char *err_str = krb5_get_error_message(kcontext, status); status = KRB5_KDB_DBTYPE_INIT; krb5_set_error_message(kcontext, status, _("plugin symbol 'kdb_function_table' lookup " "failed: %s"), err_str); krb5_free_error_message(kcontext, err_str); goto clean_n_exit; } if (vftabl_addrs[0] == NULL) { /* No plugins! */ status = KRB5_KDB_DBTYPE_NOTFOUND; krb5_set_error_message(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; } memcpy(&(*lib)->vftabl, vftabl_addrs[0], sizeof(kdb_vftabl)); kdb_setup_opt_functions(*lib); 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; } library = kdb_get_library_name(kcontext); if (library == NULL) { status = KRB5_KDB_DBTYPE_NOTFOUND; 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; return v->get_principal(kcontext, search_for, flags, entry); } void krb5_db_free_principal(krb5_context kcontext, krb5_db_entry *entry) { krb5_error_code status = 0; kdb_vftabl *v; status = get_vftabl(kcontext, &v); if (status) return; v->free_principal(kcontext, entry); } static void free_db_args(krb5_context kcontext, char **db_args) { int i; if (db_args) { /* XXX Is this right? Or are we borrowing storage from the caller? */ for (i = 0; db_args[i]; i++) krb5_db_free(kcontext, 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)--; krb5_db_free(kcontext, 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(kcontext, 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(kcontext, db_args); return status; } krb5_error_code krb5_db_put_principal(krb5_context kcontext, krb5_db_entry *entry) { krb5_error_code status = 0; kdb_vftabl *v; char **db_args = NULL; kdb_incr_update_t *upd = NULL; char *princ_name = NULL; int ulog_locked = 0; 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) goto cleanup; if (logging(kcontext)) { upd = k5alloc(sizeof(*upd), &status); if (upd == NULL) goto cleanup; if ((status = ulog_conv_2logentry(kcontext, entry, upd))) goto cleanup; status = ulog_lock(kcontext, KRB5_LOCKMODE_EXCLUSIVE); if (status != 0) goto cleanup; ulog_locked = 1; 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); if ((status = ulog_add_update(kcontext, upd)) != 0) goto cleanup; } status = v->put_principal(kcontext, entry, db_args); if (status == 0 && ulog_locked) (void) ulog_finish_update(kcontext, upd); cleanup: if (ulog_locked) ulog_lock(kcontext, KRB5_LOCKMODE_UNLOCK); free_db_args(kcontext, db_args); 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_vftabl *v; kdb_incr_update_t upd; char *princ_name = NULL; int ulog_locked = 0; status = get_vftabl(kcontext, &v); if (status) return status; if (v->delete_principal == NULL) return KRB5_PLUGIN_OP_NOTSUPP; if (logging(kcontext)) { status = ulog_lock(kcontext, KRB5_LOCKMODE_EXCLUSIVE); if (status) return status; ulog_locked = 1; status = krb5_unparse_name(kcontext, search_for, &princ_name); if (status) goto cleanup; (void) 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); if (status) goto cleanup; } status = v->delete_principal(kcontext, search_for); if (status == 0 && ulog_locked) (void) ulog_finish_update(kcontext, &upd); cleanup: if (ulog_locked) ulog_lock(kcontext, KRB5_LOCKMODE_UNLOCK); free(princ_name); return status; } krb5_error_code krb5_db_iterate(krb5_context kcontext, char *match_entry, int (*func)(krb5_pointer, krb5_db_entry *), krb5_pointer func_arg) { krb5_error_code status = 0; kdb_vftabl *v; status = get_vftabl(kcontext, &v); if (status) return status; if (v->iterate == NULL) return KRB5_PLUGIN_OP_NOTSUPP; return v->iterate(kcontext, match_entry, func, func_arg); } /* 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: if (tmp_key.contents) { zap(tmp_key.contents, tmp_key.length); krb5_db_free(context, tmp_key.contents); } 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) { krb5_error_code status; kdb_vftabl *v; status = get_vftabl(kcontext, &v); if (status) return NULL; return v->alloc(kcontext, ptr, size); } void krb5_db_free(krb5_context kcontext, void *ptr) { krb5_error_code status; kdb_vftabl *v; status = get_vftabl(kcontext, &v); if (status) return; v->free(kcontext, 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) { if ((entry->key_data = (krb5_key_data *) krb5_db_alloc(context, entry->key_data, (sizeof(krb5_key_data) * (entry->n_key_data + 1)))) == NULL) return (ENOMEM); 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 { krb5_set_error_message(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_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]; } } return (krb5_dbe_update_tl_data(context, entry, &tl_data)); } #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 { krb5_set_error_message(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; krb5_boolean found = FALSE; krb5_tl_data tl_data; ssize_t len; /* 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); } len = k5_buf_len(&buf); if (len == -1) return ENOMEM; if (len > 65535) return KRB5_KDB_STRINGS_TOOLONG; tl_data.tl_data_type = KRB5_TL_STRING_ATTRS; tl_data.tl_data_contents = (krb5_octet *)k5_buf_data(&buf); tl_data.tl_data_length = len; code = krb5_dbe_update_tl_data(context, entry, &tl_data); k5_free_buf(&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. */ if ((tmp = (krb5_octet *) krb5_db_alloc(context, NULL, new_tl_data->tl_data_length)) == 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 = krb5_db_alloc(context, NULL, sizeof(krb5_tl_data)); if (tl_data == NULL) { free(tmp); return (ENOMEM); } memset(tl_data, 0, sizeof(krb5_tl_data)); tl_data->tl_data_next = *tl_datap; *tl_datap = tl_data; (*n_tl_datap)++; } /* fill in the record */ if (tl_data->tl_data_contents) krb5_db_free(context, 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; } /* 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; int ulog_locked = 0; status = get_vftabl(kcontext, &v); if (status) return status; if (v->create_policy == NULL) return KRB5_PLUGIN_OP_NOTSUPP; if (logging(kcontext)) { status = ulog_lock(kcontext, KRB5_LOCKMODE_EXCLUSIVE); if (status != 0) return status; ulog_locked = 1; } status = v->create_policy(kcontext, policy); /* iprop does not support policy mods; force full resync. */ if (!status && ulog_locked) ulog_init_header(kcontext); if (ulog_locked) ulog_lock(kcontext, KRB5_LOCKMODE_UNLOCK); 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; int ulog_locked = 0; status = get_vftabl(kcontext, &v); if (status) return status; if (v->put_policy == NULL) return KRB5_PLUGIN_OP_NOTSUPP; if (logging(kcontext)) { status = ulog_lock(kcontext, KRB5_LOCKMODE_EXCLUSIVE); if (status) return status; ulog_locked = 1; } status = v->put_policy(kcontext, policy); /* iprop does not support policy mods; force full resync. */ if (!status && ulog_locked) ulog_init_header(kcontext); if (ulog_locked) ulog_lock(kcontext, KRB5_LOCKMODE_UNLOCK); 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; int ulog_locked = 0; status = get_vftabl(kcontext, &v); if (status) return status; if (v->delete_policy == NULL) return KRB5_PLUGIN_OP_NOTSUPP; if (logging(kcontext)) { status = ulog_lock(kcontext, KRB5_LOCKMODE_EXCLUSIVE); if (status) return status; ulog_locked = 1; } status = v->delete_policy(kcontext, policy); /* iprop does not support policy mods; force full resync. */ if (!status && ulog_locked) ulog_init_header(kcontext); if (ulog_locked) ulog_lock(kcontext, KRB5_LOCKMODE_UNLOCK); return status; } void krb5_db_free_policy(krb5_context kcontext, osa_policy_ent_t policy) { krb5_error_code status = 0; kdb_vftabl *v; status = get_vftabl(kcontext, &v); if (status || v->free_policy == NULL) return; v->free_policy(kcontext, 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); }