summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/list/mailbox-list-fs-iter.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/list/mailbox-list-fs-iter.c')
-rw-r--r--src/lib-storage/list/mailbox-list-fs-iter.c833
1 files changed, 833 insertions, 0 deletions
diff --git a/src/lib-storage/list/mailbox-list-fs-iter.c b/src/lib-storage/list/mailbox-list-fs-iter.c
new file mode 100644
index 0000000..b95aabc
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-fs-iter.c
@@ -0,0 +1,833 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "unichar.h"
+#include "imap-match.h"
+#include "imap-utf7.h"
+#include "mail-storage.h"
+#include "mailbox-tree.h"
+#include "mailbox-list-subscriptions.h"
+#include "mailbox-list-iter-private.h"
+#include "mailbox-list-fs.h"
+
+#include <stdio.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+struct list_dir_entry {
+ const char *fname;
+ enum mailbox_info_flags info_flags;
+};
+
+struct list_dir_context {
+ struct list_dir_context *parent;
+
+ pool_t pool;
+ const char *storage_name;
+ /* this directory's info flags. */
+ enum mailbox_info_flags info_flags;
+
+ /* all files in this directory */
+ ARRAY(struct list_dir_entry) entries;
+ unsigned int entry_idx;
+};
+
+struct fs_list_iterate_context {
+ struct mailbox_list_iterate_context ctx;
+
+ const char *const *valid_patterns;
+ /* roots can be either /foo, ~user/bar or baz */
+ ARRAY(const char *) roots;
+ unsigned int root_idx;
+ char sep;
+
+ pool_t info_pool;
+ struct mailbox_info info;
+ /* current directory we're handling */
+ struct list_dir_context *dir;
+
+ bool inbox_found:1;
+ bool inbox_has_children:1;
+ bool listed_prefix_inbox:1;
+};
+
+static int
+fs_get_existence_info_flag(struct fs_list_iterate_context *ctx,
+ const char *vname,
+ enum mailbox_info_flags *info_flags)
+{
+ struct mailbox *box;
+ enum mailbox_flags flags = 0;
+ enum mailbox_existence existence;
+ bool auto_boxes;
+ int ret;
+
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RAW_LIST) != 0)
+ flags |= MAILBOX_FLAG_IGNORE_ACLS;
+ auto_boxes = (ctx->ctx.flags & MAILBOX_LIST_ITER_NO_AUTO_BOXES) == 0;
+ box = mailbox_alloc(ctx->ctx.list, vname, flags);
+ ret = mailbox_exists(box, auto_boxes, &existence);
+ mailbox_free(&box);
+
+ if (ret < 0) {
+ /* this can only be an internal error */
+ mailbox_list_set_internal_error(ctx->ctx.list);
+ return -1;
+ }
+ switch (existence) {
+ case MAILBOX_EXISTENCE_NONE:
+ /* We already found out that this mailbox exists. So this is
+ either a race condition or ACL plugin prevented access to
+ this. In any case treat this as a \NoSelect mailbox so that
+ we'll recurse into its potential children. This is
+ especially important if ACL disabled access to the parent
+ mailbox, but child mailboxes would be accessible. */
+ case MAILBOX_EXISTENCE_NOSELECT:
+ *info_flags |= MAILBOX_NOSELECT;
+ break;
+ case MAILBOX_EXISTENCE_SELECT:
+ *info_flags |= MAILBOX_SELECT;
+ break;
+ }
+ return 0;
+}
+
+static void
+fs_list_rename_invalid(struct fs_list_iterate_context *ctx,
+ const char *storage_name)
+{
+ /* the storage_name is completely invalid, rename it to
+ something more sensible. we could do this for all names that
+ aren't valid mUTF-7, but that might lead to accidents in
+ future when UTF-8 storage names are used */
+ string_t *destname = t_str_new(128);
+ string_t *dest = t_str_new(128);
+ const char *root, *src;
+
+ root = mailbox_list_get_root_forced(ctx->ctx.list,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ src = t_strconcat(root, "/", storage_name, NULL);
+
+ if (uni_utf8_get_valid_data((const void *)storage_name,
+ strlen(storage_name), destname))
+ i_unreached(); /* already checked that it was invalid */
+
+ str_append(dest, root);
+ str_append_c(dest, '/');
+ (void)imap_utf8_to_utf7(str_c(destname), dest);
+
+ if (rename(src, str_c(dest)) < 0 && errno != ENOENT)
+ e_error(ctx->ctx.list->ns->user->event,
+ "rename(%s, %s) failed: %m", src, str_c(dest));
+}
+
+static const char *
+dir_get_storage_name(struct list_dir_context *dir, const char *fname)
+{
+ if (*dir->storage_name == '\0') {
+ /* regular root */
+ return fname;
+ } else if (strcmp(dir->storage_name, "/") == 0) {
+ /* full_filesystem_access=yes "/" root */
+ return t_strconcat("/", fname, NULL);
+ } else {
+ /* child */
+ return *fname == '\0' ? dir->storage_name :
+ t_strconcat(dir->storage_name, "/", fname, NULL);
+ }
+}
+
+static int
+dir_entry_get(struct fs_list_iterate_context *ctx, const char *dir_path,
+ struct list_dir_context *dir, const struct dirent *d)
+{
+ const char *storage_name, *vname, *root_dir;
+ struct list_dir_entry *entry;
+ enum imap_match_result match;
+ enum mailbox_info_flags info_flags;
+ int ret;
+
+ /* skip . and .. */
+ if (d->d_name[0] == '.' &&
+ (d->d_name[1] == '\0' ||
+ (d->d_name[1] == '.' && d->d_name[2] == '\0')))
+ return 0;
+
+ if (strcmp(d->d_name, ctx->ctx.list->set.maildir_name) == 0) {
+ /* mail storage's internal directory (e.g. dbox-Mails).
+ this also means that the parent is selectable */
+ dir->info_flags &= ENUM_NEGATE(MAILBOX_NOSELECT);
+ dir->info_flags |= MAILBOX_SELECT;
+ return 0;
+ }
+ if (ctx->ctx.list->set.subscription_fname != NULL &&
+ strcmp(d->d_name, ctx->ctx.list->set.subscription_fname) == 0) {
+ /* if this is the subscriptions file, skip it */
+ root_dir = mailbox_list_get_root_forced(ctx->ctx.list,
+ MAILBOX_LIST_PATH_TYPE_DIR);
+ if (strcmp(root_dir, dir_path) == 0)
+ return 0;
+ }
+
+ /* check the pattern */
+ storage_name = dir_get_storage_name(dir, d->d_name);
+ vname = mailbox_list_get_vname(ctx->ctx.list, storage_name);
+ if (!uni_utf8_str_is_valid(vname)) {
+ fs_list_rename_invalid(ctx, storage_name);
+ /* just skip this in this iteration, we'll see it on the
+ next list */
+ return 0;
+ }
+
+ match = imap_match(ctx->ctx.glob, vname);
+ if (strcmp(d->d_name, "INBOX") == 0 && strcmp(vname, "INBOX") == 0 &&
+ ctx->ctx.list->ns->prefix_len > 0) {
+ /* The glob was matched only against "INBOX", but this
+ directory may hold also prefix/INBOX. Just assume here
+ that it matches and verify later whether it was needed
+ or not. */
+ match = IMAP_MATCH_YES;
+ }
+
+ if ((dir->info_flags & (MAILBOX_CHILDREN | MAILBOX_NOCHILDREN |
+ MAILBOX_NOINFERIORS)) == 0 &&
+ (ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) != 0) {
+ /* we don't know yet if the parent has children. need to figure
+ out if this file is actually a visible mailbox */
+ } else if (match != IMAP_MATCH_YES &&
+ (match & IMAP_MATCH_CHILDREN) == 0) {
+ /* mailbox doesn't match any patterns, we don't care about it */
+ return 0;
+ }
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SKIP_ALIASES) != 0) {
+ ret = mailbox_list_dirent_is_alias_symlink(ctx->ctx.list,
+ dir_path, d);
+ if (ret != 0)
+ return ret < 0 ? -1 : 0;
+ }
+ ret = ctx->ctx.list->v.
+ get_mailbox_flags(ctx->ctx.list, dir_path, d->d_name,
+ mailbox_list_get_file_type(d), &info_flags);
+ if (ret <= 0)
+ return ret;
+ if (!MAILBOX_INFO_FLAGS_FINISHED(info_flags)) {
+ /* mailbox existence isn't known yet. need to figure it out
+ the hard way. */
+ if (fs_get_existence_info_flag(ctx, vname, &info_flags) < 0)
+ return -1;
+ }
+ if ((info_flags & MAILBOX_NONEXISTENT) != 0)
+ return 0;
+
+ /* mailbox exists - make sure parent knows it has children */
+ dir->info_flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN | MAILBOX_NOINFERIORS);
+ dir->info_flags |= MAILBOX_CHILDREN;
+
+ if (match != IMAP_MATCH_YES && (match & IMAP_MATCH_CHILDREN) == 0) {
+ /* this mailbox didn't actually match any pattern,
+ we just needed to know the children state */
+ return 0;
+ }
+
+ /* entry matched a pattern. we're going to return this. */
+ entry = array_append_space(&dir->entries);
+ entry->fname = p_strdup(dir->pool, d->d_name);
+ entry->info_flags = info_flags;
+ return 0;
+}
+
+static bool
+fs_list_get_storage_path(struct fs_list_iterate_context *ctx,
+ const char *storage_name, const char **path_r)
+{
+ const char *root, *path = storage_name;
+
+ if (*path == '~') {
+ if (!mailbox_list_try_get_absolute_path(ctx->ctx.list, &path)) {
+ /* a) couldn't expand ~user/
+ b) mailbox is under our mail root, we changed
+ path to storage_name */
+ }
+ /* NOTE: the path may have been translated to a storage_name
+ instead of path */
+ }
+ if (*path != '/') {
+ /* non-absolute path. add the mailbox root dir as prefix. */
+ enum mailbox_list_path_type type =
+ ctx->ctx.list->set.iter_from_index_dir ?
+ MAILBOX_LIST_PATH_TYPE_INDEX :
+ MAILBOX_LIST_PATH_TYPE_MAILBOX;
+ if (!mailbox_list_get_root_path(ctx->ctx.list, type, &root))
+ return FALSE;
+ if (ctx->ctx.list->set.iter_from_index_dir &&
+ ctx->ctx.list->set.mailbox_dir_name[0] != '\0') {
+ /* append "mailboxes/" to the index root */
+ root = t_strconcat(root, "/",
+ ctx->ctx.list->set.mailbox_dir_name, NULL);
+ }
+ path = *path == '\0' ? root :
+ t_strconcat(root, "/", path, NULL);
+ }
+ *path_r = path;
+ return TRUE;
+}
+
+static int
+fs_list_dir_read(struct fs_list_iterate_context *ctx,
+ struct list_dir_context *dir)
+{
+ DIR *fsdir;
+ struct dirent *d;
+ const char *path;
+ int ret = 0;
+
+ if (!fs_list_get_storage_path(ctx, dir->storage_name, &path))
+ return 0;
+ if (path == NULL) {
+ /* no mailbox root dir */
+ return 0;
+ }
+
+ fsdir = opendir(path);
+ if (fsdir == NULL) {
+ if (ENOTFOUND(errno)) {
+ /* root) user gave invalid hierarchy, ignore
+ sub) probably just race condition with other client
+ deleting the mailbox. */
+ return 0;
+ }
+ if (errno == EACCES) {
+ /* ignore permission errors */
+ return 0;
+ }
+ mailbox_list_set_critical(ctx->ctx.list,
+ "opendir(%s) failed: %m", path);
+ return -1;
+ }
+ if ((dir->info_flags & (MAILBOX_SELECT | MAILBOX_NOSELECT)) == 0) {
+ /* we don't know if the parent is selectable or not. start with
+ the assumption that it isn't, until we see maildir_name */
+ dir->info_flags |= MAILBOX_NOSELECT;
+ }
+
+ errno = 0;
+ while ((d = readdir(fsdir)) != NULL) T_BEGIN {
+ if (dir_entry_get(ctx, path, dir, d) < 0)
+ ret = -1;
+ errno = 0;
+ } T_END;
+ if (errno != 0) {
+ mailbox_list_set_critical(ctx->ctx.list,
+ "readdir(%s) failed: %m", path);
+ ret = -1;
+ }
+ if (closedir(fsdir) < 0) {
+ mailbox_list_set_critical(ctx->ctx.list,
+ "closedir(%s) failed: %m", path);
+ ret = -1;
+ }
+ return ret;
+}
+
+static struct list_dir_context *
+fs_list_read_dir(struct fs_list_iterate_context *ctx, const char *storage_name,
+ enum mailbox_info_flags info_flags)
+{
+ struct list_dir_context *dir;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"fs iter dir", 256);
+ dir = p_new(pool, struct list_dir_context, 1);
+ dir->pool = pool;
+ dir->storage_name = p_strdup(pool, storage_name);
+ dir->info_flags = info_flags;
+ p_array_init(&dir->entries, pool, 16);
+
+ if (fs_list_dir_read(ctx, dir) < 0)
+ ctx->ctx.failed = TRUE;
+
+ if ((dir->info_flags & (MAILBOX_CHILDREN | MAILBOX_NOCHILDREN |
+ MAILBOX_NOINFERIORS)) == 0) {
+ /* assume this directory has no children */
+ dir->info_flags |= MAILBOX_NOCHILDREN;
+ }
+ return dir;
+}
+
+static bool
+fs_list_get_valid_patterns(struct fs_list_iterate_context *ctx,
+ const char *const *patterns)
+{
+ struct mailbox_list *_list = ctx->ctx.list;
+ ARRAY(const char *) valid_patterns;
+ const char *pattern, *test_pattern, *real_pattern, *error;
+ size_t prefix_len;
+
+ prefix_len = strlen(_list->ns->prefix);
+ p_array_init(&valid_patterns, ctx->ctx.pool, 8);
+ for (; *patterns != NULL; patterns++) {
+ /* check that we're not trying to do any "../../" lists */
+ test_pattern = *patterns;
+ /* skip namespace prefix if possible. this allows using
+ e.g. ~/mail/ prefix and have it pass the pattern
+ validation. */
+ if (strncmp(test_pattern, _list->ns->prefix, prefix_len) == 0)
+ test_pattern += prefix_len;
+ if (!uni_utf8_str_is_valid(test_pattern)) {
+ /* ignore invalid UTF8 patterns */
+ continue;
+ }
+ /* check pattern also when it's converted to use real
+ separators. */
+ real_pattern =
+ mailbox_list_get_storage_name(_list, test_pattern);
+ if (mailbox_list_is_valid_name(_list, test_pattern, &error) &&
+ mailbox_list_is_valid_name(_list, real_pattern, &error)) {
+ pattern = p_strdup(ctx->ctx.pool, *patterns);
+ array_push_back(&valid_patterns, &pattern);
+ }
+ }
+ array_append_zero(&valid_patterns); /* NULL-terminate */
+ ctx->valid_patterns = array_front(&valid_patterns);
+
+ return array_count(&valid_patterns) > 1;
+}
+
+static void fs_list_get_roots(struct fs_list_iterate_context *ctx)
+{
+ struct mail_namespace *ns = ctx->ctx.list->ns;
+ char ns_sep = mail_namespace_get_sep(ns);
+ bool full_fs_access =
+ ctx->ctx.list->mail_set->mail_full_filesystem_access;
+ const char *const *patterns, *pattern, *parent, *child;
+ const char *p, *last, *root, *prefix_vname;
+ unsigned int i;
+ size_t parentlen;
+
+ i_assert(*ctx->valid_patterns != NULL);
+
+ /* get the root dirs for all the patterns */
+ p_array_init(&ctx->roots, ctx->ctx.pool, 8);
+ for (patterns = ctx->valid_patterns; *patterns != NULL; patterns++) {
+ pattern = *patterns;
+
+ if (strncmp(pattern, ns->prefix, ns->prefix_len) != 0) {
+ /* typically e.g. prefix=foo/bar/, pattern=foo/%/%
+ we'll use root="" for this.
+
+ it might of course also be pattern=foo/%/prefix/%
+ where we could optimize with root=prefix, but
+ probably too much trouble to implement. */
+ prefix_vname = "";
+ } else {
+ for (p = last = pattern; *p != '\0'; p++) {
+ if (*p == '%' || *p == '*')
+ break;
+ if (*p == ns_sep)
+ last = p;
+ }
+ prefix_vname = t_strdup_until(pattern, last);
+ }
+
+ if (*pattern == ns_sep && full_fs_access) {
+ /* pattern=/something with full filesystem access.
+ (without full filesystem access we want to skip this
+ if namespace prefix begins with separator) */
+ root = "/";
+ } else if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
+ ns->prefix_len == 6 &&
+ strcasecmp(prefix_vname, "INBOX") == 0 &&
+ strncasecmp(ns->prefix, pattern, ns->prefix_len) == 0) {
+ /* special case: Namespace prefix is INBOX/ and
+ we just want to see its contents (not the
+ INBOX's children). */
+ root = "";
+ } else if ((ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0 &&
+ ns->type == MAIL_NAMESPACE_TYPE_SHARED &&
+ !ctx->ctx.list->mail_set->mail_shared_explicit_inbox &&
+ (prefix_vname[0] == '\0' ||
+ (strncmp(ns->prefix, prefix_vname, ns->prefix_len-1) == 0 &&
+ prefix_vname[ns->prefix_len-1] == '\0'))) {
+ /* we need to handle ns prefix explicitly here, because
+ getting storage name with
+ mail_shared_explicit_inbox=no would return
+ root=INBOX. (e.g. LIST "" shared/user/box has to
+ return the box when it doesn't exist but
+ shared/user/box/child exists) */
+ root = "";
+ } else {
+ root = mailbox_list_get_storage_name(ctx->ctx.list,
+ prefix_vname);
+ }
+
+ if (*root == '/') {
+ /* /absolute/path */
+ i_assert(full_fs_access);
+ } else if (*root == '~') {
+ /* ~user/path - don't expand the ~user/ path, since we
+ need to be able to convert the path back to vname */
+ i_assert(full_fs_access);
+ } else {
+ /* mailbox name */
+ }
+ root = p_strdup(ctx->ctx.pool, root);
+ array_push_back(&ctx->roots, &root);
+ }
+ /* sort the root dirs so that /foo is before /foo/bar */
+ array_sort(&ctx->roots, i_strcmp_p);
+ /* remove /foo/bar when there already exists /foo parent */
+ for (i = 1; i < array_count(&ctx->roots); ) {
+ parent = array_idx_elem(&ctx->roots, i-1);
+ child = array_idx_elem(&ctx->roots, i);
+ parentlen = strlen(parent);
+ if (str_begins(child, parent) &&
+ (parentlen == 0 ||
+ child[parentlen] == ctx->sep ||
+ child[parentlen] == '\0'))
+ array_delete(&ctx->roots, i, 1);
+ else
+ i++;
+ }
+}
+
+static void fs_list_next_root(struct fs_list_iterate_context *ctx)
+{
+ const char *const *roots;
+ unsigned int count;
+
+ i_assert(ctx->dir == NULL);
+
+ roots = array_get(&ctx->roots, &count);
+ if (ctx->root_idx == count)
+ return;
+ ctx->dir = fs_list_read_dir(ctx, roots[ctx->root_idx],
+ MAILBOX_NOSELECT);
+ ctx->root_idx++;
+}
+
+struct mailbox_list_iterate_context *
+fs_list_iter_init(struct mailbox_list *_list, const char *const *patterns,
+ enum mailbox_list_iter_flags flags)
+{
+ struct fs_list_iterate_context *ctx;
+ pool_t pool;
+
+ if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
+ /* we're listing only subscribed mailboxes. we can't optimize
+ it, so just use the generic code. */
+ return mailbox_list_subscriptions_iter_init(_list, patterns,
+ flags);
+ }
+
+ pool = pool_alloconly_create("mailbox list fs iter", 2048);
+ ctx = p_new(pool, struct fs_list_iterate_context, 1);
+ ctx->ctx.pool = pool;
+ ctx->ctx.list = _list;
+ ctx->ctx.flags = flags;
+ array_create(&ctx->ctx.module_contexts, pool, sizeof(void *), 5);
+
+ ctx->info_pool = pool_alloconly_create("fs list", 1024);
+ ctx->sep = mail_namespace_get_sep(_list->ns);
+ ctx->info.ns = _list->ns;
+
+ if (!fs_list_get_valid_patterns(ctx, patterns)) {
+ /* we've only invalid patterns (or INBOX). create a glob
+ anyway to avoid any crashes due to glob being accessed
+ elsewhere */
+ ctx->ctx.glob = imap_match_init(pool, "", TRUE, ctx->sep);
+ return &ctx->ctx;
+ }
+ ctx->ctx.glob = imap_match_init_multiple(pool, ctx->valid_patterns,
+ TRUE, ctx->sep);
+ fs_list_get_roots(ctx);
+ fs_list_next_root(ctx);
+ return &ctx->ctx;
+}
+
+int fs_list_iter_deinit(struct mailbox_list_iterate_context *_ctx)
+{
+ struct fs_list_iterate_context *ctx =
+ (struct fs_list_iterate_context *)_ctx;
+ int ret = _ctx->failed ? -1 : 0;
+
+ if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+ return mailbox_list_subscriptions_iter_deinit(_ctx);
+
+ while (ctx->dir != NULL) {
+ struct list_dir_context *dir = ctx->dir;
+
+ ctx->dir = dir->parent;
+ pool_unref(&dir->pool);
+ }
+
+ pool_unref(&ctx->info_pool);
+ pool_unref(&_ctx->pool);
+ return ret;
+}
+
+static void inbox_flags_set(struct fs_list_iterate_context *ctx)
+{
+ /* INBOX is always selectable */
+ ctx->info.flags &= ENUM_NEGATE(MAILBOX_NOSELECT | MAILBOX_NONEXISTENT);
+
+ if (mail_namespace_is_inbox_noinferiors(ctx->info.ns)) {
+ ctx->info.flags &= ENUM_NEGATE(MAILBOX_CHILDREN | MAILBOX_NOCHILDREN);
+ ctx->info.flags |= MAILBOX_NOINFERIORS;
+ }
+}
+
+static const char *
+fs_list_get_inbox_vname(struct fs_list_iterate_context *ctx)
+{
+ struct mail_namespace *ns = ctx->ctx.list->ns;
+
+ if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0)
+ return "INBOX";
+ else
+ return p_strconcat(ctx->info_pool, ns->prefix, "INBOX", NULL);
+}
+
+static bool
+list_file_unfound_inbox(struct fs_list_iterate_context *ctx)
+{
+ ctx->info.flags = 0;
+ ctx->info.vname = fs_list_get_inbox_vname(ctx);
+
+ if (mailbox_list_mailbox(ctx->ctx.list, "INBOX", &ctx->info.flags) < 0)
+ ctx->ctx.failed = TRUE;
+
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_NO_AUTO_BOXES) != 0 &&
+ (ctx->info.flags & MAILBOX_NONEXISTENT) != 0)
+ return FALSE;
+
+ inbox_flags_set(ctx);
+ if (ctx->inbox_has_children)
+ ctx->info.flags |= MAILBOX_CHILDREN;
+ else {
+ /* we got here because we didn't see INBOX among other mailboxes,
+ which means it has no children. */
+ ctx->info.flags |= MAILBOX_NOCHILDREN;
+ }
+ return TRUE;
+}
+
+static bool
+list_file_is_any_inbox(struct fs_list_iterate_context *ctx,
+ const char *storage_name)
+{
+ const char *path, *inbox_path;
+
+ if (!fs_list_get_storage_path(ctx, storage_name, &path))
+ return FALSE;
+
+ if (mailbox_list_get_path(ctx->ctx.list, "INBOX",
+ MAILBOX_LIST_PATH_TYPE_DIR, &inbox_path) <= 0)
+ i_unreached();
+ return strcmp(path, inbox_path) == 0;
+}
+
+static int
+fs_list_entry(struct fs_list_iterate_context *ctx,
+ const struct list_dir_entry *entry)
+{
+ struct mail_namespace *ns = ctx->ctx.list->ns;
+ struct list_dir_context *dir, *subdir = NULL;
+ enum imap_match_result match, child_dir_match;
+ const char *storage_name, *vname, *child_dir_name;
+
+ dir = ctx->dir;
+ storage_name = dir_get_storage_name(dir, entry->fname);
+
+ vname = mailbox_list_get_vname(ctx->ctx.list, storage_name);
+ ctx->info.vname = p_strdup(ctx->info_pool, vname);
+ ctx->info.flags = entry->info_flags;
+
+ match = imap_match(ctx->ctx.glob, ctx->info.vname);
+
+ if (strcmp(ctx->info.vname, "INBOX") == 0 &&
+ ctx->ctx.list->ns->prefix_len > 0) {
+ /* INBOX's children are matched as prefix/INBOX */
+ child_dir_name = t_strdup_printf("%sINBOX", ns->prefix);
+ } else {
+ child_dir_name =
+ t_strdup_printf("%s%c", ctx->info.vname, ctx->sep);
+ }
+ child_dir_match = imap_match(ctx->ctx.glob, child_dir_name);
+ if (child_dir_match == IMAP_MATCH_YES)
+ child_dir_match |= IMAP_MATCH_CHILDREN;
+
+ if ((ctx->info.flags &
+ (MAILBOX_NOCHILDREN | MAILBOX_NOINFERIORS)) != 0) {
+ /* mailbox has no children */
+ } else if ((ctx->info.flags & MAILBOX_CHILDREN) != 0 &&
+ (child_dir_match & IMAP_MATCH_CHILDREN) == 0) {
+ /* mailbox has children, but we don't want to list them */
+ } else if (((ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) != 0 ||
+ (child_dir_match & IMAP_MATCH_CHILDREN) != 0) &&
+ *entry->fname != '\0') {
+ /* a) mailbox has children and we want to return them
+ b) we don't want to return mailbox's children, but we need
+ to know if it has any */
+ subdir = fs_list_read_dir(ctx, storage_name, entry->info_flags);
+ subdir->parent = dir;
+ ctx->dir = subdir;
+ /* the scanning may have updated the dir's info flags */
+ ctx->info.flags = subdir->info_flags;
+ }
+
+ /* handle INBOXes correctly */
+ if (strcasecmp(ctx->info.vname, "INBOX") == 0 &&
+ (ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
+ /* either this is user's INBOX, or it's a naming conflict */
+ if (!list_file_is_any_inbox(ctx, storage_name)) {
+ if (subdir == NULL) {
+ /* no children */
+ } else if ((ctx->ctx.list->flags &
+ MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) {
+ if (strcmp(storage_name, "INBOX") == 0) {
+ /* INBOX and its children are in
+ different paths */
+ ctx->inbox_has_children = TRUE;
+ } else {
+ /* naming conflict, skip its
+ children also */
+ ctx->dir = dir;
+ pool_unref(&subdir->pool);
+ }
+ } else if ((ctx->info.flags & MAILBOX_NOINFERIORS) == 0) {
+ /* INBOX itself is \NoInferiors, but this INBOX
+ is a directory, and we can make INBOX have
+ children using it. */
+ ctx->inbox_has_children = TRUE;
+ }
+ return 0;
+ }
+ if (subdir != NULL)
+ ctx->inbox_has_children = TRUE;
+ inbox_flags_set(ctx);
+ ctx->info.vname = "INBOX"; /* always return uppercased */
+ ctx->inbox_found = TRUE;
+ } else if (strcmp(storage_name, "INBOX") == 0 &&
+ (ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
+ /* this is <ns prefix>/INBOX. don't return it, unless it has
+ children. */
+ i_assert(*ns->prefix != '\0');
+ if ((ctx->info.flags & MAILBOX_CHILDREN) == 0)
+ return 0;
+ /* although it could be selected with this name,
+ it would be confusing for clients to see the same
+ mails in both INBOX and <ns prefix>/INBOX. */
+ ctx->info.flags &= ENUM_NEGATE(MAILBOX_SELECT);
+ ctx->info.flags |= MAILBOX_NOSELECT;
+ } else if ((ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0 &&
+ list_file_is_any_inbox(ctx, storage_name)) {
+ if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
+ /* probably mbox inbox file */
+ return 0;
+ }
+ /* shared/user/INBOX */
+ ctx->info.flags &= ENUM_NEGATE(MAILBOX_NOSELECT | MAILBOX_NONEXISTENT);
+ ctx->info.flags |= MAILBOX_SELECT;
+ ctx->inbox_found = TRUE;
+ }
+
+ if (match != IMAP_MATCH_YES) {
+ /* mailbox's children may match, but the mailbox itself
+ doesn't */
+ return 0;
+ }
+ if (mailbox_list_iter_try_delete_noselect(&ctx->ctx, &ctx->info, storage_name))
+ return 0;
+ return 1;
+}
+
+static int
+fs_list_next(struct fs_list_iterate_context *ctx)
+{
+ struct list_dir_context *dir;
+ const struct list_dir_entry *entries;
+ unsigned int count;
+ int ret;
+
+ while (ctx->dir != NULL) {
+ /* NOTE: fs_list_entry() may change ctx->dir */
+ entries = array_get(&ctx->dir->entries, &count);
+ while (ctx->dir->entry_idx < count) {
+ p_clear(ctx->info_pool);
+ ret = fs_list_entry(ctx, &entries[ctx->dir->entry_idx++]);
+ if (ret > 0)
+ return 1;
+ if (ret < 0)
+ ctx->ctx.failed = TRUE;
+ entries = array_get(&ctx->dir->entries, &count);
+ }
+
+ dir = ctx->dir;
+ ctx->dir = dir->parent;
+ pool_unref(&dir->pool);
+
+ if (ctx->dir == NULL)
+ fs_list_next_root(ctx);
+ }
+
+ if (ctx->inbox_has_children && ctx->ctx.list->ns->prefix_len > 0 &&
+ !ctx->listed_prefix_inbox) {
+ ctx->info.flags = MAILBOX_CHILDREN | MAILBOX_NOSELECT;
+ ctx->info.vname =
+ p_strconcat(ctx->info_pool,
+ ctx->ctx.list->ns->prefix, "INBOX", NULL);
+ ctx->listed_prefix_inbox = TRUE;
+ if (imap_match(ctx->ctx.glob, ctx->info.vname) == IMAP_MATCH_YES)
+ return 1;
+ }
+ if (!ctx->inbox_found && ctx->ctx.glob != NULL &&
+ (ctx->ctx.list->ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0 &&
+ imap_match(ctx->ctx.glob,
+ fs_list_get_inbox_vname(ctx)) == IMAP_MATCH_YES) {
+ /* INBOX wasn't seen while listing other mailboxes. It might
+ be located elsewhere. */
+ ctx->inbox_found = TRUE;
+ return list_file_unfound_inbox(ctx) ? 1 : 0;
+ }
+
+ /* finished */
+ return 0;
+}
+
+const struct mailbox_info *
+fs_list_iter_next(struct mailbox_list_iterate_context *_ctx)
+{
+ struct fs_list_iterate_context *ctx =
+ (struct fs_list_iterate_context *)_ctx;
+ int ret;
+
+ if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+ return mailbox_list_subscriptions_iter_next(_ctx);
+
+ T_BEGIN {
+ ret = fs_list_next(ctx);
+ } T_END;
+
+ if (ret == 0)
+ return mailbox_list_iter_default_next(_ctx);
+ else if (ret < 0)
+ return NULL;
+
+ if (_ctx->list->ns->type == MAIL_NAMESPACE_TYPE_SHARED &&
+ !_ctx->list->ns->list->mail_set->mail_shared_explicit_inbox &&
+ strlen(ctx->info.vname) < _ctx->list->ns->prefix_len) {
+ /* shared/user INBOX, IMAP code already lists it */
+ return fs_list_iter_next(_ctx);
+ }
+
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) != 0) {
+ mailbox_list_set_subscription_flags(ctx->ctx.list,
+ ctx->info.vname,
+ &ctx->info.flags);
+ }
+ i_assert(ctx->info.vname != NULL);
+ return &ctx->info;
+}