Przeglądaj źródła

Ticket #355 - winsync should not delete entry that appears to be out of scope

https://fedorahosted.org/389/ticket/355
Resolves: Ticket #355
Bug Description: winsync should not delete entry that appears to be out of scope
Reviewed by: nhosoi (Thanks!)
Branch: 389-ds-base-1.2.11
Fix Description: There is a new winsync config attribute - winSyncMoveAction -
this is the action to take on the DS side when the winsync finds an AD entry
that has the same name/uid as a DS entry but the AD entry is out of the scope
of the sync agreement (winsync has to search out of scope/subtree on AD to
support deleted and moved entries).  In earlier versions of DS, these entries
were ignored.  When DS was changed to support entry move/subtree rename, the
winsync code was changed to delete entries moved out of scope.  The new
winSyncMoveAction has 3 values:
none - ignore moved entries (like older versions of DS)
delete - delete DS entries when the AD entry moves out of scope - like current
         versions of DS
unsync - new behavior - if the DS entry is currently synced with the AD entry
         this will cause the DS entry to be "unlinked" from the AD entry so
         that they will no longer be in sync
The default value is "none" because we should not unexpectedly delete DS
entries (principle of least astonishment).
Another problem with winsync is that it allowed you to change the subtree and
domain in the middle of a sync update - this can lead to a great deal of
confusion if suddenly many entries are out of scope.  The fix is to "save"
the changes in the entry, and apply those changes when the update is
complete.
Platforms tested: RHEL6 x86_64
Flag Day: yes - new attribute, schema
Doc impact: yes - new attribute, schema
(cherry picked from commit 3206571b8ac8308482c20c3866f407079479b8e6)
(cherry picked from commit 94f9ceffbc0dba95763b9a27b71955323c58384f)
Rich Megginson 13 lat temu
rodzic
commit
64d7791829

+ 2 - 1
ldap/schema/02common.ldif

@@ -144,6 +144,7 @@ attributeTypes: ( 2.16.840.1.113730.3.1.1004 NAME 'nsds7WindowsDomain' DESC 'Net
 attributeTypes: ( 2.16.840.1.113730.3.1.1005 NAME 'nsds7DirsyncCookie' DESC 'Netscape defined attribute type'  SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
 attributeTypes: ( 2.16.840.1.113730.3.1.1099 NAME 'winSyncInterval' DESC 'Netscape defined attribute type'  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
 attributeTypes: ( 2.16.840.1.113730.3.1.1100 NAME 'oneWaySync' DESC 'Netscape defined attribute type'  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
+attributeTypes: ( 2.16.840.1.113730.3.1.2139 NAME 'winSyncMoveAction' DESC 'Netscape defined attribute type'  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
 attributeTypes: ( 1.3.6.1.1.4 NAME 'vendorName' EQUALITY 1.3.6.1.4.1.1466.109.114.1 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation X-ORIGIN 'RFC 3045' )
 attributeTypes: ( 1.3.6.1.1.5 NAME 'vendorVersion' EQUALITY 1.3.6.1.4.1.1466.109.114.1 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation X-ORIGIN 'RFC 3045' )
 attributeTypes: ( 2.16.840.1.113730.3.1.3023 NAME 'nsViewFilter' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'Netscape Directory Server' )
@@ -178,7 +179,7 @@ objectClasses: ( 2.16.840.1.113730.3.2.99 NAME 'cosSuperDefinition' DESC 'Netsca
 objectClasses: ( 2.16.840.1.113730.3.2.100 NAME 'cosClassicDefinition' DESC 'Netscape defined objectclass' SUP cosSuperDefinition MAY ( cosTemplateDn $ cosspecifier ) X-ORIGIN 'Netscape Directory Server' )
 objectClasses: ( 2.16.840.1.113730.3.2.101 NAME 'cosPointerDefinition' DESC 'Netscape defined objectclass' SUP cosSuperDefinition MAY ( cosTemplateDn ) X-ORIGIN 'Netscape Directory Server' )
 objectClasses: ( 2.16.840.1.113730.3.2.102 NAME 'cosIndirectDefinition' DESC 'Netscape defined objectclass' SUP cosSuperDefinition MAY ( cosIndirectSpecifier ) X-ORIGIN 'Netscape Directory Server' )
-objectClasses: ( 2.16.840.1.113730.3.2.503 NAME 'nsDSWindowsReplicationAgreement' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsDS5ReplicaHost $ nsDS5ReplicaPort $ nsDS5ReplicaTransportInfo $ nsDS5ReplicaBindDN $ nsDS5ReplicaCredentials $ nsDS5ReplicaBindMethod $ nsDS5ReplicaRoot $ nsDS5ReplicatedAttributeList $ nsDS5ReplicaUpdateSchedule $ nsds5BeginReplicaRefresh $ description $ nsds50ruv $ nsruvReplicaLastModified $ nsds5ReplicaTimeout $ nsds5replicaChangesSentSinceStartup $ nsds5replicaLastUpdateEnd $ nsds5replicaLastUpdateStart $ nsds5replicaLastUpdateStatus $ nsds5replicaUpdateInProgress $ nsds5replicaLastInitEnd $ nsds5replicaLastInitStart $ nsds5replicaLastInitStatus $ nsds5debugreplicatimeout $ nsds5replicaBusyWaitTime $ nsds5replicaSessionPauseTime $ nsds7WindowsReplicaSubtree $ nsds7DirectoryReplicaSubtree $ nsds7NewWinUserSyncEnabled $ nsds7NewWinGroupSyncEnabled $ nsds7WindowsDomain $ nsds7DirsyncCookie $ winSyncInterval $ oneWaySync) X-ORIGIN 'Netscape Directory Server' )
+objectClasses: ( 2.16.840.1.113730.3.2.503 NAME 'nsDSWindowsReplicationAgreement' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsDS5ReplicaHost $ nsDS5ReplicaPort $ nsDS5ReplicaTransportInfo $ nsDS5ReplicaBindDN $ nsDS5ReplicaCredentials $ nsDS5ReplicaBindMethod $ nsDS5ReplicaRoot $ nsDS5ReplicatedAttributeList $ nsDS5ReplicaUpdateSchedule $ nsds5BeginReplicaRefresh $ description $ nsds50ruv $ nsruvReplicaLastModified $ nsds5ReplicaTimeout $ nsds5replicaChangesSentSinceStartup $ nsds5replicaLastUpdateEnd $ nsds5replicaLastUpdateStart $ nsds5replicaLastUpdateStatus $ nsds5replicaUpdateInProgress $ nsds5replicaLastInitEnd $ nsds5replicaLastInitStart $ nsds5replicaLastInitStatus $ nsds5debugreplicatimeout $ nsds5replicaBusyWaitTime $ nsds5replicaSessionPauseTime $ nsds7WindowsReplicaSubtree $ nsds7DirectoryReplicaSubtree $ nsds7NewWinUserSyncEnabled $ nsds7NewWinGroupSyncEnabled $ nsds7WindowsDomain $ nsds7DirsyncCookie $ winSyncInterval $ oneWaySync $ winSyncMoveAction) X-ORIGIN 'Netscape Directory Server' )
 objectClasses: ( 2.16.840.1.113730.3.2.128 NAME 'costemplate' DESC 'Netscape defined objectclass' SUP top MAY ( cn $ cospriority ) X-ORIGIN 'Netscape Directory Server' )
 objectClasses: ( 2.16.840.1.113730.3.2.304 NAME 'nsView' DESC 'Netscape defined objectclass' SUP top AUXILIARY MAY ( nsViewFilter $ description ) X-ORIGIN 'Netscape Directory Server' )
 objectClasses: ( 2.16.840.1.113730.3.2.316 NAME 'nsAttributeEncryption' DESC 'Netscape defined objectclass' SUP top MUST ( cn $ nsEncryptionAlgorithm ) X-ORIGIN 'Netscape Directory Server' )

+ 4 - 0
ldap/servers/plugins/replication/repl5.h

@@ -169,6 +169,7 @@ extern const char *type_nsds7DirsyncCookie;
 extern const char *type_nsds7WindowsDomain;
 extern const char *type_winSyncInterval;
 extern const char *type_oneWaySync;
+extern const char *type_winsyncMoveAction;
 
 /* To Allow Consumer Initialisation when adding an agreement - */
 extern const char *type_nsds5BeginReplicaRefresh;
@@ -342,6 +343,7 @@ void agmt_set_last_update_start (Repl_Agmt *ra, time_t start_time);
 void agmt_set_last_update_end (Repl_Agmt *ra, time_t end_time);
 void agmt_set_last_update_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *msg);
 void agmt_set_update_in_progress (Repl_Agmt *ra, PRBool in_progress);
+PRBool agmt_get_update_in_progress (const Repl_Agmt *ra);
 void agmt_set_last_init_start (Repl_Agmt *ra, time_t start_time);
 void agmt_set_last_init_end (Repl_Agmt *ra, time_t end_time);
 void agmt_set_last_init_status (Repl_Agmt *ra, int ldaprc, int replrc, const char *msg);
@@ -367,6 +369,7 @@ void agmt_set_cleanruv_notified_from_entry(Repl_Agmt *ra, Slapi_Entry *e);
 int agmt_set_cleanruv_data(Repl_Agmt *ra, ReplicaId rid, int op);
 int agmt_is_cleanruv_notified(Repl_Agmt *ra, ReplicaId rid);
 int agmt_set_timeout(Repl_Agmt *ra, long timeout);
+void agmt_update_done(Repl_Agmt *ra, int is_total);
 
 typedef struct replica Replica;
 
@@ -679,6 +682,7 @@ int windows_handle_modify_agreement(Repl_Agmt *ra, const char *type, Slapi_Entry
 void windows_agreement_delete(Repl_Agmt *ra);
 Repl_Connection *windows_conn_new(Repl_Agmt *agmt);
 void windows_conn_delete(Repl_Connection *conn);
+void windows_update_done(Repl_Agmt *ra, int is_total);
 
 /* repl_session_plugin.c */
 void repl_session_plugin_init();

+ 26 - 0
ldap/servers/plugins/replication/repl5_agmt.c

@@ -2265,6 +2265,17 @@ agmt_set_update_in_progress (Repl_Agmt *ra, PRBool in_progress)
 	}
 }
 
+PRBool
+agmt_get_update_in_progress (const Repl_Agmt *ra)
+{
+	PR_ASSERT(NULL != ra);
+	if (NULL != ra)
+	{
+		return ra->update_in_progress;
+	}
+	return PR_FALSE;
+}
+
 void
 agmt_inc_last_update_changecount (Repl_Agmt *ra, ReplicaId rid, int skipped)
 {
@@ -2694,3 +2705,18 @@ agmt_set_cleanruv_notified_from_entry(Repl_Agmt *ra, Slapi_Entry *e){
     }
     PR_Unlock(ra->lock);
 }
+
+/* this is called whenever an update (total/incremental)
+   is completed */
+void 
+agmt_update_done(Repl_Agmt *agmt, int is_total)
+{
+    /* we could do a lot of stuff here - consolidate all of the other stuff that gets
+       done at the end of an update - setting status, times, etc.
+       but for now, all we want to do is "flush" any pending changes made
+       during the update into the proper structures so they are in place for the
+       next run
+    */
+    windows_update_done(agmt, is_total);
+}
+

+ 2 - 0
ldap/servers/plugins/replication/repl5_inc_protocol.c

@@ -962,6 +962,7 @@ repl5_inc_run(Private_Repl_Protocol *prp)
               /* richm: We at least need to let monitors know that the protocol has been
                * shutdown - maybe they can figure out why */
               agmt_set_last_update_status(prp->agmt, 0, 0, "Protocol stopped");
+              agmt_update_done(prp->agmt, 0);
               break;
           }
 
@@ -1076,6 +1077,7 @@ repl5_inc_run(Private_Repl_Protocol *prp)
           }
           agmt_set_last_update_end(prp->agmt, current_time());
           agmt_set_update_in_progress(prp->agmt, PR_FALSE);
+          agmt_update_done(prp->agmt, 0);
           /* If timed out, close the connection after released the replica */
           release_replica(prp);
           if (rc == UPDATE_TIMEOUT) {

+ 2 - 0
ldap/servers/plugins/replication/repl5_tot_protocol.c

@@ -475,6 +475,8 @@ repl5_tot_run(Private_Repl_Protocol *prp)
 	slapi_pblock_destroy (pb);
 	agmt_set_last_init_end(prp->agmt, current_time());
 	rc = cb_data.rc;
+	agmt_set_update_in_progress(prp->agmt, PR_FALSE);
+	agmt_update_done(prp->agmt, 1);
 	release_replica(prp);
 	
     if (rc != LDAP_SUCCESS)

+ 1 - 0
ldap/servers/plugins/replication/repl_globals.c

@@ -141,6 +141,7 @@ const char *type_nsds7WindowsDomain = "nsds7WindowsDomain";
 const char *type_nsds7DirsyncCookie = "nsds7DirsyncCookie";
 const char *type_winSyncInterval = "winSyncInterval";
 const char *type_oneWaySync = "oneWaySync";
+const char *type_winsyncMoveAction = "winSyncMoveAction";
 
 /* To Allow Consumer Initialization when adding an agreement - */
 const char *type_nsds5BeginReplicaRefresh = "nsds5BeginReplicaRefresh";

+ 2 - 0
ldap/servers/plugins/replication/windows_inc_protocol.c

@@ -769,6 +769,7 @@ windows_inc_run(Private_Repl_Protocol *prp)
 	    /* richm: We at least need to let monitors know that the protocol has been
 	       shutdown - maybe they can figure out why */
 	    agmt_set_last_update_status(prp->agmt, 0, 0, "Protocol stopped");
+	    agmt_update_done(prp->agmt, 0);
 	    break;
 	  } 
 
@@ -903,6 +904,7 @@ windows_inc_run(Private_Repl_Protocol *prp)
 
 	agmt_set_last_update_end(prp->agmt, current_time());
 	agmt_set_update_in_progress(prp->agmt, PR_FALSE);
+	agmt_update_done(prp->agmt, 0);
 	/* If timed out, close the connection after released the replica */
 	windows_release_replica(prp);
 	if (rc == UPDATE_TIMEOUT) {

+ 131 - 0
ldap/servers/plugins/replication/windows_private.c

@@ -76,6 +76,7 @@ struct windowsprivate {
   void *api_cookie; /* private data used by api callbacks */
   time_t sync_interval; /* how often to run the dirsync search, in seconds */
   int one_way; /* Indicates if this is a one-way agreement and which direction it is */
+  int move_action; /* Indicates what to do with DS entry if AD entry is moved out of scope */
 };
 
 static void windows_private_set_windows_domain(const Repl_Agmt *ra, char *domain);
@@ -93,11 +94,62 @@ true_value_from_string(char *val)
 	}
 }
 
+/* yech - can't declare a constant string array because type_nsds7XX variables
+   are not constant strings - so have to build a lookup table */
+static int
+get_next_disallow_attr_type(int *ii, const char **type)
+{
+	switch (*ii) {
+	case 0: *type = type_nsds7WindowsReplicaArea; break;
+	case 1: *type = type_nsds7DirectoryReplicaArea; break;
+	case 2: *type = type_nsds7WindowsDomain; break;
+	default: *type = NULL; break;
+	}
+
+	if (*type) {
+		(*ii)++;
+		return 1;
+	}
+	return 0;
+}
+
+static int
+check_update_allowed(Repl_Agmt *ra, const char *type, Slapi_Entry *e, int *retval)
+{
+	int rc = 1;
+
+	/* note - it is not an error to defer setting the value in the ra */
+	*retval = 1;
+	if (agmt_get_update_in_progress(ra)) {
+		const char *distype = NULL;
+		int ii = 0;
+		while (get_next_disallow_attr_type(&ii, &distype)) {
+			if (slapi_attr_types_equivalent(type, distype)) {
+				char *tmpstr = slapi_entry_attr_get_charptr(e, type);
+				slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+								"windows_parse_config_entry: setting %s to %s will be "
+								"deferred until current update is completed\n",
+								type, tmpstr);
+				slapi_ch_free_string(&tmpstr);
+				rc = 0;
+				break;
+			}
+		}
+	}
+
+	return rc;
+}
+
 static int
 windows_parse_config_entry(Repl_Agmt *ra, const char *type, Slapi_Entry *e)
 {
 	char *tmpstr = NULL;
 	int retval = 0;
+
+	if (!check_update_allowed(ra, type, e, &retval))
+	{
+		return retval;
+	}
 	
 	if (type == NULL || slapi_attr_types_equivalent(type,type_nsds7WindowsReplicaArea))
 	{
@@ -190,6 +242,32 @@ windows_parse_config_entry(Repl_Agmt *ra, const char *type, Slapi_Entry *e)
 		slapi_ch_free((void**)&tmpstr);
 		retval = 1;
 	}
+	if (type == NULL || slapi_attr_types_equivalent(type,type_winsyncMoveAction))
+	{
+		tmpstr = slapi_entry_attr_get_charptr(e, type_winsyncMoveAction);
+		if (NULL != tmpstr)
+		{
+			if (strcasecmp(tmpstr, "delete") == 0) {
+				windows_private_set_move_action(ra, MOVE_DOES_DELETE);
+			} else if (strcasecmp(tmpstr, "unsync") == 0) {
+				windows_private_set_move_action(ra, MOVE_DOES_UNSYNC);
+			} else if (strcasecmp(tmpstr, "none") == 0) {
+				windows_private_set_move_action(ra, MOVE_DOES_NOTHING);
+			} else {
+				slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+					"Ignoring illegal setting for %s attribute in replication "
+					"agreement \"%s\".  Valid values are \"delete\" or "
+					"\"unsync\".\n", type_winsyncMoveAction, slapi_entry_get_dn(e));
+				windows_private_set_move_action(ra, MOVE_DOES_NOTHING);
+			}
+		}
+		else
+		{
+			windows_private_set_move_action(ra, MOVE_DOES_NOTHING);
+		}
+		slapi_ch_free((void**)&tmpstr);
+		retval = 1;
+	}
 	return retval;
 }
 
@@ -207,6 +285,26 @@ windows_handle_modify_agreement(Repl_Agmt *ra, const char *type, Slapi_Entry *e)
 	}
 }
 
+void 
+windows_update_done(Repl_Agmt *agmt, int is_total)
+{
+	/* "flush" the changes made during the update to the agmt */
+	/* get the agmt entry */
+	Slapi_DN *agmtdn = slapi_sdn_dup(agmt_get_dn_byref(agmt));
+	Slapi_Entry *agmte = NULL;
+	int rc = slapi_search_internal_get_entry(agmtdn, NULL, &agmte,
+											 repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION));
+	if ((rc == 0) && agmte) {
+		int ii = 0;
+		const char *distype = NULL;
+		while (get_next_disallow_attr_type(&ii, &distype)) {
+			windows_handle_modify_agreement(agmt, distype, agmte);
+		}
+	}
+	slapi_entry_free(agmte);
+	slapi_sdn_free(&agmtdn);
+}
+
 void
 windows_init_agreement_from_entry(Repl_Agmt *ra, Slapi_Entry *e)
 {
@@ -1019,6 +1117,39 @@ windows_private_set_sync_interval(Repl_Agmt *ra, char *str)
 	LDAPDebug0Args( LDAP_DEBUG_TRACE, "<= windows_private_set_sync_interval\n" );
 }
 
+int
+windows_private_get_move_action(const Repl_Agmt *ra)
+{
+	Dirsync_Private *dp;
+
+	LDAPDebug0Args( LDAP_DEBUG_TRACE, "=> windows_private_get_move_action\n" );
+
+	PR_ASSERT(ra);
+
+	dp = (Dirsync_Private *) agmt_get_priv(ra);
+	PR_ASSERT (dp);
+
+	LDAPDebug0Args( LDAP_DEBUG_TRACE, "<= windows_private_get_move_action\n" );
+
+	return dp->move_action;	
+}
+
+void
+windows_private_set_move_action(const Repl_Agmt *ra, int value)
+{
+	Dirsync_Private *dp;
+
+	LDAPDebug0Args( LDAP_DEBUG_TRACE, "=> windows_private_set_move_action\n" );
+
+	PR_ASSERT(ra);
+
+	dp = (Dirsync_Private *) agmt_get_priv(ra);
+	PR_ASSERT (dp);
+	dp->move_action = value;
+
+	LDAPDebug0Args( LDAP_DEBUG_TRACE, "<= windows_private_set_move_action\n" );
+}
+
 static PRCallOnceType winsync_callOnce = {0,0};
 
 struct winsync_plugin {

+ 106 - 6
ldap/servers/plugins/replication/windows_protocol_util.c

@@ -4990,6 +4990,85 @@ windows_get_local_entry(const Slapi_DN* local_dn,Slapi_Entry **local_entry)
 	return retval;
 }
 
+static int
+windows_unsync_entry(Private_Repl_Protocol *prp, Slapi_Entry *e)
+{
+	/* remote the ntuser/ntgroup objectclass and all attributes whose
+	   name begins with "nt" - this will effectively cause the entry
+	   to become "unsynced" with the corresponding windows entry */
+	Slapi_Mods *smods = NULL;
+	Slapi_Value *ntu = NULL, *ntg = NULL;
+	Slapi_Value *va[2] = {NULL, NULL};
+	char **syncattrs = NULL;
+	PRUint32 ocflags = SLAPI_OC_FLAG_REQUIRED|SLAPI_OC_FLAG_ALLOWED;
+	Slapi_PBlock *pb = NULL;
+	int ii;
+	int rc = -1;
+
+	smods = slapi_mods_new();
+	ntu = slapi_value_new_string("ntuser");
+	ntg = slapi_value_new_string("ntgroup");
+
+	if (slapi_entry_attr_has_syntax_value(e, "objectclass", ntu)) {
+		syncattrs = slapi_schema_list_objectclass_attributes(slapi_value_get_string(ntu), ocflags);
+		va[0] = ntu;
+	} else if (slapi_entry_attr_has_syntax_value(e, "objectclass", ntg)) {
+		syncattrs = slapi_schema_list_objectclass_attributes(slapi_value_get_string(ntg), ocflags);
+		va[0] = ntg;
+	} else {
+		rc = 0; /* not an error */
+		goto done; /* nothing to see here, move along */
+	}
+	slapi_mods_add_mod_values(smods, LDAP_MOD_DELETE, "objectclass", va);
+	slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+					"%s: windows_unsync_entry: removing objectclass %s from %s\n",
+					agmt_get_long_name(prp->agmt), slapi_value_get_string(va[0]),
+					slapi_entry_get_dn_const(e));
+
+	for (ii = 0; syncattrs && syncattrs[ii]; ++ii) {
+		const char *type = syncattrs[ii];
+		Slapi_Attr *attr = NULL;
+
+		if (!slapi_entry_attr_find(e, type, &attr) && attr) {
+			if (!PL_strncasecmp(type, "nt", 2)) { /* begins with "nt" */
+				slapi_mods_add_mod_values(smods, LDAP_MOD_DELETE, type, NULL);
+				slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+								"%s: windows_unsync_entry: removing attribute %s from %s\n",
+								agmt_get_long_name(prp->agmt), type,
+								slapi_entry_get_dn_const(e));
+			}
+		}
+	}
+
+	pb = slapi_pblock_new();
+	if (!pb) {
+		goto done;
+	}
+	slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+					"%s: windows_unsync_entry: modifying entry %s\n",
+					agmt_get_long_name(prp->agmt), slapi_entry_get_dn_const(e));
+	slapi_modify_internal_set_pb_ext(pb, slapi_entry_get_sdn(e),
+									 slapi_mods_get_ldapmods_byref(smods), NULL, NULL,
+									 repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+	slapi_modify_internal_pb(pb);
+	slapi_pblock_get (pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+	if (rc) {
+		slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+						"%s: windows_unsync_entry: failed to modify entry %s - error %d:%s\n",
+						agmt_get_long_name(prp->agmt), slapi_entry_get_dn_const(e),
+						rc, ldap_err2string(rc));
+	}
+	slapi_pblock_destroy(pb);
+
+done:
+	slapi_ch_array_free(syncattrs);
+	slapi_mods_free(&smods);
+	slapi_value_free(&ntu);
+	slapi_value_free(&ntg);
+
+	return rc;
+}
+
 static int 
 windows_process_dirsync_entry(Private_Repl_Protocol *prp,Slapi_Entry *e, int is_total)
 {
@@ -5124,13 +5203,34 @@ retry:
 				rc = windows_get_local_entry(local_sdn, &local_entry);
 				if ((0 == rc) && local_entry) 
 				{
-					/* Need to delete the local entry since the remote counter
-					 * part is now moved out of scope of the agreement. */
-					/* Since map_Entry_dn_oubound returned local_sdn,
-					 * the entry is either user or group. */
-					rc = windows_delete_local_entry(local_sdn);
-					slapi_entry_free(local_entry);
+					if (windows_private_get_move_action(prp->agmt) == MOVE_DOES_DELETE) {
+						/* Need to delete the local entry since the remote counter
+						 * part is now moved out of scope of the agreement. */
+						/* Since map_Entry_dn_oubound returned local_sdn,
+						 * the entry is either user or group. */
+						slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+										"%s: windows_process_dirsync_entry: deleting out of "
+										"scope entry %s\n", agmt_get_long_name(prp->agmt),
+										slapi_sdn_get_dn(local_sdn));
+						rc = windows_delete_local_entry(local_sdn);
+					} else if (windows_private_get_move_action(prp->agmt) == MOVE_DOES_UNSYNC) {
+						rc = windows_unsync_entry(prp, local_entry);
+					} else {
+						slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+										"%s: windows_process_dirsync_entry: windows "
+										"inbound entry %s has the same name as local "
+										"entry %s but the windows entry is out of the "
+										"scope of the sync subtree [%s] - if you want "
+										"these entries to be in sync, add the ntUser/ntGroup "
+										"objectclass and required attributes to the local "
+										"entry, and move the windows entry into scope\n",
+										agmt_get_long_name(prp->agmt),
+										slapi_entry_get_dn_const(e),
+										slapi_sdn_get_dn(local_sdn),
+										slapi_sdn_get_dn(windows_private_get_windows_subtree(prp->agmt)));
+					}
 				}
+				slapi_entry_free(local_entry);
 				slapi_sdn_free(&local_sdn);
 			}
 		}

+ 6 - 1
ldap/servers/plugins/replication/windows_tot_protocol.c

@@ -151,6 +151,8 @@ windows_tot_run(Private_Repl_Protocol *prp)
 
 	agmt_set_last_init_status(prp->agmt, 0, 0, "Total update in progress");
 
+	agmt_set_update_in_progress(prp->agmt, PR_TRUE);
+
 	slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "Beginning total update of replica "
 		"\"%s\".\n", agmt_get_long_name(prp->agmt));
     
@@ -216,7 +218,6 @@ windows_tot_run(Private_Repl_Protocol *prp)
     server_controls = NULL;
 
     slapi_pblock_destroy (pb);
-	agmt_set_last_init_end(prp->agmt, current_time());
 	rc = cb_data.rc;
 	windows_release_replica(prp);
 		
@@ -249,6 +250,10 @@ windows_tot_run(Private_Repl_Protocol *prp)
 	/* Save the dirsync cookie. */
 	windows_private_save_dirsync_cookie(prp->agmt);
 
+	agmt_set_last_init_end(prp->agmt, current_time());
+	agmt_set_update_in_progress(prp->agmt, PR_FALSE);
+	agmt_update_done(prp->agmt, 1);
+
 	/* call end total update callback */
 	winsync_plugin_call_end_update_cb(prp->agmt,
 									  windows_private_get_directory_subtree(prp->agmt),

+ 10 - 0
ldap/servers/plugins/replication/windowsrepl.h

@@ -86,6 +86,8 @@ time_t windows_private_get_sync_interval(const Repl_Agmt *ra);
 void windows_private_set_sync_interval(Repl_Agmt *ra, char *str);
 PRBool windows_private_get_one_way(const Repl_Agmt *ra);
 void windows_private_set_one_way(const Repl_Agmt *ra, PRBool value);
+int windows_private_get_move_action(const Repl_Agmt *ra);
+void windows_private_set_move_action(const Repl_Agmt *ra, int value);
 
 /* in windows_connection.c */
 ConnResult windows_conn_connect(Repl_Connection *conn);
@@ -141,6 +143,14 @@ int windows_check_user_password(Repl_Connection *conn, Slapi_DN *sdn, char *pass
 #define ONE_WAY_SYNC_FROM_AD 1
 #define ONE_WAY_SYNC_TO_AD 2
 
+/*
+ * Specifies what action for sync to take if it detects an AD entry has
+ * moved out of scope
+ */
+#define MOVE_DOES_NOTHING 0
+#define MOVE_DOES_UNSYNC 1
+#define MOVE_DOES_DELETE 2
+
 /* called for each replication agreement - so the winsync
    plugin can be agreement specific and store agreement
    specific data