Minor cleanups
[freeradius.git] / src / main / evaluate.c
1 /*
2  * evaluate.c   Evaluate complex conditions
3  *
4  * Version:     $Id$
5  *
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.
10  *
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.
15  *
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
19  *
20  * Copyright 2007  The FreeRADIUS server project
21  * Copyright 2007  Alan DeKok <aland@deployingradius.com>
22  */
23
24 #include <freeradius-devel/ident.h>
25 RCSID("$Id$")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29 #include <freeradius-devel/rad_assert.h>
30
31 #include <ctype.h>
32
33 #ifdef HAVE_REGEX_H
34 #include <regex.h>
35
36 /*
37  *  For POSIX Regular expressions.
38  *  (0) Means no extended regular expressions.
39  *  REG_EXTENDED means use extended regular expressions.
40  */
41 #ifndef REG_EXTENDED
42 #define REG_EXTENDED (0)
43 #endif
44
45 #ifndef REG_NOSUB
46 #define REG_NOSUB (0)
47 #endif
48 #endif
49
50
51 static int all_digits(const char *string)
52 {
53         const char *p = string;
54
55         if (*p == '-') p++;
56
57         while (isdigit((int) *p)) p++;
58
59         return (*p == '\0');
60 }
61
62 #ifndef DEBUG4
63 #define DEBUG4  if (debug_flag > 4)log_debug
64 #endif
65
66 static const char *filler = "????????????????????????????????????????????????????????????????";
67
68 static const char *expand_string(char *buffer, size_t sizeof_buffer,
69                                  REQUEST *request,
70                                  LRAD_TOKEN value_type, const char *value)
71 {
72         int result;
73         char *p;
74
75         switch (value_type) {
76         default:
77         case T_BARE_WORD:
78         case T_SINGLE_QUOTED_STRING:
79                 return value;
80
81         case T_BACK_QUOTED_STRING:
82                 result = radius_exec_program(value, request, 1,
83                                              buffer, sizeof_buffer, NULL,
84                                              NULL, 0);
85                 if (result != 0) {
86                         return NULL;
87                 }
88
89                 /*
90                  *      The result should be ASCII.
91                  */
92                 for (p = buffer; *p != '\0'; p++) {
93                         if (*p < ' ' ) {
94                                 *p = '\0';
95                                 return buffer;
96                         }
97                 }
98                 return buffer;
99
100
101         case T_DOUBLE_QUOTED_STRING:
102                 if (!strchr(value, '%')) return value;
103
104                 radius_xlat(buffer, sizeof_buffer, value, request, NULL);
105                 return buffer;
106         }
107
108         return NULL;
109 }
110
111 static LRAD_TOKEN getregex(char **ptr, char *buffer, size_t buflen)
112 {
113         char *p = *ptr;
114         char *q = buffer;
115
116         if (*p != '/') return T_OP_INVALID;
117
118         p++;
119         while (*p) {
120                 if (buflen <= 1) break;
121
122                 if (*p == '/') {
123                         p++;
124                         break;
125                 }
126
127                 if (*p == '\\') {
128                         int x;
129                         
130                         switch (p[1]) {
131                         case 'r':
132                                 *q++ = '\r';
133                                 break;
134                         case 'n':
135                                 *q++ = '\n';
136                                 break;
137                         case 't':
138                                 *q++ = '\t';
139                                 break;
140                         case '"':
141                                 *q++ = '"';
142                                 break;
143                         case '\'':
144                                 *q++ = '\'';
145                                 break;
146                         case '`':
147                                 *q++ = '`';
148                                 break;
149                                 
150                                 /*
151                                  *      FIXME: add 'x' and 'u'
152                                  */
153
154                         default:
155                                 if ((p[1] >= '0') && (p[1] <= '9') &&
156                                     (sscanf(p, "%3o", &x) == 1)) {
157                                         *q++ = x;
158                                         p += 2;
159                                 } else {
160                                         *q++ = p[1];
161                                 }
162                                 break;
163                         }
164                         p += 2;
165                         buflen--;
166                         continue;
167                 }
168
169                 *(q++) = *(p++);
170                 buflen--;
171         }
172         *q = '\0';
173         *ptr = p;
174
175         return T_BARE_WORD;
176 }
177
178 int radius_evaluate_condition(REQUEST *request, int depth,
179                               const char **ptr, int evaluate_it, int *presult)
180 {
181         int found_condition = FALSE;
182         int result = TRUE;
183         int invert = 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];
191         int lint, rint;
192
193         if (!ptr || !*ptr || (depth >= 64)) {
194                 radlog(L_ERR, "Internal sanity check failed in evaluate condition");
195                 return FALSE;
196         }
197
198         while (*p) {
199                 while ((*p == ' ') || (*p == '\t')) p++;
200
201                 if (*p == '!') {
202                         DEBUG4(">>> INVERT");
203                         invert = TRUE;
204                         p++;
205                 }
206
207                 /*
208                  *      It's a subcondition.
209                  */
210                 if (*p == '(') {
211                         const char *end = p + 1;
212
213                         /*
214                          *      Evaluate the condition, bailing out on
215                          *      parse error.
216                          */
217                         DEBUG4(">>> CALLING EVALUATE %s", end);
218                         if (!radius_evaluate_condition(request, depth + 1,
219                                                        &end,
220                                                        evaluate_next_condition,
221                                                        &result)) {
222                                 return FALSE;
223                         }
224
225                         if (invert && evaluate_next_condition) {
226                                 DEBUG2("%.*s Converting !%s -> %s",
227                                        depth, filler,
228                                        (result != FALSE) ? "TRUE" : "FALSE",
229                                        (result == FALSE) ? "TRUE" : "FALSE");
230
231                                        
232                                 result = (result == FALSE);
233                                 invert = FALSE;
234                         }
235
236                         /*
237                          *      Start from the end of the previous
238                          *      condition
239                          */
240                         p = end;
241                         DEBUG4(">>> EVALUATE RETURNED ::%s::", end);
242                         
243                         if (!((*p == ')') || (*p == '!') ||
244                               ((p[0] == '&') && (p[1] == '&')) ||
245                               ((p[0] == '|') && (p[1] == '|')))) {
246
247                                 radlog(L_ERR, "Parse error in condition at: %s", p);
248                                 return FALSE;
249                         }
250                         if (*p == ')') p++;     /* skip it */
251                         found_condition = TRUE;
252                         
253                         while ((*p == ' ') || (*p == '\t')) p++;
254
255                         /*
256                          *      EOL.  It's OK.
257                          */
258                         if (!*p) {
259                                 DEBUG4(">>> AT EOL");
260                                 *ptr = p;
261                                 *presult = result;
262                                 return TRUE;
263                                 
264                                 /*
265                                  *      (A && B) means "evaluate B
266                                  *      only if A was true"
267                                  */
268                         } else if ((p[0] == '&') && (p[1] == '&')) {
269                                 if (result == TRUE) {
270                                         evaluate_next_condition = evaluate_it;
271                                 } else {
272                                         evaluate_next_condition = FALSE;
273                                 }
274                                 p += 2;
275                                 
276                                 /*
277                                  *      (A || B) means "evaluate B
278                                  *      only if A was false"
279                                  */
280                         } else if ((p[0] == '|') && (p[1] == '|')) {
281                                 if (result == FALSE) {
282                                         evaluate_next_condition = evaluate_it;
283                                 } else {
284                                         evaluate_next_condition = FALSE;
285                                 }
286                                 p += 2;
287
288                         } else if (*p == ')') {
289                                 DEBUG4(">>> CLOSING BRACE");
290                                 *ptr = p;
291                                 *presult = result;
292                                 return TRUE;
293
294                         } else {
295                                 /*
296                                  *      Parse error
297                                  */
298                                 radlog(L_ERR, "Unexpected trailing text at: %s", p);
299                                 return FALSE;
300                         }
301                 } /* else it wasn't an opening brace */
302
303                 while ((*p == ' ') || (*p == '\t')) p++;
304
305                 /*
306                  *      More conditions, keep going.
307                  */
308                 if ((*p == '(') || (p[0] == '!')) continue;
309
310                 DEBUG4(">>> LOOKING AT %s", p);
311                 start = p;
312
313                 /*
314                  *      Look for common errors.
315                  */
316                 if ((p[0] == '%') && (p[1] == '{')) {
317                         radlog(L_ERR, "Bare %%{...} is invalid in condition at: %s", p);
318                         return FALSE;
319                 }
320
321                 /*
322                  *      Look for word == value
323                  */
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);
330                         return FALSE;
331                 }
332
333                 pleft = left;
334                 if (evaluate_next_condition) {
335                         pleft = expand_string(xleft, sizeof(xleft), request,
336                                               lt, left);
337                         if (!pleft) {
338                                 radlog(L_ERR, "Failed expanding string at: %s",
339                                        left);
340                                 return FALSE;
341                         }
342                 }
343
344                 /*
345                  *      Peek ahead.  Maybe it's just a check for
346                  *      existence.  If so, there shouldn't be anything
347                  *      else.
348                  */
349                 q = p;
350                 while ((*q == ' ') || (*q == '\t')) q++;
351
352                 /*
353                  *      End of condition, 
354                  */
355                 if (!*q || (*q == ')') || (*q == '!') ||
356                     ((q[0] == '&') && (q[1] == '&')) ||
357                     ((q[0] == '|') && (q[1] == '|'))) {
358                         /*
359                          *      Check for truth or falsehood.
360                          */
361                         if (all_digits(pleft)) {
362                                 lint = atoi(pleft);
363                                 result = (lint != 0);
364                         } else {
365                                 result = (*pleft != '\0');
366                         }
367
368                         if (invert) {
369                                 DEBUG4(">>> INVERTING result");
370                                 result = (result == FALSE);
371                                 invert = FALSE;
372                         }
373
374                         if (evaluate_next_condition) {
375                                 DEBUG2("%.*s Evaluating %s\"%s\" -> %s",
376                                        depth, filler,
377                                        invert ? "!" : "", pleft,
378                                        (result != FALSE) ? "TRUE" : "FALSE");
379
380                         } else if (request) {
381                                 DEBUG2("%.*s Skipping %s\"%s\"",
382                                        depth, filler,
383                                        invert ? "!" : "", pleft);
384                         }
385
386                         DEBUG4(">>> I%d %d:%s", invert,
387                                lt, left);
388                         goto end_of_condition;
389                 }
390
391                 /*
392                  *      Else it's a full "foo == bar" thingy.
393                  */
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);
399                         return FALSE;
400                 }
401                 
402                 /*
403                  *      Look for common errors.
404                  */
405                 if ((p[0] == '%') && (p[1] == '{')) {
406                         radlog(L_ERR, "Bare %%{...} is invalid in condition at: %s", p);
407                         return FALSE;
408                 }
409                 
410                 /*
411                  *      Validate strings.
412                  */
413                 if ((token == T_OP_REG_EQ) ||
414                     (token == T_OP_REG_NE)) {
415                         rt = getregex(&p, right, sizeof(right));
416                 } else {
417                         rt = gettoken(&p, right, sizeof(right));
418                 }
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);
424                         return FALSE;
425                 }
426                 
427                 pright = right;
428                 if (evaluate_next_condition) {
429                         pright = expand_string(xright, sizeof(xright), request,
430                                                rt, right);
431                         if (!pright) {
432                                 radlog(L_ERR, "Failed expanding string at: %s",
433                                        right);
434                                 return FALSE;
435                         }
436                 }
437                 
438                 DEBUG4(">>> %d:%s %d %d:%s",
439                        lt, pleft, token, rt, pright);
440                 
441                 if (evaluate_next_condition) {
442                         /*
443                          *      Mangle operator && conditions to
444                          *      simplify the following code.
445                          */
446                         switch (token) {
447                         case T_OP_NE:
448                                 invert = (invert == FALSE);
449                                 token = T_OP_CMP_EQ;
450                                 break;
451                                 
452                         case T_OP_GE:
453                         case T_OP_GT:
454                         case T_OP_LE:
455                         case T_OP_LT:
456                                 if (!all_digits(pleft)) {
457                                         radlog(L_ERR, "Left field is not a number at: %s", pleft);
458                                         return FALSE;
459                                 }
460                                 if (!all_digits(pright)) {
461                                         radlog(L_ERR, "Right field is not a number at: %s", pright);
462                                         return FALSE;
463                                 }
464                                 lint = atoi(pleft);
465                                 rint = atoi(pright);
466                                 break;
467                                 
468                         default:
469                                 break;
470                         }
471
472                         switch (token) {
473                         case T_OP_CMP_EQ:
474                                 result = (strcmp(pleft, pright) == 0);
475                                 break;
476
477                         case T_OP_GE:
478                                 result = (lint >= rint);
479                                 break;
480
481                         case T_OP_GT:
482                                 result = (lint > rint);
483                                 break;
484
485                         case T_OP_LE:
486                                 result = (lint <= rint);
487                                 break;
488
489                         case T_OP_LT:
490                                 result = (lint < rint);
491                                 break;
492
493 #ifdef HAVE_REGEX_H
494                         case T_OP_REG_EQ: {
495                                 int i, compare;
496                                 regex_t reg;
497                                 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
498                                 
499                                 /*
500                                  *      Include substring matches.
501                                  */
502                                 regcomp(&reg, pright, REG_EXTENDED);
503                                 compare = regexec(&reg, pleft,
504                                                   REQUEST_MAX_REGEX + 1,
505                                                   rxmatch, 0);
506                                 regfree(&reg);
507                                 
508                                 /*
509                                  *      Add %{0}, %{1}, etc.
510                                  */
511                                 for (i = 0; i <= REQUEST_MAX_REGEX; i++) {
512                                         char *r;
513                                         char buffer[1024];
514                                         
515                                         /*
516                                          *      Didn't match: delete old
517                                          *      match, if it existed.
518                                          */
519                                         if ((compare != 0) ||
520                                             (rxmatch[i].rm_so == -1)) {
521                                                 r = request_data_get(request,
522                                                                      request,
523                                                                      REQUEST_DATA_REGEX | i);
524                                                 if (r) {
525                                                         free(r);
526                                                         continue;
527                                                 }
528                                                 
529                                                 /*
530                                                  *      No previous match
531                                                  *      to delete, stop.
532                                                  */
533                                                 break;
534                                         }
535                                         
536                                         /*
537                                          *      Copy substring into buffer.
538                                          */
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';
542                                         
543                                         /*
544                                          *      Copy substring, and add it to
545                                          *      the request.
546                                          *
547                                          *      Note that we don't check
548                                          *      for out of memory, which is
549                                          *      the only error we can get...
550                                          */
551                                         r = strdup(buffer);
552                                         request_data_add(request, request,
553                                                          REQUEST_DATA_REGEX | i,
554                                                          r, free);
555                                 }
556                                 result = (compare == 0);
557                         }
558                                 break;
559
560                         case T_OP_REG_NE: {
561                                 int compare;
562                                 regex_t reg;
563                                 regmatch_t rxmatch[REQUEST_MAX_REGEX + 1];
564                                 
565                                 /*
566                                  *      Include substring matches.
567                                  */
568                                 regcomp(&reg, pright,
569                                         REG_EXTENDED);
570                                 compare = regexec(&reg, pleft,
571                                                   REQUEST_MAX_REGEX + 1,
572                                                   rxmatch, 0);
573                                 regfree(&reg);
574                                 
575                                 result = (compare != 0);
576                         }
577                                 break;
578 #endif
579
580                         default:
581                                 DEBUG4(">>> NOT IMPLEMENTED %d", token);
582                                 break;
583                         }
584
585                         DEBUG2("%.*s Evaluating %s(%.*s) -> %s",
586                                depth, filler,
587                                invert ? "!" : "", p - start, start,
588                                (result != FALSE) ? "TRUE" : "FALSE");
589
590                         DEBUG4(">>> GOT result %d", result);
591
592                         /*
593                          *      Not evaluating it.  We may be just
594                          *      parsing it.
595                          */
596                 } else if (request) {
597                         DEBUG2("%.*s Skipping %s(\"%s\" %s \"%s\")",
598                                depth, filler,
599                                invert ? "!" : "", pleft, comp, pright);
600                 }
601
602                 end_of_condition:
603                 if (invert) {
604                         DEBUG4(">>> INVERTING result");
605                         result = (result == FALSE);
606                         invert = FALSE;
607                 }
608
609                 /*
610                  *      Don't evaluate it.
611                  */
612                 DEBUG4(">>> EVALUATE %d ::%s::",
613                         evaluate_next_condition, p);
614
615                 while ((*p == ' ') || (*p == '\t')) p++;
616
617                 /*
618                  *      Closing brace or EOL, return.
619                  */
620                 if (!*p || (*p == ')') || (*p == '!') ||
621                     ((p[0] == '&') && (p[1] == '&')) ||
622                     ((p[0] == '|') && (p[1] == '|'))) {
623                         DEBUG4(">>> AT EOL2a");
624                         *ptr = p;
625                         *presult = result;
626                         return TRUE;
627                 }
628         } /* loop over the input condition */
629
630         DEBUG4(">>> AT EOL2b");
631         *ptr = p;
632         *presult = result;
633         return TRUE;
634 }
635
636 /*
637  *      Copied shamelessly from conffile.c, to simplify the API for
638  *      now...
639  */
640 typedef enum conf_type {
641         CONF_ITEM_INVALID = 0,
642         CONF_ITEM_PAIR,
643         CONF_ITEM_SECTION,
644         CONF_ITEM_DATA
645 } CONF_ITEM_TYPE;
646
647 struct conf_item {
648         struct conf_item *next;
649         struct conf_part *parent;
650         int lineno;
651         CONF_ITEM_TYPE type;
652 };
653 struct conf_pair {
654         CONF_ITEM item;
655         char *attr;
656         char *value;
657         LRAD_TOKEN operator;
658         LRAD_TOKEN value_type;
659 };
660
661
662 /*
663  *      Add attributes to a list.
664  */
665 int radius_update_attrlist(REQUEST *request, CONF_SECTION *cs,
666                            const char *name)
667 {
668         CONF_ITEM *ci;
669         VALUE_PAIR *head, **tail;
670         VALUE_PAIR **vps = NULL;
671
672         if (!request || !cs) return RLM_MODULE_INVALID;
673
674         if (strcmp(name, "request") == 0) {
675                 vps = &request->packet->vps;
676
677         } else if (strcmp(name, "reply") == 0) {
678                 vps = &request->reply->vps;
679
680         } else if (strcmp(name, "proxy-request") == 0) {
681                 if (request->proxy) vps = &request->proxy->vps;
682
683         } else if (strcmp(name, "proxy-reply") == 0) {
684                 if (request->proxy_reply) vps = &request->proxy_reply->vps;
685
686         } else if (strcmp(name, "config") == 0) {
687                 vps = &request->config_items;
688
689         } else {
690                 return RLM_MODULE_INVALID;
691         }
692
693         if (!vps) return RLM_MODULE_NOOP; /* didn't update the list */
694
695         head = NULL;
696         tail = &head;
697
698         for (ci=cf_item_find_next(cs, NULL);
699              ci != NULL;
700              ci=cf_item_find_next(cs, ci)) {
701                 const char *value;
702                 CONF_PAIR *cp;
703                 VALUE_PAIR *vp;
704                 char buffer[2048];
705
706                 if (cf_item_is_section(ci)) {
707                         pairfree(&head);
708                         return RLM_MODULE_INVALID;
709                 }
710
711                 cp = cf_itemtopair(ci);
712
713                 value = expand_string(buffer, sizeof(buffer), request,
714                                       cp->value_type, cp->value);
715                 if (!value) {
716                         pairfree(&head);
717                         return RLM_MODULE_INVALID;
718                 }
719
720                 vp = pairmake(cp->attr, value, cp->operator);
721                 if (!vp) {
722                         DEBUG2("Failed to create attribute %s value %s",
723                                cp->attr, value);
724                         pairfree(&head);
725                         return RLM_MODULE_FAIL;
726                 }
727
728                 *tail = vp;
729                 tail = &vp->next;
730         }
731
732         if (!head) return RLM_MODULE_NOOP;
733
734         pairmove(vps, &head);
735         pairfree(&head);
736
737         return RLM_MODULE_UPDATED;
738 }
739
740