]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add support for multivalued attribute comparison
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 5 Nov 2014 21:08:35 +0000 (16:08 -0500)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 5 Nov 2014 21:08:58 +0000 (16:08 -0500)
src/include/radiusd.h
src/include/tmpl.h
src/lib/value.c
src/main/evaluate.c
src/tests/keywords/if-multivalue [new file with mode: 0644]

index 1e6d9b05df864521b2eef7509a82f96449d9cab3..4b346a083e2cb27841f4261975d0c21a73c726d4 100644 (file)
@@ -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,
index 9b04505b204c869fafde2b29fa153e98bc97d3e6..e7911ef8eee72b108a8b177b5e98aea5319cf86c 100644 (file)
@@ -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
index 73a9a125c36d5959c6822e4f39bb99e2e5c3139d..9cebba1b519dd5296c3634335f56c1854278b9de 100644 (file)
@@ -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));
index 88564843f123fbd4db6fa5e9cef755177cef5c8b..9f773e37cfe89aa0f84827f6b62ea412d5a1f2e0 100644 (file)
@@ -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(&reg, rhs, REG_EXTENDED | (map->rhs->tmpl_iflag ? REG_ICASE : 0));
-               if (compare != 0) {
+       default:
+               rad_assert(rhs_type == PW_TYPE_STRING);
+               ret = regcomp(&reg, rhs->strvalue, REG_EXTENDED | (map->rhs->tmpl_iflag ? REG_ICASE : 0));
+               if (ret != 0) {
                        if (debug_flag) {
                                char errbuf[128];
 
-                               regerror(compare, &reg, errbuf, sizeof(errbuf));
+                               regerror(ret, &reg, 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 = &reg;
                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, "<INVALID>"),\
+                          fr_int2str(dict_attr_types, cast_type, "<INVALID>"));\
+               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, "<INVALID>"));
 
-                       /*
-                        *      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 (file)
index 0000000..8c50e8a
--- /dev/null
@@ -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'
+       }
+}