Path: blob/main/crypto/krb5/src/lib/kadm5/srv/svr_principal.c
39566 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/*2* Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved3*4* $Header$5*/6#include "k5-int.h"7#include <sys/time.h>8#include <kadm5/admin.h>9#include <kdb.h>10#include "server_internal.h"1112#include <krb5/kadm5_hook_plugin.h>1314#ifdef USE_VALGRIND15#include <valgrind/memcheck.h>16#else17#define VALGRIND_CHECK_DEFINED(LVALUE) ((void)0)18#endif1920extern krb5_principal master_princ;21extern krb5_principal hist_princ;22extern krb5_keyblock master_keyblock;23extern krb5_db_entry master_db;2425static int decrypt_key_data(krb5_context context,26int n_key_data, krb5_key_data *key_data,27krb5_keyblock **keyblocks, int *n_keys);2829/*30* XXX Functions that ought to be in libkrb5.a, but aren't.31*/32kadm5_ret_t33krb5_copy_key_data_contents(krb5_context context, krb5_key_data *from,34krb5_key_data *to)35{36int i, idx;3738*to = *from;3940idx = (from->key_data_ver == 1 ? 1 : 2);4142for (i = 0; i < idx; i++) {43if ( from->key_data_length[i] ) {44to->key_data_contents[i] = malloc(from->key_data_length[i]);45if (to->key_data_contents[i] == NULL) {46for (i = 0; i < idx; i++)47zapfree(to->key_data_contents[i], to->key_data_length[i]);48return ENOMEM;49}50memcpy(to->key_data_contents[i], from->key_data_contents[i],51from->key_data_length[i]);52}53}54return 0;55}5657static krb5_tl_data *dup_tl_data(krb5_tl_data *tl)58{59krb5_tl_data *n;6061n = (krb5_tl_data *) malloc(sizeof(krb5_tl_data));62if (n == NULL)63return NULL;64n->tl_data_contents = malloc(tl->tl_data_length);65if (n->tl_data_contents == NULL) {66free(n);67return NULL;68}69memcpy(n->tl_data_contents, tl->tl_data_contents, tl->tl_data_length);70n->tl_data_type = tl->tl_data_type;71n->tl_data_length = tl->tl_data_length;72n->tl_data_next = NULL;73return n;74}7576/* This is in lib/kdb/kdb_cpw.c, but is static */77static void78cleanup_key_data(krb5_context context, int count, krb5_key_data *data)79{80int i;8182for (i = 0; i < count; i++)83krb5_free_key_data_contents(context, &data[i]);84free(data);85}8687/* Check whether a ks_tuple is present in an array of ks_tuples. */88static krb5_boolean89ks_tuple_present(int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,90krb5_key_salt_tuple *looking_for)91{92int i;9394for (i = 0; i < n_ks_tuple; i++) {95if (ks_tuple[i].ks_enctype == looking_for->ks_enctype &&96ks_tuple[i].ks_salttype == looking_for->ks_salttype)97return TRUE;98}99return FALSE;100}101102/* Fetch a policy if it exists; set *have_pol_out appropriately. Return103* success whether or not the policy exists. */104static kadm5_ret_t105get_policy(kadm5_server_handle_t handle, const char *name,106kadm5_policy_ent_t policy_out, krb5_boolean *have_pol_out)107{108kadm5_ret_t ret;109110*have_pol_out = FALSE;111if (name == NULL)112return 0;113ret = kadm5_get_policy(handle->lhandle, (char *)name, policy_out);114if (ret == 0)115*have_pol_out = TRUE;116return (ret == KADM5_UNK_POLICY) ? 0 : ret;117}118119/*120* Apply the -allowedkeysalts policy (see kadmin(1)'s addpol/modpol121* commands). We use the allowed key/salt tuple list as a default if122* no ks tuples as provided by the caller. We reject lists that include123* key/salts outside the policy. We re-order the requested ks tuples124* (which may be a subset of the policy) to reflect the policy order.125*/126static kadm5_ret_t127apply_keysalt_policy(kadm5_server_handle_t handle, const char *policy,128int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,129int *new_n_kstp, krb5_key_salt_tuple **new_kstp)130{131kadm5_ret_t ret;132kadm5_policy_ent_rec polent;133krb5_boolean have_polent;134int ak_n_ks_tuple = 0;135int new_n_ks_tuple = 0;136krb5_key_salt_tuple *ak_ks_tuple = NULL;137krb5_key_salt_tuple *new_ks_tuple = NULL;138krb5_key_salt_tuple *subset;139int i, m;140141if (new_n_kstp != NULL) {142*new_n_kstp = 0;143*new_kstp = NULL;144}145146memset(&polent, 0, sizeof(polent));147ret = get_policy(handle, policy, &polent, &have_polent);148if (ret)149goto cleanup;150151if (polent.allowed_keysalts == NULL) {152/* Requested keysalts allowed or default to supported_enctypes. */153if (n_ks_tuple == 0) {154/* Default to supported_enctypes. */155n_ks_tuple = handle->params.num_keysalts;156ks_tuple = handle->params.keysalts;157}158/* Dup the requested or defaulted keysalt tuples. */159new_ks_tuple = malloc(n_ks_tuple * sizeof(*new_ks_tuple));160if (new_ks_tuple == NULL) {161ret = ENOMEM;162goto cleanup;163}164memcpy(new_ks_tuple, ks_tuple, n_ks_tuple * sizeof(*new_ks_tuple));165new_n_ks_tuple = n_ks_tuple;166ret = 0;167goto cleanup;168}169170ret = krb5_string_to_keysalts(polent.allowed_keysalts,171",", /* Tuple separators */172NULL, /* Key/salt separators */1730, /* No duplicates */174&ak_ks_tuple,175&ak_n_ks_tuple);176/*177* Malformed policy? Shouldn't happen, but it's remotely possible178* someday, so we don't assert, just bail.179*/180if (ret)181goto cleanup;182183/* Check that the requested ks_tuples are within policy, if we have one. */184for (i = 0; i < n_ks_tuple; i++) {185if (!ks_tuple_present(ak_n_ks_tuple, ak_ks_tuple, &ks_tuple[i])) {186ret = KADM5_BAD_KEYSALTS;187goto cleanup;188}189}190191/* Have policy but no ks_tuple input? Output the policy. */192if (n_ks_tuple == 0) {193new_n_ks_tuple = ak_n_ks_tuple;194new_ks_tuple = ak_ks_tuple;195ak_ks_tuple = NULL;196goto cleanup;197}198199/*200* Now filter the policy ks tuples by the requested ones so as to201* preserve in the requested sub-set the relative ordering from the202* policy. We could optimize this (if (n_ks_tuple == ak_n_ks_tuple)203* then skip this), but we don't bother.204*/205subset = calloc(n_ks_tuple, sizeof(*subset));206if (subset == NULL) {207ret = ENOMEM;208goto cleanup;209}210for (m = 0, i = 0; i < ak_n_ks_tuple && m < n_ks_tuple; i++) {211if (ks_tuple_present(n_ks_tuple, ks_tuple, &ak_ks_tuple[i]))212subset[m++] = ak_ks_tuple[i];213}214new_ks_tuple = subset;215new_n_ks_tuple = m;216ret = 0;217218cleanup:219if (have_polent)220kadm5_free_policy_ent(handle->lhandle, &polent);221free(ak_ks_tuple);222223if (new_n_kstp != NULL) {224*new_n_kstp = new_n_ks_tuple;225*new_kstp = new_ks_tuple;226} else {227free(new_ks_tuple);228}229return ret;230}231232233/*234* Set *passptr to NULL if the request looks like the first part of a krb5 1.6235* addprinc -randkey operation. The krb5 1.6 dummy password for these requests236* was invalid UTF-8, which runs afoul of the arcfour string-to-key.237*/238static void239check_1_6_dummy(kadm5_principal_ent_t entry, long mask,240int n_ks_tuple, krb5_key_salt_tuple *ks_tuple, char **passptr)241{242int i;243char *password = *passptr;244245/* Old-style randkey operations disallowed tickets to start. */246if (password == NULL || !(mask & KADM5_ATTRIBUTES) ||247!(entry->attributes & KRB5_KDB_DISALLOW_ALL_TIX))248return;249250/* The 1.6 dummy password was the octets 1..255. */251for (i = 0; (unsigned char) password[i] == i + 1; i++);252if (password[i] != '\0' || i != 255)253return;254255/* This will make the caller use a random password instead. */256*passptr = NULL;257}258259/* Return the number of keys with the newest kvno. Assumes that all key data260* with the newest kvno are at the front of the key data array. */261static int262count_new_keys(int n_key_data, krb5_key_data *key_data)263{264int n;265266for (n = 1; n < n_key_data; n++) {267if (key_data[n - 1].key_data_kvno != key_data[n].key_data_kvno)268return n;269}270return n_key_data;271}272273kadm5_ret_t274kadm5_create_principal(void *server_handle,275kadm5_principal_ent_t entry, long mask,276char *password)277{278return279kadm5_create_principal_3(server_handle, entry, mask,2800, NULL, password);281}282kadm5_ret_t283kadm5_create_principal_3(void *server_handle,284kadm5_principal_ent_t entry, long mask,285int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,286char *password)287{288krb5_db_entry *kdb;289osa_princ_ent_rec adb;290kadm5_policy_ent_rec polent;291krb5_boolean have_polent = FALSE;292krb5_timestamp now;293krb5_tl_data *tl_data_tail;294unsigned int ret;295kadm5_server_handle_t handle = server_handle;296krb5_keyblock *act_mkey;297krb5_kvno act_kvno;298int new_n_ks_tuple = 0, i;299krb5_key_salt_tuple *new_ks_tuple = NULL;300301CHECK_HANDLE(server_handle);302303krb5_clear_error_message(handle->context);304305check_1_6_dummy(entry, mask, n_ks_tuple, ks_tuple, &password);306307/*308* Argument sanity checking, and opening up the DB309*/310if (entry == NULL)311return EINVAL;312if(!(mask & KADM5_PRINCIPAL) || (mask & KADM5_MOD_NAME) ||313(mask & KADM5_MOD_TIME) || (mask & KADM5_LAST_PWD_CHANGE) ||314(mask & KADM5_MKVNO) || (mask & KADM5_AUX_ATTRIBUTES) ||315(mask & KADM5_LAST_SUCCESS) || (mask & KADM5_LAST_FAILED) ||316(mask & KADM5_FAIL_AUTH_COUNT))317return KADM5_BAD_MASK;318if ((mask & KADM5_KEY_DATA) && entry->n_key_data != 0)319return KADM5_BAD_MASK;320if((mask & KADM5_POLICY) && entry->policy == NULL)321return KADM5_BAD_MASK;322if((mask & KADM5_POLICY) && (mask & KADM5_POLICY_CLR))323return KADM5_BAD_MASK;324if((mask & ~ALL_PRINC_MASK))325return KADM5_BAD_MASK;326if (mask & KADM5_TL_DATA) {327for (tl_data_tail = entry->tl_data; tl_data_tail != NULL;328tl_data_tail = tl_data_tail->tl_data_next) {329if (tl_data_tail->tl_data_type < 256)330return KADM5_BAD_TL_TYPE;331}332}333334/*335* Check to see if the principal exists336*/337ret = kdb_get_entry(handle, entry->principal, &kdb, &adb);338339switch(ret) {340case KADM5_UNK_PRINC:341break;342case 0:343kdb_free_entry(handle, kdb, &adb);344return KADM5_DUP;345default:346return ret;347}348349kdb = calloc(1, sizeof(*kdb));350if (kdb == NULL)351return ENOMEM;352353/* In all cases the principal entry is new and key data is set; let the354* database provider know. */355kdb->mask = mask | KADM5_KEY_DATA | KADM5_PRINCIPAL;356357memset(&adb, 0, sizeof(osa_princ_ent_rec));358359/*360* If a policy was specified, load it.361* If we can not find the one specified return an error362*/363if ((mask & KADM5_POLICY)) {364ret = get_policy(handle, entry->policy, &polent, &have_polent);365if (ret)366goto cleanup;367}368if (password) {369ret = passwd_check(handle, password, have_polent ? &polent : NULL,370entry->principal);371if (ret)372goto cleanup;373}374/*375* Start populating the various DB fields, using the376* "defaults" for fields that were not specified by the377* mask.378*/379if ((ret = krb5_timeofday(handle->context, &now)))380goto cleanup;381382kdb->magic = KRB5_KDB_MAGIC_NUMBER;383kdb->len = KRB5_KDB_V1_BASE_LENGTH; /* gag me with a chainsaw */384385if ((mask & KADM5_ATTRIBUTES))386kdb->attributes = entry->attributes;387else388kdb->attributes = handle->params.flags;389390if ((mask & KADM5_MAX_LIFE))391kdb->max_life = entry->max_life;392else393kdb->max_life = handle->params.max_life;394395if (mask & KADM5_MAX_RLIFE)396kdb->max_renewable_life = entry->max_renewable_life;397else398kdb->max_renewable_life = handle->params.max_rlife;399400if ((mask & KADM5_PRINC_EXPIRE_TIME))401kdb->expiration = entry->princ_expire_time;402else403kdb->expiration = handle->params.expiration;404405kdb->pw_expiration = 0;406if (mask & KADM5_PW_EXPIRATION) {407kdb->pw_expiration = entry->pw_expiration;408} else if (have_polent && polent.pw_max_life) {409kdb->mask |= KADM5_PW_EXPIRATION;410kdb->pw_expiration = ts_incr(now, polent.pw_max_life);411}412413kdb->last_success = 0;414kdb->last_failed = 0;415kdb->fail_auth_count = 0;416417/* this is kind of gross, but in order to free the tl data, I need418to free the entire kdb entry, and that will try to free the419principal. */420421ret = krb5_copy_principal(handle->context, entry->principal, &kdb->princ);422if (ret)423goto cleanup;424425if ((ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now)))426goto cleanup;427428if (mask & KADM5_TL_DATA) {429/* splice entry->tl_data onto the front of kdb->tl_data */430for (tl_data_tail = entry->tl_data; tl_data_tail;431tl_data_tail = tl_data_tail->tl_data_next)432{433ret = krb5_dbe_update_tl_data(handle->context, kdb, tl_data_tail);434if( ret )435goto cleanup;436}437}438439/*440* We need to have setup the TL data, so we have strings, so we can441* check enctype policy, which is why we check/initialize ks_tuple442* this late.443*/444ret = apply_keysalt_policy(handle, entry->policy, n_ks_tuple, ks_tuple,445&new_n_ks_tuple, &new_ks_tuple);446if (ret)447goto cleanup;448449/* initialize the keys */450451ret = kdb_get_active_mkey(handle, &act_kvno, &act_mkey);452if (ret)453goto cleanup;454455if (mask & KADM5_KEY_DATA) {456/* The client requested no keys for this principal. */457assert(entry->n_key_data == 0);458} else if (password) {459ret = krb5_dbe_cpw(handle->context, act_mkey, new_ks_tuple,460new_n_ks_tuple, password,461(mask & KADM5_KVNO)?entry->kvno:1,462FALSE, kdb);463} else {464/* Null password means create with random key (new in 1.8). */465ret = krb5_dbe_crk(handle->context, &master_keyblock,466new_ks_tuple, new_n_ks_tuple, FALSE, kdb);467if (mask & KADM5_KVNO) {468for (i = 0; i < kdb->n_key_data; i++)469kdb->key_data[i].key_data_kvno = entry->kvno;470}471}472if (ret)473goto cleanup;474475/* Record the master key VNO used to encrypt this entry's keys */476ret = krb5_dbe_update_mkvno(handle->context, kdb, act_kvno);477if (ret)478goto cleanup;479480ret = k5_kadm5_hook_create(handle->context, handle->hook_handles,481KADM5_HOOK_STAGE_PRECOMMIT, entry, mask,482new_n_ks_tuple, new_ks_tuple, password);483if (ret)484goto cleanup;485486/* populate the admin-server-specific fields. In the OV server,487this used to be in a separate database. Since there's already488marshalling code for the admin fields, to keep things simple,489I'm going to keep it, and make all the admin stuff occupy a490single tl_data record, */491492adb.admin_history_kvno = INITIAL_HIST_KVNO;493if (mask & KADM5_POLICY) {494adb.aux_attributes = KADM5_POLICY;495496/* this does *not* need to be strdup'ed, because adb is xdr */497/* encoded in osa_adb_create_princ, and not ever freed */498499adb.policy = entry->policy;500}501502/* store the new db entry */503ret = kdb_put_entry(handle, kdb, &adb);504505(void) k5_kadm5_hook_create(handle->context, handle->hook_handles,506KADM5_HOOK_STAGE_POSTCOMMIT, entry, mask,507new_n_ks_tuple, new_ks_tuple, password);508509cleanup:510free(new_ks_tuple);511krb5_db_free_principal(handle->context, kdb);512if (have_polent)513(void) kadm5_free_policy_ent(handle->lhandle, &polent);514return ret;515}516517518kadm5_ret_t519kadm5_delete_principal(void *server_handle, krb5_principal principal)520{521unsigned int ret;522kadm5_server_handle_t handle = server_handle;523524CHECK_HANDLE(server_handle);525526krb5_clear_error_message(handle->context);527528if (principal == NULL)529return EINVAL;530531/* Deleting K/M is mostly unrecoverable, so don't allow it. */532if (krb5_principal_compare(handle->context, principal, master_princ))533return KADM5_PROTECT_PRINCIPAL;534535ret = k5_kadm5_hook_remove(handle->context, handle->hook_handles,536KADM5_HOOK_STAGE_PRECOMMIT, principal);537if (ret)538return ret;539540ret = kdb_delete_entry(handle, principal);541542if (ret == 0)543(void) k5_kadm5_hook_remove(handle->context,544handle->hook_handles,545KADM5_HOOK_STAGE_POSTCOMMIT, principal);546547return ret;548}549550kadm5_ret_t551kadm5_modify_principal(void *server_handle,552kadm5_principal_ent_t entry, long mask)553{554int ret, ret2, i;555kadm5_policy_ent_rec pol;556krb5_boolean have_pol = FALSE;557krb5_db_entry *kdb;558krb5_tl_data *tl_data_orig;559osa_princ_ent_rec adb;560kadm5_server_handle_t handle = server_handle;561562CHECK_HANDLE(server_handle);563564krb5_clear_error_message(handle->context);565566if(entry == NULL)567return EINVAL;568if((mask & KADM5_PRINCIPAL) || (mask & KADM5_LAST_PWD_CHANGE) ||569(mask & KADM5_MOD_TIME) || (mask & KADM5_MOD_NAME) ||570(mask & KADM5_MKVNO) || (mask & KADM5_AUX_ATTRIBUTES) ||571(mask & KADM5_KEY_DATA) || (mask & KADM5_LAST_SUCCESS) ||572(mask & KADM5_LAST_FAILED))573return KADM5_BAD_MASK;574if((mask & ~ALL_PRINC_MASK))575return KADM5_BAD_MASK;576if((mask & KADM5_POLICY) && entry->policy == NULL)577return KADM5_BAD_MASK;578if((mask & KADM5_POLICY) && (mask & KADM5_POLICY_CLR))579return KADM5_BAD_MASK;580if (mask & KADM5_TL_DATA) {581tl_data_orig = entry->tl_data;582while (tl_data_orig) {583if (tl_data_orig->tl_data_type < 256)584return KADM5_BAD_TL_TYPE;585tl_data_orig = tl_data_orig->tl_data_next;586}587}588589ret = kdb_get_entry(handle, entry->principal, &kdb, &adb);590if (ret)591return(ret);592593/* Let the mask propagate to the database provider. */594kdb->mask = mask;595596/*597* This is pretty much the same as create ...598*/599600if ((mask & KADM5_POLICY)) {601ret = get_policy(handle, entry->policy, &pol, &have_pol);602if (ret)603goto done;604605/* set us up to use the new policy */606adb.aux_attributes |= KADM5_POLICY;607if (adb.policy)608free(adb.policy);609adb.policy = strdup(entry->policy);610}611612if (mask & KADM5_PW_EXPIRATION) {613kdb->pw_expiration = entry->pw_expiration;614} else if (have_pol) {615/* set pw_max_life based on new policy */616kdb->mask |= KADM5_PW_EXPIRATION;617if (pol.pw_max_life) {618ret = krb5_dbe_lookup_last_pwd_change(handle->context, kdb,619&kdb->pw_expiration);620if (ret)621goto done;622kdb->pw_expiration = ts_incr(kdb->pw_expiration, pol.pw_max_life);623} else {624kdb->pw_expiration = 0;625}626}627628if ((mask & KADM5_POLICY_CLR) && (adb.aux_attributes & KADM5_POLICY)) {629free(adb.policy);630adb.policy = NULL;631adb.aux_attributes &= ~KADM5_POLICY;632kdb->pw_expiration = 0;633}634635if ((mask & KADM5_ATTRIBUTES))636kdb->attributes = entry->attributes;637if ((mask & KADM5_MAX_LIFE))638kdb->max_life = entry->max_life;639if ((mask & KADM5_PRINC_EXPIRE_TIME))640kdb->expiration = entry->princ_expire_time;641if (mask & KADM5_MAX_RLIFE)642kdb->max_renewable_life = entry->max_renewable_life;643644if((mask & KADM5_KVNO)) {645for (i = 0; i < kdb->n_key_data; i++)646kdb->key_data[i].key_data_kvno = entry->kvno;647}648649if (mask & KADM5_TL_DATA) {650krb5_tl_data *tl;651652/* may have to change the version number of the API. Updates the list with the given tl_data rather than over-writing */653654for (tl = entry->tl_data; tl;655tl = tl->tl_data_next)656{657ret = krb5_dbe_update_tl_data(handle->context, kdb, tl);658if( ret )659{660goto done;661}662}663}664665/*666* Setting entry->fail_auth_count to 0 can be used to manually unlock667* an account. It is not possible to set fail_auth_count to any other668* value using kadmin.669*/670if (mask & KADM5_FAIL_AUTH_COUNT) {671if (entry->fail_auth_count != 0) {672ret = KADM5_BAD_SERVER_PARAMS;673goto done;674}675676kdb->fail_auth_count = 0;677}678679ret = k5_kadm5_hook_modify(handle->context, handle->hook_handles,680KADM5_HOOK_STAGE_PRECOMMIT, entry, mask);681if (ret)682goto done;683684ret = kdb_put_entry(handle, kdb, &adb);685if (ret) goto done;686(void) k5_kadm5_hook_modify(handle->context, handle->hook_handles,687KADM5_HOOK_STAGE_POSTCOMMIT, entry, mask);688689ret = KADM5_OK;690done:691if (have_pol) {692ret2 = kadm5_free_policy_ent(handle->lhandle, &pol);693ret = ret ? ret : ret2;694}695kdb_free_entry(handle, kdb, &adb);696return ret;697}698699kadm5_ret_t700kadm5_rename_principal(void *server_handle,701krb5_principal source, krb5_principal target)702{703krb5_db_entry *kdb;704osa_princ_ent_rec adb;705krb5_error_code ret;706kadm5_server_handle_t handle = server_handle;707708CHECK_HANDLE(server_handle);709710krb5_clear_error_message(handle->context);711712if (source == NULL || target == NULL)713return EINVAL;714715if ((ret = kdb_get_entry(handle, target, &kdb, &adb)) == 0) {716kdb_free_entry(handle, kdb, &adb);717return(KADM5_DUP);718}719720ret = k5_kadm5_hook_rename(handle->context, handle->hook_handles,721KADM5_HOOK_STAGE_PRECOMMIT, source, target);722if (ret)723return ret;724725ret = krb5_db_rename_principal(handle->context, source, target);726if (ret)727return ret;728729/* Update the principal mod data. */730ret = kdb_get_entry(handle, target, &kdb, &adb);731if (ret)732return ret;733kdb->mask = 0;734ret = kdb_put_entry(handle, kdb, &adb);735kdb_free_entry(handle, kdb, &adb);736if (ret)737return ret;738739(void) k5_kadm5_hook_rename(handle->context, handle->hook_handles,740KADM5_HOOK_STAGE_POSTCOMMIT, source, target);741return 0;742}743744kadm5_ret_t745kadm5_get_principal(void *server_handle, krb5_principal principal,746kadm5_principal_ent_t entry,747long in_mask)748{749krb5_db_entry *kdb;750osa_princ_ent_rec adb;751krb5_error_code ret = 0;752long mask;753int i;754kadm5_server_handle_t handle = server_handle;755756CHECK_HANDLE(server_handle);757758krb5_clear_error_message(handle->context);759760/*761* In version 1, all the defined fields are always returned.762* entry is a pointer to a kadm5_principal_ent_t_v1 that should be763* filled with allocated memory.764*/765mask = in_mask;766767memset(entry, 0, sizeof(*entry));768769if (principal == NULL)770return EINVAL;771772if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))773return ret;774775if ((mask & KADM5_POLICY) &&776adb.policy && (adb.aux_attributes & KADM5_POLICY)) {777if ((entry->policy = strdup(adb.policy)) == NULL) {778ret = ENOMEM;779goto done;780}781}782783if (mask & KADM5_AUX_ATTRIBUTES)784entry->aux_attributes = adb.aux_attributes;785786if ((mask & KADM5_PRINCIPAL) &&787(ret = krb5_copy_principal(handle->context, kdb->princ,788&entry->principal))) {789goto done;790}791792if (mask & KADM5_PRINC_EXPIRE_TIME)793entry->princ_expire_time = kdb->expiration;794795if ((mask & KADM5_LAST_PWD_CHANGE) &&796(ret = krb5_dbe_lookup_last_pwd_change(handle->context, kdb,797&(entry->last_pwd_change)))) {798goto done;799}800801if (mask & KADM5_PW_EXPIRATION)802entry->pw_expiration = kdb->pw_expiration;803if (mask & KADM5_MAX_LIFE)804entry->max_life = kdb->max_life;805806/* this is a little non-sensical because the function returns two */807/* values that must be checked separately against the mask */808if ((mask & KADM5_MOD_NAME) || (mask & KADM5_MOD_TIME)) {809ret = krb5_dbe_lookup_mod_princ_data(handle->context, kdb,810&(entry->mod_date),811&(entry->mod_name));812if (ret) {813goto done;814}815816if (! (mask & KADM5_MOD_TIME))817entry->mod_date = 0;818if (! (mask & KADM5_MOD_NAME)) {819krb5_free_principal(handle->context, entry->mod_name);820entry->mod_name = NULL;821}822}823824if (mask & KADM5_ATTRIBUTES)825entry->attributes = kdb->attributes;826827if (mask & KADM5_KVNO)828for (entry->kvno = 0, i=0; i<kdb->n_key_data; i++)829if ((krb5_kvno) kdb->key_data[i].key_data_kvno > entry->kvno)830entry->kvno = kdb->key_data[i].key_data_kvno;831832if (mask & KADM5_MKVNO) {833ret = krb5_dbe_get_mkvno(handle->context, kdb, &entry->mkvno);834if (ret)835goto done;836}837838if (mask & KADM5_MAX_RLIFE)839entry->max_renewable_life = kdb->max_renewable_life;840if (mask & KADM5_LAST_SUCCESS)841entry->last_success = kdb->last_success;842if (mask & KADM5_LAST_FAILED)843entry->last_failed = kdb->last_failed;844if (mask & KADM5_FAIL_AUTH_COUNT)845entry->fail_auth_count = kdb->fail_auth_count;846if (mask & KADM5_TL_DATA) {847krb5_tl_data *tl, *tl2;848849entry->tl_data = NULL;850851tl = kdb->tl_data;852while (tl) {853if (tl->tl_data_type > 255) {854if ((tl2 = dup_tl_data(tl)) == NULL) {855ret = ENOMEM;856goto done;857}858tl2->tl_data_next = entry->tl_data;859entry->tl_data = tl2;860entry->n_tl_data++;861}862863tl = tl->tl_data_next;864}865}866if (mask & KADM5_KEY_DATA) {867entry->n_key_data = kdb->n_key_data;868if(entry->n_key_data) {869entry->key_data = k5calloc(entry->n_key_data,870sizeof(krb5_key_data), &ret);871if (entry->key_data == NULL)872goto done;873} else874entry->key_data = NULL;875876for (i = 0; i < entry->n_key_data; i++)877ret = krb5_copy_key_data_contents(handle->context,878&kdb->key_data[i],879&entry->key_data[i]);880if (ret)881goto done;882}883884ret = KADM5_OK;885886done:887if (ret && entry->principal) {888krb5_free_principal(handle->context, entry->principal);889entry->principal = NULL;890}891kdb_free_entry(handle, kdb, &adb);892893return ret;894}895896/*897* Function: check_pw_reuse898*899* Purpose: Check if a key appears in a list of keys, in order to900* enforce password history.901*902* Arguments:903*904* context (r) the krb5 context905* hist_keyblock (r) the key that hist_key_data is906* encrypted in907* n_new_key_data (r) length of new_key_data908* new_key_data (r) keys to check against909* pw_hist_data, encrypted in hist_keyblock910* n_pw_hist_data (r) length of pw_hist_data911* pw_hist_data (r) passwords to check new_key_data against912*913* Effects:914* For each new_key in new_key_data:915* decrypt new_key with the master_keyblock916* for each password in pw_hist_data:917* for each hist_key in password:918* decrypt hist_key with hist_keyblock919* compare the new_key and hist_key920*921* Returns krb5 errors, KADM5_PASS_RESUSE if a key in922* new_key_data is the same as a key in pw_hist_data, or 0.923*/924static kadm5_ret_t925check_pw_reuse(krb5_context context,926krb5_keyblock *hist_keyblocks,927int n_new_key_data, krb5_key_data *new_key_data,928unsigned int n_pw_hist_data, osa_pw_hist_ent *pw_hist_data)929{930unsigned int x, y, z;931krb5_keyblock newkey, histkey, *kb;932krb5_key_data *key_data;933krb5_error_code ret;934935assert (n_new_key_data >= 0);936for (x = 0; x < (unsigned) n_new_key_data; x++) {937/* Check only entries with the most recent kvno. */938if (new_key_data[x].key_data_kvno != new_key_data[0].key_data_kvno)939break;940ret = krb5_dbe_decrypt_key_data(context, NULL, &(new_key_data[x]),941&newkey, NULL);942if (ret)943return(ret);944for (y = 0; y < n_pw_hist_data; y++) {945for (z = 0; z < (unsigned int) pw_hist_data[y].n_key_data; z++) {946for (kb = hist_keyblocks; kb->enctype != 0; kb++) {947key_data = &pw_hist_data[y].key_data[z];948ret = krb5_dbe_decrypt_key_data(context, kb, key_data,949&histkey, NULL);950if (ret)951continue;952if (newkey.length == histkey.length &&953newkey.enctype == histkey.enctype &&954memcmp(newkey.contents, histkey.contents,955histkey.length) == 0) {956krb5_free_keyblock_contents(context, &histkey);957krb5_free_keyblock_contents(context, &newkey);958return KADM5_PASS_REUSE;959}960krb5_free_keyblock_contents(context, &histkey);961}962}963}964krb5_free_keyblock_contents(context, &newkey);965}966967return(0);968}969970static void971free_history_entry(krb5_context context, osa_pw_hist_ent *hist)972{973int i;974975for (i = 0; i < hist->n_key_data; i++)976krb5_free_key_data_contents(context, &hist->key_data[i]);977free(hist->key_data);978}979980/*981* Function: create_history_entry982*983* Purpose: Creates a password history entry from an array of984* key_data.985*986* Arguments:987*988* context (r) krb5_context to use989* mkey (r) master keyblock to decrypt key data with990* hist_key (r) history keyblock to encrypt key data with991* n_key_data (r) number of elements in key_data992* key_data (r) keys to add to the history entry993* hist_out (w) history entry to fill in994*995* Effects:996*997* hist->key_data is allocated to store n_key_data key_datas. Each998* element of key_data is decrypted with master_keyblock, re-encrypted999* in hist_key, and added to hist->key_data. hist->n_key_data is1000* set to n_key_data.1001*/1002static1003int create_history_entry(krb5_context context,1004krb5_keyblock *hist_key, int n_key_data,1005krb5_key_data *key_data, osa_pw_hist_ent *hist_out)1006{1007int i;1008krb5_error_code ret = 0;1009krb5_keyblock key;1010krb5_keysalt salt;1011krb5_ui_2 kvno;1012osa_pw_hist_ent hist;10131014hist_out->key_data = NULL;1015hist_out->n_key_data = 0;10161017if (n_key_data < 0)1018return EINVAL;10191020memset(&key, 0, sizeof(key));1021memset(&hist, 0, sizeof(hist));10221023if (n_key_data == 0)1024goto cleanup;10251026hist.key_data = k5calloc(n_key_data, sizeof(krb5_key_data), &ret);1027if (hist.key_data == NULL)1028goto cleanup;10291030/* We only want to store the most recent kvno, and key_data should already1031* be sorted in descending order by kvno. */1032kvno = key_data[0].key_data_kvno;10331034for (i = 0; i < n_key_data; i++) {1035if (key_data[i].key_data_kvno < kvno)1036break;1037ret = krb5_dbe_decrypt_key_data(context, NULL,1038&key_data[i], &key,1039&salt);1040if (ret)1041goto cleanup;10421043ret = krb5_dbe_encrypt_key_data(context, hist_key, &key, &salt,1044key_data[i].key_data_kvno,1045&hist.key_data[hist.n_key_data]);1046if (ret)1047goto cleanup;1048hist.n_key_data++;1049krb5_free_keyblock_contents(context, &key);1050/* krb5_free_keysalt(context, &salt); */1051}10521053*hist_out = hist;1054hist.n_key_data = 0;1055hist.key_data = NULL;10561057cleanup:1058krb5_free_keyblock_contents(context, &key);1059free_history_entry(context, &hist);1060return ret;1061}10621063/*1064* Function: add_to_history1065*1066* Purpose: Adds a password to a principal's password history.1067*1068* Arguments:1069*1070* context (r) krb5_context to use1071* hist_kvno (r) kvno of current history key1072* adb (r/w) admin principal entry to add keys to1073* pol (r) adb's policy1074* pw (r) keys for the password to add to adb's key history1075*1076* Effects:1077*1078* add_to_history adds a single password to adb's password history.1079* pw contains n_key_data keys in its key_data, in storage should be1080* allocated but not freed by the caller (XXX blech!).1081*1082* This function maintains adb->old_keys as a circular queue. It1083* starts empty, and grows each time this function is called until it1084* is pol->pw_history_num items long. adb->old_key_len holds the1085* number of allocated entries in the array, and must therefore be [0,1086* pol->pw_history_num). adb->old_key_next is the index into the1087* array where the next element should be written, and must be [0,1088* adb->old_key_len).1089*/1090static kadm5_ret_t add_to_history(krb5_context context,1091krb5_kvno hist_kvno,1092osa_princ_ent_t adb,1093kadm5_policy_ent_t pol,1094osa_pw_hist_ent *pw)1095{1096osa_pw_hist_ent *histp;1097uint32_t nhist;1098unsigned int i, knext, nkeys;10991100nhist = pol->pw_history_num;1101/* A history of 1 means just check the current password */1102if (nhist <= 1)1103return 0;11041105if (adb->admin_history_kvno != hist_kvno) {1106/* The history key has changed since the last password change, so we1107* have to reset the password history. */1108free(adb->old_keys);1109adb->old_keys = NULL;1110adb->old_key_len = 0;1111adb->old_key_next = 0;1112adb->admin_history_kvno = hist_kvno;1113}11141115nkeys = adb->old_key_len;1116knext = adb->old_key_next;1117/* resize the adb->old_keys array if necessary */1118if (nkeys + 1 < nhist) {1119if (adb->old_keys == NULL) {1120adb->old_keys = (osa_pw_hist_ent *)1121malloc((nkeys + 1) * sizeof (osa_pw_hist_ent));1122} else {1123adb->old_keys = (osa_pw_hist_ent *)1124realloc(adb->old_keys,1125(nkeys + 1) * sizeof (osa_pw_hist_ent));1126}1127if (adb->old_keys == NULL)1128return(ENOMEM);11291130memset(&adb->old_keys[nkeys], 0, sizeof(osa_pw_hist_ent));1131nkeys = ++adb->old_key_len;1132/*1133* To avoid losing old keys, shift forward each entry after1134* knext.1135*/1136for (i = nkeys - 1; i > knext; i--) {1137adb->old_keys[i] = adb->old_keys[i - 1];1138}1139memset(&adb->old_keys[knext], 0, sizeof(osa_pw_hist_ent));1140} else if (nkeys + 1 > nhist) {1141/*1142* The policy must have changed! Shrink the array.1143* Can't simply realloc() down, since it might be wrapped.1144* To understand the arithmetic below, note that we are1145* copying into new positions 0 .. N-1 from old positions1146* old_key_next-N .. old_key_next-1, modulo old_key_len,1147* where N = pw_history_num - 1 is the length of the1148* shortened list. Matt Crawford, FNAL1149*/1150/*1151* M = adb->old_key_len, N = pol->pw_history_num - 11152*1153* tmp[0] .. tmp[N-1] = old[(knext-N)%M] .. old[(knext-1)%M]1154*/1155int j;1156osa_pw_hist_t tmp;11571158tmp = (osa_pw_hist_ent *)1159malloc((nhist - 1) * sizeof (osa_pw_hist_ent));1160if (tmp == NULL)1161return ENOMEM;1162for (i = 0; i < nhist - 1; i++) {1163/*1164* Add nkeys once before taking remainder to avoid1165* negative values.1166*/1167j = (i + nkeys + knext - (nhist - 1)) % nkeys;1168tmp[i] = adb->old_keys[j];1169}1170/* Now free the ones we don't keep (the oldest ones) */1171for (i = 0; i < nkeys - (nhist - 1); i++) {1172j = (i + nkeys + knext) % nkeys;1173histp = &adb->old_keys[j];1174for (j = 0; j < histp->n_key_data; j++) {1175krb5_free_key_data_contents(context, &histp->key_data[j]);1176}1177free(histp->key_data);1178}1179free(adb->old_keys);1180adb->old_keys = tmp;1181nkeys = adb->old_key_len = nhist - 1;1182knext = adb->old_key_next = 0;1183}11841185/*1186* If nhist decreased since the last password change, and nkeys+11187* is less than the previous nhist, it is possible for knext to1188* index into unallocated space. This condition would not be1189* caught by the resizing code above.1190*/1191if (knext + 1 > nkeys)1192knext = adb->old_key_next = 0;1193/* free the old pw history entry if it contains data */1194histp = &adb->old_keys[knext];1195for (i = 0; i < (unsigned int) histp->n_key_data; i++)1196krb5_free_key_data_contents(context, &histp->key_data[i]);1197free(histp->key_data);11981199/* store the new entry */1200adb->old_keys[knext] = *pw;12011202/* update the next pointer */1203if (++adb->old_key_next == nhist - 1)1204adb->old_key_next = 0;12051206return(0);1207}12081209kadm5_ret_t1210kadm5_chpass_principal(void *server_handle,1211krb5_principal principal, char *password)1212{1213return1214kadm5_chpass_principal_3(server_handle, principal, FALSE,12150, NULL, password);1216}12171218kadm5_ret_t1219kadm5_chpass_principal_3(void *server_handle,1220krb5_principal principal, unsigned int keepold,1221int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,1222char *password)1223{1224krb5_timestamp now;1225kadm5_policy_ent_rec pol;1226osa_princ_ent_rec adb;1227krb5_db_entry *kdb;1228int ret, ret2, hist_added;1229krb5_boolean have_pol = FALSE;1230kadm5_server_handle_t handle = server_handle;1231osa_pw_hist_ent hist;1232krb5_keyblock *act_mkey, *hist_keyblocks = NULL;1233krb5_kvno act_kvno, hist_kvno;1234int new_n_ks_tuple = 0;1235krb5_key_salt_tuple *new_ks_tuple = NULL;12361237CHECK_HANDLE(server_handle);12381239krb5_clear_error_message(handle->context);12401241hist_added = 0;1242memset(&hist, 0, sizeof(hist));12431244if (principal == NULL || password == NULL)1245return EINVAL;1246if ((krb5_principal_compare(handle->context,1247principal, hist_princ)) == TRUE)1248return KADM5_PROTECT_PRINCIPAL;12491250if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))1251return(ret);12521253/* We will always be changing the key data, attributes, auth failure count,1254* and password expiration time. */1255kdb->mask = KADM5_KEY_DATA | KADM5_ATTRIBUTES | KADM5_FAIL_AUTH_COUNT |1256KADM5_PW_EXPIRATION;12571258ret = apply_keysalt_policy(handle, adb.policy, n_ks_tuple, ks_tuple,1259&new_n_ks_tuple, &new_ks_tuple);1260if (ret)1261goto done;12621263if ((adb.aux_attributes & KADM5_POLICY)) {1264ret = get_policy(handle, adb.policy, &pol, &have_pol);1265if (ret)1266goto done;1267}1268if (have_pol) {1269/* Create a password history entry before we change kdb's key_data. */1270ret = kdb_get_hist_key(handle, &hist_keyblocks, &hist_kvno);1271if (ret)1272goto done;1273ret = create_history_entry(handle->context, &hist_keyblocks[0],1274kdb->n_key_data, kdb->key_data, &hist);1275if (ret == KRB5_BAD_ENCTYPE)1276ret = KADM5_BAD_HIST_KEY;1277if (ret)1278goto done;1279}12801281if ((ret = passwd_check(handle, password, have_pol ? &pol : NULL,1282principal)))1283goto done;12841285ret = kdb_get_active_mkey(handle, &act_kvno, &act_mkey);1286if (ret)1287goto done;12881289ret = krb5_dbe_cpw(handle->context, act_mkey, new_ks_tuple, new_n_ks_tuple,1290password, 0 /* increment kvno */,1291keepold, kdb);1292if (ret)1293goto done;12941295ret = krb5_dbe_update_mkvno(handle->context, kdb, act_kvno);1296if (ret)1297goto done;12981299kdb->attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE;13001301ret = krb5_timeofday(handle->context, &now);1302if (ret)1303goto done;13041305kdb->pw_expiration = 0;1306if (have_pol) {1307ret = check_pw_reuse(handle->context, hist_keyblocks,1308kdb->n_key_data, kdb->key_data,13091, &hist);1310if (ret)1311goto done;13121313if (pol.pw_history_num > 1) {1314/* If hist_kvno has changed since the last password change, we1315* can't check the history. */1316if (adb.admin_history_kvno == hist_kvno) {1317ret = check_pw_reuse(handle->context, hist_keyblocks,1318kdb->n_key_data, kdb->key_data,1319adb.old_key_len, adb.old_keys);1320if (ret)1321goto done;1322}13231324/* Don't save empty history. */1325if (hist.n_key_data > 0) {1326ret = add_to_history(handle->context, hist_kvno, &adb, &pol,1327&hist);1328if (ret)1329goto done;1330hist_added = 1;1331}1332}13331334if (pol.pw_max_life)1335kdb->pw_expiration = ts_incr(now, pol.pw_max_life);1336}13371338ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now);1339if (ret)1340goto done;13411342/* unlock principal on this KDC */1343kdb->fail_auth_count = 0;13441345if (hist_added)1346kdb->mask |= KADM5_KEY_HIST;13471348ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles,1349KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold,1350new_n_ks_tuple, new_ks_tuple, password);1351if (ret)1352goto done;13531354if ((ret = kdb_put_entry(handle, kdb, &adb)))1355goto done;13561357(void) k5_kadm5_hook_chpass(handle->context, handle->hook_handles,1358KADM5_HOOK_STAGE_POSTCOMMIT, principal,1359keepold, new_n_ks_tuple, new_ks_tuple, password);1360ret = KADM5_OK;1361done:1362free(new_ks_tuple);1363if (!hist_added && hist.key_data)1364free_history_entry(handle->context, &hist);1365kdb_free_entry(handle, kdb, &adb);1366kdb_free_keyblocks(handle, hist_keyblocks);13671368if (have_pol && (ret2 = kadm5_free_policy_ent(handle->lhandle, &pol))1369&& !ret)1370ret = ret2;13711372return ret;1373}13741375kadm5_ret_t1376kadm5_randkey_principal(void *server_handle,1377krb5_principal principal,1378krb5_keyblock **keyblocks,1379int *n_keys)1380{1381return1382kadm5_randkey_principal_3(server_handle, principal,1383FALSE, 0, NULL,1384keyblocks, n_keys);1385}1386kadm5_ret_t1387kadm5_randkey_principal_3(void *server_handle,1388krb5_principal principal,1389unsigned int keepold,1390int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,1391krb5_keyblock **keyblocks,1392int *n_keys)1393{1394krb5_db_entry *kdb;1395osa_princ_ent_rec adb;1396krb5_timestamp now;1397kadm5_policy_ent_rec pol;1398int ret, n_new_keys;1399krb5_boolean have_pol = FALSE;1400kadm5_server_handle_t handle = server_handle;1401krb5_keyblock *act_mkey;1402krb5_kvno act_kvno;1403int new_n_ks_tuple = 0;1404krb5_key_salt_tuple *new_ks_tuple = NULL;14051406if (keyblocks)1407*keyblocks = NULL;14081409CHECK_HANDLE(server_handle);14101411krb5_clear_error_message(handle->context);14121413if (principal == NULL)1414return EINVAL;14151416if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))1417return(ret);14181419/* We will always be changing the key data, attributes, auth failure count,1420* and password expiration time. */1421kdb->mask = KADM5_KEY_DATA | KADM5_ATTRIBUTES | KADM5_FAIL_AUTH_COUNT |1422KADM5_PW_EXPIRATION;14231424ret = apply_keysalt_policy(handle, adb.policy, n_ks_tuple, ks_tuple,1425&new_n_ks_tuple, &new_ks_tuple);1426if (ret)1427goto done;14281429if (krb5_principal_compare(handle->context, principal, hist_princ)) {1430/* If changing the history entry, the new entry must have exactly one1431* key. */1432if (keepold) {1433ret = KADM5_PROTECT_PRINCIPAL;1434goto done;1435}1436new_n_ks_tuple = 1;1437}14381439ret = kdb_get_active_mkey(handle, &act_kvno, &act_mkey);1440if (ret)1441goto done;14421443ret = krb5_dbe_crk(handle->context, act_mkey, new_ks_tuple, new_n_ks_tuple,1444keepold, kdb);1445if (ret)1446goto done;14471448ret = krb5_dbe_update_mkvno(handle->context, kdb, act_kvno);1449if (ret)1450goto done;14511452kdb->attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE;14531454ret = krb5_timeofday(handle->context, &now);1455if (ret)1456goto done;14571458if ((adb.aux_attributes & KADM5_POLICY)) {1459ret = get_policy(handle, adb.policy, &pol, &have_pol);1460if (ret)1461goto done;1462}14631464kdb->pw_expiration = 0;1465if (have_pol && pol.pw_max_life)1466kdb->pw_expiration = ts_incr(now, pol.pw_max_life);14671468ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now);1469if (ret)1470goto done;14711472/* unlock principal on this KDC */1473kdb->fail_auth_count = 0;14741475if (keyblocks) {1476/* Return only the new keys added by krb5_dbe_crk. */1477n_new_keys = count_new_keys(kdb->n_key_data, kdb->key_data);1478ret = decrypt_key_data(handle->context, n_new_keys, kdb->key_data,1479keyblocks, n_keys);1480if (ret)1481goto done;1482}14831484ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles,1485KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold,1486new_n_ks_tuple, new_ks_tuple, NULL);1487if (ret)1488goto done;1489if ((ret = kdb_put_entry(handle, kdb, &adb)))1490goto done;14911492(void) k5_kadm5_hook_chpass(handle->context, handle->hook_handles,1493KADM5_HOOK_STAGE_POSTCOMMIT, principal,1494keepold, new_n_ks_tuple, new_ks_tuple, NULL);1495ret = KADM5_OK;1496done:1497free(new_ks_tuple);1498kdb_free_entry(handle, kdb, &adb);1499if (have_pol)1500kadm5_free_policy_ent(handle->lhandle, &pol);15011502return ret;1503}15041505kadm5_ret_t1506kadm5_setkey_principal(void *server_handle,1507krb5_principal principal,1508krb5_keyblock *keyblocks,1509int n_keys)1510{1511return1512kadm5_setkey_principal_3(server_handle, principal,1513FALSE, 0, NULL,1514keyblocks, n_keys);1515}15161517kadm5_ret_t1518kadm5_setkey_principal_3(void *server_handle,1519krb5_principal principal,1520unsigned int keepold,1521int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,1522krb5_keyblock *keyblocks,1523int n_keys)1524{1525kadm5_key_data *key_data;1526kadm5_ret_t ret;1527int i;15281529if (keyblocks == NULL)1530return EINVAL;15311532if (n_ks_tuple) {1533if (n_ks_tuple != n_keys)1534return KADM5_SETKEY3_ETYPE_MISMATCH;1535for (i = 0; i < n_ks_tuple; i++) {1536if (ks_tuple[i].ks_enctype != keyblocks[i].enctype)1537return KADM5_SETKEY3_ETYPE_MISMATCH;1538}1539}15401541key_data = calloc(n_keys, sizeof(kadm5_key_data));1542if (key_data == NULL)1543return ENOMEM;15441545for (i = 0; i < n_keys; i++) {1546key_data[i].key = keyblocks[i];1547key_data[i].salt.type =1548n_ks_tuple ? ks_tuple[i].ks_salttype : KRB5_KDB_SALTTYPE_NORMAL;1549}15501551ret = kadm5_setkey_principal_4(server_handle, principal, keepold,1552key_data, n_keys);1553free(key_data);1554return ret;1555}15561557/* Create a key/salt list from a key_data array. */1558static kadm5_ret_t1559make_ks_from_key_data(krb5_context context, kadm5_key_data *key_data,1560int n_key_data, krb5_key_salt_tuple **out)1561{1562int i;1563krb5_key_salt_tuple *ks;15641565*out = NULL;15661567ks = calloc(n_key_data, sizeof(*ks));1568if (ks == NULL)1569return ENOMEM;15701571for (i = 0; i < n_key_data; i++) {1572ks[i].ks_enctype = key_data[i].key.enctype;1573ks[i].ks_salttype = key_data[i].salt.type;1574}1575*out = ks;1576return 0;1577}15781579kadm5_ret_t1580kadm5_setkey_principal_4(void *server_handle, krb5_principal principal,1581unsigned int keepold, kadm5_key_data *key_data,1582int n_key_data)1583{1584krb5_db_entry *kdb;1585osa_princ_ent_rec adb;1586krb5_timestamp now;1587kadm5_policy_ent_rec pol;1588krb5_key_data *new_key_data = NULL;1589int i, j, ret, n_new_key_data = 0;1590krb5_kvno kvno;1591krb5_boolean similar, have_pol = FALSE;1592kadm5_server_handle_t handle = server_handle;1593krb5_keyblock *act_mkey;1594krb5_key_salt_tuple *ks_from_keys = NULL;15951596CHECK_HANDLE(server_handle);15971598krb5_clear_error_message(handle->context);15991600if (principal == NULL || key_data == NULL || n_key_data == 0)1601return EINVAL;16021603/* hist_princ will be NULL when initializing the database. */1604if (hist_princ != NULL &&1605krb5_principal_compare(handle->context, principal, hist_princ))1606return KADM5_PROTECT_PRINCIPAL;16071608/* For now, all keys must have the same kvno. */1609kvno = key_data[0].kvno;1610for (i = 1; i < n_key_data; i++) {1611if (key_data[i].kvno != kvno)1612return KADM5_SETKEY_BAD_KVNO;1613}16141615ret = kdb_get_entry(handle, principal, &kdb, &adb);1616if (ret)1617return ret;16181619/* We will always be changing the key data, attributes, auth failure count,1620* and password expiration time. */1621kdb->mask = KADM5_KEY_DATA | KADM5_ATTRIBUTES | KADM5_FAIL_AUTH_COUNT |1622KADM5_PW_EXPIRATION;16231624if (kvno == 0) {1625/* Pick the next kvno. */1626for (i = 0; i < kdb->n_key_data; i++) {1627if (kdb->key_data[i].key_data_kvno > kvno)1628kvno = kdb->key_data[i].key_data_kvno;1629}1630kvno++;1631} else if (keepold) {1632/* Check that the kvno does collide with existing keys. */1633for (i = 0; i < kdb->n_key_data; i++) {1634if (kdb->key_data[i].key_data_kvno == kvno) {1635ret = KADM5_SETKEY_BAD_KVNO;1636goto done;1637}1638}1639}16401641ret = make_ks_from_key_data(handle->context, key_data, n_key_data,1642&ks_from_keys);1643if (ret)1644goto done;16451646ret = apply_keysalt_policy(handle, adb.policy, n_key_data, ks_from_keys,1647NULL, NULL);1648free(ks_from_keys);1649if (ret)1650goto done;16511652for (i = 0; i < n_key_data; i++) {1653for (j = i + 1; j < n_key_data; j++) {1654ret = krb5_c_enctype_compare(handle->context,1655key_data[i].key.enctype,1656key_data[j].key.enctype,1657&similar);1658if (ret)1659goto done;1660if (similar) {1661if (key_data[i].salt.type == key_data[j].salt.type) {1662ret = KADM5_SETKEY_DUP_ENCTYPES;1663goto done;1664}1665}1666}1667}16681669n_new_key_data = n_key_data + (keepold ? kdb->n_key_data : 0);1670new_key_data = calloc(n_new_key_data, sizeof(krb5_key_data));1671if (new_key_data == NULL) {1672n_new_key_data = 0;1673ret = ENOMEM;1674goto done;1675}16761677n_new_key_data = 0;1678for (i = 0; i < n_key_data; i++) {16791680ret = kdb_get_active_mkey(handle, NULL, &act_mkey);1681if (ret)1682goto done;16831684ret = krb5_dbe_encrypt_key_data(handle->context, act_mkey,1685&key_data[i].key, &key_data[i].salt,1686kvno, &new_key_data[i]);1687if (ret)1688goto done;16891690n_new_key_data++;1691}16921693/* Copy old key data if necessary. */1694if (keepold) {1695memcpy(new_key_data + n_new_key_data, kdb->key_data,1696kdb->n_key_data * sizeof(krb5_key_data));1697memset(kdb->key_data, 0, kdb->n_key_data * sizeof(krb5_key_data));16981699/*1700* Sort the keys to maintain the defined kvno order. We only need to1701* sort if we keep old keys, as otherwise we allow only a single kvno1702* to be specified.1703*/1704krb5_dbe_sort_key_data(new_key_data, n_new_key_data);1705}17061707/* Replace kdb->key_data with the new keys. */1708cleanup_key_data(handle->context, kdb->n_key_data, kdb->key_data);1709kdb->key_data = new_key_data;1710kdb->n_key_data = n_new_key_data;1711new_key_data = NULL;1712n_new_key_data = 0;17131714kdb->attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE;17151716ret = krb5_timeofday(handle->context, &now);1717if (ret)1718goto done;17191720if (adb.aux_attributes & KADM5_POLICY) {1721ret = get_policy(handle, adb.policy, &pol, &have_pol);1722if (ret)1723goto done;1724}17251726kdb->pw_expiration = 0;1727if (have_pol && pol.pw_max_life)1728kdb->pw_expiration = ts_incr(now, pol.pw_max_life);17291730ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now);1731if (ret)1732goto done;17331734/* Unlock principal on this KDC. */1735kdb->fail_auth_count = 0;17361737ret = kdb_put_entry(handle, kdb, &adb);1738if (ret)1739goto done;17401741ret = KADM5_OK;17421743done:1744cleanup_key_data(handle->context, n_new_key_data, new_key_data);1745kdb_free_entry(handle, kdb, &adb);1746if (have_pol)1747kadm5_free_policy_ent(handle->lhandle, &pol);1748return ret;1749}17501751/*1752* Return the list of keys like kadm5_randkey_principal,1753* but don't modify the principal.1754*/1755kadm5_ret_t1756kadm5_get_principal_keys(void *server_handle /* IN */,1757krb5_principal principal /* IN */,1758krb5_kvno kvno /* IN */,1759kadm5_key_data **key_data_out /* OUT */,1760int *n_key_data_out /* OUT */)1761{1762krb5_db_entry *kdb;1763osa_princ_ent_rec adb;1764kadm5_ret_t ret;1765kadm5_server_handle_t handle = server_handle;1766kadm5_key_data *key_data = NULL;1767int i, nkeys = 0;17681769if (principal == NULL || key_data_out == NULL || n_key_data_out == NULL)1770return EINVAL;17711772CHECK_HANDLE(server_handle);17731774if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))1775return(ret);17761777key_data = calloc(kdb->n_key_data, sizeof(kadm5_key_data));1778if (key_data == NULL) {1779ret = ENOMEM;1780goto done;1781}17821783for (i = 0, nkeys = 0; i < kdb->n_key_data; i++) {1784if (kvno != 0 && kvno != kdb->key_data[i].key_data_kvno)1785continue;1786key_data[nkeys].kvno = kdb->key_data[i].key_data_kvno;17871788ret = krb5_dbe_decrypt_key_data(handle->context, NULL,1789&kdb->key_data[i],1790&key_data[nkeys].key,1791&key_data[nkeys].salt);1792if (ret)1793goto done;1794nkeys++;1795}17961797*n_key_data_out = nkeys;1798*key_data_out = key_data;1799key_data = NULL;1800nkeys = 0;1801ret = KADM5_OK;18021803done:1804kdb_free_entry(handle, kdb, &adb);1805kadm5_free_kadm5_key_data(handle->context, nkeys, key_data);18061807return ret;1808}180918101811/*1812* Allocate an array of n_key_data krb5_keyblocks, fill in each1813* element with the results of decrypting the nth key in key_data,1814* and if n_keys is not NULL fill it in with the1815* number of keys decrypted.1816*/1817static int decrypt_key_data(krb5_context context,1818int n_key_data, krb5_key_data *key_data,1819krb5_keyblock **keyblocks, int *n_keys)1820{1821krb5_keyblock *keys;1822int ret, i;18231824keys = (krb5_keyblock *) malloc(n_key_data*sizeof(krb5_keyblock));1825if (keys == NULL)1826return ENOMEM;1827memset(keys, 0, n_key_data*sizeof(krb5_keyblock));18281829for (i = 0; i < n_key_data; i++) {1830ret = krb5_dbe_decrypt_key_data(context, NULL, &key_data[i], &keys[i],1831NULL);1832if (ret) {1833for (; i >= 0; i--)1834krb5_free_keyblock_contents(context, &keys[i]);1835free(keys);1836return ret;1837}1838}18391840*keyblocks = keys;1841if (n_keys)1842*n_keys = n_key_data;18431844return 0;1845}18461847/*1848* Function: kadm5_decrypt_key1849*1850* Purpose: Retrieves and decrypts a principal key.1851*1852* Arguments:1853*1854* server_handle (r) kadm5 handle1855* entry (r) principal retrieved with kadm5_get_principal1856* ktype (r) enctype to search for, or -1 to ignore1857* stype (r) salt type to search for, or -1 to ignore1858* kvno (r) kvno to search for, -1 for max, 0 for max1859* only if it also matches ktype and stype1860* keyblock (w) keyblock to fill in1861* keysalt (w) keysalt to fill in, or NULL1862* kvnop (w) kvno to fill in, or NULL1863*1864* Effects: Searches the key_data array of entry, which must have been1865* retrieved with kadm5_get_principal with the KADM5_KEY_DATA mask, to1866* find a key with a specified enctype, salt type, and kvno in a1867* principal entry. If not found, return ENOENT. Otherwise, decrypt1868* it with the master key, and return the key in keyblock, the salt1869* in salttype, and the key version number in kvno.1870*1871* If ktype or stype is -1, it is ignored for the search. If kvno is1872* -1, ktype and stype are ignored and the key with the max kvno is1873* returned. If kvno is 0, only the key with the max kvno is returned1874* and only if it matches the ktype and stype; otherwise, ENOENT is1875* returned.1876*/1877kadm5_ret_t kadm5_decrypt_key(void *server_handle,1878kadm5_principal_ent_t entry, krb5_int321879ktype, krb5_int32 stype, krb5_int321880kvno, krb5_keyblock *keyblock,1881krb5_keysalt *keysalt, int *kvnop)1882{1883kadm5_server_handle_t handle = server_handle;1884krb5_db_entry dbent;1885krb5_key_data *key_data;1886krb5_keyblock *mkey_ptr;1887int ret;18881889CHECK_HANDLE(server_handle);18901891if (entry->n_key_data == 0 || entry->key_data == NULL)1892return EINVAL;18931894/* find_enctype only uses these two fields */1895dbent.n_key_data = entry->n_key_data;1896dbent.key_data = entry->key_data;1897if ((ret = krb5_dbe_find_enctype(handle->context, &dbent, ktype,1898stype, kvno, &key_data)))1899return ret;19001901/* find_mkey only uses this field */1902dbent.tl_data = entry->tl_data;1903if ((ret = krb5_dbe_find_mkey(handle->context, &dbent, &mkey_ptr))) {1904/* try refreshing master key list */1905/* XXX it would nice if we had the mkvno here for optimization */1906if (krb5_db_fetch_mkey_list(handle->context, master_princ,1907&master_keyblock) == 0) {1908if ((ret = krb5_dbe_find_mkey(handle->context, &dbent,1909&mkey_ptr))) {1910return ret;1911}1912} else {1913return ret;1914}1915}19161917if ((ret = krb5_dbe_decrypt_key_data(handle->context, NULL, key_data,1918keyblock, keysalt)))1919return ret;19201921/*1922* Coerce the enctype of the output keyblock in case we got an1923* inexact match on the enctype; this behavior will go away when1924* the key storage architecture gets redesigned for 1.3.1925*/1926if (ktype != -1)1927keyblock->enctype = ktype;19281929if (kvnop)1930*kvnop = key_data->key_data_kvno;19311932return KADM5_OK;1933}19341935kadm5_ret_t1936kadm5_purgekeys(void *server_handle,1937krb5_principal principal,1938int keepkvno)1939{1940kadm5_server_handle_t handle = server_handle;1941kadm5_ret_t ret;1942krb5_db_entry *kdb;1943osa_princ_ent_rec adb;1944krb5_key_data *old_keydata;1945int n_old_keydata;1946int i, j, k;19471948CHECK_HANDLE(server_handle);19491950if (principal == NULL)1951return EINVAL;19521953ret = kdb_get_entry(handle, principal, &kdb, &adb);1954if (ret)1955return(ret);19561957if (keepkvno <= 0) {1958keepkvno = krb5_db_get_key_data_kvno(handle->context, kdb->n_key_data,1959kdb->key_data);1960}19611962old_keydata = kdb->key_data;1963n_old_keydata = kdb->n_key_data;1964kdb->n_key_data = 0;1965/* Allocate one extra key_data to avoid allocating 0 bytes. */1966kdb->key_data = calloc(n_old_keydata, sizeof(krb5_key_data));1967if (kdb->key_data == NULL) {1968ret = ENOMEM;1969goto done;1970}1971memset(kdb->key_data, 0, n_old_keydata * sizeof(krb5_key_data));1972for (i = 0, j = 0; i < n_old_keydata; i++) {1973if (old_keydata[i].key_data_kvno < keepkvno)1974continue;19751976/* Alias the key_data_contents pointers; we null them out in the1977* source array immediately after. */1978kdb->key_data[j] = old_keydata[i];1979for (k = 0; k < old_keydata[i].key_data_ver; k++) {1980old_keydata[i].key_data_contents[k] = NULL;1981}1982j++;1983}1984kdb->n_key_data = j;1985cleanup_key_data(handle->context, n_old_keydata, old_keydata);19861987kdb->mask = KADM5_KEY_DATA;1988ret = kdb_put_entry(handle, kdb, &adb);1989if (ret)1990goto done;19911992done:1993kdb_free_entry(handle, kdb, &adb);1994return ret;1995}19961997kadm5_ret_t1998kadm5_get_strings(void *server_handle, krb5_principal principal,1999krb5_string_attr **strings_out, int *count_out)2000{2001kadm5_server_handle_t handle = server_handle;2002kadm5_ret_t ret;2003krb5_db_entry *kdb = NULL;20042005*strings_out = NULL;2006*count_out = 0;2007CHECK_HANDLE(server_handle);2008if (principal == NULL)2009return EINVAL;20102011ret = kdb_get_entry(handle, principal, &kdb, NULL);2012if (ret)2013return ret;20142015ret = krb5_dbe_get_strings(handle->context, kdb, strings_out, count_out);2016kdb_free_entry(handle, kdb, NULL);2017return ret;2018}20192020kadm5_ret_t2021kadm5_set_string(void *server_handle, krb5_principal principal,2022const char *key, const char *value)2023{2024kadm5_server_handle_t handle = server_handle;2025kadm5_ret_t ret;2026krb5_db_entry *kdb;2027osa_princ_ent_rec adb;20282029CHECK_HANDLE(server_handle);2030if (principal == NULL || key == NULL)2031return EINVAL;20322033ret = kdb_get_entry(handle, principal, &kdb, &adb);2034if (ret)2035return ret;20362037ret = krb5_dbe_set_string(handle->context, kdb, key, value);2038if (ret)2039goto done;20402041kdb->mask = KADM5_TL_DATA;2042ret = kdb_put_entry(handle, kdb, &adb);20432044done:2045kdb_free_entry(handle, kdb, &adb);2046return ret;2047}20482049kadm5_ret_t2050kadm5_create_alias(void *server_handle, krb5_principal alias,2051krb5_principal target)2052{2053krb5_db_entry *kdb;2054osa_princ_ent_rec adb = { 0 };2055krb5_error_code ret;2056kadm5_server_handle_t handle = server_handle;20572058CHECK_HANDLE(server_handle);2059if (alias == NULL || target == NULL)2060return EINVAL;2061if (!krb5_realm_compare(handle->context, alias, target))2062return KADM5_ALIAS_REALM;20632064ret = kdb_get_entry(handle, alias, &kdb, NULL);2065if (!ret) {2066kdb_free_entry(handle, kdb, NULL);2067return KADM5_DUP;2068}20692070ret = k5_kadm5_hook_alias(handle->context, handle->hook_handles,2071KADM5_HOOK_STAGE_PRECOMMIT, alias, target);2072if (ret)2073return ret;20742075ret = krb5_dbe_make_alias_entry(handle->context, alias, target, &kdb);2076if (ret)2077return ret;2078ret = kdb_put_entry(handle, kdb, &adb);2079krb5_db_free_principal(handle->context, kdb);2080if (ret)2081return ret;20822083(void) k5_kadm5_hook_alias(handle->context, handle->hook_handles,2084KADM5_HOOK_STAGE_POSTCOMMIT, alias, target);2085return 0;2086}208720882089