quota-clone-plugin.c revision 5da08ab71623953f248b24a21d45b02555bbb24b
02c335c23bf5fa225a467c19f2c063fb0dc7b8c3Timo Sirainen/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen#include "lib.h"
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen#include "module-context.h"
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen#include "dict.h"
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen#include "mail-storage-private.h"
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen#include "quota.h"
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen#include "quota-clone-plugin.h"
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen#define DICT_QUOTA_CLONE_PATH DICT_PATH_PRIVATE"quota/"
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen#define DICT_QUOTA_CLONE_BYTES_PATH DICT_QUOTA_CLONE_PATH"storage"
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen#define DICT_QUOTA_CLONE_COUNT_PATH DICT_QUOTA_CLONE_PATH"messages"
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen#define QUOTA_CLONE_USER_CONTEXT(obj) \
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen MODULE_CONTEXT(obj, quota_clone_user_module)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen#define QUOTA_CLONE_CONTEXT(obj) \
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen MODULE_CONTEXT(obj, quota_clone_storage_module)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenstatic MODULE_CONTEXT_DEFINE_INIT(quota_clone_user_module,
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen &mail_user_module_register);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenstatic MODULE_CONTEXT_DEFINE_INIT(quota_clone_storage_module,
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen &mail_storage_module_register);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenstruct quota_clone_user {
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen union mail_user_module_context module_ctx;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct dict *dict;
5da08ab71623953f248b24a21d45b02555bbb24bTimo Sirainen bool quota_flushing;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen};
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenstruct quota_clone_mailbox {
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen union mailbox_module_context module_ctx;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen bool quota_changed;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen};
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenstatic void quota_clone_flush(struct mailbox *box)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen{
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct quota_clone_mailbox *qbox = QUOTA_CLONE_CONTEXT(box);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct quota_clone_user *quser =
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen QUOTA_CLONE_USER_CONTEXT(box->storage->user);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct dict_transaction_context *trans;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct quota_root_iter *iter;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct quota_root *root;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen uint64_t value, limit;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen int ret;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen /* we'll clone the first quota root */
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen iter = quota_root_iter_init(box);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen root = quota_root_iter_next(iter);
f39a06c378f6ea80a4ae9d257f0d79221a945a57Timo Sirainen quota_root_iter_deinit(&iter);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen if (root == NULL) {
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen /* no quota roots defined for this mailbox - ignore */
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen qbox->quota_changed = FALSE;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen return;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen }
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen trans = dict_transaction_begin(quser->dict);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen /* update bytes */
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen ret = quota_get_resource(root, "", QUOTA_NAME_STORAGE_BYTES,
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen &value, &limit);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen if (ret < 0)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen i_error("quota_clone_plugin: Failed to lookup current quota bytes");
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen else {
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen dict_set(trans, DICT_QUOTA_CLONE_BYTES_PATH,
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen t_strdup_printf("%llu", (unsigned long long)value));
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen }
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen /* update messages */
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen ret = quota_get_resource(root, "", QUOTA_NAME_MESSAGES,
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen &value, &limit);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen if (ret < 0)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen i_error("quota_clone_plugin: Failed to lookup current quota count");
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen else {
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen dict_set(trans, DICT_QUOTA_CLONE_COUNT_PATH,
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen t_strdup_printf("%llu", (unsigned long long)value));
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen }
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen if (dict_transaction_commit(&trans) < 0)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen i_error("quota_clone_plugin: Failed to commit dict update");
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen else
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen qbox->quota_changed = FALSE;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen}
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenstatic int quota_clone_save_finish(struct mail_save_context *ctx)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen{
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct quota_clone_mailbox *qbox =
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen QUOTA_CLONE_CONTEXT(ctx->transaction->box);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen qbox->quota_changed = TRUE;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen return qbox->module_ctx.super.save_finish(ctx);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen}
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenstatic int
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenquota_clone_copy(struct mail_save_context *ctx, struct mail *mail)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen{
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct quota_clone_mailbox *qbox =
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen QUOTA_CLONE_CONTEXT(ctx->transaction->box);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen qbox->quota_changed = TRUE;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen return qbox->module_ctx.super.copy(ctx, mail);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen}
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenstatic void
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenquota_clone_mailbox_sync_notify(struct mailbox *box, uint32_t uid,
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen enum mailbox_sync_type sync_type)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen{
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct quota_clone_mailbox *qbox = QUOTA_CLONE_CONTEXT(box);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen if (qbox->module_ctx.super.sync_notify != NULL)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen qbox->module_ctx.super.sync_notify(box, uid, sync_type);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen if (sync_type == MAILBOX_SYNC_TYPE_EXPUNGE)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen qbox->quota_changed = TRUE;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen}
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenstatic void quota_clone_mailbox_close(struct mailbox *box)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen{
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct quota_clone_mailbox *qbox = QUOTA_CLONE_CONTEXT(box);
5da08ab71623953f248b24a21d45b02555bbb24bTimo Sirainen struct quota_clone_user *quser =
5da08ab71623953f248b24a21d45b02555bbb24bTimo Sirainen QUOTA_CLONE_USER_CONTEXT(box->storage->user);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen qbox->module_ctx.super.close(box);
8705e45564a2e87d32bd825e0e997a8177846f77Timo Sirainen
5da08ab71623953f248b24a21d45b02555bbb24bTimo Sirainen if (quser->quota_flushing) {
8705e45564a2e87d32bd825e0e997a8177846f77Timo Sirainen /* recursing back from quota recalculation */
8705e45564a2e87d32bd825e0e997a8177846f77Timo Sirainen } else if (qbox->quota_changed) {
5da08ab71623953f248b24a21d45b02555bbb24bTimo Sirainen quser->quota_flushing = TRUE;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen quota_clone_flush(box);
5da08ab71623953f248b24a21d45b02555bbb24bTimo Sirainen quser->quota_flushing = FALSE;
8705e45564a2e87d32bd825e0e997a8177846f77Timo Sirainen }
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen}
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenstatic void quota_clone_mailbox_allocated(struct mailbox *box)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen{
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct quota_clone_user *quser =
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen QUOTA_CLONE_USER_CONTEXT(box->storage->user);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct mailbox_vfuncs *v = box->vlast;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct quota_clone_mailbox *qbox;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen if (quser == NULL)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen return;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen qbox = p_new(box->pool, struct quota_clone_mailbox, 1);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen qbox->module_ctx.super = *v;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen box->vlast = &qbox->module_ctx.super;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen v->save_finish = quota_clone_save_finish;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen v->copy = quota_clone_copy;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen v->sync_notify = quota_clone_mailbox_sync_notify;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen v->close = quota_clone_mailbox_close;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen MODULE_CONTEXT_SET(box, quota_clone_storage_module, qbox);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen}
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenstatic void quota_clone_mail_user_deinit(struct mail_user *user)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen{
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct quota_clone_user *quser = QUOTA_CLONE_USER_CONTEXT(user);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen dict_deinit(&quser->dict);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen quser->module_ctx.super.deinit(user);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen}
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenstatic void quota_clone_mail_user_created(struct mail_user *user)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen{
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct quota_clone_user *quser;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct mail_user_vfuncs *v = user->vlast;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct dict_settings dict_set;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen struct dict *dict;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen const char *uri, *error;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen uri = mail_user_plugin_getenv(user, "quota_clone_dict");
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen if (uri == NULL || uri[0] == '\0') {
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen i_error("The quota_clone_dict setting is missing from configuration");
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen return;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen }
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen memset(&dict_set, 0, sizeof(dict_set));
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen dict_set.username = user->username;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen dict_set.base_dir = user->set->base_dir;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen (void)mail_user_get_home(user, &dict_set.home_dir);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen if (dict_init_full(uri, &dict_set, &dict, &error) < 0) {
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen i_error("quota_clone_dict: Failed to initialize '%s': %s",
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen uri, error);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen return;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen }
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen quser = p_new(user->pool, struct quota_clone_user, 1);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen quser->module_ctx.super = *v;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen user->vlast = &quser->module_ctx.super;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen v->deinit = quota_clone_mail_user_deinit;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen quser->dict = dict;
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen MODULE_CONTEXT_SET(user, quota_clone_user_module, quser);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen}
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenstatic struct mail_storage_hooks quota_clone_mail_storage_hooks = {
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen .mailbox_allocated = quota_clone_mailbox_allocated,
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen .mail_user_created = quota_clone_mail_user_created
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen};
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenvoid quota_clone_plugin_init(struct module *module ATTR_UNUSED)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen{
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen mail_storage_hooks_add(module, &quota_clone_mail_storage_hooks);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen}
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenvoid quota_clone_plugin_deinit(void)
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen{
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen mail_storage_hooks_remove(&quota_clone_mail_storage_hooks);
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen}
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainen
1f2f38f518ea14d1042c98ab039e6df053f7b285Timo Sirainenconst char *quota_clone_plugin_dependencies[] = { "quota", NULL };