kadmin: fix leak
[metze/heimdal/wip.git] / kadmin / server.c
index f1186cc16fc3c824914321e0d11eae9f94d136c8..cc82644c6add9d602843ae9eb57155d25a3874dc 100644 (file)
@@ -1,67 +1,74 @@
 /*
- * Copyright (c) 1997-1999 Kungliga Tekniska Högskolan
- * (Royal Institute of Technology, Stockholm, Sweden). 
- * All rights reserved. 
+ * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
  *
- * Redistribution and use in source and binary forms, with or without 
- * modification, are permitted provided that the following conditions 
- * are met: 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
  *
- * 1. Redistributions of source code must retain the above copyright 
- *    notice, this list of conditions and the following disclaimer. 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
  *
- * 2. 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. 
+ * 2. 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.
  *
- * 3. All advertising materials mentioning features or use of this software 
- *    must display the following acknowledgement: 
- *      This product includes software developed by Kungliga Tekniska 
- *      Högskolan and its contributors. 
+ * 3. Neither the name of the Institute nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
  *
- * 4. Neither the name of the Institute nor the names of its contributors 
- *    may be used to endorse or promote products derived from this software 
- *    without specific prior written permission. 
- *
- * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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. 
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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 "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_data *in, krb5_data *out)
+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, *exp;
+    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;
-    
-    krb5_unparse_name_fixed(context->context, context->caller, 
+    int len;
+
+    krb5_unparse_name_fixed(contextp->context, contextp->caller,
                            client, sizeof(client));
-    
+
     sp = krb5_storage_from_data(in);
+    if (sp == NULL) {
+       ret = krb5_enomem(contextp->context);
+       goto fail;
+    }
 
     krb5_ret_int32(sp, &cmd);
     switch(cmd){
@@ -71,26 +78,63 @@ kadmind_dispatch(void *kadm_handle, krb5_data *in, krb5_data *out)
        if(ret)
            goto fail;
        ret = krb5_ret_int32(sp, &mask);
-       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);
-       ret = _kadm5_acl_check_permission(context, KADM5_PRIV_GET);
-       if(ret){
-           krb5_free_principal(context->context, princ);
+
+       mask |= KADM5_PRINCIPAL;
+       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:{
@@ -98,17 +142,24 @@ kadmind_dispatch(void *kadm_handle, krb5_data *in, krb5_data *out)
        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);
-       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;
     }
@@ -119,31 +170,43 @@ kadmind_dispatch(void *kadm_handle, krb5_data *in, krb5_data *out)
            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,129 +217,347 @@ kadmind_dispatch(void *kadm_handle, krb5_data *in, krb5_data *out)
            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, 
-                                         KADM5_PRIV_ADD|KADM5_PRIV_DELETE);
-       if(ret){
-           krb5_free_principal(context->context, princ);
+       ret = _kadm5_acl_check_permission(contextp,
+                                         KADM5_PRIV_ADD,
+                                         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;
+
+       ret = krb5_ret_int32(sp, &keepold);
+       if (ret && ret != HEIM_ERR_EOF)
            goto fail;
+
+       krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
+       krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
+
+       /*
+        * 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;
        }
-       krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
-       krb5_warnx(context->context, "%s: %s %s", client, op, name);
-#if 0
-       /* anyone can change her/his own password */
-       /* but not until there is a way to ensure that the
-           authentication was done via an initial ticket request */
-       if(!krb5_principal_compare(context->context, context->caller, princ))
-           ret = KADM5_AUTH_INSUFFICIENT;
+
+       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;
+    }
+    case kadm_chpass_with_key:{
+       int i;
+       krb5_key_data *key_data;
+       int n_key_data;
+
+       op = "CHPASS_WITH_KEY";
+       ret = krb5_ret_principal(sp, &princ);
        if(ret)
-#endif
-           ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW);
-       if(ret){
-           krb5_free_principal(context->context, princ);
+           goto fail;
+       ret = krb5_ret_int32(sp, &n_key_data);
+       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 ||
+           (size_t)n_key_data > UINT_MAX/sizeof(*key_data)) {
+           ret = ERANGE;
            goto fail;
        }
-       ret = kadm5_chpass_principal(kadm_handle, princ, password);
-       krb5_free_principal(context->context, princ);
-       memset(password, 0, strlen(password));
-       free(password);
+
+       key_data = malloc (n_key_data * sizeof(*key_data));
+       if (key_data == NULL && n_key_data != 0) {
+           ret = krb5_enomem(contextp->context);
+           goto fail;
+       }
+
+       for (i = 0; i < n_key_data; ++i) {
+           ret = kadm5_ret_key_data (sp, &key_data[i]);
+           if (ret) {
+               int16_t dummy = i;
+
+               kadm5_free_key_data (contextp, &dummy, key_data);
+               free (key_data);
+               goto fail;
+           }
+       }
+
+       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(contextp, KADM5_PRIV_CPW, princ);
+       if(ret) {
+           int16_t dummy = n_key_data;
+
+           kadm5_free_key_data (contextp, &dummy, key_data);
+           free (key_data);
+           goto fail;
+       }
+       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 (contextp, &dummy, key_data);
+       }
+       free (key_data);
        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);
-#if 0
-       /* anyone can change her/his own password */
-       /* but not until there is a way to ensure that the
-           authentication was done via an initial ticket request */
-       if(!krb5_principal_compare(context->context, context->caller, princ))
-           ret = KADM5_AUTH_INSUFFICIENT;
-       if(ret)
-#endif
-           ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW);
-       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);
+       /*
+        * The change is allowed if at least one of:
+        * a) it's for the principal him/herself and this was an initial ticket
+        * b) the user is on the CPW ACL.
+        */
+
+       if (initial
+           && krb5_principal_compare (contextp->context, contextp->caller,
+                                      princ))
+           ret = 0;
+       else
+           ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, 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);
        }
        break;
     }
     case kadm_get_privs:{
-       ret = kadm5_get_privs(kadm_handle, &mask);
+       uint32_t 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_int32(sp, mask);
+           krb5_store_uint32(sp, privs);
        break;
     }
     case kadm_get_princs:{
@@ -285,150 +566,330 @@ kadmind_dispatch(void *kadm_handle, krb5_data *in, krb5_data *out)
        if(ret)
            goto fail;
        if(tmp){
-           ret = krb5_ret_string(sp, &exp);
+           ret = krb5_ret_string(sp, &expression);
            if(ret)
                goto fail;
        }else
-           exp = NULL;
-       krb5_warnx(context->context, "%s: %s %s", client, op, exp ? exp : "*");
-       ret = _kadm5_acl_check_permission(context, KADM5_PRIV_LIST);
+           expression = NULL;
+       krb5_warnx(contextp->context, "%s: %s %s", client, op,
+                  expression ? expression : "*");
+       ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_LIST, NULL);
        if(ret){
-           free(exp);
+           free(expression);
            goto fail;
        }
-       ret = kadm5_get_principals(kadm_handle, exp, &princs, &n_princs);
-       free(exp);
+       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);
-    sp->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;
 }
 
-krb5_error_code
-kadmind_loop (krb5_context, krb5_auth_context, krb5_keytab, int);
+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;
 
-void
-handle_v4(krb5_context context,
-         int len,
-         int fd);
+    *out = NULL;
 
-krb5_error_code
-kadmind_loop(krb5_context context,
-            krb5_auth_context ac,
-            krb5_keytab keytab, 
-            int fd)
+    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 contextp,
+        krb5_auth_context ac,
+        krb5_boolean initial,
+        void *kadm_handlep,
+        krb5_socket_t fd)
 {
-    char *client;
     krb5_error_code ret;
-    void *kadm_handle;
-
-    while(1){
-       krb5_data in, out, msg, reply;
-       unsigned char tmp[4];
-       unsigned long len;
-       ssize_t n;
-       struct iovec iov[2];
-       krb5_boolean krb4_packet = 0;
-       
-       n = krb5_net_read(context, &fd, tmp, 4);
-       if(n == 0)
+    krb5_data in, out;
+
+    for (;;) {
+       doing_useful_work = 0;
+       if(term_flag)
            exit(0);
-       if(n < 0)
-           krb5_errx(context, 1, "read error: %d", errno);
-       if(n < 4)
-           krb5_errx(context, 1, "short read (%ld)", (long int)n);
-       _krb5_get_int(tmp, &len, 4);
-       if(len > 0xffff && (len & 0xffff) == ('K' << 8) + 'A') {
-           len >>= 16;
-           krb4_packet = 1;
-#ifdef KRB4
-           handle_v4(context, len, fd);
-#else
-           krb5_errx(context, 1, "packet appears to be version 4");
-#endif
-       }
-       krb5_net_read(context, &fd, tmp, sizeof(tmp));
-       if(len != sizeof(KRB5_SENDAUTH_VERSION) || 
-          memcmp(tmp, KRB5_SENDAUTH_VERSION, sizeof(tmp)) != 0)
-           krb5_errx(context, 1, "bad sendauth version %.8s", tmp);
-       
-       {
-           krb5_ticket *ticket;
-           krb5_principal server;
-           krb5_parse_name(context, KADM5_ADMIN_SERVICE, &server);
-           ret = krb5_recvauth(context, &ac, &fd, KADMIN_APPL_VERSION, 
-                               server, KRB5_RECVAUTH_IGNORE_VERSION, 
-                               keytab, &ticket);
-           krb5_free_principal(context, server);
-           
-           if(ret)
-               krb5_err(context, 1, ret, "krb5_recvauth");
-           krb5_unparse_name(context, ticket->client, &client);
-           ret = kadm5_init_with_password_ctx(context, 
-                                              client, 
-                                              NULL,
-                                              KADM5_ADMIN_SERVICE,
-                                              NULL, 0, 0, 
-                                              &kadm_handle);
-           if(ret)
-               krb5_abort(context, ret, "kadm5_init_with_password_ctx");
-       }
-       
-
-       in.length = len;
-       in.data = malloc(in.length);
-       n = krb5_net_read(context, &fd, in.data, in.length);
-       if(n < 0)
-           krb5_errx(context, 1, "read error: %d", errno);
-       if(n < in.length)
-           krb5_errx(context, 1, "short read (%ld)", (long int)n);
-       if(!krb4_packet) {
-           ret = krb5_rd_priv(context, ac, &in, &out, NULL);
-           krb5_data_free(&in);
-           kadmind_dispatch(kadm_handle, &out, &msg);
-           krb5_data_free(&out);
-       }
-       ret = krb5_mk_priv(context, ac, &msg, &reply, NULL);
-       krb5_data_free(&msg);
-       if(ret) 
-           krb5_err(context, 1, ret, "krb5_mk_priv");
-
-       _krb5_put_int(tmp, reply.length, 4);
-
-       iov[0].iov_base = tmp;
-       iov[0].iov_len = 4;
-       iov[1].iov_base = reply.data;
-       iov[1].iov_len = reply.length;
-       n = writev(fd, iov, 2);
-       krb5_data_free(&reply);
-       if(n < 0)
-           krb5_err(context, 1, errno, "writev");
-       if(n < iov[0].iov_len + iov[1].iov_len)
-           krb5_errx(context, 1, "short write");
+       ret = krb5_read_priv_message(contextp, ac, &fd, &in);
+       if(ret == HEIM_ERR_EOF)
+           exit(0);
+       if(ret)
+           krb5_err(contextp, 1, ret, "krb5_read_priv_message");
+       doing_useful_work = 1;
+       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(contextp, ac, &fd, &out);
+       krb5_data_free(&out);
+       if(ret)
+           krb5_err(contextp, 1, ret, "krb5_write_priv_message");
+    }
+}
+
+static krb5_boolean
+match_appl_version(const void *data, const char *appl_version)
+{
+    unsigned minor;
+    if(sscanf(appl_version, "KADM0.%u", &minor) != 1)
+       return 0;
+    /*XXX*/
+    *(unsigned*)(intptr_t)data = minor;
+    return 1;
+}
+
+static void
+handle_v5(krb5_context contextp,
+         krb5_keytab keytab,
+         krb5_socket_t fd)
+{
+    krb5_error_code ret;
+    krb5_ticket *ticket;
+    char *server_name;
+    char *client;
+    void *kadm_handlep;
+    krb5_boolean initial;
+    krb5_auth_context ac = NULL;
+
+    unsigned kadm_version = 1;
+    kadm5_config_params realm_params;
+
+    ret = krb5_recvauth_match_version(contextp, &ac, &fd,
+                                     match_appl_version, &kadm_version,
+                                     NULL, KRB5_RECVAUTH_IGNORE_VERSION,
+                                     keytab, &ticket);
+    if (ret)
+       krb5_err(contextp, 1, ret, "krb5_recvauth");
+
+    ret = krb5_unparse_name (contextp, ticket->server, &server_name);
+    if (ret)
+       krb5_err (contextp, 1, ret, "krb5_unparse_name");
+
+    if (strncmp (server_name, KADM5_ADMIN_SERVICE,
+                strlen(KADM5_ADMIN_SERVICE)) != 0)
+       krb5_errx (contextp, 1, "ticket for strange principal (%s)",
+                  server_name);
+
+    free (server_name);
+
+    memset(&realm_params, 0, sizeof(realm_params));
+
+    if(kadm_version == 1) {
+       krb5_data params;
+       ret = krb5_read_priv_message(contextp, ac, &fd, &params);
+       if(ret)
+           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(contextp, ticket->client, &client);
+    if (ret)
+       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_handlep);
+    if(ret)
+       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 contextp,
+            krb5_keytab keytab,
+            krb5_socket_t sock)
+{
+    u_char buf[sizeof(KRB5_SENDAUTH_VERSION) + 4];
+    ssize_t n;
+    unsigned long len;
+
+    n = krb5_net_read(contextp, &sock, buf, 4);
+    if(n == 0)
+       exit(0);
+    if(n < 0)
+       krb5_err(contextp, 1, errno, "read");
+    _krb5_get_int(buf, &len, 4);
+
+    if (len == sizeof(KRB5_SENDAUTH_VERSION)) {
+
+       n = krb5_net_read(contextp, &sock, buf + 4, len);
+       if (n < 0)
+           krb5_err (contextp, 1, errno, "reading sendauth version");
+       if (n == 0)
+           krb5_errx (contextp, 1, "EOF reading sendauth version");
+
+       if(memcmp(buf + 4, KRB5_SENDAUTH_VERSION, len) == 0) {
+           handle_v5(contextp, keytab, sock);
+           return 0;
+       }
+       len += 4;
+    } else
+       len = 4;
+
+    handle_mit(contextp, buf, len, sock);
+
+    return 0;
 }
+