diff --git a/Makefile b/Makefile index 4b0676767..d598e60ac 100644 --- a/Makefile +++ b/Makefile @@ -868,7 +868,7 @@ OBJS += src/mux_h2.o src/mux_fcgi.o src/http_ana.o src/mux_h1.o src/stream.o \ src/listener.o src/dns.o src/connection.o src/tcp_rules.o src/debug.o \ src/sink.o src/payload.o src/mux_pt.o src/filters.o src/fcgi-app.o \ src/server_state.o src/vars.o src/map.o src/cfgparse-global.o \ - src/task.o src/flt_http_comp.o src/session.o src/sock.o \ + src/task.o src/flt_http_comp.o src/session.o src/sock.o src/cfgcond.o \ src/flt_trace.o src/acl.o src/trace.o src/http_rules.o src/queue.o \ src/mjson.o src/h2.o src/h1.o src/mworker.o src/lb_chash.o src/ring.o \ src/activity.o src/tcp_sample.o src/proto_tcp.o src/htx.o src/h1_htx.o \ diff --git a/include/haproxy/cfgcond-t.h b/include/haproxy/cfgcond-t.h new file mode 100644 index 000000000..04c8df16e --- /dev/null +++ b/include/haproxy/cfgcond-t.h @@ -0,0 +1,62 @@ +/* + * include/haproxy/cfgcond-t.h + * Types for the configuration condition preprocessor + * + * Copyright (C) 2000-2021 Willy Tarreau - w@1wt.eu + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _HAPROXY_CFGCOND_T_H +#define _HAPROXY_CFGCOND_T_H + +#include + +/* nested if/elif/else/endif block states */ +enum nested_cond_state { + NESTED_COND_IF_TAKE, // "if" with a true condition + NESTED_COND_IF_DROP, // "if" with a false condition + NESTED_COND_IF_SKIP, // "if" masked by an outer false condition + + NESTED_COND_ELIF_TAKE, // "elif" with a true condition from a false one + NESTED_COND_ELIF_DROP, // "elif" with a false condition from a false one + NESTED_COND_ELIF_SKIP, // "elif" masked by an outer false condition or a previously taken if + + NESTED_COND_ELSE_TAKE, // taken "else" after an if false condition + NESTED_COND_ELSE_DROP, // "else" masked by outer false condition or an if true condition +}; + +/* 100 levels of nested conditions should already be sufficient */ +#define MAXNESTEDCONDS 100 + +/* supported conditional predicates for .if/.elif */ +enum cond_predicate { + CFG_PRED_NONE, // none + CFG_PRED_DEFINED, // "defined" + CFG_PRED_FEATURE, // "feature" + CFG_PRED_STREQ, // "streq" + CFG_PRED_STRNEQ, // "strneq" + CFG_PRED_VERSION_ATLEAST, // "version_atleast" + CFG_PRED_VERSION_BEFORE, // "version_before" +}; + +/* keyword for a condition predicate */ +struct cond_pred_kw { + const char *word; // NULL marks the end of the list + enum cond_predicate prd; // one of the CFG_PRED_* above + uint64_t arg_mask; // mask of supported arguments (strings only) +}; + +#endif /* _HAPROXY_CFGCOND_T_H */ diff --git a/include/haproxy/cfgcond.h b/include/haproxy/cfgcond.h new file mode 100644 index 000000000..bf8f58df6 --- /dev/null +++ b/include/haproxy/cfgcond.h @@ -0,0 +1,31 @@ +/* + * include/haproxy/cfgcond.h + * Configuration condition preprocessor + * + * Copyright (C) 2000-2021 Willy Tarreau - w@1wt.eu + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _HAPROXY_CFGCOND_H +#define _HAPROXY_CFGCOND_H + +#include +#include + +const struct cond_pred_kw *cfg_lookup_cond_pred(const char *str); +int cfg_eval_condition(char **args, char **err, const char **errptr); + +#endif diff --git a/include/haproxy/cfgparse.h b/include/haproxy/cfgparse.h index 51e5b9edf..1c97a88b9 100644 --- a/include/haproxy/cfgparse.h +++ b/include/haproxy/cfgparse.h @@ -126,7 +126,6 @@ void free_email_alert(struct proxy *p); const char *cfg_find_best_match(const char *word, const struct list *list, int section, const char **extra); int warnifnotcap(struct proxy *proxy, int cap, const char *file, int line, const char *arg, const char *hint); int failifnotcap(struct proxy *proxy, int cap, const char *file, int line, const char *arg, const char *hint); -int cfg_eval_condition(char **args, char **err, const char **errptr); /* simplified way to define a section parser */ #define REGISTER_CONFIG_SECTION(name, parse, post) \ diff --git a/src/cfgcond.c b/src/cfgcond.c new file mode 100644 index 000000000..d3c087b66 --- /dev/null +++ b/src/cfgcond.c @@ -0,0 +1,147 @@ +/* + * Configuration condition preprocessor + * + * Copyright 2000-2021 Willy Tarreau + * + * 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 +#include +#include +#include +#include + +/* 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) }, + { NULL, CFG_PRED_NONE, 0 } +}; + +/* looks up a cond predicate matching the keyword in , 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; +} + +/* evaluate a condition on a .if/.elif line. The condition is already tokenized + * in . 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 + * is not NULL, it's set to the first invalid character on error. + */ +int cfg_eval_condition(char **args, char **err, const char **errptr) +{ + const struct cond_pred_kw *cond_pred = NULL; + const char *end_ptr; + struct arg *argp = NULL; + int err_arg; + int nbargs; + int ret = -1; + char *end; + long val; + + if (!*args[0]) /* note: empty = false */ + return 0; + + val = strtol(args[0], &end, 0); + if (end && *end == '\0') + return val != 0; + + /* below we'll likely all make_arg_list() so we must return only via + * the label which frees the arg list. + */ + cond_pred = cfg_lookup_cond_pred(args[0]); + if (cond_pred) { + nbargs = make_arg_list(args[0] + strlen(cond_pred->word), -1, + cond_pred->arg_mask, &argp, 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, cond_pred->word); + if (errptr) + *errptr = end_ptr; + goto done; + } + + /* here we know we have a valid predicate with valid + * arguments, placed in (which we'll need to free). + */ + switch (cond_pred->prd) { + case CFG_PRED_DEFINED: // checks if arg exists as an environment variable + ret = getenv(argp[0].data.str.area) != NULL; + goto done; + + case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature + const char *p; + + for (p = build_features; (p = strstr(p, argp[0].data.str.area)); p++) { + if ((p[argp[0].data.str.data] == ' ' || p[argp[0].data.str.data] == 0) && + p > build_features) { + if (*(p-1) == '+') { // "+OPENSSL" + ret = 1; + goto done; + } + else if (*(p-1) == '-') { // "-OPENSSL" + ret = 0; + goto done; + } + /* it was a sub-word, let's restart from next place */ + } + } + /* not found */ + ret = 0; + goto done; + } + case CFG_PRED_STREQ: // checks if the two arg are equal + ret = strcmp(argp[0].data.str.area, argp[1].data.str.area) == 0; + goto done; + + case CFG_PRED_STRNEQ: // checks if the two arg are different + ret = strcmp(argp[0].data.str.area, argp[1].data.str.area) != 0; + goto done; + + case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one + ret = compare_current_version(argp[0].data.str.area) <= 0; + goto done; + + case CFG_PRED_VERSION_BEFORE: // checks if the current version is older than this one + ret = compare_current_version(argp[0].data.str.area) > 0; + goto done; + + default: + memprintf(err, "internal error: unhandled conditional expression predicate '%s'", cond_pred->word); + if (errptr) + *errptr = args[0]; + goto done; + } + } + + memprintf(err, "unparsable conditional expression '%s'", args[0]); + if (errptr) + *errptr = args[0]; + done: + free_args(argp); + ha_free(&argp); + return ret; +} diff --git a/src/cfgparse.c b/src/cfgparse.c index a1b9a4764..4cee8a7b6 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -116,51 +117,6 @@ struct cfg_kw_list cfg_keywords = { .list = LIST_HEAD_INIT(cfg_keywords.list) }; -/* nested if/elif/else/endif block states */ -enum nested_cond_state { - NESTED_COND_IF_TAKE, // "if" with a true condition - NESTED_COND_IF_DROP, // "if" with a false condition - NESTED_COND_IF_SKIP, // "if" masked by an outer false condition - - NESTED_COND_ELIF_TAKE, // "elif" with a true condition from a false one - NESTED_COND_ELIF_DROP, // "elif" with a false condition from a false one - NESTED_COND_ELIF_SKIP, // "elif" masked by an outer false condition or a previously taken if - - NESTED_COND_ELSE_TAKE, // taken "else" after an if false condition - NESTED_COND_ELSE_DROP, // "else" masked by outer false condition or an if true condition -}; - -/* 100 levels of nested conditions should already be sufficient */ -#define MAXNESTEDCONDS 100 - -/* supported conditional predicates for .if/.elif */ -enum cond_predicate { - CFG_PRED_NONE, // none - CFG_PRED_DEFINED, // "defined" - CFG_PRED_FEATURE, // "feature" - CFG_PRED_STREQ, // "streq" - CFG_PRED_STRNEQ, // "strneq" - CFG_PRED_VERSION_ATLEAST, // "version_atleast" - CFG_PRED_VERSION_BEFORE, // "version_before" -}; - -struct cond_pred_kw { - const char *word; // NULL marks the end of the list - enum cond_predicate prd; // one of the CFG_PRED_* above - uint64_t arg_mask; // mask of supported arguments (strings only) -}; - -/* 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) }, - { NULL, CFG_PRED_NONE, 0 } -}; - /* * converts to a list of listeners which are dynamically allocated. * The format is "{addr|'*'}:port[-end][,{addr|'*'}:port[-end]]*", where : @@ -1713,125 +1669,6 @@ static int cfg_parse_global_def_path(char **args, int section_type, struct proxy return ret; } -/* looks up a cond predicate matching the keyword in , possibly followed - * by a parenthesis. Returns a pointer to it or NULL if not found. - */ -static 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; -} - -/* evaluate a condition on a .if/.elif line. The condition is already tokenized - * in . 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 - * is not NULL, it's set to the first invalid character on error. - */ -int cfg_eval_condition(char **args, char **err, const char **errptr) -{ - const struct cond_pred_kw *cond_pred = NULL; - const char *end_ptr; - struct arg *argp = NULL; - int err_arg; - int nbargs; - int ret = -1; - char *end; - long val; - - if (!*args[0]) /* note: empty = false */ - return 0; - - val = strtol(args[0], &end, 0); - if (end && *end == '\0') - return val != 0; - - /* below we'll likely all make_arg_list() so we must return only via - * the label which frees the arg list. - */ - cond_pred = cfg_lookup_cond_pred(args[0]); - if (cond_pred) { - nbargs = make_arg_list(args[0] + strlen(cond_pred->word), -1, - cond_pred->arg_mask, &argp, 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, cond_pred->word); - if (errptr) - *errptr = end_ptr; - goto done; - } - - /* here we know we have a valid predicate with valid - * arguments, placed in (which we'll need to free). - */ - switch (cond_pred->prd) { - case CFG_PRED_DEFINED: // checks if arg exists as an environment variable - ret = getenv(argp[0].data.str.area) != NULL; - goto done; - - case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature - const char *p; - - for (p = build_features; (p = strstr(p, argp[0].data.str.area)); p++) { - if ((p[argp[0].data.str.data] == ' ' || p[argp[0].data.str.data] == 0) && - p > build_features) { - if (*(p-1) == '+') { // "+OPENSSL" - ret = 1; - goto done; - } - else if (*(p-1) == '-') { // "-OPENSSL" - ret = 0; - goto done; - } - /* it was a sub-word, let's restart from next place */ - } - } - /* not found */ - ret = 0; - goto done; - } - case CFG_PRED_STREQ: // checks if the two arg are equal - ret = strcmp(argp[0].data.str.area, argp[1].data.str.area) == 0; - goto done; - - case CFG_PRED_STRNEQ: // checks if the two arg are different - ret = strcmp(argp[0].data.str.area, argp[1].data.str.area) != 0; - goto done; - - case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one - ret = compare_current_version(argp[0].data.str.area) <= 0; - goto done; - - case CFG_PRED_VERSION_BEFORE: // checks if the current version is older than this one - ret = compare_current_version(argp[0].data.str.area) > 0; - goto done; - - default: - memprintf(err, "internal error: unhandled conditional expression predicate '%s'", cond_pred->word); - if (errptr) - *errptr = args[0]; - goto done; - } - } - - memprintf(err, "unparsable conditional expression '%s'", args[0]); - if (errptr) - *errptr = args[0]; - done: - free_args(argp); - ha_free(&argp); - return ret; -} - /* * This function reads and parses the configuration file given in the argument. * Returns the error code, 0 if OK, -1 if the config file couldn't be opened, diff --git a/src/haproxy.c b/src/haproxy.c index 2d32bb84f..c863e13ec 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -87,6 +87,7 @@ #include #include #include +#include #include #include #include