Pass2 for attributes in existence checks
[freeradius.git] / src / main / parser.c
index 5f161ea..e1e5581 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) {
@@ -133,43 +134,6 @@ next:
 }
 
 
-/*
- *     Cast a literal vpt to a value_data_t
- */
-static int cast_vpt(value_pair_tmpl_t *vpt, DICT_ATTR const *da)
-{
-       VALUE_PAIR *vp;
-       value_data_t *data;
-
-       rad_assert(vpt->type == VPT_TYPE_LITERAL);
-
-       vp = pairalloc(vpt, da);
-       if (!vp) return false;
-
-       if (!pairparsevalue(vp, vpt->name)) {
-               pairfree(&vp);
-               return false;
-       }
-
-       vpt->length = vp->length;
-       vpt->vpd = data = talloc(vpt, value_data_t);
-       if (!vpt->vpd) return false;
-
-       vpt->type = VPT_TYPE_DATA;
-       vpt->da = da;
-
-       if (vp->da->flags.is_pointer) {
-               data->ptr = talloc_steal(vpt, vp->data.ptr);
-               vp->data.ptr = NULL;
-       } else {
-               memcpy(data, &vp->data, sizeof(*data));
-       }
-
-       pairfree(&vp);
-
-       return true;
-}
-
 static ssize_t condition_tokenize_string(TALLOC_CTX *ctx, char const *start, char **out,
                                         FR_TOKEN *op, char const **error)
 {
@@ -321,6 +285,27 @@ static ssize_t condition_tokenize_cast(char const *start, DICT_ATTR const **pda,
                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";
@@ -355,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;
@@ -403,6 +389,7 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *st
                 *      brackets.  Go recurse to get more.
                 */
                c->type = COND_TYPE_CHILD;
+               c->ci = ci;
                slen = condition_tokenize(c, ci, p, true, &c->data.child, error, flags);
                if (slen <= 0) {
                        return_SLEN;
@@ -481,22 +468,42 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *st
                        }
 
                        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");
+                               /*
+                                *      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_P("Failed creating exists");
+                               }
+                               c->data.vpt = radius_str2tmpl(c, lhs + 1, lhs_type, REQUEST_CURRENT, PAIR_LIST_REQUEST);
+                               if (!c->data.vpt) {
+                                       return_P("Failed creating exists");
+                               }
+                               rad_const_free(c->data.vpt->name);
+                               c->data.vpt->name = talloc_strdup(c->data.vpt, lhs);
+                               c->pass2_fixup = PASS2_FIXUP_ATTR;
                        }
 
                        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.
                         */
                        regex = false;
                        c->type = COND_TYPE_MAP;
+                       c->ci = ci;
+
                        switch (*p) {
                        default:
                                return_P("Invalid text. Expected comparison operator");
@@ -624,7 +631,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++;
                                }
 
@@ -636,19 +643,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.
-                                        */
+                               /*
+                                *      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;
                        }
 
                        /*
@@ -682,7 +699,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;
                                }
 
@@ -695,7 +712,7 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *st
                                 *      Cast it to the appropriate data type.
                                 */
                                if ((c->data.map->dst->type == VPT_TYPE_LITERAL) &&
-                                   !cast_vpt(c->data.map->dst, c->cast)) {
+                                   !radius_cast_tmpl(c->data.map->dst, c->cast)) {
                                        *error = "Failed to parse field";
                                        if (lhs) talloc_free(lhs);
                                        if (rhs) talloc_free(rhs);
@@ -709,18 +726,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) &&
-                                   !cast_vpt(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;
                                }
 
@@ -730,9 +806,9 @@ 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 attribute type");
+                                       return_0("Attribute comparisons must be of the same data type");
                                }
 
                                /*
@@ -758,7 +834,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)) {
@@ -772,9 +848,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:
@@ -789,7 +865,7 @@ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *st
                                 *      literal.  Cast the RHS to the type of the cast.
                                 */
                                if (c->cast && (c->data.map->src->type == VPT_TYPE_LITERAL) &&
-                                   !cast_vpt(c->data.map->src, c->cast)) {
+                                   !radius_cast_tmpl(c->data.map->src, c->cast)) {
                                        return_rhs("Failed to parse field");
                                }
 
@@ -799,8 +875,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) &&
-                                   !cast_vpt(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) ||
@@ -992,6 +1068,9 @@ done:
                /*
                 *      FOO =* BAR --> FOO
                 *      FOO !* BAR --> !FOO
+                *
+                *      FOO may be a string, or a delayed attribute
+                *      reference.
                 */
                if ((c->data.map->op == T_OP_CMP_TRUE) ||
                    (c->data.map->op == T_OP_CMP_FALSE)) {
@@ -1029,7 +1108,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 {
@@ -1048,16 +1126,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;
                        }
 
@@ -1067,11 +1148,64 @@ 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);
 
        /*
         *      Existence checks.  We short-circuit static strings,
         *      too.
+        *
+        *      FIXME: the data types should be in the template, too.
+        *      So that we know where a literal came from.
+        *
+        *      "foo" is NOT the same as 'foo' or a bare foo.
         */
        if (c->type == COND_TYPE_EXISTS) {
                switch (c->data.vpt->type) {
@@ -1123,6 +1257,7 @@ done:
 
                        } else if (lhs_type == T_BARE_WORD) {
                                int rcode;
+                               bool zeros = true;
                                char const *q;
 
                                for (q = c->data.vpt->name;
@@ -1131,6 +1266,7 @@ done:
                                        if (!isdigit((int) *q)) {
                                                break;
                                        }
+                                       if (*q != '0') zeros = false;
                                }
 
                                /*
@@ -1138,11 +1274,23 @@ done:
                                 *      'true'.
                                 */
                                if (!*q) {
-                                       c->type = COND_TYPE_TRUE;
+                                       if (zeros) {
+                                               c->type = COND_TYPE_TRUE;
+                                       } else {
+                                               c->type = COND_TYPE_TRUE;
+                                       }
                                        TALLOC_FREE(c->data.vpt);
                                        break;
                                }
 
+                               /*
+                                *      Allow &Foo-Bar where Foo-Bar is an attribute
+                                *      defined by a module.
+                                */
+                               if (c->pass2_fixup == PASS2_FIXUP_ATTR) {
+                                       break;
+                               }
+
                                rcode = fr_str2int(allowed_return_codes,
                                                   c->data.vpt->name, 0);
                                if (!rcode) {