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 FR_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++) {
104 case T_DOUBLE_QUOTED_STRING:
105 if (!strchr(value, '%')) return value;
107 radius_xlat(buffer, sizeof_buffer, value, request, NULL);
115 static FR_TOKEN getregex(char **ptr, char *buffer, size_t buflen, int *pcflags)
120 if (*p != '/') return T_OP_INVALID;
122 *pcflags = REG_EXTENDED;
126 if (buflen <= 1) break;
132 * Check for case insensitivity
136 *pcflags |= REG_ICASE;
166 * FIXME: add 'x' and 'u'
170 if ((p[1] >= '0') && (p[1] <= '9') &&
171 (sscanf(p, "%3o", &x) == 1)) {
190 return T_DOUBLE_QUOTED_STRING;
194 static const FR_NAME_NUMBER modreturn_table[] = {
195 { "reject", RLM_MODULE_REJECT },
196 { "fail", RLM_MODULE_FAIL },
197 { "ok", RLM_MODULE_OK },
198 { "handled", RLM_MODULE_HANDLED },
199 { "invalid", RLM_MODULE_INVALID },
200 { "userlock", RLM_MODULE_USERLOCK },
201 { "notfound", RLM_MODULE_NOTFOUND },
202 { "noop", RLM_MODULE_NOOP },
203 { "updated", RLM_MODULE_UPDATED },
207 int radius_evaluate_condition(REQUEST *request, int modreturn, int depth,
208 const char **ptr, int evaluate_it, int *presult)
210 int found_condition = FALSE;
213 int evaluate_next_condition = evaluate_it;
214 const char *p = *ptr;
215 const char *q, *start;
216 FR_TOKEN token, lt, rt;
217 char left[1024], right[1024], comp[4];
218 const char *pleft, *pright;
219 char xleft[1024], xright[1024];
225 if (!ptr || !*ptr || (depth >= 64)) {
226 radlog(L_ERR, "Internal sanity check failed in evaluate condition");
231 while ((*p == ' ') || (*p == '\t')) p++;
234 DEBUG4(">>> INVERT");
240 * It's a subcondition.
243 const char *end = p + 1;
246 * Evaluate the condition, bailing out on
249 DEBUG4(">>> CALLING EVALUATE %s", end);
250 if (!radius_evaluate_condition(request, modreturn,
252 evaluate_next_condition,
257 if (invert && evaluate_next_condition) {
258 DEBUG2("%.*s Converting !%s -> %s",
260 (result != FALSE) ? "TRUE" : "FALSE",
261 (result == FALSE) ? "TRUE" : "FALSE");
264 result = (result == FALSE);
269 * Start from the end of the previous
273 DEBUG4(">>> EVALUATE RETURNED ::%s::", end);
275 if (!((*p == ')') || (*p == '!') ||
276 ((p[0] == '&') && (p[1] == '&')) ||
277 ((p[0] == '|') && (p[1] == '|')))) {
279 radlog(L_ERR, "Parse error in condition at: %s", p);
282 if (*p == ')') p++; /* skip it */
283 found_condition = TRUE;
285 while ((*p == ' ') || (*p == '\t')) p++;
291 DEBUG4(">>> AT EOL");
297 * (A && B) means "evaluate B
298 * only if A was true"
300 } else if ((p[0] == '&') && (p[1] == '&')) {
301 if (result == TRUE) {
302 evaluate_next_condition = evaluate_it;
304 evaluate_next_condition = FALSE;
309 * (A || B) means "evaluate B
310 * only if A was false"
312 } else if ((p[0] == '|') && (p[1] == '|')) {
313 if (result == FALSE) {
314 evaluate_next_condition = evaluate_it;
316 evaluate_next_condition = FALSE;
320 } else if (*p == ')') {
321 DEBUG4(">>> CLOSING BRACE");
330 radlog(L_ERR, "Unexpected trailing text at: %s", p);
333 } /* else it wasn't an opening brace */
335 while ((*p == ' ') || (*p == '\t')) p++;
338 * More conditions, keep going.
340 if ((*p == '(') || (p[0] == '!')) continue;
342 DEBUG4(">>> LOOKING AT %s", p);
346 * Look for common errors.
348 if ((p[0] == '%') && (p[1] == '{')) {
349 radlog(L_ERR, "Bare %%{...} is invalid in condition at: %s", p);
354 * Look for word == value
356 lt = gettoken(&p, left, sizeof(left));
357 if ((lt != T_BARE_WORD) &&
358 (lt != T_DOUBLE_QUOTED_STRING) &&
359 (lt != T_SINGLE_QUOTED_STRING) &&
360 (lt != T_BACK_QUOTED_STRING)) {
361 radlog(L_ERR, "Expected string or numbers at: %s", p);
366 if (evaluate_next_condition) {
367 pleft = expand_string(xleft, sizeof(xleft), request,
370 radlog(L_ERR, "Failed expanding string at: %s",
377 * Peek ahead. Maybe it's just a check for
378 * existence. If so, there shouldn't be anything
382 while ((*q == ' ') || (*q == '\t')) q++;
387 if (!*q || (*q == ')') ||
388 ((*q == '!') && (q[1] != '=') && (q[1] != '~')) ||
389 ((q[0] == '&') && (q[1] == '&')) ||
390 ((q[0] == '|') && (q[1] == '|'))) {
392 * Check for truth or falsehood.
394 if (all_digits(pleft)) {
396 result = (lint != 0);
398 } else if (lt == T_BARE_WORD) {
399 result = (modreturn == fr_str2int(modreturn_table, pleft, -1));
401 result = (*pleft != '\0');
405 DEBUG4(">>> INVERTING result");
406 result = (result == FALSE);
410 if (evaluate_next_condition) {
411 DEBUG2("%.*s Evaluating %s\"%s\" -> %s",
413 invert ? "!" : "", pleft,
414 (result != FALSE) ? "TRUE" : "FALSE");
416 } else if (request) {
417 DEBUG2("%.*s Skipping %s\"%s\"",
419 invert ? "!" : "", pleft);
422 DEBUG4(">>> I%d %d:%s", invert,
424 goto end_of_condition;
428 * Else it's a full "foo == bar" thingy.
430 token = gettoken(&p, comp, sizeof(comp));
431 if ((token < T_OP_NE) || (token > T_OP_CMP_EQ) ||
432 (token == T_OP_CMP_TRUE) ||
433 (token == T_OP_CMP_FALSE)) {
434 radlog(L_ERR, "Expected comparison at: %s", comp);
439 * Look for common errors.
441 if ((p[0] == '%') && (p[1] == '{')) {
442 radlog(L_ERR, "Bare %%{...} is invalid in condition at: %s", p);
450 if ((token == T_OP_REG_EQ) ||
451 (token == T_OP_REG_NE)) {
452 rt = getregex(&p, right, sizeof(right), &cflags);
453 if (rt != T_DOUBLE_QUOTED_STRING) {
454 radlog(L_ERR, "Expected regular expression at: %s", p);
459 rt = gettoken(&p, right, sizeof(right));
461 if ((rt != T_BARE_WORD) &&
462 (rt != T_DOUBLE_QUOTED_STRING) &&
463 (rt != T_SINGLE_QUOTED_STRING) &&
464 (rt != T_BACK_QUOTED_STRING)) {
465 radlog(L_ERR, "Expected string or numbers at: %s", p);
470 if (evaluate_next_condition) {
471 pright = expand_string(xright, sizeof(xright), request,
474 radlog(L_ERR, "Failed expanding string at: %s",
480 DEBUG4(">>> %d:%s %d %d:%s",
481 lt, pleft, token, rt, pright);
483 if (evaluate_next_condition) {
488 if (!all_digits(pleft)) {
489 radlog(L_ERR, "Left field is not a number at: %s", pleft);
492 if (!all_digits(pright)) {
493 radlog(L_ERR, "Right field is not a number at: %s", pright);
506 result = (strcmp(pleft, pright) == 0);
510 result = (strcmp(pleft, pright) != 0);
514 result = (lint >= rint);
518 result = (lint > rint);
522 result = (lint <= rint);
526 result = (lint < rint);
533 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
536 * Include substring matches.
538 regcomp(®, pright, cflags);
539 compare = regexec(®, pleft,
540 REQUEST_MAX_REGEX + 1,
545 * Add new %{0}, %{1}, etc.
547 if (compare == 0) for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
551 free(request_data_get(request, request,
552 REQUEST_DATA_REGEX | i));
555 * We MAY have %{2} without %{1}.
557 if (rxmatch[i].rm_so == -1) continue;
560 * Copy substring into buffer.
562 memcpy(buffer, pleft + rxmatch[i].rm_so,
563 rxmatch[i].rm_eo - rxmatch[i].rm_so);
564 buffer[rxmatch[i].rm_eo - rxmatch[i].rm_so] = '\0';
567 * Copy substring, and add it to
570 * Note that we don't check
571 * for out of memory, which is
572 * the only error we can get...
575 request_data_add(request, request,
576 REQUEST_DATA_REGEX | i,
579 result = (compare == 0);
586 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
589 * Include substring matches.
591 regcomp(®, pright, cflags);
592 compare = regexec(®, pleft,
593 REQUEST_MAX_REGEX + 1,
597 result = (compare != 0);
603 DEBUG4(">>> NOT IMPLEMENTED %d", token);
607 DEBUG2("%.*s Evaluating %s(%.*s) -> %s",
609 invert ? "!" : "", p - start, start,
610 (result != FALSE) ? "TRUE" : "FALSE");
612 DEBUG4(">>> GOT result %d", result);
615 * Not evaluating it. We may be just
618 } else if (request) {
619 DEBUG2("%.*s Skipping %s(\"%s\" %s \"%s\")",
621 invert ? "!" : "", pleft, comp, pright);
626 DEBUG4(">>> INVERTING result");
627 result = (result == FALSE);
634 DEBUG4(">>> EVALUATE %d ::%s::",
635 evaluate_next_condition, p);
637 while ((*p == ' ') || (*p == '\t')) p++;
640 * Closing brace or EOL, return.
642 if (!*p || (*p == ')') ||
643 ((*p == '!') && (p[1] != '=') && (p[1] != '~')) ||
644 ((p[0] == '&') && (p[1] == '&')) ||
645 ((p[0] == '|') && (p[1] == '|'))) {
646 DEBUG4(">>> AT EOL2a");
648 if (evaluate_next_condition) *presult = result;
651 } /* loop over the input condition */
653 DEBUG4(">>> AT EOL2b");
655 if (evaluate_next_condition) *presult = result;
660 * The pairmove() function in src/lib/valuepair.c does all sorts of
661 * extra magic that we don't want here.
663 * FIXME: integrate this with the code calling it, so that we
664 * only paircopy() those attributes that we're really going to
667 static void my_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
669 int i, j, count, from_count, to_count, tailto;
670 VALUE_PAIR *vp, *next, **last;
671 VALUE_PAIR **from_list, **to_list;
675 * Set up arrays for editing, to remove some of the
676 * O(N^2) dependencies. This also makes it easier to
677 * insert and remove attributes.
679 * It also means that the operators apply ONLY to the
680 * attributes in the original list. With the previous
681 * implementation of pairmove(), adding two attributes
682 * via "+=" and then "=" would mean that the second one
683 * wasn't added, because of the existence of the first
684 * one in the "to" list. This implementation doesn't
687 * Also, the previous implementation did NOT implement
688 * "-=" correctly. If two of the same attributes existed
689 * in the "to" list, and you tried to subtract something
690 * matching the *second* value, then the pairdelete()
691 * function was called, and the *all* attributes of that
692 * number were deleted. With this implementation, only
693 * the matching attributes are deleted.
696 for (vp = from; vp != NULL; vp = vp->next) count++;
697 from_list = rad_malloc(sizeof(*from_list) * count);
699 for (vp = *to; vp != NULL; vp = vp->next) count++;
700 to_list = rad_malloc(sizeof(*to_list) * count);
703 * Move the lists to the arrays, and break the list
707 for (vp = from; vp != NULL; vp = next) {
709 from_list[from_count++] = vp;
714 for (vp = *to; vp != NULL; vp = next) {
716 to_list[to_count++] = vp;
720 edited = rad_malloc(sizeof(*edited) * to_count);
721 memset(edited, 0, sizeof(*edited) * to_count);
723 DEBUG4("::: FROM %d TO %d MAX %d", from_count, to_count, count);
726 * Now that we have the lists initialized, start working
729 for (i = 0; i < from_count; i++) {
732 DEBUG4("::: Examining %s", from_list[i]->name);
735 * Attribute should be appended, OR the "to" list
736 * is empty, and we're supposed to replace or
737 * "add if not existing".
739 if (from_list[i]->operator == T_OP_ADD) goto append;
742 for (j = 0; j < to_count; j++) {
743 if (edited[j]) continue;
746 * Attributes aren't the same, skip them.
748 if (from_list[i]->attribute != to_list[j]->attribute) {
753 * We don't use a "switch" statement here
754 * because we want to break out of the
755 * "for" loop over 'j' in most cases.
759 * Over-write the FIRST instance of the
760 * matching attribute name. We free the
761 * one in the "to" list, and move over
762 * the one in the "from" list.
764 if (from_list[i]->operator == T_OP_SET) {
765 DEBUG4("::: OVERWRITING %s FROM %d TO %d",
766 to_list[j]->name, i, j);
767 pairfree(&to_list[j]);
768 to_list[j] = from_list[i];
775 * Add the attribute only if it does not
776 * exist... but it exists, so we stop
779 if (from_list[i]->operator == T_OP_EQ) {
785 * Delete all matching attributes from
788 if ((from_list[i]->operator == T_OP_SUB) ||
789 (from_list[i]->operator == T_OP_CMP_EQ) ||
790 (from_list[i]->operator == T_OP_LE) ||
791 (from_list[i]->operator == T_OP_GE)) {
793 int old_op = from_list[i]->operator;
796 * Check for equality.
798 from_list[i]->operator = T_OP_CMP_EQ;
801 * If equal, delete the one in
804 rcode = radius_compare_vps(NULL, from_list[i],
807 * We may want to do more
808 * subtractions, so we re-set the
809 * operator back to it's original
812 from_list[i]->operator = old_op;
816 if (rcode != 0) goto delete;
822 DEBUG4("::: DELETING %s FROM %d TO %d",
823 from_list[i]->name, i, j);
824 pairfree(&to_list[j]);
830 * Enforce <=. If it's
835 DEBUG4("::: REPLACING %s FROM %d TO %d",
836 from_list[i]->name, i, j);
837 pairfree(&to_list[j]);
838 to_list[j] = from_list[i];
846 DEBUG4("::: REPLACING %s FROM %d TO %d",
847 from_list[i]->name, i, j);
848 pairfree(&to_list[j]);
849 to_list[j] = from_list[i];
859 rad_assert(0 == 1); /* panic! */
863 * We were asked to add it if it didn't exist,
864 * and it doesn't exist. Move it over to the
865 * tail of the "to" list, UNLESS it was already
866 * moved by another operator.
868 if (!found && from_list[i]) {
869 if ((from_list[i]->operator == T_OP_EQ) ||
870 (from_list[i]->operator == T_OP_LE) ||
871 (from_list[i]->operator == T_OP_GE) ||
872 (from_list[i]->operator == T_OP_SET)) {
874 DEBUG4("::: APPENDING %s FROM %d TO %d",
875 from_list[i]->name, i, tailto);
876 to_list[tailto++] = from_list[i];
883 * Delete attributes in the "from" list.
885 for (i = 0; i < from_count; i++) {
886 if (!from_list[i]) continue;
888 pairfree(&from_list[i]);
892 DEBUG4("::: TO in %d out %d", to_count, tailto);
895 * Re-chain the "to" list.
899 for (i = 0; i < tailto; i++) {
900 if (!to_list[i]) continue;
902 DEBUG4("::: to[%d] = %s", i, to_list[i]->name);
905 * Mash the operator to a simple '='. The
906 * operators in the "to" list aren't used for
907 * anything. BUT they're used in the "detail"
908 * file and debug output, where we don't want to
911 to_list[i]->operator = T_OP_EQ;
914 last = &(*last)->next;
917 * Fix dumb cache issues
919 if ((i >= to_count) || edited[i]) {
920 if (to_list[i]->attribute == PW_USER_NAME) {
921 request->username = to_list[i];
923 } else if (to_list[i]->attribute == PW_USER_PASSWORD) {
924 request->password = to_list[i];
934 * Copied shamelessly from conffile.c, to simplify the API for
937 typedef enum conf_type {
938 CONF_ITEM_INVALID = 0,
945 struct conf_item *next;
946 struct conf_part *parent;
948 const char *filename;
960 * Add attributes to a list.
962 int radius_update_attrlist(REQUEST *request, CONF_SECTION *cs,
963 VALUE_PAIR *input_vps, const char *name)
966 VALUE_PAIR *newlist, *vp;
967 VALUE_PAIR **output_vps = NULL;
968 REQUEST *request_vps = request;
970 if (!request || !cs) return RLM_MODULE_INVALID;
973 * If we are an inner tunnel session, permit the
974 * policy to update the outer lists directly.
976 if (memcmp(name, "outer.", 6) == 0) {
977 if (!request->parent) return RLM_MODULE_NOOP;
979 request_vps = request->parent;
983 if (strcmp(name, "request") == 0) {
984 output_vps = &request_vps->packet->vps;
986 } else if (strcmp(name, "reply") == 0) {
987 output_vps = &request_vps->reply->vps;
989 } else if (strcmp(name, "proxy-request") == 0) {
990 if (request->proxy) output_vps = &request_vps->proxy->vps;
992 } else if (strcmp(name, "proxy-reply") == 0) {
993 if (request->proxy_reply) output_vps = &request->proxy_reply->vps;
995 } else if (strcmp(name, "config") == 0) {
996 output_vps = &request_vps->config_items;
998 } else if (strcmp(name, "control") == 0) {
999 output_vps = &request_vps->config_items;
1002 return RLM_MODULE_INVALID;
1005 if (!output_vps) return RLM_MODULE_NOOP; /* didn't update the list */
1007 newlist = paircopy(input_vps);
1009 DEBUG2("Out of memory");
1010 return RLM_MODULE_FAIL;
1014 for (ci=cf_item_find_next(cs, NULL);
1016 ci=cf_item_find_next(cs, ci)) {
1020 * This should never happen.
1022 if (cf_item_is_section(ci)) {
1024 return RLM_MODULE_INVALID;
1027 cp = cf_itemtopair(ci);
1030 * The VP && CF lists should be in sync. If they're
1033 if (vp->flags.do_xlat) {
1037 value = expand_string(buffer, sizeof(buffer), request,
1038 cp->value_type, cp->value);
1041 return RLM_MODULE_INVALID;
1044 if (!pairparsevalue(vp, value)) {
1045 DEBUG2("ERROR: Failed parsing value \"%s\" for attribute %s: %s",
1046 value, vp->name, librad_errstr);
1048 return RLM_MODULE_FAIL;
1050 vp->flags.do_xlat = 0;
1055 my_pairmove(request, output_vps, newlist);
1057 return RLM_MODULE_UPDATED;