summaryrefslogtreecommitdiffstats
path: root/ldap
diff options
context:
space:
mode:
authorDavid Boreham <dboreham@redhat.com>2005-03-11 02:44:17 +0000
committerDavid Boreham <dboreham@redhat.com>2005-03-11 02:44:17 +0000
commita957eeb8962ee1611b2546fda2bb11a5c909e59b (patch)
treea954c178f40f3531a52b01227c48e7a2df8d8894 /ldap
parent9a7d1e1fd10a644ed17952acd18f755470d4744a (diff)
downloadds-a957eeb8962ee1611b2546fda2bb11a5c909e59b.tar.gz
ds-a957eeb8962ee1611b2546fda2bb11a5c909e59b.tar.xz
ds-a957eeb8962ee1611b2546fda2bb11a5c909e59b.zip
Merge over new code: fractional replication, wan replication and windows sync plus associated UI
Diffstat (limited to 'ldap')
-rw-r--r--ldap/schema/00core.ldif4
-rw-r--r--ldap/servers/plugins/replication/Makefile6
-rw-r--r--ldap/servers/plugins/replication/cl5_api.c70
-rw-r--r--ldap/servers/plugins/replication/cl5_api.h3
-rw-r--r--ldap/servers/plugins/replication/repl.h1
-rw-r--r--ldap/servers/plugins/replication/repl5.h81
-rw-r--r--ldap/servers/plugins/replication/repl5_agmt.c373
-rw-r--r--ldap/servers/plugins/replication/repl5_agmtlist.c37
-rw-r--r--ldap/servers/plugins/replication/repl5_connection.c453
-rw-r--r--ldap/servers/plugins/replication/repl5_inc_protocol.c511
-rw-r--r--ldap/servers/plugins/replication/repl5_init.c1
-rw-r--r--ldap/servers/plugins/replication/repl5_plugins.c7
-rw-r--r--ldap/servers/plugins/replication/repl5_prot_private.h8
-rw-r--r--ldap/servers/plugins/replication/repl5_protocol.c27
-rw-r--r--ldap/servers/plugins/replication/repl5_protocol_util.c447
-rw-r--r--ldap/servers/plugins/replication/repl5_replica.c25
-rw-r--r--ldap/servers/plugins/replication/repl5_ruv.c21
-rw-r--r--ldap/servers/plugins/replication/repl5_tot_protocol.c330
-rw-r--r--ldap/servers/plugins/replication/repl5_total.c38
-rw-r--r--ldap/servers/plugins/replication/repl_extop.c21
-rw-r--r--ldap/servers/plugins/replication/repl_globals.c7
-rw-r--r--ldap/servers/plugins/replication/replutil.c4
-rw-r--r--ldap/servers/plugins/replication/windows_connection.c1503
-rw-r--r--ldap/servers/plugins/replication/windows_inc_protocol.c2609
-rw-r--r--ldap/servers/plugins/replication/windows_private.c245
-rw-r--r--ldap/servers/plugins/replication/windows_prot_private.h70
-rw-r--r--ldap/servers/plugins/replication/windows_protocol_util.c226
-rw-r--r--ldap/servers/plugins/replication/windows_replica.c1213
-rw-r--r--ldap/servers/plugins/replication/windows_tot_protocol.c327
-rw-r--r--ldap/servers/plugins/replication/windows_total.c727
-rw-r--r--ldap/servers/plugins/replication/windowsrepl.h384
-rw-r--r--ldap/servers/slapd/back-ldbm/import-threads.c14
-rw-r--r--ldap/servers/slapd/connection.c94
-rw-r--r--ldap/servers/slapd/libslapd.def1
-rw-r--r--ldap/servers/slapd/operation.c24
-rw-r--r--ldap/servers/slapd/slapi-plugin.h2
-rw-r--r--ldap/synctools/passwordsync/README.txt2
-rw-r--r--ldap/synctools/passwordsync/build.bat27
-rw-r--r--ldap/synctools/passwordsync/passhand.cpp122
-rw-r--r--ldap/synctools/passwordsync/passhand.h40
-rw-r--r--ldap/synctools/passwordsync/passhook/passhook.cpp47
-rw-r--r--ldap/synctools/passwordsync/passhook/passhook.def6
-rw-r--r--ldap/synctools/passwordsync/passhook/passhook.dep9
-rw-r--r--ldap/synctools/passwordsync/passhook/passhook.dsp117
-rw-r--r--ldap/synctools/passwordsync/passhook/passhook.mak207
-rw-r--r--ldap/synctools/passwordsync/passsync.dsw41
-rw-r--r--ldap/synctools/passwordsync/passsync/dssynch.h39
-rw-r--r--ldap/synctools/passwordsync/passsync/dssynchmsg.h604
-rw-r--r--ldap/synctools/passwordsync/passsync/ntservice.cpp576
-rw-r--r--ldap/synctools/passwordsync/passsync/ntservice.h80
-rw-r--r--ldap/synctools/passwordsync/passsync/passsync.dep32
-rw-r--r--ldap/synctools/passwordsync/passsync/passsync.dsp145
-rw-r--r--ldap/synctools/passwordsync/passsync/passsync.mak222
-rw-r--r--ldap/synctools/passwordsync/passsync/service.cpp295
-rw-r--r--ldap/synctools/passwordsync/passsync/subuniutil.cpp59
-rw-r--r--ldap/synctools/passwordsync/passsync/subuniutil.h20
-rw-r--r--ldap/synctools/passwordsync/passsync/synchcmds.h55
-rw-r--r--ldap/synctools/passwordsync/passsync/syncserv.cpp236
-rw-r--r--ldap/synctools/passwordsync/passsync/syncserv.h55
-rw-r--r--ldap/synctools/passwordsync/wix/PassSync.wxs1210
-rw-r--r--ldap/synctools/passwordsync/wix/README.txt2
61 files changed, 13809 insertions, 353 deletions
diff --git a/ldap/schema/00core.ldif b/ldap/schema/00core.ldif
index 6713d92b..4d04fcd7 100644
--- a/ldap/schema/00core.ldif
+++ b/ldap/schema/00core.ldif
@@ -260,6 +260,9 @@ attributeTypes: ( 2.16.840.1.113730.3.1.1097 NAME 'nsds5replicaBusyWaitTime' DES
attributeTypes: ( 2.16.840.1.113730.3.1.1098 NAME 'nsds5replicaSessionPauseTime' DESC 'Netscape defined attribute type' EQUALITY integerMatch 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.9999999 NAME 'nsds5debugreplicatimeout' 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.973 NAME 'nsds5ReplConflict' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE directoryOperation X-ORIGIN 'Netscape Directory Server' )
+attributeTypes: ( 2.16.840.1.113730.3.1.1000 NAME 'nsds7WindowsReplicaSubtree' 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.1001 NAME 'nsds7DirectoryReplicaSubtree' 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.1002 NAME 'nsds7NewWinUserSyncEnabled' 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' )
@@ -328,6 +331,7 @@ objectClasses: ( 2.16.840.1.113730.3.2.100 NAME 'cosClassicDefinition' DESC 'Net
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.103 NAME 'nsDS5ReplicationAgreement' 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 ) 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 ) X-ORIGIN 'Netscape Directory Server' )
objectClasses: ( 2.16.840.1.113730.3.2.104 NAME 'nsContainer' DESC 'Netscape defined objectclass' SUP top MUST ( CN ) X-ORIGIN 'Netscape Directory Server' )
objectClasses: ( 2.16.840.1.113730.3.2.108 NAME 'nsDS5Replica' DESC 'Netscape defined objectclass' SUP top MUST ( nsDS5ReplicaRoot $ nsDS5ReplicaId ) MAY (cn $ nsDS5ReplicaType $ nsDS5ReplicaBindDN $ nsState $ nsDS5ReplicaName $ nsDS5Flags $ nsDS5Task $ nsDS5ReplicaReferral $ nsDS5ReplicaAutoReferral $ nsds5ReplicaPurgeDelay $ nsds5ReplicaTombstonePurgeInterval $ nsds5ReplicaChangeCount $ nsds5ReplicaLegacyConsumer) X-ORIGIN 'Netscape Directory Server' )
objectClasses: ( 2.16.840.1.113730.3.2.109 NAME 'nsBackendInstance' DESC 'Netscape defined objectclass' SUP top MUST ( CN ) X-ORIGIN 'Netscape Directory Server' )
diff --git a/ldap/servers/plugins/replication/Makefile b/ldap/servers/plugins/replication/Makefile
index e39d18d4..439b09e6 100644
--- a/ldap/servers/plugins/replication/Makefile
+++ b/ldap/servers/plugins/replication/Makefile
@@ -90,6 +90,12 @@ LOCAL_OBJS= \
repl5_replica_hash.o\
repl5_replica_dnhash.o\
repl5_updatedn_list.o\
+ windows_inc_protocol.o \
+ windows_tot_protocol.o \
+ windows_total.o \
+ windows_protocol_util.o \
+ windows_private.o \
+ windows_connection.o
LIBREPLICATION_OBJS = $(addprefix $(OBJDEST)/, $(LOCAL_OBJS))
diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c
index 024335f5..4ff55802 100644
--- a/ldap/servers/plugins/replication/cl5_api.c
+++ b/ldap/servers/plugins/replication/cl5_api.c
@@ -303,6 +303,7 @@ static int _cl5ReadBervals (struct berval ***bv, char** buff, unsigned int size)
static int _cl5WriteBervals (struct berval **bv, char** buff, unsigned int *size);
/* replay iteration */
+static PRBool _cl5ValidReplayIterator (const CL5ReplayIterator *iterator);
static int _cl5PositionCursorForReplay (ReplicaId consumerRID, const RUV *consumerRuv,
Object *replica, Object *fileObject, CL5ReplayIterator **iterator);
static int _cl5CheckMissingCSN (const CSN *minCsn, const RUV *supplierRUV, CL5DBFile *file);
@@ -1687,9 +1688,64 @@ int cl5WriteOperation(const char *replName, const char *replGen,
flag is set to FALSE for all repolicas except the last traversed.
*/
+int cl5CreateReplayIteratorEx (Private_Repl_Protocol *prp, const RUV *consumerRuv,
+ CL5ReplayIterator **iterator, ReplicaId consumerRID )
+{
+ int rc;
+ Object *replica;
+ Object *obj = NULL;
+
+ replica = prp->replica_object;
+ if (replica == NULL || consumerRuv == NULL || iterator == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5CreateReplayIterator: invalid parameter\n");
+ return CL5_BAD_DATA;
+ }
+
+ *iterator = NULL;
+
+ if (s_cl5Desc.dbState == CL5_STATE_NONE)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
+ "cl5CreateReplayIterator: changelog is not initialized\n");
+ return CL5_BAD_STATE;
+ }
+
+ /* make sure that changelog is open while operation is in progress */
+ rc = _cl5AddThread ();
+ if (rc != CL5_SUCCESS ) return rc;
+
+
+ rc = _cl5GetDBFile (replica, &obj);
+ if (rc == CL5_SUCCESS)
+ {
+ /* iterate through the ruv in csn order to find first master for which
+ we can replay changes */
+
+ rc = _cl5PositionCursorForReplay (consumerRID, consumerRuv, replica, obj, iterator);
+ if (rc != CL5_SUCCESS)
+ {
+ if (obj)
+ object_release (obj);
+ }
+ }
+
+ _cl5RemoveThread ();
+
+ return rc;
+}
+
+/* cl5CreateReplayIterator is now a wrapper for cl5CreateReplayIteratorEx */
int cl5CreateReplayIterator (Private_Repl_Protocol *prp, const RUV *consumerRuv,
CL5ReplayIterator **iterator)
{
+
+/* DBDB : I thought it should be possible to refactor this like so, but it seems to not work.
+ Possibly the ordering of the calls is significant.
+ ReplicaId consumerRID = agmt_get_consumer_rid ( prp->agmt, prp->conn );
+ return cl5CreateReplayIteratorEx(prp,consumerRuv,iterator,consumerRID); */
+
int rc;
Object *replica;
Object *obj = NULL;
@@ -1733,6 +1789,7 @@ int cl5CreateReplayIterator (Private_Repl_Protocol *prp, const RUV *consumerRuv,
_cl5RemoveThread ();
return rc;
+
}
/* Name: cl5GetNextOperationToReplay
@@ -4733,7 +4790,7 @@ _cl5LDIF2Operation (char *ldifEntry, slapi_operation_parameters *op, char **repl
if (rc != 0)
{
if ( errmsg != NULL ) {
- slapi_log_error(SLAPI_LOG_PARSE, repl_plugin_name_cl, "%s", errmsg);
+ slapi_log_error(SLAPI_LOG_PARSE, repl_plugin_name_cl, "%s", errmsg);
PR_smprintf_free(errmsg );
}
slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name_cl,
@@ -5290,6 +5347,17 @@ PRBool cl5HelperEntry (const char *csnstr, CSN *csnp)
return retval;
}
+/* Replay iteration helper functions */
+static PRBool _cl5ValidReplayIterator (const CL5ReplayIterator *iterator)
+{
+ if (iterator == NULL ||
+ iterator->consumerRuv == NULL || iterator->supplierRuvObj == NULL ||
+ iterator->fileObj == NULL)
+ return PR_FALSE;
+
+ return PR_TRUE;
+}
+
/* Algorithm: ONREPL!!!
*/
struct replica_hash_entry
diff --git a/ldap/servers/plugins/replication/cl5_api.h b/ldap/servers/plugins/replication/cl5_api.h
index fa815261..cf1c78d9 100644
--- a/ldap/servers/plugins/replication/cl5_api.h
+++ b/ldap/servers/plugins/replication/cl5_api.h
@@ -391,6 +391,9 @@ int cl5WriteOperation(const char *repl_name, const char *repl_gen,
*/
int cl5CreateReplayIterator (Private_Repl_Protocol *prp, const RUV *ruv,
CL5ReplayIterator **iterator);
+int cl5CreateReplayIteratorEx (Private_Repl_Protocol *prp, const RUV *consumerRuv,
+ CL5ReplayIterator **iterator, ReplicaId consumerRID );
+
/* Name: cl5GetNextOperationToReplay
Description: retrieves next operation to be sent to the consumer and
diff --git a/ldap/servers/plugins/replication/repl.h b/ldap/servers/plugins/replication/repl.h
index 07d8b1f7..3532346a 100644
--- a/ldap/servers/plugins/replication/repl.h
+++ b/ldap/servers/plugins/replication/repl.h
@@ -238,6 +238,7 @@ void ldapi_initialize_changenumbers(chglog4Info *cl4, changeNumber first, change
#define REPL_PROTOCOL_40 1
#define REPL_PROTOCOL_50_INCREMENTAL 2
#define REPL_PROTOCOL_50_TOTALUPDATE 3
+#define REPL_PROTOCOL_71_TOTALUPDATE 4
/* In repl_globals.c */
int decrement_repl_active_threads();
diff --git a/ldap/servers/plugins/replication/repl5.h b/ldap/servers/plugins/replication/repl5.h
index d899025d..3527f56f 100644
--- a/ldap/servers/plugins/replication/repl5.h
+++ b/ldap/servers/plugins/replication/repl5.h
@@ -23,6 +23,12 @@
#include "repl5_ruv.h"
#include "cl4.h"
+#define REPLICA_TYPE_WINDOWS 1
+#define REPLICA_TYPE_MULTIMASTER 0
+#define REPL_DIRSYNC_CONTROL_OID "1.2.840.113556.1.4.841"
+#define CONN_SUPPORTS_DIRSYNC 12
+#define CONN_DOES_NOT_SUPPORT_DIRSYNC 13
+
/* DS 5.0 replication protocol OIDs */
#define REPL_START_NSDS50_REPLICATION_REQUEST_OID "2.16.840.1.113730.3.5.3"
#define REPL_END_NSDS50_REPLICATION_REQUEST_OID "2.16.840.1.113730.3.5.5"
@@ -31,6 +37,21 @@
#define REPL_NSDS50_UPDATE_INFO_CONTROL_OID "2.16.840.1.113730.3.4.13"
#define REPL_NSDS50_INCREMENTAL_PROTOCOL_OID "2.16.840.1.113730.3.6.1"
#define REPL_NSDS50_TOTAL_PROTOCOL_OID "2.16.840.1.113730.3.6.2"
+/* DS7.1 introduces pipelineing in the protocol : really not much different to the 5.0
+ * protocol, but enough change to make it unsafe to interoperate the two. So we define
+ * new OIDs for 7.1 here. The supplier server looks for these on the consumer and
+ * if they're not there it falls back to the older 5.0 non-pipelined protocol */
+#define REPL_NSDS71_INCREMENTAL_PROTOCOL_OID "2.16.840.1.113730.3.6.4"
+#define REPL_NSDS71_TOTAL_PROTOCOL_OID "2.16.840.1.113730.3.6.3"
+/* The new protocol OIDs above do not help us with determining if a consumer
+ * Supports them or not. That's because they're burried inside the start replication
+ * extended operation, and are not visible in the support controls and operations list
+ * So, we add a new extended operation for the 7.1 total protocol. This is partly because
+ * the total protocol is slightly different (no LDAP_BUSY allowed in 7.1) and partly
+ * because we need a handy way to spot the difference between a pre-7.1 and post-7.0
+ * consumer at the supplier */
+#define REPL_NSDS71_REPLICATION_ENTRY_REQUEST_OID "2.16.840.1.113730.3.5.9"
+
/* DS 5.0 replication protocol error codes */
#define NSDS50_REPL_REPLICA_READY 0x00 /* Replica ready, go ahead */
@@ -84,6 +105,11 @@ extern const char *type_nsds5ReplicaTimeout;
extern const char *type_nsds5ReplicaBusyWaitTime;
extern const char *type_nsds5ReplicaSessionPauseTime;
+/* Attribute names for windows replication agreements */
+extern const char *type_nsds7WindowsReplicaArea;
+extern const char *type_nsds7DirectoryReplicaArea;
+extern const char *type_nsds7CreateNewUsers;
+
/* To Allow Consumer Initialisation when adding an agreement - */
extern const char *type_nsds5BeginReplicaRefresh;
@@ -200,6 +226,7 @@ long agmt_get_timeout(const Repl_Agmt *ra);
long agmt_get_busywaittime(const Repl_Agmt *ra);
long agmt_get_pausetime(const Repl_Agmt *ra);
int agmt_start(Repl_Agmt *ra);
+int windows_agmt_start(Repl_Agmt *ra);
int agmt_stop(Repl_Agmt *ra);
int agmt_replicate_now(Repl_Agmt *ra);
char *agmt_get_hostname(const Repl_Agmt *ra);
@@ -243,6 +270,13 @@ 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);
void agmt_inc_last_update_changecount (Repl_Agmt *ra, ReplicaId rid, int skipped);
void agmt_get_changecount_string (Repl_Agmt *ra, char *buf, int bufsize);
+int agmt_set_replicated_attributes_from_entry(Repl_Agmt *ra, const Slapi_Entry *e);
+int agmt_set_replicated_attributes_from_attr(Repl_Agmt *ra, Slapi_Attr *sattr);
+char **agmt_get_fractional_attrs(const Repl_Agmt *ra);
+char **agmt_validate_replicated_attributes(Repl_Agmt *ra);
+
+
+int get_agmt_agreement_type ( Repl_Agmt *agmt);
typedef struct replica Replica;
@@ -279,34 +313,44 @@ typedef enum
CONN_LOCAL_ERROR,
CONN_BUSY,
CONN_SSL_NOT_ENABLED,
- CONN_TIMEOUT
+ CONN_TIMEOUT,
+ CONN_SUPPORTS_DS71_REPL,
+ CONN_DOES_NOT_SUPPORT_DS71_REPL,
+ CONN_IS_READONLY,
+ CONN_IS_NOT_READONLY
} ConnResult;
Repl_Connection *conn_new(Repl_Agmt *agmt);
ConnResult conn_connect(Repl_Connection *conn);
void conn_disconnect(Repl_Connection *conn);
void conn_delete(Repl_Connection *conn);
void conn_get_error(Repl_Connection *conn, int *operation, int *error);
+void conn_get_error_ex(Repl_Connection *conn, int *operation, int *error, char **error_string);
ConnResult conn_send_add(Repl_Connection *conn, const char *dn, LDAPMod **attrs,
- LDAPControl *update_control, LDAPControl ***returned_controls);
+ LDAPControl *update_control, int *message_id);
ConnResult conn_send_delete(Repl_Connection *conn, const char *dn,
- LDAPControl *update_control, LDAPControl ***returned_controls);
+ LDAPControl *update_control, int *message_id);
ConnResult conn_send_modify(Repl_Connection *conn, const char *dn, LDAPMod **mods,
- LDAPControl *update_control, LDAPControl ***returned_controls);
+ LDAPControl *update_control, int *message_id);
ConnResult conn_send_rename(Repl_Connection *conn, const char *dn,
const char *newrdn, const char *newparent, int deleteoldrdn,
- LDAPControl *update_control, LDAPControl ***returned_controls);
+ LDAPControl *update_control, int *message_id);
ConnResult conn_send_extended_operation(Repl_Connection *conn, const char *extop_oid,
- struct berval *payload, char **retoidp, struct berval **retdatap,
- LDAPControl *update_control, LDAPControl ***returned_controls);
+ struct berval *payload, LDAPControl *update_control, int *message_id);
const char *conn_get_status(Repl_Connection *conn);
void conn_start_linger(Repl_Connection *conn);
void conn_cancel_linger(Repl_Connection *conn);
ConnResult conn_replica_supports_ds5_repl(Repl_Connection *conn);
+ConnResult conn_replica_supports_ds71_repl(Repl_Connection *conn);
+ConnResult conn_replica_is_readonly(Repl_Connection *conn);
+
ConnResult conn_read_entry_attribute(Repl_Connection *conn, const char *dn, char *type,
struct berval ***returned_bvals);
ConnResult conn_push_schema(Repl_Connection *conn, CSN **remotecsn);
void conn_set_timeout(Repl_Connection *conn, long timeout);
+long conn_get_timeout(Repl_Connection *conn);
void conn_set_agmt_changed(Repl_Connection *conn);
+ConnResult conn_read_result(Repl_Connection *conn, int *message_id);
+ConnResult conn_read_result_ex(Repl_Connection *conn, char **retoidp, struct berval **retdatap, LDAPControl ***returned_controls, int *message_id, int noblock);
/* In repl5_protocol.c */
typedef struct repl_protocol Repl_Protocol;
@@ -349,6 +393,7 @@ typedef int (*FNEnumReplica) (Replica *r, void *arg);
/* this function should be called to construct the replica object
from the data already in the DIT */
Replica *replica_new(const Slapi_DN *root);
+Replica *windows_replica_new(const Slapi_DN *root);
/* this function should be called to construct the replica object
during addition of the replica over LDAP */
Replica *replica_new_from_entry (Slapi_Entry *e, char *errortext, PRBool is_add_operation);
@@ -433,6 +478,7 @@ void replica_set_tombstone_reap_stop(Replica *r, PRBool val);
void replica_enable_replication (Replica *r);
void replica_disable_replication (Replica *r, Object *r_obj);
int replica_start_agreement(Replica *r, Repl_Agmt *ra);
+int windows_replica_start_agreement(Replica *r, Repl_Agmt *ra);
CSN* replica_generate_next_csn ( Slapi_PBlock *pb, const CSN *basecsn );
int replica_get_attr ( Slapi_PBlock *pb, const char *type, void *value );
@@ -447,6 +493,7 @@ void multimaster_be_state_change (void *handle, char *be_name, int old_be_state,
/* In repl5_replica_config.c */
int replica_config_init();
void replica_config_destroy ();
+int get_replica_type(Replica *r);
/* replutil.c */
LDAPControl* create_managedsait_control ();
@@ -477,5 +524,23 @@ void replica_updatedn_list_enumerate(ReplicaUpdateDNList list, FNEnumDN fn, void
#endif
void repl5_set_debug_timeout(const char *val);
-
+/* temp hack XXX */
+ReplicaId agmt_get_consumerRID(Repl_Agmt *ra);
+
+/* windows_private.c */
+typedef struct windowsprivate Dirsync_Private;
+void windows_private_delete(Dirsync_Private **dp);
+void* get_priv_from_agmt (const Repl_Agmt *agmt);
+Dirsync_Private* windows_private_new();
+void windows_private_set_windows_replarea (const Repl_Agmt *ra,const Slapi_DN* sdn );
+const Slapi_DN* windows_private_get_windows_replarea (const Repl_Agmt *ra);
+void windows_private_set_directory_replarea (const Repl_Agmt *ra,const Slapi_DN* sdn );
+const Slapi_DN* windows_private_get_directory_replarea (const Repl_Agmt *ra);
+LDAPControl* windows_private_dirsync_control(const Repl_Agmt *ra);
+ConnResult perform_search(Repl_Connection *conn);
+Slapi_Entry *windows_conn_get_search_result(Repl_Connection *conn );
+void windows_private_update_dirsync_control(const Repl_Agmt *ra,LDAPControl **controls );
+void windows_private_null_dirsync_control(const Repl_Agmt *ra);
+void windows_private_set_create_users(const Repl_Agmt *ra, PRBool value);
+PRBool windows_private_create_users(const Repl_Agmt *ra);
#endif /* _REPL5_H_ */
diff --git a/ldap/servers/plugins/replication/repl5_agmt.c b/ldap/servers/plugins/replication/repl5_agmt.c
index dae8da60..a52df287 100644
--- a/ldap/servers/plugins/replication/repl5_agmt.c
+++ b/ldap/servers/plugins/replication/repl5_agmt.c
@@ -45,8 +45,10 @@
*/
#include "repl5.h"
+#include "windowsrepl.h"
#include "repl5_prot_private.h"
#include "cl5_api.h"
+#include "slapi-plugin.h"
#define DEFAULT_TIMEOUT 600 /* (seconds) default outbound LDAP connection */
#define TRANSPORT_FLAG_SSL 1
@@ -96,6 +98,8 @@ typedef struct repl5agmt {
to allow another supplier to send its updates -
should be greater than busywaittime -
if set to 0, this means do not pause */
+ void *priv; /* private data, used for cookie, and windows domain name */
+ int agreement_type;
} repl5agmt;
/* Forward declarations */
@@ -106,6 +110,7 @@ static int get_agmt_status(Slapi_PBlock *pb, Slapi_Entry* e,
static int agmt_set_bind_method_no_lock(Repl_Agmt *ra, const Slapi_Entry *e);
static int agmt_set_transportinfo_no_lock(Repl_Agmt *ra, const Slapi_Entry *e);
+
/*
Schema for replication agreement:
@@ -268,6 +273,10 @@ agmt_new_from_entry(Slapi_Entry *e)
ra->replarea = slapi_sdn_new_dn_passin(tmpstr);
}
/* XXXggood get fractional attribute include/exclude lists here */
+ /* Alrighty Gordon, you get your way... */
+ if (slapi_entry_attr_find(e, type_nsds5ReplicaUpdateSchedule, &sattr) == 0)
+ {
+ }
/* Replication schedule */
ra->schedule = schedule_new(update_window_state_change_callback, ra, agmt_get_long_name(ra));
if (slapi_entry_attr_find(e, type_nsds5ReplicaUpdateSchedule, &sattr) == 0)
@@ -313,6 +322,39 @@ agmt_new_from_entry(Slapi_Entry *e)
ra->long_name = slapi_ch_smprintf("agmt=\"%s\" (%s:%d)", agmtname, hostname, ra->port);
}
+ /* DBDB: review this code */
+ if (slapi_entry_attr_hasvalue(e, "objectclass", "nsDSWindowsReplicationAgreement"))
+ {
+ ra->agreement_type = REPLICA_TYPE_WINDOWS;
+ ra->priv = windows_private_new();
+
+ /* DN of entry at root of replicated area */
+ tmpstr = slapi_entry_attr_get_charptr(e, type_nsds7WindowsReplicaArea);
+ if (NULL != tmpstr)
+ {
+ windows_private_set_windows_replarea(ra, slapi_sdn_new_dn_passin(tmpstr) );
+ }
+
+ tmpstr = slapi_entry_attr_get_charptr(e, type_nsds7DirectoryReplicaArea);
+ if (NULL != tmpstr)
+ {
+ windows_private_set_directory_replarea(ra, slapi_sdn_new_dn_passin(tmpstr) );
+ }
+
+ tmpstr = slapi_entry_attr_get_charptr(e, type_nsds7CreateNewUsers);
+ if (NULL != tmpstr)
+ windows_private_set_create_users(ra, PR_TRUE);
+ else
+ windows_private_set_create_users(ra, PR_FALSE);
+
+ }
+ else
+ {
+ ra->agreement_type = REPLICA_TYPE_MULTIMASTER;
+ }
+
+
+
/* Initialize status information */
ra->last_update_start_time = 0UL;
ra->last_update_end_time = 0UL;
@@ -324,6 +366,30 @@ agmt_new_from_entry(Slapi_Entry *e)
ra->last_init_start_time = 0UL;
ra->last_init_status[0] = '\0';
+ /* Fractional attributes */
+ if (slapi_entry_attr_find(e, type_nsds5ReplicatedAttributeList, &sattr) == 0)
+ {
+ char **denied_attrs = NULL;
+ /* New set of excluded attributes */
+ if (agmt_set_replicated_attributes_from_attr(ra, sattr) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_add_callback: "
+ "failed to parse replicated attributes for agreement %s\n",
+ agmt_get_long_name(ra));
+ }
+ /* Check that there are no verboten attributes in the exclude list */
+ denied_attrs = agmt_validate_replicated_attributes(ra);
+ if (denied_attrs)
+ {
+ /* Report the error to the client */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "WARNING: "
+ "Attempt to exclude illegal attributes from a fractional agreement");
+ /* Free the list */
+ slapi_ch_array_free(denied_attrs);
+ goto loser;
+ }
+ }
+
if (!agmt_is_valid(ra))
{
goto loser;
@@ -413,6 +479,11 @@ agmt_delete(void **rap)
slapi_ch_free((void **)&ra->changecounters[ra->num_changecounters]);
}
+ if (ra->agreement_type == REPLICA_TYPE_WINDOWS)
+ {
+ windows_private_delete(ra->priv);
+ }
+
schedule_destroy(ra->schedule);
slapi_ch_free((void **)&ra->long_name);
slapi_ch_free((void **)rap);
@@ -466,6 +537,55 @@ agmt_start(Repl_Agmt *ra)
return 0;
}
+
+/*
+ * Allow replication for this replica to begin. Replication will
+ * occur at the next scheduled time. Returns 0 on success, -1 on
+ * failure.
+ */
+int
+windows_agmt_start(Repl_Agmt *ra)
+{
+ Repl_Protocol *prot = NULL;
+
+ int protocol_state;
+
+ /* To Allow Consumer Initialisation when adding an agreement: */
+ if (ra->auto_initialize == STATE_PERFORMING_TOTAL_UPDATE)
+ {
+ protocol_state = STATE_PERFORMING_TOTAL_UPDATE;
+ }
+ else
+ {
+ protocol_state = STATE_PERFORMING_INCREMENTAL_UPDATE;
+ }
+
+ /* First, create a new protocol object */
+ if ((prot = prot_new(ra, protocol_state)) == NULL) {
+ return -1;
+ }
+
+ /* Now it is safe to own the agreement lock */
+ PR_Lock(ra->lock);
+
+ /* Check that replication is not already started */
+ if (ra->protocol != NULL) {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "replication already started for agreement \"%s\"\n", agmt_get_long_name(ra));
+ PR_Unlock(ra->lock);
+ prot_free(&prot);
+ return 0;
+ }
+
+ ra->protocol = prot;
+
+ /* Start the protocol thread */
+ prot_start(ra->protocol);
+
+ PR_Unlock(ra->lock);
+ return 0;
+}
+
+
/*
Cease replicating to this replica as soon as possible.
*/
@@ -621,13 +741,33 @@ agmt_is_fractional(const Repl_Agmt *ra)
return return_value;
}
+/* Returns a COPY of the attr list, remember to free it */
+char **
+agmt_get_fractional_attrs(const Repl_Agmt *ra)
+{
+ char ** return_value = NULL;
+ PR_ASSERT(NULL != ra);
+ if (NULL == ra->frac_attrs)
+ {
+ return NULL;
+ }
+ PR_Lock(ra->lock);
+ return_value = charray_dup(ra->frac_attrs);
+ PR_Unlock(ra->lock);
+ return return_value;
+}
int
agmt_is_fractional_attr(const Repl_Agmt *ra, const char *attrname)
{
int return_value;
PR_ASSERT(NULL != ra);
+ if (NULL == ra->frac_attrs)
+ {
+ return 0;
+ }
PR_Lock(ra->lock);
- return_value = 1; /* XXXggood finish this */
+ /* Scan the list looking for a match */
+ return_value = charray_inlist(ra->frac_attrs,(char*)attrname);
PR_Unlock(ra->lock);
return return_value;
}
@@ -833,6 +973,217 @@ agmt_set_binddn_from_entry(Repl_Agmt *ra, const Slapi_Entry *e)
return return_value;
}
+static int
+agmt_parse_excluded_attrs_filter(const char *attr_string, size_t *offset)
+{
+ char *filterstring = "(objectclass=*) ";
+ size_t filterstringlen = strlen(filterstring);
+ int retval = 0;
+
+ if (strncmp(attr_string + *offset,filterstring,filterstringlen) == 0)
+ {
+ (*offset) += filterstringlen;
+ } else
+ {
+ retval = -1;
+ }
+ return retval;
+}
+
+static int
+agmt_parse_excluded_attrs_exclude(const char *attr_string, size_t *offset)
+{
+ char *excludestring = "$ EXCLUDE ";
+ size_t excludestringlen = strlen(excludestring);
+ int retval = 0;
+
+ if (strncmp(attr_string + *offset,excludestring,excludestringlen) == 0)
+ {
+ (*offset) += excludestringlen;
+ } else
+ {
+ retval = -1;
+ }
+ return retval;
+}
+
+static int
+agmt_parse_excluded_attrs_next(const char *attr_string, size_t *offset, char*** attrs)
+{
+ int retval = 0;
+ char *beginstr = ((char*) attr_string) + *offset;
+ char *tmpstr = NULL;
+ size_t stringlen = 0;
+ char c = 0;
+
+ /* Find the end of the current attribute name, if one is present */
+ while (1)
+ {
+ c = *(beginstr + stringlen);
+ if ('\0' == c || ' ' == c)
+ {
+ break;
+ }
+ stringlen++;
+ }
+ if (0 != stringlen)
+ {
+ tmpstr = slapi_ch_malloc(stringlen + 1);
+ strncpy(tmpstr,beginstr,stringlen);
+ tmpstr[stringlen] = '\0';
+ charray_add(attrs,tmpstr);
+ (*offset) += stringlen;
+ /* Skip a delimiting space */
+ if (c == ' ')
+ {
+ (*offset)++;
+ }
+ } else
+ {
+ retval = -1;
+ }
+ return retval;
+}
+ /* It looks like this:
+ nsDS5ReplicatedAttributeList: (objectclass=*) $ EXCLUDE jpegPhoto telephoneNumber
+ */
+static int
+agmt_parse_excluded_attrs_config_attr(const char *attr_string, char*** attrs)
+{
+ int retval = 0;
+ size_t offset = 0;
+ char **new_attrs = NULL;
+
+ *attrs = NULL;
+ /* First parse and skip the filter */
+ retval = agmt_parse_excluded_attrs_filter(attr_string, &offset);
+ if (retval)
+ {
+ goto error;
+ }
+ /* Now look for the 'EXCLUDE' keyword */
+ retval = agmt_parse_excluded_attrs_exclude(attr_string, &offset);
+ if (retval)
+ {
+ goto error;
+ }
+ /* Finally walk the list of attrs, storing in our chararray */
+ while (!retval)
+ {
+ retval = agmt_parse_excluded_attrs_next(attr_string, &offset, &new_attrs);
+ }
+ /* If we got to here, we can't have an error */
+ retval = 0;
+ if (new_attrs)
+ {
+ *attrs = new_attrs;
+ }
+error:
+ return retval;
+}
+
+/*
+ * Set or reset the set of replicated attributes.
+ *
+ * Returns 0 if DN set, or -1 if an error occurred.
+ */
+int
+agmt_set_replicated_attributes_from_entry(Repl_Agmt *ra, const Slapi_Entry *e)
+{
+ Slapi_Attr *sattr = NULL;
+ int return_value = 0;
+
+ PR_ASSERT(NULL != ra);
+ slapi_entry_attr_find(e, type_nsds5ReplicatedAttributeList, &sattr);
+ PR_Lock(ra->lock);
+ if (ra->frac_attrs)
+ {
+ slapi_ch_array_free(ra->frac_attrs);
+ ra->frac_attrs = NULL;
+ }
+ if (NULL != sattr)
+ {
+ Slapi_Value *sval = NULL;
+ slapi_attr_first_value(sattr, &sval);
+ if (NULL != sval)
+ {
+ const char *val = slapi_value_get_string(sval);
+ return_value = agmt_parse_excluded_attrs_config_attr(val,&(ra->frac_attrs));
+ }
+ }
+ PR_Unlock(ra->lock);
+ prot_notify_agmt_changed(ra->protocol, ra->long_name);
+ return return_value;
+}
+
+/*
+ * Set or reset the set of replicated attributes.
+ *
+ * Returns 0 if DN set, or -1 if an error occurred.
+ */
+int
+agmt_set_replicated_attributes_from_attr(Repl_Agmt *ra, Slapi_Attr *sattr)
+{
+ int return_value = 0;
+
+ PR_ASSERT(NULL != ra);
+ PR_Lock(ra->lock);
+ if (ra->frac_attrs)
+ {
+ slapi_ch_array_free(ra->frac_attrs);
+ ra->frac_attrs = NULL;
+ }
+ if (NULL != sattr)
+ {
+ Slapi_Value *sval = NULL;
+ slapi_attr_first_value(sattr, &sval);
+ if (NULL != sval)
+ {
+ const char *val = slapi_value_get_string(sval);
+ return_value = agmt_parse_excluded_attrs_config_attr(val,&(ra->frac_attrs));
+ }
+ }
+ PR_Unlock(ra->lock);
+ return return_value;
+}
+
+char **
+agmt_validate_replicated_attributes(Repl_Agmt *ra)
+{
+
+ static char* verbotten_attrs[] = {
+ "nsuniqueid",
+ "modifiersname",
+ "lastmodifiedtime",
+ NULL
+ };
+
+ char **retval = NULL;
+ char **frac_attrs = ra->frac_attrs;
+
+ /* Iterate over the frac attrs */
+ if (frac_attrs)
+ {
+ char *this_attr = NULL;
+ int i = 0;
+ for (i = 0; this_attr = frac_attrs[i]; i++)
+ {
+ if (charray_inlist(verbotten_attrs,this_attr)) {
+ int k = 0;
+ charray_add(&retval,this_attr);
+ /* Remove this attr from the list */
+ for (k = i; frac_attrs[k] ; k++)
+ {
+ frac_attrs[k] = frac_attrs[k+1];
+ }
+ i--;
+ }
+ }
+ }
+
+ return retval;
+}
+
/*
* Set or reset the bind method used to bind to the remote replica.
*
@@ -1761,3 +2112,23 @@ agmt_get_consumer_rid ( Repl_Agmt *agmt, void *conn )
return agmt->consumerRID;
}
+
+int
+get_agmt_agreement_type( Repl_Agmt *agmt)
+{
+ PR_ASSERT (agmt);
+ return agmt->agreement_type;
+}
+
+void* get_priv_from_agmt (const Repl_Agmt *agmt)
+{
+ PR_ASSERT (agmt);
+ return agmt->priv;
+
+}
+
+ReplicaId agmt_get_consumerRID(Repl_Agmt *ra)
+{
+ return ra->consumerRID;
+}
+
diff --git a/ldap/servers/plugins/replication/repl5_agmtlist.c b/ldap/servers/plugins/replication/repl5_agmtlist.c
index 1c93e14f..178a22b0 100644
--- a/ldap/servers/plugins/replication/repl5_agmtlist.c
+++ b/ldap/servers/plugins/replication/repl5_agmtlist.c
@@ -14,9 +14,12 @@
#define AGMT_CONFIG_BASE "cn=mapping tree, cn=config"
#define CONFIG_FILTER "(objectclass=nsds5replicationagreement)"
+#define WINDOWS_CONFIG_FILTER "(objectclass=nsdsWindowsreplicationagreement)"
+#define GLOBAL_CONFIG_FILTER "(|" CONFIG_FILTER WINDOWS_CONFIG_FILTER " )"
+
PRCallOnceType once = {0};
-static Objset *agmt_set = NULL; /* The set of replication agreements */
+Objset *agmt_set = NULL; /* The set of replication agreements */
typedef struct agmt_wrapper {
Repl_Agmt *agmt;
@@ -102,7 +105,7 @@ agmtlist_release_agmt(Repl_Agmt *ra)
* on to this reference until the agreement is deleted (or until the
* server is shut down).
*/
-static int
+int
add_new_agreement(Slapi_Entry *e)
{
int rc = 0;
@@ -357,6 +360,34 @@ agmtlist_modify_callback(Slapi_PBlock *pb, Slapi_Entry *entryBefore, Slapi_Entry
}
}
else if (slapi_attr_types_equivalent(mods[i]->mod_type,
+ type_nsds5ReplicatedAttributeList))
+ {
+ char **denied_attrs = NULL;
+ /* New set of excluded attributes */
+ if (agmt_set_replicated_attributes_from_entry(agmt, e) != 0)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: "
+ "failed to update replicated attributes for agreement %s\n",
+ agmt_get_long_name(agmt));
+ *returncode = LDAP_OPERATIONS_ERROR;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ }
+ /* Check that there are no verboten attributes in the exclude list */
+ denied_attrs = agmt_validate_replicated_attributes(agmt);
+ if (denied_attrs)
+ {
+ /* Report the error to the client */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "agmtlist_modify_callback: "
+ "attempt to exclude an illegal attribute in a fractional agreement\n");
+
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
+ rc = SLAPI_DSE_CALLBACK_ERROR;
+ /* Free the deny list if we got one */
+ slapi_ch_array_free(denied_attrs);
+ break;
+ }
+ }
+ else if (slapi_attr_types_equivalent(mods[i]->mod_type,
"nsds5debugreplicatimeout"))
{
char *val = slapi_entry_attr_get_charptr(e, "nsds5debugreplicatimeout");
@@ -510,7 +541,7 @@ agmtlist_config_init()
/* Search the DIT and find all the replication agreements */
pb = slapi_pblock_new();
slapi_search_internal_set_pb(pb, AGMT_CONFIG_BASE, LDAP_SCOPE_SUBTREE,
- CONFIG_FILTER, NULL /* attrs */, 0 /* attrsonly */,
+ GLOBAL_CONFIG_FILTER, NULL /* attrs */, 0 /* attrsonly */,
NULL, /* controls */ NULL /* uniqueid */,
repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0 /* actions */);
slapi_search_internal_callback_pb(pb,
diff --git a/ldap/servers/plugins/replication/repl5_connection.c b/ldap/servers/plugins/replication/repl5_connection.c
index eed22750..3c435bf7 100644
--- a/ldap/servers/plugins/replication/repl5_connection.c
+++ b/ldap/servers/plugins/replication/repl5_connection.c
@@ -16,6 +16,7 @@ replica locked. Seems like right thing to do.
#include "repl5.h"
#include "ldappr.h"
+#include "ldap-extension.h"
typedef struct repl_connection
{
@@ -33,6 +34,7 @@ typedef struct repl_connection
int supports_ldapv3; /* 1 if does, 0 if doesn't, -1 if not determined */
int supports_ds50_repl; /* 1 if does, 0 if doesn't, -1 if not determined */
int supports_ds40_repl; /* 1 if does, 0 if doesn't, -1 if not determined */
+ int supports_ds71_repl; /* 1 if does, 0 if doesn't, -1 if not determined */
int linger_time; /* time in seconds to leave an idle connection open */
PRBool linger_active;
Slapi_Eq_Context *linger_event;
@@ -120,6 +122,8 @@ conn_new(Repl_Agmt *agmt)
rpc->supports_ldapv3 = -1;
rpc->supports_ds40_repl = -1;
rpc->supports_ds50_repl = -1;
+ rpc->supports_ds71_repl = -1;
+
rpc->linger_active = PR_FALSE;
rpc->delete_after_linger = PR_FALSE;
rpc->linger_event = NULL;
@@ -216,84 +220,86 @@ conn_get_error(Repl_Connection *conn, int *operation, int *error)
PR_Unlock(conn->lock);
}
-
/*
- * Common code to send an LDAPv3 operation and collect the result.
- * Return values:
- * CONN_OPERATION_SUCCESS - the operation succeeded
- * CONN_OPERATION_FAILED - the operation was sent to the consumer
- * and failed. Use conn_get_error() to determine the LDAP error
- * code.
- * CONN_NOT_CONNECTED - no connection is active. The caller should
- * use conn_connect() to connect to the replica and bind, then should
- * reacquire the replica (if needed).
- * CONN_BUSY - the server is busy with previous requests, must wait for a while
- * before retrying
+ * Return the last operation type processed by the connection
+ * object, and the LDAP error encountered.
+ * Beware that the error string will only be in scope and valid
+ * before the next operation result has been read from the connection
+ * (so don't alias the pointer).
*/
-static ConnResult
-perform_operation(Repl_Connection *conn, int optype, const char *dn,
- LDAPMod **attrs, const char *newrdn, const char *newparent,
- int deleteoldrdn, LDAPControl *update_control,
- const char *extop_oid, struct berval *extop_payload, char **retoidp,
- struct berval **retdatap, LDAPControl ***returned_controls)
+void
+conn_get_error_ex(Repl_Connection *conn, int *operation, int *error, char **error_string)
{
- int rc;
- ConnResult return_value;
- LDAPControl *server_controls[3];
- LDAPControl **loc_returned_controls;
- const char *op_string = NULL;
- const char *extra_op_string = NULL;
-
- server_controls[0] = &manageDSAITControl;
- server_controls[1] = update_control;
- server_controls[2] = NULL;
-
- if (conn_connected(conn))
- {
- int msgid;
+ PR_Lock(conn->lock);
+ *operation = conn->last_operation;
+ *error = conn->last_ldap_error;
+ *error_string = conn->last_ldap_errmsg;
+ PR_Unlock(conn->lock);
+}
- conn->last_operation = optype;
- switch (optype)
- {
- case CONN_ADD:
- conn->status = STATUS_PROCESSING_ADD;
- op_string = "add";
- rc = ldap_add_ext(conn->ld, dn, attrs, server_controls,
- NULL /* clientctls */, &msgid);
- break;
- case CONN_MODIFY:
- conn->status = STATUS_PROCESSING_MODIFY;
- op_string = "modify";
- rc = ldap_modify_ext(conn->ld, dn, attrs, server_controls,
- NULL /* clientctls */, &msgid);
- break;
- case CONN_DELETE:
- conn->status = STATUS_PROCESSING_DELETE;
- op_string = "delete";
- rc = ldap_delete_ext(conn->ld, dn, server_controls,
- NULL /* clientctls */, &msgid);
- break;
- case CONN_RENAME:
- conn->status = STATUS_PROCESSING_RENAME;
- op_string = "rename";
- rc = ldap_rename(conn->ld, dn, newrdn, newparent, deleteoldrdn,
- server_controls, NULL /* clientctls */, &msgid);
- break;
- case CONN_EXTENDED_OPERATION:
- conn->status = STATUS_PROCESSING_EXTENDED_OPERATION;
- op_string = "extended";
- extra_op_string = extop_oid;
- rc = ldap_extended_operation(conn->ld, extop_oid, extop_payload,
- server_controls, NULL /* clientctls */, &msgid);
- }
- if (LDAP_SUCCESS == rc)
- {
+/* Returns the result (asyncronously) from an opertation and also returns that operations message ID */
+/* The _ex version handles a bunch of parameters (retoidp et al) that were present in the original
+ * sync operation functions, but were never actually used) */
+ConnResult
+conn_read_result_ex(Repl_Connection *conn, char **retoidp, struct berval **retdatap, LDAPControl ***returned_controls, int *message_id, int block)
+{
LDAPMessage *res = NULL;
int setlevel = 0;
+ int rc = 0;
+ int return_value = 0;
+ LDAPControl **loc_returned_controls = NULL;
+ struct timeval local_timeout = {0};
+ time_t time_now = 0;
+ time_t start_time = time( NULL );
+ int backoff_time = 1;
Slapi_Eq_Context eqctx = repl5_start_debug_timeout(&setlevel);
- rc = ldap_result(conn->ld, msgid, 1, &conn->timeout, &res);
+ /* Here, we want to not block inside ldap_result().
+ * Reason is that blocking there will deadlock with a
+ * concurrent sender. We send concurrently, and hence
+ * blocking is not good : deadlock results.
+ * So, instead, we call ldap_result() with a zero timeout.
+ * This makes it do a non-blocking poll and return to us
+ * if there's no data to read.
+ * We can then handle our timeout here by sleeping and re-trying.
+ * In order that we do pickup results reasonably quickly,
+ * we implement a backoff algorithm for the sleep: if we
+ * keep getting results quickly then we won't spend much time sleeping.
+ */
+
+ while (1)
+ {
+ rc = ldap_result(conn->ld, LDAP_RES_ANY , 1, &local_timeout, &res);
+ if (0 != rc)
+ {
+ /* Something other than a timeout happened */
+ break;
+ }
+ if (block)
+ {
+ /* Did the connection's timeout expire ? */
+ time_now = time( NULL );
+ if (conn->timeout.tv_sec <= ( time_now - start_time ))
+ {
+ /* We timed out */
+ rc = 0;
+ break;
+ }
+ /* Otherwise we backoff */
+ DS_Sleep(PR_MillisecondsToInterval(backoff_time));
+ if (backoff_time < 1000)
+ {
+ backoff_time <<= 1;
+ }
+ } else
+ {
+ rc = 0;
+ break;
+ }
+ }
+
repl5_stop_debug_timeout(eqctx, &setlevel);
+
if (0 == rc)
{
/* Timeout */
@@ -307,11 +313,7 @@ perform_operation(Repl_Connection *conn, int optype, const char *dn,
char *s = NULL;
rc = ldap_get_lderrno(conn->ld, NULL, &s);
- slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
- "%s: Received error %d: %s for %s operation\n",
- agmt_get_long_name(conn->agmt),
- rc, s ? s : "NULL",
- op_string ? op_string : "NULL");
+ conn->last_ldap_errmsg = s;
conn->last_ldap_error = rc;
/* some errors will require a disconnect and retry the connection
later */
@@ -333,6 +335,11 @@ perform_operation(Repl_Connection *conn, int optype, const char *dn,
char **referrals = NULL;
char *matched = NULL;
+ if (message_id)
+ {
+ *message_id = ldap_msgid(res);
+ }
+
rc = ldap_parse_result(conn->ld, res, &err, &matched,
&errmsg, &referrals, &loc_returned_controls,
0 /* Don't free the result */);
@@ -349,11 +356,12 @@ perform_operation(Repl_Connection *conn, int optype, const char *dn,
return_value = CONN_NOT_CONNECTED;
}
/* Got a result */
- else if (CONN_EXTENDED_OPERATION == optype)
- {
- if ((rc == LDAP_SUCCESS) && (err == LDAP_BUSY))
+ if ((rc == LDAP_SUCCESS) && (err == LDAP_BUSY))
return_value = CONN_BUSY;
- else {
+ else if (retoidp)
+ {
+ if (!((rc == LDAP_SUCCESS) && (err == LDAP_BUSY)))
+ {
if (rc == LDAP_SUCCESS) {
rc = ldap_parse_extended_result(conn->ld, res, retoidp,
retdatap, 0 /* Don't Free it */);
@@ -379,13 +387,6 @@ perform_operation(Repl_Connection *conn, int optype, const char *dn,
}
return_value = LDAP_SUCCESS == conn->last_ldap_error ? CONN_OPERATION_SUCCESS : CONN_OPERATION_FAILED;
}
- slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
- "%s: Received result code %d for %s operation %s%s\n",
- agmt_get_long_name(conn->agmt),
- conn->last_ldap_error,
- op_string == NULL ? "" : op_string,
- extra_op_string == NULL ? "" : extra_op_string,
- extra_op_string == NULL ? "" : " ");
/*
* XXXggood do I need to free matched, referrals,
* anything else? Or can I pass NULL for the args
@@ -398,6 +399,144 @@ perform_operation(Repl_Connection *conn, int optype, const char *dn,
conn->status = STATUS_CONNECTED;
}
if (res) ldap_msgfree(res);
+ return return_value;
+}
+
+ConnResult
+conn_read_result(Repl_Connection *conn, int *message_id)
+{
+ return conn_read_result_ex(conn,NULL,NULL,NULL,message_id,1);
+}
+
+/*
+ * Common code to send an LDAPv3 operation and collect the result.
+ * Return values:
+ * CONN_OPERATION_SUCCESS - the operation succeeded
+ * CONN_OPERATION_FAILED - the operation was sent to the consumer
+ * and failed. Use conn_get_error() to determine the LDAP error
+ * code.
+ * CONN_NOT_CONNECTED - no connection is active. The caller should
+ * use conn_connect() to connect to the replica and bind, then should
+ * reacquire the replica (if needed).
+ * CONN_BUSY - the server is busy with previous requests, must wait for a while
+ * before retrying
+ * DBDB: also returns the operation's message ID, if it was successfully sent, now that
+ * we're reading results async.
+ */
+static ConnResult
+perform_operation(Repl_Connection *conn, int optype, const char *dn,
+ LDAPMod **attrs, const char *newrdn, const char *newparent,
+ int deleteoldrdn, LDAPControl *update_control,
+ const char *extop_oid, struct berval *extop_payload, int *message_id)
+{
+ int rc;
+ ConnResult return_value = CONN_OPERATION_FAILED;
+ LDAPControl *server_controls[3];
+ /* LDAPControl **loc_returned_controls; */
+ const char *op_string = NULL;
+ const char *extra_op_string = NULL;
+ int msgid = 0;
+
+ server_controls[0] = &manageDSAITControl;
+ server_controls[1] = update_control;
+ server_controls[2] = NULL;
+
+ if (conn_connected(conn))
+ {
+ int setlevel = 0;
+
+ Slapi_Eq_Context eqctx = repl5_start_debug_timeout(&setlevel);
+
+ /* Because the SDK isn't really thread-safe (it can deadlock between
+ * a thread sending an operation and a thread trying to retrieve a response
+ * on the same connection), we need to _first_ verify that the connection
+ * is writable. If it isn't, we can deadlock if we proceed any further...
+ */
+ {
+ struct PRPollDesc pr_pd;
+ PRIntervalTime timeout = PR_SecondsToInterval(conn->timeout.tv_sec);
+ PRFileDesc *prfd = NULL;
+ struct lextiof_socket_private *socketargp = NULL;
+ PRLDAPSocketInfo soi;
+
+ if ( (rc = ldap_get_option(conn->ld, LDAP_X_OPT_SOCKETARG, &socketargp)) != LDAP_SUCCESS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Failed call to ldap_get_option in perform_operation: LDAP error %d (%s)\n",
+ agmt_get_long_name(conn->agmt),
+ op_string ? op_string : "NULL", rc, ldap_err2string(rc));
+ conn->last_ldap_error = rc;
+ return CONN_OPERATION_FAILED;
+ }
+ memset( &soi, 0, sizeof(soi));
+ soi.soinfo_size = PRLDAP_SOCKETINFO_SIZE;
+ if (LDAP_SUCCESS != (rc = prldap_get_socket_info(0, socketargp, &soi)))
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Failed call to prldap_get_socket_info in perform_operation: LDAP error %d (%s)\n",
+ agmt_get_long_name(conn->agmt),
+ op_string ? op_string : "NULL", rc, ldap_err2string(rc));
+ conn->last_ldap_error = rc;
+ return CONN_OPERATION_FAILED;
+ }
+ prfd = soi.soinfo_prfd;
+ /* Before we connect, the prfd can be null */
+ if (prfd)
+ {
+ pr_pd.fd = prfd;
+ pr_pd.in_flags = PR_POLL_WRITE;
+ pr_pd.out_flags = 0;
+ rc = PR_Poll(&pr_pd, 1, timeout);
+ /* Did we time out ? */
+ if (rc == 0)
+ {
+ return CONN_TIMEOUT;
+ }
+ }
+ }
+
+ conn->last_operation = optype;
+ switch (optype)
+ {
+ case CONN_ADD:
+ conn->status = STATUS_PROCESSING_ADD;
+ op_string = "add";
+ rc = ldap_add_ext(conn->ld, dn, attrs, server_controls,
+ NULL /* clientctls */, &msgid);
+ break;
+ case CONN_MODIFY:
+ conn->status = STATUS_PROCESSING_MODIFY;
+ op_string = "modify";
+ rc = ldap_modify_ext(conn->ld, dn, attrs, server_controls,
+ NULL /* clientctls */, &msgid);
+ break;
+ case CONN_DELETE:
+ conn->status = STATUS_PROCESSING_DELETE;
+ op_string = "delete";
+ rc = ldap_delete_ext(conn->ld, dn, server_controls,
+ NULL /* clientctls */, &msgid);
+ break;
+ case CONN_RENAME:
+ conn->status = STATUS_PROCESSING_RENAME;
+ op_string = "rename";
+ rc = ldap_rename(conn->ld, dn, newrdn, newparent, deleteoldrdn,
+ server_controls, NULL /* clientctls */, &msgid);
+ break;
+ case CONN_EXTENDED_OPERATION:
+ conn->status = STATUS_PROCESSING_EXTENDED_OPERATION;
+ op_string = "extended";
+ extra_op_string = extop_oid;
+ rc = ldap_extended_operation(conn->ld, extop_oid, extop_payload,
+ server_controls, NULL /* clientctls */, &msgid);
+ }
+ repl5_stop_debug_timeout(eqctx, &setlevel);
+ if (LDAP_SUCCESS == rc)
+ {
+ /* DBDB: The code that used to be here has been moved for async operation
+ * Results are now picked up in another thread. All we need to do here is
+ * queue the operation details in the outstanding operation list.
+ */
+ return_value = CONN_OPERATION_SUCCESS;
}
else
{
@@ -426,6 +565,10 @@ perform_operation(Repl_Connection *conn, int optype, const char *dn,
*/
return_value = CONN_NOT_CONNECTED;
}
+ if (message_id)
+ {
+ *message_id = msgid;
+ }
return return_value;
}
@@ -434,12 +577,11 @@ perform_operation(Repl_Connection *conn, int optype, const char *dn,
*/
ConnResult
conn_send_add(Repl_Connection *conn, const char *dn, LDAPMod **attrs,
- LDAPControl *update_control, LDAPControl ***returned_controls)
+ LDAPControl *update_control, int *message_id)
{
return perform_operation(conn, CONN_ADD, dn, attrs, NULL /* newrdn */,
NULL /* newparent */, 0 /* deleteoldrdn */, update_control,
- NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */,
- NULL /* retdatap */, returned_controls);
+ NULL /* extop OID */, NULL /* extop payload */, message_id);
}
@@ -448,12 +590,11 @@ conn_send_add(Repl_Connection *conn, const char *dn, LDAPMod **attrs,
*/
ConnResult
conn_send_delete(Repl_Connection *conn, const char *dn,
- LDAPControl *update_control, LDAPControl ***returned_controls)
+ LDAPControl *update_control, int *message_id)
{
return perform_operation(conn, CONN_DELETE, dn, NULL /* attrs */,
NULL /* newrdn */, NULL /* newparent */, 0 /* deleteoldrdn */,
- update_control, NULL /* extop OID */, NULL /* extop payload */,
- NULL /* retoidp */, NULL /* retdatap */, returned_controls);
+ update_control, NULL /* extop OID */, NULL /* extop payload */, message_id);
}
@@ -462,12 +603,11 @@ conn_send_delete(Repl_Connection *conn, const char *dn,
*/
ConnResult
conn_send_modify(Repl_Connection *conn, const char *dn, LDAPMod **mods,
- LDAPControl *update_control, LDAPControl ***returned_controls)
+ LDAPControl *update_control, int *message_id)
{
return perform_operation(conn, CONN_MODIFY, dn, mods, NULL /* newrdn */,
NULL /* newparent */, 0 /* deleteoldrdn */, update_control,
- NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */,
- NULL /* retdatap */, returned_controls);
+ NULL /* extop OID */, NULL /* extop payload */, message_id);
}
/*
@@ -476,12 +616,11 @@ conn_send_modify(Repl_Connection *conn, const char *dn, LDAPMod **mods,
ConnResult
conn_send_rename(Repl_Connection *conn, const char *dn,
const char *newrdn, const char *newparent, int deleteoldrdn,
- LDAPControl *update_control, LDAPControl ***returned_controls)
+ LDAPControl *update_control, int *message_id)
{
return perform_operation(conn, CONN_RENAME, dn, NULL /* attrs */,
newrdn, newparent, deleteoldrdn, update_control,
- NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */,
- NULL /* retdatap */, returned_controls);
+ NULL /* extop OID */, NULL /* extop payload */, message_id);
}
@@ -490,13 +629,12 @@ conn_send_rename(Repl_Connection *conn, const char *dn,
*/
ConnResult
conn_send_extended_operation(Repl_Connection *conn, const char *extop_oid,
- struct berval *payload, char **retoidp, struct berval **retdatap,
- LDAPControl *update_control, LDAPControl ***returned_controls)
+ struct berval *payload,
+ LDAPControl *update_control, int *message_id)
{
return perform_operation(conn, CONN_EXTENDED_OPERATION, NULL /* dn */, NULL /* attrs */,
NULL /* newrdn */, NULL /* newparent */, 0 /* deleteoldrdn */,
- update_control, extop_oid, payload, retoidp, retdatap,
- returned_controls);
+ update_control, extop_oid, payload, message_id);
}
@@ -784,8 +922,8 @@ conn_connect(Repl_Connection *conn)
"%s: Trying %s slapi_ldap_init\n",
agmt_get_long_name(conn->agmt),
secure ? "secure" : "non-secure");
-
- conn->ld = slapi_ldap_init(conn->hostname, conn->port, secure, 0);
+ /* shared = 1 because we will read results from a second thread */
+ conn->ld = slapi_ldap_init(conn->hostname, conn->port, secure, 1);
if (NULL == conn->ld)
{
return_value = CONN_OPERATION_FAILED;
@@ -874,6 +1012,7 @@ close_connection_internal(Repl_Connection *conn)
conn->state = STATE_DISCONNECTED;
conn->status = STATUS_DISCONNECTED;
conn->supports_ds50_repl = -1;
+ conn->supports_ds71_repl = -1;
slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
"%s: Disconnected from the consumer\n", agmt_get_long_name(conn->agmt));
}
@@ -975,7 +1114,89 @@ conn_replica_supports_ds5_repl(Repl_Connection *conn)
}
+/*
+ * Determine if the remote replica supports DS 5.0 replication.
+ * Return codes:
+ * CONN_SUPPORTS_DS71_REPL - the remote replica suport DS5 replication
+ * CONN_DOES_NOT_SUPPORT_DS71_REPL - the remote replica does not
+ * support DS5 replication.
+ * CONN_OPERATION_FAILED - it could not be determined if the remote
+ * replica supports DS5 replication.
+ * CONN_NOT_CONNECTED - no connection was active.
+ */
+ConnResult
+conn_replica_supports_ds71_repl(Repl_Connection *conn)
+{
+ ConnResult return_value;
+ int ldap_rc;
+ if (conn_connected(conn))
+ {
+ if (conn->supports_ds71_repl == -1) {
+ LDAPMessage *res = NULL;
+ LDAPMessage *entry = NULL;
+ char *attrs[] = {"supportedcontrol", "supportedextension", NULL};
+
+ conn->status = STATUS_SEARCHING;
+ ldap_rc = ldap_search_ext_s(conn->ld, "", LDAP_SCOPE_BASE,
+ "(objectclass=*)", attrs, 0 /* attrsonly */,
+ NULL /* server controls */, NULL /* client controls */,
+ &conn->timeout, LDAP_NO_LIMIT, &res);
+ if (LDAP_SUCCESS == ldap_rc)
+ {
+ conn->supports_ds71_repl = 0;
+ entry = ldap_first_entry(conn->ld, res);
+ if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_NSDS71_REPLICATION_ENTRY_REQUEST_OID))
+ {
+ return_value = CONN_DOES_NOT_SUPPORT_DS71_REPL;
+ }
+ else
+ {
+ conn->supports_ds71_repl = 1;
+ return_value = CONN_SUPPORTS_DS71_REPL;
+ }
+ }
+ else
+ {
+ if (IS_DISCONNECT_ERROR(ldap_rc))
+ {
+ conn->last_ldap_error = ldap_rc; /* specific reason */
+ conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else
+ {
+ return_value = CONN_OPERATION_FAILED;
+ }
+ }
+ if (NULL != res)
+ ldap_msgfree(res);
+ }
+ else {
+ return_value = conn->supports_ds71_repl ? CONN_SUPPORTS_DS71_REPL : CONN_DOES_NOT_SUPPORT_DS71_REPL;
+ }
+ }
+ else
+ {
+ /* Not connected */
+ return_value = CONN_NOT_CONNECTED;
+ }
+ return return_value;
+}
+
+/* Determine if the replica is read-only */
+ConnResult
+conn_replica_is_readonly(Repl_Connection *conn)
+{
+ ReplicaId rid = agmt_get_consumer_rid( (Repl_Agmt *) conn->agmt, conn );
+ if (rid == READ_ONLY_REPLICA_ID)
+ {
+ return CONN_IS_READONLY;
+ } else
+ {
+ return CONN_IS_NOT_READONLY;
+ }
+}
/*
@@ -1169,7 +1390,8 @@ conn_push_schema(Repl_Connection *conn, CSN **remotecsn)
}
atmod.mod_bvalues[numvalues] = NULL;
- result = conn_send_modify(conn, "cn=schema", attrs, NULL, NULL);
+ result = conn_send_modify(conn, "cn=schema", attrs, NULL, NULL); /* DBDB: this needs to be fixed to use async */
+ result = conn_read_result(conn,NULL);
switch (result)
{
case CONN_OPERATION_FAILED:
@@ -1227,6 +1449,15 @@ conn_set_timeout(Repl_Connection *conn, long timeout)
PR_Unlock(conn->lock);
}
+long
+conn_get_timeout(Repl_Connection *conn)
+{
+ long retval = 0;
+ PR_ASSERT(NULL != conn);
+ retval = conn->timeout.tv_sec;
+ return retval;
+}
+
void conn_set_agmt_changed(Repl_Connection *conn)
{
PR_ASSERT(NULL != conn);
@@ -1430,6 +1661,18 @@ repl5_set_debug_timeout(const char *val)
}
}
+static time_t
+PRTime2time_t (PRTime tm)
+{
+ PRInt64 rt;
+
+ PR_ASSERT (tm);
+
+ LL_DIV(rt, tm, PR_USEC_PER_SEC);
+
+ return (time_t)rt;
+}
+
static Slapi_Eq_Context
repl5_start_debug_timeout(int *setlevel)
{
@@ -1449,7 +1692,7 @@ repl5_stop_debug_timeout(Slapi_Eq_Context eqctx, int *setlevel)
char msg[SLAPI_DSE_RETURNTEXT_SIZE];
if (eqctx && !*setlevel) {
- (void)slapi_eq_cancel(eqctx);
+ int found = slapi_eq_cancel(eqctx);
}
if (s_debug_timeout && s_debug_level && *setlevel) {
diff --git a/ldap/servers/plugins/replication/repl5_inc_protocol.c b/ldap/servers/plugins/replication/repl5_inc_protocol.c
index c5601c67..088c3308 100644
--- a/ldap/servers/plugins/replication/repl5_inc_protocol.c
+++ b/ldap/servers/plugins/replication/repl5_inc_protocol.c
@@ -41,6 +41,32 @@ typedef struct repl5_inc_private
PRUint32 eventbits;
} repl5_inc_private;
+/* Structures used to communicate with the result reading thread */
+
+typedef struct repl5_inc_operation
+{
+ int ldap_message_id;
+ unsigned long operation_type;
+ char *csn_str;
+ char *uniqueid;
+ ReplicaId replica_id;
+ struct repl5_inc_operation *next;
+} repl5_inc_operation;
+
+typedef struct result_data
+{
+ Private_Repl_Protocol *prp;
+ int rc;
+ PRLock *lock; /* Lock to protect access to this structure, the message id list and to force memory barriers */
+ PRThread *result_tid; /* The async result thread */
+ repl5_inc_operation *operation_list_head; /* List of IDs for outstanding operations */
+ repl5_inc_operation *operation_list_tail; /* List of IDs for outstanding operations */
+ int abort; /* Flag used to tell the sending thread asyncronously that it should abort (because an error came up in a result) */
+ PRUint32 num_changes_sent;
+ int stop_result_thread; /* Flag used to tell the result thread to exit */
+ int last_message_id_sent;
+ int last_message_id_received;
+} result_data;
/* Various states the incremental protocol can pass through */
#define STATE_START 0 /* ONREPL - should we rename this - we don't use it just to start up? */
@@ -105,6 +131,319 @@ static PRBool ignore_error_and_keep_going(int error);
static const char* state2name (int state);
static const char* event2name (int event);
static const char* op2string (int op);
+static int repl5_inc_update_from_op_result(Private_Repl_Protocol *prp, ConnResult replay_crc, int connection_error, char *csn_str, char *uniqueid, ReplicaId replica_id, int* finished, PRUint32 *num_changes_sent);
+
+
+/* Push a newly sent operation onto the tail of the list */
+static void repl5_int_push_operation(result_data *rd, repl5_inc_operation *it)
+{
+ repl5_inc_operation *tail = NULL;
+ PR_Lock(rd->lock);
+ tail = rd->operation_list_tail;
+ if (tail)
+ {
+ tail->next = it;
+ }
+ if (NULL == rd->operation_list_head)
+ {
+ rd->operation_list_head = it;
+ }
+ rd->operation_list_tail = it;
+ PR_Unlock(rd->lock);
+}
+
+/* Pop the next operation in line to respond from the list */
+/* The caller is expected to free the operation item */
+static repl5_inc_operation *repl5_inc_pop_operation(result_data *rd)
+{
+ repl5_inc_operation *head = NULL;
+ repl5_inc_operation *ret = NULL;
+ PR_Lock(rd->lock);
+ head = rd->operation_list_head;
+ if (head)
+ {
+ ret = head;
+ rd->operation_list_head = head->next;
+ if (rd->operation_list_tail == head)
+ {
+ rd->operation_list_tail = NULL;
+ }
+ }
+ PR_Unlock(rd->lock);
+ return ret;
+}
+
+static void
+repl5_inc_op_free(repl5_inc_operation *op)
+{
+ /* First free any payload */
+ if (op->csn_str)
+ {
+ slapi_ch_free((void **)&(op->csn_str));
+ }
+ if (op->uniqueid)
+ {
+ slapi_ch_free((void **)&(op->uniqueid));
+ }
+ slapi_ch_free((void**)&op);
+}
+
+static repl5_inc_operation *repl5_inc_operation_new()
+{
+ repl5_inc_operation *ret = NULL;
+ ret = (repl5_inc_operation *) slapi_ch_calloc(1,sizeof(repl5_inc_operation));
+ return ret;
+}
+
+/* Called when in compatibility mode, to get the next result from the wire
+ * The operation thread will not send a second operation until it has read the
+ * previous result. */
+static int
+repl5_inc_get_next_result(result_data *rd)
+{
+ ConnResult conres = 0;
+ int message_id = 0;
+ /* Wait on the next result */
+ conres = conn_read_result(rd->prp->conn, &message_id);
+ /* Return it to the caller */
+ return conres;
+}
+
+static void
+repl5_inc_log_operation_failure(int operation_code, int ldap_error, char* ldap_error_string, const char *agreement_name)
+{
+ char *op_string = slapi_op_type_to_string(operation_code);
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Received error %d: %s for %s operation\n",
+ agreement_name,
+ ldap_error, ldap_error_string ? ldap_error_string : "NULL",
+ op_string ? op_string : "NULL");
+}
+
+/* Thread that collects results from async operations sent to the consumer */
+static void repl5_inc_result_threadmain(void *param)
+{
+ result_data *rd = (result_data*) param;
+ int res = 0;
+ ConnResult conres = 0;
+ Repl_Connection *conn = rd->prp->conn;
+ int finished = 0;
+ int message_id = 0;
+
+ while (!finished)
+ {
+ repl5_inc_operation *op = NULL;
+ int connection_error = 0;
+ char *csn_str = NULL;
+ char *uniqueid = NULL;
+ ReplicaId replica_id = 0;
+ int operation_code = 0;
+ char *ldap_error_string = NULL;
+ time_t time_now = 0;
+ time_t start_time = time( NULL );
+ int backoff_time = 1;
+
+ /* Read the next result */
+ /* We call the get result function with a short timeout (non-blocking)
+ * this is so we don't block here forever, and can stop this thread when
+ * the time comes. However, we do need to implement blocking with timeout
+ * semantics here instead.
+ */
+
+ while (!finished)
+ {
+ conres = conn_read_result_ex(conn, NULL, NULL, NULL, &message_id, 0);
+ /* Timeout here means that we didn't block, not a real timeout */
+ if (CONN_TIMEOUT == conres)
+ {
+ /* We need to a) check that the 'real' timeout hasn't expired and
+ * b) implement a backoff sleep to avoid spinning */
+ /* Did the connection's timeout expire ? */
+ time_now = time( NULL );
+ if (conn_get_timeout(conn) <= ( time_now - start_time ))
+ {
+ /* We timed out */
+ conres = CONN_TIMEOUT;
+ break;
+ }
+ /* Otherwise we backoff */
+ DS_Sleep(PR_MillisecondsToInterval(backoff_time));
+ if (backoff_time < 1000)
+ {
+ backoff_time <<= 1;
+ }
+ /* Should we stop ? */
+ PR_Lock(rd->lock);
+ if (rd->stop_result_thread)
+ {
+ finished = 1;
+ }
+ PR_Unlock(rd->lock);
+ } else
+ {
+ /* Something other than a timeout, so we exit the loop */
+ break;
+ }
+ }
+ if (conres != CONN_TIMEOUT)
+ {
+ if (message_id)
+ {
+ rd->last_message_id_received = message_id;
+ }
+ /* Handle any error etc */
+
+ /* Get the stored operation details from the queue, unless we timed out... */
+ op = repl5_inc_pop_operation(rd);
+ if (op)
+ {
+ csn_str = op->csn_str;
+ replica_id = op->replica_id;
+ uniqueid = op->uniqueid;
+ }
+
+ conn_get_error_ex(conn, &operation_code, &connection_error, &ldap_error_string);
+ if (connection_error)
+ {
+ repl5_inc_log_operation_failure(op ? op->operation_type : 0, connection_error, ldap_error_string, agmt_get_long_name(rd->prp->agmt));
+ }
+ res = repl5_inc_update_from_op_result(rd->prp, conres, connection_error, csn_str, uniqueid, replica_id, &finished, &(rd->num_changes_sent));
+ if (0 != conres)
+ {
+ /* If so then we need to take steps to abort the update process */
+ PR_Lock(rd->lock);
+ rd->abort = 1;
+ PR_Unlock(rd->lock);
+ /* We also need to log the error, including details stored from when the operation was sent */
+ }
+ }
+ /* Should we stop ? */
+ PR_Lock(rd->lock);
+ if (rd->stop_result_thread)
+ {
+ finished = 1;
+ }
+ PR_Unlock(rd->lock);
+ if (op)
+ {
+ repl5_inc_op_free(op);
+ }
+ }
+}
+
+static result_data *
+repl5_inc_rd_new(Private_Repl_Protocol *prp)
+{
+ result_data *res = NULL;
+ res = (result_data *) slapi_ch_calloc(1,sizeof(result_data));
+ if (res) {
+ res->prp = prp;
+ res->lock = PR_NewLock();
+ if (NULL == res->lock) {
+ slapi_ch_free((void **)&res);
+ res = NULL;
+ }
+ }
+ return res;
+}
+
+static void
+repl5_inc_rd_list_destroy(repl5_inc_operation *op)
+{
+ repl5_inc_operation *cur = op;
+ while (op) {
+ repl5_inc_operation *next = op->next;
+ repl5_inc_op_free(op);
+ op = next;
+ }
+}
+
+static void
+repl5_inc_rd_destroy(result_data **pres)
+{
+ result_data *res = *pres;
+ if (res->lock) {
+ PR_DestroyLock(res->lock);
+ }
+ /* Delete the linked list if we have one */
+ /* Begin at the head */
+ repl5_inc_rd_list_destroy(res->operation_list_head);
+ slapi_ch_free((void **)pres);
+}
+
+static int
+repl5_inc_create_async_result_thread(result_data *rd)
+{
+ int retval = 0;
+ PRThread *tid = NULL;
+ /* Create a thread that reads results from the connection and stores status in the callback_data structure */
+ tid = PR_CreateThread(PR_USER_THREAD,
+ repl5_inc_result_threadmain, (void*)rd,
+ PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
+ SLAPD_DEFAULT_THREAD_STACKSIZE);
+ if (NULL == tid)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, NULL,
+ "repl5_tot_create_async_result_thread failed. "
+ SLAPI_COMPONENT_NAME_NSPR " error %d (%s)\n",
+ PR_GetError(), slapd_pr_strerror( PR_GetError() ));
+ retval = -1;
+ } else {
+ rd->result_tid = tid;
+ }
+ return retval;
+}
+
+static int
+repl5_inc_destroy_async_result_thread(result_data *rd)
+{
+ int retval = 0;
+ PRThread *tid = rd->result_tid;
+ if (tid) {
+ PR_Lock(rd->lock);
+ rd->stop_result_thread = 1;
+ PR_Unlock(rd->lock);
+ (void)PR_JoinThread(tid);
+ }
+ return retval;
+}
+
+static void
+repl5_inc_waitfor_async_results(result_data *rd)
+{
+ int done = 0;
+ int loops = 0;
+ /* Keep pulling results off the LDAP connection until we catch up to the last message id stored in the rd */
+ while (!done)
+ {
+ /* Lock the structure to force memory barrier */
+ PR_Lock(rd->lock);
+ /* Are we caught up ? */
+ slapi_log_error(SLAPI_LOG_FATAL, NULL,
+ "repl5_inc_waitfor_async_results: %d %d\n",
+ rd->last_message_id_received, rd->last_message_id_sent, 0);
+ if (rd->last_message_id_received >= rd->last_message_id_sent)
+ {
+ /* If so then we're done */
+ done = 1;
+ }
+ PR_Unlock(rd->lock);
+ /* If not then sleep a bit */
+ DS_Sleep(PR_SecondsToInterval(1));
+ loops++;
+ /* If we sleep forever then we can conclude that something bad happened, and bail... */
+ /* Arbitrary 30 second delay : basically we should only expect to wait as long as it takes to process a few operations, which should be on the order of a second at most */
+ if (loops > 300)
+ {
+ /* Log a warning */
+ slapi_log_error(SLAPI_LOG_FATAL, NULL,
+ "repl5_inc_waitfor_async_results timed out waiting for responses: %d %d\n",
+ rd->last_message_id_received, rd->last_message_id_sent, 0);
+ done = 1;
+ }
+ }
+}
/*
* It's specifically ok to delete a protocol instance that
@@ -969,7 +1308,7 @@ reset_events (Private_Repl_Protocol *prp)
* and send the operation to the consumer.
*/
ConnResult
-replay_update(Private_Repl_Protocol *prp, slapi_operation_parameters *op)
+replay_update(Private_Repl_Protocol *prp, slapi_operation_parameters *op, int *message_id)
{
ConnResult return_value;
LDAPControl *update_control;
@@ -1031,20 +1370,29 @@ replay_update(Private_Repl_Protocol *prp, slapi_operation_parameters *op)
}
else
{
+ /* If fractional agreement, trim down the entry */
+ if (agmt_is_fractional(prp->agmt))
+ {
+ repl5_strip_fractional_mods(prp->agmt,entryattrs);
+ }
return_value = conn_send_add(prp->conn, op->target_address.dn,
- entryattrs, update_control, NULL /* returned controls */);
+ entryattrs, update_control, message_id);
ldap_mods_free(entryattrs, 1);
}
break;
}
case SLAPI_OPERATION_MODIFY:
+ /* If fractional agreement, trim down the mods */
+ if (agmt_is_fractional(prp->agmt))
+ {
+ repl5_strip_fractional_mods(prp->agmt,op->p.p_modify.modify_mods);
+ }
return_value = conn_send_modify(prp->conn, op->target_address.dn,
- op->p.p_modify.modify_mods, update_control,
- NULL /* returned controls */);
+ op->p.p_modify.modify_mods, update_control, message_id);
break;
case SLAPI_OPERATION_DELETE:
return_value = conn_send_delete(prp->conn, op->target_address.dn,
- update_control, NULL /* returned controls */);
+ update_control, message_id);
break;
case SLAPI_OPERATION_MODRDN:
/* XXXggood need to pass modrdn mods in update control! */
@@ -1052,7 +1400,7 @@ replay_update(Private_Repl_Protocol *prp, slapi_operation_parameters *op)
op->p.p_modrdn.modrdn_newrdn,
op->p.p_modrdn.modrdn_newsuperior_address.dn,
op->p.p_modrdn.modrdn_deloldrdn,
- update_control, NULL /* returned controls */);
+ update_control, message_id);
break;
default:
slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "%s: replay_update: Unknown "
@@ -1066,7 +1414,7 @@ replay_update(Private_Repl_Protocol *prp, slapi_operation_parameters *op)
if (CONN_OPERATION_SUCCESS == return_value)
{
slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
- "%s: replay_update: Consumer successfully replayed operation with csn %s\n",
+ "%s: replay_update: Consumer successfully sent operation with csn %s\n",
agmt_get_long_name(prp->agmt), csn_str);
}
else
@@ -1126,7 +1474,83 @@ cl5_operation_parameters_done (struct slapi_operation_parameters *sop)
}
+/* Helper to update the agreement state based on a the result of a replay operation */
+static int
+repl5_inc_update_from_op_result(Private_Repl_Protocol *prp, ConnResult replay_crc, int connection_error, char *csn_str, char *uniqueid, ReplicaId replica_id, int* finished, PRUint32 *num_changes_sent)
+{
+ int return_value = 0;
+
+ /* Indentation is wrong here so we can get a sensible cvs diff */
+ if (CONN_OPERATION_SUCCESS != replay_crc)
+ {
+ /* Figure out what to do next */
+ if (CONN_OPERATION_FAILED == replay_crc)
+ {
+ /* Map ldap error code to return value */
+ if (!ignore_error_and_keep_going(connection_error))
+ {
+ return_value = UPDATE_TRANSIENT_ERROR;
+ *finished = 1;
+ }
+ else
+ {
+ agmt_inc_last_update_changecount (prp->agmt, replica_id, 1 /*skipped*/);
+ }
+ slapi_log_error(*finished ? SLAPI_LOG_FATAL : slapi_log_urp, repl_plugin_name,
+ "%s: Consumer failed to replay change (uniqueid %s, CSN %s): %s. %s.\n",
+ agmt_get_long_name(prp->agmt),
+ uniqueid, csn_str,
+ ldap_err2string(connection_error),
+ *finished ? "Will retry later" : "Skipping");
+ }
+ else if (CONN_NOT_CONNECTED == replay_crc)
+ {
+ /* We lost the connection - enter backoff state */
+ return_value = UPDATE_TRANSIENT_ERROR;
+ *finished = 1;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Consumer failed to replay change (uniqueid %s, CSN %s): "
+ "%s. Will retry later.\n",
+ agmt_get_long_name(prp->agmt),
+ uniqueid, csn_str,
+ connection_error ? ldap_err2string(connection_error) : "Connection lost");
+ }
+ else if (CONN_TIMEOUT == replay_crc)
+ {
+ return_value = UPDATE_TIMEOUT;
+ *finished = 1;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Consumer timed out to replay change (uniqueid %s, CSN %s): "
+ "%s.\n",
+ agmt_get_long_name(prp->agmt),
+ uniqueid, csn_str,
+ connection_error ? ldap_err2string(connection_error) : "Timeout");
+ }
+ else if (CONN_LOCAL_ERROR == replay_crc)
+ {
+ /*
+ * Something bad happened on the local server - enter
+ * backoff state.
+ */
+ return_value = UPDATE_TRANSIENT_ERROR;
+ *finished = 1;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Failed to replay change (uniqueid %s, CSN %s): "
+ "Local error. Will retry later.\n",
+ agmt_get_long_name(prp->agmt),
+ uniqueid, csn_str);
+ }
+
+ }
+ else
+ {
+ /* Positive response received */
+ (*num_changes_sent)++;
+ agmt_inc_last_update_changecount (prp->agmt, replica_id, 0 /*replayed*/);
+ }
+ return return_value;
+}
/*
* Send a set of updates to the replica. Assumes that (1) the replica
@@ -1146,6 +1570,8 @@ send_updates(Private_Repl_Protocol *prp, RUV *remote_update_vector, PRUint32 *nu
int return_value;
int rc;
CL5ReplayIterator *changelog_iterator;
+ int message_id = 0;
+ result_data *rd = NULL;
*num_changes_sent = 0;
/*
@@ -1258,6 +1684,19 @@ send_updates(Private_Repl_Protocol *prp, RUV *remote_update_vector, PRUint32 *nu
ConnResult replay_crc;
char csn_str[CSN_STRSIZE];
+ /* Start the results reading thread */
+ rd = repl5_inc_rd_new(prp);
+ if (!prp->repl50consumer)
+ {
+ rc = repl5_inc_create_async_result_thread(rd);
+ if (rc) {
+ slapi_log_error (SLAPI_LOG_FATAL, repl_plugin_name, "%s: repl5_inc_run: "
+ "repl5_tot_create_async_result_thread failed; error - %d\n",
+ agmt_get_long_name(prp->agmt), rc);
+ return_value = UPDATE_FATAL_ERROR;
+ }
+ }
+
memset ( (void*)&op, 0, sizeof (op) );
entry.op = &op;
do {
@@ -1276,7 +1715,12 @@ send_updates(Private_Repl_Protocol *prp, RUV *remote_update_vector, PRUint32 *nu
agmt_get_long_name(prp->agmt), csn_as_string(entry.op->csn, PR_FALSE, csn_str));
continue;
}
- replay_crc = replay_update(prp, entry.op);
+ replay_crc = replay_update(prp, entry.op, &message_id);
+ if (message_id)
+ {
+ rd->last_message_id_sent = message_id;
+ }
+ /* If we're talking to an old non-async replica, we need to pick up the response here */
if (CONN_OPERATION_SUCCESS != replay_crc)
{
int operation, error;
@@ -1344,9 +1788,36 @@ send_updates(Private_Repl_Protocol *prp, RUV *remote_update_vector, PRUint32 *nu
}
else
{
- /* Positive response received */
- (*num_changes_sent)++;
- agmt_inc_last_update_changecount (prp->agmt, csn_get_replicaid(entry.op->csn), 0 /*replayed*/);
+ char *uniqueid = NULL;
+ ReplicaId replica_id = 0;
+
+ csn_as_string(entry.op->csn, PR_FALSE, csn_str);
+ replica_id = csn_get_replicaid(entry.op->csn);
+ uniqueid = entry.op->target_address.uniqueid;
+
+ if (prp->repl50consumer)
+ {
+ int operation, error = 0;
+
+ conn_get_error(prp->conn, &operation, &error);
+
+ /* Get the response here */
+ replay_crc = repl5_inc_get_next_result(rd);
+ conn_get_error(prp->conn, &operation, &error);
+ csn_as_string(entry.op->csn, PR_FALSE, csn_str);
+ return_value = repl5_inc_update_from_op_result(prp, replay_crc, error, csn_str, uniqueid, replica_id, &finished, num_changes_sent);
+ }
+ else {
+ /* Queue the details for pickup later in the response thread */
+ repl5_inc_operation *sop = NULL;
+ sop = repl5_inc_operation_new();
+ sop->csn_str = slapi_ch_strdup(csn_str);
+ sop->ldap_message_id = message_id;
+ sop->operation_type = entry.op->operation_type;
+ sop->replica_id = replica_id;
+ sop->uniqueid = slapi_ch_strdup(uniqueid);
+ repl5_int_push_operation(rd,sop);
+ }
}
break;
case CL5_BAD_DATA:
@@ -1400,6 +1871,23 @@ send_updates(Private_Repl_Protocol *prp, RUV *remote_update_vector, PRUint32 *nu
finished = 1;
}
} while (!finished);
+
+ /* Terminate the results reading thread */
+ if (!prp->repl50consumer)
+ {
+ /* We need to ensure that we wait until all the responses have been recived from our operations */
+ repl5_inc_waitfor_async_results(rd);
+
+ rc = repl5_inc_destroy_async_result_thread(rd);
+ if (rc) {
+ slapi_log_error (SLAPI_LOG_FATAL, repl_plugin_name, "%s: repl5_inc_run: "
+ "repl5_tot_destroy_async_result_thread failed; error - %d\n",
+ agmt_get_long_name(prp->agmt), rc);
+ }
+ *num_changes_sent = rd->num_changes_sent;
+ }
+ repl5_inc_rd_destroy(&rd);
+
cl5_operation_parameters_done ( entry.op );
cl5DestroyReplayIterator(&changelog_iterator);
}
@@ -1527,6 +2015,7 @@ Repl_5_Inc_Protocol_new(Repl_Protocol *rp)
rip->rp = rp;
prp->private = (void *)rip;
prp->replica_acquired = PR_FALSE;
+ prp->repl50consumer = 0;
return prp;
loser:
repl5_inc_delete(&prp);
diff --git a/ldap/servers/plugins/replication/repl5_init.c b/ldap/servers/plugins/replication/repl5_init.c
index d2207501..1409af7a 100644
--- a/ldap/servers/plugins/replication/repl5_init.c
+++ b/ldap/servers/plugins/replication/repl5_init.c
@@ -67,6 +67,7 @@ static char *end_name_list[] = {
};
static char *total_oid_list[] = {
REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID,
+ REPL_NSDS71_REPLICATION_ENTRY_REQUEST_OID,
NULL
};
static char *total_name_list[] = {
diff --git a/ldap/servers/plugins/replication/repl5_plugins.c b/ldap/servers/plugins/replication/repl5_plugins.c
index c81dbc3f..e0c9b7fc 100644
--- a/ldap/servers/plugins/replication/repl5_plugins.c
+++ b/ldap/servers/plugins/replication/repl5_plugins.c
@@ -1398,3 +1398,10 @@ multimaster_be_state_change (void *handle, char *be_name, int old_be_state, int
object_release (r_obj);
}
+
+static void
+close_changelog_for_replica (Object *r_obj)
+{
+ if (cl5GetState () == CL5_STATE_OPEN)
+ cl5CloseDB (r_obj);
+}
diff --git a/ldap/servers/plugins/replication/repl5_prot_private.h b/ldap/servers/plugins/replication/repl5_prot_private.h
index a4823822..f4ede144 100644
--- a/ldap/servers/plugins/replication/repl5_prot_private.h
+++ b/ldap/servers/plugins/replication/repl5_prot_private.h
@@ -35,10 +35,13 @@ typedef struct private_repl_protocol
Object *replica_object;
void *private;
PRBool replica_acquired;
+ int repl50consumer; /* Flag to tell us if this is a 5.0-style consumer we're talking to */
} Private_Repl_Protocol;
extern Private_Repl_Protocol *Repl_5_Inc_Protocol_new();
extern Private_Repl_Protocol *Repl_5_Tot_Protocol_new();
+extern Private_Repl_Protocol *Windows_Inc_Protocol_new();
+extern Private_Repl_Protocol *Windows_Tot_Protocol_new();
#define PROTOCOL_TERMINATION_NORMAL 301
#define PROTOCOL_TERMINATION_ABNORMAL 302
@@ -59,8 +62,11 @@ extern Private_Repl_Protocol *Repl_5_Tot_Protocol_new();
/* protocol related functions */
void release_replica(Private_Repl_Protocol *prp);
int acquire_replica(Private_Repl_Protocol *prp, char *prot_oid, RUV **ruv);
-BerElement *entry2bere(const Slapi_Entry *e);
+BerElement *entry2bere(const Slapi_Entry *e, char **excluded_attrs);
CSN *get_current_csn(Slapi_DN *replarea_sdn);
char* protocol_response2string (int response);
+int repl5_strip_fractional_mods(Repl_Agmt *agmt, LDAPMod **);
+void windows_release_replica(Private_Repl_Protocol *prp);
+int windows_acquire_replica(Private_Repl_Protocol *prp, RUV **ruv);
#endif /* _REPL5_PROT_PRIVATE_H_ */
diff --git a/ldap/servers/plugins/replication/repl5_protocol.c b/ldap/servers/plugins/replication/repl5_protocol.c
index c1aa16a9..4d8dc3a9 100644
--- a/ldap/servers/plugins/replication/repl5_protocol.c
+++ b/ldap/servers/plugins/replication/repl5_protocol.c
@@ -15,12 +15,15 @@
*/
#include "repl5.h"
+#include "windowsrepl.h"
#include "repl5_prot_private.h"
#define PROTOCOL_5_INCREMENTAL 1
#define PROTOCOL_5_TOTAL 2
#define PROTOCOL_4_INCREMENTAL 3
#define PROTOCOL_4_TOTAL 4
+#define PROTOCOL_WINDOWS_INCREMENTAL 5
+#define PROTOCOL_WINDOWS_TOTAL 6
typedef struct repl_protocol
{
@@ -70,10 +73,11 @@ prot_new(Repl_Agmt *agmt, int protocol_state)
goto loser;
}
rp->agmt = agmt;
+ /* now done in private_protocol_factory
if ((rp->conn = conn_new(agmt)) == NULL)
{
goto loser;
- }
+ } */
/* Acquire the local replica object */
replarea_sdn = agmt_get_replarea(agmt);
rp->replica_object = replica_get_replica_from_dn(replarea_sdn);
@@ -86,8 +90,18 @@ prot_new(Repl_Agmt *agmt, int protocol_state)
slapi_sdn_get_dn(replarea_sdn));
goto loser;
}
+
+ if (get_agmt_agreement_type(agmt) == REPLICA_TYPE_MULTIMASTER)
+ {
rp->prp_incremental = private_protocol_factory(rp, PROTOCOL_5_INCREMENTAL);
rp->prp_total = private_protocol_factory(rp, PROTOCOL_5_TOTAL);
+ }
+ else if (get_agmt_agreement_type(agmt) == REPLICA_TYPE_WINDOWS)
+ {
+ rp->prp_incremental = private_protocol_factory(rp, PROTOCOL_WINDOWS_INCREMENTAL);
+ rp->prp_total = private_protocol_factory(rp, PROTOCOL_WINDOWS_TOTAL);
+ }
+
/* XXXggood register callback handlers for entries updated, and
schedule window enter/leave. */
slapi_sdn_free(&replarea_sdn);
@@ -489,14 +503,25 @@ static Private_Repl_Protocol *
private_protocol_factory(Repl_Protocol *rp, int type)
{
Private_Repl_Protocol *prp;
+
switch (type)
{
case PROTOCOL_5_INCREMENTAL:
+ if ((rp->conn = conn_new(rp->agmt)) != NULL)
prp = Repl_5_Inc_Protocol_new(rp);
break;
case PROTOCOL_5_TOTAL:
+ if ((rp->conn = conn_new(rp->agmt)) != NULL)
prp = Repl_5_Tot_Protocol_new(rp);
break;
+ case PROTOCOL_WINDOWS_INCREMENTAL:
+ if ((rp->conn = windows_conn_new(rp->agmt)) != NULL)
+ prp = Windows_Inc_Protocol_new(rp);
+ break;
+ case PROTOCOL_WINDOWS_TOTAL:
+ if ((rp->conn = windows_conn_new(rp->agmt)) != NULL)
+ prp = Windows_Tot_Protocol_new(rp);
+ break;
}
return prp;
}
diff --git a/ldap/servers/plugins/replication/repl5_protocol_util.c b/ldap/servers/plugins/replication/repl5_protocol_util.c
index fdf7769f..6bee6564 100644
--- a/ldap/servers/plugins/replication/repl5_protocol_util.c
+++ b/ldap/servers/plugins/replication/repl5_protocol_util.c
@@ -58,7 +58,6 @@ get_current_csn(Slapi_DN *replarea_sdn)
return current_csn;
}
-
/*
* Acquire exclusive access to a replica. Send a start replication extended
* operation to the replica. The response will contain a success code, and
@@ -151,201 +150,246 @@ acquire_replica(Private_Repl_Protocol *prp, char *prot_oid, RUV **ruv)
}
else
{
- /* Good to go. Start the protocol. */
- CSN *current_csn = NULL;
- struct berval *retdata = NULL;
- char *retoid = NULL;
- Slapi_DN *replarea_sdn;
-
- /* Obtain a current CSN */
- replarea_sdn = agmt_get_replarea(prp->agmt);
- current_csn = get_current_csn(replarea_sdn);
- if (NULL != current_csn)
+ /* Does the remote replica support the 7.1 protocol? */
+ crc = conn_replica_supports_ds71_repl(conn);
+ if (CONN_DOES_NOT_SUPPORT_DS71_REPL == crc)
+ {
+ prp->repl50consumer = 1;
+ }
+ if (CONN_NOT_CONNECTED == crc || CONN_OPERATION_FAILED == crc)
+ {
+ /* We don't know anything about the remote replica. Try again later. */
+ return_value = ACQUIRE_TRANSIENT_ERROR;
+ } else
{
- struct berval *payload = NSDS50StartReplicationRequest_new(
- prot_oid, slapi_sdn_get_ndn(replarea_sdn),
- NULL /* XXXggood need to provide referral(s) */, current_csn);
- /* JCMREPL - Need to extract the referrals from the RUV */
- csn_free(&current_csn);
- current_csn = NULL;
- crc = conn_send_extended_operation(conn,
- REPL_START_NSDS50_REPLICATION_REQUEST_OID, payload, &retoid,
- &retdata, NULL /* update control */, NULL /* returned controls */);
- ber_bvfree(payload);
- payload = NULL;
- /* Look at the response we got. */
- if (CONN_OPERATION_SUCCESS == crc)
+ CSN *current_csn = NULL;
+ struct berval *retdata = NULL;
+ char *retoid = NULL;
+ Slapi_DN *replarea_sdn;
+
+ /* Check if this is a fractional agreement, we need to
+ * verify that the consumer is read-only */
+ if (agmt_is_fractional(prp->agmt)) {
+ crc = conn_replica_is_readonly(conn);
+ if (CONN_IS_NOT_READONLY == crc) {
+ /* This is a fatal error */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to acquire replica: "
+ "the agreement is fractional but the replica is not read-only. Fractional agreements must specify a read-only replica "
+ "Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
+ goto error;
+ }
+ }
+
+ /* Good to go. Start the protocol. */
+
+ /* Obtain a current CSN */
+ replarea_sdn = agmt_get_replarea(prp->agmt);
+ current_csn = get_current_csn(replarea_sdn);
+ if (NULL != current_csn)
{
- /*
- * Extop was processed. Look at extop response to see if we're
- * permitted to go ahead.
- */
- struct berval **ruv_bervals = NULL;
- int extop_result;
- int extop_rc = decode_repl_ext_response(retdata, &extop_result,
- &ruv_bervals);
- if (0 == extop_rc)
+ struct berval *payload = NSDS50StartReplicationRequest_new(
+ prot_oid, slapi_sdn_get_ndn(replarea_sdn),
+ NULL /* XXXggood need to provide referral(s) */, current_csn);
+ /* JCMREPL - Need to extract the referrals from the RUV */
+ csn_free(&current_csn);
+ current_csn = NULL;
+ crc = conn_send_extended_operation(conn,
+ REPL_START_NSDS50_REPLICATION_REQUEST_OID, payload, NULL /* update control */, NULL /* Message ID */);
+ if (CONN_OPERATION_SUCCESS != crc)
{
- prp->last_acquire_response_code = extop_result;
- switch (extop_result)
+ int operation, error;
+ conn_get_error(conn, &operation, &error);
+
+ /* Couldn't send the extended operation */
+ return_value = ACQUIRE_TRANSIENT_ERROR; /* XXX right return value? */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to send a startReplication "
+ "extended operation to consumer (%s). Will retry later.\n",
+ agmt_get_long_name(prp->agmt),
+ error ? ldap_err2string(error) : "unknown error");
+ }
+ /* Since the operation request is async, we need to wait for the response here */
+ crc = conn_read_result_ex(conn,&retoid,&retdata,NULL,NULL,1);
+ ber_bvfree(payload);
+ payload = NULL;
+ /* Look at the response we got. */
+ if (CONN_OPERATION_SUCCESS == crc)
+ {
+ /*
+ * Extop was processed. Look at extop response to see if we're
+ * permitted to go ahead.
+ */
+ struct berval **ruv_bervals = NULL;
+ int extop_result;
+ int extop_rc = decode_repl_ext_response(retdata, &extop_result,
+ &ruv_bervals);
+ if (0 == extop_rc)
{
- /* XXXggood handle other error codes here */
- case NSDS50_REPL_INTERNAL_ERROR:
+ prp->last_acquire_response_code = extop_result;
+ switch (extop_result)
+ {
+ /* XXXggood handle other error codes here */
+ case NSDS50_REPL_INTERNAL_ERROR:
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to acquire replica: "
+ "an internal error occurred on the remote replica. "
+ "Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
+ break;
+ case NSDS50_REPL_PERMISSION_DENIED:
+ /* Not allowed to send updates */
+ {
+ char *repl_binddn = agmt_get_binddn(prp->agmt);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to acquire replica: permission denied. "
+ "The bind dn \"%s\" does not have permission to "
+ "supply replication updates to the replica. "
+ "Will retry later.\n",
+ agmt_get_long_name(prp->agmt), repl_binddn);
+ slapi_ch_free((void **)&repl_binddn);
+ return_value = ACQUIRE_TRANSIENT_ERROR;
+ break;
+ }
+ case NSDS50_REPL_NO_SUCH_REPLICA:
+ /* There is no such replica on the consumer */
+ {
+ Slapi_DN *repl_root = agmt_get_replarea(prp->agmt);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to acquire replica: there is no "
+ "replicated area \"%s\" on the consumer server. "
+ "Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt),
+ slapi_sdn_get_dn(repl_root));
+ slapi_sdn_free(&repl_root);
+ return_value = ACQUIRE_FATAL_ERROR;
+ break;
+ }
+ case NSDS50_REPL_EXCESSIVE_CLOCK_SKEW:
+ /* Large clock skew between the consumer and the supplier */
slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
"%s: Unable to acquire replica: "
- "an internal error occurred on the remote replica. "
- "Replication is aborting.\n",
+ "Excessive clock skew between the supplier and "
+ "the consumer. Replication is aborting.\n",
agmt_get_long_name(prp->agmt));
return_value = ACQUIRE_FATAL_ERROR;
break;
- case NSDS50_REPL_PERMISSION_DENIED:
- /* Not allowed to send updates */
- {
- char *repl_binddn = agmt_get_binddn(prp->agmt);
+ case NSDS50_REPL_DECODING_ERROR:
+ /* We sent something the replica couldn't understand. */
slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
- "%s: Unable to acquire replica: permission denied. "
- "The bind dn \"%s\" does not have permission to "
- "supply replication updates to the replica. "
- "Will retry later.\n",
- agmt_get_long_name(prp->agmt), repl_binddn);
- slapi_ch_free((void **)&repl_binddn);
- return_value = ACQUIRE_TRANSIENT_ERROR;
+ "%s: Unable to acquire replica: "
+ "the consumer was unable to decode the "
+ "startReplicationRequest extended operation sent by the "
+ "supplier. Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
break;
- }
- case NSDS50_REPL_NO_SUCH_REPLICA:
- /* There is no such replica on the consumer */
- {
- Slapi_DN *repl_root = agmt_get_replarea(prp->agmt);
+ case NSDS50_REPL_REPLICA_BUSY:
+ /* Someone else is updating the replica. Try later. */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Unable to acquire replica: "
+ "the replica is currently being updated"
+ "by another supplier. Will try later\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_REPLICA_BUSY;
+ break;
+ case NSDS50_REPL_LEGACY_CONSUMER:
+ /* remote replica is a legacy consumer */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to acquire replica: the replica "
+ "is supplied by a legacy supplier. "
+ "Replication is aborting.\n", agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
+ break;
+ case NSDS50_REPL_REPLICAID_ERROR:
+ /* remote replica detected a duplicate ReplicaID */
slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
- "%s: Unable to acquire replica: there is no "
- "replicated area \"%s\" on the consumer server. "
+ "%s: Unable to aquire replica: the replica "
+ "has the same Replica ID as this one. "
"Replication is aborting.\n",
- agmt_get_long_name(prp->agmt),
- slapi_sdn_get_dn(repl_root));
- slapi_sdn_free(&repl_root);
+ agmt_get_long_name(prp->agmt));
return_value = ACQUIRE_FATAL_ERROR;
break;
- }
- case NSDS50_REPL_EXCESSIVE_CLOCK_SKEW:
- /* Large clock skew between the consumer and the supplier */
- slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
- "%s: Unable to acquire replica: "
- "Excessive clock skew between the supplier and "
- "the consumer. Replication is aborting.\n",
- agmt_get_long_name(prp->agmt));
- return_value = ACQUIRE_FATAL_ERROR;
- break;
- case NSDS50_REPL_DECODING_ERROR:
- /* We sent something the replica couldn't understand. */
- slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
- "%s: Unable to acquire replica: "
- "the consumer was unable to decode the "
- "startReplicationRequest extended operation sent by the "
- "supplier. Replication is aborting.\n",
- agmt_get_long_name(prp->agmt));
- return_value = ACQUIRE_FATAL_ERROR;
- break;
- case NSDS50_REPL_REPLICA_BUSY:
- /* Someone else is updating the replica. Try later. */
- slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
- "%s: Unable to acquire replica: "
- "the replica is currently being updated"
- "by another supplier. Will try later\n",
- agmt_get_long_name(prp->agmt));
- return_value = ACQUIRE_REPLICA_BUSY;
- break;
- case NSDS50_REPL_LEGACY_CONSUMER:
- /* remote replica is a legacy consumer */
- slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
- "%s: Unable to acquire replica: the replica "
- "is supplied by a legacy supplier. "
- "Replication is aborting.\n", agmt_get_long_name(prp->agmt));
- return_value = ACQUIRE_FATAL_ERROR;
- break;
- case NSDS50_REPL_REPLICAID_ERROR:
- /* remote replica detected a duplicate ReplicaID */
- slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
- "%s: Unable to aquire replica: the replica "
- "has the same Replica ID as this one. "
- "Replication is aborting.\n",
- agmt_get_long_name(prp->agmt));
- return_value = ACQUIRE_FATAL_ERROR;
- break;
- case NSDS50_REPL_REPLICA_READY:
- /* We've acquired the replica. */
- slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
- "%s: Replica was successfully acquired.\n",
- agmt_get_long_name(prp->agmt));
- /* Parse the update vector */
- if (NULL != ruv_bervals && NULL != ruv)
- {
- if (ruv_init_from_bervals(ruv_bervals, ruv) != RUV_SUCCESS)
+ case NSDS50_REPL_REPLICA_READY:
+ /* We've acquired the replica. */
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Replica was successfully acquired.\n",
+ agmt_get_long_name(prp->agmt));
+ /* Parse the update vector */
+ if (NULL != ruv_bervals && NULL != ruv)
{
- /* Couldn't parse the update vector */
- *ruv = NULL;
- slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
- "%s: Warning: acquired replica, "
- "but could not parse update vector. "
- "The replica must be reinitialized.\n",
- agmt_get_long_name(prp->agmt));
+ if (ruv_init_from_bervals(ruv_bervals, ruv) != RUV_SUCCESS)
+ {
+ /* Couldn't parse the update vector */
+ *ruv = NULL;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Warning: acquired replica, "
+ "but could not parse update vector. "
+ "The replica must be reinitialized.\n",
+ agmt_get_long_name(prp->agmt));
+ }
}
- }
- /* Save consumer's RUV in the replication agreement.
- It is used by the changelog trimming code */
- if (ruv && *ruv)
- agmt_set_consumer_ruv (prp->agmt, *ruv);
+ /* Save consumer's RUV in the replication agreement.
+ It is used by the changelog trimming code */
+ if (ruv && *ruv)
+ agmt_set_consumer_ruv (prp->agmt, *ruv);
- return_value = ACQUIRE_SUCCESS;
- break;
- default:
+ return_value = ACQUIRE_SUCCESS;
+ break;
+ default:
+ return_value = ACQUIRE_FATAL_ERROR;
+ }
+ }
+ else
+ {
+ /* Couldn't parse the response */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Unable to parse the response to the "
+ "startReplication extended operation. "
+ "Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt));
+ prp->last_acquire_response_code = NSDS50_REPL_INTERNAL_ERROR;
return_value = ACQUIRE_FATAL_ERROR;
}
+ if (NULL != ruv_bervals)
+ ber_bvecfree(ruv_bervals);
}
else
{
- /* Couldn't parse the response */
+ int operation, error;
+ conn_get_error(conn, &operation, &error);
+
+ /* Couldn't send the extended operation */
+ return_value = ACQUIRE_TRANSIENT_ERROR; /* XXX right return value? */
slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
- "%s: Unable to parse the response to the "
- "startReplication extended operation. "
- "Replication is aborting.\n",
- agmt_get_long_name(prp->agmt));
- prp->last_acquire_response_code = NSDS50_REPL_INTERNAL_ERROR;
- return_value = ACQUIRE_FATAL_ERROR;
+ "%s: Unable to receive the response for a startReplication "
+ "extended operation to consumer (%s). Will retry later.\n",
+ agmt_get_long_name(prp->agmt),
+ error ? ldap_err2string(error) : "unknown error");
}
- if (NULL != ruv_bervals)
- ber_bvecfree(ruv_bervals);
}
else
{
- int operation, error;
- conn_get_error(conn, &operation, &error);
-
- /* Couldn't send the extended operation */
- return_value = ACQUIRE_TRANSIENT_ERROR; /* XXX right return value? */
+ /* Couldn't get a current CSN */
slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
- "%s: Unable to send a startReplication "
- "extended operation to consumer (%s). Will retry later.\n",
- agmt_get_long_name(prp->agmt),
- error ? ldap_err2string(error) : "unknown error");
+ "%s: Unable to obtain current CSN. "
+ "Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
}
+ slapi_sdn_free(&replarea_sdn);
+ if (NULL != retoid)
+ ldap_memfree(retoid);
+ if (NULL != retdata)
+ ber_bvfree(retdata);
}
- else
- {
- /* Couldn't get a current CSN */
- slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
- "%s: Unable to obtain current CSN. "
- "Replication is aborting.\n",
- agmt_get_long_name(prp->agmt));
- return_value = ACQUIRE_FATAL_ERROR;
- }
- slapi_sdn_free(&replarea_sdn);
- if (NULL != retoid)
- ldap_memfree(retoid);
- if (NULL != retdata)
- ber_bvfree(retdata);
}
}
+error:
if (ACQUIRE_SUCCESS != return_value)
{
@@ -374,6 +418,9 @@ release_replica(Private_Repl_Protocol *prp)
char *retoid = NULL;
struct berval *payload = NULL;
Slapi_DN *replarea_sdn = NULL;
+ int sent_message_id = 0;
+ int ret_message_id = 0;
+ ConnResult conres = 0;
PR_ASSERT(NULL != prp);
PR_ASSERT(NULL != prp->conn);
@@ -385,8 +432,7 @@ release_replica(Private_Repl_Protocol *prp)
payload = NSDS50EndReplicationRequest_new((char *)slapi_sdn_get_dn(replarea_sdn)); /* XXXggood had to cast away const */
slapi_sdn_free(&replarea_sdn);
rc = conn_send_extended_operation(prp->conn,
- REPL_END_NSDS50_REPLICATION_REQUEST_OID, payload, &retoid,
- &retdata, NULL /* update control */, NULL /* returned controls */);
+ REPL_END_NSDS50_REPLICATION_REQUEST_OID, payload, NULL /* update control */, &sent_message_id /* Message ID */);
if (0 != rc)
{
int operation, error;
@@ -395,12 +441,37 @@ release_replica(Private_Repl_Protocol *prp)
"%s: Warning: unable to send endReplication extended operation (%s)\n",
agmt_get_long_name(prp->agmt),
error ? ldap_err2string(error) : "unknown error");
+ goto error;
+ }
+ /* Since the operation request is async, we need to wait for the response here */
+ conres = conn_read_result_ex(prp->conn,&retoid,&retdata,NULL,&ret_message_id,1);
+ if (CONN_OPERATION_SUCCESS != conres)
+ {
+ int operation, error;
+ conn_get_error(prp->conn, &operation, &error);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Warning: unable to receive endReplication extended operation response (%s)\n",
+ agmt_get_long_name(prp->agmt),
+ error ? ldap_err2string(error) : "unknown error");
}
else
{
struct berval **ruv_bervals = NULL; /* Shouldn't actually be returned */
int extop_result;
- int extop_rc = decode_repl_ext_response(retdata, &extop_result,
+ int extop_rc = 0;
+
+ /* Check the message id's match */
+ if (sent_message_id != sent_message_id)
+ {
+ int operation, error;
+ conn_get_error(prp->conn, &operation, &error);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Warning: response message id does not match the request (%s)\n",
+ agmt_get_long_name(prp->agmt),
+ error ? ldap_err2string(error) : "unknown error");
+ }
+
+ extop_rc = decode_repl_ext_response(retdata, &extop_result,
(struct berval ***)&ruv_bervals);
if (0 == extop_rc)
{
@@ -440,7 +511,7 @@ release_replica(Private_Repl_Protocol *prp)
/* replica is released, start the linger timer on the connection, which
was stopped in acquire_replica */
conn_start_linger(prp->conn);
-
+error:
prp->replica_acquired = PR_FALSE;
}
@@ -466,3 +537,47 @@ protocol_response2string (int response)
default: return "unknown error";
}
}
+
+int
+repl5_strip_fractional_mods(Repl_Agmt *agmt, LDAPMod ** mods)
+{
+ int retval = 0;
+ int i = 0;
+ char **a = agmt_get_fractional_attrs(agmt);
+ if (a) {
+ /* Iterate through the fractional attr list */
+ for ( i = 0; a[i] != NULL; i++ )
+ {
+ char *this_excluded_attr = a[i];
+ int j = 0;
+
+ for ( j = 0; NULL != mods[ j ]; )
+ {
+ /* For each one iterate through the attrs in this mod list */
+ /* For any that match, remove the mod */
+ LDAPMod *this_mod = mods[j];
+ if (0 == slapi_attr_type_cmp(this_mod->mod_type,this_excluded_attr,SLAPI_TYPE_CMP_SUBTYPE))
+ {
+ /* Move down all subsequent mods */
+ int k = 0;
+ for (k = j; mods[k+1] ; k++)
+ {
+ mods[k] = mods[k+1];
+ }
+ /* Zero the end of the array */
+ mods[k] = NULL;
+ /* Adjust value of j, implicit in not incrementing it */
+ /* Free this mod */
+ ber_bvecfree(this_mod->mod_bvalues);
+ slapi_ch_free((void **)&(this_mod->mod_type));
+ slapi_ch_free((void **)&this_mod);
+
+ } else {
+ j++;
+ }
+ }
+ }
+ slapi_ch_array_free(a);
+ }
+ return retval;
+}
diff --git a/ldap/servers/plugins/replication/repl5_replica.c b/ldap/servers/plugins/replication/repl5_replica.c
index 36bae420..3208e845 100644
--- a/ldap/servers/plugins/replication/repl5_replica.c
+++ b/ldap/servers/plugins/replication/repl5_replica.c
@@ -75,9 +75,10 @@ static int _replica_configure_ruv (Replica *r, PRBool isLocked);
static void _replica_update_state (time_t when, void *arg);
static char * _replica_get_config_dn (const Slapi_DN *root);
static char * _replica_type_as_string (const Replica *r);
-static int replica_create_ruv_tombstone(Replica *r);
-static void assign_csn_callback(const CSN *csn, void *data);
-static void abort_csn_callback(const CSN *csn, void *data);
+/* DBDB, I think this is probably bogus : */
+int replica_create_ruv_tombstone(Replica *r);
+void assign_csn_callback(const CSN *csn, void *data);
+void abort_csn_callback(const CSN *csn, void *data);
static void eq_cb_reap_tombstones(time_t when, void *arg);
static CSN *_replica_get_purge_csn_nolock (const Replica *r);
static void replica_get_referrals_nolock (const Replica *r, char ***referrals);
@@ -3283,6 +3284,24 @@ int replica_start_agreement(Replica *r, Repl_Agmt *ra)
return ret;
}
+int windows_replica_start_agreement(Replica *r, Repl_Agmt *ra)
+{
+ int ret = 0;
+
+ if (r == NULL) return -1;
+
+ PR_Lock(r->agmt_lock);
+
+ if (!replica_is_state_flag_set(r, REPLICA_AGREEMENTS_DISABLED)) {
+ ret = windows_agmt_start(ra); /* Start the replication agreement */
+ /* ret = windows_agmt_start(ra); Start the replication agreement */
+ }
+
+ PR_Unlock(r->agmt_lock);
+ return ret;
+}
+
+
/*
* A callback function registed as op->o_csngen_handler and
* called by backend ops to generate opcsn.
diff --git a/ldap/servers/plugins/replication/repl5_ruv.c b/ldap/servers/plugins/replication/repl5_ruv.c
index dfc11227..a8891833 100644
--- a/ldap/servers/plugins/replication/repl5_ruv.c
+++ b/ldap/servers/plugins/replication/repl5_ruv.c
@@ -106,6 +106,19 @@ ruv_init_new(const char *replGen, ReplicaId rid, const char *purl, RUV **ruv)
return RUV_SUCCESS;
}
+int ruv_private_new(RUV **ruv, RUV *clone )
+{
+
+
+ int rc;
+ rc = ruvInit (ruv, dl_get_count(clone->elements) );
+ if (rc != RUV_SUCCESS)
+ return rc;
+
+ (*ruv)->replGen = slapi_ch_strdup (clone->replGen);
+
+ return RUV_SUCCESS;
+}
/*
* Create a new RUV and initialize its contents from the provided Slapi_Attr.
@@ -1836,6 +1849,14 @@ ruv_is_newer (Object *sruvobj, Object *cruvobj)
/* A hub may have a dummy ruv with rid 65535 */
if ( sreplica->csn == NULL ) continue;
+ if ( cruv->elements == NULL )
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "ruv_is_newer, consumer RUV has no elements\n");
+ is_newer=PR_FALSE;
+ break;
+ }
+
for (creplica = dl_get_first (cruv->elements, &ccookie); creplica;
creplica = dl_get_next (cruv->elements, &ccookie))
{
diff --git a/ldap/servers/plugins/replication/repl5_tot_protocol.c b/ldap/servers/plugins/replication/repl5_tot_protocol.c
index b7bb38bd..89167aeb 100644
--- a/ldap/servers/plugins/replication/repl5_tot_protocol.c
+++ b/ldap/servers/plugins/replication/repl5_tot_protocol.c
@@ -25,6 +25,12 @@ typedef struct repl5_tot_private
PRUint32 eventbits;
} repl5_tot_private;
+typedef struct operation_id_list_item
+{
+ int ldap_message_id;
+ struct operation_id_list_item *next;
+} operation_id_list_item;
+
typedef struct callback_data
{
Private_Repl_Protocol *prp;
@@ -32,6 +38,13 @@ typedef struct callback_data
unsigned long num_entries;
time_t sleep_on_busy;
time_t last_busy;
+ PRLock *lock; /* Lock to protect access to this structure, the message id list and to force memory barriers */
+ PRThread *result_tid; /* The async result thread */
+ operation_id_list_item *message_id_list; /* List of IDs for outstanding operations */
+ int abort; /* Flag used to tell the sending thread asyncronously that it should abort (because an error came up in a result) */
+ int stop_result_thread; /* Flag used to tell the result thread to exit */
+ int last_message_id_sent;
+ int last_message_id_received;
} callback_data;
/*
@@ -46,6 +59,221 @@ static int send_entry (Slapi_Entry *e, void *callback_data);
static void repl5_tot_delete(Private_Repl_Protocol **prp);
/*
+ * Notes on the async version of this code:
+ * First, we need to have the supplier and consumer both be async-capable.
+ * This is for two reasons : 1) We won't do any testing with mixed releases,
+ * so even if we think it might work, we can't be sure. 2) Actually it won't
+ * work either because we can't be sure that the consumer will not re-order
+ * operations. Also the pre-7.1 consumer had the evil LDAP_BUSY return code,
+ * which is incompatible with pipelineing. The 7.1 consumer has interlocks
+ * to only process operations in transport-order, and it blocks when the
+ * import queue is full rather than returning the LDAP_BUSY return code.
+ * Note that it's ok to have a 7.0 supplier talk to a 7.1 consumer because
+ * the consumer-side changes are benign to the old supplier code.
+ */
+
+/* Code for async result reading.
+ * This allows use of full network throughput on high-delay links,
+ * because we don't wait for the result PDU to come back before sending the
+ * next entry. In order to do this we need to spin up a thread to read the
+ * results and handle any errors.
+ */
+
+static void
+repl5_tot_log_operation_failure(int ldap_error, char* ldap_error_string, const char *agreement_name)
+{
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Received error %d: %s for total update operation\n",
+ agreement_name,
+ ldap_error, ldap_error_string ? ldap_error_string : "NULL",
+ 0);
+}
+
+/* Thread that collects results from async operations sent to the consumer */
+static void repl5_tot_result_threadmain(void *param)
+{
+ callback_data *cb = (callback_data*) param;
+ int res = 0;
+ ConnResult conres = 0;
+ Repl_Connection *conn = cb->prp->conn;
+ int finished = 0;
+ int connection_error = 0;
+ char *ldap_error_string = NULL;
+ int operation_code = 0;
+
+ while (!finished)
+ {
+ int message_id = 0;
+ time_t time_now = 0;
+ time_t start_time = time( NULL );
+ int backoff_time = 1;
+
+ /* Read the next result */
+ /* We call the get result function with a short timeout (non-blocking)
+ * this is so we don't block here forever, and can stop this thread when
+ * the time comes. However, we do need to implement blocking with timeout
+ * semantics here instead.
+ */
+
+ while (!finished)
+ {
+ conres = conn_read_result_ex(conn, NULL, NULL, NULL, &message_id, 0);
+ /* Timeout here means that we didn't block, not a real timeout */
+ if (CONN_TIMEOUT == conres)
+ {
+ /* We need to a) check that the 'real' timeout hasn't expired and
+ * b) implement a backoff sleep to avoid spinning */
+ /* Did the connection's timeout expire ? */
+ time_now = time( NULL );
+ if (conn_get_timeout(conn) <= ( time_now - start_time ))
+ {
+ /* We timed out */
+ conres = CONN_TIMEOUT;
+ break;
+ }
+ /* Otherwise we backoff */
+ DS_Sleep(PR_MillisecondsToInterval(backoff_time));
+ if (backoff_time < 1000)
+ {
+ backoff_time <<= 1;
+ }
+ /* Should we stop ? */
+ PR_Lock(cb->lock);
+ if (cb->stop_result_thread)
+ {
+ finished = 1;
+ }
+ PR_Unlock(cb->lock);
+ } else
+ {
+ /* Something other than a timeout, so we exit the loop */
+ break;
+ }
+ }
+
+ if (message_id)
+ {
+ cb->last_message_id_received = message_id;
+ }
+ conn_get_error_ex(conn, &operation_code, &connection_error, &ldap_error_string);
+
+ if (connection_error)
+ {
+ repl5_tot_log_operation_failure(connection_error,ldap_error_string,agmt_get_long_name(cb->prp->agmt));
+ }
+ /* Was the result itself an error ? */
+ if (0 != conres)
+ {
+ /* If so then we need to take steps to abort the update process */
+ PR_Lock(cb->lock);
+ cb->abort = 1;
+ PR_Unlock(cb->lock);
+ }
+ /* Should we stop ? */
+ PR_Lock(cb->lock);
+ if (cb->stop_result_thread)
+ {
+ finished = 1;
+ }
+ PR_Unlock(cb->lock);
+ }
+}
+
+static int repl5_tot_create_async_result_thread(callback_data *cb_data)
+{
+ int retval = 0;
+ PRThread *tid = NULL;
+ /* Create a thread that reads results from the connection and stores status in the callback_data structure */
+ tid = PR_CreateThread(PR_USER_THREAD,
+ repl5_tot_result_threadmain, (void*)cb_data,
+ PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
+ SLAPD_DEFAULT_THREAD_STACKSIZE);
+ if (NULL == tid)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, NULL,
+ "repl5_tot_create_async_result_thread failed. "
+ SLAPI_COMPONENT_NAME_NSPR " error %d (%s)\n",
+ PR_GetError(), slapd_pr_strerror( PR_GetError() ));
+ retval = -1;
+ } else {
+ cb_data->result_tid = tid;
+ }
+ return retval;
+}
+
+static int repl5_tot_destroy_async_result_thread(callback_data *cb_data)
+{
+ int retval = 0;
+ PRThread *tid = cb_data->result_tid;
+ if (tid) {
+ PR_Lock(cb_data->lock);
+ cb_data->stop_result_thread = 1;
+ PR_Unlock(cb_data->lock);
+ (void)PR_JoinThread(tid);
+ }
+ return retval;
+}
+
+/* Called when in compatibility mode, to get the next result from the wire
+ * The operation thread will not send a second operation until it has read the
+ * previous result. */
+static int
+repl5_tot_get_next_result(callback_data *cb_data)
+{
+ ConnResult conres = 0;
+ int message_id = 0;
+ int connection_error = 0;
+ char *ldap_error_string = NULL;
+ int operation_code = 0;
+ /* Wait on the next result */
+ conres = conn_read_result(cb_data->prp->conn, &message_id);
+ conn_get_error_ex(cb_data->prp->conn, &operation_code, &connection_error, &ldap_error_string);
+ if (connection_error)
+ {
+ repl5_tot_log_operation_failure(connection_error,ldap_error_string,agmt_get_long_name(cb_data->prp->agmt));
+ }
+ /* Return it to the caller */
+ return conres;
+}
+
+static void
+repl5_tot_waitfor_async_results(callback_data *cb_data)
+{
+ int done = 0;
+ int loops = 0;
+ /* Keep pulling results off the LDAP connection until we catch up to the last message id stored in the rd */
+ while (!done)
+ {
+ /* Lock the structure to force memory barrier */
+ PR_Lock(cb_data->lock);
+ /* Are we caught up ? */
+ slapi_log_error(SLAPI_LOG_FATAL, NULL,
+ "repl5_inc_waitfor_async_results: %d %d\n",
+ cb_data->last_message_id_received, cb_data->last_message_id_sent, 0);
+ if (cb_data->last_message_id_received >= cb_data->last_message_id_sent)
+ {
+ /* If so then we're done */
+ done = 1;
+ }
+ PR_Unlock(cb_data->lock);
+ /* If not then sleep a bit */
+ DS_Sleep(PR_SecondsToInterval(1));
+ loops++;
+ /* If we sleep forever then we can conclude that something bad happened, and bail... */
+ /* Arbitrary 30 second delay : basically we should only expect to wait as long as it takes to process a few operations, which should be on the order of a second at most */
+ if (loops > 300)
+ {
+ /* Log a warning */
+ slapi_log_error(SLAPI_LOG_FATAL, NULL,
+ "repl5_tot_waitfor_async_results timed out waiting for responses: %d %d\n",
+ cb_data->last_message_id_received, cb_data->last_message_id_sent, 0);
+ done = 1;
+ }
+ }
+}
+
+
+/*
* Completely refresh a replica. The basic protocol interaction goes
* like this:
* - Acquire Replica by sending a StartReplicationRequest extop, with the
@@ -57,7 +285,7 @@ static void
repl5_tot_run(Private_Repl_Protocol *prp)
{
int rc;
- callback_data cb_data;
+ callback_data cb_data = {0};
Slapi_PBlock *pb;
LDAPControl **ctrls;
char *hostname = NULL;
@@ -141,11 +369,29 @@ repl5_tot_run(Private_Repl_Protocol *prp)
slapi_search_internal_set_pb (pb, slapi_sdn_get_dn (area_sdn),
LDAP_SCOPE_SUBTREE, "(|(objectclass=ldapsubentry)(objectclass=nstombstone)(nsuniqueid=*))", NULL, 0, ctrls, NULL,
repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+
cb_data.prp = prp;
cb_data.rc = 0;
cb_data.num_entries = 0UL;
cb_data.sleep_on_busy = 0UL;
cb_data.last_busy = current_time ();
+ cb_data.lock = PR_NewLock();
+
+ /* Before we get started on sending entries to the replica, we need to
+ * setup things for async propagation:
+ * 1. Create a thread that will read the LDAP results from the connection.
+ * 2. Anything else ?
+ */
+ if (!prp->repl50consumer)
+ {
+ rc = repl5_tot_create_async_result_thread(&cb_data);
+ if (rc) {
+ slapi_log_error (SLAPI_LOG_FATAL, repl_plugin_name, "%s: repl5_tot_run: "
+ "repl5_tot_create_async_result_thread failed; error - %d\n",
+ agmt_get_long_name(prp->agmt), rc);
+ goto done;
+ }
+ }
/* this search get all the entries from the replicated area including tombstones
and referrals */
@@ -153,7 +399,32 @@ repl5_tot_run(Private_Repl_Protocol *prp)
get_result /* result callback */,
send_entry /* entry callback */,
NULL /* referral callback*/);
- slapi_pblock_destroy (pb);
+
+ /*
+ * After completing the sending operation (or optionally failing), we need to clean up
+ * the async propagation stuff:
+ * 1. Stop the thread that collects LDAP results from the connection.
+ * 2. Anything else ?
+ */
+
+ if (!prp->repl50consumer)
+ {
+ repl5_tot_waitfor_async_results(&cb_data);
+ rc = repl5_tot_destroy_async_result_thread(&cb_data);
+ if (rc) {
+ slapi_log_error (SLAPI_LOG_FATAL, repl_plugin_name, "%s: repl5_tot_run: "
+ "repl5_tot_destroy_async_result_thread failed; error - %d\n",
+ agmt_get_long_name(prp->agmt), rc);
+ }
+ }
+
+ /* From here on, things are the same as in the old sync code :
+ * the entire total update either succeeded, or it failed.
+ * If it failed, then cb_data.rc contains the error code, and
+ * suitable messages will have been logged to the error log about the failure.
+ */
+
+ slapi_pblock_destroy (pb);
agmt_set_last_init_end(prp->agmt, current_time());
rc = cb_data.rc;
release_replica(prp);
@@ -173,6 +444,10 @@ repl5_tot_run(Private_Repl_Protocol *prp)
done:
slapi_ch_free_string(&hostname);
+ if (cb_data.lock)
+ {
+ PR_DestroyLock(cb_data.lock);
+ }
prp->stopped = 1;
}
@@ -259,6 +534,7 @@ Repl_5_Tot_Protocol_new(Repl_Protocol *rp)
rip->rp = rp;
prp->private = (void *)rip;
prp->replica_acquired = PR_FALSE;
+ prp->repl50consumer = 0;
return prp;
loser:
repl5_tot_delete(&prp);
@@ -287,6 +563,9 @@ int send_entry (Slapi_Entry *e, void *cb_data)
unsigned long *num_entriesp;
time_t *sleep_on_busyp;
time_t *last_busyp;
+ int message_id = 0;
+ int retval = 0;
+ char **frac_excluded_attrs = NULL;
PR_ASSERT (cb_data);
@@ -314,14 +593,26 @@ int send_entry (Slapi_Entry *e, void *cb_data)
Instead, it will get removed when this replica stops being 4.0 consumer and
then propagated to all its consumer */
+ if (agmt_is_fractional(prp->agmt))
+ {
+ frac_excluded_attrs = agmt_get_fractional_attrs(prp->agmt);
+ }
+
/* convert the entry to the on the wire format */
- bere = entry2bere(e);
+ bere = entry2bere(e,frac_excluded_attrs);
+
+ if (frac_excluded_attrs)
+ {
+ slapi_ch_array_free(frac_excluded_attrs);
+ }
+
if (bere == NULL)
{
slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name, "%s: send_entry: Encoding Error\n",
agmt_get_long_name(prp->agmt));
((callback_data*)cb_data)->rc = -1;
- return -1;
+ retval = -1;
+ goto error;
}
rc = ber_flatten(bere, &bv);
@@ -329,15 +620,30 @@ int send_entry (Slapi_Entry *e, void *cb_data)
if (rc != 0)
{
((callback_data*)cb_data)->rc = -1;
- return -1;
+ retval = -1;
+ goto error;
}
do {
/* push the entry to the consumer */
rc = conn_send_extended_operation(prp->conn, REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID,
- bv /* payload */, NULL /* retoidp */,
- NULL /* retdatap */, NULL /* update_control */,
- NULL /* returned_controls */);
+ bv /* payload */, NULL /* update_control */, &message_id);
+
+ if (message_id)
+ {
+ ((callback_data*)cb_data)->last_message_id_sent = message_id;
+ }
+
+ /* If we are talking to a 5.0 type consumer, we need to wait here and retrieve the
+ * response. Reason is that it can return LDAP_BUSY, indicating that its queue has
+ * filled up. This completely breaks pipelineing, and so we need to fall back to
+ * sync transmission for those consumers, in case they pull the LDAP_BUSY stunt on us :( */
+
+ if (prp->repl50consumer)
+ {
+ /* Get the response here */
+ rc = repl5_tot_get_next_result((callback_data*)cb_data);
+ }
if (rc == CONN_BUSY) {
time_t now = current_time ();
@@ -361,11 +667,15 @@ int send_entry (Slapi_Entry *e, void *cb_data)
ber_bvfree(bv);
(*num_entriesp)++;
+ /* For async operation we need to inspect the abort status from the result thread here */
+
if (CONN_OPERATION_SUCCESS == rc) {
- return 0;
+ retval = 0;
} else {
((callback_data*)cb_data)->rc = rc;
- return -1;
+ retval = -1;
}
+error:
+ return retval;
}
diff --git a/ldap/servers/plugins/replication/repl5_total.c b/ldap/servers/plugins/replication/repl5_total.c
index 5524a46a..ac6190f7 100644
--- a/ldap/servers/plugins/replication/repl5_total.c
+++ b/ldap/servers/plugins/replication/repl5_total.c
@@ -85,7 +85,7 @@ static int my_ber_scanf_value(BerElement *ber, Slapi_Value **value, PRBool *dele
*
*/
BerElement *
-entry2bere(const Slapi_Entry *e)
+entry2bere(const Slapi_Entry *e, char **excluded_attrs)
{
BerElement *ber = NULL;
const char *str = NULL;
@@ -151,12 +151,16 @@ entry2bere(const Slapi_Entry *e)
slapi_attr_get_type (attr, &type);
if (strcasecmp (type, SLAPI_ATTR_UNIQUEID) != 0)
{
- /* Process this attribute */
- rc = my_ber_printf_attr (ber, attr, PR_FALSE);
- if (rc != 0)
- {
- goto loser;
- }
+ /* Check to see if this attribute is excluded by the fractional list */
+ if ( (NULL == excluded_attrs) || !charray_inlist(excluded_attrs,type))
+ {
+ /* Process this attribute */
+ rc = my_ber_printf_attr (ber, attr, PR_FALSE);
+ if (rc != 0)
+ {
+ goto loser;
+ }
+ }
}
prev_attr = attr;
@@ -169,12 +173,17 @@ entry2bere(const Slapi_Entry *e)
entry_first_deleted_attribute(e, &attr);
while (attr != NULL)
{
- /* Process this attribute */
- rc = my_ber_printf_attr (ber, attr, PR_TRUE);
- if (rc != 0)
- {
- goto loser;
- }
+ slapi_attr_get_type (attr, &type);
+ /* Check to see if this attribute is excluded by the fractional list */
+ if ( (NULL == excluded_attrs) || !charray_inlist(excluded_attrs,type))
+ {
+ /* Process this attribute */
+ rc = my_ber_printf_attr (ber, attr, PR_TRUE);
+ if (rc != 0)
+ {
+ goto loser;
+ }
+ }
entry_next_deleted_attribute(e, &attr);
}
BER_DEBUG("]");
@@ -675,7 +684,8 @@ decode_total_update_extop(Slapi_PBlock *pb, Slapi_Entry **ep)
slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value);
if (NULL == extop_oid ||
- strcmp(extop_oid, REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID) != 0 ||
+ ((strcmp(extop_oid, REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID) != 0) &&
+ (strcmp(extop_oid, REPL_NSDS71_REPLICATION_ENTRY_REQUEST_OID) != 0)) ||
NULL == extop_value)
{
/* Bogus */
diff --git a/ldap/servers/plugins/replication/repl_extop.c b/ldap/servers/plugins/replication/repl_extop.c
index 5f518b81..9cce8b04 100644
--- a/ldap/servers/plugins/replication/repl_extop.c
+++ b/ldap/servers/plugins/replication/repl_extop.c
@@ -586,6 +586,27 @@ multimaster_extop_StartNSDS50ReplicationRequest(Slapi_PBlock *pb)
connid, opid, repl_root);
isInc = PR_FALSE;
}
+ else if (strcmp(protocol_oid, REPL_NSDS71_INCREMENTAL_PROTOCOL_OID) == 0)
+ {
+ /* Stash info that this is an incremental update session */
+ connext->repl_protocol_version = REPL_PROTOCOL_50_INCREMENTAL;
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "conn=%d op=%d repl=\"%s\": Begin 7.1 incremental protocol\n",
+ connid, opid, repl_root);
+ isInc = PR_TRUE;
+ }
+ else if (strcmp(protocol_oid, REPL_NSDS71_TOTAL_PROTOCOL_OID) == 0)
+ {
+ /* Stash info that this is a total update session */
+ if (NULL != connext)
+ {
+ connext->repl_protocol_version = REPL_PROTOCOL_71_TOTALUPDATE;
+ }
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "conn=%d op=%d repl=\"%s\": Begin 7.1 total protocol\n",
+ connid, opid, repl_root);
+ isInc = PR_FALSE;
+ }
else
{
/* Unknown replication protocol */
diff --git a/ldap/servers/plugins/replication/repl_globals.c b/ldap/servers/plugins/replication/repl_globals.c
index 4a1e6ac6..bfabf777 100644
--- a/ldap/servers/plugins/replication/repl_globals.c
+++ b/ldap/servers/plugins/replication/repl_globals.c
@@ -8,6 +8,7 @@
#include "repl.h"
char *repl_plugin_name = REPL_PLUGIN_NAME;
+char *windows_repl_plugin_name = REPL_PLUGIN_NAME;
char *repl_plugin_name_cl = REPL_PLUGIN_NAME " - changelog program";
/* String constants (no need to change these for I18N) */
@@ -88,6 +89,12 @@ const char *type_nsds5ReplicaTimeout = "nsds5ReplicaTimeout";
const char *type_nsds5ReplicaBusyWaitTime = "nsds5ReplicaBusyWaitTime";
const char *type_nsds5ReplicaSessionPauseTime = "nsds5ReplicaSessionPauseTime";
+/* windows sync specifica attributes */
+const char *type_nsds7WindowsReplicaArea = "nsds7WindowsReplicaSubtree";
+const char *type_nsds7DirectoryReplicaArea = "nsds7DirectoryReplicaSubtree";
+const char *type_nsds7CreateNewUsers = "nsds7NewWinUserSyncEnabled";
+
+
/* To Allow Consumer Initialisation when adding an agreement - */
const char *type_nsds5BeginReplicaRefresh = "nsds5BeginReplicaRefresh";
diff --git a/ldap/servers/plugins/replication/replutil.c b/ldap/servers/plugins/replication/replutil.c
index e1aab69d..6507b298 100644
--- a/ldap/servers/plugins/replication/replutil.c
+++ b/ldap/servers/plugins/replication/replutil.c
@@ -413,8 +413,8 @@ parse_changes_string(char *str)
if (rc != 0)
{
/* ONREPL - log warning */
- if ( errmsg != NULL ) {
- slapi_log_error( SLAPI_LOG_PARSE, repl_plugin_name, "%s", errmsg );
+ if ( errmsg != NULL ) {
+ slapi_log_error( SLAPI_LOG_PARSE, repl_plugin_name, "%s", errmsg );
PR_smprintf_free(errmsg );
}
slapi_log_error( SLAPI_LOG_REPL, repl_plugin_name,
diff --git a/ldap/servers/plugins/replication/windows_connection.c b/ldap/servers/plugins/replication/windows_connection.c
new file mode 100644
index 00000000..d083ed7d
--- /dev/null
+++ b/ldap/servers/plugins/replication/windows_connection.c
@@ -0,0 +1,1503 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_connection.c */
+/*
+
+ The connection object manages a connection to a single replication
+ consumer.
+
+XXXggood what to do on timeout? If we close connection, then we won't leave a
+replica locked. Seems like right thing to do.
+*/
+
+#include "repl5.h"
+#include "ldappr.h"
+#include "windowsrepl.h"
+
+typedef struct repl_connection
+{
+ char *hostname;
+ int port;
+ char *binddn;
+ int bindmethod;
+ int state;
+ int last_operation;
+ int last_ldap_error;
+ const char *status;
+ char *last_ldap_errmsg;
+ PRUint32 transport_flags;
+ LDAP *ld;
+ int supports_ldapv3; /* 1 if does, 0 if doesn't, -1 if not determined */
+ int supports_ds50_repl; /* 1 if does, 0 if doesn't, -1 if not determined */
+ int supports_ds40_repl; /* 1 if does, 0 if doesn't, -1 if not determined */
+ int linger_time; /* time in seconds to leave an idle connection open */
+ int supports_dirsync; /* 1 if does, 0 if doesn't, -1 if not determined */
+ PRBool linger_active;
+ Slapi_Eq_Context *linger_event;
+ PRBool delete_after_linger;
+ int refcnt;
+ const Repl_Agmt *agmt;
+ PRLock *lock;
+ struct timeval timeout;
+ int flag_agmt_changed;
+ char *plain;
+} repl_connection;
+
+/* #define DEFAULT_LINGER_TIME (5 * 60) */ /* 5 minutes */
+#define DEFAULT_LINGER_TIME (60)
+
+/* Controls we add on every outbound operation */
+
+static LDAPControl manageDSAITControl = {LDAP_CONTROL_MANAGEDSAIT, {0, ""}, '\0'};
+static int attribute_string_value_present(LDAP *ld, LDAPMessage *entry,
+ const char *type, const char *value);
+static int bind_and_check_pwp(Repl_Connection *conn, char * binddn, char *password);
+static int do_simple_bind (Repl_Connection *conn, LDAP *ld, char * binddn, char *password);
+
+static int s_debug_timeout = 0;
+static int s_debug_level = 0;
+static Slapi_Eq_Context repl5_start_debug_timeout(int *setlevel);
+static void repl5_stop_debug_timeout(Slapi_Eq_Context eqctx, int *setlevel);
+static void repl5_debug_timeout_callback(time_t when, void *arg);
+#ifndef DSE_RETURNTEXT_SIZE
+#define SLAPI_DSE_RETURNTEXT_SIZE 512
+#endif
+
+#define STATE_CONNECTED 600
+#define STATE_DISCONNECTED 601
+
+#define STATUS_DISCONNECTED "disconnected"
+#define STATUS_CONNECTED "connected"
+#define STATUS_PROCESSING_ADD "processing add operation"
+#define STATUS_PROCESSING_DELETE "processing delete operation"
+#define STATUS_PROCESSING_MODIFY "processing modify operation"
+#define STATUS_PROCESSING_RENAME "processing rename operation"
+#define STATUS_PROCESSING_EXTENDED_OPERATION "processing extended operation"
+#define STATUS_LINGERING "lingering"
+#define STATUS_SHUTTING_DOWN "shutting down"
+#define STATUS_BINDING "connecting and binding"
+#define STATUS_SEARCHING "processing search operation"
+
+#define CONN_NO_OPERATION 0
+#define CONN_ADD 1
+#define CONN_DELETE 2
+#define CONN_MODIFY 3
+#define CONN_RENAME 4
+#define CONN_EXTENDED_OPERATION 5
+#define CONN_BIND 6
+#define CONN_INIT 7
+#define CONN_SEARCH 8
+
+/* These are errors returned from ldap operations which should cause us to disconnect and
+ retry the connection later */
+#define IS_DISCONNECT_ERROR(rc) (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR || rc == LDAP_INVALID_CREDENTIALS || rc == LDAP_INAPPROPRIATE_AUTH || rc == LDAP_LOCAL_ERROR)
+
+/* Forward declarations */
+static void close_connection_internal(Repl_Connection *conn);
+
+/*
+ * Create a new conenction object. Returns a pointer to the object, or
+ * NULL if an error occurs.
+ */
+Repl_Connection *
+windows_conn_new(Repl_Agmt *agmt)
+{
+ Repl_Connection *rpc;
+
+ rpc = (Repl_Connection *)slapi_ch_malloc(sizeof(repl_connection));
+ if ((rpc->lock = PR_NewLock()) == NULL)
+ {
+ goto loser;
+ }
+ rpc->hostname = agmt_get_hostname(agmt);
+ rpc->port = agmt_get_port(agmt);
+ rpc->binddn = agmt_get_binddn(agmt);
+ rpc->bindmethod = agmt_get_bindmethod(agmt);
+ rpc->transport_flags = agmt_get_transport_flags(agmt);
+ rpc->ld = NULL;
+ rpc->state = STATE_DISCONNECTED;
+ rpc->last_operation = CONN_NO_OPERATION;
+ rpc->last_ldap_error = LDAP_SUCCESS;
+ rpc->last_ldap_errmsg = NULL;
+ rpc->supports_ldapv3 = -1;
+ rpc->supports_ds40_repl = -1;
+ rpc->supports_ds50_repl = -1;
+ rpc->supports_dirsync = -1;
+ rpc->linger_active = PR_FALSE;
+ rpc->delete_after_linger = PR_FALSE;
+ rpc->linger_event = NULL;
+ rpc->linger_time = DEFAULT_LINGER_TIME;
+ rpc->status = STATUS_DISCONNECTED;
+ rpc->agmt = agmt;
+ rpc->refcnt = 1;
+ rpc->timeout.tv_sec = agmt_get_timeout(agmt);
+ rpc->timeout.tv_usec = 0;
+ rpc->flag_agmt_changed = 0;
+ rpc->plain = NULL;
+ return rpc;
+loser:
+ windows_conn_delete(rpc);
+ return NULL;
+}
+
+
+/*
+ * Return PR_TRUE if the connection is in the connected state
+ */
+static PRBool
+windows_conn_connected(Repl_Connection *conn)
+{
+ PRBool return_value;
+ PR_Lock(conn->lock);
+ return_value = STATE_CONNECTED == conn->state;
+ PR_Unlock(conn->lock);
+ return return_value;
+}
+
+
+/*
+ * Destroy a connection object.
+ */
+static void
+windows_conn_delete_internal(Repl_Connection *conn)
+{
+ PR_ASSERT(NULL != conn);
+ close_connection_internal(conn);
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free((void **)&conn->hostname);
+ slapi_ch_free((void **)&conn->binddn);
+ slapi_ch_free((void **)&conn->plain);
+}
+
+/*
+ * Destroy a connection. It is an error to use the connection object
+ * after windows_conn_delete() has been called.
+ */
+void
+windows_conn_delete(Repl_Connection *conn)
+{
+ PRBool destroy_it = PR_FALSE;
+
+ PR_ASSERT(NULL != conn);
+ PR_Lock(conn->lock);
+ if (conn->linger_active)
+ {
+ if (slapi_eq_cancel(conn->linger_event) == 1)
+ {
+ /* Event was found and cancelled. Destroy the connection object. */
+ PR_Unlock(conn->lock);
+ destroy_it = PR_TRUE;
+ }
+ else
+ {
+ /*
+ * The event wasn't found, but we think it's still active.
+ * That means an event is in the process of being fired
+ * off, so arrange for the event to destroy the object .
+ */
+ conn->delete_after_linger = PR_TRUE;
+ PR_Unlock(conn->lock);
+ }
+ }
+ if (destroy_it)
+ {
+ windows_conn_delete_internal(conn);
+ }
+}
+
+
+/*
+ * Return the last operation type processed by the connection
+ * object, and the LDAP error encountered.
+ */
+void
+windows_conn_get_error(Repl_Connection *conn, int *operation, int *error)
+{
+ PR_Lock(conn->lock);
+ *operation = conn->last_operation;
+ *error = conn->last_ldap_error;
+ PR_Unlock(conn->lock);
+}
+
+
+/*
+ * Common code to send an LDAPv3 operation and collect the result.
+ * Return values:
+ * CONN_OPERATION_SUCCESS - the operation succeeded
+ * CONN_OPERATION_FAILED - the operation was sent to the consumer
+ * and failed. Use conn_get_error() to determine the LDAP error
+ * code.
+ * CONN_NOT_CONNECTED - no connection is active. The caller should
+ * use conn_connect() to connect to the replica and bind, then should
+ * reacquire the replica (if needed).
+ * CONN_BUSY - the server is busy with previous requests, must wait for a while
+ * before retrying
+ */
+static ConnResult
+windows_perform_operation(Repl_Connection *conn, int optype, const char *dn,
+ LDAPMod **attrs, const char *newrdn, const char *newparent,
+ int deleteoldrdn, LDAPControl *update_control,
+ const char *extop_oid, struct berval *extop_payload, char **retoidp,
+ struct berval **retdatap, LDAPControl ***returned_controls)
+{
+ int rc;
+ ConnResult return_value;
+ LDAPControl *server_controls[1];
+ LDAPControl **loc_returned_controls;
+ const char *op_string = NULL;
+ const char *extra_op_string = NULL;
+
+ server_controls[0] = NULL;
+
+ if (windows_conn_connected(conn))
+ {
+ int msgid;
+
+ conn->last_operation = optype;
+ switch (optype)
+ {
+ case CONN_ADD:
+ conn->status = STATUS_PROCESSING_ADD;
+ op_string = "add";
+ rc = ldap_add_ext(conn->ld, dn, attrs, server_controls,
+ NULL /* clientctls */, &msgid);
+ break;
+ case CONN_MODIFY:
+ conn->status = STATUS_PROCESSING_MODIFY;
+ op_string = "modify";
+ rc = ldap_modify_ext(conn->ld, dn, attrs, server_controls,
+ NULL /* clientctls */, &msgid);
+ break;
+ case CONN_DELETE:
+ conn->status = STATUS_PROCESSING_DELETE;
+ op_string = "delete";
+ rc = ldap_delete_ext(conn->ld, dn, server_controls,
+ NULL /* clientctls */, &msgid);
+ break;
+ case CONN_RENAME:
+ conn->status = STATUS_PROCESSING_RENAME;
+ op_string = "rename";
+ rc = ldap_rename(conn->ld, dn, newrdn, newparent, deleteoldrdn,
+ server_controls, NULL /* clientctls */, &msgid);
+ break;
+ case CONN_EXTENDED_OPERATION:
+ conn->status = STATUS_PROCESSING_EXTENDED_OPERATION;
+ op_string = "extended";
+ extra_op_string = extop_oid;
+ rc = ldap_extended_operation(conn->ld, extop_oid, extop_payload,
+ server_controls, NULL /* clientctls */, &msgid);
+ }
+ if (LDAP_SUCCESS == rc)
+ {
+ LDAPMessage *res = NULL;
+ int setlevel = 0;
+ Slapi_Eq_Context eqctx = repl5_start_debug_timeout(&setlevel);
+
+ rc = ldap_result(conn->ld, msgid, 1, &conn->timeout, &res);
+ repl5_stop_debug_timeout(eqctx, &setlevel);
+ if (0 == rc)
+ {
+ /* Timeout */
+ rc = ldap_get_lderrno(conn->ld, NULL, NULL);
+ conn->last_ldap_error = LDAP_TIMEOUT;
+ return_value = CONN_TIMEOUT;
+ }
+ else if (-1 == rc)
+ {
+ /* Error */
+ char *s = NULL;
+
+ rc = ldap_get_lderrno(conn->ld, NULL, &s);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Received error %d: %s for %s operation\n",
+ agmt_get_long_name(conn->agmt),
+ rc, s ? s : "NULL",
+ op_string ? op_string : "NULL");
+ conn->last_ldap_error = rc;
+ /* some errors will require a disconnect and retry the connection
+ later */
+ if (IS_DISCONNECT_ERROR(rc))
+ {
+ windows_conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else
+ {
+ conn->status = STATUS_CONNECTED;
+ return_value = CONN_OPERATION_FAILED;
+ }
+ }
+ else
+ {
+ int err;
+ char *errmsg = NULL;
+ char **referrals = NULL;
+ char *matched = NULL;
+
+ rc = ldap_parse_result(conn->ld, res, &err, &matched,
+ &errmsg, &referrals, &loc_returned_controls,
+ 0 /* Don't free the result */);
+ if (IS_DISCONNECT_ERROR(rc))
+ {
+ conn->last_ldap_error = rc;
+ windows_conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else if (IS_DISCONNECT_ERROR(err))
+ {
+ conn->last_ldap_error = err;
+ windows_conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else if (err == LDAP_UNWILLING_TO_PERFORM && optype == CONN_MODIFY)
+ {
+ /* this permits password updates to fail gracefully */
+ conn->last_ldap_error = LDAP_SUCCESS;
+ return_value = CONN_OPERATION_SUCCESS;
+ }
+ else if (err == LDAP_ALREADY_EXISTS && optype == CONN_ADD)
+ {
+ conn->last_ldap_error = LDAP_SUCCESS;
+ return_value = CONN_OPERATION_SUCCESS;
+ }
+ else if (err == LDAP_NO_SUCH_OBJECT && optype == CONN_DELETE)
+ {
+ conn->last_ldap_error = LDAP_SUCCESS;
+ return_value = CONN_OPERATION_SUCCESS;
+ }
+ else /* regular operation, result returned */
+ {
+ if (NULL != returned_controls)
+ {
+ *returned_controls = loc_returned_controls;
+ }
+ if (LDAP_SUCCESS != rc)
+ {
+ conn->last_ldap_error = rc;
+ }
+ else
+ {
+ conn->last_ldap_error = err;
+ }
+ return_value = LDAP_SUCCESS == conn->last_ldap_error ? CONN_OPERATION_SUCCESS : CONN_OPERATION_FAILED;
+ }
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Received result code %d for %s operation %s%s\n",
+ agmt_get_long_name(conn->agmt),
+ conn->last_ldap_error,
+ op_string == NULL ? "" : op_string,
+ extra_op_string == NULL ? "" : extra_op_string,
+ extra_op_string == NULL ? "" : " ");
+ /*
+ * XXXggood do I need to free matched, referrals,
+ * anything else? Or can I pass NULL for the args
+ * I'm not interested in?
+ */
+ /* Good question! Meanwhile, as RTM aproaches, let's free them... */
+ slapi_ch_free((void **) &errmsg);
+ slapi_ch_free((void **) &matched);
+ charray_free(referrals);
+ conn->status = STATUS_CONNECTED;
+ }
+ if (res) ldap_msgfree(res);
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Failed to send %s operation: LDAP error %d (%s)\n",
+ agmt_get_long_name(conn->agmt),
+ op_string ? op_string : "NULL", rc, ldap_err2string(rc));
+ conn->last_ldap_error = rc;
+ if (IS_DISCONNECT_ERROR(rc))
+ {
+ windows_conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else
+ {
+ conn->status = STATUS_CONNECTED;
+ return_value = CONN_OPERATION_FAILED;
+ }
+ }
+ }
+ else
+ {
+ /* conn->last_ldap_error has been set to a more specific value
+ * in windows_conn_connected()
+ * conn->last_ldap_error = LDAP_SERVER_DOWN;
+ */
+ return_value = CONN_NOT_CONNECTED;
+ }
+ return return_value;
+}
+
+ConnResult
+perform_search(Repl_Connection *conn)
+{
+ int rc;
+ ConnResult return_value;
+ LDAPControl *server_controls[2];
+ int msgid;
+
+ const char *op_string = NULL;
+
+ const char* old_dn = NULL;
+ char* dn = NULL;
+ int i=0;
+ int num_comp=0;
+
+ /* need to strip the dn down to dc= */
+ /* XXX: this is not the most elegant way of doing this */
+ old_dn = slapi_sdn_get_ndn( windows_private_get_windows_replarea(conn->agmt) );
+ dn = strstr(old_dn, "dc=");
+
+ if (windows_conn_connected(conn))
+ {
+ if (conn->supports_dirsync == 0)
+ server_controls[0] = NULL; /* unsupported */
+ else
+ server_controls[0] = windows_private_dirsync_control(conn->agmt); /* yes, or don't know */
+
+ server_controls[1] = NULL;
+ conn->last_operation = CONN_SEARCH;
+ conn->status = STATUS_SEARCHING;
+ op_string = "search";
+
+ rc = ldap_search_ext( conn->ld, dn, LDAP_SCOPE_SUBTREE, "(objectclass=*)", /* filter */
+ NULL /*attrs */, PR_FALSE, server_controls, NULL, /* ClientControls */
+ 0,0, &msgid);
+
+ if (LDAP_SUCCESS == rc)
+ {
+
+ int setlevel = 0;
+ int finished = 0;
+ return_value = 0;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Failed to get %s operation: LDAP error %d (%s)\n",
+ agmt_get_long_name(conn->agmt),
+ op_string ? op_string : "NULL", rc, ldap_err2string(rc));
+ conn->last_ldap_error = rc;
+ if (IS_DISCONNECT_ERROR(rc))
+ {
+ windows_conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else
+ {
+ conn->status = STATUS_CONNECTED;
+ return_value = CONN_OPERATION_FAILED;
+ }
+ }
+ }
+ else
+ {
+ /* conn->last_ldap_error has been set to a more specific value
+ * in windows_conn_connected()
+ * conn->last_ldap_error = LDAP_SERVER_DOWN;
+ */
+ return_value = CONN_NOT_CONNECTED;
+ }
+ return return_value;
+}
+
+
+/*
+ * Send an LDAP add operation.
+ */
+ConnResult
+windows_conn_send_add(Repl_Connection *conn, const char *dn, LDAPMod **attrs,
+ LDAPControl *update_control, LDAPControl ***returned_controls)
+{
+ return windows_perform_operation(conn, CONN_ADD, dn, attrs, NULL /* newrdn */,
+ NULL /* newparent */, 0 /* deleteoldrdn */, update_control,
+ NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */,
+ NULL /* retdatap */, returned_controls);
+}
+
+
+/*
+ * Send an LDAP delete operation.
+ */
+ConnResult
+windows_conn_send_delete(Repl_Connection *conn, const char *dn,
+ LDAPControl *update_control, LDAPControl ***returned_controls)
+{
+ return windows_perform_operation(conn, CONN_DELETE, dn, NULL /* attrs */,
+ NULL /* newrdn */, NULL /* newparent */, 0 /* deleteoldrdn */,
+ update_control, NULL /* extop OID */, NULL /* extop payload */,
+ NULL /* retoidp */, NULL /* retdatap */, returned_controls);
+}
+
+
+/*
+ * Send an LDAP modify operation.
+ */
+ConnResult
+windows_conn_send_modify(Repl_Connection *conn, const char *dn, LDAPMod **mods,
+ LDAPControl *update_control, LDAPControl ***returned_controls)
+{
+ return windows_perform_operation(conn, CONN_MODIFY, dn, mods, NULL /* newrdn */,
+ NULL /* newparent */, 0 /* deleteoldrdn */, update_control,
+ NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */,
+ NULL /* retdatap */, returned_controls);
+}
+
+/*
+ * Send an LDAP moddn operation.
+ */
+ConnResult
+windows_conn_send_rename(Repl_Connection *conn, const char *dn,
+ const char *newrdn, const char *newparent, int deleteoldrdn,
+ LDAPControl *update_control, LDAPControl ***returned_controls)
+{
+ return windows_perform_operation(conn, CONN_RENAME, dn, NULL /* attrs */,
+ newrdn, newparent, deleteoldrdn, update_control,
+ NULL /* extop OID */, NULL /* extop payload */, NULL /* retoidp */,
+ NULL /* retdatap */, returned_controls);
+}
+
+/*
+ * Send an LDAP search operation.
+ */
+
+Slapi_Entry * windows_conn_get_search_result(Repl_Connection *conn)
+{
+ int rc=0;
+ int matches=0;
+ LDAPMessage *res = NULL;
+ Slapi_Entry *e = NULL;
+ LDAPMessage *lm = NULL;
+ char *a = "";
+ char *dn = "";
+ BerElement *ber = NULL;
+ if (windows_conn_connected(conn))
+ {
+ rc = ldap_result( conn->ld, LDAP_RES_ANY, 0, &conn->timeout, &res );
+ switch (rc) {
+ case 0:
+ case -1:
+ case LDAP_RES_SEARCH_REFERENCE:
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "error, rc=%d\n", rc);
+ ldap_msgfree( res );
+ break;
+ case LDAP_RES_SEARCH_RESULT:
+ {
+ LDAPControl **returned_controls;
+ int code=0;
+ int parse_rc;
+ parse_rc = ldap_parse_result( conn->ld, res, &code, NULL, NULL, NULL, &returned_controls, 1 );
+ windows_private_update_dirsync_control(conn->agmt, returned_controls);
+ }
+ break;
+ case LDAP_RES_SEARCH_ENTRY:
+ {
+ if (( dn = ldap_get_dn( conn->ld, res )) != NULL )
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,"found an entry %s\n", dn);
+ lm = ldap_first_entry( conn->ld, res );
+ e = slapi_entry_alloc();
+ slapi_entry_init(e, dn, NULL);
+ for ( a = ldap_first_attribute( conn->ld, lm, &ber ); a != NULL; a = ldap_next_attribute( conn->ld, lm, ber ) )
+ {
+ int i =0;
+ char **vals;
+
+ if ((vals = ldap_get_values( conn->ld, lm, a)) != NULL )
+ {
+ for ( i = 0; vals[i] != NULL; i++ )
+ {
+ slapi_entry_add_string (e, a, vals[i]);
+ }
+ ldap_value_free( vals );
+ }
+ }
+ }
+ }
+ break;
+
+ } // switch
+ } //if
+
+ return e;
+
+}
+
+
+/*
+ * Send an LDAP extended operation.
+ */
+ConnResult
+windows_conn_send_extended_operation(Repl_Connection *conn, const char *extop_oid,
+ struct berval *payload, char **retoidp, struct berval **retdatap,
+ LDAPControl *update_control, LDAPControl ***returned_controls)
+{
+ return windows_perform_operation(conn, CONN_EXTENDED_OPERATION, NULL /* dn */, NULL /* attrs */,
+ NULL /* newrdn */, NULL /* newparent */, 0 /* deleteoldrdn */,
+ update_control, extop_oid, payload, retoidp, retdatap,
+ returned_controls);
+}
+
+
+/*
+ * Synchronously read an entry and return a specific attribute's values.
+ * Returns CONN_OPERATION_SUCCESS if successful. Returns
+ * CONN_OPERATION_FAILED if the operation was sent but an LDAP error
+ * occurred (conn->last_ldap_error is set in this case), and
+ * CONN_NOT_CONNECTED if no connection was active.
+ *
+ * The caller must free the returned_bvals.
+ */
+ConnResult
+windows_conn_read_entry_attribute(Repl_Connection *conn, const char *dn,
+ char *type, struct berval ***returned_bvals)
+{
+ ConnResult return_value;
+ int ldap_rc;
+ LDAPControl *server_controls[2];
+ LDAPMessage *res = NULL;
+ char *attrs[2];
+
+ PR_ASSERT(NULL != type);
+ if (windows_conn_connected(conn))
+ {
+ server_controls[0] = &manageDSAITControl;
+ server_controls[1] = NULL;
+ attrs[0] = type;
+ attrs[1] = NULL;
+ ldap_rc = ldap_search_ext_s(conn->ld, dn, LDAP_SCOPE_BASE,
+ "(objectclass=*)", attrs, 0 /* attrsonly */,
+ server_controls, NULL /* client controls */,
+ &conn->timeout, 0 /* sizelimit */, &res);
+ if (LDAP_SUCCESS == ldap_rc)
+ {
+ LDAPMessage *entry = ldap_first_entry(conn->ld, res);
+ if (NULL != entry)
+ {
+ *returned_bvals = ldap_get_values_len(conn->ld, entry, type);
+ }
+ return_value = CONN_OPERATION_SUCCESS;
+ }
+ else if (IS_DISCONNECT_ERROR(ldap_rc))
+ {
+ windows_conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else
+ {
+ return_value = CONN_OPERATION_FAILED;
+ }
+ conn->last_ldap_error = ldap_rc;
+ if (NULL != res)
+ {
+ ldap_msgfree(res);
+ res = NULL;
+ }
+ }
+ else
+ {
+ return_value = CONN_NOT_CONNECTED;
+ }
+ return return_value;
+}
+
+
+/*
+ * Return an pointer to a string describing the connection's status.
+*/
+
+const char *
+windows_conn_get_status(Repl_Connection *conn)
+{
+ return conn->status;
+}
+
+
+
+/*
+ * Cancel any outstanding linger timer. Should be called when
+ * a replication session is beginning.
+ */
+void
+windows_conn_cancel_linger(Repl_Connection *conn)
+{
+ PR_ASSERT(NULL != conn);
+ PR_Lock(conn->lock);
+ if (conn->linger_active)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Cancelling linger on the connection\n",
+ agmt_get_long_name(conn->agmt));
+ conn->linger_active = PR_FALSE;
+ if (slapi_eq_cancel(conn->linger_event) == 1)
+ {
+ conn->refcnt--;
+ }
+ conn->linger_event = NULL;
+ conn->status = STATUS_CONNECTED;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: No linger to cancel on the connection\n",
+ agmt_get_long_name(conn->agmt));
+ }
+ PR_Unlock(conn->lock);
+}
+
+
+/*
+ * Called when our linger timeout timer expires. This means
+ * we should check to see if perhaps the connection's become
+ * active again, in which case we do nothing. Otherwise,
+ * we close the connection.
+ */
+static void
+linger_timeout(time_t event_time, void *arg)
+{
+ PRBool delete_now;
+ Repl_Connection *conn = (Repl_Connection *)arg;
+
+ PR_ASSERT(NULL != conn);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Linger timeout has expired on the connection\n",
+ agmt_get_long_name(conn->agmt));
+ PR_Lock(conn->lock);
+ if (conn->linger_active)
+ {
+ conn->linger_active = PR_FALSE;
+ conn->linger_event = NULL;
+ close_connection_internal(conn);
+ }
+ delete_now = conn->delete_after_linger;
+ PR_Unlock(conn->lock);
+ if (delete_now)
+ {
+ windows_conn_delete_internal(conn);
+ }
+}
+
+
+/*
+ * Indicate that a session is ending. The linger timer starts when
+ * this function is called.
+ */
+void
+windows_conn_start_linger(Repl_Connection *conn)
+{
+ time_t now;
+
+ PR_ASSERT(NULL != conn);
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Beginning linger on the connection\n",
+ agmt_get_long_name(conn->agmt));
+ if (!windows_conn_connected(conn))
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: No linger on the closed conn\n",
+ agmt_get_long_name(conn->agmt));
+ return;
+ }
+ time(&now);
+ PR_Lock(conn->lock);
+ if (conn->linger_active)
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Linger already active on the connection\n",
+ agmt_get_long_name(conn->agmt));
+ }
+ else
+ {
+ conn->linger_active = PR_TRUE;
+ conn->linger_event = slapi_eq_once(linger_timeout, conn, now + conn->linger_time);
+ conn->status = STATUS_LINGERING;
+ }
+ PR_Unlock(conn->lock);
+}
+
+
+
+/*
+ * If no connection is currently active, opens a connection and binds to
+ * the remote server. If a connection is open (e.g. lingering) then
+ * this is a no-op.
+ *
+ * Returns CONN_OPERATION_SUCCESS on success, or CONN_OPERATION_FAILED
+ * on failure. Sets conn->last_ldap_error and conn->last_operation;
+ */
+ConnResult
+windows_conn_connect(Repl_Connection *conn)
+{
+ int ldap_rc;
+ int optdata;
+ int secure = 0;
+ char* binddn = NULL;
+ struct berval *creds;
+ ConnResult return_value = CONN_OPERATION_SUCCESS;
+ int pw_ret = 1;
+
+ /** Connection already open just return SUCCESS **/
+ if(conn->state == STATE_CONNECTED) return return_value;
+
+ PR_Lock(conn->lock);
+ if (conn->flag_agmt_changed) {
+ /* So far we cannot change Hostname and Port */
+ /* slapi_ch_free((void **)&conn->hostname); */
+ /* conn->hostname = agmt_get_hostname(conn->agmt); */
+ /* conn->port = agmt_get_port(conn->agmt); */
+ slapi_ch_free((void **)&conn->binddn);
+ conn->binddn = agmt_get_binddn(conn->agmt);
+ conn->bindmethod = agmt_get_bindmethod(conn->agmt);
+ conn->transport_flags = agmt_get_transport_flags(conn->agmt);
+ conn->timeout.tv_sec = agmt_get_timeout(conn->agmt);
+ conn->flag_agmt_changed = 0;
+ slapi_ch_free((void **)&conn->plain);
+ }
+ PR_Unlock(conn->lock);
+
+ creds = agmt_get_credentials(conn->agmt);
+
+ if (conn->plain == NULL) {
+
+ char *plain = NULL;
+
+ /* kexcoff: for reversible encryption */
+ /* We need to test the return code of pw_rever_decode in order to decide
+ * if a free for plain will be needed (pw_ret == 0) or not (pw_ret != 0) */
+ pw_ret = pw_rever_decode(creds->bv_val, &plain, type_nsds5ReplicaCredentials);
+ /* Pb occured in decryption: stop now, binding will fail */
+ if ( pw_ret == -1 )
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Decoding of the credentials failed.\n",
+ agmt_get_long_name(conn->agmt));
+
+ return_value = CONN_OPERATION_FAILED;
+ conn->last_ldap_error = LDAP_INVALID_CREDENTIALS;
+ conn->state = STATE_DISCONNECTED;
+ return (return_value);
+ } /* Else, does not mean that the plain is correct, only means the we had no internal
+ decoding pb */
+ conn->plain = slapi_ch_strdup (plain);
+ if (!pw_ret) slapi_ch_free((void**)&plain);
+ }
+
+
+ /* ugaston: if SSL has been selected in the replication agreement, SSL client
+ * initialisation should be done before ever trying to open any connection at all.
+ */
+ if (conn->transport_flags == TRANSPORT_FLAG_TLS)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Replication secured by StartTLS not currently supported\n",
+ agmt_get_long_name(conn->agmt));
+
+ return_value = CONN_OPERATION_FAILED;
+ conn->last_ldap_error = LDAP_STRONG_AUTH_NOT_SUPPORTED;
+ conn->state = STATE_DISCONNECTED;
+ } else if(conn->transport_flags == TRANSPORT_FLAG_SSL)
+ {
+
+ /** Make sure the SSL Library has been initialized before anything else **/
+ if(slapd_security_library_is_initialized() != 1)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: SSL Not Initialized, Replication over SSL FAILED\n",
+ agmt_get_long_name(conn->agmt));
+ conn->last_ldap_error = LDAP_INAPPROPRIATE_AUTH;
+ conn->last_operation = CONN_INIT;
+ ber_bvfree(creds);
+ creds = NULL;
+ return CONN_SSL_NOT_ENABLED;
+ } else
+ {
+ secure = 1;
+ }
+ }
+
+ if (return_value == CONN_OPERATION_SUCCESS) {
+ int io_timeout_ms;
+ /* Now we initialize the LDAP Structure and set options */
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Trying %s slapi_ldap_init\n",
+ agmt_get_long_name(conn->agmt),
+ secure ? "secure" : "non-secure");
+
+ conn->ld = slapi_ldap_init(conn->hostname, conn->port, secure, 0);
+ if (NULL == conn->ld)
+ {
+ return_value = CONN_OPERATION_FAILED;
+ conn->state = STATE_DISCONNECTED;
+ conn->last_operation = CONN_INIT;
+ conn->last_ldap_error = LDAP_LOCAL_ERROR;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Failed to establish %sconnection to the consumer\n",
+ agmt_get_long_name(conn->agmt),
+ secure ? "secure " : "");
+ ber_bvfree(creds);
+ creds = NULL;
+ return return_value;
+ }
+
+ /* slapi_ch_strdup is OK with NULL strings */
+ binddn = slapi_ch_strdup(conn->binddn);
+
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: binddn = %s, passwd = %s\n",
+ agmt_get_long_name(conn->agmt),
+ binddn?binddn:"NULL", creds->bv_val?creds->bv_val:"NULL");
+
+ /* Set some options for the connection. */
+ optdata = LDAP_DEREF_NEVER; /* Don't dereference aliases */
+ ldap_set_option(conn->ld, LDAP_OPT_DEREF, &optdata);
+
+ optdata = LDAP_VERSION3; /* We need LDAP version 3 */
+ ldap_set_option(conn->ld, LDAP_OPT_PROTOCOL_VERSION, &optdata);
+
+ /* Don't chase any referrals (although we shouldn't get any) */
+ ldap_set_option(conn->ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
+
+ /* override the default timeout with the specified timeout */
+ io_timeout_ms = conn->timeout.tv_sec * 1000 + conn->timeout.tv_usec / 1000;
+ prldap_set_session_option(conn->ld, NULL, PRLDAP_OPT_IO_MAX_TIMEOUT,
+ io_timeout_ms);
+
+ /* We've got an ld. Now bind to the server. */
+ conn->last_operation = CONN_BIND;
+
+ }
+
+ if ( bind_and_check_pwp(conn, binddn, conn->plain) == CONN_OPERATION_FAILED )
+ {
+ conn->last_ldap_error = ldap_get_lderrno (conn->ld, NULL, NULL);
+ conn->state = STATE_DISCONNECTED;
+ return_value = CONN_OPERATION_FAILED;
+ }
+ else
+ {
+ conn->last_ldap_error = ldap_rc = LDAP_SUCCESS;
+ conn->state = STATE_CONNECTED;
+ return_value = CONN_OPERATION_SUCCESS;
+ }
+
+ windows_conn_replica_supports_dirsync(conn);
+
+ ber_bvfree(creds);
+ creds = NULL;
+
+ slapi_ch_free((void**)&binddn);
+
+ if(return_value == CONN_OPERATION_FAILED)
+ {
+ close_connection_internal(conn);
+ } else
+ {
+ conn->last_ldap_error = ldap_rc = LDAP_SUCCESS;
+ conn->state = STATE_CONNECTED;
+ }
+
+ return return_value;
+}
+
+
+static void
+close_connection_internal(Repl_Connection *conn)
+{
+ if (NULL != conn->ld)
+ {
+ /* Since we call slapi_ldap_init,
+ we must call slapi_ldap_unbind */
+ slapi_ldap_unbind(conn->ld);
+ }
+ conn->ld = NULL;
+ conn->state = STATE_DISCONNECTED;
+ conn->status = STATUS_DISCONNECTED;
+ conn->supports_ds50_repl = -1;
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "%s: Disconnected from the consumer\n", agmt_get_long_name(conn->agmt));
+}
+
+void
+windows_conn_disconnect(Repl_Connection *conn)
+{
+ PR_ASSERT(NULL != conn);
+ PR_Lock(conn->lock);
+ close_connection_internal(conn);
+ PR_Unlock(conn->lock);
+}
+
+
+/*
+ * Determine if the remote replica supports DS 5.0 replication.
+ * Return codes:
+ * CONN_SUPPORTS_DS5_REPL - the remote replica suport DS5 replication
+ * CONN_DOES_NOT_SUPPORT_DS5_REPL - the remote replica does not
+ * support DS5 replication.
+ * CONN_OPERATION_FAILED - it could not be determined if the remote
+ * replica supports DS5 replication.
+ * CONN_NOT_CONNECTED - no connection was active.
+ */
+ConnResult
+windows_conn_replica_supports_ds5_repl(Repl_Connection *conn)
+{
+ ConnResult return_value;
+ int ldap_rc;
+
+ if (windows_conn_connected(conn))
+ {
+ if (conn->supports_ds50_repl == -1) {
+ LDAPMessage *res = NULL;
+ LDAPMessage *entry = NULL;
+ char *attrs[] = {"supportedcontrol", "supportedextension", NULL};
+
+ conn->status = STATUS_SEARCHING;
+ ldap_rc = ldap_search_ext_s(conn->ld, "", LDAP_SCOPE_BASE,
+ "(objectclass=*)", attrs, 0 /* attrsonly */,
+ NULL /* server controls */, NULL /* client controls */,
+ &conn->timeout, LDAP_NO_LIMIT, &res);
+ if (LDAP_SUCCESS == ldap_rc)
+ {
+ conn->supports_ds50_repl = 0;
+ entry = ldap_first_entry(conn->ld, res);
+ if (!attribute_string_value_present(conn->ld, entry, "supportedcontrol", REPL_NSDS50_UPDATE_INFO_CONTROL_OID))
+ {
+ return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL;
+ }
+ else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_START_NSDS50_REPLICATION_REQUEST_OID))
+ {
+ return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL;
+ }
+ else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_END_NSDS50_REPLICATION_REQUEST_OID))
+ {
+ return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL;
+ }
+ else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID))
+ {
+ return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL;
+ }
+ else if (!attribute_string_value_present(conn->ld, entry, "supportedextension", REPL_NSDS50_REPLICATION_RESPONSE_OID))
+ {
+ return_value = CONN_DOES_NOT_SUPPORT_DS5_REPL;
+ }
+ else
+ {
+ conn->supports_ds50_repl = 1;
+ return_value = CONN_SUPPORTS_DS5_REPL;
+ }
+ }
+ else
+ {
+ if (IS_DISCONNECT_ERROR(ldap_rc))
+ {
+ conn->last_ldap_error = ldap_rc; /* specific reason */
+ windows_conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else
+ {
+ return_value = CONN_OPERATION_FAILED;
+ }
+ }
+ if (NULL != res)
+ ldap_msgfree(res);
+ }
+ else {
+ return_value = conn->supports_ds50_repl ? CONN_SUPPORTS_DS5_REPL : CONN_DOES_NOT_SUPPORT_DS5_REPL;
+ }
+ }
+ else
+ {
+ /* Not connected */
+ return_value = CONN_NOT_CONNECTED;
+ }
+ return return_value;
+}
+
+
+ConnResult
+windows_conn_replica_supports_dirsync(Repl_Connection *conn)
+{
+ ConnResult return_value;
+ int ldap_rc;
+
+ if (windows_conn_connected(conn))
+ {
+ if (conn->supports_dirsync == -1) {
+ LDAPMessage *res = NULL;
+ LDAPMessage *entry = NULL;
+ char *attrs[] = {"supportedcontrol", NULL};
+
+ conn->status = STATUS_SEARCHING;
+ ldap_rc = ldap_search_ext_s(conn->ld, "", LDAP_SCOPE_BASE,
+ "(objectclass=*)", attrs, 0 /* attrsonly */,
+ NULL /* server controls */, NULL /* client controls */,
+ &conn->timeout, LDAP_NO_LIMIT, &res);
+ if (LDAP_SUCCESS == ldap_rc)
+ {
+ conn->supports_dirsync = 0;
+ entry = ldap_first_entry(conn->ld, res);
+ if (!attribute_string_value_present(conn->ld, entry, "supportedcontrol", REPL_DIRSYNC_CONTROL_OID))
+ {
+ return_value = CONN_DOES_NOT_SUPPORT_DIRSYNC;
+ }
+ else
+ {
+
+ conn->supports_dirsync =1;
+ return_value = CONN_SUPPORTS_DIRSYNC;
+ }
+ }
+ else
+ {
+ if (IS_DISCONNECT_ERROR(ldap_rc))
+ {
+ conn->last_ldap_error = ldap_rc; /* specific reason */
+ windows_conn_disconnect(conn);
+ return_value = CONN_NOT_CONNECTED;
+ }
+ else
+ {
+ return_value = CONN_OPERATION_FAILED;
+ }
+ }
+ if (NULL != res)
+ ldap_msgfree(res);
+ }
+ else {
+ return_value = conn->supports_dirsync ? CONN_SUPPORTS_DIRSYNC : CONN_DOES_NOT_SUPPORT_DIRSYNC;
+ }
+ }
+ else
+ {
+ /* Not connected */
+ return_value = CONN_NOT_CONNECTED;
+ }
+ return return_value;
+}
+
+
+
+/*
+ * Return 1 if "value" is a value of attribute type "type" in entry "entry".
+ * Otherwise, return 0.
+ */
+static int
+attribute_string_value_present(LDAP *ld, LDAPMessage *entry, const char *type,
+ const char *value)
+{
+ int return_value = 0;
+
+ if (NULL != entry)
+ {
+ char *atype = NULL;
+ BerElement *ber = NULL;
+
+ atype = ldap_first_attribute(ld, entry, &ber);
+ while (NULL != atype && 0 == return_value)
+ {
+ if (strcasecmp(atype, type) == 0)
+ {
+ char **strvals = ldap_get_values(ld, entry, atype);
+ int i;
+ for (i = 0; return_value == 0 && NULL != strvals && NULL != strvals[i]; i++)
+ {
+ if (strcmp(strvals[i], value) == 0)
+ {
+ return_value = 1;
+ }
+ }
+ if (NULL != strvals)
+ {
+ ldap_value_free(strvals);
+ }
+ }
+ ldap_memfree(atype);
+ atype = ldap_next_attribute(ld, entry, ber);
+ }
+ if (NULL != ber)
+ ldap_ber_free(ber, 0);
+ /* The last atype has not been freed yet */
+ if (NULL != atype)
+ ldap_memfree(atype);
+ }
+ return return_value;
+}
+
+
+
+
+/*
+ * Read the remote server's schema entry, then read the local schema entry,
+ * and compare the nsschemacsn attribute. If the local csn is newer, or
+ * the remote csn is absent, push the schema down to the consumer.
+ * Return codes:
+ * CONN_SCHEMA_UPDATED if the schema was pushed successfully
+ * CONN_SCHEMA_NO_UPDATE_NEEDED if the schema was as new or newer than
+ * the local server's schema
+ * CONN_OPERATION_FAILED if an error occurred
+ * CONN_NOT_CONNECTED if no connection was active
+ * NOTE: Should only be called when a replication session has been
+ * established by sending a startReplication extended operation.
+ */
+
+void
+windows_conn_set_timeout(Repl_Connection *conn, long timeout)
+{
+ PR_ASSERT(NULL != conn);
+ PR_ASSERT(timeout >= 0);
+ PR_Lock(conn->lock);
+ conn->timeout.tv_sec = timeout;
+ PR_Unlock(conn->lock);
+}
+
+void windows_conn_set_agmt_changed(Repl_Connection *conn)
+{
+ PR_ASSERT(NULL != conn);
+ PR_Lock(conn->lock);
+ if (NULL != conn->agmt)
+ conn->flag_agmt_changed = 1;
+ PR_Unlock(conn->lock);
+}
+
+/*
+ * Check the result of an ldap_simple_bind operation to see we it
+ * contains the expiration controls
+ * return: -1 error, not bound
+ * 0, OK bind has succeeded
+ */
+static int
+bind_and_check_pwp(Repl_Connection *conn, char * binddn, char *password)
+{
+
+ LDAPControl **ctrls = NULL;
+ LDAPMessage *res = NULL;
+ char *errmsg = NULL;
+ LDAP *ld = conn->ld;
+ int msgid;
+ int *msgidAdr = &msgid;
+ int rc;
+
+ char * optype; /* ldap_simple_bind or slapd_SSL_client_bind */
+
+ if ( conn->transport_flags == TRANSPORT_FLAG_SSL )
+ {
+ char *auth;
+ optype = "ldap_sasl_bind";
+
+ if ( conn->bindmethod == BINDMETHOD_SSL_CLIENTAUTH )
+ {
+ rc = slapd_sasl_ext_client_bind(conn->ld, &msgidAdr);
+ auth = "SSL client authentication";
+
+ if ( rc == LDAP_SUCCESS )
+ {
+ if (conn->last_ldap_error != rc)
+ {
+ conn->last_ldap_error = rc;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Replication bind with %s resumed\n",
+ agmt_get_long_name(conn->agmt), auth);
+ }
+ }
+ else
+ {
+ /* Do not report the same error over and over again */
+ if (conn->last_ldap_error != rc)
+ {
+ conn->last_ldap_error = rc;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Replication bind with %s failed: LDAP error %d (%s)\n",
+ agmt_get_long_name(conn->agmt), auth, rc,
+ ldap_err2string(rc));
+ }
+
+ return (CONN_OPERATION_FAILED);
+ }
+ }
+ else
+ {
+ if( ( msgid = do_simple_bind( conn, ld, binddn, password ) ) == -1 )
+ {
+ return (CONN_OPERATION_FAILED);
+ }
+ }
+ }
+ else
+ {
+ optype = "ldap_simple_bind";
+ if( ( msgid = do_simple_bind( conn, ld, binddn, password ) ) == -1 )
+ {
+ return (CONN_OPERATION_FAILED);
+ }
+ }
+
+ /* Wait for the result */
+ if ( ldap_result( ld, msgid, LDAP_MSG_ALL, NULL, &res ) == -1 )
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Received error from consumer for %s operation\n",
+ agmt_get_long_name(conn->agmt), optype);
+
+ return (CONN_OPERATION_FAILED);
+ }
+ /* Don't check ldap_result against 0 because, no timeout is specified */
+
+ /* Free res as we won't use it any longer */
+ if ( ldap_parse_result( ld, res, &rc, NULL, NULL, NULL, &ctrls, 1 /* Free res */)
+ != LDAP_SUCCESS )
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Received error from consumer for %s operation\n",
+ agmt_get_long_name(conn->agmt), optype);
+
+ return (CONN_OPERATION_FAILED);
+ }
+
+ if ( rc == LDAP_SUCCESS )
+ {
+ if ( ctrls )
+ {
+ int i;
+ for( i = 0; ctrls[ i ] != NULL; ++i )
+ {
+ if ( !(strcmp( ctrls[ i ]->ldctl_oid, LDAP_CONTROL_PWEXPIRED)) )
+ {
+ /* Bind is successfull but password has expired */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Succesfully bound %s to consumer, "
+ "but password has expired on consumer.\n",
+ agmt_get_long_name(conn->agmt), binddn);
+ }
+ else if ( !(strcmp( ctrls[ i ]->ldctl_oid, LDAP_CONTROL_PWEXPIRING)) )
+ {
+ /* The password is expiring in n seconds */
+ if ( (ctrls[ i ]->ldctl_value.bv_val != NULL) &&
+ (ctrls[ i ]->ldctl_value.bv_len > 0) )
+ {
+ int password_expiring = atoi( ctrls[ i ]->ldctl_value.bv_val );
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Succesfully bound %s to consumer, "
+ "but password is expiring on consumer in %d seconds.\n",
+ agmt_get_long_name(conn->agmt), binddn, password_expiring);
+ }
+ }
+ }
+ ldap_controls_free( ctrls );
+ }
+
+ return (CONN_OPERATION_SUCCESS);
+ }
+ else
+ {
+ /* errmsg is a pointer directly into the ld structure - do not free */
+ rc = ldap_get_lderrno( ld, NULL, &errmsg );
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Replication bind to %s on consumer failed: %d (%s)\n",
+ agmt_get_long_name(conn->agmt), binddn, rc, errmsg);
+
+ conn->last_ldap_error = rc; /* specific error */
+ return (CONN_OPERATION_FAILED);
+ }
+}
+
+static int
+do_simple_bind (Repl_Connection *conn, LDAP *ld, char * binddn, char *password)
+{
+ int msgid;
+
+ if( ( msgid = ldap_simple_bind( ld, binddn, password ) ) == -1 )
+ {
+ char *ldaperrtext = NULL;
+ int ldaperr;
+ int prerr = PR_GetError();
+
+ ldaperr = ldap_get_lderrno( ld, NULL, &ldaperrtext );
+ /* Do not report the same error over and over again */
+ if (conn->last_ldap_error != ldaperr)
+ {
+ conn->last_ldap_error = ldaperr;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Simple bind failed, "
+ SLAPI_COMPONENT_NAME_LDAPSDK " error %d (%s), "
+ SLAPI_COMPONENT_NAME_NSPR " error %d (%s)\n",
+ agmt_get_long_name(conn->agmt),
+ ldaperr, ldaperrtext ? ldaperrtext : ldap_err2string(ldaperr),
+ prerr, slapd_pr_strerror(prerr));
+ }
+ }
+ else if (conn->last_ldap_error != LDAP_SUCCESS)
+ {
+ conn->last_ldap_error = LDAP_SUCCESS;
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "%s: Simple bind resumed\n",
+ agmt_get_long_name(conn->agmt));
+ }
+ return msgid;
+}
+
+
+static time_t
+PRTime2time_t (PRTime tm)
+{
+ PRInt64 rt;
+
+ PR_ASSERT (tm);
+
+ LL_DIV(rt, tm, PR_USEC_PER_SEC);
+
+ return (time_t)rt;
+}
+
+static Slapi_Eq_Context
+repl5_start_debug_timeout(int *setlevel)
+{
+ Slapi_Eq_Context eqctx = 0;
+ if (s_debug_timeout && s_debug_level) {
+ time_t now = time(NULL);
+ eqctx = slapi_eq_once(repl5_debug_timeout_callback, setlevel,
+ s_debug_timeout + now);
+ }
+ return eqctx;
+}
+
+static void
+repl5_stop_debug_timeout(Slapi_Eq_Context eqctx, int *setlevel)
+{
+ char buf[20];
+ char msg[SLAPI_DSE_RETURNTEXT_SIZE];
+
+ if (eqctx && !*setlevel) {
+ int found = slapi_eq_cancel(eqctx);
+ }
+
+ if (s_debug_timeout && s_debug_level && *setlevel) {
+ void config_set_errorlog_level(const char *type, char *buf, char *msg, int apply);
+ sprintf(buf, "%d", 0);
+ config_set_errorlog_level("nsslapd-errorlog-level", buf, msg, 1);
+ }
+}
+
+static void
+repl5_debug_timeout_callback(time_t when, void *arg)
+{
+ int *setlevel = (int *)arg;
+ void config_set_errorlog_level(const char *type, char *buf, char *msg, int apply);
+ char buf[20];
+ char msg[SLAPI_DSE_RETURNTEXT_SIZE];
+
+ *setlevel = 1;
+ sprintf(buf, "%d", s_debug_level);
+ config_set_errorlog_level("nsslapd-errorlog-level", buf, msg, 1);
+
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "repl5_debug_timeout_callback: set debug level to %d at %d\n",
+ s_debug_level, when);
+}
diff --git a/ldap/servers/plugins/replication/windows_inc_protocol.c b/ldap/servers/plugins/replication/windows_inc_protocol.c
new file mode 100644
index 00000000..52cd4e44
--- /dev/null
+++ b/ldap/servers/plugins/replication/windows_inc_protocol.c
@@ -0,0 +1,2609 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_inc_protocol.c */
+/*
+
+ The Prot_Incremental object implements the DS 5.0 multi-master incremental
+ replication protocol.
+
+
+Stuff to do:
+
+- Need to figure out how asynchronous events end up in here. They are:
+ - entry updated in replicated area.
+ - backoff timeout
+ - enter/leave.
+
+Perhaps these events should be properties of the main protocol.
+*/
+
+#include "repl.h"
+#include "repl5.h"
+#include "windowsrepl.h"
+#include "windows_prot_private.h"
+#include "slap.h" /* PSEUDO_ATTR_UNHASHED */
+#include "repl5_ruv.h"
+
+#include "cl5_api.h"
+#include "slapi-plugin.h"
+extern int slapi_log_urp;
+
+typedef struct attribute_map
+{
+ char *old_name;
+ char *new_name;
+ int map_to;
+
+} attribute_map;
+
+static attribute_map mapping[] =
+{ /* map the attribute from , to, which platform we going to? */
+ { "ntUserDomainId", "sAMAccountName", REPLICA_TYPE_WINDOWS},
+ { "ntUserHomeDir", "homeDirectory", REPLICA_TYPE_WINDOWS},
+ { "ntUserScriptPath", "scriptPath", REPLICA_TYPE_WINDOWS},
+ { "ntUserLastLogon", "lastLogon", REPLICA_TYPE_WINDOWS},
+ { "ntUserLastLogoff", "lastLogoff", REPLICA_TYPE_WINDOWS},
+ { "cn", "description", REPLICA_TYPE_WINDOWS}, /* temporary */
+ { "uid", "cn", REPLICA_TYPE_WINDOWS},
+ { "dn", "dn", REPLICA_TYPE_WINDOWS},
+ { "sn", "sn", REPLICA_TYPE_WINDOWS},
+
+ { "sAMAccountName", "ntUserDomainId", REPLICA_TYPE_MULTIMASTER},
+ { "homeDirectory", "ntUserHomeDir", REPLICA_TYPE_MULTIMASTER},
+ { "scriptPath", "ntUserScriptPath", REPLICA_TYPE_MULTIMASTER},
+ { "lastLogon", "ntUserLastLogon", REPLICA_TYPE_MULTIMASTER},
+ { "lastLogoff", "ntUserLastLogoff", REPLICA_TYPE_MULTIMASTER},
+ { "sAMAccountName", "uid", REPLICA_TYPE_MULTIMASTER},
+ { "dn", "dn", REPLICA_TYPE_MULTIMASTER},
+ { "sAMAccountName", "sn", REPLICA_TYPE_MULTIMASTER},
+ { "sAMAccountName", "cn", REPLICA_TYPE_MULTIMASTER},
+
+ {NULL, NULL, -1}
+};
+
+static attribute_map group_map[] =
+{
+ { "ntGroupDomainId ", "name", REPLICA_TYPE_WINDOWS},
+ { "ntGroupDomainId ", "sAMAccountName", REPLICA_TYPE_WINDOWS},
+ { "ntGroupType", "groupType", REPLICA_TYPE_WINDOWS},
+
+ { "sAMAccountName", "ntGroupDomainId ", REPLICA_TYPE_MULTIMASTER},
+ { "groupType", "ntGroupType", REPLICA_TYPE_MULTIMASTER},
+
+ {NULL, NULL, -1}
+
+};
+
+void map_entry_user(Slapi_Entry **e, int map_to, char** password);
+static Slapi_Entry* windows_entry_already_exists(Slapi_Entry *e);
+
+static Slapi_DN* map_dn_user(Slapi_DN *sdn, int map_to, const Slapi_DN *root);
+static Slapi_DN* map_dn_group(Slapi_DN *sdn, int map_to, const Slapi_DN *root);
+static void make_mods_from_entries(Slapi_Entry *new_entry, Slapi_Entry *existing_entry, LDAPMod ***attrs);
+static void alter_mods(LDAPMod ***m, char** password);
+
+/*** from proto-slap.h ***/
+void ava_done(struct ava *ava);
+
+typedef struct windows_inc_private
+{
+ char *ruv; /* RUV on remote replica (use diff type for this? - ggood */
+ Backoff_Timer *backoff;
+ Repl_Protocol *rp;
+ PRLock *lock;
+ PRUint32 eventbits;
+} windows_inc_private;
+
+
+/* Various states the incremental protocol can pass through */
+#define STATE_START 0 /* ONREPL - should we rename this - we don't use it just to start up? */
+#define STATE_WAIT_WINDOW_OPEN 1
+#define STATE_WAIT_CHANGES 2
+#define STATE_READY_TO_ACQUIRE 3
+#define STATE_BACKOFF_START 4 /* ONREPL - can we combine BACKOFF_START and BACKOFF states? */
+#define STATE_BACKOFF 5
+#define STATE_SENDING_UPDATES 6
+#define STATE_STOP_FATAL_ERROR 7
+#define STATE_STOP_FATAL_ERROR_PART2 8
+#define STATE_STOP_NORMAL_TERMINATION 9
+
+/* Events (synchronous and asynchronous; these are bits) */
+#define EVENT_WINDOW_OPENED 1
+#define EVENT_WINDOW_CLOSED 2
+#define EVENT_TRIGGERING_CRITERIA_MET 4 /* ONREPL - should we rename this to EVENT_CHANGE_AVAILABLE */
+#define EVENT_BACKOFF_EXPIRED 8
+#define EVENT_REPLICATE_NOW 16
+#define EVENT_PROTOCOL_SHUTDOWN 32
+#define EVENT_AGMT_CHANGED 64
+
+#define UPDATE_NO_MORE_UPDATES 201
+#define UPDATE_TRANSIENT_ERROR 202
+#define UPDATE_FATAL_ERROR 203
+#define UPDATE_SCHEDULE_WINDOW_CLOSED 204
+#define UPDATE_CONNECTION_LOST 205
+#define UPDATE_TIMEOUT 206
+#define UPDATE_YIELD 207
+
+/* Return codes from examine_update_vector */
+#define EXAMINE_RUV_PRISTINE_REPLICA 401
+#define EXAMINE_RUV_GENERATION_MISMATCH 402
+#define EXAMINE_RUV_REPLICA_TOO_OLD 403
+#define EXAMINE_RUV_OK 404
+#define EXAMINE_RUV_PARAM_ERROR 405
+
+#define MAX_CHANGES_PER_SESSION 10000
+/*
+ * Maximum time to wait between replication sessions. If we
+ * don't see any updates for a period equal to this interval,
+ * we go ahead and start a replication session, just to be safe
+ */
+#define MAX_WAIT_BETWEEN_SESSIONS PR_SecondsToInterval(60 * 5) /* 5 minutes */
+
+/*
+ * tests if the protocol has been shutdown and we need to quit
+ * event_occurred resets the bits in the bit flag, so whoever tests for shutdown
+ * resets the flags, so the next one who tests for shutdown won't get it, so we
+ * also look at the terminate flag
+ */
+#define PROTOCOL_IS_SHUTDOWN(prp) (event_occurred(prp, EVENT_PROTOCOL_SHUTDOWN) || prp->terminate)
+
+/* Forward declarations */
+static PRUint32 event_occurred(Private_Repl_Protocol *prp, PRUint32 event);
+static void reset_events (Private_Repl_Protocol *prp);
+static void protocol_sleep(Private_Repl_Protocol *prp, PRIntervalTime duration);
+static int send_updates(Private_Repl_Protocol *prp, RUV *ruv, PRUint32 *num_changes_sent);
+static void windows_inc_backoff_expired(time_t timer_fire_time, void *arg);
+static int windows_examine_update_vector(Private_Repl_Protocol *prp, RUV *ruv);
+static PRBool ignore_error_and_keep_going(int error);
+static const char* state2name (int state);
+static const char* event2name (int event);
+static const char* op2string (int op);
+
+/*
+ * It's specifically ok to delete a protocol instance that
+ * is currently running. The instance will be shut down, and
+ * then resources will be freed. Since a graceful shutdown is
+ * attempted, this function may take some time to complete.
+ */
+static void
+windows_inc_delete(Private_Repl_Protocol **prpp)
+{
+ /* First, stop the protocol if it isn't already stopped */
+ /* Then, delete all resources used by the protocol */
+}
+
+/* helper function */
+void
+w_set_pause_and_busy_time(long *pausetime, long *busywaittime)
+{
+ /* If neither are set, set busy time to its default */
+ if (!*pausetime && !*busywaittime)
+ {
+ *busywaittime = PROTOCOL_BUSY_BACKOFF_MINIMUM;
+ }
+ /* pause time must be at least 1 more than the busy backoff time */
+ if (*pausetime && !*busywaittime)
+ {
+ /*
+ * user specified a pause time but no busy wait time - must
+ * set busy wait time to 1 less than pause time - if pause
+ * time is 1, we must set it to 2
+ */
+ if (*pausetime < 2)
+ {
+ *pausetime = 2;
+ }
+ *busywaittime = *pausetime - 1;
+ }
+ else if (!*pausetime && *busywaittime)
+ {
+ /*
+ * user specified a busy wait time but no pause time - must
+ * set pause time to 1 more than busy wait time
+ */
+ *pausetime = *busywaittime + 1;
+ }
+ else if (*pausetime && *busywaittime && *pausetime <= *busywaittime)
+ {
+ /*
+ * user specified both pause and busy wait times, but the pause
+ * time was <= busy wait time - pause time must be at least
+ * 1 more than the busy wait time
+ */
+ *pausetime = *busywaittime + 1;
+ }
+}
+
+/*
+ * Do the incremental protocol.
+ *
+ * What's going on here? This thing is a state machine. It has the
+ * following states:
+ *
+ * State transition table:
+ *
+ * Curr State Condition/Event Next State
+ * ---------- ------------ -----------
+ * START schedule window is open ACQUIRE_REPLICA
+ * schedule window is closed WAIT_WINDOW_OPEN
+ * WAIT_WINDOW_OPEN schedule change START
+ * replicate now ACQUIRE_REPLICA
+ * schedule window opens ACQUIRE_REPLICA
+ * ACQUIRE_REPLICA acquired replica SEND_CHANGES
+ * failed to acquire - transient error START_BACKOFF
+ * failed to acquire - fatal error STOP_FATAL_ERROR
+ * SEND_CHANGES can't update CONSUMER_NEEDS_REINIT
+ * no changes to send WAIT_CHANGES
+ * can't send - thransient error START_BACKOF
+ * can't send - window closed WAIT_WINDOW_OPEN
+ * can'r send - fatal error STOP_FATAL_ERROR
+ * START_BACKOF replicate now ACQUIRE_REPLICA
+ * schedule changes START
+ * schedule window closes WAIT_WINDOW_OPEN
+ * backoff expires & can acquire SEND_CHANGES
+ * backoff expires & can't acquire-trans BACKOFF
+ * backoff expires & can't acquire-fatal STOP_FATAL_ERROR
+ * BACKOF replicate now ACQUIRE_REPLICA
+ * schedule changes START
+ * schedule window closes WAIT_WINDOW_OPEN
+ * backoff expires & can acquire SEND_CHANGES
+ * backoff expires & can't acquire-trans BACKOFF
+ * backoff expires & can't acquire-fatal STOP_FATAL_ERROR
+ * WAIT_CHANGES schedule window closes WAIT_WINDOW_OPEN
+ * replicate_now ACQUIRE_REPLICA
+ * change available ACQUIRE_REPLICA
+ * schedule_change START
+ */
+
+/*
+ * Main state machine for the incremental protocol. This routine will,
+ * under normal circumstances, not return until the protocol is shut
+ * down.
+ */
+static void
+windows_inc_run(Private_Repl_Protocol *prp)
+{
+ int current_state = STATE_START;
+ int next_state = STATE_START;
+ windows_inc_private *prp_priv = (windows_inc_private *)prp->private;
+ int done;
+ int e1;
+ RUV *ruv = NULL;
+ CSN *cons_schema_csn;
+ Replica *replica;
+ int wait_change_timer_set = 0;
+ time_t last_start_time;
+ PRUint32 num_changes_sent;
+ char *hostname = NULL;
+ int portnum = 0;
+ /* use a different backoff timer strategy for ACQUIRE_REPLICA_BUSY errors */
+ PRBool use_busy_backoff_timer = PR_FALSE;
+ long pausetime = 0;
+ long busywaittime = 0;
+
+ prp->stopped = 0;
+ prp->terminate = 0;
+ hostname = agmt_get_hostname(prp->agmt);
+ portnum = agmt_get_port(prp->agmt);
+
+ /* establish_protocol_callbacks(prp); */
+ done = 0;
+ do {
+ int rc;
+
+ /* Take action, based on current state, and compute new state. */
+ switch (current_state)
+ {
+ case STATE_START:
+
+ dev_debug("windows_inc_run(STATE_START)");
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ done = 1;
+ break;
+ }
+
+ /*
+ * Our initial state. See if we're in a schedule window. If
+ * so, then we're ready to acquire the replica and see if it
+ * needs any updates from us. If not, then wait for the window
+ * to open.
+ */
+ if (agmt_schedule_in_window_now(prp->agmt))
+ {
+ next_state = STATE_READY_TO_ACQUIRE;
+ }
+ else
+ {
+ next_state = STATE_WAIT_WINDOW_OPEN;
+ }
+
+ /* we can get here from other states because some events happened and were
+ not cleared. For instance when we wake up in STATE_WAIT_CHANGES state.
+ Since this is a fresh start state, we should clear all events */
+ /* ONREPL - this does not feel right - we should take another look
+ at this state machine */
+ reset_events (prp);
+
+ /* Cancel any linger timer that might be in effect... */
+ windows_conn_cancel_linger(prp->conn);
+ /* ... and disconnect, if currently connected */
+ windows_conn_disconnect(prp->conn);
+ /* get the new pause time, if any */
+ pausetime = agmt_get_pausetime(prp->agmt);
+ /* get the new busy wait time, if any */
+ busywaittime = agmt_get_busywaittime(prp->agmt);
+ if (pausetime || busywaittime)
+ {
+ /* helper function to make sure they are set correctly */
+ w_set_pause_and_busy_time(&pausetime, &busywaittime);
+ }
+ break;
+ case STATE_WAIT_WINDOW_OPEN:
+ /*
+ * We're waiting for a schedule window to open. If one did,
+ * or we receive a "replicate now" event, then start a protocol
+ * session immediately. If the replication schedule changed, go
+ * back to start. Otherwise, go back to sleep.
+ */
+ dev_debug("windows_inc_run(STATE_WAIT_WINDOW_OPEN)");
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ done = 1;
+ break;
+ }
+ else if (event_occurred(prp, EVENT_WINDOW_OPENED))
+ {
+ next_state = STATE_READY_TO_ACQUIRE;
+ }
+ else if (event_occurred(prp, EVENT_REPLICATE_NOW))
+ {
+ next_state = STATE_READY_TO_ACQUIRE;
+ }
+ else if (event_occurred(prp, EVENT_AGMT_CHANGED))
+ {
+ next_state = STATE_START;
+ windows_conn_set_agmt_changed(prp->conn);
+ }
+ else if (event_occurred(prp, EVENT_TRIGGERING_CRITERIA_MET)) /* change available */
+ {
+ /* just ignore it and go to sleep */
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ }
+ else if (e1 = event_occurred(prp, EVENT_WINDOW_CLOSED) ||
+ event_occurred(prp, EVENT_BACKOFF_EXPIRED))
+ {
+ /* this events - should not occur - log a warning and go to sleep */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Incremental protocol: "
+ "event %s should not occur in state %s; going to sleep\n",
+ agmt_get_long_name(prp->agmt),
+ e1 ? event2name(EVENT_WINDOW_CLOSED) : event2name(EVENT_BACKOFF_EXPIRED),
+ state2name(current_state));
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ }
+ else
+ {
+ /* wait until window opens or an event occurs */
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "%s: Incremental protocol: "
+ "waiting for update window to open\n", agmt_get_long_name(prp->agmt));
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ }
+ break;
+ case STATE_WAIT_CHANGES:
+ /*
+ * We're in a replication window, but we're waiting for more
+ * changes to accumulate before we actually hook up and send
+ * them.
+ */
+ dev_debug("windows_inc_run(STATE_WAIT_CHANGES)");
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ dev_debug("windows_inc_run(STATE_WAIT_CHANGES): PROTOCOL_IS_SHUTING_DOWN -> end windows_inc_run\n");
+ done = 1;
+ break;
+ }
+ else if (event_occurred(prp, EVENT_REPLICATE_NOW))
+ {
+ dev_debug("windows_inc_run(STATE_WAIT_CHANGES): EVENT_REPLICATE_NOW received -> STATE_READY_TO_ACQUIRE\n");
+ next_state = STATE_READY_TO_ACQUIRE;
+ wait_change_timer_set = 0;
+ }
+ else if (event_occurred(prp, EVENT_AGMT_CHANGED))
+ {
+ dev_debug("windows_inc_run(STATE_WAIT_CHANGES): EVENT_AGMT_CHANGED received -> STATE_START\n");
+ next_state = STATE_START;
+ windows_conn_set_agmt_changed(prp->conn);
+ wait_change_timer_set = 0;
+ }
+ else if (event_occurred(prp, EVENT_WINDOW_CLOSED))
+ {
+ dev_debug("windows_inc_run(STATE_WAIT_CHANGES): EVENT_WINDOW_CLOSED received -> STATE_WAIT_WINDOW_OPEN\n");
+ next_state = STATE_WAIT_WINDOW_OPEN;
+ wait_change_timer_set = 0;
+ }
+ else if (event_occurred(prp, EVENT_TRIGGERING_CRITERIA_MET))
+ {
+ dev_debug("windows_inc_run(STATE_WAIT_CHANGES): EVENT_TRIGGERING_CRITERIA_MET received -> STATE_READY_TO_ACQUIRE\n");
+ next_state = STATE_READY_TO_ACQUIRE;
+ wait_change_timer_set = 0;
+ }
+ else if (e1 = event_occurred(prp, EVENT_WINDOW_OPENED) ||
+ event_occurred(prp, EVENT_BACKOFF_EXPIRED))
+ {
+ /* this events - should not occur - log a warning and clear the event */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "%s: Incremental protocol: "
+ "event %s should not occur in state %s\n",
+ agmt_get_long_name(prp->agmt),
+ e1 ? event2name(EVENT_WINDOW_OPENED) : event2name(EVENT_BACKOFF_EXPIRED),
+ state2name(current_state));
+ wait_change_timer_set = 0;
+ }
+ else
+ {
+ if (wait_change_timer_set)
+ {
+ /* We are here because our timer expired */
+ dev_debug("windows_inc_run(STATE_WAIT_CHANGES): wait_change_timer_set expired -> STATE_START\n");
+ next_state = STATE_START;
+ wait_change_timer_set = 0;
+ }
+ else
+ {
+ /* We are here because the last replication session
+ * finished or aborted.
+ */
+ wait_change_timer_set = 1;
+ protocol_sleep(prp, MAX_WAIT_BETWEEN_SESSIONS);
+ }
+ }
+ break;
+ case STATE_READY_TO_ACQUIRE:
+
+ dev_debug("windows_inc_run(STATE_READY_TO_ACQUIRE)");
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ done = 1;
+ break;
+ }
+
+ /* ONREPL - at this state we unconditionally acquire the replica
+ ignoring all events. Not sure if this is good */
+ object_acquire(prp->replica_object);
+ replica = object_get_data(prp->replica_object);
+
+ rc = windows_acquire_replica(prp, &ruv);
+
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "windows_acquire_replica returned %s (%d)\n",
+ state2name(rc),
+ rc);
+
+ use_busy_backoff_timer = PR_FALSE; /* default */
+ if (rc == ACQUIRE_SUCCESS)
+ {
+ next_state = STATE_SENDING_UPDATES;
+ }
+ else if (rc == ACQUIRE_REPLICA_BUSY)
+ {
+ next_state = STATE_BACKOFF_START;
+ use_busy_backoff_timer = PR_TRUE;
+ }
+ else if (rc == ACQUIRE_CONSUMER_WAS_UPTODATE)
+ {
+ next_state = STATE_WAIT_CHANGES;
+ }
+ else if (rc == ACQUIRE_TRANSIENT_ERROR)
+ {
+ next_state = STATE_BACKOFF_START;
+ }
+ else if (rc == ACQUIRE_FATAL_ERROR)
+ {
+ next_state = STATE_STOP_FATAL_ERROR;
+ }
+ if (rc != ACQUIRE_SUCCESS)
+ {
+ int optype, ldaprc;
+ windows_conn_get_error(prp->conn, &optype, &ldaprc);
+ agmt_set_last_update_status(prp->agmt, ldaprc,
+ prp->last_acquire_response_code, NULL);
+ }
+
+ object_release(prp->replica_object); replica = NULL;
+ break;
+ case STATE_BACKOFF_START:
+ dev_debug("windows_inc_run(STATE_BACKOFF_START)");
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ done = 1;
+ break;
+ }
+ if (event_occurred(prp, EVENT_REPLICATE_NOW))
+ {
+ next_state = STATE_READY_TO_ACQUIRE;
+ }
+ else if (event_occurred(prp, EVENT_AGMT_CHANGED))
+ {
+ next_state = STATE_START;
+ windows_conn_set_agmt_changed(prp->conn);
+ }
+ else if (event_occurred (prp, EVENT_WINDOW_CLOSED))
+ {
+ next_state = STATE_WAIT_WINDOW_OPEN;
+ }
+ else if (event_occurred (prp, EVENT_TRIGGERING_CRITERIA_MET))
+ {
+ /* consume and ignore */
+ }
+ else if (e1 = event_occurred (prp, EVENT_WINDOW_OPENED) ||
+ event_occurred (prp, EVENT_BACKOFF_EXPIRED))
+ {
+ /* This should never happen */
+ /* this events - should not occur - log a warning and go to sleep */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Incremental protocol: event %s should not occur in state %s\n",
+ agmt_get_long_name(prp->agmt),
+ e1 ? event2name(EVENT_WINDOW_OPENED) : event2name(EVENT_BACKOFF_EXPIRED),
+ state2name(current_state));
+ }
+ else
+ {
+ /* Set up the backoff timer to wake us up at the appropriate time */
+ if (use_busy_backoff_timer)
+ {
+ /* we received a busy signal from the consumer, wait for a while */
+ if (!busywaittime)
+ {
+ busywaittime = PROTOCOL_BUSY_BACKOFF_MINIMUM;
+ }
+ prp_priv->backoff = backoff_new(BACKOFF_FIXED, busywaittime,
+ busywaittime);
+ }
+ else
+ {
+ prp_priv->backoff = backoff_new(BACKOFF_EXPONENTIAL, PROTOCOL_BACKOFF_MINIMUM,
+ PROTOCOL_BACKOFF_MAXIMUM);
+ }
+ next_state = STATE_BACKOFF;
+ backoff_reset(prp_priv->backoff, windows_inc_backoff_expired, (void *)prp);
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ use_busy_backoff_timer = PR_FALSE;
+ }
+ break;
+ case STATE_BACKOFF:
+ /*
+ * We're in a backoff state.
+ */
+ dev_debug("windows_inc_run(STATE_BACKOFF)");
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ if (prp_priv->backoff)
+ backoff_delete(&prp_priv->backoff);
+ done = 1;
+ break;
+ }
+ else if (event_occurred(prp, EVENT_REPLICATE_NOW))
+ {
+ next_state = STATE_READY_TO_ACQUIRE;
+ }
+ else if (event_occurred(prp, EVENT_AGMT_CHANGED))
+ {
+ next_state = STATE_START;
+
+ windows_conn_set_agmt_changed(prp->conn);
+ /* Destroy the backoff timer, since we won't need it anymore */
+ if (prp_priv->backoff)
+ backoff_delete(&prp_priv->backoff);
+ }
+ else if (event_occurred(prp, EVENT_WINDOW_CLOSED))
+ {
+ next_state = STATE_WAIT_WINDOW_OPEN;
+ /* Destroy the backoff timer, since we won't need it anymore */
+ if (prp_priv->backoff)
+ backoff_delete(&prp_priv->backoff);
+ }
+ else if (event_occurred(prp, EVENT_BACKOFF_EXPIRED))
+ {
+ rc = windows_acquire_replica(prp, &ruv);
+ use_busy_backoff_timer = PR_FALSE;
+ if (rc == ACQUIRE_SUCCESS)
+ {
+ next_state = STATE_SENDING_UPDATES;
+ }
+ else if (rc == ACQUIRE_REPLICA_BUSY)
+ {
+ next_state = STATE_BACKOFF;
+ use_busy_backoff_timer = PR_TRUE;
+ }
+ else if (rc == ACQUIRE_CONSUMER_WAS_UPTODATE)
+ {
+ next_state = STATE_WAIT_CHANGES;
+ }
+ else if (rc == ACQUIRE_TRANSIENT_ERROR)
+ {
+ next_state = STATE_BACKOFF;
+ }
+ else if (rc == ACQUIRE_FATAL_ERROR)
+ {
+ next_state = STATE_STOP_FATAL_ERROR;
+ }
+ if (rc != ACQUIRE_SUCCESS)
+ {
+ int optype, ldaprc;
+ windows_conn_get_error(prp->conn, &optype, &ldaprc);
+ agmt_set_last_update_status(prp->agmt, ldaprc,
+ prp->last_acquire_response_code, NULL);
+ }
+ /*
+ * We either need to step the backoff timer, or
+ * destroy it if we don't need it anymore.
+ */
+ if (STATE_BACKOFF == next_state)
+ {
+ time_t next_fire_time;
+ time_t now;
+ /* Step the backoff timer */
+ time(&now);
+ next_fire_time = backoff_step(prp_priv->backoff);
+ /* And go back to sleep */
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "%s: Replication session backing off for %d seconds\n",
+ agmt_get_long_name(prp->agmt),
+ next_fire_time - now);
+
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ }
+ else
+ {
+ /* Destroy the backoff timer, since we won't need it anymore */
+ backoff_delete(&prp_priv->backoff);
+ }
+ use_busy_backoff_timer = PR_FALSE;
+ }
+ else if (event_occurred(prp, EVENT_TRIGGERING_CRITERIA_MET))
+ {
+ /* changes are available */
+ if ( prp_priv->backoff == NULL || backoff_expired (prp_priv->backoff, 60) )
+ {
+ /*
+ * Have seen cases that the agmt stuck here forever since
+ * somehow the backoff timer was not in event queue anymore.
+ * If the backoff timer has expired more than 60 seconds,
+ * destroy it.
+ */
+ if ( prp_priv->backoff )
+ backoff_delete(&prp_priv->backoff);
+ next_state = STATE_READY_TO_ACQUIRE;
+ }
+ else
+ {
+ /* ignore changes and go to sleep */
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ }
+ }
+ else if (event_occurred(prp, EVENT_WINDOW_OPENED))
+ {
+ /* this should never happen - log an error and go to sleep */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "%s: Incremental protocol: "
+ "event %s should not occur in state %s; going to sleep\n",
+ agmt_get_long_name(prp->agmt),
+ event2name(EVENT_WINDOW_OPENED), state2name(current_state));
+ protocol_sleep(prp, PR_INTERVAL_NO_TIMEOUT);
+ }
+ break;
+ case STATE_SENDING_UPDATES:
+ dev_debug("windows_inc_run(STATE_SENDING_UPDATES)");
+ agmt_set_update_in_progress(prp->agmt, PR_TRUE);
+ num_changes_sent = 0;
+ last_start_time = current_time();
+ agmt_set_last_update_start(prp->agmt, last_start_time);
+ /*
+ * We've acquired the replica, and are ready to send any
+ * needed updates.
+ */
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ windows_release_replica (prp);
+ done = 1;
+ agmt_set_update_in_progress(prp->agmt, PR_FALSE);
+ agmt_set_last_update_end(prp->agmt, current_time());
+ /* MAB: I don't find the following status correct. How do we know it has
+ been stopped by an admin and not by a total update request, for instance?
+ In any case, how is this protocol shutdown situation different from all the
+ other ones that are present in this state machine? */
+ /* 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");
+ break;
+ }
+
+ agmt_set_last_update_status(prp->agmt, 0, 0, "Incremental update started");
+
+ /* ONREPL - in this state we send changes no matter what other events occur.
+ This is because we can get because of the REPLICATE_NOW event which
+ has high priority. Is this ok? */
+ /* First, push new schema to the consumer if needed */
+ /* ONREPL - should we push schema after we examine the RUV? */
+ /*
+ * GGOOREPL - I don't see why we should wait until we've
+ * examined the RUV. The schema entry has its own CSN that is
+ * used to decide if the remote schema needs to be updated.
+ */
+ cons_schema_csn = agmt_get_consumer_schema_csn ( prp->agmt );
+ // rc = windows_conn_push_schema(prp->conn, &cons_schema_csn); XXX
+ if ( cons_schema_csn != agmt_get_consumer_schema_csn ( prp->agmt ))
+ {
+ agmt_set_consumer_schema_csn ( prp->agmt, cons_schema_csn );
+ }
+ if (CONN_SCHEMA_UPDATED != rc && CONN_SCHEMA_NO_UPDATE_NEEDED != rc)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Warning: unable to replicate schema: rc=%d\n",
+ agmt_get_long_name(prp->agmt), rc);
+ /* But keep going */
+ }
+ dev_debug("windows_inc_run(STATE_SENDING_UPDATES) -> windows_examine_update_vector");
+ rc = windows_examine_update_vector(prp, ruv);
+ /*
+ * Decide what to do next - proceed with incremental,
+ * backoff, or total update
+ */
+ switch (rc)
+ {
+ case EXAMINE_RUV_PARAM_ERROR:
+ /* this is really bad - we have NULL prp! */
+ next_state = STATE_STOP_FATAL_ERROR;
+ break;
+ case EXAMINE_RUV_PRISTINE_REPLICA:
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Replica has no update vector. It has never been initialized.\n",
+ agmt_get_long_name(prp->agmt));
+ next_state = STATE_BACKOFF_START;
+ break;
+ case EXAMINE_RUV_GENERATION_MISMATCH:
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Replica has a different generation ID than the local data.\n",
+ agmt_get_long_name(prp->agmt));
+ next_state = STATE_BACKOFF_START;
+ break;
+ case EXAMINE_RUV_REPLICA_TOO_OLD:
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Replica update vector is too out of date to bring "
+ "into sync using the incremental protocol. The replica "
+ "must be reinitialized.\n", agmt_get_long_name(prp->agmt));
+ next_state = STATE_BACKOFF_START;
+ break;
+ case EXAMINE_RUV_OK:
+ /* update our csn generator state with the consumer's ruv data */
+ dev_debug("windows_inc_run(STATE_SENDING_UPDATES) -> windows_examine_update_vector OK");
+ object_acquire(prp->replica_object);
+ replica = object_get_data(prp->replica_object);
+ rc = replica_update_csngen_state (replica, ruv);
+ object_release (prp->replica_object);
+ replica = NULL;
+ if (rc != 0) /* too much skew */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Incremental protocol: fatal error - too much time skew between replicas!\n",
+ agmt_get_long_name(prp->agmt));
+ next_state = STATE_STOP_FATAL_ERROR;
+ }
+ else
+ {
+ rc = send_updates(prp, ruv, &num_changes_sent);
+ if (rc == UPDATE_NO_MORE_UPDATES)
+ {
+ dev_debug("windows_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_NO_MORE_UPDATES -> STATE_WAIT_CHANGES");
+ agmt_set_last_update_status(prp->agmt, 0, 0, "Incremental update succeeded");
+ next_state = STATE_WAIT_CHANGES;
+ }
+ else if (rc == UPDATE_YIELD)
+ {
+ dev_debug("windows_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_YIELD -> STATE_BACKOFF_START");
+ agmt_set_last_update_status(prp->agmt, 0, 0, "Incremental update succeeded and yielded");
+ next_state = STATE_BACKOFF_START;
+ }
+ else if (rc == UPDATE_TRANSIENT_ERROR)
+ {
+ dev_debug("windows_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_TRANSIENT_ERROR -> STATE_BACKOFF_START");
+ next_state = STATE_BACKOFF_START;
+ }
+ else if (rc == UPDATE_FATAL_ERROR)
+ {
+ dev_debug("windows_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_FATAL_ERROR -> STATE_STOP_FATAL_ERROR");
+ next_state = STATE_STOP_FATAL_ERROR;
+ }
+ else if (rc == UPDATE_SCHEDULE_WINDOW_CLOSED)
+ {
+ dev_debug("windows_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_SCHEDULE_WINDOW_CLOSED -> STATE_WAIT_WINDOW_OPEN");
+ /* ONREPL - I don't think we should check this. We might be
+ here because of replicate_now event - so we don't care
+ about the schedule */
+ next_state = STATE_WAIT_WINDOW_OPEN;
+ /* ONREPL - do we need to release the replica here ? */
+ windows_conn_disconnect (prp->conn);
+ }
+ else if (rc == UPDATE_CONNECTION_LOST)
+ {
+ dev_debug("windows_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_CONNECTION_LOST -> STATE_BACKOFF_START");
+ next_state = STATE_BACKOFF_START;
+ }
+ else if (rc == UPDATE_TIMEOUT)
+ {
+ dev_debug("windows_inc_run(STATE_SENDING_UPDATES) -> send_updates = UPDATE_TIMEOUT -> STATE_BACKOFF_START");
+ next_state = STATE_BACKOFF_START;
+ }
+ }
+ last_start_time = 0UL;
+ break;
+ }
+/* if (NULL != ruv)
+ {
+ ruv_destroy(&ruv); ruv = NULL;
+ } XXX elliot commented this out */
+ agmt_set_last_update_end(prp->agmt, current_time());
+ agmt_set_update_in_progress(prp->agmt, PR_FALSE);
+ /* If timed out, close the connection after released the replica */
+ windows_release_replica(prp);
+ if (rc == UPDATE_TIMEOUT) {
+ windows_conn_disconnect(prp->conn);
+ }
+ if (rc == UPDATE_NO_MORE_UPDATES && num_changes_sent > 0)
+ {
+ if (pausetime > 0)
+ {
+ /* richm - 20020219 - If we have acquired the consumer, and another master has gone
+ into backoff waiting for us to release it, we may acquire the replica sooner
+ than the other master has a chance to, and the other master may not be able
+ to acquire the consumer for a long time (hours, days?) if this server is
+ under a heavy load (see reliab06 et. al. system tests)
+ So, this sleep gives the other master(s) a chance to acquire the consumer
+ replica */
+ long loops = pausetime;
+ /* the while loop is so that we don't just sleep and sleep if an
+ event comes in that we should handle immediately (like shutdown) */
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "%s: Pausing updates for %ld seconds to allow other suppliers to update consumer\n",
+ agmt_get_long_name(prp->agmt), pausetime);
+ while (loops-- && !(PROTOCOL_IS_SHUTDOWN(prp)))
+ {
+ DS_Sleep(PR_SecondsToInterval(1));
+ }
+ }
+ else if (num_changes_sent > 10)
+ {
+ /* wait for consumer to write its ruv if the replication was busy */
+ /* When asked, consumer sends its ruv in cache to the supplier. */
+ /* DS_Sleep ( PR_SecondsToInterval(1) ); */
+ }
+ }
+ windows_dirsync_inc_run(prp);
+
+ break;
+ case STATE_STOP_FATAL_ERROR:
+ /*
+ * We encountered some sort of a fatal error. Suspend.
+ */
+ /* XXXggood update state in replica */
+ agmt_set_last_update_status(prp->agmt, -1, 0, "Incremental update has failed and requires administrator action");
+ dev_debug("windows_inc_run(STATE_STOP_FATAL_ERROR)");
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Incremental update failed and requires administrator action\n",
+ agmt_get_long_name(prp->agmt));
+ next_state = STATE_STOP_FATAL_ERROR_PART2;
+ break;
+ case STATE_STOP_FATAL_ERROR_PART2:
+ if (PROTOCOL_IS_SHUTDOWN(prp))
+ {
+ done = 1;
+ break;
+ }
+
+ /* MAB: This state is the FATAL state where we are supposed to get
+ as a result of a FATAL error on send_updates. But, as bug
+ states, send_updates was always returning TRANSIENT errors and never
+ FATAL... In other words, this code has never been tested before...
+
+ As of 01/16/01, this piece of code was in a very dangerous state. In particular,
+ 1) it does not catch any events
+ 2) it is a terminal state (once reached it never transitions to a different state)
+
+ Both things combined make this state to become a consuming infinite loop
+ that is useless after all (we are in a fatal place requiring manual admin jobs */
+
+ /* MAB: The following lines fix problem number 1 above... When the code gets
+ into this state, it should only get a chance to get out of it by an
+ EVENT_AGMT_CHANGED event... All other events should be ignored */
+ else if (event_occurred(prp, EVENT_AGMT_CHANGED))
+ {
+ dev_debug("windows_inc_run(STATE_STOP_FATAL_ERROR): EVENT_AGMT_CHANGED received\n");
+ /* Chance to recover for the EVENT_AGMT_CHANGED event.
+ This is not mandatory, but fixes problem 2 above */
+ next_state = STATE_STOP_NORMAL_TERMINATION;
+ }
+ else
+ {
+ dev_debug("windows_inc_run(STATE_STOP_FATAL_ERROR): Event received. Clearing it\n");
+ reset_events (prp);
+ }
+
+ protocol_sleep (prp, PR_INTERVAL_NO_TIMEOUT);
+ break;
+
+ case STATE_STOP_NORMAL_TERMINATION:
+ /*
+ * We encountered some sort of a fatal error. Return.
+ */
+ /* XXXggood update state in replica */
+ dev_debug("windows_inc_run(STATE_STOP_NORMAL_TERMINATION)");
+ done = 1;
+ break;
+ }
+
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "%s: State: %s -> %s\n",
+ agmt_get_long_name(prp->agmt),
+ state2name(current_state), state2name(next_state));
+
+
+ current_state = next_state;
+ } while (!done);
+ slapi_ch_free((void**)&hostname);
+ /* remove_protocol_callbacks(prp); */
+ prp->stopped = 1;
+ /* Cancel any linger timer that might be in effect... */
+ conn_cancel_linger(prp->conn);
+ /* ... and disconnect, if currently connected */
+ conn_disconnect(prp->conn);
+}
+
+
+
+/*
+ * Go to sleep until awakened.
+ */
+static void
+protocol_sleep(Private_Repl_Protocol *prp, PRIntervalTime duration)
+{
+ PR_ASSERT(NULL != prp);
+ PR_Lock(prp->lock);
+ /* we should not go to sleep if there are events available to be processed.
+ Otherwise, we can miss the event that suppose to wake us up */
+ if (prp->eventbits == 0)
+ PR_WaitCondVar(prp->cvar, duration);
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "%s: Incremental protocol: can't go to sleep: event bits - %x\n",
+ agmt_get_long_name(prp->agmt), prp->eventbits);
+ }
+ PR_Unlock(prp->lock);
+}
+
+
+/*
+ * Notify the protocol about some event. Signal the condition
+ * variable in case the protocol is sleeping. Multiple occurences
+ * of a single event type are not remembered (e.g. no stack
+ * of events is maintained).
+ */
+static void
+event_notify(Private_Repl_Protocol *prp, PRUint32 event)
+{
+ PR_ASSERT(NULL != prp);
+ PR_Lock(prp->lock);
+ prp->eventbits |= event;
+ PR_NotifyCondVar(prp->cvar);
+ PR_Unlock(prp->lock);
+}
+
+
+/*
+ * Test to see if an event occurred. The event is cleared when
+ * read.
+ */
+static PRUint32
+event_occurred(Private_Repl_Protocol *prp, PRUint32 event)
+{
+ PRUint32 return_value;
+ PR_ASSERT(NULL != prp);
+ PR_Lock(prp->lock);
+ return_value = (prp->eventbits & event);
+ prp->eventbits &= ~event; /* Clear event */
+ PR_Unlock(prp->lock);
+ return return_value;
+}
+
+static void
+reset_events (Private_Repl_Protocol *prp)
+{
+ PR_ASSERT(NULL != prp);
+ PR_Lock(prp->lock);
+ prp->eventbits = 0;
+ PR_Unlock(prp->lock);
+}
+
+
+/*
+ * Replay the actual update to the consumer. Construct an appropriate LDAP
+ * operation, attach the baggage LDAPv3 control that contains the CSN, etc.,
+ * and send the operation to the consumer.
+ */
+ConnResult
+w_replay_update(Private_Repl_Protocol *prp, slapi_operation_parameters *op)
+{
+ ConnResult return_value;
+ LDAPControl *update_control = NULL;
+ char csn_str[CSN_STRSIZE]; /* For logging only */
+ char *old_dn;
+ char *password = NULL;
+
+ old_dn = slapi_ch_strdup(op->target_address.dn);
+
+ csn_as_string(op->csn, PR_FALSE, csn_str);
+
+ if ( windows_private_get_windows_replarea(prp->agmt) && windows_private_get_directory_replarea(prp->agmt) )
+ {
+ Slapi_DN *sdn;
+
+ sdn = slapi_sdn_new_dn_passin( op->target_address.dn );
+ sdn = map_dn_user(sdn, REPLICA_TYPE_WINDOWS, windows_private_get_windows_replarea( prp->agmt) );
+
+ op->target_address.dn = slapi_ch_strdup(slapi_sdn_get_ndn(sdn));
+
+ }
+
+ /* Construct the replication info control that accompanies the operation */
+ if (SLAPI_OPERATION_ADD == op->operation_type)
+ {
+
+ map_entry_user(&(op->p.p_add.target_entry), REPLICA_TYPE_WINDOWS, &password);
+
+
+ }
+ else if (SLAPI_OPERATION_MODRDN == op->operation_type)
+ {
+ /* op->p.p_modrdn.modrdn_newrdn needs to change from
+ uid=fbar to cn=fbar */
+
+
+ }
+ else if (SLAPI_OPERATION_MODIFY == op->operation_type)
+ {
+ alter_mods(& op->p.p_modify.modify_mods, &password) ;
+ /* if the entry already exists, modify_mods will be null.
+ attempting to modifiy null will return LDAP_PARAM_ERROR.
+ The password still might be set. bail */
+ if ( op->p.p_modify.modify_mods == NULL)
+ {
+ return_value = CONN_OPERATION_SUCCESS;
+ goto skipchange;
+ }
+
+ }
+
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "%s: replay_update: Sending %s operation (dn=\"%s\" csn=%s)\n",
+ agmt_get_long_name(prp->agmt),
+ op2string(op->operation_type), op->target_address.dn, csn_str);
+ /* What type of operation is it? */
+ switch (op->operation_type)
+ {
+ case SLAPI_OPERATION_ADD:
+ {
+ LDAPMod **entryattrs;
+ /* Convert entry to mods */
+ (void)slapi_entry2mods (op->p.p_add.target_entry,
+ NULL /* &entrydn : We don't need it */,
+ &entryattrs);
+ if (NULL == entryattrs)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: replay_update: Cannot convert entry to LDAPMods.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = CONN_LOCAL_ERROR;
+ }
+ else
+ {
+ return_value = windows_conn_send_add(prp->conn, op->target_address.dn,
+ entryattrs, update_control, NULL /* returned controls */);
+ ldap_mods_free(entryattrs, 1);
+ }
+ break;
+ }
+ case SLAPI_OPERATION_MODIFY:
+ return_value = windows_conn_send_modify(prp->conn, op->target_address.dn,
+ op->p.p_modify.modify_mods, update_control,
+ NULL /* returned controls */);
+ break;
+ case SLAPI_OPERATION_DELETE:
+ return_value = windows_conn_send_delete(prp->conn, op->target_address.dn,
+ update_control, NULL /* returned controls */);
+ break;
+ case SLAPI_OPERATION_MODRDN:
+ /* XXXggood need to pass modrdn mods in update control! */
+ return_value = windows_conn_send_rename(prp->conn, op->target_address.dn,
+ op->p.p_modrdn.modrdn_newrdn,
+ op->p.p_modrdn.modrdn_newsuperior_address.dn,
+ op->p.p_modrdn.modrdn_deloldrdn,
+ update_control, NULL /* returned controls */);
+ break;
+ default:
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "%s: replay_update: Unknown "
+ "operation type %d found in changelog - skipping change.\n",
+ agmt_get_long_name(prp->agmt), op->operation_type);
+ }
+
+skipchange:
+ if (NULL != password)
+ { /* this entry had a password, handle it seperately */
+ /* http://support.microsoft.com/?kbid=269190 */
+
+ int pw_return;
+ LDAPMod *pw_mods[2];
+ LDAPMod pw_mod;
+
+ char *pw[2];
+ pw[0] = password;
+ pw[1] = NULL;
+
+ pw_mod.mod_type = "UnicodePwd";
+ pw_mod.mod_op = LDAP_MOD_REPLACE;
+ pw_mod.mod_values = pw;
+
+ pw_mods[0] = &pw_mod;
+ pw_mods[1] = NULL;
+
+
+ pw_return = windows_conn_send_modify(prp->conn, op->target_address.dn,
+ pw_mods, NULL, NULL );
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "%s: replay_update: update password returned %d\n",
+ agmt_get_long_name(prp->agmt), pw_return );
+
+
+ // slapi_ch_free((void**) &password);
+
+ }
+
+ /* XXX
+ what else should be 'ignored' here?. and in which cases?
+ Or should this go in a different part of the file? */
+ if ( CONN_OPERATION_SUCCESS == return_value )
+ {
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "%s: replay_update: Consumer successfully replayed operation with csn %s\n",
+ agmt_get_long_name(prp->agmt), csn_str);
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "%s: replay_update: Consumer could not replay operation with csn %s\n",
+ agmt_get_long_name(prp->agmt), csn_str);
+ }
+ return return_value;
+}
+
+static PRBool
+is_dummy_operation (const slapi_operation_parameters *op)
+{
+ return (strcmp (op->target_address.uniqueid, START_ITERATION_ENTRY_UNIQUEID) == 0);
+}
+
+
+
+void
+w_cl5_operation_parameters_done (struct slapi_operation_parameters *sop)
+{
+ if(sop!=NULL) {
+ switch(sop->operation_type)
+ {
+ case SLAPI_OPERATION_BIND:
+ slapi_ch_free((void **)&(sop->p.p_bind.bind_saslmechanism));
+ if (sop->p.p_bind.bind_creds)
+ ber_bvecfree((struct berval**)&(sop->p.p_bind.bind_creds));
+ if (sop->p.p_bind.bind_ret_saslcreds)
+ ber_bvecfree((struct berval**)&(sop->p.p_bind.bind_ret_saslcreds));
+ sop->p.p_bind.bind_creds = NULL;
+ sop->p.p_bind.bind_ret_saslcreds = NULL;
+ break;
+ case SLAPI_OPERATION_COMPARE:
+ ava_done((struct ava *)&(sop->p.p_compare.compare_ava));
+ break;
+ case SLAPI_OPERATION_SEARCH:
+ slapi_ch_free((void **)&(sop->p.p_search.search_strfilter));
+ charray_free(sop->p.p_search.search_attrs);
+ slapi_filter_free(sop->p.p_search.search_filter,1);
+ break;
+ case SLAPI_OPERATION_MODRDN:
+ sop->p.p_modrdn.modrdn_deloldrdn = 0;
+ break;
+ case SLAPI_OPERATION_EXTENDED:
+ slapi_ch_free((void **)&(sop->p.p_extended.exop_oid));
+ if (sop->p.p_extended.exop_value)
+ ber_bvecfree((struct berval**)&(sop->p.p_extended.exop_value));
+ sop->p.p_extended.exop_value = NULL;
+ break;
+ default:
+ break;
+ }
+ }
+ operation_parameters_done(sop);
+
+}
+
+
+
+/*
+ * Send a set of updates to the replica. Assumes that (1) the replica
+ * has already been acquired, (2) that the consumer's update vector has
+ * been checked and (3) that it's ok to send incremental updates.
+ * Returns:
+ * UPDATE_NO_MORE_UPDATES - all updates were sent succussfully
+ * UPDATE_TRANSIENT_ERROR - some non-permanent error occurred. Try again later.
+ * UPDATE_FATAL_ERROR - some bad, permanent error occurred.
+ * UPDATE_SCHEDULE_WINDOW_CLOSED - the schedule window closed on us.
+ */
+static int
+send_updates(Private_Repl_Protocol *prp, RUV *remote_update_vector, PRUint32 *num_changes_sent)
+{
+ CL5Entry entry;
+ slapi_operation_parameters op;
+ int return_value;
+ int rc;
+ CL5ReplayIterator *changelog_iterator = NULL;
+
+ *num_changes_sent = 0;
+ /*
+ * Iterate over the changelog. Retrieve each update,
+ * construct an appropriate LDAP operation,
+ * attaching the CSN, and send the change.
+ */
+
+ //rc = cl5CreateReplayIterator( prp, remote_update_vector, &changelog_iterator); // XXX fixme
+ rc = cl5CreateReplayIteratorEx( prp, remote_update_vector, &changelog_iterator, agmt_get_consumerRID(prp->agmt)); // XXX fixme
+ if (CL5_SUCCESS != rc)
+ {
+ switch (rc)
+ {
+ case CL5_BAD_DATA: /* invalid parameter passed to the function */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Invalid parameter passed to cl5CreateReplayIterator\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_BAD_FORMAT: /* db data has unexpected format */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Unexpected format encountered in changelog database\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_BAD_STATE: /* changelog is in an incorrect state for attempted operation */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Changelog database was in an incorrect state\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_BAD_DBVERSION: /* changelog has invalid dbversion */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Incorrect dbversion found in changelog database\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_DB_ERROR: /* database error */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: A changelog database error was encountered\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_NOTFOUND: /* we have no changes to send */
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "%s: No changes to send\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_NO_MORE_UPDATES;
+ break;
+ case CL5_MEMORY_ERROR: /* memory allocation failed */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Memory allocation error occurred\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_SYSTEM_ERROR: /* NSPR error occurred: use PR_GetError for furhter info */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: An NSPR error (%d) occurred\n",
+ agmt_get_long_name(prp->agmt), PR_GetError());
+ return_value = UPDATE_TRANSIENT_ERROR;
+ break;
+ case CL5_CSN_ERROR: /* CSN API failed */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: A CSN API failure was encountered\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_TRANSIENT_ERROR;
+ break;
+ case CL5_RUV_ERROR: /* RUV API failed */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: An RUV API failure occurred\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_TRANSIENT_ERROR;
+ break;
+ case CL5_OBJSET_ERROR: /* namedobjset api failed */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: A namedobject API failure occurred\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_TRANSIENT_ERROR;
+ break;
+ case CL5_PURGED_DATA: /* requested data has been purged */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Data required to update replica has been purged. "
+ "The replica must be reinitialized.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_MISSING_DATA: /* data should be in the changelog, but is missing */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Missing data encountered\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ case CL5_UNKNOWN_ERROR: /* unclassified error */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: An unknown error was ecountered\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_TRANSIENT_ERROR;
+ break;
+ default:
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: An unknown error (%d) occurred "
+ "(cl5CreateReplayIterator)\n",
+ agmt_get_long_name(prp->agmt), rc);
+ return_value = UPDATE_TRANSIENT_ERROR;
+ }
+ }
+ else
+ {
+ int finished = 0;
+ ConnResult replay_crc;
+ char csn_str[CSN_STRSIZE];
+
+ memset ( (void*)&op, 0, sizeof (op) );
+ entry.op = &op;
+ do {
+ w_cl5_operation_parameters_done ( entry.op );
+ memset ( (void*)entry.op, 0, sizeof (op) );
+ rc = cl5GetNextOperationToReplay(changelog_iterator, &entry);
+ switch (rc)
+ {
+ case CL5_SUCCESS:
+ /* check that we don't return dummy entries */
+ if (is_dummy_operation (entry.op))
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: changelog iteration code returned a dummy entry with csn %s, "
+ "skipping ...\n",
+ agmt_get_long_name(prp->agmt), csn_as_string(entry.op->csn, PR_FALSE, csn_str));
+ continue;
+ }
+ replay_crc = w_replay_update(prp, entry.op);
+ if (CONN_OPERATION_SUCCESS != replay_crc)
+ {
+ int operation, error;
+ conn_get_error(prp->conn, &operation, &error);
+ csn_as_string(entry.op->csn, PR_FALSE, csn_str);
+ /* Figure out what to do next */
+ if (CONN_OPERATION_FAILED == replay_crc)
+ {
+ /* Map ldap error code to return value */
+ if (!ignore_error_and_keep_going(error))
+ {
+ return_value = UPDATE_TRANSIENT_ERROR;
+ finished = 1;
+ }
+ else
+ {
+ agmt_inc_last_update_changecount (prp->agmt, csn_get_replicaid(entry.op->csn), 1 /*skipped*/);
+ }
+ slapi_log_error(finished ? SLAPI_LOG_FATAL : slapi_log_urp, windows_repl_plugin_name,
+ "%s: Consumer failed to replay change (uniqueid %s, CSN %s): %s. %s.\n",
+ agmt_get_long_name(prp->agmt),
+ entry.op->target_address.uniqueid, csn_str,
+ ldap_err2string(error),
+ finished ? "Will retry later" : "Skipping");
+ }
+ else if (CONN_NOT_CONNECTED == replay_crc)
+ {
+ /* We lost the connection - enter backoff state */
+
+ return_value = UPDATE_TRANSIENT_ERROR;
+ finished = 1;
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Consumer failed to replay change (uniqueid %s, CSN %s): "
+ "%s. Will retry later.\n",
+ agmt_get_long_name(prp->agmt),
+ entry.op->target_address.uniqueid, csn_str,
+ error ? ldap_err2string(error) : "Connection lost");
+ }
+ else if (CONN_TIMEOUT == replay_crc)
+ {
+ return_value = UPDATE_TIMEOUT;
+ finished = 1;
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Consumer timed out to replay change (uniqueid %s, CSN %s): "
+ "%s.\n",
+ agmt_get_long_name(prp->agmt),
+ entry.op->target_address.uniqueid, csn_str,
+ error ? ldap_err2string(error) : "Timeout");
+ }
+ else if (CONN_LOCAL_ERROR == replay_crc)
+ {
+ /*
+ * Something bad happened on the local server - enter
+ * backoff state.
+ */
+ return_value = UPDATE_TRANSIENT_ERROR;
+ finished = 1;
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Failed to replay change (uniqueid %s, CSN %s): "
+ "Local error. Will retry later.\n",
+ agmt_get_long_name(prp->agmt),
+ entry.op->target_address.uniqueid, csn_str);
+ }
+
+ }
+ else
+ {
+ /* Positive response received */
+ (*num_changes_sent)++;
+ agmt_inc_last_update_changecount (prp->agmt, csn_get_replicaid(entry.op->csn), 0 /*replayed*/);
+ }
+ break;
+ case CL5_BAD_DATA:
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Invalid parameter passed to cl5GetNextOperationToReplay\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ finished = 1;
+ break;
+ case CL5_NOTFOUND:
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "%s: No more updates to send (cl5GetNextOperationToReplay)\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_NO_MORE_UPDATES;
+ finished = 1;
+ break;
+ case CL5_DB_ERROR:
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: A database error occurred (cl5GetNextOperationToReplay)\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ finished = 1;
+ break;
+ case CL5_BAD_FORMAT:
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: A malformed changelog entry was encountered (cl5GetNextOperationToReplay)\n",
+ agmt_get_long_name(prp->agmt));
+ break;
+ case CL5_MEMORY_ERROR:
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: A memory allocation error occurred (cl5GetNextOperationToRepla)\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = UPDATE_FATAL_ERROR;
+ break;
+ default:
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Unknown error code (%d) returned from cl5GetNextOperationToReplay\n",
+ agmt_get_long_name(prp->agmt), rc);
+ return_value = UPDATE_TRANSIENT_ERROR;
+ break;
+ }
+ /* Check for protocol shutdown */
+ if (prp->terminate)
+ {
+ return_value = UPDATE_NO_MORE_UPDATES;
+ finished = 1;
+ }
+ if (*num_changes_sent >= MAX_CHANGES_PER_SESSION)
+ {
+ return_value = UPDATE_YIELD;
+ finished = 1;
+ }
+ } while (!finished);
+ w_cl5_operation_parameters_done ( entry.op );
+ cl5DestroyReplayIterator(&changelog_iterator);
+ }
+ return return_value;
+}
+
+
+
+/*
+ * XXXggood this should probably be in the superclass, since the full update
+ * protocol is going to need it too.
+ */
+static int
+windows_inc_stop(Private_Repl_Protocol *prp)
+{
+ int return_value;
+ PRIntervalTime start, maxwait, now;
+ int seconds = 1200;
+
+ maxwait = PR_SecondsToInterval(seconds);
+ prp->terminate = 1;
+ event_notify(prp, EVENT_PROTOCOL_SHUTDOWN);
+ start = PR_IntervalNow();
+ now = start;
+ while (!prp->stopped && ((now - start) < maxwait))
+ {
+ DS_Sleep(PR_SecondsToInterval(1));
+ now = PR_IntervalNow();
+ }
+ if (!prp->stopped)
+ {
+ /* Isn't listening. Do something drastic. */
+ return_value = -1;
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "%s: windows_inc_stop: protocol does not stop after %d seconds\n",
+ agmt_get_long_name(prp->agmt), seconds);
+ }
+ else
+ {
+ return_value = 0;
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "%s: windows_inc_stop: protocol stopped after %d seconds\n",
+ agmt_get_long_name(prp->agmt),
+ PR_IntervalToSeconds(now-start));
+ }
+ return return_value;
+}
+
+
+
+static int
+windows_inc_status(Private_Repl_Protocol *prp)
+{
+ int return_value = 0;
+
+ return return_value;
+}
+
+
+
+static void
+windows_inc_notify_update(Private_Repl_Protocol *prp)
+{
+ event_notify(prp, EVENT_TRIGGERING_CRITERIA_MET);
+}
+
+
+static void
+windows_inc_update_now(Private_Repl_Protocol *prp)
+{
+ event_notify(prp, EVENT_REPLICATE_NOW);
+}
+
+
+static void
+windows_inc_notify_agmt_changed(Private_Repl_Protocol *prp)
+{
+ event_notify(prp, EVENT_AGMT_CHANGED);
+}
+
+static void
+windows_inc_notify_window_opened (Private_Repl_Protocol *prp)
+{
+ event_notify(prp, EVENT_WINDOW_OPENED);
+}
+
+static void
+windows_inc_notify_window_closed (Private_Repl_Protocol *prp)
+{
+ event_notify(prp, EVENT_WINDOW_CLOSED);
+}
+
+Private_Repl_Protocol *
+Windows_Inc_Protocol_new(Repl_Protocol *rp)
+{
+ windows_inc_private *rip = NULL;
+ Private_Repl_Protocol *prp = (Private_Repl_Protocol *)slapi_ch_malloc(sizeof(Private_Repl_Protocol));
+ prp->delete = windows_inc_delete;
+ prp->run = windows_inc_run;
+ prp->stop = windows_inc_stop;
+ prp->status = windows_inc_status;
+ prp->notify_update = windows_inc_notify_update;
+ prp->notify_agmt_changed = windows_inc_notify_agmt_changed;
+ prp->notify_window_opened = windows_inc_notify_window_opened;
+ prp->notify_window_closed = windows_inc_notify_window_closed;
+ prp->update_now = windows_inc_update_now;
+ prp->replica_object = prot_get_replica_object(rp);
+ if ((prp->lock = PR_NewLock()) == NULL)
+ {
+ goto loser;
+ }
+ if ((prp->cvar = PR_NewCondVar(prp->lock)) == NULL)
+ {
+ goto loser;
+ }
+ prp->stopped = 0;
+ prp->terminate = 0;
+ prp->eventbits = 0;
+ prp->conn = prot_get_connection(rp);
+ prp->agmt = prot_get_agreement(rp);
+ prp->last_acquire_response_code = NSDS50_REPL_REPLICA_READY;
+ rip = (void *)slapi_ch_malloc(sizeof(windows_inc_private));
+ rip->ruv = NULL;
+ rip->backoff = NULL;
+ rip->rp = rp;
+ prp->private = (void *)rip;
+ prp->replica_acquired = PR_FALSE;
+ return prp;
+loser:
+ windows_inc_delete(&prp);
+ return NULL;
+}
+
+
+
+
+static void
+windows_inc_backoff_expired(time_t timer_fire_time, void *arg)
+{
+ Private_Repl_Protocol *prp = (Private_Repl_Protocol *)arg;
+ PR_ASSERT(NULL != prp);
+ event_notify(prp, EVENT_BACKOFF_EXPIRED);
+}
+
+
+
+/*
+ * Examine the update vector and determine our course of action.
+ * There are 3 different possibilities, plus a catch-all error:
+ * 1 - no update vector (ruv is NULL). The consumer's replica is
+ * pristine, so it needs to be initialized. Return
+ * EXAMINE_RUV_PRISTINE_REPLICA.
+ * 2 - ruv is present, but its database generation ID doesn't
+ * match the local generation ID. This means that either
+ * the local replica must be reinitialized from the remote
+ * replica or vice-versa. Return
+ * EXAMINE_RUV_GENERATION_MISMATCH.
+ * 3 - ruv is present, and we have all updates needed to bring
+ * the replica up to date using the incremental protocol.
+ * return EXAMINE_RUV_OK.
+ * 4 - parameter error. Return EXAMINE_RUV_PARAM_ERROR
+ */
+static int
+windows_examine_update_vector(Private_Repl_Protocol *prp, RUV *remote_ruv)
+{
+ int return_value;
+
+ PR_ASSERT(NULL != prp);
+ if (NULL == prp)
+ {
+ return_value = EXAMINE_RUV_PARAM_ERROR;
+ }
+ else if (NULL == remote_ruv)
+ {
+ return_value = EXAMINE_RUV_PRISTINE_REPLICA;
+ }
+ else
+ {
+ char *local_gen = NULL;
+ char *remote_gen = ruv_get_replica_generation(remote_ruv);
+ Object *local_ruv_obj;
+ RUV *local_ruv;
+ Replica *replica;
+
+ PR_ASSERT(NULL != prp->replica_object);
+ replica = object_get_data(prp->replica_object);
+ PR_ASSERT(NULL != replica);
+ local_ruv_obj = replica_get_ruv (replica);
+ if (NULL != local_ruv_obj)
+ {
+ local_ruv = (RUV*) object_get_data (local_ruv_obj);
+ PR_ASSERT (local_ruv);
+ local_gen = ruv_get_replica_generation(local_ruv);
+ object_release (local_ruv_obj);
+ }
+ if (NULL == remote_gen || NULL == local_gen || strcmp(remote_gen, local_gen) != 0)
+ {
+ return_value = EXAMINE_RUV_GENERATION_MISMATCH;
+ }
+ else
+ {
+ return_value = EXAMINE_RUV_OK;
+ }
+ slapi_ch_free((void**)&remote_gen);
+ slapi_ch_free((void**)&local_gen);
+ }
+ return return_value;
+}
+
+
+/*
+ * When we get an error from an LDAP operation, we call this
+ * function to decide if we should just keep replaying
+ * updates, or if we should stop, back off, and try again
+ * later.
+ * Returns PR_TRUE if we shoould keep going, PR_FALSE if
+ * we should back off and try again later.
+ *
+ * In general, we keep going if the return code is consistent
+ * with some sort of bug in URP that causes the consumer to
+ * emit an error code that it shouldn't have, e.g. LDAP_ALREADY_EXISTS.
+ *
+ * We stop if there's some indication that the server just completely
+ * failed to process the operation, e.g. LDAP_OPERATIONS_ERROR.
+ */
+static PRBool
+ignore_error_and_keep_going(int error)
+{
+ int return_value;
+
+ switch (error)
+ {
+ /* Cases where we keep going */
+ case LDAP_SUCCESS:
+ case LDAP_NO_SUCH_ATTRIBUTE:
+ case LDAP_UNDEFINED_TYPE:
+ case LDAP_CONSTRAINT_VIOLATION:
+ case LDAP_TYPE_OR_VALUE_EXISTS:
+ case LDAP_INVALID_SYNTAX:
+ case LDAP_NO_SUCH_OBJECT:
+ case LDAP_INVALID_DN_SYNTAX:
+ case LDAP_IS_LEAF:
+ case LDAP_INSUFFICIENT_ACCESS:
+ case LDAP_NAMING_VIOLATION:
+ case LDAP_OBJECT_CLASS_VIOLATION:
+ case LDAP_NOT_ALLOWED_ON_NONLEAF:
+ case LDAP_NOT_ALLOWED_ON_RDN:
+ case LDAP_ALREADY_EXISTS:
+ case LDAP_NO_OBJECT_CLASS_MODS:
+ return_value = PR_TRUE;
+ break;
+
+ /* Cases where we stop and retry */
+ case LDAP_OPERATIONS_ERROR:
+ case LDAP_PROTOCOL_ERROR:
+ case LDAP_TIMELIMIT_EXCEEDED:
+ case LDAP_SIZELIMIT_EXCEEDED:
+ case LDAP_STRONG_AUTH_NOT_SUPPORTED:
+ case LDAP_STRONG_AUTH_REQUIRED:
+ case LDAP_PARTIAL_RESULTS:
+ case LDAP_REFERRAL:
+ case LDAP_ADMINLIMIT_EXCEEDED:
+ case LDAP_UNAVAILABLE_CRITICAL_EXTENSION:
+ case LDAP_CONFIDENTIALITY_REQUIRED:
+ case LDAP_SASL_BIND_IN_PROGRESS:
+ case LDAP_INAPPROPRIATE_MATCHING:
+ case LDAP_ALIAS_PROBLEM:
+ case LDAP_ALIAS_DEREF_PROBLEM:
+ case LDAP_INAPPROPRIATE_AUTH:
+ case LDAP_INVALID_CREDENTIALS:
+ case LDAP_BUSY:
+ case LDAP_UNAVAILABLE:
+ case LDAP_UNWILLING_TO_PERFORM:
+ case LDAP_LOOP_DETECT:
+ case LDAP_SORT_CONTROL_MISSING:
+ case LDAP_INDEX_RANGE_ERROR:
+ case LDAP_RESULTS_TOO_LARGE:
+ case LDAP_AFFECTS_MULTIPLE_DSAS:
+ case LDAP_OTHER:
+ case LDAP_SERVER_DOWN:
+ case LDAP_LOCAL_ERROR:
+ case LDAP_ENCODING_ERROR:
+ case LDAP_DECODING_ERROR:
+ case LDAP_TIMEOUT:
+ case LDAP_AUTH_UNKNOWN:
+ case LDAP_FILTER_ERROR:
+ case LDAP_USER_CANCELLED:
+ case LDAP_PARAM_ERROR:
+ case LDAP_NO_MEMORY:
+ case LDAP_CONNECT_ERROR:
+ case LDAP_NOT_SUPPORTED:
+ case LDAP_CONTROL_NOT_FOUND:
+ case LDAP_NO_RESULTS_RETURNED:
+ case LDAP_MORE_RESULTS_TO_RETURN:
+ case LDAP_CLIENT_LOOP:
+ case LDAP_REFERRAL_LIMIT_EXCEEDED:
+ return_value = PR_FALSE;
+ break;
+ }
+ return return_value;
+}
+
+/* this function converts a state to its name - for debug output */
+static const char*
+state2name (int state)
+{
+ switch (state)
+ {
+ case STATE_START: return "start";
+ case STATE_WAIT_WINDOW_OPEN: return "wait_for_window_to_open";
+ case STATE_WAIT_CHANGES: return "wait_for_changes";
+ case STATE_READY_TO_ACQUIRE: return "ready_to_acquire_replica";
+ case STATE_BACKOFF_START: return "start_backoff";
+ case STATE_BACKOFF: return "backoff";
+ case STATE_SENDING_UPDATES: return "sending_updates";
+ case STATE_STOP_FATAL_ERROR: return "stop_fatal_error";
+ case STATE_STOP_FATAL_ERROR_PART2: return "stop_fatal_error";
+ case STATE_STOP_NORMAL_TERMINATION: return "stop_normal_termination";
+ default: return "invalid_state";
+ }
+}
+
+/* this function convert s an event to its name - for debug output */
+static const char*
+event2name (int event)
+{
+ switch (event)
+ {
+ case EVENT_WINDOW_OPENED: return "update_window_opened";
+ case EVENT_WINDOW_CLOSED: return "update_window_closed";
+ case EVENT_TRIGGERING_CRITERIA_MET: return "data_modified";
+ case EVENT_BACKOFF_EXPIRED: return "backoff_timer_expired";
+ case EVENT_REPLICATE_NOW: return "replicate_now";
+ case EVENT_PROTOCOL_SHUTDOWN: return "protocol_shutdown";
+ case EVENT_AGMT_CHANGED: return "agreement_changed";
+ default: return "invalid_event";
+ }
+}
+
+static const char*
+op2string(int op)
+{
+ switch (op) {
+ case SLAPI_OPERATION_ADD:
+ return "add";
+ case SLAPI_OPERATION_MODIFY:
+ return "modify";
+ case SLAPI_OPERATION_DELETE:
+ return "delete";
+ case SLAPI_OPERATION_MODRDN:
+ return "rename";
+ case SLAPI_OPERATION_EXTENDED:
+ return "extended";
+ }
+
+ return "unknown";
+}
+
+
+
+void map_entry_user(Slapi_Entry **e, int map_to, char** password) {
+ /* do schema changes */
+
+ Slapi_Entry *new_entry;
+ Slapi_Attr *a;
+ Slapi_Attr **attr = &a;
+ Slapi_Attr *prevattr = NULL;
+ int rc;
+ char* ldif = ""; /* for debugging only */
+ int ldif_len=0; /* for debugging only */
+
+ a = slapi_attr_new();
+
+ new_entry = slapi_entry_alloc();
+
+
+ rc =slapi_entry_first_attr( *e, attr );
+
+ while (rc == 0)
+ {
+ char* attr_type;
+ Slapi_Value *old_value;
+ int i=-1;
+ int ret = 0;
+ slapi_attr_get_type(*attr, &attr_type);
+
+
+ while ( mapping[++i].old_name != NULL)
+ {
+
+
+ if ((mapping[i].map_to == map_to) && strcasecmp(attr_type, mapping[i].old_name) == 0 )
+ {
+
+ slapi_attr_first_value( *attr, &old_value);
+ slapi_entry_add_value (new_entry, mapping[i].new_name, old_value);
+
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "translate_attrib \n\t%s\n\t%s\n", mapping[i].old_name, mapping[i].new_name);
+
+ }
+
+ }
+
+ if ((map_to == REPLICA_TYPE_WINDOWS) && (
+ slapi_attr_type_cmp(attr_type, PSEUDO_ATTR_UNHASHEDUSERPASSWORD , 1) == 0))
+ {
+ int b;
+ const char* pw;
+ pw = slapi_value_get_string(old_value);
+
+ *password = (char*) slapi_ch_malloc(strlen( pw ) + 3); /* "%s"\0 */
+ b = sprintf(*password, "\"%s\"", pw);
+
+ }
+
+
+ if (mapping[i].old_name != NULL) {
+ slapi_attr_first_value( *attr, &old_value);
+ ret = slapi_entry_add_value (new_entry, mapping[i].new_name, old_value);
+ }
+
+ prevattr = *attr;
+ rc = slapi_entry_next_attr( *e, prevattr, attr );
+
+ }
+ /* replace the existing entry with our duplicate */
+ {
+ char *ad_objectclass[] = {"top", "person", "organizationalperson", "user", NULL};
+ char *ds_objectclass[] = {"top", "person", "organizationalperson", "inetorgperson", "ntuser", NULL};
+
+ Slapi_ValueSet *vs = slapi_valueset_new();
+ Slapi_Value * addval;
+ int j = -1;
+
+ if (map_to == REPLICA_TYPE_WINDOWS) {
+
+ while (ad_objectclass[++j]!= NULL)
+ {
+ addval = slapi_value_new_string(ad_objectclass[j]);
+ slapi_valueset_add_value(vs,addval);
+ slapi_value_free(&addval);
+ }
+ }
+ else
+ {
+ while (ds_objectclass[++j]!= NULL)
+ {
+ addval = slapi_value_new_string(ds_objectclass[j]);
+ slapi_valueset_add_value(vs,addval);
+ slapi_value_free(&addval);
+ }
+ }
+
+ slapi_entry_add_valueset(new_entry,"objectclass", vs);
+ slapi_valueset_free(vs);
+
+ }
+
+ /*
+ ldif = slapi_entry2str( *e, &ldif_len );
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "found entry %s from windows:\n%s\n", slapi_entry_get_ndn(*e), ldif);
+ ldif = slapi_entry2str( new_entry, &ldif_len );
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "found entry %s from windows:\n%s\n", slapi_entry_get_ndn(new_entry), ldif);
+ slapi_ch_free_string(&ldif);
+*/
+
+
+ *e = slapi_entry_dup( new_entry );
+
+}
+
+
+
+void map_entry_group(Slapi_Entry **e, int map_to) {
+ /* do schema changes */
+
+ Slapi_Entry *new_entry;
+ Slapi_Attr *a;
+ Slapi_Attr **attr = &a;
+ Slapi_Attr *prevattr = NULL;
+ int rc;
+ char* ldif = ""; /* for debugging only */
+ int ldif_len=0; /* for debugging only */
+
+ a = slapi_attr_new();
+
+ new_entry = slapi_entry_alloc();
+
+
+ rc =slapi_entry_first_attr( *e, attr );
+
+ while (rc == 0)
+ {
+ char* attr_type;
+ Slapi_Value *old_value;
+ int i=-1;
+ int ret = 0;
+ slapi_attr_get_type(*attr, &attr_type);
+
+
+ while ( group_map[++i].old_name != NULL)
+ {
+
+
+ if ((group_map[i].map_to == map_to) && strcasecmp(attr_type, group_map[i].old_name) == 0 )
+ {
+
+ slapi_attr_first_value( *attr, &old_value);
+ slapi_entry_add_value (new_entry, group_map[i].new_name, old_value);
+
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "translate_attrib \n\t%s\n\t%s\n", group_map[i].old_name, group_map[i].new_name);
+
+ }
+
+ }
+
+ if (group_map[i].old_name != NULL) {
+ slapi_attr_first_value( *attr, &old_value);
+ ret = slapi_entry_add_value (new_entry, group_map[i].new_name, old_value);
+ }
+
+ prevattr = *attr;
+ rc = slapi_entry_next_attr( *e, prevattr, attr );
+
+ }
+ /* replace the existing entry with our duplicate */
+ {
+ char *ad_objectclass[] = {"top", "group", NULL};
+ char *ds_objectclass[] = {"top", "ntGroup", NULL};
+
+ Slapi_ValueSet *vs = slapi_valueset_new();
+ Slapi_Value * addval;
+ int j = -1;
+
+ if (map_to == REPLICA_TYPE_WINDOWS) {
+
+ while (ad_objectclass[++j]!= NULL)
+ {
+ addval = slapi_value_new_string(ad_objectclass[j]);
+ slapi_valueset_add_value(vs,addval);
+ slapi_value_free(&addval);
+ }
+ }
+ else
+ {
+ while (ds_objectclass[++j]!= NULL)
+ {
+ addval = slapi_value_new_string(ds_objectclass[j]);
+ slapi_valueset_add_value(vs,addval);
+ slapi_value_free(&addval);
+ }
+ }
+
+ slapi_entry_add_valueset(new_entry,"objectclass", vs);
+ slapi_valueset_free(vs);
+
+ }
+
+
+ ldif = slapi_entry2str( *e, &ldif_len );
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "found entry %s from windows:\n%s\n", slapi_entry_get_ndn(*e), ldif);
+ ldif = slapi_entry2str( new_entry, &ldif_len );
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "found entry %s from windows:\n%s\n", slapi_entry_get_ndn(new_entry), ldif);
+ slapi_ch_free_string(&ldif);
+
+
+
+ *e = slapi_entry_dup( new_entry );
+
+}
+
+static Slapi_DN* map_dn_user(Slapi_DN *sdn, int map_to, const Slapi_DN *root)
+{
+ /* function operation
+ convert to rdn
+ pop off naming attribute of sdn "{ cn | uid }"
+ replace with other naming attribute "{ uid | cn }"
+ append root
+ return sdn by reference
+ */
+ Slapi_RDN *old_rdn = NULL;
+ Slapi_RDN *new_rdn = NULL;
+ char* naming_type = "";
+ char* naming_value= "";
+ Slapi_DN *return_sdn = NULL;
+ int rc = 0;
+ char* tempdn;
+
+ PR_ASSERT(sdn);
+ PR_ASSERT(root);
+
+ old_rdn = slapi_rdn_new_sdn(sdn); /* cn=administrator */
+
+ new_rdn = slapi_rdn_new();
+
+ rc = slapi_rdn_get_first(old_rdn, &naming_type, &naming_value); /* cn administrator */
+
+ if (map_to == REPLICA_TYPE_MULTIMASTER)
+ naming_type = slapi_ch_strdup("uid"); /* uid administrator */
+ else if (map_to == REPLICA_TYPE_WINDOWS)
+ naming_type = slapi_ch_strdup("cn");
+
+
+ rc = slapi_rdn_add(new_rdn, naming_type, naming_value); /* uid=administrator */
+
+ /* XXX this should be done with SDN and not char* */
+
+ tempdn = (char*) slapi_ch_malloc( strlen (naming_type) + strlen(naming_value) +
+ slapi_sdn_get_ndn_len(root) + 4);
+
+ sprintf(tempdn, "%s=%s, %s", naming_type, naming_value, slapi_sdn_get_ndn(root) );
+
+
+ return_sdn = slapi_sdn_new_dn_byref(tempdn);
+
+
+ return return_sdn;
+
+}
+
+static Slapi_DN* map_dn_group(Slapi_DN *sdn, int map_to, const Slapi_DN *root)
+{
+ /* function operation
+ convert to rdn
+ append root
+ return sdn by reference
+ */
+ Slapi_RDN *old_rdn = NULL;
+ Slapi_RDN *new_rdn = NULL;
+ char* naming_type = "";
+ char* naming_value= "";
+ Slapi_DN *return_sdn = NULL;
+ int rc = 0;
+ char* tempdn;
+
+ PR_ASSERT(sdn);
+ PR_ASSERT(root);
+
+ old_rdn = slapi_rdn_new_sdn(sdn); /* cn=administrator */
+
+ new_rdn = slapi_rdn_new();
+
+ rc = slapi_rdn_get_first(old_rdn, &naming_type, &naming_value); /* cn administrator */
+
+ rc = slapi_rdn_add(new_rdn, naming_type, naming_value);
+
+ /* XXX this should be done with SDN and not char* */
+
+ tempdn = (char*) slapi_ch_malloc( strlen (naming_type) + strlen(naming_value) +
+ slapi_sdn_get_ndn_len(root) + 4);
+
+ sprintf(tempdn, "%s=%s, %s", naming_type, naming_value, slapi_sdn_get_ndn(root) );
+
+
+ return_sdn = slapi_sdn_new_dn_passin(tempdn);
+
+
+ return return_sdn;
+
+}
+
+static void alter_mods(LDAPMod ***m, char** password)
+{
+ LDAPMod **mods = *m;
+ int i=0;
+
+ while (NULL != mods && NULL != mods[i])
+ {
+ int j=-1; /* index of the attribute mapping array */
+ char* attr_type;
+ attr_type = slapi_ch_strdup(mods[i]->mod_type);
+ while ( mapping[++j].old_name != NULL)
+ {
+
+ if ( (mapping[j].map_to == REPLICA_TYPE_WINDOWS) &&
+ (slapi_attr_type_cmp(attr_type, mapping[j].old_name, 1) == 0 ) )
+ {
+ mods[i]->mod_type = slapi_ch_strdup(mapping[j].new_name);
+
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "alter_mods \n\t%s\n\t%s\n", mapping[j].old_name, mapping[j].new_name);
+ break;
+
+ }
+ }
+
+ if (slapi_attr_type_cmp(attr_type, PSEUDO_ATTR_UNHASHEDUSERPASSWORD , 1) == 0)
+ {
+ int b;
+ char *pw;
+ if (mods[i]->mod_op | LDAP_MOD_BVALUES)
+ {
+ *password = (char*) slapi_ch_malloc(strlen( mods[i]->mod_bvalues[0]->bv_val) + 3); /* "%s"\0 */
+ pw = mods[i]->mod_bvalues[0]->bv_val;
+ }
+ else
+ {
+ *password = (char*) slapi_ch_malloc(strlen( mods[i]->mod_values[0]) + 3); /* "%s"\0 */
+ pw = mods[i]->mod_values[0];
+ }
+
+ b = sprintf(*password, "\"%s\"", pw);
+
+
+ }
+
+ if (mapping[j].old_name == NULL)
+ {
+ LDAPMod *this_mod = mods[i];
+
+ /* Move down all subsequent mods */
+ int k = 0;
+ for (k = i; mods[k+1] ; k++)
+ {
+ mods[k] = mods[k+1];
+ }
+ /* Zero the end of the array */
+ mods[k] = NULL;
+ /* Adjust value of j, implicit in not incrementing it */
+ /* Free this mod */
+ ber_bvecfree(this_mod->mod_bvalues);
+ slapi_ch_free((void **)&(this_mod->mod_type));
+ slapi_ch_free((void **)&this_mod);
+ }
+ else
+ i++;
+ }
+}
+
+
+/* the entry has already been translated, so be sure to search for ntuserid
+ and not samaccountname or anything else. */
+
+static Slapi_Entry* windows_entry_already_exists(Slapi_Entry *e){
+
+ int rc = 0;
+ Slapi_DN *sdn = NULL;
+ Slapi_Entry *entry = NULL;
+
+ sdn = slapi_entry_get_sdn(e);
+ rc = slapi_search_internal_get_entry( sdn, NULL, &entry, repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION));
+
+ if (rc == LDAP_SUCCESS)
+ {
+ return entry;
+ }
+ else
+ {
+ return NULL;
+ }
+
+}
+
+static PRBool entry_is_tombstone(Slapi_Entry *e){
+ return PR_FALSE;
+}
+
+static int delete_user(Slapi_Entry *e){
+ const char* dn;
+ const char* new_dn;
+ Slapi_PBlock *pb = NULL;
+ Slapi_DN *sdn = NULL;
+ int return_value = 0;
+
+ PR_ASSERT(e);
+
+ dn = slapi_ch_strdup( slapi_entry_get_ndn(e) );
+ new_dn = slapi_ch_strdup(dn);
+
+ /* XXX dn parsing in this if block could use some improvement */
+ if ( strstr(new_dn, "\ndel:") != NULL )
+ {
+ char* comma;
+ const char* c = ",";
+ pb = slapi_pblock_new();
+
+ comma = strstr(dn, c);
+
+ strcpy( strstr(new_dn, "\ndel:"), comma);
+
+ slapi_delete_internal_set_pb(pb, new_dn, NULL, NULL, repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0);
+ slapi_delete_internal_pb(pb);
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &return_value);
+ slapi_pblock_destroy(pb);
+
+
+ }
+ else
+ {
+ return_value = -1;
+ }
+
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "delete operation of entry %s returned: %d\n", new_dn, return_value);
+
+ slapi_ch_free((void**) &dn);
+ slapi_ch_free((void**) &new_dn);
+
+ return return_value;
+}
+
+
+static void make_mods_from_entries(Slapi_Entry *new_entry, Slapi_Entry *existing_entry, LDAPMod ***attrs){
+ Slapi_Attr *attr;
+ int rc = 0;
+ Slapi_Mods smods;
+
+ PR_ASSERT (new_entry && attrs && existing_entry);
+
+ slapi_mods_init (&smods, 0);
+
+ rc =slapi_entry_first_attr( new_entry, &attr );
+
+ while (rc == 0)
+ {
+ char* attr_type;
+ Slapi_Attr *existing_attr;
+ int i=-1;
+ Slapi_Value *v1 = NULL;
+ Slapi_Value *v2 = NULL;
+
+ slapi_attr_get_type(attr, &attr_type);
+
+ slapi_attr_first_value( attr, &v1);
+
+ if (slapi_entry_attr_find(existing_entry, attr_type, &existing_attr) == 0)
+ {
+ /* found */
+ slapi_attr_first_value( existing_attr, &v2);
+
+ if (slapi_value_compare(existing_attr, v1, v2) == 0)
+ {
+ /* same, do nothing */
+ /* slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "make_mods_from_entries: %s has same value (%s) on both sides\n", attr_type, slapi_value_get_string(v1)); */
+
+ }
+ else
+ {
+ /* attributes different -- create CHANGE */
+ /* slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "make_mods_from_entries: %s has different value (%s, %s)\n",
+ attr_type, slapi_value_get_string(v1),slapi_value_get_string(v2)); */
+
+ slapi_mods_add_mod_values(&smods, LDAP_MOD_REPLACE, attr_type, &v1 );
+ }
+
+
+ }
+ else
+ {
+ /* not found -- create ADD */
+ /*slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "make_mods_from_entries: %s has no value ( will be %s)\n",
+ attr_type, slapi_value_get_string(v1)); */
+
+
+ slapi_mods_add_mod_values(&smods, LDAP_MOD_ADD, attr_type, &v1 );
+
+ }
+
+ /* XXX TODO:
+ delete an attribute */
+
+ rc = slapi_entry_next_attr(new_entry, attr, &attr);
+
+
+ }
+ *attrs = slapi_mods_get_ldapmods_passout (&smods);
+ slapi_mods_done (&smods);
+
+
+}
+
+int add_or_modify_user(Slapi_Entry *e){
+ Slapi_PBlock *pb = NULL;
+ int return_value = 0;
+ char *dn;
+ Slapi_Entry *existing = NULL;
+
+ pb = slapi_pblock_new();
+ dn = slapi_ch_strdup( slapi_entry_get_ndn(e) );
+
+ existing = windows_entry_already_exists(e);
+ if ( existing == NULL)
+ {
+ slapi_add_entry_internal_set_pb(pb, e, NULL,
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION),
+ 0);
+ slapi_add_internal_pb(pb);
+
+ }
+ else
+ {
+ LDAPMod **mods;
+ make_mods_from_entries(e, existing, &mods);
+ if (NULL == mods)
+ { /* also get null if there are no differences, so this is ok. */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "add_or_modify_user: Cannot convert entry %s to LDAPMods.\n", dn );
+ return_value = 0;
+ goto bail;
+ }
+
+ slapi_modify_internal_set_pb (pb, slapi_entry_get_ndn(e), mods, NULL, NULL,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+ slapi_modify_internal_pb (pb);
+ ldap_mods_free(mods, 1);
+ }
+
+
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &return_value);
+
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "operation of entry %s returned: %d\n", dn, return_value);
+
+bail:
+
+ if (pb)
+ slapi_pblock_destroy(pb);
+
+ return return_value;
+}
+
+
+int add_or_modify_group(Slapi_Entry *e){
+ Slapi_PBlock *pb = NULL;
+ int return_value = 0;
+ char *dn;
+ Slapi_Entry *existing = NULL;
+
+ pb = slapi_pblock_new();
+ dn = slapi_ch_strdup( slapi_entry_get_ndn(e) );
+
+ existing = windows_entry_already_exists(e);
+ if ( existing == NULL)
+ {
+ slapi_add_entry_internal_set_pb(pb, e, NULL,
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION),
+ 0);
+ slapi_add_internal_pb(pb);
+
+ }
+ else
+ {
+ LDAPMod **mods;
+ make_mods_from_entries(e, existing, &mods);
+ if (NULL == mods)
+ { /* also get null if there are no differences, so this is ok. */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "add_or_modify_user: Cannot convert entry %s to LDAPMods.\n", dn );
+ return_value = 0;
+ goto bail;
+ }
+
+ slapi_modify_internal_set_pb (pb, slapi_entry_get_ndn(e), mods, NULL, NULL,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+ slapi_modify_internal_pb (pb);
+ ldap_mods_free(mods, 1);
+ }
+
+
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &return_value);
+
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "operation of entry %s returned: %d\n", dn, return_value);
+
+bail:
+
+ if (pb)
+ slapi_pblock_destroy(pb);
+
+ return return_value;
+}
+
+
+
+void windows_dirsync_inc_run(Private_Repl_Protocol *prp)
+ {
+
+ int rc;
+ int msgid=0;
+ Slapi_PBlock *pb = NULL;
+ Slapi_Filter *filter_user = NULL;
+ Slapi_Filter *filter_user_deleted = NULL;
+ Slapi_Filter *filter_group = NULL;
+ Slapi_Filter *filter_group_deleted = NULL;
+
+ rc = perform_search(prp->conn);
+ if (rc == CONN_OPERATION_SUCCESS)
+ {
+
+ Slapi_Entry *e;
+ int filter_ret = 0;
+
+
+#define FILTER_USER "(objectclass=user)"
+#define FILTER_GROUP "(objectclass=group)"
+#define FILTER_USER_DELETED "(&(isdeleted=*)(objectclass=user))"
+#define FILTER_GROUP_DELETED "(&(isdeleted=*)(objectclass=group))"
+
+ filter_user = slapi_str2filter( slapi_ch_strdup( FILTER_USER ) );
+ filter_user_deleted = slapi_str2filter( slapi_ch_strdup( FILTER_USER_DELETED ) );
+ filter_group = slapi_str2filter( slapi_ch_strdup( FILTER_GROUP ) );
+ filter_group_deleted = slapi_str2filter( slapi_ch_strdup( FILTER_GROUP_DELETED ) );
+
+
+
+ while ((e = windows_conn_get_search_result(prp->conn)) != NULL)
+ {
+ const Slapi_DN* sdn = NULL;
+ /* deleted users are outside the 'correct container'.
+ They live in cn=deleted objects, windows_private_get_directory_replarea( prp->agmt) */
+
+ if ( slapi_filter_test( pb, e, filter_user_deleted, 0 ) == 0 )
+ {
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "entry %s marked for deletion\n", slapi_entry_get_ndn(e));
+
+ sdn = map_dn_user(slapi_entry_get_sdn(e),REPLICA_TYPE_MULTIMASTER, windows_private_get_directory_replarea( prp->agmt) );
+ slapi_entry_set_sdn(e, sdn);
+
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "entry %s marked for deletion\n", slapi_entry_get_ndn(e));
+
+
+ delete_user(e); /* CN is "garbled" if the entry is deleted */
+ }
+ else if ( slapi_filter_test( pb, e, filter_group_deleted, 0 ) == 0 )
+ {
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "group %s marked for deletion\n", slapi_entry_get_ndn(e));
+
+ sdn = map_dn_group(slapi_entry_get_sdn(e),REPLICA_TYPE_MULTIMASTER, windows_private_get_directory_replarea( prp->agmt) );
+ slapi_entry_set_sdn(e, sdn);
+
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "entry %s marked for deletion\n", slapi_entry_get_ndn(e));
+
+ delete_user(e); /* CN is "garbled" if the entry is deleted */
+
+
+ }
+ /* must be in the previously specified container to be replicated, lest it is ignored */
+ else if (slapi_sdn_isparent(windows_private_get_windows_replarea( prp->agmt ), slapi_entry_get_sdn(e)) )
+ {
+ if (0 == slapi_filter_test( pb, e, filter_user, 0))
+ {
+ sdn = map_dn_user(slapi_entry_get_sdn(e),REPLICA_TYPE_MULTIMASTER, windows_private_get_directory_replarea( prp->agmt) );
+ map_entry_user(&e, REPLICA_TYPE_MULTIMASTER, NULL);
+
+ slapi_entry_set_sdn(e, sdn);
+ /* if (windows_private_create_users(prp->ra)) */
+ add_or_modify_user(e);
+ }
+ else if (0 == slapi_filter_test( pb, e, filter_group, 0))
+ {
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "group %s marked for addition\n", slapi_entry_get_ndn(e));
+
+ sdn = map_dn_group(slapi_entry_get_sdn(e),REPLICA_TYPE_MULTIMASTER, windows_private_get_directory_replarea( prp->agmt) );
+ slapi_entry_set_sdn(e, sdn);
+
+ map_entry_group(&e, REPLICA_TYPE_MULTIMASTER);
+
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "group %s marked for addition\n", slapi_entry_get_ndn(e));
+
+ add_or_modify_group(e);
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "entry %s in %s ignored\n", slapi_entry_get_ndn(e), slapi_sdn_get_dn(windows_private_get_directory_replarea( prp->agmt)) );
+ }
+ /* inside container */
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "entry %s ignored\n", slapi_entry_get_ndn(e));
+ }
+ }
+ }
+}
+
diff --git a/ldap/servers/plugins/replication/windows_private.c b/ldap/servers/plugins/replication/windows_private.c
new file mode 100644
index 00000000..71401e1c
--- /dev/null
+++ b/ldap/servers/plugins/replication/windows_private.c
@@ -0,0 +1,245 @@
+/** BEGIN COPYRIGHT BLOCK
+ *
+ *
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* windows_private.c */
+
+#include "repl.h"
+#include "repl5.h"
+
+
+struct windowsprivate {
+
+ Slapi_DN *windows_replarea; /* DN of replicated area (on the windows side) */
+ Slapi_DN *directory_replarea; /* DN of replicated area on directory side */
+ /* this simplifies the mapping as it's simply
+ from the former to the latter container, or
+ vice versa */
+
+ int dirsync_flags;
+ int dirsync_maxattributecount;
+ char *dirsync_cookie;
+ int dirsync_cookie_len;
+ PRBool dirsync_cookie_has_more;
+
+ PRBool create_users_from_dirsync;
+};
+
+
+
+
+Dirsync_Private* windows_private_new()
+{
+ Dirsync_Private *dp;
+ dp = (Dirsync_Private *)slapi_ch_malloc(sizeof(Dirsync_Private));
+
+ dp->windows_replarea = NULL;
+ dp->dirsync_flags = 0;
+ dp->dirsync_maxattributecount = -1;
+ dp->dirsync_cookie = NULL;
+ dp->dirsync_cookie_len = 0;
+ dp->dirsync_cookie_has_more = 0;
+ dp->create_users_from_dirsync = PR_TRUE;
+ return dp;
+
+}
+
+void windows_private_delete(Dirsync_Private **dp)
+{
+
+ PR_ASSERT(dp != NULL);
+ PR_ASSERT(*dp != NULL);
+
+ /* TODO: free cookie */
+
+ /* slapi_sdn_done((**dp).windows_replarea); */
+ slapi_ch_free((void **)dp);
+
+}
+
+
+
+const Slapi_DN* windows_private_get_windows_replarea (const Repl_Agmt *ra)
+{
+ Dirsync_Private *dp;
+ PR_ASSERT(ra);
+
+ dp = (Dirsync_Private *) get_priv_from_agmt(ra);
+ PR_ASSERT (dp);
+
+ if(dp->windows_replarea)
+ return slapi_sdn_dup (dp->windows_replarea);
+ else
+ return NULL;
+}
+
+const Slapi_DN* windows_private_get_directory_replarea (const Repl_Agmt *ra)
+{
+ Dirsync_Private *dp;
+ PR_ASSERT(ra);
+
+ dp = (Dirsync_Private *) get_priv_from_agmt(ra);
+ PR_ASSERT (dp);
+
+ if(dp->directory_replarea)
+ return slapi_sdn_dup (dp->directory_replarea);
+ else
+ return NULL;
+}
+
+
+void windows_private_set_windows_replarea (const Repl_Agmt *ra,const Slapi_DN* sdn )
+{
+
+ Dirsync_Private *dp;
+ PR_ASSERT(ra);
+ PR_ASSERT(sdn);
+
+ dp = (Dirsync_Private *) get_priv_from_agmt(ra);
+ PR_ASSERT (dp);
+
+ dp->windows_replarea = slapi_sdn_dup(sdn);
+}
+
+void windows_private_set_directory_replarea (const Repl_Agmt *ra,const Slapi_DN* sdn )
+{
+
+ Dirsync_Private *dp;
+ PR_ASSERT(ra);
+ PR_ASSERT(sdn);
+
+ dp = (Dirsync_Private *) get_priv_from_agmt(ra);
+ PR_ASSERT (dp);
+
+ dp->directory_replarea = slapi_sdn_dup(sdn);
+}
+
+PRBool windows_private_create_users(const Repl_Agmt *ra)
+{
+ Dirsync_Private *dp;
+ PR_ASSERT(ra);
+ dp = (Dirsync_Private *) get_priv_from_agmt(ra);
+ PR_ASSERT (dp);
+
+ return dp->create_users_from_dirsync;
+
+}
+
+
+void windows_private_set_create_users(const Repl_Agmt *ra, PRBool value)
+{
+ Dirsync_Private *dp;
+ PR_ASSERT(ra);
+ dp = (Dirsync_Private *) get_priv_from_agmt(ra);
+ PR_ASSERT (dp);
+
+ dp->create_users_from_dirsync = value;
+
+}
+
+
+/*
+ This function returns the current Dirsync_Private that's inside
+ Repl_Agmt ra as a ldap control.
+
+ */
+LDAPControl* windows_private_dirsync_control(const Repl_Agmt *ra){
+
+ LDAPControl *control = NULL;
+ LDAPControl **lc = &control ;
+ BerElement *ber;
+ Dirsync_Private *dp;
+
+ PR_ASSERT(ra);
+
+ dp = (Dirsync_Private *) get_priv_from_agmt(ra);
+ PR_ASSERT (dp);
+ ber = ber_alloc();
+
+ ber_printf( ber, "{iio}", dp->dirsync_flags, dp->dirsync_maxattributecount, dp->dirsync_cookie, dp->dirsync_cookie_len );
+
+ slapi_build_control( REPL_DIRSYNC_CONTROL_OID, ber, PR_TRUE, &control);
+
+
+ return control;
+
+}
+
+/*
+ This function scans the array of controls and updates the Repl_Agmt's
+ Dirsync_Private if the dirsync control is found.
+
+*/
+void windows_private_update_dirsync_control(const Repl_Agmt *ra,LDAPControl **controls ){
+
+ Dirsync_Private *dp;
+ int foundDirsyncControl;
+ int i;
+ LDAPControl *dirsync;
+ BerElement *ber;
+ int hasMoreData;
+ int maxAttributeCount;
+
+
+ BerValue *serverCookie;
+
+ PR_ASSERT(ra);
+
+ dp = (Dirsync_Private *) get_priv_from_agmt(ra);
+ PR_ASSERT (dp);
+
+ if (NULL != controls )
+ {
+ foundDirsyncControl = 0;
+ for ( i = 0; (( controls[i] != NULL ) && ( !foundDirsyncControl )); i++ ) {
+ foundDirsyncControl = !strcmp( controls[i]->ldctl_oid, REPL_DIRSYNC_CONTROL_OID );
+ }
+
+ if ( !foundDirsyncControl )
+ return;
+ else
+ dirsync = slapi_dup_control( controls[i-1]);
+
+ ber = ber_init( &dirsync->ldctl_value ) ;
+
+ ber_scanf( ber, "{iiO}", &hasMoreData, &maxAttributeCount, &serverCookie);
+
+ slapi_ch_free(&dp->dirsync_cookie);
+ dp->dirsync_cookie = ( char* ) slapi_ch_malloc(serverCookie->bv_len + 1);
+
+ memcpy(dp->dirsync_cookie, serverCookie->bv_val, serverCookie->bv_len);
+ dp->dirsync_cookie_len = (int) serverCookie->bv_len; /* XXX shouldn't cast? */
+
+ dp->dirsync_maxattributecount = maxAttributeCount;
+ dp->dirsync_cookie_has_more = hasMoreData;
+
+ ber_bvfree(serverCookie);
+ ber_free(ber,1);
+ }
+
+}
+
+PRBool windows_private_dirsync_has_more(const Repl_Agmt *ra)
+{
+ Dirsync_Private *dp;
+ PR_ASSERT(ra);
+
+ dp = (Dirsync_Private *) get_priv_from_agmt(ra);
+ PR_ASSERT (dp);
+
+ return dp->dirsync_cookie_has_more;
+
+}
+
+void windows_private_null_dirsync_control(const Repl_Agmt *ra){
+
+ Dirsync_Private *dp;
+ dp = (Dirsync_Private *) get_priv_from_agmt(ra);
+ PR_ASSERT (dp);
+
+ dp->dirsync_cookie_len = 0;
+ /* XXX should this value be free'd? */
+ dp->dirsync_cookie = NULL;
+} \ No newline at end of file
diff --git a/ldap/servers/plugins/replication/windows_prot_private.h b/ldap/servers/plugins/replication/windows_prot_private.h
new file mode 100644
index 00000000..f84e4087
--- /dev/null
+++ b/ldap/servers/plugins/replication/windows_prot_private.h
@@ -0,0 +1,70 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "repl5_prot_private.h"
+
+#ifndef _WINDOWS_PROT_PRIVATE_H_
+#define _WINDOWS_PROT_PRIVATE_H_
+
+#define ACQUIRE_SUCCESS 101
+#define ACQUIRE_REPLICA_BUSY 102
+#define ACQUIRE_FATAL_ERROR 103
+#define ACQUIRE_CONSUMER_WAS_UPTODATE 104
+#define ACQUIRE_TRANSIENT_ERROR 105
+
+typedef struct windows_private_repl_protocol
+{
+ void (*delete)(struct windows_private_repl_protocol **);
+ void (*run)(struct windows_private_repl_protocol *);
+ int (*stop)(struct windows_private_repl_protocol *);
+ int (*status)(struct windows_private_repl_protocol *);
+ void (*notify_update)(struct windows_private_repl_protocol *);
+ void (*notify_agmt_changed)(struct windows_private_repl_protocol *);
+ void (*notify_window_opened)(struct windows_private_repl_protocol *);
+ void (*notify_window_closed)(struct windows_private_repl_protocol *);
+ void (*update_now)(struct windows_private_repl_protocol *);
+ PRLock *lock;
+ PRCondVar *cvar;
+ int stopped;
+ int terminate;
+ PRUint32 eventbits;
+ Repl_Connection *conn;
+ int last_acquire_response_code;
+ Repl_Agmt *agmt;
+ Object *replica_object;
+ void *private;
+ PRBool replica_acquired;
+} Windows_Private_Repl_Protocol;
+
+/*
+extern Windows_Private_Repl_Protocol *Windows_Inc_Protocol_new();
+extern Windows_Private_Repl_Protocol *Windows_Tot_Protocol_new();
+*/
+
+#define PROTOCOL_TERMINATION_NORMAL 301
+#define PROTOCOL_TERMINATION_ABNORMAL 302
+#define PROTOCOL_TERMINATION_NEEDS_TOTAL_UPDATE 303
+
+#define RESUME_DO_TOTAL_UPDATE 401
+#define RESUME_DO_INCREMENTAL_UPDATE 402
+#define RESUME_TERMINATE 403
+#define RESUME_SUSPEND 404
+
+/* Backoff timer settings for reconnect */
+#define PROTOCOL_BACKOFF_MINIMUM 3 /* 3 seconds */
+#define PROTOCOL_BACKOFF_MAXIMUM (60 * 5) /* 5 minutes */
+/* Backoff timer settings for replica busy reconnect */
+#define PROTOCOL_BUSY_BACKOFF_MINIMUM PROTOCOL_BACKOFF_MINIMUM
+#define PROTOCOL_BUSY_BACKOFF_MAXIMUM PROTOCOL_BUSY_BACKOFF_MINIMUM
+
+/* protocol related functions */
+
+CSN *get_current_csn(Slapi_DN *replarea_sdn);
+char* protocol_response2string (int response);
+
+void windows_dirsync_inc_run(Private_Repl_Protocol *prp);
+
+#endif /* _REPL5_PROT_PRIVATE_H_ */
diff --git a/ldap/servers/plugins/replication/windows_protocol_util.c b/ldap/servers/plugins/replication/windows_protocol_util.c
new file mode 100644
index 00000000..433e13b2
--- /dev/null
+++ b/ldap/servers/plugins/replication/windows_protocol_util.c
@@ -0,0 +1,226 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5_protocol_util.c */
+/*
+
+Code common to both incremental and total protocols.
+
+*/
+
+#include "repl5.h"
+// #include "windows_prot_private.h"
+#include "repl5_prot_private.h"
+#include "windowsrepl.h"
+
+
+int ruv_private_new( RUV **ruv, RUV *clone );
+
+/*
+ * Acquire exclusive access to a replica. Send a start replication extended
+ * operation to the replica. The response will contain a success code, and
+ * optionally the replica's update vector if acquisition is successful.
+ * This function returns one of the following:
+ * ACQUIRE_SUCCESS - the replica was acquired, and we have exclusive update access
+ * ACQUIRE_REPLICA_BUSY - another master was updating the replica
+ * ACQUIRE_FATAL_ERROR - something bad happened, and it's not likely to improve
+ * if we wait.
+ * ACQUIRE_TRANSIENT_ERROR - something bad happened, but it's probably worth
+ * another try after waiting a while.
+ * If ACQUIRE_SUCCESS is returned, then ruv will point to the replica's update
+ * vector. It's possible that the replica does something goofy and doesn't
+ * return us an update vector, so be prepared for ruv to be NULL (but this is
+ * an error).
+ */
+int
+windows_acquire_replica(Private_Repl_Protocol *prp, RUV **ruv)
+{
+ char * prot_oid = REPL_NSDS50_INCREMENTAL_PROTOCOL_OID; //xXX get rid of this
+ int return_value = ACQUIRE_SUCCESS;
+ ConnResult crc;
+ Repl_Connection *conn;
+
+ PR_ASSERT(prp);
+
+ if (prp->replica_acquired) /* we already acquire replica */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Remote replica already acquired\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
+ return ACQUIRE_SUCCESS;
+ }
+
+ //if (NULL != ruv)
+ //{ ruv_destroy ( ruv ); }
+
+ {
+ Replica *replica;
+ Object *supl_ruv_obj, *cons_ruv_obj;
+ PRBool is_newer = PR_FALSE;
+ RUV *r;
+
+
+ if (prp->agmt)
+ {
+ cons_ruv_obj = agmt_get_consumer_ruv(prp->agmt);
+ }
+
+
+
+
+
+ object_acquire(prp->replica_object);
+ replica = object_get_data(prp->replica_object);
+ supl_ruv_obj = replica_get_ruv ( replica );
+
+ /* make a copy of the existing RUV as a starting point
+ XXX this is probably a not-so-elegant hack */
+
+ slapi_log_error(SLAPI_LOG_REPL, NULL, "acquire_replica, supplier RUV:\n");
+ if (supl_ruv_obj) {
+ object_acquire(supl_ruv_obj);
+ ruv_dump ((RUV*) object_get_data ( supl_ruv_obj ), "supplier", NULL);
+ object_release(supl_ruv_obj);
+ }else
+ slapi_log_error(SLAPI_LOG_REPL, NULL, "acquire_replica, supplier RUV = null\n");
+
+ slapi_log_error(SLAPI_LOG_REPL, NULL, "acquire_replica, consumer RUV:\n");
+
+ if (cons_ruv_obj) {
+ RUV* con;
+ object_acquire(cons_ruv_obj);
+ con = (RUV*) object_get_data ( cons_ruv_obj );
+ ruv_dump (con,"consumer", NULL);
+ object_release( cons_ruv_obj );
+ }else
+ slapi_log_error(SLAPI_LOG_REPL, NULL, "acquire_replica, consumer RUV = null\n");
+
+
+ is_newer = ruv_is_newer ( supl_ruv_obj, cons_ruv_obj );
+
+ /* This follows ruv_is_newer, since it's always newer if it's null */
+ if (cons_ruv_obj == NULL)
+ {
+ RUV *s; // int rc;
+ s = (RUV*) object_get_data ( replica_get_ruv ( replica ) );
+
+ agmt_set_consumer_ruv(prp->agmt, s );
+ object_release ( replica_get_ruv ( replica ) );
+ cons_ruv_obj = agmt_get_consumer_ruv(prp->agmt);
+ }
+
+ r = (RUV*) object_get_data ( cons_ruv_obj);
+ *ruv = r;
+
+
+
+ if ( supl_ruv_obj ) object_release ( supl_ruv_obj );
+ if ( cons_ruv_obj ) object_release ( cons_ruv_obj );
+ object_release (prp->replica_object);
+ replica = NULL;
+
+ if (is_newer == PR_FALSE) {
+ prp->last_acquire_response_code = NSDS50_REPL_UPTODATE;
+ return ACQUIRE_CONSUMER_WAS_UPTODATE;
+ }
+ }
+
+ prp->last_acquire_response_code = NSDS50_REPL_REPLICA_NO_RESPONSE;
+
+ /* Get the connection */
+ conn = prp->conn;
+
+ crc = windows_conn_connect(conn);
+ if (CONN_OPERATION_FAILED == crc)
+ {
+ return_value = ACQUIRE_TRANSIENT_ERROR;
+ }
+ else if (CONN_SSL_NOT_ENABLED == crc)
+ {
+ return_value = ACQUIRE_FATAL_ERROR;
+ }
+ else
+ {
+ /* we don't want the timer to go off in the middle of an operation */
+ windows_conn_cancel_linger(conn);
+ /* Does the remote replica support the dirsync protocol?
+ if it update the conn object */
+ windows_conn_replica_supports_dirsync(conn);
+ if (CONN_NOT_CONNECTED == crc || CONN_OPERATION_FAILED == crc)
+ {
+ /* We don't know anything about the remote replica. Try again later. */
+ return_value = ACQUIRE_TRANSIENT_ERROR;
+ }
+ else
+ {
+ /* Good to go. Start the protocol. */
+ CSN *current_csn = NULL;
+ Slapi_DN *replarea_sdn;
+
+ /* Obtain a current CSN */
+ replarea_sdn = agmt_get_replarea(prp->agmt);
+ current_csn = get_current_csn(replarea_sdn);
+ if (NULL != current_csn)
+ {
+
+ /* Save consumer's RUV in the replication agreement.
+ It is used by the changelog trimming code */
+ // if (ruv && *ruv) agmt_set_consumer_ruv (prp->agmt, *ruv); XXX deadlock?
+// XXX
+ return_value = ACQUIRE_SUCCESS;
+
+
+
+ }
+ else
+ {
+ /* Couldn't get a current CSN */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "%s: Unable to obtain current CSN. "
+ "Replication is aborting.\n",
+ agmt_get_long_name(prp->agmt));
+ return_value = ACQUIRE_FATAL_ERROR;
+ }
+ slapi_sdn_free(&replarea_sdn);
+ }
+ }
+
+ if (ACQUIRE_SUCCESS != return_value)
+ {
+ /* could not acquire the replica, so reinstate the linger timer, since this
+ means we won't call release_replica, which also reinstates the timer */
+ windows_conn_start_linger(conn);
+ }
+ else
+ {
+ /* replica successfully acquired */
+ prp->replica_acquired = PR_TRUE;
+ }
+
+ return return_value;
+}
+
+void
+windows_release_replica(Private_Repl_Protocol *prp)
+{
+
+ struct berval *retdata = NULL;
+ char *retoid = NULL;
+ struct berval *payload = NULL;
+ Slapi_DN *replarea_sdn = NULL;
+
+ PR_ASSERT(NULL != prp);
+ PR_ASSERT(NULL != prp->conn);
+
+ if (!prp->replica_acquired)
+ return;
+
+ windows_conn_start_linger(prp->conn);
+
+ prp->replica_acquired = PR_FALSE;
+
+}
diff --git a/ldap/servers/plugins/replication/windows_replica.c b/ldap/servers/plugins/replication/windows_replica.c
new file mode 100644
index 00000000..0acd931a
--- /dev/null
+++ b/ldap/servers/plugins/replication/windows_replica.c
@@ -0,0 +1,1213 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+/* repl5_replica.c */
+
+#include "slapi-plugin.h"
+#include "repl.h" /* ONREPL - this is bad */
+#include "repl5.h"
+#include "windowsrepl.h"
+#include "repl_shared.h"
+#include "csnpl.h"
+#include "cl5_api.h"
+
+/* from proto-slap.h */
+int g_get_shutdown();
+
+#define RUV_SAVE_INTERVAL (30 * 1000) /* 30 seconds */
+#define START_UPDATE_DELAY 2 /* 2 second */
+#define START_REAP_DELAY 3600 /* 1 hour */
+
+#define REPLICA_RDN "cn=windowsreplica"
+#define CHANGELOG_RDN "cn=legacy changelog"
+
+/*
+ * A replica is a locally-held copy of a portion of the DIT.
+ */
+struct replica {
+ Slapi_DN *repl_root; /* top of the replicated area */
+ char *repl_name; /* unique replica name */
+ PRBool new_name; /* new name was generated - need to be saved */
+ ReplicaUpdateDNList updatedn_list; /* list of dns with which a supplier should bind
+ to update this replica */
+ ReplicaType repl_type; /* is this replica read-only ? */
+ PRBool legacy_consumer; /* if true, this replica is supplied by 4.0 consumer */
+ char* legacy_purl; /* partial url of the legacy supplier */
+ ReplicaId repl_rid; /* replicaID */
+ Object *repl_ruv; /* replica update vector */
+ PRBool repl_ruv_dirty; /* Dirty flag for ruv */
+ CSNPL *min_csn_pl; /* Pending list for minimal CSN */
+ void *csn_pl_reg_id; /* registration assignment for csn callbacks */
+ unsigned long repl_state_flags; /* state flags */
+ PRUint32 repl_flags; /* persistent, externally visible flags */
+ PRLock *repl_lock; /* protects entire structure */
+ Slapi_Eq_Context repl_eqcxt_rs; /* context to cancel event that saves ruv */
+ Slapi_Eq_Context repl_eqcxt_tr; /* context to cancel event that reaps tombstones */
+ Object *repl_csngen; /* CSN generator for this replica */
+ PRBool repl_csn_assigned; /* Flag set when new csn is assigned. */
+ PRUint32 repl_purge_delay; /* When purgeable, CSNs are held on to for this many extra seconds */
+ PRBool tombstone_reap_stop; /* TRUE when the tombstone reaper should stop */
+ PRBool tombstone_reap_active; /* TRUE when the tombstone reaper is running */
+ long tombstone_reap_interval; /* Time in seconds between tombstone reaping */
+ Slapi_ValueSet *repl_referral; /* A list of administrator provided referral URLs */
+ PRBool state_update_inprogress; /* replica state is being updated */
+ PRLock *agmt_lock; /* protects agreement creation, start and stop */
+ char *locking_purl; /* supplier who has exclusive access */
+
+
+ Object *consumer_repl_ruv; /* tracks location in changelog for changes to send to active directoroy */
+
+};
+
+
+typedef struct reap_callback_data
+{
+ int rc;
+ unsigned long num_entries;
+ unsigned long num_purged_entries;
+ CSN *purge_csn;
+ PRBool *tombstone_reap_stop;
+} reap_callback_data;
+
+
+
+
+/* Forward declarations of helper functions*/
+static Slapi_Entry* _windows_replica_get_config_entry (const Slapi_DN *root);
+static int _windows_replica_check_validity (const Replica *r);
+static int _windows_replica_init_from_config (Replica *r, Slapi_Entry *e, char *errortext);
+static int __replica_update_entry (Replica *r, Slapi_Entry *e, char *errortext);
+static int __replica_configure_ruv (Replica *r, PRBool isLocked);
+static void _windows_replica_update_state (time_t when, void *arg);
+static char * _replica_get_config_dn (const Slapi_DN *root);
+static char * __replica_type_as_string (const Replica *r);
+static int replica_create_ruv_tombstone(Replica *r);
+static void assign_csn_callback(const CSN *csn, void *data);
+static void abort_csn_callback(const CSN *csn, void *data);
+static void eq_cb_reap_tombstones(time_t when, void *arg);
+static CSN *_replica_get_purge_csn_nolock (const Replica *r);
+static void replica_get_referrals_nolock (const Replica *r, char ***referrals);
+static void replica_clear_legacy_referrals (const Slapi_DN *repl_root_sdn, char **referrals, const char *state);
+static void replica_remove_legacy_attr (const Slapi_DN *repl_root_sdn, const char *attr);
+static int replica_log_ruv_elements_nolock (const Replica *r);
+static void replica_replace_ruv_tombstone(Replica *r);
+static void start_agreements_for_replica (Replica *r, PRBool start);
+void replica_consumer_set_ruv (Replica *r, RUV *ruv) ;
+
+/* PRBool */
+/* replica_is_state_flag_set(Replica *r, PRInt32 flag) */
+/* { */
+/* PR_ASSERT(r); */
+/* if (r) */
+/* return (r->repl_state_flags & flag); */
+/* else */
+/* return PR_FALSE; */
+/* } */
+
+/* Replica * */
+/* windows_replica_new(const Slapi_DN *root) */
+/* { */
+/* Replica *r = NULL; */
+/* Slapi_Entry *e = NULL; */
+/* char errorbuf[BUFSIZ]; */
+/* char ebuf[BUFSIZ]; */
+
+/* PR_ASSERT (root); */
+
+/* /\* check if there is a replica associated with the tree *\/ */
+/* e = _windows_replica_get_config_entry (root); */
+/* if (e) */
+/* { */
+/* errorbuf[0] = '\0'; */
+/* r = windows_replica_new_from_entry(e, errorbuf, */
+/* PR_FALSE /\* not a newly added entry *\/); */
+
+/* if (NULL == r) */
+/* { */
+/* slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "Unable to " */
+/* "configure replica %s: %s\n", */
+/* escape_string(slapi_sdn_get_dn(root), ebuf), */
+/* errorbuf); */
+/* } */
+
+/* slapi_entry_free (e); */
+/* } */
+
+/* return r; */
+/* } */
+
+/*
+int windows_replica_start_agreement
+(Replica *r, Repl_Agmt *ra)
+{
+ int ret = 0;
+
+ if (r == NULL) return -1;
+
+ PR_Lock(r->agmt_lock);
+
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "windows_replica_start_agreement: state_flag =%d\n",
+ !replica_is_state_flag_set(r, REPLICA_AGREEMENTS_DISABLED));
+
+
+ if (!replica_is_state_flag_set(r, REPLICA_AGREEMENTS_DISABLED)) {
+ ret = windows_agmt_start(ra);
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "windows_replica_start_agreement: rc=%d\n", ret);
+ ret = 0;
+ }
+
+ PR_Unlock(r->agmt_lock);
+
+ return ret;
+} */
+
+
+/*
+ * A callback function registed as op->o_replica_attr_handler and
+ * called by backend ops to get replica attributes.
+ */
+int
+__replica_get_attr ( Slapi_PBlock *pb, const char* type, void *value )
+{
+ int rc = -1;
+
+ Object *replica_obj;
+ replica_obj = replica_get_replica_for_op (pb);
+ if (NULL != replica_obj)
+ {
+ Replica *replica = (Replica*) object_get_data (replica_obj);
+ if ( NULL != replica )
+ {
+ if (strcasecmp (type, type_replicaTombstonePurgeInterval) == 0)
+ {
+ *((int*)value) = replica->tombstone_reap_interval;
+ rc = 0;
+ }
+ else if (strcasecmp (type, type_replicaPurgeDelay) == 0)
+ {
+ *((int*)value) = replica->repl_purge_delay;
+ rc = 0;
+ }
+ }
+ object_release (replica_obj);
+ }
+
+ return rc;
+}
+
+
+static Slapi_Entry*
+_windows_replica_get_config_entry (const Slapi_DN *root)
+{
+ int rc = 0;
+ char *dn = NULL;
+ Slapi_Entry **entries;
+ Slapi_Entry *e = NULL;
+ Slapi_PBlock *pb = NULL;
+
+ dn = _replica_get_config_dn (root);
+ pb = slapi_pblock_new ();
+
+ slapi_search_internal_set_pb (pb, dn, LDAP_SCOPE_BASE, "objectclass=*", NULL, 0, NULL,
+ NULL, repl_get_plugin_identity (PLUGIN_WINDOWS_REPLICATION), 0);
+ slapi_search_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc == 0)
+ {
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ e = slapi_entry_dup (entries [0]);
+ }
+
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy (pb);
+ slapi_ch_free_string(&dn);
+
+ return e;
+}
+
+
+static char*
+_replica_get_config_dn (const Slapi_DN *root)
+{
+ char *dn;
+ const char *mp_base = slapi_get_mapping_tree_config_root ();
+ int len;
+
+ PR_ASSERT (root);
+
+ len = strlen (REPLICA_RDN) + strlen (slapi_sdn_get_dn (root)) +
+ strlen (mp_base) + 8; /* 8 = , + cn= + \" + \" + , + \0 */
+
+ dn = (char*)slapi_ch_malloc (len);
+ sprintf (dn, "%s,cn=\"%s\",%s", REPLICA_RDN, slapi_sdn_get_dn (root), mp_base);
+
+ return dn;
+}
+
+Replica *
+windows_replica_new_from_entry (Slapi_Entry *e, char *errortext, PRBool is_add_operation)
+{
+ int rc = 0;
+ Replica *r;
+ RUV *ruv;
+ RUV *consumer_ruv;
+ char *repl_name = NULL;
+
+ if (e == NULL)
+ {
+ if (NULL != errortext)
+ {
+ sprintf (errortext, "NULL entry");
+ }
+ return NULL;
+ }
+
+ r = (Replica *)slapi_ch_calloc(1, sizeof(Replica));
+
+ if ((r->repl_lock = PR_NewLock()) == NULL)
+ {
+ if (NULL != errortext)
+ {
+ sprintf (errortext, "failed to create replica lock");
+ }
+ rc = -1;
+ goto done;
+ }
+
+ if ((r->agmt_lock = PR_NewLock()) == NULL)
+ {
+ if (NULL != errortext)
+ {
+ sprintf (errortext, "failed to create replica lock");
+ }
+ rc = -1;
+ goto done;
+ }
+
+ /* read parameters from the replica config entry */
+ rc = _windows_replica_init_from_config (r, e, errortext);
+ if (rc != 0)
+ {
+ goto done;
+ }
+
+ /* configure ruv */
+ rc = __replica_configure_ruv (r, PR_FALSE);
+ if (rc != 0)
+ {
+ goto done;
+ }
+
+ /* If smallest csn exists in RUV for our local replica, it's ok to begin iteration */
+ ruv = (RUV*) object_get_data (r->repl_ruv); //XXX
+ PR_ASSERT (ruv);
+
+ consumer_ruv = ruv_dup(ruv);
+ replica_consumer_set_ruv(r, consumer_ruv);
+
+ if (is_add_operation)
+ {
+ /*
+ * This is called by an ldap add operation.
+ * Update the entry to contain information generated
+ * during replica initialization
+ */
+ rc = __replica_update_entry (r, e, errortext);
+ }
+ else
+ {
+ /*
+ * Entry is already in dse.ldif - update it on the disk
+ * (done by the update state event scheduled below)
+ */
+ }
+ if (rc != 0)
+ goto done;
+
+ /* ONREPL - the state update can occur before the entry is added to the DIT.
+ In that case the updated would fail but nothing bad would happen. The next
+ scheduled update would save the state */
+ repl_name = slapi_ch_strdup (r->repl_name);
+ r->repl_eqcxt_rs = slapi_eq_repeat(_windows_replica_update_state, repl_name,
+ current_time () + START_UPDATE_DELAY, RUV_SAVE_INTERVAL);
+
+ if (r->tombstone_reap_interval > 0)
+ {
+ /*
+ * Reap Tombstone should be started some time after the plugin started.
+ * This will allow the server to fully start before consuming resources.
+ */
+ repl_name = slapi_ch_strdup (r->repl_name);
+ // XXX r->repl_eqcxt_tr = slapi_eq_repeat(eq_cb_reap_tombstones, repl_name, current_time() + START_REAP_DELAY, 1000 * r->tombstone_reap_interval);
+ }
+
+ if (r->legacy_consumer)
+ {
+ char ebuf[BUFSIZ];
+
+ legacy_consumer_init_referrals (r);
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name, "replica_new_from_entry: "
+ "replica for %s was configured as legacy consumer\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ }
+
+done:
+ if (rc != 0 && r)
+ {
+ replica_destroy ((void**)&r);
+ }
+
+ return r;
+
+
+
+}
+
+static int
+_windows_replica_init_from_config (Replica *r, Slapi_Entry *e, char *errortext)
+{
+ int rc;
+ Slapi_Attr *attr;
+ char *val;
+ CSNGen *gen;
+ char buf [BUFSIZ];
+ char *errormsg = errortext? errortext : buf;
+ Slapi_Attr *a = NULL;
+ char dnescape[BUFSIZ]; /* for escape_string */
+
+ PR_ASSERT (r && e);
+
+ /* get replica root */
+ val = slapi_entry_attr_get_charptr (e, attr_replicaRoot);
+ if (val == NULL)
+ {
+ sprintf (errormsg, "failed to retrieve %s attribute from (%s)\n",
+ attr_replicaRoot,
+ escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), dnescape));
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "_replica_init_from_config: %s\n",
+ errormsg);
+
+ return -1;
+ }
+
+ r->repl_root = slapi_sdn_new_dn_passin (val);
+
+ /* get replica type */
+ val = slapi_entry_attr_get_charptr (e, attr_replicaType);
+ if (val)
+ {
+ r->repl_type = atoi(val);
+ slapi_ch_free ((void**)&val);
+ }
+ else
+ {
+ r->repl_type = REPLICA_TYPE_READONLY;
+ }
+
+ /* get legacy consumer flag */
+ val = slapi_entry_attr_get_charptr (e, type_replicaLegacyConsumer);
+ if (val)
+ {
+ if (strcasecmp (val, "on") == 0 || strcasecmp (val, "yes") == 0 ||
+ strcasecmp (val, "true") == 0 || strcasecmp (val, "1") == 0)
+ {
+ r->legacy_consumer = PR_TRUE;
+ }
+ else
+ {
+ r->legacy_consumer = PR_FALSE;
+ }
+
+ slapi_ch_free ((void**)&val);
+ }
+ else
+ {
+ r->legacy_consumer = PR_FALSE;
+ }
+
+ /* get replica flags */
+ r->repl_flags = slapi_entry_attr_get_ulong(e, attr_flags);
+
+ /* get replicaid */
+ /* the replica id is ignored for read only replicas and is set to the
+ special value READ_ONLY_REPLICA_ID */
+ if (r->repl_type == REPLICA_TYPE_READONLY)
+ {
+ r->repl_rid = READ_ONLY_REPLICA_ID;
+ slapi_entry_attr_set_uint(e, attr_replicaId, (unsigned int)READ_ONLY_REPLICA_ID);
+ }
+ /* a replica id is required for updatable and primary replicas */
+ else if (r->repl_type == REPLICA_TYPE_UPDATABLE ||
+ r->repl_type == REPLICA_TYPE_PRIMARY)
+ {
+ if ((val = slapi_entry_attr_get_charptr (e, attr_replicaId)))
+ {
+ int temprid = atoi (val);
+ slapi_ch_free ((void**)&val);
+ if (temprid <= 0 || temprid >= READ_ONLY_REPLICA_ID)
+ {
+/* sprintf (errormsg, */
+/* "attribute %s must have a value greater than 0 " */
+/* "and less than %d: entry %s", */
+/* attr_replicaId, READ_ONLY_REPLICA_ID, */
+/* escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), */
+/* dnescape)); */
+/* slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, */
+/* "_replica_init_from_config: %s\n", */
+/* errormsg); */
+/* return -1; */
+ }
+ else
+ {
+ r->repl_rid = (ReplicaId)temprid;
+ }
+ }
+ else
+ {
+ sprintf (errormsg, "failed to retrieve required %s attribute from %s",
+ attr_replicaId,
+ escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e),
+ dnescape));
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "_replica_init_from_config: %s\n",
+ errormsg);
+ return -1;
+ }
+ }
+
+ attr = NULL;
+ rc = slapi_entry_attr_find(e, attr_state, &attr);
+ gen = csngen_new (r->repl_rid, attr);
+ if (gen == NULL)
+ {
+ sprintf (errormsg, "failed to create csn generator for replica (%s)",
+ escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e),
+ dnescape));
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "_replica_init_from_config: %s\n",
+ errormsg);
+ return -1;
+ }
+ r->repl_csngen = object_new((void*)gen, (FNFree)csngen_free);
+
+ /* Hook generator so we can maintain min/max CSN info */
+ r->csn_pl_reg_id = csngen_register_callbacks(gen, assign_csn_callback, r, abort_csn_callback, r);
+
+ /* get replication bind dn */
+ r->updatedn_list = replica_updatedn_list_new(e);
+
+ /* get replica name */
+ val = slapi_entry_attr_get_charptr (e, attr_replicaName);
+ if (val) {
+ r->repl_name = val;
+ }
+ else
+ {
+ rc = slapi_uniqueIDGenerateString (&r->repl_name);
+ if (rc != UID_SUCCESS)
+ {
+ sprintf (errormsg, "failed to assign replica name for replica (%s); "
+ "uuid generator error - %d ",
+ escape_string((char*)slapi_entry_get_dn ((Slapi_Entry*)e), dnescape),
+ rc);
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "_replica_init_from_config: %s\n",
+ errormsg);
+ return -1;
+ }
+ else
+ r->new_name = PR_TRUE;
+ }
+
+ /* get the list of referrals */
+ slapi_entry_attr_find( e, attr_replicaReferral, &attr );
+ if(attr!=NULL)
+ {
+ slapi_attr_get_valueset(attr, &r->repl_referral);
+ }
+
+ /*
+ * Set the purge offset (default 7 days). This is the extra
+ * time we allow purgeable CSNs to stick around, in case a
+ * replica regresses. Could also be useful when LCUP happens,
+ * since we don't know about LCUP replicas, and they can just
+ * turn up whenever they want to.
+ */
+ if (slapi_entry_attr_find(e, type_replicaPurgeDelay, &a) == -1)
+ {
+ /* No purge delay provided, so use default */
+ r->repl_purge_delay = 60 * 60 * 24 * 7; /* One week, in seconds */
+ }
+ else
+ {
+ r->repl_purge_delay = slapi_entry_attr_get_uint(e, type_replicaPurgeDelay);
+ }
+
+ if (slapi_entry_attr_find(e, type_replicaTombstonePurgeInterval, &a) == -1)
+ {
+ /* No reap interval provided, so use default */
+ r->tombstone_reap_interval = 3600 * 24; /* One day */
+ }
+ else
+ {
+ r->tombstone_reap_interval = slapi_entry_attr_get_int(e, type_replicaTombstonePurgeInterval);
+ }
+
+ r->tombstone_reap_stop = r->tombstone_reap_active = PR_FALSE;
+
+ return (_windows_replica_check_validity (r));
+}
+
+
+static int
+_windows_replica_check_validity (const Replica *r)
+{
+ PR_ASSERT (r);
+
+ if (r->repl_root == NULL || r->repl_type == 0 ||
+ r->repl_rid > MAX_REPLICA_ID || r->repl_name == NULL)
+ {
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+
+/* NOTE - this is the only non-api function that performs locking because
+ it is called by the event queue */
+static void
+_windows_replica_update_state (time_t when, void *arg)
+{
+ int rc;
+ const char *replica_name = (const char *)arg;
+ Object *replica_object = NULL;
+ Replica *r;
+ Slapi_Mod smod;
+ LDAPMod *mods[3];
+ Slapi_PBlock *pb = NULL;
+ char *dn = NULL;
+
+ if (NULL == replica_name)
+ return;
+
+ /*
+ * replica_get_by_name() will acquire the replica object
+ * and that could prevent the replica from being destroyed
+ * until the object_release is called.
+ */
+ replica_object = replica_get_by_name(replica_name);
+ if (NULL == replica_object)
+ {
+ return;
+ }
+
+ /* We have a reference, so replica won't vanish on us. */
+ r = (Replica *)object_get_data(replica_object);
+ if (NULL == r)
+ {
+ goto done;
+ }
+
+ PR_Lock(r->repl_lock);
+
+ /* replica state is currently being updated
+ or no CSN was assigned - bail out */
+ if (r->state_update_inprogress)
+ {
+ PR_Unlock(r->repl_lock);
+ goto done;
+ }
+
+ /* This might be a consumer */
+ if (!r->repl_csn_assigned)
+ {
+ /* EY: the consumer needs to flush ruv to disk. */
+ PR_Unlock(r->repl_lock);
+ replica_write_ruv(r);
+ goto done;
+ }
+
+ /* ONREPL update csn generator state of an updatable replica only */
+ /* ONREPL state always changes because we update time every second and
+ we write state to the disk less frequently */
+ rc = csngen_get_state ((CSNGen*)object_get_data (r->repl_csngen), &smod);
+ if (rc != 0)
+ {
+ PR_Unlock(r->repl_lock);
+ goto done;
+ }
+
+ r->state_update_inprogress = PR_TRUE;
+ r->repl_csn_assigned = PR_FALSE;
+
+ dn = _replica_get_config_dn (r->repl_root);
+ pb = slapi_pblock_new();
+ mods[0] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod);
+
+ /* we don't want to held lock during operations since it causes lock contention
+ and sometimes deadlock. So releasing lock here */
+
+ PR_Unlock(r->repl_lock);
+
+ /* replica repl_name and new_name attributes do not get changed once
+ the replica is configured - so it is ok that they are outside replica lock */
+
+ /* write replica name if it has not been written before */
+ if (r->new_name)
+ {
+ struct berval *vals[2];
+ struct berval val;
+ LDAPMod mod;
+
+ mods[1] = &mod;
+
+ mod.mod_op = LDAP_MOD_REPLACE;
+ mod.mod_type = (char*)attr_replicaName;
+ mod.mod_bvalues = vals;
+ vals [0] = &val;
+ vals [1] = NULL;
+ val.bv_val = r->repl_name;
+ val.bv_len = strlen (val.bv_val);
+ mods[2] = NULL;
+ }
+ else
+ {
+ mods[1] = NULL;
+ }
+
+ slapi_modify_internal_set_pb (pb, dn, mods, NULL, NULL,
+ repl_get_plugin_identity (PLUGIN_WINDOWS_REPLICATION), 0);
+ slapi_modify_internal_pb (pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc != LDAP_SUCCESS)
+ {
+ char ebuf[BUFSIZ];
+
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "_replica_update_state: "
+ "failed to update state of csn generator for replica %s: LDAP "
+ "error - %d\n", escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc);
+ }
+ else
+ {
+ r->new_name = PR_FALSE;
+ }
+
+ /* update RUV - performs its own locking */
+ replica_write_ruv (r);
+
+ /* since this is the only place this value is changed and we are
+ guaranteed that only one thread enters the function, its ok
+ to change it outside replica lock */
+ r->state_update_inprogress = PR_FALSE;
+
+ slapi_ch_free ((void**)&dn);
+ slapi_pblock_destroy (pb);
+ slapi_mod_done (&smod);
+
+done:
+ if (replica_object)
+ object_release (replica_object);
+}
+
+
+/* this function is called during server startup for each replica
+ to check whether the replica's data was reloaded offline and
+ whether replica's changelog needs to be reinitialized */
+
+/* the function does not use replica lock but all functions it calls are
+ thread safe. Locking replica lock while calling changelog functions
+ causes a deadlock because changelog calls replica functions that
+ that lock the same lock */
+int windows_replica_check_for_data_reload (Replica *r, void *arg)
+{
+ int rc = 0;
+ RUV *upper_bound_ruv = NULL;
+ RUV *r_ruv = NULL;
+ Object *r_obj, *ruv_obj;
+ int cl_cover_be, be_cover_cl;
+
+ PR_ASSERT (r);
+
+ /* check that we have a changelog and if this replica logs changes */
+ if (cl5GetState () == CL5_STATE_OPEN && r->repl_flags & REPLICA_LOG_CHANGES)
+ {
+ /* Compare new ruv to the purge ruv. If the new contains csns which
+ are smaller than those in purge ruv, we need to remove old and
+ create new changelog file for this replica. This is because we
+ will not have sufficient changes to incrementally update a consumer
+ to the current state of the supplier. */
+
+ rc = cl5GetUpperBoundRUV (r, &upper_bound_ruv);
+ if (rc != CL5_SUCCESS && rc != CL5_NOTFOUND)
+ {
+ return -1;
+ }
+
+ if (upper_bound_ruv)
+ {
+ ruv_obj = replica_get_ruv (r);
+ r_ruv = object_get_data (ruv_obj);
+ PR_ASSERT (r_ruv);
+
+ /* Compare new ruv to the changelog's upper bound ruv. We could only keep
+ the existing changelog if its upper bound is the same as replica's RUV.
+ This is because if changelog has changes not in RUV, they will be
+ eventually sent to the consumer's which will cause a state mismatch
+ (because the supplier does not actually contain the changes in its data store.
+ If, on the other hand, the changelog is not as up to date as the supplier,
+ it is not really useful since out of sync consumer's can't be brought
+ up to date using this changelog and hence will need to be reinitialized */
+
+ /*
+ * Actually we can ignore the scenario that the changelog's upper
+ * bound ruv covers data store's ruv for two reasons: (1) a change
+ * is always written to the changelog after it is committed to the
+ * data store; (2) a change will be ignored if the server has seen
+ * it before - this happens frequently at the beginning of replication
+ * sessions.
+ */
+
+ be_cover_cl = ruv_covers_ruv (r_ruv, upper_bound_ruv);
+ cl_cover_be = ruv_covers_ruv (upper_bound_ruv, r_ruv);
+ if (!cl_cover_be)
+ {
+ /* the data was reloaded and we can no longer use existing changelog */
+ char ebuf[BUFSIZ];
+
+ /* create a temporary replica object to conform to the interface */
+ r_obj = object_new (r, NULL);
+
+ /* We can't use existing changelog - remove existing file */
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "replica_check_for_data_reload: "
+ "Warning: data for replica %s was reloaded and it no longer matches the data "
+ "in the changelog (replica data %s changelog). Recreating the changelog file. This could affect replication "
+ "with replica's consumers in which case the consumers should be reinitialized.\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf),
+ ((!be_cover_cl && !cl_cover_be) ? "<>" : (!be_cover_cl ? "<" : ">")) );
+
+ rc = cl5DeleteDBSync (r_obj);
+
+ object_release (r_obj);
+
+ if (rc == CL5_SUCCESS)
+ {
+ /* log changes to mark starting point for replication */
+ rc = replica_log_ruv_elements (r);
+ }
+ }
+
+ object_release (ruv_obj);
+ }
+ else /* we have no changes currently logged for this replica */
+ {
+ /* log changes to mark starting point for replication */
+ rc = replica_log_ruv_elements (r);
+ }
+ }
+
+ if (rc == 0)
+ {
+ /* reset mapping tree referrals based on new local RUV */
+ // consumer5_set_mapping_tree_state_for_replica(r, NULL);
+ }
+
+ if (upper_bound_ruv)
+ ruv_destroy (&upper_bound_ruv);
+
+ return rc;
+}
+
+/* This function updates the entry to contain information generated
+ during replica initialization.
+ Returns 0 if successful and -1 otherwise */
+static int
+__replica_update_entry (Replica *r, Slapi_Entry *e, char *errortext)
+{
+ int rc;
+ Slapi_Mod smod;
+ Slapi_Value *val;
+
+ PR_ASSERT (r);
+
+ /* add attribute that stores state of csn generator */
+ rc = csngen_get_state ((CSNGen*)object_get_data (r->repl_csngen), &smod);
+ if (rc != CSN_SUCCESS)
+ {
+ sprintf (errortext, "failed to get csn generator's state; csn error - %d", rc);
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_update_entry: %s\n", errortext);
+ return -1;
+ }
+
+ val = slapi_value_new_berval(slapi_mod_get_first_value(&smod));
+
+ rc = slapi_entry_add_value (e, slapi_mod_get_type (&smod), val);
+
+ slapi_value_free(&val);
+ slapi_mod_done (&smod);
+
+ if (rc != 0)
+ {
+ sprintf (errortext, "failed to update replica entry");
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_update_entry: %s\n", errortext);
+ return -1;
+ }
+
+ /* add attribute that stores replica name */
+ rc = slapi_entry_add_string (e, attr_replicaName, r->repl_name);
+ if (rc != 0)
+ {
+ sprintf (errortext, "failed to update replica entry");
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_update_entry: %s\n", errortext);
+ return -1;
+ }
+ else
+ r->new_name = PR_FALSE;
+
+ return 0;
+}
+
+
+/* This function retrieves RUV from the root of the replicated tree.
+ * The attribute can be missing if
+ * (1) this replica is the first supplier and replica generation has not been assigned
+ * or
+ * (2) this is a consumer that has not been yet initialized
+ * In either case, replica_set_ruv should be used to further initialize the replica.
+ * Returns 0 on success, -1 on failure. If 0 is returned, the RUV is present in the replica.
+ */
+static int
+__replica_configure_ruv (Replica *r, PRBool isLocked)
+{
+ Slapi_PBlock *pb = NULL;
+ char *attrs[2];
+ int rc;
+ int return_value = -1;
+ Slapi_Entry **entries = NULL;
+ Slapi_Attr *attr;
+ RUV *ruv = NULL;
+ CSN *csn = NULL;
+ ReplicaId rid = 0;
+ char ebuf[BUFSIZ];
+
+ /* read ruv state from the ruv tombstone entry */
+ pb = slapi_pblock_new();
+ attrs[0] = (char*)type_ruvElement;
+ attrs[1] = NULL;
+ slapi_search_internal_set_pb(
+ pb,
+ slapi_sdn_get_dn(r->repl_root),
+ LDAP_SCOPE_BASE,
+ "objectclass=*",
+ attrs,
+ 0, /* attrsonly */
+ NULL, /* controls */
+ RUV_STORAGE_ENTRY_UNIQUEID,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION),
+ OP_FLAG_REPLICATED); /* flags */
+ slapi_search_internal_pb (pb);
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+ if (rc == LDAP_SUCCESS)
+ {
+ /* get RUV attributes and construct the RUV */
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
+ if (NULL == entries || NULL == entries[0])
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_configure_ruv: replica ruv tombstone entry for "
+ "replica %s not found\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ goto done;
+ }
+
+ rc = slapi_entry_attr_find(entries[0], type_ruvElement, &attr);
+ if (rc != 0) /* ruv attribute is missing - this not allowed */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_configure_ruv: replica ruv tombstone entry for "
+ "replica %s does not contain %s\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), type_ruvElement);
+ goto done;
+ }
+
+ /* Check in the tombstone we have retrieved if the local purl is
+ already present:
+ rid == 0: the local purl is not present
+ rid != 0: the local purl is present ==> nothing to do
+ */
+ ruv_init_from_slapi_attr_and_check_purl (attr, &ruv, &rid);
+ if (ruv)
+ {
+ char *generation = NULL;
+ generation = ruv_get_replica_generation(ruv);
+ if (NULL != generation)
+ {
+ r->repl_ruv = object_new((void*)ruv, (FNFree)ruv_destroy);
+
+ /* Is the local purl in the ruv? (the port or the host could have
+ changed)
+ */
+ /* A consumer only doesn't have its purl in its ruv */
+ if (r->repl_type == REPLICA_TYPE_UPDATABLE)
+ {
+ int need_update = 0;
+ if (rid == 0)
+ {
+ /* We can not have more than 1 ruv with the same rid
+ so we replace it */
+ const char *purl = NULL;
+
+ purl = multimaster_get_local_purl();
+ ruv_delete_replica(ruv, r->repl_rid);
+ ruv_add_index_replica(ruv, r->repl_rid, purl, 1);
+ need_update = 1; /* ruv changed, so write tombstone */
+ }
+ else /* bug 540844: make sure the local supplier rid is first in the ruv */
+ {
+ /* make sure local supplier is first in list */
+ ReplicaId first_rid = 0;
+ char *first_purl = NULL;
+ ruv_get_first_id_and_purl(ruv, &first_rid, &first_purl);
+ /* if the local supplier is not first in the list . . . */
+ if (rid != first_rid)
+ {
+ /* . . . move the local supplier to the beginning of the list */
+ ruv_move_local_supplier_to_first(ruv, rid);
+ need_update = 1; /* must update tombstone also */
+ }
+ }
+
+ /* Update also the directory entry */
+ if (need_update) {
+ /* richm 20010821 bug 556498
+ replica_replace_ruv_tombstone acquires the repl_lock, so release
+ the lock then reacquire it if locked */
+ if (isLocked) PR_Unlock(r->repl_lock);
+ replica_replace_ruv_tombstone(r);
+ if (isLocked) PR_Lock(r->repl_lock);
+ }
+ }
+
+ slapi_ch_free((void **)&generation);
+ return_value = 0;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "RUV for replica %s is missing replica generation\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ goto done;
+ }
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "Unable to convert %s attribute in entry %s to a replica update vector.\n",
+ type_ruvElement, escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ goto done;
+ }
+
+ }
+ else /* search failed */
+ {
+ if (LDAP_NO_SUCH_OBJECT == rc)
+ {
+ /* The entry doesn't exist: create it */
+ rc = replica_create_ruv_tombstone(r);
+ if (LDAP_SUCCESS != rc)
+ {
+ /*
+ * XXXggood - the following error appears on startup if we try
+ * to initialize replica RUVs before the backend instance is up.
+ * It's alarming to see this error, and we should suppress it
+ * (or avoid trying to configure it) if the backend instance is
+ * not yet online.
+ */
+ /*
+ * XXXrichm - you can also get this error when the backend is in
+ * read only mode c.f. bug 539782
+ */
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_configure_ruv: failed to create replica ruv tombstone "
+ "entry (%s); LDAP error - %d\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc);
+ goto done;
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "_replica_configure_ruv: No ruv tombstone found for replica %s. "
+ "Created a new one\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ return_value = 0;
+ }
+ }
+ else
+ {
+ /* see if the suffix is disabled */
+ char *state = slapi_mtn_get_state(r->repl_root);
+ if (state && !strcasecmp(state, "disabled"))
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_configure_ruv: replication disabled for "
+ "entry (%s); LDAP error - %d\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc);
+ slapi_ch_free_string(&state);
+ goto done;
+ }
+ else if (!r->repl_ruv) /* other error */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name,
+ "_replica_configure_ruv: replication broken for "
+ "entry (%s); LDAP error - %d\n",
+ escape_string(slapi_sdn_get_dn(r->repl_root),ebuf), rc);
+ slapi_ch_free_string(&state);
+ goto done;
+ }
+ else /* some error but continue anyway? */
+ {
+ slapi_log_error(SLAPI_LOG_REPL, repl_plugin_name,
+ "_replica_configure_ruv: Error %d reading tombstone for replica %s.\n",
+ rc, escape_string(slapi_sdn_get_dn(r->repl_root),ebuf));
+ return_value = 0;
+ }
+ slapi_ch_free_string(&state);
+ }
+ }
+
+ if (NULL != r->min_csn_pl)
+ {
+ csnplFree (&r->min_csn_pl);
+ }
+
+ /* create pending list for min csn if necessary */
+ if (ruv_get_smallest_csn_for_replica ((RUV*)object_get_data (r->repl_ruv),
+ r->repl_rid, &csn) == RUV_SUCCESS)
+ {
+ csn_free (&csn);
+ r->min_csn_pl = NULL;
+ }
+ else
+ {
+ /*
+ * The local replica has not generated any of its own CSNs yet.
+ * We need to watch CSNs being generated and note the first
+ * locally-generated CSN that's committed. Once that event occurs,
+ * the RUV is suitable for iteration over locally generated
+ * changes.
+ */
+ r->min_csn_pl = csnplNew();
+ }
+
+done:
+ if (NULL != pb)
+ {
+ slapi_free_search_results_internal(pb);
+ slapi_pblock_destroy (pb);
+ }
+ if (return_value != 0)
+ {
+ if (ruv)
+ ruv_destroy (&ruv);
+ }
+
+ return return_value;
+}
+
+/* Update the tombstone entry to reflect the content of the ruv */
+static void
+replica_replace_ruv_tombstone(Replica *r)
+{
+ Slapi_PBlock *pb = NULL;
+ char *dn;
+ int rc;
+
+ Slapi_Mod smod;
+ Slapi_Mod smod_last_modified;
+ LDAPMod *mods [3];
+
+ PR_ASSERT(NULL != r && NULL != r->repl_root);
+
+ PR_Lock(r->repl_lock);
+
+ PR_ASSERT (r->repl_ruv);
+ ruv_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod);
+ ruv_last_modified_to_smod ((RUV*)object_get_data(r->repl_ruv), &smod_last_modified);
+
+ dn = _replica_get_config_dn (r->repl_root);
+ mods[0] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod);
+ mods[1] = (LDAPMod*)slapi_mod_get_ldapmod_byref(&smod_last_modified);
+
+ PR_Unlock (r->repl_lock);
+
+ mods [2] = NULL;
+ pb = slapi_pblock_new();
+
+ slapi_modify_internal_set_pb(
+ pb,
+ (char*)slapi_sdn_get_dn (r->repl_root), /* only used to select be */
+ mods,
+ NULL, /* controls */
+ RUV_STORAGE_ENTRY_UNIQUEID,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION),
+ OP_FLAG_REPLICATED | OP_FLAG_REPL_FIXUP);
+
+ slapi_modify_internal_pb (pb);
+
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+
+ if (rc != LDAP_SUCCESS)
+ {
+ if ((rc != LDAP_NO_SUCH_OBJECT) || !replica_is_state_flag_set(r, REPLICA_IN_USE))
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "replica_replace_ruv_tombstone: "
+ "failed to update replication update vector for replica %s: LDAP "
+ "error - %d\n", (char*)slapi_sdn_get_dn (r->repl_root), rc);
+ }
+ }
+
+ slapi_ch_free ((void**)&dn);
+ slapi_pblock_destroy (pb);
+ slapi_mod_done (&smod);
+ slapi_mod_done (&smod_last_modified);
+}
+
+
+/*
+ * Returns refcounted object that contains RUV. The caller should release the
+ * object once it is no longer used. To release, call object_release
+ */
+Object *
+replica_consumer_get_ruv (const Replica *r)
+{
+ Object *ruv = NULL;
+
+ PR_ASSERT(r);
+ // PR_ASSERT (r->repl_ruv);
+
+ object_acquire (r->consumer_repl_ruv);
+ ruv = r->consumer_repl_ruv;
+
+ return ruv;
+}
+
+void
+replica_consumer_set_ruv (Replica *r, RUV *ruv)
+{
+ PR_ASSERT(r);
+ PR_ASSERT(ruv);
+
+ if(NULL != r->consumer_repl_ruv)
+ {
+ object_release(r->consumer_repl_ruv);
+ }
+
+ r->consumer_repl_ruv = object_new((void*)ruv, (FNFree)ruv_destroy);
+}
diff --git a/ldap/servers/plugins/replication/windows_tot_protocol.c b/ldap/servers/plugins/replication/windows_tot_protocol.c
new file mode 100644
index 00000000..5229bb51
--- /dev/null
+++ b/ldap/servers/plugins/replication/windows_tot_protocol.c
@@ -0,0 +1,327 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* windows_tot_protocol.c */
+/*
+
+ The tot_protocol object implements the DS 5.0 multi-master total update
+ replication protocol, used to (re)populate a replica.
+
+*/
+
+#include "windowsrepl.h"
+#include "windows_prot_private.h"
+#include "repl.h"
+#include "repl5.h"
+#include "repl5_prot_private.h"
+
+/* Private data structures */
+typedef struct windows_tot_private
+{
+ Repl_Protocol *rp;
+ Repl_Agmt *ra;
+ PRLock *lock;
+ PRUint32 eventbits;
+} windows_tot_private;
+
+typedef struct callback_data
+{
+ Private_Repl_Protocol *prp;
+ int rc;
+ unsigned long num_entries;
+ time_t sleep_on_busy;
+ time_t last_busy;
+} callback_data;
+
+/*
+ * Number of window seconds to wait until we programmatically decide
+ * that the replica has got out of BUSY state
+ */
+#define SLEEP_ON_BUSY_WINDOW (10)
+
+/* Helper functions */
+static void get_result (int rc, void *cb_data);
+static int send_entry (Slapi_Entry *e, void *callback_data);
+static void windows_tot_delete(Private_Repl_Protocol **prp);
+
+/*
+ * Completely refresh a replica. The basic protocol interaction goes
+ * like this:
+ * - Acquire Replica by sending a StartReplicationRequest extop, with the
+ * total update protocol OID and supplier's ruv.
+ * - Send a series of extended operations containing entries.
+ * - send an EndReplicationRequest extended operation
+ */
+static void
+windows_tot_run(Private_Repl_Protocol *prp)
+{
+ int rc;
+ callback_data cb_data;
+ Slapi_PBlock *pb;
+ const char* dn;
+ CSN *remote_schema_csn = NULL;
+ PRBool cookie_has_more = PR_TRUE;
+ RUV *ruv = NULL;
+
+ PR_ASSERT(NULL != prp);
+
+ prp->stopped = 0;
+ if (prp->terminate)
+ {
+ prp->stopped = 1;
+ goto done;
+ }
+
+ conn_set_timeout(prp->conn, agmt_get_timeout(prp->agmt));
+
+ /* acquire remote replica */
+ agmt_set_last_init_start(prp->agmt, current_time());
+
+ rc = windows_acquire_replica (prp, &ruv);
+ /* We never retry total protocol, even in case a transient error.
+ This is because if somebody already updated the replica we don't
+ want to do it again */
+ if (rc != ACQUIRE_SUCCESS)
+ {
+ int optype, ldaprc;
+ conn_get_error(prp->conn, &optype, &ldaprc);
+ agmt_set_last_init_status(prp->agmt, ldaprc,
+ prp->last_acquire_response_code, NULL);
+ goto done;
+ }
+ else if (prp->terminate)
+ {
+ conn_disconnect(prp->conn);
+ prp->stopped = 1;
+ goto done;
+ }
+
+
+ agmt_set_last_init_status(prp->agmt, 0, 0, "Total schema update in progress");
+ remote_schema_csn = agmt_get_consumer_schema_csn ( prp->agmt );
+
+ agmt_set_last_init_status(prp->agmt, 0, 0, "Total update in progress");
+
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "Beginning total update of replica "
+ "\"%s\".\n", agmt_get_long_name(prp->agmt));
+
+ windows_private_null_dirsync_control(prp->agmt);
+
+ /* get everything */
+ windows_dirsync_inc_run(prp);
+ cookie_has_more = windows_private_dirsync_has_more(prp->agmt);
+
+
+
+ /* send everything */
+ dn = slapi_sdn_get_dn( windows_private_get_directory_replarea(prp->agmt));
+
+ pb = slapi_pblock_new ();
+ slapi_search_internal_set_pb (pb, dn, /* XXX modify the searchfilter and scope? */
+ LDAP_SCOPE_ONELEVEL, "(|(objectclass=ntuser)(objectclass=ntgroup)(nsuniqueid=*))", NULL, 0, NULL, NULL,
+ repl_get_plugin_identity (PLUGIN_MULTIMASTER_REPLICATION), 0);
+ cb_data.prp = prp;
+ cb_data.rc = 0;
+ cb_data.num_entries = 0UL;
+ cb_data.sleep_on_busy = 0UL;
+ cb_data.last_busy = current_time ();
+
+ /* this search get all the entries from the replicated area including tombstones
+ and referrals */
+ slapi_search_internal_callback_pb (pb, &cb_data /* callback data */,
+ get_result /* result callback */,
+ send_entry /* entry callback */,
+ NULL /* referral callback*/);
+ slapi_pblock_destroy (pb);
+ agmt_set_last_init_end(prp->agmt, current_time());
+ rc = cb_data.rc;
+ windows_release_replica(prp);
+
+ if (rc != LDAP_SUCCESS)
+ {
+ slapi_log_error (SLAPI_LOG_REPL, windows_repl_plugin_name, "%s: windows_tot_run: "
+ "failed to obtain data to send to the consumer; LDAP error - %d\n",
+ agmt_get_long_name(prp->agmt), rc);
+ agmt_set_last_init_status(prp->agmt, rc, 0, "Total update aborted");
+ } else {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "Finished total update of replica "
+ "\"%s\". Sent %d entries.\n", agmt_get_long_name(prp->agmt), cb_data.num_entries);
+ agmt_set_last_init_status(prp->agmt, 0, 0, "Total update succeeded");
+ }
+
+done:
+
+ prp->stopped = 1;
+}
+
+static int
+windows_tot_stop(Private_Repl_Protocol *prp)
+{
+ int return_value;
+ int seconds = 600;
+ PRIntervalTime start, maxwait, now;
+
+ prp->terminate = 1;
+ maxwait = PR_SecondsToInterval(seconds);
+ start = PR_IntervalNow();
+ now = start;
+ while (!prp->stopped && ((now - start) < maxwait))
+ {
+ DS_Sleep(PR_SecondsToInterval(1));
+ now = PR_IntervalNow();
+ }
+ if (!prp->stopped)
+ {
+ /* Isn't listening. Disconnect from the replica. */
+ slapi_log_error (SLAPI_LOG_REPL, windows_repl_plugin_name, "windows_tot_run: "
+ "protocol not stopped after waiting for %d seconds "
+ "for agreement %s\n", PR_IntervalToSeconds(now-start),
+ agmt_get_long_name(prp->agmt));
+ conn_disconnect(prp->conn);
+ return_value = -1;
+ }
+ else
+ {
+ return_value = 0;
+ }
+
+ return return_value;
+}
+
+
+
+static int
+windows_tot_status(Private_Repl_Protocol *prp)
+{
+ int return_value = 0;
+ return return_value;
+}
+
+
+
+static void
+windows_tot_noop(Private_Repl_Protocol *prp)
+{
+ /* noop */
+}
+
+
+Private_Repl_Protocol *
+Windows_Tot_Protocol_new(Repl_Protocol *rp)
+{
+ windows_tot_private *rip = NULL;
+ Private_Repl_Protocol *prp = (Private_Repl_Protocol *)slapi_ch_malloc(sizeof(Private_Repl_Protocol));
+ prp->delete = windows_tot_delete;
+ prp->run = windows_tot_run;
+ prp->stop = windows_tot_stop;
+ prp->status = windows_tot_status;
+ prp->notify_update = windows_tot_noop;
+ prp->notify_agmt_changed = windows_tot_noop;
+ prp->notify_window_opened = windows_tot_noop;
+ prp->notify_window_closed = windows_tot_noop;
+ prp->replica_object = prot_get_replica_object(rp);
+ prp->update_now = windows_tot_noop;
+ if ((prp->lock = PR_NewLock()) == NULL)
+ {
+ goto loser;
+ }
+ if ((prp->cvar = PR_NewCondVar(prp->lock)) == NULL)
+ {
+ goto loser;
+ }
+ prp->stopped = 1;
+ prp->terminate = 0;
+ prp->eventbits = 0;
+ prp->conn = prot_get_connection(rp);
+ prp->agmt = prot_get_agreement(rp);
+ rip = (void *)slapi_ch_malloc(sizeof(windows_tot_private));
+ rip->rp = rp;
+ prp->private = (void *)rip;
+ prp->replica_acquired = PR_FALSE;
+ return prp;
+loser:
+ windows_tot_delete(&prp);
+ return NULL;
+}
+
+static void
+windows_tot_delete(Private_Repl_Protocol **prp)
+{
+}
+
+static
+void get_result (int rc, void *cb_data)
+{
+ PR_ASSERT (cb_data);
+ ((callback_data*)cb_data)->rc = rc;
+}
+
+static
+int send_entry (Slapi_Entry *e, void *cb_data)
+{
+ int rc;
+ Private_Repl_Protocol *prp;
+
+ // struct berval *bv;
+ unsigned long *num_entriesp;
+ time_t *sleep_on_busyp;
+ time_t *last_busyp;
+
+ PR_ASSERT (cb_data);
+
+ prp = ((callback_data*)cb_data)->prp;
+ num_entriesp = &((callback_data *)cb_data)->num_entries;
+ sleep_on_busyp = &((callback_data *)cb_data)->sleep_on_busy;
+ last_busyp = &((callback_data *)cb_data)->last_busy;
+ PR_ASSERT (prp);
+
+ if (prp->terminate)
+ {
+ conn_disconnect(prp->conn);
+ prp->stopped = 1;
+ ((callback_data*)cb_data)->rc = -1;
+ return -1;
+ }
+
+ /* skip ruv tombstone - need to do this because it might be
+ more up to date then the data we are sending to the client.
+ RUV is sent separately via the protocol */
+ if (is_ruv_tombstone_entry (e))
+ return 0;
+
+ do {
+ /* push the entry to the consumer */
+ rc = add_or_modify_user(e);
+
+ if (rc == CONN_BUSY) {
+ time_t now = current_time ();
+ if ((now - *last_busyp) < (*sleep_on_busyp + 10)) {
+ *sleep_on_busyp +=5;
+ }
+ else {
+ *sleep_on_busyp = 5;
+ }
+ *last_busyp = now;
+
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name,
+ "Replica \"%s\" is busy. Waiting %ds while"
+ " it finishes processing its current import queue\n",
+ agmt_get_long_name(prp->agmt), *sleep_on_busyp);
+ DS_Sleep(PR_SecondsToInterval(*sleep_on_busyp));
+ }
+ }
+ while (rc == CONN_BUSY);
+
+ (*num_entriesp)++;
+
+ if (CONN_OPERATION_SUCCESS == rc) {
+ return 0;
+ } else {
+ ((callback_data*)cb_data)->rc = rc;
+ return -1;
+ }
+}
+
diff --git a/ldap/servers/plugins/replication/windows_total.c b/ldap/servers/plugins/replication/windows_total.c
new file mode 100644
index 00000000..289d5f57
--- /dev/null
+++ b/ldap/servers/plugins/replication/windows_total.c
@@ -0,0 +1,727 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+
+/*
+ repl5_total.c - code that implements a total replica update.
+
+ The requestValue of the NSDS50ReplicationEntry looks like this:
+
+ requestValue ::= SEQUENCE {
+ uniqueid OCTET STRING,
+ dn LDAPDN,
+ annotatedAttributes AnnotatedAttributeList
+ }
+
+ AnnotatedAttributeList ::= SET OF SEQUENCE {
+ attributeType AttributeDescription,
+ attributeDeletionCSN OCTET STRING OPTIONAL,
+ attributeDeleted BOOLEAN DEFAULT FALSE,
+ annotatedValues SET OF AnnotatedValue
+ }
+
+ AnnotatedValue ::= SEQUENCE {
+ value AttributeValue,
+ valueDeleted BOOLEAN DEFAULT FALSE,
+ valueCSNSet SEQUENCE OF ValueCSN,
+ }
+
+ ValueCSN ::= SEQUENCE {
+ CSNType ENUMERATED {
+ valuePresenceCSN (1),
+ valueDeletionCSN (2),
+ valueDistinguishedCSN (3)
+ }
+ CSN OCTET STRING,
+ }
+*/
+
+#include "repl5.h"
+#include "windowsrepl.h"
+
+#define CSN_TYPE_VALUE_UPDATED_ON_WIRE 1
+#define CSN_TYPE_VALUE_DELETED_ON_WIRE 2
+#define CSN_TYPE_VALUE_DISTINGUISHED_ON_WIRE 3
+
+/* #define GORDONS_PATENTED_BER_DEBUG 1 */
+#ifdef GORDONS_PATENTED_BER_DEBUG
+#define BER_DEBUG(a) printf(a)
+#else
+#define BER_DEBUG(a)
+#endif
+
+/* Forward declarations */
+static int my_ber_printf_csn(BerElement *ber, const CSN *csn, const CSNType t);
+static int my_ber_printf_value(BerElement *ber, const char *type,
+ const Slapi_Value *value, PRBool deleted);
+static int my_ber_printf_attr (BerElement *ber, Slapi_Attr *attr, PRBool deleted);
+static int my_ber_scanf_attr (BerElement *ber, Slapi_Attr **attr, PRBool *deleted);
+static int my_ber_scanf_value(BerElement *ber, Slapi_Value **value, PRBool *deleted);
+
+/*
+ * Helper function - convert a CSN to a string and ber_printf() it.
+ */
+static int
+my_ber_printf_csn(BerElement *ber, const CSN *csn, const CSNType t)
+{
+ char csn_str[CSN_STRSIZE];
+ unsigned long len;
+ int rc = -1;
+ int csn_type_as_ber = -1;
+
+ switch (t)
+ {
+ case CSN_TYPE_VALUE_UPDATED:
+ csn_type_as_ber = CSN_TYPE_VALUE_UPDATED_ON_WIRE;
+ break;
+ case CSN_TYPE_VALUE_DELETED:
+ csn_type_as_ber = CSN_TYPE_VALUE_DELETED_ON_WIRE;
+ break;
+ case CSN_TYPE_VALUE_DISTINGUISHED:
+ csn_type_as_ber = CSN_TYPE_VALUE_DISTINGUISHED_ON_WIRE;
+ break;
+ case CSN_TYPE_ATTRIBUTE_DELETED:
+ break;
+ default:
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "my_ber_printf_csn: unknown "
+ "csn type %d encountered.\n", (int)t);
+ return -1;
+ }
+
+ csn_as_string(csn,PR_FALSE,csn_str);
+
+ /* we don't send type for attr csn since there is only one */
+ if (t == CSN_TYPE_ATTRIBUTE_DELETED)
+ {
+ rc = ber_printf(ber, "s", csn_str);
+ BER_DEBUG("s(csn_str)");
+ }
+ else
+ {
+ len = CSN_STRSIZE;
+ rc = ber_printf(ber, "{es}", csn_type_as_ber, csn_str);
+ BER_DEBUG("{e(csn type)s(csn)}");
+ }
+
+ return rc;
+}
+
+
+/*
+ * Send a single annotated attribute value.
+ */
+static int
+my_ber_printf_value(BerElement *ber, const char *type, const Slapi_Value *value, PRBool deleted)
+{
+ const struct berval *bval = NULL;
+ int rc = -1;
+ const CSNSet *csnset;
+ void *cookie;
+ CSN *csn;
+ CSNType t;
+
+ bval = slapi_value_get_berval(value);
+ BER_DEBUG("{o(value)");
+ if (ber_printf(ber, "{o", bval->bv_val, bval->bv_len) == -1) /* Start sequence */
+ {
+ goto done;
+ }
+
+/* if (ber_printf(ber, "o", bval->bv_val, bval->bv_len) == -1)
+ {
+ goto done;
+ } */
+
+ if (deleted)
+ {
+ BER_DEBUG("b(deleted flag)");
+ if (ber_printf (ber, "b", PR_TRUE) == -1)
+ {
+ goto done;
+ }
+ }
+ /* Send value CSN list */
+ BER_DEBUG("{");
+ if (ber_printf(ber, "{") == -1) /* Start set */
+ {
+ goto done;
+ }
+
+ /* Iterate over the sequence of CSNs. */
+ csnset = value_get_csnset (value);
+ if (csnset)
+ {
+ for (cookie = csnset_get_first_csn (csnset, &csn, &t); NULL != cookie;
+ cookie = csnset_get_next_csn (csnset, cookie, &csn, &t))
+ {
+ /* Don't send any adcsns, since that was already sent */
+ if (t != CSN_TYPE_ATTRIBUTE_DELETED)
+ {
+ if (my_ber_printf_csn(ber, csn, t) == -1)
+ {
+ goto done;
+ }
+ }
+ }
+ }
+
+ BER_DEBUG("}");
+ if (ber_printf(ber, "}") == -1) /* End CSN sequence */
+ {
+ goto done;
+ }
+ BER_DEBUG("}");
+ if (ber_printf(ber, "}") == -1) /* End sequence */
+ {
+ goto done;
+ }
+
+ /* Everything's ok */
+ rc = 0;
+
+done:
+ return rc;
+
+}
+
+/* send a single attribute */
+static int
+my_ber_printf_attr (BerElement *ber, Slapi_Attr *attr, PRBool deleted)
+{
+ Slapi_Value *value;
+ char *type;
+ int i;
+ const CSN *csn;
+
+ /* First, send the type */
+ slapi_attr_get_type(attr, &type);
+ BER_DEBUG("{s(type ");
+ BER_DEBUG(type);
+ BER_DEBUG(")");
+ if (ber_printf(ber, "{s", type) == -1) /* Begin sequence for this type */
+ {
+ goto loser;
+ }
+
+ /* Send the attribute deletion CSN if present */
+ csn = attr_get_deletion_csn(attr);
+ if (csn)
+ {
+ if (my_ber_printf_csn(ber, csn, CSN_TYPE_ATTRIBUTE_DELETED) == -1)
+ {
+ goto loser;
+ }
+ }
+
+ /* only send "is deleted" flag for deleted attributes since it defaults to false */
+ if (deleted)
+ {
+ BER_DEBUG("b(del flag)");
+ if (ber_printf (ber, "b", PR_TRUE) == -1)
+ {
+ goto loser;
+ }
+ }
+
+ /*
+ * Iterate through all the values.
+ */
+ BER_DEBUG("[");
+ if (ber_printf(ber, "[") == -1) /* Begin set */
+ {
+ goto loser;
+ }
+
+ /*
+ * Process the non-deleted values first.
+ */
+ i = slapi_attr_first_value(attr, &value);
+ while (i != -1)
+ {
+ if (my_ber_printf_value(ber, type, value, PR_FALSE) == -1)
+ {
+ goto loser;
+ }
+ i= slapi_attr_next_value(attr, i, &value);
+ }
+
+ /*
+ * Now iterate over all of the deleted values.
+ */
+ i= attr_first_deleted_value(attr, &value);
+ while (i != -1)
+ {
+ if (my_ber_printf_value(ber, type, value, PR_TRUE) == -1)
+ {
+ goto loser;
+ }
+ i= attr_next_deleted_value(attr, i, &value);
+ }
+ BER_DEBUG("]");
+ if (ber_printf(ber, "]") == -1) /* End set */
+ {
+ goto loser;
+ }
+
+ BER_DEBUG("}");
+ if (ber_printf(ber, "}") == -1) /* End sequence for this type */
+ {
+ goto loser;
+ }
+
+ return 0;
+loser:
+ return -1;
+}
+
+/*
+ * Get an annotated value from the BerElement. Returns 0 on
+ * success, -1 on failure.
+ */
+static int
+my_ber_scanf_value(BerElement *ber, Slapi_Value **value, PRBool *deleted)
+{
+ struct berval *attrval = NULL;
+ unsigned long len;
+ unsigned long tag;
+ CSN *csn = NULL;
+ char csnstring[CSN_STRSIZE + 1];
+ CSNType csntype;
+ char *lasti;
+
+ PR_ASSERT(ber && value && deleted);
+
+ *value = NULL;
+
+ if (NULL == ber && NULL == value)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "my_ber_scanf_value BAD 1\n");
+ goto loser;
+ }
+
+ /* Each value is a sequence */
+ if (ber_scanf(ber, "{O", &attrval) == -1)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "my_ber_scanf_value BAD 2\n");
+ goto loser;
+ }
+ /* Allocate and fill in the attribute value */
+ if ((*value = slapi_value_new_berval(attrval)) == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "my_ber_scanf_value BAD 3\n");
+ goto loser;
+ }
+
+ /* check if this is a deleted value */
+ if (ber_peek_tag(ber, &len) == LBER_BOOLEAN)
+ {
+ if (ber_scanf(ber, "b", deleted) == -1)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "my_ber_scanf_value BAD 4\n");
+ goto loser;
+ }
+ }
+
+ else /* default is present value */
+ {
+ *deleted = PR_FALSE;
+ }
+
+ /* Read the sequence of CSNs */
+ for (tag = ber_first_element(ber, &len, &lasti);
+ tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET;
+ tag = ber_next_element(ber, &len, lasti))
+ {
+ long csntype_tmp;
+ /* Each CSN is in a sequence that includes a csntype and CSN */
+ len = CSN_STRSIZE;
+ if (ber_scanf(ber, "{es}", &csntype_tmp, csnstring, &len) == -1)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "my_ber_scanf_value BAD 7 - bval is %s\n", attrval->bv_val);
+ goto loser;
+ }
+ switch (csntype_tmp)
+ {
+ case CSN_TYPE_VALUE_UPDATED_ON_WIRE:
+ csntype = CSN_TYPE_VALUE_UPDATED;
+ break;
+ case CSN_TYPE_VALUE_DELETED_ON_WIRE:
+ csntype = CSN_TYPE_VALUE_DELETED;
+ break;
+ case CSN_TYPE_VALUE_DISTINGUISHED_ON_WIRE:
+ csntype = CSN_TYPE_VALUE_DISTINGUISHED;
+ break;
+ default:
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "Error: preposterous CSN type "
+ "%d received during total update.\n", csntype_tmp);
+ goto loser;
+ }
+ csn = csn_new_by_string(csnstring);
+ if (csn == NULL)
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "my_ber_scanf_value BAD 8\n");
+ goto loser;
+ }
+ value_add_csn(*value, csntype, csn);
+ csn_free (&csn);
+ }
+
+ if (ber_scanf(ber, "}") == -1) /* End of annotated attribute value seq */
+ {
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "my_ber_scanf_value BAD 10\n");
+ goto loser;
+ }
+
+ if (attrval)
+ ber_bvfree(attrval);
+ return 0;
+
+loser:
+ /* Free any stuff we allocated */
+ if (csn)
+ csn_free (&csn);
+ if (attrval)
+ ber_bvfree(attrval);
+ if (value)
+ {
+ slapi_value_free (value);
+ }
+
+ return -1;
+}
+
+static int
+my_ber_scanf_attr (BerElement *ber, Slapi_Attr **attr, PRBool *deleted)
+{
+ char *attrtype = NULL;
+ CSN *attr_deletion_csn = NULL;
+ PRBool val_deleted;
+ char *lasti;
+ unsigned long len;
+ unsigned long tag;
+ char *str;
+ int rc;
+ Slapi_Value *value;
+
+ PR_ASSERT (ber && attr && deleted);
+
+ /* allocate the attribute */
+ *attr = slapi_attr_new ();
+ if (attr == NULL)
+ {
+ goto loser;
+ }
+
+ if (ber_scanf(ber, "{a", &attrtype) == -1) /* Begin sequence for this attr */
+ {
+ goto loser;
+ }
+
+
+ slapi_attr_init(*attr, attrtype);
+ slapi_ch_free ((void **)&attrtype);
+
+ /* The attribute deletion CSN is next and is optional? */
+ if (ber_peek_tag(ber, &len) == LBER_OCTETSTRING)
+ {
+ if (ber_scanf(ber, "a", &str) == -1)
+ {
+ goto loser;
+ }
+ attr_deletion_csn = csn_new_by_string(str);
+ slapi_ch_free((void **)&str);
+ }
+
+ if (attr_deletion_csn)
+ {
+ rc = attr_set_deletion_csn(*attr, attr_deletion_csn);
+ csn_free (&attr_deletion_csn);
+ if (rc != 0)
+ {
+ goto loser;
+ }
+ }
+
+ /* The "attribute deleted" flag is next, and is optional */
+ if (ber_peek_tag(ber, &len) == LBER_BOOLEAN)
+ {
+ if (ber_scanf(ber, "b", deleted) == -1)
+ {
+ goto loser;
+ }
+ }
+ else /* default is present */
+ {
+ *deleted = PR_FALSE;
+ }
+
+ /* loop over the list of attribute values */
+ for (tag = ber_first_element(ber, &len, &lasti);
+ tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET;
+ tag = ber_next_element(ber, &len, lasti))
+ {
+
+ value = NULL;
+ if (my_ber_scanf_value(ber, &value, &val_deleted) == -1)
+ {
+ goto loser;
+ }
+
+ if (val_deleted)
+ {
+ /* Add the value to the attribute */
+ if (attr_add_deleted_value(*attr, value) == -1) /* attr has ownership of value */
+ {
+ goto loser;
+ }
+ }
+ else
+ {
+ /* Add the value to the attribute */
+ if (slapi_attr_add_value(*attr, value) == -1) /* attr has ownership of value */
+ {
+ goto loser;
+ }
+ }
+ if (value)
+ slapi_value_free(&value);
+ }
+
+ if (ber_scanf(ber, "}") == -1) /* End sequence for this attribute */
+ {
+ goto loser;
+ }
+
+ return 0;
+loser:
+ if (*attr)
+ slapi_attr_free (attr);
+ if (value)
+ slapi_value_free (&value);
+
+ return -1;
+}
+
+/*
+ * Extract the payload from a total update extended operation,
+ * decode it, and produce a Slapi_Entry structure representing a new
+ * entry to be added to the local database.
+ */
+static int
+decode_total_update_extop(Slapi_PBlock *pb, Slapi_Entry **ep)
+{
+ BerElement *tmp_bere = NULL;
+ Slapi_Entry *e = NULL;
+ Slapi_Attr *attr = NULL;
+ char *str = NULL;
+ CSN *dn_csn = NULL;
+ struct berval *extop_value = NULL;
+ char *extop_oid = NULL;
+ unsigned long len;
+ char *lasto;
+ unsigned long tag;
+ int rc;
+ PRBool deleted;
+
+ PR_ASSERT(NULL != pb);
+ PR_ASSERT(NULL != ep);
+
+ slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &extop_oid);
+ slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &extop_value);
+
+ if (NULL == extop_oid ||
+ strcmp(extop_oid, REPL_NSDS50_REPLICATION_ENTRY_REQUEST_OID) != 0 ||
+ NULL == extop_value)
+ {
+ /* Bogus */
+ goto loser;
+ }
+
+ if ((tmp_bere = ber_init(extop_value)) == NULL)
+ {
+ goto loser;
+ }
+
+ if ((e = slapi_entry_alloc()) == NULL)
+ {
+ goto loser;
+ }
+
+ if (ber_scanf(tmp_bere, "{") == -1) /* Begin outer sequence */
+ {
+ goto loser;
+ }
+
+ /* The entry's uniqueid is first */
+ if (ber_scanf(tmp_bere, "a", &str) == -1)
+ {
+ goto loser;
+ }
+ slapi_entry_set_uniqueid(e, str);
+ str = NULL; /* Slapi_Entry now owns the uniqueid */
+
+ /* The entry's DN is next */
+ if (ber_scanf(tmp_bere, "a", &str) == -1)
+ {
+ goto loser;
+ }
+ slapi_entry_set_dn(e, str);
+ str = NULL; /* Slapi_Entry now owns the dn */
+
+ /* Get the attributes */
+ for ( tag = ber_first_element( tmp_bere, &len, &lasto );
+ tag != LBER_ERROR && tag != LBER_END_OF_SEQORSET;
+ tag = ber_next_element( tmp_bere, &len, lasto ) )
+ {
+
+ if (my_ber_scanf_attr (tmp_bere, &attr, &deleted) != 0)
+ {
+ goto loser;
+ }
+
+ /* Add the attribute to the entry */
+ if (deleted)
+ entry_add_deleted_attribute_wsi(e, attr); /* entry now owns attr */
+ else
+ entry_add_present_attribute_wsi(e, attr); /* entry now owns attr */
+ attr = NULL;
+ }
+
+ if (ber_scanf(tmp_bere, "}") == -1) /* End sequence for this entry */
+ {
+ goto loser;
+ }
+
+ /* Check for ldapsubentries and tombstone entries to set flags properly */
+ slapi_entry_attr_find(e, "objectclass", &attr);
+ if (attr != NULL) {
+ struct berval bv;
+ bv.bv_val = "ldapsubentry";
+ bv.bv_len = strlen(bv.bv_val);
+ if (slapi_attr_value_find(attr, &bv) == 0) {
+ slapi_entry_set_flag(e, SLAPI_ENTRY_LDAPSUBENTRY);
+ }
+ bv.bv_val = SLAPI_ATTR_VALUE_TOMBSTONE;
+ bv.bv_len = strlen(bv.bv_val);
+ if (slapi_attr_value_find(attr, &bv) == 0) {
+ slapi_entry_set_flag(e, SLAPI_ENTRY_FLAG_TOMBSTONE);
+ }
+ }
+
+ /* If we get here, the entry is properly constructed. Return it. */
+
+ rc = 0;
+ *ep = e;
+ goto free_and_return;
+
+loser:
+ rc = -1;
+ /* slapi_ch_free accepts NULL pointer */
+ slapi_ch_free((void **)&str);
+
+ if (NULL != dn_csn)
+ {
+ csn_free(&dn_csn);
+ }
+ if (attr != NULL)
+ {
+ slapi_attr_free (&attr);
+ }
+
+ if (NULL != e)
+ {
+ slapi_entry_free (e);
+ }
+ *ep = NULL;
+ slapi_log_error(SLAPI_LOG_FATAL, windows_repl_plugin_name, "Error: could not decode extended "
+ "operation containing entry for total update.\n");
+
+free_and_return:
+ if (NULL != tmp_bere)
+ {
+ ber_free(tmp_bere, 1);
+ tmp_bere = NULL;
+ }
+ return rc;
+}
+
+/*
+ * This plugin entry point is called whenever an NSDS50ReplicationEntry
+ * extended operation is received.
+ */
+int
+___multimaster_extop_NSDS50ReplicationEntry(Slapi_PBlock *pb)
+{
+ int rc;
+ Slapi_Entry *e = NULL;
+ Slapi_Connection *conn = NULL;
+ int connid, opid;
+
+ connid = 0;
+ slapi_pblock_get(pb, SLAPI_CONN_ID, &connid);
+ opid = 0;
+ slapi_pblock_get(pb, SLAPI_OPERATION_ID, &opid);
+
+ /* Decode the extended operation */
+ rc = decode_total_update_extop(pb, &e);
+
+ if (0 == rc)
+ {
+#ifdef notdef
+ /*
+ * Just spew LDIF so we're sure we got it right. Later we'll firehose
+ * this into the database import code
+ */
+ int len;
+ char *str = slapi_entry2str_with_options(e, &len,SLAPI_DUMP_UNIQUEID);
+ puts(str);
+ free(str);
+#endif
+
+ rc = slapi_import_entry (pb, e);
+ /* slapi_import_entry return an LDAP error in case of problem
+ * LDAP_BUSY is used to indicate that the import queue is full
+ * and that flow control must happen to stop the supplier
+ * from sending entries
+ */
+ if ((rc != LDAP_SUCCESS) && (rc != LDAP_BUSY))
+ {
+ const char *dn = slapi_entry_get_dn_const(e);
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "Error %d: could not import entry dn %s "
+ "for total update operation conn=%d op=%d\n",
+ rc, dn, connid, opid);
+ rc = -1;
+ }
+
+ }
+ else
+ {
+ slapi_log_error(SLAPI_LOG_REPL, windows_repl_plugin_name,
+ "Error %d: could not decode the total update extop "
+ "for total update operation conn=%d op=%d\n",
+ rc, connid, opid);
+ }
+
+ if ((rc != 0) && (rc != LDAP_BUSY))
+ {
+ /* just disconnect from the supplier. bulk import is stopped when
+ connection object is destroyed */
+ slapi_pblock_get (pb, SLAPI_CONNECTION, &conn);
+ if (conn)
+ {
+ slapi_disconnect_server(conn);
+ }
+
+ /* cleanup */
+ if (e)
+ {
+ slapi_entry_free (e);
+ }
+ }
+
+ return rc;
+}
diff --git a/ldap/servers/plugins/replication/windowsrepl.h b/ldap/servers/plugins/replication/windowsrepl.h
new file mode 100644
index 00000000..bb5ffd78
--- /dev/null
+++ b/ldap/servers/plugins/replication/windowsrepl.h
@@ -0,0 +1,384 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright 2001 Sun Microsystems, Inc.
+ * Portions copyright 1999, 2001-2003 Netscape Communications Corporation.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/* repl5.h - 5.0 replication header */
+#include "repl.h"
+#include "repl5.h"
+
+#ifndef _WINDOWSREPL5_H_
+#define _WINDOWSREPL5_H_
+
+#include <limits.h>
+#include <time.h>
+#include <stdio.h>
+
+#undef REPL_PLUGIN_NAME
+#define REPL_PLUGIN_NAME "WindowsSyncPlugin"
+#define WINDOWS_REPL_PLUGIN_NAME REPL_PLUGIN_NAME
+
+char* get_repl_session_id (Slapi_PBlock *pb, char *id, CSN **opcsn);
+#include <string.h>
+#ifndef _WIN32
+#include <sys/param.h>
+#endif /* _WIN32 */
+
+#include "portable.h" /* GGOODREPL - is this cheating? */
+#include "repl_shared.h"
+#include "llist.h"
+#include "repl5_ruv.h"
+#include "cl4.h"
+
+
+/* Attribute names for replication agreement attributes */
+extern const char *type_nsdsWindowsReplicaCookie;
+extern char *windows_repl_plugin_name;
+
+/* windows plugin points */
+int windows_preop_bind (Slapi_PBlock *pb);
+int windows_preop_add (Slapi_PBlock *pb);
+int windows_preop_delete (Slapi_PBlock *pb);
+int windows_preop_modify (Slapi_PBlock *pb);
+int windows_preop_modrdn (Slapi_PBlock *pb);
+int windows_preop_search (Slapi_PBlock *pb);
+int windows_preop_compare (Slapi_PBlock *pb);
+int windows_bepreop_add (Slapi_PBlock *pb);
+int windows_bepreop_delete (Slapi_PBlock *pb);
+int windows_bepreop_modify (Slapi_PBlock *pb);
+int windows_bepreop_modrdn (Slapi_PBlock *pb);
+int windows_bepostop_modrdn (Slapi_PBlock *pb);
+int windows_bepostop_delete (Slapi_PBlock *pb);
+int windows_postop_bind (Slapi_PBlock *pb);
+int windows_postop_add (Slapi_PBlock *pb);
+int windows_postop_delete (Slapi_PBlock *pb);
+int windows_postop_modify (Slapi_PBlock *pb);
+int windows_postop_modrdn (Slapi_PBlock *pb);
+
+/* In repl5_init.c */
+char* get_thread_private_agmtname ();
+void set_thread_private_agmtname (const char *agmtname);
+void* get_thread_private_cache ();
+void set_thread_private_cache (void *buf);
+char* get_repl_session_id (Slapi_PBlock *pb, char *id, CSN **opcsn);
+
+/* In repl_extop.c */
+int windows_extop_StartNSDS50ReplicationRequest(Slapi_PBlock *pb);
+int windows_extop_EndNSDS50ReplicationRequest(Slapi_PBlock *pb);
+int extop_noop(Slapi_PBlock *pb);
+struct berval *NSDS50StartReplicationRequest_new(const char *protocol_oid,
+ const char *repl_root, char **extra_referrals, CSN *csn);
+struct berval *NSDS50EndReplicationRequest_new(char *repl_root);
+int decode_repl_ext_response(struct berval *data, int *response_code,
+ struct berval ***ruv_bervals);
+
+/* extension construct/destructor */
+void* windows_operation_extension_constructor (void *object, void *parent);
+void windows_operation_extension_destructor (void* ext,void *object, void *parent);
+void* windows_consumer_operation_extension_constructor (void *object, void *parent);
+void windows_consumer_operation_extension_destructor (void* ext,void *object, void *parent);
+void windows_mtnode_extension_destructor (void* ext, void *object, void *parent);
+void *windows_mtnode_extension_constructor (void *object, void *parent);
+
+
+
+
+
+typedef struct windows_operation_extension
+{
+ int prevent_recursive_call;
+ struct slapi_operation_parameters *operation_parameters;
+ char *repl_gen;
+} windows_operation_extension;
+
+/* windows_ext.c */
+void windows_repl_sup_init_ext ();
+void windows_repl_con_init_ext ();
+int windows_repl_sup_register_ext (ext_type type); // never used?
+int windows_repl_con_register_ext (ext_type type); // never used
+void* windows_repl_sup_get_ext (ext_type type, void *object);
+void* windows_repl_con_get_ext (ext_type type, void *object);
+
+
+
+/* In repl5_total.c */
+int windows_extop_NSDS50ReplicationEntry(Slapi_PBlock *pb);
+
+/* In repl_controls.c */
+int create_NSDS50ReplUpdateInfoControl(const char *uuid,
+ const char *superior_uuid, const CSN *csn,
+ LDAPMod **modify_mods, LDAPControl **ctrlp);
+void destroy_NSDS50ReplUpdateInfoControl(LDAPControl **ctrlp);
+int decode_NSDS50ReplUpdateInfoControl(LDAPControl **controlsp,
+ char **uuid, char **newsuperior_uuid, CSN **csn, LDAPMod ***modrdn_mods);
+
+/* In repl5_replsupplier.c */
+/* typedef struct repl_supplier Repl_Supplier; */
+Repl_Supplier *replsupplier_init(Slapi_Entry *e);
+void replsupplier_configure(Repl_Supplier *rs, Slapi_PBlock *pb);
+void replsupplier_start(Repl_Supplier *rs);
+void replsupplier_stop(Repl_Supplier *rs);
+void replsupplier_destroy(Repl_Supplier **rs);
+void replsupplier_notify(Repl_Supplier *rs, PRUint32 eventmask);
+PRUint32 replsupplier_get_status(Repl_Supplier *rs);
+
+/* In repl5_plugins.c */
+int windows_set_local_purl();
+const char *windows_get_local_purl();
+PRBool windows_started();
+
+/* In repl5_schedule.c */
+/* typedef struct schedule Schedule; */
+/* typedef void (*window_state_change_callback)(void *arg, PRBool opened); */
+Schedule *schedule_new(window_state_change_callback callback_fn, void *callback_arg, const char *session_id);
+void schedule_destroy(Schedule *s);
+int schedule_set(Schedule *sch, Slapi_Attr *attr);
+char **schedule_get(Schedule *sch);
+int schedule_in_window_now(Schedule *sch);
+PRTime schedule_next(Schedule *sch);
+int schedule_notify(Schedule *sch, Slapi_PBlock *pb);
+void schedule_set_priority_attributes(Schedule *sch, char **prio_attrs, int override_schedule);
+void schedule_set_startup_delay(Schedule *sch, size_t startup_delay);
+void schedule_set_maximum_backlog(Schedule *sch, size_t max_backlog);
+void schedule_notify_session(Schedule *sch, PRTime session_end_time, unsigned int flags);
+#define REPLICATION_SESSION_SUCCESS 0
+
+/* In repl5_bos.c */
+/* typedef struct repl_bos Repl_Bos; */
+
+/* In repl5_agmt.c */
+/* typedef struct repl5agmt Repl_Agmt; */
+/* #define TRANSPORT_FLAG_SSL 1 */
+/* #define TRANSPORT_FLAG_TLS 2 */
+/* #define BINDMETHOD_SIMPLE_AUTH 1 */
+/* #define BINDMETHOD_SSL_CLIENTAUTH 2 */
+Repl_Agmt *windows_agmt_new_from_entry(Slapi_Entry *e);
+/* Repl_Agmt *agmt_new_from_pblock(Slapi_PBlock *pb); */
+/* void agmt_delete(void **ra); */
+/* const Slapi_DN *agmt_get_dn_byref(const Repl_Agmt *ra); */
+/* int agmt_get_auto_initialize(const Repl_Agmt *ra); */
+/* long agmt_get_timeout(const Repl_Agmt *ra); */
+/* long agmt_get_busywaittime(const Repl_Agmt *ra); */
+/* long agmt_get_pausetime(const Repl_Agmt *ra); */
+int windows_agmt_start(Repl_Agmt *ra);
+int windows_agmt_stop(Repl_Agmt *ra);
+/* int agmt_replicate_now(Repl_Agmt *ra); */
+/* char *agmt_get_hostname(const Repl_Agmt *ra); */
+/* int agmt_get_port(const Repl_Agmt *ra); */
+/* PRUint32 agmt_get_transport_flags(const Repl_Agmt *ra); */
+/* char *agmt_get_binddn(const Repl_Agmt *ra); */
+/* struct berval *agmt_get_credentials(const Repl_Agmt *ra); */
+/* int agmt_get_bindmethod(const Repl_Agmt *ra); */
+Slapi_DN *windows_agmt_get_replarea(const Repl_Agmt *ra);
+/* int agmt_is_fractional(const Repl_Agmt *ra); */
+/* int agmt_is_fractional_attr(const Repl_Agmt *ra, const char *attrname); */
+/* int agmt_is_50_mm_protocol(const Repl_Agmt *ra); */
+/* int agmt_matches_name(const Repl_Agmt *ra, const Slapi_DN *name); */
+/* int agmt_replarea_matches(const Repl_Agmt *ra, const Slapi_DN *name); */
+int windows_agmt_schedule_in_window_now(const Repl_Agmt *ra);
+int windows_agmt_set_schedule_from_entry( Repl_Agmt *ra, const Slapi_Entry *e );
+/* int agmt_set_timeout_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); */
+/* int agmt_set_busywaittime_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); */
+/* int agmt_set_pausetime_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); */
+int windows_agmt_set_credentials_from_entry( Repl_Agmt *ra, const Slapi_Entry *e );
+/* int agmt_set_binddn_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); */
+/* int agmt_set_bind_method_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); */
+/* int agmt_set_transportinfo_from_entry( Repl_Agmt *ra, const Slapi_Entry *e ); */
+const char *windows_agmt_get_long_name(const Repl_Agmt *ra);
+/* int agmt_initialize_replica(const Repl_Agmt *agmt); */
+/* void agmt_replica_init_done (const Repl_Agmt *agmt); */
+/* void agmt_notify_change(Repl_Agmt *ra, Slapi_PBlock *pb); */
+/* Object* agmt_get_consumer_ruv (Repl_Agmt *ra); */
+/* ReplicaId agmt_get_consumer_rid ( Repl_Agmt *ra, void *conn ); */
+/* int agmt_set_consumer_ruv (Repl_Agmt *ra, RUV *ruv); */
+void windows_agmt_update_consumer_ruv (Repl_Agmt *ra);
+/* CSN* agmt_get_consumer_schema_csn (Repl_Agmt *ra); */
+/* void agmt_set_consumer_schema_csn (Repl_Agmt *ra, CSN *csn); */
+/* void agmt_set_last_update_in_progress (Repl_Agmt *ra, PRBool in_progress); */
+/* 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); */
+/* 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); */
+/* void agmt_inc_last_update_changecount (Repl_Agmt *ra, ReplicaId rid, int skipped); */
+/* void agmt_get_changecount_string (Repl_Agmt *ra, char *buf, int bufsize); */
+
+/* typedef struct replica Replica; */
+
+/* In repl5_agmtlist.c */
+int windows_agmtlist_config_init();
+void windows_agmtlist_shutdown();
+void windows_agmtlist_notify_all(Slapi_PBlock *pb);
+Object* windows_agmtlist_get_first_agreement_for_replica(Replica *r);
+Object* windows_agmtlist_get_next_agreement_for_replica(Replica *r, Object *prev);
+
+
+/* In repl5_backoff.c */
+/* typedef struct backoff_timer Backoff_Timer; */
+#define BACKOFF_FIXED 1
+#define BACKOFF_EXPONENTIAL 2
+#define BACKOFF_RANDOM 3
+Backoff_Timer *backoff_new(int timer_type, int initial_interval, int max_interval);
+time_t backoff_reset(Backoff_Timer *bt, slapi_eq_fn_t callback, void *callback_data);
+time_t backoff_step(Backoff_Timer *bt);
+int backoff_expired(Backoff_Timer *bt, int margin);
+void backoff_delete(Backoff_Timer **btp);
+
+/* In repl5_connection.c */
+/* typedef struct repl_connection Repl_Connection; */
+/* typedef enum */
+/* { */
+/* CONN_OPERATION_SUCCESS, */
+/* CONN_OPERATION_FAILED, */
+/* CONN_NOT_CONNECTED, */
+/* CONN_SUPPORTS_DS5_REPL, */
+/* CONN_DOES_NOT_SUPPORT_DS5_REPL, */
+/* CONN_SCHEMA_UPDATED, */
+/* CONN_SCHEMA_NO_UPDATE_NEEDED, */
+/* CONN_LOCAL_ERROR, */
+/* CONN_BUSY, */
+/* CONN_SSL_NOT_ENABLED, */
+/* CONN_TIMEOUT */
+/* } ConnResult; */
+
+Repl_Connection *windows_conn_new(Repl_Agmt *agmt);
+ConnResult windows_conn_connect(Repl_Connection *conn);
+void windows_conn_disconnect(Repl_Connection *conn);
+void windows_conn_delete(Repl_Connection *conn);
+void windows_conn_get_error(Repl_Connection *conn, int *operation, int *error);
+ConnResult windows_conn_send_add(Repl_Connection *conn, const char *dn, LDAPMod **attrs,
+ LDAPControl *update_control, LDAPControl ***returned_controls);
+ConnResult windows_conn_send_delete(Repl_Connection *conn, const char *dn,
+ LDAPControl *update_control, LDAPControl ***returned_controls);
+ConnResult windows_conn_send_modify(Repl_Connection *conn, const char *dn, LDAPMod **mods,
+ LDAPControl *update_control, LDAPControl ***returned_controls);
+ConnResult windows_conn_send_rename(Repl_Connection *conn, const char *dn,
+ const char *newrdn, const char *newparent, int deleteoldrdn,
+ LDAPControl *update_control, LDAPControl ***returned_controls);
+ConnResult windows_conn_send_extended_operation(Repl_Connection *conn, const char *extop_oid,
+ struct berval *payload, char **retoidp, struct berval **retdatap,
+ LDAPControl *update_control, LDAPControl ***returned_controls);
+const char *windows_conn_get_status(Repl_Connection *conn);
+void windows_conn_start_linger(Repl_Connection *conn);
+void windows_conn_cancel_linger(Repl_Connection *conn);
+ConnResult windows_conn_replica_supports_ds5_repl(Repl_Connection *conn);
+ConnResult windows_conn_replica_supports_dirsync(Repl_Connection *conn);
+ConnResult windows_conn_read_entry_attribute(Repl_Connection *conn, const char *dn, char *type,
+ struct berval ***returned_bvals);
+ConnResult windows_conn_push_schema(Repl_Connection *conn, CSN **remotecsn);
+void windows_conn_set_timeout(Repl_Connection *conn, long timeout);
+void windows_conn_set_agmt_changed(Repl_Connection *conn);
+
+
+
+/* In repl5_protocol.c */
+typedef struct windows_repl_protocol Windows_Repl_Protocol;
+Repl_Protocol *protocol_new(Repl_Agmt *agmt, int protocol_state);
+void protocol_start(Repl_Protocol *rp);
+
+Repl_Agmt *protocol_get_agreement(Repl_Protocol *rp);
+/* initiate total protocol */
+void protocol_initialize_replica(Repl_Protocol *rp);
+/* stop protocol session in progress */
+void protocol_stop(Repl_Protocol *rp);
+void protocol_free(Repl_Protocol **rpp);
+void protocol_delete(Repl_Protocol **rpp);
+PRBool protocol_set_active_protocol (Repl_Protocol *rp, PRBool total);
+void protocol_clear_active_protocol (Repl_Protocol *rp);
+Repl_Connection *protocol_get_connection(Repl_Protocol *rp);
+void protocol_resume(Repl_Protocol *rp, int wakeup_action);
+void protocol_notify_update(Repl_Protocol *rp);
+void protocol_notify_agmt_changed(Repl_Protocol *rp, char * agmt_name);
+void protocol_notify_window_opened (Repl_Protocol *rp);
+void protocol_notify_window_closed (Repl_Protocol *rp);
+Object *protocol_get_replica_object(Repl_Protocol *rp);
+void protocol_replicate_now(Repl_Protocol *rp);
+
+/* In repl5_replica.c */
+/* typedef enum */
+/* { */
+/* REPLICA_TYPE_UNKNOWN, */
+/* REPLICA_TYPE_PRIMARY, */
+/* REPLICA_TYPE_READONLY, */
+/* REPLICA_TYPE_UPDATABLE, */
+/* REPLICA_TYPE_END */
+/* } ReplicaType; */
+
+/* #define RUV_STORAGE_ENTRY_UNIQUEID "ffffffff-ffffffff-ffffffff-ffffffff" */
+/* #define START_ITERATION_ENTRY_UNIQUEID "00000000-00000000-00000000-00000000" */
+/* #define START_ITERATION_ENTRY_DN "cn=start iteration" */
+
+/* typedef int (*FNEnumReplica) (Replica *r, void *arg); */
+
+/* this function should be called to construct the replica object
+ from the data already in the DIT */
+ Replica *windows_replica_new(const Slapi_DN *root);
+
+#define REPLICA_IN_USE 1 /* The replica is busy */
+#define REPLICA_INCREMENTAL_IN_PROGRESS 2 /* Set only between start and stop inc */
+#define REPLICA_TOTAL_IN_PROGRESS 4 /* Set only between start and stop total */
+#define REPLICA_AGREEMENTS_DISABLED 8 /* Replica is offline */
+PRBool replica_is_state_flag_set(Replica *r, PRInt32 flag);
+void replica_set_state_flag (Replica *r, PRUint32 flag, PRBool clear);
+void replica_enable_replication (Replica *r);
+void replica_disable_replication (Replica *r, Object *r_obj);
+int windows_replica_start_agreement(Replica *r, Repl_Agmt *ra);
+
+CSN* replica_generate_next_csn ( Slapi_PBlock *pb, const CSN *basecsn );
+int replica_get_attr ( Slapi_PBlock *pb, const char *type, void *value );
+
+/* mapping tree extensions manipulation */
+void windows_mtnode_extension_init ();
+void windows_mtnode_extension_destroy ();
+void windows_mtnode_construct_replicas ();
+
+void windows_be_state_change (void *handle, char *be_name, int old_be_state, int new_be_state);
+
+/* In repl5_replica_config.c */
+int windows_replica_config_init();
+void windows_replica_config_destroy ();
+
+/* replutil.c */
+LDAPControl* create_managedsait_control ();
+LDAPControl* create_backend_control(Slapi_DN *sdn);
+void repl_set_mtn_state_and_referrals(const Slapi_DN *sdn, const char *mtn_state,
+ const RUV *ruv, char **ruv_referrals,
+ char **other_referrals);
+void repl_set_repl_plugin_path(const char *path);
+
+/* repl5_updatedn_list.c */
+/* typedef void *ReplicaUpdateDNList; */
+/* typedef int (*FNEnumDN)(Slapi_DN *dn, void *arg); */
+ReplicaUpdateDNList replica_updatedn_list_new(const Slapi_Entry *entry);
+void replica_updatedn_list_free(ReplicaUpdateDNList list);
+void replica_updatedn_list_replace(ReplicaUpdateDNList list, const Slapi_ValueSet *vs);
+void replica_updatedn_list_delete(ReplicaUpdateDNList list, const Slapi_ValueSet *vs);
+void replica_updatedn_list_add(ReplicaUpdateDNList list, const Slapi_ValueSet *vs);
+PRBool replica_updatedn_list_ismember(ReplicaUpdateDNList list, const Slapi_DN *dn);
+char *replica_updatedn_list_to_string(ReplicaUpdateDNList list, const char *delimiter);
+void replica_updatedn_list_enumerate(ReplicaUpdateDNList list, FNEnumDN fn, void *arg);
+
+
+/* windows_private.c */
+PRBool windows_private_dirsync_has_more(const Repl_Agmt *ra);
+
+/* windows_protocol.c */
+int add_or_modify_user(Slapi_Entry *e);
+
+
+void repl5_set_debug_timeout(const char *val);
+
+typedef struct windows_mtnode_extension
+{
+ Object *replica;
+} windows_mtnode_extension;
+
+
+
+
+
+#endif /* _WINDOWSREPL5_H_ */
+
diff --git a/ldap/servers/slapd/back-ldbm/import-threads.c b/ldap/servers/slapd/back-ldbm/import-threads.c
index 489668b7..198dedb7 100644
--- a/ldap/servers/slapd/back-ldbm/import-threads.c
+++ b/ldap/servers/slapd/back-ldbm/import-threads.c
@@ -1501,12 +1501,13 @@ static int bulk_import_queue(ImportJob *job, Slapi_Entry *entry, int flag_block)
{
struct backentry *ep = NULL, *old_ep = NULL;
int idx;
- ID id = job->lead_ID + 1;
+ ID id = 0;
Slapi_Attr *attr = NULL;
size_t newesize = 0;
PR_Lock(job->wire_lock);
-
+ /* Let's do this inside the lock !*/
+ id = job->lead_ID + 1;
/* generate uniqueid if necessary */
import_generate_uniqueid(job, entry);
@@ -1544,6 +1545,11 @@ static int bulk_import_queue(ImportJob *job, Slapi_Entry *entry, int flag_block)
DS_Sleep(PR_MillisecondsToInterval(import_sleep_time));
else
{
+ /* DBBD: Argh -- why not just block, what's the benefit to this ?? */
+ /* I think that to support pipelining in the transport, we need to block here, */
+ /* Otherwise evil things could happen where we say we're busy for operation N, but */
+ /* Not for operation N+1, but the sender doesn't find out about this until after sending */
+ /* Operation N+2 etc. Seems possible to end up with children processed before parents which won't work. */
PR_Unlock(job->wire_lock);
return LDAP_BUSY;
}
@@ -1678,8 +1684,10 @@ int ldbm_back_wire_import(Slapi_PBlock *pb)
/* silently skip */
return 0;
}
+ /* These days, we don't want to return LDAP_BUSY (it makes pipelineing impossible
+ and actually doesn't achieve anything anyway). So we pass '1' for the block flag. */
return bulk_import_queue(job, pb->pb_import_entry,
- job->flags & FLAG_USE_FILES);
+ 1);
}
thread = job->main_thread;
diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c
index 8fc6e533..38115fbe 100644
--- a/ldap/servers/slapd/connection.c
+++ b/ldap/servers/slapd/connection.c
@@ -583,13 +583,14 @@ typedef enum {read_data, write_data, new_connection} work_type;
static int wait_on_new_work(Connection **ppConn, work_type *type);
static int issue_new_read(Connection *conn);
static int finished_chomping(Connection *conn);
-static int read_the_data(Connection *op, int *process_op);
+static int read_the_data(Connection *op, int *process_op, int *defer_io, int *defer_pushback);
static int is_new_operation(Connection *conn);
static int process_operation(Connection *conn, Operation *op);
static int connection_operation_new(Connection *conn, Operation **ppOp);
Operation *get_current_op(Connection *conn);
static int handle_read_data(Connection *conn,Operation **op,
int * connection_referenced);
+int queue_pushed_back_data(Connection *conn);
static void inc_op_count(Connection* conn)
{
@@ -703,7 +704,10 @@ static int handle_read_data(Connection *conn,Operation **op,
int * connection_referenced)
{
int return_value = 0;
+ int return_value2 = 0;
int process_op = 0; /* Do we or do we not process a complete operation now ? */
+ int defer_io = 0;
+ int defer_pushback = 0;
if (is_new_operation(conn)) {
return_value = connection_operation_new(conn,op);
@@ -719,16 +723,31 @@ static int handle_read_data(Connection *conn,Operation **op,
return return_value;
}
- return_value = read_the_data(conn,&process_op);
+ return_value = read_the_data(conn,&process_op, &defer_io, &defer_pushback);
if (0 == return_value) {
+ int replication_session = conn->c_isreplication_session;
if (0 != process_op)
return_value = process_operation(conn,*op);
+ /* Post any pending I/O operation _after_ processing any operation */
+ if (replication_session) {
+ /* Initiate any deferred I/O here */
+ if (defer_io) {
+ return_value2 = issue_new_read(conn);
+ }
+ if (defer_pushback) {
+ return_value2 = queue_pushed_back_data(conn);
+ }
+ }
}
else
*connection_referenced = 1;
- return return_value;
+ if (return_value) {
+ return return_value;
+ } else {
+ return return_value2;
+ }
}
/* Function which does the work involved in servicing an LDAP operation. */
@@ -849,6 +868,7 @@ struct Conn_private {
char *c_buffer;
DWORD c_number_of_async_bytes_read;
DWORD c_buffer_offset;
+ DWORD c_deferred_length;
#else
#endif
/* Now the platform independent part */
@@ -937,13 +957,14 @@ static int remove_from_select_set(Connection *conn)
static HANDLE completion_port = INVALID_HANDLE_VALUE;
#define COMPKEY_DIE ((DWORD) -1L) /* used to kill off workers */
-static int push_back_data(Connection *conn, size_t offset, size_t length);
+static void push_back_data(Connection *conn, size_t offset, size_t length);
+static int queue_pushed_back_data(Connection *conn);
/* Called when we've read from the completion queue, so there's data
* waiting for us to pickup. We're told: the number of bytes read, the
* address of the buffer, the state of this connection (new op, middle of op).
*/
-static int read_the_data(Connection *conn, int *process_op)
+static int read_the_data(Connection *conn, int *process_op, int *defer_io, int *defer_pushback)
{
Conn_private *priv = conn->c_private;
Operation *op = NULL;
@@ -954,6 +975,9 @@ static int read_the_data(Connection *conn, int *process_op)
unsigned long ber_len = 0;
unsigned long Bytes_Scanned = 0;
+ *defer_io = 0;
+ *defer_pushback = 0;
+
op = priv->c_current_op;
Bytes_Read = priv->c_number_of_async_bytes_read;
Buffer = priv->c_buffer + priv->c_buffer_offset;
@@ -1051,21 +1075,33 @@ static int read_the_data(Connection *conn, int *process_op)
*/
return (0);
}
- if ((return_value = push_back_data(conn,priv->c_overlapped.Offset + Bytes_Scanned,
- Bytes_Read-Bytes_Scanned)) == -1) {
- /* MAB: 25 jan 01 we need to decrement the conn refcnt before leaving... Otherwise,
- * this thread will unbalance the ref_cnt inc and dec for this connection
- * and the result is that the connection is never closed and instead is kept
- * forever an never released -> this was causing a fd starvation on NT
- */
- connection_decrement_reference(conn);
- LDAPDebug(LDAP_DEBUG_CONNS,
- "push_back_data failed: closing conn %d fd=%d\n",
- conn->c_connid,conn->c_sd,0);
+ push_back_data(conn,priv->c_overlapped.Offset + Bytes_Scanned,Bytes_Read-Bytes_Scanned);
+ if (!conn->c_isreplication_session) {
+ if ((return_value = queue_pushed_back_data(conn)) == -1) {
+ /* MAB: 25 jan 01 we need to decrement the conn refcnt before leaving... Otherwise,
+ * this thread will unbalance the ref_cnt inc and dec for this connection
+ * and the result is that the connection is never closed and instead is kept
+ * forever an never released -> this was causing a fd starvation on NT
+ */
+ connection_decrement_reference(conn);
+ LDAPDebug(LDAP_DEBUG_CONNS,
+ "push_back_data failed: closing conn %d fd=%d\n",
+ conn->c_connid,conn->c_sd,0);
+ }
+ } else {
+ /* Queue the I/O later to serialize */
+ *defer_pushback = 1;
+ return_value = 0;
}
} else {
priv->c_overlapped.Offset = 0;
- return_value = issue_new_read(conn);
+ if (!conn->c_isreplication_session) {
+ return_value = issue_new_read(conn);
+ } else {
+ /* Queue the I/O later to serialize */
+ *defer_io = 1;
+ return_value = 0;
+ }
}
} else {
/* SSL */
@@ -1089,14 +1125,19 @@ static int read_the_data(Connection *conn, int *process_op)
return return_value;
}
+
+void push_back_data(Connection *conn, size_t offset, size_t length)
+{
+ conn->c_private->c_overlapped.Offset = offset;
+ conn->c_private->c_deferred_length = length;
+}
-int push_back_data(Connection *conn, size_t offset, size_t length)
+int queue_pushed_back_data(Connection *conn)
{
/* Use PostQueuedCompletionStatus() to push the data back up the pipe */
BOOL return_bool = FALSE;
- conn->c_private->c_overlapped.Offset = offset;
- return_bool = PostQueuedCompletionStatus(completion_port,length,(DWORD)conn,&conn->c_private->c_overlapped);
+ return_bool = PostQueuedCompletionStatus(completion_port,conn->c_private->c_deferred_length,(DWORD)conn,&conn->c_private->c_overlapped);
if (return_bool) {
return 0;
@@ -1306,7 +1347,8 @@ int connection_activity(Connection *conn)
/* MAB: 25 Jan 01: let's return on error and pray this won't leak */
return (-1);
}
- return push_back_data(conn, 0, 1);
+ push_back_data(conn, 0, 1);
+ return queue_pushed_back_data(conn);
}
static int finished_chomping(Connection *conn)
@@ -1889,6 +1931,7 @@ connection_threadmain()
int thread_turbo_flag = 0;
int ret = 0;
int more_data = 0;
+ int replication_connection = 0; /* If this connection is from a replication supplier, we want to ensure that operation processing is serialized */
#if defined( OSF1 ) || defined( hpux )
/* Arrange to ignore SIGPIPE signals. */
@@ -1992,8 +2035,12 @@ connection_threadmain()
* Similarly, if we are in turbo mode, don't send the socket
* back to the poll set.
* more_data: [blackflag 624234]
+ * If the connection is from a replication supplier, don't make it readable here.
+ * We want to ensure that replication operations are processed strictly in the order
+ * they are received off the wire.
*/
- if (tag != LDAP_REQ_UNBIND && (!thread_turbo_flag) && !more_data) {
+ replication_connection = conn->c_isreplication_session;
+ if (tag != LDAP_REQ_UNBIND && (!thread_turbo_flag) && !more_data && !replication_connection) {
connection_make_readable(conn);
}
@@ -2056,7 +2103,8 @@ done:
}
PR_Unlock( conn->c_mutex );
}
- if (1 == is_timedout && !more_data)
+ /* Since we didn't do so earlier, we need to make a replication connection readable again here */
+ if ( ((1 == is_timedout) || (replication_connection && !thread_turbo_flag)) && !more_data)
connection_make_readable(conn);
pb = NULL;
diff --git a/ldap/servers/slapd/libslapd.def b/ldap/servers/slapd/libslapd.def
index eee9cc2e..6c79ed9e 100644
--- a/ldap/servers/slapd/libslapd.def
+++ b/ldap/servers/slapd/libslapd.def
@@ -1145,3 +1145,4 @@ EXPORTS
sasl_map_init @1178
sasl_map_done @1179
slapd_SECITEM_FreeItem @1180
+ slapi_op_type_to_string @1181
diff --git a/ldap/servers/slapd/operation.c b/ldap/servers/slapd/operation.c
index 6f72bd4d..bb5058ed 100644
--- a/ldap/servers/slapd/operation.c
+++ b/ldap/servers/slapd/operation.c
@@ -198,6 +198,30 @@ slapi_op_get_type(Slapi_Operation *op)
return op->o_params.operation_type;
}
+char *
+slapi_op_type_to_string(unsigned long type)
+{
+ switch (type)
+ {
+ case SLAPI_OPERATION_ADD:
+ return "add";
+ case SLAPI_OPERATION_DELETE:
+ return "delete";
+ case SLAPI_OPERATION_MODIFY:
+ return "modify";
+ case SLAPI_OPERATION_MODRDN:
+ return "modrdn";
+ case SLAPI_OPERATION_BIND:
+ return "bind";
+ case SLAPI_OPERATION_COMPARE:
+ return "compare";
+ case SLAPI_OPERATION_SEARCH:
+ return "search";
+ default:
+ return "unknown operation type";
+ }
+}
+
/* DEPRECATED : USE FUNCTION ABOVE FOR NEW DVLPT */
unsigned long
operation_get_type(Slapi_Operation *op)
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
index e9e4a15a..9d28954d 100644
--- a/ldap/servers/slapd/slapi-plugin.h
+++ b/ldap/servers/slapd/slapi-plugin.h
@@ -445,7 +445,7 @@ int slapi_op_reserved(Slapi_PBlock *pb);
void slapi_operation_set_csngen_handler ( Slapi_Operation *op, void *callback );
void slapi_operation_set_replica_attr_handler ( Slapi_Operation *op, void *callback );
int slapi_operation_get_replica_attr ( Slapi_PBlock *pb, Slapi_Operation *op, const char *type, void *value );
-
+char *slapi_op_type_to_string(unsigned long type);
/*
* LDAPMod manipulation routines
diff --git a/ldap/synctools/passwordsync/README.txt b/ldap/synctools/passwordsync/README.txt
new file mode 100644
index 00000000..5caa65a0
--- /dev/null
+++ b/ldap/synctools/passwordsync/README.txt
@@ -0,0 +1,2 @@
+1. Download Wix and unzip into Wix folder.
+2. run build.bat. \ No newline at end of file
diff --git a/ldap/synctools/passwordsync/build.bat b/ldap/synctools/passwordsync/build.bat
new file mode 100644
index 00000000..323e6288
--- /dev/null
+++ b/ldap/synctools/passwordsync/build.bat
@@ -0,0 +1,27 @@
+@echo off
+cd passsync
+nmake passsync.mak
+copy Debug\passsync.exe ..\Wix
+cd ..\passhook
+nmake passhook.mak
+copy Debug\passhook.dll ..\Wix
+
+if EXIST ..\Wix (goto :WIX)
+
+goto :EOF
+:WIX
+
+cd ..\Wix
+if NOT EXIST candle.exe (
+ echo ERROR: Wix not properly installed. See readme.
+ pause
+ exit 1 )
+
+if NOT EXIST light.exe (
+ echo ERROR: Wix not properly installed. See readme.
+ pause
+ exit 1 )
+
+candle PassSync.wxs
+light PassSync.wixobj
+
diff --git a/ldap/synctools/passwordsync/passhand.cpp b/ldap/synctools/passwordsync/passhand.cpp
new file mode 100644
index 00000000..23725e78
--- /dev/null
+++ b/ldap/synctools/passwordsync/passhand.cpp
@@ -0,0 +1,122 @@
+// Created: 2-8-2005
+// Author(s): Scott Bridges
+#include "passhand.h"
+
+PasswordHandler::PasswordHandler()
+{
+}
+
+PasswordHandler::~PasswordHandler()
+{
+}
+
+int PasswordHandler::SaveSet(char* filename)
+{
+ fstream outFile;
+ list<USER_PASS_PAIR>::iterator currentPair;
+
+ outFile.open(filename, ios::out | ios::binary);
+
+ if(!outFile.is_open())
+ {
+ return -1;
+ }
+
+ for(currentPair = userPassPairs.begin(); currentPair != userPassPairs.end(); currentPair++)
+ {
+ outFile.write((char*)&currentPair->username.Length, sizeof(currentPair->username.Length));
+ outFile.write((char*)currentPair->username.Buffer, currentPair->username.Length);
+
+ outFile.write((char*)&currentPair->password.Length, sizeof(currentPair->password.Length));
+ outFile.write((char*)currentPair->password.Buffer, currentPair->password.Length);
+ }
+
+ // ToDo: Zero out memory.
+ userPassPairs.clear();
+
+ return 0;
+}
+
+int PasswordHandler::LoadSet(char* filename)
+{
+ fstream inFile;
+ USER_PASS_PAIR newPair;
+
+ inFile.open(filename, ios::in | ios::binary);
+
+ if(!inFile.is_open())
+ {
+ return -1;
+ }
+
+ while(!inFile.eof())
+ {
+ inFile.read((char*)&newPair.username.Length, sizeof(newPair.username.Length));
+ newPair.username.Buffer = (unsigned short*)malloc(newPair.username.Length);
+ inFile.read((char*)newPair.username.Buffer, newPair.username.Length);
+ newPair.username.MaximumLength = newPair.username.Length;
+
+ inFile.read((char*)&newPair.password.Length, sizeof(newPair.password.Length));
+ newPair.password.Buffer = (unsigned short*)malloc(newPair.password.Length);
+ inFile.read((char*)newPair.password.Buffer, newPair.password.Length);
+ newPair.password.MaximumLength = newPair.password.Length;
+
+ if(!inFile.eof())
+ {
+ userPassPairs.push_back(newPair);
+ }
+ }
+
+ return 0;
+}
+
+int PasswordHandler::PushUserPass(PUNICODE_STRING username, PUNICODE_STRING password)
+{
+ USER_PASS_PAIR newPair;
+
+ newPair.username.Length = username->Length;
+ newPair.username.Buffer = (unsigned short*)malloc(username->Length);
+ memcpy(newPair.username.Buffer, username->Buffer, username->Length);
+ newPair.username.MaximumLength = newPair.username.Length;
+
+ newPair.password.Length = password->Length;
+ newPair.password.Buffer = (unsigned short*)malloc(password->Length);
+ memcpy(newPair.password.Buffer, password->Buffer, password->Length);
+ newPair.password.MaximumLength = newPair.password.Length;
+
+ userPassPairs.push_back(newPair);
+
+ return 0;
+}
+
+int PasswordHandler::PeekUserPass(PUNICODE_STRING username, PUNICODE_STRING password)
+{
+ list<USER_PASS_PAIR>::iterator currentPair;
+
+ if(userPassPairs.size() == 0)
+ {
+ return -1;
+ }
+
+ currentPair = userPassPairs.begin();
+
+ username->Length = currentPair->username.Length;
+ username->Buffer = (unsigned short*)malloc(username->Length);
+ memcpy(username->Buffer, currentPair->username.Buffer, username->Length);
+ username->MaximumLength = username->Length;
+
+ password->Length = currentPair->password.Length;
+ password->Buffer = (unsigned short*)malloc(password->Length);
+ memcpy(password->Buffer, currentPair->password.Buffer, password->Length);
+ password->MaximumLength = password->Length;
+
+ return 0;
+}
+
+int PasswordHandler::PopUserPass()
+{
+ // ToDo: Zero out memory.
+ userPassPairs.pop_front();
+
+ return 0;
+} \ No newline at end of file
diff --git a/ldap/synctools/passwordsync/passhand.h b/ldap/synctools/passwordsync/passhand.h
new file mode 100644
index 00000000..03b5f630
--- /dev/null
+++ b/ldap/synctools/passwordsync/passhand.h
@@ -0,0 +1,40 @@
+// Created: 2-8-2005
+// Author(s): Scott Bridges
+#ifndef _PASSHAND_H_
+#define _PASSHAND_H_
+
+#include <windows.h>
+#include <ntsecapi.h>
+#include <fstream>
+#include <list>
+
+#define PASSHAND_EVENT_NAME "passhand_event"
+
+#define STRSTREAM_BUF_SIZE 1024
+
+using namespace std;
+
+struct USER_PASS_PAIR
+{
+ UNICODE_STRING username;
+ UNICODE_STRING password;
+};
+
+class PasswordHandler
+{
+public:
+ PasswordHandler();
+ ~PasswordHandler();
+
+ //WritePassToStorage(PUNICODE_STRING username, PUNICODE_STRING password);
+ //ReadPassFromStorage(PUNICODE_STRING username, PUNICODE_STRING password);
+ int SaveSet(char* filename);
+ int LoadSet(char* filename);
+ int PushUserPass(PUNICODE_STRING username, PUNICODE_STRING password);
+ int PeekUserPass(PUNICODE_STRING username, PUNICODE_STRING password);
+ int PopUserPass();
+private:
+ list<USER_PASS_PAIR> userPassPairs;
+};
+
+#endif \ No newline at end of file
diff --git a/ldap/synctools/passwordsync/passhook/passhook.cpp b/ldap/synctools/passwordsync/passhook/passhook.cpp
new file mode 100644
index 00000000..ff49d575
--- /dev/null
+++ b/ldap/synctools/passwordsync/passhook/passhook.cpp
@@ -0,0 +1,47 @@
+// Created: 2-8-2005
+// Author(s): Scott Bridges
+#include <windows.h>
+#include <ntsecapi.h>
+#include "../passhand.h"
+
+#ifndef STATUS_SUCCESS
+#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
+#endif
+
+NTSTATUS NTAPI PasswordChangeNotify(
+ PUNICODE_STRING UserName,
+ ULONG RelativeId,
+ PUNICODE_STRING Password)
+{
+ PasswordHandler ourPasswordHandler;
+ HANDLE passhookEventHandle = OpenEvent(EVENT_MODIFY_STATE, FALSE, PASSHAND_EVENT_NAME);
+
+ ourPasswordHandler.LoadSet("passhook.dat");
+ ourPasswordHandler.PushUserPass(UserName, Password);
+ ourPasswordHandler.SaveSet("passhook.dat");
+
+ if(passhookEventHandle == NULL)
+ {
+ // ToDo: Generate event sync service not running.
+ }
+ else
+ {
+ SetEvent(passhookEventHandle);
+ }
+
+ return STATUS_SUCCESS;
+}
+
+BOOL NTAPI PasswordFilter(
+ PUNICODE_STRING UserName,
+ PUNICODE_STRING FullName,
+ PUNICODE_STRING Password,
+ BOOL SetOperation)
+{
+ return TRUE;
+}
+
+BOOL NTAPI InitializeChangeNotify()
+{
+ return TRUE;
+} \ No newline at end of file
diff --git a/ldap/synctools/passwordsync/passhook/passhook.def b/ldap/synctools/passwordsync/passhook/passhook.def
new file mode 100644
index 00000000..eeddeeea
--- /dev/null
+++ b/ldap/synctools/passwordsync/passhook/passhook.def
@@ -0,0 +1,6 @@
+
+LIBRARY passhook
+EXPORTS
+ InitializeChangeNotify
+ PasswordChangeNotify
+ PasswordFilter
diff --git a/ldap/synctools/passwordsync/passhook/passhook.dep b/ldap/synctools/passwordsync/passhook/passhook.dep
new file mode 100644
index 00000000..5eb1b6b5
--- /dev/null
+++ b/ldap/synctools/passwordsync/passhook/passhook.dep
@@ -0,0 +1,9 @@
+# Microsoft Developer Studio Generated Dependency File, included by passhook.mak
+
+..\passhand.cpp : \
+ "..\passhand.h"\
+
+
+.\passhook.cpp : \
+ "..\passhand.h"\
+
diff --git a/ldap/synctools/passwordsync/passhook/passhook.dsp b/ldap/synctools/passwordsync/passhook/passhook.dsp
new file mode 100644
index 00000000..385972a3
--- /dev/null
+++ b/ldap/synctools/passwordsync/passhook/passhook.dsp
@@ -0,0 +1,117 @@
+# Microsoft Developer Studio Project File - Name="passhook" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=passhook - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "passhook.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "passhook.mak" CFG="passhook - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "passhook - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "passhook - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "passhook - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PASSHOOK_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PASSHOOK_EXPORTS" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
+
+!ELSEIF "$(CFG)" == "passhook - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PASSHOOK_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PASSHOOK_EXPORTS" /YX /FD /GZ /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "passhook - Win32 Release"
+# Name "passhook - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=..\passhand.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\passhook.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\passhook.def
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=..\passhand.h
+# End Source File
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/ldap/synctools/passwordsync/passhook/passhook.mak b/ldap/synctools/passwordsync/passhook/passhook.mak
new file mode 100644
index 00000000..7f24e3ca
--- /dev/null
+++ b/ldap/synctools/passwordsync/passhook/passhook.mak
@@ -0,0 +1,207 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on passhook.dsp
+!IF "$(CFG)" == ""
+CFG=passhook - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to passhook - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "passhook - Win32 Release" && "$(CFG)" != "passhook - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "passhook.mak" CFG="passhook - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "passhook - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "passhook - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "passhook - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+ALL : "$(OUTDIR)\passhook.dll"
+
+
+CLEAN :
+ -@erase "$(INTDIR)\passhand.obj"
+ -@erase "$(INTDIR)\passhook.obj"
+ -@erase "$(INTDIR)\vc60.idb"
+ -@erase "$(OUTDIR)\passhook.dll"
+ -@erase "$(OUTDIR)\passhook.exp"
+ -@erase "$(OUTDIR)\passhook.lib"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PASSHOOK_EXPORTS" /Fp"$(INTDIR)\passhook.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "NDEBUG" /mktyplib203 /win32
+RSC=rc.exe
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\passhook.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /incremental:no /pdb:"$(OUTDIR)\passhook.pdb" /machine:I386 /def:".\passhook.def" /out:"$(OUTDIR)\passhook.dll" /implib:"$(OUTDIR)\passhook.lib"
+DEF_FILE= \
+ ".\passhook.def"
+LINK32_OBJS= \
+ "$(INTDIR)\passhand.obj" \
+ "$(INTDIR)\passhook.obj"
+
+"$(OUTDIR)\passhook.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+!ELSEIF "$(CFG)" == "passhook - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+ALL : "$(OUTDIR)\passhook.dll"
+
+
+CLEAN :
+ -@erase "$(INTDIR)\passhand.obj"
+ -@erase "$(INTDIR)\passhook.obj"
+ -@erase "$(INTDIR)\vc60.idb"
+ -@erase "$(INTDIR)\vc60.pdb"
+ -@erase "$(OUTDIR)\passhook.dll"
+ -@erase "$(OUTDIR)\passhook.exp"
+ -@erase "$(OUTDIR)\passhook.ilk"
+ -@erase "$(OUTDIR)\passhook.lib"
+ -@erase "$(OUTDIR)\passhook.pdb"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PASSHOOK_EXPORTS" /Fp"$(INTDIR)\passhook.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /GZ /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+MTL=midl.exe
+MTL_PROJ=/nologo /D "_DEBUG" /mktyplib203 /win32
+RSC=rc.exe
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\passhook.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /incremental:yes /pdb:"$(OUTDIR)\passhook.pdb" /debug /machine:I386 /def:".\passhook.def" /out:"$(OUTDIR)\passhook.dll" /implib:"$(OUTDIR)\passhook.lib" /pdbtype:sept
+DEF_FILE= \
+ ".\passhook.def"
+LINK32_OBJS= \
+ "$(INTDIR)\passhand.obj" \
+ "$(INTDIR)\passhook.obj"
+
+"$(OUTDIR)\passhook.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("passhook.dep")
+!INCLUDE "passhook.dep"
+!ELSE
+!MESSAGE Warning: cannot find "passhook.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "passhook - Win32 Release" || "$(CFG)" == "passhook - Win32 Debug"
+SOURCE=..\passhand.cpp
+
+"$(INTDIR)\passhand.obj" : $(SOURCE) "$(INTDIR)"
+ $(CPP) $(CPP_PROJ) $(SOURCE)
+
+
+SOURCE=.\passhook.cpp
+
+"$(INTDIR)\passhook.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/ldap/synctools/passwordsync/passsync.dsw b/ldap/synctools/passwordsync/passsync.dsw
new file mode 100644
index 00000000..f20d73a0
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync.dsw
@@ -0,0 +1,41 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
+
+###############################################################################
+
+Project: "passhook"=".\passhook\passhook.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "passsync"=".\passsync\passsync.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
diff --git a/ldap/synctools/passwordsync/passsync/dssynch.h b/ldap/synctools/passwordsync/passsync/dssynch.h
new file mode 100644
index 00000000..f594cccd
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync/dssynch.h
@@ -0,0 +1,39 @@
+/**
+ * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+ * license terms. Copyright © 2001 Sun Microsystems, Inc.
+ * Some preexisting portions Copyright © 2001 Netscape Communications Corp.
+ * All rights reserved.
+ */
+/***********************************************************************
+**
+** Copyright 1996 - Netscape Communications Corporation
+**
+** NAME
+** DSSynch.h
+**
+** DESCRIPTION
+** Exported name of Directory Synchronization Service
+**
+** AUTHOR
+** Rob Weltman <rweltman@netscape.com>
+**
+***********************************************************************/
+
+#ifndef _DSSYNCH_H_
+#define _DSSYNCH_H_
+
+#define PLUGIN_STATE_UNKNOWN 0
+#define PLUGIN_STATE_DISABLED 1
+#define PLUGIN_STATE_ENABLED 2
+
+#if defined(_UNICODE)
+#define DS_SERVICE_NAME _T("Netscape Directory Synchronization Service")
+#else
+#define DS_SERVICE_NAME "Netscape Directory Synchronization Service"
+#endif
+#define DS_SERVICE_NAME_UNI L"Netscape Directory Synchronization Service"
+#define DS_EVENT_NAME TEXT("Netscape DirSynch")
+#define DSS_TERM_EVENT TEXT("NS_DSSYNCH")
+#define SYNCH_VERSION "5.0"
+
+#endif // _DSSYNCH_H_
diff --git a/ldap/synctools/passwordsync/passsync/dssynchmsg.h b/ldap/synctools/passwordsync/passsync/dssynchmsg.h
new file mode 100644
index 00000000..b3d2753e
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync/dssynchmsg.h
@@ -0,0 +1,604 @@
+//
+// Values are 32 bit values layed out as follows:
+//
+// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
+// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+// +---+-+-+-----------------------+-------------------------------+
+// |Sev|C|R| Facility | Code |
+// +---+-+-+-----------------------+-------------------------------+
+//
+// where
+//
+// Sev - is the severity code
+//
+// 00 - Success
+// 01 - Informational
+// 10 - Warning
+// 11 - Error
+//
+// C - is the Customer code flag
+//
+// R - is a reserved bit
+//
+// Facility - is the facility code
+//
+// Code - is the facility's status code
+//
+//
+// Define the facility codes
+//
+
+
+//
+// Define the severity codes
+//
+
+
+//
+// MessageId: EVMSG_INSTALLED
+//
+// MessageText:
+//
+// The %1 service was installed.
+//
+#define EVMSG_INSTALLED 0x00000064L
+
+//
+// MessageId: EVMSG_REMOVED
+//
+// MessageText:
+//
+// The %1 service was removed.
+//
+#define EVMSG_REMOVED 0x00000065L
+
+//
+// MessageId: EVMSG_NOTREMOVED
+//
+// MessageText:
+//
+// The %1 service could not be removed.
+//
+#define EVMSG_NOTREMOVED 0x00000066L
+
+//
+// MessageId: EVMSG_CTRLHANDLERNOTINSTALLED
+//
+// MessageText:
+//
+// The control handler could not be installed.
+//
+#define EVMSG_CTRLHANDLERNOTINSTALLED 0x00000067L
+
+//
+// MessageId: EVMSG_FAILEDINIT
+//
+// MessageText:
+//
+// The initialization process failed.
+//
+#define EVMSG_FAILEDINIT 0x00000068L
+
+//
+// MessageId: EVMSG_STARTED
+//
+// MessageText:
+//
+// The service was started.
+//
+#define EVMSG_STARTED 0x00000069L
+
+//
+// MessageId: EVMSG_RESTART
+//
+// MessageText:
+//
+// The service was restarted.
+//
+#define EVMSG_RESTART 0x0000006AL
+
+//
+// MessageId: EVMSG_BADREQUEST
+//
+// MessageText:
+//
+// The service received an unsupported request.
+//
+#define EVMSG_BADREQUEST 0x0000006BL
+
+//
+// MessageId: EVMSG_SECURITYREGISTER
+//
+// MessageText:
+//
+// The service has registered for security events.
+//
+#define EVMSG_SECURITYREGISTER 0x0000006CL
+
+//
+// MessageId: EVMSG_FAILEDNOTIFY
+//
+// MessageText:
+//
+// The service failed on waiting for security event.
+//
+#define EVMSG_FAILEDNOTIFY 0x0000006DL
+
+//
+// MessageId: EVMSG_SECURITYNOTIFY
+//
+// MessageText:
+//
+// There was a security event.
+//
+#define EVMSG_SECURITYNOTIFY 0x0000006EL
+
+//
+// MessageId: EVMSG_FAILEDSECURITYNOTIFY
+//
+// MessageText:
+//
+// Failure waiting for security event involving Users.
+//
+#define EVMSG_FAILEDSECURITYNOTIFY 0x0000006FL
+
+//
+// MessageId: EVMSG_SECURITYAUDITINGENABLED
+//
+// MessageText:
+//
+// Auditing of User and Group Management events has been enabled.
+//
+#define EVMSG_SECURITYAUDITINGENABLED 0x00000070L
+
+//
+// MessageId: EVMSG_SECURITYAUDITINGDISABLED
+//
+// MessageText:
+//
+// Auditing of User and Group Management events has been turned off.
+// Notification-based monitoring of user changes has terminated.
+//
+#define EVMSG_SECURITYAUDITINGDISABLED 0x00000071L
+
+//
+// MessageId: EVMSG_FAILEDBINDDS
+//
+// MessageText:
+//
+// Failed to bind to Directory Service on %1 at %2.
+//
+#define EVMSG_FAILEDBINDDS 0x00000072L
+
+//
+// MessageId: EVMSG_BINDDS
+//
+// MessageText:
+//
+// Connected to Directory Service on %1 at %2.
+//
+#define EVMSG_BINDDS 0x00000073L
+
+//
+// MessageId: EVMSG_FAILEDGETNTUSERS
+//
+// MessageText:
+//
+// Failed to get user information from NT SAM.
+//
+#define EVMSG_FAILEDGETNTUSERS 0x00000074L
+
+//
+// MessageId: EVMSG_FAILEDGETDSUSERS
+//
+// MessageText:
+//
+// Failed to get user information from Directory Server.
+//
+#define EVMSG_FAILEDGETDSUSERS 0x00000075L
+
+//
+// MessageId: EVMSG_FAILEDSETDSTIMESTAMPS
+//
+// MessageText:
+//
+// Failed to set initial time stamps of NT users on Directory Server.
+//
+#define EVMSG_FAILEDSETDSTIMESTAMPS 0x00000076L
+
+//
+// MessageId: EVMSG_FAILEDGETNTUSERINFO
+//
+// MessageText:
+//
+// Failed to get user information for <%1> from NT.
+//
+#define EVMSG_FAILEDGETNTUSERINFO 0x00000077L
+
+//
+// MessageId: EVMSG_FAILEDADDDSUSER
+//
+// MessageText:
+//
+// Failed to add user <%1> to Directory Server.
+//
+#define EVMSG_FAILEDADDDSUSER 0x00000078L
+
+//
+// MessageId: EVMSG_FAILEDMODIFYDSUSER
+//
+// MessageText:
+//
+// Failed to modify user <%1> on Directory Server.
+//
+#define EVMSG_FAILEDMODIFYDSUSER 0x00000079L
+
+//
+// MessageId: EVMSG_FAILEDDELETEDSUSER
+//
+// MessageText:
+//
+// Failed to delete user <%1> on Directory Server.
+//
+#define EVMSG_FAILEDDELETEDSUSER 0x0000007AL
+
+//
+// MessageId: EVMSG_FAILEDADDNTUSER
+//
+// MessageText:
+//
+// Failed to add user <%1> to NT.
+//
+#define EVMSG_FAILEDADDNTUSER 0x0000007BL
+
+//
+// MessageId: EVMSG_FAILEDMODIFYNTUSER
+//
+// MessageText:
+//
+// Failed to modify user <%1> in NT.
+//
+#define EVMSG_FAILEDMODIFYNTUSER 0x0000007CL
+
+//
+// MessageId: EVMSG_FAILEDDELETENTUSER
+//
+// MessageText:
+//
+// Failed to delete user <%1> in NT.
+//
+#define EVMSG_FAILEDDELETENTUSER 0x0000007DL
+
+//
+// MessageId: EVMSG_FAILEDREADEVENTLOG
+//
+// MessageText:
+//
+// Failed reading Security Event Log.
+//
+#define EVMSG_FAILEDREADEVENTLOG 0x0000007EL
+
+//
+// MessageId: EVMSG_FAILEDCOMMANDOPEN
+//
+// MessageText:
+//
+// Failed opening command socket at port %1.
+//
+#define EVMSG_FAILEDCOMMANDOPEN 0x0000007FL
+
+//
+// MessageId: EVMSG_FAILEDCOMMANDREAD
+//
+// MessageText:
+//
+// Failed reading from command socket.
+//
+#define EVMSG_FAILEDCOMMANDREAD 0x00000080L
+
+//
+// MessageId: EVMSG_FAILEDCOMMANDWRITE
+//
+// MessageText:
+//
+// Failed writing to command socket.
+//
+#define EVMSG_FAILEDCOMMANDWRITE 0x00000081L
+
+//
+// MessageId: EVMSG_FAILEDGETKEY
+//
+// MessageText:
+//
+// Failed to get registry key <%1>.
+//
+#define EVMSG_FAILEDGETKEY 0x00000082L
+
+//
+// MessageId: EVMSG_FAILEDREADREGVALUE
+//
+// MessageText:
+//
+// Failed to read registry value <%1>.
+//
+#define EVMSG_FAILEDREADREGVALUE 0x00000083L
+
+//
+// MessageId: EVMSG_FAILEDWRITEREGVALUE
+//
+// MessageText:
+//
+// Failed to write registry value <%1>.
+//
+#define EVMSG_FAILEDWRITEREGVALUE 0x00000084L
+
+//
+// MessageId: EVMSG_FAILEDSAVESTATUS
+//
+// MessageText:
+//
+// Failed to save status to registry.
+//
+#define EVMSG_FAILEDSAVESTATUS 0x00000085L
+
+//
+// MessageId: EVMSG_UserNotFound
+//
+// MessageText:
+//
+// The user name could not be found.
+//
+#define EVMSG_UserNotFound 0x00000086L
+
+//
+// MessageId: EVMSG_UserExists
+//
+// MessageText:
+//
+// The user account already exists.
+//
+#define EVMSG_UserExists 0x00000087L
+
+//
+// MessageId: EVMSG_PasswordTooShort
+//
+// MessageText:
+//
+// The password is shorter than required.
+//
+#define EVMSG_PasswordTooShort 0x00000088L
+
+//
+// MessageId: EVMSG_NameTooLong
+//
+// MessageText:
+//
+// The user name %1 is too long.
+//
+#define EVMSG_NameTooLong 0x00000089L
+
+//
+// MessageId: EVMSG_PasswordHistConflict
+//
+// MessageText:
+//
+// This password cannot be used now.
+//
+#define EVMSG_PasswordHistConflict 0x0000008AL
+
+//
+// MessageId: EVMSG_InvalidDatabase
+//
+// MessageText:
+//
+// The security database is corrupted.
+//
+#define EVMSG_InvalidDatabase 0x0000008BL
+
+//
+// MessageId: EVMSG_NetworkError
+//
+// MessageText:
+//
+// A general network error occurred.
+//
+#define EVMSG_NetworkError 0x0000008CL
+
+//
+// MessageId: EVMSG_BadUsername
+//
+// MessageText:
+//
+// The user name or group name parameter is invalid.
+//
+#define EVMSG_BadUsername 0x0000008DL
+
+//
+// MessageId: EVMSG_UserUnkownError
+//
+// MessageText:
+//
+// Unexpected error - number %1.
+//
+#define EVMSG_UserUnkownError 0x0000008EL
+
+//
+// MessageId: EVMSG_DEBUG
+//
+// MessageText:
+//
+// Debug: %1
+//
+#define EVMSG_DEBUG 0x0000008FL
+
+//
+// MessageId: EVMSG_STOPPED
+//
+// MessageText:
+//
+// The service was stopped.
+//
+#define EVMSG_STOPPED 0x00000090L
+
+//
+// MessageId: EVMSG_UIDNOTDUPLICATED
+//
+// MessageText:
+//
+// The UID "%1" already exists in the subtree and has not been duplicated.
+//
+#define EVMSG_UIDNOTDUPLICATED 0x00000091L
+
+//
+// MessageId: EVMSG_UIDDUPLICATED
+//
+// MessageText:
+//
+// The UID "%1" already exists in the subtree but HAS been duplicated because the user RDN component requires it. Further action may need to be taken if there are systems using the Directory Server which require unique UIDs.
+//
+#define EVMSG_UIDDUPLICATED 0x00000092L
+
+//
+// MessageId: EVMSG_FAILEDCONTROLOPEN
+//
+// MessageText:
+//
+// Failed opening control message socket at port %1.
+//
+#define EVMSG_FAILEDCONTROLOPEN 0x00000093L
+
+//
+// MessageId: EVMSG_FAILEDPREOPOPEN
+//
+// MessageText:
+//
+// Failed opening preoperation processing socket at port %1.
+//
+#define EVMSG_FAILEDPREOPOPEN 0x00000094L
+
+//
+// MessageId: EVMSG_FAILEDPOSTOPEN
+//
+// MessageText:
+//
+// Failed opening postoperation processing socket at port %1.
+//
+#define EVMSG_FAILEDPOSTOPEN 0x00000095L
+
+//
+// MessageId: EVMSG_FAILEDCONTROLWRITE
+//
+// MessageText:
+//
+// Failed writing to control message socket at port %1.
+//
+#define EVMSG_FAILEDCONTROLWRITE 0x00000096L
+
+//
+// MessageId: EVMSG_FAILEDPREOPWRITE
+//
+// MessageText:
+//
+// Failed writing to preoperation processing socket at port %1.
+//
+#define EVMSG_FAILEDPREOPWRITE 0x00000097L
+
+//
+// MessageId: EVMSG_FAILEDPOSTOPWRITE
+//
+// MessageText:
+//
+// Failed writing to postoperation processing socket at port %1.
+//
+#define EVMSG_FAILEDPOSTOPWRITE 0x00000098L
+
+//
+// MessageId: EVMSG_FAILEDCONTROLREAD
+//
+// MessageText:
+//
+// Failed reading from control message socket at port %1.
+//
+#define EVMSG_FAILEDCONTROLREAD 0x00000099L
+
+//
+// MessageId: EVMSG_FAILEDPREOPREAD
+//
+// MessageText:
+//
+// Failed reading from preoperation processing socket at port %1.
+//
+#define EVMSG_FAILEDPREOPREAD 0x0000009AL
+
+//
+// MessageId: EVMSG_FAILEDPOSTOPREAD
+//
+// MessageText:
+//
+// Failed reading from postoperation processing socket at port %1.
+//
+#define EVMSG_FAILEDPOSTOPREAD 0x0000009BL
+
+//
+// MessageId: EVMSG_FAILEDCONTROLBADBASE
+//
+// MessageText:
+//
+// The subtree "%1" is invalid.
+//
+#define EVMSG_FAILEDCONTROLBADBASE 0x0000009CL
+
+//
+// MessageId: EVMSG_FAILEDGETNTGROUPINFO
+//
+// MessageText:
+//
+// Failed to get group information for <%1> from NT.
+//
+#define EVMSG_FAILEDGETNTGROUPINFO 0x0000009DL
+
+//
+// MessageId: EVMSG_FAILEDADDDSGROUP
+//
+// MessageText:
+//
+// Failed to add group <%1> to Directory Server.
+//
+#define EVMSG_FAILEDADDDSGROUP 0x0000009EL
+
+//
+// MessageId: EVMSG_FAILEDMODIFYDSGROUP
+//
+// MessageText:
+//
+// Failed to modify group <%1> on Directory Server.
+//
+#define EVMSG_FAILEDMODIFYDSGROUP 0x0000009FL
+
+//
+// MessageId: EVMSG_FAILEDDELETEDSGROUP
+//
+// MessageText:
+//
+// Failed to delete group <%1> on Directory Server.
+//
+#define EVMSG_FAILEDDELETEDSGROUP 0x000000A0L
+
+//
+// MessageId: EVMSG_FAILEDCONTROLDUPTREE
+//
+// MessageText:
+//
+// Synch Service control connection rejected by Directory Server, duplicate users base <%1> or groups base <%2>.
+//
+#define EVMSG_FAILEDCONTROLDUPTREE 0x000000A1L
+
+//
+// MessageId: EVMSG_FAILEDCONTROLSETUP
+//
+// MessageText:
+//
+// Synch Service control connection rejected by Directory Server, users base <%1>, groups base <%2>.
+//
+#define EVMSG_FAILEDCONTROLSETUP 0x000000A2L
+
diff --git a/ldap/synctools/passwordsync/passsync/ntservice.cpp b/ldap/synctools/passwordsync/passsync/ntservice.cpp
new file mode 100644
index 00000000..6d0f1151
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync/ntservice.cpp
@@ -0,0 +1,576 @@
+/***********************************************************************
+**
+** Copyright 1996 - Netscape Communications Corporation
+**
+** NAME
+** NTService.cpp
+**
+** DESCRIPTION
+** Base class for NT Service app
+**
+** AUTHOR
+** Rob Weltman <rweltman@netscape.com>
+**
+***********************************************************************/
+
+/***********************************************************************
+** Includes
+***********************************************************************/
+// Removed: 2-8-2005
+//#include "sysplat.h"
+// Added: 2-8-2005
+#include <stdio.h>
+// End Change
+
+#include <tchar.h>
+#include <time.h>
+#include "NTService.h"
+// Remove: 2-8-2005
+//#include "uniutil.h"
+// End Change
+#include "dssynchmsg.h"
+
+// static variables
+CNTService* CNTService::m_pThis = NULL;
+
+CNTService::CNTService(const TCHAR* szServiceName)
+{
+ // copy the address of the current object so we can access it from
+ // the static member callback functions.
+ // WARNING: This limits the application to only one CNTService object.
+ m_pThis = this;
+
+ // Set the default service name and version
+ _tcsncpy(m_szServiceName, szServiceName, sizeof(m_szServiceName)-1);
+ m_iMajorVersion = 1;
+ m_iMinorVersion = 0;
+ m_hEventSource = NULL;
+
+ // set up the initial service status
+ m_hServiceStatus = NULL;
+ m_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ m_Status.dwCurrentState = SERVICE_STOPPED;
+ m_Status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+ m_Status.dwWin32ExitCode = 0;
+ m_Status.dwServiceSpecificExitCode = 0;
+ m_Status.dwCheckPoint = 0;
+ m_Status.dwWaitHint = 0;
+ m_bIsRunning = FALSE;
+}
+
+CNTService::~CNTService()
+{
+ DebugMsg(_T("CNTService::~CNTService()"));
+ if (m_hEventSource) {
+ ::DeregisterEventSource(m_hEventSource);
+ m_hEventSource = NULL;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// Default command line argument parsing
+
+// Returns TRUE if it found an arg it recognised, FALSE if not
+// Note: processing some arguments causes output to stdout to be generated.
+BOOL CNTService::ParseStandardArgs(int argc, char* argv[])
+{
+ // See if we have any command line args we recognise
+ if (argc <= 1) return FALSE;
+
+ if (_stricmp(argv[1], "-v") == 0) {
+
+ // Spit out version info
+ _tprintf(_T("%s Version %d.%d\n"),
+ m_szServiceName, m_iMajorVersion, m_iMinorVersion);
+ _tprintf(_T("The service is %s installed\n"),
+ IsInstalled() ? _T("currently") : _T("not"));
+ return TRUE; // say we processed the argument
+
+ } else if (_stricmp(argv[1], "-i") == 0) {
+
+ // Request to install.
+ if (IsInstalled()) {
+ _tprintf(_T("%s is already installed\n"), m_szServiceName);
+ } else {
+ // Try and install the copy that's running
+ if (Install()) {
+ _tprintf(_T("%s installed\n"), m_szServiceName);
+ } else {
+ _tprintf(_T("%s failed to install. Error %d\n"),
+ m_szServiceName, GetLastError());
+ }
+ }
+ return TRUE; // say we processed the argument
+
+ } else if (_stricmp(argv[1], "-u") == 0) {
+
+ // Request to uninstall.
+ if (!IsInstalled()) {
+ _tprintf(_T("%s is not installed\n"), m_szServiceName);
+ } else {
+ // Try and remove the copy that's installed
+ if (Uninstall()) {
+ // Get the executable file path
+ TCHAR szFilePath[_MAX_PATH];
+ ::GetModuleFileName(NULL, szFilePath, sizeof(szFilePath));
+ _tprintf(_T("%s removed. (You must delete the file (%s) yourself.)\n"),
+ m_szServiceName, szFilePath);
+ } else {
+ _tprintf(_T("Could not remove %s. Error %d\n"),
+ m_szServiceName, GetLastError());
+ }
+ }
+ return TRUE; // say we processed the argument
+
+ }
+
+ // Don't recognise the args
+ return FALSE;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// Install/uninstall routines
+
+// Test if the service is currently installed
+BOOL CNTService::IsInstalled()
+{
+ BOOL bResult = FALSE;
+
+ // Open the Service Control Manager
+ SC_HANDLE hSCM = ::OpenSCManager(NULL, // local machine
+ NULL, // ServicesActive database
+ SC_MANAGER_ALL_ACCESS); // full access
+ if (hSCM) {
+
+ // Try to open the service
+ SC_HANDLE hService = ::OpenService(hSCM,
+ m_szServiceName,
+ SERVICE_QUERY_CONFIG);
+ if (hService) {
+ bResult = TRUE;
+ ::CloseServiceHandle(hService);
+ }
+
+ ::CloseServiceHandle(hSCM);
+ }
+
+ return bResult;
+}
+
+BOOL CNTService::Install()
+{
+ // Open the Service Control Manager
+ SC_HANDLE hSCM = ::OpenSCManager(NULL, // local machine
+ NULL, // ServicesActive database
+ SC_MANAGER_ALL_ACCESS); // full access
+ if (!hSCM) return FALSE;
+
+ // Get the executable file path
+ TCHAR szFilePath[_MAX_PATH];
+ ::GetModuleFileName(NULL, szFilePath, sizeof(szFilePath)/sizeof(*szFilePath));
+
+ // Create the service
+ SC_HANDLE hService = ::CreateService(hSCM,
+ m_szServiceName,
+ m_szServiceName,
+ SERVICE_ALL_ACCESS,
+ SERVICE_WIN32_OWN_PROCESS,
+ SERVICE_DEMAND_START, // start condition
+ SERVICE_ERROR_NORMAL,
+ szFilePath,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!hService) {
+ ::CloseServiceHandle(hSCM);
+ return FALSE;
+ }
+
+ // make registry entries to support logging messages
+ // Add the source name as a subkey under the Application
+ // key in the EventLog service portion of the registry.
+ TCHAR szKey[256];
+ HKEY hKey = NULL;
+ _tcscpy(szKey, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\"));
+ _tcscat(szKey, GetEventName());
+ if (::RegCreateKey(HKEY_LOCAL_MACHINE, szKey, &hKey) != ERROR_SUCCESS) {
+ ::CloseServiceHandle(hService);
+ ::CloseServiceHandle(hSCM);
+ return FALSE;
+ }
+
+ // Add the Event ID message-file name to the 'EventMessageFile' subkey.
+ ::RegSetValueEx(hKey,
+ _T("EventMessageFile"),
+ 0,
+ REG_EXPAND_SZ,
+ (CONST BYTE*)szFilePath,
+ (_tcslen(szFilePath) + 1)*sizeof(*szFilePath));
+
+ // Set the supported types flags.
+ DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
+ ::RegSetValueEx(hKey,
+ _T("TypesSupported"),
+ 0,
+ REG_DWORD,
+ (CONST BYTE*)&dwData,
+ sizeof(DWORD));
+ ::RegCloseKey(hKey);
+
+ LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_INSTALLED, m_szServiceName);
+
+ // tidy up
+ ::CloseServiceHandle(hService);
+ ::CloseServiceHandle(hSCM);
+ return TRUE;
+}
+
+BOOL CNTService::Uninstall()
+{
+ // Open the Service Control Manager
+ SC_HANDLE hSCM = ::OpenSCManager(NULL, // local machine
+ NULL, // ServicesActive database
+ SC_MANAGER_ALL_ACCESS); // full access
+ if (!hSCM) return FALSE;
+
+ BOOL bResult = FALSE;
+ SC_HANDLE hService = ::OpenService(hSCM,
+ m_szServiceName,
+ DELETE);
+ if (hService) {
+ // Stop it if it is running
+ SERVICE_STATUS serviceStatus;
+ BOOL bStop = ControlService( hService, SERVICE_CONTROL_STOP,
+ &serviceStatus );
+ if (::DeleteService(hService)) {
+ LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_REMOVED, m_szServiceName);
+ bResult = TRUE;
+ } else {
+ LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_NOTREMOVED, m_szServiceName);
+ }
+ ::CloseServiceHandle(hService);
+ }
+
+ ::CloseServiceHandle(hSCM);
+ return bResult;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////
+// Logging functions
+
+// This function makes an entry into the application event log
+void CNTService::LogEvent(WORD wType, DWORD dwID,
+ const wchar_t* pszS1,
+ const wchar_t* pszS2,
+ const wchar_t* pszS3)
+{
+#ifndef _DEBUG
+ if ( EVMSG_DEBUG == dwID )
+ return;
+#endif
+ const wchar_t* ps[3];
+ ps[0] = pszS1;
+ ps[1] = pszS2;
+ ps[2] = pszS3;
+
+ int iStr = 0;
+ for (int i = 0; i < 3; i++) {
+ if (ps[i] != NULL) iStr++;
+ }
+
+ // Check the event source has been registered and if
+ // not then register it now
+ if (!m_hEventSource) {
+ TCHAR *name = GetEventName();
+// Modification: 2-8-2005
+// m_hEventSource = ::RegisterEventSourceW(NULL, // local machine
+// GetEventName()); // source name
+ m_hEventSource = ::RegisterEventSourceW(NULL, // local machine
+ (const unsigned short *)GetEventName()); // source name
+// End Change
+ }
+
+ if (m_hEventSource) {
+ ::ReportEventW(m_hEventSource,
+ wType,
+ 0,
+ dwID,
+ NULL, // sid
+ iStr,
+ 0,
+ ps,
+ NULL);
+ }
+}
+
+// This function makes an entry into the application event log
+void CNTService::LogEvent(WORD wType, DWORD dwID,
+ const char* pszS1,
+ const char* pszS2,
+ const char* pszS3)
+{
+ wchar_t *p1 = pszS1 ? StrToUnicode( pszS1 ) : NULL;
+ wchar_t *p2 = pszS2 ? StrToUnicode( pszS2 ) : NULL;
+ wchar_t *p3 = pszS3 ? StrToUnicode( pszS3 ) : NULL;
+ LogEvent( wType, dwID, p1, p2, p3 );
+ if ( p1 )
+ free( p1 );
+ if ( p2 )
+ free( p2 );
+ if ( p3 )
+ free( p3 );
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// Service startup and registration
+
+BOOL CNTService::StartService()
+{
+ CNTService* pService = m_pThis;
+ SERVICE_TABLE_ENTRY st[] = {
+ {m_szServiceName, ServiceMain},
+ {NULL, NULL}
+ };
+
+ DebugMsg(_T("Calling StartServiceCtrlDispatcher()"));
+ // Fails if started from command line, but StartService
+ // works any way
+ BOOL b = ::StartServiceCtrlDispatcher(st);
+ DWORD err = GetLastError();
+ DebugMsg(_T("Returned from StartServiceCtrlDispatcher()"));
+ return b;
+}
+
+BOOL CNTService::StartServiceDirect()
+{
+ BOOL b = FALSE;
+
+ // Open the Service Control Manager
+ SC_HANDLE hSCM = ::OpenSCManager(NULL, // local machine
+ NULL, // ServicesActive database
+ SC_MANAGER_ALL_ACCESS); // full access
+ if (!hSCM) return FALSE;
+ SC_HANDLE hService = ::OpenService(hSCM,
+ m_szServiceName,
+ SERVICE_START);
+ if (hService)
+ {
+ DebugMsg(_T("Calling StartServiceDirect()"));
+ b = ::StartService( hService, 0, NULL );
+ ::CloseServiceHandle(hService);
+ }
+ ::CloseServiceHandle(hSCM);
+
+ return b;
+}
+
+// static member function (callback)
+void CNTService::ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv)
+{
+ // Get a pointer to the C++ object
+ CNTService* pService = m_pThis;
+
+ pService->DebugMsg(_T("Entering CNTService::ServiceMain()"));
+ // Register the control request handler
+ pService->m_Status.dwCurrentState = SERVICE_START_PENDING;
+ pService->m_hServiceStatus = RegisterServiceCtrlHandler(pService->m_szServiceName,
+ Handler);
+ if (pService->m_hServiceStatus == NULL) {
+ pService->LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_CTRLHANDLERNOTINSTALLED);
+ return;
+ }
+
+ // Start the initialisation
+ if (pService->Initialize()) {
+
+ // Do the real work.
+ // When the Run function returns, the service has stopped.
+ pService->m_bIsRunning = TRUE;
+ pService->m_Status.dwWin32ExitCode = 0;
+ pService->m_Status.dwCheckPoint = 0;
+ pService->m_Status.dwWaitHint = 0;
+ pService->Run();
+ }
+
+ // Tell the service manager we are stopped
+ pService->SetStatus(SERVICE_STOPPED);
+
+ pService->DebugMsg(_T("Leaving CNTService::ServiceMain()"));
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+// status functions
+
+void CNTService::SetStatus(DWORD dwState)
+{
+ DebugMsg(_T("CNTService::SetStatus(%lu, %lu)"), m_hServiceStatus, dwState);
+ m_Status.dwCurrentState = dwState;
+ ::SetServiceStatus(m_hServiceStatus, &m_Status);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+// Service initialization
+
+BOOL CNTService::Initialize()
+{
+ DebugMsg(_T("Entering CNTService::Initialize()"));
+
+ // Start the initialization
+ SetStatus(SERVICE_START_PENDING);
+
+ // Perform the actual initialization
+ BOOL bResult = OnInit();
+
+ // Set final state
+ m_Status.dwWin32ExitCode = GetLastError();
+ m_Status.dwCheckPoint = 0;
+ m_Status.dwWaitHint = 0;
+ if (!bResult) {
+ LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_FAILEDINIT);
+ SetStatus(SERVICE_STOPPED);
+ return FALSE;
+ }
+
+ LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_STARTED);
+ SetStatus(SERVICE_RUNNING);
+
+ DebugMsg(_T("Leaving CNTService::Initialize()"));
+ return TRUE;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+// main function to do the real work of the service
+
+// This function performs the main work of the service.
+// When this function returns the service has stopped.
+void CNTService::Run()
+{
+ DebugMsg(_T("Entering CNTService::Run()"));
+
+ while (m_bIsRunning) {
+ DebugMsg(_T("Sleeping..."));
+ Sleep(5000);
+ }
+
+ // nothing more to do
+ DebugMsg(_T("Leaving CNTService::Run()"));
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+// Control request handlers
+
+// static member function (callback) to handle commands from the
+// service control manager
+void CNTService::Handler(DWORD dwOpcode)
+{
+ // Get a pointer to the object
+ CNTService* pService = m_pThis;
+
+ pService->DebugMsg(_T("CNTService::Handler(%lu)"), dwOpcode);
+ switch (dwOpcode) {
+ case SERVICE_CONTROL_STOP: // 1
+ pService->SetStatus(SERVICE_STOP_PENDING);
+ pService->OnStop();
+ pService->m_bIsRunning = FALSE;
+ pService->LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_STOPPED);
+ if (pService->m_hEventSource) {
+ ::DeregisterEventSource(pService->m_hEventSource);
+ pService->m_hEventSource = NULL;
+ }
+
+ break;
+
+ case SERVICE_CONTROL_PAUSE: // 2
+ pService->OnPause();
+ break;
+
+ case SERVICE_CONTROL_CONTINUE: // 3
+ pService->OnContinue();
+ break;
+
+ case SERVICE_CONTROL_INTERROGATE: // 4
+ pService->OnInterrogate();
+ break;
+
+ case SERVICE_CONTROL_SHUTDOWN: // 5
+ pService->OnShutdown();
+ break;
+
+ default:
+ if (dwOpcode >= SERVICE_CONTROL_USER) {
+ if (!pService->OnUserControl(dwOpcode)) {
+ pService->LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_BADREQUEST);
+ }
+ } else {
+ pService->LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_BADREQUEST);
+ }
+ break;
+ }
+
+ // Report current status
+ pService->DebugMsg(_T("Updating status (%lu, %lu)"),
+ pService->m_hServiceStatus,
+ pService->m_Status.dwCurrentState);
+ ::SetServiceStatus(pService->m_hServiceStatus, &pService->m_Status);
+}
+
+// Called when the service is first initialized
+BOOL CNTService::OnInit()
+{
+ DebugMsg(_T("CNTService::OnInit()"));
+ return TRUE;
+}
+
+// Called when the service control manager wants to stop the service
+void CNTService::OnStop()
+{
+ DebugMsg(_T("CNTService::OnStop()"));
+}
+
+// called when the service is interrogated
+void CNTService::OnInterrogate()
+{
+ DebugMsg(_T("CNTService::OnInterrogate()"));
+}
+
+// called when the service is paused
+void CNTService::OnPause()
+{
+ DebugMsg(_T("CNTService::OnPause()"));
+}
+
+// called when the service is continued
+void CNTService::OnContinue()
+{
+ DebugMsg(_T("CNTService::OnContinue()"));
+}
+
+// called when the service is shut down
+void CNTService::OnShutdown()
+{
+ DebugMsg(_T("CNTService::OnShutdown()"));
+}
+
+// called when the service gets a user control message
+BOOL CNTService::OnUserControl(DWORD dwOpcode)
+{
+ DebugMsg(_T("CNTService::OnUserControl(%8.8lXH)"), dwOpcode);
+ return FALSE; // say not handled
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// Debugging support
+
+void CNTService::DebugMsg(const TCHAR* pszFormat, ...)
+{
+ TCHAR buf[1024];
+ _stprintf(buf, _T("[%s](%lu): "), m_szServiceName, GetCurrentThreadId());
+ va_list arglist;
+ va_start(arglist, pszFormat);
+ _vstprintf(&buf[_tcslen(buf)], pszFormat, arglist);
+ va_end(arglist);
+ _tcscat(buf, _T("\n"));
+ OutputDebugString(buf);
+}
diff --git a/ldap/synctools/passwordsync/passsync/ntservice.h b/ldap/synctools/passwordsync/passsync/ntservice.h
new file mode 100644
index 00000000..38233ab1
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync/ntservice.h
@@ -0,0 +1,80 @@
+/***********************************************************************
+**
+** Copyright 1996 - Netscape Communications Corporation
+**
+** NAME
+** NTService.h
+**
+** DESCRIPTION
+**
+**
+** AUTHOR
+** Rob Weltman <rweltman@netscape.com>
+**
+***********************************************************************/
+
+#ifndef _NTSERVICE_H_
+#define _NTSERVICE_H_
+
+// Added: 2-8-2005
+#include <windows.h>
+#include "subuniutil.h"
+// End Change
+
+// #include "dssynchmsg.h" // Event message ids
+
+#define SERVICE_CONTROL_USER 128
+
+class CNTService
+{
+public:
+ CNTService(const TCHAR* szServiceName);
+ virtual ~CNTService();
+ BOOL ParseStandardArgs(int argc, char* argv[]);
+ BOOL IsInstalled();
+ BOOL Install();
+ BOOL Uninstall();
+ void LogEvent(WORD wType, DWORD dwID,
+ const wchar_t* pszS1 = NULL,
+ const wchar_t* pszS2 = NULL,
+ const wchar_t* pszS3 = NULL);
+ void LogEvent(WORD wType, DWORD dwID,
+ const char* pszS1,
+ const char* pszS2 = NULL,
+ const char* pszS3 = NULL);
+ BOOL StartService();
+ BOOL StartServiceDirect();
+ void SetStatus(DWORD dwState);
+ BOOL Initialize();
+ virtual void Run();
+ virtual BOOL OnInit();
+ virtual void OnStop();
+ virtual void OnInterrogate();
+ virtual void OnPause();
+ virtual void OnContinue();
+ virtual void OnShutdown();
+ virtual BOOL OnUserControl(DWORD dwOpcode);
+ virtual TCHAR *GetEventName() { return m_szServiceName; }
+ void DebugMsg(const TCHAR* pszFormat, ...);
+
+ // static member functions
+ static void WINAPI ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv);
+ static void WINAPI Handler(DWORD dwOpcode);
+
+ // data members
+ TCHAR m_szServiceName[64];
+ int m_iMajorVersion;
+ int m_iMinorVersion;
+ SERVICE_STATUS_HANDLE m_hServiceStatus;
+ SERVICE_STATUS m_Status;
+ BOOL IsRunning() { return m_bIsRunning; }
+
+ // static data
+ static CNTService* m_pThis; // nasty hack to get object ptr
+
+private:
+ HANDLE m_hEventSource;
+ BOOL m_bIsRunning;
+};
+
+#endif // _NTSERVICE_H_
diff --git a/ldap/synctools/passwordsync/passsync/passsync.dep b/ldap/synctools/passwordsync/passsync/passsync.dep
new file mode 100644
index 00000000..36cca3c7
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync/passsync.dep
@@ -0,0 +1,32 @@
+# Microsoft Developer Studio Generated Dependency File, included by passsync.mak
+
+.\ntservice.cpp : \
+ ".\dssynchmsg.h"\
+ ".\ntservice.h"\
+ ".\subuniutil.h"\
+
+
+..\passhand.cpp : \
+ "..\passhand.h"\
+
+
+.\service.cpp : \
+ "..\passhand.h"\
+ ".\dssynch.h"\
+ ".\dssynchmsg.h"\
+ ".\ntservice.h"\
+ ".\subuniutil.h"\
+ ".\synchcmds.h"\
+ ".\syncserv.h"\
+
+
+.\subuniutil.cpp : \
+ ".\subuniutil.h"\
+
+
+.\syncserv.cpp : \
+ "..\passhand.h"\
+ ".\ntservice.h"\
+ ".\subuniutil.h"\
+ ".\syncserv.h"\
+
diff --git a/ldap/synctools/passwordsync/passsync/passsync.dsp b/ldap/synctools/passwordsync/passsync/passsync.dsp
new file mode 100644
index 00000000..e4d8c4de
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync/passsync.dsp
@@ -0,0 +1,145 @@
+# Microsoft Developer Studio Project File - Name="passsync" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=passsync - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "passsync.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "passsync.mak" CFG="passsync - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "passsync - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "passsync - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "passsync - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+
+!ELSEIF "$(CFG)" == "passsync - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "passsync___Win32_Debug"
+# PROP BASE Intermediate_Dir "passsync___Win32_Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 nsldapssl32v50.lib nsldap32v50.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "passsync - Win32 Release"
+# Name "passsync - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\ntservice.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=..\passhand.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\service.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\subuniutil.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\syncserv.cpp
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=.\dssynch.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\dssynchmsg.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\ntservice.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\passhand.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\subuniutil.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\synchcmds.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\syncserv.h
+# End Source File
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/ldap/synctools/passwordsync/passsync/passsync.mak b/ldap/synctools/passwordsync/passsync/passsync.mak
new file mode 100644
index 00000000..58ed9269
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync/passsync.mak
@@ -0,0 +1,222 @@
+# Microsoft Developer Studio Generated NMAKE File, Based on passsync.dsp
+!IF "$(CFG)" == ""
+CFG=passsync - Win32 Debug
+!MESSAGE No configuration specified. Defaulting to passsync - Win32 Debug.
+!ENDIF
+
+!IF "$(CFG)" != "passsync - Win32 Release" && "$(CFG)" != "passsync - Win32 Debug"
+!MESSAGE Invalid configuration "$(CFG)" specified.
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "passsync.mak" CFG="passsync - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "passsync - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "passsync - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+!ERROR An invalid configuration is specified.
+!ENDIF
+
+!IF "$(OS)" == "Windows_NT"
+NULL=
+!ELSE
+NULL=nul
+!ENDIF
+
+!IF "$(CFG)" == "passsync - Win32 Release"
+
+OUTDIR=.\Release
+INTDIR=.\Release
+# Begin Custom Macros
+OutDir=.\Release
+# End Custom Macros
+
+ALL : "$(OUTDIR)\passsync.exe"
+
+
+CLEAN :
+ -@erase "$(INTDIR)\ntservice.obj"
+ -@erase "$(INTDIR)\passhand.obj"
+ -@erase "$(INTDIR)\service.obj"
+ -@erase "$(INTDIR)\subuniutil.obj"
+ -@erase "$(INTDIR)\syncserv.obj"
+ -@erase "$(INTDIR)\vc60.idb"
+ -@erase "$(OUTDIR)\passsync.exe"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /ML /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Fp"$(INTDIR)\passsync.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+RSC=rc.exe
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\passsync.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /incremental:no /pdb:"$(OUTDIR)\passsync.pdb" /machine:I386 /out:"$(OUTDIR)\passsync.exe"
+LINK32_OBJS= \
+ "$(INTDIR)\ntservice.obj" \
+ "$(INTDIR)\passhand.obj" \
+ "$(INTDIR)\service.obj" \
+ "$(INTDIR)\subuniutil.obj" \
+ "$(INTDIR)\syncserv.obj"
+
+"$(OUTDIR)\passsync.exe" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+!ELSEIF "$(CFG)" == "passsync - Win32 Debug"
+
+OUTDIR=.\Debug
+INTDIR=.\Debug
+# Begin Custom Macros
+OutDir=.\Debug
+# End Custom Macros
+
+ALL : "$(OUTDIR)\passsync.exe"
+
+
+CLEAN :
+ -@erase "$(INTDIR)\ntservice.obj"
+ -@erase "$(INTDIR)\passhand.obj"
+ -@erase "$(INTDIR)\service.obj"
+ -@erase "$(INTDIR)\subuniutil.obj"
+ -@erase "$(INTDIR)\syncserv.obj"
+ -@erase "$(INTDIR)\vc60.idb"
+ -@erase "$(INTDIR)\vc60.pdb"
+ -@erase "$(OUTDIR)\passsync.exe"
+ -@erase "$(OUTDIR)\passsync.ilk"
+ -@erase "$(OUTDIR)\passsync.pdb"
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
+
+CPP=cl.exe
+CPP_PROJ=/nologo /MLd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Fp"$(INTDIR)\passsync.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /GZ /c
+
+.c{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.obj::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.c{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cpp{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+.cxx{$(INTDIR)}.sbr::
+ $(CPP) @<<
+ $(CPP_PROJ) $<
+<<
+
+RSC=rc.exe
+BSC32=bscmake.exe
+BSC32_FLAGS=/nologo /o"$(OUTDIR)\passsync.bsc"
+BSC32_SBRS= \
+
+LINK32=link.exe
+LINK32_FLAGS=nsldapssl32v50.lib nsldap32v50.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /incremental:yes /pdb:"$(OUTDIR)\passsync.pdb" /debug /machine:I386 /out:"$(OUTDIR)\passsync.exe" /pdbtype:sept
+LINK32_OBJS= \
+ "$(INTDIR)\ntservice.obj" \
+ "$(INTDIR)\passhand.obj" \
+ "$(INTDIR)\service.obj" \
+ "$(INTDIR)\subuniutil.obj" \
+ "$(INTDIR)\syncserv.obj"
+
+"$(OUTDIR)\passsync.exe" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
+ $(LINK32) @<<
+ $(LINK32_FLAGS) $(LINK32_OBJS)
+<<
+
+!ENDIF
+
+
+!IF "$(NO_EXTERNAL_DEPS)" != "1"
+!IF EXISTS("passsync.dep")
+!INCLUDE "passsync.dep"
+!ELSE
+!MESSAGE Warning: cannot find "passsync.dep"
+!ENDIF
+!ENDIF
+
+
+!IF "$(CFG)" == "passsync - Win32 Release" || "$(CFG)" == "passsync - Win32 Debug"
+SOURCE=.\ntservice.cpp
+
+"$(INTDIR)\ntservice.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=..\passhand.cpp
+
+"$(INTDIR)\passhand.obj" : $(SOURCE) "$(INTDIR)"
+ $(CPP) $(CPP_PROJ) $(SOURCE)
+
+
+SOURCE=.\service.cpp
+
+"$(INTDIR)\service.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\subuniutil.cpp
+
+"$(INTDIR)\subuniutil.obj" : $(SOURCE) "$(INTDIR)"
+
+
+SOURCE=.\syncserv.cpp
+
+"$(INTDIR)\syncserv.obj" : $(SOURCE) "$(INTDIR)"
+
+
+
+!ENDIF
+
diff --git a/ldap/synctools/passwordsync/passsync/service.cpp b/ldap/synctools/passwordsync/passsync/service.cpp
new file mode 100644
index 00000000..09e0ce68
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync/service.cpp
@@ -0,0 +1,295 @@
+// Created: 2-8-2005
+// Author(s): Scott Bridges
+
+#include <windows.h>
+#include <iostream.h>
+#include "syncserv.h"
+#include "dssynchmsg.h"
+// syncserv.h
+// ntservice.h (modified)
+// ntservice.cpp (modified)
+// sysplat
+// uniutil
+// dssynchmsg
+
+// Copied: 2-10-2005
+// From: ntsynch.cpp
+//#include "sysplat.h"
+
+//#include "NTSynch.h"
+//#include "ldapconn.h"
+//#include "dsperson.h"
+#include "synchcmds.h"
+#include "dssynch.h"
+
+#ifdef _DEBUG
+void doDebug( PassSyncService *pSynch );
+#endif // _DEBUG
+
+//#include <nspr.h>
+
+/////////////////////////////////////////////////////////////////
+static void usage()
+{
+ printf( "DS Synchronization Service version %s\n",
+ SYNCH_VERSION );
+ printf( " -%c NUMBER NT polling interval (minutes)\n",
+ SYNCH_CMD_NT_POLL_INTERVAL );
+// printf( " -%c NUMBER DS polling interval (minutes)\n",
+// SYNCH_CMD_DS_POLL_INTERVAL );
+// printf( " -%c CALENDAR NT update schedule\n",
+// SYNCH_CMD_NT_CALENDAR );
+// printf( " -%c CALENDAR DS update schedule\n",
+// SYNCH_CMD_DS_CALENDAR );
+ printf( " -%c NUMBER NT synchronization start time (minutes)\n",
+ SYNCH_CMD_NT_CALENDAR );
+// printf( " -%c NUMBER DS synchronization start time (minutes)\n",
+// SYNCH_CMD_DS_CALENDAR );
+ printf( " -%c NAME DS distinguished name\n",
+ SYNCH_CMD_ADMIN_DN );
+ printf( " -%c USERS_BASE DS users base\n",
+ SYNCH_CMD_DIRECTORY_USERS_BASE );
+ printf( " -%c GROUPS_BASE DS groups base\n",
+ SYNCH_CMD_DIRECTORY_GROUPS_BASE );
+ printf( " -%c HOST DS host\n",
+ SYNCH_CMD_DS_HOST );
+ printf( " -%c NUMBER DS port\n",
+ SYNCH_CMD_DS_PORT );
+ printf( " -%c PASSWORD DS password\n",
+ SYNCH_CMD_ADMIN_PASSWORD );
+ printf( " -%c NUMBER Command port\n",
+ SYNCH_CMD_NT_PORT );
+ printf( " -v Display this message\n",
+ SYNCH_CMD_NT_POLL_INTERVAL );
+ printf( " -i Install the service\n" );
+ printf( " -u Uninstall the service\n" );
+ printf( " -%c Synchronize all NT users to DS now\n",
+ SYNCH_CMD_SYNCH_FROM_NT );
+// printf( " -%c Synchronize DS users to NT now\n",
+// SYNCH_CMD_SYNCH_FROM_DS );
+ printf( " -%c Resynchronize changes to NT users now\n",
+ SYNCH_CMD_SYNCH_CHANGES );
+ printf( " -%c Load settings from Registry\n",
+ SYNCH_CMD_RELOAD_SETTINGS );
+// printf( "Options -t and -k are contradictory, as are -m and -y\n" );
+}
+
+#define OPT_NONE 0
+#define OPT_START 1
+#define OPT_APP 2
+#define OPT_TERMINATE 3
+#define OPT_START_DIRECT 4
+
+/////////////////////////////////////////////////////////////////
+static int checkOptions( PassSyncService *pSynch, int& argc, char *argv[] )
+{
+ int result = OPT_START; // Default is to start the service
+
+ // Check first for uninstall, since we shouldn't do anything else if set
+ int i;
+ for( i = 1; i < argc; i++ )
+ {
+ if ( !strncmp( argv[i], "-u", 2 ) )
+ {
+ // Uninstall
+ if ( !pSynch->IsInstalled() )
+ wprintf( L"%s is not installed\n", pSynch->m_szServiceName );
+ else
+ {
+ // Try and remove the copy that's installed
+ if ( pSynch->Uninstall() )
+ wprintf( L"%s removed\n", pSynch->m_szServiceName );
+ else
+ wprintf( L"Could not remove %s. Error %d\n",
+ pSynch->m_szServiceName, GetLastError() );
+// pSynch->ClearRegistry();
+ }
+ // Terminate after completion
+ result = OPT_TERMINATE;
+ argc = 1;
+ return result;
+ }
+ }
+
+ // Check command-line arguments
+ for( i = 1; i < argc; )
+ {
+ if ( '-' != argv[i][0] )
+ {
+ i++;
+ continue;
+ }
+ char opt = argv[i][1];
+ BOOL bLocal = FALSE;
+
+ // Usage
+ if ( 'v' == opt )
+ {
+ result = OPT_NONE;
+ usage();
+ bLocal = TRUE;
+ }
+ // Secret option to start as app, not service
+ else if ( 'a' == opt )
+ {
+ result = OPT_APP;
+ bLocal = TRUE;
+ }
+ // Start service
+ else if ( 'x' == opt )
+ {
+ result = OPT_START_DIRECT;
+ bLocal = TRUE;
+ }
+/*
+ // Command port
+ else if ( 'c' == opt )
+ {
+ result = OPT_NONE;
+ if ( i < (argc-1) )
+ {
+ i++;
+ pSynch->SetCommandPort( atoi( argv[i] ) );
+ bLocal = TRUE;
+ }
+ }
+*/
+ // Install
+ else if ( 'i' == opt )
+ {
+ result = OPT_NONE;
+ if ( pSynch->IsInstalled() )
+ printf( "%S is already installed\n", pSynch->m_szServiceName );
+ else
+ {
+ // Try and install the copy that's running
+ if ( pSynch->Install() )
+ {
+ printf( "%S installed\n", pSynch->m_szServiceName );
+ }
+ else
+ {
+ printf( "%S failed to install. Error %d\n",
+ pSynch->m_szServiceName, GetLastError() );
+ }
+ }
+ bLocal = TRUE;
+ }
+ // Synchronize from NT to DS
+ // Terminate after completion
+ else if ( 'n' == opt )
+ {
+ result = OPT_NONE;
+ }
+ // Synchronize from DS to NT
+ // Terminate after completion
+ else if ( 's' == opt )
+ {
+ result = OPT_NONE;
+ }
+ // Synchronize both ways
+ // Terminate after completion
+ else if ( 'r' == opt )
+ {
+ result = OPT_NONE;
+ }
+ if ( bLocal )
+ {
+ for( int j = i; j < (argc-1); j++ )
+ {
+ argv[j] = argv[j+1];
+ }
+ argc--;
+ }
+ else
+ {
+ i++;
+ if ( i >= argc )
+ break;
+ }
+ }
+ return result;
+}
+
+static int initialize( PassSyncService *pSynch, int argc, char *argv[] )
+{
+ // Check command-line arguments
+ for( int i = 1; i < argc; i++ )
+ {
+ if ( '-' != argv[i][0] )
+ continue;
+ char opt = argv[i][1];
+
+// pSynch->argToSynch( opt, argv[i] );
+
+ if ( i >= argc )
+ break;
+ }
+ if ( argc > 1 )
+ {
+ // Save settings to Registry
+// pSynch->SaveConfig();
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////
+
+int
+main( int argc, char *argv[] )
+{
+ // Global single instance
+ PassSyncService theSynch("Password Synchronization Service");
+
+ // Process special install/uninstall switches; this does install/uninstall
+ // It returns non-zero to actually start the service
+ int nStart = checkOptions( &theSynch, argc, argv );
+
+ // Set up configuration
+ if ( OPT_TERMINATE != nStart )
+ initialize( &theSynch, argc, argv );
+
+ // Started by Service Control Manager
+ if ( OPT_START == nStart )
+ {
+ // Start the service; doesn't return until the service is started
+ BOOL bStarted = theSynch.StartService();
+ if ( !bStarted )
+ {
+ printf( "Service could not be started\n" );
+ return(1);
+ }
+ return 0;
+ }
+#if 0
+ // Started from command line
+ else if ( OPT_START_DIRECT == nStart )
+ {
+ // This may fail, but the rest still succeeds
+ BOOL bStarted = theSynch.StartService();
+ bStarted = theSynch.StartServiceDirect();
+ if ( !bStarted )
+ {
+ printf( "Service could not be started\n" );
+ return(1);
+ }
+ return 0;
+ }
+#endif
+ // Secret debugging option - run as app instead of as service
+ else if ( OPT_APP == nStart )
+ {
+ if ( theSynch.OnInit() )
+ theSynch.Run();
+ }
+
+ exit(theSynch.m_Status.dwWin32ExitCode);
+
+
+ ////////// That's it - the rest is debugging stuff //////
+#ifdef _DEBUG
+ doDebug( &theSynch );
+#endif
+ return 0;
+}
diff --git a/ldap/synctools/passwordsync/passsync/subuniutil.cpp b/ldap/synctools/passwordsync/passsync/subuniutil.cpp
new file mode 100644
index 00000000..398e8a76
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync/subuniutil.cpp
@@ -0,0 +1,59 @@
+#include "subuniutil.h"
+
+// Copied: 2-8-2005
+// From: secuniutil.c
+unsigned long
+utf8getcc( const char** src )
+{
+ register unsigned long c;
+ register const unsigned char* s = (const unsigned char*)*src;
+ switch (UTF8len [(*s >> 2) & 0x3F]) {
+ case 0: /* erroneous: s points to the middle of a character. */
+ c = (*s++) & 0x3F; goto more5;
+ case 1: c = (*s++); break;
+ case 2: c = (*s++) & 0x1F; goto more1;
+ case 3: c = (*s++) & 0x0F; goto more2;
+ case 4: c = (*s++) & 0x07; goto more3;
+ case 5: c = (*s++) & 0x03; goto more4;
+ case 6: c = (*s++) & 0x01; goto more5;
+ more5: if ((*s & 0xC0) != 0x80) break; c = (c << 6) | ((*s++) & 0x3F);
+ more4: if ((*s & 0xC0) != 0x80) break; c = (c << 6) | ((*s++) & 0x3F);
+ more3: if ((*s & 0xC0) != 0x80) break; c = (c << 6) | ((*s++) & 0x3F);
+ more2: if ((*s & 0xC0) != 0x80) break; c = (c << 6) | ((*s++) & 0x3F);
+ more1: if ((*s & 0xC0) != 0x80) break; c = (c << 6) | ((*s++) & 0x3F);
+ break;
+ }
+ *src = (const char*)s;
+ return c;
+}
+//
+wchar_t *
+ASCIIToUnicode( const char *buf, wchar_t *uni, int inUnilen )
+ /* Convert the 0-terminated UTF-8 string 'buf' to 0-terminated UCS-2;
+ write the result into uni, truncated (if necessary) to fit in 0..unilen-1. */
+ /* XXX This function should be named UTF8ToUnicode */
+ /* XXX unilen should be size_t, not int */
+{
+ auto size_t unilen = (size_t)inUnilen; /* to get rid of warnings for now */
+ auto size_t i;
+ if (unilen > 0 && buf && uni) {
+ for (i = 0; i < unilen; ++i) {
+ register unsigned long c = utf8getcc( &buf );
+ if (c >= 0xfffeUL) c = 0xfffdUL; /* REPLACEMENT CHARACTER */
+ if (0 == (uni[i] = (wchar_t)c)) break;
+ }
+ if (i >= unilen && unilen > 0) {
+ uni[unilen-1] = 0;
+ }
+ }
+ return uni;
+}
+
+wchar_t *
+StrToUnicode( const char *buf )
+{
+ wchar_t unibuf[1024];
+ ASCIIToUnicode( buf, unibuf, sizeof(unibuf) );
+ return _wcsdup( unibuf );
+}
+// End Copy \ No newline at end of file
diff --git a/ldap/synctools/passwordsync/passsync/subuniutil.h b/ldap/synctools/passwordsync/passsync/subuniutil.h
new file mode 100644
index 00000000..49d71f49
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync/subuniutil.h
@@ -0,0 +1,20 @@
+#ifndef _SUBUNIUTIL_H_
+#define _SUBUNIUTIL_H_
+
+#include <windows.h>
+
+// Copied: 2-8-2005
+// From: secuniutil.c
+/* From ns/netsite/ldap/libraries/libldap/utf8.c */
+static char UTF8len[64]
+= {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5, 6};
+// End Copy
+
+unsigned long utf8getcc( const char** src );
+wchar_t * ASCIIToUnicode( const char *buf, wchar_t *uni, int inUnilen );
+wchar_t * StrToUnicode( const char *buf );
+
+#endif
diff --git a/ldap/synctools/passwordsync/passsync/synchcmds.h b/ldap/synctools/passwordsync/passsync/synchcmds.h
new file mode 100644
index 00000000..0b1c4210
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync/synchcmds.h
@@ -0,0 +1,55 @@
+/**
+ * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
+ * license terms. Copyright © 2001 Sun Microsystems, Inc.
+ * Some preexisting portions Copyright © 2001 Netscape Communications Corp.
+ * All rights reserved.
+ */
+/***********************************************************************
+**
+** Copyright 1996 - Netscape Communications Corporation
+**
+** NAME
+** synchcmds.h
+**
+** DESCRIPTION
+** Commands accepted by DS Synchronization Service
+**
+** AUTHOR
+** Rob Weltman <rweltman@netscape.com>
+**
+***********************************************************************/
+
+#ifndef _SYNCHCMDS_H_
+#define _SYNCHCMDS_H_
+
+#define SYNCH_CMD_NT_POLL_INTERVAL 't'
+#define SYNCH_CMD_DS_POLL_INTERVAL 'm'
+#define SYNCH_CMD_ADMIN_DN 'd'
+#define SYNCH_CMD_ADMIN_PASSWORD 'w'
+#define SYNCH_CMD_DIRECTORY_USERS_BASE 'b'
+#define SYNCH_CMD_DIRECTORY_GROUPS_BASE 'f'
+#define SYNCH_CMD_DS_HOST 'h'
+#define SYNCH_CMD_DS_PORT 'p'
+#define SYNCH_CMD_NT_PORT 'c'
+#define SYNCH_CMD_NT_CALENDAR 'k'
+#define SYNCH_CMD_DS_CALENDAR 'y'
+#define SYNCH_CMD_SYNCH_FROM_NT 'n'
+#define SYNCH_CMD_SYNCH_FROM_DS 's'
+#define SYNCH_CMD_SYNCH_CHANGES 'r'
+#define SYNCH_CMD_RELOAD_SETTINGS 'l'
+
+/* LDAP error codes */
+#define SYNCH_ERR_PARTIAL_RESULTS 0x09
+#define SYNCH_ERR_INVALID_DN_SYNTAX 0x22
+#define SYNCH_ERR_INAPPROPRIATE_AUTH 0x30
+#define SYNCH_ERR_INVALID_CREDENTIALS 0x31
+#define SYNCH_ERR_INSUFFICIENT_ACCESS 0x32
+#define SYNCH_ERR_CONNECT_ERROR 0x5b
+
+#define SYNCH_ERR_INVALID_USERS_BASE 100
+#define SYNCH_ERR_INVALID_GROUPS_BASE 200
+#define SYNCH_ERR_INVALID_UID_UNIQUE_BASE 300
+#define SYNCH_ERR_BAD_CONFIG 400
+
+#endif _SYNCHCMDS_H_
+
diff --git a/ldap/synctools/passwordsync/passsync/syncserv.cpp b/ldap/synctools/passwordsync/passsync/syncserv.cpp
new file mode 100644
index 00000000..800e2977
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync/syncserv.cpp
@@ -0,0 +1,236 @@
+// Created: 2-8-2005
+// Author(s): Scott Bridges
+#include "syncserv.h"
+
+PassSyncService::PassSyncService(const TCHAR *serviceName) : CNTService(serviceName)
+{
+ HKEY regKey;
+ DWORD type;
+ unsigned long size;
+
+ passhandEventHandle = CreateEvent(NULL, FALSE, FALSE, PASSHAND_EVENT_NAME);
+
+ pLdapConnection = NULL;
+ results = NULL;
+ currentResult = NULL;
+ lastLdapError = LDAP_SUCCESS;
+
+ dataFilename = "C:\\WINDOWS\\system32\\passhook.dat";
+ logFilename = NULL;
+ multipleModify = true;
+
+ ldapHostName = (char*)malloc(REG_BUF_SIZE);
+ ldpaHostPort = (char*)malloc(REG_BUF_SIZE);
+ ldalAuthUsername = (char*)malloc(REG_BUF_SIZE);
+ ldapAuthPassword = (char*)malloc(REG_BUF_SIZE);
+ ldapSearchBase = (char*)malloc(REG_BUF_SIZE);
+ ldapUsernameField = (char*)malloc(REG_BUF_SIZE);
+ ldapPasswordField = (char*)malloc(REG_BUF_SIZE);
+
+ RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\PasswordSync", &regKey);
+ size = REG_BUF_SIZE;
+ RegQueryValueEx(regKey, "Host Name", NULL, &type, (unsigned char*)ldapHostName, &size);
+ size = REG_BUF_SIZE;
+ RegQueryValueEx(regKey, "Port Number", NULL, &type, (unsigned char*)ldpaHostPort, &size);
+ size = REG_BUF_SIZE;
+ RegQueryValueEx(regKey, "User Name", NULL, &type, (unsigned char*)ldalAuthUsername, &size);
+ size = REG_BUF_SIZE;
+ RegQueryValueEx(regKey, "Password", NULL, &type, (unsigned char*)ldapAuthPassword, &size);
+ size = REG_BUF_SIZE;
+ RegQueryValueEx(regKey, "Search Base", NULL, &type, (unsigned char*)ldapSearchBase, &size);
+ size = REG_BUF_SIZE;
+ RegQueryValueEx(regKey, "User Name Field", NULL, &type, (unsigned char*)ldapUsernameField, &size);
+ size = REG_BUF_SIZE;
+ RegQueryValueEx(regKey, "Password Field", NULL, &type, (unsigned char*)ldapPasswordField, &size);
+ RegCloseKey(regKey);
+}
+
+PassSyncService::~PassSyncService()
+{
+}
+
+int PassSyncService::SyncPasswords()
+{
+ UNICODE_STRING uUsername;
+ UNICODE_STRING uPassword;
+ char* username;
+ char* password;
+ char* dn;
+
+ if(Connect() < 0)
+ {
+ // ToDo: Generate event connection failure.
+ return -1;
+ }
+
+ ourPasswordHandler.LoadSet(dataFilename);
+
+ while(ourPasswordHandler.PeekUserPass(&uUsername, &uPassword) > -1)
+ {
+
+ username = (char*)malloc(uUsername.Length);
+ password = (char*)malloc(uPassword.Length);
+
+ sprintf(username, "%S", uUsername.Buffer);
+ sprintf(password, "%S", uPassword.Buffer);
+
+ results = NULL;
+ currentResult = NULL;
+ if(QueryUsername(username) < 0)
+ {
+ // ToDo: Generate event search failure.
+ }
+ else
+ {
+ while(dn != NULL)
+ {
+ if(GetDN(&dn) < 0)
+ {
+ // ToDo: Generate event multiple results.
+ }
+ else
+ {
+ if(ModifyPassword(dn, password) < 0)
+ {
+ // ToDo: Generate event modify failure.
+ }
+ else
+ {
+ ourPasswordHandler.PopUserPass();
+ }
+ }
+ }
+ }
+
+ // ToDo: Zero out buffers
+ free(username);
+ free(password);
+ }
+
+ ourPasswordHandler.SaveSet(dataFilename);
+
+ Disconnect();
+
+ return 0;
+}
+
+void PassSyncService::Run()
+{
+ while(true)
+ {
+ WaitForSingleObject(passhandEventHandle, INFINITE);
+ SyncPasswords();
+ ResetEvent(passhandEventHandle);
+ //Sleep(60000);
+ }
+}
+
+int PassSyncService::Connect()
+{
+ pLdapConnection = ldap_init(ldapHostName, atoi(ldpaHostPort));
+
+ lastLdapError = ldap_simple_bind_s(pLdapConnection, ldalAuthUsername, ldapAuthPassword);
+ if(lastLdapError != LDAP_SUCCESS)
+ {
+ // ToDo: Log reason for bind failure.
+ return -1;
+ }
+
+ return 0;
+}
+
+int PassSyncService::Disconnect()
+{
+ ldap_unbind(pLdapConnection);
+
+ pLdapConnection = NULL;
+
+ return 0;
+}
+
+int PassSyncService::QueryUsername(char* username)
+{
+ char* searchFilter = (char*)malloc(strlen(ldapUsernameField) + strlen(username) + 4);
+
+ sprintf(searchFilter, "(%s=%s)", ldapUsernameField, username);
+
+ lastLdapError = ldap_search_ext_s(
+ pLdapConnection,
+ ldapSearchBase,
+ LDAP_SCOPE_ONELEVEL,
+ searchFilter,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ -1,
+ &results);
+
+ free(searchFilter);
+
+ if(lastLdapError != LDAP_SUCCESS)
+ {
+ // ToDo: Log reason for search failure.
+ return -1;
+ }
+
+ return 0;
+}
+
+int PassSyncService::GetDN(char** dn)
+{
+ if(multipleModify)
+ {
+ if(currentResult == NULL)
+ {
+ currentResult = ldap_first_entry(pLdapConnection, results);
+ }
+ else
+ {
+ currentResult = ldap_next_entry(pLdapConnection, results);
+ }
+
+ if(currentResult == NULL)
+ {
+ *dn = NULL;
+ return 0;
+ }
+
+ *dn = ldap_get_dn(pLdapConnection, currentResult);
+ return 0;
+ }
+ else
+ {
+ currentResult = ldap_first_entry(pLdapConnection, results);
+ if(ldap_next_entry(pLdapConnection, results) != NULLMSG)
+ {
+ // ToDo: Log that multiple results for username were found.
+ *dn = NULL;
+ return -1;
+ }
+
+ *dn = ldap_get_dn(pLdapConnection, currentResult);
+ return 0;
+ }
+}
+
+int PassSyncService::ModifyPassword(char* dn, char* password)
+{
+ LDAPMod passMod;
+ LDAPMod* mods[2] = {&passMod, NULL};
+ char* modValues[2] = {password, NULL};
+
+ passMod.mod_type = ldapPasswordField;
+ passMod.mod_op = LDAP_MOD_REPLACE;
+ passMod.mod_values = modValues;
+
+ lastLdapError = ldap_modify_ext_s(pLdapConnection, dn, mods, NULL, NULL);
+ if(lastLdapError != LDAP_SUCCESS)
+ {
+ // ToDo: Log the reason for the modify failure.
+ return -1;
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/ldap/synctools/passwordsync/passsync/syncserv.h b/ldap/synctools/passwordsync/passsync/syncserv.h
new file mode 100644
index 00000000..64b95260
--- /dev/null
+++ b/ldap/synctools/passwordsync/passsync/syncserv.h
@@ -0,0 +1,55 @@
+// Created: 2-8-2005
+// Author(s): Scott Bridges
+#ifndef _SYNCSERV_H_
+#define _SYNCSERV_H_
+
+#include <stdio.h>
+#include <ldap.h>
+#include <ldap_ssl.h>
+#include "ntservice.h"
+#include "../passhand.h"
+
+#define REG_BUF_SIZE 64
+
+class PassSyncService : public CNTService
+{
+public:
+ PassSyncService(const TCHAR* serviceName);
+ ~PassSyncService();
+
+ void Run();
+
+ // ToDo: Move to private.
+ int Connect();
+ int Disconnect();
+ int QueryUsername(char* username);
+ int GetDN(char** dn);
+ int ModifyPassword(char* dn, char* password);
+
+ int SyncPasswords();
+
+private:
+
+ PasswordHandler ourPasswordHandler;
+ HANDLE passhandEventHandle;
+
+ // LDAP variables
+ LDAP* pLdapConnection;
+ LDAPMessage* results;
+ LDAPMessage* currentResult;
+ int lastLdapError;
+
+ // Config variables
+ char* dataFilename;
+ char* logFilename;
+ char* ldapHostName;
+ char* ldpaHostPort;
+ char* ldalAuthUsername;
+ char* ldapAuthPassword;
+ char* ldapSearchBase;
+ char* ldapUsernameField;
+ char* ldapPasswordField;
+ bool multipleModify;
+};
+
+#endif \ No newline at end of file
diff --git a/ldap/synctools/passwordsync/wix/PassSync.wxs b/ldap/synctools/passwordsync/wix/PassSync.wxs
new file mode 100644
index 00000000..3cefe39f
--- /dev/null
+++ b/ldap/synctools/passwordsync/wix/PassSync.wxs
@@ -0,0 +1,1210 @@
+<?xml version='1.0' encoding='windows-1252'?>
+<Wix xmlns='http://schemas.microsoft.com/wix/2003/01/wi'>
+ <Product Name='Password Sync' Id='DB501C18-86C7-4D14-AEC0-86416A69ABDE'
+ Language='1033' Codepage='1252'
+ Version='1.0.0' Manufacturer='Acme Ltd.'>
+
+ <Package Id='????????-????-????-????-????????????' Keywords='Installer'
+ Description="Password Synchronization Installer"
+ Comments='Foobar is a registered trademark of Acme Ltd.' Manufacturer='Acme Ltd.'
+ InstallerVersion='100' Languages='1033' Compressed='yes' SummaryCodepage='1252' />
+
+ <Media Id='1' Cabinet='Sample.cab' EmbedCab='yes' DiskPrompt="CD-ROM #1" />
+ <Property Id='DiskPrompt' Value="Password Sync Installation [1]" />
+
+ <Directory Id='TARGETDIR' Name='SourceDir'>
+
+ <Directory Id='WinDir' Name='WinDir' LongName='WINDOWS'>
+ <Directory Id='SysDir' Name='SysDir' LongName='system32'>
+ <Component Id='HelperLibrary' Guid='5C4B892B-6BE3-460D-A14F-75658D16550B'>
+ <File Id='PasshookDLL' Name='passhook.dll' DiskId='1' src='passhook.dll' Vital='yes' />
+ </Component>
+ </Directory>
+ </Directory>
+ <Directory Id='ProgramFilesFolder' Name='PFiles'>
+
+ <Directory Id='INSTALLDIR' Name='PassSync' LongName='Password Synchronization'>
+
+ <Component Id='MainExecutable' Guid='DCEECAA4-83F1-4F22-985B-FDB3C8ABD471'>
+ <File Id='PassSyncEXE' Name='PassSync.exe' LongName='passsync.exe' DiskId='1'
+ src='passsync.exe' Vital='yes' />
+ <Shortcut Id="startmenuPassSync" Directory="ProgramMenuDir" Name="PassSync"
+ LongName="Password Synchronization" Target="MainProgram" WorkingDirectory='INSTALLDIR'
+ Icon="PassSync.exe" IconIndex="0" />
+ <Shortcut Id="desktopPassSync" Directory="DesktopFolder" Name="PassSync"
+ LongName="Password Synchronization" Target="MainProgram" WorkingDirectory='INSTALLDIR'
+ Icon="PassSync.exe" IconIndex="0" />
+
+ <ServiceInstall Id='PassSyncEXE' Name='PassSync' DisplayName='Password Synchronization' Type='ownProcess'
+ Interactive='yes' Start='auto' Vital='yes' ErrorControl='normal'/>
+ <ServiceControl Id='PassSyncEXE' Name='PassSync' Start='install' Stop='both' Remove='uninstall' Wait='yes'/>
+
+
+ <Registry Id='HostName' Root='HKLM' Key='Software\PasswordSync' Name='Host Name' Action='write' Type='string' Value='[HOSTNAME]' />
+ <Registry Id='PortNum' Root='HKLM' Key='Software\PasswordSync' Name='Port Number' Action='write' Type='string' Value='[PORTNUM]' />
+ <Registry Id='UserName' Root='HKLM' Key='Software\PasswordSync' Name='User Name' Action='write' Type='string' Value='[USER]' />
+ <Registry Id='Password' Root='HKLM' Key='Software\PasswordSync' Name='Password' Action='write' Type='string' Value='[PASSWORD]' />
+ <Registry Id='SrchBase' Root='HKLM' Key='Software\PasswordSync' Name='Search Base' Action='write' Type='string' Value='[SRCHBASE]' />
+ <Registry Id='UserFld' Root='HKLM' Key='Software\PasswordSync' Name='User Name Field' Action='write' Type='string' Value='ntuserdomainid' />
+ <Registry Id='PassFld' Root='HKLM' Key='Software\PasswordSync' Name='Password Field' Action='write' Type='string' Value='ntusercomment' />
+ <Registry Id='NotPkgs' Root='HKLM' Key='SYSTEM\ControlSet001\Control\Lsa' Name='Notification Packages' Action='append'
+ Type='multiString' Value='passhook'/>
+
+ </Component>
+
+ </Directory>
+ </Directory>
+
+ <Directory Id="ProgramMenuFolder" Name="PMenu" LongName="Programs">
+ <Directory Id="ProgramMenuDir" Name='Foobar10' LongName="Foobar 1.0" />
+ </Directory>
+
+ <Directory Id="DesktopFolder" Name="Desktop" />
+ </Directory>
+
+ <Feature Id='Complete' Title='Foobar 1.0' Description='The complete package.'
+ TypicalDefault='install' Display='expand' Level='1'
+ ConfigurableDirectory='INSTALLDIR'>
+ <Feature Id='MainProgram' Title='Program' Description='The main executable.'
+ TypicalDefault='install' Level='1'>
+ <ComponentRef Id='MainExecutable' />
+ <ComponentRef Id='HelperLibrary' />
+ </Feature>
+ </Feature>
+
+ <UI>
+ <Property Id="DefaultUIFont">DlgFont8</Property>
+ <Property Id="ErrorDialog">ErrorDlg</Property>
+
+ <Dialog Id="AdminWelcomeDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_Next]">
+ <Publish Property="InstallMode" Value="Server Image">1</Publish>
+ <Publish Event="NewDialog" Value="AdminRegistrationDlg">1</Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="[DialogBitmap]" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Back]" />
+ <Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="30" Transparent="yes" NoPrefix="yes">
+ <Text>The [Wizard] will create a server image of [ProductName], at a specified network location. Click Next to continue or Cancel to exit the [Wizard].</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes">
+ <Text>{\VerdanaBold13}Welcome to the [ProductName] [Wizard]</Text>
+ </Control>
+ </Dialog>
+
+ <Dialog Id="PrepareDlg" Width="370" Height="270" Title="[ProductName] [Setup]" Modeless="yes">
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="[DialogBitmap]" />
+ <Control Id="ActionText" Type="Text" X="135" Y="100" Width="220" Height="20" Transparent="yes" NoPrefix="yes">
+ <Subscribe Event="ActionText" Attribute="Text" />
+ </Control>
+ <Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="20" Transparent="yes" NoPrefix="yes">
+ <Text>Please wait while the [Wizard] prepares to guide you through the installation.</Text>
+ </Control>
+ <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Disabled="yes" TabSkip="yes" Text="[ButtonText_Next]" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Disabled="yes" TabSkip="yes" Text="[ButtonText_Back]" />
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes">
+ <Text>{\VerdanaBold13}Welcome to the [ProductName] [Wizard]</Text>
+ </Control>
+ <Control Id="ActionData" Type="Text" X="135" Y="125" Width="220" Height="30" Transparent="yes" NoPrefix="yes">
+ <Subscribe Event="ActionData" Attribute="Text" />
+ </Control>
+ </Dialog>
+
+ <Dialog Id="ProgressDlg" Width="370" Height="270" Title="[ProductName] [Setup]" Modeless="yes">
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Back]" />
+ <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Next]" />
+ <Control Id="ActionText" Type="Text" X="70" Y="100" Width="265" Height="10">
+ <Subscribe Event="ActionText" Attribute="Text" />
+ </Control>
+ <Control Id="Text" Type="Text" X="35" Y="65" Width="300" Height="20">
+ <Text>Please wait while the [Wizard] [Progress2] [ProductName]. This may take several minutes.</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="20" Y="15" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont][Progress1] [ProductName]</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ <Control Id="ProgressBar" Type="ProgressBar" X="35" Y="115" Width="300" Height="10" ProgressBlocks="yes" Text="Progress done">
+ <Subscribe Event="SetProgress" Attribute="Progress" />
+ </Control>
+ <Control Id="StatusLabel" Type="Text" X="35" Y="100" Width="35" Height="10" Text="Status:" />
+ </Dialog>
+
+ <Dialog Id="UserExit" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="Finish" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="[ButtonText_Finish]">
+ <Publish Event="EndDialog" Value="Exit">1</Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Cancel]" />
+ <Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="[DialogBitmap]" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Back]" />
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes">
+ <Text>{\VerdanaBold13}[ProductName] [Wizard] was interrupted</Text>
+ </Control>
+ <Control Id="Description1" Type="Text" X="135" Y="70" Width="220" Height="40" Transparent="yes" NoPrefix="yes">
+ <Text>[ProductName] setup was interrupted. Your system has not been modified. To install this program at a later time, please run the installation again.</Text>
+ </Control>
+ <Control Id="Description2" Type="Text" X="135" Y="115" Width="220" Height="20" Transparent="yes" NoPrefix="yes">
+ <Text>Click the Finish button to exit the [Wizard].</Text>
+ </Control>
+ </Dialog>
+
+ <Dialog Id="FatalError" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="Finish" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="[ButtonText_Finish]">
+ <Publish Event="EndDialog" Value="Exit">1</Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Cancel]" />
+ <Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="[DialogBitmap]" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Back]" />
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes">
+ <Text>{\VerdanaBold13}[ProductName] [Wizard] ended prematurely</Text>
+ </Control>
+ <Control Id="Description1" Type="Text" X="135" Y="70" Width="220" Height="40" Transparent="yes" NoPrefix="yes">
+ <Text>[ProductName] setup ended prematurely because of an error. Your system has not been modified. To install this program at a later time, please run the installation again.</Text>
+ </Control>
+ <Control Id="Description2" Type="Text" X="135" Y="115" Width="220" Height="20" Transparent="yes" NoPrefix="yes">
+ <Text>Click the Finish button to exit the [Wizard].</Text>
+ </Control>
+ </Dialog>
+
+ <Dialog Id="ExitDialog" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="Finish" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="[ButtonText_Finish]">
+ <Publish Event="EndDialog" Value="Return">1</Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Cancel]" />
+ <Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="[DialogBitmap]" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Back]" />
+ <Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="20" Transparent="yes" NoPrefix="yes">
+ <Text>Click the Finish button to exit the [Wizard].</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes">
+ <Text>{\VerdanaBold13}Completing the [ProductName] [Wizard]</Text>
+ </Control>
+ </Dialog>
+
+ <Dialog Id="AdminBrowseDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="PathEdit" Type="PathEdit" X="84" Y="202" Width="261" Height="17" Property="TARGETDIR" />
+ <Control Id="OK" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_OK]">
+ <Publish Event="SetTargetPath" Value="TARGETDIR">1</Publish>
+ <Publish Event="EndDialog" Value="Return">1</Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="240" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="Reset" Value="0">1</Publish>
+ <Publish Event="EndDialog" Value="Return">1</Publish>
+ </Control>
+ <Control Id="ComboLabel" Type="Text" X="25" Y="58" Width="44" Height="10" TabSkip="no" Text="&amp;Look in:" />
+ <Control Id="DirectoryCombo" Type="DirectoryCombo" X="70" Y="55" Width="220" Height="80" Property="TARGETDIR" Removable="yes" Fixed="yes" Remote="yes">
+ <Subscribe Event="IgnoreChange" Attribute="IgnoreChange" />
+ </Control>
+ <Control Id="Up" Type="PushButton" X="298" Y="55" Width="19" Height="19" ToolTip="Up One Level" Icon="yes" FixedSize="yes" IconSize="16" Text="Up">
+ <Publish Event="DirectoryListUp" Value="0">1</Publish>
+ </Control>
+ <Control Id="NewFolder" Type="PushButton" X="325" Y="55" Width="19" Height="19" ToolTip="Create A New Folder" Icon="yes" FixedSize="yes" IconSize="16" Text="New">
+ <Publish Event="DirectoryListNew" Value="0">1</Publish>
+ </Control>
+ <Control Id="DirectoryList" Type="DirectoryList" X="25" Y="83" Width="320" Height="110" Property="TARGETDIR" Sunken="yes" TabSkip="no" />
+ <Control Id="PathLabel" Type="Text" X="25" Y="205" Width="59" Height="10" TabSkip="no" Text="&amp;Folder name:" />
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>Browse to the destination folder</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Change current destination folder</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ </Dialog>
+
+ <Dialog Id="AdminInstallPointDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="Text" Type="Text" X="25" Y="80" Width="320" Height="10" TabSkip="no">
+ <Text>&amp;Enter a new network location or click Browse to browse to one.</Text>
+ </Control>
+ <Control Id="PathEdit" Type="PathEdit" X="25" Y="93" Width="320" Height="18" Property="TARGETDIR" />
+ <Control Id="Browse" Type="PushButton" X="289" Y="119" Width="56" Height="17" Text="[ButtonText_Browse]">
+ <Publish Event="SpawnDialog" Value="AdminBrowseDlg">1</Publish>
+ </Control>
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="[ButtonText_Back]">
+ <Publish Event="NewDialog" Value="AdminRegistrationDlg">1</Publish>
+ </Control>
+ <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_Next]">
+ <Publish Event="SetTargetPath" Value="TARGETDIR">1</Publish>
+ <Publish Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Description" Type="Text" X="25" Y="20" Width="280" Height="20" Transparent="yes" NoPrefix="yes">
+ <Text>Please specify a network location for the server image of [ProductName] product</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Network Location</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ </Dialog>
+
+ <Dialog Id="AdminRegistrationDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="OrganizationLabel" Type="Text" X="45" Y="71" Width="285" Height="30" TabSkip="no">
+ <Text>&amp;Please enter the name of your organization in the box below. This will be used as default company name for subsequent installations of [ProductName]:</Text>
+ </Control>
+ <Control Id="OrganizationEdit" Type="Edit" X="45" Y="105" Width="220" Height="18" Property="COMPANYNAME" Text="{80}" />
+ <Control Id="CDKeyLabel" Type="Text" X="45" Y="130" Width="50" Height="10" TabSkip="no">
+ <Text>CD &amp;Key:</Text>
+ </Control>
+ <Control Id="CDKeyEdit" Type="MaskedEdit" X="45" Y="143" Width="250" Height="16" Property="PIDKEY" Text="[PortTemplate]" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Image="yes" Text="[ButtonText_Back]">
+ <Publish Event="NewDialog" Value="AdminWelcomeDlg">1</Publish>
+ </Control>
+ <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_Next]">
+ <Publish Event="ValidateProductID" Value="0">0</Publish>
+ <Publish Event="NewDialog" Value="AdminInstallPointDlg">ProductID</Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>Please enter your company information</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Company Information</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ </Dialog>
+
+ <Dialog Id="BrowseDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="PathEdit" Type="PathEdit" X="84" Y="202" Width="261" Height="18" Property="_BrowseProperty" Indirect="yes" />
+ <Control Id="OK" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_OK]">
+ <Publish Event="SetTargetPath" Value="[_BrowseProperty]">1</Publish>
+ <Publish Event="EndDialog" Value="Return">1</Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="240" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="Reset" Value="0">1</Publish>
+ <Publish Event="EndDialog" Value="Return">1</Publish>
+ </Control>
+ <Control Id="ComboLabel" Type="Text" X="25" Y="58" Width="44" Height="10" TabSkip="no" Text="&amp;Look in:" />
+ <Control Id="DirectoryCombo" Type="DirectoryCombo" X="70" Y="55" Width="220" Height="80" Property="_BrowseProperty" Indirect="yes" Fixed="yes" Remote="yes">
+ <Subscribe Event="IgnoreChange" Attribute="IgnoreChange" />
+ </Control>
+ <Control Id="Up" Type="PushButton" X="298" Y="55" Width="19" Height="19" ToolTip="Up One Level" Icon="yes" FixedSize="yes" IconSize="16" Text="Up">
+ <Publish Event="DirectoryListUp" Value="0">1</Publish>
+ </Control>
+ <Control Id="NewFolder" Type="PushButton" X="325" Y="55" Width="19" Height="19" ToolTip="Create A New Folder" Icon="yes" FixedSize="yes" IconSize="16" Text="New">
+ <Publish Event="DirectoryListNew" Value="0">1</Publish>
+ </Control>
+ <Control Id="DirectoryList" Type="DirectoryList" X="25" Y="83" Width="320" Height="110" Property="_BrowseProperty" Sunken="yes" Indirect="yes" TabSkip="no" />
+ <Control Id="PathLabel" Type="Text" X="25" Y="205" Width="59" Height="10" TabSkip="no" Text="&amp;Folder name:" />
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>Browse to the destination folder</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Change current destination folder</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ </Dialog>
+
+ <Dialog Id="CancelDlg" Width="260" Height="85" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="No" Type="PushButton" X="132" Y="57" Width="56" Height="17" Default="yes" Cancel="yes" Text="[ButtonText_No]">
+ <Publish Event="EndDialog" Value="Return">1</Publish>
+ </Control>
+ <Control Id="Yes" Type="PushButton" X="72" Y="57" Width="56" Height="17" Text="[ButtonText_Yes]">
+ <Publish Event="EndDialog" Value="Exit">1</Publish>
+ </Control>
+ <Control Id="Text" Type="Text" X="48" Y="15" Width="194" Height="30">
+ <Text>Are you sure you want to cancel [ProductName] installation?</Text>
+ </Control>
+ <Control Id="Icon" Type="Icon" X="15" Y="15" Width="24" Height="24" ToolTip="Information icon" FixedSize="yes" IconSize="32" Text="[InfoIcon]" />
+ </Dialog>
+
+ <Dialog Id="CustomizeDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes" TrackDiskSpace="yes">
+ <Control Id="Tree" Type="SelectionTree" X="25" Y="85" Width="175" Height="95" Property="_BrowseProperty" Sunken="yes" TabSkip="no" Text="Tree of selections" />
+ <Control Id="Browse" Type="PushButton" X="304" Y="200" Width="56" Height="17" Text="[ButtonText_Browse]">
+ <Publish Event="SelectionBrowse" Value="BrowseDlg">1</Publish>
+ <Condition Action="hide">Installed</Condition>
+ </Control>
+ <Control Id="Reset" Type="PushButton" X="42" Y="243" Width="56" Height="17" Text="[ButtonText_Reset]">
+ <Publish Event="Reset" Value="0">1</Publish>
+ <Subscribe Event="SelectionNoItems" Attribute="Enabled" />
+ </Control>
+ <Control Id="DiskCost" Type="PushButton" X="111" Y="243" Width="56" Height="17">
+ <Text>Disk &amp;Usage</Text>
+ <Publish Event="SpawnDialog" Value="DiskCostDlg">1</Publish>
+ <Subscribe Event="SelectionNoItems" Attribute="Enabled" />
+ </Control>
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="[ButtonText_Back]">
+ <Publish Event="NewDialog" Value="MaintenanceTypeDlg"><![CDATA[InstallMode = "Change"]]></Publish>
+ <Publish Event="NewDialog" Value="SetupTypeDlg"><![CDATA[InstallMode = "Custom"]]></Publish>
+ </Control>
+ <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_Next]">
+ <Publish Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
+ <Subscribe Event="SelectionNoItems" Attribute="Enabled" />
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>Select the way you want features to be installed.</Text>
+ </Control>
+ <Control Id="Text" Type="Text" X="25" Y="55" Width="320" Height="20">
+ <Text>Click on the icons in the tree below to change the way features will be installed.</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Custom Setup</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ <Control Id="Box" Type="GroupBox" X="210" Y="81" Width="140" Height="98" />
+ <Control Id="ItemDescription" Type="Text" X="215" Y="90" Width="131" Height="30">
+ <Text>Multiline description of the currently selected item.</Text>
+ <Subscribe Event="SelectionDescription" Attribute="Text" />
+ </Control>
+ <Control Id="ItemSize" Type="Text" X="215" Y="130" Width="131" Height="45">
+ <Text>The size of the currently selected item.</Text>
+ <Subscribe Event="SelectionSize" Attribute="Text" />
+ </Control>
+ <Control Id="Location" Type="Text" X="75" Y="200" Width="215" Height="20">
+ <Text>&lt;The selection's path&gt;</Text>
+ <Subscribe Event="SelectionPath" Attribute="Text" />
+ <Subscribe Event="SelectionPathOn" Attribute="Visible" />
+ <Condition Action="hide">Installed</Condition>
+ </Control>
+ <Control Id="LocationLabel" Type="Text" X="25" Y="200" Width="50" Height="10" Text="Location:">
+ <Subscribe Event="SelectionPathOn" Attribute="Visible" />
+ <Condition Action="hide">Installed</Condition>
+ </Control>
+ </Dialog>
+
+ <Dialog Id="DiskCostDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="OK" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="[ButtonText_OK]">
+ <Publish Event="EndDialog" Value="Return">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Description" Type="Text" X="20" Y="20" Width="280" Height="20" Transparent="yes" NoPrefix="yes">
+ <Text>The disk space required for the installation of the selected features.</Text>
+ </Control>
+ <Control Id="Text" Type="Text" X="20" Y="53" Width="330" Height="40">
+ <Text>The highlighted volumes (if any) do not have enough disk space available for the currently selected features. You can either remove some files from the highlighted volumes, or choose to install less features onto local drive(s), or select different destination drive(s).</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Disk Space Requirements</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ <Control Id="VolumeList" Type="VolumeCostList" X="20" Y="100" Width="330" Height="120" Sunken="yes" Fixed="yes" Remote="yes">
+ <Text>{120}{70}{70}{70}{70}</Text>
+ </Control>
+ </Dialog>
+
+ <Dialog Id="ErrorDlg" Width="270" Height="105" Title="Installer Information" ErrorDialog="yes" NoMinimize="yes">
+ <Control Id="ErrorText" Type="Text" X="48" Y="15" Width="205" Height="60" TabSkip="no" Text="Information text" />
+ <Control Id="Y" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="[ButtonText_Yes]">
+ <Publish Event="EndDialog" Value="ErrorYes">1</Publish>
+ </Control>
+ <Control Id="A" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="EndDialog" Value="ErrorAbort">1</Publish>
+ </Control>
+ <Control Id="C" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="EndDialog" Value="ErrorCancel">1</Publish>
+ </Control>
+ <Control Id="ErrorIcon" Type="Icon" X="15" Y="15" Width="24" Height="24" ToolTip="Information icon" FixedSize="yes" IconSize="32" Text="[InfoIcon]" />
+ <Control Id="I" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="[ButtonText_Ignore]">
+ <Publish Event="EndDialog" Value="ErrorIgnore">1</Publish>
+ </Control>
+ <Control Id="N" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="[ButtonText_No]">
+ <Publish Event="EndDialog" Value="ErrorNo">1</Publish>
+ </Control>
+ <Control Id="O" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="[ButtonText_OK]">
+ <Publish Event="EndDialog" Value="ErrorOk">1</Publish>
+ </Control>
+ <Control Id="R" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="[ButtonText_Retry]">
+ <Publish Event="EndDialog" Value="ErrorRetry">1</Publish>
+ </Control>
+ </Dialog>
+
+ <Dialog Id="FilesInUse" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes" KeepModeless="yes">
+ <Control Id="Retry" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="[ButtonText_Retry]">
+ <Publish Event="EndDialog" Value="Retry">1</Publish>
+ </Control>
+ <Control Id="Ignore" Type="PushButton" X="235" Y="243" Width="56" Height="17" Text="[ButtonText_Ignore]">
+ <Publish Event="EndDialog" Value="Ignore">1</Publish>
+ </Control>
+ <Control Id="Exit" Type="PushButton" X="166" Y="243" Width="56" Height="17" Text="[ButtonText_Exit]">
+ <Publish Event="EndDialog" Value="Exit">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Description" Type="Text" X="20" Y="23" Width="280" Height="20" Transparent="yes" NoPrefix="yes">
+ <Text>Some files that need to be updated are currently in use.</Text>
+ </Control>
+ <Control Id="Text" Type="Text" X="20" Y="55" Width="330" Height="30">
+ <Text>The following applications are using files that need to be updated by this setup. Close these applications and then click Retry to continue the installation or Cancel to exit it.</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Files in Use</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ <Control Id="List" Type="ListBox" X="20" Y="87" Width="330" Height="130" Property="FileInUseProcess" Sunken="yes" TabSkip="yes" />
+ </Dialog>
+
+ <Dialog Id="LicenseAgreementDlg" Width="370" Height="270" Title="[ProductName] License Agreement" NoMinimize="yes">
+ <Control Id="Buttons" Type="RadioButtonGroup" X="20" Y="187" Width="330" Height="40" Property="IAgree" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="[ButtonText_Back]">
+ <Publish Event="NewDialog" Value="WelcomeDlg">1</Publish>
+ </Control>
+ <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_Next]">
+ <Publish Event="NewDialog" Value="UserRegistrationDlg"><![CDATA[IAgree = "Yes" AND ShowUserRegistrationDlg = 1]]></Publish>
+ <Publish Event="SpawnWaitDialog" Value="WaitForCostingDlg">CostingComplete = 1</Publish>
+ <Publish Event="NewDialog" Value="SetupTypeDlg"><![CDATA[IAgree = "Yes" AND ShowUserRegistrationDlg <> 1]]></Publish>
+ <Condition Action="disable"><![CDATA[IAgree <> "Yes"]]></Condition>
+ <Condition Action="enable">IAgree = "Yes"</Condition>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="AgreementText" Type="ScrollableText" X="20" Y="60" Width="330" Height="120" Sunken="yes" TabSkip="no">
+ <Text src="Binary/License.rtf" />
+ </Control>
+ <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>Please read the following license agreement carefully</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]End-User License Agreement</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ </Dialog>
+
+ <Dialog Id="MaintenanceTypeDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="ChangeLabel" Type="Text" X="105" Y="65" Width="100" Height="10" TabSkip="no">
+ <Text>[DlgTitleFont]&amp;Modify</Text>
+ </Control>
+ <Control Id="ChangeButton" Type="PushButton" X="50" Y="65" Width="38" Height="38" ToolTip="Modify Installation" Default="yes" Icon="yes" FixedSize="yes" IconSize="32" Text="[CustomSetupIcon]">
+ <Publish Property="InstallMode" Value="Change">1</Publish>
+ <Publish Property="Progress1" Value="Changing">1</Publish>
+ <Publish Property="Progress2" Value="changes">1</Publish>
+ <Publish Event="NewDialog" Value="UserRegistrationDlg">1</Publish>
+ </Control>
+ <Control Id="RepairLabel" Type="Text" X="105" Y="114" Width="100" Height="10" TabSkip="no">
+ <Text>[DlgTitleFont]Re&amp;pair</Text>
+ </Control>
+ <Control Id="RepairButton" Type="PushButton" X="50" Y="114" Width="38" Height="38" ToolTip="Repair Installation" Icon="yes" FixedSize="yes" IconSize="32" Text="[RepairIcon]">
+ <Publish Property="InstallMode" Value="Repair">1</Publish>
+ <Publish Property="Progress1" Value="Repairing">1</Publish>
+ <Publish Property="Progress2" Value="repaires">1</Publish>
+ <Publish Event="NewDialog" Value="VerifyRepairDlg">1</Publish>
+ </Control>
+ <Control Id="RemoveLabel" Type="Text" X="105" Y="163" Width="100" Height="10" TabSkip="no">
+ <Text>[DlgTitleFont]&amp;Remove</Text>
+ </Control>
+ <Control Id="RemoveButton" Type="PushButton" X="50" Y="163" Width="38" Height="38" ToolTip="Remove Installation" Icon="yes" FixedSize="yes" IconSize="32" Text="[RemoveIcon]">
+ <Publish Property="InstallMode" Value="Remove">1</Publish>
+ <Publish Property="Progress1" Value="Removing">1</Publish>
+ <Publish Property="Progress2" Value="removes">1</Publish>
+ <Publish Event="NewDialog" Value="VerifyRemoveDlg">1</Publish>
+ </Control>
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="[ButtonText_Back]">
+ <Publish Event="NewDialog" Value="MaintenanceWelcomeDlg">1</Publish>
+ </Control>
+ <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Next]" />
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="20" Transparent="yes" NoPrefix="yes">
+ <Text>Select the operation you wish to perform.</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="240" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Modify, Repair or Remove installation</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ <Control Id="ChangeText" Type="Text" X="105" Y="78" Width="230" Height="20">
+ <Text>Allows users to change the way features are installed.</Text>
+ </Control>
+ <Control Id="RemoveText" Type="Text" X="105" Y="176" Width="230" Height="20">
+ <Text>Removes [ProductName] from your computer.</Text>
+ </Control>
+ <Control Id="RepairText" Type="Text" X="105" Y="127" Width="230" Height="30">
+ <Text>Repairs errors in the most recent installation state - fixes missing or corrupt files, shortcuts and registry entries.</Text>
+ </Control>
+ </Dialog>
+
+ <Dialog Id="MaintenanceWelcomeDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_Next]">
+ <Publish Event="SpawnWaitDialog" Value="WaitForCostingDlg">CostingComplete = 1</Publish>
+ <Publish Event="NewDialog" Value="MaintenanceTypeDlg">1</Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="[DialogBitmap]" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Back]" />
+ <Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="60" Transparent="yes" NoPrefix="yes">
+ <Text>The [Wizard] will allow you to change the way [ProductName] features are installed on your computer or even to remove [ProductName] from your computer. Click Next to continue or Cancel to exit the [Wizard].</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes">
+ <Text>{\VerdanaBold13}Welcome to the [ProductName] [Wizard]</Text>
+ </Control>
+ </Dialog>
+
+ <Dialog Id="OutOfDiskDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="OK" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="[ButtonText_OK]">
+ <Publish Event="EndDialog" Value="Return">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Description" Type="Text" X="20" Y="20" Width="280" Height="20" Transparent="yes" NoPrefix="yes">
+ <Text>Disk space required for the installation exceeds available disk space.</Text>
+ </Control>
+ <Control Id="Text" Type="Text" X="20" Y="53" Width="330" Height="40">
+ <Text>The highlighted volumes do not have enough disk space available for the currently selected features. You can either remove some files from the highlighted volumes, or choose to install less features onto local drive(s), or select different destination drive(s).</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Out of Disk Space</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ <Control Id="VolumeList" Type="VolumeCostList" X="20" Y="100" Width="330" Height="120" Sunken="yes" Fixed="yes" Remote="yes">
+ <Text>{120}{70}{70}{70}{70}</Text>
+ </Control>
+ </Dialog>
+
+ <Dialog Id="OutOfRbDiskDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="No" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="[ButtonText_No]">
+ <Publish Event="EndDialog" Value="Return">1</Publish>
+ </Control>
+ <Control Id="Yes" Type="PushButton" X="240" Y="243" Width="56" Height="17" Text="[ButtonText_Yes]">
+ <Publish Event="EnableRollback" Value="False">1</Publish>
+ <Publish Event="EndDialog" Value="Return">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Description" Type="Text" X="20" Y="20" Width="280" Height="20" Transparent="yes" NoPrefix="yes">
+ <Text>Disk space required for the installation exceeds available disk space.</Text>
+ </Control>
+ <Control Id="Text" Type="Text" X="20" Y="53" Width="330" Height="40">
+ <Text>The highlighted volumes do not have enough disk space available for the currently selected features. You can either remove some files from the highlighted volumes, or choose to install less features onto local drive(s), or select different destination drive(s).</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Out of Disk Space</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ <Control Id="VolumeList" Type="VolumeCostList" X="20" Y="140" Width="330" Height="80" Sunken="yes" Fixed="yes" Remote="yes" ShowRollbackCost="yes">
+ <Text>{120}{70}{70}{70}{70}</Text>
+ </Control>
+ <Control Id="Text2" Type="Text" X="20" Y="94" Width="330" Height="40">
+ <Text>Alternatively, you may choose to disable the installer's rollback functionality. This allows the installer to restore your computer's original state should the installation be interrupted in any way. Click Yes if you wish to take the risk to disable rollback.</Text>
+ </Control>
+ </Dialog>
+
+ <Dialog Id="ResumeDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="Install" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_Install]">
+ <Publish Event="SpawnWaitDialog" Value="WaitForCostingDlg">CostingComplete = 1</Publish>
+ <Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
+ <Publish Event="SpawnDialog" Value="OutOfRbDiskDlg"><![CDATA[OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)]]></Publish>
+ <Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"]]></Publish>
+ <Publish Event="EnableRollback" Value="False"><![CDATA[OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"]]></Publish>
+ <Publish Event="SpawnDialog" Value="OutOfDiskDlg"><![CDATA[(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")]]></Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="[DialogBitmap]" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Back]" />
+ <Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="30" Transparent="yes" NoPrefix="yes">
+ <Text>The [Wizard] will complete the installation of [ProductName] on your computer. Click Install to continue or Cancel to exit the [Wizard].</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes">
+ <Text>{\VerdanaBold13}Resuming the [ProductName] [Wizard]</Text>
+ </Control>
+ </Dialog>
+
+ <Dialog Id="SetupTypeDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="TypicalLabel" Type="Text" X="105" Y="65" Width="100" Height="10" TabSkip="no">
+ <Text>[DlgTitleFont]&amp;Typical</Text>
+ </Control>
+ <Control Id="TypicalButton" Type="PushButton" X="50" Y="65" Width="38" Height="38" ToolTip="Typical Installation" Default="yes" Icon="yes" FixedSize="yes" IconSize="32" Text="[InstallerIcon]">
+ <Publish Property="InstallMode" Value="Typical">1</Publish>
+ <Publish Event="SetInstallLevel" Value="3">1</Publish>
+ <Publish Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
+ </Control>
+ <Control Id="CustomLabel" Type="Text" X="105" Y="118" Width="100" Height="10" TabSkip="no">
+ <Text>[DlgTitleFont]C&amp;ustom</Text>
+ </Control>
+ <Control Id="CustomButton" Type="PushButton" X="50" Y="118" Width="38" Height="38" ToolTip="Custom Installation" Icon="yes" FixedSize="yes" IconSize="32" Text="[CustomSetupIcon]">
+ <Publish Property="InstallMode" Value="Custom">1</Publish>
+ <Publish Event="NewDialog" Value="CustomizeDlg">1</Publish>
+ </Control>
+ <Control Id="CompleteLabel" Type="Text" X="105" Y="171" Width="100" Height="10" TabSkip="no">
+ <Text>[DlgTitleFont]C&amp;omplete</Text>
+ </Control>
+ <Control Id="CompleteButton" Type="PushButton" X="50" Y="171" Width="38" Height="38" ToolTip="Complete Installation" Icon="yes" FixedSize="yes" IconSize="32" Text="[CompleteSetupIcon]">
+ <Publish Property="InstallMode" Value="Complete">1</Publish>
+ <Publish Event="SetInstallLevel" Value="1000">1</Publish>
+ <Publish Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
+ </Control>
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="[ButtonText_Back]">
+ <Publish Event="NewDialog" Value="LicenseAgreementDlg"><![CDATA[ShowUserRegistrationDlg <> 1]]></Publish>
+ <Publish Event="NewDialog" Value="UserRegistrationDlg">ShowUserRegistrationDlg = 1</Publish>
+ </Control>
+ <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Next]" />
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>Choose the setup type that best suits your needs</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Choose Setup Type</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ <Control Id="CompleteText" Type="Text" X="105" Y="184" Width="230" Height="20">
+ <Text>All program features will be installed. (Requires most disk space)</Text>
+ </Control>
+ <Control Id="CustomText" Type="Text" X="105" Y="131" Width="230" Height="30">
+ <Text>Allows users to choose which program features will be installed and where they will be installed. Recommended for advanced users.</Text>
+ </Control>
+ <Control Id="TypicalText" Type="Text" X="105" Y="78" Width="230" Height="20">
+ <Text>Installs the most common program features. Recommended for most users.</Text>
+ </Control>
+ </Dialog>
+
+ <Dialog Id="UserRegistrationDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="HostLabel" Type="Text" X="45" Y="73" Width="50" Height="15" TabSkip="no" Text="&amp;Host Name:" />
+ <Control Id="HostEdit" Type="Edit" X="100" Y="71" Width="220" Height="18" Property="HOSTNAME"/>
+ <Control Id="PortLabel" Type="Text" X="45" Y="100" Width="50" Height="15" TabSkip="no" Text="&amp;Port Number:" />
+ <Control Id="PortEdit" Type="MaskedEdit" X="100" Y="98" Width="220" Height="18" Property="PORTNUM" Text="[PortTemplate]" />
+ <Control Id="UserLabel" Type="Text" X="45" Y="127" Width="50" Height="15" TabSkip="no" Text="&amp;User Name:" />
+ <Control Id="UserEdit" Type="Edit" X="100" Y="125" Width="220" Height="18" Property="USER"/>
+ <Control Id="PwdLabel" Type="Text" X="45" Y="154" Width="50" Height="15" TabSkip="no" Text="&amp;Password:"/>
+ <Control Id="PwdEdit" Type="Edit" Password="yes" X="100" Y="152" Width="220" Height="18" Property="PASSWORD"/>
+ <Control Id="SBLabel" Type="Text" X="45" Y="181" Width="50" Height="15" TabSkip="no" Text="&amp;Search Base:"/>
+ <Control Id="SBEdit" Type="Edit" X="100" Y="179" Width="220" Height="18" Property="SRCHBASE"/>
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="[ButtonText_Back]">
+ <Publish Event="NewDialog" Value="WelcomeDlg">1</Publish>
+ </Control>
+ <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_Next]">
+ <Publish Event="SpawnWaitDialog" Value="WaitForCostingDlg">CostingComplete = 1</Publish>
+ <Publish Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>Please enter your password synchronization information</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Password Synchronization Information</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ </Dialog>
+
+ <Dialog Id="VerifyReadyDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes" TrackDiskSpace="yes">
+ <Control Id="Install" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_Install]">
+ <Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
+ <Publish Event="SpawnDialog" Value="OutOfRbDiskDlg"><![CDATA[OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)]]></Publish>
+ <Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"]]></Publish>
+ <Publish Event="EnableRollback" Value="False"><![CDATA[OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"]]></Publish>
+ <Publish Event="SpawnDialog" Value="OutOfDiskDlg"><![CDATA[(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")]]></Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="[ButtonText_Back]">
+ <Publish Event="NewDialog" Value="UserRegistrationDlg">1</Publish>
+ </Control>
+ <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>The [Wizard] is ready to begin the [InstallMode] installation</Text>
+ </Control>
+ <Control Id="Text" Type="Text" X="25" Y="70" Width="320" Height="20">
+ <Text>Click Install to begin the installation. If you want to review or change any of your installation settings, click Back. Click Cancel to exit the wizard.</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Ready to Install</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ </Dialog>
+
+ <Dialog Id="VerifyRemoveDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes" TrackDiskSpace="yes">
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_Back]">
+ <Publish Event="NewDialog" Value="MaintenanceTypeDlg">1</Publish>
+ </Control>
+ <Control Id="Remove" Type="PushButton" X="236" Y="243" Width="56" Height="17" Text="[ButtonText_Remove]">
+ <Publish Event="Remove" Value="All"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
+ <Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
+ <Publish Event="SpawnDialog" Value="OutOfRbDiskDlg"><![CDATA[OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)]]></Publish>
+ <Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"]]></Publish>
+ <Publish Event="EnableRollback" Value="False"><![CDATA[OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"]]></Publish>
+ <Publish Event="SpawnDialog" Value="OutOfDiskDlg"><![CDATA[(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")]]></Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>You have chosen to remove the program from your computer.</Text>
+ </Control>
+ <Control Id="Text" Type="Text" X="25" Y="70" Width="320" Height="30">
+ <Text>Click Remove to remove [ProductName] from your computer. If you want to review or change any of your installation settings, click Back. Click Cancel to exit the wizard.</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Remove [ProductName]</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ </Dialog>
+
+ <Dialog Id="VerifyRepairDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes" TrackDiskSpace="yes">
+ <Control Id="Repair" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_Repair]">
+ <Publish Event="ReinstallMode" Value="ecmus"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
+ <Publish Event="Reinstall" Value="All"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
+ <Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace <> 1]]></Publish>
+ <Publish Event="SpawnDialog" Value="OutOfRbDiskDlg"><![CDATA[OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)]]></Publish>
+ <Publish Event="EndDialog" Value="Return"><![CDATA[OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"]]></Publish>
+ <Publish Event="EnableRollback" Value="False"><![CDATA[OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"]]></Publish>
+ <Publish Event="SpawnDialog" Value="OutOfDiskDlg"><![CDATA[(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")]]></Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="[BannerBitmap]" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="[ButtonText_Back]">
+ <Publish Event="NewDialog" Value="MaintenanceTypeDlg">1</Publish>
+ </Control>
+ <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>The [Wizard] is ready to begin the repair of [ProductName].</Text>
+ </Control>
+ <Control Id="Text" Type="Text" X="25" Y="70" Width="320" Height="30">
+ <Text>Click Repair to repair the installation of [ProductName]. If you want to review or change any of your installation settings, click Back. Click Cancel to exit the wizard.</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
+ <Text>[DlgTitleFont]Repair [ProductName]</Text>
+ </Control>
+ <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
+ </Dialog>
+
+ <Dialog Id="WaitForCostingDlg" Width="260" Height="85" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="Return" Type="PushButton" X="102" Y="57" Width="56" Height="17" Default="yes" Cancel="yes" Text="[ButtonText_Return]">
+ <Publish Event="EndDialog" Value="Exit">1</Publish>
+ </Control>
+ <Control Id="Text" Type="Text" X="48" Y="15" Width="194" Height="30">
+ <Text>Please wait while the installer finishes determining your disk space requirements.</Text>
+ </Control>
+ <Control Id="Icon" Type="Icon" X="15" Y="15" Width="24" Height="24" ToolTip="Exclamation icon" FixedSize="yes" IconSize="32" Text="[ExclamationIcon]" />
+ </Dialog>
+
+ <Dialog Id="WelcomeDlg" Width="370" Height="270" Title="[ProductName] [Setup]" NoMinimize="yes">
+ <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_Next]">
+ <Publish Event="NewDialog" Value="UserRegistrationDlg">1</Publish>
+ </Control>
+ <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="[ButtonText_Cancel]">
+ <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
+ </Control>
+ <Control Id="Bitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="234" TabSkip="no" Text="[DialogBitmap]" />
+ <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Disabled="yes" Text="[ButtonText_Back]" />
+ <Control Id="Description" Type="Text" X="135" Y="70" Width="220" Height="30" Transparent="yes" NoPrefix="yes">
+ <Text>The [Wizard] will install [ProductName] on your computer. Click Next to continue or Cancel to exit the [Wizard].</Text>
+ </Control>
+ <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
+ <Control Id="Title" Type="Text" X="135" Y="20" Width="220" Height="60" Transparent="yes" NoPrefix="yes">
+ <Text>{\VerdanaBold13}Welcome to the [ProductName] [Wizard]</Text>
+ </Control>
+ </Dialog>
+
+ <RadioButtonGroup Property="IAgree">
+ <RadioButton Text="{\DlgFont8}I &amp;accept the terms in the License Agreement" Value="Yes" X="5" Y="0" Width="250" Height="15" />
+ <RadioButton Text="{\DlgFont8}I &amp;do not accept the terms in the License Agreement" Value="No" X="5" Y="20" Width="250" Height="15" />
+ </RadioButtonGroup>
+
+ <TextStyle Id="DlgFont8" FaceName="Tahoma" Size="8" />
+ <TextStyle Id="DlgFontBold8" FaceName="Tahoma" Size="8" Bold="yes" />
+ <TextStyle Id="VerdanaBold13" FaceName="Verdana" Size="13" Bold="yes" />
+
+ <UIText Id="AbsentPath" />
+ <UIText Id="bytes">bytes</UIText>
+ <UIText Id="GB">GB</UIText>
+ <UIText Id="KB">KB</UIText>
+ <UIText Id="MB">MB</UIText>
+ <UIText Id="MenuAbsent">Entire feature will be unavailable</UIText>
+ <UIText Id="MenuAdvertise">Feature will be installed when required</UIText>
+ <UIText Id="MenuAllCD">Entire feature will be installed to run from CD</UIText>
+ <UIText Id="MenuAllLocal">Entire feature will be installed on local hard drive</UIText>
+ <UIText Id="MenuAllNetwork">Entire feature will be installed to run from network</UIText>
+ <UIText Id="MenuCD">Will be installed to run from CD</UIText>
+ <UIText Id="MenuLocal">Will be installed on local hard drive</UIText>
+ <UIText Id="MenuNetwork">Will be installed to run from network</UIText>
+ <UIText Id="ScriptInProgress">Gathering required information...</UIText>
+ <UIText Id="SelAbsentAbsent">This feature will remain uninstalled</UIText>
+ <UIText Id="SelAbsentAdvertise">This feature will be set to be installed when required</UIText>
+ <UIText Id="SelAbsentCD">This feature will be installed to run from CD</UIText>
+ <UIText Id="SelAbsentLocal">This feature will be installed on the local hard drive</UIText>
+ <UIText Id="SelAbsentNetwork">This feature will be installed to run from the network</UIText>
+ <UIText Id="SelAdvertiseAbsent">This feature will become unavailable</UIText>
+ <UIText Id="SelAdvertiseAdvertise">Will be installed when required</UIText>
+ <UIText Id="SelAdvertiseCD">This feature will be available to run from CD</UIText>
+ <UIText Id="SelAdvertiseLocal">This feature will be installed on your local hard drive</UIText>
+ <UIText Id="SelAdvertiseNetwork">This feature will be available to run from the network</UIText>
+ <UIText Id="SelCDAbsent">This feature will be uninstalled completely, you won't be able to run it from CD</UIText>
+ <UIText Id="SelCDAdvertise">This feature will change from run from CD state to set to be installed when required</UIText>
+ <UIText Id="SelCDCD">This feature will remain to be run from CD</UIText>
+ <UIText Id="SelCDLocal">This feature will change from run from CD state to be installed on the local hard drive</UIText>
+ <UIText Id="SelChildCostNeg">This feature frees up [1] on your hard drive.</UIText>
+ <UIText Id="SelChildCostPos">This feature requires [1] on your hard drive.</UIText>
+ <UIText Id="SelCostPending">Compiling cost for this feature...</UIText>
+ <UIText Id="SelLocalAbsent">This feature will be completely removed</UIText>
+ <UIText Id="SelLocalAdvertise">This feature will be removed from your local hard drive, but will be set to be installed when required</UIText>
+ <UIText Id="SelLocalCD">This feature will be removed from your local hard drive, but will be still available to run from CD</UIText>
+ <UIText Id="SelLocalLocal">This feature will remain on you local hard drive</UIText>
+ <UIText Id="SelLocalNetwork">This feature will be removed from your local hard drive, but will be still available to run from the network</UIText>
+ <UIText Id="SelNetworkAbsent">This feature will be uninstalled completely, you won't be able to run it from the network</UIText>
+ <UIText Id="SelNetworkAdvertise">This feature will change from run from network state to set to be installed when required</UIText>
+ <UIText Id="SelNetworkLocal">This feature will change from run from network state to be installed on the local hard drive</UIText>
+ <UIText Id="SelNetworkNetwork">This feature will remain to be run from the network</UIText>
+ <UIText Id="SelParentCostNegNeg">This feature frees up [1] on your hard drive. It has [2] of [3] subfeatures selected. The subfeatures free up [4] on your hard drive.</UIText>
+ <UIText Id="SelParentCostNegPos">This feature frees up [1] on your hard drive. It has [2] of [3] subfeatures selected. The subfeatures require [4] on your hard drive.</UIText>
+ <UIText Id="SelParentCostPosNeg">This feature requires [1] on your hard drive. It has [2] of [3] subfeatures selected. The subfeatures free up [4] on your hard drive.</UIText>
+ <UIText Id="SelParentCostPosPos">This feature requires [1] on your hard drive. It has [2] of [3] subfeatures selected. The subfeatures require [4] on your hard drive.</UIText>
+ <UIText Id="TimeRemaining">Time remaining: {[1] minutes }{[2] seconds}</UIText>
+ <UIText Id="VolumeCostAvailable">Available</UIText>
+ <UIText Id="VolumeCostDifference">Difference</UIText>
+ <UIText Id="VolumeCostRequired">Required</UIText>
+ <UIText Id="VolumeCostSize">Disk Size</UIText>
+ <UIText Id="VolumeCostVolume">Volume</UIText>
+ <ProgressText Action="CostFinalize">Computing space requirements</ProgressText>
+ <ProgressText Action="CostInitialize">Computing space requirements</ProgressText>
+ <ProgressText Action="FileCost">Computing space requirements</ProgressText>
+ <ProgressText Action="InstallValidate">Validating install</ProgressText>
+ <ProgressText Action="InstallFiles" Template="File: [1], Directory: [9], Size: [6]">Copying new files</ProgressText>
+ <ProgressText Action="InstallAdminPackage" Template="File: [1], Directory: [9], Size: [6]">Copying network install files</ProgressText>
+ <ProgressText Action="CreateShortcuts" Template="Shortcut: [1]">Creating shortcuts</ProgressText>
+ <ProgressText Action="PublishComponents" Template="Component ID: [1], Qualifier: [2]">Publishing Qualified Components</ProgressText>
+ <ProgressText Action="PublishFeatures" Template="Feature: [1]">Publishing Product Features</ProgressText>
+ <ProgressText Action="PublishProduct">Publishing product information</ProgressText>
+ <ProgressText Action="ClassInfo" Template="Class Id: [1]">Registering Class servers</ProgressText>
+ <ProgressText Action="RegisterExtensionInfo" Template="Extension: [1]">Registering extension servers</ProgressText>
+ <ProgressText Action="RegisterMIMEInfo" Template="MIME Content Type: [1], Extension: [2]">Registering MIME info</ProgressText>
+ <ProgressText Action="RegisterProgIdInfo" Template="ProgId: [1]">Registering program identifiers</ProgressText>
+ <ProgressText Action="AllocateRegistrySpace" Template="Free space: [1]">Allocating registry space</ProgressText>
+ <ProgressText Action="AppSearch" Template="Property: [1], Signature: [2]">Searching for installed applications</ProgressText>
+ <ProgressText Action="BindImage" Template="File: [1]">Binding executables</ProgressText>
+ <ProgressText Action="CCPSearch">Searching for qualifying products</ProgressText>
+ <ProgressText Action="CreateFolders" Template="Folder: [1]">Creating folders</ProgressText>
+ <ProgressText Action="DeleteServices" Template="Service: [1]">Deleting services</ProgressText>
+ <ProgressText Action="DuplicateFiles" Template="File: [1], Directory: [9], Size: [6]">Creating duplicate files</ProgressText>
+ <ProgressText Action="FindRelatedProducts" Template="Found application: [1]">Searching for related applications</ProgressText>
+ <ProgressText Action="InstallODBC">Installing ODBC components</ProgressText>
+ <ProgressText Action="InstallServices" Template="Service: [2]">Installing new services</ProgressText>
+ <ProgressText Action="LaunchConditions">Evaluating launch conditions</ProgressText>
+ <ProgressText Action="MigrateFeatureStates" Template="Application: [1]">Migrating feature states from related applications</ProgressText>
+ <ProgressText Action="MoveFiles" Template="File: [1], Directory: [9], Size: [6]">Moving files</ProgressText>
+ <ProgressText Action="PatchFiles" Template="File: [1], Directory: [2], Size: [3]">Patching files</ProgressText>
+ <ProgressText Action="ProcessComponents">Updating component registration</ProgressText>
+ <ProgressText Action="RegisterComPlus" Template="AppId: [1]{{, AppType: [2], Users: [3], RSN: [4]}}">Registering COM+ Applications and Components</ProgressText>
+ <ProgressText Action="RegisterFonts" Template="Font: [1]">Registering fonts</ProgressText>
+ <ProgressText Action="RegisterProduct" Template="[1]">Registering product</ProgressText>
+ <ProgressText Action="RegisterTypeLibraries" Template="LibID: [1]">Registering type libraries</ProgressText>
+ <ProgressText Action="RegisterUser" Template="[1]">Registering user</ProgressText>
+ <ProgressText Action="RemoveDuplicateFiles" Template="File: [1], Directory: [9]">Removing duplicated files</ProgressText>
+ <ProgressText Action="RemoveEnvironmentStrings" Template="Name: [1], Value: [2], Action [3]">Updating environment strings</ProgressText>
+ <ProgressText Action="RemoveExistingProducts" Template="Application: [1], Command line: [2]">Removing applications</ProgressText>
+ <ProgressText Action="RemoveFiles" Template="File: [1], Directory: [9]">Removing files</ProgressText>
+ <ProgressText Action="RemoveFolders" Template="Folder: [1]">Removing folders</ProgressText>
+ <ProgressText Action="RemoveIniValues" Template="File: [1], Section: [2], Key: [3], Value: [4]">Removing INI files entries</ProgressText>
+ <ProgressText Action="RemoveODBC">Removing ODBC components</ProgressText>
+ <ProgressText Action="RemoveRegistryValues" Template="Key: [1], Name: [2]">Removing system registry values</ProgressText>
+ <ProgressText Action="RemoveShortcuts" Template="Shortcut: [1]">Removing shortcuts</ProgressText>
+ <ProgressText Action="RMCCPSearch">Searching for qualifying products</ProgressText>
+ <ProgressText Action="SelfRegModules" Template="File: [1], Folder: [2]">Registering modules</ProgressText>
+ <ProgressText Action="SelfUnregModules" Template="File: [1], Folder: [2]">Unregistering modules</ProgressText>
+ <ProgressText Action="SetODBCFolders">Initializing ODBC directories</ProgressText>
+ <ProgressText Action="StartServices" Template="Service: [1]">Starting services</ProgressText>
+ <ProgressText Action="StopServices" Template="Service: [1]">Stopping services</ProgressText>
+ <ProgressText Action="UnpublishComponents" Template="Component ID: [1], Qualifier: [2]">Unpublishing Qualified Components</ProgressText>
+ <ProgressText Action="UnpublishFeatures" Template="Feature: [1]">Unpublishing Product Features</ProgressText>
+ <ProgressText Action="UnregisterClassInfo" Template="Class Id: [1]">Unregister Class servers</ProgressText>
+ <ProgressText Action="UnregisterComPlus" Template="AppId: [1]{{, AppType: [2]}}">Unregistering COM+ Applications and Components</ProgressText>
+ <ProgressText Action="UnregisterExtensionInfo" Template="Extension: [1]">Unregistering extension servers</ProgressText>
+ <ProgressText Action="UnregisterFonts" Template="Font: [1]">Unregistering fonts</ProgressText>
+ <ProgressText Action="UnregisterMIMEInfo" Template="MIME Content Type: [1], Extension: [2]">Unregistering MIME info</ProgressText>
+ <ProgressText Action="UnregisterProgIdInfo" Template="ProgId: [1]">Unregistering program identifiers</ProgressText>
+ <ProgressText Action="UnregisterTypeLibraries" Template="LibID: [1]">Unregistering type libraries</ProgressText>
+ <ProgressText Action="WriteEnvironmentStrings" Template="Name: [1], Value: [2], Action [3]">Updating environment strings</ProgressText>
+ <ProgressText Action="WriteIniValues" Template="File: [1], Section: [2], Key: [3], Value: [4]">Writing INI files values</ProgressText>
+ <ProgressText Action="WriteRegistryValues" Template="Key: [1], Name: [2], Value: [3]">Writing system registry values</ProgressText>
+ <ProgressText Action="Advertise">Advertising application</ProgressText>
+ <ProgressText Action="GenerateScript" Template="[1]">Generating script operations for action:</ProgressText>
+ <ProgressText Action="InstallSFPCatalogFile" Template="File: [1], Dependencies: [2]">Installing system catalog</ProgressText>
+ <ProgressText Action="MsiPublishAssemblies" Template="Application Context:[1], Assembly Name:[2]">Publishing assembly information</ProgressText>
+ <ProgressText Action="MsiUnpublishAssemblies" Template="Application Context:[1], Assembly Name:[2]">Unpublishing assembly information</ProgressText>
+ <ProgressText Action="Rollback" Template="[1]">Rolling back action:</ProgressText>
+ <ProgressText Action="RollbackCleanup" Template="File: [1]">Removing backup files</ProgressText>
+ <ProgressText Action="UnmoveFiles" Template="File: [1], Directory: [9]">Removing moved files</ProgressText>
+ <ProgressText Action="UnpublishProduct">Unpublishing product information</ProgressText>
+ <Error Id="0">{{Fatal error: }}</Error>
+ <Error Id="1">{{Error [1]. }}</Error>
+ <Error Id="2">Warning [1]. </Error>
+ <Error Id="3" />
+ <Error Id="4">Info [1]. </Error>
+ <Error Id="5">The installer has encountered an unexpected error installing this package. This may indicate a problem with this package. The error code is [1]. {{The arguments are: [2], [3], [4]}}</Error>
+ <Error Id="6" />
+ <Error Id="7">{{Disk full: }}</Error>
+ <Error Id="8">Action [Time]: [1]. [2]</Error>
+ <Error Id="9">[ProductName]</Error>
+ <Error Id="10">{[2]}{, [3]}{, [4]}</Error>
+ <Error Id="11">Message type: [1], Argument: [2]</Error>
+ <Error Id="12">=== Logging started: [Date] [Time] ===</Error>
+ <Error Id="13">=== Logging stopped: [Date] [Time] ===</Error>
+ <Error Id="14">Action start [Time]: [1].</Error>
+ <Error Id="15">Action ended [Time]: [1]. Return value [2].</Error>
+ <Error Id="16">Time remaining: {[1] minutes }{[2] seconds}</Error>
+ <Error Id="17">Out of memory. Shut down other applications before retrying.</Error>
+ <Error Id="18">Installer is no longer responding.</Error>
+ <Error Id="19">Installer stopped prematurely.</Error>
+ <Error Id="20">Please wait while Windows configures [ProductName]</Error>
+ <Error Id="21">Gathering required information...</Error>
+ <Error Id="22">Removing older versions of this application...</Error>
+ <Error Id="23">Preparing to remove older versions of this application...</Error>
+ <Error Id="32">{[ProductName] }Setup completed successfully.</Error>
+ <Error Id="33">{[ProductName] }Setup failed.</Error>
+ <Error Id="1101">Error reading from file: [2]. {{ System error [3].}} Verify that the file exists and that you can access it.</Error>
+ <Error Id="1301">Cannot create the file '[2]'. A directory with this name already exists. Cancel the install and try installing to a different location.</Error>
+ <Error Id="1302">Please insert the disk: [2]</Error>
+ <Error Id="1303">The installer has insufficient privileges to access this directory: [2]. The installation cannot continue. Log on as administrator or contact your system administrator.</Error>
+ <Error Id="1304">Error writing to file: [2]. Verify that you have access to that directory.</Error>
+ <Error Id="1305">Error reading from file [2]. {{ System error [3].}} Verify that the file exists and that you can access it.</Error>
+ <Error Id="1306">Another application has exclusive access to the file '[2]'. Please shut down all other applications, then click Retry.</Error>
+ <Error Id="1307">There is not enough disk space to install this file: [2]. Free some disk space and click Retry, or click Cancel to exit.</Error>
+ <Error Id="1308">Source file not found: [2]. Verify that the file exists and that you can access it.</Error>
+ <Error Id="1309">Error reading from file: [3]. {{ System error [2].}} Verify that the file exists and that you can access it.</Error>
+ <Error Id="1310">Error writing to file: [3]. {{ System error [2].}} Verify that you have access to that directory.</Error>
+ <Error Id="1311">Source file not found{{(cabinet)}}: [2]. Verify that the file exists and that you can access it.</Error>
+ <Error Id="1312">Cannot create the directory '[2]'. A file with this name already exists. Please rename or remove the file and click retry, or click Cancel to exit.</Error>
+ <Error Id="1313">The volume [2] is currently unavailable. Please select another.</Error>
+ <Error Id="1314">The specified path '[2]' is unavailable.</Error>
+ <Error Id="1315">Unable to write to the specified folder: [2].</Error>
+ <Error Id="1316">A network error occurred while attempting to read from the file: [2]</Error>
+ <Error Id="1317">An error occurred while attempting to create the directory: [2]</Error>
+ <Error Id="1318">A network error occurred while attempting to create the directory: [2]</Error>
+ <Error Id="1319">A network error occurred while attempting to open the source file cabinet: [2]</Error>
+ <Error Id="1320">The specified path is too long: [2]</Error>
+ <Error Id="1321">The Installer has insufficient privileges to modify this file: [2].</Error>
+ <Error Id="1322">A portion of the folder path '[2]' is invalid. It is either empty or exceeds the length allowed by the system.</Error>
+ <Error Id="1323">The folder path '[2]' contains words that are not valid in folder paths.</Error>
+ <Error Id="1324">The folder path '[2]' contains an invalid character.</Error>
+ <Error Id="1325">'[2]' is not a valid short file name.</Error>
+ <Error Id="1326">Error getting file security: [3] GetLastError: [2]</Error>
+ <Error Id="1327">Invalid Drive: [2]</Error>
+ <Error Id="1328">Error applying patch to file [2]. It has probably been updated by other means, and can no longer be modified by this patch. For more information contact your patch vendor. {{System Error: [3]}}</Error>
+ <Error Id="1329">A file that is required cannot be installed because the cabinet file [2] is not digitally signed. This may indicate that the cabinet file is corrupt.</Error>
+ <Error Id="1330">A file that is required cannot be installed because the cabinet file [2] has an invalid digital signature. This may indicate that the cabinet file is corrupt.{{ Error [3] was returned by WinVerifyTrust.}}</Error>
+ <Error Id="1331">Failed to correctly copy [2] file: CRC error.</Error>
+ <Error Id="1332">Failed to correctly move [2] file: CRC error.</Error>
+ <Error Id="1333">Failed to correctly patch [2] file: CRC error.</Error>
+ <Error Id="1334">The file '[2]' cannot be installed because the file cannot be found in cabinet file '[3]'. This could indicate a network error, an error reading from the CD-ROM, or a problem with this package.</Error>
+ <Error Id="1335">The cabinet file '[2]' required for this installation is corrupt and cannot be used. This could indicate a network error, an error reading from the CD-ROM, or a problem with this package.</Error>
+ <Error Id="1336">There was an error creating a temporary file that is needed to complete this installation.{{ Folder: [3]. System error code: [2]}}</Error>
+ <Error Id="1401">Could not create key: [2]. {{ System error [3].}} Verify that you have sufficient access to that key, or contact your support personnel. </Error>
+ <Error Id="1402">Could not open key: [2]. {{ System error [3].}} Verify that you have sufficient access to that key, or contact your support personnel. </Error>
+ <Error Id="1403">Could not delete value [2] from key [3]. {{ System error [4].}} Verify that you have sufficient access to that key, or contact your support personnel. </Error>
+ <Error Id="1404">Could not delete key [2]. {{ System error [3].}} Verify that you have sufficient access to that key, or contact your support personnel. </Error>
+ <Error Id="1405">Could not read value [2] from key [3]. {{ System error [4].}} Verify that you have sufficient access to that key, or contact your support personnel. </Error>
+ <Error Id="1406">Could not write value [2] to key [3]. {{ System error [4].}} Verify that you have sufficient access to that key, or contact your support personnel.</Error>
+ <Error Id="1407">Could not get value names for key [2]. {{ System error [3].}} Verify that you have sufficient access to that key, or contact your support personnel.</Error>
+ <Error Id="1408">Could not get sub key names for key [2]. {{ System error [3].}} Verify that you have sufficient access to that key, or contact your support personnel.</Error>
+ <Error Id="1409">Could not read security information for key [2]. {{ System error [3].}} Verify that you have sufficient access to that key, or contact your support personnel.</Error>
+ <Error Id="1410">Could not increase the available registry space. [2] KB of free registry space is required for the installation of this application.</Error>
+ <Error Id="1500">Another installation is in progress. You must complete that installation before continuing this one.</Error>
+ <Error Id="1501">Error accessing secured data. Please make sure the Windows Installer is configured properly and try the install again.</Error>
+ <Error Id="1502">User '[2]' has previously initiated an install for product '[3]'. That user will need to run that install again before they can use that product. Your current install will now continue.</Error>
+ <Error Id="1503">User '[2]' has previously initiated an install for product '[3]'. That user will need to run that install again before they can use that product.</Error>
+ <Error Id="1601">Out of disk space -- Volume: '[2]'; required space: [3] KB; available space: [4] KB. Free some disk space and retry.</Error>
+ <Error Id="1602">Are you sure you want to cancel?</Error>
+ <Error Id="1603">The file [2][3] is being held in use{ by the following process: Name: [4], Id: [5], Window Title: '[6]'}. Close that application and retry.</Error>
+ <Error Id="1604">The product '[2]' is already installed, preventing the installation of this product. The two products are incompatible.</Error>
+ <Error Id="1605">There is not enough disk space on the volume '[2]' to continue the install with recovery enabled. [3] KB are required, but only [4] KB are available. Click Ignore to continue the install without saving recovery information, click Retry to check for available space again, or click Cancel to quit the installation.</Error>
+ <Error Id="1606">Could not access network location [2].</Error>
+ <Error Id="1607">The following applications should be closed before continuing the install:</Error>
+ <Error Id="1608">Could not find any previously installed compliant products on the machine for installing this product.</Error>
+ <Error Id="1609">An error occurred while applying security settings. [2] is not a valid user or group. This could be a problem with the package, or a problem connecting to a domain controller on the network. Check your network connection and click Retry, or Cancel to end the install. {{Unable to locate the user's SID, system error [3]}}</Error>
+ <Error Id="1701">The key [2] is not valid. Verify that you entered the correct key.</Error>
+ <Error Id="1702">The installer must restart your system before configuration of [2] can continue. Click Yes to restart now or No if you plan to manually restart later.</Error>
+ <Error Id="1703">You must restart your system for the configuration changes made to [2] to take effect. Click Yes to restart now or No if you plan to manually restart later.</Error>
+ <Error Id="1704">An installation for [2] is currently suspended. You must undo the changes made by that installation to continue. Do you want to undo those changes?</Error>
+ <Error Id="1705">A previous installation for this product is in progress. You must undo the changes made by that installation to continue. Do you want to undo those changes?</Error>
+ <Error Id="1706">An installation package for the product [2] cannot be found. Try the installation again using a valid copy of the installation package '[3]'.</Error>
+ <Error Id="1707">Installation completed successfully.</Error>
+ <Error Id="1708">Installation failed.</Error>
+ <Error Id="1709">Product: [2] -- [3]</Error>
+ <Error Id="1710">You may either restore your computer to its previous state or continue the install later. Would you like to restore?</Error>
+ <Error Id="1711">An error occurred while writing installation information to disk. Check to make sure enough disk space is available, and click Retry, or Cancel to end the install.</Error>
+ <Error Id="1712">One or more of the files required to restore your computer to its previous state could not be found. Restoration will not be possible.</Error>
+ <Error Id="1713">[2] cannot install one of its required products. Contact your technical support group. {{System Error: [3].}}</Error>
+ <Error Id="1714">The older version of [2] cannot be removed. Contact your technical support group. {{System Error [3].}}</Error>
+ <Error Id="1715">Installed [2]</Error>
+ <Error Id="1716">Configured [2]</Error>
+ <Error Id="1717">Removed [2]</Error>
+ <Error Id="1718">File [2] was rejected by digital signature policy.</Error>
+ <Error Id="1719">The Windows Installer Service could not be accessed. This can occur if you are running Windows in safe mode, or if the Windows Installer is not correctly installed. Contact your support personnel for assistance.</Error>
+ <Error Id="1720">There is a problem with this Windows Installer package. A script required for this install to complete could not be run. Contact your support personnel or package vendor. {{Custom action [2] script error [3], [4]: [5] Line [6], Column [7], [8] }}</Error>
+ <Error Id="1721">There is a problem with this Windows Installer package. A program required for this install to complete could not be run. Contact your support personnel or package vendor. {{Action: [2], location: [3], command: [4] }}</Error>
+ <Error Id="1722">There is a problem with this Windows Installer package. A program run as part of the setup did not finish as expected. Contact your support personnel or package vendor. {{Action [2], location: [3], command: [4] }}</Error>
+ <Error Id="1723">There is a problem with this Windows Installer package. A DLL required for this install to complete could not be run. Contact your support personnel or package vendor. {{Action [2], entry: [3], library: [4] }}</Error>
+ <Error Id="1724">Removal completed successfully.</Error>
+ <Error Id="1725">Removal failed.</Error>
+ <Error Id="1726">Advertisement completed successfully.</Error>
+ <Error Id="1727">Advertisement failed.</Error>
+ <Error Id="1728">Configuration completed successfully.</Error>
+ <Error Id="1729">Configuration failed.</Error>
+ <Error Id="1730">You must be an Administrator to remove this application. To remove this application, you can log on as an Administrator, or contact your technical support group for assistance.</Error>
+ <Error Id="1801">The path [2] is not valid. Please specify a valid path.</Error>
+ <Error Id="1802">Out of memory. Shut down other applications before retrying.</Error>
+ <Error Id="1803">There is no disk in drive [2]. Please insert one and click Retry, or click Cancel to go back to the previously selected volume.</Error>
+ <Error Id="1804">There is no disk in drive [2]. Please insert one and click Retry, or click Cancel to return to the browse dialog and select a different volume.</Error>
+ <Error Id="1805">The folder [2] does not exist. Please enter a path to an existing folder.</Error>
+ <Error Id="1806">You have insufficient privileges to read this folder.</Error>
+ <Error Id="1807">A valid destination folder for the install could not be determined.</Error>
+ <Error Id="1901">Error attempting to read from the source install database: [2].</Error>
+ <Error Id="1902">Scheduling reboot operation: Renaming file [2] to [3]. Must reboot to complete operation.</Error>
+ <Error Id="1903">Scheduling reboot operation: Deleting file [2]. Must reboot to complete operation.</Error>
+ <Error Id="1904">Module [2] failed to register. HRESULT [3]. Contact your support personnel.</Error>
+ <Error Id="1905">Module [2] failed to unregister. HRESULT [3]. Contact your support personnel.</Error>
+ <Error Id="1906">Failed to cache package [2]. Error: [3]. Contact your support personnel.</Error>
+ <Error Id="1907">Could not register font [2]. Verify that you have sufficient permissions to install fonts, and that the system supports this font.</Error>
+ <Error Id="1908">Could not unregister font [2]. Verify that you that you have sufficient permissions to remove fonts.</Error>
+ <Error Id="1909">Could not create Shortcut [2]. Verify that the destination folder exists and that you can access it.</Error>
+ <Error Id="1910">Could not remove Shortcut [2]. Verify that the shortcut file exists and that you can access it.</Error>
+ <Error Id="1911">Could not register type library for file [2]. Contact your support personnel.</Error>
+ <Error Id="1912">Could not unregister type library for file [2]. Contact your support personnel.</Error>
+ <Error Id="1913">Could not update the ini file [2][3]. Verify that the file exists and that you can access it.</Error>
+ <Error Id="1914">Could not schedule file [2] to replace file [3] on reboot. Verify that you have write permissions to file [3].</Error>
+ <Error Id="1915">Error removing ODBC driver manager, ODBC error [2]: [3]. Contact your support personnel.</Error>
+ <Error Id="1916">Error installing ODBC driver manager, ODBC error [2]: [3]. Contact your support personnel.</Error>
+ <Error Id="1917">Error removing ODBC driver: [4], ODBC error [2]: [3]. Verify that you have sufficient privileges to remove ODBC drivers.</Error>
+ <Error Id="1918">Error installing ODBC driver: [4], ODBC error [2]: [3]. Verify that the file [4] exists and that you can access it.</Error>
+ <Error Id="1919">Error configuring ODBC data source: [4], ODBC error [2]: [3]. Verify that the file [4] exists and that you can access it.</Error>
+ <Error Id="1920">Service '[2]' ([3]) failed to start. Verify that you have sufficient privileges to start system services.</Error>
+ <Error Id="1921">Service '[2]' ([3]) could not be stopped. Verify that you have sufficient privileges to stop system services.</Error>
+ <Error Id="1922">Service '[2]' ([3]) could not be deleted. Verify that you have sufficient privileges to remove system services.</Error>
+ <Error Id="1923">Service '[2]' ([3]) could not be installed. Verify that you have sufficient privileges to install system services.</Error>
+ <Error Id="1924">Could not update environment variable '[2]'. Verify that you have sufficient privileges to modify environment variables.</Error>
+ <Error Id="1925">You do not have sufficient privileges to complete this installation for all users of the machine. Log on as administrator and then retry this installation.</Error>
+ <Error Id="1926">Could not set file security for file '[3]'. Error: [2]. Verify that you have sufficient privileges to modify the security permissions for this file.</Error>
+ <Error Id="1927">Component Services (COM+ 1.0) are not installed on this computer. This installation requires Component Services in order to complete successfully. Component Services are available on Windows 2000.</Error>
+ <Error Id="1928">Error registering COM+ Application. Contact your support personnel for more information.</Error>
+ <Error Id="1929">Error unregistering COM+ Application. Contact your support personnel for more information.</Error>
+ <Error Id="1930">The description for service '[2]' ([3]) could not be changed.</Error>
+ <Error Id="1931">The Windows Installer service cannot update the system file [2] because the file is protected by Windows. You may need to update your operating system for this program to work correctly. {{Package version: [3], OS Protected version: [4]}}</Error>
+ <Error Id="1932">The Windows Installer service cannot update the protected Windows file [2]. {{Package version: [3], OS Protected version: [4], SFP Error: [5]}}</Error>
+ <Error Id="1933">The Windows Installer service cannot update one or more protected Windows files. {{SFP Error: [2]. List of protected files:\r\n[3]}}</Error>
+ <Error Id="1934">User installations are disabled via policy on the machine.</Error>
+ <Error Id="1935">An error occured during the installation of assembly component [2]. HRESULT: [3]. {{assembly interface: [4], function: [5], assembly name: [6]}}</Error>
+
+ <AdminUISequence>
+ <Show Dialog="FatalError" OnExit="error" />
+ <Show Dialog="UserExit" OnExit="cancel" />
+ <Show Dialog="ExitDialog" OnExit="success" />
+ <Show Dialog="PrepareDlg" Before="CostInitialize"></Show>
+ <Show Dialog="AdminWelcomeDlg" After="CostFinalize" />
+ <Show Dialog="ProgressDlg" After="AdminWelcomeDlg" />
+ </AdminUISequence>
+
+ <InstallUISequence>
+ <Show Dialog="FatalError" OnExit="error" />
+ <Show Dialog="UserExit" OnExit="cancel" />
+ <Show Dialog="ExitDialog" OnExit="success" />
+ <Show Dialog="PrepareDlg" After="LaunchConditions" />
+ <Show Dialog="WelcomeDlg" After="MigrateFeatureStates">NOT Installed</Show>
+ <Show Dialog="ResumeDlg" After="WelcomeDlg">Installed AND (RESUME OR Preselected)</Show>
+ <Show Dialog="MaintenanceWelcomeDlg" After="ResumeDlg">Installed AND NOT RESUME AND NOT Preselected</Show>
+ <Show Dialog="ProgressDlg" After="MaintenanceWelcomeDlg" />
+ </InstallUISequence>
+ </UI>
+
+ <Property Id="ALLUSERS">2</Property>
+ <Property Id="ROOTDRIVE"><![CDATA[C:\]]></Property>
+ <Property Id="ButtonText_No">&amp;No</Property>
+ <Property Id="ButtonText_Install">&amp;Install</Property>
+ <Property Id="Setup">Setup</Property>
+ <Property Id="ButtonText_Browse">Br&amp;owse</Property>
+ <Property Id="CustomSetupIcon">custicon</Property>
+ <Property Id="ButtonText_Next">&amp;Next &gt;</Property>
+ <Property Id="ButtonText_Back">&lt; &amp;Back</Property>
+ <Property Id="InstallMode">Typical</Property>
+ <Property Id="Progress2">installs</Property>
+ <Property Id="IAgree">No</Property>
+ <Property Id="Wizard">Setup Wizard</Property>
+ <Property Id="RemoveIcon">removico</Property>
+ <Property Id="ExclamationIcon">exclamic</Property>
+ <Property Id="ShowUserRegistrationDlg">1</Property>
+ <Property Id="ProductID">none</Property>
+ <Property Id="ButtonText_Reset">&amp;Reset</Property>
+ <Property Id="ButtonText_Remove">&amp;Remove</Property>
+ <Property Id="CompleteSetupIcon">completi</Property>
+ <Property Id="ButtonText_Yes">&amp;Yes</Property>
+ <Property Id="ButtonText_Exit">&amp;Exit</Property>
+ <Property Id="ButtonText_Return">&amp;Return</Property>
+ <Property Id="DialogBitmap">dlgbmp</Property>
+ <Property Id="DlgTitleFont">{&amp;DlgFontBold8}</Property>
+ <Property Id="ButtonText_Ignore">&amp;Ignore</Property>
+ <Property Id="RepairIcon">repairic</Property>
+ <Property Id="ButtonText_Resume">&amp;Resume</Property>
+ <Property Id="InstallerIcon">insticon</Property>
+ <Property Id="ButtonText_Finish">&amp;Finish</Property>
+ <Property Id="PROMPTROLLBACKCOST">P</Property>
+ <Property Id="PortTemplate"><![CDATA[12345<######>@@@@@]]></Property>
+ <Property Id="Progress1">Installing</Property>
+ <Property Id="ButtonText_Cancel">Cancel</Property>
+ <Property Id="INSTALLLEVEL">3</Property>
+ <Property Id="InfoIcon">info</Property>
+ <Property Id="ButtonText_Repair">&amp;Repair</Property>
+ <Property Id="ButtonText_Retry">&amp;Retry</Property>
+ <Property Id="BannerBitmap">bannrbmp</Property>
+ <Property Id="ButtonText_OK">OK</Property>
+
+ <AdminExecuteSequence />
+
+ <InstallExecuteSequence>
+ <RemoveExistingProducts After='InstallFinalize' />
+ </InstallExecuteSequence>
+
+ <Binary Id="Up" src="Binary\Up.ico" />
+ <Binary Id="New" src="Binary\New.ico" />
+ <Binary Id="custicon" src="Binary\Custom.ico" />
+ <Binary Id="repairic" src="Binary\Repair.ico" />
+ <Binary Id="exclamic" src="Binary\Exclam.ico" />
+ <Binary Id="removico" src="Binary\Remove.ico" />
+ <Binary Id="completi" src="Binary\Complete.ico" />
+ <Binary Id="insticon" src="Binary\Typical.ico" />
+ <Binary Id="info" src="Binary\Info.ico" />
+ <Binary Id="bannrbmp" src="Binary\Banner.bmp" />
+ <Binary Id="dlgbmp" src="Binary\Dialog.bmp" />
+ <Icon Id="PassSync.exe" src="passsync.exe" />
+ </Product>
+</Wix>
diff --git a/ldap/synctools/passwordsync/wix/README.txt b/ldap/synctools/passwordsync/wix/README.txt
new file mode 100644
index 00000000..883d5a41
--- /dev/null
+++ b/ldap/synctools/passwordsync/wix/README.txt
@@ -0,0 +1,2 @@
+1. Download Wix and unzip into this folder.
+2. run build.bat in Password Sync folder. \ No newline at end of file