Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 1 | /* |
| 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 */ |
| 20 | const 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 | */ |
| 33 | const 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 Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 48 | /* Frees <term> and its args. NULL is supported and does nothing. */ |
| 49 | void 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 Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 59 | /* 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 Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 61 | * 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 Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 65 | * in this case. In this case the first bad character will be reported in |
| 66 | * <errptr>. |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 67 | */ |
Willy Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 68 | int cfg_parse_cond_term(const char **text, struct cfg_cond_term **term, char **err, const char **errptr) |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 69 | { |
Willy Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 70 | struct cfg_cond_term *t; |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 71 | const char *in = *text; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 72 | const char *end_ptr; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 73 | int err_arg; |
| 74 | int nbargs; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 75 | char *end; |
| 76 | long val; |
| 77 | |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 78 | while (*in == ' ' || *in == '\t') |
| 79 | in++; |
| 80 | |
| 81 | if (!*in) /* empty term does not parse */ |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 82 | return 0; |
| 83 | |
Willy Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 84 | 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 Tarreau | ca56d3d | 2021-07-16 13:56:54 +0200 | [diff] [blame] | 94 | /* !<term> negates the term. White spaces permitted */ |
| 95 | while (*in == '!') { |
Willy Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 96 | t->neg = !t->neg; |
Willy Tarreau | ca56d3d | 2021-07-16 13:56:54 +0200 | [diff] [blame] | 97 | do { in++; } while (*in == ' ' || *in == '\t'); |
| 98 | } |
| 99 | |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 100 | val = strtol(in, &end, 0); |
| 101 | if (end != in) { |
Willy Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 102 | t->type = val ? CCTT_TRUE : CCTT_FALSE; |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 103 | *text = end; |
| 104 | return 1; |
| 105 | } |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 106 | |
| 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 Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 110 | 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 Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 115 | &end_ptr, &err_arg, NULL); |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 116 | if (nbargs < 0) { |
Willy Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 117 | memprintf(err, "%s in argument %d of predicate '%s' used in conditional expression", *err, err_arg, t->pred->word); |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 118 | if (errptr) |
| 119 | *errptr = end_ptr; |
Willy Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 120 | goto fail2; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 121 | } |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 122 | *text = end_ptr; |
| 123 | return 1; |
| 124 | } |
| 125 | |
| 126 | memprintf(err, "unparsable conditional expression '%s'", *text); |
Willy Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 127 | fail1: |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 128 | if (errptr) |
| 129 | *errptr = *text; |
Willy Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 130 | fail2: |
| 131 | cfg_free_cond_term(term); |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 132 | 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 | */ |
| 140 | int cfg_eval_cond_term(const struct cfg_cond_term *term, char **err) |
| 141 | { |
| 142 | int ret = -1; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 143 | |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 144 | 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 Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 151 | */ |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 152 | switch (term->pred->prd) { |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 153 | case CFG_PRED_DEFINED: // checks if arg exists as an environment variable |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 154 | ret = getenv(term->args[0].data.str.area) != NULL; |
| 155 | break; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 156 | |
| 157 | case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature |
| 158 | const char *p; |
| 159 | |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 160 | 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 Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 166 | ret = 1; |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 167 | break; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 168 | } |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 169 | else if (*(p-1) == '-') { // e.g. "-OPENSSL" |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 170 | ret = 0; |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 171 | break; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 172 | } |
| 173 | /* it was a sub-word, let's restart from next place */ |
| 174 | } |
| 175 | } |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 176 | break; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 177 | } |
| 178 | case CFG_PRED_STREQ: // checks if the two arg are equal |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 179 | ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) == 0; |
| 180 | break; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 181 | |
| 182 | case CFG_PRED_STRNEQ: // checks if the two arg are different |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 183 | ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) != 0; |
| 184 | break; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 185 | |
| 186 | case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 187 | ret = compare_current_version(term->args[0].data.str.area) <= 0; |
| 188 | break; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 189 | |
| 190 | case CFG_PRED_VERSION_BEFORE: // checks if the current version is older than this one |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 191 | ret = compare_current_version(term->args[0].data.str.area) > 0; |
| 192 | break; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 193 | |
| 194 | default: |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 195 | memprintf(err, "internal error: unhandled conditional expression predicate '%s'", term->pred->word); |
| 196 | break; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 197 | } |
| 198 | } |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 199 | else { |
| 200 | memprintf(err, "internal error: unhandled condition term type %d", (int)term->type); |
| 201 | } |
Willy Tarreau | ca56d3d | 2021-07-16 13:56:54 +0200 | [diff] [blame] | 202 | |
| 203 | if (ret >= 0 && term->neg) |
| 204 | ret = !ret; |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 205 | 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 | */ |
| 214 | int cfg_eval_condition(char **args, char **err, const char **errptr) |
| 215 | { |
Willy Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 216 | struct cfg_cond_term *term = NULL; |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 217 | 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 Tarreau | 379ceea | 2021-07-16 16:18:03 +0200 | [diff] [blame] | 227 | 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 Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 237 | ret = cfg_eval_cond_term(term, err); |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 238 | goto done; |
| 239 | } |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 240 | |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 241 | /* ret == 0, no other way to parse this */ |
| 242 | ret = -1; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 243 | memprintf(err, "unparsable conditional expression '%s'", args[0]); |
Willy Tarreau | 379ceea | 2021-07-16 16:18:03 +0200 | [diff] [blame] | 244 | fail: |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 245 | if (errptr) |
Willy Tarreau | f869095 | 2021-07-16 12:12:00 +0200 | [diff] [blame] | 246 | *errptr = text; |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 247 | done: |
Willy Tarreau | 087b2d0 | 2021-07-16 14:27:20 +0200 | [diff] [blame^] | 248 | cfg_free_cond_term(&term); |
Willy Tarreau | 66243b4 | 2021-07-16 15:39:28 +0200 | [diff] [blame] | 249 | return ret; |
| 250 | } |