#51252 Add failover credentials to replication agreement
Closed 4 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={agmtEndTime}

-                         />                    </Col>

+                         />

+                     </Col>

                  </Row>

              </div>;

  

          if (agmtSync) {

              scheduleRow = "";

          }

+         if (!agmtBootstrap) {

+             bootstrapRow = "";

+         }

          return (

              <Modal show={showModal} onHide={closeHandler}>

                  <div className="ds-no-horizontal-scrollbar">
@@ -860,7 +959,12 @@ 

                                      Connection Protocol

                                  </Col>

                                  <Col sm={8}>

-                                     <select className="btn btn-default dropdown" id="agmtProtocol" defaultValue={agmtProtocol} name={name} onChange={handleChange}>

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

+                                         id="agmtProtocol"

+                                         defaultValue={agmtProtocol}

+                                         name={name}

+                                         onChange={handleChange}

+                                     >

                                          <option>LDAP</option>

                                          <option>LDAPS</option>

                                          <option title="Currently not recommended">StartTLS</option>
@@ -872,7 +976,12 @@ 

                                      Authentication Method

                                  </Col>

                                  <Col sm={8}>

-                                     <select className="btn btn-default dropdown" defaultValue={agmtBindMethod} id="agmtBindMethod" name={name} onChange={handleChange}>

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

+                                         defaultValue={agmtBindMethod}

+                                         id="agmtBindMethod"

+                                         name={name}

+                                         onChange={handleChange}

+                                     >

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

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

                                          <option title="Use SASL Digest-MD5">SASL/DIGEST-MD5</option>
@@ -887,7 +996,7 @@ 

                                  textClosed="Show Advanced Settings"

                              >

                                  <div className="ds-margin-left">

-                                     <Row className="ds-margin-top" title="Attribute to exclude from replication">

+                                     <Row className="ds-margin-top-lg" title="Attribute to exclude from replication">

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

                                              Exclude Attributes

                                          </Col>
@@ -938,6 +1047,22 @@ 

                                              />

                                          </Col>

                                      </Row>

+                                     <hr />

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

+                                         <Col sm={8}>

+                                             <Checkbox

+                                                 id="agmtBootstrap"

+                                                 defaultChecked={agmtBootstrap}

+                                                 onChange={handleChange}

+                                                 name={name}

+                                                 title={bootstrapTitle}

+                                             >

+                                                 Configure Bootstrap Settings

+                                             </Checkbox>

+                                         </Col>

+                                     </Row>

+                                     {bootstrapRow}

+                                     <hr />

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

                                          <Col sm={8}>

                                              <Checkbox
@@ -1543,6 +1668,12 @@ 

      agmtBindDN: PropTypes.string,

      agmtBindPW: PropTypes.string,

      agmtBindPWConfirm: PropTypes.string,

+     agmtBootstrap: PropTypes.bool,

+     agmtBootstrapProtocol: PropTypes.string,

+     agmtBootstrapBindMethod: PropTypes.string,

+     agmtBootstrapBindDN: PropTypes.string,

+     agmtBootstrapBindPW: PropTypes.string,

+     agmtBootstrapBindPWConfirm: PropTypes.string,

      agmtStripAttrs: PropTypes.array,

      agmtFracAttrs: PropTypes.array,

      agmtFracInitAttrs: PropTypes.array,
@@ -1580,6 +1711,12 @@ 

      agmtBindDN: "",

      agmtBindPW: "",

      agmtBindPWConfirm: "",

+     agmtBootstrap: false,

+     agmtBootstrapProtocol: "LDAP",

+     agmtBootstrapBindMethod: "SIMPLE",

+     agmtBootstrapBindDN: "",

+     agmtBootstrapBindPW: "",

+     agmtBootstrapBindPWConfirm: "",

      agmtStripAttrs: [],

      agmtFracAttrs: [],

      agmtFracInitAttrs: [],

@@ -49,6 +49,10 @@ 

          'bind_dn': 'nsds5replicabinddn',

          'bind_passwd': 'nsds5replicacredentials',

          'bind_method': 'nsds5replicabindmethod',

+         'bootstrap_conn_protocol': 'nsds5replicabootstraptransportinfo',

+         'bootstrap_bind_dn': 'nsds5replicabootstrapbinddn',

+         'bootstrap_bind_passwd': 'nsds5replicabootstrapcredentials',

+         'bootstrap_bind_method': 'nsds5replicabootstrapbindmethod',

          'frac_list': 'nsds5replicatedattributelist',

          'frac_list_total': 'nsds5replicatedattributelisttotal',

          'strip_list': 'nsds5replicastripattrs',
@@ -724,6 +728,24 @@ 

      if args.strip_list is not None:

          properties['nsds5replicastripattrs'] = args.strip_list

  

+     # Handle the optional bootstrap settings

+     if args.bootstrap_bind_dn is not None:

+         if not is_a_dn(args.bootstrap_bind_dn):

+             raise ValueError("The replica bootstrap bind DN is not a valid DN")

+         properties['nsDS5ReplicaBootstrapBindDN'] = args.bootstrap_bind_dn

+     if args.bootstrap_bind_passwd is not None:

+         properties['nsDS5ReplicaBootstrapCredentials'] = args.bootstrap_bind_passwd

+     if args.bootstrap_bind_method is not None:

+         bs_bind_method = args.bootstrap_bind_method.lower()

+         if bs_bind_method != "simple" and bs_bind_method != "sslclientauth":

+             raise ValueError('Bootstrap bind method can only be "SIMPLE" or "SSLCLIENTAUTH"')

+         properties['nsDS5ReplicaBootstrapBindMethod'] = args.bootstrap_bind_method

+     if args.bootstrap_conn_protocol is not None:

+         bootstrap_conn_protocol = args.bootstrap_conn_protocol.lower()

+         if bootstrap_conn_protocol != "ldap" and bootstrap_conn_protocol != "ldaps" and bootstrap_conn_protocol != "starttls":

+             raise ValueError('Bootstrap connection protocol can only be "LDAP", "LDAPS", or "STARTTLS"')

+         properties['nsDS5ReplicaBootstrapTransportInfo'] = args.bootstrap_conn_protocol

+ 

      # We do need the bind dn and credentials for none-sasl bind methods

      if (bind_method in ('simple', 'sslclientauth')) and (args.bind_dn is None or args.bind_passwd is None):

          raise ValueError("You need to set the bind dn (--bind-dn) and the password (--bind-passwd) for bind method ({})".format(bind_method))
@@ -795,6 +817,14 @@ 

                  for frac_attr in value.split():

                      frac_list += " " + frac_attr

                  value = frac_list

+             elif attr == 'nsds5replicabootstrapbindmethod':

+                 bs_bind_method = value.lower()

+                 if bs_bind_method != "simple" and bs_bind_method != "sslclientauth":

+                     raise ValueError('Bootstrap bind method can only be "SIMPLE" or "SSLCLIENTAUTH"')

+             elif attr == 'nsds5replicabootstraptransportinfo':

+                 bs_conn_protocol = value.lower()

+                 if bs_conn_protocol != "ldap" and bs_conn_protocol != "ldaps" and bs_conn_protocol != "starttls":

+                     raise ValueError('Bootstrap bind method can only be "LDAP", "LDAPS, or "STARTTLS"')

              modlist.append((attr, value))

  

      if len(modlist) > 0:
@@ -1332,7 +1362,7 @@ 

      agmt_add_parser.add_argument('--strip-list', help="A list of attributes that are removed from updates only if the event "

                                                        "would otherwise be empty.  Typically this is set to \"modifiersname\" and \"modifytimestmap\"")

      agmt_add_parser.add_argument('--schedule', help="Sets the replication update schedule: 'HHMM-HHMM DDDDDDD'  D = 0-6 (Sunday - Saturday).")

-     agmt_add_parser.add_argument('--conn-timeout', help="The timeout used for replicaton connections")

+     agmt_add_parser.add_argument('--conn-timeout', help="The timeout used for replication connections")

      agmt_add_parser.add_argument('--protocol-timeout', help="A timeout in seconds on how long to wait before stopping "

                                                              "replication when the server is under load")

      agmt_add_parser.add_argument('--wait-async-results', help="The amount of time in milliseconds the server waits if "
@@ -1343,6 +1373,10 @@ 

      agmt_add_parser.add_argument('--session-pause-time', help="The amount of time in seconds a supplier should wait between update sessions.")

      agmt_add_parser.add_argument('--flow-control-window', help="Sets the maximum number of entries and updates sent by a supplier, which are not acknowledged by the consumer.")

      agmt_add_parser.add_argument('--flow-control-pause', help="The time in milliseconds to pause after reaching the number of entries and updates set in \"--flow-control-window\"")

+     agmt_add_parser.add_argument('--bootstrap-bind-dn', help="An optional Bind DN the agreement can use to bootstrap initialization when bind groups are being used")

+     agmt_add_parser.add_argument('--bootstrap-bind-passwd', help="The bootstrap credentials for the Bind DN")

+     agmt_add_parser.add_argument('--bootstrap-conn-protocol', help="The replication bootstrap connection protocol: LDAP, LDAPS, or StartTLS")

+     agmt_add_parser.add_argument('--bootstrap-bind-method', help="The bind method: \"SIMPLE\", or \"SSLCLIENTAUTH\"")

      agmt_add_parser.add_argument('--init', action='store_true', default=False, help="Initialize the agreement after creating it.")

  

      # Set - Note can not use add's parent args because for "set" there are no "required=True" args
@@ -1361,7 +1395,7 @@ 

      agmt_set_parser.add_argument('--strip-list', help="A list of attributes that are removed from updates only if the event "

                                                        "would otherwise be empty.  Typically this is set to \"modifiersname\" and \"modifytimestmap\"")

      agmt_set_parser.add_argument('--schedule', help="Sets the replication update schedule: 'HHMM-HHMM DDDDDDD'  D = 0-6 (Sunday - Saturday).")

-     agmt_set_parser.add_argument('--conn-timeout', help="The timeout used for replicaton connections")

+     agmt_set_parser.add_argument('--conn-timeout', help="The timeout used for replication connections")

      agmt_set_parser.add_argument('--protocol-timeout', help="A timeout in seconds on how long to wait before stopping "

                                                              "replication when the server is under load")

      agmt_set_parser.add_argument('--wait-async-results', help="The amount of time in milliseconds the server waits if "
@@ -1371,6 +1405,10 @@ 

      agmt_set_parser.add_argument('--session-pause-time', help="The amount of time in seconds a supplier should wait between update sessions.")

      agmt_set_parser.add_argument('--flow-control-window', help="Sets the maximum number of entries and updates sent by a supplier, which are not acknowledged by the consumer.")

      agmt_set_parser.add_argument('--flow-control-pause', help="The time in milliseconds to pause after reaching the number of entries and updates set in \"--flow-control-window\"")

+     agmt_set_parser.add_argument('--bootstrap-bind-dn', help="An optional Bind DN the agreement can use to bootstrap initialization when bind groups are being used")

+     agmt_set_parser.add_argument('--bootstrap-bind-passwd', help="The bootstrap credentials for the Bind DN")

+     agmt_set_parser.add_argument('--bootstrap-conn-protocol', help="The replication bootstrap connection protocol: LDAP, LDAPS, or StartTLS")

+     agmt_set_parser.add_argument('--bootstrap-bind-method', help="The bind method: \"SIMPLE\", or \"SSLCLIENTAUTH\"")

  

      # Get

      agmt_get_parser = agmt_subcommands.add_parser('get', help='Get replication configuration')

Add replication bootstrap credentials for when replication bind groups fail to authenticate. This allows replication to bootstrap/reset and allow replication to get in sync. Replication will always attempt the default credentials first, but if things get out of sync it can try the bootstrap credentials.

https://www.port389.org/docs/389ds/design/repl-agmt-bootstrap-design.html

This PR is made up of 4 commits to break apart each component: core server, CLI, UI, and upgrade

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

without rehashing my previous comments about this feature and it's threat/security implications, perhaps if this is to be a bootstrap only credential, than after next successful incremental repl with the primary creds, can we remove the bootstrap password? That way the bootstrap password is only needed to be added in the case of recovery scenarioes?

without rehashing my previous comments about this feature and it's threat/security implications, perhaps if this is to be a bootstrap only credential, than after next successful incremental repl with the primary creds, can we remove the bootstrap password? That way the bootstrap password is only needed to be added in the case of recovery scenarioes?

No not really, because one of the scenarios that brought this up happened during OS upgrade. So we always need valid credentials. Waiting for replication to break before resetting the bootstrap password kind of defeats the purpose of this. It's not just for initialization, it can happen in other scenarios. The upgrade scenario is one (incremental update), and also if replica that was disabled and then re-enabled later(incremental or total). I know you don't like this patch, you made that very obvious with all your previous comments, but people are not forced to use this feature. This is optional, and the "risks" will be documented.

Hmmm okay. If that's scenario was accounted for than thats fine. :)

@tbordaz i've already checked this and it was okay to me, can you check too just to be completely sure it's good to go?

Could also be slapi_ber_bvdone that looks more the symmetric of slapi_ber_bvcpy

It can also be done in a function agmt_set_bootstrap_credential_no_lock

could be replaced by slapi_ber_bvcpy (as well in agmt_get_credentials)

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

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

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 ?

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

Well, you can't really have two outbound identities with GSSAPI, and if your GSSAPI is broken (more than likely) you'll need to use dn + pw to resolve it which I think is the intent of this ticket. Which as mentioned, does mean that the security becomes the baseline of dn + pw and the benefit of GSSAPI at all in this becomes questionable :)

So as it stands, provided people are willing to accept that having GSSAPI + fallback dn + pw is equivalent to "dn + pw only" in security, than it's no problem to allow the current code.

Okay I am fine dropping GSSAPI as fallback method.
Note that the intention was to not put any limit on how fallback works. For example an admin may prefer dn+pw as primary method, because it is faster, and fallback to a more expensive/slow GSSAPI.

389-ds-base is moving from Pagure to Github. This means that new issues and pull requests
will be accepted only in 389-ds-base's github repository.

This pull request has been cloned to Github as issue and is available here:
- https://github.com/389ds/389-ds-base/issues/4305

If you want to continue to work on the PR, please navigate to the github issue,
download the patch from the attachments and file a new pull request.

Thank you for understanding. We apologize for all inconvenience.

Pull-Request has been closed by spichugi

4 years ago