From: Alan T. DeKok Date: Fri, 28 Mar 2025 18:34:02 +0000 (-0400) Subject: add and document regex.escape() X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0ef9f55831e22c6a524d1436f594cbe74e4ea34e;p=thirdparty%2Ffreeradius-server.git add and document regex.escape() which always escapes the input --- diff --git a/doc/antora/modules/reference/pages/unlang/condition/regex.adoc b/doc/antora/modules/reference/pages/unlang/condition/regex.adoc index c168becc353..ff15bd95e77 100644 --- a/doc/antora/modules/reference/pages/unlang/condition/regex.adoc +++ b/doc/antora/modules/reference/pages/unlang/condition/regex.adoc @@ -146,7 +146,23 @@ in the values produced by the dynamic expansion, so that they are treated as literal characters in the regular expression. To allow non-literals in dynamic elements of regular expressions, wrap any literal -values or expansions in `%regex.safe(...)`. +values or expansions in `%regex.escape(...)`. + +.Using regex.escape to perform escaping +==== +[source,unlang] +---- +local domain + +domain = %regex.escape("example.com") +if (Stripped-User-Name =~ /domain$/) { + ... +} +---- +==== + + +If you want to _prevent_ escaping you can use `%regex.safe()` to indicate that the text is safe for use as-is, in regular expressions. .Using regex.safe to prevent escaping ==== diff --git a/src/lib/unlang/xlat_builtin.c b/src/lib/unlang/xlat_builtin.c index c7e8e175782..85710beb2bf 100644 --- a/src/lib/unlang/xlat_builtin.c +++ b/src/lib/unlang/xlat_builtin.c @@ -229,7 +229,7 @@ void xlat_debug_attr_list(request_t *request, fr_pair_list_t const *list) } } -/** Common function to move boxes form input list to output list +/** Common function to move boxes from input list to output list * * This can be used to implement safe_for functions, as the xlat framework * can be used for concatenation, casting, and marking up output boxes as @@ -2737,6 +2737,28 @@ static xlat_action_t xlat_func_range(TALLOC_CTX *ctx, fr_dcursor_t *out, return XLAT_ACTION_DONE; } +static int CC_HINT(nonnull(2,3)) regex_xlat_escape(UNUSED request_t *request, fr_value_box_t *vb, UNUSED void *uctx) +{ + ssize_t slen; + fr_sbuff_t *out = NULL; + fr_value_box_entry_t entry; + + FR_SBUFF_TALLOC_THREAD_LOCAL(&out, 256, 4096); + + slen = fr_value_box_print(out, vb, ®ex_escape_rules); + if (slen < 0) return -1; + + entry = vb->entry; + + fr_value_box_clear(vb); + fr_value_box_init(vb, FR_TYPE_STRING, NULL, false); + (void) fr_value_box_bstrndup(vb, vb, NULL, fr_sbuff_start(out), fr_sbuff_used(out), false); + + vb->entry = entry; + + return 0; +} + #if defined(HAVE_REGEX_PCRE) || defined(HAVE_REGEX_PCRE2) static xlat_arg_parser_t const xlat_func_regex_args[] = { { .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .type = FR_TYPE_VOID }, @@ -4281,11 +4303,24 @@ do { \ XLAT_ARG_PARSER_TERMINATOR }; + static xlat_arg_parser_t const xlat_regex_escape_args[] = { + { .type = FR_TYPE_STRING, + .func = regex_xlat_escape, .safe_for = FR_REGEX_SAFE_FOR, .always_escape = true, + .variadic = true, .concat = true }, + XLAT_ARG_PARSER_TERMINATOR + }; + if (unlikely((xlat = xlat_func_register(xlat_ctx, "regex.safe", xlat_transparent, FR_TYPE_STRING)) == NULL)) return -1; xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_INTERNAL); xlat_func_args_set(xlat, xlat_regex_safe_args); xlat_func_safe_for_set(xlat, FR_REGEX_SAFE_FOR); + + if (unlikely((xlat = xlat_func_register(xlat_ctx, "regex.escape", + xlat_transparent, FR_TYPE_STRING)) == NULL)) return -1; + xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_INTERNAL); + xlat_func_args_set(xlat, xlat_regex_escape_args); + xlat_func_safe_for_set(xlat, FR_REGEX_SAFE_FOR); } XLAT_REGISTER_PURE("sha1", xlat_func_sha1, FR_TYPE_OCTETS, xlat_func_sha_arg); diff --git a/src/tests/keywords/regex-escape b/src/tests/keywords/regex-escape index a07054e2ce6..fb5fd1d5658 100644 --- a/src/tests/keywords/regex-escape +++ b/src/tests/keywords/regex-escape @@ -1,25 +1,40 @@ # # PRE: if # -string test_string1 -string test_string2 +string domain +string escaped +string from_user # # Strings which are expanded in a regex have regex special # characters escaped. Because the input strings are unsafe. # -test_string1 := %taint("example.com") -test_string2 := "exampleXcom" +domain := "example.com" +escaped := %regex.escape(domain) +from_user := "exampleXcom" -if ("exampleXcom" =~ /%{test_string1}/) { +if (domain == escaped) { test_fail } -if ("exampleXcom" !~ /%regex.safe(%{test_string1})/) { +# +# We require an explicit '.' in the from_user srring. +# +if ("exampleXcom" =~ /%{escaped}/) { test_fail } -if (test_string2 =~ /%{test_string1}/) { +# +# interpret the '.' as a regex match, but marking the text as safe. +# +if ("exampleXcom" !~ /%regex.safe(domain)/) { + test_fail +} + +# +# The text from the user should match, too. +# +if (from_user =~ /%{escaped}/) { test_fail }