Allow clearpolicy restriction for kadmin addprinc
[krb5.git] / src / lib / kadm5 / srv / svr_principal.c
index 7339ea1bdba1d117b1115e4a5cc0f5dc76a30a7b..a0b110def012d343d536ad59ded21219348e7c08 100644 (file)
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 /*
  * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved
  *
  * $Header$
  */
+#include "k5-int.h"
+#include        <sys/time.h>
+#include        <kadm5/admin.h>
+#include        <kdb.h>
+#include        "server_internal.h"
+#ifdef USE_PASSWORD_SERVER
+#include        <sys/wait.h>
+#include        <signal.h>
+#endif
+
+#include <krb5/kadm5_hook_plugin.h>
 
-#if !defined(lint) && !defined(__CODECENTER__)
-static char *rcsid = "$Header$";
+#ifdef USE_VALGRIND
+#include <valgrind/memcheck.h>
+#else
+#define VALGRIND_CHECK_DEFINED(LVALUE) ((void)0)
 #endif
 
-#include       <sys/types.h>
-#include       <sys/time.h>
-#include       <kadm5/admin.h>
-#include       "adb.h"
-#include       "k5-int.h"
-#include       <krb5/kdb.h>
-#include       <stdio.h>
-#include       <string.h>
-#include       "server_internal.h"
-#include       <stdarg.h>
-#include       <stdlib.h>
-
-extern krb5_principal      master_princ;
-extern krb5_principal      hist_princ;
-extern krb5_encrypt_block  master_encblock;
-extern krb5_encrypt_block  hist_encblock;
-extern krb5_keyblock       master_keyblock;
-extern krb5_keyblock       hist_key;
-extern krb5_db_entry       master_db;
-extern krb5_db_entry       hist_db;
-extern  krb5_kvno          hist_kvno;
+extern  krb5_principal      master_princ;
+extern  krb5_principal      hist_princ;
+extern  krb5_keyblock       master_keyblock;
+extern  krb5_actkvno_node  *active_mkey_list;
+extern  krb5_db_entry       master_db;
 
 static int decrypt_key_data(krb5_context context,
-                           int n_key_data, krb5_key_data *key_data,
-                           krb5_keyblock **keyblocks, int *n_keys);
+                            int n_key_data, krb5_key_data *key_data,
+                            krb5_keyblock **keyblocks, int *n_keys);
+
+static krb5_error_code
+kadm5_copy_principal(krb5_context context, krb5_const_principal inprinc, krb5_principal *outprinc)
+{
+    register krb5_principal tempprinc;
+    register int i, nelems;
+
+    tempprinc = (krb5_principal)krb5_db_alloc(context, NULL, sizeof(krb5_principal_data));
+
+    if (tempprinc == 0)
+        return ENOMEM;
+
+    VALGRIND_CHECK_DEFINED(*inprinc);
+    *tempprinc = *inprinc;
+
+    nelems = (int) krb5_princ_size(context, inprinc);
+    tempprinc->data = krb5_db_alloc(context, NULL, nelems * sizeof(krb5_data));
+    if (tempprinc->data == 0) {
+        krb5_db_free(context, (char *)tempprinc);
+        return ENOMEM;
+    }
+
+    for (i = 0; i < nelems; i++) {
+        unsigned int len = krb5_princ_component(context, inprinc, i)->length;
+        krb5_princ_component(context, tempprinc, i)->length = len;
+        if (((krb5_princ_component(context, tempprinc, i)->data =
+              krb5_db_alloc(context, NULL, len)) == 0) && len) {
+            while (--i >= 0)
+                krb5_db_free(context, krb5_princ_component(context, tempprinc, i)->data);
+            krb5_db_free (context, tempprinc->data);
+            krb5_db_free (context, tempprinc);
+            return ENOMEM;
+        }
+        if (len)
+            memcpy(krb5_princ_component(context, tempprinc, i)->data,
+                   krb5_princ_component(context, inprinc, i)->data, len);
+        krb5_princ_component(context, tempprinc, i)->magic = KV5M_DATA;
+    }
+
+    tempprinc->realm.data =
+        krb5_db_alloc(context, NULL, tempprinc->realm.length = inprinc->realm.length);
+    if (!tempprinc->realm.data && tempprinc->realm.length) {
+        for (i = 0; i < nelems; i++)
+            krb5_db_free(context, krb5_princ_component(context, tempprinc, i)->data);
+        krb5_db_free(context, tempprinc->data);
+        krb5_db_free(context, tempprinc);
+        return ENOMEM;
+    }
+    if (tempprinc->realm.length)
+        memcpy(tempprinc->realm.data, inprinc->realm.data,
+               inprinc->realm.length);
+
+    *outprinc = tempprinc;
+    return 0;
+}
+
+static void
+kadm5_free_principal(krb5_context context, krb5_principal val)
+{
+    register krb5_int32 i;
+
+    if (!val)
+        return;
+
+    if (val->data) {
+        i = krb5_princ_size(context, val);
+        while(--i >= 0)
+            krb5_db_free(context, krb5_princ_component(context, val, i)->data);
+        krb5_db_free(context, val->data);
+    }
+    if (val->realm.data)
+        krb5_db_free(context, val->realm.data);
+    krb5_db_free(context, val);
+}
 
 /*
  * XXX Functions that ought to be in libkrb5.a, but aren't.
  */
 kadm5_ret_t krb5_copy_key_data_contents(context, from, to)
-   krb5_context context;
-   krb5_key_data *from, *to;
+    krb5_context context;
+    krb5_key_data *from, *to;
 {
-     int i, idx;
-     
-     *to = *from;
-
-     idx = (from->key_data_ver == 1 ? 1 : 2);
-
-     for (i = 0; i < idx; i++) {
-       if ( from->key_data_length[i] ) {
-        to->key_data_contents[i] = malloc(from->key_data_length[i]);
-        if (to->key_data_contents[i] == NULL) {
-          for (i = 0; i < idx; i++) {
-            if (to->key_data_contents[i]) {
-              memset(to->key_data_contents[i], 0,
-                     to->key_data_length[i]);
-              free(to->key_data_contents[i]);
-            }
-          }
-          return ENOMEM;
-        }
-        memcpy(to->key_data_contents[i], from->key_data_contents[i],
-               from->key_data_length[i]);
-       }
-     }
-     return 0;
+    int i, idx;
+
+    *to = *from;
+
+    idx = (from->key_data_ver == 1 ? 1 : 2);
+
+    for (i = 0; i < idx; i++) {
+        if ( from->key_data_length[i] ) {
+            to->key_data_contents[i] = malloc(from->key_data_length[i]);
+            if (to->key_data_contents[i] == NULL) {
+                for (i = 0; i < idx; i++) {
+                    if (to->key_data_contents[i]) {
+                        memset(to->key_data_contents[i], 0,
+                               to->key_data_length[i]);
+                        free(to->key_data_contents[i]);
+                    }
+                }
+                return ENOMEM;
+            }
+            memcpy(to->key_data_contents[i], from->key_data_contents[i],
+                   from->key_data_length[i]);
+        }
+    }
+    return 0;
 }
 
 static krb5_tl_data *dup_tl_data(krb5_tl_data *tl)
 {
-     krb5_tl_data *n;
-
-     n = (krb5_tl_data *) malloc(sizeof(krb5_tl_data));
-     if (n == NULL)
-         return NULL;
-     n->tl_data_contents = malloc(tl->tl_data_length);
-     if (n->tl_data_contents == NULL) {
-         free(n);
-         return NULL;
-     }
-     memcpy(n->tl_data_contents, tl->tl_data_contents, tl->tl_data_length);
-     n->tl_data_type = tl->tl_data_type;
-     n->tl_data_length = tl->tl_data_length;
-     n->tl_data_next = NULL;
-     return n;
+    krb5_tl_data *n;
+
+    n = (krb5_tl_data *) malloc(sizeof(krb5_tl_data));
+    if (n == NULL)
+        return NULL;
+    n->tl_data_contents = malloc(tl->tl_data_length);
+    if (n->tl_data_contents == NULL) {
+        free(n);
+        return NULL;
+    }
+    memcpy(n->tl_data_contents, tl->tl_data_contents, tl->tl_data_length);
+    n->tl_data_type = tl->tl_data_type;
+    n->tl_data_length = tl->tl_data_length;
+    n->tl_data_next = NULL;
+    return n;
+}
+
+/* This is in lib/kdb/kdb_cpw.c, but is static */
+static void cleanup_key_data(context, count, data)
+    krb5_context   context;
+    int                    count;
+    krb5_key_data        * data;
+{
+    int i, j;
+
+    for (i = 0; i < count; i++)
+        for (j = 0; j < data[i].key_data_ver; j++)
+            if (data[i].key_data_length[j])
+                krb5_db_free(context, data[i].key_data_contents[j]);
+    krb5_db_free(context, data);
+}
+
+/*
+ * Set *passptr to NULL if the request looks like the first part of a krb5 1.6
+ * addprinc -randkey operation.  The krb5 1.6 dummy password for these requests
+ * was invalid UTF-8, which runs afoul of the arcfour string-to-key.
+ */
+static void
+check_1_6_dummy(kadm5_principal_ent_t entry, long mask,
+                int n_ks_tuple, krb5_key_salt_tuple *ks_tuple, char **passptr)
+{
+    int i;
+    char *password = *passptr;
+
+    /* Old-style randkey operations disallowed tickets to start. */
+    if (!(mask & KADM5_ATTRIBUTES) ||
+        !(entry->attributes & KRB5_KDB_DISALLOW_ALL_TIX))
+        return;
+
+    /* The 1.6 dummy password was the octets 1..255. */
+    for (i = 0; (unsigned char) password[i] == i + 1; i++);
+    if (password[i] != '\0' || i != 255)
+        return;
+
+    /* This will make the caller use a random password instead. */
+    *passptr = NULL;
 }
 
 kadm5_ret_t
 kadm5_create_principal(void *server_handle,
-                           kadm5_principal_ent_t entry, long mask,
-                           char *password)
+                       kadm5_principal_ent_t entry, long mask,
+                       char *password)
+{
+    return
+        kadm5_create_principal_3(server_handle, entry, mask,
+                                 0, NULL, password);
+}
+kadm5_ret_t
+kadm5_create_principal_3(void *server_handle,
+                         kadm5_principal_ent_t entry, long mask,
+                         int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
+                         char *password)
 {
-    krb5_db_entry              kdb;
-    osa_princ_ent_rec          adb;
-    kadm5_policy_ent_rec       polent;
-    krb5_int32                 now;
-    krb5_tl_data               *tl_data_orig, *tl_data_tail;
-    unsigned int               ret;
+    krb5_db_entry               *kdb;
+    osa_princ_ent_rec           adb;
+    kadm5_policy_ent_rec        polent;
+    krb5_boolean                have_polent = FALSE;
+    krb5_int32                  now;
+    krb5_tl_data                *tl_data_orig, *tl_data_tail;
+    unsigned int                ret;
     kadm5_server_handle_t handle = server_handle;
+    krb5_keyblock               *act_mkey;
+    krb5_kvno                   act_kvno;
 
     CHECK_HANDLE(server_handle);
 
+    krb5_clear_error_message(handle->context);
+
+    check_1_6_dummy(entry, mask, n_ks_tuple, ks_tuple, &password);
+
     /*
      * Argument sanity checking, and opening up the DB
      */
     if(!(mask & KADM5_PRINCIPAL) || (mask & KADM5_MOD_NAME) ||
        (mask & KADM5_MOD_TIME) || (mask & KADM5_LAST_PWD_CHANGE) ||
-       (mask & KADM5_MKVNO) || (mask & KADM5_POLICY_CLR) ||
-       (mask & KADM5_AUX_ATTRIBUTES) || (mask & KADM5_KEY_DATA) ||
-       (mask & KADM5_LAST_SUCCESS) || (mask & KADM5_LAST_FAILED) ||
-       (mask & KADM5_FAIL_AUTH_COUNT))
-       return KADM5_BAD_MASK;
+       (mask & KADM5_MKVNO) || (mask & KADM5_AUX_ATTRIBUTES) ||
+       (mask & KADM5_KEY_DATA) || (mask & KADM5_LAST_SUCCESS) ||
+       (mask & KADM5_LAST_FAILED) || (mask & KADM5_FAIL_AUTH_COUNT))
+        return KADM5_BAD_MASK;
+    if((mask & KADM5_POLICY) && (mask & KADM5_POLICY_CLR))
+        return KADM5_BAD_MASK;
     if((mask & ~ALL_PRINC_MASK))
-       return KADM5_BAD_MASK;
-    if (entry == (kadm5_principal_ent_t) NULL || password == NULL)
-       return EINVAL;
+        return KADM5_BAD_MASK;
+    if (entry == NULL)
+        return EINVAL;
+
+    /* Use default keysalts if caller did not provide any. */
+    if (n_ks_tuple == 0) {
+        ks_tuple = handle->params.keysalts;
+        n_ks_tuple = handle->params.num_keysalts;
+    }
 
     /*
      * Check to see if the principal exists
@@ -123,15 +260,18 @@ kadm5_create_principal(void *server_handle,
 
     switch(ret) {
     case KADM5_UNK_PRINC:
-       break;
+        break;
     case 0:
-       kdb_free_entry(handle, &kdb, &adb);
-       return KADM5_DUP;
+        kdb_free_entry(handle, kdb, &adb);
+        return KADM5_DUP;
     default:
-       return ret;
+        return ret;
     }
 
-    memset(&kdb, 0, sizeof(krb5_db_entry));
+    kdb = krb5_db_alloc(handle->context, NULL, sizeof(*kdb));
+    if (kdb == NULL)
+        return ENOMEM;
+    memset(kdb, 0, sizeof(*kdb));
     memset(&adb, 0, sizeof(osa_princ_ent_rec));
 
     /*
@@ -139,98 +279,118 @@ kadm5_create_principal(void *server_handle,
      * If we can not find the one specified return an error
      */
     if ((mask & KADM5_POLICY)) {
-        if ((ret = kadm5_get_policy(handle->lhandle, entry->policy,
-                                    &polent)) != KADM5_OK) {
-           if(ret == EINVAL) 
-               return KADM5_BAD_POLICY;
-           else
-               return ret;
-       }
-    }
-    if (ret = passwd_check(handle, password, (mask & KADM5_POLICY),
-                          &polent, entry->principal)) {
-       if (mask & KADM5_POLICY)
-            (void) kadm5_free_policy_ent(handle->lhandle, &polent);
-       return ret;
+        if ((ret = kadm5_get_policy(handle->lhandle, entry->policy,
+                                    &polent)) != KADM5_OK) {
+            if (ret == EINVAL)
+                ret = KADM5_BAD_POLICY;
+            if (ret)
+                goto cleanup;
+        }
+        have_polent = TRUE;
+    }
+    if (password) {
+        ret = passwd_check(handle, password, have_polent ? &polent : NULL,
+                           entry->principal);
+        if (ret)
+            goto cleanup;
     }
     /*
      * Start populating the various DB fields, using the
      * "defaults" for fields that were not specified by the
      * mask.
      */
-    if (ret = krb5_timeofday(handle->context, &now)) {
-       if (mask & KADM5_POLICY)
-            (void) kadm5_free_policy_ent(handle->lhandle, &polent);
-       return ret;
-    }
+    if ((ret = krb5_timeofday(handle->context, &now)))
+        goto cleanup;
 
-    kdb.magic = KRB5_KDB_MAGIC_NUMBER;
-    kdb.len = KRB5_KDB_V1_BASE_LENGTH; /* gag me with a chainsaw */
+    kdb->magic = KRB5_KDB_MAGIC_NUMBER;
+    kdb->len = KRB5_KDB_V1_BASE_LENGTH; /* gag me with a chainsaw */
 
-    if ((mask & KADM5_ATTRIBUTES)) 
-       kdb.attributes = entry->attributes;
+    if ((mask & KADM5_ATTRIBUTES))
+        kdb->attributes = entry->attributes;
     else
-       kdb.attributes = handle->params.flags;
+        kdb->attributes = handle->params.flags;
 
     if ((mask & KADM5_MAX_LIFE))
-       kdb.max_life = entry->max_life; 
-    else 
-       kdb.max_life = handle->params.max_life;
+        kdb->max_life = entry->max_life;
+    else
+        kdb->max_life = handle->params.max_life;
 
     if (mask & KADM5_MAX_RLIFE)
-        kdb.max_renewable_life = entry->max_renewable_life;
+        kdb->max_renewable_life = entry->max_renewable_life;
     else
-        kdb.max_renewable_life = handle->params.max_rlife;
+        kdb->max_renewable_life = handle->params.max_rlife;
 
     if ((mask & KADM5_PRINC_EXPIRE_TIME))
-       kdb.expiration = entry->princ_expire_time;
+        kdb->expiration = entry->princ_expire_time;
     else
-       kdb.expiration = handle->params.expiration;
-
-    kdb.pw_expiration = 0;
-    if ((mask & KADM5_POLICY)) {
-       if(polent.pw_max_life)
-           kdb.pw_expiration = now + polent.pw_max_life;
-       else
-           kdb.pw_expiration = 0;
+        kdb->expiration = handle->params.expiration;
+
+    kdb->pw_expiration = 0;
+    if (have_polent) {
+        if(polent.pw_max_life)
+            kdb->pw_expiration = now + polent.pw_max_life;
+        else
+            kdb->pw_expiration = 0;
     }
     if ((mask & KADM5_PW_EXPIRATION))
-        kdb.pw_expiration = entry->pw_expiration;
-    
-    kdb.last_success = 0;
-    kdb.last_failed = 0;
-    kdb.fail_auth_count = 0;
+        kdb->pw_expiration = entry->pw_expiration;
+
+    kdb->last_success = 0;
+    kdb->last_failed = 0;
+    kdb->fail_auth_count = 0;
 
     /* this is kind of gross, but in order to free the tl data, I need
        to free the entire kdb entry, and that will try to free the
        principal. */
 
-    if (ret = krb5_copy_principal(handle->context,
-                                 entry->principal, &(kdb.princ))) {
-       if (mask & KADM5_POLICY)
-            (void) kadm5_free_policy_ent(handle->lhandle, &polent);
-       return(ret);
-    }
+    if ((ret = kadm5_copy_principal(handle->context,
+                                    entry->principal, &(kdb->princ))))
+        goto cleanup;
 
-    if (ret = krb5_dbe_update_last_pwd_change(handle->context, &kdb, now)) {
-       krb5_dbe_free_contents(handle->context, &kdb);
-       if (mask & KADM5_POLICY)
-            (void) kadm5_free_policy_ent(handle->lhandle, &polent);
-       return(ret);
+    if ((ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now)))
+        goto cleanup;
+
+    if (mask & KADM5_TL_DATA) {
+        /* splice entry->tl_data onto the front of kdb->tl_data */
+        tl_data_orig = kdb->tl_data;
+        for (tl_data_tail = entry->tl_data; tl_data_tail;
+             tl_data_tail = tl_data_tail->tl_data_next)
+        {
+            ret = krb5_dbe_update_tl_data(handle->context, kdb, tl_data_tail);
+            if( ret )
+                goto cleanup;
+        }
     }
 
     /* initialize the keys */
 
-    if (ret = krb5_dbe_cpw(handle->context, &master_encblock,
-                          handle->params.keysalts,
-                          handle->params.num_keysalts,
-                          password,
-                          (mask & KADM5_KVNO)?entry->kvno:1, &kdb)) {
-       krb5_dbe_free_contents(handle->context, &kdb);
-       if (mask & KADM5_POLICY)
-            (void) kadm5_free_policy_ent(handle->lhandle, &polent);
-       return(ret);
+    ret = krb5_dbe_find_act_mkey(handle->context, active_mkey_list, &act_kvno,
+                                 &act_mkey);
+    if (ret)
+        goto cleanup;
+
+    if (password) {
+        ret = krb5_dbe_cpw(handle->context, act_mkey, ks_tuple, n_ks_tuple,
+                           password, (mask & KADM5_KVNO)?entry->kvno:1,
+                           FALSE, kdb);
+    } else {
+        /* Null password means create with random key (new in 1.8). */
+        ret = krb5_dbe_crk(handle->context, &master_keyblock,
+                           ks_tuple, n_ks_tuple, FALSE, kdb);
     }
+    if (ret)
+        goto cleanup;
+
+    /* Record the master key VNO used to encrypt this entry's keys */
+    ret = krb5_dbe_update_mkvno(handle->context, kdb, act_kvno);
+    if (ret)
+        goto cleanup;
+
+    ret = k5_kadm5_hook_create(handle->context, handle->hook_handles,
+                               KADM5_HOOK_STAGE_PRECOMMIT, entry, mask,
+                               n_ks_tuple, ks_tuple, password);
+    if (ret)
+        goto cleanup;
 
     /* populate the admin-server-specific fields.  In the OV server,
        this used to be in a separate database.  Since there's already
@@ -238,545 +398,537 @@ kadm5_create_principal(void *server_handle,
        I'm going to keep it, and make all the admin stuff occupy a
        single tl_data record, */
 
-    adb.admin_history_kvno = hist_kvno;
-    if ((mask & KADM5_POLICY)) {
-       adb.aux_attributes = KADM5_POLICY;
+    adb.admin_history_kvno = INITIAL_HIST_KVNO;
+    if (have_polent) {
+        adb.aux_attributes = KADM5_POLICY;
 
-       /* this does *not* need to be strdup'ed, because adb is xdr */
-       /* encoded in osa_adb_create_princ, and not ever freed */
+        /* this does *not* need to be strdup'ed, because adb is xdr */
+        /* encoded in osa_adb_create_princ, and not ever freed */
 
-       adb.policy = entry->policy;
+        adb.policy = entry->policy;
     }
 
     /* increment the policy ref count, if any */
 
-    if ((mask & KADM5_POLICY)) {
-       polent.policy_refcnt++;
-       if ((ret = kadm5_modify_policy_internal(handle->lhandle, &polent,
-                                                   KADM5_REF_COUNT))
-           != KADM5_OK) {
-           krb5_dbe_free_contents(handle->context, &kdb);
-           if (mask & KADM5_POLICY)
-                (void) kadm5_free_policy_ent(handle->lhandle, &polent);
-           return(ret);
-       }
+    if (have_polent) {
+        polent.policy_refcnt++;
+        if ((ret = kadm5_modify_policy_internal(handle->lhandle, &polent,
+                                                KADM5_REF_COUNT))
+            != KADM5_OK)
+            goto cleanup;
     }
 
-    if (mask & KADM5_TL_DATA) {
-        /* splice entry->tl_data onto the front of kdb.tl_data */
-        tl_data_orig = kdb.tl_data;
-        for (tl_data_tail = entry->tl_data; tl_data_tail->tl_data_next;
-             tl_data_tail = tl_data_tail->tl_data_next)
-             ;
-        tl_data_tail->tl_data_next = kdb.tl_data;
-        kdb.tl_data = entry->tl_data;
-    }
+    /* In all cases key and the principal data is set, let the database provider know */
+    kdb->mask = mask | KADM5_KEY_DATA | KADM5_PRINCIPAL ;
 
     /* store the new db entry */
-    ret = kdb_put_entry(handle, &kdb, &adb);
-
-    if (mask & KADM5_TL_DATA) {
-        /* remove entry->tl_data from the front of kdb.tl_data */
-        tl_data_tail->tl_data_next = NULL;
-        kdb.tl_data = tl_data_orig;
-    }
+    ret = kdb_put_entry(handle, kdb, &adb);
 
-    krb5_dbe_free_contents(handle->context, &kdb);
 
     if (ret) {
-       if ((mask & KADM5_POLICY)) {
-           /* decrement the policy ref count */
-
-           polent.policy_refcnt--;
-           /*
-            * if this fails, there's nothing we can do anyway.  the
-            * policy refcount wil be too high.
-            */
-           (void) kadm5_modify_policy_internal(handle->lhandle, &polent,
-                                                    KADM5_REF_COUNT);
-       }
-
-       if (mask & KADM5_POLICY)
-            (void) kadm5_free_policy_ent(handle->lhandle, &polent);
-       return(ret);
+        if (have_polent) {
+            /* decrement the policy ref count */
+
+            polent.policy_refcnt--;
+            /*
+             * if this fails, there's nothing we can do anyway.  the
+             * policy refcount wil be too high.
+             */
+            (void) kadm5_modify_policy_internal(handle->lhandle, &polent,
+                                                KADM5_REF_COUNT);
+        }
     }
 
-    if (mask & KADM5_POLICY)
-        (void) kadm5_free_policy_ent(handle->lhandle, &polent);
+    (void) k5_kadm5_hook_create(handle->context, handle->hook_handles,
+                                KADM5_HOOK_STAGE_POSTCOMMIT, entry, mask,
+                                n_ks_tuple, ks_tuple, password);
 
-    return KADM5_OK;
+cleanup:
+    krb5_db_free_principal(handle->context, kdb);
+    if (have_polent)
+        (void) kadm5_free_policy_ent(handle->lhandle, &polent);
+    return ret;
 }
 
-       
+
 kadm5_ret_t
 kadm5_delete_principal(void *server_handle, krb5_principal principal)
 {
-    unsigned int               ret;
-    kadm5_policy_ent_rec       polent;
-    krb5_db_entry              kdb;
-    osa_princ_ent_rec          adb;
+    unsigned int                ret;
+    kadm5_policy_ent_rec        polent;
+    krb5_db_entry               *kdb;
+    osa_princ_ent_rec           adb;
     kadm5_server_handle_t handle = server_handle;
 
     CHECK_HANDLE(server_handle);
 
+    krb5_clear_error_message(handle->context);
+
     if (principal == NULL)
-       return EINVAL;
+        return EINVAL;
 
-    if (ret = kdb_get_entry(handle, principal, &kdb, &adb))
-       return(ret);
+    if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
+        return(ret);
+    ret = k5_kadm5_hook_remove(handle->context, handle->hook_handles,
+                               KADM5_HOOK_STAGE_PRECOMMIT, principal);
+    if (ret) {
+        kdb_free_entry(handle, kdb, &adb);
+        return ret;
+    }
 
     if ((adb.aux_attributes & KADM5_POLICY)) {
-       if ((ret = kadm5_get_policy(handle->lhandle,
-                                   adb.policy, &polent))
-           == KADM5_OK) {
-           polent.policy_refcnt--;
-           if ((ret = kadm5_modify_policy_internal(handle->lhandle, &polent,
-                                                        KADM5_REF_COUNT))
-               != KADM5_OK) {
-               (void) kadm5_free_policy_ent(handle->lhandle, &polent);
-               kdb_free_entry(handle, &kdb, &adb);
-               return(ret);
-           }
-       }
-       if (ret = kadm5_free_policy_ent(handle->lhandle, &polent)) {
-           kdb_free_entry(handle, &kdb, &adb);
-           return ret;
-       }
+        if ((ret = kadm5_get_policy(handle->lhandle,
+                                    adb.policy, &polent))
+            == KADM5_OK) {
+            polent.policy_refcnt--;
+            if ((ret = kadm5_modify_policy_internal(handle->lhandle, &polent,
+                                                    KADM5_REF_COUNT))
+                != KADM5_OK) {
+                (void) kadm5_free_policy_ent(handle->lhandle, &polent);
+                kdb_free_entry(handle, kdb, &adb);
+                return(ret);
+            }
+        }
+        if ((ret = kadm5_free_policy_ent(handle->lhandle, &polent))) {
+            kdb_free_entry(handle, kdb, &adb);
+            return ret;
+        }
     }
 
     ret = kdb_delete_entry(handle, principal);
 
-    kdb_free_entry(handle, &kdb, &adb);
+    kdb_free_entry(handle, kdb, &adb);
+
+    if (ret == 0)
+        (void) k5_kadm5_hook_remove(handle->context,
+                                    handle->hook_handles,
+                                    KADM5_HOOK_STAGE_POSTCOMMIT, principal);
 
     return ret;
 }
 
 kadm5_ret_t
 kadm5_modify_principal(void *server_handle,
-                           kadm5_principal_ent_t entry, long mask)
+                       kadm5_principal_ent_t entry, long mask)
 {
-    int                            ret, ret2, i;
+    int                     ret, ret2, i;
     kadm5_policy_ent_rec    npol, opol;
-    int                            have_npol = 0, have_opol = 0;
-    krb5_db_entry          kdb;
-    krb5_tl_data           *tl_data_orig, *tl_data_tail;
-    osa_princ_ent_rec      adb;
+    int                     have_npol = 0, have_opol = 0;
+    krb5_db_entry           *kdb;
+    krb5_tl_data            *tl_data_orig;
+    osa_princ_ent_rec       adb;
     kadm5_server_handle_t handle = server_handle;
 
     CHECK_HANDLE(server_handle);
 
+    krb5_clear_error_message(handle->context);
+
     if((mask & KADM5_PRINCIPAL) || (mask & KADM5_LAST_PWD_CHANGE) ||
        (mask & KADM5_MOD_TIME) || (mask & KADM5_MOD_NAME) ||
        (mask & KADM5_MKVNO) || (mask & KADM5_AUX_ATTRIBUTES) ||
        (mask & KADM5_KEY_DATA) || (mask & KADM5_LAST_SUCCESS) ||
        (mask & KADM5_LAST_FAILED))
-       return KADM5_BAD_MASK;
+        return KADM5_BAD_MASK;
     if((mask & ~ALL_PRINC_MASK))
-       return KADM5_BAD_MASK;
+        return KADM5_BAD_MASK;
     if((mask & KADM5_POLICY) && (mask & KADM5_POLICY_CLR))
-       return KADM5_BAD_MASK;
+        return KADM5_BAD_MASK;
     if(entry == (kadm5_principal_ent_t) NULL)
-       return EINVAL;
+        return EINVAL;
+    if (mask & KADM5_TL_DATA) {
+        tl_data_orig = entry->tl_data;
+        while (tl_data_orig) {
+            if (tl_data_orig->tl_data_type < 256)
+                return KADM5_BAD_TL_TYPE;
+            tl_data_orig = tl_data_orig->tl_data_next;
+        }
+    }
 
-    if (ret = kdb_get_entry(handle, entry->principal, &kdb, &adb))
-       return(ret);
+    ret = kdb_get_entry(handle, entry->principal, &kdb, &adb);
+    if (ret)
+        return(ret);
 
     /*
      * This is pretty much the same as create ...
      */
 
     if ((mask & KADM5_POLICY)) {
-        /* get the new policy */
-        ret = kadm5_get_policy(handle->lhandle, entry->policy, &npol);
-        if (ret) {
-             switch (ret) {
-             case EINVAL:
-                  ret = KADM5_BAD_POLICY;
-                  break;
-             case KADM5_UNK_POLICY:
-             case KADM5_BAD_POLICY:
-                  ret =  KADM5_UNK_POLICY;
-                  break;
-             }
-             goto done;
-        }
-        have_npol = 1;
-
-        /* if we already have a policy, get it to decrement the refcnt */
-        if(adb.aux_attributes & KADM5_POLICY) {
-             /* ... but not if the old and new are the same */
-             if(strcmp(adb.policy, entry->policy)) {
-                  ret = kadm5_get_policy(handle->lhandle,
-                                         adb.policy, &opol);
-                  switch(ret) {
-                  case EINVAL:
-                  case KADM5_BAD_POLICY:
-                  case KADM5_UNK_POLICY:
-                       break;
-                  case KADM5_OK:
-                       have_opol = 1;
-                       opol.policy_refcnt--;
-                       break;
-                  default:
-                       goto done;
-                       break;
-                  }
-                  npol.policy_refcnt++;
-             }
-        } else npol.policy_refcnt++;
-
-        /* set us up to use the new policy */
-        adb.aux_attributes |= KADM5_POLICY;
-        if (adb.policy)
-             free(adb.policy);
-        adb.policy = strdup(entry->policy);
-
-        /* set pw_max_life based on new policy */
-        if (npol.pw_max_life) {
-             if (ret = krb5_dbe_lookup_last_pwd_change(handle->context, &kdb,
-                                                       &(kdb.pw_expiration)))
-                  goto done;
-             kdb.pw_expiration += npol.pw_max_life;
-        } else {
-             kdb.pw_expiration = 0;
-        }
+        /* get the new policy */
+        ret = kadm5_get_policy(handle->lhandle, entry->policy, &npol);
+        if (ret) {
+            switch (ret) {
+            case EINVAL:
+                ret = KADM5_BAD_POLICY;
+                break;
+            case KADM5_UNK_POLICY:
+            case KADM5_BAD_POLICY:
+                ret =  KADM5_UNK_POLICY;
+                break;
+            }
+            goto done;
+        }
+        have_npol = 1;
+
+        /* if we already have a policy, get it to decrement the refcnt */
+        if(adb.aux_attributes & KADM5_POLICY) {
+            /* ... but not if the old and new are the same */
+            if(strcmp(adb.policy, entry->policy)) {
+                ret = kadm5_get_policy(handle->lhandle,
+                                       adb.policy, &opol);
+                switch(ret) {
+                case EINVAL:
+                case KADM5_BAD_POLICY:
+                case KADM5_UNK_POLICY:
+                    break;
+                case KADM5_OK:
+                    have_opol = 1;
+                    opol.policy_refcnt--;
+                    break;
+                default:
+                    goto done;
+                    break;
+                }
+                npol.policy_refcnt++;
+            }
+        } else npol.policy_refcnt++;
+
+        /* set us up to use the new policy */
+        adb.aux_attributes |= KADM5_POLICY;
+        if (adb.policy)
+            free(adb.policy);
+        adb.policy = strdup(entry->policy);
+
+        /* set pw_max_life based on new policy */
+        if (npol.pw_max_life) {
+            ret = krb5_dbe_lookup_last_pwd_change(handle->context, kdb,
+                                                  &(kdb->pw_expiration));
+            if (ret)
+                goto done;
+            kdb->pw_expiration += npol.pw_max_life;
+        } else {
+            kdb->pw_expiration = 0;
+        }
     }
 
     if ((mask & KADM5_POLICY_CLR) &&
-       (adb.aux_attributes & KADM5_POLICY)) {
-        ret = kadm5_get_policy(handle->lhandle, adb.policy, &opol);
-        switch(ret) {
-        case EINVAL:
-        case KADM5_BAD_POLICY:
-        case KADM5_UNK_POLICY:
-             ret = KADM5_BAD_DB;
-             goto done;
-             break;
-        case KADM5_OK:
-             have_opol = 1;
-             if (adb.policy)
-                  free(adb.policy);
-             adb.policy = NULL;
-             adb.aux_attributes &= ~KADM5_POLICY;
-             kdb.pw_expiration = 0;
-             opol.policy_refcnt--;
-             break;
-        default:
-             goto done;
-             break;
-        }
+        (adb.aux_attributes & KADM5_POLICY)) {
+        ret = kadm5_get_policy(handle->lhandle, adb.policy, &opol);
+        switch(ret) {
+        case EINVAL:
+        case KADM5_BAD_POLICY:
+        case KADM5_UNK_POLICY:
+            ret = KADM5_BAD_DB;
+            goto done;
+            break;
+        case KADM5_OK:
+            have_opol = 1;
+            if (adb.policy)
+                free(adb.policy);
+            adb.policy = NULL;
+            adb.aux_attributes &= ~KADM5_POLICY;
+            kdb->pw_expiration = 0;
+            opol.policy_refcnt--;
+            break;
+        default:
+            goto done;
+            break;
+        }
     }
 
     if (((mask & KADM5_POLICY) || (mask & KADM5_POLICY_CLR)) &&
-       (((have_opol) &&
-         (ret =
-          kadm5_modify_policy_internal(handle->lhandle, &opol,
-                                            KADM5_REF_COUNT))) ||
-        ((have_npol) &&
-         (ret =
-          kadm5_modify_policy_internal(handle->lhandle, &npol,
-                                            KADM5_REF_COUNT)))))
-       goto done;
-
-    if ((mask & KADM5_ATTRIBUTES)) 
-       kdb.attributes = entry->attributes;
+        (((have_opol) &&
+          (ret =
+           kadm5_modify_policy_internal(handle->lhandle, &opol,
+                                        KADM5_REF_COUNT))) ||
+         ((have_npol) &&
+          (ret =
+           kadm5_modify_policy_internal(handle->lhandle, &npol,
+                                        KADM5_REF_COUNT)))))
+        goto done;
+
+    if ((mask & KADM5_ATTRIBUTES))
+        kdb->attributes = entry->attributes;
     if ((mask & KADM5_MAX_LIFE))
-       kdb.max_life = entry->max_life;
+        kdb->max_life = entry->max_life;
     if ((mask & KADM5_PRINC_EXPIRE_TIME))
-       kdb.expiration = entry->princ_expire_time;
+        kdb->expiration = entry->princ_expire_time;
     if (mask & KADM5_PW_EXPIRATION)
-        kdb.pw_expiration = entry->pw_expiration;
+        kdb->pw_expiration = entry->pw_expiration;
     if (mask & KADM5_MAX_RLIFE)
-        kdb.max_renewable_life = entry->max_renewable_life;
-    if (mask & KADM5_FAIL_AUTH_COUNT)
-        kdb.fail_auth_count = entry->fail_auth_count;
-    
+        kdb->max_renewable_life = entry->max_renewable_life;
+
     if((mask & KADM5_KVNO)) {
-        for (i = 0; i < kdb.n_key_data; i++)
-             kdb.key_data[i].key_data_kvno = entry->kvno;
+        for (i = 0; i < kdb->n_key_data; i++)
+            kdb->key_data[i].key_data_kvno = entry->kvno;
     }
 
     if (mask & KADM5_TL_DATA) {
-        krb5_tl_data *tl;
-        /*
-         * Replace kdb.tl_data with what was passed in.  The
-         * KRB5_TL_KADM_DATA will be re-added (based on adb) by
-         * kdb_put_entry, below.
-         */
-        while (kdb.tl_data) {
-             tl = kdb.tl_data->tl_data_next;
-             free(kdb.tl_data->tl_data_contents);
-             free(kdb.tl_data);
-             kdb.tl_data = tl;
-        }
-        
-        kdb.tl_data = entry->tl_data;
-        kdb.n_tl_data = entry->n_tl_data;
-    }
-
-    if ((ret = kdb_put_entry(handle, &kdb, &adb)))
-       goto done;
+        krb5_tl_data *tl;
+
+        /* may have to change the version number of the API. Updates the list with the given tl_data rather than over-writting */
+
+        for (tl = entry->tl_data; tl;
+             tl = tl->tl_data_next)
+        {
+            ret = krb5_dbe_update_tl_data(handle->context, kdb, tl);
+            if( ret )
+            {
+                goto done;
+            }
+        }
+    }
+
+    /*
+     * Setting entry->fail_auth_count to 0 can be used to manually unlock
+     * an account. It is not possible to set fail_auth_count to any other
+     * value using kadmin.
+     */
+    if (mask & KADM5_FAIL_AUTH_COUNT) {
+        if (entry->fail_auth_count != 0) {
+            ret = KADM5_BAD_SERVER_PARAMS;
+            goto done;
+        }
+
+        kdb->fail_auth_count = 0;
+    }
+
+    /* let the mask propagate to the database provider */
+    kdb->mask = mask;
+
+    ret = k5_kadm5_hook_modify(handle->context, handle->hook_handles,
+                               KADM5_HOOK_STAGE_PRECOMMIT, entry, mask);
+    if (ret)
+        goto done;
+
+    ret = kdb_put_entry(handle, kdb, &adb);
+    if (ret) goto done;
+    (void) k5_kadm5_hook_modify(handle->context, handle->hook_handles,
+                                KADM5_HOOK_STAGE_POSTCOMMIT, entry, mask);
 
     ret = KADM5_OK;
 done:
     if (have_opol) {
-        ret2 = kadm5_free_policy_ent(handle->lhandle, &opol);
-        ret = ret ? ret : ret2;
+        ret2 = kadm5_free_policy_ent(handle->lhandle, &opol);
+        ret = ret ? ret : ret2;
     }
     if (have_npol) {
-        ret2 = kadm5_free_policy_ent(handle->lhandle, &npol);
-        ret = ret ? ret : ret2;
+        ret2 = kadm5_free_policy_ent(handle->lhandle, &npol);
+        ret = ret ? ret : ret2;
     }
-    kdb_free_entry(handle, &kdb, &adb);
+    kdb_free_entry(handle, kdb, &adb);
     return ret;
 }
-    
+
 kadm5_ret_t
 kadm5_rename_principal(void *server_handle,
-                           krb5_principal source, krb5_principal target)
+                       krb5_principal source, krb5_principal target)
 {
-    krb5_db_entry      kdb;
-    osa_princ_ent_rec  adb;
-    int                        ret, i;
+    krb5_db_entry *kdb;
+    osa_princ_ent_rec adb;
+    krb5_error_code ret;
     kadm5_server_handle_t handle = server_handle;
+    krb5_int16 stype, i;
+    krb5_data *salt = NULL;
 
     CHECK_HANDLE(server_handle);
 
+    krb5_clear_error_message(handle->context);
+
     if (source == NULL || target == NULL)
-       return EINVAL;
+        return EINVAL;
 
     if ((ret = kdb_get_entry(handle, target, &kdb, &adb)) == 0) {
-       kdb_free_entry(handle, &kdb, &adb);
-       return(KADM5_DUP);
+        kdb_free_entry(handle, kdb, &adb);
+        return(KADM5_DUP);
     }
 
     if ((ret = kdb_get_entry(handle, source, &kdb, &adb)))
-       return ret;
-
-    /* this is kinda gross, but unavoidable */
-
-    for (i=0; i<kdb.n_key_data; i++) {
-       if ((kdb.key_data[i].key_data_ver == 1) ||
-           (kdb.key_data[i].key_data_type[1] == KRB5_KDB_SALTTYPE_NORMAL)) {
-           ret = KADM5_NO_RENAME_SALT;
-           goto done;
-       }
+        return ret;
+
+    /* Transform salts as necessary. */
+    for (i = 0; i < kdb->n_key_data; i++) {
+        ret = krb5_dbe_compute_salt(handle->context, &kdb->key_data[i],
+                                    kdb->princ, &stype, &salt);
+        if (ret == KRB5_KDB_BAD_SALTTYPE)
+            ret = KADM5_NO_RENAME_SALT;
+        if (ret)
+            goto done;
+        kdb->key_data[i].key_data_type[1] = KRB5_KDB_SALTTYPE_SPECIAL;
+        free(kdb->key_data[i].key_data_contents[1]);
+        kdb->key_data[i].key_data_contents[1] = (krb5_octet *)salt->data;
+        kdb->key_data[i].key_data_length[1] = salt->length;
+        kdb->key_data[i].key_data_ver = 2;
+        free(salt);
+        salt = NULL;
     }
 
-    krb5_free_principal(handle->context, kdb.princ);
-    if (ret = krb5_copy_principal(handle->context, target, &kdb.princ)) {
-       kdb.princ = NULL; /* so freeing the dbe doesn't lose */
-       goto done;
+    kadm5_free_principal(handle->context, kdb->princ);
+    ret = kadm5_copy_principal(handle->context, target, &kdb->princ);
+    if (ret) {
+        kdb->princ = NULL; /* so freeing the dbe doesn't lose */
+        goto done;
     }
 
-    if ((ret = kdb_put_entry(handle, &kdb, &adb)))
-       goto done;
+    if ((ret = kdb_put_entry(handle, kdb, &adb)))
+        goto done;
 
     ret = kdb_delete_entry(handle, source);
 
 done:
-    kdb_free_entry(handle, &kdb, &adb);
+    krb5_free_data(handle->context, salt);
+    kdb_free_entry(handle, kdb, &adb);
     return ret;
 }
 
 kadm5_ret_t
 kadm5_get_principal(void *server_handle, krb5_principal principal,
-                   kadm5_principal_ent_t entry,
-                   long in_mask)
+                    kadm5_principal_ent_t entry,
+                    long in_mask)
 {
-    krb5_db_entry              kdb;
-    osa_princ_ent_rec          adb;
-    osa_adb_ret_t              ret = 0;
-    long                       mask;
+    krb5_db_entry               *kdb;
+    osa_princ_ent_rec           adb;
+    krb5_error_code             ret = 0;
+    long                        mask;
     int i;
     kadm5_server_handle_t handle = server_handle;
-    kadm5_principal_ent_rec    entry_local, *entry_orig;
 
     CHECK_HANDLE(server_handle);
 
+    krb5_clear_error_message(handle->context);
+
     /*
      * In version 1, all the defined fields are always returned.
      * entry is a pointer to a kadm5_principal_ent_t_v1 that should be
      * filled with allocated memory.
      */
-    if (handle->api_version == KADM5_API_VERSION_1) {
-        mask = KADM5_PRINCIPAL_NORMAL_MASK;
-        entry_orig = entry;
-        entry = &entry_local;
-    } else {
-        mask = in_mask;
-    }
+    mask = in_mask;
 
-    memset((char *) entry, 0, sizeof(*entry));
+    memset(entry, 0, sizeof(*entry));
 
     if (principal == NULL)
-       return EINVAL;
+        return EINVAL;
 
     if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
-       return ret;
+        return ret;
 
     if ((mask & KADM5_POLICY) &&
-       adb.policy && (adb.aux_attributes & KADM5_POLICY)) {
-       if ((entry->policy = (char *) malloc(strlen(adb.policy) + 1)) == NULL) {
-           ret = ENOMEM;
-           goto done;
-       }
-       strcpy(entry->policy, adb.policy);
+        adb.policy && (adb.aux_attributes & KADM5_POLICY)) {
+        if ((entry->policy = strdup(adb.policy)) == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
     }
 
     if (mask & KADM5_AUX_ATTRIBUTES)
-        entry->aux_attributes = adb.aux_attributes;
+        entry->aux_attributes = adb.aux_attributes;
 
     if ((mask & KADM5_PRINCIPAL) &&
-       (ret = krb5_copy_principal(handle->context, principal,
-                                  &entry->principal))) { 
-       goto done;
+        (ret = krb5_copy_principal(handle->context, kdb->princ,
+                                   &entry->principal))) {
+        goto done;
     }
 
     if (mask & KADM5_PRINC_EXPIRE_TIME)
-        entry->princ_expire_time = kdb.expiration;
+        entry->princ_expire_time = kdb->expiration;
 
     if ((mask & KADM5_LAST_PWD_CHANGE) &&
-       (ret = krb5_dbe_lookup_last_pwd_change(handle->context, &kdb,
-                                              &(entry->last_pwd_change)))) {
-       goto done;
+        (ret = krb5_dbe_lookup_last_pwd_change(handle->context, kdb,
+                                               &(entry->last_pwd_change)))) {
+        goto done;
     }
 
     if (mask & KADM5_PW_EXPIRATION)
-        entry->pw_expiration = kdb.pw_expiration;
+        entry->pw_expiration = kdb->pw_expiration;
     if (mask & KADM5_MAX_LIFE)
-        entry->max_life = kdb.max_life;
+        entry->max_life = kdb->max_life;
 
     /* this is a little non-sensical because the function returns two */
     /* values that must be checked separately against the mask */
     if ((mask & KADM5_MOD_NAME) || (mask & KADM5_MOD_TIME)) {
-        if (ret = krb5_dbe_lookup_mod_princ_data(handle->context, &kdb,
-                                                 &(entry->mod_date),
-                                                 &(entry->mod_name))) {
-             goto done;
-        }
-        if (! (mask & KADM5_MOD_TIME))
-             entry->mod_date = 0;
-        if (! (mask & KADM5_MOD_NAME)) {
-             krb5_free_principal(handle->context, entry->principal);
-             entry->principal = NULL;
-        }
+        ret = krb5_dbe_lookup_mod_princ_data(handle->context, kdb,
+                                             &(entry->mod_date),
+                                             &(entry->mod_name));
+        if (ret) {
+            goto done;
+        }
+
+        if (! (mask & KADM5_MOD_TIME))
+            entry->mod_date = 0;
+        if (! (mask & KADM5_MOD_NAME)) {
+            krb5_free_principal(handle->context, entry->mod_name);
+            entry->mod_name = NULL;
+        }
     }
 
     if (mask & KADM5_ATTRIBUTES)
-        entry->attributes = kdb.attributes;
+        entry->attributes = kdb->attributes;
 
     if (mask & KADM5_KVNO)
-        for (entry->kvno = 0, i=0; i<kdb.n_key_data; i++)
-             if (kdb.key_data[i].key_data_kvno > entry->kvno)
-                  entry->kvno = kdb.key_data[i].key_data_kvno;
-    
-    if (handle->api_version == KADM5_API_VERSION_2)
-        entry->mkvno = 0;
-    else {
-        /* XXX I'll be damned if I know how to deal with this one --marc */
-        entry->mkvno = 1;
+        for (entry->kvno = 0, i=0; i<kdb->n_key_data; i++)
+            if ((krb5_kvno) kdb->key_data[i].key_data_kvno > entry->kvno)
+                entry->kvno = kdb->key_data[i].key_data_kvno;
+
+    if (mask & KADM5_MKVNO) {
+        ret = krb5_dbe_get_mkvno(handle->context, kdb, &entry->mkvno);
+        if (ret)
+            goto done;
     }
 
-    /*
-     * The new fields that only exist in version 2 start here
-     */
-    if (handle->api_version == KADM5_API_VERSION_2) {
-        if (mask & KADM5_MAX_RLIFE)
-             entry->max_renewable_life = kdb.max_renewable_life;
-        if (mask & KADM5_LAST_SUCCESS)
-             entry->last_success = kdb.last_success;
-        if (mask & KADM5_LAST_FAILED)
-             entry->last_failed = kdb.last_failed;
-        if (mask & KADM5_FAIL_AUTH_COUNT)
-             entry->fail_auth_count = kdb.fail_auth_count;
-        if (mask & KADM5_TL_DATA) {
-             krb5_tl_data td, *tl, *tl2;
-
-             entry->n_tl_data = kdb.n_tl_data;
-             entry->tl_data = NULL;
-             
-             tl = kdb.tl_data;
-             while (tl) {
-                  if ((tl2 = dup_tl_data(tl)) == NULL) {
-                       ret = ENOMEM;
-                       goto done;
-                  }
-                  tl2->tl_data_next = entry->tl_data;
-                  entry->tl_data = tl2;
-
-                  tl = tl->tl_data_next;
-             }
-             
-             if (kdb.e_length) {
-                  td.tl_data_type = KRB5_TL_KADM5_E_DATA;
-                  td.tl_data_length = kdb.e_length;
-                  td.tl_data_contents = kdb.e_data;
-
-                  if ((tl = dup_tl_data(&td)) == NULL) {
-                       ret = ENOMEM;
-                       goto done;
-                  }
-                  tl->tl_data_next = entry->tl_data;
-                  entry->tl_data = tl;
-             }
-        }
-        if (mask & KADM5_KEY_DATA) {
-             entry->n_key_data = kdb.n_key_data;
-             if(entry->n_key_data) {
-                     entry->key_data = (krb5_key_data *)
-                             malloc(entry->n_key_data*sizeof(krb5_key_data));
-                     if (entry->key_data == NULL) {
-                             ret = ENOMEM;
-                             goto done;
-                     }
-             } else 
-                     entry->key_data = NULL;
-
-             for (i = 0; i < entry->n_key_data; i++)
-                  if (ret = krb5_copy_key_data_contents(handle->context,
-                                                        &kdb.key_data[i],
-                                                        &entry->key_data[i]))
-                       goto done;
-        }
+    if (mask & KADM5_MAX_RLIFE)
+        entry->max_renewable_life = kdb->max_renewable_life;
+    if (mask & KADM5_LAST_SUCCESS)
+        entry->last_success = kdb->last_success;
+    if (mask & KADM5_LAST_FAILED)
+        entry->last_failed = kdb->last_failed;
+    if (mask & KADM5_FAIL_AUTH_COUNT)
+        entry->fail_auth_count = kdb->fail_auth_count;
+    if (mask & KADM5_TL_DATA) {
+        krb5_tl_data *tl, *tl2;
+
+        entry->tl_data = NULL;
+
+        tl = kdb->tl_data;
+        while (tl) {
+            if (tl->tl_data_type > 255) {
+                if ((tl2 = dup_tl_data(tl)) == NULL) {
+                    ret = ENOMEM;
+                    goto done;
+                }
+                tl2->tl_data_next = entry->tl_data;
+                entry->tl_data = tl2;
+                entry->n_tl_data++;
+            }
+
+            tl = tl->tl_data_next;
+        }
     }
-
-    /*
-     * If KADM5_API_VERSION_1, we return an allocated structure, and
-     * we need to convert the new structure back into the format the
-     * caller is expecting.
-     */
-    if (handle->api_version == KADM5_API_VERSION_1) {
-        kadm5_principal_ent_t_v1 newv1;
-
-        newv1 = ((kadm5_principal_ent_t_v1) calloc(1, sizeof(*newv1)));
-        if (newv1 == NULL) {
-             ret = ENOMEM;
-             goto done;
-        }
-        
-        newv1->principal = entry->principal;
-        newv1->princ_expire_time = entry->princ_expire_time;
-        newv1->last_pwd_change = entry->last_pwd_change;
-        newv1->pw_expiration = entry->pw_expiration;
-        newv1->max_life = entry->max_life;
-        newv1->mod_name = entry->mod_name;
-        newv1->mod_date = entry->mod_date;
-        newv1->attributes = entry->attributes;
-        newv1->kvno = entry->kvno;
-        newv1->mkvno = entry->mkvno;
-        newv1->policy = entry->policy;
-        newv1->aux_attributes = entry->aux_attributes;
-
-        *((kadm5_principal_ent_t_v1 *) entry_orig) = newv1;
+    if (mask & KADM5_KEY_DATA) {
+        entry->n_key_data = kdb->n_key_data;
+        if(entry->n_key_data) {
+            entry->key_data = malloc(entry->n_key_data*sizeof(krb5_key_data));
+            if (entry->key_data == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+        } else
+            entry->key_data = NULL;
+
+        for (i = 0; i < entry->n_key_data; i++)
+            ret = krb5_copy_key_data_contents(handle->context,
+                                              &kdb->key_data[i],
+                                              &entry->key_data[i]);
+        if (ret)
+            goto done;
     }
 
     ret = KADM5_OK;
 
 done:
-    if (ret && entry->principal)
-        krb5_free_principal(handle->context, entry->principal);
-    kdb_free_entry(handle, &kdb, &adb);
+    if (ret && entry->principal) {
+        krb5_free_principal(handle->context, entry->principal);
+        entry->principal = NULL;
+    }
+    kdb_free_entry(handle, kdb, &adb);
 
     return ret;
 }
@@ -789,64 +941,67 @@ done:
  *
  * Arguments:
  *
- *     context                 (r) the krb5 context
- *     histkey_encblock        (r) the encblock that hist_key_data is
- *                             encrypted in
- *     n_new_key_data          (r) length of new_key_data
- *     new_key_data            (r) keys to check against
- *                             pw_hist_data, encrypted in histkey_encblock
- *     n_pw_hist_data          (r) length of pw_hist_data
- *     pw_hist_data            (r) passwords to check new_key_data against
+ *      context                 (r) the krb5 context
+ *      hist_keyblock           (r) the key that hist_key_data is
+ *                              encrypted in
+ *      n_new_key_data          (r) length of new_key_data
+ *      new_key_data            (r) keys to check against
+ *                              pw_hist_data, encrypted in hist_keyblock
+ *      n_pw_hist_data          (r) length of pw_hist_data
+ *      pw_hist_data            (r) passwords to check new_key_data against
  *
  * Effects:
  * For each new_key in new_key_data:
- *     decrypt new_key with the master_encblock
- *     for each password in pw_hist_data:
- *             for each hist_key in password:
- *                     decrypt hist_key with histkey_encblock
- *                     compare the new_key and hist_key
+ *      decrypt new_key with the master_keyblock
+ *      for each password in pw_hist_data:
+ *              for each hist_key in password:
+ *                      decrypt hist_key with hist_keyblock
+ *                      compare the new_key and hist_key
  *
  * Returns krb5 errors, KADM5_PASS_RESUSE if a key in
  * new_key_data is the same as a key in pw_hist_data, or 0.
  */
 static kadm5_ret_t
 check_pw_reuse(krb5_context context,
-              krb5_encrypt_block *histkey_encblock,
-              int n_new_key_data, krb5_key_data *new_key_data,
-              int n_pw_hist_data, osa_pw_hist_ent *pw_hist_data)
+               krb5_keyblock *hist_keyblocks,
+               int n_new_key_data, krb5_key_data *new_key_data,
+               unsigned int n_pw_hist_data, osa_pw_hist_ent *pw_hist_data)
 {
-    int x, y, z;
-    krb5_keyblock newkey, histkey;
+    unsigned int x, y, z;
+    krb5_keyblock newkey, histkey, *kb;
+    krb5_key_data *key_data;
     krb5_error_code ret;
 
-    for (x = 0; x < n_new_key_data; x++) {
-        if (ret = krb5_dbekd_decrypt_key_data(context,
-                                              &master_encblock,
-                                              &(new_key_data[x]),
-                                              &newkey, NULL))
-           return(ret);
-       for (y = 0; y < n_pw_hist_data; y++) {
-            for (z = 0; z < pw_hist_data[y].n_key_data; z++) {
-                 if (ret =
-                     krb5_dbekd_decrypt_key_data(context,
-                                                 histkey_encblock,
-                                                 &pw_hist_data[y].key_data[z],
-                                                 &histkey, NULL))
-                      return(ret);             
-                 
-                 if ((newkey.length == histkey.length) &&
-                     (newkey.enctype == histkey.enctype) &&
-                     (memcmp(newkey.contents, histkey.contents,
-                             histkey.length) == 0)) {
-                      krb5_free_keyblock_contents(context, &histkey);
-                      krb5_free_keyblock_contents(context, &newkey);
-                      
-                      return(KADM5_PASS_REUSE);
-                 }
-                 krb5_free_keyblock_contents(context, &histkey);
-            }
-       }
-       krb5_free_keyblock_contents(context, &newkey);
+    assert (n_new_key_data >= 0);
+    for (x = 0; x < (unsigned) n_new_key_data; x++) {
+        /* Check only entries with the most recent kvno. */
+        if (new_key_data[x].key_data_kvno != new_key_data[0].key_data_kvno)
+            break;
+        ret = krb5_dbe_decrypt_key_data(context, NULL, &(new_key_data[x]),
+                                        &newkey, NULL);
+        if (ret)
+            return(ret);
+        for (y = 0; y < n_pw_hist_data; y++) {
+            for (z = 0; z < (unsigned int) pw_hist_data[y].n_key_data; z++) {
+                for (kb = hist_keyblocks; kb->enctype != 0; kb++) {
+                    key_data = &pw_hist_data[y].key_data[z];
+                    ret = krb5_dbe_decrypt_key_data(context, kb, key_data,
+                                                    &histkey, NULL);
+                    if (ret)
+                        continue;
+                    if (newkey.length == histkey.length &&
+                        newkey.enctype == histkey.enctype &&
+                        memcmp(newkey.contents, histkey.contents,
+                               histkey.length) == 0) {
+                        krb5_free_keyblock_contents(context, &histkey);
+                        krb5_free_keyblock_contents(context, &newkey);
+                        return KADM5_PASS_REUSE;
+                    }
+                    krb5_free_keyblock_contents(context, &histkey);
+                }
+            }
+        }
+        krb5_free_keyblock_contents(context, &newkey);
     }
 
     return(0);
@@ -860,57 +1015,62 @@ check_pw_reuse(krb5_context context,
  *
  * Arguments:
  *
- *     context         (r) krb5_context to use
- *     n_key_data      (r) number of elements in key_data
- *     key_data        (r) keys to add to the history entry
- *     hist            (w) history entry to fill in
+ *      context         (r) krb5_context to use
+ *      mkey            (r) master keyblock to decrypt key data with
+ *      hist_key        (r) history keyblock to encrypt key data with
+ *      n_key_data      (r) number of elements in key_data
+ *      key_data        (r) keys to add to the history entry
+ *      hist            (w) history entry to fill in
  *
  * Effects:
  *
  * hist->key_data is allocated to store n_key_data key_datas.  Each
- * element of key_data is decrypted with master_encblock, re-encrypted
- * in hist_encblock, and added to hist->key_data.  hist->n_key_data is
+ * element of key_data is decrypted with master_keyblock, re-encrypted
+ * in hist_key, and added to hist->key_data.  hist->n_key_data is
  * set to n_key_data.
  */
-int create_history_entry(krb5_context context, int n_key_data,
-                        krb5_key_data *key_data, osa_pw_hist_ent *hist)
+static
+int create_history_entry(krb5_context context,
+                         krb5_keyblock *hist_key, int n_key_data,
+                         krb5_key_data *key_data, osa_pw_hist_ent *hist)
 {
-     int i, ret;
-     krb5_keyblock key;
-     krb5_keysalt salt;
-     
-     hist->key_data = (krb5_key_data*)malloc(n_key_data*sizeof(krb5_key_data));
-     if (hist->key_data == NULL)
-         return ENOMEM;
-     memset(hist->key_data, 0, n_key_data*sizeof(krb5_key_data));
-
-     for (i = 0; i < n_key_data; i++) {
-         if (ret = krb5_dbekd_decrypt_key_data(context,
-                                               &master_encblock,
-                                               &key_data[i],
-                                               &key, &salt))
-              return ret;
-         if (ret = krb5_dbekd_encrypt_key_data(context,
-                                               &hist_encblock,
-                                               &key, &salt,
-                                               key_data[i].key_data_kvno,
-                                               &hist->key_data[i]))
-              return ret;
-         krb5_free_keyblock_contents(context, &key);
-         /* krb5_free_keysalt(context, &salt); */
-     }
-
-     hist->n_key_data = n_key_data;
-     return 0;
+    int i, ret;
+    krb5_keyblock key;
+    krb5_keysalt salt;
+
+    hist->key_data = (krb5_key_data*)malloc(n_key_data*sizeof(krb5_key_data));
+    if (hist->key_data == NULL)
+        return ENOMEM;
+    memset(hist->key_data, 0, n_key_data*sizeof(krb5_key_data));
+
+    for (i = 0; i < n_key_data; i++) {
+        ret = krb5_dbe_decrypt_key_data(context, NULL, &key_data[i], &key,
+                                        &salt);
+        if (ret)
+            return ret;
+
+        ret = krb5_dbe_encrypt_key_data(context, hist_key, &key, &salt,
+                                        key_data[i].key_data_kvno,
+                                        &hist->key_data[i]);
+        if (ret)
+            return ret;
+
+        krb5_free_keyblock_contents(context, &key);
+        /* krb5_free_keysalt(context, &salt); */
+    }
+
+    hist->n_key_data = n_key_data;
+    return 0;
 }
 
-int free_history_entry(krb5_context context, osa_pw_hist_ent *hist)
+static
+void free_history_entry(krb5_context context, osa_pw_hist_ent *hist)
 {
-     int i;
+    int i;
 
-     for (i = 0; i < hist->n_key_data; i++)
-         krb5_free_key_data_contents(context, &hist->key_data[i]);
-     free(hist->key_data);
+    for (i = 0; i < hist->n_key_data; i++)
+        krb5_free_key_data_contents(context, &hist->key_data[i]);
+    free(hist->key_data);
 }
 
 /*
@@ -920,10 +1080,11 @@ int free_history_entry(krb5_context context, osa_pw_hist_ent *hist)
  *
  * Arguments:
  *
- *     context         (r) krb5_context to use
- *     adb             (r/w) admin principal entry to add keys to
- *     pol             (r) adb's policy
- *     pw              (r) keys for the password to add to adb's key history
+ *      context         (r) krb5_context to use
+ *      hist_kvno       (r) kvno of current history key
+ *      adb             (r/w) admin principal entry to add keys to
+ *      pol             (r) adb's policy
+ *      pw              (r) keys for the password to add to adb's key history
  *
  * Effects:
  *
@@ -940,331 +1101,1027 @@ int free_history_entry(krb5_context context, osa_pw_hist_ent *hist)
  * adb->old_key_len).
  */
 static kadm5_ret_t add_to_history(krb5_context context,
-                                 osa_princ_ent_t adb,
-                                 kadm5_policy_ent_t pol,
-                                 osa_pw_hist_ent *pw)
+                                  krb5_kvno hist_kvno,
+                                  osa_princ_ent_t adb,
+                                  kadm5_policy_ent_t pol,
+                                  osa_pw_hist_ent *pw)
+{
+    osa_pw_hist_ent *histp;
+    uint32_t nhist;
+    unsigned int i, knext, nkeys;
+
+    nhist = pol->pw_history_num;
+    /* A history of 1 means just check the current password */
+    if (nhist <= 1)
+        return 0;
+
+    if (adb->admin_history_kvno != hist_kvno) {
+        /* The history key has changed since the last password change, so we
+         * have to reset the password history. */
+        free(adb->old_keys);
+        adb->old_keys = NULL;
+        adb->old_key_len = 0;
+        adb->old_key_next = 0;
+        adb->admin_history_kvno = hist_kvno;
+    }
+
+    nkeys = adb->old_key_len;
+    knext = adb->old_key_next;
+    /* resize the adb->old_keys array if necessary */
+    if (nkeys + 1 < nhist) {
+        if (adb->old_keys == NULL) {
+            adb->old_keys = (osa_pw_hist_ent *)
+                malloc((nkeys + 1) * sizeof (osa_pw_hist_ent));
+        } else {
+            adb->old_keys = (osa_pw_hist_ent *)
+                realloc(adb->old_keys,
+                        (nkeys + 1) * sizeof (osa_pw_hist_ent));
+        }
+        if (adb->old_keys == NULL)
+            return(ENOMEM);
+
+        memset(&adb->old_keys[nkeys], 0, sizeof(osa_pw_hist_ent));
+        nkeys = ++adb->old_key_len;
+        /*
+         * To avoid losing old keys, shift forward each entry after
+         * knext.
+         */
+        for (i = nkeys - 1; i > knext; i--) {
+            adb->old_keys[i] = adb->old_keys[i - 1];
+        }
+        memset(&adb->old_keys[knext], 0, sizeof(osa_pw_hist_ent));
+    } else if (nkeys + 1 > nhist) {
+        /*
+         * The policy must have changed!  Shrink the array.
+         * Can't simply realloc() down, since it might be wrapped.
+         * To understand the arithmetic below, note that we are
+         * copying into new positions 0 .. N-1 from old positions
+         * old_key_next-N .. old_key_next-1, modulo old_key_len,
+         * where N = pw_history_num - 1 is the length of the
+         * shortened list.        Matt Crawford, FNAL
+         */
+        /*
+         * M = adb->old_key_len, N = pol->pw_history_num - 1
+         *
+         * tmp[0] .. tmp[N-1] = old[(knext-N)%M] .. old[(knext-1)%M]
+         */
+        int j;
+        osa_pw_hist_t tmp;
+
+        tmp = (osa_pw_hist_ent *)
+            malloc((nhist - 1) * sizeof (osa_pw_hist_ent));
+        if (tmp == NULL)
+            return ENOMEM;
+        for (i = 0; i < nhist - 1; i++) {
+            /*
+             * Add nkeys once before taking remainder to avoid
+             * negative values.
+             */
+            j = (i + nkeys + knext - (nhist - 1)) % nkeys;
+            tmp[i] = adb->old_keys[j];
+        }
+        /* Now free the ones we don't keep (the oldest ones) */
+        for (i = 0; i < nkeys - (nhist - 1); i++) {
+            j = (i + nkeys + knext) % nkeys;
+            histp = &adb->old_keys[j];
+            for (j = 0; j < histp->n_key_data; j++) {
+                krb5_free_key_data_contents(context, &histp->key_data[j]);
+            }
+            free(histp->key_data);
+        }
+        free(adb->old_keys);
+        adb->old_keys = tmp;
+        nkeys = adb->old_key_len = nhist - 1;
+        knext = adb->old_key_next = 0;
+    }
+
+    /*
+     * If nhist decreased since the last password change, and nkeys+1
+     * is less than the previous nhist, it is possible for knext to
+     * index into unallocated space.  This condition would not be
+     * caught by the resizing code above.
+     */
+    if (knext + 1 > nkeys)
+        knext = adb->old_key_next = 0;
+    /* free the old pw history entry if it contains data */
+    histp = &adb->old_keys[knext];
+    for (i = 0; i < (unsigned int) histp->n_key_data; i++)
+        krb5_free_key_data_contents(context, &histp->key_data[i]);
+    free(histp->key_data);
+
+    /* store the new entry */
+    adb->old_keys[knext] = *pw;
+
+    /* update the next pointer */
+    if (++adb->old_key_next == nhist - 1)
+        adb->old_key_next = 0;
+
+    return(0);
+}
+
+/* FIXME: don't use global variable for this */
+krb5_boolean use_password_server = 0;
+
+#ifdef USE_PASSWORD_SERVER
+static krb5_boolean
+kadm5_use_password_server (void)
+{
+    return use_password_server;
+}
+#endif
+
+void
+kadm5_set_use_password_server (void)
 {
-     osa_pw_hist_ent hist, *histp;
-     int ret, i;
-
-     /* A history of 1 means just check the current password */
-     if (pol->pw_history_num == 1)
-         return 0;
-
-     /* resize the adb->old_keys array if necessary */
-     if (adb->old_key_len < pol->pw_history_num-1) {
-         adb->old_keys = (osa_pw_hist_ent *)
-              realloc(adb->old_keys,
-                      (adb->old_key_len+1)*sizeof(osa_pw_hist_ent));
-         if (adb->old_keys == NULL)
-              return(ENOMEM);
-         
-         memset(&adb->old_keys[adb->old_key_len],0,sizeof(osa_pw_hist_ent)); 
-         adb->old_key_len++;
-     }
-
-     /* free the old pw history entry if it contains data */
-     histp = &adb->old_keys[adb->old_key_next];
-     for (i = 0; i < histp->n_key_data; i++)
-         krb5_free_key_data_contents(context, &histp->key_data[i]);
-     
-     /* store the new entry */
-     adb->old_keys[adb->old_key_next] = *pw;
-
-     /* update the next pointer */
-     if (++adb->old_key_next == pol->pw_history_num-1)
-              adb->old_key_next = 0;
-
-     return(0);
+    use_password_server = 1;
+}
+
+#ifdef USE_PASSWORD_SERVER
+
+/*
+ * kadm5_launch_task () runs a program (task_path) to synchronize the
+ * Apple password server with the Kerberos database.  Password server
+ * programs can receive arguments on the command line (task_argv)
+ * and a block of data via stdin (data_buffer).
+ *
+ * Because a failure to communicate with the tool results in the
+ * password server falling out of sync with the database,
+ * kadm5_launch_task() always fails if it can't talk to the tool.
+ */
+
+static kadm5_ret_t
+kadm5_launch_task (krb5_context context,
+                   const char *task_path, char * const task_argv[],
+                   const char *buffer)
+{
+    kadm5_ret_t ret;
+    int data_pipe[2];
+
+    ret = pipe (data_pipe);
+    if (ret)
+        ret = errno;
+
+    if (!ret) {
+        pid_t pid = fork ();
+        if (pid == -1) {
+            ret = errno;
+            close (data_pipe[0]);
+            close (data_pipe[1]);
+        } else if (pid == 0) {
+            /* The child: */
+
+            if (dup2 (data_pipe[0], STDIN_FILENO) == -1)
+                _exit (1);
+
+            close (data_pipe[0]);
+            close (data_pipe[1]);
+
+            execv (task_path, task_argv);
+
+            _exit (1); /* Fail if execv fails */
+        } else {
+            /* The parent: */
+            int status;
+
+            ret = 0;
+
+            close (data_pipe[0]);
+
+            /* Write out the buffer to the child, add \n */
+            if (buffer) {
+                if (krb5_net_write (context, data_pipe[1], buffer, strlen (buffer)) < 0
+                    || krb5_net_write (context, data_pipe[1], "\n", 1) < 0)
+                {
+                    /* kill the child to make sure waitpid() won't hang later */
+                    ret = errno;
+                    kill (pid, SIGKILL);
+                }
+            }
+            close (data_pipe[1]);
+
+            waitpid (pid, &status, 0);
+
+            if (!ret) {
+                if (WIFEXITED (status)) {
+                    /* child read password and exited.  Check the return value. */
+                    if ((WEXITSTATUS (status) != 0) && (WEXITSTATUS (status) != 252)) {
+                        ret = KRB5KDC_ERR_POLICY; /* password change rejected */
+                    }
+                } else {
+                    /* child read password but crashed or was killed */
+                    ret = KRB5KRB_ERR_GENERIC; /* FIXME: better error */
+                }
+            }
+        }
+    }
+
+    return ret;
 }
 
+#endif
+
 kadm5_ret_t
 kadm5_chpass_principal(void *server_handle,
-                           krb5_principal principal, char *password)
+                       krb5_principal principal, char *password)
 {
-    krb5_int32                 now;
-    kadm5_policy_ent_rec       pol;
-    osa_princ_ent_rec          adb;
-    krb5_db_entry              kdb, kdb_save;
-    int                                ret, ret2, last_pwd, i, hist_added;
-    int                                have_pol = 0;
-    kadm5_server_handle_t      handle = server_handle;
-    osa_pw_hist_ent            hist;
+    return
+        kadm5_chpass_principal_3(server_handle, principal, FALSE,
+                                 0, NULL, password);
+}
+
+kadm5_ret_t
+kadm5_chpass_principal_3(void *server_handle,
+                         krb5_principal principal, krb5_boolean keepold,
+                         int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
+                         char *password)
+{
+    krb5_int32                  now;
+    kadm5_policy_ent_rec        pol;
+    osa_princ_ent_rec           adb;
+    krb5_db_entry               *kdb;
+    int                         ret, ret2, last_pwd, hist_added;
+    int                         have_pol = 0;
+    kadm5_server_handle_t       handle = server_handle;
+    osa_pw_hist_ent             hist;
+    krb5_keyblock               *act_mkey, *hist_keyblocks = NULL;
+    krb5_kvno                   act_kvno, hist_kvno;
 
     CHECK_HANDLE(server_handle);
 
+    krb5_clear_error_message(handle->context);
+
     hist_added = 0;
     memset(&hist, 0, sizeof(hist));
 
     if (principal == NULL || password == NULL)
-       return EINVAL;
+        return EINVAL;
     if ((krb5_principal_compare(handle->context,
-                               principal, hist_princ)) == TRUE)
-       return KADM5_PROTECT_PRINCIPAL;
+                                principal, hist_princ)) == TRUE)
+        return KADM5_PROTECT_PRINCIPAL;
+
+    /* Use default keysalts if caller did not provide any. */
+    if (n_ks_tuple == 0) {
+        ks_tuple = handle->params.keysalts;
+        n_ks_tuple = handle->params.num_keysalts;
+    }
 
     if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
-       return(ret);
+        return(ret);
 
-    /* we are going to need the current keys after the new keys are set */
-    if ((ret = kdb_get_entry(handle, principal, &kdb_save, NULL))) {
-        kdb_free_entry(handle, &kdb, &adb);
-        return(ret);
-    }
-    
     if ((adb.aux_attributes & KADM5_POLICY)) {
-       if ((ret = kadm5_get_policy(handle->lhandle, adb.policy, &pol)))
-            goto done;
-       have_pol = 1;
+        if ((ret = kadm5_get_policy(handle->lhandle, adb.policy, &pol)))
+            goto done;
+        have_pol = 1;
+
+        /* Create a password history entry before we change kdb's key_data. */
+        ret = kdb_get_hist_key(handle, &hist_keyblocks, &hist_kvno);
+        if (ret)
+            goto done;
+        ret = create_history_entry(handle->context, &hist_keyblocks[0],
+                                   kdb->n_key_data, kdb->key_data, &hist);
+        if (ret)
+            goto done;
     }
 
-    if ((ret = passwd_check(handle, password, adb.aux_attributes &
-                           KADM5_POLICY, &pol, principal)))
-        goto done;
+    if ((ret = passwd_check(handle, password, have_pol ? &pol : NULL,
+                            principal)))
+        goto done;
+
+    ret = krb5_dbe_find_act_mkey(handle->context, active_mkey_list, &act_kvno,
+                                 &act_mkey);
+    if (ret)
+        goto done;
+
+    ret = krb5_dbe_cpw(handle->context, act_mkey, ks_tuple, n_ks_tuple,
+                       password, 0 /* increment kvno */,
+                       keepold, kdb);
+    if (ret)
+        goto done;
 
-    if (ret = krb5_dbe_cpw(handle->context, &master_encblock,
-                          handle->params.keysalts,
-                          handle->params.num_keysalts,
-                          password, 0 /* increment kvno */, &kdb))
-       goto done;
+    ret = krb5_dbe_update_mkvno(handle->context, kdb, act_kvno);
+    if (ret)
+        goto done;
 
-    kdb.attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE;
+    kdb->attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE;
+
+    ret = krb5_timeofday(handle->context, &now);
+    if (ret)
+        goto done;
 
-    if (ret = krb5_timeofday(handle->context, &now))
-        goto done;
-    
     if ((adb.aux_attributes & KADM5_POLICY)) {
-       /* the policy was loaded before */
+        /* the policy was loaded before */
 
-       if (ret = krb5_dbe_lookup_last_pwd_change(handle->context,
-                                                 &kdb, &last_pwd))
-            goto done;
+        ret = krb5_dbe_lookup_last_pwd_change(handle->context, kdb, &last_pwd);
+        if (ret)
+            goto done;
 
 #if 0
-        /*
-         * The spec says this check is overridden if the caller has
-         * modify privilege.  The admin server therefore makes this
-         * check itself (in chpass_principal_wrapper, misc.c). A
-         * local caller implicitly has all authorization bits.
-         */
-       if ((now - last_pwd) < pol.pw_min_life &&
-           !(kdb.attributes & KRB5_KDB_REQUIRES_PWCHANGE)) {
-            ret = KADM5_PASS_TOOSOON;
-            goto done;
-       }
+        /*
+         * The spec says this check is overridden if the caller has
+         * modify privilege.  The admin server therefore makes this
+         * check itself (in chpass_principal_wrapper, misc.c). A
+         * local caller implicitly has all authorization bits.
+         */
+        if ((now - last_pwd) < pol.pw_min_life &&
+            !(kdb->attributes & KRB5_KDB_REQUIRES_PWCHANGE)) {
+            ret = KADM5_PASS_TOOSOON;
+            goto done;
+        }
 #endif
 
-       if (ret = create_history_entry(handle->context,
-                                      kdb_save.n_key_data,
-                                      kdb_save.key_data, &hist))
-            goto done;
-
-       if (ret = check_pw_reuse(handle->context,
-                                &hist_encblock,
-                                kdb.n_key_data, kdb.key_data,
-                                1, &hist))
-            goto done;
-        
-       if (pol.pw_history_num > 1) {
-           if (adb.admin_history_kvno != hist_kvno) {
-               ret = KADM5_BAD_HIST_KEY;
-               goto done;
-           }
-
-           if (ret = check_pw_reuse(handle->context,
-                                    &hist_encblock,
-                                    kdb.n_key_data, kdb.key_data,
-                                    adb.old_key_len, adb.old_keys))
-               goto done;
-
-           if (ret = add_to_history(handle->context, &adb, &pol, &hist))
-                goto done;
-           hist_added = 1;
-       }
-
-       if (pol.pw_max_life)
-          kdb.pw_expiration = now + pol.pw_max_life;
-       else
-          kdb.pw_expiration = 0;
+        ret = check_pw_reuse(handle->context, hist_keyblocks,
+                             kdb->n_key_data, kdb->key_data,
+                             1, &hist);
+        if (ret)
+            goto done;
+
+        if (pol.pw_history_num > 1) {
+            /* If hist_kvno has changed since the last password change, we
+             * can't check the history. */
+            if (adb.admin_history_kvno == hist_kvno) {
+                ret = check_pw_reuse(handle->context, hist_keyblocks,
+                                     kdb->n_key_data, kdb->key_data,
+                                     adb.old_key_len, adb.old_keys);
+                if (ret)
+                    goto done;
+            }
+
+            ret = add_to_history(handle->context, hist_kvno, &adb, &pol,
+                                 &hist);
+            if (ret)
+                goto done;
+            hist_added = 1;
+        }
+
+        if (pol.pw_max_life)
+            kdb->pw_expiration = now + pol.pw_max_life;
+        else
+            kdb->pw_expiration = 0;
     } else {
-       kdb.pw_expiration = 0;
+        kdb->pw_expiration = 0;
     }
 
-    if (ret = krb5_dbe_update_last_pwd_change(handle->context, &kdb, now))
-       goto done;
+#ifdef USE_PASSWORD_SERVER
+    if (kadm5_use_password_server () &&
+        (krb5_princ_size (handle->context, principal) == 1)) {
+        krb5_data *princ = krb5_princ_component (handle->context, principal, 0);
+        const char *path = "/usr/sbin/mkpassdb";
+        char *argv[] = { "mkpassdb", "-setpassword", NULL, NULL };
+        char *pstring = NULL;
+
+        if (!ret) {
+            pstring = malloc ((princ->length + 1) * sizeof (char));
+            if (pstring == NULL) { ret = ENOMEM; }
+        }
+
+        if (!ret) {
+            memcpy (pstring, princ->data, princ->length);
+            pstring [princ->length] = '\0';
+            argv[2] = pstring;
+
+            ret = kadm5_launch_task (handle->context, path, argv, password);
+        }
+
+        if (pstring != NULL)
+            free (pstring);
+
+        if (ret)
+            goto done;
+    }
+#endif
 
-    if ((ret = kdb_put_entry(handle, &kdb, &adb)))
-       goto done;
+    ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now);
+    if (ret)
+        goto done;
 
+    /* unlock principal on this KDC */
+    kdb->fail_auth_count = 0;
+
+    /* key data and attributes changed, let the database provider know */
+    kdb->mask = KADM5_KEY_DATA | KADM5_ATTRIBUTES |
+        KADM5_FAIL_AUTH_COUNT;
+    /* | KADM5_CPW_FUNCTION */
+
+    ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
+                               KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold,
+                               n_ks_tuple, ks_tuple, password);
+    if (ret)
+        goto done;
+
+    if ((ret = kdb_put_entry(handle, kdb, &adb)))
+        goto done;
+
+    (void) k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
+                                KADM5_HOOK_STAGE_POSTCOMMIT, principal,
+                                keepold, n_ks_tuple, ks_tuple, password);
     ret = KADM5_OK;
 done:
     if (!hist_added && hist.key_data)
-        free_history_entry(handle->context, &hist);
-    kdb_free_entry(handle, &kdb, &adb);
-    kdb_free_entry(handle, &kdb_save, NULL);
-    krb5_dbe_free_contents(handle->context, &kdb);
+        free_history_entry(handle->context, &hist);
+    kdb_free_entry(handle, kdb, &adb);
+    kdb_free_keyblocks(handle, hist_keyblocks);
 
     if (have_pol && (ret2 = kadm5_free_policy_ent(handle->lhandle, &pol))
-       && !ret) 
-        ret = ret2;
+        && !ret)
+        ret = ret2;
 
     return ret;
 }
 
 kadm5_ret_t
 kadm5_randkey_principal(void *server_handle,
-                       krb5_principal principal,
-                       krb5_keyblock **keyblocks,
-                       int *n_keys)
+                        krb5_principal principal,
+                        krb5_keyblock **keyblocks,
+                        int *n_keys)
+{
+    return
+        kadm5_randkey_principal_3(server_handle, principal,
+                                  FALSE, 0, NULL,
+                                  keyblocks, n_keys);
+}
+kadm5_ret_t
+kadm5_randkey_principal_3(void *server_handle,
+                          krb5_principal principal,
+                          krb5_boolean keepold,
+                          int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
+                          krb5_keyblock **keyblocks,
+                          int *n_keys)
 {
-    krb5_db_entry              kdb;
-    osa_princ_ent_rec          adb;
-    krb5_int32                 now;
-    kadm5_policy_ent_rec       pol;
-    krb5_key_data              *key_data;
-    krb5_keyblock              *keyblock;
-    int                                ret, last_pwd, have_pol = 0;
-    kadm5_server_handle_t      handle = server_handle;
+    krb5_db_entry               *kdb;
+    osa_princ_ent_rec           adb;
+    krb5_int32                  now;
+    kadm5_policy_ent_rec        pol;
+    int                         ret, last_pwd, have_pol = 0;
+    kadm5_server_handle_t       handle = server_handle;
+    krb5_keyblock               *act_mkey;
 
     if (keyblocks)
-        *keyblocks = NULL;
+        *keyblocks = NULL;
 
     CHECK_HANDLE(server_handle);
 
+    /* Use default keysalts if caller did not provide any. */
+    if (n_ks_tuple == 0) {
+        ks_tuple = handle->params.keysalts;
+        n_ks_tuple = handle->params.num_keysalts;
+    }
+
+    krb5_clear_error_message(handle->context);
+
     if (principal == NULL)
-       return EINVAL;
-    if (hist_princ && /* this will be NULL when initializing the databse */
-       ((krb5_principal_compare(handle->context,
-                                principal, hist_princ)) == TRUE))
-       return KADM5_PROTECT_PRINCIPAL;
+        return EINVAL;
+    if (krb5_principal_compare(handle->context, principal, hist_princ)) {
+        /* If changing the history entry, the new entry must have exactly one
+         * key. */
+        if (keepold)
+            return KADM5_PROTECT_PRINCIPAL;
+        n_ks_tuple = 1;
+    }
 
     if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
-       return(ret);
+        return(ret);
+
+    ret = krb5_dbe_find_act_mkey(handle->context, active_mkey_list, NULL,
+                                 &act_mkey);
+    if (ret)
+        goto done;
 
-    if (ret = krb5_dbe_crk(handle->context, &master_encblock,
-                          handle->params.keysalts,
-                          handle->params.num_keysalts,
-                          &kdb))
-       goto done;
+    ret = krb5_dbe_crk(handle->context, act_mkey, ks_tuple, n_ks_tuple,
+                       keepold, kdb);
+    if (ret)
+        goto done;
 
-    kdb.attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE;
+    kdb->attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE;
 
-    if (ret = krb5_timeofday(handle->context, &now))
-       goto done;
+    ret = krb5_timeofday(handle->context, &now);
+    if (ret)
+        goto done;
 
     if ((adb.aux_attributes & KADM5_POLICY)) {
-       if ((ret = kadm5_get_policy(handle->lhandle, adb.policy,
-                                   &pol)) != KADM5_OK) 
-          goto done;
-       have_pol = 1;
+        if ((ret = kadm5_get_policy(handle->lhandle, adb.policy,
+                                    &pol)) != KADM5_OK)
+            goto done;
+        have_pol = 1;
 
-       if (ret = krb5_dbe_lookup_last_pwd_change(handle->context,
-                                                 &kdb, &last_pwd))
-            goto done;
+        ret = krb5_dbe_lookup_last_pwd_change(handle->context, kdb, &last_pwd);
+        if (ret)
+            goto done;
 
 #if 0
-        /*
-         * The spec says this check is overridden if the caller has
-         * modify privilege.  The admin server therefore makes this
-         * check itself (in chpass_principal_wrapper, misc.c).  A
-         * local caller implicitly has all authorization bits.
-         */
-       if((now - last_pwd) < pol.pw_min_life &&
-          !(kdb.attributes & KRB5_KDB_REQUIRES_PWCHANGE)) {
-            ret = KADM5_PASS_TOOSOON;
-            goto done;
-       }
+        /*
+         * The spec says this check is overridden if the caller has
+         * modify privilege.  The admin server therefore makes this
+         * check itself (in chpass_principal_wrapper, misc.c).  A
+         * local caller implicitly has all authorization bits.
+         */
+        if((now - last_pwd) < pol.pw_min_life &&
+           !(kdb->attributes & KRB5_KDB_REQUIRES_PWCHANGE)) {
+            ret = KADM5_PASS_TOOSOON;
+            goto done;
+        }
 #endif
 
-       if(pol.pw_history_num > 1) {
-           if(adb.admin_history_kvno != hist_kvno) {
-               ret = KADM5_BAD_HIST_KEY;
-               goto done;
-           }
-
-           if (ret = check_pw_reuse(handle->context,
-                                    &hist_encblock,
-                                    kdb.n_key_data, kdb.key_data,
-                                    adb.old_key_len, adb.old_keys))
-               goto done;
-       }
-       if (pol.pw_max_life)
-          kdb.pw_expiration = now + pol.pw_max_life;
-       else
-          kdb.pw_expiration = 0;
+        if (pol.pw_max_life)
+            kdb->pw_expiration = now + pol.pw_max_life;
+        else
+            kdb->pw_expiration = 0;
     } else {
-       kdb.pw_expiration = 0;
+        kdb->pw_expiration = 0;
     }
 
-    if (ret = krb5_dbe_update_last_pwd_change(handle->context, &kdb, now))
-        goto done;
+    ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now);
+    if (ret)
+        goto done;
+
+    /* unlock principal on this KDC */
+    kdb->fail_auth_count = 0;
 
     if (keyblocks) {
-        if (handle->api_version == KADM5_API_VERSION_1) {
-             /* Version 1 clients will expect to see a DES_CRC enctype. */
-             if (ret = krb5_dbe_find_enctype(handle->context, &kdb,
-                                             ENCTYPE_DES_CBC_CRC,
-                                             -1, -1, &key_data))
-                  goto done;
-
-             if (ret = decrypt_key_data(handle->context, 1, key_data,
-                                        keyblocks, NULL))
-                  goto done;
-        } else {
-             ret = decrypt_key_data(handle->context,
-                                    kdb.n_key_data, kdb.key_data,
-                                    keyblocks, n_keys);
-             if (ret)
-                  goto done;
-        }
-    }   
-    
-    if ((ret = kdb_put_entry(handle, &kdb, &adb)))
-       goto done;
+        ret = decrypt_key_data(handle->context,
+                               kdb->n_key_data, kdb->key_data,
+                               keyblocks, n_keys);
+        if (ret)
+            goto done;
+    }
+
+    /* key data changed, let the database provider know */
+    kdb->mask = KADM5_KEY_DATA | KADM5_FAIL_AUTH_COUNT;
+    /* | KADM5_RANDKEY_USED */;
+
+    ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
+                               KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold,
+                               n_ks_tuple, ks_tuple, NULL);
+    if (ret)
+        goto done;
+    if ((ret = kdb_put_entry(handle, kdb, &adb)))
+        goto done;
+
+    (void) k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
+                                KADM5_HOOK_STAGE_POSTCOMMIT, principal,
+                                keepold, n_ks_tuple, ks_tuple, NULL);
+    ret = KADM5_OK;
+done:
+    kdb_free_entry(handle, kdb, &adb);
+    if (have_pol)
+        kadm5_free_policy_ent(handle->lhandle, &pol);
+
+    return ret;
+}
+
+/*
+ * kadm5_setv4key_principal:
+ *
+ * Set only ONE key of the principal, removing all others.  This key
+ * must have the DES_CBC_CRC enctype and is entered as having the
+ * krb4 salttype.  This is to enable things like kadmind4 to work.
+ */
+kadm5_ret_t
+kadm5_setv4key_principal(void *server_handle,
+                         krb5_principal principal,
+                         krb5_keyblock *keyblock)
+{
+    krb5_db_entry               *kdb;
+    osa_princ_ent_rec           adb;
+    krb5_int32                  now;
+    kadm5_policy_ent_rec        pol;
+    krb5_keysalt                keysalt;
+    int                         i, k, kvno, ret, have_pol = 0;
+#if 0
+    int                         last_pwd;
+#endif
+    kadm5_server_handle_t       handle = server_handle;
+    krb5_key_data               tmp_key_data;
+    krb5_keyblock               *act_mkey;
+
+    memset( &tmp_key_data, 0, sizeof(tmp_key_data));
+
+    CHECK_HANDLE(server_handle);
+
+    krb5_clear_error_message(handle->context);
+
+    if (principal == NULL || keyblock == NULL)
+        return EINVAL;
+    if (hist_princ && /* this will be NULL when initializing the databse */
+        ((krb5_principal_compare(handle->context,
+                                 principal, hist_princ)) == TRUE))
+        return KADM5_PROTECT_PRINCIPAL;
+
+    if (keyblock->enctype != ENCTYPE_DES_CBC_CRC)
+        return KADM5_SETV4KEY_INVAL_ENCTYPE;
+
+    if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
+        return(ret);
+
+    for (kvno = 0, i=0; i<kdb->n_key_data; i++)
+        if (kdb->key_data[i].key_data_kvno > kvno)
+            kvno = kdb->key_data[i].key_data_kvno;
+
+    if (kdb->key_data != NULL)
+        cleanup_key_data(handle->context, kdb->n_key_data, kdb->key_data);
+
+    kdb->key_data = (krb5_key_data*)krb5_db_alloc(handle->context, NULL, sizeof(krb5_key_data));
+    if (kdb->key_data == NULL)
+        return ENOMEM;
+    memset(kdb->key_data, 0, sizeof(krb5_key_data));
+    kdb->n_key_data = 1;
+    keysalt.type = KRB5_KDB_SALTTYPE_V4;
+    /* XXX data.magic? */
+    keysalt.data.length = 0;
+    keysalt.data.data = NULL;
+
+    ret = krb5_dbe_find_act_mkey(handle->context, active_mkey_list, NULL,
+                                 &act_mkey);
+    if (ret)
+        goto done;
+
+    /* use tmp_key_data as temporary location and reallocate later */
+    ret = krb5_dbe_encrypt_key_data(handle->context, act_mkey, keyblock,
+                                    &keysalt, kvno + 1, &tmp_key_data);
+    if (ret) {
+        goto done;
+    }
+
+    for (k = 0; k < tmp_key_data.key_data_ver; k++) {
+        kdb->key_data->key_data_type[k] = tmp_key_data.key_data_type[k];
+        kdb->key_data->key_data_length[k] = tmp_key_data.key_data_length[k];
+        if (tmp_key_data.key_data_contents[k]) {
+            kdb->key_data->key_data_contents[k] = krb5_db_alloc(handle->context, NULL, tmp_key_data.key_data_length[k]);
+            if (kdb->key_data->key_data_contents[k] == NULL) {
+                cleanup_key_data(handle->context, kdb->n_key_data, kdb->key_data);
+                kdb->key_data = NULL;
+                kdb->n_key_data = 0;
+                ret = ENOMEM;
+                goto done;
+            }
+            memcpy (kdb->key_data->key_data_contents[k], tmp_key_data.key_data_contents[k], tmp_key_data.key_data_length[k]);
+
+            memset (tmp_key_data.key_data_contents[k], 0, tmp_key_data.key_data_length[k]);
+            free (tmp_key_data.key_data_contents[k]);
+            tmp_key_data.key_data_contents[k] = NULL;
+        }
+    }
+
+
+
+    kdb->attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE;
+
+    ret = krb5_timeofday(handle->context, &now);
+    if (ret)
+        goto done;
+
+    if ((adb.aux_attributes & KADM5_POLICY)) {
+        if ((ret = kadm5_get_policy(handle->lhandle, adb.policy,
+                                    &pol)) != KADM5_OK)
+            goto done;
+        have_pol = 1;
+
+#if 0
+        /*
+         * The spec says this check is overridden if the caller has
+         * modify privilege.  The admin server therefore makes this
+         * check itself (in chpass_principal_wrapper, misc.c).  A
+         * local caller implicitly has all authorization bits.
+         */
+        if (ret = krb5_dbe_lookup_last_pwd_change(handle->context,
+                                                  kdb, &last_pwd))
+            goto done;
+        if((now - last_pwd) < pol.pw_min_life &&
+           !(kdb->attributes & KRB5_KDB_REQUIRES_PWCHANGE)) {
+            ret = KADM5_PASS_TOOSOON;
+            goto done;
+        }
+#endif
+
+        if (pol.pw_max_life)
+            kdb->pw_expiration = now + pol.pw_max_life;
+        else
+            kdb->pw_expiration = 0;
+    } else {
+        kdb->pw_expiration = 0;
+    }
+
+    ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now);
+    if (ret)
+        goto done;
+
+    /* unlock principal on this KDC */
+    kdb->fail_auth_count = 0;
+
+    if ((ret = kdb_put_entry(handle, kdb, &adb)))
+        goto done;
+
+    ret = KADM5_OK;
+done:
+    for (i = 0; i < tmp_key_data.key_data_ver; i++) {
+        if (tmp_key_data.key_data_contents[i]) {
+            memset (tmp_key_data.key_data_contents[i], 0, tmp_key_data.key_data_length[i]);
+            free (tmp_key_data.key_data_contents[i]);
+        }
+    }
+
+    kdb_free_entry(handle, kdb, &adb);
+    if (have_pol)
+        kadm5_free_policy_ent(handle->lhandle, &pol);
+
+    return ret;
+}
+
+kadm5_ret_t
+kadm5_setkey_principal(void *server_handle,
+                       krb5_principal principal,
+                       krb5_keyblock *keyblocks,
+                       int n_keys)
+{
+    return
+        kadm5_setkey_principal_3(server_handle, principal,
+                                 FALSE, 0, NULL,
+                                 keyblocks, n_keys);
+}
+
+kadm5_ret_t
+kadm5_setkey_principal_3(void *server_handle,
+                         krb5_principal principal,
+                         krb5_boolean keepold,
+                         int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
+                         krb5_keyblock *keyblocks,
+                         int n_keys)
+{
+    krb5_db_entry               *kdb;
+    osa_princ_ent_rec           adb;
+    krb5_int32                  now;
+    kadm5_policy_ent_rec        pol;
+    krb5_key_data               *old_key_data;
+    int                         n_old_keys;
+    int                         i, j, k, kvno, ret, have_pol = 0;
+#if 0
+    int                         last_pwd;
+#endif
+    kadm5_server_handle_t       handle = server_handle;
+    krb5_boolean                similar;
+    krb5_keysalt                keysalt;
+    krb5_key_data         tmp_key_data;
+    krb5_key_data        *tptr;
+    krb5_keyblock               *act_mkey;
+
+    CHECK_HANDLE(server_handle);
+
+    krb5_clear_error_message(handle->context);
+
+    if (principal == NULL || keyblocks == NULL)
+        return EINVAL;
+    if (hist_princ && /* this will be NULL when initializing the databse */
+        ((krb5_principal_compare(handle->context,
+                                 principal, hist_princ)) == TRUE))
+        return KADM5_PROTECT_PRINCIPAL;
+
+    for (i = 0; i < n_keys; i++) {
+        for (j = i+1; j < n_keys; j++) {
+            if ((ret = krb5_c_enctype_compare(handle->context,
+                                              keyblocks[i].enctype,
+                                              keyblocks[j].enctype,
+                                              &similar)))
+                return(ret);
+            if (similar) {
+                if (n_ks_tuple) {
+                    if (ks_tuple[i].ks_salttype == ks_tuple[j].ks_salttype)
+                        return KADM5_SETKEY_DUP_ENCTYPES;
+                } else
+                    return KADM5_SETKEY_DUP_ENCTYPES;
+            }
+        }
+    }
+
+    if (n_ks_tuple && n_ks_tuple != n_keys)
+        return KADM5_SETKEY3_ETYPE_MISMATCH;
+
+    if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
+        return(ret);
+
+    for (kvno = 0, i=0; i<kdb->n_key_data; i++)
+        if (kdb->key_data[i].key_data_kvno > kvno)
+            kvno = kdb->key_data[i].key_data_kvno;
+
+    if (keepold) {
+        old_key_data = kdb->key_data;
+        n_old_keys = kdb->n_key_data;
+    } else {
+        if (kdb->key_data != NULL)
+            cleanup_key_data(handle->context, kdb->n_key_data, kdb->key_data);
+        n_old_keys = 0;
+        old_key_data = NULL;
+    }
+
+    kdb->key_data = (krb5_key_data*)krb5_db_alloc(handle->context, NULL, (n_keys+n_old_keys)
+                                                  *sizeof(krb5_key_data));
+    if (kdb->key_data == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    memset(kdb->key_data, 0, (n_keys+n_old_keys)*sizeof(krb5_key_data));
+    kdb->n_key_data = 0;
+
+    for (i = 0; i < n_keys; i++) {
+        if (n_ks_tuple) {
+            keysalt.type = ks_tuple[i].ks_salttype;
+            keysalt.data.length = 0;
+            keysalt.data.data = NULL;
+            if (ks_tuple[i].ks_enctype != keyblocks[i].enctype) {
+                ret = KADM5_SETKEY3_ETYPE_MISMATCH;
+                goto done;
+            }
+        }
+        memset (&tmp_key_data, 0, sizeof(tmp_key_data));
+
+        ret = krb5_dbe_find_act_mkey(handle->context, active_mkey_list, NULL,
+                                     &act_mkey);
+        if (ret)
+            goto done;
+
+        ret = krb5_dbe_encrypt_key_data(handle->context, act_mkey,
+                                        &keyblocks[i],
+                                        n_ks_tuple ? &keysalt : NULL, kvno + 1,
+                                        &tmp_key_data);
+        if (ret)
+            goto done;
+
+        tptr = &kdb->key_data[i];
+        tptr->key_data_ver = tmp_key_data.key_data_ver;
+        tptr->key_data_kvno = tmp_key_data.key_data_kvno;
+        for (k = 0; k < tmp_key_data.key_data_ver; k++) {
+            tptr->key_data_type[k] = tmp_key_data.key_data_type[k];
+            tptr->key_data_length[k] = tmp_key_data.key_data_length[k];
+            if (tmp_key_data.key_data_contents[k]) {
+                tptr->key_data_contents[k] = krb5_db_alloc(handle->context, NULL, tmp_key_data.key_data_length[k]);
+                if (tptr->key_data_contents[k] == NULL) {
+                    int i1;
+                    for (i1 = k; i1 < tmp_key_data.key_data_ver; i1++) {
+                        if (tmp_key_data.key_data_contents[i1]) {
+                            memset (tmp_key_data.key_data_contents[i1], 0, tmp_key_data.key_data_length[i1]);
+                            free (tmp_key_data.key_data_contents[i1]);
+                        }
+                    }
+
+                    ret =  ENOMEM;
+                    goto done;
+                }
+                memcpy (tptr->key_data_contents[k], tmp_key_data.key_data_contents[k], tmp_key_data.key_data_length[k]);
+
+                memset (tmp_key_data.key_data_contents[k], 0, tmp_key_data.key_data_length[k]);
+                free (tmp_key_data.key_data_contents[k]);
+                tmp_key_data.key_data_contents[k] = NULL;
+            }
+        }
+        kdb->n_key_data++;
+    }
+
+    /* copy old key data if necessary */
+    for (i = 0; i < n_old_keys; i++) {
+        kdb->key_data[i+n_keys] = old_key_data[i];
+        memset(&old_key_data[i], 0, sizeof (krb5_key_data));
+        kdb->n_key_data++;
+    }
+
+    if (old_key_data)
+        krb5_db_free(handle->context, old_key_data);
+
+    /* assert(kdb->n_key_data == n_keys + n_old_keys) */
+    kdb->attributes &= ~KRB5_KDB_REQUIRES_PWCHANGE;
+
+    if ((ret = krb5_timeofday(handle->context, &now)))
+        goto done;
+
+    if ((adb.aux_attributes & KADM5_POLICY)) {
+        if ((ret = kadm5_get_policy(handle->lhandle, adb.policy,
+                                    &pol)) != KADM5_OK)
+            goto done;
+        have_pol = 1;
+
+#if 0
+        /*
+         * The spec says this check is overridden if the caller has
+         * modify privilege.  The admin server therefore makes this
+         * check itself (in chpass_principal_wrapper, misc.c).  A
+         * local caller implicitly has all authorization bits.
+         */
+        if (ret = krb5_dbe_lookup_last_pwd_change(handle->context,
+                                                  kdb, &last_pwd))
+            goto done;
+        if((now - last_pwd) < pol.pw_min_life &&
+           !(kdb->attributes & KRB5_KDB_REQUIRES_PWCHANGE)) {
+            ret = KADM5_PASS_TOOSOON;
+            goto done;
+        }
+#endif
+
+        if (pol.pw_max_life)
+            kdb->pw_expiration = now + pol.pw_max_life;
+        else
+            kdb->pw_expiration = 0;
+    } else {
+        kdb->pw_expiration = 0;
+    }
+
+    if ((ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now)))
+        goto done;
+
+    /* unlock principal on this KDC */
+    kdb->fail_auth_count = 0;
+
+    if ((ret = kdb_put_entry(handle, kdb, &adb)))
+        goto done;
 
     ret = KADM5_OK;
 done:
-    kdb_free_entry(handle, &kdb, &adb);
+    kdb_free_entry(handle, kdb, &adb);
     if (have_pol)
-        kadm5_free_policy_ent(handle->lhandle, &pol);
+        kadm5_free_policy_ent(handle->lhandle, &pol);
 
     return ret;
 }
 
+/*
+ * Return the list of keys like kadm5_randkey_principal,
+ * but don't modify the principal.
+ */
+kadm5_ret_t
+kadm5_get_principal_keys(void *server_handle /* IN */,
+                         krb5_principal principal /* IN */,
+                         krb5_keyblock **keyblocks /* OUT */,
+                         int *n_keys /* OUT */)
+{
+    krb5_db_entry               *kdb;
+    osa_princ_ent_rec           adb;
+    kadm5_ret_t                 ret;
+    kadm5_server_handle_t       handle = server_handle;
+
+    if (keyblocks)
+        *keyblocks = NULL;
+
+    CHECK_HANDLE(server_handle);
+
+    if (principal == NULL)
+        return EINVAL;
+
+    if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
+        return(ret);
+
+    if (keyblocks) {
+        ret = decrypt_key_data(handle->context,
+                               kdb->n_key_data, kdb->key_data,
+                               keyblocks, n_keys);
+        if (ret)
+            goto done;
+    }
+
+    ret = KADM5_OK;
+done:
+    kdb_free_entry(handle, kdb, &adb);
+
+    return ret;
+}
+
+
 /*
  * Allocate an array of n_key_data krb5_keyblocks, fill in each
- * element with the results of decrypting the nth key in key_data with
- * master_encblock, and if n_keys is not NULL fill it in with the
+ * element with the results of decrypting the nth key in key_data,
+ * and if n_keys is not NULL fill it in with the
  * number of keys decrypted.
  */
 static int decrypt_key_data(krb5_context context,
-                           int n_key_data, krb5_key_data *key_data,
-                           krb5_keyblock **keyblocks, int *n_keys)
+                            int n_key_data, krb5_key_data *key_data,
+                            krb5_keyblock **keyblocks, int *n_keys)
 {
-     krb5_keyblock *keys;
-     int ret, i;
-
-     keys = (krb5_keyblock *) malloc(n_key_data*sizeof(krb5_keyblock));
-     if (keys == NULL)
-         return ENOMEM;
-     memset((char *) keys, 0, n_key_data*sizeof(krb5_keyblock));
-
-     for (i = 0; i < n_key_data; i++) {
-         if (ret = krb5_dbekd_decrypt_key_data(context,
-                                               &master_encblock,
-                                               &key_data[i], 
-                                               &keys[i], NULL)) {
-
-              memset((char *) keys, 0, n_key_data*sizeof(krb5_keyblock));
-              free(keys);
-              return ret;
-         }
-     }
-
-     *keyblocks = keys;
-     if (n_keys)
-         *n_keys = n_key_data;
-
-     return 0;
+    krb5_keyblock *keys;
+    int ret, i;
+
+    keys = (krb5_keyblock *) malloc(n_key_data*sizeof(krb5_keyblock));
+    if (keys == NULL)
+        return ENOMEM;
+    memset(keys, 0, n_key_data*sizeof(krb5_keyblock));
+
+    for (i = 0; i < n_key_data; i++) {
+        ret = krb5_dbe_decrypt_key_data(context, NULL, &key_data[i], &keys[i],
+                                        NULL);
+        if (ret) {
+            for (; i >= 0; i--) {
+                if (keys[i].contents) {
+                    memset (keys[i].contents, 0, keys[i].length);
+                    free( keys[i].contents );
+                }
+            }
+
+            memset(keys, 0, n_key_data*sizeof(krb5_keyblock));
+            free(keys);
+            return ret;
+        }
+    }
+
+    *keyblocks = keys;
+    if (n_keys)
+        *n_keys = n_key_data;
+
+    return 0;
 }
 
 /*
@@ -1274,15 +2131,15 @@ static int decrypt_key_data(krb5_context context,
  *
  * Arguments:
  *
- *     server_handle   (r) kadm5 handle
- *     entry           (r) principal retrieved with kadm5_get_principal
- *     ktype           (r) enctype to search for, or -1 to ignore
- *     stype           (r) salt type to search for, or -1 to ignore
- *     kvno            (r) kvno to search for, -1 for max, 0 for max
- *                     only if it also matches ktype and stype
- *     keyblock        (w) keyblock to fill in
- *     keysalt         (w) keysalt to fill in, or NULL
- *     kvnop           (w) kvno to fill in, or NULL
+ *      server_handle   (r) kadm5 handle
+ *      entry           (r) principal retrieved with kadm5_get_principal
+ *      ktype           (r) enctype to search for, or -1 to ignore
+ *      stype           (r) salt type to search for, or -1 to ignore
+ *      kvno            (r) kvno to search for, -1 for max, 0 for max
+ *                      only if it also matches ktype and stype
+ *      keyblock        (w) keyblock to fill in
+ *      keysalt         (w) keysalt to fill in, or NULL
+ *      kvnop           (w) kvno to fill in, or NULL
  *
  * Effects: Searches the key_data array of entry, which must have been
  * retrived with kadm5_get_principal with the KADM5_KEY_DATA mask, to
@@ -1298,35 +2155,173 @@ static int decrypt_key_data(krb5_context context,
  * returned.
  */
 kadm5_ret_t kadm5_decrypt_key(void *server_handle,
-                             kadm5_principal_ent_t entry, krb5_int32
-                             ktype, krb5_int32 stype, krb5_int32
-                             kvno, krb5_keyblock *keyblock,
-                             krb5_keysalt *keysalt, int *kvnop)
+                              kadm5_principal_ent_t entry, krb5_int32
+                              ktype, krb5_int32 stype, krb5_int32
+                              kvno, krb5_keyblock *keyblock,
+                              krb5_keysalt *keysalt, int *kvnop)
 {
     kadm5_server_handle_t handle = server_handle;
     krb5_db_entry dbent;
     krb5_key_data *key_data;
+    krb5_keyblock *mkey_ptr;
     int ret;
 
     CHECK_HANDLE(server_handle);
 
     if (entry->n_key_data == 0 || entry->key_data == NULL)
-        return EINVAL;
+        return EINVAL;
 
     /* find_enctype only uses these two fields */
     dbent.n_key_data = entry->n_key_data;
     dbent.key_data = entry->key_data;
-    if (ret = krb5_dbe_find_enctype(handle->context, &dbent, ktype,
-                                   stype, kvno, &key_data))
-        return ret;
+    if ((ret = krb5_dbe_find_enctype(handle->context, &dbent, ktype,
+                                     stype, kvno, &key_data)))
+        return ret;
+
+    /* find_mkey only uses this field */
+    dbent.tl_data = entry->tl_data;
+    if ((ret = krb5_dbe_find_mkey(handle->context, &dbent, &mkey_ptr))) {
+        /* try refreshing master key list */
+        /* XXX it would nice if we had the mkvno here for optimization */
+        if (krb5_db_fetch_mkey_list(handle->context, master_princ,
+                                    &master_keyblock) == 0) {
+            if ((ret = krb5_dbe_find_mkey(handle->context, &dbent,
+                                          &mkey_ptr))) {
+                return ret;
+            }
+        } else {
+            return ret;
+        }
+    }
+
+    if ((ret = krb5_dbe_decrypt_key_data(handle->context, NULL, key_data,
+                                         keyblock, keysalt)))
+        return ret;
 
-    if (ret = krb5_dbekd_decrypt_key_data(handle->context,
-                                         &master_encblock, key_data,
-                                         keyblock, keysalt))
-        return ret;
+    /*
+     * Coerce the enctype of the output keyblock in case we got an
+     * inexact match on the enctype; this behavior will go away when
+     * the key storage architecture gets redesigned for 1.3.
+     */
+    if (ktype != -1)
+        keyblock->enctype = ktype;
 
     if (kvnop)
-        *kvnop = key_data->key_data_kvno;
+        *kvnop = key_data->key_data_kvno;
 
     return KADM5_OK;
 }
+
+kadm5_ret_t
+kadm5_purgekeys(void *server_handle,
+                krb5_principal principal,
+                int keepkvno)
+{
+    kadm5_server_handle_t handle = server_handle;
+    kadm5_ret_t ret;
+    krb5_db_entry *kdb;
+    osa_princ_ent_rec adb;
+    krb5_key_data *old_keydata;
+    int n_old_keydata;
+    int i, j, k;
+
+    CHECK_HANDLE(server_handle);
+
+    if (principal == NULL)
+        return EINVAL;
+
+    ret = kdb_get_entry(handle, principal, &kdb, &adb);
+    if (ret)
+        return(ret);
+
+    if (keepkvno <= 0) {
+        keepkvno = krb5_db_get_key_data_kvno(handle->context, kdb->n_key_data,
+                                             kdb->key_data);
+    }
+
+    old_keydata = kdb->key_data;
+    n_old_keydata = kdb->n_key_data;
+    kdb->n_key_data = 0;
+    kdb->key_data = krb5_db_alloc(handle->context, NULL,
+                                  n_old_keydata * sizeof(krb5_key_data));
+    if (kdb->key_data == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+    memset(kdb->key_data, 0, n_old_keydata * sizeof(krb5_key_data));
+    for (i = 0, j = 0; i < n_old_keydata; i++) {
+        if (old_keydata[i].key_data_kvno < keepkvno)
+            continue;
+
+        /* Alias the key_data_contents pointers; we null them out in the
+         * source array immediately after. */
+        kdb->key_data[j] = old_keydata[i];
+        for (k = 0; k < old_keydata[i].key_data_ver; k++) {
+            old_keydata[i].key_data_contents[k] = NULL;
+        }
+        j++;
+    }
+    kdb->n_key_data = j;
+    cleanup_key_data(handle->context, n_old_keydata, old_keydata);
+
+    kdb->mask = KADM5_KEY_DATA;
+    ret = kdb_put_entry(handle, kdb, &adb);
+    if (ret)
+        goto done;
+
+done:
+    kdb_free_entry(handle, kdb, &adb);
+    return ret;
+}
+
+kadm5_ret_t
+kadm5_get_strings(void *server_handle, krb5_principal principal,
+                  krb5_string_attr **strings_out, int *count_out)
+{
+    kadm5_server_handle_t handle = server_handle;
+    kadm5_ret_t ret;
+    krb5_db_entry *kdb = NULL;
+
+    *strings_out = NULL;
+    *count_out = 0;
+    CHECK_HANDLE(server_handle);
+    if (principal == NULL)
+        return EINVAL;
+
+    ret = kdb_get_entry(handle, principal, &kdb, NULL);
+    if (ret)
+        return ret;
+
+    ret = krb5_dbe_get_strings(handle->context, kdb, strings_out, count_out);
+    kdb_free_entry(handle, kdb, NULL);
+    return ret;
+}
+
+kadm5_ret_t
+kadm5_set_string(void *server_handle, krb5_principal principal,
+                 const char *key, const char *value)
+{
+    kadm5_server_handle_t handle = server_handle;
+    kadm5_ret_t ret;
+    krb5_db_entry *kdb;
+    osa_princ_ent_rec adb;
+
+    CHECK_HANDLE(server_handle);
+    if (principal == NULL || key == NULL)
+        return EINVAL;
+
+    ret = kdb_get_entry(handle, principal, &kdb, &adb);
+    if (ret)
+        return ret;
+
+    ret = krb5_dbe_set_string(handle->context, kdb, key, value);
+    if (ret)
+        goto done;
+
+    kdb->mask = KADM5_TL_DATA;
+    ret = kdb_put_entry(handle, kdb, &adb);
+
+done:
+    kdb_free_entry(handle, kdb, &adb);
+    return ret;
+}