From: Alan T. DeKok Date: Mon, 28 Aug 2023 14:20:26 +0000 (-0400) Subject: remove old condition code X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0ed2c1fcacde10987cf0c3835c16c52bc8a9221b;p=thirdparty%2Ffreeradius-server.git remove old condition code --- diff --git a/src/bin/unit_test_attribute.c b/src/bin/unit_test_attribute.c index 51e5856ffb1..e896a03a20c 100644 --- a/src/bin/unit_test_attribute.c +++ b/src/bin/unit_test_attribute.c @@ -31,7 +31,6 @@ typedef struct request_s request_t; #include #include #include -#include #include #include #include diff --git a/src/lib/server/base.h b/src/lib/server/base.h index 75d325ba8aa..37e11fb50eb 100644 --- a/src/lib/server/base.h +++ b/src/lib/server/base.h @@ -33,7 +33,6 @@ RCSIDH(base_h, "$Id$") #include #include #include -#include #include #include #include @@ -52,7 +51,6 @@ RCSIDH(base_h, "$Id$") #include #include #include -#include #include #include #include diff --git a/src/lib/server/cf_file.c b/src/lib/server/cf_file.c index f995a5b8238..ea216618f51 100644 --- a/src/lib/server/cf_file.c +++ b/src/lib/server/cf_file.c @@ -32,7 +32,6 @@ RCSID("$Id$") #include #include -#include #include #include #include @@ -3043,20 +3042,18 @@ int cf_section_write(FILE *fp, CONF_SECTION *cs, int depth) * cf_data_find(cs, CF_DATA_TYPE_UNLANG, "if"); */ if (cs->name2) { - fr_cond_t *c; - fputs(" ", fp); +#if 0 c = cf_data_value(cf_data_find(cs, fr_cond_t, NULL)); if (c) { char buffer[1024]; cond_print(&FR_SBUFF_OUT(buffer, sizeof(buffer)), c); fprintf(fp, "(%s)", buffer); - - } else { /* dump the string as-is */ + } else +#endif cf_string_write(fp, cs->name2, strlen(cs->name2), cs->name2_quote); - } } fputs(" {\n", fp); diff --git a/src/lib/server/cond.h b/src/lib/server/cond.h deleted file mode 100644 index 1bdbe6eb475..00000000000 --- a/src/lib/server/cond.h +++ /dev/null @@ -1,115 +0,0 @@ -#pragma once -/* - * 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 lib/server/cond.h - * @brief Condition parser API - * - * @copyright 2013 Alan DeKok (aland@freeradius.org) - */ -RCSIDH(cond_h, "$Id$") - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -#ifndef RADIUSD_H -/* - * Also defined in radiusd.h for radius_evalute_cond() - */ -typedef struct fr_cond_s fr_cond_t; -#endif - -extern fr_table_num_sorted_t const cond_logical_op_table[]; -extern size_t cond_logical_op_table_len; - -extern fr_table_num_sorted_t const cond_cmp_op_table[]; -extern size_t cond_cmp_op_table_len; - -extern fr_table_num_sorted_t const cond_quote_table[]; -extern size_t cond_quote_table_len; - -typedef enum { - COND_TYPE_INVALID = 0, - COND_TYPE_TRUE, - COND_TYPE_FALSE, - COND_TYPE_TMPL, - COND_TYPE_RCODE, - COND_TYPE_MAP, - COND_TYPE_AND, - COND_TYPE_OR, - COND_TYPE_CHILD -} fr_cond_type_t; - -typedef enum { - PASS2_FIXUP_NONE = 0, - PASS2_FIXUP_ATTR, - PASS2_FIXUP_TYPE, - PASS2_PAIRCOMPARE -} fr_cond_pass2_t; - -/* - * Allow for the following structures: - * - * FOO no OP, RHS is NULL - * FOO OP BAR - * (COND) no LHS/RHS, child is COND, child OP is true - * (!(COND)) no LHS/RHS, child is COND, child OP is NOT - * (COND1 OP COND2) no LHS/RHS, next is COND2, next OP is OP - */ -struct fr_cond_s { - fr_cond_type_t type; - - union { - map_t *map; //!< Binary expression. - tmpl_t *vpt; //!< Unary expression. - fr_cond_t *child; //!< Nested condition. - rlm_rcode_t rcode; //!< Rcode check. We handle this outside of - ///< tmpls as it doesn't apply anywhere else. - } data; - - bool async_required; //!< is async required? - bool negate; //!< Invert the result of the expression. - fr_cond_pass2_t pass2_fixup; - - fr_cond_t *parent; - fr_cond_t *next; -}; - -typedef struct { - fr_cond_t *cond; -} fr_cond_iter_t; - -ssize_t fr_cond_tokenize(CONF_SECTION *cs, fr_cond_t **head, tmpl_rules_t const *rules, fr_sbuff_t *in, bool flag) CC_HINT(nonnull(1,2,4)); - -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 flag) CC_HINT(nonnull(1)); - -ssize_t cond_print(fr_sbuff_t *out, fr_cond_t const *c); - -fr_cond_t *fr_cond_iter_init(fr_cond_iter_t *iter, fr_cond_t *head); -fr_cond_t *fr_cond_iter_next(fr_cond_iter_t *iter); - -void fr_cond_async_update(fr_cond_t *cond); - -#ifdef __cplusplus -} -#endif diff --git a/src/lib/server/cond_eval.c b/src/lib/server/cond_eval.c deleted file mode 100644 index e6464e5f809..00000000000 --- a/src/lib/server/cond_eval.c +++ /dev/null @@ -1,1120 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include - -#include - -#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, ""), cond); - INFO("\tnegate : %s", c->negate ? "true" : "false"); - INFO("\tfixup : %s", fr_table_str_by_value(cond_pass2_table, c->pass2_fixup, "")); - - 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); -} diff --git a/src/lib/server/cond_eval.h b/src/lib/server/cond_eval.h deleted file mode 100644 index f754249e13c..00000000000 --- a/src/lib/server/cond_eval.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once -/* - * 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 lib/server/cond_eval.h - * @brief Structures and prototypes for the condition evaluation code - * - * @copyright 2007 The FreeRADIUS server project - * @copyright 2007 Alan DeKok (aland@deployingradius.com) - */ -RCSIDH(cond_eval_h, "$Id$") - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* evaluate.c */ -typedef struct fr_cond_s fr_cond_t; - -void cond_debug(fr_cond_t const *cond); - -bool cond_eval(request_t *request, rlm_rcode_t modreturn, fr_cond_t const *c); - -typedef struct { - TALLOC_CTX *ctx; //!< for intermediate value boxes - fr_cond_t const *c; //!< the current condition being evaluated - rlm_rcode_t modreturn; //!< the previous module return code; - - tmpl_t const *tmpl_lhs; //!< the LHS async template to evaluate - tmpl_t const *tmpl_rhs; //!< the RHS async template to evaluate - - fr_value_box_t *vb_lhs; //!< the output of the LHS async evaluation - fr_value_box_t *vb_rhs; //!< the output of the RHS async evaluation - - enum { - COND_EVAL_STATE_INVALID = 0, - COND_EVAL_STATE_INIT, - COND_EVAL_STATE_EXPAND, - COND_EVAL_STATE_EVAL, - COND_EVAL_STATE_DONE, - } state; - - bool result; //!< the final conditional result -} fr_cond_async_t; - -int cond_eval_async(request_t *request, fr_cond_async_t *a); - -bool fr_cond_eval_map(request_t *request, map_t const *map); - -#ifdef __cplusplus -} -#endif diff --git a/src/lib/server/cond_tokenize.c b/src/lib/server/cond_tokenize.c deleted file mode 100644 index 9ba70e685e4..00000000000 --- a/src/lib/server/cond_tokenize.c +++ /dev/null @@ -1,1599 +0,0 @@ -/* - * 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 -#include -#include -#include -#include - -#include - -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. 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, "")); - 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; - } -} diff --git a/src/lib/server/connection.c b/src/lib/server/connection.c index 19b418ba100..8ddc32eae3c 100644 --- a/src/lib/server/connection.c +++ b/src/lib/server/connection.c @@ -28,7 +28,6 @@ typedef struct fr_connection_s fr_connection_t; #define _CONNECTION_PRIVATE 1 #include -#include #include #include diff --git a/src/lib/server/libfreeradius-server.mk b/src/lib/server/libfreeradius-server.mk index 0e948d46eb2..474275033ca 100644 --- a/src/lib/server/libfreeradius-server.mk +++ b/src/lib/server/libfreeradius-server.mk @@ -8,8 +8,6 @@ SOURCES := \ cf_util.c \ client.c \ command.c \ - cond_eval.c \ - cond_tokenize.c \ connection.c \ dependency.c \ dl_module.c \ @@ -67,7 +65,7 @@ LOG_ID_LIB := 1 # different pieces of this library $(call DEFINE_LOG_ID_SECTION,config, 1,cf_file.c cf_parse.c cf_util.c) -$(call DEFINE_LOG_ID_SECTION,conditions,2,conf_eval.c cond_tokenize.c) +# 2 was the old conditions $(call DEFINE_LOG_ID_SECTION,exec, 3,exec.c exec_legacy.c) $(call DEFINE_LOG_ID_SECTION,modules, 4,dl_module.c module.c module_rlm.c method.c) $(call DEFINE_LOG_ID_SECTION,map, 5,map.c map_proc.c map_async.c) diff --git a/src/lib/server/main_config.c b/src/lib/server/main_config.c index 77bc604301a..c0961e73f46 100644 --- a/src/lib/server/main_config.c +++ b/src/lib/server/main_config.c @@ -29,7 +29,6 @@ RCSID("$Id$") #include #include #include -#include #include #include #include diff --git a/src/lib/server/map.c b/src/lib/server/map.c index dd05cc21d96..e741274a58e 100644 --- a/src/lib/server/map.c +++ b/src/lib/server/map.c @@ -28,7 +28,6 @@ RCSID("$Id$") -#include #include #include #include @@ -46,6 +45,15 @@ RCSID("$Id$") #include +static 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 } +}; +static size_t cond_quote_table_len = NUM_ELEMENTS(cond_quote_table); + + #ifdef DEBUG_MAP static void map_dump(request_t *request, map_t const *map) { diff --git a/src/lib/server/module.c b/src/lib/server/module.c index 341e33cc4c1..47079acd7aa 100644 --- a/src/lib/server/module.c +++ b/src/lib/server/module.c @@ -30,7 +30,6 @@ RCSID("$Id$") #include #include -#include #include #include #include diff --git a/src/lib/server/virtual_servers.c b/src/lib/server/virtual_servers.c index 875d53d7f3b..f2b1f8f5896 100644 --- a/src/lib/server/virtual_servers.c +++ b/src/lib/server/virtual_servers.c @@ -30,7 +30,6 @@ RCSID("$Id$") #include #include #include -#include #include #include #include diff --git a/src/lib/unlang/compile.c b/src/lib/unlang/compile.c index 6922d302391..7c4b1fa8322 100644 --- a/src/lib/unlang/compile.c +++ b/src/lib/unlang/compile.c @@ -3452,7 +3452,7 @@ static unlang_t *compile_if(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF .len = sizeof(unlang_cond_t), .type_name = "unlang_cond_t", .pool_headers = 1 + 1 + (TMPL_POOL_DEF_HEADERS * 2), - .pool_len = sizeof(fr_cond_t) + sizeof(map_t) + (TMPL_POOL_DEF_LEN * 2) + .pool_len = sizeof(map_t) + (TMPL_POOL_DEF_LEN * 2) }; return compile_if_subsection(parent, unlang_ctx, cs, &if_ext); @@ -3465,7 +3465,7 @@ static unlang_t *compile_elsif(unlang_t *parent, unlang_compile_t *unlang_ctx, C .len = sizeof(unlang_cond_t), .type_name = "unlang_cond_t", .pool_headers = 1 + 1 + (TMPL_POOL_DEF_HEADERS * 2), - .pool_len = sizeof(fr_cond_t) + sizeof(map_t) + (TMPL_POOL_DEF_LEN * 2) + .pool_len = sizeof(map_t) + (TMPL_POOL_DEF_LEN * 2) }; return compile_if_subsection(parent, unlang_ctx, cs, &elsif_ext); diff --git a/src/lib/unlang/module.c b/src/lib/unlang/module.c index 721bae35318..0f75ed85228 100644 --- a/src/lib/unlang/module.c +++ b/src/lib/unlang/module.c @@ -26,7 +26,6 @@ RCSID("$Id$") -#include #include #include #include diff --git a/src/lib/unlang/unlang_priv.h b/src/lib/unlang/unlang_priv.h index d4c2f4727c6..61a8ea98a5e 100644 --- a/src/lib/unlang/unlang_priv.h +++ b/src/lib/unlang/unlang_priv.h @@ -26,7 +26,6 @@ * @copyright 2016-2019 The FreeRADIUS server project */ #include /* Need CONF_* definitions */ -#include #include #include #include diff --git a/src/tests/unit/condition/base.txt b/src/tests/unit/condition/base.txt index a41f4e8e929..f12f9189d26 100644 --- a/src/tests/unit/condition/base.txt +++ b/src/tests/unit/condition/base.txt @@ -649,7 +649,7 @@ match &reply. # and empty strings # # @todo - disabled due to moving cf_expand_variables() from -# cond_tokenize() to cf_file.c. The new xlat expression parser +# the condition parser to cf_file.c. The new xlat expression parser # will *not* optimize this at parse time, but *will* optimize # this at purification time. #