doveadm-sieve-plugin.c [plain text]
#include "lib.h"
#include "ioloop.h"
#include "istream.h"
#include "istream-concat.h"
#include "sieve-script.h"
#include "sieve-script-file.h"
#include "sieve-storage.h"
#include "sieve-storage-list.h"
#include "sieve-storage-save.h"
#include "sieve-storage-script.h"
#include "mail-storage-private.h"
#define SIEVE_MAIL_CONTEXT(obj) \
MODULE_CONTEXT(obj, sieve_storage_module)
#define SIEVE_USER_CONTEXT(obj) \
MODULE_CONTEXT(obj, sieve_user_module)
struct sieve_mail_user {
union mail_user_module_context module_ctx;
struct sieve_instance *svinst;
struct sieve_storage *sieve_storage;
};
struct sieve_mailbox_attribute_iter {
struct mailbox_attribute_iter iter;
struct mailbox_attribute_iter *super;
struct sieve_list_context *sieve_list;
string_t *name;
bool failed;
bool have_active;
};
void doveadm_sieve_plugin_init(struct module *module);
void doveadm_sieve_plugin_deinit(void);
static MODULE_CONTEXT_DEFINE_INIT(sieve_storage_module,
&mail_storage_module_register);
static MODULE_CONTEXT_DEFINE_INIT(sieve_user_module,
&mail_user_module_register);
const char *doveadm_sieve_plugin_version = DOVECOT_ABI_VERSION;
static const char *
mail_sieve_get_setting(void *context, const char *identifier)
{
struct mail_user *mail_user = context;
return mail_user_plugin_getenv(mail_user, identifier);
}
static const struct sieve_callbacks mail_sieve_callbacks = {
NULL,
mail_sieve_get_setting
};
static void mail_sieve_user_deinit(struct mail_user *user)
{
struct sieve_mail_user *suser = SIEVE_USER_CONTEXT(user);
sieve_storage_free(suser->sieve_storage);
sieve_deinit(&suser->svinst);
suser->module_ctx.super.deinit(user);
}
static int
mail_sieve_user_init
(struct mail_user *user, struct sieve_storage **svstorage_r)
{
struct sieve_mail_user *suser = SIEVE_USER_CONTEXT(user);
enum sieve_storage_flags storage_flags = SIEVE_STORAGE_FLAG_SYNCHRONIZING;
struct mail_user_vfuncs *v = user->vlast;
struct sieve_environment svenv;
if (suser != NULL) {
*svstorage_r = suser->sieve_storage;
return 0;
}
memset(&svenv, 0, sizeof(svenv));
svenv.username = user->username;
(void)mail_user_get_home(user, &svenv.home_dir);
svenv.base_dir = user->set->base_dir;
svenv.flags = SIEVE_FLAG_HOME_RELATIVE;
suser = p_new(user->pool, struct sieve_mail_user, 1);
suser->module_ctx.super = *v;
user->vlast = &suser->module_ctx.super;
v->deinit = mail_sieve_user_deinit;
if (user->mail_debug)
storage_flags |= SIEVE_STORAGE_FLAG_DEBUG;
suser->svinst = sieve_init(&svenv, &mail_sieve_callbacks,
user, user->mail_debug);
suser->sieve_storage = sieve_storage_create(suser->svinst, user,
svenv.home_dir, storage_flags);
MODULE_CONTEXT_SET(user, sieve_user_module, suser);
*svstorage_r = suser->sieve_storage;
return 0;
}
static int sieve_attribute_unset_script(struct mail_storage *storage,
struct sieve_storage *svstorage,
const char *scriptname)
{
struct sieve_script *script;
const char *errstr;
enum sieve_error error;
int ret = 0;
script = sieve_storage_script_init(svstorage, scriptname);
ret = script == NULL ? -1 :
sieve_storage_script_delete(&script);
if (ret < 0) {
errstr = sieve_storage_get_last_error(svstorage, &error);
if (error == SIEVE_ERROR_NOT_FOUND) {
return 0;
}
mail_storage_set_critical(storage,
"Failed to delete Sieve script '%s': %s", scriptname,
errstr);
return -1;
}
return 0;
}
static int
sieve_attribute_set_active(struct mail_storage *storage,
struct sieve_storage *svstorage,
const struct mail_attribute_value *value)
{
const char *scriptname;
struct sieve_script *script;
int ret;
if (mailbox_attribute_value_to_string(storage, value, &scriptname) < 0)
return -1;
i_assert(scriptname[0] == MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_LINK);
scriptname++;
if (scriptname == NULL) {
if ((ret=sieve_storage_active_script_is_no_link(svstorage)) != 0) {
if (ret < 0) {
mail_storage_set_internal_error(storage);
return -1;
}
return 0;
}
if (sieve_storage_deactivate(svstorage, value->last_change) < 0) {
mail_storage_set_critical(storage,
"Failed to deactivate Sieve: %s",
sieve_storage_get_last_error(svstorage, NULL));
return -1;
}
return 0;
}
script = sieve_storage_script_init(svstorage, scriptname);
ret = script == NULL ? -1 :
sieve_storage_script_activate(script, value->last_change);
if (ret < 0) {
mail_storage_set_critical(storage,
"Failed to activate Sieve script '%s': %s", scriptname,
sieve_storage_get_last_error(svstorage, NULL));
}
if (script != NULL)
sieve_script_unref(&script);
sieve_storage_set_modified(svstorage, value->last_change);
return ret;
}
static int
sieve_attribute_unset_active_script(struct mail_storage *storage,
struct sieve_storage *svstorage, time_t last_change)
{
int ret;
if ((ret=sieve_storage_active_script_is_no_link(svstorage)) <= 0) {
if (ret < 0)
mail_storage_set_internal_error(storage);
return ret;
}
if (sieve_storage_deactivate(svstorage, last_change) < 0) {
mail_storage_set_critical(storage,
"Failed to deactivate sieve: %s",
sieve_storage_get_last_error(svstorage, NULL));
return -1;
}
return 0;
}
static int
sieve_attribute_set_active_script(struct mail_storage *storage,
struct sieve_storage *svstorage,
const struct mail_attribute_value *value)
{
struct istream *input;
if (value->value != NULL) {
input = i_stream_create_from_data(value->value, strlen(value->value));
} else if (value->value_stream != NULL) {
input = value->value_stream;
i_stream_ref(input);
} else {
return sieve_attribute_unset_active_script(storage, svstorage, value->last_change);
}
i_stream_skip(input, 1);
if (sieve_storage_save_as_active_script
(svstorage, input, value->last_change) < 0) {
mail_storage_set_critical(storage,
"Failed to save active sieve script: %s",
sieve_storage_get_last_error(svstorage, NULL));
i_stream_unref(&input);
return -1;
}
sieve_storage_set_modified(svstorage, value->last_change);
i_stream_unref(&input);
return 0;
}
static int
sieve_attribute_set_default(struct mail_storage *storage,
struct sieve_storage *svstorage,
const struct mail_attribute_value *value)
{
const unsigned char *data;
size_t size;
ssize_t ret;
char type;
if (value->value != NULL) {
type = value->value[0];
} else {
ret = i_stream_read_data(value->value_stream, &data, &size, 0);
if (ret == -1) {
mail_storage_set_critical(storage, "read(%s) failed: %m",
i_stream_get_name(value->value_stream));
return -1;
}
i_assert(ret > 0);
type = data[0];
}
if (type == MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_LINK)
return sieve_attribute_set_active(storage, svstorage, value);
if (type == MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_SCRIPT)
return sieve_attribute_set_active_script(storage, svstorage, value);
mail_storage_set_error(storage, MAIL_ERROR_PARAMS,
"Invalid value for default sieve attribute");
return -1;
}
static int
sieve_attribute_set_sieve(struct mail_storage *storage,
const char *key,
const struct mail_attribute_value *value)
{
struct sieve_storage *svstorage;
struct sieve_save_context *save_ctx;
struct istream *input;
const char *scriptname;
int ret = 0;
if (mail_sieve_user_init(storage->user, &svstorage) < 0)
return -1;
if (strcmp(key, MAILBOX_ATTRIBUTE_SIEVE_DEFAULT) == 0)
return sieve_attribute_set_default(storage, svstorage, value);
if (strncmp(key, MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES,
strlen(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES)) != 0) {
mail_storage_set_error(storage, MAIL_ERROR_NOTFOUND,
"Nonexistent sieve attribute");
return -1;
}
scriptname = key + strlen(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES);
if (value->value != NULL) {
input = i_stream_create_from_data(value->value,
strlen(value->value));
save_ctx = sieve_storage_save_init(svstorage, scriptname, input);
i_stream_unref(&input);
} else if (value->value_stream != NULL) {
input = value->value_stream;
save_ctx = sieve_storage_save_init(svstorage, scriptname, input);
} else {
return sieve_attribute_unset_script(storage, svstorage, scriptname);
}
sieve_storage_save_set_mtime(save_ctx, value->last_change);
if (save_ctx == NULL) {
mail_storage_set_critical(storage,
"Failed to save sieve script '%s': %s", scriptname,
sieve_storage_get_last_error(svstorage, NULL));
return -1;
}
while (i_stream_read(input) > 0) {
if (sieve_storage_save_continue(save_ctx) < 0) {
mail_storage_set_critical(storage,
"Failed to save sieve script '%s': %s", scriptname,
sieve_storage_get_last_error(svstorage, NULL));
ret = -1;
break;
}
}
i_assert(input->eof);
if (input->stream_errno != 0) {
errno = input->stream_errno;
mail_storage_set_critical(storage,
"Saving sieve script: read(%s) failed: %m",
i_stream_get_name(input));
ret = -1;
}
if (ret == 0 && sieve_storage_save_finish(save_ctx) < 0) {
mail_storage_set_critical(storage,
"Failed to save sieve script '%s': %s", scriptname,
sieve_storage_get_last_error(svstorage, NULL));
ret = -1;
}
if (ret < 0)
sieve_storage_save_cancel(&save_ctx);
else if (sieve_storage_save_commit(&save_ctx) < 0) {
mail_storage_set_critical(storage,
"Failed to save sieve script '%s': %s", scriptname,
sieve_storage_get_last_error(svstorage, NULL));
ret = -1;
}
return ret;
}
static int
sieve_attribute_set(struct mailbox_transaction_context *t,
enum mail_attribute_type type, const char *key,
const struct mail_attribute_value *value)
{
struct mail_user *user = t->box->storage->user;
union mailbox_module_context *sbox = SIEVE_MAIL_CONTEXT(t->box);
time_t ts = value->last_change != 0 ? value->last_change : ioloop_time;
if (t->box->storage->user->dsyncing &&
type == MAIL_ATTRIBUTE_TYPE_PRIVATE &&
strncmp(key, MAILBOX_ATTRIBUTE_PREFIX_SIEVE,
strlen(MAILBOX_ATTRIBUTE_PREFIX_SIEVE)) == 0) {
if (sieve_attribute_set_sieve(t->box->storage, key, value) < 0)
return -1;
if (user->mail_debug)
i_debug("doveadm-sieve: Assigned value for key `%s'", key);
if (value->value != NULL || value->value_stream != NULL)
mail_index_attribute_set(t->itrans, TRUE, key, ts, 0);
else
mail_index_attribute_unset(t->itrans, TRUE, key, ts);
return 0;
}
return sbox->super.attribute_set(t, type, key, value);
}
static int
sieve_attribute_retrieve_script(struct mail_storage *storage,
struct sieve_storage *svstorage, struct sieve_script *script,
bool add_type_prefix,
struct mail_attribute_value *value_r, const char **errorstr_r)
{
char type = MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_SCRIPT;
struct istream *input, *inputs[3];
const struct stat *st;
enum sieve_error error;
if (script == NULL)
*errorstr_r = sieve_storage_get_last_error(svstorage, &error);
else if (sieve_script_get_stream(script, &input, &error) < 0)
sieve_script_unref(&script);
if (script == NULL) {
if (error == SIEVE_ERROR_NOT_FOUND) {
(void)sieve_storage_get_last_change(svstorage,
&value_r->last_change);
return 0;
}
*errorstr_r = sieve_storage_get_last_error(svstorage, &error);
return -1;
}
if (i_stream_stat(input, FALSE, &st) < 0) {
mail_storage_set_critical(storage,
"stat(%s) failed: %m", i_stream_get_name(input));
} else {
value_r->last_change = st->st_mtime;
}
if (!add_type_prefix) {
i_stream_ref(input);
value_r->value_stream = input;
} else {
inputs[0] = i_stream_create_from_data(&type, 1);
inputs[1] = input;
inputs[2] = NULL;
value_r->value_stream = i_stream_create_concat(inputs);
}
sieve_script_unref(&script);
return 1;
}
static int
sieve_attribute_get_active_script(struct mail_storage *storage,
struct sieve_storage *svstorage,
struct mail_attribute_value *value_r)
{
struct sieve_script *script;
const char *errstr;
int ret;
if ((ret=sieve_storage_active_script_is_no_link(svstorage)) <= 0) {
if (ret == 0 && sieve_storage_active_script_get_last_change
(svstorage, &value_r->last_change) < 0) {
ret = -1;
}
if (ret < 0)
mail_storage_set_internal_error(storage);
return ret;
}
if ((script=sieve_storage_active_script_get(svstorage)) == NULL)
return 0;
if ((ret=sieve_attribute_retrieve_script
(storage, svstorage, script, TRUE, value_r, &errstr)) < 0) {
mail_storage_set_critical(storage,
"Failed to access active sieve script: %s", errstr);
}
return ret;
}
static int
sieve_attribute_get_default(struct mail_storage *storage,
struct sieve_storage *svstorage,
struct mail_attribute_value *value_r)
{
const char *scriptname;
int ret;
ret = sieve_storage_active_script_get_name(svstorage, &scriptname);
if (ret == 0)
return sieve_attribute_get_active_script(storage, svstorage, value_r);
if (ret > 0) {
value_r->value = t_strdup_printf("%c%s",
MAILBOX_ATTRIBUTE_SIEVE_DEFAULT_LINK, scriptname);
if (sieve_storage_active_script_get_last_change
(svstorage, &value_r->last_change) < 0)
ret = -1;
}
if (ret < 0)
mail_storage_set_internal_error(storage);
return ret;
}
static int
sieve_attribute_get_sieve(struct mail_storage *storage, const char *key,
struct mail_attribute_value *value_r)
{
struct sieve_storage *svstorage;
struct sieve_script *script;
const char *scriptname, *errstr;
int ret;
if (mail_sieve_user_init(storage->user, &svstorage) < 0)
return -1;
if (strcmp(key, MAILBOX_ATTRIBUTE_SIEVE_DEFAULT) == 0)
return sieve_attribute_get_default(storage, svstorage, value_r);
if (strncmp(key, MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES,
strlen(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES)) != 0)
return 0;
if ((value_r->flags & MAIL_ATTRIBUTE_VALUE_FLAG_INT_STREAMS) == 0) {
mail_storage_set_error(storage, MAIL_ERROR_PARAMS,
"Sieve attributes are available only as streams");
return -1;
}
scriptname = key + strlen(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES);
script = sieve_storage_script_init(svstorage, scriptname);
if ((ret=sieve_attribute_retrieve_script
(storage, svstorage, script, FALSE, value_r, &errstr)) < 0) {
mail_storage_set_critical(storage,
"Failed to access sieve script '%s': %s",
scriptname, errstr);
}
return ret;
}
static int
sieve_attribute_get(struct mailbox_transaction_context *t,
enum mail_attribute_type type, const char *key,
struct mail_attribute_value *value_r)
{
union mailbox_module_context *sbox = SIEVE_MAIL_CONTEXT(t->box);
struct mail_user *user = t->box->storage->user;
int ret;
if (t->box->storage->user->dsyncing &&
type == MAIL_ATTRIBUTE_TYPE_PRIVATE &&
strncmp(key, MAILBOX_ATTRIBUTE_PREFIX_SIEVE,
strlen(MAILBOX_ATTRIBUTE_PREFIX_SIEVE)) == 0) {
ret = sieve_attribute_get_sieve(t->box->storage, key, value_r);
if (ret >= 0 && user->mail_debug) {
struct tm *tm = localtime(&value_r->last_change);
char str[256];
const char *timestamp = "";
if (strftime(str, sizeof(str),
" (last change: %Y-%m-%d %H:%M:%S)", tm) > 0)
timestamp = str;
if (ret > 0) {
i_debug("doveadm-sieve: Retrieved value for key `%s'%s",
key, timestamp);
} else {
i_debug("doveadm-sieve: Value missing for key `%s'%s",
key, timestamp);
}
}
return ret;
}
return sbox->super.attribute_get(t, type, key, value_r);
}
static int
sieve_attribute_iter_script_init(struct sieve_mailbox_attribute_iter *siter)
{
struct mail_user *user = siter->iter.box->storage->user;
struct sieve_storage *svstorage;
if (user->mail_debug)
i_debug("doveadm-sieve: Iterating Sieve mailbox attributes");
if (mail_sieve_user_init(user, &svstorage) < 0)
return -1;
siter->sieve_list = sieve_storage_list_init(svstorage);
if (siter->sieve_list == NULL) {
mail_storage_set_critical(siter->iter.box->storage,
"Failed to iterate sieve scripts: %s",
sieve_storage_get_last_error(svstorage, NULL));
return -1;
}
siter->name = str_new(default_pool, 128);
str_append(siter->name, MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES);
return 0;
}
static struct mailbox_attribute_iter *
sieve_attribute_iter_init(struct mailbox *box, enum mail_attribute_type type,
const char *prefix)
{
union mailbox_module_context *sbox = SIEVE_MAIL_CONTEXT(box);
struct sieve_mailbox_attribute_iter *siter;
siter = i_new(struct sieve_mailbox_attribute_iter, 1);
siter->iter.box = box;
siter->super = sbox->super.attribute_iter_init(box, type, prefix);
if (box->storage->user->dsyncing &&
type == MAIL_ATTRIBUTE_TYPE_PRIVATE &&
strncmp(prefix, MAILBOX_ATTRIBUTE_PREFIX_SIEVE,
strlen(prefix)) == 0) {
if (sieve_attribute_iter_script_init(siter) < 0)
siter->failed = TRUE;
}
return &siter->iter;
}
static const char *
sieve_attribute_iter_next_script(struct sieve_mailbox_attribute_iter *siter)
{
struct mail_user *user = siter->iter.box->storage->user;
struct sieve_mail_user *suser = SIEVE_USER_CONTEXT(user);
struct sieve_storage *svstorage = suser->sieve_storage;
const char *scriptname;
bool active;
int ret;
while ((scriptname = sieve_storage_list_next(siter->sieve_list, &active))
!= NULL) {
if (active)
siter->have_active = TRUE;
str_truncate(siter->name, strlen(MAILBOX_ATTRIBUTE_PREFIX_SIEVE_FILES));
str_append(siter->name, scriptname);
return str_c(siter->name);
}
if (sieve_storage_list_deinit(&siter->sieve_list) < 0) {
mail_storage_set_critical(siter->iter.box->storage,
"Failed to iterate sieve scripts: %s",
sieve_storage_get_last_error(svstorage, NULL));
siter->failed = TRUE;
return NULL;
}
if ((ret=sieve_storage_active_script_is_no_link(svstorage)) < 0) {
mail_storage_set_critical(siter->iter.box->storage,
"Failed to iterate sieve scripts: %s",
sieve_storage_get_last_error(svstorage, NULL));
return NULL;
}
if (ret > 0)
return MAILBOX_ATTRIBUTE_SIEVE_DEFAULT;
return siter->have_active ? MAILBOX_ATTRIBUTE_SIEVE_DEFAULT : NULL;
}
static const char *
sieve_attribute_iter_next(struct mailbox_attribute_iter *iter)
{
struct sieve_mailbox_attribute_iter *siter =
(struct sieve_mailbox_attribute_iter *)iter;
union mailbox_module_context *sbox = SIEVE_MAIL_CONTEXT(iter->box);
struct mail_user *user = iter->box->storage->user;
const char *key;
if (siter->sieve_list != NULL) {
if ((key = sieve_attribute_iter_next_script(siter)) != NULL) {
if (user->mail_debug) {
i_debug("doveadm-sieve: Iterating Sieve mailbox attribute: %s", key);
}
return key;
}
}
return sbox->super.attribute_iter_next(siter->super);
}
static int
sieve_attribute_iter_deinit(struct mailbox_attribute_iter *iter)
{
struct sieve_mailbox_attribute_iter *siter =
(struct sieve_mailbox_attribute_iter *)iter;
union mailbox_module_context *sbox = SIEVE_MAIL_CONTEXT(iter->box);
int ret = siter->failed ? -1 : 0;
if (siter->super != NULL) {
if (sbox->super.attribute_iter_deinit(siter->super) < 0)
ret = -1;
}
if (siter->sieve_list != NULL)
(void)sieve_storage_list_deinit(&siter->sieve_list);
if (siter->name != NULL)
str_free(&siter->name);
i_free(siter);
return ret;
}
static void sieve_mailbox_allocated(struct mailbox *box)
{
struct mailbox_vfuncs *v = box->vlast;
union mailbox_module_context *sbox;
if (!box->inbox_user)
return;
sbox = p_new(box->pool, union mailbox_module_context, 1);
sbox->super = *v;
box->vlast = &sbox->super;
v->attribute_set = sieve_attribute_set;
v->attribute_get = sieve_attribute_get;
v->attribute_iter_init = sieve_attribute_iter_init;
v->attribute_iter_next = sieve_attribute_iter_next;
v->attribute_iter_deinit = sieve_attribute_iter_deinit;
MODULE_CONTEXT_SET_SELF(box, sieve_storage_module, sbox);
}
static struct mail_storage_hooks doveadm_sieve_mail_storage_hooks = {
.mailbox_allocated = sieve_mailbox_allocated
};
void doveadm_sieve_plugin_init(struct module *module)
{
mail_storage_hooks_add_forced(module, &doveadm_sieve_mail_storage_hooks);
}
void doveadm_sieve_plugin_deinit(void)
{
}