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 | |
| 48 | /* evaluate a condition on a .if/.elif line. The condition is already tokenized |
| 49 | * in <err>. Returns -1 on error (in which case err is filled with a message, |
| 50 | * and only in this case), 0 if the condition is false, 1 if it's true. If |
| 51 | * <errptr> is not NULL, it's set to the first invalid character on error. |
| 52 | */ |
| 53 | int cfg_eval_condition(char **args, char **err, const char **errptr) |
| 54 | { |
| 55 | const struct cond_pred_kw *cond_pred = NULL; |
| 56 | const char *end_ptr; |
| 57 | struct arg *argp = NULL; |
| 58 | int err_arg; |
| 59 | int nbargs; |
| 60 | int ret = -1; |
| 61 | char *end; |
| 62 | long val; |
| 63 | |
| 64 | if (!*args[0]) /* note: empty = false */ |
| 65 | return 0; |
| 66 | |
| 67 | val = strtol(args[0], &end, 0); |
| 68 | if (end && *end == '\0') |
| 69 | return val != 0; |
| 70 | |
| 71 | /* below we'll likely all make_arg_list() so we must return only via |
| 72 | * the <done> label which frees the arg list. |
| 73 | */ |
| 74 | cond_pred = cfg_lookup_cond_pred(args[0]); |
| 75 | if (cond_pred) { |
| 76 | nbargs = make_arg_list(args[0] + strlen(cond_pred->word), -1, |
| 77 | cond_pred->arg_mask, &argp, err, |
| 78 | &end_ptr, &err_arg, NULL); |
| 79 | |
| 80 | if (nbargs < 0) { |
| 81 | memprintf(err, "%s in argument %d of predicate '%s' used in conditional expression", *err, err_arg, cond_pred->word); |
| 82 | if (errptr) |
| 83 | *errptr = end_ptr; |
| 84 | goto done; |
| 85 | } |
| 86 | |
| 87 | /* here we know we have a valid predicate with <nbargs> valid |
| 88 | * arguments, placed in <argp> (which we'll need to free). |
| 89 | */ |
| 90 | switch (cond_pred->prd) { |
| 91 | case CFG_PRED_DEFINED: // checks if arg exists as an environment variable |
| 92 | ret = getenv(argp[0].data.str.area) != NULL; |
| 93 | goto done; |
| 94 | |
| 95 | case CFG_PRED_FEATURE: { // checks if the arg matches an enabled feature |
| 96 | const char *p; |
| 97 | |
| 98 | for (p = build_features; (p = strstr(p, argp[0].data.str.area)); p++) { |
| 99 | if ((p[argp[0].data.str.data] == ' ' || p[argp[0].data.str.data] == 0) && |
| 100 | p > build_features) { |
| 101 | if (*(p-1) == '+') { // "+OPENSSL" |
| 102 | ret = 1; |
| 103 | goto done; |
| 104 | } |
| 105 | else if (*(p-1) == '-') { // "-OPENSSL" |
| 106 | ret = 0; |
| 107 | goto done; |
| 108 | } |
| 109 | /* it was a sub-word, let's restart from next place */ |
| 110 | } |
| 111 | } |
| 112 | /* not found */ |
| 113 | ret = 0; |
| 114 | goto done; |
| 115 | } |
| 116 | case CFG_PRED_STREQ: // checks if the two arg are equal |
| 117 | ret = strcmp(argp[0].data.str.area, argp[1].data.str.area) == 0; |
| 118 | goto done; |
| 119 | |
| 120 | case CFG_PRED_STRNEQ: // checks if the two arg are different |
| 121 | ret = strcmp(argp[0].data.str.area, argp[1].data.str.area) != 0; |
| 122 | goto done; |
| 123 | |
| 124 | case CFG_PRED_VERSION_ATLEAST: // checks if the current version is at least this one |
| 125 | ret = compare_current_version(argp[0].data.str.area) <= 0; |
| 126 | goto done; |
| 127 | |
| 128 | case CFG_PRED_VERSION_BEFORE: // checks if the current version is older than this one |
| 129 | ret = compare_current_version(argp[0].data.str.area) > 0; |
| 130 | goto done; |
| 131 | |
| 132 | default: |
| 133 | memprintf(err, "internal error: unhandled conditional expression predicate '%s'", cond_pred->word); |
| 134 | if (errptr) |
| 135 | *errptr = args[0]; |
| 136 | goto done; |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | memprintf(err, "unparsable conditional expression '%s'", args[0]); |
| 141 | if (errptr) |
| 142 | *errptr = args[0]; |
| 143 | done: |
| 144 | free_args(argp); |
| 145 | ha_free(&argp); |
| 146 | return ret; |
| 147 | } |