view src/plugins/acl/acl-mailbox-list.c @ 6044:3ca063521d9a HEAD

If user didn't have "lookup" right to mailbox, "insert" right didn't work.
author Timo Sirainen <tss@iki.fi>
date Tue, 17 Jul 2007 23:58:04 +0300
parents cc1f4688a988
children 65c69a53a7be
line wrap: on
line source

/* Copyright (C) 2006 Timo Sirainen */

#include "lib.h"
#include "array.h"
#include "imap-match.h"
#include "mailbox-tree.h"
#include "mail-namespace.h"
#include "mailbox-list-private.h"
#include "acl-cache.h"
#include "acl-api-private.h"
#include "acl-plugin.h"

#include <stdlib.h>

#define ACL_LIST_CONTEXT(obj) \
	MODULE_CONTEXT(obj, acl_mailbox_list_module)

#define MAILBOX_FLAG_MATCHED 0x40000000

struct acl_mailbox_list {
	union mailbox_list_module_context module_ctx;

	struct acl_storage_rights_context rights;
};

struct acl_mailbox_list_iterate_context {
	struct mailbox_list_iterate_context ctx;
	struct mailbox_list_iterate_context *super_ctx;

	struct mailbox_tree_context *tree;
	struct mailbox_tree_iterate_context *tree_iter;
	struct mailbox_info info;
};

static MODULE_CONTEXT_DEFINE_INIT(acl_mailbox_list_module,
				  &mailbox_list_module_register);

struct acl_backend *acl_mailbox_list_get_backend(struct mailbox_list *list)
{
	struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(list);

	return alist->rights.backend;
}

const char *acl_mailbox_list_get_parent_mailbox_name(struct mailbox_list *list,
						     const char *name)
{
	const char *p;
	char sep;

	sep = mailbox_list_get_hierarchy_sep(list);
	p = strrchr(name, sep);
	return p == NULL ? "" : t_strdup_until(name, p);
}

static int
acl_mailbox_list_have_right(struct acl_mailbox_list *alist, const char *name,
			    unsigned int acl_storage_right_idx, bool *can_see_r)
{
	return acl_storage_rights_ctx_have_right(&alist->rights, name,
						 acl_storage_right_idx,
						 can_see_r);
}

static bool
acl_mailbox_try_list_fast(struct acl_mailbox_list_iterate_context *ctx,
			  const char *const *patterns)
{
	struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(ctx->ctx.list);
	struct acl_backend *backend = alist->rights.backend;
	const unsigned int *idxp =
		alist->rights.acl_storage_right_idx + ACL_STORAGE_RIGHT_LOOKUP;
	const struct acl_mask *acl_mask;
	struct acl_mailbox_list_context *nonowner_list_ctx;
	struct imap_match_glob *glob;
	enum imap_match_result match;
	struct mailbox_node *node;
	const char *name;
	char sep;
	int try, ret;
	bool created;

	if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RAW_LIST) != 0)
		return FALSE;

	if (acl_backend_get_default_rights(backend, &acl_mask) < 0 ||
	    acl_cache_mask_isset(acl_mask, *idxp))
		return FALSE;

	/* default is to not list mailboxes. we can optimize this. */
	t_push();
	sep = mailbox_list_get_hierarchy_sep(ctx->ctx.list);
	glob = imap_match_init_multiple(pool_datastack_create(), patterns,
					TRUE, sep);

	for (try = 0; try < 2; try++) {
		nonowner_list_ctx =
			acl_backend_nonowner_lookups_iter_init(backend);
		ctx->tree = mailbox_tree_init(sep);

		while ((ret = acl_backend_nonowner_lookups_iter_next(
					nonowner_list_ctx, &name)) > 0) {
			match = imap_match(glob, name);
			if (match == IMAP_MATCH_YES) {
				node = mailbox_tree_get(ctx->tree, name,
							&created);
				if (created)
					node->flags |= MAILBOX_NOCHILDREN;
				node->flags |= MAILBOX_FLAG_MATCHED;
				node->flags &= ~MAILBOX_NONEXISTENT;
			} else if ((match & IMAP_MATCH_PARENT) != 0) {
				node = mailbox_tree_get(ctx->tree, name,
							&created);
				if (created)
					node->flags |= MAILBOX_NONEXISTENT;
				node->flags |= MAILBOX_FLAG_MATCHED |
					MAILBOX_CHILDREN;
				node->flags &= ~MAILBOX_NOCHILDREN;
			}
		}
		if (ret == 0)
			break;

		/* try again */
		mailbox_tree_deinit(&ctx->tree);
		acl_backend_nonowner_lookups_iter_deinit(&nonowner_list_ctx);
	}
	t_pop();
	if (ret < 0)
		return FALSE;

	ctx->tree_iter = mailbox_tree_iterate_init(ctx->tree, NULL,
						   MAILBOX_FLAG_MATCHED);
	return TRUE;
}

static struct mailbox_list_iterate_context *
acl_mailbox_list_iter_init(struct mailbox_list *list,
			   const char *const *patterns,
			   enum mailbox_list_iter_flags flags)
{
	struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(list);
	struct acl_mailbox_list_iterate_context *ctx;

	ctx = i_new(struct acl_mailbox_list_iterate_context, 1);
	ctx->ctx.list = list;
	ctx->ctx.flags = flags;

	if (!acl_mailbox_try_list_fast(ctx, patterns)) {
		ctx->super_ctx = alist->module_ctx.super.
			iter_init(list, patterns, flags);
	}
	return &ctx->ctx;
}

static const struct mailbox_info *
acl_mailbox_list_iter_next(struct mailbox_list_iterate_context *_ctx)
{
	struct acl_mailbox_list_iterate_context *ctx =
		(struct acl_mailbox_list_iterate_context *)_ctx;
	struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(_ctx->list);
	const struct mailbox_info *info;
	struct mailbox_node *node;
	int ret;

	for (;;) {
		if (ctx->tree_iter != NULL) {
			node = mailbox_tree_iterate_next(ctx->tree_iter,
							 &ctx->info.name);
			if (node == NULL)
				return NULL;
			ctx->info.flags = node->flags;
			info = &ctx->info;
		} else {
			info = alist->module_ctx.super.
				iter_next(ctx->super_ctx);
			if (info == NULL)
				return NULL;
		}

		if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RAW_LIST) != 0) {
			/* skip ACL checks. */
			return info;
		}

		ret = acl_mailbox_list_have_right(alist, info->name,
						  ACL_STORAGE_RIGHT_LOOKUP,
						  NULL);
		if (ret > 0)
			return info;
		if (ret < 0) {
			ctx->ctx.failed = TRUE;
			return NULL;
		}

		/* no permission to see this mailbox */
		if ((ctx->info.flags & MAILBOX_SUBSCRIBED) != 0) {
			/* it's subscribed, show it as non-existent */
			if (info != &ctx->info) {
				ctx->info = *info;
				info = &ctx->info;
			}
			ctx->info.flags = MAILBOX_NONEXISTENT |
				MAILBOX_SUBSCRIBED;
			return info;
		}

		/* skip to next one */
	}
}

static int
acl_mailbox_list_iter_deinit(struct mailbox_list_iterate_context *_ctx)
{
	struct acl_mailbox_list_iterate_context *ctx =
		(struct acl_mailbox_list_iterate_context *)_ctx;
	struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(_ctx->list);
	int ret = ctx->ctx.failed ? -1 : 0;

	if (ctx->super_ctx != NULL) {
		if (alist->module_ctx.super.iter_deinit(ctx->super_ctx) < 0)
			ret = -1;
	}
	if (ctx->tree_iter != NULL)
		mailbox_tree_iterate_deinit(&ctx->tree_iter);

	i_free(ctx);
	return ret;
}

static int acl_get_mailbox_name_status(struct mailbox_list *list,
				       const char *name,
				       enum mailbox_name_status *status)
{
	struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(list);
	const char *parent;
	int ret;

	ret = acl_mailbox_list_have_right(alist, name, ACL_STORAGE_RIGHT_LOOKUP,
					  NULL);
	if (ret < 0)
		return -1;
	if (ret == 0) {
		/* If we have INSERT right for the mailbox, we'll need to
		   reveal its existence so that APPEND and COPY works. */
		ret = acl_mailbox_list_have_right(alist, name,
						  ACL_STORAGE_RIGHT_INSERT,
						  NULL);
		if (ret < 0)
			return -1;
	}

	if (alist->module_ctx.super.get_mailbox_name_status(list, name,
							    status) < 0)
		return -1;
	if (ret > 0)
		return 0;

	/* we shouldn't reveal this mailbox's existance */
	switch (*status) {
	case MAILBOX_NAME_EXISTS:
		*status = MAILBOX_NAME_VALID;
		break;
	case MAILBOX_NAME_VALID:
	case MAILBOX_NAME_INVALID:
		break;
	case MAILBOX_NAME_NOINFERIORS:
		/* have to check if we are allowed to see the parent */
		t_push();
		parent = acl_mailbox_list_get_parent_mailbox_name(list, name);
		ret = acl_mailbox_list_have_right(alist, parent,
						  ACL_STORAGE_RIGHT_LOOKUP,
						  NULL);
		t_pop();

		if (ret < 0)
			return -1;
		if (ret == 0) {
			/* no permission to see the parent */
			*status = MAILBOX_NAME_VALID;
		}
		break;
	}
	return 0;
}

static int
acl_mailbox_list_delete(struct mailbox_list *list, const char *name)
{
	struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(list);
	bool can_see;
	int ret;

	ret = acl_mailbox_list_have_right(alist, name, ACL_STORAGE_RIGHT_DELETE,
					  &can_see);
	if (ret <= 0) {
		if (ret < 0)
			return -1;
		if (can_see) {
			mailbox_list_set_error(list, MAIL_ERROR_PERM,
					       MAIL_ERRSTR_NO_PERMISSION);
		} else {
			mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
				T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
		}
		return -1;
	}

	return alist->module_ctx.super.delete_mailbox(list, name);
}

static int
acl_mailbox_list_rename(struct mailbox_list *list,
			const char *oldname, const char *newname)
{
	struct acl_mailbox_list *alist = ACL_LIST_CONTEXT(list);
	bool can_see;
	int ret;

	/* renaming requires rights to delete the old mailbox */
	ret = acl_mailbox_list_have_right(alist, oldname,
					  ACL_STORAGE_RIGHT_DELETE, &can_see);
	if (ret <= 0) {
		if (ret < 0)
			return -1;
		if (can_see) {
			mailbox_list_set_error(list, MAIL_ERROR_PERM,
					       MAIL_ERRSTR_NO_PERMISSION);
		} else {
			mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
				T_MAIL_ERR_MAILBOX_NOT_FOUND(oldname));
		}
		return 0;
	}

	/* and create the new one under the parent mailbox */
	t_push();
	ret = acl_mailbox_list_have_right(alist,
		acl_mailbox_list_get_parent_mailbox_name(list, newname),
		ACL_STORAGE_RIGHT_CREATE, NULL);
	t_pop();

	if (ret <= 0) {
		if (ret == 0) {
			/* Note that if the mailbox didn't have LOOKUP
			   permission, this not reveals to user the mailbox's
			   existence. Can't help it. */
			mailbox_list_set_error(list, MAIL_ERROR_PERM,
					       MAIL_ERRSTR_NO_PERMISSION);
		}
		return -1;
	}

	return alist->module_ctx.super.rename_mailbox(list, oldname, newname);
}

void acl_mailbox_list_created(struct mailbox_list *list)
{
	struct acl_mailbox_list *alist;
	struct acl_backend *backend;
	struct mail_namespace *ns;
	enum mailbox_list_flags flags;
	const char *acl_env, *current_username, *owner_username;
	bool owner = TRUE;

	if (acl_next_hook_mailbox_list_created != NULL)
		acl_next_hook_mailbox_list_created(list);

	acl_env = getenv("ACL");
	i_assert(acl_env != NULL);

	owner_username = getenv("USER");
	if (owner_username == NULL)
		i_fatal("ACL: USER environment not set");

	current_username = getenv("MASTER_USER");
	if (current_username == NULL)
		current_username = owner_username;
	else
		owner = strcmp(current_username, owner_username) == 0;

	/* We don't care about the username for non-private mailboxes.
	   It's used only when checking if we're the mailbox owner. We never
	   are for shared/public mailboxes. */
	ns = mailbox_list_get_namespace(list);
	if (ns->type != NAMESPACE_PRIVATE)
		owner = FALSE;

	/* FIXME: set groups */
	backend = acl_backend_init(acl_env, list, current_username,
				   getenv("ACL_GROUPS") == NULL ? NULL :
				   t_strsplit(getenv("ACL_GROUPS"), ","),
				   owner);
	if (backend == NULL)
		i_fatal("ACL backend initialization failed");

	flags = mailbox_list_get_flags(list);
	if ((flags & MAILBOX_LIST_FLAG_FULL_FS_ACCESS) != 0) {
		/* not necessarily, but safer to do this for now.. */
		i_fatal("mail_full_filesystem_access=yes is "
			"incompatible with ACLs");
	}

	alist = p_new(list->pool, struct acl_mailbox_list, 1);
	alist->module_ctx.super = list->v;
	list->v.iter_init = acl_mailbox_list_iter_init;
	list->v.iter_next = acl_mailbox_list_iter_next;
	list->v.iter_deinit = acl_mailbox_list_iter_deinit;
	list->v.get_mailbox_name_status = acl_get_mailbox_name_status;
	list->v.delete_mailbox = acl_mailbox_list_delete;
	list->v.rename_mailbox = acl_mailbox_list_rename;

	acl_storage_rights_ctx_init(&alist->rights, backend);

	MODULE_CONTEXT_SET(list, acl_mailbox_list_module, alist);
}