add usec resolution to %S. Fixes #1917
[freeradius.git] / src / main / xlat.c
index 919b1d3..3198728 100644 (file)
@@ -37,8 +37,8 @@ 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.
-       RAD_XLAT_FUNC           func;                   //!< xlat function.
-       RADIUS_ESCAPE_STRING    escape;                 //!< Escape function to apply to dynamic input to func.
+       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;
 
@@ -167,10 +167,14 @@ static ssize_t xlat_integer(UNUSED void *instance, REQUEST *request,
                return snprintf(out, outlen, "%u", htonl((*(uint32_t *)(vp->vp_ipv4prefix + 2))));
 
        case PW_TYPE_INTEGER:
-       case PW_TYPE_DATE:
                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);
 
@@ -470,7 +474,7 @@ static ssize_t xlat_debug_attr(UNUSED void *instance, REQUEST *request, char con
                                              &vp->data, vp->vp_length);
                        if (ret < 0) goto next_type;    /* We expect some to fail */
 
-                       value = vp_data_aprints_value(dst, type->number, NULL, dst, (size_t)ret, '\'');
+                       value = value_data_aprints(dst, type->number, NULL, dst, (size_t)ret, '\'');
                        if (!value) goto next_type;
 
                        if ((pad = (11 - strlen(type->name))) < 0) {
@@ -491,6 +495,35 @@ static ssize_t xlat_debug_attr(UNUSED void *instance, REQUEST *request, char con
        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
  *
  */
@@ -590,6 +623,10 @@ static ssize_t xlat_string(UNUSED void *instance, REQUEST *request,
                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;
@@ -620,6 +657,8 @@ static ssize_t xlat_xlat(UNUSED void *instance, REQUEST *request,
 
        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);
 }
 
@@ -696,7 +735,7 @@ static xlat_t *xlat_find(char const *name)
  * @param[in] instance of module that's registering the xlat function.
  * @return 0 on success, -1 on failure
  */
-int xlat_register(char const *name, RAD_XLAT_FUNC func, RADIUS_ESCAPE_STRING escape, void *instance)
+int xlat_register(char const *name, xlat_func_t func, xlat_escape_t escape, void *instance)
 {
        xlat_t  *c;
        xlat_t  my_xlat;
@@ -750,6 +789,7 @@ int xlat_register(char const *name, RAD_XLAT_FUNC func, RADIUS_ESCAPE_STRING esc
                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)
@@ -818,7 +858,7 @@ int xlat_register(char const *name, RAD_XLAT_FUNC func, RADIUS_ESCAPE_STRING esc
  * @param[in] func unused.
  * @param[in] instance data.
  */
-void xlat_unregister(char const *name, UNUSED RAD_XLAT_FUNC func, void *instance)
+void xlat_unregister(char const *name, UNUSED xlat_func_t func, void *instance)
 {
        xlat_t  *c;
        xlat_t          my_xlat;
@@ -1099,7 +1139,7 @@ static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **
 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,
-                          RADIUS_ESCAPE_STRING escape, void *escape_ctx);
+                          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)
@@ -1246,6 +1286,7 @@ static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **
         *      Check for empty expressions %{}
         */
        if ((*q == '}') && (q == p)) {
+               talloc_free(node);
                *error = "Empty expression is invalid";
                return -(p - fmt);
        }
@@ -1300,6 +1341,8 @@ static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **
                } else {
                        *error = fr_strerror();
                }
+
+               talloc_free(node);
                return slen - (p - fmt);
        }
 
@@ -1308,6 +1351,12 @@ static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **
         */
        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;
@@ -1332,7 +1381,7 @@ static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **
                *error = "No matching closing brace";
                return -1;      /* second character of format string */
        }
-       p++;
+       *p++ = '\0';
        *head = node;
        rad_assert(node->next == NULL);
 
@@ -1364,7 +1413,9 @@ static ssize_t xlat_tokenize_literal(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **he
                                *error = "Invalid escape at end of string";
                                return -(p - fmt);
                        }
+
                        p += 2;
+                       node->len += 2;
                        continue;
                }
 
@@ -1418,27 +1469,31 @@ static ssize_t xlat_tokenize_literal(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **he
                        ssize_t slen;
                        xlat_exp_t *next;
 
-                       if (!p[1] || !strchr("%dlmntDGHISTYv", p[1])) {
-                                       talloc_free(node);
-                                       *error = "Invalid variable expansion";
-                                       p++;
-                                       return - (p - fmt);
+                       if (!p[1] || !strchr("%}dlmntDGHISTYv", p[1])) {
+                               talloc_free(node);
+                               *error = "Invalid variable expansion";
+                               p++;
+                               return - (p - fmt);
                        }
 
                        next = talloc_zero(node, xlat_exp_t);
                        next->len = 1;
 
-                       if (p[1] == '%') {
-                               next->fmt = talloc_typed_strdup(next, "%");
+                       switch (p[1]) {
+                       case '%':
+                       case '}':
+                               next->fmt = talloc_strndup(next, p + 1, 1);
 
-                               XLAT_DEBUG("LITERAL-PERCENT <-- %s", next->fmt);
+                               XLAT_DEBUG("LITERAL-ESCAPED <-- %s", next->fmt);
                                next->type = XLAT_LITERAL;
+                               break;
 
-                       } else {
+                       default:
                                next->fmt = p + 1;
 
                                XLAT_DEBUG("PERCENT <-- %c", *next->fmt);
                                next->type = XLAT_PERCENT;
+                               break;
                        }
 
                        node->next = next;
@@ -1568,10 +1623,10 @@ static void xlat_tokenize_debug(xlat_exp_t const *node, int lvl)
 #endif
 
                case XLAT_ALTERNATE:
-                       DEBUG("%.*sif {", lvl, xlat_tabs);
+                       DEBUG("%.*sXLAT-IF {", lvl, xlat_tabs);
                        xlat_tokenize_debug(node->child, lvl + 1);
                        DEBUG("%.*s}", lvl, xlat_tabs);
-                       DEBUG("%.*selse {", lvl, xlat_tabs);
+                       DEBUG("%.*sXLAT-ELSE {", lvl, xlat_tabs);
                        xlat_tokenize_debug(node->alternate, lvl + 1);
                        DEBUG("%.*s}", lvl, xlat_tabs);
                        break;
@@ -1781,11 +1836,19 @@ static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt,
        RADIUS_PACKET *packet = NULL;
        DICT_VALUE *dv;
        char *ret = NULL;
-       int err;
 
+       vp_cursor_t cursor;
        char quote = escape ? '"' : '\0';
 
-       vp_cursor_t cursor;
+       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
@@ -1793,7 +1856,7 @@ static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt,
         *      This allows users to manipulate virtual attributes as if
         *      they were real ones.
         */
-       vp = tmpl_cursor_init(&err, &cursor, request, vpt);
+       vp = tmpl_cursor_init(NULL, &cursor, request, vpt);
        if (vp) goto do_print;
 
        /*
@@ -1887,15 +1950,15 @@ static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt,
         *      various VP functions.
         */
        case PW_PACKET_AUTHENTICATION_VECTOR:
-               virtual = pairalloc(ctx, vpt->tmpl_da);
-               pairmemcpy(virtual, packet->vector, sizeof(packet->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 = pairalloc(ctx, vpt->tmpl_da);
+                       virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
                        virtual->vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
                        vp = virtual;
                }
@@ -1903,7 +1966,7 @@ static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt,
 
        case PW_PACKET_DST_IP_ADDRESS:
                if (packet->dst_ipaddr.af == AF_INET) {
-                       virtual = pairalloc(ctx, vpt->tmpl_da);
+                       virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
                        virtual->vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
                        vp = virtual;
                }
@@ -1911,7 +1974,7 @@ static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt,
 
        case PW_PACKET_SRC_IPV6_ADDRESS:
                if (packet->src_ipaddr.af == AF_INET6) {
-                       virtual = pairalloc(ctx, vpt->tmpl_da);
+                       virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
                        memcpy(&virtual->vp_ipv6addr,
                               &packet->src_ipaddr.ipaddr.ip6addr,
                               sizeof(packet->src_ipaddr.ipaddr.ip6addr));
@@ -1921,7 +1984,7 @@ static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt,
 
        case PW_PACKET_DST_IPV6_ADDRESS:
                if (packet->dst_ipaddr.af == AF_INET6) {
-                       virtual = pairalloc(ctx, vpt->tmpl_da);
+                       virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
                        memcpy(&virtual->vp_ipv6addr,
                               &packet->dst_ipaddr.ipaddr.ip6addr,
                               sizeof(packet->dst_ipaddr.ipaddr.ip6addr));
@@ -1930,13 +1993,13 @@ static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt,
                break;
 
        case PW_PACKET_SRC_PORT:
-               virtual = pairalloc(ctx, vpt->tmpl_da);
+               virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
                virtual->vp_integer = packet->src_port;
                vp = virtual;
                break;
 
        case PW_PACKET_DST_PORT:
-               virtual = pairalloc(ctx, vpt->tmpl_da);
+               virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
                virtual->vp_integer = packet->dst_port;
                vp = virtual;
                break;
@@ -1984,8 +2047,9 @@ do_print:
        {
                int count = 0;
 
-               fr_cursor_first(&cursor);
-               while (fr_cursor_next_by_da(&cursor, vpt->tmpl_da, vpt->tmpl_tag)) count++;
+               for (vp = tmpl_cursor_init(NULL, &cursor, request, vpt);
+                    vp;
+                    vp = tmpl_cursor_next(&cursor, vpt)) count++;
 
                return talloc_typed_asprintf(ctx, "%d", count);
        }
@@ -2040,7 +2104,7 @@ static const char xlat_spaces[] = "
 #endif
 
 static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * const node,
-                        RADIUS_ESCAPE_STRING escape, void *escape_ctx, int lvl)
+                        xlat_escape_t escape, void *escape_ctx, int lvl)
 {
        ssize_t rcode;
        char *str = NULL, *child;
@@ -2053,7 +2117,7 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
                 *      Don't escape this.
                 */
        case XLAT_LITERAL:
-               XLAT_DEBUG("xlat_aprint LITERAL");
+               XLAT_DEBUG("%.*sxlat_aprint LITERAL", lvl, xlat_spaces);
                return talloc_typed_strdup(ctx, node->fmt);
 
                /*
@@ -2065,15 +2129,18 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
                size_t freespace = 256;
                struct tm ts;
                time_t when;
+               int usec;
 
-               XLAT_DEBUG("xlat_aprint PERCENT");
+               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) {
@@ -2130,12 +2197,15 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
 
                case 'S': /* request timestamp in SQL format*/
                        if (!localtime_r(&when, &ts)) goto error;
-                       strftime(str, freespace, "%Y-%m-%d %H:%M:%S", &ts);
+                       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 'T': /* request timestamp */
                        if (!localtime_r(&when, &ts)) goto error;
-                       strftime(str, freespace, "%Y-%m-%d-%H.%M.%S.000000", &ts);
+                       strftime(str, freespace, "%Y-%m-%d-%H.%M.%S", &ts);
+                       
                        break;
 
                case 'Y': /* request year */
@@ -2161,15 +2231,15 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
                break;
 
        case XLAT_ATTRIBUTE:
-               XLAT_DEBUG("xlat_aprint 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("EXPAND attr %s", node->attr.tmpl_da->name);
-                       XLAT_DEBUG("       ---> %s", str);
+                       XLAT_DEBUG("%.*sEXPAND attr %s", lvl, xlat_spaces, node->attr.tmpl_da->name);
+                       XLAT_DEBUG("%.*s       ---> %s", lvl ,xlat_spaces, str);
                }
                break;
 
@@ -2204,7 +2274,7 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
                /*
                 *      Smash \n --> CR.
                 *
-                *      The OUTPUT of xlat is a printable string.  The INPUT might not be...
+                *      The OUTPUT of xlat is a "raw" string.  The INPUT is a printable string.
                 *
                 *      This is really the reverse of fr_prints().
                 */
@@ -2263,27 +2333,42 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
 
 #ifdef HAVE_REGEX
        case XLAT_REGEX:
-               XLAT_DEBUG("xlat_aprint 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("xlat_aprint ALTERNATE");
+               XLAT_DEBUG("%.*sxlat_aprint ALTERNATE", lvl, xlat_spaces);
                rad_assert(node->child != NULL);
                rad_assert(node->alternate != NULL);
 
-               str = xlat_aprint(ctx, request, node->child, escape, escape_ctx, lvl);
-               if (str) {
-                       XLAT_DEBUG("ALTERNATE got string: %s", str);
-                       break;
-               }
+               /*
+                *      If there are no "next" nodes, call ourselves
+                *      recursively, which is fast.
+                *
+                *      If there are "next" nodes, call xlat_process()
+                *      which does a ton more work.
+                */
+               if (!node->next) {
+                       str = xlat_aprint(ctx, request, node->child, escape, escape_ctx, lvl);
+                       if (str) {
+                               XLAT_DEBUG("%.*sALTERNATE got first string: %s", lvl, xlat_spaces, str);
+                       } else {
+                               str = xlat_aprint(ctx, request, node->alternate, escape, escape_ctx, lvl);
+                               XLAT_DEBUG("%.*sALTERNATE got alternate string %s", lvl, xlat_spaces, str);
+                       }
+               } else {
 
-               XLAT_DEBUG("ALTERNATE going to alternate");
-               str = xlat_aprint(ctx, request, node->alternate, escape, escape_ctx, lvl);
+                       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;
-
        }
 
        /*
@@ -2298,10 +2383,13 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
         *      Escape the non-literals we found above.
         */
        if (str && escape) {
+               size_t len;
                char *escaped;
 
-               escaped = talloc_array(ctx, char, 2048); /* FIXME: do something intelligent */
-               escape(request, escaped, 2038, str, escape_ctx);
+               len = talloc_array_length(str) * 3;
+
+               escaped = talloc_array(ctx, char, len);
+               escape(request, escaped, len, str, escape_ctx);
                talloc_free(str);
                str = escaped;
        }
@@ -2311,7 +2399,7 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
 
 
 static size_t xlat_process(char **out, REQUEST *request, xlat_exp_t const * const head,
-                          RADIUS_ESCAPE_STRING escape, void *escape_ctx)
+                          xlat_escape_t escape, void *escape_ctx)
 {
        int i, list;
        size_t total;
@@ -2394,7 +2482,7 @@ static size_t xlat_process(char **out, REQUEST *request, xlat_exp_t const * cons
 
 /** Replace %whatever in a string.
  *
- * See 'doc/variables.txt' for more information.
+ * See 'doc/configuration/variables.rst' for more information.
  *
  * @param[out] out Where to write pointer to output buffer.
  * @param[in] outlen Size of out.
@@ -2405,7 +2493,7 @@ static size_t xlat_process(char **out, REQUEST *request, xlat_exp_t const * cons
  * @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,
-                                 RADIUS_ESCAPE_STRING escape, void *escape_ctx)
+                                 xlat_escape_t escape, void *escape_ctx)
 {
        char *buff;
        ssize_t len;
@@ -2420,6 +2508,7 @@ static ssize_t xlat_expand_struct(char **out, size_t outlen, REQUEST *request, x
        }
 
        len = strlen(buff);
+
        /*
         *      If out doesn't point to an existing buffer
         *      copy the pointer to our buffer over.
@@ -2438,11 +2527,11 @@ static ssize_t xlat_expand_struct(char **out, size_t outlen, REQUEST *request, x
 }
 
 static ssize_t xlat_expand(char **out, size_t outlen, REQUEST *request, char const *fmt,
-                          RADIUS_ESCAPE_STRING escape, void *escape_ctx) CC_HINT(nonnull (1, 3, 4));
+                          xlat_escape_t escape, void *escape_ctx) CC_HINT(nonnull (1, 3, 4));
 
 /** Replace %whatever in a string.
  *
- * See 'doc/variables.txt' for more information.
+ * See 'doc/configuration/variables.rst' for more information.
  *
  * @param[out] out Where to write pointer to output buffer.
  * @param[in] outlen Size of out.
@@ -2453,7 +2542,7 @@ static ssize_t xlat_expand(char **out, size_t outlen, REQUEST *request, char con
  * @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,
-                          RADIUS_ESCAPE_STRING escape, void *escape_ctx)
+                          xlat_escape_t escape, void *escape_ctx)
 {
        ssize_t len;
        xlat_exp_t *node;
@@ -2495,7 +2584,7 @@ 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)) return NULL;
+       if (node->next || (node->type != XLAT_ATTRIBUTE) || (node->attr.type != TMPL_TYPE_ATTR)) return NULL;
 
        /*
         *   Concat means something completely different as an attribute reference
@@ -2525,6 +2614,7 @@ xlat_exp_t *xlat_from_tmpl_attr(TALLOC_CTX *ctx, vp_tmpl_t *vpt)
        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));
@@ -2532,22 +2622,24 @@ xlat_exp_t *xlat_from_tmpl_attr(TALLOC_CTX *ctx, vp_tmpl_t *vpt)
        return node;
 }
 
-ssize_t radius_xlat(char *out, size_t outlen, REQUEST *request, char const *fmt, RADIUS_ESCAPE_STRING escape, void *ctx)
+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, RADIUS_ESCAPE_STRING escape, void *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, RADIUS_ESCAPE_STRING escape, void *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, RADIUS_ESCAPE_STRING escape, void *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);
 }