1 /* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
2  */
3 
4 #include "lib.h"
5 #include "ioloop.h"
6 #include "str-sanitize.h"
7 #include "strfuncs.h"
8 #include "istream.h"
9 #include "istream-header-filter.h"
10 #include "ostream.h"
11 #include "mail-storage.h"
12 
13 #include "rfc2822.h"
14 
15 #include "sieve-common.h"
16 #include "sieve-limits.h"
17 #include "sieve-address.h"
18 #include "sieve-commands.h"
19 #include "sieve-code.h"
20 #include "sieve-message.h"
21 #include "sieve-actions.h"
22 #include "sieve-validator.h"
23 #include "sieve-generator.h"
24 #include "sieve-interpreter.h"
25 #include "sieve-code-dumper.h"
26 #include "sieve-result.h"
27 #include "sieve-smtp.h"
28 #include "sieve-message.h"
29 
30 #include <stdio.h>
31 
32 /*
33  * Redirect command
34  *
35  * Syntax
36  *   redirect <address: string>
37  */
38 
39 static bool
40 cmd_redirect_validate(struct sieve_validator *validator,
41 		      struct sieve_command *cmd);
42 static bool
43 cmd_redirect_generate(const struct sieve_codegen_env *cgenv,
44 		      struct sieve_command *cmd);
45 
46 const struct sieve_command_def cmd_redirect = {
47 	.identifier = "redirect",
48 	.type = SCT_COMMAND,
49 	.positional_args = 1,
50 	.subtests = 0,
51 	.block_allowed = FALSE,
52 	.block_required = FALSE,
53 	.validate = cmd_redirect_validate,
54 	.generate = cmd_redirect_generate
55 };
56 
57 /*
58  * Redirect operation
59  */
60 
61 static bool
62 cmd_redirect_operation_dump(const struct sieve_dumptime_env *denv,
63 			    sieve_size_t *address);
64 static int
65 cmd_redirect_operation_execute(const struct sieve_runtime_env *renv,
66 			       sieve_size_t *address);
67 
68 const struct sieve_operation_def cmd_redirect_operation = {
69 	.mnemonic = "REDIRECT",
70 	.code = SIEVE_OPERATION_REDIRECT,
71 	.dump = cmd_redirect_operation_dump,
72 	.execute = cmd_redirect_operation_execute
73 };
74 
75 /*
76  * Redirect action
77  */
78 
79 static bool
80 act_redirect_equals(const struct sieve_script_env *senv,
81 		    const struct sieve_action *act1,
82 		    const struct sieve_action *act2);
83 static int
84 act_redirect_check_duplicate(const struct sieve_runtime_env *renv,
85 			     const struct sieve_action *act,
86 			     const struct sieve_action *act_other);
87 static void
88 act_redirect_print(const struct sieve_action *action,
89 		   const struct sieve_result_print_env *rpenv, bool *keep);
90 
91 static int
92 act_redirect_start(const struct sieve_action_exec_env *aenv, void **tr_context);
93 static int
94 act_redirect_execute(const struct sieve_action_exec_env *aenv, void *tr_context,
95 		    bool *keep);
96 static int
97 act_redirect_commit(const struct sieve_action_exec_env *aenv, void *tr_context);
98 
99 const struct sieve_action_def act_redirect = {
100 	.name = "redirect",
101 	.flags = SIEVE_ACTFLAG_TRIES_DELIVER,
102 	.equals = act_redirect_equals,
103 	.check_duplicate = act_redirect_check_duplicate,
104 	.print = act_redirect_print,
105 	.start = act_redirect_start,
106 	.execute = act_redirect_execute,
107 	.commit = act_redirect_commit,
108 };
109 
110 /*
111  * Validation
112  */
113 
114 static bool
cmd_redirect_validate(struct sieve_validator * validator,struct sieve_command * cmd)115 cmd_redirect_validate(struct sieve_validator *validator,
116 		      struct sieve_command *cmd)
117 {
118 	struct sieve_instance *svinst = sieve_validator_svinst(validator);
119 	struct sieve_ast_argument *arg = cmd->first_positional;
120 
121 	/* Check and activate address argument */
122 
123 	if (!sieve_validate_positional_argument(validator, cmd, arg, "address",
124 						1, SAAT_STRING))
125 		return FALSE;
126 
127 	if (!sieve_validator_argument_activate(validator, cmd, arg, FALSE))
128 		return FALSE;
129 
130 	/* We can only assess the validity of the outgoing address when it is
131 	 * a string literal. For runtime-generated strings this needs to be
132 	 * done at runtime.
133 	 */
134 	if (sieve_argument_is_string_literal(arg)) {
135 		string_t *raw_address = sieve_ast_argument_str(arg);
136 		const char *error;
137 		bool result;
138 
139 		T_BEGIN {
140 			/* Parse the address */
141 			result = sieve_address_validate_str(raw_address, &error);
142 			if (!result) {
143 				sieve_argument_validate_error(
144 					validator, arg,
145 					"specified redirect address '%s' is invalid: %s",
146 					str_sanitize(str_c(raw_address),128),
147 					error);
148 			}
149 		} T_END;
150 
151 		return result;
152 	}
153 
154 	if (svinst->max_redirects == 0) {
155 		sieve_command_validate_error(validator, cmd,
156 			"local policy prohibits the use of a redirect action");
157 		return FALSE;
158 	}
159 	return TRUE;
160 }
161 
162 /*
163  * Code generation
164  */
165 
166 static bool
cmd_redirect_generate(const struct sieve_codegen_env * cgenv,struct sieve_command * cmd)167 cmd_redirect_generate(const struct sieve_codegen_env *cgenv,
168 		      struct sieve_command *cmd)
169 {
170 	sieve_operation_emit(cgenv->sblock, NULL,  &cmd_redirect_operation);
171 
172 	/* Generate arguments */
173 	return sieve_generate_arguments(cgenv, cmd, NULL);
174 }
175 
176 /*
177  * Code dump
178  */
179 
180 static bool
cmd_redirect_operation_dump(const struct sieve_dumptime_env * denv,sieve_size_t * address)181 cmd_redirect_operation_dump(const struct sieve_dumptime_env *denv,
182 			    sieve_size_t *address)
183 {
184 	sieve_code_dumpf(denv, "REDIRECT");
185 	sieve_code_descend(denv);
186 
187 	if (sieve_action_opr_optional_dump(denv, address, NULL) != 0)
188 		return FALSE;
189 
190 	return sieve_opr_string_dump(denv, address, "address");
191 }
192 
193 /*
194  * Code execution
195  */
196 
197 static int
cmd_redirect_operation_execute(const struct sieve_runtime_env * renv,sieve_size_t * address)198 cmd_redirect_operation_execute(const struct sieve_runtime_env *renv,
199 			       sieve_size_t *address)
200 {
201 	const struct sieve_execute_env *eenv = renv->exec_env;
202 	struct sieve_instance *svinst = eenv->svinst;
203 	struct sieve_side_effects_list *slist = NULL;
204 	string_t *redirect;
205 	const struct smtp_address *to_address;
206 	const char *error;
207 	int ret;
208 
209 	/*
210 	 * Read data
211 	 */
212 
213 	/* Optional operands (side effects only) */
214 	if (sieve_action_opr_optional_read(renv, address, NULL,
215 					   &ret, &slist) != 0)
216 		return ret;
217 
218 	/* Read the address */
219 	if ((ret = sieve_opr_string_read(renv, address, "address",
220 					 &redirect)) <= 0)
221 		return ret;
222 
223 	/*
224 	 * Perform operation
225 	 */
226 
227 	/* Parse the address */
228 	to_address = sieve_address_parse_str(redirect, &error);
229 	if (to_address == NULL) {
230 		sieve_runtime_error(renv, NULL,
231 			"specified redirect address '%s' is invalid: %s",
232 			str_sanitize(str_c(redirect),128), error);
233 		return SIEVE_EXEC_FAILURE;
234 	}
235 
236 	if (svinst->max_redirects == 0) {
237 		sieve_runtime_error(renv, NULL,
238 			"local policy prohibits the use of a redirect action");
239 		return SIEVE_EXEC_FAILURE;
240 	}
241 
242 	if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS)) {
243 		sieve_runtime_trace(renv, 0, "redirect action");
244 		sieve_runtime_trace_descend(renv);
245 		sieve_runtime_trace(renv, 0, "forward message to address %s",
246 			smtp_address_encode_path(to_address));
247 	}
248 
249 	/* Add redirect action to the result */
250 
251 	return sieve_act_redirect_add_to_result(renv, "redirect", slist,
252 						to_address);
253 }
254 
255 /*
256  * Action implementation
257  */
258 
259 struct act_redirect_transaction {
260 	const char *msg_id, *new_msg_id;
261 	const char *dupeid;
262 
263 	bool skip_redirect:1;
264 };
265 
266 static bool
act_redirect_equals(const struct sieve_script_env * senv ATTR_UNUSED,const struct sieve_action * act1,const struct sieve_action * act2)267 act_redirect_equals(const struct sieve_script_env *senv ATTR_UNUSED,
268 		    const struct sieve_action *act1,
269 		    const struct sieve_action *act2)
270 {
271 	struct act_redirect_context *rd_ctx1 =
272 		(struct act_redirect_context *)act1->context;
273 	struct act_redirect_context *rd_ctx2 =
274 		(struct act_redirect_context *)act2->context;
275 
276 	/* Address is already normalized */
277 	return (smtp_address_equals(rd_ctx1->to_address, rd_ctx2->to_address));
278 }
279 
280 static int
act_redirect_check_duplicate(const struct sieve_runtime_env * renv,const struct sieve_action * act,const struct sieve_action * act_other)281 act_redirect_check_duplicate(const struct sieve_runtime_env *renv,
282 			     const struct sieve_action *act,
283 			     const struct sieve_action *act_other)
284 {
285 	const struct sieve_execute_env *eenv = renv->exec_env;
286 
287 	return (act_redirect_equals(eenv->scriptenv, act, act_other) ? 1 : 0);
288 }
289 
290 static void
act_redirect_print(const struct sieve_action * action,const struct sieve_result_print_env * rpenv,bool * keep)291 act_redirect_print(const struct sieve_action *action,
292 		   const struct sieve_result_print_env *rpenv, bool *keep)
293 {
294 	struct act_redirect_context *ctx =
295 		(struct act_redirect_context *)action->context;
296 
297 	sieve_result_action_printf(rpenv, "redirect message to: %s",
298 				   smtp_address_encode_path(ctx->to_address));
299 	*keep = FALSE;
300 }
301 
302 static int
act_redirect_send(const struct sieve_action_exec_env * aenv,struct mail * mail,struct act_redirect_context * ctx,const char * new_msg_id)303 act_redirect_send(const struct sieve_action_exec_env *aenv, struct mail *mail,
304 		  struct act_redirect_context *ctx, const char *new_msg_id)
305 		  ATTR_NULL(4)
306 {
307 	static const char *hide_headers[] = { "Return-Path" };
308 	const struct sieve_execute_env *eenv = aenv->exec_env;
309 	struct sieve_instance *svinst = eenv->svinst;
310 	struct sieve_message_context *msgctx = aenv->msgctx;
311 	const struct sieve_script_env *senv = eenv->scriptenv;
312 	struct sieve_address_source env_from = svinst->redirect_from;
313 	struct istream *input;
314 	struct ostream *output;
315 	const struct smtp_address *sender;
316 	const char *error;
317 	struct sieve_smtp_context *sctx;
318 	int ret;
319 
320 	/* Just to be sure */
321 	if (!sieve_smtp_available(senv)) {
322 		sieve_result_global_warning(aenv, "no means to send mail");
323 		return SIEVE_EXEC_FAILURE;
324 	}
325 
326 	if (mail_get_stream(mail, NULL, NULL, &input) < 0) {
327 		return sieve_result_mail_error(aenv, mail,
328 					       "failed to read input message");
329 	}
330 
331 	/* Determine which sender to use
332 
333 	   From RFC 5228, Section 4.2:
334 
335 	   The envelope sender address on the outgoing message is chosen by the
336 	   sieve implementation. It MAY be copied from the message being
337 	   processed. However, if the message being processed has an empty
338 	   envelope sender address the outgoing message MUST also have an empty
339 	   envelope sender address. This last requirement is imposed to prevent
340 	   loops in the case where a message is redirected to an invalid address
341 	   when then returns a delivery status notification that also ends up
342 	   being redirected to the same invalid address.
343 	 */
344 	if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0) {
345 		/* Envelope available */
346 		sender = sieve_message_get_sender(msgctx);
347 		if (sender != NULL &&
348 		    sieve_address_source_get_address(&env_from, svinst, senv,
349 						     msgctx, eenv->flags,
350 						     &sender) < 0)
351 			sender = NULL;
352 	} else {
353 		/* No envelope available */
354 		ret = sieve_address_source_get_address(&env_from, svinst, senv,
355 						       msgctx, eenv->flags,
356 						       &sender);
357 		if (ret < 0)
358 			sender = NULL;
359 		else if (ret == 0)
360 			sender = svinst->user_email;
361 	}
362 
363 	/* Open SMTP transport */
364 	sctx = sieve_smtp_start_single(senv, ctx->to_address, sender, &output);
365 
366 	/* Remove unwanted headers */
367 	input = i_stream_create_header_filter(
368 		input, HEADER_FILTER_EXCLUDE | HEADER_FILTER_NO_CR,
369 		hide_headers, N_ELEMENTS(hide_headers),
370 		*null_header_filter_callback, (void *)NULL);
371 
372 	T_BEGIN {
373 		string_t *hdr = t_str_new(256);
374 		const struct smtp_address *user_email;
375 
376 		/* Prepend sieve headers (should not affect signatures) */
377 		rfc2822_header_append(hdr, "X-Sieve", SIEVE_IMPLEMENTATION,
378 				      FALSE, NULL);
379 		if (svinst->user_email == NULL &&
380 		    (eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0)
381 			user_email = sieve_message_get_final_recipient(msgctx);
382 		else
383 			user_email = sieve_get_user_email(svinst);
384 		if (user_email != NULL) {
385 			rfc2822_header_append(hdr, "X-Sieve-Redirected-From",
386 					      smtp_address_encode(user_email),
387 					      FALSE, NULL);
388 		}
389 
390 		/* Add new Message-ID if message doesn't have one */
391 		if (new_msg_id != NULL)
392 			rfc2822_header_write(hdr, "Message-ID", new_msg_id);
393 
394 		o_stream_nsend(output, str_data(hdr), str_len(hdr));
395 	} T_END;
396 
397 	o_stream_nsend_istream(output, input);
398 
399 	if (input->stream_errno != 0) {
400 		sieve_result_critical(aenv, "failed to read input message",
401 				      "read(%s) failed: %s",
402 				      i_stream_get_name(input),
403 				      i_stream_get_error(input));
404 		i_stream_unref(&input);
405 		sieve_smtp_abort(sctx);
406 		return SIEVE_EXEC_TEMP_FAILURE;
407 	}
408 	i_stream_unref(&input);
409 
410 	/* Close SMTP transport */
411 	if ((ret = sieve_smtp_finish(sctx, &error)) <= 0) {
412 		if (ret < 0) {
413 			sieve_result_global_error(
414 				aenv, "failed to redirect message to <%s>: %s "
415 				"(temporary failure)",
416 				smtp_address_encode(ctx->to_address),
417 				str_sanitize(error, 512));
418 			return SIEVE_EXEC_TEMP_FAILURE;
419 		}
420 
421 		sieve_result_global_log_error(
422 			aenv, "failed to redirect message to <%s>: %s "
423 			"(permanent failure)",
424 			smtp_address_encode(ctx->to_address),
425 			str_sanitize(error, 512));
426 		return SIEVE_EXEC_FAILURE;
427 	}
428 
429 	return SIEVE_EXEC_OK;
430 }
431 
432 static int
act_redirect_get_duplicate_id(struct act_redirect_context * ctx,const struct sieve_action_exec_env * aenv,const char * msg_id,const char ** dupeid_r)433 act_redirect_get_duplicate_id(struct act_redirect_context *ctx,
434 			      const struct sieve_action_exec_env *aenv,
435 			      const char *msg_id, const char **dupeid_r)
436 {
437 	const struct sieve_execute_env *eenv = aenv->exec_env;
438 	struct sieve_message_context *msgctx = aenv->msgctx;
439 	const struct sieve_message_data *msgdata = eenv->msgdata;
440 	struct mail *mail = msgdata->mail;
441 	const struct smtp_address *recipient;
442 	const char *resent_id = NULL, *list_id = NULL;
443 
444 	/* Read identifying headers */
445 	if (mail_get_first_header(mail, "resent-message-id", &resent_id) < 0) {
446 		return sieve_result_mail_error(
447 			aenv, mail,
448 			"failed to read header field `resent-message-id'");
449 	}
450 	if (resent_id == NULL &&
451 	    mail_get_first_header(mail, "resent-from", &resent_id) < 0) {
452 		return sieve_result_mail_error(
453 			aenv, mail,
454 			"failed to read header field `resent-from'");
455 	}
456 	if (mail_get_first_header(mail, "list-id", &list_id) < 0) {
457 		return sieve_result_mail_error(
458 			aenv, mail,
459 			"failed to read header field `list-id'");
460 	}
461 
462 	if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0)
463 		recipient = sieve_message_get_orig_recipient(msgctx);
464 	else
465 		recipient = sieve_get_user_email(eenv->svinst);
466 
467 	pool_t pool = sieve_result_pool(aenv->result);
468 
469 	/* Base the duplicate ID on:
470 	   - the message id
471 	   - the recipient running this Sieve script
472 	   - redirect target address
473 	   - if this message is resent: the message-id or from-address of
474 		   the original message
475 	   - if the message came through a mailing list: the mailinglist ID
476 	 */
477 	*dupeid_r = p_strdup_printf(
478 		pool, "%s-%s-%s-%s-%s", msg_id,
479 		(recipient != NULL ? smtp_address_encode(recipient) : ""),
480 		smtp_address_encode(ctx->to_address),
481 		(resent_id != NULL ? resent_id : ""),
482 		(list_id != NULL ? list_id : ""));
483 	return SIEVE_EXEC_OK;
484 }
485 
486 static int
act_redirect_check_loop_header(const struct sieve_action_exec_env * aenv,struct mail * mail,bool * loop_detected_r)487 act_redirect_check_loop_header(const struct sieve_action_exec_env *aenv,
488 			       struct mail *mail, bool *loop_detected_r)
489 {
490 	const struct sieve_execute_env *eenv = aenv->exec_env;
491 	struct sieve_message_context *msgctx = aenv->msgctx;
492 	const char *const *headers;
493 	const char *recipient, *user_email;
494 	const struct smtp_address *addr;
495 	int ret;
496 
497 	*loop_detected_r = FALSE;
498 
499 	ret = mail_get_headers(mail, "x-sieve-redirected-from", &headers);
500 	if (ret < 0) {
501 		return sieve_result_mail_error(
502 			aenv, mail, "failed to read header field "
503 			"`x-sieve-redirected-from'");
504 	}
505 
506 	if (ret == 0)
507 		return SIEVE_EXEC_OK;
508 
509 	recipient = user_email = NULL;
510 	if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0) {
511 		addr = sieve_message_get_final_recipient(msgctx);
512 		if (addr != NULL)
513 			recipient = smtp_address_encode(addr);
514 	}
515 	addr = sieve_get_user_email(eenv->svinst);
516 	if (addr != NULL)
517 		user_email = smtp_address_encode(addr);
518 
519 	while (*headers != NULL) {
520 		const char *header = t_str_trim(*headers, " \t\r\n");
521 		if (recipient != NULL && strcmp(header, recipient) == 0) {
522 			*loop_detected_r = TRUE;
523 			break;
524 		}
525 		if (user_email != NULL && strcmp(header, user_email) == 0) {
526 			*loop_detected_r = TRUE;
527 			break;
528 		}
529 		headers++;
530 	}
531 
532 	return SIEVE_EXEC_OK;
533 }
534 
535 static int
act_redirect_start(const struct sieve_action_exec_env * aenv,void ** tr_context)536 act_redirect_start(const struct sieve_action_exec_env *aenv, void **tr_context)
537 {
538 	struct act_redirect_transaction *trans;
539 	pool_t pool = sieve_result_pool(aenv->result);
540 
541 	/* Create transaction context */
542 	trans = p_new(pool, struct act_redirect_transaction, 1);
543 	*tr_context = trans;
544 
545 	return SIEVE_EXEC_OK;
546 }
547 
548 static int
act_redirect_execute(const struct sieve_action_exec_env * aenv,void * tr_context,bool * keep)549 act_redirect_execute(const struct sieve_action_exec_env *aenv,
550 		     void *tr_context, bool *keep)
551 {
552 	const struct sieve_action *action = aenv->action;
553 	const struct sieve_execute_env *eenv = aenv->exec_env;
554 	struct sieve_instance *svinst = eenv->svinst;
555 	struct act_redirect_context *ctx =
556 		(struct act_redirect_context *)action->context;
557 	struct act_redirect_transaction *trans = tr_context;
558 	struct sieve_message_context *msgctx = aenv->msgctx;
559 	struct mail *mail = (action->mail != NULL ?
560 			     action->mail : sieve_message_get_mail(msgctx));
561 	const struct sieve_message_data *msgdata = eenv->msgdata;
562 	bool duplicate, loop_detected = FALSE;
563 	int ret;
564 
565 	/*
566 	 * Prevent mail loops
567 	 */
568 
569 	/* Create Message-ID for the message if it has none */
570 	trans->msg_id = msgdata->id;
571 	if (trans->msg_id == NULL) {
572 		pool_t pool = sieve_result_pool(aenv->result);
573 		trans->msg_id = trans->new_msg_id =
574 			p_strdup(pool, sieve_message_get_new_id(svinst));
575 	}
576 
577 	/* Create ID for duplicate database lookup */
578 	ret = act_redirect_get_duplicate_id(ctx, aenv, trans->msg_id,
579 					    &trans->dupeid);
580 	if (ret != SIEVE_EXEC_OK)
581 		return ret;
582 	i_assert(trans->dupeid != NULL);
583 
584 	/* Check whether we've seen this message before */
585 	ret = sieve_action_duplicate_check(aenv, trans->dupeid,
586 					   strlen(trans->dupeid),
587 					   &duplicate);
588 	if (ret < SIEVE_EXEC_OK) {
589 		sieve_result_critical(
590 			aenv, "failed to check for duplicate forward",
591 			"failed to check for duplicate forward to <%s>%s",
592 			smtp_address_encode(ctx->to_address),
593 			(ret == SIEVE_EXEC_TEMP_FAILURE ?
594 			 " (temporaty failure)" : ""));
595 		return ret;
596 	}
597 	if (duplicate) {
598 		sieve_result_global_log(
599 			aenv, "discarded duplicate forward to <%s>",
600 			smtp_address_encode(ctx->to_address));
601 		trans->skip_redirect = TRUE;
602 		return SIEVE_EXEC_OK;
603 	}
604 
605 	/* Check whether we've seen this message before based on added headers
606 	 */
607 	ret = act_redirect_check_loop_header(aenv, mail, &loop_detected);
608 	if (ret != SIEVE_EXEC_OK)
609 		return ret;
610 	if (loop_detected) {
611 		sieve_result_global_log(
612 			aenv, "not forwarding message to <%s>: "
613 			"the `x-sieve-redirected-from' header indicates a mail loop",
614 			smtp_address_encode(ctx->to_address));
615 		trans->skip_redirect = TRUE;
616 		return SIEVE_EXEC_OK;
617 	}
618 
619 	/* Cancel implicit keep */
620 	*keep = FALSE;
621 
622 	return SIEVE_EXEC_OK;
623 }
624 
625 static int
act_redirect_commit(const struct sieve_action_exec_env * aenv,void * tr_context)626 act_redirect_commit(const struct sieve_action_exec_env *aenv, void *tr_context)
627 {
628 	const struct sieve_action *action = aenv->action;
629 	const struct sieve_execute_env *eenv = aenv->exec_env;
630 	struct sieve_instance *svinst = eenv->svinst;
631 	struct act_redirect_context *ctx =
632 		(struct act_redirect_context *)action->context;
633 	struct sieve_message_context *msgctx = aenv->msgctx;
634 	struct mail *mail = (action->mail != NULL ?
635 			     action->mail : sieve_message_get_mail(msgctx));
636 	struct act_redirect_transaction *trans = tr_context;
637 	int ret;
638 
639 	if (trans->skip_redirect)
640 		return SIEVE_EXEC_OK;
641 
642 	/*
643 	 * Try to forward the message
644 	 */
645 
646 	ret = act_redirect_send(aenv, mail, ctx, trans->new_msg_id);
647 	if (ret == SIEVE_EXEC_OK) {
648 		/* Mark this message id as forwarded to the specified
649 		   destination */
650 		sieve_action_duplicate_mark(
651 			aenv, trans->dupeid, strlen(trans->dupeid),
652 			ioloop_time + svinst->redirect_duplicate_period);
653 
654 		eenv->exec_status->significant_action_executed = TRUE;
655 
656 		struct event_passthrough *e =
657 			sieve_action_create_finish_event(aenv)->
658 			add_str("redirect_target",
659 				smtp_address_encode(ctx->to_address));
660 
661 		sieve_result_event_log(aenv, e->event(),
662 				       "forwarded to <%s>",
663 				       smtp_address_encode(ctx->to_address));
664 
665 		/* Indicate that message was successfully forwarded */
666 		eenv->exec_status->message_forwarded = TRUE;
667 
668 		return SIEVE_EXEC_OK;
669 	}
670 
671 	return ret;
672 }
673