/* 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 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 = ab; 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(®, rhs, REG_EXTENDED)) != 0) { size_t siz; char *errbuf; siz = regerror(ec, ®, NULL, 0); errbuf = t_malloc_no0(siz); (void)regerror(ec, ®, errbuf, siz); *error_r = t_strdup_printf("if: regex failed: %s", errbuf); return -1; } if ((ec = regexec(®, lhs, 0, 0, 0)) != 0) { i_assert(ec == REG_NOMATCH); res = FALSE; } else { res = TRUE; } regfree(®); /* 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(¶ms, 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(¶ms, &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(¶ms, &par); } if (array_count(¶ms) != 5) { if (array_count(¶ms) == 4) { const char *empty = ""; array_push_back(¶ms, &empty); } else { *error_r = t_strdup_printf("if: requires four or five parameters, got %u", array_count(¶ms)); return -1; } } array_append_zero(¶ms); parms = array_front(¶ms); t_array_init(¶ms, 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(¶ms, &p); } i_assert(array_count(¶ms) == 5); /* execute comparison */ const char *const *args = array_front(¶ms); 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; }