More / better errors and tests
[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                 slen = condition_tokenize_word(c, p, &lhs, &lhs_type, error);
342                 if (slen <= 0) {
343                         talloc_free(c);
344                         COND_DEBUG("RETURN %d", __LINE__);
345                         return slen - (p - start);
346                 }
347                 p += slen;
348
349                 while (isspace((int)*p)) p++; /* skip spaces after LHS */
350
351                 /*
352                  *      We may (or not) have an operator
353                  */
354
355
356                 /*
357                  *      (FOO)
358                  */
359                 if (*p == ')') {
360                         /*
361                          *      don't skip the brace.  We'll look for it later.
362                          */
363                 exists:
364                         c->type = COND_TYPE_EXISTS;
365                         c->data.vpt = radius_str2tmpl(c, lhs, lhs_type);
366                         if (!c->data.vpt) {
367                                 talloc_free(c);
368                                 *error = "Failed creating exists";
369                                 COND_DEBUG("RETURN %d", __LINE__);
370                                 return -(p - start);
371                         }
372
373                         /*
374                          *      FOO
375                          */
376                 } else if (!*p) {
377                         if (brace) {
378                                 talloc_free(c);
379                                 *error = "No closing brace at end of string";
380                                 COND_DEBUG("RETURN %d", __LINE__);
381                                 return -(p - start);
382                         }
383
384                         goto exists;
385
386                         /*
387                          *      FOO && ...
388                          */
389                 } else if (((p[0] == '&') && (p[1] == '&')) ||
390                            ((p[0] == '|') && (p[1] == '|'))) {
391
392                         goto exists;
393
394                 } else { /* it's an operator */
395                         int regex;
396
397                         COND_DEBUG("OPERATOR %s", p);
398
399                         /*
400                          *      The next thing should now be a comparison operator.
401                          */
402                         regex = FALSE;
403                         c->type = COND_TYPE_MAP;
404                         switch (*p) {
405                         default:
406                                 talloc_free(c);
407                                 *error = "Invalid text. Expected comparison operator";
408                                 COND_DEBUG("RETURN %d", __LINE__);
409                                 return -(p - start);
410
411                         case '!':
412                                 if (p[1] == '=') {
413                                         op = T_OP_NE;
414                                         p += 2;
415
416                                 } else if (p[1] == '~') {
417                                 regex = TRUE;
418
419                                 op = T_OP_REG_NE;
420                                 p += 2;
421
422                                 } else if (p[1] == '*') {
423                                         /*
424                                          *      FOO !* BAR
425                                          *
426                                          *      is really !(FOO)
427                                          *
428                                          *      FIXME: we should
429                                          *      really re-write it...
430                                          */
431                                         op = T_OP_CMP_FALSE;
432                                         p += 2;
433
434                                 } else {
435                                 invalid_operator:
436                                         talloc_free(c);
437                                         *error = "Invalid operator";
438                                         COND_DEBUG("RETURN %d", __LINE__);
439                                         return -(p - start);
440                                 }
441                                 break;
442
443                         case '=':
444                                 if (p[1] == '=') {
445                                         op = T_OP_CMP_EQ;
446                                         p += 2;
447
448                                 } else if (p[1] == '~') {
449                                         regex = TRUE;
450
451                                         op = T_OP_REG_EQ;
452                                         p += 2;
453
454                                 } else if (p[1] == '*') {
455                                         op = T_OP_CMP_TRUE;
456                                         p += 2;
457
458                                 } else {
459                                         goto invalid_operator;
460                                 }
461
462                                 break;
463
464                         case '<':
465                                 if (p[1] == '=') {
466                                         op = T_OP_LE;
467                                         p += 2;
468
469                                 } else {
470                                         op = T_OP_LT;
471                                         p++;
472                                 }
473                                 break;
474
475                         case '>':
476                                 if (p[1] == '=') {
477                                         op = T_OP_GE;
478                                         p += 2;
479
480                                 } else {
481                                         op = T_OP_GT;
482                                         p++;
483                                 }
484                                 break;
485                         }
486
487                         while (isspace((int) *p)) p++; /* skip spaces after operator */
488
489                         if (!*p) {
490                                 talloc_free(c);
491                                 *error = "Expected text after operator";
492                                 COND_DEBUG("RETURN %d", __LINE__);
493                                 return -(p - start);
494                         }
495
496                         COND_DEBUG("RHS %s", p);
497
498                         /*
499                          *      Grab the RHS
500                          */
501                         slen = condition_tokenize_word(c, p, &rhs, &rhs_type, error);
502                         if (slen <= 0) {
503                                 talloc_free(c);
504                                 COND_DEBUG("RETURN %d", __LINE__);
505                                 return slen - (p - start);
506                         }
507
508                         /*
509                          *      Sanity checks for regexes.
510                          */
511                         if (regex) {
512                                 if (*p != '/') {
513                                         talloc_free(c);
514                                         *error = "Expected regular expression";
515                                         COND_DEBUG("RETURN %d", __LINE__);
516                                         return -(p - start);
517                                 }
518
519                                 /*
520                                  *      Allow /foo/i
521                                  */
522                                 if (p[slen] == 'i') {
523                                         c->regex_i = TRUE;
524                                         slen++;
525                                 }
526
527                                 COND_DEBUG("DONE REGEX %s", p + slen);
528
529                         } else if (!regex && (*p == '/')) {
530                                 talloc_free(c);
531                                 *error = "Unexpected regular expression";
532                                 COND_DEBUG("RETURN %d", __LINE__);
533                                 return -(p - start);
534                         }
535
536                         c->data.map = radius_str2map(c, lhs, lhs_type, op, rhs, rhs_type,
537                                                      REQUEST_CURRENT, PAIR_LIST_REQUEST,
538                                                      REQUEST_CURRENT, PAIR_LIST_REQUEST);
539                         if (!c->data.map) {
540                                 talloc_free(c);
541                                 *error = "Failed creating check";
542                                 COND_DEBUG("RETURN %d", __LINE__);
543                                 return -(p - start);
544                         }
545                         p += slen;
546
547                         while (isspace((int) *p)) p++; /* skip spaces after RHS */
548                 } /* parse OP RHS */
549         } /* parse a condition (COND) or FOO OP BAR*/
550
551         /*
552          *      ...COND)
553          */
554         if (*p == ')') {
555                 if (!brace) {
556                         talloc_free(c);
557                         *error = "Unexpected closing brace";
558                         COND_DEBUG("RETURN %d", __LINE__);
559                         return -(p - start);
560                 }
561
562                 p++;
563                 while (isspace((int) *p)) p++; /* skip spaces after closing brace */
564                 brace = FALSE;
565                 goto done;
566         }
567
568         /*
569          *      End of string is now allowed.
570          */
571         if (!*p) {
572                 if (brace) {
573                         talloc_free(c);
574                         *error = "No closing brace at end of string";
575                         COND_DEBUG("RETURN %d", __LINE__);
576                         return -(p - start);
577                 }
578
579                 goto done;
580         }
581
582         if (!(((p[0] == '&') && (p[1] == '&')) ||
583               ((p[0] == '|') && (p[1] == '|')))) {
584                 talloc_free(c);
585                 *error = "Unexpected text after condition";
586                 return -(p - start);
587         }
588
589         /*
590          *      Recurse to parse the next condition.
591          */
592         COND_DEBUG("GOT %c%c", p[0], p[1]);
593         c->next_op = p[0];
594         p += 2;
595
596         /*
597          *      May still be looking for a closing brace.
598          */
599         COND_DEBUG("RECURSE AND/OR");
600         slen = condition_tokenize(c, p, brace, &c->next, error);
601         if (slen <= 0) {
602                 talloc_free(c);
603                 COND_DEBUG("RETURN %d", __LINE__);
604                 return slen - (p - start);
605         }
606         p += slen;
607
608 done:
609         COND_DEBUG("RETURN %d", __LINE__);
610
611         /*
612          *      Normalize it before returning it.
613          */
614
615         /*
616          *      (FOO)     --> FOO
617          *      (FOO) ... --> FOO ...
618          */
619         if ((c->type == COND_TYPE_CHILD) &&
620             !c->data.child->next) {
621                 fr_cond_t *child;
622
623                 child = c->data.child;
624                 child->next = c->next;
625                 child->next_op = c->next_op;
626                 c->next = NULL;
627                 c->data.child = NULL;
628
629                 /*
630                  *      Set the negation properly
631                  */
632                 if ((c->negate && !child->negate) ||
633                     (!c->negate && child->negate)) {
634                         child->negate = TRUE;
635                 } else {
636                         child->negate = FALSE;
637                 }
638
639                 (void) talloc_steal(ctx, child);
640                 talloc_free(c);
641                 c = child;
642         }
643
644         /*
645          *      (FOO ...) --> FOO ...
646          *
647          *      But don't do !(FOO || BAR) --> !FOO || BAR
648          *      Because that's different.
649          */
650         if ((c->type == COND_TYPE_CHILD) && !c->next &&
651             !c->negate) {
652                 fr_cond_t *child;
653
654                 child = c->data.child;
655                 c->data.child = NULL;
656                 (void) talloc_steal(ctx, child);
657                 talloc_free(c);
658                 c = child;
659         }
660
661         *pcond = c;
662         return p - start;
663 }
664
665 /** Tokenize a conditional check
666  *
667  *  @param[in] ctx for talloc
668  *  @param[in] start the start of the string to process.  Should be "(..."
669  *  @param[out] head the parsed condition structure
670  *  @param[out] error the parse error (if any)
671  *  @return length of the string skipped, or when negative, the offset to the offending error
672  */
673 ssize_t fr_condition_tokenize(TALLOC_CTX *ctx, char const *start, fr_cond_t **head, char const **error)
674 {
675         return condition_tokenize(ctx, start, FALSE, head, error);
676 }