/* Copyright (c) 2002-2010 Dovecot Sieve authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "strfuncs.h" #include "str-sanitize.h" #include "unichar.h" #include "mail-deliver.h" #include "mail-storage.h" #include "sieve-code.h" #include "sieve-extensions.h" #include "sieve-binary.h" #include "sieve-interpreter.h" #include "sieve-dump.h" #include "sieve-result.h" #include "sieve-actions.h" #include <ctype.h> /* * Side-effect operand */ const struct sieve_operand_class sieve_side_effect_operand_class = { "SIDE-EFFECT" }; bool sieve_opr_side_effect_read (const struct sieve_runtime_env *renv, sieve_size_t *address, struct sieve_side_effect *seffect) { const struct sieve_side_effect_def *sdef; seffect->context = NULL; if ( !sieve_opr_object_read (renv, &sieve_side_effect_operand_class, address, &seffect->object) ) return FALSE; sdef = seffect->def = (const struct sieve_side_effect_def *) seffect->object.def; if ( sdef->read_context != NULL && !sdef->read_context(seffect, renv, address, &seffect->context) ) { return FALSE; } return TRUE; } bool sieve_opr_side_effect_dump (const struct sieve_dumptime_env *denv, sieve_size_t *address) { struct sieve_side_effect seffect; const struct sieve_side_effect_def *sdef; if ( !sieve_opr_object_dump (denv, &sieve_side_effect_operand_class, address, &seffect.object) ) return FALSE; sdef = seffect.def = (const struct sieve_side_effect_def *) seffect.object.def; if ( sdef->dump_context != NULL ) { sieve_code_descend(denv); if ( !sdef->dump_context(&seffect, denv, address) ) { return FALSE; } sieve_code_ascend(denv); } return TRUE; } /* * Optional operands */ int sieve_action_opr_optional_dump (const struct sieve_dumptime_env *denv, sieve_size_t *address, signed int *opt_code) { signed int _opt_code = 0; bool final = FALSE, opok = TRUE; if ( opt_code == NULL ) { opt_code = &_opt_code; final = TRUE; } while ( opok ) { int opt; if ( (opt=sieve_opr_optional_dump(denv, address, opt_code)) <= 0 ) return opt; if ( *opt_code == SIEVE_OPT_SIDE_EFFECT ) { opok = sieve_opr_side_effect_dump(denv, address); } else { return ( final ? -1 : 1 ); } } return -1; } int sieve_action_opr_optional_read (const struct sieve_runtime_env *renv, sieve_size_t *address, signed int *opt_code, int *exec_status, struct sieve_side_effects_list **list) { signed int _opt_code = 0; bool final = FALSE; int ret; if ( opt_code == NULL ) { opt_code = &_opt_code; final = TRUE; } if ( exec_status != NULL ) *exec_status = SIEVE_EXEC_OK; for ( ;; ) { int opt; if ( (opt=sieve_opr_optional_read(renv, address, opt_code)) <= 0 ) { if ( opt < 0 && exec_status != NULL ) *exec_status = SIEVE_EXEC_BIN_CORRUPT; return opt; } if ( *opt_code == SIEVE_OPT_SIDE_EFFECT ) { struct sieve_side_effect seffect; i_assert( list != NULL ); if ( (ret=sieve_opr_side_effect_read(renv, address, &seffect)) <= 0 ) { if ( exec_status != NULL ) *exec_status = ret; return -1; } if ( *list == NULL ) *list = sieve_side_effects_list_create(renv->result); sieve_side_effects_list_add(*list, &seffect); } else { if ( final ) { sieve_runtime_trace_error(renv, "invalid optional operand"); if ( exec_status != NULL ) *exec_status = SIEVE_EXEC_BIN_CORRUPT; return -1; } return 1; } } i_unreached(); return -1; } /* * Store action */ /* Forward declarations */ static bool act_store_equals (const struct sieve_script_env *senv, const struct sieve_action *act1, const struct sieve_action *act2); static int act_store_check_duplicate (const struct sieve_runtime_env *renv, const struct sieve_action *act, const struct sieve_action *act_other); static void act_store_print (const struct sieve_action *action, const struct sieve_result_print_env *rpenv, bool *keep); static bool act_store_start (const struct sieve_action *action, const struct sieve_action_exec_env *aenv, void **tr_context); static bool act_store_execute (const struct sieve_action *action, const struct sieve_action_exec_env *aenv, void *tr_context); static bool act_store_commit (const struct sieve_action *action, const struct sieve_action_exec_env *aenv, void *tr_context, bool *keep); static void act_store_rollback (const struct sieve_action *action, const struct sieve_action_exec_env *aenv, void *tr_context, bool success); /* Action object */ const struct sieve_action_def act_store = { "store", SIEVE_ACTFLAG_TRIES_DELIVER, act_store_equals, act_store_check_duplicate, NULL, act_store_print, act_store_start, act_store_execute, act_store_commit, act_store_rollback, }; /* API */ int sieve_act_store_add_to_result (const struct sieve_runtime_env *renv, struct sieve_side_effects_list *seffects, const char *mailbox, unsigned int source_line) { pool_t pool; struct act_store_context *act; /* Add redirect action to the result */ pool = sieve_result_pool(renv->result); act = p_new(pool, struct act_store_context, 1); act->mailbox = p_strdup(pool, mailbox); return sieve_result_add_action(renv, NULL, &act_store, seffects, source_line, (void *) act, 0); } void sieve_act_store_add_flags (const struct sieve_action_exec_env *aenv, void *tr_context, const char *const *keywords, enum mail_flags flags) { struct act_store_transaction *trans = (struct act_store_transaction *) tr_context; /* Assign mail keywords for subsequent mailbox_copy() */ if ( *keywords != NULL ) { const char *const *kw; if ( !array_is_created(&trans->keywords) ) { pool_t pool = sieve_result_pool(aenv->result); p_array_init(&trans->keywords, pool, 2); } kw = keywords; while ( *kw != NULL ) { const char *kw_error; if ( trans->box != NULL ) { if ( mailbox_keyword_is_valid(trans->box, *kw, &kw_error) ) array_append(&trans->keywords, kw, 1); else { char *error = ""; if ( kw_error != NULL && *kw_error != '\0' ) { error = t_strdup_noconst(kw_error); error[0] = i_tolower(error[0]); } sieve_result_warning(aenv, "specified IMAP keyword '%s' is invalid (ignored): %s", str_sanitize(*kw, 64), error); } } kw++; } } /* Assign mail flags for subsequent mailbox_copy() */ trans->flags |= flags; trans->flags_altered = TRUE; } void sieve_act_store_get_storage_error (const struct sieve_action_exec_env *aenv, struct act_store_transaction *trans) { pool_t pool = sieve_result_pool(aenv->result); trans->error = p_strdup(pool, mail_storage_get_last_error(mailbox_get_storage(trans->box), &trans->error_code)); } /* Equality */ static bool act_store_equals (const struct sieve_script_env *senv, const struct sieve_action *act1, const struct sieve_action *act2) { struct act_store_context *st_ctx1 = ( act1 == NULL ? NULL : (struct act_store_context *) act1->context ); struct act_store_context *st_ctx2 = ( act2 == NULL ? NULL : (struct act_store_context *) act2->context ); const char *mailbox1, *mailbox2; /* FIXME: consider namespace aliases */ if ( st_ctx1 == NULL && st_ctx2 == NULL ) return TRUE; mailbox1 = ( st_ctx1 == NULL ? SIEVE_SCRIPT_DEFAULT_MAILBOX(senv) : st_ctx1->mailbox ); mailbox2 = ( st_ctx2 == NULL ? SIEVE_SCRIPT_DEFAULT_MAILBOX(senv) : st_ctx2->mailbox ); if ( strcmp(mailbox1, mailbox2) == 0 ) return TRUE; return ( strcasecmp(mailbox1, "INBOX") == 0 && strcasecmp(mailbox2, "INBOX") == 0 ); } /* Result verification */ static int act_store_check_duplicate (const struct sieve_runtime_env *renv, const struct sieve_action *act, const struct sieve_action *act_other) { return ( act_store_equals(renv->scriptenv, act, act_other) ? 1 : 0 ); } /* Result printing */ static void act_store_print (const struct sieve_action *action, const struct sieve_result_print_env *rpenv, bool *keep) { struct act_store_context *ctx = (struct act_store_context *) action->context; const char *mailbox; mailbox = ( ctx == NULL ? SIEVE_SCRIPT_DEFAULT_MAILBOX(rpenv->scriptenv) : ctx->mailbox ); sieve_result_action_printf(rpenv, "store message in folder: %s", str_sanitize(mailbox, 128)); *keep = FALSE; } /* Action implementation */ static bool act_store_mailbox_open (const struct sieve_action_exec_env *aenv, const char *mailbox, struct mailbox **box_r, enum mail_error *error_code_r, const char **error_r) { struct mail_storage **storage = &(aenv->exec_status->last_storage); struct mail_deliver_save_open_context save_ctx; *box_r = NULL; if ( !uni_utf8_str_is_valid(mailbox) ) { /* FIXME: check utf-8 validity at compiletime/runtime */ *error_r = t_strdup_printf("mailbox name not utf-8: %s", mailbox); *error_code_r = MAIL_ERROR_PARAMS; return FALSE; } memset(&save_ctx, 0, sizeof(save_ctx)); save_ctx.user = aenv->scriptenv->user; save_ctx.lda_mailbox_autocreate = aenv->scriptenv->mailbox_autocreate; save_ctx.lda_mailbox_autosubscribe = aenv->scriptenv->mailbox_autosubscribe; if (mail_deliver_save_open(&save_ctx, mailbox, box_r, error_code_r, error_r) < 0) return FALSE; *storage = mailbox_get_storage(*box_r); return TRUE; } static bool act_store_start (const struct sieve_action *action, const struct sieve_action_exec_env *aenv, void **tr_context) { struct act_store_context *ctx = (struct act_store_context *) action->context; const struct sieve_script_env *senv = aenv->scriptenv; struct act_store_transaction *trans; struct mailbox *box = NULL; pool_t pool = sieve_result_pool(aenv->result); const char *error = NULL; enum mail_error error_code = MAIL_ERROR_NONE; bool disabled = FALSE, open_failed = FALSE; /* If context is NULL, the store action is the result of (implicit) keep */ if ( ctx == NULL ) { ctx = p_new(pool, struct act_store_context, 1); ctx->mailbox = p_strdup(pool, SIEVE_SCRIPT_DEFAULT_MAILBOX(senv)); } /* Open the requested mailbox */ /* NOTE: The caller of the sieve library is allowed to leave user set * to NULL. This implementation will then skip actually storing the message. */ if ( senv->user != NULL ) { if ( !act_store_mailbox_open(aenv, ctx->mailbox, &box, &error_code, &error) ) { open_failed = TRUE; } } else { disabled = TRUE; } /* Create transaction context */ trans = p_new(pool, struct act_store_transaction, 1); trans->context = ctx; trans->box = box; trans->flags = 0; trans->disabled = disabled; if ( open_failed ) { trans->error = error; trans->error_code = error_code; } else { trans->error_code = MAIL_ERROR_NONE; } *tr_context = (void *)trans; return ( trans->error_code == MAIL_ERROR_NONE || trans->error_code == MAIL_ERROR_NOTFOUND ); } static struct mail_keywords *act_store_keywords_create (const struct sieve_action_exec_env *aenv, ARRAY_TYPE(const_string) *keywords, struct mailbox *box) { struct mail_keywords *box_keywords = NULL; if ( array_is_created(keywords) && array_count(keywords) > 0 ) { const char *const *kwds; (void)array_append_space(keywords); kwds = array_idx(keywords, 0); /* FIXME: Do we need to clear duplicates? */ if ( mailbox_keywords_create(box, kwds, &box_keywords) < 0) { sieve_result_error(aenv, "invalid keywords set for stored message"); return NULL; } } return box_keywords; } static bool act_store_execute (const struct sieve_action *action ATTR_UNUSED, const struct sieve_action_exec_env *aenv, void *tr_context) { struct act_store_transaction *trans = (struct act_store_transaction *) tr_context; const struct sieve_message_data *msgdata = aenv->msgdata; struct mail_save_context *save_ctx; struct mail_keywords *keywords = NULL; bool result = TRUE; /* Verify transaction */ if ( trans == NULL ) return FALSE; /* Check whether we need to do anything */ if ( trans->disabled ) return TRUE; /* Exit early if mailbox is not available */ if ( trans->box == NULL || trans->error_code != MAIL_ERROR_NONE ) return FALSE; /* If the message originates from the target mailbox, only update the flags * and keywords */ if ( mailbox_backends_equal(trans->box, msgdata->mail->box) ) { trans->redundant = TRUE; if ( trans->flags_altered ) { keywords = act_store_keywords_create (aenv, &trans->keywords, msgdata->mail->box); if ( keywords != NULL ) { mail_update_keywords(msgdata->mail, MODIFY_REPLACE, keywords); mailbox_keywords_unref(trans->box, &keywords); } mail_update_flags(msgdata->mail, MODIFY_REPLACE, trans->flags); } return TRUE; } /* Mark attempt to store in default mailbox */ if ( strcmp(trans->context->mailbox, SIEVE_SCRIPT_DEFAULT_MAILBOX(aenv->scriptenv)) == 0 ) aenv->exec_status->tried_default_save = TRUE; /* Mark attempt to use storage. Can only get here when all previous actions * succeeded. */ aenv->exec_status->last_storage = mailbox_get_storage(trans->box); /* Start mail transaction */ trans->mail_trans = mailbox_transaction_begin (trans->box, MAILBOX_TRANSACTION_FLAG_EXTERNAL); /* Create mail object for stored message */ trans->dest_mail = mail_alloc(trans->mail_trans, 0, NULL); /* Store the message */ save_ctx = mailbox_save_alloc(trans->mail_trans); mailbox_save_set_dest_mail(save_ctx, trans->dest_mail); /* Apply keywords and flags that side-effects may have added */ if ( trans->flags_altered ) { keywords = act_store_keywords_create(aenv, &trans->keywords, trans->box); mailbox_save_set_flags(save_ctx, trans->flags, keywords); } if ( mailbox_copy(&save_ctx, aenv->msgdata->mail) < 0 ) { sieve_act_store_get_storage_error(aenv, trans); result = FALSE; } /* Deallocate keywords */ if ( keywords != NULL ) { mailbox_keywords_unref(trans->box, &keywords); } return result; } static void act_store_log_status (struct act_store_transaction *trans, const struct sieve_action_exec_env *aenv, bool rolled_back, bool status ) { const char *mailbox_name; if ( trans->box != NULL ) mailbox_name = str_sanitize(mailbox_get_vname(trans->box), 128); else mailbox_name = str_sanitize(trans->context->mailbox, 128); /* Store disabled? */ if ( trans->disabled ) { sieve_result_log(aenv, "store into mailbox '%s' skipped", mailbox_name); /* Store redundant? */ } else if ( trans->redundant ) { sieve_result_log(aenv, "left message in mailbox '%s'", mailbox_name); /* Store failed? */ } else if ( !status ) { const char *errstr; enum mail_error error; if ( trans->error != NULL ) errstr = trans->error; else errstr = mail_storage_get_last_error (mailbox_get_storage(trans->box), &error); sieve_result_error(aenv, "failed to store into mailbox '%s': %s", mailbox_name, errstr); /* Store aborted? */ } else if ( rolled_back ) { sieve_result_log(aenv, "store into mailbox '%s' aborted", mailbox_name); /* Succeeded */ } else { sieve_result_log(aenv, "stored mail into mailbox '%s'", mailbox_name); } } static bool act_store_commit (const struct sieve_action *action ATTR_UNUSED, const struct sieve_action_exec_env *aenv, void *tr_context, bool *keep) { struct act_store_transaction *trans = (struct act_store_transaction *) tr_context; bool status = TRUE; /* Verify transaction */ if ( trans == NULL ) return FALSE; /* Check whether we need to do anything */ if ( trans->disabled ) { act_store_log_status(trans, aenv, FALSE, status); *keep = FALSE; if ( trans->box != NULL ) mailbox_free(&trans->box); return TRUE; } else if ( trans->redundant ) { act_store_log_status(trans, aenv, FALSE, status); aenv->exec_status->keep_original = TRUE; aenv->exec_status->message_saved = TRUE; if ( trans->box != NULL ) mailbox_free(&trans->box); return TRUE; } /* Mark attempt to use storage. Can only get here when all previous actions * succeeded. */ aenv->exec_status->last_storage = mailbox_get_storage(trans->box); /* Free mail object for stored message */ if ( trans->dest_mail != NULL ) mail_free(&trans->dest_mail); /* Commit mailbox transaction */ status = ( mailbox_transaction_commit(&trans->mail_trans) == 0 ); /* Note the fact that the message was stored at least once */ if ( status ) aenv->exec_status->message_saved = TRUE; /* Log our status */ act_store_log_status(trans, aenv, FALSE, status); /* Cancel implicit keep if all went well */ *keep = !status; /* Close mailbox */ if ( trans->box != NULL ) mailbox_free(&trans->box); return status; } static void act_store_rollback (const struct sieve_action *action ATTR_UNUSED, const struct sieve_action_exec_env *aenv, void *tr_context, bool success) { struct act_store_transaction *trans = (struct act_store_transaction *) tr_context; /* Log status */ act_store_log_status(trans, aenv, TRUE, success); /* Free mailobject for stored message */ if ( trans->dest_mail != NULL ) mail_free(&trans->dest_mail); /* Rollback mailbox transaction */ if ( trans->mail_trans != NULL ) mailbox_transaction_rollback(&trans->mail_trans); /* Close the mailbox */ if ( trans->box != NULL ) mailbox_free(&trans->box); } /* * Action utility functions */ bool sieve_action_duplicate_check_available (const struct sieve_script_env *senv) { return ( senv->duplicate_check != NULL && senv->duplicate_mark != NULL ); } int sieve_action_duplicate_check (const struct sieve_script_env *senv, const void *id, size_t id_size) { if ( senv->duplicate_check == NULL || senv->duplicate_mark == NULL) return 0; return senv->duplicate_check (senv->script_context, id, id_size, senv->username); } void sieve_action_duplicate_mark (const struct sieve_script_env *senv, const void *id, size_t id_size, time_t time) { if ( senv->duplicate_check == NULL || senv->duplicate_mark == NULL) return; senv->duplicate_mark (senv->script_context, id, id_size, senv->username, time); }