summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c1111
1 files changed, 1018 insertions, 93 deletions
diff --git a/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
index 419e3e25..1697fb5c 100644
--- a/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
+++ b/ipa-server/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c
@@ -155,6 +155,18 @@ Slapi_Mutex *ipa_globals = NULL;
static struct ipapwd_config *ipapwd_config = NULL;
+
+#define IPAPWD_PLUGIN_NAME "ipa-pwd-extop"
+#define IPAPWD_FEATURE_DESC "IPA Password Manager"
+#define IPAPWD_PLUGIN_DESC "IPA Password Extended Operation plugin"
+
+static Slapi_PluginDesc pdesc = {
+ IPAPWD_FEATURE_DESC,
+ "FreeIPA project",
+ "FreeIPA/1.0",
+ IPAPWD_PLUGIN_DESC
+};
+
static void *ipapwd_plugin_id;
#define IPA_CHANGETYPE_NORMAL 0
@@ -163,8 +175,8 @@ static void *ipapwd_plugin_id;
struct ipapwd_data {
Slapi_Entry *target;
- const char *dn;
- const char *password;
+ char *dn;
+ char *password;
time_t timeNow;
time_t lastPwChange;
time_t expireTime;
@@ -741,6 +753,21 @@ enc_error:
return NULL;
}
+static 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);
+}
+
+
struct ntlm_keys {
uint8_t lm[16];
uint8_t nt[16];
@@ -1204,9 +1231,9 @@ static Slapi_Value *ipapwd_strip_pw_date(Slapi_Value *pw)
/* check password strenght and history */
static int ipapwd_CheckPolicy(struct ipapwd_data *data)
{
- const char *krbPrincipalExpiration;
- const char *krbLastPwdChange = NULL;
- const char *krbPasswordExpiration = NULL;
+ char *krbPrincipalExpiration = NULL;
+ char *krbLastPwdChange = NULL;
+ char *krbPasswordExpiration = NULL;
int krbMaxPwdLife = IPAPWD_DEFAULT_PWDLIFE;
int krbPwdMinLength = IPAPWD_DEFAULT_MINLEN;
int krbPwdMinDiffChars = 0;
@@ -1216,7 +1243,7 @@ static int ipapwd_CheckPolicy(struct ipapwd_data *data)
Slapi_Attr *passwordHistory = NULL;
struct tm tm;
int tmp, ret;
- const char *old_pw;
+ char *old_pw;
/* check account is not expired. Ignore unixtime = 0 (Jan 1 1970) */
krbPrincipalExpiration = slapi_entry_attr_get_charptr(data->target, "krbPrincipalExpiration");
@@ -1743,20 +1770,9 @@ static int ipapwd_SetPassword(struct ipapwd_data *data)
free_and_return:
slapi_mods_free(&smods);
+ ipapwd_free_slapi_value_array(&svals);
+ ipapwd_free_slapi_value_array(&pwvals);
- if (svals) {
- for (i = 0; svals[i]; i++) {
- slapi_value_free(&svals[i]);
- }
- free(svals);
- }
-
- if (pwvals) {
- for (i = 0; pwvals[i]; i++) {
- slapi_value_free(&pwvals[i]);
- }
- free(pwvals);
- }
return ret;
}
@@ -2804,63 +2820,81 @@ free_and_error:
return LDAP_OPERATIONS_ERROR;
}
-
-static int ipapwd_extop(Slapi_PBlock *pb)
+static int ipapwd_gen_checks(Slapi_PBlock *pb, char **errMesg,
+ int check_secure_conn)
{
- char *oid = NULL;
- char *errMesg = NULL;
- int ret=0, rc=0, sasl_ssf=0, is_ssl=0;
+ int ret, sasl_ssf, is_ssl;
+ int rc = LDAP_SUCCESS;
- slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_extop\n");
+ slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_gen_checks\n");
#ifdef LDAP_EXTOP_PASSMOD_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) {
- errMesg = "Could not get SASL SSF from connection\n";
- rc = LDAP_OPERATIONS_ERROR;
- slapi_log_error( SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- errMesg );
- goto free_and_return;
- }
+ if (check_secure_conn) {
+ /* 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, "ipa_pwd_extop",
+ "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, "ipa_pwd_extop",
+ "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 (slapi_pblock_get(pb, SLAPI_CONN_IS_SSL_SESSION, &is_ssl) != 0) {
- errMesg = "Could not get IS SSL from connection\n";
- rc = LDAP_OPERATIONS_ERROR;
- slapi_log_error( SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- errMesg );
- goto free_and_return;
- }
+ /* make sure we have the master key */
+ if (NULL == ipapwd_config) {
+ krb5_context krbctx;
+ krb5_error_code krberr;
+
+ krberr = krb5_init_context(&krbctx);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_start",
+ "krb5_init_context failed\n");
+ *errMesg = "Fatal Internal Error";
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ ret = ipapwd_getConfig(krbctx, ipa_realm_dn);
+ if (ret != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
+ "Error Retrieving Master Key");
+ *errMesg = "Fatal Internal Error";
+ rc = LDAP_OPERATIONS_ERROR;
+ krb5_free_context(krbctx);
+ goto done;
+ }
+ krb5_free_context(krbctx);
+ }
+done:
+ return rc;
+}
- if ((is_ssl == 0) && (sasl_ssf <= 1)) {
- errMesg = "Operation requires a secure connection.\n";
- rc = LDAP_CONFIDENTIALITY_REQUIRED;
- goto free_and_return;
- }
-#endif
+static int ipapwd_extop(Slapi_PBlock *pb)
+{
+ char *errMesg = NULL;
+ char *oid = NULL;
+ int rc;
- /* make sure we have the master key */
- if (ipapwd_config == NULL) {
- krb5_context krbctx;
- krb5_error_code krberr;
+ slapi_log_error(SLAPI_LOG_TRACE, "ipa_pwd_extop", "=> ipapwd_extop\n");
- krberr = krb5_init_context(&krbctx);
- if (krberr) {
- slapi_log_error(SLAPI_LOG_FATAL, "ipapwd_start",
- "krb5_init_context failed\n");
- errMesg = "Fatal Internal Error";
- rc = LDAP_OPERATIONS_ERROR;
- goto free_and_return;
- }
- ret = ipapwd_getConfig(krbctx, ipa_realm_dn);
- if (ret != LDAP_SUCCESS) {
- slapi_log_error(SLAPI_LOG_PLUGIN, "ipa_pwd_extop",
- "Error Retrieving Master Key");
- errMesg = "Fatal Internal Error";
- rc = LDAP_OPERATIONS_ERROR;
- goto free_and_return;
- }
- krb5_free_context(krbctx);
+ rc = ipapwd_gen_checks(pb, &errMesg, 1);
+ if (rc) {
+ goto free_and_return;
}
/* Before going any further, we'll make sure that the right extended
@@ -2894,6 +2928,833 @@ free_and_return:
return SLAPI_PLUGIN_EXTENDED_SENT_RESULT;
}
+/*****************************************************************************
+ * pre/post operations to intercept writes to userPassword
+ ****************************************************************************/
+
+#define IPAPWD_OP_NULL 0
+#define IPAPWD_OP_ADD 1
+#define IPAPWD_OP_MOD 2
+struct ipapwd_operation {
+ struct ipapwd_data pwdata;
+ int pwd_op;
+ int is_krb;
+};
+
+/* 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 */
+};
+
+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));
+
+ /* target should never be set, but just in case ... */
+ if (pwdop->pwdata.target)
+ slapi_entry_free(pwdop->pwdata.target);
+ }
+ slapi_ch_free((void **)&pwdop);
+}
+
+static 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;
+}
+
+static int ipapwd_preop_gen_hashes(struct ipapwd_operation *pwdop,
+ char *userpw,
+ int is_krb, int is_smb,
+ Slapi_Value ***svals,
+ char **nthash, char **lmhash)
+{
+ int rc;
+
+ if (is_krb) {
+ krb5_error_code krberr;
+ krb5_context krbctx;
+
+ pwdop->is_krb = 1;
+
+ krberr = krb5_init_context(&krbctx);
+ if (krberr) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "krb5_init_context failed\n");
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ *svals = encrypt_encode_key(krbctx, &pwdop->pwdata);
+
+ /* done with it */
+ krb5_free_context(krbctx);
+
+ if (!*svals) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "key encryption/encoding failed\n");
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ }
+
+ if (is_smb) {
+ char lm[33], nt[33];
+ struct ntlm_keys ntlm;
+ int ntlm_flags = 0;
+ int ret;
+
+ /* TODO: retrieve if we want to store the LM hash or not */
+ ntlm_flags = KTF_LM_HASH | KTF_NT_HASH;
+
+ ret = encode_ntlm_keys(userpw, ntlm_flags, &ntlm);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "Failed to generate NT/LM hashes\n");
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ if (ntlm_flags & KTF_LM_HASH) {
+ hexbuf(lm, ntlm.lm);
+ lm[32] = '\0';
+ *lmhash = slapi_ch_strdup(lm);
+ }
+ if (ntlm_flags & KTF_NT_HASH) {
+ hexbuf(nt, ntlm.nt);
+ nt[32] = '\0';
+ *nthash = slapi_ch_strdup(nt);
+ }
+ }
+
+ rc = LDAP_SUCCESS;
+
+done:
+
+ return rc;
+}
+
+/* PRE ADD 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.
+ * 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)
+{
+ 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;
+ int ret, rc;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME, "=> ipapwd_pre_add\n");
+
+ ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op);
+ if (ret != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "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) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "Strdup failed, Out of memory\n");
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ slapi_ch_free_string(&userpw);
+ userpw = tmp;
+ } else if (slapi_is_encoded(userpw)) {
+
+ slapi_ch_free_string(&userpw);
+
+ /* check if we have access to the unhashed user password */
+ userpw = slapi_entry_attr_get_charptr(e, "unhashed#user#password");
+ if (!userpw) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Pre-Encoded passwords are not valid\n");
+ errMesg = "Pre-Encoded passwords are not valid\n";
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+ }
+ }
+
+ rc = ipapwd_entry_checks(pb, e,
+ &is_root, &is_krb, &is_smb,
+ NULL, SLAPI_ACL_ADD);
+ if (rc) {
+ goto done;
+ }
+
+ rc = ipapwd_gen_checks(pb, &errMesg, 0);
+ if (rc) {
+ 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) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "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 {
+ pwdop->pwdata.changetype = IPA_CHANGETYPE_ADMIN;
+ }
+
+ pwdop->pwdata.dn = slapi_ch_strdup(dn);
+ pwdop->pwdata.timeNow = time(NULL);
+ pwdop->pwdata.target = e;
+
+ ret = ipapwd_CheckPolicy(&pwdop->pwdata);
+ if (ret) {
+ errMesg = "Password Fails to meet minimum strength criteria";
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ if (is_krb || is_smb) {
+
+ Slapi_Value **svals = NULL;
+ char *nt = NULL;
+ char *lm = NULL;
+
+ rc = ipapwd_preop_gen_hashes(pwdop, userpw,
+ is_krb, is_smb,
+ &svals, &nt, &lm);
+ if (rc) {
+ goto done;
+ }
+
+ if (svals) {
+ /* add/replace values in existing entry */
+ ret = slapi_entry_attr_replace_sv(e, "krbPrincipalKey", svals);
+ if (ret) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "failed to set encoded values in entry\n");
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ ipapwd_free_slapi_value_array(&svals);
+ }
+
+ if (lm) {
+ /* set value */
+ slapi_entry_attr_set_charptr(e, "sambaLMPassword", lm);
+ slapi_ch_free_string(&lm);
+ }
+ if (nt) {
+ /* set value */
+ slapi_entry_attr_set_charptr(e, "sambaNTPassword", nt);
+ slapi_ch_free_string(&nt);
+ }
+ }
+
+ /* we do not know if the entry pointer will still be valid after the op
+ * make sure we do not reference it by mistake later on */
+ pwdop->pwdata.target = NULL;
+
+ rc = LDAP_SUCCESS;
+
+done:
+ slapi_ch_free_string(&userpw);
+ if (rc != LDAP_SUCCESS) {
+ slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
+ return -1;
+ }
+ return 0;
+}
+
+/* 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)
+{
+ char *errMesg = NULL;
+ LDAPMod **mods;
+ Slapi_Mod *smod, *tmod;
+ Slapi_Mods *smods;
+ char *userpw = NULL;
+ char *unhashedpw = NULL;
+ char *dn = NULL;
+ Slapi_DN *tmp_dn;
+ struct slapi_entry *e = NULL;
+ int free_entry = 0;
+ struct ipapwd_operation *pwdop = NULL;
+ void *op;
+ int is_repl_op, is_pwd_op, is_root, is_krb, is_smb;
+ int ret, rc;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME, "=> ipapwd_pre_mod\n");
+
+ ret = slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl_op);
+ if (ret != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "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;
+
+ /* 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;
+ tmod = slapi_mod_new();
+ smod = slapi_mods_get_first_smod(smods, tmod);
+ while (smod) {
+ struct berval *bv;
+ const char *type;
+ int mop;
+
+ type = slapi_mod_get_type(smod);
+ if (slapi_attr_types_equivalent(type, SLAPI_USERPWD_ATTR)) {
+ mop = slapi_mod_get_operation(smod);
+ /* check op filtering out LDAP_MOD_BVALUES */
+ switch (mop & 0x0f) {
+ case LDAP_MOD_ADD:
+ case LDAP_MOD_REPLACE:
+ is_pwd_op = 1;
+ default:
+ break;
+ }
+ }
+
+ /* 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 absurd
+ * reason */
+ if (slapi_attr_types_equivalent(type, "unhashed#user#password")) {
+ bv = slapi_mod_get_first_value(smod);
+ if (!bv) {
+ slapi_mod_free(&tmod);
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ slapi_ch_free_string(&unhashedpw);
+ unhashedpw = slapi_ch_malloc(bv->bv_len+1);
+ if (!unhashedpw) {
+ slapi_mod_free(&tmod);
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ memcpy(unhashedpw, bv->bv_val, bv->bv_len);
+ unhashedpw[bv->bv_len] = '\0';
+ }
+ slapi_mod_done(tmod);
+ smod = slapi_mods_get_next_smod(smods, tmod);
+ }
+ slapi_mod_free(&tmod);
+
+ /* If userPassword is not modified we are done here */
+ if (! is_pwd_op) {
+ rc = LDAP_SUCCESS;
+ goto done;
+ }
+
+ /* OK swe 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);
+ */
+ slapi_search_internal_get_entry(tmp_dn, 0, &e, ipapwd_plugin_id);
+ slapi_sdn_free(&tmp_dn);
+ free_entry = 1;
+ }
+
+ rc = ipapwd_entry_checks(pb, e,
+ &is_root, &is_krb, &is_smb,
+ SLAPI_USERPWD_ATTR, SLAPI_ACL_WRITE);
+ if (rc) {
+ goto done;
+ }
+
+ rc = ipapwd_gen_checks(pb, &errMesg, 0);
+ if (rc) {
+ goto done;
+ }
+
+ /* run through the mods again and adjust flags if operations affect them */
+ tmod = slapi_mod_new();
+ smod = slapi_mods_get_first_smod(smods, tmod);
+ while (smod) {
+ struct berval *bv;
+ const char *type;
+ int mop;
+
+ type = slapi_mod_get_type(smod);
+ if (slapi_attr_types_equivalent(type, SLAPI_USERPWD_ATTR)) {
+ mop = slapi_mod_get_operation(smod);
+ /* check op filtering out LDAP_MOD_BVALUES */
+ switch (mop & 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;
+ bv = slapi_mod_get_first_value(smod);
+ if (!bv) {
+ slapi_mod_free(&tmod);
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ slapi_ch_free_string(&userpw);
+ userpw = slapi_ch_malloc(bv->bv_len+1);
+ if (!userpw) {
+ slapi_mod_free(&tmod);
+ 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 */
+ bv = slapi_mod_get_first_value(smod);
+ if (!bv) {
+ is_pwd_op = 0;
+ } else {
+ if (0 == strncmp(userpw, bv->bv_val, bv->bv_len) ||
+ 0 == strncmp(unhashedpw, bv->bv_val, bv->bv_len))
+ is_pwd_op = 0;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (slapi_attr_types_equivalent(type, SLAPI_ATTR_OBJECTCLASS)) {
+ mop = slapi_mod_get_operation(smod);
+ /* check op filtering out LDAP_MOD_BVALUES */
+ switch (mop & 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;
+
+ case LDAP_MOD_ADD:
+ bv = slapi_mod_get_first_value(smod);
+ if (!bv) {
+ slapi_mod_free(&tmod);
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ do {
+ if (0 == strncasecmp("krbPrincipalAux", bv->bv_val, bv->bv_len))
+ is_krb = 1;
+ if (0 == strncasecmp("sambaSamAccount", bv->bv_val, bv->bv_len))
+ is_smb = 1;
+ } while ((bv = slapi_mod_get_next_value(smod)) != NULL);
+
+ break;
+
+ case LDAP_MOD_DELETE:
+ /* can this happen for objectclasses ? */
+ is_krb = 0;
+ is_smb = 0;
+
+ default:
+ break;
+ }
+ }
+
+ slapi_mod_done(tmod);
+ smod = slapi_mods_get_next_smod(smods, tmod);
+ }
+ slapi_mod_free(&tmod);
+
+ /* 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) {
+ if ('{' == userpw[0]) {
+ if (0 == strncasecmp(userpw, "{CLEAR}", strlen("{CLEAR}"))) {
+ unhashedpw = slapi_ch_strdup(&userpw[strlen("{CLEAR}")]);
+ if (NULL == unhashedpw) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "Strdup failed, Out of memory\n");
+ rc = LDAP_OPERATIONS_ERROR;
+ goto done;
+ }
+ slapi_ch_free_string(&userpw);
+
+ } else if (slapi_is_encoded(userpw)) {
+
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "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) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "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_MOD;
+ pwdop->pwdata.password = slapi_ch_strdup(unhashedpw);
+ pwdop->pwdata.changetype = IPA_CHANGETYPE_NORMAL;
+
+ if (is_root) {
+ pwdop->pwdata.changetype = IPA_CHANGETYPE_DSMGR;
+ } else {
+ char *binddn;
+ Slapi_DN *bdn, *tdn;
+
+ /* 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;
+ }
+
+ slapi_sdn_free(&bdn);
+ slapi_sdn_free(&tdn);
+ }
+
+ pwdop->pwdata.dn = slapi_ch_strdup(dn);
+ pwdop->pwdata.timeNow = time(NULL);
+ pwdop->pwdata.target = e;
+
+ ret = ipapwd_CheckPolicy(&pwdop->pwdata);
+ if (ret) {
+ errMesg = "Password Fails to meet minimum strength criteria";
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ if (is_krb || is_smb) {
+
+ Slapi_Value **svals = NULL;
+ char *nt = NULL;
+ char *lm = NULL;
+
+ rc = ipapwd_preop_gen_hashes(pwdop, unhashedpw,
+ is_krb, is_smb,
+ &svals, &nt, &lm);
+ 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) {
+ /* replace value */
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "sambaLMPassword", lm);
+ slapi_ch_free_string(&lm);
+ }
+ if (nt) {
+ /* replace value */
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "sambaNTPassword", nt);
+ slapi_ch_free_string(&nt);
+ }
+ }
+
+ rc = LDAP_SUCCESS;
+
+done:
+ slapi_ch_free_string(&userpw); /* just to be sure */
+ if (e) slapi_entry_free(e); /* this is a copy in this function */
+ if (pwdop) pwdop->pwdata.target = NULL;
+
+ if (rc != LDAP_SUCCESS) {
+ slapi_mods_free(&smods);
+ if (!pwdop) {
+ slapi_ch_free_string(&unhashedpw);
+ slapi_ch_free_string(&dn);
+ }
+ slapi_send_ldap_result(pb, rc, NULL, errMesg, 0, NULL);
+ return -1;
+ }
+
+ /* put back a, possibly modified, set of mods */
+ mods = slapi_mods_get_ldapmods_passout(smods);
+ slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods);
+ slapi_mods_free(&smods);
+
+ return 0;
+}
+
+static int ipapwd_post_op(Slapi_PBlock *pb)
+{
+ char *errMesg = "Internal operations error\n";
+ void *op;
+ struct ipapwd_operation *pwdop = NULL;
+ Slapi_Mods *smods;
+ Slapi_Value **pwvals;
+ struct tm utctime;
+ char timestr[GENERALIZED_TIME_LENGTH+1];
+ int ret;
+
+ slapi_log_error(SLAPI_LOG_TRACE, IPAPWD_PLUGIN_NAME,
+ "=> ipapwd_post_add\n");
+
+ /* time to get the operation handler */
+ ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op);
+ if (ret != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
+ "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) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Internal error, couldn't find pluginextension ?!\n");
+ return 0;
+ }
+
+ /* not interesting */
+ if (IPAPWD_OP_NULL == pwdop->pwd_op)
+ return 0;
+
+ if ( ! (pwdop->is_krb)) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Not a kerberos user, ignore krb attributes\n");
+ return 0;
+ }
+
+ /* prepare changes that can be made only as root */
+ smods = slapi_mods_new();
+
+ /* change Last Password Change field with the current date */
+ if (!gmtime_r(&(pwdop->pwdata.timeNow), &utctime)) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "failed to parse current date (buggy gmtime_r ?)\n");
+ return 0;
+ }
+ 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(&(pwdop->pwdata.expireTime), &utctime)) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "failed to parse expiration date (buggy gmtime_r ?)\n");
+ return 0;
+ }
+ strftime(timestr, GENERALIZED_TIME_LENGTH+1,
+ "%Y%m%d%H%M%SZ", &utctime);
+ slapi_mods_add_string(smods, LDAP_MOD_REPLACE,
+ "krbPasswordExpiration", timestr);
+
+ /* 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) {
+ pwvals = ipapwd_setPasswordHistory(smods, &pwdop->pwdata);
+ if (pwvals) {
+ slapi_mods_add_mod_values(smods, LDAP_MOD_REPLACE,
+ "passwordHistory", pwvals);
+ }
+ }
+
+ ret = ipapwd_apply_mods(pwdop->pwdata.dn, smods);
+ if (ret)
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Failed to set additional password attributes in the post-op!\n");
+
+ slapi_mods_free(&smods);
+ return 0;
+}
+
+/* Copied from ipamo_string2filter()
+ *
+ * ipapwd_string2filter()
+ *
+ * For some reason slapi_str2filter writes to its input
+ * which means you cannot pass in a string constant
+ * so this is a fix up function for that
+ */
+Slapi_Filter *ipapwd_string2filter(char *strfilter)
+{
+ Slapi_Filter *ret = NULL;
+ char *idontbelieveit = slapi_ch_strdup(strfilter);
+
+ ret = slapi_str2filter(idontbelieveit);
+
+ slapi_ch_free_string(&idontbelieveit);
+
+ return ret;
+}
/* Init data structs */
static int ipapwd_start( Slapi_PBlock *pb )
@@ -2967,6 +3828,24 @@ static int ipapwd_start( Slapi_PBlock *pb )
return ret;
}
+
+static int ipapwd_ext_init()
+{
+ 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_oid_list[] = {
EXOP_PASSWD_OID,
KEYTAB_SET_OID,
@@ -2980,33 +3859,79 @@ static char *ipapwd_name_list[] = {
NULL
};
-/* Initialization function */
-int ipapwd_init( Slapi_PBlock *pb )
+/* Init pre ops */
+static int ipapwd_pre_init(Slapi_PBlock *pb)
{
- /* Get the arguments appended to the plugin extendedop directive. The first argument
- * (after the standard arguments for the directive) should contain the OID of the
- * extended operation.
- */
- if ((slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &ipapwd_plugin_id) != 0)
- || (ipapwd_plugin_id == NULL)) {
- slapi_log_error( SLAPI_LOG_PLUGIN, "ipapwd_init", "Could not get identity or identity was NULL\n");
- return( -1 );
- }
+ int ret;
- /* Register the plug-in function as an extended operation
- * plug-in function that handles the operation identified by
- * OID 1.3.6.1.4.1.4203.1.11.1 . Also specify the version of the server
- * plug-in */
- if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01 ) != 0 ||
- slapi_pblock_set( pb, SLAPI_PLUGIN_START_FN, (void *) ipapwd_start ) != 0 ||
- slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_FN, (void *) ipapwd_extop ) != 0 ||
- slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, ipapwd_oid_list ) != 0 ||
- slapi_pblock_set( pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, ipapwd_name_list ) != 0) {
+ ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&pdesc);
+ 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);
- slapi_log_error( SLAPI_LOG_PLUGIN, "ipapwd_init",
- "Failed to set plug-in version, function, and OID.\n" );
- return( -1 );
- }
+ return ret;
+}
- return( 0 );
+/* Init post ops */
+static 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 *)&pdesc);
+ 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;
+}
+
+/* Initialization function */
+int ipapwd_init( Slapi_PBlock *pb )
+{
+ int ret;
+
+ /* Get the arguments appended to the plugin extendedop directive. The first argument
+ * (after the standard arguments for the directive) should contain the OID of the
+ * extended operation. */
+
+ ret = slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &ipapwd_plugin_id);
+ if ((ret != 0) || (NULL == ipapwd_plugin_id)) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, "ipapwd_init",
+ "Could not get identity or identity was NULL\n");
+ return -1;
+ }
+
+ if (ipapwd_ext_init() != 0) {
+ slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME,
+ "Object Extension Operation failed\n");
+ return -1;
+ }
+
+ /* Register the plug-in function as an extended operation
+ * plug-in function that handles the operation identified by
+ * OID 1.3.6.1.4.1.4203.1.11.1 . Also specify the version of the server
+ * plug-in */
+ ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *)ipapwd_start);
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&pdesc);
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, ipapwd_oid_list);
+ if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, ipapwd_name_list);
+ if (!ret) slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)ipapwd_extop);
+ if (ret) {
+ slapi_log_error( SLAPI_LOG_PLUGIN, "ipapwd_init",
+ "Failed to set plug-in version, function, and OID.\n" );
+ return -1;
+ }
+
+ slapi_register_plugin("preoperation", 1,
+ "ipapwd_pre_init", ipapwd_pre_init,
+ "IPA pwd pre ops", NULL,
+ ipapwd_plugin_id);
+
+ slapi_register_plugin("postoperation", 1,
+ "ipapwd_post_init", ipapwd_post_init,
+ "IPA pwd post ops", NULL,
+ ipapwd_plugin_id);
+
+ return 0;
}