summaryrefslogtreecommitdiffstats
path: root/src/cfgcond.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/cfgcond.c521
1 files changed, 521 insertions, 0 deletions
diff --git a/src/cfgcond.c b/src/cfgcond.c
new file mode 100644
index 0000000..5fb7069
--- /dev/null
+++ b/src/cfgcond.c
@@ -0,0 +1,521 @@
+/*
+ * Configuration condition preprocessor
+ *
+ * Copyright 2000-2021 Willy Tarreau <w@1wt.eu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <haproxy/api.h>
+#include <haproxy/arg.h>
+#include <haproxy/cfgcond.h>
+#include <haproxy/global.h>
+#include <haproxy/tools.h>
+
+/* supported condition predicates */
+const struct cond_pred_kw cond_predicates[] = {
+ { "defined", CFG_PRED_DEFINED, ARG1(1, STR) },
+ { "feature", CFG_PRED_FEATURE, ARG1(1, STR) },
+ { "streq", CFG_PRED_STREQ, ARG2(2, STR, STR) },
+ { "strneq", CFG_PRED_STRNEQ, ARG2(2, STR, STR) },
+ { "version_atleast", CFG_PRED_VERSION_ATLEAST, ARG1(1, STR) },
+ { "version_before", CFG_PRED_VERSION_BEFORE, ARG1(1, STR) },
+ { "openssl_version_atleast", CFG_PRED_OSSL_VERSION_ATLEAST, ARG1(1, STR) },
+ { "openssl_version_before", CFG_PRED_OSSL_VERSION_BEFORE, ARG1(1, STR) },
+ { "ssllib_name_startswith", CFG_PRED_SSLLIB_NAME_STARTSWITH, ARG1(1, STR) },
+ { NULL, CFG_PRED_NONE, 0 }
+};
+
+/* looks up a cond predicate matching the keyword in <str>, possibly followed
+ * by a parenthesis. Returns a pointer to it or NULL if not found.
+ */
+const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str)
+{
+ const struct cond_pred_kw *ret;
+ int len = strcspn(str, " (");
+
+ for (ret = &cond_predicates[0]; ret->word; ret++) {
+ if (len != strlen(ret->word))
+ continue;
+ if (strncmp(str, ret->word, len) != 0)
+ continue;
+ return ret;
+ }
+ return NULL;
+}
+
+/* Frees <term> and its args. NULL is supported and does nothing. */
+void cfg_free_cond_term(struct cfg_cond_term *term)
+{
+ if (!term)
+ return;
+
+ if (term->type == CCTT_PAREN) {
+ cfg_free_cond_expr(term->expr);
+ term->expr = NULL;
+ }
+
+ free_args(term->args);
+ free(term->args);
+ free(term);
+}
+
+/* Parse an indirect input text as a possible config condition term.
+ * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
+ * success. <term> is allocated and filled with the parsed info, and <text>
+ * is updated on success to point to the first unparsed character, or is left
+ * untouched on failure. On success, the caller must free <term> using
+ * cfg_free_cond_term(). An error will be set in <err> on error, and only
+ * in this case. In this case the first bad character will be reported in
+ * <errptr>. <maxdepth> corresponds to the maximum recursion depth permitted,
+ * it is decremented on each recursive call and the parsing will fail one
+ * reaching <= 0.
+ */
+int cfg_parse_cond_term(const char **text, struct cfg_cond_term **term, char **err, const char **errptr, int maxdepth)
+{
+ struct cfg_cond_term *t;
+ const char *in = *text;
+ const char *end_ptr;
+ int err_arg;
+ int nbargs;
+ char *end;
+ long val;
+
+ while (*in == ' ' || *in == '\t')
+ in++;
+
+ if (!*in) /* empty term does not parse */
+ return 0;
+
+ *term = NULL;
+ if (maxdepth <= 0)
+ goto fail0;
+
+ t = *term = calloc(1, sizeof(**term));
+ if (!t) {
+ memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
+ goto fail1;
+ }
+
+ t->type = CCTT_NONE;
+ t->args = NULL;
+ t->neg = 0;
+
+ /* !<term> negates the term. White spaces permitted */
+ while (*in == '!') {
+ t->neg = !t->neg;
+ do { in++; } while (*in == ' ' || *in == '\t');
+ }
+
+ val = strtol(in, &end, 0);
+ if (end != in) {
+ t->type = val ? CCTT_TRUE : CCTT_FALSE;
+ *text = end;
+ return 1;
+ }
+
+ /* Try to parse '(' EXPR ')' */
+ if (*in == '(') {
+ int ret;
+
+ t->type = CCTT_PAREN;
+ t->args = NULL;
+
+ do { in++; } while (*in == ' ' || *in == '\t');
+ ret = cfg_parse_cond_expr(&in, &t->expr, err, errptr, maxdepth - 1);
+ if (ret == -1)
+ goto fail2;
+ if (ret == 0)
+ goto fail0;
+
+ /* find the closing ')' */
+ while (*in == ' ' || *in == '\t')
+ in++;
+ if (*in != ')') {
+ memprintf(err, "expected ')' after conditional expression '%s'", *text);
+ goto fail1;
+ }
+ do { in++; } while (*in == ' ' || *in == '\t');
+ *text = in;
+ return 1;
+ }
+
+ /* below we'll likely all make_arg_list() so we must return only via
+ * the <done> label which frees the arg list.
+ */
+ t->pred = cfg_lookup_cond_pred(in);
+ if (t->pred) {
+ t->type = CCTT_PRED;
+ nbargs = make_arg_list(in + strlen(t->pred->word), -1,
+ t->pred->arg_mask, &t->args, err,
+ &end_ptr, &err_arg, NULL);
+ if (nbargs < 0) {
+ memprintf(err, "%s in argument %d of predicate '%s' used in conditional expression", *err, err_arg, t->pred->word);
+ if (errptr)
+ *errptr = end_ptr;
+ goto fail2;
+ }
+ *text = end_ptr;
+ return 1;
+ }
+
+ fail0:
+ memprintf(err, "unparsable conditional expression '%s'", *text);
+ fail1:
+ if (errptr)
+ *errptr = *text;
+ fail2:
+ cfg_free_cond_term(*term);
+ *term = NULL;
+ return -1;
+}
+
+/* evaluate a condition term on a .if/.elif line. The condition was already
+ * parsed in <term>. Returns -1 on error (in which case err is filled with a
+ * message, and only in this case), 0 if the condition is false, 1 if it's
+ * true.
+ */
+int cfg_eval_cond_term(const struct cfg_cond_term *term, char **err)
+{
+ int ret = -1;
+
+ if (term->type == CCTT_FALSE)
+ ret = 0;
+ else if (term->type == CCTT_TRUE)
+ ret = 1;
+ else if (term->type == CCTT_PRED) {
+ /* here we know we have a valid predicate with valid arguments
+ * placed in term->args (which the caller will free).
+ */
+ switch (term->pred->prd) {
+ case CFG_PRED_DEFINED: // checks if arg exists as an environment variable
+ ret = getenv(term->args[0].data.str.area) != NULL;
+ break;
+
+ case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature
+ const char *p;
+
+ ret = 0; // assume feature not found
+ for (p = build_features; (p = strstr(p, term->args[0].data.str.area)); p++) {
+ if (p > build_features &&
+ (p[term->args[0].data.str.data] == ' ' ||
+ p[term->args[0].data.str.data] == 0)) {
+ if (*(p-1) == '+') { // e.g. "+OPENSSL"
+ ret = 1;
+ break;
+ }
+ else if (*(p-1) == '-') { // e.g. "-OPENSSL"
+ ret = 0;
+ break;
+ }
+ /* it was a sub-word, let's restart from next place */
+ }
+ }
+ break;
+ }
+ case CFG_PRED_STREQ: // checks if the two arg are equal
+ ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) == 0;
+ break;
+
+ case CFG_PRED_STRNEQ: // checks if the two arg are different
+ ret = strcmp(term->args[0].data.str.area, term->args[1].data.str.area) != 0;
+ break;
+
+ case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one
+ ret = compare_current_version(term->args[0].data.str.area) <= 0;
+ break;
+
+ case CFG_PRED_VERSION_BEFORE: // checks if the current version is older than this one
+ ret = compare_current_version(term->args[0].data.str.area) > 0;
+ break;
+
+ case CFG_PRED_OSSL_VERSION_ATLEAST: { // checks if the current openssl version is at least this one
+ int opensslret = openssl_compare_current_version(term->args[0].data.str.area);
+
+ if (opensslret < -1) /* can't parse the string or no openssl available */
+ ret = -1;
+ else
+ ret = opensslret <= 0;
+ break;
+ }
+ case CFG_PRED_OSSL_VERSION_BEFORE: { // checks if the current openssl version is older than this one
+ int opensslret = openssl_compare_current_version(term->args[0].data.str.area);
+
+ if (opensslret < -1) /* can't parse the string or no openssl available */
+ ret = -1;
+ else
+ ret = opensslret > 0;
+ break;
+ }
+ 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)
+ ret = openssl_compare_current_name(term->args[0].data.str.area) == 0;
+ break;
+ }
+ default:
+ memprintf(err, "internal error: unhandled conditional expression predicate '%s'", term->pred->word);
+ break;
+ }
+ }
+ else if (term->type == CCTT_PAREN) {
+ ret = cfg_eval_cond_expr(term->expr, err);
+ }
+ else {
+ memprintf(err, "internal error: unhandled condition term type %d", (int)term->type);
+ }
+
+ if (ret >= 0 && term->neg)
+ ret = !ret;
+ return ret;
+}
+
+
+/* Frees <expr> and its terms and args. NULL is supported and does nothing. */
+void cfg_free_cond_and(struct cfg_cond_and *expr)
+{
+ struct cfg_cond_and *prev;
+
+ while (expr) {
+ cfg_free_cond_term(expr->left);
+ prev = expr;
+ expr = expr->right;
+ free(prev);
+ }
+}
+
+/* Frees <expr> and its terms and args. NULL is supported and does nothing. */
+void cfg_free_cond_expr(struct cfg_cond_expr *expr)
+{
+ struct cfg_cond_expr *prev;
+
+ while (expr) {
+ cfg_free_cond_and(expr->left);
+ prev = expr;
+ expr = expr->right;
+ free(prev);
+ }
+}
+
+/* Parse an indirect input text as a possible config condition sub-expr.
+ * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
+ * success. <expr> is filled with the parsed info, and <text> is updated on
+ * success to point to the first unparsed character, or is left untouched
+ * on failure. On success, the caller will have to free all lower-level
+ * allocated structs using cfg_free_cond_expr(). An error will be set in
+ * <err> on error, and only in this case. In this case the first bad
+ * character will be reported in <errptr>. <maxdepth> corresponds to the
+ * maximum recursion depth permitted, it is decremented on each recursive
+ * call and the parsing will fail one reaching <= 0.
+ */
+int cfg_parse_cond_and(const char **text, struct cfg_cond_and **expr, char **err, const char **errptr, int maxdepth)
+{
+ struct cfg_cond_and *e;
+ const char *in = *text;
+ int ret = -1;
+
+ if (!*in) /* empty expr does not parse */
+ return 0;
+
+ *expr = NULL;
+ if (maxdepth <= 0) {
+ memprintf(err, "unparsable conditional sub-expression '%s'", in);
+ if (errptr)
+ *errptr = in;
+ goto done;
+ }
+
+ e = *expr = calloc(1, sizeof(**expr));
+ if (!e) {
+ memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
+ goto done;
+ }
+
+ ret = cfg_parse_cond_term(&in, &e->left, err, errptr, maxdepth - 1);
+ if (ret == -1) // parse error, error already reported
+ goto done;
+
+ if (ret == 0) {
+ /* ret == 0, no other way to parse this */
+ memprintf(err, "unparsable conditional sub-expression '%s'", in);
+ if (errptr)
+ *errptr = in;
+ ret = -1;
+ goto done;
+ }
+
+ /* ret=1, we have a term in the left hand set */
+
+ /* find an optional '&&' */
+ while (*in == ' ' || *in == '\t')
+ in++;
+
+ *text = in;
+ if (in[0] != '&' || in[1] != '&')
+ goto done;
+
+ /* we have a '&&', let's parse the right handset's subexp */
+ in += 2;
+ while (*in == ' ' || *in == '\t')
+ in++;
+
+ ret = cfg_parse_cond_and(&in, &e->right, err, errptr, maxdepth - 1);
+ if (ret > 0)
+ *text = in;
+ done:
+ if (ret < 0) {
+ cfg_free_cond_and(*expr);
+ *expr = NULL;
+ }
+ return ret;
+}
+
+/* Parse an indirect input text as a possible config condition term.
+ * Returns <0 on parsing error, 0 if the parser is desynchronized, or >0 on
+ * success. <expr> is filled with the parsed info, and <text> is updated on
+ * success to point to the first unparsed character, or is left untouched
+ * on failure. On success, the caller will have to free all lower-level
+ * allocated structs using cfg_free_cond_expr(). An error will be set in
+ * <err> on error, and only in this case. In this case the first bad
+ * character will be reported in <errptr>. <maxdepth> corresponds to the
+ * maximum recursion depth permitted, it is decremented on each recursive call
+ * and the parsing will fail one reaching <= 0.
+ */
+int cfg_parse_cond_expr(const char **text, struct cfg_cond_expr **expr, char **err, const char **errptr, int maxdepth)
+{
+ struct cfg_cond_expr *e;
+ const char *in = *text;
+ int ret = -1;
+
+ if (!*in) /* empty expr does not parse */
+ return 0;
+
+ *expr = NULL;
+ if (maxdepth <= 0) {
+ memprintf(err, "unparsable conditional expression '%s'", in);
+ if (errptr)
+ *errptr = in;
+ goto done;
+ }
+
+ e = *expr = calloc(1, sizeof(**expr));
+ if (!e) {
+ memprintf(err, "memory allocation error while parsing conditional expression '%s'", *text);
+ goto done;
+ }
+
+ ret = cfg_parse_cond_and(&in, &e->left, err, errptr, maxdepth - 1);
+ if (ret == -1) // parse error, error already reported
+ goto done;
+
+ if (ret == 0) {
+ /* ret == 0, no other way to parse this */
+ memprintf(err, "unparsable conditional expression '%s'", in);
+ if (errptr)
+ *errptr = in;
+ ret = -1;
+ goto done;
+ }
+
+ /* ret=1, we have a sub-expr in the left hand set */
+
+ /* find an optional '||' */
+ while (*in == ' ' || *in == '\t')
+ in++;
+
+ *text = in;
+ if (in[0] != '|' || in[1] != '|')
+ goto done;
+
+ /* we have a '||', let's parse the right handset's subexp */
+ in += 2;
+ while (*in == ' ' || *in == '\t')
+ in++;
+
+ ret = cfg_parse_cond_expr(&in, &e->right, err, errptr, maxdepth - 1);
+ if (ret > 0)
+ *text = in;
+ done:
+ if (ret < 0) {
+ cfg_free_cond_expr(*expr);
+ *expr = NULL;
+ }
+ return ret;
+}
+
+/* evaluate an sub-expression on a .if/.elif line. The expression is valid and
+ * was already parsed in <expr>. Returns -1 on error (in which case err is
+ * filled with a message, and only in this case), 0 if the condition is false,
+ * 1 if it's true.
+ */
+int cfg_eval_cond_and(struct cfg_cond_and *expr, char **err)
+{
+ int ret;
+
+ /* AND: loop on terms and sub-exp's terms as long as they're TRUE
+ * (stop on FALSE and ERROR).
+ */
+ while ((ret = cfg_eval_cond_term(expr->left, err)) > 0 && expr->right)
+ expr = expr->right;
+ return ret;
+}
+
+/* evaluate an expression on a .if/.elif line. The expression is valid and was
+ * already parsed in <expr>. Returns -1 on error (in which case err is filled
+ * with a message, and only in this case), 0 if the condition is false, 1 if
+ * it's true.
+ */
+int cfg_eval_cond_expr(struct cfg_cond_expr *expr, char **err)
+{
+ int ret;
+
+ /* OR: loop on sub-exps as long as they're FALSE (stop on TRUE and ERROR) */
+ while ((ret = cfg_eval_cond_and(expr->left, err)) == 0 && expr->right)
+ expr = expr->right;
+ return ret;
+}
+
+/* evaluate a condition on a .if/.elif line. The condition is already tokenized
+ * in <err>. Returns -1 on error (in which case err is filled with a message,
+ * and only in this case), 0 if the condition is false, 1 if it's true. If
+ * <errptr> is not NULL, it's set to the first invalid character on error.
+ */
+int cfg_eval_condition(char **args, char **err, const char **errptr)
+{
+ struct cfg_cond_expr *expr = NULL;
+ const char *text = args[0];
+ int ret = -1;
+
+ if (!*text) /* note: empty = false */
+ return 0;
+
+ ret = cfg_parse_cond_expr(&text, &expr, err, errptr, MAX_CFG_RECURSION);
+ if (ret != 0) {
+ if (ret == -1) // parse error, error already reported
+ goto done;
+ while (*text == ' ' || *text == '\t')
+ text++;
+
+ if (*text) {
+ ret = -1;
+ memprintf(err, "unexpected character '%c' at the end of conditional expression '%s'",
+ *text, args[0]);
+ goto fail;
+ }
+
+ ret = cfg_eval_cond_expr(expr, err);
+ goto done;
+ }
+
+ /* ret == 0, no other way to parse this */
+ ret = -1;
+ memprintf(err, "unparsable conditional expression '%s'", args[0]);
+ fail:
+ if (errptr)
+ *errptr = text;
+ done:
+ cfg_free_cond_expr(expr);
+ return ret;
+}