blob: c1259c0f47f1079c8c26083801a7fbae3c61673f [file] [log] [blame]
Willy Tarreau66243b42021-07-16 15:39:28 +02001/*
2 * Configuration condition preprocessor
3 *
4 * Copyright 2000-2021 Willy Tarreau <w@1wt.eu>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13#include <haproxy/api.h>
14#include <haproxy/arg.h>
15#include <haproxy/cfgcond.h>
16#include <haproxy/global.h>
17#include <haproxy/tools.h>
18
19/* supported condition predicates */
20const struct cond_pred_kw cond_predicates[] = {
21 { "defined", CFG_PRED_DEFINED, ARG1(1, STR) },
22 { "feature", CFG_PRED_FEATURE, ARG1(1, STR) },
23 { "streq", CFG_PRED_STREQ, ARG2(2, STR, STR) },
24 { "strneq", CFG_PRED_STRNEQ, ARG2(2, STR, STR) },
25 { "version_atleast", CFG_PRED_VERSION_ATLEAST, ARG1(1, STR) },
26 { "version_before", CFG_PRED_VERSION_BEFORE, ARG1(1, STR) },
27 { NULL, CFG_PRED_NONE, 0 }
28};
29
30/* looks up a cond predicate matching the keyword in <str>, possibly followed
31 * by a parenthesis. Returns a pointer to it or NULL if not found.
32 */
33const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str)
34{
35 const struct cond_pred_kw *ret;
36 int len = strcspn(str, " (");
37
38 for (ret = &cond_predicates[0]; ret->word; ret++) {
39 if (len != strlen(ret->word))
40 continue;
41 if (strncmp(str, ret->word, len) != 0)
42 continue;
43 return ret;
44 }
45 return NULL;
46}
47
Willy Tarreau087b2d02021-07-16 14:27:20 +020048/* Frees <term> and its args. NULL is supported and does nothing. */
Willy Tarreauf1db20c2021-07-17 18:46:30 +020049void cfg_free_cond_term(struct cfg_cond_term *term)
Willy Tarreau087b2d02021-07-16 14:27:20 +020050{
Willy Tarreauf1db20c2021-07-17 18:46:30 +020051 if (!term)
Willy Tarreau087b2d02021-07-16 14:27:20 +020052 return;
53
Willy Tarreauf1db20c2021-07-17 18:46:30 +020054 if (term->type == CCTT_PAREN) {
55 cfg_free_cond_expr(term->expr);
56 term->expr = NULL;
57 }
Willy Tarreau316ea7e2021-07-16 14:56:59 +020058
Willy Tarreauf1db20c2021-07-17 18:46:30 +020059 free_args(term->args);
60 free(term->args);
61 free(term);
Willy Tarreau087b2d02021-07-16 14:27:20 +020062}
63
Willy Tarreauf8690952021-07-16 12:12:00 +020064/* Parse an indirect input text as a possible config condition term.
65 * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
Willy Tarreau087b2d02021-07-16 14:27:20 +020066 * success. <term> is allocated and filled with the parsed info, and <text>
67 * is updated on success to point to the first unparsed character, or is left
68 * untouched on failure. On success, the caller must free <term> using
69 * cfg_free_cond_term(). An error will be set in <err> on error, and only
Willy Tarreauf8690952021-07-16 12:12:00 +020070 * in this case. In this case the first bad character will be reported in
71 * <errptr>.
Willy Tarreau66243b42021-07-16 15:39:28 +020072 */
Willy Tarreau087b2d02021-07-16 14:27:20 +020073int cfg_parse_cond_term(const char **text, struct cfg_cond_term **term, char **err, const char **errptr)
Willy Tarreau66243b42021-07-16 15:39:28 +020074{
Willy Tarreau087b2d02021-07-16 14:27:20 +020075 struct cfg_cond_term *t;
Willy Tarreauf8690952021-07-16 12:12:00 +020076 const char *in = *text;
Willy Tarreau66243b42021-07-16 15:39:28 +020077 const char *end_ptr;
Willy Tarreau66243b42021-07-16 15:39:28 +020078 int err_arg;
79 int nbargs;
Willy Tarreau66243b42021-07-16 15:39:28 +020080 char *end;
81 long val;
82
Willy Tarreauf8690952021-07-16 12:12:00 +020083 while (*in == ' ' || *in == '\t')
84 in++;
85
86 if (!*in) /* empty term does not parse */
Willy Tarreau66243b42021-07-16 15:39:28 +020087 return 0;
88
Willy Tarreau087b2d02021-07-16 14:27:20 +020089 t = *term = calloc(1, sizeof(**term));
90 if (!t) {
91 memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
92 goto fail1;
93 }
94
95 t->type = CCTT_NONE;
96 t->args = NULL;
97 t->neg = 0;
98
Willy Tarreauca56d3d2021-07-16 13:56:54 +020099 /* !<term> negates the term. White spaces permitted */
100 while (*in == '!') {
Willy Tarreau087b2d02021-07-16 14:27:20 +0200101 t->neg = !t->neg;
Willy Tarreauca56d3d2021-07-16 13:56:54 +0200102 do { in++; } while (*in == ' ' || *in == '\t');
103 }
104
Willy Tarreauf8690952021-07-16 12:12:00 +0200105 val = strtol(in, &end, 0);
106 if (end != in) {
Willy Tarreau087b2d02021-07-16 14:27:20 +0200107 t->type = val ? CCTT_TRUE : CCTT_FALSE;
Willy Tarreauf8690952021-07-16 12:12:00 +0200108 *text = end;
109 return 1;
110 }
Willy Tarreau66243b42021-07-16 15:39:28 +0200111
Willy Tarreau316ea7e2021-07-16 14:56:59 +0200112 /* Try to parse '(' EXPR ')' */
113 if (*in == '(') {
114 int ret;
115
116 t->type = CCTT_PAREN;
117 t->args = NULL;
118
119 do { in++; } while (*in == ' ' || *in == '\t');
120 ret = cfg_parse_cond_expr(&in, &t->expr, err, errptr);
121 if (ret == -1)
122 goto fail2;
123 if (ret == 0)
124 goto fail0;
125
126 /* find the closing ')' */
127 while (*in == ' ' || *in == '\t')
128 in++;
129 if (*in != ')') {
130 memprintf(err, "expected ')' after conditional expression '%s'", *text);
131 goto fail1;
132 }
133 do { in++; } while (*in == ' ' || *in == '\t');
134 *text = in;
135 return 1;
136 }
137
Willy Tarreau66243b42021-07-16 15:39:28 +0200138 /* below we'll likely all make_arg_list() so we must return only via
139 * the <done> label which frees the arg list.
140 */
Willy Tarreau087b2d02021-07-16 14:27:20 +0200141 t->pred = cfg_lookup_cond_pred(in);
142 if (t->pred) {
143 t->type = CCTT_PRED;
144 nbargs = make_arg_list(in + strlen(t->pred->word), -1,
145 t->pred->arg_mask, &t->args, err,
Willy Tarreau66243b42021-07-16 15:39:28 +0200146 &end_ptr, &err_arg, NULL);
Willy Tarreau66243b42021-07-16 15:39:28 +0200147 if (nbargs < 0) {
Willy Tarreau087b2d02021-07-16 14:27:20 +0200148 memprintf(err, "%s in argument %d of predicate '%s' used in conditional expression", *err, err_arg, t->pred->word);
Willy Tarreau66243b42021-07-16 15:39:28 +0200149 if (errptr)
150 *errptr = end_ptr;
Willy Tarreau087b2d02021-07-16 14:27:20 +0200151 goto fail2;
Willy Tarreau66243b42021-07-16 15:39:28 +0200152 }
Willy Tarreauf8690952021-07-16 12:12:00 +0200153 *text = end_ptr;
154 return 1;
155 }
156
Willy Tarreau316ea7e2021-07-16 14:56:59 +0200157 fail0:
Willy Tarreauf8690952021-07-16 12:12:00 +0200158 memprintf(err, "unparsable conditional expression '%s'", *text);
Willy Tarreau087b2d02021-07-16 14:27:20 +0200159 fail1:
Willy Tarreauf8690952021-07-16 12:12:00 +0200160 if (errptr)
161 *errptr = *text;
Willy Tarreau087b2d02021-07-16 14:27:20 +0200162 fail2:
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200163 cfg_free_cond_term(*term);
164 *term = NULL;
Willy Tarreauf8690952021-07-16 12:12:00 +0200165 return -1;
166}
167
168/* evaluate a condition term on a .if/.elif line. The condition was already
169 * parsed in <term>. Returns -1 on error (in which case err is filled with a
170 * message, and only in this case), 0 if the condition is false, 1 if it's
171 * true.
172 */
173int cfg_eval_cond_term(const struct cfg_cond_term *term, char **err)
174{
175 int ret = -1;
Willy Tarreau66243b42021-07-16 15:39:28 +0200176
Willy Tarreauf8690952021-07-16 12:12:00 +0200177 if (term->type == CCTT_FALSE)
178 ret = 0;
179 else if (term->type == CCTT_TRUE)
180 ret = 1;
181 else if (term->type == CCTT_PRED) {
182 /* here we know we have a valid predicate with valid arguments
183 * placed in term->args (which the caller will free).
Willy Tarreau66243b42021-07-16 15:39:28 +0200184 */
Willy Tarreauf8690952021-07-16 12:12:00 +0200185 switch (term->pred->prd) {
Willy Tarreau66243b42021-07-16 15:39:28 +0200186 case CFG_PRED_DEFINED: // checks if arg exists as an environment variable
Willy Tarreauf8690952021-07-16 12:12:00 +0200187 ret = getenv(term->args[0].data.str.area) != NULL;
188 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200189
190 case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature
191 const char *p;
192
Willy Tarreauf8690952021-07-16 12:12:00 +0200193 ret = 0; // assume feature not found
194 for (p = build_features; (p = strstr(p, term->args[0].data.str.area)); p++) {
195 if (p > build_features &&
196 (p[term->args[0].data.str.data] == ' ' ||
197 p[term->args[0].data.str.data] == 0)) {
198 if (*(p-1) == '+') { // e.g. "+OPENSSL"
Willy Tarreau66243b42021-07-16 15:39:28 +0200199 ret = 1;
Willy Tarreauf8690952021-07-16 12:12:00 +0200200 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200201 }
Willy Tarreauf8690952021-07-16 12:12:00 +0200202 else if (*(p-1) == '-') { // e.g. "-OPENSSL"
Willy Tarreau66243b42021-07-16 15:39:28 +0200203 ret = 0;
Willy Tarreauf8690952021-07-16 12:12:00 +0200204 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200205 }
206 /* it was a sub-word, let's restart from next place */
207 }
208 }
Willy Tarreauf8690952021-07-16 12:12:00 +0200209 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200210 }
211 case CFG_PRED_STREQ: // checks if the two arg are equal
Willy Tarreauf8690952021-07-16 12:12:00 +0200212 ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) == 0;
213 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200214
215 case CFG_PRED_STRNEQ: // checks if the two arg are different
Willy Tarreauf8690952021-07-16 12:12:00 +0200216 ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) != 0;
217 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200218
219 case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one
Willy Tarreauf8690952021-07-16 12:12:00 +0200220 ret = compare_current_version(term->args[0].data.str.area) <= 0;
221 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200222
223 case CFG_PRED_VERSION_BEFORE: // checks if the current version is older than this one
Willy Tarreauf8690952021-07-16 12:12:00 +0200224 ret = compare_current_version(term->args[0].data.str.area) > 0;
225 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200226
227 default:
Willy Tarreauf8690952021-07-16 12:12:00 +0200228 memprintf(err, "internal error: unhandled conditional expression predicate '%s'", term->pred->word);
229 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200230 }
231 }
Willy Tarreau316ea7e2021-07-16 14:56:59 +0200232 else if (term->type == CCTT_PAREN) {
233 ret = cfg_eval_cond_expr(term->expr, err);
234 }
Willy Tarreauf8690952021-07-16 12:12:00 +0200235 else {
236 memprintf(err, "internal error: unhandled condition term type %d", (int)term->type);
237 }
Willy Tarreauca56d3d2021-07-16 13:56:54 +0200238
239 if (ret >= 0 && term->neg)
240 ret = !ret;
Willy Tarreauf8690952021-07-16 12:12:00 +0200241 return ret;
242}
243
244
Willy Tarreauca818872021-07-16 14:46:09 +0200245/* Frees <expr> and its terms and args. NULL is supported and does nothing. */
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200246void cfg_free_cond_and(struct cfg_cond_and *expr)
Willy Tarreauca818872021-07-16 14:46:09 +0200247{
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200248 struct cfg_cond_and *prev;
249
250 while (expr) {
251 cfg_free_cond_term(expr->left);
252 prev = expr;
253 expr = expr->right;
254 free(prev);
Willy Tarreauca818872021-07-16 14:46:09 +0200255 }
256}
257
258/* Frees <expr> and its terms and args. NULL is supported and does nothing. */
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200259void cfg_free_cond_expr(struct cfg_cond_expr *expr)
Willy Tarreauca818872021-07-16 14:46:09 +0200260{
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200261 struct cfg_cond_expr *prev;
262
263 while (expr) {
264 cfg_free_cond_and(expr->left);
265 prev = expr;
266 expr = expr->right;
267 free(prev);
Willy Tarreauca818872021-07-16 14:46:09 +0200268 }
269}
270
271/* Parse an indirect input text as a possible config condition sub-expr.
272 * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
273 * success. <expr> is filled with the parsed info, and <text> is updated on
274 * success to point to the first unparsed character, or is left untouched
275 * on failure. On success, the caller will have to free all lower-level
276 * allocated structs using cfg_free_cond_expr(). An error will be set in
277 * <err> on error, and only in this case. In this case the first bad
278 * character will be reported in <errptr>.
279 */
280int cfg_parse_cond_and(const char **text, struct cfg_cond_and **expr, char **err, const char **errptr)
281{
282 struct cfg_cond_and *e;
283 const char *in = *text;
284 int ret = -1;
285
286 if (!*in) /* empty expr does not parse */
287 return 0;
288
289 e = *expr = calloc(1, sizeof(**expr));
290 if (!e) {
291 memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
292 goto done;
293 }
294
295 ret = cfg_parse_cond_term(&in, &e->left, err, errptr);
296 if (ret == -1) // parse error, error already reported
297 goto done;
298
299 if (ret == 0) {
300 /* ret == 0, no other way to parse this */
301 memprintf(err, "unparsable conditional sub-expression '%s'", in);
302 if (errptr)
303 *errptr = in;
304 ret = -1;
305 goto done;
306 }
307
308 /* ret=1, we have a term in the left hand set */
309
310 /* find an optionnal '&&' */
311 while (*in == ' ' || *in == '\t')
312 in++;
313
314 *text = in;
315 if (in[0] != '&' || in[1] != '&')
316 goto done;
317
318 /* we have a '&&', let's parse the right handset's subexp */
319 in += 2;
320 while (*in == ' ' || *in == '\t')
321 in++;
322
323 ret = cfg_parse_cond_and(&in, &e->right, err, errptr);
324 if (ret > 0)
325 *text = in;
326 done:
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200327 if (ret < 0) {
328 cfg_free_cond_and(*expr);
329 *expr = NULL;
330 }
Willy Tarreauca818872021-07-16 14:46:09 +0200331 return ret;
332}
333
334/* Parse an indirect input text as a possible config condition term.
335 * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
336 * success. <expr> is filled with the parsed info, and <text> is updated on
337 * success to point to the first unparsed character, or is left untouched
338 * on failure. On success, the caller will have to free all lower-level
339 * allocated structs using cfg_free_cond_expr(). An error will be set in
340 * <err> on error, and only in this case. In this case the first bad
341 * character will be reported in <errptr>.
342 */
343int cfg_parse_cond_expr(const char **text, struct cfg_cond_expr **expr, char **err, const char **errptr)
344{
345 struct cfg_cond_expr *e;
346 const char *in = *text;
347 int ret = -1;
348
349 if (!*in) /* empty expr does not parse */
350 return 0;
351
352 e = *expr = calloc(1, sizeof(**expr));
353 if (!e) {
354 memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
355 goto done;
356 }
357
358 ret = cfg_parse_cond_and(&in, &e->left, err, errptr);
359 if (ret == -1) // parse error, error already reported
360 goto done;
361
362 if (ret == 0) {
363 /* ret == 0, no other way to parse this */
364 memprintf(err, "unparsable conditional expression '%s'", in);
365 if (errptr)
366 *errptr = in;
367 ret = -1;
368 goto done;
369 }
370
371 /* ret=1, we have a sub-expr in the left hand set */
372
373 /* find an optionnal '||' */
374 while (*in == ' ' || *in == '\t')
375 in++;
376
377 *text = in;
378 if (in[0] != '|' || in[1] != '|')
379 goto done;
380
381 /* we have a '||', let's parse the right handset's subexp */
382 in += 2;
383 while (*in == ' ' || *in == '\t')
384 in++;
385
386 ret = cfg_parse_cond_expr(&in, &e->right, err, errptr);
387 if (ret > 0)
388 *text = in;
389 done:
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200390 if (ret < 0) {
391 cfg_free_cond_expr(*expr);
392 *expr = NULL;
393 }
Willy Tarreauca818872021-07-16 14:46:09 +0200394 return ret;
395}
396
397/* evaluate an sub-expression on a .if/.elif line. The expression is valid and
398 * was already parsed in <expr>. Returns -1 on error (in which case err is
399 * filled with a message, and only in this case), 0 if the condition is false,
400 * 1 if it's true.
401 */
402int cfg_eval_cond_and(struct cfg_cond_and *expr, char **err)
403{
404 int ret;
405
406 /* AND: loop on terms and sub-exp's terms as long as they're TRUE
407 * (stop on FALSE and ERROR).
408 */
409 while ((ret = cfg_eval_cond_term(expr->left, err)) > 0 && expr->right)
410 expr = expr->right;
411 return ret;
412}
413
414/* evaluate an expression on a .if/.elif line. The expression is valid and was
415 * already parsed in <expr>. Returns -1 on error (in which case err is filled
416 * with a message, and only in this case), 0 if the condition is false, 1 if
417 * it's true.
418 */
419int cfg_eval_cond_expr(struct cfg_cond_expr *expr, char **err)
420{
421 int ret;
422
423 /* OR: loop on sub-exps as long as they're FALSE (stop on TRUE and ERROR) */
424 while ((ret = cfg_eval_cond_and(expr->left, err)) == 0 && expr->right)
425 expr = expr->right;
426 return ret;
427}
428
Willy Tarreauf8690952021-07-16 12:12:00 +0200429/* evaluate a condition on a .if/.elif line. The condition is already tokenized
430 * in <err>. Returns -1 on error (in which case err is filled with a message,
431 * and only in this case), 0 if the condition is false, 1 if it's true. If
432 * <errptr> is not NULL, it's set to the first invalid character on error.
433 */
434int cfg_eval_condition(char **args, char **err, const char **errptr)
435{
Willy Tarreauca818872021-07-16 14:46:09 +0200436 struct cfg_cond_expr *expr = NULL;
Willy Tarreauf8690952021-07-16 12:12:00 +0200437 const char *text = args[0];
438 int ret = -1;
439
440 if (!*text) /* note: empty = false */
441 return 0;
442
Willy Tarreauca818872021-07-16 14:46:09 +0200443 ret = cfg_parse_cond_expr(&text, &expr, err, errptr);
Willy Tarreauf8690952021-07-16 12:12:00 +0200444 if (ret != 0) {
445 if (ret == -1) // parse error, error already reported
446 goto done;
Willy Tarreau379ceea2021-07-16 16:18:03 +0200447 while (*text == ' ' || *text == '\t')
448 text++;
449
450 if (*text) {
451 ret = -1;
452 memprintf(err, "unexpected character '%c' at the end of conditional expression '%s'",
453 *text, args[0]);
454 goto fail;
455 }
456
Willy Tarreauca818872021-07-16 14:46:09 +0200457 ret = cfg_eval_cond_expr(expr, err);
Willy Tarreauf8690952021-07-16 12:12:00 +0200458 goto done;
459 }
Willy Tarreau66243b42021-07-16 15:39:28 +0200460
Willy Tarreauf8690952021-07-16 12:12:00 +0200461 /* ret == 0, no other way to parse this */
462 ret = -1;
Willy Tarreau66243b42021-07-16 15:39:28 +0200463 memprintf(err, "unparsable conditional expression '%s'", args[0]);
Willy Tarreau379ceea2021-07-16 16:18:03 +0200464 fail:
Willy Tarreau66243b42021-07-16 15:39:28 +0200465 if (errptr)
Willy Tarreauf8690952021-07-16 12:12:00 +0200466 *errptr = text;
Willy Tarreau66243b42021-07-16 15:39:28 +0200467 done:
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200468 cfg_free_cond_expr(expr);
Willy Tarreau66243b42021-07-16 15:39:28 +0200469 return ret;
470}