#51252 Add failover credentials to replication agreement
Closed 3 years ago by spichugi. Opened 4 years ago by mreynolds.
mreynolds/389-ds-base issue51156  into  master

@@ -0,0 +1,118 @@ 

+ import logging

+ import pytest

+ import os

+ import time

+ from lib389._constants import DEFAULT_SUFFIX

+ from lib389.topologies import topology_m2 as topo

+ from lib389.replica import BootstrapReplicationManager,  Replicas

+ from lib389.idm.user import TEST_USER_PROPERTIES, UserAccounts,  UserAccount

+ from lib389.idm.group import Group

+ 

+ DEBUGGING = os.getenv("DEBUGGING", default=False)

+ if DEBUGGING:

+     logging.getLogger(__name__).setLevel(logging.DEBUG)

+ else:

+     logging.getLogger(__name__).setLevel(logging.INFO)

+ log = logging.getLogger(__name__)

+ 

+ BOOTSTRAP_MGR_DN = 'uid=replication manager,cn=config'

+ BOOTSTRAP_MGR_PWD = 'boostrap_manager_password'

+ BIND_GROUP_DN = 'cn=replication_managers,' + DEFAULT_SUFFIX

+ 

+ 

+ def test_repl_agmt_bootstrap_credentials(topo):

+     """Test that the agreement bootstrap credentials works if the default

+     credentials fail for some reason.

+ 

+     :id: 38c8095c-d958-415a-b602-74854b7882b3

+     :setup: 2 Master Instances

+     :steps:

+         1.  Change the bind dn group member passwords

+         2.  Verify replication is not working

+         3.  Create a new repl manager on master 2 for bootstrapping

+         4.  Add bootstrap credentials to agmt on master 1

+         5.  Verify replication is now working with bootstrap creds

+         6.  Trigger new repl session and default credentials are used first

+     :expectedresults:

+         1.  Success

+         2.  Success

+         3.  Success

+         4.  Success

+         5.  Success

+         6.  Success

+     """

+ 

+     # Gather all of our objects for the test

+     m1 = topo.ms["master1"]

+     m2 = topo.ms["master2"]

+     master1_replica = Replicas(m1).get(DEFAULT_SUFFIX)

+     master2_replica = Replicas(m2).get(DEFAULT_SUFFIX)

+     master2_users = UserAccounts(m2, DEFAULT_SUFFIX)

+     m1_agmt = master1_replica.get_agreements().list()[0]

+     num_of_original_users = len(master2_users.list())

+ 

+     # Change the member's passwords which should break replication

+     bind_group = Group(m2, dn=BIND_GROUP_DN)

+     members = bind_group.list_members()

+     for member_dn in members:

+         member = UserAccount(m2, dn=member_dn)

+         member.replace('userPassword', 'not_right')

+     time.sleep(3)

+     m1_agmt.pause()

+     m1_agmt.resume()

+ 

+     # Verify replication is not working, a new user should not be replicated

+     users = UserAccounts(m1, DEFAULT_SUFFIX)

+     test_user = users.ensure_state(properties=TEST_USER_PROPERTIES)

+     time.sleep(3)

+     assert len(master2_users.list()) == num_of_original_users

+ 

+     # Create a repl manager on replica

+     repl_mgr = BootstrapReplicationManager(m2, dn=BOOTSTRAP_MGR_DN)

+     mgr_properties = {

+         'uid': 'replication manager',

+         'cn': 'replication manager',

+         'userPassword': BOOTSTRAP_MGR_PWD,

+     }

+     repl_mgr.create(properties=mgr_properties)

+ 

+     # Update master 2 config

+     master2_replica.remove_all('nsDS5ReplicaBindDNGroup')

+     master2_replica.remove_all('nsDS5ReplicaBindDnGroupCheckInterval')

+     master2_replica.replace('nsDS5ReplicaBindDN', BOOTSTRAP_MGR_DN)

+ 

+     # Add bootstrap credentials to master1 agmt, and restart agmt

+     m1_agmt.replace('nsds5ReplicaBootstrapTransportInfo', 'LDAP')

+     m1_agmt.replace('nsds5ReplicaBootstrapBindMethod', 'SIMPLE')

+     m1_agmt.replace('nsds5ReplicaBootstrapCredentials', BOOTSTRAP_MGR_PWD)

+     m1_agmt.replace('nsds5ReplicaBootstrapBindDN', BOOTSTRAP_MGR_DN)

+     m1_agmt.pause()

+     m1_agmt.resume()

+ 

+     # Verify replication is working.  The user should have been replicated

+     time.sleep(3)

+     assert len(master2_users.list()) > num_of_original_users

+ 

+     # Finally check if the default credentials are used on the next repl

+     # session.  Clear out the logs, and disable log buffering.  Then

+     # trigger a replication update/session.

+     m1_agmt.pause()

+     m2.stop()

+     m2.deleteLog(m2.accesslog)  # Clear out the logs

+     m2.start()

+     m2.config.set('nsslapd-accesslog-logbuffering', 'off')

+     m1_agmt.resume()

+     test_user.delete()

+     time.sleep(3)

+ 

+     # We know if the default credentials are used it will fail (err=49)

+     results = m2.ds_access_log.match('.* err=49 .*')

+     assert len(results) > 0

+ 

+ 

+ if __name__ == '__main__':

+     # Run isolated

+     # -s for DEBUG mode

+     CURRENT_FILE = os.path.realpath(__file__)

+     pytest.main(["-s", CURRENT_FILE])

+ 

@@ -234,6 +234,7 @@ 

  nsslapd-pluginenabled: on

  nsslapd-pluginarg0: nsmultiplexorcredentials

  nsslapd-pluginarg1: nsds5ReplicaCredentials

+ nsslapd-pluginarg2: nsds5ReplicaBootstrapCredentials

  nsslapd-pluginid: aes-storage-scheme

  nsslapd-pluginprecedence: 1

  

file modified
+5 -1
@@ -322,6 +322,10 @@ 

  attributeTypes: ( 2.16.840.1.113730.3.1.603 NAME 'dncomp' DESC 'Internal database attribute for each DN component' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'Netscape Directory Server' )

  attributeTypes: ( 2.16.840.1.113730.3.1.604 NAME 'parentid' DESC 'Internal database attribute for the parent ID of the entry' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'Netscape Directory Server' )

  attributeTypes: ( 2.16.840.1.113730.3.1.605 NAME 'entryid' DESC 'Internal database attribute for the ID of the entry' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'Netscape Directory Server' )

+ attributeTypes: ( 2.16.840.1.113730.3.1.2371 NAME 'nsDS5ReplicaBootstrapBindDN' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'Netscape Directory Server' )

+ attributeTypes: ( 2.16.840.1.113730.3.1.2372 NAME 'nsDS5ReplicaBootstrapCredentials' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )

+ attributeTypes: ( 2.16.840.1.113730.3.1.2373 NAME 'nsDS5ReplicaBootstrapBindMethod' 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.2374 NAME 'nsDS5ReplicaBootstrapTransportInfo' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )

  #

  # objectclasses

  #
@@ -333,7 +337,7 @@ 

  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 'Replication configuration objectclass' SUP top  MUST ( nsDS5ReplicaRoot $  nsDS5ReplicaId ) MAY (cn $ nsds5ReplicaPreciseTombstonePurging $ nsds5ReplicaCleanRUV $ nsds5ReplicaAbortCleanRUV $ nsDS5ReplicaType $ nsDS5ReplicaBindDN $ nsDS5ReplicaBindDNGroup $ nsState $ nsDS5ReplicaName $ nsDS5Flags $ nsDS5Task $ nsDS5ReplicaReferral $ nsDS5ReplicaAutoReferral $ nsds5ReplicaPurgeDelay $ nsds5ReplicaTombstonePurgeInterval $ nsds5ReplicaChangeCount $ nsds5ReplicaLegacyConsumer $ nsds5ReplicaProtocolTimeout $ nsds5ReplicaBackoffMin $ nsds5ReplicaBackoffMax $ nsds5ReplicaReleaseTimeout $ nsDS5ReplicaBindDnGroupCheckInterval ) X-ORIGIN 'Netscape Directory Server' )

  objectClasses: ( 2.16.840.1.113730.3.2.113 NAME 'nsTombstone' DESC 'Netscape defined objectclass' SUP top MAY ( nstombstonecsn $ nsParentUniqueId $ nscpEntryDN ) 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 ( nsds5ReplicaCleanRUVNotified $ nsDS5ReplicaHost $ nsDS5ReplicaPort $ nsDS5ReplicaTransportInfo $ nsDS5ReplicaBindDN $ nsDS5ReplicaCredentials $ nsDS5ReplicaBindMethod $ nsDS5ReplicaRoot $ nsDS5ReplicatedAttributeList $ nsDS5ReplicatedAttributeListTotal $ nsDS5ReplicaUpdateSchedule $ nsds5BeginReplicaRefresh $ description $ nsds50ruv $ nsruvReplicaLastModified $ nsds5ReplicaTimeout $ nsds5replicaChangesSentSinceStartup $ nsds5replicaLastUpdateEnd $ nsds5replicaLastUpdateStart $ nsds5replicaLastUpdateStatus $ nsds5replicaUpdateInProgress $ nsds5replicaLastInitEnd $ nsds5ReplicaEnabled $ nsds5replicaLastInitStart $ nsds5replicaLastInitStatus $ nsds5debugreplicatimeout $ nsds5replicaBusyWaitTime $ nsds5ReplicaStripAttrs $ nsds5replicaSessionPauseTime $ nsds5ReplicaProtocolTimeout $ nsds5ReplicaFlowControlWindow $ nsds5ReplicaFlowControlPause $ nsDS5ReplicaWaitForAsyncResults $ nsds5ReplicaIgnoreMissingChange) 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 ( nsds5ReplicaCleanRUVNotified $ nsDS5ReplicaHost $ nsDS5ReplicaPort $ nsDS5ReplicaTransportInfo $ nsDS5ReplicaBindDN $ nsDS5ReplicaCredentials $ nsDS5ReplicaBindMethod $ nsDS5ReplicaRoot $ nsDS5ReplicatedAttributeList $ nsDS5ReplicatedAttributeListTotal $ nsDS5ReplicaUpdateSchedule $ nsds5BeginReplicaRefresh $ description $ nsds50ruv $ nsruvReplicaLastModified $ nsds5ReplicaTimeout $ nsds5replicaChangesSentSinceStartup $ nsds5replicaLastUpdateEnd $ nsds5replicaLastUpdateStart $ nsds5replicaLastUpdateStatus $ nsds5replicaUpdateInProgress $ nsds5replicaLastInitEnd $ nsds5ReplicaEnabled $ nsds5replicaLastInitStart $ nsds5replicaLastInitStatus $ nsds5debugreplicatimeout $ nsds5replicaBusyWaitTime $ nsds5ReplicaStripAttrs $ nsds5replicaSessionPauseTime $ nsds5ReplicaProtocolTimeout $ nsds5ReplicaFlowControlWindow $ nsds5ReplicaFlowControlPause $ nsDS5ReplicaWaitForAsyncResults $ nsds5ReplicaIgnoreMissingChange $ nsDS5ReplicaBootstrapBindDN $ nsDS5ReplicaBootstrapCredentials $ nsDS5ReplicaBootstrapBindMethod $ nsDS5ReplicaBootstrapTransportInfo ) X-ORIGIN 'Netscape Directory Server' )

  objectClasses: ( 2.16.840.1.113730.3.2.39 NAME 'nsslapdConfig' DESC 'Netscape defined objectclass' SUP top MAY ( cn ) X-ORIGIN 'Netscape Directory Server' )

  objectClasses: ( 2.16.840.1.113730.3.2.317 NAME 'nsSaslMapping' DESC 'Netscape defined objectclass' SUP top MUST ( cn $ nsSaslMapRegexString $ nsSaslMapBaseDNTemplate $ nsSaslMapFilterTemplate ) MAY ( nsSaslMapPriority ) X-ORIGIN 'Netscape Directory Server' )

  objectClasses: ( 2.16.840.1.113730.3.2.43 NAME 'nsSNMP' DESC 'Netscape defined objectclass' SUP top MUST ( cn $ nsSNMPEnabled ) MAY ( nsSNMPOrganization $ nsSNMPLocation $ nsSNMPContact $ nsSNMPDescription $ nsSNMPName $ nsSNMPMasterHost $ nsSNMPMasterPort ) X-ORIGIN 'Netscape Directory Server' )

@@ -157,6 +157,10 @@ 

  extern const char *type_replicaBackoffMax;

  extern const char *type_replicaPrecisePurge;

  extern const char *type_replicaIgnoreMissingChange;

+ extern const char *type_nsds5ReplicaBootstrapBindDN;

+ extern const char *type_nsds5ReplicaBootstrapCredentials;

+ extern const char *type_nsds5ReplicaBootstrapBindMethod;

+ extern const char *type_nsds5ReplicaBootstrapTransportInfo;

  

  /* Attribute names for windows replication agreements */

  extern const char *type_nsds7WindowsReplicaArea;
@@ -385,9 +389,13 @@ 

  char *agmt_get_hostname(const Repl_Agmt *ra);

  int agmt_get_port(const Repl_Agmt *ra);

  uint32_t agmt_get_transport_flags(const Repl_Agmt *ra);

+ uint32_t agmt_get_bootstrap_transport_flags(const Repl_Agmt *ra);

  char *agmt_get_binddn(const Repl_Agmt *ra);

+ char *agmt_get_bootstrap_binddn(const Repl_Agmt *ra);

  struct berval *agmt_get_credentials(const Repl_Agmt *ra);

+ struct berval *agmt_get_bootstrap_credentials(const Repl_Agmt *ra);

  int agmt_get_bindmethod(const Repl_Agmt *ra);

+ int32_t agmt_get_bootstrap_bindmethod(const Repl_Agmt *ra);

  Slapi_DN *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);
@@ -404,9 +412,11 @@ 

  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 agmt_set_credentials_from_entry(Repl_Agmt *ra, const Slapi_Entry *e);

+ int32_t agmt_set_bootstrap_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);

+ int32_t agmt_set_bootstrap_binddn_from_entry(Repl_Agmt *ra, const Slapi_Entry *e);

+ int agmt_set_bind_method_from_entry(Repl_Agmt *ra, const Slapi_Entry *e, PRBool bootstrap);

+ int agmt_set_transportinfo_from_entry(Repl_Agmt *ra, const Slapi_Entry *e, PRBool bootstrap);

  int agmt_set_port_from_entry(Repl_Agmt *ra, const Slapi_Entry *e);

  int agmt_set_host_from_entry(Repl_Agmt *ra, const Slapi_Entry *e);

  const char *agmt_get_long_name(const Repl_Agmt *ra);

@@ -94,9 +94,9 @@ 

      struct changecounter **changecounters; /* changes sent/skipped since server start up */

      int64_t num_changecounters;

      int64_t max_changecounters;

-     time_t last_update_start_time;       /* Local start time of last update session */

-     time_t last_update_end_time;         /* Local end time of last update session */

-     char last_update_status[STATUS_LEN]; /* Status of last update. Format = numeric code <space> textual description */

+     time_t last_update_start_time;         /* Local start time of last update session */

+     time_t last_update_end_time;           /* Local end time of last update session */

+     char last_update_status[STATUS_LEN];   /* Status of last update. Format = numeric code <space> textual description */

      char last_update_status_json[STATUS_LEN];

      PRBool update_in_progress;

      PRBool is_enabled;
@@ -129,14 +129,19 @@ 

                                * modifiersname, modifytimestamp, internalModifiersname, internalModifyTimestamp, etc */

      int64_t agreement_type;

      Slapi_Counter *protocol_timeout;

-     char *maxcsn;                /* agmt max csn */

-     int64_t flowControlWindow;   /* This is the maximum number of entries sent without acknowledgment */

-     int64_t flowControlPause;    /* When nb of not acknowledged entries overpass totalUpdateWindow

-                                   * This is the duration (in msec) that the RA will pause before sending the next entry */

-     int64_t ignoreMissingChange; /* if set replication will try to continue even if change cannot be found in changelog */

-     Slapi_RWLock *attr_lock;     /* RW lock for all the stripped attrs */

-     int64_t WaitForAsyncResults; /* Pass to DS_Sleep(PR_MillisecondsToInterval(WaitForAsyncResults))

-                                   * in repl5_inc_waitfor_async_results */

+     char *maxcsn;                      /* agmt max csn */

+     int64_t flowControlWindow;         /* This is the maximum number of entries sent without acknowledgment */

+     int64_t flowControlPause;          /* When nb of not acknowledged entries overpass totalUpdateWindow

+                                         * This is the duration (in msec) that the RA will pause before sending the next entry */

+     int64_t ignoreMissingChange;       /* if set replication will try to continue even if change cannot be found in changelog */

+     Slapi_RWLock *attr_lock;           /* RW lock for all the stripped attrs */

+     int64_t WaitForAsyncResults;       /* Pass to DS_Sleep(PR_MillisecondsToInterval(WaitForAsyncResults))

+                                         * in repl5_inc_waitfor_async_results */

+     char *bootstrapBindDN;             /* Bootstrap bind dn */

+     struct berval *bootstrapCreds;     /* Bootstrap credentials */

+     int64_t bootstrapBindmethod;       /* Bootstrap Bind Method: simple, TLS, client auth, etc */

+     uint32_t bootstrapTransportFlags;  /* Bootstrap Transport Info: LDAPS, StartTLS, etc. */

+ 

  } repl5agmt;

  

  /* Forward declarations */
@@ -144,7 +149,9 @@ 

  static void update_window_state_change_callback(void *arg, PRBool opened);

  static int get_agmt_status(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *entryAfter, int *returncode, char *returntext, void *arg);

  static int agmt_set_bind_method_no_lock(Repl_Agmt *ra, const Slapi_Entry *e);

+ static int32_t agmt_set_bootstrap_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);

+ static int32_t agmt_set_bootstrap_transportinfo_no_lock(Repl_Agmt *ra, const Slapi_Entry *e);

  static ReplicaId agmt_maxcsn_get_rid(char *maxcsn);

  static void agmt_replica_reset_ignoremissing(const Repl_Agmt *agmt);

  
@@ -309,10 +316,9 @@ 

      if (NULL == ra->binddn) {

          ra->binddn = slapi_ch_strdup("");

      }

+ 

      /* Credentials to use when binding. */

-     ra->creds = (struct berval *)slapi_ch_malloc(sizeof(struct berval));

-     ra->creds->bv_val = NULL;

-     ra->creds->bv_len = 0;

+     ra->creds = (struct berval *)slapi_ch_calloc(1, sizeof(struct berval));

      if (slapi_entry_attr_find(e, type_nsds5ReplicaCredentials, &sattr) == 0) {

          Slapi_Value *sval;

          if (slapi_attr_first_value(sattr, &sval) == 0) {
@@ -325,6 +331,22 @@ 

      /* How to bind */

      (void)agmt_set_bind_method_no_lock(ra, e);

  

+     /* Process bootstrap settings */

+     ra->bootstrapBindDN = slapi_entry_attr_get_charptr(e, type_nsds5ReplicaBootstrapBindDN);

+     ra->bootstrapCreds = (struct berval *)slapi_ch_calloc(1, sizeof(struct berval));

+     if (slapi_entry_attr_find(e, type_nsds5ReplicaBootstrapCredentials, &sattr) == 0) {

It can also be done in a function agmt_set_bootstrap_credential_no_lock

+         Slapi_Value *sval;

+         if (slapi_attr_first_value(sattr, &sval) == 0) {

+             const struct berval *bv = slapi_value_get_berval(sval);

+             if (NULL != bv) {

+                 slapi_ber_bvcpy(ra->bootstrapCreds, bv);

+             }

+         }

+     }

+     ra->bootstrapTransportFlags = 0;

+     (void)agmt_set_bootstrap_transportinfo_no_lock(ra, e);

+     (void)agmt_set_bootstrap_bind_method_no_lock(ra, e);

+ 

      /* timeout. */

      ra->timeout = DEFAULT_TIMEOUT;

      if ((val = slapi_entry_attr_get_ref(e, type_nsds5ReplicaTimeout))) {
@@ -614,6 +636,7 @@ 

      slapi_rdn_free((Slapi_RDN **)&ra->rdn);

      slapi_ch_free_string(&ra->hostname);

      slapi_ch_free_string(&ra->binddn);

+     slapi_ch_free_string(&ra->bootstrapBindDN);

      slapi_ch_array_free(ra->frac_attrs);

      slapi_ch_array_free(ra->frac_attrs_total);

      ra->frac_attr_total_defined = PR_FALSE;
@@ -621,6 +644,9 @@ 

      if (NULL != ra->creds) {

          ber_bvfree(ra->creds);

      }

+     if (NULL != ra->bootstrapCreds) {

+         ber_bvfree(ra->bootstrapCreds);

Could also be slapi_ber_bvdone that looks more the symmetric of slapi_ber_bvcpy

+     }

      if (NULL != ra->replarea) {

          /*

           * Get the replica for this agreement from the repl area
@@ -923,7 +949,7 @@ 

  uint32_t

  agmt_get_transport_flags(const Repl_Agmt *ra)

  {

-     unsigned int return_value;

+     uint32_t return_value;

      PR_ASSERT(NULL != ra);

      PR_Lock(ra->lock);

      return_value = ra->transport_flags;
@@ -931,6 +957,18 @@ 

      return return_value;

  }

  

+ uint32_t

+ agmt_get_bootstrap_transport_flags(const Repl_Agmt *ra)

+ {

+     uint32_t return_value;

+ 

+     PR_Lock(ra->lock);

+     return_value = ra->bootstrapTransportFlags;

+     PR_Unlock(ra->lock);

+ 

+     return return_value;

+ }

+ 

  /*

   * Return a copy of the bind dn to be used with this

   * agreement (may return NULL if no binddn is required,
@@ -947,6 +985,18 @@ 

      return return_value;

  }

  

+ char *

+ agmt_get_bootstrap_binddn(const Repl_Agmt *ra)

+ {

+     char *return_value;

+ 

+     PR_Lock(ra->lock);

+     return_value = ra->bootstrapBindDN == NULL ? NULL : slapi_ch_strdup(ra->bootstrapBindDN);

+     PR_Unlock(ra->lock);

+ 

+     return return_value;

+ }

+ 

  /*

   * Return a copy of the credentials.

   */
@@ -965,6 +1015,22 @@ 

      return return_value;

  }

  

+ struct berval *

+ agmt_get_bootstrap_credentials(const Repl_Agmt *ra)

+ {

+     struct berval *return_value;

+ 

+     PR_Lock(ra->lock);

+     return_value = (struct berval *)slapi_ch_malloc(sizeof(struct berval));

+     return_value->bv_val = (char *)slapi_ch_malloc(ra->bootstrapCreds->bv_len + 1);

could be replaced by slapi_ber_bvcpy (as well in agmt_get_credentials)

+     return_value->bv_len = ra->bootstrapCreds->bv_len;

+     memcpy(return_value->bv_val, ra->bootstrapCreds->bv_val, ra->bootstrapCreds->bv_len);

+     return_value->bv_val[return_value->bv_len] = '\0'; /* just in case */

+     PR_Unlock(ra->lock);

+ 

+     return return_value;

+ }

+ 

  int

  agmt_get_bindmethod(const Repl_Agmt *ra)

  {
@@ -976,6 +1042,18 @@ 

      return return_value;

  }

  

+ int32_t

bootstrapBindmethod is be int64_t

+ agmt_get_bootstrap_bindmethod(const Repl_Agmt *ra)

+ {

+     int32_t return_value;

+ 

+     PR_Lock(ra->lock);

+     return_value = ra->bootstrapBindmethod;

+     PR_Unlock(ra->lock);

+ 

+     return return_value;

+ }

+ 

  /*

   * Return a copy of the dn at the top of the replicated area.

   */
@@ -1248,6 +1326,33 @@ 

      return return_value;

  }

  

+ int32_t

+ agmt_set_bootstrap_credentials_from_entry(Repl_Agmt *ra, const Slapi_Entry *e)

+ {

+     Slapi_Attr *sattr = NULL;

+     int32_t return_value = 0;

+ 

+     slapi_entry_attr_find(e, type_nsds5ReplicaBootstrapCredentials, &sattr);

+     PR_Lock(ra->lock);

+     slapi_ber_bvdone(ra->bootstrapCreds);

+     if (NULL != sattr) {

+         Slapi_Value *sval = NULL;

+         slapi_attr_first_value(sattr, &sval);

+         if (NULL != sval) {

+             const struct berval *bv = slapi_value_get_berval(sval);

+             slapi_ber_bvcpy(ra->bootstrapCreds, bv);

+         }

+     }

+     /* If no credentials set, set to zero-length string */

+     if (ra->bootstrapCreds->bv_val == NULL) {

+         ra->bootstrapCreds->bv_val = slapi_ch_strdup("");

ra->bootstrapCreds->bv_len = 0 is not strictly necessary (slapi_ber_bvdone did it) but IMHO it helps when reading the code.

+     }

+     PR_Unlock(ra->lock);

+     prot_notify_agmt_changed(ra->protocol, ra->long_name);

+ 

+     return return_value;

+ }

+ 

  /*

   * Set or reset the DN used to bind to the remote replica.

   *
@@ -1262,7 +1367,7 @@ 

      PR_ASSERT(NULL != ra);

      slapi_entry_attr_find(e, type_nsds5ReplicaBindDN, &sattr);

      PR_Lock(ra->lock);

-     slapi_ch_free((void **)&ra->binddn);

+     slapi_ch_free_string(&ra->binddn);

      ra->binddn = NULL;

      if (NULL != sattr) {

          Slapi_Value *sval = NULL;
@@ -1281,6 +1386,34 @@ 

      return return_value;

  }

  

+ int32_t

+ agmt_set_bootstrap_binddn_from_entry(Repl_Agmt *ra, const Slapi_Entry *e)

+ {

+     Slapi_Attr *sattr = NULL;

+     int32_t return_value = 0;

+ 

+     slapi_entry_attr_find(e, type_nsds5ReplicaBootstrapBindDN, &sattr);

+     PR_Lock(ra->lock);

+     slapi_ch_free_string(&ra->bootstrapBindDN);

+     ra->bootstrapBindDN = 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);

+             ra->bootstrapBindDN = slapi_ch_strdup(val);

+         }

+     }

+     /* If no BindDN set, set to zero-length string */

+     if (ra->bootstrapBindDN == NULL) {

+         ra->bootstrapBindDN = slapi_ch_strdup("");

+     }

+     PR_Unlock(ra->lock);

+     prot_notify_agmt_changed(ra->protocol, ra->long_name);

+ 

+     return return_value;

+ }

+ 

  /*

   * Reset the port number of the remote replica.

   *
@@ -1705,8 +1838,27 @@ 

      return return_value;

  }

  

+ /* Set the bootstrap bind method, we only allow SIMPLE or SSLClientAuth */

Why not BINDMETHOD_SASL_GSSAPI ? @firstyear raised a concern of have bootstrap bind method weaker than main one ?

+ static int32_t

+ agmt_set_bootstrap_bind_method_no_lock(Repl_Agmt *ra, const Slapi_Entry *e)

+ {

+     const char *tmpstr = NULL;

+ 

+     tmpstr = slapi_entry_attr_get_ref((Slapi_Entry *)e, type_nsds5ReplicaBootstrapBindMethod);

+     if (NULL == tmpstr || strcasecmp(tmpstr, "SIMPLE") == 0) {

+         ra->bootstrapBindmethod = BINDMETHOD_SIMPLE_AUTH;

+     } else if (strcasecmp(tmpstr, "SSLCLIENTAUTH") == 0) {

+         ra->bootstrapBindmethod = BINDMETHOD_SSL_CLIENTAUTH;

+     } else {

+         return -1;

+     }

+ 

+     return 0;

+ }

+ 

+ 

  int

- agmt_set_bind_method_from_entry(Repl_Agmt *ra, const Slapi_Entry *e)

+ agmt_set_bind_method_from_entry(Repl_Agmt *ra, const Slapi_Entry *e, PRBool bootstrap)

  {

      int return_value = 0;

  
@@ -1716,7 +1868,11 @@ 

          PR_Unlock(ra->lock);

          return return_value;

      }

-     return_value = agmt_set_bind_method_no_lock(ra, e);

+     if (bootstrap) {

+         return_value = agmt_set_bootstrap_bind_method_no_lock(ra, e);

+     } else {

+         return_value = agmt_set_bind_method_no_lock(ra, e);

+     }

      PR_Unlock(ra->lock);

      prot_notify_agmt_changed(ra->protocol, ra->long_name);

      return return_value;
@@ -1746,6 +1902,25 @@ 

      return (rc);

  }

  

+ static int32_t

+ agmt_set_bootstrap_transportinfo_no_lock(Repl_Agmt *ra, const Slapi_Entry *e)

+ {

+     const char *tmpstr;

+ 

+     tmpstr = slapi_entry_attr_get_ref((Slapi_Entry *)e, type_nsds5ReplicaBootstrapTransportInfo);

+     if (!tmpstr || !strcasecmp(tmpstr, "LDAP")) {

+         ra->bootstrapTransportFlags = 0;

+     } else if (strcasecmp(tmpstr, "SSL") == 0 || strcasecmp(tmpstr, "LDAPS") == 0) {

+         ra->bootstrapTransportFlags = TRANSPORT_FLAG_LDAPS;

+     } else if (strcasecmp(tmpstr, "TLS") == 0 || strcasecmp(tmpstr, "StartTLS") == 0) {

+         ra->bootstrapTransportFlags = TRANSPORT_FLAG_STARTTLS;

+     } else {

+         return -1;

+     }

+ 

+     return 0;

+ }

+ 

  int

  agmt_set_WaitForAsyncResults(Repl_Agmt *ra, const Slapi_Entry *e)

  {
@@ -1768,7 +1943,7 @@ 

  }

  

  int

- agmt_set_transportinfo_from_entry(Repl_Agmt *ra, const Slapi_Entry *e)

+ agmt_set_transportinfo_from_entry(Repl_Agmt *ra, const Slapi_Entry *e, PRBool bootstrap)

  {

      int return_value = 0;

  
@@ -1778,6 +1953,11 @@ 

          PR_Unlock(ra->lock);

          return return_value;

      }

+     if (bootstrap) {

+         return_value = agmt_set_bootstrap_transportinfo_no_lock(ra, e);

+     } else {

+         return_value = agmt_set_transportinfo_no_lock(ra, e);

+     }

      return_value = agmt_set_transportinfo_no_lock(ra, e);

      PR_Unlock(ra->lock);

      prot_notify_agmt_changed(ra->protocol, ra->long_name);

@@ -342,6 +342,16 @@ 

                  rc = SLAPI_DSE_CALLBACK_ERROR;

              }

          } else if (slapi_attr_types_equivalent(mods[i]->mod_type,

+                                                type_nsds5ReplicaBootstrapCredentials)) {

+             /* New replica bootstrap credentials */

+             if (agmt_set_bootstrap_credentials_from_entry(agmt, e) != 0) {

+                 slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmtlist_modify_callback - "

+                         "Failed to update bootstrap credentials for agreement %s\n",

+                         agmt_get_long_name(agmt));

+                 *returncode = LDAP_OPERATIONS_ERROR;

+                 rc = SLAPI_DSE_CALLBACK_ERROR;

+             }

+         } else if (slapi_attr_types_equivalent(mods[i]->mod_type,

                                                 type_nsds5ReplicaTimeout)) {

              /* New replica timeout */

              if (agmt_set_timeout_from_entry(agmt, e) != 0) {
@@ -411,6 +421,17 @@ 

                  *returncode = LDAP_OPERATIONS_ERROR;

                  rc = SLAPI_DSE_CALLBACK_ERROR;

              }

+         } else if (slapi_attr_types_equivalent(mods[i]->mod_type,

+                                                type_nsds5ReplicaBootstrapBindDN)) {

+             /* New bootstrap Bind DN */

+             if (agmt_set_bootstrap_binddn_from_entry(agmt, e) != 0) {

+                 slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmtlist_modify_callback-  "

+                         "Failed to update bind DN for agreement %s\n",

+                         agmt_get_long_name(agmt));

+                 *returncode = LDAP_OPERATIONS_ERROR;

+                 rc = SLAPI_DSE_CALLBACK_ERROR;

+             }

+ 

          } else if (slapi_attr_types_equivalent(mods[i]->mod_type, type_nsds5ReplicaHost)) {

              /* New replica host */

              if (agmt_set_host_from_entry(agmt, e) != 0) {
@@ -447,19 +468,40 @@ 

          } else if (slapi_attr_types_equivalent(mods[i]->mod_type,

                                                 type_nsds5TransportInfo)) {

              /* New Transport info */

-             if (agmt_set_transportinfo_from_entry(agmt, e) != 0) {

+             if (agmt_set_transportinfo_from_entry(agmt, e, PR_FALSE /* get default value */) != 0) {

                  slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmtlist_modify_callback - "

-                                                                "Failed to update transport info for agreement %s\n",

-                               agmt_get_long_name(agmt));

+                         "Failed to update transport info for agreement %s\n",

+                         agmt_get_long_name(agmt));

+                 *returncode = LDAP_OPERATIONS_ERROR;

+                 rc = SLAPI_DSE_CALLBACK_ERROR;

+             }

+         } else if (slapi_attr_types_equivalent(mods[i]->mod_type,

+                                                type_nsds5ReplicaBootstrapTransportInfo))

+         {

+             /* Bootstrap Transport info */

+             if (agmt_set_transportinfo_from_entry(agmt, e, PR_TRUE /* get bootstrap value */) != 0) {

+                 slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmtlist_modify_callback - "

+                         "Failed to update bootstrap transport info for agreement %s\n",

+                         agmt_get_long_name(agmt));

                  *returncode = LDAP_OPERATIONS_ERROR;

                  rc = SLAPI_DSE_CALLBACK_ERROR;

              }

          } else if (slapi_attr_types_equivalent(mods[i]->mod_type,

                                                 type_nsds5ReplicaBindMethod)) {

-             if (agmt_set_bind_method_from_entry(agmt, e) != 0) {

+             if (agmt_set_bind_method_from_entry(agmt, e, PR_FALSE /* get default value */) != 0) {

                  slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmtlist_modify_callback - "

-                                                                "Failed to update bind method for agreement %s\n",

-                               agmt_get_long_name(agmt));

+                         "Failed to update bind method for agreement %s\n",

+                         agmt_get_long_name(agmt));

+                 *returncode = LDAP_OPERATIONS_ERROR;

+                 rc = SLAPI_DSE_CALLBACK_ERROR;

+             }

+         } else if (slapi_attr_types_equivalent(mods[i]->mod_type,

+                                                type_nsds5ReplicaBootstrapBindMethod)) {

+             /* Bootstrap bind method */

+             if (agmt_set_bind_method_from_entry(agmt, e, PR_TRUE /* get bootstrap value */) != 0) {

+                 slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmtlist_modify_callback - "

+                         "Failed to update bootstrap bind method for agreement %s\n",

+                         agmt_get_long_name(agmt));

                  *returncode = LDAP_OPERATIONS_ERROR;

                  rc = SLAPI_DSE_CALLBACK_ERROR;

              }

@@ -73,6 +73,7 @@ 

  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 int32_t conn_connect_with_bootstrap(Repl_Connection *conn, PRBool bootstrap);

  

  static int s_debug_timeout = 0;

  static int s_debug_level = 0;
@@ -1046,12 +1047,7 @@ 

  ConnResult

  conn_connect(Repl_Connection *conn)

  {

-     int optdata;

-     int secure = 0;

-     char *binddn = NULL;

-     struct berval *creds = NULL;

      ConnResult return_value = CONN_OPERATION_SUCCESS;

-     int pw_ret = 1;

  

      PR_Lock(conn->lock);

  
@@ -1060,38 +1056,84 @@ 

          PR_Unlock(conn->lock);

          return return_value;

      }

+     return_value = conn_connect_with_bootstrap(conn, PR_FALSE);

+     if (return_value != CONN_OPERATION_SUCCESS &&

+         (conn->last_ldap_error == LDAP_INVALID_CREDENTIALS ||

+          conn->last_ldap_error == LDAP_INAPPROPRIATE_AUTH ||

+          conn->last_ldap_error == LDAP_NO_SUCH_OBJECT))

+     {

+         /* try the bootstrap credentials */

+         return_value = conn_connect_with_bootstrap(conn, PR_TRUE);

+     }

  

-     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);

+     PR_Unlock(conn->lock);

+ 

+     return return_value;

+ }

+ 

+ /*

+  * There are cases when using bind DN group credentials that the consumer does

+  * not have the Bind DN group, or it is outdated.  In those cases we can try

+  * use bootstrap credentials (if set) to attempt to authenticate again.  Each

+  * new connection will always try the default credentials first, but if it

+  * fails it will try the bootstrap settings as a backup to get things synched

+  * up again, or "bootstrapped".

+  */

+ static int32_t

+ conn_connect_with_bootstrap(Repl_Connection *conn, PRBool bootstrap)

+ {

+     int32_t optdata;

+     int32_t secure = 0;

+     struct berval *creds = NULL;

+     ConnResult return_value = CONN_OPERATION_SUCCESS;

+     int32_t pw_ret = 1;

+ 

+     if (bootstrap) {

+         /* default credentials failed, try the bootstrap creds */

+         char *binddn = NULL;

+ 

+         if((binddn = agmt_get_bootstrap_binddn(conn->agmt)) == NULL) {

+             /* There are no bootstrap settings, just return error */

+             return CONN_OPERATION_FAILED;

+         }

+         slapi_ch_free_string(&conn->plain);

+         slapi_ch_free_string(&conn->binddn);

+         conn->binddn = binddn;

+         creds = agmt_get_bootstrap_credentials(conn->agmt);

+         conn->bindmethod = agmt_get_bootstrap_bindmethod(conn->agmt);

+         conn->transport_flags = agmt_get_bootstrap_transport_flags(conn->agmt);

+     } else {

+         slapi_ch_free_string(&conn->binddn);

          conn->binddn = agmt_get_binddn(conn->agmt);

+         creds = agmt_get_credentials(conn->agmt);

          conn->bindmethod = agmt_get_bindmethod(conn->agmt);

          conn->transport_flags = agmt_get_transport_flags(conn->agmt);

+     }

+ 

+     if (conn->flag_agmt_changed) {

+         /* So far we cannot change Hostname */

          conn->timeout.tv_sec = agmt_get_timeout(conn->agmt);

          conn->flag_agmt_changed = 0;

          conn->port = agmt_get_port(conn->agmt); /* port could be updated */

-         slapi_ch_free((void **)&conn->plain);

-     }

  

-     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);

+         if (bootstrap) {

+             pw_ret = pw_rever_decode(creds->bv_val, &plain, type_nsds5ReplicaBootstrapCredentials);

+         } else {

+             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_err(SLAPI_LOG_ERR, repl_plugin_name,

                            "conn_connect - %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;
@@ -1099,11 +1141,11 @@ 

          } /* 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);

+         if (!pw_ret) {

+             slapi_ch_free_string(&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.

       */
@@ -1127,7 +1169,6 @@ 

  

      if (return_value == CONN_OPERATION_SUCCESS) {

          /* Now we initialize the LDAP Structure and set options */

- 

          slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name,

                        "conn_connect - %s - Trying %s%s slapi_ldap_init_ext\n",

                        agmt_get_long_name(conn->agmt),
@@ -1153,13 +1194,10 @@ 

              goto done;

          }

  

-         /* slapi_ch_strdup is OK with NULL strings */

-         binddn = slapi_ch_strdup(conn->binddn);

- 

          slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name,

                        "conn_connect - %s - binddn = %s,  passwd = %s\n",

                        agmt_get_long_name(conn->agmt),

-                       binddn ? binddn : "NULL", creds->bv_val ? creds->bv_val : "NULL");

+                       conn->binddn ? conn->binddn : "NULL", creds->bv_val ? creds->bv_val : "NULL");

  

          /* Set some options for the connection. */

          optdata = LDAP_DEREF_NEVER; /* Don't dereference aliases */
@@ -1177,7 +1215,7 @@ 

          conn->last_operation = CONN_BIND;

      }

  

-     if (bind_and_check_pwp(conn, binddn, conn->plain) == CONN_OPERATION_FAILED) {

+     if (bind_and_check_pwp(conn, conn->binddn, conn->plain) == CONN_OPERATION_FAILED) {

          conn->last_ldap_error = slapi_ldap_get_lderrno(conn->ld, NULL, NULL);

          conn->state = STATE_DISCONNECTED;

          return_value = CONN_OPERATION_FAILED;
@@ -1189,17 +1227,17 @@ 

  

  done:

      ber_bvfree(creds);

-     creds = NULL;

Does not it leak ?
Not related to your fix. Reading the code, creds is a allocated berval that is bvcpy from the agmt credential (agmt_get_credentials). ber_bvfree frees bv_val but does it free the berval itself ?

- 

-     slapi_ch_free((void **)&binddn);

  

+     if (bootstrap) {

+         /* free "plain" so we use the default credentials on the next session */

+         slapi_ch_free_string(&conn->plain);

+     }

      if (return_value == CONN_OPERATION_SUCCESS) {

          conn->last_ldap_error = LDAP_SUCCESS;

          conn->state = STATE_CONNECTED;

      } else {

          close_connection_internal(conn);

      }

-     PR_Unlock(conn->lock);

  

      return return_value;

  }
@@ -1861,8 +1899,7 @@ 

          }

  

          if (ctrls) {

-             int i;

-             for (i = 0; ctrls[i] != NULL; ++i) {

+             for (size_t i = 0; ctrls[i] != NULL; ++i) {

                  if (!(strcmp(ctrls[i]->ldctl_oid, LDAP_CONTROL_PWEXPIRED))) {

                      /* Bind is successfull but password has expired */

                      slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name,

@@ -113,6 +113,10 @@ 

  const char *type_nsds5ReplicaFlowControlPause = "nsds5ReplicaFlowControlPause";

  const char *type_nsds5WaitForAsyncResults = "nsds5ReplicaWaitForAsyncResults";

  const char *type_replicaIgnoreMissingChange = "nsds5ReplicaIgnoreMissingChange";

+ const char *type_nsds5ReplicaBootstrapBindDN = "nsds5ReplicaBootstrapBindDN";

+ const char *type_nsds5ReplicaBootstrapCredentials = "nsds5ReplicaBootstrapCredentials";

+ const char *type_nsds5ReplicaBootstrapBindMethod = "nsds5ReplicaBootstrapBindMethod";

+ const char *type_nsds5ReplicaBootstrapTransportInfo = "nsds5ReplicaBootstrapTransportInfo";

  

  /* windows sync specific attributes */

  const char *type_nsds7WindowsReplicaArea = "nsds7WindowsReplicaSubtree";

@@ -42,6 +42,60 @@ 

      return uresult;

  }

  

+ /*

+  * Add the new replication bootstrap bind DN password attribute to the AES

+  * reversible password plugin

+  */

+ static int32_t

+ upgrade_AES_reverpwd_plugin(void)

+ {

+     Slapi_PBlock *search_pb = slapi_pblock_new();

+     Slapi_Entry *plugin_entry = NULL;

+     Slapi_DN *sdn = NULL;

+     const char *plugin_dn = "cn=AES,cn=Password Storage Schemes,cn=plugins,cn=config";

+     char *plugin_attr = "nsslapd-pluginarg2";

+     char *repl_bootstrap_val = "nsds5replicabootstrapcredentials";

+     upgrade_status uresult = UPGRADE_SUCCESS;

+ 

+     sdn = slapi_sdn_new_dn_byref(plugin_dn);

+     slapi_search_get_entry(&search_pb, sdn, NULL, &plugin_entry, NULL);

+     if (plugin_entry) {

+         if (slapi_entry_attr_get_ref(plugin_entry, plugin_attr) == NULL) {

+             /* The attribute is not set, add it */

+             Slapi_PBlock *mod_pb = slapi_pblock_new();

+             LDAPMod mod_add;

+             LDAPMod *mods[2];

+             char *add_val[2];

+             int32_t result;

+ 

+             add_val[0] = repl_bootstrap_val;

+             add_val[1] = 0;

+             mod_add.mod_op = LDAP_MOD_ADD;

+             mod_add.mod_type = plugin_attr;

+             mod_add.mod_values = add_val;

+             mods[0] = &mod_add;

+             mods[1] = 0;

+ 

+             slapi_modify_internal_set_pb(mod_pb, plugin_dn,

+                     mods, 0, 0, (void *)plugin_get_default_component_id(), 0);

+             slapi_modify_internal_pb(mod_pb);

+             slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &result);

+             if (result != LDAP_SUCCESS) {

+                 slapi_log_err(SLAPI_LOG_ERR, "upgrade_AES_reverpwd_plugin",

+                         "Failed to upgrade (%s) with new replication "

+                         "bootstrap password attribute (%s), error %d\n",

+                         plugin_dn, plugin_attr, result);

+                 uresult = UPGRADE_FAILURE;

+             }

+             slapi_pblock_destroy(mod_pb);

+         }

+     }

+     slapi_search_get_entry_done(&search_pb);

+     slapi_sdn_free(&sdn);

+ 

+     return uresult;

+ }

+ 

  #ifdef RUST_ENABLE

  static upgrade_status

  upgrade_143_entryuuid_exists(void) {
@@ -75,6 +129,10 @@ 

      }

  #endif

  

+     if (upgrade_AES_reverpwd_plugin() != UPGRADE_SUCCESS) {

Nice !!

+         return UPGRADE_FAILURE;

+     }

+ 

      return UPGRADE_SUCCESS;

  }

  

@@ -35,6 +35,12 @@ 

              agmtBindDN: "",

              agmtBindPW: "",

              agmtBindPWConfirm: "",

+             agmtBootstrap: false,

+             agmtBootstrapProtocol: "LDAP",

+             agmtBootstrapBindMethod: "SIMPLE",

+             agmtBootstrapBindDN: "",

+             agmtBootstrapBindPW: "",

+             agmtBootstrapBindPWConfirm: "",

              agmtStripAttrs: [],

              agmtFracAttrs: [],

              agmtFracInitAttrs: [],
@@ -113,6 +119,7 @@ 

  

      handleChange (e) {

          let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;

+         let attr = e.target.id;

          let time_val = "";

          let valueErr = false;

          let errObj = this.state.errObj;
@@ -123,10 +130,9 @@ 

          if (value == "") {

              valueErr = true;

          }

-         errObj[e.target.id] = valueErr;

+         errObj[attr] = valueErr;

          if (e.target.name == "agmt-modal-edit") {

              let orig_attr = "_" + e.target.id;

-             let attr = e.target.id;

              all_good = false;

              if ((attr != 'agmtHost' && this.state.agmtHost != this.state._agmtHost) ||

                  (attr != 'agmtPort' && this.state.agmtPort != this.state._agmtPort) ||
@@ -134,6 +140,7 @@ 

                  (attr != 'agmtBindMethod' && this.state.agmtBindMethod != this.state._agmtBindMethod) ||

                  (attr != 'agmtProtocol' && this.state.agmtProtocol != this.state._agmtProtocol) ||

                  (attr != 'agmtSync' && this.state.agmtSync != this.state._agmtSync) ||

+                 (attr != 'agmtBootstrap' && this.state.agmtBootstrap != this.state._agmtBootstrap) ||

                  (attr != 'agmtStripAttrs' && !this.listEqual(this.state.agmtStripAttrs, this.state._agmtStripAttrs)) ||

                  (attr != 'agmtFracAttrs' && !this.listEqual(this.state.agmtFracAttrs, this.state._agmtFracAttrs)) ||

                  (attr != 'agmtFracInitAttrs' && !this.listEqual(this.state.agmtFracInitAttrs, this.state._agmtFracInitAttrs))) {
@@ -150,6 +157,17 @@ 

                      all_good = true;

                  }

              }

+             if (this.state._agmtBootstrap) {

+                 if ((attr != 'agmtBootstrapBindDN' && this.state.agmtBootstrapBindDN != this.state._agmtBootstrapBindDN) ||

+                     (attr != 'agmtBootstrapBindPW' && this.state.agmtBootstrapBindPW != this.state._agmtBootstrapBindPW) ||

+                     (attr != 'agmtBootstrapBindPWConfirm' && this.state.agmtBootstrapBindPWConfirm != this.state._agmtBootstrapBindPWConfirm) ||

+                     (attr != 'agmtBootstrapBindMethod' && this.state.agmtBootstrapBindMethod != this.state._agmtBootstrapBindMethod) ||

+                     (attr != 'agmtBootstrapProtocol' && this.state.agmtBootstrapProtocol != this.state._agmtBootstrapProtocol)) {

+                     all_good = true;

+                 } else {

+                     all_good = false;

+                 }

+             }

              if (attr != 'agmtStripAttrs' &&

                  attr != 'agmtFracAttrs' &&

                  attr != 'agmtFracInitAttrs' &&
@@ -169,14 +187,14 @@ 

  

          if (e.target.name.startsWith("agmt-modal")) {

              // Validate modal settings "live"

-             if (e.target.id == 'agmtName') {

+             if (attr == 'agmtName') {

                  if (value == "") {

                      all_good = false;

                  }

              } else if (this.state.agmtName == "") {

                  all_good = false;

              }

-             if (e.target.id == 'agmtHost') {

+             if (attr == 'agmtHost') {

                  if (value == "") {

                      all_good = false;

                  }
@@ -185,7 +203,7 @@ 

              } else if (edit && value == this.state._agmtHost) {

                  all_good = false;

              }

-             if (e.target.id == 'agmtPort') {

+             if (attr == 'agmtPort') {

                  if (value == "") {

                      all_good = false;

                  } else if (!valid_port(value)) {
@@ -196,7 +214,7 @@ 

              } else if (this.state.agmtPort == "") {

                  all_good = false;

              }

-             if (e.target.id == 'agmtBindDN') {

+             if (attr == 'agmtBindDN') {

                  if (value == "") {

                      all_good = false;

                  }
@@ -212,7 +230,7 @@ 

                  errObj['agmtBindDN'] = true;

                  all_good = false;

              }

-             if (e.target.id == 'agmtBindPW') {

+             if (attr == 'agmtBindPW') {

                  if (value == "") {

                      all_good = false;

                  } else if (value != this.state.agmtBindPWConfirm) {
@@ -227,7 +245,7 @@ 

              } else if (this.state.agmtBindPW == "") {

                  all_good = false;

              }

-             if (e.target.id == 'agmtBindPWConfirm') {

+             if (attr == 'agmtBindPWConfirm') {

                  if (value == "") {

                      all_good = false;

                  } else if (value != this.state.agmtBindPW) {
@@ -242,7 +260,189 @@ 

              } else if (this.state.agmtBindPWConfirm == "") {

                  all_good = false;

              }

-             if (e.target.id == 'agmtSync') {

+ 

+             // Check for conflicting bind methods verses connection protocol

+             if (attr == 'agmtBindMethod') {

+                 if (value == "SSLCLIENTAUTH" && this.state.agmtProtocol == "LDAP") {

+                     modal_msg = "You must use the connection protocol LDAPS if you choose the bind method SSLCLIENTAUTH";

+                     errObj['agmtBindMethod'] = true;

+                     all_good = false;

+                 } else if (value == "SASL/GSSAPI" && this.state.agmtProtocol == "LDAPS") {

+                     // GSSAPI must be over LDAP, not LDAPS

Is this true? I thought you could use GSSAPI over LDAPS?

+                     modal_msg = "You must use the connection protocol LDAP if you choose the bind method SASL/GSSAPI";

+                     errObj['agmtBindMethod'] = true;

+                     all_good = false;

+                 } else {

+                     errObj['agmtBindMethod'] = false;

+                     errObj['agmtProtocol'] = false;

+                 }

+             } else if (attr == 'agmtProtocol') {

+                 if (value == "LDAP" && this.state.agmtBindMethod == "SSLCLIENTAUTH") {

+                     modal_msg = "You must use the connection protocol LDAPS if you choose the bind method SSLCLIENTAUTH";

+                     errObj['agmtBindMethod'] = true;

+                     all_good = false;

+                 } else if (value == "LDAPS" && this.state.agmtBindMethod == "SASL/GSSAPI") {

+                     // GSSAPI must be over LDAP, not LDAPS

+                     modal_msg = "You must use the connection protocol LDAP if you choose the bind method SASL/GSSAPI";

+                     errObj['agmtBindMethod'] = true;

+                     all_good = false;

+                 } else {

+                     errObj['agmtBindMethod'] = false;

+                     errObj['agmtProtocol'] = false;

+                 }

+             } else {

+                 if (this.state.agmtBindMethod == "SSLCLIENTAUTH" && this.state.agmtProtocol == "LDAP") {

+                     modal_msg = "You must use the connection protocol LDAPS if you choose the bind method SSLCLIENTAUTH";

+                     errObj['agmtBindMethod'] = true;

+                     errObj['agmtProtocol'] = true;

+                     all_good = false;

+                 } else {

+                     errObj['agmtBindMethod'] = false;

+                     errObj['agmtProtocol'] = false;

+                 }

+             }

+             // Handle the bootstrap settings.  There is a lot going on here.  If

+             // the Bind Method is SIMPLE we need a user password, if it's

+             // SSLCLIENTAUTH we do not need a password.  We also have to enforce

+             // LDAPS is used for SSLCLIENTAUTH.  This is similar to how we

+             // handle the agmt schedule settings.  We always need to check all

+             // the bootstrap settings if one of the bootstrap settings is

+             // changed - so there is a lot of overlap of checks, and setting and

+             // unsetting the errObj, etc

+             if (attr == 'agmtBootstrap') {

+                 if (value) {

+                     if (this.state.agmtBootstrapBindMethod == "SIMPLE") {

+                         if (this.state.agmtBootstrapBindPW == "" || this.state.agmtBootstrapBindPWConfirm == "") {

+                             // Can't be empty

+                             errObj['agmtBootstrapBindPW'] = true;

+                             errObj['agmtBootstrapBindPWConfirm'] = true;

+                             all_good = false;

+                         } else if (this.state.agmtBootstrapBindPW != this.state.agmtBootstrapBindPWConfirm) {

+                             // Must match

+                             modal_msg = "Bootstrap Passwords Do Not Match";

+                             errObj['agmtBootstrapBindPW'] = true;

+                             errObj['agmtBootstrapBindPWConfirm'] = true;

+                             all_good = false;

+                         } else {

+                             errObj['agmtBootstrapProtocol'] = false;

+                             errObj['agmtBootstrapBindMethod'] = false;

+                         }

+                     } else if (this.state.agmtBootstrapProtocol != "LDAPS") {

+                         modal_msg = "You must use the connection protocol LDAPS if you choose the bind method SSLCLIENTAUTH";

+                         errObj['agmtBootstrapProtocol'] = true;

+                         all_good = false;

+                     } else {

+                         // All good, reset the errObj

+                         errObj['agmtBootstrapProtocol'] = false;

+                         errObj['agmtBootstrapBindMethod'] = false;

+                     }

+                     if (this.state.agmtBootstrapBindDN == "" || !valid_dn(this.state.agmtBootstrapBindDN)) {

+                         errObj['agmtBootstrapBindDN'] = true;

+                         all_good = false;

+                     }

+                 }

+             } else if (this.state.agmtBootstrap) {

+                 // Check all the bootstrap settings

+                 if (attr == "agmtBootstrapBindDN") {

+                     if (value == "" || !valid_dn(value)) {

+                         errObj['agmtBootstrapBindDN'] = true;

+                         all_good = false;

+                     } else {

+                         errObj['agmtBootstrapBindDN'] = false;

+                     }

+                 } else if (this.state.agmtBootstrapBindDN == "" || !valid_dn(this.state.agmtBootstrapBindDN)) {

+                     errObj['agmtBootstrapBindDN'] = true;

+                     all_good = false;

+                 } else {

+                     // No problems here, make sure the errObj is reset

+                     errObj['agmtBootstrapBindDN'] = false;

+                 }

+ 

+                 if (attr == 'agmtBootstrapBindMethod') {

+                     // Adjusting the Bind Method, if SIMPLE then verify the

+                     // passwords are set and correct

+                     console.log("MARK proto2: ", this.state.agmtBootstrapProtocol);

+                     if (value == "SIMPLE") {

+                         if (this.state.agmtBootstrapBindPW == "" || this.state.agmtBootstrapBindPWConfirm == "") {

+                             // Can't be empty

+                             errObj['agmtBootstrapBindPW'] = true;

+                             errObj['agmtBootstrapBindPWConfirm'] = true;

+                             all_good = false;

+                         } else if (this.state.agmtBootstrapBindPW != this.state.agmtBootstrapBindPWConfirm) {

+                             // Must match

+                             modal_msg = "Bootstrap Passwords Do Not Match";

+                             errObj['agmtBootstrapBindPW'] = true;

+                             errObj['agmtBootstrapBindPWConfirm'] = true;

+                             all_good = false;

+                         }

+                     } else {

+                         // Not SIMPLE, ignore the passwords and reset errObj

+                         errObj['agmtBootstrapBindPW'] = false;

+                         errObj['agmtBootstrapBindPWConfirm'] = false;

+                         if (this.state.agmtBootstrapProtocol != "LDAPS") {

+                             modal_msg = "You must use the connection protocol LDAPS if you choose the bind method SSLCLIENTAUTH";

+                             errObj['agmtBootstrapBindMethod'] = true;

+                             all_good = false;

+                         } else {

+                             // All good, reset the errObj

+                             errObj['agmtBootstrapProtocol'] = false;

+                             errObj['agmtBootstrapBindMethod'] = false;

+                         }

+                     }

+                 } else if (this.state.agmtBootstrapBindMethod == "SIMPLE") {

+                     // Current bind method is SIMPLE, check old password values,

+                     // and new ones.

+                     if (attr == 'agmtBootstrapBindPW') {

+                         // Modifying password

+                         if (value == "") {

+                             all_good = false;

+                             errObj['agmtBootstrapBindPW'] = true;

+                         } else if (value != this.state.agmtBootstrapBindPWConfirm) {

+                             modal_msg = "Bootstrap Passwords Do Not Match";

+                             errObj['agmtBootstrapBindPW'] = true;

+                             errObj['agmtBootstrapBindPWConfirm'] = true;

+                             all_good = false;

+                         } else {

+                             errObj['agmtBootstrapBindPW'] = false;

+                             errObj['agmtBootstrapBindPWConfirm'] = false;

+                         }

+                     } else if (this.state.agmtBootstrapBindPW == "") {

+                         // Current value is no good

+                         all_good = false;

+                     }

+                     if (attr == 'agmtBootstrapBindPWConfirm') {

+                         // Modifying password confirmation

+                         if (value == "") {

+                             all_good = false;

+                             errObj['agmtBootstrapBindPWConfirm'] = true;

+                         } else if (value != this.state.agmtBootstrapBindPW) {

+                             modal_msg = "Bootstrap Passwords Do Not Match";

+                             errObj['agmtBootstrapBindPW'] = true;

+                             errObj['agmtBootstrapBindPWConfirm'] = true;

+                             all_good = false;

+                         } else {

+                             errObj['agmtBootstrapBindPW'] = false;

+                             errObj['agmtBootstrapBindPWConfirm'] = false;

+                         }

+                     } else if (this.state.agmtBootstrapBindPWConfirm == "") {

+                         // Current value is no good

+                         all_good = false;

+                     }

+                 } else {

+                     // Bind method is SSLCLIENTAUTH, make sure the connection protocol is LDAPS

+                     if (this.state.agmtBootstrapProtocol != "LDAPS") {

+                         modal_msg = "You must use the connection protocol LDAPS if you choose the bind method SSLCLIENTAUTH";

+                         errObj['agmtBootstrapProtocol'] = true;

+                         all_good = false;

+                     } else {

+                         // All good, reset the errObj

+                         errObj['agmtBootstrapProtocol'] = false;

+                         errObj['agmtBootstrapBindMethod'] = false;

+                     }

+                 }

+             }

+ 

+             if (attr == 'agmtSync') {

                  if (!value) {

                      if (this.state.agmtStartTime >= this.state.agmtEndTime) {

                          modal_schedule_msg = "Schedule start time is greater than or equal to the end time";
@@ -256,7 +456,7 @@ 

                  let days = ["agmtSyncSun", "agmtSyncMon", "agmtSyncTue", "agmtSyncWed",

                      "agmtSyncThu", "agmtSyncFri", "agmtSyncSat"];

                  for (let day of days) {

-                     if ((e.target.id != day && this.state[day]) || (e.target.id == day && value)) {

+                     if ((attr != day && this.state[day]) || (attr == day && value)) {

                          have_days = true;

                          break;

                      }
@@ -264,7 +464,7 @@ 

                  if (!have_days) {

                      modal_schedule_msg = "You must select at least one day for replication";

                      all_good = false;

-                 } else if (e.target.id == 'agmtStartTime') {

+                 } else if (attr == 'agmtStartTime') {

                      if (time_val == "") {

                          all_good = false;

                          errObj['agmtStartTime'] = true;
@@ -279,7 +479,7 @@ 

                          errObj['agmtStartTime'] = false;

                          errObj['agmtEndTime'] = false;

                      }

-                 } else if (e.target.id == 'agmtEndTime') {

+                 } else if (attr == 'agmtEndTime') {

                      if (time_val == "") {

                          errObj['agmtEndTime'] = true;

                          all_good = false;
@@ -301,8 +501,9 @@ 

              }

              // End of agmt modal live validation

          }

+ 

          this.setState({

-             [e.target.id]: value,

+             [attr]: value,

              errObj: errObj,

              agmtSaveOK: all_good,

              modalMsg: modal_msg,
@@ -433,6 +634,12 @@ 

              agmtBindDN: "",

              agmtBindPW: "",

              agmtBindPWConfirm: "",

+             agmtBootstrap: false,

+             agmtBootstrapProtocol: "LDAP",

+             agmtBootstrapBindMethod: "SIMPLE",

+             agmtBootstrapBindDN: "",

+             agmtBootstrapBindPW: "",

+             agmtBootstrapBindPWConfirm: "",

              agmtStripAttrs: [],

              agmtFracAttrs: [],

              agmtFracInitAttrs: [],
@@ -495,6 +702,12 @@ 

                      let agmtBindDN = "";

                      let agmtBindPW = "";

                      let agmtBindPWConfirm = "";

+                     let agmtBootstrap = false;

+                     let agmtBootstrapProtocol = "";

+                     let agmtBootstrapBindMethod = "";

+                     let agmtBootstrapBindDN = "";

+                     let agmtBootstrapBindPW = "";

+                     let agmtBootstrapBindPWConfirm = "";

                      let agmtStripAttrs = [];

                      let agmtFracAttrs = [];

                      let agmtFracInitAttrs = [];
@@ -532,6 +745,20 @@ 

                              agmtBindPW = val;

                              agmtBindPWConfirm = val;

                          }

+                         if (attr == "nsds5replicabootstraptransportinfo") {

+                             agmtBootstrapProtocol = val;

+                         }

+                         if (attr == "nsds5replicabootstrapbindmethod") {

+                             agmtBootstrapBindMethod = val.toUpperCase();

+                         }

+                         if (attr == "nsds5replicabootstrapbinddn") {

+                             agmtBootstrapBindDN = val;

+                             agmtBootstrap = true;

+                         }

+                         if (attr == "nsds5replicabootstrapcredentials") {

+                             agmtBootstrapBindPW = val;

+                             agmtBootstrapBindPWConfirm = val;

+                         }

                          if (attr == "nsds5replicatedattributelist") {

                              let attrs = val.replace("(objectclass=*) $ EXCLUDE", "").trim();

                              agmtFracAttrs = attrs.split(' ');
@@ -589,6 +816,12 @@ 

                              agmtBindDN: agmtBindDN,

                              agmtBindPW: agmtBindPW,

                              agmtBindPWConfirm: agmtBindPWConfirm,

+                             agmtBootstrap: agmtBootstrap,

+                             agmtBootstrapProtocol: agmtBootstrapProtocol,

+                             agmtBootstrapBindMethod: agmtBootstrapBindMethod,

+                             agmtBootstrapBindDN: agmtBootstrapBindDN,

+                             agmtBootstrapBindPW: agmtBootstrapBindPW,

+                             agmtBootstrapBindPWConfirm: agmtBootstrapBindPWConfirm,

                              agmtStripAttrs: agmtStripAttrs,

                              agmtFracAttrs: agmtFracAttrs,

                              agmtFracInitAttrs: agmtFracInitAttrs,
@@ -603,6 +836,8 @@ 

                              agmtStartTime: agmtStartTime,

                              agmtEndTime: agmtEndTime,

                              agmtSaveOK: false,

+                             modalMsg: "",

+                             errObj: {},

                              // Record original values before editing

                              _agmtName: agmtName,

                              _agmtHost: agmtHost,
@@ -612,6 +847,12 @@ 

                              _agmtBindDN: agmtBindDN,

                              _agmtBindPW: agmtBindPW,

                              _agmtBindPWConfirm: agmtBindPWConfirm,

+                             _agmtBootstrap: agmtBootstrap,

+                             _agmtBootstrapProtocol: agmtBootstrapProtocol,

+                             _agmtBootstrapBindMethod: agmtBootstrapBindMethod,

+                             _agmtBootstrapBindDN: agmtBootstrapBindDN,

+                             _agmtBootstrapBindPW: agmtBootstrapBindPW,

+                             _agmtBootstrapBindPWConfirm: agmtBootstrapBindPWConfirm,

                              _agmtStripAttrs: agmtStripAttrs,

                              _agmtFracAttrs: agmtFracAttrs,

                              _agmtFracInitAttrs: agmtFracInitAttrs,
@@ -699,6 +940,20 @@ 

          if (this.state.agmtPort != this.state._agmtPort) {

              cmd.push('--port=' + this.state.agmtPort);

          }

+         if (this.state.agmtBootstrap) {

+             if (this.state.agmtBootstrapBindMethod != this.state._agmtBootstrapBindMethod) {

+                 cmd.push('--bootstrap-bind-method=' + this.state.agmtBootstrapBindMethod);

+             }

+             if (this.state.agmtBootstrapProtocol != this.state._agmtBootstrapProtocol) {

+                 cmd.push('--bootstrap-conn-protocol=' + this.state.agmtBootstrapProtocol);

+             }

+             if (this.state.agmtBootstrapBindPW != this.state._agmtBootstrapBindPW) {

+                 cmd.push('--bootstrap-bind-passwd=' + this.state.agmtBootstrapBindPW);

+             }

+             if (this.state.agmtBootstrapBindDN != this.state._agmtBootstrapBindDN) {

+                 cmd.push('--bootstrap-bind-dn=' + this.state.agmtBootstrapBindDN);

+             }

+         }

  

          this.setState({

              savingAgmt: true
@@ -933,6 +1188,21 @@ 

              cmd.push('--strip-list=' + this.state.agmtStripAttrs.join(' '));

          }

  

+         if (this.state.agmtBootstrap) {

+             if (this.state.agmtBootstrapBindDN != "") {

+                 cmd.push('--bootstrap-bind-dn=' + this.state.agmtBootstrapBindDN);

+             }

+             if (this.state.agmtBootstrapBindDNPW != "") {

+                 cmd.push('--bootstrap-bind-passwd=' + this.state.agmtBootstrapBindDNPW);

+             }

+             if (this.state.agmtBootstrapBindMethod != "") {

+                 cmd.push('--bootstrap-bind-method=' + this.state.agmtBootstrapBindMethod);

+             }

+             if (this.state.agmtBootstrapProtocol != "") {

+                 cmd.push('--bootstrap-conn-protocol=' + this.state.agmtBootstrapProtocol);

+             }

+         }

+ 

          this.setState({

              savingAgmt: true

          });
@@ -1030,6 +1300,12 @@ 

                      agmtBindPWConfirm={this.state.agmtBindPWConfirm}

                      agmtProtocol={this.state.agmtProtocol}

                      agmtBindMethod={this.state.agmtBindMethod}

+                     agmtBootstrap={this.state.agmtBootstrap}

+                     agmtBootstrapBindDN={this.state.agmtBootstrapBindDN}

+                     agmtBootstrapBindPW={this.state.agmtBootstrapBindPW}

+                     agmtBootstrapBindPWConfirm={this.state.agmtBootstrapBindPWConfirm}

+                     agmtBootstrapProtocol={this.state.agmtBootstrapProtocol}

+                     agmtBootstrapBindMethod={this.state.agmtBootstrapBindMethod}

                      agmtStripAttrs={this.state.agmtStripAttrs}

                      agmtFracAttrs={this.state.agmtFracAttrs}

                      agmtFracInitAttrs={this.state.agmtFracInitAttrs}
@@ -1066,6 +1342,12 @@ 

                      agmtBindPWConfirm={this.state.agmtBindPWConfirm}

                      agmtProtocol={this.state.agmtProtocol}

                      agmtBindMethod={this.state.agmtBindMethod}

+                     agmtBootstrap={this.state.agmtBootstrap}

+                     agmtBootstrapBindDN={this.state.agmtBootstrapBindDN}

+                     agmtBootstrapBindPW={this.state.agmtBootstrapBindPW}

+                     agmtBootstrapBindPWConfirm={this.state.agmtBootstrapBindPWConfirm}

+                     agmtBootstrapProtocol={this.state.agmtBootstrapProtocol}

+                     agmtBootstrapBindMethod={this.state.agmtBootstrapBindMethod}

                      agmtStripAttrs={this.state.agmtStripAttrs}

                      agmtFracAttrs={this.state.agmtFracAttrs}

                      agmtFracInitAttrs={this.state.agmtFracInitAttrs}

@@ -547,6 +547,12 @@ 

              agmtBindDN,

              agmtBindPW,

              agmtBindPWConfirm,

+             agmtBootstrap,

+             agmtBootstrapBindDN,

+             agmtBootstrapBindPW,

+             agmtBootstrapBindPWConfirm,

+             agmtBootstrapProtocol,

+             agmtBootstrapBindMethod,

              agmtStripAttrs,

              agmtFracAttrs,

              agmtFracInitAttrs,
@@ -572,6 +578,10 @@ 

          let errMsgClass = "ds-center ds-modal-error";

          let errMsg = errorMsg;

          let name = "agmt-modal";

+         let bootstrapTitle = "If you are using Bind Group's on the consumer " +

+             "replica you can configure bootstrap credentials that can be used " +

+             "to do online initializations, or bootstrap a session if the bind " +

+             "groups get out of synchronization";

  

          if (this.props.edit) {

              title = "Edit";
@@ -608,6 +618,91 @@ 

                  </Row>;

          }

  

+         let bootstrapRow =

+             <div className="ds-left-indent-md">

+                 <Row className="ds-margin-top-lg" title="The Bind DN the agreement can use to bootstrap initialization">

+                     <Col componentClass={ControlLabel} sm={4}>

+                         Bind DN

+                     </Col>

+                     <Col sm={8}>

+                         <FormControl

+                             id="agmtBootstrapBindDN"

+                             type="text"

+                             name={name}

+                             className={error.agmtBootstrapBindDN ? "ds-input-bad" : ""}

+                             onChange={handleChange}

+                             autoComplete="false"

+                             defaultValue={agmtBootstrapBindDN}

+                         />

+                     </Col>

+                 </Row>

+                 <Row className="ds-margin-top">

+                     <Col componentClass={ControlLabel} sm={4} title="The Bind DN password for bootstrap initialization">

+                         Password

+                     </Col>

+                     <Col sm={8}>

+                         <FormControl

+                             id="agmtBootstrapBindPW"

+                             type="password"

+                             name={name}

+                             className={error.agmtBootstrapBindPW ? "ds-input-bad" : ""}

+                             onChange={handleChange}

+                             autoComplete="new-password"

+                             defaultValue={agmtBootstrapBindPW}

+                         />

+                     </Col>

+                 </Row>

+                 <Row className="ds-margin-top">

+                     <Col componentClass={ControlLabel} sm={4} title="Confirm the Bind DN password for bootstrap initialization">

+                         Confirm Password

+                     </Col>

+                     <Col sm={8}>

+                         <FormControl

+                             id="agmtBootstrapBindPWConfirm"

+                             type="password"

+                             name={name}

+                             className={error.agmtBootstrapBindPWConfirm ? "ds-input-bad" : ""}

+                             onChange={handleChange}

+                             autoComplete="new-password"

+                             defaultValue={agmtBootstrapBindPWConfirm}

+                         />

+                     </Col>

+                 </Row>

+                 <Row className="ds-margin-top">

+                     <Col componentClass={ControlLabel} sm={4} title="The connection protocol for bootstrap initialization">

+                         Connection Protocol

+                     </Col>

+                     <Col sm={8}>

+                         <select className={error.agmtBootstrapProtocol ? "btn btn-default dropdown ds-input-bad" : "btn btn-default dropdown"}

+                             id="agmtBootstrapProtocol"

+                             defaultValue={agmtBootstrapProtocol}

+                             name={name}

+                             onChange={handleChange}

+                         >

+                             <option>LDAP</option>

+                             <option>LDAPS</option>

+                             <option title="Currently not recommended">StartTLS</option>

+                         </select>

+                     </Col>

+                 </Row>

+                 <Row className="ds-margin-top">

+                     <Col componentClass={ControlLabel} sm={4} title="The authentication method for bootstrap initialization">

+                         Authentication Method

+                     </Col>

+                     <Col sm={8}>

+                         <select className={error.agmtBootstrapBindMethod ? "btn btn-default dropdown ds-input-bad" : "btn btn-default dropdown"}

+                             defaultValue={agmtBootstrapBindMethod}

+                             id="agmtBootstrapBindMethod"

+                             name={name}

+                             onChange={handleChange}

+                         >

+                             <option title="Use a bind DN and password">SIMPLE</option>

+                             <option title="Use a SSL/TLS Client Certificate">SSLCLIENTAUTH</option>

+                         </select>

+                     </Col>

+                 </Row>

+             </div>;

+ 

          let scheduleRow =

              <div className="ds-left-indent-md">

                  <Row className="ds-margin-top-lg">
@@ -731,13 +826,17 @@ 

                              className={error.agmtEndTime ? "ds-input-bad" : ""}

                              onChange={handleChange}

                              defaultValue={