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 #define COND_DEBUG(fmt, ...) printf(fmt, ## __VA_ARGS__);printf("\n")
36 * This file shouldn't use any functions from the server core.
40 #define COND_DEBUG DEBUG
42 #define COND_DEBUG(...)
46 typedef enum cond_op_t {
55 typedef struct cond_t cond_t;
58 * Allow for the following structures:
60 * FOO no OP, RHS is NULL
62 * (COND) no LHS/RHS, child is COND, child OP is TRUE
63 * (!(COND)) no LHS/RHS, child is COND, child OP is NOT
64 * (COND1 OP COND2) no LHS/RHS, next is COND2, next OP is OP
78 static void cond_debug(const cond_t *c)
82 if (c->child_op == COND_NOT) {
86 if (c->op != T_OP_INVALID) {
87 rad_assert(c->lhs != NULL);
90 if (c->op != T_OP_CMP_TRUE) {
91 printf(" %s ", fr_token_name(c->op));
93 rad_assert(c->rhs != NULL);
98 rad_assert(c->child != NULL);
100 rad_assert(c->child_op != COND_AND);
101 rad_assert(c->child_op != COND_OR);
102 rad_assert(c->child != NULL);
105 cond_debug(c->child);
109 if (c->next_op == COND_NONE) {
110 rad_assert(c->next == NULL);
114 rad_assert(c->next_op != COND_TRUE);
115 rad_assert(c->next_op != COND_NOT);
117 if (c->next_op == COND_AND) {
120 } else if (c->next_op == COND_OR) {
132 static ssize_t condition_tokenize_string(TALLOC_CTX *ctx, const char *start, char **out, const char **error)
134 const char *p = start;
138 COND_DEBUG("STRING %s", start);
141 size_t len = (p + 1) - start;
143 COND_DEBUG("end of string %s", p);
144 *out = talloc_array(ctx, char, len + 1);
146 memcpy(*out, start, len);
154 *error = "End of string after escape";
155 COND_DEBUG("RETURN %d", __LINE__);
160 p++; /* allow anything else */
163 *error = "Unterminated string";
167 static ssize_t condition_tokenize_word(TALLOC_CTX *ctx, const char *start, char **out, const char **error)
170 const char *p = start;
172 if ((*p == '"') || (*p == '\'') || (*p == '`') || (*p == '/')) {
173 return condition_tokenize_string(ctx, start, out, error);
178 * The LHS should really be limited to only a few
179 * things. For now, we allow pretty much anything.
182 *error = "Unexpected escape";
183 COND_DEBUG("RETURN %d", __LINE__);
195 * Spaces or special characters delineate the word
197 if (isspace((int) *p) || (*p == '&') || (*p == '|') ||
198 (*p == '!') || (*p == '=') || (*p == '<') || (*p == '>')) {
202 if ((*p == '"') || (*p == '\'') || (*p == '`')) {
203 COND_DEBUG("RETURN %d", __LINE__);
204 *error = "Unexpected start of string";
213 *error = "Empty string is invalid";
217 *out = talloc_array(ctx, char, len + 1);
218 memcpy(*out, start, len);
220 COND_DEBUG("PARSED WORD %s", *out);
224 /** Tokenize a conditional check
226 * @param[in] start the start of the string to process. Should be "(..."
227 * @param[in] brace look for a closing brace
228 * @param[out] child whether or not a child expression was parsed
229 * @param[out] error the parse error (if any)
230 * @return length of the string skipped, or when negative, the offset to the offending error
232 static ssize_t condition_tokenize(TALLOC_CTX *ctx, const char *start, int brace, cond_t **pcond, const char **error)
236 const char *p = start;
241 COND_DEBUG("START %s", p);
243 c = talloc_zero(ctx, cond_t);
245 rad_assert(c != NULL);
247 while (isspace((int) *p)) p++; /* skip spaces before condition */
251 COND_DEBUG("RETURN %d", __LINE__);
252 *error = "Empty condition is invalid";
261 c->child_op = COND_NOT;
262 while (isspace((int) *p)) p++; /* skip spaces after negation */
271 if (c->child_op == COND_NONE) c->child_op = COND_TRUE;
274 * We've already eaten one layer of
275 * brackets. Go recurse to get more.
277 slen = condition_tokenize(c, p, TRUE, &c->child, error);
280 COND_DEBUG("RETURN %d", __LINE__);
281 return slen - (p - start);
286 *error = "Empty condition is invalid";
287 COND_DEBUG("RETURN %d", __LINE__);
292 while (isspace((int) *p)) p++; /* skip spaces after (COND)*/
294 } else { /* it's a bare FOO==BAR */
296 * We didn't see anything special. The condition must be one of
305 COND_DEBUG("LHS %s", p);
306 slen = condition_tokenize_word(c, p, &c->lhs, error);
309 COND_DEBUG("RETURN %d", __LINE__);
310 return slen - (p - start);
314 while (isspace((int)*p)) p++; /* skip spaces after LHS */
317 * We may (or not) have an operator
326 * don't skip the brace. We'll look for it later.
328 c->op = T_OP_CMP_TRUE;
336 *error = "No closing brace at end of string";
337 COND_DEBUG("RETURN %d", __LINE__);
341 c->op = T_OP_CMP_TRUE;
346 } else if (((p[0] == '&') && (p[1] == '&')) ||
347 ((p[0] == '|') && (p[1] == '|'))) {
349 c->op = T_OP_CMP_TRUE;
351 } else { /* it's an operator */
354 COND_DEBUG("OPERATOR %s", p);
357 * The next thing should now be a comparison operator.
363 *error = "Invalid text. Expected comparison operator";
364 COND_DEBUG("RETURN %d", __LINE__);
372 } else if (p[1] == '~') {
378 } else if (p[1] == '*') {
379 c->op = T_OP_CMP_FALSE;
385 *error = "Invalid operator";
386 COND_DEBUG("RETURN %d", __LINE__);
396 } else if (p[1] == '~') {
402 } else if (p[1] == '*') {
403 c->op = T_OP_CMP_TRUE;
407 goto invalid_operator;
435 while (isspace((int) *p)) p++; /* skip spaces after operator */
439 *error = "Expected text after operator";
440 COND_DEBUG("RETURN %d", __LINE__);
444 COND_DEBUG("RHS %s", p);
449 slen = condition_tokenize_word(c, p, &c->rhs, error);
452 COND_DEBUG("RETURN %d", __LINE__);
453 return slen - (p - start);
457 * Sanity checks for regexes.
462 *error = "Expected regular expression";
463 COND_DEBUG("RETURN %d", __LINE__);
470 if (p[slen] == 'i') {
475 COND_DEBUG("DONE REGEX %s", p + slen);
477 } else if (!regex && (*p == '/')) {
479 *error = "Unexpected regular expression";
480 COND_DEBUG("RETURN %d", __LINE__);
486 while (isspace((int) *p)) p++; /* skip spaces after RHS */
488 } /* parse a condition (COND) or FOO OP BAR*/
496 *error = "Unexpected closing brace";
497 COND_DEBUG("RETURN %d", __LINE__);
502 while (isspace((int) *p)) p++; /* skip spaces after closing brace */
508 * End of string is now allowed.
513 *error = "No closing brace at end of string";
514 COND_DEBUG("RETURN %d", __LINE__);
521 if (!(((p[0] == '&') && (p[1] == '&')) ||
522 ((p[0] == '|') && (p[1] == '|')))) {
524 *error = "Unexpected text after condition";
529 * Recurse to parse the next condition.
531 COND_DEBUG("GOT %c%c", p[0], p[1]);
536 * May still be looking for a closing brace.
538 COND_DEBUG("RECURSE AND/OR");
539 slen = condition_tokenize(c, p, brace, &c->next, error);
542 COND_DEBUG("RETURN %d", __LINE__);
543 return slen - (p - start);
549 COND_DEBUG("RETURN %d", __LINE__);
553 /** Tokenize a conditional check
555 * @param[in] start the start of the string to process. Should be "(..."
556 * @param[out] error the parse error (if any)
557 * @return length of the string skipped, or when negative, the offset to the offending error
559 ssize_t fr_condition_tokenize(const char *start, const char **error)
564 slen = condition_tokenize(NULL, start, FALSE, &c, error);
565 if (slen <= 0) return slen;
568 COND_DEBUG("RETURN %d", __LINE__);
569 *error = "Empty condition is invalid";