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