/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "strfuncs.h" #include "md5.h" #include "hostpid.h" #include "str-sanitize.h" #include "ostream.h" #include "message-address.h" #include "message-date.h" #include "var-expand.h" #include "ioloop.h" #include "mail-storage.h" #include "rfc2822.h" #include "sieve-common.h" #include "sieve-stringlist.h" #include "sieve-code.h" #include "sieve-address.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-message.h" #include "sieve-smtp.h" #include "ext-vacation-common.h" #include /* * Forward declarations */ static const struct sieve_argument_def vacation_days_tag; static const struct sieve_argument_def vacation_subject_tag; static const struct sieve_argument_def vacation_from_tag; static const struct sieve_argument_def vacation_addresses_tag; static const struct sieve_argument_def vacation_mime_tag; static const struct sieve_argument_def vacation_handle_tag; /* * Vacation command * * Syntax: * vacation [":days" number] [":subject" string] * [":from" string] [":addresses" string-list] * [":mime"] [":handle" string] */ static bool cmd_vacation_registered(struct sieve_validator *valdtr, const struct sieve_extension *ext, struct sieve_command_registration *cmd_reg); static bool cmd_vacation_pre_validate(struct sieve_validator *valdtr, struct sieve_command *cmd); static bool cmd_vacation_validate(struct sieve_validator *valdtr, struct sieve_command *cmd); static bool cmd_vacation_generate(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd); const struct sieve_command_def vacation_command = { .identifier = "vacation", .type = SCT_COMMAND, .positional_args = 1, .subtests = 0, .block_allowed = FALSE, .block_required = FALSE, .registered = cmd_vacation_registered, .pre_validate = cmd_vacation_pre_validate, .validate = cmd_vacation_validate, .generate = cmd_vacation_generate, }; /* * Vacation command tags */ /* Forward declarations */ static bool cmd_vacation_validate_number_tag(struct sieve_validator *valdtr, struct sieve_ast_argument **arg, struct sieve_command *cmd); static bool cmd_vacation_validate_string_tag(struct sieve_validator *valdtr, struct sieve_ast_argument **arg, struct sieve_command *cmd); static bool cmd_vacation_validate_stringlist_tag(struct sieve_validator *valdtr, struct sieve_ast_argument **arg, struct sieve_command *cmd); static bool cmd_vacation_validate_mime_tag(struct sieve_validator *valdtr, struct sieve_ast_argument **arg, struct sieve_command *cmd); /* Argument objects */ static const struct sieve_argument_def vacation_days_tag = { .identifier = "days", .validate = cmd_vacation_validate_number_tag }; static const struct sieve_argument_def vacation_seconds_tag = { .identifier = "seconds", .validate = cmd_vacation_validate_number_tag }; static const struct sieve_argument_def vacation_subject_tag = { .identifier = "subject", .validate = cmd_vacation_validate_string_tag }; static const struct sieve_argument_def vacation_from_tag = { .identifier = "from", .validate = cmd_vacation_validate_string_tag }; static const struct sieve_argument_def vacation_addresses_tag = { .identifier = "addresses", .validate = cmd_vacation_validate_stringlist_tag }; static const struct sieve_argument_def vacation_mime_tag = { .identifier = "mime", .validate = cmd_vacation_validate_mime_tag }; static const struct sieve_argument_def vacation_handle_tag = { .identifier = "handle", .validate = cmd_vacation_validate_string_tag }; /* Codes for optional arguments */ enum cmd_vacation_optional { OPT_END, OPT_SECONDS, OPT_SUBJECT, OPT_FROM, OPT_ADDRESSES, OPT_MIME }; /* * Vacation operation */ static bool ext_vacation_operation_dump(const struct sieve_dumptime_env *denv, sieve_size_t *address); static int ext_vacation_operation_execute(const struct sieve_runtime_env *renv, sieve_size_t *address); const struct sieve_operation_def vacation_operation = { .mnemonic = "VACATION", .ext_def = &vacation_extension, .dump = ext_vacation_operation_dump, .execute = ext_vacation_operation_execute }; /* * Vacation action */ /* Forward declarations */ static int act_vacation_check_duplicate(const struct sieve_runtime_env *renv, const struct sieve_action *act, const struct sieve_action *act_other); int act_vacation_check_conflict(const struct sieve_runtime_env *renv, const struct sieve_action *act, const struct sieve_action *act_other); static void act_vacation_print(const struct sieve_action *action, const struct sieve_result_print_env *rpenv, bool *keep); static int act_vacation_commit(const struct sieve_action_exec_env *aenv, void *tr_context); /* Action object */ const struct sieve_action_def act_vacation = { .name = "vacation", .flags = SIEVE_ACTFLAG_SENDS_RESPONSE, .check_duplicate = act_vacation_check_duplicate, .check_conflict = act_vacation_check_conflict, .print = act_vacation_print, .commit = act_vacation_commit }; /* Action context information */ struct act_vacation_context { const char *reason; sieve_number_t seconds; const char *subject; const char *handle; bool mime; const char *from; const struct smtp_address *from_address; const struct smtp_address *const *addresses; }; /* * Command validation context */ struct cmd_vacation_context_data { string_t *from; string_t *subject; bool mime; struct sieve_ast_argument *handle_arg; }; /* * Tag validation */ static bool cmd_vacation_validate_number_tag(struct sieve_validator *valdtr, struct sieve_ast_argument **arg, struct sieve_command *cmd) { const struct sieve_extension *ext = sieve_argument_ext(*arg); const struct ext_vacation_config *config = (const struct ext_vacation_config *)ext->context; struct sieve_ast_argument *tag = *arg; sieve_number_t period, seconds; /* Detach the tag itself */ *arg = sieve_ast_arguments_detach(*arg,1); /* Check syntax: * :days number */ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0, SAAT_NUMBER, FALSE)) return FALSE; period = sieve_ast_argument_number(*arg); if (sieve_argument_is(tag, vacation_days_tag)) seconds = period * (24*60*60); else if (sieve_argument_is(tag, vacation_seconds_tag)) seconds = period; else i_unreached(); /* Enforce :seconds >= min_period */ if (seconds < config->min_period) { seconds = config->min_period; sieve_argument_validate_warning( valdtr, *arg, "specified :%s value '%llu' is under the minimum", sieve_argument_identifier(tag), (unsigned long long)period); /* Enforce :days <= max_period */ } else if (config->max_period > 0 && seconds > config->max_period) { seconds = config->max_period; sieve_argument_validate_warning( valdtr, *arg, "specified :%s value '%llu' is over the maximum", sieve_argument_identifier(tag), (unsigned long long)period); } sieve_ast_argument_number_set(*arg, seconds); /* Skip parameter */ *arg = sieve_ast_argument_next(*arg); return TRUE; } static bool cmd_vacation_validate_string_tag(struct sieve_validator *valdtr, struct sieve_ast_argument **arg, struct sieve_command *cmd) { struct sieve_ast_argument *tag = *arg; struct cmd_vacation_context_data *ctx_data = (struct cmd_vacation_context_data *)cmd->data; /* Detach the tag itself */ *arg = sieve_ast_arguments_detach(*arg,1); /* Check syntax: * :subject string * :from string * :handle string */ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING, FALSE)) return FALSE; if (sieve_argument_is(tag, vacation_from_tag)) { if (sieve_argument_is_string_literal(*arg)) { string_t *address = sieve_ast_argument_str(*arg); const char *error; bool result; T_BEGIN { result = sieve_address_validate_str(address, &error); if (!result) { sieve_argument_validate_error( valdtr, *arg, "specified :from address '%s' is invalid for vacation action: %s", str_sanitize(str_c(address), 128), error); } } T_END; if (!result) return FALSE; } ctx_data->from = sieve_ast_argument_str(*arg); /* Skip parameter */ *arg = sieve_ast_argument_next(*arg); } else if (sieve_argument_is(tag, vacation_subject_tag)) { ctx_data->subject = sieve_ast_argument_str(*arg); /* Skip parameter */ *arg = sieve_ast_argument_next(*arg); } else if (sieve_argument_is(tag, vacation_handle_tag)) { ctx_data->handle_arg = *arg; /* Detach optional argument (emitted as mandatory) */ *arg = sieve_ast_arguments_detach(*arg, 1); } return TRUE; } static bool cmd_vacation_validate_stringlist_tag(struct sieve_validator *valdtr, struct sieve_ast_argument **arg, struct sieve_command *cmd) { struct sieve_ast_argument *tag = *arg; /* Detach the tag itself */ *arg = sieve_ast_arguments_detach(*arg,1); /* Check syntax: * :addresses string-list */ if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0, SAAT_STRING_LIST, FALSE)) return FALSE; /* Skip parameter */ *arg = sieve_ast_argument_next(*arg); return TRUE; } static bool cmd_vacation_validate_mime_tag(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_ast_argument **arg, struct sieve_command *cmd) { struct cmd_vacation_context_data *ctx_data = (struct cmd_vacation_context_data *)cmd->data; ctx_data->mime = TRUE; /* Skip tag */ *arg = sieve_ast_argument_next(*arg); return TRUE; } /* * Command registration */ static bool cmd_vacation_registered(struct sieve_validator *valdtr, const struct sieve_extension *ext, struct sieve_command_registration *cmd_reg) { sieve_validator_register_tag(valdtr, cmd_reg, ext, &vacation_days_tag, OPT_SECONDS); sieve_validator_register_tag(valdtr, cmd_reg, ext, &vacation_subject_tag, OPT_SUBJECT); sieve_validator_register_tag(valdtr, cmd_reg, ext, &vacation_from_tag, OPT_FROM); sieve_validator_register_tag(valdtr, cmd_reg, ext, &vacation_addresses_tag, OPT_ADDRESSES); sieve_validator_register_tag(valdtr, cmd_reg, ext, &vacation_mime_tag, OPT_MIME); sieve_validator_register_tag(valdtr, cmd_reg, ext, &vacation_handle_tag, 0); return TRUE; } bool ext_vacation_register_seconds_tag( struct sieve_validator *valdtr, const struct sieve_extension *vacation_ext) { sieve_validator_register_external_tag( valdtr, vacation_command.identifier, vacation_ext, &vacation_seconds_tag, OPT_SECONDS); return TRUE; } /* * Command validation */ static bool cmd_vacation_pre_validate(struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd) { struct cmd_vacation_context_data *ctx_data; /* Assign context */ ctx_data = p_new(sieve_command_pool(cmd), struct cmd_vacation_context_data, 1); cmd->data = ctx_data; return TRUE; } static const char _handle_empty_subject[] = ""; static const char _handle_empty_from[] = ""; static const char _handle_mime_enabled[] = ""; static const char _handle_mime_disabled[] = ""; static bool cmd_vacation_validate(struct sieve_validator *valdtr, struct sieve_command *cmd) { struct sieve_ast_argument *arg = cmd->first_positional; struct cmd_vacation_context_data *ctx_data = (struct cmd_vacation_context_data *)cmd->data; if (!sieve_validate_positional_argument(valdtr, cmd, arg, "reason", 1, SAAT_STRING)) return FALSE; if (!sieve_validator_argument_activate(valdtr, cmd, arg, FALSE)) return FALSE; /* Construct handle if not set explicitly */ if (ctx_data->handle_arg == NULL) { T_BEGIN { string_t *handle; string_t *reason = sieve_ast_argument_str(arg); unsigned int size = str_len(reason); /* Precalculate the size of it all */ size += (ctx_data->subject == NULL ? sizeof(_handle_empty_subject) - 1 : str_len(ctx_data->subject)); size += (ctx_data->from == NULL ? sizeof(_handle_empty_from) - 1 : str_len(ctx_data->from)); size += (ctx_data->mime ? sizeof(_handle_mime_enabled) - 1 : sizeof(_handle_mime_disabled) - 1); /* Construct the string */ handle = t_str_new(size); str_append_str(handle, reason); if (ctx_data->subject != NULL) str_append_str(handle, ctx_data->subject); else str_append(handle, _handle_empty_subject); if (ctx_data->from != NULL) str_append_str(handle, ctx_data->from); else str_append(handle, _handle_empty_from); str_append(handle, (ctx_data->mime ? _handle_mime_enabled : _handle_mime_disabled)); /* Create positional handle argument */ ctx_data->handle_arg = sieve_ast_argument_string_create( cmd->ast_node, handle, sieve_ast_node_line(cmd->ast_node)); } T_END; if (!sieve_validator_argument_activate( valdtr, cmd, ctx_data->handle_arg, TRUE)) return FALSE; } else { /* Attach explicit handle argument as positional */ (void)sieve_ast_argument_attach(cmd->ast_node, ctx_data->handle_arg); } return TRUE; } /* * Code generation */ static bool cmd_vacation_generate(const struct sieve_codegen_env *cgenv, struct sieve_command *cmd) { sieve_operation_emit(cgenv->sblock, cmd->ext, &vacation_operation); /* Generate arguments */ if (!sieve_generate_arguments(cgenv, cmd, NULL)) return FALSE; return TRUE; } /* * Code dump */ static bool ext_vacation_operation_dump(const struct sieve_dumptime_env *denv, sieve_size_t *address) { int opt_code = 0; sieve_code_dumpf(denv, "VACATION"); 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_SECONDS: opok = sieve_opr_number_dump(denv, address, "seconds"); break; case OPT_SUBJECT: opok = sieve_opr_string_dump(denv, address, "subject"); break; case OPT_FROM: opok = sieve_opr_string_dump(denv, address, "from"); break; case OPT_ADDRESSES: opok = sieve_opr_stringlist_dump(denv, address, "addresses"); break; case OPT_MIME: sieve_code_dumpf(denv, "mime"); break; default: return FALSE; } if (!opok) return FALSE; } /* Dump reason and handle operands */ return (sieve_opr_string_dump(denv, address, "reason") && sieve_opr_string_dump(denv, address, "handle")); } /* * Code execution */ static int ext_vacation_operation_execute(const struct sieve_runtime_env *renv, sieve_size_t *address) { const struct sieve_extension *this_ext = renv->oprtn->ext; const struct ext_vacation_config *config = (const struct ext_vacation_config *)this_ext->context; struct sieve_side_effects_list *slist = NULL; struct act_vacation_context *act; pool_t pool; int opt_code = 0; sieve_number_t seconds = config->default_period; bool mime = FALSE; struct sieve_stringlist *addresses = NULL; string_t *reason, *subject = NULL, *from = NULL, *handle = NULL; const struct smtp_address *from_address = NULL; int ret; /* * Read code */ /* 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_SECONDS: ret = sieve_opr_number_read(renv, address, "seconds", &seconds); break; case OPT_SUBJECT: ret = sieve_opr_string_read(renv, address, "subject", &subject); break; case OPT_FROM: ret = sieve_opr_string_read(renv, address, "from", &from); break; case OPT_ADDRESSES: ret = sieve_opr_stringlist_read(renv, address, "addresses", &addresses); break; case OPT_MIME: mime = TRUE; ret = SIEVE_EXEC_OK; break; default: sieve_runtime_trace_error( renv, "unknown optional operand"); ret = SIEVE_EXEC_BIN_CORRUPT; } if (ret <= 0) return ret; } /* Fixed operands */ if ((ret =sieve_opr_string_read(renv, address, "reason", &reason)) <= 0 || (ret = sieve_opr_string_read(renv, address, "handle", &handle)) <= 0) return ret; /* * Perform operation */ /* Trace */ if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS)) { sieve_runtime_trace(renv, 0, "vacation action"); sieve_runtime_trace_descend(renv); sieve_runtime_trace(renv, 0, "auto-reply with message `%s'", str_sanitize(str_c(reason), 80)); } /* Parse :from address */ if (from != NULL) { const char *error; from_address = sieve_address_parse_str(from, &error); if (from_address == NULL) { sieve_runtime_error( renv, NULL, "specified :from address '%s' is invalid for vacation action: %s", str_sanitize(str_c(from), 128), error); } } /* Add vacation action to the result */ pool = sieve_result_pool(renv->result); act = p_new(pool, struct act_vacation_context, 1); act->reason = p_strdup(pool, str_c(reason)); act->handle = p_strdup(pool, str_c(handle)); act->seconds = seconds; act->mime = mime; if (subject != NULL) act->subject = p_strdup(pool, str_c(subject)); if (from != NULL) { act->from = p_strdup(pool, str_c(from)); act->from_address = smtp_address_clone(pool, from_address); } /* Normalize all addresses */ if (addresses != NULL) { ARRAY_TYPE(smtp_address_const) addrs; string_t *raw_address; int ret; sieve_stringlist_reset(addresses); p_array_init(&addrs, pool, 4); raw_address = NULL; while ((ret = sieve_stringlist_next_item(addresses, &raw_address)) > 0) { const struct smtp_address *addr; const char *error; addr = sieve_address_parse_str(raw_address, &error); if (addr != NULL) { addr = smtp_address_clone(pool, addr); array_append(&addrs, &addr, 1); } else { sieve_runtime_error( renv, NULL, "specified :addresses item '%s' is invalid: " "%s for vacation action (ignored)", str_sanitize(str_c(raw_address),128), error); } } if (ret < 0) { sieve_runtime_trace_error( renv, "invalid addresses stringlist"); return SIEVE_EXEC_BIN_CORRUPT; } (void)array_append_space(&addrs); act->addresses = array_idx(&addrs, 0); } if (sieve_result_add_action(renv, this_ext, "vacation", &act_vacation, slist, (void *)act, 0, FALSE) < 0) return SIEVE_EXEC_FAILURE; return SIEVE_EXEC_OK; } /* * Action */ /* Runtime verification */ static int act_vacation_check_duplicate(const struct sieve_runtime_env *renv ATTR_UNUSED, const struct sieve_action *act, const struct sieve_action *act_other) { if (!sieve_action_is_executed(act_other, renv->result)) { sieve_runtime_error( renv, act->location, "duplicate vacation action not allowed " "(previously triggered one was here: %s)", act_other->location); return -1; } /* Not an error if executed in preceeding script */ return 1; } int act_vacation_check_conflict(const struct sieve_runtime_env *renv, const struct sieve_action *act, const struct sieve_action *act_other) { if ((act_other->def->flags & SIEVE_ACTFLAG_SENDS_RESPONSE) > 0) { if (!sieve_action_is_executed(act_other, renv->result)) { sieve_runtime_error( renv, act->location, "vacation action conflicts with other action: " "the %s action (%s) also sends a response back to the sender", act_other->def->name, act_other->location); return -1; } else { /* Not an error if executed in preceeding script */ return 1; } } return 0; } /* Result printing */ static void act_vacation_print(const struct sieve_action *action ATTR_UNUSED, const struct sieve_result_print_env *rpenv, bool *keep ATTR_UNUSED) { struct act_vacation_context *ctx = (struct act_vacation_context *)action->context; sieve_result_action_printf(rpenv, "send vacation message:"); sieve_result_printf(rpenv, " => seconds : %llu\n", (unsigned long long)ctx->seconds); if (ctx->subject != NULL) { sieve_result_printf(rpenv, " => subject : %s\n", ctx->subject); } if (ctx->from != NULL) { sieve_result_printf(rpenv, " => from : %s\n", ctx->from); } if (ctx->handle != NULL) { sieve_result_printf(rpenv, " => handle : %s\n", ctx->handle); } sieve_result_printf(rpenv, "\nSTART MESSAGE\n%s\nEND MESSAGE\n", ctx->reason); } /* Result execution */ /* Headers known to be associated with mailing lists */ static const char * const _list_headers[] = { "list-id", "list-owner", "list-subscribe", "list-post", "list-unsubscribe", "list-help", "list-archive", NULL }; /* Headers that should be searched for the user's own mail address(es) */ static const char * const _my_address_headers[] = { "to", "cc", "bcc", "resent-to", "resent-cc", "resent-bcc", NULL }; /* Headers that should be searched for the full sender address */ static const char * const _sender_headers[] = { "sender", "resent-from", "from", NULL }; static inline bool _is_system_address(const struct smtp_address *address) { if (strcasecmp(address->localpart, "MAILER-DAEMON") == 0) return TRUE; if (strcasecmp(address->localpart, "LISTSERV") == 0) return TRUE; if (strcasecmp(address->localpart, "majordomo") == 0) return TRUE; if (strstr(address->localpart, "-request") != NULL) return TRUE; if (str_begins(address->localpart, "owner-")) return TRUE; return FALSE; } static bool _msg_address_equals(const struct message_address *addr1, const struct smtp_address *addr2) { struct smtp_address saddr; i_assert(addr1->mailbox != NULL); return (smtp_address_init_from_msg(&saddr, addr1) >= 0 && smtp_address_equals_icase(addr2, &saddr)); } static inline bool _header_contains_my_address(const char *header_val, const struct smtp_address *my_address) { const struct message_address *msg_addr; msg_addr = message_address_parse(pool_datastack_create(), (const unsigned char *)header_val, strlen(header_val), 256, 0); while (msg_addr != NULL) { if (msg_addr->domain != NULL) { if (_msg_address_equals(msg_addr, my_address)) return TRUE; } msg_addr = msg_addr->next; } return FALSE; } static inline bool _contains_my_address(const char * const *headers, const struct smtp_address *my_address) { const char *const *hdsp = headers; while (*hdsp != NULL) { bool result; T_BEGIN { result = _header_contains_my_address(*hdsp, my_address); } T_END; if (result) return TRUE; hdsp++; } return FALSE; } static bool _contains_8bit(const char *text) { const unsigned char *p = (const unsigned char *)text; for (; *p != '\0'; p++) { if ((*p & 0x80) != 0) return TRUE; } return FALSE; } static bool _header_get_full_reply_recipient(const struct ext_vacation_config *config, const struct smtp_address *smtp_to, const char *header, struct message_address *reply_to_r) { const struct message_address *addr; addr = message_address_parse( pool_datastack_create(), (const unsigned char *)header, strlen(header), 256, 0); for (; addr != NULL; addr = addr->next) { bool matched = config->to_header_ignore_envelope; if (addr->domain == NULL || addr->invalid_syntax) continue; if (!matched) matched = _msg_address_equals(addr, smtp_to); if (matched) { *reply_to_r = *addr; return TRUE; } } return FALSE; } static int _get_full_reply_recipient(const struct sieve_action_exec_env *aenv, const struct ext_vacation_config *config, const struct smtp_address *smtp_to, struct message_address *reply_to_r) { const struct sieve_execute_env *eenv = aenv->exec_env; const struct sieve_message_data *msgdata = eenv->msgdata; const char *const *hdsp; int ret; hdsp = _sender_headers; for (; *hdsp != NULL; hdsp++) { const char *header; if ((ret = mail_get_first_header(msgdata->mail, *hdsp, &header)) < 0) { return sieve_result_mail_error( aenv, msgdata->mail, "failed to read header field `%s'", *hdsp); } if (ret == 0 || header == NULL) continue; if (_header_get_full_reply_recipient(config, smtp_to, header, reply_to_r)) return SIEVE_EXEC_OK; } reply_to_r->mailbox = smtp_to->localpart; reply_to_r->domain = smtp_to->domain; return SIEVE_EXEC_OK; } static const struct var_expand_table * _get_var_expand_table(const struct sieve_action_exec_env *aenv ATTR_UNUSED, const char *subject) { const struct var_expand_table stack_tab[] = { { '$', subject, "subject" }, { '\0', NULL, NULL } }; return p_memdup(unsafe_data_stack_pool, stack_tab, sizeof(stack_tab)); } static int act_vacation_get_default_subject(const struct sieve_action_exec_env *aenv, const struct ext_vacation_config *config, const char **subject_r) { const struct sieve_execute_env *eenv = aenv->exec_env; const struct sieve_message_data *msgdata = eenv->msgdata; const char *header, *error; string_t *str; const struct var_expand_table *tab; int ret; *subject_r = (config->default_subject == NULL ? "Automated reply" : config->default_subject); if ((ret = mail_get_first_header_utf8(msgdata->mail, "subject", &header)) < 0) { return sieve_result_mail_error( aenv, msgdata->mail, "failed to read header field `subject'"); } if (ret == 0) return SIEVE_EXEC_OK; if (config->default_subject_template == NULL) { *subject_r = t_strconcat("Auto: ", header, NULL); return SIEVE_EXEC_OK; } str = t_str_new(256); tab = _get_var_expand_table(aenv, header); if (var_expand(str, config->default_subject_template, tab, &error) <= 0) { i_error("Failed to expand deliver_log_format=%s: %s", config->default_subject_template, error); *subject_r = t_strconcat("Auto: ", header, NULL); return SIEVE_EXEC_OK; } *subject_r = str_c(str); return SIEVE_EXEC_OK; } static int act_vacation_send(const struct sieve_action_exec_env *aenv, const struct ext_vacation_config *config, struct act_vacation_context *ctx, const struct smtp_address *smtp_to, const struct smtp_address *smtp_from, const struct message_address *reply_from) { const struct sieve_execute_env *eenv = aenv->exec_env; const struct sieve_message_data *msgdata = eenv->msgdata; const struct sieve_script_env *senv = eenv->scriptenv; struct sieve_smtp_context *sctx; struct ostream *output; string_t *msg; struct message_address reply_to; const char *header, *outmsgid, *subject, *error; int ret; /* Check smpt functions just to be sure */ if (!sieve_smtp_available(senv)) { sieve_result_global_warning( aenv, "vacation action has no means to send mail"); return SIEVE_EXEC_OK; } /* Make sure we have a subject for our reply */ if (ctx->subject == NULL || *(ctx->subject) == '\0') { if ((ret = act_vacation_get_default_subject(aenv, config, &subject)) <= 0) return ret; } else { subject = ctx->subject; } subject = str_sanitize_utf8(subject, config->max_subject_codepoints); /* Obtain full To address for reply */ i_zero(&reply_to); reply_to.mailbox = smtp_to->localpart; reply_to.domain = smtp_to->domain; if ((ret = _get_full_reply_recipient(aenv, config, smtp_to, &reply_to)) <= 0) return ret; /* Open smtp session */ sctx = sieve_smtp_start_single(senv, smtp_to, smtp_from, &output); outmsgid = sieve_message_get_new_id(eenv->svinst); /* Produce a proper reply */ msg = t_str_new(512); rfc2822_header_write(msg, "X-Sieve", SIEVE_IMPLEMENTATION); rfc2822_header_write(msg, "Message-ID", outmsgid); rfc2822_header_write(msg, "Date", message_date_create(ioloop_time)); if (ctx->from != NULL && *(ctx->from) != '\0') { rfc2822_header_write_address(msg, "From", ctx->from); } else { if (reply_from == NULL || reply_from->mailbox == NULL || *reply_from->mailbox == '\0') reply_from = sieve_get_postmaster(senv); rfc2822_header_write( msg, "From", message_address_first_to_string(reply_from)); } rfc2822_header_write(msg, "To", message_address_first_to_string(&reply_to)); if (_contains_8bit(subject)) rfc2822_header_utf8_printf(msg, "Subject", "%s", subject); else rfc2822_header_printf(msg, "Subject", "%s", subject); /* Compose proper in-reply-to and references headers */ if ((ret = mail_get_first_header(msgdata->mail, "references", &header)) < 0) { sieve_smtp_abort(sctx); return sieve_result_mail_error( aenv, msgdata->mail, "failed to read header field `references'"); } if (msgdata->id != NULL) { rfc2822_header_write(msg, "In-Reply-To", msgdata->id); if (ret > 0 && header != NULL) { rfc2822_header_write( msg, "References", t_strconcat(header, " ", msgdata->id, NULL)); } else { rfc2822_header_write(msg, "References", msgdata->id); } } else if (ret > 0 && header != NULL) { rfc2822_header_write(msg, "References", header); } rfc2822_header_write(msg, "Auto-Submitted", "auto-replied (vacation)"); rfc2822_header_write(msg, "Precedence", "bulk"); /* Prevent older Microsoft products from replying to this message */ rfc2822_header_write(msg, "X-Auto-Response-Suppress", "All"); rfc2822_header_write(msg, "MIME-Version", "1.0"); if (!ctx->mime) { rfc2822_header_write(msg, "Content-Type", "text/plain; charset=utf-8"); rfc2822_header_write(msg, "Content-Transfer-Encoding", "8bit"); str_append(msg, "\r\n"); } str_printfa(msg, "%s\r\n", ctx->reason); o_stream_nsend(output, str_data(msg), str_len(msg)); /* Close smtp session */ if ((ret = sieve_smtp_finish(sctx, &error)) <= 0) { if (ret < 0) { sieve_result_global_error( aenv, "failed to send vacation response to %s: " "<%s> (temporary error)", smtp_address_encode(smtp_to), str_sanitize(error, 512)); } else { sieve_result_global_log_error( aenv, "failed to send vacation response to %s: " "<%s> (permanent error)", smtp_address_encode(smtp_to), str_sanitize(error, 512)); } /* This error will be ignored in the end */ return SIEVE_EXEC_FAILURE; } eenv->exec_status->significant_action_executed = TRUE; return SIEVE_EXEC_OK; } static void act_vacation_hash(struct act_vacation_context *vctx, const char *sender, unsigned char hash_r[]) { const char *rpath = t_str_lcase(sender); struct md5_context ctx; md5_init(&ctx); md5_update(&ctx, rpath, strlen(rpath)); md5_update(&ctx, vctx->handle, strlen(vctx->handle)); md5_final(&ctx, hash_r); } static int act_vacation_commit(const struct sieve_action_exec_env *aenv, void *tr_context ATTR_UNUSED) { const struct sieve_action *action = aenv->action; const struct sieve_extension *ext = action->ext; const struct sieve_execute_env *eenv = aenv->exec_env; struct sieve_instance *svinst = eenv->svinst; const struct ext_vacation_config *config = (const struct ext_vacation_config *)ext->context; struct act_vacation_context *ctx = (struct act_vacation_context *)action->context; unsigned char dupl_hash[MD5_RESULTLEN]; struct mail *mail = sieve_message_get_mail(aenv->msgctx); const struct smtp_address *sender, *recipient; const struct smtp_address *orig_recipient, *user_email; const struct smtp_address *smtp_from; struct message_address reply_from; const char *const *hdsp, *const *headers; int ret; if ((eenv->flags & SIEVE_EXECUTE_FLAG_SKIP_RESPONSES) != 0) { sieve_result_global_log( aenv, "not sending vacation reply (skipped)"); return SIEVE_EXEC_OK; } sender = sieve_message_get_sender(aenv->msgctx); recipient = sieve_message_get_final_recipient(aenv->msgctx); i_zero(&reply_from); smtp_from = orig_recipient = user_email = NULL; /* Is the recipient unset? */ if (smtp_address_isnull(recipient)) { sieve_result_global_warning( aenv, "vacation action aborted: " "envelope recipient is <>"); return SIEVE_EXEC_OK; } /* Is the return path unset ? */ if (smtp_address_isnull(sender)) { sieve_result_global_log(aenv, "discarded vacation reply to <>"); return SIEVE_EXEC_OK; } /* Are we perhaps trying to respond to ourselves ? */ if (smtp_address_equals_icase(sender, recipient)) { sieve_result_global_log( aenv, "discarded vacation reply to own address <%s>", smtp_address_encode(sender)); return SIEVE_EXEC_OK; } /* Are we perhaps trying to respond to one of our alternative :addresses? */ if (ctx->addresses != NULL) { const struct smtp_address * const *alt_address; alt_address = ctx->addresses; while (*alt_address != NULL) { if (smtp_address_equals_icase(sender, *alt_address)) { sieve_result_global_log( aenv, "discarded vacation reply to own address <%s> " "(as specified using :addresses argument)", smtp_address_encode(sender)); return SIEVE_EXEC_OK; } alt_address++; } } /* Did whe respond to this user before? */ if (sieve_action_duplicate_check_available(aenv)) { bool duplicate; act_vacation_hash(ctx, smtp_address_encode(sender), dupl_hash); ret = sieve_action_duplicate_check(aenv, dupl_hash, sizeof(dupl_hash), &duplicate); if (ret < SIEVE_EXEC_OK) { sieve_result_critical( aenv, "failed to check for duplicate vacation response", "failed to check for duplicate vacation response%s", (ret == SIEVE_EXEC_TEMP_FAILURE ? " (temporaty failure)" : "")); return ret; } if (duplicate) { sieve_result_global_log( aenv, "discarded duplicate vacation response to <%s>", smtp_address_encode(sender)); return SIEVE_EXEC_OK; } } /* Are we trying to respond to a mailing list ? */ hdsp = _list_headers; while (*hdsp != NULL) { if ((ret = mail_get_headers(mail, *hdsp, &headers)) < 0) { return sieve_result_mail_error( aenv, mail, "failed to read header field `%s'", *hdsp); } if (ret > 0 && headers[0] != NULL) { /* Yes, bail out */ sieve_result_global_log( aenv, "discarding vacation response " "to mailinglist recipient <%s>", smtp_address_encode(sender)); return SIEVE_EXEC_OK; } hdsp++; } /* Is the message that we are replying to an automatic reply ? */ if ((ret = mail_get_headers(mail, "auto-submitted", &headers)) < 0) { return sieve_result_mail_error( aenv, mail, "failed to read header field `auto-submitted'"); } /* Theoretically multiple headers could exist, so lets make sure */ if (ret > 0) { hdsp = headers; while (*hdsp != NULL) { if (strcasecmp(*hdsp, "no") != 0) { sieve_result_global_log( aenv, "discarding vacation response " "to auto-submitted message from <%s>", smtp_address_encode(sender)); return SIEVE_EXEC_OK; } hdsp++; } } /* Check for the (non-standard) precedence header */ if ((ret = mail_get_headers(mail, "precedence", &headers)) < 0) { return sieve_result_mail_error( aenv, mail, "failed to read header field `precedence'"); } /* Theoretically multiple headers could exist, so lets make sure */ if (ret > 0) { hdsp = headers; while (*hdsp != NULL) { if (strcasecmp(*hdsp, "junk") == 0 || strcasecmp(*hdsp, "bulk") == 0 || strcasecmp(*hdsp, "list") == 0) { sieve_result_global_log( aenv, "discarding vacation response " "to precedence=%s message from <%s>", *hdsp, smtp_address_encode(sender)); return SIEVE_EXEC_OK; } hdsp++; } } /* Check for the (non-standard) Microsoft X-Auto-Response-Suppress header */ if ((ret = mail_get_headers(mail, "x-auto-response-suppress", &headers)) < 0) { return sieve_result_mail_error( aenv, mail, "failed to read header field `x-auto-response-suppress'"); } /* Theoretically multiple headers could exist, so lets make sure */ if (ret > 0) { hdsp = headers; while (*hdsp != NULL) { const char *const *flags = t_strsplit(*hdsp, ","); while (*flags != NULL) { const char *flag = t_str_trim(*flags, " \t"); if (strcasecmp(flag, "All") == 0 || strcasecmp(flag, "OOF") == 0) { sieve_result_global_log( aenv, "discarding vacation response to message from <%s> " "(`%s' flag found in x-auto-response-suppress header)", smtp_address_encode(sender), flag); return SIEVE_EXEC_OK; } flags++; } hdsp++; } } /* Do not reply to system addresses */ if (_is_system_address(sender)) { sieve_result_global_log( aenv, "not sending vacation response to system address <%s>", smtp_address_encode(sender)); return SIEVE_EXEC_OK; } /* Fetch original recipient if necessary */ if (config->use_original_recipient) orig_recipient = sieve_message_get_orig_recipient(aenv->msgctx); /* Fetch explicitly configured user email address */ if (svinst->user_email != NULL) user_email = svinst->user_email; /* Is the original message directly addressed to the user or the addresses * specified using the :addresses tag? */ hdsp = _my_address_headers; while (*hdsp != NULL) { if ((ret = mail_get_headers(mail, *hdsp, &headers)) < 0) { return sieve_result_mail_error( aenv, mail, "failed to read header field `%s'", *hdsp); } if (ret > 0 && headers[0] != NULL) { /* Final recipient directly listed in headers? */ if (_contains_my_address(headers, recipient)) { smtp_from = recipient; message_address_init_from_smtp( &reply_from, NULL, recipient); break; } /* Original recipient directly listed in headers? */ if (!smtp_address_isnull(orig_recipient) && _contains_my_address(headers, orig_recipient)) { smtp_from = orig_recipient; message_address_init_from_smtp( &reply_from, NULL, orig_recipient); break; } /* User-provided :addresses listed in headers? */ if (ctx->addresses != NULL) { bool found = FALSE; const struct smtp_address * const *my_address; my_address = ctx->addresses; while (!found && *my_address != NULL) { if ((found = _contains_my_address(headers, *my_address))) { /* Avoid letting user determine SMTP sender directly */ smtp_from = (orig_recipient == NULL ? recipient : orig_recipient); message_address_init_from_smtp( &reply_from, NULL, *my_address); } my_address++; } if (found) break; } /* Explicitly-configured user email address directly listed in headers? */ if (user_email != NULL && _contains_my_address(headers, user_email)) { smtp_from = user_email; message_address_init_from_smtp( &reply_from, NULL, smtp_from); break; } } hdsp++; } /* My address not found in the headers; we got an implicit delivery */ if (*hdsp == NULL) { if (config->dont_check_recipient) { /* Send reply from envelope recipient address */ smtp_from = (orig_recipient == NULL ? recipient : orig_recipient); if (user_email == NULL) user_email = sieve_get_user_email(svinst); message_address_init_from_smtp(&reply_from, NULL, user_email); } else { const char *orig_rcpt_str = "", *user_email_str = ""; /* Bail out */ if (config->use_original_recipient) { orig_rcpt_str = t_strdup_printf("original-recipient=<%s>, ", (orig_recipient == NULL ? "UNAVAILABLE" : smtp_address_encode(orig_recipient))); } if (user_email != NULL) { user_email_str = t_strdup_printf( "user-email=<%s>, ", smtp_address_encode(user_email)); } sieve_result_global_log( aenv, "discarding vacation response for implicitly delivered message; " "no known (envelope) recipient address found in message headers " "(recipient=<%s>, %s%sand%s additional `:addresses' are specified)", smtp_address_encode(recipient), orig_rcpt_str, user_email_str, (ctx->addresses == NULL || *ctx->addresses == NULL ? " no" : "")); return SIEVE_EXEC_OK; } } /* Send the message */ T_BEGIN { ret = act_vacation_send( aenv, config, ctx, sender, (config->send_from_recipient ? smtp_from : NULL), &reply_from); } T_END; if (ret == SIEVE_EXEC_OK) { sieve_number_t seconds; eenv->exec_status->significant_action_executed = TRUE; struct event_passthrough *e = sieve_action_create_finish_event(aenv); sieve_result_event_log(aenv, e->event(), "sent vacation response to <%s>", smtp_address_encode(sender)); /* Check period limits once more */ seconds = ctx->seconds; if (seconds < config->min_period) seconds = config->min_period; else if (config->max_period > 0 && seconds > config->max_period) seconds = config->max_period; /* Mark as replied */ if (seconds > 0) { sieve_action_duplicate_mark(aenv, dupl_hash, sizeof(dupl_hash), ioloop_time + seconds); } } if (ret == SIEVE_EXEC_TEMP_FAILURE) return SIEVE_EXEC_TEMP_FAILURE; /* Ignore all other errors */ return SIEVE_EXEC_OK; }