import 389-ds-base-1.4.3.23-12.module+el8.5.0+13329+4096c77a
This commit is contained in:
		
							parent
							
								
									8a7f112eaf
								
							
						
					
					
						commit
						7900475d31
					
				| @ -0,0 +1,415 @@ | ||||
| From 18a8ed29ae0b300083a6b83665b0137948a2ef7c Mon Sep 17 00:00:00 2001 | ||||
| From: tbordaz <tbordaz@redhat.com> | ||||
| Date: Thu, 23 Sep 2021 10:48:50 +0200 | ||||
| Subject: [PATCH 1/3] Issue 4925 - Performance ACI: targetfilter evaluation | ||||
|  result can be reused (#4926) | ||||
| 
 | ||||
| Bug description: | ||||
| 	An ACI may contain targetfilter. For a given returned entry, of a | ||||
|         SRCH request, the same targetfilter is evaluated for each of the | ||||
|         returned attributes. | ||||
|         Once the filter has been evaluated, it is useless to reevaluate | ||||
|         it for a next attribute. | ||||
| 
 | ||||
| Fix description: | ||||
| 	The fix implements a very simple cache (linked list) that keeps | ||||
|         the results of the previously evaluated 'targetfilter'. | ||||
|         This cache is per-entry. For an operation, a aclpb is allocated | ||||
|         that is used to evaluate ACIs against each successive entry. | ||||
|         Each time a candidate entry is added in the aclpb | ||||
|         (acl_access_allowed), the cache (aclpb_curr_entry_targetfilters) | ||||
|         is freed. Then for each 'targetfilter', the original targetfilter | ||||
|         is lookup from the cache. If this is the first evaluation of it | ||||
|         then the result of the evaluation is stored into the cache using | ||||
|         the original targetfilter as the key in the cache | ||||
| 
 | ||||
| 	The key to lookup/store the cache is the string representation | ||||
|         of the targetfilter. The string contains a redzone to detect | ||||
|         that the filter exceeds the maximum size (2K). If it exceeds | ||||
|         then the key is invalid and the lookup/store is noop. | ||||
| 
 | ||||
| relates: #4925 | ||||
| 
 | ||||
| Reviewed by: Mark Reynolds, William Brown (Thanks) | ||||
| 
 | ||||
| Platforms tested: F34 | ||||
| ---
 | ||||
|  ldap/servers/plugins/acl/acl.c     | 138 +++++++++++++++++++++++++++-- | ||||
|  ldap/servers/plugins/acl/acl.h     |  14 +++ | ||||
|  ldap/servers/plugins/acl/acl_ext.c |  12 +++ | ||||
|  ldap/servers/slapd/libglobs.c      |  29 ++++++ | ||||
|  ldap/servers/slapd/proto-slap.h    |   2 + | ||||
|  ldap/servers/slapd/slap.h          |   2 + | ||||
|  6 files changed, 191 insertions(+), 6 deletions(-) | ||||
| 
 | ||||
| diff --git a/ldap/servers/plugins/acl/acl.c b/ldap/servers/plugins/acl/acl.c
 | ||||
| index 4e811f73a..377c18e68 100644
 | ||||
| --- a/ldap/servers/plugins/acl/acl.c
 | ||||
| +++ b/ldap/servers/plugins/acl/acl.c
 | ||||
| @@ -67,6 +67,9 @@ static void print_access_control_summary(char *source,
 | ||||
|                                           const char *edn, | ||||
|                                           aclResultReason_t *acl_reason); | ||||
|  static int check_rdn_access(Slapi_PBlock *pb, Slapi_Entry *e, const char *newrdn, int access); | ||||
| +static struct targetfilter_cached_result *targetfilter_cache_lookup(struct acl_pblock *aclpb, char *filter, PRBool filter_valid);
 | ||||
| +static void targetfilter_cache_add(struct acl_pblock *aclpb, char *filter, int result, PRBool filter_valid);
 | ||||
| +
 | ||||
|   | ||||
|   | ||||
|  /* | ||||
| @@ -176,6 +179,70 @@ check_rdn_access(Slapi_PBlock *pb, Slapi_Entry *e, const char *dn, int access)
 | ||||
|      return (retCode); | ||||
|  } | ||||
|   | ||||
| +/* Retrieves, in the targetfilter cache (list), this
 | ||||
| + * filter in case it was already evaluated
 | ||||
| + *
 | ||||
| + * filter: key to retrieve the evaluation in the cache
 | ||||
| + * filter_valid: PR_FALSE means that the filter key is truncated, PR_TRUE else
 | ||||
| + */
 | ||||
| +static struct targetfilter_cached_result *
 | ||||
| +targetfilter_cache_lookup(struct acl_pblock *aclpb, char *filter, PRBool filter_valid)
 | ||||
| +{
 | ||||
| +    struct targetfilter_cached_result *results;
 | ||||
| +    if (! aclpb->targetfilter_cache_enabled) {
 | ||||
| +        /* targetfilter cache is disabled */
 | ||||
| +        return NULL;
 | ||||
| +    }
 | ||||
| +    if (filter == NULL) {
 | ||||
| +        return NULL;
 | ||||
| +    }
 | ||||
| +    for(results = aclpb->aclpb_curr_entry_targetfilters; results; results = results->next) {
 | ||||
| +        if (strcmp(results->filter, filter) == 0) {
 | ||||
| +            return results;
 | ||||
| +        }
 | ||||
| +    }
 | ||||
| +
 | ||||
| +    return NULL;
 | ||||
| +}
 | ||||
| +
 | ||||
| +/* Free all evaluations cached for this current entry */
 | ||||
| +void
 | ||||
| +targetfilter_cache_free(struct acl_pblock *aclpb)
 | ||||
| +{
 | ||||
| +    struct targetfilter_cached_result *results, *next;
 | ||||
| +    if (aclpb == NULL) {
 | ||||
| +        return;
 | ||||
| +    }
 | ||||
| +    for(results = aclpb->aclpb_curr_entry_targetfilters; results;) {
 | ||||
| +        next = results->next;
 | ||||
| +        slapi_ch_free_string(&results->filter);
 | ||||
| +        slapi_ch_free((void **) &results);
 | ||||
| +        results = next;
 | ||||
| +    }
 | ||||
| +    aclpb->aclpb_curr_entry_targetfilters = NULL;
 | ||||
| +}
 | ||||
| +
 | ||||
| +/* add a new targetfilter evaluation into the cache (per entry)
 | ||||
| + * ATM just use a linked list of evaluation
 | ||||
| + *
 | ||||
| + * filter: key to retrieve the evaluation in the cache
 | ||||
| + * result: result of the evaluation
 | ||||
| + * filter_valid: PR_FALSE means that the filter key is truncated, PR_TRUE else
 | ||||
| + */
 | ||||
| +static void
 | ||||
| +targetfilter_cache_add(struct acl_pblock *aclpb, char *filter, int result, PRBool filter_valid)
 | ||||
| +{
 | ||||
| +    struct targetfilter_cached_result *results;
 | ||||
| +    if (! filter_valid || ! aclpb->targetfilter_cache_enabled) {
 | ||||
| +        /* targetfilter cache is disabled or filter is truncated */
 | ||||
| +        return;
 | ||||
| +    }
 | ||||
| +    results = (struct targetfilter_cached_result *) slapi_ch_calloc(1, (sizeof(struct targetfilter_cached_result)));
 | ||||
| +    results->filter = slapi_ch_strdup(filter);
 | ||||
| +    results->next = aclpb->aclpb_curr_entry_targetfilters;
 | ||||
| +    results->matching_result = result;
 | ||||
| +    aclpb->aclpb_curr_entry_targetfilters = results;
 | ||||
| +}
 | ||||
|  /*************************************************************************** | ||||
|  * | ||||
|  * acl_access_allowed | ||||
| @@ -496,6 +563,7 @@ acl_access_allowed(
 | ||||
|   | ||||
|          /* Keep the ptr to the current entry */ | ||||
|          aclpb->aclpb_curr_entry = (Slapi_Entry *)e; | ||||
| +        targetfilter_cache_free(aclpb);
 | ||||
|   | ||||
|          /* Get the attr info */ | ||||
|          deallocate_attrEval = acl__get_attrEval(aclpb, attr); | ||||
| @@ -1924,7 +1992,7 @@ acl_modified(Slapi_PBlock *pb, int optype, Slapi_DN *e_sdn, void *change)
 | ||||
|  *    None. | ||||
|  * | ||||
|  **************************************************************************/ | ||||
| -static int
 | ||||
| +int
 | ||||
|  acl__scan_for_acis(Acl_PBlock *aclpb, int *err) | ||||
|  { | ||||
|      aci_t *aci; | ||||
| @@ -2405,10 +2473,68 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a
 | ||||
|                                                      ACL_EVAL_TARGET_FILTER); | ||||
|              slapi_ch_free((void **)&lasinfo); | ||||
|          } else { | ||||
| -            if (slapi_vattr_filter_test(NULL, aclpb->aclpb_curr_entry,
 | ||||
| -                                        aci->targetFilter,
 | ||||
| -                                        0 /*don't do access check*/) != 0) {
 | ||||
| -                filter_matched = ACL_FALSE;
 | ||||
| +            Slapi_DN *sdn;
 | ||||
| +            char* attr_evaluated = "None";
 | ||||
| +            char logbuf[2048] = {0};
 | ||||
| +            char *redzone = "the redzone";
 | ||||
| +            int32_t redzone_idx;
 | ||||
| +            char *filterstr; /* key to retrieve/add targetfilter value in the cache */
 | ||||
| +            PRBool valid_filter;
 | ||||
| +            struct targetfilter_cached_result *previous_filter_test;
 | ||||
| +
 | ||||
| +            /* only usefull for debug purpose */
 | ||||
| +            if (aclpb->aclpb_curr_attrEval && aclpb->aclpb_curr_attrEval->attrEval_name) {
 | ||||
| +                attr_evaluated = aclpb->aclpb_curr_attrEval->attrEval_name;
 | ||||
| +            }
 | ||||
| +            sdn = slapi_entry_get_sdn(aclpb->aclpb_curr_entry);
 | ||||
| +
 | ||||
| +            /* The key for the cache is the string representation of the original filter
 | ||||
| +             * If the string can not fit into the provided buffer (overwrite redzone)
 | ||||
| +             * then the filter is said invalid (for the cache) and it will be evaluated
 | ||||
| +             */
 | ||||
| +            redzone_idx = sizeof(logbuf) - 1 - strlen(redzone);
 | ||||
| +            strcpy(&logbuf[redzone_idx], redzone);
 | ||||
| +            filterstr = slapi_filter_to_string(aci->targetFilter, logbuf, sizeof(logbuf));
 | ||||
| +
 | ||||
| +            /* if the redzone was overwritten that means filterstr is truncated and not valid */
 | ||||
| +            valid_filter = (strcmp(&logbuf[redzone_idx], redzone) == 0);
 | ||||
| +            if (!valid_filter) {
 | ||||
| +                strcpy(&logbuf[50], "...");
 | ||||
| +                slapi_log_err(SLAPI_LOG_ACL, "acl__ressource_match_aci", "targetfilter too large (can not be cache) %s\n", logbuf);
 | ||||
| +            }
 | ||||
| +
 | ||||
| +            previous_filter_test = targetfilter_cache_lookup(aclpb, filterstr, valid_filter);
 | ||||
| +            if (previous_filter_test) {
 | ||||
| +                /* The filter was already evaluated against that same entry */
 | ||||
| +                if (previous_filter_test->matching_result == 0) {
 | ||||
| +                    slapi_log_err(SLAPI_LOG_ACL, "acl__ressource_match_aci", "cached result for entry %s did NOT match %s (%s)\n",
 | ||||
| +                            slapi_sdn_get_ndn(sdn),
 | ||||
| +                            filterstr,
 | ||||
| +                            attr_evaluated);
 | ||||
| +                    filter_matched = ACL_FALSE;
 | ||||
| +                } else {
 | ||||
| +                    slapi_log_err(SLAPI_LOG_ACL, "acl__ressource_match_aci", "cached result for entry %s did match %s (%s)\n",
 | ||||
| +                            slapi_sdn_get_ndn(sdn),
 | ||||
| +                            filterstr,
 | ||||
| +                            attr_evaluated);
 | ||||
| +                }
 | ||||
| +            } else {
 | ||||
| +                /* The filter has not already been evaluated against that entry
 | ||||
| +                 * evaluate it and cache the result
 | ||||
| +                 */
 | ||||
| +                if (slapi_vattr_filter_test(NULL, aclpb->aclpb_curr_entry,
 | ||||
| +                        aci->targetFilter,
 | ||||
| +                        0 /*don't do access check*/) != 0) {
 | ||||
| +                    filter_matched = ACL_FALSE;
 | ||||
| +                    targetfilter_cache_add(aclpb, filterstr, 0, valid_filter); /* does not match */
 | ||||
| +                } else {
 | ||||
| +                    targetfilter_cache_add(aclpb, filterstr, 1, valid_filter); /* does match */
 | ||||
| +                }
 | ||||
| +                slapi_log_err(SLAPI_LOG_ACL, "acl__ressource_match_aci", "entry %s %s match %s (%s)\n",
 | ||||
| +                        slapi_sdn_get_ndn(sdn),
 | ||||
| +                        filter_matched == ACL_FALSE ? "does not" : "does",
 | ||||
| +                        filterstr,
 | ||||
| +                        attr_evaluated);
 | ||||
|              } | ||||
|          } | ||||
|   | ||||
| @@ -2858,7 +2984,7 @@ acl__resource_match_aci_EXIT:
 | ||||
|  *    None. | ||||
|  * | ||||
|  **************************************************************************/ | ||||
| -static int
 | ||||
| +int
 | ||||
|  acl__TestRights(Acl_PBlock *aclpb, int access, const char **right, const char **map_generic, aclResultReason_t *result_reason) | ||||
|  { | ||||
|      ACLEvalHandle_t *acleval; | ||||
| diff --git a/ldap/servers/plugins/acl/acl.h b/ldap/servers/plugins/acl/acl.h
 | ||||
| index becc7f920..c9b9efa56 100644
 | ||||
| --- a/ldap/servers/plugins/acl/acl.h
 | ||||
| +++ b/ldap/servers/plugins/acl/acl.h
 | ||||
| @@ -407,6 +407,17 @@ struct aci_container
 | ||||
|  }; | ||||
|  typedef struct aci_container AciContainer; | ||||
|   | ||||
| +/* This structure is stored in the aclpb.
 | ||||
| + * It is a linked list containing the result of
 | ||||
| + * the filter matching against a specific entry.
 | ||||
| + *
 | ||||
| + * This list is free for each new entry in the aclpb*/
 | ||||
| +struct targetfilter_cached_result {
 | ||||
| +    char *filter;                            /* strdup of string representation of aci->targetFilter */
 | ||||
| +    int matching_result;                     /* 0 does not match / 1 does match */
 | ||||
| +    struct targetfilter_cached_result *next; /* next targetfilter already evaluated */
 | ||||
| +};
 | ||||
| +
 | ||||
|  struct acl_pblock | ||||
|  { | ||||
|      int aclpb_state; | ||||
| @@ -476,6 +487,8 @@ struct acl_pblock
 | ||||
|   | ||||
|      /* Current entry/dn/attr evaluation info */ | ||||
|      Slapi_Entry *aclpb_curr_entry; /* current Entry being processed */ | ||||
| +    int32_t targetfilter_cache_enabled;
 | ||||
| +    struct targetfilter_cached_result *aclpb_curr_entry_targetfilters;
 | ||||
|      int aclpb_num_entries; | ||||
|      Slapi_DN *aclpb_curr_entry_sdn;    /* Entry's SDN */ | ||||
|      Slapi_DN *aclpb_authorization_sdn; /* dn used for authorization */ | ||||
| @@ -723,6 +736,7 @@ void acl_modified(Slapi_PBlock *pb, int optype, Slapi_DN *e_sdn, void *change);
 | ||||
|   | ||||
|  int acl_access_allowed_disjoint_resource(Slapi_PBlock *pb, Slapi_Entry *e, char *attr, struct berval *val, int access); | ||||
|  int acl_access_allowed_main(Slapi_PBlock *pb, Slapi_Entry *e, char **attrs, struct berval *val, int access, int flags, char **errbuf); | ||||
| +void targetfilter_cache_free(struct acl_pblock *aclpb);
 | ||||
|  int acl_access_allowed(Slapi_PBlock *pb, Slapi_Entry *e, char *attr, struct berval *val, int access); | ||||
|  aclUserGroup *acl_get_usersGroup(struct acl_pblock *aclpb, char *n_dn); | ||||
|  void acl_print_acllib_err(NSErr_t *errp, char *str); | ||||
| diff --git a/ldap/servers/plugins/acl/acl_ext.c b/ldap/servers/plugins/acl/acl_ext.c
 | ||||
| index 797c5d2fd..c88f7389f 100644
 | ||||
| --- a/ldap/servers/plugins/acl/acl_ext.c
 | ||||
| +++ b/ldap/servers/plugins/acl/acl_ext.c
 | ||||
| @@ -189,6 +189,11 @@ acl_operation_ext_constructor(void *object __attribute__((unused)), void *parent
 | ||||
|          slapi_log_err(SLAPI_LOG_ERR, plugin_name, | ||||
|                        "acl_operation_ext_constructor - Operation extension allocation Failed\n"); | ||||
|      } | ||||
| +    /* targetfilter_cache toggle set during aclpb allocation
 | ||||
| +     * to avoid accessing configuration during the evaluation
 | ||||
| +     * of each aci
 | ||||
| +     */
 | ||||
| +    aclpb->targetfilter_cache_enabled = config_get_targetfilter_cache();
 | ||||
|   | ||||
|      TNF_PROBE_0_DEBUG(acl_operation_ext_constructor_end, "ACL", ""); | ||||
|   | ||||
| @@ -713,6 +718,7 @@ acl__free_aclpb(Acl_PBlock **aclpb_ptr)
 | ||||
|      slapi_ch_free((void **)&(aclpb->aclpb_curr_entryEval_context.acle_handles_matched_target)); | ||||
|      slapi_ch_free((void **)&(aclpb->aclpb_prev_entryEval_context.acle_handles_matched_target)); | ||||
|      slapi_ch_free((void **)&(aclpb->aclpb_prev_opEval_context.acle_handles_matched_target)); | ||||
| +    targetfilter_cache_free(aclpb);
 | ||||
|      slapi_sdn_free(&aclpb->aclpb_authorization_sdn); | ||||
|      slapi_sdn_free(&aclpb->aclpb_curr_entry_sdn); | ||||
|      if (aclpb->aclpb_macro_ht) { | ||||
| @@ -921,6 +927,12 @@ acl__done_aclpb(struct acl_pblock *aclpb)
 | ||||
|                        aclpb->aclpb_acleval ? (char *)aclpb->aclpb_acleval : "NULL"); | ||||
|      } | ||||
|   | ||||
| +    /* This aclpb return to the aclpb pool, make sure
 | ||||
| +     * the cached evaluations are freed and that
 | ||||
| +     * aclpb_curr_entry_targetfilters is NULL
 | ||||
| +     */
 | ||||
| +    targetfilter_cache_free(aclpb);
 | ||||
| +
 | ||||
|      /* Now Free the contents or clean it */ | ||||
|      slapi_sdn_done(aclpb->aclpb_curr_entry_sdn); | ||||
|      if (aclpb->aclpb_Evalattr) | ||||
| diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
 | ||||
| index db7d01bbc..2ea4cd760 100644
 | ||||
| --- a/ldap/servers/slapd/libglobs.c
 | ||||
| +++ b/ldap/servers/slapd/libglobs.c
 | ||||
| @@ -221,6 +221,7 @@ slapi_onoff_t init_return_exact_case;
 | ||||
|  slapi_onoff_t init_result_tweak; | ||||
|  slapi_onoff_t init_plugin_track; | ||||
|  slapi_onoff_t init_moddn_aci; | ||||
| +slapi_onoff_t init_targetfilter_cache;
 | ||||
|  slapi_onoff_t init_lastmod; | ||||
|  slapi_onoff_t init_readonly; | ||||
|  slapi_onoff_t init_accesscontrol; | ||||
| @@ -903,6 +904,11 @@ static struct config_get_and_set
 | ||||
|       (void **)&global_slapdFrontendConfig.moddn_aci, | ||||
|       CONFIG_ON_OFF, (ConfigGetFunc)config_get_moddn_aci, | ||||
|       &init_moddn_aci, NULL}, | ||||
| +    {CONFIG_TARGETFILTER_CACHE_ATTRIBUTE, config_set_targetfilter_cache,
 | ||||
| +     NULL, 0,
 | ||||
| +     (void **)&global_slapdFrontendConfig.targetfilter_cache,
 | ||||
| +     CONFIG_ON_OFF, (ConfigGetFunc)config_get_targetfilter_cache,
 | ||||
| +     &init_targetfilter_cache, NULL},
 | ||||
|      {CONFIG_ATTRIBUTE_NAME_EXCEPTION_ATTRIBUTE, config_set_attrname_exceptions, | ||||
|       NULL, 0, | ||||
|       (void **)&global_slapdFrontendConfig.attrname_exceptions, | ||||
| @@ -1688,6 +1694,7 @@ FrontendConfig_init(void)
 | ||||
|      init_syntaxcheck = cfg->syntaxcheck = LDAP_ON; | ||||
|      init_plugin_track = cfg->plugin_track = LDAP_OFF; | ||||
|      init_moddn_aci = cfg->moddn_aci = LDAP_ON; | ||||
| +    init_targetfilter_cache = cfg->targetfilter_cache = LDAP_ON;
 | ||||
|      init_syntaxlogging = cfg->syntaxlogging = LDAP_OFF; | ||||
|      init_dn_validate_strict = cfg->dn_validate_strict = LDAP_OFF; | ||||
|      init_ds4_compatible_schema = cfg->ds4_compatible_schema = LDAP_OFF; | ||||
| @@ -4053,6 +4060,21 @@ config_set_moddn_aci(const char *attrname, char *value, char *errorbuf, int appl
 | ||||
|      return retVal; | ||||
|  } | ||||
|   | ||||
| +int32_t
 | ||||
| +config_set_targetfilter_cache(const char *attrname, char *value, char *errorbuf, int apply)
 | ||||
| +{
 | ||||
| +    int32_t retVal = LDAP_SUCCESS;
 | ||||
| +    slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
 | ||||
| +
 | ||||
| +    retVal = config_set_onoff(attrname,
 | ||||
| +                              value,
 | ||||
| +                              &(slapdFrontendConfig->targetfilter_cache),
 | ||||
| +                              errorbuf,
 | ||||
| +                              apply);
 | ||||
| +
 | ||||
| +    return retVal;
 | ||||
| +}
 | ||||
| +
 | ||||
|  int32_t | ||||
|  config_set_dynamic_plugins(const char *attrname, char *value, char *errorbuf, int apply) | ||||
|  { | ||||
| @@ -5903,6 +5925,13 @@ config_get_moddn_aci(void)
 | ||||
|      return slapi_atomic_load_32(&(slapdFrontendConfig->moddn_aci), __ATOMIC_ACQUIRE); | ||||
|  } | ||||
|   | ||||
| +int32_t
 | ||||
| +config_get_targetfilter_cache(void)
 | ||||
| +{
 | ||||
| +    slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
 | ||||
| +    return slapi_atomic_load_32(&(slapdFrontendConfig->targetfilter_cache), __ATOMIC_ACQUIRE);
 | ||||
| +}
 | ||||
| +
 | ||||
|  int32_t | ||||
|  config_get_security(void) | ||||
|  { | ||||
| diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
 | ||||
| index 2768d5a1d..c143f3772 100644
 | ||||
| --- a/ldap/servers/slapd/proto-slap.h
 | ||||
| +++ b/ldap/servers/slapd/proto-slap.h
 | ||||
| @@ -263,6 +263,7 @@ int config_set_lastmod(const char *attrname, char *value, char *errorbuf, int ap
 | ||||
|  int config_set_nagle(const char *attrname, char *value, char *errorbuf, int apply); | ||||
|  int config_set_accesscontrol(const char *attrname, char *value, char *errorbuf, int apply); | ||||
|  int config_set_moddn_aci(const char *attrname, char *value, char *errorbuf, int apply); | ||||
| +int32_t config_set_targetfilter_cache(const char *attrname, char *value, char *errorbuf, int apply);
 | ||||
|  int config_set_security(const char *attrname, char *value, char *errorbuf, int apply); | ||||
|  int config_set_readonly(const char *attrname, char *value, char *errorbuf, int apply); | ||||
|  int config_set_schemacheck(const char *attrname, char *value, char *errorbuf, int apply); | ||||
| @@ -469,6 +470,7 @@ int config_get_accesscontrol(void);
 | ||||
|  int config_get_return_exact_case(void); | ||||
|  int config_get_result_tweak(void); | ||||
|  int config_get_moddn_aci(void); | ||||
| +int32_t config_get_targetfilter_cache(void);
 | ||||
|  int config_get_security(void); | ||||
|  int config_get_schemacheck(void); | ||||
|  int config_get_syntaxcheck(void); | ||||
| diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
 | ||||
| index c48516157..a3c0eff93 100644
 | ||||
| --- a/ldap/servers/slapd/slap.h
 | ||||
| +++ b/ldap/servers/slapd/slap.h
 | ||||
| @@ -2229,6 +2229,7 @@ typedef struct _slapdEntryPoints
 | ||||
|  #define CONFIG_REWRITE_RFC1274_ATTRIBUTE "nsslapd-rewrite-rfc1274" | ||||
|  #define CONFIG_PLUGIN_BINDDN_TRACKING_ATTRIBUTE "nsslapd-plugin-binddn-tracking" | ||||
|  #define CONFIG_MODDN_ACI_ATTRIBUTE "nsslapd-moddn-aci" | ||||
| +#define CONFIG_TARGETFILTER_CACHE_ATTRIBUTE "nsslapd-targetfilter-cache"
 | ||||
|  #define CONFIG_GLOBAL_BACKEND_LOCK "nsslapd-global-backend-lock" | ||||
|  #define CONFIG_ENABLE_NUNC_STANS "nsslapd-enable-nunc-stans" | ||||
|  #define CONFIG_ENABLE_UPGRADE_HASH "nsslapd-enable-upgrade-hash" | ||||
| @@ -2401,6 +2402,7 @@ typedef struct _slapdFrontendConfig
 | ||||
|      char **plugin; | ||||
|      slapi_onoff_t plugin_track; | ||||
|      slapi_onoff_t moddn_aci; | ||||
| +    slapi_onoff_t targetfilter_cache;
 | ||||
|      struct pw_scheme *pw_storagescheme; | ||||
|      slapi_onoff_t pwpolicy_local; | ||||
|      slapi_onoff_t pw_is_global_policy; | ||||
| -- 
 | ||||
| 2.31.1 | ||||
| 
 | ||||
| @ -0,0 +1,468 @@ | ||||
| From 375c1aad59989fb418ab1ead6050f919cfa1ceea Mon Sep 17 00:00:00 2001 | ||||
| From: tbordaz <tbordaz@redhat.com> | ||||
| Date: Fri, 5 Nov 2021 09:56:43 +0100 | ||||
| Subject: [PATCH 2/3] Issue 4972 - gecos with IA5 introduces a compatibility | ||||
|  issue with previous (#4981) | ||||
| 
 | ||||
| releases where it was DirectoryString | ||||
| 
 | ||||
| Bug description: | ||||
|        For years 'gecos' was DirectoryString (UTF8), with #50933 it was restricted to IA5 (ascii) | ||||
|        https://github.com/389ds/389-ds-base/commit/0683bcde1b667b6d0ca6e8d1ef605f17c51ea2f7# | ||||
| 
 | ||||
|        IA5 definition conforms rfc2307 but is a problem for existing deployments | ||||
|        where entries can have 'gecos' attribute value with UTF8. | ||||
| 
 | ||||
| Fix description: | ||||
|        Revert the definition to of 'gecos' being Directory String | ||||
| 
 | ||||
|        Additional fix to make test_replica_backup_and_restore more | ||||
|        robust to CI | ||||
| 
 | ||||
| relates: https://github.com/389ds/389-ds-base/issues/4972 | ||||
| 
 | ||||
| Reviewed by: William Brown, Pierre Rogier, James Chapman (Thanks !) | ||||
| 
 | ||||
| Platforms tested: F34 | ||||
| ---
 | ||||
|  .../tests/suites/schema/schema_test.py        | 398 +++++++++++++++++- | ||||
|  ldap/schema/10rfc2307compat.ldif              |   6 +- | ||||
|  2 files changed, 400 insertions(+), 4 deletions(-) | ||||
| 
 | ||||
| diff --git a/dirsrvtests/tests/suites/schema/schema_test.py b/dirsrvtests/tests/suites/schema/schema_test.py
 | ||||
| index d590624b6..5d62b8d59 100644
 | ||||
| --- a/dirsrvtests/tests/suites/schema/schema_test.py
 | ||||
| +++ b/dirsrvtests/tests/suites/schema/schema_test.py
 | ||||
| @@ -18,8 +18,12 @@ import pytest
 | ||||
|  import six | ||||
|  from ldap.cidict import cidict | ||||
|  from ldap.schema import SubSchema | ||||
| +from lib389.schema import SchemaLegacy
 | ||||
|  from lib389._constants import * | ||||
| -from lib389.topologies import topology_st
 | ||||
| +from lib389.topologies import topology_st, topology_m2 as topo_m2
 | ||||
| +from lib389.idm.user import UserAccounts, UserAccount
 | ||||
| +from lib389.replica import ReplicationManager
 | ||||
| +from lib389.utils import ensure_bytes
 | ||||
|   | ||||
|  pytestmark = pytest.mark.tier1 | ||||
|   | ||||
| @@ -165,6 +169,398 @@ def test_schema_comparewithfiles(topology_st):
 | ||||
|   | ||||
|      log.info('test_schema_comparewithfiles: PASSED') | ||||
|   | ||||
| +def test_gecos_directoryString(topology_st):
 | ||||
| +    """Check that gecos supports directoryString value
 | ||||
| +
 | ||||
| +    :id: aee422bb-6299-4124-b5cd-d7393dac19d3
 | ||||
| +
 | ||||
| +    :setup: Standalone instance
 | ||||
| +
 | ||||
| +    :steps:
 | ||||
| +        1. Add a common user
 | ||||
| +        2. replace gecos with a direstoryString value
 | ||||
| +
 | ||||
| +    :expectedresults:
 | ||||
| +        1. Success
 | ||||
| +        2. Success
 | ||||
| +    """
 | ||||
| +
 | ||||
| +    users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
 | ||||
| +
 | ||||
| +    user_properties = {
 | ||||
| +        'uid': 'testuser',
 | ||||
| +        'cn' : 'testuser',
 | ||||
| +        'sn' : 'user',
 | ||||
| +        'uidNumber' : '1000',
 | ||||
| +        'gidNumber' : '2000',
 | ||||
| +        'homeDirectory' : '/home/testuser',
 | ||||
| +    }
 | ||||
| +    testuser = users.create(properties=user_properties)
 | ||||
| +
 | ||||
| +    # Add a gecos UTF value
 | ||||
| +    testuser.replace('gecos', 'Hélène')
 | ||||
| +
 | ||||
| +def test_gecos_mixed_definition_topo(topo_m2, request):
 | ||||
| +    """Check that replication is still working if schema contains
 | ||||
| +       definitions that does not conform with a replicated entry
 | ||||
| +
 | ||||
| +    :id: d5940e71-d18a-4b71-aaf7-b9185361fffe
 | ||||
| +    :setup: Two suppliers replication setup
 | ||||
| +    :steps:
 | ||||
| +        1. Create a testuser on M1
 | ||||
| +        2  Stop M1 and M2
 | ||||
| +        3  Change gecos def on M2 to be IA5
 | ||||
| +        4  Update testuser with gecos directoryString value
 | ||||
| +        5  Check replication is still working
 | ||||
| +    :expectedresults:
 | ||||
| +        1. success
 | ||||
| +        2. success
 | ||||
| +        3. success
 | ||||
| +        4. success
 | ||||
| +        5. success
 | ||||
| +
 | ||||
| +    """
 | ||||
| +
 | ||||
| +    repl = ReplicationManager(DEFAULT_SUFFIX)
 | ||||
| +    m1 = topo_m2.ms["supplier1"]
 | ||||
| +    m2 = topo_m2.ms["supplier2"]
 | ||||
| +    
 | ||||
| +
 | ||||
| +    # create a test user
 | ||||
| +    testuser_dn = 'uid={},{}'.format('testuser', DEFAULT_SUFFIX)
 | ||||
| +    testuser = UserAccount(m1, testuser_dn)
 | ||||
| +    try:
 | ||||
| +        testuser.create(properties={
 | ||||
| +            'uid': 'testuser',
 | ||||
| +            'cn': 'testuser',
 | ||||
| +            'sn': 'testuser',
 | ||||
| +            'uidNumber' : '1000',
 | ||||
| +            'gidNumber' : '2000',
 | ||||
| +            'homeDirectory' : '/home/testuser',
 | ||||
| +        })
 | ||||
| +    except ldap.ALREADY_EXISTS:
 | ||||
| +        pass
 | ||||
| +    repl.wait_for_replication(m1, m2)
 | ||||
| +
 | ||||
| +    # Stop suppliers to update the schema
 | ||||
| +    m1.stop()
 | ||||
| +    m2.stop()
 | ||||
| +
 | ||||
| +    # on M1: gecos is DirectoryString (default)
 | ||||
| +    # on M2: gecos is IA5
 | ||||
| +    schema_filename = (m2.schemadir + "/99user.ldif")
 | ||||
| +    try:
 | ||||
| +        with open(schema_filename, 'w') as schema_file:
 | ||||
| +            schema_file.write("dn: cn=schema\n")
 | ||||
| +            schema_file.write("attributetypes: ( 1.3.6.1.1.1.1.2 NAME " +
 | ||||
| +                              "'gecos' DESC 'The GECOS field; the common name' " +
 | ||||
| +                              "EQUALITY caseIgnoreIA5Match " +
 | ||||
| +                              "SUBSTR caseIgnoreIA5SubstringsMatch " +
 | ||||
| +                              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 " +
 | ||||
| +                              "SINGLE-VALUE )\n")
 | ||||
| +        os.chmod(schema_filename, 0o777)
 | ||||
| +    except OSError as e:
 | ||||
| +        log.fatal("Failed to update schema file: " +
 | ||||
| +                  "{} Error: {}".format(schema_filename, str(e)))
 | ||||
| +
 | ||||
| +    # start the instances
 | ||||
| +    m1.start()
 | ||||
| +    m2.start()
 | ||||
| +
 | ||||
| +    # Check that gecos is IA5 on M2
 | ||||
| +    schema = SchemaLegacy(m2)
 | ||||
| +    attributetypes = schema.query_attributetype('gecos')
 | ||||
| +    assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.26"
 | ||||
| +
 | ||||
| +
 | ||||
| +    # Add a gecos UTF value on M1
 | ||||
| +    testuser.replace('gecos', 'Hélène')
 | ||||
| +
 | ||||
| +    # Check replication is still working
 | ||||
| +    testuser.replace('displayName', 'ascii value')
 | ||||
| +    repl.wait_for_replication(m1, m2)
 | ||||
| +    testuser_m2 = UserAccount(m2, testuser_dn)
 | ||||
| +    assert testuser_m2.exists()
 | ||||
| +    assert testuser_m2.get_attr_val_utf8('displayName') == 'ascii value'
 | ||||
| +
 | ||||
| +    def fin():
 | ||||
| +        m1.start()
 | ||||
| +        m2.start()
 | ||||
| +        testuser.delete()
 | ||||
| +        repl.wait_for_replication(m1, m2)
 | ||||
| +
 | ||||
| +        # on M2 restore a default 99user.ldif
 | ||||
| +        m2.stop()
 | ||||
| +        os.remove(m2.schemadir + "/99user.ldif")
 | ||||
| +        schema_filename = (m2.schemadir + "/99user.ldif")
 | ||||
| +        try:
 | ||||
| +            with open(schema_filename, 'w') as schema_file:
 | ||||
| +                schema_file.write("dn: cn=schema\n")
 | ||||
| +            os.chmod(schema_filename, 0o777)
 | ||||
| +        except OSError as e:
 | ||||
| +            log.fatal("Failed to update schema file: " +
 | ||||
| +                      "{} Error: {}".format(schema_filename, str(e)))
 | ||||
| +        m2.start()
 | ||||
| +        m1.start()
 | ||||
| +
 | ||||
| +    request.addfinalizer(fin)
 | ||||
| +
 | ||||
| +def test_gecos_directoryString_wins_M1(topo_m2, request):
 | ||||
| +    """Check that if inital syntax are IA5(M2) and DirectoryString(M1)
 | ||||
| +    Then directoryString wins when nsSchemaCSN M1 is the greatest
 | ||||
| +
 | ||||
| +    :id: ad119fa5-7671-45c8-b2ef-0b28ffb68fdb
 | ||||
| +    :setup: Two suppliers replication setup
 | ||||
| +    :steps:
 | ||||
| +        1. Create a testuser on M1
 | ||||
| +        2  Stop M1 and M2
 | ||||
| +        3  Change gecos def on M2 to be IA5
 | ||||
| +        4  Start M1 and M2
 | ||||
| +        5  Update M1 schema so that M1 has greatest nsSchemaCSN
 | ||||
| +        6  Update testuser with gecos directoryString value
 | ||||
| +        7  Check replication is still working
 | ||||
| +        8  Check gecos is DirectoryString on M1 and M2
 | ||||
| +    :expectedresults:
 | ||||
| +        1. success
 | ||||
| +        2. success
 | ||||
| +        3. success
 | ||||
| +        4. success
 | ||||
| +        5. success
 | ||||
| +        6. success
 | ||||
| +        7. success
 | ||||
| +        8. success
 | ||||
| +
 | ||||
| +    """
 | ||||
| +
 | ||||
| +    repl = ReplicationManager(DEFAULT_SUFFIX)
 | ||||
| +    m1 = topo_m2.ms["supplier1"]
 | ||||
| +    m2 = topo_m2.ms["supplier2"]
 | ||||
| +    
 | ||||
| +
 | ||||
| +    # create a test user
 | ||||
| +    testuser_dn = 'uid={},{}'.format('testuser', DEFAULT_SUFFIX)
 | ||||
| +    testuser = UserAccount(m1, testuser_dn)
 | ||||
| +    try:
 | ||||
| +        testuser.create(properties={
 | ||||
| +            'uid': 'testuser',
 | ||||
| +            'cn': 'testuser',
 | ||||
| +            'sn': 'testuser',
 | ||||
| +            'uidNumber' : '1000',
 | ||||
| +            'gidNumber' : '2000',
 | ||||
| +            'homeDirectory' : '/home/testuser',
 | ||||
| +        })
 | ||||
| +    except ldap.ALREADY_EXISTS:
 | ||||
| +        pass
 | ||||
| +    repl.wait_for_replication(m1, m2)
 | ||||
| +
 | ||||
| +    # Stop suppliers to update the schema
 | ||||
| +    m1.stop()
 | ||||
| +    m2.stop()
 | ||||
| +
 | ||||
| +    # on M1: gecos is DirectoryString (default)
 | ||||
| +    # on M2: gecos is IA5
 | ||||
| +    schema_filename = (m2.schemadir + "/99user.ldif")
 | ||||
| +    try:
 | ||||
| +        with open(schema_filename, 'w') as schema_file:
 | ||||
| +            schema_file.write("dn: cn=schema\n")
 | ||||
| +            schema_file.write("attributetypes: ( 1.3.6.1.1.1.1.2 NAME " +
 | ||||
| +                              "'gecos' DESC 'The GECOS field; the common name' " +
 | ||||
| +                              "EQUALITY caseIgnoreIA5Match " +
 | ||||
| +                              "SUBSTR caseIgnoreIA5SubstringsMatch " +
 | ||||
| +                              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 " +
 | ||||
| +                              "SINGLE-VALUE )\n")
 | ||||
| +        os.chmod(schema_filename, 0o777)
 | ||||
| +    except OSError as e:
 | ||||
| +        log.fatal("Failed to update schema file: " +
 | ||||
| +                  "{} Error: {}".format(schema_filename, str(e)))
 | ||||
| +
 | ||||
| +    # start the instances
 | ||||
| +    m1.start()
 | ||||
| +    m2.start()
 | ||||
| +
 | ||||
| +    # Check that gecos is IA5 on M2
 | ||||
| +    schema = SchemaLegacy(m2)
 | ||||
| +    attributetypes = schema.query_attributetype('gecos')
 | ||||
| +    assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.26"
 | ||||
| +
 | ||||
| +
 | ||||
| +    # update M1 schema to increase its nsschemaCSN
 | ||||
| +    new_at = "( dummy-oid NAME 'dummy' DESC 'dummy attribute' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'RFC 2307' )"
 | ||||
| +    m1.schema.add_schema('attributetypes', ensure_bytes(new_at))
 | ||||
| +
 | ||||
| +    # Add a gecos UTF value on M1
 | ||||
| +    testuser.replace('gecos', 'Hélène')
 | ||||
| +
 | ||||
| +    # Check replication is still working
 | ||||
| +    testuser.replace('displayName', 'ascii value')
 | ||||
| +    repl.wait_for_replication(m1, m2)
 | ||||
| +    testuser_m2 = UserAccount(m2, testuser_dn)
 | ||||
| +    assert testuser_m2.exists()
 | ||||
| +    assert testuser_m2.get_attr_val_utf8('displayName') == 'ascii value'
 | ||||
| +
 | ||||
| +    # Check that gecos is DirectoryString on M1
 | ||||
| +    schema = SchemaLegacy(m1)
 | ||||
| +    attributetypes = schema.query_attributetype('gecos')
 | ||||
| +    assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.15"
 | ||||
| +
 | ||||
| +    # Check that gecos is DirectoryString on M2
 | ||||
| +    schema = SchemaLegacy(m2)
 | ||||
| +    attributetypes = schema.query_attributetype('gecos')
 | ||||
| +    assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.15"
 | ||||
| +
 | ||||
| +    def fin():
 | ||||
| +        m1.start()
 | ||||
| +        m2.start()
 | ||||
| +        testuser.delete()
 | ||||
| +        m1.schema.del_schema('attributetypes', ensure_bytes(new_at))
 | ||||
| +        repl.wait_for_replication(m1, m2)
 | ||||
| +
 | ||||
| +        # on M2 restore a default 99user.ldif
 | ||||
| +        m2.stop()
 | ||||
| +        os.remove(m2.schemadir + "/99user.ldif")
 | ||||
| +        schema_filename = (m2.schemadir + "/99user.ldif")
 | ||||
| +        try:
 | ||||
| +            with open(schema_filename, 'w') as schema_file:
 | ||||
| +                schema_file.write("dn: cn=schema\n")
 | ||||
| +            os.chmod(schema_filename, 0o777)
 | ||||
| +        except OSError as e:
 | ||||
| +            log.fatal("Failed to update schema file: " +
 | ||||
| +                      "{} Error: {}".format(schema_filename, str(e)))
 | ||||
| +        m2.start()
 | ||||
| +        m1.start()
 | ||||
| +
 | ||||
| +    request.addfinalizer(fin)
 | ||||
| +
 | ||||
| +def test_gecos_directoryString_wins_M2(topo_m2, request):
 | ||||
| +    """Check that if inital syntax are IA5(M2) and DirectoryString(M1)
 | ||||
| +    Then directoryString wins when nsSchemaCSN M2 is the greatest
 | ||||
| +
 | ||||
| +    :id: 2da7f1b1-f86d-4072-a940-ba56d4bc8348
 | ||||
| +    :setup: Two suppliers replication setup
 | ||||
| +    :steps:
 | ||||
| +        1. Create a testuser on M1
 | ||||
| +        2  Stop M1 and M2
 | ||||
| +        3  Change gecos def on M2 to be IA5
 | ||||
| +        4  Start M1 and M2
 | ||||
| +        5  Update M2 schema so that M2 has greatest nsSchemaCSN
 | ||||
| +        6  Update testuser on M2 and trigger replication to M1
 | ||||
| +        7  Update testuser on M2 with gecos directoryString value
 | ||||
| +        8  Check replication is still working
 | ||||
| +        9  Check gecos is DirectoryString on M1 and M2
 | ||||
| +    :expectedresults:
 | ||||
| +        1. success
 | ||||
| +        2. success
 | ||||
| +        3. success
 | ||||
| +        4. success
 | ||||
| +        5. success
 | ||||
| +        6. success
 | ||||
| +        7. success
 | ||||
| +        8. success
 | ||||
| +        9. success
 | ||||
| +
 | ||||
| +    """
 | ||||
| +
 | ||||
| +    repl = ReplicationManager(DEFAULT_SUFFIX)
 | ||||
| +    m1 = topo_m2.ms["supplier1"]
 | ||||
| +    m2 = topo_m2.ms["supplier2"]
 | ||||
| +    
 | ||||
| +
 | ||||
| +    # create a test user
 | ||||
| +    testuser_dn = 'uid={},{}'.format('testuser', DEFAULT_SUFFIX)
 | ||||
| +    testuser = UserAccount(m1, testuser_dn)
 | ||||
| +    try:
 | ||||
| +        testuser.create(properties={
 | ||||
| +            'uid': 'testuser',
 | ||||
| +            'cn': 'testuser',
 | ||||
| +            'sn': 'testuser',
 | ||||
| +            'uidNumber' : '1000',
 | ||||
| +            'gidNumber' : '2000',
 | ||||
| +            'homeDirectory' : '/home/testuser',
 | ||||
| +        })
 | ||||
| +    except ldap.ALREADY_EXISTS:
 | ||||
| +        pass
 | ||||
| +    testuser.replace('displayName', 'to trigger replication M1-> M2')
 | ||||
| +    repl.wait_for_replication(m1, m2)
 | ||||
| +
 | ||||
| +    # Stop suppliers to update the schema
 | ||||
| +    m1.stop()
 | ||||
| +    m2.stop()
 | ||||
| +
 | ||||
| +    # on M1: gecos is DirectoryString (default)
 | ||||
| +    # on M2: gecos is IA5
 | ||||
| +    schema_filename = (m2.schemadir + "/99user.ldif")
 | ||||
| +    try:
 | ||||
| +        with open(schema_filename, 'w') as schema_file:
 | ||||
| +            schema_file.write("dn: cn=schema\n")
 | ||||
| +            schema_file.write("attributetypes: ( 1.3.6.1.1.1.1.2 NAME " +
 | ||||
| +                              "'gecos' DESC 'The GECOS field; the common name' " +
 | ||||
| +                              "EQUALITY caseIgnoreIA5Match " +
 | ||||
| +                              "SUBSTR caseIgnoreIA5SubstringsMatch " +
 | ||||
| +                              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 " +
 | ||||
| +                              "SINGLE-VALUE )\n")
 | ||||
| +        os.chmod(schema_filename, 0o777)
 | ||||
| +    except OSError as e:
 | ||||
| +        log.fatal("Failed to update schema file: " +
 | ||||
| +                  "{} Error: {}".format(schema_filename, str(e)))
 | ||||
| +
 | ||||
| +    # start the instances
 | ||||
| +    m1.start()
 | ||||
| +    m2.start()
 | ||||
| +
 | ||||
| +    # Check that gecos is IA5 on M2
 | ||||
| +    schema = SchemaLegacy(m2)
 | ||||
| +    attributetypes = schema.query_attributetype('gecos')
 | ||||
| +    assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.26"
 | ||||
| +
 | ||||
| +    # update M2 schema to increase its nsschemaCSN
 | ||||
| +    new_at = "( dummy-oid NAME 'dummy' DESC 'dummy attribute' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'RFC 2307' )"
 | ||||
| +    m2.schema.add_schema('attributetypes', ensure_bytes(new_at))
 | ||||
| +
 | ||||
| +    # update just to trigger replication M2->M1
 | ||||
| +    # and update of M2 schema
 | ||||
| +    testuser_m2 = UserAccount(m2, testuser_dn)
 | ||||
| +    testuser_m2.replace('displayName', 'to trigger replication M2-> M1')
 | ||||
| +
 | ||||
| +    # Add a gecos UTF value on M1
 | ||||
| +    testuser.replace('gecos', 'Hélène')
 | ||||
| +
 | ||||
| +    # Check replication is still working
 | ||||
| +    testuser.replace('displayName', 'ascii value')
 | ||||
| +    repl.wait_for_replication(m1, m2)
 | ||||
| +    assert testuser_m2.exists()
 | ||||
| +    assert testuser_m2.get_attr_val_utf8('displayName') == 'ascii value'
 | ||||
| +
 | ||||
| +    # Check that gecos is DirectoryString on M1
 | ||||
| +    schema = SchemaLegacy(m1)
 | ||||
| +    attributetypes = schema.query_attributetype('gecos')
 | ||||
| +    assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.15"
 | ||||
| +
 | ||||
| +    # Check that gecos is DirectoryString on M2
 | ||||
| +    schema = SchemaLegacy(m2)
 | ||||
| +    attributetypes = schema.query_attributetype('gecos')
 | ||||
| +    assert attributetypes[0].syntax == "1.3.6.1.4.1.1466.115.121.1.15"
 | ||||
| +
 | ||||
| +    def fin():
 | ||||
| +        m1.start()
 | ||||
| +        m2.start()
 | ||||
| +        testuser.delete()
 | ||||
| +        m1.schema.del_schema('attributetypes', ensure_bytes(new_at))
 | ||||
| +        repl.wait_for_replication(m1, m2)
 | ||||
| +
 | ||||
| +        # on M2 restore a default 99user.ldif
 | ||||
| +        m2.stop()
 | ||||
| +        os.remove(m2.schemadir + "/99user.ldif")
 | ||||
| +        schema_filename = (m2.schemadir + "/99user.ldif")
 | ||||
| +        try:
 | ||||
| +            with open(schema_filename, 'w') as schema_file:
 | ||||
| +                schema_file.write("dn: cn=schema\n")
 | ||||
| +            os.chmod(schema_filename, 0o777)
 | ||||
| +        except OSError as e:
 | ||||
| +            log.fatal("Failed to update schema file: " +
 | ||||
| +                      "{} Error: {}".format(schema_filename, str(e)))
 | ||||
| +        m2.start()
 | ||||
| +
 | ||||
| +    request.addfinalizer(fin)
 | ||||
|   | ||||
|  if __name__ == '__main__': | ||||
|      # Run isolated | ||||
| diff --git a/ldap/schema/10rfc2307compat.ldif b/ldap/schema/10rfc2307compat.ldif
 | ||||
| index 8ba72e1e3..998b8983b 100644
 | ||||
| --- a/ldap/schema/10rfc2307compat.ldif
 | ||||
| +++ b/ldap/schema/10rfc2307compat.ldif
 | ||||
| @@ -21,9 +21,9 @@ attributeTypes: (
 | ||||
|  attributeTypes: ( | ||||
|    1.3.6.1.1.1.1.2 NAME 'gecos' | ||||
|    DESC 'The GECOS field; the common name' | ||||
| -  EQUALITY caseIgnoreIA5Match
 | ||||
| -  SUBSTR caseIgnoreIA5SubstringsMatch
 | ||||
| -  SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
 | ||||
| +  EQUALITY caseIgnoreMatch
 | ||||
| +  SUBSTR caseIgnoreSubstringsMatch
 | ||||
| +  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
 | ||||
|    SINGLE-VALUE | ||||
|    ) | ||||
|  attributeTypes: ( | ||||
| -- 
 | ||||
| 2.31.1 | ||||
| 
 | ||||
| @ -0,0 +1,120 @@ | ||||
| From 096c95690a27c942d47b20a85fa3d7fe15ffe624 Mon Sep 17 00:00:00 2001 | ||||
| From: Mark Reynolds <mreynolds@redhat.com> | ||||
| Date: Wed, 8 Sep 2021 10:31:19 -0400 | ||||
| Subject: [PATCH] Issue 4910 - db reindex corrupts RUV tombstone nsuiqueid | ||||
|  index | ||||
| 
 | ||||
| Bug Description:  During a reindex task we skip the RUV tombstone entry, | ||||
|                   which corrupts the nsuniqueid index. | ||||
| 
 | ||||
| Fix Description:  Make sure we still index nsuniqueid index for | ||||
|                   the RUV tombstone entry. | ||||
| 
 | ||||
| relates: https://github.com/389ds/389-ds-base/issues/4910 | ||||
| 
 | ||||
| Reviewed by: firstyear & progier389 (Thanks!!) | ||||
| ---
 | ||||
|  .../tests/suites/replication/ruvstore_test.py | 35 +++++++++++++++++++ | ||||
|  .../slapd/back-ldbm/db-bdb/bdb_ldif2db.c      | 12 +++++-- | ||||
|  2 files changed, 44 insertions(+), 3 deletions(-) | ||||
| 
 | ||||
| diff --git a/dirsrvtests/tests/suites/replication/ruvstore_test.py b/dirsrvtests/tests/suites/replication/ruvstore_test.py
 | ||||
| index c04fd079e..4e5326227 100644
 | ||||
| --- a/dirsrvtests/tests/suites/replication/ruvstore_test.py
 | ||||
| +++ b/dirsrvtests/tests/suites/replication/ruvstore_test.py
 | ||||
| @@ -12,6 +12,8 @@ import ldap
 | ||||
|  import pytest | ||||
|  from ldif import LDIFParser | ||||
|  from lib389.replica import Replicas | ||||
| +from lib389.backend import Backends
 | ||||
| +from lib389.idm.domain import Domain
 | ||||
|  from lib389.idm.user import UserAccounts | ||||
|  from lib389.topologies import topology_m2 as topo | ||||
|  from lib389._constants import * | ||||
| @@ -156,6 +158,39 @@ def test_memoryruv_sync_with_databaseruv(topo):
 | ||||
|      _compare_memoryruv_and_databaseruv(topo, 'delete') | ||||
|   | ||||
|   | ||||
| +def test_ruv_after_reindex(topo):
 | ||||
| +    """Test that the tombstone RUV entry is not corrupted after a reindex task
 | ||||
| +
 | ||||
| +    :id: 988c0fab-1905-4dc5-a45d-fbf195843a33
 | ||||
| +    :setup: 2 suppliers
 | ||||
| +    :steps:
 | ||||
| +        1. Reindex database
 | ||||
| +        2. Perform some updates
 | ||||
| +        3. Check error log does not have "_entryrdn_insert_key" errors
 | ||||
| +    :expectedresults:
 | ||||
| +        1. Success
 | ||||
| +        2. Success
 | ||||
| +        3. Success
 | ||||
| +    """
 | ||||
| +
 | ||||
| +    inst = topo.ms['supplier1']
 | ||||
| +    suffix = Domain(inst, "ou=people," + DEFAULT_SUFFIX)
 | ||||
| +    backends = Backends(inst)
 | ||||
| +    backend = backends.get(DEFAULT_BENAME)
 | ||||
| +
 | ||||
| +    # Reindex nsuniqueid
 | ||||
| +    backend.reindex(attrs=['nsuniqueid'], wait=True)
 | ||||
| +
 | ||||
| +    # Do some updates
 | ||||
| +    for idx in range(0, 5):
 | ||||
| +        suffix.replace('description', str(idx))
 | ||||
| +
 | ||||
| +    # Check error log for RUV entryrdn errors.  Stopping instance forces RUV
 | ||||
| +    # to be written and quickly exposes the error
 | ||||
| +    inst.stop()
 | ||||
| +    assert not inst.searchErrorsLog("entryrdn_insert_key")
 | ||||
| +
 | ||||
| +
 | ||||
|  if __name__ == '__main__': | ||||
|      # Run isolated | ||||
|      # -s for DEBUG mode | ||||
| diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c
 | ||||
| index 506c285a3..6100dbf77 100644
 | ||||
| --- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c
 | ||||
| +++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_ldif2db.c
 | ||||
| @@ -25,6 +25,7 @@
 | ||||
|  #define DB2INDEX_ENTRYRDN 0x2     /* index entryrdn */ | ||||
|  #define DB2LDIF_ENTRYRDN 0x4      /* export entryrdn */ | ||||
|  #define DB2INDEX_OBJECTCLASS 0x10 /* for reindexing "objectclass: nstombstone" */ | ||||
| +#define DB2INDEX_NSUNIQUEID 0x20  /* for reindexing RUV tombstone */
 | ||||
|   | ||||
|  #define LDIF2LDBM_EXTBITS(x) ((x)&0xf) | ||||
|   | ||||
| @@ -1543,6 +1544,9 @@ bdb_db2index(Slapi_PBlock *pb)
 | ||||
|                      if (strcasecmp(attrs[i] + 1, SLAPI_ATTR_OBJECTCLASS) == 0) { | ||||
|                          index_ext |= DB2INDEX_OBJECTCLASS; | ||||
|                      } | ||||
| +                    if (strcasecmp(attrs[i] + 1, SLAPI_ATTR_UNIQUEID) == 0) {
 | ||||
| +                        index_ext |= DB2INDEX_NSUNIQUEID;
 | ||||
| +                    }
 | ||||
|                      charray_add(&indexAttrs, attrs[i] + 1); | ||||
|                      ai->ai_indexmask |= INDEX_OFFLINE; | ||||
|                      slapi_task_log_notice(task, "%s: Indexing attribute: %s", | ||||
| @@ -1895,7 +1899,7 @@ bdb_db2index(Slapi_PBlock *pb)
 | ||||
|           * Update the attribute indexes | ||||
|           */ | ||||
|          if (indexAttrs) { | ||||
| -            if (istombstone && !(index_ext & (DB2INDEX_ENTRYRDN | DB2INDEX_OBJECTCLASS))) {
 | ||||
| +            if (istombstone && !(index_ext & (DB2INDEX_ENTRYRDN | DB2INDEX_OBJECTCLASS | DB2INDEX_NSUNIQUEID))) {
 | ||||
|                  /* if it is a tombstone entry, just entryrdn or "objectclass: nstombstone" | ||||
|                   * need to be reindexed.  the to-be-indexed list does not contain them. */ | ||||
|                  backentry_free(&ep); | ||||
| @@ -1915,8 +1919,10 @@ bdb_db2index(Slapi_PBlock *pb)
 | ||||
|                          if (istombstone) { | ||||
|                              if (!slapi_attr_type_cmp(indexAttrs[j], SLAPI_ATTR_OBJECTCLASS, SLAPI_TYPE_CMP_SUBTYPE)) { | ||||
|                                  is_tombstone_obj = 1; /* is tombstone && is objectclass. need to index "nstombstone"*/ | ||||
| -                            } else if (slapi_attr_type_cmp(indexAttrs[j], LDBM_ENTRYRDN_STR, SLAPI_TYPE_CMP_SUBTYPE)) {
 | ||||
| -                                /* Entry is a tombstone && this index is not an entryrdn. */
 | ||||
| +                            } else if (slapi_attr_type_cmp(indexAttrs[j], LDBM_ENTRYRDN_STR, SLAPI_TYPE_CMP_SUBTYPE) &&
 | ||||
| +                                       slapi_attr_type_cmp(indexAttrs[j], SLAPI_ATTR_UNIQUEID, SLAPI_TYPE_CMP_SUBTYPE))
 | ||||
| +                            {
 | ||||
| +                                /* Entry is a tombstone && this index is not entryrdn or nsuniqueid */
 | ||||
|                                  continue; | ||||
|                              } | ||||
|                          } | ||||
| -- 
 | ||||
| 2.31.1 | ||||
| 
 | ||||
| @ -48,7 +48,7 @@ ExcludeArch: i686 | ||||
| Summary:          389 Directory Server (base) | ||||
| Name:             389-ds-base | ||||
| Version:          1.4.3.23 | ||||
| Release:          %{?relprefix}10%{?prerel}%{?dist} | ||||
| Release:          %{?relprefix}12%{?prerel}%{?dist} | ||||
| License:          GPLv3+ | ||||
| URL:              https://www.port389.org | ||||
| Group:            System Environment/Daemons | ||||
| @ -267,7 +267,9 @@ Patch27:          0027-Issue-4734-import-of-entry-with-no-parent-warning-47.patc | ||||
| Patch28:          0028-Issue-4872-BUG-entryuuid-enabled-by-default-causes-r.patch | ||||
| Patch29:          0029-Remove-GOST-YESCRYPT-password-sotrage-scheme.patch | ||||
| Patch30:          0030-Issue-4884-server-crashes-when-dnaInterval-attribute.patch | ||||
| 
 | ||||
| Patch31:          0031-Issue-4925-Performance-ACI-targetfilter-evaluation-r.patch | ||||
| Patch32:          0032-Issue-4972-gecos-with-IA5-introduces-a-compatibility.patch | ||||
| Patch33:          0033-Issue-4910-db-reindex-corrupts-RUV-tombstone-nsuique.patch | ||||
| 
 | ||||
| %description | ||||
| 389 Directory Server is an LDAPv3 compliant server.  The base package includes | ||||
| @ -886,6 +888,15 @@ exit 0 | ||||
| %doc README.md | ||||
| 
 | ||||
| %changelog | ||||
| * Thu Nov 18 2021 Mark Reynolds <mreynolds@redhat.com> - 1.4.3.23-12 | ||||
| - Bump version to 1.4.3.23-12 | ||||
| - Resolves: Bug 2024697 - DB corruption "_entryrdn_insert_key - Same DN (dn: nsuniqueid=ffffffff-ffffffff-ffffffff-ffffffff,<SUFFIX>) is already in the entryrdn file" | ||||
| 
 | ||||
| * Wed Nov 10 2021 Mark Reynolds <mreynolds@redhat.com> - 1.4.3.23-11 | ||||
| - Bump version to 1.4.3.23-11 | ||||
| - Resolves: Bug 2022005 - ipa user-add fails with "gecos: invalid per syntax: Invalid syntax" | ||||
| - Resolves: Bug 2021998 - IPA server is very slow in execution of some searches | ||||
| 
 | ||||
| * Thu Aug 26 2021 Mark Reynolds <mreynolds@redhat.com> - 1.4.3.23-10 | ||||
| - Bump version to 1.4.3.23-10 | ||||
| - Resolves: Bug 1997138 - LDAP server crashes when dnaInterval attribute is set to 0 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user