]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add run-time evaluation of regular expressions.
authorAlan T. DeKok <aland@freeradius.org>
Wed, 1 Jun 2022 13:44:29 +0000 (09:44 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 3 Jun 2022 11:15:56 +0000 (07:15 -0400)
and more error checks, and tests

src/lib/unlang/xlat_expr.c
src/tests/unit/xlat/cond_regex.txt [new file with mode: 0644]

index e8d77c918fee721acf42b544d784b5982dd84eb7..034be26631d7d3670e06841a8f2f05a2e05c47e6 100644 (file)
@@ -331,10 +331,16 @@ XLAT_CMP_FUNC(cmp_gt,  T_OP_GT)
 XLAT_CMP_FUNC(cmp_ge,  T_OP_GE)
 
 typedef struct {
+       fr_token_t      op;
        xlat_exp_t      *regex;         //!< precompiled regex
        xlat_exp_t      *xlat;          //!< to expand
 } xlat_regex_inst_t;
 
+typedef struct {
+       bool                    last_success;
+       fr_value_box_list_t     list;
+} xlat_regex_rctx_t;
+
 static fr_slen_t xlat_expr_print_regex(fr_sbuff_t *out, xlat_exp_t const *node, void *instance, fr_sbuff_escape_rules_t const *e_rules)
 {
        size_t                  at_in = fr_sbuff_used_total(out);
@@ -394,6 +400,8 @@ static int xlat_instantiate_regex(xlat_inst_ctx_t const *xctx)
        fr_assert(rhs->type == XLAT_GROUP);
        regex = xlat_exp_head(rhs->group);
 
+       inst->op = xctx->ex->call.func->token;
+
        /*
         *      The RHS is more then just one regex node, it has to be dynamically expanded.
         */
@@ -426,8 +434,8 @@ static int xlat_instantiate_regex(xlat_inst_ctx_t const *xctx)
 }
 
 static xlat_arg_parser_t const regex_op_xlat_args[] = {
-       { .required = true, .type = FR_TYPE_STRING },
-       { .required = true, .type = FR_TYPE_STRING },
+       { .required = true, .concat = true, .type = FR_TYPE_STRING },
+       { .concat = true, .type = FR_TYPE_STRING },
        XLAT_ARG_PARSER_TERMINATOR
 };
 
@@ -495,12 +503,44 @@ static xlat_action_t xlat_regex_match(TALLOC_CTX *ctx, request_t *request, fr_va
        return XLAT_ACTION_DONE;
 }
 
+static xlat_action_t xlat_regex_resume(TALLOC_CTX *ctx, fr_dcursor_t *out,
+                                      xlat_ctx_t const *xctx,
+                                      request_t *request, fr_value_box_list_t *in)
+{
+       xlat_regex_inst_t const *inst = talloc_get_type_abort_const(xctx->inst, xlat_regex_inst_t);
+       xlat_regex_rctx_t       *rctx = talloc_get_type_abort(xctx->rctx, xlat_regex_rctx_t);
+       ssize_t                 slen;
+       fr_value_box_t          *lhs, *rhs;
+       regex_t                 *preg = NULL;
+
+       /*
+        *      If the expansions fails, then we fail the entire thing.
+        */
+       if (!rctx->last_success) {
+               talloc_free(rctx);
+               return XLAT_ACTION_FAIL;
+       }
+
+       /*
+        *      LHS should already have been expanded.  RHS was just expanded by us.
+        */
+       lhs = fr_dlist_head(in);
+       rhs = fr_dlist_head(&rctx->list);
+
+       slen = regex_compile(rctx, &preg, rhs->vb_strvalue, rhs->vb_length,
+                            NULL, true, true); /* no flags, allow subcaptures, at runtime */
+       if (slen <= 0) return XLAT_ACTION_FAIL;
+
+       return xlat_regex_match(ctx, request, lhs, &preg, out, inst->op);
+}
+
 static xlat_action_t xlat_regex_op(TALLOC_CTX *ctx, fr_dcursor_t *out,
                                   xlat_ctx_t const *xctx,
                                   request_t *request, fr_value_box_list_t *in,
                                   fr_token_t op)
 {
        xlat_regex_inst_t       *inst = talloc_get_type_abort(xctx->inst, xlat_regex_inst_t);
+       xlat_regex_rctx_t       *rctx;
        regex_t                 *preg;
        fr_value_box_t          *lhs;
 
@@ -515,7 +555,19 @@ static xlat_action_t xlat_regex_op(TALLOC_CTX *ctx, fr_dcursor_t *out,
                return xlat_regex_match(ctx, request, lhs, &preg, out, op);
        }
 
-       return XLAT_ACTION_FAIL;
+       MEM(rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), xlat_regex_rctx_t));
+       fr_value_box_list_init(&rctx->list);
+
+       if (unlang_xlat_yield(request, xlat_regex_resume, NULL, rctx) != XLAT_ACTION_YIELD) {
+       fail:
+               talloc_free(rctx);
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (unlang_xlat_push(ctx, &rctx->last_success, &rctx->list,
+                            request, inst->xlat->group, UNLANG_SUB_FRAME) < 0) goto fail;
+
+       return XLAT_ACTION_PUSH_UNLANG;
 }
 
 #define XLAT_REGEX_FUNC(_name, _op)  \
@@ -1416,6 +1468,11 @@ static ssize_t tokenize_field(xlat_exp_head_t *head, xlat_exp_t **out, fr_sbuff_
 
        XLAT_DEBUG("FIELD <-- %pV", fr_box_strvalue_len(fr_sbuff_current(in), fr_sbuff_remaining(in)));
 
+       /*
+        *      Regexes cannot have casts or subgroups.
+        */
+       if (expect_regex && !fr_sbuff_is_char(&our_in, '/')) goto expected_regex_error;
+
        /*
         *      Allow for explicit casts.  Non-leaf types are forbidden.
         */
@@ -1504,6 +1561,7 @@ static ssize_t tokenize_field(xlat_exp_head_t *head, xlat_exp_t **out, fr_sbuff_
        if (expect_regex) {
                if (quote != T_SOLIDUS_QUOTED_STRING) {
                        fr_sbuff_advance(&our_in, -(slen + 1)); /* account for quote */
+               expected_regex_error:
                        fr_strerror_const("Expected regular expression");
                        goto error;
                }
diff --git a/src/tests/unit/xlat/cond_regex.txt b/src/tests/unit/xlat/cond_regex.txt
new file mode 100644 (file)
index 0000000..54bc1b2
--- /dev/null
@@ -0,0 +1,77 @@
+proto-dictionary radius
+
+xlat_purify &User-Name !~ /^foo\nbar$/
+match (&User-Name !~ /^foo\nbar$/)
+
+xlat_purify &User-Name !~ /^foo\nbar$/i
+match (&User-Name !~ /^foo\nbar$/i)
+
+xlat_purify (&User-Name =~ "handled")
+match ERROR offset 15: Expected regular expression
+
+xlat_purify (&User-Name == /foo/)
+match ERROR offset 15: Unexpected regular expression
+
+# @todo - this should be allowed?
+xlat_purify &User-Name =~ &Filter-Id
+match ERROR offset 14: Expected regular expression
+
+xlat_expr &User-Name =~ (/foo/)
+match ERROR offset 14: Expected regular expression
+
+#
+#  Flags
+#
+xlat_purify &User-Name =~ /bar/i
+match (&User-Name =~ /bar/i)
+
+xlat_purify &User-Name =~ /bar/m
+match (&User-Name =~ /bar/m)
+
+xlat_purify &User-Name =~ /bar/im
+match (&User-Name =~ /bar/im)
+
+xlat_purify &User-Name =~ /bar/ima
+match ERROR offset 19: Unsupported regex flag 'a'
+
+xlat_purify &User-Name =~ /bar/ii
+match ERROR offset 19: Duplicate regex flag 'i'
+
+xlat_purify &User-Name =~ /bar/iia
+match ERROR offset 19: Duplicate regex flag 'i'
+
+#
+#  Escape the backslashes correctly
+#  And print them correctly
+#
+
+xlat_purify &User-Name =~ /@|./
+match (&User-Name =~ /@|./)
+
+xlat_purify &User-Name =~ /@|\\/
+match (&User-Name =~ /@|\\/)
+
+xlat_purify &User-Name =~ /^([^\\]*)\\(.*)$/
+match (&User-Name =~ /^([^\\]*)\\(.*)$/)
+
+xlat_purify &Tmp-Integer-0 =~ /%{Tmp-Integer-1} foo/
+match (&Tmp-Integer-0=~ /%{Request[0].Tmp-Integer-1} foo/)
+
+#
+#  If they're dumb enough to add a cast, then it will be just cast again
+#  to "string" before the regular expression is evaluated.
+#
+xlat_purify <integer>&Tmp-String-0 =~ /foo/
+match ((uint32)&Tmp-String-0 =~ /foo/)
+
+xlat_purify &Tmp-String-0 =~ <integer>/foo/
+match ERROR offset 17: Expected regular expression
+
+xlat_expr %{33}
+match ERROR offset 2: Invalid regex reference.  Must be in range 0-32
+
+xlat_purify &User-Name == /foo/
+match ERROR offset 14: Unexpected regular expression
+
+count
+match 41