add usec resolution to %S. Fixes #1917
[freeradius.git] / src / main / xlat.c
index be32960..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;
 
@@ -64,7 +64,7 @@ struct xlat_exp {
        xlat_exp_t *child;      //!< Nested expansion.
        xlat_exp_t *alternate;  //!< Alternative expansion if this one expanded to a zero length string.
 
-       value_pair_tmpl_t attr; //!< An attribute template.
+       vp_tmpl_t attr; //!< An attribute template.
        xlat_t const *xlat;     //!< The xlat expansion to expand format with.
 };
 
@@ -89,12 +89,8 @@ static char const * const xlat_foreach_names[] = {"Foreach-Variable-0",
                                                  NULL};
 #endif
 
-#if REQUEST_MAX_REGEX > 8
-#  error Please fix the following line
-#endif
-static int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };        /* up to 8 for regex */
 
-char const *radiusd_short_version = RADIUSD_VERSION_STRING;
+static int xlat_inst[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };     /* up to 10 for foreach */
 
 /** Print length of its RHS.
  *
@@ -109,7 +105,7 @@ static ssize_t xlat_strlen(UNUSED void *instance, UNUSED REQUEST *request,
 /** Print the size of the attribute in bytes.
  *
  */
-static ssize_t xlat_length(UNUSED void *instance, UNUSED REQUEST *request,
+static ssize_t xlat_length(UNUSED void *instance, REQUEST *request,
                           char const *fmt, char *out, size_t outlen)
 {
        VALUE_PAIR *vp;
@@ -120,7 +116,7 @@ static ssize_t xlat_length(UNUSED void *instance, UNUSED REQUEST *request,
                return 0;
        }
 
-       snprintf(out, outlen, "%zu", vp->length);
+       snprintf(out, outlen, "%zu", vp->vp_length);
        return strlen(out);
 }
 
@@ -145,16 +141,16 @@ static ssize_t xlat_integer(UNUSED void *instance, REQUEST *request,
        switch (vp->da->type) {
        case PW_TYPE_OCTETS:
        case PW_TYPE_STRING:
-               if (vp->length > 8) {
+               if (vp->vp_length > 8) {
                        break;
                }
 
-               if (vp->length > 4) {
-                       memcpy(&int64, vp->vp_octets, vp->length);
+               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->length);
+               memcpy(&int32, vp->vp_octets, vp->vp_length);
                return snprintf(out, outlen, "%i", htonl(int32));
 
        case PW_TYPE_INTEGER64:
@@ -171,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);
 
@@ -183,7 +183,7 @@ static ssize_t xlat_integer(UNUSED void *instance, REQUEST *request,
         *      bigendian.
         */
        case PW_TYPE_ETHERNET:
-               memcpy(&int64, &vp->vp_ether, vp->length);
+               memcpy(&int64, vp->vp_ether, vp->vp_length);
                return snprintf(out, outlen, "%" PRIu64, htonll(int64));
 
        case PW_TYPE_SIGNED:
@@ -193,14 +193,14 @@ static ssize_t xlat_integer(UNUSED void *instance, REQUEST *request,
                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])));
+               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->length);
+               fr_int2str(dict_attr_types, vp->da->type, "???"), vp->vp_length);
        *out = '\0';
 
        return -1;
@@ -217,31 +217,52 @@ static ssize_t xlat_hex(UNUSED void *instance, REQUEST *request,
        uint8_t const *p;
        ssize_t ret;
        size_t  len;
+       value_data_t dst;
+       uint8_t const *buff = NULL;
 
        while (isspace((int) *fmt)) fmt++;
 
        if ((radius_get_vp(&vp, request, fmt) < 0) || !vp) {
+       error:
                *out = '\0';
                return -1;
        }
 
-       ret = rad_vp2data(&p, vp);
-       if (ret < 0) {
-               return ret;
+       /*
+        *      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;
        }
-       len = (size_t) ret;
+
+       rad_assert(p);
 
        /*
         *      Don't truncate the data.
         */
-       if ((ret < 0 ) || (outlen < (len * 2))) {
-               *out = 0;
-               return 0;
+       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;
 }
@@ -269,6 +290,87 @@ static ssize_t xlat_tag(UNUSED void *instance, REQUEST *request,
        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((int) *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((int) *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((int) *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((int) *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.
@@ -277,20 +379,15 @@ static ssize_t xlat_tag(UNUSED void *instance, REQUEST *request,
  * value. This is helpful to determine types for unknown attributes of long
  * passed vendors, or just crazy/broken NAS.
  *
- * It's also useful for exposing issues in the packet decoding functions, as in
- * some cases they get fed random garbage data.
- *
  * 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, **vps;
-       REQUEST *current;
+       VALUE_PAIR *vp;
        vp_cursor_t cursor;
-       char buffer[1024];
 
-       value_pair_tmpl_t vpt;
+       vp_tmpl_t vpt;
 
        if (!RDEBUG_ENABLED2) {
                *out = '\0';
@@ -299,72 +396,98 @@ static ssize_t xlat_debug_attr(UNUSED void *instance, REQUEST *request, char con
 
        while (isspace((int) *fmt)) fmt++;
 
-       if (tmpl_from_attr_str(&vpt, fmt, REQUEST_CURRENT, PAIR_LIST_REQUEST) <= 0) {
+       if (tmpl_from_attr_str(&vpt, fmt, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false) <= 0) {
                RDEBUG("%s", fr_strerror());
                return -1;
        }
 
-       current = request;
-       if (radius_request(&current, vpt.tmpl_request) < 0) return -2;
-
-       vps = radius_list(current, vpt.tmpl_list);
-       if (!vps) {
-               return -2;
-       }
-
        RIDEBUG("Attributes matching \"%s\"", fmt);
-       vp = fr_cursor_init(&cursor, vps);
 
-       if (vpt.tmpl_da) {
-               vp = fr_cursor_next_by_da(&cursor, vpt.tmpl_da, TAG_ANY);
-       }
-       while (vp) {
-               DICT_ATTR *dac = NULL;
-               DICT_VENDOR *dv;
-
-               vp_prints_value(buffer, sizeof(buffer), vp, '\'');
+       RINDENT();
+       for (vp = tmpl_cursor_init(NULL, &cursor, request, &vpt);
+            vp;
+            vp = tmpl_cursor_next(&cursor, &vpt)) {
+               FR_NAME_NUMBER const *type;
+               char *value;
 
-               RINDENT();
+               value = vp_aprints_value(vp, vp, '\'');
                if (vp->da->flags.has_tag) {
-                       RIDEBUG2("%s:%s:%i %s %s",
+                       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>"),
-                               buffer);
+                               value);
                } else {
-                       RIDEBUG2("%s:%s %s %s",
+                       RIDEBUG2("&%s:%s %s %s",
                                fr_int2str(pair_lists, vpt.tmpl_list, "<INVALID>"),
                                vp->da->name,
                                fr_int2str(fr_tokens, vp->op, "<INVALID>"),
-                               buffer);
+                               value);
                }
-               REXDENT();
+               talloc_free(value);
 
-               if (!RDEBUG_ENABLED3) goto next_vp;
+               if (!RDEBUG_ENABLED3) continue;
 
-               RINDENT();
-               RINDENT();
                if (vp->da->vendor) {
+                       DICT_VENDOR *dv;
+
                        dv = dict_vendorbyvalue(vp->da->vendor);
-                       RDEBUG3("Vendor : %i (%s)", vp->da->vendor, dv ? dv->name : "unknown");
+                       RIDEBUG2("Vendor : %i (%s)", vp->da->vendor, dv ? dv->name : "unknown");
                }
-               RDEBUG3("Type   : %s", fr_int2str(dict_attr_types, vp->da->type, "<INVALID>"));
-               RDEBUG3("Length : %zu", vp->length);
-               REXDENT();
-               REXDENT();
+               RIDEBUG2("Type   : %s", fr_int2str(dict_attr_types, vp->da->type, "<INVALID>"));
+               RIDEBUG2("Length : %zu", vp->vp_length);
 
-               dac = talloc_memdup(request, vp->da, sizeof(DICT_ATTR));
-               if (!dac) return -1;
-               talloc_set_type(dac, DICT_ATTR);
+               if (!RDEBUG_ENABLED4) continue;
 
-       next_vp:
-               talloc_free(dac);
+               type = dict_attr_types;
+               while (type->name) {
+                       int pad;
 
-               if (vpt.tmpl_da) {
-                       vp = fr_cursor_next_by_da(&cursor, vpt.tmpl_da, TAG_ANY);
-               } else {
-                       vp = fr_cursor_next(&cursor);
+                       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();
+
+               next_type:
+                       talloc_free(dst);
+                       type++;
                }
        }
 
@@ -372,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
  *
  */
@@ -383,6 +535,30 @@ static ssize_t xlat_module(UNUSED void *instance, REQUEST *request,
        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
  *
@@ -397,7 +573,7 @@ static ssize_t xlat_foreach(void *instance, REQUEST *request,
        /*
         *      See modcall, "FOREACH" for how this works.
         */
-       pvp = (VALUE_PAIR **) request_data_reference(request, radius_get_vp, *(int*) instance);
+       pvp = (VALUE_PAIR **) request_data_reference(request, (void *)radius_get_vp, *(int*) instance);
        if (!pvp || !*pvp) {
                *out = '\0';
                return 0;
@@ -444,15 +620,19 @@ static ssize_t xlat_string(UNUSED void *instance, REQUEST *request,
 
        switch (vp->da->type) {
        case PW_TYPE_OCTETS:
-               len = fr_print_string((char const *) p, vp->length, out, outlen, '\0');
+               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_print_string((char const *) p, ret, out, outlen, '\0');
+               len = fr_prints(out, outlen, (char const *) p, ret, '\0');
                break;
        }
 
@@ -477,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);
 }
 
@@ -492,7 +674,7 @@ static ssize_t xlat_debug(UNUSED void *instance, REQUEST *request,
        /*
         *  Expand to previous (or current) level
         */
-       snprintf(out, outlen, "%d", request->log.lvl & RAD_REQUEST_OPTION_DEBUG4);
+       snprintf(out, outlen, "%d", request->log.lvl);
 
        /*
         *  Assume we just want to get the current value and NOT set it to 0
@@ -502,7 +684,7 @@ static ssize_t xlat_debug(UNUSED void *instance, REQUEST *request,
 
        level = atoi(fmt);
        if (level == 0) {
-               request->log.lvl = RAD_REQUEST_OPTION_NONE;
+               request->log.lvl = RAD_REQUEST_LVL_NONE;
                request->log.func = NULL;
        } else {
                if (level > 4) level = 4;
@@ -553,10 +735,10 @@ 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;
+       xlat_t  *c;
+       xlat_t  my_xlat;
        rbnode_t *node;
 
        if (!name || !*name) {
@@ -575,7 +757,7 @@ int xlat_register(char const *name, RAD_XLAT_FUNC func, RADIUS_ESCAPE_STRING esc
                int i;
 #endif
 
-               xlat_root = rbtree_create(NULL, xlat_cmp, free, 0);
+               xlat_root = rbtree_create(NULL, xlat_cmp, NULL, RBTREE_FLAG_REPLACE);
                if (!xlat_root) {
                        DEBUG("xlat_register: Failed to create tree");
                        return -1;
@@ -601,10 +783,18 @@ int xlat_register(char const *name, RAD_XLAT_FUNC func, RADIUS_ESCAPE_STRING esc
                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");
@@ -633,8 +823,7 @@ int xlat_register(char const *name, RAD_XLAT_FUNC func, RADIUS_ESCAPE_STRING esc
        /*
         *      Doesn't exist.  Create it.
         */
-       c = rad_malloc(sizeof(*c));
-       memset(c, 0, sizeof(*c));
+       c = talloc_zero(xlat_root, xlat_t);
 
        c->func = func;
        c->escape = escape;
@@ -648,6 +837,15 @@ int xlat_register(char const *name, RAD_XLAT_FUNC func, RADIUS_ESCAPE_STRING esc
                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;
 }
 
@@ -660,12 +858,12 @@ 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;
 
-       if (!name) return;
+       if (!name || !xlat_root) return;
 
        strlcpy(my_xlat.name, name, sizeof(my_xlat.name));
        my_xlat.length = strlen(my_xlat.name);
@@ -692,6 +890,200 @@ 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
  *
@@ -736,7 +1128,6 @@ void xlat_free(void)
        rbtree_free(xlat_root);
 }
 
-
 #ifdef DEBUG_XLAT
 #  define XLAT_DEBUG DEBUG3
 #else
@@ -746,9 +1137,9 @@ void xlat_free(void)
 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,
-                                    int brace, char const **error);
+                                    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)
@@ -824,9 +1215,9 @@ static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **
                                       char const **error)
 {
        ssize_t slen;
-       char *p, *q, *brace;
-       char const *attrname;
+       char *p, *q;
        xlat_exp_t *node;
+       long num;
 
        rad_assert(fmt[0] == '%');
        rad_assert(fmt[1] == '{');
@@ -834,33 +1225,34 @@ static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **
        /*
         *      %{%{...}:-bar}
         */
-       if ((fmt[2] == '%') && (fmt[3] == '{')) {
-               return xlat_tokenize_alternation(ctx, fmt, head, error);
-       }
+       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);
-       attrname = node->fmt = fmt + 2;
+       node->fmt = fmt + 2;
        node->len = 0;
 
 #ifdef HAVE_REGEX
        /*
         *      Handle regex's specially.
         */
-       if (isdigit((int) fmt[2]) && (fmt[3] == '}')) {
-               if (fmt[2] == '9') {
+       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";
+                       *error = "Invalid regex reference.  Must be in range 0-" STRINGIFY(REQUEST_MAX_REGEX);
                        return -2;
                }
-
-               XLAT_DEBUG("REGEX <-- %s", fmt);
-               fmt[3] = '\0';
-               node->attr.tmpl_num = fmt[2] - '0'; /* ASCII */
+               node->attr.tmpl_num = num;
 
                node->type = XLAT_REGEX;
                *head = node;
-               return 4;
+
+               return (q - fmt) + 1;
        }
 #endif /* HAVE_REGEX */
 
@@ -874,250 +1266,131 @@ static ssize_t xlat_tokenize_expansion(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **
         *      %{request:Tunnel-Password:1[#]}
         *      %{mod:foo}
         */
-        brace = NULL;
-       for (p = fmt + 2; *p != '\0'; p++) {
-               if (*p == ':') break;
 
-               if (isspace((int) *p)) break;
+       /*
+        *      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 (*p == '[') break;
+               if (isspace((int) *q)) break;
 
-               if (*p == '}') break;
+               if (*q == '[') continue;
+
+               if (*q == '}') break;
        }
 
-       if (*p != ':') p = NULL;
+       /*
+        *      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 (p) {
-               *p = '\0';
-
-               /*
-                *      %{mod:foo}
-                */
+       if (*q == ':') {
+               *q = '\0';
                node->xlat = xlat_find(node->fmt);
                if (node->xlat) {
+                       /*
+                        *      %{mod:foo}
+                        */
                        node->type = XLAT_MODULE;
 
-                       XLAT_DEBUG("MOD <-- %s ... %s", node->fmt, p + 1);
+                       p = q + 1;
+                       XLAT_DEBUG("MOD <-- %s ... %s", node->fmt, p);
 
-                       slen = xlat_tokenize_literal(node, p + 1, &node->child, true, error);
-                       if (slen <= 0) {
+                       slen = xlat_tokenize_literal(node, p, &node->child, true, error);
+                       if (slen < 0) {
                                talloc_free(node);
                                return slen - (p - fmt);
                        }
-                       p += slen + 1;
+                       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) {
                /*
-                *      Modules can have '}' in their RHS, so we
-                *      didn't check for that until now.
-                *
-                *      As of now, node->fmt MUST be a reference to an
-                *      attribute, however complicated.  So it MUST have a closing brace.
-                */
-               brace = strchr(p + 1, '}');
-               if (!brace) goto no_brace;
-               *brace = '\0';
-
-               /*
-                *      %{User-Name}
-                *      %{User-Name[1]}
-                *      %{Tunnel-Password:1}
-                *      %{request:Tunnel-Password:1}
-                *
-                *      <sigh>  The syntax is fairly poor.
-                */
-               XLAT_DEBUG("Looking for list in '%s'", attrname);
-
-               /*
-                *      Not a module.  Has to be an attribute
-                *      reference.
-                *
-                *      As of v3, we've removed %{request: ..>} as
-                *      internally registered xlats.
+                *      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.
                 */
-               *p = ':';
-               node->attr.tmpl_request = radius_request_name(&attrname, REQUEST_CURRENT);
-               rad_assert(node->attr.tmpl_request != REQUEST_UNKNOWN);
-
-               node->attr.tmpl_list = radius_list_name(&attrname, PAIR_LIST_REQUEST);
-               if (node->attr.tmpl_list == PAIR_LIST_UNKNOWN) {
-                       talloc_free(node);
+               if ((*q == ':') && ((p + (slen * -1)) < q)) {
                        *error = "Unknown module";
-                       return -2;
-               }
-
-               /*
-                *      Check for a trailing tag.
-                */
-               p = strchr(attrname, ':');
-               if (p) *p = '\0';
-
-       } else {
-               brace = strchr(attrname, '}');
-               if (!brace) {
-               no_brace:
-                       talloc_free(node);
-                       *error = "No matching closing brace";
-                       return -1;      /* second character of format string */
+               } else {
+                       *error = fr_strerror();
                }
-               *brace = '\0';
-
-               node->attr.tmpl_request = REQUEST_CURRENT;
-               node->attr.tmpl_list = PAIR_LIST_REQUEST;
-       }
-
-       *brace = '\0';
-
-       XLAT_DEBUG("Looking for attribute name in %s", attrname);
 
-       /*
-        *      Allow for an array reference.  They come AFTER the
-        *      tag, if the tag exists.  Otherwise, they come after
-        *      the attribute name.
-        */
-       if (p) {
-               q = strchr(p + 1, '[');
-       } else {
-               q = strchr(attrname, '[');
-       }
-       if (q) *(q++) = '\0';
-
-       if (!*attrname) {
                talloc_free(node);
-               *error = "Empty expression is invalid";
-               return -(attrname - fmt);
+               return slen - (p - fmt);
        }
 
        /*
-        *      It's either an attribute name, or a Tunnel-Password:TAG
-        *      with the ':' already set to NULL.
+        *      Might be a virtual XLAT attribute
         */
-       node->attr.tmpl_da = dict_attrbyname(attrname);
-       if (!node->attr.tmpl_da) {
-               /*
-                *      Foreach.  Maybe other stuff, too.
-                */
-               node->xlat = xlat_find(attrname);
+       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 = attrname;
+                       node->fmt = node->attr.tmpl_unknown_name;
 
                        XLAT_DEBUG("VIRTUAL <-- %s", node->fmt);
                        *head = node;
                        rad_assert(node->next == NULL);
-                       brace++;
-                       return brace - fmt;
+                       q++;
+                       return q - fmt;
                }
 
                talloc_free(node);
                *error = "Unknown attribute";
-               return -(attrname - fmt);
-       }
-
-       /*
-        *      Parse the tag.
-        */
-       if (p) {
-               unsigned long tag;
-               char *end;
-
-               if (!node->attr.tmpl_da->flags.has_tag) {
-                       talloc_free(node);
-                       *error = "Attribute cannot have a tag";
-                       return - (p - fmt);
-               }
-
-               tag = strtoul(p + 1, &end, 10);
-               p++;
-
-               if (tag == ULONG_MAX) {
-                       talloc_free(node);
-                       *error = "Invalid tag value";
-                       return - (p - fmt);
-               }
-
-               node->attr.tmpl_tag = tag;
-               p = end;
-
-               if (*p) {
-                       talloc_free(node);
-                       *error = "Unexpected text after tag";
-                       return - (p - fmt);
-               }
-
-       } else {
-               node->attr.tmpl_tag = TAG_ANY;
-               /* leave p alone */
-       }
-
-       /*
-        *      Check for array reference
-        */
-       if (q) {
-               unsigned long num;
-               char *end;
-
-               p = q;
-               if (*p== '#') {
-                       node->attr.tmpl_num = NUM_COUNT;
-                       p++;
-
-               } else if (*p == '*') {
-                       node->attr.tmpl_num = NUM_ALL;
-                       p++;
-
-               } else if (isdigit((int) *p)) {
-                       num = strtoul(p, &end, 10);
-                       if (num > 1000) {
-                               talloc_free(node);
-                               *error = "Invalid array index";
-                               return - (p - fmt);
-                       }
-                       p = end;
-                       node->attr.tmpl_num = num;
-
-               } else {
-                       talloc_free(node);
-                       *error = "Invalid array index";
-                       return - (p - fmt);
-               }
-
-               if (*p != ']') {
-                       talloc_free(node);
-                       *error = "Expected ']'";
-                       return - (p - fmt);
-               }
-
-               p++;
-               if (*p) {
-                       talloc_free(node);
-                       *error = "Unexpected text after array reference";
-                       return - (p - fmt);
-               }
-       } else {
-               node->attr.tmpl_num = NUM_ANY;
+               return -(p - fmt);
        }
 
-       rad_assert(!p || (p == brace));
-
        node->type = XLAT_ATTRIBUTE;
-       p = brace + 1;
+       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,
-                                    int brace, char const **error)
+                                    bool brace, char const **error)
 {
        char *p;
        xlat_exp_t *node;
@@ -1140,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;
                }
 
@@ -1150,7 +1425,7 @@ static ssize_t xlat_tokenize_literal(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **he
                if ((p[0] == '%') && (p[1] == '{')) {
                        ssize_t slen;
 
-                       XLAT_DEBUG("LITERAL <-- %s", node->fmt);
+                       XLAT_DEBUG("EXPANSION-2 <-- %s", node->fmt);
 
                        slen = xlat_tokenize_expansion(node, p, &node->next, error);
                        if (slen <= 0) {
@@ -1182,6 +1457,7 @@ static ssize_t xlat_tokenize_literal(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **he
                                return slen - (p - fmt);
                        }
 
+                       brace = false; /* it was found above, or else the above code errored out */
                        p += slen;
                        break;  /* stop processing the string */
                }
@@ -1193,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("%dlmtDGHISTYv", 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 <-- %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;
@@ -1232,6 +1512,7 @@ static ssize_t xlat_tokenize_literal(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **he
                                return slen - (p - fmt);
                        }
 
+                       brace = false; /* it was found above, or else the above code errored out */
                        p += slen;
                        break;  /* stop processing the string */
                }
@@ -1240,6 +1521,7 @@ static ssize_t xlat_tokenize_literal(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **he
                 *      If required, eat the brace.
                 */
                if (brace && (*p == '}')) {
+                       brace = false;
                        *p = '\0';
                        p++;
                        break;
@@ -1250,12 +1532,23 @@ static ssize_t xlat_tokenize_literal(TALLOC_CTX *ctx, char *fmt, xlat_exp_t **he
        }
 
        /*
+        *      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 {
-               *head = talloc_steal(ctx, node->next);
+               (void) talloc_steal(ctx, node->next);
+               *head = node->next;
                talloc_free(node);
        }
 
@@ -1330,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;
@@ -1484,7 +1777,7 @@ static ssize_t xlat_tokenize_request(REQUEST *request, char const *fmt, xlat_exp
 {
        ssize_t slen;
        char *tokens;
-       char const *error;
+       char const *error = NULL;
 
        *head = NULL;
 
@@ -1519,7 +1812,7 @@ static ssize_t xlat_tokenize_request(REQUEST *request, char const *fmt, xlat_exp
                return slen;
        }
 
-       if (*head && (debug_flag > 2)) {
+       if (*head && (rad_debug_lvl > 2)) {
                DEBUG("%s", fmt);
                DEBUG("Parsed xlat tree:");
                xlat_tokenize_debug(*head, 0);
@@ -1536,112 +1829,79 @@ static ssize_t xlat_tokenize_request(REQUEST *request, char const *fmt, xlat_exp
 }
 
 
-static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, pair_lists_t list, DICT_ATTR const *da,
-                       int8_t tag, int num, bool return_null)
+static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, vp_tmpl_t const *vpt,
+                       bool escape, bool return_null)
 {
-       VALUE_PAIR *vp, *vps = NULL, *myvp = NULL;
+       VALUE_PAIR *vp = NULL, *virtual = NULL;
        RADIUS_PACKET *packet = NULL;
        DICT_VALUE *dv;
        char *ret = NULL;
 
-       /*
-        *      Arg.  Too much abstraction is annoying.
-        */
-       switch (list) {
-       default:
-               if (return_null) return NULL;
-               return vp_aprint_type(ctx, da->type);
-
-       case PAIR_LIST_CONTROL:
-               vps = request->config_items;
-               break;
-
-       case PAIR_LIST_REQUEST:
-               packet = request->packet;
-               if (packet) vps = packet->vps;
-               break;
-
-       case PAIR_LIST_REPLY:
-               packet = request->reply;
-               if (packet) vps = packet->vps;
-               break;
-
-#ifdef WITH_PROXY
-       case PAIR_LIST_PROXY_REQUEST:
-               packet = request->proxy;
-               if (packet) vps = packet->vps;
-               break;
-
-       case PAIR_LIST_PROXY_REPLY:
-               packet = request->proxy_reply;
-               if (packet) vps = packet->vps;
-               break;
-#endif
+       vp_cursor_t cursor;
+       char quote = escape ? '"' : '\0';
 
-#ifdef WITH_COA
-       case PAIR_LIST_COA:
-       case PAIR_LIST_DM:
-               if (request->coa) packet = request->coa->packet;
-               if (packet) vps = packet->vps;
-               break;
+       rad_assert((vpt->type == TMPL_TYPE_ATTR) || (vpt->type == TMPL_TYPE_LIST));
 
-       case PAIR_LIST_COA_REPLY:
-       case PAIR_LIST_DM_REPLY:
-               if (request->coa) packet = request->coa->reply;
-               if (packet) vps = packet->vps;
-               break;
-
-#endif
+       /*
+        *      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;
        }
 
        /*
-        *      Now we have the list, check to see if we have an attribute in
-        *      the request, if we do, it takes precedence over the virtual
-        *      attributes.
+        *      See if we're dealing with an attribute in the request
         *
-        *      This allows users to manipulate virtual attributes as if they
-        *      were real ones.
+        *      This allows users to manipulate virtual attributes as if
+        *      they were real ones.
         */
-       vp = pairfind(vps, da->attr, da->vendor, tag);
+       vp = tmpl_cursor_init(NULL, &cursor, request, vpt);
        if (vp) goto do_print;
 
        /*
-        *      We didn't find the VP in a list.  It MIGHT be a
-        *      virtual one, in which case we do lots more checks
-        *      below.  However, if we're looking for a normal
-        *      attribute, it must exist, and therefore not finding it
-        *      means we return NULL.
+        *      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 (!da->flags.virtual) return NULL;
+       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 (da->attr) {
+       switch (vpt->tmpl_da->attr) {
        default:
                break;          /* ignore them */
 
        case PW_CLIENT_SHORTNAME:
-               if (num == NUM_COUNT) goto count;
+               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 (num == NUM_COUNT) goto count;
+               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 (num == NUM_COUNT) goto count;
+               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 (num == NUM_COUNT) goto count;
+               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, ""));
        }
@@ -1651,13 +1911,14 @@ static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, pair_lists_t list, DI
         *      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_aprint_type(ctx, da->type);
+               return vp_aprints_type(ctx, vpt->tmpl_da->type);
        }
 
        vp = NULL;
-       switch (da->attr) {
+       switch (vpt->tmpl_da->attr) {
        default:
                break;
 
@@ -1689,66 +1950,66 @@ static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, pair_lists_t list, DI
         *      various VP functions.
         */
        case PW_PACKET_AUTHENTICATION_VECTOR:
-               myvp = pairalloc(ctx, da);
-               pairmemcpy(myvp, packet->vector, sizeof(packet->vector));
-               vp = myvp;
+               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) {
-                       myvp = pairalloc(ctx, da);
-                       myvp->vp_ipaddr = packet->src_ipaddr.ipaddr.ip4addr.s_addr;
-                       vp = myvp;
+                       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) {
-                       myvp = pairalloc(ctx, da);
-                       myvp->vp_ipaddr = packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
-                       vp = myvp;
+                       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) {
-                       myvp = pairalloc(ctx, da);
-                       memcpy(&myvp->vp_ipv6addr,
+                       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 = myvp;
+                       vp = virtual;
                }
                break;
 
        case PW_PACKET_DST_IPV6_ADDRESS:
                if (packet->dst_ipaddr.af == AF_INET6) {
-                       myvp = pairalloc(ctx, da);
-                       memcpy(&myvp->vp_ipv6addr,
+                       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 = myvp;
+                       vp = virtual;
                }
                break;
 
        case PW_PACKET_SRC_PORT:
-               myvp = pairalloc(ctx, da);
-               myvp->vp_integer = packet->src_port;
-               vp = myvp;
+               virtual = fr_pair_afrom_da(ctx, vpt->tmpl_da);
+               virtual->vp_integer = packet->src_port;
+               vp = virtual;
                break;
 
        case PW_PACKET_DST_PORT:
-               myvp = pairalloc(ctx, da);
-               myvp->vp_integer = packet->dst_port;
-               vp = myvp;
+               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 (myvp) {
-               if (num != NUM_ANY) switch (num) {
+       if (virtual) {
+               if (vpt->tmpl_num != NUM_ANY) switch (vpt->tmpl_num) {
                /*
                 *      [n] is NULL (we only have [0])
                 */
@@ -1764,7 +2025,7 @@ static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, pair_lists_t list, DI
                 *      [#] means 1 (as there's only one)
                 */
                case NUM_COUNT:
-               count:
+               count_virtual:
                        ret = talloc_strdup(ctx, "1");
                        goto finish;
 
@@ -1778,66 +2039,63 @@ static char *xlat_getvp(TALLOC_CTX *ctx, REQUEST *request, pair_lists_t list, DI
        }
 
 do_print:
+       switch (vpt->tmpl_num) {
        /*
-        *      We want the N'th VP.
+        *      Return a count of the VPs.
         */
-       if (num != NUM_ANY) {
+       case NUM_COUNT:
+       {
                int count = 0;
-               vp_cursor_t cursor;
 
-               switch (num) {
-               /*
-                *      Return a count of the VPs.
-                */
-               case NUM_COUNT:
-                       fr_cursor_init(&cursor, &vp);
-                       while (fr_cursor_next_by_da(&cursor, da, tag) != NULL) {
-                               count++;
-                       }
-                       return talloc_typed_asprintf(ctx, "%d", count);
+               for (vp = tmpl_cursor_init(NULL, &cursor, request, vpt);
+                    vp;
+                    vp = tmpl_cursor_next(&cursor, vpt)) count++;
 
-               /*
-                *      Ugly, but working.
-                */
-               case NUM_ALL:
-               {
-                       char *p, *q;
-
-                       (void) fr_cursor_init(&cursor, &vp);
-                       vp = fr_cursor_next_by_da(&cursor, da, tag);
-                       if (!vp) return NULL;
-
-                       p = vp_aprint_value(ctx, vp, '"');
-                       if (!p) return NULL;
-                       while ((vp = fr_cursor_next_by_da(&cursor, da, tag)) != NULL) {
-                               q = vp_aprint_value(ctx, vp, '"');
-                               if (!q) return NULL;
-                               p = talloc_strdup_append(p, ",");
-                               p = talloc_strdup_append(p, q);
-                       }
+               return talloc_typed_asprintf(ctx, "%d", count);
+       }
 
-                       return p;
-               }
 
-               default:
-                       fr_cursor_init(&cursor, &vp);
-                       while ((vp = fr_cursor_next_by_da(&cursor, da, tag)) != NULL) {
-                               if (count++ == num) break;
-                       }
-                       break;
+       /*
+        *      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, ",");
+                       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_aprint_type(ctx, da->type);
+               return vp_aprints_type(ctx, vpt->tmpl_da->type);
        }
 
 print:
-       ret = vp_aprint_value(ctx, vp, '"');
+       ret = vp_aprints_value(ctx, vp, quote);
 
 finish:
-       talloc_free(myvp);
+       talloc_free(virtual);
        return ret;
 }
 
@@ -1846,22 +2104,20 @@ 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;
-       char *q;
        char const *p;
-       REQUEST *ref;
 
-       XLAT_DEBUG("%.*sxlat aprint %d", lvl, xlat_spaces, node->type);
+       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("xlat_aprint LITERAL");
+               XLAT_DEBUG("%.*sxlat_aprint LITERAL", lvl, xlat_spaces);
                return talloc_typed_strdup(ctx, node->fmt);
 
                /*
@@ -1873,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) {
@@ -1891,7 +2150,7 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
                        break;
 
                case 'd': /* request day */
-                       (void) localtime_r(&when, &ts);
+                       if (!localtime_r(&when, &ts)) goto error;
                        strftime(str, freespace, "%d", &ts);
                        break;
 
@@ -1901,7 +2160,7 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
                        break;
 
                case 'm': /* request month */
-                       (void) localtime_r(&when, &ts);
+                       if (!localtime_r(&when, &ts)) goto error;
                        strftime(str, freespace, "%m", &ts);
                        break;
 
@@ -1916,17 +2175,17 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
                        break;
 
                case 'D': /* request date */
-                       (void) localtime_r(&when, &ts);
+                       if (!localtime_r(&when, &ts)) goto error;
                        strftime(str, freespace, "%Y%m%d", &ts);
                        break;
 
                case 'G': /* request minute */
-                       (void) localtime_r(&when, &ts);
+                       if (!localtime_r(&when, &ts)) goto error;
                        strftime(str, freespace, "%M", &ts);
                        break;
 
                case 'H': /* request hour */
-                       (void) localtime_r(&when, &ts);
+                       if (!localtime_r(&when, &ts)) goto error;
                        strftime(str, freespace, "%H", &ts);
                        break;
 
@@ -1937,22 +2196,31 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
                        break;
 
                case 'S': /* request timestamp in SQL format*/
-                       (void) localtime_r(&when, &ts);
-                       strftime(str, freespace, "%Y-%m-%d %H:%M:%S", &ts);
+                       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 'T': /* request timestamp */
-                       (void) localtime_r(&when, &ts);
-                       strftime(str, freespace, "%Y-%m-%d-%H.%M.%S.000000", &ts);
+                       if (!localtime_r(&when, &ts)) goto error;
+                       strftime(str, freespace, "%Y-%m-%d-%H.%M.%S", &ts);
+                       
                        break;
 
                case 'Y': /* request year */
-                       (void) localtime_r(&when, &ts);
+                       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 */
-                       snprintf(str, freespace, "%s", radiusd_short_version);
+                       RWDEBUG("%%v is deprecated and will be removed.  Use ${version.freeradius-server}");
+                       snprintf(str, freespace, "%s", radiusd_version_short);
                        break;
 
                default:
@@ -1963,19 +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");
-               ref = request;
-               if (radius_request(&ref, node->attr.tmpl_request) < 0) {
-                       return NULL;
-               }
+               XLAT_DEBUG("%.*sxlat_aprint ATTRIBUTE", lvl, xlat_spaces);
 
                /*
                 *      Some attributes are virtual <sigh>
                 */
-               str = xlat_getvp(ctx, ref, node->attr.tmpl_list, node->attr.tmpl_da, node->attr.tmpl_tag, node->attr.tmpl_num, true);
+               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;
 
@@ -1987,46 +2251,74 @@ static char *xlat_aprint(TALLOC_CTX *ctx, REQUEST *request, xlat_exp_t const * c
                        talloc_free(str);
                        return NULL;
                }
+               RDEBUG2("EXPAND %s", node->xlat->name);
+               RDEBUG2("   --> %s", str);
                break;
 
        case XLAT_MODULE:
                XLAT_DEBUG("xlat_aprint MODULE");
-               if (xlat_process(&child, request, node->child, node->xlat->escape, node->xlat->instance) == 0) {
-                       return NULL;
+
+               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("%.*sEXPAND mod %s %s", lvl, xlat_spaces, node->fmt, node->child->fmt);
                XLAT_DEBUG("%.*s      ---> %s", lvl, xlat_spaces, child);
 
                /*
                 *      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_print_string().
+                *      This is really the reverse of fr_prints().
                 */
-               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;
+               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;
                        }
 
-                       *(q++) = *(p++);
+                       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';
                }
-               *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 */
@@ -2041,36 +2333,63 @@ 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");
-               child = request_data_reference(request, request,
-                                              REQUEST_DATA_REGEX | node->attr.tmpl_num);
-               if (!child) return NULL;
+               XLAT_DEBUG("%.*sxlat_aprint REGEX", lvl, xlat_spaces);
+               if (regex_request_to_sub(ctx, &str, request, node->attr.tmpl_num) < 0) return NULL;
 
-               str = talloc_typed_strdup(ctx, child);
                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) 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 {
 
-               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;
+       }
 
+       /*
+        *      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;
 
-               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;
        }
@@ -2080,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;
@@ -2163,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.
@@ -2174,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;
@@ -2188,22 +2507,31 @@ static ssize_t xlat_expand_struct(char **out, size_t outlen, REQUEST *request, x
                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;
-       } else {
-               strlcpy(*out, buff, outlen);
-               talloc_free(buff);
+               return len;
        }
 
-       return strlen(*out);
+       /*
+        *      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,
-                          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.
@@ -2213,8 +2541,8 @@ static ssize_t xlat_expand(char **out, size_t outlen, REQUEST *request, char con
  * @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 CC_HINT(nonnull (1, 3, 4)) xlat_expand(char **out, size_t outlen, REQUEST *request, char const *fmt,
-                                                     RADIUS_ESCAPE_STRING escape, void *escape_ctx)
+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;
@@ -2246,45 +2574,72 @@ static ssize_t CC_HINT(nonnull (1, 3, 4)) xlat_expand(char **out, size_t outlen,
        return len;
 }
 
-/*
- *     Try to convert an xlat to a tmpl for efficiency
+/** 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.
  */
-value_pair_tmpl_t *radius_xlat2tmpl(TALLOC_CTX *ctx, xlat_exp_t *node)
+vp_tmpl_t *xlat_to_tmpl_attr(TALLOC_CTX *ctx, xlat_exp_t *node)
 {
-       value_pair_tmpl_t *vpt;
+       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;
 
        /*
-        * @todo it should be possible to emulate the concat and count operations in the
-        * map code.
+        *   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;
-       vpt->tmpl_request = node->attr.tmpl_request;
-       vpt->tmpl_list = node->attr.tmpl_list;
-       vpt->tmpl_da = node->attr.tmpl_da;
-       vpt->tmpl_num = node->attr.tmpl_num;
-       vpt->tmpl_tag = node->attr.tmpl_tag;
+       memcpy(&vpt->data, &node->attr.data, sizeof(vpt->data));
 
        VERIFY_TMPL(vpt);
 
        return vpt;
 }
 
-ssize_t radius_xlat(char *out, size_t outlen, REQUEST *request, char const *fmt, RADIUS_ESCAPE_STRING escape, void *ctx)
+/** 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_axlat(char **out, REQUEST *request, char const *fmt, 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, 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);
 }