#include "map_priv.h"
-typedef enum {
- UNLANG_UPDATE_MAP_INIT = 0, //!< Start processing a map.
- UNLANG_UPDATE_MAP_EXPANDED_LHS, //!< Expand the LHS xlat or exec (if needed).
- UNLANG_UPDATE_MAP_EXPANDED_RHS //!< Expand the RHS xlat or exec (if needed).
-} unlang_update_state_t;
-
-/** State of an update block
- *
- */
-typedef struct {
- fr_dcursor_t maps; //!< Cursor of maps to evaluate.
-
- fr_dlist_head_t vlm_head; //!< Head of list of VP List Mod.
-
- fr_value_box_list_t lhs_result; //!< Result of expanding the LHS
- fr_value_box_list_t rhs_result; //!< Result of expanding the RHS.
-
- unlang_update_state_t state; //!< What we're currently doing.
-} unlang_frame_state_update_t;
-
/** State of a map block
*
*/
*/
#define MAP_CTX(_mod_inst, _map_inst, _rctx) &(map_ctx_t){ .moi = _mod_inst, .mpi = _map_inst, .rctx = _rctx }
-/** Apply a list of modifications on one or more fr_pair_t lists.
- *
- * @param[in] request The current request.
- * @param[out] p_result The rcode indicating what the result
- * of the operation was.
- * @return
- * - UNLANG_ACTION_CALCULATE_RESULT changes were applied.
- * - UNLANG_ACTION_PUSHED_CHILD async execution of an expansion is required.
- */
-static unlang_action_t list_mod_apply(unlang_result_t *p_result, request_t *request)
-{
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_frame_state_update_t *update_state = frame->state;
- vp_list_mod_t const *vlm = NULL;
-
- /*
- * No modifications...
- */
- if (fr_dlist_empty(&update_state->vlm_head)) {
- RDEBUG2("Nothing to update");
- goto done;
- }
-
- /*
- * Apply the list of modifications. This should not fail
- * except on memory allocation error.
- */
- while ((vlm = fr_dlist_next(&update_state->vlm_head, vlm))) {
- int ret;
-
- ret = map_list_mod_apply(request, vlm);
- if (!fr_cond_assert(ret == 0)) {
- TALLOC_FREE(frame->state);
-
- return UNLANG_ACTION_FAIL;
- }
- }
-
-done:
- RETURN_UNLANG_NOOP;
-}
-
-/** Create a list of modifications to apply to one or more fr_pair_t lists
- *
- * @param[out] p_result The rcode indicating what the result
- * of the operation was.
- * @param[in] request The current request.
- * @param[in] frame Current stack frame.
- * @return
- * - UNLANG_ACTION_CALCULATE_RESULT changes were applied.
- * - UNLANG_ACTION_PUSHED_CHILD async execution of an expansion is required.
- */
-static unlang_action_t list_mod_create(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
-{
- unlang_frame_state_update_t *update_state = talloc_get_type_abort(frame->state, unlang_frame_state_update_t);
- map_t *map;
-
- /*
- * Iterate over the maps producing a set of modifications to apply.
- */
- for (map = fr_dcursor_current(&update_state->maps);
- map;
- map = fr_dcursor_next(&update_state->maps)) {
- repeatable_set(frame); /* Call us again when done */
-
- switch (update_state->state) {
- case UNLANG_UPDATE_MAP_INIT:
- update_state->state = UNLANG_UPDATE_MAP_EXPANDED_LHS;
-
- fr_assert(fr_value_box_list_empty(&update_state->lhs_result)); /* Should have been consumed */
- fr_assert(fr_value_box_list_empty(&update_state->rhs_result)); /* Should have been consumed */
-
- switch (map->lhs->type) {
- default:
- break;
-
- case TMPL_TYPE_EXEC:
- if (unlang_tmpl_push(update_state, NULL, &update_state->lhs_result,
- request, map->lhs,
- NULL, UNLANG_SUB_FRAME) < 0) {
- return UNLANG_ACTION_STOP_PROCESSING;
- }
- return UNLANG_ACTION_PUSHED_CHILD;
-
- case TMPL_TYPE_XLAT:
- if (unlang_xlat_push(update_state, NULL, &update_state->lhs_result,
- request, tmpl_xlat(map->lhs), false) < 0) {
- return UNLANG_ACTION_STOP_PROCESSING;
- }
- return UNLANG_ACTION_PUSHED_CHILD;
-
- case TMPL_TYPE_REGEX_XLAT_UNRESOLVED:
- case TMPL_TYPE_REGEX:
- case TMPL_TYPE_REGEX_UNCOMPILED:
- case TMPL_TYPE_REGEX_XLAT:
- case TMPL_TYPE_XLAT_UNRESOLVED:
- fr_assert(0);
- error:
- TALLOC_FREE(frame->state);
- repeatable_clear(frame);
- return UNLANG_ACTION_FAIL;
- }
- FALL_THROUGH;
-
- case UNLANG_UPDATE_MAP_EXPANDED_LHS:
- /*
- * map_to_list_mod() already concatenates the LHS, so we don't need to do it here.
- */
- if (!map->rhs) goto next;
-
- update_state->state = UNLANG_UPDATE_MAP_EXPANDED_RHS;
-
- switch (map->rhs->type) {
- default:
- break;
-
- case TMPL_TYPE_EXEC:
- if (unlang_tmpl_push(update_state, NULL, &update_state->rhs_result,
- request, map->rhs, NULL, UNLANG_SUB_FRAME) < 0) {
- return UNLANG_ACTION_STOP_PROCESSING;
- }
- return UNLANG_ACTION_PUSHED_CHILD;
-
- case TMPL_TYPE_XLAT:
- if (unlang_xlat_push(update_state, NULL, &update_state->rhs_result,
- request, tmpl_xlat(map->rhs), false) < 0) {
- return UNLANG_ACTION_STOP_PROCESSING;
- }
- return UNLANG_ACTION_PUSHED_CHILD;
-
- case TMPL_TYPE_REGEX:
- case TMPL_TYPE_REGEX_UNCOMPILED:
- case TMPL_TYPE_REGEX_XLAT:
- case TMPL_TYPE_REGEX_XLAT_UNRESOLVED:
- case TMPL_TYPE_XLAT_UNRESOLVED:
- fr_assert(0);
- goto error;
- }
- FALL_THROUGH;
-
- case UNLANG_UPDATE_MAP_EXPANDED_RHS:
- {
- vp_list_mod_t *new_mod;
- /*
- * Concat the top level results together
- */
- if (!fr_value_box_list_empty(&update_state->rhs_result) &&
- (fr_value_box_list_concat_in_place(update_state,
- fr_value_box_list_head(&update_state->rhs_result), &update_state->rhs_result, FR_TYPE_STRING,
- FR_VALUE_BOX_LIST_FREE, true,
- SIZE_MAX) < 0)) {
- RPEDEBUG("Failed concatenating RHS expansion results");
- goto error;
- }
-
- if (map_to_list_mod(update_state, &new_mod,
- request, map,
- &update_state->lhs_result, &update_state->rhs_result) < 0) goto error;
- if (new_mod) fr_dlist_insert_tail(&update_state->vlm_head, new_mod);
-
- fr_value_box_list_talloc_free(&update_state->rhs_result);
- }
-
- next:
- update_state->state = UNLANG_UPDATE_MAP_INIT;
- fr_value_box_list_talloc_free(&update_state->lhs_result);
-
- break;
- }
- }
-
- return list_mod_apply(p_result, request);
-}
-
-
-/** Execute an update block
- *
- * Update blocks execute in two phases, first there's an evaluation phase where
- * each input map is evaluated, outputting one or more modification maps. The modification
- * maps detail a change that should be made to a list in the current request.
- * The request is not modified during this phase.
- *
- * The second phase applies those modification maps to the current request.
- * This re-enables the atomic functionality of update blocks provided in v2.x.x.
- * If one map fails in the evaluation phase, no more maps are processed, and the current
- * result is discarded.
- */
-static unlang_action_t unlang_update_state_init(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
-{
- unlang_group_t *g = unlang_generic_to_group(frame->instruction);
- unlang_map_t *gext = unlang_group_to_map(g);
- unlang_frame_state_update_t *update_state;
-
- /*
- * Initialise the frame state
- */
- MEM(frame->state = update_state = talloc_zero_pooled_object(request->stack, unlang_frame_state_update_t,
- (sizeof(map_t) +
- (sizeof(tmpl_t) * 2) + 128), /* 128 is for string buffers */
- unlang_list_num_elements(&g->children)));
-
- fr_dcursor_init(&update_state->maps, &gext->map.head);
- fr_value_box_list_init(&update_state->lhs_result);
- fr_value_box_list_init(&update_state->rhs_result);
- fr_dlist_init(&update_state->vlm_head, vp_list_mod_t, entry);
-
- /*
- * Call list_mod_create
- */
- frame_repeat(frame, list_mod_create);
- return list_mod_create(p_result, request, frame);
-}
-
static unlang_action_t map_proc_resume(unlang_result_t *p_result, request_t *request,
#ifdef WITH_VERIFY_PTR
unlang_stack_frame_t *frame
return map_proc_apply(p_result, request, frame);
}
-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;
-
- 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);
- }
- }
-
- return 0;
-}
-
-/*
- * Convert "update" to "edit" using evil spells and sorcery.
- */
-static unlang_t *compile_update_to_edit(unlang_t *parent, unlang_compile_ctx_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 (!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.
- */
- {
- fr_dict_attr_t const *tmpl_list;
-
- /*
- * 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) {
-
- /*
- * Catch one more case where the behavior is different.
- *
- * &request += &config[*]
- */
- 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;
- }
- }
- }
-
- 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.
- */
- 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;
- }
-
- 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;
- }
-
- if (rcode < 0) {
- cf_log_err(cp, "Failed converting entry");
- return NULL;
- }
- }
-
- return UNLANG_IGNORE;
-}
-
-static unlang_t *unlang_compile_update(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci)
-{
- CONF_SECTION *cs = cf_item_to_section(ci);
- int rcode;
-
- unlang_group_t *g;
- unlang_map_t *gext;
-
- unlang_t *c;
- char const *name2 = cf_section_name2(cs);
-
- 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);
- }
-
- /*
- * 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 = unlang_group_allocate(parent, cs, UNLANG_TYPE_UPDATE);
- if (!g) return NULL;
-
- gext = unlang_group_to_map(g);
-
- /*
- * This looks at cs->name2 to determine which list to update
- */
- 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);
- 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;
- }
-
- if (!pass2_fixup_update(g, unlang_ctx->rules)) goto error;
-
- return c;
-}
-
static int compile_map_name(unlang_group_t *g)
{
unlang_map_t *gext = unlang_group_to_map(g);
void unlang_map_init(void)
{
- unlang_register(&(unlang_op_t){
- .name = "update",
- .type = UNLANG_TYPE_UPDATE,
- .flag = UNLANG_OP_FLAG_DEBUG_BRACES,
-
- .compile = unlang_compile_update,
- .interpret = unlang_update_state_init,
-
- .unlang_size = sizeof(unlang_map_t),
- .unlang_name = "unlang_map_t",
- });
-
unlang_register(&(unlang_op_t){
.name = "map",
.type = UNLANG_TYPE_MAP,