Don't allow list references in conditions
[freeradius.git] / src / main / parser.c
1 /*
2  * parser.c     Parse various things
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 2013  Alan DeKok <aland@freeradius.org>
21  */
22
23 RCSID("$Id$")
24
25 #include <freeradius-devel/radiusd.h>
26 #include <freeradius-devel/parser.h>
27 #include <freeradius-devel/rad_assert.h>
28
29 #include <ctype.h>
30
31 #if 0
32 #define COND_DEBUG(fmt, ...) printf(fmt, ## __VA_ARGS__);printf("\n")
33 #endif
34
35 /*
36  *      This file shouldn't use any functions from the server core.
37  */
38 #ifndef COND_DEBUG
39 #if 0
40 #define COND_DEBUG DEBUG
41 #else
42 #define COND_DEBUG(...)
43 #endif
44 #endif
45
46 size_t fr_cond_sprint(char *buffer, size_t bufsize, fr_cond_t const *c)
47 {
48         size_t len;
49         char *p = buffer;
50         char *end = buffer + bufsize - 1;
51
52 next:
53         if (c->negate) {
54                 *(p++) = '!';   /* FIXME: only allow for child? */
55         }
56
57         switch (c->type) {
58         case COND_TYPE_EXISTS:
59                 rad_assert(c->data.vpt != NULL);
60                 len = radius_tmpl2str(p, end - p, c->data.vpt);
61                 p += len;
62                 break;
63
64         case COND_TYPE_MAP:
65                 rad_assert(c->data.map != NULL);
66 #if 0
67                 *(p++) = '[';   /* for extra-clear debugging */
68 #endif
69                 len = radius_map2str(p, end - p, c->data.map);
70                 p += len;
71 #if 0
72                 *(p++) = ']';
73 #endif
74                 break;
75
76         case COND_TYPE_CHILD:
77                 rad_assert(c->data.child != NULL);
78                 *(p++) = '(';
79                 len = fr_cond_sprint(p, end - p, c->data.child);
80                 p += len;
81                 *(p++) = ')';
82                 break;
83
84         default:
85                 *buffer = '\0';
86                 return 0;
87         }
88
89         if (c->next_op == COND_NONE) {
90                 rad_assert(c->next == NULL);
91                 *p = '\0';
92                 return p - buffer;
93         }
94
95         if (c->next_op == COND_AND) {
96                 strlcpy(p, " && ", end - p);
97                 p += strlen(p);
98
99         } else if (c->next_op == COND_OR) {
100                 strlcpy(p, " || ", end - p);
101                 p += strlen(p);
102
103         } else {
104                 rad_assert(0 == 1);
105         }
106
107         c = c->next;
108         goto next;
109 }
110
111
112 static ssize_t condition_tokenize_string(TALLOC_CTX *ctx, char const *start, char **out,
113                                          FR_TOKEN *op, char const **error)
114 {
115         const char *p = start;
116         char *q;
117
118         switch (*p++) {
119         default:
120                 return -1;
121
122         case '"':
123                 *op = T_DOUBLE_QUOTED_STRING;
124                 break;
125
126         case '\'':
127                 *op = T_SINGLE_QUOTED_STRING;
128                 break;
129
130         case '`':
131                 *op = T_BACK_QUOTED_STRING;
132                 break;
133
134         case '/':
135                 *op = T_OP_REG_EQ; /* a bit of a hack. */
136                 break;
137
138         }
139
140         *out = talloc_array(ctx, char, strlen(start) - 1); /* + 2 - 1 */
141         if (!*out) return -1;
142
143         q = *out;
144
145         COND_DEBUG("STRING %s", start);
146         while (*p) {
147                 if (*p == *start) {
148                         *q = '\0';
149                         p++;
150
151                         COND_DEBUG("end of string %s", p);
152
153                         return (p - start);
154                 }
155
156                 if (*p == '\\') {
157                         p++;
158                         if (!*p) {
159                                 *error = "End of string after escape";
160                                 COND_DEBUG("RETURN %d", __LINE__);
161                                 return -(p - start);
162                         }
163
164                         switch (*p) {
165                         case 'r':
166                                 *q++ = '\r';
167                                 break;
168                         case 'n':
169                                 *q++ = '\n';
170                                 break;
171                         case 't':
172                                 *q++ = '\t';
173                                 break;
174                         default:
175                                 *q++ = *p;
176                                 break;
177                         }
178                         p++;
179                         continue;
180                 }
181         
182                 *(q++) = *(p++);
183         }
184
185         *error = "Unterminated string";
186         return -1;
187 }
188
189 static ssize_t condition_tokenize_word(TALLOC_CTX *ctx, char const *start, char **out,
190                                        FR_TOKEN *op, char const **error)
191 {
192         size_t len;
193         char const *p = start;
194
195         if ((*p == '"') || (*p == '\'') || (*p == '`') || (*p == '/')) {
196                 return condition_tokenize_string(ctx, start, out, op, error);
197         }
198
199         *op = T_BARE_WORD;
200         if (*p == '&') p++;     /* special-case &User-Name */
201
202         while (*p) {
203                 /*
204                  *      The LHS should really be limited to only a few
205                  *      things.  For now, we allow pretty much anything.
206                  */
207                 if (*p == '\\') {
208                         *error = "Unexpected escape";
209                         COND_DEBUG("RETURN %d", __LINE__);
210                         return -(p - start);
211                 }
212
213                 /*
214                  *      ("foo") is valid.
215                  */
216                 if (*p == ')') {
217                         break;
218                 }
219
220                 /*
221                  *      Spaces or special characters delineate the word
222                  */
223                 if (isspace((int) *p) || (*p == '&') || (*p == '|') ||
224                     (*p == '!') || (*p == '=') || (*p == '<') || (*p == '>')) {
225                         break;
226                 }
227
228                 if ((*p == '"') || (*p == '\'') || (*p == '`')) {
229                         COND_DEBUG("RETURN %d", __LINE__);
230                         *error = "Unexpected start of string";
231                         return -(p - start);
232                 }
233
234                 p++;
235         }
236
237         len = p - start;
238         if (!len) {
239                 *error = "Empty string is invalid";
240                 return 0;
241         }
242
243         *out = talloc_array(ctx, char, len + 1);
244         memcpy(*out, start, len);
245         (*out)[len] = '\0';
246         COND_DEBUG("PARSED WORD %s", *out);
247         return len;
248 }
249
250 /** Tokenize a conditional check
251  *
252  *  @param[in] ctx for talloc
253  *  @param[in] start the start of the string to process.  Should be "(..."
254  *  @param[in] brace look for a closing brace
255  *  @param[out] pcond pointer to the returned condition structure
256  *  @param[out] error the parse error (if any)
257  *  @return length of the string skipped, or when negative, the offset to the offending error
258  */
259 static ssize_t condition_tokenize(TALLOC_CTX *ctx, char const *start, int brace, fr_cond_t **pcond, char const **error)
260 {
261         ssize_t slen;
262         const char *p = start;
263         fr_cond_t *c;
264         char *lhs, *rhs;
265         FR_TOKEN op, lhs_type, rhs_type;
266
267         COND_DEBUG("START %s", p);
268
269         c = talloc_zero(ctx, fr_cond_t);
270
271         rad_assert(c != NULL);
272
273         while (isspace((int) *p)) p++; /* skip spaces before condition */
274
275         if (!*p) {
276                 talloc_free(c);
277                 COND_DEBUG("RETURN %d", __LINE__);
278                 *error = "Empty condition is invalid";
279                 return -(p - start);
280         }
281
282         /*
283          *      !COND
284          */
285         if (*p == '!') {
286                 p++;
287                 c->negate = true;
288                 while (isspace((int) *p)) p++; /* skip spaces after negation */
289
290                 /*
291                  *  Just for stupidity
292                  */
293                 if (*p == '!') {
294                         talloc_free(c);
295                         COND_DEBUG("RETURN %d", __LINE__);
296                         *error = "Double negation is invalid";
297                         return -(p - start);
298                 }
299         }
300
301         /*
302          *      (COND)
303          */
304         if (*p == '(') {
305                 p++;
306
307                 /*
308                  *      We've already eaten one layer of
309                  *      brackets.  Go recurse to get more.
310                  */
311                 c->type = COND_TYPE_CHILD;
312                 slen = condition_tokenize(c, p, true, &c->data.child, error);
313                 if (slen <= 0) {
314                         talloc_free(c);
315                         COND_DEBUG("RETURN %d", __LINE__);
316                         return slen - (p - start);
317                 }
318
319                 if (!c->data.child) {
320                         talloc_free(c);
321                         *error = "Empty condition is invalid";
322                         COND_DEBUG("RETURN %d", __LINE__);
323                         return -(p - start);
324                 }
325
326                 p += slen;
327                 while (isspace((int) *p)) p++; /* skip spaces after (COND)*/
328
329         } else { /* it's a bare FOO==BAR */
330                 /*
331                  *      We didn't see anything special.  The condition must be one of
332                  *
333                  *      FOO
334                  *      FOO OP BAR
335                  */
336
337                 /*
338                  *      Grab the LHS
339                  */
340                 COND_DEBUG("LHS %s", p);
341                 if (*p == '/') {
342                         talloc_free(c);
343                         *error = "Conditional check cannot begin with a regular expression";
344                         COND_DEBUG("RETURN %d", __LINE__);
345                         return -(p - start);
346                 }
347
348                 slen = condition_tokenize_word(c, p, &lhs, &lhs_type, error);
349                 if (slen <= 0) {
350                         talloc_free(c);
351                         COND_DEBUG("RETURN %d", __LINE__);
352                         return slen - (p - start);
353                 }
354                 p += slen;
355
356                 while (isspace((int)*p)) p++; /* skip spaces after LHS */
357
358                 /*
359                  *      We may (or not) have an operator
360                  */
361
362
363                 /*
364                  *      (FOO)
365                  */
366                 if (*p == ')') {
367                         /*
368                          *      don't skip the brace.  We'll look for it later.
369                          */
370                 exists:
371                         c->type = COND_TYPE_EXISTS;
372                         c->data.vpt = radius_str2tmpl(c, lhs, lhs_type);
373                         if (!c->data.vpt) {
374                                 talloc_free(c);
375                                 *error = "Failed creating exists";
376                                 COND_DEBUG("RETURN %d", __LINE__);
377                                 return -(p - start);
378                         }
379
380                         /*
381                          *      FOO
382                          */
383                 } else if (!*p) {
384                         if (brace) {
385                                 talloc_free(c);
386                                 *error = "No closing brace at end of string";
387                                 COND_DEBUG("RETURN %d", __LINE__);
388                                 return -(p - start);
389                         }
390
391                         goto exists;
392
393                         /*
394                          *      FOO && ...
395                          */
396                 } else if (((p[0] == '&') && (p[1] == '&')) ||
397                            ((p[0] == '|') && (p[1] == '|'))) {
398
399                         goto exists;
400
401                 } else { /* it's an operator */
402                         int regex;
403
404                         COND_DEBUG("OPERATOR %s", p);
405
406                         /*
407                          *      The next thing should now be a comparison operator.
408                          */
409                         regex = false;
410                         c->type = COND_TYPE_MAP;
411                         switch (*p) {
412                         default:
413                                 talloc_free(c);
414                                 *error = "Invalid text. Expected comparison operator";
415                                 COND_DEBUG("RETURN %d", __LINE__);
416                                 return -(p - start);
417
418                         case '!':
419                                 if (p[1] == '=') {
420                                         op = T_OP_NE;
421                                         p += 2;
422
423                                 } else if (p[1] == '~') {
424                                 regex = true;
425
426                                 op = T_OP_REG_NE;
427                                 p += 2;
428
429                                 } else if (p[1] == '*') {
430                                         /*
431                                          *      FOO !* BAR
432                                          *
433                                          *      is really !(FOO)
434                                          *
435                                          *      FIXME: we should
436                                          *      really re-write it...
437                                          */
438                                         op = T_OP_CMP_FALSE;
439                                         p += 2;
440
441                                 } else {
442                                 invalid_operator:
443                                         talloc_free(c);
444                                         *error = "Invalid operator";
445                                         COND_DEBUG("RETURN %d", __LINE__);
446                                         return -(p - start);
447                                 }
448                                 break;
449
450                         case '=':
451                                 if (p[1] == '=') {
452                                         op = T_OP_CMP_EQ;
453                                         p += 2;
454
455                                 } else if (p[1] == '~') {
456                                         regex = true;
457
458                                         op = T_OP_REG_EQ;
459                                         p += 2;
460
461                                 } else if (p[1] == '*') {
462                                         op = T_OP_CMP_TRUE;
463                                         p += 2;
464
465                                 } else {
466                                         goto invalid_operator;
467                                 }
468
469                                 break;
470
471                         case '<':
472                                 if (p[1] == '=') {
473                                         op = T_OP_LE;
474                                         p += 2;
475
476                                 } else {
477                                         op = T_OP_LT;
478                                         p++;
479                                 }
480                                 break;
481
482                         case '>':
483                                 if (p[1] == '=') {
484                                         op = T_OP_GE;
485                                         p += 2;
486
487                                 } else {
488                                         op = T_OP_GT;
489                                         p++;
490                                 }
491                                 break;
492                         }
493
494                         while (isspace((int) *p)) p++; /* skip spaces after operator */
495
496                         if (!*p) {
497                                 talloc_free(c);
498                                 *error = "Expected text after operator";
499                                 COND_DEBUG("RETURN %d", __LINE__);
500                                 return -(p - start);
501                         }
502
503                         COND_DEBUG("RHS %s", p);
504
505                         /*
506                          *      Grab the RHS
507                          */
508                         slen = condition_tokenize_word(c, p, &rhs, &rhs_type, error);
509                         if (slen <= 0) {
510                                 talloc_free(c);
511                                 COND_DEBUG("RETURN %d", __LINE__);
512                                 return slen - (p - start);
513                         }
514
515                         /*
516                          *      Sanity checks for regexes.
517                          */
518                         if (regex) {
519                                 if (*p != '/') {
520                                         talloc_free(c);
521                                         *error = "Expected regular expression";
522                                         COND_DEBUG("RETURN %d", __LINE__);
523                                         return -(p - start);
524                                 }
525
526                                 /*
527                                  *      Allow /foo/i
528                                  */
529                                 if (p[slen] == 'i') {
530                                         c->regex_i = true;
531                                         slen++;
532                                 }
533
534                                 COND_DEBUG("DONE REGEX %s", p + slen);
535
536                         } else if (!regex && (*p == '/')) {
537                                 talloc_free(c);
538                                 *error = "Unexpected regular expression";
539                                 COND_DEBUG("RETURN %d", __LINE__);
540                                 return -(p - start);
541                         }
542
543                         c->data.map = radius_str2map(c, lhs, lhs_type, op, rhs, rhs_type,
544                                                      REQUEST_CURRENT, PAIR_LIST_REQUEST,
545                                                      REQUEST_CURRENT, PAIR_LIST_REQUEST);
546                         if (!c->data.map) {
547                                 talloc_free(c);
548                                 *error = "Failed creating check";
549                                 COND_DEBUG("RETURN %d", __LINE__);
550                                 return -(p - start);
551                         }
552
553                         /*
554                          *      @todo: check LHS and RHS separately, to
555                          *      get better errors
556                          */
557                         if ((c->data.map->src->type == VPT_TYPE_LIST) ||
558                             (c->data.map->dst->type == VPT_TYPE_LIST)) {
559                                 talloc_free(c);
560                                 *error = "Cannot use list references in condition";
561                                 COND_DEBUG("RETURN %d", __LINE__);
562                                 return 0;
563                         }
564
565                         p += slen;
566
567                         while (isspace((int) *p)) p++; /* skip spaces after RHS */
568                 } /* parse OP RHS */
569         } /* parse a condition (COND) or FOO OP BAR*/
570
571         /*
572          *      ...COND)
573          */
574         if (*p == ')') {
575                 if (!brace) {
576                         talloc_free(c);
577                         *error = "Unexpected closing brace";
578                         COND_DEBUG("RETURN %d", __LINE__);
579                         return -(p - start);
580                 }
581
582                 p++;
583                 while (isspace((int) *p)) p++; /* skip spaces after closing brace */
584                 brace = false;
585                 goto done;
586         }
587
588         /*
589          *      End of string is now allowed.
590          */
591         if (!*p) {
592                 if (brace) {
593                         talloc_free(c);
594                         *error = "No closing brace at end of string";
595                         COND_DEBUG("RETURN %d", __LINE__);
596                         return -(p - start);
597                 }
598
599                 goto done;
600         }
601
602         if (!(((p[0] == '&') && (p[1] == '&')) ||
603               ((p[0] == '|') && (p[1] == '|')))) {
604                 talloc_free(c);
605                 *error = "Unexpected text after condition";
606                 return -(p - start);
607         }
608
609         /*
610          *      Recurse to parse the next condition.
611          */
612         COND_DEBUG("GOT %c%c", p[0], p[1]);
613         c->next_op = p[0];
614         p += 2;
615
616         /*
617          *      May still be looking for a closing brace.
618          */
619         COND_DEBUG("RECURSE AND/OR");
620         slen = condition_tokenize(c, p, brace, &c->next, error);
621         if (slen <= 0) {
622                 talloc_free(c);
623                 COND_DEBUG("RETURN %d", __LINE__);
624                 return slen - (p - start);
625         }
626         p += slen;
627
628 done:
629         COND_DEBUG("RETURN %d", __LINE__);
630
631         /*
632          *      Normalize it before returning it.
633          */
634
635         /*
636          *      (FOO)     --> FOO
637          *      (FOO) ... --> FOO ...
638          */
639         if ((c->type == COND_TYPE_CHILD) &&
640             !c->data.child->next) {
641                 fr_cond_t *child;
642
643                 child = c->data.child;
644                 child->next = c->next;
645                 child->next_op = c->next_op;
646                 c->next = NULL;
647                 c->data.child = NULL;
648
649                 /*
650                  *      Set the negation properly
651                  */
652                 if ((c->negate && !child->negate) ||
653                     (!c->negate && child->negate)) {
654                         child->negate = true;
655                 } else {
656                         child->negate = false;
657                 }
658
659                 (void) talloc_steal(ctx, child);
660                 talloc_free(c);
661                 c = child;
662         }
663
664         /*
665          *      (FOO ...) --> FOO ...
666          *
667          *      But don't do !(FOO || BAR) --> !FOO || BAR
668          *      Because that's different.
669          */
670         if ((c->type == COND_TYPE_CHILD) && !c->next &&
671             !c->negate) {
672                 fr_cond_t *child;
673
674                 child = c->data.child;
675                 c->data.child = NULL;
676                 (void) talloc_steal(ctx, child);
677                 talloc_free(c);
678                 c = child;
679         }
680
681         *pcond = c;
682         return p - start;
683 }
684
685 /** Tokenize a conditional check
686  *
687  *  @param[in] ctx for talloc
688  *  @param[in] start the start of the string to process.  Should be "(..."
689  *  @param[out] head the parsed condition structure
690  *  @param[out] error the parse error (if any)
691  *  @return length of the string skipped, or when negative, the offset to the offending error
692  */
693 ssize_t fr_condition_tokenize(TALLOC_CTX *ctx, char const *start, fr_cond_t **head, char const **error)
694 {
695         return condition_tokenize(ctx, start, false, head, error);
696 }