/* 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 #include #include #include 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 /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 /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; }