Skip to content

Commit 3b51650

Browse files
committed
tmpl, value: accept null as an explicit keyword
Adds tmpl_afrom_null_substr so the bareword `null` is recognised at tmpl-tokenize time and builds a TMPL_TYPE_DATA wrapping an FR_TYPE_NULL box. Wired in before the numeric / address / bool / attribute branches in tmpl_afrom_substr so a dictionary attribute named "null" can't shadow it. FR_TYPE_NULL previously doubled as the "uninitialised box" sentinel, which is why TMPL_VERIFY panicked when it saw one inside a TMPL_TYPE_DATA and why fr_value_box_cast_to_{string,octets} lacked a source case for it. With the null keyword those encounters are now deliberate, so: - Drop the "FR_TYPE_NULL inside TMPL_TYPE_DATA is uninitialised" assertion in tmpl_tokenize.c's TMPL_VERIFY. - Cast FR_TYPE_NULL to an empty string / zero-length octets box. The result is that positional xlat arguments can carry an explicit "no value" placeholder without the framework dropping the slot or the type system tripping over it.
1 parent 30867b7 commit 3b51650

2 files changed

Lines changed: 64 additions & 6 deletions

File tree

src/lib/server/tmpl_tokenize.c

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,6 +2616,41 @@ static fr_slen_t tmpl_afrom_value_substr(TALLOC_CTX *ctx, tmpl_t **out, fr_sbuff
26162616
FR_SBUFF_SET_RETURN(in, &our_in);
26172617
}
26182618

2619+
/** Match the bareword `null` and return a TMPL_TYPE_DATA carrying an FR_TYPE_NULL box
2620+
*
2621+
* Used as an explicit "no value" placeholder by callers that want the
2622+
* argument slot to remain present (so positional xlat arguments line
2623+
* up) without carrying any bytes. Downstream code distinguishes an
2624+
* intentional null from an uninitialised one by checking
2625+
* `fr_type_is_null(vb->type)` after the box has made it into an arg
2626+
* list - if it reaches the xlat body, the author put it there.
2627+
*
2628+
* @param[in] ctx to allocate tmpl to.
2629+
* @param[out] out where to write tmpl.
2630+
* @param[in] in sbuff to parse.
2631+
* @param[in] p_rules formatting rules.
2632+
* @return
2633+
* - 0 sbuff does not contain the `null` keyword.
2634+
* - > 0 how many bytes were parsed.
2635+
*/
2636+
static fr_slen_t tmpl_afrom_null_substr(TALLOC_CTX *ctx, tmpl_t **out, fr_sbuff_t *in,
2637+
fr_sbuff_parse_rules_t const *p_rules)
2638+
{
2639+
fr_sbuff_t our_in = FR_SBUFF(in);
2640+
tmpl_t *vpt;
2641+
2642+
if (!fr_sbuff_adv_past_strcase_literal(&our_in, "null")) return 0;
2643+
if (!tmpl_substr_terminal_check(&our_in, p_rules)) return 0;
2644+
2645+
MEM(vpt = tmpl_alloc(ctx, TMPL_TYPE_DATA, T_BARE_WORD,
2646+
fr_sbuff_start(&our_in), fr_sbuff_used(&our_in)));
2647+
fr_value_box_init(&vpt->data.literal, FR_TYPE_NULL, NULL, false);
2648+
2649+
*out = vpt;
2650+
2651+
FR_SBUFF_SET_RETURN(in, &our_in);
2652+
}
2653+
26192654
/** Parse a truth value
26202655
*
26212656
* @param[in] ctx to allocate tmpl to.
@@ -3380,6 +3415,16 @@ fr_slen_t tmpl_afrom_substr(TALLOC_CTX *ctx, tmpl_t **out,
33803415
fr_assert(!*out);
33813416
}
33823417

3418+
/*
3419+
* See if it's the `null` keyword. Matched before the
3420+
* numeric / address / enum branches so it isn't
3421+
* shadowed by a dictionary attribute literally named
3422+
* "null".
3423+
*/
3424+
slen = tmpl_afrom_null_substr(ctx, out, &our_in, p_rules);
3425+
if (slen > 0) goto done_bareword;
3426+
fr_assert(!*out);
3427+
33833428
/*
33843429
* See if it's a boolean value
33853430
*/
@@ -5354,11 +5399,12 @@ void tmpl_verify(char const *file, int line, tmpl_t const *vpt)
53545399
file, line);
53555400
}
53565401

5357-
if (fr_type_is_null(tmpl_value_type(vpt))) {
5358-
fr_fatal_assert_fail("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_DATA type was "
5359-
"FR_TYPE_NULL (uninitialised)", file, line);
5360-
}
5361-
5402+
/*
5403+
* An FR_TYPE_NULL box inside a TMPL_TYPE_DATA used to
5404+
* fire here as the "you forgot to init the box" signal,
5405+
* but the `null` keyword (see tmpl_afrom_null_substr)
5406+
* deliberately constructs one. Accept it.
5407+
*/
53625408
if (tmpl_value_type(vpt) >= FR_TYPE_MAX) {
53635409
fr_fatal_assert_fail("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_DATA type was "
53645410
"%i (outside the range of fr_type_ts)", file, line, tmpl_value_type(vpt));

src/lib/util/value.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2615,6 +2615,12 @@ static inline int fr_value_box_cast_to_strvalue(TALLOC_CTX *ctx, fr_value_box_t
26152615
fr_value_box_init(dst, FR_TYPE_STRING, dst_enumv, false);
26162616

26172617
switch (src->type) {
2618+
/*
2619+
* Explicit null casts to an empty string.
2620+
*/
2621+
case FR_TYPE_NULL:
2622+
return fr_value_box_bstrndup(ctx, dst, dst_enumv, "", 0, src->tainted);
2623+
26182624
/*
26192625
* The presentation format of octets is hex
26202626
* What we actually want here is the raw string
@@ -2667,6 +2673,13 @@ static inline int fr_value_box_cast_to_octets(TALLOC_CTX *ctx, fr_value_box_t *d
26672673
fr_value_box_safety_copy_changed(dst, src);
26682674

26692675
switch (src->type) {
2676+
/*
2677+
* An explicit null (e.g. the `null` keyword in unlang)
2678+
* casts to a zero-length octets box.
2679+
*/
2680+
case FR_TYPE_NULL:
2681+
return fr_value_box_memdup(ctx, dst, dst_enumv, NULL, 0, src->tainted);
2682+
26702683
/*
26712684
* <string> (excluding terminating \0)
26722685
*/
@@ -2746,7 +2759,6 @@ static inline int fr_value_box_cast_to_octets(TALLOC_CTX *ctx, fr_value_box_t *d
27462759
case FR_TYPE_VENDOR:
27472760
case FR_TYPE_UNION:
27482761
case FR_TYPE_INTERNAL:
2749-
case FR_TYPE_NULL:
27502762
case FR_TYPE_ATTR:
27512763
case FR_TYPE_COMBO_IP_ADDR: /* the types should have been realized to ipv4 / ipv6 */
27522764
case FR_TYPE_COMBO_IP_PREFIX:

0 commit comments

Comments
 (0)