]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add and document regex.escape()
authorAlan T. DeKok <aland@freeradius.org>
Fri, 28 Mar 2025 18:34:02 +0000 (14:34 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 28 Mar 2025 19:20:53 +0000 (15:20 -0400)
which always escapes the input

doc/antora/modules/reference/pages/unlang/condition/regex.adoc
src/lib/unlang/xlat_builtin.c
src/tests/keywords/regex-escape

index c168becc3539fd4fb2b823bcb44c2bfd721f320f..ff15bd95e7731bd0d3df69e74311fde72f3a0995 100644 (file)
@@ -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
 ====
index c7e8e175782d1da06b420b03ead25ef094184bf7..85710beb2bf7893a6af73324f616b17ed63e73fa 100644 (file)
@@ -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, &regex_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);
index a07054e2ce669d3f1dcc960a2ffb198b786e8270..fb5fd1d565801de3f5adba0050fffa9249ac960f 100644 (file)
@@ -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
 }