+++ /dev/null
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-/**
- * $Id$
- *
- * @file src/lib/server/cond_eval.c
- * @brief Evaluate complex conditions
- *
- * @copyright 2007 The FreeRADIUS server project
- * @copyright 2007 Alan DeKok (aland@deployingradius.com)
- */
-RCSID("$Id$")
-
-#include <freeradius-devel/server/cond.h>
-#include <freeradius-devel/server/cond_eval.h>
-#include <freeradius-devel/server/module.h>
-#include <freeradius-devel/server/paircmp.h>
-#include <freeradius-devel/server/regex.h>
-#include <freeradius-devel/server/tmpl_dcursor.h>
-#include <freeradius-devel/util/debug.h>
-#include <freeradius-devel/util/print.h>
-
-#include <ctype.h>
-
-#ifdef WITH_EVAL_DEBUG
-# define EVAL_DEBUG(fmt, ...) printf("EVAL: ");fr_fprintf(stdout, fmt, ## __VA_ARGS__);printf("\n");fflush(stdout)
-#else
-# define EVAL_DEBUG(...)
-#endif
-
-static int cond_realize_tmpl(request_t *request,
- fr_value_box_t **out, fr_value_box_t **to_free,
- tmpl_t *in, tmpl_t *other, fr_value_box_t *async);
-
-/** Map keywords to #fr_pair_list_t values
- */
-static fr_table_num_sorted_t const cond_type_table[] = {
- { L("child"), COND_TYPE_CHILD },
- { L("tmpl"), COND_TYPE_TMPL },
- { L("false"), COND_TYPE_FALSE },
- { L("invalid"), COND_TYPE_INVALID },
- { L("map"), COND_TYPE_MAP },
- { L("true"), COND_TYPE_TRUE },
-};
-static size_t cond_type_table_len = NUM_ELEMENTS(cond_type_table);
-
-static fr_table_num_sorted_t const cond_pass2_table[] = {
- { L("none"), PASS2_FIXUP_NONE },
- { L("attr"), PASS2_FIXUP_ATTR },
- { L("type"), PASS2_FIXUP_TYPE },
- { L("paircompre"), PASS2_PAIRCOMPARE },
-};
-static size_t cond_pass2_table_len = NUM_ELEMENTS(cond_pass2_table);
-
-
-/** Debug function to dump a cond structure
- *
- */
-void cond_debug(fr_cond_t const *cond)
-{
- fr_cond_t const *c;
-
- for (c = cond; c; c =c->next) {
- INFO("cond %s (%p)", fr_table_str_by_value(cond_type_table, c->type, "<INVALID>"), cond);
- INFO("\tnegate : %s", c->negate ? "true" : "false");
- INFO("\tfixup : %s", fr_table_str_by_value(cond_pass2_table, c->pass2_fixup, "<INVALID>"));
-
- switch (c->type) {
- case COND_TYPE_MAP:
- INFO("lhs (");
- tmpl_debug(c->data.map->lhs);
- INFO(")");
- INFO("rhs (");
- tmpl_debug(c->data.map->rhs);
- INFO(")");
- break;
-
- case COND_TYPE_RCODE:
- INFO("\trcode : %s", fr_table_str_by_value(rcode_table, c->data.rcode, ""));
- break;
-
- case COND_TYPE_TMPL:
- tmpl_debug(c->data.vpt);
- break;
-
- case COND_TYPE_CHILD:
- INFO("child (");
- cond_debug(c->data.child);
- INFO(")");
- break;
-
- case COND_TYPE_AND:
- INFO("&& ");
- break;
-
- case COND_TYPE_OR:
- INFO("|| ");
- break;
-
- default:
- break;
- }
- }
-}
-
-/** Evaluate a template
- *
- * Converts a tmpl_t to a boolean value.
- *
- * @param[in] request the request_t
- * @param[in] in the template to evaluate
- * @param[in] async the asynchronously evaluated value box, for XLAT and EXEC
- * @return
- * - 0 for "no match"
- * - 1 for "match".
- */
-static bool cond_eval_tmpl(request_t *request, tmpl_t const *in, fr_value_box_t *async)
-{
- int rcode = false;
- fr_pair_t *vp = NULL;
- fr_value_box_t *box, *box_free;
- tmpl_t *vpt;
-
- box = box_free = NULL;
- memcpy(&vpt, &in, sizeof(in)); /* const issues */
-
- switch (vpt->type) {
- case TMPL_TYPE_ATTR:
- /*
- * No cast means that it's an existence check.
- */
- if (fr_type_is_null(tmpl_rules_cast(vpt))) {
- return (tmpl_find_vp(NULL, request, vpt) == 0);
- }
-
- /*
- * Cast means that we cast the attribute to a
- * particular type.
- */
- if (tmpl_find_vp(&vp, request, vpt) < 0) {
- return false;
- }
-
- MEM(box = fr_value_box_alloc_null(request));
- box_free = box;
-
- if (fr_value_box_cast(box, box, tmpl_rules_cast(vpt), NULL, &vp->data) < 0) {
- RPEDEBUG("Failed casting %pV to type %s", box,
- fr_type_to_str(tmpl_rules_cast(vpt)));
- goto done;
- }
- break;
-
- case TMPL_TYPE_XLAT:
- case TMPL_TYPE_EXEC:
- /*
- * Realize and cast the tmpl.
- */
- if (cond_realize_tmpl(request, &box, &box_free, vpt, NULL, async) < 0) {
- return false;
- }
-
- /*
- * Old-style: zero length strings are false.
- * Other strings are true.
- *
- * We don't yet have xlats returning lists of
- * value boxes, so there's an assert.
- */
- if (fr_type_is_null(tmpl_rules_cast(vpt))) {
- switch (box->type) {
- case FR_TYPE_STRING:
- case FR_TYPE_OCTETS:
- rcode = (box->vb_length > 0);
- goto done;
-
- /*
- * Not yet handled.
- */
- default:
- fr_assert(0);
- return false;
- }
- }
- break;
-
- /*
- * Everything else MUST have been forbidden, or
- * already realized to a COND_TYPE_TRUE/FALSE.
- */
- default:
- fr_assert(0);
- EVAL_DEBUG("FAIL %d", __LINE__);
- goto done;
- }
-
- /*
- * If it's already a bool, just use that.
- *
- * Otherwise cast the data to bool. This cast lets the
- * value code figure out what is false and what is true.
- */
- if (box->type == FR_TYPE_BOOL) {
- rcode = box->vb_bool;
-
- } else {
- fr_value_box_t out;
-
- fr_value_box_init_null(&out);
- if (fr_value_box_cast(request, &out, FR_TYPE_BOOL, NULL, box) < 0) {
- talloc_free(box_free);
- return -1;
- }
-
- rcode = out.vb_bool;
- fr_value_box_clear(&out);
- }
-
-done:
- talloc_free(box_free);
- return rcode;
-}
-
-
-#ifdef HAVE_REGEX
-/** Perform a regular expressions comparison between two operands
- *
- * @param[in] request The current request.
- * @param[in] subject to executed regex against.
- * @param[in,out] preg Pointer to pre-compiled or runtime-compiled
- * regular expression. In the case of runtime-compiled
- * the pattern may be stolen by the `regex_sub_to_request`
- * function as the original pattern is needed to resolve
- * capture groups.
- * The caller should only free the `regex_t *` if it
- * compiled it, and the pointer has not been set to NULL
- * when this function returns.
- * @return
- * - -1 on failure.
- * - 0 for "no match".
- * - 1 for "match".
- */
-static int cond_do_regex(request_t *request, fr_value_box_t const *subject, regex_t **preg)
-{
- uint32_t subcaptures;
- int ret;
-
- fr_regmatch_t *regmatch;
-
- if (!fr_cond_assert(subject != NULL)) return -1;
- if (!fr_cond_assert(subject->type == FR_TYPE_STRING)) return -1;
-
- EVAL_DEBUG("CMP WITH REGEX");
-
- subcaptures = regex_subcapture_count(*preg);
- if (!subcaptures) subcaptures = REQUEST_MAX_REGEX + 1; /* +1 for %{0} (whole match) capture group */
- MEM(regmatch = regex_match_data_alloc(NULL, subcaptures));
-
- /*
- * Evaluate the expression
- */
- ret = regex_exec(*preg, subject->vb_strvalue, subject->vb_length, regmatch);
- switch (ret) {
- case 0:
- EVAL_DEBUG("CLEARING SUBCAPTURES");
- regex_sub_to_request(request, NULL, NULL); /* clear out old entries */
- break;
-
- case 1:
- EVAL_DEBUG("SETTING SUBCAPTURES");
- regex_sub_to_request(request, preg, ®match);
- break;
-
- case -1:
- EVAL_DEBUG("REGEX ERROR");
- RPEDEBUG("regex failed");
- break;
-
- default:
- break;
- }
-
- talloc_free(regmatch); /* free if not consumed */
-
- return ret;
-}
-#endif
-
-static size_t regex_escape(UNUSED request_t *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
-{
- char *p = out;
-
- while (*in && (outlen >= 2)) {
- switch (*in) {
- case '\\':
- case '.':
- case '*':
- case '+':
- case '?':
- case '|':
- case '^':
- case '$':
- case '[': /* we don't list close braces */
- case '{':
- case '(':
- if (outlen < 3) goto done;
-
- *(p++) = '\\';
- outlen--;
- FALL_THROUGH;
-
- default:
- *(p++) = *(in++);
- outlen--;
- break;
- }
- }
-
-done:
- *(p++) = '\0';
- return p - out;
-}
-
-/** Turn a raw #tmpl_t into #fr_value_box_t, mostly.
- *
- * It does nothing for lists, attributes, and precompiled regexes.
- *
- * For #TMPL_TYPE_DATA, it returns the raw data, which MUST NOT have
- * a cast, and which MUST have the correct data type.
- *
- * For everything else (exec, xlat, regex-xlat), it evaluates the
- * tmpl, and returns a "realized" #fr_value_box_t. That box can then
- * be used for comparisons, with minimal extra processing.
- */
-static int cond_realize_tmpl(request_t *request,
- fr_value_box_t **out, fr_value_box_t **to_free,
- tmpl_t *in, tmpl_t *other, /* both really should be 'const' */
- fr_value_box_t *async)
-{
- fr_value_box_t *box;
- xlat_escape_legacy_t escape = NULL;
-
- *out = *to_free = NULL;
-
- switch (in->type) {
- /*
- * These are handled elsewhere.
- */
-#ifdef HAVE_REGEX
- case TMPL_TYPE_REGEX:
- fr_assert(!async);
- return 0;
-#endif
-
- case TMPL_TYPE_ATTR:
- /*
- * fast path? If there's only one attribute, AND
- * tmpl_attr_tail_num is a simple number, then just find
- * that attribute. This fast path should ideally
- * avoid all of the cost of setting up the
- * cursors?
- */
- fr_assert(!async);
- return 0;
-
- /*
- * Return the raw data, which MUST already have been
- * converted to the correct thing.
- */
- case TMPL_TYPE_DATA:
- fr_assert((fr_type_is_null(tmpl_rules_cast(in))) || (tmpl_rules_cast(in) == tmpl_value_type(in)));
- *out = tmpl_value(in);
- fr_assert(!async);
- return 0;
-
-#ifdef HAVE_REGEX
- case TMPL_TYPE_REGEX_XLAT:
- escape = regex_escape;
- FALL_THROUGH;
-#endif
-
- case TMPL_TYPE_EXEC:
- case TMPL_TYPE_XLAT:
- {
- ssize_t ret;
- fr_type_t cast_type;
- fr_dict_attr_t const *da = NULL;
-
- /*
- * We can't be TMPL_TYPE_ATTR or TMPL_TYPE_DATA,
- * because that was caught above.
- *
- * So we look for an explicit cast, and if we
- * don't find that, then the *other* side MUST
- * have an explicit data type.
- */
- if (tmpl_rules_cast(in) != FR_TYPE_NULL) {
- cast_type = tmpl_rules_cast(in);
-
- } else if (!other) {
- cast_type = FR_TYPE_STRING;
-
- } else if (tmpl_rules_cast(other)) {
- cast_type = tmpl_rules_cast(other);
-
- } else if (tmpl_is_attr(other)) {
- da = tmpl_attr_tail_da(other);
- cast_type = da->type;
-
- } else if (tmpl_is_data(other)) {
- cast_type = tmpl_value_type(other);
-
- } else {
- cast_type = FR_TYPE_STRING;
- }
-
- if (!async) {
- box = NULL;
- ret = tmpl_aexpand(request, &box, request, in, escape, NULL);
- if (ret < 0) {
- if (cast_type != FR_TYPE_STRING) return -1;
-
- box = fr_value_box_alloc(request, FR_TYPE_STRING, NULL);
- if (!box) return -1;
- }
-
- fr_assert(box != NULL);
- *out = *to_free = box;
-
- } else {
- *out = box = async;
- *to_free = NULL;
- }
-
- if (cast_type != box->type) {
- if (fr_value_box_cast_in_place(box, box, cast_type, da) < 0) {
- *out = *to_free = NULL;
- RPEDEBUG("Failed casting!");
- return -1;
- }
- }
-
- return 0;
- }
-
- default:
- break;
- }
-
- /*
- * Other tmpl type, return an error.
- */
- fr_assert(0);
- return -1;
-}
-
-
-static int cond_realize_attr(request_t *request, fr_value_box_t **realized, fr_value_box_t *box,
- tmpl_t *vpt, fr_pair_t *vp, fr_dict_attr_t const *da)
-{
- fr_type_t cast_type;
-
- /*
- * Sometimes we're casting to a type with enums. If so,
- * use that.
- */
- if (da) {
- cast_type = da->type;
-
- } else if (tmpl_rules_cast(vpt) != FR_TYPE_NULL) {
- /*
- * If there's an explicit cast, use that.
- */
- cast_type = tmpl_rules_cast(vpt);
-
- } else {
- /*
- * Otherwise the VP is already of the correct type.
- */
- goto dont_cast;
- }
-
- /*
- * No casting needed. Just return the data.
- */
- if (cast_type == vp->vp_type) {
- dont_cast:
- *realized = &vp->data;
- return 0;
- }
-
- fr_value_box_init_null(box);
- if (fr_value_box_cast(request, box, cast_type, da, &vp->data) < 0) {
- if (request) RPEDEBUG("Failed casting %pV to type %s", &vp->data,
- fr_type_to_str(tmpl_rules_cast(vpt)));
- return -1;
- }
-
- *realized = box;
- return 0;
-}
-
-static bool cond_compare_attrs(request_t *request, fr_value_box_t *lhs, map_t const *map)
-{
- int rcode;
- fr_pair_t *vp;
- fr_dcursor_t cursor;
- tmpl_dcursor_ctx_t cc;
- fr_value_box_t *rhs, rhs_cast;
- fr_dict_attr_t const *da = NULL;
-
- if (tmpl_is_attr(map->lhs) && fr_type_is_null(tmpl_rules_cast(map->lhs))) da = tmpl_attr_tail_da(map->lhs);
-
- fr_assert(lhs != NULL);
- rhs = NULL; /* shut up clang scan */
- fr_value_box_init_null(&rhs_cast);
-
- for (vp = tmpl_dcursor_init(&rcode, request, &cc, &cursor, request, map->rhs);
- vp;
- vp = fr_dcursor_next(&cursor)) {
- if (cond_realize_attr(request, &rhs, &rhs_cast, map->rhs, vp, da) < 0) {
- RPEDEBUG("Failed realizing RHS %pV", &vp->data);
- if (rhs == &rhs_cast) fr_value_box_clear(&rhs_cast);
- rcode = -1;
- break;
- }
-
- fr_assert(rhs && (lhs->type == rhs->type));
-
- rcode = fr_value_box_cmp_op(map->op, lhs, rhs);
-
- if (rhs == &rhs_cast) fr_value_box_clear(&rhs_cast);
- if (rcode != 0) break;
- }
-
- tmpl_dcursor_clear(&cc);
- return (rcode == 1);
-}
-
-static bool cond_compare_virtual(request_t *request, map_t const *map)
-{
- int rcode;
- fr_pair_t *vp;
- fr_value_box_t *rhs, rhs_cast;
- fr_dcursor_t cursor;
- tmpl_dcursor_ctx_t cc;
-
- fr_assert(tmpl_is_attr(map->lhs));
- fr_assert(tmpl_is_attr(map->rhs));
-
- rhs = NULL; /* shut up clang scan */
- fr_value_box_init_null(&rhs_cast);
-
- for (vp = tmpl_dcursor_init(&rcode, request, &cc, &cursor, request, map->rhs);
- vp;
- vp = fr_dcursor_next(&cursor)) {
- if (cond_realize_attr(request, &rhs, &rhs_cast, map->rhs, vp, NULL) < 0) {
- RPEDEBUG("Failed realizing RHS %pV", &vp->data);
- if (rhs == &rhs_cast) fr_value_box_clear(&rhs_cast);
- rcode = -1;
- break;
- }
-
- rcode = paircmp_virtual(request, tmpl_attr_tail_da(map->lhs), map->op, rhs);
- rcode = (rcode == 0) ? 1 : 0;
- if (rhs == &rhs_cast) fr_value_box_clear(&rhs_cast);
- if (rcode != 0) break;
- }
-
- tmpl_dcursor_clear(&cc);
- return (rcode == 1);
-}
-
-/** Evaluate a map
- *
- * @param[in] request the request_t
- * @param[in] c the condition to evaluate
- * @param[in] async_lhs the asynchronously evaluated value box, for XLAT and EXEC
- * @param[in] async_rhs the asynchronously evaluated value box, for XLAT and EXEC
- * @return
- * - false for no match (including "not found")
- * - true for match
- */
-static bool cond_eval_map(request_t *request, fr_cond_t const *c,
- fr_value_box_t *async_lhs, fr_value_box_t *async_rhs)
-{
- int rcode = 0;
- map_t const *map = c->data.map;
-
- fr_value_box_t *lhs, *lhs_free;
- fr_value_box_t *rhs, *rhs_free;
- regex_t *preg, *preg_free;
-
-#ifndef NDEBUG
- /*
- * At this point, all tmpls MUST have been resolved.
- */
- fr_assert(!tmpl_is_unresolved(c->data.map->lhs));
- fr_assert(!tmpl_is_unresolved(c->data.map->rhs));
-#endif
-
- EVAL_DEBUG(">>> MAP TYPES LHS: %s, RHS: %s",
- tmpl_type_to_str(map->lhs->type),
- tmpl_type_to_str(map->rhs->type));
-#ifdef WITH_EVAL_DEBUG
- tmpl_debug(map->lhs);
- tmpl_debug(map->rhs);
-#endif
-
- MAP_VERIFY(map);
- preg = preg_free = NULL;
-
- /*
- * Realize the LHS of a condition.
- */
- if (cond_realize_tmpl(request, &lhs, &lhs_free, map->lhs, map->rhs, async_lhs) < 0) {
- fr_strerror_const("Failed evaluating left side of condition");
- return false;
- }
-
- /*
- * Realize the RHS of a condition.
- */
- if (cond_realize_tmpl(request, &rhs, &rhs_free, map->rhs, map->lhs, async_rhs) < 0) {
- fr_strerror_const("Failed evaluating right side of condition");
- return false;
- }
-
- /*
- * Precompile the regular expressions.
- */
- if (map->op == T_OP_REG_EQ) {
- if (tmpl_is_regex(map->rhs)) {
- if (!fr_cond_assert(!rhs)) goto done;
-
- preg = tmpl_regex(map->rhs);
- } else {
- ssize_t slen;
-
- if (!fr_cond_assert(rhs && tmpl_contains_regex(map->rhs))) goto done;
-
- slen = regex_compile(request, &preg_free, rhs->vb_strvalue, rhs->vb_length,
- tmpl_regex_flags(map->rhs), true, true);
- if (slen <= 0) {
- REMARKER(rhs->vb_strvalue, -slen, "%s", fr_strerror());
- EVAL_DEBUG("FAIL %d", __LINE__);
- return false;
- }
- preg = preg_free;
- }
-
- /*
- * We have a value on the LHS. Just go do that.
- */
- if (lhs) {
- rcode = cond_do_regex(request, lhs, &preg);
- goto done;
- }
-
- /*
- * Otherwise loop over the LHS attribute / list.
- */
- goto check_attrs;
- }
-
- /*
- * We have both left and right sides as #fr_value_box_t,
- * we can just evaluate the comparison here.
- *
- * This is largely just cond_cmp_values() ...
- */
- if (lhs && rhs) {
- rcode = fr_value_box_cmp_op(map->op, lhs, rhs);
- goto done;
- }
-
- /*
- * LHS is a virtual attribute. The RHS MUST be data, not
- * an attribute or a list.
- */
- if (c->pass2_fixup == PASS2_PAIRCOMPARE) {
- fr_assert(tmpl_is_attr(map->lhs));
-
- if (map->op == T_OP_REG_EQ) {
- fr_strerror_const("Virtual attributes cannot be used with regular expressions");
- return false;
- }
-
- /*
- * &LDAP-Group == &Filter-Id
- */
- if (tmpl_is_attr(map->rhs)) {
- fr_assert(!lhs);
- fr_assert(!rhs);
-
- rcode = cond_compare_virtual(request, map);
- goto done;
- }
-
- /*
- * Forbid bad things.
- */
- if (!rhs) {
- fr_strerror_const("Invalid comparison for virtual attribute");
- return false;
- }
-
- /*
- * Do JUST the virtual attribute comparison.
- * Skip all of the rest of the complexity of paircmp().
- */
- rcode = paircmp_virtual(request, tmpl_attr_tail_da(map->lhs), c->data.map->op, rhs);
- rcode = (rcode == 0) ? 1 : 0;
- goto done;
- }
-
-check_attrs:
- switch (map->lhs->type) {
- /*
- * LHS is an attribute or list
- */
- case TMPL_TYPE_ATTR:
- {
- fr_pair_t *vp;
- fr_dcursor_t cursor;
- tmpl_dcursor_ctx_t cc;
-
- fr_assert(!lhs);
-
- for (vp = tmpl_dcursor_init(&rcode, request, &cc, &cursor, request, map->lhs);
- vp;
- vp = fr_dcursor_next(&cursor)) {
- fr_value_box_t lhs_cast;
-
- /*
- * Take the value box directly from the
- * attribute, _unless_ there's a cast.
- */
- if (cond_realize_attr(request, &lhs, &lhs_cast, map->lhs, vp, NULL) < 0) {
- rcode = -1;
- goto done;
- }
- fr_assert(lhs != NULL);
-
- /*
- * Now that we have a realized LHS, we
- * can do a regex comparison, using the
- * precompiled regex.
- */
- if (map->op == T_OP_REG_EQ) {
- rcode = cond_do_regex(request, lhs, &preg);
- goto next;
- }
-
- /*
- * We have a realized RHS. Just do the
- * comparisons with the value boxes.
- *
- * Realizing the LHS means that we've
- * either used the VP data as-is, or cast
- * it to the correct data type.
- */
- if (rhs) {
- fr_assert(lhs->type == rhs->type);
- rcode = fr_value_box_cmp_op(map->op, lhs, rhs);
- goto next;
- }
-
- /*
- * And we're left with attribute
- * comparisons. We've got to find the
- * attribute on the RHS, and do the
- * comparisons.
- *
- * This comparison means looping over all
- * matching attributes. We're already
- * many layers deep of indentation, so
- * just dump this code into a separate
- * function.
- */
- fr_assert(tmpl_is_attr(map->rhs));
-
- rcode = cond_compare_attrs(request, lhs, map);
-
- next:
- if (lhs == &lhs_cast) fr_value_box_clear(&lhs_cast);
- lhs = NULL;
- if (rcode != 0) goto done;
- continue;
- }
-
- tmpl_dcursor_clear(&cc);
- }
- break;
-
- default:
- fr_assert(0);
- rcode = -1;
- break;
- }
-
- EVAL_DEBUG("<<<");
-
-done:
- talloc_free(lhs_free);
- talloc_free(rhs_free);
-
- /*
- * Capture groups may have grabbed preg and put it into
- * request data, in which case we don't free it.
- */
- if (preg) talloc_free(preg_free);
- return (rcode == 1);
-}
-
-
-/** Evaluate a fr_cond_t;
- *
- * @param[in] request the request_t
- * @param[in] modreturn the previous module return code
- * @param[in] c the condition to evaluate
- * @return
- * - false for "no match", including "not found"
- * - true for "match"
- */
-bool cond_eval(request_t *request, rlm_rcode_t modreturn, fr_cond_t const *c)
-{
- int rcode = false;
-
-#ifdef WITH_EVAL_DEBUG
- char buffer[1024];
-
- cond_print(&FR_SBUFF_OUT(buffer, sizeof(buffer)), c);
- EVAL_DEBUG("%s", buffer);
-#endif
-
- while (c) {
- switch (c->type) {
- case COND_TYPE_TMPL:
- rcode = cond_eval_tmpl(request, c->data.vpt, NULL);
- break;
-
- case COND_TYPE_RCODE:
- rcode = (c->data.rcode == modreturn);
- break;
-
- case COND_TYPE_MAP:
- rcode = cond_eval_map(request, c, NULL, NULL);
- break;
-
- case COND_TYPE_CHILD:
- c = c->data.child;
- continue;
-
- case COND_TYPE_TRUE:
- rcode = true;
- break;
-
- case COND_TYPE_FALSE:
- rcode = false;
- break;
- default:
- EVAL_DEBUG("FAIL %d", __LINE__);
- return false;
- }
-
- /*
- * Errors are "no match".
- */
- if (rcode < 0) return false;
-
- if (c->negate) rcode = !rcode;
-
- /*
- * We've fallen off of the end of this evaluation
- * string. Go back up to the parent, and then to
- * the next sibling of the parent.
- *
- * Do this repeatedly until we have a c->next
- */
- while (!c->next) {
-return_to_parent:
- c = c->parent;
- if (!c) goto done;
- }
-
- /*
- * Do short-circuit evaluations.
- */
- switch (c->next->type) {
- case COND_TYPE_AND:
- if (!rcode) goto return_to_parent;
-
- c = c->next->next; /* skip the && */
- break;
-
- case COND_TYPE_OR:
- if (rcode) goto return_to_parent;
-
- c = c->next->next; /* skip the || */
- break;
-
- default:
- fr_assert(0);
- c = c->next;
- break;
- }
- }
-
-done:
- if (rcode < 0) {
- EVAL_DEBUG("FAIL %d", __LINE__);
- }
- return (rcode == 1);
-}
-
-/** Asynchronous evaluation of conditions.
- *
- * The caller is expected to clear the structure, and then set
- * a->ctx = talloc ctx for ephemeral value boxes
- * a->state = COND_EVAL_STATE_INIT
- * a->c = condition to evaluate
- * a->modreturn the module return code before the condition
- * a->result = true
- *
- * On return, the caller checks a->state
- *
- * COND_EVAL_STATE_EXPAND - a->tmpl_lhs and/or a->tmpl_rhs are
- * asynchronous templates which need to be pushed onto the unlang
- * stack in order to be evaluated. The evaluation results should go
- * into a->vb_lhs and a->vb_rhs, respectively. The caller should then
- * set a->state = COND_EVAL_STATE_EVAL, and call the function again to
- * evaluate the results.
- *
- * COND_EVAL_STATE_DONE - the result of the condition is in a->result.
- *
- * @param[in] request the request to evaluate
- * @param[in,out] a the asynchronous data structure to evaluate
- * @return
- * - <0 on error
- * - 0 on success
- */
-int cond_eval_async(request_t *request, fr_cond_async_t *a)
-{
- int rcode;
- fr_cond_t const *c;
-
- if (!request || !a || !a->c) return -1;
-
-redo:
- c = a->c;
-
- if (a->state == COND_EVAL_STATE_INIT) {
- while (c->type == COND_TYPE_CHILD) {
- c = c->data.child;
- }
-
- /*
- * Evaluate synchronous conditions as quickly as
- * possible.
- */
- if (!c->async_required) {
- a->result = cond_eval(request, a->modreturn, a->c);
- goto return_to_parent;
- }
-
- switch (c->type) {
- case COND_TYPE_TMPL:
- fr_assert(tmpl_async_required(c->data.vpt));
- a->tmpl_lhs = c->data.vpt;
- a->tmpl_rhs = NULL;
- break;
-
- case COND_TYPE_MAP:
- a->tmpl_lhs = tmpl_async_required(c->data.map->lhs) ? c->data.map->lhs : NULL;
- a->tmpl_rhs = tmpl_async_required(c->data.map->rhs) ? c->data.map->rhs : NULL;
-
- fr_assert(a->tmpl_lhs || a->tmpl_rhs);
- break;
-
- default:
- fr_assert(0);
- return -1;
- }
-
- /*
- * Tell the caller to expand the tmpls.
- *
- * The caller should then set
- *
- * a->state = COND_EVAL_STATE_EVAL
- *
- * in order to tell us that we need to evaluate
- * the expanded tmpls.
- */
- a->state = COND_EVAL_STATE_EXPAND;
- return 0;
- } /* INIT state */
-
- if (a->state == COND_EVAL_STATE_EVAL) {
- switch (c->type) {
- case COND_TYPE_TMPL:
- fr_assert(a->vb_lhs);
- rcode = cond_eval_tmpl(request, c->data.vpt, a->vb_lhs);
- a->result = (rcode == 1);
- break;
-
- case COND_TYPE_MAP:
- fr_assert(a->vb_lhs || a->vb_rhs);
-
- rcode = cond_eval_map(request, c, a->vb_lhs, a->vb_rhs);
- a->result = (rcode == 1);
- break;
-
- default:
- fr_assert(0);
- return -1;
- }
-
- TALLOC_FREE(a->vb_lhs);
- TALLOC_FREE(a->vb_rhs);
- a->tmpl_lhs = a->tmpl_rhs = NULL;
-
- if (c->negate) a->result = !a->result;
- } /* EVAL state */
-
- /*
- * We've fallen off of the end of this evaluation
- * string. Go back up to the parent, and then to
- * the next sibling of the parent.
- *
- * Do this repeatedly until we have a c->next.
- */
- while (!c->next) {
-return_to_parent:
- c = c->parent;
- if (!c) {
- a->state = COND_EVAL_STATE_DONE;
- return 0;
- }
- }
- c = c->next;
-
- /*
- * Do short-circuit evaluations.
- */
- switch (c->type) {
- case COND_TYPE_AND:
- if (!a->result) goto return_to_parent;
-
- fr_assert(c->next != NULL);
- c = c->next; /* skip the && */
- break;
-
- case COND_TYPE_OR:
- if (a->result) goto return_to_parent;
-
- fr_assert(c->next != NULL);
- c = c->next; /* skip the || */
- break;
-
- default:
- fr_assert(0);
- break;
- }
-
- /*
- * We now have a new condition which needs to be
- * evaluated. Go back to figuring out if it's async or
- * not.
- */
- a->c = c;
- a->state = COND_EVAL_STATE_INIT;
- goto redo;
-}
-
-/** Evaluate a map as if it is a condition.
- *
- */
-bool fr_cond_eval_map(request_t *request, map_t const *map)
-{
- fr_cond_t cond;
-
- memset(&cond, 0, sizeof(cond));
-
- /*
- * Convert !* and =* to existence checks.
- */
- switch (map->op) {
- case T_OP_CMP_FALSE:
- cond.negate = true;
- FALL_THROUGH;
-
- case T_OP_CMP_TRUE:
- cond.type = COND_TYPE_TMPL;
- cond.data.vpt = UNCONST(tmpl_t *, map->lhs);
- break;
-
- default:
- cond.type = COND_TYPE_MAP;
- cond.data.map = UNCONST(map_t *, map);
- break;
- }
-
- return cond_eval(request, RLM_MODULE_NOOP, &cond);
-}
+++ /dev/null
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-/**
- * $Id$
- *
- * @file src/lib/server/cond_tokenize.c
- * @brief Parse complex conditions
- *
- * @copyright 2013 Alan DeKok (aland@freeradius.org)
- */
-RCSID("$Id$")
-
-#include <freeradius-devel/server/base.h>
-#include <freeradius-devel/server/cond_eval.h>
-#include <freeradius-devel/server/cond.h>
-#include <freeradius-devel/util/debug.h>
-#include <freeradius-devel/protocol/freeradius/freeradius.internal.h>
-
-#include <ctype.h>
-
-static fr_table_num_sorted_t const allowed_return_codes[] = {
- { L("fail"), 1 },
- { L("handled"), 1 },
- { L("invalid"), 1 },
- { L("noop"), 1 },
- { L("notfound"), 1 },
- { L("ok"), 1 },
- { L("reject"), 1 },
- { L("updated"), 1 },
- { L("disallow"), 1 }
-};
-static size_t allowed_return_codes_len = NUM_ELEMENTS(allowed_return_codes);
-
-fr_table_num_sorted_t const cond_quote_table[] = {
- { L("\""), T_DOUBLE_QUOTED_STRING }, /* Don't re-order, backslash throws off ordering */
- { L("'"), T_SINGLE_QUOTED_STRING },
- { L("/"), T_SOLIDUS_QUOTED_STRING },
- { L("`"), T_BACK_QUOTED_STRING }
-};
-size_t cond_quote_table_len = NUM_ELEMENTS(cond_quote_table);
-
-fr_table_num_sorted_t const cond_logical_op_table[] = {
- { L("&&"), COND_TYPE_AND },
- { L("||"), COND_TYPE_OR }
-};
-size_t cond_logical_op_table_len = NUM_ELEMENTS(cond_logical_op_table);
-
-fr_table_num_sorted_t const cond_cmp_op_table[] = {
- { L("!*"), T_OP_CMP_FALSE },
- { L("!="), T_OP_NE },
- { L("!~"), T_OP_REG_NE },
- { L(":="), T_OP_SET },
- { L("<"), T_OP_LT },
- { L("<="), T_OP_LE },
- { L("="), T_OP_EQ },
- { L("=*"), T_OP_CMP_TRUE },
- { L("=="), T_OP_CMP_EQ },
- { L("=~"), T_OP_REG_EQ },
- { L(">"), T_OP_GT },
- { L(">="), T_OP_GE }
-};
-size_t cond_cmp_op_table_len = NUM_ELEMENTS(cond_cmp_op_table);
-
-/*
- * This file shouldn't use any functions from the server core.
- */
-ssize_t cond_print(fr_sbuff_t *out, fr_cond_t const *in)
-{
- fr_sbuff_t our_out = FR_SBUFF(out);
- fr_cond_t const *c = in;
-
- while (c) {
- if (c->negate) FR_SBUFF_IN_CHAR_RETURN(&our_out, '!');
-
- switch (c->type) {
- case COND_TYPE_TMPL:
- fr_assert(c->data.vpt != NULL);
- FR_SBUFF_RETURN(tmpl_print_quoted, &our_out, c->data.vpt, TMPL_ATTR_REF_PREFIX_YES);
- break;
-
- case COND_TYPE_RCODE:
- fr_assert(c->data.rcode != RLM_MODULE_NOT_SET);
- FR_SBUFF_IN_STRCPY_RETURN(&our_out, fr_table_str_by_value(rcode_table, c->data.rcode, ""));
- break;
-
- case COND_TYPE_MAP:
- FR_SBUFF_RETURN(map_print, &our_out, c->data.map);
- break;
-
- case COND_TYPE_CHILD:
- FR_SBUFF_IN_CHAR_RETURN(&our_out, '(');
- FR_SBUFF_RETURN(cond_print, &our_out, c->data.child);
- FR_SBUFF_IN_CHAR_RETURN(&our_out, ')');
- break;
-
- case COND_TYPE_AND:
- FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, " && ");
- break;
-
- case COND_TYPE_OR:
- FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, " || ");
- break;
-
- case COND_TYPE_TRUE:
- FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "true");
- break;
-
- case COND_TYPE_FALSE:
- FR_SBUFF_IN_STRCPY_LITERAL_RETURN(&our_out, "false");
- break;
-
- default:
- break;
- }
-
- c = c->next;
- }
-
- fr_sbuff_terminate(&our_out);
- FR_SBUFF_SET_RETURN(out, &our_out);
-}
-
-
-static int cond_cast_tmpl(tmpl_t *vpt, fr_type_t type, tmpl_t *other)
-{
- fr_dict_attr_t const *da;
-
- fr_assert(type != FR_TYPE_NULL);
- fr_assert(type < FR_TYPE_TLV);
-
- if (tmpl_is_attr(vpt)) {
- (void) tmpl_cast_set(vpt, type);
- return 0;
-
- } else if (!tmpl_is_data(vpt) && !tmpl_is_unresolved(vpt)) {
- /*
- * Nothing to do.
- */
- return 0;
-
- } else if (tmpl_value_type(vpt) == type) {
-#if 0
- /*
- * The parser will parse "256" as a 16-bit
- * integer. If that's being compared to an 8-bit
- * type, then fr_type_promote() will promote that
- * 8-bit integer to 16-bits.
- *
- * However... if the 8-bit data type comes from
- * an attribute, then we know at compile time
- * that the value won't fit. So we should issue
- * a compile-time error.
- *
- * As a result, we call the cast below, even if
- * the type of the value matches the type we're
- * going to cast.
- */
- if (tmpl_is_attr(other)) {
- // double check it?
- }
-#endif
-
-// (void) tmpl_cast_set(vpt, FR_TYPE_NULL);
- return 0;
- }
-
- /*
- * Allow enumerated values like "PPP" for
- * Framed-Protocol, which is an integer data type.
- */
- if (tmpl_is_attr(other)) {
- da = tmpl_attr_tail_da(other);
- } else {
- da = NULL;
- }
-
- fr_strerror_clear();
-
- if (tmpl_cast_in_place(vpt, type, da) < 0) {
- return -1;
- }
-
- /*
- * The result has to be data, AND of the correct type.
- * Which means we no longer need the cast.
- */
- fr_assert(tmpl_is_data(vpt));
- fr_assert_msg(fr_type_is_null(tmpl_rules_cast(vpt)) || (tmpl_rules_cast(vpt) == tmpl_value_type(vpt)),
- "Cast mismatch, target was %s, but output was %s",
- fr_type_to_str(tmpl_rules_cast(vpt)),
- fr_type_to_str(tmpl_value_type(vpt)));
- (void) tmpl_cast_set(vpt, FR_TYPE_NULL);
- return 0;
-}
-
-
-/** Promote the types in a FOO OP BAR comparison.
- *
- */
-int fr_cond_promote_types(fr_cond_t *c, fr_sbuff_t *in, fr_sbuff_marker_t *m_lhs, fr_sbuff_marker_t *m_rhs, bool simple_parse)
-{
- fr_type_t lhs_type, rhs_type;
- fr_type_t cast_type;
-
-#ifdef HAVE_REGEX
- /*
- * Regular expressions have their own casting rules.
- */
- if (tmpl_contains_regex(c->data.map->rhs)) {
- fr_assert((c->data.map->op == T_OP_REG_EQ) || (c->data.map->op == T_OP_REG_NE));
- fr_assert(fr_type_is_null(tmpl_rules_cast(c->data.map->rhs)));
-
- /*
- * Can't use casts with regular expressions, on
- * LHS or RHS. Instead, the regular expression
- * returns a true/false match.
- *
- * It's OK to have a redundant cast to string on
- * the LHS. We also allow a cast to octets, in
- * which case we do a binary regex comparison.
- *
- * @todo - ensure that the regex interpreter is
- * binary-safe!
- */
- cast_type = tmpl_rules_cast(c->data.map->lhs);
- if (cast_type != FR_TYPE_NULL) {
- if ((cast_type != FR_TYPE_STRING) &&
- (cast_type != FR_TYPE_OCTETS)) {
- fr_strerror_const("Casts cannot be used with regular expressions");
- if (in) fr_sbuff_set(in, m_lhs);
- return -1;
- }
- } else {
- cast_type = FR_TYPE_STRING;
- }
-
- /*
- * Ensure that the data is cast appropriately.
- */
- if (tmpl_is_unresolved(c->data.map->lhs) || tmpl_is_data(c->data.map->lhs)) {
- if (tmpl_cast_in_place(c->data.map->lhs, FR_TYPE_STRING, NULL) < 0) {
- if (in) fr_sbuff_set(in, m_lhs);
- return -1;
- }
-
- (void) tmpl_cast_set(c->data.map->lhs, FR_TYPE_NULL);
- }
-
- /*
- * Force a cast to 'string', so the conditional
- * evaluator has less work to do.
- */
- (void) tmpl_cast_set(c->data.map->lhs, cast_type);
- return 0;
- }
-#endif
-
- /*
- * Rewrite the map so that the attribute being evaluated
- * is on the LHS. This exchange makes cond_eval() easier
- * to implement, as it doesn't have to check both sides
- * for attributes.
- */
- if (tmpl_is_attr(c->data.map->rhs) &&
- !tmpl_contains_attr(c->data.map->lhs)) { /* also unresolved attributes! */
- tmpl_t *tmp;
- fr_sbuff_marker_t *m_tmp;
-
- tmp = c->data.map->rhs;
- c->data.map->rhs = c->data.map->lhs;
- c->data.map->lhs = tmp;
-
- m_tmp = m_rhs;
- m_rhs = m_lhs;
- m_lhs = m_tmp;
-
- switch (c->data.map->op) {
- case T_OP_CMP_EQ:
- case T_OP_NE:
- /* do nothing */
- break;
-
- case T_OP_LE:
- c->data.map->op = T_OP_GE;
- break;
-
- case T_OP_LT:
- c->data.map->op = T_OP_GT;
- break;
-
- case T_OP_GE:
- c->data.map->op = T_OP_LE;
- break;
-
- case T_OP_GT:
- c->data.map->op = T_OP_LT;
- break;
-
- default:
- fr_strerror_printf("%s: Internal sanity check failed", __FUNCTION__);
- return -1;
- }
- }
-
- /*
- * Figure out the type of the LHS.
- */
- if (tmpl_rules_cast(c->data.map->lhs) != FR_TYPE_NULL) {
- lhs_type = tmpl_rules_cast(c->data.map->lhs);
- /*
- * Two explicit casts MUST be the same, otherwise
- * it's an error.
- *
- * We only do type promotion when at least one
- * data type is implicitly specified.
- */
- if (tmpl_rules_cast(c->data.map->rhs) != FR_TYPE_NULL) {
- if (tmpl_rules_cast(c->data.map->rhs) != lhs_type) {
- fr_strerror_printf("Incompatible casts '%s' and '%s'",
- fr_type_to_str(tmpl_rules_cast(c->data.map->rhs)),
- fr_type_to_str(lhs_type));
- if (in) fr_sbuff_set(in, fr_sbuff_start(in));
- return -1;
- }
-
- return 0;
- }
-
- } else if (tmpl_is_data(c->data.map->lhs)) {
- /*
- * Choose the data type which was parsed.
- */
- lhs_type = tmpl_value_type(c->data.map->lhs);
-
- } else if (tmpl_is_attr(c->data.map->lhs)) {
- /*
- * Choose the attribute type which was parsed.
- */
- lhs_type = tmpl_attr_tail_da(c->data.map->lhs)->type;
-
- } else if (tmpl_is_exec(c->data.map->lhs)) {
- lhs_type = FR_TYPE_STRING;
-
- } else if (tmpl_is_xlat(c->data.map->lhs) && (c->data.map->lhs->quote != T_BARE_WORD)) {
- /*
- * bare xlats return a list of typed value-boxes.
- * We don't know those types until run-time.
- */
- lhs_type = FR_TYPE_STRING;
-
- } else {
-#ifdef HAVE_REGEX
- fr_assert(!tmpl_is_regex(c->data.map->lhs));
-#endif
-
- lhs_type = FR_TYPE_NULL;
- }
-
- /*
- * Figure out the type of the RHS.
- */
- if (tmpl_rules_cast(c->data.map->rhs) != FR_TYPE_NULL) {
- rhs_type = tmpl_rules_cast(c->data.map->rhs);
-
- } else if (tmpl_is_data(c->data.map->rhs)) {
- rhs_type = tmpl_value_type(c->data.map->rhs);
-
- /*
- * If we have ATTR op DATA, then ensure that the
- * data type we choose is the one from the
- * attribute, because that's what limits the
- * range of the RHS data.
- */
- if (fr_type_is_numeric(lhs_type) && tmpl_is_attr(c->data.map->lhs)) rhs_type = lhs_type;
-
- } else if (tmpl_is_attr(c->data.map->rhs)) {
- rhs_type = tmpl_attr_tail_da(c->data.map->rhs)->type;
-
- } else if (tmpl_is_exec(c->data.map->rhs)) {
- rhs_type = FR_TYPE_STRING;
-
- } else if (tmpl_is_xlat(c->data.map->rhs) && (c->data.map->rhs->quote != T_BARE_WORD)) {
- /*
- * bare xlats return a list of typed value-boxes.
- * We don't know those types until run-time.
- */
- rhs_type = FR_TYPE_STRING;
-
- } else {
- rhs_type = FR_TYPE_NULL;
-
- /*
- * Both sides are have unresolved issues. Leave
- * them alone...
- */
- if (fr_type_is_null(lhs_type)) {
- /*
- * If we still have unresolved data, then
- * ensure that they are converted to
- * strings.
- */
- if ((c->pass2_fixup == PASS2_FIXUP_NONE) &&
- tmpl_is_unresolved(c->data.map->lhs) && tmpl_is_unresolved(c->data.map->rhs)) {
- if (tmpl_cast_in_place(c->data.map->lhs, FR_TYPE_STRING, NULL) < 0) return -1;
- if (tmpl_cast_in_place(c->data.map->rhs, FR_TYPE_STRING, NULL) < 0) return -1;
- }
-
- return 0;
- }
- }
-
- /*
- * Both types are identical. Ensure that LHS / RHS are
- * cast as appropriate.
- */
- if (lhs_type == rhs_type) {
- cast_type = lhs_type;
- goto set_types;
- }
-
- /*
- * Only one side has a known data type. Cast the other
- * side to it.
- *
- * Note that we don't check the return code for
- * tmpl_cast_set(). If one side is an unresolved
- * attribute, then the cast will fail. Which is fine,
- * because we will just check it again after the pass2
- * fixups.
- */
- if (!fr_type_is_null(lhs_type) && fr_type_is_null(rhs_type)) {
- cast_type = lhs_type;
- goto set_types;
- }
-
- if (!fr_type_is_null(rhs_type) && fr_type_is_null(lhs_type)) {
- cast_type = rhs_type;
- goto set_types;
- }
-
- cast_type = fr_type_promote(lhs_type, rhs_type);
- if (!fr_cond_assert_msg(cast_type != FR_TYPE_NULL, "%s", fr_strerror())) return -1;
-
-set_types:
- /*
- * If the caller is doing comparisons with prefixes, then
- * update the cast to an IP prefix. But only if they're
- * not comparing IP addresses by value. <sigh> We should
- * really have separate "set membership" operators.
- */
- if (((cast_type == FR_TYPE_IPV4_ADDR) || (cast_type == FR_TYPE_IPV6_ADDR)) &&
- (lhs_type != rhs_type)) {
- switch (c->data.map->op) {
- default:
- break;
-
- case T_OP_LT:
- case T_OP_LE:
- case T_OP_GT:
- case T_OP_GE:
- if (cast_type == FR_TYPE_IPV4_ADDR) {
- cast_type = FR_TYPE_IPV4_PREFIX;
- } else {
- cast_type = FR_TYPE_IPV6_PREFIX;
- }
- break;
- }
- }
-
- /*
- * Skip casting.
- */
- if (simple_parse) return 0;
-
- /*
- * Cast both sides to the promoted type.
- */
- if (cond_cast_tmpl(c->data.map->lhs, cast_type, c->data.map->rhs) < 0) {
- if (in) fr_sbuff_set(in, m_lhs);
- return -1;
- }
-
- if (cond_cast_tmpl(c->data.map->rhs, cast_type, c->data.map->lhs) < 0) {
- if (in) fr_sbuff_set(in, m_rhs);
- return -1;
- }
-
- return 0;
-}
-
-/** Normalise one level of a condition
- *
- * This function is called after every individual condition is
- * tokenized. As a result, this function does not need to
- * recurse. Instead, it just looks at itself, and it's immediate
- * children for optimizations
- */
-static int cond_normalise(TALLOC_CTX *ctx, fr_token_t lhs_type, fr_cond_t **c_out)
-{
- fr_cond_t *c = *c_out;
- fr_cond_t *next;
-
- /*
- * Normalize the condition before returning.
- *
- * We convert maps to literals. Then literals to
- * true/false statements. Then true/false ||/&& followed
- * by other conditions to just conditions.
- *
- * Order is important. The more complex cases are
- * converted to simpler ones, from the most complex cases
- * to the simplest ones.
- */
-
- /*
- * Do some limited normalizations here.
- */
- if (c->type == COND_TYPE_CHILD) {
- fr_cond_t *child = c->data.child;
-
- /*
- * ((FOO)) --> (FOO)
- * !(!(FOO)) --> FOO
- */
- if (!c->next && !child->next) {
- if ((child->type == COND_TYPE_CHILD) ||
- (child->type == COND_TYPE_TRUE) ||
- (child->type == COND_TYPE_FALSE)) {
- (void) talloc_steal(ctx, child);
- child->parent = c->parent;
- child->negate = (c->negate != child->negate);
-
- talloc_free(c);
- c = child;
- }
- }
-
- /*
- * No further optimizations are possible, so we
- * just check for and/or short-circuit.
- */
- goto check_short_circuit;
- }
-
- /*
- * Normalise the equality checks.
- *
- * This doesn't make a lot of difference, but it does
- * help fix !* and =*, which are horrible hacks.
- */
- if (c->type == COND_TYPE_MAP) switch (c->data.map->op) {
- /*
- * !FOO !~ BAR --> FOO =~ BAR
- *
- * FOO !~ BAR --> !FOO =~ BAR
- */
- case T_OP_REG_NE:
- if (c->negate) {
- c->negate = false;
- c->data.map->op = T_OP_REG_EQ;
- } else {
- c->negate = true;
- c->data.map->op = T_OP_REG_EQ;
- }
- break;
-
- /*
- * !FOO != BAR --> FOO == BAR
- *
- * This next one catches "LDAP-Group != foo",
- * which doesn't work as-is, but this hack fixes
- * it.
- *
- * FOO != BAR --> !FOO == BAR
- */
- case T_OP_NE:
- if (c->negate) {
- c->negate = false;
- c->data.map->op = T_OP_CMP_EQ;
- } else {
- c->negate = true;
- c->data.map->op = T_OP_CMP_EQ;
- }
- break;
-
- /*
- * Don't do any other re-writing.
- */
- default:
- break;
- }
-
- /*
- * Do compile-time evaluation of literals. That way it
- * does not need to be done at run-time.
- */
- if (c->type == COND_TYPE_MAP) {
- /*
- * Both are data (IP address, integer, etc.)
- *
- * We can do the evaluation here, so that it
- * doesn't need to be done at run time
- */
- if (tmpl_is_data(c->data.map->lhs) &&
- tmpl_is_data(c->data.map->rhs)) {
- bool rcode;
-
- fr_assert(c->next == NULL);
-
- rcode = cond_eval(NULL, RLM_MODULE_NOOP, c);
- TALLOC_FREE(c->data.map);
-
- if (rcode) {
- c->type = COND_TYPE_TRUE;
- } else {
- c->type = COND_TYPE_FALSE;
- }
-
- c->negate = false;
- goto check_true; /* it's no longer a map */
- }
-
- c->async_required = tmpl_async_required(c->data.map->lhs) || tmpl_async_required(c->data.map->rhs);
- }
-
- /*
- * Existence checks. We short-circuit static strings,
- * too.
- *
- * FIXME: the data types should be in the template, too.
- * So that we know where a literal came from.
- *
- * "foo" is NOT the same as 'foo' or a bare foo.
- */
- if (c->type == COND_TYPE_TMPL) {
- switch (c->data.vpt->type) {
- case TMPL_TYPE_XLAT:
- case TMPL_TYPE_XLAT_UNRESOLVED:
- case TMPL_TYPE_ATTR:
- case TMPL_TYPE_ATTR_UNRESOLVED:
- case TMPL_TYPE_EXEC:
- break;
-
- /*
- * 'true' and 'false' are special strings
- * which mean themselves.
- *
- * For integers, 0 is false, all other
- * integers are true.
- *
- * For strings, '' and "" are false.
- * 'foo' and "foo" are true.
- *
- * The str2tmpl function takes care of
- * marking "%{foo}" as TMPL_TYPE_XLAT_UNRESOLVED, so
- * the strings here are fixed at compile
- * time.
- *
- * `exec` and "%{...}" are left alone.
- *
- * Bare words must be module return
- * codes.
- */
- case TMPL_TYPE_UNRESOLVED:
- check_bool:
- if (!*c->data.vpt->name) {
- c->type = COND_TYPE_FALSE;
- TALLOC_FREE(c->data.vpt);
-
- } else if ((lhs_type == T_SINGLE_QUOTED_STRING) ||
- (lhs_type == T_DOUBLE_QUOTED_STRING)) {
- c->type = COND_TYPE_TRUE;
- TALLOC_FREE(c->data.vpt);
-
- } else if (lhs_type == T_BARE_WORD) {
- int rcode;
- bool zeros = true;
- char const *q;
-
- for (q = c->data.vpt->name;
- *q != '\0';
- q++) {
- if (!isdigit((uint8_t) *q)) {
- break;
- }
- if (*q != '0') zeros = false;
- }
-
- /*
- * It's all digits, and therefore
- * 'false' if zero, and 'true' otherwise.
- */
- if (!*q) {
- if (zeros) {
- c->type = COND_TYPE_FALSE;
- } else {
- c->type = COND_TYPE_TRUE;
- }
- TALLOC_FREE(c->data.vpt);
- break;
- }
-
- /*
- * Allow &Foo-Bar where Foo-Bar is an attribute
- * defined by a module.
- */
- if (c->pass2_fixup == PASS2_FIXUP_ATTR) {
- break;
- }
-
- rcode = fr_table_value_by_str(allowed_return_codes, c->data.vpt->name, 0);
- if (!rcode) {
- fr_strerror_const("Expected a module return code");
- return -1;
- }
- }
-
- /*
- * Else lhs_type==T_INVALID, and this
- * node was made by promoting a child
- * which had already been normalized.
- */
- break;
-
- case TMPL_TYPE_DATA:
- if (lhs_type != T_BARE_WORD) goto check_bool;
-
- {
- fr_value_box_t res;
-
- if (fr_value_box_cast(NULL, &res, FR_TYPE_BOOL, NULL, tmpl_value(c->data.vpt)) < 0) return -1;
- c->type = res.vb_bool ? COND_TYPE_TRUE : COND_TYPE_FALSE;
- TALLOC_FREE(c->data.vpt);
- }
- break;
-
- default:
- fr_assert_fail("%s: Internal sanity check failed", __FUNCTION__);
- return -1;
- }
-
- if (c->type == COND_TYPE_TMPL) {
- c->async_required = tmpl_async_required(c->data.vpt);
- }
- }
-
- /*
- * !TRUE -> FALSE
- */
-check_true:
- if (c->type == COND_TYPE_TRUE) {
- if (c->negate) {
- c->negate = false;
- c->type = COND_TYPE_FALSE;
- }
- }
-
- /*
- * !FALSE -> TRUE
- */
- if (c->type == COND_TYPE_FALSE) {
- if (c->negate) {
- c->negate = false;
- c->type = COND_TYPE_TRUE;
- }
- }
-
- /*
- * We now do short-circuit evaluation of && and ||.
- */
-
-check_short_circuit:
- if (!c->next) goto done;
-
- /*
- * true && FOO --> FOO
- */
- if ((c->type == COND_TYPE_TRUE) &&
- (c->next->type == COND_TYPE_AND)) {
- goto hoist_grandchild;
- }
-
- /*
- * false && FOO --> false
- */
- if ((c->type == COND_TYPE_FALSE) &&
- (c->next->type == COND_TYPE_AND)) {
- goto drop_child;
- }
-
- /*
- * false || FOO --> FOO
- */
- if ((c->type == COND_TYPE_FALSE) &&
- (c->next->type == COND_TYPE_OR)) {
- hoist_grandchild:
- next = talloc_steal(ctx, c->next->next);
- talloc_free(c->next);
- talloc_free(c);
- c = next;
- goto done; /* we've already called normalise for FOO */
- }
-
- /*
- * true || FOO --> true
- */
- if ((c->type == COND_TYPE_TRUE) &&
- (c->next->type == COND_TYPE_OR)) {
-
- drop_child:
- TALLOC_FREE(c->next);
- goto done; /* we don't need to normalise a boolean */
- }
-
- /*
- * the short-circuit operators don't call normalise, so
- * we have to check for that, too.
- */
- next = c->next;
- if (!next->next) goto done;
-
- /*
- * FOO && true --> FOO
- */
- if ((next->type == COND_TYPE_AND) &&
- (next->next->type == COND_TYPE_TRUE)) {
- goto drop_next_child;
- }
-
- /*
- * FOO && false --> false
- */
- if ((next->type == COND_TYPE_AND) &&
- (next->next->type == COND_TYPE_FALSE)) {
- goto hoist_next_grandchild;
- }
-
- /*
- * FOO || false --> FOO
- */
- if ((next->type == COND_TYPE_OR) &&
- (next->next->type == COND_TYPE_FALSE)) {
- drop_next_child:
- TALLOC_FREE(c->next);
- goto done;
- }
-
- /*
- * FOO || true --> true
- */
- if ((next->type == COND_TYPE_OR) &&
- (next->next->type == COND_TYPE_TRUE)) {
- hoist_next_grandchild:
- next = talloc_steal(ctx, next->next);
- talloc_free(c->next);
- c = next;
- }
-
-done:
- *c_out = c;
- return 0;
-}
-
-static CC_HINT(nonnull) int cond_forbid_groups(tmpl_t *vpt, fr_sbuff_t *in, fr_sbuff_marker_t *m_lhs)
-{
- if (!tmpl_is_attr(vpt) || tmpl_attr_tail_is_unresolved(vpt)) return 0;
-
- if (tmpl_is_list(vpt)) {
- fr_strerror_const("Cannot use list references in condition");
- fr_sbuff_set(in, m_lhs);
- return -1;
- }
-
- switch (tmpl_attr_tail_da(vpt)->type) {
- case FR_TYPE_LEAF:
- break;
-
- default:
- fr_strerror_const("Nesting types such as groups or TLVs cannot "
- "be used in condition comparisons");
- fr_sbuff_set(in, m_lhs);
- return -1;
- }
-
- return 0;
-}
-
-static fr_slen_t cond_tokenize_operand(fr_cond_t *c, tmpl_t **out,
- fr_sbuff_marker_t *opd_start, fr_sbuff_t *in,
- tmpl_rules_t const *t_rules, bool simple_parse)
-{
- fr_sbuff_term_t const bareword_terminals =
- FR_SBUFF_TERMS(
- L(""), /* Hack for EOF */
- L("\t"),
- L("\n"),
- L(" "),
- L("!*"),
- L("!="),
- L("!~"),
- L("&&"), /* Logical operator */
- L(")"), /* Close condition/sub-condition */
- L("+="),
- L("-="),
- L(":="),
- L("<"),
- L("<="),
- L("=*"),
- L("=="),
- L("=~"),
- L(">"),
- L(">="),
- L("||"), /* Logical operator */
- );
-
- fr_sbuff_t our_in = FR_SBUFF(in);
- fr_sbuff_marker_t m;
- tmpl_t *vpt;
- fr_token_t type;
- fr_type_t cast = FR_TYPE_NULL;
- fr_sbuff_parse_rules_t tmp_p_rules;
- fr_sbuff_parse_rules_t const *p_rules;
- fr_slen_t slen;
- tmpl_rules_t our_t_rules = *t_rules;
-
- *out = NULL;
-
- /*
- * Parse (optional) cast
- */
- if (tmpl_cast_from_substr(&our_t_rules, &our_in) < 0) FR_SBUFF_ERROR_RETURN(&our_in);
-
- fr_sbuff_adv_past_whitespace(&our_in, SIZE_MAX, NULL);
- fr_sbuff_marker(&m, &our_in);
-
- /*
- * Check for quoting
- */
- fr_sbuff_out_by_longest_prefix(&slen, &type, cond_quote_table, &our_in, T_BARE_WORD);
- switch (type) {
- default:
- case T_BARE_WORD:
- tmp_p_rules = (fr_sbuff_parse_rules_t){ /* Stack allocated due to CL scope */
- .terminals = &bareword_terminals,
- .escapes = NULL
- };
- p_rules = &tmp_p_rules;
- break;
-
- case T_BACK_QUOTED_STRING:
- case T_DOUBLE_QUOTED_STRING:
- case T_SINGLE_QUOTED_STRING:
-#ifdef HAVE_REGEX
- case T_SOLIDUS_QUOTED_STRING:
-#endif
- p_rules = value_parse_rules_quoted[type];
- break;
-#ifndef HAVE_REGEX
- case T_SOLIDUS_QUOTED_STRING:
- fr_strerror_const("Compiled without support for regexes");
- fr_sbuff_set(&our_in, &m);
- fr_sbuff_advance(&our_in, 1);
- goto error;
-#endif
- }
-
- slen = tmpl_afrom_substr(c, &vpt, &our_in, type, p_rules, &our_t_rules);
- if (slen < 0) {
- error:
- talloc_free(vpt);
- FR_SBUFF_ERROR_RETURN(&our_in);
- }
-
- if ((type != T_BARE_WORD) && !fr_sbuff_next_if_char(&our_in, fr_token_quote[type])) { /* Quoting */
- fr_strerror_const("Unterminated string");
- fr_sbuff_set(&our_in, &m);
- goto error;
- }
-
-#ifdef HAVE_REGEX
- /*
- * Parse the regex flags
- *
- * The quote parsing we performed for the RHS
- * earlier means out buffer should be sitting
- * at the start of the flags.
- */
- if (type == T_SOLIDUS_QUOTED_STRING) {
- if (cast != FR_TYPE_NULL) {
- fr_strerror_const("Casts cannot be used with regular expressions");
- fr_sbuff_set(&our_in, &m);
- goto error;
- }
-
- if (!tmpl_contains_regex(vpt)) {
- fr_strerror_const("Expected regex");
- fr_sbuff_set(&our_in, &m);
- goto error;
- }
-
- if (tmpl_regex_flags_substr(vpt, &our_in, &bareword_terminals) < 0) goto error;
-
- /*
- * We've now got the expressions and
- * the flags. Try to compile the
- * regex.
- */
- if (!simple_parse && tmpl_is_regex_uncompiled(vpt)) {
- if (tmpl_regex_compile(vpt, true) < 0) {
- fr_sbuff_set(&our_in, &m); /* Reset to start of expression */
- goto error;
- }
- }
- }
-#endif
-
- /*
- * Sanity check for nested types
- */
- if (tmpl_is_attr(vpt) && (tmpl_attr_unknown_add(vpt) < 0)) {
- fr_strerror_printf("Failed defining attribute %s", tmpl_attr_tail_da(vpt)->name);
- fr_sbuff_set(&our_in, &m);
- goto error;
- }
-
- if (tmpl_is_unresolved(vpt) &&
- ((type == T_BACK_QUOTED_STRING) || (type == T_SINGLE_QUOTED_STRING) || (type == T_DOUBLE_QUOTED_STRING))) {
- if (tmpl_cast_in_place(vpt, FR_TYPE_STRING, NULL) < 0) {
- fr_sbuff_set(&our_in, &m);
- goto error;
- }
- }
-
- if (tmpl_is_attr_unresolved(vpt)) c->pass2_fixup = PASS2_FIXUP_ATTR;
-
- *out = vpt;
-
- fr_sbuff_marker(opd_start, in);
- fr_sbuff_set(opd_start, &m);
-
- FR_SBUFF_SET_RETURN(in, &our_in);
-}
-
-/** Tokenize a conditional check
- *
- * @param[in] ctx talloc ctx
- * @param[out] out pointer to the returned condition structure
- * @param[in] cs our configuration section
- * @param[in] in the start of the string to process. Should be "(..."
- * @param[in] brace look for a closing brace (how many deep we are)
- * @param[in] t_rules for attribute parsing
- * @param[in] simple_parse temporary hack
- * @return
- * - Length of the string skipped.
- * - < 0 (the offset to the offending error) on error.
- */
-static fr_slen_t cond_tokenize(TALLOC_CTX *ctx, fr_cond_t **out,
- CONF_SECTION *cs, fr_sbuff_t *in, int brace,
- tmpl_rules_t const *t_rules, bool simple_parse)
-{
- fr_sbuff_t our_in = FR_SBUFF(in);
- ssize_t slen;
- fr_cond_t *c;
-
- tmpl_t *lhs = NULL;
- fr_token_t op;
- fr_cond_type_t cond_op;
-
- fr_sbuff_marker_t m_lhs, m_lhs_cast, m_op, m_rhs, m_rhs_cast;
-
- MEM(c = talloc_zero(ctx, fr_cond_t));
-
- fr_sbuff_adv_past_whitespace(&our_in, SIZE_MAX, NULL);
- if (!fr_sbuff_extend(&our_in)) {
- fr_strerror_const("Empty condition is invalid");
- error:
- talloc_free(c);
- FR_SBUFF_ERROR_RETURN(&our_in);
- }
-
- /*
- * !COND
- */
- if (fr_sbuff_next_if_char(&our_in, '!')) {
- c->negate = true;
- fr_sbuff_adv_past_whitespace(&our_in, SIZE_MAX, NULL);
-
- /*
- * Just for stupidity
- */
- if (fr_sbuff_is_char(&our_in, '!')) {
- fr_strerror_const("Double negation is invalid");
- goto error;
- }
- }
-
- /*
- * (COND)
- */
- if (fr_sbuff_next_if_char(&our_in, '(')) {
- /*
- * We've already eaten one layer of
- * brackets. Go recurse to get more.
- */
- c->type = COND_TYPE_CHILD;
-
- /*
- * Children are allocated from the parent.
- */
- if (cond_tokenize(c, &c->data.child, cs, &our_in, brace + 1, t_rules, simple_parse) < 0) goto error;
-
- if (!c->data.child) {
- fr_strerror_const("Empty condition is invalid");
- goto error;
- }
-
- fr_sbuff_adv_past_whitespace(&our_in, SIZE_MAX, NULL);
- goto closing_brace;
- }
-
- /*
- * We didn't see anything special. The condition must be one of
- *
- * FOO
- * FOO OP BAR
- */
-
- /*
- * Grab the LHS
- */
- fr_sbuff_marker(&m_lhs_cast, &our_in);
-
- /*
- * Check to see if this is an rcode operand. These are
- * common enough and specific enough to conditions that
- * we handle them in the condition code specifically.
- *
- * Unary barewords can only be rcodes, so anything that's
- * not a rcode an rcode is an error.
- */
- {
- rlm_rcode_t rcode;
- size_t match_len;
-
- fr_sbuff_out_by_longest_prefix(&match_len, &rcode, rcode_table, &our_in, RLM_MODULE_NOT_SET);
- if (rcode != RLM_MODULE_NOT_SET) {
- c->type = COND_TYPE_RCODE;
- c->data.rcode = rcode;
-
- fr_sbuff_adv_past_whitespace(&our_in, SIZE_MAX, NULL);
- goto closing_brace;
- }
- }
-
- if (cond_tokenize_operand(c, &lhs, &m_lhs, &our_in, t_rules, simple_parse) < 0) goto error;
-
-#ifdef HAVE_REGEX
- /*
- * LHS can't have regex. We can't use regex as a unary
- * existence check.
- */
- if (tmpl_contains_regex(lhs)) {
- fr_strerror_const("Unexpected regular expression");
- fr_sbuff_set(&our_in, &m_lhs);
- goto error;
- }
-#endif
-
- /*
- * We may (or not) have an operator
- */
- fr_sbuff_adv_past_whitespace(&our_in, SIZE_MAX, NULL);
-
- /*
- * What's found directly after the LHS token determines
- * what type of expression this is.
- */
-
- /*
- * Closing curly brace - end of sub-expression
- */
- if (fr_sbuff_is_char(&our_in, ')')) {
- if (fr_sbuff_used_total(&our_in) == 0) {
- fr_strerror_const("Empty string is invalid");
- goto error;
- }
-
- /*
- * don't skip the brace. We'll look for it later.
- */
- goto unary;
-
- } else if (fr_sbuff_is_char(&our_in, '&') || fr_sbuff_is_char(&our_in, '|')) {
- /*
- * FOO && ...
- * FOO || ...
- *
- * end of sub-expression.
- */
- goto unary;
-
- } else if (!fr_sbuff_extend(&our_in)) {
- /*
- * FOO - Existence check at EOF
- */
- if (brace) {
- fr_strerror_const("Missing closing brace");
- goto error;
- }
-
- unary:
- if (tmpl_rules_cast(lhs) != FR_TYPE_NULL) {
- fr_strerror_const("Cannot do cast for existence check");
- fr_sbuff_set(&our_in, &m_lhs_cast);
- goto error;
- }
-
- c->type = COND_TYPE_TMPL;
- c->data.vpt = lhs;
-
- goto closing_brace;
- }
-
- /*
- * We now have LHS OP RHS. So the LHS can't be a group,
- * list, or nested thing.
- */
- if (cond_forbid_groups(lhs, &our_in, &m_lhs) < 0) goto error;
-
- /*
- * Check for any other operator
- */
- fr_sbuff_marker(&m_op, &our_in);
- fr_sbuff_out_by_longest_prefix(&slen, &op, cond_cmp_op_table, &our_in, 0);
- if (slen == 0) {
- fr_strerror_const("Invalid operator");
- goto error;
- }
- fr_sbuff_adv_past_whitespace(&our_in, SIZE_MAX, NULL);
-
- {
- map_t *map;
- tmpl_t *rhs;
-
- /*
- * The next thing should now be a comparison operator.
- */
- c->type = COND_TYPE_MAP;
-
- switch (op) {
- case T_OP_CMP_FALSE:
- case T_OP_CMP_TRUE:
- fr_strerror_printf("Invalid operator %s",
- fr_table_str_by_value(cond_cmp_op_table, op, "<INVALID>"));
- fr_sbuff_set(&our_in, &m_op);
- goto error;
-
- default:
- break;
- }
-
- if (!fr_sbuff_extend(&our_in)) {
- fr_strerror_const("Expected text after operator");
- goto error;
- }
-
- MEM(c->data.map = map = talloc_zero(c, map_t));
-
- /*
- * Grab the RHS
- */
- fr_sbuff_marker(&m_rhs_cast, &our_in);
- if (cond_tokenize_operand(c, &rhs, &m_rhs, &our_in, t_rules, simple_parse) < 0) goto error;
-
- /*
- * Groups can't be on the RHS of a comparison, either
- */
- if (cond_forbid_groups(rhs, &our_in, &m_rhs) < 0) goto error;
-
- *map = (map_t) {
- .ci = cf_section_to_item(cs),
- .lhs = lhs,
- .op = op,
- .rhs = rhs
- };
-
-#ifdef HAVE_REGEX
- /*
- * LHS can't have regex. We can't use regex as a unary
- * existence check.
- */
- if (tmpl_contains_regex(rhs) &&
- !((op == T_OP_REG_EQ) || (op == T_OP_REG_NE))) {
- fr_strerror_const("Unexpected regular expression");
- fr_sbuff_set(&our_in, &m_rhs);
- goto error;
- }
-
- /*
- * =~ and !~ MUST have regular expression on the
- * RHS.
- */
- if ((op == T_OP_REG_EQ) || (op == T_OP_REG_NE)) {
- if (!tmpl_contains_regex(rhs)) {
- fr_strerror_const("Expected regular expression");
- fr_sbuff_set(&our_in, &m_rhs);
- goto error;
- }
- }
-#endif
-
- fr_sbuff_adv_past_whitespace(&our_in, SIZE_MAX, NULL);
-
- /*
- * Promote the data types to the appropriate
- * values.
- */
- if (fr_cond_promote_types(c, &our_in, &m_lhs, &m_rhs, simple_parse) < 0) {
- goto error;
- }
- } /* parse OP RHS */
-
-closing_brace:
-
- /*
- * Recurse to parse the next condition.
- */
-
- /*
- * ...COND)
- */
- if (fr_sbuff_is_char(&our_in, ')')) {
- if (!brace) {
- fr_strerror_const("Unexpected closing brace");
- goto error;
- }
- fr_sbuff_advance(&our_in, 1);
- fr_sbuff_adv_past_whitespace(&our_in, SIZE_MAX, NULL);
- goto done;
- }
-
- /*
- * End of string is allowed, unless we're still looking
- * for closing braces.
- */
- if (!fr_sbuff_extend(&our_in)) {
- if (brace) {
- fr_strerror_const("Missing closing brace");
- goto error;
- }
- goto done;
- }
-
- /*
- * We've parsed all of the condition, stop.
- */
- if (brace == 0) {
- if (fr_sbuff_is_space(&our_in)) goto done;
-
- /*
- * Open a section, it's OK to be done.
- */
- if (fr_sbuff_is_char(&our_in, '{')) goto done;
- }
-
- fr_sbuff_out_by_longest_prefix(&slen, &cond_op, cond_logical_op_table,
- &our_in, COND_TYPE_INVALID);
- if (slen == 0) {
- /*
- * Peek ahead to give slightly better error messages.
- */
- if (fr_sbuff_is_char(&our_in, '=')) {
- fr_strerror_const("Invalid location for operator");
-
- } else if (fr_sbuff_is_char(&our_in, '!')) {
- fr_strerror_const("Invalid location for negation");
-
- } else {
- fr_strerror_const("Expected closing brace or logical operator");
- }
- goto error;
- }
-
- /*
- * We have a short-circuit condition, create it.
- */
- if (cond_op != COND_TYPE_INVALID) {
- fr_cond_t *child;
-
- /*
- * This node is talloc parented by the previous
- * condition.
- */
- MEM(child = talloc_zero(c, fr_cond_t));
- child->type = cond_op;
-
- /*
- * siblings are allocated from their older
- * siblings.
- */
- if (cond_tokenize(child, &child->next, cs, &our_in, brace, t_rules, simple_parse) < 0) goto error;
- c->next = child;
- goto done;
- }
-
- /*
- * May still be looking for a closing brace.
- *
- * siblings are allocated from their older
- * siblings.
- */
- if (cond_tokenize(c, &c->next, cs, &our_in, brace, t_rules, simple_parse) < 0) goto error;
-
-done:
- if (cond_normalise(ctx, lhs ? lhs->quote : T_INVALID, &c) < 0) {
- talloc_free(c);
- fr_sbuff_set_to_start(&our_in);
- FR_SBUFF_ERROR_RETURN(&our_in);
- }
-
- *out = c;
-
- FR_SBUFF_SET_RETURN(in, &our_in);
-}
-
-/*
- * Normalisation will restructure the conditional tree, including
- * removing and/or rearranging the parents. So we reparent
- * everything after the full normalization has run.
- */
-static void cond_reparent(fr_cond_t *c, fr_cond_t *parent)
-{
- while (c) {
- c->parent = parent;
-
- if (c->type == COND_TYPE_CHILD) cond_reparent(c->data.child, c);
-
- if (parent) parent->async_required |= c->async_required;
-
- c = c->next;
- }
-}
-
-/** Tokenize a conditional check
- *
- * @param[in] cs current CONF_SECTION and talloc ctx
- * @param[out] head the parsed condition structure
- * @param[in] rules for parsing operands.
- * @param[in] in the start of the string to process.
- * @param[in] simple_parse temporary hack
- * @return
- * - Length of the string skipped.
- * - < 0 (the offset to the offending error) on error.
- */
-ssize_t fr_cond_tokenize(CONF_SECTION *cs, fr_cond_t **head, tmpl_rules_t const *rules, fr_sbuff_t *in, bool simple_parse)
-{
- ssize_t slen;
- fr_sbuff_t our_in = FR_SBUFF(in);
-
- *head = NULL;
-
- slen = cond_tokenize(cs, head, cs, &our_in, 0, rules, simple_parse);
- if (slen <= 0) return slen;
-
- /*
- * Now that everything has been normalized, reparent the children.
- */
- if (*head) {
- fr_cond_t *c = *head;
-
- /*
- * (FOO) -> FOO
- * !(!(FOO)) -> FOO
- */
- if ((c->type == COND_TYPE_CHILD) && !c->next) {
- if (!c->negate || !c->data.child->next) {
- fr_cond_t *child = c->data.child;
- (void) talloc_steal(cs, child);
- child->parent = c->parent;
- child->negate = (c->negate != child->negate);
-
- talloc_free(c);
- *head = child;
- }
- }
-
- cond_reparent(*head, NULL);
- }
-
- FR_SBUFF_SET_RETURN(in, &our_in);
-}
-
-/** Initialise a cond iterator
- *
- * Will return the first leaf condition node.
- *
- * @param[out] iter to initialise.
- * @param[in] head the root of the condition structure.
- * @return The first leaf condition node.
- */
-fr_cond_t *fr_cond_iter_init(fr_cond_iter_t *iter, fr_cond_t *head)
-{
- fr_cond_t *c;
-
- for (c = head; c->type == COND_TYPE_CHILD; c = c->data.child); /* Deepest condition */
-
- return iter->cond = c;
-}
-
-/** Get the next leaf condition node
- *
- * @param[in] iter to iterate over.
- * @return The next leaf condition node.
- */
-fr_cond_t *fr_cond_iter_next(fr_cond_iter_t *iter)
-{
- fr_cond_t *c;
-
- /*
- * Walk up the tree, maybe...
- */
- for (c = iter->cond; c; c = c->parent) {
- if (!c->next) continue; /* Done with this level */
- for (c = c->next; c->type == COND_TYPE_CHILD; c = c->data.child); /* down we go... */
- break;
- }
-
- return iter->cond = c;
-}
-
-/** Update the condition with "is async required".
- *
- * This is done only by the compiler, in pass2, after is has resolved
- * all of the various TMPLs. Note that we MUST walk over the entire
- * condition, as tmpl_async_required() returns "true" for unresolved
- * TMPLs. Which means that if the TMPL is resolved to a synchronous
- * one, then the flag will be set. So we have to clear the flag, and
- * then set it again ourselves.
- */
-void fr_cond_async_update(fr_cond_t *c)
-{
- while (c) {
- c->async_required = false;
-
- switch (c->type) {
- case COND_TYPE_INVALID:
- case COND_TYPE_RCODE:
- case COND_TYPE_AND:
- case COND_TYPE_OR:
- case COND_TYPE_TRUE:
- case COND_TYPE_FALSE:
- break;
-
- case COND_TYPE_TMPL:
- c->async_required = tmpl_async_required(c->data.vpt);
- break;
-
- case COND_TYPE_MAP:
- c->async_required = tmpl_async_required(c->data.map->lhs) || tmpl_async_required(c->data.map->rhs);
- break;
-
- case COND_TYPE_CHILD:
- c = c->data.child;
- continue;
-
- }
-
- /*
- * Mark up the parent, too. If any of the
- * children are async, then the parent is async,
- * too.
- */
- if (c->parent) c->parent->async_required |= c->async_required;
-
- /*
- * Go up if there's no sibling, or to the next
- * sibling if it exists.
- */
- while (!c->next) {
- c = c->parent;
- if (!c) return;
- }
- c = c->next;
- }
-}