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 static size_t cond_sprint_string(char *buffer, size_t bufsize, char const *str, FR_TOKEN type)
51 char *end = q + bufsize - 3;
58 strlcpy(buffer, str, bufsize);
59 return strlen(buffer);
65 case T_DOUBLE_QUOTED_STRING:
69 case T_SINGLE_QUOTED_STRING:
73 case T_BACK_QUOTED_STRING:
80 while (*p && (q < end)) {
123 size_t fr_cond_sprint(char *buffer, size_t bufsize, fr_cond_t const *c)
127 char *end = buffer + bufsize - 1;
130 if (c->child_op == COND_NOT) {
134 if (c->op != T_OP_INVALID) {
135 rad_assert(c->lhs != NULL);
137 len = cond_sprint_string(p, end - p, c->lhs, c->lhs_type);
140 if (c->op != T_OP_CMP_TRUE) {
142 strlcpy(p, fr_token_name(c->op), end - p);
146 rad_assert(c->rhs != NULL);
148 len = cond_sprint_string(p, end - p, c->rhs, c->rhs_type);
153 rad_assert(c->child != NULL);
155 rad_assert(c->child_op != COND_AND);
156 rad_assert(c->child_op != COND_OR);
157 rad_assert(c->child != NULL);
160 len = fr_cond_sprint(p, end - p, c->child);
165 if (c->next_op == COND_NONE) {
166 rad_assert(c->next == NULL);
171 rad_assert(c->next_op != COND_TRUE);
172 rad_assert(c->next_op != COND_NOT);
174 if (c->next_op == COND_AND) {
175 strlcpy(p, " && ", end - p);
178 } else if (c->next_op == COND_OR) {
179 strlcpy(p, " || ", end - p);
190 DIAG_ON(unused-function)
193 static ssize_t condition_tokenize_string(TALLOC_CTX *ctx, char const *start, char **out,
194 FR_TOKEN *op, char const **error)
196 const char *p = start;
204 *op = T_DOUBLE_QUOTED_STRING;
208 *op = T_SINGLE_QUOTED_STRING;
212 *op = T_BACK_QUOTED_STRING;
216 *op = T_OP_REG_EQ; /* a bit of a hack. */
221 *out = talloc_array(ctx, char, strlen(start) - 1); /* + 2 - 1 */
222 if (!*out) return -1;
226 COND_DEBUG("STRING %s", start);
232 COND_DEBUG("end of string %s", p);
240 *error = "End of string after escape";
241 COND_DEBUG("RETURN %d", __LINE__);
266 *error = "Unterminated string";
270 static ssize_t condition_tokenize_word(TALLOC_CTX *ctx, char const *start, char **out,
271 FR_TOKEN *op, char const **error)
274 char const *p = start;
276 if ((*p == '"') || (*p == '\'') || (*p == '`') || (*p == '/')) {
277 return condition_tokenize_string(ctx, start, out, op, error);
284 * The LHS should really be limited to only a few
285 * things. For now, we allow pretty much anything.
288 *error = "Unexpected escape";
289 COND_DEBUG("RETURN %d", __LINE__);
301 * Spaces or special characters delineate the word
303 if (isspace((int) *p) || (*p == '&') || (*p == '|') ||
304 (*p == '!') || (*p == '=') || (*p == '<') || (*p == '>')) {
308 if ((*p == '"') || (*p == '\'') || (*p == '`')) {
309 COND_DEBUG("RETURN %d", __LINE__);
310 *error = "Unexpected start of string";
319 *error = "Empty string is invalid";
323 *out = talloc_array(ctx, char, len + 1);
324 memcpy(*out, start, len);
326 COND_DEBUG("PARSED WORD %s", *out);
330 /** Tokenize a conditional check
332 * @param[in] ctx for talloc
333 * @param[in] start the start of the string to process. Should be "(..."
334 * @param[in] brace look for a closing brace
335 * @param[out] pcond pointer to the returned condition structure
336 * @param[out] error the parse error (if any)
337 * @return length of the string skipped, or when negative, the offset to the offending error
339 static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace, fr_cond_t **pcond, char const **error)
342 const char *p = start;
345 COND_DEBUG("START %s", p);
347 c = talloc_zero(ctx, fr_cond_t);
349 rad_assert(c != NULL);
351 while (isspace((int) *p)) p++; /* skip spaces before condition */
355 COND_DEBUG("RETURN %d", __LINE__);
356 *error = "Empty condition is invalid";
365 c->child_op = COND_NOT;
366 while (isspace((int) *p)) p++; /* skip spaces after negation */
375 if (c->child_op == COND_NONE) c->child_op = COND_TRUE;
378 * We've already eaten one layer of
379 * brackets. Go recurse to get more.
381 slen = condition_tokenize(c, p, TRUE, &c->child, error);
384 COND_DEBUG("RETURN %d", __LINE__);
385 return slen - (p - start);
390 *error = "Empty condition is invalid";
391 COND_DEBUG("RETURN %d", __LINE__);
396 while (isspace((int) *p)) p++; /* skip spaces after (COND)*/
398 } else { /* it's a bare FOO==BAR */
400 * We didn't see anything special. The condition must be one of
409 COND_DEBUG("LHS %s", p);
410 slen = condition_tokenize_word(c, p, &c->lhs, &c->lhs_type, error);
413 COND_DEBUG("RETURN %d", __LINE__);
414 return slen - (p - start);
418 while (isspace((int)*p)) p++; /* skip spaces after LHS */
421 * We may (or not) have an operator
430 * don't skip the brace. We'll look for it later.
432 c->op = T_OP_CMP_TRUE;
440 *error = "No closing brace at end of string";
441 COND_DEBUG("RETURN %d", __LINE__);
445 c->op = T_OP_CMP_TRUE;
450 } else if (((p[0] == '&') && (p[1] == '&')) ||
451 ((p[0] == '|') && (p[1] == '|'))) {
453 c->op = T_OP_CMP_TRUE;
455 } else { /* it's an operator */
458 COND_DEBUG("OPERATOR %s", p);
461 * The next thing should now be a comparison operator.
467 *error = "Invalid text. Expected comparison operator";
468 COND_DEBUG("RETURN %d", __LINE__);
476 } else if (p[1] == '~') {
482 } else if (p[1] == '*') {
489 * really re-write it...
491 c->op = T_OP_CMP_FALSE;
497 *error = "Invalid operator";
498 COND_DEBUG("RETURN %d", __LINE__);
508 } else if (p[1] == '~') {
514 } else if (p[1] == '*') {
515 c->op = T_OP_CMP_TRUE;
519 goto invalid_operator;
547 while (isspace((int) *p)) p++; /* skip spaces after operator */
551 *error = "Expected text after operator";
552 COND_DEBUG("RETURN %d", __LINE__);
556 COND_DEBUG("RHS %s", p);
561 slen = condition_tokenize_word(c, p, &c->rhs, &c->rhs_type, error);
564 COND_DEBUG("RETURN %d", __LINE__);
565 return slen - (p - start);
569 * Sanity checks for regexes.
574 *error = "Expected regular expression";
575 COND_DEBUG("RETURN %d", __LINE__);
582 if (p[slen] == 'i') {
587 COND_DEBUG("DONE REGEX %s", p + slen);
589 } else if (!regex && (*p == '/')) {
591 *error = "Unexpected regular expression";
592 COND_DEBUG("RETURN %d", __LINE__);
598 while (isspace((int) *p)) p++; /* skip spaces after RHS */
600 } /* parse a condition (COND) or FOO OP BAR*/
608 *error = "Unexpected closing brace";
609 COND_DEBUG("RETURN %d", __LINE__);
614 while (isspace((int) *p)) p++; /* skip spaces after closing brace */
620 * End of string is now allowed.
625 *error = "No closing brace at end of string";
626 COND_DEBUG("RETURN %d", __LINE__);
633 if (!(((p[0] == '&') && (p[1] == '&')) ||
634 ((p[0] == '|') && (p[1] == '|')))) {
636 *error = "Unexpected text after condition";
641 * Recurse to parse the next condition.
643 COND_DEBUG("GOT %c%c", p[0], p[1]);
648 * May still be looking for a closing brace.
650 COND_DEBUG("RECURSE AND/OR");
651 slen = condition_tokenize(c, p, brace, &c->next, error);
654 COND_DEBUG("RETURN %d", __LINE__);
655 return slen - (p - start);
660 COND_DEBUG("RETURN %d", __LINE__);
668 * FIXME: do more normalization.
671 * ((COND1) || (COND2)) --> COND1 || COND2
674 if (c->child && !c->next &&
675 (c->child->child_op == c->child_op)) {
676 fr_cond_t *child = c->child;
678 (void) talloc_steal(ctx, child);
683 c->child_op = COND_TRUE;
690 /** Tokenize a conditional check
692 * @param[in] ctx for talloc
693 * @param[in] start the start of the string to process. Should be "(..."
694 * @param[out] head the parsed condition structure
695 * @param[out] error the parse error (if any)
696 * @return length of the string skipped, or when negative, the offset to the offending error
698 ssize_t fr_condition_tokenize(TALLOC_CTX *ctx, char const *start, fr_cond_t **head, char const **error)
700 return condition_tokenize(ctx, start, FALSE, head, error);