krb5 commit: Add C test program to replace libkadm5 tcl tests

Greg Hudson ghudson at mit.edu
Tue May 11 02:08:02 EDT 2021


https://github.com/krb5/krb5/commit/10648ca330e40b07f30165ec51f4a45ef9b96eea
commit 10648ca330e40b07f30165ec51f4a45ef9b96eea
Author: Greg Hudson <ghudson at mit.edu>
Date:   Mon Apr 12 01:29:52 2021 -0400

    Add C test program to replace libkadm5 tcl tests

 src/lib/kadm5/Makefile.in |    9 +
 src/lib/kadm5/t_kadm5.c   | 1326 +++++++++++++++++++++++++++++++++++++++++++++
 src/lib/kadm5/t_kadm5.py  |   45 ++
 src/util/k5test.py        |    2 +-
 4 files changed, 1381 insertions(+), 1 deletions(-)

diff --git a/src/lib/kadm5/Makefile.in b/src/lib/kadm5/Makefile.in
index c4eaad3..f94c0a7 100644
--- a/src/lib/kadm5/Makefile.in
+++ b/src/lib/kadm5/Makefile.in
@@ -84,6 +84,15 @@ all-prerecurse: all-libobjs
 
 all-windows: $(OBJS)
 
+t_kadm5clnt: t_kadm5.o $(KADMCLNT_DEPLIBS) $(KRB5_BASE_DEPLIBS)
+	$(CC_LINK) -o $@ t_kadm5.o $(KADMCLNT_LIBS) $(KRB5_BASE_LIBS)
+
+t_kadm5srv: t_kadm5.o $(KADMSRV_DEPLIBS) $(KRB5_BASE_DEPLIBS)
+	$(CC_LINK) -o $@ t_kadm5.o $(KADMSRV_LIBS) $(KRB5_BASE_LIBS)
+
+check-pytests: t_kadm5clnt t_kadm5srv
+	$(RUNPYTEST) $(srcdir)/t_kadm5.py $(PYTESTFLAGS)
+
 generate-files-mac-prerecurse: includes
 
 check-windows:
diff --git a/src/lib/kadm5/t_kadm5.c b/src/lib/kadm5/t_kadm5.c
new file mode 100644
index 0000000..153147f
--- /dev/null
+++ b/src/lib/kadm5/t_kadm5.c
@@ -0,0 +1,1326 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/kadm5/t_kadm5.c - API tests for libkadm5 */
+/*
+ * Copyright (C) 2021 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in
+ *   the documentation and/or other materials provided with the
+ *   distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "k5-int.h"
+#include <kadm5/admin.h>
+
+static uint32_t api;
+static krb5_boolean rpc;
+
+static krb5_context context;
+
+/* These must match the creation commands in t_kadm5.py. */
+#define ADMIN_PASSWORD "admin"
+#define USER_PASSWORD "us3r"
+
+/* This list must match the supported_enctypes setting in t_kadm5.py. */
+static krb5_enctype
+default_supported_enctypes[] = {
+    ENCTYPE_AES256_CTS_HMAC_SHA1_96, ENCTYPE_AES128_CTS_HMAC_SHA1_96,
+    ENCTYPE_NULL
+};
+
+static void
+check(krb5_error_code code)
+{
+    assert(code == 0);
+}
+
+static void
+check_fail(krb5_error_code code, krb5_error_code expected)
+{
+    assert(code == expected);
+}
+
+/*
+ * Initialize a handle using the global context.  The caller must destroy this
+ * handle before initializing another one.  If the client name begins with '$',
+ * authenticate to kadmin/changepw; otherwise authenticate to kadmin/admin.  If
+ * client is null, return a null handle.
+ */
+static void *
+get_handle(char *client)
+{
+    void *handle;
+    char *service, *pass;
+
+    if (client == NULL)
+        return NULL;
+
+    if (*client == '$') {
+        service = KADM5_CHANGEPW_SERVICE;
+        client++;
+    } else {
+        service = KADM5_ADMIN_SERVICE;
+    }
+    pass = (strcmp(client, "user") == 0) ? USER_PASSWORD : ADMIN_PASSWORD;
+
+    check(kadm5_init(context, client, pass, service, NULL,
+                     KADM5_STRUCT_VERSION, api, NULL, &handle));
+    return handle;
+}
+
+static void
+free_handle(void *handle)
+{
+    if (handle != NULL)
+        check(kadm5_destroy(handle));
+}
+
+static krb5_principal
+parse_princ(const char *str)
+{
+    krb5_principal princ;
+
+    check(krb5_parse_name(context, str, &princ));
+    return princ;
+}
+
+static void
+create_simple_policy(char *name)
+{
+    void *handle = get_handle("admin");
+    kadm5_policy_ent_rec ent;
+
+    memset(&ent, 0, sizeof(ent));
+    ent.policy = name;
+    check(kadm5_create_policy(handle, &ent, KADM5_POLICY));
+    free_handle(handle);
+}
+
+static void
+delete_policy(char *name)
+{
+    void *handle = get_handle("admin");
+
+    check(kadm5_delete_policy(handle, name));
+    free_handle(handle);
+}
+
+static void
+compare_policy(kadm5_policy_ent_t x, uint32_t mask)
+{
+    kadm5_policy_ent_rec g;
+    void *handle = get_handle("admin");
+
+    check(kadm5_get_policy(handle, x->policy, &g));
+
+    assert(strcmp(g.policy, x->policy) == 0);
+    if (mask & KADM5_PW_MAX_LIFE)
+        assert(g.pw_max_life == x->pw_max_life);
+    if (mask & KADM5_PW_MIN_LIFE)
+        assert(g.pw_min_life == x->pw_min_life);
+    if (mask & KADM5_PW_MIN_LENGTH)
+        assert(g.pw_min_length == x->pw_min_length);
+    if (mask & KADM5_PW_MIN_CLASSES)
+        assert(g.pw_min_classes == x->pw_min_classes);
+    if (mask & KADM5_PW_HISTORY_NUM)
+        assert(g.pw_history_num == x->pw_history_num);
+    if (mask & KADM5_PW_MAX_FAILURE)
+        assert(g.pw_max_fail == x->pw_max_fail);
+    if (mask & KADM5_PW_FAILURE_COUNT_INTERVAL)
+        assert(g.pw_failcnt_interval == x->pw_failcnt_interval);
+    if (mask & KADM5_PW_LOCKOUT_DURATION)
+        assert(g.pw_lockout_duration == x->pw_lockout_duration);
+
+    check(kadm5_free_policy_ent(handle, &g));
+    free_handle(handle);
+}
+
+static void
+create_simple_princ(krb5_principal princ, char *policy)
+{
+    void *handle = get_handle("admin");
+    kadm5_principal_ent_rec ent;
+    uint32_t mask = KADM5_PRINCIPAL;
+
+    memset(&ent, 0, sizeof(ent));
+    ent.principal = princ;
+    ent.policy = policy;
+    if (policy != NULL)
+        mask |= KADM5_POLICY;
+    check(kadm5_create_principal(handle, &ent, mask, "pw"));
+    free_handle(handle);
+}
+
+static void
+delete_princ(krb5_principal princ)
+{
+    void *handle = get_handle("admin");
+
+    check(kadm5_delete_principal(handle, princ));
+    free_handle(handle);
+}
+
+static void
+compare_key_data(kadm5_principal_ent_t ent, const krb5_enctype *etypes)
+{
+    int i;
+
+    for (i = 0; etypes[i] != ENCTYPE_NULL; i++) {
+        assert(i < ent->n_key_data);
+        assert(ent->key_data[i].key_data_ver >= 1);
+        assert(ent->key_data[i].key_data_type[0] == etypes[i]);
+    }
+}
+
+static void
+compare_princ(kadm5_principal_ent_t x, uint32_t mask)
+{
+    void *handle = get_handle("admin");
+    kadm5_principal_ent_rec g;
+    kadm5_policy_ent_rec pol;
+
+    check(kadm5_get_principal(handle, x->principal, &g,
+                              KADM5_PRINCIPAL_NORMAL_MASK));
+
+    assert(krb5_principal_compare(context, g.principal, x->principal));
+    if (mask & KADM5_POLICY)
+        assert(strcmp(g.policy, x->policy) == 0);
+    if (mask & KADM5_PRINC_EXPIRE_TIME)
+        assert(g.princ_expire_time == x->princ_expire_time);
+    if (mask & KADM5_MAX_LIFE)
+        assert(g.max_life == x->max_life);
+    if (mask & KADM5_MAX_RLIFE)
+        assert(g.max_renewable_life == x->max_renewable_life);
+    if (mask & KADM5_FAIL_AUTH_COUNT)
+        assert(g.fail_auth_count == x->fail_auth_count);
+    if (mask & KADM5_ATTRIBUTES)
+        assert(g.attributes == x->attributes);
+    if (mask & KADM5_KVNO)
+        assert(g.kvno == x->kvno);
+
+    if (mask & KADM5_PW_EXPIRATION) {
+        assert(g.pw_expiration == x->pw_expiration);
+    } else if ((mask & KADM5_POLICY) &&
+               kadm5_get_policy(handle, g.policy, &pol) == 0) {
+        /* Check the policy pw_max_life computation. */
+        if (pol.pw_max_life != 0) {
+            assert(ts_incr(g.last_pwd_change, pol.pw_max_life) ==
+                   g.pw_expiration);
+        } else {
+            assert(g.pw_expiration == 0);
+        }
+        check(kadm5_free_policy_ent(handle, &pol));
+    }
+
+    if (mask & KADM5_POLICY_CLR) {
+        assert(g.policy == NULL);
+        if (!(mask & KADM5_PW_EXPIRATION))
+            assert(g.pw_expiration == 0);
+    }
+
+    check(kadm5_free_principal_ent(handle, &g));
+    free_handle(handle);
+}
+
+static void
+kinit(krb5_ccache cc, const char *user, const char *pass, const char *service)
+{
+    krb5_get_init_creds_opt *opt;
+    krb5_principal client = parse_princ(user);
+    krb5_creds creds;
+
+    check(krb5_get_init_creds_opt_alloc(context, &opt));
+    check(krb5_get_init_creds_opt_set_out_ccache(context, opt, cc));
+    check(krb5_get_init_creds_password(context, &creds, client, pass, NULL,
+                                       NULL, 0, service, opt));
+    krb5_get_init_creds_opt_free(context, opt);
+    krb5_free_cred_contents(context, &creds);
+    krb5_free_principal(context, client);
+}
+
+static void
+cpw_test_fail(char *user, krb5_principal princ, char *pass,
+              krb5_error_code code)
+{
+    void *handle = get_handle(user);
+
+    check_fail(kadm5_chpass_principal(handle, princ, pass), code);
+    free_handle(handle);
+}
+
+static void
+cpw_test_succeed(char *user, krb5_principal princ, char *pass)
+{
+    cpw_test_fail(user, princ, pass, 0);
+}
+
+static void
+test_chpass()
+{
+    krb5_principal princ = parse_princ("chpass-test");
+    krb5_principal hist_princ = parse_princ("kadmin/history");
+    kadm5_principal_ent_rec ent;
+    void *handle;
+
+    /* Specify a policy so that kadmin/history is created. */
+    create_simple_princ(princ, "minlife-pol");
+
+    /* Check kvno and enctypes after a password change. */
+    handle = get_handle("admin");
+    check(kadm5_chpass_principal(handle, princ, "newpassword"));
+    check(kadm5_get_principal(handle, princ, &ent, KADM5_KEY_DATA));
+    compare_key_data(&ent, default_supported_enctypes);
+    assert(ent.key_data[0].key_data_kvno == 2);
+    check(kadm5_free_principal_ent(handle, &ent));
+    free_handle(handle);
+
+    /* Fails for protected principal. */
+    cpw_test_fail("admin", hist_princ, "pw", KADM5_PROTECT_PRINCIPAL);
+
+    /* Fails over RPC if "change" ACL is not granted, or if we authenticated to
+     * kadmin/changepw and are changing another principal's password. */
+    if (rpc) {
+        cpw_test_succeed("admin/modify", princ, "pw2");
+        cpw_test_fail("admin/none", princ, "pw3", KADM5_AUTH_CHANGEPW);
+        cpw_test_fail("$admin", princ, "pw3", KADM5_AUTH_CHANGEPW);
+    }
+
+    /* Fails with null handle or principal name. */
+    cpw_test_fail(NULL, princ, "pw", KADM5_BAD_SERVER_HANDLE);
+    cpw_test_fail("admin", NULL, "pw", EINVAL);
+
+    delete_princ(princ);
+    krb5_free_principal(context, princ);
+    krb5_free_principal(context, hist_princ);
+}
+
+static void
+cpol_test_fail(char *user, kadm5_policy_ent_t ent, uint32_t mask,
+               krb5_error_code code)
+{
+    void *handle = get_handle(user);
+
+    check_fail(kadm5_create_policy(handle, ent, mask | KADM5_POLICY), code);
+    free_handle(handle);
+}
+
+static void
+cpol_test_compare(char *user, kadm5_policy_ent_t ent, uint32_t mask)
+{
+    cpol_test_fail(user, ent, mask, 0);
+    compare_policy(ent, mask);
+    delete_policy(ent->policy);
+}
+
+static void
+test_create_policy()
+{
+    void *handle;
+    kadm5_policy_ent_rec ent;
+
+    memset(&ent, 0, sizeof(ent));
+
+    /* Fails with undefined mask bit. */
+    ent.policy = "create-policy-test";
+    cpol_test_fail("admin", &ent, 0x10000000, KADM5_BAD_MASK);
+
+    /* Fails without KADM5_POLICY mask bit. */
+    handle = get_handle("admin");
+    check_fail(kadm5_create_policy(handle, &ent, 0), KADM5_BAD_MASK);
+    free_handle(handle);
+
+    /* pw_min_life = 0 and pw_min_life != 0 */
+    cpol_test_compare("admin", &ent, KADM5_PW_MIN_LIFE);
+    ent.pw_min_life = 32;
+    cpol_test_compare("admin", &ent, KADM5_PW_MIN_LIFE);
+
+    /* pw_max_life = 0 and pw_max_life != 0 */
+    cpol_test_compare("admin", &ent, KADM5_PW_MAX_LIFE);
+    ent.pw_max_life = 32;
+    cpol_test_compare("admin", &ent, KADM5_PW_MAX_LIFE);
+
+    /* pw_min_length = 0 (rejected) and pw_min_length != 0 */
+    cpol_test_fail("admin", &ent, KADM5_PW_MIN_LENGTH, KADM5_BAD_LENGTH);
+    ent.pw_min_length = 32;
+    cpol_test_compare("admin", &ent, KADM5_PW_MIN_LENGTH);
+
+    /* pw_min_classes = 0 (rejected), 1, 5, 6 (rejected) */
+    cpol_test_fail("admin", &ent, KADM5_PW_MIN_CLASSES, KADM5_BAD_CLASS);
+    ent.pw_min_classes = 1;
+    cpol_test_compare("admin", &ent, KADM5_PW_MIN_CLASSES);
+    ent.pw_min_classes = 5;
+    cpol_test_compare("admin", &ent, KADM5_PW_MIN_CLASSES);
+    ent.pw_min_classes = 6;
+    cpol_test_fail("admin", &ent, KADM5_PW_MIN_CLASSES, KADM5_BAD_CLASS);
+
+    /* pw_history_num = 0 (rejected), 1, 10 */
+    cpol_test_fail("admin", &ent, KADM5_PW_HISTORY_NUM, KADM5_BAD_HISTORY);
+    ent.pw_history_num = 1;
+    cpol_test_compare("admin", &ent, KADM5_PW_HISTORY_NUM);
+    ent.pw_history_num = 10;
+    cpol_test_compare("admin", &ent, KADM5_PW_HISTORY_NUM);
+
+    if (api >= KADM5_API_VERSION_3) {
+        ent.pw_max_fail = 2;
+        cpol_test_compare("admin", &ent, KADM5_PW_MAX_FAILURE);
+        ent.pw_failcnt_interval = 90;
+        cpol_test_compare("admin", &ent,
+                          KADM5_PW_FAILURE_COUNT_INTERVAL);
+        ent.pw_lockout_duration = 180;
+        cpol_test_compare("admin", &ent, KADM5_PW_LOCKOUT_DURATION);
+    }
+
+    /* Fails over RPC if "add" ACL is not granted, or if we authenticated to
+     * kadmin/changepw. */
+    if (rpc) {
+        cpol_test_fail("$admin", &ent, 0, KADM5_AUTH_ADD);
+        cpol_test_fail("admin/none", &ent, 0, KADM5_AUTH_ADD);
+        cpol_test_fail("admin/get", &ent, 0, KADM5_AUTH_ADD);
+        cpol_test_fail("admin/modify", &ent, 0, KADM5_AUTH_ADD);
+        cpol_test_fail("admin/delete", &ent, 0, KADM5_AUTH_ADD);
+        cpol_test_compare("admin/add", &ent, 0);
+    }
+
+    /* Fails with existing policy name. */
+    ent.policy = "test-pol";
+    cpol_test_fail("admin", &ent, 0, KADM5_DUP);
+
+    /* Fails with null or empty policy name, or invalid character in name. */
+    ent.policy = NULL;
+    cpol_test_fail("admin", &ent, 0, EINVAL);
+    ent.policy = "";
+    cpol_test_fail("admin", &ent, 0, KADM5_BAD_POLICY);
+    ent.policy = "pol\7";
+    cpol_test_fail("admin", &ent, 0, KADM5_BAD_POLICY);
+
+    /* Fails with null handle or policy ent. */
+    cpol_test_fail(NULL, &ent, 0, KADM5_BAD_SERVER_HANDLE);
+    cpol_test_fail("admin", NULL, 0, EINVAL);
+}
+
+static void
+cprinc_test_fail(char *user, kadm5_principal_ent_t ent, uint32_t mask,
+                 char *pass, krb5_error_code code)
+{
+    void *handle = get_handle(user);
+
+    check_fail(kadm5_create_principal(handle, ent, mask | KADM5_PRINCIPAL,
+                                      pass), code);
+    free_handle(handle);
+}
+
+static void
+cprinc_test_compare(char *user, kadm5_principal_ent_t ent, uint32_t mask,
+                    char *pass)
+{
+    cprinc_test_fail(user, ent, mask, pass, 0);
+    compare_princ(ent, mask);
+    delete_princ(ent->principal);
+}
+
+static void
+test_create_principal()
+{
+    void *handle;
+    kadm5_principal_ent_rec ent;
+    krb5_principal princ = parse_princ("create-principal-test");
+    krb5_principal user_princ = parse_princ("user");
+
+    memset(&ent, 0, sizeof(ent));
+    ent.principal = princ;
+
+    /* Fails with undefined or prohibited mask bit. */
+    cprinc_test_fail("admin", &ent, 0x100000, "", KADM5_BAD_MASK);
+    cprinc_test_fail("admin", &ent, KADM5_LAST_PWD_CHANGE, "pw",
+                     KADM5_BAD_MASK);
+    cprinc_test_fail("admin", &ent, KADM5_MOD_TIME, "pw", KADM5_BAD_MASK);
+    cprinc_test_fail("admin", &ent, KADM5_MOD_NAME, "pw", KADM5_BAD_MASK);
+    cprinc_test_fail("admin", &ent, KADM5_MKVNO, "pw", KADM5_BAD_MASK);
+    cprinc_test_fail("admin", &ent, KADM5_AUX_ATTRIBUTES, "pw",
+                     KADM5_BAD_MASK);
+
+    /* Fails without KADM5_PRINCIPAL mask bit. */
+    handle = get_handle("admin");
+    check_fail(kadm5_create_principal(handle, &ent, 0, "pw"), KADM5_BAD_MASK);
+    free_handle(handle);
+
+    /* Fails with empty password or password prohibited by policy. */
+    cprinc_test_fail("admin", &ent, 0, "", KADM5_PASS_Q_TOOSHORT);
+    ent.policy = "test-pol";
+    cprinc_test_fail("admin", &ent, KADM5_POLICY, "tP", KADM5_PASS_Q_TOOSHORT);
+    cprinc_test_fail("admin", &ent, KADM5_POLICY, "testpassword",
+                     KADM5_PASS_Q_CLASS);
+    cprinc_test_fail("admin", &ent, KADM5_POLICY, "Abyssinia",
+                     KADM5_PASS_Q_DICT);
+
+    cprinc_test_compare("admin", &ent, 0, "pw");
+    ent.policy = "nonexistent-pol";
+    cprinc_test_compare("admin", &ent, KADM5_POLICY, "pw");
+    cprinc_test_compare("admin/rename", &ent, KADM5_POLICY, "pw");
+
+    /* Test pw_expiration explicit specifications vs. policy pw_max_life. */
+    ent.policy = "test-pol";
+    cprinc_test_compare("admin", &ent, KADM5_POLICY, "NotinTheDictionary");
+    cprinc_test_compare("admin", &ent, KADM5_PRINC_EXPIRE_TIME, "pw");
+    cprinc_test_compare("admin", &ent, KADM5_PW_EXPIRATION, "pw");
+    cprinc_test_compare("admin", &ent, KADM5_POLICY | KADM5_PW_EXPIRATION,
+                        "NotinTheDictionary");
+    ent.pw_expiration = 1234;
+    cprinc_test_compare("admin", &ent, KADM5_PW_EXPIRATION, "pw");
+    cprinc_test_compare("admin", &ent, KADM5_POLICY | KADM5_PW_EXPIRATION,
+                        "NotinTheDictionary");
+    ent.pw_expiration = 999999999;
+    cprinc_test_compare("admin", &ent, KADM5_POLICY | KADM5_PW_EXPIRATION,
+                        "NotinTheDictionary");
+    ent.policy = "dict-only-pol";
+    cprinc_test_compare("admin", &ent, KADM5_POLICY | KADM5_PW_EXPIRATION,
+                        "pw");
+
+    /* Fails over RPC if "add" ACL is not granted, or if we authenticated to
+     * kadmin/changepw. */
+    if (rpc) {
+        cprinc_test_fail("$admin", &ent, 0, "pw", KADM5_AUTH_ADD);
+        cprinc_test_fail("admin/none", &ent, 0, "pw", KADM5_AUTH_ADD);
+        cprinc_test_fail("admin/get", &ent, 0, "pw", KADM5_AUTH_ADD);
+        cprinc_test_fail("admin/modify", &ent, 0, "pw", KADM5_AUTH_ADD);
+        cprinc_test_fail("admin/delete", &ent, 0, "pw", KADM5_AUTH_ADD);
+    }
+
+    /* Fails with existing policy name. */
+    ent.principal = user_princ;
+    cprinc_test_fail("admin", &ent, 0, "pw", KADM5_DUP);
+
+    /* Fails with null handle or principal ent. */
+    cprinc_test_fail(NULL, &ent, 0, "pw", KADM5_BAD_SERVER_HANDLE);
+    cprinc_test_fail("admin", NULL, 0, "pw", EINVAL);
+
+    krb5_free_principal(context, princ);
+    krb5_free_principal(context, user_princ);
+}
+
+static void
+dpol_test_fail(char *user, char *name, krb5_error_code code)
+{
+    void *handle = get_handle(user);
+
+    check_fail(kadm5_delete_policy(handle, name), code);
+    free_handle(handle);
+}
+
+static void
+dpol_test_succeed(char *user, char *name)
+{
+    dpol_test_fail(user, name, 0);
+}
+
+static void
+test_delete_policy()
+{
+    krb5_principal princ = parse_princ("delete-policy-test-princ");
+
+    /* Fails with unknown policy. */
+    dpol_test_fail("admin", "delete-policy-test", KADM5_UNK_POLICY);
+
+    /* Fails with empty policy name. */
+    dpol_test_fail("admin", "", KADM5_BAD_POLICY);
+
+    /* Succeeds with "delete" ACL (or local authentication). */
+    create_simple_policy("delete-policy-test");
+    dpol_test_succeed("admin/delete", "delete-policy-test");
+
+    /* Succeeds even if a principal references the policy, since we now allow
+     * principals to reference nonexistent policies. */
+    create_simple_policy("delete-policy-test");
+    create_simple_princ(princ, "delete-policy-test");
+    dpol_test_succeed("admin", "delete-policy-test");
+    delete_princ(princ);
+
+    /* Fails over RPC if "delete" ACL is not granted, or if we authenticated to
+     * kadmin/changepw. */
+    if (rpc) {
+        dpol_test_fail("$admin", "test-pol", KADM5_AUTH_DELETE);
+        dpol_test_fail("admin/none", "test-pol", KADM5_AUTH_DELETE);
+        dpol_test_fail("admin/add", "test-pol", KADM5_AUTH_DELETE);
+    }
+
+    /* Fails with null handle or principal ent. */
+    dpol_test_fail(NULL, "test-pol", KADM5_BAD_SERVER_HANDLE);
+    dpol_test_fail("admin", NULL, EINVAL);
+
+    krb5_free_principal(context, princ);
+}
+
+static void
+dprinc_test_fail(char *user, krb5_principal princ, krb5_error_code code)
+{
+    void *handle = get_handle(user);
+
+    check_fail(kadm5_delete_principal(handle, princ), code);
+    free_handle(handle);
+}
+
+static void
+dprinc_test_succeed(char *user, krb5_principal princ)
+{
+    dprinc_test_fail(user, princ, 0);
+}
+
+static void
+test_delete_principal()
+{
+    krb5_principal princ = parse_princ("delete-principal-test");
+
+    /* Fails with unknown principal. */
+    dprinc_test_fail("admin", princ, KADM5_UNK_PRINC);
+
+    /* Succeeds with "delete" ACL (or local authentication). */
+    create_simple_princ(princ, NULL);
+    dprinc_test_succeed("admin/delete", princ);
+
+    /* Fails over RPC if "delete" ACL is not granted, or if we authenticated to
+     * kadmin/changepw. */
+    if (rpc) {
+        dprinc_test_fail("$admin", princ, KADM5_AUTH_DELETE);
+        dprinc_test_fail("admin/add", princ, KADM5_AUTH_DELETE);
+        dprinc_test_fail("admin/modify", princ, KADM5_AUTH_DELETE);
+        dprinc_test_fail("admin/get", princ, KADM5_AUTH_DELETE);
+        dprinc_test_fail("admin/none", princ, KADM5_AUTH_DELETE);
+    }
+
+    /* Fails with null handle or principal ent. */
+    dprinc_test_fail(NULL, princ, KADM5_BAD_SERVER_HANDLE);
+    dprinc_test_fail("admin", NULL, EINVAL);
+
+    krb5_free_principal(context, princ);
+}
+
+static void
+gpol_test_succeed(char *user, char *name)
+{
+    void *handle = get_handle(user);
+    kadm5_policy_ent_rec ent;
+
+    check(kadm5_get_policy(handle, name, &ent));
+    assert(strcmp(ent.policy, name) == 0);
+    check(kadm5_free_policy_ent(handle, &ent));
+    free_handle(handle);
+}
+
+static void
+gpol_test_fail(char *user, char *name, krb5_error_code code)
+{
+    void *handle = get_handle(user);
+    kadm5_policy_ent_rec ent;
+
+    check_fail(kadm5_get_policy(handle, name, &ent), code);
+    free_handle(handle);
+}
+
+static void
+test_get_policy()
+{
+    /* Fails with unknown policy. */
+    dpol_test_fail("admin", "unknown-policy", KADM5_UNK_POLICY);
+
+    /* Fails with empty or null policy name or a null handle. */
+    gpol_test_fail("admin", "", KADM5_BAD_POLICY);
+    gpol_test_fail("admin", NULL, EINVAL);
+    gpol_test_fail(NULL, "", KADM5_BAD_SERVER_HANDLE);
+
+    /* Fails over RPC unless "get" ACL is granted or the principal's own policy
+     * is retrieved. */
+    if (rpc) {
+        gpol_test_fail("admin/none", "test-pol", KADM5_AUTH_GET);
+        gpol_test_fail("admin/add", "test-pol", KADM5_AUTH_GET);
+        gpol_test_succeed("admin/get", "test-pol");
+        gpol_test_succeed("user", "minlife-pol");
+        gpol_test_succeed("$user", "minlife-pol");
+    }
+}
+
+static void
+gprinc_test_succeed(char *user, krb5_principal princ)
+{
+    void *handle = get_handle(user);
+    kadm5_principal_ent_rec ent;
+
+    check(kadm5_get_principal(handle, princ, &ent,
+                              KADM5_PRINCIPAL_NORMAL_MASK));
+    assert(krb5_principal_compare(context, ent.principal, princ));
+    check(kadm5_free_principal_ent(handle, &ent));
+    free_handle(handle);
+}
+
+static void
+gprinc_test_fail(char *user, krb5_principal princ, krb5_error_code code)
+{
+    void *handle = get_handle(user);
+    kadm5_principal_ent_rec ent;
+
+    check_fail(kadm5_get_principal(handle, princ, &ent,
+                                   KADM5_PRINCIPAL_NORMAL_MASK), code);
+    free_handle(handle);
+}
+
+static void
+test_get_principal()
+{
+    void *handle;
+    kadm5_principal_ent_rec ent;
+    krb5_principal princ = parse_princ("get-principal-test");
+    krb5_principal admin_princ = parse_princ("admin");
+    krb5_principal admin_none_princ = parse_princ("admin/none");
+    int i;
+
+    /* Fails with unknown principal. */
+    gprinc_test_fail("admin", princ, KADM5_UNK_PRINC);
+
+    create_simple_princ(princ, NULL);
+
+    /* Succeeds with "get" ACL (or local authentication), or operating on
+     * self. */
+    gprinc_test_succeed("admin/none", admin_none_princ);
+    gprinc_test_succeed("$admin", admin_princ);
+    gprinc_test_succeed("admin/get", princ);
+
+    /* Fails over RPC if "get" ACL is not granted, or if we authenticated to
+     * kadmin/changepw and getting another principal entry. */
+    if (rpc) {
+        gprinc_test_fail("$admin", princ, KADM5_AUTH_GET);
+        gprinc_test_fail("admin/none", princ, KADM5_AUTH_GET);
+        gprinc_test_fail("admin/add", princ, KADM5_AUTH_GET);
+        gprinc_test_fail("admin/modify", princ, KADM5_AUTH_GET);
+        gprinc_test_fail("admin/delete", princ, KADM5_AUTH_GET);
+    }
+
+    /* Entry contains no key data or tl-data unless asked for. */
+    handle = get_handle("admin");
+    check(kadm5_get_principal(handle, princ, &ent,
+                              KADM5_PRINCIPAL_NORMAL_MASK));
+    assert(ent.n_tl_data == 0);
+    assert(ent.n_key_data == 0);
+    assert(ent.tl_data == NULL);
+    check(kadm5_free_principal_ent(handle, &ent));
+
+    /* Key data (without the actual keys over RPC) is provided if asked for. */
+    check(kadm5_get_principal(handle, princ, &ent,
+                              KADM5_PRINCIPAL_NORMAL_MASK | KADM5_KEY_DATA));
+    assert(ent.n_key_data == 2);
+    for (i = 0; i < ent.n_key_data; i++)
+        assert(rpc == (ent.key_data[i].key_data_length[0] == 0));
+    check(kadm5_free_principal_ent(handle, &ent));
+    free_handle(handle);
+
+    /* Fails with null handle or principal. */
+    gprinc_test_fail(NULL, princ, KADM5_BAD_SERVER_HANDLE);
+    gprinc_test_fail("admin", NULL, EINVAL);
+
+    delete_princ(princ);
+    krb5_free_principal(context, princ);
+    krb5_free_principal(context, admin_princ);
+    krb5_free_principal(context, admin_none_princ);
+}
+
+static void
+test_init_destroy()
+{
+    krb5_context ctx;
+    kadm5_ret_t ret;
+    kadm5_config_params params;
+    kadm5_principal_ent_rec ent, gent;
+    krb5_principal princ = parse_princ("init-test");
+    krb5_ccache cc;
+    void *handle;
+    char hostname[MAXHOSTNAMELEN];
+    int r;
+
+    memset(&params, 0, sizeof(params));
+    memset(&ent, 0, sizeof(ent));
+    ent.principal = princ;
+
+    r = gethostname(hostname, sizeof(hostname));
+    assert(r == 0);
+
+    /* Destroy fails with no server handle. */
+    check_fail(kadm5_destroy(NULL), KADM5_BAD_SERVER_HANDLE);
+
+    /* Fails with bad structure version mask. */
+    check_fail(kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE, NULL,
+                          0x65432101, api, NULL, &handle),
+               KADM5_BAD_STRUCT_VERSION);
+    check_fail(kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE, NULL,
+                          1, api, NULL, &handle), KADM5_BAD_STRUCT_VERSION);
+
+    /* Fails with too-old or too-new structure version. */
+    check_fail(kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE, NULL,
+                          KADM5_STRUCT_VERSION_MASK, api, NULL, &handle),
+               KADM5_OLD_STRUCT_VERSION);
+    check_fail(kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE, NULL,
+                          KADM5_STRUCT_VERSION_MASK | 0xca, api, NULL,
+                          &handle), KADM5_NEW_STRUCT_VERSION);
+
+    /* Fails with bad API version mask. */
+    check_fail(kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE, NULL,
+                          KADM5_STRUCT_VERSION, 0x65432100, NULL, &handle),
+               KADM5_BAD_API_VERSION);
+    check_fail(kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE, NULL,
+                          KADM5_STRUCT_VERSION, 4, NULL, &handle),
+               KADM5_BAD_API_VERSION);
+
+    /* Fails with too-old or too-new API version.*/
+    ret = kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE, NULL,
+                     KADM5_STRUCT_VERSION, KADM5_API_VERSION_MASK, NULL,
+                     &handle);
+    assert(ret == (rpc ? KADM5_OLD_LIB_API_VERSION :
+                   KADM5_OLD_SERVER_API_VERSION));
+    ret = kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE, NULL,
+                     KADM5_STRUCT_VERSION, KADM5_API_VERSION_MASK | 0xca, NULL,
+                     &handle);
+    assert(ret == (rpc ? KADM5_NEW_LIB_API_VERSION :
+                   KADM5_NEW_SERVER_API_VERSION));
+
+    /* Fails with structure and API version reversed. */
+    check_fail(kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE, NULL,
+                          api, KADM5_STRUCT_VERSION, NULL, &handle),
+               KADM5_BAD_STRUCT_VERSION);
+
+    /* Hardcoded default max lifetime is used when no handle or krb5.conf
+     * setting is given. */
+    handle = get_handle("admin");
+    check(kadm5_create_principal(handle, &ent, KADM5_PRINCIPAL, "pw"));
+    check(kadm5_get_principal(handle, princ, &gent,
+                              KADM5_PRINCIPAL_NORMAL_MASK));
+    assert(gent.max_life == KRB5_KDB_MAX_LIFE);
+    check(kadm5_delete_principal(handle, princ));
+    check(kadm5_free_principal_ent(handle, &gent));
+    free_handle(handle);
+
+    /* Fails with configured unknown realm.  Do these tests in separate krb5
+     * contexts since the realm setting sticks to the context. */
+    check(kadm5_init_krb5_context(&ctx));
+    params.realm = "";
+    params.mask = KADM5_CONFIG_REALM;
+    ret = kadm5_init(ctx, "admin", "admin", KADM5_ADMIN_SERVICE, &params,
+                     KADM5_STRUCT_VERSION, api, NULL, &handle);
+    assert(ret == (rpc ? KADM5_MISSING_KRB5_CONF_PARAMS : ENOENT));
+    krb5_free_context(ctx);
+
+    check(kadm5_init_krb5_context(&ctx));
+    params.realm = "@";
+    ret = kadm5_init(ctx, "admin", "admin", KADM5_ADMIN_SERVICE, &params,
+                     KADM5_STRUCT_VERSION, api, NULL, &handle);
+    assert(ret == (rpc ? KADM5_MISSING_KRB5_CONF_PARAMS : ENOENT));
+    krb5_free_context(ctx);
+
+    check(kadm5_init_krb5_context(&ctx));
+    params.realm = "BAD.REALM";
+    ret = kadm5_init(ctx, "admin", "admin", KADM5_ADMIN_SERVICE, &params,
+                     KADM5_STRUCT_VERSION, api, NULL, &handle);
+    assert(ret == (rpc ? KADM5_MISSING_KRB5_CONF_PARAMS : ENOENT));
+    krb5_free_context(ctx);
+
+    /* Succeeds with explicit client realm and configured realm. */
+    check(kadm5_init_krb5_context(&ctx));
+    params.realm = "KRBTEST.COM";
+    check(kadm5_init(ctx, "admin at KRBTEST.COM", "admin", KADM5_ADMIN_SERVICE,
+                     &params, KADM5_STRUCT_VERSION, api, NULL, &handle));
+    check(kadm5_destroy(handle));
+    krb5_free_context(ctx);
+
+    /* Succeeds with explicit client realm. */
+    check(kadm5_init(context, "admin at KRBTEST.COM", "admin",
+                     KADM5_ADMIN_SERVICE, NULL, KADM5_STRUCT_VERSION, api,
+                     NULL, &handle));
+    check(kadm5_destroy(handle));
+
+
+    if (rpc) {
+        check(krb5_cc_default(context, &cc));
+
+        /* Succeeds with configured host and port. */
+        params.admin_server = hostname;
+        params.kadmind_port = 61001;
+        params.mask = KADM5_CONFIG_ADMIN_SERVER | KADM5_CONFIG_KADMIND_PORT;
+        check(kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE,
+                         &params, KADM5_STRUCT_VERSION, api, NULL, &handle));
+        check(kadm5_destroy(handle));
+
+        /* Fails with wrong configured port. */
+        params.kadmind_port = 4;
+        check_fail(kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE,
+                              &params, KADM5_STRUCT_VERSION, api, NULL,
+                              &handle), KADM5_RPC_ERROR);
+
+        /* Fails with non-resolving hostname. */
+        params.admin_server = "does.not.exist";
+        params.mask = KADM5_CONFIG_ADMIN_SERVER;
+        check_fail(kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE,
+                              &params, KADM5_STRUCT_VERSION, api, NULL,
+                              &handle), KADM5_CANT_RESOLVE);
+
+        /* Fails with uninitialized cache. */
+        check_fail(kadm5_init_with_creds(context, "admin", cc,
+                                         KADM5_ADMIN_SERVICE, NULL,
+                                         KADM5_STRUCT_VERSION, api, NULL,
+                                         &handle), KRB5_FCC_NOFILE);
+
+        /* Succeeds with cache containing kadmin/admin cred. */
+        kinit(cc, "admin", "admin", KADM5_ADMIN_SERVICE);
+        check(kadm5_init_with_creds(context, "admin", cc, KADM5_ADMIN_SERVICE,
+                                    NULL, KADM5_STRUCT_VERSION, api, NULL,
+                                    &handle));
+        check(kadm5_destroy(handle));
+
+        /* Succeeds with cache containing kadmin/changepw cred. */
+        kinit(cc, "admin", "admin", KADM5_CHANGEPW_SERVICE);
+        check(kadm5_init_with_creds(context, "admin", cc,
+                                    KADM5_CHANGEPW_SERVICE, NULL,
+                                    KADM5_STRUCT_VERSION, api, NULL, &handle));
+        check(kadm5_destroy(handle));
+
+        /* Fails with cache containing only a TGT. */
+        kinit(cc, "admin", "admin", NULL);
+        check_fail(kadm5_init_with_creds(context, "admin", cc,
+                                         KADM5_ADMIN_SERVICE, NULL,
+                                         KADM5_STRUCT_VERSION, api, NULL,
+                                         &handle), KRB5_CC_NOTFOUND);
+
+        /* Fails authenticating to non-kadmin princ. */
+        check_fail(kadm5_init(context, "admin", "admin", "user", NULL,
+                              KADM5_STRUCT_VERSION, api, NULL, &handle),
+                   KADM5_RPC_ERROR);
+
+        /* Fails authenticating to nonexistent princ. */
+        check_fail(kadm5_init(context, "admin", "admin", "noexist", NULL,
+                              KADM5_STRUCT_VERSION, api, NULL, &handle),
+                   KADM5_SECURE_PRINC_MISSING);
+
+        /* Fails authenticating to client princ (which is non-kadmin). */
+        check_fail(kadm5_init(context, "admin", "admin", "admin", NULL,
+                              KADM5_STRUCT_VERSION, api, NULL, &handle),
+                   KADM5_RPC_ERROR);
+
+        /* Fails with wrong password. */
+        check_fail(kadm5_init(context, "admin", "wrong", KADM5_ADMIN_SERVICE,
+                              NULL, KADM5_STRUCT_VERSION, api, NULL, &handle),
+                   KADM5_BAD_PASSWORD);
+
+        /* Fails with null client name. */
+        check_fail(kadm5_init(context, NULL, "admin", KADM5_ADMIN_SERVICE,
+                              NULL, KADM5_STRUCT_VERSION, api, NULL, &handle),
+                   EINVAL);
+
+        /* Fails with nonexistent client name. */
+        check_fail(kadm5_init(context, "noexist", "admin", KADM5_ADMIN_SERVICE,
+                              NULL, KADM5_STRUCT_VERSION, api, NULL, &handle),
+                   KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN);
+
+        /* Fails with nonexistent client name with explicit realm. */
+        check_fail(kadm5_init(context, "noexist at KRBTEST.COM", "admin",
+                              KADM5_ADMIN_SERVICE, NULL, KADM5_STRUCT_VERSION,
+                              api, NULL, &handle),
+                   KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN);
+
+        /* Fails with nonexistent client name with unknown realm. */
+        check_fail(kadm5_init(context, "noexist at BAD.REALM", "admin",
+                              KADM5_ADMIN_SERVICE, NULL, KADM5_STRUCT_VERSION,
+                              api, NULL, &handle), KRB5_REALM_UNKNOWN);
+
+        /* Fails with known name but unknown realm. */
+        check_fail(kadm5_init(context, "admin at BAD.REALM", "admin",
+                              KADM5_ADMIN_SERVICE, NULL, KADM5_STRUCT_VERSION,
+                              api, NULL, &handle), KRB5_REALM_UNKNOWN);
+
+        check(krb5_cc_destroy(context, cc));
+    } else {
+        /* Fails with nonexistent stash file. */
+        params.stash_file = "does/not/exist";
+        params.mask = KADM5_CONFIG_STASH_FILE;
+        check_fail(kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE,
+                              &params, KADM5_STRUCT_VERSION, api, NULL,
+                              &handle), KRB5_KDB_CANTREAD_STORED);
+
+        /* Uses configured defaults for principal creation. */
+        params.max_life = 10;
+        params.max_rlife = 20;
+        params.expiration = 30;
+        params.num_keysalts = 0;
+        params.mask = KADM5_CONFIG_MAX_LIFE | KADM5_CONFIG_MAX_RLIFE |
+            KADM5_CONFIG_EXPIRATION | KADM5_CONFIG_ENCTYPES;
+        check(kadm5_init(context, "admin", "admin", KADM5_ADMIN_SERVICE,
+                         &params, KADM5_STRUCT_VERSION, api, NULL, &handle));
+        check(kadm5_create_principal(handle, &ent, KADM5_PRINCIPAL, "pw"));
+        check(kadm5_get_principal(handle, princ, &gent,
+                                  KADM5_PRINCIPAL_NORMAL_MASK |
+                                  KADM5_KEY_DATA));
+        assert(gent.max_life == 10);
+        assert(gent.max_renewable_life == 20);
+        assert(gent.princ_expire_time == 30);
+        assert(gent.n_key_data == 0);
+        check(kadm5_delete_principal(handle, princ));
+        check(kadm5_free_principal_ent(handle, &gent));
+        check(kadm5_destroy(handle));
+
+        /* Succeeds with incorrect password using local auth. */
+        check(kadm5_init(context, "admin", "wrong", KADM5_ADMIN_SERVICE, NULL,
+                         KADM5_STRUCT_VERSION, api, NULL, &handle));
+        check(kadm5_destroy(handle));
+
+        /* Succeeds with null service using local auth. */
+        check(kadm5_init(context, "admin", "admin", NULL, NULL,
+                         KADM5_STRUCT_VERSION, api, NULL, &handle));
+        check(kadm5_destroy(handle));
+
+        /* Succeeds with nonexistent, non-kadmin service using local auth. */
+        check(kadm5_init(context, "admin", "admin", "foobar", NULL,
+                         KADM5_STRUCT_VERSION, api, NULL, &handle));
+        check(kadm5_destroy(handle));
+    }
+
+    krb5_free_principal(context, princ);
+}
+
+static void
+mpol_test_fail(char *user, kadm5_policy_ent_t ent, uint32_t mask,
+               krb5_error_code code)
+{
+    void *handle = get_handle(user);
+
+    check_fail(kadm5_modify_policy(handle, ent, mask), code);
+    free_handle(handle);
+}
+
+static void
+mpol_test_compare(void *handle, kadm5_policy_ent_t ent, uint32_t mask)
+{
+    mpol_test_fail(handle, ent, mask, 0);
+    compare_policy(ent, mask);
+}
+
+static void
+test_modify_policy()
+{
+    kadm5_policy_ent_rec ent;
+
+    memset(&ent, 0, sizeof(ent));
+    ent.policy = "modify-policy-test";
+    create_simple_policy(ent.policy);
+
+    /* pw_min_life = 0 and pw_min_life != 0 */
+    mpol_test_compare("admin", &ent, KADM5_PW_MIN_LIFE);
+    ent.pw_min_life = 32;
+    mpol_test_compare("admin", &ent, KADM5_PW_MIN_LIFE);
+
+    /* pw_max_life = 0 and pw_max_life != 0 */
+    mpol_test_compare("admin", &ent, KADM5_PW_MAX_LIFE);
+    ent.pw_max_life = 32;
+    mpol_test_compare("admin", &ent, KADM5_PW_MAX_LIFE);
+
+    /* pw_min_length = 0 (rejected) and pw_min_length != 0 */
+    mpol_test_fail("admin", &ent, KADM5_PW_MIN_LENGTH, KADM5_BAD_LENGTH);
+    ent.pw_min_length = 8;
+    mpol_test_compare("admin", &ent, KADM5_PW_MIN_LENGTH);
+
+    /* pw_min_classes = 0 (rejected), 1, 5, 6 (rejected) */
+    mpol_test_fail("admin", &ent, KADM5_PW_MIN_CLASSES, KADM5_BAD_CLASS);
+    ent.pw_min_classes = 1;
+    mpol_test_compare("admin", &ent, KADM5_PW_MIN_CLASSES);
+    ent.pw_min_classes = 5;
+    mpol_test_compare("admin", &ent, KADM5_PW_MIN_CLASSES);
+    ent.pw_min_classes = 6;
+    mpol_test_fail("admin", &ent, KADM5_PW_MIN_CLASSES, KADM5_BAD_CLASS);
+
+    /* pw_history_num = 0 (rejected), 1, 10 */
+    mpol_test_fail("admin", &ent, KADM5_PW_HISTORY_NUM, KADM5_BAD_HISTORY);
+    ent.pw_history_num = 1;
+    mpol_test_compare("admin", &ent, KADM5_PW_HISTORY_NUM);
+    ent.pw_history_num = 10;
+    mpol_test_compare("admin", &ent, KADM5_PW_HISTORY_NUM);
+
+    if (api >= KADM5_API_VERSION_3) {
+        ent.pw_max_fail = 2;
+        mpol_test_compare("admin", &ent, KADM5_PW_MAX_FAILURE);
+        ent.pw_failcnt_interval = 90;
+        mpol_test_compare("admin", &ent, KADM5_PW_FAILURE_COUNT_INTERVAL);
+        ent.pw_lockout_duration = 180;
+        mpol_test_compare("admin", &ent, KADM5_PW_LOCKOUT_DURATION);
+    }
+
+    /* Fails over RPC if "modify" ACL is not granted, or if we authenticated to
+     * kadmin/changepw. */
+    if (rpc) {
+        mpol_test_fail("$admin", &ent, KADM5_PW_MAX_LIFE, KADM5_AUTH_MODIFY);
+        mpol_test_fail("admin/none", &ent, KADM5_PW_MAX_LIFE,
+                       KADM5_AUTH_MODIFY);
+        mpol_test_fail("admin/get", &ent, KADM5_PW_MAX_LIFE,
+                       KADM5_AUTH_MODIFY);
+        mpol_test_compare("admin/modify", &ent, KADM5_PW_MAX_LIFE);
+    }
+
+    delete_policy(ent.policy);
+
+    /* Fails with empty or null policy name. */
+    ent.policy = NULL;
+    mpol_test_fail("admin", &ent, KADM5_PW_MAX_LIFE, EINVAL);
+    ent.policy = "";
+    mpol_test_fail("admin", &ent, KADM5_PW_MAX_LIFE, KADM5_BAD_POLICY);
+
+    /* Fails with null handle or policy ent. */
+    mpol_test_fail(NULL, &ent, KADM5_PW_MAX_LIFE, KADM5_BAD_SERVER_HANDLE);
+    mpol_test_fail("admin", NULL, KADM5_PW_MAX_LIFE, EINVAL);
+}
+
+static void
+mprinc_test_fail(char *user, kadm5_principal_ent_t ent, uint32_t mask,
+                 krb5_error_code code)
+{
+    void *handle = get_handle(user);
+
+    check_fail(kadm5_modify_principal(handle, ent, mask), code);
+    free_handle(handle);
+}
+
+static void
+mprinc_test_compare(char *user, kadm5_principal_ent_t ent, uint32_t mask)
+{
+    mprinc_test_fail(user, ent, mask, 0);
+    compare_princ(ent, mask);
+}
+
+static void
+test_modify_principal()
+{
+    void *handle;
+    krb5_principal princ = parse_princ("modify-principal-test");
+    kadm5_principal_ent_rec ent;
+    krb5_tl_data tl = { NULL, 1, 1, (uint8_t *)"x" };
+    krb5_tl_data tl2 = { NULL, 999, 6, (uint8_t *)"foobar" };
+
+    memset(&ent, 0, sizeof(ent));
+    ent.principal = princ;
+
+    /* Fails with unknown principal. */
+    mprinc_test_fail("admin", &ent, KADM5_KVNO, KADM5_UNK_PRINC);
+
+    create_simple_princ(princ, NULL);
+
+    /* Fails with prohibited mask bit or tl-data type. */
+    mprinc_test_fail("admin", &ent, KADM5_AUX_ATTRIBUTES, KADM5_BAD_MASK);
+    mprinc_test_fail("admin", &ent, KADM5_KEY_DATA, KADM5_BAD_MASK);
+    mprinc_test_fail("admin", &ent, KADM5_LAST_FAILED, KADM5_BAD_MASK);
+    mprinc_test_fail("admin", &ent, KADM5_LAST_SUCCESS, KADM5_BAD_MASK);
+    mprinc_test_fail("admin", &ent, KADM5_LAST_PWD_CHANGE, KADM5_BAD_MASK);
+    mprinc_test_fail("admin", &ent, KADM5_MKVNO, KADM5_BAD_MASK);
+    mprinc_test_fail("admin", &ent, KADM5_MOD_NAME, KADM5_BAD_MASK);
+    mprinc_test_fail("admin", &ent, KADM5_MOD_TIME, KADM5_BAD_MASK);
+    mprinc_test_fail("admin", &ent, KADM5_PRINCIPAL, KADM5_BAD_MASK);
+
+    /* Fails with tl-data type below 256. */
+    ent.n_tl_data = 1;
+    ent.tl_data = &tl;
+    mprinc_test_fail("admin", &ent, KADM5_TL_DATA, KADM5_BAD_TL_TYPE);
+
+    /* Fails with fail_auth_count other than zero. */
+    ent.fail_auth_count = 1234;
+    mprinc_test_fail("admin", &ent, KADM5_FAIL_AUTH_COUNT,
+                     KADM5_BAD_SERVER_PARAMS);
+    ent.fail_auth_count = 0;
+
+    /* Succeeds with zero values of various fields. */
+    mprinc_test_compare("admin", &ent, KADM5_PW_EXPIRATION);
+    mprinc_test_compare("admin", &ent, KADM5_MAX_LIFE);
+    mprinc_test_compare("admin", &ent, KADM5_MAX_RLIFE);
+    mprinc_test_compare("admin", &ent, KADM5_FAIL_AUTH_COUNT);
+    mprinc_test_compare("admin/modify", &ent, KADM5_PRINC_EXPIRE_TIME);
+    mprinc_test_compare("admin", &ent, KADM5_POLICY_CLR);
+
+    /* Setting a policy causes a pw_expiration computation.  Explicit
+     * PW_EXPIRATION overrides the policy. */
+    ent.pw_expiration = 1234;
+    mprinc_test_compare("admin", &ent, KADM5_PW_EXPIRATION);
+    ent.policy = "dict-only-pol";
+    mprinc_test_compare("admin", &ent, KADM5_POLICY);
+    ent.policy = "test-pol";
+    mprinc_test_compare("admin", &ent, KADM5_POLICY);
+    ent.pw_expiration = 999999999;
+    mprinc_test_compare("admin", &ent, KADM5_PW_EXPIRATION);
+    mprinc_test_compare("admin", &ent, KADM5_POLICY_CLR);
+
+    /* Succeeds with non-zero values of various fields. */
+    ent.princ_expire_time = 1234;
+    mprinc_test_compare("admin", &ent, KADM5_PRINC_EXPIRE_TIME);
+    ent.attributes = KRB5_KDB_DISALLOW_ALL_TIX;
+    mprinc_test_compare("admin", &ent, KADM5_ATTRIBUTES);
+    ent.attributes = KRB5_KDB_REQUIRES_PWCHANGE;
+    mprinc_test_compare("admin", &ent, KADM5_ATTRIBUTES);
+    ent.attributes = KRB5_KDB_DISALLOW_TGT_BASED;
+    mprinc_test_compare("admin", &ent, KADM5_ATTRIBUTES);
+    ent.max_life = 3456;
+    mprinc_test_compare("admin", &ent, KADM5_MAX_LIFE);
+    ent.kvno = 7;
+    mprinc_test_compare("admin", &ent, KADM5_KVNO);
+
+    /* Fails over RPC if "modify" ACL is not granted, or if we authenticated to
+     * kadmin/changepw. */
+    if (rpc) {
+        mprinc_test_fail("$admin", &ent, KADM5_KVNO, KADM5_AUTH_MODIFY);
+        mprinc_test_fail("admin/none", &ent, KADM5_KVNO, KADM5_AUTH_MODIFY);
+        mprinc_test_fail("admin/get", &ent, KADM5_KVNO, KADM5_AUTH_MODIFY);
+        mprinc_test_fail("admin/add", &ent, KADM5_KVNO, KADM5_AUTH_MODIFY);
+        mprinc_test_fail("admin/delete", &ent, KADM5_KVNO, KADM5_AUTH_MODIFY);
+    }
+
+    /* tl-data of type > 255 is accepted. */
+    handle = get_handle("admin");
+    ent.max_renewable_life = 88;
+    ent.tl_data = &tl2;
+    check(kadm5_modify_principal(handle, &ent,
+                                 KADM5_MAX_RLIFE | KADM5_TL_DATA));
+    memset(&ent, 0, sizeof(ent));
+    check(kadm5_get_principal(handle, princ, &ent,
+                              KADM5_PRINCIPAL_NORMAL_MASK | KADM5_TL_DATA));
+    assert(ent.max_renewable_life == 88);
+    assert(ent.n_tl_data == 1);
+    assert(ent.tl_data->tl_data_type == tl2.tl_data_type);
+    assert(ent.tl_data->tl_data_length == tl2.tl_data_length);
+    assert(memcmp(ent.tl_data->tl_data_contents, tl2.tl_data_contents,
+                  tl2.tl_data_length) == 0);
+    check(kadm5_free_principal_ent(handle, &ent));
+    free_handle(handle);
+
+    /* Fails with null handle or principal ent. */
+    mprinc_test_fail(NULL, &ent, KADM5_KVNO, KADM5_BAD_SERVER_HANDLE);
+    mprinc_test_fail("admin", NULL, KADM5_KVNO, EINVAL);
+
+    delete_princ(princ);
+    krb5_free_principal(context, princ);
+}
+
+static void
+rnd_test_fail(char *user, krb5_principal princ, krb5_error_code code)
+{
+    void *handle = get_handle(user);
+
+    check_fail(kadm5_randkey_principal(handle, princ, NULL, NULL), code);
+    free_handle(handle);
+}
+
+static void
+rnd_test_succeed(char *user, krb5_principal princ)
+{
+    rnd_test_fail(user, princ, 0);
+}
+
+static void
+test_randkey()
+{
+    void *handle;
+    krb5_principal princ = parse_princ("randkey-principal-test");
+    krb5_principal user_princ = parse_princ("user");
+    krb5_principal admin_princ = parse_princ("admin");
+    kadm5_principal_ent_rec ent;
+    krb5_keyblock *keys;
+    int n_keys, i;
+
+    create_simple_princ(princ, NULL);
+
+    /* Check kvno and enctypes after randkey. */
+    handle = get_handle("admin");
+    check(kadm5_randkey_principal(handle, princ, &keys, &n_keys));
+    check(kadm5_get_principal(handle, princ, &ent, KADM5_KEY_DATA));
+    compare_key_data(&ent, default_supported_enctypes);
+    assert(ent.key_data[0].key_data_kvno == 2);
+    assert(n_keys == ent.n_key_data);
+    for (i = 0; i < n_keys; i++)
+        krb5_free_keyblock_contents(context, &keys[i]);
+    free(keys);
+    check(kadm5_free_principal_ent(handle, &ent));
+    free_handle(handle);
+
+    /*
+     * Fails over RPC if "change" ACL is not granted, or if we authenticated to
+     * kadmin/changepw and are changing another principal's password, or for
+     * self-service if the policy minimum life has not elapsed since the last
+     * key change.
+     */
+    if (rpc) {
+        rnd_test_fail("$admin", user_princ, KADM5_AUTH_CHANGEPW);
+        rnd_test_fail("admin/none", user_princ, KADM5_AUTH_CHANGEPW);
+        rnd_test_fail("admin/delete", user_princ, KADM5_AUTH_CHANGEPW);
+        rnd_test_succeed("admin/modify", user_princ);
+        cpw_test_succeed("admin", user_princ, USER_PASSWORD);
+        rnd_test_fail("user", user_princ, KADM5_PASS_TOOSOON);
+        rnd_test_fail("$user", user_princ, KADM5_PASS_TOOSOON);
+    }
+
+    /* Succeeds with change privilege in spite of policy minimum life. */
+    rnd_test_succeed("admin/modify", user_princ);
+    cpw_test_succeed("admin", user_princ, USER_PASSWORD);
+
+    /* Succeeds for self-service when authenticating to kadmin/changepw. */
+    handle = get_handle("$admin");
+    check(kadm5_randkey_principal(handle, admin_princ, NULL, NULL));
+    check(kadm5_chpass_principal(handle, admin_princ, ADMIN_PASSWORD));
+    free_handle(handle);
+
+    /* Fails with null handle or principal name. */
+    rnd_test_fail(NULL, princ, KADM5_BAD_SERVER_HANDLE);
+    rnd_test_fail("admin", NULL, EINVAL);
+
+    delete_princ(princ);
+    krb5_free_principal(context, princ);
+    krb5_free_principal(context, user_princ);
+    krb5_free_principal(context, admin_princ);
+}
+
+int
+main(int argc, char **argv)
+{
+    assert(argc == 2);
+    rpc = (strcmp(argv[1], "clnt") == 0);
+
+    check(kadm5_init_krb5_context(&context));
+
+    api = KADM5_API_VERSION_2;
+    test_create_policy();
+    test_get_policy();
+    test_modify_policy();
+
+    api = KADM5_API_VERSION_4;
+    test_chpass();
+    test_create_policy();
+    test_create_principal();
+    test_delete_policy();
+    test_delete_principal();
+    test_get_policy();
+    test_get_principal();
+    test_init_destroy();
+    test_modify_policy();
+    test_modify_principal();
+    test_randkey();
+
+    krb5_free_context(context);
+
+    return 0;
+}
diff --git a/src/lib/kadm5/t_kadm5.py b/src/lib/kadm5/t_kadm5.py
new file mode 100644
index 0000000..c218b67
--- /dev/null
+++ b/src/lib/kadm5/t_kadm5.py
@@ -0,0 +1,45 @@
+from k5test import *
+
+# Specify a supported_enctypes so the chpass tests know what to expect.
+supported_enctypes = 'aes256-cts:normal aes128-cts:normal'
+conf = {'realms': {'$realm': {'supported_enctypes': supported_enctypes}}}
+realm = K5Realm(create_user=False, create_host=False, kdc_conf=conf)
+
+with open(os.path.join(realm.testdir, 'acl'), 'w') as f:
+    f.write('''
+admin                   admcilse
+admin/get               il
+admin/modify            mc
+admin/delete            d
+admin/add               a
+admin/rename            adil
+''')
+
+with open(os.path.join(realm.testdir, 'dictfile'), 'w') as f:
+    f.write('''
+Abyssinia
+Discordianism
+foo
+''')
+
+realm.start_kadmind()
+
+realm.run([kadminl, 'addpol', '-maxlife', '10000s', '-minlength', '8',
+           '-minclasses', '2', '-maxfailure', '2',
+           '-failurecountinterval', '90s', '-lockoutduration', '180s',
+           'test-pol'])
+realm.run([kadminl, 'addpol', '-minlife', '10s', 'minlife-pol'])
+realm.run([kadminl, 'addpol', 'dict-only-pol'])
+realm.run([kadminl, 'addprinc', '-pw', 'admin', 'admin'])
+realm.run([kadminl, 'addprinc', '-pw', 'admin', 'admin/get'])
+realm.run([kadminl, 'addprinc', '-pw', 'admin', 'admin/modify'])
+realm.run([kadminl, 'addprinc', '-pw', 'admin', 'admin/delete'])
+realm.run([kadminl, 'addprinc', '-pw', 'admin', 'admin/add'])
+realm.run([kadminl, 'addprinc', '-pw', 'admin', 'admin/rename'])
+realm.run([kadminl, 'addprinc', '-pw', 'admin', 'admin/none'])
+realm.run([kadminl, 'addprinc', '-pw', 'us3r', '-policy', 'minlife-pol',
+           'user'])
+
+realm.run(['./t_kadm5srv', 'srv'])
+realm.run(['./t_kadm5clnt', 'clnt'])
+success('kadm5 API tests')
diff --git a/src/util/k5test.py b/src/util/k5test.py
index 54889ed..c26bc69 100644
--- a/src/util/k5test.py
+++ b/src/util/k5test.py
@@ -1277,7 +1277,7 @@ _default_kdc_conf = {
             'iprop_port': '$port4',
             'key_stash_file': '$testdir/stash',
             'acl_file': '$testdir/acl',
-            'dictfile': '$testdir/dictfile',
+            'dict_file': '$testdir/dictfile',
             'kadmind_port': '$port1',
             'kpasswd_port': '$port2',
             'kdc_listen': '$port0',


More information about the cvs-krb5 mailing list