blob: 5b0aeecf5fa5fa00749cf096be2863ac32fa5fd8 [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. */
49void cfg_free_cond_term(struct cfg_cond_term **term)
50{
51 if (!term || !*term)
52 return;
53
54 free_args((*term)->args);
55 free((*term)->args);
56 ha_free(term);
57}
58
Willy Tarreauf8690952021-07-16 12:12:00 +020059/* Parse an indirect input text as a possible config condition term.
60 * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
Willy Tarreau087b2d02021-07-16 14:27:20 +020061 * success. <term> is allocated and filled with the parsed info, and <text>
62 * is updated on success to point to the first unparsed character, or is left
63 * untouched on failure. On success, the caller must free <term> using
64 * cfg_free_cond_term(). An error will be set in <err> on error, and only
Willy Tarreauf8690952021-07-16 12:12:00 +020065 * in this case. In this case the first bad character will be reported in
66 * <errptr>.
Willy Tarreau66243b42021-07-16 15:39:28 +020067 */
Willy Tarreau087b2d02021-07-16 14:27:20 +020068int cfg_parse_cond_term(const char **text, struct cfg_cond_term **term, char **err, const char **errptr)
Willy Tarreau66243b42021-07-16 15:39:28 +020069{
Willy Tarreau087b2d02021-07-16 14:27:20 +020070 struct cfg_cond_term *t;
Willy Tarreauf8690952021-07-16 12:12:00 +020071 const char *in = *text;
Willy Tarreau66243b42021-07-16 15:39:28 +020072 const char *end_ptr;
Willy Tarreau66243b42021-07-16 15:39:28 +020073 int err_arg;
74 int nbargs;
Willy Tarreau66243b42021-07-16 15:39:28 +020075 char *end;
76 long val;
77
Willy Tarreauf8690952021-07-16 12:12:00 +020078 while (*in == ' ' || *in == '\t')
79 in++;
80
81 if (!*in) /* empty term does not parse */
Willy Tarreau66243b42021-07-16 15:39:28 +020082 return 0;
83
Willy Tarreau087b2d02021-07-16 14:27:20 +020084 t = *term = calloc(1, sizeof(**term));
85 if (!t) {
86 memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
87 goto fail1;
88 }
89
90 t->type = CCTT_NONE;
91 t->args = NULL;
92 t->neg = 0;
93
Willy Tarreauca56d3d2021-07-16 13:56:54 +020094 /* !<term> negates the term. White spaces permitted */
95 while (*in == '!') {
Willy Tarreau087b2d02021-07-16 14:27:20 +020096 t->neg = !t->neg;
Willy Tarreauca56d3d2021-07-16 13:56:54 +020097 do { in++; } while (*in == ' ' || *in == '\t');
98 }
99
Willy Tarreauf8690952021-07-16 12:12:00 +0200100 val = strtol(in, &end, 0);
101 if (end != in) {
Willy Tarreau087b2d02021-07-16 14:27:20 +0200102 t->type = val ? CCTT_TRUE : CCTT_FALSE;
Willy Tarreauf8690952021-07-16 12:12:00 +0200103 *text = end;
104 return 1;
105 }
Willy Tarreau66243b42021-07-16 15:39:28 +0200106
107 /* below we'll likely all make_arg_list() so we must return only via
108 * the <done> label which frees the arg list.
109 */
Willy Tarreau087b2d02021-07-16 14:27:20 +0200110 t->pred = cfg_lookup_cond_pred(in);
111 if (t->pred) {
112 t->type = CCTT_PRED;
113 nbargs = make_arg_list(in + strlen(t->pred->word), -1,
114 t->pred->arg_mask, &t->args, err,
Willy Tarreau66243b42021-07-16 15:39:28 +0200115 &end_ptr, &err_arg, NULL);
Willy Tarreau66243b42021-07-16 15:39:28 +0200116 if (nbargs < 0) {
Willy Tarreau087b2d02021-07-16 14:27:20 +0200117 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 +0200118 if (errptr)
119 *errptr = end_ptr;
Willy Tarreau087b2d02021-07-16 14:27:20 +0200120 goto fail2;
Willy Tarreau66243b42021-07-16 15:39:28 +0200121 }
Willy Tarreauf8690952021-07-16 12:12:00 +0200122 *text = end_ptr;
123 return 1;
124 }
125
126 memprintf(err, "unparsable conditional expression '%s'", *text);
Willy Tarreau087b2d02021-07-16 14:27:20 +0200127 fail1:
Willy Tarreauf8690952021-07-16 12:12:00 +0200128 if (errptr)
129 *errptr = *text;
Willy Tarreau087b2d02021-07-16 14:27:20 +0200130 fail2:
131 cfg_free_cond_term(term);
Willy Tarreauf8690952021-07-16 12:12:00 +0200132 return -1;
133}
134
135/* evaluate a condition term on a .if/.elif line. The condition was already
136 * parsed in <term>. Returns -1 on error (in which case err is filled with a
137 * message, and only in this case), 0 if the condition is false, 1 if it's
138 * true.
139 */
140int cfg_eval_cond_term(const struct cfg_cond_term *term, char **err)
141{
142 int ret = -1;
Willy Tarreau66243b42021-07-16 15:39:28 +0200143
Willy Tarreauf8690952021-07-16 12:12:00 +0200144 if (term->type == CCTT_FALSE)
145 ret = 0;
146 else if (term->type == CCTT_TRUE)
147 ret = 1;
148 else if (term->type == CCTT_PRED) {
149 /* here we know we have a valid predicate with valid arguments
150 * placed in term->args (which the caller will free).
Willy Tarreau66243b42021-07-16 15:39:28 +0200151 */
Willy Tarreauf8690952021-07-16 12:12:00 +0200152 switch (term->pred->prd) {
Willy Tarreau66243b42021-07-16 15:39:28 +0200153 case CFG_PRED_DEFINED: // checks if arg exists as an environment variable
Willy Tarreauf8690952021-07-16 12:12:00 +0200154 ret = getenv(term->args[0].data.str.area) != NULL;
155 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200156
157 case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature
158 const char *p;
159
Willy Tarreauf8690952021-07-16 12:12:00 +0200160 ret = 0; // assume feature not found
161 for (p = build_features; (p = strstr(p, term->args[0].data.str.area)); p++) {
162 if (p > build_features &&
163 (p[term->args[0].data.str.data] == ' ' ||
164 p[term->args[0].data.str.data] == 0)) {
165 if (*(p-1) == '+') { // e.g. "+OPENSSL"
Willy Tarreau66243b42021-07-16 15:39:28 +0200166 ret = 1;
Willy Tarreauf8690952021-07-16 12:12:00 +0200167 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200168 }
Willy Tarreauf8690952021-07-16 12:12:00 +0200169 else if (*(p-1) == '-') { // e.g. "-OPENSSL"
Willy Tarreau66243b42021-07-16 15:39:28 +0200170 ret = 0;
Willy Tarreauf8690952021-07-16 12:12:00 +0200171 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200172 }
173 /* it was a sub-word, let's restart from next place */
174 }
175 }
Willy Tarreauf8690952021-07-16 12:12:00 +0200176 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200177 }
178 case CFG_PRED_STREQ: // checks if the two arg are equal
Willy Tarreauf8690952021-07-16 12:12:00 +0200179 ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) == 0;
180 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200181
182 case CFG_PRED_STRNEQ: // checks if the two arg are different
Willy Tarreauf8690952021-07-16 12:12:00 +0200183 ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) != 0;
184 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200185
186 case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one
Willy Tarreauf8690952021-07-16 12:12:00 +0200187 ret = compare_current_version(term->args[0].data.str.area) <= 0;
188 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200189
190 case CFG_PRED_VERSION_BEFORE: // checks if the current version is older than this one
Willy Tarreauf8690952021-07-16 12:12:00 +0200191 ret = compare_current_version(term->args[0].data.str.area) > 0;
192 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200193
194 default:
Willy Tarreauf8690952021-07-16 12:12:00 +0200195 memprintf(err, "internal error: unhandled conditional expression predicate '%s'", term->pred->word);
196 break;
Willy Tarreau66243b42021-07-16 15:39:28 +0200197 }
198 }
Willy Tarreauf8690952021-07-16 12:12:00 +0200199 else {
200 memprintf(err, "internal error: unhandled condition term type %d", (int)term->type);
201 }
Willy Tarreauca56d3d2021-07-16 13:56:54 +0200202
203 if (ret >= 0 && term->neg)
204 ret = !ret;
Willy Tarreauf8690952021-07-16 12:12:00 +0200205 return ret;
206}
207
208
209/* evaluate a condition on a .if/.elif line. The condition is already tokenized
210 * in <err>. Returns -1 on error (in which case err is filled with a message,
211 * and only in this case), 0 if the condition is false, 1 if it's true. If
212 * <errptr> is not NULL, it's set to the first invalid character on error.
213 */
214int cfg_eval_condition(char **args, char **err, const char **errptr)
215{
Willy Tarreau087b2d02021-07-16 14:27:20 +0200216 struct cfg_cond_term *term = NULL;
Willy Tarreauf8690952021-07-16 12:12:00 +0200217 const char *text = args[0];
218 int ret = -1;
219
220 if (!*text) /* note: empty = false */
221 return 0;
222
223 ret = cfg_parse_cond_term(&text, &term, err, errptr);
224 if (ret != 0) {
225 if (ret == -1) // parse error, error already reported
226 goto done;
Willy Tarreau379ceea2021-07-16 16:18:03 +0200227 while (*text == ' ' || *text == '\t')
228 text++;
229
230 if (*text) {
231 ret = -1;
232 memprintf(err, "unexpected character '%c' at the end of conditional expression '%s'",
233 *text, args[0]);
234 goto fail;
235 }
236
Willy Tarreau087b2d02021-07-16 14:27:20 +0200237 ret = cfg_eval_cond_term(term, err);
Willy Tarreauf8690952021-07-16 12:12:00 +0200238 goto done;
239 }
Willy Tarreau66243b42021-07-16 15:39:28 +0200240
Willy Tarreauf8690952021-07-16 12:12:00 +0200241 /* ret == 0, no other way to parse this */
242 ret = -1;
Willy Tarreau66243b42021-07-16 15:39:28 +0200243 memprintf(err, "unparsable conditional expression '%s'", args[0]);
Willy Tarreau379ceea2021-07-16 16:18:03 +0200244 fail:
Willy Tarreau66243b42021-07-16 15:39:28 +0200245 if (errptr)
Willy Tarreauf8690952021-07-16 12:12:00 +0200246 *errptr = text;
Willy Tarreau66243b42021-07-16 15:39:28 +0200247 done:
Willy Tarreau087b2d02021-07-16 14:27:20 +0200248 cfg_free_cond_term(&term);
Willy Tarreau66243b42021-07-16 15:39:28 +0200249 return ret;
250}