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>
37 * For POSIX Regular expressions.
38 * (0) Means no extended regular expressions.
39 * REG_EXTENDED means use extended regular expressions.
42 #define REG_EXTENDED (0)
51 static int all_digits(const char *string)
53 const char *p = string;
57 while (isdigit((int) *p)) p++;
63 #define DEBUG4 if (debug_flag > 4)log_debug
66 static const char *filler = "????????????????????????????????????????????????????????????????";
68 static const char *expand_string(char *buffer, size_t sizeof_buffer,
70 LRAD_TOKEN value_type, const char *value)
78 case T_SINGLE_QUOTED_STRING:
81 case T_BACK_QUOTED_STRING:
82 result = radius_exec_program(value, request, 1,
83 buffer, sizeof_buffer, NULL,
90 * The result should be ASCII.
92 for (p = buffer; *p != '\0'; p++) {
101 case T_DOUBLE_QUOTED_STRING:
102 if (!strchr(value, '%')) return value;
104 radius_xlat(buffer, sizeof_buffer, value, request, NULL);
111 static LRAD_TOKEN getregex(char **ptr, char *buffer, size_t buflen)
116 if (*p != '/') return T_OP_INVALID;
120 if (buflen <= 1) break;
151 * FIXME: add 'x' and 'u'
155 if ((p[1] >= '0') && (p[1] <= '9') &&
156 (sscanf(p, "%3o", &x) == 1)) {
178 int radius_evaluate_condition(REQUEST *request, int depth,
179 const char **ptr, int evaluate_it, int *presult)
181 int found_condition = FALSE;
184 int evaluate_next_condition = evaluate_it;
185 const char *p = *ptr;
186 const char *q, *start;
187 LRAD_TOKEN token, lt, rt;
188 char left[1024], right[1024], comp[4];
189 const char *pleft, *pright;
190 char xleft[1024], xright[1024];
193 if (!ptr || !*ptr || (depth >= 64)) {
194 radlog(L_ERR, "Internal sanity check failed in evaluate condition");
199 while ((*p == ' ') || (*p == '\t')) p++;
202 DEBUG4(">>> INVERT");
208 * It's a subcondition.
211 const char *end = p + 1;
214 * Evaluate the condition, bailing out on
217 DEBUG4(">>> CALLING EVALUATE %s", end);
218 if (!radius_evaluate_condition(request, depth + 1,
220 evaluate_next_condition,
225 if (invert && evaluate_next_condition) {
226 DEBUG2("%.*s Converting !%s -> %s",
228 (result != FALSE) ? "TRUE" : "FALSE",
229 (result == FALSE) ? "TRUE" : "FALSE");
232 result = (result == FALSE);
237 * Start from the end of the previous
241 DEBUG4(">>> EVALUATE RETURNED ::%s::", end);
243 if (!((*p == ')') || (*p == '!') ||
244 ((p[0] == '&') && (p[1] == '&')) ||
245 ((p[0] == '|') && (p[1] == '|')))) {
247 radlog(L_ERR, "Parse error in condition at: %s", p);
250 if (*p == ')') p++; /* skip it */
251 found_condition = TRUE;
253 while ((*p == ' ') || (*p == '\t')) p++;
259 DEBUG4(">>> AT EOL");
265 * (A && B) means "evaluate B
266 * only if A was true"
268 } else if ((p[0] == '&') && (p[1] == '&')) {
269 if (result == TRUE) {
270 evaluate_next_condition = evaluate_it;
272 evaluate_next_condition = FALSE;
277 * (A || B) means "evaluate B
278 * only if A was false"
280 } else if ((p[0] == '|') && (p[1] == '|')) {
281 if (result == FALSE) {
282 evaluate_next_condition = evaluate_it;
284 evaluate_next_condition = FALSE;
288 } else if (*p == ')') {
289 DEBUG4(">>> CLOSING BRACE");
298 radlog(L_ERR, "Unexpected trailing text at: %s", p);
301 } /* else it wasn't an opening brace */
303 while ((*p == ' ') || (*p == '\t')) p++;
306 * More conditions, keep going.
308 if ((*p == '(') || (p[0] == '!')) continue;
310 DEBUG4(">>> LOOKING AT %s", p);
314 * Look for common errors.
316 if ((p[0] == '%') && (p[1] == '{')) {
317 radlog(L_ERR, "Bare %%{...} is invalid in condition at: %s", p);
322 * Look for word == value
324 lt = gettoken(&p, left, sizeof(left));
325 if ((lt != T_BARE_WORD) &&
326 (lt != T_DOUBLE_QUOTED_STRING) &&
327 (lt != T_SINGLE_QUOTED_STRING) &&
328 (lt != T_BACK_QUOTED_STRING)) {
329 radlog(L_ERR, "Expected string or numbers at: %s", p);
334 if (evaluate_next_condition) {
335 pleft = expand_string(xleft, sizeof(xleft), request,
338 radlog(L_ERR, "Failed expanding string at: %s",
345 * Peek ahead. Maybe it's just a check for
346 * existence. If so, there shouldn't be anything
350 while ((*q == ' ') || (*q == '\t')) q++;
355 if (!*q || (*q == ')') || (*q == '!') ||
356 ((q[0] == '&') && (q[1] == '&')) ||
357 ((q[0] == '|') && (q[1] == '|'))) {
359 * Check for truth or falsehood.
361 if (all_digits(pleft)) {
363 result = (lint != 0);
365 result = (*pleft != '\0');
369 DEBUG4(">>> INVERTING result");
370 result = (result == FALSE);
374 if (evaluate_next_condition) {
375 DEBUG2("%.*s Evaluating %s\"%s\" -> %s",
377 invert ? "!" : "", pleft,
378 (result != FALSE) ? "TRUE" : "FALSE");
380 } else if (request) {
381 DEBUG2("%.*s Skipping %s\"%s\"",
383 invert ? "!" : "", pleft);
386 DEBUG4(">>> I%d %d:%s", invert,
388 goto end_of_condition;
392 * Else it's a full "foo == bar" thingy.
394 token = gettoken(&p, comp, sizeof(comp));
395 if ((token < T_OP_NE) || (token > T_OP_CMP_EQ) ||
396 (token == T_OP_CMP_TRUE) ||
397 (token == T_OP_CMP_FALSE)) {
398 radlog(L_ERR, "Expected comparison at: %s", comp);
403 * Look for common errors.
405 if ((p[0] == '%') && (p[1] == '{')) {
406 radlog(L_ERR, "Bare %%{...} is invalid in condition at: %s", p);
413 if ((token == T_OP_REG_EQ) ||
414 (token == T_OP_REG_NE)) {
415 rt = getregex(&p, right, sizeof(right));
417 rt = gettoken(&p, right, sizeof(right));
419 if ((rt != T_BARE_WORD) &&
420 (rt != T_DOUBLE_QUOTED_STRING) &&
421 (rt != T_SINGLE_QUOTED_STRING) &&
422 (rt != T_BACK_QUOTED_STRING)) {
423 radlog(L_ERR, "Expected string or numbers at: %s", p);
428 if (evaluate_next_condition) {
429 pright = expand_string(xright, sizeof(xright), request,
432 radlog(L_ERR, "Failed expanding string at: %s",
438 DEBUG4(">>> %d:%s %d %d:%s",
439 lt, pleft, token, rt, pright);
441 if (evaluate_next_condition) {
443 * Mangle operator && conditions to
444 * simplify the following code.
448 invert = (invert == FALSE);
456 if (!all_digits(pleft)) {
457 radlog(L_ERR, "Left field is not a number at: %s", pleft);
460 if (!all_digits(pright)) {
461 radlog(L_ERR, "Right field is not a number at: %s", pright);
474 result = (strcmp(pleft, pright) == 0);
478 result = (lint >= rint);
482 result = (lint > rint);
486 result = (lint <= rint);
490 result = (lint < rint);
497 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
500 * Include substring matches.
502 regcomp(®, pright, REG_EXTENDED);
503 compare = regexec(®, pleft,
504 REQUEST_MAX_REGEX + 1,
509 * Add %{0}, %{1}, etc.
511 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
516 * Didn't match: delete old
517 * match, if it existed.
519 if ((compare != 0) ||
520 (rxmatch[i].rm_so == -1)) {
521 r = request_data_get(request,
523 REQUEST_DATA_REGEX | i);
537 * Copy substring into buffer.
539 memcpy(buffer, pleft + rxmatch[i].rm_so,
540 rxmatch[i].rm_eo - rxmatch[i].rm_so);
541 buffer[rxmatch[i].rm_eo - rxmatch[i].rm_so] = '\0';
544 * Copy substring, and add it to
547 * Note that we don't check
548 * for out of memory, which is
549 * the only error we can get...
552 request_data_add(request, request,
553 REQUEST_DATA_REGEX | i,
556 result = (compare == 0);
563 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
566 * Include substring matches.
568 regcomp(®, pright,
570 compare = regexec(®, pleft,
571 REQUEST_MAX_REGEX + 1,
575 result = (compare != 0);
581 DEBUG4(">>> NOT IMPLEMENTED %d", token);
585 DEBUG2("%.*s Evaluating %s(%.*s) -> %s",
587 invert ? "!" : "", p - start, start,
588 (result != FALSE) ? "TRUE" : "FALSE");
590 DEBUG4(">>> GOT result %d", result);
593 * Not evaluating it. We may be just
596 } else if (request) {
597 DEBUG2("%.*s Skipping %s(\"%s\" %s \"%s\")",
599 invert ? "!" : "", pleft, comp, pright);
604 DEBUG4(">>> INVERTING result");
605 result = (result == FALSE);
612 DEBUG4(">>> EVALUATE %d ::%s::",
613 evaluate_next_condition, p);
615 while ((*p == ' ') || (*p == '\t')) p++;
618 * Closing brace or EOL, return.
620 if (!*p || (*p == ')') || (*p == '!') ||
621 ((p[0] == '&') && (p[1] == '&')) ||
622 ((p[0] == '|') && (p[1] == '|'))) {
623 DEBUG4(">>> AT EOL2a");
628 } /* loop over the input condition */
630 DEBUG4(">>> AT EOL2b");
637 * Copied shamelessly from conffile.c, to simplify the API for
640 typedef enum conf_type {
641 CONF_ITEM_INVALID = 0,
648 struct conf_item *next;
649 struct conf_part *parent;
658 LRAD_TOKEN value_type;
663 * Add attributes to a list.
665 int radius_update_attrlist(REQUEST *request, CONF_SECTION *cs,
669 VALUE_PAIR *head, **tail;
670 VALUE_PAIR **vps = NULL;
672 if (!request || !cs) return RLM_MODULE_INVALID;
674 if (strcmp(name, "request") == 0) {
675 vps = &request->packet->vps;
677 } else if (strcmp(name, "reply") == 0) {
678 vps = &request->reply->vps;
680 } else if (strcmp(name, "proxy-request") == 0) {
681 if (request->proxy) vps = &request->proxy->vps;
683 } else if (strcmp(name, "proxy-reply") == 0) {
684 if (request->proxy_reply) vps = &request->proxy_reply->vps;
686 } else if (strcmp(name, "config") == 0) {
687 vps = &request->config_items;
690 return RLM_MODULE_INVALID;
693 if (!vps) return RLM_MODULE_NOOP; /* didn't update the list */
698 for (ci=cf_item_find_next(cs, NULL);
700 ci=cf_item_find_next(cs, ci)) {
706 if (cf_item_is_section(ci)) {
708 return RLM_MODULE_INVALID;
711 cp = cf_itemtopair(ci);
713 value = expand_string(buffer, sizeof(buffer), request,
714 cp->value_type, cp->value);
717 return RLM_MODULE_INVALID;
720 vp = pairmake(cp->attr, value, cp->operator);
722 DEBUG2("Failed to create attribute %s value %s",
725 return RLM_MODULE_FAIL;
732 if (!head) return RLM_MODULE_NOOP;
734 pairmove(vps, &head);
737 return RLM_MODULE_UPDATED;