From: Arran Cudbard-Bell Date: Tue, 21 Apr 2026 23:29:33 +0000 (-0400) Subject: Allow CF_IDENT_ANY to work with sections and pairs in CONF_PARSER arrays X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=db2d129faf7247e4249469819515feb8983198cd;p=thirdparty%2Ffreeradius-server.git Allow CF_IDENT_ANY to work with sections and pairs in CONF_PARSER arrays --- diff --git a/src/lib/server/cf_parse.c b/src/lib/server/cf_parse.c index 76540ce3928..cf841aebcab 100644 --- a/src/lib/server/cf_parse.c +++ b/src/lib/server/cf_parse.c @@ -911,6 +911,14 @@ static int cf_section_parse_init(CONF_SECTION *cs, void *base, conf_parser_t con return 0; } + /* + * FR_CONF_FUNC rules own their own output routing via the + * parse function - there's no guaranteed offset into `base` + * for us to NULL out. The func decides where the result + * lives. + */ + if (rule->func) return 0; + if (rule->data) { *(char **) rule->data = NULL; } else if (base) { @@ -974,11 +982,27 @@ static int cf_subsection_parse(TALLOC_CTX *ctx, void *out, void *base, CONF_SECT size_t subcs_size = rule->subcs_size; conf_parser_t const *rules = rule->subcs; + /* + * CF_IDENT_ANY section rules act as a catch-all: skip any + * subsection that's already been claimed and marked parsed + * by an earlier specific rule, so the wildcard only handles + * the leftovers. CONF_FLAG_ALWAYS_PARSE overrides this and + * makes the rule observe every matching subsection. + */ + bool const skip_parsed = (rule->name1 == CF_IDENT_ANY) && + !(rule->flags & CONF_FLAG_ALWAYS_PARSE); + uint8_t **array = NULL; fr_assert(rule->flags & CONF_FLAG_SUBSECTION); - subcs = cf_section_find(cs, rule->name1, rule->name2); + if (skip_parsed) { + while ((subcs = cf_section_find_next(cs, subcs, rule->name1, rule->name2))) { + if (!cf_item_is_parsed(cf_section_to_item(subcs))) break; + } + } else { + subcs = cf_section_find(cs, rule->name1, rule->name2); + } if (!subcs) return 0; /* @@ -1026,7 +1050,10 @@ static int cf_subsection_parse(TALLOC_CTX *ctx, void *out, void *base, CONF_SECT * Handle the multi subsection case (which is harder) */ subcs = NULL; - while ((subcs = cf_section_find_next(cs, subcs, rule->name1, rule->name2))) count++; + while ((subcs = cf_section_find_next(cs, subcs, rule->name1, rule->name2))) { + if (skip_parsed && cf_item_is_parsed(cf_section_to_item(subcs))) continue; + count++; + } /* * Allocate an array to hold the subsections @@ -1046,6 +1073,8 @@ static int cf_subsection_parse(TALLOC_CTX *ctx, void *out, void *base, CONF_SECT while ((subcs = cf_section_find_next(cs, subcs, rule->name1, rule->name2))) { uint8_t *buff = NULL; + if (skip_parsed && cf_item_is_parsed(cf_section_to_item(subcs))) continue; + if (DEBUG_ENABLED4) cf_log_debug(cs, "Evaluating rules for %s[%i] section. Output %p", cf_section_name1(subcs), i, out); @@ -1115,6 +1144,38 @@ static int cf_section_parse_rule(TALLOC_CTX *ctx, void *base, CONF_SECTION *cs, return cf_subsection_parse(ctx, data, base, cs, rule); } + /* + * A pair rule with name1 == CF_IDENT_ANY is a catch-all: + * feed every still-unparsed CONF_PAIR in this section to the + * rule's parser func. Order matters - specific rules before + * this entry have already claimed (and marked parsed) their + * pairs, so the catch-all only sees the leftovers, unless + * CONF_FLAG_ALWAYS_PARSE is set (in which case it sees every + * pair regardless). + * + * The rule must provide a func; there's no sensible default + * output offset when the pair's name varies. The func + * receives one CONF_PAIR at a time via ci and can read the + * name via cf_pair_attr(). + */ + if (!(rule->flags & CONF_FLAG_REF) && rule->name1 == CF_IDENT_ANY) { + bool const always_parse = (rule->flags & CONF_FLAG_ALWAYS_PARSE); + CONF_PAIR *cp = NULL; + + if (!rule->func) { + cf_log_err(cs, "CF_IDENT_ANY pair rule must provide a parse function"); + return -1; + } + + while ((cp = cf_pair_find_next(cs, cp, NULL))) { + if (!always_parse && cf_item_is_parsed(cf_pair_to_item(cp))) continue; + + if (rule->func(ctx, data, base, cf_pair_to_item(cp), rule) < 0) return -1; + cf_item_mark_parsed(cf_pair_to_item(cp)); + } + return 0; + } + /* * Ignore this rule if it's a reference, as the * rules it points to have been pushed by the diff --git a/src/lib/server/cf_parse.h b/src/lib/server/cf_parse.h index 6d8ca220cf7..82ee0a797f6 100644 --- a/src/lib/server/cf_parse.h +++ b/src/lib/server/cf_parse.h @@ -456,6 +456,11 @@ typedef enum CC_HINT(flag_enum) { ///< generated documentation. CONF_FLAG_REF = (1 << 25), //!< reference another conf_parser_t inline in this one CONF_FLAG_OPTIONAL = (1 << 26), //!< subsection is pushed only if a non-optional matching one is pushed + CONF_FLAG_ALWAYS_PARSE = (1 << 27), //!< Run this rule even against items already marked + ///< parsed. Useful for CF_IDENT_ANY observer rules that + ///< want to see every item, not just the leftovers, or for + ///< a second-pass rule that needs to re-examine something + ///< an earlier rule already claimed. } conf_parser_flags_t; DIAG_ON(attributes)