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 DIAG_OFF(unused-function)
79 static void cond_debug(const cond_t *c)
83 if (c->child_op == COND_NOT) {
87 if (c->op != T_OP_INVALID) {
88 rad_assert(c->lhs != NULL);
91 if (c->op != T_OP_CMP_TRUE) {
92 printf(" %s ", fr_token_name(c->op));
94 rad_assert(c->rhs != NULL);
99 rad_assert(c->child != NULL);
101 rad_assert(c->child_op != COND_AND);
102 rad_assert(c->child_op != COND_OR);
103 rad_assert(c->child != NULL);
106 cond_debug(c->child);
110 if (c->next_op == COND_NONE) {
111 rad_assert(c->next == NULL);
115 rad_assert(c->next_op != COND_TRUE);
116 rad_assert(c->next_op != COND_NOT);
118 if (c->next_op == COND_AND) {
121 } else if (c->next_op == COND_OR) {
132 DIAG_ON(unused-function)
135 static ssize_t condition_tokenize_string(TALLOC_CTX *ctx, const char *start, char **out, const char **error)
137 const char *p = start;
141 COND_DEBUG("STRING %s", start);
144 size_t len = (p + 1) - start;
146 COND_DEBUG("end of string %s", p);
147 *out = talloc_array(ctx, char, len + 1);
149 memcpy(*out, start, len);
157 *error = "End of string after escape";
158 COND_DEBUG("RETURN %d", __LINE__);
163 p++; /* allow anything else */
166 *error = "Unterminated string";
170 static ssize_t condition_tokenize_word(TALLOC_CTX *ctx, const char *start, char **out, const char **error)
173 const char *p = start;
175 if ((*p == '"') || (*p == '\'') || (*p == '`') || (*p == '/')) {
176 return condition_tokenize_string(ctx, start, out, error);
181 * The LHS should really be limited to only a few
182 * things. For now, we allow pretty much anything.
185 *error = "Unexpected escape";
186 COND_DEBUG("RETURN %d", __LINE__);
198 * Spaces or special characters delineate the word
200 if (isspace((int) *p) || (*p == '&') || (*p == '|') ||
201 (*p == '!') || (*p == '=') || (*p == '<') || (*p == '>')) {
205 if ((*p == '"') || (*p == '\'') || (*p == '`')) {
206 COND_DEBUG("RETURN %d", __LINE__);
207 *error = "Unexpected start of string";
216 *error = "Empty string is invalid";
220 *out = talloc_array(ctx, char, len + 1);
221 memcpy(*out, start, len);
223 COND_DEBUG("PARSED WORD %s", *out);
227 /** Tokenize a conditional check
229 * @param[in] ctx for talloc
230 * @param[in] start the start of the string to process. Should be "(..."
231 * @param[in] brace look for a closing brace
232 * @param[out] pcond pointer to the returned condition structure
233 * @param[out] error the parse error (if any)
234 * @return length of the string skipped, or when negative, the offset to the offending error
236 static ssize_t condition_tokenize(TALLOC_CTX *ctx, const char *start, int brace, cond_t **pcond, const char **error)
239 const char *p = start;
242 COND_DEBUG("START %s", p);
244 c = talloc_zero(ctx, cond_t);
246 rad_assert(c != NULL);
248 while (isspace((int) *p)) p++; /* skip spaces before condition */
252 COND_DEBUG("RETURN %d", __LINE__);
253 *error = "Empty condition is invalid";
262 c->child_op = COND_NOT;
263 while (isspace((int) *p)) p++; /* skip spaces after negation */
272 if (c->child_op == COND_NONE) c->child_op = COND_TRUE;
275 * We've already eaten one layer of
276 * brackets. Go recurse to get more.
278 slen = condition_tokenize(c, p, TRUE, &c->child, error);
281 COND_DEBUG("RETURN %d", __LINE__);
282 return slen - (p - start);
287 *error = "Empty condition is invalid";
288 COND_DEBUG("RETURN %d", __LINE__);
293 while (isspace((int) *p)) p++; /* skip spaces after (COND)*/
295 } else { /* it's a bare FOO==BAR */
297 * We didn't see anything special. The condition must be one of
306 COND_DEBUG("LHS %s", p);
307 slen = condition_tokenize_word(c, p, &c->lhs, error);
310 COND_DEBUG("RETURN %d", __LINE__);
311 return slen - (p - start);
315 while (isspace((int)*p)) p++; /* skip spaces after LHS */
318 * We may (or not) have an operator
327 * don't skip the brace. We'll look for it later.
329 c->op = T_OP_CMP_TRUE;
337 *error = "No closing brace at end of string";
338 COND_DEBUG("RETURN %d", __LINE__);
342 c->op = T_OP_CMP_TRUE;
347 } else if (((p[0] == '&') && (p[1] == '&')) ||
348 ((p[0] == '|') && (p[1] == '|'))) {
350 c->op = T_OP_CMP_TRUE;
352 } else { /* it's an operator */
355 COND_DEBUG("OPERATOR %s", p);
358 * The next thing should now be a comparison operator.
364 *error = "Invalid text. Expected comparison operator";
365 COND_DEBUG("RETURN %d", __LINE__);
373 } else if (p[1] == '~') {
379 } else if (p[1] == '*') {
380 c->op = T_OP_CMP_FALSE;
386 *error = "Invalid operator";
387 COND_DEBUG("RETURN %d", __LINE__);
397 } else if (p[1] == '~') {
403 } else if (p[1] == '*') {
404 c->op = T_OP_CMP_TRUE;
408 goto invalid_operator;
436 while (isspace((int) *p)) p++; /* skip spaces after operator */
440 *error = "Expected text after operator";
441 COND_DEBUG("RETURN %d", __LINE__);
445 COND_DEBUG("RHS %s", p);
450 slen = condition_tokenize_word(c, p, &c->rhs, error);
453 COND_DEBUG("RETURN %d", __LINE__);
454 return slen - (p - start);
458 * Sanity checks for regexes.
463 *error = "Expected regular expression";
464 COND_DEBUG("RETURN %d", __LINE__);
471 if (p[slen] == 'i') {
476 COND_DEBUG("DONE REGEX %s", p + slen);
478 } else if (!regex && (*p == '/')) {
480 *error = "Unexpected regular expression";
481 COND_DEBUG("RETURN %d", __LINE__);
487 while (isspace((int) *p)) p++; /* skip spaces after RHS */
489 } /* parse a condition (COND) or FOO OP BAR*/
497 *error = "Unexpected closing brace";
498 COND_DEBUG("RETURN %d", __LINE__);
503 while (isspace((int) *p)) p++; /* skip spaces after closing brace */
509 * End of string is now allowed.
514 *error = "No closing brace at end of string";
515 COND_DEBUG("RETURN %d", __LINE__);
522 if (!(((p[0] == '&') && (p[1] == '&')) ||
523 ((p[0] == '|') && (p[1] == '|')))) {
525 *error = "Unexpected text after condition";
530 * Recurse to parse the next condition.
532 COND_DEBUG("GOT %c%c", p[0], p[1]);
537 * May still be looking for a closing brace.
539 COND_DEBUG("RECURSE AND/OR");
540 slen = condition_tokenize(c, p, brace, &c->next, error);
543 COND_DEBUG("RETURN %d", __LINE__);
544 return slen - (p - start);
550 COND_DEBUG("RETURN %d", __LINE__);
554 /** Tokenize a conditional check
556 * @param[in] start the start of the string to process. Should be "(..."
557 * @param[out] error the parse error (if any)
558 * @return length of the string skipped, or when negative, the offset to the offending error
560 ssize_t fr_condition_tokenize(const char *start, const char **error)
565 slen = condition_tokenize(NULL, start, FALSE, &c, error);
566 if (slen <= 0) return slen;
569 COND_DEBUG("RETURN %d", __LINE__);
570 *error = "Empty condition is invalid";