components/krb5/patches/075-ldap-pw-hist.patch
changeset 7246 b3414fa83399
equal deleted inserted replaced
7245:934578b959f0 7246:b3414fa83399
       
     1 #
       
     2 # This patch cherry-picks Password history in LDAP KDB plugin feature from
       
     3 # MIT krb5 1.15.
       
     4 #
       
     5 # It is 1-1 port of the following changesets:
       
     6 #    44ad57d8d38efc944f64536354435f5b721c0ee0
       
     7 #    d7f91ac2f6655e77bb3658c2c8cc6132f958a340
       
     8 #    b46cce2ea8c0841f7f93db73eefcd180c87a3eae
       
     9 #    9526953f36b39323ec07448a5f218d27c6f1c76f
       
    10 #
       
    11 # Patch source: upstream
       
    12 #
       
    13 # When upgrading to MIT krb5 1.15 this patch will be dropped.
       
    14 #
       
    15 --- a/src/include/kdb.h
       
    16 +++ b/src/include/kdb.h
       
    17 @@ -1,6 +1,6 @@
       
    18  /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
       
    19  /*
       
    20 - * Copyright 1990,1991 by the Massachusetts Institute of Technology.
       
    21 + * Copyright 1990, 1991, 2016 by the Massachusetts Institute of Technology.
       
    22   * All Rights Reserved.
       
    23   *
       
    24   * Export of this software from the United States of America may
       
    25 @@ -209,6 +209,8 @@ typedef struct _krb5_db_entry_new {
       
    26  
       
    27      krb5_principal        princ;                /* Length, data */
       
    28      krb5_tl_data        * tl_data;              /* Linked list */
       
    29 +
       
    30 +    /* key_data must be sorted by kvno in descending order. */
       
    31      krb5_key_data       * key_data;             /* Array */
       
    32  } krb5_db_entry;
       
    33  
       
    34 @@ -683,6 +685,19 @@ krb5_error_code krb5_db_check_allowed_to
       
    35                                                    const krb5_db_entry *server,
       
    36                                                    krb5_const_principal proxy);
       
    37  
       
    38 +/**
       
    39 + * Sort an array of @a krb5_key_data keys in descending order by their kvno.
       
    40 + * Key data order within a kvno is preserved.
       
    41 + *
       
    42 + * @param key_data
       
    43 + *     The @a krb5_key_data array to sort.  This is sorted in place so the
       
    44 + *     array will be modified.
       
    45 + * @param key_data_length
       
    46 + *     The length of @a key_data.
       
    47 + */
       
    48 +void
       
    49 +krb5_dbe_sort_key_data(krb5_key_data *key_data, size_t key_data_length);
       
    50 +
       
    51  /* default functions. Should not be directly called */
       
    52  /*
       
    53   *   Default functions prototype
       
    54 --- a/src/lib/kadm5/admin.h
       
    55 +++ b/src/lib/kadm5/admin.h
       
    56 @@ -113,7 +113,7 @@ typedef long            kadm5_ret_t;
       
    57  #define KADM5_RANDKEY_USED      0x100000
       
    58  #endif
       
    59  #define KADM5_LOAD              0x200000
       
    60 -#define KADM5_NOKEY             0x400000
       
    61 +#define KADM5_KEY_HIST          0x400000
       
    62  
       
    63  /* all but KEY_DATA, TL_DATA, LOAD */
       
    64  #define KADM5_PRINCIPAL_NORMAL_MASK 0x41ffff
       
    65 --- a/src/lib/kadm5/srv/svr_principal.c
       
    66 +++ b/src/lib/kadm5/srv/svr_principal.c
       
    67 @@ -1084,6 +1084,16 @@ check_pw_reuse(krb5_context context,
       
    68      return(0);
       
    69  }
       
    70  
       
    71 +static void
       
    72 +free_history_entry(krb5_context context, osa_pw_hist_ent *hist)
       
    73 +{
       
    74 +    int i;
       
    75 +
       
    76 +    for (i = 0; i < hist->n_key_data; i++)
       
    77 +        krb5_free_key_data_contents(context, &hist->key_data[i]);
       
    78 +    free(hist->key_data);
       
    79 +}
       
    80 +
       
    81  /*
       
    82   * Function: create_history_entry
       
    83   *
       
    84 @@ -1097,7 +1107,7 @@ check_pw_reuse(krb5_context context,
       
    85   *      hist_key        (r) history keyblock to encrypt key data with
       
    86   *      n_key_data      (r) number of elements in key_data
       
    87   *      key_data        (r) keys to add to the history entry
       
    88 - *      hist            (w) history entry to fill in
       
    89 + *      hist_out        (w) history entry to fill in
       
    90   *
       
    91   * Effects:
       
    92   *
       
    93 @@ -1109,45 +1119,62 @@ check_pw_reuse(krb5_context context,
       
    94  static
       
    95  int create_history_entry(krb5_context context,
       
    96                           krb5_keyblock *hist_key, int n_key_data,
       
    97 -                         krb5_key_data *key_data, osa_pw_hist_ent *hist)
       
    98 +                         krb5_key_data *key_data, osa_pw_hist_ent *hist_out)
       
    99  {
       
   100 -    krb5_error_code ret;
       
   101 +    int i;
       
   102 +    krb5_error_code ret = 0;
       
   103      krb5_keyblock key;
       
   104      krb5_keysalt salt;
       
   105 -    int i;
       
   106 +    krb5_ui_2 kvno;
       
   107 +    osa_pw_hist_ent hist;
       
   108 +
       
   109 +    hist_out->key_data = NULL;
       
   110 +    hist_out->n_key_data = 0;
       
   111 +
       
   112 +    if (n_key_data < 0)
       
   113 +        return EINVAL;
       
   114 +
       
   115 +    memset(&key, 0, sizeof(key));
       
   116 +    memset(&hist, 0, sizeof(hist));
       
   117 +
       
   118 +    if (n_key_data == 0)
       
   119 +        goto cleanup;
       
   120  
       
   121 -    hist->key_data = k5calloc(n_key_data, sizeof(krb5_key_data), &ret);
       
   122 -    if (hist->key_data == NULL)
       
   123 -        return ret;
       
   124 +    hist.key_data = k5calloc(n_key_data, sizeof(krb5_key_data), &ret);
       
   125 +    if (hist.key_data == NULL)
       
   126 +        goto cleanup;
       
   127 +
       
   128 +    /* We only want to store the most recent kvno, and key_data should already
       
   129 +     * be sorted in descending order by kvno. */
       
   130 +    kvno = key_data[0].key_data_kvno;
       
   131  
       
   132      for (i = 0; i < n_key_data; i++) {
       
   133 -        ret = krb5_dbe_decrypt_key_data(context, NULL, &key_data[i], &key,
       
   134 +        if (key_data[i].key_data_kvno < kvno)
       
   135 +            break;
       
   136 +        ret = krb5_dbe_decrypt_key_data(context, NULL,
       
   137 +                                        &key_data[i], &key,
       
   138                                          &salt);
       
   139          if (ret)
       
   140 -            return ret;
       
   141 +            goto cleanup;
       
   142  
       
   143          ret = krb5_dbe_encrypt_key_data(context, hist_key, &key, &salt,
       
   144                                          key_data[i].key_data_kvno,
       
   145 -                                        &hist->key_data[i]);
       
   146 +                                        &hist.key_data[hist.n_key_data]);
       
   147          if (ret)
       
   148 -            return ret;
       
   149 -
       
   150 +            goto cleanup;
       
   151 +        hist.n_key_data++;
       
   152          krb5_free_keyblock_contents(context, &key);
       
   153          /* krb5_free_keysalt(context, &salt); */
       
   154      }
       
   155  
       
   156 -    hist->n_key_data = n_key_data;
       
   157 -    return 0;
       
   158 -}
       
   159 -
       
   160 -static
       
   161 -void free_history_entry(krb5_context context, osa_pw_hist_ent *hist)
       
   162 -{
       
   163 -    int i;
       
   164 -
       
   165 -    for (i = 0; i < hist->n_key_data; i++)
       
   166 -        krb5_free_key_data_contents(context, &hist->key_data[i]);
       
   167 -    free(hist->key_data);
       
   168 +    *hist_out = hist;
       
   169 +    hist.n_key_data = 0;
       
   170 +    hist.key_data = NULL;
       
   171 +
       
   172 +cleanup:
       
   173 +    krb5_free_keyblock_contents(context, &key);
       
   174 +    free_history_entry(context, &hist);
       
   175 +    return ret;
       
   176  }
       
   177  
       
   178  /*
       
   179 @@ -1526,11 +1553,14 @@ kadm5_chpass_principal_3(void *server_ha
       
   180                      goto done;
       
   181              }
       
   182  
       
   183 -            ret = add_to_history(handle->context, hist_kvno, &adb, &pol,
       
   184 -                                 &hist);
       
   185 -            if (ret)
       
   186 -                goto done;
       
   187 -            hist_added = 1;
       
   188 +            /* Don't save empty history. */
       
   189 +            if (hist.n_key_data > 0) {
       
   190 +                ret = add_to_history(handle->context, hist_kvno, &adb, &pol,
       
   191 +                                     &hist);
       
   192 +                if (ret)
       
   193 +                    goto done;
       
   194 +                hist_added = 1;
       
   195 +            }
       
   196          }
       
   197  
       
   198          if (pol.pw_max_life)
       
   199 @@ -1582,6 +1612,9 @@ kadm5_chpass_principal_3(void *server_ha
       
   200          KADM5_FAIL_AUTH_COUNT;
       
   201      /* | KADM5_CPW_FUNCTION */
       
   202  
       
   203 +    if (hist_added)
       
   204 +        kdb->mask |= KADM5_KEY_HIST;
       
   205 +
       
   206      ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
       
   207                                 KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold,
       
   208                                 new_n_ks_tuple, new_ks_tuple, password);
       
   209 --- a/src/lib/kdb/kdb5.c
       
   210 +++ b/src/lib/kdb/kdb5.c
       
   211 @@ -1,6 +1,7 @@
       
   212  /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
       
   213  /*
       
   214 - * Copyright 2006, 2009, 2010 by the Massachusetts Institute of Technology.
       
   215 + * Copyright 2006, 2009, 2010, 2016 by the Massachusetts Institute of
       
   216 + * Technology.
       
   217   * All Rights Reserved.
       
   218   *
       
   219   * Export of this software from the United States of America may
       
   220 @@ -758,7 +759,15 @@ krb5_db_get_principal(krb5_context kcont
       
   221          return status;
       
   222      if (v->get_principal == NULL)
       
   223          return KRB5_PLUGIN_OP_NOTSUPP;
       
   224 -    return v->get_principal(kcontext, search_for, flags, entry);
       
   225 +    status = v->get_principal(kcontext, search_for, flags, entry);
       
   226 +    if (status)
       
   227 +        return status;
       
   228 +
       
   229 +    /* Sort the keys in the db entry as some parts of krb5 expect it to be. */
       
   230 +    if ((*entry)->key_data != NULL)
       
   231 +        krb5_dbe_sort_key_data((*entry)->key_data, (*entry)->n_key_data);
       
   232 +
       
   233 +    return 0;
       
   234  }
       
   235  
       
   236  void
       
   237 @@ -948,6 +957,26 @@ krb5_db_delete_principal(krb5_context kc
       
   238      return status;
       
   239  }
       
   240  
       
   241 +/*
       
   242 + * Use a proxy function for iterate so that we can sort the keys before sending
       
   243 + * them to the callback.
       
   244 + */
       
   245 +struct callback_proxy_args {
       
   246 +    int (*func)(krb5_pointer, krb5_db_entry *);
       
   247 +    krb5_pointer func_arg;
       
   248 +};
       
   249 +
       
   250 +static int
       
   251 +sort_entry_callback_proxy(krb5_pointer func_arg, krb5_db_entry *entry)
       
   252 +{
       
   253 +    struct callback_proxy_args *args = (struct callback_proxy_args *)func_arg;
       
   254 +
       
   255 +    /* Sort the keys in the db entry as some parts of krb5 expect it to be. */
       
   256 +    if (entry && entry->key_data)
       
   257 +        krb5_dbe_sort_key_data(entry->key_data, entry->n_key_data);
       
   258 +    return args->func(args->func_arg, entry);
       
   259 +}
       
   260 +
       
   261  krb5_error_code
       
   262  krb5_db_iterate(krb5_context kcontext, char *match_entry,
       
   263                  int (*func)(krb5_pointer, krb5_db_entry *),
       
   264 @@ -955,13 +984,20 @@ krb5_db_iterate(krb5_context kcontext, c
       
   265  {
       
   266      krb5_error_code status = 0;
       
   267      kdb_vftabl *v;
       
   268 +    struct callback_proxy_args proxy_args;
       
   269  
       
   270      status = get_vftabl(kcontext, &v);
       
   271      if (status)
       
   272          return status;
       
   273      if (v->iterate == NULL)
       
   274          return KRB5_PLUGIN_OP_NOTSUPP;
       
   275 -    return v->iterate(kcontext, match_entry, func, func_arg, iterflags);
       
   276 +
       
   277 +    /* Use the proxy function to sort key data before passing entries to
       
   278 +     * callback. */
       
   279 +    proxy_args.func = func;
       
   280 +    proxy_args.func_arg = func_arg;
       
   281 +    return v->iterate(kcontext, match_entry, sort_entry_callback_proxy,
       
   282 +                      &proxy_args, iterflags);
       
   283  }
       
   284  
       
   285  /* Return a read only pointer alias to mkey list.  Do not free this! */
       
   286 @@ -2570,3 +2606,22 @@ krb5_db_check_allowed_to_delegate(krb5_c
       
   287          return KRB5_PLUGIN_OP_NOTSUPP;
       
   288      return v->check_allowed_to_delegate(kcontext, client, server, proxy);
       
   289  }
       
   290 +
       
   291 +void
       
   292 +krb5_dbe_sort_key_data(krb5_key_data *key_data, size_t key_data_length)
       
   293 +{
       
   294 +    size_t i, j;
       
   295 +    krb5_key_data tmp;
       
   296 +
       
   297 +    /* Use insertion sort as a stable sort. */
       
   298 +    for (i = 1; i < key_data_length; i++) {
       
   299 +        j = i;
       
   300 +        while (j > 0 &&
       
   301 +               key_data[j - 1].key_data_kvno < key_data[j].key_data_kvno) {
       
   302 +            tmp = key_data[j];
       
   303 +            key_data[j] = key_data[j - 1];
       
   304 +            key_data[j - 1] = tmp;
       
   305 +            j--;
       
   306 +        }
       
   307 +    }
       
   308 +}
       
   309 --- a/src/lib/kdb/libkdb5.exports
       
   310 +++ b/src/lib/kdb/libkdb5.exports
       
   311 @@ -99,3 +99,4 @@ ulog_get_sno_status
       
   312  ulog_replay
       
   313  ulog_set_last
       
   314  xdr_kdb_incr_update_t
       
   315 +krb5_dbe_sort_key_data
       
   316 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
       
   317 +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
       
   318 @@ -40,6 +40,7 @@
       
   319  #include "ldap_pwd_policy.h"
       
   320  #include <time.h>
       
   321  #include <ctype.h>
       
   322 +#include <kadm5/admin.h>
       
   323  
       
   324  #ifdef NEED_STRPTIME_PROTO
       
   325  extern char *strptime(const char *, const char *, struct tm *);
       
   326 @@ -1324,6 +1325,22 @@ remove_overlapping_subtrees(char **list,
       
   327      *subtcount = count;
       
   328  }
       
   329  
       
   330 +static void
       
   331 +free_princ_ent_contents(osa_princ_ent_t princ_ent)
       
   332 +{
       
   333 +    unsigned int i;
       
   334 +
       
   335 +    for (i = 0; i < princ_ent->old_key_len; i++) {
       
   336 +        k5_free_key_data(princ_ent->old_keys[i].n_key_data,
       
   337 +                         princ_ent->old_keys[i].key_data);
       
   338 +        princ_ent->old_keys[i].n_key_data = 0;
       
   339 +        princ_ent->old_keys[i].key_data = NULL;
       
   340 +    }
       
   341 +    free(princ_ent->old_keys);
       
   342 +    princ_ent->old_keys = NULL;
       
   343 +    princ_ent->old_key_len = 0;
       
   344 +}
       
   345 +
       
   346  /*
       
   347   * Fill out a krb5_db_entry princ entry struct given a LDAP message containing
       
   348   * the results of a principal search of the directory.
       
   349 @@ -1344,6 +1361,9 @@ populate_krb5_db_entry(krb5_context cont
       
   350      char **pnvalues = NULL, **ocvalues = NULL, **a2d2 = NULL;
       
   351      struct berval **ber_key_data = NULL, **ber_tl_data = NULL;
       
   352      krb5_tl_data userinfo_tl_data = { NULL }, **endp, *tl;
       
   353 +    osa_princ_ent_rec princ_ent;
       
   354 +
       
   355 +    memset(&princ_ent, 0, sizeof(princ_ent));
       
   356  
       
   357      ret = krb5_copy_principal(context, princ, &entry->princ);
       
   358      if (ret)
       
   359 @@ -1462,8 +1482,21 @@ populate_krb5_db_entry(krb5_context cont
       
   360          ret = krb5_ldap_policydn_to_name(context, pwdpolicydn, &polname);
       
   361          if (ret)
       
   362              goto cleanup;
       
   363 +        princ_ent.policy = polname;
       
   364 +        princ_ent.aux_attributes |= KADM5_POLICY;
       
   365 +    }
       
   366 +
       
   367 +    ber_key_data = ldap_get_values_len(ld, ent, "krbpwdhistory");
       
   368 +    if (ber_key_data != NULL) {
       
   369 +        mask |= KDB_PWD_HISTORY_ATTR;
       
   370 +        ret = krb5_decode_histkey(context, ber_key_data, &princ_ent);
       
   371 +        if (ret)
       
   372 +            goto cleanup;
       
   373 +        ldap_value_free_len(ber_key_data);
       
   374 +    }
       
   375  
       
   376 -        ret = krb5_update_tl_kadm_data(context, entry, polname);
       
   377 +    if (princ_ent.aux_attributes) {
       
   378 +        ret = krb5_update_tl_kadm_data(context, entry, &princ_ent);
       
   379          if (ret)
       
   380              goto cleanup;
       
   381      }
       
   382 @@ -1471,8 +1504,7 @@ populate_krb5_db_entry(krb5_context cont
       
   383      ber_key_data = ldap_get_values_len(ld, ent, "krbprincipalkey");
       
   384      if (ber_key_data != NULL) {
       
   385          mask |= KDB_SECRET_KEY_ATTR;
       
   386 -        ret = krb5_decode_krbsecretkey(context, entry, ber_key_data,
       
   387 -                                       &userinfo_tl_data, &mkvno);
       
   388 +        ret = krb5_decode_krbsecretkey(context, entry, ber_key_data, &mkvno);
       
   389          if (ret)
       
   390              goto cleanup;
       
   391          if (mkvno != 0) {
       
   392 @@ -1578,6 +1610,7 @@ cleanup:
       
   393      free(tktpolname);
       
   394      free(policydn);
       
   395      krb5_free_unparsed_name(context, user);
       
   396 +    free_princ_ent_contents(&princ_ent);
       
   397      return ret;
       
   398  }
       
   399  
       
   400 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c
       
   401 +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c
       
   402 @@ -59,6 +59,7 @@ char     *principal_attributes[] = { "kr
       
   403                                       "krbExtraData",
       
   404                                       "krbObjectReferences",
       
   405                                       "krbAllowedToDelegateTo",
       
   406 +                                     "krbPwdHistory",
       
   407                                       NULL };
       
   408  
       
   409  /* Must match KDB_*_ATTR macros in ldap_principal.h.  */
       
   410 @@ -77,14 +78,38 @@ static char *attributes_set[] = { "krbma
       
   411                                    "krbLastFailedAuth",
       
   412                                    "krbLoginFailedCount",
       
   413                                    "krbLastAdminUnlock",
       
   414 +                                  "krbPwdHistory",
       
   415                                    NULL };
       
   416  
       
   417 +
       
   418 +static void
       
   419 +k5_free_key_data_contents(krb5_key_data *key)
       
   420 +{
       
   421 +    int16_t i;
       
   422 +
       
   423 +    for (i = 0; i < key->key_data_ver; i++) {
       
   424 +        zapfree(key->key_data_contents[i], key->key_data_length[i]);
       
   425 +        key->key_data_contents[i] = NULL;
       
   426 +    }
       
   427 +}
       
   428 +
       
   429 +void
       
   430 +k5_free_key_data(krb5_int16 n_key_data, krb5_key_data *key_data)
       
   431 +{
       
   432 +    int16_t i;
       
   433 +
       
   434 +    if (key_data == NULL)
       
   435 +        return;
       
   436 +    for (i = 0; i < n_key_data; i++)
       
   437 +        k5_free_key_data_contents(&key_data[i]);
       
   438 +    free(key_data);
       
   439 +}
       
   440 +
       
   441  void
       
   442  krb5_dbe_free_contents(krb5_context context, krb5_db_entry *entry)
       
   443  {
       
   444      krb5_tl_data        *tl_data_next=NULL;
       
   445      krb5_tl_data        *tl_data=NULL;
       
   446 -    int i, j;
       
   447  
       
   448      if (entry->e_data)
       
   449          free(entry->e_data);
       
   450 @@ -96,24 +121,7 @@ krb5_dbe_free_contents(krb5_context cont
       
   451              free(tl_data->tl_data_contents);
       
   452          free(tl_data);
       
   453      }
       
   454 -    if (entry->key_data) {
       
   455 -        for (i = 0; i < entry->n_key_data; i++) {
       
   456 -            for (j = 0; j < entry->key_data[i].key_data_ver; j++) {
       
   457 -                if (entry->key_data[i].key_data_length[j]) {
       
   458 -                    if (entry->key_data[i].key_data_contents[j]) {
       
   459 -                        memset(entry->key_data[i].key_data_contents[j],
       
   460 -                               0,
       
   461 -                               (unsigned) entry->key_data[i].key_data_length[j]);
       
   462 -                        free (entry->key_data[i].key_data_contents[j]);
       
   463 -                    }
       
   464 -                }
       
   465 -                entry->key_data[i].key_data_contents[j] = NULL;
       
   466 -                entry->key_data[i].key_data_length[j] = 0;
       
   467 -                entry->key_data[i].key_data_type[j] = 0;
       
   468 -            }
       
   469 -        }
       
   470 -        free(entry->key_data);
       
   471 -    }
       
   472 +    k5_free_key_data(entry->n_key_data, entry->key_data);
       
   473      memset(entry, 0, sizeof(*entry));
       
   474      return;
       
   475  }
       
   476 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h
       
   477 +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h
       
   478 @@ -32,6 +32,7 @@
       
   479  #define _LDAP_PRINCIPAL_H 1
       
   480  
       
   481  #include "ldap_tkt_policy.h"
       
   482 +#include "princ_xdr.h"
       
   483  
       
   484  #define  KEYHEADER  12
       
   485  
       
   486 @@ -82,6 +83,7 @@
       
   487  #define KDB_LAST_FAILED_ATTR                 0x001000
       
   488  #define KDB_FAIL_AUTH_COUNT_ATTR             0x002000
       
   489  #define KDB_LAST_ADMIN_UNLOCK_ATTR           0x004000
       
   490 +#define KDB_PWD_HISTORY_ATTR                 0x008000
       
   491  
       
   492  /*
       
   493   * This is a private contract between krb5_ldap_lockout_audit()
       
   494 @@ -112,6 +114,12 @@ krb5_ldap_iterate(krb5_context, char *,
       
   495                    krb5_pointer, krb5_flags);
       
   496  
       
   497  void
       
   498 +k5_free_key_data(krb5_int16 n_key_data, krb5_key_data *key_data);
       
   499 +
       
   500 +void
       
   501 +krb5_dbe_free_contents(krb5_context context, krb5_db_entry *entry);
       
   502 +
       
   503 +void
       
   504  krb5_dbe_free_contents(krb5_context, krb5_db_entry *);
       
   505  
       
   506  krb5_error_code
       
   507 @@ -121,8 +129,11 @@ krb5_error_code
       
   508  krb5_ldap_parse_principal_name(char *, char **);
       
   509  
       
   510  krb5_error_code
       
   511 +krb5_decode_histkey(krb5_context, struct berval **, osa_princ_ent_rec *);
       
   512 +
       
   513 +krb5_error_code
       
   514  krb5_decode_krbsecretkey(krb5_context, krb5_db_entry *, struct berval **,
       
   515 -                         krb5_tl_data *, krb5_kvno *);
       
   516 +                         krb5_kvno *);
       
   517  
       
   518  krb5_error_code
       
   519  berval2tl_data(struct berval *in, krb5_tl_data **out);
       
   520 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
       
   521 +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
       
   522 @@ -1,6 +1,35 @@
       
   523  /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
       
   524  /* plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c */
       
   525  /*
       
   526 + * Copyright (C) 2016 by the Massachusetts Institute of Technology.
       
   527 + * All rights reserved.
       
   528 + *
       
   529 + * Redistribution and use in source and binary forms, with or without
       
   530 + * modification, are permitted provided that the following conditions
       
   531 + * are met:
       
   532 + *
       
   533 + * * Redistributions of source code must retain the above copyright
       
   534 + *   notice, this list of conditions and the following disclaimer.
       
   535 + *
       
   536 + * * Redistributions in binary form must reproduce the above copyright
       
   537 + *   notice, this list of conditions and the following disclaimer in
       
   538 + *   the documentation and/or other materials provided with the
       
   539 + *   distribution.
       
   540 + *
       
   541 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
       
   542 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
       
   543 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
       
   544 + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
       
   545 + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
       
   546 + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
       
   547 + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
       
   548 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
       
   549 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
       
   550 + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
       
   551 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
       
   552 + * OF THE POSSIBILITY OF SUCH DAMAGE.
       
   553 + */
       
   554 +/*
       
   555   * Copyright (c) 2004-2005, Novell, Inc.
       
   556   * All rights reserved.
       
   557   *
       
   558 @@ -362,13 +391,14 @@ asn1_encode_sequence_of_keys(krb5_key_da
       
   559  }
       
   560  
       
   561  static krb5_error_code
       
   562 -asn1_decode_sequence_of_keys(krb5_data *in, krb5_key_data **out,
       
   563 -                             krb5_int16 *n_key_data, krb5_kvno *mkvno)
       
   564 +asn1_decode_sequence_of_keys(krb5_data *in, ldap_seqof_key_data *out)
       
   565  {
       
   566      krb5_error_code err;
       
   567      ldap_seqof_key_data *p;
       
   568      int i;
       
   569  
       
   570 +    memset(out, 0, sizeof(*out));
       
   571 +
       
   572      /*
       
   573       * This should be pushed back into other library initialization
       
   574       * code.
       
   575 @@ -390,9 +420,7 @@ asn1_decode_sequence_of_keys(krb5_data *
       
   576              p->key_data[i].key_data_ver = 2;
       
   577      }
       
   578  
       
   579 -    *out = p->key_data;
       
   580 -    *n_key_data = p->n_key_data;
       
   581 -    *mkvno = p->mkvno;
       
   582 +    *out = *p;
       
   583      free(p);
       
   584      return 0;
       
   585  }
       
   586 @@ -416,19 +444,24 @@ free_berdata(struct berval **array)
       
   587      }
       
   588  }
       
   589  
       
   590 -/* Decoding ASN.1 encoded key */
       
   591 -static struct berval **
       
   592 -krb5_encode_krbsecretkey(krb5_key_data *key_data_in, int n_key_data,
       
   593 -                         krb5_kvno mkvno) {
       
   594 -    struct berval **ret = NULL;
       
   595 -    int currkvno;
       
   596 -    int num_versions = 1;
       
   597 -    int i, j, last;
       
   598 +/*
       
   599 + * Encode krb5_key_data into a berval struct for insertion into LDAP.
       
   600 + */
       
   601 +static krb5_error_code
       
   602 +encode_keys(krb5_key_data *key_data_in, int n_key_data, krb5_kvno mkvno,
       
   603 +            struct berval **bval_out)
       
   604 +{
       
   605      krb5_error_code err = 0;
       
   606 +    int i;
       
   607      krb5_key_data *key_data = NULL;
       
   608 +    struct berval *bval = NULL;
       
   609 +    krb5_data *code;
       
   610  
       
   611 -    if (n_key_data < 0)
       
   612 -        return NULL;
       
   613 +    *bval_out = NULL;
       
   614 +    if (n_key_data <= 0) {
       
   615 +        err = EINVAL;
       
   616 +        goto cleanup;
       
   617 +    }
       
   618  
       
   619      /* Make a shallow copy of the key data so we can alter it. */
       
   620      key_data = k5calloc(n_key_data, sizeof(*key_data), &err);
       
   621 @@ -447,31 +480,68 @@ krb5_encode_krbsecretkey(krb5_key_data *
       
   622          }
       
   623      }
       
   624  
       
   625 +    bval = k5alloc(sizeof(struct berval), &err);
       
   626 +    if (bval == NULL)
       
   627 +        goto cleanup;
       
   628 +
       
   629 +    err = asn1_encode_sequence_of_keys(key_data, n_key_data, mkvno, &code);
       
   630 +    if (err)
       
   631 +        goto cleanup;
       
   632 +
       
   633 +    /* Steal the data pointer from code for bval and discard code. */
       
   634 +    bval->bv_len = code->length;
       
   635 +    bval->bv_val = code->data;
       
   636 +    free(code);
       
   637 +
       
   638 +    *bval_out = bval;
       
   639 +    bval = NULL;
       
   640 +
       
   641 +cleanup:
       
   642 +    free(key_data);
       
   643 +    free(bval);
       
   644 +    return err;
       
   645 +}
       
   646 +
       
   647 +/* Decoding ASN.1 encoded key */
       
   648 +static struct berval **
       
   649 +krb5_encode_krbsecretkey(krb5_key_data *key_data, int n_key_data,
       
   650 +                         krb5_kvno mkvno)
       
   651 +{
       
   652 +    struct berval **ret = NULL;
       
   653 +    int currkvno;
       
   654 +    int num_versions = 0;
       
   655 +    int i, j, last;
       
   656 +    krb5_error_code err = 0;
       
   657 +
       
   658 +    if (n_key_data < 0)
       
   659 +        return NULL;
       
   660 +
       
   661      /* Find the number of key versions */
       
   662 -    for (i = 0; i < n_key_data - 1; i++)
       
   663 -        if (key_data[i].key_data_kvno != key_data[i + 1].key_data_kvno)
       
   664 -            num_versions++;
       
   665 +    if (n_key_data > 0) {
       
   666 +        for (i = 0, num_versions = 1; i < n_key_data - 1; i++) {
       
   667 +            if (key_data[i].key_data_kvno != key_data[i + 1].key_data_kvno)
       
   668 +                num_versions++;
       
   669 +        }
       
   670 +    }
       
   671  
       
   672 -    ret = (struct berval **) calloc (num_versions + 1, sizeof (struct berval *));
       
   673 +    ret = calloc(num_versions + 1, sizeof(struct berval *));
       
   674      if (ret == NULL) {
       
   675          err = ENOMEM;
       
   676          goto cleanup;
       
   677      }
       
   678 -    for (i = 0, last = 0, j = 0, currkvno = key_data[0].key_data_kvno; i < n_key_data; i++) {
       
   679 -        krb5_data *code;
       
   680 +    ret[num_versions] = NULL;
       
   681 +
       
   682 +    /* n_key_data may be 0 if a principal is created without a key. */
       
   683 +    if (n_key_data == 0)
       
   684 +        goto cleanup;
       
   685 +
       
   686 +    currkvno = key_data[0].key_data_kvno;
       
   687 +    for (i = 0, last = 0, j = 0; i < n_key_data; i++) {
       
   688          if (i == n_key_data - 1 || key_data[i + 1].key_data_kvno != currkvno) {
       
   689 -            ret[j] = k5alloc(sizeof(struct berval), &err);
       
   690 -            if (ret[j] == NULL)
       
   691 -                goto cleanup;
       
   692 -            err = asn1_encode_sequence_of_keys(key_data + last,
       
   693 -                                               (krb5_int16)i - last + 1,
       
   694 -                                               mkvno, &code);
       
   695 +            err = encode_keys(key_data + last, (krb5_int16)i - last + 1, mkvno,
       
   696 +                              &ret[j]);
       
   697              if (err)
       
   698                  goto cleanup;
       
   699 -            /*CHECK_NULL(ret[j]); */
       
   700 -            ret[j]->bv_len = code->length;
       
   701 -            ret[j]->bv_val = code->data;
       
   702 -            free(code);
       
   703              j++;
       
   704              last = i + 1;
       
   705  
       
   706 @@ -479,11 +549,48 @@ krb5_encode_krbsecretkey(krb5_key_data *
       
   707                  currkvno = key_data[i + 1].key_data_kvno;
       
   708          }
       
   709      }
       
   710 -    ret[num_versions] = NULL;
       
   711  
       
   712  cleanup:
       
   713 +    if (err != 0) {
       
   714 +        free_berdata(ret);
       
   715 +        ret = NULL;
       
   716 +    }
       
   717  
       
   718 -    free(key_data);
       
   719 +    return ret;
       
   720 +}
       
   721 +
       
   722 +/*
       
   723 + * Encode a principal's key history for insertion into ldap.
       
   724 + */
       
   725 +static struct berval **
       
   726 +krb5_encode_histkey(osa_princ_ent_rec *princ_ent)
       
   727 +{
       
   728 +    unsigned int i;
       
   729 +    krb5_error_code err = 0;
       
   730 +    struct berval **ret = NULL;
       
   731 +
       
   732 +    if (princ_ent->old_key_len <= 0)
       
   733 +        return NULL;
       
   734 +
       
   735 +    ret = k5calloc(princ_ent->old_key_len + 1, sizeof(struct berval *), &err);
       
   736 +    if (ret == NULL)
       
   737 +        goto cleanup;
       
   738 +
       
   739 +    for (i = 0; i < princ_ent->old_key_len; i++) {
       
   740 +        if (princ_ent->old_keys[i].n_key_data <= 0) {
       
   741 +            err = EINVAL;
       
   742 +            goto cleanup;
       
   743 +        }
       
   744 +        err = encode_keys(princ_ent->old_keys[i].key_data,
       
   745 +                          princ_ent->old_keys[i].n_key_data,
       
   746 +                          princ_ent->admin_history_kvno, &ret[i]);
       
   747 +        if (err)
       
   748 +            goto cleanup;
       
   749 +    }
       
   750 +
       
   751 +    ret[princ_ent->old_key_len] = NULL;
       
   752 +
       
   753 +cleanup:
       
   754      if (err != 0) {
       
   755          free_berdata(ret);
       
   756          ret = NULL;
       
   757 @@ -1004,7 +1111,7 @@ krb5_ldap_put_principal(krb5_context con
       
   758          free (strval[0]);
       
   759      }
       
   760  
       
   761 -    if (entry->mask & KADM5_POLICY) {
       
   762 +    if (entry->mask & KADM5_POLICY || entry->mask & KADM5_KEY_HIST) {
       
   763          memset(&princ_ent, 0, sizeof(princ_ent));
       
   764          for (tl_data=entry->tl_data; tl_data; tl_data=tl_data->tl_data_next) {
       
   765              if (tl_data->tl_data_type == KRB5_TL_KADM_DATA) {
       
   766 @@ -1014,7 +1121,9 @@ krb5_ldap_put_principal(krb5_context con
       
   767                  break;
       
   768              }
       
   769          }
       
   770 +    }
       
   771  
       
   772 +    if (entry->mask & KADM5_POLICY) {
       
   773          if (princ_ent.aux_attributes & KADM5_POLICY) {
       
   774              memset(strval, 0, sizeof(strval));
       
   775              if ((st = krb5_ldap_name_to_policydn (context, princ_ent.policy, &polname)) != 0)
       
   776 @@ -1042,6 +1151,22 @@ krb5_ldap_put_principal(krb5_context con
       
   777              goto cleanup;
       
   778      }
       
   779  
       
   780 +    if (entry->mask & KADM5_KEY_HIST) {
       
   781 +        bersecretkey = krb5_encode_histkey(&princ_ent);
       
   782 +        if (bersecretkey == NULL) {
       
   783 +            st = ENOMEM;
       
   784 +            goto cleanup;
       
   785 +        }
       
   786 +
       
   787 +        st = krb5_add_ber_mem_ldap_mod(&mods, "krbpwdhistory",
       
   788 +                                       LDAP_MOD_REPLACE | LDAP_MOD_BVALUES,
       
   789 +                                       bersecretkey);
       
   790 +        if (st != 0)
       
   791 +            goto cleanup;
       
   792 +        free_berdata(bersecretkey);
       
   793 +        bersecretkey = NULL;
       
   794 +    }
       
   795 +
       
   796      if (entry->mask & KADM5_KEY_DATA || entry->mask & KADM5_KVNO) {
       
   797          krb5_kvno mkvno;
       
   798  
       
   799 @@ -1376,22 +1501,62 @@ cleanup:
       
   800      return st;
       
   801  }
       
   802  
       
   803 -krb5_error_code
       
   804 -krb5_decode_krbsecretkey(krb5_context context, krb5_db_entry *entries,
       
   805 -                         struct berval **bvalues,
       
   806 -                         krb5_tl_data *userinfo_tl_data, krb5_kvno *mkvno)
       
   807 +static void
       
   808 +free_ldap_seqof_key_data(ldap_seqof_key_data *keysets, krb5_int16 n_keysets)
       
   809  {
       
   810 -    char                        *user=NULL;
       
   811 -    int                         i=0, j=0, noofkeys=0;
       
   812 -    krb5_key_data               *key_data=NULL, *tmp;
       
   813 -    krb5_error_code             st=0;
       
   814 +    int i;
       
   815  
       
   816 -    if ((st=krb5_unparse_name(context, entries->princ, &user)) != 0)
       
   817 +    if (keysets == NULL)
       
   818 +        return;
       
   819 +
       
   820 +    for (i = 0; i < n_keysets; i++)
       
   821 +        k5_free_key_data(keysets[i].n_key_data, keysets[i].key_data);
       
   822 +    free(keysets);
       
   823 +}
       
   824 +
       
   825 +/*
       
   826 + * Decode keys from ldap search results.
       
   827 + *
       
   828 + * Arguments:
       
   829 + *  - bvalues
       
   830 + *      The ldap search results containing the key data.
       
   831 + *  - mkvno
       
   832 + *      The master kvno that the keys were encrypted with.
       
   833 + *  - keysets_out
       
   834 + *      The decoded keys in a ldap_seqof_key_data struct.  Must be freed using
       
   835 + *      free_ldap_seqof_key_data.
       
   836 + *  - n_keysets_out
       
   837 + *      The number of entries in keys_out.
       
   838 + *  - total_keys_out
       
   839 + *      An optional argument that if given will be set to the total number of
       
   840 + *      keys found throughout all the entries: sum(keys_out.n_key_data)
       
   841 + *      May be NULL.
       
   842 + */
       
   843 +static krb5_error_code
       
   844 +decode_keys(struct berval **bvalues, ldap_seqof_key_data **keysets_out,
       
   845 +            krb5_int16 *n_keysets_out, krb5_int16 *total_keys_out)
       
   846 +{
       
   847 +    krb5_error_code err = 0;
       
   848 +    krb5_int16 n_keys, i, ki, total_keys;
       
   849 +    ldap_seqof_key_data *keysets = NULL;
       
   850 +
       
   851 +    *keysets_out = NULL;
       
   852 +    *n_keysets_out = 0;
       
   853 +    if (total_keys_out)
       
   854 +        *total_keys_out = 0;
       
   855 +
       
   856 +    /* Precount the number of keys. */
       
   857 +    for (n_keys = 0, i = 0; bvalues[i] != NULL; i++) {
       
   858 +        if (bvalues[i]->bv_len > 0)
       
   859 +            n_keys++;
       
   860 +    }
       
   861 +
       
   862 +    keysets = k5calloc(n_keys, sizeof(ldap_seqof_key_data), &err);
       
   863 +    if (keysets == NULL)
       
   864          goto cleanup;
       
   865 +    memset(keysets, 0, n_keys * sizeof(ldap_seqof_key_data));
       
   866  
       
   867 -    for (i=0; bvalues[i] != NULL; ++i) {
       
   868 -        krb5_int16 n_kd;
       
   869 -        krb5_key_data *kd;
       
   870 +    for (i = 0, ki = 0, total_keys = 0; bvalues[i] != NULL; i++) {
       
   871          krb5_data in;
       
   872  
       
   873          if (bvalues[i]->bv_len == 0)
       
   874 @@ -1399,39 +1564,131 @@ krb5_decode_krbsecretkey(krb5_context co
       
   875          in.length = bvalues[i]->bv_len;
       
   876          in.data = bvalues[i]->bv_val;
       
   877  
       
   878 -        st = asn1_decode_sequence_of_keys (&in,
       
   879 -                                           &kd,
       
   880 -                                           &n_kd,
       
   881 -                                           mkvno);
       
   882 -
       
   883 -        if (st != 0) {
       
   884 -            const char *msg = error_message(st);
       
   885 -            st = -1; /* Something more appropriate ? */
       
   886 -            k5_setmsg(context, st,
       
   887 -                      _("unable to decode stored principal key data (%s)"),
       
   888 -                      msg);
       
   889 -            goto cleanup;
       
   890 -        }
       
   891 -        noofkeys += n_kd;
       
   892 -        tmp = key_data;
       
   893 -        /* Allocate an extra key data to avoid allocating zero bytes. */
       
   894 -        key_data = realloc(key_data, (noofkeys + 1) * sizeof (krb5_key_data));
       
   895 -        if (key_data == NULL) {
       
   896 -            key_data = tmp;
       
   897 -            st = ENOMEM;
       
   898 +        err = asn1_decode_sequence_of_keys(&in, &keysets[ki]);
       
   899 +        if (err)
       
   900              goto cleanup;
       
   901 -        }
       
   902 -        for (j = 0; j < n_kd; j++)
       
   903 -            key_data[noofkeys - n_kd + j] = kd[j];
       
   904 -        free (kd);
       
   905 +
       
   906 +        if (total_keys_out)
       
   907 +            total_keys += keysets[ki].n_key_data;
       
   908 +        ki++;
       
   909 +    }
       
   910 +
       
   911 +    if (total_keys_out)
       
   912 +        *total_keys_out = total_keys;
       
   913 +
       
   914 +    *n_keysets_out = n_keys;
       
   915 +    *keysets_out = keysets;
       
   916 +    keysets = NULL;
       
   917 +    n_keys = 0;
       
   918 +
       
   919 +cleanup:
       
   920 +    free_ldap_seqof_key_data(keysets, n_keys);
       
   921 +    return err;
       
   922 +}
       
   923 +
       
   924 +krb5_error_code
       
   925 +krb5_decode_krbsecretkey(krb5_context context, krb5_db_entry *entries,
       
   926 +                         struct berval **bvalues, krb5_kvno *mkvno)
       
   927 +{
       
   928 +    krb5_key_data *key_data = NULL, *tmp;
       
   929 +    krb5_error_code err = 0;
       
   930 +    ldap_seqof_key_data *keysets = NULL;
       
   931 +    krb5_int16 i, n_keysets = 0, total_keys = 0;
       
   932 +
       
   933 +    err = decode_keys(bvalues, &keysets, &n_keysets, &total_keys);
       
   934 +    if (err != 0) {
       
   935 +        k5_prependmsg(context, err,
       
   936 +                      _("unable to decode stored principal key data"));
       
   937 +        goto cleanup;
       
   938      }
       
   939  
       
   940 -    entries->n_key_data = noofkeys;
       
   941 +    key_data = k5calloc(total_keys, sizeof(krb5_key_data), &err);
       
   942 +    if (key_data == NULL)
       
   943 +        goto cleanup;
       
   944 +    memset(key_data, 0, total_keys * sizeof(krb5_key_data));
       
   945 +
       
   946 +    if (n_keysets > 0)
       
   947 +        *mkvno = keysets[0].mkvno;
       
   948 +
       
   949 +    /* Transfer key data values from keysets to a flat list in entries. */
       
   950 +    tmp = key_data;
       
   951 +    for (i = 0; i < n_keysets; i++) {
       
   952 +        memcpy(tmp, keysets[i].key_data,
       
   953 +               sizeof(krb5_key_data) * keysets[i].n_key_data);
       
   954 +        tmp += keysets[i].n_key_data;
       
   955 +        keysets[i].n_key_data = 0;
       
   956 +    }
       
   957 +    entries->n_key_data = total_keys;
       
   958      entries->key_data = key_data;
       
   959 +    key_data = NULL;
       
   960  
       
   961  cleanup:
       
   962 -    free (user);
       
   963 -    return st;
       
   964 +    free_ldap_seqof_key_data(keysets, n_keysets);
       
   965 +    k5_free_key_data(total_keys, key_data);
       
   966 +    return err;
       
   967 +}
       
   968 +
       
   969 +static int
       
   970 +compare_osa_pw_hist_ent(const void *left_in, const void *right_in)
       
   971 +{
       
   972 +    int kvno_left, kvno_right;
       
   973 +    osa_pw_hist_ent *left = (osa_pw_hist_ent *)left_in;
       
   974 +    osa_pw_hist_ent *right = (osa_pw_hist_ent *)right_in;
       
   975 +
       
   976 +    kvno_left = left->n_key_data ? left->key_data[0].key_data_kvno : 0;
       
   977 +    kvno_right = right->n_key_data ? right->key_data[0].key_data_kvno : 0;
       
   978 +    return kvno_left - kvno_right;
       
   979 +}
       
   980 +
       
   981 +/*
       
   982 + * Decode the key history entries from an LDAP search.
       
   983 + *
       
   984 + * NOTE: the caller must free princ_ent->old_keys even on error.
       
   985 + */
       
   986 +krb5_error_code
       
   987 +krb5_decode_histkey(krb5_context context, struct berval **bvalues,
       
   988 +                    osa_princ_ent_rec *princ_ent)
       
   989 +{
       
   990 +    krb5_error_code err = 0;
       
   991 +    krb5_int16 i, n_keysets = 0;
       
   992 +    ldap_seqof_key_data *keysets = NULL;
       
   993 +
       
   994 +    err = decode_keys(bvalues, &keysets, &n_keysets, NULL);
       
   995 +    if (err != 0) {
       
   996 +        k5_prependmsg(context, err,
       
   997 +                      _("unable to decode stored principal pw history"));
       
   998 +        goto cleanup;
       
   999 +    }
       
  1000 +
       
  1001 +    princ_ent->old_keys = k5calloc(n_keysets, sizeof(osa_pw_hist_ent), &err);
       
  1002 +    if (princ_ent->old_keys == NULL)
       
  1003 +        goto cleanup;
       
  1004 +    princ_ent->old_key_len = n_keysets;
       
  1005 +
       
  1006 +    if (n_keysets > 0)
       
  1007 +        princ_ent->admin_history_kvno = keysets[0].mkvno;
       
  1008 +
       
  1009 +    /* Transfer key data pointers from keysets to princ_ent. */
       
  1010 +    for (i = 0; i < n_keysets; i++) {
       
  1011 +        princ_ent->old_keys[i].n_key_data = keysets[i].n_key_data;
       
  1012 +        princ_ent->old_keys[i].key_data = keysets[i].key_data;
       
  1013 +        keysets[i].n_key_data = 0;
       
  1014 +        keysets[i].key_data = NULL;
       
  1015 +    }
       
  1016 +
       
  1017 +    /* Sort the principal entries by kvno in ascending order. */
       
  1018 +    qsort(princ_ent->old_keys, princ_ent->old_key_len, sizeof(osa_pw_hist_ent),
       
  1019 +          &compare_osa_pw_hist_ent);
       
  1020 +
       
  1021 +    princ_ent->aux_attributes |= KADM5_KEY_HIST;
       
  1022 +
       
  1023 +    /* Set the next key to the end of the list.  The queue will be lengthened
       
  1024 +     * if it isn't full yet; the first entry will be replaced if it is full. */
       
  1025 +    princ_ent->old_key_next = princ_ent->old_key_len;
       
  1026 +
       
  1027 +cleanup:
       
  1028 +    free_ldap_seqof_key_data(keysets, n_keysets);
       
  1029 +    return err;
       
  1030  }
       
  1031  
       
  1032  static char *
       
  1033 --- a/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.c
       
  1034 +++ b/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.c
       
  1035 @@ -204,20 +204,14 @@ krb5_lookup_tl_kadm_data(krb5_tl_data *t
       
  1036  
       
  1037  krb5_error_code
       
  1038  krb5_update_tl_kadm_data(krb5_context context, krb5_db_entry *entry,
       
  1039 -			 char *policy_dn)
       
  1040 +			 osa_princ_ent_rec *princ_entry)
       
  1041  {
       
  1042      XDR xdrs;
       
  1043 -    osa_princ_ent_rec princ_entry;
       
  1044      krb5_tl_data tl_data;
       
  1045      krb5_error_code retval;
       
  1046  
       
  1047 -    memset(&princ_entry, 0, sizeof(osa_princ_ent_rec));
       
  1048 -    princ_entry.admin_history_kvno = 2;
       
  1049 -    princ_entry.aux_attributes = KADM5_POLICY;
       
  1050 -    princ_entry.policy = policy_dn;
       
  1051 -
       
  1052      xdralloc_create(&xdrs, XDR_ENCODE);
       
  1053 -    if (! ldap_xdr_osa_princ_ent_rec(&xdrs, &princ_entry)) {
       
  1054 +    if (! ldap_xdr_osa_princ_ent_rec(&xdrs, princ_entry)) {
       
  1055  	xdr_destroy(&xdrs);
       
  1056  	return KADM5_XDR_FAILURE;
       
  1057      }
       
  1058 --- a/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.h
       
  1059 +++ b/src/plugins/kdb/ldap/libkdb_ldap/princ_xdr.h
       
  1060 @@ -57,6 +57,6 @@ krb5_lookup_tl_kadm_data(krb5_tl_data *t
       
  1061  
       
  1062  krb5_error_code
       
  1063  krb5_update_tl_kadm_data(krb5_context context, krb5_db_entry *entry,
       
  1064 -			 char *policy_dn);
       
  1065 +                         osa_princ_ent_rec *princ_entry);
       
  1066  
       
  1067  #endif
       
  1068 --- a/src/tests/kdbtest.c
       
  1069 +++ b/src/tests/kdbtest.c
       
  1070 @@ -97,7 +97,7 @@ static krb5_tl_data tl3 = { &tl4, KRB5_T
       
  1071                              U("\x12\x34\x5C\x01\x00\x00\x00\x08"
       
  1072                                "\x3C\x74\x65\x73\x74\x2A\x3E\x00"
       
  1073                                "\x00\x00\x08\x00\x00\x00\x00\x00"
       
  1074 -                              "\x00\x00\x00\x02\x00\x00\x00\x00") };
       
  1075 +                              "\x00\x00\x00\x00\x00\x00\x00\x00") };
       
  1076  static krb5_tl_data tl2 = { &tl3, KRB5_TL_MOD_PRINC, 8, U("\5\6\7\0x@Y\0") };
       
  1077  static krb5_tl_data tl1 = { &tl2, KRB5_TL_LAST_PWD_CHANGE, 4, U("\1\2\3\4") };
       
  1078  
       
  1079 --- a/src/tests/t_kdb.py
       
  1080 +++ b/src/tests/t_kdb.py
       
  1081 @@ -337,6 +337,31 @@ realm.run([kadminl, 'modprinc', '+requir
       
  1082  realm.kinit('canon', password('canon'))
       
  1083  realm.kinit('alias', password('canon'), ['-C'])
       
  1084  
       
  1085 +# Test password history.
       
  1086 +def test_pwhist(nhist):
       
  1087 +    def cpw(n, **kwargs):
       
  1088 +        realm.run([kadminl, 'cpw', '-pw', str(n), princ], **kwargs)
       
  1089 +    def cpw_fail(n):
       
  1090 +        cpw(n, expected_code=1)
       
  1091 +    output('*** Testing password history of size %d\n' % nhist)
       
  1092 +    princ = 'pwhistprinc' + str(nhist)
       
  1093 +    pol = 'pwhistpol' + str(nhist)
       
  1094 +    realm.run([kadminl, 'addpol', '-history', str(nhist), pol])
       
  1095 +    realm.run([kadminl, 'addprinc', '-policy', pol, '-nokey', princ])
       
  1096 +    for i in range(nhist):
       
  1097 +        # Set a password, then check that all previous passwords fail.
       
  1098 +        cpw(i)
       
  1099 +        for j in range(i + 1):
       
  1100 +            cpw_fail(j)
       
  1101 +    # Set one more new password, and make sure the oldest key is
       
  1102 +    # rotated out.
       
  1103 +    cpw(nhist)
       
  1104 +    cpw_fail(1)
       
  1105 +    cpw(0)
       
  1106 +
       
  1107 +for n in (1, 2, 3, 4, 5):
       
  1108 +    test_pwhist(n)
       
  1109 +
       
  1110  # Regression test for #7980 (fencepost when dividing keys up by kvno).
       
  1111  realm.run([kadminl, 'addprinc', '-randkey', '-e', 'aes256-cts,aes128-cts',
       
  1112             'kvnoprinc'])
       
  1113 @@ -368,6 +393,13 @@ out = realm.run([kadminl, 'getprinc', 'k
       
  1114  if 'Number of keys: 0' not in out:
       
  1115      fail('After purgekeys -all, keys remain')
       
  1116  
       
  1117 +# Test for 8354 (old password history entries when -keepold is used)
       
  1118 +realm.run([kadminl, 'addpol', '-history', '2', 'keepoldpasspol'])
       
  1119 +realm.run([kadminl, 'addprinc', '-policy', 'keepoldpasspol', '-pw', 'aaaa',
       
  1120 +           'keepoldpassprinc'])
       
  1121 +for p in ('bbbb', 'cccc', 'aaaa'):
       
  1122 +    realm.run([kadminl, 'cpw', '-keepold', '-pw', p, 'keepoldpassprinc'])
       
  1123 +
       
  1124  realm.stop()
       
  1125  
       
  1126  # Briefly test dump and load.