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)
53 static int all_digits(const char *string)
55 const char *p = string;
59 while (isdigit((int) *p)) p++;
64 static const char *filler = "????????????????????????????????????????????????????????????????";
66 static const char *expand_string(char *buffer, size_t sizeof_buffer,
68 FR_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++) {
98 case T_DOUBLE_QUOTED_STRING:
99 if (!strchr(value, '%')) return value;
101 radius_xlat(buffer, sizeof_buffer, value, request, NULL);
109 static FR_TOKEN getregex(const char **ptr, char *buffer, size_t buflen,
112 const char *p = *ptr;
115 if (*p != '/') return T_OP_INVALID;
117 *pcflags = REG_EXTENDED;
121 if (buflen <= 1) break;
127 * Check for case insensitivity
131 *pcflags |= REG_ICASE;
161 * FIXME: add 'x' and 'u'
165 if ((p[1] >= '0') && (p[1] <= '9') &&
166 (sscanf(p, "%3o", &x) == 1)) {
185 return T_DOUBLE_QUOTED_STRING;
189 static const FR_NAME_NUMBER modreturn_table[] = {
190 { "reject", RLM_MODULE_REJECT },
191 { "fail", RLM_MODULE_FAIL },
192 { "ok", RLM_MODULE_OK },
193 { "handled", RLM_MODULE_HANDLED },
194 { "invalid", RLM_MODULE_INVALID },
195 { "userlock", RLM_MODULE_USERLOCK },
196 { "notfound", RLM_MODULE_NOTFOUND },
197 { "noop", RLM_MODULE_NOOP },
198 { "updated", RLM_MODULE_UPDATED },
203 static int radius_get_vp(REQUEST *request, const char *name, VALUE_PAIR **vp_p)
206 const char *vp_name = name;
207 REQUEST *myrequest = request;
209 VALUE_PAIR *vps = NULL;
214 * Allow for tunneled sessions.
216 if (strncmp(vp_name, "outer.", 6) == 0) {
217 if (!myrequest->parent) return TRUE;
219 myrequest = myrequest->parent;
222 if (strncmp(vp_name, "request:", 8) == 0) {
224 vps = myrequest->packet->vps;
226 } else if (strncmp(vp_name, "reply:", 6) == 0) {
228 vps = myrequest->reply->vps;
231 } else if (strncmp(vp_name, "proxy-request:", 14) == 0) {
233 if (request->proxy) vps = myrequest->proxy->vps;
235 } else if (strncmp(vp_name, "proxy-reply:", 12) == 0) {
237 if (request->proxy_reply) vps = myrequest->proxy_reply->vps;
240 } else if (strncmp(vp_name, "config:", 7) == 0) {
242 vps = myrequest->config_items;
244 } else if (strncmp(vp_name, "control:", 8) == 0) {
246 vps = myrequest->config_items;
249 vps = myrequest->packet->vps;
252 da = dict_attrbyname(vp_name);
253 if (!da) return FALSE; /* not a dictionary name */
256 * May not may not be found, but it *is* a known name.
258 *vp_p = pairfind(vps, da->attr);
264 * *presult is "did comparison match or not"
266 static int radius_do_cmp(REQUEST *request, int *presult,
267 FR_TOKEN lt, const char *pleft, FR_TOKEN token,
268 FR_TOKEN rt, const char *pright,
269 int cflags, int modreturn)
273 VALUE_PAIR *vp = NULL;
277 cflags = cflags; /* -Wunused */
280 rt = rt; /* -Wunused */
282 if (lt == T_BARE_WORD) {
284 * Maybe check the last return code.
286 if (token == T_OP_CMP_TRUE) {
290 * Looks like a return code, treat is as such.
292 isreturn = fr_str2int(modreturn_table, pleft, -1);
293 if (isreturn != -1) {
294 *presult = (modreturn == isreturn);
300 * Bare words on the left can be attribute names.
302 if (radius_get_vp(request, pleft, &vp)) {
306 * VP exists, and that's all we're looking for.
308 if (token == T_OP_CMP_TRUE) {
309 *presult = (vp != NULL);
317 * The attribute on the LHS may
318 * have been a dynamically
319 * registered callback. i.e. it
320 * doesn't exist as a VALUE_PAIR.
321 * If so, try looking for it.
323 da = dict_attrbyname(pleft);
324 if (da && radius_find_compare(da->attr)) {
325 VALUE_PAIR *check = pairmake(pleft, pright, token);
326 *presult = (radius_callback_compare(request, NULL, check, NULL, NULL) == 0);
328 if (*presult) return TRUE;
332 RDEBUG2(" (Attribute %s was not found)",
339 * Regex comparisons treat everything as
342 if ((token == T_OP_REG_EQ) ||
343 (token == T_OP_REG_NE)) {
344 vp_prints_value(buffer, sizeof(buffer), vp, 0);
350 memcpy(&myvp, vp, sizeof(myvp));
351 if (!pairparsevalue(&myvp, pright)) {
352 RDEBUG2("Failed parsing \"%s\": %s",
353 pright, fr_strerror());
357 myvp.operator = token;
358 *presult = paircmp(&myvp, vp);
360 } /* else it's not a VP in a list */
371 if (!all_digits(pright)) {
372 RDEBUG2(" (Right field is not a number at: %s)", pright);
376 if (!all_digits(pleft)) {
377 RDEBUG2(" (Left field is not a number at: %s)", pleft);
384 lint = rint = 0; /* quiet the compiler */
391 * Check for truth or falsehood.
393 if (all_digits(pleft)) {
395 result = (lint != 0);
398 result = (*pleft != '\0');
404 result = (strcmp(pleft, pright) == 0);
408 result = (strcmp(pleft, pright) != 0);
412 result = (lint >= rint);
416 result = (lint > rint);
420 result = (lint <= rint);
424 result = (lint < rint);
431 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
434 * Include substring matches.
436 regcomp(®, pright, cflags);
437 compare = regexec(®, pleft,
438 REQUEST_MAX_REGEX + 1,
443 * Add new %{0}, %{1}, etc.
445 if (compare == 0) for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
448 free(request_data_get(request, request,
449 REQUEST_DATA_REGEX | i));
453 * We MAY have %{2} without %{1}.
455 if (rxmatch[i].rm_so == -1) continue;
458 * Copy substring into allocated buffer
460 r = rad_malloc(rxmatch[i].rm_eo -rxmatch[i].rm_so + 1);
461 memcpy(r, pleft + rxmatch[i].rm_so,
462 rxmatch[i].rm_eo - rxmatch[i].rm_so);
463 r[rxmatch[i].rm_eo - rxmatch[i].rm_so] = '\0';
465 request_data_add(request, request,
466 REQUEST_DATA_REGEX | i,
469 result = (compare == 0);
476 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
479 * Include substring matches.
481 regcomp(®, pright, cflags);
482 compare = regexec(®, pleft,
483 REQUEST_MAX_REGEX + 1,
487 result = (compare != 0);
493 RDEBUG4(">>> NOT IMPLEMENTED %d", token);
502 int radius_evaluate_condition(REQUEST *request, int modreturn, int depth,
503 const char **ptr, int evaluate_it, int *presult)
505 int found_condition = FALSE;
508 int evaluate_next_condition = evaluate_it;
509 const char *p = *ptr;
510 const char *q, *start;
511 FR_TOKEN token, lt, rt;
512 char left[1024], right[1024], comp[4];
513 const char *pleft, *pright;
514 char xleft[1024], xright[1024];
517 if (!ptr || !*ptr || (depth >= 64)) {
518 radlog(L_ERR, "Internal sanity check failed in evaluate condition");
523 while ((*p == ' ') || (*p == '\t')) p++;
526 RDEBUG4(">>> INVERT");
532 * It's a subcondition.
535 const char *end = p + 1;
538 * Evaluate the condition, bailing out on
541 RDEBUG4(">>> CALLING EVALUATE %s", end);
542 if (!radius_evaluate_condition(request, modreturn,
544 evaluate_next_condition,
549 if (invert && evaluate_next_condition) {
550 RDEBUG2("%.*s Converting !%s -> %s",
552 (result != FALSE) ? "TRUE" : "FALSE",
553 (result == FALSE) ? "TRUE" : "FALSE");
556 result = (result == FALSE);
561 * Start from the end of the previous
565 RDEBUG4(">>> EVALUATE RETURNED ::%s::", end);
567 if (!((*p == ')') || (*p == '!') ||
568 ((p[0] == '&') && (p[1] == '&')) ||
569 ((p[0] == '|') && (p[1] == '|')))) {
571 radlog(L_ERR, "Parse error in condition at: %s", p);
574 if (*p == ')') p++; /* skip it */
575 found_condition = TRUE;
577 while ((*p == ' ') || (*p == '\t')) p++;
583 RDEBUG4(">>> AT EOL");
589 * (A && B) means "evaluate B
590 * only if A was true"
592 } else if ((p[0] == '&') && (p[1] == '&')) {
593 if (result == TRUE) {
594 evaluate_next_condition = evaluate_it;
596 evaluate_next_condition = FALSE;
601 * (A || B) means "evaluate B
602 * only if A was false"
604 } else if ((p[0] == '|') && (p[1] == '|')) {
605 if (result == FALSE) {
606 evaluate_next_condition = evaluate_it;
608 evaluate_next_condition = FALSE;
612 } else if (*p == ')') {
613 RDEBUG4(">>> CLOSING BRACE");
622 radlog(L_ERR, "Unexpected trailing text at: %s", p);
625 } /* else it wasn't an opening brace */
627 while ((*p == ' ') || (*p == '\t')) p++;
630 * More conditions, keep going.
632 if ((*p == '(') || (p[0] == '!')) continue;
634 RDEBUG4(">>> LOOKING AT %s", p);
638 * Look for common errors.
640 if ((p[0] == '%') && (p[1] == '{')) {
641 radlog(L_ERR, "Bare %%{...} is invalid in condition at: %s", p);
646 * Look for word == value
648 lt = gettoken(&p, left, sizeof(left));
649 if ((lt != T_BARE_WORD) &&
650 (lt != T_DOUBLE_QUOTED_STRING) &&
651 (lt != T_SINGLE_QUOTED_STRING) &&
652 (lt != T_BACK_QUOTED_STRING)) {
653 radlog(L_ERR, "Expected string or numbers at: %s", p);
658 if (evaluate_next_condition) {
659 pleft = expand_string(xleft, sizeof(xleft), request,
662 radlog(L_ERR, "Failed expanding string at: %s",
669 * Peek ahead. Maybe it's just a check for
670 * existence. If so, there shouldn't be anything
674 while ((*q == ' ') || (*q == '\t')) q++;
679 if (!*q || (*q == ')') ||
680 ((*q == '!') && (q[1] != '=') && (q[1] != '~')) ||
681 ((q[0] == '&') && (q[1] == '&')) ||
682 ((q[0] == '|') && (q[1] == '|'))) {
686 token = T_OP_CMP_TRUE;
693 * Else it's a full "foo == bar" thingy.
695 token = gettoken(&p, comp, sizeof(comp));
696 if ((token < T_OP_NE) || (token > T_OP_CMP_EQ) ||
697 (token == T_OP_CMP_TRUE) ||
698 (token == T_OP_CMP_FALSE)) {
699 radlog(L_ERR, "Expected comparison at: %s", comp);
704 * Look for common errors.
706 if ((p[0] == '%') && (p[1] == '{')) {
707 radlog(L_ERR, "Bare %%{...} is invalid in condition at: %s", p);
715 if ((token == T_OP_REG_EQ) ||
716 (token == T_OP_REG_NE)) {
717 rt = getregex(&p, right, sizeof(right), &cflags);
718 if (rt != T_DOUBLE_QUOTED_STRING) {
719 radlog(L_ERR, "Expected regular expression at: %s", p);
724 rt = gettoken(&p, right, sizeof(right));
726 if ((rt != T_BARE_WORD) &&
727 (rt != T_DOUBLE_QUOTED_STRING) &&
728 (rt != T_SINGLE_QUOTED_STRING) &&
729 (rt != T_BACK_QUOTED_STRING)) {
730 radlog(L_ERR, "Expected string or numbers at: %s", p);
735 if (evaluate_next_condition) {
736 pright = expand_string(xright, sizeof(xright), request,
739 radlog(L_ERR, "Failed expanding string at: %s",
745 RDEBUG4(">>> %d:%s %d %d:%s",
746 lt, pleft, token, rt, pright);
749 if (evaluate_next_condition) {
753 if (!radius_do_cmp(request, &result, lt, pleft, token,
754 rt, pright, cflags, modreturn)) {
758 RDEBUG2("%.*s Evaluating %s(%.*s) -> %s",
760 invert ? "!" : "", p - start, start,
761 (result != FALSE) ? "TRUE" : "FALSE");
763 RDEBUG4(">>> GOT result %d", result);
766 * Not evaluating it. We may be just
769 } else if (request) {
770 RDEBUG2("%.*s Skipping %s(%.*s)",
772 invert ? "!" : "", p - start, start);
776 RDEBUG4(">>> INVERTING result");
777 result = (result == FALSE);
784 RDEBUG4(">>> EVALUATE %d ::%s::",
785 evaluate_next_condition, p);
787 while ((*p == ' ') || (*p == '\t')) p++;
790 * Closing brace or EOL, return.
792 if (!*p || (*p == ')') ||
793 ((*p == '!') && (p[1] != '=') && (p[1] != '~')) ||
794 ((p[0] == '&') && (p[1] == '&')) ||
795 ((p[0] == '|') && (p[1] == '|'))) {
796 RDEBUG4(">>> AT EOL2a");
798 if (evaluate_next_condition) *presult = result;
801 } /* loop over the input condition */
803 RDEBUG4(">>> AT EOL2b");
805 if (evaluate_next_condition) *presult = result;
810 static void fix_up(REQUEST *request)
814 request->username = NULL;
815 request->password = NULL;
817 for (vp = request->packet->vps; vp != NULL; vp = vp->next) {
818 if ((vp->attribute == PW_USER_NAME) &&
819 !request->username) {
820 request->username = vp;
822 } else if (vp->attribute == PW_STRIPPED_USER_NAME) {
823 request->username = vp;
825 } else if (vp->attribute == PW_USER_PASSWORD) {
826 request->password = vp;
832 * The pairmove() function in src/lib/valuepair.c does all sorts of
833 * extra magic that we don't want here.
835 * FIXME: integrate this with the code calling it, so that we
836 * only paircopy() those attributes that we're really going to
839 void radius_pairmove(REQUEST *request, VALUE_PAIR **to, VALUE_PAIR *from)
841 int i, j, count, from_count, to_count, tailto;
842 VALUE_PAIR *vp, *next, **last;
843 VALUE_PAIR **from_list, **to_list;
847 * Set up arrays for editing, to remove some of the
848 * O(N^2) dependencies. This also makes it easier to
849 * insert and remove attributes.
851 * It also means that the operators apply ONLY to the
852 * attributes in the original list. With the previous
853 * implementation of pairmove(), adding two attributes
854 * via "+=" and then "=" would mean that the second one
855 * wasn't added, because of the existence of the first
856 * one in the "to" list. This implementation doesn't
859 * Also, the previous implementation did NOT implement
860 * "-=" correctly. If two of the same attributes existed
861 * in the "to" list, and you tried to subtract something
862 * matching the *second* value, then the pairdelete()
863 * function was called, and the *all* attributes of that
864 * number were deleted. With this implementation, only
865 * the matching attributes are deleted.
868 for (vp = from; vp != NULL; vp = vp->next) count++;
869 from_list = rad_malloc(sizeof(*from_list) * count);
871 for (vp = *to; vp != NULL; vp = vp->next) count++;
872 to_list = rad_malloc(sizeof(*to_list) * count);
875 * Move the lists to the arrays, and break the list
879 for (vp = from; vp != NULL; vp = next) {
881 from_list[from_count++] = vp;
886 for (vp = *to; vp != NULL; vp = next) {
888 to_list[to_count++] = vp;
892 edited = rad_malloc(sizeof(*edited) * to_count);
893 memset(edited, 0, sizeof(*edited) * to_count);
895 RDEBUG4("::: FROM %d TO %d MAX %d", from_count, to_count, count);
898 * Now that we have the lists initialized, start working
901 for (i = 0; i < from_count; i++) {
904 RDEBUG4("::: Examining %s", from_list[i]->name);
907 * Attribute should be appended, OR the "to" list
908 * is empty, and we're supposed to replace or
909 * "add if not existing".
911 if (from_list[i]->operator == T_OP_ADD) goto append;
914 for (j = 0; j < to_count; j++) {
915 if (edited[j]) continue;
918 * Attributes aren't the same, skip them.
920 if (from_list[i]->attribute != to_list[j]->attribute) {
925 * We don't use a "switch" statement here
926 * because we want to break out of the
927 * "for" loop over 'j' in most cases.
931 * Over-write the FIRST instance of the
932 * matching attribute name. We free the
933 * one in the "to" list, and move over
934 * the one in the "from" list.
936 if (from_list[i]->operator == T_OP_SET) {
937 RDEBUG4("::: OVERWRITING %s FROM %d TO %d",
938 to_list[j]->name, i, j);
939 pairfree(&to_list[j]);
940 to_list[j] = from_list[i];
947 * Add the attribute only if it does not
948 * exist... but it exists, so we stop
951 if (from_list[i]->operator == T_OP_EQ) {
957 * Delete all matching attributes from
960 if ((from_list[i]->operator == T_OP_SUB) ||
961 (from_list[i]->operator == T_OP_CMP_EQ) ||
962 (from_list[i]->operator == T_OP_LE) ||
963 (from_list[i]->operator == T_OP_GE)) {
965 int old_op = from_list[i]->operator;
968 * Check for equality.
970 from_list[i]->operator = T_OP_CMP_EQ;
973 * If equal, delete the one in
976 rcode = radius_compare_vps(NULL, from_list[i],
979 * We may want to do more
980 * subtractions, so we re-set the
981 * operator back to it's original
984 from_list[i]->operator = old_op;
988 if (rcode != 0) goto delete;
994 RDEBUG4("::: DELETING %s FROM %d TO %d",
995 from_list[i]->name, i, j);
996 pairfree(&to_list[j]);
1002 * Enforce <=. If it's
1007 RDEBUG4("::: REPLACING %s FROM %d TO %d",
1008 from_list[i]->name, i, j);
1009 pairfree(&to_list[j]);
1010 to_list[j] = from_list[i];
1011 from_list[i] = NULL;
1018 RDEBUG4("::: REPLACING %s FROM %d TO %d",
1019 from_list[i]->name, i, j);
1020 pairfree(&to_list[j]);
1021 to_list[j] = from_list[i];
1022 from_list[i] = NULL;
1031 rad_assert(0 == 1); /* panic! */
1035 * We were asked to add it if it didn't exist,
1036 * and it doesn't exist. Move it over to the
1037 * tail of the "to" list, UNLESS it was already
1038 * moved by another operator.
1040 if (!found && from_list[i]) {
1041 if ((from_list[i]->operator == T_OP_EQ) ||
1042 (from_list[i]->operator == T_OP_LE) ||
1043 (from_list[i]->operator == T_OP_GE) ||
1044 (from_list[i]->operator == T_OP_SET)) {
1046 RDEBUG4("::: APPENDING %s FROM %d TO %d",
1047 from_list[i]->name, i, tailto);
1048 to_list[tailto++] = from_list[i];
1049 from_list[i] = NULL;
1055 * Delete attributes in the "from" list.
1057 for (i = 0; i < from_count; i++) {
1058 if (!from_list[i]) continue;
1060 pairfree(&from_list[i]);
1064 RDEBUG4("::: TO in %d out %d", to_count, tailto);
1067 * Re-chain the "to" list.
1072 for (i = 0; i < tailto; i++) {
1073 if (!to_list[i]) continue;
1075 RDEBUG4("::: to[%d] = %s", i, to_list[i]->name);
1078 * Mash the operator to a simple '='. The
1079 * operators in the "to" list aren't used for
1080 * anything. BUT they're used in the "detail"
1081 * file and debug output, where we don't want to
1082 * see the operators.
1084 to_list[i]->operator = T_OP_EQ;
1087 last = &(*last)->next;
1091 * Fix dumb cache issues
1093 if (to == &request->packet->vps) {
1095 } else if (request->parent && (to == &request->parent->packet->vps)) {
1096 fix_up(request->parent);
1106 * Copied shamelessly from conffile.c, to simplify the API for
1109 typedef enum conf_type {
1110 CONF_ITEM_INVALID = 0,
1117 struct conf_item *next;
1118 struct conf_part *parent;
1120 const char *filename;
1121 CONF_ITEM_TYPE type;
1128 FR_TOKEN value_type;
1132 * Add attributes to a list.
1134 int radius_update_attrlist(REQUEST *request, CONF_SECTION *cs,
1135 VALUE_PAIR *input_vps, const char *name)
1138 VALUE_PAIR *newlist, *vp;
1139 VALUE_PAIR **output_vps = NULL;
1140 REQUEST *request_vps = request;
1142 if (!request || !cs) return RLM_MODULE_INVALID;
1145 * If we are an inner tunnel session, permit the
1146 * policy to update the outer lists directly.
1148 if (strncmp(name, "outer.", 6) == 0) {
1149 if (!request->parent) return RLM_MODULE_NOOP;
1151 request_vps = request->parent;
1155 if (strcmp(name, "request") == 0) {
1156 output_vps = &request_vps->packet->vps;
1158 } else if (strcmp(name, "reply") == 0) {
1159 output_vps = &request_vps->reply->vps;
1162 } else if (strcmp(name, "proxy-request") == 0) {
1163 if (request->proxy) output_vps = &request_vps->proxy->vps;
1165 } else if (strcmp(name, "proxy-reply") == 0) {
1166 if (request->proxy_reply) output_vps = &request->proxy_reply->vps;
1169 } else if (strcmp(name, "config") == 0) {
1170 output_vps = &request_vps->config_items;
1172 } else if (strcmp(name, "control") == 0) {
1173 output_vps = &request_vps->config_items;
1176 return RLM_MODULE_INVALID;
1179 if (!output_vps) return RLM_MODULE_NOOP; /* didn't update the list */
1181 newlist = paircopy(input_vps);
1183 RDEBUG2("Out of memory");
1184 return RLM_MODULE_FAIL;
1188 for (ci=cf_item_find_next(cs, NULL);
1190 ci=cf_item_find_next(cs, ci)) {
1194 * This should never happen.
1196 if (cf_item_is_section(ci)) {
1198 return RLM_MODULE_INVALID;
1201 cp = cf_itemtopair(ci);
1204 * The VP && CF lists should be in sync. If they're
1207 if (vp->flags.do_xlat) {
1211 value = expand_string(buffer, sizeof(buffer), request,
1212 cp->value_type, cp->value);
1215 return RLM_MODULE_INVALID;
1218 if (!pairparsevalue(vp, value)) {
1219 RDEBUG2("ERROR: Failed parsing value \"%s\" for attribute %s: %s",
1220 value, vp->name, fr_strerror());
1222 return RLM_MODULE_FAIL;
1224 vp->flags.do_xlat = 0;
1229 radius_pairmove(request, output_vps, newlist);
1231 return RLM_MODULE_UPDATED;