summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/lib-sieve/plugins/notify/cmd-notify.c
diff options
context:
space:
mode:
Diffstat (limited to 'pigeonhole/src/lib-sieve/plugins/notify/cmd-notify.c')
-rw-r--r--pigeonhole/src/lib-sieve/plugins/notify/cmd-notify.c900
1 files changed, 900 insertions, 0 deletions
diff --git a/pigeonhole/src/lib-sieve/plugins/notify/cmd-notify.c b/pigeonhole/src/lib-sieve/plugins/notify/cmd-notify.c
new file mode 100644
index 0000000..a683c31
--- /dev/null
+++ b/pigeonhole/src/lib-sieve/plugins/notify/cmd-notify.c
@@ -0,0 +1,900 @@
+/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
+ */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "ioloop.h"
+#include "str-sanitize.h"
+#include "ostream.h"
+#include "message-date.h"
+#include "mail-storage.h"
+
+#include "rfc2822.h"
+
+#include "sieve-common.h"
+#include "sieve-stringlist.h"
+#include "sieve-code.h"
+#include "sieve-extensions.h"
+#include "sieve-commands.h"
+#include "sieve-actions.h"
+#include "sieve-validator.h"
+#include "sieve-generator.h"
+#include "sieve-interpreter.h"
+#include "sieve-dump.h"
+#include "sieve-result.h"
+#include "sieve-address.h"
+#include "sieve-message.h"
+#include "sieve-smtp.h"
+
+#include "ext-notify-common.h"
+#include "ext-notify-limits.h"
+
+#include <ctype.h>
+
+/* Notify command (DEPRECATED)
+ *
+ * Syntax:
+ * notify [":method" string] [":id" string] [":options" string-list]
+ * [<":low" / ":normal" / ":high">] ["message:" string]
+ *
+ */
+
+static bool
+cmd_notify_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg);
+static bool
+cmd_notify_pre_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd);
+static bool
+cmd_notify_validate(struct sieve_validator *valdtr,
+ struct sieve_command *cmd);
+static bool
+cmd_notify_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *ctx);
+
+const struct sieve_command_def cmd_notify_old = {
+ .identifier = "notify",
+ .type = SCT_COMMAND,
+ .positional_args = 0,
+ .subtests = 0,
+ .block_allowed = FALSE,
+ .block_required = FALSE,
+ .registered = cmd_notify_registered,
+ .pre_validate = cmd_notify_pre_validate,
+ .validate = cmd_notify_validate,
+ .generate = cmd_notify_generate
+};
+
+/*
+ * Tagged arguments
+ */
+
+/* Forward declarations */
+
+static bool
+cmd_notify_validate_string_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+static bool
+cmd_notify_validate_stringlist_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd);
+
+/* Argument objects */
+
+static const struct sieve_argument_def notify_method_tag = {
+ .identifier = "method",
+ .validate = cmd_notify_validate_string_tag
+};
+
+static const struct sieve_argument_def notify_options_tag = {
+ .identifier = "options",
+ .validate = cmd_notify_validate_stringlist_tag
+};
+
+static const struct sieve_argument_def notify_id_tag = {
+ .identifier = "id",
+ .validate = cmd_notify_validate_string_tag
+};
+
+static const struct sieve_argument_def notify_message_tag = {
+ .identifier = "message",
+ .validate = cmd_notify_validate_string_tag
+};
+
+/*
+ * Notify operation
+ */
+
+static bool
+cmd_notify_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address);
+static int
+cmd_notify_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address);
+
+const struct sieve_operation_def notify_old_operation = {
+ .mnemonic = "NOTIFY",
+ .ext_def = &notify_extension,
+ .code = EXT_NOTIFY_OPERATION_NOTIFY,
+ .dump = cmd_notify_operation_dump,
+ .execute = cmd_notify_operation_execute
+};
+
+/* Codes for optional operands */
+
+enum cmd_notify_optional {
+ OPT_END,
+ OPT_MESSAGE,
+ OPT_IMPORTANCE,
+ OPT_OPTIONS,
+ OPT_ID
+};
+
+/*
+ * Notify action
+ */
+
+/* Forward declarations */
+
+static int
+act_notify_check_duplicate(const struct sieve_runtime_env *renv,
+ const struct sieve_action *act,
+ const struct sieve_action *act_other);
+static void
+act_notify_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv, bool *keep);
+static int
+act_notify_commit(const struct sieve_action_exec_env *aenv,
+ void *tr_context);
+
+/* Action object */
+
+const struct sieve_action_def act_notify_old = {
+ .name = "notify",
+ .check_duplicate = act_notify_check_duplicate,
+ .print = act_notify_print,
+ .commit = act_notify_commit
+};
+
+/*
+ * Command validation context
+ */
+
+struct cmd_notify_context_data {
+ struct sieve_ast_argument *id;
+ struct sieve_ast_argument *method;
+ struct sieve_ast_argument *options;
+ struct sieve_ast_argument *message;
+};
+
+/*
+ * Tag validation
+ */
+
+static bool
+cmd_notify_validate_string_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+ struct cmd_notify_context_data *ctx_data =
+ (struct cmd_notify_context_data *)cmd->data;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+
+ /* Check syntax:
+ * :id <string>
+ * :method <string>
+ * :message <string>
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_STRING, FALSE))
+ return FALSE;
+
+ if (sieve_argument_is(tag, notify_method_tag)) {
+ ctx_data->method = *arg;
+
+ /* Removed */
+ *arg = sieve_ast_arguments_detach(*arg, 1);
+ } else if (sieve_argument_is(tag, notify_id_tag)) {
+ ctx_data->id = *arg;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ } else if (sieve_argument_is(tag, notify_message_tag)) {
+ ctx_data->message = *arg;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+ }
+ return TRUE;
+}
+
+static bool
+cmd_notify_validate_stringlist_tag(struct sieve_validator *valdtr,
+ struct sieve_ast_argument **arg,
+ struct sieve_command *cmd)
+{
+ struct sieve_ast_argument *tag = *arg;
+ struct cmd_notify_context_data *ctx_data =
+ (struct cmd_notify_context_data *)cmd->data;
+
+ /* Detach the tag itself */
+ *arg = sieve_ast_arguments_detach(*arg,1);
+
+ /* Check syntax:
+ * :options string-list
+ */
+ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
+ SAAT_STRING_LIST, FALSE))
+ return FALSE;
+
+ /* Assign context */
+ ctx_data->options = *arg;
+
+ /* Skip parameter */
+ *arg = sieve_ast_argument_next(*arg);
+
+ return TRUE;
+}
+
+/*
+ * Command registration
+ */
+
+static bool
+cmd_notify_registered(struct sieve_validator *valdtr,
+ const struct sieve_extension *ext,
+ struct sieve_command_registration *cmd_reg)
+{
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &notify_method_tag, 0);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &notify_id_tag, OPT_ID);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &notify_message_tag, OPT_MESSAGE);
+ sieve_validator_register_tag(valdtr, cmd_reg, ext,
+ &notify_options_tag, OPT_OPTIONS);
+
+ ext_notify_register_importance_tags(valdtr, cmd_reg, ext,
+ OPT_IMPORTANCE);
+
+ return TRUE;
+}
+
+/*
+ * Command validation
+ */
+
+static bool
+cmd_notify_pre_validate(struct sieve_validator *valdtr ATTR_UNUSED,
+ struct sieve_command *cmd)
+{
+ struct cmd_notify_context_data *ctx_data;
+
+ /* Create context */
+ ctx_data = p_new(sieve_command_pool(cmd),
+ struct cmd_notify_context_data, 1);
+ cmd->data = ctx_data;
+
+ return TRUE;
+}
+
+static int
+cmd_notify_address_validate(void *context, struct sieve_ast_argument *arg)
+{
+ struct sieve_validator *valdtr = (struct sieve_validator *)context;
+
+ if (sieve_argument_is_string_literal(arg)) {
+ string_t *address = sieve_ast_argument_str(arg);
+ const char *error;
+ int result;
+
+ T_BEGIN {
+ result = (sieve_address_validate_str(address, &error) ?
+ 1 : -1);
+
+ if (result <= 0) {
+ sieve_argument_validate_error(
+ valdtr, arg,
+ "specified :options address '%s' is invalid for "
+ "the mailto notify method: %s",
+ str_sanitize(str_c(address), 128),
+ error);
+ }
+ } T_END;
+
+ return result;
+ }
+
+ return 1;
+}
+
+static bool
+cmd_notify_validate(struct sieve_validator *valdtr, struct sieve_command *cmd)
+{
+ struct cmd_notify_context_data *ctx_data =
+ (struct cmd_notify_context_data *)cmd->data;
+
+ /* Check :method argument */
+ if (ctx_data->method != NULL) {
+ const char *method = sieve_ast_argument_strc(ctx_data->method);
+
+ if (strcasecmp(method, "mailto") != 0) {
+ sieve_command_validate_error(
+ valdtr, cmd,
+ "the notify command of the deprecated notify extension "
+ "only supports the 'mailto' notification method");
+ return FALSE;
+ }
+ }
+
+ /* Check :options argument */
+ if (ctx_data->options != NULL) {
+ struct sieve_ast_argument *option = ctx_data->options;
+
+ /* Parse and check options */
+ if (sieve_ast_stringlist_map(
+ &option, (void *)valdtr,
+ cmd_notify_address_validate) <= 0) {
+ return FALSE;
+ }
+ } else {
+ sieve_command_validate_warning(
+ valdtr, cmd,
+ "no :options (and hence recipients) specified for the notify command");
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code generation
+ */
+
+static bool
+cmd_notify_generate(const struct sieve_codegen_env *cgenv,
+ struct sieve_command *cmd)
+{
+ sieve_operation_emit(cgenv->sblock, cmd->ext, &notify_old_operation);
+
+ /* Generate arguments */
+ return sieve_generate_arguments(cgenv, cmd, NULL);
+}
+
+/*
+ * Code dump
+ */
+
+static bool
+cmd_notify_operation_dump(const struct sieve_dumptime_env *denv,
+ sieve_size_t *address)
+{
+ int opt_code = 0;
+
+ sieve_code_dumpf(denv, "NOTIFY");
+ sieve_code_descend(denv);
+
+ /* Dump optional operands */
+ for (;;) {
+ int opt;
+ bool opok = TRUE;
+
+ if ((opt = sieve_opr_optional_dump(denv, address,
+ &opt_code)) < 0)
+ return FALSE;
+
+ if (opt == 0)
+ break;
+
+ switch (opt_code) {
+ case OPT_IMPORTANCE:
+ opok = sieve_opr_number_dump(denv, address,
+ "importance");
+ break;
+ case OPT_ID:
+ opok = sieve_opr_string_dump(denv, address,
+ "id");
+ break;
+ case OPT_OPTIONS:
+ opok = sieve_opr_stringlist_dump(denv, address,
+ "options");
+ break;
+ case OPT_MESSAGE:
+ opok = sieve_opr_string_dump(denv, address,
+ "message");
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!opok)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Code execution
+ */
+
+
+static int
+cmd_notify_operation_execute(const struct sieve_runtime_env *renv,
+ sieve_size_t *address)
+{
+ const struct sieve_extension *this_ext = renv->oprtn->ext;
+ struct ext_notify_action *act;
+ pool_t pool;
+ int opt_code = 0;
+ sieve_number_t importance = 1;
+ struct sieve_stringlist *options = NULL;
+ string_t *message = NULL, *id = NULL;
+ int ret = 0;
+
+ /*
+ * Read operands
+ */
+
+ /* Optional operands */
+
+ for (;;) {
+ int opt;
+
+ if ((opt = sieve_opr_optional_read(renv, address,
+ &opt_code)) < 0)
+ return SIEVE_EXEC_BIN_CORRUPT;
+
+ if (opt == 0)
+ break;
+
+ switch (opt_code) {
+ case OPT_IMPORTANCE:
+ ret = sieve_opr_number_read(renv, address, "importance",
+ &importance);
+ break;
+ case OPT_ID:
+ ret = sieve_opr_string_read(renv, address, "id", &id);
+ break;
+ case OPT_MESSAGE:
+ ret = sieve_opr_string_read(renv, address, "from",
+ &message);
+ break;
+ case OPT_OPTIONS:
+ ret = sieve_opr_stringlist_read(renv, address,
+ "options", &options);
+ break;
+ default:
+ sieve_runtime_trace_error(
+ renv, "unknown optional operand");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if (ret <= 0) return ret;
+ }
+
+ /*
+ * Perform operation
+ */
+
+ /* Enforce 0 < importance < 4 (just to be sure) */
+
+ if (importance < 1)
+ importance = 1;
+ else if (importance > 3)
+ importance = 3;
+
+ /* Trace */
+
+ sieve_runtime_trace(renv, SIEVE_TRLVL_ACTIONS, "notify action");
+
+ /* Compose action */
+ if (options != NULL) {
+ string_t *raw_address;
+ string_t *out_message;
+
+ pool = sieve_result_pool(renv->result);
+ act = p_new(pool, struct ext_notify_action, 1);
+ if (id != NULL)
+ act->id = p_strdup(pool, str_c(id));
+ act->importance = importance;
+
+ /* Process message */
+
+ out_message = t_str_new(1024);
+ if ((ret = ext_notify_construct_message(
+ renv, (message == NULL ? NULL : str_c(message)),
+ out_message)) <= 0)
+ return ret;
+ act->message = p_strdup(pool, str_c(out_message));
+
+ /* Normalize and verify all :options addresses */
+
+ sieve_stringlist_reset(options);
+
+ p_array_init(&act->recipients, pool, 4);
+
+ raw_address = NULL;
+ while ((ret = sieve_stringlist_next_item(
+ options, &raw_address)) > 0) {
+ const char *error = NULL;
+ const struct smtp_address *address;
+
+ /* Add if valid address */
+ address = sieve_address_parse_str(raw_address, &error);
+ if (address != NULL) {
+ const struct ext_notify_recipient *rcpts;
+ unsigned int rcpt_count, i;
+
+ /* Prevent duplicates */
+ rcpts = array_get(&act->recipients, &rcpt_count);
+
+ for (i = 0; i < rcpt_count; i++) {
+ if (smtp_address_equals(rcpts[i].address,
+ address))
+ break;
+ }
+
+ /* Add only if unique */
+ if (i != rcpt_count) {
+ sieve_runtime_warning(
+ renv, NULL,
+ "duplicate recipient '%s' specified in the :options argument of "
+ "the deprecated notify command",
+ str_sanitize(str_c(raw_address), 128));
+
+ } else if (array_count(&act->recipients) >=
+ EXT_NOTIFY_MAX_RECIPIENTS) {
+ sieve_runtime_warning(renv, NULL,
+ "more than the maximum %u recipients are specified "
+ "for the deprecated notify command; "
+ "the rest is discarded",
+ EXT_NOTIFY_MAX_RECIPIENTS);
+ break;
+
+ } else {
+ struct ext_notify_recipient recipient;
+
+ recipient.full =
+ p_strdup(pool, str_c(raw_address));
+ recipient.address =
+ smtp_address_clone(pool, address);
+
+ array_append(&act->recipients, &recipient, 1);
+ }
+ } else {
+ sieve_runtime_error(
+ renv, NULL,
+ "specified :options address '%s' is invalid for "
+ "the deprecated notify command: %s",
+ str_sanitize(str_c(raw_address), 128), error);
+ return SIEVE_EXEC_FAILURE;
+ }
+ }
+
+ if (ret < 0) {
+ sieve_runtime_trace_error(
+ renv, "invalid options stringlist");
+ return SIEVE_EXEC_BIN_CORRUPT;
+ }
+
+ if (sieve_result_add_action(renv, this_ext, "notify",
+ &act_notify_old, NULL, (void *)act,
+ 0, FALSE) < 0)
+ return SIEVE_EXEC_FAILURE;
+ }
+
+ return SIEVE_EXEC_OK;
+}
+
+/*
+ * Action
+ */
+
+/* Runtime verification */
+
+static int
+act_notify_check_duplicate(const struct sieve_runtime_env *renv ATTR_UNUSED,
+ const struct sieve_action *act ATTR_UNUSED,
+ const struct sieve_action *act_other ATTR_UNUSED)
+{
+ struct ext_notify_action *new_nact, *old_nact;
+ const struct ext_notify_recipient *new_rcpts;
+ const struct ext_notify_recipient *old_rcpts;
+ unsigned int new_count, old_count, i, j;
+ unsigned int del_start = 0, del_len = 0;
+
+ if (act->context == NULL || act_other->context == NULL)
+ return 0;
+
+ new_nact = (struct ext_notify_action *)act->context;
+ old_nact = (struct ext_notify_action *)act_other->context;
+
+ new_rcpts = array_get(&new_nact->recipients, &new_count);
+ old_rcpts = array_get(&old_nact->recipients, &old_count);
+
+ for (i = 0; i < new_count; i++) {
+ for (j = 0; j < old_count; j++) {
+ if (smtp_address_equals(new_rcpts[i].address,
+ old_rcpts[j].address))
+ break;
+ }
+
+ if (j == old_count) {
+ /* Not duplicate */
+ if (del_len > 0) {
+ /* Perform pending deletion */
+ array_delete(&new_nact->recipients,
+ del_start, del_len);
+
+ /* Make sure the loop integrity is maintained */
+ i -= del_len;
+ new_rcpts = array_get(&new_nact->recipients,
+ &new_count);
+ }
+
+ del_len = 0;
+ } else {
+ /* Mark deletion */
+ if (del_len == 0)
+ del_start = i;
+ del_len++;
+ }
+ }
+
+ /* Perform pending deletion */
+ if (del_len > 0)
+ array_delete(&new_nact->recipients, del_start, del_len);
+
+ return (array_count(&new_nact->recipients) > 0 ? 0 : 1);
+}
+
+/* Result printing */
+
+static void
+act_notify_print(const struct sieve_action *action,
+ const struct sieve_result_print_env *rpenv,
+ bool *keep ATTR_UNUSED)
+{
+ const struct ext_notify_action *act =
+ (const struct ext_notify_action *)action->context;
+ const struct ext_notify_recipient *recipients;
+ unsigned int count, i;
+
+ sieve_result_action_printf(
+ rpenv, "send (deprecated) notification with method 'mailto':");
+
+ /* Print main method parameters */
+
+ sieve_result_printf(rpenv, " => importance : %llu\n",
+ (unsigned long long)act->importance);
+
+ if (act->message != NULL) {
+ sieve_result_printf(
+ rpenv, " => message : %s\n", act->message);
+ }
+ if (act->id != NULL) {
+ sieve_result_printf(
+ rpenv, " => id : %s \n", act->id);
+ }
+
+ /* Print mailto: recipients */
+
+ sieve_result_printf(rpenv, " => recipients :\n");
+
+ recipients = array_get(&act->recipients, &count);
+ if (count == 0) {
+ sieve_result_printf(
+ rpenv, " NONE, action has no effect\n");
+ } else {
+ for (i = 0; i < count; i++) {
+ sieve_result_printf(
+ rpenv, " + To: %s\n", recipients[i].full);
+ }
+ }
+
+ /* Finish output with an empty line */
+
+ sieve_result_printf(rpenv, "\n");
+}
+
+/* Result execution */
+
+static bool contains_8bit(const char *msg)
+{
+ const unsigned char *s = (const unsigned char *)msg;
+
+ for (; *s != '\0'; s++) {
+ if ((*s & 0x80) != 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool
+act_notify_send(const struct sieve_action_exec_env *aenv,
+ const struct ext_notify_action *act)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ const struct sieve_script_env *senv = eenv->scriptenv;
+ const struct ext_notify_recipient *recipients;
+ struct sieve_smtp_context *sctx;
+ unsigned int count, i;
+ struct ostream *output;
+ string_t *msg, *to, *all;
+ const char *outmsgid, *error;
+ int ret;
+
+ /* Get recipients */
+ recipients = array_get(&act->recipients, &count);
+ if (count == 0) {
+ sieve_result_warning(
+ aenv, "notify action specifies no recipients; "
+ "action has no effect");
+ return TRUE;
+ }
+
+ /* Just to be sure */
+ if (!sieve_smtp_available(senv)) {
+ sieve_result_global_warning(
+ aenv, "notify action has no means to send mail");
+ return TRUE;
+ }
+
+ /* Compose common headers */
+ msg = t_str_new(512);
+ rfc2822_header_write(msg, "X-Sieve", SIEVE_IMPLEMENTATION);
+ rfc2822_header_write(msg, "Date", message_date_create(ioloop_time));
+
+ /* Set importance */
+ switch (act->importance) {
+ case 1:
+ rfc2822_header_write(msg, "X-Priority", "1 (Highest)");
+ rfc2822_header_write(msg, "Importance", "High");
+ break;
+ case 3:
+ rfc2822_header_write(msg, "X-Priority", "5 (Lowest)");
+ rfc2822_header_write(msg, "Importance", "Low");
+ break;
+ case 2:
+ default:
+ rfc2822_header_write(msg, "X-Priority", "3 (Normal)");
+ rfc2822_header_write(msg, "Importance", "Normal");
+ break;
+ }
+
+ rfc2822_header_write(msg, "From", sieve_get_postmaster_address(senv));
+
+ rfc2822_header_write(msg, "Subject", "[SIEVE] New mail notification");
+
+ rfc2822_header_write(msg, "Auto-Submitted", "auto-generated (notify)");
+ rfc2822_header_write(msg, "Precedence", "bulk");
+
+ rfc2822_header_write(msg, "MIME-Version", "1.0");
+ if (contains_8bit(act->message)) {
+ rfc2822_header_write(msg, "Content-Type",
+ "text/plain; charset=utf-8");
+ rfc2822_header_write(msg, "Content-Transfer-Encoding", "8bit");
+ } else {
+ rfc2822_header_write(msg, "Content-Type",
+ "text/plain; charset=us-ascii");
+ rfc2822_header_write(msg, "Content-Transfer-Encoding", "7bit");
+ }
+
+ outmsgid = sieve_message_get_new_id(eenv->svinst);
+ rfc2822_header_write(msg, "Message-ID", outmsgid);
+
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0 &&
+ sieve_message_get_sender(aenv->msgctx) != NULL) {
+ sctx = sieve_smtp_start(senv, sieve_get_postmaster_smtp(senv));
+ } else {
+ sctx = sieve_smtp_start(senv, NULL);
+ }
+
+ /* Add all recipients (and compose To header field) */
+ to = t_str_new(128);
+ all = t_str_new(256);
+ for (i = 0; i < count; i++) {
+ sieve_smtp_add_rcpt(sctx, recipients[i].address);
+ if (i > 0)
+ str_append(to, ", ");
+ str_append(to, recipients[i].full);
+ if (i < 3) {
+ if (i > 0)
+ str_append(all, ", ");
+ str_append(all, smtp_address_encode_path(
+ recipients[i].address));
+ } else if (i == 3) {
+ str_printfa(all, ", ... (%u total)", count);
+ }
+ }
+
+ rfc2822_header_write_address(msg, "To", str_c(to));
+
+ /* Generate message body */
+ str_printfa(msg, "\r\n%s\r\n", act->message);
+
+ output = sieve_smtp_send(sctx);
+ o_stream_nsend(output, str_data(msg), str_len(msg));
+
+ if ((ret = sieve_smtp_finish(sctx, &error)) <= 0) {
+ if (ret < 0) {
+ sieve_result_global_error(
+ aenv, "failed to send mail notification to %s: "
+ "%s (temporary failure)", str_c(all),
+ str_sanitize(error, 512));
+ } else {
+ sieve_result_global_log_error(
+ aenv, "failed to send mail notification to %s: "
+ "%s (permanent failure)", str_c(all),
+ str_sanitize(error, 512));
+ }
+ } else {
+ struct event_passthrough *e =
+ sieve_action_create_finish_event(aenv)->
+ add_str("notify_target", str_c(all));
+
+ sieve_result_event_log(aenv, e->event(),
+ "sent mail notification to %s",
+ str_c(all));
+ }
+
+ return TRUE;
+}
+
+static int
+act_notify_commit(const struct sieve_action_exec_env *aenv,
+ void *tr_context ATTR_UNUSED)
+{
+ const struct sieve_execute_env *eenv = aenv->exec_env;
+ struct mail *mail = eenv->msgdata->mail;
+ const struct ext_notify_action *act =
+ (const struct ext_notify_action *)aenv->action->context;
+ const char *const *hdsp;
+ bool result;
+ int ret;
+
+ /* Is the message an automatic reply ? */
+ if ((ret = mail_get_headers(mail, "auto-submitted", &hdsp)) < 0) {
+ return sieve_result_mail_error(
+ aenv, mail,
+ "failed to read `auto-submitted' header field");
+ }
+
+ /* Theoretically multiple headers could exist, so lets make sure */
+ if (ret > 0) {
+ while (*hdsp != NULL) {
+ if (strcasecmp(*hdsp, "no") != 0) {
+ const struct smtp_address *sender = NULL;
+ const char *from;
+
+ if ((eenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0)
+ sender = sieve_message_get_sender(aenv->msgctx);
+ from = (sender == NULL ? "" :
+ t_strdup_printf(" from <%s>",
+ smtp_address_encode(sender)));
+
+ sieve_result_global_log(
+ aenv,
+ "not sending notification for auto-submitted message%s",
+ from);
+ return SIEVE_EXEC_OK;
+ }
+ hdsp++;
+ }
+ }
+
+ T_BEGIN {
+ result = act_notify_send(aenv, act);
+ } T_END;
+
+ if (!result)
+ return SIEVE_EXEC_FAILURE;
+ eenv->exec_status->significant_action_executed = TRUE;
+ return SIEVE_EXEC_OK;
+}