From c19a7ab59e9b2280454154fc463fac2b24a2aba8 Mon Sep 17 00:00:00 2001 From: "Alan T. DeKok" Date: Wed, 1 Jun 2022 09:44:29 -0400 Subject: [PATCH] add run-time evaluation of regular expressions. and more error checks, and tests --- src/lib/unlang/xlat_expr.c | 64 +++++++++++++++++++++++-- src/tests/unit/xlat/cond_regex.txt | 77 ++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 src/tests/unit/xlat/cond_regex.txt diff --git a/src/lib/unlang/xlat_expr.c b/src/lib/unlang/xlat_expr.c index e8d77c918fe..034be26631d 100644 --- a/src/lib/unlang/xlat_expr.c +++ b/src/lib/unlang/xlat_expr.c @@ -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 index 00000000000..54bc1b2f568 --- /dev/null +++ b/src/tests/unit/xlat/cond_regex.txt @@ -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 &Tmp-String-0 =~ /foo/ +match ((uint32)&Tmp-String-0 =~ /foo/) + +xlat_purify &Tmp-String-0 =~ /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 -- 2.47.3