From: Sarah Day Date: Fri, 29 Apr 2016 14:26:31 +0000 (-0400) Subject: Implement principal renaming in LDAP X-Git-Tag: krb5-1.15-beta1~195 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2ac75e548afadde4f87f20fcc1ee1472cdac3fed;p=thirdparty%2Fkrb5.git 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@mit.edu: minor naming changes and code simplifications] ticket: 8065 --- diff --git a/src/plugins/kdb/ldap/ldap_exp.c b/src/plugins/kdb/ldap/ldap_exp.c index 66f4891798..d524941ef2 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 b29a944ca5..32efc4f54a 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) { @@ -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) { diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.h b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.h index fb4f3cedf0..9ea5dd5c5e 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.h +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.h @@ -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 **); diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c index d4802c5b0d..56839ffbdb 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 2e015927a1..752f54f283 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 79c4cf05cc..74bc361515 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 36bde5a4fe..9d1db88ef3 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 4653a1c2c0..46a051c984 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@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'])