/* * parser.c Parse various things * * Version: $Id$ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * * Copyright 2013 Alan DeKok */ RCSID("$Id$") #include #include #include #include #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. */ 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; rad_assert(bufsize > 0); next: rad_assert(p < end); if (!c) { p[0] = '\0'; return 0; } /* * Don't overflow the output buffer. */ if ((end - p) < 2) { p[0] = '\0'; return 0; } if (c->negate) { *(p++) = '!'; /* FIXME: only allow for child? */ } switch (c->type) { case COND_TYPE_EXISTS: rad_assert(c->data.vpt != NULL); if (c->cast) { snprintf(p, end - p, "<%s>", fr_int2str(dict_attr_types, c->cast->type, "??")); p += strlen(p); } len = tmpl_prints(p, end - p, c->data.vpt, NULL); p += len; break; case COND_TYPE_MAP: rad_assert(c->data.map != NULL); #if 0 *(p++) = '['; /* for extra-clear debugging */ #endif if (c->cast) { snprintf(p, end - p, "<%s>", fr_int2str(dict_attr_types, c->cast->type, "??")); p += strlen(p); } len = map_prints(p, end - p, c->data.map); p += len; #if 0 *(p++) = ']'; #endif break; case COND_TYPE_CHILD: rad_assert(c->data.child != NULL); *(p++) = '('; len = fr_cond_sprint(p, end - p, c->data.child); p += len; *(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; } if (c->next_op == COND_NONE) { rad_assert(c->next == NULL); *p = '\0'; return p - buffer; } if (c->next_op == COND_AND) { strlcpy(p, " && ", end - p); p += strlen(p); } else if (c->next_op == COND_OR) { strlcpy(p, " || ", end - p); p += strlen(p); } else { rad_assert(0 == 1); } c = c->next; goto next; } static ssize_t condition_tokenize_string(TALLOC_CTX *ctx, char **out, char const **error, char const *start, FR_TOKEN *op) { char const *p = start; char *q; switch (*p++) { default: return -1; case '"': *op = T_DOUBLE_QUOTED_STRING; break; case '\'': *op = T_SINGLE_QUOTED_STRING; break; case '`': *op = T_BACK_QUOTED_STRING; break; case '/': *op = T_OP_REG_EQ; /* a bit of a hack. */ break; } *out = talloc_array(ctx, char, strlen(start) - 1); /* + 2 - 1 */ if (!*out) return -1; q = *out; while (*p) { if (*p == *start) { /* * Call the STANDARD parse function to figure out what the string is. */ if (cf_new_escape) { ssize_t slen; value_data_t data; char quote = *start; PW_TYPE src_type = PW_TYPE_STRING; /* * Regex compilers can handle escapes. So we don't do it. */ if (quote == '/') quote = '\0'; slen = value_data_from_str(ctx, &data, &src_type, NULL, start + 1, p - (start + 1), quote); if (slen < 0) { *error = "error parsing string"; return slen - 1; } talloc_free(*out); *out = talloc_steal(ctx, data.ptr); data.strvalue = NULL; } else { char *out2; *(q++) = '\0'; /* terminate the output string */ out2 = talloc_realloc(ctx, *out, char, (q - *out)); if (!out2) { *error = "Out of memory"; return -1; } *out = out2; } p++; return (p - start); } if (*p == '\\') { if (!p[1]) { p++; *error = "End of string after escape"; return -(p - start); } /* * Hacks for backwards compatibility */ if (cf_new_escape) { if (p[1] == start[0]) { /* Convert '\'' --> ' */ p++; } else { *(q++) = *(p++); } } else { switch (p[1]) { case 'r': *q++ = '\r'; break; case 'n': *q++ = '\n'; break; case 't': *q++ = '\t'; break; default: *q++ = p[1]; break; } p += 2; continue; } } *(q++) = *(p++); } *error = "Unterminated string"; return -1; } static ssize_t condition_tokenize_word(TALLOC_CTX *ctx, char const *start, char **out, FR_TOKEN *op, char const **error) { size_t len; char const *p = start; if ((*p == '"') || (*p == '\'') || (*p == '`') || (*p == '/')) { return condition_tokenize_string(ctx, out, error, start, op); } *op = T_BARE_WORD; if (*p == '&') p++; /* special-case &User-Name */ while (*p) { /* * The LHS should really be limited to only a few * things. For now, we allow pretty much anything. */ if (*p == '\\') { *error = "Unexpected escape"; return -(p - start); } /* * ("foo") is valid. */ if (*p == ')') { break; } /* * Spaces or special characters delineate the word */ if (isspace((uint8_t) *p) || (*p == '&') || (*p == '|') || (*p == '!') || (*p == '=') || (*p == '<') || (*p == '>')) { break; } if ((*p == '"') || (*p == '\'') || (*p == '`')) { *error = "Unexpected start of string"; return -(p - start); } p++; } len = p - start; if (!len) { *error = "Empty string is invalid"; return 0; } *out = talloc_array(ctx, char, len + 1); memcpy(*out, start, len); (*out)[len] = '\0'; 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((uint8_t) *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_COMBO_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((uint8_t) *q)) q++; /* skip spaces after cast */ return q - start; } static bool condition_check_types(fr_cond_t *c, PW_TYPE lhs_type) { /* * SOME integer mismatch is OK. If the LHS has a large type, * and the RHS has a small type, it's OK. * * If the LHS has a small type, and the RHS has a large type, * then add a cast to the LHS. */ if (lhs_type == PW_TYPE_INTEGER64) { if ((c->data.map->rhs->tmpl_da->type == PW_TYPE_INTEGER) || (c->data.map->rhs->tmpl_da->type == PW_TYPE_SHORT) || (c->data.map->rhs->tmpl_da->type == PW_TYPE_BYTE)) { c->cast = NULL; return true; } } if (lhs_type == PW_TYPE_INTEGER) { if ((c->data.map->rhs->tmpl_da->type == PW_TYPE_SHORT) || (c->data.map->rhs->tmpl_da->type == PW_TYPE_BYTE)) { c->cast = NULL; return true; } if (c->data.map->rhs->tmpl_da->type == PW_TYPE_INTEGER64) { c->cast = c->data.map->rhs->tmpl_da; return true; } } if (lhs_type == PW_TYPE_SHORT) { if (c->data.map->rhs->tmpl_da->type == PW_TYPE_BYTE) { c->cast = NULL; return true; } if ((c->data.map->rhs->tmpl_da->type == PW_TYPE_INTEGER64) || (c->data.map->rhs->tmpl_da->type == PW_TYPE_INTEGER)) { c->cast = c->data.map->rhs->tmpl_da; return true; } } if (lhs_type == PW_TYPE_BYTE) { if ((c->data.map->rhs->tmpl_da->type == PW_TYPE_INTEGER64) || (c->data.map->rhs->tmpl_da->type == PW_TYPE_INTEGER) || (c->data.map->rhs->tmpl_da->type == PW_TYPE_SHORT)) { c->cast = c->data.map->rhs->tmpl_da; return true; } } if ((lhs_type == PW_TYPE_IPV4_PREFIX) && (c->data.map->rhs->tmpl_da->type == PW_TYPE_IPV4_ADDR)) { return true; } if ((lhs_type == PW_TYPE_IPV6_PREFIX) && (c->data.map->rhs->tmpl_da->type == PW_TYPE_IPV6_ADDR)) { return true; } /* * Same checks as above, but with the types swapped, and * with explicit cast for the interpretor. */ if ((lhs_type == PW_TYPE_IPV4_ADDR) && (c->data.map->rhs->tmpl_da->type == PW_TYPE_IPV4_PREFIX)) { c->cast = c->data.map->rhs->tmpl_da; return true; } if ((lhs_type == PW_TYPE_IPV6_ADDR) && (c->data.map->rhs->tmpl_da->type == PW_TYPE_IPV6_PREFIX)) { c->cast = c->data.map->rhs->tmpl_da; return true; } return false; } /* * 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[out] pcond pointer to the returned 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 */ static ssize_t condition_tokenize(TALLOC_CTX *ctx, CONF_ITEM *ci, char const *start, bool brace, fr_cond_t **pcond, char const **error, int flags) { ssize_t slen, tlen; char const *p = start; char const *lhs_p, *rhs_p; fr_cond_t *c; char *lhs, *rhs; FR_TOKEN op, lhs_type, rhs_type; c = talloc_zero(ctx, fr_cond_t); rad_assert(c != NULL); lhs = rhs = NULL; lhs_type = rhs_type = T_INVALID; while (isspace((uint8_t) *p)) p++; /* skip spaces before condition */ if (!*p) { return_P("Empty condition is invalid"); } /* * !COND */ if (*p == '!') { p++; c->negate = true; while (isspace((uint8_t) *p)) p++; /* skip spaces after negation */ /* * Just for stupidity */ if (*p == '!') { return_P("Double negation is invalid"); } } /* * (COND) */ if (*p == '(') { p++; /* * We've already eaten one layer of * 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; } if (!c->data.child) { return_P("Empty condition is invalid"); } p += slen; while (isspace((uint8_t) *p)) p++; /* skip spaces after (COND)*/ } else { /* it's a bare FOO==BAR */ /* * We didn't see anything special. The condition must be one of * * FOO * FOO OP BAR */ /* * Grab the LHS */ if (*p == '/') { 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; #ifndef __clang_analyzer__ lhs_p = p; #endif slen = condition_tokenize_word(c, p, &lhs, &lhs_type, error); if (slen <= 0) { return_SLEN; } p += slen; #ifdef __clang_analyzer__ if (!lhs) return_P("Internal error"); #endif /* * If the LHS is 0xabcdef... automatically cast it to octets */ if (!c->cast && (lhs_type == T_BARE_WORD) && (lhs[0] == '0') && (lhs[1] == 'x') && ((slen & 0x01) == 0)) { if (slen == 2) { return_P("Empty octet string is invalid"); } c->cast = dict_attrbyvalue(PW_CAST_BASE + PW_TYPE_OCTETS, 0); } while (isspace((uint8_t)*p)) p++; /* skip spaces after LHS */ /* * We may (or not) have an operator */ /* * (FOO) */ if (*p == ')') { /* * don't skip the brace. We'll look for it later. */ goto exists; /* * FOO */ } else if (!*p) { if (brace) { return_P("No closing brace at end of string"); } goto exists; /* * FOO && ... */ } else if (((p[0] == '&') && (p[1] == '&')) || ((p[0] == '|') && (p[1] == '|'))) { exists: if (c->cast) { return_0("Cannot do cast for existence check"); } c->type = COND_TYPE_EXISTS; c->ci = ci; tlen = tmpl_afrom_str(c, &c->data.vpt, lhs, talloc_array_length(lhs) - 1, lhs_type, REQUEST_CURRENT, PAIR_LIST_REQUEST, false); if (tlen < 0) { p = lhs_p - tlen; return_P(fr_strerror()); } rad_assert(c->data.vpt->type != TMPL_TYPE_REGEX); if (c->data.vpt->type == TMPL_TYPE_ATTR_UNDEFINED) { c->pass2_fixup = PASS2_FIXUP_ATTR; } } else { /* it's an operator */ #ifdef HAVE_REGEX bool regex = false; bool iflag = false; bool mflag = false; #endif vp_map_t *map; /* * The next thing should now be a comparison operator. */ c->type = COND_TYPE_MAP; c->ci = ci; switch (*p) { default: return_P("Invalid text. Expected comparison operator"); case '!': if (p[1] == '=') { op = T_OP_NE; p += 2; #ifdef HAVE_REGEX } else if (p[1] == '~') { regex = true; op = T_OP_REG_NE; p += 2; #endif } else if (p[1] == '*') { if (lhs_type != T_BARE_WORD) { return_P("Cannot use !* on a string"); } op = T_OP_CMP_FALSE; p += 2; } else { goto invalid_operator; } break; case '=': if (p[1] == '=') { op = T_OP_CMP_EQ; p += 2; #ifdef HAVE_REGEX } else if (p[1] == '~') { regex = true; op = T_OP_REG_EQ; p += 2; #endif } 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 { invalid_operator: return_P("Invalid operator"); } break; case '<': if (p[1] == '=') { op = T_OP_LE; p += 2; } else { op = T_OP_LT; p++; } break; case '>': if (p[1] == '=') { op = T_OP_GE; p += 2; } else { op = T_OP_GT; p++; } break; } while (isspace((uint8_t) *p)) p++; /* skip spaces after operator */ if (!*p) { return_P("Expected text after operator"); } /* * 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; } #ifdef __clang_analyzer__ if (!cast_da) return_P("Internal error"); #endif 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) { return_SLEN; } #ifdef HAVE_REGEX /* * Sanity checks for regexes. */ if (regex) { if (*p != '/') { return_P("Expected regular expression"); } for (;;) { switch (p[slen]) { /* * /foo/i */ case 'i': iflag = true; slen++; continue; /* * /foo/m */ case 'm': mflag = true; slen++; continue; default: break; } break; } } else if (!regex && (*p == '/')) { return_P("Unexpected regular expression"); } #endif /* * Duplicate map_from_fields here, as we * want to separate parse errors in the * LHS from ones in the RHS. */ c->data.map = map = talloc_zero(c, vp_map_t); tlen = tmpl_afrom_str(map, &map->lhs, lhs, talloc_array_length(lhs) - 1, lhs_type, REQUEST_CURRENT, PAIR_LIST_REQUEST, false); if (tlen < 0) { p = lhs_p - tlen; return_P(fr_strerror()); } if (tmpl_define_unknown_attr(map->lhs) < 0) { return_lhs("Failed defining attribute"); return_lhs: if (lhs) talloc_free(lhs); if (rhs) talloc_free(rhs); talloc_free(c); return -(lhs_p - start); } map->op = op; /* * If the RHS is 0xabcdef... automatically cast it to octets * unless the LHS is an attribute of type octets, or an * integer type. */ if (!c->cast && (rhs_type == T_BARE_WORD) && (rhs[0] == '0') && (rhs[1] == 'x') && ((slen & 0x01) == 0)) { if (slen == 2) { return_P("Empty octet string is invalid"); } if ((map->lhs->type != TMPL_TYPE_ATTR) || !((map->lhs->tmpl_da->type == PW_TYPE_OCTETS) || (map->lhs->tmpl_da->type == PW_TYPE_BYTE) || (map->lhs->tmpl_da->type == PW_TYPE_SHORT) || (map->lhs->tmpl_da->type == PW_TYPE_INTEGER) || (map->lhs->tmpl_da->type == PW_TYPE_INTEGER64))) { c->cast = dict_attrbyvalue(PW_CAST_BASE + PW_TYPE_OCTETS, 0); } } if ((map->lhs->type == TMPL_TYPE_ATTR) && map->lhs->tmpl_da->flags.is_unknown && map_cast_from_hex(map, rhs_type, rhs)) { /* do nothing */ } else { tlen = tmpl_afrom_str(map, &map->rhs, rhs, talloc_array_length(rhs) - 1, rhs_type, REQUEST_CURRENT, PAIR_LIST_REQUEST, false); if (tlen < 0) { p = rhs_p - tlen; return_P(fr_strerror()); } if (tmpl_define_unknown_attr(map->rhs) < 0) { return_rhs("Failed defining attribute"); } } /* * Unknown attributes get marked up for pass2. */ if ((c->data.map->lhs->type == TMPL_TYPE_ATTR_UNDEFINED) || (c->data.map->rhs->type == TMPL_TYPE_ATTR_UNDEFINED)) { c->pass2_fixup = PASS2_FIXUP_ATTR; } #ifdef HAVE_REGEX if (c->data.map->rhs->type == TMPL_TYPE_REGEX) { c->data.map->rhs->tmpl_iflag = iflag; c->data.map->rhs->tmpl_mflag = mflag; } #endif /* * 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->rhs->type == TMPL_TYPE_LIST) || (c->data.map->lhs->type == TMPL_TYPE_LIST)) { return_0("Cannot use list references in condition"); } /* * 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->rhs->type == TMPL_TYPE_ATTR) && (c->cast->type != c->data.map->rhs->tmpl_da->type)) { if (condition_check_types(c, c->cast->type)) { goto keep_going; } goto same_type; } #ifdef HAVE_REGEX if (c->data.map->rhs->type == TMPL_TYPE_REGEX) { return_0("Cannot use cast with regex comparison"); } #endif /* * The LHS is a literal which has been cast to a data type. * Cast it to the appropriate data type. */ if ((c->data.map->lhs->type == TMPL_TYPE_LITERAL) && (tmpl_cast_in_place(c->data.map->lhs, c->cast->type, c->cast) < 0)) { *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->lhs->type == TMPL_TYPE_DATA) && (c->data.map->rhs->type == TMPL_TYPE_LITERAL) && (tmpl_cast_in_place(c->data.map->rhs, c->cast->type, c->cast) < 0)) { return_rhs("Failed to parse field"); } /* * We may be casting incompatible * types. We check this based on * their size. */ if (c->data.map->lhs->type == TMPL_TYPE_ATTR) { /* * dst.min == src.min * dst.max == src.max */ if ((dict_attr_sizes[c->cast->type][0] == dict_attr_sizes[c->data.map->lhs->tmpl_da->type][0]) && (dict_attr_sizes[c->cast->type][1] == dict_attr_sizes[c->data.map->lhs->tmpl_da->type][1])) { goto cast_ok; } /* * Run-time parsing of strings. * Run-time copying of octets. */ if ((c->data.map->lhs->tmpl_da->type == PW_TYPE_STRING) || (c->data.map->lhs->tmpl_da->type == PW_TYPE_OCTETS)) { goto cast_ok; } /* * ifid to integer64 is OK */ if ((c->data.map->lhs->tmpl_da->type == PW_TYPE_IFID) && (c->cast->type == PW_TYPE_INTEGER64)) { goto cast_ok; } /* * ipaddr to ipv4prefix is OK */ if ((c->data.map->lhs->tmpl_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->lhs->tmpl_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->lhs->tmpl_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->lhs->tmpl_da->type][0]) || (dict_attr_sizes[c->cast->type][0] > dict_attr_sizes[c->data.map->lhs->tmpl_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->lhs->type == TMPL_TYPE_ATTR) && (c->cast->type == c->data.map->lhs->tmpl_da->type)) { c->cast = NULL; } } else { vp_tmpl_t *vpt; /* * Two attributes? They must be of the same type */ if ((c->data.map->rhs->type == TMPL_TYPE_ATTR) && (c->data.map->lhs->type == TMPL_TYPE_ATTR) && (c->data.map->lhs->tmpl_da->type != c->data.map->rhs->tmpl_da->type)) { if (condition_check_types(c, c->data.map->lhs->tmpl_da->type)) { goto keep_going; } 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->rhs->type == TMPL_TYPE_ATTR) && (c->data.map->lhs->type != TMPL_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->lhs->type == TMPL_TYPE_ATTR) && (c->data.map->rhs->type != TMPL_TYPE_ATTR) && (c->data.map->lhs->tmpl_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->lhs->type == TMPL_TYPE_ATTR) && (c->data.map->rhs->type != TMPL_TYPE_ATTR) && (c->data.map->lhs->tmpl_da->type != PW_TYPE_STRING) && (c->data.map->lhs->tmpl_da->type != PW_TYPE_OCTETS) && (c->data.map->lhs->tmpl_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->rhs->type == TMPL_TYPE_LITERAL) && (tmpl_cast_in_place(c->data.map->rhs, c->cast->type, c->cast) < 0)) { return_rhs("Failed to parse field"); } /* * The LHS is an attribute, and the RHS is a literal. Cast the * RHS to the data type of the LHS. * * Note: There's a hack in here to always parse RHS as the * equivalent prefix type if the LHS is an IP address. * * This allows Framed-IP-Address < 192.168.0.0./24 */ if ((c->data.map->lhs->type == TMPL_TYPE_ATTR) && (c->data.map->rhs->type == TMPL_TYPE_LITERAL)) { PW_TYPE type = c->data.map->lhs->tmpl_da->type; switch (c->data.map->lhs->tmpl_da->type) { case PW_TYPE_IPV4_ADDR: if (strchr(c->data.map->rhs->name, '/') != NULL) { type = PW_TYPE_IPV4_PREFIX; c->cast = dict_attrbyvalue(PW_CAST_BASE + type, 0); } break; case PW_TYPE_IPV6_ADDR: if (strchr(c->data.map->rhs->name, '/') != NULL) { type = PW_TYPE_IPV6_PREFIX; c->cast = dict_attrbyvalue(PW_CAST_BASE + type, 0); } break; default: break; } if (tmpl_cast_in_place(c->data.map->rhs, type, c->data.map->lhs->tmpl_da) < 0) { DICT_ATTR const *da = c->data.map->lhs->tmpl_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"); } } /* * Stupid WiMAX shit. * Cast the LHS to the * type of the RHS. */ if (c->data.map->lhs->tmpl_da->type == PW_TYPE_COMBO_IP_ADDR) { DICT_ATTR const *da; da = dict_attrbytype(c->data.map->lhs->tmpl_da->attr, c->data.map->lhs->tmpl_da->vendor, c->data.map->rhs->tmpl_data_type); if (!da) { return_rhs("Cannot find type for attribute"); } c->data.map->lhs->tmpl_da = da; } } /* attr to literal comparison */ /* * The RHS will turn into... something. Allow for prefixes * there, too. */ if ((c->data.map->lhs->type == TMPL_TYPE_ATTR) && ((c->data.map->rhs->type == TMPL_TYPE_XLAT) || (c->data.map->rhs->type == TMPL_TYPE_XLAT_STRUCT) || (c->data.map->rhs->type == TMPL_TYPE_EXEC))) { if (c->data.map->lhs->tmpl_da->type == PW_TYPE_IPV4_ADDR) { c->cast = dict_attrbyvalue(PW_CAST_BASE + PW_TYPE_IPV4_PREFIX, 0); } if (c->data.map->lhs->tmpl_da->type == PW_TYPE_IPV6_ADDR) { c->cast = dict_attrbyvalue(PW_CAST_BASE + PW_TYPE_IPV6_PREFIX, 0); } } /* * If the LHS is a bare word, AND it looks like * an attribute, try to parse it as such. * * This allows LDAP-Group and SQL-Group to work. * * The real fix is to just read the config files, * and do no parsing until after all of the modules * are loaded. But that has issues, too. */ if ((c->data.map->lhs->type == TMPL_TYPE_LITERAL) && (lhs_type == T_BARE_WORD)) { int hyphens = 0; bool may_be_attr = true; size_t i; ssize_t attr_slen; /* * Backwards compatibility: Allow Foo-Bar, * e.g. LDAP-Group and SQL-Group. */ for (i = 0; i < c->data.map->lhs->len; i++) { if (!dict_attr_allowed_chars[(unsigned char) c->data.map->lhs->name[i]]) { may_be_attr = false; break; } if (c->data.map->lhs->name[i] == '-') { hyphens++; } } if (!hyphens || (hyphens > 3)) may_be_attr = false; if (may_be_attr) { attr_slen = tmpl_afrom_attr_str(c->data.map, &vpt, lhs, REQUEST_CURRENT, PAIR_LIST_REQUEST, true, true); if ((attr_slen > 0) && (vpt->len == c->data.map->lhs->len)) { talloc_free(c->data.map->lhs); c->data.map->lhs = vpt; c->pass2_fixup = PASS2_FIXUP_ATTR; } } } } /* we didn't have a cast */ keep_going: p += slen; while (isspace((uint8_t) *p)) p++; /* skip spaces after RHS */ } /* parse OP RHS */ } /* parse a condition (COND) or FOO OP BAR*/ /* * ...COND) */ if (*p == ')') { if (!brace) { return_P("Unexpected closing brace"); } p++; while (isspace((uint8_t) *p)) p++; /* skip spaces after closing brace */ goto done; } /* * End of string is now allowed. */ if (!*p) { if (brace) { return_P("No closing brace at end of string"); } goto done; } if (!(((p[0] == '&') && (p[1] == '&')) || ((p[0] == '|') && (p[1] == '|')))) { *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. */ c->next_op = p[0]; p += 2; /* * May still be looking for a closing brace. */ 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); return slen - (p - start); } p += slen; done: /* * 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) { fr_cond_t *child; 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 */ if ((c->negate && !child->negate) || (!c->negate && child->negate)) { child->negate = true; } else { child->negate = false; } lhs = rhs = NULL; talloc_free(c); c = child; } /* * (FOO ...) --> FOO ... * * But don't do !(FOO || BAR) --> !FOO || BAR * Because that's different. */ if ((c->type == COND_TYPE_CHILD) && !c->next && !c->negate) { fr_cond_t *child; child = talloc_steal(ctx, c->data.child); c->data.child = NULL; 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 { VERIFY_MAP(c->data.map); /* * !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 * * 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)) { vp_tmpl_t *vpt; vpt = talloc_steal(c, c->data.map->lhs); c->data.map->lhs = 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->lhs->type == TMPL_TYPE_DATA) && (c->data.map->rhs->type == TMPL_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 TMPL_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->rhs->type == TMPL_TYPE_LITERAL) && (c->data.map->lhs->type == TMPL_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 { DEBUG3("OPTIMIZING (%s %s %s) --> FALSE", c->data.map->lhs->name, fr_int2str(fr_tokens, c->data.map->op, "??"), c->data.map->rhs->name); c->type = COND_TYPE_FALSE; } /* * Free map after using it above. */ TALLOC_FREE(c->data.map); break; } /* * "foo" CMP &Attribute-Name The cast may * not be necessary, and we can re-write it so * that the attribute reference is on the LHS. */ if (c->cast && (c->data.map->rhs->type == TMPL_TYPE_ATTR) && (c->cast->type == c->data.map->rhs->tmpl_da->type) && (c->data.map->lhs->type != TMPL_TYPE_ATTR)) { vp_tmpl_t *tmp; tmp = c->data.map->rhs; c->data.map->rhs = c->data.map->lhs; c->data.map->lhs = 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 1"); } /* * This must have been parsed into TMPL_TYPE_DATA. */ rad_assert(c->data.map->rhs->type != TMPL_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) { VERIFY_TMPL(c->data.vpt); switch (c->data.vpt->type) { case TMPL_TYPE_XLAT: case TMPL_TYPE_ATTR: case TMPL_TYPE_ATTR_UNDEFINED: case TMPL_TYPE_LIST: case TMPL_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 TMPL_TYPE_XLAT, so * the strings here are fixed at compile * time. * * `exec` and "%{...}" are left alone. * * Bare words must be module return * codes. */ case TMPL_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; bool zeros = true; char const *q; for (q = c->data.vpt->name; *q != '\0'; q++) { if (!isdigit((uint8_t) *q)) { break; } if (*q != '0') zeros = false; } /* * It's all digits, and therefore * 'false' if zero, and 'true' otherwise. */ if (!*q) { if (zeros) { c->type = COND_TYPE_FALSE; } 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) { return_0("Expected a module return code"); } } /* * Else lhs_type==T_INVALID, and this * node was made by promoting a child * which had already been normalized. */ break; case TMPL_TYPE_DATA: return_0("Cannot use data here"); default: return_0("Internal sanity check failed 2"); } } /* * !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; } /** 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, CONF_ITEM *ci, char const *start, fr_cond_t **head, char const **error, int flags) { 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; }