Allow delayed references to attributes. Helps with #711
[freeradius.git] / src / main / parser.c
index df61c83..5ceaeed 100644 (file)
@@ -28,26 +28,31 @@ RCSID("$Id$")
 
 #include <ctype.h>
 
-#if 0
-#define COND_DEBUG(fmt, ...) printf(fmt, ## __VA_ARGS__);printf("\n")
-#endif
+#define PW_CAST_BASE (1850)
+
+static const FR_NAME_NUMBER allowed_return_codes[] = {
+       { "reject",     1 },
+       { "fail",       1 },
+       { "ok",         1 },
+       { "handled",    1 },
+       { "invalid",    1 },
+       { "userlock",   1 },
+       { "notfound",   1 },
+       { "noop",       1 },
+       { "updated",    1 },
+       { NULL, 0 }
+};
 
 /*
  *     This file shouldn't use any functions from the server core.
  */
-#ifndef COND_DEBUG
-#if 0
-#define COND_DEBUG DEBUG
-#else
-#define COND_DEBUG(...)
-#endif
-#endif
 
-size_t fr_cond_sprint(char *buffer, size_t bufsize, fr_cond_t const *c)
+size_t fr_cond_sprint(char *buffer, size_t bufsize, fr_cond_t const *in)
 {
        size_t len;
        char *p = buffer;
        char *end = buffer + bufsize - 1;
+       fr_cond_t const *c = in;
 
 next:
        if (c->negate) {
@@ -57,6 +62,12 @@ next:
        switch (c->type) {
        case COND_TYPE_EXISTS:
                rad_assert(c->data.vpt != NULL);
+               if (c->cast) {
+                       len = snprintf(p, end - p, "<%s>", fr_int2str(dict_attr_types,
+                                                                     c->cast->type, "??"));
+                       p += len;
+               }
+
                len = radius_tmpl2str(p, end - p, c->data.vpt);
                p += len;
                break;
@@ -66,6 +77,12 @@ next:
 #if 0
                *(p++) = '[';   /* for extra-clear debugging */
 #endif
+               if (c->cast) {
+                       len = snprintf(p, end - p, "<%s>", fr_int2str(dict_attr_types,
+                                                                     c->cast->type, "??"));
+                       p += len;
+               }
+
                len = radius_map2str(p, end - p, c->data.map);
                p += len;
 #if 0
@@ -81,6 +98,14 @@ next:
                *(p++) = ')';
                break;
 
+       case COND_TYPE_TRUE:
+               strlcpy(buffer, "true", bufsize);
+               return strlen(buffer);
+
+       case COND_TYPE_FALSE:
+               strlcpy(buffer, "false", bufsize);
+               return strlen(buffer);
+
        default:
                *buffer = '\0';
                return 0;
@@ -112,7 +137,7 @@ next:
 static ssize_t condition_tokenize_string(TALLOC_CTX *ctx, char const *start, char **out,
                                         FR_TOKEN *op, char const **error)
 {
-       const char *p = start;
+       char const *p = start;
        char *q;
 
        switch (*p++) {
@@ -142,14 +167,10 @@ static ssize_t condition_tokenize_string(TALLOC_CTX *ctx, char const *start, cha
 
        q = *out;
 
-       COND_DEBUG("STRING %s", start);
        while (*p) {
                if (*p == *start) {
                        *q = '\0';
                        p++;
-
-                       COND_DEBUG("end of string %s", p);
-
                        return (p - start);
                }
 
@@ -157,7 +178,6 @@ static ssize_t condition_tokenize_string(TALLOC_CTX *ctx, char const *start, cha
                        p++;
                        if (!*p) {
                                *error = "End of string after escape";
-                               COND_DEBUG("RETURN %d", __LINE__);
                                return -(p - start);
                        }
 
@@ -178,7 +198,7 @@ static ssize_t condition_tokenize_string(TALLOC_CTX *ctx, char const *start, cha
                        p++;
                        continue;
                }
-       
+
                *(q++) = *(p++);
        }
 
@@ -206,7 +226,6 @@ static ssize_t condition_tokenize_word(TALLOC_CTX *ctx, char const *start, char
                 */
                if (*p == '\\') {
                        *error = "Unexpected escape";
-                       COND_DEBUG("RETURN %d", __LINE__);
                        return -(p - start);
                }
 
@@ -226,7 +245,6 @@ static ssize_t condition_tokenize_word(TALLOC_CTX *ctx, char const *start, char
                }
 
                if ((*p == '"') || (*p == '\'') || (*p == '`')) {
-                       COND_DEBUG("RETURN %d", __LINE__);
                        *error = "Unexpected start of string";
                        return -(p - start);
                }
@@ -243,40 +261,105 @@ static ssize_t condition_tokenize_word(TALLOC_CTX *ctx, char const *start, char
        *out = talloc_array(ctx, char, len + 1);
        memcpy(*out, start, len);
        (*out)[len] = '\0';
-       COND_DEBUG("PARSED WORD %s", *out);
        return len;
 }
 
+
+static ssize_t condition_tokenize_cast(char const *start, DICT_ATTR const **pda, char const **error)
+{
+       char const *p = start;
+       char const *q;
+       PW_TYPE cast;
+
+       while (isspace((int) *p)) p++; /* skip spaces before condition */
+
+       if (*p != '<') return 0;
+       p++;
+
+       q = p;
+       while (*q && *q != '>') q++;
+
+       cast = fr_substr2int(dict_attr_types, p, PW_TYPE_INVALID, q - p);
+       if (cast == PW_TYPE_INVALID) {
+               *error = "Invalid data type in cast";
+               return -(p - start);
+       }
+
+       /*
+        *      We can only cast to basic data types.  Complex ones
+        *      are forbidden.
+        */
+       switch (cast) {
+#ifdef WITH_ASCEND_BINARY
+       case PW_TYPE_ABINARY:
+#endif
+       case PW_TYPE_IP_ADDR:
+       case PW_TYPE_TLV:
+       case PW_TYPE_EXTENDED:
+       case PW_TYPE_LONG_EXTENDED:
+       case PW_TYPE_EVS:
+       case PW_TYPE_VSA:
+               *error = "Forbidden data type in cast";
+               return -(p - start);
+
+       default:
+               break;
+       }
+
+       *pda = dict_attrbyvalue(PW_CAST_BASE + cast, 0);
+       if (!*pda) {
+               *error = "Cannot cast to this data type";
+               return -(p - start);
+       }
+
+       q++;
+
+       while (isspace((int) *q)) q++; /* skip spaces after cast */
+
+       return q - start;
+}
+
+/*
+ *     Less code means less bugs
+ */
+#define return_P(_x) *error = _x;goto return_p
+#define return_0(_x) *error = _x;goto return_0
+#define return_lhs(_x) *error = _x;goto return_lhs
+#define return_rhs(_x) *error = _x;goto return_rhs
+#define return_SLEN goto return_slen
+
+
 /** Tokenize a conditional check
  *
  *  @param[in] ctx for talloc
+ *  @param[in] ci for CONF_ITEM
  *  @param[in] start the start of the string to process.  Should be "(..."
  *  @param[in] brace look for a closing brace
+ *  @param[in] flags do one/two pass
  *  @param[out] pcond pointer to the returned condition structure
  *  @param[out] error the parse error (if any)
  *  @return length of the string skipped, or when negative, the offset to the offending error
  */
-static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace, fr_cond_t **pcond, char const **error)
+static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *start, int brace,
+                                 fr_cond_t **pcond, char const **error, int flags)
 {
        ssize_t slen;
-       const char *p = start;
+       char const *p = start;
+       char const *lhs_p, *rhs_p;
        fr_cond_t *c;
        char *lhs, *rhs;
        FR_TOKEN op, lhs_type, rhs_type;
 
-       COND_DEBUG("START %s", p);
-
        c = talloc_zero(ctx, fr_cond_t);
 
        rad_assert(c != NULL);
+       lhs = rhs = NULL;
+       lhs_type = rhs_type = T_OP_INVALID;
 
        while (isspace((int) *p)) p++; /* skip spaces before condition */
 
        if (!*p) {
-               talloc_free(c);
-               COND_DEBUG("RETURN %d", __LINE__);
-               *error = "Empty condition is invalid";
-               return -(p - start);
+               return_P("Empty condition is invalid");
        }
 
        /*
@@ -291,10 +374,7 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace,
                 *  Just for stupidity
                 */
                if (*p == '!') {
-                       talloc_free(c);
-                       COND_DEBUG("RETURN %d", __LINE__);
-                       *error = "Double negation is invalid";
-                       return -(p - start);
+                       return_P("Double negation is invalid");
                }
        }
 
@@ -309,18 +389,14 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace,
                 *      brackets.  Go recurse to get more.
                 */
                c->type = COND_TYPE_CHILD;
-               slen = condition_tokenize(c, p, true, &c->data.child, error);
+               c->ci = ci;
+               slen = condition_tokenize(c, ci, p, true, &c->data.child, error, flags);
                if (slen <= 0) {
-                       talloc_free(c);
-                       COND_DEBUG("RETURN %d", __LINE__);
-                       return slen - (p - start);
+                       return_SLEN;
                }
 
                if (!c->data.child) {
-                       talloc_free(c);
-                       *error = "Empty condition is invalid";
-                       COND_DEBUG("RETURN %d", __LINE__);
-                       return -(p - start);
+                       return_P("Empty condition is invalid");
                }
 
                p += slen;
@@ -337,19 +413,20 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace,
                /*
                 *      Grab the LHS
                 */
-               COND_DEBUG("LHS %s", p);
                if (*p == '/') {
-                       talloc_free(c);
-                       *error = "Conditional check cannot begin with a regular expression";
-                       COND_DEBUG("RETURN %d", __LINE__);
-                       return -(p - start);
+                       return_P("Conditional check cannot begin with a regular expression");
+               }
+
+               slen = condition_tokenize_cast(p, &c->cast, error);
+               if (slen < 0) {
+                       return_SLEN;
                }
+               p += slen;
 
+               lhs_p = p;
                slen = condition_tokenize_word(c, p, &lhs, &lhs_type, error);
                if (slen <= 0) {
-                       talloc_free(c);
-                       COND_DEBUG("RETURN %d", __LINE__);
-                       return slen - (p - start);
+                       return_SLEN;
                }
                p += slen;
 
@@ -367,25 +444,14 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace,
                        /*
                         *      don't skip the brace.  We'll look for it later.
                         */
-               exists:
-                       c->type = COND_TYPE_EXISTS;
-                       c->data.vpt = radius_str2tmpl(c, lhs, lhs_type);
-                       if (!c->data.vpt) {
-                               talloc_free(c);
-                               *error = "Failed creating exists";
-                               COND_DEBUG("RETURN %d", __LINE__);
-                               return -(p - start);
-                       }
+                       goto exists;
 
                        /*
                         *      FOO
                         */
                } else if (!*p) {
                        if (brace) {
-                               talloc_free(c);
-                               *error = "No closing brace at end of string";
-                               COND_DEBUG("RETURN %d", __LINE__);
-                               return -(p - start);
+                               return_P("No closing brace at end of string");
                        }
 
                        goto exists;
@@ -396,24 +462,35 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace,
                } else if (((p[0] == '&') && (p[1] == '&')) ||
                           ((p[0] == '|') && (p[1] == '|'))) {
 
-                       goto exists;
+               exists:
+                       if (c->cast) {
+                               return_0("Cannot do cast for existence check");
+                       }
 
-               } else { /* it's an operator */
-                       int regex;
+                       c->type = COND_TYPE_EXISTS;
+                       c->ci = ci;
+
+                       c->data.vpt = radius_str2tmpl(c, lhs, lhs_type, REQUEST_CURRENT, PAIR_LIST_REQUEST);
+                       if (!c->data.vpt) {
+                               return_P("Failed creating exists");
+                       }
 
-                       COND_DEBUG("OPERATOR %s", p);
+                       rad_assert(c->data.vpt->type != VPT_TYPE_REGEX);
+
+               } else { /* it's an operator */
+                       bool regex;
+                       bool i_flag = false;
 
                        /*
                         *      The next thing should now be a comparison operator.
                         */
                        regex = false;
                        c->type = COND_TYPE_MAP;
+                       c->ci = ci;
+
                        switch (*p) {
                        default:
-                               talloc_free(c);
-                               *error = "Invalid text. Expected comparison operator";
-                               COND_DEBUG("RETURN %d", __LINE__);
-                               return -(p - start);
+                               return_P("Invalid text. Expected comparison operator");
 
                        case '!':
                                if (p[1] == '=') {
@@ -427,23 +504,15 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace,
                                p += 2;
 
                                } else if (p[1] == '*') {
-                                       /*
-                                        *      FOO !* BAR
-                                        *
-                                        *      is really !(FOO)
-                                        *
-                                        *      FIXME: we should
-                                        *      really re-write it...
-                                        */
+                                       if (lhs_type != T_BARE_WORD) {
+                                               return_P("Cannot use !* on a string");
+                                       }
+
                                        op = T_OP_CMP_FALSE;
                                        p += 2;
 
                                } else {
-                               invalid_operator:
-                                       talloc_free(c);
-                                       *error = "Invalid operator";
-                                       COND_DEBUG("RETURN %d", __LINE__);
-                                       return -(p - start);
+                                       goto invalid_operator;
                                }
                                break;
 
@@ -459,11 +528,16 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace,
                                        p += 2;
 
                                } else if (p[1] == '*') {
+                                       if (lhs_type != T_BARE_WORD) {
+                                               return_P("Cannot use =* on a string");
+                                       }
+
                                        op = T_OP_CMP_TRUE;
                                        p += 2;
 
                                } else {
-                                       goto invalid_operator;
+                               invalid_operator:
+                                       return_P("Invalid operator");
                                }
 
                                break;
@@ -494,22 +568,39 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace,
                        while (isspace((int) *p)) p++; /* skip spaces after operator */
 
                        if (!*p) {
-                               talloc_free(c);
-                               *error = "Expected text after operator";
-                               COND_DEBUG("RETURN %d", __LINE__);
-                               return -(p - start);
+                               return_P("Expected text after operator");
                        }
 
-                       COND_DEBUG("RHS %s", p);
+                       /*
+                        *      Cannot have a cast on the RHS.
+                        *      But produce good errors, too.
+                        */
+                       if (*p == '<') {
+                               DICT_ATTR const *cast_da;
+
+                               slen = condition_tokenize_cast(p, &cast_da, error);
+                               if (slen < 0) {
+                                       return_SLEN;
+                               }
+
+                               if (!c->cast) {
+                                       return_P("Unexpected cast");
+                               }
+
+                               if (c->cast != cast_da) {
+                                       return_P("Cannot cast to a different data type");
+                               }
+
+                               return_P("Unnecessary cast");
+                       }
 
                        /*
                         *      Grab the RHS
                         */
+                       rhs_p = p;
                        slen = condition_tokenize_word(c, p, &rhs, &rhs_type, error);
                        if (slen <= 0) {
-                               talloc_free(c);
-                               COND_DEBUG("RETURN %d", __LINE__);
-                               return slen - (p - start);
+                               return_SLEN;
                        }
 
                        /*
@@ -517,66 +608,281 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace,
                         */
                        if (regex) {
                                if (*p != '/') {
-                                       talloc_free(c);
-                                       *error = "Expected regular expression";
-                                       COND_DEBUG("RETURN %d", __LINE__);
-                                       return -(p - start);
+                                       return_P("Expected regular expression");
                                }
 
                                /*
                                 *      Allow /foo/i
                                 */
                                if (p[slen] == 'i') {
-                                       c->regex_i = true;
+                                       i_flag = true;
                                        slen++;
                                }
 
-                               COND_DEBUG("DONE REGEX %s", p + slen);
-
                        } else if (!regex && (*p == '/')) {
-                               talloc_free(c);
-                               *error = "Unexpected regular expression";
-                               COND_DEBUG("RETURN %d", __LINE__);
-                               return -(p - start);
+                               return_P("Unexpected regular expression");
                        }
 
                        c->data.map = radius_str2map(c, lhs, lhs_type, op, rhs, rhs_type,
                                                     REQUEST_CURRENT, PAIR_LIST_REQUEST,
                                                     REQUEST_CURRENT, PAIR_LIST_REQUEST);
                        if (!c->data.map) {
-                               talloc_free(c);
-                               *error = "Failed creating check";
-                               COND_DEBUG("RETURN %d", __LINE__);
-                               return -(p - start);
+                               /*
+                                *      FIXME: if strings are T_BARE_WORD and they start with '&',
+                                *      then they refer to attributes which have not yet been
+                                *      defined.  Create the template(s) as literals, and
+                                *      fix them up in pass2.
+                                */
+                               if ((*lhs != '&') ||
+                                   (lhs_type != T_BARE_WORD)) {
+                                       return_0("Syntax error");
+                               }
+                               c->data.map = radius_str2map(c, lhs, lhs_type + 1, op, rhs, rhs_type,
+                                                            REQUEST_CURRENT, PAIR_LIST_REQUEST,
+                                                            REQUEST_CURRENT, PAIR_LIST_REQUEST);
+                               if (!c->data.map) {
+                                       return_0("Unknown attribute");
+                               }
+                               rad_const_free(c->data.map->dst->name);
+                               c->data.map->dst->name = talloc_strdup(c->data.map->dst, lhs);
+                               c->pass2_fixup = PASS2_FIXUP_ATTR;
+                       }
+
+                       if (c->data.map->src->type == VPT_TYPE_REGEX) {
+                               c->data.map->src->vpt_iflag = i_flag;
+                       }
+
+                       /*
+                        *      Could have been a reference to an attribute which is registered later.
+                        *      Mark it as being checked in pass2.
+                        */
+                       if ((lhs_type == T_BARE_WORD) &&
+                           (c->data.map->dst->type == VPT_TYPE_LITERAL)) {
+                               c->pass2_fixup = PASS2_FIXUP_ATTR;
                        }
 
                        /*
+                        *      Save the CONF_ITEM for later.
+                        */
+                       c->data.map->ci = ci;
+
+                       /*
                         *      @todo: check LHS and RHS separately, to
                         *      get better errors
                         */
                        if ((c->data.map->src->type == VPT_TYPE_LIST) ||
                            (c->data.map->dst->type == VPT_TYPE_LIST)) {
-                               talloc_free(c);
-                               *error = "Cannot use list references in condition";
-                               COND_DEBUG("RETURN %d", __LINE__);
-                               return 0;
+                               return_0("Cannot use list references in condition");
                        }
 
-                       if ((c->data.map->src->type == VPT_TYPE_ATTR) &&
-                           (c->data.map->dst->type != VPT_TYPE_ATTR)) {
-                               talloc_free(c);
-                               *error = "Cannot use attribute reference on right side of condition";
-                               COND_DEBUG("RETURN %d", __LINE__);
-                               return 0;
-                       }
+                       /*
+                        *      Check cast type.  We can have the RHS
+                        *      a string if the LHS has a cast.  But
+                        *      if the RHS is an attr, it MUST be the
+                        *      same type as the LHS.
+                        */
+                       if (c->cast) {
+                               if ((c->data.map->src->type == VPT_TYPE_ATTR) &&
+                                   (c->cast->type != c->data.map->src->vpt_da->type)) {
+                                       goto same_type;
+                               }
+
+                               if (c->data.map->src->type == VPT_TYPE_REGEX) {
+                                       return_0("Cannot use cast with regex comparison");
+                               }
+
+                               /*
+                                *      The LHS is a literal which has been cast to a data type.
+                                *      Cast it to the appropriate data type.
+                                */
+                               if ((c->data.map->dst->type == VPT_TYPE_LITERAL) &&
+                                   !radius_cast_tmpl(c->data.map->dst, c->cast)) {
+                                       *error = "Failed to parse field";
+                                       if (lhs) talloc_free(lhs);
+                                       if (rhs) talloc_free(rhs);
+                                       talloc_free(c);
+                                       return -(lhs_p - start);
+                               }
+
+                               /*
+                                *      The RHS is a literal, and the LHS has been cast to a data
+                                *      type.
+                                */
+                               if ((c->data.map->dst->type == VPT_TYPE_DATA) &&
+                                   (c->data.map->src->type == VPT_TYPE_LITERAL) &&
+                                   !radius_cast_tmpl(c->data.map->src, c->data.map->dst->vpt_da)) {
+                                       return_rhs("Failed to parse field");
+                               }
+
+                               /*
+                                *      We may be casting incompatible
+                                *      types.  We check this based on
+                                *      their size.
+                                */
+                               if (c->data.map->dst->type == VPT_TYPE_ATTR) {
+                                       /*
+                                        *      dst.min == src.min
+                                        *      dst.max == src.max
+                                        */
+                                       if ((dict_attr_sizes[c->cast->type][0] == dict_attr_sizes[c->data.map->dst->vpt_da->type][0]) &&
+                                           (dict_attr_sizes[c->cast->type][1] == dict_attr_sizes[c->data.map->dst->vpt_da->type][1])) {
+                                               goto cast_ok;
+                                       }
+
+                                       /*
+                                        *      Run-time parsing of strings.
+                                        *      Run-time copying of octets.
+                                        */
+                                       if ((c->data.map->dst->vpt_da->type == PW_TYPE_STRING) ||
+                                           (c->data.map->dst->vpt_da->type == PW_TYPE_OCTETS)) {
+                                               goto cast_ok;
+                                       }
+
+                                       /*
+                                        *      ipaddr to ipv4prefix is OK
+                                        */
+                                       if ((c->data.map->dst->vpt_da->type == PW_TYPE_IPV4_ADDR) &&
+                                           (c->cast->type == PW_TYPE_IPV4_PREFIX)) {
+                                               goto cast_ok;
+                                       }
+
+                                       /*
+                                        *      ipv6addr to ipv6prefix is OK
+                                        */
+                                       if ((c->data.map->dst->vpt_da->type == PW_TYPE_IPV6_ADDR) &&
+                                           (c->cast->type == PW_TYPE_IPV6_PREFIX)) {
+                                               goto cast_ok;
+                                       }
+
+                                       /*
+                                        *      integer64 to ethernet is OK.
+                                        */
+                                       if ((c->data.map->dst->vpt_da->type == PW_TYPE_INTEGER64) &&
+                                           (c->cast->type == PW_TYPE_ETHERNET)) {
+                                               goto cast_ok;
+                                       }
+
+                                       /*
+                                        *      dst.max < src.min
+                                        *      dst.min > src.max
+                                        */
+                                       if ((dict_attr_sizes[c->cast->type][1] < dict_attr_sizes[c->data.map->dst->vpt_da->type][0]) ||
+                                           (dict_attr_sizes[c->cast->type][0] > dict_attr_sizes[c->data.map->dst->vpt_da->type][1])) {
+                                               return_0("Cannot cast to attribute of incompatible size");
+                                       }
+                               }
+
+                       cast_ok:
+                               /*
+                                *      Casting to a redundant type means we don't need the cast.
+                                *
+                                *      Do this LAST, as the rest of the code above assumes c->cast
+                                *      is not NULL.
+                                */
+                               if ((c->data.map->dst->type == VPT_TYPE_ATTR) &&
+                                   (c->cast->type == c->data.map->dst->vpt_da->type)) {
+                                       c->cast = NULL;
+                               }
+
+                       } else {
+                               /*
+                                *      Two attributes?  They must be of the same type
+                                */
+                               if ((c->data.map->src->type == VPT_TYPE_ATTR) &&
+                                   (c->data.map->dst->type == VPT_TYPE_ATTR) &&
+                                   (c->data.map->dst->vpt_da->type != c->data.map->src->vpt_da->type)) {
+                               same_type:
+                                       return_0("Attribute comparisons must be of the same data type");
+                               }
+
+                               /*
+                                *      Without a cast, we can't compare "foo" to User-Name,
+                                *      it has to be done the other way around.
+                                */
+                               if ((c->data.map->src->type == VPT_TYPE_ATTR) &&
+                                   (c->data.map->dst->type != VPT_TYPE_ATTR)) {
+                                       *error = "Cannot use attribute reference on right side of condition";
+                               return_0:
+                                       if (lhs) talloc_free(lhs);
+                                       if (rhs) talloc_free(rhs);
+                                       talloc_free(c);
+                                       return 0;
+                               }
+
+                               /*
+                                *      Invalid: User-Name == bob
+                                *      Valid:   User-Name == "bob"
+                                *
+                                *      There's no real reason for
+                                *      this, other than consistency.
+                                */
+                               if ((c->data.map->dst->type == VPT_TYPE_ATTR) &&
+                                   (c->data.map->src->type != VPT_TYPE_ATTR) &&
+                                   (c->data.map->dst->vpt_da->type == PW_TYPE_STRING) &&
+                                   (c->data.map->op != T_OP_CMP_TRUE) &&
+                                   (c->data.map->op != T_OP_CMP_FALSE) &&
+                                   (rhs_type == T_BARE_WORD)) {
+                                       return_rhs("Must have string as value for attribute");
+                               }
+
+                               /*
+                                *      Quotes around non-string
+                                *      attributes mean that it's
+                                *      either xlat, or an exec.
+                                */
+                               if ((c->data.map->dst->type == VPT_TYPE_ATTR) &&
+                                   (c->data.map->src->type != VPT_TYPE_ATTR) &&
+                                   (c->data.map->dst->vpt_da->type != PW_TYPE_STRING) &&
+                                   (c->data.map->dst->vpt_da->type != PW_TYPE_OCTETS) &&
+                                   (c->data.map->dst->vpt_da->type != PW_TYPE_DATE) &&
+                                   (rhs_type == T_SINGLE_QUOTED_STRING)) {
+                                       *error = "Value must be an unquoted string";
+                               return_rhs:
+                                       if (lhs) talloc_free(lhs);
+                                       if (rhs) talloc_free(rhs);
+                                       talloc_free(c);
+                                       return -(rhs_p - start);
+                               }
+
+                               /*
+                                *      The LHS has been cast to a data type, and the RHS is a
+                                *      literal.  Cast the RHS to the type of the cast.
+                                */
+                               if (c->cast && (c->data.map->src->type == VPT_TYPE_LITERAL) &&
+                                   !radius_cast_tmpl(c->data.map->src, c->cast)) {
+                                       return_rhs("Failed to parse field");
+                               }
 
-                       if ((c->data.map->src->type == VPT_TYPE_ATTR) &&
-                           (c->data.map->dst->type == VPT_TYPE_ATTR) &&
-                           (c->data.map->dst->da->type != c->data.map->src->da->type)) {
-                               talloc_free(c);
-                               *error = "Attribute comparisons must be of the same attribute type";
-                               COND_DEBUG("RETURN %d", __LINE__);
-                               return 0;
+                               /*
+                                *      The LHS is an attribute, and the RHS is a literal.  Cast the
+                                *      RHS to the data type of the LHS.
+                                */
+                               if ((c->data.map->dst->type == VPT_TYPE_ATTR) &&
+                                   (c->data.map->src->type == VPT_TYPE_LITERAL) &&
+                                   !radius_cast_tmpl(c->data.map->src, c->data.map->dst->vpt_da)) {
+                                       DICT_ATTR const *da = c->data.map->dst->vpt_da;
+
+                                       if ((da->vendor == 0) &&
+                                           ((da->attr == PW_AUTH_TYPE) ||
+                                            (da->attr == PW_AUTZ_TYPE) ||
+                                            (da->attr == PW_ACCT_TYPE) ||
+                                            (da->attr == PW_SESSION_TYPE) ||
+                                            (da->attr == PW_POST_AUTH_TYPE) ||
+                                            (da->attr == PW_PRE_PROXY_TYPE) ||
+                                            (da->attr == PW_POST_PROXY_TYPE) ||
+                                            (da->attr == PW_PRE_ACCT_TYPE) ||
+                                            (da->attr == PW_RECV_COA_TYPE) ||
+                                            (da->attr == PW_SEND_COA_TYPE))) {
+                                               /*
+                                                *      The types for these attributes are dynamically allocated
+                                                *      by modules.c, so we can't enforce strictness here.
+                                                */
+                                               c->pass2_fixup = PASS2_FIXUP_TYPE;
+
+                                       } else {
+                                               return_rhs("Failed to parse value for attribute");
+                                       }
+                               }
                        }
 
                        p += slen;
@@ -590,15 +896,11 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace,
         */
        if (*p == ')') {
                if (!brace) {
-                       talloc_free(c);
-                       *error = "Unexpected closing brace";
-                       COND_DEBUG("RETURN %d", __LINE__);
-                       return -(p - start);
+                       return_P("Unexpected closing brace");
                }
 
                p++;
                while (isspace((int) *p)) p++; /* skip spaces after closing brace */
-               brace = false;
                goto done;
        }
 
@@ -607,10 +909,7 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace,
         */
        if (!*p) {
                if (brace) {
-                       talloc_free(c);
-                       *error = "No closing brace at end of string";
-                       COND_DEBUG("RETURN %d", __LINE__);
-                       return -(p - start);
+                       return_P("No closing brace at end of string");
                }
 
                goto done;
@@ -618,51 +917,62 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace,
 
        if (!(((p[0] == '&') && (p[1] == '&')) ||
              ((p[0] == '|') && (p[1] == '|')))) {
-               talloc_free(c);
                *error = "Unexpected text after condition";
+       return_p:
+               if (lhs) talloc_free(lhs);
+               if (rhs) talloc_free(rhs);
+               talloc_free(c);
                return -(p - start);
        }
 
        /*
         *      Recurse to parse the next condition.
         */
-       COND_DEBUG("GOT %c%c", p[0], p[1]);
        c->next_op = p[0];
        p += 2;
 
        /*
         *      May still be looking for a closing brace.
         */
-       COND_DEBUG("RECURSE AND/OR");
-       slen = condition_tokenize(c, p, brace, &c->next, error);
+       slen = condition_tokenize(c, ci, p, brace, &c->next, error, flags);
        if (slen <= 0) {
+       return_slen:
+               if (lhs) talloc_free(lhs);
+               if (rhs) talloc_free(rhs);
                talloc_free(c);
-               COND_DEBUG("RETURN %d", __LINE__);
                return slen - (p - start);
        }
        p += slen;
 
 done:
-       COND_DEBUG("RETURN %d", __LINE__);
-
        /*
-        *      Normalize it before returning it.
+        *      Normalize the condition before returning.
+        *
+        *      We collapse multiple levels of braces to one.  Then
+        *      convert maps to literals.  Then literals to true/false
+        *      statements.  Then true/false ||/&& followed by other
+        *      conditions to just conditions.
+        *
+        *      Order is important.  The more complex cases are
+        *      converted to simpler ones, from the most complex cases
+        *      to the simplest ones.
         */
 
        /*
         *      (FOO)     --> FOO
         *      (FOO) ... --> FOO ...
         */
-       if ((c->type == COND_TYPE_CHILD) &&
-           !c->data.child->next) {
+       if ((c->type == COND_TYPE_CHILD) && !c->data.child->next) {
                fr_cond_t *child;
 
-               child = c->data.child;
-               child->next = c->next;
-               child->next_op = c->next_op;
-               c->next = NULL;
+               child = talloc_steal(ctx, c->data.child);
                c->data.child = NULL;
 
+               child->next = talloc_steal(child, c->next);
+               c->next = NULL;
+
+               child->next_op = c->next_op;
+
                /*
                 *      Set the negation properly
                 */
@@ -673,7 +983,7 @@ done:
                        child->negate = false;
                }
 
-               (void) talloc_steal(ctx, child);
+               lhs = rhs = NULL;
                talloc_free(c);
                c = child;
        }
@@ -684,17 +994,360 @@ done:
         *      But don't do !(FOO || BAR) --> !FOO || BAR
         *      Because that's different.
         */
-       if ((c->type == COND_TYPE_CHILD) && !c->next &&
-           !c->negate) {
+       if ((c->type == COND_TYPE_CHILD) &&
+           !c->next && !c->negate) {
                fr_cond_t *child;
 
-               child = c->data.child;
+               child = talloc_steal(ctx, c->data.child);
                c->data.child = NULL;
-               (void) talloc_steal(ctx, child);
+
+               lhs = rhs = NULL;
                talloc_free(c);
                c = child;
        }
 
+       /*
+        *      Convert maps to literals.  Convert one form of map to
+        *      a standardized form.  This doesn't make any
+        *      theoretical difference, but it does mean that the
+        *      run-time evaluation has fewer cases to check.
+        */
+       if (c->type == COND_TYPE_MAP) do {
+               /*
+                *      !FOO !~ BAR --> FOO =~ BAR
+                */
+               if (c->negate && (c->data.map->op == T_OP_REG_NE)) {
+                       c->negate = false;
+                       c->data.map->op = T_OP_REG_EQ;
+               }
+
+               /*
+                *      FOO !~ BAR --> !FOO =~ BAR
+                */
+               if (!c->negate && (c->data.map->op == T_OP_REG_NE)) {
+                       c->negate = true;
+                       c->data.map->op = T_OP_REG_EQ;
+               }
+
+               /*
+                *      !FOO != BAR --> FOO == BAR
+                */
+               if (c->negate && (c->data.map->op == T_OP_NE)) {
+                       c->negate = false;
+                       c->data.map->op = T_OP_CMP_EQ;
+               }
+
+               /*
+                *      This next one catches "LDAP-Group != foo",
+                *      which doesn't work as-is, but this hack fixes
+                *      it.
+                *
+                *      FOO != BAR --> !FOO == BAR
+                */
+               if (!c->negate && (c->data.map->op == T_OP_NE)) {
+                       c->negate = true;
+                       c->data.map->op = T_OP_CMP_EQ;
+               }
+
+               /*
+                *      FOO =* BAR --> FOO
+                *      FOO !* BAR --> !FOO
+                */
+               if ((c->data.map->op == T_OP_CMP_TRUE) ||
+                   (c->data.map->op == T_OP_CMP_FALSE)) {
+                       value_pair_tmpl_t *vpt;
+
+                       vpt = talloc_steal(c, c->data.map->dst);
+                       c->data.map->dst = NULL;
+
+                       /*
+                        *      Invert the negation bit.
+                        */
+                       if (c->data.map->op == T_OP_CMP_FALSE) {
+                               c->negate = !c->negate;
+                       }
+
+                       TALLOC_FREE(c->data.map);
+
+                       c->type = COND_TYPE_EXISTS;
+                       c->data.vpt = vpt;
+                       break;  /* it's no longer a map */
+               }
+
+               /*
+                *      Both are data (IP address, integer, etc.)
+                *
+                *      We can do the evaluation here, so that it
+                *      doesn't need to be done at run time
+                */
+               if ((c->data.map->dst->type == VPT_TYPE_DATA) &&
+                   (c->data.map->src->type == VPT_TYPE_DATA)) {
+                       int rcode;
+
+                       rad_assert(c->cast != NULL);
+
+                       rcode = radius_evaluate_map(NULL, 0, 0, c);
+                       TALLOC_FREE(c->data.map);
+                       c->cast = NULL;
+                       if (rcode) {
+                               c->type = COND_TYPE_TRUE;
+                       } else {
+                               c->type = COND_TYPE_FALSE;
+                       }
+
+                       break;  /* it's no longer a map */
+               }
+
+               /*
+                *      Both are literal strings.  They're not parsed
+                *      as VPT_TYPE_DATA because there's no cast to an
+                *      attribute.
+                *
+                *      We can do the evaluation here, so that it
+                *      doesn't need to be done at run time
+                */
+               if ((c->data.map->src->type == VPT_TYPE_LITERAL) &&
+                   (c->data.map->dst->type == VPT_TYPE_LITERAL) &&
+                   !c->pass2_fixup) {
+                       int rcode;
+
+                       rad_assert(c->cast == NULL);
+
+                       rcode = radius_evaluate_map(NULL, 0, 0, c);
+                       if (rcode) {
+                               c->type = COND_TYPE_TRUE;
+                       } else {
+                               DEBUG("OPTIMIZING %s %s --> FALSE",
+                                     c->data.map->dst->name,
+                                     c->data.map->src->name);
+                               c->type = COND_TYPE_FALSE;
+                       }
+
+                       /*
+                        *      Free map after using it above.
+                        */
+                       TALLOC_FREE(c->data.map);
+                       break;
+               }
+
+               /*
+                *      <ipaddr>"foo" CMP &Attribute-Name The cast is
+                *      unnecessary, and we can re-write it so that
+                *      the attribute reference is on the LHS.
+                */
+               if (c->cast &&
+                   (c->data.map->src->type == VPT_TYPE_ATTR) &&
+                   (c->data.map->dst->type != VPT_TYPE_ATTR)) {
+                       value_pair_tmpl_t *tmp;
+
+                       tmp = c->data.map->src;
+                       c->data.map->src = c->data.map->dst;
+                       c->data.map->dst = tmp;
+
+                       c->cast = NULL;
+
+                       switch (c->data.map->op) {
+                       case T_OP_CMP_EQ:
+                               /* do nothing */
+                               break;
+
+                       case T_OP_LE:
+                               c->data.map->op = T_OP_GE;
+                               break;
+
+                       case T_OP_LT:
+                               c->data.map->op = T_OP_GT;
+                               break;
+
+                       case T_OP_GE:
+                               c->data.map->op = T_OP_LE;
+                               break;
+
+                       case T_OP_GT:
+                               c->data.map->op = T_OP_LT;
+                               break;
+
+                       default:
+                               return_0("Internal sanity check failed");
+                       }
+
+                       /*
+                        *      This must have been parsed into VPT_TYPE_DATA.
+                        */
+                       rad_assert(c->data.map->src->type != VPT_TYPE_LITERAL);
+               }
+
+       } while (0);
+
+       /*
+        *      Existence checks.  We short-circuit static strings,
+        *      too.
+        */
+       if (c->type == COND_TYPE_EXISTS) {
+               switch (c->data.vpt->type) {
+               case VPT_TYPE_XLAT:
+               case VPT_TYPE_ATTR:
+               case VPT_TYPE_LIST:
+               case VPT_TYPE_EXEC:
+                       break;
+
+                       /*
+                        *      'true' and 'false' are special strings
+                        *      which mean themselves.
+                        *
+                        *      For integers, 0 is false, all other
+                        *      integers are true.
+                        *
+                        *      For strings, '' and "" are false.
+                        *      'foo' and "foo" are true.
+                        *
+                        *      The str2tmpl function takes care of
+                        *      marking "%{foo}" as VPT_TYPE_XLAT, so
+                        *      the strings here are fixed at compile
+                        *      time.
+                        *
+                        *      `exec` and "%{...}" are left alone.
+                        *
+                        *      Bare words must be module return
+                        *      codes.
+                        */
+               case VPT_TYPE_LITERAL:
+                       if ((strcmp(c->data.vpt->name, "true") == 0) ||
+                           (strcmp(c->data.vpt->name, "1") == 0)) {
+                               c->type = COND_TYPE_TRUE;
+                               TALLOC_FREE(c->data.vpt);
+
+                       } else if ((strcmp(c->data.vpt->name, "false") == 0) ||
+                                  (strcmp(c->data.vpt->name, "0") == 0)) {
+                               c->type = COND_TYPE_FALSE;
+                               TALLOC_FREE(c->data.vpt);
+
+                       } else if (!*c->data.vpt->name) {
+                               c->type = COND_TYPE_FALSE;
+                               TALLOC_FREE(c->data.vpt);
+
+                       } else if ((lhs_type == T_SINGLE_QUOTED_STRING) ||
+                                  (lhs_type == T_DOUBLE_QUOTED_STRING)) {
+                               c->type = COND_TYPE_TRUE;
+                               TALLOC_FREE(c->data.vpt);
+
+                       } else if (lhs_type == T_BARE_WORD) {
+                               int rcode;
+                               char const *q;
+
+                               for (q = c->data.vpt->name;
+                                    *q != '\0';
+                                    q++) {
+                                       if (!isdigit((int) *q)) {
+                                               break;
+                                       }
+                               }
+
+                               /*
+                                *      It's all digits, and therefore
+                                *      'true'.
+                                */
+                               if (!*q) {
+                                       c->type = COND_TYPE_TRUE;
+                                       TALLOC_FREE(c->data.vpt);
+                                       break;
+                               }
+
+                               rcode = fr_str2int(allowed_return_codes,
+                                                  c->data.vpt->name, 0);
+                               if (!rcode) {
+                                       return_0("Expected a module return code");
+                               }
+                       }
+
+                       /*
+                        *      Else lhs_type==T_OP_INVALID, and this
+                        *      node was made by promoting a child
+                        *      which had already been normalized.
+                        */
+                       break;
+
+               case VPT_TYPE_DATA:
+                       return_0("Cannot use data here");
+
+               default:
+                       return_0("Internal sanity check failed");
+               }
+       }
+
+       /*
+        *      !TRUE -> FALSE
+        */
+       if (c->type == COND_TYPE_TRUE) {
+               if (c->negate) {
+                       c->negate = false;
+                       c->type = COND_TYPE_FALSE;
+               }
+       }
+
+       /*
+        *      !FALSE -> TRUE
+        */
+       if (c->type == COND_TYPE_FALSE) {
+               if (c->negate) {
+                       c->negate = false;
+                       c->type = COND_TYPE_TRUE;
+               }
+       }
+
+       /*
+        *      true && FOO --> FOO
+        */
+       if ((c->type == COND_TYPE_TRUE) &&
+           (c->next_op == COND_AND)) {
+               fr_cond_t *next;
+
+               next = talloc_steal(ctx, c->next);
+               c->next = NULL;
+
+               lhs = rhs = NULL;
+               talloc_free(c);
+               c = next;
+       }
+
+       /*
+        *      false && FOO --> false
+        */
+       if ((c->type == COND_TYPE_FALSE) &&
+           (c->next_op == COND_AND)) {
+               talloc_free(c->next);
+               c->next = NULL;
+               c->next_op = COND_NONE;
+       }
+
+       /*
+        *      false || FOO --> FOO
+        */
+       if ((c->type == COND_TYPE_FALSE) &&
+           (c->next_op == COND_OR)) {
+               fr_cond_t *next;
+
+               next = talloc_steal(ctx, c->next);
+               c->next = NULL;
+
+               lhs = rhs = NULL;
+               talloc_free(c);
+               c = next;
+       }
+
+       /*
+        *      true || FOO --> true
+        */
+       if ((c->type == COND_TYPE_TRUE) &&
+           (c->next_op == COND_OR)) {
+               talloc_free(c->next);
+               c->next = NULL;
+               c->next_op = COND_NONE;
+       }
+
+       if (lhs) talloc_free(lhs);
+       if (rhs) talloc_free(rhs);
+
        *pcond = c;
        return p - start;
 }
@@ -702,12 +1355,58 @@ done:
 /** Tokenize a conditional check
  *
  *  @param[in] ctx for talloc
+ *  @param[in] ci for CONF_ITEM
  *  @param[in] start the start of the string to process.  Should be "(..."
  *  @param[out] head the parsed condition structure
  *  @param[out] error the parse error (if any)
+ *  @param[in] flags do one/two pass
  *  @return length of the string skipped, or when negative, the offset to the offending error
  */
-ssize_t fr_condition_tokenize(TALLOC_CTX *ctx, char const *start, fr_cond_t **head, char const **error)
+ssize_t fr_condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *start, fr_cond_t **head, char const **error, int flags)
 {
-       return condition_tokenize(ctx, start, false, head, error);
+       return condition_tokenize(ctx, ci, start, false, head, error, flags);
+}
+
+/*
+ *     Walk in order.
+ */
+bool fr_condition_walk(fr_cond_t *c, bool (*callback)(void *, fr_cond_t *), void *ctx)
+{
+       while (c) {
+               /*
+                *      Process this one, exit on error.
+                */
+               if (!callback(ctx, c)) return false;
+
+               switch (c->type) {
+               case COND_TYPE_INVALID:
+                       return false;
+
+               case COND_TYPE_EXISTS:
+               case COND_TYPE_MAP:
+               case COND_TYPE_TRUE:
+               case COND_TYPE_FALSE:
+                       break;
+
+               case COND_TYPE_CHILD:
+                       /*
+                        *      Walk over the child.
+                        */
+                       if (!fr_condition_walk(c->data.child, callback, ctx)) {
+                               return false;
+                       }
+               }
+
+               /*
+                *      No sibling, stop.
+                */
+               if (c->next_op == COND_NONE) break;
+
+               /*
+                *      process the next sibling
+                */
+               c = c->next;
+       }
+
+       return true;
 }