summaryrefslogtreecommitdiffstats
path: root/src/main/pair.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/pair.c')
-rw-r--r--src/main/pair.c911
1 files changed, 911 insertions, 0 deletions
diff --git a/src/main/pair.c b/src/main/pair.c
new file mode 100644
index 0000000..3725ba1
--- /dev/null
+++ b/src/main/pair.c
@@ -0,0 +1,911 @@
+/*
+ * 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 Valuepair functions that are radiusd-specific and as such do not
+ * belong in the library.
+ * @file main/pair.c
+ *
+ * @ingroup AVP
+ *
+ * @copyright 2000,2006 The FreeRADIUS server project
+ * @copyright 2000 Alan DeKok <aland@ox.org>
+ */
+
+RCSID("$Id$")
+
+#include <ctype.h>
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/rad_assert.h>
+
+struct cmp {
+ DICT_ATTR const *attribute;
+ DICT_ATTR const *from;
+ bool first_only;
+ void *instance; /* module instance */
+ RAD_COMPARE_FUNC compare;
+ struct cmp *next;
+};
+static struct cmp *cmp;
+
+/** Compares check and vp by value.
+ *
+ * Does not call any per-attribute comparison function, but does honour
+ * check.operator. Basically does "vp.value check.op check.value".
+ *
+ * @param request Current request.
+ * @param check rvalue, and operator.
+ * @param vp lvalue.
+ * @return 0 if check and vp are equal, -1 if vp value is less than check value, 1 is vp value is more than check
+ * value, -2 on error.
+ */
+#ifdef HAVE_REGEX
+int radius_compare_vps(REQUEST *request, VALUE_PAIR *check, VALUE_PAIR *vp)
+#else
+int radius_compare_vps(UNUSED REQUEST *request, VALUE_PAIR *check, VALUE_PAIR *vp)
+#endif
+{
+ int ret = 0;
+
+ /*
+ * Check for =* and !* and return appropriately
+ */
+ if (check->op == T_OP_CMP_TRUE) return 0;
+ if (check->op == T_OP_CMP_FALSE) return 1;
+
+#ifdef HAVE_REGEX
+ if ((check->op == T_OP_REG_EQ) || (check->op == T_OP_REG_NE)) {
+ ssize_t slen;
+ regex_t *preg = NULL;
+ regmatch_t rxmatch[REQUEST_MAX_REGEX + 1]; /* +1 for %{0} (whole match) capture group */
+ size_t nmatch = sizeof(rxmatch) / sizeof(regmatch_t);
+
+ char *expr = NULL, *value = NULL;
+ char const *expr_p, *value_p;
+
+ if (!vp) return -2;
+
+ if (check->da->type == PW_TYPE_STRING) {
+ expr_p = check->vp_strvalue;
+ } else {
+ expr_p = expr = vp_aprints_value(request, check, '\0');
+ }
+
+ if (vp->da->type == PW_TYPE_STRING) {
+ value_p = vp->vp_strvalue;
+ } else {
+ value_p = value = vp_aprints_value(request, vp, '\0');
+ }
+
+ if (!expr_p || !value_p) {
+ REDEBUG("Error stringifying operand for regular expression");
+
+ regex_error:
+ talloc_free(preg);
+ talloc_free(expr);
+ talloc_free(value);
+ return -2;
+ }
+
+ /*
+ * Include substring matches.
+ */
+ slen = regex_compile(request, &preg, expr_p, talloc_array_length(expr_p) - 1, false, false, true, true);
+ if (slen <= 0) {
+ REMARKER(expr_p, -slen, fr_strerror());
+
+ goto regex_error;
+ }
+
+ slen = regex_exec(preg, value_p, talloc_array_length(value_p) - 1, rxmatch, &nmatch);
+ if (slen < 0) {
+ RERROR("%s", fr_strerror());
+
+ goto regex_error;
+ }
+
+ if (check->op == T_OP_REG_EQ) {
+ /*
+ * Add in %{0}. %{1}, etc.
+ */
+ regex_sub_to_request(request, &preg, value_p, talloc_array_length(value_p) - 1,
+ rxmatch, nmatch);
+ ret = (slen == 1) ? 0 : -1;
+ } else {
+ ret = (slen != 1) ? 0 : -1;
+ }
+
+ talloc_free(preg);
+ talloc_free(expr);
+ talloc_free(value);
+ goto finish;
+ }
+#endif
+
+ /*
+ * Attributes must be of the same type.
+ *
+ * FIXME: deal with type mismatch properly if one side contain
+ * ABINARY, OCTETS or STRING by converting the other side to
+ * a string
+ *
+ */
+ if (vp->da->type != check->da->type) return -1;
+
+ /*
+ * Tagged attributes are equal if and only if both the
+ * tag AND value match.
+ */
+ if (check->da->flags.has_tag && !TAG_EQ(check->tag, vp->tag)) {
+ ret = ((int) vp->tag) - ((int) check->tag);
+ if (ret != 0) goto finish;
+ }
+
+ /*
+ * Not a regular expression, compare the types.
+ */
+ switch (check->da->type) {
+#ifdef WITH_ASCEND_BINARY
+ /*
+ * Ascend binary attributes can be treated
+ * as opaque objects, I guess...
+ */
+ case PW_TYPE_ABINARY:
+#endif
+ case PW_TYPE_OCTETS:
+ if (vp->vp_length != check->vp_length) {
+ ret = 1; /* NOT equal */
+ break;
+ }
+ ret = memcmp(vp->vp_strvalue, check->vp_strvalue,
+ vp->vp_length);
+ break;
+
+ case PW_TYPE_STRING:
+ ret = strcmp(vp->vp_strvalue,
+ check->vp_strvalue);
+ break;
+
+ case PW_TYPE_BYTE:
+ ret = vp->vp_byte - check->vp_byte;
+ break;
+ case PW_TYPE_SHORT:
+ ret = vp->vp_short - check->vp_short;
+ break;
+ case PW_TYPE_INTEGER:
+ ret = vp->vp_integer - check->vp_integer;
+ break;
+
+ case PW_TYPE_INTEGER64:
+ /*
+ * Don't want integer overflow!
+ */
+ if (vp->vp_integer64 < check->vp_integer64) {
+ ret = -1;
+ } else if (vp->vp_integer64 > check->vp_integer64) {
+ ret = +1;
+ } else {
+ ret = 0;
+ }
+ break;
+
+ case PW_TYPE_SIGNED:
+ if (vp->vp_signed < check->vp_signed) {
+ ret = -1;
+ } else if (vp->vp_signed > check->vp_signed) {
+ ret = +1;
+ } else {
+ ret = 0;
+ }
+ break;
+
+ case PW_TYPE_DATE:
+ ret = vp->vp_date - check->vp_date;
+ break;
+
+ case PW_TYPE_IPV4_ADDR:
+ ret = ntohl(vp->vp_ipaddr) - ntohl(check->vp_ipaddr);
+ break;
+
+ case PW_TYPE_IPV6_ADDR:
+ ret = memcmp(&vp->vp_ipv6addr, &check->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
+ break;
+
+ case PW_TYPE_IPV4_PREFIX:
+ case PW_TYPE_IPV6_PREFIX:
+ ret = fr_pair_cmp_op(check->op, vp, check);
+ if (ret == -1) return -2; // error
+ if (check->op == T_OP_LT || check->op == T_OP_LE)
+ ret = (ret == 1) ? -1 : 1;
+ else if (check->op == T_OP_GT || check->op == T_OP_GE)
+ ret = (ret == 1) ? 1 : -1;
+ else if (check->op == T_OP_CMP_EQ)
+ ret = (ret == 1) ? 0 : -1;
+ break;
+
+ case PW_TYPE_IFID:
+ ret = memcmp(vp->vp_ifid, check->vp_ifid, sizeof(vp->vp_ifid));
+ break;
+
+ default:
+ break;
+ }
+
+finish:
+ if (ret > 0) return 1;
+ if (ret < 0) return -1;
+ return 0;
+}
+
+
+/** Compare check and vp. May call the attribute comparison function.
+ *
+ * Unlike radius_compare_vps() this function will call any attribute-specific
+ * comparison functions registered.
+ *
+ * @param request Current request.
+ * @param req list pairs.
+ * @param check item to compare.
+ * @param check_pairs list.
+ * @param reply_pairs list.
+ * @return 0 if check and vp are equal, -1 if vp value is less than check value, 1 is vp value is more than check
+ * value.
+ */
+int radius_callback_compare(REQUEST *request, VALUE_PAIR *req,
+ VALUE_PAIR *check, VALUE_PAIR *check_pairs,
+ VALUE_PAIR **reply_pairs)
+{
+ struct cmp *c;
+
+ /*
+ * Check for =* and !* and return appropriately
+ */
+ if (check->op == T_OP_CMP_TRUE) return 0;
+ if (check->op == T_OP_CMP_FALSE) return 1;
+
+ /*
+ * See if there is a special compare function.
+ *
+ * FIXME: use new RB-Tree code.
+ */
+ for (c = cmp; c; c = c->next) {
+ if (c->attribute == check->da) {
+ return (c->compare)(c->instance, request, req, check,
+ check_pairs, reply_pairs);
+ }
+ }
+
+ if (!req) return -1; /* doesn't exist, don't compare it */
+
+ return radius_compare_vps(request, check, req);
+}
+
+
+/** Find a comparison function for two attributes.
+ *
+ * @todo this should probably take DA's.
+ * @param attribute to find comparison function for.
+ * @return true if a comparison function was found, else false.
+ */
+int radius_find_compare(DICT_ATTR const *attribute)
+{
+ struct cmp *c;
+
+ for (c = cmp; c; c = c->next) {
+ if (c->attribute == attribute) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/** See what attribute we want to compare with.
+ *
+ * @param attribute to find comparison function for.
+ * @param from reference to compare with
+ * @return true if the comparison callback require a matching attribue in the request, else false.
+ */
+static bool otherattr(DICT_ATTR const *attribute, DICT_ATTR const **from)
+{
+ struct cmp *c;
+
+ for (c = cmp; c; c = c->next) {
+ if (c->attribute == attribute) {
+ *from = c->from;
+ return c->first_only;
+ }
+ }
+
+ *from = attribute;
+ return false;
+}
+
+/** Register a function as compare function.
+ *
+ * @param name the attribute comparison to register
+ * @param from the attribute we want to compare with. Normally this is the same as attribute.
+ * If null call the comparison function on every attributes in the request if first_only is false
+ * @param first_only will decide if we loop over the request attributes or stop on the first one
+ * @param func comparison function
+ * @param instance argument to comparison function
+ * @return 0
+ */
+int paircompare_register_byname(char const *name, DICT_ATTR const *from,
+ bool first_only, RAD_COMPARE_FUNC func, void *instance)
+{
+ ATTR_FLAGS flags;
+ DICT_ATTR const *da;
+
+ memset(&flags, 0, sizeof(flags));
+ flags.compare = 1;
+
+ da = dict_attrbyname(name);
+ if (da) {
+ if (!da->flags.compare) {
+ fr_strerror_printf("Attribute '%s' already exists.", name);
+ return -1;
+ }
+ } else if (from) {
+ if (dict_addattr(name, -1, 0, from->type, flags) < 0) {
+ fr_strerror_printf("Failed creating attribute '%s'", name);
+ return -1;
+ }
+
+ da = dict_attrbyname(name);
+ if (!da) {
+ fr_strerror_printf("Failed finding attribute '%s'", name);
+ return -1;
+ }
+
+ DEBUG("Creating attribute %s", name);
+ }
+
+ return paircompare_register(da, from, first_only, func, instance);
+}
+
+/** Register a function as compare function.
+ *
+ * @param attribute to register comparison function for.
+ * @param from the attribute we want to compare with. Normally this is the same as attribute.
+ * If null call the comparison function on every attributes in the request if first_only is false
+ * @param first_only will decide if we loop over the request attributes or stop on the first one
+ * @param func comparison function
+ * @param instance argument to comparison function
+ * @return 0
+ */
+int paircompare_register(DICT_ATTR const *attribute, DICT_ATTR const *from,
+ bool first_only, RAD_COMPARE_FUNC func, void *instance)
+{
+ struct cmp *c;
+
+ rad_assert(attribute != NULL);
+
+ paircompare_unregister(attribute, func);
+
+ c = rad_malloc(sizeof(struct cmp));
+
+ c->compare = func;
+ c->attribute = attribute;
+ c->from = from;
+ c->first_only = first_only;
+ c->instance = instance;
+ c->next = cmp;
+ cmp = c;
+
+ return 0;
+}
+
+/** Unregister comparison function for an attribute
+ *
+ * @param attribute dict reference to unregister for.
+ * @param func comparison function to remove.
+ */
+void paircompare_unregister(DICT_ATTR const *attribute, RAD_COMPARE_FUNC func)
+{
+ struct cmp *c, *last;
+
+ last = NULL;
+ for (c = cmp; c; c = c->next) {
+ if (c->attribute == attribute && c->compare == func) {
+ break;
+ }
+ last = c;
+ }
+
+ if (c == NULL) return;
+
+ if (last != NULL) {
+ last->next = c->next;
+ } else {
+ cmp = c->next;
+ }
+
+ free(c);
+}
+
+/** Unregister comparison function for a module
+ *
+ * All paircompare() functions for this module will be unregistered.
+ *
+ * @param instance the module instance
+ */
+void paircompare_unregister_instance(void *instance)
+{
+ struct cmp *c, **tail;
+
+ tail = &cmp;
+ while ((c = *tail) != NULL) {
+ if (c->instance == instance) {
+ *tail = c->next;
+ free(c);
+ continue;
+ }
+
+ tail = &(c->next);
+ }
+}
+
+/** Compare two pair lists except for the password information.
+ *
+ * For every element in "check" at least one matching copy must be present
+ * in "reply".
+ *
+ * @param[in] request Current request.
+ * @param[in] req_list request valuepairs.
+ * @param[in] check Check/control valuepairs.
+ * @param[in,out] rep_list Reply value pairs.
+ *
+ * @return 0 on match.
+ */
+int paircompare(REQUEST *request, VALUE_PAIR *req_list, VALUE_PAIR *check,
+ VALUE_PAIR **rep_list)
+{
+ vp_cursor_t cursor;
+ VALUE_PAIR *check_item;
+ VALUE_PAIR *auth_item = NULL;
+ DICT_ATTR const *from;
+
+ int result = 0;
+ int compare;
+ bool first_only;
+
+ for (check_item = fr_cursor_init(&cursor, &check);
+ check_item;
+ check_item = fr_cursor_next(&cursor)) {
+ /*
+ * If the user is setting a configuration value,
+ * then don't bother comparing it to any attributes
+ * sent to us by the user. It ALWAYS matches.
+ */
+ if ((check_item->op == T_OP_SET) ||
+ (check_item->op == T_OP_ADD)) {
+ continue;
+ }
+
+ if (!check_item->da->vendor) switch (check_item->da->attr) {
+ /*
+ * Attributes we skip during comparison.
+ * These are "server" check items.
+ */
+ case PW_CRYPT_PASSWORD:
+ case PW_AUTH_TYPE:
+ case PW_AUTZ_TYPE:
+ case PW_ACCT_TYPE:
+ case PW_SESSION_TYPE:
+ case PW_STRIP_USER_NAME:
+ continue;
+
+ /*
+ * IF the password attribute exists, THEN
+ * we can do comparisons against it. If not,
+ * then the request did NOT contain a
+ * User-Password attribute, so we CANNOT do
+ * comparisons against it.
+ *
+ * This hack makes CHAP-Password work..
+ */
+ case PW_USER_PASSWORD:
+ if (check_item->op == T_OP_CMP_EQ) {
+ WARN("Found User-Password == \"...\"");
+ WARN("Are you sure you don't mean Cleartext-Password?");
+ WARN("See \"man rlm_pap\" for more information");
+ }
+ if (fr_pair_find_by_num(req_list, PW_USER_PASSWORD, 0, TAG_ANY) == NULL) {
+ continue;
+ }
+ break;
+ }
+
+ /*
+ * See if this item is present in the request.
+ */
+ first_only = otherattr(check_item->da, &from);
+
+ auth_item = req_list;
+ try_again:
+ if (!first_only) {
+ while (auth_item != NULL) {
+ VERIFY_VP(auth_item);
+ if ((auth_item->da == from) || (!from)) {
+ break;
+ }
+ auth_item = auth_item->next;
+ }
+ }
+
+ /*
+ * Not found, it's not a match.
+ */
+ if (auth_item == NULL) {
+ /*
+ * Didn't find it. If we were *trying*
+ * to not find it, then we succeeded.
+ */
+ if (check_item->op == T_OP_CMP_FALSE) {
+ continue;
+ } else {
+ return -1;
+ }
+ }
+
+ /*
+ * Else we found it, but we were trying to not
+ * find it, so we failed.
+ */
+ if (check_item->op == T_OP_CMP_FALSE) {
+ return -1;
+ }
+
+ /*
+ * We've got to xlat the string before doing
+ * the comparison.
+ */
+ radius_xlat_do(request, check_item);
+
+ /*
+ * OK it is present now compare them.
+ */
+ compare = radius_callback_compare(request, auth_item,
+ check_item, check, rep_list);
+
+ switch (check_item->op) {
+ case T_OP_EQ:
+ default:
+ RWDEBUG("Invalid operator '%s' for item %s: reverting to '=='",
+ fr_int2str(fr_tokens, check_item->op, "<INVALID>"), check_item->da->name);
+ /* FALL-THROUGH */
+ case T_OP_CMP_TRUE:
+ case T_OP_CMP_FALSE:
+ case T_OP_CMP_EQ:
+ if (compare != 0) result = -1;
+ break;
+
+ case T_OP_NE:
+ if (compare == 0) result = -1;
+ break;
+
+ case T_OP_LT:
+ if (compare >= 0) result = -1;
+ break;
+
+ case T_OP_GT:
+ if (compare <= 0) result = -1;
+ break;
+
+ case T_OP_LE:
+ if (compare > 0) result = -1;
+ break;
+
+ case T_OP_GE:
+ if (compare < 0) result = -1;
+ break;
+
+#ifdef HAVE_REGEX
+ case T_OP_REG_EQ:
+ case T_OP_REG_NE:
+ if (compare != 0) result = -1;
+ break;
+#endif
+ } /* switch over the operator of the check item */
+
+ /*
+ * This attribute didn't match, but maybe there's
+ * another of the same attribute, which DOES match.
+ */
+ if ((result != 0) && (!first_only)) {
+ fr_assert(auth_item != NULL);
+ auth_item = auth_item->next;
+ result = 0;
+ goto try_again;
+ }
+
+ } /* for every entry in the check item list */
+
+ return result;
+}
+
+/** Expands an attribute marked with fr_pair_mark_xlat
+ *
+ * Writes the new value to the vp.
+ *
+ * @param request Current request.
+ * @param vp to expand.
+ * @return 0 if successful else -1 (on xlat failure) or -2 (on parse failure).
+ * On failure pair will still no longer be marked for xlat expansion.
+ */
+int radius_xlat_do(REQUEST *request, VALUE_PAIR *vp)
+{
+ ssize_t slen;
+
+ char *expanded = NULL;
+ if (vp->type != VT_XLAT) return 0;
+
+ vp->type = VT_DATA;
+
+ slen = radius_axlat(&expanded, request, vp->value.xlat, NULL, NULL);
+ rad_const_free(vp->value.xlat);
+ vp->value.xlat = NULL;
+ if (slen < 0) {
+ return -1;
+ }
+
+ /*
+ * Parse the string into a new value.
+ *
+ * If the VALUE_PAIR is being used in a regular expression
+ * then we just want to copy the new value in unmolested.
+ */
+ if ((vp->op == T_OP_REG_EQ) || (vp->op == T_OP_REG_NE)) {
+ fr_pair_value_strsteal(vp, expanded);
+ return 0;
+ }
+
+ if (fr_pair_value_from_str(vp, expanded, -1) < 0){
+ talloc_free(expanded);
+ return -2;
+ }
+
+ talloc_free(expanded);
+
+ return 0;
+}
+
+/** Create a VALUE_PAIR and add it to a list of VALUE_PAIR s
+ *
+ * @note This function ALWAYS returns. If we're OOM, then it causes the
+ * @note server to exit, so you don't need to check the return value.
+ *
+ * @param[in] ctx for talloc
+ * @param[out] vps List to add new VALUE_PAIR to, if NULL will just
+ * return VALUE_PAIR.
+ * @param[in] attribute number.
+ * @param[in] vendor number.
+ * @return a new VLAUE_PAIR or causes server to exit on error.
+ */
+VALUE_PAIR *radius_pair_create(TALLOC_CTX *ctx, VALUE_PAIR **vps,
+ unsigned int attribute, unsigned int vendor)
+{
+ VALUE_PAIR *vp;
+
+ vp = fr_pair_afrom_num(ctx, attribute, vendor);
+ if (!vp) {
+ ERROR("No memory!");
+ rad_assert("No memory" == NULL);
+ fr_exit_now(1);
+ }
+
+ if (vps) fr_pair_add(vps, vp);
+
+ return vp;
+}
+
+/** Print a single valuepair to stderr or error log.
+ *
+ * @param[in] vp list to print.
+ */
+void debug_pair(VALUE_PAIR *vp)
+{
+ if (!vp || !rad_debug_lvl || !fr_log_fp) return;
+
+ vp_print(fr_log_fp, vp);
+}
+
+/** Print a single valuepair to stderr or error log.
+ *
+ * @param[in] level Debug level (1-4).
+ * @param[in] request to read logging params from.
+ * @param[in] vp to print.
+ * @param[in] prefix (optional).
+ */
+void rdebug_pair(log_lvl_t level, REQUEST *request, VALUE_PAIR *vp, char const *prefix)
+{
+ char buffer[768];
+ if (!vp || !request || !request->log.func) return;
+
+ if (!radlog_debug_enabled(L_DBG, level, request)) return;
+
+ if (vp->da->flags.secret && request->root && request->root->suppress_secrets && (rad_debug_lvl < 3)) {
+ RDEBUGX(level, "%s%s = <<< secret >>>", prefix ? prefix : "", vp->da->name);
+ return;
+ }
+
+ vp_prints(buffer, sizeof(buffer), vp);
+ RDEBUGX(level, "%s%s", prefix ? prefix : "", buffer);
+}
+
+/** Print a list of VALUE_PAIRs.
+ *
+ * @param[in] level Debug level (1-4).
+ * @param[in] request to read logging params from.
+ * @param[in] vp to print.
+ * @param[in] prefix (optional).
+ */
+void rdebug_pair_list(log_lvl_t level, REQUEST *request, VALUE_PAIR *vp, char const *prefix)
+{
+ vp_cursor_t cursor;
+ char buffer[768];
+ if (!vp || !request || !request->log.func) return;
+
+ if (!radlog_debug_enabled(L_DBG, level, request)) return;
+
+ RINDENT();
+ for (vp = fr_cursor_init(&cursor, &vp);
+ vp;
+ vp = fr_cursor_next(&cursor)) {
+ VERIFY_VP(vp);
+
+ if (vp->da->flags.secret && request->root && request->root->suppress_secrets && (rad_debug_lvl < 3)) {
+ RDEBUGX(level, "%s%s = <<< secret >>>", prefix ? prefix : "", vp->da->name);
+ continue;
+ }
+
+ vp_prints(buffer, sizeof(buffer), vp);
+ RDEBUGX(level, "%s%s", prefix ? prefix : "", buffer);
+ }
+ REXDENT();
+}
+
+/** Print a list of protocol VALUE_PAIRs.
+ *
+ * @param[in] level Debug level (1-4).
+ * @param[in] request to read logging params from.
+ * @param[in] vp to print.
+ */
+void rdebug_proto_pair_list(log_lvl_t level, REQUEST *request, VALUE_PAIR *vp)
+{
+ vp_cursor_t cursor;
+ char buffer[768];
+ if (!vp || !request || !request->log.func) return;
+
+ if (!radlog_debug_enabled(L_DBG, level, request)) return;
+
+ RINDENT();
+ for (vp = fr_cursor_init(&cursor, &vp);
+ vp;
+ vp = fr_cursor_next(&cursor)) {
+ VERIFY_VP(vp);
+ if ((vp->da->vendor == 0) &&
+ ((vp->da->attr & 0xFFFF) > 0xff)) continue;
+
+ if (vp->da->flags.secret && request->root && request->root->suppress_secrets && (rad_debug_lvl < 3)) {
+ RDEBUGX(level, "%s = <<< secret >>>", vp->da->name);
+ continue;
+ }
+
+ vp_prints(buffer, sizeof(buffer), vp);
+ RDEBUGX(level, "%s", buffer);
+ }
+ REXDENT();
+}
+
+/** Return a VP from the specified request.
+ *
+ * @param out where to write the pointer to the resolved VP.
+ * Will be NULL if the attribute couldn't be resolved.
+ * @param request current request.
+ * @param name attribute name including qualifiers.
+ * @return -4 if either the attribute or qualifier were invalid, and the same error codes as tmpl_find_vp for other
+ * error conditions.
+ */
+int radius_get_vp(VALUE_PAIR **out, REQUEST *request, char const *name)
+{
+ int rcode;
+ vp_tmpl_t vpt;
+
+ *out = NULL;
+
+ if (tmpl_from_attr_str(&vpt, name, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false) <= 0) {
+ return -4;
+ }
+
+ rcode = tmpl_find_vp(out, request, &vpt);
+
+ return rcode;
+}
+
+/** Copy VP(s) from the specified request.
+ *
+ * @param ctx to alloc new VALUE_PAIRs in.
+ * @param out where to write the pointer to the copied VP.
+ * Will be NULL if the attribute couldn't be resolved.
+ * @param request current request.
+ * @param name attribute name including qualifiers.
+ * @return -4 if either the attribute or qualifier were invalid, and the same error codes as tmpl_find_vp for other
+ * error conditions.
+ */
+int radius_copy_vp(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, char const *name)
+{
+ int rcode;
+ vp_tmpl_t vpt;
+
+ *out = NULL;
+
+ if (tmpl_from_attr_str(&vpt, name, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false) <= 0) {
+ return -4;
+ }
+
+ rcode = tmpl_copy_vps(ctx, out, request, &vpt);
+
+ return rcode;
+}
+
+void module_failure_msg(REQUEST *request, char const *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmodule_failure_msg(request, fmt, ap);
+ va_end(ap);
+}
+
+/** Add a module failure message VALUE_PAIR to the request
+ */
+void vmodule_failure_msg(REQUEST *request, char const *fmt, va_list ap)
+{
+ char *p;
+ VALUE_PAIR *vp;
+ va_list aq;
+
+ if (!fmt || !request || !request->packet) {
+ return;
+ }
+
+ /*
+ * If we don't copy the original ap we get a segfault from vasprintf. This is apparently
+ * due to ap sometimes being implemented with a stack offset which is invalidated if
+ * ap is passed into another function. See here:
+ * http://julipedia.meroh.net/2011/09/using-vacopy-to-safely-pass-ap.html
+ *
+ * I don't buy that explanation, but doing a va_copy here does prevent SEGVs seen when
+ * running unit tests which generate errors under CI.
+ */
+ va_copy(aq, ap);
+ p = talloc_vasprintf(request, fmt, aq);
+ va_end(aq);
+
+ MEM(vp = pair_make_request("Module-Failure-Message", NULL, T_OP_ADD));
+ if (request->module && (request->module[0] != '\0')) {
+ fr_pair_value_sprintf(vp, "%s: %s", request->module, p);
+ } else {
+ fr_pair_value_sprintf(vp, "%s", p);
+ }
+ talloc_free(p);
+}