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);
49 len = radius_tmpl2str(p, end - p, c->data.vpt);
54 rad_assert(c->data.map != NULL);
56 *(p++) = '['; /* for extra-clear debugging */
58 len = radius_map2str(p, end - p, c->data.map);
66 rad_assert(c->data.child != NULL);
68 len = fr_cond_sprint(p, end - p, c->data.child);
78 if (c->next_op == COND_NONE) {
79 rad_assert(c->next == NULL);
84 if (c->next_op == COND_AND) {
85 strlcpy(p, " && ", end - p);
88 } else if (c->next_op == COND_OR) {
89 strlcpy(p, " || ", end - p);
101 static ssize_t condition_tokenize_string(TALLOC_CTX *ctx, char const *start, char **out,
102 FR_TOKEN *op, char const **error)
104 const char *p = start;
112 *op = T_DOUBLE_QUOTED_STRING;
116 *op = T_SINGLE_QUOTED_STRING;
120 *op = T_BACK_QUOTED_STRING;
124 *op = T_OP_REG_EQ; /* a bit of a hack. */
129 *out = talloc_array(ctx, char, strlen(start) - 1); /* + 2 - 1 */
130 if (!*out) return -1;
144 *error = "End of string after escape";
169 *error = "Unterminated string";
173 static ssize_t condition_tokenize_word(TALLOC_CTX *ctx, char const *start, char **out,
174 FR_TOKEN *op, char const **error)
177 char const *p = start;
179 if ((*p == '"') || (*p == '\'') || (*p == '`') || (*p == '/')) {
180 return condition_tokenize_string(ctx, start, out, op, error);
184 if (*p == '&') p++; /* special-case &User-Name */
188 * The LHS should really be limited to only a few
189 * things. For now, we allow pretty much anything.
192 *error = "Unexpected escape";
204 * Spaces or special characters delineate the word
206 if (isspace((int) *p) || (*p == '&') || (*p == '|') ||
207 (*p == '!') || (*p == '=') || (*p == '<') || (*p == '>')) {
211 if ((*p == '"') || (*p == '\'') || (*p == '`')) {
212 *error = "Unexpected start of string";
221 *error = "Empty string is invalid";
225 *out = talloc_array(ctx, char, len + 1);
226 memcpy(*out, start, len);
232 * Less code means less bugs
234 #define return_P(_x) talloc_free(c);*error = _x;return -(p - start)
235 #define return_SLEN talloc_free(c);return slen -(p - start)
238 /** Tokenize a conditional check
240 * @param[in] ctx for talloc
241 * @param[in] start the start of the string to process. Should be "(..."
242 * @param[in] brace look for a closing brace
243 * @param[out] pcond pointer to the returned condition structure
244 * @param[out] error the parse error (if any)
245 * @return length of the string skipped, or when negative, the offset to the offending error
247 static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace, fr_cond_t **pcond, char const **error)
250 const char *p = start;
253 FR_TOKEN op, lhs_type, rhs_type;
255 c = talloc_zero(ctx, fr_cond_t);
257 rad_assert(c != NULL);
259 while (isspace((int) *p)) p++; /* skip spaces before condition */
262 return_P("Empty condition is invalid");
271 while (isspace((int) *p)) p++; /* skip spaces after negation */
277 return_P("Double negation is invalid");
288 * We've already eaten one layer of
289 * brackets. Go recurse to get more.
291 c->type = COND_TYPE_CHILD;
292 slen = condition_tokenize(c, p, true, &c->data.child, error);
297 if (!c->data.child) {
298 return_P("Empty condition is invalid");
302 while (isspace((int) *p)) p++; /* skip spaces after (COND)*/
304 } else { /* it's a bare FOO==BAR */
306 * We didn't see anything special. The condition must be one of
316 return_P("Conditional check cannot begin with a regular expression");
319 slen = condition_tokenize_word(c, p, &lhs, &lhs_type, error);
325 while (isspace((int)*p)) p++; /* skip spaces after LHS */
328 * We may (or not) have an operator
337 * don't skip the brace. We'll look for it later.
346 return_P("No closing brace at end of string");
354 } else if (((p[0] == '&') && (p[1] == '&')) ||
355 ((p[0] == '|') && (p[1] == '|'))) {
358 c->type = COND_TYPE_EXISTS;
359 c->data.vpt = radius_str2tmpl(c, lhs, lhs_type);
361 return_P("Failed creating exists");
364 } else { /* it's an operator */
368 * The next thing should now be a comparison operator.
371 c->type = COND_TYPE_MAP;
374 return_P("Invalid text. Expected comparison operator");
381 } else if (p[1] == '~') {
387 } else if (p[1] == '*') {
394 * really re-write it...
400 goto invalid_operator;
409 } else if (p[1] == '~') {
415 } else if (p[1] == '*') {
421 return_P("Invalid operator");
449 while (isspace((int) *p)) p++; /* skip spaces after operator */
452 return_P("Expected text after operator");
458 slen = condition_tokenize_word(c, p, &rhs, &rhs_type, error);
464 * Sanity checks for regexes.
468 return_P("Expected regular expression");
474 if (p[slen] == 'i') {
479 } else if (!regex && (*p == '/')) {
480 return_P("Unexpected regular expression");
483 c->data.map = radius_str2map(c, lhs, lhs_type, op, rhs, rhs_type,
484 REQUEST_CURRENT, PAIR_LIST_REQUEST,
485 REQUEST_CURRENT, PAIR_LIST_REQUEST);
487 return_P("Failed creating check");
491 * @todo: check LHS and RHS separately, to
494 if ((c->data.map->src->type == VPT_TYPE_LIST) ||
495 (c->data.map->dst->type == VPT_TYPE_LIST)) {
497 *error = "Cannot use list references in condition";
501 if ((c->data.map->src->type == VPT_TYPE_ATTR) &&
502 (c->data.map->dst->type != VPT_TYPE_ATTR)) {
504 *error = "Cannot use attribute reference on right side of condition";
508 if ((c->data.map->src->type == VPT_TYPE_ATTR) &&
509 (c->data.map->dst->type == VPT_TYPE_ATTR) &&
510 (c->data.map->dst->da->type != c->data.map->src->da->type)) {
512 *error = "Attribute comparisons must be of the same attribute type";
518 while (isspace((int) *p)) p++; /* skip spaces after RHS */
520 } /* parse a condition (COND) or FOO OP BAR*/
527 return_P("Unexpected closing brace");
531 while (isspace((int) *p)) p++; /* skip spaces after closing brace */
537 * End of string is now allowed.
541 return_P("No closing brace at end of string");
547 if (!(((p[0] == '&') && (p[1] == '&')) ||
548 ((p[0] == '|') && (p[1] == '|')))) {
549 return_P("Unexpected text after condition");
553 * Recurse to parse the next condition.
559 * May still be looking for a closing brace.
561 slen = condition_tokenize(c, p, brace, &c->next, error);
569 * Normalize it before returning it.
574 * (FOO) ... --> FOO ...
576 if ((c->type == COND_TYPE_CHILD) && !c->data.child->next) {
579 child = c->data.child;
580 child->next = c->next;
581 child->next_op = c->next_op;
583 c->data.child = NULL;
586 * Set the negation properly
588 if ((c->negate && !child->negate) ||
589 (!c->negate && child->negate)) {
590 child->negate = true;
592 child->negate = false;
595 (void) talloc_steal(ctx, child);
601 * (FOO ...) --> FOO ...
603 * But don't do !(FOO || BAR) --> !FOO || BAR
604 * Because that's different.
606 if ((c->type == COND_TYPE_CHILD) &&
607 !c->next && !c->negate) {
610 child = c->data.child;
611 c->data.child = NULL;
612 (void) talloc_steal(ctx, child);
618 * Normalize negation. This doesn't really make any
619 * difference, but it simplifies the run-time code in
622 if (c->type == COND_TYPE_MAP) {
624 * !FOO !~ BAR --> FOO =~ BAR
626 if (c->negate && (c->data.map->op == T_OP_REG_NE)) {
628 c->data.map->op = T_OP_REG_EQ;
632 * FOO !~ BAR --> !FOO =~ BAR
634 if (!c->negate && (c->data.map->op == T_OP_REG_NE)) {
636 c->data.map->op = T_OP_REG_EQ;
640 * !FOO != BAR --> FOO == BAR
642 if (c->negate && (c->data.map->op == T_OP_NE)) {
644 c->data.map->op = T_OP_CMP_EQ;
648 * This next one catches "LDAP-Group != foo",
649 * which doesn't really work, but this hack fixes it.
651 * FOO != BAR --> !FOO == BAR
653 if (!c->negate && (c->data.map->op == T_OP_NE)) {
655 c->data.map->op = T_OP_CMP_EQ;
663 /** Tokenize a conditional check
665 * @param[in] ctx for talloc
666 * @param[in] start the start of the string to process. Should be "(..."
667 * @param[out] head the parsed condition structure
668 * @param[out] error the parse error (if any)
669 * @return length of the string skipped, or when negative, the offset to the offending error
671 ssize_t fr_condition_tokenize(TALLOC_CTX *ctx, char const *start, fr_cond_t **head, char const **error)
673 return condition_tokenize(ctx, start, false, head, error);