]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Allow CF_IDENT_ANY to work with sections and pairs in CONF_PARSER arrays
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Tue, 21 Apr 2026 23:29:33 +0000 (19:29 -0400)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 22 Apr 2026 03:48:37 +0000 (23:48 -0400)
src/lib/server/cf_parse.c
src/lib/server/cf_parse.h

index 76540ce3928585dcc24a274ee70e5db5180a956c..cf841aebcab9b29d6c7de944acfbe75b9512a74c 100644 (file)
@@ -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
index 6d8ca220cf7ad2ad78b764313ebfee9a6906cf24..82ee0a797f68ac6729b78727d5f3088ab4ca0ecc 100644 (file)
@@ -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)