summaryrefslogtreecommitdiffstats
path: root/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c
diff options
context:
space:
mode:
authorSimo Sorce <ssorce@redhat.com>2010-09-29 17:00:41 -0400
committerSimo Sorce <ssorce@redhat.com>2010-10-01 11:17:40 -0400
commit3127df2aee8ad8ef95f34610665a004fece51acf (patch)
tree55f4498f2e12036a1213b52ee88994d76377987a /daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c
parent58f1026e34c3acbde5a307e499c568476301416b (diff)
downloadfreeipa-3127df2aee8ad8ef95f34610665a004fece51acf.tar.gz
freeipa-3127df2aee8ad8ef95f34610665a004fece51acf.tar.xz
freeipa-3127df2aee8ad8ef95f34610665a004fece51acf.zip
Split ipa_pwd_extop plugin in multiple files
The plugin was getting difficult to read and maintain. Split it (and apply cosmetic cleanups to some functions) in smaller pieces that perform specific tasks.
Diffstat (limited to 'daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c')
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c1427
1 files changed, 1427 insertions, 0 deletions
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c
new file mode 100644
index 000000000..b1826dbb2
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd_common.c
@@ -0,0 +1,1427 @@
+/** 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; version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
+ * right to link the code of this Program with code not covered under the GNU
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
+ * permitted under this exception must only link 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 GNU General Public License. Only Red Hat, Inc. may make changes or
+ * additions to the list of Approved Interfaces. You must obey the GNU General
+ * Public License in all respects for all of the Program code and other code
+ * used in conjunction with the Program except the Non-GPL Code covered by this
+ * exception. If you modify this file, you may extend this exception to your
+ * version of the file, but you are not obligated to do so. If you do not wish
+ * to provide this exception without modification, you must delete this
+ * exception statement from your version and license this file solely under the
+ * GPL without exception.
+ *
+ * Authors:
+ * Simo Sorce <ssorce@redhat.com>
+ *
+ * Copyright (C) 2007-2010 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "ipapwd.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_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 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 int new_ipapwd_encsalt(krb5_context krbctx,
+ const char * const *encsalts,
+ struct ipapwd_encsalt **es_types,
+ int *num_es_types)
+{
+ struct ipapwd_encsalt *es;
+ int nes, i;
+
+ for (i = 0; encsalts[i]; i++) /* count */ ;
+ es = calloc(i + 1, sizeof(struct ipapwd_encsalt));
+ if (!es) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "Out of memory!\n");
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ for (i = 0, nes = 0; encsalts[i]; i++) {
+ char *enc, *salt;
+ krb5_int32 tmpsalt;
+ krb5_enctype tmpenc;
+ krb5_boolean similar;
+ krb5_error_code krberr;
+ int j;
+
+ enc = strdup(encsalts[i]);
+ if (!enc) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Allocation error\n");
+ return LDAP_OPERATIONS_ERROR;
+ }
+ salt = strchr(enc, ':');
+ if (!salt) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Invalid krb5 enc string\n");
+ free(enc);
+ continue;
+ }
+ *salt = '\0'; /* null terminate the enc type */
+ salt++; /* skip : */
+
+ krberr = krb5_string_to_enctype(enc, &tmpenc);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Invalid krb5 enctype\n");
+ free(enc);
+ continue;
+ }
+
+ krberr = krb5_string_to_salttype(salt, &tmpsalt);
+ for (j = 0; j < nes; j++) {
+ krb5_c_enctype_compare(krbctx, es[j].enc_type, tmpenc, &similar);
+ if (similar && (es[j].salt_type == tmpsalt)) {
+ break;
+ }
+ }
+
+ if (j == nes) {
+ /* not found */
+ es[j].enc_type = tmpenc;
+ es[j].salt_type = tmpsalt;
+ nes++;
+ }
+
+ free(enc);
+ }
+
+ *es_types = es;
+ *num_es_types = nes;
+
+ return LDAP_SUCCESS;
+}
+
+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, tmp;
+ ber_int_t ttype;
+ const struct berval *bval;
+ struct berval *mkey = NULL;
+ char **encsalts;
+ char *tmpstr;
+ int i, ret;
+
+ config = calloc(1, sizeof(struct ipapwd_krbcfg));
+ if (!config) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "Out of memory!\n");
+ goto free_and_error;
+ }
+ kmkey = calloc(1, sizeof(krb5_keyblock));
+ if (!kmkey) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "Out of memory!\n");
+ goto free_and_error;
+ }
+ config->kmkey = kmkey;
+
+ krberr = krb5_init_context(&config->krbctx);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "krb5_init_context failed\n");
+ goto free_and_error;
+ }
+
+ ret = krb5_get_default_realm(config->krbctx, &config->realm);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "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) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "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) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "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) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "No master key??\n");
+ goto free_and_error;
+ }
+
+ bval = slapi_value_get_berval(v);
+ if (!bval) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "Error retrieving master key berval\n");
+ goto free_and_error;
+ }
+
+ be = ber_init(bval);
+ if (!bval) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "ber_init() failed!\n");
+ goto free_and_error;
+ }
+
+ tag = ber_scanf(be, "{i{iO}}", &tmp, &ttype, &mkey);
+ if (tag == LBER_ERROR) {
+ slapi_log_error(SLAPI_LOG_TRACE, "ipapwd_getConfig",
+ "Bad Master key encoding ?!\n");
+ goto free_and_error;
+ }
+
+ kmkey->magic = KV5M_KEYBLOCK;
+ kmkey->enctype = ttype;
+ kmkey->length = mkey->bv_len;
+ kmkey->contents = malloc(mkey->bv_len);
+ if (!kmkey->contents) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "Out of memory!\n");
+ 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) {
+ ret = new_ipapwd_encsalt(config->krbctx,
+ (const char * const *)encsalts,
+ &config->supp_encsalts,
+ &config->num_supp_encsalts);
+ slapi_ch_array_free(encsalts);
+ } else {
+ slapi_log_error(SLAPI_LOG_TRACE, "ipapwd_getConfig",
+ "No configured salt types use defaults\n");
+ ret = new_ipapwd_encsalt(config->krbctx,
+ ipapwd_def_encsalts,
+ &config->supp_encsalts,
+ &config->num_supp_encsalts);
+ }
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "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) {
+ ret = new_ipapwd_encsalt(config->krbctx,
+ (const char * const *)encsalts,
+ &config->pref_encsalts,
+ &config->num_pref_encsalts);
+ slapi_ch_array_free(encsalts);
+ } else {
+ slapi_log_error(SLAPI_LOG_TRACE, "ipapwd_getConfig",
+ "No configured salt types use defaults\n");
+ ret = new_ipapwd_encsalt(config->krbctx,
+ ipapwd_def_encsalts,
+ &config->pref_encsalts,
+ &config->num_pref_encsalts);
+ }
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "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) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "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) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_getConfig",
+ "Out of memory!\n");
+ goto free_and_error;
+ }
+ for (i = 0; config->passsync_mgrs[i]; i++) /* count */ ;
+ config->num_passsync_mgrs = i;
+
+ 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_getPolicy(const char *dn,
+ Slapi_Entry *target, Slapi_Entry **e)
+{
+ 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;
+ char **edn;
+ int ret, res, dist, rdnc, scope, i;
+ Slapi_DN *sdn = NULL;
+ int buffer_flags=0;
+ Slapi_ValueSet* results = NULL;
+ char* actual_type_name = NULL;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: Searching policy for [%s]\n", dn);
+
+ sdn = slapi_sdn_new_dn_byref(dn);
+ if (sdn == NULL) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: Out of memory on [%s]\n", dn);
+ 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;
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: 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) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: Invalid DN [%s]\n", dn);
+ ret = -1;
+ goto done;
+ }
+ pdn = slapi_sdn_get_dn(psdn);
+ scope = LDAP_SCOPE_SUBTREE;
+ }
+
+ *e = NULL;
+
+ 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) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: 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) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: 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) {
+ *e = slapi_entry_dup(es[0]);
+
+ ret = 0;
+ goto done;
+ }
+
+ /* count number of RDNs in DN */
+ edn = ldap_explode_dn(dn, 0);
+ if (!edn) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getPolicy: ldap_explode_dn(dn) failed ?!");
+ ret = -1;
+ goto done;
+ }
+ for (rdnc = 0; edn[rdnc]; rdnc++) /* count */ ;
+ ldap_value_free(edn);
+
+ 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;
+ char **e1;
+ int c1;
+
+ dn1 = slapi_sdn_get_dn(esdn);
+ if (!dn1) continue;
+ e1 = ldap_explode_dn(dn1, 0);
+ if (!e1) continue;
+ for (c1 = 0; e1[c1]; c1++) /* count */ ;
+ ldap_value_free(e1);
+ 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;
+ }
+
+ *e = slapi_entry_dup(pe);
+ 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;
+}
+
+static Slapi_Value *ipapwd_strip_pw_date(Slapi_Value *pw)
+{
+ const char *pwstr;
+
+ pwstr = slapi_value_get_string(pw);
+ return slapi_value_new_string(&pwstr[GENERALIZED_TIME_LENGTH]);
+}
+
+/* ascii hex output of bytes in "in"
+ * out len is 32 (preallocated)
+ * in len is 16 */
+static const char hexchars[] = "0123456789ABCDEF";
+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];
+ }
+}
+
+/* searches the directory and finds the policy closest to the DN */
+/* return 0 on success, -1 on error or if no policy is found */
+static int ipapwd_sv_pw_cmp(const void *pv1, const void *pv2)
+{
+ const char *pw1 = slapi_value_get_string(*((Slapi_Value **)pv1));
+ const char *pw2 = slapi_value_get_string(*((Slapi_Value **)pv2));
+
+ return strncmp(pw1, pw2, GENERALIZED_TIME_LENGTH);
+}
+
+
+/*==Common-public-functions=============================================*/
+
+int ipapwd_entry_checks(Slapi_PBlock *pb, struct slapi_entry *e,
+ int *is_root, int *is_krb, int *is_smb,
+ char *attr, int access)
+{
+ 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, access);
+ 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);
+
+ rc = LDAP_SUCCESS;
+
+done:
+ return rc;
+}
+
+int ipapwd_gen_checks(Slapi_PBlock *pb, char **errMesg,
+ struct ipapwd_krbcfg **config, int check_flags)
+{
+ int ret, sasl_ssf, is_ssl;
+ int rc = LDAP_SUCCESS;
+ Slapi_Backend *be;
+ const Slapi_DN *psdn;
+ Slapi_DN *sdn;
+ char *dn = NULL;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "=> ipapwd_gen_checks\n");
+
+#ifdef LDAP_EXTOP_PASSMOD_CONN_SECURE
+ if (check_flags & IPAPWD_CHECK_CONN_SECURE) {
+ /* Allow password modify only for SSL/TLS established connections and
+ * connections using SASL privacy layers */
+ if (slapi_pblock_get(pb, SLAPI_CONN_SASL_SSF, &sasl_ssf) != 0) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Could not get SASL SSF from connection\n");
+ *errMesg = "Operation requires a secure connection.\n";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ if (slapi_pblock_get(pb, SLAPI_CONN_IS_SSL_SESSION, &is_ssl) != 0) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Could not get IS SSL from connection\n");
+ *errMesg = "Operation requires a secure connection.\n";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ if ((0 == is_ssl) && (sasl_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) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Tried to change password for an invalid DN "
+ "[%s]\n", dn ? dn : "<NULL>");
+ *errMesg = "Invalid DN";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ sdn = slapi_sdn_new_dn_byref(dn);
+ if (!sdn) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "Unable to convert dn to sdn %s",
+ dn ? dn : "<NULL>");
+ *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) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Error Retrieving Master Key");
+ *errMesg = "Fatal Internal Error";
+ rc = LDAP_OPERATIONS_ERROR;
+ }
+
+done:
+ return rc;
+}
+
+/* 90 days default pwd max lifetime */
+#define IPAPWD_DEFAULT_PWDLIFE (90 * 24 *3600)
+#define IPAPWD_DEFAULT_MINLEN 0
+
+/* check password strenght and history */
+int ipapwd_CheckPolicy(struct ipapwd_data *data)
+{
+ char *krbPrincipalExpiration = NULL;
+ char *krbLastPwdChange = NULL;
+ char *krbPasswordExpiration = NULL;
+ int krbMaxPwdLife = IPAPWD_DEFAULT_PWDLIFE;
+ int krbPwdMinLength = IPAPWD_DEFAULT_MINLEN;
+ int krbPwdMinDiffChars = 0;
+ int krbMinPwdLife = 0;
+ int pwdCharLen = 0;
+ Slapi_Entry *policy = NULL;
+ Slapi_Attr *passwordHistory = NULL;
+ struct tm tm;
+ int tmp, ret;
+ char *old_pw;
+
+ /* check account is not expired. Ignore unixtime = 0 (Jan 1 1970) */
+ krbPrincipalExpiration =
+ slapi_entry_attr_get_charptr(data->target, "krbPrincipalExpiration");
+ if (krbPrincipalExpiration &&
+ (strcasecmp("19700101000000Z", krbPrincipalExpiration) != 0)) {
+ /* if expiration date is set check it */
+ memset(&tm, 0, sizeof(struct tm));
+ ret = sscanf(krbPrincipalExpiration,
+ "%04u%02u%02u%02u%02u%02u",
+ &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
+ &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
+
+ if (ret == 6) {
+ tm.tm_year -= 1900;
+ tm.tm_mon -= 1;
+
+ if (data->timeNow > timegm(&tm)) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "Account Expired");
+ return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDMODNOTALLOWED;
+ }
+ }
+ /* FIXME: else error out ? */
+ }
+ slapi_ch_free_string(&krbPrincipalExpiration);
+
+ /* find the entry with the password policy */
+ ret = ipapwd_getPolicy(data->dn, data->target, &policy);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "No password policy");
+ goto no_policy;
+ }
+
+ /* Retrieve Max History Len */
+ data->pwHistoryLen =
+ slapi_entry_attr_get_int(policy, "krbPwdHistoryLength");
+
+ 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) {
+ data->expireTime = data->timeNow;
+ }
+
+ /* skip policy checks */
+ slapi_entry_free(policy);
+ goto no_policy;
+ }
+
+ /* first of all check current password, if any */
+ old_pw = slapi_entry_attr_get_charptr(data->target, "userPassword");
+ if (old_pw) {
+ Slapi_Value *cpw[2] = {NULL, NULL};
+ Slapi_Value *pw;
+
+ cpw[0] = slapi_value_new_string(old_pw);
+ pw = slapi_value_new_string(data->password);
+ if (!pw) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ slapi_entry_free(policy);
+ slapi_ch_free_string(&old_pw);
+ slapi_value_free(&cpw[0]);
+ slapi_value_free(&pw);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ ret = slapi_pw_find_sv(cpw, pw);
+ slapi_ch_free_string(&old_pw);
+ slapi_value_free(&cpw[0]);
+ slapi_value_free(&pw);
+
+ if (ret == 0) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Password in history\n");
+ slapi_entry_free(policy);
+ return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDINHISTORY;
+ }
+ }
+
+ krbPasswordExpiration =
+ slapi_entry_attr_get_charptr(data->target, "krbPasswordExpiration");
+ krbLastPwdChange =
+ slapi_entry_attr_get_charptr(data->target, "krbLastPwdChange");
+ /* if no previous change, it means this is probably a new account
+ * or imported, log and just ignore */
+ if (krbLastPwdChange) {
+
+ memset(&tm, 0, sizeof(struct tm));
+ ret = sscanf(krbLastPwdChange,
+ "%04u%02u%02u%02u%02u%02u",
+ &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
+ &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
+
+ if (ret == 6) {
+ tm.tm_year -= 1900;
+ tm.tm_mon -= 1;
+ data->lastPwChange = timegm(&tm);
+ }
+ /* FIXME: *else* report an error ? */
+ } else {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "Warning: Last Password Change Time is not available\n");
+ }
+
+ /* Check min age */
+ krbMinPwdLife = slapi_entry_attr_get_int(policy, "krbMinPwdLife");
+ /* if no default then treat it as no limit */
+ if (krbMinPwdLife != 0) {
+
+ /* check for reset cases */
+ if (krbLastPwdChange == NULL ||
+ ((krbPasswordExpiration != NULL) &&
+ strcmp(krbPasswordExpiration, krbLastPwdChange) == 0)) {
+ /* Expiration and last change time are the same or
+ * missing this happens only when a password is reset
+ * by an admin or the account is new or no expiration
+ * policy is set, PASS */
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPolicy: Ignore krbMinPwdLife "
+ "Expiration, not enough info\n");
+
+ } else if (data->timeNow < data->lastPwChange + krbMinPwdLife) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPolicy: Too soon to change password\n");
+ slapi_entry_free(policy);
+ slapi_ch_free_string(&krbPasswordExpiration);
+ slapi_ch_free_string(&krbLastPwdChange);
+ return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDTOOYOUNG;
+ }
+ }
+
+ /* free strings or we leak them */
+ slapi_ch_free_string(&krbPasswordExpiration);
+ slapi_ch_free_string(&krbLastPwdChange);
+
+ /* Retrieve min length */
+ tmp = slapi_entry_attr_get_int(policy, "krbPwdMinLength");
+ if (tmp != 0) {
+ krbPwdMinLength = tmp;
+ }
+
+ /* check complexity */
+ /* FIXME: this code is partially based on Directory Server code,
+ * the plan is to merge this code later making it available
+ * trough a pulic DS API for slapi plugins */
+ krbPwdMinDiffChars =
+ slapi_entry_attr_get_int(policy, "krbPwdMinDiffChars");
+ if (krbPwdMinDiffChars != 0) {
+ int num_digits = 0;
+ int num_alphas = 0;
+ int num_uppers = 0;
+ int num_lowers = 0;
+ int num_specials = 0;
+ int num_8bit = 0;
+ int num_repeated = 0;
+ int max_repeated = 0;
+ int num_categories = 0;
+ char *p, *pwd;
+
+ pwd = strdup(data->password);
+
+ /* check character types */
+ p = pwd;
+ while (p && *p) {
+ if (ldap_utf8isdigit(p)) {
+ num_digits++;
+ } else if (ldap_utf8isalpha(p)) {
+ num_alphas++;
+ if (slapi_utf8isLower((unsigned char *)p)) {
+ num_lowers++;
+ } else {
+ num_uppers++;
+ }
+ } else {
+ /* check if this is an 8-bit char */
+ if (*p & 128) {
+ num_8bit++;
+ } else {
+ num_specials++;
+ }
+ }
+
+ /* check for repeating characters. If this is the
+ first char of the password, no need to check */
+ if (pwd != p) {
+ int len = ldap_utf8len(p);
+ char *prev_p = ldap_utf8prev(p);
+
+ if (len == ldap_utf8len(prev_p)) {
+ if (memcmp(p, prev_p, len) == 0) {
+ num_repeated++;
+ if (max_repeated < num_repeated) {
+ max_repeated = num_repeated;
+ }
+ } else {
+ num_repeated = 0;
+ }
+ } else {
+ num_repeated = 0;
+ }
+ }
+
+ p = ldap_utf8next(p);
+ }
+
+ free(pwd);
+ p = pwd = NULL;
+
+ /* tally up the number of character categories */
+ if (num_digits > 0) ++num_categories;
+ if (num_uppers > 0) ++num_categories;
+ if (num_lowers > 0) ++num_categories;
+ if (num_specials > 0) ++num_categories;
+ if (num_8bit > 0) ++num_categories;
+
+ /* FIXME: the kerberos plicy schema does not define separated
+ * threshold values, so just treat anything as a category,
+ * we will fix this when we merge with DS policies */
+
+ if (max_repeated > 1) --num_categories;
+
+ if (num_categories < krbPwdMinDiffChars) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Password not complex enough\n");
+ slapi_entry_free(policy);
+ return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_INVALIDPWDSYNTAX;
+ }
+ }
+
+ /* Check password history */
+ ret = slapi_entry_attr_find(data->target,
+ "passwordHistory", &passwordHistory);
+ if (ret == 0) {
+ int ret, hint, count, i, j;
+ const char *pwstr;
+ Slapi_Value **pH;
+ Slapi_Value *pw;
+
+ hint = 0;
+ count = 0;
+ ret = slapi_attr_get_numvalues(passwordHistory, &count);
+ /* check history only if we have one */
+ if (count > 0 && data->pwHistoryLen > 0) {
+ pH = calloc(count + 2, sizeof(Slapi_Value *));
+ if (!pH) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ slapi_entry_free(policy);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ i = 0;
+ hint = slapi_attr_first_value(passwordHistory, &pw);
+ while (hint != -1) {
+ pwstr = slapi_value_get_string(pw);
+ /* if shorter than GENERALIZED_TIME_LENGTH, it
+ * is garbage, we never set timeless entries */
+ if (pwstr &&
+ (strlen(pwstr) > GENERALIZED_TIME_LENGTH)) {
+ pH[i] = pw;
+ i++;
+ }
+ hint = slapi_attr_next_value(passwordHistory, hint, &pw);
+ }
+
+ qsort(pH, i, sizeof(Slapi_Value *), ipapwd_sv_pw_cmp);
+
+ if (i > data->pwHistoryLen) {
+ i = data->pwHistoryLen;
+ pH[i] = NULL;
+ }
+
+ for (j = 0; pH[j]; j++) {
+ pH[j] = ipapwd_strip_pw_date(pH[j]);
+ }
+
+ pw = slapi_value_new_string(data->password);
+ if (!pw) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ slapi_entry_free(policy);
+ free(pH);
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ ret = slapi_pw_find_sv(pH, pw);
+
+ for (j = 0; pH[j]; j++) {
+ slapi_value_free(&pH[j]);
+ }
+ slapi_value_free(&pw);
+ free(pH);
+
+ if (ret == 0) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Password in history\n");
+ slapi_entry_free(policy);
+ return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDINHISTORY;
+ }
+ }
+ }
+
+ /* Calculate max age */
+ tmp = slapi_entry_attr_get_int(policy, "krbMaxPwdLife");
+ if (tmp != 0) {
+ krbMaxPwdLife = tmp;
+ }
+
+ slapi_entry_free(policy);
+
+no_policy:
+
+ /* check min lenght */
+ pwdCharLen = ldap_utf8characters(data->password);
+
+ if (pwdCharLen < krbPwdMinLength) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Password too short "
+ "(%d < %d)\n", pwdCharLen, krbPwdMinLength);
+ return IPAPWD_POLICY_ERROR | LDAP_PWPOLICY_PWDTOOSHORT;
+ }
+
+ if (data->expireTime == 0) {
+ data->expireTime = data->timeNow + krbMaxPwdLife;
+ }
+
+ return IPAPWD_POLICY_OK;
+}
+
+/* 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;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "=> ipapwd_getEntry\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) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ipapwd_getEntry: No such entry-(%s), err (%d)\n",
+ dn, search_result);
+ }
+
+ slapi_sdn_free(&sdn);
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "<= ipapwd_getEntry: %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) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "Error retrieving berval from Slapi_Value\n");
+ goto next;
+ }
+ be = ber_init(cbval);
+ if (!be) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "ber_init() failed!\n");
+ goto next;
+ }
+
+ tag = ber_scanf(be, "{xxt[i]", &tmp, &tkvno);
+ if (tag == LBER_ERROR) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "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;
+ Slapi_Value **svals = NULL;
+ Slapi_Value **pwvals = NULL;
+ struct tm utctime;
+ char timestr[GENERALIZED_TIME_LENGTH+1];
+ krb5_context krbctx;
+ krb5_error_code krberr;
+ char *lm = NULL;
+ char *nt = NULL;
+ int is_smb = 0;
+ Slapi_Value *sambaSamAccount;
+ char *errMesg = NULL;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "=> ipapwd_SetPassword\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);
+
+ ret = ipapwd_gen_hashes(krbcfg, data,
+ data->password,
+ is_krb, is_smb,
+ &svals, &nt, &lm, &errMesg);
+ if (ret) {
+ goto free_and_return;
+ }
+
+ smods = slapi_mods_new();
+
+ if (svals) {
+ slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
+ "krbPrincipalKey", svals);
+
+ /* change Last Password Change field with the current date */
+ if (!gmtime_r(&(data->timeNow), &utctime)) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "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)) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "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) {
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "sambaLMPassword", lm);
+ }
+
+ if (nt) {
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "sambaNTPassword", nt);
+ }
+
+ /* 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 */
+ 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);
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "<= ipapwd_SetPassword: %d\n", ret);
+
+free_and_return:
+ if (lm) slapi_ch_free((void **)&lm);
+ if (nt) slapi_ch_free((void **)&nt);
+ slapi_mods_free(&smods);
+ ipapwd_free_slapi_value_array(&svals);
+ ipapwd_free_slapi_value_array(&pwvals);
+
+ return ret;
+}
+
+Slapi_Value **ipapwd_setPasswordHistory(Slapi_Mods *smods,
+ struct ipapwd_data *data)
+{
+ Slapi_Value **pH = NULL;
+ Slapi_Attr *passwordHistory = NULL;
+ char timestr[GENERALIZED_TIME_LENGTH+1];
+ char *histr, *old_pw;
+ struct tm utctime;
+ int ret, pc;
+
+ old_pw = slapi_entry_attr_get_charptr(data->target, "userPassword");
+ if (!old_pw) {
+ /* no old password to store, just return */
+ return NULL;
+ }
+
+ if (!gmtime_r(&(data->timeNow), &utctime)) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "failed to retrieve current date (buggy gmtime_r ?)\n");
+ return NULL;
+ }
+ strftime(timestr, GENERALIZED_TIME_LENGTH+1, "%Y%m%d%H%M%SZ", &utctime);
+
+ histr = slapi_ch_smprintf("%s%s", timestr, old_pw);
+ if (!histr) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ return NULL;
+ }
+
+ /* retrieve current history */
+ ret = slapi_entry_attr_find(data->target,
+ "passwordHistory", &passwordHistory);
+ if (ret == 0) {
+ int ret, hint, count, i;
+ const char *pwstr;
+ Slapi_Value *pw;
+
+ hint = 0;
+ count = 0;
+ ret = slapi_attr_get_numvalues(passwordHistory, &count);
+ /* if we have one */
+ if (count > 0 && data->pwHistoryLen > 0) {
+ pH = calloc(count + 2, sizeof(Slapi_Value *));
+ if (!pH) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ free(histr);
+ return NULL;
+ }
+
+ i = 0;
+ hint = slapi_attr_first_value(passwordHistory, &pw);
+ while (hint != -1) {
+ pwstr = slapi_value_get_string(pw);
+ /* if shorter than GENERALIZED_TIME_LENGTH, it
+ * is garbage, we never set timeless entries */
+ if (pwstr &&
+ (strlen(pwstr) > GENERALIZED_TIME_LENGTH)) {
+ pH[i] = pw;
+ i++;
+ }
+ hint = slapi_attr_next_value(passwordHistory, hint, &pw);
+ }
+
+ qsort(pH, i, sizeof(Slapi_Value *), ipapwd_sv_pw_cmp);
+
+ if (i >= data->pwHistoryLen) {
+ i = data->pwHistoryLen;
+ pH[i] = NULL;
+ i--;
+ }
+
+ pc = i;
+
+ /* copy only interesting entries */
+ for (i = 0; i < pc; i++) {
+ pH[i] = slapi_value_dup(pH[i]);
+ if (pH[i] == NULL) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ while (i) {
+ i--;
+ slapi_value_free(&pH[i]);
+ }
+ free(pH);
+ free(histr);
+ return NULL;
+ }
+ }
+ }
+ }
+
+ if (pH == NULL) {
+ pH = calloc(2, sizeof(Slapi_Value *));
+ if (!pH) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "ipapwd_checkPassword: Out of Memory\n");
+ free(histr);
+ return NULL;
+ }
+ pc = 0;
+ }
+
+ /* add new history value */
+ pH[pc] = slapi_value_new_string(histr);
+
+ free(histr);
+
+ 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;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "=> ipapwd_apply_mods\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) {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "WARNING: modify error %d on entry '%s'\n", ret, dn);
+ } else {
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret);
+
+ if (ret != LDAP_SUCCESS){
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "WARNING: modify error %d on entry '%s'\n",
+ ret, dn);
+ } else {
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "<= ipapwd_apply_mods: Successful\n");
+ }
+ }
+
+ slapi_pblock_destroy(pb);
+
+ 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;
+};
+