mailbox-list-subscriptions.c revision 3568ea090b5a072e498438e74db23b98103ff2de
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden/* Copyright (c) 2002-2013 Dovecot authors, see the included COPYING file */
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden#include "lib.h"
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden#include "ioloop.h"
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden#include "array.h"
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden#include "unichar.h"
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden#include "imap-match.h"
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden#include "subscription-file.h"
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden#include "mailbox-tree.h"
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden#include "mailbox-list-private.h"
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden#include "mailbox-list-subscriptions.h"
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden#include <sys/stat.h>
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearsonstruct subscriptions_mailbox_list_iterate_context {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden struct mailbox_list_iterate_context ctx;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden struct mailbox_tree_context *tree;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden struct mailbox_tree_iterate_context *iter;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden struct mailbox_info info;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden};
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Maddenstatic int
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearsonmailbox_list_subscription_fill_one(struct mailbox_list *list,
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden struct mailbox_list *src_list,
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden const char *name)
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden{
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden struct mail_namespace *ns, *default_ns = list->ns;
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson struct mail_namespace *namespaces = default_ns->user->namespaces;
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson struct mailbox_node *node;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden const char *vname, *ns_name, *error;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden unsigned int len;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden bool created;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden /* default_ns is whatever namespace we're currently listing.
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if we have e.g. prefix="" and prefix=pub/ namespaces with
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson pub/ namespace having subscriptions=no, we want to:
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden 1) when listing "" namespace we want to skip over any names
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden that begin with pub/. */
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if (src_list->ns->prefix_len == 0)
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden ns_name = name;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden else {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden /* we could have two-level namespace: ns/ns2/ */
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden ns_name = t_strconcat(src_list->ns->prefix, name, NULL);
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden }
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden ns = mail_namespace_find_unsubscribable(namespaces, ns_name);
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if (ns != NULL && ns != default_ns) {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if (ns->prefix_len > 0)
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden return 0;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden /* prefix="" namespace=no : catching this is basically the
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden same as not finding any namespace. */
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden ns = NULL;
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden }
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden /* 2) when listing pub/ namespace, skip over entries that don't
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden begin with pub/. */
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden if (ns == NULL &&
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden (default_ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) == 0)
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden return 0;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden /* When listing shared namespace's subscriptions, we need to
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden autocreate all the visible child namespaces. their subscriptions
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden are listed later. */
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden if (ns != NULL && ns->type == MAIL_NAMESPACE_TYPE_SHARED &&
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden (ns->flags & NAMESPACE_FLAG_AUTOCREATED) == 0) {
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden /* we'll need to get the namespace autocreated.
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden one easy way is to just ask to join a reference and
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden pattern */
965c9c7794176a6815b197f53aa146a29e181b99Neil Madden (void)mailbox_list_join_refpattern(ns->list, ns_name, "");
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden }
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden /* When listing pub/ namespace, skip over the namespace
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden prefix in the name. the rest of the name is storage_name. */
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if (ns == NULL)
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden ns = default_ns;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden else if (strncmp(ns_name, ns->prefix, ns->prefix_len) == 0) {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden ns_name += ns->prefix_len;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden name = ns_name;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden } else {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden /* "pub" entry - this shouldn't be possible normally, because
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden it should be saved as "pub/", but handle it anyway */
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden i_assert(strncmp(ns_name, ns->prefix, ns->prefix_len-1) == 0 &&
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden ns_name[ns->prefix_len-1] == '\0');
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden name = ns_name = "";
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden }
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden len = strlen(name);
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if (len > 0 && name[len-1] == mail_namespace_get_sep(ns)) {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden /* entry ends with hierarchy separator, remove it.
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden this exists mainly for backwards compatibility with old
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden Dovecot versions and non-Dovecot software that added them */
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden name = t_strndup(name, len-1);
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden }
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if (!mailbox_list_is_valid_name(list, name, &error)) {
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson /* we'll only get into trouble if we show this */
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson return -1;
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson } else {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden vname = mailbox_list_get_vname(list, name);
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if (!uni_utf8_str_is_valid(vname))
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson return -1;
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson node = mailbox_tree_get(list->subscriptions, vname, &created);
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson node->flags = MAILBOX_SUBSCRIBED;
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson }
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson return 0;
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson}
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearsonint mailbox_list_subscriptions_refresh(struct mailbox_list *src_list,
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson struct mailbox_list *dest_list)
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson{
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson struct subsfile_list_context *subsfile_ctx;
4dc8bf8db4b0422db4c958bb68a450f04c56431aJoel Pearson struct stat st;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden enum mailbox_list_path_type type;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden const char *path, *name;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden char sep;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden int ret;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden /* src_list is subscriptions=yes, dest_list is subscriptions=no
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden (or the same as src_list) */
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden i_assert((src_list->ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) != 0);
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if (dest_list->subscriptions == NULL) {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden sep = mail_namespace_get_sep(src_list->ns);
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden dest_list->subscriptions = mailbox_tree_init(sep);
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden }
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden type = src_list->set.control_dir != NULL ?
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden MAILBOX_LIST_PATH_TYPE_CONTROL : MAILBOX_LIST_PATH_TYPE_DIR;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if (!mailbox_list_get_root_path(src_list, type, &path) ||
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden src_list->set.subscription_fname == NULL) {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden /* no subscriptions (e.g. pop3c) */
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden return 0;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden }
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden path = t_strconcat(path, "/", src_list->set.subscription_fname, NULL);
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if (stat(path, &st) < 0) {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if (errno == ENOENT) {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden /* no subscriptions */
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden mailbox_tree_clear(dest_list->subscriptions);
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden dest_list->subscriptions_mtime = 0;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden return 0;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden }
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden mailbox_list_set_critical(dest_list, "stat(%s) failed: %m",
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden path);
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden return -1;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden }
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if (st.st_mtime == dest_list->subscriptions_mtime &&
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden st.st_mtime < dest_list->subscriptions_read_time-1) {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden /* we're up to date */
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden return 0;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden }
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden mailbox_tree_clear(dest_list->subscriptions);
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden dest_list->subscriptions_read_time = ioloop_time;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden subsfile_ctx = subsfile_list_init(dest_list, path);
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden if (subsfile_list_fstat(subsfile_ctx, &st) == 0)
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden dest_list->subscriptions_mtime = st.st_mtime;
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden while ((name = subsfile_list_next(subsfile_ctx)) != NULL) T_BEGIN {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden T_BEGIN {
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden ret = mailbox_list_subscription_fill_one(dest_list,
c8ab19d28fde5eda3b2daab4b1124887681fedf9Neil Madden src_list, name);
} T_END;
if (ret < 0) {
i_warning("Subscriptions file %s: "
"Removing invalid entry: %s",
path, name);
(void)subsfile_set_subscribed(src_list, path,
mailbox_list_get_temp_prefix(src_list),
name, FALSE);
}
} T_END;
if (subsfile_list_deinit(&subsfile_ctx) < 0) {
dest_list->subscriptions_mtime = (time_t)-1;
return -1;
}
return 0;
}
void mailbox_list_set_subscription_flags(struct mailbox_list *list,
const char *vname,
enum mailbox_info_flags *flags)
{
struct mailbox_node *node;
*flags &= ~(MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
node = mailbox_tree_lookup(list->subscriptions, vname);
if (node != NULL) {
*flags |= node->flags & MAILBOX_SUBSCRIBED;
/* the only reason why node might have a child is if one of
them is subscribed */
if (node->children != NULL)
*flags |= MAILBOX_CHILD_SUBSCRIBED;
}
}
void mailbox_list_subscriptions_fill(struct mailbox_list_iterate_context *ctx,
struct mailbox_tree_context *tree,
bool default_nonexistent)
{
struct mailbox_list_iter_update_context update_ctx;
struct mailbox_tree_iterate_context *iter;
struct mailbox_node *node;
const char *name;
memset(&update_ctx, 0, sizeof(update_ctx));
update_ctx.iter_ctx = ctx;
update_ctx.tree_ctx = tree;
update_ctx.glob = ctx->glob;
update_ctx.leaf_flags = MAILBOX_SUBSCRIBED;
if (default_nonexistent)
update_ctx.leaf_flags |= MAILBOX_NONEXISTENT;
update_ctx.parent_flags = MAILBOX_CHILD_SUBSCRIBED;
update_ctx.match_parents =
(ctx->flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0;
iter = mailbox_tree_iterate_init(ctx->list->subscriptions, NULL,
MAILBOX_SUBSCRIBED);
while ((node = mailbox_tree_iterate_next(iter, &name)) != NULL)
mailbox_list_iter_update(&update_ctx, name);
mailbox_tree_iterate_deinit(&iter);
}
struct mailbox_list_iterate_context *
mailbox_list_subscriptions_iter_init(struct mailbox_list *list,
const char *const *patterns,
enum mailbox_list_iter_flags flags)
{
struct subscriptions_mailbox_list_iterate_context *ctx;
pool_t pool;
char sep = mail_namespace_get_sep(list->ns);
pool = pool_alloconly_create("mailbox list subscriptions iter", 1024);
ctx = p_new(pool, struct subscriptions_mailbox_list_iterate_context, 1);
ctx->ctx.pool = pool;
ctx->ctx.list = list;
ctx->ctx.flags = flags;
ctx->ctx.glob = imap_match_init_multiple(pool, patterns, TRUE, sep);
array_create(&ctx->ctx.module_contexts, pool, sizeof(void *), 5);
ctx->tree = mailbox_tree_init(sep);
mailbox_list_subscriptions_fill(&ctx->ctx, ctx->tree, FALSE);
ctx->info.ns = list->ns;
/* the tree usually has only those entries we want to iterate through,
but there are also non-matching root entries (e.g. "LSUB foo/%" will
include the "foo"), which we'll drop with MAILBOX_MATCHED. */
ctx->iter = mailbox_tree_iterate_init(ctx->tree, NULL, MAILBOX_MATCHED);
return &ctx->ctx;
}
const struct mailbox_info *
mailbox_list_subscriptions_iter_next(struct mailbox_list_iterate_context *_ctx)
{
struct subscriptions_mailbox_list_iterate_context *ctx =
(struct subscriptions_mailbox_list_iterate_context *)_ctx;
struct mailbox_list *list = _ctx->list;
struct mailbox_node *node;
enum mailbox_info_flags subs_flags;
const char *vname, *storage_name, *error;
int ret;
node = mailbox_tree_iterate_next(ctx->iter, &vname);
if (node == NULL)
return NULL;
ctx->info.vname = vname;
subs_flags = node->flags & (MAILBOX_SUBSCRIBED |
MAILBOX_CHILD_SUBSCRIBED);
if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0 &&
(_ctx->flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) == 0) {
/* don't care about flags, just return it */
ctx->info.flags = subs_flags;
return &ctx->info;
}
storage_name = mailbox_list_get_storage_name(list, vname);
if (!mailbox_list_is_valid_name(list, storage_name, &error)) {
/* broken entry in subscriptions file */
ctx->info.flags = MAILBOX_NONEXISTENT;
} else if (mailbox_list_mailbox(list, storage_name,
&ctx->info.flags) < 0) {
ctx->info.flags = 0;
_ctx->failed = TRUE;
} else if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) != 0 &&
(ctx->info.flags & (MAILBOX_CHILDREN |
MAILBOX_NOCHILDREN)) == 0) {
ret = mailbox_has_children(list, storage_name);
if (ret < 0)
_ctx->failed = TRUE;
else if (ret == 0)
ctx->info.flags |= MAILBOX_NOCHILDREN;
else
ctx->info.flags |= MAILBOX_CHILDREN;
}
ctx->info.flags &= ~(MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
ctx->info.flags |=
node->flags & (MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
return &ctx->info;
}
int mailbox_list_subscriptions_iter_deinit(struct mailbox_list_iterate_context *_ctx)
{
struct subscriptions_mailbox_list_iterate_context *ctx =
(struct subscriptions_mailbox_list_iterate_context *)_ctx;
int ret = _ctx->failed ? -1 : 0;
mailbox_tree_iterate_deinit(&ctx->iter);
mailbox_tree_deinit(&ctx->tree);
pool_unref(&_ctx->pool);
return ret;
}