2 * evaluate.c Evaluate complex conditions
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 2007 The FreeRADIUS server project
21 * Copyright 2007 Alan DeKok <aland@deployingradius.com>
24 #include <freeradius-devel/ident.h>
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29 #include <freeradius-devel/rad_assert.h>
35 * For POSIX Regular expressions.
36 * (0) Means no extended regular expressions.
37 * REG_EXTENDED means use extended regular expressions.
40 #define REG_EXTENDED (0)
49 static int all_digits(const char *string)
51 const char *p = string;
55 while (isdigit((int) *p)) p++;
61 #define DEBUG4 if (debug_flag > 4)log_debug
64 static const char *filler = "????????????????????????????????????????????????????????????????";
66 static const char *expand_string(char *buffer, size_t sizeof_buffer,
68 LRAD_TOKEN value_type, const char *value)
76 case T_SINGLE_QUOTED_STRING:
79 case T_BACK_QUOTED_STRING:
80 result = radius_exec_program(value, request, 1,
81 buffer, sizeof_buffer, NULL,
88 * The result should be ASCII.
90 for (p = buffer; *p != '\0'; p++) {
99 case T_DOUBLE_QUOTED_STRING:
100 if (!strchr(value, '%')) return value;
102 radius_xlat(buffer, sizeof_buffer, value, request, NULL);
109 static LRAD_TOKEN getregex(char **ptr, char *buffer, size_t buflen)
114 if (*p != '/') return T_OP_INVALID;
118 if (buflen <= 1) break;
149 * FIXME: add 'x' and 'u'
153 if ((p[1] >= '0') && (p[1] <= '9') &&
154 (sscanf(p, "%3o", &x) == 1)) {
175 int radius_evaluate_condition(REQUEST *request, int depth,
176 const char **ptr, int evaluate_it, int *presult)
178 int found_condition = FALSE;
181 int evaluate_next_condition = evaluate_it;
182 const char *p = *ptr;
183 const char *q, *start;
184 LRAD_TOKEN token, lt, rt;
185 char left[1024], right[1024], comp[4];
186 const char *pleft, *pright;
187 char xleft[1024], xright[1024];
190 if (!ptr || !*ptr || (depth >= 64)) {
191 radlog(L_ERR, "Internal sanity check failed in evaluate condition");
196 while ((*p == ' ') || (*p == '\t')) p++;
199 DEBUG4(">>> INVERT");
205 * It's a subcondition.
208 const char *end = p + 1;
211 * Evaluate the condition, bailing out on
214 DEBUG4(">>> CALLING EVALUATE %s", end);
215 if (!radius_evaluate_condition(request, depth + 1,
217 evaluate_next_condition,
222 if (invert && evaluate_next_condition) {
223 DEBUG2("%.*s Converting !%s -> %s",
225 (result != FALSE) ? "TRUE" : "FALSE",
226 (result == FALSE) ? "TRUE" : "FALSE");
229 result = (result == FALSE);
234 * Start from the end of the previous
238 DEBUG4(">>> EVALUATE RETURNED ::%s::", end);
241 ((p[0] == '&') && (p[1] == '&')) ||
242 ((p[0] == '|') && (p[1] == '|')))) {
244 radlog(L_ERR, "Parse error in condition at: %s", p);
247 if (*p == ')') p++; /* skip it */
248 found_condition = TRUE;
250 while ((*p == ' ') || (*p == '\t')) p++;
256 DEBUG4(">>> AT EOL");
262 * (A && B) means "evaluate B
263 * only if A was true"
265 } else if ((p[0] == '&') && (p[1] == '&')) {
266 if (result == TRUE) {
267 evaluate_next_condition = evaluate_it;
269 evaluate_next_condition = FALSE;
274 * (A || B) means "evaluate B
275 * only if A was false"
277 } else if ((p[0] == '|') && (p[1] == '|')) {
278 if (result == FALSE) {
279 evaluate_next_condition = evaluate_it;
281 evaluate_next_condition = FALSE;
285 } else if (*p == ')') {
286 DEBUG4(">>> CLOSING BRACE");
295 radlog(L_ERR, "Unexpected trailing text at: %s", p);
298 } /* else it wasn't an opening brace */
300 while ((*p == ' ') || (*p == '\t')) p++;
303 * More conditions, keep going.
306 ((p[0] == '!') && (p[1] == '('))) continue;
308 DEBUG4(">>> LOOKING AT %s", p);
312 * Look for common errors.
314 if ((p[0] == '%') && (p[1] == '{')) {
315 radlog(L_ERR, "Bare %%{...} is invalid in condition at: %s", p);
320 * Look for word == value
322 lt = gettoken(&p, left, sizeof(left));
323 if ((lt != T_BARE_WORD) &&
324 (lt != T_DOUBLE_QUOTED_STRING) &&
325 (lt != T_SINGLE_QUOTED_STRING) &&
326 (lt != T_BACK_QUOTED_STRING)) {
327 radlog(L_ERR, "Expected string or numbers at: %s", p);
332 if (evaluate_next_condition) {
333 pleft = expand_string(xleft, sizeof(xleft), request,
336 radlog(L_ERR, "Failed expanding string at: %s",
343 * Peek ahead. Maybe it's just a check for
344 * existence. If so, there shouldn't be anything
348 while ((*q == ' ') || (*q == '\t')) q++;
353 if (!*q || (*q == ')') ||
354 ((q[0] == '&') && (q[1] == '&')) ||
355 ((q[0] == '|') && (q[1] == '|'))) {
357 * Check for truth or falsehood.
359 if (all_digits(pleft)) {
361 result = (lint != 0);
363 result = (*pleft != '\0');
366 if (evaluate_next_condition) {
367 DEBUG2("%.*s Evaluating %s\"%s\" -> %s",
369 invert ? "!" : "", pleft,
370 (result != FALSE) ? "TRUE" : "FALSE");
372 } else if (request) {
373 DEBUG2("%.*s Skipping %s\"%s\"",
375 invert ? "!" : "", pleft);
378 DEBUG4(">>> I%d %d:%s", invert,
380 goto end_of_condition;
384 * Else it's a full "foo == bar" thingy.
386 token = gettoken(&p, comp, sizeof(comp));
387 if ((token < T_OP_NE) || (token > T_OP_CMP_EQ) ||
388 (token == T_OP_CMP_TRUE) ||
389 (token == T_OP_CMP_FALSE)) {
390 radlog(L_ERR, "Expected comparison at: %s", comp);
395 * Look for common errors.
397 if ((p[0] == '%') && (p[1] == '{')) {
398 radlog(L_ERR, "Bare %%{...} is invalid in condition at: %s", p);
405 * FIXME: If token == T_OP_REG_EQ or T_OP_REG_NE,
406 * quote strings by '/', rather than anything
409 if ((token == T_OP_REG_EQ) ||
410 (token == T_OP_REG_NE)) {
411 rt = getregex(&p, right, sizeof(right));
413 rt = gettoken(&p, right, sizeof(right));
415 if ((rt != T_BARE_WORD) &&
416 (rt != T_DOUBLE_QUOTED_STRING) &&
417 (rt != T_SINGLE_QUOTED_STRING) &&
418 (rt != T_BACK_QUOTED_STRING)) {
419 radlog(L_ERR, "Expected string or numbers at 2 %d: %s", rt, p);
424 if (evaluate_next_condition) {
425 pright = expand_string(xright, sizeof(xright), request,
428 radlog(L_ERR, "Failed expanding string at: %s",
434 DEBUG4(">>> %d:%s %d %d:%s",
435 lt, pleft, token, rt, pright);
437 if (evaluate_next_condition) {
439 * Mangle operator && conditions to
440 * simplify the following code.
444 invert = (invert == FALSE);
452 if (!all_digits(pleft)) {
453 radlog(L_ERR, "Left field is not a number at: %s", pleft);
456 if (!all_digits(pright)) {
457 radlog(L_ERR, "Right field is not a number at: %s", pright);
470 result = (strcmp(pleft, pright) == 0);
474 result = (lint >= rint);
478 result = (lint > rint);
482 result = (lint <= rint);
486 result = (lint < rint);
493 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
496 * Include substring matches.
498 regcomp(®, pright, REG_EXTENDED);
499 compare = regexec(®, pleft,
500 REQUEST_MAX_REGEX + 1,
505 * Add %{0}, %{1}, etc.
507 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
512 * Didn't match: delete old
513 * match, if it existed.
515 if ((compare != 0) ||
516 (rxmatch[i].rm_so == -1)) {
517 p = request_data_get(request,
519 REQUEST_DATA_REGEX | i);
533 * Copy substring into buffer.
535 memcpy(buffer, pleft + rxmatch[i].rm_so,
536 rxmatch[i].rm_eo - rxmatch[i].rm_so);
537 buffer[rxmatch[i].rm_eo - rxmatch[i].rm_so] = '\0';
540 * Copy substring, and add it to
543 * Note that we don't check
544 * for out of memory, which is
545 * the only error we can get...
548 request_data_add(request, request,
549 REQUEST_DATA_REGEX | i,
552 result = (compare == 0);
559 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
562 * Include substring matches.
564 regcomp(®, pright,
566 compare = regexec(®, pleft,
567 REQUEST_MAX_REGEX + 1,
571 result = (compare != 0);
577 DEBUG4(">>> NOT IMPLEMENTED %d", token);
581 DEBUG2("%.*s Evaluating %s(%.*s) -> %s",
583 invert ? "!" : "", p - start, start,
584 (result != FALSE) ? "TRUE" : "FALSE");
586 DEBUG4(">>> GOT result %d", result);
589 * Not evaluating it. We may be just
592 } else if (request) {
593 DEBUG2("%.*s Skipping %s(\"%s\" %s \"%s\")",
595 invert ? "!" : "", pleft, comp, pright);
600 DEBUG4(">>> INVERTING result");
601 result = (result == FALSE);
608 DEBUG4(">>> EVALUATE %d ::%s::",
609 evaluate_next_condition, p);
611 while ((*p == ' ') || (*p == '\t')) p++;
614 * Closing brace or EOL, return.
616 if (!*p || (*p == ')') ||
617 ((p[0] == '&') && (p[1] == '&')) ||
618 ((p[0] == '|') && (p[1] == '|'))) {
619 DEBUG4(">>> AT EOL2a");
624 } /* loop over the input condition */
626 DEBUG4(">>> AT EOL2b");
633 * Copied shamelessly from conffile.c, to simplify the API for
636 typedef enum conf_type {
637 CONF_ITEM_INVALID = 0,
644 struct conf_item *next;
645 struct conf_part *parent;
654 LRAD_TOKEN value_type;
659 * Add attributes to a list.
661 int radius_update_attrlist(REQUEST *request, CONF_SECTION *cs,
665 VALUE_PAIR *head, **tail;
666 VALUE_PAIR **vps = NULL;
668 if (!request || !cs) return RLM_MODULE_INVALID;
670 if (strcmp(name, "request") == 0) {
671 vps = &request->packet->vps;
673 } else if (strcmp(name, "reply") == 0) {
674 vps = &request->reply->vps;
676 } else if (strcmp(name, "proxy-request") == 0) {
677 if (request->proxy) vps = &request->proxy->vps;
679 } else if (strcmp(name, "proxy-reply") == 0) {
680 if (request->proxy_reply) vps = &request->proxy_reply->vps;
682 } else if (strcmp(name, "config") == 0) {
683 vps = &request->config_items;
686 return RLM_MODULE_INVALID;
689 if (!vps) return RLM_MODULE_NOOP; /* didn't update the list */
694 for (ci=cf_item_find_next(cs, NULL);
696 ci=cf_item_find_next(cs, ci)) {
702 if (cf_item_is_section(ci)) {
704 return RLM_MODULE_INVALID;
707 cp = cf_itemtopair(ci);
709 value = expand_string(buffer, sizeof(buffer), request,
710 cp->value_type, cp->value);
713 return RLM_MODULE_INVALID;
716 vp = pairmake(cp->attr, value, cp->operator);
719 return RLM_MODULE_FAIL;
726 if (!head) return RLM_MODULE_NOOP;
728 pairmove(vps, &head);
731 return RLM_MODULE_UPDATED;