krb5 commit: Implement principal renaming in LDAP

Greg Hudson ghudson at mit.edu
Mon May 23 16:25:52 EDT 2016


https://github.com/krb5/krb5/commit/2ac75e548afadde4f87f20fcc1ee1472cdac3fed
commit 2ac75e548afadde4f87f20fcc1ee1472cdac3fed
Author: Sarah Day <sarahday at mit.edu>
Date:   Fri Apr 29 10:26:31 2016 -0400

    Implement principal renaming in LDAP
    
    The generic method of renaming principals (by adding a new entry and
    deleting the old one) does not work in LDAP.  Add an LDAP
    implementation of rename that properly renames the DN and attributes
    when necessary.
    
    [ghudson at mit.edu: minor naming changes and code simplifications]
    
    ticket: 8065

 src/plugins/kdb/ldap/ldap_exp.c                    |    2 +-
 src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c       |   52 +++++
 src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.h       |    8 +
 src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c  |  216 ++++++++++++++++++++
 src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h  |    7 +
 src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c |    2 +-
 .../kdb/ldap/libkdb_ldap/libkdb_ldap.exports       |    1 +
 src/tests/t_kdb.py                                 |   12 +
 8 files changed, 298 insertions(+), 2 deletions(-)

diff --git a/src/plugins/kdb/ldap/ldap_exp.c b/src/plugins/kdb/ldap/ldap_exp.c
index 66f4891..d524941 100644
--- a/src/plugins/kdb/ldap/ldap_exp.c
+++ b/src/plugins/kdb/ldap/ldap_exp.c
@@ -61,7 +61,7 @@ kdb_vftabl PLUGIN_SYMBOL_NAME(krb5_ldap, kdb_function_table) = {
     /* free_principal */                    krb5_ldap_free_principal,
     /* put_principal */                     krb5_ldap_put_principal,
     /* delete_principal */                  krb5_ldap_delete_principal,
-    /* rename_principal */                  NULL,
+    /* rename_principal */                  krb5_ldap_rename_principal,
     /* iterate */                           krb5_ldap_iterate,
     /* create_policy */                     krb5_ldap_create_password_policy,
     /* get_policy */                        krb5_ldap_get_password_policy,
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
index b29a944..32efc4f 100644
--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
@@ -799,6 +799,48 @@ get_str_from_tl_data(krb5_context context, krb5_db_entry *entry, int type,
     return 0;
 }
 
+/*
+ * Replace the relative DN component of dn with newrdn.
+ */
+krb5_error_code
+replace_rdn(krb5_context context, const char *dn, const char *newrdn,
+            char **newdn_out)
+{
+    krb5_error_code ret;
+    LDAPDN ldn = NULL;
+    LDAPRDN lrdn = NULL;
+    char *next;
+
+    *newdn_out = NULL;
+
+    ret = ldap_str2dn(dn, &ldn, LDAP_DN_FORMAT_LDAPV3);
+    if (ret != LDAP_SUCCESS || ldn[0] == NULL) {
+        ret = EINVAL;
+        goto cleanup;
+    }
+
+    ret = ldap_str2rdn(newrdn, &lrdn, &next, LDAP_DN_FORMAT_LDAPV3);
+    if (ret != LDAP_SUCCESS) {
+        ret = EINVAL;
+        goto cleanup;
+    }
+
+    ldap_rdnfree(ldn[0]);
+    ldn[0] = lrdn;
+    lrdn = NULL;
+
+    ret = ldap_dn2str(ldn, newdn_out, LDAP_DN_FORMAT_LDAPV3);
+    if (ret != LDAP_SUCCESS)
+        ret = KRB5_KDB_SERVER_INTERNAL_ERR;
+
+cleanup:
+    if (ldn != NULL)
+        ldap_dnfree(ldn);
+    if (lrdn != NULL)
+        ldap_rdnfree(lrdn);
+    return ret;
+}
+
 krb5_error_code
 krb5_get_userdn(krb5_context context, krb5_db_entry *entry, char **userdn)
 {
@@ -1069,6 +1111,16 @@ krb5_add_int_mem_ldap_mod(LDAPMod ***list, char *attribute, int op, int value)
 }
 
 krb5_error_code
+krb5_ldap_modify_ext(krb5_context context, LDAP *ld, const char *dn,
+                     LDAPMod **mods, int op)
+{
+    int ret;
+
+    ret = ldap_modify_ext_s(ld, dn, mods, NULL, NULL);
+    return (ret == LDAP_SUCCESS) ? 0 : set_ldap_error(context, ret, op);
+}
+
+krb5_error_code
 krb5_ldap_lock(krb5_context kcontext, int mode)
 {
     krb5_error_code status = KRB5_PLUGIN_OP_NOTSUPP;
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.h b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.h
index fb4f3ce..9ea5dd5 100644
--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.h
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.h
@@ -60,6 +60,10 @@ krb5_error_code
 krb5_get_userdn(krb5_context, krb5_db_entry *, char **);
 
 krb5_error_code
+replace_rdn(krb5_context context, const char *dn, const char *newrdn,
+            char **newdn);
+
+krb5_error_code
 store_tl_data(krb5_tl_data *, int, void *);
 
 krb5_error_code
@@ -93,6 +97,10 @@ krb5_error_code
 krb5_add_int_mem_ldap_mod(LDAPMod  ***, char *, int , int);
 
 krb5_error_code
+krb5_ldap_modify_ext(krb5_context context, LDAP *ld, const char *dn,
+                     LDAPMod **mods, int op);
+
+krb5_error_code
 krb5_ldap_free_mod_array(LDAPMod **);
 
 krb5_error_code
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c
index d4802c5..56839ff 100644
--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c
@@ -352,6 +352,222 @@ cleanup:
     return st;
 }
 
+/*
+ * Set *res will to 1 if entry is a standalone principal entry, 0 if not.  On
+ * error, the value of *res is not defined.
+ */
+static inline krb5_error_code
+is_standalone_principal(krb5_context kcontext, krb5_db_entry *entry, int *res)
+{
+    krb5_error_code code;
+
+    code = krb5_get_princ_type(kcontext, entry, res);
+    if (!code)
+        *res = (*res == KDB_STANDALONE_PRINCIPAL_OBJECT) ? 1 : 0;
+    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.
+ *
+ * NOTE: Not every LDAP ds supports deleting the old rdn. If that is desired,
+ * it will have to be deleted afterwards.
+ */
+static krb5_error_code
+rename_principal_rdn(krb5_context context, LDAP *ld, const char *dn,
+                     const char *newprinc, char **newdn_out)
+{
+    int ret;
+    char *newrdn = NULL;
+
+    *newdn_out = NULL;
+
+    ret = asprintf(&newrdn, "krbprincipalname=%s", newprinc);
+    if (ret < 0)
+        return ENOMEM;
+
+    /*
+     * ldap_rename_s takes a deleteoldrdn parameter, but setting it to 1 fails
+     * on 389 Directory Server (as of version 1.3.5.4) if the old RDN value
+     * contains uppercase letters.  Instead, change the RDN without deleting
+     * the old value and delete it later.
+     */
+    ret = ldap_rename_s(ld, dn, newrdn, NULL, 0, NULL, NULL);
+    if (ret == -1) {
+        ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &ret);
+        ret = set_ldap_error(context, ret, OP_MOD);
+        goto cleanup;
+    }
+
+    ret = replace_rdn(context, dn, newrdn, newdn_out);
+
+cleanup:
+    free(newrdn);
+    return ret;
+}
+
+/*
+ * Rename a principal.
+ */
+krb5_error_code
+krb5_ldap_rename_principal(krb5_context context, krb5_const_principal source,
+                           krb5_const_principal target)
+{
+    int is_standalone;
+    krb5_error_code st;
+    char *suser = NULL, *tuser = NULL, *strval[2], *dn = NULL, *newdn = NULL;
+    krb5_db_entry *entry = NULL;
+    krb5_kvno mkvno;
+    struct berval **bersecretkey = NULL;
+    kdb5_dal_handle *dal_handle = NULL;
+    krb5_ldap_context *ldap_context = NULL;
+    krb5_ldap_server_handle *ldap_server_handle = NULL;
+    LDAP *ld = NULL;
+    LDAPMod **mods = NULL;
+
+    /* Clear the global error string */
+    krb5_clear_error_message(context);
+
+    SETUP_CONTEXT();
+    if (ldap_context->lrparams == NULL || ldap_context->container_dn == NULL)
+        return EINVAL;
+
+    /* get ldap handle */
+    GET_HANDLE();
+
+    /* Pass no flags.  Principal aliases won't be returned, which is a good
+     * thing since we don't support renaming aliases. */
+    st = krb5_ldap_get_principal(context, source, 0, &entry);
+    if (st)
+        goto cleanup;
+
+    st = is_standalone_principal(context, entry, &is_standalone);
+    if (st)
+        goto cleanup;
+
+    st = krb5_get_userdn(context, entry, &dn);
+    if (st)
+        goto cleanup;
+    if (dn == NULL) {
+        st = EINVAL;
+        k5_setmsg(context, st, _("dn information missing"));
+        goto cleanup;
+    }
+
+    st = unparse_principal_name(context, source, &suser);
+    if (st)
+        goto cleanup;
+    st = unparse_principal_name(context, target, &tuser);
+    if (st)
+        goto cleanup;
+
+    /* Specialize the salt and store it first so that in case of an error the
+     * correct salt will still be used. */
+    st = krb5_dbe_specialize_salt(context, entry);
+    if (st)
+        goto cleanup;
+
+    st = krb5_dbe_lookup_mkvno(context, entry, &mkvno);
+    if (st)
+        goto cleanup;
+
+    bersecretkey = krb5_encode_krbsecretkey(entry->key_data, entry->n_key_data,
+                                            mkvno);
+    if (bersecretkey == NULL) {
+        st = ENOMEM;
+        goto cleanup;
+    }
+
+    st = krb5_add_ber_mem_ldap_mod(&mods, "krbPrincipalKey",
+                                   LDAP_MOD_REPLACE | LDAP_MOD_BVALUES,
+                                   bersecretkey);
+    if (st != 0)
+        goto cleanup;
+
+    /* Update the principal. */
+    st = krb5_ldap_modify_ext(context, ld, dn, mods, OP_MOD);
+    if (st)
+        goto cleanup;
+    ldap_mods_free(mods, 1);
+    mods = NULL;
+
+    /* If this is a standalone principal, we want to rename the DN of the LDAP
+     * entry.  If not, we will modify the entry without changing its DN. */
+    if (is_standalone) {
+        st = rename_principal_rdn(context, ld, dn, tuser, &newdn);
+        if (st)
+            goto cleanup;
+        free(dn);
+        dn = newdn;
+        newdn = NULL;
+    }
+
+    /* There can be more than one krbPrincipalName, so we have to delete
+     * the old one and add the new one. */
+    strval[0] = suser;
+    strval[1] = NULL;
+    st = krb5_add_str_mem_ldap_mod(&mods, "krbPrincipalName", LDAP_MOD_DELETE,
+                                   strval);
+    if (st)
+        goto cleanup;
+
+    strval[0] = tuser;
+    strval[1] = NULL;
+    if (!is_standalone) {
+        st = krb5_add_str_mem_ldap_mod(&mods, "krbPrincipalName", LDAP_MOD_ADD,
+                                       strval);
+        if (st)
+            goto cleanup;
+    }
+
+    st = krb5_add_str_mem_ldap_mod(&mods, "krbCanonicalName", LDAP_MOD_REPLACE,
+                                   strval);
+    if (st)
+        goto cleanup;
+
+    /* Update the principal. */
+    st = krb5_ldap_modify_ext(context, ld, dn, mods, OP_MOD);
+    if (st)
+        goto cleanup;
+
+cleanup:
+    free(dn);
+    free(suser);
+    free(tuser);
+    krb5_ldap_free_principal(context, entry);
+    ldap_mods_free(mods, 1);
+    krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
+    return st;
+}
 
 /*
  * Function: krb5_ldap_unparse_principal_name
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h
index 2e01592..752f54f 100644
--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h
@@ -105,6 +105,9 @@ krb5_ldap_get_principal(krb5_context , krb5_const_principal ,
 krb5_error_code
 krb5_ldap_delete_principal(krb5_context, krb5_const_principal);
 
+krb5_error_code
+krb5_ldap_rename_principal(krb5_context context, krb5_const_principal source,
+                           krb5_const_principal target);
 void
 krb5_ldap_free_principal(krb5_context, krb5_db_entry *);
 
@@ -128,6 +131,10 @@ krb5_ldap_unparse_principal_name(char *);
 krb5_error_code
 krb5_ldap_parse_principal_name(char *, char **);
 
+struct berval**
+krb5_encode_krbsecretkey(krb5_key_data *key_data, int n_key_data,
+                         krb5_kvno mkvno);
+
 krb5_error_code
 krb5_decode_histkey(krb5_context, struct berval **, osa_princ_ent_rec *);
 
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
index 79c4cf0..74bc361 100644
--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
@@ -503,7 +503,7 @@ cleanup:
 }
 
 /* Decoding ASN.1 encoded key */
-static struct berval **
+struct berval **
 krb5_encode_krbsecretkey(krb5_key_data *key_data, int n_key_data,
                          krb5_kvno mkvno)
 {
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/libkdb_ldap.exports b/src/plugins/kdb/ldap/libkdb_ldap/libkdb_ldap.exports
index 36bde5a..9d1db88 100644
--- a/src/plugins/kdb/ldap/libkdb_ldap/libkdb_ldap.exports
+++ b/src/plugins/kdb/ldap/libkdb_ldap/libkdb_ldap.exports
@@ -9,6 +9,7 @@ krb5_ldap_read_server_params
 krb5_ldap_put_principal
 krb5_ldap_get_principal
 krb5_ldap_delete_principal
+krb5_ldap_rename_principal
 krb5_ldap_free_principal
 krb5_ldap_iterate
 krb5_ldap_read_krbcontainer_dn
diff --git a/src/tests/t_kdb.py b/src/tests/t_kdb.py
index 4653a1c..46a051c 100755
--- a/src/tests/t_kdb.py
+++ b/src/tests/t_kdb.py
@@ -384,6 +384,18 @@ def test_pwhist(nhist):
 for n in (1, 2, 3, 4, 5):
     test_pwhist(n)
 
+# Test principal renaming and make sure last modified is changed
+def get_princ(princ):
+    out = realm.run([kadminl, 'getprinc', princ])
+    return dict(map(str.strip, x.split(":", 1)) for x in out.splitlines())
+
+realm.addprinc("rename", password('rename'))
+renameprinc = get_princ("rename")
+realm.run([kadminl, '-p', 'fake at KRBTEST.COM', 'renprinc', 'rename', 'renamed'])
+renamedprinc = get_princ("renamed")
+if renameprinc['Last modified'] == renamedprinc['Last modified']:
+    fail('Last modified data not updated when principal was renamed')
+
 # Regression test for #7980 (fencepost when dividing keys up by kvno).
 realm.run([kadminl, 'addprinc', '-randkey', '-e', 'aes256-cts,aes128-cts',
            'kvnoprinc'])


More information about the cvs-krb5 mailing list