summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/list/mailbox-list-fs.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/list/mailbox-list-fs.c')
-rw-r--r--src/lib-storage/list/mailbox-list-fs.c558
1 files changed, 558 insertions, 0 deletions
diff --git a/src/lib-storage/list/mailbox-list-fs.c b/src/lib-storage/list/mailbox-list-fs.c
new file mode 100644
index 0000000..c1aabb9
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-fs.c
@@ -0,0 +1,558 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "mkdir-parents.h"
+#include "mailbox-log.h"
+#include "subscription-file.h"
+#include "mail-storage.h"
+#include "mailbox-list-subscriptions.h"
+#include "mailbox-list-delete.h"
+#include "mailbox-list-fs.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#define GLOBAL_TEMP_PREFIX ".temp."
+
+extern struct mailbox_list fs_mailbox_list;
+
+static struct mailbox_list *fs_list_alloc(void)
+{
+ struct fs_mailbox_list *list;
+ pool_t pool;
+
+ pool = pool_alloconly_create("fs list", 2048);
+
+ list = p_new(pool, struct fs_mailbox_list, 1);
+ list->list = fs_mailbox_list;
+ list->list.pool = pool;
+
+ list->temp_prefix = p_strconcat(pool, GLOBAL_TEMP_PREFIX,
+ my_hostname, ".", my_pid, ".", NULL);
+ return &list->list;
+}
+
+static void fs_list_deinit(struct mailbox_list *_list)
+{
+ struct fs_mailbox_list *list = (struct fs_mailbox_list *)_list;
+
+ pool_unref(&list->list.pool);
+}
+
+static char fs_list_get_hierarchy_sep(struct mailbox_list *list ATTR_UNUSED)
+{
+ return '/';
+}
+
+static const char *
+fs_list_get_path_to(const struct mailbox_list_settings *set,
+ const char *root_dir, const char *name)
+{
+ if (*set->maildir_name != '\0' && set->index_control_use_maildir_name) {
+ return t_strdup_printf("%s/%s%s/%s", root_dir,
+ set->mailbox_dir_name, name,
+ set->maildir_name);
+ } else {
+ return t_strdup_printf("%s/%s%s", root_dir,
+ set->mailbox_dir_name, name);
+ }
+}
+
+static int
+fs_list_get_path(struct mailbox_list *_list, const char *name,
+ enum mailbox_list_path_type type, const char **path_r)
+{
+ const struct mailbox_list_settings *set = &_list->set;
+ const char *root_dir, *error;
+
+ if (name == NULL) {
+ /* return root directories */
+ return mailbox_list_set_get_root_path(set, type, path_r) ? 1 : 0;
+ }
+
+ i_assert(mailbox_list_is_valid_name(_list, name, &error));
+
+ if (mailbox_list_try_get_absolute_path(_list, &name)) {
+ if (type == MAILBOX_LIST_PATH_TYPE_INDEX &&
+ *set->index_dir == '\0')
+ return 0;
+ *path_r = name;
+ return 1;
+ }
+
+ root_dir = set->root_dir;
+ switch (type) {
+ case MAILBOX_LIST_PATH_TYPE_DIR:
+ if (*set->maildir_name != '\0') {
+ *path_r = t_strdup_printf("%s/%s%s", set->root_dir,
+ set->mailbox_dir_name, name);
+ return 1;
+ }
+ break;
+ case MAILBOX_LIST_PATH_TYPE_ALT_DIR:
+ if (set->alt_dir == NULL)
+ return 0;
+ if (*set->maildir_name != '\0') {
+ /* maildir_name is for the mailbox, caller is asking
+ for the directory name */
+ *path_r = t_strdup_printf("%s/%s%s", set->alt_dir,
+ set->mailbox_dir_name, name);
+ return 1;
+ }
+ root_dir = set->alt_dir;
+ break;
+ case MAILBOX_LIST_PATH_TYPE_MAILBOX:
+ break;
+ case MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX:
+ if (set->alt_dir == NULL)
+ return 0;
+ root_dir = set->alt_dir;
+ break;
+ case MAILBOX_LIST_PATH_TYPE_CONTROL:
+ if (set->control_dir != NULL) {
+ *path_r = fs_list_get_path_to(set, set->control_dir, name);
+ return 1;
+ }
+ break;
+ case MAILBOX_LIST_PATH_TYPE_INDEX_CACHE:
+ if (set->index_cache_dir != NULL) {
+ *path_r = fs_list_get_path_to(set, set->index_cache_dir, name);
+ return 1;
+ }
+ /* fall through */
+ case MAILBOX_LIST_PATH_TYPE_INDEX:
+ if (set->index_dir != NULL) {
+ if (*set->index_dir == '\0')
+ return 0;
+ *path_r = fs_list_get_path_to(set, set->index_dir, name);
+ return 1;
+ }
+ break;
+ case MAILBOX_LIST_PATH_TYPE_INDEX_PRIVATE:
+ if (set->index_pvt_dir == NULL)
+ return 0;
+ *path_r = fs_list_get_path_to(set, 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 && set->inbox_path != NULL) {
+ /* If INBOX is a file, index and control directories are
+ located in root directory. */
+ if ((_list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0 ||
+ type == MAILBOX_LIST_PATH_TYPE_MAILBOX ||
+ type == MAILBOX_LIST_PATH_TYPE_DIR) {
+ *path_r = set->inbox_path;
+ return 1;
+ }
+ }
+
+ if (root_dir == NULL)
+ return 0;
+ if (*set->maildir_name == '\0') {
+ *path_r = t_strdup_printf("%s/%s%s", root_dir,
+ set->mailbox_dir_name, name);
+ } else {
+ *path_r = t_strdup_printf("%s/%s%s/%s", root_dir,
+ set->mailbox_dir_name, name,
+ set->maildir_name);
+ }
+ return 1;
+}
+
+static const char *
+fs_list_get_temp_prefix(struct mailbox_list *_list, bool global)
+{
+ struct fs_mailbox_list *list = (struct fs_mailbox_list *)_list;
+
+ return global ? GLOBAL_TEMP_PREFIX : list->temp_prefix;
+}
+
+static const char *
+fs_list_join_refpattern(struct mailbox_list *_list ATTR_UNUSED,
+ const char *ref, const char *pattern)
+{
+ if (*pattern == '/' || *pattern == '~') {
+ /* pattern overrides reference */
+ } else if (*ref != '\0') {
+ /* merge reference and pattern */
+ pattern = t_strconcat(ref, pattern, NULL);
+ }
+ return pattern;
+}
+
+static int fs_list_set_subscribed(struct mailbox_list *_list,
+ const char *name, bool set)
+{
+ struct fs_mailbox_list *list = (struct fs_mailbox_list *)_list;
+ enum mailbox_list_path_type type;
+ const char *path;
+
+ if (_list->set.subscription_fname == NULL) {
+ mailbox_list_set_error(_list, MAIL_ERROR_NOTPOSSIBLE,
+ "Subscriptions not supported");
+ return -1;
+ }
+
+ type = _list->set.control_dir != NULL ?
+ MAILBOX_LIST_PATH_TYPE_CONTROL : MAILBOX_LIST_PATH_TYPE_DIR;
+
+ path = t_strconcat(mailbox_list_get_root_forced(_list, type),
+ "/", _list->set.subscription_fname, NULL);
+ return subsfile_set_subscribed(_list, path, list->temp_prefix,
+ name, set);
+}
+
+
+static const char *mailbox_list_fs_get_trash_dir(struct 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/"MAILBOX_LIST_FS_TRASH_DIR_NAME, root_dir);
+}
+
+static int
+fs_list_delete_maildir(struct mailbox_list *list, const char *name)
+{
+ const char *path, *trash_dir;
+ bool rmdir_path;
+ int ret;
+
+ if (*list->set.maildir_name != '\0' &&
+ *list->set.mailbox_dir_name != '\0') {
+ trash_dir = mailbox_list_fs_get_trash_dir(list);
+ ret = mailbox_list_delete_maildir_via_trash(list, name,
+ trash_dir);
+ if (ret < 0)
+ return -1;
+
+ if (ret > 0) {
+ /* try to delete the parent directory */
+ if (mailbox_list_get_path(list, name,
+ MAILBOX_LIST_PATH_TYPE_DIR,
+ &path) <= 0)
+ i_unreached();
+ if (rmdir(path) < 0 && errno != ENOENT &&
+ errno != ENOTEMPTY && errno != EEXIST) {
+ mailbox_list_set_critical(list,
+ "rmdir(%s) failed: %m", path);
+ }
+ return 0;
+ }
+ }
+
+ rmdir_path = *list->set.maildir_name != '\0';
+ if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &path) <= 0)
+ i_unreached();
+ return mailbox_list_delete_mailbox_nonrecursive(list, name, path,
+ rmdir_path);
+}
+
+static int fs_list_delete_mailbox(struct mailbox_list *list, const char *name)
+{
+ const char *path;
+ int ret;
+
+ ret = mailbox_list_get_path(list, name,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX, &path);
+ if (ret < 0)
+ return -1;
+ i_assert(ret > 0);
+
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) {
+ ret = mailbox_list_delete_mailbox_file(list, name, path);
+ } else {
+ ret = fs_list_delete_maildir(list, name);
+ }
+ if (ret == 0 && list->set.no_noselect)
+ mailbox_list_delete_until_root(list, path, MAILBOX_LIST_PATH_TYPE_MAILBOX);
+
+ i_assert(ret <= 0);
+ return mailbox_list_delete_finish_ret(list, name, ret == 0);
+}
+
+static int fs_list_rmdir(struct mailbox_list *list, const char *name,
+ const char *path)
+{
+ guid_128_t dir_sha128;
+
+ if (rmdir(path) < 0)
+ return -1;
+
+ mailbox_name_get_sha128(name, dir_sha128);
+ mailbox_list_add_change(list, MAILBOX_LOG_RECORD_DELETE_DIR,
+ dir_sha128);
+ return 0;
+}
+
+static int fs_list_delete_dir(struct mailbox_list *list, const char *name)
+{
+ const char *path, *child_name, *child_path, *p;
+ char sep;
+ int ret;
+
+ if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR,
+ &path) <= 0)
+ i_unreached();
+ ret = fs_list_rmdir(list, name, path);
+ if (!list->set.iter_from_index_dir) {
+ /* it should exist only in the mail directory */
+ if (ret == 0)
+ return 0;
+ } else if (ret == 0 || errno == ENOENT) {
+ /* the primary list location is the index directory, but it
+ exists in both index and mail directories. */
+ if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_INDEX,
+ &path) <= 0)
+ i_unreached();
+ if (fs_list_rmdir(list, name, path) == 0)
+ return 0;
+ if (ret == 0 && errno == ENOENT) {
+ /* partial existence: exists in _DIR, but not in
+ _INDEX. return success anyway. */
+ return 0;
+ }
+ /* a) both directories didn't exist
+ b) index directory couldn't be rmdir()ed for some reason */
+ }
+
+ if (errno == ENOENT || errno == ENOTDIR) {
+ mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(list, name));
+ } else if (errno == ENOTEMPTY || errno == EEXIST) {
+ /* mbox workaround: if only .imap/ directory is preventing the
+ deletion, remove it */
+ sep = mailbox_list_get_hierarchy_sep(list);
+ child_name = t_strdup_printf("%s%cchild", name, sep);
+ if (mailbox_list_get_path(list, child_name,
+ MAILBOX_LIST_PATH_TYPE_INDEX,
+ &child_path) > 0 &&
+ str_begins(child_path, path)) {
+ /* drop the "/child" part out. */
+ p = strrchr(child_path, '/');
+ if (rmdir(t_strdup_until(child_path, p)) == 0) {
+ /* try again */
+ if (fs_list_rmdir(list, name, path) == 0)
+ return 0;
+ }
+ }
+
+ mailbox_list_set_error(list, MAIL_ERROR_EXISTS,
+ "Mailbox has children, delete them first");
+ } else {
+ mailbox_list_set_critical(list, "rmdir(%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, bool rmdir_parent)
+{
+ struct stat st;
+ const char *oldpath, *newpath, *p, *oldparent, *newparent;
+
+ 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;
+
+ p = strrchr(oldpath, '/');
+ oldparent = p == NULL ? "/" : t_strdup_until(oldpath, p);
+ p = strrchr(newpath, '/');
+ newparent = p == NULL ? "/" : t_strdup_until(newpath, p);
+
+ if (strcmp(oldparent, newparent) != 0 && stat(oldpath, &st) == 0) {
+ /* make sure the newparent exists */
+ struct mailbox_permissions perm;
+
+ mailbox_list_get_root_permissions(newlist, &perm);
+ if (mkdir_parents_chgrp(newparent, perm.dir_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin) < 0 &&
+ errno != EEXIST) {
+ if (mailbox_list_set_error_from_errno(oldlist))
+ return -1;
+
+ mailbox_list_set_critical(oldlist,
+ "mkdir_parents(%s) failed: %m", newparent);
+ return -1;
+ }
+ }
+
+ if (rename(oldpath, newpath) < 0 && errno != ENOENT) {
+ mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m",
+ oldpath, newpath);
+ return -1;
+ }
+ if (rmdir_parent && (p = strrchr(oldpath, '/')) != NULL) {
+ oldpath = t_strdup_until(oldpath, p);
+ if (rmdir(oldpath) < 0 && errno != ENOENT &&
+ errno != ENOTEMPTY && errno != EEXIST) {
+ mailbox_list_set_critical(oldlist,
+ "rmdir(%s) failed: %m", oldpath);
+ }
+ }
+
+ /* avoid leaving empty directories lying around */
+ mailbox_list_delete_until_root(oldlist, oldpath, type);
+ return 0;
+}
+
+static int fs_list_rename_mailbox(struct mailbox_list *oldlist,
+ const char *oldname,
+ struct mailbox_list *newlist,
+ const char *newname)
+{
+ struct mail_storage *oldstorage;
+ const char *oldvname, *oldpath, *newpath, *alt_newpath, *root_path, *p;
+ struct stat st;
+ struct mailbox_permissions old_perm, new_perm;
+ bool rmdir_parent = FALSE;
+
+ oldvname = mailbox_list_get_vname(oldlist, oldname);
+ if (mailbox_list_get_storage(&oldlist, oldvname, &oldstorage) < 0)
+ return -1;
+
+ if (mailbox_list_get_path(oldlist, oldname,
+ MAILBOX_LIST_PATH_TYPE_DIR, &oldpath) <= 0 ||
+ mailbox_list_get_path(newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_DIR, &newpath) <= 0)
+ i_unreached();
+ if (mailbox_list_get_path(newlist, newname, MAILBOX_LIST_PATH_TYPE_ALT_DIR,
+ &alt_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;
+ }
+
+ mailbox_list_get_permissions(oldlist, oldname, &old_perm);
+ mailbox_list_get_permissions(newlist, newname, &new_perm);
+
+ /* 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 &&
+ (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;
+ }
+
+ /* create the hierarchy */
+ p = strrchr(newpath, '/');
+ if (p != NULL) {
+ p = t_strdup_until(newpath, p);
+ if (mkdir_parents_chgrp(p, new_perm.dir_create_mode,
+ new_perm.file_create_gid,
+ new_perm.file_create_gid_origin) < 0 &&
+ errno != EEXIST) {
+ if (mailbox_list_set_error_from_errno(oldlist))
+ return -1;
+
+ mailbox_list_set_critical(oldlist,
+ "mkdir_parents(%s) failed: %m", p);
+ return -1;
+ }
+ }
+
+ /* first check that the destination mailbox doesn't exist.
+ this is racy, but we need to be atomic and there's hardly any
+ possibility that someone actually tries to rename two mailboxes
+ to same new one */
+ if (lstat(newpath, &st) == 0) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_EXISTS,
+ "Target mailbox already exists");
+ return -1;
+ } else if (errno == ENOTDIR) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ "Target mailbox doesn't allow inferior mailboxes");
+ return -1;
+ } else if (errno != ENOENT && errno != EACCES) {
+ mailbox_list_set_critical(oldlist, "lstat(%s) failed: %m",
+ newpath);
+ return -1;
+ }
+
+ if (alt_newpath != NULL) {
+ if (stat(alt_newpath, &st) == 0) {
+ /* race condition or a directory left there lying around?
+ safest to just report error. */
+ mailbox_list_set_error(oldlist, MAIL_ERROR_EXISTS,
+ "Target mailbox already exists");
+ return -1;
+ } else if (errno != ENOENT) {
+ mailbox_list_set_critical(oldlist, "stat(%s) failed: %m",
+ alt_newpath);
+ return -1;
+ }
+ }
+
+ if (rename(oldpath, newpath) < 0) {
+ if (ENOTFOUND(errno)) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(oldlist, oldname));
+ } else if (!mailbox_list_set_error_from_errno(oldlist)) {
+ mailbox_list_set_critical(oldlist,
+ "rename(%s, %s) failed: %m", oldpath, newpath);
+ }
+ return -1;
+ }
+
+ if (alt_newpath != NULL) {
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_ALT_DIR, rmdir_parent);
+ }
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_CONTROL, rmdir_parent);
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_INDEX, rmdir_parent);
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_INDEX_CACHE, rmdir_parent);
+ return 0;
+}
+
+struct mailbox_list fs_mailbox_list = {
+ .name = MAILBOX_LIST_NAME_FS,
+ .props = 0,
+ .mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,
+
+ .v = {
+ .alloc = fs_list_alloc,
+ .deinit = fs_list_deinit,
+ .get_hierarchy_sep = fs_list_get_hierarchy_sep,
+ .get_vname = mailbox_list_default_get_vname,
+ .get_storage_name = mailbox_list_default_get_storage_name,
+ .get_path = fs_list_get_path,
+ .get_temp_prefix = fs_list_get_temp_prefix,
+ .join_refpattern = fs_list_join_refpattern,
+ .iter_init = fs_list_iter_init,
+ .iter_next = fs_list_iter_next,
+ .iter_deinit = fs_list_iter_deinit,
+ .get_mailbox_flags = fs_list_get_mailbox_flags,
+ .subscriptions_refresh = mailbox_list_subscriptions_refresh,
+ .set_subscribed = fs_list_set_subscribed,
+ .delete_mailbox = fs_list_delete_mailbox,
+ .delete_dir = fs_list_delete_dir,
+ .delete_symlink = mailbox_list_delete_symlink_default,
+ .rename_mailbox = fs_list_rename_mailbox,
+ }
+};