From 1e1bab4edc0ce4b70a370deac8109092b53b97a2 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Thu, 9 May 2013 14:43:17 -0400 Subject: Remove unnecessary prefixes from ipa-pwd-extop files --- .../ipa-slapi-plugins/ipa-pwd-extop/Makefile.am | 6 +- daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c | 1107 ++++++++++++++++ daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c | 291 +++++ daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h | 6 +- .../ipa-pwd-extop/ipapwd_common.c | 1107 ---------------- .../ipa-pwd-extop/ipapwd_encoding.c | 291 ----- .../ipa-pwd-extop/ipapwd_prepost.c | 1349 -------------------- daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 1349 ++++++++++++++++++++ 8 files changed, 2753 insertions(+), 2753 deletions(-) create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c delete mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c delete mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_encoding.c delete mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_prepost.c create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am index ec98f95e6..90f940fd3 100644 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am @@ -30,9 +30,9 @@ plugin_LTLIBRARIES = \ $(NULL) libipa_pwd_extop_la_SOURCES = \ - ipapwd_common.c \ - ipapwd_encoding.c \ - ipapwd_prepost.c \ + common.c \ + encoding.c \ + prepost.c \ ipa_pwd_extop.c \ $(KRB5_UTIL_SRCS) \ $(NULL) diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c new file mode 100644 index 000000000..bb1d96ade --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c @@ -0,0 +1,1107 @@ +/** BEGIN COPYRIGHT BLOCK + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional permission under GPLv3 section 7: + * + * In the following paragraph, "GPL" means the GNU General Public + * License, version 3 or any later version, and "Non-GPL Code" means + * code that is governed neither by the GPL nor a license + * compatible with the GPL. + * + * You may link the code of this Program with Non-GPL Code and convey + * linked combinations including the two, provided that such Non-GPL + * Code only links to the code of this Program through those well + * defined interfaces identified in the file named EXCEPTION found in + * the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline + * functions from the Approved Interfaces without causing the resulting + * work to be covered by the GPL. Only the copyright holders of this + * Program may make changes or additions to the list of Approved + * Interfaces. + * + * Authors: + * Simo Sorce + * + * Copyright (C) 2007-2010 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "ipapwd.h" +#include "util.h" + +/* Type of connection for this operation;*/ +#define LDAP_EXTOP_PASSMOD_CONN_SECURE + +/* Uncomment the following #undef FOR TESTING: + * allows non-SSL connections to use the password change extended op */ +/* #undef LDAP_EXTOP_PASSMOD_CONN_SECURE */ + +extern void *ipapwd_plugin_id; +extern const char *ipa_realm_dn; +extern const char *ipa_etc_config_dn; +extern const char *ipa_pwd_config_dn; + +/* These are the default enc:salt types if nothing is defined. + * TODO: retrieve the configure set of ecntypes either from the + * kfc.conf file or by synchronizing the file content into + * the directory */ +static const char *ipapwd_def_encsalts[] = { + "des3-hmac-sha1:normal", +/* "arcfour-hmac:normal", + "des-hmac-sha1:normal", + "des-cbc-md5:normal", */ + "des-cbc-crc:normal", +/* "des-cbc-crc:v4", + "des-cbc-crc:afs3", */ + NULL +}; + +static struct ipapwd_krbcfg *ipapwd_getConfig(void) +{ + krb5_error_code krberr; + struct ipapwd_krbcfg *config = NULL; + krb5_keyblock *kmkey = NULL; + Slapi_Entry *realm_entry = NULL; + Slapi_Entry *config_entry = NULL; + Slapi_Attr *a; + Slapi_Value *v; + BerElement *be = NULL; + ber_tag_t tag, tvno; + ber_int_t ttype; + const struct berval *bval; + struct berval *mkey = NULL; + char **encsalts; + char **tmparray; + char *tmpstr; + int i, ret; + + config = calloc(1, sizeof(struct ipapwd_krbcfg)); + if (!config) { + LOG_OOM(); + goto free_and_error; + } + kmkey = calloc(1, sizeof(krb5_keyblock)); + if (!kmkey) { + LOG_OOM(); + goto free_and_error; + } + config->kmkey = kmkey; + + krberr = krb5_init_context(&config->krbctx); + if (krberr) { + LOG_FATAL("krb5_init_context failed\n"); + goto free_and_error; + } + + ret = krb5_get_default_realm(config->krbctx, &config->realm); + if (ret) { + LOG_FATAL("Failed to get default realm?!\n"); + goto free_and_error; + } + + /* get the Realm Container entry */ + ret = ipapwd_getEntry(ipa_realm_dn, &realm_entry, NULL); + if (ret != LDAP_SUCCESS) { + LOG_FATAL("No realm Entry?\n"); + goto free_and_error; + } + + /*** get the Kerberos Master Key ***/ + + ret = slapi_entry_attr_find(realm_entry, "krbMKey", &a); + if (ret == -1) { + LOG_FATAL("No master key??\n"); + goto free_and_error; + } + + /* there should be only one value here */ + ret = slapi_attr_first_value(a, &v); + if (ret == -1) { + LOG_FATAL("No master key??\n"); + goto free_and_error; + } + + bval = slapi_value_get_berval(v); + if (!bval) { + LOG_FATAL("Error retrieving master key berval\n"); + goto free_and_error; + } + + be = ber_init(discard_const(bval)); + if (!be) { + LOG_FATAL("ber_init() failed!\n"); + goto free_and_error; + } + + tag = ber_scanf(be, "{i{iO}}", &tvno, &ttype, &mkey); + if (tag == LBER_ERROR) { + LOG_FATAL("Bad Master key encoding ?!\n"); + goto free_and_error; + } + + config->mkvno = tvno; + kmkey->magic = KV5M_KEYBLOCK; + kmkey->enctype = ttype; + kmkey->length = mkey->bv_len; + kmkey->contents = malloc(mkey->bv_len); + if (!kmkey->contents) { + LOG_OOM(); + goto free_and_error; + } + memcpy(kmkey->contents, mkey->bv_val, mkey->bv_len); + ber_bvfree(mkey); + ber_free(be, 1); + mkey = NULL; + be = NULL; + + /*** get the Supported Enc/Salt types ***/ + + encsalts = slapi_entry_attr_get_charray(realm_entry, + "krbSupportedEncSaltTypes"); + if (encsalts) { + for (i = 0; encsalts[i]; i++) /* count */ ; + ret = parse_bval_key_salt_tuples(config->krbctx, + (const char * const *)encsalts, i, + &config->supp_encsalts, + &config->num_supp_encsalts); + slapi_ch_array_free(encsalts); + } else { + LOG("No configured salt types use defaults\n"); + for (i = 0; ipapwd_def_encsalts[i]; i++) /* count */ ; + ret = parse_bval_key_salt_tuples(config->krbctx, + ipapwd_def_encsalts, i, + &config->supp_encsalts, + &config->num_supp_encsalts); + } + if (ret) { + LOG_FATAL("Can't get Supported EncSalt Types\n"); + goto free_and_error; + } + + /*** get the Preferred Enc/Salt types ***/ + + encsalts = slapi_entry_attr_get_charray(realm_entry, + "krbDefaultEncSaltTypes"); + if (encsalts) { + for (i = 0; encsalts[i]; i++) /* count */ ; + ret = parse_bval_key_salt_tuples(config->krbctx, + (const char * const *)encsalts, i, + &config->pref_encsalts, + &config->num_pref_encsalts); + slapi_ch_array_free(encsalts); + } else { + LOG("No configured salt types use defaults\n"); + for (i = 0; ipapwd_def_encsalts[i]; i++) /* count */ ; + ret = parse_bval_key_salt_tuples(config->krbctx, + ipapwd_def_encsalts, i, + &config->pref_encsalts, + &config->num_pref_encsalts); + } + if (ret) { + LOG_FATAL("Can't get Preferred EncSalt Types\n"); + goto free_and_error; + } + + slapi_entry_free(realm_entry); + + /* get the Realm Container entry */ + ret = ipapwd_getEntry(ipa_pwd_config_dn, &config_entry, NULL); + if (ret != LDAP_SUCCESS) { + LOG_FATAL("No config Entry? Impossible!\n"); + goto free_and_error; + } + config->passsync_mgrs = + slapi_entry_attr_get_charray(config_entry, "passSyncManagersDNs"); + /* now add Directory Manager, it is always added by default */ + tmpstr = slapi_ch_strdup("cn=Directory Manager"); + slapi_ch_array_add(&config->passsync_mgrs, tmpstr); + if (config->passsync_mgrs == NULL) { + LOG_OOM(); + goto free_and_error; + } + for (i = 0; config->passsync_mgrs[i]; i++) /* count */ ; + config->num_passsync_mgrs = i; + + slapi_entry_free(config_entry); + + /* get the ipa etc/ipaConfig entry */ + config->allow_lm_hash = false; + config->allow_nt_hash = false; + ret = ipapwd_getEntry(ipa_etc_config_dn, &config_entry, NULL); + if (ret != LDAP_SUCCESS) { + LOG_FATAL("No config Entry?\n"); + goto free_and_error; + } else { + tmparray = slapi_entry_attr_get_charray(config_entry, + "ipaConfigString"); + for (i = 0; tmparray && tmparray[i]; i++) { + if (strcasecmp(tmparray[i], "AllowLMhash") == 0) { + config->allow_lm_hash = true; + continue; + } + if (strcasecmp(tmparray[i], "AllowNThash") == 0) { + config->allow_nt_hash = true; + continue; + } + } + if (tmparray) slapi_ch_array_free(tmparray); + } + + slapi_entry_free(config_entry); + + return config; + +free_and_error: + if (mkey) ber_bvfree(mkey); + if (be) ber_free(be, 1); + if (kmkey) { + free(kmkey->contents); + free(kmkey); + } + if (config) { + if (config->krbctx) { + if (config->realm) + krb5_free_default_realm(config->krbctx, config->realm); + krb5_free_context(config->krbctx); + } + free(config->pref_encsalts); + free(config->supp_encsalts); + slapi_ch_array_free(config->passsync_mgrs); + free(config); + } + slapi_entry_free(config_entry); + slapi_entry_free(realm_entry); + return NULL; +} + +/* Easier handling for virtual attributes. You must call pwd_values_free() + * to free memory allocated here. It must be called before + * slapi_free_search_results_internal(entries) or + * slapi_pblock_destroy(pb) + */ +static int pwd_get_values(const Slapi_Entry *ent, const char *attrname, + Slapi_ValueSet** results, char** actual_type_name, + int *buffer_flags) +{ + int flags=0; + int type_name_disposition = 0; + int ret; + + ret = slapi_vattr_values_get((Slapi_Entry *)ent, (char *)attrname, + results, &type_name_disposition, + actual_type_name, flags, buffer_flags); + + return ret; +} + +static void pwd_values_free(Slapi_ValueSet** results, + char** actual_type_name, int buffer_flags) +{ + slapi_vattr_values_free(results, actual_type_name, buffer_flags); +} + +static int ipapwd_rdn_count(const char *dn) +{ + int rdnc = 0; + LDAPDN ldn; + int ret; + + ret = ldap_str2dn(dn, &ldn, LDAP_DN_FORMAT_LDAPV3); + if (ret != LDAP_SUCCESS) { + LOG_TRACE("ldap_str2dn(dn) failed ?!"); + return -1; + } + + for (rdnc = 0; ldn != NULL && ldn[rdnc]; rdnc++) /* count */ ; + ldap_dnfree(ldn); + + return rdnc; +} + +int ipapwd_getPolicy(const char *dn, + Slapi_Entry *target, + struct ipapwd_policy *policy) +{ + const char *krbPwdPolicyReference; + const char *pdn; + const Slapi_DN *psdn; + Slapi_Backend *be; + Slapi_PBlock *pb = NULL; + char *attrs[] = { "krbMaxPwdLife", "krbMinPwdLife", + "krbPwdMinDiffChars", "krbPwdMinLength", + "krbPwdHistoryLength", NULL}; + Slapi_Entry **es = NULL; + Slapi_Entry *pe = NULL; + int ret, res, dist, rdnc, scope, i; + Slapi_DN *sdn = NULL; + int buffer_flags=0; + Slapi_ValueSet* results = NULL; + char* actual_type_name = NULL; + int tmpint; + + LOG_TRACE("Searching policy for [%s]\n", dn); + + sdn = slapi_sdn_new_dn_byref(dn); + if (sdn == NULL) { + LOG_OOM(); + ret = -1; + goto done; + } + + pwd_get_values(target, "krbPwdPolicyReference", + &results, &actual_type_name, &buffer_flags); + if (results) { + Slapi_Value *sv; + slapi_valueset_first_value(results, &sv); + krbPwdPolicyReference = slapi_value_get_string(sv); + pdn = krbPwdPolicyReference; + scope = LDAP_SCOPE_BASE; + LOG_TRACE("using policy reference: %s\n", pdn); + } else { + /* Find ancestor base DN */ + be = slapi_be_select(sdn); + psdn = slapi_be_getsuffix(be, 0); + if (psdn == NULL) { + LOG_FATAL("Invalid DN [%s]\n", dn); + ret = -1; + goto done; + } + pdn = slapi_sdn_get_dn(psdn); + scope = LDAP_SCOPE_SUBTREE; + } + + pb = slapi_pblock_new(); + slapi_search_internal_set_pb(pb, + pdn, scope, + "(objectClass=krbPwdPolicy)", + attrs, 0, + NULL, /* Controls */ + NULL, /* UniqueID */ + ipapwd_plugin_id, + 0); /* Flags */ + + /* do search the tree */ + ret = slapi_search_internal_pb(pb); + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res); + if (ret == -1 || res != LDAP_SUCCESS) { + LOG_FATAL("Couldn't find policy, err (%d)\n", res ? res : ret); + ret = -1; + goto done; + } + + /* get entries */ + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &es); + if (!es) { + LOG_TRACE("No entries ?!"); + ret = -1; + goto done; + } + + /* count entries */ + for (i = 0; es[i]; i++) /* count */ ; + + /* if there is only one, return that */ + if (i == 1) { + pe = es[0]; + goto fill; + } + + /* count number of RDNs in DN */ + rdnc = ipapwd_rdn_count(dn); + if (rdnc == -1) { + LOG_TRACE("ipapwd_rdn_count(dn) failed"); + ret = -1; + goto done; + } + + pe = NULL; + dist = -1; + + /* find closest entry */ + for (i = 0; es[i]; i++) { + const Slapi_DN *esdn; + + esdn = slapi_entry_get_sdn_const(es[i]); + if (esdn == NULL) continue; + if (0 == slapi_sdn_compare(esdn, sdn)) { + pe = es[i]; + dist = 0; + break; + } + if (slapi_sdn_issuffix(sdn, esdn)) { + const char *dn1; + int c1; + + dn1 = slapi_sdn_get_dn(esdn); + if (!dn1) continue; + c1 = ipapwd_rdn_count(dn1); + if (c1 == -1) continue; + if ((dist == -1) || + ((rdnc - c1) < dist)) { + dist = rdnc - c1; + pe = es[i]; + } + } + if (dist == 0) break; /* found closest */ + } + + if (pe == NULL) { + ret = -1; + goto done; + } + +fill: + policy->min_pwd_life = slapi_entry_attr_get_int(pe, "krbMinPwdLife"); + + tmpint = slapi_entry_attr_get_int(pe, "krbMaxPwdLife"); + if (tmpint != 0) { + policy->max_pwd_life = tmpint; + } + + tmpint = slapi_entry_attr_get_int(pe, "krbPwdMinLength"); + if (tmpint != 0) { + policy->min_pwd_length = tmpint; + } + + policy->history_length = slapi_entry_attr_get_int(pe, + "krbPwdHistoryLength"); + + policy->min_complexity = slapi_entry_attr_get_int(pe, + "krbPwdMinDiffChars"); + + ret = 0; + +done: + if (results) { + pwd_values_free(&results, &actual_type_name, buffer_flags); + } + if (pb) { + slapi_free_search_results_internal(pb); + slapi_pblock_destroy(pb); + } + if (sdn) slapi_sdn_free(&sdn); + return ret; +} + + +/*==Common-public-functions=============================================*/ + +int ipapwd_entry_checks(Slapi_PBlock *pb, struct slapi_entry *e, + int *is_root, int *is_krb, int *is_smb, int *is_ipant, + char *attr, int acc) +{ + Slapi_Value *sval; + int rc; + + /* Check ACIs */ + slapi_pblock_get(pb, SLAPI_REQUESTOR_ISROOT, is_root); + + if (!*is_root) { + /* verify this user is allowed to write a user password */ + rc = slapi_access_allowed(pb, e, attr, NULL, acc); + if (rc != LDAP_SUCCESS) { + /* we have no business here, the operation will be denied anyway */ + rc = LDAP_SUCCESS; + goto done; + } + } + + /* Check if this is a krbPrincial and therefore needs us to generate other + * hashes */ + sval = slapi_value_new_string("krbPrincipalAux"); + if (!sval) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + *is_krb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval); + slapi_value_free(&sval); + + sval = slapi_value_new_string("sambaSamAccount"); + if (!sval) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + *is_smb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval); + slapi_value_free(&sval); + + sval = slapi_value_new_string("ipaNTUserAttrs"); + if (!sval) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + *is_ipant = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, + sval); + slapi_value_free(&sval); + + rc = LDAP_SUCCESS; + +done: + return rc; +} + +int ipapwd_gen_checks(Slapi_PBlock *pb, char **errMesg, + struct ipapwd_krbcfg **config, int check_flags) +{ + int ret, ssf; + int rc = LDAP_SUCCESS; + Slapi_Backend *be; + const Slapi_DN *psdn; + Slapi_DN *sdn; + char *dn = NULL; + + LOG_TRACE("=>\n"); + +#ifdef LDAP_EXTOP_PASSMOD_CONN_SECURE + if (check_flags & IPAPWD_CHECK_CONN_SECURE) { + /* Allow password modify on all connections with a Security Strength + * Factor (SSF) higher than 1 */ + if (slapi_pblock_get(pb, SLAPI_OPERATION_SSF, &ssf) != 0) { + LOG("Could not get SSF from connection\n"); + *errMesg = "Operation requires a secure connection.\n"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + if (ssf <= 1) { + *errMesg = "Operation requires a secure connection.\n"; + rc = LDAP_CONFIDENTIALITY_REQUIRED; + goto done; + } + } +#endif + + if (check_flags & IPAPWD_CHECK_DN) { + /* check we have a valid DN in the pblock or just abort */ + ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); + if (ret) { + LOG("Tried to change password for an invalid DN [%s]\n", + dn ? dn : ""); + *errMesg = "Invalid DN"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + sdn = slapi_sdn_new_dn_byref(dn); + if (!sdn) { + LOG_FATAL("Unable to convert dn to sdn %s", dn ? dn : ""); + *errMesg = "Internal Error"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + be = slapi_be_select(sdn); + slapi_sdn_free(&sdn); + + psdn = slapi_be_getsuffix(be, 0); + if (!psdn) { + *errMesg = "Invalid DN"; + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + } + + /* get the kerberos context and master key */ + *config = ipapwd_getConfig(); + if (NULL == *config) { + LOG_FATAL("Error Retrieving Master Key"); + *errMesg = "Fatal Internal Error"; + rc = LDAP_OPERATIONS_ERROR; + } + +done: + return rc; +} + +/* check password strenght and history */ +int ipapwd_CheckPolicy(struct ipapwd_data *data) +{ + struct ipapwd_policy pol = {0}; + time_t acct_expiration; + time_t pwd_expiration; + time_t last_pwd_change; + char **pwd_history; + char *tmpstr; + int ret; + + pol.max_pwd_life = IPAPWD_DEFAULT_PWDLIFE; + pol.min_pwd_length = IPAPWD_DEFAULT_MINLEN; + + if (data->changetype != IPA_CHANGETYPE_NORMAL) { + /* We must skip policy checks (Admin change) but + * force a password change on the next login. + * But not if Directory Manager */ + if (data->changetype == IPA_CHANGETYPE_ADMIN) { + /* The expiration date needs to be older than the current time + * otherwise the KDC may not immediately register the password + * as expired. The last password change needs to match the + * password expiration otherwise minlife issues will arise. + */ + data->timeNow -= 1; + data->expireTime = data->timeNow; + } + + /* do not load policies */ + } else { + + /* find the entry with the password policy */ + ret = ipapwd_getPolicy(data->dn, data->target, &pol); + if (ret) { + LOG_TRACE("No password policy, use defaults"); + } + } + + tmpstr = slapi_entry_attr_get_charptr(data->target, + "krbPrincipalExpiration"); + acct_expiration = ipapwd_gentime_to_time_t(tmpstr); + slapi_ch_free_string(&tmpstr); + + tmpstr = slapi_entry_attr_get_charptr(data->target, + "krbPasswordExpiration"); + pwd_expiration = ipapwd_gentime_to_time_t(tmpstr); + slapi_ch_free_string(&tmpstr); + + tmpstr = slapi_entry_attr_get_charptr(data->target, + "krbLastPwdChange"); + last_pwd_change = ipapwd_gentime_to_time_t(tmpstr); + slapi_ch_free_string(&tmpstr); + + pwd_history = slapi_entry_attr_get_charray(data->target, + "passwordHistory"); + + /* check policy */ + ret = ipapwd_check_policy(&pol, data->password, + data->timeNow, + acct_expiration, + pwd_expiration, + last_pwd_change, + pwd_history); + + slapi_ch_array_free(pwd_history); + + if (data->expireTime == 0) { + data->expireTime = data->timeNow + pol.max_pwd_life; + } + + data->policy = pol; + + return ret; +} + +/* Searches the dn in directory, + * If found : fills in slapi_entry structure and returns 0 + * If NOT found : returns the search result as LDAP_NO_SUCH_OBJECT + */ +int ipapwd_getEntry(const char *dn, Slapi_Entry **e2, char **attrlist) +{ + Slapi_DN *sdn; + int search_result = 0; + + LOG_TRACE("=>\n"); + + sdn = slapi_sdn_new_dn_byref(dn); + search_result = slapi_search_internal_get_entry(sdn, attrlist, e2, + ipapwd_plugin_id); + if (search_result != LDAP_SUCCESS) { + LOG_TRACE("No such entry-(%s), err (%d)\n", dn, search_result); + } + + slapi_sdn_free(&sdn); + LOG_TRACE("<= result: %d\n", search_result); + return search_result; +} + +int ipapwd_get_cur_kvno(Slapi_Entry *target) +{ + Slapi_Attr *krbPrincipalKey = NULL; + Slapi_ValueSet *svs; + Slapi_Value *sv; + BerElement *be = NULL; + const struct berval *cbval; + ber_tag_t tag, tmp; + ber_int_t tkvno; + int hint; + int kvno; + int ret; + + /* retrieve current kvno and and keys */ + ret = slapi_entry_attr_find(target, "krbPrincipalKey", &krbPrincipalKey); + if (ret != 0) { + return 0; + } + + kvno = 0; + + slapi_attr_get_valueset(krbPrincipalKey, &svs); + hint = slapi_valueset_first_value(svs, &sv); + while (hint != -1) { + cbval = slapi_value_get_berval(sv); + if (!cbval) { + LOG_TRACE("Error retrieving berval from Slapi_Value\n"); + goto next; + } + be = ber_init(discard_const(cbval)); + if (!be) { + LOG_TRACE("ber_init() failed!\n"); + goto next; + } + + tag = ber_scanf(be, "{xxt[i]", &tmp, &tkvno); + if (tag == LBER_ERROR) { + LOG_TRACE("Bad OLD key encoding ?!\n"); + ber_free(be, 1); + goto next; + } + + if (tkvno > kvno) { + kvno = tkvno; + } + + ber_free(be, 1); +next: + hint = slapi_valueset_next_value(svs, hint, &sv); + } + + return kvno; +} + +/* Modify the Password attributes of the entry */ +int ipapwd_SetPassword(struct ipapwd_krbcfg *krbcfg, + struct ipapwd_data *data, int is_krb) +{ + int ret = 0; + Slapi_Mods *smods = NULL; + Slapi_Value **svals = NULL; + Slapi_Value **ntvals = NULL; + Slapi_Value **pwvals = NULL; + struct tm utctime; + char timestr[GENERALIZED_TIME_LENGTH+1]; + char *lm = NULL; + char *nt = NULL; + int is_smb = 0; + int is_ipant = 0; + int is_host = 0; + Slapi_Value *sambaSamAccount; + Slapi_Value *ipaNTUserAttrs; + Slapi_Value *ipaHost; + char *errMesg = NULL; + char *modtime = NULL; + + LOG_TRACE("=>\n"); + + sambaSamAccount = slapi_value_new_string("sambaSamAccount"); + if (slapi_entry_attr_has_syntax_value(data->target, + "objectClass", sambaSamAccount)) { + is_smb = 1; + } + slapi_value_free(&sambaSamAccount); + + ipaNTUserAttrs = slapi_value_new_string("ipaNTUserAttrs"); + if (slapi_entry_attr_has_syntax_value(data->target, + "objectClass", ipaNTUserAttrs)) { + is_ipant = 1; + } + slapi_value_free(&ipaNTUserAttrs); + + ipaHost = slapi_value_new_string("ipaHost"); + if (slapi_entry_attr_has_syntax_value(data->target, + "objectClass", ipaHost)) { + is_host = 1; + } + slapi_value_free(&ipaHost); + + ret = ipapwd_gen_hashes(krbcfg, data, + data->password, + is_krb, is_smb, is_ipant, + &svals, &nt, &lm, &ntvals, &errMesg); + if (ret) { + goto free_and_return; + } + + smods = slapi_mods_new(); + + if (svals) { + slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, + "krbPrincipalKey", svals); + + /* krbLastPwdChange is used to tell whether a host entry has a + * keytab so don't set it on hosts. + */ + if (!is_host) { + /* change Last Password Change field with the current date */ + if (!gmtime_r(&(data->timeNow), &utctime)) { + LOG_FATAL("failed to retrieve current date (buggy gmtime_r ?)\n"); + ret = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + strftime(timestr, GENERALIZED_TIME_LENGTH + 1, + "%Y%m%d%H%M%SZ", &utctime); + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "krbLastPwdChange", timestr); + + /* set Password Expiration date */ + if (!gmtime_r(&(data->expireTime), &utctime)) { + LOG_FATAL("failed to convert expiration date\n"); + ret = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + strftime(timestr, GENERALIZED_TIME_LENGTH + 1, + "%Y%m%d%H%M%SZ", &utctime); + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "krbPasswordExpiration", timestr); + } + } + + if (lm && is_smb) { + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "sambaLMPassword", lm); + } + + if (nt && is_smb) { + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "sambaNTPassword", nt); + } + + if (ntvals && is_ipant) { + slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, + "ipaNTHash", ntvals); + } + + if (is_smb) { + /* with samba integration we need to also set sambaPwdLastSet or + * samba will decide the user has to change the password again */ + if (data->changetype == IPA_CHANGETYPE_ADMIN) { + /* if it is an admin change instead we need to let know to + * samba as well that the use rmust change its password */ + modtime = slapi_ch_smprintf("0"); + } else { + modtime = slapi_ch_smprintf("%ld", (long)data->timeNow); + } + if (!modtime) { + LOG_FATAL("failed to smprintf string!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto free_and_return; + } + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "sambaPwdLastset", modtime); + } + if (is_krb) { + if (data->changetype == IPA_CHANGETYPE_ADMIN) { + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "krbLoginFailedCount", "0"); + } + } + /* let DS encode the password itself, this allows also other plugins to + * intercept it to perform operations like synchronization with Active + * Directory domains through the replication plugin */ + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "userPassword", data->password); + + /* set password history */ + if (data->policy.history_length > 0) { + pwvals = ipapwd_setPasswordHistory(smods, data); + if (pwvals) { + slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, + "passwordHistory", pwvals); + } + } + + /* FIXME: + * instead of replace we should use a delete/add so that we are + * completely sure nobody else modified the entry meanwhile and + * fail if that's the case */ + + /* commit changes */ + ret = ipapwd_apply_mods(data->dn, smods); + + LOG_TRACE("<= result: %d\n", ret); + +free_and_return: + if (lm) slapi_ch_free((void **)&lm); + if (nt) slapi_ch_free((void **)&nt); + if (modtime) slapi_ch_free((void **)&modtime); + slapi_mods_free(&smods); + ipapwd_free_slapi_value_array(&svals); + ipapwd_free_slapi_value_array(&ntvals); + ipapwd_free_slapi_value_array(&pwvals); + + return ret; +} + + +Slapi_Value **ipapwd_setPasswordHistory(Slapi_Mods *smods, + struct ipapwd_data *data) +{ + Slapi_Value **pH = NULL; + char **pwd_history = NULL; + char **new_pwd_history = NULL; + int n = 0; + int ret; + int i; + + pwd_history = slapi_entry_attr_get_charray(data->target, + "passwordHistory"); + + ret = ipapwd_generate_new_history(data->password, data->timeNow, + data->policy.history_length, + pwd_history, &new_pwd_history, &n); + + if (ret && data->policy.history_length) { + LOG_FATAL("failed to generate new password history!\n"); + goto done; + } + + pH = (Slapi_Value **)slapi_ch_calloc(n + 1, sizeof(Slapi_Value *)); + if (!pH) { + LOG_OOM(); + goto done; + } + + for (i = 0; i < n; i++) { + pH[i] = slapi_value_new_string(new_pwd_history[i]); + if (!pH[i]) { + ipapwd_free_slapi_value_array(&pH); + LOG_OOM(); + goto done; + } + } + +done: + slapi_ch_array_free(pwd_history); + for (i = 0; i < n; i++) { + free(new_pwd_history[i]); + } + free(new_pwd_history); + return pH; +} + +/* Construct Mods pblock and perform the modify operation + * Sets result of operation in SLAPI_PLUGIN_INTOP_RESULT + */ +int ipapwd_apply_mods(const char *dn, Slapi_Mods *mods) +{ + Slapi_PBlock *pb; + int ret; + + LOG_TRACE("=>\n"); + + if (!mods || (slapi_mods_get_num_mods(mods) == 0)) { + return -1; + } + + pb = slapi_pblock_new(); + slapi_modify_internal_set_pb(pb, dn, + slapi_mods_get_ldapmods_byref(mods), + NULL, /* Controls */ + NULL, /* UniqueID */ + ipapwd_plugin_id, /* PluginID */ + 0); /* Flags */ + + ret = slapi_modify_internal_pb(pb); + if (ret) { + LOG_TRACE("WARNING: modify error %d on entry '%s'\n", ret, dn); + } else { + + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret); + + if (ret != LDAP_SUCCESS){ + LOG_TRACE("WARNING: modify error %d on entry '%s'\n", ret, dn); + } else { + LOG_TRACE("<= Successful\n"); + } + } + + slapi_pblock_destroy(pb); + + return ret; +} + +int ipapwd_set_extradata(const char *dn, + const char *principal, + time_t unixtime) +{ + Slapi_Mods *smods; + Slapi_Value *va[2] = { NULL }; + struct berval bv; + char *xdata; + int xd_len; + int p_len; + int ret; + + p_len = strlen(principal); + xd_len = 2 + 4 + p_len + 1; + xdata = malloc(xd_len); + if (!xdata) { + return LDAP_OPERATIONS_ERROR; + } + + smods = slapi_mods_new(); + + /* data type id */ + xdata[0] = 0x00; + xdata[1] = 0x02; + + /* unix timestamp in Little Endian */ + xdata[2] = unixtime & 0xff; + xdata[3] = (unixtime & 0xff00) >> 8; + xdata[4] = (unixtime & 0xff0000) >> 16; + xdata[5] = (unixtime & 0xff000000) >> 24; + + /* append the principal name */ + strncpy(&xdata[6], principal, p_len); + + xdata[xd_len -1] = 0; + + bv.bv_val = xdata; + bv.bv_len = xd_len; + va[0] = slapi_value_new_berval(&bv); + + slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, "krbExtraData", va); + + ret = ipapwd_apply_mods(dn, smods); + + slapi_value_free(&va[0]); + slapi_mods_free(&smods); + + return ret; +} + +void ipapwd_free_slapi_value_array(Slapi_Value ***svals) +{ + Slapi_Value **sv = *svals; + int i; + + if (sv) { + for (i = 0; sv[i]; i++) { + slapi_value_free(&sv[i]); + } + } + + slapi_ch_free((void **)sv); +} + +void free_ipapwd_krbcfg(struct ipapwd_krbcfg **cfg) +{ + struct ipapwd_krbcfg *c = *cfg; + + if (!c) return; + + krb5_free_default_realm(c->krbctx, c->realm); + krb5_free_context(c->krbctx); + free(c->kmkey->contents); + free(c->kmkey); + free(c->supp_encsalts); + free(c->pref_encsalts); + slapi_ch_array_free(c->passsync_mgrs); + free(c); + *cfg = NULL; +}; + diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c new file mode 100644 index 000000000..a92eaf0da --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/encoding.c @@ -0,0 +1,291 @@ +/** BEGIN COPYRIGHT BLOCK + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional permission under GPLv3 section 7: + * + * In the following paragraph, "GPL" means the GNU General Public + * License, version 3 or any later version, and "Non-GPL Code" means + * code that is governed neither by the GPL nor a license + * compatible with the GPL. + * + * You may link the code of this Program with Non-GPL Code and convey + * linked combinations including the two, provided that such Non-GPL + * Code only links to the code of this Program through those well + * defined interfaces identified in the file named EXCEPTION found in + * the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline + * functions from the Approved Interfaces without causing the resulting + * work to be covered by the GPL. Only the copyright holders of this + * Program may make changes or additions to the list of Approved + * Interfaces. + * + * Authors: + * Simo Sorce + * + * Copyright (C) 2007-2010 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "ipapwd.h" +#include "util.h" +#include "ipa_krb5.h" + +/* krbTicketFlags */ +#define KTF_DISALLOW_POSTDATED 0x00000001 +#define KTF_DISALLOW_FORWARDABLE 0x00000002 +#define KTF_DISALLOW_TGT_BASED 0x00000004 +#define KTF_DISALLOW_RENEWABLE 0x00000008 +#define KTF_DISALLOW_PROXIABLE 0x00000010 +#define KTF_DISALLOW_DUP_SKEY 0x00000020 +#define KTF_DISALLOW_ALL_TIX 0x00000040 +#define KTF_REQUIRES_PRE_AUTH 0x00000080 +#define KTF_REQUIRES_HW_AUTH 0x00000100 +#define KTF_REQUIRES_PWCHANGE 0x00000200 +#define KTF_DISALLOW_SVR 0x00001000 +#define KTF_PWCHANGE_SERVICE 0x00002000 + +/* ascii hex output of bytes in "in" + * out len is 32 (preallocated) + * in len is 16 */ +static const char hexchars[] = "0123456789ABCDEF"; +static void hexbuf(char *out, const uint8_t *in) +{ + int i; + + for (i = 0; i < 16; i++) { + out[i*2] = hexchars[in[i] >> 4]; + out[i*2+1] = hexchars[in[i] & 0x0f]; + } +} + +void ipapwd_keyset_free(struct ipapwd_keyset **pkset) +{ + struct ipapwd_keyset *kset = *pkset; + int i; + + if (!kset) return; + + for (i = 0; i < kset->num_keys; i++) { + free(kset->keys[i].key_data_contents[0]); + free(kset->keys[i].key_data_contents[1]); + } + free(kset->keys); + free(kset); + *pkset = NULL; +} + +static Slapi_Value **encrypt_encode_key(struct ipapwd_krbcfg *krbcfg, + struct ipapwd_data *data, + char **errMesg) +{ + krb5_context krbctx; + char *krbPrincipalName = NULL; + int kvno; + struct berval *bval = NULL; + Slapi_Value **svals = NULL; + krb5_principal princ = NULL; + krb5_error_code krberr; + krb5_data pwd; + struct ipapwd_keyset *kset = NULL; + + krbctx = krbcfg->krbctx; + + svals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *)); + if (!svals) { + LOG_OOM(); + return NULL; + } + + kvno = ipapwd_get_cur_kvno(data->target); + + krbPrincipalName = slapi_entry_attr_get_charptr(data->target, + "krbPrincipalName"); + if (!krbPrincipalName) { + *errMesg = "no krbPrincipalName present in this entry\n"; + LOG_FATAL("%s", *errMesg); + goto enc_error; + } + + krberr = krb5_parse_name(krbctx, krbPrincipalName, &princ); + if (krberr) { + LOG_FATAL("krb5_parse_name failed [%s]\n", + krb5_get_error_message(krbctx, krberr)); + goto enc_error; + } + + pwd.data = (char *)data->password; + pwd.length = strlen(data->password); + + kset = malloc(sizeof(struct ipapwd_keyset)); + if (!kset) { + LOG_OOM(); + goto enc_error; + } + + /* this encoding assumes all keys have the same kvno */ + /* major-vno = 1 and minor-vno = 1 */ + kset->major_vno = 1; + kset->minor_vno = 1; + /* increment kvno (will be 1 if this is a new entry) */ + kvno += 1; + kset->mkvno = krbcfg->mkvno; + + krberr = ipa_krb5_generate_key_data(krbctx, princ, + pwd, kvno, krbcfg->kmkey, + krbcfg->num_pref_encsalts, + krbcfg->pref_encsalts, + &kset->num_keys, &kset->keys); + if (krberr != 0) { + LOG_FATAL("generating kerberos keys failed [%s]\n", + krb5_get_error_message(krbctx, krberr)); + goto enc_error; + } + + krberr = ber_encode_krb5_key_data(kset->keys, kset->num_keys, + kset->mkvno, &bval); + if (krberr != 0) { + LOG_FATAL("encoding krb5_key_data failed\n"); + goto enc_error; + } + + svals[0] = slapi_value_new_berval(bval); + if (!svals[0]) { + LOG_FATAL("Converting berval to Slapi_Value\n"); + goto enc_error; + } + + ipapwd_keyset_free(&kset); + krb5_free_principal(krbctx, princ); + slapi_ch_free_string(&krbPrincipalName); + ber_bvfree(bval); + return svals; + +enc_error: + *errMesg = "key encryption/encoding failed\n"; + if (kset) ipapwd_keyset_free(&kset); + krb5_free_principal(krbctx, princ); + slapi_ch_free_string(&krbPrincipalName); + if (bval) ber_bvfree(bval); + free(svals); + return NULL; +} + +int ipapwd_gen_hashes(struct ipapwd_krbcfg *krbcfg, + struct ipapwd_data *data, char *userpw, + int is_krb, int is_smb, int is_ipant, Slapi_Value ***svals, + char **nthash, char **lmhash, Slapi_Value ***ntvals, + char **errMesg) +{ + int rc; + char *userpw_uc = NULL; + + *svals = NULL; + *nthash = NULL; + *lmhash = NULL; + *errMesg = NULL; + + if (is_krb) { + + *svals = encrypt_encode_key(krbcfg, data, errMesg); + + if (!*svals) { + /* errMesg should have been set in encrypt_encode_key() */ + LOG_FATAL("key encryption/encoding failed\n"); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + } + + if (is_smb || is_ipant) { + char lm[33], nt[33]; + struct ntlm_keys ntlm; + int ret; + + userpw_uc = (char *) slapi_utf8StrToUpper((unsigned char *) userpw); + if (!userpw_uc) { + *errMesg = "Failed to generate upper case password\n"; + LOG_FATAL("%s", *errMesg); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + ret = encode_ntlm_keys(userpw, + userpw_uc, + krbcfg->allow_lm_hash, + krbcfg->allow_nt_hash, + &ntlm); + memset(userpw_uc, 0, strlen(userpw_uc)); + slapi_ch_free_string(&userpw_uc); + if (ret) { + *errMesg = "Failed to generate NT/LM hashes\n"; + LOG_FATAL("%s", *errMesg); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + if (krbcfg->allow_lm_hash) { + hexbuf(lm, ntlm.lm); + lm[32] = '\0'; + *lmhash = slapi_ch_strdup(lm); + } + if (krbcfg->allow_nt_hash) { + hexbuf(nt, ntlm.nt); + nt[32] = '\0'; + *nthash = slapi_ch_strdup(nt); + } + + if (is_ipant) { + *ntvals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *)); + if (!*ntvals) { + LOG_OOM(); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + (*ntvals)[0] = slapi_value_new(); + if (slapi_value_set((*ntvals)[0], ntlm.nt, 16) == NULL) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + } + } + + rc = LDAP_SUCCESS; + +done: + + /* when error, free possibly allocated output parameters */ + if (rc) { + ipapwd_free_slapi_value_array(svals); + ipapwd_free_slapi_value_array(ntvals); + } + + return rc; +} + diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h index 3689783ca..372441ddd 100644 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h @@ -96,7 +96,7 @@ struct ipapwd_operation { #define GENERALIZED_TIME_LENGTH 15 -/* from ipapwd_common.c */ +/* from common.c */ struct ipapwd_krbcfg { krb5_context krbctx; char *realm; @@ -131,7 +131,7 @@ int ipapwd_set_extradata(const char *dn, void ipapwd_free_slapi_value_array(Slapi_Value ***svals); void free_ipapwd_krbcfg(struct ipapwd_krbcfg **cfg); -/* from ipapwd_encoding.c */ +/* from encoding.c */ struct ipapwd_keyset { uint16_t major_vno; uint16_t minor_vno; @@ -148,7 +148,7 @@ int ipapwd_gen_hashes(struct ipapwd_krbcfg *krbcfg, Slapi_Value ***svals, char **nthash, char **lmhash, Slapi_Value ***ntvals, char **errMesg); -/* from ipapwd_prepost.c */ +/* from prepost.c */ int ipapwd_ext_init(void); int ipapwd_pre_init(Slapi_PBlock *pb); int ipapwd_post_init(Slapi_PBlock *pb); diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c deleted file mode 100644 index bb1d96ade..000000000 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c +++ /dev/null @@ -1,1107 +0,0 @@ -/** BEGIN COPYRIGHT BLOCK - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Additional permission under GPLv3 section 7: - * - * In the following paragraph, "GPL" means the GNU General Public - * License, version 3 or any later version, and "Non-GPL Code" means - * code that is governed neither by the GPL nor a license - * compatible with the GPL. - * - * You may link the code of this Program with Non-GPL Code and convey - * linked combinations including the two, provided that such Non-GPL - * Code only links to the code of this Program through those well - * defined interfaces identified in the file named EXCEPTION found in - * the source code files (the "Approved Interfaces"). The files of - * Non-GPL Code may instantiate templates or use macros or inline - * functions from the Approved Interfaces without causing the resulting - * work to be covered by the GPL. Only the copyright holders of this - * Program may make changes or additions to the list of Approved - * Interfaces. - * - * Authors: - * Simo Sorce - * - * Copyright (C) 2007-2010 Red Hat, Inc. - * All rights reserved. - * END COPYRIGHT BLOCK **/ - -#include "ipapwd.h" -#include "util.h" - -/* Type of connection for this operation;*/ -#define LDAP_EXTOP_PASSMOD_CONN_SECURE - -/* Uncomment the following #undef FOR TESTING: - * allows non-SSL connections to use the password change extended op */ -/* #undef LDAP_EXTOP_PASSMOD_CONN_SECURE */ - -extern void *ipapwd_plugin_id; -extern const char *ipa_realm_dn; -extern const char *ipa_etc_config_dn; -extern const char *ipa_pwd_config_dn; - -/* These are the default enc:salt types if nothing is defined. - * TODO: retrieve the configure set of ecntypes either from the - * kfc.conf file or by synchronizing the file content into - * the directory */ -static const char *ipapwd_def_encsalts[] = { - "des3-hmac-sha1:normal", -/* "arcfour-hmac:normal", - "des-hmac-sha1:normal", - "des-cbc-md5:normal", */ - "des-cbc-crc:normal", -/* "des-cbc-crc:v4", - "des-cbc-crc:afs3", */ - NULL -}; - -static struct ipapwd_krbcfg *ipapwd_getConfig(void) -{ - krb5_error_code krberr; - struct ipapwd_krbcfg *config = NULL; - krb5_keyblock *kmkey = NULL; - Slapi_Entry *realm_entry = NULL; - Slapi_Entry *config_entry = NULL; - Slapi_Attr *a; - Slapi_Value *v; - BerElement *be = NULL; - ber_tag_t tag, tvno; - ber_int_t ttype; - const struct berval *bval; - struct berval *mkey = NULL; - char **encsalts; - char **tmparray; - char *tmpstr; - int i, ret; - - config = calloc(1, sizeof(struct ipapwd_krbcfg)); - if (!config) { - LOG_OOM(); - goto free_and_error; - } - kmkey = calloc(1, sizeof(krb5_keyblock)); - if (!kmkey) { - LOG_OOM(); - goto free_and_error; - } - config->kmkey = kmkey; - - krberr = krb5_init_context(&config->krbctx); - if (krberr) { - LOG_FATAL("krb5_init_context failed\n"); - goto free_and_error; - } - - ret = krb5_get_default_realm(config->krbctx, &config->realm); - if (ret) { - LOG_FATAL("Failed to get default realm?!\n"); - goto free_and_error; - } - - /* get the Realm Container entry */ - ret = ipapwd_getEntry(ipa_realm_dn, &realm_entry, NULL); - if (ret != LDAP_SUCCESS) { - LOG_FATAL("No realm Entry?\n"); - goto free_and_error; - } - - /*** get the Kerberos Master Key ***/ - - ret = slapi_entry_attr_find(realm_entry, "krbMKey", &a); - if (ret == -1) { - LOG_FATAL("No master key??\n"); - goto free_and_error; - } - - /* there should be only one value here */ - ret = slapi_attr_first_value(a, &v); - if (ret == -1) { - LOG_FATAL("No master key??\n"); - goto free_and_error; - } - - bval = slapi_value_get_berval(v); - if (!bval) { - LOG_FATAL("Error retrieving master key berval\n"); - goto free_and_error; - } - - be = ber_init(discard_const(bval)); - if (!be) { - LOG_FATAL("ber_init() failed!\n"); - goto free_and_error; - } - - tag = ber_scanf(be, "{i{iO}}", &tvno, &ttype, &mkey); - if (tag == LBER_ERROR) { - LOG_FATAL("Bad Master key encoding ?!\n"); - goto free_and_error; - } - - config->mkvno = tvno; - kmkey->magic = KV5M_KEYBLOCK; - kmkey->enctype = ttype; - kmkey->length = mkey->bv_len; - kmkey->contents = malloc(mkey->bv_len); - if (!kmkey->contents) { - LOG_OOM(); - goto free_and_error; - } - memcpy(kmkey->contents, mkey->bv_val, mkey->bv_len); - ber_bvfree(mkey); - ber_free(be, 1); - mkey = NULL; - be = NULL; - - /*** get the Supported Enc/Salt types ***/ - - encsalts = slapi_entry_attr_get_charray(realm_entry, - "krbSupportedEncSaltTypes"); - if (encsalts) { - for (i = 0; encsalts[i]; i++) /* count */ ; - ret = parse_bval_key_salt_tuples(config->krbctx, - (const char * const *)encsalts, i, - &config->supp_encsalts, - &config->num_supp_encsalts); - slapi_ch_array_free(encsalts); - } else { - LOG("No configured salt types use defaults\n"); - for (i = 0; ipapwd_def_encsalts[i]; i++) /* count */ ; - ret = parse_bval_key_salt_tuples(config->krbctx, - ipapwd_def_encsalts, i, - &config->supp_encsalts, - &config->num_supp_encsalts); - } - if (ret) { - LOG_FATAL("Can't get Supported EncSalt Types\n"); - goto free_and_error; - } - - /*** get the Preferred Enc/Salt types ***/ - - encsalts = slapi_entry_attr_get_charray(realm_entry, - "krbDefaultEncSaltTypes"); - if (encsalts) { - for (i = 0; encsalts[i]; i++) /* count */ ; - ret = parse_bval_key_salt_tuples(config->krbctx, - (const char * const *)encsalts, i, - &config->pref_encsalts, - &config->num_pref_encsalts); - slapi_ch_array_free(encsalts); - } else { - LOG("No configured salt types use defaults\n"); - for (i = 0; ipapwd_def_encsalts[i]; i++) /* count */ ; - ret = parse_bval_key_salt_tuples(config->krbctx, - ipapwd_def_encsalts, i, - &config->pref_encsalts, - &config->num_pref_encsalts); - } - if (ret) { - LOG_FATAL("Can't get Preferred EncSalt Types\n"); - goto free_and_error; - } - - slapi_entry_free(realm_entry); - - /* get the Realm Container entry */ - ret = ipapwd_getEntry(ipa_pwd_config_dn, &config_entry, NULL); - if (ret != LDAP_SUCCESS) { - LOG_FATAL("No config Entry? Impossible!\n"); - goto free_and_error; - } - config->passsync_mgrs = - slapi_entry_attr_get_charray(config_entry, "passSyncManagersDNs"); - /* now add Directory Manager, it is always added by default */ - tmpstr = slapi_ch_strdup("cn=Directory Manager"); - slapi_ch_array_add(&config->passsync_mgrs, tmpstr); - if (config->passsync_mgrs == NULL) { - LOG_OOM(); - goto free_and_error; - } - for (i = 0; config->passsync_mgrs[i]; i++) /* count */ ; - config->num_passsync_mgrs = i; - - slapi_entry_free(config_entry); - - /* get the ipa etc/ipaConfig entry */ - config->allow_lm_hash = false; - config->allow_nt_hash = false; - ret = ipapwd_getEntry(ipa_etc_config_dn, &config_entry, NULL); - if (ret != LDAP_SUCCESS) { - LOG_FATAL("No config Entry?\n"); - goto free_and_error; - } else { - tmparray = slapi_entry_attr_get_charray(config_entry, - "ipaConfigString"); - for (i = 0; tmparray && tmparray[i]; i++) { - if (strcasecmp(tmparray[i], "AllowLMhash") == 0) { - config->allow_lm_hash = true; - continue; - } - if (strcasecmp(tmparray[i], "AllowNThash") == 0) { - config->allow_nt_hash = true; - continue; - } - } - if (tmparray) slapi_ch_array_free(tmparray); - } - - slapi_entry_free(config_entry); - - return config; - -free_and_error: - if (mkey) ber_bvfree(mkey); - if (be) ber_free(be, 1); - if (kmkey) { - free(kmkey->contents); - free(kmkey); - } - if (config) { - if (config->krbctx) { - if (config->realm) - krb5_free_default_realm(config->krbctx, config->realm); - krb5_free_context(config->krbctx); - } - free(config->pref_encsalts); - free(config->supp_encsalts); - slapi_ch_array_free(config->passsync_mgrs); - free(config); - } - slapi_entry_free(config_entry); - slapi_entry_free(realm_entry); - return NULL; -} - -/* Easier handling for virtual attributes. You must call pwd_values_free() - * to free memory allocated here. It must be called before - * slapi_free_search_results_internal(entries) or - * slapi_pblock_destroy(pb) - */ -static int pwd_get_values(const Slapi_Entry *ent, const char *attrname, - Slapi_ValueSet** results, char** actual_type_name, - int *buffer_flags) -{ - int flags=0; - int type_name_disposition = 0; - int ret; - - ret = slapi_vattr_values_get((Slapi_Entry *)ent, (char *)attrname, - results, &type_name_disposition, - actual_type_name, flags, buffer_flags); - - return ret; -} - -static void pwd_values_free(Slapi_ValueSet** results, - char** actual_type_name, int buffer_flags) -{ - slapi_vattr_values_free(results, actual_type_name, buffer_flags); -} - -static int ipapwd_rdn_count(const char *dn) -{ - int rdnc = 0; - LDAPDN ldn; - int ret; - - ret = ldap_str2dn(dn, &ldn, LDAP_DN_FORMAT_LDAPV3); - if (ret != LDAP_SUCCESS) { - LOG_TRACE("ldap_str2dn(dn) failed ?!"); - return -1; - } - - for (rdnc = 0; ldn != NULL && ldn[rdnc]; rdnc++) /* count */ ; - ldap_dnfree(ldn); - - return rdnc; -} - -int ipapwd_getPolicy(const char *dn, - Slapi_Entry *target, - struct ipapwd_policy *policy) -{ - const char *krbPwdPolicyReference; - const char *pdn; - const Slapi_DN *psdn; - Slapi_Backend *be; - Slapi_PBlock *pb = NULL; - char *attrs[] = { "krbMaxPwdLife", "krbMinPwdLife", - "krbPwdMinDiffChars", "krbPwdMinLength", - "krbPwdHistoryLength", NULL}; - Slapi_Entry **es = NULL; - Slapi_Entry *pe = NULL; - int ret, res, dist, rdnc, scope, i; - Slapi_DN *sdn = NULL; - int buffer_flags=0; - Slapi_ValueSet* results = NULL; - char* actual_type_name = NULL; - int tmpint; - - LOG_TRACE("Searching policy for [%s]\n", dn); - - sdn = slapi_sdn_new_dn_byref(dn); - if (sdn == NULL) { - LOG_OOM(); - ret = -1; - goto done; - } - - pwd_get_values(target, "krbPwdPolicyReference", - &results, &actual_type_name, &buffer_flags); - if (results) { - Slapi_Value *sv; - slapi_valueset_first_value(results, &sv); - krbPwdPolicyReference = slapi_value_get_string(sv); - pdn = krbPwdPolicyReference; - scope = LDAP_SCOPE_BASE; - LOG_TRACE("using policy reference: %s\n", pdn); - } else { - /* Find ancestor base DN */ - be = slapi_be_select(sdn); - psdn = slapi_be_getsuffix(be, 0); - if (psdn == NULL) { - LOG_FATAL("Invalid DN [%s]\n", dn); - ret = -1; - goto done; - } - pdn = slapi_sdn_get_dn(psdn); - scope = LDAP_SCOPE_SUBTREE; - } - - pb = slapi_pblock_new(); - slapi_search_internal_set_pb(pb, - pdn, scope, - "(objectClass=krbPwdPolicy)", - attrs, 0, - NULL, /* Controls */ - NULL, /* UniqueID */ - ipapwd_plugin_id, - 0); /* Flags */ - - /* do search the tree */ - ret = slapi_search_internal_pb(pb); - slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &res); - if (ret == -1 || res != LDAP_SUCCESS) { - LOG_FATAL("Couldn't find policy, err (%d)\n", res ? res : ret); - ret = -1; - goto done; - } - - /* get entries */ - slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &es); - if (!es) { - LOG_TRACE("No entries ?!"); - ret = -1; - goto done; - } - - /* count entries */ - for (i = 0; es[i]; i++) /* count */ ; - - /* if there is only one, return that */ - if (i == 1) { - pe = es[0]; - goto fill; - } - - /* count number of RDNs in DN */ - rdnc = ipapwd_rdn_count(dn); - if (rdnc == -1) { - LOG_TRACE("ipapwd_rdn_count(dn) failed"); - ret = -1; - goto done; - } - - pe = NULL; - dist = -1; - - /* find closest entry */ - for (i = 0; es[i]; i++) { - const Slapi_DN *esdn; - - esdn = slapi_entry_get_sdn_const(es[i]); - if (esdn == NULL) continue; - if (0 == slapi_sdn_compare(esdn, sdn)) { - pe = es[i]; - dist = 0; - break; - } - if (slapi_sdn_issuffix(sdn, esdn)) { - const char *dn1; - int c1; - - dn1 = slapi_sdn_get_dn(esdn); - if (!dn1) continue; - c1 = ipapwd_rdn_count(dn1); - if (c1 == -1) continue; - if ((dist == -1) || - ((rdnc - c1) < dist)) { - dist = rdnc - c1; - pe = es[i]; - } - } - if (dist == 0) break; /* found closest */ - } - - if (pe == NULL) { - ret = -1; - goto done; - } - -fill: - policy->min_pwd_life = slapi_entry_attr_get_int(pe, "krbMinPwdLife"); - - tmpint = slapi_entry_attr_get_int(pe, "krbMaxPwdLife"); - if (tmpint != 0) { - policy->max_pwd_life = tmpint; - } - - tmpint = slapi_entry_attr_get_int(pe, "krbPwdMinLength"); - if (tmpint != 0) { - policy->min_pwd_length = tmpint; - } - - policy->history_length = slapi_entry_attr_get_int(pe, - "krbPwdHistoryLength"); - - policy->min_complexity = slapi_entry_attr_get_int(pe, - "krbPwdMinDiffChars"); - - ret = 0; - -done: - if (results) { - pwd_values_free(&results, &actual_type_name, buffer_flags); - } - if (pb) { - slapi_free_search_results_internal(pb); - slapi_pblock_destroy(pb); - } - if (sdn) slapi_sdn_free(&sdn); - return ret; -} - - -/*==Common-public-functions=============================================*/ - -int ipapwd_entry_checks(Slapi_PBlock *pb, struct slapi_entry *e, - int *is_root, int *is_krb, int *is_smb, int *is_ipant, - char *attr, int acc) -{ - Slapi_Value *sval; - int rc; - - /* Check ACIs */ - slapi_pblock_get(pb, SLAPI_REQUESTOR_ISROOT, is_root); - - if (!*is_root) { - /* verify this user is allowed to write a user password */ - rc = slapi_access_allowed(pb, e, attr, NULL, acc); - if (rc != LDAP_SUCCESS) { - /* we have no business here, the operation will be denied anyway */ - rc = LDAP_SUCCESS; - goto done; - } - } - - /* Check if this is a krbPrincial and therefore needs us to generate other - * hashes */ - sval = slapi_value_new_string("krbPrincipalAux"); - if (!sval) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - *is_krb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval); - slapi_value_free(&sval); - - sval = slapi_value_new_string("sambaSamAccount"); - if (!sval) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - *is_smb = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, sval); - slapi_value_free(&sval); - - sval = slapi_value_new_string("ipaNTUserAttrs"); - if (!sval) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - *is_ipant = slapi_entry_attr_has_syntax_value(e, SLAPI_ATTR_OBJECTCLASS, - sval); - slapi_value_free(&sval); - - rc = LDAP_SUCCESS; - -done: - return rc; -} - -int ipapwd_gen_checks(Slapi_PBlock *pb, char **errMesg, - struct ipapwd_krbcfg **config, int check_flags) -{ - int ret, ssf; - int rc = LDAP_SUCCESS; - Slapi_Backend *be; - const Slapi_DN *psdn; - Slapi_DN *sdn; - char *dn = NULL; - - LOG_TRACE("=>\n"); - -#ifdef LDAP_EXTOP_PASSMOD_CONN_SECURE - if (check_flags & IPAPWD_CHECK_CONN_SECURE) { - /* Allow password modify on all connections with a Security Strength - * Factor (SSF) higher than 1 */ - if (slapi_pblock_get(pb, SLAPI_OPERATION_SSF, &ssf) != 0) { - LOG("Could not get SSF from connection\n"); - *errMesg = "Operation requires a secure connection.\n"; - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - - if (ssf <= 1) { - *errMesg = "Operation requires a secure connection.\n"; - rc = LDAP_CONFIDENTIALITY_REQUIRED; - goto done; - } - } -#endif - - if (check_flags & IPAPWD_CHECK_DN) { - /* check we have a valid DN in the pblock or just abort */ - ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); - if (ret) { - LOG("Tried to change password for an invalid DN [%s]\n", - dn ? dn : ""); - *errMesg = "Invalid DN"; - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - sdn = slapi_sdn_new_dn_byref(dn); - if (!sdn) { - LOG_FATAL("Unable to convert dn to sdn %s", dn ? dn : ""); - *errMesg = "Internal Error"; - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - be = slapi_be_select(sdn); - slapi_sdn_free(&sdn); - - psdn = slapi_be_getsuffix(be, 0); - if (!psdn) { - *errMesg = "Invalid DN"; - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - } - - /* get the kerberos context and master key */ - *config = ipapwd_getConfig(); - if (NULL == *config) { - LOG_FATAL("Error Retrieving Master Key"); - *errMesg = "Fatal Internal Error"; - rc = LDAP_OPERATIONS_ERROR; - } - -done: - return rc; -} - -/* check password strenght and history */ -int ipapwd_CheckPolicy(struct ipapwd_data *data) -{ - struct ipapwd_policy pol = {0}; - time_t acct_expiration; - time_t pwd_expiration; - time_t last_pwd_change; - char **pwd_history; - char *tmpstr; - int ret; - - pol.max_pwd_life = IPAPWD_DEFAULT_PWDLIFE; - pol.min_pwd_length = IPAPWD_DEFAULT_MINLEN; - - if (data->changetype != IPA_CHANGETYPE_NORMAL) { - /* We must skip policy checks (Admin change) but - * force a password change on the next login. - * But not if Directory Manager */ - if (data->changetype == IPA_CHANGETYPE_ADMIN) { - /* The expiration date needs to be older than the current time - * otherwise the KDC may not immediately register the password - * as expired. The last password change needs to match the - * password expiration otherwise minlife issues will arise. - */ - data->timeNow -= 1; - data->expireTime = data->timeNow; - } - - /* do not load policies */ - } else { - - /* find the entry with the password policy */ - ret = ipapwd_getPolicy(data->dn, data->target, &pol); - if (ret) { - LOG_TRACE("No password policy, use defaults"); - } - } - - tmpstr = slapi_entry_attr_get_charptr(data->target, - "krbPrincipalExpiration"); - acct_expiration = ipapwd_gentime_to_time_t(tmpstr); - slapi_ch_free_string(&tmpstr); - - tmpstr = slapi_entry_attr_get_charptr(data->target, - "krbPasswordExpiration"); - pwd_expiration = ipapwd_gentime_to_time_t(tmpstr); - slapi_ch_free_string(&tmpstr); - - tmpstr = slapi_entry_attr_get_charptr(data->target, - "krbLastPwdChange"); - last_pwd_change = ipapwd_gentime_to_time_t(tmpstr); - slapi_ch_free_string(&tmpstr); - - pwd_history = slapi_entry_attr_get_charray(data->target, - "passwordHistory"); - - /* check policy */ - ret = ipapwd_check_policy(&pol, data->password, - data->timeNow, - acct_expiration, - pwd_expiration, - last_pwd_change, - pwd_history); - - slapi_ch_array_free(pwd_history); - - if (data->expireTime == 0) { - data->expireTime = data->timeNow + pol.max_pwd_life; - } - - data->policy = pol; - - return ret; -} - -/* Searches the dn in directory, - * If found : fills in slapi_entry structure and returns 0 - * If NOT found : returns the search result as LDAP_NO_SUCH_OBJECT - */ -int ipapwd_getEntry(const char *dn, Slapi_Entry **e2, char **attrlist) -{ - Slapi_DN *sdn; - int search_result = 0; - - LOG_TRACE("=>\n"); - - sdn = slapi_sdn_new_dn_byref(dn); - search_result = slapi_search_internal_get_entry(sdn, attrlist, e2, - ipapwd_plugin_id); - if (search_result != LDAP_SUCCESS) { - LOG_TRACE("No such entry-(%s), err (%d)\n", dn, search_result); - } - - slapi_sdn_free(&sdn); - LOG_TRACE("<= result: %d\n", search_result); - return search_result; -} - -int ipapwd_get_cur_kvno(Slapi_Entry *target) -{ - Slapi_Attr *krbPrincipalKey = NULL; - Slapi_ValueSet *svs; - Slapi_Value *sv; - BerElement *be = NULL; - const struct berval *cbval; - ber_tag_t tag, tmp; - ber_int_t tkvno; - int hint; - int kvno; - int ret; - - /* retrieve current kvno and and keys */ - ret = slapi_entry_attr_find(target, "krbPrincipalKey", &krbPrincipalKey); - if (ret != 0) { - return 0; - } - - kvno = 0; - - slapi_attr_get_valueset(krbPrincipalKey, &svs); - hint = slapi_valueset_first_value(svs, &sv); - while (hint != -1) { - cbval = slapi_value_get_berval(sv); - if (!cbval) { - LOG_TRACE("Error retrieving berval from Slapi_Value\n"); - goto next; - } - be = ber_init(discard_const(cbval)); - if (!be) { - LOG_TRACE("ber_init() failed!\n"); - goto next; - } - - tag = ber_scanf(be, "{xxt[i]", &tmp, &tkvno); - if (tag == LBER_ERROR) { - LOG_TRACE("Bad OLD key encoding ?!\n"); - ber_free(be, 1); - goto next; - } - - if (tkvno > kvno) { - kvno = tkvno; - } - - ber_free(be, 1); -next: - hint = slapi_valueset_next_value(svs, hint, &sv); - } - - return kvno; -} - -/* Modify the Password attributes of the entry */ -int ipapwd_SetPassword(struct ipapwd_krbcfg *krbcfg, - struct ipapwd_data *data, int is_krb) -{ - int ret = 0; - Slapi_Mods *smods = NULL; - Slapi_Value **svals = NULL; - Slapi_Value **ntvals = NULL; - Slapi_Value **pwvals = NULL; - struct tm utctime; - char timestr[GENERALIZED_TIME_LENGTH+1]; - char *lm = NULL; - char *nt = NULL; - int is_smb = 0; - int is_ipant = 0; - int is_host = 0; - Slapi_Value *sambaSamAccount; - Slapi_Value *ipaNTUserAttrs; - Slapi_Value *ipaHost; - char *errMesg = NULL; - char *modtime = NULL; - - LOG_TRACE("=>\n"); - - sambaSamAccount = slapi_value_new_string("sambaSamAccount"); - if (slapi_entry_attr_has_syntax_value(data->target, - "objectClass", sambaSamAccount)) { - is_smb = 1; - } - slapi_value_free(&sambaSamAccount); - - ipaNTUserAttrs = slapi_value_new_string("ipaNTUserAttrs"); - if (slapi_entry_attr_has_syntax_value(data->target, - "objectClass", ipaNTUserAttrs)) { - is_ipant = 1; - } - slapi_value_free(&ipaNTUserAttrs); - - ipaHost = slapi_value_new_string("ipaHost"); - if (slapi_entry_attr_has_syntax_value(data->target, - "objectClass", ipaHost)) { - is_host = 1; - } - slapi_value_free(&ipaHost); - - ret = ipapwd_gen_hashes(krbcfg, data, - data->password, - is_krb, is_smb, is_ipant, - &svals, &nt, &lm, &ntvals, &errMesg); - if (ret) { - goto free_and_return; - } - - smods = slapi_mods_new(); - - if (svals) { - slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, - "krbPrincipalKey", svals); - - /* krbLastPwdChange is used to tell whether a host entry has a - * keytab so don't set it on hosts. - */ - if (!is_host) { - /* change Last Password Change field with the current date */ - if (!gmtime_r(&(data->timeNow), &utctime)) { - LOG_FATAL("failed to retrieve current date (buggy gmtime_r ?)\n"); - ret = LDAP_OPERATIONS_ERROR; - goto free_and_return; - } - strftime(timestr, GENERALIZED_TIME_LENGTH + 1, - "%Y%m%d%H%M%SZ", &utctime); - slapi_mods_add_string(smods, LDAP_MOD_REPLACE, - "krbLastPwdChange", timestr); - - /* set Password Expiration date */ - if (!gmtime_r(&(data->expireTime), &utctime)) { - LOG_FATAL("failed to convert expiration date\n"); - ret = LDAP_OPERATIONS_ERROR; - goto free_and_return; - } - strftime(timestr, GENERALIZED_TIME_LENGTH + 1, - "%Y%m%d%H%M%SZ", &utctime); - slapi_mods_add_string(smods, LDAP_MOD_REPLACE, - "krbPasswordExpiration", timestr); - } - } - - if (lm && is_smb) { - slapi_mods_add_string(smods, LDAP_MOD_REPLACE, - "sambaLMPassword", lm); - } - - if (nt && is_smb) { - slapi_mods_add_string(smods, LDAP_MOD_REPLACE, - "sambaNTPassword", nt); - } - - if (ntvals && is_ipant) { - slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, - "ipaNTHash", ntvals); - } - - if (is_smb) { - /* with samba integration we need to also set sambaPwdLastSet or - * samba will decide the user has to change the password again */ - if (data->changetype == IPA_CHANGETYPE_ADMIN) { - /* if it is an admin change instead we need to let know to - * samba as well that the use rmust change its password */ - modtime = slapi_ch_smprintf("0"); - } else { - modtime = slapi_ch_smprintf("%ld", (long)data->timeNow); - } - if (!modtime) { - LOG_FATAL("failed to smprintf string!\n"); - ret = LDAP_OPERATIONS_ERROR; - goto free_and_return; - } - slapi_mods_add_string(smods, LDAP_MOD_REPLACE, - "sambaPwdLastset", modtime); - } - if (is_krb) { - if (data->changetype == IPA_CHANGETYPE_ADMIN) { - slapi_mods_add_string(smods, LDAP_MOD_REPLACE, - "krbLoginFailedCount", "0"); - } - } - /* let DS encode the password itself, this allows also other plugins to - * intercept it to perform operations like synchronization with Active - * Directory domains through the replication plugin */ - slapi_mods_add_string(smods, LDAP_MOD_REPLACE, - "userPassword", data->password); - - /* set password history */ - if (data->policy.history_length > 0) { - pwvals = ipapwd_setPasswordHistory(smods, data); - if (pwvals) { - slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, - "passwordHistory", pwvals); - } - } - - /* FIXME: - * instead of replace we should use a delete/add so that we are - * completely sure nobody else modified the entry meanwhile and - * fail if that's the case */ - - /* commit changes */ - ret = ipapwd_apply_mods(data->dn, smods); - - LOG_TRACE("<= result: %d\n", ret); - -free_and_return: - if (lm) slapi_ch_free((void **)&lm); - if (nt) slapi_ch_free((void **)&nt); - if (modtime) slapi_ch_free((void **)&modtime); - slapi_mods_free(&smods); - ipapwd_free_slapi_value_array(&svals); - ipapwd_free_slapi_value_array(&ntvals); - ipapwd_free_slapi_value_array(&pwvals); - - return ret; -} - - -Slapi_Value **ipapwd_setPasswordHistory(Slapi_Mods *smods, - struct ipapwd_data *data) -{ - Slapi_Value **pH = NULL; - char **pwd_history = NULL; - char **new_pwd_history = NULL; - int n = 0; - int ret; - int i; - - pwd_history = slapi_entry_attr_get_charray(data->target, - "passwordHistory"); - - ret = ipapwd_generate_new_history(data->password, data->timeNow, - data->policy.history_length, - pwd_history, &new_pwd_history, &n); - - if (ret && data->policy.history_length) { - LOG_FATAL("failed to generate new password history!\n"); - goto done; - } - - pH = (Slapi_Value **)slapi_ch_calloc(n + 1, sizeof(Slapi_Value *)); - if (!pH) { - LOG_OOM(); - goto done; - } - - for (i = 0; i < n; i++) { - pH[i] = slapi_value_new_string(new_pwd_history[i]); - if (!pH[i]) { - ipapwd_free_slapi_value_array(&pH); - LOG_OOM(); - goto done; - } - } - -done: - slapi_ch_array_free(pwd_history); - for (i = 0; i < n; i++) { - free(new_pwd_history[i]); - } - free(new_pwd_history); - return pH; -} - -/* Construct Mods pblock and perform the modify operation - * Sets result of operation in SLAPI_PLUGIN_INTOP_RESULT - */ -int ipapwd_apply_mods(const char *dn, Slapi_Mods *mods) -{ - Slapi_PBlock *pb; - int ret; - - LOG_TRACE("=>\n"); - - if (!mods || (slapi_mods_get_num_mods(mods) == 0)) { - return -1; - } - - pb = slapi_pblock_new(); - slapi_modify_internal_set_pb(pb, dn, - slapi_mods_get_ldapmods_byref(mods), - NULL, /* Controls */ - NULL, /* UniqueID */ - ipapwd_plugin_id, /* PluginID */ - 0); /* Flags */ - - ret = slapi_modify_internal_pb(pb); - if (ret) { - LOG_TRACE("WARNING: modify error %d on entry '%s'\n", ret, dn); - } else { - - slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret); - - if (ret != LDAP_SUCCESS){ - LOG_TRACE("WARNING: modify error %d on entry '%s'\n", ret, dn); - } else { - LOG_TRACE("<= Successful\n"); - } - } - - slapi_pblock_destroy(pb); - - return ret; -} - -int ipapwd_set_extradata(const char *dn, - const char *principal, - time_t unixtime) -{ - Slapi_Mods *smods; - Slapi_Value *va[2] = { NULL }; - struct berval bv; - char *xdata; - int xd_len; - int p_len; - int ret; - - p_len = strlen(principal); - xd_len = 2 + 4 + p_len + 1; - xdata = malloc(xd_len); - if (!xdata) { - return LDAP_OPERATIONS_ERROR; - } - - smods = slapi_mods_new(); - - /* data type id */ - xdata[0] = 0x00; - xdata[1] = 0x02; - - /* unix timestamp in Little Endian */ - xdata[2] = unixtime & 0xff; - xdata[3] = (unixtime & 0xff00) >> 8; - xdata[4] = (unixtime & 0xff0000) >> 16; - xdata[5] = (unixtime & 0xff000000) >> 24; - - /* append the principal name */ - strncpy(&xdata[6], principal, p_len); - - xdata[xd_len -1] = 0; - - bv.bv_val = xdata; - bv.bv_len = xd_len; - va[0] = slapi_value_new_berval(&bv); - - slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, "krbExtraData", va); - - ret = ipapwd_apply_mods(dn, smods); - - slapi_value_free(&va[0]); - slapi_mods_free(&smods); - - return ret; -} - -void ipapwd_free_slapi_value_array(Slapi_Value ***svals) -{ - Slapi_Value **sv = *svals; - int i; - - if (sv) { - for (i = 0; sv[i]; i++) { - slapi_value_free(&sv[i]); - } - } - - slapi_ch_free((void **)sv); -} - -void free_ipapwd_krbcfg(struct ipapwd_krbcfg **cfg) -{ - struct ipapwd_krbcfg *c = *cfg; - - if (!c) return; - - krb5_free_default_realm(c->krbctx, c->realm); - krb5_free_context(c->krbctx); - free(c->kmkey->contents); - free(c->kmkey); - free(c->supp_encsalts); - free(c->pref_encsalts); - slapi_ch_array_free(c->passsync_mgrs); - free(c); - *cfg = NULL; -}; - diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_encoding.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_encoding.c deleted file mode 100644 index a92eaf0da..000000000 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_encoding.c +++ /dev/null @@ -1,291 +0,0 @@ -/** BEGIN COPYRIGHT BLOCK - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Additional permission under GPLv3 section 7: - * - * In the following paragraph, "GPL" means the GNU General Public - * License, version 3 or any later version, and "Non-GPL Code" means - * code that is governed neither by the GPL nor a license - * compatible with the GPL. - * - * You may link the code of this Program with Non-GPL Code and convey - * linked combinations including the two, provided that such Non-GPL - * Code only links to the code of this Program through those well - * defined interfaces identified in the file named EXCEPTION found in - * the source code files (the "Approved Interfaces"). The files of - * Non-GPL Code may instantiate templates or use macros or inline - * functions from the Approved Interfaces without causing the resulting - * work to be covered by the GPL. Only the copyright holders of this - * Program may make changes or additions to the list of Approved - * Interfaces. - * - * Authors: - * Simo Sorce - * - * Copyright (C) 2007-2010 Red Hat, Inc. - * All rights reserved. - * END COPYRIGHT BLOCK **/ - -#ifdef HAVE_CONFIG_H -# include -#endif - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include "ipapwd.h" -#include "util.h" -#include "ipa_krb5.h" - -/* krbTicketFlags */ -#define KTF_DISALLOW_POSTDATED 0x00000001 -#define KTF_DISALLOW_FORWARDABLE 0x00000002 -#define KTF_DISALLOW_TGT_BASED 0x00000004 -#define KTF_DISALLOW_RENEWABLE 0x00000008 -#define KTF_DISALLOW_PROXIABLE 0x00000010 -#define KTF_DISALLOW_DUP_SKEY 0x00000020 -#define KTF_DISALLOW_ALL_TIX 0x00000040 -#define KTF_REQUIRES_PRE_AUTH 0x00000080 -#define KTF_REQUIRES_HW_AUTH 0x00000100 -#define KTF_REQUIRES_PWCHANGE 0x00000200 -#define KTF_DISALLOW_SVR 0x00001000 -#define KTF_PWCHANGE_SERVICE 0x00002000 - -/* ascii hex output of bytes in "in" - * out len is 32 (preallocated) - * in len is 16 */ -static const char hexchars[] = "0123456789ABCDEF"; -static void hexbuf(char *out, const uint8_t *in) -{ - int i; - - for (i = 0; i < 16; i++) { - out[i*2] = hexchars[in[i] >> 4]; - out[i*2+1] = hexchars[in[i] & 0x0f]; - } -} - -void ipapwd_keyset_free(struct ipapwd_keyset **pkset) -{ - struct ipapwd_keyset *kset = *pkset; - int i; - - if (!kset) return; - - for (i = 0; i < kset->num_keys; i++) { - free(kset->keys[i].key_data_contents[0]); - free(kset->keys[i].key_data_contents[1]); - } - free(kset->keys); - free(kset); - *pkset = NULL; -} - -static Slapi_Value **encrypt_encode_key(struct ipapwd_krbcfg *krbcfg, - struct ipapwd_data *data, - char **errMesg) -{ - krb5_context krbctx; - char *krbPrincipalName = NULL; - int kvno; - struct berval *bval = NULL; - Slapi_Value **svals = NULL; - krb5_principal princ = NULL; - krb5_error_code krberr; - krb5_data pwd; - struct ipapwd_keyset *kset = NULL; - - krbctx = krbcfg->krbctx; - - svals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *)); - if (!svals) { - LOG_OOM(); - return NULL; - } - - kvno = ipapwd_get_cur_kvno(data->target); - - krbPrincipalName = slapi_entry_attr_get_charptr(data->target, - "krbPrincipalName"); - if (!krbPrincipalName) { - *errMesg = "no krbPrincipalName present in this entry\n"; - LOG_FATAL("%s", *errMesg); - goto enc_error; - } - - krberr = krb5_parse_name(krbctx, krbPrincipalName, &princ); - if (krberr) { - LOG_FATAL("krb5_parse_name failed [%s]\n", - krb5_get_error_message(krbctx, krberr)); - goto enc_error; - } - - pwd.data = (char *)data->password; - pwd.length = strlen(data->password); - - kset = malloc(sizeof(struct ipapwd_keyset)); - if (!kset) { - LOG_OOM(); - goto enc_error; - } - - /* this encoding assumes all keys have the same kvno */ - /* major-vno = 1 and minor-vno = 1 */ - kset->major_vno = 1; - kset->minor_vno = 1; - /* increment kvno (will be 1 if this is a new entry) */ - kvno += 1; - kset->mkvno = krbcfg->mkvno; - - krberr = ipa_krb5_generate_key_data(krbctx, princ, - pwd, kvno, krbcfg->kmkey, - krbcfg->num_pref_encsalts, - krbcfg->pref_encsalts, - &kset->num_keys, &kset->keys); - if (krberr != 0) { - LOG_FATAL("generating kerberos keys failed [%s]\n", - krb5_get_error_message(krbctx, krberr)); - goto enc_error; - } - - krberr = ber_encode_krb5_key_data(kset->keys, kset->num_keys, - kset->mkvno, &bval); - if (krberr != 0) { - LOG_FATAL("encoding krb5_key_data failed\n"); - goto enc_error; - } - - svals[0] = slapi_value_new_berval(bval); - if (!svals[0]) { - LOG_FATAL("Converting berval to Slapi_Value\n"); - goto enc_error; - } - - ipapwd_keyset_free(&kset); - krb5_free_principal(krbctx, princ); - slapi_ch_free_string(&krbPrincipalName); - ber_bvfree(bval); - return svals; - -enc_error: - *errMesg = "key encryption/encoding failed\n"; - if (kset) ipapwd_keyset_free(&kset); - krb5_free_principal(krbctx, princ); - slapi_ch_free_string(&krbPrincipalName); - if (bval) ber_bvfree(bval); - free(svals); - return NULL; -} - -int ipapwd_gen_hashes(struct ipapwd_krbcfg *krbcfg, - struct ipapwd_data *data, char *userpw, - int is_krb, int is_smb, int is_ipant, Slapi_Value ***svals, - char **nthash, char **lmhash, Slapi_Value ***ntvals, - char **errMesg) -{ - int rc; - char *userpw_uc = NULL; - - *svals = NULL; - *nthash = NULL; - *lmhash = NULL; - *errMesg = NULL; - - if (is_krb) { - - *svals = encrypt_encode_key(krbcfg, data, errMesg); - - if (!*svals) { - /* errMesg should have been set in encrypt_encode_key() */ - LOG_FATAL("key encryption/encoding failed\n"); - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - } - - if (is_smb || is_ipant) { - char lm[33], nt[33]; - struct ntlm_keys ntlm; - int ret; - - userpw_uc = (char *) slapi_utf8StrToUpper((unsigned char *) userpw); - if (!userpw_uc) { - *errMesg = "Failed to generate upper case password\n"; - LOG_FATAL("%s", *errMesg); - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - - ret = encode_ntlm_keys(userpw, - userpw_uc, - krbcfg->allow_lm_hash, - krbcfg->allow_nt_hash, - &ntlm); - memset(userpw_uc, 0, strlen(userpw_uc)); - slapi_ch_free_string(&userpw_uc); - if (ret) { - *errMesg = "Failed to generate NT/LM hashes\n"; - LOG_FATAL("%s", *errMesg); - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - if (krbcfg->allow_lm_hash) { - hexbuf(lm, ntlm.lm); - lm[32] = '\0'; - *lmhash = slapi_ch_strdup(lm); - } - if (krbcfg->allow_nt_hash) { - hexbuf(nt, ntlm.nt); - nt[32] = '\0'; - *nthash = slapi_ch_strdup(nt); - } - - if (is_ipant) { - *ntvals = (Slapi_Value **)calloc(2, sizeof(Slapi_Value *)); - if (!*ntvals) { - LOG_OOM(); - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - (*ntvals)[0] = slapi_value_new(); - if (slapi_value_set((*ntvals)[0], ntlm.nt, 16) == NULL) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - } - } - - rc = LDAP_SUCCESS; - -done: - - /* when error, free possibly allocated output parameters */ - if (rc) { - ipapwd_free_slapi_value_array(svals); - ipapwd_free_slapi_value_array(ntvals); - } - - return rc; -} - diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_prepost.c deleted file mode 100644 index 0318cecdc..000000000 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_prepost.c +++ /dev/null @@ -1,1349 +0,0 @@ -/** BEGIN COPYRIGHT BLOCK - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Additional permission under GPLv3 section 7: - * - * In the following paragraph, "GPL" means the GNU General Public - * License, version 3 or any later version, and "Non-GPL Code" means - * code that is governed neither by the GPL nor a license - * compatible with the GPL. - * - * You may link the code of this Program with Non-GPL Code and convey - * linked combinations including the two, provided that such Non-GPL - * Code only links to the code of this Program through those well - * defined interfaces identified in the file named EXCEPTION found in - * the source code files (the "Approved Interfaces"). The files of - * Non-GPL Code may instantiate templates or use macros or inline - * functions from the Approved Interfaces without causing the resulting - * work to be covered by the GPL. Only the copyright holders of this - * Program may make changes or additions to the list of Approved - * Interfaces. - * - * Authors: - * Simo Sorce - * - * Copyright (C) 2007-2010 Red Hat, Inc. - * All rights reserved. - * END COPYRIGHT BLOCK **/ - -#ifdef HAVE_CONFIG_H -# include -#endif - -/* strptime needs _XOPEN_SOURCE and endian.h needs __USE_BSD - * _GNU_SOURCE imply both, and we use it elsewhere, so use this */ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE 1 -#endif - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "ipapwd.h" -#include "util.h" - -#define IPAPWD_OP_NULL 0 -#define IPAPWD_OP_ADD 1 -#define IPAPWD_OP_MOD 2 - -extern Slapi_PluginDesc ipapwd_plugin_desc; -extern void *ipapwd_plugin_id; -extern const char *ipa_realm_tree; - -/* structure with information for each extension */ -struct ipapwd_op_ext { - char *object_name; /* name of the object extended */ - int object_type; /* handle to the extended object */ - int handle; /* extension handle */ -}; -/***************************************************************************** - * pre/post operations to intercept writes to userPassword - ****************************************************************************/ -static struct ipapwd_op_ext ipapwd_op_ext_list; - -static void *ipapwd_op_ext_constructor(void *object, void *parent) -{ - struct ipapwd_operation *ext; - - ext = (struct ipapwd_operation *)slapi_ch_calloc(1, sizeof(struct ipapwd_operation)); - return ext; -} - -static void ipapwd_op_ext_destructor(void *ext, void *object, void *parent) -{ - struct ipapwd_operation *pwdop = (struct ipapwd_operation *)ext; - if (!pwdop) - return; - if (pwdop->pwd_op != IPAPWD_OP_NULL) { - slapi_ch_free_string(&(pwdop->pwdata.dn)); - slapi_ch_free_string(&(pwdop->pwdata.password)); - } - slapi_ch_free((void **)&pwdop); -} - -int ipapwd_ext_init(void) -{ - int ret; - - ipapwd_op_ext_list.object_name = SLAPI_EXT_OPERATION; - - ret = slapi_register_object_extension(IPAPWD_PLUGIN_NAME, - SLAPI_EXT_OPERATION, - ipapwd_op_ext_constructor, - ipapwd_op_ext_destructor, - &ipapwd_op_ext_list.object_type, - &ipapwd_op_ext_list.handle); - - return ret; -} - - -static char *ipapwd_getIpaConfigAttr(const char *attr) -{ - /* check if migrtion is enabled */ - Slapi_Entry *entry = NULL; - const char *attrs_list[] = {attr, 0}; - char *value = NULL; - char *dn = NULL; - int ret; - - dn = slapi_ch_smprintf("cn=ipaconfig,cn=etc,%s", ipa_realm_tree); - if (!dn) { - LOG_OOM(); - goto done; - } - - ret = ipapwd_getEntry(dn, &entry, (char **) attrs_list); - if (ret) { - LOG("failed to retrieve config entry: %s\n", dn); - goto done; - } - - value = slapi_entry_attr_get_charptr(entry, attr); - -done: - slapi_entry_free(entry); - slapi_ch_free_string(&dn); - return value; -} - - -/* PRE ADD Operation: - * Gets the clean text password (fail the operation if the password came - * pre-hashed, unless this is a replicated operation or migration mode is - * enabled). - * Check user is authorized to add it otherwise just returns, operation will - * fail later anyway. - * Run a password policy check. - * Check if krb or smb hashes are required by testing if the krb or smb - * objectclasses are present. - * store information for the post operation - */ -static int ipapwd_pre_add(Slapi_PBlock *pb) -{ - struct ipapwd_krbcfg *krbcfg = NULL; - char *errMesg = "Internal operations error\n"; - struct slapi_entry *e = NULL; - char *userpw = NULL; - char *dn = NULL; - struct ipapwd_operation *pwdop = NULL; - void *op; - int is_repl_op, is_root, is_krb, is_smb, is_ipant; - int ret; - int rc = LDAP_SUCCESS; - - LOG_TRACE("=>\n"); - - ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op); - if (ret != 0) { - LOG_FATAL("slapi_pblock_get failed!?\n"); - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - - /* pass through if this is a replicated operation */ - if (is_repl_op) - return 0; - - /* retrieve the entry */ - slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e); - if (NULL == e) - return 0; - - /* check this is something interesting for us first */ - userpw = slapi_entry_attr_get_charptr(e, SLAPI_USERPWD_ATTR); - if (!userpw) { - /* nothing interesting here */ - return 0; - } - - /* Ok this is interesting, - * Check this is a clear text password, or refuse operation */ - if ('{' == userpw[0]) { - if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) { - char *tmp = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]); - if (NULL == tmp) { - LOG_OOM(); - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - slapi_ch_free_string(&userpw); - userpw = tmp; - } else if (slapi_is_encoded(userpw)) { - const char *userpw_clear = NULL; - Slapi_Value **pwvals = NULL; - - /* Try to get clear password from an entry extension. - * This function does not return a copy of the values, - * no need to free them. */ - rc = slapi_pw_get_entry_ext(e, &pwvals); - if (LDAP_SUCCESS == rc) { - userpw_clear = slapi_value_get_string(pwvals[0]); - } - - /* Fail if we did not get a real clear text password from - * the extension. This will happen if the password is hashed. */ - if (!userpw_clear || (0 == strcmp(userpw, userpw_clear))) { - rc = LDAP_CONSTRAINT_VIOLATION; - slapi_ch_free_string(&userpw); - } else { - userpw = slapi_ch_strdup(userpw_clear); - } - - if (rc != LDAP_SUCCESS) { - /* we don't have access to the clear text password; - * let it slide if migration is enabled, but don't - * generate kerberos keys */ - char *enabled = ipapwd_getIpaConfigAttr("ipamigrationenabled"); - if (NULL == enabled) { - LOG("no ipaMigrationEnabled in config, assuming FALSE\n"); - } else if (0 == strcmp(enabled, "TRUE")) { - return 0; - } - - LOG("pre-hashed passwords are not valid\n"); - errMesg = "pre-hashed passwords are not valid\n"; - goto done; - } - } - } - - rc = ipapwd_entry_checks(pb, e, - &is_root, &is_krb, &is_smb, &is_ipant, - NULL, SLAPI_ACL_ADD); - if (rc != LDAP_SUCCESS) { - goto done; - } - - rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN); - if (rc != LDAP_SUCCESS) { - goto done; - } - - /* Get target DN */ - ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); - if (ret) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - - /* time to get the operation handler */ - ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op); - if (ret != 0) { - LOG_FATAL("slapi_pblock_get failed!?\n"); - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - - pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type, - op, ipapwd_op_ext_list.handle); - if (NULL == pwdop) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - - pwdop->pwd_op = IPAPWD_OP_ADD; - pwdop->pwdata.password = slapi_ch_strdup(userpw); - - if (is_root) { - pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR; - } else { - char *binddn; - int i; - - pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN; - - /* Check Bind DN */ - slapi_pblock_get(pb, SLAPI_CONN_DN, &binddn); - - /* if it is a passsync manager we also need to skip resets */ - for (i = 0; i < krbcfg->num_passsync_mgrs; i++) { - if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) { - pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR; - break; - } - } - } - - pwdop->pwdata.dn = slapi_ch_strdup(dn); - pwdop->pwdata.timeNow = time(NULL); - pwdop->pwdata.target = e; - - ret = ipapwd_CheckPolicy(&pwdop->pwdata); - if (ret) { - errMesg = ipapwd_error2string(ret); - rc = LDAP_CONSTRAINT_VIOLATION; - goto done; - } - - if (is_krb || is_smb || is_ipant) { - - Slapi_Value **svals = NULL; - Slapi_Value **ntvals = NULL; - char *nt = NULL; - char *lm = NULL; - - pwdop->is_krb = is_krb; - - rc = ipapwd_gen_hashes(krbcfg, &pwdop->pwdata, - userpw, is_krb, is_smb, is_ipant, - &svals, &nt, &lm, &ntvals, &errMesg); - if (rc != LDAP_SUCCESS) { - goto done; - } - - if (svals) { - /* add/replace values in existing entry */ - ret = slapi_entry_attr_replace_sv(e, "krbPrincipalKey", svals); - if (ret) { - LOG_FATAL("failed to set encoded values in entry\n"); - rc = LDAP_OPERATIONS_ERROR; - ipapwd_free_slapi_value_array(&svals); - goto done; - } - - ipapwd_free_slapi_value_array(&svals); - } - - if (lm && is_smb) { - /* set value */ - slapi_entry_attr_set_charptr(e, "sambaLMPassword", lm); - slapi_ch_free_string(&lm); - } - if (nt && is_smb) { - /* set value */ - slapi_entry_attr_set_charptr(e, "sambaNTPassword", nt); - slapi_ch_free_string(&nt); - } - - if (ntvals && is_ipant) { - slapi_entry_attr_replace_sv(e, "ipaNTHash", ntvals); - ipapwd_free_slapi_value_array(&ntvals); - } - - if (is_smb) { - /* with samba integration we need to also set sambaPwdLastSet or - * samba will decide the user has to change the password again */ - if (pwdop->pwdata.changetype == IPA_CHANGETYPE_ADMIN) { - /* if it is an admin change instead we need to let know to - * samba as well that the use rmust change its password */ - slapi_entry_attr_set_long(e, "sambaPwdLastset", 0L); - } else { - slapi_entry_attr_set_long(e, "sambaPwdLastset", - (long)pwdop->pwdata.timeNow); - } - } - } - - rc = LDAP_SUCCESS; - -done: - if (pwdop) pwdop->pwdata.target = NULL; - free_ipapwd_krbcfg(&krbcfg); - slapi_ch_free_string(&userpw); - if (rc != LDAP_SUCCESS) { - slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); - return -1; - } - return 0; -} - -#define NTHASH_REGEN_VAL "MagicRegen" -#define NTHASH_REGEN_LEN sizeof(NTHASH_REGEN_VAL) -static int ipapwd_regen_nthash(Slapi_PBlock *pb, Slapi_Mods *smods, - char *dn, struct slapi_entry *entry, - struct ipapwd_krbcfg *krbcfg); - -/* PRE MOD Operation: - * Gets the clean text password (fail the operation if the password came - * pre-hashed, unless this is a replicated operation). - * Check user is authorized to add it otherwise just returns, operation will - * fail later anyway. - * Check if krb or smb hashes are required by testing if the krb or smb - * objectclasses are present. - * Run a password policy check. - * store information for the post operation - */ -static int ipapwd_pre_mod(Slapi_PBlock *pb) -{ - struct ipapwd_krbcfg *krbcfg = NULL; - char *errMesg = NULL; - LDAPMod **mods; - LDAPMod *lmod; - Slapi_Mods *smods = NULL; - char *userpw = NULL; - char *unhashedpw = NULL; - char *dn = NULL; - Slapi_DN *tmp_dn; - struct slapi_entry *e = NULL; - struct ipapwd_operation *pwdop = NULL; - void *op; - int is_repl_op, is_pwd_op, is_root, is_krb, is_smb, is_ipant; - int has_krb_keys = 0; - int has_history = 0; - int gen_krb_keys = 0; - int is_magic_regen = 0; - int ret, rc; - - LOG_TRACE( "=>\n"); - - ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op); - if (ret != 0) { - LOG_FATAL("slapi_pblock_get failed!?\n"); - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - - /* pass through if this is a replicated operation */ - if (is_repl_op) { - rc = LDAP_SUCCESS; - goto done; - } - - /* grab the mods - we'll put them back later with - * our modifications appended - */ - slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); - smods = slapi_mods_new(); - slapi_mods_init_passin(smods, mods); - - /* In the first pass, - * only check there is anything we are interested in */ - is_pwd_op = 0; - lmod = slapi_mods_get_first_mod(smods); - while (lmod) { - struct berval *bv; - - if (slapi_attr_types_equivalent(lmod->mod_type, SLAPI_USERPWD_ATTR)) { - /* check op filtering out LDAP_MOD_BVALUES */ - switch (lmod->mod_op & 0x0f) { - case LDAP_MOD_ADD: - case LDAP_MOD_REPLACE: - is_pwd_op = 1; - default: - break; - } - } else if (slapi_attr_types_equivalent(lmod->mod_type, "ipaNTHash")) { - /* check op filtering out LDAP_MOD_BVALUES */ - switch (lmod->mod_op & 0x0f) { - case LDAP_MOD_ADD: - if (!lmod->mod_bvalues || - !lmod->mod_bvalues[0]) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - bv = lmod->mod_bvalues[0]; - if ((bv->bv_len >= NTHASH_REGEN_LEN -1) && - (bv->bv_len <= NTHASH_REGEN_LEN) && - (strncmp(NTHASH_REGEN_VAL, - bv->bv_val, bv->bv_len) == 0)) { - is_magic_regen = 1; - /* make sure the database will later ignore this mod */ - slapi_mods_remove(smods); - } - default: - break; - } - } else if (slapi_attr_types_equivalent(lmod->mod_type, - "unhashed#user#password")) { - /* we check for unahsehd password here so that we are sure to - * catch them early, before further checks go on, this helps - * checking LDAP_MOD_DELETE operations in some corner cases later. - * We keep only the last one if multiple are provided for any - * reason */ - if (!lmod->mod_bvalues || - !lmod->mod_bvalues[0]) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - bv = lmod->mod_bvalues[0]; - slapi_ch_free_string(&unhashedpw); - unhashedpw = slapi_ch_malloc(bv->bv_len+1); - if (!unhashedpw) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - memcpy(unhashedpw, bv->bv_val, bv->bv_len); - unhashedpw[bv->bv_len] = '\0'; - } - lmod = slapi_mods_get_next_mod(smods); - } - - /* If userPassword is not modified check if this is a request to generate - * NT hashes otherwise we are done here */ - if (!is_pwd_op && !is_magic_regen) { - rc = LDAP_SUCCESS; - goto done; - } - - /* OK we have something interesting here, start checking for - * pre-requisites */ - - /* Get target DN */ - ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); - if (ret) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - - tmp_dn = slapi_sdn_new_dn_byref(dn); - if (tmp_dn) { - /* xxxPAR: Ideally SLAPI_MODIFY_EXISTING_ENTRY should be - * available but it turns out that is only true if you are - * a dbm backend pre-op plugin - lucky dbm backend pre-op - * plugins. - * I think that is wrong since the entry is useful for filter - * tests and schema checks and this plugin shouldn't be limited - * to a single backend type, but I don't want that fight right - * now so we go get the entry here - * - slapi_pblock_get( pb, SLAPI_MODIFY_EXISTING_ENTRY, &e); - */ - ret = slapi_search_internal_get_entry(tmp_dn, 0, &e, ipapwd_plugin_id); - slapi_sdn_free(&tmp_dn); - if (ret != LDAP_SUCCESS) { - LOG("Failed to retrieve entry?!\n"); - rc = LDAP_NO_SUCH_OBJECT; - goto done; - } - } - - rc = ipapwd_entry_checks(pb, e, - &is_root, &is_krb, &is_smb, &is_ipant, - SLAPI_USERPWD_ATTR, SLAPI_ACL_WRITE); - if (rc) { - goto done; - } - - rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN); - if (rc) { - goto done; - } - - if (!is_pwd_op) { - /* This may be a magic op to ask us to generate the NT hashes */ - if (is_magic_regen) { - /* Make sense to call only if this entry has krb keys to source - * the nthash from */ - if (is_krb) { - rc = ipapwd_regen_nthash(pb, smods, dn, e, krbcfg); - } else { - rc = LDAP_UNWILLING_TO_PERFORM; - } - } else { - rc = LDAP_OPERATIONS_ERROR; - } - goto done; - } - - /* run through the mods again and adjust flags if operations affect them */ - lmod = slapi_mods_get_first_mod(smods); - while (lmod) { - struct berval *bv; - - if (slapi_attr_types_equivalent(lmod->mod_type, SLAPI_USERPWD_ATTR)) { - /* check op filtering out LDAP_MOD_BVALUES */ - switch (lmod->mod_op & 0x0f) { - case LDAP_MOD_ADD: - /* FIXME: should we try to track cases where we would end up - * with multiple userPassword entries ?? */ - case LDAP_MOD_REPLACE: - is_pwd_op = 1; - if (!lmod->mod_bvalues || - !lmod->mod_bvalues[0]) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - bv = lmod->mod_bvalues[0]; - slapi_ch_free_string(&userpw); - userpw = slapi_ch_malloc(bv->bv_len+1); - if (!userpw) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - memcpy(userpw, bv->bv_val, bv->bv_len); - userpw[bv->bv_len] = '\0'; - break; - case LDAP_MOD_DELETE: - /* reset only if we are deleting all values, or the exact - * same value previously set, otherwise we are just trying to - * add a new value and delete an existing one */ - if (!lmod->mod_bvalues || - !lmod->mod_bvalues[0]) { - is_pwd_op = 0; - } else { - bv = lmod->mod_bvalues[0]; - if ((userpw && - strncmp(userpw, bv->bv_val, bv->bv_len) == 0) || - (unhashedpw && - strncmp(unhashedpw, bv->bv_val, bv->bv_len) == 0)) { - is_pwd_op = 0; - } - } - default: - break; - } - - } else if (slapi_attr_types_equivalent(lmod->mod_type, - SLAPI_ATTR_OBJECTCLASS)) { - int i; - /* check op filtering out LDAP_MOD_BVALUES */ - switch (lmod->mod_op & 0x0f) { - case LDAP_MOD_REPLACE: - /* if objectclasses are replaced we need to start clean with - * flags, so we sero them out and see if they get set again */ - is_krb = 0; - is_smb = 0; - is_ipant = 0; - - case LDAP_MOD_ADD: - if (!lmod->mod_bvalues || - !lmod->mod_bvalues[0]) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - for (i = 0; (bv = lmod->mod_bvalues[i]) != NULL; i++) { - if (strncasecmp("krbPrincipalAux", - bv->bv_val, bv->bv_len) == 0) { - is_krb = 1; - } else if (strncasecmp("sambaSamAccount", - bv->bv_val, bv->bv_len) == 0) { - is_smb = 1; - } else if (strncasecmp("ipaNTUserAttrs", - bv->bv_val, bv->bv_len) == 0) { - is_ipant = 1; - } - } - - break; - - case LDAP_MOD_DELETE: - /* can this happen for objectclasses ? */ - is_krb = 0; - is_smb = 0; - is_ipant = 0; - - default: - break; - } - - } else if (slapi_attr_types_equivalent(lmod->mod_type, - "krbPrincipalKey")) { - - /* if we are getting a krbPrincipalKey, also avoid regenerating - * the keys, it means kadmin has alredy done the job and is simply - * keeping userPassword and sambaXXPAssword in sync */ - - /* we also check we have enough authority */ - if (is_root) { - has_krb_keys = 1; - } - - } else if (slapi_attr_types_equivalent(lmod->mod_type, - "passwordHistory")) { - - /* if we are getting a passwordHistory, also avoid regenerating - * the hashes, it means kadmin has alredy done the job and is - * simply keeping userPassword and sambaXXPAssword in sync */ - - /* we also check we have enough authority */ - if (is_root) { - has_history = 1; - } - } - - lmod = slapi_mods_get_next_mod(smods); - } - - if (is_krb) { - if (has_krb_keys) { - gen_krb_keys = 0; - } else { - gen_krb_keys = 1; - } - } - - /* It seem like we have determined that the end result will be deletion of - * the userPassword attribute, so we have no more business here */ - if (! is_pwd_op) { - rc = LDAP_SUCCESS; - goto done; - } - - /* Check this is a clear text password, or refuse operation (only if we need - * to comput other hashes */ - if (! unhashedpw && (gen_krb_keys || is_smb || is_ipant)) { - if ('{' == userpw[0]) { - if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) { - unhashedpw = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]); - if (NULL == unhashedpw) { - LOG_OOM(); - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - slapi_ch_free_string(&userpw); - - } else if (slapi_is_encoded(userpw)) { - - LOG("Pre-Encoded passwords are not valid\n"); - errMesg = "Pre-Encoded passwords are not valid\n"; - rc = LDAP_CONSTRAINT_VIOLATION; - goto done; - } - } - } - - /* time to get the operation handler */ - ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op); - if (ret != 0) { - LOG_FATAL("slapi_pblock_get failed!?\n"); - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - - pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type, - op, ipapwd_op_ext_list.handle); - if (NULL == pwdop) { - rc = LDAP_OPERATIONS_ERROR; - goto done; - } - - pwdop->is_krb = is_krb; - pwdop->pwd_op = IPAPWD_OP_MOD; - pwdop->pwdata.password = slapi_ch_strdup(unhashedpw); - pwdop->pwdata.changetype = IPA_CHANGETYPE_NORMAL; - pwdop->skip_history = has_history; - pwdop->skip_keys = has_krb_keys; - - if (is_root) { - pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR; - } else { - char *binddn; - Slapi_DN *bdn, *tdn; - int i; - - /* Check Bind DN */ - slapi_pblock_get(pb, SLAPI_CONN_DN, &binddn); - bdn = slapi_sdn_new_dn_byref(binddn); - tdn = slapi_sdn_new_dn_byref(dn); - - /* if the change is performed by someone else, - * it is an admin change that will require a new - * password change immediately as per our IPA policy */ - if (slapi_sdn_compare(bdn, tdn)) { - pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN; - - /* if it is a passsync manager we also need to skip resets */ - for (i = 0; i < krbcfg->num_passsync_mgrs; i++) { - if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) { - pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR; - break; - } - } - - } - - slapi_sdn_free(&bdn); - slapi_sdn_free(&tdn); - - } - - pwdop->pwdata.dn = slapi_ch_strdup(dn); - pwdop->pwdata.timeNow = time(NULL); - pwdop->pwdata.target = e; - - /* if krb keys are being set by an external agent we assume password - * policies have been properly checked already, so we check them only - * if no krb keys are available */ - if (has_krb_keys == 0) { - ret = ipapwd_CheckPolicy(&pwdop->pwdata); - if (ret) { - errMesg = ipapwd_error2string(ret); - rc = LDAP_CONSTRAINT_VIOLATION; - goto done; - } - } - - if (gen_krb_keys || is_smb || is_ipant) { - - Slapi_Value **svals = NULL; - Slapi_Value **ntvals = NULL; - char *nt = NULL; - char *lm = NULL; - - rc = ipapwd_gen_hashes(krbcfg, &pwdop->pwdata, unhashedpw, - gen_krb_keys, is_smb, is_ipant, - &svals, &nt, &lm, &ntvals, &errMesg); - if (rc) { - goto done; - } - - if (svals) { - /* replace values */ - slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, - "krbPrincipalKey", svals); - ipapwd_free_slapi_value_array(&svals); - } - - if (lm && is_smb) { - /* replace value */ - slapi_mods_add_string(smods, LDAP_MOD_REPLACE, - "sambaLMPassword", lm); - slapi_ch_free_string(&lm); - } - if (nt && is_smb) { - /* replace value */ - slapi_mods_add_string(smods, LDAP_MOD_REPLACE, - "sambaNTPassword", nt); - slapi_ch_free_string(&nt); - } - - if (ntvals && is_ipant) { - slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, - "ipaNTHash", ntvals); - ipapwd_free_slapi_value_array(&ntvals); - } - - if (is_smb) { - /* with samba integration we need to also set sambaPwdLastSet or - * samba will decide the user has to change the password again */ - if (pwdop->pwdata.changetype == IPA_CHANGETYPE_ADMIN) { - /* if it is an admin change instead we need to let know to - * samba as well that the use rmust change its password */ - slapi_entry_attr_set_long(e, "sambaPwdLastset", 0L); - } else { - slapi_entry_attr_set_long(e, "sambaPwdLastset", - (long)pwdop->pwdata.timeNow); - } - } - } - - rc = LDAP_SUCCESS; - -done: - free_ipapwd_krbcfg(&krbcfg); - slapi_ch_free_string(&userpw); /* just to be sure */ - slapi_ch_free_string(&unhashedpw); /* we copied it to pwdop */ - if (e) slapi_entry_free(e); /* this is a copy in this function */ - if (pwdop) pwdop->pwdata.target = NULL; - - /* put back a, possibly modified, set of mods */ - if (smods) { - mods = slapi_mods_get_ldapmods_passout(smods); - if (slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods)) { - LOG_FATAL("slapi_pblock_set failed!\n"); - rc = LDAP_OPERATIONS_ERROR; - } - slapi_mods_free(&smods); - } - - if (rc != LDAP_SUCCESS) { - slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); - return -1; - } - - return 0; -} - -static int ipapwd_regen_nthash(Slapi_PBlock *pb, Slapi_Mods *smods, - char *dn, struct slapi_entry *entry, - struct ipapwd_krbcfg *krbcfg) -{ - Slapi_Attr *attr; - Slapi_Value *value; - const struct berval *val; - struct berval *ntvals[2] = { NULL, NULL }; - struct berval bval; - krb5_key_data *keys; - int num_keys; - int mkvno; - int ret; - int i; - - ret = slapi_entry_attr_find(entry, "ipaNTHash", &attr); - if (ret == 0) { - /* We refuse to regen if there is already a value */ - return LDAP_CONSTRAINT_VIOLATION; - } - - /* ok let's see if we can find the RC4 hash in the keys */ - ret = slapi_entry_attr_find(entry, "krbPrincipalKey", &attr); - if (ret) { - return LDAP_UNWILLING_TO_PERFORM; - } - - ret = slapi_attr_first_value(attr, &value); - if (ret) { - return LDAP_OPERATIONS_ERROR; - } - - val = slapi_value_get_berval(value); - if (!val) { - return LDAP_OPERATIONS_ERROR; - } - - ret = ber_decode_krb5_key_data((struct berval *)val, - &mkvno, &num_keys, &keys); - if (ret) { - return LDAP_OPERATIONS_ERROR; - } - - ret = LDAP_UNWILLING_TO_PERFORM; - - for (i = 0; i < num_keys; i++) { - char nthash[16]; - krb5_enc_data cipher; - krb5_data plain; - krb5_int16 t; - - if (keys[i].key_data_type[0] != ENCTYPE_ARCFOUR_HMAC) { - continue; - } - - memcpy(&t, keys[i].key_data_contents[0], 2); - plain.length = le16toh(t); - if (plain.length != 16) { - continue; - } - plain.data = nthash; - - memset(&cipher, 0, sizeof(krb5_enc_data)); - cipher.enctype = krbcfg->kmkey->enctype; - cipher.ciphertext.length = keys[i].key_data_length[0] - 2; - cipher.ciphertext.data = ((char *)keys[i].key_data_contents[0]) + 2; - - ret = krb5_c_decrypt(krbcfg->krbctx, krbcfg->kmkey, - 0, NULL, &cipher, &plain); - if (ret) { - ret = LDAP_OPERATIONS_ERROR; - break; - } - - bval.bv_val = nthash; - bval.bv_len = 16; - ntvals[0] = &bval; - - slapi_mods_add_modbvps(smods, LDAP_MOD_ADD, "ipaNTHash", ntvals); - - ret = LDAP_SUCCESS; - break; - } - - ipa_krb5_free_key_data(keys, num_keys); - - return ret; -} - -static int ipapwd_post_op(Slapi_PBlock *pb) -{ - void *op; - struct ipapwd_operation *pwdop = NULL; - Slapi_Mods *smods; - Slapi_Value **pwvals; - struct tm utctime; - char timestr[GENERALIZED_TIME_LENGTH+1]; - int ret; - char *errMsg = "Internal operations error\n"; - struct ipapwd_krbcfg *krbcfg = NULL; - char *principal = NULL; - Slapi_Value *ipahost; - - LOG_TRACE("=>\n"); - - /* time to get the operation handler */ - ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op); - if (ret != 0) { - LOG_FATAL("slapi_pblock_get failed!?\n"); - return 0; - } - - pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type, - op, ipapwd_op_ext_list.handle); - if (NULL == pwdop) { - LOG_FATAL("Internal error, couldn't find pluginextension ?!\n"); - return 0; - } - - /* not interesting */ - if (IPAPWD_OP_NULL == pwdop->pwd_op) - return 0; - - if ( ! (pwdop->is_krb)) { - LOG("Not a kerberos user, ignore krb attributes\n"); - return 0; - } - - if (pwdop->skip_keys && pwdop->skip_history) { - /* nothing to do, caller already set all interesting attributes */ - return 0; - } - - ret = ipapwd_gen_checks(pb, &errMsg, &krbcfg, 0); - if (ret != 0) { - LOG_FATAL("ipapwd_gen_checks failed!?\n"); - return 0; - } - - /* prepare changes that can be made only as root */ - smods = slapi_mods_new(); - - /* This was a mod operation on an existing entry, make sure we also update - * the password history based on the entry we saved from the pre-op */ - if (IPAPWD_OP_MOD == pwdop->pwd_op && !pwdop->skip_history) { - Slapi_DN *tmp_dn = slapi_sdn_new_dn_byref(pwdop->pwdata.dn); - if (tmp_dn) { - ret = slapi_search_internal_get_entry(tmp_dn, 0, - &pwdop->pwdata.target, - ipapwd_plugin_id); - slapi_sdn_free(&tmp_dn); - if (ret != LDAP_SUCCESS) { - LOG("Failed to retrieve entry?!\n"); - goto done; - } - } - pwvals = ipapwd_setPasswordHistory(smods, &pwdop->pwdata); - if (pwvals) { - slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, - "passwordHistory", pwvals); - } - } - - /* we assume that krb attributes are properly updated too if keys were - * passed in */ - if (!pwdop->skip_keys) { - /* Don't set a last password change or expiration on host passwords. - * krbLastPwdChange is used to tell whether we have a valid keytab. - * If we set it on userPassword it confuses enrollment. - * If krbPasswordExpiration is set on a host entry then the keytab - * will appear to be expired. - * - * When a host is issued a keytab these attributes get set properly by - * ipapwd_setkeytab(). - */ - ipahost = slapi_value_new_string("ipaHost"); - if (!pwdop->pwdata.target || - (slapi_entry_attr_has_syntax_value(pwdop->pwdata.target, - SLAPI_ATTR_OBJECTCLASS, ipahost)) == 0) { - /* set Password Expiration date */ - if (!gmtime_r(&(pwdop->pwdata.expireTime), &utctime)) { - LOG_FATAL("failed to parse expiration date (buggy gmtime_r ?)\n"); - goto done; - } - strftime(timestr, GENERALIZED_TIME_LENGTH+1, - "%Y%m%d%H%M%SZ", &utctime); - slapi_mods_add_string(smods, LDAP_MOD_REPLACE, - "krbPasswordExpiration", timestr); - - /* change Last Password Change field with the current date */ - if (!gmtime_r(&(pwdop->pwdata.timeNow), &utctime)) { - LOG_FATAL("failed to parse current date (buggy gmtime_r ?)\n"); - slapi_value_free(&ipahost); - goto done; - } - strftime(timestr, GENERALIZED_TIME_LENGTH+1, - "%Y%m%d%H%M%SZ", &utctime); - slapi_mods_add_string(smods, LDAP_MOD_REPLACE, - "krbLastPwdChange", timestr); - } - slapi_value_free(&ipahost); - } - - ret = ipapwd_apply_mods(pwdop->pwdata.dn, smods); - if (ret) - LOG("Failed to set additional password attributes in the post-op!\n"); - - if (!pwdop->skip_keys) { - if (pwdop->pwdata.changetype == IPA_CHANGETYPE_NORMAL) { - principal = slapi_entry_attr_get_charptr(pwdop->pwdata.target, - "krbPrincipalName"); - } else { - principal = slapi_ch_smprintf("root/admin@%s", krbcfg->realm); - } - ipapwd_set_extradata(pwdop->pwdata.dn, principal, pwdop->pwdata.timeNow); - } - -done: - if (pwdop && pwdop->pwdata.target) slapi_entry_free(pwdop->pwdata.target); - slapi_mods_free(&smods); - slapi_ch_free_string(&principal); - free_ipapwd_krbcfg(&krbcfg); - return 0; -} - -/* PRE BIND Operation: - * Used for password migration from DS to IPA. - * Gets the clean text password, authenticates the user and generates - * a kerberos key if missing. - * Person to blame if anything blows up: Pavel Zuna - */ -static int ipapwd_pre_bind(Slapi_PBlock *pb) -{ - struct ipapwd_krbcfg *krbcfg = NULL; - struct ipapwd_data pwdata; - struct berval *credentials; /* bind credentials */ - Slapi_Entry *entry = NULL; - Slapi_Value **pwd_values = NULL; /* values of userPassword attribute */ - Slapi_Value *value = NULL; - Slapi_Attr *attr = NULL; - struct tm expire_tm; - char *errMesg = "Internal operations error\n"; /* error message */ - char *expire = NULL; /* passwordExpirationTime attribute value */ - char *dn = NULL; /* bind DN */ - Slapi_Value *objectclass; - int method; /* authentication method */ - int ret = 0; - char *principal = NULL; - - LOG_TRACE("=>\n"); - - /* get BIND parameters */ - ret |= slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn); - ret |= slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method); - ret |= slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &credentials); - if (ret) { - LOG_FATAL("slapi_pblock_get failed!?\n"); - goto done; - } - - /* we're only interested in simple authentication */ - if (method != LDAP_AUTH_SIMPLE) - goto done; - - /* list of attributes to retrieve */ - const char *attrs_list[] = {SLAPI_USERPWD_ATTR, "krbprincipalkey", "uid", - "krbprincipalname", "objectclass", - "passwordexpirationtime", "passwordhistory", - NULL}; - - /* retrieve user entry */ - ret = ipapwd_getEntry(dn, &entry, (char **) attrs_list); - if (ret) { - LOG("failed to retrieve user entry: %s\n", dn); - goto done; - } - - /* check the krbPrincipalName attribute is present */ - ret = slapi_entry_attr_find(entry, "krbprincipalname", &attr); - if (ret) { - LOG("no krbPrincipalName in user entry: %s\n", dn); - goto done; - } - - /* we aren't interested in host principals */ - objectclass = slapi_value_new_string("ipaHost"); - if ((slapi_entry_attr_has_syntax_value(entry, SLAPI_ATTR_OBJECTCLASS, objectclass)) == 1) { - slapi_value_free(&objectclass); - goto done; - } - slapi_value_free(&objectclass); - - /* check the krbPrincipalKey attribute is NOT present */ - ret = slapi_entry_attr_find(entry, "krbprincipalkey", &attr); - if (!ret) { - LOG("kerberos key already present in user entry: %s\n", dn); - goto done; - } - - /* retrieve userPassword attribute */ - ret = slapi_entry_attr_find(entry, SLAPI_USERPWD_ATTR, &attr); - if (ret) { - LOG("no " SLAPI_USERPWD_ATTR " in user entry: %s\n", dn); - goto done; - } - - /* get the number of userPassword values and allocate enough memory */ - slapi_attr_get_numvalues(attr, &ret); - ret = (ret + 1) * sizeof (Slapi_Value *); - pwd_values = (Slapi_Value **) slapi_ch_malloc(ret); - if (!pwd_values) { - /* probably not required: should terminate the server anyway */ - LOG_OOM(); - goto done; - } - /* zero-fill the allocated memory; we need the array ending with NULL */ - memset(pwd_values, 0, ret); - - /* retrieve userPassword values */ - ret = slapi_attr_first_value(attr, &value); - while (ret != -1) { - pwd_values[ret] = value; - ret = slapi_attr_next_value(attr, ret, &value); - } - - /* check if BIND password and userPassword match */ - value = slapi_value_new_berval(credentials); - ret = slapi_pw_find_sv(pwd_values, value); - - /* free before checking ret; we might not get a chance later */ - slapi_ch_free((void **) &pwd_values); - slapi_value_free(&value); - - if (ret) { - LOG("invalid BIND password for user entry: %s\n", dn); - goto done; - } - - /* general checks */ - ret = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN); - if (ret) { - LOG_FATAL("Generic checks failed: %s", errMesg); - goto done; - } - - /* delete userPassword - a new one will be generated later */ - /* this is needed, otherwise ipapwd_CheckPolicy will think - * we're changing the password to its previous value - * and force a password change on next login */ - ret = slapi_entry_attr_delete(entry, SLAPI_USERPWD_ATTR); - if (ret) { - LOG_FATAL("failed to delete " SLAPI_USERPWD_ATTR "\n"); - goto done; - } - - /* prepare data for kerberos key generation */ - memset(&pwdata, 0, sizeof (pwdata)); - pwdata.dn = dn; - pwdata.target = entry; - pwdata.password = credentials->bv_val; - pwdata.timeNow = time(NULL); - pwdata.changetype = IPA_CHANGETYPE_NORMAL; - - /* keep password expiration time from DS, if possible */ - expire = slapi_entry_attr_get_charptr(entry, "passwordexpirationtime"); - if (expire) { - memset(&expire_tm, 0, sizeof (expire_tm)); - if (strptime(expire, "%Y%m%d%H%M%SZ", &expire_tm)) - pwdata.expireTime = mktime(&expire_tm); - } - - /* check password policy */ - ret = ipapwd_CheckPolicy(&pwdata); - if (ret) { - /* Password fails to meet IPA password policy, - * force user to change his password next time he logs in. */ - LOG("password policy check failed on user entry: %s" - " (force password change on next login)\n", dn); - pwdata.expireTime = time(NULL); - } - - /* generate kerberos keys */ - ret = ipapwd_SetPassword(krbcfg, &pwdata, 1); - if (ret) { - LOG("failed to set kerberos key for user entry: %s\n", dn); - goto done; - } - - /* we need to make sure the ExtraData is set, otherwise kadmin - * will not like the object */ - principal = slapi_entry_attr_get_charptr(entry, "krbPrincipalName"); - if (!principal) { - LOG_OOM(); - goto done; - } - ipapwd_set_extradata(pwdata.dn, principal, pwdata.timeNow); - - LOG("kerberos key generated for user entry: %s\n", dn); - -done: - slapi_ch_free_string(&principal); - slapi_ch_free_string(&expire); - if (entry) - slapi_entry_free(entry); - free_ipapwd_krbcfg(&krbcfg); - - return 0; -} - - - -/* Init pre ops */ -int ipapwd_pre_init(Slapi_PBlock *pb) -{ - int ret; - - ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *)ipapwd_pre_bind); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *)ipapwd_pre_add); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *)ipapwd_pre_mod); - - return ret; -} - -int ipapwd_pre_init_betxn(Slapi_PBlock *pb) -{ - int ret; - - ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN, (void *)ipapwd_pre_add); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN, (void *)ipapwd_pre_mod); - - return ret; -} - -/* Init post ops */ -int ipapwd_post_init(Slapi_PBlock *pb) -{ - int ret; - - ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *)ipapwd_post_op); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_op); - - return ret; -} - -int ipapwd_post_init_betxn(Slapi_PBlock *pb) -{ - int ret; - - ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_ADD_FN, (void *)ipapwd_post_op); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN, (void *)ipapwd_post_op); - - return ret; -} diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c new file mode 100644 index 000000000..0318cecdc --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c @@ -0,0 +1,1349 @@ +/** BEGIN COPYRIGHT BLOCK + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional permission under GPLv3 section 7: + * + * In the following paragraph, "GPL" means the GNU General Public + * License, version 3 or any later version, and "Non-GPL Code" means + * code that is governed neither by the GPL nor a license + * compatible with the GPL. + * + * You may link the code of this Program with Non-GPL Code and convey + * linked combinations including the two, provided that such Non-GPL + * Code only links to the code of this Program through those well + * defined interfaces identified in the file named EXCEPTION found in + * the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline + * functions from the Approved Interfaces without causing the resulting + * work to be covered by the GPL. Only the copyright holders of this + * Program may make changes or additions to the list of Approved + * Interfaces. + * + * Authors: + * Simo Sorce + * + * Copyright (C) 2007-2010 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +/* strptime needs _XOPEN_SOURCE and endian.h needs __USE_BSD + * _GNU_SOURCE imply both, and we use it elsewhere, so use this */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ipapwd.h" +#include "util.h" + +#define IPAPWD_OP_NULL 0 +#define IPAPWD_OP_ADD 1 +#define IPAPWD_OP_MOD 2 + +extern Slapi_PluginDesc ipapwd_plugin_desc; +extern void *ipapwd_plugin_id; +extern const char *ipa_realm_tree; + +/* structure with information for each extension */ +struct ipapwd_op_ext { + char *object_name; /* name of the object extended */ + int object_type; /* handle to the extended object */ + int handle; /* extension handle */ +}; +/***************************************************************************** + * pre/post operations to intercept writes to userPassword + ****************************************************************************/ +static struct ipapwd_op_ext ipapwd_op_ext_list; + +static void *ipapwd_op_ext_constructor(void *object, void *parent) +{ + struct ipapwd_operation *ext; + + ext = (struct ipapwd_operation *)slapi_ch_calloc(1, sizeof(struct ipapwd_operation)); + return ext; +} + +static void ipapwd_op_ext_destructor(void *ext, void *object, void *parent) +{ + struct ipapwd_operation *pwdop = (struct ipapwd_operation *)ext; + if (!pwdop) + return; + if (pwdop->pwd_op != IPAPWD_OP_NULL) { + slapi_ch_free_string(&(pwdop->pwdata.dn)); + slapi_ch_free_string(&(pwdop->pwdata.password)); + } + slapi_ch_free((void **)&pwdop); +} + +int ipapwd_ext_init(void) +{ + int ret; + + ipapwd_op_ext_list.object_name = SLAPI_EXT_OPERATION; + + ret = slapi_register_object_extension(IPAPWD_PLUGIN_NAME, + SLAPI_EXT_OPERATION, + ipapwd_op_ext_constructor, + ipapwd_op_ext_destructor, + &ipapwd_op_ext_list.object_type, + &ipapwd_op_ext_list.handle); + + return ret; +} + + +static char *ipapwd_getIpaConfigAttr(const char *attr) +{ + /* check if migrtion is enabled */ + Slapi_Entry *entry = NULL; + const char *attrs_list[] = {attr, 0}; + char *value = NULL; + char *dn = NULL; + int ret; + + dn = slapi_ch_smprintf("cn=ipaconfig,cn=etc,%s", ipa_realm_tree); + if (!dn) { + LOG_OOM(); + goto done; + } + + ret = ipapwd_getEntry(dn, &entry, (char **) attrs_list); + if (ret) { + LOG("failed to retrieve config entry: %s\n", dn); + goto done; + } + + value = slapi_entry_attr_get_charptr(entry, attr); + +done: + slapi_entry_free(entry); + slapi_ch_free_string(&dn); + return value; +} + + +/* PRE ADD Operation: + * Gets the clean text password (fail the operation if the password came + * pre-hashed, unless this is a replicated operation or migration mode is + * enabled). + * Check user is authorized to add it otherwise just returns, operation will + * fail later anyway. + * Run a password policy check. + * Check if krb or smb hashes are required by testing if the krb or smb + * objectclasses are present. + * store information for the post operation + */ +static int ipapwd_pre_add(Slapi_PBlock *pb) +{ + struct ipapwd_krbcfg *krbcfg = NULL; + char *errMesg = "Internal operations error\n"; + struct slapi_entry *e = NULL; + char *userpw = NULL; + char *dn = NULL; + struct ipapwd_operation *pwdop = NULL; + void *op; + int is_repl_op, is_root, is_krb, is_smb, is_ipant; + int ret; + int rc = LDAP_SUCCESS; + + LOG_TRACE("=>\n"); + + ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op); + if (ret != 0) { + LOG_FATAL("slapi_pblock_get failed!?\n"); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + /* pass through if this is a replicated operation */ + if (is_repl_op) + return 0; + + /* retrieve the entry */ + slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e); + if (NULL == e) + return 0; + + /* check this is something interesting for us first */ + userpw = slapi_entry_attr_get_charptr(e, SLAPI_USERPWD_ATTR); + if (!userpw) { + /* nothing interesting here */ + return 0; + } + + /* Ok this is interesting, + * Check this is a clear text password, or refuse operation */ + if ('{' == userpw[0]) { + if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) { + char *tmp = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]); + if (NULL == tmp) { + LOG_OOM(); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + slapi_ch_free_string(&userpw); + userpw = tmp; + } else if (slapi_is_encoded(userpw)) { + const char *userpw_clear = NULL; + Slapi_Value **pwvals = NULL; + + /* Try to get clear password from an entry extension. + * This function does not return a copy of the values, + * no need to free them. */ + rc = slapi_pw_get_entry_ext(e, &pwvals); + if (LDAP_SUCCESS == rc) { + userpw_clear = slapi_value_get_string(pwvals[0]); + } + + /* Fail if we did not get a real clear text password from + * the extension. This will happen if the password is hashed. */ + if (!userpw_clear || (0 == strcmp(userpw, userpw_clear))) { + rc = LDAP_CONSTRAINT_VIOLATION; + slapi_ch_free_string(&userpw); + } else { + userpw = slapi_ch_strdup(userpw_clear); + } + + if (rc != LDAP_SUCCESS) { + /* we don't have access to the clear text password; + * let it slide if migration is enabled, but don't + * generate kerberos keys */ + char *enabled = ipapwd_getIpaConfigAttr("ipamigrationenabled"); + if (NULL == enabled) { + LOG("no ipaMigrationEnabled in config, assuming FALSE\n"); + } else if (0 == strcmp(enabled, "TRUE")) { + return 0; + } + + LOG("pre-hashed passwords are not valid\n"); + errMesg = "pre-hashed passwords are not valid\n"; + goto done; + } + } + } + + rc = ipapwd_entry_checks(pb, e, + &is_root, &is_krb, &is_smb, &is_ipant, + NULL, SLAPI_ACL_ADD); + if (rc != LDAP_SUCCESS) { + goto done; + } + + rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN); + if (rc != LDAP_SUCCESS) { + goto done; + } + + /* Get target DN */ + ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); + if (ret) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + /* time to get the operation handler */ + ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op); + if (ret != 0) { + LOG_FATAL("slapi_pblock_get failed!?\n"); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type, + op, ipapwd_op_ext_list.handle); + if (NULL == pwdop) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + pwdop->pwd_op = IPAPWD_OP_ADD; + pwdop->pwdata.password = slapi_ch_strdup(userpw); + + if (is_root) { + pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR; + } else { + char *binddn; + int i; + + pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN; + + /* Check Bind DN */ + slapi_pblock_get(pb, SLAPI_CONN_DN, &binddn); + + /* if it is a passsync manager we also need to skip resets */ + for (i = 0; i < krbcfg->num_passsync_mgrs; i++) { + if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) { + pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR; + break; + } + } + } + + pwdop->pwdata.dn = slapi_ch_strdup(dn); + pwdop->pwdata.timeNow = time(NULL); + pwdop->pwdata.target = e; + + ret = ipapwd_CheckPolicy(&pwdop->pwdata); + if (ret) { + errMesg = ipapwd_error2string(ret); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + + if (is_krb || is_smb || is_ipant) { + + Slapi_Value **svals = NULL; + Slapi_Value **ntvals = NULL; + char *nt = NULL; + char *lm = NULL; + + pwdop->is_krb = is_krb; + + rc = ipapwd_gen_hashes(krbcfg, &pwdop->pwdata, + userpw, is_krb, is_smb, is_ipant, + &svals, &nt, &lm, &ntvals, &errMesg); + if (rc != LDAP_SUCCESS) { + goto done; + } + + if (svals) { + /* add/replace values in existing entry */ + ret = slapi_entry_attr_replace_sv(e, "krbPrincipalKey", svals); + if (ret) { + LOG_FATAL("failed to set encoded values in entry\n"); + rc = LDAP_OPERATIONS_ERROR; + ipapwd_free_slapi_value_array(&svals); + goto done; + } + + ipapwd_free_slapi_value_array(&svals); + } + + if (lm && is_smb) { + /* set value */ + slapi_entry_attr_set_charptr(e, "sambaLMPassword", lm); + slapi_ch_free_string(&lm); + } + if (nt && is_smb) { + /* set value */ + slapi_entry_attr_set_charptr(e, "sambaNTPassword", nt); + slapi_ch_free_string(&nt); + } + + if (ntvals && is_ipant) { + slapi_entry_attr_replace_sv(e, "ipaNTHash", ntvals); + ipapwd_free_slapi_value_array(&ntvals); + } + + if (is_smb) { + /* with samba integration we need to also set sambaPwdLastSet or + * samba will decide the user has to change the password again */ + if (pwdop->pwdata.changetype == IPA_CHANGETYPE_ADMIN) { + /* if it is an admin change instead we need to let know to + * samba as well that the use rmust change its password */ + slapi_entry_attr_set_long(e, "sambaPwdLastset", 0L); + } else { + slapi_entry_attr_set_long(e, "sambaPwdLastset", + (long)pwdop->pwdata.timeNow); + } + } + } + + rc = LDAP_SUCCESS; + +done: + if (pwdop) pwdop->pwdata.target = NULL; + free_ipapwd_krbcfg(&krbcfg); + slapi_ch_free_string(&userpw); + if (rc != LDAP_SUCCESS) { + slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); + return -1; + } + return 0; +} + +#define NTHASH_REGEN_VAL "MagicRegen" +#define NTHASH_REGEN_LEN sizeof(NTHASH_REGEN_VAL) +static int ipapwd_regen_nthash(Slapi_PBlock *pb, Slapi_Mods *smods, + char *dn, struct slapi_entry *entry, + struct ipapwd_krbcfg *krbcfg); + +/* PRE MOD Operation: + * Gets the clean text password (fail the operation if the password came + * pre-hashed, unless this is a replicated operation). + * Check user is authorized to add it otherwise just returns, operation will + * fail later anyway. + * Check if krb or smb hashes are required by testing if the krb or smb + * objectclasses are present. + * Run a password policy check. + * store information for the post operation + */ +static int ipapwd_pre_mod(Slapi_PBlock *pb) +{ + struct ipapwd_krbcfg *krbcfg = NULL; + char *errMesg = NULL; + LDAPMod **mods; + LDAPMod *lmod; + Slapi_Mods *smods = NULL; + char *userpw = NULL; + char *unhashedpw = NULL; + char *dn = NULL; + Slapi_DN *tmp_dn; + struct slapi_entry *e = NULL; + struct ipapwd_operation *pwdop = NULL; + void *op; + int is_repl_op, is_pwd_op, is_root, is_krb, is_smb, is_ipant; + int has_krb_keys = 0; + int has_history = 0; + int gen_krb_keys = 0; + int is_magic_regen = 0; + int ret, rc; + + LOG_TRACE( "=>\n"); + + ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op); + if (ret != 0) { + LOG_FATAL("slapi_pblock_get failed!?\n"); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + /* pass through if this is a replicated operation */ + if (is_repl_op) { + rc = LDAP_SUCCESS; + goto done; + } + + /* grab the mods - we'll put them back later with + * our modifications appended + */ + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + smods = slapi_mods_new(); + slapi_mods_init_passin(smods, mods); + + /* In the first pass, + * only check there is anything we are interested in */ + is_pwd_op = 0; + lmod = slapi_mods_get_first_mod(smods); + while (lmod) { + struct berval *bv; + + if (slapi_attr_types_equivalent(lmod->mod_type, SLAPI_USERPWD_ATTR)) { + /* check op filtering out LDAP_MOD_BVALUES */ + switch (lmod->mod_op & 0x0f) { + case LDAP_MOD_ADD: + case LDAP_MOD_REPLACE: + is_pwd_op = 1; + default: + break; + } + } else if (slapi_attr_types_equivalent(lmod->mod_type, "ipaNTHash")) { + /* check op filtering out LDAP_MOD_BVALUES */ + switch (lmod->mod_op & 0x0f) { + case LDAP_MOD_ADD: + if (!lmod->mod_bvalues || + !lmod->mod_bvalues[0]) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + bv = lmod->mod_bvalues[0]; + if ((bv->bv_len >= NTHASH_REGEN_LEN -1) && + (bv->bv_len <= NTHASH_REGEN_LEN) && + (strncmp(NTHASH_REGEN_VAL, + bv->bv_val, bv->bv_len) == 0)) { + is_magic_regen = 1; + /* make sure the database will later ignore this mod */ + slapi_mods_remove(smods); + } + default: + break; + } + } else if (slapi_attr_types_equivalent(lmod->mod_type, + "unhashed#user#password")) { + /* we check for unahsehd password here so that we are sure to + * catch them early, before further checks go on, this helps + * checking LDAP_MOD_DELETE operations in some corner cases later. + * We keep only the last one if multiple are provided for any + * reason */ + if (!lmod->mod_bvalues || + !lmod->mod_bvalues[0]) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + bv = lmod->mod_bvalues[0]; + slapi_ch_free_string(&unhashedpw); + unhashedpw = slapi_ch_malloc(bv->bv_len+1); + if (!unhashedpw) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + memcpy(unhashedpw, bv->bv_val, bv->bv_len); + unhashedpw[bv->bv_len] = '\0'; + } + lmod = slapi_mods_get_next_mod(smods); + } + + /* If userPassword is not modified check if this is a request to generate + * NT hashes otherwise we are done here */ + if (!is_pwd_op && !is_magic_regen) { + rc = LDAP_SUCCESS; + goto done; + } + + /* OK we have something interesting here, start checking for + * pre-requisites */ + + /* Get target DN */ + ret = slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn); + if (ret) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + tmp_dn = slapi_sdn_new_dn_byref(dn); + if (tmp_dn) { + /* xxxPAR: Ideally SLAPI_MODIFY_EXISTING_ENTRY should be + * available but it turns out that is only true if you are + * a dbm backend pre-op plugin - lucky dbm backend pre-op + * plugins. + * I think that is wrong since the entry is useful for filter + * tests and schema checks and this plugin shouldn't be limited + * to a single backend type, but I don't want that fight right + * now so we go get the entry here + * + slapi_pblock_get( pb, SLAPI_MODIFY_EXISTING_ENTRY, &e); + */ + ret = slapi_search_internal_get_entry(tmp_dn, 0, &e, ipapwd_plugin_id); + slapi_sdn_free(&tmp_dn); + if (ret != LDAP_SUCCESS) { + LOG("Failed to retrieve entry?!\n"); + rc = LDAP_NO_SUCH_OBJECT; + goto done; + } + } + + rc = ipapwd_entry_checks(pb, e, + &is_root, &is_krb, &is_smb, &is_ipant, + SLAPI_USERPWD_ATTR, SLAPI_ACL_WRITE); + if (rc) { + goto done; + } + + rc = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN); + if (rc) { + goto done; + } + + if (!is_pwd_op) { + /* This may be a magic op to ask us to generate the NT hashes */ + if (is_magic_regen) { + /* Make sense to call only if this entry has krb keys to source + * the nthash from */ + if (is_krb) { + rc = ipapwd_regen_nthash(pb, smods, dn, e, krbcfg); + } else { + rc = LDAP_UNWILLING_TO_PERFORM; + } + } else { + rc = LDAP_OPERATIONS_ERROR; + } + goto done; + } + + /* run through the mods again and adjust flags if operations affect them */ + lmod = slapi_mods_get_first_mod(smods); + while (lmod) { + struct berval *bv; + + if (slapi_attr_types_equivalent(lmod->mod_type, SLAPI_USERPWD_ATTR)) { + /* check op filtering out LDAP_MOD_BVALUES */ + switch (lmod->mod_op & 0x0f) { + case LDAP_MOD_ADD: + /* FIXME: should we try to track cases where we would end up + * with multiple userPassword entries ?? */ + case LDAP_MOD_REPLACE: + is_pwd_op = 1; + if (!lmod->mod_bvalues || + !lmod->mod_bvalues[0]) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + bv = lmod->mod_bvalues[0]; + slapi_ch_free_string(&userpw); + userpw = slapi_ch_malloc(bv->bv_len+1); + if (!userpw) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + memcpy(userpw, bv->bv_val, bv->bv_len); + userpw[bv->bv_len] = '\0'; + break; + case LDAP_MOD_DELETE: + /* reset only if we are deleting all values, or the exact + * same value previously set, otherwise we are just trying to + * add a new value and delete an existing one */ + if (!lmod->mod_bvalues || + !lmod->mod_bvalues[0]) { + is_pwd_op = 0; + } else { + bv = lmod->mod_bvalues[0]; + if ((userpw && + strncmp(userpw, bv->bv_val, bv->bv_len) == 0) || + (unhashedpw && + strncmp(unhashedpw, bv->bv_val, bv->bv_len) == 0)) { + is_pwd_op = 0; + } + } + default: + break; + } + + } else if (slapi_attr_types_equivalent(lmod->mod_type, + SLAPI_ATTR_OBJECTCLASS)) { + int i; + /* check op filtering out LDAP_MOD_BVALUES */ + switch (lmod->mod_op & 0x0f) { + case LDAP_MOD_REPLACE: + /* if objectclasses are replaced we need to start clean with + * flags, so we sero them out and see if they get set again */ + is_krb = 0; + is_smb = 0; + is_ipant = 0; + + case LDAP_MOD_ADD: + if (!lmod->mod_bvalues || + !lmod->mod_bvalues[0]) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + for (i = 0; (bv = lmod->mod_bvalues[i]) != NULL; i++) { + if (strncasecmp("krbPrincipalAux", + bv->bv_val, bv->bv_len) == 0) { + is_krb = 1; + } else if (strncasecmp("sambaSamAccount", + bv->bv_val, bv->bv_len) == 0) { + is_smb = 1; + } else if (strncasecmp("ipaNTUserAttrs", + bv->bv_val, bv->bv_len) == 0) { + is_ipant = 1; + } + } + + break; + + case LDAP_MOD_DELETE: + /* can this happen for objectclasses ? */ + is_krb = 0; + is_smb = 0; + is_ipant = 0; + + default: + break; + } + + } else if (slapi_attr_types_equivalent(lmod->mod_type, + "krbPrincipalKey")) { + + /* if we are getting a krbPrincipalKey, also avoid regenerating + * the keys, it means kadmin has alredy done the job and is simply + * keeping userPassword and sambaXXPAssword in sync */ + + /* we also check we have enough authority */ + if (is_root) { + has_krb_keys = 1; + } + + } else if (slapi_attr_types_equivalent(lmod->mod_type, + "passwordHistory")) { + + /* if we are getting a passwordHistory, also avoid regenerating + * the hashes, it means kadmin has alredy done the job and is + * simply keeping userPassword and sambaXXPAssword in sync */ + + /* we also check we have enough authority */ + if (is_root) { + has_history = 1; + } + } + + lmod = slapi_mods_get_next_mod(smods); + } + + if (is_krb) { + if (has_krb_keys) { + gen_krb_keys = 0; + } else { + gen_krb_keys = 1; + } + } + + /* It seem like we have determined that the end result will be deletion of + * the userPassword attribute, so we have no more business here */ + if (! is_pwd_op) { + rc = LDAP_SUCCESS; + goto done; + } + + /* Check this is a clear text password, or refuse operation (only if we need + * to comput other hashes */ + if (! unhashedpw && (gen_krb_keys || is_smb || is_ipant)) { + if ('{' == userpw[0]) { + if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) { + unhashedpw = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]); + if (NULL == unhashedpw) { + LOG_OOM(); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + slapi_ch_free_string(&userpw); + + } else if (slapi_is_encoded(userpw)) { + + LOG("Pre-Encoded passwords are not valid\n"); + errMesg = "Pre-Encoded passwords are not valid\n"; + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + } + } + + /* time to get the operation handler */ + ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op); + if (ret != 0) { + LOG_FATAL("slapi_pblock_get failed!?\n"); + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type, + op, ipapwd_op_ext_list.handle); + if (NULL == pwdop) { + rc = LDAP_OPERATIONS_ERROR; + goto done; + } + + pwdop->is_krb = is_krb; + pwdop->pwd_op = IPAPWD_OP_MOD; + pwdop->pwdata.password = slapi_ch_strdup(unhashedpw); + pwdop->pwdata.changetype = IPA_CHANGETYPE_NORMAL; + pwdop->skip_history = has_history; + pwdop->skip_keys = has_krb_keys; + + if (is_root) { + pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR; + } else { + char *binddn; + Slapi_DN *bdn, *tdn; + int i; + + /* Check Bind DN */ + slapi_pblock_get(pb, SLAPI_CONN_DN, &binddn); + bdn = slapi_sdn_new_dn_byref(binddn); + tdn = slapi_sdn_new_dn_byref(dn); + + /* if the change is performed by someone else, + * it is an admin change that will require a new + * password change immediately as per our IPA policy */ + if (slapi_sdn_compare(bdn, tdn)) { + pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN; + + /* if it is a passsync manager we also need to skip resets */ + for (i = 0; i < krbcfg->num_passsync_mgrs; i++) { + if (strcasecmp(krbcfg->passsync_mgrs[i], binddn) == 0) { + pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR; + break; + } + } + + } + + slapi_sdn_free(&bdn); + slapi_sdn_free(&tdn); + + } + + pwdop->pwdata.dn = slapi_ch_strdup(dn); + pwdop->pwdata.timeNow = time(NULL); + pwdop->pwdata.target = e; + + /* if krb keys are being set by an external agent we assume password + * policies have been properly checked already, so we check them only + * if no krb keys are available */ + if (has_krb_keys == 0) { + ret = ipapwd_CheckPolicy(&pwdop->pwdata); + if (ret) { + errMesg = ipapwd_error2string(ret); + rc = LDAP_CONSTRAINT_VIOLATION; + goto done; + } + } + + if (gen_krb_keys || is_smb || is_ipant) { + + Slapi_Value **svals = NULL; + Slapi_Value **ntvals = NULL; + char *nt = NULL; + char *lm = NULL; + + rc = ipapwd_gen_hashes(krbcfg, &pwdop->pwdata, unhashedpw, + gen_krb_keys, is_smb, is_ipant, + &svals, &nt, &lm, &ntvals, &errMesg); + if (rc) { + goto done; + } + + if (svals) { + /* replace values */ + slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, + "krbPrincipalKey", svals); + ipapwd_free_slapi_value_array(&svals); + } + + if (lm && is_smb) { + /* replace value */ + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "sambaLMPassword", lm); + slapi_ch_free_string(&lm); + } + if (nt && is_smb) { + /* replace value */ + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "sambaNTPassword", nt); + slapi_ch_free_string(&nt); + } + + if (ntvals && is_ipant) { + slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, + "ipaNTHash", ntvals); + ipapwd_free_slapi_value_array(&ntvals); + } + + if (is_smb) { + /* with samba integration we need to also set sambaPwdLastSet or + * samba will decide the user has to change the password again */ + if (pwdop->pwdata.changetype == IPA_CHANGETYPE_ADMIN) { + /* if it is an admin change instead we need to let know to + * samba as well that the use rmust change its password */ + slapi_entry_attr_set_long(e, "sambaPwdLastset", 0L); + } else { + slapi_entry_attr_set_long(e, "sambaPwdLastset", + (long)pwdop->pwdata.timeNow); + } + } + } + + rc = LDAP_SUCCESS; + +done: + free_ipapwd_krbcfg(&krbcfg); + slapi_ch_free_string(&userpw); /* just to be sure */ + slapi_ch_free_string(&unhashedpw); /* we copied it to pwdop */ + if (e) slapi_entry_free(e); /* this is a copy in this function */ + if (pwdop) pwdop->pwdata.target = NULL; + + /* put back a, possibly modified, set of mods */ + if (smods) { + mods = slapi_mods_get_ldapmods_passout(smods); + if (slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods)) { + LOG_FATAL("slapi_pblock_set failed!\n"); + rc = LDAP_OPERATIONS_ERROR; + } + slapi_mods_free(&smods); + } + + if (rc != LDAP_SUCCESS) { + slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL); + return -1; + } + + return 0; +} + +static int ipapwd_regen_nthash(Slapi_PBlock *pb, Slapi_Mods *smods, + char *dn, struct slapi_entry *entry, + struct ipapwd_krbcfg *krbcfg) +{ + Slapi_Attr *attr; + Slapi_Value *value; + const struct berval *val; + struct berval *ntvals[2] = { NULL, NULL }; + struct berval bval; + krb5_key_data *keys; + int num_keys; + int mkvno; + int ret; + int i; + + ret = slapi_entry_attr_find(entry, "ipaNTHash", &attr); + if (ret == 0) { + /* We refuse to regen if there is already a value */ + return LDAP_CONSTRAINT_VIOLATION; + } + + /* ok let's see if we can find the RC4 hash in the keys */ + ret = slapi_entry_attr_find(entry, "krbPrincipalKey", &attr); + if (ret) { + return LDAP_UNWILLING_TO_PERFORM; + } + + ret = slapi_attr_first_value(attr, &value); + if (ret) { + return LDAP_OPERATIONS_ERROR; + } + + val = slapi_value_get_berval(value); + if (!val) { + return LDAP_OPERATIONS_ERROR; + } + + ret = ber_decode_krb5_key_data((struct berval *)val, + &mkvno, &num_keys, &keys); + if (ret) { + return LDAP_OPERATIONS_ERROR; + } + + ret = LDAP_UNWILLING_TO_PERFORM; + + for (i = 0; i < num_keys; i++) { + char nthash[16]; + krb5_enc_data cipher; + krb5_data plain; + krb5_int16 t; + + if (keys[i].key_data_type[0] != ENCTYPE_ARCFOUR_HMAC) { + continue; + } + + memcpy(&t, keys[i].key_data_contents[0], 2); + plain.length = le16toh(t); + if (plain.length != 16) { + continue; + } + plain.data = nthash; + + memset(&cipher, 0, sizeof(krb5_enc_data)); + cipher.enctype = krbcfg->kmkey->enctype; + cipher.ciphertext.length = keys[i].key_data_length[0] - 2; + cipher.ciphertext.data = ((char *)keys[i].key_data_contents[0]) + 2; + + ret = krb5_c_decrypt(krbcfg->krbctx, krbcfg->kmkey, + 0, NULL, &cipher, &plain); + if (ret) { + ret = LDAP_OPERATIONS_ERROR; + break; + } + + bval.bv_val = nthash; + bval.bv_len = 16; + ntvals[0] = &bval; + + slapi_mods_add_modbvps(smods, LDAP_MOD_ADD, "ipaNTHash", ntvals); + + ret = LDAP_SUCCESS; + break; + } + + ipa_krb5_free_key_data(keys, num_keys); + + return ret; +} + +static int ipapwd_post_op(Slapi_PBlock *pb) +{ + void *op; + struct ipapwd_operation *pwdop = NULL; + Slapi_Mods *smods; + Slapi_Value **pwvals; + struct tm utctime; + char timestr[GENERALIZED_TIME_LENGTH+1]; + int ret; + char *errMsg = "Internal operations error\n"; + struct ipapwd_krbcfg *krbcfg = NULL; + char *principal = NULL; + Slapi_Value *ipahost; + + LOG_TRACE("=>\n"); + + /* time to get the operation handler */ + ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op); + if (ret != 0) { + LOG_FATAL("slapi_pblock_get failed!?\n"); + return 0; + } + + pwdop = slapi_get_object_extension(ipapwd_op_ext_list.object_type, + op, ipapwd_op_ext_list.handle); + if (NULL == pwdop) { + LOG_FATAL("Internal error, couldn't find pluginextension ?!\n"); + return 0; + } + + /* not interesting */ + if (IPAPWD_OP_NULL == pwdop->pwd_op) + return 0; + + if ( ! (pwdop->is_krb)) { + LOG("Not a kerberos user, ignore krb attributes\n"); + return 0; + } + + if (pwdop->skip_keys && pwdop->skip_history) { + /* nothing to do, caller already set all interesting attributes */ + return 0; + } + + ret = ipapwd_gen_checks(pb, &errMsg, &krbcfg, 0); + if (ret != 0) { + LOG_FATAL("ipapwd_gen_checks failed!?\n"); + return 0; + } + + /* prepare changes that can be made only as root */ + smods = slapi_mods_new(); + + /* This was a mod operation on an existing entry, make sure we also update + * the password history based on the entry we saved from the pre-op */ + if (IPAPWD_OP_MOD == pwdop->pwd_op && !pwdop->skip_history) { + Slapi_DN *tmp_dn = slapi_sdn_new_dn_byref(pwdop->pwdata.dn); + if (tmp_dn) { + ret = slapi_search_internal_get_entry(tmp_dn, 0, + &pwdop->pwdata.target, + ipapwd_plugin_id); + slapi_sdn_free(&tmp_dn); + if (ret != LDAP_SUCCESS) { + LOG("Failed to retrieve entry?!\n"); + goto done; + } + } + pwvals = ipapwd_setPasswordHistory(smods, &pwdop->pwdata); + if (pwvals) { + slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE, + "passwordHistory", pwvals); + } + } + + /* we assume that krb attributes are properly updated too if keys were + * passed in */ + if (!pwdop->skip_keys) { + /* Don't set a last password change or expiration on host passwords. + * krbLastPwdChange is used to tell whether we have a valid keytab. + * If we set it on userPassword it confuses enrollment. + * If krbPasswordExpiration is set on a host entry then the keytab + * will appear to be expired. + * + * When a host is issued a keytab these attributes get set properly by + * ipapwd_setkeytab(). + */ + ipahost = slapi_value_new_string("ipaHost"); + if (!pwdop->pwdata.target || + (slapi_entry_attr_has_syntax_value(pwdop->pwdata.target, + SLAPI_ATTR_OBJECTCLASS, ipahost)) == 0) { + /* set Password Expiration date */ + if (!gmtime_r(&(pwdop->pwdata.expireTime), &utctime)) { + LOG_FATAL("failed to parse expiration date (buggy gmtime_r ?)\n"); + goto done; + } + strftime(timestr, GENERALIZED_TIME_LENGTH+1, + "%Y%m%d%H%M%SZ", &utctime); + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "krbPasswordExpiration", timestr); + + /* change Last Password Change field with the current date */ + if (!gmtime_r(&(pwdop->pwdata.timeNow), &utctime)) { + LOG_FATAL("failed to parse current date (buggy gmtime_r ?)\n"); + slapi_value_free(&ipahost); + goto done; + } + strftime(timestr, GENERALIZED_TIME_LENGTH+1, + "%Y%m%d%H%M%SZ", &utctime); + slapi_mods_add_string(smods, LDAP_MOD_REPLACE, + "krbLastPwdChange", timestr); + } + slapi_value_free(&ipahost); + } + + ret = ipapwd_apply_mods(pwdop->pwdata.dn, smods); + if (ret) + LOG("Failed to set additional password attributes in the post-op!\n"); + + if (!pwdop->skip_keys) { + if (pwdop->pwdata.changetype == IPA_CHANGETYPE_NORMAL) { + principal = slapi_entry_attr_get_charptr(pwdop->pwdata.target, + "krbPrincipalName"); + } else { + principal = slapi_ch_smprintf("root/admin@%s", krbcfg->realm); + } + ipapwd_set_extradata(pwdop->pwdata.dn, principal, pwdop->pwdata.timeNow); + } + +done: + if (pwdop && pwdop->pwdata.target) slapi_entry_free(pwdop->pwdata.target); + slapi_mods_free(&smods); + slapi_ch_free_string(&principal); + free_ipapwd_krbcfg(&krbcfg); + return 0; +} + +/* PRE BIND Operation: + * Used for password migration from DS to IPA. + * Gets the clean text password, authenticates the user and generates + * a kerberos key if missing. + * Person to blame if anything blows up: Pavel Zuna + */ +static int ipapwd_pre_bind(Slapi_PBlock *pb) +{ + struct ipapwd_krbcfg *krbcfg = NULL; + struct ipapwd_data pwdata; + struct berval *credentials; /* bind credentials */ + Slapi_Entry *entry = NULL; + Slapi_Value **pwd_values = NULL; /* values of userPassword attribute */ + Slapi_Value *value = NULL; + Slapi_Attr *attr = NULL; + struct tm expire_tm; + char *errMesg = "Internal operations error\n"; /* error message */ + char *expire = NULL; /* passwordExpirationTime attribute value */ + char *dn = NULL; /* bind DN */ + Slapi_Value *objectclass; + int method; /* authentication method */ + int ret = 0; + char *principal = NULL; + + LOG_TRACE("=>\n"); + + /* get BIND parameters */ + ret |= slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn); + ret |= slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method); + ret |= slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &credentials); + if (ret) { + LOG_FATAL("slapi_pblock_get failed!?\n"); + goto done; + } + + /* we're only interested in simple authentication */ + if (method != LDAP_AUTH_SIMPLE) + goto done; + + /* list of attributes to retrieve */ + const char *attrs_list[] = {SLAPI_USERPWD_ATTR, "krbprincipalkey", "uid", + "krbprincipalname", "objectclass", + "passwordexpirationtime", "passwordhistory", + NULL}; + + /* retrieve user entry */ + ret = ipapwd_getEntry(dn, &entry, (char **) attrs_list); + if (ret) { + LOG("failed to retrieve user entry: %s\n", dn); + goto done; + } + + /* check the krbPrincipalName attribute is present */ + ret = slapi_entry_attr_find(entry, "krbprincipalname", &attr); + if (ret) { + LOG("no krbPrincipalName in user entry: %s\n", dn); + goto done; + } + + /* we aren't interested in host principals */ + objectclass = slapi_value_new_string("ipaHost"); + if ((slapi_entry_attr_has_syntax_value(entry, SLAPI_ATTR_OBJECTCLASS, objectclass)) == 1) { + slapi_value_free(&objectclass); + goto done; + } + slapi_value_free(&objectclass); + + /* check the krbPrincipalKey attribute is NOT present */ + ret = slapi_entry_attr_find(entry, "krbprincipalkey", &attr); + if (!ret) { + LOG("kerberos key already present in user entry: %s\n", dn); + goto done; + } + + /* retrieve userPassword attribute */ + ret = slapi_entry_attr_find(entry, SLAPI_USERPWD_ATTR, &attr); + if (ret) { + LOG("no " SLAPI_USERPWD_ATTR " in user entry: %s\n", dn); + goto done; + } + + /* get the number of userPassword values and allocate enough memory */ + slapi_attr_get_numvalues(attr, &ret); + ret = (ret + 1) * sizeof (Slapi_Value *); + pwd_values = (Slapi_Value **) slapi_ch_malloc(ret); + if (!pwd_values) { + /* probably not required: should terminate the server anyway */ + LOG_OOM(); + goto done; + } + /* zero-fill the allocated memory; we need the array ending with NULL */ + memset(pwd_values, 0, ret); + + /* retrieve userPassword values */ + ret = slapi_attr_first_value(attr, &value); + while (ret != -1) { + pwd_values[ret] = value; + ret = slapi_attr_next_value(attr, ret, &value); + } + + /* check if BIND password and userPassword match */ + value = slapi_value_new_berval(credentials); + ret = slapi_pw_find_sv(pwd_values, value); + + /* free before checking ret; we might not get a chance later */ + slapi_ch_free((void **) &pwd_values); + slapi_value_free(&value); + + if (ret) { + LOG("invalid BIND password for user entry: %s\n", dn); + goto done; + } + + /* general checks */ + ret = ipapwd_gen_checks(pb, &errMesg, &krbcfg, IPAPWD_CHECK_DN); + if (ret) { + LOG_FATAL("Generic checks failed: %s", errMesg); + goto done; + } + + /* delete userPassword - a new one will be generated later */ + /* this is needed, otherwise ipapwd_CheckPolicy will think + * we're changing the password to its previous value + * and force a password change on next login */ + ret = slapi_entry_attr_delete(entry, SLAPI_USERPWD_ATTR); + if (ret) { + LOG_FATAL("failed to delete " SLAPI_USERPWD_ATTR "\n"); + goto done; + } + + /* prepare data for kerberos key generation */ + memset(&pwdata, 0, sizeof (pwdata)); + pwdata.dn = dn; + pwdata.target = entry; + pwdata.password = credentials->bv_val; + pwdata.timeNow = time(NULL); + pwdata.changetype = IPA_CHANGETYPE_NORMAL; + + /* keep password expiration time from DS, if possible */ + expire = slapi_entry_attr_get_charptr(entry, "passwordexpirationtime"); + if (expire) { + memset(&expire_tm, 0, sizeof (expire_tm)); + if (strptime(expire, "%Y%m%d%H%M%SZ", &expire_tm)) + pwdata.expireTime = mktime(&expire_tm); + } + + /* check password policy */ + ret = ipapwd_CheckPolicy(&pwdata); + if (ret) { + /* Password fails to meet IPA password policy, + * force user to change his password next time he logs in. */ + LOG("password policy check failed on user entry: %s" + " (force password change on next login)\n", dn); + pwdata.expireTime = time(NULL); + } + + /* generate kerberos keys */ + ret = ipapwd_SetPassword(krbcfg, &pwdata, 1); + if (ret) { + LOG("failed to set kerberos key for user entry: %s\n", dn); + goto done; + } + + /* we need to make sure the ExtraData is set, otherwise kadmin + * will not like the object */ + principal = slapi_entry_attr_get_charptr(entry, "krbPrincipalName"); + if (!principal) { + LOG_OOM(); + goto done; + } + ipapwd_set_extradata(pwdata.dn, principal, pwdata.timeNow); + + LOG("kerberos key generated for user entry: %s\n", dn); + +done: + slapi_ch_free_string(&principal); + slapi_ch_free_string(&expire); + if (entry) + slapi_entry_free(entry); + free_ipapwd_krbcfg(&krbcfg); + + return 0; +} + + + +/* Init pre ops */ +int ipapwd_pre_init(Slapi_PBlock *pb) +{ + int ret; + + ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *)ipapwd_pre_bind); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, (void *)ipapwd_pre_add); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, (void *)ipapwd_pre_mod); + + return ret; +} + +int ipapwd_pre_init_betxn(Slapi_PBlock *pb) +{ + int ret; + + ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN, (void *)ipapwd_pre_add); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN, (void *)ipapwd_pre_mod); + + return ret; +} + +/* Init post ops */ +int ipapwd_post_init(Slapi_PBlock *pb) +{ + int ret; + + ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *)ipapwd_post_op); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_op); + + return ret; +} + +int ipapwd_post_init_betxn(Slapi_PBlock *pb) +{ + int ret; + + ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_ADD_FN, (void *)ipapwd_post_op); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN, (void *)ipapwd_post_op); + + return ret; +} -- cgit