1 /* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "array.h"
5 #include "str.h"
6 #include "var-expand.h"
7 #include "var-expand-private.h"
8 #include "wildcard-match.h"
9
10 #include <regex.h>
11
12 enum var_expand_if_op {
13 OP_UNKNOWN,
14 OP_NUM_EQ,
15 OP_NUM_LT,
16 OP_NUM_LE,
17 OP_NUM_GT,
18 OP_NUM_GE,
19 OP_NUM_NE,
20 /* put all numeric comparisons before this line */
21 OP_STR_EQ,
22 OP_STR_LT,
23 OP_STR_LE,
24 OP_STR_GT,
25 OP_STR_GE,
26 OP_STR_NE,
27 OP_STR_LIKE,
28 OP_STR_NOT_LIKE,
29 OP_STR_REGEXP,
30 OP_STR_NOT_REGEXP,
31 /* keep this as last */
32 OP_COUNT
33 };
34
var_expand_if_str_to_comp(const char * op)35 static enum var_expand_if_op var_expand_if_str_to_comp(const char *op)
36 {
37 const char *ops[OP_COUNT] = {
38 NULL,
39 "==",
40 "<",
41 "<=",
42 ">",
43 ">=",
44 "!=",
45 "eq",
46 "lt",
47 "le",
48 "gt",
49 "ge",
50 "ne",
51 "*",
52 "!*",
53 "~",
54 "!~",
55 };
56 for(enum var_expand_if_op i = 1; i < OP_COUNT; i++) {
57 i_assert(ops[i] != NULL);
58 if (strcmp(op, ops[i]) == 0)
59 return i;
60 }
61 return OP_UNKNOWN;
62 }
63
var_expand_if_comp(const char * lhs,const char * _op,const char * rhs,bool * result_r,const char ** error_r)64 static int var_expand_if_comp(const char *lhs, const char *_op, const char *rhs,
65 bool *result_r, const char **error_r)
66 {
67 bool neg = FALSE;
68 enum var_expand_if_op op = var_expand_if_str_to_comp(_op);
69
70 *result_r = FALSE;
71 if (op == OP_UNKNOWN) {
72 *error_r = t_strdup_printf("if: Unsupported comparator '%s'", _op);
73 return -1;
74 }
75
76 if (op < OP_STR_EQ) {
77 intmax_t a;
78 intmax_t b;
79 if (str_to_intmax(lhs, &a) < 0) {
80 *error_r = t_strdup_printf("if: %s (lhs) is not a number", lhs);
81 return -1;
82 }
83 if (str_to_intmax(rhs, &b) < 0) {
84 *error_r = t_strdup_printf("if: %s (rhs) is not a number", rhs);
85 return -1;
86 }
87 switch(op) {
88 case OP_NUM_EQ:
89 *result_r = a==b;
90 return 0;
91 case OP_NUM_LT:
92 *result_r = a<b;
93 return 0;
94 case OP_NUM_LE:
95 *result_r = a<=b;
96 return 0;
97 case OP_NUM_GT:
98 *result_r = a>b;
99 return 0;
100 case OP_NUM_GE:
101 *result_r = a>=b;
102 return 0;
103 case OP_NUM_NE:
104 *result_r = a!=b;
105 return 0;
106 default:
107 i_panic("Missing numeric comparator %u", op);
108 }
109 }
110
111 switch(op) {
112 case OP_STR_EQ:
113 *result_r = strcmp(lhs,rhs)==0;
114 return 0;
115 case OP_STR_LT:
116 *result_r = strcmp(lhs,rhs)<0;
117 return 0;
118 case OP_STR_LE:
119 *result_r = strcmp(lhs,rhs)<=0;
120 return 0;
121 case OP_STR_GT:
122 *result_r = strcmp(lhs,rhs)>0;
123 return 0;
124 case OP_STR_GE:
125 *result_r = strcmp(lhs,rhs)>=0;
126 return 0;
127 case OP_STR_NE:
128 *result_r = strcmp(lhs,rhs)!=0;
129 return 0;
130 case OP_STR_LIKE:
131 *result_r = wildcard_match(lhs, rhs);
132 return 0;
133 case OP_STR_NOT_LIKE:
134 *result_r = !wildcard_match(lhs, rhs);
135 return 0;
136 case OP_STR_NOT_REGEXP:
137 neg = TRUE;
138 /* fall through */
139 case OP_STR_REGEXP: {
140 int ec;
141 bool res;
142 regex_t reg;
143 if ((ec = regcomp(®, rhs, REG_EXTENDED)) != 0) {
144 size_t siz;
145 char *errbuf;
146 siz = regerror(ec, ®, NULL, 0);
147 errbuf = t_malloc_no0(siz);
148 (void)regerror(ec, ®, errbuf, siz);
149 *error_r = t_strdup_printf("if: regex failed: %s",
150 errbuf);
151 return -1;
152 }
153 if ((ec = regexec(®, lhs, 0, 0, 0)) != 0) {
154 i_assert(ec == REG_NOMATCH);
155 res = FALSE;
156 } else {
157 res = TRUE;
158 }
159 regfree(®);
160 /* this should be same as neg.
161 if NOT_REGEXP, neg == TRUE and res should be FALSE
162 if REGEXP, ned == FALSE, and res should be TRUE
163 */
164 *result_r = res != neg;
165 return 0;
166 }
167 default:
168 i_panic("Missing generic comparator %u", op);
169 }
170 }
171
var_expand_if(struct var_expand_context * ctx,const char * key,const char * field,const char ** result_r,const char ** error_r)172 int var_expand_if(struct var_expand_context *ctx,
173 const char *key, const char *field,
174 const char **result_r, const char **error_r)
175 {
176 /* in case the original input had :, we need to fix that
177 by concatenating the key and field together. */
178 const char *input = t_strconcat(key, ":", field, NULL);
179 const char *p = strchr(input, ';');
180 const char *par_end;
181 string_t *parbuf;
182 const char *const *parms;
183 unsigned int depth = 0;
184 int ret;
185 bool result, escape = FALSE, maybe_var = FALSE;
186
187 if (p == NULL) {
188 *error_r = "if: missing parameter(s)";
189 return -1;
190 }
191 ARRAY_TYPE(const_string) params;
192 t_array_init(¶ms, 6);
193
194 parbuf = t_str_new(64);
195 /* we need to skip any %{} parameters here, so we can split the string
196 correctly from , without breaking any inner expansions */
197 for(par_end = p+1; *par_end != '\0'; par_end++) {
198 if (*par_end == '\\') {
199 escape = TRUE;
200 continue;
201 } else if (escape) {
202 str_append_c(parbuf, *par_end);
203 escape = FALSE;
204 continue;
205 }
206 if (*par_end == '%') {
207 maybe_var = TRUE;
208 } else if (maybe_var && *par_end == '{') {
209 depth++;
210 maybe_var = FALSE;
211 } else if (depth > 0 && *par_end == '}') {
212 depth--;
213 } else if (depth == 0 && *par_end == ';') {
214 const char *par = str_c(parbuf);
215 array_push_back(¶ms, &par);
216 parbuf = t_str_new(64);
217 continue;
218 /* if there is a unescaped : at top level it means
219 that the key + arguments end here. it's probably
220 a by-product of the t_strconcat at top of function,
221 which is best handled here. */
222 } else if (depth == 0 && *par_end == ':') {
223 break;
224 }
225 str_append_c(parbuf, *par_end);
226 }
227
228 if (str_len(parbuf) > 0) {
229 const char *par = str_c(parbuf);
230 array_push_back(¶ms, &par);
231 }
232
233 if (array_count(¶ms) != 5) {
234 if (array_count(¶ms) == 4) {
235 const char *empty = "";
236 array_push_back(¶ms, &empty);
237 } else {
238 *error_r = t_strdup_printf("if: requires four or five parameters, got %u",
239 array_count(¶ms));
240 return -1;
241 }
242 }
243
244 array_append_zero(¶ms);
245 parms = array_front(¶ms);
246 t_array_init(¶ms, 6);
247
248 for(;*parms != NULL; parms++) {
249 /* expand the parameters */
250 string_t *param = t_str_new(64);
251 if ((ret = var_expand_with_funcs(param, *parms, ctx->table,
252 ctx->func_table, ctx->context,
253 error_r)) <= 0) {
254 return ret;
255 }
256 const char *p = str_c(param);
257 array_push_back(¶ms, &p);
258 }
259
260 i_assert(array_count(¶ms) == 5);
261
262 /* execute comparison */
263 const char *const *args = array_front(¶ms);
264 if (var_expand_if_comp(args[0], args[1], args[2], &result, error_r)<0)
265 return -1;
266 *result_r = result ? args[3] : args[4];
267 return 1;
268 }
269
270