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 size_t fr_cond_sprint(char *buffer, size_t bufsize, fr_cond_t const *c)
50 char *end = buffer + bufsize - 1;
54 *(p++) = '!'; /* FIXME: only allow for child? */
58 case COND_TYPE_EXISTS:
59 rad_assert(c->data.vpt != NULL);
60 len = radius_tmpl2str(p, end - p, c->data.vpt);
65 rad_assert(c->data.map != NULL);
67 *(p++) = '['; /* for extra-clear debugging */
69 len = radius_map2str(p, end - p, c->data.map);
77 rad_assert(c->data.child != NULL);
79 len = fr_cond_sprint(p, end - p, c->data.child);
89 if (c->next_op == COND_NONE) {
90 rad_assert(c->next == NULL);
95 if (c->next_op == COND_AND) {
96 strlcpy(p, " && ", end - p);
99 } else if (c->next_op == COND_OR) {
100 strlcpy(p, " || ", end - p);
112 static ssize_t condition_tokenize_string(TALLOC_CTX *ctx, char const *start, char **out,
113 FR_TOKEN *op, char const **error)
115 const char *p = start;
123 *op = T_DOUBLE_QUOTED_STRING;
127 *op = T_SINGLE_QUOTED_STRING;
131 *op = T_BACK_QUOTED_STRING;
135 *op = T_OP_REG_EQ; /* a bit of a hack. */
140 *out = talloc_array(ctx, char, strlen(start) - 1); /* + 2 - 1 */
141 if (!*out) return -1;
145 COND_DEBUG("STRING %s", start);
151 COND_DEBUG("end of string %s", p);
159 *error = "End of string after escape";
160 COND_DEBUG("RETURN %d", __LINE__);
185 *error = "Unterminated string";
189 static ssize_t condition_tokenize_word(TALLOC_CTX *ctx, char const *start, char **out,
190 FR_TOKEN *op, char const **error)
193 char const *p = start;
195 if ((*p == '"') || (*p == '\'') || (*p == '`') || (*p == '/')) {
196 return condition_tokenize_string(ctx, start, out, op, error);
200 if (*p == '&') p++; /* special-case &User-Name */
204 * The LHS should really be limited to only a few
205 * things. For now, we allow pretty much anything.
208 *error = "Unexpected escape";
209 COND_DEBUG("RETURN %d", __LINE__);
221 * Spaces or special characters delineate the word
223 if (isspace((int) *p) || (*p == '&') || (*p == '|') ||
224 (*p == '!') || (*p == '=') || (*p == '<') || (*p == '>')) {
228 if ((*p == '"') || (*p == '\'') || (*p == '`')) {
229 COND_DEBUG("RETURN %d", __LINE__);
230 *error = "Unexpected start of string";
239 *error = "Empty string is invalid";
243 *out = talloc_array(ctx, char, len + 1);
244 memcpy(*out, start, len);
246 COND_DEBUG("PARSED WORD %s", *out);
250 /** Tokenize a conditional check
252 * @param[in] ctx for talloc
253 * @param[in] start the start of the string to process. Should be "(..."
254 * @param[in] brace look for a closing brace
255 * @param[out] pcond pointer to the returned condition structure
256 * @param[out] error the parse error (if any)
257 * @return length of the string skipped, or when negative, the offset to the offending error
259 static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace, fr_cond_t **pcond, char const **error)
262 const char *p = start;
265 FR_TOKEN op, lhs_type, rhs_type;
267 COND_DEBUG("START %s", p);
269 c = talloc_zero(ctx, fr_cond_t);
271 rad_assert(c != NULL);
273 while (isspace((int) *p)) p++; /* skip spaces before condition */
277 COND_DEBUG("RETURN %d", __LINE__);
278 *error = "Empty condition is invalid";
288 while (isspace((int) *p)) p++; /* skip spaces after negation */
295 COND_DEBUG("RETURN %d", __LINE__);
296 *error = "Double negation is invalid";
308 * We've already eaten one layer of
309 * brackets. Go recurse to get more.
311 c->type = COND_TYPE_CHILD;
312 slen = condition_tokenize(c, p, true, &c->data.child, error);
315 COND_DEBUG("RETURN %d", __LINE__);
316 return slen - (p - start);
319 if (!c->data.child) {
321 *error = "Empty condition is invalid";
322 COND_DEBUG("RETURN %d", __LINE__);
327 while (isspace((int) *p)) p++; /* skip spaces after (COND)*/
329 } else { /* it's a bare FOO==BAR */
331 * We didn't see anything special. The condition must be one of
340 COND_DEBUG("LHS %s", p);
343 *error = "Conditional check cannot begin with a regular expression";
344 COND_DEBUG("RETURN %d", __LINE__);
348 slen = condition_tokenize_word(c, p, &lhs, &lhs_type, error);
351 COND_DEBUG("RETURN %d", __LINE__);
352 return slen - (p - start);
356 while (isspace((int)*p)) p++; /* skip spaces after LHS */
359 * We may (or not) have an operator
368 * don't skip the brace. We'll look for it later.
371 c->type = COND_TYPE_EXISTS;
372 c->data.vpt = radius_str2tmpl(c, lhs, lhs_type);
375 *error = "Failed creating exists";
376 COND_DEBUG("RETURN %d", __LINE__);
386 *error = "No closing brace at end of string";
387 COND_DEBUG("RETURN %d", __LINE__);
396 } else if (((p[0] == '&') && (p[1] == '&')) ||
397 ((p[0] == '|') && (p[1] == '|'))) {
401 } else { /* it's an operator */
404 COND_DEBUG("OPERATOR %s", p);
407 * The next thing should now be a comparison operator.
410 c->type = COND_TYPE_MAP;
414 *error = "Invalid text. Expected comparison operator";
415 COND_DEBUG("RETURN %d", __LINE__);
423 } else if (p[1] == '~') {
429 } else if (p[1] == '*') {
436 * really re-write it...
444 *error = "Invalid operator";
445 COND_DEBUG("RETURN %d", __LINE__);
455 } else if (p[1] == '~') {
461 } else if (p[1] == '*') {
466 goto invalid_operator;
494 while (isspace((int) *p)) p++; /* skip spaces after operator */
498 *error = "Expected text after operator";
499 COND_DEBUG("RETURN %d", __LINE__);
503 COND_DEBUG("RHS %s", p);
508 slen = condition_tokenize_word(c, p, &rhs, &rhs_type, error);
511 COND_DEBUG("RETURN %d", __LINE__);
512 return slen - (p - start);
516 * Sanity checks for regexes.
521 *error = "Expected regular expression";
522 COND_DEBUG("RETURN %d", __LINE__);
529 if (p[slen] == 'i') {
534 COND_DEBUG("DONE REGEX %s", p + slen);
536 } else if (!regex && (*p == '/')) {
538 *error = "Unexpected regular expression";
539 COND_DEBUG("RETURN %d", __LINE__);
543 c->data.map = radius_str2map(c, lhs, lhs_type, op, rhs, rhs_type,
544 REQUEST_CURRENT, PAIR_LIST_REQUEST,
545 REQUEST_CURRENT, PAIR_LIST_REQUEST);
548 *error = "Failed creating check";
549 COND_DEBUG("RETURN %d", __LINE__);
554 * @todo: check LHS and RHS separately, to
557 if ((c->data.map->src->type == VPT_TYPE_LIST) ||
558 (c->data.map->dst->type == VPT_TYPE_LIST)) {
560 *error = "Cannot use list references in condition";
561 COND_DEBUG("RETURN %d", __LINE__);
567 while (isspace((int) *p)) p++; /* skip spaces after RHS */
569 } /* parse a condition (COND) or FOO OP BAR*/
577 *error = "Unexpected closing brace";
578 COND_DEBUG("RETURN %d", __LINE__);
583 while (isspace((int) *p)) p++; /* skip spaces after closing brace */
589 * End of string is now allowed.
594 *error = "No closing brace at end of string";
595 COND_DEBUG("RETURN %d", __LINE__);
602 if (!(((p[0] == '&') && (p[1] == '&')) ||
603 ((p[0] == '|') && (p[1] == '|')))) {
605 *error = "Unexpected text after condition";
610 * Recurse to parse the next condition.
612 COND_DEBUG("GOT %c%c", p[0], p[1]);
617 * May still be looking for a closing brace.
619 COND_DEBUG("RECURSE AND/OR");
620 slen = condition_tokenize(c, p, brace, &c->next, error);
623 COND_DEBUG("RETURN %d", __LINE__);
624 return slen - (p - start);
629 COND_DEBUG("RETURN %d", __LINE__);
632 * Normalize it before returning it.
637 * (FOO) ... --> FOO ...
639 if ((c->type == COND_TYPE_CHILD) &&
640 !c->data.child->next) {
643 child = c->data.child;
644 child->next = c->next;
645 child->next_op = c->next_op;
647 c->data.child = NULL;
650 * Set the negation properly
652 if ((c->negate && !child->negate) ||
653 (!c->negate && child->negate)) {
654 child->negate = true;
656 child->negate = false;
659 (void) talloc_steal(ctx, child);
665 * (FOO ...) --> FOO ...
667 * But don't do !(FOO || BAR) --> !FOO || BAR
668 * Because that's different.
670 if ((c->type == COND_TYPE_CHILD) && !c->next &&
674 child = c->data.child;
675 c->data.child = NULL;
676 (void) talloc_steal(ctx, child);
685 /** Tokenize a conditional check
687 * @param[in] ctx for talloc
688 * @param[in] start the start of the string to process. Should be "(..."
689 * @param[out] head the parsed condition structure
690 * @param[out] error the parse error (if any)
691 * @return length of the string skipped, or when negative, the offset to the offending error
693 ssize_t fr_condition_tokenize(TALLOC_CTX *ctx, char const *start, fr_cond_t **head, char const **error)
695 return condition_tokenize(ctx, start, false, head, error);