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(&reg, rhs, REG_EXTENDED)) != 0) {
144 			size_t siz;
145 			char *errbuf;
146 			siz = regerror(ec, &reg, NULL, 0);
147 			errbuf = t_malloc_no0(siz);
148 			(void)regerror(ec, &reg, errbuf, siz);
149 			*error_r = t_strdup_printf("if: regex failed: %s",
150 						   errbuf);
151 			return -1;
152 		}
153 		if ((ec = regexec(&reg, lhs, 0, 0, 0)) != 0) {
154 			i_assert(ec == REG_NOMATCH);
155 			res = FALSE;
156 		} else {
157 			res = TRUE;
158 		}
159 		regfree(&reg);
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(&params, 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(&params, &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(&params, &par);
231 	}
232 
233 	if (array_count(&params) != 5) {
234 		if (array_count(&params) == 4) {
235 			const char *empty = "";
236 			array_push_back(&params, &empty);
237 		} else {
238 			*error_r = t_strdup_printf("if: requires four or five parameters, got %u",
239 						   array_count(&params));
240 			return -1;
241 		}
242 	}
243 
244 	array_append_zero(&params);
245 	parms = array_front(&params);
246 	t_array_init(&params, 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(&params, &p);
258 	}
259 
260 	i_assert(array_count(&params) == 5);
261 
262 	/* execute comparison */
263 	const char *const *args = array_front(&params);
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