diff options
Diffstat (limited to '')
-rw-r--r-- | src/plugins/mailbox-alias/mailbox-alias-plugin.c | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/src/plugins/mailbox-alias/mailbox-alias-plugin.c b/src/plugins/mailbox-alias/mailbox-alias-plugin.c new file mode 100644 index 0000000..ebd433c --- /dev/null +++ b/src/plugins/mailbox-alias/mailbox-alias-plugin.c @@ -0,0 +1,356 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "mail-storage-hooks.h" +#include "mail-storage-private.h" +#include "mailbox-list-private.h" +#include "mailbox-alias-plugin.h" + +#define MAILBOX_ALIAS_USER_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mailbox_alias_user_module) +#define MAILBOX_ALIAS_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mailbox_alias_storage_module) +#define MAILBOX_ALIAS_LIST_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, mailbox_alias_mailbox_list_module) + +struct mailbox_alias { + const char *old_vname, *new_vname; +}; + +struct mailbox_alias_user { + union mail_user_module_context module_ctx; + + ARRAY(struct mailbox_alias) aliases; +}; + +struct mailbox_alias_mailbox_list { + union mailbox_list_module_context module_ctx; +}; + +struct mailbox_alias_mailbox { + union mailbox_module_context module_ctx; +}; + +enum mailbox_symlink_existence { + MAILBOX_SYMLINK_EXISTENCE_NONEXISTENT, + MAILBOX_SYMLINK_EXISTENCE_SYMLINK, + MAILBOX_SYMLINK_EXISTENCE_NOT_SYMLINK +}; + +static MODULE_CONTEXT_DEFINE_INIT(mailbox_alias_user_module, + &mail_user_module_register); +static MODULE_CONTEXT_DEFINE_INIT(mailbox_alias_storage_module, + &mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(mailbox_alias_mailbox_list_module, + &mailbox_list_module_register); + +const char *mailbox_alias_plugin_version = DOVECOT_ABI_VERSION; + +static const char * +mailbox_alias_find_new(struct mail_user *user, const char *new_vname) +{ + struct mailbox_alias_user *auser = MAILBOX_ALIAS_USER_CONTEXT(user); + const struct mailbox_alias *alias; + + array_foreach(&auser->aliases, alias) { + if (strcmp(alias->new_vname, new_vname) == 0) + return alias->old_vname; + } + return NULL; +} + +static int mailbox_symlink_exists(struct mailbox_list *list, const char *vname, + enum mailbox_symlink_existence *existence_r) +{ + struct mailbox_alias_mailbox_list *alist = + MAILBOX_ALIAS_LIST_CONTEXT(list); + struct stat st; + const char *symlink_name, *symlink_path; + int ret; + + symlink_name = alist->module_ctx.super.get_storage_name(list, vname); + ret = mailbox_list_get_path(list, symlink_name, + MAILBOX_LIST_PATH_TYPE_DIR, &symlink_path); + if (ret < 0) + return -1; + i_assert(ret > 0); + + if (lstat(symlink_path, &st) < 0) { + if (errno == ENOENT) { + *existence_r = MAILBOX_SYMLINK_EXISTENCE_NONEXISTENT; + return 0; + } + mailbox_list_set_critical(list, + "lstat(%s) failed: %m", symlink_path); + return -1; + } + if (S_ISLNK(st.st_mode)) + *existence_r = MAILBOX_SYMLINK_EXISTENCE_SYMLINK; + else + *existence_r = MAILBOX_SYMLINK_EXISTENCE_NOT_SYMLINK; + return 0; +} + +static int mailbox_is_alias_symlink(struct mailbox *box) +{ + enum mailbox_symlink_existence existence; + + if (mailbox_alias_find_new(box->storage->user, box->vname) == NULL) + return 0; + if (mailbox_symlink_exists(box->list, box->vname, &existence) < 0) { + mail_storage_copy_list_error(box->storage, box->list); + return -1; + } + return existence == MAILBOX_SYMLINK_EXISTENCE_SYMLINK ? 1 : 0; +} + +static int +mailbox_has_aliases(struct mailbox_list *list, const char *old_vname) +{ + struct mailbox_alias_user *auser = + MAILBOX_ALIAS_USER_CONTEXT(list->ns->user); + const struct mailbox_alias *alias; + enum mailbox_symlink_existence existence; + int ret = 0; + + array_foreach(&auser->aliases, alias) { + if (strcmp(alias->old_vname, old_vname) == 0) { + if (mailbox_symlink_exists(list, alias->new_vname, + &existence) < 0) + ret = -1; + else if (existence == MAILBOX_SYMLINK_EXISTENCE_SYMLINK) + return 1; + } + } + return ret; +} + +static int +mailbox_alias_create_symlink(struct mailbox *box, + const char *old_name, const char *new_name) +{ + const char *old_path, *new_path, *fname; + int ret; + + ret = mailbox_list_get_path(box->list, old_name, + MAILBOX_LIST_PATH_TYPE_DIR, &old_path); + if (ret > 0) { + ret = mailbox_list_get_path(box->list, new_name, + MAILBOX_LIST_PATH_TYPE_DIR, + &new_path); + } + if (ret < 0) + return -1; + if (ret == 0) { + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Mailbox aliases not supported by storage"); + return -1; + } + fname = strrchr(old_path, '/'); + i_assert(fname != NULL); + fname++; + i_assert(strncmp(new_path, old_path, fname-old_path) == 0); + + if (symlink(fname, new_path) < 0) { + if (errno == EEXIST) { + mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS, + "Mailbox already exists"); + return -1; + } + mailbox_set_critical(box, + "symlink(%s, %s) failed: %m", fname, new_path); + return -1; + } + return 0; +} + +static const char * +mailbox_alias_get_storage_name(struct mailbox_list *list, const char *vname) +{ + struct mailbox_alias_mailbox_list *alist = + MAILBOX_ALIAS_LIST_CONTEXT(list); + const char *old_vname; + enum mailbox_symlink_existence existence; + + /* access the old mailbox so that e.g. full text search won't + index the mailbox twice. this also means that deletion must be + careful to delete the symlink, box->name. */ + old_vname = mailbox_alias_find_new(list->ns->user, vname); + if (old_vname != NULL && + mailbox_symlink_exists(list, vname, &existence) == 0 && + existence != MAILBOX_SYMLINK_EXISTENCE_NOT_SYMLINK) + vname = old_vname; + + return alist->module_ctx.super.get_storage_name(list, vname); +} + +static int +mailbox_alias_create(struct mailbox *box, const struct mailbox_update *update, + bool directory) +{ + struct mailbox_alias_mailbox *abox = MAILBOX_ALIAS_CONTEXT(box); + struct mailbox_alias_mailbox_list *alist = + MAILBOX_ALIAS_LIST_CONTEXT(box->list); + const char *symlink_name; + int ret; + + ret = abox->module_ctx.super.create_box(box, update, directory); + if (mailbox_alias_find_new(box->storage->user, box->vname) == NULL) + return ret; + if (ret < 0 && mailbox_get_last_mail_error(box) != MAIL_ERROR_EXISTS) + return ret; + + /* all the code so far has actually only created the original + mailbox. now we'll create the symlink if it's missing. */ + symlink_name = alist->module_ctx.super. + get_storage_name(box->list, box->vname); + return mailbox_alias_create_symlink(box, box->name, symlink_name); +} + +static int mailbox_alias_delete(struct mailbox *box) +{ + struct mailbox_alias_mailbox *abox = MAILBOX_ALIAS_CONTEXT(box); + struct mailbox_alias_mailbox_list *alist = + MAILBOX_ALIAS_LIST_CONTEXT(box->list); + const char *symlink_name; + int ret; + + ret = mailbox_has_aliases(box->list, box->vname); + if (ret < 0) + return -1; + if (ret > 0) { + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't delete mailbox while it has aliases"); + return -1; + } + + if ((ret = mailbox_is_alias_symlink(box)) < 0) + return -1; + if (ret > 0) { + /* we're deleting an alias mailbox. we'll need to handle this + explicitly since box->name points to the original mailbox */ + symlink_name = alist->module_ctx.super. + get_storage_name(box->list, box->vname); + if (mailbox_list_delete_symlink(box->list, symlink_name) < 0) { + mail_storage_copy_list_error(box->storage, box->list); + return -1; + } + return 0; + } + + return abox->module_ctx.super.delete_box(box); +} + +static int mailbox_alias_rename(struct mailbox *src, struct mailbox *dest) +{ + struct mailbox_alias_mailbox *abox = MAILBOX_ALIAS_CONTEXT(src); + int ret; + + if ((ret = mailbox_is_alias_symlink(src)) < 0) + return -1; + else if (ret > 0) { + mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't rename alias mailboxes"); + return -1; + } + if ((ret = mailbox_is_alias_symlink(dest)) < 0) + return -1; + else if (ret > 0) { + mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't rename to mailbox alias"); + return -1; + } + ret = mailbox_has_aliases(src->list, src->vname); + if (ret < 0) + return -1; + if (ret > 0) { + mail_storage_set_error(src->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't rename mailbox while it has aliases"); + return -1; + } + + return abox->module_ctx.super.rename_box(src, dest); +} + +static void mailbox_alias_mail_user_created(struct mail_user *user) +{ + struct mail_user_vfuncs *v = user->vlast; + struct mailbox_alias_user *auser; + struct mailbox_alias *alias; + string_t *oldkey, *newkey; + const char *old_vname, *new_vname; + unsigned int i; + + auser = p_new(user->pool, struct mailbox_alias_user, 1); + auser->module_ctx.super = *v; + user->vlast = &auser->module_ctx.super; + + p_array_init(&auser->aliases, user->pool, 8); + + oldkey = t_str_new(32); + newkey = t_str_new(32); + str_append(oldkey, "mailbox_alias_old"); + str_append(newkey, "mailbox_alias_new"); + for (i = 2;; i++) { + old_vname = mail_user_plugin_getenv(user, str_c(oldkey)); + new_vname = mail_user_plugin_getenv(user, str_c(newkey)); + if (old_vname == NULL || new_vname == NULL) + break; + + alias = array_append_space(&auser->aliases); + alias->old_vname = old_vname; + alias->new_vname = new_vname; + + str_truncate(oldkey, 0); + str_truncate(newkey, 0); + str_printfa(oldkey, "mailbox_alias_old%u", i); + str_printfa(newkey, "mailbox_alias_new%u", i); + } + + MODULE_CONTEXT_SET(user, mailbox_alias_user_module, auser); +} + +static void mailbox_alias_mailbox_list_created(struct mailbox_list *list) +{ + struct mailbox_list_vfuncs *v = list->vlast; + struct mailbox_alias_mailbox_list *alist; + + alist = p_new(list->pool, struct mailbox_alias_mailbox_list, 1); + alist->module_ctx.super = *v; + list->vlast = &alist->module_ctx.super; + + v->get_storage_name = mailbox_alias_get_storage_name; + MODULE_CONTEXT_SET(list, mailbox_alias_mailbox_list_module, alist); +} + +static void mailbox_alias_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + struct mailbox_alias_mailbox *abox; + + abox = p_new(box->pool, struct mailbox_alias_mailbox, 1); + abox->module_ctx.super = *v; + box->vlast = &abox->module_ctx.super; + + v->create_box = mailbox_alias_create; + v->delete_box = mailbox_alias_delete; + v->rename_box = mailbox_alias_rename; + MODULE_CONTEXT_SET(box, mailbox_alias_storage_module, abox); +} + +static struct mail_storage_hooks mailbox_alias_mail_storage_hooks = { + .mail_user_created = mailbox_alias_mail_user_created, + .mailbox_list_created = mailbox_alias_mailbox_list_created, + .mailbox_allocated = mailbox_alias_mailbox_allocated +}; + +void mailbox_alias_plugin_init(struct module *module) +{ + mail_storage_hooks_add(module, &mailbox_alias_mail_storage_hooks); +} + +void mailbox_alias_plugin_deinit(void) +{ + mail_storage_hooks_remove(&mailbox_alias_mail_storage_hooks); +} |