From: Arran Cudbard-Bell Date: Wed, 5 Nov 2014 21:08:35 +0000 (-0500) Subject: Add support for multivalued attribute comparison X-Git-Tag: branch_3_1_x~4766 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b2e90ece1bc268fc457349a43dfdefaff8ceff8b;p=thirdparty%2Ffreeradius-server.git Add support for multivalued attribute comparison --- diff --git a/src/include/radiusd.h b/src/include/radiusd.h index 1e6d9b05df8..4b346a083e2 100644 --- a/src/include/radiusd.h +++ b/src/include/radiusd.h @@ -735,7 +735,7 @@ void mark_home_server_dead(home_server_t *home, struct timeval *when); /* evaluate.c */ typedef struct fr_cond_t fr_cond_t; -int radius_expand_tmpl(char **out, REQUEST *request, value_pair_tmpl_t const *vpt); +ssize_t radius_expand_tmpl(char **out, REQUEST *request, value_pair_tmpl_t const *vpt); int radius_evaluate_tmpl(REQUEST *request, int modreturn, int depth, value_pair_tmpl_t const *vpt); int radius_evaluate_map(REQUEST *request, int modreturn, int depth, diff --git a/src/include/tmpl.h b/src/include/tmpl.h index 9b04505b204..e7911ef8eee 100644 --- a/src/include/tmpl.h +++ b/src/include/tmpl.h @@ -129,6 +129,7 @@ typedef struct value_pair_tmpl_t { //!< attribute, just the string id for //!< the attribute. size_t len; //!< Name length. + bool iflag; //!< Case insensitive (if operand is used in regex comparison) union { /* @@ -149,10 +150,7 @@ typedef struct value_pair_tmpl_t { xlat_exp_t *xlat; //!< pre-parsed xlat_exp_t #ifdef HAVE_REGEX - struct { - regex_t *comp; //!< pre-parsed regex_t - bool iflag; //!< Case insensitive - } preg; + regex_t *preg; //!< pre-parsed regex_t #endif } data; } value_pair_tmpl_t; @@ -173,8 +171,8 @@ typedef struct value_pair_tmpl_t { #define tmpl_data_value data.literal.data #ifdef HAVE_REGEX -# define tmpl_preg data.preg.comp -# define tmpl_iflag data.preg.iflag +# define tmpl_preg data.preg +# define tmpl_iflag iflag #endif #ifndef WITH_VERIFY_PTR diff --git a/src/lib/value.c b/src/lib/value.c index 73a9a125c36..9cebba1b519 100644 --- a/src/lib/value.c +++ b/src/lib/value.c @@ -542,7 +542,6 @@ ssize_t value_data_from_str(TALLOC_CTX *ctx, value_data_t *dst, goto finish; } - do_octets: len -= 2; @@ -968,6 +967,13 @@ ssize_t value_data_cast(TALLOC_CTX *ctx, value_data_t *dst, { if (!fr_assert(dst_type != src_type)) return -1; + /* + * Deserialise a value_data_t + */ + if (src_type == PW_TYPE_STRING) { + return value_data_from_str(ctx, dst, &dst_type, dst_enumv, src->strvalue, src_len); + } + /* * Converts the src data to octets with no processing. */ @@ -990,13 +996,6 @@ ssize_t value_data_cast(TALLOC_CTX *ctx, value_data_t *dst, return talloc_array_length(dst->strvalue) - 1; } - /* - * Deserialise a value_data_t - */ - if (src_type == PW_TYPE_STRING) { - return value_data_from_str(ctx, dst, &dst_type, dst_enumv, src->strvalue, src_len); - } - if ((src_type == PW_TYPE_IFID) && (dst_type == PW_TYPE_INTEGER64)) { memcpy(&dst->integer64, &src->ifid, sizeof(src->ifid)); diff --git a/src/main/evaluate.c b/src/main/evaluate.c index 88564843f12..9f773e37cfe 100644 --- a/src/main/evaluate.c +++ b/src/main/evaluate.c @@ -66,12 +66,14 @@ FR_NAME_NUMBER const modreturn_table[] = { }; -static int all_digits(char const *string) +static bool all_digits(char const *string) { char const *p = string; rad_assert(p != NULL); + if (*p == '\0') return false; + if (*p == '-') p++; while (isdigit((int) *p)) p++; @@ -88,9 +90,10 @@ static int all_digits(char const *string) * @param vpt to evaluate. * @return -1 on error, else 0. */ -int radius_expand_tmpl(char **out, REQUEST *request, value_pair_tmpl_t const *vpt) +ssize_t radius_expand_tmpl(char **out, REQUEST *request, value_pair_tmpl_t const *vpt) { VALUE_PAIR *vp; + ssize_t slen; *out = NULL; rad_assert(vpt->type != TMPL_TYPE_LIST); @@ -99,35 +102,42 @@ int radius_expand_tmpl(char **out, REQUEST *request, value_pair_tmpl_t const *vp switch (vpt->type) { case TMPL_TYPE_LITERAL: - EVAL_DEBUG("TMPL LITERAL"); + EVAL_DEBUG("EXPAND TMPL LITERAL"); *out = talloc_typed_strdup(request, vpt->name); + slen = talloc_array_length(*out) - 1; break; case TMPL_TYPE_EXEC: - EVAL_DEBUG("TMPL EXEC"); + EVAL_DEBUG("EXPAND TMPL EXEC"); *out = talloc_array(request, char, 1024); if (radius_exec_program(*out, 1024, NULL, request, vpt->name, NULL, true, false, EXEC_TIMEOUT) != 0) { TALLOC_FREE(*out); return -1; } + slen = strlen(*out); break; case TMPL_TYPE_XLAT: - EVAL_DEBUG("TMPL XLAT"); + EVAL_DEBUG("EXPAND TMPL XLAT"); /* Error in expansion, this is distinct from zero length expansion */ - if (radius_axlat(out, request, vpt->name, NULL, NULL) < 0) { + slen = radius_axlat(out, request, vpt->name, NULL, NULL); + if (slen < 0) { rad_assert(!*out); - return -1; + return slen; } + slen = strlen(*out); break; case TMPL_TYPE_XLAT_STRUCT: - EVAL_DEBUG("TMPL XLAT_STRUCT"); + EVAL_DEBUG("EXPAND TMPL XLAT STRUCT"); /* Error in expansion, this is distinct from zero length expansion */ - if (radius_axlat_struct(out, request, vpt->tmpl_xlat, NULL, NULL) < 0) { + slen = radius_axlat_struct(out, request, vpt->tmpl_xlat, NULL, NULL); + if (slen < 0) { rad_assert(!*out); - return -1; + return slen; } + slen = strlen(*out); + RDEBUG2("EXPAND %s", vpt->name); /* xlat_struct doesn't do this */ RDEBUG2(" --> %s", *out); break; @@ -136,33 +146,38 @@ int radius_expand_tmpl(char **out, REQUEST *request, value_pair_tmpl_t const *vp { int ret; - EVAL_DEBUG("TMPL ATTR"); + EVAL_DEBUG("EXPANDTMPL ATTR"); ret = tmpl_find_vp(&vp, request, vpt); if (ret < 0) return -2; *out = vp_aprints_value(request, vp, '\0'); if (!*out) return -1; + slen = talloc_array_length(*out) - 1; } break; - /* - * We should never be expanding these. - */ + /* + * We should never be expanding these. + */ case TMPL_TYPE_DATA: case TMPL_TYPE_REGEX: case TMPL_TYPE_REGEX_STRUCT: rad_assert(0 == 1); + slen = -1; /* FALL-THROUGH */ default: + slen = 0; break; } - EVAL_DEBUG("Expand tmpl --> %s", *out); - return 0; + EVAL_DEBUG(" --> %s", *out); + return slen; } /** Evaluate a template + * + * Converts a value_pair_tmpl_t to a boolean value. * * @param[in] request the REQUEST * @param[in] modreturn the previous module return code @@ -170,8 +185,7 @@ int radius_expand_tmpl(char **out, REQUEST *request, value_pair_tmpl_t const *vp * @param[in] vpt the template to evaluate * @return -1 on error, 0 for "no match", 1 for "match". */ -int radius_evaluate_tmpl(REQUEST *request, int modreturn, UNUSED int depth, - value_pair_tmpl_t const *vpt) +int radius_evaluate_tmpl(REQUEST *request, int modreturn, UNUSED int depth, value_pair_tmpl_t const *vpt) { int rcode; int modcode; @@ -237,31 +251,37 @@ int radius_evaluate_tmpl(REQUEST *request, int modreturn, UNUSED int depth, } #ifdef HAVE_REGEX -static int do_regex(REQUEST *request, value_pair_map_t const *map) +/** Perform a regular expressions comparison between two operands + * + * @return -1 on error, 0 for "no match", 1 for "match". + */ +static int cond_do_regex(REQUEST *request, fr_cond_t const *c, + PW_TYPE lhs_type, value_data_t const *lhs, UNUSED size_t lhs_len, + PW_TYPE rhs_type, value_data_t const *rhs, UNUSED size_t rhs_len) { - int compare, rcode, ret; + value_pair_map_t const *map = c->data.map; + + int compare, ret; regex_t reg, *preg = NULL; - char *lhs = NULL, *rhs = NULL; regmatch_t rxmatch[REQUEST_MAX_REGEX + 1]; - /* - * Expand and then compile it. - */ + rad_assert(lhs_type == PW_TYPE_STRING); + + EVAL_DEBUG("CMP WITH REGEX %s", map->rhs->tmpl_iflag ? "CASE INSENSITIVE" : "CASE SENSITIVE"); + switch (map->rhs->type) { - case TMPL_TYPE_XLAT_STRUCT: /* pre-compiled to an xlat thing */ - rcode = radius_expand_tmpl(&rhs, request, map->rhs); - if (rcode < 0) { - EVAL_DEBUG("FAIL %d", __LINE__); - return -1; - } - rad_assert(rhs != NULL); + case TMPL_TYPE_REGEX_STRUCT: /* pre-compiled to a regex */ + preg = map->rhs->tmpl_preg; + break; - compare = regcomp(®, rhs, REG_EXTENDED | (map->rhs->tmpl_iflag ? REG_ICASE : 0)); - if (compare != 0) { + default: + rad_assert(rhs_type == PW_TYPE_STRING); + ret = regcomp(®, rhs->strvalue, REG_EXTENDED | (map->rhs->tmpl_iflag ? REG_ICASE : 0)); + if (ret != 0) { if (debug_flag) { char errbuf[128]; - regerror(compare, ®, errbuf, sizeof(errbuf)); + regerror(ret, ®, errbuf, sizeof(errbuf)); ERROR("Failed compiling regular expression: %s", errbuf); } EVAL_DEBUG("FAIL %d", __LINE__); @@ -270,37 +290,17 @@ static int do_regex(REQUEST *request, value_pair_map_t const *map) } preg = ® break; - - case TMPL_TYPE_REGEX_STRUCT: /* pre-compiled to a regex */ - preg = map->rhs->tmpl_preg; - break; - - default: - rad_assert(0); - ret = -1; - goto finish; } - rcode = radius_expand_tmpl(&lhs, request, map->lhs); - if (rcode < 0) { - EVAL_DEBUG("FAIL %d", __LINE__); - ret = -1; - goto finish; - } - rad_assert(lhs != NULL); - /* * regexec doesn't initialise unused elements */ memset(&rxmatch, 0, sizeof(rxmatch)); - compare = regexec(preg, lhs, REQUEST_MAX_REGEX + 1, rxmatch, 0); - rad_regcapture(request, compare, lhs, rxmatch); + compare = regexec(preg, lhs->strvalue, REQUEST_MAX_REGEX + 1, rxmatch, 0); + rad_regcapture(request, compare, lhs->strvalue, rxmatch); ret = (compare == 0); finish: - talloc_free(rhs); - talloc_free(lhs); - /* * regcomp allocs extra memory for the expression, so if the * result wasn't cached we need to free it here. @@ -311,550 +311,402 @@ finish: } #endif -/* - * Copy data from src to dst, where the attributes are of - * different type. - */ -static int do_cast_copy(VALUE_PAIR *dst, VALUE_PAIR const *src) +#ifdef WITH_EVAL_DEBUG +static void cond_print_operands(REQUEST *request, + PW_TYPE lhs_type, value_data_t const *lhs, size_t lhs_len, + PW_TYPE rhs_type, value_data_t const *rhs, size_t rhs_len) { - rad_assert(dst->da->type != src->da->type); + if (lhs) { + if (lhs_type == PW_TYPE_STRING) { + EVAL_DEBUG("LHS: \"%s\" (%zu)" , lhs->strvalue, lhs_len); + } else { + char *lhs_hex; - if (dst->da->type == PW_TYPE_STRING) { - dst->vp_strvalue = vp_aprints_value(dst, src, '\0'); - dst->length = talloc_array_length(dst->vp_strvalue) - 1; - return 0; - } + lhs_hex = talloc_array(request, char, (lhs_len * 2) + 1); + fr_bin2hex(lhs_hex, (uint8_t const *)lhs, lhs_len); - if (dst->da->type == PW_TYPE_OCTETS) { - if (src->da->type == PW_TYPE_STRING) { - pairmemcpy(dst, src->vp_octets, src->length); /* Copy embedded NULLs */ - } else { - pairmemcpy(dst, (uint8_t const *) &src->data, src->length); + EVAL_DEBUG("LHS: 0x%s (%zu)", lhs_hex, lhs_len); + + talloc_free(lhs_hex); } - return 0; + } else { + EVAL_DEBUG("LHS: VIRTUAL"); } - if (src->da->type == PW_TYPE_STRING) { - return pairparsevalue(dst, src->vp_strvalue, -1); - } + if (rhs) { + if (rhs_type == PW_TYPE_STRING) { + EVAL_DEBUG("RHS: \"%s\" (%zu)" , rhs->strvalue, rhs_len); + } else { + char *rhs_hex; - if ((src->da->type == PW_TYPE_IFID) && - (dst->da->type == PW_TYPE_INTEGER64)) { - memcpy(&dst->vp_integer64, &src->vp_ifid, sizeof(src->vp_ifid)); - dst->vp_integer64 = htonll(dst->vp_integer64); - return 0; - } + rhs_hex = talloc_array(request, char, (rhs_len * 2) + 1); + fr_bin2hex(rhs_hex, (uint8_t const *)rhs, rhs_len); - if ((src->da->type == PW_TYPE_INTEGER64) && - (dst->da->type == PW_TYPE_ETHERNET)) { - uint8_t array[8]; - uint64_t i; + EVAL_DEBUG("RHS: 0x%s (%zu)", rhs_hex, rhs_len); - i = htonll(src->vp_integer64); - memcpy(array, &i, 8); + talloc_free(rhs_hex); + } + } else { + EVAL_DEBUG("RHS: COMPILED"); + } +} +#endif - /* - * For OUIs in the DB. - */ - if ((array[0] != 0) || (array[1] != 0)) return -1; +/** Call the correct data comparison function for the condition + * + * Deals with regular expression comparisons, virtual attribute + * comparisons, and data comparisons. + * + * @return -1 on error, 0 for "no match", 1 for "match". + */ +static int cond_cmp_values(REQUEST *request, fr_cond_t const *c, + PW_TYPE lhs_type, value_data_t const *lhs, size_t lhs_len, + PW_TYPE rhs_type, value_data_t const *rhs, size_t rhs_len) +{ + value_pair_map_t const *map = c->data.map; + int rcode; - memcpy(&dst->vp_ether, &array[2], 6); - dst->length = 6; - return 0; - } +#ifdef WITH_EVAL_DEBUG + EVAL_DEBUG("CMP OPERANDS"); + cond_print_operands(request, lhs_type, lhs, lhs_len, rhs_type, rhs, rhs_len); +#endif +#ifdef HAVE_REGEX /* - * For integers, we allow the casting of a SMALL type to - * a larger type, but not vice-versa. + * Regex comparison */ - if (dst->da->type == PW_TYPE_INTEGER64) { - switch (src->da->type) { - case PW_TYPE_BYTE: - dst->vp_integer64 = src->vp_byte; - break; - - case PW_TYPE_SHORT: - dst->vp_integer64 = src->vp_short; - break; - - case PW_TYPE_INTEGER: - dst->vp_integer64 = src->vp_integer; - break; - - case PW_TYPE_OCTETS: - goto do_octets; - - default: - EVAL_DEBUG("Invalid cast to integer64"); - return -1; - - } - return 0; + if (map->op == T_OP_REG_EQ) { + rcode = cond_do_regex(request, c, lhs_type, lhs, lhs_len, rhs_type, rhs, rhs_len); + goto finish; } - +#endif /* - * We can compare LONG integers to SHORTER ones, so long - * as the long one is on the LHS. + * Virtual attribute comparison. */ - if (dst->da->type == PW_TYPE_INTEGER) { - switch (src->da->type) { - case PW_TYPE_BYTE: - dst->vp_integer = src->vp_byte; - break; + if (c->pass2_fixup == PASS2_PAIRCOMPARE) { + VALUE_PAIR *vp; - case PW_TYPE_SHORT: - dst->vp_integer = src->vp_short; - break; - - case PW_TYPE_OCTETS: - goto do_octets; - - default: - EVAL_DEBUG("Invalid cast to integer"); - return -1; + EVAL_DEBUG("CMP WITH PAIRCOMPARE"); + rad_assert(map->lhs->type == TMPL_TYPE_ATTR); - } - return 0; - } - - if (dst->da->type == PW_TYPE_SHORT) { - switch (src->da->type) { - case PW_TYPE_BYTE: - dst->vp_short = src->vp_byte; - break; + vp = pairalloc(request, map->lhs->tmpl_da); + vp->op = c->data.map->op; + pairdatacpy(vp, rhs_type, rhs, rhs_len); - case PW_TYPE_OCTETS: - goto do_octets; - - default: - EVAL_DEBUG("Invalid cast to short"); - return -1; - - } - return 0; + rcode = paircompare(request, request->packet->vps, vp, NULL); + rcode = (rcode == 0) ? 1 : 0; + talloc_free(vp); + goto finish; } /* - * The attribute we've found has to have a size which is - * compatible with the type of the destination cast. + * At this point both operands should have been normalised + * to the same type, and there's no special comparisons + * left. */ - if ((src->length < dict_attr_sizes[dst->da->type][0]) || - (src->length > dict_attr_sizes[dst->da->type][1])) { - EVAL_DEBUG("Casted attribute is wrong size (%u)", (unsigned int) src->length); - return -1; - } - - if (src->da->type == PW_TYPE_OCTETS) { - do_octets: - switch (dst->da->type) { - case PW_TYPE_INTEGER64: - dst->vp_integer64 = ntohll(*(uint64_t const *) src->vp_octets); - break; + rad_assert(lhs_type == rhs_type); - case PW_TYPE_INTEGER: - case PW_TYPE_DATE: - case PW_TYPE_SIGNED: - dst->vp_integer = ntohl(*(uint32_t const *) src->vp_octets); - break; - - case PW_TYPE_SHORT: - dst->vp_short = ntohs(*(uint16_t const *) src->vp_octets); - break; - - case PW_TYPE_BYTE: - dst->vp_byte = src->vp_octets[0]; - break; - - default: - memcpy(&dst->data, src->vp_octets, src->length); - break; - } + EVAL_DEBUG("CMP WITH VALUE DATA"); + rcode = value_data_cmp_op(map->op, lhs_type, lhs, lhs_len, rhs_type, rhs, rhs_len); +finish: + switch (rcode) { + case 0: + EVAL_DEBUG("FALSE"); + break; - dst->length = src->length; - return 0; - } + case 1: + EVAL_DEBUG("TRUE"); + break; - /* - * Convert host order to network byte order. - */ - if ((dst->da->type == PW_TYPE_IPV4_ADDR) && - ((src->da->type == PW_TYPE_INTEGER) || - (src->da->type == PW_TYPE_DATE) || - (src->da->type == PW_TYPE_SIGNED))) { - dst->vp_ipaddr = htonl(src->vp_integer); - - } else if ((src->da->type == PW_TYPE_IPV4_ADDR) && - ((dst->da->type == PW_TYPE_INTEGER) || - (dst->da->type == PW_TYPE_DATE) || - (dst->da->type == PW_TYPE_SIGNED))) { - dst->vp_integer = htonl(src->vp_ipaddr); - - } else { /* they're of the same byte order */ - memcpy(&dst->data, &src->data, src->length); + default: + EVAL_DEBUG("ERROR %i", rcode); + break; } - dst->length = src->length; - - return 0; + return rcode; } -/** Evaluate a map + +/** Convert both operands to the same type + * + * If casting is successful, we call cond_cmp_values to do the comparison * - * @param[in] request the REQUEST - * @param[in] modreturn the previous module return code - * @param[in] depth of the recursion (only used for debugging) - * @param[in] c the condition to evaluate * @return -1 on error, 0 for "no match", 1 for "match". */ -int radius_evaluate_map(REQUEST *request, UNUSED int modreturn, UNUSED int depth, - fr_cond_t const *c) +static int cond_normalise_values(REQUEST *request, fr_cond_t const *c, + PW_TYPE lhs_type, DICT_ATTR const *lhs_enumv, value_data_t const *lhs, size_t lhs_len) { - int rcode; - char *lhs, *rhs; - value_pair_map_t *map; + value_pair_map_t const *map = c->data.map; - rad_assert(c->type == COND_TYPE_MAP); - map = c->data.map; + DICT_ATTR const *cast = NULL; + PW_TYPE cast_type = PW_TYPE_STRING; - rad_assert(map->lhs->type != TMPL_TYPE_UNKNOWN); - rad_assert(map->rhs->type != TMPL_TYPE_UNKNOWN); - rad_assert(map->lhs->type != TMPL_TYPE_LIST); - rad_assert(map->rhs->type != TMPL_TYPE_LIST); - rad_assert(map->rhs->type != TMPL_TYPE_REGEX); - rad_assert(map->lhs->type != TMPL_TYPE_REGEX); - rad_assert(map->lhs->type != TMPL_TYPE_REGEX_STRUCT); + int rcode; - EVAL_DEBUG("MAP TYPES LHS: %s, RHS: %s", - fr_int2str(template_names, map->lhs->type, "???"), - fr_int2str(template_names, map->rhs->type, "???")); + PW_TYPE rhs_type = PW_TYPE_INVALID; + DICT_ATTR const *rhs_enumv = NULL; + value_data_t const *rhs = NULL; + size_t rhs_len; + + value_data_t lhs_cast, rhs_cast; + void *lhs_cast_buff = NULL, *rhs_cast_buff = NULL; /* - * They're both attributes. Do attribute-specific work. + * Cast operand to correct type. + * + * With hack for strings that look like integers, to cast them + * to 64 bit unsigned integers. + * + * @fixme For things like this it'd be useful to have a 64bit signed type. */ - if (!c->cast && (map->lhs->type == TMPL_TYPE_ATTR) && (map->rhs->type == TMPL_TYPE_ATTR)) { - VALUE_PAIR *lhs_vp, *rhs_vp; - ssize_t ret; - value_data_t cast_data; - - EVAL_DEBUG("ATTR to ATTR"); - if ((tmpl_find_vp(&lhs_vp, request, map->lhs) < 0) || - (tmpl_find_vp(&rhs_vp, request, map->rhs) < 0)) return -1; - - if (map->lhs->tmpl_da->type == map->rhs->tmpl_da->type) { - return paircmp_op(map->op, lhs_vp, rhs_vp); - } - - /* - * Compare a large integer (lhs) to a small integer (rhs). - * We allow this without a cast. - */ - rad_assert((map->lhs->tmpl_da->type == PW_TYPE_INTEGER64) || - (map->lhs->tmpl_da->type == PW_TYPE_INTEGER) || - (map->lhs->tmpl_da->type == PW_TYPE_SHORT)); - rad_assert((map->rhs->tmpl_da->type == PW_TYPE_INTEGER) || - (map->rhs->tmpl_da->type == PW_TYPE_SHORT) || - (map->rhs->tmpl_da->type == PW_TYPE_BYTE)); - - ret = value_data_cast(rhs_vp, &cast_data, - lhs_vp->da->type, lhs_vp->da, - rhs_vp->da->type, rhs_vp->da, - &rhs_vp->data, rhs_vp->length); - if (ret < 0) return -1; - - rcode = value_data_cmp_op(map->op, - lhs_vp->da->type, &lhs_vp->data, lhs_vp->length, - lhs_vp->da->type, &cast_data, (size_t)ret); - if (lhs_vp->da->flags.is_pointer) talloc_free(cast_data.ptr); - - return rcode; - } +#define CAST(_s) \ +do {\ + if (!cast && lhs && rhs && (lhs_type == PW_TYPE_STRING) && (rhs_type == PW_TYPE_STRING) &&\ + all_digits(lhs->strvalue) && all_digits(rhs->strvalue)) cast_type = PW_TYPE_INTEGER64;\ + if ((cast_type != _s ## _type) && (_s ## _type != PW_TYPE_INVALID)) {\ + ssize_t r;\ + EVAL_DEBUG("CASTING " #_s " FROM %s TO %s",\ + fr_int2str(dict_attr_types, _s ## _type, ""),\ + fr_int2str(dict_attr_types, cast_type, ""));\ + r = value_data_cast(request, &_s ## _cast, cast_type, cast, _s ## _type, _s ## _enumv, _s, _s ## _len);\ + if (r < 0) {\ + REDEBUG("Failed casting " #_s " operand: %s", fr_strerror());\ + rcode = -1;\ + goto finish;\ + }\ + if (cast && cast->flags.is_pointer) _s ## _cast_buff = _s ## _cast.ptr;\ + _s ## _type = cast_type;\ + _s ## _len = (size_t)r;\ + _s = &_s ## _cast;\ + }\ +} while (0) /* - * LHS is a cast. Do type-specific comparisons, as if - * the LHS was a real attribute. + * Regular expressions need both operands to be strings */ - if (c->cast) { - VALUE_PAIR *lhs_vp, *rhs_vp; - - /* - * Try to copy data from the VP which is being - * casted, instead of printing it to a string and - * then re-parsing it. - */ - if (map->lhs->type == TMPL_TYPE_ATTR) { - VALUE_PAIR *cast_vp; - - if (tmpl_find_vp(&cast_vp, request, map->lhs) < 0) return false; +#ifdef HAVE_REGEX + if (map->op == T_OP_REG_EQ) cast_type = PW_TYPE_STRING; + else +#endif + /* + * If it's a pair comparison, data gets cast to the + * type of the pair comparison attribute. + * + * Magic attribute is always the LHS. + */ + if (c->pass2_fixup == PASS2_PAIRCOMPARE) { + rad_assert(!c->cast); + rad_assert(map->lhs->type == TMPL_TYPE_ATTR); +#ifndef NDEBUG + /* expensive assert */ + rad_assert((map->rhs->type != TMPL_TYPE_ATTR) || !radius_find_compare(map->rhs->tmpl_da)); +#endif + cast = map->lhs->tmpl_da; + cast_type = cast->type; + /* + * Otherwise we use the explicit cast, or implicit + * cast (from an attribute reference). + * We already have the data for the lhs, so we convert + * it here. + */ + } else if (c->cast) cast = c->cast; + else if (map->lhs->type == TMPL_TYPE_ATTR) cast = map->lhs->tmpl_da; + else if (map->rhs->type == TMPL_TYPE_ATTR) cast = map->rhs->tmpl_da; + else if (map->lhs->type == TMPL_TYPE_DATA) cast_type = map->lhs->tmpl_data_type; + else if (map->rhs->type == TMPL_TYPE_DATA) cast_type = map->rhs->tmpl_data_type; + if (cast) cast_type = cast->type; - lhs_vp = pairalloc(request, c->cast); - if (!lhs_vp) return -1; + EVAL_DEBUG("NORMALISATION TYPE %s", fr_int2str(dict_attr_types, cast_type, "")); - /* - * In a separate function for clarity - */ - if (do_cast_copy(lhs_vp, cast_vp) < 0) { - talloc_free(lhs_vp); - return -1; - } + switch (map->rhs->type) { + case TMPL_TYPE_ATTR: + { + VALUE_PAIR *vp; + vp_cursor_t cursor; - } else { - rcode = tmpl_cast_to_vp(&lhs_vp, request, map->lhs, c->cast); - if (rcode < 0) return rcode; - } - rad_assert(lhs_vp); + for (vp = tmpl_cursor_init(&rcode, &cursor, request, map->rhs); + vp; + vp = tmpl_cursor_next(&cursor, map->rhs)) { + rhs_type = vp->da->type; + rhs_enumv = vp->da; + rhs = &vp->data; + rhs_len = vp->length; - /* - * Get either a real VP, or parse the RHS into a - * VP, and return that. - */ - if (map->rhs->type == TMPL_TYPE_ATTR) { - if (tmpl_find_vp(&rhs_vp, request, map->rhs) < 0) { - return -2; - } - } else { - rcode = tmpl_cast_to_vp(&rhs_vp, request, map->rhs, c->cast); - if (rcode < 0) { - return rcode; - } - rad_assert(rhs_vp); - } - if (!rhs_vp) return -2; + CAST(lhs); + CAST(rhs); - EVAL_DEBUG("CAST to %s", - fr_int2str(dict_attr_types, - c->cast->type, "?Unknown?")); + rcode = cond_cmp_values(request, c, lhs_type, lhs, lhs_len, rhs_type, rhs, rhs_len); + if (rcode != 0) break; - rcode = paircmp_op(map->op, lhs_vp, rhs_vp); - pairfree(&lhs_vp); - if (map->rhs->type != TMPL_TYPE_ATTR) { - pairfree(&rhs_vp); + TALLOC_FREE(rhs_cast_buff); } - return rcode; } + break; - /* - * Might be a virtual comparison - */ - if ((map->lhs->type == TMPL_TYPE_ATTR) && - (map->op != T_OP_REG_EQ) && - (c->pass2_fixup == PASS2_PAIRCOMPARE)) { - int ret; - VALUE_PAIR *lhs_vp; - - EVAL_DEBUG("virtual ATTR to DATA"); + case TMPL_TYPE_DATA: + rhs_type = map->rhs->tmpl_data_type; + rhs = &map->rhs->tmpl_data_value; + rhs_len = map->rhs->tmpl_data_length; - rcode = tmpl_cast_to_vp(&lhs_vp, request, map->rhs, map->lhs->tmpl_da); - if (rcode < 0) return rcode; - rad_assert(lhs_vp); + CAST(lhs); + CAST(rhs); - /* - * paircompare requires the operator be set for the - * check attribute. - */ - lhs_vp->op = map->op; - ret = paircompare(request, request->packet->vps, lhs_vp, NULL); - talloc_free(lhs_vp); - if (ret == 0) { - return true; - } - return false; - } - rad_assert(c->pass2_fixup != PASS2_PAIRCOMPARE); + rcode = cond_cmp_values(request, c, lhs_type, lhs, lhs_len, rhs_type, rhs, rhs_len); + break; /* - * RHS has been pre-parsed into binary data. Go check - * that. + * Expanded types start as strings, then get converted + * to the type of the attribute or the explicit cast. */ - if ((map->lhs->type == TMPL_TYPE_ATTR) && - (map->rhs->type == TMPL_TYPE_DATA)) { - VALUE_PAIR *lhs_vp, *rhs_vp; - - EVAL_DEBUG("ATTR to DATA"); + case TMPL_TYPE_LITERAL: + case TMPL_TYPE_EXEC: + case TMPL_TYPE_XLAT: + case TMPL_TYPE_XLAT_STRUCT: + { + ssize_t ret; + value_data_t data; - if (tmpl_find_vp(&lhs_vp, request, map->lhs) < 0) return -2; + ret = radius_expand_tmpl((char **)&data.ptr, request, map->rhs); + if (ret < 0) { + EVAL_DEBUG("FAIL [%i]", __LINE__); + rcode = -1; + goto finish; + } + rhs_len = ret; + rhs_type = PW_TYPE_STRING; + rhs = &data; - rcode = tmpl_cast_to_vp(&rhs_vp, request, map->rhs, map->lhs->tmpl_da); - if (rcode < 0) return rcode; - rad_assert(rhs_vp); + CAST(lhs); + CAST(rhs); -#ifdef WITH_EVAL_DEBUG - debug_pair(lhs_vp); - debug_pair(rhs_vp); -#endif + rcode = cond_cmp_values(request, c, lhs_type, lhs, lhs_len, rhs_type, rhs, rhs_len); + talloc_free(data.ptr); - rcode = paircmp_op(map->op, lhs_vp, rhs_vp); - pairfree(&rhs_vp); - return rcode; + break; } - rad_assert(map->rhs->type != TMPL_TYPE_DATA); - rad_assert(map->lhs->type != TMPL_TYPE_DATA); - -#ifdef HAVE_REGEX /* - * Parse regular expressions. + * RHS is a compiled regex, we don't need to do anything with it. */ - if (map->op == T_OP_REG_EQ) { - return do_regex(request, map); - } -#endif - + case TMPL_TYPE_REGEX_STRUCT: + CAST(lhs); + rcode = cond_cmp_values(request, c, lhs_type, lhs, lhs_len, PW_TYPE_INVALID, NULL, 0); + break; /* - * The RHS now needs to be expanded into a string. + * Unsupported types (should have been parse errors) */ - EVAL_DEBUG("TMPL RHS is %s", map->rhs->name); - rcode = radius_expand_tmpl(&rhs, request, map->rhs); - if (rcode < 0) { - EVAL_DEBUG("FAIL %d", __LINE__); - return rcode; + case TMPL_TYPE_NULL: + case TMPL_TYPE_LIST: + case TMPL_TYPE_UNKNOWN: + case TMPL_TYPE_ATTR_UNKNOWN: + case TMPL_TYPE_REGEX: /* Should now be a TMPL_TYPE_REGEX_STRUCT or TMPL_TYPE_XLAT_STRUCT */ + rad_assert(0); + rcode = -1; + break; } - rad_assert(rhs != NULL); - /* - * User-Name == FOO - * - * Parse the RHS to be the same DA as the LHS. do - * comparisons. So long as it's not a regex, which does - * string comparisons. - * - * The LHS may be a virtual attribute, too. - */ - if (map->lhs->type == TMPL_TYPE_ATTR) { - VALUE_PAIR *lhs_vp, *rhs_vp; +finish: + talloc_free(lhs_cast_buff); + talloc_free(rhs_cast_buff); - EVAL_DEBUG("ATTR to non-REGEX"); + return rcode; +} +/** Evaluate a map + * + * @param[in] request the REQUEST + * @param[in] modreturn the previous module return code + * @param[in] depth of the recursion (only used for debugging) + * @param[in] c the condition to evaluate + * @return -1 on error, 0 for "no match", 1 for "match". + */ +int radius_evaluate_map(REQUEST *request, UNUSED int modreturn, UNUSED int depth, fr_cond_t const *c) +{ + int rcode = 0; + + value_pair_map_t const *map = c->data.map; + + EVAL_DEBUG(">>> MAP TYPES LHS: %s, RHS: %s", + fr_int2str(template_names, map->lhs->type, "???"), + fr_int2str(template_names, map->rhs->type, "???")); + + switch (map->lhs->type) { + /* + * LHS is an attribute or list + */ + case TMPL_TYPE_LIST: + case TMPL_TYPE_ATTR: + { + VALUE_PAIR *vp; + vp_cursor_t cursor; /* - * No LHS means no match + * Legacy paircompare call, skip processing the magic attribute + * if it's the LHS and cast RHS to the same type. */ - if (tmpl_find_vp(&lhs_vp, request, map->lhs) < 0) { + if ((c->pass2_fixup == PASS2_PAIRCOMPARE) && (map->op != T_OP_REG_EQ)) { +#ifndef NDEBUG + rad_assert(radius_find_compare(map->lhs->tmpl_da)); /* expensive assert */ +#endif + rcode = cond_normalise_values(request, c, PW_TYPE_INVALID, NULL, NULL, 0); + break; + } + for (vp = tmpl_cursor_init(&rcode, &cursor, request, map->lhs); + vp; + vp = tmpl_cursor_next(&cursor, map->lhs)) { /* - * Not a real attr: might be a dynamic comparison. + * Evaluate all LHS values, condition evaluates to true + * if we get at least one set of operands that + * evaluates to true. */ - if ((map->lhs->type == TMPL_TYPE_ATTR) && - (map->lhs->tmpl_da->vendor == 0) && - radius_find_compare(map->lhs->tmpl_da)) { - rhs_vp = pairalloc(request, map->lhs->tmpl_da); - rad_assert(rhs_vp != NULL); - if (pairparsevalue(rhs_vp, rhs, -1) < 0) { - talloc_free(rhs); - EVAL_DEBUG("FAIL %d", __LINE__); - return -1; - } - talloc_free(rhs); + rcode = cond_normalise_values(request, c, vp->da->type, vp->da, &vp->data, vp->length); + if (rcode != 0) break; + } + } + break; - rcode = (radius_callback_compare(request, NULL, rhs_vp, NULL, NULL) == 0); - pairfree(&rhs_vp); - return rcode; - } + case TMPL_TYPE_DATA: + rcode = cond_normalise_values(request, c, + map->lhs->tmpl_data_type, NULL, &map->lhs->tmpl_data_value, + map->lhs->tmpl_data_length); + break; - return -2; - } + case TMPL_TYPE_LITERAL: + case TMPL_TYPE_EXEC: + case TMPL_TYPE_XLAT: + case TMPL_TYPE_XLAT_STRUCT: + { + ssize_t ret; + value_data_t data; - /* - * Get VP for RHS - */ - rhs_vp = pairalloc(request, map->lhs->tmpl_da); - rad_assert(rhs_vp != NULL); - if (pairparsevalue(rhs_vp, rhs, -1) < 0) { - talloc_free(rhs); - pairfree(&rhs_vp); - EVAL_DEBUG("FAIL %d", __LINE__); - return -1; + ret = radius_expand_tmpl((char **)&data.ptr, request, map->lhs); + if (ret < 0) { + EVAL_DEBUG("FAIL [%i]", __LINE__); + return ret; } - - rcode = paircmp_op(map->op, lhs_vp, rhs_vp); - talloc_free(rhs); - pairfree(&rhs_vp); - return rcode; + rcode = cond_normalise_values(request, c, PW_TYPE_STRING, NULL, &data, ret); + talloc_free(data.ptr); } + break; /* - * The LHS is a string. Expand it. + * Unsupported types (should have been parse errors) */ - EVAL_DEBUG("TMPL LHS is %s", map->lhs->name); - rcode = radius_expand_tmpl(&lhs, request, map->lhs); - if (rcode < 0) { - EVAL_DEBUG("FAIL %d", __LINE__); - return rcode; + case TMPL_TYPE_NULL: + case TMPL_TYPE_ATTR_UNKNOWN: + case TMPL_TYPE_UNKNOWN: + case TMPL_TYPE_REGEX: /* should now be a TMPL_TYPE_REGEX_STRUCT or TMPL_TYPE_XLAT_STRUCT */ + case TMPL_TYPE_REGEX_STRUCT: /* not allowed as LHS */ + rad_assert(0); + rcode = -1; + break; } - rad_assert(lhs != NULL); - - EVAL_DEBUG("LHS is %s", lhs); - EVAL_DEBUG("RHS is %s", rhs); - - /* - * Loop over the string, doing comparisons - */ - if (all_digits(lhs) && all_digits(rhs)) { - int lint, rint; - - lint = strtoul(lhs, NULL, 0); - rint = strtoul(rhs, NULL, 0); - talloc_free(lhs); - talloc_free(rhs); - - switch (map->op) { - case T_OP_CMP_EQ: - return (lint == rint); - - case T_OP_NE: - return (lint != rint); - - case T_OP_LT: - return (lint < rint); - - case T_OP_GT: - return (lint > rint); - - case T_OP_LE: - return (lint <= rint); - - case T_OP_GE: - return (lint >= rint); - - default: - break; - } - - } else { - rad_assert(lhs != NULL); - rad_assert(rhs != NULL); - - rcode = strcmp(lhs, rhs); - talloc_free(lhs); - talloc_free(rhs); - - switch (map->op) { - case T_OP_CMP_EQ: - return (rcode == 0); - - case T_OP_NE: - return (rcode != 0); - - case T_OP_LT: - return (rcode < 0); - case T_OP_GT: - return (rcode > 0); + EVAL_DEBUG("<<<"); - case T_OP_LE: - return (rcode <= 0); - - case T_OP_GE: - return (rcode >= 0); - - default: - break; - } - } - - EVAL_DEBUG("FAIL %d", __LINE__); - return -1; + return rcode; } - /** Evaluate a fr_cond_t; * * @param[in] request the REQUEST @@ -863,8 +715,7 @@ int radius_evaluate_map(REQUEST *request, UNUSED int modreturn, UNUSED int depth * @param[in] c the condition to evaluate * @return -1 on failure, -2 on attribute not found, 0 for "no match", 1 for "match". */ -int radius_evaluate_cond(REQUEST *request, int modreturn, int depth, - fr_cond_t const *c) +int radius_evaluate_cond(REQUEST *request, int modreturn, int depth, fr_cond_t const *c) { int rcode = -1; #ifdef WITH_EVAL_DEBUG diff --git a/src/tests/keywords/if-multivalue b/src/tests/keywords/if-multivalue new file mode 100644 index 00000000000..8c50e8afc94 --- /dev/null +++ b/src/tests/keywords/if-multivalue @@ -0,0 +1,173 @@ +# +# PRE: update if +# +update { + control:Cleartext-Password := 'hello' + reply:Filter-Id := 'filter' +} + +update request { + Tmp-String-0 := 'foo' + Tmp-String-0 += 'bar' + Tmp-String-0 += 'baz' + + Tmp-String-1 := 'GROUP ADMINISTRATORS' + Tmp-String-1 += 'GROUP STUDENTS' + Tmp-String-1 += 'GROUP PEONS' + + Tmp-String-2 := 'PEONS' + Tmp-String-2 += 'STUDENTS' + Tmp-String-2 += 'ADMINISTRATORS' + + Tmp-String-3 := 'no' + Tmp-String-3 += 'no' + Tmp-String-3 += 'yes' + + Tmp-Integer-0 := 1 + Tmp-Integer-0 += 2 + Tmp-Integer-0 += 5 +} + +update control { + Tmp-String-0 := 'foo' + Tmp-String-0 += 'bar' + Tmp-String-0 += 'baz' + + Tmp-String-1 := 'boink' + Tmp-String-1 += 'tard' + Tmp-String-1 += 'dink' + Tmp-String-1 += 'slink' + + Tmp-Integer-0 := 01 + Tmp-Integer-0 += 02 + Tmp-Integer-0 += 05 + Tmp-Integer-0 += 04 + + Tmp-Integer-1 := 10 + Tmp-Integer-1 += 20 + Tmp-Integer-1 += 30 +} + +# +# Mmmm O(N^2) +# +if (&request:Tmp-String-0[*] != &control:Tmp-String-0[*]) { + update reply { + Filter-Id += 'fail 0' + } +} + +if (&request:Tmp-String-0[*] == &control:Tmp-String-1[*]) { + update reply { + Filter-Id += 'fail 1' + } +} + +if (&request:Tmp-String-1[*] == &control:Tmp-String-0[*]) { + update reply { + Filter-Id += 'fail 2' + } +} + +# +# Integer comparison and normalisation +# +if (&request:Tmp-Integer-0 != &control:Tmp-Integer-0) { + update reply { + Filter-Id += 'fail 3' + } +} + +# +# if any value of request:Tmp-Integer-0 > any value of +# request:Tmp-Integer-1 then evaluate to true +# +if (&request:Tmp-Integer-0[*] > &control:Tmp-Integer-1[*]) { + update reply { + Filter-Id += 'fail 4' + } +} + +# +# Compiled reqex comparisons +# +if (&request:Tmp-String-1[*] !~ /PEONS$/) { + update reply { + Filter-Id += 'fail 5' + } +} + +if (&control:Tmp-String-1 =~ /PEONS$/) { + update reply { + Filter-Id += 'fail 6' + } +} + +if (&control:Tmp-String-1 =~ /DINKS$/) { + update reply { + Filter-Id += 'fail 7' + } +} + +# +# Dynamic reqex comparisons +# +if (&request:Tmp-String-1[*] !~ /%{Tmp-String-2[0]}$/) { + update reply { + Filter-Id += 'fail 8' + } +} + +if (&request:Tmp-String-1 =~ /%{Tmp-String-2[1]}$/) { + update reply { + Filter-Id += 'fail 9' + } +} + +if (&request:Tmp-String-1 !~ /%{Tmp-String-2[2]}$/) { + update reply { + Filter-Id += 'fail 10' + } +} + +if (&request:Tmp-String-1 =~ /%{Tmp-String-2[#]}$/) { + update reply { + Filter-Id += 'fail 11' + } +} + +# +# XLAT virtual comparisons +# +if (&control:Tmp-Integer-0[*] != "%{control:Tmp-Integer-0[#]}") { + update reply { + Filter-Id += 'fail 12' + } +} + +# +# Literal comparisons +# +if (&control:Tmp-String-1[*] != 'boink') { + update reply { + Filter-Id += 'fail 13' + } +} + +if (&control:Tmp-String-1[*] == 'foo') { + update reply { + Filter-Id += 'fail 14' + } +} + +if (&request:Tmp-Integer-0[*] > 10) { + update reply { + Filter-Id += 'fail 15' + } +} + +if (!(&request:Tmp-Integer-0[*] < 10)) { + update reply { + Filter-Id += 'fail 16' + } +}