view src/plugins/acl/acl-lookup-dict.c @ 8433:dfe39e9a9e78 HEAD

Initial support for LISTing users with shared mailboxes.
author Timo Sirainen <tss@iki.fi>
date Sun, 16 Nov 2008 19:20:28 +0200
parents
children 6d5ca089bd1e
line wrap: on
line source

/* Copyright (c) 2008 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "str.h"
#include "dict.h"
#include "mail-user.h"
#include "mail-namespace.h"
#include "acl-api-private.h"
#include "acl-storage.h"
#include "acl-plugin.h"
#include "acl-lookup-dict.h"

#include <stdlib.h>

#define DICT_SHARED_BOXES_PATH "shared-boxes/"

struct acl_lookup_dict {
	struct mail_user *user;
};

struct acl_lookup_dict_iter {
	pool_t pool;
	struct acl_lookup_dict *dict;

	ARRAY_TYPE(const_string) iter_ids;
	struct dict_iterate_context *dict_iter;
	unsigned int iter_idx;

	const char *prefix;
	unsigned int prefix_len;

	unsigned int failed:1;
};

static struct dict *acl_dict;

void acl_lookup_dicts_init(void)
{
	const char *uri;

	uri = getenv("ACL_SHARED_DICT");
	if (uri == NULL) {
		if (getenv("DEBUG") != NULL) {
			i_info("acl: No acl_shared_dict setting - "
			       "shared mailbox listing is disabled");
		}
		return;
	}

	acl_dict = dict_init(uri, DICT_DATA_TYPE_STRING, "");
	if (acl_dict == NULL)
		i_fatal("acl: dict_init(%s) failed", uri);
}

void acl_lookup_dicts_deinit(void)
{
	if (acl_dict != NULL)
		dict_deinit(&acl_dict);
}

struct acl_lookup_dict *acl_lookup_dict_init(struct mail_user *user)
{
	struct acl_lookup_dict *dict;

	dict = i_new(struct acl_lookup_dict, 1);
	dict->user = user;
	return dict;
}

void acl_lookup_dict_deinit(struct acl_lookup_dict **_dict)
{
	struct acl_lookup_dict *dict = *_dict;

	*_dict = NULL;
	i_free(dict);
}

static void
acl_lookup_dict_write_rights_id(string_t *dest, const struct acl_rights *right)
{
	switch (right->id_type) {
	case ACL_ID_ANYONE:
	case ACL_ID_AUTHENTICATED:
		/* don't bother separating these */
		str_append(dest, "anyone");
		break;
	case ACL_ID_USER:
		str_append(dest, "user/");
		str_append(dest, right->identifier);
		break;
	case ACL_ID_GROUP:
	case ACL_ID_GROUP_OVERRIDE:
		str_append(dest, "group/");
		str_append(dest, right->identifier);
		break;
	case ACL_ID_OWNER:
	case ACL_ID_TYPE_COUNT:
		i_unreached();
	}
}

static int acl_lookup_dict_rebuild_add_backend(struct mail_namespace *ns,
					       ARRAY_TYPE(const_string) *ids)
{
	struct acl_backend *backend;
	struct acl_mailbox_list_context *ctx;
	struct acl_object *aclobj;
	struct acl_object_list_iter *iter;
	struct acl_rights rights;
	const char *name, *id_dup;
	string_t *id;
	int ret, ret2 = 0;

	id = t_str_new(128);
	backend = acl_storage_get_backend(ns->storage);
	ctx = acl_backend_nonowner_lookups_iter_init(backend);
	while ((ret = acl_backend_nonowner_lookups_iter_next(ctx, &name)) > 0) {
		aclobj = acl_object_init_from_name(backend, ns->storage, name);

		iter = acl_object_list_init(aclobj);
		while ((ret = acl_object_list_next(iter, &rights)) > 0) {
			if (acl_rights_has_nonowner_lookup_changes(&rights)) {
				str_truncate(id, 0);
				acl_lookup_dict_write_rights_id(id, &rights);
				id_dup = t_strdup(str_c(id));
				array_append(ids, &id_dup, 1);
			}
		}
		acl_object_list_deinit(&iter);
		if (ret < 0)
			ret2 = -1;
		acl_object_deinit(&aclobj);
	}
	acl_backend_nonowner_lookups_iter_deinit(&ctx);
	return ret < 0 || ret2 < 0 ? -1 : 0;
}

static int
acl_lookup_dict_rebuild_update(struct acl_lookup_dict *dict,
			       const ARRAY_TYPE(const_string) *new_ids_arr,
			       bool no_removes)
{
	const char *username = dict->user->username;
	struct dict_iterate_context *iter;
	struct dict_transaction_context *dt;
	const char *prefix, *key, *value, **old_ids, *const *new_ids, *p;
	ARRAY_TYPE(const_string) old_ids_arr;
	unsigned int newi, oldi, old_count, new_count;
	string_t *path;
	unsigned int prefix_len;
	int ret;

	/* get all existing identifiers for the user */
	t_array_init(&old_ids_arr, 128);
	prefix = DICT_PATH_SHARED DICT_SHARED_BOXES_PATH;
	prefix_len = strlen(prefix);
	iter = dict_iterate_init(acl_dict, prefix, DICT_ITERATE_FLAG_RECURSE);
	while ((ret = dict_iterate(iter, &key, &value)) > 0) {
		/* prefix/$dest/$source */
		key += prefix_len;
		p = strchr(key, '/');
		if (p != NULL && strcmp(p + 1, username) == 0) {
			key = t_strdup_until(key, p);
			array_append(&old_ids_arr, &key, 1);
		}
	}
	dict_iterate_deinit(&iter);
	if (ret < 0) {
		i_error("acl: dict iteration failed, can't update dict");
		return -1;
	}

	/* sort the existing identifiers */
	old_ids = array_get_modifiable(&old_ids_arr, &old_count);
	qsort(old_ids, old_count, sizeof(*old_ids), i_strcmp_p);

	/* sync the identifiers */
	path = t_str_new(256);
	str_append(path, prefix);

	dt = dict_transaction_begin(acl_dict);
	new_ids = array_get(new_ids_arr, &new_count);
	for (newi = oldi = 0; newi < new_count || oldi < old_count; ) {
		ret = newi == new_count ? 1 :
			oldi == old_count ? -1 :
			strcmp(new_ids[newi], old_ids[oldi]);
		if (ret == 0) {
			newi++; oldi++;
		} else if (ret < 0) {
			/* new identifier, add it */
			str_truncate(path, prefix_len);
			str_append(path, new_ids[newi]);
			str_append_c(path, '/');
			str_append(path, username);
			dict_set(dt, str_c(path), "1");
			newi++;
		} else if (!no_removes) {
			/* old identifier removed */
			str_truncate(path, prefix_len);
			str_append(path, old_ids[oldi]);
			str_append_c(path, '/');
			str_append(path, username);
			dict_unset(dt, str_c(path));
			oldi++;
		}
	}
	if (dict_transaction_commit(&dt) < 0) {
		i_error("acl: dict commit failed");
		return -1;
	}
	return 0;
}

int acl_lookup_dict_rebuild(struct acl_lookup_dict *dict)
{
	struct mail_namespace *ns;
	ARRAY_TYPE(const_string) ids_arr;
	const char **ids;
	unsigned int i, dest, count;
	int ret = 0;

	/* get all ACL identifiers with a positive lookup right */
	t_array_init(&ids_arr, 128);
	for (ns = dict->user->namespaces; ns != NULL; ns = ns->next) {
		if (acl_lookup_dict_rebuild_add_backend(ns, &ids_arr) < 0)
			ret = -1;
	}

	/* sort identifiers and remove duplicates */
	ids = array_get_modifiable(&ids_arr, &count);
	qsort(ids, count, sizeof(*ids), i_strcmp_p);

	for (i = 1, dest = 0; i < count; i++) {
		if (strcmp(ids[dest], ids[i]) != 0) {
			if (++dest != i)
				ids[dest] = ids[i];
		}
	}
	if (++dest < count)
		array_delete(&ids_arr, dest, count-dest);

	/* if lookup failed at some point we can still add new ids,
	   but we can't remove any existing ones */
	if (acl_lookup_dict_rebuild_update(dict, &ids_arr, ret < 0) < 0)
		ret = -1;
	return ret;
}

static void acl_lookup_dict_iterate_start(struct acl_lookup_dict_iter *iter)
{
	const char *const *idp;

	idp = array_idx(&iter->iter_ids, iter->iter_idx);
	iter->iter_idx++;
	iter->prefix = p_strconcat(iter->pool, DICT_PATH_SHARED
				   DICT_SHARED_BOXES_PATH, *idp, "/", NULL);
	iter->prefix_len = strlen(iter->prefix);

	iter->dict_iter = dict_iterate_init(acl_dict, iter->prefix,
					    DICT_ITERATE_FLAG_RECURSE);
}

struct acl_lookup_dict_iter *
acl_lookup_dict_iterate_visible_init(struct acl_lookup_dict *dict)
{
	struct acl_user *auser = ACL_USER_CONTEXT(dict->user);
	struct acl_lookup_dict_iter *iter;
	const char *id;
	unsigned int i;
	pool_t pool;

	pool = pool_alloconly_create("acl lookup dict iter", 512);
	iter = p_new(pool, struct acl_lookup_dict_iter, 1);
	iter->pool = pool;
	iter->dict = dict;

	p_array_init(&iter->iter_ids, pool, 16);
	id = "anyone";
	array_append(&iter->iter_ids, &id, 1);
	id = p_strconcat(pool, "user/", dict->user->username, NULL);
	array_append(&iter->iter_ids, &id, 1);

	/* get all groups we belong to */
	if (auser->groups != NULL) {
		for (i = 0; auser->groups[i] != NULL; i++) {
			id = p_strconcat(pool, "group/", auser->groups[i],
					 NULL);
			array_append(&iter->iter_ids, &id, 1);
		}
	}

	/* iterate through all identifiers that match us, start with the
	   first one */
	acl_lookup_dict_iterate_start(iter);
	return iter;
}

const char *
acl_lookup_dict_iterate_visible_next(struct acl_lookup_dict_iter *iter)
{
	const char *key, *value;
	int ret;

	ret = dict_iterate(iter->dict_iter, &key, &value);
	if (ret > 0) {
		i_assert(iter->prefix_len < strlen(key));
		return key + iter->prefix_len;
	}
	if (ret < 0)
		iter->failed = TRUE;
	dict_iterate_deinit(&iter->dict_iter);

	if (iter->iter_idx < array_count(&iter->iter_ids)) {
		/* get to the next iterator */
		acl_lookup_dict_iterate_start(iter);
		return acl_lookup_dict_iterate_visible_next(iter);
	}
	return NULL;
}

int acl_lookup_dict_iterate_visible_deinit(struct acl_lookup_dict_iter **_iter)
{
	struct acl_lookup_dict_iter *iter = *_iter;
	int ret = iter->failed ? -1 : 0;

	*_iter = NULL;
	if (iter->dict_iter != NULL)
		dict_iterate_deinit(&iter->dict_iter);
	pool_unref(&iter->pool);
	return ret;
}