summaryrefslogtreecommitdiffstats
path: root/src/lib/var-expand-if.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib/var-expand-if.c269
1 files changed, 269 insertions, 0 deletions
diff --git a/src/lib/var-expand-if.c b/src/lib/var-expand-if.c
new file mode 100644
index 0000000..aa0a9b3
--- /dev/null
+++ b/src/lib/var-expand-if.c
@@ -0,0 +1,269 @@
+/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "var-expand.h"
+#include "var-expand-private.h"
+#include "wildcard-match.h"
+
+#include <regex.h>
+
+enum var_expand_if_op {
+ OP_UNKNOWN,
+ OP_NUM_EQ,
+ OP_NUM_LT,
+ OP_NUM_LE,
+ OP_NUM_GT,
+ OP_NUM_GE,
+ OP_NUM_NE,
+/* put all numeric comparisons before this line */
+ OP_STR_EQ,
+ OP_STR_LT,
+ OP_STR_LE,
+ OP_STR_GT,
+ OP_STR_GE,
+ OP_STR_NE,
+ OP_STR_LIKE,
+ OP_STR_NOT_LIKE,
+ OP_STR_REGEXP,
+ OP_STR_NOT_REGEXP,
+/* keep this as last */
+ OP_COUNT
+};
+
+static enum var_expand_if_op var_expand_if_str_to_comp(const char *op)
+{
+ const char *ops[OP_COUNT] = {
+ NULL,
+ "==",
+ "<",
+ "<=",
+ ">",
+ ">=",
+ "!=",
+ "eq",
+ "lt",
+ "le",
+ "gt",
+ "ge",
+ "ne",
+ "*",
+ "!*",
+ "~",
+ "!~",
+ };
+ for(enum var_expand_if_op i = 1; i < OP_COUNT; i++) {
+ i_assert(ops[i] != NULL);
+ if (strcmp(op, ops[i]) == 0)
+ return i;
+ }
+ return OP_UNKNOWN;
+}
+
+static int var_expand_if_comp(const char *lhs, const char *_op, const char *rhs,
+ bool *result_r, const char **error_r)
+{
+ bool neg = FALSE;
+ enum var_expand_if_op op = var_expand_if_str_to_comp(_op);
+
+ *result_r = FALSE;
+ if (op == OP_UNKNOWN) {
+ *error_r = t_strdup_printf("if: Unsupported comparator '%s'", _op);
+ return -1;
+ }
+
+ if (op < OP_STR_EQ) {
+ intmax_t a;
+ intmax_t b;
+ if (str_to_intmax(lhs, &a) < 0) {
+ *error_r = t_strdup_printf("if: %s (lhs) is not a number", lhs);
+ return -1;
+ }
+ if (str_to_intmax(rhs, &b) < 0) {
+ *error_r = t_strdup_printf("if: %s (rhs) is not a number", rhs);
+ return -1;
+ }
+ switch(op) {
+ case OP_NUM_EQ:
+ *result_r = a==b;
+ return 0;
+ case OP_NUM_LT:
+ *result_r = a<b;
+ return 0;
+ case OP_NUM_LE:
+ *result_r = a<=b;
+ return 0;
+ case OP_NUM_GT:
+ *result_r = a>b;
+ return 0;
+ case OP_NUM_GE:
+ *result_r = a>=b;
+ return 0;
+ case OP_NUM_NE:
+ *result_r = a!=b;
+ return 0;
+ default:
+ i_panic("Missing numeric comparator %u", op);
+ }
+ }
+
+ switch(op) {
+ case OP_STR_EQ:
+ *result_r = strcmp(lhs,rhs)==0;
+ return 0;
+ case OP_STR_LT:
+ *result_r = strcmp(lhs,rhs)<0;
+ return 0;
+ case OP_STR_LE:
+ *result_r = strcmp(lhs,rhs)<=0;
+ return 0;
+ case OP_STR_GT:
+ *result_r = strcmp(lhs,rhs)>0;
+ return 0;
+ case OP_STR_GE:
+ *result_r = strcmp(lhs,rhs)>=0;
+ return 0;
+ case OP_STR_NE:
+ *result_r = strcmp(lhs,rhs)!=0;
+ return 0;
+ case OP_STR_LIKE:
+ *result_r = wildcard_match(lhs, rhs);
+ return 0;
+ case OP_STR_NOT_LIKE:
+ *result_r = !wildcard_match(lhs, rhs);
+ return 0;
+ case OP_STR_NOT_REGEXP:
+ neg = TRUE;
+ /* fall through */
+ case OP_STR_REGEXP: {
+ int ec;
+ bool res;
+ regex_t reg;
+ if ((ec = regcomp(&reg, rhs, REG_EXTENDED)) != 0) {
+ size_t siz;
+ char *errbuf;
+ siz = regerror(ec, &reg, NULL, 0);
+ errbuf = t_malloc_no0(siz);
+ (void)regerror(ec, &reg, errbuf, siz);
+ *error_r = t_strdup_printf("if: regex failed: %s",
+ errbuf);
+ return -1;
+ }
+ if ((ec = regexec(&reg, lhs, 0, 0, 0)) != 0) {
+ i_assert(ec == REG_NOMATCH);
+ res = FALSE;
+ } else {
+ res = TRUE;
+ }
+ regfree(&reg);
+ /* this should be same as neg.
+ if NOT_REGEXP, neg == TRUE and res should be FALSE
+ if REGEXP, ned == FALSE, and res should be TRUE
+ */
+ *result_r = res != neg;
+ return 0;
+ }
+ default:
+ i_panic("Missing generic comparator %u", op);
+ }
+}
+
+int var_expand_if(struct var_expand_context *ctx,
+ const char *key, const char *field,
+ const char **result_r, const char **error_r)
+{
+ /* in case the original input had :, we need to fix that
+ by concatenating the key and field together. */
+ const char *input = t_strconcat(key, ":", field, NULL);
+ const char *p = strchr(input, ';');
+ const char *par_end;
+ string_t *parbuf;
+ const char *const *parms;
+ unsigned int depth = 0;
+ int ret;
+ bool result, escape = FALSE, maybe_var = FALSE;
+
+ if (p == NULL) {
+ *error_r = "if: missing parameter(s)";
+ return -1;
+ }
+ ARRAY_TYPE(const_string) params;
+ t_array_init(&params, 6);
+
+ parbuf = t_str_new(64);
+ /* we need to skip any %{} parameters here, so we can split the string
+ correctly from , without breaking any inner expansions */
+ for(par_end = p+1; *par_end != '\0'; par_end++) {
+ if (*par_end == '\\') {
+ escape = TRUE;
+ continue;
+ } else if (escape) {
+ str_append_c(parbuf, *par_end);
+ escape = FALSE;
+ continue;
+ }
+ if (*par_end == '%') {
+ maybe_var = TRUE;
+ } else if (maybe_var && *par_end == '{') {
+ depth++;
+ maybe_var = FALSE;
+ } else if (depth > 0 && *par_end == '}') {
+ depth--;
+ } else if (depth == 0 && *par_end == ';') {
+ const char *par = str_c(parbuf);
+ array_push_back(&params, &par);
+ parbuf = t_str_new(64);
+ continue;
+ /* if there is a unescaped : at top level it means
+ that the key + arguments end here. it's probably
+ a by-product of the t_strconcat at top of function,
+ which is best handled here. */
+ } else if (depth == 0 && *par_end == ':') {
+ break;
+ }
+ str_append_c(parbuf, *par_end);
+ }
+
+ if (str_len(parbuf) > 0) {
+ const char *par = str_c(parbuf);
+ array_push_back(&params, &par);
+ }
+
+ if (array_count(&params) != 5) {
+ if (array_count(&params) == 4) {
+ const char *empty = "";
+ array_push_back(&params, &empty);
+ } else {
+ *error_r = t_strdup_printf("if: requires four or five parameters, got %u",
+ array_count(&params));
+ return -1;
+ }
+ }
+
+ array_append_zero(&params);
+ parms = array_front(&params);
+ t_array_init(&params, 6);
+
+ for(;*parms != NULL; parms++) {
+ /* expand the parameters */
+ string_t *param = t_str_new(64);
+ if ((ret = var_expand_with_funcs(param, *parms, ctx->table,
+ ctx->func_table, ctx->context,
+ error_r)) <= 0) {
+ return ret;
+ }
+ const char *p = str_c(param);
+ array_push_back(&params, &p);
+ }
+
+ i_assert(array_count(&params) == 5);
+
+ /* execute comparison */
+ const char *const *args = array_front(&params);
+ if (var_expand_if_comp(args[0], args[1], args[2], &result, error_r)<0)
+ return -1;
+ *result_r = result ? args[3] : args[4];
+ return 1;
+}
+