/*
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

/**
 * $Id: 6746bdec8d9f8f61bb0cc5b0e271994c150e6169 $
 *
 * @brief #VALUE_PAIR template functions
 * @file main/tmpl.c
 *
 * @ingroup AVP
 *
 * @copyright 2014-2015 The FreeRADIUS server project
 */
RCSID("$Id: 6746bdec8d9f8f61bb0cc5b0e271994c150e6169 $")

#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/rad_assert.h>

#include <ctype.h>

/** Map #tmpl_type_t values to descriptive strings
 */
FR_NAME_NUMBER const tmpl_names[] = {
	{ "literal",		TMPL_TYPE_LITERAL 	},
	{ "xlat",		TMPL_TYPE_XLAT		},
	{ "attr",		TMPL_TYPE_ATTR		},
	{ "unknown attr",	TMPL_TYPE_ATTR_UNDEFINED	},
	{ "list",		TMPL_TYPE_LIST		},
	{ "regex",		TMPL_TYPE_REGEX		},
	{ "exec",		TMPL_TYPE_EXEC		},
	{ "data",		TMPL_TYPE_DATA		},
	{ "parsed xlat",	TMPL_TYPE_XLAT_STRUCT	},
	{ "parsed regex",	TMPL_TYPE_REGEX_STRUCT	},
	{ "null",		TMPL_TYPE_NULL		},
	{ NULL, 0 }
};

/** Map keywords to #pair_lists_t values
 */
const FR_NAME_NUMBER pair_lists[] = {
	{ "request",		PAIR_LIST_REQUEST },
	{ "reply",		PAIR_LIST_REPLY },
	{ "control",		PAIR_LIST_CONTROL },		/* New name should have priority */
	{ "config",		PAIR_LIST_CONTROL },
	{ "session-state",	PAIR_LIST_STATE },
#ifdef WITH_PROXY
	{ "proxy-request",	PAIR_LIST_PROXY_REQUEST },
	{ "proxy-reply",	PAIR_LIST_PROXY_REPLY },
#endif
#ifdef WITH_COA
	{ "coa",		PAIR_LIST_COA },
	{ "coa-reply",		PAIR_LIST_COA_REPLY },
	{ "disconnect",		PAIR_LIST_DM },
	{ "disconnect-reply",	PAIR_LIST_DM_REPLY },
#endif
	{  NULL , -1 }
};

/** Map keywords to #request_refs_t values
 */
const FR_NAME_NUMBER request_refs[] = {
	{ "outer",		REQUEST_OUTER },
	{ "current",		REQUEST_CURRENT },
	{ "parent",		REQUEST_PARENT },
	{  NULL , -1 }
};

/** @name Parse list and request qualifiers to #pair_lists_t and #request_refs_t values
 *
 * These functions also resolve #pair_lists_t and #request_refs_t values to #REQUEST
 * structs and the head of #VALUE_PAIR lists in those structs.
 *
 * For adding new #VALUE_PAIR to the lists, the #radius_list_ctx function can be used
 * to obtain the appropriate TALLOC_CTX pointer.
 *
 * @note These don't really have much to do with #vp_tmpl_t. They're in the same
 *	file as they're used almost exclusively by the tmpl_* functions.
 * @{
 */

/** Resolve attribute name to a #pair_lists_t value.
 *
 * Check the name string for #pair_lists qualifiers and write a #pair_lists_t value
 * for that list to out. This value may be passed to #radius_list, along with the current
 * #REQUEST, to get a pointer to the actual list in the #REQUEST.
 *
 * If we're sure we've definitely found a list qualifier token delimiter (``:``) but the
 * string doesn't match a #radius_list qualifier, return 0 and write #PAIR_LIST_UNKNOWN
 * to out.
 *
 * If we can't find a string that looks like a request qualifier, set out to def, and
 * return 0.
 *
 * @note #radius_list_name should be called before passing a name string that may
 *	contain qualifiers to #dict_attrbyname.
 *
 * @param[out] out Where to write the list qualifier.
 * @param[in] name String containing list qualifiers to parse.
 * @param[in] def the list to return if no qualifiers were found.
 * @return 0 if no valid list qualifier could be found, else the number of bytes consumed.
 *	The caller may then advanced the name pointer by the value returned, to get the
 *	start of the attribute name (if any).
 *
 * @see pair_list
 * @see radius_list
 */
size_t radius_list_name(pair_lists_t *out, char const *name, pair_lists_t def)
{
	char const *p = name;
	char const *q;

	/* This should never be a NULL pointer */
	rad_assert(name);

	/*
	 *	Try and determine the end of the token
	 */
	for (q = p; dict_attr_allowed_chars[(uint8_t) *q]; q++);

	switch (*q) {
	/*
	 *	It's a bareword made up entirely of dictionary chars
	 *	check and see if it's a list qualifier, and if it's
	 *	not, return the def and say we couldn't parse
	 *	anything.
	 */
	case '\0':
		*out = fr_substr2int(pair_lists, p, PAIR_LIST_UNKNOWN, (q - p));
		if (*out != PAIR_LIST_UNKNOWN) return q - p;
		*out = def;
		return 0;

	/*
	 *	It may be a list qualifier delimiter. Because of tags
	 *	We need to check that it doesn't look like a tag suffix.
	 *	We do this by looking at the chars between ':' and the
	 *	next token delimiter, and seeing if they're all digits.
	 */
	case ':':
	{
		char const *d = q + 1;

		if (isdigit((uint8_t) *d)) {
			while (isdigit((uint8_t) *d)) d++;

			/*
			 *	Char after the number string
			 *	was a token delimiter, so this is a
			 *	tag, not a list qualifier.
			 */
			if (!dict_attr_allowed_chars[(uint8_t) *d]) {
				*out = def;
				return 0;
			}
		}

		*out = fr_substr2int(pair_lists, p, PAIR_LIST_UNKNOWN, (q - p));
		if (*out == PAIR_LIST_UNKNOWN) return 0;

		return (q + 1) - name; /* Consume the list and delimiter */
	}

	default:
		*out = def;
		return 0;
	}
}

/** Resolve attribute #pair_lists_t value to an attribute list.
 *
 * The value returned is a pointer to the pointer of the HEAD of a #VALUE_PAIR list in the
 * #REQUEST. If the head of the list changes, the pointer will still be valid.
 *
 * @param[in] request containing the target lists.
 * @param[in] list #pair_lists_t value to resolve to #VALUE_PAIR list. Will be NULL if list
 *	name couldn't be resolved.
 * @return a pointer to the HEAD of a list in the #REQUEST.
 *
 * @see tmpl_cursor_init
 * @see fr_cursor_init
 */
VALUE_PAIR **radius_list(REQUEST *request, pair_lists_t list)
{
	if (!request) return NULL;

	switch (list) {
	/* Don't add default */
	case PAIR_LIST_UNKNOWN:
		break;

	case PAIR_LIST_REQUEST:
		if (!request->packet) return NULL;
		return &request->packet->vps;

	case PAIR_LIST_REPLY:
		if (!request->reply) return NULL;
		return &request->reply->vps;

	case PAIR_LIST_CONTROL:
		return &request->config;

	case PAIR_LIST_STATE:
		return &request->state;

#ifdef WITH_PROXY
	case PAIR_LIST_PROXY_REQUEST:
		if (!request->proxy) break;
		return &request->proxy->vps;

	case PAIR_LIST_PROXY_REPLY:
		if (!request->proxy_reply) break;
		return &request->proxy_reply->vps;
#endif
#ifdef WITH_COA
	case PAIR_LIST_COA:
		if (request->coa &&
		    (request->coa->proxy->code == PW_CODE_COA_REQUEST)) {
			return &request->coa->proxy->vps;
		}
		break;

	case PAIR_LIST_COA_REPLY:
		if (request->coa && /* match reply with request */
		    (request->coa->proxy->code == PW_CODE_COA_REQUEST) &&
		    request->coa->proxy_reply) {
			return &request->coa->proxy_reply->vps;
		}
		break;

	case PAIR_LIST_DM:
		if (request->coa &&
		    (request->coa->proxy->code == PW_CODE_DISCONNECT_REQUEST)) {
			return &request->coa->proxy->vps;
		}
		break;

	case PAIR_LIST_DM_REPLY:
		if (request->coa && /* match reply with request */
		    (request->coa->proxy->code == PW_CODE_DISCONNECT_REQUEST) &&
		    request->coa->proxy_reply) {
			return &request->coa->proxy_reply->vps;
		}
		break;
#endif
	}

	RWDEBUG2("List \"%s\" is not available",
		fr_int2str(pair_lists, list, "<INVALID>"));

	return NULL;
}

/** Resolve a list to the #RADIUS_PACKET holding the HEAD pointer for a #VALUE_PAIR list
 *
 * Returns a pointer to the #RADIUS_PACKET that holds the HEAD pointer of a given list,
 * for the current #REQUEST.
 *
 * @param[in] request To resolve list in.
 * @param[in] list #pair_lists_t value to resolve to #RADIUS_PACKET.
 * @return a #RADIUS_PACKET on success, else NULL.
 *
 * @see radius_list
 */
RADIUS_PACKET *radius_packet(REQUEST *request, pair_lists_t list)
{
	switch (list) {
	/* Don't add default */
	case PAIR_LIST_STATE:
	case PAIR_LIST_CONTROL:
	case PAIR_LIST_UNKNOWN:
		return NULL;

	case PAIR_LIST_REQUEST:
		return request->packet;

	case PAIR_LIST_REPLY:
		return request->reply;

#ifdef WITH_PROXY
	case PAIR_LIST_PROXY_REQUEST:
		return request->proxy;

	case PAIR_LIST_PROXY_REPLY:
		return request->proxy_reply;
#endif

#ifdef WITH_COA
	case PAIR_LIST_COA:
	case PAIR_LIST_DM:
		return request->coa->proxy;

	case PAIR_LIST_COA_REPLY:
	case PAIR_LIST_DM_REPLY:
		return request->coa->proxy_reply;
#endif
	}

	return NULL;
}

/** Return the correct TALLOC_CTX to alloc #VALUE_PAIR in, for a list
 *
 * Allocating new #VALUE_PAIR in the context of a #REQUEST is usually wrong.
 * #VALUE_PAIR should be allocated in the context of a #RADIUS_PACKET, so that if the
 * #RADIUS_PACKET is freed before the #REQUEST, the associated #VALUE_PAIR lists are
 * freed too.
 *
 * @param[in] request containing the target lists.
 * @param[in] list #pair_lists_t value to resolve to TALLOC_CTX.
 * @return a TALLOC_CTX on success, else NULL.
 *
 * @see radius_list
 */
TALLOC_CTX *radius_list_ctx(REQUEST *request, pair_lists_t list)
{
	if (!request) return NULL;

	switch (list) {
	case PAIR_LIST_REQUEST:
		return request->packet;

	case PAIR_LIST_REPLY:
		return request->reply;

	case PAIR_LIST_CONTROL:
		return request;

	case PAIR_LIST_STATE:
		return request->state_ctx;

#ifdef WITH_PROXY
	case PAIR_LIST_PROXY_REQUEST:
		return request->proxy;

	case PAIR_LIST_PROXY_REPLY:
		return request->proxy_reply;
#endif

#ifdef WITH_COA
	case PAIR_LIST_COA:
		if (!request->coa) return NULL;
		rad_assert(request->coa->proxy != NULL);
		if (request->coa->proxy->code != PW_CODE_COA_REQUEST) return NULL;
		return request->coa->proxy;

	case PAIR_LIST_COA_REPLY:
		if (!request->coa) return NULL;
		rad_assert(request->coa->proxy != NULL);
		if (request->coa->proxy->code != PW_CODE_COA_REQUEST) return NULL;
		return request->coa->proxy_reply;

	case PAIR_LIST_DM:
		if (!request->coa) return NULL;
		rad_assert(request->coa->proxy != NULL);
		if (request->coa->proxy->code != PW_CODE_DISCONNECT_REQUEST) return NULL;
		return request->coa->proxy;

	case PAIR_LIST_DM_REPLY:
		if (!request->coa) return NULL;
		rad_assert(request->coa->proxy != NULL);
		if (request->coa->proxy->code != PW_CODE_DISCONNECT_REQUEST) return NULL;
		return request->coa->proxy_reply;
#endif
	/* Don't add default */
	case PAIR_LIST_UNKNOWN:
		break;
	}

	return NULL;
}

/** Resolve attribute name to a #request_refs_t value.
 *
 * Check the name string for qualifiers that reference a parent #REQUEST.
 *
 * If we find a string that matches a #request_refs qualifier, return the number of chars
 * we consumed.
 *
 * If we're sure we've definitely found a list qualifier token delimiter (``*``) but the
 * qualifier doesn't match one of the #request_refs qualifiers, return 0 and set out to
 * #REQUEST_UNKNOWN.
 *
 * If we can't find a string that looks like a request qualifier, set out to def, and
 * return 0.
 *
 * @param[out] out The #request_refs_t value the name resolved to (or #REQUEST_UNKNOWN).
 * @param[in] name of attribute.
 * @param[in] def default request ref to return if no request qualifier is present.
 * @return 0 if no valid request qualifier could be found, else the number of bytes consumed.
 *	The caller may then advanced the name pointer by the value returned, to get the
 *	start of the attribute list or attribute name(if any).
 *
 * @see radius_list_name
 * @see request_refs
 */
size_t radius_request_name(request_refs_t *out, char const *name, request_refs_t def)
{
	char const *p, *q;

	p = name;
	/*
	 *	Try and determine the end of the token
	 */
	for (q = p; dict_attr_allowed_chars[(uint8_t) *q] && (*q != '.') && (*q != '-'); q++);

	/*
	 *	First token delimiter wasn't a '.'
	 */
	if (*q != '.') {
		*out = def;
		return 0;
	}

	*out = fr_substr2int(request_refs, name, REQUEST_UNKNOWN, q - p);
	if (*out == REQUEST_UNKNOWN) return 0;

	return (q + 1) - p;
}

/** Resolve a #request_refs_t to a #REQUEST.
 *
 * Sometimes #REQUEST structs may be chained to each other, as is the case
 * when internally proxying EAP. This function resolves a #request_refs_t
 * to a #REQUEST higher in the chain than the current #REQUEST.
 *
 * @see radius_list
 * @param[in,out] context #REQUEST to start resolving from, and where to write
 *	a pointer to the resolved #REQUEST back to.
 * @param[in] name (request) to resolve.
 * @return 0 if request is valid in this context, else -1.
 */
int radius_request(REQUEST **context, request_refs_t name)
{
	REQUEST *request = *context;

	switch (name) {
	case REQUEST_CURRENT:
		return 0;

	case REQUEST_PARENT:	/* for future use in request chaining */
	case REQUEST_OUTER:
		if (!request->parent) {
			return -1;
		}
		*context = request->parent;
		break;

	case REQUEST_UNKNOWN:
	default:
		rad_assert(0);
		return -1;
	}

	return 0;
}
/** @} */

/** @name Alloc or initialise #vp_tmpl_t
 *
 * @note Should not usually be called outside of tmpl_* functions, use one of
 *	the tmpl_*from_* functions instead.
 * @{
 */

/** Initialise stack allocated #vp_tmpl_t
 *
 * @note Name is not strdupe'd or memcpy'd so must be available, and must not change
 *	for the lifetime of the #vp_tmpl_t.
 *
 * @param[out] vpt to initialise.
 * @param[in] type to set in the #vp_tmpl_t.
 * @param[in] name of the #vp_tmpl_t.
 * @param[in] len The length of the buffer (or a substring of the buffer) pointed to by name.
 *	If < 0 strlen will be used to determine the length.
 * @return a pointer to the initialised #vp_tmpl_t. The same value as
 *	vpt.
 */
vp_tmpl_t *tmpl_init(vp_tmpl_t *vpt, tmpl_type_t type, char const *name, ssize_t len)
{
	rad_assert(vpt);
	rad_assert(type != TMPL_TYPE_UNKNOWN);
	rad_assert(type <= TMPL_TYPE_NULL);

	memset(vpt, 0, sizeof(vp_tmpl_t));
	vpt->type = type;

	if (name) {
		vpt->name = name;
		vpt->len = len < 0 ? strlen(name) :
				     (size_t) len;
	}
	return vpt;
}

/** Create a new heap allocated #vp_tmpl_t
 *
 * @param[in,out] ctx to allocate in.
 * @param[in] type to set in the #vp_tmpl_t.
 * @param[in] name of the #vp_tmpl_t (will be copied to a new talloc buffer parented
 *	by the #vp_tmpl_t).
 * @param[in] len The length of the buffer (or a substring of the buffer) pointed to by name.
 *	If < 0 strlen will be used to determine the length.
 * @return the newly allocated #vp_tmpl_t.
 */
vp_tmpl_t *tmpl_alloc(TALLOC_CTX *ctx, tmpl_type_t type, char const *name, ssize_t len)
{
	vp_tmpl_t *vpt;

	rad_assert(type != TMPL_TYPE_UNKNOWN);
	rad_assert(type <= TMPL_TYPE_NULL);

	vpt = talloc_zero(ctx, vp_tmpl_t);
	if (!vpt) return NULL;
	vpt->type = type;
	if (name) {
		vpt->name = talloc_bstrndup(vpt, name, len < 0 ? strlen(name) : (size_t)len);
		vpt->len = talloc_array_length(vpt->name) - 1;
	}

	return vpt;
}
/* @} **/

/** @name Create new #vp_tmpl_t from a string
 *
 * @{
 */
/** Parse a string into a TMPL_TYPE_ATTR_* or #TMPL_TYPE_LIST type #vp_tmpl_t
 *
 * @note The name field is just a copy of the input pointer, if you know that string might be
 *	freed before you're done with the #vp_tmpl_t use #tmpl_afrom_attr_str
 *	instead.
 *
 * @param[out] vpt to modify.
 * @param[in] name of attribute including #request_refs and #pair_lists qualifiers.
 *	If only #request_refs and #pair_lists qualifiers are found, a #TMPL_TYPE_LIST
 *	#vp_tmpl_t will be produced.
 * @param[in] request_def The default #REQUEST to set if no #request_refs qualifiers are
 *	found in name.
 * @param[in] list_def The default list to set if no #pair_lists qualifiers are found in
 *	name.
 * @param[in] allow_unknown If true attributes in the format accepted by
 *	#dict_unknown_from_substr will be allowed, even if they're not in the main
 *	dictionaries.
 *	If an unknown attribute is found a #TMPL_TYPE_ATTR #vp_tmpl_t will be
 *	produced with the unknown #DICT_ATTR stored in the ``unknown.da`` buffer.
 *	This #DICT_ATTR will have its ``flags.is_unknown`` field set to true.
 *	If #tmpl_from_attr_substr is being called on startup, the #vp_tmpl_t may be
 *	passed to #tmpl_define_unknown_attr to add the unknown attribute to the main
 *	dictionary.
 *	If the unknown attribute is not added to the main dictionary the #vp_tmpl_t
 *	cannot be used to search for a #VALUE_PAIR in a #REQUEST.
 * @param[in] allow_undefined If true, we don't generate a parse error on unknown attributes.
 *	If an unknown attribute is found a #TMPL_TYPE_ATTR_UNDEFINED #vp_tmpl_t
 *	will be produced.
 * @return <= 0 on error (offset as negative integer), > 0 on success
 *	(number of bytes parsed).
 *
 * @see REMARKER to produce pretty error markers from the return value.
 */
ssize_t tmpl_from_attr_substr(vp_tmpl_t *vpt, char const *name,
			      request_refs_t request_def, pair_lists_t list_def,
			      bool allow_unknown, bool allow_undefined)
{
	char const *p;
	long num;
	char *q;
	tmpl_type_t type = TMPL_TYPE_ATTR;
	DICT_ATTR const *da;

	value_pair_tmpl_attr_t attr;	/* So we don't fill the tmpl with junk and then error out */

	memset(vpt, 0, sizeof(*vpt));
	memset(&attr, 0, sizeof(attr));

	p = name;

	if (*p == '&') p++;

	p += radius_request_name(&attr.request, p, request_def);
	if (attr.request == REQUEST_UNKNOWN) {
		fr_strerror_printf("Invalid request qualifier");
		return -(p - name);
	}

	/*
	 *	Finding a list qualifier is optional
	 */
	p += radius_list_name(&attr.list, p, list_def);
	if (attr.list == PAIR_LIST_UNKNOWN) {
		fr_strerror_printf("Invalid list qualifier");
		return -(p - name);
	}

	attr.tag = TAG_ANY;
	attr.num = NUM_ANY;

	/*
	 *	This may be just a bare list, but it can still
	 *	have instance selectors and tag selectors.
	 */
	switch (*p) {
	case '\0':
		type = TMPL_TYPE_LIST;
		attr.num = NUM_ALL;	/* Hack - Should be removed once tests are updated */
		goto finish;

	case '[':
		type = TMPL_TYPE_LIST;
		attr.num = NUM_ALL;	/* Hack - Should be removed once tests are updated */
		goto do_num;

	default:
		break;
	}

	attr.da = dict_attrbyname_substr(&p);
	if (!attr.da) {
		char const *a;

		/*
		 *	Record start of attribute in case we need to error out.
		 */
		a = p;

		fr_strerror();	/* Clear out any existing errors */

		/*
		 *	Attr-1.2.3.4 is OK.
		 */
		if (dict_unknown_from_substr((DICT_ATTR *)&attr.unknown.da, &p) == 0) {
			/*
			 *	Check what we just parsed really hasn't been defined
			 *	in the main dictionaries.
			 *
			 *	If it has, parsing is the same as if the attribute
			 *	name had been used instead of its OID.
			 */
			attr.da = dict_attrbyvalue(((DICT_ATTR *)&attr.unknown.da)->attr,
						   ((DICT_ATTR *)&attr.unknown.da)->vendor);
			if (attr.da) {
				vpt->auto_converted = true;
				goto do_num;
			}

			if (!allow_unknown) {
				fr_strerror_printf("Unknown attribute");
				return -(a - name);
			}

			/*
			 *	Unknown attributes can't be encoded, as we don't
			 *	know how to encode them!
			 */
			attr.da = (DICT_ATTR *)&attr.unknown.da;

			goto do_num; /* unknown attributes can't have tags */
		}

		/*
		 *	Can't parse it as an attribute, might be a literal string
		 *	let the caller decide.
		 *
		 *	Don't alter the fr_strerror buffer, should contain the parse
		 *	error from dict_unknown_from_substr.
		 */
		if (!allow_undefined) return -(a - name);

		/*
		 *	Copy the name to a field for later resolution
		 */
		type = TMPL_TYPE_ATTR_UNDEFINED;
		for (q = attr.unknown.name; dict_attr_allowed_chars[(int) *p]; *q++ = *p++) {
			if (q >= (attr.unknown.name + sizeof(attr.unknown.name) - 1)) {
				fr_strerror_printf("Attribute name is too long");
				return -(p - name);
			}
		}
		*q = '\0';

		goto do_num;
	}

	/*
	 *	Canonicalize the attribute.
	 *
	 *	We can define multiple names for one attribute.  In
	 *	which case we only use the canonical name.
	 */
	da = dict_attrbyvalue(attr.da->attr, attr.da->vendor);
	if (da && (attr.da != da)) attr.da = da;


	/*
	 *	The string MIGHT have a tag.
	 */
	if (*p == ':') {
		if (attr.da && !attr.da->flags.has_tag) { /* Lists don't have a da */
			fr_strerror_printf("Attribute '%s' cannot have a tag", attr.da->name);
			return -(p - name);
		}

		num = strtol(p + 1, &q, 10);
		if ((num > 0x1f) || (num < 0)) {
			fr_strerror_printf("Invalid tag value '%li' (should be between 0-31)", num);
			return -((p + 1)- name);
		}

		attr.tag = num;
		p = q;
	}

do_num:
	if (*p == '\0') goto finish;

	if (*p == '[') {
		p++;

		switch (*p) {
		case '#':
			attr.num = NUM_COUNT;
			p++;
			break;

		case '*':
			attr.num = NUM_ALL;
			p++;
			break;

		case 'n':
			attr.num = NUM_LAST;
			p++;
			break;

		default:
			num = strtol(p, &q, 10);
			if (p == q) {
				fr_strerror_printf("Array index is not an integer");
				return -(p - name);
			}

			if ((num > 1000) || (num < 0)) {
				fr_strerror_printf("Invalid array reference '%li' (should be between 0-1000)", num);
				return -(p - name);
			}
			attr.num = num;
			p = q;
			break;
		}

		if (*p != ']') {
			fr_strerror_printf("No closing ']' for array index");
			return -(p - name);
		}
		p++;
	}

finish:
	vpt->type = type;
	vpt->name = name;
	vpt->len = p - name;

	/*
	 *	Copy over the attribute definition, now we're
	 *	sure what we were passed is valid.
	 */
	memcpy(&vpt->data.attribute, &attr, sizeof(vpt->data.attribute));
	if ((vpt->type == TMPL_TYPE_ATTR) && attr.da->flags.is_unknown) {
		vpt->tmpl_da = (DICT_ATTR *)&vpt->data.attribute.unknown.da;
	}

	VERIFY_TMPL(vpt);

	return vpt->len;
}

/** Parse a string into a TMPL_TYPE_ATTR_* or #TMPL_TYPE_LIST type #vp_tmpl_t
 *
 * @note Unlike #tmpl_from_attr_substr this function will error out if the entire
 *	name string isn't parsed.
 *
 * @copydetails tmpl_from_attr_substr
 */
ssize_t tmpl_from_attr_str(vp_tmpl_t *vpt, char const *name,
			   request_refs_t request_def, pair_lists_t list_def,
			   bool allow_unknown, bool allow_undefined)
{
	ssize_t slen;

	slen = tmpl_from_attr_substr(vpt, name, request_def, list_def, allow_unknown, allow_undefined);
	if (slen <= 0) return slen;
	if (name[slen] != '\0') {
		/* This looks wrong, but it produces meaningful errors for unknown attrs with tags */
		fr_strerror_printf("Unexpected text after %s", fr_int2str(tmpl_names, vpt->type, "<INVALID>"));
		return -slen;
	}

	VERIFY_TMPL(vpt);

	return slen;
}

/** Parse a string into a TMPL_TYPE_ATTR_* or #TMPL_TYPE_LIST type #vp_tmpl_t
 *
 * @param[in,out] ctx to allocate #vp_tmpl_t in.
 * @param[out] out Where to write pointer to new #vp_tmpl_t.
 * @param[in] name of attribute including #request_refs and #pair_lists qualifiers.
 *	If only #request_refs #pair_lists qualifiers are found, a #TMPL_TYPE_LIST
 *	#vp_tmpl_t will be produced.
 * @param[in] request_def The default #REQUEST to set if no #request_refs qualifiers are
 *	found in name.
 * @param[in] list_def The default list to set if no #pair_lists qualifiers are found in
 *	name.
 * @param[in] allow_unknown If true attributes in the format accepted by
 *	#dict_unknown_from_substr will be allowed, even if they're not in the main
 *	dictionaries.
 *	If an unknown attribute is found a #TMPL_TYPE_ATTR #vp_tmpl_t will be
 *	produced with the unknown #DICT_ATTR stored in the ``unknown.da`` buffer.
 *	This #DICT_ATTR will have its ``flags.is_unknown`` field set to true.
 *	If #tmpl_from_attr_substr is being called on startup, the #vp_tmpl_t may be
 *	passed to #tmpl_define_unknown_attr to add the unknown attribute to the main
 *	dictionary.
 *	If the unknown attribute is not added to the main dictionary the #vp_tmpl_t
 *	cannot be used to search for a #VALUE_PAIR in a #REQUEST.
 * @param[in] allow_undefined If true, we don't generate a parse error on unknown attributes.
 *	If an unknown attribute is found a #TMPL_TYPE_ATTR_UNDEFINED #vp_tmpl_t
 *	will be produced.
 * @return <= 0 on error (offset as negative integer), > 0 on success
 *	(number of bytes parsed).
 *
 * @see REMARKER to produce pretty error markers from the return value.
 */
ssize_t tmpl_afrom_attr_substr(TALLOC_CTX *ctx, vp_tmpl_t **out, char const *name,
			       request_refs_t request_def, pair_lists_t list_def,
			       bool allow_unknown, bool allow_undefined)
{
	ssize_t slen;
	vp_tmpl_t *vpt;

	MEM(vpt = talloc(ctx, vp_tmpl_t)); /* tmpl_from_attr_substr zeros it */

	slen = tmpl_from_attr_substr(vpt, name, request_def, list_def, allow_unknown, allow_undefined);
	if (slen <= 0) {
		TALLOC_FREE(vpt);
		return slen;
	}
	vpt->name = talloc_strndup(vpt, vpt->name, slen);

	VERIFY_TMPL(vpt);

	*out = vpt;

	return slen;
}

/** Parse a string into a TMPL_TYPE_ATTR_* or #TMPL_TYPE_LIST type #vp_tmpl_t
 *
 * @note Unlike #tmpl_afrom_attr_substr this function will error out if the entire
 *	name string isn't parsed.
 *
 * @copydetails tmpl_afrom_attr_substr
 */
ssize_t tmpl_afrom_attr_str(TALLOC_CTX *ctx, vp_tmpl_t **out, char const *name,
			    request_refs_t request_def, pair_lists_t list_def,
			    bool allow_unknown, bool allow_undefined)
{
	ssize_t slen;
	vp_tmpl_t *vpt;

	MEM(vpt = talloc(ctx, vp_tmpl_t)); /* tmpl_from_attr_substr zeros it */

	slen = tmpl_from_attr_substr(vpt, name, request_def, list_def, allow_unknown, allow_undefined);
	if (slen <= 0) {
		TALLOC_FREE(vpt);
		return slen;
	}
	if (name[slen] != '\0') {
		/* This looks wrong, but it produces meaningful errors for unknown attrs with tags */
		fr_strerror_printf("Unexpected text after %s", fr_int2str(tmpl_names, vpt->type, "<INVALID>"));
		TALLOC_FREE(vpt);
		return -slen;
	}
	vpt->name = talloc_strndup(vpt, vpt->name, vpt->len);

	VERIFY_TMPL(vpt);

	*out = vpt;

	return slen;
}

/** Convert an arbitrary string into a #vp_tmpl_t
 *
 * @note Unlike #tmpl_afrom_attr_str return code 0 doesn't necessarily indicate failure,
 *	may just mean a 0 length string was parsed.
 *
 * @note xlats and regexes are left uncompiled.  This is to support the two pass parsing
 *	done by the modcall code.  Compilation on pass1 of that code could fail, as
 *	attributes or xlat functions registered by modules may not be available (yet).
 *
 * @note For details of attribute parsing see #tmpl_from_attr_substr.
 *
 * @param[in,out] ctx To allocate #vp_tmpl_t in.
 * @param[out] out Where to write the pointer to the new #vp_tmpl_t.
 * @param[in] in String to convert to a #vp_tmpl_t.
 * @param[in] inlen length of string to convert.
 * @param[in] type of quoting around value. May be one of:
 *	- #T_BARE_WORD - If string begins with ``&`` produces #TMPL_TYPE_ATTR,
 *	  #TMPL_TYPE_ATTR_UNDEFINED, #TMPL_TYPE_LIST or error.
 *	  If string does not begin with ``&`` produces #TMPL_TYPE_LITERAL,
 *	  #TMPL_TYPE_ATTR or #TMPL_TYPE_LIST.
 *	- #T_SINGLE_QUOTED_STRING - Produces #TMPL_TYPE_LITERAL
 *	- #T_DOUBLE_QUOTED_STRING - Produces #TMPL_TYPE_XLAT or #TMPL_TYPE_LITERAL (if
 *	  string doesn't contain ``%``).
 *	- #T_BACK_QUOTED_STRING - Produces #TMPL_TYPE_EXEC
 *	- #T_OP_REG_EQ - Produces #TMPL_TYPE_REGEX
 * @param[in] request_def The default #REQUEST to set if no #request_refs qualifiers are
 *	found in name.
 * @param[in] list_def The default list to set if no #pair_lists qualifiers are found in
 *	name.
 * @param[in] do_unescape whether or not we should do unescaping. Should be false if the
 *	caller already did it.
 * @return <= 0 on error (offset as negative integer), > 0 on success
 *	(number of bytes parsed).
 *	@see REMARKER to produce pretty error markers from the return value.
 *
 * @see tmpl_from_attr_substr
 */
ssize_t tmpl_afrom_str(TALLOC_CTX *ctx, vp_tmpl_t **out, char const *in, size_t inlen, FR_TOKEN type,
		       request_refs_t request_def, pair_lists_t list_def, bool do_unescape)
{
	bool do_xlat;
	char quote;
	char const *p;
	ssize_t slen;
	PW_TYPE data_type = PW_TYPE_STRING;
	vp_tmpl_t *vpt = NULL;
	value_data_t data;

	switch (type) {
	case T_BARE_WORD:
		/*
		 *	If we can parse it as an attribute, it's an attribute.
		 *	Otherwise, treat it as a literal.
		 */
		quote = '\0';

		slen = tmpl_afrom_attr_str(ctx, &vpt, in, request_def, list_def, true, (in[0] == '&'));
		if ((in[0] == '&') && (slen <= 0)) return slen;
		if (slen > 0) break;
		goto parse;

	case T_SINGLE_QUOTED_STRING:
		quote = '\'';

	parse:
		if (cf_new_escape && do_unescape) {
			slen = value_data_from_str(ctx, &data, &data_type, NULL, in, inlen, quote);
			if (slen < 0) return 0;

			vpt = tmpl_alloc(ctx, TMPL_TYPE_LITERAL, data.strvalue, talloc_array_length(data.strvalue) - 1);
			talloc_free(data.ptr);
		} else {
			vpt = tmpl_alloc(ctx, TMPL_TYPE_LITERAL, in, inlen);
		}
		vpt->quote = quote;
		slen = vpt->len;
		break;

	case T_DOUBLE_QUOTED_STRING:
		do_xlat = false;

		p = in;
		while (*p) {
			if (do_unescape) { /* otherwise \ is just another character */
				if (*p == '\\') {
					if (!p[1]) break;
					p += 2;
					continue;
				}
			}

			if (*p == '%') {
				do_xlat = true;
				break;
			}

			p++;
		}

		/*
		 *	If the double quoted string needs to be
		 *	expanded at run time, make it an xlat
		 *	expansion.  Otherwise, convert it to be a
		 *	literal.
		 */
		if (cf_new_escape && do_unescape) {
			slen = value_data_from_str(ctx, &data, &data_type, NULL, in, inlen, '"');
			if (slen < 0) return slen;

			if (do_xlat) {
				vpt = tmpl_alloc(ctx, TMPL_TYPE_XLAT, data.strvalue,
						 talloc_array_length(data.strvalue) - 1);
			} else {
				vpt = tmpl_alloc(ctx, TMPL_TYPE_LITERAL, data.strvalue,
						 talloc_array_length(data.strvalue) - 1);
				vpt->quote = '"';
			}
			talloc_free(data.ptr);
		} else {
			if (do_xlat) {
				vpt = tmpl_alloc(ctx, TMPL_TYPE_XLAT, in, inlen);
			} else {
				vpt = tmpl_alloc(ctx, TMPL_TYPE_LITERAL, in, inlen);
				vpt->quote = '"';
			}
		}
		slen = vpt->len;
		break;

	case T_BACK_QUOTED_STRING:
		if (cf_new_escape && do_unescape) {
			slen = value_data_from_str(ctx, &data, &data_type, NULL, in, inlen, '`');
			if (slen < 0) return slen;

			vpt = tmpl_alloc(ctx, TMPL_TYPE_EXEC, data.strvalue, talloc_array_length(data.strvalue) - 1);
			talloc_free(data.ptr);
		} else {
			vpt = tmpl_alloc(ctx, TMPL_TYPE_EXEC, in, inlen);
		}
		slen = vpt->len;
		break;

	case T_OP_REG_EQ: /* hack */
		vpt = tmpl_alloc(ctx, TMPL_TYPE_REGEX, in, inlen);
		slen = vpt->len;
		break;

	default:
		rad_assert(0);
		return 0;	/* 0 is an error here too */
	}

	rad_assert((slen >= 0) && (vpt != NULL));

	VERIFY_TMPL(vpt);

	*out = vpt;

	return slen;
}
/* @} **/

/** @name Cast or convert #vp_tmpl_t
 *
 * #tmpl_cast_in_place can be used to convert #TMPL_TYPE_LITERAL to a #TMPL_TYPE_DATA of a
 *  specified #PW_TYPE.
 *
 * #tmpl_cast_in_place_str does the same as #tmpl_cast_in_place, but will always convert to
 * #PW_TYPE #PW_TYPE_STRING.
 *
 * #tmpl_cast_to_vp does the same as #tmpl_cast_in_place, but outputs a #VALUE_PAIR.
 *
 * #tmpl_define_unknown_attr converts a #TMPL_TYPE_ATTR with an unknown #DICT_ATTR to a
 * #TMPL_TYPE_ATTR with a known #DICT_ATTR, by adding the unknown #DICT_ATTR to the main
 * dictionary, and updating the ``tmpl_da`` pointer.
 * @{
 */

/** Convert #vp_tmpl_t of type #TMPL_TYPE_LITERAL or #TMPL_TYPE_DATA to #TMPL_TYPE_DATA of type specified
 *
 * @note Conversion is done in place.
 * @note Irrespective of whether the #vp_tmpl_t was #TMPL_TYPE_LITERAL or #TMPL_TYPE_DATA,
 *	on successful cast it will be #TMPL_TYPE_DATA.
 *
 * @param[in,out] vpt The template to modify. Must be of type #TMPL_TYPE_LITERAL
 *	or #TMPL_TYPE_DATA.
 * @param[in] type to cast to.
 * @param[in] enumv Enumerated dictionary values associated with a #DICT_ATTR.
 * @return 0 on success, -1 on failure.
 */
int tmpl_cast_in_place(vp_tmpl_t *vpt, PW_TYPE type, DICT_ATTR const *enumv)
{
	ssize_t ret;

	VERIFY_TMPL(vpt);

	rad_assert(vpt != NULL);
	rad_assert((vpt->type == TMPL_TYPE_LITERAL) || (vpt->type == TMPL_TYPE_DATA));

	switch (vpt->type) {
	case TMPL_TYPE_LITERAL:
		/*
		 *	Why do we pass a pointer to the tmpl type? Goddamn WiMAX.
		 */
		ret = value_data_from_str(vpt, &vpt->tmpl_data_value, &type,
					  enumv, vpt->name, vpt->len, '\0');
		if (ret < 0) {
			VERIFY_TMPL(vpt);
			return -1;
		}

		vpt->tmpl_data_type = type;
		vpt->type = TMPL_TYPE_DATA;
		vpt->tmpl_data_length = (size_t) ret;
		break;

	case TMPL_TYPE_DATA:
	{
		value_data_t new;

		if (type == vpt->tmpl_data_type) return 0;	/* noop */

		ret = value_data_cast(vpt, &new, type, enumv, vpt->tmpl_data_type,
				      NULL, &vpt->tmpl_data_value, vpt->tmpl_data_length);
		if (ret < 0) return -1;

		/*
		 *	Free old value buffers
		 */
		switch (vpt->tmpl_data_type) {
		case PW_TYPE_STRING:
		case PW_TYPE_OCTETS:
			talloc_free(vpt->tmpl_data_value.ptr);
			break;

		default:
			break;
		}

		memcpy(&vpt->tmpl_data_value, &new, sizeof(vpt->tmpl_data_value));
		vpt->tmpl_data_type = type;
		vpt->tmpl_data_length = (size_t) ret;
	}
		break;

	default:
		rad_assert(0);
	}

	VERIFY_TMPL(vpt);

	return 0;
}

/** Convert #vp_tmpl_t of type #TMPL_TYPE_LITERAL to #TMPL_TYPE_DATA of type #PW_TYPE_STRING
 *
 * @note Conversion is done in place.
 *
 * @param[in,out] vpt The template to modify. Must be of type #TMPL_TYPE_LITERAL.
 */
void tmpl_cast_in_place_str(vp_tmpl_t *vpt)
{
	rad_assert(vpt != NULL);
	rad_assert(vpt->type == TMPL_TYPE_LITERAL);

	vpt->tmpl_data.vp_strvalue = talloc_typed_strdup(vpt, vpt->name);
	rad_assert(vpt->tmpl_data.vp_strvalue != NULL);

	vpt->type = TMPL_TYPE_DATA;
	vpt->tmpl_data_type = PW_TYPE_STRING;
	vpt->tmpl_data_length = talloc_array_length(vpt->tmpl_data.vp_strvalue) - 1;
}

/** Expand a #vp_tmpl_t to a string, parse it as an attribute of type cast, create a #VALUE_PAIR from the result
 *
 * @note Like #tmpl_expand, but produces a #VALUE_PAIR.
 *
 * @param out Where to write pointer to the new #VALUE_PAIR.
 * @param request The current #REQUEST.
 * @param vpt to cast. Must be one of the following types:
 *	- #TMPL_TYPE_LITERAL
 *	- #TMPL_TYPE_EXEC
 *	- #TMPL_TYPE_XLAT
 *	- #TMPL_TYPE_XLAT_STRUCT
 *	- #TMPL_TYPE_ATTR
 *	- #TMPL_TYPE_DATA
 * @param cast type of #VALUE_PAIR to create.
 * @return 0 on success, -1 on failure.
 */
int tmpl_cast_to_vp(VALUE_PAIR **out, REQUEST *request,
		    vp_tmpl_t const *vpt, DICT_ATTR const *cast)
{
	int rcode;
	VALUE_PAIR *vp;
	value_data_t data;
	char *p;

	VERIFY_TMPL(vpt);

	*out = NULL;

	vp = fr_pair_afrom_da(request, cast);
	if (!vp) return -1;

	if (vpt->type == TMPL_TYPE_DATA) {
		VERIFY_VP(vp);
		rad_assert(vp->da->type == vpt->tmpl_data_type);

		value_data_copy(vp, &vp->data, vpt->tmpl_data_type, &vpt->tmpl_data_value, vpt->tmpl_data_length);
		*out = vp;
		return 0;
	}

	rcode = tmpl_aexpand(vp, &p, request, vpt, NULL, NULL);
	if (rcode < 0) {
		fr_pair_list_free(&vp);
		return rcode;
	}
	data.strvalue = p;

	/*
	 *	New escapes: strings are in binary form.
	 */
	if (cf_new_escape && (vp->da->type == PW_TYPE_STRING)) {
		vp->data.ptr = talloc_steal(vp, data.ptr);
		vp->vp_length = rcode;

	} else if (fr_pair_value_from_str(vp, data.strvalue, rcode) < 0) {
		talloc_free(data.ptr);
		fr_pair_list_free(&vp);
		return -1;
	}

	/*
	 *	Copy over any additional fields needed...
	 */
	if ((vpt->type == TMPL_TYPE_ATTR) && vp->da->flags.has_tag) {
		vp->tag = vpt->tmpl_tag;
	}

	*out = vp;
	return 0;
}

/** Add an unknown #DICT_ATTR specified by a #vp_tmpl_t to the main dictionary
 *
 * @param vpt to add. ``tmpl_da`` pointer will be updated to point to the
 *	#DICT_ATTR inserted into the dictionary.
 * @return 0 on success, -1 on failure.
 */
int tmpl_define_unknown_attr(vp_tmpl_t *vpt)
{
	DICT_ATTR const *da;

	if (!vpt) return -1;

	VERIFY_TMPL(vpt);

	if (vpt->type != TMPL_TYPE_ATTR) return 0;

	if (!vpt->tmpl_da->flags.is_unknown) return 0;

	da = dict_unknown_add(vpt->tmpl_da);
	if (!da) return -1;
	vpt->tmpl_da = da;
	return 0;
}
/* @} **/

/** @name Resolve a #vp_tmpl_t outputting the result in various formats
 *
 * @{
 */

/** Expand a #vp_tmpl_t to a string writing the result to a buffer
 *
 * The intended use of #tmpl_expand and #tmpl_aexpand is for modules to easily convert a #vp_tmpl_t
 * provided by the conf parser, into a usable value.
 * The value returned should be raw and undoctored for #PW_TYPE_STRING and #PW_TYPE_OCTETS types,
 * and the printable (string) version of the data for all others.
 *
 * Depending what arguments are passed, either copies the value to buff, or writes a pointer
 * to a string buffer to out. This allows the most efficient access to the value resolved by
 * the #vp_tmpl_t, avoiding unecessary string copies.
 *
 * @note This function is used where raw string values are needed, which may mean the string
 *	returned may be binary data or contain unprintable chars. #fr_prints or #fr_aprints should
 *	be used before using these values in debug statements. #is_printable can be used to check
 *	if the string only contains printable chars.
 *
 * @param out Where to write a pointer to the string buffer. On return may point to buff if
 *	buff was used to store the value. Otherwise will point to a #value_data_t buffer,
 *	or the name of the template. To force copying to buff, out should be NULL.
 * @param buff Expansion buffer, may be NULL if out is not NULL, and processing #TMPL_TYPE_LITERAL
 *	or string types.
 * @param bufflen Length of expansion buffer.
 * @param request Current request.
 * @param vpt to expand. Must be one of the following types:
 *	- #TMPL_TYPE_LITERAL
 *	- #TMPL_TYPE_EXEC
 *	- #TMPL_TYPE_XLAT
 *	- #TMPL_TYPE_XLAT_STRUCT
 *	- #TMPL_TYPE_ATTR
 *	- #TMPL_TYPE_DATA
 * @param escape xlat escape function (only used for xlat types).
 * @param escape_ctx xlat escape function data.
 * @return -1 on error, else the length of data written to buff, or pointed to by out.
 */
ssize_t tmpl_expand(char const **out, char *buff, size_t bufflen, REQUEST *request,
		    vp_tmpl_t const *vpt, xlat_escape_t escape, void *escape_ctx)
{
	VALUE_PAIR *vp;
	ssize_t slen = -1;	/* quiet compiler */

	VERIFY_TMPL(vpt);

	rad_assert(vpt->type != TMPL_TYPE_LIST);

	if (out) *out = NULL;

	switch (vpt->type) {
	case TMPL_TYPE_LITERAL:
		RDEBUG4("EXPAND TMPL LITERAL");

		if (!out) {
			rad_assert(buff);
			memcpy(buff, vpt->name, vpt->len >= bufflen ? bufflen : vpt->len + 1);
		} else {
			*out = vpt->name;
		}
		return vpt->len;

	case TMPL_TYPE_EXEC:
	{
		RDEBUG4("EXPAND TMPL EXEC");
		rad_assert(buff);
		if (radius_exec_program(request, buff, bufflen, NULL, request, vpt->name, NULL,
					true, false, EXEC_TIMEOUT) != 0) {
			return -1;
		}
		slen = strlen(buff);
		if (out) *out = buff;
	}
		break;

	case TMPL_TYPE_XLAT:
		RDEBUG4("EXPAND TMPL XLAT");
		rad_assert(buff);
		/* Error in expansion, this is distinct from zero length expansion */
		slen = radius_xlat(buff, bufflen, request, vpt->name, escape, escape_ctx);
		if (slen < 0) return slen;
		if (out) *out = buff;
		break;

	case TMPL_TYPE_XLAT_STRUCT:
		RDEBUG4("EXPAND TMPL XLAT STRUCT");
		rad_assert(buff);
		/* Error in expansion, this is distinct from zero length expansion */
		slen = radius_xlat_struct(buff, bufflen, request, vpt->tmpl_xlat, escape, escape_ctx);
		if (slen < 0) {
			return slen;
		}
		slen = strlen(buff);
		if (out) *out = buff;
		break;

	case TMPL_TYPE_ATTR:
	{
		int ret;

		RDEBUG4("EXPAND TMPL ATTR");
		rad_assert(buff);
		ret = tmpl_find_vp(&vp, request, vpt);
		if (ret < 0) return -2;

		if (out && ((vp->da->type == PW_TYPE_STRING) || (vp->da->type == PW_TYPE_OCTETS))) {
			*out = vp->data.ptr;
			slen = vp->vp_length;
		} else {
			if (out) *out = buff;
			slen = vp_prints_value(buff, bufflen, vp, '\0');
		}
	}
		break;

	case TMPL_TYPE_DATA:
	{
		RDEBUG4("EXPAND TMPL DATA");

		if (out && ((vpt->tmpl_data_type == PW_TYPE_STRING) || (vpt->tmpl_data_type == PW_TYPE_OCTETS))) {
			*out = vpt->tmpl_data_value.ptr;
			slen = vpt->tmpl_data_length;
		} else {
			if (out) *out = buff;
			/**
			 *  @todo tmpl_expand should accept an enumv da from the lhs of the map.
			 */
			slen = value_data_prints(buff, bufflen, vpt->tmpl_data_type, NULL, &vpt->tmpl_data_value, vpt->tmpl_data_length, '\0');
		}
	}
		break;

	/*
	 *	We should never be expanding these.
	 */
	case TMPL_TYPE_UNKNOWN:
	case TMPL_TYPE_NULL:
	case TMPL_TYPE_LIST:
	case TMPL_TYPE_REGEX:
	case TMPL_TYPE_ATTR_UNDEFINED:
	case TMPL_TYPE_REGEX_STRUCT:
		rad_assert(0 == 1);
		slen = -1;
		break;
	}

	if (slen < 0) return slen;


#if 0
	/*
	 *	If we're doing correct escapes, we may have to re-parse the string.
	 *	If the string is from another expansion, it needs re-parsing.
	 *	Or, if it's from a "string" attribute, it needs re-parsing.
	 *	Integers, IP addresses, etc. don't need re-parsing.
	 */
	if (cf_new_escape && (vpt->type != TMPL_TYPE_ATTR)) {
	     	value_data_t	vd;
	     	int		ret;

		PW_TYPE type = PW_TYPE_STRING;

		slen = value_data_from_str(ctx, &vd, &type, NULL, *out, slen, '"');
		talloc_free(*out);	/* free the old value */
		*out = vd.ptr;
	}
#endif

	if (vpt->type == TMPL_TYPE_XLAT_STRUCT) {
		RDEBUG2("EXPAND %s", vpt->name); /* xlat_struct doesn't do this */
		RDEBUG2("   --> %s", buff);
	}

	return slen;
}

/** Expand a template to a string, allocing a new buffer to hold the string
 *
 * The intended use of #tmpl_expand and #tmpl_aexpand is for modules to easily convert a #vp_tmpl_t
 * provided by the conf parser, into a usable value.
 * The value returned should be raw and undoctored for #PW_TYPE_STRING and #PW_TYPE_OCTETS types,
 * and the printable (string) version of the data for all others.
 *
 * This function will always duplicate values, whereas #tmpl_expand may return a pointer to an
 * existing buffer.
 *
 * @note This function is used where raw string values are needed, which may mean the string
 *	returned may be binary data or contain unprintable chars. #fr_prints or #fr_aprints should
 *	be used before using these values in debug statements. #is_printable can be used to check
 *	if the string only contains printable chars.
 *
 * @note The type (char or uint8_t) can be obtained with talloc_get_type, and may be used as a
 *	hint as to how to process or print the data.
 *
 * @param ctx to allocate new buffer in.
 * @param out Where to write pointer to the new buffer.
 * @param request Current request.
 * @param vpt to expand. Must be one of the following types:
 *	- #TMPL_TYPE_LITERAL
 *	- #TMPL_TYPE_EXEC
 *	- #TMPL_TYPE_XLAT
 *	- #TMPL_TYPE_XLAT_STRUCT
 *	- #TMPL_TYPE_ATTR
 *	- #TMPL_TYPE_DATA
 * @param escape xlat escape function (only used for xlat types).
 * @param escape_ctx xlat escape function data (only used for xlat types).
 * @return
 *	- -1 on failure.
 *	- The length of data written to buff, or pointed to by out.
 */
ssize_t tmpl_aexpand(TALLOC_CTX *ctx, char **out, REQUEST *request, vp_tmpl_t const *vpt,
		     xlat_escape_t escape, void *escape_ctx)
{
	VALUE_PAIR *vp;
	ssize_t slen = -1;	/* quiet compiler */

	rad_assert(vpt->type != TMPL_TYPE_LIST);

	VERIFY_TMPL(vpt);

	*out = NULL;

	switch (vpt->type) {
	case TMPL_TYPE_LITERAL:
		RDEBUG4("EXPAND TMPL LITERAL");
		*out = talloc_bstrndup(ctx, vpt->name, vpt->len);
		return vpt->len;

	case TMPL_TYPE_EXEC:
	{
		char *buff = NULL;

		RDEBUG4("EXPAND TMPL EXEC");
		buff = talloc_array(ctx, char, 1024);
		if (radius_exec_program(request, buff, 1024, NULL, request, vpt->name, NULL,
					true, false, EXEC_TIMEOUT) != 0) {
			TALLOC_FREE(buff);
			return -1;
		}
		slen = strlen(buff);
		*out = buff;
	}
		break;

	case TMPL_TYPE_XLAT:
		RDEBUG4("EXPAND TMPL XLAT");
		/* Error in expansion, this is distinct from zero length expansion */
		slen = radius_axlat(out, request, vpt->name, escape, escape_ctx);
		if (slen < 0) {
			rad_assert(!*out);
			return slen;
		}
		rad_assert(*out);
		slen = strlen(*out);
		break;

	case TMPL_TYPE_XLAT_STRUCT:
		RDEBUG4("EXPAND TMPL XLAT STRUCT");
		/* Error in expansion, this is distinct from zero length expansion */
		slen = radius_axlat_struct(out, request, vpt->tmpl_xlat, escape, escape_ctx);
		if (slen < 0) {
			rad_assert(!*out);
			return slen;
		}
		slen = strlen(*out);
		break;

	case TMPL_TYPE_ATTR:
	{
		int ret;

		RDEBUG4("EXPAND TMPL ATTR");
		ret = tmpl_find_vp(&vp, request, vpt);
		if (ret < 0) return -2;

		switch (vpt->tmpl_da->type) {
		case PW_TYPE_STRING:
			*out = talloc_bstrndup(ctx, vp->vp_strvalue, vp->vp_length);
			if (!*out) return -1;
			slen = vp->vp_length;
			break;

		case PW_TYPE_OCTETS:
			*out = talloc_memdup(ctx, vp->vp_octets, vp->vp_length);
			if (!*out) return -1;
			slen = vp->vp_length;
			break;

		default:
			*out = vp_aprints_value(ctx, vp, '\0');
			if (!*out) return -1;
			slen = talloc_array_length(*out) - 1;
			break;
		}
	}
		break;

	case TMPL_TYPE_DATA:
	{
		RDEBUG4("EXPAND TMPL DATA");

		switch (vpt->tmpl_data_type) {
		case PW_TYPE_STRING:
			*out = talloc_bstrndup(ctx, vpt->tmpl_data_value.strvalue, vpt->tmpl_data_length);
			if (!*out) return -1;
			slen = vpt->tmpl_data_length;
			break;

		case PW_TYPE_OCTETS:
			*out = talloc_memdup(ctx, vpt->tmpl_data_value.octets, vpt->tmpl_data_length);
			if (!*out) return -1;
			slen = vpt->tmpl_data_length;
			break;

		default:
			*out = value_data_aprints(ctx, vpt->tmpl_data_type, NULL, &vpt->tmpl_data_value, vpt->tmpl_data_length, '\0');
			if (!*out) return -1;
			slen = talloc_array_length(*out) - 1;
			break;
		}
	}
		break;

	/*
	 *	We should never be expanding these.
	 */
	case TMPL_TYPE_UNKNOWN:
	case TMPL_TYPE_NULL:
	case TMPL_TYPE_LIST:
	case TMPL_TYPE_REGEX:
	case TMPL_TYPE_ATTR_UNDEFINED:
	case TMPL_TYPE_REGEX_STRUCT:
		rad_assert(0 == 1);
		slen = -1;
		break;
	}

	if (slen < 0) return slen;

	/*
	 *	If we're doing correct escapes, we may have to re-parse the string.
	 *	If the string is from another expansion, it needs re-parsing.
	 *	Or, if it's from a "string" attribute, it needs re-parsing.
	 *	Integers, IP addresses, etc. don't need re-parsing.
	 */
	if (cf_new_escape && (vpt->type != TMPL_TYPE_ATTR)) {
	     	value_data_t	vd;

		PW_TYPE type = PW_TYPE_STRING;

		slen = value_data_from_str(ctx, &vd, &type, NULL, *out, slen, '"');
		talloc_free(*out);	/* free the old value */
		*out = vd.ptr;
	}

	if (vpt->type == TMPL_TYPE_XLAT_STRUCT) {
		RDEBUG2("EXPAND %s", vpt->name); /* xlat_struct doesn't do this */
		RDEBUG2("   --> %s", *out);
	}

	return slen;
}

/** Print a #vp_tmpl_t to a string
 *
 * @param[out] out Where to write the presentation format #vp_tmpl_t string.
 * @param[in] outlen Size of output buffer.
 * @param[in] vpt to print
 * @param[in] values Used for integer attributes only. #DICT_ATTR to use when mapping integer
 *	values to strings.
 * @return the size of the string written to the output buffer.
 */
size_t tmpl_prints(char *out, size_t outlen, vp_tmpl_t const *vpt, DICT_ATTR const *values)
{
	size_t len;
	char c;
	char const *p;
	char *q = out;

	if (!vpt) {
		*out = '\0';
		return 0;
	}

	VERIFY_TMPL(vpt);

	switch (vpt->type) {
	default:
		return 0;

	case TMPL_TYPE_REGEX:
	case TMPL_TYPE_REGEX_STRUCT:
		c = '/';
		break;

	case TMPL_TYPE_XLAT:
	case TMPL_TYPE_XLAT_STRUCT:
		c = '"';
		break;
	case TMPL_TYPE_LITERAL:	/* single-quoted or bare word */
		/*
		 *	Hack
		 */
		for (p = vpt->name; *p != '\0'; p++) {
			if (*p == ' ') break;
			if (*p == '\'') break;
			if (!dict_attr_allowed_chars[(int) *p]) break;
		}

		if (!*p) {
			strlcpy(out, vpt->name, outlen);
			return strlen(out);
		}

		c = vpt->quote;
		break;

	case TMPL_TYPE_EXEC:
		c = '`';
		break;

	case TMPL_TYPE_LIST:
		out[0] = '&';
		if (vpt->tmpl_request == REQUEST_CURRENT) {
			snprintf(out + 1, outlen - 1, "%s:",
				 fr_int2str(pair_lists, vpt->tmpl_list, ""));
		} else {
			snprintf(out + 1, outlen - 1, "%s.%s:",
				 fr_int2str(request_refs, vpt->tmpl_request, ""),
				 fr_int2str(pair_lists, vpt->tmpl_list, ""));
		}
		len = strlen(out);
		goto attr_inst_tag;

	case TMPL_TYPE_ATTR:
		out[0] = '&';
		if (vpt->tmpl_request == REQUEST_CURRENT) {
			if (vpt->tmpl_list == PAIR_LIST_REQUEST) {
				strlcpy(out + 1, vpt->tmpl_da->name, outlen - 1);
			} else {
				snprintf(out + 1, outlen - 1, "%s:%s",
					 fr_int2str(pair_lists, vpt->tmpl_list, ""),
					 vpt->tmpl_da->name);
			}

		} else {
			snprintf(out + 1, outlen - 1, "%s.%s:%s",
				 fr_int2str(request_refs, vpt->tmpl_request, ""),
				 fr_int2str(pair_lists, vpt->tmpl_list, ""),
				 vpt->tmpl_da->name);
		}

		len = strlen(out);

	attr_inst_tag:
		if ((vpt->tmpl_tag == TAG_ANY) && (vpt->tmpl_num == NUM_ANY)) return len;

		q = out + len;
		outlen -= len;

		if (vpt->tmpl_tag != TAG_ANY) {
			snprintf(q, outlen, ":%d", vpt->tmpl_tag);
			len = strlen(q);
			q += len;
			outlen -= len;
		}

		switch (vpt->tmpl_num) {
		case NUM_ANY:
			break;

		case NUM_ALL:
			snprintf(q, outlen, "[*]");
			len = strlen(q);
			q += len;
			break;

		case NUM_COUNT:
			snprintf(q, outlen, "[#]");
			len = strlen(q);
			q += len;
			break;

		case NUM_LAST:
			snprintf(q, outlen, "[n]");
			len = strlen(q);
			q += len;
			break;

		default:
			snprintf(q, outlen, "[%i]", vpt->tmpl_num);
			len = strlen(q);
			q += len;
			break;
		}

		return (q - out);

	case TMPL_TYPE_ATTR_UNDEFINED:
		out[0] = '&';
		if (vpt->tmpl_request == REQUEST_CURRENT) {
			if (vpt->tmpl_list == PAIR_LIST_REQUEST) {
				strlcpy(out + 1, vpt->tmpl_unknown_name, outlen - 1);
			} else {
				snprintf(out + 1, outlen - 1, "%s:%s",
					 fr_int2str(pair_lists, vpt->tmpl_list, ""),
					 vpt->tmpl_unknown_name);
			}

		} else {
			snprintf(out + 1, outlen - 1, "%s.%s:%s",
				 fr_int2str(request_refs, vpt->tmpl_request, ""),
				 fr_int2str(pair_lists, vpt->tmpl_list, ""),
				 vpt->tmpl_unknown_name);
		}

		len = strlen(out);

		if (vpt->tmpl_num == NUM_ANY) {
			return len;
		}

		q = out + len;
		outlen -= len;

		if (vpt->tmpl_num != NUM_ANY) {
			snprintf(q, outlen, "[%i]", vpt->tmpl_num);
			len = strlen(q);
			q += len;
		}

		return (q - out);

	case TMPL_TYPE_DATA:
		return value_data_prints(out, outlen, vpt->tmpl_data_type, values, &vpt->tmpl_data_value,
					 vpt->tmpl_data_length, vpt->quote);
	}

	if (outlen <= 3) {
		*out = '\0';
		return 0;
	}

	*(q++) = c;

	/*
	 *	Print it with appropriate escaping
	 */
	if (cf_new_escape && (c == '/')) {
		len = fr_prints(q, outlen - 3, vpt->name, vpt->len, '\0');
	} else {
		len = fr_prints(q, outlen - 3, vpt->name, vpt->len, c);
	}

	q += len;
	*(q++) = c;
	*q = '\0';

	return q - out;
}

/** Initialise a #vp_cursor_t to the #VALUE_PAIR specified by a #vp_tmpl_t
 *
 * This makes iterating over the one or more #VALUE_PAIR specified by a #vp_tmpl_t
 * significantly easier.
 *
 * @param err May be NULL if no error code is required. Will be set to:
 *	- 0 on success.
 *	- -1 if no matching #VALUE_PAIR could be found.
 *	- -2 if list could not be found (doesn't exist in current #REQUEST).
 *	- -3 if context could not be found (no parent #REQUEST available).
 * @param cursor to store iterator state.
 * @param request The current #REQUEST.
 * @param vpt specifying the #VALUE_PAIR type/tag or list to iterate over.
 * @return the first #VALUE_PAIR specified by the #vp_tmpl_t, or NULL if no matching
 *	#VALUE_PAIR found, and NULL on error.
 *
 * @see tmpl_cursor_next
 */
VALUE_PAIR *tmpl_cursor_init(int *err, vp_cursor_t *cursor, REQUEST *request, vp_tmpl_t const *vpt)
{
	VALUE_PAIR **vps, *vp = NULL;
	int num;

	VERIFY_TMPL(vpt);

	rad_assert((vpt->type == TMPL_TYPE_ATTR) || (vpt->type == TMPL_TYPE_LIST));

	if (err) *err = 0;

	if (radius_request(&request, vpt->tmpl_request) < 0) {
		if (err) *err = -3;
		return NULL;
	}
	vps = radius_list(request, vpt->tmpl_list);
	if (!vps) {
		if (err) *err = -2;
		return NULL;
	}
	(void) fr_cursor_init(cursor, vps);

	switch (vpt->type) {
	/*
	 *	May not may not be found, but it *is* a known name.
	 */
	case TMPL_TYPE_ATTR:
		switch (vpt->tmpl_num) {
		case NUM_ANY:
			vp = fr_cursor_next_by_da(cursor, vpt->tmpl_da, vpt->tmpl_tag);
			if (!vp) {
				if (err) *err = -1;
				return NULL;
			}
			VERIFY_VP(vp);
			return vp;

		/*
		 *	Get the last instance of a VALUE_PAIR.
		 */
		case NUM_LAST:
		{
			VALUE_PAIR *last = NULL;

			while ((vp = fr_cursor_next_by_da(cursor, vpt->tmpl_da, vpt->tmpl_tag))) {
				VERIFY_VP(vp);
				last = vp;
			}
			VERIFY_VP(last);
			if (!last) break;
			return last;
		}

		/*
		 *	Callers expect NUM_COUNT to setup the cursor to point
		 *	to the first attribute in the list we're meant to be
		 *	counting.
		 *
		 *	It does not produce a virtual attribute containing the
		 *	total number of attributes.
		 */
		case NUM_COUNT:
			return fr_cursor_next_by_da(cursor, vpt->tmpl_da, vpt->tmpl_tag);

		default:
			num = vpt->tmpl_num;
			while ((vp = fr_cursor_next_by_da(cursor, vpt->tmpl_da, vpt->tmpl_tag))) {
				VERIFY_VP(vp);
				if (num-- <= 0) return vp;
			}
			break;
		}

		if (err) *err = -1;
		return NULL;

	case TMPL_TYPE_LIST:
		switch (vpt->tmpl_num) {
		case NUM_COUNT:
		case NUM_ANY:
		case NUM_ALL:
			vp = fr_cursor_init(cursor, vps);
			if (!vp) {
				if (err) *err = -1;
				return NULL;
			}
			VERIFY_VP(vp);
			return vp;

		/*
		 *	Get the last instance of a VALUE_PAIR.
		 */
		case NUM_LAST:
		{
			VALUE_PAIR *last = NULL;

			for (vp = fr_cursor_init(cursor, vps);
			     vp;
			     vp = fr_cursor_next(cursor)) {
				VERIFY_VP(vp);
				last = vp;
			}
			if (!last) break;
			VERIFY_VP(last);
			return last;
		}

		default:
			num = vpt->tmpl_num;
			for (vp = fr_cursor_init(cursor, vps);
			     vp;
			     vp = fr_cursor_next(cursor)) {
				VERIFY_VP(vp);
				if (num-- <= 0) return vp;
			}
			break;
		}

		break;

	default:
		rad_assert(0);
	}

	return vp;
}

/** Returns the next #VALUE_PAIR specified by vpt
 *
 * @param cursor initialised with #tmpl_cursor_init.
 * @param vpt specifying the #VALUE_PAIR type/tag to iterate over.
 *	Must be one of the following types:
 *	- #TMPL_TYPE_LIST
 *	- #TMPL_TYPE_ATTR
 * @return NULL if no more matching #VALUE_PAIR of the specified type/tag are found.
 */
VALUE_PAIR *tmpl_cursor_next(vp_cursor_t *cursor, vp_tmpl_t const *vpt)
{
	rad_assert((vpt->type == TMPL_TYPE_ATTR) || (vpt->type == TMPL_TYPE_LIST));

	VERIFY_TMPL(vpt);

	switch (vpt->type) {
	/*
	 *	May not may not be found, but it *is* a known name.
	 */
	case TMPL_TYPE_ATTR:
		switch (vpt->tmpl_num) {
		default:
			return NULL;

		case NUM_ALL:
		case NUM_COUNT:	/* This cursor is being used to count matching attrs */
			break;
		}
		return fr_cursor_next_by_da(cursor, vpt->tmpl_da, vpt->tmpl_tag);

	case TMPL_TYPE_LIST:
		switch (vpt->tmpl_num) {
		default:
			return NULL;

		case NUM_ALL:
		case NUM_COUNT:	/* This cursor is being used to count matching attrs */
			break;
		}
		return fr_cursor_next(cursor);

	default:
		rad_assert(0);
		return NULL;	/* Older versions of GCC flag the lack of return as an error */
	}
}

/** Copy pairs matching a #vp_tmpl_t in the current #REQUEST
 *
 * @param ctx to allocate new #VALUE_PAIR in.
 * @param out Where to write the copied #VALUE_PAIR (s).
 * @param request The current #REQUEST.
 * @param vpt specifying the #VALUE_PAIR type/tag or list to copy.
 *	Must be one of the following types:
 *	- #TMPL_TYPE_LIST
 *	- #TMPL_TYPE_ATTR
 * @return
 *	- -1 if no matching #VALUE_PAIR could be found.
 *	- -2 if list could not be found (doesn't exist in current #REQUEST).
 *	- -3 if context could not be found (no parent #REQUEST available).
 *	- -4 on memory allocation error.
 */
int tmpl_copy_vps(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_tmpl_t const *vpt)
{
	VALUE_PAIR *vp;
	vp_cursor_t from, to;

	VERIFY_TMPL(vpt);

	int err;

	rad_assert((vpt->type == TMPL_TYPE_ATTR) || (vpt->type == TMPL_TYPE_LIST));

	*out = NULL;

	fr_cursor_init(&to, out);

	for (vp = tmpl_cursor_init(&err, &from, request, vpt);
	     vp;
	     vp = tmpl_cursor_next(&from, vpt)) {
		vp = fr_pair_copy(ctx, vp);
		if (!vp) {
			fr_pair_list_free(out);
			return -4;
		}
		fr_cursor_insert(&to, vp);
	}

	return err;
}

/** Returns the first VP matching a #vp_tmpl_t
 *
 * @param out where to write the retrieved vp.
 * @param request The current #REQUEST.
 * @param vpt specifying the #VALUE_PAIR type/tag to find.
 *	Must be one of the following types:
 *	- #TMPL_TYPE_LIST
 *	- #TMPL_TYPE_ATTR
 * @return
 *	- -1 if no matching #VALUE_PAIR could be found.
 *	- -2 if list could not be found (doesn't exist in current #REQUEST).
 *	- -3 if context could not be found (no parent #REQUEST available).
 */
int tmpl_find_vp(VALUE_PAIR **out, REQUEST *request, vp_tmpl_t const *vpt)
{
	vp_cursor_t cursor;
	VALUE_PAIR *vp;

	VERIFY_TMPL(vpt);

	int err;

	vp = tmpl_cursor_init(&err, &cursor, request, vpt);
	if (out) *out = vp;

	return err;
}
/* @} **/

#ifdef WITH_VERIFY_PTR
/** Used to check whether areas of a vp_tmpl_t are zeroed out
 *
 * @param ptr Offset to begin checking at.
 * @param len How many bytes to check.
 * @return pointer to the first non-zero byte, or NULL if all bytes were zero.
 */
static uint8_t const *not_zeroed(uint8_t const *ptr, size_t len)
{
	size_t i;

	for (i = 0; i < len; i++) {
		if (ptr[i] != 0x00) return ptr + i;
	}

	return NULL;
}
#define CHECK_ZEROED(_x) not_zeroed((uint8_t const *)&_x + sizeof(_x), sizeof(vpt->data) - sizeof(_x))

/** Verify fields of a vp_tmpl_t make sense
 *
 * @note If the #vp_tmpl_t is invalid, causes the server to exit.
 *
 * @param file obtained with __FILE__.
 * @param line obtained with __LINE__.
 * @param vpt to check.
 */
void tmpl_verify(char const *file, int line, vp_tmpl_t const *vpt)
{
	rad_assert(vpt);

	if (vpt->type == TMPL_TYPE_UNKNOWN) {
		FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: vp_tmpl_t type was "
			     "TMPL_TYPE_UNKNOWN (uninitialised)", file, line);
		fr_assert(0);
		fr_exit_now(1);
	}

	if (vpt->type > TMPL_TYPE_NULL) {
		FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: vp_tmpl_t type was %i "
			     "(outside range of tmpl_names)", file, line, vpt->type);
		fr_assert(0);
		fr_exit_now(1);
	}

	/*
	 *  Do a memcmp of the bytes after where the space allocated for
	 *  the union member should have ended and the end of the union.
	 *  These should always be zero if the union has been initialised
	 *  properly.
	 *
	 *  If they're still all zero, do TMPL_TYPE specific checks.
	 */
	switch (vpt->type) {
	case TMPL_TYPE_NULL:
		if (not_zeroed((uint8_t const *)&vpt->data, sizeof(vpt->data))) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_NULL "
				     "has non-zero bytes in its data union", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}
		break;

	case TMPL_TYPE_LITERAL:
		if (not_zeroed((uint8_t const *)&vpt->data, sizeof(vpt->data))) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_LITERAL "
				     "has non-zero bytes in its data union", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}
		break;

	case TMPL_TYPE_XLAT:
	case TMPL_TYPE_XLAT_STRUCT:
		break;

/* @todo When regexes get converted to xlat the flags field of the regex union is used
	case TMPL_TYPE_XLAT:
		if (not_zeroed((uint8_t const *)&vpt->data, sizeof(vpt->data))) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_XLAT "
				     "has non-zero bytes in its data union", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}
		break;

	case TMPL_TYPE_XLAT_STRUCT:
		if (CHECK_ZEROED(vpt->data.xlat)) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_XLAT_STRUCT "
				     "has non-zero bytes after the data.xlat pointer in the union", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}
		break;
*/

	case TMPL_TYPE_EXEC:
		if (not_zeroed((uint8_t const *)&vpt->data, sizeof(vpt->data))) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_EXEC "
				     "has non-zero bytes in its data union", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}
		break;

	case TMPL_TYPE_ATTR_UNDEFINED:
		rad_assert(vpt->tmpl_da == NULL);
		break;

	case TMPL_TYPE_ATTR:
		if (CHECK_ZEROED(vpt->data.attribute)) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_ATTR "
				     "has non-zero bytes after the data.attribute struct in the union",
				     file, line);
			fr_assert(0);
			fr_exit_now(1);
		}

		if (vpt->tmpl_da->flags.is_unknown) {
			if (vpt->tmpl_da != (DICT_ATTR const *)&vpt->data.attribute.unknown.da) {
				FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_ATTR "
					     "da is marked as unknown, but does not point to the template's "
					     "unknown da buffer", file, line);
				fr_assert(0);
				fr_exit_now(1);
			}

		} else {
			DICT_ATTR const *da;

			/*
			 *	Attribute may be present with multiple names
			 */
			da = dict_attrbyname(vpt->tmpl_da->name);
			if (!da) {
				FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_ATTR "
					     "attribute \"%s\" (%s) not found in global dictionary",
					     file, line, vpt->tmpl_da->name,
					     fr_int2str(dict_attr_types, vpt->tmpl_da->type, "<INVALID>"));
				fr_assert(0);
				fr_exit_now(1);
			}

			if ((da->type == PW_TYPE_COMBO_IP_ADDR) && (da->type != vpt->tmpl_da->type)) {
				da = dict_attrbytype(vpt->tmpl_da->attr, vpt->tmpl_da->vendor, vpt->tmpl_da->type);
				if (!da) {
					FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_ATTR "
						     "attribute \"%s\" variant (%s) not found in global dictionary",
						     file, line, vpt->tmpl_da->name,
						     fr_int2str(dict_attr_types, vpt->tmpl_da->type, "<INVALID>"));
					fr_assert(0);
					fr_exit_now(1);
				}
			}

			if (da != vpt->tmpl_da) {
				FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_ATTR "
					     "dictionary pointer %p \"%s\" (%s) "
					     "and global dictionary pointer %p \"%s\" (%s) differ",
					     file, line,
					     vpt->tmpl_da, vpt->tmpl_da->name,
					     fr_int2str(dict_attr_types, vpt->tmpl_da->type, "<INVALID>"),
					     da, da->name,
					     fr_int2str(dict_attr_types, da->type, "<INVALID>"));
				fr_assert(0);
				fr_exit_now(1);
			}
		}
		break;

	case TMPL_TYPE_LIST:
		if (CHECK_ZEROED(vpt->data.attribute)) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_LIST"
				     "has non-zero bytes after the data.attribute struct in the union", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}

		if (vpt->tmpl_da != NULL) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_LIST da pointer was NULL", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}
		break;

	case TMPL_TYPE_DATA:
		if (CHECK_ZEROED(vpt->data.literal)) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_DATA "
				     "has non-zero bytes after the data.literal struct in the union",
				     file, line);
			fr_assert(0);
			fr_exit_now(1);
		}

		if (vpt->tmpl_data_type == PW_TYPE_INVALID) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_DATA type was "
				     "PW_TYPE_INVALID (uninitialised)", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}

		if (vpt->tmpl_data_type >= PW_TYPE_MAX) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_DATA type was "
				     "%i (outside the range of PW_TYPEs)", file, line, vpt->tmpl_data_type);
			fr_assert(0);
			fr_exit_now(1);
		}
		/*
		 *	Unlike VALUE_PAIRs we can't guarantee that VALUE_PAIR_TMPL buffers will
		 *	be talloced. They may be allocated on the stack or in global variables.
		 */
		switch (vpt->tmpl_data_type) {
		case PW_TYPE_STRING:
		if (vpt->tmpl_data.vp_strvalue[vpt->tmpl_data_length] != '\0') {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_DATA char buffer not \\0 "
				     "terminated", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}
			break;

		case PW_TYPE_TLV:
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_DATA is of type TLV",
				     file, line);
			fr_assert(0);
			fr_exit_now(1);

		case PW_TYPE_OCTETS:
			break;

		default:
			if (vpt->tmpl_data_length == 0) {
				FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_DATA data pointer not NULL "
				             "but len field is zero", file, line);
				fr_assert(0);
				fr_exit_now(1);
			}
		}

		break;

	case TMPL_TYPE_REGEX:
		/*
		 *	iflag field is used for non compiled regexes too.
		 */
		if (CHECK_ZEROED(vpt->data.preg)) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_REGEX "
				     "has non-zero bytes after the data.preg struct in the union", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}

		if (vpt->tmpl_preg != NULL) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_REGEX "
				     "preg field was not nULL", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}

		if ((vpt->tmpl_iflag != true) && (vpt->tmpl_iflag != false)) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_REGEX "
				     "iflag field was neither true or false", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}

		if ((vpt->tmpl_mflag != true) && (vpt->tmpl_mflag != false)) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_REGEX "
				     "mflag field was neither true or false", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}

		break;

	case TMPL_TYPE_REGEX_STRUCT:
		if (CHECK_ZEROED(vpt->data.preg)) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_REGEX_STRUCT "
				     "has non-zero bytes after the data.preg struct in the union", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}

		if (vpt->tmpl_preg == NULL) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_REGEX_STRUCT "
				     "comp field was NULL", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}

		if ((vpt->tmpl_iflag != true) && (vpt->tmpl_iflag != false)) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_REGEX_STRUCT "
				     "iflag field was neither true or false", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}

		if ((vpt->tmpl_mflag != true) && (vpt->tmpl_mflag != false)) {
			FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_REGEX "
				     "mflag field was neither true or false", file, line);
			fr_assert(0);
			fr_exit_now(1);
		}
		break;

	case TMPL_TYPE_UNKNOWN:
		rad_assert(0);
	}
}
#endif
