summaryrefslogtreecommitdiffstats
path: root/ipa-server
diff options
context:
space:
mode:
authorSimo Sorce <ssorce@redhat.com>2008-06-09 17:56:11 -0400
committerSimo Sorce <ssorce@redhat.com>2008-08-12 14:48:41 -0400
commit0d6b6fa084830c9c93ee2a53707b6697f6cbcfa9 (patch)
treeabb1c6effebd6ceff0d22db765e3c3095893f7a3 /ipa-server
parent9648da8f5f5e4292451f1ba796c4fd027585fdce (diff)
downloadfreeipa-0d6b6fa084830c9c93ee2a53707b6697f6cbcfa9.tar.gz
freeipa-0d6b6fa084830c9c93ee2a53707b6697f6cbcfa9.tar.xz
freeipa-0d6b6fa084830c9c93ee2a53707b6697f6cbcfa9.zip
Implement password operation checks and key material generation for the
ldap add and modify operation performed on the userPassword attribute. Add helper functions to reduce code duplication. Do not enforce encrypted connections on ldap add/ldap mod for compatibility reasons. (We cannot enforce people not to send the password in the clear anyway, we can only refuse to accept it at the most which does not gain you much if someone then re-send you the same password previously exposed)
Diffstat (limited to 'ipa-server')
-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 419e3e259..1697fb5c8 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;
}