summaryrefslogtreecommitdiffstats
path: root/src/main/xlat.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/xlat.c')
-rw-r--r--src/main/xlat.c2696
1 files changed, 2696 insertions, 0 deletions
diff --git a/src/main/xlat.c b/src/main/xlat.c
new file mode 100644
index 0000000..4bd0a37
--- /dev/null
+++ b/src/main/xlat.c
@@ -0,0 +1,2696 @@
+/*
+ * 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$
+ *
+ * @file xlat.c
+ * @brief String expansion ("translation"). Implements %Attribute -> value
+ *
+ * @copyright 2000,2006 The FreeRADIUS server project
+ * @copyright 2000 Alan DeKok <aland@ox.org>
+ */
+
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/parser.h>
+#include <freeradius-devel/rad_assert.h>
+#include <freeradius-devel/base64.h>
+
+#include <ctype.h>
+
+typedef struct xlat_t {
+ char name[MAX_STRING_LEN]; //!< Name of the xlat expansion.
+ int length; //!< Length of name.
+ void *instance; //!< Module instance passed to xlat and escape functions.
+ xlat_func_t func; //!< xlat function.
+ xlat_escape_t escape; //!< Escape function to apply to dynamic input to func.
+ bool internal; //!< If true, cannot be redefined.
+} xlat_t;
+
+typedef enum {
+ XLAT_LITERAL, //!< Literal string
+ XLAT_PERCENT, //!< Literal string with %v
+ XLAT_MODULE, //!< xlat module
+ XLAT_VIRTUAL, //!< virtual attribute
+ XLAT_ATTRIBUTE, //!< xlat attribute
+#ifdef HAVE_REGEX
+ XLAT_REGEX, //!< regex reference
+#endif
+ XLAT_ALTERNATE //!< xlat conditional syntax :-
+} xlat_state_t;
+
+struct xlat_exp {
+ char const *fmt; //!< The format string.
+ size_t len; //!< Length of the format string.
+
+ xlat_state_t type; //!< type of this expansion.
+ xlat_exp_t *next; //!< Next in the list.
+
+ xlat_exp_t *child; //!< Nested expansion.
+ xlat_exp_t *alternate; //!< Alternative expansion if this one expanded to a zero length string.
+
+ vp_tmpl_t attr; //!< An attribute template.
+ xlat_t const *xlat; //!< The xlat expansion to expand format with.
+};
+
+static rbtree_t *xlat_root = NULL;
+
+#ifdef WITH_UNLANG
+static char const * const xlat_foreach_names[] = {"Foreach-Variable-0",
+ "Foreach-Variable-1",
+ "Foreach-Variable-2",
+ "Foreach-Variable-3",
+ "Foreach-Variable-4",
+ "Foreach-Variable-5",
+ "Foreach-Variable-6",
+ "Foreach-Variable-7",
+ "Foreach-Variable-8",
+ "Foreach-Variable-9",
+ NULL};
+#endif
+
+
+static int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; /* up to 10 for foreach */
+
+static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt,
+ bool escape, bool return_null, char const *concat);
+
+/** Concatenation
+ *
+ */
+static ssize_t xlat_concat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ ssize_t slen;
+ vp_tmpl_t vpt;
+ char *str;
+ char const *concat;
+ char buffer[2];
+
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ slen = tmpl_from_attr_substr(&vpt, fmt, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false);
+ if (slen <= 0) {
+ RDEBUG("%s", fr_strerror());
+ return -1;
+ }
+
+ fmt += slen;
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ if (!*fmt) {
+ concat = ",";
+ } else {
+ buffer[0] = *fmt;
+ buffer[1] = '\0';
+
+ concat = buffer;
+ }
+
+ str = xlat_getvp(request, request, &vpt, true, true, concat);
+ if (!str) return 0;
+
+ strlcpy(out, str, outlen);
+ talloc_free(str);
+
+ return strlen(out);
+}
+
+/** Print length of its RHS.
+ *
+ */
+static ssize_t xlat_strlen(UNUSED void *instance, UNUSED REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ snprintf(out, outlen, "%u", (unsigned int) strlen(fmt));
+ return strlen(out);
+}
+
+/** Print the size of the attribute in bytes.
+ *
+ */
+static ssize_t xlat_length(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ VALUE_PAIR *vp;
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
+ *out = '\0';
+ return 0;
+ }
+
+ snprintf(out, outlen, "%zu", vp->vp_length);
+ return strlen(out);
+}
+
+/** Print data as integer, not as VALUE.
+ *
+ */
+static ssize_t xlat_integer(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ VALUE_PAIR *vp;
+
+ uint64_t int64 = 0; /* Needs to be initialised to zero */
+ uint32_t int32 = 0; /* Needs to be initialised to zero */
+
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
+ *out = '\0';
+ return 0;
+ }
+
+ switch (vp->da->type) {
+ case PW_TYPE_OCTETS:
+ case PW_TYPE_STRING:
+ if (vp->vp_length > 8) {
+ break;
+ }
+
+ if (vp->vp_length > 4) {
+ memcpy(&int64, vp->vp_octets, vp->vp_length);
+ return snprintf(out, outlen, "%" PRIu64, htonll(int64));
+ }
+
+ memcpy(&int32, vp->vp_octets, vp->vp_length);
+ return snprintf(out, outlen, "%i", htonl(int32));
+
+ case PW_TYPE_INTEGER64:
+ return snprintf(out, outlen, "%" PRIu64, vp->vp_integer64);
+
+ /*
+ * IP addresses are treated specially, as parsing functions assume the value
+ * is bigendian and will convert it for us.
+ */
+ case PW_TYPE_IPV4_ADDR:
+ return snprintf(out, outlen, "%u", htonl(vp->vp_ipaddr));
+
+ case PW_TYPE_IPV4_PREFIX:
+ return snprintf(out, outlen, "%u", htonl((*(uint32_t *)(&vp->vp_ipv4prefix[2]))));
+
+ case PW_TYPE_INTEGER:
+ return snprintf(out, outlen, "%u", vp->vp_integer);
+
+ case PW_TYPE_DATE:
+ return snprintf(out, outlen, "%u", vp->vp_date);
+
+ case PW_TYPE_BYTE:
+ return snprintf(out, outlen, "%u", (unsigned int) vp->vp_byte);
+
+ case PW_TYPE_SHORT:
+ return snprintf(out, outlen, "%u", (unsigned int) vp->vp_short);
+
+ /*
+ * Ethernet is weird... It's network related, so we assume to it should be
+ * bigendian.
+ */
+ case PW_TYPE_ETHERNET:
+ memcpy(&int64, vp->vp_ether, vp->vp_length);
+ return snprintf(out, outlen, "%" PRIu64, htonll(int64));
+
+ case PW_TYPE_SIGNED:
+ return snprintf(out, outlen, "%i", vp->vp_signed);
+
+ case PW_TYPE_IPV6_ADDR:
+ return fr_prints_uint128(out, outlen, ntohlll(*(uint128_t const *) &vp->vp_ipv6addr));
+
+ case PW_TYPE_IPV6_PREFIX:
+ return fr_prints_uint128(out, outlen, ntohlll(*(uint128_t const *) &vp->vp_ipv6prefix[2]));
+
+ default:
+ break;
+ }
+
+ REDEBUG("Type '%s' of length %zu cannot be converted to integer",
+ fr_int2str(dict_attr_types, vp->da->type, "???"), vp->vp_length);
+ *out = '\0';
+
+ return -1;
+}
+
+/** Print data as hex, not as VALUE.
+ *
+ */
+static ssize_t xlat_hex(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ size_t i;
+ VALUE_PAIR *vp;
+ uint8_t const *p;
+ ssize_t ret;
+ size_t len;
+ value_data_t dst;
+ uint8_t const *buff = NULL;
+
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
+ error:
+ *out = '\0';
+ return -1;
+ }
+
+ /*
+ * The easy case.
+ */
+ if (vp->da->type == PW_TYPE_OCTETS) {
+ p = vp->vp_octets;
+ len = vp->vp_length;
+ /*
+ * Cast the value_data_t of the VP to an octets string and
+ * print that as hex.
+ */
+ } else {
+ ret = value_data_cast(request, &dst, PW_TYPE_OCTETS, NULL, vp->da->type,
+ NULL, &vp->data, vp->vp_length);
+ if (ret < 0) {
+ REDEBUG("%s", fr_strerror());
+ goto error;
+ }
+ len = (size_t) ret;
+ p = buff = dst.octets;
+ }
+
+ rad_assert(p);
+
+ /*
+ * Don't truncate the data.
+ */
+ if (outlen < (len * 2)) {
+ rad_const_free(buff);
+ goto error;
+ }
+
+ for (i = 0; i < len; i++) {
+ snprintf(out + 2*i, 3, "%02x", p[i]);
+ }
+ rad_const_free(buff);
+
+ return len * 2;
+}
+
+/** Return the tag of an attribute reference
+ *
+ */
+static ssize_t xlat_tag(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ VALUE_PAIR *vp;
+
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
+ *out = '\0';
+ return 0;
+ }
+
+ if (!vp->da->flags.has_tag || !TAG_VALID(vp->tag)) {
+ *out = '\0';
+ return 0;
+ }
+
+ return snprintf(out, outlen, "%u", vp->tag);
+}
+
+/** Return the vendor of an attribute reference
+ *
+ */
+static ssize_t xlat_vendor(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ VALUE_PAIR *vp;
+ DICT_VENDOR *vendor;
+
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
+ *out = '\0';
+ return 0;
+ }
+
+ vendor = dict_vendorbyvalue(vp->da->vendor);
+ if (!vendor) {
+ *out = '\0';
+ return 0;
+ }
+ strlcpy(out, vendor->name, outlen);
+
+ return vendor->length;
+}
+
+/** Return the vendor number of an attribute reference
+ *
+ */
+static ssize_t xlat_vendor_num(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ VALUE_PAIR *vp;
+
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
+ *out = '\0';
+ return 0;
+ }
+
+ return snprintf(out, outlen, "%u", vp->da->vendor);
+}
+
+/** Return the attribute name of an attribute reference
+ *
+ */
+static ssize_t xlat_attr(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ VALUE_PAIR *vp;
+
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
+ *out = '\0';
+ return 0;
+ }
+ strlcpy(out, vp->da->name, outlen);
+
+ return strlen(vp->da->name);
+}
+
+/** Return the attribute number of an attribute reference
+ *
+ */
+static ssize_t xlat_attr_num(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ VALUE_PAIR *vp;
+
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
+ *out = '\0';
+ return 0;
+ }
+
+ return snprintf(out, outlen, "%u", vp->da->attr);
+}
+
+/** Print out attribute info
+ *
+ * Prints out all instances of a current attribute, or all attributes in a list.
+ *
+ * At higher debugging levels, also prints out alternative decodings of the same
+ * value. This is helpful to determine types for unknown attributes of long
+ * passed vendors, or just crazy/broken NAS.
+ *
+ * This expands to a zero length string.
+ */
+static ssize_t xlat_debug_attr(UNUSED void *instance, REQUEST *request, char const *fmt,
+ char *out, UNUSED size_t outlen)
+{
+ VALUE_PAIR *vp;
+ vp_cursor_t cursor;
+
+ vp_tmpl_t vpt;
+
+ if (!RDEBUG_ENABLED2) {
+ *out = '\0';
+ return -1;
+ }
+
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ if (tmpl_from_attr_str(&vpt, fmt, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false) <= 0) {
+ RDEBUG("%s", fr_strerror());
+ return -1;
+ }
+
+ RIDEBUG("Attributes matching \"%s\"", fmt);
+
+ RINDENT();
+ for (vp = tmpl_cursor_init(NULL, &cursor, request, &vpt);
+ vp;
+ vp = tmpl_cursor_next(&cursor, &vpt)) {
+ FR_NAME_NUMBER const *type;
+ char *value;
+
+ value = vp_aprints_value(vp, vp, '\'');
+ if (vp->da->flags.has_tag) {
+ RIDEBUG2("&%s:%s:%i %s %s",
+ fr_int2str(pair_lists, vpt.tmpl_list, "<INVALID>"),
+ vp->da->name,
+ vp->tag,
+ fr_int2str(fr_tokens, vp->op, "<INVALID>"),
+ value);
+ } else {
+ RIDEBUG2("&%s:%s %s %s",
+ fr_int2str(pair_lists, vpt.tmpl_list, "<INVALID>"),
+ vp->da->name,
+ fr_int2str(fr_tokens, vp->op, "<INVALID>"),
+ value);
+ }
+ talloc_free(value);
+
+ if (!RDEBUG_ENABLED3) continue;
+
+ if (vp->da->vendor) {
+ DICT_VENDOR *dv;
+
+ dv = dict_vendorbyvalue(vp->da->vendor);
+ RIDEBUG2("Vendor : %i (%s)", vp->da->vendor, dv ? dv->name : "unknown");
+ }
+ RIDEBUG2("Type : %s", fr_int2str(dict_attr_types, vp->da->type, "<INVALID>"));
+ RIDEBUG2("Length : %zu", vp->vp_length);
+
+ if (!RDEBUG_ENABLED4) continue;
+
+ type = dict_attr_types;
+ while (type->name) {
+ int pad;
+
+ value_data_t *dst = NULL;
+
+ ssize_t ret;
+
+ if ((PW_TYPE) type->number == vp->da->type) {
+ goto next_type;
+ }
+
+ switch (type->number) {
+ case PW_TYPE_INVALID: /* Not real type */
+ case PW_TYPE_MAX: /* Not real type */
+ case PW_TYPE_EXTENDED: /* Not safe/appropriate */
+ case PW_TYPE_LONG_EXTENDED: /* Not safe/appropriate */
+ case PW_TYPE_TLV: /* Not safe/appropriate */
+ case PW_TYPE_EVS: /* Not safe/appropriate */
+ case PW_TYPE_VSA: /* @fixme We need special behaviour for these */
+ case PW_TYPE_COMBO_IP_ADDR: /* Covered by IPv4 address IPv6 address */
+ case PW_TYPE_COMBO_IP_PREFIX: /* Covered by IPv4 address IPv6 address */
+ case PW_TYPE_TIMEVAL: /* Not a VALUE_PAIR type */
+ goto next_type;
+
+ default:
+ break;
+ }
+
+ dst = talloc_zero(vp, value_data_t);
+ ret = value_data_cast(dst, dst, type->number, NULL, vp->da->type, vp->da,
+ &vp->data, vp->vp_length);
+ if (ret < 0) goto next_type; /* We expect some to fail */
+
+ value = value_data_aprints(dst, type->number, NULL, dst, (size_t)ret, '\'');
+ if (!value) goto next_type;
+
+ if ((pad = (11 - strlen(type->name))) < 0) {
+ pad = 0;
+ }
+
+ RINDENT();
+ RDEBUG2("as %s%*s: %s", type->name, pad, " ", value);
+ REXDENT();
+ talloc_free(value);
+
+ next_type:
+ talloc_free(dst);
+ type++;
+ }
+ }
+ REXDENT();
+
+ *out = '\0';
+ return 0;
+}
+
+/** Processes fmt as a map string and applies it to the current request
+ *
+ * e.g. "%{map:&User-Name := 'foo'}"
+ *
+ * Allows sets of modifications to be cached and then applied.
+ * Useful for processing generic attributes from LDAP.
+ */
+static ssize_t xlat_map(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ vp_map_t *map = NULL;
+ int ret;
+
+ if (map_afrom_attr_str(request, &map, fmt,
+ REQUEST_CURRENT, PAIR_LIST_REQUEST,
+ REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) {
+ REDEBUG("Failed parsing \"%s\" as map: %s", fmt, fr_strerror());
+ return -1;
+ }
+
+ RINDENT();
+ ret = map_to_request(request, map, map_to_vp, NULL);
+ REXDENT();
+ talloc_free(map);
+ if (ret < 0) return strlcpy(out, "0", outlen);
+
+ return strlcpy(out, "1", outlen);
+}
+
+/** Prints the current module processing the request
+ *
+ */
+static ssize_t xlat_module(UNUSED void *instance, REQUEST *request,
+ UNUSED char const *fmt, char *out, size_t outlen)
+{
+ strlcpy(out, request->module, outlen);
+
+ return strlen(out);
+}
+
+#if defined(HAVE_REGEX) && defined(HAVE_PCRE)
+static ssize_t xlat_regex(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ char *p;
+ size_t len;
+
+ if (regex_request_to_sub_named(request, &p, request, fmt) < 0) {
+ *out = '\0';
+ return 0;
+ }
+
+ len = talloc_array_length(p);
+ if (len > outlen) {
+ RDEBUG("Insufficient buffer space to write subcapture value, needed %zu bytes, have %zu bytes",
+ len, outlen);
+ return -1;
+ }
+ strlcpy(out, p, outlen);
+
+ return len - 1; /* - \0 */
+}
+#endif
+
+#ifdef WITH_UNLANG
+/** Implements the Foreach-Variable-X
+ *
+ * @see modcall()
+ */
+static ssize_t xlat_foreach(void *instance, REQUEST *request,
+ UNUSED char const *fmt, char *out, size_t outlen)
+{
+ VALUE_PAIR **pvp;
+ size_t len;
+
+ /*
+ * See modcall, "FOREACH" for how this works.
+ */
+ pvp = (VALUE_PAIR **) request_data_reference(request, (void *)radius_get_vp, *(int*) instance);
+ if (!pvp || !*pvp) {
+ *out = '\0';
+ return 0;
+ }
+
+ len = vp_prints_value(out, outlen, *pvp, 0);
+ if (is_truncated(len, outlen)) {
+ RDEBUG("Insufficient buffer space to write foreach value");
+ return -1;
+ }
+
+ return len;
+}
+#endif
+
+/** Print data as string, if possible.
+ *
+ * If attribute "Foo" is defined as "octets" it will normally
+ * be printed as 0x0a0a0a. The xlat "%{string:Foo}" will instead
+ * expand to "\n\n\n"
+ */
+static ssize_t xlat_string(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ size_t len;
+ ssize_t ret;
+ VALUE_PAIR *vp;
+ uint8_t const *p;
+
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ if (outlen < 3) {
+ nothing:
+ *out = '\0';
+ return 0;
+ }
+
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) goto nothing;
+
+ ret = rad_vp2data(&p, vp);
+ if (ret < 0) {
+ return ret;
+ }
+
+ switch (vp->da->type) {
+ case PW_TYPE_OCTETS:
+ len = fr_prints(out, outlen, (char const *) p, vp->vp_length, '"');
+ break;
+
+ /*
+ * Note that "%{string:...}" is NOT binary safe!
+ * It is explicitly used to get rid of embedded zeros.
+ */
+ case PW_TYPE_STRING:
+ len = strlcpy(out, vp->vp_strvalue, outlen);
+ break;
+
+ default:
+ len = fr_prints(out, outlen, (char const *) p, ret, '\0');
+ break;
+ }
+
+ return len;
+}
+
+/** xlat expand string attribute value
+ *
+ */
+static ssize_t xlat_xlat(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ VALUE_PAIR *vp;
+
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ if (outlen < 3) {
+ nothing:
+ *out = '\0';
+ return 0;
+ }
+
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) goto nothing;
+
+ if (vp->da->type != PW_TYPE_STRING) goto nothing;
+
+ return radius_xlat(out, outlen, request, vp->vp_strvalue, NULL, NULL);
+}
+
+/** Dynamically change the debugging level for the current request
+ *
+ * Example %{debug:3}
+ */
+static ssize_t xlat_debug(UNUSED void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ int level = 0;
+
+ /*
+ * Expand to previous (or current) level
+ */
+ snprintf(out, outlen, "%d", request->log.lvl);
+
+ /*
+ * Assume we just want to get the current value and NOT set it to 0
+ */
+ if (!*fmt)
+ goto done;
+
+ level = atoi(fmt);
+ if (level == 0) {
+ request->log.lvl = RAD_REQUEST_LVL_NONE;
+ request->log.func = NULL;
+ } else {
+ if (level > 4) level = 4;
+
+ request->log.lvl = level;
+ request->log.func = vradlog_request;
+ }
+
+ done:
+ return strlen(out);
+}
+
+/*
+ * Compare two xlat_t structs, based ONLY on the module name.
+ */
+static int xlat_cmp(void const *one, void const *two)
+{
+ xlat_t const *a = one;
+ xlat_t const *b = two;
+
+ if (a->length != b->length) {
+ return a->length - b->length;
+ }
+
+ return memcmp(a->name, b->name, a->length);
+}
+
+
+/*
+ * find the appropriate registered xlat function.
+ */
+static xlat_t *xlat_find(char const *name)
+{
+ xlat_t my_xlat;
+
+ strlcpy(my_xlat.name, name, sizeof(my_xlat.name));
+ my_xlat.length = strlen(my_xlat.name);
+
+ return rbtree_finddata(xlat_root, &my_xlat);
+}
+
+
+/** Register an xlat function.
+ *
+ * @param[in] name xlat name.
+ * @param[in] func xlat function to be called.
+ * @param[in] escape function to sanitize any sub expansions passed to the xlat function.
+ * @param[in] instance of module that's registering the xlat function.
+ * @return 0 on success, -1 on failure
+ */
+int xlat_register(char const *name, xlat_func_t func, xlat_escape_t escape, void *instance)
+{
+ xlat_t *c;
+ xlat_t my_xlat;
+ rbnode_t *node;
+
+ if (!name || !*name) {
+ DEBUG("xlat_register: Invalid xlat name");
+ return -1;
+ }
+
+ /*
+ * First time around, build up the tree...
+ *
+ * FIXME: This code should be hoisted out of this function,
+ * and into a global "initialization". But it isn't critical...
+ */
+ if (!xlat_root) {
+#ifdef WITH_UNLANG
+ int i;
+#endif
+
+ xlat_root = rbtree_create(NULL, xlat_cmp, NULL, RBTREE_FLAG_REPLACE);
+ if (!xlat_root) {
+ DEBUG("xlat_register: Failed to create tree");
+ return -1;
+ }
+
+#ifdef WITH_UNLANG
+ for (i = 0; xlat_foreach_names[i] != NULL; i++) {
+ xlat_register(xlat_foreach_names[i],
+ xlat_foreach, NULL, &xlat_inst[i]);
+ c = xlat_find(xlat_foreach_names[i]);
+ rad_assert(c != NULL);
+ c->internal = true;
+ }
+#endif
+
+#define XLAT_REGISTER(_x) xlat_register(STRINGIFY(_x), xlat_ ## _x, NULL, NULL); \
+ c = xlat_find(STRINGIFY(_x)); \
+ rad_assert(c != NULL); \
+ c->internal = true
+
+ XLAT_REGISTER(concat);
+ XLAT_REGISTER(integer);
+ XLAT_REGISTER(strlen);
+ XLAT_REGISTER(length);
+ XLAT_REGISTER(hex);
+ XLAT_REGISTER(tag);
+ XLAT_REGISTER(vendor);
+ XLAT_REGISTER(vendor_num);
+ XLAT_REGISTER(attr);
+ XLAT_REGISTER(attr_num);
+ XLAT_REGISTER(string);
+ XLAT_REGISTER(xlat);
+ XLAT_REGISTER(map);
+ XLAT_REGISTER(module);
+ XLAT_REGISTER(debug_attr);
+#if defined(HAVE_REGEX) && defined(HAVE_PCRE)
+ XLAT_REGISTER(regex);
+#endif
+
+ xlat_register("debug", xlat_debug, NULL, &xlat_inst[0]);
+ c = xlat_find("debug");
+ rad_assert(c != NULL);
+ c->internal = true;
+ }
+
+ /*
+ * If it already exists, replace the instance.
+ */
+ strlcpy(my_xlat.name, name, sizeof(my_xlat.name));
+ my_xlat.length = strlen(my_xlat.name);
+ c = rbtree_finddata(xlat_root, &my_xlat);
+ if (c) {
+ if (c->internal) {
+ DEBUG("xlat_register: Cannot re-define internal xlat");
+ return -1;
+ }
+
+ c->func = func;
+ c->escape = escape;
+ c->instance = instance;
+ return 0;
+ }
+
+ /*
+ * Doesn't exist. Create it.
+ */
+ c = talloc_zero(xlat_root, xlat_t);
+
+ c->func = func;
+ c->escape = escape;
+ strlcpy(c->name, name, sizeof(c->name));
+ c->length = strlen(c->name);
+ c->instance = instance;
+
+ node = rbtree_insert_node(xlat_root, c);
+ if (!node) {
+ talloc_free(c);
+ return -1;
+ }
+
+ /*
+ * Ensure that the data is deleted when the node is
+ * deleted.
+ *
+ * @todo: Maybe this should be the other way around...
+ * when a thing IN the tree is deleted, it's automatically
+ * removed from the tree. But for now, this works.
+ */
+ (void) talloc_steal(node, c);
+ return 0;
+}
+
+/** Unregister an xlat function
+ *
+ * We can only have one function to call per name, so the passing of "func"
+ * here is extraneous.
+ *
+ * @param[in] name xlat to unregister.
+ * @param[in] func unused.
+ * @param[in] instance data.
+ */
+void xlat_unregister(char const *name, UNUSED xlat_func_t func, void *instance)
+{
+ xlat_t *c;
+ xlat_t my_xlat;
+
+ if (!name || !xlat_root) return;
+
+ strlcpy(my_xlat.name, name, sizeof(my_xlat.name));
+ my_xlat.length = strlen(my_xlat.name);
+
+ c = rbtree_finddata(xlat_root, &my_xlat);
+ if (!c) return;
+
+ if (c->instance != instance) return;
+
+ rbtree_deletebydata(xlat_root, c);
+}
+
+static int xlat_unregister_callback(void *instance, void *data)
+{
+ xlat_t *c = (xlat_t *) data;
+
+ if (c->instance != instance) return 0; /* keep walking */
+
+ return 2; /* delete it */
+}
+
+void xlat_unregister_module(void *instance)
+{
+ rbtree_walk(xlat_root, RBTREE_DELETE_ORDER, xlat_unregister_callback, instance);
+}
+
+/*
+ * Internal redundant handler for xlats
+ */
+typedef enum xlat_redundant_type_t {
+ XLAT_INVALID = 0,
+ XLAT_REDUNDANT,
+ XLAT_LOAD_BALANCE,
+ XLAT_REDUNDANT_LOAD_BALANCE,
+} xlat_redundant_type_t;
+
+typedef struct xlat_redundant_t {
+ xlat_redundant_type_t type;
+ uint32_t count;
+ CONF_SECTION *cs;
+} xlat_redundant_t;
+
+
+static ssize_t xlat_redundant(void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ xlat_redundant_t *xr = instance;
+ CONF_ITEM *ci;
+ char const *name;
+ xlat_t *xlat;
+
+ rad_assert(xr->type == XLAT_REDUNDANT);
+
+ /*
+ * Pick the first xlat which succeeds
+ */
+ for (ci = cf_item_find_next(xr->cs, NULL);
+ ci != NULL;
+ ci = cf_item_find_next(xr->cs, ci)) {
+ ssize_t rcode;
+
+ if (!cf_item_is_pair(ci)) continue;
+
+ name = cf_pair_attr(cf_item_to_pair(ci));
+ rad_assert(name != NULL);
+
+ xlat = xlat_find(name);
+ if (!xlat) continue;
+
+ rcode = xlat->func(xlat->instance, request, fmt, out, outlen);
+ if (rcode <= 0) continue;
+ return rcode;
+ }
+
+ /*
+ * Everything failed. Oh well.
+ */
+ *out = 0;
+ return 0;
+}
+
+
+static ssize_t xlat_load_balance(void *instance, REQUEST *request,
+ char const *fmt, char *out, size_t outlen)
+{
+ uint32_t count = 0;
+ xlat_redundant_t *xr = instance;
+ CONF_ITEM *ci;
+ CONF_ITEM *found = NULL;
+ char const *name;
+ xlat_t *xlat;
+
+ /*
+ * Choose a child at random.
+ */
+ for (ci = cf_item_find_next(xr->cs, NULL);
+ ci != NULL;
+ ci = cf_item_find_next(xr->cs, ci)) {
+ if (!cf_item_is_pair(ci)) continue;
+ count++;
+
+ /*
+ * Replace the previously found one with a random
+ * new one.
+ */
+ if ((count * (fr_rand() & 0xffff)) < (uint32_t) 0x10000) {
+ found = ci;
+ }
+ }
+
+ /*
+ * Plain load balancing: do one child, and only one child.
+ */
+ if (xr->type == XLAT_LOAD_BALANCE) {
+ name = cf_pair_attr(cf_item_to_pair(found));
+ rad_assert(name != NULL);
+
+ xlat = xlat_find(name);
+ if (!xlat) return -1;
+
+ return xlat->func(xlat->instance, request, fmt, out, outlen);
+ }
+
+ rad_assert(xr->type == XLAT_REDUNDANT_LOAD_BALANCE);
+
+ /*
+ * Try the random one we found. If it fails, keep going
+ * through the rest of the children.
+ */
+ ci = found;
+ do {
+ name = cf_pair_attr(cf_item_to_pair(ci));
+ rad_assert(name != NULL);
+
+ xlat = xlat_find(name);
+ if (xlat) {
+ ssize_t rcode;
+
+ rcode = xlat->func(xlat->instance, request, fmt, out, outlen);
+ if (rcode > 0) return rcode;
+ }
+
+ /*
+ * Go to the next one, wrapping around at the end.
+ */
+ ci = cf_item_find_next(xr->cs, ci);
+ if (!ci) ci = cf_item_find_next(xr->cs, NULL);
+ } while (ci != found);
+
+ return -1;
+}
+
+
+bool xlat_register_redundant(CONF_SECTION *cs)
+{
+ char const *name1, *name2;
+ xlat_redundant_t *xr;
+
+ name1 = cf_section_name1(cs);
+ name2 = cf_section_name2(cs);
+
+ if (!name2) return false;
+
+ if (xlat_find(name2)) {
+ cf_log_err_cs(cs, "An expansion is already registered for this name");
+ return false;
+ }
+
+ xr = talloc_zero(cs, xlat_redundant_t);
+ if (!xr) return false;
+
+ if (strcmp(name1, "redundant") == 0) {
+ xr->type = XLAT_REDUNDANT;
+
+ } else if (strcmp(name1, "redundant-load-balance") == 0) {
+ xr->type = XLAT_REDUNDANT_LOAD_BALANCE;
+
+ } else if (strcmp(name1, "load-balance") == 0) {
+ xr->type = XLAT_LOAD_BALANCE;
+
+ } else {
+ return false;
+ }
+
+ xr->cs = cs;
+
+ /*
+ * Get the number of children for load balancing.
+ */
+ if (xr->type == XLAT_REDUNDANT) {
+ if (xlat_register(name2, xlat_redundant, NULL, xr) < 0) {
+ talloc_free(xr);
+ return false;
+ }
+
+ } else {
+ CONF_ITEM *ci;
+
+ for (ci = cf_item_find_next(cs, NULL);
+ ci != NULL;
+ ci = cf_item_find_next(cs, ci)) {
+ if (!cf_item_is_pair(ci)) continue;
+
+ if (!xlat_find(cf_pair_attr(cf_item_to_pair(ci)))) {
+ talloc_free(xr);
+ return false;
+ }
+
+ xr->count++;
+ }
+
+ if (xlat_register(name2, xlat_load_balance, NULL, xr) < 0) {
+ talloc_free(xr);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/** Crappy temporary function to add attribute ref support to xlats
+ *
+ * This needs to die, and hopefully will die, when xlat functions accept
+ * xlat node structures.
+ *
+ * Provides either a pointer to a buffer which contains the value of the reference VALUE_PAIR
+ * in an architecture independent format. Or a pointer to the start of the fmt string.
+ *
+ * The pointer is only guaranteed to be valid between calls to xlat_fmt_to_ref,
+ * and so long as the source VALUE_PAIR is not freed.
+ *
+ * @param out where to write a pointer to the buffer to the data the xlat function needs to work on.
+ * @param request current request.
+ * @param fmt string.
+ * @returns the length of the data or -1 on error.
+ */
+ssize_t xlat_fmt_to_ref(uint8_t const **out, REQUEST *request, char const *fmt)
+{
+ VALUE_PAIR *vp;
+
+ while (isspace((uint8_t) *fmt)) fmt++;
+
+ if (fmt[0] == '&') {
+ if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
+ *out = NULL;
+ return -1;
+ }
+
+ return rad_vp2data(out, vp);
+ }
+
+ *out = (uint8_t const *)fmt;
+ return strlen(fmt);
+}
+
+/** De-register all xlat functions, used mainly for debugging.
+ *
+ */
+void xlat_free(void)
+{
+ rbtree_free(xlat_root);
+}
+
+#ifdef DEBUG_XLAT
+# define XLAT_DEBUG DEBUG3
+#else
+# define XLAT_DEBUG(...)
+#endif
+
+static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head,
+ char const **error);
+static ssize_t xlat_tokenize_literal(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head,
+ bool brace, char const **error);
+static size_t xlat_process(char **out, REQUEST *request, xlat_exp_t const * const head,
+ xlat_escape_t escape, void *escape_ctx);
+
+static ssize_t xlat_tokenize_alternation(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head,
+ char const **error)
+{
+ ssize_t slen;
+ char *p;
+ xlat_exp_t *node;
+
+ rad_assert(fmt[0] == '%');
+ rad_assert(fmt[1] == '{');
+ rad_assert(fmt[2] == '%');
+ rad_assert(fmt[3] == '{');
+
+ XLAT_DEBUG("ALTERNATE <-- %s", fmt);
+
+ node = talloc_zero(ctx, xlat_exp_t);
+ node->type = XLAT_ALTERNATE;
+
+ p = fmt + 2;
+ slen = xlat_tokenize_expansion(node, p, &node->child, error);
+ if (slen <= 0) {
+ talloc_free(node);
+ return slen - (p - fmt);
+ }
+ p += slen;
+
+ if (p[0] != ':') {
+ talloc_free(node);
+ *error = "Expected ':' after first expansion";
+ return -(p - fmt);
+ }
+ p++;
+
+ if (p[0] != '-') {
+ talloc_free(node);
+ *error = "Expected '-' after ':'";
+ return -(p - fmt);
+ }
+ p++;
+
+ /*
+ * Allow the RHS to be empty as a special case.
+ */
+ if (*p == '}') {
+ /*
+ * Hack up an empty string.
+ */
+ node->alternate = talloc_zero(node, xlat_exp_t);
+ node->alternate->type = XLAT_LITERAL;
+ node->alternate->fmt = talloc_typed_strdup(node->alternate, "");
+ *(p++) = '\0';
+
+ } else {
+ slen = xlat_tokenize_literal(node, p, &node->alternate, true, error);
+ if (slen <= 0) {
+ talloc_free(node);
+ return slen - (p - fmt);
+ }
+
+ if (!node->alternate) {
+ talloc_free(node);
+ *error = "Empty expansion is invalid";
+ return -(p - fmt);
+ }
+ p += slen;
+ }
+
+ *head = node;
+ return p - fmt;
+}
+
+static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head,
+ char const **error)
+{
+ ssize_t slen;
+ char *p, *q;
+ xlat_exp_t *node;
+ long num;
+
+ rad_assert(fmt[0] == '%');
+ rad_assert(fmt[1] == '{');
+
+ /*
+ * %{%{...}:-bar}
+ */
+ if ((fmt[2] == '%') && (fmt[3] == '{')) return xlat_tokenize_alternation(ctx, fmt, head, error);
+
+ XLAT_DEBUG("EXPANSION <-- %s", fmt);
+ node = talloc_zero(ctx, xlat_exp_t);
+ node->fmt = fmt + 2;
+ node->len = 0;
+
+#ifdef HAVE_REGEX
+ /*
+ * Handle regex's specially.
+ */
+ p = fmt + 2;
+ num = strtol(p, &q, 10);
+ if (p != q && (*q == '}')) {
+ XLAT_DEBUG("REGEX <-- %s", fmt);
+ *q = '\0';
+
+ if ((num > REQUEST_MAX_REGEX) || (num < 0)) {
+ talloc_free(node);
+ *error = "Invalid regex reference. Must be in range 0-" STRINGIFY(REQUEST_MAX_REGEX);
+ return -2;
+ }
+ node->attr.tmpl_num = num;
+
+ node->type = XLAT_REGEX;
+ *head = node;
+
+ return (q - fmt) + 1;
+ }
+#endif /* HAVE_REGEX */
+
+ /*
+ * %{Attr-Name}
+ * %{Attr-Name[#]}
+ * %{Tunnel-Password:1}
+ * %{Tunnel-Password:1[#]}
+ * %{request:Attr-Name}
+ * %{request:Tunnel-Password:1}
+ * %{request:Tunnel-Password:1[#]}
+ * %{mod:foo}
+ */
+
+ /*
+ * This is for efficiency, so we don't search for an xlat,
+ * when what's being referenced is obviously an attribute.
+ */
+ p = fmt + 2;
+ for (q = p; *q != '\0'; q++) {
+ if (*q == ':') break;
+
+ if (isspace((uint8_t) *q)) break;
+
+ if (*q == '[') continue;
+
+ if (*q == '}') break;
+ }
+
+ /*
+ * Check for empty expressions %{}
+ */
+ if ((*q == '}') && (q == p)) {
+ talloc_free(node);
+ *error = "Empty expression is invalid";
+ return -(p - fmt);
+ }
+
+ /*
+ * Might be a module name reference.
+ *
+ * If it's not, it's an attribute or parse error.
+ */
+ if (*q == ':') {
+ *q = '\0';
+ node->xlat = xlat_find(node->fmt);
+ if (node->xlat) {
+ /*
+ * %{mod:foo}
+ */
+ node->type = XLAT_MODULE;
+
+ p = q + 1;
+ XLAT_DEBUG("MOD <-- %s ... %s", node->fmt, p);
+
+ slen = xlat_tokenize_literal(node, p, &node->child, true, error);
+ if (slen < 0) {
+ talloc_free(node);
+ return slen - (p - fmt);
+ }
+ p += slen;
+
+ *head = node;
+ rad_assert(node->next == NULL);
+
+ return p - fmt;
+ }
+ *q = ':'; /* Avoids a strdup */
+ }
+
+ /*
+ * The first token ends with:
+ * - '[' - Which is an attribute index, so it must be an attribute.
+ * - '}' - The end of the expansion, which means it was a bareword.
+ */
+ slen = tmpl_from_attr_substr(&node->attr, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, true, true);
+ if (slen <= 0) {
+ /*
+ * If the parse error occurred before the ':'
+ * then the error is changed to 'Unknown module',
+ * as it was more likely to be a bad module name,
+ * than a request qualifier.
+ */
+ if ((*q == ':') && ((p + (slen * -1)) < q)) {
+ *error = "Unknown module";
+ } else {
+ *error = fr_strerror();
+ }
+
+ talloc_free(node);
+ return slen - (p - fmt);
+ }
+
+ /*
+ * Might be a virtual XLAT attribute
+ */
+ if (node->attr.type == TMPL_TYPE_ATTR_UNDEFINED) {
+ node->xlat = xlat_find(node->attr.tmpl_unknown_name);
+ if (node->xlat && node->xlat->instance && !node->xlat->internal) {
+ talloc_free(node);
+ *error = "Missing content in expansion";
+ return -(p - fmt) - slen;
+ }
+
+ if (node->xlat) {
+ node->type = XLAT_VIRTUAL;
+ node->fmt = node->attr.tmpl_unknown_name;
+
+ XLAT_DEBUG("VIRTUAL <-- %s", node->fmt);
+ *head = node;
+ rad_assert(node->next == NULL);
+ q++;
+ return q - fmt;
+ }
+
+ talloc_free(node);
+ *error = "Unknown attribute";
+ return -(p - fmt);
+ }
+
+ /*
+ * Might be a list, too...
+ */
+ node->type = XLAT_ATTRIBUTE;
+ p += slen;
+
+ if (*p != '}') {
+ talloc_free(node);
+ *error = "No matching closing brace";
+ return -1; /* second character of format string */
+ }
+ *p++ = '\0';
+ *head = node;
+ rad_assert(node->next == NULL);
+
+ return p - fmt;
+}
+
+
+static ssize_t xlat_tokenize_literal(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head,
+ bool brace, char const **error)
+{
+ char *p;
+ xlat_exp_t *node;
+
+ if (!*fmt) return 0;
+
+ XLAT_DEBUG("LITERAL <-- %s", fmt);
+
+ node = talloc_zero(ctx, xlat_exp_t);
+ node->fmt = fmt;
+ node->len = 0;
+ node->type = XLAT_LITERAL;
+
+ p = fmt;
+
+ while (*p) {
+ if (*p == '\\') {
+ if (!p[1]) {
+ talloc_free(node);
+ *error = "Invalid escape at end of string";
+ return -(p - fmt);
+ }
+
+ p += 2;
+ node->len += 2;
+ continue;
+ }
+
+ /*
+ * Process the expansion.
+ */
+ if ((p[0] == '%') && (p[1] == '{')) {
+ ssize_t slen;
+
+ XLAT_DEBUG("EXPANSION-2 <-- %s", node->fmt);
+
+ slen = xlat_tokenize_expansion(node, p, &node->next, error);
+ if (slen <= 0) {
+ talloc_free(node);
+ return slen - (p - fmt);
+ }
+ *p = '\0'; /* end the literal */
+ p += slen;
+
+ rad_assert(node->next != NULL);
+
+ /*
+ * Short-circuit the recursive call.
+ * This saves another function call and
+ * memory allocation.
+ */
+ if (!*p) break;
+
+ /*
+ * "foo %{User-Name} bar"
+ * LITERAL "foo "
+ * EXPANSION User-Name
+ * LITERAL " bar"
+ */
+ slen = xlat_tokenize_literal(node->next, p, &(node->next->next), brace, error);
+ rad_assert(slen != 0);
+ if (slen < 0) {
+ talloc_free(node);
+ return slen - (p - fmt);
+ }
+
+ brace = false; /* it was found above, or else the above code errored out */
+ p += slen;
+ break; /* stop processing the string */
+ }
+
+ /*
+ * Check for valid single-character expansions.
+ */
+ if (p[0] == '%') {
+ ssize_t slen;
+ xlat_exp_t *next;
+
+ if (!p[1] || !strchr("%}cdelmntCDGHIMSTYv", p[1])) {
+ talloc_free(node);
+ *error = "Invalid variable expansion";
+ p++;
+ return - (p - fmt);
+ }
+
+ next = talloc_zero(node, xlat_exp_t);
+ next->len = 1;
+
+ switch (p[1]) {
+ case '%':
+ case '}':
+ next->fmt = talloc_strndup(next, p + 1, 1);
+
+ XLAT_DEBUG("LITERAL-ESCAPED <-- %s", next->fmt);
+ next->type = XLAT_LITERAL;
+ break;
+
+ default:
+ next->fmt = p + 1;
+
+ XLAT_DEBUG("PERCENT <-- %c", *next->fmt);
+ next->type = XLAT_PERCENT;
+ break;
+ }
+
+ node->next = next;
+ *p = '\0';
+ p += 2;
+
+ if (!*p) break;
+
+ /*
+ * And recurse.
+ */
+ slen = xlat_tokenize_literal(node->next, p, &(node->next->next), brace, error);
+ rad_assert(slen != 0);
+ if (slen < 0) {
+ talloc_free(node);
+ return slen - (p - fmt);
+ }
+
+ brace = false; /* it was found above, or else the above code errored out */
+ p += slen;
+ break; /* stop processing the string */
+ }
+
+ /*
+ * If required, eat the brace.
+ */
+ if (brace && (*p == '}')) {
+ brace = false;
+ *p = '\0';
+ p++;
+ break;
+ }
+
+ p++;
+ node->len++;
+ }
+
+ /*
+ * We were told to look for a brace, but we ran off of
+ * the end of the string before we found one.
+ */
+ if (brace) {
+ *error = "Missing closing brace at end of string";
+ return -(p - fmt);
+ }
+
+ /*
+ * Squash zero-width literals
+ */
+ if (node->len > 0) {
+ *head = node;
+
+ } else {
+ (void) talloc_steal(ctx, node->next);
+ *head = node->next;
+ talloc_free(node);
+ }
+
+ return p - fmt;
+}
+
+
+static char const xlat_tabs[] = " ";
+
+static void xlat_tokenize_debug(xlat_exp_t const *node, int lvl)
+{
+ rad_assert(node != NULL);
+
+ if (lvl >= (int) sizeof(xlat_tabs)) lvl = sizeof(xlat_tabs);
+
+ while (node) {
+ switch (node->type) {
+ case XLAT_LITERAL:
+ DEBUG("%.*sliteral --> %s", lvl, xlat_tabs, node->fmt);
+ break;
+
+ case XLAT_PERCENT:
+ DEBUG("%.*spercent --> %c", lvl, xlat_tabs, node->fmt[0]);
+ break;
+
+ case XLAT_ATTRIBUTE:
+ rad_assert(node->attr.tmpl_da != NULL);
+ DEBUG("%.*sattribute --> %s", lvl, xlat_tabs, node->attr.tmpl_da->name);
+ rad_assert(node->child == NULL);
+ if ((node->attr.tmpl_tag != TAG_ANY) || (node->attr.tmpl_num != NUM_ANY)) {
+ DEBUG("%.*s{", lvl, xlat_tabs);
+
+ DEBUG("%.*sref %d", lvl + 1, xlat_tabs, node->attr.tmpl_request);
+ DEBUG("%.*slist %d", lvl + 1, xlat_tabs, node->attr.tmpl_list);
+
+ if (node->attr.tmpl_tag != TAG_ANY) {
+ DEBUG("%.*stag %d", lvl + 1, xlat_tabs, node->attr.tmpl_tag);
+ }
+ if (node->attr.tmpl_num != NUM_ANY) {
+ if (node->attr.tmpl_num == NUM_COUNT) {
+ DEBUG("%.*s[#]", lvl + 1, xlat_tabs);
+ } else if (node->attr.tmpl_num == NUM_ALL) {
+ DEBUG("%.*s[*]", lvl + 1, xlat_tabs);
+ } else {
+ DEBUG("%.*s[%d]", lvl + 1, xlat_tabs, node->attr.tmpl_num);
+ }
+ }
+
+ DEBUG("%.*s}", lvl, xlat_tabs);
+ }
+ break;
+
+ case XLAT_VIRTUAL:
+ rad_assert(node->fmt != NULL);
+ DEBUG("%.*svirtual --> %s", lvl, xlat_tabs, node->fmt);
+ break;
+
+ case XLAT_MODULE:
+ rad_assert(node->xlat != NULL);
+ DEBUG("%.*sxlat --> %s", lvl, xlat_tabs, node->xlat->name);
+ if (node->child) {
+ DEBUG("%.*s{", lvl, xlat_tabs);
+ xlat_tokenize_debug(node->child, lvl + 1);
+ DEBUG("%.*s}", lvl, xlat_tabs);
+ }
+ break;
+
+#ifdef HAVE_REGEX
+ case XLAT_REGEX:
+ DEBUG("%.*sregex-var --> %d", lvl, xlat_tabs, node->attr.tmpl_num);
+ break;
+#endif
+
+ case XLAT_ALTERNATE:
+ DEBUG("%.*sXLAT-IF {", lvl, xlat_tabs);
+ xlat_tokenize_debug(node->child, lvl + 1);
+ DEBUG("%.*s}", lvl, xlat_tabs);
+ DEBUG("%.*sXLAT-ELSE {", lvl, xlat_tabs);
+ xlat_tokenize_debug(node->alternate, lvl + 1);
+ DEBUG("%.*s}", lvl, xlat_tabs);
+ break;
+ }
+ node = node->next;
+ }
+}
+
+size_t xlat_sprint(char *buffer, size_t bufsize, xlat_exp_t const *node)
+{
+ size_t len;
+ char *p, *end;
+
+ if (!node) {
+ *buffer = '\0';
+ return 0;
+ }
+
+ p = buffer;
+ end = buffer + bufsize;
+
+ while (node) {
+ switch (node->type) {
+ case XLAT_LITERAL:
+ strlcpy(p, node->fmt, end - p);
+ p += strlen(p);
+ break;
+
+ case XLAT_PERCENT:
+ p[0] = '%';
+ p[1] = node->fmt[0];
+ p += 2;
+ break;
+
+ case XLAT_ATTRIBUTE:
+ *(p++) = '%';
+ *(p++) = '{';
+
+ /*
+ * The node MAY NOT be an attribute. It
+ * may be a list.
+ */
+ tmpl_prints(p, end - p, &node->attr, NULL);
+ if (*p == '&') {
+ memmove(p, p + 1, strlen(p + 1) + 1);
+ }
+ p += strlen(p);
+ *(p++) = '}';
+ break;
+#ifdef HAVE_REGEX
+ case XLAT_REGEX:
+ snprintf(p, end - p, "%%{%i}", node->attr.tmpl_num);
+ p += strlen(p);
+ break;
+#endif
+ case XLAT_VIRTUAL:
+ *(p++) = '%';
+ *(p++) = '{';
+ strlcpy(p, node->fmt, end - p);
+ p += strlen(p);
+ *(p++) = '}';
+ break;
+
+ case XLAT_MODULE:
+ *(p++) = '%';
+ *(p++) = '{';
+ strlcpy(p, node->xlat->name, end - p);
+ p += strlen(p);
+ *(p++) = ':';
+ rad_assert(node->child != NULL);
+ len = xlat_sprint(p, end - p, node->child);
+ p += len;
+ *(p++) = '}';
+ break;
+
+ case XLAT_ALTERNATE:
+ *(p++) = '%';
+ *(p++) = '{';
+
+ len = xlat_sprint(p, end - p, node->child);
+ p += len;
+
+ *(p++) = ':';
+ *(p++) = '-';
+
+ len = xlat_sprint(p, end - p, node->alternate);
+ p += len;
+
+ *(p++) = '}';
+ break;
+ }
+
+
+ if (p == end) break;
+
+ node = node->next;
+ }
+
+ *p = '\0';
+
+ return p - buffer;
+}
+
+ssize_t xlat_tokenize(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **head,
+ char const **error)
+{
+ return xlat_tokenize_literal(ctx, fmt, head, false, error);
+}
+
+
+/** Tokenize an xlat expansion
+ *
+ * @param[in] request the input request. Memory will be attached here.
+ * @param[in] fmt the format string to expand
+ * @param[out] head the head of the xlat list / tree structure.
+ */
+static ssize_t xlat_tokenize_request(REQUEST *request, char const *fmt, xlat_exp_t **head)
+{
+ ssize_t slen;
+ char *tokens;
+ char const *error = NULL;
+
+ *head = NULL;
+
+ /*
+ * Copy the original format string to a buffer so that
+ * the later functions can mangle it in-place, which is
+ * much faster.
+ */
+ tokens = talloc_typed_strdup(request, fmt);
+ if (!tokens) {
+ error = "Out of memory";
+ return -1;
+ }
+
+ slen = xlat_tokenize_literal(request, tokens, head, false, &error);
+
+ /*
+ * Zero length expansion, return a zero length node.
+ */
+ if (slen == 0) {
+ *head = talloc_zero(request, xlat_exp_t);
+ }
+
+ /*
+ * Output something like:
+ *
+ * "format string"
+ * " ^ error was here"
+ */
+ if (slen < 0) {
+ talloc_free(tokens);
+
+ if (!error) error = "Unknown error";
+
+ REMARKER(fmt, -slen, error);
+ return slen;
+ }
+
+ if (*head && (rad_debug_lvl > 2)) {
+ DEBUG("%s", fmt);
+ DEBUG("Parsed xlat tree:");
+ xlat_tokenize_debug(*head, 0);
+ }
+
+ /*
+ * All of the nodes point to offsets in the "tokens"
+ * string. Let's ensure that free'ing head will free
+ * "tokens", too.
+ */
+ (void) talloc_steal(*head, tokens);
+
+ return slen;
+}
+
+
+static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt,
+ bool escape, bool return_null, char const *concat)
+{
+ VALUE_PAIR *vp = NULL, *virtual = NULL;
+ RADIUS_PACKET *packet = NULL;
+ DICT_VALUE *dv;
+ char *ret = NULL;
+
+ vp_cursor_t cursor;
+ char quote = escape ? '"' : '\0';
+
+ rad_assert((vpt->type == TMPL_TYPE_ATTR) || (vpt->type == TMPL_TYPE_LIST));
+
+ /*
+ * We only support count and concatenate operations on lists.
+ */
+ if (vpt->type == TMPL_TYPE_LIST) {
+ vp = tmpl_cursor_init(NULL, &cursor, request, vpt);
+ goto do_print;
+ }
+
+ /*
+ * See if we're dealing with an attribute in the request
+ *
+ * This allows users to manipulate virtual attributes as if
+ * they were real ones.
+ */
+ vp = tmpl_cursor_init(NULL, &cursor, request, vpt);
+ if (vp) goto do_print;
+
+ /*
+ * We didn't find the VP in a list.
+ * If it's not a virtual one, and we're not meant to
+ * be counting it, return.
+ */
+ if (!vpt->tmpl_da->flags.virtual) {
+ if (vpt->tmpl_num == NUM_COUNT) goto do_print;
+ return NULL;
+ }
+
+ /*
+ * Switch out the request to the one specified by the template
+ */
+ if (radius_request(&request, vpt->tmpl_request) < 0) return NULL;
+
+ /*
+ * Some non-packet expansions
+ */
+ switch (vpt->tmpl_da->attr) {
+ default:
+ break; /* ignore them */
+
+ case PW_CLIENT_SHORTNAME:
+ if (vpt->tmpl_num == NUM_COUNT) goto count_virtual;
+ if (request->client && request->client->shortname) {
+ return talloc_typed_strdup(ctx, request->client->shortname);
+ }
+ return talloc_typed_strdup(ctx, "<UNKNOWN-CLIENT>");
+
+ case PW_REQUEST_PROCESSING_STAGE:
+ if (vpt->tmpl_num == NUM_COUNT) goto count_virtual;
+ if (request->component) {
+ return talloc_typed_strdup(ctx, request->component);
+ }
+ return talloc_typed_strdup(ctx, "server_core");
+
+ case PW_VIRTUAL_SERVER:
+ if (vpt->tmpl_num == NUM_COUNT) goto count_virtual;
+ if (!request->server) return NULL;
+ return talloc_typed_strdup(ctx, request->server);
+
+ case PW_MODULE_RETURN_CODE:
+ if (vpt->tmpl_num == NUM_COUNT) goto count_virtual;
+ if (!request->rcode) return NULL;
+ return talloc_typed_strdup(ctx, fr_int2str(modreturn_table, request->rcode, ""));
+ }
+
+ /*
+ * All of the attributes must now refer to a packet.
+ * If there's no packet, we can't print any attribute
+ * referencing it.
+ */
+ packet = radius_packet(request, vpt->tmpl_list);
+ if (!packet) {
+ if (return_null) return NULL;
+ return vp_aprints_type(ctx, vpt->tmpl_da->type);
+ }
+
+ vp = NULL;
+ switch (vpt->tmpl_da->attr) {
+ default:
+ break;
+
+ case PW_PACKET_TYPE:
+ dv = dict_valbyattr(PW_PACKET_TYPE, 0, packet->code);
+ if (dv) return talloc_typed_strdup(ctx, dv->name);
+ return talloc_typed_asprintf(ctx, "%d", packet->code);
+
+ case PW_RESPONSE_PACKET_TYPE:
+ {
+ int code = 0;
+
+#ifdef WITH_PROXY
+ if (request->proxy_reply && (!request->reply || !request->reply->code)) {
+ code = request->proxy_reply->code;
+ } else
+#endif
+ if (request->reply) {
+ code = request->reply->code;
+ }
+
+ if (!code) return NULL;
+
+ if (code >= FR_MAX_PACKET_CODE) {
+ return talloc_typed_asprintf(ctx, "%d", packet->code);
+ }
+
+ return talloc_typed_strdup(ctx, fr_packet_codes[code]);
+ }
+
+ /*
+ * Virtual attributes which require a temporary VALUE_PAIR
+ * to be allocated. We can't use stack allocated memory
+ * because of the talloc checks sprinkled throughout the
+ * various VP functions.
+ */
+ case PW_PACKET_AUTHENTICATION_VECTOR:
+ virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
+ fr_pair_value_memcpy(virtual, packet->vector, sizeof(packet->vector));
+ vp = virtual;
+ break;
+
+ case PW_CLIENT_IP_ADDRESS:
+ case PW_PACKET_SRC_IP_ADDRESS:
+ if (packet->src_ipaddr.af == AF_INET) {
+ virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
+ virtual->vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
+ vp = virtual;
+ }
+ break;
+
+ case PW_PACKET_DST_IP_ADDRESS:
+ if (packet->dst_ipaddr.af == AF_INET) {
+ virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
+ virtual->vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
+ vp = virtual;
+ }
+ break;
+
+ case PW_PACKET_SRC_IPV6_ADDRESS:
+ if (packet->src_ipaddr.af == AF_INET6) {
+ virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
+ memcpy(&virtual->vp_ipv6addr,
+ &packet->src_ipaddr.ipaddr.ip6addr,
+ sizeof(packet->src_ipaddr.ipaddr.ip6addr));
+ vp = virtual;
+ }
+ break;
+
+ case PW_PACKET_DST_IPV6_ADDRESS:
+ if (packet->dst_ipaddr.af == AF_INET6) {
+ virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
+ memcpy(&virtual->vp_ipv6addr,
+ &packet->dst_ipaddr.ipaddr.ip6addr,
+ sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
+ vp = virtual;
+ }
+ break;
+
+ case PW_PACKET_SRC_PORT:
+ virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
+ virtual->vp_integer = packet->src_port;
+ vp = virtual;
+ break;
+
+ case PW_PACKET_DST_PORT:
+ virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
+ virtual->vp_integer = packet->dst_port;
+ vp = virtual;
+ break;
+ }
+
+ /*
+ * Fake various operations for virtual attributes.
+ */
+ if (virtual) {
+ if (vpt->tmpl_num != NUM_ANY) switch (vpt->tmpl_num) {
+ /*
+ * [n] is NULL (we only have [0])
+ */
+ default:
+ goto finish;
+ /*
+ * [*] means only one.
+ */
+ case NUM_ALL:
+ break;
+
+ /*
+ * [#] means 1 (as there's only one)
+ */
+ case NUM_COUNT:
+ count_virtual:
+ ret = talloc_strdup(ctx, "1");
+ goto finish;
+
+ /*
+ * [0] is fine (get the first instance)
+ */
+ case 0:
+ break;
+ }
+ goto print;
+ }
+
+do_print:
+ switch (vpt->tmpl_num) {
+ /*
+ * Return a count of the VPs.
+ */
+ case NUM_COUNT:
+ {
+ int count = 0;
+
+ for (vp = tmpl_cursor_init(NULL, &cursor, request, vpt);
+ vp;
+ vp = tmpl_cursor_next(&cursor, vpt)) count++;
+
+ return talloc_typed_asprintf(ctx, "%d", count);
+ }
+
+
+ /*
+ * Concatenate all values together,
+ * separated by commas.
+ */
+ case NUM_ALL:
+ {
+ char *p, *q;
+
+ if (!fr_cursor_current(&cursor)) return NULL;
+ p = vp_aprints_value(ctx, vp, quote);
+ if (!p) return NULL;
+
+ while ((vp = tmpl_cursor_next(&cursor, vpt)) != NULL) {
+ q = vp_aprints_value(ctx, vp, quote);
+ if (!q) return NULL;
+ p = talloc_strdup_append(p, concat);
+ p = talloc_strdup_append(p, q);
+ }
+
+ return p;
+ }
+
+ default:
+ /*
+ * The cursor was set to the correct
+ * position above by tmpl_cursor_init.
+ */
+ vp = fr_cursor_current(&cursor);
+ break;
+ }
+
+ if (!vp) {
+ if (return_null) return NULL;
+ return vp_aprints_type(ctx, vpt->tmpl_da->type);
+ }
+
+print:
+ ret = vp_aprints_value(ctx, vp, quote);
+
+finish:
+ talloc_free(virtual);
+ return ret;
+}
+
+#ifdef DEBUG_XLAT
+static const char xlat_spaces[] = " ";
+#endif
+
+static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * const node,
+ xlat_escape_t escape, void *escape_ctx,
+#ifndef DEBUG_XLAT
+ UNUSED
+#endif
+ int lvl)
+{
+ ssize_t rcode;
+ char *str = NULL, *child;
+ char const *p;
+
+ XLAT_DEBUG("%.*sxlat aprint %d %s", lvl, xlat_spaces, node->type, node->fmt);
+
+ switch (node->type) {
+ /*
+ * Don't escape this.
+ */
+ case XLAT_LITERAL:
+ XLAT_DEBUG("%.*sxlat_aprint LITERAL", lvl, xlat_spaces);
+ return talloc_typed_strdup(ctx, node->fmt);
+
+ /*
+ * Do a one-character expansion.
+ */
+ case XLAT_PERCENT:
+ {
+ char *nl;
+ size_t freespace = 256;
+ struct tm ts;
+ time_t when;
+ int usec;
+
+ XLAT_DEBUG("%.*sxlat_aprint PERCENT", lvl, xlat_spaces);
+
+ str = talloc_array(ctx, char, freespace); /* @todo do better allocation */
+ p = node->fmt;
+
+ when = request->timestamp;
+ usec = 0;
+ if (request->packet) {
+ when = request->packet->timestamp.tv_sec;
+ usec = request->packet->timestamp.tv_usec;
+ }
+
+ switch (*p) {
+ case '%':
+ str[0] = '%';
+ str[1] = '\0';
+ break;
+
+ case 'c': /* current epoch time seconds */
+ snprintf(str, freespace, "%" PRIu64, (uint64_t) time(NULL));
+ break;
+
+ case 'd': /* request day */
+ if (!localtime_r(&when, &ts)) goto error;
+ strftime(str, freespace, "%d", &ts);
+ break;
+
+ case 'e': /* request second */
+ if (!localtime_r(&when, &ts)) goto error;
+
+ snprintf(str, freespace, "%d", ts.tm_sec);
+ break;
+
+ case 'l': /* request timestamp */
+ snprintf(str, freespace, "%lu",
+ (unsigned long) when);
+ break;
+
+ case 'm': /* request month */
+ if (!localtime_r(&when, &ts)) goto error;
+ strftime(str, freespace, "%m", &ts);
+ break;
+
+ case 'n': /* Request Number*/
+ snprintf(str, freespace, "%u", request->number);
+ break;
+
+ case 't': /* request timestamp */
+ CTIME_R(&when, str, freespace);
+ nl = strchr(str, '\n');
+ if (nl) *nl = '\0';
+ break;
+
+ case 'C': /* current epoch time microseconds */
+ {
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+
+ snprintf(str, freespace, "%" PRIu64, (uint64_t) tv.tv_usec);
+ }
+ break;
+
+ case 'D': /* request date */
+ if (!localtime_r(&when, &ts)) goto error;
+ strftime(str, freespace, "%Y%m%d", &ts);
+ break;
+
+ case 'G': /* request minute */
+ if (!localtime_r(&when, &ts)) goto error;
+ strftime(str, freespace, "%M", &ts);
+ break;
+
+ case 'H': /* request hour */
+ if (!localtime_r(&when, &ts)) goto error;
+ strftime(str, freespace, "%H", &ts);
+ break;
+
+ case 'I': /* Request ID */
+ if (request->packet) {
+ snprintf(str, freespace, "%i", request->packet->id);
+ }
+ break;
+
+ case 'M': /* request microsecond component */
+ snprintf(str, freespace, "%06u", (unsigned int) usec);
+ break;
+
+ case 'S': /* request timestamp in SQL format*/
+ if (!localtime_r(&when, &ts)) goto error;
+ strftime(str, freespace, "%Y-%m-%d %H:%M:%S", &ts);
+ break;
+
+ case 'T': /* request timestamp */
+ if (!localtime_r(&when, &ts)) goto error;
+ nl = str + strftime(str, freespace, "%Y-%m-%d-%H.%M.%S", &ts);
+ rad_assert(((str + freespace) - nl) >= 8);
+ snprintf(nl, (str + freespace) - nl, ".%06d", usec);
+ break;
+
+ case 'Y': /* request year */
+ if (!localtime_r(&when, &ts)) {
+ error:
+ REDEBUG("Failed converting packet timestamp to localtime: %s", fr_syserror(errno));
+ talloc_free(str);
+ return NULL;
+ }
+ strftime(str, freespace, "%Y", &ts);
+ break;
+
+ case 'v': /* Version of code */
+ RWDEBUG("%%v is deprecated and will be removed. Use ${version.freeradius-server}");
+ snprintf(str, freespace, "%s", radiusd_version_short);
+ break;
+
+ default:
+ rad_assert(0 == 1);
+ break;
+ }
+ }
+ break;
+
+ case XLAT_ATTRIBUTE:
+ XLAT_DEBUG("%.*sxlat_aprint ATTRIBUTE", lvl, xlat_spaces);
+
+ /*
+ * Some attributes are virtual <sigh>
+ */
+ str = xlat_getvp(ctx, request, &node->attr, escape ? false : true, true, ",");
+ if (str) {
+ XLAT_DEBUG("%.*sEXPAND attr %s", lvl, xlat_spaces, node->attr.tmpl_da->name);
+ XLAT_DEBUG("%.*s ---> %s", lvl ,xlat_spaces, str);
+ }
+ break;
+
+ case XLAT_VIRTUAL:
+ XLAT_DEBUG("xlat_aprint VIRTUAL");
+ str = talloc_array(ctx, char, 2048); /* FIXME: have the module call talloc_typed_asprintf */
+ rcode = node->xlat->func(node->xlat->instance, request, NULL, str, 2048);
+ if (rcode < 0) {
+ talloc_free(str);
+ return NULL;
+ }
+ RDEBUG2("EXPAND %s", node->xlat->name);
+ RDEBUG2(" --> %s", str);
+
+ /*
+ * Resize the buffer to the correct size.
+ */
+ if (rcode == 0) {
+ talloc_free(str);
+ str = talloc_strdup(ctx, "");
+ } else if (rcode < 2047) {
+ child = talloc_memdup(ctx, str, rcode + 1);
+ talloc_free(str);
+ str = child;
+ }
+ break;
+
+ case XLAT_MODULE:
+ XLAT_DEBUG("xlat_aprint MODULE");
+
+ if (node->child) {
+ if (xlat_process(&child, request, node->child, node->xlat->escape, node->xlat->instance) == 0) {
+ return NULL;
+ }
+
+ XLAT_DEBUG("%.*sEXPAND mod %s %s", lvl, xlat_spaces, node->fmt, node->child->fmt);
+ } else {
+ XLAT_DEBUG("%.*sEXPAND mod %s", lvl, xlat_spaces, node->fmt);
+ child = talloc_typed_strdup(ctx, "");
+ }
+
+ XLAT_DEBUG("%.*s ---> %s", lvl, xlat_spaces, child);
+
+ /*
+ * Smash \n --> CR.
+ *
+ * The OUTPUT of xlat is a "raw" string. The INPUT is a printable string.
+ *
+ * This is really the reverse of fr_prints().
+ */
+ if (cf_new_escape && *child) {
+ ssize_t slen;
+ PW_TYPE type;
+ value_data_t data;
+
+ type = PW_TYPE_STRING;
+ slen = value_data_from_str(request, &data, &type, NULL, child, talloc_array_length(child) - 1, '"');
+ if (slen <= 0) {
+ talloc_free(child);
+ return NULL;
+ }
+
+ talloc_free(child);
+ child = data.ptr;
+
+ } else {
+ char *q;
+
+ p = q = child;
+ while (*p) {
+ if (*p == '\\') switch (p[1]) {
+ default:
+ *(q++) = p[1];
+ p += 2;
+ continue;
+
+ case 'n':
+ *(q++) = '\n';
+ p += 2;
+ continue;
+
+ case 't':
+ *(q++) = '\t';
+ p += 2;
+ continue;
+ }
+
+ *(q++) = *(p++);
+ }
+ *q = '\0';
+ }
+
+ str = talloc_array(ctx, char, 2048); /* FIXME: have the module call talloc_typed_asprintf */
+ *str = '\0'; /* Be sure the string is NULL terminated, we now only free on error */
+
+ rcode = node->xlat->func(node->xlat->instance, request, child, str, 2048);
+ talloc_free(child);
+ if (rcode < 0) {
+ talloc_free(str);
+ return NULL;
+ }
+ break;
+
+#ifdef HAVE_REGEX
+ case XLAT_REGEX:
+ XLAT_DEBUG("%.*sxlat_aprint REGEX", lvl, xlat_spaces);
+ if (regex_request_to_sub(ctx, &str, request, node->attr.tmpl_num) < 0) return NULL;
+
+ break;
+#endif
+
+ case XLAT_ALTERNATE:
+ XLAT_DEBUG("%.*sxlat_aprint ALTERNATE", lvl, xlat_spaces);
+ rad_assert(node->child != NULL);
+ rad_assert(node->alternate != NULL);
+
+ /*
+ * Call xlat_process recursively. The child /
+ * alternate nodes may have "next" pointers, and
+ * those need to be expanded.
+ */
+ if (xlat_process(&str, request, node->child, escape, escape_ctx) > 0) {
+ XLAT_DEBUG("%.*sALTERNATE got first string: %s", lvl, xlat_spaces, str);
+ } else {
+ (void) xlat_process(&str, request, node->alternate, escape, escape_ctx);
+ XLAT_DEBUG("%.*sALTERNATE got alternate string %s", lvl, xlat_spaces, str);
+ }
+ break;
+ }
+
+ /*
+ * If there's no data, return that, instead of an empty string.
+ */
+ if (str && !str[0]) {
+ talloc_free(str);
+ return NULL;
+ }
+
+ /*
+ * Escape the non-literals we found above.
+ */
+ if (str && escape) {
+ size_t len;
+ char *escaped;
+
+ len = talloc_array_length(str) * 3;
+
+ escaped = talloc_array(ctx, char, len);
+ escape(request, escaped, len, str, escape_ctx);
+ talloc_free(str);
+ str = escaped;
+ }
+
+ return str;
+}
+
+
+static size_t xlat_process(char **out, REQUEST *request, xlat_exp_t const * const head,
+ xlat_escape_t escape, void *escape_ctx)
+{
+ int i, list;
+ size_t total;
+ char **array, *answer;
+ xlat_exp_t const *node;
+
+ *out = NULL;
+
+ /*
+ * There are no nodes to process, so the result is a zero
+ * length string.
+ */
+ if (!head) {
+ *out = talloc_zero_array(request, char, 1);
+ return 0;
+ }
+
+ /*
+ * Hack for speed. If it's one expansion, just allocate
+ * that and return, instead of allocating an intermediary
+ * array.
+ */
+ if (!head->next) {
+ /*
+ * Pass the MAIN escape function. Recursive
+ * calls will call node-specific escape
+ * functions.
+ */
+ answer = xlat_aprint(request, request, head, escape, escape_ctx, 0);
+ if (!answer) {
+ *out = talloc_zero_array(request, char, 1);
+ return 0;
+ }
+ *out = answer;
+ return strlen(answer);
+ }
+
+ list = 0; /* FIXME: calculate this once */
+ for (node = head; node != NULL; node = node->next) {
+ list++;
+ }
+
+ array = talloc_array(request, char *, list);
+ if (!array) return -1;
+
+ for (node = head, i = 0; node != NULL; node = node->next, i++) {
+ array[i] = xlat_aprint(array, request, node, escape, escape_ctx, 0); /* may be NULL */
+ }
+
+ total = 0;
+ for (i = 0; i < list; i++) {
+ if (array[i]) total += strlen(array[i]); /* FIXME: calculate strlen once */
+ }
+
+ if (!total) {
+ talloc_free(array);
+ *out = talloc_zero_array(request, char, 1);
+ return 0;
+ }
+
+ answer = talloc_array(request, char, total + 1);
+
+ total = 0;
+ for (i = 0; i < list; i++) {
+ size_t len;
+
+ if (array[i]) {
+ len = strlen(array[i]);
+ memcpy(answer + total, array[i], len);
+ total += len;
+ }
+ }
+ answer[total] = '\0';
+ talloc_free(array); /* and child entries */
+
+ *out = answer;
+ return total;
+}
+
+
+/** Replace %whatever in a string.
+ *
+ * See 'doc/configuration/variables.rst' for more information.
+ *
+ * @param[out] out Where to write pointer to output buffer.
+ * @param[in] outlen Size of out.
+ * @param[in] request current request.
+ * @param[in] node the xlat structure to expand
+ * @param[in] escape function to escape final value e.g. SQL quoting.
+ * @param[in] escape_ctx pointer to pass to escape function.
+ * @return length of string written @bug should really have -1 for failure
+ */
+static ssize_t xlat_expand_struct(char **out, size_t outlen, REQUEST *request, xlat_exp_t const *node,
+ xlat_escape_t escape, void *escape_ctx)
+{
+ char *buff;
+ ssize_t len;
+
+ rad_assert(node != NULL);
+
+ len = xlat_process(&buff, request, node, escape, escape_ctx);
+ if ((len < 0) || !buff) {
+ rad_assert(buff == NULL);
+ if (*out) *out[0] = '\0';
+ return len;
+ }
+
+ len = strlen(buff);
+
+ /*
+ * If out doesn't point to an existing buffer
+ * copy the pointer to our buffer over.
+ */
+ if (!*out) {
+ *out = buff;
+ return len;
+ }
+
+ /*
+ * Otherwise copy the malloced buffer to the fixed one.
+ */
+ strlcpy(*out, buff, outlen);
+ talloc_free(buff);
+ return len;
+}
+
+static ssize_t xlat_expand(char **out, size_t outlen, REQUEST *request, char const *fmt,
+ xlat_escape_t escape, void *escape_ctx) CC_HINT(nonnull (1, 3, 4));
+
+/** Replace %whatever in a string.
+ *
+ * See 'doc/configuration/variables.rst' for more information.
+ *
+ * @param[out] out Where to write pointer to output buffer.
+ * @param[in] outlen Size of out.
+ * @param[in] request current request.
+ * @param[in] fmt string to expand.
+ * @param[in] escape function to escape final value e.g. SQL quoting.
+ * @param[in] escape_ctx pointer to pass to escape function.
+ * @return length of string written @bug should really have -1 for failure
+ */
+static ssize_t xlat_expand(char **out, size_t outlen, REQUEST *request, char const *fmt,
+ xlat_escape_t escape, void *escape_ctx)
+{
+ ssize_t len;
+ xlat_exp_t *node;
+
+ /*
+ * Give better errors than the old code.
+ */
+ len = xlat_tokenize_request(request, fmt, &node);
+ if (len == 0) {
+ if (*out) {
+ *out[0] = '\0';
+ } else {
+ *out = talloc_zero_array(request, char, 1);
+ }
+ return 0;
+ }
+
+ if (len < 0) {
+ if (*out) *out[0] = '\0';
+ return -1;
+ }
+
+ len = xlat_expand_struct(out, outlen, request, node, escape, escape_ctx);
+ talloc_free(node);
+
+ RDEBUG2("EXPAND %s", fmt);
+ RDEBUG2(" --> %s", *out);
+
+ return len;
+}
+
+/** Try to convert an xlat to a tmpl for efficiency
+ *
+ * @param ctx to allocate new vp_tmpl_t in.
+ * @param node to convert.
+ * @return NULL if unable to convert (not necessarily error), or a new vp_tmpl_t.
+ */
+vp_tmpl_t *xlat_to_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_t *node)
+{
+ vp_tmpl_t *vpt;
+
+ if (node->next || (node->type != XLAT_ATTRIBUTE) || (node->attr.type != TMPL_TYPE_ATTR)) return NULL;
+
+ /*
+ * Concat means something completely different as an attribute reference
+ * Count isn't implemented.
+ */
+ if ((node->attr.tmpl_num == NUM_COUNT) || (node->attr.tmpl_num == NUM_ALL)) return NULL;
+
+ vpt = tmpl_alloc(ctx, TMPL_TYPE_ATTR, node->fmt, -1);
+ if (!vpt) return NULL;
+ memcpy(&vpt->data, &node->attr.data, sizeof(vpt->data));
+
+ VERIFY_TMPL(vpt);
+
+ return vpt;
+}
+
+/** Try to convert attr tmpl to an xlat for &attr[*] and artificially constructing expansions
+ *
+ * @param ctx to allocate new xlat_expt_t in.
+ * @param vpt to convert.
+ * @return NULL if unable to convert (not necessarily error), or a new vp_tmpl_t.
+ */
+xlat_exp_t *xlat_from_tmpl_attr(TALLOC_CTX *ctx, vp_tmpl_t *vpt)
+{
+ xlat_exp_t *node;
+
+ if (vpt->type != TMPL_TYPE_ATTR) return NULL;
+
+ node = talloc_zero(ctx, xlat_exp_t);
+ node->type = XLAT_ATTRIBUTE;
+ node->fmt = talloc_bstrndup(node, vpt->name, vpt->len);
+ tmpl_init(&node->attr, TMPL_TYPE_ATTR, node->fmt, talloc_array_length(node->fmt) - 1);
+ memcpy(&node->attr.data, &vpt->data, sizeof(vpt->data));
+
+ return node;
+}
+
+ssize_t radius_xlat(char *out, size_t outlen, REQUEST *request, char const *fmt, xlat_escape_t escape, void *ctx)
+{
+ return xlat_expand(&out, outlen, request, fmt, escape, ctx);
+}
+
+ssize_t radius_xlat_struct(char *out, size_t outlen, REQUEST *request, xlat_exp_t const *xlat, xlat_escape_t escape, void *ctx)
+{
+ return xlat_expand_struct(&out, outlen, request, xlat, escape, ctx);
+}
+
+ssize_t radius_axlat(char **out, REQUEST *request, char const *fmt, xlat_escape_t escape, void *ctx)
+{
+ *out = NULL;
+ return xlat_expand(out, 0, request, fmt, escape, ctx);
+}
+
+ssize_t radius_axlat_struct(char **out, REQUEST *request, xlat_exp_t const *xlat, xlat_escape_t escape, void *ctx)
+{
+ *out = NULL;
+ return xlat_expand_struct(out, 0, request, xlat, escape, ctx);
+}