--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/components/krb5/patches/075-ldap-pw-hist.patch Fri Nov 04 09:14:49 2016 -0700
@@ -0,0 +1,1126 @@
+#
+# This patch cherry-picks Password history in LDAP KDB plugin feature from
+# MIT krb5 1.15.
+#
+# It is 1-1 port of the following changesets:
+# 44ad57d8d38efc944f64536354435f5b721c0ee0
+# d7f91ac2f6655e77bb3658c2c8cc6132f958a340
+# b46cce2ea8c0841f7f93db73eefcd180c87a3eae
+# 9526953f36b39323ec07448a5f218d27c6f1c76f
+#
+# Patch source: upstream
+#
+# When upgrading to MIT krb5 1.15 this patch will be dropped.
+#
+--- a/src/include/kdb.h
++++ b/src/include/kdb.h
+@@ -1,6 +1,6 @@
+ /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+ /*
+- * Copyright 1990,1991 by the Massachusetts Institute of Technology.
++ * Copyright 1990, 1991, 2016 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+@@ -209,6 +209,8 @@ typedef struct _krb5_db_entry_new {
+
+ krb5_principal princ; /* Length, data */
+ krb5_tl_data * tl_data; /* Linked list */
++
++ /* key_data must be sorted by kvno in descending order. */
+ krb5_key_data * key_data; /* Array */
+ } krb5_db_entry;
+
+@@ -683,6 +685,19 @@ krb5_error_code krb5_db_check_allowed_to
+ const krb5_db_entry *server,
+ krb5_const_principal proxy);
+
++/**
++ * Sort an array of @a krb5_key_data keys in descending order by their kvno.
++ * Key data order within a kvno is preserved.
++ *
++ * @param key_data
++ * The @a krb5_key_data array to sort. This is sorted in place so the
++ * array will be modified.
++ * @param key_data_length
++ * The length of @a key_data.
++ */
++void
++krb5_dbe_sort_key_data(krb5_key_data *key_data, size_t key_data_length);
++
+ /* default functions. Should not be directly called */
+ /*
+ * Default functions prototype
+--- a/src/lib/kadm5/admin.h
++++ b/src/lib/kadm5/admin.h
+@@ -113,7 +113,7 @@ typedef long kadm5_ret_t;
+ #define KADM5_RANDKEY_USED 0x100000
+ #endif
+ #define KADM5_LOAD 0x200000
+-#define KADM5_NOKEY 0x400000
++#define KADM5_KEY_HIST 0x400000
+
+ /* all but KEY_DATA, TL_DATA, LOAD */
+ #define KADM5_PRINCIPAL_NORMAL_MASK 0x41ffff
+--- a/src/lib/kadm5/srv/svr_principal.c
++++ b/src/lib/kadm5/srv/svr_principal.c
+@@ -1084,6 +1084,16 @@ check_pw_reuse(krb5_context context,
+ return(0);
+ }
+
++static void
++free_history_entry(krb5_context context, osa_pw_hist_ent *hist)
++{
++ 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);
++}
++
+ /*
+ * Function: create_history_entry
+ *
+@@ -1097,7 +1107,7 @@ check_pw_reuse(krb5_context context,
+ * 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
++ * hist_out (w) history entry to fill in
+ *
+ * Effects:
+ *
+@@ -1109,45 +1119,62 @@ check_pw_reuse(krb5_context context,
+ 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)
++ krb5_key_data *key_data, osa_pw_hist_ent *hist_out)
+ {
+- krb5_error_code ret;
++ int i;
++ krb5_error_code ret = 0;
+ krb5_keyblock key;
+ krb5_keysalt salt;
+- int i;
++ krb5_ui_2 kvno;
++ osa_pw_hist_ent hist;
++
++ hist_out->key_data = NULL;
++ hist_out->n_key_data = 0;
++
++ if (n_key_data < 0)
++ return EINVAL;
++
++ memset(&key, 0, sizeof(key));
++ memset(&hist, 0, sizeof(hist));
++
++ if (n_key_data == 0)
++ goto cleanup;
+
+- hist->key_data = k5calloc(n_key_data, sizeof(krb5_key_data), &ret);
+- if (hist->key_data == NULL)
+- return ret;
++ hist.key_data = k5calloc(n_key_data, sizeof(krb5_key_data), &ret);
++ if (hist.key_data == NULL)
++ goto cleanup;
++
++ /* We only want to store the most recent kvno, and key_data should already
++ * be sorted in descending order by kvno. */
++ kvno = key_data[0].key_data_kvno;
+
+ for (i = 0; i < n_key_data; i++) {
+- ret = krb5_dbe_decrypt_key_data(context, NULL, &key_data[i], &key,
++ if (key_data[i].key_data_kvno < kvno)
++ break;
++ ret = krb5_dbe_decrypt_key_data(context, NULL,
++ &key_data[i], &key,
+ &salt);
+ if (ret)
+- return ret;
++ goto cleanup;
+
+ ret = krb5_dbe_encrypt_key_data(context, hist_key, &key, &salt,
+ key_data[i].key_data_kvno,
+- &hist->key_data[i]);
++ &hist.key_data[hist.n_key_data]);
+ if (ret)
+- return ret;
+-
++ goto cleanup;
++ hist.n_key_data++;
+ krb5_free_keyblock_contents(context, &key);
+ /* krb5_free_keysalt(context, &salt); */
+ }
+
+- hist->n_key_data = n_key_data;
+- return 0;
+-}
+-
+-static
+-void free_history_entry(krb5_context context, osa_pw_hist_ent *hist)
+-{
+- 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);
++ *hist_out = hist;
++ hist.n_key_data = 0;
++ hist.key_data = NULL;
++
++cleanup:
++ krb5_free_keyblock_contents(context, &key);
++ free_history_entry(context, &hist);
++ return ret;
+ }
+
+ /*
+@@ -1526,11 +1553,14 @@ kadm5_chpass_principal_3(void *server_ha
+ goto done;
+ }
+
+- ret = add_to_history(handle->context, hist_kvno, &adb, &pol,
+- &hist);
+- if (ret)
+- goto done;
+- hist_added = 1;
++ /* Don't save empty history. */
++ if (hist.n_key_data > 0) {
++ ret = add_to_history(handle->context, hist_kvno, &adb, &pol,
++ &hist);
++ if (ret)
++ goto done;
++ hist_added = 1;
++ }
+ }
+
+ if (pol.pw_max_life)
+@@ -1582,6 +1612,9 @@ kadm5_chpass_principal_3(void *server_ha
+ KADM5_FAIL_AUTH_COUNT;
+ /* | KADM5_CPW_FUNCTION */
+
++ if (hist_added)
++ kdb->mask |= KADM5_KEY_HIST;
++
+ ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
+ KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold,
+ new_n_ks_tuple, new_ks_tuple, password);
+--- a/src/lib/kdb/kdb5.c
++++ b/src/lib/kdb/kdb5.c
+@@ -1,6 +1,7 @@
+ /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+ /*
+- * Copyright 2006, 2009, 2010 by the Massachusetts Institute of Technology.
++ * Copyright 2006, 2009, 2010, 2016 by the Massachusetts Institute of
++ * Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+@@ -758,7 +759,15 @@ krb5_db_get_principal(krb5_context kcont
+ return status;
+ if (v->get_principal == NULL)
+ return KRB5_PLUGIN_OP_NOTSUPP;
+- return v->get_principal(kcontext, search_for, flags, entry);
++ status = v->get_principal(kcontext, search_for, flags, entry);
++ if (status)
++ return status;
++
++ /* Sort the keys in the db entry as some parts of krb5 expect it to be. */
++ if ((*entry)->key_data != NULL)
++ krb5_dbe_sort_key_data((*entry)->key_data, (*entry)->n_key_data);
++
++ return 0;
+ }
+
+ void
+@@ -948,6 +957,26 @@ krb5_db_delete_principal(krb5_context kc
+ return status;
+ }
+
++/*
++ * Use a proxy function for iterate so that we can sort the keys before sending
++ * them to the callback.
++ */
++struct callback_proxy_args {
++ int (*func)(krb5_pointer, krb5_db_entry *);
++ krb5_pointer func_arg;
++};
++
++static int
++sort_entry_callback_proxy(krb5_pointer func_arg, krb5_db_entry *entry)
++{
++ struct callback_proxy_args *args = (struct callback_proxy_args *)func_arg;
++
++ /* Sort the keys in the db entry as some parts of krb5 expect it to be. */
++ if (entry && entry->key_data)
++ krb5_dbe_sort_key_data(entry->key_data, entry->n_key_data);
++ return args->func(args->func_arg, entry);
++}
++
+ krb5_error_code
+ krb5_db_iterate(krb5_context kcontext, char *match_entry,
+ int (*func)(krb5_pointer, krb5_db_entry *),
+@@ -955,13 +984,20 @@ krb5_db_iterate(krb5_context kcontext, c
+ {
+ krb5_error_code status = 0;
+ kdb_vftabl *v;
++ struct callback_proxy_args proxy_args;
+
+ status = get_vftabl(kcontext, &v);
+ if (status)
+ return status;
+ if (v->iterate == NULL)
+ return KRB5_PLUGIN_OP_NOTSUPP;
+- return v->iterate(kcontext, match_entry, func, func_arg, iterflags);
++
++ /* Use the proxy function to sort key data before passing entries to
++ * callback. */
++ proxy_args.func = func;
++ proxy_args.func_arg = func_arg;
++ return v->iterate(kcontext, match_entry, sort_entry_callback_proxy,
++ &proxy_args, iterflags);
+ }
+
+ /* Return a read only pointer alias to mkey list. Do not free this! */
+@@ -2570,3 +2606,22 @@ krb5_db_check_allowed_to_delegate(krb5_c
+ return KRB5_PLUGIN_OP_NOTSUPP;
+ return v->check_allowed_to_delegate(kcontext, client, server, proxy);
+ }
++
++void
++krb5_dbe_sort_key_data(krb5_key_data *key_data, size_t key_data_length)
++{
++ size_t i, j;
++ krb5_key_data tmp;
++
++ /* Use insertion sort as a stable sort. */
++ for (i = 1; i < key_data_length; i++) {
++ j = i;
++ while (j > 0 &&
++ key_data[j - 1].key_data_kvno < key_data[j].key_data_kvno) {
++ tmp = key_data[j];
++ key_data[j] = key_data[j - 1];
++ key_data[j - 1] = tmp;
++ j--;
++ }
++ }
++}
+--- a/src/lib/kdb/libkdb5.exports
++++ b/src/lib/kdb/libkdb5.exports
+@@ -99,3 +99,4 @@ ulog_get_sno_status
+ ulog_replay
+ ulog_set_last
+ xdr_kdb_incr_update_t
++krb5_dbe_sort_key_data
+--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
++++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
+@@ -40,6 +40,7 @@
+ #include "ldap_pwd_policy.h"
+ #include <time.h>
+ #include <ctype.h>
++#include <kadm5/admin.h>
+
+ #ifdef NEED_STRPTIME_PROTO
+ extern char *strptime(const char *, const char *, struct tm *);
+@@ -1324,6 +1325,22 @@ remove_overlapping_subtrees(char **list,
+ *subtcount = count;
+ }
+
++static void
++free_princ_ent_contents(osa_princ_ent_t princ_ent)
++{
++ unsigned int i;
++
++ for (i = 0; i < princ_ent->old_key_len; i++) {
++ k5_free_key_data(princ_ent->old_keys[i].n_key_data,
++ princ_ent->old_keys[i].key_data);
++ princ_ent->old_keys[i].n_key_data = 0;
++ princ_ent->old_keys[i].key_data = NULL;
++ }
++ free(princ_ent->old_keys);
++ princ_ent->old_keys = NULL;
++ princ_ent->old_key_len = 0;
++}
++
+ /*
+ * Fill out a krb5_db_entry princ entry struct given a LDAP message containing
+ * the results of a principal search of the directory.
+@@ -1344,6 +1361,9 @@ populate_krb5_db_entry(krb5_context cont
+ char **pnvalues = NULL, **ocvalues = NULL, **a2d2 = NULL;
+ struct berval **ber_key_data = NULL, **ber_tl_data = NULL;
+ krb5_tl_data userinfo_tl_data = { NULL }, **endp, *tl;
++ osa_princ_ent_rec princ_ent;
++
++ memset(&princ_ent, 0, sizeof(princ_ent));
+
+ ret = krb5_copy_principal(context, princ, &entry->princ);
+ if (ret)
+@@ -1462,8 +1482,21 @@ populate_krb5_db_entry(krb5_context cont
+ ret = krb5_ldap_policydn_to_name(context, pwdpolicydn, &polname);
+ if (ret)
+ goto cleanup;
++ princ_ent.policy = polname;
++ princ_ent.aux_attributes |= KADM5_POLICY;
++ }
++
++ ber_key_data = ldap_get_values_len(ld, ent, "krbpwdhistory");
++ if (ber_key_data != NULL) {
++ mask |= KDB_PWD_HISTORY_ATTR;
++ ret = krb5_decode_histkey(context, ber_key_data, &princ_ent);
++ if (ret)
++ goto cleanup;
++ ldap_value_free_len(ber_key_data);
++ }
+
+- ret = krb5_update_tl_kadm_data(context, entry, polname);
++ if (princ_ent.aux_attributes) {
++ ret = krb5_update_tl_kadm_data(context, entry, &princ_ent);
+ if (ret)
+ goto cleanup;
+ }
+@@ -1471,8 +1504,7 @@ populate_krb5_db_entry(krb5_context cont
+ ber_key_data = ldap_get_values_len(ld, ent, "krbprincipalkey");
+ if (ber_key_data != NULL) {
+ mask |= KDB_SECRET_KEY_ATTR;
+- ret = krb5_decode_krbsecretkey(context, entry, ber_key_data,
+- &userinfo_tl_data, &mkvno);
++ ret = krb5_decode_krbsecretkey(context, entry, ber_key_data, &mkvno);
+ if (ret)
+ goto cleanup;
+ if (mkvno != 0) {
+@@ -1578,6 +1610,7 @@ cleanup:
+ free(tktpolname);
+ free(policydn);
+ krb5_free_unparsed_name(context, user);
++ free_princ_ent_contents(&princ_ent);
+ return ret;
+ }
+
+--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c
++++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c
+@@ -59,6 +59,7 @@ char *principal_attributes[] = { "kr
+ "krbExtraData",
+ "krbObjectReferences",
+ "krbAllowedToDelegateTo",
++ "krbPwdHistory",
+ NULL };
+
+ /* Must match KDB_*_ATTR macros in ldap_principal.h. */
+@@ -77,14 +78,38 @@ static char *attributes_set[] = { "krbma
+ "krbLastFailedAuth",
+ "krbLoginFailedCount",
+ "krbLastAdminUnlock",
++ "krbPwdHistory",
+ NULL };
+
++
++static void
++k5_free_key_data_contents(krb5_key_data *key)
++{
++ int16_t i;
++
++ for (i = 0; i < key->key_data_ver; i++) {
++ zapfree(key->key_data_contents[i], key->key_data_length[i]);
++ key->key_data_contents[i] = NULL;
++ }
++}
++
++void
++k5_free_key_data(krb5_int16 n_key_data, krb5_key_data *key_data)
++{
++ int16_t i;
++
++ if (key_data == NULL)
++ return;
++ for (i = 0; i < n_key_data; i++)
++ k5_free_key_data_contents(&key_data[i]);
++ free(key_data);
++}
++
+ void
+ krb5_dbe_free_contents(krb5_context context, krb5_db_entry *entry)
+ {
+ krb5_tl_data *tl_data_next=NULL;
+ krb5_tl_data *tl_data=NULL;
+- int i, j;
+
+ if (entry->e_data)
+ free(entry->e_data);
+@@ -96,24 +121,7 @@ krb5_dbe_free_contents(krb5_context cont
+ free(tl_data->tl_data_contents);
+ free(tl_data);
+ }
+- if (entry->key_data) {
+- for (i = 0; i < entry->n_key_data; i++) {
+- for (j = 0; j < entry->key_data[i].key_data_ver; j++) {
+- if (entry->key_data[i].key_data_length[j]) {
+- if (entry->key_data[i].key_data_contents[j]) {
+- memset(entry->key_data[i].key_data_contents[j],
+- 0,
+- (unsigned) entry->key_data[i].key_data_length[j]);
+- free (entry->key_data[i].key_data_contents[j]);
+- }
+- }
+- entry->key_data[i].key_data_contents[j] = NULL;
+- entry->key_data[i].key_data_length[j] = 0;
+- entry->key_data[i].key_data_type[j] = 0;
+- }
+- }
+- free(entry->key_data);
+- }
++ k5_free_key_data(entry->n_key_data, entry->key_data);
+ memset(entry, 0, sizeof(*entry));
+ return;
+ }
+--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h
++++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h
+@@ -32,6 +32,7 @@
+ #define _LDAP_PRINCIPAL_H 1
+
+ #include "ldap_tkt_policy.h"
++#include "princ_xdr.h"
+
+ #define KEYHEADER 12
+
+@@ -82,6 +83,7 @@
+ #define KDB_LAST_FAILED_ATTR 0x001000
+ #define KDB_FAIL_AUTH_COUNT_ATTR 0x002000
+ #define KDB_LAST_ADMIN_UNLOCK_ATTR 0x004000
++#define KDB_PWD_HISTORY_ATTR 0x008000
+
+ /*
+ * This is a private contract between krb5_ldap_lockout_audit()
+@@ -112,6 +114,12 @@ krb5_ldap_iterate(krb5_context, char *,
+ krb5_pointer, krb5_flags);
+
+ void
++k5_free_key_data(krb5_int16 n_key_data, krb5_key_data *key_data);
++
++void
++krb5_dbe_free_contents(krb5_context context, krb5_db_entry *entry);
++
++void
+ krb5_dbe_free_contents(krb5_context, krb5_db_entry *);
+
+ krb5_error_code
+@@ -121,8 +129,11 @@ krb5_error_code
+ krb5_ldap_parse_principal_name(char *, char **);
+
+ krb5_error_code
++krb5_decode_histkey(krb5_context, struct berval **, osa_princ_ent_rec *);
++
++krb5_error_code
+ krb5_decode_krbsecretkey(krb5_context, krb5_db_entry *, struct berval **,
+- krb5_tl_data *, krb5_kvno *);
++ krb5_kvno *);
+
+ krb5_error_code
+ berval2tl_data(struct berval *in, krb5_tl_data **out);
+--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
++++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
+@@ -1,6 +1,35 @@
+ /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+ /* plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c */
+ /*
++ * Copyright (C) 2016 by the Massachusetts Institute of Technology.
++ * All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * * Redistributions of source code must retain the above copyright
++ * notice, this list of conditions and the following disclaimer.
++ *
++ * * Redistributions in binary form must reproduce the above copyright
++ * notice, this list of conditions and the following disclaimer in
++ * the documentation and/or other materials provided with the
++ * distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
++ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
++ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
++ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
++ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
++ * OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++/*
+ * Copyright (c) 2004-2005, Novell, Inc.
+ * All rights reserved.
+ *
+@@ -362,13 +391,14 @@ asn1_encode_sequence_of_keys(krb5_key_da
+ }
+
+ static krb5_error_code
+-asn1_decode_sequence_of_keys(krb5_data *in, krb5_key_data **out,
+- krb5_int16 *n_key_data, krb5_kvno *mkvno)
++asn1_decode_sequence_of_keys(krb5_data *in, ldap_seqof_key_data *out)
+ {
+ krb5_error_code err;
+ ldap_seqof_key_data *p;
+ int i;
+
++ memset(out, 0, sizeof(*out));
++
+ /*
+ * This should be pushed back into other library initialization
+ * code.
+@@ -390,9 +420,7 @@ asn1_decode_sequence_of_keys(krb5_data *
+ p->key_data[i].key_data_ver = 2;
+ }
+
+- *out = p->key_data;
+- *n_key_data = p->n_key_data;
+- *mkvno = p->mkvno;
++ *out = *p;
+ free(p);
+ return 0;
+ }
+@@ -416,19 +444,24 @@ free_berdata(struct berval **array)
+ }
+ }
+
+-/* Decoding ASN.1 encoded key */
+-static struct berval **
+-krb5_encode_krbsecretkey(krb5_key_data *key_data_in, int n_key_data,
+- krb5_kvno mkvno) {
+- struct berval **ret = NULL;
+- int currkvno;
+- int num_versions = 1;
+- int i, j, last;
++/*
++ * Encode krb5_key_data into a berval struct for insertion into LDAP.
++ */
++static krb5_error_code
++encode_keys(krb5_key_data *key_data_in, int n_key_data, krb5_kvno mkvno,
++ struct berval **bval_out)
++{
+ krb5_error_code err = 0;
++ int i;
+ krb5_key_data *key_data = NULL;
++ struct berval *bval = NULL;
++ krb5_data *code;
+
+- if (n_key_data < 0)
+- return NULL;
++ *bval_out = NULL;
++ if (n_key_data <= 0) {
++ err = EINVAL;
++ goto cleanup;
++ }
+
+ /* Make a shallow copy of the key data so we can alter it. */
+ key_data = k5calloc(n_key_data, sizeof(*key_data), &err);
+@@ -447,31 +480,68 @@ krb5_encode_krbsecretkey(krb5_key_data *
+ }
+ }
+
++ bval = k5alloc(sizeof(struct berval), &err);
++ if (bval == NULL)
++ goto cleanup;
++
++ err = asn1_encode_sequence_of_keys(key_data, n_key_data, mkvno, &code);
++ if (err)
++ goto cleanup;
++
++ /* Steal the data pointer from code for bval and discard code. */
++ bval->bv_len = code->length;
++ bval->bv_val = code->data;
++ free(code);
++
++ *bval_out = bval;
++ bval = NULL;
++
++cleanup:
++ free(key_data);
++ free(bval);
++ return err;
++}
++
++/* Decoding ASN.1 encoded key */
++static struct berval **
++krb5_encode_krbsecretkey(krb5_key_data *key_data, int n_key_data,
++ krb5_kvno mkvno)
++{
++ struct berval **ret = NULL;
++ int currkvno;
++ int num_versions = 0;
++ int i, j, last;
++ krb5_error_code err = 0;
++
++ if (n_key_data < 0)
++ return NULL;
++
+ /* Find the number of key versions */
+- for (i = 0; i < n_key_data - 1; i++)
+- if (key_data[i].key_data_kvno != key_data[i + 1].key_data_kvno)
+- num_versions++;
++ if (n_key_data > 0) {
++ for (i = 0, num_versions = 1; i < n_key_data - 1; i++) {
++ if (key_data[i].key_data_kvno != key_data[i + 1].key_data_kvno)
++ num_versions++;
++ }
++ }
+
+- ret = (struct berval **) calloc (num_versions + 1, sizeof (struct berval *));
++ ret = calloc(num_versions + 1, sizeof(struct berval *));
+ if (ret == NULL) {
+ err = ENOMEM;
+ goto cleanup;
+ }
+- for (i = 0, last = 0, j = 0, currkvno = key_data[0].key_data_kvno; i < n_key_data; i++) {
+- krb5_data *code;
++ ret[num_versions] = NULL;
++
++ /* n_key_data may be 0 if a principal is created without a key. */
++ if (n_key_data == 0)
++ goto cleanup;
++
++ currkvno = key_data[0].key_data_kvno;
++ for (i = 0, last = 0, j = 0; i < n_key_data; i++) {
+ if (i == n_key_data - 1 || key_data[i + 1].key_data_kvno != currkvno) {
+- ret[j] = k5alloc(sizeof(struct berval), &err);
+- if (ret[j] == NULL)
+- goto cleanup;
+- err = asn1_encode_sequence_of_keys(key_data + last,
+- (krb5_int16)i - last + 1,
+- mkvno, &code);
++ err = encode_keys(key_data + last, (krb5_int16)i - last + 1, mkvno,
++ &ret[j]);
+ if (err)
+ goto cleanup;
+- /*CHECK_NULL(ret[j]); */
+- ret[j]->bv_len = code->length;
+- ret[j]->bv_val = code->data;
+- free(code);
+ j++;
+ last = i + 1;
+
+@@ -479,11 +549,48 @@ krb5_encode_krbsecretkey(krb5_key_data *
+ currkvno = key_data[i + 1].key_data_kvno;
+ }
+ }
+- ret[num_versions] = NULL;
+
+ cleanup:
++ if (err != 0) {
++ free_berdata(ret);
++ ret = NULL;
++ }
+
+- free(key_data);
++ return ret;
++}
++
++/*
++ * Encode a principal's key history for insertion into ldap.
++ */
++static struct berval **
++krb5_encode_histkey(osa_princ_ent_rec *princ_ent)
++{
++ unsigned int i;
++ krb5_error_code err = 0;
++ struct berval **ret = NULL;
++
++ if (princ_ent->old_key_len <= 0)
++ return NULL;
++
++ ret = k5calloc(princ_ent->old_key_len + 1, sizeof(struct berval *), &err);
++ if (ret == NULL)
++ goto cleanup;
++
++ for (i = 0; i < princ_ent->old_key_len; i++) {
++ if (princ_ent->old_keys[i].n_key_data <= 0) {
++ err = EINVAL;
++ goto cleanup;
++ }
++ err = encode_keys(princ_ent->old_keys[i].key_data,
++ princ_ent->old_keys[i].n_key_data,
++ princ_ent->admin_history_kvno, &ret[i]);
++ if (err)
++ goto cleanup;
++ }
++
++ ret[princ_ent->old_key_len] = NULL;
++
++cleanup:
+ if (err != 0) {
+ free_berdata(ret);
+ ret = NULL;
+@@ -1004,7 +1111,7 @@ krb5_ldap_put_principal(krb5_context con
+ free (strval[0]);
+ }
+
+- if (entry->mask & KADM5_POLICY) {
++ if (entry->mask & KADM5_POLICY || entry->mask & KADM5_KEY_HIST) {
+ memset(&princ_ent, 0, sizeof(princ_ent));
+ for (tl_data=entry->tl_data; tl_data; tl_data=tl_data->tl_data_next) {
+ if (tl_data->tl_data_type == KRB5_TL_KADM_DATA) {
+@@ -1014,7 +1121,9 @@ krb5_ldap_put_principal(krb5_context con
+ break;
+ }
+ }
++ }
+
++ if (entry->mask & KADM5_POLICY) {
+ if (princ_ent.aux_attributes & KADM5_POLICY) {
+ memset(strval, 0, sizeof(strval));
+ if ((st = krb5_ldap_name_to_policydn (context, princ_ent.policy, &polname)) != 0)
+@@ -1042,6 +1151,22 @@ krb5_ldap_put_principal(krb5_context con
+ goto cleanup;
+ }
+
++ if (entry->mask & KADM5_KEY_HIST) {
++ bersecretkey = krb5_encode_histkey(&princ_ent);
++ if (bersecretkey == NULL) {
++ st = ENOMEM;
++ goto cleanup;
++ }
++
++ st = krb5_add_ber_mem_ldap_mod(&mods, "krbpwdhistory",
++ LDAP_MOD_REPLACE | LDAP_MOD_BVALUES,
++ bersecretkey);
++ if (st != 0)
++ goto cleanup;
++ free_berdata(bersecretkey);
++ bersecretkey = NULL;
++ }
++
+ if (entry->mask & KADM5_KEY_DATA || entry->mask & KADM5_KVNO) {
+ krb5_kvno mkvno;
+
+@@ -1376,22 +1501,62 @@ cleanup:
+ return st;
+ }
+
+-krb5_error_code
+-krb5_decode_krbsecretkey(krb5_context context, krb5_db_entry *entries,
+- struct berval **bvalues,
+- krb5_tl_data *userinfo_tl_data, krb5_kvno *mkvno)
++static void
++free_ldap_seqof_key_data(ldap_seqof_key_data *keysets, krb5_int16 n_keysets)
+ {
+- char *user=NULL;
+- int i=0, j=0, noofkeys=0;
+- krb5_key_data *key_data=NULL, *tmp;
+- krb5_error_code st=0;
++ int i;
+
+- if ((st=krb5_unparse_name(context, entries->princ, &user)) != 0)
++ if (keysets == NULL)
++ return;
++
++ for (i = 0; i < n_keysets; i++)
++ k5_free_key_data(keysets[i].n_key_data, keysets[i].key_data);
++ free(keysets);
++}
++
++/*
++ * Decode keys from ldap search results.
++ *
++ * Arguments:
++ * - bvalues
++ * The ldap search results containing the key data.
++ * - mkvno
++ * The master kvno that the keys were encrypted with.
++ * - keysets_out
++ * The decoded keys in a ldap_seqof_key_data struct. Must be freed using
++ * free_ldap_seqof_key_data.
++ * - n_keysets_out
++ * The number of entries in keys_out.
++ * - total_keys_out
++ * An optional argument that if given will be set to the total number of
++ * keys found throughout all the entries: sum(keys_out.n_key_data)
++ * May be NULL.
++ */
++static krb5_error_code
++decode_keys(struct berval **bvalues, ldap_seqof_key_data **keysets_out,
++ krb5_int16 *n_keysets_out, krb5_int16 *total_keys_out)
++{
++ krb5_error_code err = 0;
++ krb5_int16 n_keys, i, ki, total_keys;
++ ldap_seqof_key_data *keysets = NULL;
++
++ *keysets_out = NULL;
++ *n_keysets_out = 0;
++ if (total_keys_out)
++ *total_keys_out = 0;
++
++ /* Precount the number of keys. */
++ for (n_keys = 0, i = 0; bvalues[i] != NULL; i++) {
++ if (bvalues[i]->bv_len > 0)
++ n_keys++;
++ }
++
++ keysets = k5calloc(n_keys, sizeof(ldap_seqof_key_data), &err);
++ if (keysets == NULL)
+ goto cleanup;
++ memset(keysets, 0, n_keys * sizeof(ldap_seqof_key_data));
+
+- for (i=0; bvalues[i] != NULL; ++i) {
+- krb5_int16 n_kd;
+- krb5_key_data *kd;
++ for (i = 0, ki = 0, total_keys = 0; bvalues[i] != NULL; i++) {
+ krb5_data in;
+
+ if (bvalues[i]->bv_len == 0)
+@@ -1399,39 +1564,131 @@ krb5_decode_krbsecretkey(krb5_context co
+ in.length = bvalues[i]->bv_len;
+ in.data = bvalues[i]->bv_val;
+
+- st = asn1_decode_sequence_of_keys (&in,
+- &kd,
+- &n_kd,
+- mkvno);
+-
+- if (st != 0) {
+- const char *msg = error_message(st);
+- st = -1; /* Something more appropriate ? */
+- k5_setmsg(context, st,
+- _("unable to decode stored principal key data (%s)"),
+- msg);
+- goto cleanup;
+- }
+- noofkeys += n_kd;
+- tmp = key_data;
+- /* Allocate an extra key data to avoid allocating zero bytes. */
+- key_data = realloc(key_data, (noofkeys + 1) * sizeof (krb5_key_data));
+- if (key_data == NULL) {
+- key_data = tmp;
+- st = ENOMEM;
++ err = asn1_decode_sequence_of_keys(&in, &keysets[ki]);
++ if (err)
+ goto cleanup;
+- }
+- for (j = 0; j < n_kd; j++)
+- key_data[noofkeys - n_kd + j] = kd[j];
+- free (kd);
++
++ if (total_keys_out)
++ total_keys += keysets[ki].n_key_data;
++ ki++;
++ }
++
++ if (total_keys_out)
++ *total_keys_out = total_keys;
++
++ *n_keysets_out = n_keys;
++ *keysets_out = keysets;
++ keysets = NULL;
++ n_keys = 0;
++
++cleanup:
++ free_ldap_seqof_key_data(keysets, n_keys);
++ return err;
++}
++
++krb5_error_code
++krb5_decode_krbsecretkey(krb5_context context, krb5_db_entry *entries,
++ struct berval **bvalues, krb5_kvno *mkvno)
++{
++ krb5_key_data *key_data = NULL, *tmp;
++ krb5_error_code err = 0;
++ ldap_seqof_key_data *keysets = NULL;
++ krb5_int16 i, n_keysets = 0, total_keys = 0;
++
++ err = decode_keys(bvalues, &keysets, &n_keysets, &total_keys);
++ if (err != 0) {
++ k5_prependmsg(context, err,
++ _("unable to decode stored principal key data"));
++ goto cleanup;
+ }
+
+- entries->n_key_data = noofkeys;
++ key_data = k5calloc(total_keys, sizeof(krb5_key_data), &err);
++ if (key_data == NULL)
++ goto cleanup;
++ memset(key_data, 0, total_keys * sizeof(krb5_key_data));
++
++ if (n_keysets > 0)
++ *mkvno = keysets[0].mkvno;
++
++ /* Transfer key data values from keysets to a flat list in entries. */
++ tmp = key_data;
++ for (i = 0; i < n_keysets; i++) {
++ memcpy(tmp, keysets[i].key_data,
++ sizeof(krb5_key_data) * keysets[i].n_key_data);
++ tmp += keysets[i].n_key_data;
++ keysets[i].n_key_data = 0;
++ }
++ entries->n_key_data = total_keys;
+ entries->key_data = key_data;
++ key_data = NULL;
+
+ cleanup:
+- free (user);
+- return st;
++ free_ldap_seqof_key_data(keysets, n_keysets);
++ k5_free_key_data(total_keys, key_data);
++ return err;
++}
++
++static int
++compare_osa_pw_hist_ent(const void *left_in, const void *right_in)
++{
++ int kvno_left, kvno_right;
++ osa_pw_hist_ent *left = (osa_pw_hist_ent *)left_in;
++ osa_pw_hist_ent *right = (osa_pw_hist_ent *)right_in;
++
++ kvno_left = left->n_key_data ? left->key_data[0].key_data_kvno : 0;
++ kvno_right = right->n_key_data ? right->key_data[0].key_data_kvno : 0;
++ return kvno_left - kvno_right;
++}
++
++/*
++ * Decode the key history entries from an LDAP search.
++ *
++ * NOTE: the caller must free princ_ent->old_keys even on error.
++ */
++krb5_error_code
++krb5_decode_histkey(krb5_context context, struct berval **bvalues,
++ osa_princ_ent_rec *princ_ent)
++{
++ krb5_error_code err = 0;
++ krb5_int16 i, n_keysets = 0;
++ ldap_seqof_key_data *keysets = NULL;
++
++ err = decode_keys(bvalues, &keysets, &n_keysets, NULL);
++ if (err != 0) {
++ k5_prependmsg(context, err,
++ _("unable to decode stored principal pw history"));
++ goto cleanup;
++ }
++
++ princ_ent->old_keys = k5calloc(n_keysets, sizeof(osa_pw_hist_ent), &err);
++ if (princ_ent->old_keys == NULL)
++ goto cleanup;
++ princ_ent->old_key_len = n_keysets;
++
++ if (n_keysets > 0)
++ princ_ent->admin_history_kvno = keysets[0].mkvno;
++
++ /* Transfer key data pointers from keysets to princ_ent. */
++ for (i = 0; i < n_keysets; i++) {
++ princ_ent->old_keys[i].n_key_data = keysets[i].n_key_data;
++ princ_ent->old_keys[i].key_data = keysets[i].key_data;
++ keysets[i].n_key_data = 0;
++ keysets[i].key_data = NULL;
++ }
++
++ /* Sort the principal entries by kvno in ascending order. */
++ qsort(princ_ent->old_keys, princ_ent->old_key_len, sizeof(osa_pw_hist_ent),
++ &compare_osa_pw_hist_ent);
++
++ princ_ent->aux_attributes |= KADM5_KEY_HIST;
++
++ /* Set the next key to the end of the list. The queue will be lengthened
++ * if it isn't full yet; the first entry will be replaced if it is full. */
++ princ_ent->old_key_next = princ_ent->old_key_len;
++
++cleanup:
++ free_ldap_seqof_key_data(keysets, n_keysets);
++ return err;
+ }
+
+ static char *
+--- a/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.c
++++ b/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.c
+@@ -204,20 +204,14 @@ krb5_lookup_tl_kadm_data(krb5_tl_data *t
+
+ krb5_error_code
+ krb5_update_tl_kadm_data(krb5_context context, krb5_db_entry *entry,
+- char *policy_dn)
++ osa_princ_ent_rec *princ_entry)
+ {
+ XDR xdrs;
+- osa_princ_ent_rec princ_entry;
+ krb5_tl_data tl_data;
+ krb5_error_code retval;
+
+- memset(&princ_entry, 0, sizeof(osa_princ_ent_rec));
+- princ_entry.admin_history_kvno = 2;
+- princ_entry.aux_attributes = KADM5_POLICY;
+- princ_entry.policy = policy_dn;
+-
+ xdralloc_create(&xdrs, XDR_ENCODE);
+- if (! ldap_xdr_osa_princ_ent_rec(&xdrs, &princ_entry)) {
++ if (! ldap_xdr_osa_princ_ent_rec(&xdrs, princ_entry)) {
+ xdr_destroy(&xdrs);
+ return KADM5_XDR_FAILURE;
+ }
+--- a/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.h
++++ b/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.h
+@@ -57,6 +57,6 @@ krb5_lookup_tl_kadm_data(krb5_tl_data *t
+
+ krb5_error_code
+ krb5_update_tl_kadm_data(krb5_context context, krb5_db_entry *entry,
+- char *policy_dn);
++ osa_princ_ent_rec *princ_entry);
+
+ #endif
+--- a/src/tests/kdbtest.c
++++ b/src/tests/kdbtest.c
+@@ -97,7 +97,7 @@ static krb5_tl_data tl3 = { &tl4, KRB5_T
+ U("\x12\x34\x5C\x01\x00\x00\x00\x08"
+ "\x3C\x74\x65\x73\x74\x2A\x3E\x00"
+ "\x00\x00\x08\x00\x00\x00\x00\x00"
+- "\x00\x00\x00\x02\x00\x00\x00\x00") };
++ "\x00\x00\x00\x00\x00\x00\x00\x00") };
+ static krb5_tl_data tl2 = { &tl3, KRB5_TL_MOD_PRINC, 8, U("\5\6\7\0x@Y\0") };
+ static krb5_tl_data tl1 = { &tl2, KRB5_TL_LAST_PWD_CHANGE, 4, U("\1\2\3\4") };
+
+--- a/src/tests/t_kdb.py
++++ b/src/tests/t_kdb.py
+@@ -337,6 +337,31 @@ realm.run([kadminl, 'modprinc', '+requir
+ realm.kinit('canon', password('canon'))
+ realm.kinit('alias', password('canon'), ['-C'])
+
++# Test password history.
++def test_pwhist(nhist):
++ def cpw(n, **kwargs):
++ realm.run([kadminl, 'cpw', '-pw', str(n), princ], **kwargs)
++ def cpw_fail(n):
++ cpw(n, expected_code=1)
++ output('*** Testing password history of size %d\n' % nhist)
++ princ = 'pwhistprinc' + str(nhist)
++ pol = 'pwhistpol' + str(nhist)
++ realm.run([kadminl, 'addpol', '-history', str(nhist), pol])
++ realm.run([kadminl, 'addprinc', '-policy', pol, '-nokey', princ])
++ for i in range(nhist):
++ # Set a password, then check that all previous passwords fail.
++ cpw(i)
++ for j in range(i + 1):
++ cpw_fail(j)
++ # Set one more new password, and make sure the oldest key is
++ # rotated out.
++ cpw(nhist)
++ cpw_fail(1)
++ cpw(0)
++
++for n in (1, 2, 3, 4, 5):
++ test_pwhist(n)
++
+ # Regression test for #7980 (fencepost when dividing keys up by kvno).
+ realm.run([kadminl, 'addprinc', '-randkey', '-e', 'aes256-cts,aes128-cts',
+ 'kvnoprinc'])
+@@ -368,6 +393,13 @@ out = realm.run([kadminl, 'getprinc', 'k
+ if 'Number of keys: 0' not in out:
+ fail('After purgekeys -all, keys remain')
+
++# Test for 8354 (old password history entries when -keepold is used)
++realm.run([kadminl, 'addpol', '-history', '2', 'keepoldpasspol'])
++realm.run([kadminl, 'addprinc', '-policy', 'keepoldpasspol', '-pw', 'aaaa',
++ 'keepoldpassprinc'])
++for p in ('bbbb', 'cccc', 'aaaa'):
++ realm.run([kadminl, 'cpw', '-keepold', '-pw', p, 'keepoldpassprinc'])
++
+ realm.stop()
+
+ # Briefly test dump and load.