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++;
64 #define DEBUG4 if (debug_flag > 4)log_debug
67 #define DEBUG4 if (0) log_debug
70 static const char *filler = "????????????????????????????????????????????????????????????????";
72 static const char *expand_string(char *buffer, size_t sizeof_buffer,
74 LRAD_TOKEN value_type, const char *value)
82 case T_SINGLE_QUOTED_STRING:
85 case T_BACK_QUOTED_STRING:
86 result = radius_exec_program(value, request, 1,
87 buffer, sizeof_buffer, NULL,
94 * The result should be ASCII.
96 for (p = buffer; *p != '\0'; p++) {
105 case T_DOUBLE_QUOTED_STRING:
106 if (!strchr(value, '%')) return value;
108 radius_xlat(buffer, sizeof_buffer, value, request, NULL);
116 static LRAD_TOKEN getregex(char **ptr, char *buffer, size_t buflen, int *pcflags)
121 if (*p != '/') return T_OP_INVALID;
123 *pcflags = REG_EXTENDED;
127 if (buflen <= 1) break;
133 * Check for case insensitivity
137 *pcflags |= REG_ICASE;
167 * FIXME: add 'x' and 'u'
171 if ((p[1] >= '0') && (p[1] <= '9') &&
172 (sscanf(p, "%3o", &x) == 1)) {
195 int radius_evaluate_condition(REQUEST *request, int depth,
196 const char **ptr, int evaluate_it, int *presult)
198 int found_condition = FALSE;
201 int evaluate_next_condition = evaluate_it;
202 const char *p = *ptr;
203 const char *q, *start;
204 LRAD_TOKEN token, lt, rt;
205 char left[1024], right[1024], comp[4];
206 const char *pleft, *pright;
207 char xleft[1024], xright[1024];
213 if (!ptr || !*ptr || (depth >= 64)) {
214 radlog(L_ERR, "Internal sanity check failed in evaluate condition");
219 while ((*p == ' ') || (*p == '\t')) p++;
222 DEBUG4(">>> INVERT");
228 * It's a subcondition.
231 const char *end = p + 1;
234 * Evaluate the condition, bailing out on
237 DEBUG4(">>> CALLING EVALUATE %s", end);
238 if (!radius_evaluate_condition(request, depth + 1,
240 evaluate_next_condition,
245 if (invert && evaluate_next_condition) {
246 DEBUG2("%.*s Converting !%s -> %s",
248 (result != FALSE) ? "TRUE" : "FALSE",
249 (result == FALSE) ? "TRUE" : "FALSE");
252 result = (result == FALSE);
257 * Start from the end of the previous
261 DEBUG4(">>> EVALUATE RETURNED ::%s::", end);
263 if (!((*p == ')') || (*p == '!') ||
264 ((p[0] == '&') && (p[1] == '&')) ||
265 ((p[0] == '|') && (p[1] == '|')))) {
267 radlog(L_ERR, "Parse error in condition at: %s", p);
270 if (*p == ')') p++; /* skip it */
271 found_condition = TRUE;
273 while ((*p == ' ') || (*p == '\t')) p++;
279 DEBUG4(">>> AT EOL");
285 * (A && B) means "evaluate B
286 * only if A was true"
288 } else if ((p[0] == '&') && (p[1] == '&')) {
289 if (result == TRUE) {
290 evaluate_next_condition = evaluate_it;
292 evaluate_next_condition = FALSE;
297 * (A || B) means "evaluate B
298 * only if A was false"
300 } else if ((p[0] == '|') && (p[1] == '|')) {
301 if (result == FALSE) {
302 evaluate_next_condition = evaluate_it;
304 evaluate_next_condition = FALSE;
308 } else if (*p == ')') {
309 DEBUG4(">>> CLOSING BRACE");
318 radlog(L_ERR, "Unexpected trailing text at: %s", p);
321 } /* else it wasn't an opening brace */
323 while ((*p == ' ') || (*p == '\t')) p++;
326 * More conditions, keep going.
328 if ((*p == '(') || (p[0] == '!')) continue;
330 DEBUG4(">>> LOOKING AT %s", p);
334 * Look for common errors.
336 if ((p[0] == '%') && (p[1] == '{')) {
337 radlog(L_ERR, "Bare %%{...} is invalid in condition at: %s", p);
342 * Look for word == value
344 lt = gettoken(&p, left, sizeof(left));
345 if ((lt != T_BARE_WORD) &&
346 (lt != T_DOUBLE_QUOTED_STRING) &&
347 (lt != T_SINGLE_QUOTED_STRING) &&
348 (lt != T_BACK_QUOTED_STRING)) {
349 radlog(L_ERR, "Expected string or numbers at: %s", p);
354 if (evaluate_next_condition) {
355 pleft = expand_string(xleft, sizeof(xleft), request,
358 radlog(L_ERR, "Failed expanding string at: %s",
365 * Peek ahead. Maybe it's just a check for
366 * existence. If so, there shouldn't be anything
370 while ((*q == ' ') || (*q == '\t')) q++;
375 if (!*q || (*q == ')') || ((*q == '!') && (q[1] != '=')) ||
376 ((q[0] == '&') && (q[1] == '&')) ||
377 ((q[0] == '|') && (q[1] == '|'))) {
379 * Check for truth or falsehood.
381 if (all_digits(pleft)) {
383 result = (lint != 0);
385 result = (*pleft != '\0');
389 DEBUG4(">>> INVERTING result");
390 result = (result == FALSE);
394 if (evaluate_next_condition) {
395 DEBUG2("%.*s Evaluating %s\"%s\" -> %s",
397 invert ? "!" : "", pleft,
398 (result != FALSE) ? "TRUE" : "FALSE");
400 } else if (request) {
401 DEBUG2("%.*s Skipping %s\"%s\"",
403 invert ? "!" : "", pleft);
406 DEBUG4(">>> I%d %d:%s", invert,
408 goto end_of_condition;
412 * Else it's a full "foo == bar" thingy.
414 token = gettoken(&p, comp, sizeof(comp));
415 if ((token < T_OP_NE) || (token > T_OP_CMP_EQ) ||
416 (token == T_OP_CMP_TRUE) ||
417 (token == T_OP_CMP_FALSE)) {
418 radlog(L_ERR, "Expected comparison at: %s", comp);
423 * Look for common errors.
425 if ((p[0] == '%') && (p[1] == '{')) {
426 radlog(L_ERR, "Bare %%{...} is invalid in condition at: %s", p);
434 if ((token == T_OP_REG_EQ) ||
435 (token == T_OP_REG_NE)) {
436 rt = getregex(&p, right, sizeof(right), &cflags);
439 rt = gettoken(&p, right, sizeof(right));
441 if ((rt != T_BARE_WORD) &&
442 (rt != T_DOUBLE_QUOTED_STRING) &&
443 (rt != T_SINGLE_QUOTED_STRING) &&
444 (rt != T_BACK_QUOTED_STRING)) {
445 radlog(L_ERR, "Expected string or numbers at: %s", p);
450 if (evaluate_next_condition) {
451 pright = expand_string(xright, sizeof(xright), request,
454 radlog(L_ERR, "Failed expanding string at: %s",
460 DEBUG4(">>> %d:%s %d %d:%s",
461 lt, pleft, token, rt, pright);
463 if (evaluate_next_condition) {
468 if (!all_digits(pleft)) {
469 radlog(L_ERR, "Left field is not a number at: %s", pleft);
472 if (!all_digits(pright)) {
473 radlog(L_ERR, "Right field is not a number at: %s", pright);
486 result = (strcmp(pleft, pright) == 0);
490 result = (strcmp(pleft, pright) != 0);
494 result = (lint >= rint);
498 result = (lint > rint);
502 result = (lint <= rint);
506 result = (lint < rint);
513 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
516 * Include substring matches.
518 regcomp(®, pright, cflags);
519 compare = regexec(®, pleft,
520 REQUEST_MAX_REGEX + 1,
525 * Add %{0}, %{1}, etc.
527 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
532 * Didn't match: delete old
533 * match, if it existed.
535 if ((compare != 0) ||
536 (rxmatch[i].rm_so == -1)) {
537 r = request_data_get(request,
539 REQUEST_DATA_REGEX | i);
553 * Copy substring into buffer.
555 memcpy(buffer, pleft + rxmatch[i].rm_so,
556 rxmatch[i].rm_eo - rxmatch[i].rm_so);
557 buffer[rxmatch[i].rm_eo - rxmatch[i].rm_so] = '\0';
560 * Copy substring, and add it to
563 * Note that we don't check
564 * for out of memory, which is
565 * the only error we can get...
568 request_data_add(request, request,
569 REQUEST_DATA_REGEX | i,
572 result = (compare == 0);
579 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
582 * Include substring matches.
584 regcomp(®, pright, cflags);
585 compare = regexec(®, pleft,
586 REQUEST_MAX_REGEX + 1,
590 result = (compare != 0);
596 DEBUG4(">>> NOT IMPLEMENTED %d", token);
600 DEBUG2("%.*s Evaluating %s(%.*s) -> %s",
602 invert ? "!" : "", p - start, start,
603 (result != FALSE) ? "TRUE" : "FALSE");
605 DEBUG4(">>> GOT result %d", result);
608 * Not evaluating it. We may be just
611 } else if (request) {
612 DEBUG2("%.*s Skipping %s(\"%s\" %s \"%s\")",
614 invert ? "!" : "", pleft, comp, pright);
619 DEBUG4(">>> INVERTING result");
620 result = (result == FALSE);
627 DEBUG4(">>> EVALUATE %d ::%s::",
628 evaluate_next_condition, p);
630 while ((*p == ' ') || (*p == '\t')) p++;
633 * Closing brace or EOL, return.
635 if (!*p || (*p == ')') || ((*p == '!') && (p[1] != '=')) ||
636 ((p[0] == '&') && (p[1] == '&')) ||
637 ((p[0] == '|') && (p[1] == '|'))) {
638 DEBUG4(">>> AT EOL2a");
640 if (evaluate_next_condition) *presult = result;
643 } /* loop over the input condition */
645 DEBUG4(">>> AT EOL2b");
647 if (evaluate_next_condition) *presult = result;
652 * The pairmove() function in src/lib/valuepair.c does all sorts of
653 * extra magic that we don't want here.
655 * FIXME: integrate this with the code calling it, so that we
656 * only paircopy() those attributes that we're really going to
659 static void my_pairmove(VALUE_PAIR **to, VALUE_PAIR *from)
661 int i, j, count, from_count, to_count, tailto;
662 VALUE_PAIR *vp, *next, **last;
663 VALUE_PAIR **from_list, **to_list;
666 * Set up arrays for editing, to remove some of the
667 * O(N^2) dependencies. This also makes it easier to
668 * insert and remove attributes.
670 * It also means that the operators apply ONLY to the
671 * attributes in the original list. With the previous
672 * implementation of pairmove(), adding two attributes
673 * via "+=" and then "=" would mean that the second one
674 * wasn't added, because of the existence of the first
675 * one in the "to" list. This implementation doesn't
678 * Also, the previous implementation did NOT implement
679 * "-=" correctly. If two of the same attributes existed
680 * in the "to" list, and you tried to subtract something
681 * matching the *second* value, then the pairdelete()
682 * function was called, and the *all* attributes of that
683 * number were deleted. With this implementation, only
684 * the matching attributes are deleted.
687 for (vp = from; vp != NULL; vp = vp->next) count++;
688 from_list = rad_malloc(sizeof(*from_list) * count);
690 for (vp = *to; vp != NULL; vp = vp->next) count++;
691 to_list = rad_malloc(sizeof(*to_list) * count);
694 * Move the lists to the arrays, and break the list
698 for (vp = from; vp != NULL; vp = next) {
700 from_list[from_count++] = vp;
705 for (vp = *to; vp != NULL; vp = next) {
707 to_list[to_count++] = vp;
712 DEBUG4("FROM %d TO %d MAX %d", from_count, to_count, count);
715 * Now that we have the lists initialized, start working
718 for (i = 0; i < from_count; i++) {
722 * Attribute should be appended, OR the "to" list
723 * is empty, and we're supposed to replace or
724 * "add if not existing".
726 if ((from_list[i]->operator == T_OP_ADD) ||
728 ((from_list[i]->operator == T_OP_SET) ||
729 (from_list[i]->operator == T_OP_EQ)))) {
730 DEBUG4("APPENDING %s FROM %d TO %d",
731 from_list[i]->name, i, tailto);
732 to_list[tailto++] = from_list[i];
738 for (j = 0; j < to_count; j++) {
740 * Attributes aren't the same, skip them.
742 if (from_list[i]->attribute != to_list[j]->attribute) {
747 * We don't use a "switch" statement here
748 * because we want to break out of the
749 * "for" loop over 'j' in most cases.
753 * Over-write the FIRST instance of the
754 * matching attribute name. We free the
755 * one in the "to" list, and move over
756 * the one in the "from" list.
758 if (from_list[i]->operator == T_OP_SET) {
759 DEBUG4("OVERWRITING %s FROM %d TO %d",
760 to_list[j]->name, i, j);
761 pairfree(&to_list[j]);
762 to_list[j] = from_list[i];
768 * Add the attribute only if it does not
769 * exist... but it exists, so we stop
772 if (from_list[i]->operator == T_OP_EQ) {
778 * Delete all matching attributes from
781 if (from_list[i]->operator == T_OP_SUB) {
783 * Check for equality.
785 from_list[i]->operator = T_OP_CMP_EQ;
788 * If equal, delete the one in
791 if (radius_compare_vps(NULL, from_list[i],
794 DEBUG4("DELETING %s FROM %d TO %d",
795 from_list[i]->name, i, j);
796 pairfree(&to_list[j]);
801 * We may want to do more
802 * subtractions, so we re-set the
803 * operator back to it's original
806 from_list[i]->operator = T_OP_SUB;
810 rad_assert(0 == 1); /* panic! */
814 * We were asked to add it if it didn't exist,
815 * and it doesn't exist. Move it over to the tail
818 if ((from_list[i]->operator == T_OP_EQ) && !found) {
819 to_list[tailto++] = from_list[i];
825 * Delete attributes in the "from" list.
827 for (i = 0; i < from_count; i++) {
828 if (!from_list[i]) continue;
830 pairfree(&from_list[i]);
834 DEBUG4("TO %d %d", to_count, tailto);
837 * Re-chain the "to" list.
841 for (i = 0; i < tailto; i++) {
842 if (!to_list[i]) continue;
844 DEBUG4("to[%d] = %s", i, to_list[i]->name);
847 last = &(*last)->next;
854 * Copied shamelessly from conffile.c, to simplify the API for
857 typedef enum conf_type {
858 CONF_ITEM_INVALID = 0,
865 struct conf_item *next;
866 struct conf_part *parent;
875 LRAD_TOKEN value_type;
879 * Add attributes to a list.
881 int radius_update_attrlist(REQUEST *request, CONF_SECTION *cs,
882 VALUE_PAIR *input_vps, const char *name)
885 VALUE_PAIR *newlist, *vp;
886 VALUE_PAIR **output_vps = NULL;
888 if (!request || !cs) return RLM_MODULE_INVALID;
890 if (strcmp(name, "request") == 0) {
891 output_vps = &request->packet->vps;
893 } else if (strcmp(name, "reply") == 0) {
894 output_vps = &request->reply->vps;
896 } else if (strcmp(name, "proxy-request") == 0) {
897 if (request->proxy) output_vps = &request->proxy->vps;
899 } else if (strcmp(name, "proxy-reply") == 0) {
900 if (request->proxy_reply) output_vps = &request->proxy_reply->vps;
902 } else if (strcmp(name, "config") == 0) {
903 output_vps = &request->config_items;
905 } else if (strcmp(name, "control") == 0) {
906 output_vps = &request->config_items;
909 return RLM_MODULE_INVALID;
912 if (!output_vps) return RLM_MODULE_NOOP; /* didn't update the list */
914 newlist = paircopy(input_vps);
916 DEBUG2("Out of memory");
917 return RLM_MODULE_FAIL;
921 for (ci=cf_item_find_next(cs, NULL);
923 ci=cf_item_find_next(cs, ci)) {
927 * This should never happen.
929 if (cf_item_is_section(ci)) {
931 return RLM_MODULE_INVALID;
934 cp = cf_itemtopair(ci);
937 * The VP && CF lists should be in sync. If they're
940 if (vp->flags.do_xlat) {
944 value = expand_string(buffer, sizeof(buffer), request,
945 cp->value_type, cp->value);
948 return RLM_MODULE_INVALID;
951 if (!pairparsevalue(vp, value)) {
952 DEBUG2("ERROR: Failed parsing value \"%s\" for attribute %s: %s",
953 value, vp->name, librad_errstr);
955 return RLM_MODULE_FAIL;
957 vp->flags.do_xlat = 0;
962 my_pairmove(output_vps, newlist);
964 return RLM_MODULE_UPDATED;