krb5 commit: Policy extensions + new policy: allowed ks types

Greg Hudson ghudson at MIT.EDU
Mon Jul 30 19:11:47 EDT 2012


https://github.com/krb5/krb5/commit/5829ca2b348974e52a67b553afc7f7491007c33a
commit 5829ca2b348974e52a67b553afc7f7491007c33a
Author: Nicolas Williams <nico at cryptonector.com>
Date:   Wed Jul 18 16:27:35 2012 -0500

    Policy extensions + new policy: allowed ks types
    
    This simply adds KADM5_API_VERSION_4 and various fields to the
    policy structures:
    
     - attributes         (policy-ish principal attributes)
     - max_life           (max ticket life)
     - max_renewable_life (max ticket renewable life)
     - allowed_keysalts   (allowed key/salt types)
     - TL data            (future policy extensions)
    
    Of these only allowed_keysalts is currently implemented.
    
    Some refactoring of TL data handling is also done.
    
    ticket: 7223 (new)

 .../krb_admins/admin_commands/kadmin_local.rst     |    8 +
 .../krb_admins/admin_commands/kdb5_util.rst        |   15 +
 src/clients/kinit/kinit_kdb.c                      |    2 +-
 src/include/kdb.h                                  |   15 +
 src/kadmin/cli/kadmin.c                            |   73 +++--
 src/kadmin/dbutil/dump.c                           |  316 ++++++++++++++++----
 src/kadmin/dbutil/kadm5_create.c                   |    2 +-
 src/kadmin/dbutil/kdb5_util.c                      |    4 +-
 src/kadmin/server/ovsec_kadmd.c                    |    2 +-
 src/kadmin/testing/util/tcl_kadm5.c                |    2 +
 src/lib/kadm5/admin.h                              |   32 ++-
 src/lib/kadm5/admin_internal.h                     |    2 +-
 src/lib/kadm5/clnt/client_init.c                   |   12 +-
 src/lib/kadm5/clnt/clnt_policy.c                   |    2 +
 src/lib/kadm5/kadm_err.et                          |    1 +
 src/lib/kadm5/kadm_rpc_xdr.c                       |   40 ++-
 src/lib/kadm5/misc_free.c                          |   15 +-
 src/lib/kadm5/srv/server_init.c                    |    2 +-
 src/lib/kadm5/srv/svr_policy.c                     |  190 +++++++++++-
 src/lib/kadm5/srv/svr_principal.c                  |  258 ++++++++++++++---
 src/lib/kadm5/unit-test/destroy-test.c             |    2 +-
 src/lib/kadm5/unit-test/handle-test.c              |    2 +-
 src/lib/kadm5/unit-test/init-test.c                |    2 +-
 src/lib/kadm5/unit-test/iter-test.c                |    2 +-
 src/lib/kadm5/unit-test/randkey-test.c             |    2 +-
 src/lib/kadm5/unit-test/setkey-test.c              |    2 +-
 src/lib/kdb/kdb5.c                                 |   20 +-
 src/lib/kdb/libkdb5.exports                        |    1 +
 src/plugins/kdb/db2/pol_xdr.c                      |   75 ++---
 src/plugins/kdb/db2/policy_db.h                    |    1 +
 src/slave/kpropd.c                                 |    2 +-
 src/tests/Makefile.in                              |    1 +
 src/tests/hist.c                                   |    2 +-
 src/tests/t_allowed_keysalts.py                    |   93 ++++++
 src/tests/t_general.py                             |   16 +
 35 files changed, 996 insertions(+), 220 deletions(-)

diff --git a/doc/rst_source/krb_admins/admin_commands/kadmin_local.rst b/doc/rst_source/krb_admins/admin_commands/kadmin_local.rst
index 8e55b3f..fbb6038 100644
--- a/doc/rst_source/krb_admins/admin_commands/kadmin_local.rst
+++ b/doc/rst_source/krb_admins/admin_commands/kadmin_local.rst
@@ -626,6 +626,14 @@ The following options are available:
     occur without the specified failure count interval elapsing.
     A duration of 0 means forever.
 
+**-allowedkeysalts**
+    Specifies the key/salt tuples supported for long-term keys when
+    setting or changing a principal's password/keys.  See
+    :ref:`Encryption_and_salt_types` in :ref:`kdc.conf(5)` for a list
+    of the accepted values, but note that key/salt tuples must be
+    separated with commas (',') only.  To clear the allowed key/salt
+    policy use a value of '-'.
+
 Example:
 
  ::
diff --git a/doc/rst_source/krb_admins/admin_commands/kdb5_util.rst b/doc/rst_source/krb_admins/admin_commands/kdb5_util.rst
index 3601b4d..ea533f5 100644
--- a/doc/rst_source/krb_admins/admin_commands/kdb5_util.rst
+++ b/doc/rst_source/krb_admins/admin_commands/kdb5_util.rst
@@ -159,6 +159,11 @@ load_dump version 6".  If filename is not specified, or is the string
     load_dump version 5").  This was the dump format produced on
     releases prior to 1.8.
 
+**-r18**
+    causes the dump to be in the Kerberos 5 1.8 format ("kdb5_util
+    load_dump version 6").  This was the dump format produced on
+    releases prior to 1.11.
+
 **-verbose**
     causes the name of each principal and policy to be printed as it
     is dumped.
@@ -220,6 +225,16 @@ Options:
     requires the database to be in "ovsec_adm_import" format.  Must be
     used with the **-update** option.
 
+**-r13**
+    requires the database to be in Kerberos 5 1.3 format ("kdb5_util
+    load_dump version 5").  This was the dump format produced on
+    releases prior to 1.8.
+
+**-r18**
+    requires the database to be in Kerberos 5 1.8 format ("kdb5_util
+    load_dump version 6").  This was the dump format produced on
+    releases prior to 1.11.
+
 **-hash**
     requires the database to be stored as a hash.  If this option is
     not specified, the database will be stored as a btree.  This
diff --git a/src/clients/kinit/kinit_kdb.c b/src/clients/kinit/kinit_kdb.c
index cc3df04..8e949f9 100644
--- a/src/clients/kinit/kinit_kdb.c
+++ b/src/clients/kinit/kinit_kdb.c
@@ -62,7 +62,7 @@ kinit_kdb_init(krb5_context *pcontext, char *realm)
     config.realm = realm;
     retval = kadm5_init(*pcontext, "kinit", NULL /*pass*/,
                         "kinit", &config,
-                        KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+                        KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
                         &server_handle);
     if (retval)
         return retval;
diff --git a/src/include/kdb.h b/src/include/kdb.h
index 291a05b..2a5d2d5 100644
--- a/src/include/kdb.h
+++ b/src/include/kdb.h
@@ -220,6 +220,13 @@ typedef struct _osa_policy_ent_t {
     krb5_ui_4       pw_max_fail;                /* pwdMaxFailure */
     krb5_ui_4       pw_failcnt_interval;        /* pwdFailureCountInterval */
     krb5_ui_4       pw_lockout_duration;        /* pwdLockoutDuration */
+    /* Only valid if version > 2 */
+    krb5_ui_4       attributes;
+    krb5_ui_4       max_life;
+    krb5_ui_4       max_renewable_life;
+    char          * allowed_keysalts;
+    krb5_int16      n_tl_data;
+    krb5_tl_data  * tl_data;
 } osa_policy_ent_rec, *osa_policy_ent_t;
 
 typedef       void    (*osa_adb_iter_policy_func) (void *, osa_policy_ent_t);
@@ -232,6 +239,8 @@ typedef struct __krb5_key_salt_tuple {
 #define KRB5_KDB_MAGIC_NUMBER           0xdbdbdbdb
 #define KRB5_KDB_V1_BASE_LENGTH         38
 
+#define KRB5_KDB_MAX_ALLOWED_KS_LEN     512
+
 #define KRB5_TL_LAST_PWD_CHANGE         0x0001
 #define KRB5_TL_MOD_PRINC               0x0002
 #define KRB5_TL_KADM_DATA               0x0003
@@ -566,6 +575,12 @@ krb5_dbe_delete_tl_data( krb5_context    context,
                          krb5_int16      tl_data_type);
 
 krb5_error_code
+krb5_db_update_tl_data(krb5_context          context,
+                       krb5_int16          * n_tl_datap,
+                       krb5_tl_data        **tl_datap,
+                       krb5_tl_data        * new_tl_data);
+
+krb5_error_code
 krb5_dbe_update_tl_data( krb5_context          context,
                          krb5_db_entry       * entry,
                          krb5_tl_data        * new_tl_data);
diff --git a/src/kadmin/cli/kadmin.c b/src/kadmin/cli/kadmin.c
index bcc01b5..649bbc1 100644
--- a/src/kadmin/cli/kadmin.c
+++ b/src/kadmin/cli/kadmin.c
@@ -489,13 +489,13 @@ kadmin_startup(int argc, char *argv[])
                  "credentials.\n"), princstr);
         retval = kadm5_init_with_creds(context, princstr, cc, svcname, &params,
                                        KADM5_STRUCT_VERSION,
-                                       KADM5_API_VERSION_3, db_args, &handle);
+                                       KADM5_API_VERSION_4, db_args, &handle);
     } else if (use_anonymous) {
         printf(_("Authenticating as principal %s with password; "
                  "anonymous requested.\n"), princstr);
         retval = kadm5_init_anonymous(context, princstr, svcname, &params,
                                       KADM5_STRUCT_VERSION,
-                                      KADM5_API_VERSION_3, db_args, &handle);
+                                      KADM5_API_VERSION_4, db_args, &handle);
     } else if (use_keytab) {
         if (keytab_name)
             printf(_("Authenticating as principal %s with keytab %s.\n"),
@@ -505,13 +505,13 @@ kadmin_startup(int argc, char *argv[])
                    princstr);
         retval = kadm5_init_with_skey(context, princstr, keytab_name, svcname,
                                       &params, KADM5_STRUCT_VERSION,
-                                      KADM5_API_VERSION_3, db_args, &handle);
+                                      KADM5_API_VERSION_4, db_args, &handle);
     } else {
         printf(_("Authenticating as principal %s with password.\n"),
                princstr);
         retval = kadm5_init_with_password(context, princstr, password, svcname,
                                           &params, KADM5_STRUCT_VERSION,
-                                          KADM5_API_VERSION_3, db_args,
+                                          KADM5_API_VERSION_4, db_args,
                                           &handle);
     }
     if (retval) {
@@ -855,14 +855,14 @@ cleanup:
 }
 
 static void
-kadmin_free_tl_data(kadm5_principal_ent_t princ)
+kadmin_free_tl_data(krb5_int16 *n_tl_datap, krb5_tl_data **tl_datap)
 {
-    krb5_tl_data *tl_data = princ->tl_data, *next;
-    int n_tl_data = princ->n_tl_data;
+    krb5_tl_data *tl_data = *tl_datap, *next;
+    int n_tl_data = *n_tl_datap;
     int i;
 
-    princ->n_tl_data = 0;
-    princ->tl_data = NULL;
+    *n_tl_datap = 0;
+    *tl_datap = NULL;
 
     for (i = 0; tl_data && (i < n_tl_data); i++) {
         next = tl_data->tl_data_next;
@@ -872,12 +872,12 @@ kadmin_free_tl_data(kadm5_principal_ent_t princ)
     }
 }
 
-/* Construct a tl_data element and add it to the tail of princ->tl_data. */
+/* Construct a tl_data element and add it to the tail of *tl_datap. */
 static void
-add_tl_data(kadm5_principal_ent_t princ, krb5_int16 tl_type, krb5_ui_2 len,
-            krb5_octet *contents)
+add_tl_data(krb5_int16 *n_tl_datap, krb5_tl_data **tl_datap,
+            krb5_int16 tl_type, krb5_ui_2 len, krb5_octet *contents)
 {
-    krb5_tl_data *tl_data, **tlp;
+    krb5_tl_data *tl_data;
     krb5_octet *copy;
 
     copy = malloc(len);
@@ -893,9 +893,9 @@ add_tl_data(kadm5_principal_ent_t princ, krb5_int16 tl_type, krb5_ui_2 len,
     tl_data->tl_data_contents = copy;
     tl_data->tl_data_next = NULL;
 
-    for (tlp = &princ->tl_data; *tlp != NULL; tlp = &(*tlp)->tl_data_next);
-    *tlp = tl_data;
-    princ->n_tl_data++;
+    for (; *tl_datap != NULL; tl_datap = &(*tl_datap)->tl_data_next);
+    *tl_datap = tl_data;
+    (*n_tl_datap)++;
 }
 
 static void
@@ -917,7 +917,8 @@ unlock_princ(kadm5_principal_ent_t princ, long *mask, const char *caller)
         exit(1);
     }
     store_32_le((krb5_int32)now, timebuf);
-    add_tl_data(princ, KRB5_TL_LAST_ADMIN_UNLOCK, 4, timebuf);
+    add_tl_data(&princ->n_tl_data, &princ->tl_data,
+                KRB5_TL_LAST_ADMIN_UNLOCK, 4, timebuf);
     *mask |= KADM5_TL_DATA;
 }
 
@@ -949,7 +950,8 @@ kadmin_parse_princ_args(int argc, char *argv[], kadm5_principal_ent_t oprinc,
             if (++i > argc - 2)
                 return -1;
 
-            add_tl_data(oprinc, KRB5_TL_DB_ARGS, strlen(argv[i]) + 1,
+            add_tl_data(&oprinc->n_tl_data, &oprinc->tl_data,
+                        KRB5_TL_DB_ARGS, strlen(argv[i]) + 1,
                         (krb5_octet *)argv[i]);
             *mask |= KADM5_TL_DATA;
             continue;
@@ -1259,7 +1261,7 @@ cleanup:
     krb5_free_principal(context, princ.principal);
     free(ks_tuple);
     free(canon);
-    kadmin_free_tl_data(&princ);
+    kadmin_free_tl_data(&princ.n_tl_data, &princ.tl_data);
 }
 
 void
@@ -1323,7 +1325,7 @@ kadmin_modprinc(int argc, char *argv[])
 cleanup:
     krb5_free_principal(context, kprinc);
     krb5_free_principal(context, princ.principal);
-    kadmin_free_tl_data(&princ);
+    kadmin_free_tl_data(&princ.n_tl_data, &princ.tl_data);
     free(canon);
     free(ks_tuple);
 }
@@ -1473,6 +1475,7 @@ static int
 kadmin_parse_policy_args(int argc, char *argv[], kadm5_policy_ent_t policy,
                          long *mask, char *caller)
 {
+    krb5_error_code retval;
     int i;
     time_t now, date;
 
@@ -1562,6 +1565,25 @@ kadmin_parse_policy_args(int argc, char *argv[], kadm5_policy_ent_t policy,
             }
             *mask |= KADM5_PW_LOCKOUT_DURATION;
             continue;
+        } else if (!strcmp(argv[i], "-allowedkeysalts")) {
+            krb5_key_salt_tuple *ks_tuple = NULL;
+            int n_ks_tuple = 0;
+
+            if (++i > argc - 2)
+                return -1;
+            if (strcmp(argv[i], "-")) {
+                retval = krb5_string_to_keysalts(argv[i], ",", ":.-", 0,
+                                                 &ks_tuple, &n_ks_tuple);
+                if (retval) {
+                    com_err(caller, retval, _("while parsing keysalts %s"),
+                            argv[i]);
+                    return -1;
+                }
+                free(ks_tuple);
+                policy->allowed_keysalts = argv[i];
+            }
+            *mask |= KADM5_POLICY_ALLOWED_KEYSALTS;
+            continue;
         } else
             return -1;
     }
@@ -1580,7 +1602,8 @@ kadmin_addmodpol_usage(char *func)
     fprintf(stderr,
             _("\t\t[-maxlife time] [-minlife time] [-minlength length]\n"
               "\t\t[-minclasses number] [-history number]\n"
-              "\t\t[-maxfailure number] [-failurecountinterval time]\n"));
+              "\t\t[-maxfailure number] [-failurecountinterval time]\n"
+              "\t\t[-allowedkeysalts keysalts]\n"));
     fprintf(stderr, _("\t\t[-lockoutduration time]\n"));
 }
 
@@ -1683,14 +1706,18 @@ kadmin_getpol(int argc, char *argv[])
                strdur(policy.pw_failcnt_interval));
         printf(_("Password lockout duration: %s\n"),
                strdur(policy.pw_lockout_duration));
+        if (policy.allowed_keysalts != NULL)
+            printf(_("Allowed key/salt types: %s\n"), policy.allowed_keysalts);
     } else {
-        printf("\"%s\"\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%lu\t%ld\t%ld\n",
+        printf("\"%s\"\t%ld\t%ld\t%ld\t%ld\t%ld\t%ld\t%lu\t%ld\t%ld\t%s\n",
                policy.policy, policy.pw_max_life, policy.pw_min_life,
                policy.pw_min_length, policy.pw_min_classes,
                policy.pw_history_num, policy.policy_refcnt,
                (unsigned long)policy.pw_max_fail,
                (long)policy.pw_failcnt_interval,
-               (long)policy.pw_lockout_duration);
+               (long)policy.pw_lockout_duration,
+               (policy.allowed_keysalts == NULL) ? "-" :
+               policy.allowed_keysalts);
     }
     kadm5_free_policy_ent(handle, &policy);
 }
diff --git a/src/kadmin/dbutil/dump.c b/src/kadmin/dbutil/dump.c
index 2d817df..e421e76 100644
--- a/src/kadmin/dbutil/dump.c
+++ b/src/kadmin/dbutil/dump.c
@@ -49,6 +49,10 @@ krb5_kvno                       new_mkvno;
 static int      backwards;
 static int      recursive;
 
+#define K5Q1(x)                     #x
+#define K5Q(x)                      K5Q1(x)
+#define K5CONST_WIDTH_SCANF_STR(x)  "%" K5Q(x) "s"
+
 /*
  * Use compile(3) if no regcomp present.
  */
@@ -94,6 +98,7 @@ static krb5_error_code dump_ov_princ (krb5_pointer,
                                       krb5_db_entry *);
 static void dump_k5beta7_policy (void *, osa_policy_ent_t);
 static void dump_r1_8_policy (void *, osa_policy_ent_t);
+static void dump_r1_11_policy (void *, osa_policy_ent_t);
 
 typedef krb5_error_code (*dump_func)(krb5_pointer,
                                      krb5_db_entry *);
@@ -106,6 +111,8 @@ static int process_k5beta7_record (char *, krb5_context,
                                    FILE *, int, int *);
 static int process_r1_8_record (char *, krb5_context,
                                 FILE *, int, int *);
+static int process_r1_11_record (char *, krb5_context,
+                                FILE *, int, int *);
 static int process_ov_record (char *, krb5_context,
                               FILE *, int, int *);
 typedef krb5_error_code (*load_func)(char *, krb5_context,
@@ -185,14 +192,23 @@ dump_version r1_8_version = {
     dump_r1_8_policy,
     process_r1_8_record,
 };
+dump_version r1_11_version = {
+    "Kerberos version 5 release 1.11",
+    "kdb5_util load_dump version 7\n",
+    0,
+    0,
+    dump_k5beta7_princ_withpolicy,
+    dump_r1_11_policy,
+    process_r1_11_record,
+};
 dump_version ipropx_1_version = {
     "Kerberos iprop extensible version",
     "ipropx",
     0,
     0,
     dump_k5beta7_princ_withpolicy,
-    dump_r1_8_policy,
-    process_r1_8_record,
+    dump_r1_11_policy,
+    process_r1_11_record,
 };
 
 /* External data */
@@ -222,6 +238,7 @@ static const char null_mprinc_name[] = "kdb5_dump at MISSING";
 #define trash_end_fmt     _("%s(%d): ignoring trash at end of line: ")
 #define read_nomem        _("entry (out of memory)")
 #define read_header       _("dump entry header")
+#define read_negint       _("dump entry (unexpected negative numeric field)")
 #define read_name_string  _("name string")
 #define read_key_type     _("key type")
 #define read_key_data     _("key data")
@@ -269,6 +286,7 @@ static const char updateoption[] = "-update";
 static const char hashoption[] = "-hash";
 static const char ovoption[] = "-ov";
 static const char r13option[] = "-r13";
+static const char r18option[] = "-r18";
 static const char dump_tmptrail[] = "~";
 
 /*
@@ -700,6 +718,33 @@ dump_k5beta6_iterator(ptr, entry)
     return dump_k5beta6_iterator_ext(ptr, entry, 0);
 }
 
+/*
+ * Dumps TL data; common to principals and policies.
+ *
+ * If filter_kadm then the KRB5_TL_KADM_DATA (where a principal's policy
+ * name is stored) is filtered out.  This is for dump formats that don't
+ * support policies.
+ */
+static void
+dump_tl_data(FILE *ofile, krb5_tl_data *tlp, krb5_boolean filter_kadm)
+{
+    int i;
+
+    for (; tlp; tlp = tlp->tl_data_next) {
+        if (tlp->tl_data_type == KRB5_TL_KADM_DATA && filter_kadm)
+            continue;
+        fprintf(ofile, "\t%d\t%d\t",
+                (int) tlp->tl_data_type,
+                (int) tlp->tl_data_length);
+        if (tlp->tl_data_length) {
+            for (i = 0; i < tlp->tl_data_length; i++)
+                fprintf(ofile, "%02x", tlp->tl_data_contents[i]);
+        } else {
+            fprintf(ofile, "%d", -1);
+        }
+    }
+}
+
 static krb5_error_code
 dump_k5beta6_iterator_ext(ptr, entry, kadm)
     krb5_pointer        ptr;
@@ -795,7 +840,7 @@ dump_k5beta6_iterator_ext(ptr, entry, kadm)
                     (int) entry->n_key_data,
                     (int) entry->e_length,
                     name);
-            fprintf(arg->ofile, "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t",
+            fprintf(arg->ofile, "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d",
                     entry->attributes,
                     entry->max_life,
                     entry->max_renewable_life,
@@ -804,21 +849,10 @@ dump_k5beta6_iterator_ext(ptr, entry, kadm)
                     (arg->flags & FLAG_OMIT_NRA) ? 0 : entry->last_success,
                     (arg->flags & FLAG_OMIT_NRA) ? 0 : entry->last_failed,
                     (arg->flags & FLAG_OMIT_NRA) ? 0 : entry->fail_auth_count);
-            /* Pound out tagged data. */
-            for (tlp = entry->tl_data; tlp; tlp = tlp->tl_data_next) {
-                if (tlp->tl_data_type == KRB5_TL_KADM_DATA && !kadm)
-                    continue; /* see above, [krb5-admin/89] */
 
-                fprintf(arg->ofile, "%d\t%d\t",
-                        (int) tlp->tl_data_type,
-                        (int) tlp->tl_data_length);
-                if (tlp->tl_data_length)
-                    for (i=0; i<tlp->tl_data_length; i++)
-                        fprintf(arg->ofile, "%02x", tlp->tl_data_contents[i]);
-                else
-                    fprintf(arg->ofile, "%d", -1);
-                fprintf(arg->ofile, "\t");
-            }
+            /* Pound out tagged data. */
+            dump_tl_data(arg->ofile, entry->tl_data, !kadm);
+            fprintf(arg->ofile, "\t");
 
             /* Pound out key data */
             for (counter=0; counter<entry->n_key_data; counter++) {
@@ -950,6 +984,28 @@ void dump_r1_8_policy(void *data, osa_policy_ent_t entry)
             entry->pw_failcnt_interval, entry->pw_lockout_duration);
 }
 
+void
+dump_r1_11_policy(void *data, osa_policy_ent_t entry)
+{
+    struct dump_args *arg;
+
+    arg = (struct dump_args *) data;
+    fprintf(arg->ofile,
+            "policy\t%s\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t"
+            "%d\t%d\t%d\t%s\t%d",
+            entry->name,
+            entry->pw_min_life, entry->pw_max_life, entry->pw_min_length,
+            entry->pw_min_classes, entry->pw_history_num,
+            entry->policy_refcnt, entry->pw_max_fail,
+            entry->pw_failcnt_interval, entry->pw_lockout_duration,
+            entry->attributes, entry->max_life, entry->max_renewable_life,
+            entry->allowed_keysalts ? entry->allowed_keysalts : "-",
+            entry->n_tl_data);
+
+    dump_tl_data(arg->ofile, entry->tl_data, FALSE);
+    fprintf(arg->ofile, "\n");
+}
+
 static void print_key_data(FILE *f, krb5_key_data *key_data)
 {
     int c;
@@ -1060,9 +1116,9 @@ static krb5_error_code dump_ov_princ(krb5_pointer ptr, krb5_db_entry *kdb)
 
 /*
  * usage is:
- *      dump_db [-old] [-b6] [-b7] [-ov] [-r13] [-verbose] [-mkey_convert]
- *              [-new_mkey_file mkey_file] [-rev] [-recurse]
- *              [filename [principals...]]
+ *      dump_db [-old] [-b6] [-b7] [-ov] [-r13] [-r18] [-verbose]
+ *              [-mkey_convert] [-new_mkey_file mkey_file] [-rev]
+ *              [-recurse] [filename [principals...]]
  */
 void
 dump_db(argc, argv)
@@ -1085,7 +1141,7 @@ dump_db(argc, argv)
      * Parse the arguments.
      */
     ofile = (char *) NULL;
-    dump = &r1_8_version;
+    dump = &r1_11_version;
     arglist.flags = 0;
     new_mkey_file = 0;
     mkey_convert = 0;
@@ -1107,6 +1163,8 @@ dump_db(argc, argv)
             dump = &ov_version;
         else if (!strcmp(argv[aindex], r13option))
             dump = &r1_3_version;
+        else if (!strcmp(argv[aindex], r18option))
+            dump = &r1_8_version;
         else if (!strncmp(argv[aindex], ipropoption, sizeof(ipropoption) - 1)) {
             if (log_ctx && log_ctx->iproprole) {
                 /* Note: ipropx_version is the maximum version acceptable */
@@ -1792,6 +1850,65 @@ process_k5beta_record(fname, kcontext, filep, flags, linenop)
     return(retval);
 }
 
+/* Allocate and form a TL data list of a desired size. */
+static int
+alloc_tl_data(krb5_int16 n_tl_data, krb5_tl_data **tldp)
+{
+    krb5_tl_data **tlp = tldp;
+    int i;
+
+    for (i = 0; i < n_tl_data; i++) {
+        *tlp = calloc(1, sizeof(krb5_tl_data));
+        if (*tlp == NULL)
+            return ENOMEM; /* caller cleans up */
+        tlp = &((*tlp)->tl_data_next);
+    }
+
+    return 0;
+}
+
+/* Read TL data; common to principals and policies */
+static int
+process_tl_data(const char *fname, FILE *filep, krb5_tl_data *tl_data,
+                const char **errstr)
+{
+    krb5_tl_data         *tl;
+    int                   nread;
+    krb5_int32            t1, t2;
+
+    for (tl = tl_data; tl; tl = tl->tl_data_next) {
+        nread = fscanf(filep, "%d\t%d\t", &t1, &t2);
+        if (nread != 2) {
+            *errstr = read_ttypelen;
+            return EINVAL;
+        }
+        if (t2 < 0) {
+            *errstr = read_negint;
+            return EINVAL;
+        }
+        tl->tl_data_type = (krb5_int16) t1;
+        tl->tl_data_length = (krb5_int16) t2;
+        if (tl->tl_data_length) {
+            tl->tl_data_contents = malloc(t2 + 1);
+            if (tl->tl_data_contents == NULL)
+                return ENOMEM;
+            if (read_octet_string(filep, tl->tl_data_contents,
+                                  tl->tl_data_length)) {
+                *errstr = read_tcontents;
+                return EINVAL;
+            }
+        } else {
+            nread = fscanf(filep, "%d", &t1);
+            if (nread != 1 || t1 != -1) {
+                *errstr = read_tcontents;
+                return EINVAL;
+            }
+        }
+    }
+
+    return 0;
+}
+
 /*
  * process_k5beta6_record()     - Handle a dump record in krb5b6 format.
  *
@@ -1805,11 +1922,10 @@ process_k5beta6_record(char *fname, krb5_context kcontext, FILE *filep,
     krb5_db_entry       *dbentry;
     krb5_int32          t1, t2, t3, t4, t5, t6, t7, t8, t9;
     int                 nread;
-    int                 error = 1;
     int                 i, j;
     char                *name;
     krb5_key_data       *kp, *kdatap;
-    krb5_tl_data        **tlp, *tl;
+    krb5_tl_data        *tl;
     krb5_octet          *op;
     krb5_error_code     kret;
     const char          *try2read = read_header;
@@ -1824,7 +1940,6 @@ process_k5beta6_record(char *fname, krb5_context kcontext, FILE *filep,
     op = NULL;
     nread = fscanf(filep, "%d\t%d\t%d\t%d\t%d\t", &t1, &t2, &t3, &t4, &t5);
     if (nread == EOF) {
-        error = 0;
         retval = -1;
         goto cleanup;
     }
@@ -1836,14 +1951,9 @@ process_k5beta6_record(char *fname, krb5_context kcontext, FILE *filep,
         goto cleanup;
 
     /* Get memory for and form tagged data linked list */
-    tlp = &dbentry->tl_data;
-    for (i = 0; i < t3; i++) {
-        if (!(*tlp = malloc(sizeof(krb5_tl_data))))
-            goto cleanup;
-        memset(*tlp, 0, sizeof(krb5_tl_data));
-        tlp = &((*tlp)->tl_data_next);
-        dbentry->n_tl_data++;
-    }
+    if (alloc_tl_data(t3, &dbentry->tl_data))
+        goto cleanup;
+    dbentry->n_tl_data = t3;
 
     /* Get memory for key list */
     if (t4 && (kp = malloc(t4*sizeof(krb5_key_data))) == NULL)
@@ -1911,31 +2021,11 @@ process_k5beta6_record(char *fname, krb5_context kcontext, FILE *filep,
      * that's what I did.  [krb5-admin/89]
      */
     if (dbentry->n_tl_data) {
+        if (process_tl_data(fname, filep, dbentry->tl_data, &try2read))
+            goto cleanup;
         for (tl = dbentry->tl_data; tl; tl = tl->tl_data_next) {
-            nread = fscanf(filep, "%d\t%d\t", &t1, &t2);
-            if (nread != 2) {
-                try2read = read_ttypelen;
-                goto cleanup;
-            }
-            tl->tl_data_type = (krb5_int16) t1;
-            tl->tl_data_length = (krb5_int16) t2;
-            if (!tl->tl_data_length) {
-                /* Should be a null field */
-                nread = fscanf(filep, "%d", &t9);
-                if ((nread != 1) || (t9 != -1)) {
-                    try2read = read_tcontents;
-                    goto cleanup;
-                }
-                continue;
-            }
-            if (!(tl->tl_data_contents = malloc(t2 + 1)) ||
-                read_octet_string(filep, tl->tl_data_contents, t2)) {
-                try2read = read_nomem;
-                goto cleanup;
-            }
-
             /* test to set mask fields */
-            if (t1 == KRB5_TL_KADM_DATA) {
+            if (tl->tl_data_type == KRB5_TL_KADM_DATA) {
                 XDR xdrs;
                 osa_princ_ent_rec osa_princ_ent;
 
@@ -2032,10 +2122,9 @@ process_k5beta6_record(char *fname, krb5_context kcontext, FILE *filep,
     if (flags & FLAG_VERBOSE)
         fprintf(stderr, add_princ_fmt, name);
     retval = 0;
-    error = 0;
 
 cleanup:
-    if (error)
+    if (retval > 0)
         fprintf(stderr, read_err_fmt, fname, *linenop, try2read);
 
     free(op);
@@ -2110,7 +2199,7 @@ process_r1_8_policy(fname, kcontext, filep, flags, linenop)
      * To make this compatible with future policy extensions, we
      * ignore any additional values.
      */
-    nread = fscanf(filep, "%1024s\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d",
+    nread = fscanf(filep, "%1024s\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d%*[^\n]",
                    rec.name,
                    &rec.pw_min_life, &rec.pw_max_life,
                    &rec.pw_min_length, &rec.pw_min_classes,
@@ -2139,6 +2228,83 @@ process_r1_8_policy(fname, kcontext, filep, flags, linenop)
     return 0;
 }
 
+static int
+process_r1_11_policy(char *fname, krb5_context kcontext, FILE *filep,
+                     int flags, int *linenop)
+{
+    osa_policy_ent_rec    rec;
+    krb5_tl_data         *tl, *tl_next;
+    char                  namebuf[1024];
+    char                  keysaltbuf[KRB5_KDB_MAX_ALLOWED_KS_LEN + 1];
+    int                   nread;
+    int                   ret = 0;
+    const char           *try2read = NULL;
+
+    memset(&rec, 0, sizeof(rec));
+
+    (*linenop)++;
+    rec.name = namebuf;
+    rec.allowed_keysalts = keysaltbuf;
+
+    nread = fscanf(filep,
+                   "%1023s\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t"
+                   "%d\t%d\t%d\t"
+                   K5CONST_WIDTH_SCANF_STR(KRB5_KDB_MAX_ALLOWED_KS_LEN)
+                   "\t%hd",
+                   rec.name,
+                   &rec.pw_min_life, &rec.pw_max_life,
+                   &rec.pw_min_length, &rec.pw_min_classes,
+                   &rec.pw_history_num, &rec.policy_refcnt,
+                   &rec.pw_max_fail, &rec.pw_failcnt_interval,
+                   &rec.pw_lockout_duration,
+                   &rec.attributes, &rec.max_life, &rec.max_renewable_life,
+                   rec.allowed_keysalts, &rec.n_tl_data);
+    if (nread == EOF)
+        return -1;
+    else if (nread != 15) {
+        fprintf(stderr, "cannot parse policy on line %d (%d read)\n",
+                *linenop, nread);
+        return 1;
+    }
+
+    if (rec.allowed_keysalts && !strcmp(rec.allowed_keysalts, "-"))
+        rec.allowed_keysalts = NULL;
+
+    /* Get TL data */
+    ret = alloc_tl_data(rec.n_tl_data, &rec.tl_data);
+    if (ret)
+        goto cleanup;
+
+    ret = process_tl_data(fname, filep, rec.tl_data, &try2read);
+    if (ret)
+        goto cleanup;
+
+    if ((ret = krb5_db_create_policy(kcontext, &rec)) &&
+        (ret = krb5_db_put_policy(kcontext, &rec))) {
+        fprintf(stderr, "cannot create policy on line %d: %s\n",
+                *linenop, error_message(ret));
+        try2read = NULL;
+        goto cleanup;
+    }
+    if (flags & FLAG_VERBOSE)
+        fprintf(stderr, "created policy %s\n", rec.name);
+
+cleanup:
+    for (tl = rec.tl_data; tl; tl = tl_next) {
+        tl_next = tl->tl_data_next;
+        free(tl->tl_data_contents);
+        free(tl);
+    }
+    if (ret == ENOMEM)
+        try2read = no_mem_fmt;
+    if (ret) {
+        if (try2read)
+            fprintf(stderr, read_err_fmt, fname, *linenop, try2read);
+        return 1;
+    }
+    return 0;
+}
+
 /*
  * process_k5beta7_record()     - Handle a dump record in krb5b7 format.
  *
@@ -2250,6 +2416,36 @@ process_r1_8_record(fname, kcontext, filep, flags, linenop)
 }
 
 /*
+ * process_r1_11_record()        - Handle a dump record in krb5 1.11 format.
+ *
+ * Returns -1 for end of file, 0 for success and 1 for failure.
+ */
+static int
+process_r1_11_record(char *fname, krb5_context kcontext, FILE *filep,
+                     int flags, int *linenop)
+{
+    int nread;
+    char rectype[100];
+
+    nread = fscanf(filep, "%100s\t", rectype);
+    if (nread == EOF)
+        return -1;
+    else if (nread != 1)
+        return 1;
+    if (!strcmp(rectype, "princ"))
+        process_k5beta6_record(fname, kcontext, filep, flags, linenop);
+    else if (!strcmp(rectype, "policy"))
+        process_r1_11_policy(fname, kcontext, filep, flags, linenop);
+    else {
+        fprintf(stderr, _("unknown record type \"%s\" on line %d\n"),
+                rectype, *linenop);
+        return 1;
+    }
+
+    return 0;
+}
+
+/*
  * restore_dump()       - Restore the database from any version dump file.
  */
 static int
@@ -2333,6 +2529,8 @@ load_db(argc, argv)
             load = &ov_version;
         else if (!strcmp(argv[aindex], r13option))
             load = &r1_3_version;
+        else if (!strcmp(argv[aindex], r18option))
+            load = &r1_8_version;
         else if (!strcmp(argv[aindex], ipropoption)) {
             if (log_ctx && log_ctx->iproprole) {
                 load = &iprop_version;
@@ -2426,6 +2624,8 @@ load_db(argc, argv)
             load = &r1_3_version;
         else if (strcmp(buf, r1_8_version.header) == 0)
             load = &r1_8_version;
+        else if (strcmp(buf, r1_11_version.header) == 0)
+            load = &r1_11_version;
         else if (strncmp(buf, ov_version.header,
                          strlen(ov_version.header)) == 0)
             load = &ov_version;
diff --git a/src/kadmin/dbutil/kadm5_create.c b/src/kadmin/dbutil/kadm5_create.c
index 9d5ee1d..567f5f5 100644
--- a/src/kadmin/dbutil/kadm5_create.c
+++ b/src/kadmin/dbutil/kadm5_create.c
@@ -108,7 +108,7 @@ int kadm5_create_magic_princs(kadm5_config_params *params,
         return retval;
     if ((retval = kadm5_init(context, progname, NULL, NULL, params,
                              KADM5_STRUCT_VERSION,
-                             KADM5_API_VERSION_3,
+                             KADM5_API_VERSION_4,
                              db5util_db_args,
                              &handle))) {
         com_err(progname, retval, _("while initializing the Kerberos admin "
diff --git a/src/kadmin/dbutil/kdb5_util.c b/src/kadmin/dbutil/kdb5_util.c
index ca1cdd2..f12c685 100644
--- a/src/kadmin/dbutil/kdb5_util.c
+++ b/src/kadmin/dbutil/kdb5_util.c
@@ -85,10 +85,10 @@ void usage()
               "\tcreate  [-s]\n"
               "\tdestroy [-f]\n"
               "\tstash   [-f keyfile]\n"
-              "\tdump    [-old|-ov|-b6|-b7|-r13] [-verbose]\n"
+              "\tdump    [-old|-ov|-b6|-b7|-r13|-r18] [-verbose]\n"
               "\t        [-mkey_convert] [-new_mkey_file mkey_file]\n"
               "\t        [-rev] [-recurse] [filename [princs...]]\n"
-              "\tload    [-old|-ov|-b6|-b7|-r13] [-verbose] [-update] "
+              "\tload    [-old|-ov|-b6|-b7|-r13|-r18] [-verbose] [-update] "
               "filename\n"
               "\tark     [-e etype_list] principal\n"
               "\tadd_mkey [-e etype] [-s]\n"
diff --git a/src/kadmin/server/ovsec_kadmd.c b/src/kadmin/server/ovsec_kadmd.c
index dbb90cb..b77e765 100644
--- a/src/kadmin/server/ovsec_kadmd.c
+++ b/src/kadmin/server/ovsec_kadmd.c
@@ -318,7 +318,7 @@ int main(int argc, char *argv[])
     if((ret = kadm5_init(context, "kadmind", NULL,
                          NULL, &params,
                          KADM5_STRUCT_VERSION,
-                         KADM5_API_VERSION_3,
+                         KADM5_API_VERSION_4,
                          db_args,
                          &global_server_handle)) != KADM5_OK) {
         const char *e_txt = krb5_get_error_message (context, ret);
diff --git a/src/kadmin/testing/util/tcl_kadm5.c b/src/kadmin/testing/util/tcl_kadm5.c
index 0d9c7f1..8338cc9 100644
--- a/src/kadmin/testing/util/tcl_kadm5.c
+++ b/src/kadmin/testing/util/tcl_kadm5.c
@@ -2528,6 +2528,8 @@ void Tcl_kadm5_init(Tcl_Interp *interp)
     Tcl_SetVar(interp, "KADM5_API_VERSION_2", buf, TCL_GLOBAL_ONLY);
     (void) sprintf(buf, "%d", KADM5_API_VERSION_3);
     Tcl_SetVar(interp, "KADM5_API_VERSION_3", buf, TCL_GLOBAL_ONLY);
+    (void) sprintf(buf, "%d", KADM5_API_VERSION_4);
+    Tcl_SetVar(interp, "KADM5_API_VERSION_4", buf, TCL_GLOBAL_ONLY);
     (void) sprintf(buf, "%d", KADM5_API_VERSION_MASK);
     Tcl_SetVar(interp, "KADM5_API_VERSION_MASK", buf, TCL_GLOBAL_ONLY);
     (void) sprintf(buf, "%d", KADM5_STRUCT_VERSION_MASK);
diff --git a/src/lib/kadm5/admin.h b/src/lib/kadm5/admin.h
index 020962b..037e2f9 100644
--- a/src/lib/kadm5/admin.h
+++ b/src/lib/kadm5/admin.h
@@ -116,15 +116,20 @@ typedef long            kadm5_ret_t;
 
 
 /* kadm5_policy_ent_t */
-#define KADM5_PW_MAX_LIFE       0x004000
-#define KADM5_PW_MIN_LIFE       0x008000
-#define KADM5_PW_MIN_LENGTH     0x010000
-#define KADM5_PW_MIN_CLASSES    0x020000
-#define KADM5_PW_HISTORY_NUM    0x040000
-#define KADM5_REF_COUNT         0x080000
-#define KADM5_PW_MAX_FAILURE            0x100000
-#define KADM5_PW_FAILURE_COUNT_INTERVAL 0x200000
-#define KADM5_PW_LOCKOUT_DURATION       0x400000
+#define KADM5_PW_MAX_LIFE               0x00004000
+#define KADM5_PW_MIN_LIFE               0x00008000
+#define KADM5_PW_MIN_LENGTH             0x00010000
+#define KADM5_PW_MIN_CLASSES            0x00020000
+#define KADM5_PW_HISTORY_NUM            0x00040000
+#define KADM5_REF_COUNT                 0x00080000
+#define KADM5_PW_MAX_FAILURE            0x00100000
+#define KADM5_PW_FAILURE_COUNT_INTERVAL 0x00200000
+#define KADM5_PW_LOCKOUT_DURATION       0x00400000
+#define KADM5_POLICY_ATTRIBUTES         0x00800000
+#define KADM5_POLICY_MAX_LIFE           0x01000000
+#define KADM5_POLICY_MAX_RLIFE          0x02000000
+#define KADM5_POLICY_ALLOWED_KEYSALTS   0x04000000
+#define KADM5_POLICY_TL_DATA            0x08000000
 
 /* kadm5_config_params */
 #define KADM5_CONFIG_REALM              0x00000001
@@ -179,6 +184,7 @@ typedef long            kadm5_ret_t;
 #define KADM5_API_VERSION_MASK  0x12345700
 #define KADM5_API_VERSION_2     (KADM5_API_VERSION_MASK|0x02)
 #define KADM5_API_VERSION_3     (KADM5_API_VERSION_MASK|0x03)
+#define KADM5_API_VERSION_4     (KADM5_API_VERSION_MASK|0x04)
 
 typedef struct _kadm5_principal_ent_t {
     krb5_principal  principal;
@@ -218,6 +224,14 @@ typedef struct _kadm5_policy_ent_t {
     krb5_kvno       pw_max_fail;
     krb5_deltat     pw_failcnt_interval;
     krb5_deltat     pw_lockout_duration;
+
+    /* version 4 fields */
+    krb5_flags      attributes;
+    krb5_deltat     max_life;
+    krb5_deltat     max_renewable_life;
+    char            *allowed_keysalts;
+    krb5_int16      n_tl_data;
+    krb5_tl_data    *tl_data;
 } kadm5_policy_ent_rec, *kadm5_policy_ent_t;
 
 /*
diff --git a/src/lib/kadm5/admin_internal.h b/src/lib/kadm5/admin_internal.h
index dc21a65..6d79243 100644
--- a/src/lib/kadm5/admin_internal.h
+++ b/src/lib/kadm5/admin_internal.h
@@ -32,7 +32,7 @@
             return KADM5_BAD_API_VERSION;                               \
         if (srvr->api_version < KADM5_API_VERSION_2)                    \
             return old_api_version;                                     \
-        if (srvr->api_version > KADM5_API_VERSION_3)                    \
+        if (srvr->api_version > KADM5_API_VERSION_4)                    \
             return new_api_version;                                     \
     }
 
diff --git a/src/lib/kadm5/clnt/client_init.c b/src/lib/kadm5/clnt/client_init.c
index a8abebf..adc050c 100644
--- a/src/lib/kadm5/clnt/client_init.c
+++ b/src/lib/kadm5/clnt/client_init.c
@@ -197,7 +197,7 @@ init_any(krb5_context context, char *client_name, enum init_type init_type,
     handle->destroy_cache = 0;
     handle->context = 0;
     *handle->lhandle = *handle;
-    handle->lhandle->api_version = KADM5_API_VERSION_3;
+    handle->lhandle->api_version = KADM5_API_VERSION_4;
     handle->lhandle->struct_version = KADM5_STRUCT_VERSION;
     handle->lhandle->lhandle = handle->lhandle;
 
@@ -337,6 +337,16 @@ init_any(krb5_context context, char *client_name, enum init_type init_type,
 #endif
         goto error;
     }
+    /* Drop down to v3 wire protocol if server does not support v4 */
+    if (r->code == KADM5_NEW_SERVER_API_VERSION &&
+        handle->api_version == KADM5_API_VERSION_4) {
+        handle->api_version = KADM5_API_VERSION_3;
+        r = init_2(&handle->api_version, handle->clnt);
+        if (r == NULL) {
+            code = KADM5_RPC_ERROR;
+            goto error;
+        }
+    }
     /* Drop down to v2 wire protocol if server does not support v3 */
     if (r->code == KADM5_NEW_SERVER_API_VERSION &&
         handle->api_version == KADM5_API_VERSION_3) {
diff --git a/src/lib/kadm5/clnt/clnt_policy.c b/src/lib/kadm5/clnt/clnt_policy.c
index eda354a..3b3823f 100644
--- a/src/lib/kadm5/clnt/clnt_policy.c
+++ b/src/lib/kadm5/clnt/clnt_policy.c
@@ -89,6 +89,8 @@ kadm5_get_policy(void *server_handle, char *name, kadm5_policy_ent_t ent)
     gpol_ret        *r;
     kadm5_server_handle_t handle = server_handle;
 
+    memset(ent, 0, sizeof(*ent));
+
     CHECK_HANDLE(server_handle);
 
     arg.name = name;
diff --git a/src/lib/kadm5/kadm_err.et b/src/lib/kadm5/kadm_err.et
index 5530cca..c717670 100644
--- a/src/lib/kadm5/kadm_err.et
+++ b/src/lib/kadm5/kadm_err.et
@@ -62,4 +62,5 @@ error_code KADM5_MISSING_KRB5_CONF_PARAMS, "Missing parameters in krb5.conf requ
 error_code KADM5_XDR_FAILURE,		"XDR encoding error"
 error_code KADM5_CANT_RESOLVE, "Cannot resolve network address for admin server in requested realm"
 error_code KADM5_PASS_Q_GENERIC, "Unspecified password quality failure"
+error_code KADM5_BAD_KEYSALTS, "Invalid key/salt tuples"
 end
diff --git a/src/lib/kadm5/kadm_rpc_xdr.c b/src/lib/kadm5/kadm_rpc_xdr.c
index 0b0253a..153b962 100644
--- a/src/lib/kadm5/kadm_rpc_xdr.c
+++ b/src/lib/kadm5/kadm_rpc_xdr.c
@@ -492,17 +492,45 @@ _xdr_kadm5_policy_ent_rec(XDR *xdrs, kadm5_policy_ent_rec *objp, int vers)
 	if (!xdr_long(xdrs, &objp->policy_refcnt)) {
 		return (FALSE);
 	}
-	if (vers == KADM5_API_VERSION_3) {
+	if (xdrs->x_op == XDR_DECODE) {
+		objp->pw_max_fail = 0;
+		objp->pw_failcnt_interval = 0;
+		objp->pw_lockout_duration = 0;
+		objp->attributes = 0;
+		objp->max_life = 0;
+		objp->max_renewable_life = 0;
+		objp->allowed_keysalts = NULL;
+		objp->n_tl_data = 0;
+		objp->tl_data = NULL;
+	}
+	if (vers >= KADM5_API_VERSION_3) {
 		if (!xdr_krb5_kvno(xdrs, &objp->pw_max_fail))
 			return (FALSE);
 		if (!xdr_krb5_deltat(xdrs, &objp->pw_failcnt_interval))
 			return (FALSE);
 		if (!xdr_krb5_deltat(xdrs, &objp->pw_lockout_duration))
 			return (FALSE);
-	} else if (xdrs->x_op == XDR_DECODE) {
-		objp->pw_max_fail = 0;
-		objp->pw_failcnt_interval = 0;
-		objp->pw_lockout_duration = 0;
+	}
+	if (vers >= KADM5_API_VERSION_4) {
+		if (!xdr_krb5_flags(xdrs, &objp->attributes)) {
+			return (FALSE);
+		}
+		if (!xdr_krb5_deltat(xdrs, &objp->max_life)) {
+			return (FALSE);
+		}
+		if (!xdr_krb5_deltat(xdrs, &objp->max_renewable_life)) {
+			return (FALSE);
+		}
+		if (!xdr_nullstring(xdrs, &objp->allowed_keysalts)) {
+			return (FALSE);
+		}
+		if (!xdr_krb5_int16(xdrs, &objp->n_tl_data)) {
+			return (FALSE);
+		}
+		if (!xdr_nulltype(xdrs, (void **) &objp->tl_data,
+				  xdr_krb5_tl_data)) {
+			return FALSE;
+		}
 	}
 	return (TRUE);
 }
@@ -510,7 +538,7 @@ _xdr_kadm5_policy_ent_rec(XDR *xdrs, kadm5_policy_ent_rec *objp, int vers)
 bool_t
 xdr_kadm5_policy_ent_rec(XDR *xdrs, kadm5_policy_ent_rec *objp)
 {
-	return _xdr_kadm5_policy_ent_rec(xdrs, objp, KADM5_API_VERSION_3);
+	return _xdr_kadm5_policy_ent_rec(xdrs, objp, KADM5_API_VERSION_4);
 }
 
 bool_t
diff --git a/src/lib/kadm5/misc_free.c b/src/lib/kadm5/misc_free.c
index 1d70791..497b8c2 100644
--- a/src/lib/kadm5/misc_free.c
+++ b/src/lib/kadm5/misc_free.c
@@ -11,10 +11,21 @@
 kadm5_ret_t
 kadm5_free_policy_ent(void *server_handle, kadm5_policy_ent_t val)
 {
+    krb5_tl_data *tl_next;
+
     _KADM5_CHECK_HANDLE(server_handle);
 
-    if (val)
-        free(val->policy);
+    if (val == NULL)
+        return KADM5_OK;
+
+    free(val->policy);
+    free(val->allowed_keysalts);
+    for (; val->tl_data; val->tl_data = tl_next) {
+        tl_next = val->tl_data->tl_data_next;
+        free(val->tl_data->tl_data_contents);
+        free(val->tl_data);
+    }
+    memset(val, 0, sizeof(*val));
     return KADM5_OK;
 }
 
diff --git a/src/lib/kadm5/srv/server_init.c b/src/lib/kadm5/srv/server_init.c
index b170e9e..3c3a879 100644
--- a/src/lib/kadm5/srv/server_init.c
+++ b/src/lib/kadm5/srv/server_init.c
@@ -283,7 +283,7 @@ kadm5_ret_t kadm5_init(krb5_context context, char *client_name, char *pass,
         return ENOMEM;
     }
     *handle->lhandle = *handle;
-    handle->lhandle->api_version = KADM5_API_VERSION_3;
+    handle->lhandle->api_version = KADM5_API_VERSION_4;
     handle->lhandle->struct_version = KADM5_STRUCT_VERSION;
     handle->lhandle->lhandle = handle->lhandle;
 
diff --git a/src/lib/kadm5/srv/svr_policy.c b/src/lib/kadm5/srv/svr_policy.c
index 4e0065c..3a8f82e 100644
--- a/src/lib/kadm5/srv/svr_policy.c
+++ b/src/lib/kadm5/srv/svr_policy.c
@@ -52,6 +52,24 @@ kadm5_create_policy(void *server_handle,
         return kadm5_create_policy_internal(server_handle, entry, mask);
 }
 
+/* Validate allowed_keysalts. */
+static kadm5_ret_t
+validate_allowed_keysalts(char *allowed_keysalts)
+{
+    kadm5_ret_t ret;
+    krb5_key_salt_tuple *ks_tuple = NULL;
+    krb5_int32 n_ks_tuple = 0;
+
+    if (strchr(allowed_keysalts, '\t') != NULL)
+        return KADM5_BAD_KEYSALTS;
+    ret = krb5_string_to_keysalts(allowed_keysalts, ",", ":.-", 0,
+                                  &ks_tuple, &n_ks_tuple);
+    free(ks_tuple);
+    if (ret == EINVAL)
+        return KADM5_BAD_KEYSALTS;
+    return ret;
+}
+
 /*
  * Function: kadm5_create_policy_internal
  *
@@ -89,7 +107,14 @@ kadm5_create_policy_internal(void *server_handle,
         return KADM5_BAD_POLICY;
     if (!(mask & KADM5_POLICY))
         return KADM5_BAD_MASK;
+    if ((mask & KADM5_POLICY_ALLOWED_KEYSALTS) &&
+        entry->allowed_keysalts != NULL) {
+        ret = validate_allowed_keysalts(entry->allowed_keysalts);
+        if (ret)
+            return ret;
+    }
 
+    memset(&pent, 0, sizeof(pent));
     pent.name = entry->policy;
     p = entry->policy;
     while(*p != '\0') {
@@ -138,7 +163,32 @@ kadm5_create_policy_internal(void *server_handle,
     else
         pent.policy_refcnt = entry->policy_refcnt;
 
-    if (handle->api_version == KADM5_API_VERSION_3) {
+    if (handle->api_version >= KADM5_API_VERSION_4) {
+        if (!(mask & KADM5_POLICY_ATTRIBUTES))
+            pent.attributes = 0;
+        else
+            pent.attributes = entry->attributes;
+        if (!(mask & KADM5_POLICY_MAX_LIFE))
+            pent.max_life = 0;
+        else
+            pent.max_life = entry->max_life;
+        if (!(mask & KADM5_POLICY_MAX_RLIFE))
+            pent.max_renewable_life = 0;
+        else
+            pent.max_renewable_life = entry->max_renewable_life;
+        if (!(mask & KADM5_POLICY_ALLOWED_KEYSALTS))
+            pent.allowed_keysalts = 0;
+        else
+            pent.allowed_keysalts = entry->allowed_keysalts;
+        if (!(mask & KADM5_POLICY_TL_DATA)) {
+            pent.n_tl_data = 0;
+            pent.tl_data = NULL;
+        } else {
+            pent.n_tl_data = entry->n_tl_data;
+            pent.tl_data = entry->tl_data;
+        }
+    }
+    if (handle->api_version >= KADM5_API_VERSION_3) {
         if (!(mask & KADM5_PW_MAX_FAILURE))
             pent.pw_max_fail = 0;
         else
@@ -151,10 +201,6 @@ kadm5_create_policy_internal(void *server_handle,
             pent.pw_lockout_duration = 0;
         else
             pent.pw_lockout_duration = entry->pw_lockout_duration;
-    } else {
-        pent.pw_max_fail = 0;
-        pent.pw_failcnt_interval = 0;
-        pent.pw_lockout_duration = 0;
     }
 
     if ((ret = krb5_db_create_policy(handle->context, &pent)))
@@ -209,13 +255,58 @@ kadm5_modify_policy(void *server_handle,
         return kadm5_modify_policy_internal(server_handle, entry, mask);
 }
 
+/* Allocate and form a TL data list of a desired size. */
+static int
+alloc_tl_data(krb5_int16 n_tl_data, krb5_tl_data **tldp)
+{
+    krb5_tl_data **tlp = tldp;
+    int i;
+
+    for (i = 0; i < n_tl_data; i++) {
+        *tlp = calloc(1, sizeof(krb5_tl_data));
+        if (*tlp == NULL)
+            return ENOMEM; /* caller cleans up */
+        memset(*tlp, 0, sizeof(krb5_tl_data));
+        tlp = &((*tlp)->tl_data_next);
+    }
+
+    return 0;
+}
+
+static kadm5_ret_t
+copy_tl_data(krb5_int16 n_tl_data, krb5_tl_data *tl_data,
+             krb5_tl_data **out)
+{
+    kadm5_ret_t ret;
+    krb5_tl_data *tl, *tl_new;
+
+    if ((ret = alloc_tl_data(n_tl_data, out)))
+        return ret; /* caller cleans up */
+
+    tl = tl_data;
+    tl_new = *out;
+    for (; tl; tl = tl->tl_data_next, tl_new = tl_new->tl_data_next) {
+        tl_new->tl_data_contents = malloc(tl->tl_data_length);
+        if (tl_new->tl_data_contents == NULL)
+            return ENOMEM;
+        memcpy(tl_new->tl_data_contents, tl->tl_data_contents,
+               tl->tl_data_length);
+        tl_new->tl_data_type = tl->tl_data_type;
+        tl_new->tl_data_length = tl->tl_data_length;
+    }
+
+    return 0;
+}
+
 kadm5_ret_t
 kadm5_modify_policy_internal(void *server_handle,
                              kadm5_policy_ent_t entry, long mask)
 {
-    kadm5_server_handle_t handle = server_handle;
-    osa_policy_ent_t    p;
-    int                 ret;
+    kadm5_server_handle_t    handle = server_handle;
+    krb5_tl_data            *tl;
+    osa_policy_ent_t         p;
+    int                      ret;
+    size_t                   len;
 
     CHECK_HANDLE(server_handle);
 
@@ -225,6 +316,20 @@ kadm5_modify_policy_internal(void *server_handle,
         return KADM5_BAD_POLICY;
     if((mask & KADM5_POLICY))
         return KADM5_BAD_MASK;
+    if ((mask & KADM5_POLICY_ALLOWED_KEYSALTS) &&
+        entry->allowed_keysalts != NULL) {
+        ret = validate_allowed_keysalts(entry->allowed_keysalts);
+        if (ret)
+            return ret;
+    }
+    if ((mask & KADM5_POLICY_TL_DATA)) {
+        tl = entry->tl_data;
+        while (tl != NULL) {
+            if (tl->tl_data_type < 256)
+                return KADM5_BAD_TL_TYPE;
+            tl = tl->tl_data_next;
+        }
+    }
 
     ret = krb5_db_get_policy(handle->context, entry->policy, &p);
     if (ret == KRB5_KDB_NOENTRY)
@@ -265,7 +370,7 @@ kadm5_modify_policy_internal(void *server_handle,
     }
     if ((mask & KADM5_REF_COUNT))
         p->policy_refcnt = entry->policy_refcnt;
-    if (handle->api_version == KADM5_API_VERSION_3) {
+    if (handle->api_version >= KADM5_API_VERSION_3) {
         if ((mask & KADM5_PW_MAX_FAILURE))
             p->pw_max_fail = entry->pw_max_fail;
         if ((mask & KADM5_PW_FAILURE_COUNT_INTERVAL))
@@ -273,7 +378,39 @@ kadm5_modify_policy_internal(void *server_handle,
         if ((mask & KADM5_PW_LOCKOUT_DURATION))
             p->pw_lockout_duration = entry->pw_lockout_duration;
     }
+    if (handle->api_version >= KADM5_API_VERSION_4) {
+        if ((mask & KADM5_POLICY_ATTRIBUTES))
+            p->attributes = entry->attributes;
+        if ((mask & KADM5_POLICY_MAX_LIFE))
+            p->max_life = entry->max_life;
+        if ((mask & KADM5_POLICY_MAX_RLIFE))
+            p->max_renewable_life = entry->max_renewable_life;
+        if ((mask & KADM5_POLICY_ALLOWED_KEYSALTS)) {
+            krb5_db_free(handle->context, p->allowed_keysalts);
+            p->allowed_keysalts = NULL;
+            if (entry->allowed_keysalts != NULL) {
+                len = strlen(entry->allowed_keysalts) + 1;
+                p->allowed_keysalts = krb5_db_alloc(handle->context, NULL,
+                                                    len);
+                if (p->allowed_keysalts == NULL) {
+                    ret = ENOMEM;
+                    goto cleanup;
+                }
+                memcpy(p->allowed_keysalts, entry->allowed_keysalts, len);
+            }
+        }
+        if ((mask & KADM5_POLICY_TL_DATA)) {
+            for (tl = entry->tl_data; tl != NULL; tl = tl->tl_data_next) {
+                ret = krb5_db_update_tl_data(handle->context, &p->n_tl_data,
+                                             &p->tl_data, tl);
+                if (ret)
+                    goto cleanup;
+            }
+        }
+    }
     ret = krb5_db_put_policy(handle->context, p);
+
+cleanup:
     krb5_db_free_policy(handle->context, p);
     return ret;
 }
@@ -283,9 +420,11 @@ kadm5_get_policy(void *server_handle, kadm5_policy_t name,
                  kadm5_policy_ent_t entry)
 {
     osa_policy_ent_t            t;
-    int                         ret;
+    kadm5_ret_t                 ret;
     kadm5_server_handle_t handle = server_handle;
 
+    memset(entry, 0, sizeof(*entry));
+
     CHECK_HANDLE(server_handle);
 
     krb5_clear_error_message(handle->context);
@@ -301,8 +440,8 @@ kadm5_get_policy(void *server_handle, kadm5_policy_t name,
         return ret;
 
     if ((entry->policy = strdup(t->name)) == NULL) {
-        krb5_db_free_policy(handle->context, t);
-        return ENOMEM;
+        ret = ENOMEM;
+        goto cleanup;
     }
     entry->pw_min_life = t->pw_min_life;
     entry->pw_max_life = t->pw_max_life;
@@ -310,12 +449,33 @@ kadm5_get_policy(void *server_handle, kadm5_policy_t name,
     entry->pw_min_classes = t->pw_min_classes;
     entry->pw_history_num = t->pw_history_num;
     entry->policy_refcnt = t->policy_refcnt;
-    if (handle->api_version == KADM5_API_VERSION_3) {
+    if (handle->api_version >= KADM5_API_VERSION_3) {
         entry->pw_max_fail = t->pw_max_fail;
         entry->pw_failcnt_interval = t->pw_failcnt_interval;
         entry->pw_lockout_duration = t->pw_lockout_duration;
     }
-    krb5_db_free_policy(handle->context, t);
+    if (handle->api_version >= KADM5_API_VERSION_4) {
+        entry->attributes = t->attributes;
+        entry->max_life = t->max_life;
+        entry->max_renewable_life = t->max_renewable_life;
+        if (t->allowed_keysalts) {
+            entry->allowed_keysalts = strdup(t->allowed_keysalts);
+            if (!entry->allowed_keysalts) {
+                ret = ENOMEM;
+                goto cleanup;
+            }
+        }
+        ret = copy_tl_data(t->n_tl_data, t->tl_data, &entry->tl_data);
+        if (ret)
+            goto cleanup;
+        entry->n_tl_data = t->n_tl_data;
+    }
+
+    ret = 0;
 
-    return KADM5_OK;
+cleanup:
+    if (ret)
+        kadm5_free_policy_ent(handle, entry);
+    krb5_db_free_policy(handle->context, t);
+    return ret;
 }
diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c
index f5ea005..f405f55 100644
--- a/src/lib/kadm5/srv/svr_principal.c
+++ b/src/lib/kadm5/srv/svr_principal.c
@@ -173,6 +173,138 @@ static void cleanup_key_data(context, count, data)
     krb5_db_free(context, data);
 }
 
+/* Check whether a ks_tuple is present in an array of ks_tuples. */
+static krb5_boolean
+ks_tuple_present(int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
+                 krb5_key_salt_tuple *looking_for)
+{
+    int i;
+
+    for (i = 0; i < n_ks_tuple; i++) {
+        if (ks_tuple[i].ks_enctype == looking_for->ks_enctype &&
+            ks_tuple[i].ks_salttype == looking_for->ks_salttype)
+            return TRUE;
+    }
+    return FALSE;
+}
+
+/*
+ * Apply the -allowedkeysalts policy (see kadmin(1)'s addpol/modpol
+ * commands).  We use the allowed key/salt tuple list as a default if
+ * no ks tuples as provided by the caller.  We reject lists that include
+ * key/salts outside the policy.  We re-order the requested ks tuples
+ * (which may be a subset of the policy) to reflect the policy order.
+ */
+static kadm5_ret_t
+apply_keysalt_policy(kadm5_server_handle_t handle, const char *policy,
+                     int n_ks_tuple, krb5_key_salt_tuple *ks_tuple,
+                     int *new_n_kstp, krb5_key_salt_tuple **new_kstp)
+{
+    kadm5_ret_t ret;
+    kadm5_policy_ent_rec polent;
+    int ak_n_ks_tuple = 0;
+    int new_n_ks_tuple = 0;
+    krb5_key_salt_tuple *ak_ks_tuple = NULL;
+    krb5_key_salt_tuple *new_ks_tuple = NULL;
+    krb5_key_salt_tuple *subset;
+    int i, m;
+
+    if (new_n_kstp != NULL) {
+        *new_n_kstp = 0;
+        *new_kstp = NULL;
+    }
+
+    memset(&polent, 0, sizeof(polent));
+    if (policy != NULL &&
+        (ret = kadm5_get_policy(handle->lhandle, (char *)policy,
+                                &polent)) != KADM5_OK) {
+        if (ret == EINVAL)
+            ret = KADM5_BAD_POLICY;
+        if (ret)
+            goto cleanup;
+    }
+
+    if (polent.allowed_keysalts == NULL && new_n_kstp != NULL) {
+        /* Requested keysalts allowed or default to supported_enctypes. */
+        if (n_ks_tuple == 0) {
+            /* Default to supported_enctypes. */
+            n_ks_tuple = handle->params.num_keysalts;
+            ks_tuple = handle->params.keysalts;
+        }
+        /* Dup the requested or defaulted keysalt tuples. */
+        new_ks_tuple = malloc(n_ks_tuple * sizeof(*new_ks_tuple));
+        if (new_ks_tuple == NULL) {
+            ret = ENOMEM;
+            goto cleanup;
+        }
+        memcpy(new_ks_tuple, ks_tuple, n_ks_tuple * sizeof(*new_ks_tuple));
+        new_n_ks_tuple = n_ks_tuple;
+        ret = 0;
+        goto cleanup;
+    }
+
+    ret = krb5_string_to_keysalts(polent.allowed_keysalts,
+                                  ", ",  /* Tuple separators */
+                                  ":.-", /* Key/salt separators */
+                                  0,     /* No duplicates */
+                                  &ak_ks_tuple,
+                                  &ak_n_ks_tuple);
+    /*
+     * Malformed policy?  Shouldn't happen, but it's remotely possible
+     * someday, so we don't assert, just bail.
+     */
+    if (ret)
+        goto cleanup;
+
+    /* Check that the requested ks_tuples are within policy, if we have one. */
+    for (i = 0; i < n_ks_tuple; i++) {
+        if (!ks_tuple_present(ak_n_ks_tuple, ak_ks_tuple, &ks_tuple[i])) {
+            ret = KADM5_BAD_KEYSALTS;
+            goto cleanup;
+        }
+    }
+
+    /* Have policy but no ks_tuple input?  Output the policy. */
+    if (n_ks_tuple == 0) {
+        new_n_ks_tuple = ak_n_ks_tuple;
+        new_ks_tuple = ak_ks_tuple;
+        ak_ks_tuple = NULL;
+        goto cleanup;
+    }
+
+    /*
+     * Now filter the policy ks tuples by the requested ones so as to
+     * preserve in the requested sub-set the relative ordering from the
+     * policy.  We could optimize this (if (n_ks_tuple == ak_n_ks_tuple)
+     * then skip this), but we don't bother.
+     */
+    subset = calloc(n_ks_tuple, sizeof(*subset));
+    if (subset == NULL) {
+        ret = ENOMEM;
+        goto cleanup;
+    }
+    for (m = 0, i = 0; i < ak_n_ks_tuple && m < n_ks_tuple; i++) {
+        if (ks_tuple_present(n_ks_tuple, ks_tuple, &ak_ks_tuple[i]))
+            subset[m++] = ak_ks_tuple[i];
+    }
+    new_ks_tuple = subset;
+    new_n_ks_tuple = m;
+    ret = 0;
+
+cleanup:
+    kadm5_free_policy_ent(handle->lhandle, &polent);
+    free(ak_ks_tuple);
+
+    if (new_n_kstp != NULL) {
+        *new_n_kstp = new_n_ks_tuple;
+        *new_kstp = new_ks_tuple;
+    } else {
+        free(new_ks_tuple);
+    }
+    return ret;
+}
+
+
 /*
  * 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
@@ -224,6 +356,8 @@ kadm5_create_principal_3(void *server_handle,
     kadm5_server_handle_t handle = server_handle;
     krb5_keyblock               *act_mkey;
     krb5_kvno                   act_kvno;
+    int                         new_n_ks_tuple = 0;
+    krb5_key_salt_tuple         *new_ks_tuple = NULL;
 
     CHECK_HANDLE(server_handle);
 
@@ -247,12 +381,6 @@ kadm5_create_principal_3(void *server_handle,
     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
      */
@@ -362,6 +490,16 @@ kadm5_create_principal_3(void *server_handle,
         }
     }
 
+    /*
+     * We need to have setup the TL data, so we have strings, so we can
+     * check enctype policy, which is why we check/initialize ks_tuple
+     * this late.
+     */
+    ret = apply_keysalt_policy(handle, entry->policy, n_ks_tuple, ks_tuple,
+                               &new_n_ks_tuple, &new_ks_tuple);
+    if (ret)
+        goto cleanup;
+
     /* initialize the keys */
 
     ret = krb5_dbe_find_act_mkey(handle->context, active_mkey_list, &act_kvno,
@@ -370,13 +508,14 @@ kadm5_create_principal_3(void *server_handle,
         goto cleanup;
 
     if (password) {
-        ret = krb5_dbe_cpw(handle->context, act_mkey, ks_tuple, n_ks_tuple,
-                           password, (mask & KADM5_KVNO)?entry->kvno:1,
+        ret = krb5_dbe_cpw(handle->context, act_mkey, new_ks_tuple,
+                           new_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);
+                           new_ks_tuple, new_n_ks_tuple, FALSE, kdb);
     }
     if (ret)
         goto cleanup;
@@ -388,7 +527,7 @@ kadm5_create_principal_3(void *server_handle,
 
     ret = k5_kadm5_hook_create(handle->context, handle->hook_handles,
                                KADM5_HOOK_STAGE_PRECOMMIT, entry, mask,
-                               n_ks_tuple, ks_tuple, password);
+                               new_n_ks_tuple, new_ks_tuple, password);
     if (ret)
         goto cleanup;
 
@@ -441,9 +580,10 @@ kadm5_create_principal_3(void *server_handle,
 
     (void) k5_kadm5_hook_create(handle->context, handle->hook_handles,
                                 KADM5_HOOK_STAGE_POSTCOMMIT, entry, mask,
-                                n_ks_tuple, ks_tuple, password);
+                                new_n_ks_tuple, new_ks_tuple, password);
 
 cleanup:
+    free(new_ks_tuple);
     krb5_db_free_principal(handle->context, kdb);
     if (have_polent)
         (void) kadm5_free_policy_ent(handle->lhandle, &polent);
@@ -1345,6 +1485,8 @@ kadm5_chpass_principal_3(void *server_handle,
     osa_pw_hist_ent             hist;
     krb5_keyblock               *act_mkey, *hist_keyblocks = NULL;
     krb5_kvno                   act_kvno, hist_kvno;
+    int                         new_n_ks_tuple = 0;
+    krb5_key_salt_tuple         *new_ks_tuple = NULL;
 
     CHECK_HANDLE(server_handle);
 
@@ -1359,15 +1501,14 @@ kadm5_chpass_principal_3(void *server_handle,
                                 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);
 
+    ret = apply_keysalt_policy(handle, adb.policy, n_ks_tuple, ks_tuple,
+                               &new_n_ks_tuple, &new_ks_tuple);
+    if (ret)
+        goto done;
+
     if ((adb.aux_attributes & KADM5_POLICY)) {
         if ((ret = kadm5_get_policy(handle->lhandle, adb.policy, &pol)))
             goto done;
@@ -1392,7 +1533,7 @@ kadm5_chpass_principal_3(void *server_handle,
     if (ret)
         goto done;
 
-    ret = krb5_dbe_cpw(handle->context, act_mkey, ks_tuple, n_ks_tuple,
+    ret = krb5_dbe_cpw(handle->context, act_mkey, new_ks_tuple, new_n_ks_tuple,
                        password, 0 /* increment kvno */,
                        keepold, kdb);
     if (ret)
@@ -1504,7 +1645,7 @@ kadm5_chpass_principal_3(void *server_handle,
 
     ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
                                KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold,
-                               n_ks_tuple, ks_tuple, password);
+                               new_n_ks_tuple, new_ks_tuple, password);
     if (ret)
         goto done;
 
@@ -1513,9 +1654,10 @@ kadm5_chpass_principal_3(void *server_handle,
 
     (void) k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
                                 KADM5_HOOK_STAGE_POSTCOMMIT, principal,
-                                keepold, n_ks_tuple, ks_tuple, password);
+                                keepold, new_n_ks_tuple, new_ks_tuple, password);
     ret = KADM5_OK;
 done:
+    free(new_ks_tuple);
     if (!hist_added && hist.key_data)
         free_history_entry(handle->context, &hist);
     kdb_free_entry(handle, kdb, &adb);
@@ -1554,39 +1696,41 @@ kadm5_randkey_principal_3(void *server_handle,
     int                         ret, last_pwd, have_pol = 0;
     kadm5_server_handle_t       handle = server_handle;
     krb5_keyblock               *act_mkey;
+    int                         new_n_ks_tuple = 0;
+    krb5_key_salt_tuple         *new_ks_tuple = NULL;
 
     if (keyblocks)
         *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 ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
+        return(ret);
+
+    ret = apply_keysalt_policy(handle, adb.policy, n_ks_tuple, ks_tuple,
+                               &new_n_ks_tuple, &new_ks_tuple);
+    if (ret)
+        goto done;
+
     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;
+        new_n_ks_tuple = 1;
     }
 
-    if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
-        return(ret);
-
     ret = krb5_dbe_find_act_mkey(handle->context, active_mkey_list, NULL,
                                  &act_mkey);
     if (ret)
         goto done;
 
-    ret = krb5_dbe_crk(handle->context, act_mkey, ks_tuple, n_ks_tuple,
+    ret = krb5_dbe_crk(handle->context, act_mkey, new_ks_tuple, new_n_ks_tuple,
                        keepold, kdb);
     if (ret)
         goto done;
@@ -1650,7 +1794,7 @@ kadm5_randkey_principal_3(void *server_handle,
 
     ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
                                KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold,
-                               n_ks_tuple, ks_tuple, NULL);
+                               new_n_ks_tuple, new_ks_tuple, NULL);
     if (ret)
         goto done;
     if ((ret = kdb_put_entry(handle, kdb, &adb)))
@@ -1658,9 +1802,10 @@ kadm5_randkey_principal_3(void *server_handle,
 
     (void) k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
                                 KADM5_HOOK_STAGE_POSTCOMMIT, principal,
-                                keepold, n_ks_tuple, ks_tuple, NULL);
+                                keepold, new_n_ks_tuple, new_ks_tuple, NULL);
     ret = KADM5_OK;
 done:
+    free(new_ks_tuple);
     kdb_free_entry(handle, kdb, &adb);
     if (have_pol)
         kadm5_free_policy_ent(handle->lhandle, &pol);
@@ -1838,6 +1983,24 @@ kadm5_setkey_principal(void *server_handle,
                                  keyblocks, n_keys);
 }
 
+/* Make key/salt list from keys for kadm5_setkey_principal_3() */
+static kadm5_ret_t
+make_ks_from_keys(krb5_context context, int n_keys, krb5_keyblock *keyblocks,
+                  krb5_key_salt_tuple **ks_tuple)
+{
+    int i;
+
+    *ks_tuple = calloc(n_keys, sizeof(**ks_tuple));
+    if (ks_tuple == NULL)
+        return ENOMEM;
+
+    for (i = 0; i < n_keys; i++) {
+        (*ks_tuple)[i].ks_enctype = keyblocks[i].enctype;
+        (*ks_tuple)[i].ks_salttype = KRB5_KDB_SALTTYPE_NORMAL;
+    }
+    return 0;
+}
+
 kadm5_ret_t
 kadm5_setkey_principal_3(void *server_handle,
                          krb5_principal principal,
@@ -1862,6 +2025,7 @@ kadm5_setkey_principal_3(void *server_handle,
     krb5_key_data         tmp_key_data;
     krb5_key_data        *tptr;
     krb5_keyblock               *act_mkey;
+    krb5_key_salt_tuple         *ks_from_keys = NULL;
 
     CHECK_HANDLE(server_handle);
 
@@ -1874,6 +2038,31 @@ kadm5_setkey_principal_3(void *server_handle,
                                  principal, hist_princ)) == TRUE))
         return KADM5_PROTECT_PRINCIPAL;
 
+    if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
+        return(ret);
+
+    if (!n_ks_tuple) {
+        /* Apply policy to the key/salt types implied by the given keys */
+        ret = make_ks_from_keys(handle->context, n_keys, keyblocks,
+                                &ks_from_keys);
+        if (ret)
+            goto done;
+        ret = apply_keysalt_policy(handle, adb.policy, n_keys, ks_from_keys,
+                                   NULL, NULL);
+        free(ks_from_keys);
+    } else {
+        /*
+         * Apply policy to the given ks_tuples.  Note that further below
+         * we enforce keyblocks[i].enctype == ks_tuple[i].ks_enctype for
+         * all i from 0 to n_keys, and that n_ks_tuple == n_keys if ks
+         * tuples are given.
+         */
+        ret = apply_keysalt_policy(handle, adb.policy, n_ks_tuple, ks_tuple,
+                                   NULL, NULL);
+    }
+    if (ret)
+        goto done;
+
     for (i = 0; i < n_keys; i++) {
         for (j = i+1; j < n_keys; j++) {
             if ((ret = krb5_c_enctype_compare(handle->context,
@@ -1894,9 +2083,6 @@ kadm5_setkey_principal_3(void *server_handle,
     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;
diff --git a/src/lib/kadm5/unit-test/destroy-test.c b/src/lib/kadm5/unit-test/destroy-test.c
index ccca249..738cfeb 100644
--- a/src/lib/kadm5/unit-test/destroy-test.c
+++ b/src/lib/kadm5/unit-test/destroy-test.c
@@ -28,7 +28,7 @@ int main()
     }
     for(x = 0; x < TEST_NUM; x++) {
         ret = kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE, 0,
-                         KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+                         KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
                          &server_handle);
         if(ret != KADM5_OK) {
             com_err("test", ret, "init");
diff --git a/src/lib/kadm5/unit-test/handle-test.c b/src/lib/kadm5/unit-test/handle-test.c
index a77bc11..29bd2c9 100644
--- a/src/lib/kadm5/unit-test/handle-test.c
+++ b/src/lib/kadm5/unit-test/handle-test.c
@@ -31,7 +31,7 @@ int main(int argc, char *argv[])
     kadm5_init_krb5_context(&context);
 
     ret = kadm5_init(context, "admin/none", "admin", KADM5_ADMIN_SERVICE, NULL,
-                     KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+                     KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
                      &server_handle);
     if(ret != KADM5_OK) {
         com_err("test", ret, "init");
diff --git a/src/lib/kadm5/unit-test/init-test.c b/src/lib/kadm5/unit-test/init-test.c
index 354b812..880400c 100644
--- a/src/lib/kadm5/unit-test/init-test.c
+++ b/src/lib/kadm5/unit-test/init-test.c
@@ -21,7 +21,7 @@ int main()
         exit(1);
     }
     ret = kadm5_init(context, "admin", "admin", NULL, &params,
-                     KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+                     KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
                      &server_handle);
     if (ret == KADM5_RPC_ERROR) {
         krb5_free_context(context);
diff --git a/src/lib/kadm5/unit-test/iter-test.c b/src/lib/kadm5/unit-test/iter-test.c
index bc7cfdc..cd85ebe 100644
--- a/src/lib/kadm5/unit-test/iter-test.c
+++ b/src/lib/kadm5/unit-test/iter-test.c
@@ -23,7 +23,7 @@ int main(int argc, char **argv)
         exit(1);
     }
     ret = kadm5_init("admin", "admin", KADM5_ADMIN_SERVICE, 0,
-                     KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+                     KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
                      &server_handle);
     if (ret != KADM5_OK) {
         com_err("iter-test", ret, "while initializing");
diff --git a/src/lib/kadm5/unit-test/randkey-test.c b/src/lib/kadm5/unit-test/randkey-test.c
index 7cf4ee8..dbef88a 100644
--- a/src/lib/kadm5/unit-test/randkey-test.c
+++ b/src/lib/kadm5/unit-test/randkey-test.c
@@ -24,7 +24,7 @@ int main()
 
     krb5_parse_name(context, "testuser", &tprinc);
     ret = kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE, NULL,
-                     KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+                     KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
                      &server_handle);
     if(ret != KADM5_OK) {
         com_err("test", ret, "init");
diff --git a/src/lib/kadm5/unit-test/setkey-test.c b/src/lib/kadm5/unit-test/setkey-test.c
index 53056e4..c1b9c5d 100644
--- a/src/lib/kadm5/unit-test/setkey-test.c
+++ b/src/lib/kadm5/unit-test/setkey-test.c
@@ -120,7 +120,7 @@ main(int argc, char **argv)
     }
 
     ret = kadm5_init(context, authprinc, NULL, KADM5_ADMIN_SERVICE, NULL,
-                     KADM5_STRUCT_VERSION, KADM5_API_VERSION_3, NULL,
+                     KADM5_STRUCT_VERSION, KADM5_API_VERSION_4, NULL,
                      &handle);
     if (ret) {
         com_err(whoami, ret, "while initializing connection");
diff --git a/src/lib/kdb/kdb5.c b/src/lib/kdb/kdb5.c
index a3c2a5f..3cf116b 100644
--- a/src/lib/kdb/kdb5.c
+++ b/src/lib/kdb/kdb5.c
@@ -2185,8 +2185,8 @@ krb5_dbe_delete_tl_data(krb5_context context, krb5_db_entry *entry,
 }
 
 krb5_error_code
-krb5_dbe_update_tl_data(krb5_context context, krb5_db_entry *entry,
-                        krb5_tl_data *new_tl_data)
+krb5_db_update_tl_data(krb5_context context, krb5_int16 *n_tl_datap,
+                       krb5_tl_data **tl_datap, krb5_tl_data *new_tl_data)
 {
     krb5_tl_data *tl_data = NULL;
     krb5_octet *tmp;
@@ -2206,7 +2206,7 @@ krb5_dbe_update_tl_data(krb5_context context, krb5_db_entry *entry,
      */
 
     if (new_tl_data->tl_data_type != KRB5_TL_DB_ARGS) { /* db_args can be multiple */
-        for (tl_data = entry->tl_data; tl_data;
+        for (tl_data = *tl_datap; tl_data;
              tl_data = tl_data->tl_data_next)
             if (tl_data->tl_data_type == new_tl_data->tl_data_type)
                 break;
@@ -2221,9 +2221,9 @@ krb5_dbe_update_tl_data(krb5_context context, krb5_db_entry *entry,
             return (ENOMEM);
         }
         memset(tl_data, 0, sizeof(krb5_tl_data));
-        tl_data->tl_data_next = entry->tl_data;
-        entry->tl_data = tl_data;
-        entry->n_tl_data++;
+        tl_data->tl_data_next = *tl_datap;
+        *tl_datap = tl_data;
+        (*n_tl_datap)++;
     }
 
     /* fill in the record */
@@ -2240,6 +2240,14 @@ krb5_dbe_update_tl_data(krb5_context context, krb5_db_entry *entry,
 }
 
 krb5_error_code
+krb5_dbe_update_tl_data(krb5_context context, krb5_db_entry *entry,
+                        krb5_tl_data *new_tl_data)
+{
+    return krb5_db_update_tl_data(context, &entry->n_tl_data, &entry->tl_data,
+                                  new_tl_data);
+}
+
+krb5_error_code
 krb5_dbe_compute_salt(krb5_context context, const krb5_key_data *key,
                       krb5_const_principal princ, krb5_int16 *salttype_out,
                       krb5_data **salt_out)
diff --git a/src/lib/kdb/libkdb5.exports b/src/lib/kdb/libkdb5.exports
index 0e58262..9aa8d1a 100644
--- a/src/lib/kdb/libkdb5.exports
+++ b/src/lib/kdb/libkdb5.exports
@@ -65,6 +65,7 @@ krb5_dbe_update_mkey_aux
 krb5_dbe_update_mkvno
 krb5_dbe_update_mod_princ_data
 krb5_dbe_update_tl_data
+krb5_db_update_tl_data
 krb5_dbe_def_encrypt_key_data
 krb5_dbe_def_decrypt_key_data
 krb5_dbe_decrypt_key_data
diff --git a/src/plugins/kdb/db2/pol_xdr.c b/src/plugins/kdb/db2/pol_xdr.c
index 315d0d1..e857633 100644
--- a/src/plugins/kdb/db2/pol_xdr.c
+++ b/src/plugins/kdb/db2/pol_xdr.c
@@ -2,68 +2,29 @@
 #include <krb5.h>
 #include <gssrpc/rpc.h>
 #include <kdb.h>
+#include <kadm5/admin_xdr.h>
 #include "policy_db.h"
 #ifdef HAVE_MEMORY_H
 #include <memory.h>
 #endif
 #include <string.h>
 
-static
-bool_t xdr_nullstring(XDR *xdrs, char **objp)
-{
-     u_int size;
-
-     if (xdrs->x_op == XDR_ENCODE) {
-          if (*objp == NULL)
-               size = 0;
-          else
-               size = strlen(*objp) + 1;
-     }
-     if (! xdr_u_int(xdrs, &size)) {
-          return FALSE;
-        }
-     switch (xdrs->x_op) {
-     case XDR_DECODE:
-          if (size == 0) {
-               *objp = NULL;
-               return TRUE;
-          } else if (*objp == NULL) {
-               *objp = (char *) mem_alloc(size);
-               if (*objp == NULL) {
-                    errno = ENOMEM;
-                    return FALSE;
-               }
-          }
-          return (xdr_opaque(xdrs, *objp, size));
-
-     case XDR_ENCODE:
-          if (size != 0)
-               return (xdr_opaque(xdrs, *objp, size));
-          return TRUE;
-
-     case XDR_FREE:
-          if (*objp != NULL)
-               mem_free(*objp, size);
-          *objp = NULL;
-          return TRUE;
-     }
-
-     return FALSE;
-}
-
 static int
 osa_policy_min_vers(osa_policy_ent_t objp)
 {
-    int vers;
+    if (objp->attributes ||
+        objp->max_life ||
+        objp->max_renewable_life ||
+        objp->allowed_keysalts ||
+        objp->n_tl_data)
+        return OSA_ADB_POLICY_VERSION_3;
 
     if (objp->pw_max_fail ||
         objp->pw_failcnt_interval ||
         objp->pw_lockout_duration)
-        vers = OSA_ADB_POLICY_VERSION_2;
-    else
-        vers = OSA_ADB_POLICY_VERSION_1;
+        return OSA_ADB_POLICY_VERSION_2;
 
-    return vers;
+    return OSA_ADB_POLICY_VERSION_1;
 }
 
 bool_t
@@ -81,7 +42,8 @@ xdr_osa_policy_ent_rec(XDR *xdrs, osa_policy_ent_t objp)
 	 if (!xdr_int(xdrs, &objp->version))
 	      return FALSE;
 	 if (objp->version != OSA_ADB_POLICY_VERSION_1 &&
-             objp->version != OSA_ADB_POLICY_VERSION_2)
+             objp->version != OSA_ADB_POLICY_VERSION_2 &&
+             objp->version != OSA_ADB_POLICY_VERSION_3)
 	      return FALSE;
 	 break;
     }
@@ -108,5 +70,20 @@ xdr_osa_policy_ent_rec(XDR *xdrs, osa_policy_ent_t objp)
         if (!xdr_u_int32(xdrs, &objp->pw_lockout_duration))
 	    return (FALSE);
     }
+    if (objp->version > OSA_ADB_POLICY_VERSION_2) {
+        if (!xdr_u_int32(xdrs, &objp->attributes))
+	    return (FALSE);
+        if (!xdr_u_int32(xdrs, &objp->max_life))
+	    return (FALSE);
+        if (!xdr_u_int32(xdrs, &objp->max_renewable_life))
+	    return (FALSE);
+        if (!xdr_nullstring(xdrs, &objp->allowed_keysalts))
+	    return (FALSE);
+        if (!xdr_short(xdrs, &objp->n_tl_data))
+            return (FALSE);
+        if (!xdr_nulltype(xdrs, (void **) &objp->tl_data,
+                          xdr_krb5_tl_data))
+            return FALSE;
+    }
     return (TRUE);
 }
diff --git a/src/plugins/kdb/db2/policy_db.h b/src/plugins/kdb/db2/policy_db.h
index 6c920bc..07026e3 100644
--- a/src/plugins/kdb/db2/policy_db.h
+++ b/src/plugins/kdb/db2/policy_db.h
@@ -41,6 +41,7 @@ typedef long            osa_adb_ret_t;
 #define OSA_ADB_POLICY_VERSION_MASK     0x12345D00
 #define OSA_ADB_POLICY_VERSION_1        0x12345D01
 #define OSA_ADB_POLICY_VERSION_2        0x12345D02
+#define OSA_ADB_POLICY_VERSION_3        0x12345D03
 
 
 
diff --git a/src/slave/kpropd.c b/src/slave/kpropd.c
index aa6e979..309717d 100644
--- a/src/slave/kpropd.c
+++ b/src/slave/kpropd.c
@@ -713,7 +713,7 @@ reinit:
                                   master_svc_princstr,
                                   &params,
                                   KADM5_STRUCT_VERSION,
-                                  KADM5_API_VERSION_3,
+                                  KADM5_API_VERSION_4,
                                   db_args,
                                   &server_handle);
 
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in
index 210bd8d..39a047ec 100644
--- a/src/tests/Makefile.in
+++ b/src/tests/Makefile.in
@@ -69,6 +69,7 @@ check-pytests:: hist
 	$(RUNPYTEST) $(srcdir)/t_lockout.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_kadm5_hook.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_keyrollover.py $(PYTESTFLAGS)
+	$(RUNPYTEST) $(srcdir)/t_allowed_keysalts.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_renew.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_renprinc.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_ccache.py $(PYTESTFLAGS)
diff --git a/src/tests/hist.c b/src/tests/hist.c
index c0b2b97..3d9e29e 100644
--- a/src/tests/hist.c
+++ b/src/tests/hist.c
@@ -72,7 +72,7 @@ main(int argc, char **argv)
     params.mask |= KADM5_CONFIG_REALM;
     params.realm = realm;
     check(kadm5_init(ctx, "user", "", "", &params, KADM5_STRUCT_VERSION,
-                     KADM5_API_VERSION_3, NULL, &handle));
+                     KADM5_API_VERSION_4, NULL, &handle));
     if (strcmp(argv[1], "make") == 0) {
         memset(&kent, 0, sizeof(kent));
         kent.principal = hprinc;
diff --git a/src/tests/t_allowed_keysalts.py b/src/tests/t_allowed_keysalts.py
new file mode 100644
index 0000000..8c76335
--- /dev/null
+++ b/src/tests/t_allowed_keysalts.py
@@ -0,0 +1,93 @@
+#!/usr/bin/python
+from k5test import *
+import re
+
+krb5_conf1 = {'all': {'libdefaults': {
+            'supported_enctypes': 'aes256-cts'}}}
+
+realm = K5Realm(krb5_conf=krb5_conf1, create_host=False, get_creds=False)
+
+# Add policy.
+realm.run_kadminl('addpol -allowedkeysalts aes256-cts:normal ak')
+realm.run_kadminl('addprinc -randkey -e aes256-cts:normal server')
+
+# Test with one-enctype allowed_keysalts.
+realm.run_kadminl('modprinc -policy ak server')
+realm.run_kadminl('getprinc server')
+output = realm.run_kadminl('cpw -randkey -e aes128-cts:normal server')
+if not 'Invalid key/salt tuples' in output:
+    fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+output = realm.run_kadminl('cpw -randkey -e aes256-cts:normal server')
+if 'Invalid key/salt tuples' in output:
+    fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+
+# Now test a multi-enctype allowed_keysalts.  Test that subsets are allowed,
+# the the complete set is allowed, that order doesn't matter, and that
+# enctypes outside the set are not allowed.
+
+# Test modpol.
+realm.run_kadminl('modpol -allowedkeysalts '
+                  'aes256-cts:normal,rc4-hmac:normal ak')
+output = realm.run_kadminl('getpol ak')
+if not 'Allowed key/salt types: aes256-cts:normal,rc4-hmac:normal' in output:
+    fail('getpol does not implement allowedkeysalts?')
+
+# Test one subset.
+output = realm.run_kadminl('cpw -randkey -e rc4-hmac:normal server')
+if 'Invalid key/salt tuples' in output:
+    fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+
+# Test another subset.
+output = realm.run_kadminl('cpw -randkey -e aes256-cts:normal server')
+if 'Invalid key/salt tuples' in output:
+    fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+output = realm.run_kadminl('cpw -randkey -e '
+                           'rc4-hmac:normal,aes256-cts:normal server')
+if 'Invalid key/salt tuples' in output:
+    fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+
+# Test full set.
+output = realm.run_kadminl('cpw -randkey -e aes256-cts:normal,rc4-hmac:normal '
+                           'server')
+if 'Invalid key/salt tuples' in output:
+    fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+output = realm.run_kadminl('cpw -randkey -e rc4-hmac:normal,aes128-cts:normal '
+                           'server')
+if not 'Invalid key/salt tuples' in output:
+    fail('allowed_keysalts policy not applied properly')
+realm.run_kadminl('getprinc server')
+output = realm.run_kadminl('getprinc -terse server')
+if not '2\t1\t6\t18\t0\t1\t6\t23\t0' in output:
+    fail('allowed_keysalts policy did not preserve order')
+
+# Test full set in opposite order.
+output = realm.run_kadminl('cpw -randkey -e rc4-hmac:normal,aes256-cts:normal,'
+                           'aes128-cts:normal server')
+if not 'Invalid key/salt tuples' in output:
+    fail('allowed_keysalts policy not applied properly')
+
+# Check that the order we got is the one from the policy.
+realm.run_kadminl('getprinc server')
+output = realm.run_kadminl('getprinc -terse server')
+if not '2\t1\t6\t18\t0\t1\t6\t23\t0' in output:
+    fail('allowed_keysalts policy did not preserve order')
+
+# Test reset of allowedkeysalts.
+realm.run_kadminl('modpol -allowedkeysalts - ak')
+output = realm.run_kadminl('getpol ak')
+if 'Allowed key/salt types' in output:
+    fail('failed to clear allowedkeysalts')
+output = realm.run_kadminl('cpw -randkey -e aes128-cts:normal server')
+if 'Invalid key/salt tuples' in output:
+    fail('key change rejected that should have been permitted')
+realm.run_kadminl('getprinc server')
+
+realm.stop()
+
+success('allowed_keysalts')
diff --git a/src/tests/t_general.py b/src/tests/t_general.py
index 2b04b8e..77246d5 100755
--- a/src/tests/t_general.py
+++ b/src/tests/t_general.py
@@ -28,10 +28,26 @@ realm = K5Realm(create_host=False)
 realm.run_kadminl('addpol fred')
 dumpfile = os.path.join(realm.testdir, 'dump')
 realm.run_as_master([kdb5_util, 'dump', dumpfile])
+f = open('testdir/dump', 'a')
+f.write('policy	barney	0	0	1	1	1	0	'
+        '0	0	0	0	0	0	-	1	'
+        '2	28	'
+        'fd100f5064625f6372656174696f6e404b5242544553542e434f4d00')
+f.close()
+realm.run_as_master([kdb5_util, 'load', dumpfile])
+output = realm.run_kadminl('getpols')
+if 'fred\n' not in output:
+    fail('Policy not preserved across dump/load.')
+if 'barney\n' not in output:
+    fail('Policy not loaded.')
+
+realm.run_as_master([kdb5_util, 'dump', dumpfile])
 realm.run_as_master([kdb5_util, 'load', dumpfile])
 output = realm.run_kadminl('getpols')
 if 'fred\n' not in output:
     fail('Policy not preserved across dump/load.')
+if 'barney\n' not in output:
+    fail('Policy not preserved across dump/load.')
 
 # Spot-check KRB5_TRACE output
 tracefile = os.path.join(realm.testdir, 'trace')


More information about the cvs-krb5 mailing list