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>
36 /** Release memory allocated to value pair template.
38 * @param[in,out] tmpl to free.
40 void radius_tmplfree(value_pair_tmpl_t **tmpl)
42 if (*tmpl == NULL) return;
44 dict_attr_free(&((*tmpl)->da));
51 /** Parse qualifiers to convert attrname into a value_pair_tmpl_t.
53 * VPTs are used in various places where we need to pre-parse configuration
54 * sections into attribute mappings.
56 * Note: name field is just a copy of the input pointer, if you know that
57 * string might be freed before you're done with the vpt use radius_attr2tmpl
60 * @param[in] name attribute name including qualifiers.
61 * @param[out] vpt to modify.
62 * @param[in] request_def The default request to insert unqualified
64 * @param[in] list_def The default list to insert unqualified attributes into.
65 * @return -1 on error or 0 on success.
67 int radius_parse_attr(char const *name, value_pair_tmpl_t *vpt,
68 request_refs_t request_def,
69 pair_lists_t list_def)
75 memset(vpt, 0, sizeof(*vpt));
79 vpt->request = radius_request_name(&p, request_def);
81 if (vpt->request == REQUEST_UNKNOWN) {
82 ERROR("Invalid request qualifier \"%.*s\"", (int) len, name);
87 vpt->list = radius_list_name(&p, list_def);
88 if (vpt->list == PAIR_LIST_UNKNOWN) {
90 ERROR("Invalid list qualifier \"%.*s\"", (int) len, name);
95 vpt->type = VPT_TYPE_LIST;
99 da = dict_attrbyname(p);
101 da = dict_attrunknownbyname(p, false);
103 ERROR("Unknown attribute \"%s\"", p);
109 vpt->type = VPT_TYPE_ATTR;
113 /** Parse qualifiers to convert attrname into a value_pair_tmpl_t.
115 * VPTs are used in various places where we need to pre-parse configuration
116 * sections into attribute mappings.
118 * @param[in] ctx for talloc
119 * @param[in] name attribute name including qualifiers.
120 * @param[in] request_def The default request to insert unqualified
122 * @param[in] list_def The default list to insert unqualified attributes into.
123 * @return pointer to a value_pair_tmpl_t struct (must be freed with
124 * radius_tmplfree) or NULL on error.
126 value_pair_tmpl_t *radius_attr2tmpl(TALLOC_CTX *ctx, char const *name,
127 request_refs_t request_def,
128 pair_lists_t list_def)
130 value_pair_tmpl_t *vpt;
133 vpt = talloc(ctx, value_pair_tmpl_t); /* parse_attr zeroes it */
134 copy = talloc_strdup(vpt, name);
136 if (radius_parse_attr(copy, vpt, request_def, list_def) < 0) {
137 radius_tmplfree(&vpt);
144 /** Convert module specific attribute id to value_pair_tmpl_t.
146 * @param[in] ctx for talloc
147 * @param[in] name string to convert.
148 * @param[in] type Type of quoting around value.
149 * @return pointer to new VPT.
151 value_pair_tmpl_t *radius_str2tmpl(TALLOC_CTX *ctx, char const *name, FR_TOKEN type)
153 value_pair_tmpl_t *vpt;
155 vpt = talloc_zero(ctx, value_pair_tmpl_t);
156 vpt->name = talloc_strdup(vpt, name);
160 if (*name == '&') name++;
162 if (!isdigit((int) *name)) {
165 char const *p = name;
167 ref = radius_request_name(&p, REQUEST_CURRENT);
168 if (ref == REQUEST_UNKNOWN) goto literal;
170 list = radius_list_name(&p, PAIR_LIST_REQUEST);
171 if (list == PAIR_LIST_UNKNOWN) goto literal;
173 if ((p != name) && !*p) {
174 vpt->type = VPT_TYPE_LIST;
178 da = dict_attrbyname(p);
180 vpt->type = VPT_TYPE_LITERAL;
184 vpt->type = VPT_TYPE_ATTR;
193 case T_SINGLE_QUOTED_STRING:
195 vpt->type = VPT_TYPE_LITERAL;
197 case T_DOUBLE_QUOTED_STRING:
198 vpt->type = VPT_TYPE_XLAT;
200 case T_BACK_QUOTED_STRING:
201 vpt->type = VPT_TYPE_EXEC;
203 case T_OP_REG_EQ: /* hack */
204 vpt->type = VPT_TYPE_REGEX;
215 /** Convert strings to value_pair_map_e
217 * Treatment of operands depends on quotation, barewords are treated
218 * as attribute references, double quoted values are treated as
219 * expandable strings, single quoted values are treated as literal
222 * Return must be freed with talloc_free
224 * @param[in] ctx for talloc
225 * @param[in] lhs of the operation
226 * @param[in] lhs_type type of the LHS string
227 * @param[in] op the operation to perform
228 * @param[in] rhs of the operation
229 * @param[in] rhs_type type of the RHS string
230 * @param[in] dst_request_def The default request to insert unqualified
232 * @param[in] dst_list_def The default list to insert unqualified attributes
234 * @param[in] src_request_def The default request to resolve attribute
236 * @param[in] src_list_def The default list to resolve unqualified attributes
238 * @return value_pair_map_t if successful or NULL on error.
240 value_pair_map_t *radius_str2map(TALLOC_CTX *ctx, char const *lhs, FR_TOKEN lhs_type,
241 FR_TOKEN op, char const *rhs, FR_TOKEN rhs_type,
242 request_refs_t dst_request_def,
243 pair_lists_t dst_list_def,
244 request_refs_t src_request_def,
245 pair_lists_t src_list_def)
247 value_pair_map_t *map;
249 map = talloc_zero(ctx, value_pair_map_t);
251 if ((lhs_type == T_BARE_WORD) && (*lhs == '&')) {
252 map->dst = radius_attr2tmpl(map, lhs + 1, dst_request_def, dst_list_def);
254 map->dst = radius_str2tmpl(map, lhs, lhs_type);
266 * Ignore the RHS if it's a true / false comparison.
268 if ((map->op == T_OP_CMP_TRUE) || (map->op == T_OP_CMP_FALSE)) {
272 if ((rhs_type == T_BARE_WORD) && (*rhs == '&')) {
273 map->src = radius_attr2tmpl(map, rhs + 1, src_request_def, src_list_def);
275 map->src = radius_str2tmpl(map, rhs, rhs_type);
278 if (!map->dst) goto error;
284 /** Convert CONFIG_PAIR (which may contain refs) to value_pair_map_t.
286 * Treats the left operand as an attribute reference
287 * @verbatim<request>.<list>.<attribute>@endverbatim
289 * Treatment of left operand depends on quotation, barewords are treated as
290 * attribute references, double quoted values are treated as expandable strings,
291 * single quoted values are treated as literal strings.
293 * Return must be freed with talloc_free
295 * @param[in] ctx for talloc
296 * @param[in] cp to convert to map.
297 * @param[in] dst_request_def The default request to insert unqualified
299 * @param[in] dst_list_def The default list to insert unqualified attributes
301 * @param[in] src_request_def The default request to resolve attribute
303 * @param[in] src_list_def The default list to resolve unqualified attributes
305 * @return value_pair_map_t if successful or NULL on error.
307 value_pair_map_t *radius_cp2map(TALLOC_CTX *ctx, CONF_PAIR *cp,
308 request_refs_t dst_request_def,
309 pair_lists_t dst_list_def,
310 request_refs_t src_request_def,
311 pair_lists_t src_list_def)
313 value_pair_map_t *map;
317 CONF_ITEM *ci = cf_pairtoitem(cp);
319 if (!cp) return NULL;
321 map = talloc_zero(ctx, value_pair_map_t);
323 attr = cf_pair_attr(cp);
324 value = cf_pair_value(cp);
326 cf_log_err(ci, "Missing attribute value");
330 map->dst = radius_attr2tmpl(map, attr, dst_request_def, dst_list_def);
332 cf_log_err(ci, "Syntax error in attribute definition");
337 * Bare words always mean attribute references.
339 type = cf_pair_value_type(cp);
340 if (type == T_BARE_WORD) {
342 map->src = radius_attr2tmpl(map, value + 1, src_request_def, src_list_def);
345 if (!isdigit((int) *value) &&
346 ((strchr(value, ':') != NULL) ||
347 (dict_attrbyname(value) != NULL))) {
348 map->src = radius_attr2tmpl(map, value, src_request_def, src_list_def);
351 WDEBUG("%s[%d]: Please add '&' for attribute reference '%s = &%s'",
352 cf_pair_filename(cp), cf_pair_lineno(cp),
355 map->src = radius_str2tmpl(map, value, type);
359 map->src = radius_str2tmpl(map, value, type);
366 map->op = cf_pair_operator(cp);
370 * Lots of sanity checks for insane people...
374 * We don't support implicit type conversion,
375 * except for "octets"
377 if (map->dst->da && map->src->da &&
378 (map->src->da->type != map->dst->da->type) &&
379 (map->src->da->type != PW_TYPE_OCTETS) &&
380 (map->dst->da->type != PW_TYPE_OCTETS)) {
381 cf_log_err(ci, "Attribute type mismatch");
386 * What exactly where you expecting to happen here?
388 if ((map->dst->type == VPT_TYPE_ATTR) &&
389 (map->src->type == VPT_TYPE_LIST)) {
390 cf_log_err(ci, "Can't copy list into an attribute");
395 * Can't copy an xlat expansion or literal into a list,
396 * we don't know what type of attribute we'd need
399 if ((map->dst->type == VPT_TYPE_LIST) &&
400 ((map->src->type == VPT_TYPE_XLAT) || (map->src->type == VPT_TYPE_LITERAL))) {
401 cf_log_err(ci, "Can't copy value into list (we don't know which attribute to create)");
406 * Depending on the attribute type, some operators are
409 if (map->dst->type == VPT_TYPE_ATTR) {
410 if ((map->op != T_OP_EQ) &&
411 (map->op != T_OP_CMP_EQ) &&
412 (map->op != T_OP_ADD) &&
413 (map->op != T_OP_SUB) &&
414 (map->op != T_OP_LE) &&
415 (map->op != T_OP_GE) &&
416 (map->op != T_OP_CMP_FALSE) &&
417 (map->op != T_OP_SET)) {
418 cf_log_err(ci, "Invalid operator for attribute");
423 switch (map->src->type) {
425 * Only += and -= operators are supported for list copy.
434 cf_log_err(ci, "Operator \"%s\" not allowed "
436 fr_int2str(fr_tokens, map->op, "<INVALID>"));
452 /** Convert an 'update' config section into an attribute map.
454 * Uses 'name2' of section to set default request and lists.
456 * @param[in] cs the update section
457 * @param[out] head Where to store the head of the map.
458 * @param[in] dst_list_def The default destination list, usually dictated by
459 * the section the module is being called in.
460 * @param[in] src_list_def The default source list, usually dictated by the
461 * section the module is being called in.
462 * @param[in] max number of mappings to process.
463 * @return -1 on error, else 0.
465 int radius_attrmap(CONF_SECTION *cs, value_pair_map_t **head,
466 pair_lists_t dst_list_def, pair_lists_t src_list_def,
469 char const *cs_list, *p;
471 request_refs_t request_def = REQUEST_CURRENT;
476 unsigned int total = 0;
477 value_pair_map_t **tail, *map;
486 * The first map has cs as the parent.
487 * The rest have the previous map as the parent.
491 ci = cf_sectiontoitem(cs);
493 cs_list = p = cf_section_name2(cs);
495 request_def = radius_request_name(&p, REQUEST_UNKNOWN);
496 if (request_def == REQUEST_UNKNOWN) {
497 cf_log_err(ci, "Default request specified "
498 "in mapping section is invalid");
502 dst_list_def = fr_str2int(pair_lists, p, PAIR_LIST_UNKNOWN);
503 if (dst_list_def == PAIR_LIST_UNKNOWN) {
504 cf_log_err(ci, "Default list \"%s\" specified "
505 "in mapping section is invalid", p);
510 for (ci = cf_item_find_next(cs, NULL);
512 ci = cf_item_find_next(cs, ci)) {
513 if (total++ == max) {
514 cf_log_err(ci, "Map size exceeded");
518 if (!cf_item_is_pair(ci)) {
519 cf_log_err(ci, "Entry is not in \"attribute = "
524 cp = cf_itemtopair(ci);
525 map = radius_cp2map(ctx, cp, request_def, dst_list_def,
526 REQUEST_CURRENT, src_list_def);
542 /** Print a template to a string
544 * @param[out] buffer for the output string
545 * @param[in] bufsize of the buffer
546 * @param[in] vpt to print
547 * @return the size of the string printed
549 size_t radius_tmpl2str(char *buffer, size_t bufsize, value_pair_tmpl_t const *vpt)
568 case VPT_TYPE_LITERAL: /* single-quoted or bare word */
572 for (p = vpt->name; *p != '\0'; p++) {
573 if (*p == ' ') break;
574 if (*p == '\'') break;
575 if (!dict_attr_allowed_chars[(int) *p]) break;
579 strlcpy(buffer, vpt->name, bufsize);
580 return strlen(buffer);
592 if (vpt->request == REQUEST_CURRENT) {
593 if (vpt->list == PAIR_LIST_REQUEST) {
594 strlcpy(buffer + 1, vpt->da->name, bufsize - 1);
596 snprintf(buffer + 1, bufsize - 1, "%s:%s",
597 fr_int2str(pair_lists, vpt->list, ""),
602 snprintf(buffer + 1, bufsize - 1, "%s.%s:%s",
603 fr_int2str(request_refs, vpt->request, ""),
604 fr_int2str(pair_lists, vpt->list, ""),
607 return strlen(buffer);
614 memcpy(&ctx, &vpt, sizeof(ctx)); /* hack */
616 vp = pairalloc(ctx, vpt->da);
617 memcpy(&vp->data, vpt->vpd, sizeof(vp->data));
618 vp->length = vpt->length;
620 q = vp_aprint(vp, vp);
622 if ((vpt->da->type != PW_TYPE_STRING) &&
623 (vpt->da->type != PW_TYPE_DATE)) {
624 strlcpy(buffer, q, bufsize);
627 * FIXME: properly escape the string...
629 snprintf(buffer, bufsize, "\"%s\"", q);
634 return strlen(buffer);
646 end = buffer + bufsize - 3; /* quotes + EOS */
648 while (*p && (q < end)) {
650 if ((q - end) < 4) goto no_room; /* escape, char, quote, EOS */
658 if ((q - end) < 4) goto no_room;
664 if ((q - end) < 4) goto no_room;
671 if ((q - end) < 4) goto no_room;
678 if ((q - end) < 4) goto no_room;
697 /** Print a map to a string
699 * @param[out] buffer for the output string
700 * @param[in] bufsize of the buffer
701 * @param[in] map to print
702 * @return the size of the string printed
704 size_t radius_map2str(char *buffer, size_t bufsize, value_pair_map_t const *map)
708 char *end = buffer + bufsize;
710 len = radius_tmpl2str(buffer, bufsize, map->dst);
714 strlcpy(p, fr_token_name(map->op), end - p);
719 * The RHS doesn't matter for many operators
721 if ((map->op == T_OP_CMP_TRUE) ||
722 (map->op == T_OP_CMP_FALSE)) {
723 strlcpy(p, "ANY", (end - p));
728 rad_assert(map->src != NULL);
730 if ((map->dst->type == VPT_TYPE_ATTR) &&
731 (map->dst->da->type == PW_TYPE_STRING) &&
732 (map->src->type == VPT_TYPE_LITERAL)) {
734 len = radius_tmpl2str(p, end - p, map->src);
739 len = radius_tmpl2str(p, end - p, map->src);