Blob Blame Raw
From f1a7002926a9d955a3e19c4fd8ab7438d45e4fdb Mon Sep 17 00:00:00 2001
From: Noriko Hosoi <nhosoi@redhat.com>
Date: Mon, 23 Sep 2013 11:53:38 -0700
Subject: [PATCH 16/28] Ticket #47492 - PassSync removes User must change password flag on the Windows side

Bug description: Windows Sync sends password modify even if it is
from PassSync originated on AD.  The modify updates the pwdLastSet
attribute value to non-zero value.  The value 0 indicates the pass-
word must change at next logon on AD.

Fix description: Before sending the password modify, check whether
the current pwdLastSet value is 0 or not.  If it is 0 (means the
password must change), reset pwdLastSet value to 0 along with the
password modify.  This operation replaces the password on AD, but
the password still must change at next logon.

Note: If "password must change at next logon" on the both DS and AD,
the password needs to be changed by the user on the both servers to
enable it on each.

https://fedorahosted.org/389/ticket/47492

Reviewed by Rich (Thank you!!)
(cherry picked from commit f9d1d9e08225e5885b76cede4da677708892ee7a)
(cherry picked from commit 8d34f77f6d8d3c83dce1f29e6df709df1adef09d)
---
 .../plugins/replication/windows_protocol_util.c    |  139 ++++++++++++++------
 1 files changed, 100 insertions(+), 39 deletions(-)

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