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