1 /* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
2  */
3 
4 #include "lib.h"
5 #include "str-sanitize.h"
6 #include "array.h"
7 #include "mail-storage.h"
8 
9 #include "sieve-code.h"
10 #include "sieve-stringlist.h"
11 #include "sieve-extensions.h"
12 #include "sieve-commands.h"
13 #include "sieve-result.h"
14 #include "sieve-validator.h"
15 #include "sieve-generator.h"
16 #include "sieve-interpreter.h"
17 #include "sieve-actions.h"
18 #include "sieve-dump.h"
19 
20 #include "ext-imap4flags-common.h"
21 
22 #include <ctype.h>
23 
24 /*
25  * Flags tagged argument
26  */
27 
28 static bool
29 tag_flags_validate(struct sieve_validator *valdtr,
30 		   struct sieve_ast_argument **arg, struct sieve_command *cmd);
31 static bool
32 tag_flags_validate_persistent(struct sieve_validator *valdtr,
33 			      struct sieve_command *cmd,
34 			      const struct sieve_extension *ext);
35 static bool
36 tag_flags_generate(const struct sieve_codegen_env *cgenv,
37 		   struct sieve_ast_argument *arg, struct sieve_command *cmd);
38 
39 const struct sieve_argument_def tag_flags = {
40 	.identifier = "flags",
41 	.validate = tag_flags_validate,
42 	.generate = tag_flags_generate,
43 };
44 
45 const struct sieve_argument_def tag_flags_implicit = {
46 	.identifier = "flags-implicit",
47 	.validate_persistent = tag_flags_validate_persistent,
48 	.generate = tag_flags_generate,
49 };
50 
51 /*
52  * Side effect
53  */
54 
55 static bool
56 seff_flags_dump_context(const struct sieve_side_effect *seffect,
57 			const struct sieve_dumptime_env *denv,
58 			sieve_size_t *address);
59 static int
60 seff_flags_read_context(const struct sieve_side_effect *seffect,
61 			const struct sieve_runtime_env *renv,
62 			sieve_size_t *address, void **context);
63 
64 static int
65 seff_flags_merge(const struct sieve_runtime_env *renv,
66 		 const struct sieve_action *action,
67 		 const struct sieve_side_effect *old_seffect,
68 		 const struct sieve_side_effect *new_seffect,
69 		 void **old_context);
70 
71 static void
72 seff_flags_print(const struct sieve_side_effect *seffect,
73 		 const struct sieve_action *action,
74 		 const struct sieve_result_print_env *rpenv, bool *keep);
75 
76 static int
77 seff_flags_pre_execute(const struct sieve_side_effect *seffect,
78 		       const struct sieve_action_exec_env *aenv,
79 		       void *tr_context, void **se_tr_context);
80 
81 const struct sieve_side_effect_def flags_side_effect = {
82 	SIEVE_OBJECT("flags", &flags_side_effect_operand, 0),
83 	.to_action = &act_store,
84 	.dump_context = seff_flags_dump_context,
85 	.read_context = seff_flags_read_context,
86 	.merge = seff_flags_merge,
87 	.print = seff_flags_print,
88 	.pre_execute = seff_flags_pre_execute,
89 };
90 
91 /*
92  * Operand
93  */
94 
95 static const struct sieve_extension_objects ext_side_effects =
96 	SIEVE_EXT_DEFINE_SIDE_EFFECT(flags_side_effect);
97 
98 const struct sieve_operand_def flags_side_effect_operand = {
99 	.name = "flags operand",
100 	.ext_def = &imap4flags_extension,
101 	.class = &sieve_side_effect_operand_class,
102 	.interface = &ext_side_effects,
103 };
104 
105 /*
106  * Tag validation
107  */
108 
109 static bool
tag_flags_validate_persistent(struct sieve_validator * valdtr ATTR_UNUSED,struct sieve_command * cmd,const struct sieve_extension * ext)110 tag_flags_validate_persistent(struct sieve_validator *valdtr ATTR_UNUSED,
111 			      struct sieve_command *cmd,
112 			      const struct sieve_extension *ext)
113 {
114 	if (sieve_command_find_argument(cmd, &tag_flags) == NULL)
115 		sieve_command_add_dynamic_tag(cmd, ext, &tag_flags_implicit,
116 					      -1);
117 	return TRUE;
118 }
119 
120 static bool
tag_flags_validate(struct sieve_validator * valdtr,struct sieve_ast_argument ** arg,struct sieve_command * cmd)121 tag_flags_validate(struct sieve_validator *valdtr,
122 		   struct sieve_ast_argument **arg, struct sieve_command *cmd)
123 {
124 	struct sieve_ast_argument *tag = *arg;
125 
126 	/* Detach the tag itself */
127 	*arg = sieve_ast_argument_next(*arg);
128 
129 	/* Check syntax:
130 	 *   :flags <list-of-flags: string-list>
131 	 */
132 	if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
133 					  SAAT_STRING_LIST, FALSE))
134 		return FALSE;
135 
136 	tag->parameters = *arg;
137 
138 	/* Detach parameter */
139 	*arg = sieve_ast_arguments_detach(*arg,1);
140 	return TRUE;
141 }
142 
143 /*
144  * Code generation
145  */
146 
147 static bool
tag_flags_generate(const struct sieve_codegen_env * cgenv,struct sieve_ast_argument * arg,struct sieve_command * cmd)148 tag_flags_generate(const struct sieve_codegen_env *cgenv,
149 		   struct sieve_ast_argument *arg, struct sieve_command *cmd)
150 {
151 	struct sieve_ast_argument *param;
152 
153 	if (sieve_ast_argument_type(arg) != SAAT_TAG)
154 		return FALSE;
155 
156 	sieve_opr_side_effect_emit(cgenv->sblock, arg->argument->ext,
157 				   &flags_side_effect);
158 
159 	if (sieve_argument_is(arg, tag_flags)) {
160 		/* Explicit :flags tag */
161 		param = arg->parameters;
162 
163 		/* Call the generation function for the argument */
164 		if (param->argument != NULL && param->argument->def != NULL &&
165 		    param->argument->def->generate != NULL &&
166 		    !param->argument->def->generate(cgenv, param, cmd))
167 			return FALSE;
168 	} else if (sieve_argument_is(arg, tag_flags_implicit)) {
169 		/* Implicit flags */
170 		sieve_opr_omitted_emit(cgenv->sblock);
171 	} else {
172 		/* Something else?! */
173 		i_unreached();
174 	}
175 	return TRUE;
176 }
177 
178 /*
179  * Side effect implementation
180  */
181 
182 /* Context data */
183 
184 struct seff_flags_context {
185 	ARRAY(const char *) keywords;
186 	enum mail_flags flags;
187 };
188 
189 /* Context coding */
190 
191 static bool
seff_flags_dump_context(const struct sieve_side_effect * seffect ATTR_UNUSED,const struct sieve_dumptime_env * denv,sieve_size_t * address)192 seff_flags_dump_context(const struct sieve_side_effect *seffect ATTR_UNUSED,
193 			const struct sieve_dumptime_env *denv,
194 			sieve_size_t *address)
195 {
196 	return sieve_opr_stringlist_dump_ex(denv, address, "flags", "INTERNAL");
197 }
198 
199 static struct seff_flags_context *
seff_flags_get_implicit_context(const struct sieve_extension * this_ext,struct sieve_result * result)200 seff_flags_get_implicit_context(const struct sieve_extension *this_ext,
201 				struct sieve_result *result)
202 {
203 	pool_t pool = sieve_result_pool(result);
204 	struct seff_flags_context *ctx;
205 	const char *flag;
206 	struct ext_imap4flags_iter flit;
207 
208 	ctx = p_new(pool, struct seff_flags_context, 1);
209 	p_array_init(&ctx->keywords, pool, 2);
210 
211 	T_BEGIN {
212 		/* Unpack */
213 		ext_imap4flags_get_implicit_flags_init(&flit, this_ext, result);
214 		while ((flag = ext_imap4flags_iter_get_flag(&flit)) != NULL) {
215 			if (flag != NULL && *flag != '\\') {
216 				/* keyword */
217 				const char *keyword = p_strdup(pool, flag);
218 				array_append(&ctx->keywords, &keyword, 1);
219 			} else {
220 				/* system flag */
221 				if (flag == NULL ||
222 				    strcasecmp(flag, "\\flagged") == 0)
223 					ctx->flags |= MAIL_FLAGGED;
224 				else if (strcasecmp(flag, "\\answered") == 0)
225 					ctx->flags |= MAIL_ANSWERED;
226 				else if (strcasecmp(flag, "\\deleted") == 0)
227 					ctx->flags |= MAIL_DELETED;
228 				else if (strcasecmp(flag, "\\seen") == 0)
229 					ctx->flags |= MAIL_SEEN;
230 				else if (strcasecmp(flag, "\\draft") == 0)
231 					ctx->flags |= MAIL_DRAFT;
232 			}
233 		}
234 	} T_END;
235 
236 	return ctx;
237 }
238 
239 static int
seff_flags_do_read_context(const struct sieve_side_effect * seffect,const struct sieve_runtime_env * renv,sieve_size_t * address,void ** se_context)240 seff_flags_do_read_context(const struct sieve_side_effect *seffect,
241 			   const struct sieve_runtime_env *renv,
242 			   sieve_size_t *address, void **se_context)
243 {
244 	pool_t pool = sieve_result_pool(renv->result);
245 	struct seff_flags_context *ctx;
246 	string_t *flags_item;
247 	struct sieve_stringlist *flag_list = NULL;
248 	int ret;
249 
250 	ret = sieve_opr_stringlist_read_ex(renv, address, "flags", TRUE,
251 					   &flag_list);
252 	if (ret <= 0)
253 		return ret;
254 
255 	if (flag_list == NULL) {
256 		/* Flag list is omitted, use current value of internal
257 		 * variable to construct side effect context.
258 		 */
259 		*se_context = seff_flags_get_implicit_context(
260 			SIEVE_OBJECT_EXTENSION(seffect), renv->result);
261 		return SIEVE_EXEC_OK;
262 	}
263 
264 	ctx = p_new(pool, struct seff_flags_context, 1);
265 	p_array_init(&ctx->keywords, pool, 2);
266 
267 	/* Unpack */
268 	flags_item = NULL;
269 	while ((ret = sieve_stringlist_next_item(flag_list, &flags_item)) > 0) {
270 		const char *flag;
271 		struct ext_imap4flags_iter flit;
272 
273 		ext_imap4flags_iter_init(&flit, flags_item);
274 
275 		while ((flag = ext_imap4flags_iter_get_flag(&flit)) != NULL) {
276 			if (flag != NULL && *flag != '\\') {
277 				/* keyword */
278 				const char *keyword = p_strdup(pool, flag);
279 
280 				/* FIXME: should check for duplicates (cannot
281 				   trust variables) */
282 				array_append(&ctx->keywords, &keyword, 1);
283 			} else {
284 				/* system flag */
285 				if (flag == NULL ||
286 				    strcasecmp(flag, "\\flagged") == 0)
287 					ctx->flags |= MAIL_FLAGGED;
288 				else if (strcasecmp(flag, "\\answered") == 0)
289 					ctx->flags |= MAIL_ANSWERED;
290 				else if (strcasecmp(flag, "\\deleted") == 0)
291 					ctx->flags |= MAIL_DELETED;
292 				else if (strcasecmp(flag, "\\seen") == 0)
293 					ctx->flags |= MAIL_SEEN;
294 				else if (strcasecmp(flag, "\\draft") == 0)
295 					ctx->flags |= MAIL_DRAFT;
296 			}
297 		}
298 	}
299 
300 	if (ret < 0)
301 		return flag_list->exec_status;
302 
303 	*se_context = (void *)ctx;
304 	return SIEVE_EXEC_OK;
305 }
306 
307 static int
seff_flags_read_context(const struct sieve_side_effect * seffect,const struct sieve_runtime_env * renv,sieve_size_t * address,void ** se_context)308 seff_flags_read_context(const struct sieve_side_effect *seffect,
309 			const struct sieve_runtime_env *renv,
310 			sieve_size_t *address, void **se_context)
311 {
312 	int ret;
313 
314 	T_BEGIN {
315 		ret = seff_flags_do_read_context(seffect, renv, address,
316 						 se_context);
317 	} T_END;
318 
319 	return ret;
320 }
321 
322 /* Result verification */
323 
324 static int
seff_flags_merge(const struct sieve_runtime_env * renv ATTR_UNUSED,const struct sieve_action * action ATTR_UNUSED,const struct sieve_side_effect * old_seffect ATTR_UNUSED,const struct sieve_side_effect * new_seffect,void ** old_context)325 seff_flags_merge(const struct sieve_runtime_env *renv ATTR_UNUSED,
326 		 const struct sieve_action *action ATTR_UNUSED,
327 		 const struct sieve_side_effect *old_seffect ATTR_UNUSED,
328 		 const struct sieve_side_effect *new_seffect,
329 		 void **old_context)
330 {
331 	if (new_seffect != NULL)
332 		*old_context = new_seffect->context;
333 	return 1;
334 }
335 
336 /* Result printing */
337 
338 static void
seff_flags_print(const struct sieve_side_effect * seffect,const struct sieve_action * action ATTR_UNUSED,const struct sieve_result_print_env * rpenv,bool * keep ATTR_UNUSED)339 seff_flags_print(const struct sieve_side_effect *seffect,
340 		 const struct sieve_action *action ATTR_UNUSED,
341 		 const struct sieve_result_print_env *rpenv,
342 		 bool *keep ATTR_UNUSED)
343 {
344 	struct sieve_result *result = rpenv->result;
345 	struct seff_flags_context *ctx =
346 		(struct seff_flags_context *)seffect->context;
347 	unsigned int i;
348 
349 	if (ctx == NULL) {
350 		ctx = seff_flags_get_implicit_context(
351 			SIEVE_OBJECT_EXTENSION(seffect), result);
352 	}
353 
354 	if (ctx->flags != 0 || array_count(&ctx->keywords) > 0) {
355 		T_BEGIN {
356 			string_t *flags = t_str_new(128);
357 
358 			if ((ctx->flags & MAIL_FLAGGED) > 0)
359 				str_printfa(flags, " \\flagged");
360 			if ((ctx->flags & MAIL_ANSWERED) > 0)
361 				str_printfa(flags, " \\answered");
362 			if ((ctx->flags & MAIL_DELETED) > 0)
363 				str_printfa(flags, " \\deleted");
364 			if ((ctx->flags & MAIL_SEEN) > 0)
365 				str_printfa(flags, " \\seen");
366 			if ((ctx->flags & MAIL_DRAFT) > 0)
367 				str_printfa(flags, " \\draft");
368 
369 			for (i = 0; i < array_count(&ctx->keywords); i++) {
370 				const char *const *keyword =
371 					array_idx(&ctx->keywords, i);
372 				str_printfa(flags, " %s",
373 					    str_sanitize(*keyword, 64));
374 			}
375 
376 			sieve_result_seffect_printf(rpenv, "add IMAP flags:%s",
377 						    str_c(flags));
378 		} T_END;
379 	}
380 }
381 
382 /* Result execution */
383 
384 static int
seff_flags_pre_execute(const struct sieve_side_effect * seffect,const struct sieve_action_exec_env * aenv,void * tr_context,void ** se_tr_context ATTR_UNUSED)385 seff_flags_pre_execute(const struct sieve_side_effect *seffect,
386 		       const struct sieve_action_exec_env *aenv,
387 		       void *tr_context, void **se_tr_context ATTR_UNUSED)
388 {
389 	struct seff_flags_context *ctx = seffect->context;
390 	const char *const *keywords;
391 
392 	if (ctx == NULL) {
393 		ctx = seff_flags_get_implicit_context(
394 			SIEVE_OBJECT_EXTENSION(seffect), aenv->result);
395 	}
396 
397 	(void)array_append_space(&ctx->keywords);
398 	keywords = array_idx(&ctx->keywords, 0);
399 
400 	sieve_act_store_add_flags(aenv, tr_context, keywords, ctx->flags);
401 	return SIEVE_EXEC_OK;
402 }
403