]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Implement principal renaming in LDAP
authorSarah Day <sarahday@mit.edu>
Fri, 29 Apr 2016 14:26:31 +0000 (10:26 -0400)
committerGreg Hudson <ghudson@mit.edu>
Mon, 23 May 2016 16:46:51 +0000 (12:46 -0400)
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@mit.edu: minor naming changes and code simplifications]

ticket: 8065

src/plugins/kdb/ldap/ldap_exp.c
src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.h
src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c
src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.h
src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
src/plugins/kdb/ldap/libkdb_ldap/libkdb_ldap.exports
src/tests/t_kdb.py

index 66f489179801eb559f67dd5903790450c670e398..d524941ef26020e481a49b8db312f5f7b946ca1c 100644 (file)
@@ -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,
index b29a944ca5b9c545a6e18f38d10c7f8279dba9ee..32efc4f54ad07c4b37080d2bff869ab89278d26c 100644 (file)
@@ -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)
 {
@@ -1068,6 +1110,16 @@ krb5_add_int_mem_ldap_mod(LDAPMod ***list, char *attribute, int op, int value)
     return 0;
 }
 
+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)
 {
index fb4f3cedf0d3931fb5935b8bea0dadf08c5c516d..9ea5dd5c5e04be15b99966da36c60ceb68ed85fb 100644 (file)
@@ -59,6 +59,10 @@ krb5_get_linkdn(krb5_context, krb5_db_entry *, char ***);
 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 *);
 
@@ -92,6 +96,10 @@ krb5_add_ber_mem_ldap_mod(LDAPMod  ***, char *, int, struct berval **);
 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 **);
 
index d4802c5b0d25b61c14f50455558a4393093178e3..56839ffbdbee696e491d1bdd82fb56331f8088c6 100644 (file)
@@ -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
index 2e015927a16def94b61e6d9eac7ac11be8eda689..752f54f2838d8a17d01438f908dcc25897d01e1c 100644 (file)
@@ -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 *);
 
index 79c4cf05cc2f6b2da394aa4bcdc9c29fa573fd0f..74bc361515186f70de4fb3f9548ba2874c1db342 100644 (file)
@@ -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)
 {
index 36bde5a4fee5d118bb864c86184306b34bd6ab9e..9d1db88ef3c593b1a2676d0fb10736b2db45a07a 100644 (file)
@@ -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
index 4653a1c2c06282bd0928d5c25fae776d54874262..46a051c9840c79d555980386c4359ac9620a3e60 100755 (executable)
@@ -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@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'])