From 50b37d4a27d3295a29afca2286f1a5a086142cec Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 11:49:46 +0200 Subject: Adding upstream version 3.2.1+dfsg. Signed-off-by: Daniel Baumann --- src/main/tmpl.c | 2399 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2399 insertions(+) create mode 100644 src/main/tmpl.c (limited to 'src/main/tmpl.c') diff --git a/src/main/tmpl.c b/src/main/tmpl.c new file mode 100644 index 0000000..abcd768 --- /dev/null +++ b/src/main/tmpl.c @@ -0,0 +1,2399 @@ +/* + * 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$ + * + * @brief #VALUE_PAIR template functions + * @file main/tmpl.c + * + * @ingroup AVP + * + * @copyright 2014-2015 The FreeRADIUS server project + */ +RCSID("$Id$") + +#include +#include + +#include + +/** 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((int) *d)) { + while (isdigit((int) *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, "")); + + 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; + + 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; + } + + /* + * 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, "")); + 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, "")); + 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, "")); + 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, "")); + 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, ""), + da, da->name, + fr_int2str(dict_attr_types, da->type, "")); + 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 -- cgit v1.2.3