summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/list/mailbox-list-maildir.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/list/mailbox-list-maildir.c')
-rw-r--r--src/lib-storage/list/mailbox-list-maildir.c546
1 files changed, 546 insertions, 0 deletions
diff --git a/src/lib-storage/list/mailbox-list-maildir.c b/src/lib-storage/list/mailbox-list-maildir.c
new file mode 100644
index 0000000..265f3d0
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-maildir.c
@@ -0,0 +1,546 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hostpid.h"
+#include "eacces-error.h"
+#include "mkdir-parents.h"
+#include "str.h"
+#include "subscription-file.h"
+#include "mailbox-list-subscriptions.h"
+#include "mailbox-list-delete.h"
+#include "mailbox-list-maildir.h"
+
+#include <stdio.h>
+#include <sys/stat.h>
+
+#define MAILDIR_GLOBAL_TEMP_PREFIX "temp."
+#define IMAPDIR_GLOBAL_TEMP_PREFIX ".temp."
+
+extern struct mailbox_list maildir_mailbox_list;
+extern struct mailbox_list imapdir_mailbox_list;
+
+static struct mailbox_list *maildir_list_alloc(void)
+{
+ struct maildir_mailbox_list *list;
+ pool_t pool;
+
+ pool = pool_alloconly_create("maildir++ list", 2048);
+ list = p_new(pool, struct maildir_mailbox_list, 1);
+ list->list = maildir_mailbox_list;
+ list->list.pool = pool;
+ list->sep = '.';
+
+ list->global_temp_prefix = MAILDIR_GLOBAL_TEMP_PREFIX;
+ list->temp_prefix = p_strconcat(pool, list->global_temp_prefix,
+ my_hostname, ".", my_pid, ".", NULL);
+ return &list->list;
+}
+
+static struct mailbox_list *imapdir_list_alloc(void)
+{
+ struct maildir_mailbox_list *list;
+ pool_t pool;
+
+ pool = pool_alloconly_create("imapdir list", 1024);
+ list = p_new(pool, struct maildir_mailbox_list, 1);
+ list->list = imapdir_mailbox_list;
+ list->list.pool = pool;
+ list->sep = '.';
+
+ list->global_temp_prefix = IMAPDIR_GLOBAL_TEMP_PREFIX;
+ list->temp_prefix = p_strconcat(pool, list->global_temp_prefix,
+ my_hostname, ".", my_pid, ".", NULL);
+ return &list->list;
+}
+
+static void maildir_list_deinit(struct mailbox_list *_list)
+{
+ struct maildir_mailbox_list *list =
+ (struct maildir_mailbox_list *)_list;
+
+ pool_unref(&list->list.pool);
+}
+
+static const char *
+maildir_list_get_dirname_path(struct mailbox_list *list, const char *dir,
+ const char *name)
+{
+ if (*name == '\0')
+ return dir;
+ else if (strcmp(list->name, imapdir_mailbox_list.name) == 0)
+ return t_strdup_printf("%s/%s", dir, name);
+
+ return t_strdup_printf("%s/%c%s", dir,
+ mailbox_list_get_hierarchy_sep(list), name);
+}
+
+static const char *
+maildir_list_get_absolute_path(struct mailbox_list *list, const char *name)
+{
+ const char *p;
+
+ if (!mailbox_list_try_get_absolute_path(list, &name)) {
+ /* fallback to using as ~name */
+ return name;
+ }
+
+ p = strrchr(name, '/');
+ if (p == NULL)
+ return name;
+ return maildir_list_get_dirname_path(list, t_strdup_until(name, p),
+ p+1);
+}
+
+static char maildir_list_get_hierarchy_sep(struct mailbox_list *_list)
+{
+ struct maildir_mailbox_list *list =
+ (struct maildir_mailbox_list *)_list;
+
+ return list->sep;
+}
+
+static int
+maildir_list_get_path(struct mailbox_list *_list, const char *name,
+ enum mailbox_list_path_type type, const char **path_r)
+{
+ const char *root_dir;
+
+ if (name == NULL) {
+ /* return root directories */
+ return mailbox_list_set_get_root_path(&_list->set, type,
+ path_r) ? 1 : 0;
+ }
+
+ if (_list->mail_set->mail_full_filesystem_access &&
+ (*name == '/' || *name == '~')) {
+ *path_r = maildir_list_get_absolute_path(_list, name);
+ return 1;
+ }
+
+ root_dir = _list->set.root_dir;
+ switch (type) {
+ case MAILBOX_LIST_PATH_TYPE_DIR:
+ case MAILBOX_LIST_PATH_TYPE_MAILBOX:
+ break;
+ case MAILBOX_LIST_PATH_TYPE_ALT_DIR:
+ case MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX:
+ if (_list->set.alt_dir == NULL)
+ return 0;
+ root_dir = _list->set.alt_dir;
+ break;
+ case MAILBOX_LIST_PATH_TYPE_CONTROL:
+ if (_list->set.control_dir != NULL) {
+ *path_r = maildir_list_get_dirname_path(_list,
+ _list->set.control_dir, name);
+ return 1;
+ }
+ break;
+ case MAILBOX_LIST_PATH_TYPE_INDEX_CACHE:
+ if (_list->set.index_cache_dir != NULL) {
+ *path_r = maildir_list_get_dirname_path(_list,
+ _list->set.index_cache_dir, name);
+ return 1;
+ }
+ /* fall through */
+ case MAILBOX_LIST_PATH_TYPE_INDEX:
+ if (_list->set.index_dir != NULL) {
+ if (*_list->set.index_dir == '\0')
+ return 0;
+ *path_r = maildir_list_get_dirname_path(_list,
+ _list->set.index_dir, name);
+ return 1;
+ }
+ break;
+ case MAILBOX_LIST_PATH_TYPE_INDEX_PRIVATE:
+ if (_list->set.index_pvt_dir == NULL)
+ return 0;
+ *path_r = maildir_list_get_dirname_path(_list,
+ _list->set.index_pvt_dir, name);
+ return 1;
+ case MAILBOX_LIST_PATH_TYPE_LIST_INDEX:
+ i_unreached();
+ }
+
+ if (type == MAILBOX_LIST_PATH_TYPE_ALT_DIR ||
+ type == MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX) {
+ /* don't use inbox_path */
+ } else if (strcmp(name, "INBOX") == 0 && _list->set.inbox_path != NULL) {
+ *path_r = _list->set.inbox_path;
+ return 1;
+ }
+
+ *path_r = maildir_list_get_dirname_path(_list, root_dir, name);
+ return 1;
+}
+
+static const char *
+maildir_list_get_temp_prefix(struct mailbox_list *_list, bool global)
+{
+ struct maildir_mailbox_list *list =
+ (struct maildir_mailbox_list *)_list;
+
+ return global ? list->global_temp_prefix : list->temp_prefix;
+}
+
+static int maildir_list_set_subscribed(struct mailbox_list *_list,
+ const char *name, bool set)
+{
+ struct maildir_mailbox_list *list =
+ (struct maildir_mailbox_list *)_list;
+ const char *path;
+
+ if (_list->set.subscription_fname == NULL) {
+ mailbox_list_set_error(_list, MAIL_ERROR_NOTPOSSIBLE,
+ "Subscriptions not supported");
+ return -1;
+ }
+
+ path = t_strconcat(_list->set.control_dir != NULL ?
+ _list->set.control_dir : _list->set.root_dir,
+ "/", _list->set.subscription_fname, NULL);
+
+ return subsfile_set_subscribed(_list, path, list->temp_prefix,
+ name, set);
+}
+
+static const char *
+mailbox_list_maildir_get_trash_dir(struct mailbox_list *_list)
+{
+ struct maildir_mailbox_list *list =
+ (struct maildir_mailbox_list *)_list;
+ const char *root_dir;
+
+ root_dir = mailbox_list_get_root_forced(_list, MAILBOX_LIST_PATH_TYPE_DIR);
+ return t_strdup_printf("%s/%c%c"MAILBOX_LIST_MAILDIR_TRASH_DIR_NAME,
+ root_dir, list->sep, list->sep);
+}
+
+static int
+maildir_list_delete_maildir(struct mailbox_list *list, const char *name)
+{
+ const char *path, *trash_dir;
+ int ret = 0;
+
+ trash_dir = mailbox_list_maildir_get_trash_dir(list);
+ ret = mailbox_list_delete_maildir_via_trash(list, name, trash_dir);
+ if (ret < 0)
+ return -1;
+
+ if (ret == 0) {
+ /* we could actually use just unlink_directory()
+ but error handling is easier this way :) */
+ if (mailbox_list_get_path(list, name,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &path) <= 0)
+ i_unreached();
+ if (mailbox_list_delete_mailbox_nonrecursive(list, name,
+ path, TRUE) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static int
+maildir_list_delete_mailbox(struct mailbox_list *list, const char *name)
+{
+ const char *path;
+ int ret;
+
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) {
+ ret = mailbox_list_get_path(list, name,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &path);
+ if (ret < 0)
+ return -1;
+ i_assert(ret > 0);
+ ret = mailbox_list_delete_mailbox_file(list, name, path);
+ } else {
+ ret = maildir_list_delete_maildir(list, name);
+ }
+
+ i_assert(ret <= 0);
+ return mailbox_list_delete_finish_ret(list, name, ret == 0);
+}
+
+static int maildir_list_delete_dir(struct mailbox_list *list, const char *name)
+{
+ const char *path;
+ struct stat st;
+
+ /* with maildir++ there aren't any non-selectable mailboxes.
+ we'll always fail. */
+ if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR,
+ &path) <= 0)
+ i_unreached();
+ if (stat(path, &st) == 0) {
+ mailbox_list_set_error(list, MAIL_ERROR_EXISTS,
+ "Mailbox exists");
+ } else if (errno == ENOENT || errno == ENOTDIR) {
+ mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(list, name));
+ } else {
+ mailbox_list_set_critical(list, "stat(%s) failed: %m", path);
+ }
+ return -1;
+}
+
+static int rename_dir(struct mailbox_list *oldlist, const char *oldname,
+ struct mailbox_list *newlist, const char *newname,
+ enum mailbox_list_path_type type)
+{
+ const char *oldpath, *newpath;
+
+ if (mailbox_list_get_path(oldlist, oldname, type, &oldpath) <= 0 ||
+ mailbox_list_get_path(newlist, newname, type, &newpath) <= 0)
+ return 0;
+
+ if (strcmp(oldpath, newpath) == 0)
+ return 0;
+
+ if (rename(oldpath, newpath) < 0 && errno != ENOENT) {
+ mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m",
+ oldpath, newpath);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+maildir_rename_children(struct mailbox_list *oldlist, const char *oldname,
+ struct mailbox_list *newlist, const char *newname)
+{
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+ ARRAY(const char *) names_arr;
+ const char *pattern, *oldpath, *newpath, *old_childname, *new_childname;
+ const char *const *names, *old_vname, *new_vname;
+ unsigned int i, count;
+ size_t old_vnamelen;
+ pool_t pool;
+ char old_ns_sep;
+ int ret;
+
+ ret = 0;
+
+ /* first get the list of the children and save them to memory, because
+ we can't rely on readdir() not skipping files while the directory
+ is being modified. this doesn't protect against modifications by
+ other processes though. */
+ pool = pool_alloconly_create("Maildir++ children list", 1024);
+ i_array_init(&names_arr, 64);
+
+ old_vname = mailbox_list_get_vname(oldlist, oldname);
+ old_vnamelen = strlen(old_vname);
+
+ new_vname = mailbox_list_get_vname(newlist, newname);
+
+ old_ns_sep = mail_namespace_get_sep(oldlist->ns);
+ pattern = t_strdup_printf("%s%c*", old_vname, old_ns_sep);
+ iter = mailbox_list_iter_init(oldlist, pattern,
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS |
+ MAILBOX_LIST_ITER_RAW_LIST);
+ while ((info = mailbox_list_iter_next(iter)) != NULL) {
+ const char *name;
+
+ /* verify that the prefix matches, otherwise we could have
+ problems with mailbox names containing '%' and '*' chars */
+ if (strncmp(info->vname, old_vname, old_vnamelen) == 0 &&
+ info->vname[old_vnamelen] == old_ns_sep) {
+ name = p_strdup(pool, info->vname + old_vnamelen);
+ array_push_back(&names_arr, &name);
+ }
+ }
+ if (mailbox_list_iter_deinit(&iter) < 0) {
+ ret = -1;
+ names = NULL; count = 0;
+ } else {
+ names = array_get(&names_arr, &count);
+ }
+
+ for (i = 0; i < count; i++) {
+ old_childname = mailbox_list_get_storage_name(oldlist,
+ t_strconcat(old_vname, names[i], NULL));
+ if (strcmp(old_childname, new_vname) == 0) {
+ /* When doing RENAME "a" "a.b" we see "a.b" here.
+ We don't want to rename it anymore to "a.b.b". */
+ continue;
+ }
+
+ new_childname = mailbox_list_get_storage_name(newlist,
+ t_strconcat(new_vname, names[i], NULL));
+ if (mailbox_list_get_path(oldlist, old_childname,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &oldpath) <= 0 ||
+ mailbox_list_get_path(newlist, new_childname,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &newpath) <= 0)
+ i_unreached();
+
+ /* FIXME: it's possible to merge two mailboxes if either one of
+ them doesn't have existing root mailbox. We could check this
+ but I'm not sure if it's worth it. It could be even
+ considered as a feature.
+
+ Anyway, the bug with merging is that if both mailboxes have
+ identically named child mailbox they conflict. Just ignore
+ those and leave them under the old mailbox. */
+ if (rename(oldpath, newpath) == 0 || EDESTDIREXISTS(errno))
+ ret = 1;
+ else {
+ mailbox_list_set_critical(oldlist,
+ "rename(%s, %s) failed: %m", oldpath, newpath);
+ ret = -1;
+ break;
+ }
+
+ (void)rename_dir(oldlist, old_childname, newlist, new_childname,
+ MAILBOX_LIST_PATH_TYPE_CONTROL);
+ (void)rename_dir(oldlist, old_childname, newlist, new_childname,
+ MAILBOX_LIST_PATH_TYPE_INDEX);
+ (void)rename_dir(oldlist, old_childname, newlist, new_childname,
+ MAILBOX_LIST_PATH_TYPE_INDEX_CACHE);
+ }
+ array_free(&names_arr);
+ pool_unref(&pool);
+
+ return ret;
+}
+
+static int
+maildir_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname,
+ struct mailbox_list *newlist, const char *newname)
+{
+ const char *oldpath, *newpath, *root_path;
+ int ret;
+ bool found;
+
+ /* NOTE: it's possible to rename a nonexistent mailbox which has
+ children. In that case we should ignore the rename() error. */
+ if (mailbox_list_get_path(oldlist, oldname,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX, &oldpath) <= 0 ||
+ mailbox_list_get_path(newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX, &newpath) <= 0)
+ i_unreached();
+
+ root_path = mailbox_list_get_root_forced(oldlist,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ if (strcmp(oldpath, root_path) == 0) {
+ /* most likely INBOX */
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ t_strdup_printf("Renaming %s isn't supported.",
+ oldname));
+ return -1;
+ }
+
+ /* if we're renaming under another mailbox, require its permissions
+ to be same as ours. */
+ if (strchr(newname, mailbox_list_get_hierarchy_sep(newlist)) != NULL) {
+ struct mailbox_permissions old_perm, new_perm;
+
+ mailbox_list_get_permissions(oldlist, oldname, &old_perm);
+ mailbox_list_get_permissions(newlist, newname, &new_perm);
+
+ if ((new_perm.file_create_mode != old_perm.file_create_mode ||
+ new_perm.dir_create_mode != old_perm.dir_create_mode ||
+ new_perm.file_create_gid != old_perm.file_create_gid)) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ "Renaming not supported across conflicting "
+ "directory permissions");
+ return -1;
+ }
+ }
+
+
+ ret = rename(oldpath, newpath);
+ if (ret == 0 || errno == ENOENT) {
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_CONTROL);
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_INDEX);
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_INDEX_CACHE);
+
+ found = ret == 0;
+ T_BEGIN {
+ ret = maildir_rename_children(oldlist, oldname,
+ newlist, newname);
+ } T_END;
+ if (ret < 0)
+ return -1;
+ if (!found && ret == 0) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(oldlist, oldname));
+ return -1;
+ }
+
+ return 0;
+ }
+
+ if (EDESTDIREXISTS(errno)) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_EXISTS,
+ "Target mailbox already exists");
+ } else {
+ mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m",
+ oldpath, newpath);
+ }
+ return -1;
+}
+
+struct mailbox_list maildir_mailbox_list = {
+ .name = MAILBOX_LIST_NAME_MAILDIRPLUSPLUS,
+ .props = MAILBOX_LIST_PROP_NO_MAILDIR_NAME |
+ MAILBOX_LIST_PROP_NO_ALT_DIR |
+ MAILBOX_LIST_PROP_NO_NOSELECT |
+ MAILBOX_LIST_PROP_NO_INTERNAL_NAMES,
+ .mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,
+
+ .v = {
+ .alloc = maildir_list_alloc,
+ .deinit = maildir_list_deinit,
+ .get_hierarchy_sep = maildir_list_get_hierarchy_sep,
+ .get_vname = mailbox_list_default_get_vname,
+ .get_storage_name = mailbox_list_default_get_storage_name,
+ .get_path = maildir_list_get_path,
+ .get_temp_prefix = maildir_list_get_temp_prefix,
+ .iter_init = maildir_list_iter_init,
+ .iter_next = maildir_list_iter_next,
+ .iter_deinit = maildir_list_iter_deinit,
+ .get_mailbox_flags = maildir_list_get_mailbox_flags,
+ .subscriptions_refresh = mailbox_list_subscriptions_refresh,
+ .set_subscribed = maildir_list_set_subscribed,
+ .delete_mailbox = maildir_list_delete_mailbox,
+ .delete_dir = maildir_list_delete_dir,
+ .delete_symlink = mailbox_list_delete_symlink_default,
+ .rename_mailbox = maildir_list_rename_mailbox,
+ }
+};
+
+struct mailbox_list imapdir_mailbox_list = {
+ .name = MAILBOX_LIST_NAME_IMAPDIR,
+ .props = MAILBOX_LIST_PROP_NO_MAILDIR_NAME |
+ MAILBOX_LIST_PROP_NO_ALT_DIR |
+ MAILBOX_LIST_PROP_NO_NOSELECT |
+ MAILBOX_LIST_PROP_NO_INTERNAL_NAMES,
+ .mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,
+
+ .v = {
+ .alloc = imapdir_list_alloc,
+ .deinit = maildir_list_deinit,
+ .get_hierarchy_sep = maildir_list_get_hierarchy_sep,
+ .get_vname = mailbox_list_default_get_vname,
+ .get_storage_name = mailbox_list_default_get_storage_name,
+ .get_path = maildir_list_get_path,
+ .get_temp_prefix = maildir_list_get_temp_prefix,
+ .iter_init = maildir_list_iter_init,
+ .iter_next = maildir_list_iter_next,
+ .iter_deinit = maildir_list_iter_deinit,
+ .get_mailbox_flags = maildir_list_get_mailbox_flags,
+ .subscriptions_refresh = mailbox_list_subscriptions_refresh,
+ .set_subscribed = maildir_list_set_subscribed,
+ .delete_mailbox = maildir_list_delete_mailbox,
+ .delete_dir = maildir_list_delete_dir,
+ .delete_symlink = mailbox_list_delete_symlink_default,
+ .rename_mailbox = maildir_list_rename_mailbox,
+ }
+};