#include "try_priv.h"
#include "mod_action.h"
-#define UNLANG_IGNORE ((unlang_t *) -1)
-
static unsigned int unlang_number = 1;
/*
};
size_t mod_action_table_len = NUM_ELEMENTS(mod_action_table);
-typedef struct {
- virtual_server_t const *vs; //!< Virtual server we're compiling in the context of.
- ///< This shouldn't change during the compilation of
- ///< a single unlang section.
- char const *section_name1;
- char const *section_name2;
- unlang_mod_actions_t actions;
- tmpl_rules_t const *rules;
-} unlang_compile_t;
-
-/*
- * When we switch to a new unlang ctx, we use the new component
- * name and number, but we use the CURRENT actions.
- */
-static inline CC_HINT(always_inline)
-void compile_copy_context(unlang_compile_t *dst, unlang_compile_t const *src)
-{
- int i;
-
- *dst = *src;
-
- /*
- * Ensure that none of the actions are RETRY.
- */
- for (i = 0; i < RLM_MODULE_NUMCODES; i++) {
- if (dst->actions.actions[i] == MOD_ACTION_RETRY) dst->actions.actions[i] = MOD_PRIORITY_MIN;
- }
- memset(&dst->actions.retry, 0, sizeof(dst->actions.retry)); \
-}
-
-#define UPDATE_CTX2 compile_copy_context(&unlang_ctx2, unlang_ctx)
+#define UPDATE_CTX2 unlang_compile_ctx_copy(&unlang_ctx2, unlang_ctx)
-static unlang_t *compile_empty(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs, unlang_type_t type);
-
static char const unlang_spaces[] = " ";
-static inline CC_HINT(always_inline) int unlang_attr_rules_verify(tmpl_attr_rules_t const *rules)
-{
- if (!fr_cond_assert_msg(rules->dict_def, "No protocol dictionary set")) return -1;
- if (!fr_cond_assert_msg(rules->dict_def != fr_dict_internal(), "rules->attr.dict_def must not be the internal dictionary")) return -1;
- if (!fr_cond_assert_msg(!rules->allow_foreign, "rules->attr.allow_foreign must be false")) return -1;
-
- return 0;
-}
-
-static inline CC_HINT(always_inline) int unlang_rules_verify(tmpl_rules_t const *rules)
-{
- if (!fr_cond_assert_msg(!rules->at_runtime, "rules->at_runtime must be false")) return -1;
- return unlang_attr_rules_verify(&rules->attr);
-}
-
-#if 0
-#define ATTR_RULES_VERIFY(_rules) if (unlang_attr_rules_verify(_rules) < 0) return NULL;
-#endif
-#define RULES_VERIFY(_rules) do { if (unlang_rules_verify(_rules) < 0) return NULL; } while (0)
-
-static bool pass2_fixup_tmpl(UNUSED TALLOC_CTX *ctx, tmpl_t **vpt_p, CONF_ITEM const *ci, fr_dict_t const *dict)
+bool pass2_fixup_tmpl(UNUSED TALLOC_CTX *ctx, tmpl_t **vpt_p, CONF_ITEM const *ci, fr_dict_t const *dict)
{
tmpl_t *vpt = *vpt_p;
* This function resolves most things. Most notable it CAN leave the
* RHS unresolved, for use in `map` sections.
*/
-static bool pass2_fixup_map(map_t *map, tmpl_rules_t const *rules, fr_dict_attr_t const *parent)
+bool pass2_fixup_map(map_t *map, tmpl_rules_t const *rules, fr_dict_attr_t const *parent)
{
RULES_VERIFY(rules);
/*
* Do all kinds of fixups and checks for update sections.
*/
-static bool pass2_fixup_update(unlang_group_t *g, tmpl_rules_t const *rules)
+bool pass2_fixup_update(unlang_group_t *g, tmpl_rules_t const *rules)
{
unlang_map_t *gext = unlang_group_to_map(g);
map_t *map = NULL;
/*
* Compile the RHS of map sections to xlat_exp_t
*/
-static bool pass2_fixup_map_rhs(unlang_group_t *g, tmpl_rules_t const *rules)
+bool pass2_fixup_map_rhs(unlang_group_t *g, tmpl_rules_t const *rules)
{
unlang_map_t *gext = unlang_group_to_map(g);
map_t *map = NULL;
}
}
-/** Validate and fixup a map that's part of an map section.
- *
- * @param map to validate.
- * @param ctx data to pass to fixup function (currently unused).
- * @return 0 if valid else -1.
- */
-static int unlang_fixup_map(map_t *map, UNUSED void *ctx)
-{
- switch (map->lhs->type) {
- case TMPL_TYPE_ATTR:
- case TMPL_TYPE_XLAT_UNRESOLVED:
- case TMPL_TYPE_XLAT:
- break;
-
- default:
- cf_log_err(map->ci, "Left side of map must be an attribute "
- "or an xlat (that expands to an attribute), not a %s",
- tmpl_type_to_str(map->lhs->type));
- return -1;
- }
-
- switch (map->rhs->type) {
- case TMPL_TYPE_XLAT_UNRESOLVED:
- case TMPL_TYPE_DATA_UNRESOLVED:
- case TMPL_TYPE_DATA:
- case TMPL_TYPE_XLAT:
- case TMPL_TYPE_ATTR:
- case TMPL_TYPE_EXEC:
- break;
-
- default:
- cf_log_err(map->ci, "Right side of map must be an attribute, literal, xlat or exec, got type %s",
- tmpl_type_to_str(map->rhs->type));
- return -1;
- }
-
- if (!fr_assignment_op[map->op] && !fr_comparison_op[map->op]) {
- cf_log_err(map->ci, "Invalid operator \"%s\" in map section. "
- "Only assignment or filter operators are allowed",
- fr_table_str_by_value(fr_tokens_table, map->op, "<INVALID>"));
- return -1;
- }
-
- return 0;
-}
-
/** Validate and fixup a map that's part of an update section.
*
}
-static unlang_group_t *group_allocate(unlang_t *parent, CONF_SECTION *cs, unlang_type_t type)
+unlang_group_t *unlang_group_allocate(unlang_t *parent, CONF_SECTION *cs, unlang_type_t type)
{
unlang_group_t *g;
unlang_t *c;
return g;
}
-static void compile_action_defaults(unlang_t *c, unlang_compile_t *unlang_ctx)
+void unlang_compile_action_defaults(unlang_t *c, unlang_compile_ctx_t *unlang_ctx)
{
int i;
}
}
-static int compile_map_name(unlang_group_t *g)
-{
- unlang_map_t *gext = unlang_group_to_map(g);
+#define T(_x) [T_OP_ ## _x] = true
- /*
- * map <module-name> <arg>
- */
- if (gext->vpt) {
- char quote;
- size_t quoted_len;
- char *quoted_str;
-
- switch (cf_section_argv_quote(g->cs, 0)) {
- case T_DOUBLE_QUOTED_STRING:
- quote = '"';
- break;
+static const bool edit_list_sub_op[T_TOKEN_LAST] = {
+ T(NE),
+ T(GE),
+ T(GT),
+ T(LE),
+ T(LT),
+ T(CMP_EQ),
+};
- case T_SINGLE_QUOTED_STRING:
- quote = '\'';
- break;
+/** Validate and fixup a map that's part of an edit section.
+ *
+ * @param map to validate.
+ * @param ctx data to pass to fixup function (currently unused).
+ * @return 0 if valid else -1.
+ *
+ * @todo - this is only called for CONF_PAIR maps, not for
+ * CONF_SECTION. So when we parse nested maps, there's no validation
+ * done of the CONF_SECTION. In order to fix this, we need to have
+ * map_afrom_cs() call the validation function for the CONF_SECTION
+ * *before* recursing.
+ */
+static int unlang_fixup_edit(map_t *map, void *ctx)
+{
+ CONF_PAIR *cp = cf_item_to_pair(map->ci);
+ fr_dict_attr_t const *da;
+ fr_dict_attr_t const *parent = NULL;
+ map_t *parent_map = ctx;
- case T_BACK_QUOTED_STRING:
- quote = '`';
- break;
+ fr_assert(parent_map);
+#ifdef STATIC_ANALYZER
+ if (!parent_map) return -1;
+#endif
- default:
- quote = '\0';
- break;
+ fr_assert(tmpl_is_attr(parent_map->lhs));
+
+ if (parent_map && (parent_map->op == T_OP_SUB_EQ)) {
+ if (!edit_list_sub_op[map->op]) {
+ cf_log_err(cp, "Invalid operator '%s' for right-hand side list. It must be a comparison operator", fr_tokens[map->op]);
+ return -1;
}
- quoted_len = fr_snprint_len(gext->vpt->name, gext->vpt->len, quote);
- quoted_str = talloc_array(g, char, quoted_len);
- fr_snprint(quoted_str, quoted_len, gext->vpt->name, gext->vpt->len, quote);
+ } else if (map->op != T_OP_EQ) {
+ cf_log_err(cp, "Invalid operator '%s' for right-hand side list. It must be '='", fr_tokens[map->op]);
+ return -1;
+ }
- g->self.name = talloc_typed_asprintf(g, "map %s %s", cf_section_name2(g->cs), quoted_str);
- g->self.debug_name = g->self.name;
- talloc_free(quoted_str);
+ /*
+ * map_afrom_cs() will build its tree recursively, and call us for each child map.
+ */
+ if (map->parent && (map->parent != parent_map)) parent_map = map->parent;
- return 0;
- }
+ parent = tmpl_attr_tail_da(parent_map->lhs);
- g->self.name = talloc_typed_asprintf(g, "map %s", cf_section_name2(g->cs));
- g->self.debug_name = g->self.name;
+ switch (map->lhs->type) {
+ case TMPL_TYPE_ATTR:
+ da = tmpl_attr_tail_da(map->lhs);
+ if (!da->flags.internal && parent && (parent->type != FR_TYPE_GROUP) &&
+ (da->parent != parent)) {
+ /* FIXME - Broken check, doesn't work for key attributes */
+ cf_log_err(cp, "Invalid location for %s - it is not a child of %s",
+ da->name, parent->name);
+ return 0;
+ }
+ break;
- return 0;
-}
+ case TMPL_TYPE_XLAT_UNRESOLVED:
+ case TMPL_TYPE_XLAT:
+ break;
-static unlang_t *compile_map(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- int rcode;
+ default:
+ cf_log_err(map->ci, "Left side of map must be an attribute "
+ "or an xlat (that expands to an attribute), not a %s",
+ tmpl_type_to_str(map->lhs->type));
+ return -1;
+ }
- unlang_group_t *g;
- unlang_map_t *gext;
+ fr_assert(map->rhs);
- unlang_t *c;
- CONF_SECTION *modules;
- char const *tmpl_str;
+ switch (map->rhs->type) {
+ case TMPL_TYPE_DATA_UNRESOLVED:
+ case TMPL_TYPE_XLAT_UNRESOLVED:
+ case TMPL_TYPE_XLAT:
+ case TMPL_TYPE_DATA:
+ case TMPL_TYPE_ATTR:
+ case TMPL_TYPE_EXEC:
+ break;
- tmpl_t *vpt = NULL;
+ default:
+ cf_log_err(map->ci, "Right side of map must be an attribute, literal, xlat or exec, got type %s",
+ tmpl_type_to_str(map->rhs->type));
+ return -1;
+ }
- map_proc_t *proc;
- map_proc_inst_t *proc_inst;
+ return 0;
+}
- char const *name2 = cf_section_name2(cs);
+/** Compile one edit section.
+ */
+static unlang_t *compile_edit_section(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_SECTION *cs)
+{
+ unlang_edit_t *edit;
+ unlang_t *c, *out = UNLANG_IGNORE;
+ map_t *map;
+ char const *name;
+ fr_token_t op;
+ ssize_t slen;
+ fr_dict_attr_t const *parent_da;
+ int num;
tmpl_rules_t t_rules;
- /*
- * The RHS is NOT resolved in the context of the LHS.
- */
- t_rules = *(unlang_ctx->rules);
- t_rules.attr.disallow_rhs_resolve = true;
- RULES_VERIFY(&t_rules);
-
- modules = cf_section_find(cf_root(cs), "modules", NULL);
- if (!modules) {
- cf_log_err(cs, "'map' sections require a 'modules' section");
+ name = cf_section_name2(cs);
+ if (name) {
+ cf_log_err(cs, "Unexpected name2 '%s' for editing list %s ", name, cf_section_name1(cs));
return NULL;
}
- proc = map_proc_find(name2);
- if (!proc) {
- cf_log_err(cs, "Failed to find map processor '%s'", name2);
+ op = cf_section_name2_quote(cs);
+ if ((op == T_INVALID) || !fr_list_assignment_op[op]) {
+ cf_log_err(cs, "Invalid operator '%s' for editing list %s.", fr_tokens[op], cf_section_name1(cs));
return NULL;
}
- t_rules.literals_safe_for = map_proc_literals_safe_for(proc);
-
- g = group_allocate(parent, cs, UNLANG_TYPE_MAP);
- if (!g) return NULL;
- gext = unlang_group_to_map(g);
+ if ((op == T_OP_CMP_TRUE) || (op == T_OP_CMP_FALSE)) {
+ cf_log_err(cs, "Invalid operator \"%s\".",
+ fr_table_str_by_value(fr_tokens_table, op, "<INVALID>"));
+ return NULL;
+ }
/*
- * If there's a third string, it's the map src.
- *
- * Convert it into a template.
+ * We allow unknown attributes here.
*/
- tmpl_str = cf_section_argv(cs, 0); /* AFTER name1, name2 */
- if (tmpl_str) {
- fr_token_t type;
+ t_rules = *(unlang_ctx->rules);
+ t_rules.attr.allow_unknown = true;
+ RULES_VERIFY(&t_rules);
- type = cf_section_argv_quote(cs, 0);
+ edit = talloc_zero(parent, unlang_edit_t);
+ if (!edit) return NULL;
- /*
- * Try to parse the template.
- */
- (void) tmpl_afrom_substr(gext, &vpt,
- &FR_SBUFF_IN(tmpl_str, talloc_array_length(tmpl_str) - 1),
- type,
- NULL,
- &t_rules);
- if (!vpt) {
- cf_log_perr(cs, "Failed parsing map");
- error:
- talloc_free(g);
- return NULL;
- }
+ c = out = unlang_edit_to_generic(edit);
+ c->parent = parent;
+ c->next = NULL;
+ c->name = cf_section_name1(cs);
+ c->debug_name = c->name;
+ c->type = UNLANG_TYPE_EDIT;
+ c->ci = CF_TO_ITEM(cs);
- /*
- * Limit the allowed template types.
- */
- switch (vpt->type) {
- case TMPL_TYPE_DATA_UNRESOLVED:
- case TMPL_TYPE_ATTR:
- case TMPL_TYPE_ATTR_UNRESOLVED:
- case TMPL_TYPE_XLAT:
- case TMPL_TYPE_XLAT_UNRESOLVED:
- case TMPL_TYPE_EXEC:
- case TMPL_TYPE_EXEC_UNRESOLVED:
- case TMPL_TYPE_DATA:
- break;
+ map_list_init(&edit->maps);
- default:
- talloc_free(vpt);
- cf_log_err(cs, "Invalid third argument for map");
- return NULL;
- }
- }
+ unlang_compile_action_defaults(c, unlang_ctx);
/*
- * This looks at cs->name2 to determine which list to update
+ * Allocate the map and initialize it.
*/
- map_list_init(&gext->map);
- rcode = map_afrom_cs(gext, &gext->map, cs, unlang_ctx->rules, &t_rules, unlang_fixup_map, NULL, 256);
- if (rcode < 0) return NULL; /* message already printed */
- if (map_list_empty(&gext->map)) {
- cf_log_err(cs, "'map' sections cannot be empty");
- goto error;
- }
+ MEM(map = talloc_zero(parent, map_t));
+ map->op = op;
+ map->ci = cf_section_to_item(cs);
+ map_list_init(&map->child);
+
+ name = cf_section_name1(cs);
+ slen = tmpl_afrom_attr_str(map, NULL, &map->lhs, name, &t_rules);
+ if (slen <= 0) {
+ cf_log_err(cs, "Failed parsing list reference %s - %s", name, fr_strerror());
+ fail:
+ talloc_free(edit);
+ return NULL;
+ }
/*
- * Call the map's instantiation function to validate
- * the map and perform any caching required.
+ * Can't assign to [*] or [#]
*/
- proc_inst = map_proc_instantiate(gext, proc, cs, vpt, &gext->map);
- if (!proc_inst) {
- cf_log_err(cs, "Failed instantiating map function '%s'", name2);
- goto error;
+ num = tmpl_attr_tail_num(map->lhs);
+ if ((num == NUM_ALL) || (num == NUM_COUNT)) {
+ cf_log_err(cs, "Invalid array reference in %s", name);
+ goto fail;
}
- c = unlang_group_to_generic(g);
-
- gext->vpt = vpt;
- gext->proc_inst = proc_inst;
-
- compile_map_name(g);
/*
- * Cache the module in the unlang_group_t struct.
- *
- * Ensure that the module has a "map" entry in its module
- * header? Or ensure that the map is registered in the
- * "bootstrap" phase, so that it's always available here.
+ * If the DA isn't structural, then it can't have children.
*/
- if (!pass2_fixup_map_rhs(g, unlang_ctx->rules)) goto error;
-
- compile_action_defaults(c, unlang_ctx);
-
- return c;
-}
-
-static int edit_section_alloc(CONF_SECTION *parent, CONF_SECTION **child, char const *name1, fr_token_t op)
-{
- CONF_SECTION *cs;
-
- cs = cf_section_alloc(parent, parent, name1, NULL);
- if (!cs) return -1;
+ parent_da = tmpl_attr_tail_da(map->lhs);
+ if (fr_type_is_structural(parent_da->type)) {
+ map_t *child;
- cf_section_add_name2_quote(cs, op);
-
- if (child) *child = cs;
-
- return 0;
-}
-
-static int edit_pair_alloc(CONF_SECTION *cs, CONF_PAIR *original, char const *attr, fr_token_t op, char const *value, fr_token_t list_op)
-{
- CONF_PAIR *cp;
- fr_token_t rhs_quote;
-
- if (original) {
- rhs_quote = cf_pair_value_quote(original);
- } else {
- rhs_quote = T_BARE_WORD;
- }
-
- cp = cf_pair_alloc(cs, attr, value, op, T_BARE_WORD, rhs_quote);
- if (!cp) return -1;
-
- if (!original) return 0;
-
- cf_filename_set(cp, cf_filename(original));
- cf_lineno_set(cp, cf_lineno(original));
-
- if (fr_debug_lvl >= 3) {
- if (list_op == T_INVALID) {
- cf_log_err(original, "%s %s %s --> %s %s %s",
- cf_pair_attr(original), fr_tokens[cf_pair_operator(original)], cf_pair_value(original),
- attr, fr_tokens[op], value);
- } else {
- if (*attr == '&') attr++;
- cf_log_err(original, "%s %s %s --> %s %s { %s %s %s }",
- cf_pair_attr(original), fr_tokens[cf_pair_operator(original)], cf_pair_value(original),
- cf_section_name1(cs), fr_tokens[list_op], attr, fr_tokens[op], value);
- }
- } else if (fr_debug_lvl >= 2) {
- if (list_op == T_INVALID) {
- cf_log_err(original, "--> %s %s %s",
- attr, fr_tokens[op], value);
- } else {
- cf_log_err(original, "--> %s %s { %s %s %s }",
- cf_section_name1(cs), fr_tokens[list_op], attr, fr_tokens[op], value);
+ /*
+ * Don't update namespace for &reply += { ... }
+ *
+ * Do update namespace for &reply.foo += { ... }
+ *
+ * Don't update if the LHS is an internal group.
+ */
+ if ((tmpl_attr_num_elements(map->lhs) > 1) && (t_rules.attr.list_def != parent_da) &&
+ !((parent_da->type == FR_TYPE_GROUP) && parent_da->flags.internal)) {
+ t_rules.attr.namespace = parent_da;
}
- }
-
- return 0;
-}
-
-/*
- * Convert "update" to "edit" using evil spells and sorcery.
- */
-static unlang_t *compile_update_to_edit(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs)
-{
- char const *name2 = cf_section_name2(cs);
- CONF_ITEM *ci;
- CONF_SECTION *group;
- unlang_group_t *g;
- char list_buffer[32];
- char value_buffer[256];
- char attr_buffer[256];
- char const *list;
-
- g = unlang_generic_to_group(parent);
-
- /*
- * Wrap it all in a group, no matter what. Because of
- * limitations in the cf_pair_alloc() API.
- */
- group = cf_section_alloc(g->cs, g->cs, "group", NULL);
- if (!group) return NULL;
-
- (void) cf_item_remove(g->cs, group); /* was added at the end */
- cf_item_insert_after(g->cs, cs, group);
-
- /*
- * Hoist this out of the loop, and make sure it never has a '&' prefix.
- */
- if (name2) {
- if (*name2 == '&') name2++;
- snprintf(list_buffer, sizeof(list_buffer), "%s", name2);
- } else {
- snprintf(list_buffer, sizeof(list_buffer), "%s", tmpl_list_name(unlang_ctx->rules->attr.list_def, "<INVALID>"));
-
- }
-
- /*
- * Loop over the entries, rewriting them.
- */
- for (ci = cf_item_next(cs, NULL);
- ci != NULL;
- ci = cf_item_next(cs, ci)) {
- CONF_PAIR *cp;
- CONF_SECTION *child;
- int rcode;
- fr_token_t op;
- char const *attr, *value, *end;
- if (cf_item_is_section(ci)) {
- cf_log_err(ci, "Cannot specify subsections for 'update'");
- return NULL;
+ if (map_afrom_cs_edit(map, &map->child, cs, &t_rules, &t_rules, unlang_fixup_edit, map, 256) < 0) {
+ goto fail;
}
- if (!cf_item_is_pair(ci)) continue;
-
- cp = cf_item_to_pair(ci);
-
- attr = cf_pair_attr(cp);
- value = cf_pair_value(cp);
- op = cf_pair_operator(cp);
-
- fr_assert(attr);
- fr_assert(value);
-
- list = list_buffer;
-
- if (*attr == '&') attr++;
-
- end = strchr(attr, '.');
- if (!end) end = attr + strlen(attr);
-
/*
- * Separate out the various possibilities for the "name", which could be a list, an
- * attribute name, or a list followed by an attribute name.
- *
- * Note that even if we have "update request { ....}", the v3 parser allowed the contents
- * of the "update" section to still specify parent / lists. Which makes parsing it all
- * annoying.
- *
- * The good news is that all we care about is whether or not there's a parent / list ref.
- * We don't care what that ref is.
+ * As a set of fixups... we can't do array references in -=
*/
- {
- fr_dict_attr_t const *tmpl_list;
+ if (map->op == T_OP_SUB_EQ) {
+ for (child = map_list_head(&map->child); child != NULL; child = map_list_next(&map->child, child)) {
+ if (!tmpl_is_attr(child->lhs)) continue;
- /*
- * Allow for a "parent" or "outer" reference. There may be multiple
- * "parent.parent", so we keep processing them until we get a list reference.
- */
- if (fr_table_value_by_substr(tmpl_request_ref_table, attr, end - attr, REQUEST_UNKNOWN) != REQUEST_UNKNOWN) {
+ if (tmpl_attr_tail_num(child->lhs) != NUM_UNSPEC) {
+ cf_log_err(child->ci, "Cannot use array references and values when deleting from a list");
+ goto fail;
+ }
/*
- * Catch one more case where the behavior is different.
- *
- * &request += &config[*]
+ * The edit code doesn't do this correctly, so we just forbid it.
*/
- if ((cf_pair_value_quote(cp) == T_BARE_WORD) && (*value == '&') &&
- (strchr(value, '.') == NULL) && (strchr(value, '[') != NULL)) {
- char const *p = strchr(value, '[');
-
- cf_log_err(cp, "Cannot do array assignments for lists. Just use '%s %s %.*s'",
- list, fr_tokens[op], (int) (p - value), value);
- return NULL;
- }
-
- goto attr_is_list;
-
- /*
- * Doesn't have a parent ref, maybe it's a list ref?
- */
- } else if (tmpl_attr_list_from_substr(&tmpl_list, &FR_SBUFF_IN(attr, (end - attr))) > 0) {
- char *p;
-
- attr_is_list:
- snprintf(attr_buffer, sizeof(attr_buffer), "%s", attr);
- list = attr_buffer;
- attr = NULL;
-
- p = strchr(attr_buffer, '.');
- if (p) {
- *(p++) = '\0';
- attr = p;
+ if ((tmpl_attr_num_elements(child->lhs) - tmpl_attr_num_elements(map->lhs)) > 1) {
+ cf_log_err(child->ci, "List deletion must operate directly on the final child");
+ goto fail;
}
- }
- }
- switch (op) {
- /*
- * FOO !* ANY
- *
- * The RHS doesn't matter, so we ignore it.
- */
- case T_OP_CMP_FALSE:
- if (!attr) {
/*
- * Set list to empty value.
+ * We don't do list comparisons either.
*/
- rcode = edit_section_alloc(group, NULL, list, T_OP_SET);
-
- } else {
- if (strchr(attr, '[') == NULL) {
- snprintf(value_buffer, sizeof(value_buffer), "%s[*]", attr);
- } else {
- snprintf(value_buffer, sizeof(value_buffer), "%s", attr);
- }
-
- rcode = edit_pair_alloc(group, cp, list, T_OP_SUB_EQ, value_buffer, T_INVALID);
- }
- break;
-
- case T_OP_SET:
- /*
- * Must be a list-to-list operation
- */
- if (!attr) {
- list_op:
- rcode = edit_pair_alloc(group, cp, list, op, value, T_INVALID);
- break;
- }
- goto pair_op;
-
- case T_OP_EQ:
- /*
- * Allow &list = "foo"
- */
- if (!attr) {
- if (!value) {
- cf_log_err(cp, "Missing value");
- return NULL;
+ if (fr_type_is_structural(tmpl_attr_tail_da(child->lhs)->type)) {
+ cf_log_err(child->ci, "List deletion cannot operate on lists");
+ goto fail;
}
-
- rcode = edit_pair_alloc(group, cp, list, op, value, T_INVALID);
- break;
- }
-
- pair_op:
- fr_assert(*attr != '&');
- if (snprintf(value_buffer, sizeof(value_buffer), "%s.%s", list, attr) < 0) {
- cf_log_err(cp, "RHS of update too long to convert to edit automatically");
- return NULL;
- }
-
- rcode = edit_pair_alloc(group, cp, value_buffer, op, value, T_INVALID);
- break;
-
- case T_OP_ADD_EQ:
- case T_OP_PREPEND:
- if (!attr) goto list_op;
-
- rcode = edit_section_alloc(group, &child, list, op);
- if (rcode < 0) break;
-
- rcode = edit_pair_alloc(child, cp, attr, T_OP_EQ, value, op);
- break;
-
- /*
- * Remove matching attributes
- */
- case T_OP_SUB_EQ:
- op = T_OP_CMP_EQ;
-
- filter:
- if (!attr) {
- cf_log_err(cp, "Invalid operator for list assignment");
- return NULL;
- }
-
- rcode = edit_section_alloc(group, &child, list, T_OP_SUB_EQ);
- if (rcode < 0) break;
-
- if (strchr(attr, '[') != 0) {
- cf_log_err(cp, "Cannot do filtering with array indexes");
- return NULL;
}
-
- rcode = edit_pair_alloc(child, cp, attr, op, value, T_OP_SUB_EQ);
- break;
-
- /*
- * Keep matching attributes, i.e. remove non-matching ones.
- */
- case T_OP_CMP_EQ:
- op = T_OP_NE;
- goto filter;
-
- case T_OP_NE:
- op = T_OP_CMP_EQ;
- goto filter;
-
- case T_OP_LT:
- op = T_OP_GE;
- goto filter;
-
- case T_OP_LE:
- op = T_OP_GT;
- goto filter;
-
- case T_OP_GT:
- op = T_OP_LE;
- goto filter;
-
- case T_OP_GE:
- op = T_OP_LT;
- goto filter;
-
- default:
- cf_log_err(cp, "Unsupported operator - cannot auto-convert to edit section");
- return NULL;
+ }
+ } else {
+ /*
+ * &foo := { a, b, c }
+ */
+ if (map_list_afrom_cs(map, &map->child, cs, &t_rules, NULL, NULL, 256) < 0) {
+ goto fail;
}
- if (rcode < 0) {
- cf_log_err(cp, "Failed converting entry");
- return NULL;
+ if ((map->op != T_OP_SET) && !map_list_num_elements(&map->child)) {
+ cf_log_err(cs, "Cannot use operator '%s' for assigning empty list to '%s' data type.",
+ fr_tokens[map->op], fr_type_to_str(parent_da->type));
+ goto fail;
}
}
+ /*
+ * Do basic sanity checks and resolving.
+ */
+ if (!pass2_fixup_map(map, unlang_ctx->rules, NULL)) goto fail;
- return UNLANG_IGNORE;
-}
+ /*
+ * Check operators, and ensure that the RHS has been
+ * resolved.
+ */
+// if (unlang_fixup_update(map, NULL) < 0) goto fail;
-static unlang_t *compile_update(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- int rcode;
+ map_list_insert_tail(&edit->maps, map);
- unlang_group_t *g;
- unlang_map_t *gext;
+ return out;
+}
- unlang_t *c;
- char const *name2 = cf_section_name2(cs);
+/** Compile one edit pair
+ *
+ */
+static unlang_t *compile_edit_pair(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_PAIR *cp)
+{
+ unlang_edit_t *edit;
+ unlang_t *c = NULL, *out = UNLANG_IGNORE;
+ map_t *map;
+ int num;
tmpl_rules_t t_rules;
-
- if (main_config_migrate_option_get("forbid_update")) {
- cf_log_err(cs, "The use of 'update' sections is forbidden by the server configuration");
- return NULL;
- }
-
- /*
- * If we're migrating "update" sections to edit, then go
- * do that now.
- */
- if (main_config_migrate_option_get("rewrite_update")) {
- return compile_update_to_edit(parent, unlang_ctx, cs);
- }
+ fr_token_t op;
/*
* We allow unknown attributes here.
*/
t_rules = *(unlang_ctx->rules);
t_rules.attr.allow_unknown = true;
- t_rules.attr.allow_wildcard = true;
RULES_VERIFY(&t_rules);
- g = group_allocate(parent, cs, UNLANG_TYPE_UPDATE);
- if (!g) return NULL;
+ edit = talloc_zero(parent, unlang_edit_t);
+ if (!edit) return NULL;
+
+ c = out = unlang_edit_to_generic(edit);
+ c->parent = parent;
+ c->next = NULL;
+ c->name = cf_pair_attr(cp);
+ c->debug_name = c->name;
+ c->type = UNLANG_TYPE_EDIT;
+ c->ci = CF_TO_ITEM(cp);
+
+ map_list_init(&edit->maps);
+
+ unlang_compile_action_defaults(c, unlang_ctx);
- gext = unlang_group_to_map(g);
+ op = cf_pair_operator(cp);
+ if ((op == T_OP_CMP_TRUE) || (op == T_OP_CMP_FALSE)) {
+ cf_log_err(cp, "Invalid operator \"%s\".",
+ fr_table_str_by_value(fr_tokens_table, op, "<INVALID>"));
+ return NULL;
+ }
/*
- * This looks at cs->name2 to determine which list to update
+ * Convert this particular map.
*/
- map_list_init(&gext->map);
- rcode = map_afrom_cs(gext, &gext->map, cs, &t_rules, &t_rules, unlang_fixup_update, NULL, 128);
- if (rcode < 0) return NULL; /* message already printed */
- if (map_list_empty(&gext->map)) {
- cf_log_err(cs, "'update' sections cannot be empty");
- error:
- talloc_free(g);
+ if (map_afrom_cp(edit, &map, map_list_tail(&edit->maps), cp, &t_rules, NULL, true) < 0) {
+ fail:
+ talloc_free(edit);
return NULL;
}
- c = unlang_group_to_generic(g);
- if (name2) {
- c->name = name2;
- c->debug_name = talloc_typed_asprintf(c, "update %s", name2);
- } else {
- c->name = "update";
- c->debug_name = c->name;
+ /*
+ * @todo - we still want to do fixups on the RHS?
+ */
+ if (tmpl_is_attr(map->lhs)) {
+ /*
+ * Can't assign to [*] or [#]
+ */
+ num = tmpl_attr_tail_num(map->lhs);
+ if ((num == NUM_ALL) || (num == NUM_COUNT)) {
+ cf_log_err(cp, "Invalid array reference in %s", map->lhs->name);
+ goto fail;
+ }
+
+ if ((map->op == T_OP_SUB_EQ) && fr_type_is_structural(tmpl_attr_tail_da(map->lhs)->type) &&
+ tmpl_is_attr(map->rhs) && tmpl_attr_tail_da(map->rhs)->flags.local) {
+ cf_log_err(cp, "Cannot delete local variable %s", map->rhs->name);
+ goto fail;
+ }
}
- if (!pass2_fixup_update(g, unlang_ctx->rules)) goto error;
+ /*
+ * Do basic sanity checks and resolving.
+ */
+ if (!pass2_fixup_map(map, unlang_ctx->rules, NULL)) goto fail;
+
+ /*
+ * Check operators, and ensure that the RHS has been
+ * resolved.
+ */
+ if (unlang_fixup_update(map, c) < 0) goto fail;
- compile_action_defaults(c, unlang_ctx);
+ map_list_insert_tail(&edit->maps, map);
- return c;
+ return out;
}
-#define T(_x) [T_OP_ ## _x] = true
+#define debug_braces(_type) (unlang_ops[_type].flag & UNLANG_OP_FLAG_DEBUG_BRACES)
-static const bool edit_list_sub_op[T_TOKEN_LAST] = {
- T(NE),
- T(GE),
- T(GT),
- T(LE),
- T(LT),
- T(CMP_EQ),
-};
-
-/** Validate and fixup a map that's part of an edit section.
- *
- * @param map to validate.
- * @param ctx data to pass to fixup function (currently unused).
- * @return 0 if valid else -1.
- *
- * @todo - this is only called for CONF_PAIR maps, not for
- * CONF_SECTION. So when we parse nested maps, there's no validation
- * done of the CONF_SECTION. In order to fix this, we need to have
- * map_afrom_cs() call the validation function for the CONF_SECTION
- * *before* recursing.
- */
-static int unlang_fixup_edit(map_t *map, void *ctx)
-{
- CONF_PAIR *cp = cf_item_to_pair(map->ci);
- fr_dict_attr_t const *da;
- fr_dict_attr_t const *parent = NULL;
- map_t *parent_map = ctx;
-
- fr_assert(parent_map);
-#ifdef STATIC_ANALYZER
- if (!parent_map) return -1;
-#endif
-
- fr_assert(tmpl_is_attr(parent_map->lhs));
-
- if (parent_map && (parent_map->op == T_OP_SUB_EQ)) {
- if (!edit_list_sub_op[map->op]) {
- cf_log_err(cp, "Invalid operator '%s' for right-hand side list. It must be a comparison operator", fr_tokens[map->op]);
- return -1;
- }
-
- } else if (map->op != T_OP_EQ) {
- cf_log_err(cp, "Invalid operator '%s' for right-hand side list. It must be '='", fr_tokens[map->op]);
- return -1;
- }
-
- /*
- * map_afrom_cs() will build its tree recursively, and call us for each child map.
- */
- if (map->parent && (map->parent != parent_map)) parent_map = map->parent;
-
- parent = tmpl_attr_tail_da(parent_map->lhs);
-
- switch (map->lhs->type) {
- case TMPL_TYPE_ATTR:
- da = tmpl_attr_tail_da(map->lhs);
- if (!da->flags.internal && parent && (parent->type != FR_TYPE_GROUP) &&
- (da->parent != parent)) {
- /* FIXME - Broken check, doesn't work for key attributes */
- cf_log_err(cp, "Invalid location for %s - it is not a child of %s",
- da->name, parent->name);
- return 0;
- }
- break;
-
- case TMPL_TYPE_XLAT_UNRESOLVED:
- case TMPL_TYPE_XLAT:
- break;
-
- default:
- cf_log_err(map->ci, "Left side of map must be an attribute "
- "or an xlat (that expands to an attribute), not a %s",
- tmpl_type_to_str(map->lhs->type));
- return -1;
- }
-
- fr_assert(map->rhs);
-
- switch (map->rhs->type) {
- case TMPL_TYPE_DATA_UNRESOLVED:
- case TMPL_TYPE_XLAT_UNRESOLVED:
- case TMPL_TYPE_XLAT:
- case TMPL_TYPE_DATA:
- case TMPL_TYPE_ATTR:
- case TMPL_TYPE_EXEC:
- break;
-
- default:
- cf_log_err(map->ci, "Right side of map must be an attribute, literal, xlat or exec, got type %s",
- tmpl_type_to_str(map->rhs->type));
- return -1;
- }
-
- return 0;
-}
-
-/** Compile one edit section.
- */
-static unlang_t *compile_edit_section(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs)
-{
- unlang_edit_t *edit;
- unlang_t *c, *out = UNLANG_IGNORE;
- map_t *map;
- char const *name;
- fr_token_t op;
- ssize_t slen;
- fr_dict_attr_t const *parent_da;
- int num;
-
- tmpl_rules_t t_rules;
-
- name = cf_section_name2(cs);
- if (name) {
- cf_log_err(cs, "Unexpected name2 '%s' for editing list %s ", name, cf_section_name1(cs));
- return NULL;
- }
-
- op = cf_section_name2_quote(cs);
- if ((op == T_INVALID) || !fr_list_assignment_op[op]) {
- cf_log_err(cs, "Invalid operator '%s' for editing list %s.", fr_tokens[op], cf_section_name1(cs));
- return NULL;
- }
-
- if ((op == T_OP_CMP_TRUE) || (op == T_OP_CMP_FALSE)) {
- cf_log_err(cs, "Invalid operator \"%s\".",
- fr_table_str_by_value(fr_tokens_table, op, "<INVALID>"));
- return NULL;
- }
-
- /*
- * We allow unknown attributes here.
- */
- t_rules = *(unlang_ctx->rules);
- t_rules.attr.allow_unknown = true;
- RULES_VERIFY(&t_rules);
-
- edit = talloc_zero(parent, unlang_edit_t);
- if (!edit) return NULL;
-
- c = out = unlang_edit_to_generic(edit);
- c->parent = parent;
- c->next = NULL;
- c->name = cf_section_name1(cs);
- c->debug_name = c->name;
- c->type = UNLANG_TYPE_EDIT;
- c->ci = CF_TO_ITEM(cs);
-
- map_list_init(&edit->maps);
-
- compile_action_defaults(c, unlang_ctx);
-
- /*
- * Allocate the map and initialize it.
- */
- MEM(map = talloc_zero(parent, map_t));
- map->op = op;
- map->ci = cf_section_to_item(cs);
- map_list_init(&map->child);
-
- name = cf_section_name1(cs);
-
- slen = tmpl_afrom_attr_str(map, NULL, &map->lhs, name, &t_rules);
- if (slen <= 0) {
- cf_log_err(cs, "Failed parsing list reference %s - %s", name, fr_strerror());
- fail:
- talloc_free(edit);
- return NULL;
- }
-
- /*
- * Can't assign to [*] or [#]
- */
- num = tmpl_attr_tail_num(map->lhs);
- if ((num == NUM_ALL) || (num == NUM_COUNT)) {
- cf_log_err(cs, "Invalid array reference in %s", name);
- goto fail;
- }
-
- /*
- * If the DA isn't structural, then it can't have children.
- */
- parent_da = tmpl_attr_tail_da(map->lhs);
- if (fr_type_is_structural(parent_da->type)) {
- map_t *child;
-
- /*
- * Don't update namespace for &reply += { ... }
- *
- * Do update namespace for &reply.foo += { ... }
- *
- * Don't update if the LHS is an internal group.
- */
- if ((tmpl_attr_num_elements(map->lhs) > 1) && (t_rules.attr.list_def != parent_da) &&
- !((parent_da->type == FR_TYPE_GROUP) && parent_da->flags.internal)) {
- t_rules.attr.namespace = parent_da;
- }
-
- if (map_afrom_cs_edit(map, &map->child, cs, &t_rules, &t_rules, unlang_fixup_edit, map, 256) < 0) {
- goto fail;
- }
-
- /*
- * As a set of fixups... we can't do array references in -=
- */
- if (map->op == T_OP_SUB_EQ) {
- for (child = map_list_head(&map->child); child != NULL; child = map_list_next(&map->child, child)) {
- if (!tmpl_is_attr(child->lhs)) continue;
-
- if (tmpl_attr_tail_num(child->lhs) != NUM_UNSPEC) {
- cf_log_err(child->ci, "Cannot use array references and values when deleting from a list");
- goto fail;
- }
-
- /*
- * The edit code doesn't do this correctly, so we just forbid it.
- */
- if ((tmpl_attr_num_elements(child->lhs) - tmpl_attr_num_elements(map->lhs)) > 1) {
- cf_log_err(child->ci, "List deletion must operate directly on the final child");
- goto fail;
- }
-
- /*
- * We don't do list comparisons either.
- */
- if (fr_type_is_structural(tmpl_attr_tail_da(child->lhs)->type)) {
- cf_log_err(child->ci, "List deletion cannot operate on lists");
- goto fail;
- }
- }
- }
- } else {
- /*
- * &foo := { a, b, c }
- */
- if (map_list_afrom_cs(map, &map->child, cs, &t_rules, NULL, NULL, 256) < 0) {
- goto fail;
- }
-
- if ((map->op != T_OP_SET) && !map_list_num_elements(&map->child)) {
- cf_log_err(cs, "Cannot use operator '%s' for assigning empty list to '%s' data type.",
- fr_tokens[map->op], fr_type_to_str(parent_da->type));
- goto fail;
- }
- }
- /*
- * Do basic sanity checks and resolving.
- */
- if (!pass2_fixup_map(map, unlang_ctx->rules, NULL)) goto fail;
-
- /*
- * Check operators, and ensure that the RHS has been
- * resolved.
- */
-// if (unlang_fixup_update(map, NULL) < 0) goto fail;
-
- map_list_insert_tail(&edit->maps, map);
-
- return out;
-}
-
-/** Compile one edit pair
- *
- */
-static unlang_t *compile_edit_pair(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_PAIR *cp)
-{
- unlang_edit_t *edit;
- unlang_t *c = NULL, *out = UNLANG_IGNORE;
- map_t *map;
- int num;
-
- tmpl_rules_t t_rules;
- fr_token_t op;
-
- /*
- * We allow unknown attributes here.
- */
- t_rules = *(unlang_ctx->rules);
- t_rules.attr.allow_unknown = true;
- RULES_VERIFY(&t_rules);
-
- edit = talloc_zero(parent, unlang_edit_t);
- if (!edit) return NULL;
-
- c = out = unlang_edit_to_generic(edit);
- c->parent = parent;
- c->next = NULL;
- c->name = cf_pair_attr(cp);
- c->debug_name = c->name;
- c->type = UNLANG_TYPE_EDIT;
- c->ci = CF_TO_ITEM(cp);
-
- map_list_init(&edit->maps);
-
- compile_action_defaults(c, unlang_ctx);
-
- op = cf_pair_operator(cp);
- if ((op == T_OP_CMP_TRUE) || (op == T_OP_CMP_FALSE)) {
- cf_log_err(cp, "Invalid operator \"%s\".",
- fr_table_str_by_value(fr_tokens_table, op, "<INVALID>"));
- return NULL;
- }
-
- /*
- * Convert this particular map.
- */
- if (map_afrom_cp(edit, &map, map_list_tail(&edit->maps), cp, &t_rules, NULL, true) < 0) {
- fail:
- talloc_free(edit);
- return NULL;
- }
-
- /*
- * @todo - we still want to do fixups on the RHS?
- */
- if (tmpl_is_attr(map->lhs)) {
- /*
- * Can't assign to [*] or [#]
- */
- num = tmpl_attr_tail_num(map->lhs);
- if ((num == NUM_ALL) || (num == NUM_COUNT)) {
- cf_log_err(cp, "Invalid array reference in %s", map->lhs->name);
- goto fail;
- }
-
- if ((map->op == T_OP_SUB_EQ) && fr_type_is_structural(tmpl_attr_tail_da(map->lhs)->type) &&
- tmpl_is_attr(map->rhs) && tmpl_attr_tail_da(map->rhs)->flags.local) {
- cf_log_err(cp, "Cannot delete local variable %s", map->rhs->name);
- goto fail;
- }
- }
-
- /*
- * Do basic sanity checks and resolving.
- */
- if (!pass2_fixup_map(map, unlang_ctx->rules, NULL)) goto fail;
-
- /*
- * Check operators, and ensure that the RHS has been
- * resolved.
- */
- if (unlang_fixup_update(map, c) < 0) goto fail;
-
- map_list_insert_tail(&edit->maps, map);
-
- return out;
-}
-
-static int define_local_variable(CONF_ITEM *ci, unlang_variable_t *var, tmpl_rules_t *t_rules, fr_type_t type, char const *name,
- fr_dict_attr_t const *ref);
-
-#define debug_braces(_type) (unlang_ops[_type].flag & UNLANG_OP_FLAG_DEBUG_BRACES)
-
-/** Compile a variable definition.
- *
- * Definitions which are adjacent to one another are automatically merged
- * into one larger variable definition.
- */
-static int compile_variable(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_PAIR *cp, tmpl_rules_t *t_rules)
-{
- unlang_variable_t *var;
- fr_type_t type;
- char const *attr, *value;
- unlang_group_t *group;
-
- fr_assert(debug_braces(parent->type));
-
- /*
- * Enforce locations for local variables.
- */
- switch (parent->type) {
- case UNLANG_TYPE_CASE:
- case UNLANG_TYPE_ELSE:
- case UNLANG_TYPE_ELSIF:
- case UNLANG_TYPE_FOREACH:
- case UNLANG_TYPE_GROUP:
- case UNLANG_TYPE_IF:
- case UNLANG_TYPE_TIMEOUT:
- case UNLANG_TYPE_LIMIT:
- case UNLANG_TYPE_POLICY:
- case UNLANG_TYPE_REDUNDANT:
- case UNLANG_TYPE_SUBREQUEST:
- case UNLANG_TYPE_LOAD_BALANCE:
- case UNLANG_TYPE_REDUNDANT_LOAD_BALANCE:
- break;
-
- default:
- cf_log_err(cp, "Local variables cannot be used here");
- return -1;
- }
-
- /*
- * The variables exist in the parent block.
- */
- group = unlang_generic_to_group(parent);
- if (group->variables) {
- var = group->variables;
-
- } else {
- group->variables = var = talloc_zero(parent, unlang_variable_t);
- if (!var) return -1;
-
- var->dict = fr_dict_protocol_alloc(unlang_ctx->rules->attr.dict_def);
- if (!var->dict) {
- talloc_free(var);
- return -1;
- }
- var->root = fr_dict_root(var->dict);
-
- var->max_attr = 1;
-
- /*
- * Initialize the new rules, and point them to the parent rules.
- *
- * Then replace the parse rules with our rules, and our dictionary.
- */
- *t_rules = *unlang_ctx->rules;
- t_rules->parent = unlang_ctx->rules;
-
- t_rules->attr.dict_def = var->dict;
- t_rules->attr.namespace = NULL;
-
- unlang_ctx->rules = t_rules;
- }
-
- attr = cf_pair_attr(cp); /* data type */
- value = cf_pair_value(cp); /* variable name */
-
- type = fr_table_value_by_str(fr_type_table, attr, FR_TYPE_NULL);
- if (type == FR_TYPE_NULL) {
-invalid_type:
- cf_log_err(cp, "Invalid data type '%s'", attr);
- return -1;
- }
-
- /*
- * Leaf and group are OK. TLV, Vendor, Struct, VSA, etc. are not.
- */
- if (!(fr_type_is_leaf(type) || (type == FR_TYPE_GROUP))) goto invalid_type;
-
- return define_local_variable(cf_pair_to_item(cp), var, t_rules, type, value, NULL);
-}
-
-/*
- * Compile action && rcode for later use.
- */
-static int compile_action_pair(unlang_mod_actions_t *actions, CONF_PAIR *cp)
-{
- int action;
- char const *attr, *value;
-
- attr = cf_pair_attr(cp);
- value = cf_pair_value(cp);
- if (!value) return 0;
-
- if (!strcasecmp(value, "return"))
- action = MOD_ACTION_RETURN;
-
- else if (!strcasecmp(value, "break"))
- action = MOD_ACTION_RETURN;
-
- else if (!strcasecmp(value, "reject"))
- action = MOD_ACTION_REJECT;
-
- else if (!strcasecmp(value, "retry"))
- action = MOD_ACTION_RETRY;
-
- else if (strspn(value, "0123456789")==strlen(value)) {
- action = atoi(value);
-
- if (!action || (action > MOD_PRIORITY_MAX)) {
- cf_log_err(cp, "Priorities MUST be between 1 and 64.");
- return 0;
- }
-
- } else {
- cf_log_err(cp, "Unknown action '%s'.\n",
- value);
- return 0;
- }
-
- if (strcasecmp(attr, "default") != 0) {
- int rcode;
-
- rcode = fr_table_value_by_str(mod_rcode_table, attr, -1);
- if (rcode < 0) {
- cf_log_err(cp,
- "Unknown module rcode '%s'.",
- attr);
- return 0;
- }
- actions->actions[rcode] = action;
-
- } else { /* set all unset values to the default */
- int i;
-
- for (i = 0; i < RLM_MODULE_NUMCODES; i++) {
- if (!actions->actions[i]) actions->actions[i] = action;
- }
- }
-
- return 1;
-}
-
-static bool compile_retry_section(unlang_mod_actions_t *actions, CONF_ITEM *ci)
-{
- CONF_ITEM *csi;
- CONF_SECTION *cs;
-
- cs = cf_item_to_section(ci);
- for (csi=cf_item_next(cs, NULL);
- csi != NULL;
- csi=cf_item_next(cs, csi)) {
- CONF_PAIR *cp;
- char const *name, *value;
-
- if (cf_item_is_section(csi)) {
- cf_log_err(csi, "Invalid subsection in 'retry' configuration.");
- return false;
- }
-
- if (!cf_item_is_pair(csi)) continue;
-
- cp = cf_item_to_pair(csi);
- name = cf_pair_attr(cp);
- value = cf_pair_value(cp);
-
- if (!value) {
- cf_log_err(csi, "Retry configuration must specify a value");
- return false;
- }
-
-#define CLAMP(_name, _field, _limit) do { \
- if (!fr_time_delta_ispos(actions->retry._field)) { \
- cf_log_err(csi, "Invalid value for '" STRINGIFY(_name) " = %s' - value must be positive", \
- value); \
- return false; \
- } \
- if (fr_time_delta_cmp(actions->retry._field, fr_time_delta_from_sec(_limit)) > 0) { \
- cf_log_err(csi, "Invalid value for '" STRINGIFY(_name) " = %s' - value must be less than " STRINGIFY(_limit) "s", \
- value); \
- return false; \
- } \
- } while (0)
-
- /*
- * We don't use conf_parser_t here for various
- * magical reasons.
- */
- if (strcmp(name, "initial_rtx_time") == 0) {
- if (fr_time_delta_from_str(&actions->retry.irt, value, strlen(value), FR_TIME_RES_SEC) < 0) {
- error:
- cf_log_err(csi, "Failed parsing '%s = %s' - %s",
- name, value, fr_strerror());
- return false;
- }
- CLAMP(initial_rtx_time, irt, 2);
-
- } else if (strcmp(name, "max_rtx_time") == 0) {
- if (fr_time_delta_from_str(&actions->retry.mrt, value, strlen(value), FR_TIME_RES_SEC) < 0) goto error;
-
- CLAMP(max_rtx_time, mrt, 10);
-
- } else if (strcmp(name, "max_rtx_count") == 0) {
- unsigned long v = strtoul(value, 0, 0);
-
- if (v > 10) {
- cf_log_err(csi, "Invalid value for 'max_rtx_count = %s' - value must be between 0 and 10",
- value);
- return false;
- }
-
- actions->retry.mrc = v;
-
- } else if (strcmp(name, "max_rtx_duration") == 0) {
- if (fr_time_delta_from_str(&actions->retry.mrd, value, strlen(value), FR_TIME_RES_SEC) < 0) goto error;
-
- CLAMP(max_rtx_duration, mrd, 20);
-
- } else {
- cf_log_err(csi, "Invalid item '%s' in 'retry' configuration.", name);
- return false;
- }
- }
-
- return true;
-}
-
-bool unlang_compile_actions(unlang_mod_actions_t *actions, CONF_SECTION *action_cs, bool module_retry)
-{
- int i;
- bool disallow_retry_action = false;
- CONF_ITEM *csi;
- CONF_SECTION *cs;
-
- /*
- * Over-ride the default return codes of the module.
- */
- cs = cf_item_to_section(cf_section_to_item(action_cs));
- for (csi=cf_item_next(cs, NULL);
- csi != NULL;
- csi=cf_item_next(cs, csi)) {
- char const *name;
- CONF_PAIR *cp;
-
- if (cf_item_is_section(csi)) {
- CONF_SECTION *subcs = cf_item_to_section(csi);
-
- name = cf_section_name1(subcs);
-
- /*
- * Look for a "retry" section.
- */
- if (name && (strcmp(name, "retry") == 0) && !cf_section_name2(subcs)) {
- if (!compile_retry_section(actions, csi)) return false;
- continue;
- }
-
- cf_log_err(csi, "Invalid subsection. Expected 'action = value'");
- return false;
- }
-
- if (!cf_item_is_pair(csi)) continue;
-
- cp = cf_item_to_pair(csi);
-
- /*
- * Allow 'retry = path.to.retry.config'
- */
- name = cf_pair_attr(cp);
- if (strcmp(name, "retry") == 0) {
- CONF_ITEM *subci;
- char const *value = cf_pair_value(cp);
-
- if (!value) {
- cf_log_err(csi, "Missing reference string");
- return false;
- }
-
- subci = cf_reference_item(cs, cf_root(cf_section_to_item(action_cs)), value);
- if (!subci) {
- cf_log_perr(csi, "Failed finding reference '%s'", value);
- return false;
- }
-
- if (!compile_retry_section(actions, subci)) return false;
- continue;
- }
-
- if (!compile_action_pair(actions, cp)) {
- return false;
- }
- }
-
- if (module_retry) {
- if (!fr_time_delta_ispos(actions->retry.irt)) {
- cf_log_err(csi, "initial_rtx_time MUST be non-zero for modules which support retries.");
- return false;
- }
- } else {
- if (fr_time_delta_ispos(actions->retry.irt)) {
- cf_log_err(csi, "initial_rtx_time MUST be zero, as only max_rtx_count and max_rtx_duration are used.");
- return false;
- }
-
- if (!actions->retry.mrc && !fr_time_delta_ispos(actions->retry.mrd)) {
- disallow_retry_action = true;
- }
- }
-
- /*
- * Sanity check that "fail = retry", we actually have a
- * retry section.
- */
- for (i = 0; i < RLM_MODULE_NUMCODES; i++) {
- if (actions->actions[i] != MOD_ACTION_RETRY) continue;
-
- if (module_retry) {
- cf_log_err(csi, "Cannot use a '%s = retry' action for a module which has its own retries",
- fr_table_str_by_value(mod_rcode_table, i, "<INVALID>"));
- return false;
- }
-
- if (disallow_retry_action) {
- cf_log_err(csi, "max_rtx_count and max_rtx_duration cannot both be zero when using '%s = retry'",
- fr_table_str_by_value(mod_rcode_table, i, "<INVALID>"));
- return false;
- }
-
- if (!fr_time_delta_ispos(actions->retry.irt) &&
- !actions->retry.mrc &&
- !fr_time_delta_ispos(actions->retry.mrd)) {
- cf_log_err(csi, "Cannot use a '%s = retry' action without a 'retry { ... }' section.",
- fr_table_str_by_value(mod_rcode_table, i, "<INVALID>"));
- return false;
- }
- }
-
- return true;
-}
-
-static unlang_t *compile_empty(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs, unlang_type_t type)
-{
- unlang_group_t *g;
- unlang_t *c;
-
- /*
- * If we're compiling an empty section, then the
- * *interpreter* type is GROUP, even if the *debug names*
- * are something else.
- */
- g = group_allocate(parent, cs, type);
- if (!g) return NULL;
-
- c = unlang_group_to_generic(g);
- if (!cs) {
- c->name = unlang_ops[type].name;
- c->debug_name = c->name;
-
- } else {
- char const *name2;
-
- name2 = cf_section_name2(cs);
- if (!name2) {
- c->name = cf_section_name1(cs);
- c->debug_name = c->name;
- } else {
- c->name = name2;
- c->debug_name = talloc_typed_asprintf(c, "%s %s", cf_section_name1(cs), name2);
- }
- }
-
- compile_action_defaults(c, unlang_ctx);
- return c;
-}
-
-
-static unlang_t *compile_item(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM *ci);
-
-/*
- * compile 'actions { ... }' inside of another group.
- */
-static bool compile_action_subsection(unlang_t *c, CONF_SECTION *cs, CONF_SECTION *subcs)
-{
- CONF_ITEM *ci, *next;
-
- ci = cf_section_to_item(subcs);
-
- next = cf_item_next(cs, ci);
- if (next && (cf_item_is_pair(next) || cf_item_is_section(next))) {
- cf_log_err(ci, "'actions' MUST be the last block in a section");
- return false;
- }
-
- if (cf_section_name2(subcs) != NULL) {
- cf_log_err(ci, "Invalid name for 'actions' section");
- return false;
- }
-
- /*
- * Over-riding the actions can be done in certain limited
- * situations. In other situations (e.g. "redundant",
- * "load-balance"), it doesn't make sense.
- *
- * Note that this limitation also applies to "retry"
- * timers. We can do retries of a "group". We cannot do
- * retries of "load-balance", as the "load-balance"
- * section already takes care of redundancy.
- *
- * We may need to loosen that limitation in the future.
- */
- switch (c->type) {
- case UNLANG_TYPE_CASE:
- case UNLANG_TYPE_IF:
- case UNLANG_TYPE_ELSE:
- case UNLANG_TYPE_ELSIF:
- case UNLANG_TYPE_FOREACH:
- case UNLANG_TYPE_GROUP:
- case UNLANG_TYPE_LIMIT:
- case UNLANG_TYPE_SWITCH:
- case UNLANG_TYPE_TIMEOUT:
- case UNLANG_TYPE_TRANSACTION:
- break;
-
- default:
- cf_log_err(ci, "'actions' MUST NOT be in a '%s' block", unlang_ops[c->type].name);
- return false;
- }
-
- return unlang_compile_actions(&c->actions, subcs, false);
-}
-
-
-static unlang_t *compile_children(unlang_group_t *g, unlang_compile_t *unlang_ctx_in, bool set_action_defaults)
-{
- CONF_ITEM *ci = NULL;
- unlang_t *c, *single;
- bool was_if = false;
- char const *skip_else = NULL;
- unlang_compile_t *unlang_ctx;
- unlang_compile_t unlang_ctx2;
- tmpl_rules_t t_rules;
-
- c = unlang_group_to_generic(g);
-
- /*
- * Create our own compilation context which can be edited
- * by a variable definition.
- */
- compile_copy_context(&unlang_ctx2, unlang_ctx_in);
- unlang_ctx = &unlang_ctx2;
- t_rules = *unlang_ctx_in->rules;
-
- /*
- * Loop over the children of this group.
- */
- while ((ci = cf_item_next(g->cs, ci))) {
- if (cf_item_is_data(ci)) continue;
-
- /*
- * Sections are keywords, or references to
- * modules with updated return codes.
- */
- if (cf_item_is_section(ci)) {
- char const *name = NULL;
- CONF_SECTION *subcs = cf_item_to_section(ci);
-
- /*
- * Skip precompiled blocks. This is
- * mainly for policies.
- */
- if (cf_data_find(subcs, unlang_group_t, NULL)) continue;
-
- /*
- * "actions" apply to the current group.
- * It's not a subgroup.
- */
- name = cf_section_name1(subcs);
-
- /*
- * In-line attribute editing. Nothing else in the parse has list assignments, so this must be it.
- */
- if (fr_list_assignment_op[cf_section_name2_quote(subcs)]) {
- single = compile_edit_section(c, unlang_ctx, subcs);
- if (!single) {
- talloc_free(c);
- return NULL;
- }
-
- goto add_child;
- }
-
- if (strcmp(name, "actions") == 0) {
- if (!compile_action_subsection(c, g->cs, subcs)) {
- talloc_free(c);
- return NULL;
- }
-
- continue;
- }
-
- /*
- * Special checks for "else" and "elsif".
- */
- if ((strcmp(name, "else") == 0) || (strcmp(name, "elsif") == 0)) {
- /*
- * We ran into one without a preceding "if" or "elsif".
- * That's not allowed.
- */
- if (!was_if) {
- cf_log_err(ci, "Invalid location for '%s'. There is no preceding "
- "'if' or 'elsif' statement", name);
- talloc_free(c);
- return NULL;
- }
-
- /*
- * There was a previous "if" or "elsif" which was always taken.
- * So we skip this "elsif" or "else".
- */
- if (skip_else) {
- void *ptr;
-
- /*
- * And manually free this.
- */
- ptr = cf_data_remove(subcs, xlat_exp_head_t, NULL);
- talloc_free(ptr);
-
- cf_section_free_children(subcs);
-
- cf_log_debug_prefix(ci, "Skipping contents of '%s' due to previous "
- "'%s' being always being taken.",
- name, skip_else);
- continue;
- }
- }
-
- /*
- * Otherwise it's a real keyword.
- */
- single = compile_item(c, unlang_ctx, ci);
- if (!single) {
- cf_log_err(ci, "Failed to parse \"%s\" subsection", cf_section_name1(subcs));
- talloc_free(c);
- return NULL;
- }
-
- goto add_child;
-
- } else if (cf_item_is_pair(ci)) {
- CONF_PAIR *cp = cf_item_to_pair(ci);
-
- /*
- * Variable definition.
- */
- if (cf_pair_operator(cp) == T_OP_CMP_TRUE) {
- if (compile_variable(c, unlang_ctx, cp, &t_rules) < 0) {
- talloc_free(c);
- return NULL;
- }
-
- single = UNLANG_IGNORE;
- goto add_child;
- }
-
- if (!cf_pair_value(cp)) {
- single = compile_item(c, unlang_ctx, ci);
- if (!single) {
- cf_log_err(ci, "Invalid keyword \"%s\".", cf_pair_attr(cp));
- talloc_free(c);
- return NULL;
- }
-
- goto add_child;
- }
-
- /*
- * What remains MUST be an edit pair. At this point, the state of the compiler
- * tells us what it is, and we don't really care if there's a leading '&'.
- */
- single = compile_edit_pair(c, unlang_ctx, cp);
- if (!single) {
- talloc_free(c);
- return NULL;
- }
-
- goto add_child;
- } else {
- cf_log_err(ci, "Asked to compile unknown conf type");
- talloc_free(c);
- return NULL;
- }
-
- add_child:
- if (single == UNLANG_IGNORE) continue;
-
- /*
- * Do optimizations for "if" and "elsif"
- * conditions.
- */
- switch (single->type) {
- case UNLANG_TYPE_ELSIF:
- case UNLANG_TYPE_IF:
- was_if = true;
-
- {
- unlang_group_t *f;
- unlang_cond_t *gext;
-
- /*
- * Skip else, and/or omit things which will never be run.
- */
- f = unlang_generic_to_group(single);
- gext = unlang_group_to_cond(f);
-
- if (gext->is_truthy) {
- if (gext->value) {
- skip_else = single->debug_name;
- } else {
- /*
- * The condition never
- * matches, so we can
- * avoid putting it into
- * the unlang tree.
- */
- talloc_free(single);
- continue;
- }
- }
- }
- break;
-
- default:
- was_if = false;
- skip_else = NULL;
- break;
- }
-
- /*
- * unlang_group_t is grown by adding a unlang_t to the end
- */
- fr_assert(g == talloc_parent(single));
- fr_assert(single->parent == unlang_group_to_generic(g));
- fr_assert(!single->next);
-
- *g->tail = single;
- g->tail = &single->next;
- g->num_children++;
-
- /*
- * If it's not possible to execute statement
- * after the current one, then just stop
- * processing the children.
- */
- if (g->self.closed) {
- cf_log_warn(ci, "Skipping remaining instructions due to '%s'",
- single->name);
- break;
- }
- }
-
- /*
- * Set the default actions, if they haven't already been
- * set by an "actions" section above.
- */
- if (set_action_defaults) compile_action_defaults(c, unlang_ctx);
-
- return c;
-}
-
-
-/*
- * Generic "compile a section with more unlang inside of it".
- */
-static unlang_t *compile_section(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs, unlang_type_t type)
-{
- unlang_group_t *g;
- unlang_t *c;
- char const *name1, *name2;
-
- fr_assert(unlang_ctx->rules != NULL);
- fr_assert(unlang_ctx->rules->attr.list_def);
-
- /*
- * We always create a group, even if the section is empty.
- */
- g = group_allocate(parent, cs, type);
- if (!g) return NULL;
-
- c = unlang_group_to_generic(g);
-
- /*
- * Remember the name for printing, etc.
- */
- name1 = cf_section_name1(cs);
- name2 = cf_section_name2(cs);
- c->name = name1;
-
- /*
- * Make sure to tell the user that we're running a
- * policy, and not anything else.
- */
- if (type == UNLANG_TYPE_POLICY) {
- MEM(c->debug_name = talloc_typed_asprintf(c, "policy %s", name1));
-
- } else if (!name2) {
- c->debug_name = c->name;
-
- } else {
- MEM(c->debug_name = talloc_typed_asprintf(c, "%s %s", name1, name2));
- }
-
- return compile_children(g, unlang_ctx, true);
-}
-
-
-static unlang_t *compile_group(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- if (!cf_item_next(ci, NULL)) return UNLANG_IGNORE;
-
- return compile_section(parent, unlang_ctx, cf_item_to_section(ci), UNLANG_TYPE_GROUP);
-}
-
-static fr_table_num_sorted_t transaction_keywords[] = {
- { L("case"), 1 },
- { L("else"), 1 },
- { L("elsif"), 1 },
- { L("foreach"), 1 },
- { L("group"), 1 },
- { L("if"), 1 },
- { L("limit"), 1 },
- { L("load-balance"), 1 },
- { L("redundant"), 1 },
- { L("redundant-load-balance"), 1 },
- { L("switch"), 1 },
- { L("timeout"), 1 },
- { L("transaction"), 1 },
-};
-static int transaction_keywords_len = NUM_ELEMENTS(transaction_keywords);
-
-/** Limit the operations which can appear in a transaction.
- */
-static bool transaction_ok(CONF_SECTION *cs)
-{
- CONF_ITEM *ci = NULL;
-
- while ((ci = cf_item_next(cs, ci)) != NULL) {
- char const *name;
-
- if (cf_item_is_section(ci)) {
- CONF_SECTION *subcs;
-
- subcs = cf_item_to_section(ci);
- name = cf_section_name1(subcs);
-
- if (strcmp(name, "actions") == 0) continue;
-
- /*
- * Definitely an attribute editing thing.
- */
- if (*name == '&') continue;
-
- if (fr_list_assignment_op[cf_section_name2_quote(cs)]) continue;
-
- if (fr_table_value_by_str(transaction_keywords, name, -1) < 0) {
- cf_log_err(ci, "Invalid keyword in 'transaction'");
- return false;
- }
-
- if (!transaction_ok(subcs)) return false;
-
- continue;
-
- } else if (cf_item_is_pair(ci)) {
- CONF_PAIR *cp;
-
- cp = cf_item_to_pair(ci);
- name = cf_pair_attr(cp);
-
- /*
- * If there's a value then it's not a module call.
- */
- if (cf_pair_value(cp)) continue;
-
- if (*name == '&') continue;
-
- /*
- * Allow rcodes via the "always" module.
- */
- if (fr_table_value_by_str(mod_rcode_table, name, -1) >= 0) {
- continue;
- }
-
- cf_log_err(ci, "Invalid module reference in 'transaction'");
- return false;
-
- } else {
- continue;
- }
- }
-
- return true;
-}
-
-static unlang_t *compile_transaction(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- unlang_group_t *g;
- unlang_t *c;
- unlang_compile_t unlang_ctx2;
-
- if (cf_section_name2(cs) != NULL) {
- cf_log_err(cs, "Unexpected argument to 'transaction' section");
- cf_log_err(ci, DOC_KEYWORD_REF(transaction));
- return NULL;
- }
-
- /*
- * The transaction is empty, ignore it.
- */
- if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
-
- if (!transaction_ok(cs)) return NULL;
-
- /*
- * Any failure is return, not continue.
- */
- compile_copy_context(&unlang_ctx2, unlang_ctx);
-
- unlang_ctx2.actions.actions[RLM_MODULE_REJECT] = MOD_ACTION_RETURN;
- unlang_ctx2.actions.actions[RLM_MODULE_FAIL] = MOD_ACTION_RETURN;
- unlang_ctx2.actions.actions[RLM_MODULE_INVALID] = MOD_ACTION_RETURN;
- unlang_ctx2.actions.actions[RLM_MODULE_DISALLOW] = MOD_ACTION_RETURN;
-
- g = group_allocate(parent, cs, UNLANG_TYPE_TRANSACTION);
- if (!g) return NULL;
-
- c = unlang_group_to_generic(g);
- c->debug_name = c->name = cf_section_name1(cs);
-
- if (!compile_children(g, &unlang_ctx2, false)) return NULL;
-
- /*
- * The default for a failed transaction is to continue on
- * failure.
- */
- if (!c->actions.actions[RLM_MODULE_FAIL]) c->actions.actions[RLM_MODULE_FAIL] = 1;
- if (!c->actions.actions[RLM_MODULE_INVALID]) c->actions.actions[RLM_MODULE_INVALID] = 1;
- if (!c->actions.actions[RLM_MODULE_DISALLOW]) c->actions.actions[RLM_MODULE_DISALLOW] = 1;
-
- compile_action_defaults(c, unlang_ctx);
-
- return c;
-}
-
-static unlang_t *compile_try(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- unlang_group_t *g;
- unlang_t *c;
- CONF_ITEM *next;
-
- /*
- * The transaction is empty, ignore it.
- */
- if (!cf_item_next(cs, NULL)) {
- cf_log_err(cs, "'try' sections cannot be empty");
- print_url:
- cf_log_err(ci, DOC_KEYWORD_REF(try));
- return NULL;
- }
-
- if (cf_section_name2(cs) != NULL) {
- cf_log_err(cs, "Unexpected argument to 'try' section");
- goto print_url;
- }
-
- next = cf_item_next(cf_parent(cs), ci);
- while (next && cf_item_is_data(next)) next = cf_item_next(cf_parent(cs), next);
-
- if (!next || !cf_item_is_section(next) ||
- (strcmp(cf_section_name1(cf_item_to_section(next)), "catch") != 0)) {
- cf_log_err(cs, "'try' sections must be followed by a 'catch'");
- goto print_url;
- }
-
- g = group_allocate(parent, cs, UNLANG_TYPE_TRY);
- if (!g) return NULL;
-
- c = unlang_group_to_generic(g);
- c->debug_name = c->name = cf_section_name1(cs);
-
- return compile_children(g, unlang_ctx, true);
-}
-
-static int catch_argv(CONF_SECTION *cs, unlang_catch_t *ca, char const *name)
-{
- int rcode;
-
- rcode = fr_table_value_by_str(mod_rcode_table, name, -1);
- if (rcode < 0) {
- cf_log_err(cs, "Unknown rcode '%s'.", name);
- return -1;
- }
-
- if (ca->catching[rcode]) {
- cf_log_err(cs, "Duplicate rcode '%s'.", name);
- return -1;
- }
-
- ca->catching[rcode] = true;
-
- return 0;
-}
-
-static unlang_t *compile_catch(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- unlang_group_t *g;
- unlang_t *c;
- unlang_catch_t *ca;
- CONF_ITEM *prev;
- char const *name;
-
- prev = cf_item_prev(cf_parent(ci), ci);
- while (prev && cf_item_is_data(prev)) prev = cf_item_prev(cf_parent(ci), prev);
-
- if (!prev || !cf_item_is_section(prev)) {
- fail:
- cf_log_err(cs, "Found 'catch' section with no previous 'try'");
- cf_log_err(ci, DOC_KEYWORD_REF(catch));
- return NULL;
- }
-
- name = cf_section_name1(cf_item_to_section(prev));
- fr_assert(name != NULL);
-
- if ((strcmp(name, "try") != 0) && (strcmp(name, "catch") != 0)) {
- /*
- * The previous thing has to be a section. And it has to
- * be either a "try" or a "catch".
- */
- goto fail;
- }
-
- g = group_allocate(parent, cs, UNLANG_TYPE_CATCH);
- if (!g) return NULL;
-
- c = unlang_group_to_generic(g);
-
- /*
- * Want to log what we caught
- */
- c->debug_name = c->name = talloc_typed_asprintf(c, "%s %s", cf_section_name1(cs), cf_section_name2(cs));
-
- ca = unlang_group_to_catch(g);
- if (!cf_section_name2(cs)) {
- /*
- * No arg2: catch errors
- */
- ca->catching[RLM_MODULE_REJECT] = true;
- ca->catching[RLM_MODULE_FAIL] = true;
- ca->catching[RLM_MODULE_INVALID] = true;
- ca->catching[RLM_MODULE_DISALLOW] = true;
-
- } else {
- int i;
-
- name = cf_section_name2(cs);
-
- if (catch_argv(cs, ca, name) < 0) {
- talloc_free(c);
- return NULL;
- }
-
- for (i = 0; (name = cf_section_argv(cs, i)) != NULL; i++) {
- if (catch_argv(cs, ca, name) < 0) {
- talloc_free(c);
- return NULL;
- }
- }
- }
-
- /*
- * @todo - Else parse and limit the things we catch
- */
-
- return compile_children(g, unlang_ctx, true);
-}
-
-
-static int8_t case_cmp(void const *one, void const *two)
-{
- unlang_case_t const *a = (unlang_case_t const *) one; /* may not be talloc'd! See switch.c */
- unlang_case_t const *b = (unlang_case_t const *) two; /* may not be talloc'd! */
-
- return fr_value_box_cmp(tmpl_value(a->vpt), tmpl_value(b->vpt));
-}
-
-static uint32_t case_hash(void const *data)
-{
- unlang_case_t const *a = (unlang_case_t const *) data; /* may not be talloc'd! */
-
- return fr_value_box_hash(tmpl_value(a->vpt));
-}
-
-static int case_to_key(uint8_t **out, size_t *outlen, void const *data)
-{
- unlang_case_t const *a = (unlang_case_t const *) data; /* may not be talloc'd! */
-
- return fr_value_box_to_key(out, outlen, tmpl_value(a->vpt));
-}
-
-static unlang_t *compile_case(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci);
-
-static unlang_t *compile_switch(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- CONF_ITEM *subci;
- fr_token_t token;
- char const *name1, *name2;
- char const *type_name;
-
- unlang_group_t *g;
- unlang_switch_t *gext;
-
- unlang_t *c;
- ssize_t slen;
-
- tmpl_rules_t t_rules;
-
- fr_type_t type;
- fr_htrie_type_t htype;
-
- /*
- * We allow unknown attributes here.
- */
- t_rules = *(unlang_ctx->rules);
- t_rules.attr.allow_unknown = true;
- RULES_VERIFY(&t_rules);
-
- name2 = cf_section_name2(cs);
- if (!name2) {
- cf_log_err(cs, "You must specify a variable to switch over for 'switch'");
- print_url:
- cf_log_err(ci, DOC_KEYWORD_REF(switch));
- return NULL;
- }
-
- if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
-
- g = group_allocate(parent, cs, UNLANG_TYPE_SWITCH);
- if (!g) return NULL;
-
- gext = unlang_group_to_switch(g);
-
- /*
- * Create the template. All attributes and xlats are
- * defined by now.
- *
- * The 'case' statements need g->vpt filled out to ensure
- * that the data types match.
- */
- token = cf_section_name2_quote(cs);
-
- if ((token == T_BARE_WORD) && (name2[0] != '%')) {
- slen = tmpl_afrom_attr_substr(gext, NULL, &gext->vpt,
- &FR_SBUFF_IN(name2, strlen(name2)),
- NULL,
- &t_rules);
- } else {
- slen = tmpl_afrom_substr(gext, &gext->vpt,
- &FR_SBUFF_IN(name2, strlen(name2)),
- token,
- NULL,
- &t_rules);
- }
- if (!gext->vpt) {
- cf_canonicalize_error(cs, slen, "Failed parsing argument to 'switch'", name2);
- talloc_free(g);
- return NULL;
- }
-
- c = unlang_group_to_generic(g);
- c->name = "switch";
- c->debug_name = talloc_typed_asprintf(c, "switch %s", name2);
-
- /*
- * Fixup the template before compiling the children.
- * This is so that compile_case() can do attribute type
- * checks / casts against us.
- */
- if (!pass2_fixup_tmpl(g, &gext->vpt, cf_section_to_item(cs), unlang_ctx->rules->attr.dict_def)) {
- talloc_free(g);
- return NULL;
- }
-
- if (tmpl_is_list(gext->vpt)) {
- cf_log_err(cs, "Cannot use list for 'switch' statement");
- error:
- talloc_free(g);
- goto print_url;
- }
-
- if (tmpl_contains_regex(gext->vpt)) {
- cf_log_err(cs, "Cannot use regular expression for 'switch' statement");
- goto error;
- }
-
- if (tmpl_is_data(gext->vpt)) {
- cf_log_err(cs, "Cannot use constant data for 'switch' statement");
- goto error;
- }
-
- if (tmpl_is_xlat(gext->vpt)) {
- xlat_exp_head_t *xlat = tmpl_xlat(gext->vpt);
-
- if (xlat->flags.constant || xlat->flags.pure) {
- cf_log_err(cs, "Cannot use constant data for 'switch' statement");
- goto error;
- }
- }
-
-
- if (tmpl_needs_resolving(gext->vpt)) {
- cf_log_err(cs, "Cannot resolve key for 'switch' statement");
- goto error;
- }
-
- type_name = cf_section_argv(cs, 0); /* AFTER name1, name2 */
- if (type_name) {
- type = fr_table_value_by_str(fr_type_table, type_name, FR_TYPE_NULL);
-
- /*
- * Should have been caught in cf_file.c, process_switch()
- */
- fr_assert(type != FR_TYPE_NULL);
- fr_assert(fr_type_is_leaf(type));
-
- do_cast:
- if (tmpl_cast_set(gext->vpt, type) < 0) {
- cf_log_perr(cs, "Failed setting cast type");
- goto error;
- }
-
- } else {
- /*
- * Get the return type of the tmpl. If we don't know,
- * mash it all to string.
- */
- type = tmpl_data_type(gext->vpt);
- if ((type == FR_TYPE_NULL) || (type == FR_TYPE_VOID)) {
- type = FR_TYPE_STRING;
- goto do_cast;
- }
- }
-
- htype = fr_htrie_hint(type);
- if (htype == FR_HTRIE_INVALID) {
- cf_log_err(cs, "Invalid data type '%s' used for 'switch' statement",
- fr_type_to_str(type));
- goto error;
- }
-
- gext->ht = fr_htrie_alloc(gext, htype,
- (fr_hash_t) case_hash,
- (fr_cmp_t) case_cmp,
- (fr_trie_key_t) case_to_key,
- NULL);
- if (!gext->ht) {
- cf_log_err(cs, "Failed initializing internal data structures");
- talloc_free(g);
- return NULL;
- }
-
- /*
- * Walk through the children of the switch section,
- * ensuring that they're all 'case' statements, and then compiling them.
- */
- for (subci = cf_item_next(cs, NULL);
- subci != NULL;
- subci = cf_item_next(cs, subci)) {
- CONF_SECTION *subcs;
- unlang_t *single;
- unlang_case_t *case_gext;
-
- if (!cf_item_is_section(subci)) {
- if (!cf_item_is_pair(subci)) continue;
-
- cf_log_err(subci, "\"switch\" sections can only have \"case\" subsections");
- goto error;
- }
-
- subcs = cf_item_to_section(subci); /* can't return NULL */
- name1 = cf_section_name1(subcs);
-
- if (strcmp(name1, "case") != 0) {
- /*
- * We finally support "default" sections for "switch".
- */
- if (strcmp(name1, "default") == 0) {
- if (cf_section_name2(subcs) != 0) {
- cf_log_err(subci, "\"default\" sections cannot have a match argument");
- goto error;
- }
- goto handle_default;
- }
-
- cf_log_err(subci, "\"switch\" sections can only have \"case\" subsections");
- goto error;
- }
-
- name2 = cf_section_name2(subcs);
- if (!name2) {
- handle_default:
- if (gext->default_case) {
- cf_log_err(subci, "Cannot have two 'default' case statements");
- goto error;
- }
- }
-
- /*
- * Compile the subsection.
- */
- single = compile_case(c, unlang_ctx, subci);
- if (!single) goto error;
-
- fr_assert(single->type == UNLANG_TYPE_CASE);
-
- /*
- * Remember the "default" section, and insert the
- * non-default "case" into the htrie.
- */
- case_gext = unlang_group_to_case(unlang_generic_to_group(single));
- if (!case_gext->vpt) {
- gext->default_case = single;
-
- } else if (!fr_htrie_insert(gext->ht, single)) {
- single = fr_htrie_find(gext->ht, single);
-
- /*
- * @todo - look up the key and get the previous one?
- */
- cf_log_err(subci, "Failed inserting 'case' statement. Is there a duplicate?");
-
- if (single) cf_log_err(unlang_generic_to_group(single)->cs, "Duplicate may be here.");
-
- goto error;
- }
-
- *g->tail = single;
- g->tail = &single->next;
- g->num_children++;
- }
-
- compile_action_defaults(c, unlang_ctx);
-
- return c;
-}
-
-static unlang_t *compile_case(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- int i;
- char const *name2;
- unlang_t *c;
- unlang_group_t *case_g;
- unlang_case_t *case_gext;
- tmpl_t *vpt = NULL;
- tmpl_rules_t t_rules;
-
- /*
- * We allow unknown attributes here.
- */
- t_rules = *(unlang_ctx->rules);
- t_rules.attr.allow_unknown = true;
- RULES_VERIFY(&t_rules);
-
- if (!parent || (parent->type != UNLANG_TYPE_SWITCH)) {
- cf_log_err(cs, "\"case\" statements may only appear within a \"switch\" section");
- cf_log_err(ci, DOC_KEYWORD_REF(case));
- return NULL;
- }
-
- /*
- * case THING means "match THING"
- * case means "match anything"
- */
- name2 = cf_section_name2(cs);
- if (name2) {
- ssize_t slen;
- fr_token_t quote;
- unlang_group_t *switch_g;
- unlang_switch_t *switch_gext;
-
- switch_g = unlang_generic_to_group(parent);
- switch_gext = unlang_group_to_switch(switch_g);
-
- fr_assert(switch_gext->vpt != NULL);
-
- /*
- * We need to cast case values to match
- * what we're switching over, otherwise
- * integers of different widths won't
- * match.
- */
- t_rules.cast = tmpl_expanded_type(switch_gext->vpt);
-
- /*
- * Need to pass the attribute from switch
- * to tmpl rules so we can convert the
- * case string to an integer value.
- */
- if (tmpl_is_attr(switch_gext->vpt)) {
- fr_dict_attr_t const *da = tmpl_attr_tail_da(switch_gext->vpt);
- if (da->flags.has_value) t_rules.enumv = da;
- }
-
- quote = cf_section_name2_quote(cs);
-
- slen = tmpl_afrom_substr(cs, &vpt,
- &FR_SBUFF_IN(name2, strlen(name2)),
- quote,
- NULL,
- &t_rules);
- if (!vpt) {
- cf_canonicalize_error(cs, slen, "Failed parsing argument to 'case'", name2);
- return NULL;
- }
-
- /*
- * Bare word strings are attribute references
- */
- if (tmpl_is_attr(vpt) || tmpl_is_attr_unresolved(vpt)) {
- fail_attr:
- cf_log_err(cs, "arguments to 'case' statements MUST NOT be attribute references.");
- goto fail;
- }
-
- if (!tmpl_is_data(vpt) || tmpl_is_data_unresolved(vpt)) {
- cf_log_err(cs, "arguments to 'case' statements MUST be static data.");
- fail:
- talloc_free(vpt);
- return NULL;
- }
-
- /*
- * References to unresolved attributes are forbidden. They are no longer "bare word
- * strings".
- */
- if ((quote == T_BARE_WORD) && (tmpl_value_type(vpt) == FR_TYPE_STRING)) {
- goto fail_attr;
- }
-
- } /* else it's a default 'case' statement */
-
- /*
- * If we were asked to match something, then we MUST
- * match it, even if the section is empty. Otherwise we
- * will silently skip the match, and then fall through to
- * the "default" statement.
- */
- c = compile_section(parent, unlang_ctx, cs, UNLANG_TYPE_CASE);
- if (!c) {
- talloc_free(vpt);
- return NULL;
- }
-
- case_g = unlang_generic_to_group(c);
- case_gext = unlang_group_to_case(case_g);
- case_gext->vpt = talloc_steal(case_gext, vpt);
-
- /*
- * Set all of it's codes to return, so that
- * when we pick a 'case' statement, we don't
- * fall through to processing the next one.
- */
- for (i = 0; i < RLM_MODULE_NUMCODES; i++) c->actions.actions[i] = MOD_ACTION_RETURN;
-
- return c;
-}
-
-static unlang_t *compile_timeout(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- char const *name2;
- unlang_t *c;
- unlang_group_t *g;
- unlang_timeout_t *gext;
- fr_time_delta_t timeout = fr_time_delta_from_sec(0);
- tmpl_t *vpt = NULL;
- fr_token_t token;
-
- /*
- * Timeout <time ref>
- */
- name2 = cf_section_name2(cs);
- if (!name2) {
- cf_log_err(cs, "You must specify a time value for 'timeout'");
- print_url:
- cf_log_err(ci, DOC_KEYWORD_REF(timeout));
- return NULL;
- }
-
- if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
-
- g = group_allocate(parent, cs, UNLANG_TYPE_TIMEOUT);
- if (!g) return NULL;
-
- gext = unlang_group_to_timeout(g);
-
- token = cf_section_name2_quote(cs);
-
- if ((token == T_BARE_WORD) && isdigit((uint8_t) *name2)) {
- if (fr_time_delta_from_str(&timeout, name2, strlen(name2), FR_TIME_RES_SEC) < 0) {
- cf_log_err(cs, "Failed parsing time delta %s - %s",
- name2, fr_strerror());
- return NULL;
- }
- } else {
- ssize_t slen;
- tmpl_rules_t t_rules;
-
- /*
- * We don't allow unknown attributes here.
- */
- t_rules = *(unlang_ctx->rules);
- t_rules.attr.allow_unknown = false;
- RULES_VERIFY(&t_rules);
-
- slen = tmpl_afrom_substr(gext, &vpt,
- &FR_SBUFF_IN(name2, strlen(name2)),
- token,
- NULL,
- &t_rules);
- if (!vpt) {
- cf_canonicalize_error(cs, slen, "Failed parsing argument to 'timeout'", name2);
- talloc_free(g);
- return NULL;
- }
-
- /*
- * Fixup the tmpl so that we know it's somewhat sane.
- */
- if (!pass2_fixup_tmpl(gext, &vpt, cf_section_to_item(cs), unlang_ctx->rules->attr.dict_def)) {
- talloc_free(g);
- return NULL;
- }
-
- if (tmpl_is_list(vpt)) {
- cf_log_err(cs, "Cannot use list as argument for 'timeout' statement");
- error:
- talloc_free(g);
- goto print_url;
- }
-
- if (tmpl_contains_regex(vpt)) {
- cf_log_err(cs, "Cannot use regular expression as argument for 'timeout' statement");
- goto error;
- }
-
- /*
- * Attribute or data MUST be cast to TIME_DELTA.
- */
- if (tmpl_cast_set(vpt, FR_TYPE_TIME_DELTA) < 0) {
- cf_log_perr(cs, "Failed setting cast type");
- goto error;
- }
- }
-
- /*
- * Compile the contents of a "timeout".
- */
- c = compile_section(parent, unlang_ctx, cs, UNLANG_TYPE_TIMEOUT);
- if (!c) return NULL;
-
- g = unlang_generic_to_group(c);
- gext = unlang_group_to_timeout(g);
- gext->timeout = timeout;
- gext->vpt = vpt;
-
- return c;
-}
-
-static unlang_t *compile_limit(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- char const *name2;
- unlang_t *c;
- unlang_group_t *g;
- unlang_limit_t *gext;
- tmpl_t *vpt = NULL;
- uint32_t limit = 0;
- fr_token_t token;
- ssize_t slen;
- tmpl_rules_t t_rules;
-
- /*
- * limit <number>
- */
- name2 = cf_section_name2(cs);
- if (!name2) {
- cf_log_err(cs, "You must specify a value for 'limit'");
- print_url:
- cf_log_err(ci, DOC_KEYWORD_REF(limit));
- return NULL;
- }
-
- if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
-
- g = group_allocate(parent, cs, UNLANG_TYPE_LIMIT);
- if (!g) return NULL;
-
- gext = unlang_group_to_limit(g);
-
- token = cf_section_name2_quote(cs);
-
- /*
- * We don't allow unknown attributes here.
- */
- t_rules = *(unlang_ctx->rules);
- t_rules.attr.allow_unknown = false;
- RULES_VERIFY(&t_rules);
-
- slen = tmpl_afrom_substr(gext, &vpt,
- &FR_SBUFF_IN(name2, strlen(name2)),
- token,
- NULL,
- &t_rules);
- if (!vpt) {
- syntax_error:
- cf_canonicalize_error(cs, slen, "Failed parsing argument to 'foreach'", name2);
- talloc_free(g);
- return NULL;
- }
-
- /*
- * Fixup the tmpl so that we know it's somewhat sane.
- */
- if (!pass2_fixup_tmpl(gext, &vpt, cf_section_to_item(cs), unlang_ctx->rules->attr.dict_def)) {
- talloc_free(g);
- return NULL;
- }
-
- if (tmpl_is_list(vpt)) {
- cf_log_err(cs, "Cannot use list as argument for 'limit' statement");
- error:
- talloc_free(g);
- goto print_url;
- }
-
- if (tmpl_contains_regex(vpt)) {
- cf_log_err(cs, "Cannot use regular expression as argument for 'limit' statement");
- goto error;
- }
-
- if (tmpl_is_data(vpt) && (token == T_BARE_WORD)) {
- fr_value_box_t box;
-
- if (fr_value_box_cast(NULL, &box, FR_TYPE_UINT32, NULL, tmpl_value(vpt)) < 0) goto syntax_error;
-
- limit = box.vb_uint32;
-
- } else {
- /*
- * Attribute or data MUST be cast to a 32-bit unsigned number.
- */
- if (tmpl_cast_set(vpt, FR_TYPE_UINT32) < 0) {
- cf_log_perr(cs, "Failed setting cast type");
- goto syntax_error;
- }
- }
-
- /*
- * Compile the contents of a "limit".
- */
- c = compile_section(parent, unlang_ctx, cs, UNLANG_TYPE_LIMIT);
- if (!c) return NULL;
-
- g = unlang_generic_to_group(c);
- gext = unlang_group_to_limit(g);
- gext->limit = limit;
- gext->vpt = vpt;
-
- return c;
-}
-
-static unlang_t *compile_foreach(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- fr_token_t token;
- char const *name2;
- char const *type_name, *variable_name;
- fr_type_t type;
- unlang_t *c;
-
- fr_type_t key_type;
- char const *key_name;
-
- unlang_group_t *g;
- unlang_foreach_t *gext;
-
- ssize_t slen;
- tmpl_t *vpt;
- fr_dict_attr_t const *da = NULL;
-
- tmpl_rules_t t_rules;
- unlang_compile_t unlang_ctx2;
-
- /*
- * Ignore empty "foreach" blocks, and don't even sanity check their arguments.
- */
- if (!cf_item_next(cs, NULL)) {
- return UNLANG_IGNORE;
- }
-
- /*
- * We allow unknown attributes here.
- */
- t_rules = *(unlang_ctx->rules);
- t_rules.attr.allow_unknown = true;
- t_rules.attr.allow_wildcard = true;
- RULES_VERIFY(&t_rules);
-
- name2 = cf_section_name2(cs);
- fr_assert(name2 != NULL); /* checked in cf_file.c */
-
- /*
- * Allocate a group for the "foreach" block.
- */
- g = group_allocate(parent, cs, UNLANG_TYPE_FOREACH);
- if (!g) return NULL;
-
- c = unlang_group_to_generic(g);
-
- /*
- * Create the template. If we fail, AND it's a bare word
- * with &Foo-Bar, it MAY be an attribute defined by a
- * module. Allow it for now. The pass2 checks below
- * will fix it up.
- */
- token = cf_section_name2_quote(cs);
- if (token != T_BARE_WORD) {
- cf_log_err(cs, "Data being looped over in 'foreach' must be an attribute reference or dynamic expansion, not a string");
- print_ref:
- cf_log_err(ci, DOC_KEYWORD_REF(foreach));
- error:
- talloc_free(g);
- return NULL;
- }
+/** Compile a variable definition.
+ *
+ * Definitions which are adjacent to one another are automatically merged
+ * into one larger variable definition.
+ */
+static int compile_variable(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_PAIR *cp, tmpl_rules_t *t_rules)
+{
+ unlang_variable_t *var;
+ fr_type_t type;
+ char const *attr, *value;
+ unlang_group_t *group;
- slen = tmpl_afrom_substr(g, &vpt,
- &FR_SBUFF_IN(name2, strlen(name2)),
- token,
- NULL,
- &t_rules);
- if (!vpt) {
- cf_canonicalize_error(cs, slen, "Failed parsing argument to 'foreach'", name2);
- goto error;
- }
+ fr_assert(debug_braces(parent->type));
/*
- * If we don't have a negative return code, we must have a vpt
- * (mostly to quiet coverity).
+ * Enforce locations for local variables.
*/
- fr_assert(vpt);
-
- if (tmpl_is_attr(vpt)) {
- if (tmpl_attr_tail_num(vpt) == NUM_UNSPEC) {
- cf_log_warn(cs, "Attribute reference should be updated to use %s[*]", vpt->name);
- tmpl_attr_rewrite_leaf_num(vpt, NUM_ALL);
- }
-
- if (tmpl_attr_tail_num(vpt) != NUM_ALL) {
- cf_log_err(cs, "Attribute references must be of the form ...%s[*]", tmpl_attr_tail_da(vpt)->name);
- goto print_ref;
- }
+ switch (parent->type) {
+ case UNLANG_TYPE_CASE:
+ case UNLANG_TYPE_ELSE:
+ case UNLANG_TYPE_ELSIF:
+ case UNLANG_TYPE_FOREACH:
+ case UNLANG_TYPE_GROUP:
+ case UNLANG_TYPE_IF:
+ case UNLANG_TYPE_TIMEOUT:
+ case UNLANG_TYPE_LIMIT:
+ case UNLANG_TYPE_POLICY:
+ case UNLANG_TYPE_REDUNDANT:
+ case UNLANG_TYPE_SUBREQUEST:
+ case UNLANG_TYPE_LOAD_BALANCE:
+ case UNLANG_TYPE_REDUNDANT_LOAD_BALANCE:
+ break;
- } else if (!tmpl_contains_xlat(vpt)) {
- cf_log_err(cs, "Invalid content in 'foreach (...)', it must be an attribute reference or a dynamic expansion");
- goto print_ref;
+ default:
+ cf_log_err(cp, "Local variables cannot be used here");
+ return -1;
}
- gext = unlang_group_to_foreach(g);
- gext->vpt = vpt;
-
- c->name = "foreach";
- MEM(c->debug_name = talloc_typed_asprintf(c, "foreach %s", name2));
-
- /*
- * Copy over the compilation context. This is mostly
- * just to ensure that retry is handled correctly.
- * i.e. reset.
- */
- compile_copy_context(&unlang_ctx2, unlang_ctx);
-
/*
- * Then over-write the new compilation context.
- */
- unlang_ctx2.section_name1 = "foreach";
- unlang_ctx2.section_name2 = name2;
- unlang_ctx2.rules = &t_rules;
- t_rules.parent = unlang_ctx->rules;
-
- /*
- * If we have "type name", then define a local variable of that name.
+ * The variables exist in the parent block.
*/
- type_name = cf_section_argv(cs, 0); /* AFTER name1, name2 */
-
- key_name = cf_section_argv(cs, 2);
- if (key_name) {
- key_type = fr_table_value_by_str(fr_type_table, key_name, FR_TYPE_VOID);
- } else {
- key_type = FR_TYPE_VOID;
- }
- key_name = cf_section_argv(cs, 3);
-
- if (tmpl_is_xlat(vpt)) {
- if (!type_name) {
- cf_log_err(cs, "Dynamic expansions MUST specify a data type for the variable");
- goto print_ref;
- }
-
- type = fr_table_value_by_str(fr_type_table, type_name, FR_TYPE_VOID);
-
- /*
- * No data type was specified, see if we can get one from the function.
- */
- if (type == FR_TYPE_NULL) {
- type = xlat_data_type(tmpl_xlat(vpt));
- if (fr_type_is_leaf(type)) goto get_name;
-
- cf_log_err(cs, "Unable to determine return data type from dynamic expansion");
- goto print_ref;
- }
-
- if (!fr_type_is_leaf(type)) {
- cf_log_err(cs, "Dynamic expansions MUST specify a non-structural data type for the variable");
- goto print_ref;
- }
-
- if ((key_type != FR_TYPE_VOID) && !fr_type_is_numeric(key_type)) {
- cf_log_err(cs, "Invalid data type '%s' for 'key' variable - it should be numeric", fr_type_to_str(key_type));
- goto print_ref;
- }
+ group = unlang_generic_to_group(parent);
+ if (group->variables) {
+ var = group->variables;
- goto get_name;
} else {
- fr_assert(tmpl_is_attr(vpt));
-
- if ((key_type != FR_TYPE_VOID) && (key_type != FR_TYPE_STRING) && (key_type != FR_TYPE_UINT32)) {
- cf_log_err(cs, "Invalid data type '%s' for 'key' variable - it should be 'string' or 'uint32'", fr_type_to_str(key_type));
- goto print_ref;
- }
- }
-
- if (type_name) {
- unlang_variable_t *var;
-
- type = fr_table_value_by_str(fr_type_table, type_name, FR_TYPE_VOID);
- fr_assert(type != FR_TYPE_VOID);
-
- /*
- * foreach string foo (&tlv-thing.[*]) { ... }
- */
- if (tmpl_attr_tail_is_unspecified(vpt)) {
- goto get_name;
- }
-
- da = tmpl_attr_tail_da(vpt);
-
- if (type == FR_TYPE_NULL) {
- type = da->type;
-
- } else if (fr_type_is_leaf(type) != fr_type_is_leaf(da->type)) {
- incompatible:
- cf_log_err(cs, "Incompatible data types in foreach variable (%s), and reference %s being looped over (%s)",
- fr_type_to_str(type), da->name, fr_type_to_str(da->type));
- goto print_ref;
-
- } else if (fr_type_is_structural(type) && (type != da->type)) {
- goto incompatible;
- }
-
- get_name:
- variable_name = cf_section_argv(cs, 1);
-
- /*
- * Define the local variables.
- */
- g->variables = var = talloc_zero(g, unlang_variable_t);
- if (!var) goto error;
+ group->variables = var = talloc_zero(parent, unlang_variable_t);
+ if (!var) return -1;
var->dict = fr_dict_protocol_alloc(unlang_ctx->rules->attr.dict_def);
- if (!var->dict) goto error;
-
+ if (!var->dict) {
+ talloc_free(var);
+ return -1;
+ }
var->root = fr_dict_root(var->dict);
var->max_attr = 1;
- if (define_local_variable(cf_section_to_item(cs), var, &t_rules, type, variable_name, da) < 0) goto error;
-
- t_rules.attr.dict_def = var->dict;
- t_rules.attr.namespace = NULL;
-
/*
- * And ensure we have the key.
+ * Initialize the new rules, and point them to the parent rules.
+ *
+ * Then replace the parse rules with our rules, and our dictionary.
*/
- gext->value = fr_dict_attr_by_name(NULL, var->root, variable_name);
- fr_assert(gext->value != NULL);
+ *t_rules = *unlang_ctx->rules;
+ t_rules->parent = unlang_ctx->rules;
- /*
- * Define the local key variable. Note that we don't copy any children.
- */
- if (key_type != FR_TYPE_VOID) {
- if (define_local_variable(cf_section_to_item(cs), var, &t_rules, key_type, key_name, NULL) < 0) goto error;
+ t_rules->attr.dict_def = var->dict;
+ t_rules->attr.namespace = NULL;
- gext->key = fr_dict_attr_by_name(NULL, var->root, key_name);
- fr_assert(gext->key != NULL);
- }
+ unlang_ctx->rules = t_rules;
}
- if (!compile_children(g, &unlang_ctx2, true)) return NULL;
-
- return c;
-}
-
-static unlang_t *compile_break(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- unlang_t *unlang;
-
- for (unlang = parent; unlang != NULL; unlang = unlang->parent) {
- /*
- * "break" doesn't go past a return point.
- */
- if ((unlang_ops[unlang->type].flag & UNLANG_OP_FLAG_RETURN_POINT) != 0) goto error;
-
- if ((unlang_ops[unlang->type].flag & UNLANG_OP_FLAG_BREAK_POINT) != 0) break;
- }
+ attr = cf_pair_attr(cp); /* data type */
+ value = cf_pair_value(cp); /* variable name */
- if (!unlang) {
- error:
- cf_log_err(ci, "Invalid location for 'break' - it can only be used inside 'foreach' or 'switch'");
- cf_log_err(ci, DOC_KEYWORD_REF(break));
- return NULL;
+ type = fr_table_value_by_str(fr_type_table, attr, FR_TYPE_NULL);
+ if (type == FR_TYPE_NULL) {
+invalid_type:
+ cf_log_err(cp, "Invalid data type '%s'", attr);
+ return -1;
}
- parent->closed = true;
+ /*
+ * Leaf and group are OK. TLV, Vendor, Struct, VSA, etc. are not.
+ */
+ if (!(fr_type_is_leaf(type) || (type == FR_TYPE_GROUP))) goto invalid_type;
- return compile_empty(parent, unlang_ctx, NULL, UNLANG_TYPE_BREAK);
+ return unlang_define_local_variable(cf_pair_to_item(cp), var, t_rules, type, value, NULL);
}
-static unlang_t *compile_continue(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
+/*
+ * Compile action && rcode for later use.
+ */
+static int compile_action_pair(unlang_mod_actions_t *actions, CONF_PAIR *cp)
{
- unlang_t *unlang;
-
- for (unlang = parent; unlang != NULL; unlang = unlang->parent) {
- /*
- * "continue" doesn't go past a return point.
- */
- if ((unlang_ops[unlang->type].flag & UNLANG_OP_FLAG_RETURN_POINT) != 0) goto error;
-
- if (unlang->type == UNLANG_TYPE_FOREACH) break;
- }
-
- if (!unlang) {
- error:
- cf_log_err(ci, "Invalid location for 'continue' - it can only be used inside 'foreach'");
- cf_log_err(ci, DOC_KEYWORD_REF(break));
- return NULL;
- }
-
- parent->closed = true;
-
- return compile_empty(parent, unlang_ctx, NULL, UNLANG_TYPE_CONTINUE);
-}
+ int action;
+ char const *attr, *value;
+ attr = cf_pair_attr(cp);
+ value = cf_pair_value(cp);
+ if (!value) return 0;
-static unlang_t *compile_detach(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- unlang_t *subrequest;
+ if (!strcasecmp(value, "return"))
+ action = MOD_ACTION_RETURN;
- for (subrequest = parent;
- subrequest != NULL;
- subrequest = subrequest->parent) {
- if (subrequest->type == UNLANG_TYPE_SUBREQUEST) break;
- }
+ else if (!strcasecmp(value, "break"))
+ action = MOD_ACTION_RETURN;
- if (!subrequest) {
- cf_log_err(ci, "'detach' can only be used inside of a 'subrequest' section.");
- cf_log_err(ci, DOC_KEYWORD_REF(detach));
- return NULL;
- }
+ else if (!strcasecmp(value, "reject"))
+ action = MOD_ACTION_REJECT;
- /*
- * This really overloads the functionality of
- * cf_item_next().
- */
- if ((parent == subrequest) && !cf_item_next(ci, ci)) {
- cf_log_err(ci, "'detach' cannot be used as the last entry in a section, as there is nothing more to do");
- return NULL;
- }
+ else if (!strcasecmp(value, "retry"))
+ action = MOD_ACTION_RETRY;
- return compile_empty(parent, unlang_ctx, NULL, UNLANG_TYPE_DETACH);
-}
+ else if (strspn(value, "0123456789")==strlen(value)) {
+ action = atoi(value);
-static unlang_t *compile_return(unlang_t *parent, unlang_compile_t *unlang_ctx, UNUSED CONF_ITEM const *ci)
-{
- /*
- * These types are all parallel, and therefore can have a "return" in them.
- */
- switch (parent->type) {
- case UNLANG_TYPE_LOAD_BALANCE:
- case UNLANG_TYPE_REDUNDANT_LOAD_BALANCE:
- case UNLANG_TYPE_PARALLEL:
- break;
+ if (!action || (action > MOD_PRIORITY_MAX)) {
+ cf_log_err(cp, "Priorities MUST be between 1 and 64.");
+ return 0;
+ }
- default:
- parent->closed = true;
- break;
+ } else {
+ cf_log_err(cp, "Unknown action '%s'.\n",
+ value);
+ return 0;
}
- return compile_empty(parent, unlang_ctx, NULL, UNLANG_TYPE_RETURN);
-}
+ if (strcasecmp(attr, "default") != 0) {
+ int rcode;
-static unlang_t *compile_tmpl(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM *ci)
-{
- CONF_PAIR *cp = cf_item_to_pair(ci);
- unlang_t *c;
- unlang_tmpl_t *ut;
- ssize_t slen;
- char const *p = cf_pair_attr(cp);
- tmpl_t *vpt;
+ rcode = fr_table_value_by_str(mod_rcode_table, attr, -1);
+ if (rcode < 0) {
+ cf_log_err(cp,
+ "Unknown module rcode '%s'.",
+ attr);
+ return 0;
+ }
+ actions->actions[rcode] = action;
- MEM(ut = talloc_zero(parent, unlang_tmpl_t));
- c = unlang_tmpl_to_generic(ut);
- c->parent = parent;
- c->next = NULL;
- c->name = p;
- c->debug_name = c->name;
- c->type = UNLANG_TYPE_TMPL;
- c->ci = CF_TO_ITEM(cp);
+ } else { /* set all unset values to the default */
+ int i;
- RULES_VERIFY(unlang_ctx->rules);
- slen = tmpl_afrom_substr(ut, &vpt,
- &FR_SBUFF_IN(p, talloc_array_length(p) - 1),
- cf_pair_attr_quote(cp),
- NULL,
- unlang_ctx->rules);
- if (!vpt) {
- cf_canonicalize_error(cp, slen, "Failed parsing expansion", p);
- talloc_free(ut);
- return NULL;
+ for (i = 0; i < RLM_MODULE_NUMCODES; i++) {
+ if (!actions->actions[i]) actions->actions[i] = action;
+ }
}
- ut->tmpl = vpt; /* const issues */
- compile_action_defaults(c, unlang_ctx);
- return c;
+ return 1;
}
-static const fr_sbuff_term_t if_terminals = FR_SBUFF_TERMS(
- L(""),
- L("{"),
-);
-
-static unlang_t *compile_if_subsection(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs, unlang_type_t type)
+static bool compile_retry_section(unlang_mod_actions_t *actions, CONF_ITEM *ci)
{
- unlang_t *c;
-
- unlang_group_t *g;
- unlang_cond_t *gext;
- CONF_ITEM *ci;
-
- xlat_exp_head_t *head = NULL;
- bool is_truthy = false, value = false;
- xlat_res_rules_t xr_rules = {
- .tr_rules = &(tmpl_res_rules_t) {
- .dict_def = unlang_ctx->rules->attr.dict_def,
- },
- };
+ CONF_ITEM *csi;
+ CONF_SECTION *cs;
- if (!cf_section_name2(cs)) {
- cf_log_err(cs, "'%s' without condition", unlang_ops[type].name);
- return NULL;
- }
+ cs = cf_item_to_section(ci);
+ for (csi=cf_item_next(cs, NULL);
+ csi != NULL;
+ csi=cf_item_next(cs, csi)) {
+ CONF_PAIR *cp;
+ char const *name, *value;
- /*
- * Migration support.
- */
- {
- char const *name2 = cf_section_name2(cs);
- ssize_t slen;
-
- tmpl_rules_t t_rules = (tmpl_rules_t) {
- .parent = unlang_ctx->rules->parent,
- .attr = {
- .dict_def = xr_rules.tr_rules->dict_def,
- .list_def = request_attr_request,
- .allow_unresolved = false,
- .allow_unknown = false,
- .allow_wildcard = true,
- },
- .literals_safe_for = unlang_ctx->rules->literals_safe_for,
- };
-
- fr_sbuff_parse_rules_t p_rules = { };
-
- p_rules.terminals = &if_terminals;
-
- slen = xlat_tokenize_condition(cs, &head, &FR_SBUFF_IN(name2, strlen(name2)), &p_rules, &t_rules);
- if (slen == 0) {
- cf_canonicalize_error(cs, slen, "Empty conditions are invalid", name2);
- return NULL;
+ if (cf_item_is_section(csi)) {
+ cf_log_err(csi, "Invalid subsection in 'retry' configuration.");
+ return false;
}
- if (slen < 0) {
- slen++; /* fr_slen_t vs ssize_t */
- cf_canonicalize_error(cs, slen, "Failed parsing condition", name2);
- return NULL;
- }
+ if (!cf_item_is_pair(csi)) continue;
- /*
- * Resolve the xlat first.
- */
- if (xlat_resolve(head, &xr_rules) < 0) {
- cf_log_err(cs, "Failed resolving condition - %s", fr_strerror());
- return NULL;
- }
+ cp = cf_item_to_pair(csi);
+ name = cf_pair_attr(cp);
+ value = cf_pair_value(cp);
- fr_assert(!xlat_needs_resolving(head));
+ if (!value) {
+ cf_log_err(csi, "Retry configuration must specify a value");
+ return false;
+ }
- is_truthy = xlat_is_truthy(head, &value);
+#define CLAMP(_name, _field, _limit) do { \
+ if (!fr_time_delta_ispos(actions->retry._field)) { \
+ cf_log_err(csi, "Invalid value for '" STRINGIFY(_name) " = %s' - value must be positive", \
+ value); \
+ return false; \
+ } \
+ if (fr_time_delta_cmp(actions->retry._field, fr_time_delta_from_sec(_limit)) > 0) { \
+ cf_log_err(csi, "Invalid value for '" STRINGIFY(_name) " = %s' - value must be less than " STRINGIFY(_limit) "s", \
+ value); \
+ return false; \
+ } \
+ } while (0)
/*
- * If the condition is always false, we don't compile the
- * children.
+ * We don't use conf_parser_t here for various
+ * magical reasons.
*/
- if (is_truthy && !value) {
- cf_log_debug_prefix(cs, "Skipping contents of '%s' as it is always 'false'",
- unlang_ops[type].name);
-
- /*
- * Free the children, which frees any xlats,
- * conditions, etc. which were defined, but are
- * now entirely unused.
- *
- * However, we still need to cache the conditions, as they will be accessed at run-time.
- */
- c = compile_empty(parent, unlang_ctx, cs, type);
- cf_section_free_children(cs);
- } else {
- c = compile_section(parent, unlang_ctx, cs, type);
- }
- }
+ if (strcmp(name, "initial_rtx_time") == 0) {
+ if (fr_time_delta_from_str(&actions->retry.irt, value, strlen(value), FR_TIME_RES_SEC) < 0) {
+ error:
+ cf_log_err(csi, "Failed parsing '%s = %s' - %s",
+ name, value, fr_strerror());
+ return false;
+ }
+ CLAMP(initial_rtx_time, irt, 2);
- if (!c) return NULL;
- fr_assert(c != UNLANG_IGNORE);
+ } else if (strcmp(name, "max_rtx_time") == 0) {
+ if (fr_time_delta_from_str(&actions->retry.mrt, value, strlen(value), FR_TIME_RES_SEC) < 0) goto error;
- g = unlang_generic_to_group(c);
- gext = unlang_group_to_cond(g);
+ CLAMP(max_rtx_time, mrt, 10);
- gext->head = head;
- gext->is_truthy = is_truthy;
- gext->value = value;
+ } else if (strcmp(name, "max_rtx_count") == 0) {
+ unsigned long v = strtoul(value, 0, 0);
- ci = cf_section_to_item(cs);
- while ((ci = cf_item_next(parent->ci, ci)) != NULL) {
- if (cf_item_is_data(ci)) continue;
+ if (v > 10) {
+ cf_log_err(csi, "Invalid value for 'max_rtx_count = %s' - value must be between 0 and 10",
+ value);
+ return false;
+ }
- break;
- }
+ actions->retry.mrc = v;
- /*
- * If there's an 'if' without an 'else', then remember that.
- */
- if (ci && cf_item_is_section(ci)) {
- char const *name;
+ } else if (strcmp(name, "max_rtx_duration") == 0) {
+ if (fr_time_delta_from_str(&actions->retry.mrd, value, strlen(value), FR_TIME_RES_SEC) < 0) goto error;
- name = cf_section_name1(cf_item_to_section(ci));
- fr_assert(name != NULL);
+ CLAMP(max_rtx_duration, mrd, 20);
- gext->has_else = (strcmp(name, "else") == 0) || (strcmp(name, "elsif") == 0);
+ } else {
+ cf_log_err(csi, "Invalid item '%s' in 'retry' configuration.", name);
+ return false;
+ }
}
- return c;
+ return true;
}
-static unlang_t *compile_if(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
+bool unlang_compile_actions(unlang_mod_actions_t *actions, CONF_SECTION *action_cs, bool module_retry)
{
- return compile_if_subsection(parent, unlang_ctx, cf_item_to_section(ci), UNLANG_TYPE_IF);
-}
+ int i;
+ bool disallow_retry_action = false;
+ CONF_ITEM *csi;
+ CONF_SECTION *cs;
-static unlang_t *compile_elsif(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- return compile_if_subsection(parent, unlang_ctx, cf_item_to_section(ci), UNLANG_TYPE_ELSIF);
-}
+ /*
+ * Over-ride the default return codes of the module.
+ */
+ cs = cf_item_to_section(cf_section_to_item(action_cs));
+ for (csi=cf_item_next(cs, NULL);
+ csi != NULL;
+ csi=cf_item_next(cs, csi)) {
+ char const *name;
+ CONF_PAIR *cp;
-static unlang_t *compile_else(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
+ if (cf_item_is_section(csi)) {
+ CONF_SECTION *subcs = cf_item_to_section(csi);
- if (cf_section_name2(cs)) {
- cf_log_err(cs, "'else' cannot have a condition");
- return NULL;
- }
+ name = cf_section_name1(subcs);
- return compile_section(parent, unlang_ctx, cs, UNLANG_TYPE_ELSE);
-}
+ /*
+ * Look for a "retry" section.
+ */
+ if (name && (strcmp(name, "retry") == 0) && !cf_section_name2(subcs)) {
+ if (!compile_retry_section(actions, csi)) return false;
+ continue;
+ }
-/*
- * redundant, load-balance and parallel have limits on what can
- * go in them.
- */
-static bool validate_limited_subsection(CONF_SECTION *cs, char const *name)
-{
- CONF_ITEM *ci;
+ cf_log_err(csi, "Invalid subsection. Expected 'action = value'");
+ return false;
+ }
+
+ if (!cf_item_is_pair(csi)) continue;
+
+ cp = cf_item_to_pair(csi);
- for (ci=cf_item_next(cs, NULL);
- ci != NULL;
- ci=cf_item_next(cs, ci)) {
/*
- * If we're a redundant, etc. group, then the
- * intention is to call modules, rather than
- * processing logic. These checks aren't
- * *strictly* necessary, but they keep the users
- * from doing crazy things.
+ * Allow 'retry = path.to.retry.config'
*/
- if (cf_item_is_section(ci)) {
- CONF_SECTION *subcs = cf_item_to_section(ci);
- char const *name1 = cf_section_name1(subcs);
+ name = cf_pair_attr(cp);
+ if (strcmp(name, "retry") == 0) {
+ CONF_ITEM *subci;
+ char const *value = cf_pair_value(cp);
- /*
- * Allow almost anything except "else"
- * statements. The normal processing
- * falls through from "if" to "else", and
- * we can't do that for redundant and
- * load-balance sections.
- */
- if ((strcmp(name1, "else") == 0) ||
- (strcmp(name1, "elsif") == 0)) {
- cf_log_err(ci, "%s sections cannot contain a \"%s\" statement",
- name, name1);
+ if (!value) {
+ cf_log_err(csi, "Missing reference string");
return false;
}
+
+ subci = cf_reference_item(cs, cf_root(cf_section_to_item(action_cs)), value);
+ if (!subci) {
+ cf_log_perr(csi, "Failed finding reference '%s'", value);
+ return false;
+ }
+
+ if (!compile_retry_section(actions, subci)) return false;
continue;
}
- if (cf_item_is_pair(ci)) {
- CONF_PAIR *cp = cf_item_to_pair(ci);
+ if (!compile_action_pair(actions, cp)) {
+ return false;
+ }
+ }
- if (cf_pair_operator(cp) == T_OP_CMP_TRUE) return true;
+ if (module_retry) {
+ if (!fr_time_delta_ispos(actions->retry.irt)) {
+ cf_log_err(csi, "initial_rtx_time MUST be non-zero for modules which support retries.");
+ return false;
+ }
+ } else {
+ if (fr_time_delta_ispos(actions->retry.irt)) {
+ cf_log_err(csi, "initial_rtx_time MUST be zero, as only max_rtx_count and max_rtx_duration are used.");
+ return false;
+ }
- if (cf_pair_value(cp) != NULL) {
- cf_log_err(cp, "Unknown keyword '%s', or invalid location", cf_pair_attr(cp));
- return false;
- }
+ if (!actions->retry.mrc && !fr_time_delta_ispos(actions->retry.mrd)) {
+ disallow_retry_action = true;
}
}
- return true;
-}
-
+ /*
+ * Sanity check that "fail = retry", we actually have a
+ * retry section.
+ */
+ for (i = 0; i < RLM_MODULE_NUMCODES; i++) {
+ if (actions->actions[i] != MOD_ACTION_RETRY) continue;
-static unlang_t *compile_redundant(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- unlang_t *c;
+ if (module_retry) {
+ cf_log_err(csi, "Cannot use a '%s = retry' action for a module which has its own retries",
+ fr_table_str_by_value(mod_rcode_table, i, "<INVALID>"));
+ return false;
+ }
- if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
+ if (disallow_retry_action) {
+ cf_log_err(csi, "max_rtx_count and max_rtx_duration cannot both be zero when using '%s = retry'",
+ fr_table_str_by_value(mod_rcode_table, i, "<INVALID>"));
+ return false;
+ }
- if (!validate_limited_subsection(cs, cf_section_name1(cs))) {
- return NULL;
+ if (!fr_time_delta_ispos(actions->retry.irt) &&
+ !actions->retry.mrc &&
+ !fr_time_delta_ispos(actions->retry.mrd)) {
+ cf_log_err(csi, "Cannot use a '%s = retry' action without a 'retry { ... }' section.",
+ fr_table_str_by_value(mod_rcode_table, i, "<INVALID>"));
+ return false;
+ }
}
- c = compile_section(parent, unlang_ctx, cs, UNLANG_TYPE_REDUNDANT);
- if (!c) return NULL;
+ return true;
+}
+
+unlang_t *unlang_compile_empty(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_SECTION *cs, unlang_type_t type)
+{
+ unlang_group_t *g;
+ unlang_t *c;
/*
- * We no longer care if "redundant" sections have a name. If they do, it's ignored.
+ * If we're compiling an empty section, then the
+ * *interpreter* type is GROUP, even if the *debug names*
+ * are something else.
*/
+ g = unlang_group_allocate(parent, cs, type);
+ if (!g) return NULL;
+
+ c = unlang_group_to_generic(g);
+ if (!cs) {
+ c->name = unlang_ops[type].name;
+ c->debug_name = c->name;
+
+ } else {
+ char const *name2;
+
+ name2 = cf_section_name2(cs);
+ if (!name2) {
+ c->name = cf_section_name1(cs);
+ c->debug_name = c->name;
+ } else {
+ c->name = name2;
+ c->debug_name = talloc_typed_asprintf(c, "%s %s", cf_section_name1(cs), name2);
+ }
+ }
+ unlang_compile_action_defaults(c, unlang_ctx);
return c;
}
-static unlang_t *compile_load_balance_subsection(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_SECTION *cs,
- unlang_type_t type)
+
+static unlang_t *compile_item(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM *ci);
+
+/*
+ * compile 'actions { ... }' inside of another group.
+ */
+static bool compile_action_subsection(unlang_t *c, CONF_SECTION *cs, CONF_SECTION *subcs)
{
- char const *name2;
- unlang_t *c;
- unlang_group_t *g;
- unlang_load_balance_t *gext;
+ CONF_ITEM *ci, *next;
- tmpl_rules_t t_rules;
+ ci = cf_section_to_item(subcs);
- /*
- * We allow unknown attributes here.
- */
- t_rules = *(unlang_ctx->rules);
- t_rules.attr.allow_unknown = true;
- RULES_VERIFY(&t_rules);
+ next = cf_item_next(cs, ci);
+ if (next && (cf_item_is_pair(next) || cf_item_is_section(next))) {
+ cf_log_err(ci, "'actions' MUST be the last block in a section");
+ return false;
+ }
+
+ if (cf_section_name2(subcs) != NULL) {
+ cf_log_err(ci, "Invalid name for 'actions' section");
+ return false;
+ }
/*
- * No children? Die!
+ * Over-riding the actions can be done in certain limited
+ * situations. In other situations (e.g. "redundant",
+ * "load-balance"), it doesn't make sense.
+ *
+ * Note that this limitation also applies to "retry"
+ * timers. We can do retries of a "group". We cannot do
+ * retries of "load-balance", as the "load-balance"
+ * section already takes care of redundancy.
+ *
+ * We may need to loosen that limitation in the future.
*/
- if (!cf_item_next(cs, NULL)) {
- cf_log_err(cs, "%s sections cannot be empty", unlang_ops[type].name);
- return NULL;
+ switch (c->type) {
+ case UNLANG_TYPE_CASE:
+ case UNLANG_TYPE_IF:
+ case UNLANG_TYPE_ELSE:
+ case UNLANG_TYPE_ELSIF:
+ case UNLANG_TYPE_FOREACH:
+ case UNLANG_TYPE_GROUP:
+ case UNLANG_TYPE_LIMIT:
+ case UNLANG_TYPE_SWITCH:
+ case UNLANG_TYPE_TIMEOUT:
+ case UNLANG_TYPE_TRANSACTION:
+ break;
+
+ default:
+ cf_log_err(ci, "'actions' MUST NOT be in a '%s' block", unlang_ops[c->type].name);
+ return false;
}
- if (!validate_limited_subsection(cs, cf_section_name1(cs))) return NULL;
+ return unlang_compile_actions(&c->actions, subcs, false);
+}
- c = compile_section(parent, unlang_ctx, cs, type);
- if (!c) return NULL;
- g = unlang_generic_to_group(c);
+unlang_t *unlang_compile_children(unlang_group_t *g, unlang_compile_ctx_t *unlang_ctx_in, bool set_action_defaults)
+{
+ CONF_ITEM *ci = NULL;
+ unlang_t *c, *single;
+ bool was_if = false;
+ char const *skip_else = NULL;
+ unlang_compile_ctx_t *unlang_ctx;
+ unlang_compile_ctx_t unlang_ctx2;
+ tmpl_rules_t t_rules;
+
+ c = unlang_group_to_generic(g);
/*
- * Allow for keyed load-balance / redundant-load-balance sections.
+ * Create our own compilation context which can be edited
+ * by a variable definition.
*/
- name2 = cf_section_name2(cs);
+ unlang_compile_ctx_copy(&unlang_ctx2, unlang_ctx_in);
+ unlang_ctx = &unlang_ctx2;
+ t_rules = *unlang_ctx_in->rules;
/*
- * Inside of the "modules" section, it's a virtual
- * module. The name is a module name, not a key.
+ * Loop over the children of this group.
*/
- if (name2) {
- if (strcmp(cf_section_name1(cf_item_to_section(cf_parent(cs))), "modules") == 0) name2 = NULL;
- }
-
- if (name2) {
- fr_token_t quote;
- ssize_t slen;
-
- /*
- * Create the template. All attributes and xlats are
- * defined by now.
- */
- quote = cf_section_name2_quote(cs);
- gext = unlang_group_to_load_balance(g);
- slen = tmpl_afrom_substr(gext, &gext->vpt,
- &FR_SBUFF_IN(name2, strlen(name2)),
- quote,
- NULL,
- &t_rules);
- if (!gext->vpt) {
- cf_canonicalize_error(cs, slen, "Failed parsing argument", name2);
- talloc_free(g);
- return NULL;
- }
-
- fr_assert(gext->vpt != NULL);
+ while ((ci = cf_item_next(g->cs, ci))) {
+ if (cf_item_is_data(ci)) continue;
/*
- * Fixup the templates
+ * Sections are keywords, or references to
+ * modules with updated return codes.
*/
- if (!pass2_fixup_tmpl(g, &gext->vpt, cf_section_to_item(cs), unlang_ctx->rules->attr.dict_def)) {
- talloc_free(g);
- return NULL;
- }
-
- switch (gext->vpt->type) {
- default:
- cf_log_err(cs, "Invalid type in '%s': data will not result in a load-balance key", name2);
- talloc_free(g);
- return NULL;
+ if (cf_item_is_section(ci)) {
+ char const *name = NULL;
+ CONF_SECTION *subcs = cf_item_to_section(ci);
/*
- * Allow only these ones.
+ * Skip precompiled blocks. This is
+ * mainly for policies.
*/
- case TMPL_TYPE_XLAT:
- case TMPL_TYPE_ATTR:
- case TMPL_TYPE_EXEC:
- break;
- }
- }
-
- return c;
-}
-
-static unlang_t *compile_load_balance(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- return compile_load_balance_subsection(parent, unlang_ctx, cf_item_to_section(ci), UNLANG_TYPE_LOAD_BALANCE);
-}
-
-
-static unlang_t *compile_redundant_load_balance(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- return compile_load_balance_subsection(parent, unlang_ctx, cf_item_to_section(ci), UNLANG_TYPE_REDUNDANT_LOAD_BALANCE);
-}
-
-static unlang_t *compile_parallel(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- unlang_t *c;
- char const *name2;
-
- unlang_group_t *g;
- unlang_parallel_t *gext;
-
- bool clone = true;
- bool detach = false;
+ if (cf_data_find(subcs, unlang_group_t, NULL)) continue;
- if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
+ /*
+ * "actions" apply to the current group.
+ * It's not a subgroup.
+ */
+ name = cf_section_name1(subcs);
- /*
- * Parallel sections can create empty children, if the
- * admin demands it. Otherwise, the principle of least
- * surprise is to copy the whole request, reply, and
- * config items.
- */
- name2 = cf_section_name2(cs);
- if (name2) {
- if (strcmp(name2, "empty") == 0) {
- clone = false;
+ /*
+ * In-line attribute editing. Nothing else in the parse has list assignments, so this must be it.
+ */
+ if (fr_list_assignment_op[cf_section_name2_quote(subcs)]) {
+ single = compile_edit_section(c, unlang_ctx, subcs);
+ if (!single) {
+ talloc_free(c);
+ return NULL;
+ }
- } else if (strcmp(name2, "detach") == 0) {
- detach = true;
+ goto add_child;
+ }
- } else {
- cf_log_err(cs, "Invalid argument '%s'", name2);
- cf_log_err(ci, DOC_KEYWORD_REF(parallel));
- return NULL;
- }
+ if (strcmp(name, "actions") == 0) {
+ if (!compile_action_subsection(c, g->cs, subcs)) {
+ talloc_free(c);
+ return NULL;
+ }
- }
+ continue;
+ }
- /*
- * We can do "if" in parallel with other "if", but we
- * cannot do "else" in parallel with "if".
- */
- if (!validate_limited_subsection(cs, cf_section_name1(cs))) {
- return NULL;
- }
+ /*
+ * Special checks for "else" and "elsif".
+ */
+ if ((strcmp(name, "else") == 0) || (strcmp(name, "elsif") == 0)) {
+ /*
+ * We ran into one without a preceding "if" or "elsif".
+ * That's not allowed.
+ */
+ if (!was_if) {
+ cf_log_err(ci, "Invalid location for '%s'. There is no preceding "
+ "'if' or 'elsif' statement", name);
+ talloc_free(c);
+ return NULL;
+ }
- c = compile_section(parent, unlang_ctx, cs, UNLANG_TYPE_PARALLEL);
- if (!c) return NULL;
+ /*
+ * There was a previous "if" or "elsif" which was always taken.
+ * So we skip this "elsif" or "else".
+ */
+ if (skip_else) {
+ void *ptr;
- g = unlang_generic_to_group(c);
- gext = unlang_group_to_parallel(g);
- gext->clone = clone;
- gext->detach = detach;
+ /*
+ * And manually free this.
+ */
+ ptr = cf_data_remove(subcs, xlat_exp_head_t, NULL);
+ talloc_free(ptr);
- return c;
-}
+ cf_section_free_children(subcs);
-static unlang_t *compile_subrequest(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- char const *name2;
+ cf_log_debug_prefix(ci, "Skipping contents of '%s' due to previous "
+ "'%s' being always being taken.",
+ name, skip_else);
+ continue;
+ }
+ }
- unlang_t *c;
+ /*
+ * Otherwise it's a real keyword.
+ */
+ single = compile_item(c, unlang_ctx, ci);
+ if (!single) {
+ cf_log_err(ci, "Failed to parse \"%s\" subsection", cf_section_name1(subcs));
+ talloc_free(c);
+ return NULL;
+ }
- unlang_group_t *g;
- unlang_subrequest_t *gext;
+ goto add_child;
- unlang_compile_t unlang_ctx2;
+ } else if (cf_item_is_pair(ci)) {
+ CONF_PAIR *cp = cf_item_to_pair(ci);
- tmpl_rules_t t_rules;
- fr_dict_autoload_talloc_t *dict_ref = NULL;
+ /*
+ * Variable definition.
+ */
+ if (cf_pair_operator(cp) == T_OP_CMP_TRUE) {
+ if (compile_variable(c, unlang_ctx, cp, &t_rules) < 0) {
+ talloc_free(c);
+ return NULL;
+ }
- fr_dict_t const *dict;
- fr_dict_attr_t const *da = NULL;
- fr_dict_enum_value_t const *type_enum = NULL;
+ single = UNLANG_IGNORE;
+ goto add_child;
+ }
- ssize_t slen;
- char *namespace = NULL;
- char const *packet_name = NULL;
+ if (!cf_pair_value(cp)) {
+ single = compile_item(c, unlang_ctx, ci);
+ if (!single) {
+ cf_log_err(ci, "Invalid keyword \"%s\".", cf_pair_attr(cp));
+ talloc_free(c);
+ return NULL;
+ }
- tmpl_t *vpt = NULL, *src_vpt = NULL, *dst_vpt = NULL;
+ goto add_child;
+ }
- /*
- * subrequest { ... }
- *
- * Create a subrequest which is of the same dictionary
- * and packet type as the current request.
- *
- * We assume that the Packet-Type attribute exists.
- */
- name2 = cf_section_name2(cs);
- if (!name2) {
- dict = unlang_ctx->rules->attr.dict_def;
- packet_name = name2 = unlang_ctx->section_name2;
- goto get_packet_type;
- }
+ /*
+ * What remains MUST be an edit pair. At this point, the state of the compiler
+ * tells us what it is, and we don't really care if there's a leading '&'.
+ */
+ single = compile_edit_pair(c, unlang_ctx, cp);
+ if (!single) {
+ talloc_free(c);
+ return NULL;
+ }
- if (cf_section_name2_quote(cs) != T_BARE_WORD) {
- cf_log_err(cs, "The arguments to 'subrequest' must be a name or an attribute reference");
- print_url:
- cf_log_err(ci, DOC_KEYWORD_REF(subrequest));
- return NULL;
- }
+ goto add_child;
+ } else {
+ cf_log_err(ci, "Asked to compile unknown conf type");
+ talloc_free(c);
+ return NULL;
+ }
- dict = unlang_ctx->rules->attr.dict_def;
+ add_child:
+ if (single == UNLANG_IGNORE) continue;
- /*
- * @foo is "dictionary foo", as with references in the dictionaries.
- *
- * @foo::bar is "dictionary foo, Packet-Type = ::bar"
- *
- * foo::bar is "dictionary foo, Packet-Type = ::bar"
- *
- * ::bar is "this dictionary, Packet-Type = ::bar", BUT
- * we don't try to parse the new dictionary name, as it
- * doesn't exist.
- */
- if ((name2[0] == '@') ||
- ((name2[0] != ':') && (name2[0] != '&') && (strchr(name2 + 1, ':') != NULL))) {
- char *q;
+ /*
+ * Do optimizations for "if" and "elsif"
+ * conditions.
+ */
+ switch (single->type) {
+ case UNLANG_TYPE_ELSIF:
+ case UNLANG_TYPE_IF:
+ was_if = true;
- if (name2[0] == '@') name2++;
+ {
+ unlang_group_t *f;
+ unlang_cond_t *gext;
- MEM(namespace = talloc_strdup(parent, name2));
- q = namespace;
+ /*
+ * Skip else, and/or omit things which will never be run.
+ */
+ f = unlang_generic_to_group(single);
+ gext = unlang_group_to_cond(f);
- while (fr_dict_attr_allowed_chars[(unsigned int) *q]) {
- q++;
- }
- *q = '\0';
-
- dict = fr_dict_by_protocol_name(namespace);
- if (!dict) {
- dict_ref = fr_dict_autoload_talloc(NULL, &dict, namespace);
- if (!dict_ref) {
- cf_log_err(cs, "Unknown namespace in '%s'", name2);
- talloc_free(namespace);
- return NULL;
+ if (gext->is_truthy) {
+ if (gext->value) {
+ skip_else = single->debug_name;
+ } else {
+ /*
+ * The condition never
+ * matches, so we can
+ * avoid putting it into
+ * the unlang tree.
+ */
+ talloc_free(single);
+ continue;
+ }
+ }
}
+ break;
+
+ default:
+ was_if = false;
+ skip_else = NULL;
+ break;
}
/*
- * Skip the dictionary name, and go to the thing
- * right after it.
+ * unlang_group_t is grown by adding a unlang_t to the end
*/
- name2 += (q - namespace);
- TALLOC_FREE(namespace);
- }
-
- /*
- * @dict::enum is "other dictionary, Packet-Type = ::enum"
- * ::enum is this dictionary, "Packet-Type = ::enum"
- */
- if ((name2[0] == ':') && (name2[1] == ':')) {
- packet_name = name2;
- goto get_packet_type;
- }
-
- /*
- * Can't do foo.bar.baz::foo, the enums are only used for Packet-Type.
- */
- if (strchr(name2, ':') != NULL) {
- cf_log_err(cs, "Reference cannot contain enum value in '%s'", name2);
- return NULL;
- }
-
- /*
- * '&' means "attribute reference"
- *
- * Or, bare word an require_enum_prefix means "attribute reference".
- */
- slen = tmpl_afrom_attr_substr(parent, NULL, &vpt,
- &FR_SBUFF_IN(name2, talloc_array_length(name2) - 1),
- NULL, unlang_ctx->rules);
- if (slen <= 0) {
- cf_log_perr(cs, "Invalid argument to 'subrequest', failed parsing packet-type");
- goto print_url;
- }
-
- fr_assert(tmpl_is_attr(vpt));
-
- /*
- * Anything resembling an integer or string is
- * OK. Nothing else makes sense.
- */
- switch (tmpl_attr_tail_da(vpt)->type) {
- case FR_TYPE_INTEGER_EXCEPT_BOOL:
- case FR_TYPE_STRING:
- break;
-
- default:
- talloc_free(vpt);
- cf_log_err(cs, "Invalid data type for attribute %s. "
- "Must be an integer type or string", name2 + 1);
- goto print_url;
- }
-
- dict = unlang_ctx->rules->attr.dict_def;
- packet_name = NULL;
-
-get_packet_type:
- /*
- * Local attributes cannot be used in a subrequest. They belong to the parent. Local attributes
- * are NOT copied to the subrequest.
- *
- * @todo - maybe we want to copy local variables, too? But there may be multiple nested local
- * variables, each with their own dictionary.
- */
- dict = fr_dict_proto_dict(dict);
+ fr_assert(g == talloc_parent(single));
+ fr_assert(single->parent == unlang_group_to_generic(g));
+ fr_assert(!single->next);
- /*
- * Use dict name instead of "namespace", because "namespace" can be omitted.
- */
- da = fr_dict_attr_by_name(NULL, fr_dict_root(dict), "Packet-Type");
- if (!da) {
- cf_log_err(cs, "No such attribute 'Packet-Type' in namespace '%s'", fr_dict_root(dict)->name);
- error:
- talloc_free(namespace);
- talloc_free(vpt);
- talloc_free(dict_ref);
- goto print_url;
- }
+ *g->tail = single;
+ g->tail = &single->next;
+ g->num_children++;
- if (packet_name) {
/*
- * Allow ::enum-name for packet types
+ * If it's not possible to execute statement
+ * after the current one, then just stop
+ * processing the children.
*/
- if ((packet_name[0] == ':') && (packet_name[1] == ':')) packet_name += 2;
-
- type_enum = fr_dict_enum_by_name(da, packet_name, -1);
- if (!type_enum) {
- cf_log_err(cs, "No such value '%s' for attribute 'Packet-Type' in namespace '%s'",
- packet_name, fr_dict_root(dict)->name);
- goto error;
+ if (g->self.closed) {
+ cf_log_warn(ci, "Skipping remaining instructions due to '%s'",
+ single->name);
+ break;
}
}
/*
- * No longer needed
- */
- talloc_free(namespace);
-
- /*
- * Source and destination arguments
+ * Set the default actions, if they haven't already been
+ * set by an "actions" section above.
*/
- {
- char const *dst, *src;
-
- src = cf_section_argv(cs, 0);
- if (src) {
- RULES_VERIFY(unlang_ctx->rules);
-
- (void) tmpl_afrom_substr(parent, &src_vpt,
- &FR_SBUFF_IN(src, talloc_array_length(src) - 1),
- cf_section_argv_quote(cs, 0), NULL, unlang_ctx->rules);
- if (!src_vpt) {
- cf_log_perr(cs, "Invalid argument to 'subrequest', failed parsing src");
- goto error;
- }
-
- if (!tmpl_contains_attr(src_vpt)) {
- cf_log_err(cs, "Invalid argument to 'subrequest' src must be an attr or list, got %s",
- tmpl_type_to_str(src_vpt->type));
- talloc_free(src_vpt);
- goto error;
- }
+ if (set_action_defaults) unlang_compile_action_defaults(c, unlang_ctx);
- dst = cf_section_argv(cs, 1);
- if (dst) {
- RULES_VERIFY(unlang_ctx->rules);
-
- (void) tmpl_afrom_substr(parent, &dst_vpt,
- &FR_SBUFF_IN(dst, talloc_array_length(dst) - 1),
- cf_section_argv_quote(cs, 1), NULL, unlang_ctx->rules);
- if (!dst_vpt) {
- cf_log_perr(cs, "Invalid argument to 'subrequest', failed parsing dst");
- goto error;
- }
+ return c;
+}
- if (!tmpl_contains_attr(dst_vpt)) {
- cf_log_err(cs, "Invalid argument to 'subrequest' dst must be an "
- "attr or list, got %s",
- tmpl_type_to_str(src_vpt->type));
- talloc_free(src_vpt);
- talloc_free(dst_vpt);
- goto error;
- }
- }
- }
- }
- if (!cf_item_next(cs, NULL)) {
- talloc_free(vpt);
- talloc_free(src_vpt);
- talloc_free(dst_vpt);
- return UNLANG_IGNORE;
- }
+/*
+ * Generic "compile a section with more unlang inside of it".
+ */
+unlang_t *unlang_compile_section(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_SECTION *cs, unlang_type_t type)
+{
+ unlang_group_t *g;
+ unlang_t *c;
+ char const *name1, *name2;
- t_rules = *unlang_ctx->rules;
- t_rules.parent = unlang_ctx->rules;
- t_rules.attr.dict_def = dict;
- t_rules.attr.allow_foreign = false;
+ fr_assert(unlang_ctx->rules != NULL);
+ fr_assert(unlang_ctx->rules->attr.list_def);
/*
- * Copy over the compilation context. This is mostly
- * just to ensure that retry is handled correctly.
- * i.e. reset.
+ * We always create a group, even if the section is empty.
*/
- compile_copy_context(&unlang_ctx2, unlang_ctx);
+ g = unlang_group_allocate(parent, cs, type);
+ if (!g) return NULL;
- /*
- * Then over-write the new compilation context.
- */
- unlang_ctx2.section_name1 = "subrequest";
- unlang_ctx2.section_name2 = name2;
- unlang_ctx2.rules = &t_rules;
+ c = unlang_group_to_generic(g);
/*
- * Compile the subsection with a *different* default dictionary.
+ * Remember the name for printing, etc.
*/
- c = compile_section(parent, &unlang_ctx2, cs, UNLANG_TYPE_SUBREQUEST);
- if (!c) return NULL;
+ name1 = cf_section_name1(cs);
+ name2 = cf_section_name2(cs);
+ c->name = name1;
/*
- * Set the dictionary and packet information, which tells
- * unlang_subrequest() how to process the request.
+ * Make sure to tell the user that we're running a
+ * policy, and not anything else.
*/
- g = unlang_generic_to_group(c);
- gext = unlang_group_to_subrequest(g);
+ if (type == UNLANG_TYPE_POLICY) {
+ MEM(c->debug_name = talloc_typed_asprintf(c, "policy %s", name1));
- if (dict_ref) {
- /*
- * Parent the dictionary reference correctly now that we
- * have the section with the dependency. This should
- * be fast as dict_ref has no siblings.
- */
- talloc_steal(gext, dict_ref);
- }
- if (vpt) gext->vpt = talloc_steal(gext, vpt);
+ } else if (!name2) {
+ c->debug_name = c->name;
- gext->dict = dict;
- gext->attr_packet_type = da;
- gext->type_enum = type_enum;
- gext->src = src_vpt;
- gext->dst = dst_vpt;
+ } else {
+ MEM(c->debug_name = talloc_typed_asprintf(c, "%s %s", name1, name2));
+ }
- return c;
+ return unlang_compile_children(g, unlang_ctx, true);
}
-static unlang_t *compile_call(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
+static unlang_t *compile_tmpl(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM *ci)
{
- CONF_SECTION *cs = cf_item_to_section(ci);
- virtual_server_t const *vs;
- unlang_t *c;
-
- unlang_group_t *g;
- unlang_call_t *gext;
-
- fr_token_t type;
- char const *server;
- CONF_SECTION *server_cs;
- fr_dict_t const *dict;
- fr_dict_attr_t const *attr_packet_type;
-
- server = cf_section_name2(cs);
- if (!server) {
- cf_log_err(cs, "You MUST specify a server name for 'call <server> { ... }'");
- print_url:
- cf_log_err(ci, DOC_KEYWORD_REF(call));
- return NULL;
- }
-
- type = cf_section_name2_quote(cs);
- if (type != T_BARE_WORD) {
- cf_log_err(cs, "The arguments to 'call' cannot be a quoted string or a dynamic value");
- goto print_url;
- }
-
- vs = virtual_server_find(server);
- if (!vs) {
- cf_log_err(cs, "Unknown virtual server '%s'", server);
- return NULL;
- }
-
- server_cs = virtual_server_cs(vs);
+ CONF_PAIR *cp = cf_item_to_pair(ci);
+ unlang_t *c;
+ unlang_tmpl_t *ut;
+ ssize_t slen;
+ char const *p = cf_pair_attr(cp);
+ tmpl_t *vpt;
- /*
- * The dictionaries are not compatible, forbid it.
- */
- dict = virtual_server_dict_by_name(server);
- if (!dict) {
- cf_log_err(cs, "Cannot call virtual server '%s', failed retrieving its namespace",
- server);
- return NULL;
- }
- if ((dict != fr_dict_internal()) && fr_dict_internal() &&
- unlang_ctx->rules->attr.dict_def && !fr_dict_compatible(unlang_ctx->rules->attr.dict_def, dict)) {
- cf_log_err(cs, "Cannot call server %s with namespace '%s' from namespaces '%s' - they have incompatible protocols",
- server, fr_dict_root(dict)->name, fr_dict_root(unlang_ctx->rules->attr.dict_def)->name);
- return NULL;
- }
+ MEM(ut = talloc_zero(parent, unlang_tmpl_t));
+ c = unlang_tmpl_to_generic(ut);
+ c->parent = parent;
+ c->next = NULL;
+ c->name = p;
+ c->debug_name = c->name;
+ c->type = UNLANG_TYPE_TMPL;
+ c->ci = CF_TO_ITEM(cp);
- attr_packet_type = fr_dict_attr_by_name(NULL, fr_dict_root(dict), "Packet-Type");
- if (!attr_packet_type) {
- cf_log_err(cs, "Cannot call server %s with namespace '%s' - it has no Packet-Type attribute",
- server, fr_dict_root(dict)->name);
+ RULES_VERIFY(unlang_ctx->rules);
+ slen = tmpl_afrom_substr(ut, &vpt,
+ &FR_SBUFF_IN(p, talloc_array_length(p) - 1),
+ cf_pair_attr_quote(cp),
+ NULL,
+ unlang_ctx->rules);
+ if (!vpt) {
+ cf_canonicalize_error(cp, slen, "Failed parsing expansion", p);
+ talloc_free(ut);
return NULL;
}
+ ut->tmpl = vpt; /* const issues */
- c = compile_section(parent, unlang_ctx, cs, UNLANG_TYPE_CALL);
- if (!c) return NULL;
-
- /*
- * Set the virtual server name, which tells unlang_call()
- * which virtual server to call.
- */
- g = unlang_generic_to_group(c);
- gext = unlang_group_to_call(g);
- gext->server_cs = server_cs;
- gext->attr_packet_type = attr_packet_type;
-
+ unlang_compile_action_defaults(c, unlang_ctx);
return c;
}
-
-static unlang_t *compile_caller(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci)
+/*
+ * redundant, load-balance and parallel have limits on what can
+ * go in them.
+ */
+bool unlang_compile_limit_subsection(CONF_SECTION *cs, char const *name)
{
- CONF_SECTION *cs = cf_item_to_section(ci);
- unlang_t *c;
-
- unlang_group_t *g;
- unlang_caller_t *gext;
-
- fr_token_t type;
- char const *name;
- fr_dict_t const *dict;
- unlang_compile_t unlang_ctx2;
- tmpl_rules_t parent_rules, t_rules;
-
- fr_dict_autoload_talloc_t *dict_ref = NULL;
-
- name = cf_section_name2(cs);
- if (!name) {
- cf_log_err(cs, "You MUST specify a protocol name for 'caller <protocol> { ... }'");
- print_url:
- cf_log_err(ci, DOC_KEYWORD_REF(caller));
- return NULL;
- }
+ CONF_ITEM *ci;
- type = cf_section_name2_quote(cs);
- if (type != T_BARE_WORD) {
- cf_log_err(cs, "The argument to 'caller' cannot be a quoted string or a dynamic value");
- goto print_url;
- }
+ for (ci=cf_item_next(cs, NULL);
+ ci != NULL;
+ ci=cf_item_next(cs, ci)) {
+ /*
+ * If we're a redundant, etc. group, then the
+ * intention is to call modules, rather than
+ * processing logic. These checks aren't
+ * *strictly* necessary, but they keep the users
+ * from doing crazy things.
+ */
+ if (cf_item_is_section(ci)) {
+ CONF_SECTION *subcs = cf_item_to_section(ci);
+ char const *name1 = cf_section_name1(subcs);
- dict = fr_dict_by_protocol_name(name);
- if (!dict) {
- dict_ref = fr_dict_autoload_talloc(NULL, &dict, name);
- if (!dict_ref) {
- cf_log_perr(cs, "Unknown protocol '%s'", name);
- goto print_url;
+ /*
+ * Allow almost anything except "else"
+ * statements. The normal processing
+ * falls through from "if" to "else", and
+ * we can't do that for redundant and
+ * load-balance sections.
+ */
+ if ((strcmp(name1, "else") == 0) ||
+ (strcmp(name1, "elsif") == 0)) {
+ cf_log_err(ci, "%s sections cannot contain a \"%s\" statement",
+ name, name1);
+ return false;
+ }
+ continue;
}
- }
- /*
- * Create a new parent context with the new dictionary.
- */
- memcpy(&parent_rules, unlang_ctx->rules, sizeof(parent_rules));
- memcpy(&t_rules, unlang_ctx->rules, sizeof(t_rules));
- parent_rules.attr.dict_def = dict;
- t_rules.parent = &parent_rules;
-
- /*
- * We don't want to modify the context we were passed, so
- * we just clone it
- */
- memcpy(&unlang_ctx2, unlang_ctx, sizeof(unlang_ctx2));
- unlang_ctx2.rules = &t_rules;
- unlang_ctx2.section_name1 = "caller";
- unlang_ctx2.section_name2 = name;
-
- c = compile_section(parent, &unlang_ctx2, cs, UNLANG_TYPE_CALLER);
- if (!c) {
- talloc_free(dict_ref);
- return NULL;
- }
-
- /*
- * Set the virtual server name, which tells unlang_call()
- * which virtual server to call.
- */
- g = unlang_generic_to_group(c);
- gext = unlang_group_to_caller(g);
- gext->dict = dict;
+ if (cf_item_is_pair(ci)) {
+ CONF_PAIR *cp = cf_item_to_pair(ci);
- if (dict_ref) {
- /*
- * Parent the dictionary reference correctly now that we
- * have the section with the dependency. This should
- * be fast as dict_ref has no siblings.
- */
- talloc_steal(gext, dict_ref);
- }
+ if (cf_pair_operator(cp) == T_OP_CMP_TRUE) return true;
- if (!g->num_children) {
- talloc_free(c);
- return UNLANG_IGNORE;
+ if (cf_pair_value(cp) != NULL) {
+ cf_log_err(cp, "Unknown keyword '%s', or invalid location", cf_pair_attr(cp));
+ return false;
+ }
+ }
}
- return c;
+ return true;
}
-static unlang_t *compile_function(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM *ci,
+
+static unlang_t *compile_function(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM *ci,
CONF_SECTION *subcs,
bool policy)
{
- unlang_compile_t unlang_ctx2;
+ unlang_compile_ctx_t unlang_ctx2;
unlang_t *c;
/*
*
* group foo { ...
*/
- c = compile_section(parent, &unlang_ctx2, subcs,
+ c = unlang_compile_section(parent, &unlang_ctx2, subcs,
policy ? UNLANG_TYPE_POLICY : UNLANG_TYPE_GROUP);
}
if (!c) return NULL;
* @return the CONF_SECTION specifying the virtual module.
*/
static CONF_SECTION *virtual_module_find_cs(CONF_ITEM *ci, UNUSED char const *real_name, char const *virtual_name,
- char const *method_name, unlang_compile_t *unlang_ctx, bool *policy)
+ char const *method_name, unlang_compile_ctx_t *unlang_ctx, bool *policy)
{
CONF_SECTION *cs, *subcs, *conf_root;
CONF_ITEM *loop;
return subcs;
}
-static unlang_t *compile_module(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM *ci, char const *name)
+static unlang_t *compile_module(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM *ci, char const *name)
{
unlang_t *c;
unlang_module_t *m;
/*
* Add in the default actions for this section.
*/
- compile_action_defaults(c, unlang_ctx);
+ unlang_compile_action_defaults(c, unlang_ctx);
/*
* Parse the method environment for this module / method
return c;
}
-typedef unlang_t *(*unlang_op_compile_t)(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci);
-
-static fr_table_ptr_sorted_t unlang_section_keywords[] = {
- { L("call"), (void *) compile_call },
- { L("caller"), (void *) compile_caller },
- { L("case"), (void *) compile_case },
- { L("catch"), (void *) compile_catch },
- { L("else"), (void *) compile_else },
- { L("elsif"), (void *) compile_elsif },
- { L("foreach"), (void *) compile_foreach },
- { L("group"), (void *) compile_group },
- { L("if"), (void *) compile_if },
- { L("limit"), (void *) compile_limit },
- { L("load-balance"), (void *) compile_load_balance },
- { L("map"), (void *) compile_map },
- { L("parallel"), (void *) compile_parallel },
- { L("redundant"), (void *) compile_redundant },
- { L("redundant-load-balance"), (void *) compile_redundant_load_balance },
- { L("subrequest"), (void *) compile_subrequest },
- { L("switch"), (void *) compile_switch },
- { L("timeout"), (void *) compile_timeout },
- { L("transaction"), (void *) compile_transaction },
- { L("try"), (void *) compile_try },
- { L("update"), (void *) compile_update },
-};
-static int unlang_section_keywords_len = NUM_ELEMENTS(unlang_section_keywords);
+extern int dict_attr_acopy_children(fr_dict_t *dict, fr_dict_attr_t *dst, fr_dict_attr_t const *src);
-static fr_table_ptr_sorted_t unlang_pair_keywords[] = {
- { L("break"), (void *) compile_break },
- { L("continue"), (void *) compile_continue },
- { L("detach"), (void *) compile_detach },
- { L("return"), (void *) compile_return },
-};
-static int unlang_pair_keywords_len = NUM_ELEMENTS(unlang_pair_keywords);
+static inline CC_HINT(always_inline) unlang_op_t const *name_to_op(char const *name)
+{
+ unlang_op_t const *op;
-extern int dict_attr_acopy_children(fr_dict_t *dict, fr_dict_attr_t *dst, fr_dict_attr_t const *src);
+ op = fr_hash_table_find(unlang_op_table, &(unlang_op_t) { .name = name });
+ if (op) return op;
+
+ DEBUG("NOT KEYWORD ::%s::", name);
+ return NULL;
+}
-static int define_local_variable(CONF_ITEM *ci, unlang_variable_t *var, tmpl_rules_t *t_rules, fr_type_t type, char const *name,
+int unlang_define_local_variable(CONF_ITEM *ci, unlang_variable_t *var, tmpl_rules_t *t_rules, fr_type_t type, char const *name,
fr_dict_attr_t const *ref)
{
fr_dict_attr_t const *da;
fr_slen_t len;
+ unlang_op_t const *op;
fr_dict_attr_flags_t flags = {
.internal = true,
/*
* No keyword section names.
*/
- if (fr_table_value_by_str(unlang_section_keywords, name, NULL) != NULL) {
- fail_unlang:
+ op = name_to_op(name);
+ if (op) {
cf_log_err(ci, "Local variable '%s' cannot be an unlang keyword.", name);
return -1;
}
- /*
- * No simple keyword names.
- */
- if (fr_table_value_by_str(unlang_pair_keywords, name, NULL) != NULL) goto fail_unlang;
-
/*
* No protocol names.
*/
/*
* Compile one unlang instruction
*/
-static unlang_t *compile_item(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM *ci)
+static unlang_t *compile_item(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM *ci)
{
char const *name, *p;
CONF_SECTION *cs, *subcs, *modules;
char const *realname;
- unlang_compile_t unlang_ctx2;
+ unlang_compile_ctx_t unlang_ctx2;
bool policy;
- unlang_op_compile_t compile;
unlang_t *c;
bool ignore_notfound = false;
+ unlang_op_t const *op;
if (cf_item_is_section(ci)) {
cs = cf_item_to_section(ci);
name = cf_section_name1(cs);
+ op = name_to_op(name);
- compile = (unlang_op_compile_t) fr_table_value_by_str(unlang_section_keywords, name, NULL);
- if (compile) {
- unlang_op_t *op;
+ if (op) {
+ /*
+ * Forbid pair keywords as section names,
+ * e.g. "break { ... }"
+ */
+ if ((op->flag & UNLANG_OP_FLAG_SINGLE_WORD) != 0) {
+ cf_log_err(ci, "Syntax error after keyword '%s' - unexpected '{'", name);
+ return NULL;
+ }
- c = compile(parent, unlang_ctx, ci);
+ c = op->compile(parent, unlang_ctx, ci);
allocate_number:
if (!c) return NULL;
if (c == UNLANG_IGNORE) return UNLANG_IGNORE;
return c;
}
- /*
- * Forbid pair keywords as section names, e.g. "break { ... }"
- */
- if (fr_table_value_by_str(unlang_pair_keywords, name, NULL) != NULL) {
- cf_log_err(ci, "Syntax error after keyword '%s' - unexpected '{'", name);
- return NULL;
- }
-
/* else it's something like sql { fail = 1 ...} */
goto check_for_module;
} else if (cf_item_is_pair(ci)) {
+
/*
* Else it's a module reference such as "sql", OR
* one of the few bare keywords that we allow.
CONF_PAIR *cp = cf_item_to_pair(ci);
name = cf_pair_attr(cp);
+ op = name_to_op(name);
+
+ /*
+ * Forbid section keywords as pair names, e.g. bare "update"
+ */
+ if (op && ((op->flag & UNLANG_OP_FLAG_SINGLE_WORD) == 0)) {
+ cf_log_err(ci, "Syntax error after keyword '%s' - missing '{'", name);
+ return NULL;
+ }
/*
* We cannot have assignments or actions here.
goto allocate_number;
}
- compile = (unlang_op_compile_t)fr_table_value_by_str(unlang_pair_keywords, name, NULL); /* Cast for -Wpedantic */
- if (compile) {
- c = compile(parent, unlang_ctx, ci);
- goto allocate_number;
- }
-
- /*
- * Forbid section keywords as pair names, e.g. bare "update"
- */
- if (fr_table_value_by_str(unlang_section_keywords, name, NULL) != NULL) {
- cf_log_err(ci, "Syntax error after keyword '%s' - expected '{'", name);
- return NULL;
- }
+ if (!op) goto check_for_module;
- goto check_for_module;
+ c = op->compile(parent, unlang_ctx, ci);
+ goto allocate_number;
} else {
cf_log_err(ci, "Asked to compile unknown conf type");
return NULL; /* who knows what it is... */
rules = &my_rules;
}
- c = compile_section(NULL,
- &(unlang_compile_t){
+ c = unlang_compile_section(NULL,
+ &(unlang_compile_ctx_t){
.vs = vs,
.section_name1 = cf_section_name1(cs),
.section_name2 = cf_section_name2(cs),
{
if (!name || !*name) return false;
- if (fr_table_value_by_str(unlang_section_keywords, name, NULL) != NULL) return true;
-
- return (fr_table_value_by_str(unlang_pair_keywords, name, NULL) != NULL);
+ return (name_to_op(name) != 0);
}
/*