2 * parser.c Parse various things
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * Copyright 2013 Alan DeKok <aland@freeradius.org>
25 #include <freeradius-devel/radiusd.h>
26 #include <freeradius-devel/parser.h>
27 #include <freeradius-devel/rad_assert.h>
32 * This file shouldn't use any functions from the server core.
35 size_t fr_cond_sprint(char *buffer, size_t bufsize, fr_cond_t const *c)
39 char *end = buffer + bufsize - 1;
43 *(p++) = '!'; /* FIXME: only allow for child? */
47 case COND_TYPE_EXISTS:
48 rad_assert(c->data.vpt != NULL);
50 len = snprintf(p, end - p, "<%s>", fr_int2str(dict_attr_types,
51 c->cast->type, "??"));
55 len = radius_tmpl2str(p, end - p, c->data.vpt);
60 rad_assert(c->data.map != NULL);
62 *(p++) = '['; /* for extra-clear debugging */
65 len = snprintf(p, end - p, "<%s>", fr_int2str(dict_attr_types,
66 c->cast->type, "??"));
70 len = radius_map2str(p, end - p, c->data.map);
78 rad_assert(c->data.child != NULL);
80 len = fr_cond_sprint(p, end - p, c->data.child);
90 if (c->next_op == COND_NONE) {
91 rad_assert(c->next == NULL);
96 if (c->next_op == COND_AND) {
97 strlcpy(p, " && ", end - p);
100 } else if (c->next_op == COND_OR) {
101 strlcpy(p, " || ", end - p);
113 static ssize_t condition_tokenize_string(TALLOC_CTX *ctx, char const *start, char **out,
114 FR_TOKEN *op, char const **error)
116 const char *p = start;
124 *op = T_DOUBLE_QUOTED_STRING;
128 *op = T_SINGLE_QUOTED_STRING;
132 *op = T_BACK_QUOTED_STRING;
136 *op = T_OP_REG_EQ; /* a bit of a hack. */
141 *out = talloc_array(ctx, char, strlen(start) - 1); /* + 2 - 1 */
142 if (!*out) return -1;
156 *error = "End of string after escape";
181 *error = "Unterminated string";
185 static ssize_t condition_tokenize_word(TALLOC_CTX *ctx, char const *start, char **out,
186 FR_TOKEN *op, char const **error)
189 char const *p = start;
191 if ((*p == '"') || (*p == '\'') || (*p == '`') || (*p == '/')) {
192 return condition_tokenize_string(ctx, start, out, op, error);
196 if (*p == '&') p++; /* special-case &User-Name */
200 * The LHS should really be limited to only a few
201 * things. For now, we allow pretty much anything.
204 *error = "Unexpected escape";
216 * Spaces or special characters delineate the word
218 if (isspace((int) *p) || (*p == '&') || (*p == '|') ||
219 (*p == '!') || (*p == '=') || (*p == '<') || (*p == '>')) {
223 if ((*p == '"') || (*p == '\'') || (*p == '`')) {
224 *error = "Unexpected start of string";
233 *error = "Empty string is invalid";
237 *out = talloc_array(ctx, char, len + 1);
238 memcpy(*out, start, len);
244 static ssize_t condition_tokenize_cast(char const *start, DICT_ATTR const **pda, char const **error)
246 char const *p = start;
250 while (isspace((int) *p)) p++; /* skip spaces before condition */
252 if (*p != '<') return 0;
256 while (*q && *q != '>') q++;
258 cast = fr_substr2int(dict_attr_types, p, PW_TYPE_INVALID, q - p);
259 if (cast == PW_TYPE_INVALID) {
260 *error = "Invalid data type in cast";
264 *pda = dict_attrbyvalue(1850 + cast, 0);
266 *error = "Cannot cast to this data type";
272 while (isspace((int) *q)) q++; /* skip spaces after cast */
278 * Less code means less bugs
280 #define return_P(_x) *error = _x;goto return_p
281 #define return_SLEN goto return_slen
284 /** Tokenize a conditional check
286 * @param[in] ctx for talloc
287 * @param[in] start the start of the string to process. Should be "(..."
288 * @param[in] brace look for a closing brace
289 * @param[out] pcond pointer to the returned condition structure
290 * @param[out] error the parse error (if any)
291 * @return length of the string skipped, or when negative, the offset to the offending error
293 static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace, fr_cond_t **pcond, char const **error)
296 const char *p = start;
299 FR_TOKEN op, lhs_type, rhs_type;
301 c = talloc_zero(ctx, fr_cond_t);
303 rad_assert(c != NULL);
306 while (isspace((int) *p)) p++; /* skip spaces before condition */
309 return_P("Empty condition is invalid");
318 while (isspace((int) *p)) p++; /* skip spaces after negation */
324 return_P("Double negation is invalid");
335 * We've already eaten one layer of
336 * brackets. Go recurse to get more.
338 c->type = COND_TYPE_CHILD;
339 slen = condition_tokenize(c, p, true, &c->data.child, error);
344 if (!c->data.child) {
345 return_P("Empty condition is invalid");
349 while (isspace((int) *p)) p++; /* skip spaces after (COND)*/
351 } else { /* it's a bare FOO==BAR */
353 * We didn't see anything special. The condition must be one of
363 return_P("Conditional check cannot begin with a regular expression");
366 slen = condition_tokenize_cast(p, &c->cast, error);
372 slen = condition_tokenize_word(c, p, &lhs, &lhs_type, error);
378 while (isspace((int)*p)) p++; /* skip spaces after LHS */
381 * We may (or not) have an operator
390 * don't skip the brace. We'll look for it later.
399 return_P("No closing brace at end of string");
407 } else if (((p[0] == '&') && (p[1] == '&')) ||
408 ((p[0] == '|') && (p[1] == '|'))) {
413 *error = "Cannot do cast for existence check";
417 c->type = COND_TYPE_EXISTS;
418 c->data.vpt = radius_str2tmpl(c, lhs, lhs_type);
420 return_P("Failed creating exists");
423 } else { /* it's an operator */
427 * The next thing should now be a comparison operator.
430 c->type = COND_TYPE_MAP;
433 return_P("Invalid text. Expected comparison operator");
440 } else if (p[1] == '~') {
446 } else if (p[1] == '*') {
447 if (lhs_type != T_BARE_WORD) {
448 return_P("Cannot use !* on a string");
455 goto invalid_operator;
464 } else if (p[1] == '~') {
470 } else if (p[1] == '*') {
471 if (lhs_type != T_BARE_WORD) {
472 return_P("Cannot use =* on a string");
480 return_P("Invalid operator");
508 while (isspace((int) *p)) p++; /* skip spaces after operator */
511 return_P("Expected text after operator");
515 * Cannot have a cast on the RHS
518 return_P("Unexpected cast");
524 slen = condition_tokenize_word(c, p, &rhs, &rhs_type, error);
530 * Sanity checks for regexes.
534 return_P("Expected regular expression");
540 if (p[slen] == 'i') {
545 } else if (!regex && (*p == '/')) {
546 return_P("Unexpected regular expression");
549 c->data.map = radius_str2map(c, lhs, lhs_type, op, rhs, rhs_type,
550 REQUEST_CURRENT, PAIR_LIST_REQUEST,
551 REQUEST_CURRENT, PAIR_LIST_REQUEST);
553 return_P("Failed creating check");
557 * foo =* bar is just (foo)
558 * foo !* bar is just (!foo)
560 if ((op == T_OP_CMP_TRUE) || (op == T_OP_CMP_FALSE)) {
561 value_pair_tmpl_t *vpt;
563 vpt = talloc_steal(c, c->data.map->dst);
564 c->data.map->dst = NULL;
566 talloc_free(c->data.map);
567 c->type = COND_TYPE_EXISTS;
571 * Invert the negation bit.
573 if (op == T_OP_CMP_FALSE) {
574 c->negate = !c->negate;
581 * @todo: check LHS and RHS separately, to
584 if ((c->data.map->src->type == VPT_TYPE_LIST) ||
585 (c->data.map->dst->type == VPT_TYPE_LIST)) {
587 *error = "Cannot use list references in condition";
592 * Check cast type. We can have the RHS
593 * a string if the LHS has a cast. But
594 * if the RHS is an attr, it MUST be the
595 * same type as the LHS.
598 if ((c->data.map->src->type == VPT_TYPE_ATTR) &&
599 (c->cast->type != c->data.map->src->da->type)) {
603 if (c->data.map->src->type == VPT_TYPE_REGEX) {
605 *error = "Cannot use cast with regex comparison";
610 * Casting to a redundant type means we don't need the cast.
612 if ((c->data.map->dst->type == VPT_TYPE_ATTR) &&
613 (c->cast->type == c->data.map->dst->da->type)) {
619 * Without a cast, we can't compare "foo" to User-Name,
620 * it has to be done the other way around.
622 if ((c->data.map->src->type == VPT_TYPE_ATTR) &&
623 (c->data.map->dst->type != VPT_TYPE_ATTR)) {
625 *error = "Cannot use attribute reference on right side of condition";
630 * Two attributes? They must be of the same type
632 if ((c->data.map->src->type == VPT_TYPE_ATTR) &&
633 (c->data.map->dst->type == VPT_TYPE_ATTR) &&
634 (c->data.map->dst->da->type != c->data.map->src->da->type)) {
637 *error = "Attribute comparisons must be of the same attribute type";
645 while (isspace((int) *p)) p++; /* skip spaces after RHS */
647 } /* parse a condition (COND) or FOO OP BAR*/
654 return_P("Unexpected closing brace");
658 while (isspace((int) *p)) p++; /* skip spaces after closing brace */
664 * End of string is now allowed.
668 return_P("No closing brace at end of string");
674 if (!(((p[0] == '&') && (p[1] == '&')) ||
675 ((p[0] == '|') && (p[1] == '|')))) {
676 *error = "Unexpected text after condition";
678 if (lhs) talloc_free(lhs);
679 if (rhs) talloc_free(rhs);
685 * Recurse to parse the next condition.
691 * May still be looking for a closing brace.
693 slen = condition_tokenize(c, p, brace, &c->next, error);
696 if (lhs) talloc_free(lhs);
697 if (rhs) talloc_free(rhs);
699 return slen - (p - start);
705 * Normalize it before returning it.
710 * (FOO) ... --> FOO ...
712 if ((c->type == COND_TYPE_CHILD) && !c->data.child->next) {
715 child = talloc_steal(ctx, c->data.child);
716 c->data.child = NULL;
718 child->next = talloc_steal(child, c->next);
721 child->next_op = c->next_op;
724 * Set the negation properly
726 if ((c->negate && !child->negate) ||
727 (!c->negate && child->negate)) {
728 child->negate = true;
730 child->negate = false;
738 * (FOO ...) --> FOO ...
740 * But don't do !(FOO || BAR) --> !FOO || BAR
741 * Because that's different.
743 if ((c->type == COND_TYPE_CHILD) &&
744 !c->next && !c->negate) {
747 child = talloc_steal(ctx, c->data.child);
748 c->data.child = NULL;
755 * Normalize negation. This doesn't really make any
756 * difference, but it simplifies the run-time code in
759 if (c->type == COND_TYPE_MAP) {
761 * !FOO !~ BAR --> FOO =~ BAR
763 if (c->negate && (c->data.map->op == T_OP_REG_NE)) {
765 c->data.map->op = T_OP_REG_EQ;
769 * FOO !~ BAR --> !FOO =~ BAR
771 if (!c->negate && (c->data.map->op == T_OP_REG_NE)) {
773 c->data.map->op = T_OP_REG_EQ;
777 * !FOO != BAR --> FOO == BAR
779 if (c->negate && (c->data.map->op == T_OP_NE)) {
781 c->data.map->op = T_OP_CMP_EQ;
785 * This next one catches "LDAP-Group != foo",
786 * which doesn't really work, but this hack fixes it.
788 * FOO != BAR --> !FOO == BAR
790 if (!c->negate && (c->data.map->op == T_OP_NE)) {
792 c->data.map->op = T_OP_CMP_EQ;
796 if (lhs) talloc_free(lhs);
797 if (rhs) talloc_free(rhs);
803 /** Tokenize a conditional check
805 * @param[in] ctx for talloc
806 * @param[in] start the start of the string to process. Should be "(..."
807 * @param[out] head the parsed condition structure
808 * @param[out] error the parse error (if any)
809 * @return length of the string skipped, or when negative, the offset to the offending error
811 ssize_t fr_condition_tokenize(TALLOC_CTX *ctx, char const *start, fr_cond_t **head, char const **error)
813 return condition_tokenize(ctx, start, false, head, error);