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
21 * @brief String expansion ("translation"). Implements %Attribute -> value
23 * @copyright 2000,2006 The FreeRADIUS server project
24 * @copyright 2000 Alan DeKok <aland@ox.org>
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/parser.h>
31 #include <freeradius-devel/rad_assert.h>
32 #include <freeradius-devel/base64.h>
36 typedef struct xlat_t {
37 char name[MAX_STRING_LEN]; //!< Name of the xlat expansion.
38 int length; //!< Length of name.
39 void *instance; //!< Module instance passed to xlat and escape functions.
40 RAD_XLAT_FUNC func; //!< xlat function.
41 RADIUS_ESCAPE_STRING escape; //!< Escape function to apply to dynamic input to func.
42 bool internal; //!< If true, cannot be redefined.
46 XLAT_LITERAL, //!< Literal string
47 XLAT_PERCENT, //!< Literal string with %v
48 XLAT_MODULE, //!< xlat module
49 XLAT_VIRTUAL, //!< virtual attribute
50 XLAT_ATTRIBUTE, //!< xlat attribute
52 XLAT_REGEX, //!< regex reference
54 XLAT_ALTERNATE //!< xlat conditional syntax :-
58 char const *fmt; //!< The format string.
59 size_t len; //!< Length of the format string.
61 xlat_state_t type; //!< type of this expansion.
62 xlat_exp_t *next; //!< Next in the list.
64 xlat_exp_t *child; //!< Nested expansion.
65 xlat_exp_t *alternate; //!< Alternative expansion if this one expanded to a zero length string.
67 value_pair_tmpl_t attr; //!< An attribute template.
68 xlat_t const *xlat; //!< The xlat expansion to expand format with.
71 typedef struct xlat_out {
72 char const *out; //!< Output data.
73 size_t len; //!< Length of the output string.
76 static rbtree_t *xlat_root = NULL;
79 static char const * const xlat_foreach_names[] = {"Foreach-Variable-0",
92 #if REQUEST_MAX_REGEX > 8
93 # error Please fix the following line
95 static int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; /* up to 8 for regex */
97 char const *radiusd_short_version = RADIUSD_VERSION_STRING;
99 /** Print length of its RHS.
102 static ssize_t xlat_strlen(UNUSED void *instance, UNUSED REQUEST *request,
103 char const *fmt, char *out, size_t outlen)
105 snprintf(out, outlen, "%u", (unsigned int) strlen(fmt));
109 /** Print the size of the attribute in bytes.
112 static ssize_t xlat_length(UNUSED void *instance, UNUSED REQUEST *request,
113 char const *fmt, char *out, size_t outlen)
116 while (isspace((int) *fmt)) fmt++;
118 if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
123 snprintf(out, outlen, "%zu", vp->length);
127 /** Print data as integer, not as VALUE.
130 static ssize_t xlat_integer(UNUSED void *instance, REQUEST *request,
131 char const *fmt, char *out, size_t outlen)
135 uint64_t int64 = 0; /* Needs to be initialised to zero */
136 uint32_t int32 = 0; /* Needs to be initialised to zero */
138 while (isspace((int) *fmt)) fmt++;
140 if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
145 switch (vp->da->type) {
148 if (vp->length > 8) {
152 if (vp->length > 4) {
153 memcpy(&int64, vp->vp_octets, vp->length);
154 return snprintf(out, outlen, "%" PRIu64, htonll(int64));
157 memcpy(&int32, vp->vp_octets, vp->length);
158 return snprintf(out, outlen, "%i", htonl(int32));
160 case PW_TYPE_INTEGER64:
161 return snprintf(out, outlen, "%" PRIu64, vp->vp_integer64);
164 * IP addresses are treated specially, as parsing functions assume the value
165 * is bigendian and will convert it for us.
167 case PW_TYPE_IPV4_ADDR:
168 return snprintf(out, outlen, "%u", htonl(vp->vp_ipaddr));
170 case PW_TYPE_IPV4_PREFIX:
171 return snprintf(out, outlen, "%u", htonl((*(uint32_t *)(vp->vp_ipv4prefix + 2))));
173 case PW_TYPE_INTEGER:
175 return snprintf(out, outlen, "%u", vp->vp_integer);
177 return snprintf(out, outlen, "%u", (unsigned int) vp->vp_byte);
179 return snprintf(out, outlen, "%u", (unsigned int) vp->vp_short);
182 * Ethernet is weird... It's network related, so we assume to it should be
185 case PW_TYPE_ETHERNET:
186 memcpy(&int64, &vp->vp_ether, vp->length);
187 return snprintf(out, outlen, "%" PRIu64, htonll(int64));
190 return snprintf(out, outlen, "%i", vp->vp_signed);
192 case PW_TYPE_IPV6_ADDR:
193 return fr_prints_uint128(out, outlen, ntohlll(*(uint128_t const *) &vp->vp_ipv6addr));
195 case PW_TYPE_IPV6_PREFIX:
196 return fr_prints_uint128(out, outlen, ntohlll(*(uint128_t const *) &(vp->vp_ipv6prefix[2])));
202 REDEBUG("Type '%s' of length %zu cannot be converted to integer",
203 fr_int2str(dict_attr_types, vp->da->type, "???"), vp->length);
209 /** Print data as hex, not as VALUE.
212 static ssize_t xlat_hex(UNUSED void *instance, REQUEST *request,
213 char const *fmt, char *out, size_t outlen)
221 uint8_t const *buff = NULL;
223 while (isspace((int) *fmt)) fmt++;
225 if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
234 if (vp->da->type == PW_TYPE_OCTETS) {
238 * Cast the value_data_t of the VP to an octets string and
242 ret = value_data_cast(request, &dst, PW_TYPE_OCTETS, NULL, vp->da->type,
243 NULL, &vp->data, vp->length);
245 REDEBUG("%s", fr_strerror());
249 p = buff = dst.octets;
255 * Don't truncate the data.
257 if (outlen < (len * 2)) {
258 rad_const_free(buff);
262 for (i = 0; i < len; i++) {
263 snprintf(out + 2*i, 3, "%02x", p[i]);
265 rad_const_free(buff);
270 /** Return the tag of an attribute reference
273 static ssize_t xlat_tag(UNUSED void *instance, REQUEST *request,
274 char const *fmt, char *out, size_t outlen)
278 while (isspace((int) *fmt)) fmt++;
280 if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
285 if (!vp->da->flags.has_tag || !TAG_VALID(vp->tag)) {
290 return snprintf(out, outlen, "%u", vp->tag);
293 /** Print out attribute info
295 * Prints out all instances of a current attribute, or all attributes in a list.
297 * At higher debugging levels, also prints out alternative decodings of the same
298 * value. This is helpful to determine types for unknown attributes of long
299 * passed vendors, or just crazy/broken NAS.
301 * It's also useful for exposing issues in the packet decoding functions, as in
302 * some cases they get fed random garbage data.
304 * This expands to a zero length string.
306 static ssize_t xlat_debug_attr(UNUSED void *instance, REQUEST *request, char const *fmt,
307 char *out, UNUSED size_t outlen)
312 value_pair_tmpl_t vpt;
314 if (!RDEBUG_ENABLED2) {
319 while (isspace((int) *fmt)) fmt++;
321 if (tmpl_from_attr_str(&vpt, fmt, REQUEST_CURRENT, PAIR_LIST_REQUEST, false) <= 0) {
322 RDEBUG("%s", fr_strerror());
326 RIDEBUG("Attributes matching \"%s\"", fmt);
329 for (vp = tmpl_cursor_init(NULL, &cursor, request, &vpt);
331 vp = tmpl_cursor_next(&cursor, &vpt)) {
332 FR_NAME_NUMBER const *type;
335 value = vp_aprints_value(vp, vp, '\'');
336 if (vp->da->flags.has_tag) {
337 RIDEBUG2("&%s:%s:%i %s %s",
338 fr_int2str(pair_lists, vpt.tmpl_list, "<INVALID>"),
341 fr_int2str(fr_tokens, vp->op, "<INVALID>"),
344 RIDEBUG2("&%s:%s %s %s",
345 fr_int2str(pair_lists, vpt.tmpl_list, "<INVALID>"),
347 fr_int2str(fr_tokens, vp->op, "<INVALID>"),
352 if (!RDEBUG_ENABLED3) continue;
354 if (vp->da->vendor) {
357 dv = dict_vendorbyvalue(vp->da->vendor);
358 RIDEBUG2("Vendor : %i (%s)", vp->da->vendor, dv ? dv->name : "unknown");
360 RIDEBUG2("Type : %s", fr_int2str(dict_attr_types, vp->da->type, "<INVALID>"));
361 RIDEBUG2("Length : %zu", vp->length);
363 if (!RDEBUG_ENABLED4) continue;
365 type = dict_attr_types;
369 value_data_t *dst = NULL;
373 if ((PW_TYPE) type->number == vp->da->type) {
377 switch (type->number) {
378 case PW_TYPE_INVALID: /* Not real type */
379 case PW_TYPE_MAX: /* Not real type */
380 case PW_TYPE_EXTENDED: /* Not safe/appropriate */
381 case PW_TYPE_LONG_EXTENDED: /* Not safe/appropriate */
382 case PW_TYPE_TLV: /* Not safe/appropriate */
383 case PW_TYPE_EVS: /* Not safe/appropriate */
384 case PW_TYPE_VSA: /* @fixme We need special behaviour for these */
385 case PW_TYPE_COMBO_IP_ADDR: /* Covered by IPv4 address IPv6 address */
386 case PW_TYPE_COMBO_IP_PREFIX: /* Covered by IPv4 address IPv6 address */
387 case PW_TYPE_TIMEVAL: /* Not a VALUE_PAIR type */
395 dst = talloc_zero(vp, value_data_t);
396 ret = value_data_cast(dst, dst, type->number, NULL, vp->da->type, vp->da,
397 &vp->data, vp->length);
398 if (ret < 0) goto next_type; /* We expect some to fail */
400 value = vp_data_aprints_value(dst, type->number, NULL, dst, (size_t)ret, '\'');
401 if (!value) goto next_type;
403 if ((pad = (11 - strlen(type->name))) < 0) {
408 RDEBUG2("as %s%*s: %s", type->name, pad, " ", value);
421 /** Prints the current module processing the request
424 static ssize_t xlat_module(UNUSED void *instance, REQUEST *request,
425 UNUSED char const *fmt, char *out, size_t outlen)
427 strlcpy(out, request->module, outlen);
433 /** Implements the Foreach-Variable-X
437 static ssize_t xlat_foreach(void *instance, REQUEST *request,
438 UNUSED char const *fmt, char *out, size_t outlen)
444 * See modcall, "FOREACH" for how this works.
446 pvp = (VALUE_PAIR **) request_data_reference(request, radius_get_vp, *(int*) instance);
452 len = vp_prints_value(out, outlen, *pvp, 0);
453 if (is_truncated(len, outlen)) {
454 RDEBUG("Insufficient buffer space to write foreach value");
462 /** Print data as string, if possible.
464 * If attribute "Foo" is defined as "octets" it will normally
465 * be printed as 0x0a0a0a. The xlat "%{string:Foo}" will instead
468 static ssize_t xlat_string(UNUSED void *instance, REQUEST *request,
469 char const *fmt, char *out, size_t outlen)
476 while (isspace((int) *fmt)) fmt++;
484 if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) goto nothing;
486 ret = rad_vp2data(&p, vp);
491 switch (vp->da->type) {
493 len = fr_print_string((char const *) p, vp->length, out, outlen, '\0');
497 len = strlcpy(out, vp->vp_strvalue, outlen);
501 len = fr_print_string((char const *) p, ret, out, outlen, '\0');
508 /** xlat expand string attribute value
511 static ssize_t xlat_xlat(UNUSED void *instance, REQUEST *request,
512 char const *fmt, char *out, size_t outlen)
516 while (isspace((int) *fmt)) fmt++;
524 if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) goto nothing;
526 return radius_xlat(out, outlen, request, vp->vp_strvalue, NULL, NULL);
529 /** Dynamically change the debugging level for the current request
533 static ssize_t xlat_debug(UNUSED void *instance, REQUEST *request,
534 char const *fmt, char *out, size_t outlen)
539 * Expand to previous (or current) level
541 snprintf(out, outlen, "%d", request->log.lvl & RAD_REQUEST_OPTION_DEBUG4);
544 * Assume we just want to get the current value and NOT set it to 0
551 request->log.lvl = RAD_REQUEST_OPTION_NONE;
552 request->log.func = NULL;
554 if (level > 4) level = 4;
556 request->log.lvl = level;
557 request->log.func = vradlog_request;
565 * Compare two xlat_t structs, based ONLY on the module name.
567 static int xlat_cmp(void const *one, void const *two)
569 xlat_t const *a = one;
570 xlat_t const *b = two;
572 if (a->length != b->length) {
573 return a->length - b->length;
576 return memcmp(a->name, b->name, a->length);
581 * find the appropriate registered xlat function.
583 static xlat_t *xlat_find(char const *name)
587 strlcpy(my_xlat.name, name, sizeof(my_xlat.name));
588 my_xlat.length = strlen(my_xlat.name);
590 return rbtree_finddata(xlat_root, &my_xlat);
594 /** Register an xlat function.
596 * @param[in] name xlat name.
597 * @param[in] func xlat function to be called.
598 * @param[in] escape function to sanitize any sub expansions passed to the xlat function.
599 * @param[in] instance of module that's registering the xlat function.
600 * @return 0 on success, -1 on failure
602 int xlat_register(char const *name, RAD_XLAT_FUNC func, RADIUS_ESCAPE_STRING escape, void *instance)
608 if (!name || !*name) {
609 DEBUG("xlat_register: Invalid xlat name");
614 * First time around, build up the tree...
616 * FIXME: This code should be hoisted out of this function,
617 * and into a global "initialization". But it isn't critical...
624 xlat_root = rbtree_create(NULL, xlat_cmp, NULL, RBTREE_FLAG_REPLACE);
626 DEBUG("xlat_register: Failed to create tree");
631 for (i = 0; xlat_foreach_names[i] != NULL; i++) {
632 xlat_register(xlat_foreach_names[i],
633 xlat_foreach, NULL, &xlat_inst[i]);
634 c = xlat_find(xlat_foreach_names[i]);
635 rad_assert(c != NULL);
640 #define XLAT_REGISTER(_x) xlat_register(STRINGIFY(_x), xlat_ ## _x, NULL, NULL); \
641 c = xlat_find(STRINGIFY(_x)); \
642 rad_assert(c != NULL); \
645 XLAT_REGISTER(integer);
646 XLAT_REGISTER(strlen);
647 XLAT_REGISTER(length);
650 XLAT_REGISTER(string);
652 XLAT_REGISTER(module);
653 XLAT_REGISTER(debug_attr);
655 xlat_register("debug", xlat_debug, NULL, &xlat_inst[0]);
656 c = xlat_find("debug");
657 rad_assert(c != NULL);
662 * If it already exists, replace the instance.
664 strlcpy(my_xlat.name, name, sizeof(my_xlat.name));
665 my_xlat.length = strlen(my_xlat.name);
666 c = rbtree_finddata(xlat_root, &my_xlat);
669 DEBUG("xlat_register: Cannot re-define internal xlat");
675 c->instance = instance;
680 * Doesn't exist. Create it.
682 c = talloc_zero(xlat_root, xlat_t);
686 strlcpy(c->name, name, sizeof(c->name));
687 c->length = strlen(c->name);
688 c->instance = instance;
690 node = rbtree_insert_node(xlat_root, c);
697 * Ensure that the data is deleted when the node is
700 * @todo: Maybe this should be the other way around...
701 * when a thing IN the tree is deleted, it's automatically
702 * removed from the tree. But for now, this works.
704 (void) talloc_steal(node, c);
708 /** Unregister an xlat function
710 * We can only have one function to call per name, so the passing of "func"
711 * here is extraneous.
713 * @param[in] name xlat to unregister.
714 * @param[in] func unused.
715 * @param[in] instance data.
717 void xlat_unregister(char const *name, UNUSED RAD_XLAT_FUNC func, void *instance)
724 strlcpy(my_xlat.name, name, sizeof(my_xlat.name));
725 my_xlat.length = strlen(my_xlat.name);
727 c = rbtree_finddata(xlat_root, &my_xlat);
730 if (c->instance != instance) return;
732 rbtree_deletebydata(xlat_root, c);
735 static int xlat_unregister_callback(void *instance, void *data)
737 xlat_t *c = (xlat_t *) data;
739 if (c->instance != instance) return 0; /* keep walking */
741 return 2; /* delete it */
744 void xlat_unregister_module(void *instance)
746 rbtree_walk(xlat_root, RBTREE_DELETE_ORDER, xlat_unregister_callback, instance);
750 /** Crappy temporary function to add attribute ref support to xlats
752 * This needs to die, and hopefully will die, when xlat functions accept
753 * xlat node structures.
755 * Provides either a pointer to a buffer which contains the value of the reference VALUE_PAIR
756 * in an architecture independent format. Or a pointer to the start of the fmt string.
758 * The pointer is only guaranteed to be valid between calls to xlat_fmt_to_ref,
759 * and so long as the source VALUE_PAIR is not freed.
761 * @param out where to write a pointer to the buffer to the data the xlat function needs to work on.
762 * @param request current request.
764 * @returns the length of the data or -1 on error.
766 ssize_t xlat_fmt_to_ref(uint8_t const **out, REQUEST *request, char const *fmt)
770 while (isspace((int) *fmt)) fmt++;
773 if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
778 return rad_vp2data(out, vp);
781 *out = (uint8_t const *)fmt;
785 /** De-register all xlat functions, used mainly for debugging.
790 rbtree_free(xlat_root);
795 # define XLAT_DEBUG DEBUG3
797 # define XLAT_DEBUG(...)
800 static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head,
802 static ssize_t xlat_tokenize_literal(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head,
803 bool brace, char const **error);
804 static size_t xlat_process(char **out, REQUEST *request, xlat_exp_t const * const head,
805 RADIUS_ESCAPE_STRING escape, void *escape_ctx);
807 static ssize_t xlat_tokenize_alternation(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head,
814 rad_assert(fmt[0] == '%');
815 rad_assert(fmt[1] == '{');
816 rad_assert(fmt[2] == '%');
817 rad_assert(fmt[3] == '{');
819 XLAT_DEBUG("ALTERNATE <-- %s", fmt);
821 node = talloc_zero(ctx, xlat_exp_t);
822 node->type = XLAT_ALTERNATE;
825 slen = xlat_tokenize_expansion(node, p, &node->child, error);
828 return slen - (p - fmt);
834 *error = "Expected ':' after first expansion";
841 *error = "Expected '-' after ':'";
847 * Allow the RHS to be empty as a special case.
851 * Hack up an empty string.
853 node->alternate = talloc_zero(node, xlat_exp_t);
854 node->alternate->type = XLAT_LITERAL;
855 node->alternate->fmt = talloc_typed_strdup(node->alternate, "");
859 slen = xlat_tokenize_literal(node, p, &node->alternate, true, error);
862 return slen - (p - fmt);
865 if (!node->alternate) {
867 *error = "Empty expansion is invalid";
877 static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head,
884 rad_assert(fmt[0] == '%');
885 rad_assert(fmt[1] == '{');
890 if ((fmt[2] == '%') && (fmt[3] == '{')) return xlat_tokenize_alternation(ctx, fmt, head, error);
892 XLAT_DEBUG("EXPANSION <-- %s", fmt);
893 node = talloc_zero(ctx, xlat_exp_t);
899 * Handle regex's specially.
901 if (isdigit((int) fmt[2]) && (fmt[3] == '}')) {
904 *error = "Invalid regex reference";
908 XLAT_DEBUG("REGEX <-- %s", fmt);
910 node->attr.tmpl_num = fmt[2] - '0'; /* ASCII */
912 node->type = XLAT_REGEX;
916 #endif /* HAVE_REGEX */
921 * %{Tunnel-Password:1}
922 * %{Tunnel-Password:1[#]}
923 * %{request:Attr-Name}
924 * %{request:Tunnel-Password:1}
925 * %{request:Tunnel-Password:1[#]}
930 * This is for efficiency, so we don't search for an xlat,
931 * when what's being referenced is obviously an attribute.
934 for (q = p; *q != '\0'; q++) {
935 if (*q == ':') break;
937 if (isspace((int) *q)) break;
939 if (*q == '[') continue;
941 if (*q == '}') break;
945 * Check for empty expressions %{}
947 if ((*q == '}') && (q == p)) {
948 *error = "Empty expression is invalid";
953 * Might be a module name reference.
955 * If it's not, it's an attribute or parse error.
959 node->xlat = xlat_find(node->fmt);
964 node->type = XLAT_MODULE;
967 XLAT_DEBUG("MOD <-- %s ... %s", node->fmt, p);
969 slen = xlat_tokenize_literal(node, p, &node->child, true, error);
972 return slen - (p - fmt);
977 rad_assert(node->next == NULL);
981 *q = ':'; /* Avoids a strdup */
985 * The first token ends with:
986 * - '[' - Which is an attribute index, so it must be an attribute.
987 * - '}' - The end of the expansion, which means it was a bareword.
989 slen = tmpl_from_attr_substr(&node->attr, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, true);
992 * If the parse error occurred before the ':'
993 * then the error is changed to 'Unknown module',
994 * as it was more likely to be a bad module name,
995 * than a request qualifier.
997 if ((*q == ':') && ((p + (slen * -1)) < q)) {
998 *error = "Unknown module";
1000 *error = fr_strerror();
1002 return slen - (p - fmt);
1006 * Might be a virtual XLAT attribute
1008 if (node->attr.type == TMPL_TYPE_ATTR_UNKNOWN) {
1009 node->xlat = xlat_find(node->attr.tmpl_unknown_name);
1011 node->type = XLAT_VIRTUAL;
1012 node->fmt = node->attr.tmpl_unknown_name;
1014 XLAT_DEBUG("VIRTUAL <-- %s", node->fmt);
1016 rad_assert(node->next == NULL);
1022 *error = "Unknown attribute";
1026 node->type = XLAT_ATTRIBUTE;
1030 *error = "No matching closing brace";
1031 return -1; /* second character of format string */
1035 rad_assert(node->next == NULL);
1041 static ssize_t xlat_tokenize_literal(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head,
1042 bool brace, char const **error)
1047 if (!*fmt) return 0;
1049 XLAT_DEBUG("LITERAL <-- %s", fmt);
1051 node = talloc_zero(ctx, xlat_exp_t);
1054 node->type = XLAT_LITERAL;
1062 *error = "Invalid escape at end of string";
1070 * Process the expansion.
1072 if ((p[0] == '%') && (p[1] == '{')) {
1075 XLAT_DEBUG("LITERAL <-- %s", node->fmt);
1077 slen = xlat_tokenize_expansion(node, p, &node->next, error);
1080 return slen - (p - fmt);
1082 *p = '\0'; /* end the literal */
1085 rad_assert(node->next != NULL);
1088 * Short-circuit the recursive call.
1089 * This saves another function call and
1090 * memory allocation.
1095 * "foo %{User-Name} bar"
1097 * EXPANSION User-Name
1100 slen = xlat_tokenize_literal(node->next, p, &(node->next->next), brace, error);
1101 rad_assert(slen != 0);
1104 return slen - (p - fmt);
1108 break; /* stop processing the string */
1112 * Check for valid single-character expansions.
1118 if (!p[1] || !strchr("%dlmtDGHISTYv", p[1])) {
1120 *error = "Invalid variable expansion";
1125 next = talloc_zero(node, xlat_exp_t);
1129 next->fmt = talloc_typed_strdup(next, "%");
1131 XLAT_DEBUG("LITERAL <-- %s", next->fmt);
1132 next->type = XLAT_LITERAL;
1137 XLAT_DEBUG("PERCENT <-- %c", *next->fmt);
1138 next->type = XLAT_PERCENT;
1150 slen = xlat_tokenize_literal(node->next, p, &(node->next->next), brace, error);
1151 rad_assert(slen != 0);
1154 return slen - (p - fmt);
1158 break; /* stop processing the string */
1162 * If required, eat the brace.
1164 if (brace && (*p == '}')) {
1175 * Squash zero-width literals
1177 if (node->len > 0) {
1181 (void) talloc_steal(ctx, node->next);
1190 static char const xlat_tabs[] = " ";
1192 static void xlat_tokenize_debug(xlat_exp_t const *node, int lvl)
1194 rad_assert(node != NULL);
1196 if (lvl >= (int) sizeof(xlat_tabs)) lvl = sizeof(xlat_tabs);
1199 switch (node->type) {
1201 DEBUG("%.*sliteral --> %s", lvl, xlat_tabs, node->fmt);
1205 DEBUG("%.*spercent --> %c", lvl, xlat_tabs, node->fmt[0]);
1208 case XLAT_ATTRIBUTE:
1209 rad_assert(node->attr.tmpl_da != NULL);
1210 DEBUG("%.*sattribute --> %s", lvl, xlat_tabs, node->attr.tmpl_da->name);
1211 rad_assert(node->child == NULL);
1212 if ((node->attr.tmpl_tag != TAG_ANY) || (node->attr.tmpl_num != NUM_ANY)) {
1213 DEBUG("%.*s{", lvl, xlat_tabs);
1215 DEBUG("%.*sref %d", lvl + 1, xlat_tabs, node->attr.tmpl_request);
1216 DEBUG("%.*slist %d", lvl + 1, xlat_tabs, node->attr.tmpl_list);
1218 if (node->attr.tmpl_tag != TAG_ANY) {
1219 DEBUG("%.*stag %d", lvl + 1, xlat_tabs, node->attr.tmpl_tag);
1221 if (node->attr.tmpl_num != NUM_ANY) {
1222 if (node->attr.tmpl_num == NUM_COUNT) {
1223 DEBUG("%.*s[#]", lvl + 1, xlat_tabs);
1224 } else if (node->attr.tmpl_num == NUM_ALL) {
1225 DEBUG("%.*s[*]", lvl + 1, xlat_tabs);
1227 DEBUG("%.*s[%d]", lvl + 1, xlat_tabs, node->attr.tmpl_num);
1231 DEBUG("%.*s}", lvl, xlat_tabs);
1236 rad_assert(node->fmt != NULL);
1237 DEBUG("%.*svirtual --> %s", lvl, xlat_tabs, node->fmt);
1241 rad_assert(node->xlat != NULL);
1242 DEBUG("%.*sxlat --> %s", lvl, xlat_tabs, node->xlat->name);
1244 DEBUG("%.*s{", lvl, xlat_tabs);
1245 xlat_tokenize_debug(node->child, lvl + 1);
1246 DEBUG("%.*s}", lvl, xlat_tabs);
1252 DEBUG("%.*sregex-var --> %d", lvl, xlat_tabs, node->attr.tmpl_num);
1256 case XLAT_ALTERNATE:
1257 DEBUG("%.*sif {", lvl, xlat_tabs);
1258 xlat_tokenize_debug(node->child, lvl + 1);
1259 DEBUG("%.*s}", lvl, xlat_tabs);
1260 DEBUG("%.*selse {", lvl, xlat_tabs);
1261 xlat_tokenize_debug(node->alternate, lvl + 1);
1262 DEBUG("%.*s}", lvl, xlat_tabs);
1269 size_t xlat_sprint(char *buffer, size_t bufsize, xlat_exp_t const *node)
1280 end = buffer + bufsize;
1283 switch (node->type) {
1285 strlcpy(p, node->fmt, end - p);
1291 p[1] = node->fmt[0];
1295 case XLAT_ATTRIBUTE:
1299 if (node->attr.tmpl_request != REQUEST_CURRENT) {
1300 strlcpy(p, fr_int2str(request_refs, node->attr.tmpl_request, "??"), end - p);
1305 if ((node->attr.tmpl_request != REQUEST_CURRENT) ||
1306 (node->attr.tmpl_list != PAIR_LIST_REQUEST)) {
1307 strlcpy(p, fr_int2str(pair_lists, node->attr.tmpl_list, "??"), end - p);
1312 strlcpy(p, node->attr.tmpl_da->name, end - p);
1315 if (node->attr.tmpl_tag != TAG_ANY) {
1317 snprintf(p, end - p, "%u", node->attr.tmpl_tag);
1321 if (node->attr.tmpl_num != NUM_ANY) {
1323 switch (node->attr.tmpl_num) {
1333 snprintf(p, end - p, "%i", node->attr.tmpl_num);
1342 snprintf(p, end - p, "%%{%i}", node->attr.tmpl_num);
1349 strlcpy(p, node->fmt, end - p);
1357 strlcpy(p, node->xlat->name, end - p);
1360 rad_assert(node->child != NULL);
1361 len = xlat_sprint(p, end - p, node->child);
1366 case XLAT_ALTERNATE:
1370 len = xlat_sprint(p, end - p, node->child);
1376 len = xlat_sprint(p, end - p, node->alternate);
1384 if (p == end) break;
1394 ssize_t xlat_tokenize(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head,
1397 return xlat_tokenize_literal(ctx, fmt, head, false, error);
1401 /** Tokenize an xlat expansion
1403 * @param[in] request the input request. Memory will be attached here.
1404 * @param[in] fmt the format string to expand
1405 * @param[out] head the head of the xlat list / tree structure.
1407 static ssize_t xlat_tokenize_request(REQUEST *request, char const *fmt, xlat_exp_t **head)
1411 char const *error = NULL;
1416 * Copy the original format string to a buffer so that
1417 * the later functions can mangle it in-place, which is
1420 tokens = talloc_typed_strdup(request, fmt);
1421 if (!tokens) return -1;
1423 slen = xlat_tokenize_literal(request, tokens, head, false, &error);
1426 * Zero length expansion, return a zero length node.
1429 *head = talloc_zero(request, xlat_exp_t);
1433 * Output something like:
1436 * " ^ error was here"
1439 talloc_free(tokens);
1440 rad_assert(error != NULL);
1442 REMARKER(fmt, -slen, error);
1446 if (*head && (debug_flag > 2)) {
1448 DEBUG("Parsed xlat tree:");
1449 xlat_tokenize_debug(*head, 0);
1453 * All of the nodes point to offsets in the "tokens"
1454 * string. Let's ensure that free'ing head will free
1457 (void) talloc_steal(*head, tokens);
1463 static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, value_pair_tmpl_t const *vpt,
1464 bool escape, bool return_null)
1466 VALUE_PAIR *vp = NULL, *virtual = NULL;
1467 RADIUS_PACKET *packet = NULL;
1472 char quote = escape ? '"' : '\0';
1477 * See if we're dealing with an attribute in the request
1479 * This allows users to manipulate virtual attributes as if
1480 * they were real ones.
1482 vp = tmpl_cursor_init(&err, &cursor, request, vpt);
1483 if (vp) goto do_print;
1486 * We didn't find the VP in a list.
1487 * If it's not a virtual one, and we're not meant to
1488 * be counting it, return.
1490 if (!vpt->tmpl_da->flags.virtual) {
1491 if (vpt->tmpl_num == NUM_COUNT) goto do_print;
1496 * Switch out the request to the one specified by the template
1498 if (radius_request(&request, vpt->tmpl_request) < 0) return NULL;
1501 * Some non-packet expansions
1503 switch (vpt->tmpl_da->attr) {
1505 break; /* ignore them */
1507 case PW_CLIENT_SHORTNAME:
1508 if (vpt->tmpl_num == NUM_COUNT) goto count_virtual;
1509 if (request->client && request->client->shortname) {
1510 return talloc_typed_strdup(ctx, request->client->shortname);
1512 return talloc_typed_strdup(ctx, "<UNKNOWN-CLIENT>");
1514 case PW_REQUEST_PROCESSING_STAGE:
1515 if (vpt->tmpl_num == NUM_COUNT) goto count_virtual;
1516 if (request->component) {
1517 return talloc_typed_strdup(ctx, request->component);
1519 return talloc_typed_strdup(ctx, "server_core");
1521 case PW_VIRTUAL_SERVER:
1522 if (vpt->tmpl_num == NUM_COUNT) goto count_virtual;
1523 if (!request->server) return NULL;
1524 return talloc_typed_strdup(ctx, request->server);
1526 case PW_MODULE_RETURN_CODE:
1527 if (vpt->tmpl_num == NUM_COUNT) goto count_virtual;
1528 if (!request->rcode) return NULL;
1529 return talloc_typed_strdup(ctx, fr_int2str(modreturn_table, request->rcode, ""));
1533 * All of the attributes must now refer to a packet.
1534 * If there's no packet, we can't print any attribute
1537 packet = radius_packet(request, vpt->tmpl_list);
1539 if (return_null) return NULL;
1540 return vp_aprints_type(ctx, vpt->tmpl_da->type);
1544 switch (vpt->tmpl_da->attr) {
1548 case PW_PACKET_TYPE:
1549 dv = dict_valbyattr(PW_PACKET_TYPE, 0, packet->code);
1550 if (dv) return talloc_typed_strdup(ctx, dv->name);
1551 return talloc_typed_asprintf(ctx, "%d", packet->code);
1553 case PW_RESPONSE_PACKET_TYPE:
1558 if (request->proxy_reply && (!request->reply || !request->reply->code)) {
1559 code = request->proxy_reply->code;
1562 if (request->reply) {
1563 code = request->reply->code;
1566 return talloc_typed_strdup(ctx, fr_packet_codes[code]);
1570 * Virtual attributes which require a temporary VALUE_PAIR
1571 * to be allocated. We can't use stack allocated memory
1572 * because of the talloc checks sprinkled throughout the
1573 * various VP functions.
1575 case PW_PACKET_AUTHENTICATION_VECTOR:
1576 virtual = pairalloc(ctx, vpt->tmpl_da);
1577 pairmemcpy(virtual, packet->vector, sizeof(packet->vector));
1581 case PW_CLIENT_IP_ADDRESS:
1582 case PW_PACKET_SRC_IP_ADDRESS:
1583 if (packet->src_ipaddr.af == AF_INET) {
1584 virtual = pairalloc(ctx, vpt->tmpl_da);
1585 virtual->vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
1590 case PW_PACKET_DST_IP_ADDRESS:
1591 if (packet->dst_ipaddr.af == AF_INET) {
1592 virtual = pairalloc(ctx, vpt->tmpl_da);
1593 virtual->vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
1598 case PW_PACKET_SRC_IPV6_ADDRESS:
1599 if (packet->src_ipaddr.af == AF_INET6) {
1600 virtual = pairalloc(ctx, vpt->tmpl_da);
1601 memcpy(&virtual->vp_ipv6addr,
1602 &packet->src_ipaddr.ipaddr.ip6addr,
1603 sizeof(packet->src_ipaddr.ipaddr.ip6addr));
1608 case PW_PACKET_DST_IPV6_ADDRESS:
1609 if (packet->dst_ipaddr.af == AF_INET6) {
1610 virtual = pairalloc(ctx, vpt->tmpl_da);
1611 memcpy(&virtual->vp_ipv6addr,
1612 &packet->dst_ipaddr.ipaddr.ip6addr,
1613 sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
1618 case PW_PACKET_SRC_PORT:
1619 virtual = pairalloc(ctx, vpt->tmpl_da);
1620 virtual->vp_integer = packet->src_port;
1624 case PW_PACKET_DST_PORT:
1625 virtual = pairalloc(ctx, vpt->tmpl_da);
1626 virtual->vp_integer = packet->dst_port;
1632 * Fake various operations for virtual attributes.
1635 if (vpt->tmpl_num != NUM_ANY) switch (vpt->tmpl_num) {
1637 * [n] is NULL (we only have [0])
1642 * [*] means only one.
1648 * [#] means 1 (as there's only one)
1652 ret = talloc_strdup(ctx, "1");
1656 * [0] is fine (get the first instance)
1665 switch (vpt->tmpl_num) {
1667 * Return a count of the VPs.
1673 fr_cursor_first(&cursor);
1674 while (fr_cursor_next_by_da(&cursor, vpt->tmpl_da, vpt->tmpl_tag)) count++;
1676 return talloc_typed_asprintf(ctx, "%d", count);
1681 * Concatenate all values together,
1682 * separated by commas.
1688 if (!fr_cursor_current(&cursor)) return NULL;
1689 p = vp_aprints_value(ctx, vp, quote);
1690 if (!p) return NULL;
1692 while ((vp = tmpl_cursor_next(&cursor, vpt)) != NULL) {
1693 q = vp_aprints_value(ctx, vp, quote);
1694 if (!q) return NULL;
1695 p = talloc_strdup_append(p, ",");
1696 p = talloc_strdup_append(p, q);
1704 * The cursor was set to the correct
1705 * position above by tmpl_cursor_init.
1707 vp = fr_cursor_current(&cursor);
1712 if (return_null) return NULL;
1713 return vp_aprints_type(ctx, vpt->tmpl_da->type);
1717 ret = vp_aprints_value(ctx, vp, quote);
1720 talloc_free(virtual);
1725 static const char xlat_spaces[] = " ";
1728 static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * const node,
1729 RADIUS_ESCAPE_STRING escape, void *escape_ctx, int lvl)
1732 char *str = NULL, *child;
1736 XLAT_DEBUG("%.*sxlat aprint %d", lvl, xlat_spaces, node->type);
1738 switch (node->type) {
1740 * Don't escape this.
1743 XLAT_DEBUG("xlat_aprint LITERAL");
1744 return talloc_typed_strdup(ctx, node->fmt);
1747 * Do a one-character expansion.
1752 size_t freespace = 256;
1756 XLAT_DEBUG("xlat_aprint PERCENT");
1758 str = talloc_array(ctx, char, freespace); /* @todo do better allocation */
1761 when = request->timestamp;
1762 if (request->packet) {
1763 when = request->packet->timestamp.tv_sec;
1772 case 'd': /* request day */
1773 if (!localtime_r(&when, &ts)) goto error;
1774 strftime(str, freespace, "%d", &ts);
1777 case 'l': /* request timestamp */
1778 snprintf(str, freespace, "%lu",
1779 (unsigned long) when);
1782 case 'm': /* request month */
1783 if (!localtime_r(&when, &ts)) goto error;
1784 strftime(str, freespace, "%m", &ts);
1787 case 'n': /* Request Number*/
1788 snprintf(str, freespace, "%u", request->number);
1791 case 't': /* request timestamp */
1792 CTIME_R(&when, str, freespace);
1793 nl = strchr(str, '\n');
1797 case 'D': /* request date */
1798 if (!localtime_r(&when, &ts)) goto error;
1799 strftime(str, freespace, "%Y%m%d", &ts);
1802 case 'G': /* request minute */
1803 if (!localtime_r(&when, &ts)) goto error;
1804 strftime(str, freespace, "%M", &ts);
1807 case 'H': /* request hour */
1808 if (!localtime_r(&when, &ts)) goto error;
1809 strftime(str, freespace, "%H", &ts);
1812 case 'I': /* Request ID */
1813 if (request->packet) {
1814 snprintf(str, freespace, "%i", request->packet->id);
1818 case 'S': /* request timestamp in SQL format*/
1819 if (!localtime_r(&when, &ts)) goto error;
1820 strftime(str, freespace, "%Y-%m-%d %H:%M:%S", &ts);
1823 case 'T': /* request timestamp */
1824 if (!localtime_r(&when, &ts)) goto error;
1825 strftime(str, freespace, "%Y-%m-%d-%H.%M.%S.000000", &ts);
1828 case 'Y': /* request year */
1829 if (!localtime_r(&when, &ts)) {
1831 REDEBUG("Failed converting packet timestamp to localtime: %s", fr_syserror(errno));
1835 strftime(str, freespace, "%Y", &ts);
1838 case 'v': /* Version of code */
1839 snprintf(str, freespace, "%s", radiusd_short_version);
1849 case XLAT_ATTRIBUTE:
1850 XLAT_DEBUG("xlat_aprint ATTRIBUTE");
1853 * Some attributes are virtual <sigh>
1855 str = xlat_getvp(ctx, request, &node->attr, escape ? false : true, true);
1857 XLAT_DEBUG("EXPAND attr %s", node->attr.tmpl_da->name);
1858 XLAT_DEBUG(" ---> %s", str);
1863 XLAT_DEBUG("xlat_aprint VIRTUAL");
1864 str = talloc_array(ctx, char, 2048); /* FIXME: have the module call talloc_typed_asprintf */
1865 rcode = node->xlat->func(node->xlat->instance, request, NULL, str, 2048);
1873 XLAT_DEBUG("xlat_aprint MODULE");
1874 if (xlat_process(&child, request, node->child, node->xlat->escape, node->xlat->instance) == 0) {
1878 XLAT_DEBUG("%.*sEXPAND mod %s %s", lvl, xlat_spaces, node->fmt, node->child->fmt);
1879 XLAT_DEBUG("%.*s ---> %s", lvl, xlat_spaces, child);
1884 * The OUTPUT of xlat is a printable string. The INPUT might not be...
1886 * This is really the reverse of fr_print_string().
1890 if (*p == '\\') switch (p[1]) {
1911 str = talloc_array(ctx, char, 2048); /* FIXME: have the module call talloc_typed_asprintf */
1912 *str = '\0'; /* Be sure the string is NULL terminated, we now only free on error */
1914 rcode = node->xlat->func(node->xlat->instance, request, child, str, 2048);
1924 XLAT_DEBUG("xlat_aprint REGEX");
1925 child = request_data_reference(request, request,
1926 REQUEST_DATA_REGEX | node->attr.tmpl_num);
1927 if (!child) return NULL;
1929 str = talloc_typed_strdup(ctx, child);
1933 case XLAT_ALTERNATE:
1934 XLAT_DEBUG("xlat_aprint ALTERNATE");
1935 rad_assert(node->child != NULL);
1936 rad_assert(node->alternate != NULL);
1938 str = xlat_aprint(ctx, request, node->child, escape, escape_ctx, lvl);
1941 str = xlat_aprint(ctx, request, node->alternate, escape, escape_ctx, lvl);
1947 * Escape the non-literals we found above.
1949 if (str && escape) {
1952 escaped = talloc_array(ctx, char, 2048); /* FIXME: do something intelligent */
1953 escape(request, escaped, 2038, str, escape_ctx);
1962 static size_t xlat_process(char **out, REQUEST *request, xlat_exp_t const * const head,
1963 RADIUS_ESCAPE_STRING escape, void *escape_ctx)
1967 char **array, *answer;
1968 xlat_exp_t const *node;
1973 * There are no nodes to process, so the result is a zero
1977 *out = talloc_zero_array(request, char, 1);
1982 * Hack for speed. If it's one expansion, just allocate
1983 * that and return, instead of allocating an intermediary
1988 * Pass the MAIN escape function. Recursive
1989 * calls will call node-specific escape
1992 answer = xlat_aprint(request, request, head, escape, escape_ctx, 0);
1994 *out = talloc_zero_array(request, char, 1);
1998 return strlen(answer);
2001 list = 0; /* FIXME: calculate this once */
2002 for (node = head; node != NULL; node = node->next) {
2006 array = talloc_array(request, char *, list);
2007 if (!array) return -1;
2009 for (node = head, i = 0; node != NULL; node = node->next, i++) {
2010 array[i] = xlat_aprint(array, request, node, escape, escape_ctx, 0); /* may be NULL */
2014 for (i = 0; i < list; i++) {
2015 if (array[i]) total += strlen(array[i]); /* FIXME: calculate strlen once */
2020 *out = talloc_zero_array(request, char, 1);
2024 answer = talloc_array(request, char, total + 1);
2027 for (i = 0; i < list; i++) {
2031 len = strlen(array[i]);
2032 memcpy(answer + total, array[i], len);
2036 answer[total] = '\0';
2037 talloc_free(array); /* and child entries */
2044 /** Replace %whatever in a string.
2046 * See 'doc/variables.txt' for more information.
2048 * @param[out] out Where to write pointer to output buffer.
2049 * @param[in] outlen Size of out.
2050 * @param[in] request current request.
2051 * @param[in] node the xlat structure to expand
2052 * @param[in] escape function to escape final value e.g. SQL quoting.
2053 * @param[in] escape_ctx pointer to pass to escape function.
2054 * @return length of string written @bug should really have -1 for failure
2056 static ssize_t xlat_expand_struct(char **out, size_t outlen, REQUEST *request, xlat_exp_t const *node,
2057 RADIUS_ESCAPE_STRING escape, void *escape_ctx)
2062 rad_assert(node != NULL);
2064 len = xlat_process(&buff, request, node, escape, escape_ctx);
2065 if ((len < 0) || !buff) {
2066 rad_assert(buff == NULL);
2067 if (*out) *out[0] = '\0';
2074 strlcpy(*out, buff, outlen);
2078 return strlen(*out);
2081 static ssize_t xlat_expand(char **out, size_t outlen, REQUEST *request, char const *fmt,
2082 RADIUS_ESCAPE_STRING escape, void *escape_ctx) CC_HINT(nonnull (1, 3, 4));
2084 /** Replace %whatever in a string.
2086 * See 'doc/variables.txt' for more information.
2088 * @param[out] out Where to write pointer to output buffer.
2089 * @param[in] outlen Size of out.
2090 * @param[in] request current request.
2091 * @param[in] fmt string to expand.
2092 * @param[in] escape function to escape final value e.g. SQL quoting.
2093 * @param[in] escape_ctx pointer to pass to escape function.
2094 * @return length of string written @bug should really have -1 for failure
2096 static ssize_t xlat_expand(char **out, size_t outlen, REQUEST *request, char const *fmt,
2097 RADIUS_ESCAPE_STRING escape, void *escape_ctx)
2103 * Give better errors than the old code.
2105 len = xlat_tokenize_request(request, fmt, &node);
2110 *out = talloc_zero_array(request, char, 1);
2116 if (*out) *out[0] = '\0';
2120 len = xlat_expand_struct(out, outlen, request, node, escape, escape_ctx);
2123 RDEBUG2("EXPAND %s", fmt);
2124 RDEBUG2(" --> %s", *out);
2130 * Try to convert an xlat to a tmpl for efficiency
2132 value_pair_tmpl_t *radius_xlat2tmpl(TALLOC_CTX *ctx, xlat_exp_t *node)
2134 value_pair_tmpl_t *vpt;
2136 if (node->next || (node->type != XLAT_ATTRIBUTE)) return NULL;
2139 * @todo it should be possible to emulate the concat and count operations in the
2142 if ((node->attr.tmpl_num == NUM_COUNT) || (node->attr.tmpl_num == NUM_ALL)) return NULL;
2144 vpt = tmpl_alloc(ctx, TMPL_TYPE_ATTR, node->fmt, -1);
2145 if (!vpt) return NULL;
2146 vpt->tmpl_request = node->attr.tmpl_request;
2147 vpt->tmpl_list = node->attr.tmpl_list;
2148 vpt->tmpl_da = node->attr.tmpl_da;
2149 vpt->tmpl_num = node->attr.tmpl_num;
2150 vpt->tmpl_tag = node->attr.tmpl_tag;
2157 ssize_t radius_xlat(char *out, size_t outlen, REQUEST *request, char const *fmt, RADIUS_ESCAPE_STRING escape, void *ctx)
2159 return xlat_expand(&out, outlen, request, fmt, escape, ctx);
2162 ssize_t radius_axlat(char **out, REQUEST *request, char const *fmt, RADIUS_ESCAPE_STRING escape, void *ctx)
2164 return xlat_expand(out, 0, request, fmt, escape, ctx);
2167 ssize_t radius_axlat_struct(char **out, REQUEST *request, xlat_exp_t const *xlat, RADIUS_ESCAPE_STRING escape, void *ctx)
2169 return xlat_expand_struct(out, 0, request, xlat, escape, ctx);