krb5 commit: Add alias support

ghudson at mit.edu ghudson at mit.edu
Thu Feb 27 13:17:40 EST 2025


https://github.com/krb5/krb5/commit/5d3fe31bf1dc48e8ee946bf65428611958cac329
commit 5d3fe31bf1dc48e8ee946bf65428611958cac329
Author: Greg Hudson <ghudson at mit.edu>
Date:   Tue Jan 14 21:25:51 2025 -0500

    Add alias support
    
    Add a new kadmin command add_alias.  Implement it for DB2 and LMDB by
    writing stub principal entries with a tl-data entry giving the target
    name.  Add libkdb5 functions to create and interpret alias entries.
    Handle these stub entries in krb5_db_get_principal(), iteratively
    resolving aliases up to a depth of 10.
    
    To allow kadm5_delete_principal() to work on aliases, remove the code
    that fetches the entry prior to deletion; it was needed before commit
    0780e46fc13dbafa177525164997cd204cc50b51 to decrement the policy
    reference count, but now serves no purpose.  Adjust kdb_delete_entry()
    to translate KRB5_KDB_NOENTRY instead of ignoring it, as we still want
    to return KADM5_UNK_PRINC when deleting a nonexistent principal name.
    
    Modify the LDAP KDB module to work with alias entries.  In
    krb5_ldap_put_principal(), recognize stub alias entries and add an
    alias to the object for the target principal.  In
    krb5_ldap_delete_principal(), don't delete the LDAP object when
    deleting an alias name.  In krb5_ldap_iterate(), generate stub entries
    for each alias name in addition to the populated entry for the
    canonical name.  A small amount of refactoring was done as part of
    this work: the LDAP-specific principal name parsing and unparsing
    functions were simplified, and a helper function search_princ() was
    added to find the LDAP object for a principal name.
    
    In kdb5_util tabdump, add a dump type "alias" to display a list of
    aliases in the database.
    
    Based on work by Alexander Bokovoy.

 doc/admin/admin_commands/kadmin_local.rst          |  22 +-
 doc/admin/admin_commands/kdb5_util.rst             |   8 +
 doc/admin/conf_ldap.rst                            |   7 +-
 doc/admin/database.rst                             |   4 +
 src/include/kdb.h                                  |  28 +-
 src/include/krb5/kadm5_auth_plugin.h               |  11 +-
 src/include/krb5/kadm5_hook_plugin.h               |   8 +-
 src/kadmin/cli/kadmin.c                            |  49 +++
 src/kadmin/cli/kadmin.h                            |   2 +
 src/kadmin/cli/kadmin_ct.ct                        |   3 +
 src/kadmin/dbutil/tabdump.c                        |  38 ++
 src/kadmin/server/auth.c                           |   8 +-
 src/kadmin/server/auth.h                           |   2 +
 src/kadmin/server/auth_acl.c                       |  14 +
 src/kadmin/server/kadm_rpc_svc.c                   |   7 +
 src/kadmin/server/server_stubs.c                   | 259 ++++++++-----
 src/lib/kadm5/admin.h                              |   3 +
 src/lib/kadm5/admin_xdr.h                          |   1 +
 src/lib/kadm5/clnt/client_principal.c              |  20 +
 src/lib/kadm5/clnt/client_rpc.c                    |   8 +
 src/lib/kadm5/clnt/libkadm5clnt_mit.exports        |   2 +
 src/lib/kadm5/kadm_err.et                          |   1 +
 src/lib/kadm5/kadm_rpc.h                           |  12 +
 src/lib/kadm5/kadm_rpc_xdr.c                       |  15 +
 src/lib/kadm5/server_internal.h                    |   7 +
 src/lib/kadm5/srv/kadm5_hook.c                     |  10 +-
 src/lib/kadm5/srv/libkadm5srv_mit.exports          |   2 +
 src/lib/kadm5/srv/server_kdb.c                     |   4 +-
 src/lib/kadm5/srv/svr_principal.c                  |  49 ++-
 src/lib/kdb/kdb5.c                                 | 112 +++++-
 src/lib/kdb/libkdb5.exports                        |   2 +
 src/lib/krb5/error_tables/kdb5_err.et              |   1 +
 src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c  | 227 ++++++-----
 src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h  |   6 +-
 src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c | 429 ++++++++++++---------
 src/tests/Makefile.in                              |   1 +
 src/tests/t_alias.py                               | 124 ++++++
 src/tests/t_kadmin_acl.py                          | 102 ++++-
 src/tests/t_kdb.py                                 |  44 ++-
 src/tests/t_tabdump.py                             |   6 +
 40 files changed, 1238 insertions(+), 420 deletions(-)

diff --git a/doc/admin/admin_commands/kadmin_local.rst b/doc/admin/admin_commands/kadmin_local.rst
index 2435b3c36..b4edc7924 100644
--- a/doc/admin/admin_commands/kadmin_local.rst
+++ b/doc/admin/admin_commands/kadmin_local.rst
@@ -460,6 +460,24 @@ This command requires the **add** and **delete** privileges.
 
 Alias: **renprinc**
 
+.. _add_alias:
+
+add_alias
+~~~~~~~~~
+
+    **add_alias** *alias_princ* *target_princ*
+
+Create an alias *alias_princ* pointing to *target_princ*.  Aliases may
+be chained (that is, *target_princ* may itself be an alias) up to a
+depth of 10.
+
+This command requires the **add** privilege for *alias_princ* and the
+**modify** privilege for *target_princ*.
+
+(New in release 1.22.)
+
+Aliases: **alias**
+
 .. _delete_principal:
 
 delete_principal
@@ -467,8 +485,8 @@ delete_principal
 
     **delete_principal** [**-force**] *principal*
 
-Deletes the specified *principal* from the database.  This command
-prompts for deletion, unless the **-force** option is given.
+Deletes the specified *principal* or alias from the database.  This
+command prompts for deletion, unless the **-force** option is given.
 
 This command requires the **delete** privilege.
 
diff --git a/doc/admin/admin_commands/kdb5_util.rst b/doc/admin/admin_commands/kdb5_util.rst
index 444c58bcd..8147e9766 100644
--- a/doc/admin/admin_commands/kdb5_util.rst
+++ b/doc/admin/admin_commands/kdb5_util.rst
@@ -376,6 +376,14 @@ Options:
 
 Dump types:
 
+**alias**
+    principal alias information
+
+    **aliasname**
+        the name of the alias
+    **targetname**
+        the target of the alias
+
 **keydata**
     principal encryption key information, including actual key data
     (which is still encrypted in the master key)
diff --git a/doc/admin/conf_ldap.rst b/doc/admin/conf_ldap.rst
index 65542c1a4..908dfd1e7 100644
--- a/doc/admin/conf_ldap.rst
+++ b/doc/admin/conf_ldap.rst
@@ -112,9 +112,10 @@ Configuring Kerberos with OpenLDAP back-end
     details.
 
 With the LDAP back end it is possible to provide aliases for principal
-entries.  Currently we provide no administrative utilities for
-creating aliases, so it must be done by direct manipulation of the
-LDAP entries.
+entries.  Beginning in release 1.22, aliases can be added with the
+kadmin **add_alias** command, but it is also possible (in release 1.7
+or later) to provide aliases through direct manipulation of the LDAP
+entries.
 
 An entry with aliases contains multiple values of the
 *krbPrincipalName* attribute.  Since LDAP attribute values are not
diff --git a/doc/admin/database.rst b/doc/admin/database.rst
index 2fd07242a..685ec272f 100644
--- a/doc/admin/database.rst
+++ b/doc/admin/database.rst
@@ -93,6 +93,10 @@ To view the attributes of a principal, use the kadmin`
 To generate a listing of principals, use the kadmin
 **list_principals** command.
 
+To give a principal additional names, use the kadmin **add_alias**
+command to create aliases to the principal (new in release 1.22).
+Aliases can be removed with the **delete_principal** command.
+
 
 .. _policies:
 
diff --git a/src/include/kdb.h b/src/include/kdb.h
index d2252d505..7beee28df 100644
--- a/src/include/kdb.h
+++ b/src/include/kdb.h
@@ -263,6 +263,8 @@ typedef struct __krb5_key_salt_tuple {
  * must use the get_strings and set_string RPCs. */
 #define KRB5_TL_STRING_ATTRS            0x000b
 
+#define KRB5_TL_ALIAS_TARGET            0x000c
+
 #define KRB5_TL_PAC_LOGON_INFO          0x0100 /* NDR encoded validation info */
 #define KRB5_TL_SERVER_REFERRAL         0x0200 /* ASN.1 encoded ServerReferralInfo */
 #define KRB5_TL_SVR_REFERRAL_DATA       0x0300 /* ASN.1 encoded PA-SVR-REFERRAL-DATA */
@@ -850,6 +852,17 @@ krb5_dbe_free_strings(krb5_context, krb5_string_attr *, int count);
 void
 krb5_dbe_free_string(krb5_context, char *);
 
+/* Set *out to a stub alias entry pointing to target. */
+krb5_error_code
+krb5_dbe_make_alias_entry(krb5_context context, krb5_const_principal alias,
+                          krb5_const_principal target, krb5_db_entry **out);
+
+/* If entry contains a stub alias entry, set *target_out to the alias target.
+ * If not, set *target_out to NULL and return 0. */
+krb5_error_code
+krb5_dbe_read_alias(krb5_context context, krb5_db_entry *entry,
+                    krb5_principal *target_out);
+
 /*
  * Register the KDB keytab type, allowing "KDB:" to be used as a keytab name.
  * For this type to work, the context used for keytab operations must have an
@@ -1080,13 +1093,18 @@ typedef struct _kdb_vftabl {
      * an entry are expected to contain correct values, regardless of whether
      * they are specified in the mask, so it is acceptable for a module to
      * ignore the mask and update the entire entry.
+     *
+     * If the module has its own representation of principal aliases, this
+     * method should recognize alias stub entries using krb5_dbe_read_alias()
+     * and should create the alias instead of storing the stub entry.
      */
     krb5_error_code (*put_principal)(krb5_context kcontext,
                                      krb5_db_entry *entry, char **db_args);
 
     /*
-     * Optional: Delete the entry for the principal search_for.  If the
-     * principal did not exist, return KRB5_KDB_NOENTRY.
+     * Optional: Delete search_for from the database.  If the principal did not
+     * exist, return KRB5_KDB_NOENTRY.  If search_for is an alias, delete the
+     * alias, not the entry for the canonical principal.
      */
     krb5_error_code (*delete_principal)(krb5_context kcontext,
                                         krb5_const_principal search_for);
@@ -1094,7 +1112,7 @@ typedef struct _kdb_vftabl {
     /*
      * Optional with default: Rename a principal.  If the source principal does
      * not exist, return KRB5_KDB_NOENTRY.  If the target exists, return an
-     * error.
+     * error.  This method will not be called if source is an alias.
      *
      * NOTE: If the module chooses to implement a custom function for renaming
      * a principal instead of using the default, then rename operations will
@@ -1109,6 +1127,10 @@ typedef struct _kdb_vftabl {
      * arguments func_arg and the entry data.  If match_entry is specified, the
      * module may narrow the iteration to principal names matching that regular
      * expression; a module may alternatively ignore match_entry.
+     *
+     * If the module has its own representation of principal aliases, this
+     * method should invoke func with a stub alias entry for each alias,
+     * created using krb5_dbe_make_alias_entry().
      */
     krb5_error_code (*iterate)(krb5_context kcontext,
                                char *match_entry,
diff --git a/src/include/krb5/kadm5_auth_plugin.h b/src/include/krb5/kadm5_auth_plugin.h
index d514e99be..3f9392362 100644
--- a/src/include/krb5/kadm5_auth_plugin.h
+++ b/src/include/krb5/kadm5_auth_plugin.h
@@ -34,7 +34,7 @@
  *
  * The kadm5_auth pluggable interface currently has only one supported major
  * version, which is 1.  Major version 1 has a current minor version number of
- * 1.
+ * 2.
  *
  * kadm5_auth plugin modules should define a function named
  * kadm5_auth_<modulename>_initvt, matching the signature:
@@ -244,6 +244,13 @@ typedef krb5_error_code
 (*kadm5_auth_iprop_fn)(krb5_context context, kadm5_auth_moddata data,
                        krb5_const_principal client);
 
+/* Optional: authorize an add-alias operation. */
+typedef krb5_error_code
+(*kadm5_auth_addalias_fn)(krb5_context context, kadm5_auth_moddata data,
+                          krb5_const_principal client,
+                          krb5_const_principal alias_princ,
+                          krb5_const_principal target_princ);
+
 /*
  * Optional: receive a notification that the most recent authorized operation
  * has ended.  If a kadm5_auth module is also a KDB module, it can assume that
@@ -301,6 +308,8 @@ typedef struct kadm5_auth_vtable_st {
 
     kadm5_auth_free_restrictions_fn free_restrictions;
     /* Minor version 1 ends here. */
+    kadm5_auth_addalias_fn addalias;
+    /* Minor version 2 ends here. */
 } *kadm5_auth_vtable;
 
 #endif /* KRB5_KADM5_AUTH_PLUGIN_H */
diff --git a/src/include/krb5/kadm5_hook_plugin.h b/src/include/krb5/kadm5_hook_plugin.h
index f4f3730f4..cca6d6350 100644
--- a/src/include/krb5/kadm5_hook_plugin.h
+++ b/src/include/krb5/kadm5_hook_plugin.h
@@ -47,7 +47,7 @@
  * does not provide strong guarantees of ABI stability.
  *
  * The kadm5_hook interface currently has only one supported major version,
- * which is 1.  Major version 1 has a current minor version number of 2.
+ * which is 1.  Major version 1 has a current minor version number of 3.
  *
  * kadm5_hook plugins should:
  * kadm5_hook_<modulename>_initvt, matching the signature:
@@ -149,6 +149,12 @@ typedef struct kadm5_hook_vtable_1_st {
 
     /* End of minor version 2. */
 
+    kadm5_ret_t (*alias)(krb5_context context, kadm5_hook_modinfo *modinfo,
+                         int stage, krb5_principal alias,
+                         krb5_principal target);
+
+    /* End of minor version 3. */
+
 } kadm5_hook_vftable_1;
 
 #endif /*H_KRB5_KADM5_HOOK_PLUGIN*/
diff --git a/src/kadmin/cli/kadmin.c b/src/kadmin/cli/kadmin.c
index 372457039..f40c69e1c 100644
--- a/src/kadmin/cli/kadmin.c
+++ b/src/kadmin/cli/kadmin.c
@@ -774,6 +774,55 @@ cleanup:
     free(ocanon);
 }
 
+void
+kadmin_addalias(int argc, char *argv[], int sci_idx, void *info_ptr)
+{
+    kadm5_ret_t retval;
+    krb5_principal alias = NULL, target = NULL;
+    char *acanon = NULL, *tcanon = NULL;
+
+    if (argc != 3) {
+        error(_("usage: add_alias alias_principal target_principal\n"));
+        return;
+    }
+    retval = kadmin_parse_name(argv[1], &alias);
+    if (retval) {
+        com_err("add_alias", retval, _("while parsing alias principal name"));
+        goto cleanup;
+    }
+    retval = kadmin_parse_name(argv[2], &target);
+    if (retval) {
+        com_err("add_alias", retval, _("while parsing target principal name"));
+        goto cleanup;
+    }
+    retval = krb5_unparse_name(context, alias, &acanon);
+    if (retval) {
+        com_err("add_alias", retval,
+                _("while canonicalizing alias principal"));
+        goto cleanup;
+    }
+    retval = krb5_unparse_name(context, target, &tcanon);
+    if (retval) {
+        com_err("add_alias", retval,
+                _("while canonicalizing target principal"));
+        goto cleanup;
+    }
+    retval = kadm5_create_alias(handle, alias, target);
+    if (retval) {
+        com_err("add_alias", retval,
+                _("while aliasing principal \"%s\" to \"%s\""),
+                acanon, tcanon);
+        goto cleanup;
+    }
+    info(_("Principal \"%s\" aliased to \"%s\".\n"), acanon, tcanon);
+
+cleanup:
+    krb5_free_principal(context, alias);
+    krb5_free_principal(context, target);
+    free(acanon);
+    free(tcanon);
+}
+
 static void
 cpw_usage(const char *str)
 {
diff --git a/src/kadmin/cli/kadmin.h b/src/kadmin/cli/kadmin.h
index f08eac153..bf63d0430 100644
--- a/src/kadmin/cli/kadmin.h
+++ b/src/kadmin/cli/kadmin.h
@@ -42,6 +42,8 @@ extern void kadmin_delprinc(int argc, char *argv[], int sci_idx,
                             void *info_ptr);
 extern void kadmin_renameprinc(int argc, char *argv[], int sci_idx,
                                void *info_ptr);
+extern void kadmin_addalias(int argc, char *argv[], int sci_idx,
+                            void *info_ptr);
 extern void kadmin_cpw(int argc, char *argv[], int sci_idx, void *info_ptr);
 extern void kadmin_addprinc(int argc, char *argv[], int sci_idx,
                             void *info_ptr);
diff --git a/src/kadmin/cli/kadmin_ct.ct b/src/kadmin/cli/kadmin_ct.ct
index 705e41840..62fb4d6a8 100644
--- a/src/kadmin/cli/kadmin_ct.ct
+++ b/src/kadmin/cli/kadmin_ct.ct
@@ -38,6 +38,9 @@ request kadmin_modprinc, "Modify principal",
 request kadmin_renameprinc, "Rename principal",
 	rename_principal, renprinc;
 
+request kadmin_addalias, "Add alias",
+	add_alias, alias;
+
 request kadmin_cpw, "Change password",
 	change_password, cpw;
 
diff --git a/src/kadmin/dbutil/tabdump.c b/src/kadmin/dbutil/tabdump.c
index da55c2d78..d4b7b7333 100644
--- a/src/kadmin/dbutil/tabdump.c
+++ b/src/kadmin/dbutil/tabdump.c
@@ -69,6 +69,7 @@ struct tdtype {
     tdump_policy_fn *policy_fn;
 };
 
+static tdump_princ_fn alias;
 static tdump_princ_fn keydata;
 static tdump_princ_fn keyinfo;
 static tdump_princ_fn princ_flags;
@@ -98,9 +99,13 @@ static char * const princ_stringattrs_fields[] = {
 static char * const princ_tktpolicy_fields[] = {
     "name", "expiration", "pw_expiration", "max_life", "max_renew_life", NULL
 };
+static char * const alias_fields[] = {
+    "aliasname", "targetname", NULL
+};
 
 /* Lookup table for tabdump record types */
 static struct tdtype tdtypes[] = {
+    {"alias", alias_fields, alias, NULL},
     {"keydata", keydata_fields, keydata, NULL},
     {"keyinfo", keyinfo_fields, keyinfo, NULL},
     {"princ_flags", princ_flags_fields, princ_flags, NULL},
@@ -252,6 +257,39 @@ write_data(struct rec_args *args, krb5_data *data)
     return ret;
 }
 
+static krb5_error_code
+alias(struct rec_args *args, const char *name, krb5_db_entry *dbe)
+{
+    krb5_error_code ret;
+    struct rechandle *h = args->rh;
+    krb5_principal target = NULL;
+    char *tname = NULL;
+
+    ret = krb5_dbe_read_alias(util_context, dbe, &target);
+    if (ret)
+        return ret;
+    if (target == NULL)
+        return 0;
+
+    ret = krb5_unparse_name(util_context, target, &tname);
+    if (ret)
+        goto cleanup;
+
+    if (startrec(h) < 0)
+        ret = errno;
+    if (!ret && writefield(h, "%s", name) < 0)
+        ret = errno;
+    if (!ret && writefield(h, "%s", tname) < 0)
+        ret = errno;
+    if (!ret && endrec(h) < 0)
+        ret = errno;
+
+cleanup:
+    krb5_free_principal(util_context, target);
+    krb5_free_unparsed_name(util_context, tname);
+    return ret;
+}
+
 /* Write a single record of a keydata/keyinfo key set. */
 static krb5_error_code
 keyinfo_rec(struct rec_args *args, const char *name, int i, krb5_key_data *kd,
diff --git a/src/kadmin/server/auth.c b/src/kadmin/server/auth.c
index 081b20a8b..224b12190 100644
--- a/src/kadmin/server/auth.c
+++ b/src/kadmin/server/auth.c
@@ -91,7 +91,7 @@ auth_init(krb5_context context, const char *acl_file)
         h = k5alloc(sizeof(*h), &ret);
         if (h == NULL)
             goto cleanup;
-        ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&h->vt);
+        ret = (*mod)(context, 1, 2, (krb5_plugin_vtable)&h->vt);
         if (ret) {              /* Failed vtable init is non-fatal. */
             TRACE_KADM5_AUTH_VTINIT_FAIL(context, ret);
             free(h);
@@ -172,6 +172,8 @@ call_module(krb5_context context, auth_handle h, int opcode,
         return h->vt.listpols(context, h->data, client);
     else if (opcode == OP_IPROP && h->vt.iprop != NULL)
         return h->vt.iprop(context, h->data, client);
+    else if (opcode == OP_ADDALIAS && h->vt.addalias != NULL)
+        return h->vt.addalias(context, h->data, client, p1, p2);
 
     return KRB5_PLUGIN_NO_HANDLE;
 }
@@ -264,12 +266,12 @@ impose_restrictions(krb5_context context,
 
 krb5_boolean
 auth_restrict(krb5_context context, int opcode, krb5_const_principal client,
-              kadm5_principal_ent_t ent, long *mask)
+              krb5_const_principal target, kadm5_principal_ent_t ent,
+              long *mask)
 {
     auth_handle *hp, h;
     krb5_boolean authorized = FALSE;
     krb5_error_code ret, rs_ret;
-    krb5_const_principal target = ent->principal;
     struct kadm5_auth_restrictions *rs;
 
     assert(opcode == OP_ADDPRINC || opcode == OP_MODPRINC);
diff --git a/src/kadmin/server/auth.h b/src/kadmin/server/auth.h
index 4d265add7..5f51a9911 100644
--- a/src/kadmin/server/auth.h
+++ b/src/kadmin/server/auth.h
@@ -52,6 +52,7 @@
 #define OP_GETPOL      17
 #define OP_LISTPOLS    18
 #define OP_IPROP       19
+#define OP_ADDALIAS    20
 
 /* Initialize all authorization modules. */
 krb5_error_code auth_init(krb5_context context, const char *acl_file);
@@ -70,6 +71,7 @@ krb5_boolean auth(krb5_context context, int opcode,
  * restrictions to ent and mask if any modules supply them. */
 krb5_boolean auth_restrict(krb5_context context, int opcode,
                            krb5_const_principal client,
+                           krb5_const_principal target,
                            kadm5_principal_ent_t ent, long *mask);
 
 /* Notify modules that the most recent authorized operation has ended. */
diff --git a/src/kadmin/server/auth_acl.c b/src/kadmin/server/auth_acl.c
index ce9ace36a..a04fb23db 100644
--- a/src/kadmin/server/auth_acl.c
+++ b/src/kadmin/server/auth_acl.c
@@ -720,6 +720,19 @@ acl_iprop(krb5_context context, kadm5_auth_moddata data,
     return acl_check(data, ACL_IPROP, client, NULL, NULL);
 }
 
+static krb5_error_code
+acl_addalias(krb5_context context, kadm5_auth_moddata data,
+             krb5_const_principal client, krb5_const_principal alias,
+             krb5_const_principal target)
+{
+    struct kadm5_auth_restrictions *rs;
+
+    if (acl_check(data, ACL_ADD, client, alias, &rs) == 0 && rs == NULL &&
+        acl_check(data, ACL_MODIFY, client, target, &rs) == 0)
+        return 0;
+    return KRB5_PLUGIN_NO_HANDLE;
+}
+
 krb5_error_code
 kadm5_auth_acl_initvt(krb5_context context, int maj_ver, int min_ver,
                       krb5_plugin_vtable vtable)
@@ -751,5 +764,6 @@ kadm5_auth_acl_initvt(krb5_context context, int maj_ver, int min_ver,
     vt->getpol = acl_getpol;
     vt->listpols = acl_listpols;
     vt->iprop = acl_iprop;
+    vt->addalias = acl_addalias;
     return 0;
 }
diff --git a/src/kadmin/server/kadm_rpc_svc.c b/src/kadmin/server/kadm_rpc_svc.c
index f0e43d9ae..867d8b040 100644
--- a/src/kadmin/server/kadm_rpc_svc.c
+++ b/src/kadmin/server/kadm_rpc_svc.c
@@ -59,6 +59,7 @@ kadm_1(struct svc_req *rqstp, SVCXPRT *transp)
 	  setkey3_arg setkey_principal3_2_arg;
 	  setkey4_arg setkey_principal4_2_arg;
 	  getpkeys_arg get_principal_keys_2_arg;
+	  calias_arg create_alias_2_arg;
      } argument;
      union {
 	  generic_ret gen_ret;
@@ -241,6 +242,12 @@ kadm_1(struct svc_req *rqstp, SVCXPRT *transp)
 	  local = (bool_t (*)(char *, void *, struct svc_req *))get_principal_keys_2_svc;
 	  break;
 
+     case CREATE_ALIAS:
+	  xdr_argument = (xdrproc_t)xdr_calias_arg;
+	  xdr_result = (xdrproc_t)xdr_generic_ret;
+	  local = (bool_t (*)(char *, void *, struct svc_req *))create_alias_2_svc;
+	  break;
+
      default:
 	  krb5_klog_syslog(LOG_ERR, "Invalid KADM5 procedure number: %s, %d",
 			   client_addr(rqstp->rq_xprt), rqstp->rq_proc);
diff --git a/src/kadmin/server/server_stubs.c b/src/kadmin/server/server_stubs.c
index 87f67874a..b2371e67a 100644
--- a/src/kadmin/server/server_stubs.c
+++ b/src/kadmin/server/server_stubs.c
@@ -265,7 +265,7 @@ static kadm5_ret_t
 stub_setup(krb5_ui_4 api_version, struct svc_req *rqstp, krb5_principal princ,
            kadm5_server_handle_t *handle_out, krb5_ui_4 *api_version_out,
            gss_buffer_t client_name_out, gss_buffer_t service_name_out,
-           char **princ_str_out)
+           char **princ_str_out, kadm5_principal_ent_rec *rec_out)
 {
     kadm5_ret_t ret;
 
@@ -289,6 +289,13 @@ stub_setup(krb5_ui_4 api_version, struct svc_req *rqstp, krb5_principal princ,
             return KADM5_BAD_PRINCIPAL;
     }
 
+    if (rec_out != NULL) {
+        if (princ == NULL)
+            return KADM5_BAD_PRINCIPAL;
+        return kadm5_get_principal(*handle_out, princ, rec_out,
+                                   KADM5_PRINCIPAL | KADM5_ATTRIBUTES);
+    }
+
     return KADM5_OK;
 }
 
@@ -324,10 +331,11 @@ stub_auth_pol(kadm5_server_handle_t handle, int opcode, const char *policy,
 
 static krb5_boolean
 stub_auth_restrict(kadm5_server_handle_t handle, int opcode,
-                   kadm5_principal_ent_t ent, long *mask)
+                   krb5_const_principal princ, kadm5_principal_ent_t ent,
+                   long *mask)
 {
     return auth_restrict(handle->context, opcode, handle->current_caller,
-                         ent, mask);
+                         princ, ent, mask);
 }
 
 /* Return true if the client authenticated to kadmin/changepw and princ is not
@@ -440,12 +448,13 @@ create_principal_2_svc(cprinc_arg *arg, generic_ret *ret,
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->rec.principal,
                            &handle, &ret->api_version, &client_name,
-                           &service_name, &prime_arg);
+                           &service_name, &prime_arg, NULL);
     if (ret->code)
         goto exit_func;
 
     if (CHANGEPW_SERVICE(rqstp) ||
-        !stub_auth_restrict(handle, OP_ADDPRINC, &arg->rec, &arg->mask)) {
+        !stub_auth_restrict(handle, OP_ADDPRINC, arg->rec.principal,
+                            &arg->rec, &arg->mask)) {
         ret->code = KADM5_AUTH_ADD;
         log_unauth("kadm5_create_principal", prime_arg,
                    &client_name, &service_name, rqstp);
@@ -480,12 +489,13 @@ create_principal3_2_svc(cprinc3_arg *arg, generic_ret *ret,
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->rec.principal,
                            &handle, &ret->api_version, &client_name,
-                           &service_name, &prime_arg);
+                           &service_name, &prime_arg, NULL);
     if (ret->code)
         goto exit_func;
 
     if (CHANGEPW_SERVICE(rqstp) ||
-        !stub_auth_restrict(handle, OP_ADDPRINC, &arg->rec, &arg->mask)) {
+        !stub_auth_restrict(handle, OP_ADDPRINC, arg->rec.principal, &arg->rec,
+                            &arg->mask)) {
         ret->code = KADM5_AUTH_ADD;
         log_unauth("kadm5_create_principal", prime_arg,
                    &client_name, &service_name, rqstp);
@@ -508,9 +518,15 @@ exit_func:
     return TRUE;
 }
 
-/* Return KADM5_PROTECT_KEYS if KRB5_KDB_LOCKDOWN_KEYS is set for princ. */
+/* Return KADM5_PROTECT_KEYS if KRB5_KDB_LOCKDOWN_KEYS is set for rec. */
+static inline kadm5_ret_t
+check_lockdown(kadm5_principal_ent_t rec)
+{
+    return (rec->attributes & KRB5_KDB_LOCKDOWN_KEYS) ? KADM5_PROTECT_KEYS : 0;
+}
+
 static kadm5_ret_t
-check_lockdown_keys(kadm5_server_handle_t handle, krb5_principal princ)
+check_lockdown_by_princ(kadm5_server_handle_t handle, krb5_principal princ)
 {
     kadm5_principal_ent_rec rec;
     kadm5_ret_t ret;
@@ -518,7 +534,7 @@ check_lockdown_keys(kadm5_server_handle_t handle, krb5_principal princ)
     ret = kadm5_get_principal(handle, princ, &rec, KADM5_ATTRIBUTES);
     if (ret)
         return ret;
-    ret = (rec.attributes & KRB5_KDB_LOCKDOWN_KEYS) ? KADM5_PROTECT_KEYS : 0;
+    ret = check_lockdown(&rec);
     kadm5_free_principal_ent(handle, &rec);
     return ret;
 }
@@ -535,7 +551,7 @@ delete_principal_2_svc(dprinc_arg *arg, generic_ret *ret,
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           &prime_arg);
+                           &prime_arg, NULL);
     if (ret->code)
         goto exit_func;
 
@@ -545,7 +561,7 @@ delete_principal_2_svc(dprinc_arg *arg, generic_ret *ret,
         log_unauth("kadm5_delete_principal", prime_arg,
                    &client_name, &service_name, rqstp);
     } else {
-        ret->code = check_lockdown_keys(handle, arg->princ);
+        ret->code = check_lockdown_by_princ(handle, arg->princ);
         if (ret->code == KADM5_PROTECT_KEYS) {
             log_unauth("kadm5_delete_principal", prime_arg, &client_name,
                        &service_name, rqstp);
@@ -577,6 +593,7 @@ modify_principal_2_svc(mprinc_arg *arg, generic_ret *ret,
                        struct svc_req *rqstp)
 {
     char                            *prime_arg = NULL;
+    kadm5_principal_ent_rec         rec = { 0 }, rec_copy;
     gss_buffer_desc                 client_name = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc                 service_name = GSS_C_EMPTY_BUFFER;
     kadm5_server_handle_t           handle;
@@ -584,18 +601,19 @@ modify_principal_2_svc(mprinc_arg *arg, generic_ret *ret,
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->rec.principal,
                            &handle, &ret->api_version, &client_name,
-                           &service_name, &prime_arg);
+                           &service_name, &prime_arg, &rec);
     if (ret->code)
         goto exit_func;
 
     if (CHANGEPW_SERVICE(rqstp) ||
-        !stub_auth_restrict(handle, OP_MODPRINC, &arg->rec, &arg->mask)) {
+        !stub_auth_restrict(handle, OP_MODPRINC, rec.principal, &arg->rec,
+                            &arg->mask)) {
         ret->code = KADM5_AUTH_MODIFY;
         log_unauth("kadm5_modify_principal", prime_arg,
                    &client_name, &service_name, rqstp);
     } else if ((arg->mask & KADM5_ATTRIBUTES) &&
                (!(arg->rec.attributes & KRB5_KDB_LOCKDOWN_KEYS))) {
-        ret->code = check_lockdown_keys(handle, arg->rec.principal);
+        ret->code = check_lockdown(&rec);
         if (ret->code == KADM5_PROTECT_KEYS) {
             log_unauth("kadm5_modify_principal", prime_arg, &client_name,
                        &service_name, rqstp);
@@ -604,7 +622,11 @@ modify_principal_2_svc(mprinc_arg *arg, generic_ret *ret,
     }
 
     if (ret->code == KADM5_OK) {
-        ret->code = kadm5_modify_principal(handle, &arg->rec, arg->mask);
+        /* Modify via the canonicalized principal name using a shallow copy of
+         * arg->rec, to ensure consistency with the ACL check. */
+        rec_copy = arg->rec;
+        rec_copy.principal = rec.principal;
+        ret->code = kadm5_modify_principal(handle, &rec_copy, arg->mask);
         if (ret->code != 0)
             errmsg = krb5_get_error_message(handle->context, ret->code);
 
@@ -616,6 +638,7 @@ modify_principal_2_svc(mprinc_arg *arg, generic_ret *ret,
     }
 
 exit_func:
+    kadm5_free_principal_ent(handle, &rec);
     stub_cleanup(handle, prime_arg, &client_name, &service_name);
     return TRUE;
 }
@@ -634,7 +657,7 @@ rename_principal_2_svc(rprinc_arg *arg, generic_ret *ret,
 
     ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           NULL);
+                           NULL, NULL);
     if (ret->code)
         goto exit_func;
 
@@ -658,7 +681,7 @@ rename_principal_2_svc(rprinc_arg *arg, generic_ret *ret,
         log_unauth("kadm5_rename_principal", prime_arg1, &client_name,
                    &service_name, rqstp);
     } else {
-        ret->code = check_lockdown_keys(handle, arg->src);
+        ret->code = check_lockdown_by_princ(handle, arg->src);
         if (ret->code == KADM5_PROTECT_KEYS) {
             log_unauth("kadm5_rename_principal", prime_arg1, &client_name,
                        &service_name, rqstp);
@@ -708,6 +731,7 @@ bool_t
 get_principal_2_svc(gprinc_arg *arg, gprinc_ret *ret, struct svc_req *rqstp)
 {
     char                            *funcname, *prime_arg = NULL;
+    kadm5_principal_ent_rec         rec = { 0 };
     gss_buffer_desc                 client_name = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc                 service_name = GSS_C_EMPTY_BUFFER;
     kadm5_server_handle_t           handle;
@@ -715,19 +739,19 @@ get_principal_2_svc(gprinc_arg *arg, gprinc_ret *ret, struct svc_req *rqstp)
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           &prime_arg);
+                           &prime_arg, &rec);
     if (ret->code)
         goto exit_func;
 
     funcname = "kadm5_get_principal";
 
-    if (changepw_not_self(handle, rqstp, arg->princ) ||
-        !stub_auth(handle, OP_GETPRINC, arg->princ, NULL, NULL, NULL)) {
+    if (changepw_not_self(handle, rqstp, rec.principal) ||
+        !stub_auth(handle, OP_GETPRINC, rec.principal, NULL, NULL, NULL)) {
         ret->code = KADM5_AUTH_GET;
         log_unauth(funcname, prime_arg,
                    &client_name, &service_name, rqstp);
     } else {
-        ret->code = kadm5_get_principal(handle, arg->princ, &ret->rec,
+        ret->code = kadm5_get_principal(handle, rec.principal, &ret->rec,
                                         arg->mask);
 
         if (ret->code != 0)
@@ -741,6 +765,7 @@ get_principal_2_svc(gprinc_arg *arg, gprinc_ret *ret, struct svc_req *rqstp)
     }
 
 exit_func:
+    kadm5_free_principal_ent(handle, &rec);
     stub_cleanup(handle, prime_arg, &client_name, &service_name);
     return TRUE;
 }
@@ -756,7 +781,7 @@ get_princs_2_svc(gprincs_arg *arg, gprincs_ret *ret, struct svc_req *rqstp)
 
     ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           NULL);
+                           NULL, NULL);
     if (ret->code)
         goto exit_func;
 
@@ -793,6 +818,7 @@ chpass_principal_2_svc(chpass_arg *arg, generic_ret *ret,
                        struct svc_req *rqstp)
 {
     char                            *prime_arg = NULL;
+    kadm5_principal_ent_rec         rec = { 0 };
     gss_buffer_desc                 client_name = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc                 service_name = GSS_C_EMPTY_BUFFER;
     kadm5_server_handle_t           handle;
@@ -800,26 +826,28 @@ chpass_principal_2_svc(chpass_arg *arg, generic_ret *ret,
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           &prime_arg);
+                           &prime_arg, &rec);
     if (ret->code)
         goto exit_func;
 
-    ret->code = check_lockdown_keys(handle, arg->princ);
+    ret->code = check_lockdown(&rec);
     if (ret->code != KADM5_OK) {
         if (ret->code == KADM5_PROTECT_KEYS) {
             log_unauth("kadm5_chpass_principal", prime_arg, &client_name,
                        &service_name, rqstp);
             ret->code = KADM5_AUTH_CHANGEPW;
         }
-    } else if (changepw_not_self(handle, rqstp, arg->princ) ||
-               !stub_auth(handle, OP_CPW, arg->princ, NULL, NULL, NULL)) {
+    } else if (changepw_not_self(handle, rqstp, rec.principal) ||
+               !stub_auth(handle, OP_CPW, rec.principal, NULL, NULL, NULL)) {
         ret->code = KADM5_AUTH_CHANGEPW;
         log_unauth("kadm5_chpass_principal", prime_arg,
                    &client_name, &service_name, rqstp);
     } else {
-        ret->code = check_self_keychange(handle, rqstp, arg->princ);
-        if (!ret->code)
-            ret->code = kadm5_chpass_principal(handle, arg->princ, arg->pass);
+        ret->code = check_self_keychange(handle, rqstp, rec.principal);
+        if (!ret->code) {
+            ret->code = kadm5_chpass_principal(handle, rec.principal,
+                                               arg->pass);
+        }
     }
 
     if (ret->code != KADM5_AUTH_CHANGEPW) {
@@ -834,6 +862,7 @@ chpass_principal_2_svc(chpass_arg *arg, generic_ret *ret,
     }
 
 exit_func:
+    kadm5_free_principal_ent(handle, &rec);
     stub_cleanup(handle, prime_arg, &client_name, &service_name);
     return TRUE;
 }
@@ -843,6 +872,7 @@ chpass_principal3_2_svc(chpass3_arg *arg, generic_ret *ret,
                         struct svc_req *rqstp)
 {
     char                            *prime_arg = NULL;
+    kadm5_principal_ent_rec         rec = { 0 };
     gss_buffer_desc                 client_name = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc                 service_name = GSS_C_EMPTY_BUFFER;
     kadm5_server_handle_t           handle;
@@ -850,26 +880,26 @@ chpass_principal3_2_svc(chpass3_arg *arg, generic_ret *ret,
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           &prime_arg);
+                           &prime_arg, &rec);
     if (ret->code)
         goto exit_func;
 
-    ret->code = check_lockdown_keys(handle, arg->princ);
+    ret->code = check_lockdown(&rec);
     if (ret->code != KADM5_OK) {
         if (ret->code == KADM5_PROTECT_KEYS) {
             log_unauth("kadm5_chpass_principal", prime_arg, &client_name,
                        &service_name, rqstp);
             ret->code = KADM5_AUTH_CHANGEPW;
         }
-    } else if (changepw_not_self(handle, rqstp, arg->princ) ||
-               !stub_auth(handle, OP_CPW, arg->princ, NULL, NULL, NULL)) {
+    } else if (changepw_not_self(handle, rqstp, rec.principal) ||
+               !stub_auth(handle, OP_CPW, rec.principal, NULL, NULL, NULL)) {
         ret->code = KADM5_AUTH_CHANGEPW;
         log_unauth("kadm5_chpass_principal", prime_arg,
                    &client_name, &service_name, rqstp);
     } else  {
-        ret->code = check_self_keychange(handle, rqstp, arg->princ);
+        ret->code = check_self_keychange(handle, rqstp, rec.principal);
         if (!ret->code) {
-            ret->code = kadm5_chpass_principal_3(handle, arg->princ,
+            ret->code = kadm5_chpass_principal_3(handle, rec.principal,
                                                  arg->keepold, arg->n_ks_tuple,
                                                  arg->ks_tuple, arg->pass);
         }
@@ -887,6 +917,7 @@ chpass_principal3_2_svc(chpass3_arg *arg, generic_ret *ret,
     }
 
 exit_func:
+    kadm5_free_principal_ent(handle, &rec);
     stub_cleanup(handle, prime_arg, &client_name, &service_name);
     return TRUE;
 }
@@ -896,6 +927,7 @@ setkey_principal_2_svc(setkey_arg *arg, generic_ret *ret,
                        struct svc_req *rqstp)
 {
     char                            *prime_arg = NULL;
+    kadm5_principal_ent_rec         rec = { 0 };
     gss_buffer_desc                 client_name = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc                 service_name = GSS_C_EMPTY_BUFFER;
     kadm5_server_handle_t           handle;
@@ -903,11 +935,11 @@ setkey_principal_2_svc(setkey_arg *arg, generic_ret *ret,
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           &prime_arg);
+                           &prime_arg, &rec);
     if (ret->code)
         goto exit_func;
 
-    ret->code = check_lockdown_keys(handle, arg->princ);
+    ret->code = check_lockdown(&rec);
     if (ret->code != KADM5_OK) {
         if (ret->code == KADM5_PROTECT_KEYS) {
             log_unauth("kadm5_setkey_principal", prime_arg, &client_name,
@@ -915,9 +947,9 @@ setkey_principal_2_svc(setkey_arg *arg, generic_ret *ret,
             ret->code = KADM5_AUTH_SETKEY;
         }
     } else if (!(CHANGEPW_SERVICE(rqstp)) &&
-               stub_auth(handle, OP_SETKEY, arg->princ, NULL, NULL, NULL)) {
-        ret->code = kadm5_setkey_principal(handle, arg->princ, arg->keyblocks,
-                                           arg->n_keys);
+               stub_auth(handle, OP_SETKEY, rec.principal, NULL, NULL, NULL)) {
+        ret->code = kadm5_setkey_principal(handle, rec.principal,
+                                           arg->keyblocks, arg->n_keys);
     } else {
         log_unauth("kadm5_setkey_principal", prime_arg,
                    &client_name, &service_name, rqstp);
@@ -936,6 +968,7 @@ setkey_principal_2_svc(setkey_arg *arg, generic_ret *ret,
     }
 
 exit_func:
+    kadm5_free_principal_ent(handle, &rec);
     stub_cleanup(handle, prime_arg, &client_name, &service_name);
     return TRUE;
 }
@@ -945,6 +978,7 @@ setkey_principal3_2_svc(setkey3_arg *arg, generic_ret *ret,
                         struct svc_req *rqstp)
 {
     char                            *prime_arg = NULL;
+    kadm5_principal_ent_rec         rec = { 0 };
     gss_buffer_desc                 client_name = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc                 service_name = GSS_C_EMPTY_BUFFER;
     kadm5_server_handle_t           handle;
@@ -952,11 +986,11 @@ setkey_principal3_2_svc(setkey3_arg *arg, generic_ret *ret,
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           &prime_arg);
+                           &prime_arg, &rec);
     if (ret->code)
         goto exit_func;
 
-    ret->code = check_lockdown_keys(handle, arg->princ);
+    ret->code = check_lockdown(&rec);
     if (ret->code != KADM5_OK) {
         if (ret->code == KADM5_PROTECT_KEYS) {
             log_unauth("kadm5_setkey_principal", prime_arg, &client_name,
@@ -964,10 +998,11 @@ setkey_principal3_2_svc(setkey3_arg *arg, generic_ret *ret,
             ret->code = KADM5_AUTH_SETKEY;
         }
     } else if (!(CHANGEPW_SERVICE(rqstp)) &&
-               stub_auth(handle, OP_SETKEY, arg->princ, NULL, NULL, NULL)) {
-        ret->code = kadm5_setkey_principal_3(handle, arg->princ, arg->keepold,
-                                             arg->n_ks_tuple, arg->ks_tuple,
-                                             arg->keyblocks, arg->n_keys);
+               stub_auth(handle, OP_SETKEY, rec.principal, NULL, NULL, NULL)) {
+        ret->code = kadm5_setkey_principal_3(handle, rec.principal,
+                                             arg->keepold, arg->n_ks_tuple,
+                                             arg->ks_tuple, arg->keyblocks,
+                                             arg->n_keys);
     } else {
         log_unauth("kadm5_setkey_principal", prime_arg,
                    &client_name, &service_name, rqstp);
@@ -986,6 +1021,7 @@ setkey_principal3_2_svc(setkey3_arg *arg, generic_ret *ret,
     }
 
 exit_func:
+    kadm5_free_principal_ent(handle, &rec);
     stub_cleanup(handle, prime_arg, &client_name, &service_name);
     return TRUE;
 }
@@ -995,6 +1031,7 @@ setkey_principal4_2_svc(setkey4_arg *arg, generic_ret *ret,
                         struct svc_req *rqstp)
 {
     char                            *prime_arg = NULL;
+    kadm5_principal_ent_rec         rec = { 0 };
     gss_buffer_desc                 client_name = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc                 service_name = GSS_C_EMPTY_BUFFER;
     kadm5_server_handle_t           handle;
@@ -1002,11 +1039,11 @@ setkey_principal4_2_svc(setkey4_arg *arg, generic_ret *ret,
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           &prime_arg);
+                           &prime_arg, &rec);
     if (ret->code)
         goto exit_func;
 
-    ret->code = check_lockdown_keys(handle, arg->princ);
+    ret->code = check_lockdown(&rec);
     if (ret->code != KADM5_OK) {
         if (ret->code == KADM5_PROTECT_KEYS) {
             log_unauth("kadm5_setkey_principal", prime_arg, &client_name,
@@ -1014,9 +1051,10 @@ setkey_principal4_2_svc(setkey4_arg *arg, generic_ret *ret,
             ret->code = KADM5_AUTH_SETKEY;
         }
     } else if (!(CHANGEPW_SERVICE(rqstp)) &&
-               stub_auth(handle, OP_SETKEY, arg->princ, NULL, NULL, NULL)) {
-        ret->code = kadm5_setkey_principal_4(handle, arg->princ, arg->keepold,
-                                             arg->key_data, arg->n_key_data);
+               stub_auth(handle, OP_SETKEY, rec.principal, NULL, NULL, NULL)) {
+        ret->code = kadm5_setkey_principal_4(handle, rec.principal,
+                                             arg->keepold, arg->key_data,
+                                             arg->n_key_data);
     } else {
         log_unauth("kadm5_setkey_principal", prime_arg, &client_name,
                    &service_name, rqstp);
@@ -1035,6 +1073,7 @@ setkey_principal4_2_svc(setkey4_arg *arg, generic_ret *ret,
     }
 
 exit_func:
+    kadm5_free_principal_ent(handle, &rec);
     stub_cleanup(handle, prime_arg, &client_name, &service_name);
     return TRUE;
 }
@@ -1042,13 +1081,13 @@ exit_func:
 /* Empty out *keys / *nkeys if princ is protected with the lockdown
  * attribute, or if we fail to check. */
 static kadm5_ret_t
-chrand_check_lockdown(kadm5_server_handle_t handle, krb5_principal princ,
+chrand_check_lockdown(kadm5_server_handle_t handle, kadm5_principal_ent_t rec,
                       krb5_keyblock **keys, int *nkeys)
 {
     kadm5_ret_t ret;
     int i;
 
-    ret = check_lockdown_keys(handle, princ);
+    ret = check_lockdown(rec);
     if (!ret)
         return 0;
 
@@ -1064,6 +1103,7 @@ bool_t
 chrand_principal_2_svc(chrand_arg *arg, chrand_ret *ret, struct svc_req *rqstp)
 {
     char                        *funcname, *prime_arg = NULL;
+    kadm5_principal_ent_rec     rec = { 0 };
     gss_buffer_desc             client_name = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc             service_name = GSS_C_EMPTY_BUFFER;
     krb5_keyblock               *k;
@@ -1073,27 +1113,27 @@ chrand_principal_2_svc(chrand_arg *arg, chrand_ret *ret, struct svc_req *rqstp)
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           &prime_arg);
+                           &prime_arg, &rec);
     if (ret->code)
         goto exit_func;
 
     funcname = "kadm5_randkey_principal";
 
-    if (changepw_not_self(handle, rqstp, arg->princ) ||
-        !stub_auth(handle, OP_CHRAND, arg->princ, NULL, NULL, NULL)) {
+    if (changepw_not_self(handle, rqstp, rec.principal) ||
+        !stub_auth(handle, OP_CHRAND, rec.principal, NULL, NULL, NULL)) {
         ret->code = KADM5_AUTH_CHANGEPW;
         log_unauth(funcname, prime_arg,
                    &client_name, &service_name, rqstp);
     } else {
-        ret->code = check_self_keychange(handle, rqstp, arg->princ);
+        ret->code = check_self_keychange(handle, rqstp, rec.principal);
         if (!ret->code) {
-            ret->code = kadm5_randkey_principal(handle, arg->princ,
+            ret->code = kadm5_randkey_principal(handle, rec.principal,
                                                 &k, &nkeys);
         }
     }
 
     if (ret->code == KADM5_OK) {
-        ret->code = chrand_check_lockdown(handle, arg->princ, &k, &nkeys);
+        ret->code = chrand_check_lockdown(handle, &rec, &k, &nkeys);
         if (ret->code == KADM5_PROTECT_KEYS)
             ret->code = KADM5_OK;
         ret->keys = k;
@@ -1112,6 +1152,7 @@ chrand_principal_2_svc(chrand_arg *arg, chrand_ret *ret, struct svc_req *rqstp)
     }
 
 exit_func:
+    kadm5_free_principal_ent(handle, &rec);
     stub_cleanup(handle, prime_arg, &client_name, &service_name);
     return TRUE;
 }
@@ -1121,6 +1162,7 @@ chrand_principal3_2_svc(chrand3_arg *arg, chrand_ret *ret,
                         struct svc_req *rqstp)
 {
     char                        *funcname, *prime_arg = NULL;
+    kadm5_principal_ent_rec     rec = { 0 };
     gss_buffer_desc             client_name = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc             service_name = GSS_C_EMPTY_BUFFER;
     krb5_keyblock               *k;
@@ -1130,21 +1172,21 @@ chrand_principal3_2_svc(chrand3_arg *arg, chrand_ret *ret,
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           &prime_arg);
+                           &prime_arg, &rec);
     if (ret->code)
         goto exit_func;
 
     funcname = "kadm5_randkey_principal";
 
-    if (changepw_not_self(handle, rqstp, arg->princ) ||
-        !stub_auth(handle, OP_CHRAND, arg->princ, NULL, NULL, NULL)) {
+    if (changepw_not_self(handle, rqstp, rec.principal) ||
+        !stub_auth(handle, OP_CHRAND, rec.principal, NULL, NULL, NULL)) {
         ret->code = KADM5_AUTH_CHANGEPW;
         log_unauth(funcname, prime_arg,
                    &client_name, &service_name, rqstp);
     } else {
-        ret->code = check_self_keychange(handle, rqstp, arg->princ);
+        ret->code = check_self_keychange(handle, rqstp, rec.principal);
         if (!ret->code) {
-            ret->code = kadm5_randkey_principal_3(handle, arg->princ,
+            ret->code = kadm5_randkey_principal_3(handle, rec.principal,
                                                   arg->keepold,
                                                   arg->n_ks_tuple,
                                                   arg->ks_tuple, &k, &nkeys);
@@ -1152,7 +1194,7 @@ chrand_principal3_2_svc(chrand3_arg *arg, chrand_ret *ret,
     }
 
     if (ret->code == KADM5_OK) {
-        ret->code = chrand_check_lockdown(handle, arg->princ, &k, &nkeys);
+        ret->code = chrand_check_lockdown(handle, &rec, &k, &nkeys);
         if (ret->code == KADM5_PROTECT_KEYS)
             ret->code = KADM5_OK;
         ret->keys = k;
@@ -1171,6 +1213,7 @@ chrand_principal3_2_svc(chrand3_arg *arg, chrand_ret *ret,
     }
 
 exit_func:
+    kadm5_free_principal_ent(handle, &rec);
     stub_cleanup(handle, prime_arg, &client_name, &service_name);
     return TRUE;
 }
@@ -1186,7 +1229,7 @@ create_policy_2_svc(cpol_arg *arg, generic_ret *ret, struct svc_req *rqstp)
 
     ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           NULL);
+                           NULL, NULL);
     if (ret->code)
         goto exit_func;
 
@@ -1228,7 +1271,7 @@ delete_policy_2_svc(dpol_arg *arg, generic_ret *ret, struct svc_req *rqstp)
 
     ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           NULL);
+                           NULL, NULL);
     if (ret->code)
         goto exit_func;
 
@@ -1268,7 +1311,7 @@ modify_policy_2_svc(mpol_arg *arg, generic_ret *ret, struct svc_req *rqstp)
 
     ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           NULL);
+                           NULL, NULL);
     if (ret->code)
         goto exit_func;
 
@@ -1313,7 +1356,7 @@ get_policy_2_svc(gpol_arg *arg, gpol_ret *ret, struct svc_req *rqstp)
 
     ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           NULL);
+                           NULL, NULL);
     if (ret->code)
         goto exit_func;
 
@@ -1362,7 +1405,7 @@ get_pols_2_svc(gpols_arg *arg, gpols_ret *ret, struct svc_req *rqstp)
 
     ret->code = stub_setup(arg->api_version, rqstp, NULL, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           NULL);
+                           NULL, NULL);
     if (ret->code)
         goto exit_func;
 
@@ -1402,7 +1445,7 @@ get_privs_2_svc(krb5_ui_4 *arg, getprivs_ret *ret, struct svc_req *rqstp)
     const char                     *errmsg = NULL;
 
     ret->code = stub_setup(*arg, rqstp, NULL, &handle, &ret->api_version,
-                           &client_name, &service_name, NULL);
+                           &client_name, &service_name, NULL, NULL);
     if (ret->code)
         goto exit_func;
 
@@ -1425,6 +1468,7 @@ bool_t
 purgekeys_2_svc(purgekeys_arg *arg, generic_ret *ret, struct svc_req *rqstp)
 {
     char                        *funcname, *prime_arg = NULL;
+    kadm5_principal_ent_rec     rec = { 0 };
     gss_buffer_desc             client_name = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc             service_name = GSS_C_EMPTY_BUFFER;
     kadm5_server_handle_t       handle;
@@ -1433,18 +1477,18 @@ purgekeys_2_svc(purgekeys_arg *arg, generic_ret *ret, struct svc_req *rqstp)
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           &prime_arg);
+                           &prime_arg, &rec);
     if (ret->code)
         goto exit_func;
 
     funcname = "kadm5_purgekeys";
 
     if (CHANGEPW_SERVICE(rqstp) ||
-        !stub_auth(handle, OP_PURGEKEYS, arg->princ, NULL, NULL, NULL)) {
+        !stub_auth(handle, OP_PURGEKEYS, rec.principal, NULL, NULL, NULL)) {
         ret->code = KADM5_AUTH_MODIFY;
         log_unauth(funcname, prime_arg, &client_name, &service_name, rqstp);
     } else {
-        ret->code = kadm5_purgekeys(handle, arg->princ, arg->keepkvno);
+        ret->code = kadm5_purgekeys(handle, rec.principal, arg->keepkvno);
         if (ret->code != 0)
             errmsg = krb5_get_error_message(handle->context, ret->code);
 
@@ -1456,6 +1500,7 @@ purgekeys_2_svc(purgekeys_arg *arg, generic_ret *ret, struct svc_req *rqstp)
     }
 
 exit_func:
+    kadm5_free_principal_ent(handle, &rec);
     stub_cleanup(handle, prime_arg, &client_name, &service_name);
     return TRUE;
 }
@@ -1464,6 +1509,7 @@ bool_t
 get_strings_2_svc(gstrings_arg *arg, gstrings_ret *ret, struct svc_req *rqstp)
 {
     char                            *prime_arg = NULL;
+    kadm5_principal_ent_rec         rec = { 0 };
     gss_buffer_desc                 client_name = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc                 service_name = GSS_C_EMPTY_BUFFER;
     kadm5_server_handle_t           handle;
@@ -1471,17 +1517,17 @@ get_strings_2_svc(gstrings_arg *arg, gstrings_ret *ret, struct svc_req *rqstp)
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           &prime_arg);
+                           &prime_arg, &rec);
     if (ret->code)
         goto exit_func;
 
     if (CHANGEPW_SERVICE(rqstp) ||
-        !stub_auth(handle, OP_GETSTRS, arg->princ, NULL, NULL, NULL)) {
+        !stub_auth(handle, OP_GETSTRS, rec.principal, NULL, NULL, NULL)) {
         ret->code = KADM5_AUTH_GET;
         log_unauth("kadm5_get_strings", prime_arg,
                    &client_name, &service_name, rqstp);
     } else {
-        ret->code = kadm5_get_strings(handle, arg->princ, &ret->strings,
+        ret->code = kadm5_get_strings(handle, rec.principal, &ret->strings,
                                       &ret->count);
         if (ret->code != 0)
             errmsg = krb5_get_error_message(handle->context, ret->code);
@@ -1494,6 +1540,7 @@ get_strings_2_svc(gstrings_arg *arg, gstrings_ret *ret, struct svc_req *rqstp)
     }
 
 exit_func:
+    kadm5_free_principal_ent(handle, &rec);
     stub_cleanup(handle, prime_arg, &client_name, &service_name);
     return TRUE;
 }
@@ -1502,6 +1549,7 @@ bool_t
 set_string_2_svc(sstring_arg *arg, generic_ret *ret, struct svc_req *rqstp)
 {
     char                            *prime_arg = NULL;
+    kadm5_principal_ent_rec         rec = { 0 };
     gss_buffer_desc                 client_name = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc                 service_name = GSS_C_EMPTY_BUFFER;
     kadm5_server_handle_t           handle;
@@ -1509,18 +1557,19 @@ set_string_2_svc(sstring_arg *arg, generic_ret *ret, struct svc_req *rqstp)
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           &prime_arg);
+                           &prime_arg, &rec);
     if (ret->code)
         goto exit_func;
 
     if (CHANGEPW_SERVICE(rqstp) ||
-        !stub_auth(handle, OP_SETSTR, arg->princ, NULL,
+        !stub_auth(handle, OP_SETSTR, rec.principal, NULL,
                    arg->key, arg->value)) {
         ret->code = KADM5_AUTH_MODIFY;
         log_unauth("kadm5_mod_strings", prime_arg,
                    &client_name, &service_name, rqstp);
     } else {
-        ret->code = kadm5_set_string(handle, arg->princ, arg->key, arg->value);
+        ret->code = kadm5_set_string(handle, rec.principal,
+                                     arg->key, arg->value);
         if (ret->code != 0)
             errmsg = krb5_get_error_message(handle->context, ret->code);
 
@@ -1532,6 +1581,7 @@ set_string_2_svc(sstring_arg *arg, generic_ret *ret, struct svc_req *rqstp)
     }
 
 exit_func:
+    kadm5_free_principal_ent(handle, &rec);
     stub_cleanup(handle, prime_arg, &client_name, &service_name);
     return TRUE;
 }
@@ -1547,7 +1597,7 @@ init_2_svc(krb5_ui_4 *arg, generic_ret *ret, struct svc_req *rqstp)
     char *cdots, *sdots;
 
     ret->code = stub_setup(*arg, rqstp, NULL, &handle, &ret->api_version,
-                           &client_name, &service_name, NULL);
+                           &client_name, &service_name, NULL, NULL);
     if (ret->code)
         goto exit_func;
 
@@ -1592,6 +1642,7 @@ get_principal_keys_2_svc(getpkeys_arg *arg, getpkeys_ret *ret,
                          struct svc_req *rqstp)
 {
     char                            *prime_arg = NULL;
+    kadm5_principal_ent_rec         rec = { 0 };
     gss_buffer_desc                 client_name = GSS_C_EMPTY_BUFFER;
     gss_buffer_desc                 service_name = GSS_C_EMPTY_BUFFER;
     kadm5_server_handle_t           handle;
@@ -1599,13 +1650,13 @@ get_principal_keys_2_svc(getpkeys_arg *arg, getpkeys_ret *ret,
 
     ret->code = stub_setup(arg->api_version, rqstp, arg->princ, &handle,
                            &ret->api_version, &client_name, &service_name,
-                           &prime_arg);
+                           &prime_arg, &rec);
     if (ret->code)
         goto exit_func;
 
     if (!(CHANGEPW_SERVICE(rqstp)) &&
-        stub_auth(handle, OP_EXTRACT, arg->princ, NULL, NULL, NULL)) {
-        ret->code = kadm5_get_principal_keys(handle, arg->princ, arg->kvno,
+        stub_auth(handle, OP_EXTRACT, rec.principal, NULL, NULL, NULL)) {
+        ret->code = kadm5_get_principal_keys(handle, rec.principal, arg->kvno,
                                              &ret->key_data, &ret->n_key_data);
     } else {
         log_unauth("kadm5_get_principal_keys", prime_arg,
@@ -1614,7 +1665,7 @@ get_principal_keys_2_svc(getpkeys_arg *arg, getpkeys_ret *ret,
     }
 
     if (ret->code == KADM5_OK) {
-        ret->code = check_lockdown_keys(handle, arg->princ);
+        ret->code = check_lockdown(&rec);
         if (ret->code != KADM5_OK) {
             kadm5_free_kadm5_key_data(handle->context, ret->n_key_data,
                                       ret->key_data);
@@ -1639,6 +1690,42 @@ get_principal_keys_2_svc(getpkeys_arg *arg, getpkeys_ret *ret,
             krb5_free_error_message(handle->context, errmsg);
     }
 
+exit_func:
+    kadm5_free_principal_ent(handle, &rec);
+    stub_cleanup(handle, prime_arg, &client_name, &service_name);
+    return TRUE;
+}
+
+bool_t
+create_alias_2_svc(calias_arg *arg, generic_ret *ret, struct svc_req *rqstp)
+{
+    char                            *prime_arg = NULL;
+    gss_buffer_desc                 client_name = GSS_C_EMPTY_BUFFER;
+    gss_buffer_desc                 service_name = GSS_C_EMPTY_BUFFER;
+    kadm5_server_handle_t           handle;
+    const char                      *errmsg = NULL;
+
+    ret->code = stub_setup(arg->api_version, rqstp, arg->alias, &handle,
+                           &ret->api_version, &client_name, &service_name,
+                           &prime_arg, NULL);
+    if (ret->code)
+        goto exit_func;
+
+    if (CHANGEPW_SERVICE(rqstp) ||
+        !stub_auth(handle, OP_ADDALIAS, arg->alias, arg->target, NULL, NULL)) {
+        ret->code = KADM5_AUTH_INSUFFICIENT;
+        log_unauth("kadm5_create_alias", prime_arg, &client_name,
+                   &service_name, rqstp);
+    } else {
+        ret->code = kadm5_create_alias(handle, arg->alias, arg->target);
+        if (ret->code)
+            errmsg = krb5_get_error_message(handle->context, ret->code);
+        log_done("kadm5_create_alias", prime_arg, errmsg, &client_name,
+                 &service_name, rqstp);
+        if (errmsg != NULL)
+            krb5_free_error_message(handle->context, errmsg);
+    }
+
 exit_func:
     stub_cleanup(handle, prime_arg, &client_name, &service_name);
     return TRUE;
diff --git a/src/lib/kadm5/admin.h b/src/lib/kadm5/admin.h
index 296c86fa6..4af1ea225 100644
--- a/src/lib/kadm5/admin.h
+++ b/src/lib/kadm5/admin.h
@@ -495,6 +495,9 @@ kadm5_ret_t    kadm5_free_strings(void *server_handle,
 kadm5_ret_t    kadm5_free_kadm5_key_data(krb5_context context, int n_key_data,
                                          kadm5_key_data *key_data);
 
+kadm5_ret_t    kadm5_create_alias(void *server_handle, krb5_principal alias,
+                                  krb5_principal target);
+
 KADM5INT_END_DECLS
 
 #endif /* __KADM5_ADMIN_H__ */
diff --git a/src/lib/kadm5/admin_xdr.h b/src/lib/kadm5/admin_xdr.h
index 9da98451e..12a2598c2 100644
--- a/src/lib/kadm5/admin_xdr.h
+++ b/src/lib/kadm5/admin_xdr.h
@@ -71,3 +71,4 @@ bool_t      xdr_osa_pw_hist_ent(XDR *xdrs, osa_pw_hist_ent *objp);
 bool_t      xdr_kadm5_key_data(XDR *xdrs, kadm5_key_data *objp);
 bool_t      xdr_getpkeys_arg(XDR *xdrs, getpkeys_arg *objp);
 bool_t      xdr_getpkeys_ret(XDR *xdrs, getpkeys_ret *objp);
+bool_t      xdr_calias_arg(XDR *xdrs, calias_arg *objp);
diff --git a/src/lib/kadm5/clnt/client_principal.c b/src/lib/kadm5/clnt/client_principal.c
index 96d9d1932..976490093 100644
--- a/src/lib/kadm5/clnt/client_principal.c
+++ b/src/lib/kadm5/clnt/client_principal.c
@@ -526,3 +526,23 @@ kadm5_get_principal_keys(void *server_handle, krb5_principal princ,
     }
     return r.code;
 }
+
+kadm5_ret_t
+kadm5_create_alias(void *server_handle, krb5_principal alias,
+                   krb5_principal target)
+{
+    calias_arg          arg;
+    generic_ret         r = { 0, 0 };
+    kadm5_server_handle_t handle = server_handle;
+
+    CHECK_HANDLE(server_handle);
+
+    arg.alias = alias;
+    arg.target = target;
+    arg.api_version = handle->api_version;
+    if (alias == NULL || target == NULL)
+        return EINVAL;
+    if (create_alias_2(&arg, &r, handle->clnt))
+        eret();
+    return r.code;
+}
diff --git a/src/lib/kadm5/clnt/client_rpc.c b/src/lib/kadm5/clnt/client_rpc.c
index c8d844e4c..6dd56872a 100644
--- a/src/lib/kadm5/clnt/client_rpc.c
+++ b/src/lib/kadm5/clnt/client_rpc.c
@@ -212,3 +212,11 @@ get_principal_keys_2(getpkeys_arg *argp, getpkeys_ret *res, CLIENT *clnt)
 			 (xdrproc_t)xdr_getpkeys_arg, (caddr_t)argp,
 			 (xdrproc_t)xdr_getpkeys_ret, (caddr_t)res, TIMEOUT);
 }
+
+enum clnt_stat
+create_alias_2(calias_arg *argp, generic_ret *res, CLIENT *clnt)
+{
+	return clnt_call(clnt, CREATE_ALIAS,
+			 (xdrproc_t)xdr_calias_arg, (caddr_t)argp,
+			 (xdrproc_t)xdr_generic_ret, (caddr_t)res, TIMEOUT);
+}
diff --git a/src/lib/kadm5/clnt/libkadm5clnt_mit.exports b/src/lib/kadm5/clnt/libkadm5clnt_mit.exports
index 9ed7d52dc..43b81e8ae 100644
--- a/src/lib/kadm5/clnt/libkadm5clnt_mit.exports
+++ b/src/lib/kadm5/clnt/libkadm5clnt_mit.exports
@@ -3,6 +3,7 @@ _kadm5_chpass_principal_util
 kadm5_chpass_principal
 kadm5_chpass_principal_3
 kadm5_chpass_principal_util
+kadm5_create_alias
 kadm5_create_policy
 kadm5_create_principal
 kadm5_create_principal_3
@@ -62,6 +63,7 @@ krb5_klog_reopen
 krb5_klog_set_context
 krb5_klog_syslog
 krb5_string_to_keysalts
+xdr_calias_arg
 xdr_chpass3_arg
 xdr_chpass_arg
 xdr_chrand3_arg
diff --git a/src/lib/kadm5/kadm_err.et b/src/lib/kadm5/kadm_err.et
index cf07e8068..f58ddf6d7 100644
--- a/src/lib/kadm5/kadm_err.et
+++ b/src/lib/kadm5/kadm_err.et
@@ -67,4 +67,5 @@ error_code KADM5_SETKEY_BAD_KVNO, "Invalid multiple or duplicate kvnos in setkey
 error_code KADM5_AUTH_EXTRACT, "Operation requires ``extract-keys'' privilege"
 error_code KADM5_PROTECT_KEYS, "Principal keys are locked down"
 error_code KADM5_AUTH_INITIAL, "Operation requires initial ticket"
+error_code KADM5_ALIAS_REALM, "Alias target must be within the same realm"
 end
diff --git a/src/lib/kadm5/kadm_rpc.h b/src/lib/kadm5/kadm_rpc.h
index 9efe49a37..bde85478a 100644
--- a/src/lib/kadm5/kadm_rpc.h
+++ b/src/lib/kadm5/kadm_rpc.h
@@ -246,6 +246,13 @@ struct getpkeys_ret {
 };
 typedef struct getpkeys_ret getpkeys_ret;
 
+struct calias_arg {
+	krb5_ui_4 api_version;
+	krb5_principal alias;
+	krb5_principal target;
+};
+typedef struct calias_arg calias_arg;
+
 #define KADM 2112
 #define KADMVERS 2
 #define CREATE_PRINCIPAL 1
@@ -360,4 +367,9 @@ extern enum clnt_stat get_principal_keys_2(getpkeys_arg *, getpkeys_ret *,
 					   CLIENT *);
 extern  bool_t get_principal_keys_2_svc(getpkeys_arg *, getpkeys_ret *,
 					struct svc_req *);
+
+#define CREATE_ALIAS 27
+extern enum clnt_stat create_alias_2(calias_arg *, generic_ret *, CLIENT *);
+extern  bool_t create_alias_2_svc(calias_arg *, generic_ret *,
+				  struct svc_req *);
 #endif /* __KADM_RPC_H__ */
diff --git a/src/lib/kadm5/kadm_rpc_xdr.c b/src/lib/kadm5/kadm_rpc_xdr.c
index 5e052dd90..8a2def8ff 100644
--- a/src/lib/kadm5/kadm_rpc_xdr.c
+++ b/src/lib/kadm5/kadm_rpc_xdr.c
@@ -1209,3 +1209,18 @@ xdr_getpkeys_ret(XDR *xdrs, getpkeys_ret *objp)
 	}
 	return TRUE;
 }
+
+bool_t
+xdr_calias_arg(XDR *xdrs, calias_arg *objp)
+{
+	if (!xdr_ui_4(xdrs, &objp->api_version)) {
+		return (FALSE);
+	}
+	if (!xdr_krb5_principal(xdrs, &objp->alias)) {
+		return (FALSE);
+	}
+	if (!xdr_krb5_principal(xdrs, &objp->target)) {
+		return (FALSE);
+	}
+	return (TRUE);
+}
diff --git a/src/lib/kadm5/server_internal.h b/src/lib/kadm5/server_internal.h
index 433f4915b..ce2e9197b 100644
--- a/src/lib/kadm5/server_internal.h
+++ b/src/lib/kadm5/server_internal.h
@@ -264,6 +264,13 @@ k5_kadm5_hook_rename (krb5_context context,
                       int stage,
                       krb5_principal oprinc, krb5_principal nprinc);
 
+/** Call alias kadm5_hook entry point. */
+kadm5_ret_t
+k5_kadm5_hook_alias (krb5_context context,
+                     kadm5_hook_handle *handles,
+                     int stage,
+                     krb5_principal alias, krb5_principal target);
+
 /** @}*/
 
 #endif /* __KADM5_SERVER_INTERNAL_H__ */
diff --git a/src/lib/kadm5/srv/kadm5_hook.c b/src/lib/kadm5/srv/kadm5_hook.c
index df337bc32..c533d743a 100644
--- a/src/lib/kadm5/srv/kadm5_hook.c
+++ b/src/lib/kadm5/srv/kadm5_hook.c
@@ -64,7 +64,7 @@ k5_kadm5_hook_load(krb5_context context,
         handle = k5alloc(sizeof(*handle), &ret);
         if (handle == NULL)
             goto cleanup;
-        ret = (*mod)(context, 1, 2, (krb5_plugin_vtable)&handle->vt);
+        ret = (*mod)(context, 1, 3, (krb5_plugin_vtable)&handle->vt);
         if (ret != 0) {         /* Failed vtable init is non-fatal. */
             free(handle);
             handle = NULL;
@@ -184,3 +184,11 @@ k5_kadm5_hook_remove(krb5_context context, kadm5_hook_handle *handles,
     ITERATE(remove, (context, h->data, stage, princ));
     return 0;
 }
+
+kadm5_ret_t
+k5_kadm5_hook_alias(krb5_context context, kadm5_hook_handle *handles,
+                    int stage, krb5_principal alias, krb5_principal target)
+{
+    ITERATE(alias, (context, h->data, stage, alias, target));
+    return 0;
+}
diff --git a/src/lib/kadm5/srv/libkadm5srv_mit.exports b/src/lib/kadm5/srv/libkadm5srv_mit.exports
index 14c02a7f1..3b39ab590 100644
--- a/src/lib/kadm5/srv/libkadm5srv_mit.exports
+++ b/src/lib/kadm5/srv/libkadm5srv_mit.exports
@@ -4,6 +4,7 @@ hist_princ
 kadm5_chpass_principal
 kadm5_chpass_principal_3
 kadm5_chpass_principal_util
+kadm5_create_alias
 kadm5_create_policy
 kadm5_create_principal
 kadm5_create_principal_3
@@ -74,6 +75,7 @@ master_db
 master_princ
 osa_free_princ_ent
 passwd_check
+xdr_calias_arg
 xdr_chpass3_arg
 xdr_chpass_arg
 xdr_chrand3_arg
diff --git a/src/lib/kadm5/srv/server_kdb.c b/src/lib/kadm5/srv/server_kdb.c
index 4efcaf994..709e7f5d3 100644
--- a/src/lib/kadm5/srv/server_kdb.c
+++ b/src/lib/kadm5/srv/server_kdb.c
@@ -410,9 +410,7 @@ kdb_delete_entry(kadm5_server_handle_t handle, krb5_principal name)
     krb5_error_code ret;
 
     ret = krb5_db_delete_principal(handle->context, name);
-    if (ret == KRB5_KDB_NOENTRY)
-        ret = 0;
-    return ret;
+    return (ret == KRB5_KDB_NOENTRY) ? KADM5_UNK_PRINC : ret;
 }
 
 typedef struct _iter_data {
diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c
index 444c16ed7..1557937f2 100644
--- a/src/lib/kadm5/srv/svr_principal.c
+++ b/src/lib/kadm5/srv/svr_principal.c
@@ -520,8 +520,6 @@ kadm5_ret_t
 kadm5_delete_principal(void *server_handle, krb5_principal principal)
 {
     unsigned int                ret;
-    krb5_db_entry               *kdb;
-    osa_princ_ent_rec           adb;
     kadm5_server_handle_t handle = server_handle;
 
     CHECK_HANDLE(server_handle);
@@ -535,19 +533,13 @@ kadm5_delete_principal(void *server_handle, krb5_principal principal)
     if (krb5_principal_compare(handle->context, principal, master_princ))
         return KADM5_PROTECT_PRINCIPAL;
 
-    if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
-        return(ret);
     ret = k5_kadm5_hook_remove(handle->context, handle->hook_handles,
                                KADM5_HOOK_STAGE_PRECOMMIT, principal);
-    if (ret) {
-        kdb_free_entry(handle, kdb, &adb);
+    if (ret)
         return ret;
-    }
 
     ret = kdb_delete_entry(handle, principal);
 
-    kdb_free_entry(handle, kdb, &adb);
-
     if (ret == 0)
         (void) k5_kadm5_hook_remove(handle->context,
                                     handle->hook_handles,
@@ -2056,3 +2048,42 @@ done:
     kdb_free_entry(handle, kdb, &adb);
     return ret;
 }
+
+kadm5_ret_t
+kadm5_create_alias(void *server_handle, krb5_principal alias,
+                   krb5_principal target)
+{
+    krb5_db_entry *kdb;
+    osa_princ_ent_rec adb = { 0 };
+    krb5_error_code ret;
+    kadm5_server_handle_t handle = server_handle;
+
+    CHECK_HANDLE(server_handle);
+    if (alias == NULL || target == NULL)
+        return EINVAL;
+    if (!krb5_realm_compare(handle->context, alias, target))
+        return KADM5_ALIAS_REALM;
+
+    ret = kdb_get_entry(handle, alias, &kdb, NULL);
+    if (!ret) {
+        kdb_free_entry(handle, kdb, NULL);
+        return KADM5_DUP;
+    }
+
+    ret = k5_kadm5_hook_alias(handle->context, handle->hook_handles,
+                              KADM5_HOOK_STAGE_PRECOMMIT, alias, target);
+    if (ret)
+        return ret;
+
+    ret = krb5_dbe_make_alias_entry(handle->context, alias, target, &kdb);
+    if (ret)
+        return ret;
+    ret = kdb_put_entry(handle, kdb, &adb);
+    krb5_db_free_principal(handle->context, kdb);
+    if (ret)
+        return ret;
+
+    (void) k5_kadm5_hook_alias(handle->context, handle->hook_handles,
+                               KADM5_HOOK_STAGE_POSTCOMMIT, alias, target);
+    return 0;
+}
diff --git a/src/lib/kdb/kdb5.c b/src/lib/kdb/kdb5.c
index a0897f5e8..b6db28d8d 100644
--- a/src/lib/kdb/kdb5.c
+++ b/src/lib/kdb/kdb5.c
@@ -797,27 +797,49 @@ krb5_db_unlock(krb5_context kcontext)
     return v->unlock(kcontext);
 }
 
+#define MAX_ALIAS_DEPTH 10
+
 krb5_error_code
 krb5_db_get_principal(krb5_context kcontext, krb5_const_principal search_for,
-                      unsigned int flags, krb5_db_entry **entry)
+                      unsigned int flags, krb5_db_entry **entry_out)
 {
     krb5_error_code status = 0;
     kdb_vftabl *v;
+    krb5_db_entry *entry;
+    krb5_principal alias_target;
+    int alias_depth = 0;
 
-    *entry = NULL;
+    *entry_out = NULL;
     status = get_vftabl(kcontext, &v);
     if (status)
         return status;
     if (v->get_principal == NULL)
         return KRB5_PLUGIN_OP_NOTSUPP;
-    status = v->get_principal(kcontext, search_for, flags, entry);
+
+    status = v->get_principal(kcontext, search_for, flags, &entry);
     if (status)
         return status;
 
+    /* Resolve any aliases up to the maximum depth. */
+    for (;;) {
+        status = krb5_dbe_read_alias(kcontext, entry, &alias_target);
+        if (status)
+            return status;
+        if (alias_target == NULL)
+            break;
+        krb5_db_free_principal(kcontext, entry);
+        status = (++alias_depth > MAX_ALIAS_DEPTH) ? KRB5_KDB_NOENTRY :
+            v->get_principal(kcontext, alias_target, flags, &entry);
+        krb5_free_principal(kcontext, alias_target);
+        if (status)
+            return status;
+    }
+
     /* Sort the keys in the db entry as some parts of krb5 expect it to be. */
-    if ((*entry)->key_data != NULL)
-        krb5_dbe_sort_key_data((*entry)->key_data, (*entry)->n_key_data);
+    if (entry->key_data != NULL)
+        krb5_dbe_sort_key_data(entry->key_data, entry->n_key_data);
 
+    *entry_out = entry;
     return 0;
 }
 
@@ -1036,6 +1058,7 @@ krb5_db_rename_principal(krb5_context kcontext, krb5_principal source,
     kdb_vftabl *v;
     krb5_error_code status;
     krb5_db_entry *entry;
+    krb5_boolean eq;
 
     status = get_vftabl(kcontext, &v);
     if (status)
@@ -1050,6 +1073,15 @@ krb5_db_rename_principal(krb5_context kcontext, krb5_principal source,
         logging(kcontext))
         return KRB5_PLUGIN_OP_NOTSUPP;
 
+    /* Disallow the operation if source is an alias. */
+    status = krb5_db_get_principal(kcontext, source, 0, &entry);
+    if (status)
+        return status;
+    eq = krb5_principal_compare(kcontext, entry->princ, source);
+    krb5_db_free_principal(kcontext, entry);
+    if (!eq)
+        return KRB5_KDB_ALIAS_UNSUPPORTED;
+
     status = krb5_db_get_principal(kcontext, target, 0, &entry);
     if (status == 0) {
         krb5_db_free_principal(kcontext, entry);
@@ -2789,3 +2821,73 @@ krb5_db_issue_pac(krb5_context context, unsigned int flags,
     return v->issue_pac(context, flags, client, replaced_reply_key, server,
                         krbtgt, authtime, old_pac, new_pac, auth_indicators);
 }
+
+krb5_error_code
+krb5_dbe_make_alias_entry(krb5_context context, krb5_const_principal alias,
+                          krb5_const_principal target, krb5_db_entry **out)
+{
+    krb5_error_code ret;
+    krb5_principal princ = NULL;
+    char *target_str = NULL;
+    krb5_tl_data *tl = NULL;
+    krb5_db_entry *ent;
+
+    *out = NULL;
+
+    ret = krb5_copy_principal(context, alias, &princ);
+    if (ret)
+        goto cleanup;
+
+    ret = krb5_unparse_name(context, target, &target_str);
+    if (ret)
+        goto cleanup;
+    tl = k5alloc(sizeof(*tl), &ret);
+    if (tl == NULL)
+        goto cleanup;
+    tl->tl_data_next = NULL;
+    tl->tl_data_type = KRB5_TL_ALIAS_TARGET;
+    tl->tl_data_length = strlen(target_str) + 1;
+    tl->tl_data_contents = (uint8_t *)target_str;
+
+    ent = k5alloc(sizeof(*ent), &ret);
+    if (ent == NULL)
+        goto cleanup;
+    ent->len = KRB5_KDB_V1_BASE_LENGTH;
+    ent->attributes = KRB5_KDB_DISALLOW_ALL_TIX;
+    ent->princ = princ;
+    ent->tl_data = tl;
+    ent->n_tl_data = 1;
+    princ = NULL;
+    target_str = NULL;
+    tl = NULL;
+    *out = ent;
+
+cleanup:
+    krb5_free_principal(context, princ);
+    krb5_free_unparsed_name(context, target_str);
+    free(tl);
+    return ret;
+}
+
+krb5_error_code
+krb5_dbe_read_alias(krb5_context context, krb5_db_entry *entry,
+                    krb5_principal *target_out)
+{
+    krb5_error_code ret;
+    krb5_tl_data tl;
+
+    *target_out = NULL;
+
+    tl.tl_data_type = KRB5_TL_ALIAS_TARGET;
+    ret = krb5_dbe_lookup_tl_data(context, entry, &tl);
+    if (ret)
+        return ret;
+
+    if (tl.tl_data_length == 0)
+        return 0;
+
+    if (tl.tl_data_contents[tl.tl_data_length - 1] != '\0')
+        return KRB5_KDB_TRUNCATED_RECORD;
+
+    return krb5_parse_name(context, (char *)tl.tl_data_contents, target_out);
+}
diff --git a/src/lib/kdb/libkdb5.exports b/src/lib/kdb/libkdb5.exports
index 574bab92f..11cd0bdd1 100644
--- a/src/lib/kdb/libkdb5.exports
+++ b/src/lib/kdb/libkdb5.exports
@@ -107,3 +107,5 @@ ulog_replay
 ulog_set_last
 xdr_kdb_incr_update_t
 krb5_dbe_sort_key_data
+krb5_dbe_make_alias_entry
+krb5_dbe_read_alias
diff --git a/src/lib/krb5/error_tables/kdb5_err.et b/src/lib/krb5/error_tables/kdb5_err.et
index 1b08ec1a6..f803df1d3 100644
--- a/src/lib/krb5/error_tables/kdb5_err.et
+++ b/src/lib/krb5/error_tables/kdb5_err.et
@@ -85,5 +85,6 @@ ec KRB5_LOG_ERROR,		"Generic update log error"
 ec KRB5_KDB_DBTYPE_MISMATCH,    "Database module does not match KDC version"
 ec KRB5_KDB_POLICY_REF,		"Policy is in use"
 ec KRB5_KDB_STRINGS_TOOLONG,    "Too much string mapping data"
+ec KRB5_KDB_ALIAS_UNSUPPORTED,  "Operation unsupported on alias principal name"
 
 end
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c
index 4ff5219c2..234a2b53b 100644
--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c
@@ -128,6 +128,76 @@ krb5_dbe_free_contents(krb5_context context, krb5_db_entry *entry)
     return;
 }
 
+static krb5_error_code
+iterate_entry(krb5_context context, krb5_ldap_context *ldap_context,
+              LDAP *ld, LDAPMessage *ent,
+              krb5_error_code (*func)(krb5_pointer, krb5_db_entry *),
+              krb5_pointer func_arg)
+{
+    krb5_error_code ret = 0;
+    krb5_principal cprinc = NULL, nprinc = NULL;
+    krb5_db_entry entry = { 0 }, *entptr;
+    char **canon = NULL, **names = NULL, **list;
+    size_t i;
+
+    canon = ldap_get_values(ld, ent, "krbCanonicalName");
+    names = ldap_get_values(ld, ent, "krbPrincipalName");
+    if (canon == NULL && names == NULL)
+        return 0;
+
+    /* Output an entry for the canonical name if one is given.  Otherwise
+     * output an entry for the first name within the realm. */
+    list = (canon != NULL) ? canon : names;
+    for (i = 0; list[i] != NULL; i++) {
+        krb5_free_principal(context, nprinc);
+        nprinc = NULL;
+        ret = krb5_ldap_parse_name(context, list[i], &nprinc);
+        if (ret)
+            goto cleanup;
+
+        if (is_principal_in_realm(ldap_context, nprinc)) {
+            ret = populate_krb5_db_entry(context, ldap_context, ld, ent,
+                                         nprinc, &entry);
+            if (ret)
+                goto cleanup;
+            ret = (*func)(func_arg, &entry);
+            krb5_dbe_free_contents(context, &entry);
+            if (ret)
+                goto cleanup;
+            break;
+        }
+    }
+
+    /* Output alias entries for each non-canonical name. */
+    if (canon != NULL && names != NULL) {
+        ret = krb5_ldap_parse_name(context, canon[0], &cprinc);
+        if (ret)
+            goto cleanup;
+        for (i = 0; names[i] != NULL; i++) {
+            if (strcmp(names[i], canon[0]) == 0)
+                continue;
+            krb5_free_principal(context, nprinc);
+            nprinc = NULL;
+            ret = krb5_ldap_parse_name(context, names[i], &nprinc);
+            if (ret)
+                goto cleanup;
+            ret = krb5_dbe_make_alias_entry(context, nprinc, cprinc, &entptr);
+            if (ret)
+                goto cleanup;
+            ret = (*func)(func_arg, entptr);
+            krb5_db_free_principal(context, entptr);
+            if (ret)
+                goto cleanup;
+        }
+    }
+
+cleanup:
+    krb5_free_principal(context, cprinc);
+    krb5_free_principal(context, nprinc);
+    ldap_value_free(canon);
+    ldap_value_free(names);
+    return ret;
+}
 
 krb5_error_code
 krb5_ldap_iterate(krb5_context context, char *match_expr,
@@ -135,9 +205,8 @@ krb5_ldap_iterate(krb5_context context, char *match_expr,
                   krb5_pointer func_arg, krb5_flags iterflags)
 {
     krb5_db_entry            entry;
-    krb5_principal           principal;
-    char                     **subtree=NULL, *princ_name=NULL, *realm=NULL, **values=NULL, *filter=NULL;
-    size_t                   tree=0, ntree=1, i=0;
+    char                     **subtree=NULL, *realm=NULL, *filter=NULL;
+    size_t                   tree=0, ntree=1;
     krb5_error_code          st=0, tempst=0;
     LDAP                     *ld=NULL;
     LDAPMessage              *result=NULL, *ent=NULL;
@@ -181,33 +250,10 @@ krb5_ldap_iterate(krb5_context context, char *match_expr,
 
         LDAP_SEARCH(subtree[tree], ldap_context->lrparams->search_scope, filter, principal_attributes);
         for (ent=ldap_first_entry(ld, result); ent != NULL; ent=ldap_next_entry(ld, ent)) {
-            values=ldap_get_values(ld, ent, "krbcanonicalname");
-            if (values == NULL)
-                values=ldap_get_values(ld, ent, "krbprincipalname");
-            if (values != NULL) {
-                for (i=0; values[i] != NULL; ++i) {
-                    if (krb5_ldap_parse_principal_name(values[i], &princ_name) != 0)
-                        continue;
-                    st = krb5_parse_name(context, princ_name, &principal);
-                    free(princ_name);
-                    if (st)
-                        continue;
-
-                    if (is_principal_in_realm(ldap_context, principal)) {
-                        st = populate_krb5_db_entry(context, ldap_context, ld,
-                                                    ent, principal, &entry);
-                        krb5_free_principal(context, principal);
-                        if (st)
-                            goto cleanup;
-                        (*func)(func_arg, &entry);
-                        krb5_dbe_free_contents(context, &entry);
-                        break;
-                    }
-                    (void) krb5_free_principal(context, principal);
-                }
-                ldap_value_free(values);
-            }
-        } /* end of for (ent= ... */
+            st = iterate_entry(context, ldap_context, ld, ent, func, func_arg);
+            if (st)
+                goto cleanup;
+        }
         ldap_msgfree(result);
         result = NULL;
     } /* end of for (tree= ... */
@@ -268,15 +314,17 @@ krb5_ldap_delete_principal(krb5_context context,
 
     GET_HANDLE();
 
-    if (ptype == KDB_STANDALONE_PRINCIPAL_OBJECT) {
+    if (ptype == KDB_STANDALONE_PRINCIPAL_OBJECT &&
+        (pcount == 1 ||
+         krb5_principal_compare(context, searchfor, entry->princ))) {
         st = ldap_delete_ext_s(ld, DN, NULL, NULL);
         if (st != LDAP_SUCCESS) {
             st = set_ldap_error (context, st, OP_DEL);
             goto cleanup;
         }
     } else {
-        if (((st=krb5_unparse_name(context, searchfor, &user)) != 0)
-            || ((st=krb5_ldap_unparse_principal_name(user)) != 0))
+        st = krb5_ldap_unparse_name(context, searchfor, &user);
+        if (st)
             goto cleanup;
 
         memset(strval, 0, sizeof(strval));
@@ -362,35 +410,6 @@ is_standalone_principal(krb5_context kcontext, krb5_db_entry *entry, int *res)
     return code;
 }
 
-/*
- * Unparse princ in the format used for LDAP attributes, and set *user to the
- * result.
- */
-static krb5_error_code
-unparse_principal_name(krb5_context context, krb5_const_principal princ,
-                       char **user_out)
-{
-    krb5_error_code st;
-    char *luser = NULL;
-
-    *user_out = NULL;
-
-    st = krb5_unparse_name(context, princ, &luser);
-    if (st)
-        goto cleanup;
-
-    st = krb5_ldap_unparse_principal_name(luser);
-    if (st)
-        goto cleanup;
-
-    *user_out = luser;
-    luser = NULL;
-
-cleanup:
-    free(luser);
-    return st;
-}
-
 /*
  * Rename a principal's rdn.
  *
@@ -478,10 +497,10 @@ krb5_ldap_rename_principal(krb5_context context, krb5_const_principal source,
         goto cleanup;
     }
 
-    st = unparse_principal_name(context, source, &suser);
+    st = krb5_ldap_unparse_name(context, source, &suser);
     if (st)
         goto cleanup;
-    st = unparse_principal_name(context, target, &tuser);
+    st = krb5_ldap_unparse_name(context, target, &tuser);
     if (st)
         goto cleanup;
 
@@ -565,65 +584,63 @@ cleanup:
     return st;
 }
 
-/*
- * Function: krb5_ldap_unparse_principal_name
- *
- * Purpose: Removes '\\' that comes before every occurrence of '@'
- *          in the principal name component.
- *
- * Arguments:
- *       user_name     (input/output)      Principal name
- *
- */
-
+/* Unparse princ in the format used for krb5 principal names within LDAP
+ * attributes. */
 krb5_error_code
-krb5_ldap_unparse_principal_name(char *user_name)
+krb5_ldap_unparse_name(krb5_context context, krb5_const_principal princ,
+                       char **user_out)
 {
-    char *in, *out;
+    krb5_error_code ret;
+    char *p, *q;
+
+    ret = krb5_unparse_name(context, princ, user_out);
+    if (ret)
+        return ret;
 
-    out = user_name;
-    for (in = user_name; *in; in++) {
-        if (*in == '\\' && *(in + 1) == '@')
+    /* Remove backslashes preceding at-signs in the unparsed string. */
+    for (q = p = *user_out; *p != '\0'; p++) {
+        if (*p == '\\' && *(p + 1) == '@')
             continue;
-        *out++ = *in;
+        *q++ = *p;
     }
-    *out = '\0';
+    *q = '\0';
 
     return 0;
 }
 
-
-/*
- * Function: krb5_ldap_parse_principal_name
- *
- * Purpose: Inserts '\\' before every occurrence of '@'
- *          in the principal name component.
- *
- * Arguments:
- *       i_princ_name     (input)      Principal name without '\\'
- *       o_princ_name     (output)     Principal name with '\\'
- *
- * Note: The caller has to free the memory allocated for o_princ_name.
- */
-
+/* Parse username in the format used for krb5 principal names within LDAP
+ * attributes. */
 krb5_error_code
-krb5_ldap_parse_principal_name(char *i_princ_name, char **o_princ_name)
+krb5_ldap_parse_name(krb5_context context, const char *username,
+                     krb5_principal *out)
 {
-    const char *at_rlm_name, *p;
+    krb5_error_code ret;
+    const char *at_realm, *p;
+    char *princstr;
     struct k5buf buf;
 
-    at_rlm_name = strrchr(i_princ_name, '@');
-    if (!at_rlm_name) {
-        *o_princ_name = strdup(i_princ_name);
+    *out = NULL;
+
+    /* Make a copy of username, inserting a backslash before each '@'
+     * before the last one. */
+    at_realm = strrchr(username, '@');
+    if (at_realm == NULL) {
+        princstr = strdup(username);
     } else {
         k5_buf_init_dynamic(&buf);
-        for (p = i_princ_name; p < at_rlm_name; p++) {
+        for (p = username; p < at_realm; p++) {
             if (*p == '@')
                 k5_buf_add(&buf, "\\");
             k5_buf_add_len(&buf, p, 1);
         }
-        k5_buf_add(&buf, at_rlm_name);
-        *o_princ_name = k5_buf_cstring(&buf);
+        k5_buf_add(&buf, at_realm);
+        princstr = k5_buf_cstring(&buf);
     }
-    return (*o_princ_name == NULL) ? ENOMEM : 0;
+
+    if (princstr == NULL)
+        return ENOMEM;
+
+    ret = krb5_parse_name(context, princstr, out);
+    free(princstr);
+    return ret;
 }
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h
index 72a9f960b..71948aac3 100644
--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h
@@ -124,10 +124,12 @@ void
 krb5_dbe_free_contents(krb5_context, krb5_db_entry *);
 
 krb5_error_code
-krb5_ldap_unparse_principal_name(char *);
+krb5_ldap_unparse_name(krb5_context context, krb5_const_principal princ,
+                       char **user_out);
 
 krb5_error_code
-krb5_ldap_parse_principal_name(char *, char **);
+krb5_ldap_parse_name(krb5_context context, const char *username,
+                     krb5_principal *out);
 
 struct berval**
 krb5_encode_krbsecretkey(krb5_key_data *key_data, int n_key_data,
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
index d929d325c..ae4e03f8c 100644
--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
@@ -98,6 +98,110 @@ berval2tl_data(struct berval *in, krb5_tl_data **out)
     return 0;
 }
 
+/*
+ * Search for filter at the specified base and scope, expecting to find a
+ * single result.  Set *entry_out to an object containing all principal
+ * attributes.  Set *result_out to the result message to be freed with
+ * ldap_msgfree().  Set both to NULL if no matching entry is found.
+ */
+static krb5_error_code
+search_at(krb5_context context, krb5_ldap_context *ldap_context,
+          krb5_ldap_server_handle *ldap_server_handle, const char *base,
+          int scope, const char *filter, const char *user,
+          LDAPMessage **entry_out, LDAPMessage **result_out)
+{
+    krb5_error_code st, tempst;
+    LDAPMessage *result = NULL;
+    LDAP *ld = ldap_server_handle->ldap_handle;
+    int nentries;
+
+    *entry_out = *result_out = NULL;
+
+    LDAP_SEARCH(base, scope, filter, principal_attributes);
+    nentries = ldap_count_entries(ld, result);
+    if (nentries > 1) {
+        st = EINVAL;
+        k5_setmsg(context, st,
+                  _("Operation cannot continue; more than one "
+                    "entry with principal name \"%s\" found"),
+                  user);
+        goto cleanup;
+    }
+
+    if (nentries == 1) {
+        *result_out = result;
+        *entry_out = ldap_first_entry(ld, result);
+        return 0;
+    }
+
+cleanup:
+    ldap_msgfree(result);
+    return st;
+}
+
+/*
+ * Search for an LDAP object matching princ, either at the specified dn with
+ * base scope, or, if dn is NULL, in all configured subtrees with the
+ * configured search scope (usually subtree).  Set *entry_out and *result_out
+ * in the same way as search_at().
+ */
+static krb5_error_code
+search_princ(krb5_context context, krb5_ldap_context *ldap_context,
+             krb5_ldap_server_handle *ldap_server_handle,
+             krb5_const_principal princ, const char *dn,
+             LDAPMessage **entry_out, LDAPMessage **result_out)
+{
+    krb5_error_code st;
+    char *user = NULL, *filtuser = NULL, *filter = NULL;
+    char **subtreelist = NULL;
+    size_t ntrees = 0, i;
+
+    *entry_out = *result_out = NULL;
+
+    st = krb5_ldap_unparse_name(context, princ, &user);
+    if (st)
+        goto cleanup;
+
+    filtuser = ldap_filter_correct(user);
+    if (filtuser == NULL) {
+        st = ENOMEM;
+        goto cleanup;
+    }
+
+    if (asprintf(&filter, FILTER"%s))", filtuser) < 0) {
+        filter = NULL;
+        st = ENOMEM;
+        goto cleanup;
+    }
+
+    if (dn != NULL) {
+        st = search_at(context, ldap_context, ldap_server_handle, dn,
+                       LDAP_SCOPE_BASE, filter, user, entry_out, result_out);
+        goto cleanup;
+    }
+
+    st = krb5_get_subtree_info(ldap_context, &subtreelist, &ntrees);
+    if (st)
+        goto cleanup;
+
+    for (i = 0; i < ntrees; i++) {
+        st = search_at(context, ldap_context, ldap_server_handle,
+                       subtreelist[i], ldap_context->lrparams->search_scope,
+                       filter, user, entry_out, result_out);
+        if (st || *entry_out != NULL)
+            goto cleanup;
+    }
+
+cleanup:
+    free(user);
+    free(filtuser);
+    free(filter);
+    while (ntrees > 0)
+        free(subtreelist[--ntrees]);
+    free(subtreelist);
+    return st;
+}
+
 /*
  * look up a principal in the directory.
  */
@@ -106,18 +210,15 @@ krb5_error_code
 krb5_ldap_get_principal(krb5_context context, krb5_const_principal searchfor,
                         unsigned int flags, krb5_db_entry **entry_ptr)
 {
-    char                        *user=NULL, *filter=NULL, *filtuser=NULL;
-    size_t                      tree=0, ntrees=1, princlen=0;
-    krb5_error_code             tempst=0, st=0;
-    char                        **values=NULL, **subtree=NULL, *cname=NULL;
-    LDAP                        *ld=NULL;
-    LDAPMessage                 *result=NULL, *ent=NULL;
-    krb5_ldap_context           *ldap_context=NULL;
-    kdb5_dal_handle             *dal_handle=NULL;
-    krb5_ldap_server_handle     *ldap_server_handle=NULL;
-    krb5_principal              cprinc=NULL;
-    krb5_boolean                found=FALSE;
-    krb5_db_entry               *entry = NULL;
+    krb5_error_code st;
+    char **values = NULL;
+    LDAP *ld = NULL;
+    LDAPMessage *ent = NULL, *result = NULL;
+    kdb5_dal_handle *dal_handle;
+    krb5_ldap_context *ldap_context;
+    krb5_ldap_server_handle *ldap_server_handle = NULL;
+    krb5_principal cprinc = NULL;
+    krb5_db_entry *entry = NULL;
 
     *entry_ptr = NULL;
 
@@ -138,116 +239,42 @@ krb5_ldap_get_principal(krb5_context context, krb5_const_principal searchfor,
         goto cleanup;
     }
 
-    if ((st=krb5_unparse_name(context, searchfor, &user)) != 0)
-        goto cleanup;
+    GET_HANDLE();
 
-    if ((st=krb5_ldap_unparse_principal_name(user)) != 0)
+    st = search_princ(context, ldap_context, ldap_server_handle, searchfor,
+                      NULL, &ent, &result);
+    if (st)
         goto cleanup;
-
-    filtuser = ldap_filter_correct(user);
-    if (filtuser == NULL) {
-        st = ENOMEM;
+    if (ent == NULL) {
+        st = KRB5_KDB_NOENTRY;
         goto cleanup;
     }
 
-    princlen = strlen(FILTER) + strlen(filtuser) + 2 + 1;  /* 2 for closing brackets */
-    if ((filter = malloc(princlen)) == NULL) {
-        st = ENOMEM;
-        goto cleanup;
+    /* Use the canonical principal name if one is present in the object. */
+    values = ldap_get_values(ld, ent, "krbCanonicalName");
+    if (values != NULL && values[0] != NULL) {
+        st = krb5_ldap_parse_name(context, values[0], &cprinc);
+        if (st != 0)
+            goto cleanup;
     }
-    snprintf(filter, princlen, FILTER"%s))", filtuser);
+    ldap_value_free(values);
 
-    if ((st = krb5_get_subtree_info(ldap_context, &subtree, &ntrees)) != 0)
+    entry = k5alloc(sizeof(*entry), &st);
+    if (entry == NULL)
+        goto cleanup;
+    st = populate_krb5_db_entry(context, ldap_context, ld, ent,
+                                cprinc != NULL ? cprinc : searchfor, entry);
+    if (st)
         goto cleanup;
 
-    GET_HANDLE();
-    for (tree=0; tree < ntrees && !found; ++tree) {
-
-        LDAP_SEARCH(subtree[tree], ldap_context->lrparams->search_scope, filter, principal_attributes);
-        for (ent=ldap_first_entry(ld, result); ent != NULL && !found; ent=ldap_next_entry(ld, ent)) {
-
-            /* get the associated directory user information */
-            if ((values=ldap_get_values(ld, ent, "krbprincipalname")) != NULL) {
-                size_t i;
-
-                /* a wild-card in a principal name can return a list of kerberos principals.
-                 * Make sure that the correct principal is returned.
-                 * NOTE: a principalname k* in ldap server will return all the principals starting with a k
-                 */
-                for (i=0; values[i] != NULL; ++i) {
-                    if (strcmp(values[i], user) == 0) {
-                        found = TRUE;
-                        break;
-                    }
-                }
-                ldap_value_free(values);
-
-                if (!found) /* no matching principal found */
-                    continue;
-            }
-
-            if ((values=ldap_get_values(ld, ent, "krbcanonicalname")) != NULL) {
-                if (values[0] && strcmp(values[0], user) != 0) {
-                    /* We matched an alias, not the canonical name. */
-                    st = krb5_ldap_parse_principal_name(values[0], &cname);
-                    if (st != 0)
-                        goto cleanup;
-                    st = krb5_parse_name(context, cname, &cprinc);
-                    if (st != 0)
-                        goto cleanup;
-                }
-                ldap_value_free(values);
-                if (!found)
-                    continue;
-            }
-
-            entry = k5alloc(sizeof(*entry), &st);
-            if (entry == NULL)
-                goto cleanup;
-            if ((st = populate_krb5_db_entry(context, ldap_context, ld, ent,
-                                             cprinc ? cprinc : searchfor,
-                                             entry)) != 0)
-                goto cleanup;
-        }
-        ldap_msgfree(result);
-        result = NULL;
-    } /* for (tree=0 ... */
-
-    if (found) {
-        *entry_ptr = entry;
-        entry = NULL;
-    } else
-        st = KRB5_KDB_NOENTRY;
+    *entry_ptr = entry;
+    entry = NULL;
 
 cleanup:
     ldap_msgfree(result);
     krb5_db_free_principal(context, entry);
-
-    if (filter)
-        free (filter);
-
-    if (subtree) {
-        for (; ntrees; --ntrees)
-            if (subtree[ntrees-1])
-                free (subtree[ntrees-1]);
-        free (subtree);
-    }
-
-    if (ldap_server_handle)
-        krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
-
-    if (user)
-        free(user);
-
-    if (filtuser)
-        free(filtuser);
-
-    if (cname)
-        free(cname);
-
-    if (cprinc)
-        krb5_free_principal(context, cprinc);
-
+    krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
+    krb5_free_principal(context, cprinc);
     return st;
 }
 
@@ -755,13 +782,93 @@ validate_xargs(krb5_context context,
     return 0;
 }
 
+static krb5_error_code
+add_alias(krb5_context context, krb5_ldap_context *ldap_context,
+          krb5_ldap_server_handle *ldap_server_handle,
+          krb5_const_principal alias, krb5_const_principal target)
+{
+    krb5_error_code st;
+    LDAP *ld = ldap_server_handle->ldap_handle;
+    LDAPMessage *ent, *result = NULL;
+    LDAPMod **mods = NULL;
+    char **canon = NULL, **names = NULL, *user = NULL, *dn = NULL;
+    char *strval[2] = { NULL }, errbuf[1024];
+
+    st = search_princ(context, ldap_context, ldap_server_handle, target, NULL,
+                      &ent, &result);
+    if (st)
+        goto cleanup;
+    if (ent == NULL) {
+        st = KRB5_KDB_NOENTRY;
+        k5_setmsg(context, st, _("target principal not found"));
+        goto cleanup;
+    }
+
+    dn = ldap_get_dn(ld, ent);
+    if (dn == NULL) {
+        ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &st);
+        st = set_ldap_error(context, st, 0);
+        goto cleanup;
+    }
+    canon = ldap_get_values(ld, ent, "krbCanonicalName");
+    names = ldap_get_values(ld, ent, "krbPrincipalName");
+
+    /* Add a krbCanonicalName attribute if one isn't set. */
+    if (canon == NULL) {
+        if (ldap_count_values(names) != 1) {
+            st = KRB5_KDB_INTERNAL_ERROR;
+            k5_setmsg(context, st,
+                      _("cannot add alias to entry with multiple "
+                        "krbPrincipalName values and no krbCanonicalName "
+                        "attribute"));
+            goto cleanup;
+        }
+        strval[0] = names[0];
+        st = krb5_add_str_mem_ldap_mod(&mods, "krbCanonicalName", LDAP_MOD_ADD,
+                                       strval);
+        if (st)
+            goto cleanup;
+    }
+
+    /* Add a krbPrincipalName value for the alias name. */
+    st = krb5_ldap_unparse_name(context, alias, &user);
+    if (st)
+        goto cleanup;
+    strval[0] = user;
+    st = krb5_add_str_mem_ldap_mod(&mods, "krbPrincipalName", LDAP_MOD_ADD,
+                                   strval);
+    if (st)
+        goto cleanup;
+
+    st = ldap_modify_ext_s(ld, dn, mods, NULL, NULL);
+    if (st == LDAP_TYPE_OR_VALUE_EXISTS) {
+        st = KRB5_KDB_INUSE;
+        goto cleanup;
+    } else if (st != LDAP_SUCCESS) {
+        snprintf(errbuf, sizeof(errbuf), _("Alias modification failed: %s"),
+                 ldap_err2string(st));
+        st = translate_ldap_error(st, OP_MOD);
+        k5_setmsg(context, st, "%s", errbuf);
+        goto cleanup;
+    }
+
+cleanup:
+    ldap_msgfree(result);
+    ldap_memfree(dn);
+    ldap_value_free(canon);
+    ldap_value_free(names);
+    krb5_free_unparsed_name(context, user);
+    ldap_mods_free(mods, 1);
+    return st;
+}
+
 krb5_error_code
 krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
                         char **db_args)
 {
     int                         kerberos_principal_object_type=0;
     size_t                      l=0, ntrees=0, tre=0;
-    krb5_error_code             st=0, tempst=0;
+    krb5_error_code             st=0;
     LDAP                        *ld=NULL;
     LDAPMessage                 *result=NULL, *ent=NULL;
     char                        **subtreelist = NULL;
@@ -778,6 +885,7 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
     kdb5_dal_handle             *dal_handle=NULL;
     krb5_ldap_context           *ldap_context=NULL;
     krb5_ldap_server_handle     *ldap_server_handle=NULL;
+    krb5_principal              alias_target=NULL;
     osa_princ_ent_rec           princ_ent = {0};
     xargs_t                     xargs = {0};
     char                        *polname = NULL;
@@ -802,9 +910,20 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
         goto cleanup;
     }
 
+    /* If this is an alias entry, add an alias to the target and return. */
+    st = krb5_dbe_read_alias(context, entry, &alias_target);
+    if (st)
+        goto cleanup;
+    if (alias_target != NULL) {
+        st = add_alias(context, ldap_context, ldap_server_handle, entry->princ,
+                       alias_target);
+        krb5_free_principal(context, alias_target);
+        goto cleanup;
+    }
+
     /* get the principal information to act on */
-    if (((st=krb5_unparse_name(context, entry->princ, &user)) != 0) ||
-        ((st=krb5_ldap_unparse_principal_name(user)) != 0))
+    st = krb5_ldap_unparse_name(context, entry->princ, &user);
+    if (st)
         goto cleanup;
     filtuser = ldap_filter_correct(user);
     if (filtuser == NULL) {
@@ -830,72 +949,29 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
         goto cleanup;
 
     if (entry->mask & KADM5_LOAD) {
-        size_t           tree = 0;
-        int              numlentries = 0;
-
-        /*  A load operation is special, will do a mix-in (add krbprinc
-         *  attrs to a non-krb object entry) if an object exists with a
-         *  matching krbprincipalname attribute so try to find existing
-         *  object and set principal_dn.  This assumes that the
-         *  krbprincipalname attribute is unique (only one object entry has
-         *  a particular krbprincipalname attribute).
+        /*
+         * A load operation is special, will do a mix-in (add krbprinc attrs to
+         * a non-krb object entry) if an object exists with a matching
+         * krbprincipalname attribute so try to find existing object and set
+         * principal_dn.  This assumes that the krbprincipalname attribute is
+         * unique (only one object entry has a particular krbprincipalname
+         * attribute).
          */
-        if (asprintf(&filter, FILTER"%s))", filtuser) < 0) {
-            filter = NULL;
-            st = ENOMEM;
-            goto cleanup;
-        }
-
-        /* get the current subtree list */
-        if ((st = krb5_get_subtree_info(ldap_context, &subtreelist, &ntrees)) != 0)
+        st = search_princ(context, ldap_context, ldap_server_handle,
+                          entry->princ, principal_dn, &ent, &result);
+        if (st && st != KRB5_KDB_NOENTRY)
             goto cleanup;
-
-        found_entry = FALSE;
-        /* search for entry with matching krbprincipalname attribute */
-        for (tree = 0; found_entry == FALSE && tree < ntrees; ++tree) {
+        if (ent != NULL && principal_dn == NULL) {
+            /* Remember this DN to be modified later. */
+            principal_dn = ldap_get_dn(ld, ent);
             if (principal_dn == NULL) {
-                LDAP_SEARCH_1(subtreelist[tree], ldap_context->lrparams->search_scope, filter, principal_attributes, IGNORE_STATUS);
-            } else {
-                /* just look for entry with principal_dn */
-                LDAP_SEARCH_1(principal_dn, LDAP_SCOPE_BASE, filter, principal_attributes, IGNORE_STATUS);
-            }
-            if (st == LDAP_SUCCESS) {
-                numlentries = ldap_count_entries(ld, result);
-                if (numlentries > 1) {
-                    st = EINVAL;
-                    k5_setmsg(context, st,
-                              _("operation can not continue, more than one "
-                                "entry with principal name \"%s\" found"),
-                              user);
-                    goto cleanup;
-                } else if (numlentries == 1) {
-                    found_entry = TRUE;
-                    if (principal_dn == NULL) {
-                        ent = ldap_first_entry(ld, result);
-                        if (ent != NULL) {
-                            /* setting principal_dn will cause that entry to be modified further down */
-                            if ((principal_dn = ldap_get_dn(ld, ent)) == NULL) {
-                                ldap_get_option (ld, LDAP_OPT_RESULT_CODE, &st);
-                                st = set_ldap_error (context, st, 0);
-                                goto cleanup;
-                            }
-                        }
-                    }
-                }
-            } else if (st != LDAP_NO_SUCH_OBJECT) {
-                /* could not perform search, return with failure */
-                st = set_ldap_error (context, st, 0);
+                ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &st);
+                st = set_ldap_error(context, st, 0);
                 goto cleanup;
             }
-            ldap_msgfree(result);
-            result = NULL;
-            /*
-             * If it isn't found then assume a standalone princ entry is to
-             * be created.
-             */
-        } /* end for (tree = 0; principal_dn == ... */
+        }
 
-        if (found_entry == FALSE && principal_dn != NULL) {
+        if (ent == NULL && principal_dn != NULL) {
             /*
              * if principal_dn is null then there is code further down to
              * deal with setting standalone_principal_dn.  Also note that
@@ -959,12 +1035,9 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
      * any of the subtrees
      */
     if (xargs.dn_from_kbd == TRUE) {
-        /* Get the current subtree list if we haven't already done so. */
-        if (subtreelist == NULL) {
-            st = krb5_get_subtree_info(ldap_context, &subtreelist, &ntrees);
-            if (st)
-                goto cleanup;
-        }
+        st = krb5_get_subtree_info(ldap_context, &subtreelist, &ntrees);
+        if (st)
+            goto cleanup;
 
         st = validate_xargs(context, ldap_server_handle, &xargs,
                             standalone_principal_dn, subtreelist, ntrees);
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in
index 1c69dc7f9..41ac0d3b2 100644
--- a/src/tests/Makefile.in
+++ b/src/tests/Makefile.in
@@ -192,6 +192,7 @@ check-pytests: responder s2p s4u2proxy unlockiter s4u2self
 	$(RUNPYTEST) $(srcdir)/t_kdcoptions.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_replay.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_sendto_kdc.py $(PYTESTFLAGS)
+	$(RUNPYTEST) $(srcdir)/t_alias.py $(PYTESTFLAGS)
 
 clean:
 	$(RM) adata conccache etinfo forward gcred hist hooks hrealm
diff --git a/src/tests/t_alias.py b/src/tests/t_alias.py
new file mode 100755
index 000000000..f52163241
--- /dev/null
+++ b/src/tests/t_alias.py
@@ -0,0 +1,124 @@
+from k5test import *
+
+realm = K5Realm(create_host=False)
+
+mark('getprinc')
+realm.addprinc('canon')
+realm.run([kadminl, 'alias', 'alias', 'canon at KRBTEST.COM'])
+realm.run([kadminl, 'getprinc', 'alias'],
+          expected_msg='Principal: canon at KRBTEST.COM')
+
+mark('delprinc')
+realm.run([kadminl, 'delprinc', 'alias'])
+realm.run([kadminl, 'getprinc', 'alias'], expected_code=1,
+          expected_msg='does not exist')
+realm.run([kadminl, 'getprinc', 'canon'], expected_msg=': canon at KRBTEST.COM')
+
+mark('no specified realm')
+realm.run([kadminl, 'alias', 'alias', 'canon'])
+realm.run([kadminl, 'getprinc', 'alias'], expected_msg=': canon at KRBTEST.COM')
+
+mark('cross-realm')
+realm.run([kadminl, 'alias', 'x', 'y at OTHER.REALM'], expected_code=1,
+          expected_msg='Alias target must be within the same realm')
+
+mark('alias as service principal')
+realm.extract_keytab('alias', realm.keytab)
+realm.run([kvno, 'alias'])
+realm.klist('user at KRBTEST.COM', 'alias at KRBTEST.COM')
+
+mark('alias as client principal')
+realm.kinit('alias', flags=['-k'])
+realm.klist('alias at KRBTEST.COM')
+realm.kinit('alias', flags=['-k', '-C'])
+realm.klist('canon at KRBTEST.COM')
+
+mark('chain')
+realm.run([kadminl, 'alias', 'a1', 'canon'])
+realm.run([kadminl, 'alias', 'a2', 'a1'])
+realm.run([kadminl, 'alias', 'a3', 'a2'])
+realm.run([kadminl, 'alias', 'a4', 'a3'])
+realm.run([kadminl, 'alias', 'a5', 'a4'])
+realm.run([kadminl, 'alias', 'a6', 'a5'])
+realm.run([kadminl, 'alias', 'a7', 'a6'])
+realm.run([kadminl, 'alias', 'a8', 'a7'])
+realm.run([kadminl, 'alias', 'a9', 'a8'])
+realm.run([kadminl, 'alias', 'a10', 'a9'])
+realm.run([kadminl, 'alias', 'a11', 'a10'])
+realm.run([kvno, 'a1'])
+realm.run([kvno, 'a2'])
+realm.run([kvno, 'a3'])
+realm.run([kvno, 'a4'])
+realm.run([kvno, 'a5'])
+realm.run([kvno, 'a6'])
+realm.run([kvno, 'a7'])
+realm.run([kvno, 'a8'])
+realm.run([kvno, 'a9'])
+realm.run([kvno, 'a10'])
+realm.run([kvno, 'a11'], expected_code=1,
+          expected_msg='Server a11 at KRBTEST.COM not found in Kerberos database')
+
+mark('circular chain')
+realm.run([kadminl, 'alias', 'selfalias', 'selfalias'])
+realm.run([kvno, 'selfalias'], expected_code=1,
+          expected_msg='Server selfalias at KRBTEST.COM not found')
+
+mark('blocking creations')
+realm.run([kadminl, 'addprinc', '-nokey', 'alias'], expected_code=1,
+          expected_msg='already exists')
+realm.run([kadminl, 'alias', 'alias', 'canon'], expected_code=1,
+          expected_msg='already exists')
+realm.run([kadminl, 'renprinc', 'user', 'alias'], expected_code=1,
+          expected_msg='already exists')
+
+# Non-resolving aliases being overwritable is emergent behavior;
+# change the tests if the behavior changes.
+mark('not blocking creations')
+realm.run([kadminl, 'alias', 'xa1', 'x'])
+realm.run([kadminl, 'alias', 'xa2', 'x'])
+realm.run([kadminl, 'alias', 'xa3', 'x'])
+realm.addprinc('xa1')
+realm.run([kadminl, 'getprinc', 'xa1'], expected_msg=': xa1 at KRBTEST.COM')
+realm.run([kadminl, 'alias', 'xa2', 'canon'])
+realm.run([kadminl, 'getprinc', 'xa2'], expected_msg=': canon at KRBTEST.COM')
+realm.run([kadminl, 'renprinc', 'xa1', 'xa3'])
+realm.run([kadminl, 'getprinc', 'xa3'], expected_msg=': xa3 at KRBTEST.COM')
+
+mark('renprinc')
+realm.run([kadminl, 'renprinc', 'alias', 'nalias'], expected_code=1,
+          expected_msg='Operation unsupported on alias principal name')
+
+mark('modprinc')
+realm.run([kadminl, 'modprinc', '+preauth', 'alias'])
+realm.run([kadminl, 'getprinc', 'canon'], expected_msg='REQUIRES_PRE_AUTH')
+
+mark('cpw')
+realm.run([kadminl, 'cpw', '-pw', 'pw', 'alias'])
+realm.run([kadminl, 'getprinc', 'canon'], expected_msg='vno 2,')
+realm.run([kadminl, 'cpw', '-e', 'aes256-cts', '-pw', 'pw', 'alias'])
+realm.run([kadminl, 'getprinc', 'canon'], expected_msg='vno 3,')
+realm.run([kadminl, 'cpw', '-randkey', 'alias'])
+realm.run([kadminl, 'getprinc', 'canon'], expected_msg='vno 4,')
+realm.run([kadminl, 'cpw', '-e', 'aes256-cts', '-randkey', 'alias'])
+realm.run([kadminl, 'getprinc', 'canon'], expected_msg='vno 5,')
+
+mark('listprincs')
+realm.run([kadminl, 'listprincs'], expected_msg='alias at KRBTEST.COM')
+
+mark('purgekeys')
+realm.run([kadminl, 'purgekeys', '-all', 'alias'])
+realm.run([kadminl, 'getprinc', 'canon'], expected_msg='Number of keys: 0')
+
+mark('setstr')
+realm.run([kadminl, 'setstr', 'alias', 'key', 'value'])
+realm.run([kadminl, 'getstrs', 'canon'], expected_msg='key: value')
+
+mark('getstrs')
+realm.run([kadminl, 'getstrs', 'alias'], expected_msg='key: value')
+
+mark('delstr')
+realm.run([kadminl, 'delstr', 'alias', 'key'])
+realm.run([kadminl, 'getstrs', 'canon'],
+          expected_msg='(No string attributes.)')
+
+success('alias tests')
diff --git a/src/tests/t_kadmin_acl.py b/src/tests/t_kadmin_acl.py
index 31a7fb871..fe762d57c 100755
--- a/src/tests/t_kadmin_acl.py
+++ b/src/tests/t_kadmin_acl.py
@@ -25,21 +25,33 @@ all_modify = make_client('all_modify')
 all_rename = make_client('all_rename')
 all_wildcard = make_client('all_wildcard')
 all_extract = make_client('all_extract')
+all_alias = make_client('all_alias')
 some_add = make_client('some_add')
 some_changepw = make_client('some_changepw')
 some_delete = make_client('some_delete')
 some_inquire = make_client('some_inquire')
 some_modify = make_client('some_modify')
 some_rename = make_client('some_rename')
+some_extract = make_client('some_extract')
+some_alias = make_client('some_alias')
 restricted_add = make_client('restricted_add')
 restricted_modify = make_client('restricted_modify')
 restricted_rename = make_client('restricted_rename')
+restricted_alias = make_client('restricted_alias')
 wctarget = make_client('wctarget')
 admin = make_client('user/admin')
 none = make_client('none')
 restrictions = make_client('restrictions')
 onetwothreefour = make_client('one/two/three/four')
 
+realm.run([kadminl, 'alias', 'aliastonone', 'none'])
+aliastonone = os.path.join(realm.testdir, 'kadmin_ccache_aliastonone')
+realm.kinit('aliastonone', password('none'),
+            flags=['-S', 'kadmin/admin', '-c', aliastonone])
+
+realm.run([kadminl, 'alias', 'aliastounselected', 'unselected'])
+realm.run([kadminl, 'alias', 'aliastoselected', 'selected'])
+
 realm.run([kadminl, 'addpol', '-minlife', '1 day', 'minlife'])
 
 f = open(os.path.join(realm.testdir, 'acl'), 'w')
@@ -53,16 +65,27 @@ all_modify         im
 all_rename         ad
 all_wildcard       x
 all_extract        ie
+all_alias          am
 some_add           a   selected
+some_add           a   aliastounselected
 some_changepw      c   selected
+some_changepw      c   aliastounselected
 some_delete        d   selected
+some_delete        d   aliastounselected
 some_inquire       i   selected
+some_inquire       i   aliastounselected
 some_modify        im  selected
+some_modify        im  aliastounselected
+some_extract       ie  selected
+some_extract       ie  aliastounselected
 some_rename        d   from
 some_rename        a   to
+some_alias         a   aliasname
+some_alias         m   canon
 restricted_add     a   *         +preauth
 restricted_modify  im  *         +preauth
 restricted_rename  ad  *         +preauth
+restricted_alias   ai  *         +preauth
 
 */*                d   *2/*1
 # The next line is a regression test for #8154; it is not used directly.
@@ -92,7 +115,13 @@ for pw in (['-pw', 'newpw'], ['-randkey']):
                   expected_msg=msg)
         kadmin_as(some_changepw, ['cpw'] + args + ['unselected'],
                   expected_code=1, expected_msg=msg)
+        # Verify that the ACL check is canonicalized.
+        kadmin_as(some_changepw, ['cpw'] + args + ['aliastounselected'],
+                  expected_code=1, expected_msg=msg)
+        kadmin_as(some_changepw, ['cpw'] + args + ['aliastoselected'])
         kadmin_as(none, ['cpw'] + args + ['none'])
+        kadmin_as(aliastonone, ['cpw'] + args + ['none'],
+                  expected_code=1, expected_msg=msg)
         realm.run([kadminl, 'modprinc', '-policy', 'minlife', 'none'])
         msg = "Current password's minimum life has not expired"
         kadmin_as(none, ['cpw'] + args + ['none'], expected_code=1,
@@ -123,6 +152,12 @@ for ks in ([], ['-e', 'aes256-cts']):
               expected_msg="Operation requires ``add'' privilege")
     kadmin_as(some_add, ['addprinc'] + args + ['unselected'], expected_code=1,
               expected_msg="Operation requires ``add'' privilege")
+    # Verify that the ACL check isn't canonicalized.  (We need the alias
+    # to resolve or we will overwrite it, currently.)
+    realm.addprinc('unselected')
+    kadmin_as(some_add, ['addprinc'] + args + ['aliastounselected'],
+              expected_code=1, expected_msg='already exists')
+    realm.run([kadminl, 'delprinc', 'unselected'])
 
 mark('delprinc')
 realm.addprinc('unselected', 'pw')
@@ -134,6 +169,11 @@ kadmin_as(none, ['delprinc', 'unselected'], expected_code=1,
           expected_msg="Operation requires ``delete'' privilege")
 kadmin_as(some_delete, ['delprinc', 'unselected'], expected_code=1,
           expected_msg="Operation requires ``delete'' privilege")
+# Verify that the ACL check isn't canonicalized.
+kadmin_as(some_delete, ['delprinc', 'aliastounselected'])
+realm.run([kadminl, 'alias', 'aliastounselected', 'unselected'])
+kadmin_as(some_delete, ['delprinc', 'aliastoselected'], expected_code=1,
+          expected_msg="Operation requires ``delete'' privilege")
 realm.run([kadminl, 'delprinc', 'unselected'])
 
 mark('getpol')
@@ -155,6 +195,11 @@ kadmin_as(none, ['getprinc', 'selected'], expected_code=1,
           expected_msg="Operation requires ``get'' privilege")
 kadmin_as(some_inquire, ['getprinc', 'unselected'], expected_code=1,
           expected_msg="Operation requires ``get'' privilege")
+# Verify that the ACL check is canonicalized.
+kadmin_as(some_inquire, ['getprinc', 'aliastounselected'], expected_code=1,
+          expected_msg="Operation requires ``get'' privilege")
+kadmin_as(some_inquire, ['getprinc', 'aliastoselected'],
+          expected_msg='Principal: selected at KRBTEST.COM')
 kadmin_as(none, ['getprinc', 'none'],
           expected_msg='Principal: none at KRBTEST.COM')
 realm.run([kadminl, 'delprinc', 'selected'])
@@ -176,6 +221,11 @@ kadmin_as(none, ['getstrs', 'selected'], expected_code=1,
           expected_msg="Operation requires ``get'' privilege")
 kadmin_as(some_inquire, ['getstrs', 'unselected'], expected_code=1,
           expected_msg="Operation requires ``get'' privilege")
+# Verify that the ACL check is canonicalized.
+kadmin_as(some_inquire, ['getstrs', 'aliastounselected'], expected_code=1,
+          expected_msg="Operation requires ``get'' privilege")
+kadmin_as(some_inquire, ['getstrs', 'aliastoselected'],
+          expected_msg='key: value')
 kadmin_as(none, ['getstrs', 'none'], expected_msg='(No string attributes.)')
 realm.run([kadminl, 'delprinc', 'selected'])
 realm.run([kadminl, 'delprinc', 'unselected'])
@@ -201,6 +251,10 @@ kadmin_as(all_inquire, ['modprinc', '-maxlife', '1 hour', 'selected'],
           expected_msg="Operation requires ``modify'' privilege")
 kadmin_as(some_modify, ['modprinc', '-maxlife', '1 hour', 'unselected'],
           expected_code=1, expected_msg='Operation requires')
+# Verify that the ACL check is canonicalized.
+kadmin_as(some_modify, ['modprinc', '-maxlife', '1 hour', 'aliastounselected'],
+          expected_code=1, expected_msg='Operation requires')
+kadmin_as(some_modify, ['modprinc', '-maxlife', '2 hours', 'aliastoselected'])
 realm.run([kadminl, 'delprinc', 'selected'])
 realm.run([kadminl, 'delprinc', 'unselected'])
 
@@ -213,6 +267,10 @@ kadmin_as(none, ['purgekeys', 'selected'], expected_code=1,
           expected_msg="Operation requires ``modify'' privilege")
 kadmin_as(some_modify, ['purgekeys', 'unselected'], expected_code=1,
           expected_msg="Operation requires ``modify'' privilege")
+# Verify that the ACL check is canonicalized.
+kadmin_as(some_modify, ['purgekeys', 'aliastounselected'], expected_code=1,
+          expected_msg="Operation requires ``modify'' privilege")
+kadmin_as(some_modify, ['purgekeys', 'aliastoselected'])
 kadmin_as(none, ['purgekeys', 'none'])
 realm.run([kadminl, 'delprinc', 'selected'])
 realm.run([kadminl, 'delprinc', 'unselected'])
@@ -232,6 +290,15 @@ kadmin_as(some_rename, ['renprinc', 'from', 'notto'], expected_code=1,
 realm.run([kadminl, 'renprinc', 'from', 'notfrom'])
 kadmin_as(some_rename, ['renprinc', 'notfrom', 'to'], expected_code=1,
           expected_msg="Insufficient authorization for operation")
+# Verify that the ACL check isn't canonicalized.
+realm.run([kadminl, 'alias', 'aliastofrom', 'from'])
+realm.run([kadminl, 'alias', 'aliastoto', 'to'])
+kadmin_as(some_rename, ['renprinc', 'aliastofrom', 'to'], expected_code=1,
+          expected_msg="Insufficient authorization for operation")
+kadmin_as(some_rename, ['renprinc', 'from', 'aliastoto'], expected_code=1,
+          expected_msg="Insufficient authorization for operation")
+realm.run([kadminl, 'delprinc', 'aliastofrom'])
+realm.run([kadminl, 'delprinc', 'aliastoto'])
 kadmin_as(restricted_rename, ['renprinc', 'notfrom', 'to'], expected_code=1,
           expected_msg="Insufficient authorization for operation")
 realm.run([kadminl, 'delprinc', 'notfrom'])
@@ -245,6 +312,10 @@ kadmin_as(none, ['setstr', 'selected',  'key', 'value'], expected_code=1,
           expected_msg="Operation requires ``modify'' privilege")
 kadmin_as(some_modify, ['setstr', 'unselected', 'key', 'value'],
           expected_code=1, expected_msg='Operation requires')
+# Verify that the ACL check is canonicalized.
+kadmin_as(some_modify, ['setstr', 'aliastounselected', 'key', 'value'],
+          expected_code=1, expected_msg='Operation requires')
+kadmin_as(some_modify, ['setstr', 'aliastoselected', 'key', 'value'])
 realm.run([kadminl, 'delprinc', 'selected'])
 realm.run([kadminl, 'delprinc', 'unselected'])
 
@@ -283,13 +354,24 @@ realm.run([kadminl, 'getprinc', 'type3'],
           expected_msg='Maximum renewable life: 0 days 02:00:00')
 
 mark('extract')
+realm.addprinc('selected')
+realm.addprinc('unselected')
 realm.run([kadminl, 'addprinc', '-pw', 'pw', 'extractkeys'])
+msg = "Operation requires ``extract-keys'' privilege"
 kadmin_as(all_wildcard, ['ktadd', '-norandkey', 'extractkeys'],
-          expected_code=1,
-          expected_msg="Operation requires ``extract-keys'' privilege")
+          expected_code=1, expected_msg=msg)
 kadmin_as(all_extract, ['ktadd', '-norandkey', 'extractkeys'])
 realm.kinit('extractkeys', flags=['-k'])
+kadmin_as(some_extract, ['ktadd', '-norandkey', 'selected'])
+kadmin_as(some_extract, ['ktadd', '-norandkey', 'unselected'], expected_code=1,
+          expected_msg=msg)
+# Verify that the ACL check is canonicalized.
+kadmin_as(some_extract, ['ktadd', '-norandkey', 'aliastounselected'],
+          expected_code=1, expected_msg=msg)
+kadmin_as(some_extract, ['ktadd', '-norandkey', 'aliastoselected'])
 os.remove(realm.keytab)
+realm.run([kadminl, 'delprinc', 'selected'])
+realm.run([kadminl, 'delprinc', 'unselected'])
 
 mark('lockdown_keys')
 kadmin_as(all_modify, ['modprinc', '+lockdown_keys', 'extractkeys'])
@@ -312,6 +394,22 @@ kadmin_as(all_extract, ['ktadd', '-norandkey', 'extractkeys'])
 realm.kinit('extractkeys', flags=['-k'])
 os.remove(realm.keytab)
 
+mark('alias')
+kadmin_as(all_alias, ['alias', 'aliasname', 'canon'])
+realm.run([kadminl, 'delprinc', 'aliasname'])
+kadmin_as(some_alias, ['alias', 'aliasname', 'canon'])
+realm.run([kadminl, 'delprinc', 'aliasname'])
+kadmin_as(all_add, ['alias', 'aliasname', 'canon'], expected_code=1,
+          expected_msg="Insufficient authorization for operation")
+kadmin_as(all_inquire, ['alias', 'aliasname', 'canon'], expected_code=1,
+          expected_msg="Insufficient authorization for operation")
+kadmin_as(some_alias, ['alias', 'aliasname', 'notcanon'], expected_code=1,
+          expected_msg="Insufficient authorization for operation")
+kadmin_as(some_alias, ['alias', 'notaliasname', 'canon'], expected_code=1,
+          expected_msg="Insufficient authorization for operation")
+kadmin_as(restricted_alias, ['alias', 'aliasname', 'canon'], expected_code=1,
+          expected_msg="Insufficient authorization for operation")
+
 # Verify that self-service key changes require an initial ticket.
 mark('self-service initial ticket')
 realm.run([kadminl, 'cpw', '-pw', password('none'), 'none'])
diff --git a/src/tests/t_kdb.py b/src/tests/t_kdb.py
index c1a717b99..14d57923f 100755
--- a/src/tests/t_kdb.py
+++ b/src/tests/t_kdb.py
@@ -360,14 +360,14 @@ mark('LDAP service principal aliases')
 
 # Test service principal aliases.
 realm.addprinc('canon', password('canon'))
-ldap_modify('dn: krbPrincipalName=canon at KRBTEST.COM,cn=t1,cn=krb5\n'
-            'changetype: modify\n'
-            'add: krbPrincipalName\n'
-            'krbPrincipalName: alias at KRBTEST.COM\n'
-            'krbPrincipalName: ent at abc@KRBTEST.COM\n'
-            '-\n'
-            'add: krbCanonicalName\n'
-            'krbCanonicalName: canon at KRBTEST.COM\n')
+realm.run([kadminl, 'alias', 'alias', 'canon'])
+realm.run([kadminl, 'alias', 'ent\\@abc', 'canon'])
+out = ldap_search('(krbPrincipalName=canon*)')
+if ('krbPrincipalName: canon at KRBTEST.COM' not in out or
+    'krbPrincipalName: alias at KRBTEST.COM' not in out or
+    'krbPrincipalName: ent at abc@KRBTEST.COM' not in out or
+    'krbCanonicalName: canon at KRBTEST.COM' not in out):
+    fail('expected names not found in canon object')
 realm.run([kadminl, 'getprinc', 'alias'],
           expected_msg='Principal: canon at KRBTEST.COM\n')
 realm.run([kadminl, 'getprinc', 'ent\\@abc'],
@@ -382,14 +382,7 @@ realm.kinit(realm.user_princ, password('user'), ['-S', 'alias'])
 realm.klist(realm.user_princ, 'alias at KRBTEST.COM')
 
 # Make sure an alias to the local TGS is still treated like an alias.
-ldap_modify('dn: krbPrincipalName=krbtgt/KRBTEST.COM at KRBTEST.COM,'
-            'cn=KRBTEST.COM,cn=krb5\n'
-            'changetype: modify\n'
-            'add:krbPrincipalName\n'
-            'krbPrincipalName: tgtalias at KRBTEST.COM\n'
-            '-\n'
-            'add: krbCanonicalName\n'
-            'krbCanonicalName: krbtgt/KRBTEST.COM at KRBTEST.COM\n')
+realm.run([kadminl, 'alias', 'tgtalias', 'krbtgt/KRBTEST.COM'])
 realm.run([kadminl, 'getprinc', 'tgtalias'],
           expected_msg='Principal: krbtgt/KRBTEST.COM at KRBTEST.COM')
 realm.kinit(realm.user_princ, password('user'))
@@ -429,6 +422,23 @@ realm.klist('ent\\@abc at KRBTEST.COM', 'alias at KRBTEST.COM')
 # Test client name canonicalization in non-krbtgt AS reply
 realm.kinit('alias', password('canon'), ['-C', '-S', 'kadmin/changepw'])
 
+# Test deleting an alias.
+mark('LDAP alias deletion')
+realm.run([kadminl, 'delprinc', 'alias'])
+realm.run([kadminl, 'getprinc', 'alias'], expected_code=1,
+          expected_msg='Principal does not exist')
+realm.run([kadminl, 'getprinc', 'ent\\@abc'],
+          expected_msg='Principal: canon at KRBTEST.COM\n')
+realm.run([kadminl, 'getprinc', 'canon'],
+          expected_msg='Principal: canon at KRBTEST.COM\n')
+
+# Test deleting a canonical name when an alias is present.
+realm.run([kadminl, 'delprinc', 'canon'])
+realm.run([kadminl, 'getprinc', 'canon'], expected_code=1,
+          expected_msg='Principal does not exist')
+realm.run([kadminl, 'getprinc', 'ent\\@abc'], expected_code=1,
+          expected_msg='Principal does not exist')
+
 mark('LDAP password history')
 
 # Test password history.
@@ -551,6 +561,8 @@ realm.run([kdb5_util, 'load', '-update', dumpfile])
 out = realm.run([kadminl, 'getprinc', 'pwuser'])
 if 'Password expiration date: [never]' in out:
     fail('pw_expiration not preserved across dump and load')
+realm.run([kadminl, 'getprinc', 'tgtalias'],
+          expected_msg='Principal: krbtgt/KRBTEST.COM at KRBTEST.COM')
 
 # Destroy the realm.
 kldaputil(['destroy', '-f'])
diff --git a/src/tests/t_tabdump.py b/src/tests/t_tabdump.py
index 49531bf49..54117467f 100755
--- a/src/tests/t_tabdump.py
+++ b/src/tests/t_tabdump.py
@@ -19,7 +19,13 @@ def checkkeys(rows, dumptype, names):
 
 
 realm = K5Realm(start_kdc=False, get_creds=False)
+realm.run([kadminl, 'alias', 'useralias', 'user'])
 
+rows = getrows('alias')
+checkkeys(rows, 'alias', ["aliasname", "targetname"])
+if (rows[0]['aliasname'] != 'useralias at KRBTEST.COM' or
+    rows[0]['targetname'] != 'user at KRBTEST.COM'):
+    fail('tabdump alias principal names')
 
 rows = getrows('keyinfo')
 checkkeys(rows, 'keyinfo',


More information about the cvs-krb5 mailing list