view src/plugins/autocreate/autocreate-plugin.c @ 12863:b16512b60808

autocreate: Don't list autocreate mailboxes with MAILBOX_LIST_ITER_NO_AUTO_BOXES.
author Timo Sirainen <tss@iki.fi>
date Wed, 23 Mar 2011 22:55:03 +0200
parents 3984231cd873
children ac8a28a8b5c0
line wrap: on
line source

/* Copyright (c) 2007-2011 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "unichar.h"
#include "imap-match.h"
#include "mailbox-list-private.h"
#include "mail-storage-private.h"
#include "mail-storage-hooks.h"
#include "autocreate-plugin.h"

#include <stdlib.h>

#define AUTOCREATE_USER_CONTEXT(obj) \
	MODULE_CONTEXT(obj, autocreate_user_module)
#define AUTOCREATE_LIST_CONTEXT(obj) \
	MODULE_CONTEXT(obj, autocreate_list_module)
#define AUTOCREATE_CONTEXT(obj) \
	MODULE_CONTEXT(obj, autocreate_storage_module)

enum match_result {
	/* list contains the mailbox */
	MATCH_RESULT_YES	= 0x01,
	/* list contains children of the mailbox */
	MATCH_RESULT_CHILDREN	= 0x02,
	/* list contains parents of the mailbox */
	MATCH_RESULT_PARENT	= 0x04
};

struct autocreate_box {
	const char *name;
	unsigned int name_len;
	enum mailbox_info_flags flags;
	bool child_listed;

	struct mail_namespace *ns;
};
ARRAY_DEFINE_TYPE(autocreate_box, struct autocreate_box);

struct autocreate_user {
	union mail_user_module_context module_ctx;

	ARRAY_TYPE(autocreate_box) autocreate_mailboxes;
	ARRAY_TYPE(autocreate_box) autosubscribe_mailboxes;
};

struct autocreate_mailbox_list_iterate_context {
	union mailbox_list_iterate_module_context module_ctx;

	pool_t pool;
	unsigned int idx;
	struct mailbox_info new_info;
	ARRAY_TYPE(autocreate_box) boxes;
};

struct autocreate_mailbox_list {
	union mailbox_list_module_context module_ctx;
};

const char *autocreate_plugin_version = DOVECOT_VERSION;

static MODULE_CONTEXT_DEFINE_INIT(autocreate_user_module,
				  &mail_user_module_register);
static MODULE_CONTEXT_DEFINE_INIT(autocreate_list_module,
				  &mailbox_list_module_register);
static MODULE_CONTEXT_DEFINE_INIT(autocreate_storage_module,
				  &mail_storage_module_register);

static enum match_result
autocreate_box_match(const ARRAY_TYPE(autocreate_box) *boxes, const char *name,
		     unsigned int *idx_r)
{
	const struct autocreate_box *autoboxes;
	unsigned int i, count, len, name_len = strlen(name);
	enum match_result result = 0;
	char sep;

	*idx_r = -1U;

	autoboxes = array_get(boxes, &count);
	for (i = 0; i < count; i++) {
		len = I_MIN(name_len, autoboxes[i].name_len);
		if (strncmp(name, autoboxes[i].name, len) != 0)
			continue;

		sep = mail_namespace_get_sep(autoboxes[i].ns);
		if (name[len] == '\0' && autoboxes[i].name[len] == '\0') {
			result |= MATCH_RESULT_YES;
			*idx_r = i;
		} else if (name[len] == '\0' && autoboxes[i].name[len] == sep)
			result |= MATCH_RESULT_CHILDREN;
		else if (name[len] == sep && autoboxes[i].name[len] == '\0')
			result |= MATCH_RESULT_PARENT;
	}
	return result;
}

static bool
is_autocreated(struct mail_user *user, const char *name)
{
	struct autocreate_user *auser = AUTOCREATE_USER_CONTEXT(user);
	unsigned int idx;

	return autocreate_box_match(&auser->autocreate_mailboxes,
				    name, &idx) == MATCH_RESULT_YES;
}

static bool
is_autosubscribed(struct mail_user *user, const char *name)
{
	struct autocreate_user *auser = AUTOCREATE_USER_CONTEXT(user);
	unsigned int idx;

	return autocreate_box_match(&auser->autosubscribe_mailboxes,
				    name, &idx) == MATCH_RESULT_YES;
}

static int autocreate_mailbox_open(struct mailbox *box)
{
	union mailbox_module_context *abox = AUTOCREATE_CONTEXT(box);
	int ret;

	if ((ret = abox->super.open(box)) < 0 &&
	    mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTFOUND &&
	    is_autocreated(box->storage->user, box->vname)) {
		/* autocreate the mailbox */
		if (mailbox_create(box, NULL, FALSE) < 0) {
			i_error("autocreate: Failed to create mailbox %s: %s",
				box->vname, mailbox_get_last_error(box, NULL));
		}
		mailbox_close(box);
		ret = box->v.open(box);
	}
	return ret;
}

static int autocreate_mailbox_exists(struct mailbox *box,
				     enum mailbox_existence *existence_r)
{
	union mailbox_module_context *abox = AUTOCREATE_CONTEXT(box);

	if (is_autocreated(box->storage->user, box->vname)) {
		*existence_r = MAILBOX_EXISTENCE_SELECT;
		return 0;
	}

	return abox->super.exists(box, existence_r);
}

static int
autocreate_mailbox_create(struct mailbox *box,
			  const struct mailbox_update *update,
			  bool directory)
{
	union mailbox_module_context *abox = AUTOCREATE_CONTEXT(box);

	if (abox->super.create(box, update, directory) < 0)
		return -1;

	if (is_autosubscribed(box->storage->user, box->vname)) {
		if (mailbox_set_subscribed(box, TRUE) < 0) {
			i_error("autocreate: Failed to subscribe to mailbox %s: %s",
				box->vname, mailbox_get_last_error(box, NULL));
		}
	}
	return 0;
}

static void autocreate_mailbox_allocated(struct mailbox *box)
{
	struct mailbox_vfuncs *v = box->vlast;
	union mailbox_module_context *abox;

	abox = p_new(box->pool, union mailbox_module_context, 1);
	abox->super = *v;
	box->vlast = &abox->super;
	v->open = autocreate_mailbox_open;
	v->exists = autocreate_mailbox_exists;
	v->create = autocreate_mailbox_create;

	MODULE_CONTEXT_SET_SELF(box, autocreate_storage_module, abox);
}

static struct mailbox_list_iterate_context *
autocreate_iter_init(struct mailbox_list *list,
		     const char *const *patterns,
		     enum mailbox_list_iter_flags flags)
{
	union mailbox_list_module_context *alist =
		AUTOCREATE_LIST_CONTEXT(list);
	struct mail_user *user = list->ns->user;
	struct autocreate_user *auser = AUTOCREATE_USER_CONTEXT(user);
	struct mailbox_list_iterate_context *ctx;
	struct autocreate_mailbox_list_iterate_context *actx;
	const ARRAY_TYPE(autocreate_box) *extra_boxes;
	const struct autocreate_box *autobox;
	pool_t pool;

	ctx = alist->super.iter_init(list, patterns, flags);

	pool = pool_alloconly_create("autocreate list iter", 1024);
	actx = p_new(pool, struct autocreate_mailbox_list_iterate_context, 1);
	actx->pool = pool;

	p_array_init(&actx->boxes, pool, 16);
	if ((flags & MAILBOX_LIST_ITER_NO_AUTO_BOXES) == 0) {
		if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0)
			extra_boxes = &auser->autocreate_mailboxes;
		else
			extra_boxes = &auser->autosubscribe_mailboxes;

		array_foreach(extra_boxes, autobox) {
			if (autobox->ns == list->ns)
				array_append(&actx->boxes, autobox, 1);
		}
	}

	MODULE_CONTEXT_SET(ctx, autocreate_list_module, actx);
	return ctx;
}

static int autocreate_iter_deinit(struct mailbox_list_iterate_context *ctx)
{
	union mailbox_list_module_context *alist =
		AUTOCREATE_LIST_CONTEXT(ctx->list);
	struct autocreate_mailbox_list_iterate_context *actx =
		AUTOCREATE_LIST_CONTEXT(ctx);

	pool_unref(&actx->pool);
	return alist->super.iter_deinit(ctx);
}

static const struct mailbox_info *
autocreate_iter_existing(struct mailbox_list_iterate_context *ctx)
{
	struct autocreate_mailbox_list_iterate_context *actx =
		AUTOCREATE_LIST_CONTEXT(ctx);
	struct autocreate_user *auser =
		AUTOCREATE_USER_CONTEXT(ctx->list->ns->user);
	struct mailbox_info *info = &actx->new_info;
	enum match_result match, match2;
	unsigned int idx;

	match = autocreate_box_match(&actx->boxes, info->name, &idx);

	if ((match & MATCH_RESULT_YES) != 0) {
		/* we have an exact match in the list.
		   don't list it at the end. */
		array_delete(&actx->boxes, idx, 1);
	}

	if ((match & MATCH_RESULT_CHILDREN) != 0) {
		if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
			info->flags |= MAILBOX_CHILD_SUBSCRIBED;
		else {
			info->flags &= ~MAILBOX_NOCHILDREN;
			info->flags |= MAILBOX_CHILDREN;
		}
	}

	/* make sure the mailbox existence flags are correct. */
	if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0)
		match2 = match;
	else {
		info->flags |= MAILBOX_SUBSCRIBED;
		match2 = autocreate_box_match(&auser->autocreate_mailboxes,
					      info->name, &idx);
	}
	if ((match2 & MATCH_RESULT_YES) != 0)
		info->flags &= ~MAILBOX_NONEXISTENT;
	if ((match2 & MATCH_RESULT_CHILDREN) != 0) {
		info->flags &= ~MAILBOX_NOCHILDREN;
		info->flags |= MAILBOX_CHILDREN;
	}

	if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0 &&
	    (ctx->flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) != 0) {
		/* we're listing all mailboxes and want \Subscribed flag */
		match2 = autocreate_box_match(&auser->autosubscribe_mailboxes,
					      info->name, &idx);
		if ((match2 & MATCH_RESULT_YES) != 0) {
			/* mailbox is also marked as autosubscribe */
			info->flags |= MAILBOX_SUBSCRIBED;
		}
		if ((match2 & MATCH_RESULT_CHILDREN) != 0) {
			/* mailbox also has a children marked as
			   autosubscribe */
			info->flags |= MAILBOX_CHILD_SUBSCRIBED;
		}
	}

	if ((match & MATCH_RESULT_PARENT) != 0) {
		/* there are autocreate parent boxes.
		   set their children flag states. */
		struct autocreate_box *autobox;
		char sep;

		array_foreach_modifiable(&actx->boxes, autobox) {
			sep = mail_namespace_get_sep(autobox->ns);

			if (strncmp(info->name, autobox->name,
				    autobox->name_len) != 0 ||
			    info->name[autobox->name_len] != sep)
				continue;

			if ((info->flags & MAILBOX_NONEXISTENT) == 0)
				autobox->flags |= MAILBOX_CHILDREN;
			if ((info->flags & MAILBOX_SUBSCRIBED) != 0)
				autobox->flags |= MAILBOX_CHILD_SUBSCRIBED;
			autobox->child_listed = TRUE;
		}
	}
	return info;
}

static bool autocreate_iter_autobox(struct mailbox_list_iterate_context *ctx,
				    const struct autocreate_box *autobox)
{
	struct autocreate_mailbox_list_iterate_context *actx =
		AUTOCREATE_LIST_CONTEXT(ctx);
	enum match_result match;

	memset(&actx->new_info, 0, sizeof(actx->new_info));
	actx->new_info.ns = ctx->list->ns;
	actx->new_info.name = autobox->name;
	actx->new_info.flags = autobox->flags;

	if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
		actx->new_info.flags |= MAILBOX_SUBSCRIBED;

	if ((actx->new_info.flags & MAILBOX_CHILDREN) == 0)
		actx->new_info.flags |= MAILBOX_NOCHILDREN;

	match = imap_match(ctx->glob, actx->new_info.name);
	if (match == IMAP_MATCH_YES)
		return TRUE;
	if ((match & IMAP_MATCH_PARENT) != 0 && !autobox->child_listed) {
		enum mailbox_info_flags old_flags = actx->new_info.flags;
		char sep = mail_namespace_get_sep(ctx->list->ns);
		const char *p;

		/* e.g. autocreate=foo/bar and we're listing % */
		actx->new_info.flags = MAILBOX_NONEXISTENT |
			(old_flags & (MAILBOX_CHILDREN |
				      MAILBOX_CHILD_SUBSCRIBED));
		if ((old_flags & MAILBOX_NONEXISTENT) == 0) {
			actx->new_info.flags |= MAILBOX_CHILDREN;
			actx->new_info.flags &= ~MAILBOX_NOCHILDREN;
		}
		if ((old_flags & MAILBOX_SUBSCRIBED) != 0)
			actx->new_info.flags |= MAILBOX_CHILD_SUBSCRIBED;
		do {
			p = strrchr(actx->new_info.name, sep);
			i_assert(p != NULL);
			actx->new_info.name =
				t_strdup_until(actx->new_info.name, p);
			match = imap_match(ctx->glob, actx->new_info.name);
		} while (match != IMAP_MATCH_YES);
		return TRUE;
	}
	return FALSE;
}

static const struct mailbox_info *
autocreate_iter_next(struct mailbox_list_iterate_context *ctx)
{
	union mailbox_list_module_context *alist =
		AUTOCREATE_LIST_CONTEXT(ctx->list);
	struct autocreate_mailbox_list_iterate_context *actx =
		AUTOCREATE_LIST_CONTEXT(ctx);
	const struct mailbox_info *info;
	const struct autocreate_box *autoboxes;
	unsigned int count;

	if (actx->idx == 0) {
		info = alist->super.iter_next(ctx);
		if (info != NULL) {
			actx->new_info = *info;
			return autocreate_iter_existing(ctx);
		}
	}

	/* list missing mailboxes */
	autoboxes = array_get(&actx->boxes, &count);
	while (actx->idx < count) {
		if (autocreate_iter_autobox(ctx, &autoboxes[actx->idx++]))
			return &actx->new_info;
	}
	return NULL;
}

static void autocreate_mailbox_list_created(struct mailbox_list *list)
{
	struct mailbox_list_vfuncs *v = list->vlast;
	union mailbox_list_module_context *alist;

	alist = p_new(list->pool, union mailbox_list_module_context, 1);
	alist->super = *v;
	list->vlast = &alist->super;
	v->iter_init = autocreate_iter_init;
	v->iter_deinit = autocreate_iter_deinit;
	v->iter_next = autocreate_iter_next;

	MODULE_CONTEXT_SET_SELF(list, autocreate_list_module, alist);
}

static void
add_autobox(struct mail_user *user, ARRAY_TYPE(autocreate_box) *boxes,
	    const char *value)
{
	struct autocreate_box *autobox;
	struct mail_namespace *ns;

	if (!uni_utf8_str_is_valid(value)) {
		i_error("autocreate: Mailbox name isn't valid UTF-8: %s",
			value);
		return;
	}

	if ((ns = mail_namespace_find(user->namespaces, value)) == NULL) {
		if (user->mail_debug) {
			i_debug("autocreate: Namespace not found for mailbox: %s",
				value);
		}
		return;
	}

	autobox = array_append_space(boxes);
	autobox->name = p_strdup(user->pool, value);
	autobox->name_len = strlen(value);
	autobox->ns = ns;
}

static void read_autobox_settings(struct mail_user *user,
				  ARRAY_TYPE(autocreate_box) *boxes,
				  const char *env_name_base)
{
	const char *value;
	char env_name[20];
	unsigned int i = 1;

	value = mail_user_plugin_getenv(user, env_name_base);
	while (value != NULL) {
		add_autobox(user, boxes, value);

		i_snprintf(env_name, sizeof(env_name), "%s%d",
			   env_name_base, ++i);
		value = mail_user_plugin_getenv(user, env_name);
	}
}

static void
autocreate_mail_namespaces_created(struct mail_namespace *namespaces)
{
	struct mail_user *user = namespaces->user;
	struct autocreate_user *auser;

	auser = p_new(user->pool, struct autocreate_user, 1);
	p_array_init(&auser->autocreate_mailboxes, user->pool, 8);
	read_autobox_settings(user, &auser->autocreate_mailboxes, "autocreate");

	p_array_init(&auser->autosubscribe_mailboxes, user->pool, 8);
	read_autobox_settings(user, &auser->autosubscribe_mailboxes,
			      "autosubscribe");

	MODULE_CONTEXT_SET(user, autocreate_user_module, auser);
}


static struct mail_storage_hooks autocreate_mail_storage_hooks = {
	.mailbox_allocated = autocreate_mailbox_allocated,
	.mailbox_list_created = autocreate_mailbox_list_created,
	.mail_namespaces_created = autocreate_mail_namespaces_created
};

void autocreate_plugin_init(struct module *module)
{
	mail_storage_hooks_add(module, &autocreate_mail_storage_hooks);
}

void autocreate_plugin_deinit(void)
{
	mail_storage_hooks_remove(&autocreate_mail_storage_hooks);
}