blob: 117cf6c2891ebfc8e92d0edcdcb029447dae6057 [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>
Willy Tarreau785b89f2023-04-22 15:09:07 +020017#include <haproxy/proto_tcp.h>
Willy Tarreau66243b42021-07-16 15:39:28 +020018#include <haproxy/tools.h>
19
20/* supported condition predicates */
21const struct cond_pred_kw cond_predicates[] = {
Remi Tricot-Le Bretonb01179a2021-10-11 15:34:12 +020022 { "defined", CFG_PRED_DEFINED, ARG1(1, STR) },
23 { "feature", CFG_PRED_FEATURE, ARG1(1, STR) },
24 { "streq", CFG_PRED_STREQ, ARG2(2, STR, STR) },
25 { "strneq", CFG_PRED_STRNEQ, ARG2(2, STR, STR) },
Christopher Fauleta1fdad72023-02-20 17:55:58 +010026 { "strstr", CFG_PRED_STRSTR, ARG2(2, STR, STR) },
Remi Tricot-Le Bretonb01179a2021-10-11 15:34:12 +020027 { "version_atleast", CFG_PRED_VERSION_ATLEAST, ARG1(1, STR) },
28 { "version_before", CFG_PRED_VERSION_BEFORE, ARG1(1, STR) },
29 { "openssl_version_atleast", CFG_PRED_OSSL_VERSION_ATLEAST, ARG1(1, STR) },
30 { "openssl_version_before", CFG_PRED_OSSL_VERSION_BEFORE, ARG1(1, STR) },
31 { "ssllib_name_startswith", CFG_PRED_SSLLIB_NAME_STARTSWITH, ARG1(1, STR) },
Christopher Fauletc13f3022023-02-21 11:16:08 +010032 { "enabled", CFG_PRED_ENABLED, ARG1(1, STR) },
Willy Tarreau66243b42021-07-16 15:39:28 +020033 { NULL, CFG_PRED_NONE, 0 }
34};
35
36/* looks up a cond predicate matching the keyword in <str>, possibly followed
37 * by a parenthesis. Returns a pointer to it or NULL if not found.
38 */
39const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str)
40{
41 const struct cond_pred_kw *ret;
42 int len = strcspn(str, " (");
43
44 for (ret = &cond_predicates[0]; ret->word; ret++) {
45 if (len != strlen(ret->word))
46 continue;
47 if (strncmp(str, ret->word, len) != 0)
48 continue;
49 return ret;
50 }
51 return NULL;
52}
53
Willy Tarreau087b2d02021-07-16 14:27:20 +020054/* Frees <term> and its args. NULL is supported and does nothing. */
Willy Tarreauf1db20c2021-07-17 18:46:30 +020055void cfg_free_cond_term(struct cfg_cond_term *term)
Willy Tarreau087b2d02021-07-16 14:27:20 +020056{
Willy Tarreauf1db20c2021-07-17 18:46:30 +020057 if (!term)
Willy Tarreau087b2d02021-07-16 14:27:20 +020058 return;
59
Willy Tarreauf1db20c2021-07-17 18:46:30 +020060 if (term->type == CCTT_PAREN) {
61 cfg_free_cond_expr(term->expr);
62 term->expr = NULL;
63 }
Willy Tarreau316ea7e2021-07-16 14:56:59 +020064
Willy Tarreauf1db20c2021-07-17 18:46:30 +020065 free_args(term->args);
66 free(term->args);
67 free(term);
Willy Tarreau087b2d02021-07-16 14:27:20 +020068}
69
Willy Tarreauf8690952021-07-16 12:12:00 +020070/* Parse an indirect input text as a possible config condition term.
71 * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
Willy Tarreau087b2d02021-07-16 14:27:20 +020072 * success. <term> is allocated and filled with the parsed info, and <text>
73 * is updated on success to point to the first unparsed character, or is left
74 * untouched on failure. On success, the caller must free <term> using
75 * cfg_free_cond_term(). An error will be set in <err> on error, and only
Willy Tarreauf8690952021-07-16 12:12:00 +020076 * in this case. In this case the first bad character will be reported in
Willy Tarreaudc70c182021-07-20 17:58:34 +020077 * <errptr>. <maxdepth> corresponds to the maximum recursion depth permitted,
78 * it is decremented on each recursive call and the parsing will fail one
79 * reaching <= 0.
Willy Tarreau66243b42021-07-16 15:39:28 +020080 */
Willy Tarreaudc70c182021-07-20 17:58:34 +020081int cfg_parse_cond_term(const char **text, struct cfg_cond_term **term, char **err, const char **errptr, int maxdepth)
Willy Tarreau66243b42021-07-16 15:39:28 +020082{
Willy Tarreau087b2d02021-07-16 14:27:20 +020083 struct cfg_cond_term *t;
Willy Tarreauf8690952021-07-16 12:12:00 +020084 const char *in = *text;
Willy Tarreau66243b42021-07-16 15:39:28 +020085 const char *end_ptr;
Willy Tarreau66243b42021-07-16 15:39:28 +020086 int err_arg;
87 int nbargs;
Willy Tarreau66243b42021-07-16 15:39:28 +020088 char *end;
89 long val;
90
Willy Tarreauf8690952021-07-16 12:12:00 +020091 while (*in == ' ' || *in == '\t')
92 in++;
93
94 if (!*in) /* empty term does not parse */
Willy Tarreau66243b42021-07-16 15:39:28 +020095 return 0;
96
Willy Tarreaudc70c182021-07-20 17:58:34 +020097 *term = NULL;
98 if (maxdepth <= 0)
99 goto fail0;
100
Willy Tarreau087b2d02021-07-16 14:27:20 +0200101 t = *term = calloc(1, sizeof(**term));
102 if (!t) {
103 memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
104 goto fail1;
105 }
106
107 t->type = CCTT_NONE;
108 t->args = NULL;
109 t->neg = 0;
110
Willy Tarreauca56d3d2021-07-16 13:56:54 +0200111 /* !<term> negates the term. White spaces permitted */
112 while (*in == '!') {
Willy Tarreau087b2d02021-07-16 14:27:20 +0200113 t->neg = !t->neg;
Willy Tarreauca56d3d2021-07-16 13:56:54 +0200114 do { in++; } while (*in == ' ' || *in == '\t');
115 }
116
Willy Tarreauf8690952021-07-16 12:12:00 +0200117 val = strtol(in, &end, 0);
118 if (end != in) {
Willy Tarreau087b2d02021-07-16 14:27:20 +0200119 t->type = val ? CCTT_TRUE : CCTT_FALSE;
Willy Tarreauf8690952021-07-16 12:12:00 +0200120 *text = end;
121 return 1;
122 }
Willy Tarreau66243b42021-07-16 15:39:28 +0200123
Willy Tarreau316ea7e2021-07-16 14:56:59 +0200124 /* Try to parse '(' EXPR ')' */
125 if (*in == '(') {
126 int ret;
127
128 t->type = CCTT_PAREN;
129 t->args = NULL;
130
131 do { in++; } while (*in == ' ' || *in == '\t');
Willy Tarreaudc70c182021-07-20 17:58:34 +0200132 ret = cfg_parse_cond_expr(&in, &t->expr, err, errptr, maxdepth - 1);
Willy Tarreau316ea7e2021-07-16 14:56:59 +0200133 if (ret == -1)
134 goto fail2;
135 if (ret == 0)
136 goto fail0;
137
138 /* find the closing ')' */
139 while (*in == ' ' || *in == '\t')
140 in++;
141 if (*in != ')') {
142 memprintf(err, "expected ')' after conditional expression '%s'", *text);
143 goto fail1;
144 }
145 do { in++; } while (*in == ' ' || *in == '\t');
146 *text = in;
147 return 1;
148 }
149
Willy Tarreau66243b42021-07-16 15:39:28 +0200150 /* below we'll likely all make_arg_list() so we must return only via
151 * the <done> label which frees the arg list.
152 */
Willy Tarreau087b2d02021-07-16 14:27:20 +0200153 t->pred = cfg_lookup_cond_pred(in);
154 if (t->pred) {
155 t->type = CCTT_PRED;
156 nbargs = make_arg_list(in + strlen(t->pred->word), -1,
157 t->pred->arg_mask, &t->args, err,
Willy Tarreau66243b42021-07-16 15:39:28 +0200158 &end_ptr, &err_arg, NULL);
Willy Tarreau66243b42021-07-16 15:39:28 +0200159 if (nbargs < 0) {
Willy Tarreau087b2d02021-07-16 14:27:20 +0200160 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 +0200161 if (errptr)
162 *errptr = end_ptr;
Willy Tarreau087b2d02021-07-16 14:27:20 +0200163 goto fail2;
Willy Tarreau66243b42021-07-16 15:39:28 +0200164 }
Willy Tarreauf8690952021-07-16 12:12:00 +0200165 *text = end_ptr;
166 return 1;
167 }
168
Willy Tarreau316ea7e2021-07-16 14:56:59 +0200169 fail0:
Willy Tarreauf8690952021-07-16 12:12:00 +0200170 memprintf(err, "unparsable conditional expression '%s'", *text);
Willy Tarreau087b2d02021-07-16 14:27:20 +0200171 fail1:
Willy Tarreauf8690952021-07-16 12:12:00 +0200172 if (errptr)
173 *errptr = *text;
Willy Tarreau087b2d02021-07-16 14:27:20 +0200174 fail2:
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200175 cfg_free_cond_term(*term);
176 *term = NULL;
Willy Tarreauf8690952021-07-16 12:12:00 +0200177 return -1;
178}
179
Christopher Fauletc13f3022023-02-21 11:16:08 +0100180/* evaluate a "enabled" expression. Only a subset of options are matched. It
181 * returns 1 if the option is enabled. 0 is returned is the option is not
182 * enabled or if it is not recognized.
183 */
184static int cfg_eval_cond_enabled(const char *str)
185{
186 if (strcmp(str, "POLL") == 0)
187 return !!(global.tune.options & GTUNE_USE_POLL);
188 else if (strcmp(str, "EPOLL") == 0)
189 return !!(global.tune.options & GTUNE_USE_EPOLL);
190 else if (strcmp(str, "KQUEUE") == 0)
191 return !!(global.tune.options & GTUNE_USE_EPOLL);
192 else if (strcmp(str, "EVPORTS") == 0)
193 return !!(global.tune.options & GTUNE_USE_EVPORTS);
194 else if (strcmp(str, "SPLICE") == 0)
195 return !!(global.tune.options & GTUNE_USE_SPLICE);
196 else if (strcmp(str, "GETADDRINFO") == 0)
197 return !!(global.tune.options & GTUNE_USE_GAI);
198 else if (strcmp(str, "REUSEPORT") == 0)
Willy Tarreau785b89f2023-04-22 15:09:07 +0200199 return !!(proto_tcpv4.flags & PROTO_F_REUSEPORT_SUPPORTED);
Christopher Fauletc13f3022023-02-21 11:16:08 +0100200 else if (strcmp(str, "FAST-FORWARD") == 0)
201 return !!(global.tune.options & GTUNE_USE_FAST_FWD);
202 else if (strcmp(str, "SERVER-SSL-VERIFY-NONE") == 0)
203 return !!(global.ssl_server_verify == SSL_SERVER_VERIFY_NONE);
204 return 0;
205}
206
Willy Tarreauf8690952021-07-16 12:12:00 +0200207/* evaluate a condition term on a .if/.elif line. The condition was already
208 * parsed in <term>. Returns -1 on error (in which case err is filled with a
209 * message, and only in this case), 0 if the condition is false, 1 if it's
210 * true.
211 */
212int cfg_eval_cond_term(const struct cfg_cond_term *term, char **err)
213{
214 int ret = -1;
Willy Tarreau66243b42021-07-16 15:39:28 +0200215
Willy Tarreauf8690952021-07-16 12:12:00 +0200216 if (term->type == CCTT_FALSE)
217 ret = 0;
218 else if (term->type == CCTT_TRUE)
219 ret = 1;
220 else if (term->type == CCTT_PRED) {
221 /* here we know we have a valid predicate with valid arguments
222 * placed in term->args (which the caller will free).
Willy Tarreau66243b42021-07-16 15:39:28 +0200223 */
Willy Tarreauf8690952021-07-16 12:12:00 +0200224 switch (term->pred->prd) {
Willy Tarreau66243b42021-07-16 15:39:28 +0200225 case CFG_PRED_DEFINED: // checks if arg exists as an environment variable
Willy Tarreauf8690952021-07-16 12:12:00 +0200226 ret = getenv(term->args[0].data.str.area) != NULL;
227 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200228
229 case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature
230 const char *p;
231
Willy Tarreauf8690952021-07-16 12:12:00 +0200232 ret = 0; // assume feature not found
233 for (p = build_features; (p = strstr(p, term->args[0].data.str.area)); p++) {
234 if (p > build_features &&
235 (p[term->args[0].data.str.data] == ' ' ||
236 p[term->args[0].data.str.data] == 0)) {
237 if (*(p-1) == '+') { // e.g. "+OPENSSL"
Willy Tarreau66243b42021-07-16 15:39:28 +0200238 ret = 1;
Willy Tarreauf8690952021-07-16 12:12:00 +0200239 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200240 }
Willy Tarreauf8690952021-07-16 12:12:00 +0200241 else if (*(p-1) == '-') { // e.g. "-OPENSSL"
Willy Tarreau66243b42021-07-16 15:39:28 +0200242 ret = 0;
Willy Tarreauf8690952021-07-16 12:12:00 +0200243 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200244 }
245 /* it was a sub-word, let's restart from next place */
246 }
247 }
Willy Tarreauf8690952021-07-16 12:12:00 +0200248 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200249 }
250 case CFG_PRED_STREQ: // checks if the two arg are equal
Willy Tarreauf8690952021-07-16 12:12:00 +0200251 ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) == 0;
252 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200253
254 case CFG_PRED_STRNEQ: // checks if the two arg are different
Willy Tarreauf8690952021-07-16 12:12:00 +0200255 ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) != 0;
256 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200257
Christopher Fauleta1fdad72023-02-20 17:55:58 +0100258 case CFG_PRED_STRSTR: // checks if the 2nd arg is found in the first one
259 ret = strstr(term->args[0].data.str.area, term->args[1].data.str.area) != NULL;
260 break;
261
Willy Tarreau66243b42021-07-16 15:39:28 +0200262 case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one
Willy Tarreauf8690952021-07-16 12:12:00 +0200263 ret = compare_current_version(term->args[0].data.str.area) <= 0;
264 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200265
266 case CFG_PRED_VERSION_BEFORE: // checks if the current version is older than this one
Willy Tarreauf8690952021-07-16 12:12:00 +0200267 ret = compare_current_version(term->args[0].data.str.area) > 0;
268 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200269
William Lallemand3aeb3f92021-08-21 23:59:56 +0200270 case CFG_PRED_OSSL_VERSION_ATLEAST: { // checks if the current openssl version is at least this one
271 int opensslret = openssl_compare_current_version(term->args[0].data.str.area);
272
273 if (opensslret < -1) /* can't parse the string or no openssl available */
274 ret = -1;
275 else
276 ret = opensslret <= 0;
277 break;
278 }
279 case CFG_PRED_OSSL_VERSION_BEFORE: { // checks if the current openssl version is older than this one
280 int opensslret = openssl_compare_current_version(term->args[0].data.str.area);
281
282 if (opensslret < -1) /* can't parse the string or no openssl available */
283 ret = -1;
284 else
285 ret = opensslret > 0;
286 break;
287 }
Remi Tricot-Le Bretonb01179a2021-10-11 15:34:12 +0200288 case CFG_PRED_SSLLIB_NAME_STARTSWITH: { // checks if the current SSL library's name starts with a specified string (can be used to distinguish OpenSSL from LibreSSL or BoringSSL)
289 ret = openssl_compare_current_name(term->args[0].data.str.area) == 0;
290 break;
291 }
Christopher Fauletc13f3022023-02-21 11:16:08 +0100292 case CFG_PRED_ENABLED: { // checks if the arg matches on a subset of enabled options
293 ret = cfg_eval_cond_enabled(term->args[0].data.str.area) != 0;
294 break;
295 }
Willy Tarreau66243b42021-07-16 15:39:28 +0200296 default:
Willy Tarreauf8690952021-07-16 12:12:00 +0200297 memprintf(err, "internal error: unhandled conditional expression predicate '%s'", term->pred->word);
298 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200299 }
300 }
Willy Tarreau316ea7e2021-07-16 14:56:59 +0200301 else if (term->type == CCTT_PAREN) {
302 ret = cfg_eval_cond_expr(term->expr, err);
303 }
Willy Tarreauf8690952021-07-16 12:12:00 +0200304 else {
305 memprintf(err, "internal error: unhandled condition term type %d", (int)term->type);
306 }
Willy Tarreauca56d3d2021-07-16 13:56:54 +0200307
308 if (ret >= 0 && term->neg)
309 ret = !ret;
Willy Tarreauf8690952021-07-16 12:12:00 +0200310 return ret;
311}
312
313
Willy Tarreauca818872021-07-16 14:46:09 +0200314/* Frees <expr> and its terms and args. NULL is supported and does nothing. */
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200315void cfg_free_cond_and(struct cfg_cond_and *expr)
Willy Tarreauca818872021-07-16 14:46:09 +0200316{
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200317 struct cfg_cond_and *prev;
318
319 while (expr) {
320 cfg_free_cond_term(expr->left);
321 prev = expr;
322 expr = expr->right;
323 free(prev);
Willy Tarreauca818872021-07-16 14:46:09 +0200324 }
325}
326
327/* Frees <expr> and its terms and args. NULL is supported and does nothing. */
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200328void cfg_free_cond_expr(struct cfg_cond_expr *expr)
Willy Tarreauca818872021-07-16 14:46:09 +0200329{
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200330 struct cfg_cond_expr *prev;
331
332 while (expr) {
333 cfg_free_cond_and(expr->left);
334 prev = expr;
335 expr = expr->right;
336 free(prev);
Willy Tarreauca818872021-07-16 14:46:09 +0200337 }
338}
339
340/* Parse an indirect input text as a possible config condition sub-expr.
341 * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
342 * success. <expr> is filled with the parsed info, and <text> is updated on
343 * success to point to the first unparsed character, or is left untouched
344 * on failure. On success, the caller will have to free all lower-level
345 * allocated structs using cfg_free_cond_expr(). An error will be set in
346 * <err> on error, and only in this case. In this case the first bad
Willy Tarreaudc70c182021-07-20 17:58:34 +0200347 * character will be reported in <errptr>. <maxdepth> corresponds to the
348 * maximum recursion depth permitted, it is decremented on each recursive
349 * call and the parsing will fail one reaching <= 0.
Willy Tarreauca818872021-07-16 14:46:09 +0200350 */
Willy Tarreaudc70c182021-07-20 17:58:34 +0200351int cfg_parse_cond_and(const char **text, struct cfg_cond_and **expr, char **err, const char **errptr, int maxdepth)
Willy Tarreauca818872021-07-16 14:46:09 +0200352{
353 struct cfg_cond_and *e;
354 const char *in = *text;
355 int ret = -1;
356
357 if (!*in) /* empty expr does not parse */
358 return 0;
359
Willy Tarreaudc70c182021-07-20 17:58:34 +0200360 *expr = NULL;
361 if (maxdepth <= 0) {
362 memprintf(err, "unparsable conditional sub-expression '%s'", in);
363 if (errptr)
364 *errptr = in;
365 goto done;
366 }
367
Willy Tarreauca818872021-07-16 14:46:09 +0200368 e = *expr = calloc(1, sizeof(**expr));
369 if (!e) {
370 memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
371 goto done;
372 }
373
Willy Tarreaudc70c182021-07-20 17:58:34 +0200374 ret = cfg_parse_cond_term(&in, &e->left, err, errptr, maxdepth - 1);
Willy Tarreauca818872021-07-16 14:46:09 +0200375 if (ret == -1) // parse error, error already reported
376 goto done;
377
378 if (ret == 0) {
379 /* ret == 0, no other way to parse this */
380 memprintf(err, "unparsable conditional sub-expression '%s'", in);
381 if (errptr)
382 *errptr = in;
383 ret = -1;
384 goto done;
385 }
386
387 /* ret=1, we have a term in the left hand set */
388
Ilya Shipitsin01881082021-08-07 14:41:56 +0500389 /* find an optional '&&' */
Willy Tarreauca818872021-07-16 14:46:09 +0200390 while (*in == ' ' || *in == '\t')
391 in++;
392
393 *text = in;
394 if (in[0] != '&' || in[1] != '&')
395 goto done;
396
397 /* we have a '&&', let's parse the right handset's subexp */
398 in += 2;
399 while (*in == ' ' || *in == '\t')
400 in++;
401
Willy Tarreaudc70c182021-07-20 17:58:34 +0200402 ret = cfg_parse_cond_and(&in, &e->right, err, errptr, maxdepth - 1);
Willy Tarreauca818872021-07-16 14:46:09 +0200403 if (ret > 0)
404 *text = in;
405 done:
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200406 if (ret < 0) {
407 cfg_free_cond_and(*expr);
408 *expr = NULL;
409 }
Willy Tarreauca818872021-07-16 14:46:09 +0200410 return ret;
411}
412
413/* Parse an indirect input text as a possible config condition term.
414 * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
415 * success. <expr> is filled with the parsed info, and <text> is updated on
416 * success to point to the first unparsed character, or is left untouched
417 * on failure. On success, the caller will have to free all lower-level
418 * allocated structs using cfg_free_cond_expr(). An error will be set in
419 * <err> on error, and only in this case. In this case the first bad
Willy Tarreaudc70c182021-07-20 17:58:34 +0200420 * character will be reported in <errptr>. <maxdepth> corresponds to the
421 * maximum recursion depth permitted, it is decremented on each recursive call
422 * and the parsing will fail one reaching <= 0.
Willy Tarreauca818872021-07-16 14:46:09 +0200423 */
Willy Tarreaudc70c182021-07-20 17:58:34 +0200424int cfg_parse_cond_expr(const char **text, struct cfg_cond_expr **expr, char **err, const char **errptr, int maxdepth)
Willy Tarreauca818872021-07-16 14:46:09 +0200425{
426 struct cfg_cond_expr *e;
427 const char *in = *text;
428 int ret = -1;
429
430 if (!*in) /* empty expr does not parse */
431 return 0;
432
Willy Tarreaudc70c182021-07-20 17:58:34 +0200433 *expr = NULL;
434 if (maxdepth <= 0) {
435 memprintf(err, "unparsable conditional expression '%s'", in);
436 if (errptr)
437 *errptr = in;
438 goto done;
439 }
440
Willy Tarreauca818872021-07-16 14:46:09 +0200441 e = *expr = calloc(1, sizeof(**expr));
442 if (!e) {
443 memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
444 goto done;
445 }
446
Willy Tarreaudc70c182021-07-20 17:58:34 +0200447 ret = cfg_parse_cond_and(&in, &e->left, err, errptr, maxdepth - 1);
Willy Tarreauca818872021-07-16 14:46:09 +0200448 if (ret == -1) // parse error, error already reported
449 goto done;
450
451 if (ret == 0) {
452 /* ret == 0, no other way to parse this */
453 memprintf(err, "unparsable conditional expression '%s'", in);
454 if (errptr)
455 *errptr = in;
456 ret = -1;
457 goto done;
458 }
459
460 /* ret=1, we have a sub-expr in the left hand set */
461
Ilya Shipitsin01881082021-08-07 14:41:56 +0500462 /* find an optional '||' */
Willy Tarreauca818872021-07-16 14:46:09 +0200463 while (*in == ' ' || *in == '\t')
464 in++;
465
466 *text = in;
467 if (in[0] != '|' || in[1] != '|')
468 goto done;
469
470 /* we have a '||', let's parse the right handset's subexp */
471 in += 2;
472 while (*in == ' ' || *in == '\t')
473 in++;
474
Willy Tarreaudc70c182021-07-20 17:58:34 +0200475 ret = cfg_parse_cond_expr(&in, &e->right, err, errptr, maxdepth - 1);
Willy Tarreauca818872021-07-16 14:46:09 +0200476 if (ret > 0)
477 *text = in;
478 done:
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200479 if (ret < 0) {
480 cfg_free_cond_expr(*expr);
481 *expr = NULL;
482 }
Willy Tarreauca818872021-07-16 14:46:09 +0200483 return ret;
484}
485
486/* evaluate an sub-expression on a .if/.elif line. The expression is valid and
487 * was already parsed in <expr>. Returns -1 on error (in which case err is
488 * filled with a message, and only in this case), 0 if the condition is false,
489 * 1 if it's true.
490 */
491int cfg_eval_cond_and(struct cfg_cond_and *expr, char **err)
492{
493 int ret;
494
495 /* AND: loop on terms and sub-exp's terms as long as they're TRUE
496 * (stop on FALSE and ERROR).
497 */
498 while ((ret = cfg_eval_cond_term(expr->left, err)) > 0 && expr->right)
499 expr = expr->right;
500 return ret;
501}
502
503/* evaluate an expression on a .if/.elif line. The expression is valid and was
504 * already parsed in <expr>. Returns -1 on error (in which case err is filled
505 * with a message, and only in this case), 0 if the condition is false, 1 if
506 * it's true.
507 */
508int cfg_eval_cond_expr(struct cfg_cond_expr *expr, char **err)
509{
510 int ret;
511
512 /* OR: loop on sub-exps as long as they're FALSE (stop on TRUE and ERROR) */
513 while ((ret = cfg_eval_cond_and(expr->left, err)) == 0 && expr->right)
514 expr = expr->right;
515 return ret;
516}
517
Willy Tarreauf8690952021-07-16 12:12:00 +0200518/* evaluate a condition on a .if/.elif line. The condition is already tokenized
519 * in <err>. Returns -1 on error (in which case err is filled with a message,
520 * and only in this case), 0 if the condition is false, 1 if it's true. If
521 * <errptr> is not NULL, it's set to the first invalid character on error.
522 */
523int cfg_eval_condition(char **args, char **err, const char **errptr)
524{
Willy Tarreauca818872021-07-16 14:46:09 +0200525 struct cfg_cond_expr *expr = NULL;
Willy Tarreauf8690952021-07-16 12:12:00 +0200526 const char *text = args[0];
527 int ret = -1;
528
529 if (!*text) /* note: empty = false */
530 return 0;
531
Willy Tarreaudc70c182021-07-20 17:58:34 +0200532 ret = cfg_parse_cond_expr(&text, &expr, err, errptr, MAX_CFG_RECURSION);
Willy Tarreauf8690952021-07-16 12:12:00 +0200533 if (ret != 0) {
534 if (ret == -1) // parse error, error already reported
535 goto done;
Willy Tarreau379ceea2021-07-16 16:18:03 +0200536 while (*text == ' ' || *text == '\t')
537 text++;
538
539 if (*text) {
540 ret = -1;
541 memprintf(err, "unexpected character '%c' at the end of conditional expression '%s'",
542 *text, args[0]);
543 goto fail;
544 }
545
Willy Tarreauca818872021-07-16 14:46:09 +0200546 ret = cfg_eval_cond_expr(expr, err);
Willy Tarreauf8690952021-07-16 12:12:00 +0200547 goto done;
548 }
Willy Tarreau66243b42021-07-16 15:39:28 +0200549
Willy Tarreauf8690952021-07-16 12:12:00 +0200550 /* ret == 0, no other way to parse this */
551 ret = -1;
Willy Tarreau66243b42021-07-16 15:39:28 +0200552 memprintf(err, "unparsable conditional expression '%s'", args[0]);
Willy Tarreau379ceea2021-07-16 16:18:03 +0200553 fail:
Willy Tarreau66243b42021-07-16 15:39:28 +0200554 if (errptr)
Willy Tarreauf8690952021-07-16 12:12:00 +0200555 *errptr = text;
Willy Tarreau66243b42021-07-16 15:39:28 +0200556 done:
Willy Tarreauf1db20c2021-07-17 18:46:30 +0200557 cfg_free_cond_expr(expr);
Willy Tarreau66243b42021-07-16 15:39:28 +0200558 return ret;
559}