Allow delayed references to attributes. Helps with #711
[freeradius.git] / src / main / parser.c
index 8aaad31..5ceaeed 100644 (file)
@@ -47,11 +47,12 @@ static const FR_NAME_NUMBER allowed_return_codes[] = {
  *     This file shouldn't use any functions from the server core.
  */
 
-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) {
@@ -292,7 +293,7 @@ static ssize_t condition_tokenize_cast(char const *start, DICT_ATTR const **pda,
 #ifdef WITH_ASCEND_BINARY
        case PW_TYPE_ABINARY:
 #endif
-       case PW_TYPE_COMBO_IP:
+       case PW_TYPE_IP_ADDR:
        case PW_TYPE_TLV:
        case PW_TYPE_EXTENDED:
        case PW_TYPE_LONG_EXTENDED:
@@ -339,7 +340,8 @@ static ssize_t condition_tokenize_cast(char const *start, DICT_ATTR const **pda,
  *  @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, CONF_ITEM *ci, char const *start, int brace, fr_cond_t **pcond, char const **error, int flags)
+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;
        char const *p = start;
@@ -476,7 +478,8 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *st
                        rad_assert(c->data.vpt->type != VPT_TYPE_REGEX);
 
                } else { /* it's an operator */
-                       int regex;
+                       bool regex;
+                       bool i_flag = false;
 
                        /*
                         *      The next thing should now be a comparison operator.
@@ -612,7 +615,7 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *st
                                 *      Allow /foo/i
                                 */
                                if (p[slen] == 'i') {
-                                       c->regex_i = true;
+                                       i_flag = true;
                                        slen++;
                                }
 
@@ -624,19 +627,29 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *st
                                                     REQUEST_CURRENT, PAIR_LIST_REQUEST,
                                                     REQUEST_CURRENT, PAIR_LIST_REQUEST);
                        if (!c->data.map) {
-                               if (*lhs == '&') {
-                                       /*
-                                        *      FIXME: In the future,
-                                        *      have str2map above
-                                        *      know whether this is
-                                        *      pass1 or pass2.  If
-                                        *      it's pass2, then an
-                                        *      unknown attribute is a
-                                        *      soft fail.
-                                        */
+                               /*
+                                *      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");
                                }
-                               return_0("Syntax error");
+                               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;
                        }
 
                        /*
@@ -670,7 +683,7 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *st
                         */
                        if (c->cast) {
                                if ((c->data.map->src->type == VPT_TYPE_ATTR) &&
-                                   (c->cast->type != c->data.map->src->da->type)) {
+                                   (c->cast->type != c->data.map->src->vpt_da->type)) {
                                        goto same_type;
                                }
 
@@ -697,18 +710,77 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *st
                                 */
                                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->da)) {
+                                   !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->da->type)) {
+                                   (c->cast->type == c->data.map->dst->vpt_da->type)) {
                                        c->cast = NULL;
                                }
 
@@ -718,7 +790,7 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *st
                                 */
                                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)) {
+                                   (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");
                                }
@@ -746,7 +818,7 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *st
                                 */
                                if ((c->data.map->dst->type == VPT_TYPE_ATTR) &&
                                    (c->data.map->src->type != VPT_TYPE_ATTR) &&
-                                   (c->data.map->dst->da->type == PW_TYPE_STRING) &&
+                                   (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)) {
@@ -760,9 +832,9 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *st
                                 */
                                if ((c->data.map->dst->type == VPT_TYPE_ATTR) &&
                                    (c->data.map->src->type != VPT_TYPE_ATTR) &&
-                                   (c->data.map->dst->da->type != PW_TYPE_STRING) &&
-                                   (c->data.map->dst->da->type != PW_TYPE_OCTETS) &&
-                                   (c->data.map->dst->da->type != PW_TYPE_DATE) &&
+                                   (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:
@@ -787,8 +859,8 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *st
                                 */
                                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->da)) {
-                                       DICT_ATTR const *da = c->data.map->dst->da;
+                                   !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) ||
@@ -1017,7 +1089,6 @@ done:
                        rcode = radius_evaluate_map(NULL, 0, 0, c);
                        TALLOC_FREE(c->data.map);
                        c->cast = NULL;
-                       c->regex_i = false;
                        if (rcode) {
                                c->type = COND_TYPE_TRUE;
                        } else {
@@ -1036,16 +1107,19 @@ done:
                 *      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->data.map->dst->type == VPT_TYPE_LITERAL) &&
+                   !c->pass2_fixup) {
                        int rcode;
 
                        rad_assert(c->cast == NULL);
-                       rad_assert(c->regex_i == false);
 
                        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;
                        }
 
@@ -1055,6 +1129,54 @@ done:
                        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);
 
        /*