kadmin: fix leak
[metze/heimdal/wip.git] / kadmin / server.c
index bdf1f5350441e44895c5d8884ef6252e7b22809d..cc82644c6add9d602843ae9eb57155d25a3874dc 100644 (file)
 #include "kadmin_locl.h"
 #include <krb5-private.h>
 
-RCSID("$Id$");
+static kadm5_ret_t check_aliases(kadm5_server_context *,
+                                 kadm5_principal_ent_rec *,
+                                 kadm5_principal_ent_rec *);
 
 static kadm5_ret_t
-kadmind_dispatch(void *kadm_handle, krb5_boolean initial,
+kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
                 krb5_data *in, krb5_data *out)
 {
     kadm5_ret_t ret;
-    int32_t cmd, mask, tmp;
-    kadm5_server_context *context = kadm_handle;
+    int32_t cmd, mask, kvno, tmp;
+    kadm5_server_context *contextp = kadm_handlep;
     char client[128], name[128], name2[128];
-    char *op = "";
-    krb5_principal princ, princ2;
-    kadm5_principal_ent_rec ent;
-    char *password, *expression;
+    const char *op = "";
+    krb5_principal princ = NULL, princ2 = NULL;
+    kadm5_principal_ent_rec ent, ent_prev;
+    char *password = NULL, *expression;
     krb5_keyblock *new_keys;
+    krb5_key_salt_tuple *ks_tuple = NULL;
+    int keepold = FALSE;
+    int n_ks_tuple = 0;
     int n_keys;
     char **princs;
     int n_princs;
+    int keys_ok = 0;
     krb5_storage *sp;
+    int len;
 
-    krb5_unparse_name_fixed(context->context, context->caller,
+    krb5_unparse_name_fixed(contextp->context, contextp->caller,
                            client, sizeof(client));
 
     sp = krb5_storage_from_data(in);
-    if (sp == NULL)
-       krb5_errx(context->context, 1, "out of memory");
+    if (sp == NULL) {
+       ret = krb5_enomem(contextp->context);
+       goto fail;
+    }
 
     krb5_ret_int32(sp, &cmd);
     switch(cmd){
@@ -69,27 +78,63 @@ kadmind_dispatch(void *kadm_handle, krb5_boolean initial,
        if(ret)
            goto fail;
        ret = krb5_ret_int32(sp, &mask);
-       if(ret){
-           krb5_free_principal(context->context, princ);
+       if (ret)
            goto fail;
-       }
+
        mask |= KADM5_PRINCIPAL;
-       krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
-       krb5_warnx(context->context, "%s: %s %s", client, op, name);
-       ret = _kadm5_acl_check_permission(context, KADM5_PRIV_GET, princ);
-       if(ret){
-           krb5_free_principal(context->context, princ);
+       krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
+       krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
+
+        /* If the caller doesn't have KADM5_PRIV_GET, we're done. */
+       ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_GET, princ);
+        if (ret)
            goto fail;
-       }
-       ret = kadm5_get_principal(kadm_handle, princ, &ent, mask);
+
+        /* Then check to see if it is ok to return keys */
+        if ((mask & KADM5_KEY_DATA) != 0) {
+            ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_GET_KEYS,
+                                              princ);
+            if (ret == 0) {
+                keys_ok = 1;
+            } else if ((mask == (KADM5_PRINCIPAL|KADM5_KEY_DATA)) ||
+                       (mask == (KADM5_PRINCIPAL|KADM5_KVNO|KADM5_KEY_DATA))) {
+                /*
+                 * Requests for keys will get bogus keys, which is useful if
+                 * the client just wants to see what (kvno, enctype)s the
+                 * principal has keys for, but terrible if the client wants to
+                 * write the keys into a keytab or modify the principal and
+                 * write the bogus keys back to the server.
+                 *
+                 * We use a heuristic to detect which case we're handling here.
+                 * If the client only asks for the flags in the above
+                 * condition, then it's very likely a kadmin ext_keytab,
+                 * add_enctype, or other request that should not see bogus
+                 * keys.  We deny them.
+                 *
+                 * The kadmin get command can be coaxed into making a request
+                 * with the same mask.  But the default long and terse output
+                 * modes request other things too, so in all likelihood this
+                 * heuristic will not hurt any kadmin get uses.
+                 */
+                goto fail;
+            }
+        }
+
+       ret = kadm5_get_principal(kadm_handlep, princ, &ent, mask);
        krb5_storage_free(sp);
        sp = krb5_storage_emem();
+        if (sp == NULL) {
+            ret = krb5_enomem(contextp->context);
+            goto fail;
+        }
        krb5_store_int32(sp, ret);
-       if(ret == 0){
-           kadm5_store_principal_ent(sp, &ent);
-           kadm5_free_principal_ent(kadm_handle, &ent);
+       if (ret == 0){
+           if (keys_ok)
+               kadm5_store_principal_ent(sp, &ent);
+           else
+               kadm5_store_principal_ent_nokeys(sp, &ent);
+           kadm5_free_principal_ent(kadm_handlep, &ent);
        }
-       krb5_free_principal(context->context, princ);
        break;
     }
     case kadm_delete:{
@@ -97,17 +142,24 @@ kadmind_dispatch(void *kadm_handle, krb5_boolean initial,
        ret = krb5_ret_principal(sp, &princ);
        if(ret)
            goto fail;
-       krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
-       krb5_warnx(context->context, "%s: %s %s", client, op, name);
-       ret = _kadm5_acl_check_permission(context, KADM5_PRIV_DELETE, princ);
-       if(ret){
-           krb5_free_principal(context->context, princ);
+       krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
+       krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
+       ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_DELETE, princ);
+       if (ret)
            goto fail;
-       }
-       ret = kadm5_delete_principal(kadm_handle, princ);
-       krb5_free_principal(context->context, princ);
+
+        /*
+         * There's no need to check that the caller has permission to
+         * delete the victim principal's aliases.
+         */
+
+       ret = kadm5_delete_principal(kadm_handlep, princ);
        krb5_storage_free(sp);
        sp = krb5_storage_emem();
+        if (sp == NULL) {
+            ret = krb5_enomem(contextp->context);
+            goto fail;
+        }
        krb5_store_int32(sp, ret);
        break;
     }
@@ -118,32 +170,43 @@ kadmind_dispatch(void *kadm_handle, krb5_boolean initial,
            goto fail;
        ret = krb5_ret_int32(sp, &mask);
        if(ret){
-           kadm5_free_principal_ent(context->context, &ent);
+           kadm5_free_principal_ent(kadm_handlep, &ent);
            goto fail;
        }
        ret = krb5_ret_string(sp, &password);
        if(ret){
-           kadm5_free_principal_ent(context->context, &ent);
+           kadm5_free_principal_ent(kadm_handlep, &ent);
            goto fail;
        }
-       krb5_unparse_name_fixed(context->context, ent.principal,
+       krb5_unparse_name_fixed(contextp->context, ent.principal,
                                name, sizeof(name));
-       krb5_warnx(context->context, "%s: %s %s", client, op, name);
-       ret = _kadm5_acl_check_permission(context, KADM5_PRIV_ADD,
+       krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
+       ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_ADD,
                                          ent.principal);
        if(ret){
-           kadm5_free_principal_ent(context->context, &ent);
-           memset(password, 0, strlen(password));
-           free(password);
+           kadm5_free_principal_ent(kadm_handlep, &ent);
            goto fail;
        }
-       ret = kadm5_create_principal(kadm_handle, &ent,
+        if ((mask & KADM5_TL_DATA)) {
+            /*
+             * Also check that the caller can create the aliases, if the
+             * new principal has any.
+             */
+            ret = check_aliases(contextp, &ent, NULL);
+            if (ret) {
+                kadm5_free_principal_ent(kadm_handlep, &ent);
+                goto fail;
+            }
+        }
+       ret = kadm5_create_principal(kadm_handlep, &ent,
                                     mask, password);
-       kadm5_free_principal_ent(kadm_handle, &ent);
-       memset(password, 0, strlen(password));
-       free(password);
+       kadm5_free_principal_ent(kadm_handlep, &ent);
        krb5_storage_free(sp);
        sp = krb5_storage_emem();
+        if (sp == NULL) {
+            ret = krb5_enomem(contextp->context);
+            goto fail;
+        }
        krb5_store_int32(sp, ret);
        break;
     }
@@ -154,111 +217,162 @@ kadmind_dispatch(void *kadm_handle, krb5_boolean initial,
            goto fail;
        ret = krb5_ret_int32(sp, &mask);
        if(ret){
-           kadm5_free_principal_ent(context, &ent);
+           kadm5_free_principal_ent(contextp, &ent);
            goto fail;
        }
-       krb5_unparse_name_fixed(context->context, ent.principal,
+       krb5_unparse_name_fixed(contextp->context, ent.principal,
                                name, sizeof(name));
-       krb5_warnx(context->context, "%s: %s %s", client, op, name);
-       ret = _kadm5_acl_check_permission(context, KADM5_PRIV_MODIFY,
+       krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
+       ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_MODIFY,
                                          ent.principal);
        if(ret){
-           kadm5_free_principal_ent(context, &ent);
+           kadm5_free_principal_ent(contextp, &ent);
            goto fail;
        }
-       ret = kadm5_modify_principal(kadm_handle, &ent, mask);
-       kadm5_free_principal_ent(kadm_handle, &ent);
+        if ((mask & KADM5_TL_DATA)) {
+            /*
+             * Also check that the caller can create aliases that are in
+             * the new entry but not the old one.  There's no need to
+             * check that the caller can delete aliases it wants to
+             * drop.  See also handling of rename.
+             */
+            ret = kadm5_get_principal(kadm_handlep, ent.principal, &ent_prev, mask);
+            if (ret) {
+                kadm5_free_principal_ent(contextp, &ent);
+                goto fail;
+            }
+            ret = check_aliases(contextp, &ent, &ent_prev);
+            kadm5_free_principal_ent(contextp, &ent_prev);
+            if (ret) {
+                kadm5_free_principal_ent(contextp, &ent);
+                goto fail;
+            }
+        }
+       ret = kadm5_modify_principal(kadm_handlep, &ent, mask);
+       kadm5_free_principal_ent(kadm_handlep, &ent);
        krb5_storage_free(sp);
        sp = krb5_storage_emem();
+        if (sp == NULL) {
+            ret = krb5_enomem(contextp->context);
+            goto fail;
+        }
        krb5_store_int32(sp, ret);
        break;
     }
+    case kadm_prune:{
+        op = "PRUNE";
+        ret = krb5_ret_principal(sp, &princ);
+        if (ret)
+            goto fail;
+        ret = krb5_ret_int32(sp, &kvno);
+        if (ret == HEIM_ERR_EOF) {
+            kvno = 0;
+        } else if (ret) {
+            goto fail;
+        }
+        krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
+        krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
+        ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
+        if (ret)
+            goto fail;
+
+        ret = kadm5_prune_principal(kadm_handlep, princ, kvno);
+        krb5_storage_free(sp);
+        sp = krb5_storage_emem();
+        if (sp == NULL) {
+            ret = krb5_enomem(contextp->context);
+            goto fail;
+        }
+        krb5_store_int32(sp, ret);
+        break;
+    }
     case kadm_rename:{
        op = "RENAME";
        ret = krb5_ret_principal(sp, &princ);
        if(ret)
            goto fail;
        ret = krb5_ret_principal(sp, &princ2);
-       if(ret){
-           krb5_free_principal(context->context, princ);
+       if (ret)
            goto fail;
-       }
-       krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
-       krb5_unparse_name_fixed(context->context, princ2, name2, sizeof(name2));
-       krb5_warnx(context->context, "%s: %s %s -> %s",
+
+       krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
+       krb5_unparse_name_fixed(contextp->context, princ2,
+                                name2, sizeof(name2));
+       krb5_warnx(contextp->context, "%s: %s %s -> %s",
                   client, op, name, name2);
-       ret = _kadm5_acl_check_permission(context,
+       ret = _kadm5_acl_check_permission(contextp,
                                          KADM5_PRIV_ADD,
-                                         princ2)
-           || _kadm5_acl_check_permission(context,
-                                          KADM5_PRIV_DELETE,
-                                          princ);
-       if(ret){
-           krb5_free_principal(context->context, princ);
-           krb5_free_principal(context->context, princ2);
+                                         princ2);
+        if (ret == 0) {
+            /*
+             * Also require modify for the principal.  For backwards
+             * compatibility, allow delete permission on the old name to
+             * cure lack of modify permission on the old name.
+             */
+            ret = _kadm5_acl_check_permission(contextp,
+                                              KADM5_PRIV_MODIFY,
+                                              princ);
+            if (ret) {
+                ret = _kadm5_acl_check_permission(contextp,
+                                                  KADM5_PRIV_DELETE,
+                                                  princ);
+            }
+        }
+       if (ret)
            goto fail;
-       }
-       ret = kadm5_rename_principal(kadm_handle, princ, princ2);
-       krb5_free_principal(context->context, princ);
-       krb5_free_principal(context->context, princ2);
+
+       ret = kadm5_rename_principal(kadm_handlep, princ, princ2);
        krb5_storage_free(sp);
        sp = krb5_storage_emem();
+        if (sp == NULL) {
+            ret = krb5_enomem(contextp->context);
+            goto fail;
+        }
        krb5_store_int32(sp, ret);
        break;
     }
     case kadm_chpass:{
+       krb5_boolean is_self_cpw, allow_self_cpw;
+
        op = "CHPASS";
        ret = krb5_ret_principal(sp, &princ);
-       if(ret)
+       if (ret)
            goto fail;
        ret = krb5_ret_string(sp, &password);
-       if(ret){
-           krb5_free_principal(context->context, princ);
+       if (ret)
            goto fail;
-       }
-       krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
-       krb5_warnx(context->context, "%s: %s %s", client, op, name);
-
-       /*
-        * The change is allowed if at least one of:
-
-        * a) it's for the principal him/herself and this was an
-        *    initial ticket, but then, check with the password quality
-        *    function.
-        * b) the user is on the CPW ACL.
-        */
-
-       if (initial
-           && krb5_principal_compare (context->context, context->caller,
-                                      princ))
-       {
-           krb5_data pwd_data;
-           const char *pwd_reason;
 
-           pwd_data.data = password;
-           pwd_data.length = strlen(password);
+       ret = krb5_ret_int32(sp, &keepold);
+       if (ret && ret != HEIM_ERR_EOF)
+           goto fail;
 
-           pwd_reason = kadm5_check_password_quality (context->context,
-                                                      princ, &pwd_data);
-           if (pwd_reason != NULL)
-               ret = KADM5_PASS_Q_DICT;
-           else
-               ret = 0;
-       } else
-           ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW, princ);
+       krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
+       krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
 
-       if(ret) {
-           krb5_free_principal(context->context, princ);
-           memset(password, 0, strlen(password));
-           free(password);
-           goto fail;
+       /*
+        * Change password requests are subject to ACLs unless the principal is
+        * changing their own password and the initial ticket flag is set, and
+        * the allow_self_change_password configuration option is TRUE.
+        */
+       is_self_cpw =
+           krb5_principal_compare(contextp->context, contextp->caller, princ);
+       allow_self_cpw =
+           krb5_config_get_bool_default(contextp->context, NULL, TRUE,
+                                        "kadmin", "allow_self_change_password", NULL);
+       if (!(is_self_cpw && initial && allow_self_cpw)) {
+           ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
+           if (ret)
+               goto fail;
        }
-       ret = kadm5_chpass_principal(kadm_handle, princ, password);
-       krb5_free_principal(context->context, princ);
-       memset(password, 0, strlen(password));
-       free(password);
+
+       ret = kadm5_chpass_principal_3(kadm_handlep, princ, keepold, 0, NULL,
+                                      password);
        krb5_storage_free(sp);
        sp = krb5_storage_emem();
+        if (sp == NULL) {
+            ret = krb5_enomem(contextp->context);
+            goto fail;
+        }
        krb5_store_int32(sp, ret);
        break;
     }
@@ -272,22 +386,23 @@ kadmind_dispatch(void *kadm_handle, krb5_boolean initial,
        if(ret)
            goto fail;
        ret = krb5_ret_int32(sp, &n_key_data);
-       if (ret) {
-           krb5_free_principal(context->context, princ);
+       if (ret)
            goto fail;
-       }
+
+       ret = krb5_ret_int32(sp, &keepold);
+       if (ret && ret != HEIM_ERR_EOF)
+           goto fail;
+
        /* n_key_data will be squeezed into an int16_t below. */
        if (n_key_data < 0 || n_key_data >= 1 << 16 ||
-           n_key_data > UINT_MAX/sizeof(*key_data)) {
+           (size_t)n_key_data > UINT_MAX/sizeof(*key_data)) {
            ret = ERANGE;
-           krb5_free_principal(context->context, princ);
            goto fail;
        }
 
        key_data = malloc (n_key_data * sizeof(*key_data));
        if (key_data == NULL && n_key_data != 0) {
-           ret = ENOMEM;
-           krb5_free_principal(context->context, princ);
+           ret = krb5_enomem(contextp->context);
            goto fail;
        }
 
@@ -296,50 +411,53 @@ kadmind_dispatch(void *kadm_handle, krb5_boolean initial,
            if (ret) {
                int16_t dummy = i;
 
-               kadm5_free_key_data (context, &dummy, key_data);
+               kadm5_free_key_data (contextp, &dummy, key_data);
                free (key_data);
-               krb5_free_principal(context->context, princ);
                goto fail;
            }
        }
 
-       krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
-       krb5_warnx(context->context, "%s: %s %s", client, op, name);
+       krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
+       krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
 
        /*
         * The change is only allowed if the user is on the CPW ACL,
         * this it to force password quality check on the user.
         */
 
-       ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW, princ);
+       ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
        if(ret) {
            int16_t dummy = n_key_data;
 
-           kadm5_free_key_data (context, &dummy, key_data);
+           kadm5_free_key_data (contextp, &dummy, key_data);
            free (key_data);
-           krb5_free_principal(context->context, princ);
            goto fail;
        }
-       ret = kadm5_chpass_principal_with_key(kadm_handle, princ,
-                                             n_key_data, key_data);
+       ret = kadm5_chpass_principal_with_key_3(kadm_handlep, princ, keepold,
+                                               n_key_data, key_data);
        {
            int16_t dummy = n_key_data;
-           kadm5_free_key_data (context, &dummy, key_data);
+           kadm5_free_key_data (contextp, &dummy, key_data);
        }
        free (key_data);
-       krb5_free_principal(context->context, princ);
        krb5_storage_free(sp);
        sp = krb5_storage_emem();
+        if (sp == NULL) {
+            ret = krb5_enomem(contextp->context);
+            goto fail;
+        }
        krb5_store_int32(sp, ret);
        break;
     }
     case kadm_randkey:{
+        size_t i;
+
        op = "RANDKEY";
        ret = krb5_ret_principal(sp, &princ);
-       if(ret)
+       if (ret)
            goto fail;
-       krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
-       krb5_warnx(context->context, "%s: %s %s", client, op, name);
+       krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
+       krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
        /*
         * The change is allowed if at least one of:
         * a) it's for the principal him/herself and this was an initial ticket
@@ -347,28 +465,82 @@ kadmind_dispatch(void *kadm_handle, krb5_boolean initial,
         */
 
        if (initial
-           && krb5_principal_compare (context->context, context->caller,
+           && krb5_principal_compare (contextp->context, contextp->caller,
                                       princ))
            ret = 0;
        else
-           ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW, princ);
+           ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
 
-       if(ret) {
-           krb5_free_principal(context->context, princ);
+       if (ret)
            goto fail;
-       }
-       ret = kadm5_randkey_principal(kadm_handle, princ,
-                                     &new_keys, &n_keys);
-       krb5_free_principal(context->context, princ);
+
+       /*
+        * See comments in kadm5_c_randkey_principal() regarding the
+        * protocol.
+        */
+       ret = krb5_ret_int32(sp, &keepold);
+       if (ret != 0 && ret != HEIM_ERR_EOF)
+           goto fail;
+
+       ret = krb5_ret_int32(sp, &n_ks_tuple);
+       if (ret == HEIM_ERR_EOF) {
+            const char *enctypes;
+           size_t n;
+
+            enctypes = krb5_config_get_string(contextp->context, NULL,
+                                              "realms",
+                                              krb5_principal_get_realm(contextp->context,
+                                                                       princ),
+                                              "supported_enctypes", NULL);
+            if (enctypes == NULL || enctypes[0] == '\0')
+                enctypes = "aes128-cts-hmac-sha1-96";
+            ret = krb5_string_to_keysalts2(contextp->context, enctypes,
+                                           &n, &ks_tuple);
+           n_ks_tuple = n;
+        }
+        if (ret != 0)
+           goto fail;
+
+        if (n_ks_tuple < 0) {
+            ret = EOVERFLOW;
+            goto fail;
+        }
+
+        if ((ks_tuple = calloc(n_ks_tuple, sizeof (*ks_tuple))) == NULL) {
+            ret = errno;
+            goto fail;
+        }
+
+        for (i = 0; i < n_ks_tuple; i++) {
+            ret = krb5_ret_int32(sp, &ks_tuple[i].ks_enctype);
+            if (ret != 0) {
+                free(ks_tuple);
+                goto fail;
+            }
+            ret = krb5_ret_int32(sp, &ks_tuple[i].ks_salttype);
+            if (ret != 0) {
+                free(ks_tuple);
+                goto fail;
+            }
+        }
+       ret = kadm5_randkey_principal_3(kadm_handlep, princ, keepold,
+                                       n_ks_tuple, ks_tuple, &new_keys,
+                                       &n_keys);
+        free(ks_tuple);
+
        krb5_storage_free(sp);
        sp = krb5_storage_emem();
+        if (sp == NULL) {
+            ret = krb5_enomem(contextp->context);
+            goto fail;
+        }
        krb5_store_int32(sp, ret);
-       if(ret == 0){
-           int i;
+       if (ret == 0){
            krb5_store_int32(sp, n_keys);
-           for(i = 0; i < n_keys; i++){
-               krb5_store_keyblock(sp, new_keys[i]);
-               krb5_free_keyblock_contents(context->context, &new_keys[i]);
+           for (i = 0; i < n_keys; i++){
+                if (ret == 0)
+                    ret = krb5_store_keyblock(sp, new_keys[i]);
+               krb5_free_keyblock_contents(contextp->context, &new_keys[i]);
            }
            free(new_keys);
        }
@@ -376,9 +548,13 @@ kadmind_dispatch(void *kadm_handle, krb5_boolean initial,
     }
     case kadm_get_privs:{
        uint32_t privs;
-       ret = kadm5_get_privs(kadm_handle, &privs);
+       ret = kadm5_get_privs(kadm_handlep, &privs);
        krb5_storage_free(sp);
        sp = krb5_storage_emem();
+        if (sp == NULL) {
+            ret = krb5_enomem(contextp->context);
+            goto fail;
+        }
        krb5_store_int32(sp, ret);
        if(ret == 0)
            krb5_store_uint32(sp, privs);
@@ -395,52 +571,194 @@ kadmind_dispatch(void *kadm_handle, krb5_boolean initial,
                goto fail;
        }else
            expression = NULL;
-       krb5_warnx(context->context, "%s: %s %s", client, op,
+       krb5_warnx(contextp->context, "%s: %s %s", client, op,
                   expression ? expression : "*");
-       ret = _kadm5_acl_check_permission(context, KADM5_PRIV_LIST, NULL);
+       ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_LIST, NULL);
        if(ret){
            free(expression);
            goto fail;
        }
-       ret = kadm5_get_principals(kadm_handle, expression, &princs, &n_princs);
+       ret = kadm5_get_principals(kadm_handlep, expression, &princs, &n_princs);
        free(expression);
        krb5_storage_free(sp);
        sp = krb5_storage_emem();
+        if (sp == NULL) {
+            ret = krb5_enomem(contextp->context);
+            goto fail;
+        }
        krb5_store_int32(sp, ret);
        if(ret == 0){
            int i;
            krb5_store_int32(sp, n_princs);
            for(i = 0; i < n_princs; i++)
                krb5_store_string(sp, princs[i]);
-           kadm5_free_name_list(kadm_handle, princs, &n_princs);
+           kadm5_free_name_list(kadm_handlep, princs, &n_princs);
        }
        break;
     }
     default:
-       krb5_warnx(context->context, "%s: UNKNOWN OP %d", client, cmd);
+       krb5_warnx(contextp->context, "%s: UNKNOWN OP %d", client, cmd);
        krb5_storage_free(sp);
        sp = krb5_storage_emem();
+        if (sp == NULL) {
+            ret = krb5_enomem(contextp->context);
+            goto fail;
+        }
        krb5_store_int32(sp, KADM5_FAILURE);
        break;
     }
+    if (password != NULL) {
+       len = strlen(password);
+       memset_s(password, len, 0, len);
+       free(password);
+    }
     krb5_storage_to_data(sp, out);
     krb5_storage_free(sp);
+    if (princ != NULL)
+       krb5_free_principal(contextp->context, princ);
+    if (princ2 != NULL)
+       krb5_free_principal(contextp->context, princ2);
     return 0;
 fail:
-    krb5_warn(context->context, ret, "%s", op);
-    krb5_storage_seek(sp, 0, SEEK_SET);
-    krb5_store_int32(sp, ret);
-    krb5_storage_to_data(sp, out);
-    krb5_storage_free(sp);
+    if (password != NULL) {
+       len = strlen(password);
+       memset_s(password, len, 0, len);
+       free(password);
+    }
+    krb5_warn(contextp->context, ret, "%s", op);
+    if (sp != NULL) {
+       krb5_storage_seek(sp, 0, SEEK_SET);
+       krb5_store_int32(sp, ret);
+       krb5_storage_to_data(sp, out);
+       krb5_storage_free(sp);
+    }
+    if (princ != NULL)
+       krb5_free_principal(contextp->context, princ);
+    if (princ2 != NULL)
+       krb5_free_principal(contextp->context, princ2);
+    return 0;
+}
+
+struct iter_aliases_ctx {
+    HDB_Ext_Aliases aliases;
+    krb5_tl_data *tl;
+    int alias_idx;
+    int done;
+};
+
+static kadm5_ret_t
+iter_aliases(kadm5_principal_ent_rec *from,
+             struct iter_aliases_ctx *ctx,
+             krb5_principal *out)
+{
+    HDB_extension ext;
+    kadm5_ret_t ret;
+    size_t size;
+
+    *out = NULL;
+
+    if (ctx->done > 0)
+        return 0;
+
+    if (ctx->done == 0) {
+        if (ctx->alias_idx < ctx->aliases.aliases.len) {
+            *out = &ctx->aliases.aliases.val[ctx->alias_idx++];
+            return 0;
+        }
+        /* Out of aliases in this TL, step to next TL */
+        ctx->tl = ctx->tl->tl_data_next;
+    } else if (ctx->done < 0) {
+        /* Setup iteration context */
+        memset(ctx, 0, sizeof(*ctx));
+        ctx->done = 0;
+        ctx->aliases.aliases.val = NULL;
+        ctx->aliases.aliases.len = 0;
+        ctx->tl = from->tl_data;
+    }
+
+    free_HDB_Ext_Aliases(&ctx->aliases);
+    ctx->alias_idx = 0;
+
+    /* Find TL with aliases */
+    for (; ctx->tl != NULL; ctx->tl = ctx->tl->tl_data_next) {
+        if (ctx->tl->tl_data_type != KRB5_TL_EXTENSION)
+            continue;
+
+        ret = decode_HDB_extension(ctx->tl->tl_data_contents,
+                                   ctx->tl->tl_data_length,
+                                   &ext, &size);
+        if (ret)
+            return ret;
+        if (ext.data.element == choice_HDB_extension_data_aliases &&
+            ext.data.u.aliases.aliases.len > 0) {
+            ctx->aliases = ext.data.u.aliases;
+            break;
+        }
+        free_HDB_extension(&ext);
+    }
+
+    if (ctx->tl != NULL && ctx->aliases.aliases.len > 0) {
+        *out = &ctx->aliases.aliases.val[ctx->alias_idx++];
+        return 0;
+    }
+
+    ctx->done = 1;
+    return 0;
+}
+
+static kadm5_ret_t
+check_aliases(kadm5_server_context *contextp,
+              kadm5_principal_ent_rec *add_princ,
+              kadm5_principal_ent_rec *del_princ)
+{
+    kadm5_ret_t ret;
+    struct iter_aliases_ctx iter;
+    struct iter_aliases_ctx iter_del;
+    krb5_principal new_name, old_name;
+    int match;
+
+    /*
+     * Yeah, this is O(N^2).  Gathering and sorting all the aliases
+     * would be a bit of a pain; if we ever have principals with enough
+     * aliases for this to be a problem, we can fix it then.
+     */
+    for (iter.done = -1; iter.done != 1;) {
+        match = 0;
+        ret = iter_aliases(add_princ, &iter, &new_name);
+        if (ret)
+            return ret;
+        if (iter.done == 1)
+            break;
+        for (iter_del.done = -1; iter_del.done != 1;) {
+            ret = iter_aliases(del_princ, &iter_del, &old_name);
+            if (ret)
+                return ret;
+            if (iter_del.done == 1)
+                break;
+            if (!krb5_principal_compare(contextp->context, new_name, old_name))
+                continue;
+            free_HDB_Ext_Aliases(&iter_del.aliases);
+            match = 1;
+            break;
+        }
+        if (match)
+            continue;
+        ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_ADD, new_name);
+        if (ret) {
+            free_HDB_Ext_Aliases(&iter.aliases);
+            return ret;
+        }
+    }
+
     return 0;
 }
 
 static void
-v5_loop (krb5_context context,
+v5_loop (krb5_context contextp,
         krb5_auth_context ac,
         krb5_boolean initial,
-        void *kadm_handle,
-        int fd)
+        void *kadm_handlep,
+        krb5_socket_t fd)
 {
     krb5_error_code ret;
     krb5_data in, out;
@@ -449,17 +767,20 @@ v5_loop (krb5_context context,
        doing_useful_work = 0;
        if(term_flag)
            exit(0);
-       ret = krb5_read_priv_message(context, ac, &fd, &in);
+       ret = krb5_read_priv_message(contextp, ac, &fd, &in);
        if(ret == HEIM_ERR_EOF)
            exit(0);
        if(ret)
-           krb5_err(context, 1, ret, "krb5_read_priv_message");
+           krb5_err(contextp, 1, ret, "krb5_read_priv_message");
        doing_useful_work = 1;
-       kadmind_dispatch(kadm_handle, initial, &in, &out);
+       ret = kadmind_dispatch(kadm_handlep, initial, &in, &out);
+       if (ret)
+           krb5_err(contextp, 1, ret, "kadmind_dispatch");
        krb5_data_free(&in);
-       ret = krb5_write_priv_message(context, ac, &fd, &out);
+       ret = krb5_write_priv_message(contextp, ac, &fd, &out);
+       krb5_data_free(&out);
        if(ret)
-           krb5_err(context, 1, ret, "krb5_write_priv_message");
+           krb5_err(contextp, 1, ret, "krb5_write_priv_message");
     }
 }
 
@@ -469,42 +790,41 @@ match_appl_version(const void *data, const char *appl_version)
     unsigned minor;
     if(sscanf(appl_version, "KADM0.%u", &minor) != 1)
        return 0;
-    *(unsigned*)data = minor;
+    /*XXX*/
+    *(unsigned*)(intptr_t)data = minor;
     return 1;
 }
 
 static void
-handle_v5(krb5_context context,
+handle_v5(krb5_context contextp,
          krb5_keytab keytab,
-         int fd)
+         krb5_socket_t fd)
 {
     krb5_error_code ret;
     krb5_ticket *ticket;
     char *server_name;
     char *client;
-    void *kadm_handle;
+    void *kadm_handlep;
     krb5_boolean initial;
     krb5_auth_context ac = NULL;
 
-    unsigned kadm_version;
+    unsigned kadm_version = 1;
     kadm5_config_params realm_params;
 
-    ret = krb5_recvauth_match_version(context, &ac, &fd,
+    ret = krb5_recvauth_match_version(contextp, &ac, &fd,
                                      match_appl_version, &kadm_version,
                                      NULL, KRB5_RECVAUTH_IGNORE_VERSION,
                                      keytab, &ticket);
-    if(ret == KRB5_KT_NOTFOUND)
-       krb5_errx(context, 1, "krb5_recvauth: key not found");
-    if(ret)
-       krb5_err(context, 1, ret, "krb5_recvauth");
+    if (ret)
+       krb5_err(contextp, 1, ret, "krb5_recvauth");
 
-    ret = krb5_unparse_name (context, ticket->server, &server_name);
+    ret = krb5_unparse_name (contextp, ticket->server, &server_name);
     if (ret)
-       krb5_err (context, 1, ret, "krb5_unparse_name");
+       krb5_err (contextp, 1, ret, "krb5_unparse_name");
 
     if (strncmp (server_name, KADM5_ADMIN_SERVICE,
                 strlen(KADM5_ADMIN_SERVICE)) != 0)
-       krb5_errx (context, 1, "ticket for strange principal (%s)",
+       krb5_errx (contextp, 1, "ticket for strange principal (%s)",
                   server_name);
 
     free (server_name);
@@ -513,62 +833,63 @@ handle_v5(krb5_context context,
 
     if(kadm_version == 1) {
        krb5_data params;
-       ret = krb5_read_priv_message(context, ac, &fd, &params);
+       ret = krb5_read_priv_message(contextp, ac, &fd, &params);
        if(ret)
-           krb5_err(context, 1, ret, "krb5_read_priv_message");
-       _kadm5_unmarshal_params(context, &params, &realm_params);
+           krb5_err(contextp, 1, ret, "krb5_read_priv_message");
+       _kadm5_unmarshal_params(contextp, &params, &realm_params);
     }
 
     initial = ticket->ticket.flags.initial;
-    ret = krb5_unparse_name(context, ticket->client, &client);
+    ret = krb5_unparse_name(contextp, ticket->client, &client);
     if (ret)
-       krb5_err (context, 1, ret, "krb5_unparse_name");
-    krb5_free_ticket (context, ticket);
-    ret = kadm5_s_init_with_password_ctx(context,
+       krb5_err (contextp, 1, ret, "krb5_unparse_name");
+    krb5_free_ticket (contextp, ticket);
+    ret = kadm5_s_init_with_password_ctx(contextp,
                                         client,
                                         NULL,
                                         KADM5_ADMIN_SERVICE,
                                         &realm_params,
                                         0, 0,
-                                        &kadm_handle);
+                                        &kadm_handlep);
     if(ret)
-       krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
-    v5_loop (context, ac, initial, kadm_handle, fd);
+       krb5_err (contextp, 1, ret, "kadm5_init_with_password_ctx");
+    v5_loop (contextp, ac, initial, kadm_handlep, fd);
 }
 
 krb5_error_code
-kadmind_loop(krb5_context context,
+kadmind_loop(krb5_context contextp,
             krb5_keytab keytab,
-            int fd)
+            krb5_socket_t sock)
 {
     u_char buf[sizeof(KRB5_SENDAUTH_VERSION) + 4];
     ssize_t n;
     unsigned long len;
 
-    n = krb5_net_read(context, &fd, buf, 4);
+    n = krb5_net_read(contextp, &sock, buf, 4);
     if(n == 0)
        exit(0);
     if(n < 0)
-       krb5_err(context, 1, errno, "read");
+       krb5_err(contextp, 1, errno, "read");
     _krb5_get_int(buf, &len, 4);
 
     if (len == sizeof(KRB5_SENDAUTH_VERSION)) {
 
-       n = krb5_net_read(context, &fd, buf + 4, len);
+       n = krb5_net_read(contextp, &sock, buf + 4, len);
        if (n < 0)
-           krb5_err (context, 1, errno, "reading sendauth version");
+           krb5_err (contextp, 1, errno, "reading sendauth version");
        if (n == 0)
-           krb5_errx (context, 1, "EOF reading sendauth version");
+           krb5_errx (contextp, 1, "EOF reading sendauth version");
 
        if(memcmp(buf + 4, KRB5_SENDAUTH_VERSION, len) == 0) {
-           handle_v5(context, keytab, fd);
+           handle_v5(contextp, keytab, sock);
            return 0;
        }
        len += 4;
     } else
        len = 4;
 
-    handle_mit(context, buf, len, fd);
+    handle_mit(contextp, buf, len, sock);
 
     return 0;
 }
+