summaryrefslogtreecommitdiffstats
path: root/src/lib/pair.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:49:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:49:46 +0000
commit50b37d4a27d3295a29afca2286f1a5a086142cec (patch)
tree9212f763934ee090ef72d823f559f52ce387f268 /src/lib/pair.c
parentInitial commit. (diff)
downloadfreeradius-50b37d4a27d3295a29afca2286f1a5a086142cec.tar.xz
freeradius-50b37d4a27d3295a29afca2286f1a5a086142cec.zip
Adding upstream version 3.2.1+dfsg.upstream/3.2.1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/pair.c')
-rw-r--r--src/lib/pair.c2518
1 files changed, 2518 insertions, 0 deletions
diff --git a/src/lib/pair.c b/src/lib/pair.c
new file mode 100644
index 0000000..898e1e8
--- /dev/null
+++ b/src/lib/pair.c
@@ -0,0 +1,2518 @@
+/*
+ * pair.c Functions to handle VALUE_PAIRs
+ *
+ * Version: $Id$
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2000,2006 The FreeRADIUS server project
+ */
+
+RCSID("$Id$")
+
+#include <freeradius-devel/libradius.h>
+#include <freeradius-devel/regex.h>
+
+#include <ctype.h>
+
+/** Free a VALUE_PAIR
+ *
+ * @note Do not call directly, use talloc_free instead.
+ *
+ * @param vp to free.
+ * @return 0
+ */
+static int _fr_pair_free(VALUE_PAIR *vp) {
+#ifndef NDEBUG
+ vp->vp_integer = 0xf4eef4ee;
+#endif
+
+#ifdef TALLOC_DEBUG
+ talloc_report_depth_cb(NULL, 0, -1, fr_talloc_verify_cb, NULL);
+#endif
+ return 0;
+}
+
+VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx)
+{
+ VALUE_PAIR *vp;
+
+ vp = talloc_zero(ctx, VALUE_PAIR);
+ if (!vp) {
+ fr_strerror_printf("Out of memory");
+ return NULL;
+ }
+
+ vp->op = T_OP_EQ;
+ vp->tag = TAG_ANY;
+ vp->type = VT_NONE;
+
+ talloc_set_destructor(vp, _fr_pair_free);
+
+ return vp;
+}
+
+
+/** Dynamically allocate a new attribute
+ *
+ * Allocates a new attribute and a new dictionary attr if no DA is provided.
+ *
+ * @param[in] ctx for allocated memory, usually a pointer to a RADIUS_PACKET
+ * @param[in] da Specifies the dictionary attribute to build the VP from.
+ * @return a new value pair or NULL if an error occurred.
+ */
+VALUE_PAIR *fr_pair_afrom_da(TALLOC_CTX *ctx, DICT_ATTR const *da)
+{
+ VALUE_PAIR *vp;
+
+ /*
+ * Caller must specify a da else we don't know what the attribute type is.
+ */
+ if (!da) {
+ fr_strerror_printf("Invalid arguments");
+ return NULL;
+ }
+
+ vp = fr_pair_alloc(ctx);
+ if (!vp) {
+ fr_strerror_printf("Out of memory");
+ return NULL;
+ }
+
+ /*
+ * Use the 'da' to initialize more fields.
+ */
+ vp->da = da;
+ vp->vp_length = da->flags.length;
+
+ return vp;
+}
+
+/** Create a new valuepair
+ *
+ * If attr and vendor match a dictionary entry then a VP with that DICT_ATTR
+ * will be returned.
+ *
+ * If attr or vendor are uknown will call dict_attruknown to create a dynamic
+ * DICT_ATTR of PW_TYPE_OCTETS.
+ *
+ * Which type of DICT_ATTR the VALUE_PAIR was created with can be determined by
+ * checking @verbatim vp->da->flags.is_unknown @endverbatim.
+ *
+ * @param[in] ctx for allocated memory, usually a pointer to a RADIUS_PACKET
+ * @param[in] attr number.
+ * @param[in] vendor number.
+ * @return the new valuepair or NULL on error.
+ */
+VALUE_PAIR *fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int attr, unsigned int vendor)
+{
+ DICT_ATTR const *da;
+
+ da = dict_attrbyvalue(attr, vendor);
+ if (!da) return NULL;
+
+ return fr_pair_afrom_da(ctx, da);
+}
+
+/** Free memory used by a valuepair list.
+ *
+ * @todo TLV: needs to free all dependents of each VP freed.
+ */
+void fr_pair_list_free(VALUE_PAIR **vps)
+{
+ VALUE_PAIR *vp;
+ vp_cursor_t cursor;
+
+ if (!vps || !*vps) {
+ return;
+ }
+
+ for (vp = fr_cursor_init(&cursor, vps);
+ vp;
+ vp = fr_cursor_next(&cursor)) {
+ VERIFY_VP(vp);
+ talloc_free(vp);
+ }
+
+ *vps = NULL;
+}
+
+/** Mark malformed or unrecognised attributed as unknown
+ *
+ * @param vp to change DICT_ATTR of.
+ * @return 0 on success (or if already unknown) else -1 on error.
+ */
+int fr_pair_to_unknown(VALUE_PAIR *vp)
+{
+ DICT_ATTR const *da;
+
+ VERIFY_VP(vp);
+ if (vp->da->flags.is_unknown) {
+ return 0;
+ }
+
+ da = dict_unknown_afrom_fields(vp, vp->da->attr, vp->da->vendor);
+ if (!da) {
+ return -1;
+ }
+
+ vp->da = da;
+
+ return 0;
+}
+
+/** Find the pair with the matching DAs
+ *
+ */
+VALUE_PAIR *fr_pair_find_by_da(VALUE_PAIR *vp, DICT_ATTR const *da, int8_t tag)
+{
+ vp_cursor_t cursor;
+
+ if(!fr_assert(da)) {
+ return NULL;
+ }
+
+ (void) fr_cursor_init(&cursor, &vp);
+ return fr_cursor_next_by_da(&cursor, da, tag);
+}
+
+
+/** Find the pair with the matching attribute
+ *
+ * @todo should take DAs and do a pointer comparison.
+ */
+VALUE_PAIR *fr_pair_find_by_num(VALUE_PAIR *vp, unsigned int attr, unsigned int vendor, int8_t tag)
+{
+ vp_cursor_t cursor;
+
+ /* List head may be NULL if it contains no VPs */
+ if (!vp) return NULL;
+
+ VERIFY_LIST(vp, "");
+
+ (void) fr_cursor_init(&cursor, &vp);
+ return fr_cursor_next_by_num(&cursor, attr, vendor, tag);
+}
+
+/** Delete matching pairs
+ *
+ * Delete matching pairs from the attribute list.
+ *
+ * @param[in,out] first VP in list.
+ * @param[in] attr to match.
+ * @param[in] vendor to match.
+ * @param[in] tag to match. TAG_ANY matches any tag, TAG_NONE matches tagless VPs.
+ *
+ * @todo should take DAs and do a point comparison.
+ */
+void fr_pair_delete_by_num(VALUE_PAIR **first, unsigned int attr, unsigned int vendor, int8_t tag)
+{
+ VALUE_PAIR *i, *next;
+ VALUE_PAIR **last = first;
+
+ for(i = *first; i; i = next) {
+ VERIFY_VP(i);
+ next = i->next;
+ if ((i->da->attr == attr) && (i->da->vendor == vendor) &&
+ (!i->da->flags.has_tag || TAG_EQ(tag, i->tag))) {
+ *last = next;
+ talloc_free(i);
+ } else {
+ last = &i->next;
+ }
+ }
+}
+
+/** Delete matching pairs by da
+ *
+ * Delete matching pairs from the attribute list.
+ *
+ * @param[in,out] first VP in list.
+ * @param[in] da to match.
+ */
+void fr_pair_delete_by_da(VALUE_PAIR **first, DICT_ATTR const *da)
+{
+ VALUE_PAIR *i, *next;
+ VALUE_PAIR **last = first;
+
+ for(i = *first; i; i = next) {
+ VERIFY_VP(i);
+ next = i->next;
+ if (i->da == da) {
+ *last = next;
+ talloc_free(i);
+ } else {
+ last = &i->next;
+ }
+ }
+}
+
+/** Add a VP to the end of the list.
+ *
+ * Locates the end of 'first', and links an additional VP 'add' at the end.
+ *
+ * @param[in] first VP in linked list. Will add new VP to the end of this list.
+ * @param[in] add VP to add to list.
+ */
+void fr_pair_add(VALUE_PAIR **first, VALUE_PAIR *add)
+{
+ VALUE_PAIR *i;
+
+ if (!add) return;
+
+ VERIFY_VP(add);
+
+ if (*first == NULL) {
+ *first = add;
+ return;
+ }
+
+ for (i = *first; i->next; i = i->next) {
+#ifdef WITH_VERIFY_PTR
+ VERIFY_VP(i);
+ /*
+ * The same VP should never by added multiple times
+ * to the same list.
+ */
+ fr_assert(i != add);
+#endif
+ }
+
+ i->next = add;
+}
+
+/** Add a VP to the start of the list.
+ *
+ * Links the new VP to 'first', then points 'first' at the new VP.
+ *
+ * @param[in] first VP in linked list. Will add new VP to the start of this list.
+ * @param[in] add VP to add to list.
+ */
+void fr_pair_prepend(VALUE_PAIR **first, VALUE_PAIR *add)
+{
+ VALUE_PAIR *i;
+
+ if (!add) return;
+
+ VERIFY_VP(add);
+
+ if (*first == NULL) {
+ *first = add;
+ return;
+ }
+
+ /*
+ * Find the end of the list to be prepended
+ */
+ for (i = add; i->next; i = i->next) {
+#ifdef WITH_VERIFY_PTR
+ VERIFY_VP(i);
+ /*
+ * The same VP should never by added multiple times
+ * to the same list.
+ */
+ fr_assert(*first != i);
+#endif
+ }
+ i->next = *first;
+ *first = add;
+}
+
+/** Replace all matching VPs
+ *
+ * Walks over 'first', and replaces the first VP that matches 'replace'.
+ *
+ * @note Memory used by the VP being replaced will be freed.
+ * @note Will not work with unknown attributes.
+ *
+ * @param[in,out] first VP in linked list. Will search and replace in this list.
+ * @param[in] replace VP to replace.
+ */
+void fr_pair_replace(VALUE_PAIR **first, VALUE_PAIR *replace)
+{
+ VALUE_PAIR *i, *next;
+ VALUE_PAIR **prev = first;
+
+ VERIFY_VP(replace);
+
+ if (*first == NULL) {
+ *first = replace;
+ return;
+ }
+
+ /*
+ * Not an empty list, so find item if it is there, and
+ * replace it. Note, we always replace the first one, and
+ * we ignore any others that might exist.
+ */
+ for(i = *first; i; i = next) {
+ VERIFY_VP(i);
+ next = i->next;
+
+ /*
+ * Found the first attribute, replace it,
+ * and return.
+ */
+ if ((i->da == replace->da) && (!i->da->flags.has_tag || TAG_EQ(replace->tag, i->tag))) {
+ *prev = replace;
+
+ /*
+ * Should really assert that replace->next == NULL
+ */
+ replace->next = next;
+ talloc_free(i);
+ return;
+ }
+
+ /*
+ * Point to where the attribute should go.
+ */
+ prev = &i->next;
+ }
+
+ /*
+ * If we got here, we didn't find anything to replace, so
+ * stopped at the last item, which we just append to.
+ */
+ *prev = replace;
+}
+
+int8_t fr_pair_cmp_by_da_tag(void const *a, void const *b)
+{
+ VALUE_PAIR const *my_a = a;
+ VALUE_PAIR const *my_b = b;
+
+ VERIFY_VP(my_a);
+ VERIFY_VP(my_b);
+
+ uint8_t cmp;
+
+ cmp = fr_pointer_cmp(my_a->da, my_b->da);
+ if (cmp != 0) return cmp;
+
+ if (my_a->tag < my_b->tag) return -1;
+
+ if (my_a->tag > my_b->tag) return 1;
+
+ return 0;
+}
+
+static void fr_pair_list_sort_split(VALUE_PAIR *source, VALUE_PAIR **front, VALUE_PAIR **back)
+{
+ VALUE_PAIR *fast;
+ VALUE_PAIR *slow;
+
+ /*
+ * Stopping condition - no more elements left to split
+ */
+ if (!source || !source->next) {
+ *front = source;
+ *back = NULL;
+
+ return;
+ }
+
+ /*
+ * Fast advances twice as fast as slow, so when it gets to the end,
+ * slow will point to the middle of the linked list.
+ */
+ slow = source;
+ fast = source->next;
+
+ while (fast) {
+ fast = fast->next;
+ if (fast) {
+ slow = slow->next;
+ fast = fast->next;
+ }
+ }
+
+ *front = source;
+ *back = slow->next;
+ slow->next = NULL;
+}
+
+static VALUE_PAIR *fr_pair_list_sort_merge(VALUE_PAIR *a, VALUE_PAIR *b, fr_cmp_t cmp)
+{
+ VALUE_PAIR *result = NULL;
+
+ if (!a) return b;
+ if (!b) return a;
+
+ /*
+ * Compare the DICT_ATTRs and tags
+ */
+ if (cmp(a, b) <= 0) {
+ result = a;
+ result->next = fr_pair_list_sort_merge(a->next, b, cmp);
+ } else {
+ result = b;
+ result->next = fr_pair_list_sort_merge(a, b->next, cmp);
+ }
+
+ return result;
+}
+
+/** Sort a linked list of VALUE_PAIRs using merge sort
+ *
+ * @param[in,out] vps List of VALUE_PAIRs to sort.
+ * @param[in] cmp to sort with
+ */
+void fr_pair_list_sort(VALUE_PAIR **vps, fr_cmp_t cmp)
+{
+ VALUE_PAIR *head = *vps;
+ VALUE_PAIR *a;
+ VALUE_PAIR *b;
+
+ /*
+ * If there's 0-1 elements it must already be sorted.
+ */
+ if (!head || !head->next) {
+ return;
+ }
+
+ fr_pair_list_sort_split(head, &a, &b); /* Split into sublists */
+ fr_pair_list_sort(&a, cmp); /* Traverse left */
+ fr_pair_list_sort(&b, cmp); /* Traverse right */
+
+ /*
+ * merge the two sorted lists together
+ */
+ *vps = fr_pair_list_sort_merge(a, b, cmp);
+}
+
+/** Write an error to the library errorbuff detailing the mismatch
+ *
+ * Retrieve output with fr_strerror();
+ *
+ * @todo add thread specific talloc contexts.
+ *
+ * @param ctx a hack until we have thread specific talloc contexts.
+ * @param failed pair of attributes which didn't match.
+ */
+void fr_pair_validate_debug(TALLOC_CTX *ctx, VALUE_PAIR const *failed[2])
+{
+ VALUE_PAIR const *filter = failed[0];
+ VALUE_PAIR const *list = failed[1];
+
+ char *value, *str;
+
+ (void) fr_strerror(); /* Clear any existing messages */
+
+ if (!fr_assert(!(!filter && !list))) return;
+
+ if (!list) {
+ if (!filter) return;
+ fr_strerror_printf("Attribute \"%s\" not found in list", filter->da->name);
+ return;
+ }
+
+ if (!filter || (filter->da != list->da)) {
+ fr_strerror_printf("Attribute \"%s\" not found in filter", list->da->name);
+ return;
+ }
+
+ if (!TAG_EQ(filter->tag, list->tag)) {
+ fr_strerror_printf("Attribute \"%s\" tag \"%i\" didn't match filter tag \"%i\"",
+ list->da->name, list->tag, filter->tag);
+ return;
+ }
+
+
+ value = vp_aprints_value(ctx, list, '"');
+ str = vp_aprints(ctx, filter, '"');
+
+ fr_strerror_printf("Attribute value \"%s\" didn't match filter: %s", value, str);
+
+ talloc_free(str);
+ talloc_free(value);
+
+ return;
+}
+
+/** Uses fr_pair_cmp to verify all VALUE_PAIRs in list match the filter defined by check
+ *
+ * @note will sort both filter and list in place.
+ *
+ * @param failed pointer to an array to write the pointers of the filter/list attributes that didn't match.
+ * May be NULL.
+ * @param filter attributes to check list against.
+ * @param list attributes, probably a request or reply
+ */
+bool fr_pair_validate(VALUE_PAIR const *failed[2], VALUE_PAIR *filter, VALUE_PAIR *list)
+{
+ vp_cursor_t filter_cursor;
+ vp_cursor_t list_cursor;
+
+ VALUE_PAIR *check, *match;
+
+ if (!filter && !list) {
+ return true;
+ }
+
+ /*
+ * This allows us to verify the sets of validate and reply are equal
+ * i.e. we have a validate rule which matches every reply attribute.
+ *
+ * @todo this should be removed one we have sets and lists
+ */
+ fr_pair_list_sort(&filter, fr_pair_cmp_by_da_tag);
+ fr_pair_list_sort(&list, fr_pair_cmp_by_da_tag);
+
+ check = fr_cursor_init(&filter_cursor, &filter);
+ match = fr_cursor_init(&list_cursor, &list);
+ while (match || check) {
+ /*
+ * Lists are of different lengths
+ */
+ if (!match || !check) goto mismatch;
+
+ /*
+ * The lists are sorted, so if the first
+ * attributes aren't of the same type, then we're
+ * done.
+ */
+ if (!ATTRIBUTE_EQ(check, match)) goto mismatch;
+
+ /*
+ * They're of the same type, but don't have the
+ * same values. This is a problem.
+ *
+ * Note that the RFCs say that for attributes of
+ * the same type, order is important.
+ */
+ if (fr_pair_cmp(check, match) != 1) goto mismatch;
+
+ check = fr_cursor_next(&filter_cursor);
+ match = fr_cursor_next(&list_cursor);
+ }
+
+ return true;
+
+mismatch:
+ if (failed) {
+ failed[0] = check;
+ failed[1] = match;
+ }
+ return false;
+}
+
+/** Uses fr_pair_cmp to verify all VALUE_PAIRs in list match the filter defined by check
+ *
+ * @note will sort both filter and list in place.
+ *
+ * @param failed pointer to an array to write the pointers of the filter/list attributes that didn't match.
+ * May be NULL.
+ * @param filter attributes to check list against.
+ * @param list attributes, probably a request or reply
+ */
+bool fr_pair_validate_relaxed(VALUE_PAIR const *failed[2], VALUE_PAIR *filter, VALUE_PAIR *list)
+{
+ vp_cursor_t filter_cursor;
+ vp_cursor_t list_cursor;
+
+ VALUE_PAIR *check, *last_check = NULL, *match = NULL;
+
+ if (!filter && !list) {
+ return true;
+ }
+
+ /*
+ * This allows us to verify the sets of validate and reply are equal
+ * i.e. we have a validate rule which matches every reply attribute.
+ *
+ * @todo this should be removed one we have sets and lists
+ */
+ fr_pair_list_sort(&filter, fr_pair_cmp_by_da_tag);
+ fr_pair_list_sort(&list, fr_pair_cmp_by_da_tag);
+
+ fr_cursor_init(&list_cursor, &list);
+ for (check = fr_cursor_init(&filter_cursor, &filter);
+ check;
+ check = fr_cursor_next(&filter_cursor)) {
+ /*
+ * Were processing check attributes of a new type.
+ */
+ if (!ATTRIBUTE_EQ(last_check, check)) {
+ /*
+ * Record the start of the matching attributes in the pair list
+ * For every other operator we require the match to be present
+ */
+ match = fr_cursor_next_by_da(&list_cursor, check->da, check->tag);
+ if (!match) {
+ if (check->op == T_OP_CMP_FALSE) continue;
+ goto mismatch;
+ }
+
+ fr_cursor_init(&list_cursor, &match);
+ last_check = check;
+ }
+
+ /*
+ * Now iterate over all attributes of the same type.
+ */
+ for (match = fr_cursor_first(&list_cursor);
+ ATTRIBUTE_EQ(match, check);
+ match = fr_cursor_next(&list_cursor)) {
+ /*
+ * This attribute passed the filter
+ */
+ if (!fr_pair_cmp(check, match)) goto mismatch;
+ }
+ }
+
+ return true;
+
+mismatch:
+ if (failed) {
+ failed[0] = check;
+ failed[1] = match;
+ }
+ return false;
+}
+
+/** Copy a single valuepair
+ *
+ * Allocate a new valuepair and copy the da from the old vp.
+ *
+ * @param[in] ctx for talloc
+ * @param[in] vp to copy.
+ * @return a copy of the input VP or NULL on error.
+ */
+VALUE_PAIR *fr_pair_copy(TALLOC_CTX *ctx, VALUE_PAIR const *vp)
+{
+ VALUE_PAIR *n;
+
+ if (!vp) return NULL;
+
+ VERIFY_VP(vp);
+
+ n = fr_pair_afrom_da(ctx, vp->da);
+ if (!n) return NULL;
+
+ memcpy(n, vp, sizeof(*n));
+
+ /*
+ * If the DA is unknown, steal "n" to "ctx". This does
+ * nothing for "n", but will also copy the unknown "da".
+ */
+ if (n->da->flags.is_unknown) {
+ fr_pair_steal(ctx, n);
+ }
+
+ n->next = NULL;
+
+ /*
+ * If it's an xlat, copy the raw string and return early,
+ * so we don't pre-expand or otherwise mangle the VALUE_PAIR.
+ */
+ if (vp->type == VT_XLAT) {
+ n->value.xlat = talloc_typed_strdup(n, n->value.xlat);
+ return n;
+ }
+
+ switch (vp->da->type) {
+ case PW_TYPE_OCTETS:
+ n->vp_octets = NULL; /* else fr_pair_value_memcpy will free vp's value */
+ fr_pair_value_memcpy(n, vp->vp_octets, n->vp_length);
+ break;
+
+ case PW_TYPE_STRING:
+ n->vp_strvalue = NULL; /* else pairstrnpy will free vp's value */
+ fr_pair_value_bstrncpy(n, vp->vp_strvalue, n->vp_length);
+ break;
+
+ default:
+ break;
+ }
+
+ return n;
+}
+
+/** Copy a pairlist.
+ *
+ * Copy all pairs from 'from' regardless of tag, attribute or vendor.
+ *
+ * @param[in] ctx for new VALUE_PAIRs to be allocated in.
+ * @param[in] from whence to copy VALUE_PAIRs.
+ * @return the head of the new VALUE_PAIR list or NULL on error.
+ */
+VALUE_PAIR *fr_pair_list_copy(TALLOC_CTX *ctx, VALUE_PAIR *from)
+{
+ vp_cursor_t src, dst;
+
+ VALUE_PAIR *out = NULL, *vp;
+
+ fr_cursor_init(&dst, &out);
+ for (vp = fr_cursor_init(&src, &from);
+ vp;
+ vp = fr_cursor_next(&src)) {
+ VERIFY_VP(vp);
+ vp = fr_pair_copy(ctx, vp);
+ if (!vp) {
+ fr_pair_list_free(&out);
+ return NULL;
+ }
+ fr_cursor_insert(&dst, vp); /* fr_pair_list_copy sets next pointer to NULL */
+ }
+
+ return out;
+}
+
+/** Copy matching pairs
+ *
+ * Copy pairs of a matching attribute number, vendor number and tag from the
+ * the input list to a new list, and returns the head of this list.
+ *
+ * @param[in] ctx for talloc
+ * @param[in] from whence to copy VALUE_PAIRs.
+ * @param[in] attr to match. If attribute PW_VENDOR_SPECIFIC and vendor 0,
+ * will match (and therefore copy) only VSAs.
+ * If attribute 0 and vendor 0 will match (and therefore copy) all
+ * attributes.
+ * @param[in] vendor to match.
+ * @param[in] tag to match, TAG_ANY matches any tag, TAG_NONE matches tagless VPs.
+ * @return the head of the new VALUE_PAIR list or NULL on error.
+ */
+VALUE_PAIR *fr_pair_list_copy_by_num(TALLOC_CTX *ctx, VALUE_PAIR *from,
+ unsigned int attr, unsigned int vendor, int8_t tag)
+{
+ vp_cursor_t src, dst;
+
+ VALUE_PAIR *out = NULL, *vp;
+
+ fr_cursor_init(&dst, &out);
+ for (vp = fr_cursor_init(&src, &from);
+ vp;
+ vp = fr_cursor_next(&src)) {
+ VERIFY_VP(vp);
+
+ if (vp->da->flags.has_tag && !TAG_EQ(tag, vp->tag)) {
+ continue;
+ }
+
+ /*
+ * Attr/vendor of 0 means "move them all".
+ * It's better than "fr_pair_copy(foo,bar);bar=NULL"
+ */
+ if ((attr == 0) && (vendor == 0)) {
+ goto do_copy;
+ }
+
+ /*
+ * vendor=0, attr = PW_VENDOR_SPECIFIC means
+ * "match any vendor attribute".
+ */
+ if ((vendor == 0) && (attr == PW_VENDOR_SPECIFIC)) {
+ /*
+ * It's a VSA: copy it over.
+ */
+ if (vp->da->vendor != 0) goto do_copy;
+
+ /*
+ * It's Vendor-Specific: copy it over.
+ */
+ if (vp->da->attr == attr) goto do_copy;
+
+ /*
+ * It's not a VSA: ignore it.
+ */
+ continue;
+ }
+
+ if ((vp->da->attr != attr) || (vp->da->vendor != vendor)) {
+ continue;
+ }
+
+ do_copy:
+ vp = fr_pair_copy(ctx, vp);
+ if (!vp) {
+ fr_pair_list_free(&out);
+ return NULL;
+ }
+ fr_cursor_insert(&dst, vp);
+ }
+
+ return out;
+}
+
+/** Steal one VP
+ *
+ * @param[in] ctx to move VALUE_PAIR into
+ * @param[in] vp VALUE_PAIR to move into the new context.
+ */
+void fr_pair_steal(TALLOC_CTX *ctx, VALUE_PAIR *vp)
+{
+ (void) talloc_steal(ctx, vp);
+
+ /*
+ * The DA may be unknown. If we're stealing the VPs to a
+ * different context, copy the unknown DA. We use the VP
+ * as a context here instead of "ctx", so that when the
+ * VP is freed, so is the DA.
+ *
+ * Since we have no introspection into OTHER VPs using
+ * the same DA, we can't have multiple VPs use the same
+ * DA. So we might as well tie it to this VP.
+ */
+ if (vp->da->flags.is_unknown) {
+ DICT_ATTR *da;
+ char *p;
+ size_t size;
+
+ size = talloc_get_size(vp->da);
+
+ p = talloc_zero_array(vp, char, size);
+ da = (DICT_ATTR *) p;
+ talloc_set_type(p, DICT_ATTR);
+ memcpy(da, vp->da, size);
+ vp->da = da;
+ }
+}
+
+/** Move pairs from source list to destination list respecting operator
+ *
+ * @note This function does some additional magic that's probably not needed
+ * in most places. Consider using radius_pairmove in server code.
+ *
+ * @note fr_pair_list_free should be called on the head of the source list to free
+ * unmoved attributes (if they're no longer needed).
+ *
+ * @note Does not respect tags when matching.
+ *
+ * @param[in] ctx for talloc
+ * @param[in,out] to destination list.
+ * @param[in,out] from source list.
+ * @param[in] op operator for move.
+ *
+ * @see radius_pairmove
+ */
+void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, FR_TOKEN op)
+{
+ VALUE_PAIR *i, *found;
+ VALUE_PAIR *head_new, **tail_new;
+ VALUE_PAIR *head_prepend;
+ VALUE_PAIR **tail_from;
+
+ if (!to || !from || !*from) return;
+
+ /*
+ * We're editing the "to" list while we're adding new
+ * attributes to it. We don't want the new attributes to
+ * be edited, so we create an intermediate list to hold
+ * them during the editing process.
+ */
+ head_new = NULL;
+ tail_new = &head_new;
+
+ /*
+ * Any attributes that are requested to be prepended
+ * are added to a temporary list here
+ */
+ head_prepend = NULL;
+
+ /*
+ * We're looping over the "from" list, moving some
+ * attributes out, but leaving others in place.
+ */
+ tail_from = from;
+ while ((i = *tail_from) != NULL) {
+ VALUE_PAIR *j;
+
+ VERIFY_VP(i);
+
+ /*
+ * We never move Fall-Through.
+ */
+ if (!i->da->vendor && i->da->attr == PW_FALL_THROUGH) {
+ tail_from = &(i->next);
+ continue;
+ }
+
+ /*
+ * Unlike previous versions, we treat all other
+ * attributes as normal. i.e. there's no special
+ * treatment for passwords or Hint.
+ */
+
+ switch (i->op) {
+ /*
+ * Anything else are operators which
+ * shouldn't occur. We ignore them, and
+ * leave them in place.
+ */
+ default:
+ tail_from = &(i->next);
+ continue;
+
+ /*
+ * Add it to the "to" list, but only if
+ * it doesn't already exist.
+ */
+ case T_OP_EQ:
+ found = fr_pair_find_by_da(*to, i->da, TAG_ANY);
+ if (!found) goto do_add;
+
+ tail_from = &(i->next);
+ continue;
+
+ /*
+ * Add it to the "to" list, and delete any attribute
+ * of the same vendor/attr which already exists.
+ */
+ case T_OP_SET:
+ found = fr_pair_find_by_da(*to, i->da, TAG_ANY);
+ if (!found) goto do_add;
+
+ /*
+ * Do NOT call fr_pair_delete_by_num() here,
+ * due to issues with re-writing
+ * "request->username".
+ *
+ * Everybody calls fr_pair_move, and
+ * expects it to work. We can't
+ * update request->username here,
+ * so instead we over-write the
+ * vp that it's pointing to.
+ */
+ switch (found->da->type) {
+ default:
+ j = found->next;
+ memcpy(found, i, sizeof(*found));
+ found->next = j;
+ break;
+
+ case PW_TYPE_OCTETS:
+ fr_pair_value_memsteal(found, i->vp_octets);
+ i->vp_octets = NULL;
+ break;
+
+ case PW_TYPE_STRING:
+ fr_pair_value_strsteal(found, i->vp_strvalue);
+ i->vp_strvalue = NULL;
+ found->tag = i->tag;
+ break;
+ }
+
+ /*
+ * Delete *all* of the attributes
+ * of the same number.
+ */
+ fr_pair_delete_by_num(&found->next,
+ found->da->attr,
+ found->da->vendor, TAG_ANY);
+
+ /*
+ * Remove this attribute from the
+ * "from" list.
+ */
+ *tail_from = i->next;
+ i->next = NULL;
+ fr_pair_list_free(&i);
+ continue;
+
+ /*
+ * Move it from the old list and add it
+ * to the new list.
+ */
+ case T_OP_ADD:
+ do_add:
+ *tail_from = i->next;
+ i->next = NULL;
+ *tail_new = i;
+ fr_pair_steal(ctx, i);
+ tail_new = &(i->next);
+ continue;
+ case T_OP_PREPEND:
+ i->next = head_prepend;
+ head_prepend = i;
+ fr_pair_steal(ctx, i);
+ continue;
+ }
+ } /* loop over the "from" list. */
+
+ /*
+ * If the op parameter was prepend, add the "new" list
+ * attributes first as those whose individual operator
+ * is prepend should be prepended to the resulting list
+ */
+ if (op == T_OP_PREPEND) {
+ fr_pair_prepend(to, head_new);
+ }
+
+ /*
+ * If there are any items in the prepend list prepend
+ * it to the "to" list
+ */
+ fr_pair_prepend(to, head_prepend);
+
+ /*
+ * If the op parameter was not prepend, take the "new"
+ * list, and append it to the "to" list.
+ */
+ if (op != T_OP_PREPEND) {
+ fr_pair_add(to, head_new);
+ }
+}
+
+/** Move matching pairs between VALUE_PAIR lists
+ *
+ * Move pairs of a matching attribute number, vendor number and tag from the
+ * the input list to the output list.
+ *
+ * @note fr_pair_list_free should be called on the head of the old list to free unmoved
+ attributes (if they're no longer needed).
+ *
+ * @param[in] ctx for talloc
+ * @param[in,out] to destination list.
+ * @param[in,out] from source list.
+ * @param[in] attr to match. If attribute PW_VENDOR_SPECIFIC and vendor 0,
+ * will match (and therefore copy) only VSAs.
+ * If attribute 0 and vendor 0 will match (and therefore copy) all
+ * attributes.
+ * @param[in] vendor to match.
+ * @param[in] tag to match, TAG_ANY matches any tag, TAG_NONE matches tagless VPs.
+ * @param[in] move if set to "true", VPs are moved. If set to "false", VPs are copied, and the old one deleted.
+ */
+static void fr_pair_list_move_by_num_internal(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from,
+ unsigned int attr, unsigned int vendor, int8_t tag,
+ bool move)
+{
+ VALUE_PAIR *to_tail, *i, *next, *this;
+ VALUE_PAIR *iprev = NULL;
+
+ /*
+ * Find the last pair in the "to" list and put it in "to_tail".
+ *
+ * @todo: replace the "if" with "VALUE_PAIR **tail"
+ */
+ if (*to != NULL) {
+ to_tail = *to;
+ for(i = *to; i; i = i->next) {
+ VERIFY_VP(i);
+ to_tail = i;
+ }
+ } else
+ to_tail = NULL;
+
+ /*
+ * Attr/vendor of 0 means "move them all".
+ * It's better than "fr_pair_add(foo,bar);bar=NULL"
+ */
+ if ((vendor == 0) && (attr == 0)) {
+ if (*to) {
+ to_tail->next = *from;
+ } else {
+ *to = *from;
+ }
+
+ for (i = *from; i; i = i->next) {
+ fr_pair_steal(ctx, i);
+ }
+
+ *from = NULL;
+ return;
+ }
+
+ for(i = *from; i; i = next) {
+ VERIFY_VP(i);
+ next = i->next;
+
+ if (i->da->flags.has_tag && !TAG_EQ(tag, i->tag)) {
+ iprev = i;
+ continue;
+ }
+
+ /*
+ * vendor=0, attr = PW_VENDOR_SPECIFIC means
+ * "match any vendor attribute".
+ */
+ if ((vendor == 0) && (attr == PW_VENDOR_SPECIFIC)) {
+ /*
+ * It's a VSA: move it over.
+ */
+ if (i->da->vendor != 0) goto move;
+
+ /*
+ * It's Vendor-Specific: move it over.
+ */
+ if (i->da->attr == attr) goto move;
+
+ /*
+ * It's not a VSA: ignore it.
+ */
+ iprev = i;
+ continue;
+ }
+
+ /*
+ * If it isn't an exact match, ignore it.
+ */
+ if (!((i->da->vendor == vendor) && (i->da->attr == attr))) {
+ iprev = i;
+ continue;
+ }
+
+ move:
+ /*
+ * Remove the attribute from the "from" list.
+ */
+ if (iprev)
+ iprev->next = next;
+ else
+ *from = next;
+
+ if (move) {
+ this = i;
+ } else {
+ this = fr_pair_copy(ctx, i);
+ }
+
+ /*
+ * Add the attribute to the "to" list.
+ */
+ if (to_tail)
+ to_tail->next = this;
+ else
+ *to = this;
+ to_tail = this;
+ this->next = NULL;
+
+ if (move) {
+ fr_pair_steal(ctx, i);
+ } else {
+ talloc_free(i);
+ }
+ }
+}
+
+
+/** Move matching pairs between VALUE_PAIR lists
+ *
+ * Move pairs of a matching attribute number, vendor number and tag from the
+ * the input list to the output list.
+ *
+ * @note pairs which are moved have their parent changed to ctx.
+ *
+ * @note fr_pair_list_free should be called on the head of the old list to free unmoved
+ attributes (if they're no longer needed).
+ *
+ * @param[in] ctx for talloc
+ * @param[in,out] to destination list.
+ * @param[in,out] from source list.
+ * @param[in] attr to match. If attribute PW_VENDOR_SPECIFIC and vendor 0,
+ * will match (and therefore copy) only VSAs.
+ * If attribute 0 and vendor 0 will match (and therefore copy) all
+ * attributes.
+ * @param[in] vendor to match.
+ * @param[in] tag to match, TAG_ANY matches any tag, TAG_NONE matches tagless VPs.
+ */
+void fr_pair_list_move_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from,
+ unsigned int attr, unsigned int vendor, int8_t tag)
+{
+ fr_pair_list_move_by_num_internal(ctx, to, from, attr, vendor, tag, true);
+}
+
+
+/** Copy / delete matching pairs between VALUE_PAIR lists
+ *
+ * Move pairs of a matching attribute number, vendor number and tag from the
+ * the input list to the output list. Like fr_pair_list_move_by_num(), but
+ * instead does copy / delete.
+ *
+ * @note The pair is NOT reparented. It is copied and deleted.
+ *
+ * @note fr_pair_list_free should be called on the head of the old list to free unmoved
+ attributes (if they're no longer needed).
+ *
+ * @param[in] ctx for talloc
+ * @param[in,out] to destination list.
+ * @param[in,out] from source list.
+ * @param[in] attr to match. If attribute PW_VENDOR_SPECIFIC and vendor 0,
+ * will match (and therefore copy) only VSAs.
+ * If attribute 0 and vendor 0 will match (and therefore copy) all
+ * attributes.
+ * @param[in] vendor to match.
+ * @param[in] tag to match, TAG_ANY matches any tag, TAG_NONE matches tagless VPs.
+ */
+void fr_pair_list_mcopy_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from,
+ unsigned int attr, unsigned int vendor, int8_t tag)
+{
+ fr_pair_list_move_by_num_internal(ctx, to, from, attr, vendor, tag, false);
+}
+
+
+/** Convert string value to native attribute value
+ *
+ * @param vp to assign value to.
+ * @param value string to convert. Binary safe for variable length values if len is provided.
+ * @param inlen may be < 0 in which case strlen(len) is used to determine length, else inline
+ * should be the length of the string or sub string to parse.
+ * @return 0 on success -1 on error.
+ */
+int fr_pair_value_from_str(VALUE_PAIR *vp, char const *value, size_t inlen)
+{
+ ssize_t ret;
+ PW_TYPE type;
+ VERIFY_VP(vp);
+
+ if (!value) return -1;
+
+ type = vp->da->type;
+
+ /*
+ * We presume that the input data is from a double quoted
+ * string, and needs escaping
+ */
+ ret = value_data_from_str(vp, &vp->data, &type, vp->da, value, inlen, '"');
+ if (ret < 0) return -1;
+
+ /*
+ * If we parsed to a different type than the DA associated with
+ * the VALUE_PAIR we now need to fixup the DA.
+ */
+ if (type != vp->da->type) {
+ DICT_ATTR const *da;
+
+ da = dict_attrbytype(vp->da->attr, vp->da->vendor, type);
+ if (!da) {
+ fr_strerror_printf("Cannot find %s variant of attribute \"%s\"",
+ fr_int2str(dict_attr_types, type, "<INVALID>"), vp->da->name);
+ return -1;
+ }
+ vp->da = da;
+ }
+
+ vp->vp_length = ret;
+ vp->type = VT_DATA;
+
+ VERIFY_VP(vp);
+
+ return 0;
+}
+
+/** Use simple heuristics to create an VALUE_PAIR from an unknown address string
+ *
+ * If a DICT_ATTR is not provided for the address type, parsing will fail with
+ * and error.
+ *
+ * @param ctx to allocate VP in.
+ * @param value IPv4/IPv6 address/prefix string.
+ * @param ipv4 dictionary attribute to use for an IPv4 address.
+ * @param ipv6 dictionary attribute to use for an IPv6 address.
+ * @param ipv4_prefix dictionary attribute to use for an IPv4 prefix.
+ * @param ipv6_prefix dictionary attribute to use for an IPv6 prefix.
+ * @return NULL on error, or new VALUE_PAIR.
+ */
+VALUE_PAIR *fr_pair_afrom_ip_str(TALLOC_CTX *ctx, char const *value, DICT_ATTR *ipv4, DICT_ATTR *ipv6,
+ DICT_ATTR *ipv4_prefix, DICT_ATTR *ipv6_prefix)
+{
+ VALUE_PAIR *vp;
+ DICT_ATTR *da = NULL;
+
+ if (!fr_assert(ipv4 || ipv6 || ipv4_prefix || ipv6_prefix)) {
+ return NULL;
+ }
+
+ /* No point in repeating the work of fr_pair_value_from_str */
+ if (strchr(value, ':')) {
+ if (strchr(value, '/')) {
+ da = ipv6_prefix;
+ goto finish;
+ }
+
+ da = ipv6;
+ goto finish;
+ }
+
+ if (strchr(value, '/')) {
+ da = ipv4_prefix;
+ goto finish;
+ }
+
+ if (ipv4) {
+ da = ipv4;
+ goto finish;
+ }
+
+ fr_strerror_printf("Invalid IP value specified, allowed types are %s%s%s%s",
+ ipv4 ? "ipaddr " : "", ipv6 ? "ipv6addr " : "",
+ ipv4_prefix ? "ipv4prefix " : "", ipv6_prefix ? "ipv6prefix" : "");
+
+finish:
+ vp = fr_pair_afrom_da(ctx, da);
+ if (!vp) return NULL;
+ if (fr_pair_value_from_str(vp, value, -1) < 0) {
+ talloc_free(vp);
+ return NULL;
+ }
+
+ return vp;
+}
+
+
+/** Create a valuepair from an ASCII attribute and value
+ *
+ * Where the attribute name is in the form:
+ * - Attr-%d
+ * - Attr-%d.%d.%d...
+ * - Vendor-%d-Attr-%d
+ * - VendorName-Attr-%d
+ *
+ * @param ctx for talloc
+ * @param attribute name to parse.
+ * @param value to parse (must be a hex string).
+ * @param op to assign to new valuepair.
+ * @return new valuepair or NULL on error.
+ */
+static VALUE_PAIR *fr_pair_make_unknown(TALLOC_CTX *ctx,
+ char const *attribute, char const *value,
+ FR_TOKEN op)
+{
+ VALUE_PAIR *vp, *vp2;
+ DICT_ATTR const *da;
+
+ uint8_t *data;
+ size_t size;
+ ssize_t len;
+
+ vp = fr_pair_alloc(ctx);
+ if (!vp) return NULL;
+
+ vp->da = dict_unknown_afrom_str(vp, attribute);
+ if (!vp->da) {
+ talloc_free(vp);
+ return NULL;
+ }
+
+ /*
+ * No value. Nothing more to do.
+ */
+ if (!value) return vp;
+
+ /*
+ * Unknown attributes MUST be of type 'octets'
+ */
+ if (strncasecmp(value, "0x", 2) != 0) {
+ fr_strerror_printf("Unknown attribute \"%s\" requires a hex "
+ "string, not \"%s\"", attribute, value);
+ talloc_free(vp);
+ return NULL;
+ }
+
+ /*
+ * Convert the hex data to binary.
+ */
+ size = strlen(value + 2);
+
+ vp->vp_length = size >> 1;
+ vp->vp_octets = data = talloc_array(vp, uint8_t, vp->vp_length);
+ vp->type = VT_DATA;
+ vp->op = (op == 0) ? T_OP_EQ : op;
+
+ if (fr_hex2bin(data, vp->vp_length, value + 2, size) != vp->vp_length) {
+ fr_strerror_printf("Invalid hex string");
+ talloc_free(vp);
+ return NULL;
+ }
+
+ /*
+ * It's still unknown, return it as-is.
+ */
+ da = dict_attrbyvalue(vp->da->attr, vp->da->vendor);
+ if (!da) return vp;
+
+ /*
+ * It MIGHT be known. See if we can decode the raw data
+ * into a valid attribute.
+ */
+ len = data2vp(ctx, NULL, NULL, NULL, da,
+ vp->vp_octets, vp->vp_length, vp->vp_length,
+ &vp2);
+ if (len <= 0) return vp;
+
+ /*
+ * It's still unknown. Return the original VP.
+ */
+ if (vp2->da->flags.is_unknown) {
+ fr_pair_list_free(&vp2);
+ return vp;
+ }
+
+ /*
+ * Didn't parse all of it. Return the "unknown" one.
+ *
+ * FIXME: it COULD have parsed 2 attributes and
+ * then not the third, so returning 2 "knowns"
+ * and 1 "unknown" is likely preferable.
+ */
+ if ((size_t) len < vp->vp_length) {
+ fr_pair_list_free(&vp2);
+ return vp;
+ }
+
+ fr_pair_list_free(&vp);
+ return vp2;
+}
+
+
+/** Create a VALUE_PAIR from ASCII strings
+ *
+ * Converts an attribute string identifier (with an optional tag qualifier)
+ * and value string into a VALUE_PAIR.
+ *
+ * The string value is parsed according to the type of VALUE_PAIR being created.
+ *
+ * @param[in] ctx for talloc
+ * @param[in] vps list where the attribute will be added (optional)
+ * @param[in] attribute name.
+ * @param[in] value attribute value (may be NULL if value will be set later).
+ * @param[in] op to assign to new VALUE_PAIR.
+ * @return a new VALUE_PAIR.
+ */
+VALUE_PAIR *fr_pair_make(TALLOC_CTX *ctx, VALUE_PAIR **vps,
+ char const *attribute, char const *value, FR_TOKEN op)
+{
+ DICT_ATTR const *da;
+ VALUE_PAIR *vp;
+ char *tc, *ts;
+ int8_t tag;
+ bool found_tag;
+ char buffer[256];
+ char const *attrname = attribute;
+
+ /*
+ * Check for tags in 'Attribute:Tag' format.
+ */
+ found_tag = false;
+ tag = TAG_ANY;
+
+ ts = strrchr(attribute, ':');
+ if (ts && !ts[1]) {
+ fr_strerror_printf("Invalid tag for attribute %s", attribute);
+ return NULL;
+ }
+
+ if (ts && ts[1]) {
+ strlcpy(buffer, attribute, sizeof(buffer));
+ attrname = buffer;
+ ts = strrchr(attrname, ':');
+ if (!ts) return NULL;
+
+ /* Colon found with something behind it */
+ if (ts[1] == '*' && ts[2] == 0) {
+ /* Wildcard tag for check items */
+ tag = TAG_ANY;
+ *ts = '\0';
+ } else if ((ts[1] >= '0') && (ts[1] <= '9')) {
+ /* It's not a wild card tag */
+ tag = strtol(ts + 1, &tc, 0);
+ if (tc && !*tc && TAG_VALID_ZERO(tag))
+ *ts = '\0';
+ else tag = TAG_ANY;
+ } else {
+ fr_strerror_printf("Invalid tag for attribute %s", attribute);
+ return NULL;
+ }
+ found_tag = true;
+ }
+
+ /*
+ * It's not found in the dictionary, so we use
+ * another method to create the attribute.
+ */
+ da = dict_attrbyname(attrname);
+ if (!da) {
+ vp = fr_pair_make_unknown(ctx, attrname, value, op);
+ if (vp && vps) fr_pair_add(vps, vp);
+ return vp;
+ }
+
+ /* Check for a tag in the 'Merit' format of:
+ * :Tag:Value. Print an error if we already found
+ * a tag in the Attribute.
+ */
+
+ if (value && (*value == ':' && da->flags.has_tag)) {
+ /* If we already found a tag, this is invalid */
+ if(found_tag) {
+ fr_strerror_printf("Duplicate tag %s for attribute %s",
+ value, da->name);
+ DEBUG("Duplicate tag %s for attribute %s\n",
+ value, da->name);
+ return NULL;
+ }
+ /* Colon found and attribute allows a tag */
+ if (value[1] == '*' && value[2] == ':') {
+ /* Wildcard tag for check items */
+ tag = TAG_ANY;
+ value += 3;
+ } else {
+ /* Real tag */
+ tag = strtol(value + 1, &tc, 0);
+ if (tc && *tc==':' && TAG_VALID_ZERO(tag))
+ value = tc + 1;
+ else tag = 0;
+ }
+ }
+
+ vp = fr_pair_afrom_da(ctx, da);
+ if (!vp) return NULL;
+ vp->op = (op == 0) ? T_OP_EQ : op;
+ vp->tag = tag;
+
+ switch (vp->op) {
+ case T_OP_CMP_TRUE:
+ case T_OP_CMP_FALSE:
+ vp->vp_strvalue = NULL;
+ vp->vp_length = 0;
+ value = NULL; /* ignore it! */
+ break;
+
+ /*
+ * Regular expression comparison of integer attributes
+ * does a STRING comparison of the names of their
+ * integer attributes.
+ */
+ case T_OP_REG_EQ: /* =~ */
+ case T_OP_REG_NE: /* !~ */
+ {
+#ifndef HAVE_REGEX
+ fr_strerror_printf("Regular expressions are not supported");
+ return NULL;
+#else
+ ssize_t slen;
+ regex_t *preg;
+
+ /*
+ * Someone else will fill in the value.
+ */
+ if (!value) break;
+
+ talloc_free(vp);
+
+ slen = regex_compile(ctx, &preg, value, strlen(value), false, false, false, true);
+ if (slen <= 0) {
+ fr_strerror_printf("Error at offset %zu compiling regex for %s: %s", -slen,
+ attribute, fr_strerror());
+ return NULL;
+ }
+ talloc_free(preg);
+
+ vp = fr_pair_make(ctx, NULL, attribute, NULL, op);
+ if (!vp) return NULL;
+
+ if (fr_pair_mark_xlat(vp, value) < 0) {
+ talloc_free(vp);
+ return NULL;
+ }
+
+ value = NULL; /* ignore it */
+ break;
+#endif
+ }
+ default:
+ break;
+ }
+
+ /*
+ * We allow this for stupidity, but it's really a bad idea.
+ */
+ if (vp->da->type == PW_TYPE_TLV) {
+ ssize_t len;
+ DICT_ATTR const *unknown;
+ VALUE_PAIR *head = NULL;
+ VALUE_PAIR **tail = &head;
+
+ if (!value) {
+ talloc_free(vp);
+ return NULL;
+ }
+
+ unknown = dict_unknown_afrom_fields(vp, vp->da->attr, vp->da->vendor);
+ if (!unknown) {
+ talloc_free(vp);
+ return NULL;
+ }
+
+ vp->da = unknown;
+
+ /*
+ * Parse it as an unknown type, i.e. octets.
+ */
+ if (fr_pair_value_from_str(vp, value, -1) < 0) {
+ talloc_free(vp);
+ return NULL;
+ }
+
+ /*
+ * It's badly formatted. Treat it as unknown.
+ */
+ if (rad_tlv_ok(vp->vp_octets, vp->vp_length, 1, 1) < 0) {
+ goto do_add;
+ }
+
+ /*
+ * Decode the TLVs
+ */
+ len = rad_data2vp_tlvs(ctx, NULL, NULL, NULL, da, vp->vp_octets,
+ vp->vp_length, tail);
+ if (len < 0) {
+ goto do_add;
+ }
+
+ talloc_free(vp);
+ vp = head;
+ goto do_add;
+ }
+
+ /*
+ * FIXME: if (strcasecmp(attribute, vp->da->name) != 0)
+ * then the user MAY have typed in the attribute name
+ * as Vendor-%d-Attr-%d, and the value MAY be octets.
+ *
+ * We probably want to fix fr_pair_value_from_str to accept
+ * octets as values for any attribute.
+ */
+ if (value && (fr_pair_value_from_str(vp, value, -1) < 0)) {
+ talloc_free(vp);
+ return NULL;
+ }
+
+do_add:
+ if (vps) fr_pair_add(vps, vp);
+ return vp;
+}
+
+/** Mark a valuepair for xlat expansion
+ *
+ * Copies xlat source (unprocessed) string to valuepair value,
+ * and sets value type.
+ *
+ * @param vp to mark for expansion.
+ * @param value to expand.
+ * @return 0 if marking succeeded or -1 if vp already had a value, or OOM.
+ */
+int fr_pair_mark_xlat(VALUE_PAIR *vp, char const *value)
+{
+ char *raw;
+
+ /*
+ * valuepair should not already have a value.
+ */
+ if (vp->type != VT_NONE) {
+ fr_strerror_printf("Pair already has a value");
+ return -1;
+ }
+
+ raw = talloc_typed_strdup(vp, value);
+ if (!raw) {
+ fr_strerror_printf("Out of memory");
+ return -1;
+ }
+
+ vp->type = VT_XLAT;
+ vp->value.xlat = raw;
+ vp->vp_length = 0;
+
+ return 0;
+}
+
+
+/** Read a single valuepair from a buffer, and advance the pointer
+ *
+ * Returns T_EOL if end of line was encountered.
+ *
+ * @param[in,out] ptr to read from and update.
+ * @param[out] raw The struct to write the raw VALUE_PAIR to.
+ * @return the last token read.
+ */
+FR_TOKEN fr_pair_raw_from_str(char const **ptr, VALUE_PAIR_RAW *raw)
+{
+ char const *p;
+ char *q;
+ FR_TOKEN ret = T_INVALID, next, quote;
+ char buf[8];
+
+ if (!ptr || !*ptr || !raw) {
+ fr_strerror_printf("Invalid arguments");
+ return T_INVALID;
+ }
+
+ /*
+ * Skip leading spaces
+ */
+ p = *ptr;
+ while ((*p == ' ') || (*p == '\t')) p++;
+
+ if (!*p) {
+ fr_strerror_printf("No token read where we expected "
+ "an attribute name");
+ return T_INVALID;
+ }
+
+ if (*p == '#') return T_HASH;
+
+ /*
+ * Try to get the attribute name.
+ */
+ q = raw->l_opand;
+ *q = '\0';
+ while (*p) {
+ uint8_t const *t = (uint8_t const *) p;
+
+ if (q >= (raw->l_opand + sizeof(raw->l_opand))) {
+ too_long:
+ fr_strerror_printf("Attribute name too long");
+ return T_INVALID;
+ }
+
+ /*
+ * This is arguably easier than trying to figure
+ * out which operators come after the attribute
+ * name. Yes, our "lexer" is bad.
+ */
+ if (!dict_attr_allowed_chars[(unsigned int) *t]) {
+ break;
+ }
+
+ /*
+ * Attribute:=value is NOT
+ *
+ * Attribute:
+ * =
+ * value
+ */
+ if ((*p == ':') && (!isdigit((int) p[1]))) {
+ break;
+ }
+
+ *(q++) = *(p++);
+ }
+
+ /*
+ * Haven't found any valid characters in the name.
+ */
+ if (!*raw->l_opand) {
+ fr_strerror_printf("Invalid attribute name");
+ return T_INVALID;
+ }
+
+ /*
+ * Look for tag (:#). This is different from :=, which
+ * is an operator.
+ */
+ if ((*p == ':') && (isdigit((int) p[1]))) {
+ if (q >= (raw->l_opand + sizeof(raw->l_opand))) {
+ goto too_long;
+ }
+ *(q++) = *(p++);
+
+ while (isdigit((int) *p)) {
+ if (q >= (raw->l_opand + sizeof(raw->l_opand))) {
+ goto too_long;
+ }
+ *(q++) = *(p++);
+ }
+ }
+
+ *q = '\0';
+ *ptr = p;
+
+ /* Now we should have an operator here. */
+ raw->op = gettoken(ptr, buf, sizeof(buf), false);
+ if (raw->op < T_EQSTART || raw->op > T_EQEND) {
+ fr_strerror_printf("Expecting operator");
+
+ return T_INVALID;
+ }
+
+ /*
+ * Read value. Note that empty string values are allowed
+ */
+ quote = gettoken(ptr, raw->r_opand, sizeof(raw->r_opand), false);
+ if (quote == T_EOL) {
+ fr_strerror_printf("Failed to get value");
+
+ return T_INVALID;
+ }
+
+ /*
+ * Peek at the next token. Must be T_EOL, T_COMMA, or T_HASH
+ */
+ p = *ptr;
+
+ next = gettoken(&p, buf, sizeof(buf), false);
+ switch (next) {
+ case T_HASH:
+ next = T_EOL;
+ break;
+
+ case T_EOL:
+ break;
+
+ case T_COMMA:
+ *ptr = p;
+ break;
+
+ default:
+ fr_strerror_printf("Expected end of line or comma");
+ return T_INVALID;
+ }
+ ret = next;
+
+ switch (quote) {
+ /*
+ * Perhaps do xlat's
+ */
+ case T_DOUBLE_QUOTED_STRING:
+ /*
+ * Only report as double quoted if it contained valid
+ * a valid xlat expansion.
+ */
+ p = strchr(raw->r_opand, '%');
+ if (p && (p[1] == '{')) {
+ raw->quote = quote;
+ } else {
+ raw->quote = T_SINGLE_QUOTED_STRING;
+ }
+
+ break;
+
+ case T_SINGLE_QUOTED_STRING:
+ case T_BACK_QUOTED_STRING:
+ case T_BARE_WORD:
+ raw->quote = quote;
+ break;
+
+ default:
+ fr_strerror_printf("Failed to find expected value on right hand side in %s", raw->l_opand);
+ return T_INVALID;
+ }
+
+ return ret;
+}
+
+/** Read one line of attribute/value pairs into a list.
+ *
+ * The line may specify multiple attributes separated by commas.
+ *
+ * @note If the function returns T_INVALID, an error has occurred and
+ * @note the valuepair list should probably be freed.
+ *
+ * @param ctx for talloc
+ * @param buffer to read valuepairs from.
+ * @param list where the parsed VALUE_PAIRs will be appended.
+ * @return the last token parsed, or T_INVALID
+ */
+FR_TOKEN fr_pair_list_afrom_str(TALLOC_CTX *ctx, char const *buffer, VALUE_PAIR **list)
+{
+ VALUE_PAIR *vp, *head, **tail;
+ char const *p;
+ FR_TOKEN last_token = T_INVALID;
+ VALUE_PAIR_RAW raw;
+
+ /*
+ * We allow an empty line.
+ */
+ if (buffer[0] == 0) {
+ return T_EOL;
+ }
+
+ head = NULL;
+ tail = &head;
+
+ p = buffer;
+ do {
+ raw.l_opand[0] = '\0';
+ raw.r_opand[0] = '\0';
+
+ last_token = fr_pair_raw_from_str(&p, &raw);
+
+ /*
+ * JUST a hash. Don't try to create a VP.
+ * Let the caller determine if an empty list is OK.
+ */
+ if (last_token == T_HASH) {
+ last_token = T_EOL;
+ break;
+ }
+ if (last_token == T_INVALID) break;
+
+ if (raw.quote == T_DOUBLE_QUOTED_STRING) {
+ vp = fr_pair_make(ctx, NULL, raw.l_opand, NULL, raw.op);
+ if (!vp) {
+ last_token = T_INVALID;
+ break;
+ }
+ if (fr_pair_mark_xlat(vp, raw.r_opand) < 0) {
+ talloc_free(vp);
+ last_token = T_INVALID;
+ break;
+ }
+ } else {
+ vp = fr_pair_make(ctx, NULL, raw.l_opand, raw.r_opand, raw.op);
+ if (!vp) {
+ last_token = T_INVALID;
+ break;
+ }
+ }
+
+ *tail = vp;
+ tail = &((*tail)->next);
+ } while (*p && (last_token == T_COMMA));
+
+ if (last_token == T_INVALID) {
+ fr_pair_list_free(&head);
+ } else {
+ fr_pair_add(list, head);
+ }
+
+ /*
+ * And return the last token which we read.
+ */
+ return last_token;
+}
+
+/*
+ * Read valuepairs from the fp up to End-Of-File.
+ */
+int fr_pair_list_afrom_file(TALLOC_CTX *ctx, VALUE_PAIR **out, FILE *fp, bool *pfiledone)
+{
+ char buf[8192];
+ FR_TOKEN last_token = T_EOL;
+
+ vp_cursor_t cursor;
+
+ VALUE_PAIR *vp = NULL;
+ fr_cursor_init(&cursor, out);
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ /*
+ * If we get a '\n' by itself, we assume that's
+ * the end of that VP
+ */
+ if (buf[0] == '\n') {
+ if (vp) {
+ *pfiledone = false;
+ return 0;
+ }
+ continue;
+ }
+
+ /*
+ * Comments get ignored
+ */
+ if (buf[0] == '#') continue;
+
+ /*
+ * Read all of the attributes on the current line.
+ */
+ vp = NULL;
+ last_token = fr_pair_list_afrom_str(ctx, buf, &vp);
+ if (!vp) {
+ if (last_token != T_EOL) goto error;
+ break;
+ }
+
+ fr_cursor_merge(&cursor, vp);
+ buf[0] = '\0';
+ }
+ *pfiledone = true;
+
+ return 0;
+
+error:
+ *pfiledone = false;
+ vp = fr_cursor_first(&cursor);
+ if (vp) fr_pair_list_free(&vp);
+
+ return -1;
+}
+
+/** Compare two pairs, using the operator from "a"
+ *
+ * i.e. given two attributes, it does:
+ *
+ * (b->data) (a->operator) (a->data)
+ *
+ * e.g. "foo" != "bar"
+ *
+ * @param[in] a the first attribute
+ * @param[in] b the second attribute
+ * @return 1 if true, 0 if false, -1 on error.
+ */
+int fr_pair_cmp(VALUE_PAIR *a, VALUE_PAIR *b)
+{
+ if (!a) return -1;
+
+ VERIFY_VP(a);
+ if (b) VERIFY_VP(b);
+
+ switch (a->op) {
+ case T_OP_CMP_TRUE:
+ return (b != NULL);
+
+ case T_OP_CMP_FALSE:
+ return (b == NULL);
+
+ /*
+ * a is a regex, compile it, print b to a string,
+ * and then do string comparisons.
+ */
+ case T_OP_REG_EQ:
+ case T_OP_REG_NE:
+#ifndef HAVE_REGEX
+ return -1;
+#else
+ if (!b) return false;
+
+ {
+ ssize_t slen;
+ regex_t *preg;
+ char *value;
+
+ if (!fr_assert(a->da->type == PW_TYPE_STRING)) return -1;
+
+ slen = regex_compile(NULL, &preg, a->value.xlat, talloc_array_length(a->value.xlat) - 1, false, false, false, true);
+ if (slen <= 0) {
+ fr_strerror_printf("Error at offset %zu compiling regex for %s: %s",
+ -slen, a->da->name, fr_strerror());
+ return -1;
+ }
+ value = vp_aprints_value(NULL, b, '\0');
+ if (!value) {
+ talloc_free(preg);
+ return -1;
+ }
+
+ /*
+ * Don't care about substring matches, oh well...
+ */
+ slen = regex_exec(preg, value, talloc_array_length(value) - 1, NULL, NULL);
+ talloc_free(preg);
+ talloc_free(value);
+
+ if (slen < 0) return -1;
+ if (a->op == T_OP_REG_EQ) return (int)slen;
+ return !slen;
+ }
+#endif
+
+ default: /* we're OK */
+ if (!b) return false;
+ break;
+ }
+
+ return fr_pair_cmp_op(a->op, b, a);
+}
+
+/** Determine equality of two lists
+ *
+ * This is useful for comparing lists of attributes inserted into a binary tree.
+ *
+ * @param a first list of VALUE_PAIRs.
+ * @param b second list of VALUE_PAIRs.
+ * @return -1 if a < b, 0 if the two lists are equal, 1 if a > b, -2 on error.
+ */
+int fr_pair_list_cmp(VALUE_PAIR *a, VALUE_PAIR *b)
+{
+ vp_cursor_t a_cursor, b_cursor;
+ VALUE_PAIR *a_p, *b_p;
+ int ret;
+
+ for (a_p = fr_cursor_init(&a_cursor, &a), b_p = fr_cursor_init(&b_cursor, &b);
+ a_p && b_p;
+ a_p = fr_cursor_next(&a_cursor), b_p = fr_cursor_next(&b_cursor)) {
+ /* Same VP, no point doing expensive checks */
+ if (a_p == b_p) {
+ continue;
+ }
+
+ if (a_p->da < b_p->da) {
+ return -1;
+ }
+ if (a_p->da > b_p->da) {
+ return 1;
+ }
+
+ if (a_p->tag < b_p->tag) {
+ return -1;
+ }
+ if (a_p->tag > b_p->tag) {
+ return 1;
+ }
+
+ ret = value_data_cmp(a_p->da->type, &a_p->data, a_p->vp_length,
+ b_p->da->type, &b_p->data, b_p->vp_length);
+ if (ret != 0) {
+ fr_assert(ret >= -1); /* Comparison error */
+ return ret;
+ }
+ }
+
+ if (!a_p && !b_p) {
+ return 0;
+ }
+
+ if (!a_p) {
+ return -1;
+ }
+
+ /* if(!b_p) */
+ return 1;
+}
+
+/** Set the type of the VALUE_PAIR value buffer to match it's DICT_ATTR
+ *
+ * @param vp to fixup.
+ */
+static void fr_pair_value_set_type(VALUE_PAIR *vp)
+{
+ if (!vp->data.ptr) return;
+
+ switch (vp->da->type) {
+ case PW_TYPE_OCTETS:
+ talloc_set_type(vp->data.ptr, uint8_t);
+ return;
+
+ case PW_TYPE_STRING:
+ talloc_set_type(vp->data.ptr, char);
+ return;
+
+ default:
+ return;
+ }
+}
+
+/** Copy data into an "octets" data type.
+ *
+ * @param[in,out] vp to update
+ * @param[in] src data to copy
+ * @param[in] size of the data, may be 0 in which case previous value will be freed.
+ */
+void fr_pair_value_memcpy(VALUE_PAIR *vp, uint8_t const *src, size_t size)
+{
+ uint8_t *p = NULL, *q;
+
+ VERIFY_VP(vp);
+
+ if (size > 0) {
+ p = talloc_memdup(vp, src, size);
+ if (!p) return;
+ talloc_set_type(p, uint8_t);
+ }
+
+ memcpy(&q, &vp->vp_octets, sizeof(q));
+ TALLOC_FREE(q);
+
+ vp->vp_octets = p;
+ vp->vp_length = size;
+
+ if (size > 0) fr_pair_value_set_type(vp);
+}
+
+/** Reparent an allocated octet buffer to a VALUE_PAIR
+ *
+ * @param[in,out] vp to update
+ * @param[in] src buffer to steal.
+ */
+void fr_pair_value_memsteal(VALUE_PAIR *vp, uint8_t const *src)
+{
+ uint8_t *q;
+
+ VERIFY_VP(vp);
+
+ memcpy(&q, &vp->vp_octets, sizeof(q));
+ talloc_free(q);
+
+ vp->vp_octets = talloc_steal(vp, src);
+ vp->type = VT_DATA;
+ vp->vp_length = talloc_array_length(vp->vp_strvalue);
+ fr_pair_value_set_type(vp);
+}
+
+/** Reparent an allocated char buffer to a VALUE_PAIR
+ *
+ * @param[in,out] vp to update
+ * @param[in] src buffer to steal.
+ */
+void fr_pair_value_strsteal(VALUE_PAIR *vp, char const *src)
+{
+ uint8_t *q;
+
+ VERIFY_VP(vp);
+
+ memcpy(&q, &vp->vp_octets, sizeof(q));
+ talloc_free(q);
+
+ vp->vp_strvalue = talloc_steal(vp, src);
+ vp->type = VT_DATA;
+ vp->vp_length = talloc_array_length(vp->vp_strvalue) - 1;
+ fr_pair_value_set_type(vp);
+}
+
+/** Copy data into an "string" data type.
+ *
+ * @param[in,out] vp to update
+ * @param[in] src data to copy
+ */
+void fr_pair_value_strcpy(VALUE_PAIR *vp, char const *src)
+{
+ char *p, *q;
+
+ VERIFY_VP(vp);
+
+ p = talloc_strdup(vp, src);
+
+ if (!p) return;
+
+ memcpy(&q, &vp->vp_strvalue, sizeof(q));
+ talloc_free(q);
+
+ vp->vp_strvalue = p;
+ vp->type = VT_DATA;
+ vp->vp_length = talloc_array_length(vp->vp_strvalue) - 1;
+ fr_pair_value_set_type(vp);
+}
+
+/** Copy data into an "string" data type.
+ *
+ * @note unlike the original strncpy, this function does not stop
+ * if it finds \0 bytes embedded in the string.
+ *
+ * @param[in,out] vp to update.
+ * @param[in] src data to copy.
+ * @param[in] len of data to copy.
+ */
+void fr_pair_value_bstrncpy(VALUE_PAIR *vp, void const *src, size_t len)
+{
+ char *p, *q;
+
+ VERIFY_VP(vp);
+
+ p = talloc_array(vp, char, len + 1);
+ if (!p) return;
+
+ memcpy(p, src, len); /* embdedded \0 safe */
+ p[len] = '\0';
+
+ memcpy(&q, &vp->vp_strvalue, sizeof(q));
+ talloc_free(q);
+
+ vp->vp_strvalue = p;
+ vp->type = VT_DATA;
+ vp->vp_length = len;
+ fr_pair_value_set_type(vp);
+}
+
+/** Print data into an "string" data type.
+ *
+ * @param[in,out] vp to update
+ * @param[in] fmt the format string
+ */
+void fr_pair_value_sprintf(VALUE_PAIR *vp, char const *fmt, ...)
+{
+ va_list ap;
+ char *p, *q;
+
+ VERIFY_VP(vp);
+
+ va_start(ap, fmt);
+ p = talloc_vasprintf(vp, fmt, ap);
+ va_end(ap);
+
+ if (!p) return;
+
+ memcpy(&q, &vp->vp_strvalue, sizeof(q));
+ talloc_free(q);
+
+ vp->vp_strvalue = p;
+ vp->type = VT_DATA;
+
+ vp->vp_length = talloc_array_length(vp->vp_strvalue) - 1;
+ fr_pair_value_set_type(vp);
+}
+
+#ifdef WITH_VERIFY_PTR
+/*
+ * Verify a VALUE_PAIR
+ */
+inline void fr_pair_verify(char const *file, int line, VALUE_PAIR const *vp)
+{
+ if (!vp) {
+ FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR pointer was NULL", file, line);
+ fr_assert(0);
+ fr_exit_now(1);
+ }
+
+ (void) talloc_get_type_abort(vp, VALUE_PAIR);
+
+ if (!vp->da) {
+ FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR da pointer was NULL", file, line);
+ fr_assert(0);
+ fr_exit_now(1);
+ }
+
+ if (vp->data.ptr) switch (vp->da->type) {
+ case PW_TYPE_OCTETS:
+ {
+ size_t len;
+ TALLOC_CTX *parent;
+
+ if (!talloc_get_type(vp->data.ptr, uint8_t)) {
+ FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" data buffer type should be "
+ "uint8_t but is %s\n", file, line, vp->da->name, talloc_get_name(vp->data.ptr));
+ (void) talloc_get_type_abort(vp->data.ptr, uint8_t);
+ }
+
+ len = talloc_array_length(vp->vp_octets);
+ if (vp->vp_length > len) {
+ FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" length %zu is greater than "
+ "uint8_t data buffer length %zu\n", file, line, vp->da->name, vp->vp_length, len);
+ fr_assert(0);
+ fr_exit_now(1);
+ }
+
+ parent = talloc_parent(vp->data.ptr);
+ if (parent != vp) {
+ FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" char buffer is not "
+ "parented by VALUE_PAIR %p, instead parented by %p (%s)\n",
+ file, line, vp->da->name,
+ vp, parent, parent ? talloc_get_name(parent) : "NULL");
+ fr_assert(0);
+ fr_exit_now(1);
+ }
+ }
+ break;
+
+ case PW_TYPE_STRING:
+ {
+ size_t len;
+ TALLOC_CTX *parent;
+
+ if (!talloc_get_type(vp->data.ptr, char)) {
+ FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" data buffer type should be "
+ "char but is %s\n", file, line, vp->da->name, talloc_get_name(vp->data.ptr));
+ (void) talloc_get_type_abort(vp->data.ptr, char);
+ }
+
+ len = (talloc_array_length(vp->vp_strvalue) - 1);
+ if (vp->vp_length > len) {
+ FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" length %zu is greater than "
+ "char buffer length %zu\n", file, line, vp->da->name, vp->vp_length, len);
+ fr_assert(0);
+ fr_exit_now(1);
+ }
+
+ if (vp->vp_strvalue[vp->vp_length] != '\0') {
+ FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" char buffer not \\0 "
+ "terminated\n", file, line, vp->da->name);
+ fr_assert(0);
+ fr_exit_now(1);
+ }
+
+ parent = talloc_parent(vp->data.ptr);
+ if (parent != vp) {
+ FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR \"%s\" char buffer is not "
+ "parented by VALUE_PAIR %p, instead parented by %p (%s)\n",
+ file, line, vp->da->name,
+ vp, parent, parent ? talloc_get_name(parent) : "NULL");
+ fr_assert(0);
+ fr_exit_now(1);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (vp->da->flags.is_unknown) {
+ (void) talloc_get_type_abort(vp->da, DICT_ATTR);
+ } else {
+ DICT_ATTR const *da;
+
+ /*
+ * Attribute may be present with multiple names
+ */
+ da = dict_attrbyname(vp->da->name);
+ if (!da) {
+ FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR attribute %p \"%s\" (%s) "
+ "not found in global dictionary",
+ file, line, vp->da, vp->da->name,
+ fr_int2str(dict_attr_types, vp->da->type, "<INVALID>"));
+ fr_assert(0);
+ fr_exit_now(1);
+ }
+
+ if (da->type == PW_TYPE_COMBO_IP_ADDR) {
+ da = dict_attrbytype(vp->da->attr, vp->da->vendor, vp->da->type);
+ if (!da) {
+ FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR attribute %p \"%s\" "
+ "variant (%s) not found in global dictionary",
+ file, line, vp->da, vp->da->name,
+ fr_int2str(dict_attr_types, vp->da->type, "<INVALID>"));
+ fr_assert(0);
+ fr_exit_now(1);
+ }
+ }
+
+
+ if (da != vp->da) {
+ FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: VALUE_PAIR "
+ "dictionary pointer %p \"%s\" (%s) "
+ "and global dictionary pointer %p \"%s\" (%s) differ",
+ file, line, vp->da, vp->da->name,
+ fr_int2str(dict_attr_types, vp->da->type, "<INVALID>"),
+ da, da->name, fr_int2str(dict_attr_types, da->type, "<INVALID>"));
+ fr_assert(0);
+ fr_exit_now(1);
+ }
+ }
+}
+
+/*
+ * Verify a pair list
+ */
+void fr_pair_list_verify(char const *file, int line, TALLOC_CTX *expected, VALUE_PAIR *vps, char const *name)
+{
+ vp_cursor_t cursor;
+ VALUE_PAIR *vp;
+ TALLOC_CTX *parent;
+
+ for (vp = fr_cursor_init(&cursor, &vps);
+ vp;
+ vp = fr_cursor_next(&cursor)) {
+ VERIFY_VP(vp);
+
+ parent = talloc_parent(vp);
+ if (expected && (parent != expected)) {
+ FR_FAULT_LOG("CONSISTENCY CHECK FAILED %s[%u]: Expected VALUE_PAIR \"%s\" to be parented "
+ "by %p (%s) name %s, instead parented by %p (%s)\n",
+ file, line, vp->da->name,
+ expected, talloc_get_name(expected), name,
+ parent, parent ? talloc_get_name(parent) : "NULL");
+
+ fr_log_talloc_report(expected);
+ if (parent) fr_log_talloc_report(parent);
+
+ fr_assert(0);
+ fr_exit_now(1);
+ }
+
+ }
+}
+#endif