summaryrefslogtreecommitdiffstats
path: root/src/plugins/mailbox-alias/mailbox-alias-plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/mailbox-alias/mailbox-alias-plugin.c')
-rw-r--r--src/plugins/mailbox-alias/mailbox-alias-plugin.c356
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);
+}