2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * @brief map / template functions
25 * @copyright 2013 The FreeRADIUS server project
26 * @copyright 2013 Alan DeKok <aland@freeradius.org>
31 #include <freeradius-devel/radiusd.h>
32 #include <freeradius-devel/rad_assert.h>
37 static void map_dump(REQUEST *request, vp_map_t const *map)
39 RDEBUG(">>> MAP TYPES LHS: %s, RHS: %s",
40 fr_int2str(tmpl_names, map->lhs->type, "???"),
41 fr_int2str(tmpl_names, map->rhs->type, "???"));
44 RDEBUG(">>> MAP NAMES %s %s", map->lhs->name, map->rhs->name);
50 /** re-parse a map where the lhs is an unknown attribute.
53 * @param map to process.
54 * @param rhs_type quotation type around rhs.
55 * @param rhs string to re-parse.
57 bool map_cast_from_hex(vp_map_t *map, FR_TOKEN rhs_type, char const *rhs)
69 rad_assert(map != NULL);
71 rad_assert(map->lhs != NULL);
72 rad_assert(map->lhs->type == TMPL_TYPE_ATTR);
74 rad_assert(map->rhs == NULL);
75 rad_assert(rhs != NULL);
80 * If the attribute is still unknown, go parse the RHS.
82 da = dict_attrbyvalue(map->lhs->tmpl_da->attr, map->lhs->tmpl_da->vendor);
83 if (!da || da->flags.is_unknown) return false;
86 * If the RHS is something OTHER than an octet
87 * string, go parse it as that.
89 if (rhs_type != T_BARE_WORD) return false;
90 if ((rhs[0] != '0') || (tolower((int)rhs[1]) != 'x')) return false;
91 if (!rhs[2]) return false;
93 len = strlen(rhs + 2);
95 ptr = talloc_array(map, uint8_t, len >> 1);
96 if (!ptr) return false;
98 len = fr_hex2bin(ptr, len >> 1, rhs + 2, len);
101 * If we can't parse it, or if it's malformed,
102 * it's still unknown.
104 rlen = data2vp(NULL, NULL, NULL, NULL, da, ptr, len, len, &vp);
107 if (rlen < 0) return false;
109 if ((size_t) rlen < len) {
111 fr_pair_list_free(&vp);
116 * Was still parsed as an unknown attribute.
118 if (vp->da->flags.is_unknown) goto free_vp;
121 * Set the RHS to the PARSED name, not the crap octet
122 * string which was input.
124 map->rhs = tmpl_alloc(map, TMPL_TYPE_DATA, NULL, 0);
125 if (!map->rhs) goto free_vp;
127 map->rhs->tmpl_data_type = da->type;
128 map->rhs->tmpl_data_length = vp->vp_length;
129 if (vp->da->flags.is_pointer) {
130 if (vp->da->type == PW_TYPE_STRING) {
131 map->rhs->tmpl_data_value.ptr = talloc_bstrndup(map->rhs, vp->data.ptr, vp->vp_length);
133 map->rhs->tmpl_data_value.ptr = talloc_memdup(map->rhs, vp->data.ptr, vp->vp_length);
136 memcpy(&map->rhs->tmpl_data_value, &vp->data, sizeof(map->rhs->tmpl_data_value));
138 map->rhs->name = vp_aprints_value(map->rhs, vp, '"');
139 map->rhs->len = talloc_array_length(map->rhs->name) - 1;
142 * Set the LHS to the REAL attribute name.
144 vpt = tmpl_alloc(map, TMPL_TYPE_ATTR, map->lhs->tmpl_da->name, -1);
145 memcpy(&vpt->data.attribute, &map->lhs->data.attribute, sizeof(vpt->data.attribute));
149 * Be sure to keep the "&control:" or "control:" prefix.
150 * If it's there, we re-generate it from whatever was in
151 * the original name, including the '&'.
155 len = radius_list_name(&list, p, PAIR_LIST_UNKNOWN);
157 if (list != PAIR_LIST_UNKNOWN) {
158 rad_const_free(vpt->name);
160 vpt->name = talloc_asprintf(vpt, "%.*s:%s",
161 (int) len, map->lhs->name,
162 map->lhs->tmpl_da->name);
163 vpt->len = strlen(vpt->name);
166 talloc_free(map->lhs);
169 fr_pair_list_free(&vp);
176 /** Convert CONFIG_PAIR (which may contain refs) to vp_map_t.
178 * Treats the left operand as an attribute reference
179 * @verbatim<request>.<list>.<attribute>@endverbatim
181 * Treatment of left operand depends on quotation, barewords are treated as
182 * attribute references, double quoted values are treated as expandable strings,
183 * single quoted values are treated as literal strings.
185 * Return must be freed with talloc_free
187 * @param[in] ctx for talloc.
188 * @param[in] out Where to write the pointer to the new value_pair_map_struct.
189 * @param[in] cp to convert to map.
190 * @param[in] dst_request_def The default request to insert unqualified
192 * @param[in] dst_list_def The default list to insert unqualified attributes
194 * @param[in] src_request_def The default request to resolve attribute
196 * @param[in] src_list_def The default list to resolve unqualified attributes
198 * @return vp_map_t if successful or NULL on error.
200 int map_afrom_cp(TALLOC_CTX *ctx, vp_map_t **out, CONF_PAIR *cp,
201 request_refs_t dst_request_def, pair_lists_t dst_list_def,
202 request_refs_t src_request_def, pair_lists_t src_list_def)
205 char const *attr, *value;
213 map = talloc_zero(ctx, vp_map_t);
214 map->op = cf_pair_operator(cp);
215 map->ci = cf_pair_to_item(cp);
217 attr = cf_pair_attr(cp);
218 value = cf_pair_value(cp);
220 cf_log_err_cp(cp, "Missing attribute value");
225 * LHS may be an expansion (that expands to an attribute reference)
226 * or an attribute reference. Quoting determines which it is.
228 type = cf_pair_attr_type(cp);
230 case T_DOUBLE_QUOTED_STRING:
231 case T_BACK_QUOTED_STRING:
232 slen = tmpl_afrom_str(ctx, &map->lhs, attr, talloc_array_length(attr) - 1,
233 type, dst_request_def, dst_list_def, true);
238 fr_canonicalize_error(ctx, &spaces, &text, slen, attr);
239 cf_log_err_cp(cp, "%s", text);
240 cf_log_err_cp(cp, "%s^ %s", spaces, fr_strerror());
249 slen = tmpl_afrom_attr_str(ctx, &map->lhs, attr, dst_request_def, dst_list_def, true, true);
251 cf_log_err_cp(cp, "Failed parsing attribute reference");
256 if (tmpl_define_unknown_attr(map->lhs) < 0) {
257 cf_log_err_cp(cp, "Failed creating attribute %s: %s",
258 map->lhs->name, fr_strerror());
266 * RHS might be an attribute reference.
268 type = cf_pair_value_type(cp);
270 if ((map->lhs->type == TMPL_TYPE_ATTR) &&
271 map->lhs->tmpl_da->flags.is_unknown &&
272 !map_cast_from_hex(map, type, value)) {
276 slen = tmpl_afrom_str(map, &map->rhs, value, strlen(value), type, src_request_def, src_list_def, true);
277 if (slen < 0) goto marker;
278 if (tmpl_define_unknown_attr(map->rhs) < 0) {
279 cf_log_err_cp(cp, "Failed creating attribute %s: %s", map->rhs->name, fr_strerror());
284 cf_log_err_cp(cp, "%s", fr_strerror());
289 * We cannot assign a count to an attribute. That must
290 * be done in an xlat.
292 if ((map->rhs->type == TMPL_TYPE_ATTR) &&
293 (map->rhs->tmpl_num == NUM_COUNT)) {
294 cf_log_err_cp(cp, "Cannot assign from a count");
309 /** Convert an 'update' config section into an attribute map.
311 * Uses 'name2' of section to set default request and lists.
313 * @param[in] cs the update section
314 * @param[out] out Where to store the head of the map.
315 * @param[in] dst_list_def The default destination list, usually dictated by
316 * the section the module is being called in.
317 * @param[in] src_list_def The default source list, usually dictated by the
318 * section the module is being called in.
319 * @param[in] validate map using this callback (may be NULL).
320 * @param[in] ctx to pass to callback.
321 * @param[in] max number of mappings to process.
322 * @return -1 on error, else 0.
324 int map_afrom_cs(vp_map_t **out, CONF_SECTION *cs,
325 pair_lists_t dst_list_def, pair_lists_t src_list_def,
326 map_validate_t validate, void *ctx,
329 char const *cs_list, *p;
331 request_refs_t request_def = REQUEST_CURRENT;
336 unsigned int total = 0;
337 vp_map_t **tail, *map;
346 * The first map has cs as the parent.
347 * The rest have the previous map as the parent.
351 ci = cf_section_to_item(cs);
353 cs_list = p = cf_section_name2(cs);
355 p += radius_request_name(&request_def, p, REQUEST_CURRENT);
356 if (request_def == REQUEST_UNKNOWN) {
357 cf_log_err(ci, "Default request specified in mapping section is invalid");
361 dst_list_def = fr_str2int(pair_lists, p, PAIR_LIST_UNKNOWN);
362 if (dst_list_def == PAIR_LIST_UNKNOWN) {
363 cf_log_err(ci, "Default list \"%s\" specified "
364 "in mapping section is invalid", p);
369 for (ci = cf_item_find_next(cs, NULL);
371 ci = cf_item_find_next(cs, ci)) {
372 if (total++ == max) {
373 cf_log_err(ci, "Map size exceeded");
379 if (!cf_item_is_pair(ci)) {
380 cf_log_err(ci, "Entry is not in \"attribute = value\" format");
384 cp = cf_item_to_pair(ci);
385 if (map_afrom_cp(parent, &map, cp, request_def, dst_list_def, REQUEST_CURRENT, src_list_def) < 0) {
392 * Check the types in the map are valid
394 if (validate && (validate(map, ctx) < 0)) goto error;
396 parent = *tail = map;
405 /** Convert strings to vp_map_t
407 * Treatment of operands depends on quotation, barewords are treated
408 * as attribute references, double quoted values are treated as
409 * expandable strings, single quoted values are treated as literal
412 * Return must be freed with talloc_free
414 * @param[in] ctx for talloc
415 * @param[out] out Where to store the head of the map.
416 * @param[in] lhs of the operation
417 * @param[in] lhs_type type of the LHS string
418 * @param[in] op the operation to perform
419 * @param[in] rhs of the operation
420 * @param[in] rhs_type type of the RHS string
421 * @param[in] dst_request_def The default request to insert unqualified
423 * @param[in] dst_list_def The default list to insert unqualified attributes
425 * @param[in] src_request_def The default request to resolve attribute
427 * @param[in] src_list_def The default list to resolve unqualified attributes
429 * @return vp_map_t if successful or NULL on error.
431 int map_afrom_fields(TALLOC_CTX *ctx, vp_map_t **out, char const *lhs, FR_TOKEN lhs_type,
432 FR_TOKEN op, char const *rhs, FR_TOKEN rhs_type,
433 request_refs_t dst_request_def,
434 pair_lists_t dst_list_def,
435 request_refs_t src_request_def,
436 pair_lists_t src_list_def)
441 map = talloc_zero(ctx, vp_map_t);
443 slen = tmpl_afrom_str(map, &map->lhs, lhs, strlen(lhs), lhs_type, dst_request_def, dst_list_def, true);
452 if ((map->lhs->type == TMPL_TYPE_ATTR) &&
453 map->lhs->tmpl_da->flags.is_unknown &&
454 map_cast_from_hex(map, rhs_type, rhs)) {
458 slen = tmpl_afrom_str(map, &map->rhs, rhs, strlen(rhs), rhs_type, src_request_def, src_list_def, true);
459 if (slen < 0) goto error;
468 /** Convert a value pair string to valuepair map
470 * Takes a valuepair string with list and request qualifiers and converts it into a
473 * @param ctx where to allocate the map.
474 * @param out Where to write the new map (must be freed with talloc_free()).
475 * @param vp_str string to parse.
476 * @param dst_request_def to use if attribute isn't qualified.
477 * @param dst_list_def to use if attribute isn't qualified.
478 * @param src_request_def to use if attribute isn't qualified.
479 * @param src_list_def to use if attribute isn't qualified.
480 * @return 0 on success, < 0 on error.
482 int map_afrom_attr_str(TALLOC_CTX *ctx, vp_map_t **out, char const *vp_str,
483 request_refs_t dst_request_def, pair_lists_t dst_list_def,
484 request_refs_t src_request_def, pair_lists_t src_list_def)
486 char const *p = vp_str;
490 vp_map_t *map = NULL;
492 quote = gettoken(&p, raw.l_opand, sizeof(raw.l_opand), false);
502 fr_strerror_printf("Left operand must be an attribute");
507 if (raw.op == T_INVALID) goto error;
509 raw.quote = gettoken(&p, raw.r_opand, sizeof(raw.r_opand), false);
510 if (raw.quote == T_INVALID) goto error;
511 if (!fr_str_tok[raw.quote]) {
512 fr_strerror_printf("Right operand must be an attribute or string");
516 if (map_afrom_fields(ctx, &map, raw.l_opand, T_BARE_WORD, raw.op, raw.r_opand, raw.quote,
517 dst_request_def, dst_list_def, src_request_def, src_list_def) < 0) {
521 rad_assert(map != NULL);
529 /** Compare map where LHS is #TMPL_TYPE_ATTR
531 * Compares maps by lhs->tmpl_da, lhs->tmpl_tag, lhs->tmpl_num
533 * @note both map->lhs must be #TMPL_TYPE_ATTR.
535 * @param a first map.
536 * @param b second map.
538 int8_t map_cmp_by_lhs_attr(void const *a, void const *b)
540 vp_tmpl_t const *my_a = ((vp_map_t const *)a)->lhs;
541 vp_tmpl_t const *my_b = ((vp_map_t const *)b)->lhs;
548 rad_assert(my_a->type == TMPL_TYPE_ATTR);
549 rad_assert(my_b->type == TMPL_TYPE_ATTR);
551 cmp = fr_pointer_cmp(my_a->tmpl_da, my_b->tmpl_da);
552 if (cmp != 0) return cmp;
554 if (my_a->tmpl_tag < my_b->tmpl_tag) return -1;
556 if (my_a->tmpl_tag > my_b->tmpl_tag) return 1;
558 if (my_a->tmpl_num < my_b->tmpl_num) return -1;
560 if (my_a->tmpl_num > my_b->tmpl_num) return 1;
565 static void map_sort_split(vp_map_t *source, vp_map_t **front, vp_map_t **back)
571 * Stopping condition - no more elements left to split
573 if (!source || !source->next) {
581 * Fast advances twice as fast as slow, so when it gets to the end,
582 * slow will point to the middle of the linked list.
600 static vp_map_t *map_sort_merge(vp_map_t *a, vp_map_t *b, fr_cmp_t cmp)
602 vp_map_t *result = NULL;
608 * Compare things in the maps
610 if (cmp(a, b) <= 0) {
612 result->next = map_sort_merge(a->next, b, cmp);
615 result->next = map_sort_merge(a, b->next, cmp);
621 /** Sort a linked list of #vp_map_t using merge sort
623 * @param[in,out] maps List of #vp_map_t to sort.
624 * @param[in] cmp to sort with
626 void map_sort(vp_map_t **maps, fr_cmp_t cmp)
628 vp_map_t *head = *maps;
633 * If there's 0-1 elements it must already be sorted.
635 if (!head || !head->next) {
639 map_sort_split(head, &a, &b); /* Split into sublists */
640 map_sort(&a, cmp); /* Traverse left */
641 map_sort(&b, cmp); /* Traverse right */
644 * merge the two sorted lists together
646 *maps = map_sort_merge(a, b, cmp);
649 /** Process map which has exec as a src
651 * Evaluate maps which specify exec as a src. This may be used by various sorts of update sections,
652 * and so has been broken out into it's own function.
654 * @param[in,out] ctx to allocate new #VALUE_PAIR (s) in.
655 * @param[out] out Where to write the #VALUE_PAIR (s).
656 * @param[in] request structure (used only for talloc).
657 * @param[in] map the map. The LHS (dst) must be TMPL_TYPE_ATTR or TMPL_TYPE_LIST. The RHS (src)
658 * must be TMPL_TYPE_EXEC.
659 * @return -1 on failure, 0 on success.
661 static int map_exec_to_vp(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map)
664 char *expanded = NULL;
666 VALUE_PAIR **input_pairs = NULL;
667 VALUE_PAIR *output_pairs = NULL;
673 rad_assert(map->rhs->type == TMPL_TYPE_EXEC);
674 rad_assert((map->lhs->type == TMPL_TYPE_ATTR) || (map->lhs->type == TMPL_TYPE_LIST));
677 * We always put the request pairs into the environment
679 input_pairs = radius_list(request, PAIR_LIST_REQUEST);
682 * Automagically switch output type depending on our destination
683 * If dst is a list, then we create attributes from the output of the program
684 * if dst is an attribute, then we create an attribute of that type and then
685 * call fr_pair_value_from_str on the output of the script.
687 result = radius_exec_program(ctx, answer, sizeof(answer),
688 (map->lhs->type == TMPL_TYPE_LIST) ? &output_pairs : NULL,
689 request, map->rhs->name, input_pairs ? *input_pairs : NULL,
690 true, true, EXEC_TIMEOUT);
691 talloc_free(expanded);
693 talloc_free(output_pairs);
697 switch (map->lhs->type) {
700 REDEBUG("No valid attributes received from program");
710 vp = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
713 vp->tag = map->lhs->tmpl_tag;
714 if (fr_pair_value_from_str(vp, answer, -1) < 0) {
715 fr_pair_list_free(&vp);
730 /** Convert a map to a VALUE_PAIR.
732 * @param[in,out] ctx to allocate #VALUE_PAIR (s) in.
733 * @param[out] out Where to write the #VALUE_PAIR (s), which may be NULL if not found
734 * @param[in] request The current request.
735 * @param[in] map the map. The LHS (dst) has to be #TMPL_TYPE_ATTR or #TMPL_TYPE_LIST.
736 * @param[in] uctx unused.
741 int map_to_vp(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, UNUSED void *uctx)
745 VALUE_PAIR *vp = NULL, *new, *found = NULL;
746 REQUEST *context = request;
754 rad_assert(map->lhs != NULL);
755 rad_assert(map->rhs != NULL);
757 rad_assert((map->lhs->type == TMPL_TYPE_LIST) || (map->lhs->type == TMPL_TYPE_ATTR));
760 * Special case for !*, we don't need to parse RHS as this is a unary operator.
762 if (map->op == T_OP_CMP_FALSE) return 0;
765 * List to list found, this is a special case because we don't need
766 * to allocate any attributes, just finding the current list, and change
769 if ((map->lhs->type == TMPL_TYPE_LIST) && (map->rhs->type == TMPL_TYPE_LIST)) {
770 VALUE_PAIR **from = NULL;
772 if (radius_request(&context, map->rhs->tmpl_request) == 0) {
773 from = radius_list(context, map->rhs->tmpl_list);
777 found = fr_pair_list_copy(ctx, *from);
780 * List to list copy is empty if the src list has no attributes.
782 if (!found) return 0;
784 for (vp = fr_cursor_init(&cursor, &found);
786 vp = fr_cursor_next(&cursor)) {
798 switch (map->rhs->type) {
799 case TMPL_TYPE_XLAT_STRUCT:
800 rad_assert(map->lhs->type == TMPL_TYPE_ATTR);
801 rad_assert(map->lhs->tmpl_da); /* We need to know which attribute to create */
802 rad_assert(map->rhs->tmpl_xlat != NULL);
804 new = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
808 slen = radius_axlat_struct(&str, request, map->rhs->tmpl_xlat, NULL, NULL);
815 * We do the debug printing because radius_axlat_struct
816 * doesn't have access to the original string. It's been
817 * mangled during the parsing to xlat_exp_t
819 RDEBUG2("EXPAND %s", map->rhs->name);
820 RDEBUG2(" --> %s", str);
822 rcode = fr_pair_value_from_str(new, str, -1);
825 fr_pair_list_free(&new);
829 new->tag = map->lhs->tmpl_tag;
834 rad_assert(map->lhs->type == TMPL_TYPE_ATTR);
835 rad_assert(map->lhs->tmpl_da); /* We need to know which attribute to create */
837 new = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
841 slen = radius_axlat(&str, request, map->rhs->name, NULL, NULL);
847 rcode = fr_pair_value_from_str(new, str, -1);
850 fr_pair_list_free(&new);
854 new->tag = map->lhs->tmpl_tag;
858 case TMPL_TYPE_LITERAL:
859 rad_assert(map->lhs->type == TMPL_TYPE_ATTR);
860 rad_assert(map->lhs->tmpl_da); /* We need to know which attribute to create */
862 new = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
865 if (fr_pair_value_from_str(new, map->rhs->name, -1) < 0) {
870 new->tag = map->lhs->tmpl_tag;
878 rad_assert(((map->lhs->type == TMPL_TYPE_ATTR) && map->lhs->tmpl_da) ||
879 ((map->lhs->type == TMPL_TYPE_LIST) && !map->lhs->tmpl_da));
882 * @todo should log error, and return -1 for v3.1 (causes update to fail)
884 if (tmpl_copy_vps(ctx, &found, request, map->rhs) < 0) return 0;
886 vp = fr_cursor_init(&from, &found);
889 * Src/Dst attributes don't match, convert src attributes
892 if ((map->lhs->type == TMPL_TYPE_ATTR) &&
893 (map->rhs->tmpl_da->type != map->lhs->tmpl_da->type)) {
896 (void) fr_cursor_init(&to, out);
897 for (; vp; vp = fr_cursor_next(&from)) {
898 new = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
901 len = value_data_cast(new, &new->data, new->da->type, new->da,
902 vp->da->type, vp->da, &vp->data, vp->vp_length);
904 REDEBUG("Attribute conversion failed: %s", fr_strerror());
905 fr_pair_list_free(&found);
906 fr_pair_list_free(&new);
910 new->vp_length = len;
911 vp = fr_cursor_remove(&from);
914 if (new->da->type == PW_TYPE_STRING) {
915 rad_assert(new->vp_strvalue != NULL);
919 new->tag = map->lhs->tmpl_tag;
920 fr_cursor_insert(&to, new);
926 * Otherwise we just need to fixup the attribute types
929 for (; vp; vp = fr_cursor_next(&from)) {
930 vp->da = map->lhs->tmpl_da;
932 vp->tag = map->lhs->tmpl_tag;
939 rad_assert(map->lhs->tmpl_da);
940 rad_assert(map->lhs->type == TMPL_TYPE_ATTR);
941 rad_assert(map->lhs->tmpl_da->type == map->rhs->tmpl_data_type);
943 new = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
946 len = value_data_copy(new, &new->data, new->da->type, &map->rhs->tmpl_data_value,
947 map->rhs->tmpl_data_length);
948 if (len < 0) goto error;
950 new->vp_length = len;
952 new->tag = map->lhs->tmpl_tag;
959 * This essentially does the same as rlm_exec xlat, except it's non-configurable.
960 * It's only really here as a convenience for people who expect the contents of
961 * backticks to be executed in a shell.
963 * exec string is xlat expanded and arguments are shell escaped.
966 return map_exec_to_vp(ctx, out, request, map);
969 rad_assert(0); /* Should have been caught at parse time */
972 fr_pair_list_free(&vp);
979 #define DEBUG_OVERWRITE(_old, _new) \
981 if (RDEBUG_ENABLED3) {\
982 char *old = vp_aprints_value(request, _old, '"');\
983 char *new = vp_aprints_value(request, _new, '"');\
984 RDEBUG3("Overwriting value \"%s\" with \"%s\"", old, new);\
990 /** Convert vp_map_t to VALUE_PAIR(s) and add them to a REQUEST.
992 * Takes a single vp_map_t, resolves request and list identifiers
993 * to pointers in the current request, then attempts to retrieve module
994 * specific value(s) using callback, and adds the resulting values to the
995 * correct request/list.
997 * @param request The current request.
998 * @param map specifying destination attribute and location and src identifier.
999 * @param func to retrieve module specific values and convert them to
1001 * @param ctx to be passed to func.
1002 * @return -1 if the operation failed, -2 in the source attribute wasn't valid, 0 on success.
1004 int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t func, void *ctx)
1008 VALUE_PAIR **list, *vp, *dst, *head = NULL;
1012 vp_cursor_t dst_list, src_list;
1018 rad_assert(map->lhs != NULL);
1019 rad_assert(map->rhs != NULL);
1022 * Preprocessing of the LHS of the map.
1024 switch (map->lhs->type) {
1026 * Already in the correct form.
1028 case TMPL_TYPE_LIST:
1029 case TMPL_TYPE_ATTR:
1033 * Everything else gets expanded, then re-parsed as an
1034 * attribute reference.
1036 case TMPL_TYPE_XLAT:
1037 case TMPL_TYPE_XLAT_STRUCT:
1038 case TMPL_TYPE_EXEC:
1043 slen = tmpl_aexpand(request, &attr, request, map->lhs, NULL, NULL);
1045 REDEBUG("Left side \"%.*s\" of map failed expansion", (int)map->lhs->len, map->lhs->name);
1050 slen = tmpl_from_attr_str(&exp_lhs, attr, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false) ;
1052 REDEBUG("Left side \"%.*s\" expansion not an attribute reference: %s",
1053 (int)map->lhs->len, map->lhs->name, fr_strerror());
1057 rad_assert((exp_lhs.type == TMPL_TYPE_ATTR) || (exp_lhs.type == TMPL_TYPE_LIST));
1059 memcpy(&exp_map, map, sizeof(exp_map));
1060 exp_map.lhs = &exp_lhs;
1072 * Sanity check inputs. We can have a list or attribute
1075 if ((map->lhs->type != TMPL_TYPE_LIST) &&
1076 (map->lhs->type != TMPL_TYPE_ATTR)) {
1077 REDEBUG("Left side \"%.*s\" of map should be an attr or list but is an %s",
1078 (int)map->lhs->len, map->lhs->name,
1079 fr_int2str(tmpl_names, map->lhs->type, "<INVALID>"));
1084 if (radius_request(&context, map->lhs->tmpl_request) < 0) {
1085 REDEBUG("Mapping \"%.*s\" -> \"%.*s\" invalid in this context",
1086 (int)map->rhs->len, map->rhs->name, (int)map->lhs->len, map->lhs->name);
1091 * If there's no CoA packet and we're updating it,
1094 if (((map->lhs->tmpl_list == PAIR_LIST_COA) ||
1095 (map->lhs->tmpl_list == PAIR_LIST_DM)) && !request->coa) {
1096 if (!request_alloc_coa(context)) {
1097 REDEBUG("Failed to create a CoA/Disconnect Request message");
1100 context->coa->proxy->code = (map->lhs->tmpl_list == PAIR_LIST_COA) ?
1101 PW_CODE_COA_REQUEST :
1102 PW_CODE_DISCONNECT_REQUEST;
1105 list = radius_list(context, map->lhs->tmpl_list);
1107 REDEBUG("Mapping \"%.*s\" -> \"%.*s\" invalid in this context",
1108 (int)map->rhs->len, map->rhs->name, (int)map->lhs->len, map->lhs->name);
1113 parent = radius_list_ctx(context, map->lhs->tmpl_list);
1117 * The callback should either return -1 to signify operations error,
1118 * -2 when it can't find the attribute or list being referenced, or
1119 * 0 to signify success. It may return "success", but still have no
1122 if (map->rhs->type != TMPL_TYPE_NULL) {
1123 rcode = func(parent, &head, request, map, ctx);
1129 RDEBUG2("No attributes updated");
1133 if (rad_debug_lvl) map_debug_log(request, map, NULL);
1139 for (vp = fr_cursor_init(&src_list, &head);
1141 vp = fr_cursor_next(&src_list)) {
1144 if (rad_debug_lvl) map_debug_log(request, map, vp);
1148 * The destination is a list (which is a completely different set of operations)
1150 if (map->lhs->type == TMPL_TYPE_LIST) {
1152 case T_OP_CMP_FALSE:
1153 /* We don't need the src VPs (should just be 'ANY') */
1156 /* Clear the entire dst list */
1157 fr_pair_list_free(list);
1159 if (map->lhs->tmpl_list == PAIR_LIST_REQUEST) {
1160 context->username = NULL;
1161 context->password = NULL;
1166 if (map->rhs->type == TMPL_TYPE_LIST) {
1167 fr_pair_list_free(list);
1172 rad_assert(map->rhs->type == TMPL_TYPE_EXEC);
1174 fr_pair_list_move(parent, list, &head);
1175 fr_pair_list_free(&head);
1180 fr_pair_list_free(&head);
1186 * Find the destination attribute. We leave with either
1187 * the dst_list and vp pointing to the attribute or the VP
1188 * being NULL (no attribute at that index).
1190 num = map->lhs->tmpl_num;
1191 (void) fr_cursor_init(&dst_list, list);
1192 if (num != NUM_ANY) {
1193 while ((dst = fr_cursor_next_by_da(&dst_list, map->lhs->tmpl_da, map->lhs->tmpl_tag))) {
1194 if (num-- == 0) break;
1197 dst = fr_cursor_next_by_da(&dst_list, map->lhs->tmpl_da, map->lhs->tmpl_tag);
1199 rad_assert(!dst || (map->lhs->tmpl_da == dst->da));
1202 * The destination is an attribute
1208 * !* - Remove all attributes which match dst in the specified list.
1209 * This doesn't use attributes returned by the func(), and immediately frees them.
1211 case T_OP_CMP_FALSE:
1212 /* We don't need the src VPs (should just be 'ANY') */
1217 * Wildcard: delete all of the matching ones, based on tag.
1219 if (map->lhs->tmpl_num == NUM_ANY) {
1220 fr_pair_delete_by_num(list, map->lhs->tmpl_da->attr, map->lhs->tmpl_da->vendor, map->lhs->tmpl_tag);
1223 * We've found the Nth one. Delete it, and only it.
1226 dst = fr_cursor_remove(&dst_list);
1227 fr_pair_list_free(&dst);
1231 * Check that the User-Name and User-Password
1232 * caches point to the correct attribute.
1237 * -= - Delete attributes in the dst list which match any of the
1238 * src_list attributes.
1240 * This operation has two modes:
1241 * - If map->lhs->tmpl_num > 0, we check each of the src_list attributes against
1242 * the dst attribute, to see if any of their values match.
1243 * - If map->lhs->tmpl_num == NUM_ANY, we compare all instances of the dst attribute
1244 * against each of the src_list attributes.
1247 /* We didn't find any attributes earlier */
1249 fr_pair_list_free(&head);
1254 * Instance specific[n] delete
1256 if (map->lhs->tmpl_num != NUM_ANY) {
1257 for (vp = fr_cursor_first(&src_list);
1259 vp = fr_cursor_next(&src_list)) {
1260 head->op = T_OP_CMP_EQ;
1261 rcode = radius_compare_vps(request, vp, dst);
1263 dst = fr_cursor_remove(&dst_list);
1264 fr_pair_list_free(&dst);
1268 fr_pair_list_free(&head);
1269 if (!found) return 0;
1274 * All instances[*] delete
1276 for (dst = fr_cursor_current(&dst_list);
1278 dst = fr_cursor_next_by_da(&dst_list, map->lhs->tmpl_da, map->lhs->tmpl_tag)) {
1279 for (vp = fr_cursor_first(&src_list);
1281 vp = fr_cursor_next(&src_list)) {
1282 head->op = T_OP_CMP_EQ;
1283 rcode = radius_compare_vps(request, vp, dst);
1285 dst = fr_cursor_remove(&dst_list);
1286 fr_pair_list_free(&dst);
1291 fr_pair_list_free(&head);
1292 if (!found) return 0;
1297 * Another fixup pass to set tags on attributes were about to insert
1299 if (map->lhs->tmpl_tag != TAG_ANY) {
1300 for (vp = fr_cursor_init(&src_list, &head);
1302 vp = fr_cursor_next(&src_list)) {
1303 vp->tag = map->lhs->tmpl_tag;
1309 * = - Set only if not already set
1313 RDEBUG3("Refusing to overwrite (use :=)");
1314 fr_pair_list_free(&head);
1318 /* Insert first instance (if multiple) */
1319 fr_cursor_first(&src_list);
1320 fr_cursor_insert(&dst_list, fr_cursor_remove(&src_list));
1321 /* Free any we didn't insert */
1322 fr_pair_list_free(&head);
1326 * := - Overwrite existing attribute with last src_list attribute
1329 /* Wind to last instance */
1330 fr_cursor_last(&src_list);
1332 DEBUG_OVERWRITE(dst, fr_cursor_current(&src_list));
1333 dst = fr_cursor_replace(&dst_list, fr_cursor_remove(&src_list));
1334 fr_pair_list_free(&dst);
1336 fr_cursor_insert(&dst_list, fr_cursor_remove(&src_list));
1338 /* Free any we didn't insert */
1339 fr_pair_list_free(&head);
1343 * += - Add all src_list attributes to the destination
1346 /* Insert all the instances! (if multiple) */
1347 fr_pair_add(list, head);
1352 * Filtering operators
1356 * If the dst doesn't exist, the filters will add
1357 * it with the given value.
1360 RDEBUG3("No existing attribute to filter, adding instead");
1361 fr_cursor_merge(&dst_list, head);
1367 * The LHS exists. We need to limit it's value based on
1368 * the operator, and the value of the RHS.
1371 for (vp = fr_cursor_first(&src_list);
1373 vp = fr_cursor_next(&src_list)) {
1375 rcode = radius_compare_vps(request, vp, dst);
1380 if (rcode == 0) continue;
1382 dst = fr_cursor_remove(&dst_list);
1383 DEBUG_OVERWRITE(dst, fr_cursor_current(&src_list));
1384 fr_pair_list_free(&dst);
1385 fr_cursor_insert(&dst_list, fr_cursor_remove(&src_list));
1390 if (rcode <= 0) continue;
1394 if (rcode >= 0) continue;
1398 fr_pair_list_free(&head);
1402 fr_pair_list_free(&head);
1403 if (!found) return 0;
1412 * Update the cached username && password. This is code
1413 * we execute on EVERY update (sigh) so that SOME modules
1414 * MIGHT NOT have to do the search themselves.
1416 * TBH, we should probably make each module just do the
1417 * search themselves.
1419 if (map->lhs->tmpl_list == PAIR_LIST_REQUEST) {
1420 context->username = NULL;
1421 context->password = NULL;
1423 for (vp = fr_cursor_init(&src_list, list);
1425 vp = fr_cursor_next(&src_list)) {
1427 if (vp->da->vendor != 0) continue;
1428 if (vp->da->flags.has_tag) continue;
1430 if (!context->username && (vp->da->attr == PW_USER_NAME)) {
1431 context->username = vp;
1435 if (vp->da->attr == PW_STRIPPED_USER_NAME) {
1436 context->username = vp;
1440 if (vp->da->attr == PW_USER_PASSWORD) {
1441 context->password = vp;
1449 /** Check whether the destination of a map is currently valid
1451 * @param request The current request.
1452 * @param map to check.
1453 * @return true if the map resolves to a request and list else false.
1455 bool map_dst_valid(REQUEST *request, vp_map_t const *map)
1457 REQUEST *context = request;
1461 if (radius_request(&context, map->lhs->tmpl_request) < 0) return false;
1462 if (!radius_list(context, map->lhs->tmpl_list)) return false;
1467 /** Print a map to a string
1469 * @param[out] buffer for the output string
1470 * @param[in] bufsize of the buffer
1471 * @param[in] map to print
1472 * @return the size of the string printed
1474 size_t map_prints(char *buffer, size_t bufsize, vp_map_t const *map)
1477 DICT_ATTR const *da = NULL;
1479 char *end = buffer + bufsize;
1483 if (map->lhs->type == TMPL_TYPE_ATTR) da = map->lhs->tmpl_da;
1485 len = tmpl_prints(buffer, bufsize, map->lhs, da);
1489 strlcpy(p, fr_token_name(map->op), end - p);
1494 * The RHS doesn't matter for many operators
1496 if ((map->op == T_OP_CMP_TRUE) ||
1497 (map->op == T_OP_CMP_FALSE)) {
1498 strlcpy(p, "ANY", (end - p));
1503 rad_assert(map->rhs != NULL);
1505 if ((map->lhs->type == TMPL_TYPE_ATTR) &&
1506 (map->lhs->tmpl_da->type == PW_TYPE_STRING) &&
1507 (map->rhs->type == TMPL_TYPE_LITERAL)) {
1509 len = tmpl_prints(p, end - p, map->rhs, da);
1514 len = tmpl_prints(p, end - p, map->rhs, da);
1522 * Debug print a map / VP
1524 void map_debug_log(REQUEST *request, vp_map_t const *map, VALUE_PAIR const *vp)
1530 rad_assert(map->lhs != NULL);
1531 rad_assert(map->rhs != NULL);
1533 rad_assert(vp || (map->rhs->type == TMPL_TYPE_NULL));
1535 switch (map->rhs->type) {
1537 * Just print the value being assigned
1540 case TMPL_TYPE_LITERAL:
1541 vp_prints_value(buffer, sizeof(buffer), vp, map->rhs->quote);
1545 case TMPL_TYPE_XLAT:
1546 case TMPL_TYPE_XLAT_STRUCT:
1547 vp_prints_value(buffer, sizeof(buffer), vp, map->rhs->quote);
1551 case TMPL_TYPE_DATA:
1552 vp_prints_value(buffer, sizeof(buffer), vp, map->rhs->quote);
1557 * For the lists, we can't use the original name, and have to
1558 * rebuild it using tmpl_prints, for each attribute we're
1561 case TMPL_TYPE_LIST:
1567 * Fudge a temporary tmpl that describes the attribute we're copying
1568 * this is a combination of the original list tmpl, and values from
1569 * the VALUE_PAIR. This way, we get tag info included.
1571 memcpy(&vpt, map->rhs, sizeof(vpt));
1572 vpt.tmpl_da = vp->da;
1573 vpt.tmpl_tag = vp->tag;
1574 vpt.type = TMPL_TYPE_ATTR;
1577 * Not appropriate to use map->rhs->quote here, as that's the quoting
1578 * around the list ref. The attribute value has no quoting, so we choose
1579 * the quoting based on the data type, and whether it's printable.
1581 if (vp->da->type == PW_TYPE_STRING) quote = is_printable(vp->vp_strvalue,
1582 vp->vp_length) ? '\'' : '"';
1583 vp_prints_value(buffer, sizeof(buffer), vp, quote);
1584 tmpl_prints(attr, sizeof(attr), &vpt, vp->da);
1585 value = talloc_typed_asprintf(request, "%s -> %s", attr, buffer);
1589 case TMPL_TYPE_ATTR:
1594 * Not appropriate to use map->rhs->quote here, as that's the quoting
1595 * around the attr ref. The attribute value has no quoting, so we choose
1596 * the quoting based on the data type, and whether it's printable.
1598 if (vp->da->type == PW_TYPE_STRING) quote = is_printable(vp->vp_strvalue,
1599 vp->vp_length) ? '\'' : '"';
1600 vp_prints_value(buffer, sizeof(buffer), vp, quote);
1601 value = talloc_typed_asprintf(request, "%.*s -> %s", (int)map->rhs->len, map->rhs->name, buffer);
1605 case TMPL_TYPE_NULL:
1606 strcpy(buffer, "ANY");
1611 switch (map->lhs->type) {
1612 case TMPL_TYPE_LIST:
1613 RDEBUG("%.*s:%s %s %s", (int)map->lhs->len, map->lhs->name, vp ? vp->da->name : "",
1614 fr_int2str(fr_tokens, vp ? vp->op : map->op, "<INVALID>"), value);
1617 case TMPL_TYPE_ATTR:
1618 RDEBUG("%s %s %s", map->lhs->name,
1619 fr_int2str(fr_tokens, vp ? vp->op : map->op, "<INVALID>"), value);
1626 if (value != buffer) talloc_free(value);