/* 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>
struct list_dir_entry {
const char *fname;
};
struct list_dir_context {
const char *storage_name;
/* this directory's info flags. */
/* all files in this directory */
unsigned int entry_idx;
};
struct fs_list_iterate_context {
const char *const *valid_patterns;
unsigned int root_idx;
char sep;
/* current directory we're handling */
};
static int
const char *vname,
enum mailbox_info_flags *info_flags)
{
bool auto_boxes;
int ret;
mailbox_free(&box);
if (ret < 0) {
/* this can only be an internal error */
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. */
break;
case MAILBOX_EXISTENCE_SELECT:
*info_flags |= MAILBOX_SELECT;
break;
}
return 0;
}
static void
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 */
(void)uni_utf8_get_valid_data((const void *)storage_name,
}
static const char *
{
/* regular root */
return fname;
/* full_filesystem_access=yes "/" root */
} else {
/* child */
}
}
static int
{
int ret;
/* skip . and .. */
if (d->d_name[0] == '.' &&
return 0;
/* mail storage's internal directory (e.g. dbox-Mails).
this also means that the parent is selectable */
return 0;
}
/* if this is the subscriptions file, skip it */
return 0;
}
/* check the pattern */
if (!uni_utf8_str_is_valid(vname)) {
/* just skip this in this iteration, we'll see it on the
next list */
return 0;
}
/* The glob was matched only against "INBOX", but this
that it matches and verify later whether it was needed
or not. */
}
MAILBOX_NOINFERIORS)) == 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;
}
dir_path, d);
if (ret != 0)
return ret < 0 ? -1 : 0;
}
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. */
return -1;
}
if ((info_flags & MAILBOX_NONEXISTENT) != 0)
return 0;
/* mailbox exists - make sure parent knows it has children */
/* 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. */
return 0;
}
static bool
const char *storage_name, const char **path_r)
{
if (*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. */
return FALSE;
/* append "mailboxes/" to the index root */
}
}
return TRUE;
}
static int
struct list_dir_context *dir)
{
struct dirent *d;
const char *path;
int ret = 0;
return 0;
/* no mailbox root dir */
return 0;
}
/* root) user gave invalid hierarchy, ignore
sub) probably just race condition with other client
deleting the mailbox. */
return 0;
}
/* ignore permission errors */
return 0;
}
"opendir(%s) failed: %m", path);
return -1;
}
/* we don't know if the parent is selectable or not. start with
the assumption that it isn't, until we see maildir_name */
}
errno = 0;
ret = -1;
errno = 0;
} T_END;
if (errno != 0) {
"readdir(%s) failed: %m", path);
ret = -1;
}
"closedir(%s) failed: %m", path);
ret = -1;
}
return ret;
}
static struct list_dir_context *
enum mailbox_info_flags info_flags)
{
MAILBOX_NOINFERIORS)) == 0) {
/* assume this directory has no children */
}
return dir;
}
static bool
const char *const *patterns)
{
ARRAY(const char *) valid_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 (!uni_utf8_str_is_valid(test_pattern)) {
/* ignore invalid UTF8 patterns */
continue;
}
/* check pattern also when it's converted to use real
separators. */
}
}
}
{
bool full_fs_access =
unsigned int i;
/* get the root dirs for all the patterns */
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 {
if (*p == '%' || *p == '*')
break;
if (*p == ns_sep)
last = p;
}
}
/* pattern=/something with full filesystem access.
(without full filesystem access we want to skip this
if namespace prefix begins with separator) */
root = "/";
/* special case: Namespace prefix is INBOX/ and
we just want to see its contents (not the
INBOX's children). */
root = "";
(prefix_vname[0] == '\0' ||
/* we need to handle ns prefix explicitly here, because
getting storage name with
mail_shared_explicit_inbox=no would return
return the box when it doesn't exist but
root = "";
} else {
}
if (*root == '/') {
} else if (*root == '~') {
need to be able to convert the path back to vname */
} else {
/* mailbox name */
}
}
(parentlen == 0 ||
else
i++;
}
}
{
const char *const *roots;
unsigned int count;
return;
}
struct mailbox_list_iterate_context *
enum mailbox_list_iter_flags flags)
{
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. */
flags);
}
/* we've only invalid patterns (or INBOX). create a glob
anyway to avoid any crashes due to glob being accessed
elsewhere */
}
}
{
(struct fs_list_iterate_context *)_ctx;
}
return ret;
}
{
/* INBOX is always selectable */
}
}
static const char *
{
return "INBOX";
else
}
static bool
{
return FALSE;
if (ctx->inbox_has_children)
else {
/* we got here because we didn't see INBOX among other mailboxes,
which means it has no children. */
}
return TRUE;
}
static bool
const char *storage_name)
{
return FALSE;
MAILBOX_LIST_PATH_TYPE_DIR, &inbox_path) <= 0)
i_unreached();
}
static int
const struct list_dir_entry *entry)
{
} else {
}
if (child_dir_match == IMAP_MATCH_YES)
(MAILBOX_NOCHILDREN | MAILBOX_NOINFERIORS)) != 0) {
/* mailbox has no children */
(child_dir_match & IMAP_MATCH_CHILDREN) == 0) {
/* mailbox has children, but we don't want to list them */
(child_dir_match & IMAP_MATCH_CHILDREN) != 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 */
/* the scanning may have updated the dir's info flags */
}
/* handle INBOXes correctly */
/* either this is user's INBOX, or it's a naming conflict */
/* no children */
MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) {
/* INBOX and its children are in
different paths */
} else {
/* naming conflict, skip its
children also */
}
/* INBOX itself is \NoInferiors, but this INBOX
is a directory, and we can make INBOX have
children using it. */
}
return 0;
}
/* this is <ns prefix>/INBOX. don't return it, unless it has
children. */
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. */
/* probably mbox inbox file */
return 0;
}
}
if (match != IMAP_MATCH_YES) {
/* mailbox's children may match, but the mailbox itself
doesn't */
return 0;
}
return 0;
return 1;
}
static int
{
unsigned int count;
int ret;
/* NOTE: fs_list_entry() may change ctx->dir */
if (ret > 0)
return 1;
if (ret < 0)
}
}
!ctx->listed_prefix_inbox) {
return 1;
}
/* INBOX wasn't seen while listing other mailboxes. It might
be located elsewhere. */
}
/* finished */
return 0;
}
const struct mailbox_info *
{
(struct fs_list_iterate_context *)_ctx;
int ret;
return mailbox_list_subscriptions_iter_next(_ctx);
T_BEGIN {
} T_END;
if (ret == 0)
return mailbox_list_iter_default_next(_ctx);
else if (ret < 0)
return NULL;
return fs_list_iter_next(_ctx);
}
}
}