summaryrefslogtreecommitdiffstats
path: root/src/main/map.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/map.c')
-rw-r--r--src/main/map.c1717
1 files changed, 1717 insertions, 0 deletions
diff --git a/src/main/map.c b/src/main/map.c
new file mode 100644
index 0000000..e59fcec
--- /dev/null
+++ b/src/main/map.c
@@ -0,0 +1,1717 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/*
+ * $Id$
+ *
+ * @brief map / template functions
+ * @file main/map.c
+ *
+ * @ingroup AVP
+ *
+ * @copyright 2013 The FreeRADIUS server project
+ * @copyright 2013 Alan DeKok <aland@freeradius.org>
+ */
+
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/rad_assert.h>
+
+#include <ctype.h>
+
+#ifdef DEBUG_MAP
+static void map_dump(REQUEST *request, vp_map_t const *map)
+{
+ RDEBUG(">>> MAP TYPES LHS: %s, RHS: %s",
+ fr_int2str(tmpl_names, map->lhs->type, "???"),
+ fr_int2str(tmpl_names, map->rhs->type, "???"));
+
+ if (map->rhs) {
+ RDEBUG(">>> MAP NAMES %s %s", map->lhs->name, map->rhs->name);
+ }
+}
+#endif
+
+
+/** re-parse a map where the lhs is an unknown attribute.
+ *
+ *
+ * @param map to process.
+ * @param rhs_type quotation type around rhs.
+ * @param rhs string to re-parse.
+ */
+bool map_cast_from_hex(vp_map_t *map, FR_TOKEN rhs_type, char const *rhs)
+{
+ size_t len;
+ ssize_t rlen;
+ uint8_t *ptr;
+ char const *p;
+ pair_lists_t list;
+
+ DICT_ATTR const *da;
+ VALUE_PAIR *vp;
+ vp_tmpl_t *vpt;
+
+ rad_assert(map != NULL);
+
+ rad_assert(map->lhs != NULL);
+ rad_assert(map->lhs->type == TMPL_TYPE_ATTR);
+
+ rad_assert(map->rhs == NULL);
+ rad_assert(rhs != NULL);
+
+ VERIFY_MAP(map);
+
+ /*
+ * If the attribute is still unknown, go parse the RHS.
+ */
+ da = dict_attrbyvalue(map->lhs->tmpl_da->attr, map->lhs->tmpl_da->vendor);
+ if (!da || da->flags.is_unknown) return false;
+
+ /*
+ * If the RHS is something OTHER than an octet
+ * string, go parse it as that.
+ */
+ if (rhs_type != T_BARE_WORD) return false;
+ if ((rhs[0] != '0') || (tolower((uint8_t)rhs[1]) != 'x')) return false;
+ if (!rhs[2]) return false;
+
+ len = strlen(rhs + 2);
+
+ ptr = talloc_array(map, uint8_t, len >> 1);
+ if (!ptr) return false;
+
+ len = fr_hex2bin(ptr, len >> 1, rhs + 2, len);
+
+ /*
+ * If we can't parse it, or if it's malformed,
+ * it's still unknown.
+ */
+ rlen = data2vp(NULL, NULL, NULL, NULL, da, ptr, len, len, &vp);
+ talloc_free(ptr);
+
+ if (rlen < 0) return false;
+
+ if ((size_t) rlen < len) {
+ free_vp:
+ fr_pair_list_free(&vp);
+ return false;
+ }
+
+ /*
+ * Was still parsed as an unknown attribute.
+ */
+ if (vp->da->flags.is_unknown) goto free_vp;
+
+ /*
+ * Set the RHS to the PARSED name, not the crap octet
+ * string which was input.
+ */
+ map->rhs = tmpl_alloc(map, TMPL_TYPE_DATA, NULL, 0);
+ if (!map->rhs) goto free_vp;
+
+ map->rhs->tmpl_data_type = da->type;
+ map->rhs->tmpl_data_length = vp->vp_length;
+ if (vp->da->flags.is_pointer) {
+ if (vp->da->type == PW_TYPE_STRING) {
+ map->rhs->tmpl_data_value.ptr = talloc_bstrndup(map->rhs, vp->data.ptr, vp->vp_length);
+ } else {
+ map->rhs->tmpl_data_value.ptr = talloc_memdup(map->rhs, vp->data.ptr, vp->vp_length);
+ }
+ } else {
+ memcpy(&map->rhs->tmpl_data_value, &vp->data, sizeof(map->rhs->tmpl_data_value));
+ }
+ map->rhs->name = vp_aprints_value(map->rhs, vp, '"');
+ map->rhs->len = talloc_array_length(map->rhs->name) - 1;
+
+ /*
+ * Set the LHS to the REAL attribute name.
+ */
+ vpt = tmpl_alloc(map, TMPL_TYPE_ATTR, map->lhs->tmpl_da->name, -1);
+ memcpy(&vpt->data.attribute, &map->lhs->data.attribute, sizeof(vpt->data.attribute));
+ vpt->tmpl_da = da;
+
+ /*
+ * Be sure to keep the "&control:" or "control:" prefix.
+ * If it's there, we re-generate it from whatever was in
+ * the original name, including the '&'.
+ */
+ p = map->lhs->name;
+ if (*p == '&') p++;
+ len = radius_list_name(&list, p, PAIR_LIST_UNKNOWN);
+
+ if (list != PAIR_LIST_UNKNOWN) {
+ rad_const_free(vpt->name);
+
+ vpt->name = talloc_asprintf(vpt, "%.*s:%s",
+ (int) len, map->lhs->name,
+ map->lhs->tmpl_da->name);
+ vpt->len = strlen(vpt->name);
+ }
+
+ talloc_free(map->lhs);
+ map->lhs = vpt;
+
+ fr_pair_list_free(&vp);
+
+ VERIFY_MAP(map);
+
+ return true;
+}
+
+/** Convert CONFIG_PAIR (which may contain refs) to vp_map_t.
+ *
+ * Treats the left operand as an attribute reference
+ * @verbatim<request>.<list>.<attribute>@endverbatim
+ *
+ * Treatment of left operand depends on quotation, barewords are treated as
+ * attribute references, double quoted values are treated as expandable strings,
+ * single quoted values are treated as literal strings.
+ *
+ * Return must be freed with talloc_free
+ *
+ * @param[in] ctx for talloc.
+ * @param[in] out Where to write the pointer to the new value_pair_map_struct.
+ * @param[in] cp to convert to map.
+ * @param[in] dst_request_def The default request to insert unqualified
+ * attributes into.
+ * @param[in] dst_list_def The default list to insert unqualified attributes
+ * into.
+ * @param[in] src_request_def The default request to resolve attribute
+ * references in.
+ * @param[in] src_list_def The default list to resolve unqualified attributes
+ * in.
+ * @return vp_map_t if successful or NULL on error.
+ */
+int map_afrom_cp(TALLOC_CTX *ctx, vp_map_t **out, CONF_PAIR *cp,
+ request_refs_t dst_request_def, pair_lists_t dst_list_def,
+ request_refs_t src_request_def, pair_lists_t src_list_def)
+{
+ vp_map_t *map;
+ char const *attr, *value;
+ ssize_t slen;
+ FR_TOKEN type;
+
+ *out = NULL;
+
+ if (!cp) return -1;
+
+ map = talloc_zero(ctx, vp_map_t);
+ map->op = cf_pair_operator(cp);
+ map->ci = cf_pair_to_item(cp);
+
+ attr = cf_pair_attr(cp);
+ value = cf_pair_value(cp);
+ if (!value) {
+ cf_log_err_cp(cp, "Missing attribute value");
+ goto error;
+ }
+
+ /*
+ * LHS may be an expansion (that expands to an attribute reference)
+ * or an attribute reference. Quoting determines which it is.
+ */
+ type = cf_pair_attr_type(cp);
+ switch (type) {
+ case T_DOUBLE_QUOTED_STRING:
+ case T_BACK_QUOTED_STRING:
+ slen = tmpl_afrom_str(ctx, &map->lhs, attr, talloc_array_length(attr) - 1,
+ type, dst_request_def, dst_list_def, true);
+ if (slen <= 0) {
+ char *spaces, *text;
+
+ marker:
+ fr_canonicalize_error(ctx, &spaces, &text, slen, attr);
+ cf_log_err_cp(cp, "%s", text);
+ cf_log_err_cp(cp, "%s^ %s", spaces, fr_strerror());
+
+ talloc_free(spaces);
+ talloc_free(text);
+ goto error;
+ }
+ break;
+
+ case T_BARE_WORD:
+ /*
+ * Foo = %{...}
+ *
+ * Not allowed!
+ */
+ if ((attr[0] == '%') && (attr[1] == '{')) {
+ cf_log_err_cp(cp, "Bare expansions are not permitted. They must be in a double-quoted string.");
+ goto error;
+ }
+ /* FALL-THROUGH */
+
+ default:
+ slen = tmpl_afrom_attr_str(ctx, &map->lhs, attr, dst_request_def, dst_list_def, true, true);
+ if (slen <= 0) {
+ cf_log_err_cp(cp, "Failed parsing attribute reference");
+
+ goto marker;
+ }
+
+ if (tmpl_define_unknown_attr(map->lhs) < 0) {
+ cf_log_err_cp(cp, "Failed creating attribute %s: %s",
+ map->lhs->name, fr_strerror());
+ goto error;
+ }
+
+ break;
+ }
+
+ /*
+ * RHS might be an attribute reference.
+ */
+ type = cf_pair_value_type(cp);
+
+ if ((map->lhs->type == TMPL_TYPE_ATTR) &&
+ map->lhs->tmpl_da->flags.is_unknown &&
+ !map_cast_from_hex(map, type, value)) {
+ goto error;
+
+ } else {
+ slen = tmpl_afrom_str(map, &map->rhs, value, strlen(value), type, src_request_def, src_list_def, true);
+ if (slen < 0) goto marker;
+ if (tmpl_define_unknown_attr(map->rhs) < 0) {
+ cf_log_err_cp(cp, "Failed creating attribute %s: %s", map->rhs->name, fr_strerror());
+ goto error;
+ }
+ }
+ if (!map->rhs) {
+ cf_log_err_cp(cp, "%s", fr_strerror());
+ goto error;
+ }
+
+ if (map->rhs->type == TMPL_TYPE_ATTR) {
+ /*
+ * We cannot assign a count to an attribute. That must
+ * be done in an xlat.
+ */
+ if (map->rhs->tmpl_num == NUM_COUNT) {
+ cf_log_err_cp(cp, "Cannot assign from a count");
+ goto error;
+ }
+
+ if (map->rhs->tmpl_da->flags.virtual) {
+ cf_log_err_cp(cp, "Virtual attributes must be in an expansion such as \"%%{%s}\".", map->rhs->tmpl_da->name);
+ goto error;
+ }
+ }
+
+ VERIFY_MAP(map);
+
+ *out = map;
+
+ return 0;
+
+error:
+ talloc_free(map);
+ return -1;
+}
+
+/** Convert an 'update' config section into an attribute map.
+ *
+ * Uses 'name2' of section to set default request and lists.
+ *
+ * @param[in] cs the update section
+ * @param[out] out Where to store the head of the map.
+ * @param[in] dst_list_def The default destination list, usually dictated by
+ * the section the module is being called in.
+ * @param[in] src_list_def The default source list, usually dictated by the
+ * section the module is being called in.
+ * @param[in] validate map using this callback (may be NULL).
+ * @param[in] ctx to pass to callback.
+ * @param[in] max number of mappings to process.
+ * @return -1 on error, else 0.
+ */
+int map_afrom_cs(vp_map_t **out, CONF_SECTION *cs,
+ pair_lists_t dst_list_def, pair_lists_t src_list_def,
+ map_validate_t validate, void *ctx,
+ unsigned int max)
+{
+ char const *cs_list, *p;
+
+ request_refs_t request_def = REQUEST_CURRENT;
+
+ CONF_ITEM *ci;
+ CONF_PAIR *cp;
+
+ unsigned int total = 0;
+ vp_map_t **tail, *map;
+ TALLOC_CTX *parent;
+
+ *out = NULL;
+ tail = out;
+
+ /*
+ * The first map has cs as the parent.
+ * The rest have the previous map as the parent.
+ */
+ parent = cs;
+
+ ci = cf_section_to_item(cs);
+
+ cs_list = p = cf_section_name2(cs);
+ if (cs_list) {
+ p += radius_request_name(&request_def, p, REQUEST_CURRENT);
+ if (request_def == REQUEST_UNKNOWN) {
+ cf_log_err(ci, "Default request specified in mapping section is invalid");
+ return -1;
+ }
+
+ dst_list_def = fr_str2int(pair_lists, p, PAIR_LIST_UNKNOWN);
+ if (dst_list_def == PAIR_LIST_UNKNOWN) {
+ cf_log_err(ci, "Default list \"%s\" specified "
+ "in mapping section is invalid", p);
+ return -1;
+ }
+ }
+
+ for (ci = cf_item_find_next(cs, NULL);
+ ci != NULL;
+ ci = cf_item_find_next(cs, ci)) {
+ if (total++ == max) {
+ cf_log_err(ci, "Map size exceeded");
+ error:
+ TALLOC_FREE(*out);
+ return -1;
+ }
+
+ if (!cf_item_is_pair(ci)) {
+ cf_log_err(ci, "Entry is not in \"attribute = value\" format");
+ goto error;
+ }
+
+ cp = cf_item_to_pair(ci);
+ if (map_afrom_cp(parent, &map, cp, request_def, dst_list_def, REQUEST_CURRENT, src_list_def) < 0) {
+ goto error;
+ }
+
+ VERIFY_MAP(map);
+
+ /*
+ * Check the types in the map are valid
+ */
+ if (validate && (validate(map, ctx) < 0)) goto error;
+
+ parent = *tail = map;
+ tail = &(map->next);
+ }
+
+ return 0;
+
+}
+
+
+/** Convert strings to vp_map_t
+ *
+ * Treatment of operands depends on quotation, barewords are treated
+ * as attribute references, double quoted values are treated as
+ * expandable strings, single quoted values are treated as literal
+ * strings.
+ *
+ * Return must be freed with talloc_free
+ *
+ * @param[in] ctx for talloc
+ * @param[out] out Where to store the head of the map.
+ * @param[in] lhs of the operation
+ * @param[in] lhs_type type of the LHS string
+ * @param[in] op the operation to perform
+ * @param[in] rhs of the operation
+ * @param[in] rhs_type type of the RHS string
+ * @param[in] dst_request_def The default request to insert unqualified
+ * attributes into.
+ * @param[in] dst_list_def The default list to insert unqualified attributes
+ * into.
+ * @param[in] src_request_def The default request to resolve attribute
+ * references in.
+ * @param[in] src_list_def The default list to resolve unqualified attributes
+ * in.
+ * @return vp_map_t if successful or NULL on error.
+ */
+int map_afrom_fields(TALLOC_CTX *ctx, vp_map_t **out, char const *lhs, FR_TOKEN lhs_type,
+ FR_TOKEN op, char const *rhs, FR_TOKEN rhs_type,
+ request_refs_t dst_request_def,
+ pair_lists_t dst_list_def,
+ request_refs_t src_request_def,
+ pair_lists_t src_list_def)
+{
+ ssize_t slen;
+ vp_map_t *map;
+
+ map = talloc_zero(ctx, vp_map_t);
+
+ slen = tmpl_afrom_str(map, &map->lhs, lhs, strlen(lhs), lhs_type, dst_request_def, dst_list_def, true);
+ if (slen < 0) {
+ error:
+ talloc_free(map);
+ return -1;
+ }
+
+ map->op = op;
+
+ if ((map->lhs->type == TMPL_TYPE_ATTR) &&
+ map->lhs->tmpl_da->flags.is_unknown &&
+ map_cast_from_hex(map, rhs_type, rhs)) {
+ return 0;
+ }
+
+ slen = tmpl_afrom_str(map, &map->rhs, rhs, strlen(rhs), rhs_type, src_request_def, src_list_def, true);
+ if (slen < 0) goto error;
+
+ VERIFY_MAP(map);
+
+ *out = map;
+
+ return 0;
+}
+
+/** Convert a value pair string to valuepair map
+ *
+ * Takes a valuepair string with list and request qualifiers and converts it into a
+ * vp_map_t.
+ *
+ * @param ctx where to allocate the map.
+ * @param out Where to write the new map (must be freed with talloc_free()).
+ * @param vp_str string to parse.
+ * @param dst_request_def to use if attribute isn't qualified.
+ * @param dst_list_def to use if attribute isn't qualified.
+ * @param src_request_def to use if attribute isn't qualified.
+ * @param src_list_def to use if attribute isn't qualified.
+ * @return 0 on success, < 0 on error.
+ */
+int map_afrom_attr_str(TALLOC_CTX *ctx, vp_map_t **out, char const *vp_str,
+ request_refs_t dst_request_def, pair_lists_t dst_list_def,
+ request_refs_t src_request_def, pair_lists_t src_list_def)
+{
+ char const *p = vp_str;
+ FR_TOKEN quote;
+
+ VALUE_PAIR_RAW raw;
+ vp_map_t *map = NULL;
+
+ quote = gettoken(&p, raw.l_opand, sizeof(raw.l_opand), false);
+ switch (quote) {
+ case T_BARE_WORD:
+ break;
+
+ case T_INVALID:
+ error:
+ return -1;
+
+ default:
+ fr_strerror_printf("Left operand must be an attribute");
+ return -1;
+ }
+
+ raw.op = getop(&p);
+ if (raw.op == T_INVALID) goto error;
+
+ raw.quote = gettoken(&p, raw.r_opand, sizeof(raw.r_opand), false);
+ if (raw.quote == T_INVALID) goto error;
+ if (!fr_str_tok[raw.quote]) {
+ fr_strerror_printf("Right operand must be an attribute or string");
+ return -1;
+ }
+
+ if (map_afrom_fields(ctx, &map, raw.l_opand, T_BARE_WORD, raw.op, raw.r_opand, raw.quote,
+ dst_request_def, dst_list_def, src_request_def, src_list_def) < 0) {
+ return -1;
+ }
+
+ rad_assert(map != NULL);
+ *out = map;
+
+ VERIFY_MAP(map);
+
+ return 0;
+}
+
+/** Compare map where LHS is #TMPL_TYPE_ATTR
+ *
+ * Compares maps by lhs->tmpl_da, lhs->tmpl_tag, lhs->tmpl_num
+ *
+ * @note both map->lhs must be #TMPL_TYPE_ATTR.
+ *
+ * @param a first map.
+ * @param b second map.
+ */
+int8_t map_cmp_by_lhs_attr(void const *a, void const *b)
+{
+ vp_tmpl_t const *my_a = ((vp_map_t const *)a)->lhs;
+ vp_tmpl_t const *my_b = ((vp_map_t const *)b)->lhs;
+
+ VERIFY_TMPL(my_a);
+ VERIFY_TMPL(my_b);
+
+ uint8_t cmp;
+
+ rad_assert(my_a->type == TMPL_TYPE_ATTR);
+ rad_assert(my_b->type == TMPL_TYPE_ATTR);
+
+ cmp = fr_pointer_cmp(my_a->tmpl_da, my_b->tmpl_da);
+ if (cmp != 0) return cmp;
+
+ if (my_a->tmpl_tag < my_b->tmpl_tag) return -1;
+
+ if (my_a->tmpl_tag > my_b->tmpl_tag) return 1;
+
+ if (my_a->tmpl_num < my_b->tmpl_num) return -1;
+
+ if (my_a->tmpl_num > my_b->tmpl_num) return 1;
+
+ return 0;
+}
+
+static void map_sort_split(vp_map_t *source, vp_map_t **front, vp_map_t **back)
+{
+ vp_map_t *fast;
+ vp_map_t *slow;
+
+ /*
+ * Stopping condition - no more elements left to split
+ */
+ if (!source || !source->next) {
+ *front = source;
+ *back = NULL;
+
+ return;
+ }
+
+ /*
+ * Fast advances twice as fast as slow, so when it gets to the end,
+ * slow will point to the middle of the linked list.
+ */
+ slow = source;
+ fast = source->next;
+
+ while (fast) {
+ fast = fast->next;
+ if (fast) {
+ slow = slow->next;
+ fast = fast->next;
+ }
+ }
+
+ *front = source;
+ *back = slow->next;
+ slow->next = NULL;
+}
+
+static vp_map_t *map_sort_merge(vp_map_t *a, vp_map_t *b, fr_cmp_t cmp)
+{
+ vp_map_t *result = NULL;
+
+ if (!a) return b;
+ if (!b) return a;
+
+ /*
+ * Compare things in the maps
+ */
+ if (cmp(a, b) <= 0) {
+ result = a;
+ result->next = map_sort_merge(a->next, b, cmp);
+ } else {
+ result = b;
+ result->next = map_sort_merge(a, b->next, cmp);
+ }
+
+ return result;
+}
+
+/** Sort a linked list of #vp_map_t using merge sort
+ *
+ * @param[in,out] maps List of #vp_map_t to sort.
+ * @param[in] cmp to sort with
+ */
+void map_sort(vp_map_t **maps, fr_cmp_t cmp)
+{
+ vp_map_t *head = *maps;
+ vp_map_t *a;
+ vp_map_t *b;
+
+ /*
+ * If there's 0-1 elements it must already be sorted.
+ */
+ if (!head || !head->next) {
+ return;
+ }
+
+ map_sort_split(head, &a, &b); /* Split into sublists */
+ map_sort(&a, cmp); /* Traverse left */
+ map_sort(&b, cmp); /* Traverse right */
+
+ /*
+ * merge the two sorted lists together
+ */
+ *maps = map_sort_merge(a, b, cmp);
+}
+
+/** Process map which has exec as a src
+ *
+ * Evaluate maps which specify exec as a src. This may be used by various sorts of update sections,
+ * and so has been broken out into it's own function.
+ *
+ * @param[in,out] ctx to allocate new #VALUE_PAIR (s) in.
+ * @param[out] out Where to write the #VALUE_PAIR (s).
+ * @param[in] request structure (used only for talloc).
+ * @param[in] map the map. The LHS (dst) must be TMPL_TYPE_ATTR or TMPL_TYPE_LIST. The RHS (src)
+ * must be TMPL_TYPE_EXEC.
+ * @return -1 on failure, 0 on success.
+ */
+static int map_exec_to_vp(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map)
+{
+ int result;
+ char *expanded = NULL;
+ char answer[1024];
+ VALUE_PAIR **input_pairs = NULL;
+ VALUE_PAIR *output_pairs = NULL;
+
+ *out = NULL;
+
+ VERIFY_MAP(map);
+
+ rad_assert(map->rhs->type == TMPL_TYPE_EXEC);
+ rad_assert((map->lhs->type == TMPL_TYPE_ATTR) || (map->lhs->type == TMPL_TYPE_LIST));
+
+ /*
+ * We always put the request pairs into the environment
+ */
+ input_pairs = radius_list(request, PAIR_LIST_REQUEST);
+
+ /*
+ * Automagically switch output type depending on our destination
+ * If dst is a list, then we create attributes from the output of the program
+ * if dst is an attribute, then we create an attribute of that type and then
+ * call fr_pair_value_from_str on the output of the script.
+ */
+ result = radius_exec_program(ctx, answer, sizeof(answer),
+ (map->lhs->type == TMPL_TYPE_LIST) ? &output_pairs : NULL,
+ request, map->rhs->name, input_pairs ? *input_pairs : NULL,
+ true, true, EXEC_TIMEOUT);
+ talloc_free(expanded);
+ if (result != 0) {
+ talloc_free(output_pairs);
+ return -1;
+ }
+
+ switch (map->lhs->type) {
+ case TMPL_TYPE_LIST:
+ if (!output_pairs) {
+ REDEBUG("No valid attributes received from program");
+ return -2;
+ }
+ *out = output_pairs;
+ return 0;
+
+ case TMPL_TYPE_ATTR:
+ {
+ VALUE_PAIR *vp;
+
+ vp = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
+ if (!vp) return -1;
+ vp->op = map->op;
+ vp->tag = map->lhs->tmpl_tag;
+ if (fr_pair_value_from_str(vp, answer, -1) < 0) {
+ fr_pair_list_free(&vp);
+ return -2;
+ }
+ *out = vp;
+
+ return 0;
+ }
+
+ default:
+ rad_assert(0);
+ }
+
+ return -1;
+}
+
+/** Convert a map to a VALUE_PAIR.
+ *
+ * @param[in,out] ctx to allocate #VALUE_PAIR (s) in.
+ * @param[out] out Where to write the #VALUE_PAIR (s), which may be NULL if not found
+ * @param[in] request The current request.
+ * @param[in] map the map. The LHS (dst) has to be #TMPL_TYPE_ATTR or #TMPL_TYPE_LIST.
+ * @param[in] uctx unused.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+int map_to_vp(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, UNUSED void *uctx)
+{
+ int rcode = 0;
+ ssize_t len;
+ VALUE_PAIR *vp = NULL, *new, *found = NULL;
+ REQUEST *context = request;
+ vp_cursor_t cursor;
+ ssize_t slen;
+ char *str;
+
+ *out = NULL;
+
+ VERIFY_MAP(map);
+ rad_assert(map->lhs != NULL);
+ rad_assert(map->rhs != NULL);
+
+ rad_assert((map->lhs->type == TMPL_TYPE_LIST) || (map->lhs->type == TMPL_TYPE_ATTR));
+
+ /*
+ * Special case for !*, we don't need to parse RHS as this is a unary operator.
+ */
+ if (map->op == T_OP_CMP_FALSE) return 0;
+
+ /*
+ * List to list found, this is a special case because we don't need
+ * to allocate any attributes, just finding the current list, and change
+ * the op.
+ */
+ if ((map->lhs->type == TMPL_TYPE_LIST) && (map->rhs->type == TMPL_TYPE_LIST)) {
+ VALUE_PAIR **from = NULL;
+
+ if (radius_request(&context, map->rhs->tmpl_request) == 0) {
+ from = radius_list(context, map->rhs->tmpl_list);
+ }
+ if (!from) return 0;
+
+ found = fr_pair_list_copy(ctx, *from);
+
+ /*
+ * List to list copy is empty if the src list has no attributes.
+ */
+ if (!found) return 0;
+
+ for (vp = fr_cursor_init(&cursor, &found);
+ vp;
+ vp = fr_cursor_next(&cursor)) {
+ vp->op = T_OP_ADD;
+ }
+
+ *out = found;
+
+ return 0;
+ }
+
+ /*
+ * And parse the RHS
+ */
+ switch (map->rhs->type) {
+ case TMPL_TYPE_XLAT_STRUCT:
+ rad_assert(map->lhs->type == TMPL_TYPE_ATTR);
+ rad_assert(map->lhs->tmpl_da); /* We need to know which attribute to create */
+ rad_assert(map->rhs->tmpl_xlat != NULL);
+
+ new = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
+ if (!new) return -1;
+
+ str = NULL;
+ slen = radius_axlat_struct(&str, request, map->rhs->tmpl_xlat, NULL, NULL);
+ if (slen < 0) {
+ rcode = slen;
+ goto error;
+ }
+
+ /*
+ * We do the debug printing because radius_axlat_struct
+ * doesn't have access to the original string. It's been
+ * mangled during the parsing to xlat_exp_t
+ */
+ RDEBUG2("EXPAND %s", map->rhs->name);
+ RDEBUG2(" --> %s", str);
+
+ rcode = fr_pair_value_from_str(new, str, -1);
+ talloc_free(str);
+ if (rcode < 0) {
+ fr_pair_list_free(&new);
+ goto error;
+ }
+ new->op = map->op;
+ new->tag = map->lhs->tmpl_tag;
+ *out = new;
+ break;
+
+ case TMPL_TYPE_XLAT:
+ rad_assert(map->lhs->type == TMPL_TYPE_ATTR);
+ rad_assert(map->lhs->tmpl_da); /* We need to know which attribute to create */
+
+ new = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
+ if (!new) return -1;
+
+ str = NULL;
+ slen = radius_axlat(&str, request, map->rhs->name, NULL, NULL);
+ if (slen < 0) {
+ rcode = slen;
+ goto error;
+ }
+
+ rcode = fr_pair_value_from_str(new, str, -1);
+ talloc_free(str);
+ if (rcode < 0) {
+ fr_pair_list_free(&new);
+ goto error;
+ }
+ new->op = map->op;
+ new->tag = map->lhs->tmpl_tag;
+ *out = new;
+ break;
+
+ case TMPL_TYPE_LITERAL:
+ rad_assert(map->lhs->type == TMPL_TYPE_ATTR);
+ rad_assert(map->lhs->tmpl_da); /* We need to know which attribute to create */
+
+ new = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
+ if (!new) return -1;
+
+ if (fr_pair_value_from_str(new, map->rhs->name, -1) < 0) {
+ rcode = 0;
+ goto error;
+ }
+ new->op = map->op;
+ new->tag = map->lhs->tmpl_tag;
+ *out = new;
+ break;
+
+ case TMPL_TYPE_ATTR:
+ {
+ vp_cursor_t from;
+
+ rad_assert(((map->lhs->type == TMPL_TYPE_ATTR) && map->lhs->tmpl_da) ||
+ ((map->lhs->type == TMPL_TYPE_LIST) && !map->lhs->tmpl_da));
+
+ /*
+ * @todo should log error, and return -1 for v3.1 (causes update to fail)
+ */
+ if (tmpl_copy_vps(ctx, &found, request, map->rhs) < 0) return 0;
+
+ vp = fr_cursor_init(&from, &found);
+
+ /*
+ * Src/Dst attributes don't match, convert src attributes
+ * to match dst.
+ */
+ if ((map->lhs->type == TMPL_TYPE_ATTR) &&
+ (map->rhs->tmpl_da->type != map->lhs->tmpl_da->type)) {
+ vp_cursor_t to;
+
+ (void) fr_cursor_init(&to, out);
+ for (; vp; vp = fr_cursor_next(&from)) {
+ new = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
+ if (!new) return -1;
+
+ len = value_data_cast(new, &new->data, new->da->type, new->da,
+ vp->da->type, vp->da, &vp->data, vp->vp_length);
+ if (len < 0) {
+ REDEBUG("Attribute conversion failed: %s", fr_strerror());
+ fr_pair_list_free(&found);
+ fr_pair_list_free(&new);
+ return -1;
+ }
+
+ new->vp_length = len;
+ vp = fr_cursor_remove(&from);
+ talloc_free(vp);
+
+ if (new->da->type == PW_TYPE_STRING) {
+ rad_assert(new->vp_strvalue != NULL);
+ }
+
+ new->op = map->op;
+ new->tag = map->lhs->tmpl_tag;
+ fr_cursor_insert(&to, new);
+ }
+ return 0;
+ }
+
+ /*
+ * Otherwise we just need to fixup the attribute types
+ * and operators
+ */
+ for (; vp; vp = fr_cursor_next(&from)) {
+ vp->da = map->lhs->tmpl_da;
+ vp->op = map->op;
+ vp->tag = map->lhs->tmpl_tag;
+ }
+ *out = found;
+ }
+ break;
+
+ case TMPL_TYPE_DATA:
+ rad_assert(map->lhs->tmpl_da);
+ rad_assert(map->lhs->type == TMPL_TYPE_ATTR);
+ rad_assert(map->lhs->tmpl_da->type == map->rhs->tmpl_data_type);
+
+ new = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
+ if (!new) return -1;
+
+ len = value_data_copy(new, &new->data, new->da->type, &map->rhs->tmpl_data_value,
+ map->rhs->tmpl_data_length);
+ if (len < 0) goto error;
+
+ new->vp_length = len;
+ new->op = map->op;
+ new->tag = map->lhs->tmpl_tag;
+ *out = new;
+
+ VERIFY_MAP(map);
+ break;
+
+ /*
+ * This essentially does the same as rlm_exec xlat, except it's non-configurable.
+ * It's only really here as a convenience for people who expect the contents of
+ * backticks to be executed in a shell.
+ *
+ * exec string is xlat expanded and arguments are shell escaped.
+ */
+ case TMPL_TYPE_EXEC:
+ return map_exec_to_vp(ctx, out, request, map);
+
+ default:
+ rad_assert(0); /* Should have been caught at parse time */
+
+ error:
+ fr_pair_list_free(&vp);
+ return rcode;
+ }
+
+ return 0;
+}
+
+#define DEBUG_OVERWRITE(_old, _new) \
+do {\
+ if (RDEBUG_ENABLED3) {\
+ char *old = vp_aprints_value(request, _old, '"');\
+ char *new = vp_aprints_value(request, _new, '"');\
+ RDEBUG3("Overwriting value \"%s\" with \"%s\"", old, new);\
+ talloc_free(old);\
+ talloc_free(new);\
+ }\
+} while (0)
+
+/** Convert vp_map_t to VALUE_PAIR(s) and add them to a REQUEST.
+ *
+ * Takes a single vp_map_t, resolves request and list identifiers
+ * to pointers in the current request, then attempts to retrieve module
+ * specific value(s) using callback, and adds the resulting values to the
+ * correct request/list.
+ *
+ * @param request The current request.
+ * @param map specifying destination attribute and location and src identifier.
+ * @param func to retrieve module specific values and convert them to
+ * VALUE_PAIRS.
+ * @param ctx to be passed to func.
+ * @return -1 if the operation failed, -2 in the source attribute wasn't valid, 0 on success.
+ */
+int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t func, void *ctx)
+{
+ int rcode = 0;
+ int num;
+ VALUE_PAIR **list, *vp, *dst, *head = NULL;
+ bool found = false;
+ REQUEST *context;
+ TALLOC_CTX *parent;
+ vp_cursor_t dst_list, src_list;
+
+ vp_map_t exp_map;
+ vp_tmpl_t exp_lhs;
+
+ VERIFY_MAP(map);
+ rad_assert(map->lhs != NULL);
+ rad_assert(map->rhs != NULL);
+
+ /*
+ * Preprocessing of the LHS of the map.
+ */
+ switch (map->lhs->type) {
+ /*
+ * Already in the correct form.
+ */
+ case TMPL_TYPE_LIST:
+ case TMPL_TYPE_ATTR:
+ break;
+
+ /*
+ * Everything else gets expanded, then re-parsed as an
+ * attribute reference.
+ */
+ case TMPL_TYPE_XLAT:
+ case TMPL_TYPE_XLAT_STRUCT:
+ case TMPL_TYPE_EXEC:
+ {
+ char *attr;
+ ssize_t slen;
+
+ slen = tmpl_aexpand(request, &attr, request, map->lhs, NULL, NULL);
+ if (slen <= 0) {
+ REDEBUG("Left side \"%.*s\" of map failed expansion", (int)map->lhs->len, map->lhs->name);
+ return -1;
+ }
+
+ slen = tmpl_from_attr_str(&exp_lhs, attr, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false) ;
+ if (slen <= 0) {
+ REDEBUG("Left side \"%.*s\" expansion not an attribute reference: %s",
+ (int)map->lhs->len, map->lhs->name, fr_strerror());
+ talloc_free(attr);
+ return -1;
+ }
+ rad_assert((exp_lhs.type == TMPL_TYPE_ATTR) || (exp_lhs.type == TMPL_TYPE_LIST));
+
+ memcpy(&exp_map, map, sizeof(exp_map));
+ exp_map.lhs = &exp_lhs;
+ map = &exp_map;
+ }
+ break;
+
+ default:
+ rad_assert(0);
+ break;
+ }
+
+
+ /*
+ * Sanity check inputs. We can have a list or attribute
+ * as a destination.
+ */
+ if ((map->lhs->type != TMPL_TYPE_LIST) &&
+ (map->lhs->type != TMPL_TYPE_ATTR)) {
+ REDEBUG("Left side \"%.*s\" of map should be an attr or list but is an %s",
+ (int)map->lhs->len, map->lhs->name,
+ fr_int2str(tmpl_names, map->lhs->type, "<INVALID>"));
+ return -2;
+ }
+
+ context = request;
+ if (radius_request(&context, map->lhs->tmpl_request) < 0) {
+ REDEBUG("Mapping \"%.*s\" -> \"%.*s\" invalid in this context",
+ (int)map->rhs->len, map->rhs->name, (int)map->lhs->len, map->lhs->name);
+ return -2;
+ }
+
+ /*
+ * If there's no CoA packet and we're updating it,
+ * auto-allocate it.
+ */
+ if (((map->lhs->tmpl_list == PAIR_LIST_COA) ||
+ (map->lhs->tmpl_list == PAIR_LIST_DM)) && !request->coa) {
+ if (request->parent) {
+ REDEBUG("You can only do 'update coa' when processing a packet which was received from the network");
+ return -2;
+ }
+
+ if ((request->packet->code == PW_CODE_COA_REQUEST) ||
+ (request->packet->code == PW_CODE_DISCONNECT_REQUEST)) {
+ REDEBUG("You cannot do 'update coa' when processing a CoA / Disconnect request. Use 'update request' instead.");
+ return -2;
+ }
+
+ if (!request_alloc_coa(context)) {
+ REDEBUG("Failed to create a CoA/Disconnect Request message");
+ return -2;
+ }
+ context->coa->proxy->code = (map->lhs->tmpl_list == PAIR_LIST_COA) ?
+ PW_CODE_COA_REQUEST :
+ PW_CODE_DISCONNECT_REQUEST;
+ }
+
+ list = radius_list(context, map->lhs->tmpl_list);
+ if (!list) {
+ REDEBUG("Mapping \"%.*s\" -> \"%.*s\" invalid in this context",
+ (int)map->rhs->len, map->rhs->name, (int)map->lhs->len, map->lhs->name);
+
+ return -2;
+ }
+
+ parent = radius_list_ctx(context, map->lhs->tmpl_list);
+ if (!parent) {
+ REDEBUG("Unable to set parent list");
+ return -1;
+ }
+
+ /*
+ * The callback should either return -1 to signify operations error,
+ * -2 when it can't find the attribute or list being referenced, or
+ * 0 to signify success. It may return "success", but still have no
+ * VPs to work with.
+ */
+ if (map->rhs->type != TMPL_TYPE_NULL) {
+ rcode = func(parent, &head, request, map, ctx);
+ if (rcode < 0) {
+ rad_assert(!head);
+ return rcode;
+ }
+ if (!head) {
+ RDEBUG2("No attributes updated for RHS %s", map->rhs->name);
+ return rcode;
+ }
+ } else {
+ if (rad_debug_lvl) map_debug_log(request, map, NULL);
+ }
+
+ /*
+ * Print the VPs
+ */
+ for (vp = fr_cursor_init(&src_list, &head);
+ vp;
+ vp = fr_cursor_next(&src_list)) {
+ VERIFY_VP(vp);
+
+ if (rad_debug_lvl) map_debug_log(request, map, vp);
+ }
+
+ /*
+ * The destination is a list (which is a completely different set of operations)
+ */
+ if (map->lhs->type == TMPL_TYPE_LIST) {
+ switch (map->op) {
+ case T_OP_CMP_FALSE:
+ /* We don't need the src VPs (should just be 'ANY') */
+ rad_assert(!head);
+
+ /* Clear the entire dst list */
+ fr_pair_list_free(list);
+
+ if (map->lhs->tmpl_list == PAIR_LIST_REQUEST) {
+ context->username = NULL;
+ context->password = NULL;
+ }
+ return 0;
+
+ case T_OP_SET:
+ if (map->rhs->type == TMPL_TYPE_LIST) {
+ fr_pair_list_free(list);
+ *list = head;
+ head = NULL;
+ } else { /* FALL-THROUGH */
+ case T_OP_EQ:
+ rad_assert(map->rhs->type == TMPL_TYPE_EXEC);
+ /* FALL-THROUGH */
+ case T_OP_ADD:
+ fr_pair_list_move(parent, list, &head, map->op);
+ fr_pair_list_free(&head);
+ }
+ goto finish;
+ case T_OP_PREPEND:
+ fr_pair_list_move(parent, list, &head, T_OP_PREPEND);
+ fr_pair_list_free(&head);
+ goto finish;
+
+ default:
+ fr_pair_list_free(&head);
+ return -1;
+ }
+ }
+
+ /*
+ * Find the destination attribute. We leave with either
+ * the dst_list and vp pointing to the attribute or the VP
+ * being NULL (no attribute at that index).
+ */
+ num = map->lhs->tmpl_num;
+ (void) fr_cursor_init(&dst_list, list);
+ if ((num != NUM_ANY) && (num > 0)) {
+ while ((dst = fr_cursor_next_by_da(&dst_list, map->lhs->tmpl_da, map->lhs->tmpl_tag))) {
+ if (num <= 0) break;
+ num--;
+ }
+ } else {
+ dst = fr_cursor_next_by_da(&dst_list, map->lhs->tmpl_da, map->lhs->tmpl_tag);
+ }
+ rad_assert(!dst || (map->lhs->tmpl_da == dst->da));
+
+ /*
+ * The destination is an attribute
+ */
+ switch (map->op) {
+ default:
+ break;
+ /*
+ * !* - Remove all attributes which match dst in the specified list.
+ * This doesn't use attributes returned by the func(), and immediately frees them.
+ */
+ case T_OP_CMP_FALSE:
+ /* We don't need the src VPs (should just be 'ANY') */
+ rad_assert(!head);
+ if (!dst) return 0;
+
+ /*
+ * Wildcard: delete all of the matching ones, based on tag.
+ */
+ if (map->lhs->tmpl_num == NUM_ANY) {
+ fr_pair_delete_by_num(list, map->lhs->tmpl_da->attr, map->lhs->tmpl_da->vendor, map->lhs->tmpl_tag);
+ dst = NULL;
+ /*
+ * We've found the Nth one. Delete it, and only it.
+ */
+ } else {
+ dst = fr_cursor_remove(&dst_list);
+ fr_pair_list_free(&dst);
+ }
+
+ /*
+ * Check that the User-Name and User-Password
+ * caches point to the correct attribute.
+ */
+ goto finish;
+
+ /*
+ * -= - Delete attributes in the dst list which match any of the
+ * src_list attributes.
+ *
+ * This operation has two modes:
+ * - If map->lhs->tmpl_num > 0, we check each of the src_list attributes against
+ * the dst attribute, to see if any of their values match.
+ * - If map->lhs->tmpl_num == NUM_ANY, we compare all instances of the dst attribute
+ * against each of the src_list attributes.
+ */
+ case T_OP_SUB:
+ /* We didn't find any attributes earlier */
+ if (!dst) {
+ fr_pair_list_free(&head);
+ return 0;
+ }
+
+ /*
+ * Instance specific[n] delete
+ */
+ if (map->lhs->tmpl_num != NUM_ANY) {
+ for (vp = fr_cursor_first(&src_list);
+ vp;
+ vp = fr_cursor_next(&src_list)) {
+ head->op = T_OP_CMP_EQ;
+ rcode = radius_compare_vps(request, vp, dst);
+ if (rcode == 0) {
+ dst = fr_cursor_remove(&dst_list);
+ fr_pair_list_free(&dst);
+ found = true;
+ }
+ }
+ fr_pair_list_free(&head);
+ if (!found) return 0;
+ goto finish;
+ }
+
+ /*
+ * All instances[*] delete
+ */
+ for (dst = fr_cursor_current(&dst_list);
+ dst;
+ dst = fr_cursor_next_by_da(&dst_list, map->lhs->tmpl_da, map->lhs->tmpl_tag)) {
+ for (vp = fr_cursor_first(&src_list);
+ vp;
+ vp = fr_cursor_next(&src_list)) {
+ head->op = T_OP_CMP_EQ;
+ rcode = radius_compare_vps(request, vp, dst);
+ if (rcode == 0) {
+ dst = fr_cursor_remove(&dst_list);
+ fr_pair_list_free(&dst);
+ found = true;
+ }
+ }
+ }
+ fr_pair_list_free(&head);
+ if (!found) return 0;
+ goto finish;
+ }
+
+ /*
+ * Another fixup pass to set tags on attributes were about to insert
+ */
+ if (map->lhs->tmpl_tag != TAG_ANY) {
+ for (vp = fr_cursor_init(&src_list, &head);
+ vp;
+ vp = fr_cursor_next(&src_list)) {
+ vp->tag = map->lhs->tmpl_tag;
+ }
+ }
+
+ switch (map->op) {
+ /*
+ * = - Set only if not already set
+ */
+ case T_OP_EQ:
+ if (dst) {
+ RDEBUG3("Refusing to overwrite (use :=)");
+ fr_pair_list_free(&head);
+ return 0;
+ }
+
+ /* Insert first instance (if multiple) */
+ fr_cursor_first(&src_list);
+ fr_cursor_insert(&dst_list, fr_cursor_remove(&src_list));
+ /* Free any we didn't insert */
+ fr_pair_list_free(&head);
+ break;
+
+ /*
+ * := - Overwrite existing attribute with last src_list attribute
+ */
+ case T_OP_SET:
+ /* Wind to last instance */
+ fr_cursor_last(&src_list);
+ if (dst) {
+ DEBUG_OVERWRITE(dst, fr_cursor_current(&src_list));
+ dst = fr_cursor_replace(&dst_list, fr_cursor_remove(&src_list));
+ fr_pair_list_free(&dst);
+ } else {
+ fr_cursor_insert(&dst_list, fr_cursor_remove(&src_list));
+ }
+ /* Free any we didn't insert */
+ fr_pair_list_free(&head);
+ break;
+
+ /*
+ * ^= - Prepend src_list attributes to the destination
+ */
+ case T_OP_PREPEND:
+ fr_pair_prepend(list, head);
+ head = NULL;
+ break;
+
+ /*
+ * += - Add all src_list attributes to the destination
+ */
+ case T_OP_ADD:
+ /* Insert all the instances! (if multiple) */
+ fr_pair_add(list, head);
+ head = NULL;
+ break;
+
+ /*
+ * Filter operators
+ */
+ case T_OP_REG_NE:
+ case T_OP_NE:
+ case T_OP_REG_EQ:
+ case T_OP_CMP_EQ:
+ case T_OP_GE:
+ case T_OP_GT:
+ case T_OP_LE:
+ case T_OP_LT:
+ {
+ VALUE_PAIR *a, *b;
+
+ fr_pair_list_sort(&head, fr_pair_cmp_by_da_tag);
+ fr_pair_list_sort(list, fr_pair_cmp_by_da_tag);
+
+ fr_cursor_first(&dst_list);
+
+ for (b = fr_cursor_first(&src_list);
+ b;
+ b = fr_cursor_next(&src_list)) {
+ found = false;
+
+ for (a = fr_cursor_current(&dst_list);
+ a;
+ a = fr_cursor_next(&dst_list)) {
+ int8_t cmp;
+
+ cmp = fr_pair_cmp_by_da_tag(a, b); /* attribute and tag match */
+ if (cmp > 0) break;
+ else if (cmp < 0) continue;
+
+ /*
+ * The LHS exists. We need to
+ * limit it's value based on the
+ * operator, and on the value of
+ * the RHS.
+ */
+ cmp = (value_data_cmp_op(map->op, a->da->type, &a->data, a->vp_length, b->da->type, &b->data, b->vp_length) == 0);
+ if (cmp == 1) switch (map->op) {
+
+ /*
+ * Keep only matching attributes.
+ */
+ default:
+ case T_OP_REG_NE:
+ case T_OP_NE:
+ case T_OP_REG_EQ:
+ case T_OP_CMP_EQ:
+ a = fr_cursor_remove(&dst_list);
+ talloc_free(a);
+ break;
+
+ /*
+ * Keep matching
+ * attribute, and enforce
+ * matching values.
+ */
+ case T_OP_GE:
+ case T_OP_GT:
+ case T_OP_LE:
+ case T_OP_LT:
+ DEBUG_OVERWRITE(a, b);
+ (void) value_data_copy(a, &a->data, a->da->type,
+ &b->data, b->vp_length);
+ found = true;
+ break;
+ }
+ }
+
+ /*
+ * End of the dst list.
+ */
+ if (!a) {
+ if (found) break;
+
+ switch (map->op) {
+ default:
+ break;
+
+ /*
+ * It wasn't found. Insert it with the given value.
+ */
+ case T_OP_GE:
+ case T_OP_GT:
+ case T_OP_LE:
+ case T_OP_LT:
+ (void) fr_cursor_insert(&dst_list, fr_pair_copy(parent, b));
+ break;
+ }
+ break;
+ }
+ }
+ fr_pair_list_free(&head);
+ }
+ break;
+
+ default:
+ rad_assert(0); /* Should have been caught be the caller */
+ return -1;
+ }
+
+finish:
+ rad_assert(!head);
+
+ /*
+ * Update the cached username && password. This is code
+ * we execute on EVERY update (sigh) so that SOME modules
+ * MIGHT NOT have to do the search themselves.
+ *
+ * TBH, we should probably make each module just do the
+ * search themselves.
+ */
+ if (map->lhs->tmpl_list == PAIR_LIST_REQUEST) {
+ context->username = NULL;
+ context->password = NULL;
+
+ for (vp = fr_cursor_init(&src_list, list);
+ vp;
+ vp = fr_cursor_next(&src_list)) {
+
+ if (vp->da->vendor != 0) continue;
+ if (vp->da->flags.has_tag) continue;
+
+ if (!context->username && (vp->da->attr == PW_USER_NAME)) {
+ context->username = vp;
+ continue;
+ }
+
+ if (vp->da->attr == PW_STRIPPED_USER_NAME) {
+ context->username = vp;
+ continue;
+ }
+
+ if (vp->da->attr == PW_USER_PASSWORD) {
+ context->password = vp;
+ continue;
+ }
+ }
+ }
+ return 0;
+}
+
+/** Check whether the destination of a map is currently valid
+ *
+ * @param request The current request.
+ * @param map to check.
+ * @return true if the map resolves to a request and list else false.
+ */
+bool map_dst_valid(REQUEST *request, vp_map_t const *map)
+{
+ REQUEST *context = request;
+
+ VERIFY_MAP(map);
+
+ if (radius_request(&context, map->lhs->tmpl_request) < 0) return false;
+ if (!radius_list(context, map->lhs->tmpl_list)) return false;
+
+ return true;
+}
+
+/** Print a map to a string
+ *
+ * @param[out] buffer for the output string
+ * @param[in] bufsize of the buffer
+ * @param[in] map to print
+ * @return the size of the string printed
+ */
+size_t map_prints(char *buffer, size_t bufsize, vp_map_t const *map)
+{
+ size_t len;
+ DICT_ATTR const *da = NULL;
+ char *p = buffer;
+ char *end = buffer + bufsize;
+
+ VERIFY_MAP(map);
+
+ if (map->lhs->type == TMPL_TYPE_ATTR) da = map->lhs->tmpl_da;
+
+ len = tmpl_prints(buffer, bufsize, map->lhs, da);
+ p += len;
+
+ *(p++) = ' ';
+ strlcpy(p, fr_token_name(map->op), end - p);
+ p += strlen(p);
+ *(p++) = ' ';
+
+ /*
+ * The RHS doesn't matter for many operators
+ */
+ if ((map->op == T_OP_CMP_TRUE) ||
+ (map->op == T_OP_CMP_FALSE)) {
+ strlcpy(p, "ANY", (end - p));
+ p += strlen(p);
+ return p - buffer;
+ }
+
+ rad_assert(map->rhs != NULL);
+
+ if ((map->lhs->type == TMPL_TYPE_ATTR) &&
+ (map->lhs->tmpl_da->type == PW_TYPE_STRING) &&
+ (map->rhs->type == TMPL_TYPE_LITERAL)) {
+ *(p++) = '\'';
+ len = tmpl_prints(p, end - p, map->rhs, da);
+ p += len;
+ *(p++) = '\'';
+ *p = '\0';
+ } else {
+ len = tmpl_prints(p, end - p, map->rhs, da);
+ p += len;
+ }
+
+ return p - buffer;
+}
+
+/*
+ * Debug print a map / VP
+ */
+void map_debug_log(REQUEST *request, vp_map_t const *map, VALUE_PAIR const *vp)
+{
+ char *value;
+ char buffer[1024];
+
+ VERIFY_MAP(map);
+ rad_assert(map->lhs != NULL);
+ rad_assert(map->rhs != NULL);
+
+ rad_assert(vp || (map->rhs->type == TMPL_TYPE_NULL));
+
+ switch (map->rhs->type) {
+ /*
+ * Just print the value being assigned
+ */
+ default:
+ case TMPL_TYPE_LITERAL:
+ vp_prints_value(buffer, sizeof(buffer), vp, map->rhs->quote);
+ value = buffer;
+ break;
+
+ case TMPL_TYPE_XLAT:
+ case TMPL_TYPE_XLAT_STRUCT:
+ vp_prints_value(buffer, sizeof(buffer), vp, map->rhs->quote);
+ value = buffer;
+ break;
+
+ case TMPL_TYPE_DATA:
+ vp_prints_value(buffer, sizeof(buffer), vp, map->rhs->quote);
+ value = buffer;
+ break;
+
+ /*
+ * For the lists, we can't use the original name, and have to
+ * rebuild it using tmpl_prints, for each attribute we're
+ * copying.
+ */
+ case TMPL_TYPE_LIST:
+ {
+ char attr[256];
+ char quote = '\0';
+ vp_tmpl_t vpt;
+ /*
+ * Fudge a temporary tmpl that describes the attribute we're copying
+ * this is a combination of the original list tmpl, and values from
+ * the VALUE_PAIR. This way, we get tag info included.
+ */
+ memcpy(&vpt, map->rhs, sizeof(vpt));
+ vpt.tmpl_da = vp->da;
+ vpt.tmpl_tag = vp->tag;
+ vpt.type = TMPL_TYPE_ATTR;
+
+ /*
+ * Not appropriate to use map->rhs->quote here, as that's the quoting
+ * around the list ref. The attribute value has no quoting, so we choose
+ * the quoting based on the data type, and whether it's printable.
+ */
+ if (vp->da->type == PW_TYPE_STRING) quote = is_printable(vp->vp_strvalue,
+ vp->vp_length) ? '\'' : '"';
+ vp_prints_value(buffer, sizeof(buffer), vp, quote);
+ tmpl_prints(attr, sizeof(attr), &vpt, vp->da);
+ value = talloc_typed_asprintf(request, "%s -> %s", attr, buffer);
+ }
+ break;
+
+ case TMPL_TYPE_ATTR:
+ {
+ char quote = '\0';
+
+ /*
+ * Not appropriate to use map->rhs->quote here, as that's the quoting
+ * around the attr ref. The attribute value has no quoting, so we choose
+ * the quoting based on the data type, and whether it's printable.
+ */
+ if (vp->da->type == PW_TYPE_STRING) quote = is_printable(vp->vp_strvalue,
+ vp->vp_length) ? '\'' : '"';
+ vp_prints_value(buffer, sizeof(buffer), vp, quote);
+ value = talloc_typed_asprintf(request, "%.*s -> %s", (int)map->rhs->len, map->rhs->name, buffer);
+ }
+ break;
+
+ case TMPL_TYPE_NULL:
+ strcpy(buffer, "ANY");
+ value = buffer;
+ break;
+ }
+
+ switch (map->lhs->type) {
+ case TMPL_TYPE_LIST:
+ RDEBUG("%.*s:%s %s %s", (int)map->lhs->len, map->lhs->name, vp ? vp->da->name : "",
+ fr_int2str(fr_tokens, vp ? vp->op : map->op, "<INVALID>"), value);
+ break;
+
+ case TMPL_TYPE_ATTR:
+ RDEBUG("%s %s %s", map->lhs->name,
+ fr_int2str(fr_tokens, vp ? vp->op : map->op, "<INVALID>"), value);
+ break;
+
+ default:
+ RDEBUG("map %s = %s", fr_int2str(tmpl_names, map->lhs->type, "???"), value);
+ break;
+ }
+
+ if (value != buffer) talloc_free(value);
+}