Blame SOURCES/0016-Ticket-47492-PassSync-removes-User-must-change-passw.patch

ba46c7
From f1a7002926a9d955a3e19c4fd8ab7438d45e4fdb Mon Sep 17 00:00:00 2001
ba46c7
From: Noriko Hosoi <nhosoi@redhat.com>
ba46c7
Date: Mon, 23 Sep 2013 11:53:38 -0700
ba46c7
Subject: [PATCH 16/28] Ticket #47492 - PassSync removes User must change password flag on the Windows side
ba46c7
ba46c7
Bug description: Windows Sync sends password modify even if it is
ba46c7
from PassSync originated on AD.  The modify updates the pwdLastSet
ba46c7
attribute value to non-zero value.  The value 0 indicates the pass-
ba46c7
word must change at next logon on AD.
ba46c7
ba46c7
Fix description: Before sending the password modify, check whether
ba46c7
the current pwdLastSet value is 0 or not.  If it is 0 (means the
ba46c7
password must change), reset pwdLastSet value to 0 along with the
ba46c7
password modify.  This operation replaces the password on AD, but
ba46c7
the password still must change at next logon.
ba46c7
ba46c7
Note: If "password must change at next logon" on the both DS and AD,
ba46c7
the password needs to be changed by the user on the both servers to
ba46c7
enable it on each.
ba46c7
ba46c7
https://fedorahosted.org/389/ticket/47492
ba46c7
ba46c7
Reviewed by Rich (Thank you!!)
ba46c7
(cherry picked from commit f9d1d9e08225e5885b76cede4da677708892ee7a)
ba46c7
(cherry picked from commit 8d34f77f6d8d3c83dce1f29e6df709df1adef09d)
ba46c7
---
ba46c7
 .../plugins/replication/windows_protocol_util.c    |  139 ++++++++++++++------
ba46c7
 1 files changed, 100 insertions(+), 39 deletions(-)
ba46c7
ba46c7
diff --git a/ldap/servers/plugins/replication/windows_protocol_util.c b/ldap/servers/plugins/replication/windows_protocol_util.c
ba46c7
index 730d9a6..69db5a0 100644
ba46c7
--- a/ldap/servers/plugins/replication/windows_protocol_util.c
ba46c7
+++ b/ldap/servers/plugins/replication/windows_protocol_util.c
ba46c7
@@ -70,7 +70,7 @@ static int windows_get_local_entry(const Slapi_DN* local_dn,Slapi_Entry **local_
ba46c7
 static int windows_get_local_entry_by_uniqueid(Private_Repl_Protocol *prp,const char* uniqueid,Slapi_Entry **local_entry, int is_global);
ba46c7
 static int windows_get_local_tombstone_by_uniqueid(Private_Repl_Protocol *prp,const char* uniqueid,Slapi_Entry **local_entry);
ba46c7
 static int windows_search_local_entry_by_uniqueid(Private_Repl_Protocol *prp, const char *uniqueid, char ** attrs, Slapi_Entry **ret_entry, int tombstone, void * component_identity, int is_global);
ba46c7
-static int map_entry_dn_outbound(Slapi_Entry *e, Slapi_DN **dn, Private_Repl_Protocol *prp, int *missing_entry, int want_guid);
ba46c7
+static int map_entry_dn_outbound(Slapi_Entry *e, Slapi_DN **dn, Private_Repl_Protocol *prp, int *missing_entry, int want_guid, Slapi_Entry **remote_entry);
ba46c7
 static char* extract_ntuserdomainid_from_entry(Slapi_Entry *e);
ba46c7
 static char* extract_container(const Slapi_DN *entry_dn, const Slapi_DN *suffix_dn);
ba46c7
 static int windows_get_remote_entry (Private_Repl_Protocol *prp, const Slapi_DN* remote_dn,Slapi_Entry **remote_entry);
ba46c7
@@ -448,7 +448,7 @@ map_dn_values(Private_Repl_Protocol *prp,Slapi_ValueSet *original_values, Slapi_
ba46c7
 				is_ours = is_subject_of_agreement_local(local_entry,prp->agmt);
ba46c7
 				if (is_ours)
ba46c7
 				{
ba46c7
-					map_entry_dn_outbound(local_entry,&remote_dn,prp,&missing_entry, 0 /* don't want GUID form here */);
ba46c7
+					map_entry_dn_outbound(local_entry,&remote_dn,prp,&missing_entry, 0 /* don't want GUID form here */, NULL);
ba46c7
 					if (remote_dn)
ba46c7
 					{
ba46c7
 						if (!missing_entry)
ba46c7
@@ -768,7 +768,10 @@ to_little_endian_double_bytes(UChar *unicode_password, int32_t unicode_password_
ba46c7
 /* this entry had a password, handle it seperately */
ba46c7
 /* http://support.microsoft.com/?kbid=269190 */
ba46c7
 static int
ba46c7
-send_password_modify(Slapi_DN *sdn, char *password, Private_Repl_Protocol *prp)
ba46c7
+send_password_modify(Slapi_DN *sdn,
ba46c7
+                     char *password,
ba46c7
+                     Private_Repl_Protocol *prp,
ba46c7
+                     Slapi_Entry *remote_entry)
ba46c7
 {
ba46c7
 		ConnResult pw_return = 0;
ba46c7
 
ba46c7
@@ -791,6 +794,35 @@ send_password_modify(Slapi_DN *sdn, char *password, Private_Repl_Protocol *prp)
ba46c7
 
ba46c7
 		} else
ba46c7
 		{
ba46c7
+			Slapi_Attr *attr = NULL;
ba46c7
+			int force_reset_pw = 0;
ba46c7
+			/* 
ba46c7
+			 * If AD entry has password must change flag is set,
ba46c7
+			 * we keep the flag (pwdLastSet == 0).
ba46c7
+			 * msdn.microsoft.com: Windows Dev Centor - Desktop
ba46c7
+			 * To force a user to change their password at next logon,
ba46c7
+			 * set the pwdLastSet attribute to zero (0).
ba46c7
+			 */
ba46c7
+			if (remote_entry &&
ba46c7
+				(0 == slapi_entry_attr_find(remote_entry, "pwdLastSet", &attr)) &&
ba46c7
+				attr) {
ba46c7
+				Slapi_Value *v = NULL;
ba46c7
+				int i = 0;
ba46c7
+				for (i = slapi_attr_first_value(attr, &v);
ba46c7
+					 v && (i != -1);
ba46c7
+					 i = slapi_attr_next_value(attr, i, &v)) {
ba46c7
+					const char *s = slapi_value_get_string(v);
ba46c7
+					if (NULL == s) {
ba46c7
+						continue;
ba46c7
+					}
ba46c7
+					if (0 == strcmp(s, "0")) {
ba46c7
+						slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
ba46c7
+						                "%s: AD entry %s set \"user must change password at next logon\". ",
ba46c7
+						                agmt_get_long_name(prp->agmt), slapi_entry_get_dn(remote_entry));
ba46c7
+						force_reset_pw = 1;
ba46c7
+					}
ba46c7
+				}
ba46c7
+			}
ba46c7
 			/* We will attempt to bind to AD with the new password first. We do
ba46c7
 			 * this to avoid playing a password change that originated from AD
ba46c7
 			 * back to AD.  If we just played the password change back, then
ba46c7
@@ -803,38 +835,53 @@ send_password_modify(Slapi_DN *sdn, char *password, Private_Repl_Protocol *prp)
ba46c7
 				quoted_password = PR_smprintf("\"%s\"",password);
ba46c7
 				if (quoted_password)
ba46c7
 				{
ba46c7
-					LDAPMod *pw_mods[2];
ba46c7
-					LDAPMod pw_mod;
ba46c7
-					struct berval bv = {0};
ba46c7
 					UChar *unicode_password = NULL;
ba46c7
 					int32_t unicode_password_length = 0; /* Length in _characters_ */
ba46c7
 					int32_t buffer_size = 0; /* Size in _characters_ */
ba46c7
 					UErrorCode error = U_ZERO_ERROR;
ba46c7
-					struct berval *bvals[2];
ba46c7
 					/* Need to UNICODE encode the password here */
ba46c7
 					/* It's one of those 'ask me first and I will tell you the buffer size' functions */
ba46c7
 					u_strFromUTF8(NULL, 0, &unicode_password_length, quoted_password, strlen(quoted_password), &error);
ba46c7
 					buffer_size = unicode_password_length;
ba46c7
 					unicode_password = (UChar *)slapi_ch_malloc(unicode_password_length * sizeof(UChar));
ba46c7
 					if (unicode_password) {
ba46c7
+						LDAPMod *pw_mods[3];
ba46c7
+						LDAPMod pw_mod;
ba46c7
+						LDAPMod reset_pw_mod;
ba46c7
+						struct berval bv = {0};
ba46c7
+						struct berval *bvals[2];
ba46c7
+						struct berval reset_bv = {0};
ba46c7
+						struct berval *reset_bvals[2];
ba46c7
 						error = U_ZERO_ERROR;
ba46c7
 						u_strFromUTF8(unicode_password, buffer_size, &unicode_password_length, quoted_password, strlen(quoted_password), &error);
ba46c7
-	
ba46c7
+
ba46c7
 						/* As an extra special twist, we need to send the unicode in little-endian order for AD to be happy */
ba46c7
 						to_little_endian_double_bytes(unicode_password, unicode_password_length);
ba46c7
-	
ba46c7
+
ba46c7
 						bv.bv_len = unicode_password_length * sizeof(UChar);
ba46c7
 						bv.bv_val = (char*)unicode_password;
ba46c7
-				
ba46c7
+
ba46c7
 						bvals[0] = &bv; 
ba46c7
 						bvals[1] = NULL;
ba46c7
 						
ba46c7
 						pw_mod.mod_type = "UnicodePwd";
ba46c7
 						pw_mod.mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES;
ba46c7
 						pw_mod.mod_bvalues = bvals;
ba46c7
-					
ba46c7
+
ba46c7
 						pw_mods[0] = &pw_mod;
ba46c7
-						pw_mods[1] = NULL;
ba46c7
+						if (force_reset_pw) {
ba46c7
+							reset_bv.bv_len = 1;
ba46c7
+							reset_bv.bv_val = "0";
ba46c7
+							reset_bvals[0] = &reset_bv; 
ba46c7
+							reset_bvals[1] = NULL;
ba46c7
+							reset_pw_mod.mod_type = "pwdLastSet";
ba46c7
+							reset_pw_mod.mod_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES;
ba46c7
+							reset_pw_mod.mod_bvalues = reset_bvals;
ba46c7
+							pw_mods[1] = &reset_pw_mod;
ba46c7
+							pw_mods[2] = NULL;
ba46c7
+						} else {
ba46c7
+							pw_mods[1] = NULL;
ba46c7
+						}
ba46c7
 
ba46c7
 						pw_return = windows_conn_send_modify(prp->conn, slapi_sdn_get_dn(sdn), pw_mods, NULL, NULL );
ba46c7
 
ba46c7
@@ -1414,6 +1461,7 @@ windows_replay_update(Private_Repl_Protocol *prp, slapi_operation_parameters *op
ba46c7
 	Slapi_DN *remote_dn = NULL;
ba46c7
 	Slapi_DN *local_dn = NULL;
ba46c7
 	Slapi_Entry *local_entry = NULL;
ba46c7
+	Slapi_Entry *remote_entry = NULL;
ba46c7
 		
ba46c7
 	LDAPDebug( LDAP_DEBUG_TRACE, "=> windows_replay_update\n", 0, 0, 0 );
ba46c7
 
ba46c7
@@ -1488,7 +1536,7 @@ windows_replay_update(Private_Repl_Protocol *prp, slapi_operation_parameters *op
ba46c7
 	if (is_ours && (is_user || is_group) ) {
ba46c7
 		int missing_entry = 0;
ba46c7
 		/* Make the entry's DN */
ba46c7
-		rc = map_entry_dn_outbound(local_entry,&remote_dn,prp,&missing_entry, 1);
ba46c7
+		rc = map_entry_dn_outbound(local_entry,&remote_dn,prp,&missing_entry, 1, &remote_entry);
ba46c7
 		if (rc || NULL == remote_dn) 
ba46c7
 		{
ba46c7
 			slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
ba46c7
@@ -1676,13 +1724,17 @@ windows_replay_update(Private_Repl_Protocol *prp, slapi_operation_parameters *op
ba46c7
 			 * seem to work over plain LDAP. */
ba46c7
 			if (is_guid_dn(remote_dn)) {
ba46c7
 				Slapi_DN *remote_dn_norm = NULL;
ba46c7
-				int norm_missing = 0;
ba46c7
 
ba46c7
-				map_entry_dn_outbound(local_entry,&remote_dn_norm,prp,&norm_missing, 0);
ba46c7
-				return_value = send_password_modify(remote_dn_norm, password, prp);
ba46c7
+				if (remote_entry) {
ba46c7
+					remote_dn_norm = slapi_sdn_dup(slapi_entry_get_sdn_const(remote_entry));
ba46c7
+				} else {
ba46c7
+					int norm_missing = 0;
ba46c7
+					map_entry_dn_outbound(local_entry,&remote_dn_norm,prp,&norm_missing, 0, &remote_entry);
ba46c7
+				}
ba46c7
+				return_value = send_password_modify(remote_dn_norm, password, prp, remote_entry);
ba46c7
 				slapi_sdn_free(&remote_dn_norm);
ba46c7
 			} else {
ba46c7
-				return_value = send_password_modify(remote_dn, password, prp);
ba46c7
+				return_value = send_password_modify(remote_dn, password, prp, remote_entry);
ba46c7
 			}
ba46c7
 
ba46c7
 			if (return_value)
ba46c7
@@ -1710,18 +1762,10 @@ windows_replay_update(Private_Repl_Protocol *prp, slapi_operation_parameters *op
ba46c7
 		/* We ignore operations that target entries outside of our sync'ed subtree, or which are not Windows users or groups */
ba46c7
 	}
ba46c7
 error:
ba46c7
-	if (local_entry)
ba46c7
-	{
ba46c7
-		slapi_entry_free(local_entry);
ba46c7
-	}
ba46c7
-	if (local_dn)
ba46c7
-	{
ba46c7
-		slapi_sdn_free (&local_dn);
ba46c7
-	}
ba46c7
-	if (remote_dn)
ba46c7
-	{
ba46c7
-		slapi_sdn_free(&remote_dn);
ba46c7
-	}
ba46c7
+	slapi_entry_free(remote_entry);
ba46c7
+	slapi_entry_free(local_entry);
ba46c7
+	slapi_sdn_free (&local_dn);
ba46c7
+	slapi_sdn_free(&remote_dn);
ba46c7
 	slapi_ch_free_string(&password);
ba46c7
 	return return_value;
ba46c7
 }
ba46c7
@@ -3447,13 +3491,27 @@ extract_container(const Slapi_DN *entry_dn, const Slapi_DN *suffix_dn)
ba46c7
 
ba46c7
 /* Given a non-tombstone entry, return the DN of its peer in AD (whether present or not) */
ba46c7
 static int 
ba46c7
-map_entry_dn_outbound(Slapi_Entry *e, Slapi_DN **dn, Private_Repl_Protocol *prp, int *missing_entry, int guid_form)
ba46c7
+map_entry_dn_outbound(Slapi_Entry *e,
ba46c7
+                      Slapi_DN **dn,
ba46c7
+                      Private_Repl_Protocol *prp,
ba46c7
+                      int *missing_entry,
ba46c7
+                      int guid_form,
ba46c7
+                      Slapi_Entry **remote_entry_to_return)
ba46c7
 {
ba46c7
 	int retval = 0;
ba46c7
 	char *guid = NULL;
ba46c7
 	Slapi_DN *new_dn = NULL;
ba46c7
 	int is_nt4 = windows_private_get_isnt4(prp->agmt);
ba46c7
-	const char *suffix = slapi_sdn_get_dn(windows_private_get_windows_subtree(prp->agmt));
ba46c7
+	const char *suffix = NULL;
ba46c7
+	Slapi_Entry *remote_entry = NULL;
ba46c7
+
ba46c7
+	if (NULL == e) {
ba46c7
+		slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
ba46c7
+		                "%s: map_entry_dn_outbound: NULL entry.\n",
ba46c7
+		                agmt_get_long_name(prp->agmt));
ba46c7
+		return -1;
ba46c7
+	}
ba46c7
+
ba46c7
 	/* To find the DN of the peer entry we first look for an ntUniqueId attribute
ba46c7
 	 * on the local entry. If that's present, we generate a GUID-form DN.
ba46c7
 	 * If there's no GUID, then we look for an ntUserDomainId attribute
ba46c7
@@ -3476,7 +3534,6 @@ map_entry_dn_outbound(Slapi_Entry *e, Slapi_DN **dn, Private_Repl_Protocol *prp,
ba46c7
 	if (guid && guid_form) 
ba46c7
 	{
ba46c7
 		int rc = 0;
ba46c7
-		Slapi_Entry *remote_entry = NULL;
ba46c7
 		new_dn = make_dn_from_guid(guid, is_nt4, suffix);
ba46c7
 		if (!new_dn) {
ba46c7
 			slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
ba46c7
@@ -3504,7 +3561,6 @@ map_entry_dn_outbound(Slapi_Entry *e, Slapi_DN **dn, Private_Repl_Protocol *prp,
ba46c7
 				slapi_sdn_free(&new_dn);
ba46c7
 				retval = -1;
ba46c7
 			}
ba46c7
-			slapi_entry_free(remote_entry);
ba46c7
 		} else {
ba46c7
 			slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
ba46c7
 					"%s: map_entry_dn_outbound: entry not found - rc %d\n",
ba46c7
@@ -3545,7 +3601,6 @@ map_entry_dn_outbound(Slapi_Entry *e, Slapi_DN **dn, Private_Repl_Protocol *prp,
ba46c7
 	} else 
ba46c7
 	{
ba46c7
 		/* No GUID found, try ntUserDomainId */
ba46c7
-		Slapi_Entry *remote_entry = NULL;
ba46c7
 		char *username = slapi_entry_attr_get_charptr(e,"ntUserDomainId");
ba46c7
 		slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
ba46c7
 				"%s: map_entry_dn_outbound: looking for AD entry for DS "
ba46c7
@@ -3621,16 +3676,22 @@ map_entry_dn_outbound(Slapi_Entry *e, Slapi_DN **dn, Private_Repl_Protocol *prp,
ba46c7
 			}
ba46c7
 			slapi_ch_free_string(&username);
ba46c7
 		}
ba46c7
-		if (remote_entry)
ba46c7
-		{
ba46c7
-			slapi_entry_free(remote_entry);
ba46c7
-		}
ba46c7
 	}
ba46c7
 done:
ba46c7
 	if (new_dn) 
ba46c7
 	{
ba46c7
 		*dn = new_dn;
ba46c7
 	}
ba46c7
+	if (remote_entry_to_return) {
ba46c7
+		if (retval) { /* failed */
ba46c7
+			slapi_entry_free(remote_entry);
ba46c7
+			*remote_entry_to_return = NULL;
ba46c7
+		} else {
ba46c7
+			*remote_entry_to_return = remote_entry;
ba46c7
+		}
ba46c7
+	} else {
ba46c7
+		slapi_entry_free(remote_entry);
ba46c7
+	}
ba46c7
 	slapi_ch_free_string(&guid);
ba46c7
 	return retval;
ba46c7
 }
ba46c7
@@ -4963,7 +5024,7 @@ int windows_process_total_entry(Private_Repl_Protocol *prp,Slapi_Entry *e)
ba46c7
 		agmt_get_long_name(prp->agmt), slapi_sdn_get_dn(slapi_entry_get_sdn_const(e)), is_ours ? "ours" : "not ours");
ba46c7
 	if (is_ours) 
ba46c7
 	{
ba46c7
-		retval = map_entry_dn_outbound(e,&remote_dn,prp,&missing_entry,0 /* we don't want the GUID */);
ba46c7
+		retval = map_entry_dn_outbound(e,&remote_dn,prp,&missing_entry,0 /* we don't want the GUID */, NULL);
ba46c7
 		if (retval || NULL == remote_dn) 
ba46c7
 		{
ba46c7
 			slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
ba46c7
-- 
ba46c7
1.7.1
ba46c7