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