lda-sieve-plugin.c [plain text]
#include "lib.h"
#include "array.h"
#include "home-expand.h"
#include "eacces-error.h"
#include "mail-storage.h"
#include "mail-deliver.h"
#include "mail-user.h"
#include "duplicate.h"
#include "smtp-client.h"
#include "mail-send.h"
#include "lda-settings.h"
#include "sieve.h"
#include "lda-sieve-plugin.h"
#include <stdlib.h>
#include <sys/stat.h>
#include <dirent.h>
#define SIEVE_DEFAULT_PATH "~/.dovecot.sieve"
#define LDA_SIEVE_MAX_USER_ERRORS 30
static deliver_mail_func_t *next_deliver_mail;
static const char *lda_sieve_get_homedir
(void *context)
{
struct mail_deliver_context *mdctx = (struct mail_deliver_context *)context;
const char *home = NULL;
if ( mdctx == NULL || mdctx->dest_user == NULL )
return NULL;
if ( mail_user_get_home(mdctx->dest_user, &home) <= 0 )
return NULL;
return home;
}
static const char *lda_sieve_get_setting
(void *context, const char *identifier)
{
struct mail_deliver_context *mdctx = (struct mail_deliver_context *)context;
const char *value = NULL;
if ( mdctx == NULL )
return NULL;
if ( mdctx->dest_user == NULL ||
(value=mail_user_plugin_getenv(mdctx->dest_user, identifier)) == NULL ) {
if ( strcmp(identifier, "recipient_delimiter") == 0 )
value = mdctx->set->recipient_delimiter;
}
return value;
}
static const struct sieve_environment lda_sieve_env = {
lda_sieve_get_homedir,
lda_sieve_get_setting
};
static void *lda_sieve_smtp_open
(void *script_ctx, const char *destination,
const char *return_path, FILE **file_r)
{
struct mail_deliver_context *dctx =
(struct mail_deliver_context *) script_ctx;
return (void *) smtp_client_open(dctx->set, destination, return_path, file_r);
}
static bool lda_sieve_smtp_close
(void *script_ctx ATTR_UNUSED, void *handle)
{
struct smtp_client *smtp_client = (struct smtp_client *) handle;
return ( smtp_client_close(smtp_client) == 0 );
}
static int lda_sieve_reject_mail(void *script_ctx, const char *recipient,
const char *reason)
{
struct mail_deliver_context *dctx =
(struct mail_deliver_context *) script_ctx;
return mail_send_rejection(dctx, recipient, reason);
}
static int lda_sieve_duplicate_check
(void *script_ctx, const void *id, size_t id_size, const char *user)
{
struct mail_deliver_context *dctx = (struct mail_deliver_context *) script_ctx;
return duplicate_check(dctx->dup_ctx, id, id_size, user);
}
static void lda_sieve_duplicate_mark
(void *script_ctx, const void *id, size_t id_size, const char *user,
time_t time)
{
struct mail_deliver_context *dctx = (struct mail_deliver_context *) script_ctx;
duplicate_mark(dctx->dup_ctx, id, id_size, user, time);
}
struct lda_sieve_run_context {
struct sieve_instance *svinst;
struct mail_deliver_context *mdctx;
const char *const *script_files;
unsigned int script_count;
const char *user_script;
const char *main_script;
const struct sieve_message_data *msgdata;
const struct sieve_script_env *scriptenv;
struct sieve_error_handler *user_ehandler;
struct sieve_error_handler *master_ehandler;
const char *userlog;
};
static const char *lda_sieve_get_personal_path
(struct sieve_instance *svinst, struct mail_user *user)
{
const char *script_path, *home;
if ( mail_user_get_home(user, &home) <= 0 )
home = NULL;
script_path = mail_user_plugin_getenv(user, "sieve");
if (script_path != NULL) {
if (*script_path == '\0') {
if ( user->mail_debug )
sieve_sys_debug(svinst, "empty script path, disabled");
return NULL;
}
script_path = mail_user_home_expand(user, script_path);
if (*script_path != '/' && *script_path != '\0') {
if ( home == NULL || *home == '\0' ) {
sieve_sys_error(svinst, "relative script path, but empty home dir: %s", script_path);
return NULL;
}
script_path = t_strconcat(home, "/", script_path, NULL);
}
} else {
if ( home == NULL || *home == '\0' ) {
sieve_sys_error(svinst,
"path to user's main active personal script is unknown. "
"See http://wiki2.dovecot.org/Pigeonhole/Sieve/Configuration");
return NULL;
}
script_path = mail_user_home_expand(user, SIEVE_DEFAULT_PATH);
}
return script_path;
}
static const char *lda_sieve_get_default_path
(struct mail_user *user)
{
const char *script_path;
script_path = mail_user_plugin_getenv(user, "sieve_global_path");
if (script_path == NULL) {
script_path = mail_user_plugin_getenv(user, "global_script_path");
}
return script_path;
}
static int lda_sieve_multiscript_get_scriptfiles
(struct sieve_instance *svinst, const char *script_path,
ARRAY_TYPE(const_string) *scriptfiles)
{
struct sieve_directory *sdir;
enum sieve_error error;
const char *file;
if ( (sdir=sieve_directory_open(svinst, script_path, &error)) == NULL )
return ( error == SIEVE_ERROR_NOT_FOUND ? 0 : -1 );
while ( (file=sieve_directory_get_scriptfile(sdir)) != NULL ) {
const char *const *scripts;
unsigned int count, i;
scripts = array_get(scriptfiles, &count);
for ( i = 0; i < count; i++ ) {
if ( strcmp(file, scripts[i]) < 0 )
break;
}
if ( i == count )
array_append(scriptfiles, &file, 1);
else
array_insert(scriptfiles, i, &file, 1);
}
sieve_directory_close(&sdir);
return 1;
}
static void lda_sieve_binary_save
(struct lda_sieve_run_context *srctx, struct sieve_binary *sbin,
unsigned int script_index)
{
const char *script_path = srctx->script_files[script_index];
enum sieve_error error;
if ( sieve_save(sbin, NULL, FALSE, &error) < 0 &&
error == SIEVE_ERROR_NO_PERM && script_path != srctx->user_script ) {
sieve_sys_error(srctx->svinst,
"the lda sieve plugin does not have permission "
"to save global sieve script binaries; "
"global sieve scripts like %s need to be "
"pre-compiled using the sievec tool", script_path);
}
}
static struct sieve_binary *lda_sieve_open
(struct lda_sieve_run_context *srctx, unsigned int script_index,
enum sieve_error *error_r)
{
struct sieve_instance *svinst = srctx->svinst;
const char *script_path = srctx->script_files[script_index];
const char *script_name =
( script_path == srctx->main_script ? "main_script" : NULL );
struct sieve_error_handler *ehandler;
struct sieve_binary *sbin;
bool debug = srctx->mdctx->dest_user->mail_debug;
if ( script_path == srctx->user_script )
ehandler = srctx->user_ehandler;
else
ehandler = srctx->master_ehandler;
if ( debug )
sieve_sys_debug(svinst, "opening script %s", script_path);
sieve_error_handler_reset(ehandler);
if ( (sbin=sieve_open(svinst, script_path, script_name, ehandler, error_r))
== NULL ) {
if ( *error_r == SIEVE_ERROR_NOT_FOUND ) {
if ( debug )
sieve_sys_debug(svinst, "script file %s is missing", script_path);
} else if ( *error_r == SIEVE_ERROR_NOT_VALID &&
script_path == srctx->user_script && srctx->userlog != NULL ) {
sieve_sys_error(svinst,
"failed to open script %s "
"(view user logfile %s for more information)",
script_path, srctx->userlog);
} else {
sieve_sys_error(svinst,
"failed to open script %s", script_path);
}
return NULL;
}
lda_sieve_binary_save(srctx, sbin, script_index);
return sbin;
}
static struct sieve_binary *lda_sieve_recompile
(struct lda_sieve_run_context *srctx, unsigned int script_index,
enum sieve_error *error_r)
{
struct sieve_instance *svinst = srctx->svinst;
const char *script_path = srctx->script_files[script_index];
const char *script_name =
( script_path == srctx->main_script ? "main_script" : NULL );
struct sieve_error_handler *ehandler;
struct sieve_binary *sbin;
bool debug = srctx->mdctx->dest_user->mail_debug;
sieve_sys_warning(svinst,
"encountered corrupt binary: re-compiling script %s", script_path);
if ( script_path == srctx->user_script )
ehandler = srctx->user_ehandler;
else
ehandler = srctx->master_ehandler;
if ( (sbin=sieve_compile
(svinst, script_path, script_name, ehandler, error_r)) == NULL ) {
if ( *error_r == SIEVE_ERROR_NOT_FOUND ) {
if ( debug )
sieve_sys_debug(svinst, "script file %s is missing for re-compile",
script_path);
} else if ( *error_r == SIEVE_ERROR_NOT_VALID &&
script_path == srctx->user_script && srctx->userlog != NULL ) {
sieve_sys_error(svinst,
"failed to re-compile script %s "
"(view user logfile %s for more information)",
script_path, srctx->userlog);
} else {
sieve_sys_error(svinst,
"failed to re-compile script %s", script_path);
}
return NULL;
}
return sbin;
}
static int lda_sieve_handle_exec_status
(struct lda_sieve_run_context *srctx, const char *script_path, int status)
{
struct sieve_instance *svinst = srctx->svinst;
const char *userlog_notice = "";
int ret;
if ( script_path == srctx->user_script && srctx->userlog != NULL ) {
userlog_notice = t_strdup_printf
(" (user logfile %s may reveal additional details)", srctx->userlog);
}
switch ( status ) {
case SIEVE_EXEC_FAILURE:
sieve_sys_error(svinst,
"execution of script %s failed, but implicit keep was successful%s",
script_path, userlog_notice);
ret = 1;
break;
case SIEVE_EXEC_BIN_CORRUPT:
sieve_sys_error(svinst,
"!!BUG!!: binary compiled from %s is still corrupt; "
"bailing out and reverting to default delivery",
script_path);
ret = -1;
break;
case SIEVE_EXEC_KEEP_FAILED:
sieve_sys_error(svinst,
"script %s failed with unsuccessful implicit keep%s",
script_path, userlog_notice);
ret = -1;
break;
default:
ret = status > 0 ? 1 : -1;
break;
}
return ret;
}
static int lda_sieve_singlescript_execute
(struct lda_sieve_run_context *srctx)
{
struct sieve_instance *svinst = srctx->svinst;
const char *script_file = srctx->script_files[0];
bool user_script = ( script_file == srctx->user_script );
struct sieve_error_handler *ehandler;
struct sieve_binary *sbin;
bool debug = srctx->mdctx->dest_user->mail_debug;
enum sieve_error error;
int ret;
if ( (sbin=lda_sieve_open(srctx, 0, &error)) == NULL )
return ( error == SIEVE_ERROR_NOT_FOUND ? 0 : -1 );
if ( debug )
sieve_sys_debug(svinst, "executing script from %s", sieve_get_source(sbin));
if ( user_script ) {
ehandler = srctx->user_ehandler;
} else {
ehandler = srctx->master_ehandler;
}
ret = sieve_execute(sbin, srctx->msgdata, srctx->scriptenv, ehandler, NULL);
if ( ret == SIEVE_EXEC_BIN_CORRUPT && sieve_is_loaded(sbin) ) {
sieve_close(&sbin);
if ( (sbin=lda_sieve_recompile(srctx, 0, &error)) == NULL )
return ( error == SIEVE_ERROR_NOT_FOUND ? 0 : -1 );
if ( debug )
sieve_sys_debug
(svinst, "executing script from %s", sieve_get_source(sbin));
ret = sieve_execute(sbin, srctx->msgdata, srctx->scriptenv, ehandler, NULL);
if ( ret != SIEVE_EXEC_BIN_CORRUPT )
lda_sieve_binary_save(srctx, sbin, 0);
}
sieve_close(&sbin);
return lda_sieve_handle_exec_status(srctx, script_file, ret);
}
static int lda_sieve_multiscript_execute
(struct lda_sieve_run_context *srctx)
{
struct sieve_instance *svinst = srctx->svinst;
const char *const *scripts = srctx->script_files;
unsigned int count = srctx->script_count;
struct sieve_multiscript *mscript;
struct sieve_error_handler *ehandler = srctx->master_ehandler;
bool debug = srctx->mdctx->dest_user->mail_debug;
const char *last_script = NULL;
bool user_script = FALSE;
unsigned int i;
int ret = 1;
bool more = TRUE;
enum sieve_error error;
mscript = sieve_multiscript_start_execute
(svinst, srctx->msgdata, srctx->scriptenv);
for ( i = 0; i < count && more; i++ ) {
struct sieve_binary *sbin = NULL;
const char *script_file = scripts[i];
bool final = ( i == count - 1 );
user_script = ( script_file == srctx->user_script );
last_script = script_file;
if ( user_script )
ehandler = srctx->user_ehandler;
else
ehandler = srctx->master_ehandler;
if ( (sbin=lda_sieve_open(srctx, i, &error)) == NULL ) {
ret = ( error == SIEVE_ERROR_NOT_FOUND ? 0 : -1 );
break;
}
if ( debug )
sieve_sys_debug(svinst, "executing script from %s", sieve_get_source(sbin));
more = sieve_multiscript_run(mscript, sbin, ehandler, final);
if ( !more ) {
if ( sieve_multiscript_status(mscript) == SIEVE_EXEC_BIN_CORRUPT &&
sieve_is_loaded(sbin) ) {
sieve_close(&sbin);
if ( (sbin=lda_sieve_recompile(srctx, i, &error)) == NULL ) {
ret = ( error == SIEVE_ERROR_NOT_FOUND ? 0 : -1 );
break;
}
more = sieve_multiscript_run(mscript, sbin, ehandler, final);
if ( more &&
sieve_multiscript_status(mscript) != SIEVE_EXEC_BIN_CORRUPT )
lda_sieve_binary_save(srctx, sbin, i);
}
}
sieve_close(&sbin);
}
ret = sieve_multiscript_finish(&mscript, ehandler, NULL);
return lda_sieve_handle_exec_status(srctx, last_script, ret);
}
static int lda_sieve_run
(struct mail_deliver_context *mdctx, struct sieve_instance *svinst,
const char *user_script, const char *default_script,
const ARRAY_TYPE (const_string) *scripts_before,
const ARRAY_TYPE (const_string) *scripts_after,
struct mail_storage **storage_r)
{
ARRAY_TYPE (const_string) scripts;
struct lda_sieve_run_context srctx;
struct sieve_message_data msgdata;
struct sieve_script_env scriptenv;
struct sieve_exec_status estatus;
int ret = 0;
*storage_r = NULL;
memset(&srctx, 0, sizeof(srctx));
srctx.svinst = svinst;
srctx.mdctx = mdctx;
t_array_init(&scripts, 32);
array_append_array(&scripts, scripts_before);
if ( user_script != NULL ) {
array_append(&scripts, &user_script, 1);
srctx.user_script = user_script;
srctx.main_script = user_script;
} else if ( default_script != NULL ) {
array_append(&scripts, &default_script, 1);
srctx.user_script = NULL;
srctx.main_script = default_script;
} else {
srctx.user_script = NULL;
srctx.main_script = NULL;
}
array_append_array(&scripts, scripts_after);
srctx.master_ehandler = sieve_system_ehandler_get(svinst);
if ( user_script != NULL ) {
srctx.userlog = t_strconcat(user_script, ".log", NULL);
srctx.user_ehandler = sieve_logfile_ehandler_create
(svinst, srctx.userlog, LDA_SIEVE_MAX_USER_ERRORS);
}
memset(&msgdata, 0, sizeof(msgdata));
msgdata.mail = mdctx->src_mail;
msgdata.return_path = mail_deliver_get_return_address(mdctx);
msgdata.orig_envelope_to = mdctx->dest_addr;
msgdata.final_envelope_to = mdctx->final_dest_addr;
msgdata.auth_user = mdctx->dest_user->username;
(void)mail_get_first_header(msgdata.mail, "Message-ID", &msgdata.id);
srctx.msgdata = &msgdata;
memset(&scriptenv, 0, sizeof(scriptenv));
memset(&estatus, 0, sizeof(estatus));
scriptenv.action_log_format = mdctx->set->deliver_log_format;
scriptenv.default_mailbox = mdctx->dest_mailbox_name;
scriptenv.mailbox_autocreate = mdctx->set->lda_mailbox_autocreate;
scriptenv.mailbox_autosubscribe = mdctx->set->lda_mailbox_autosubscribe;
scriptenv.user = mdctx->dest_user;
scriptenv.username = mdctx->dest_user->username;
scriptenv.hostname = mdctx->set->hostname;
scriptenv.postmaster_address = mdctx->set->postmaster_address;
scriptenv.smtp_open = lda_sieve_smtp_open;
scriptenv.smtp_close = lda_sieve_smtp_close;
scriptenv.duplicate_mark = lda_sieve_duplicate_mark;
scriptenv.duplicate_check = lda_sieve_duplicate_check;
scriptenv.reject_mail = lda_sieve_reject_mail;
scriptenv.script_context = (void *) mdctx;
scriptenv.exec_status = &estatus;
srctx.scriptenv = &scriptenv;
srctx.script_files = array_get(&scripts, &srctx.script_count);
if ( srctx.script_count == 1 )
ret = lda_sieve_singlescript_execute(&srctx);
else
ret = lda_sieve_multiscript_execute(&srctx);
mdctx->tried_default_save = estatus.tried_default_save;
*storage_r = estatus.last_storage;
if ( srctx.user_ehandler != NULL )
sieve_error_handler_unref(&srctx.user_ehandler);
return ret;
}
static int lda_sieve_deliver_mail
(struct mail_deliver_context *mdctx, struct mail_storage **storage_r)
{
struct sieve_instance *svinst;
struct sieve_error_handler *master_ehandler;
const char *user_script, *default_script, *sieve_before, *sieve_after;
ARRAY_TYPE (const_string) scripts_before;
ARRAY_TYPE (const_string) scripts_after;
bool debug = mdctx->dest_user->mail_debug;
int ret = 0;
svinst = sieve_init(&lda_sieve_env, mdctx, debug);
master_ehandler = sieve_master_ehandler_create(svinst, mdctx->session_id, 0);
sieve_system_ehandler_set(master_ehandler);
sieve_error_handler_accept_infolog(master_ehandler, TRUE);
sieve_error_handler_accept_debuglog(master_ehandler, debug);
*storage_r = NULL;
T_BEGIN {
struct stat st;
user_script = lda_sieve_get_personal_path(svinst, mdctx->dest_user);
default_script = lda_sieve_get_default_path(mdctx->dest_user);
if ( user_script != NULL && stat(user_script, &st) < 0 ) {
switch ( errno ) {
case ENOENT:
if ( debug )
sieve_sys_debug(svinst, "user's script path %s doesn't exist "
"(using global script path in stead)", user_script);
break;
case EACCES:
sieve_sys_error(svinst, "failed to stat user's sieve script: %s "
"(using global script path in stead)",
eacces_error_get("stat", user_script));
break;
default:
sieve_sys_error(svinst, "failed to stat user's sieve script: "
"stat(%s) failed: %m (using global script path in stead)",
user_script);
break;
}
user_script = NULL;
}
if ( debug ) {
const char *script = user_script == NULL ? default_script : user_script;
if ( script == NULL )
sieve_sys_debug(svinst, "user has no valid personal script");
else
sieve_sys_debug(svinst, "using sieve path for user's script: %s", script);
}
t_array_init(&scripts_before, 16);
t_array_init(&scripts_after, 16);
sieve_before = mail_user_plugin_getenv(mdctx->dest_user, "sieve_before");
sieve_after = mail_user_plugin_getenv(mdctx->dest_user, "sieve_after");
if ( sieve_before != NULL && *sieve_before != '\0' ) {
if ( lda_sieve_multiscript_get_scriptfiles
(svinst, sieve_before, &scripts_before) == 0 && debug ) {
sieve_sys_debug(svinst, "sieve_before path not found: %s", sieve_before);
}
}
if ( sieve_after != NULL && *sieve_after != '\0' ) {
if ( lda_sieve_multiscript_get_scriptfiles
(svinst, sieve_after, &scripts_after) == 0 && debug ) {
sieve_sys_debug(svinst, "sieve_after path not found: %s", sieve_after);
}
}
if ( debug ) {
const char *const *scriptfiles;
unsigned int count, i;
scriptfiles = array_get(&scripts_before, &count);
for ( i = 0; i < count; i ++ ) {
sieve_sys_debug(svinst, "executed before user's script(%d): %s",
i+1, scriptfiles[i]);
}
scriptfiles = array_get(&scripts_after, &count);
for ( i = 0; i < count; i ++ ) {
sieve_sys_debug(svinst, "executed after user's script(%d): %s",
i+1, scriptfiles[i]);
}
}
if ( array_count(&scripts_before) == 0 && array_count(&scripts_after) == 0 &&
user_script == NULL && default_script == NULL ) {
if ( debug )
sieve_sys_debug(svinst, "no scripts to execute: reverting to default delivery.");
ret = 0;
} else {
ret = lda_sieve_run
(mdctx, svinst, user_script, default_script, &scripts_before,
&scripts_after, storage_r);
}
} T_END;
sieve_deinit(&svinst);
sieve_error_handler_unref(&master_ehandler);
return ret;
}
const char *sieve_plugin_version = DOVECOT_VERSION;
const char sieve_plugin_binary_dependency[] = "lda lmtp";
void sieve_plugin_init(void)
{
next_deliver_mail = mail_deliver_hook_set(lda_sieve_deliver_mail);
}
void sieve_plugin_deinit(void)
{
mail_deliver_hook_set(next_deliver_mail);
}