[dovecot-cvs] dovecot/src/plugins/acl .cvsignore, NONE, 1.1 Makefile.am, NONE, 1.1 acl-api-private.h, NONE, 1.1 acl-api.c, NONE, 1.1 acl-api.h, NONE, 1.1 acl-backend-vfile.c, NONE, 1.1 acl-backend.c, NONE, 1.1 acl-cache.c, NONE, 1.1 acl-cache.h, NONE, 1.1 acl-mailbox.c, NONE, 1.1 acl-plugin.c, NONE, 1.1 acl-plugin.h, NONE, 1.1 acl-storage.c, NONE, 1.1

tss-movial at dovecot.org tss-movial at dovecot.org
Mon Feb 27 18:30:42 EET 2006


Update of /var/lib/cvs/dovecot/src/plugins/acl
In directory talvi:/tmp/cvs-serv30569/plugins/acl

Added Files:
	.cvsignore Makefile.am acl-api-private.h acl-api.c acl-api.h 
	acl-backend-vfile.c acl-backend.c acl-cache.c acl-cache.h 
	acl-mailbox.c acl-plugin.c acl-plugin.h acl-storage.c 
Log Message:
Added initial support for ACLs. Currently supports reading the ACLs from files. Since no proper support for shared folders exist yet, this is mostly intended to be used only for preventing users from fully accessing the mailboxes they'd otherwise have full access to. It anyway does support "master user" having different ACLs to mailboxes.



--- NEW FILE: .cvsignore ---
*.la
*.lo
*.o
.deps
.libs
Makefile
Makefile.in
so_locations

--- NEW FILE: Makefile.am ---
AM_CPPFLAGS = \
	-I$(top_srcdir)/src/lib \
	-I$(top_srcdir)/src/lib-mail \
	-I$(top_srcdir)/src/lib-storage

lib01_acl_plugin_la_LDFLAGS = -module -avoid-version

module_LTLIBRARIES = \
	lib01_acl_plugin.la

lib01_acl_plugin_la_SOURCES = \
	acl-api.c \
	acl-backend.c \
	acl-backend-vfile.c \
	acl-cache.c \
	acl-mailbox.c \
	acl-plugin.c \
	acl-storage.c

noinst_HEADERS = \
	acl-api.h \
	acl-api-private.h \
	acl-cache.h \
	acl-plugin.h

install-exec-local:
	$(mkdir_p) $(DESTDIR)$(moduledir)/imap $(DESTDIR)$(moduledir)/lda
	for d in imap lda; do \
	  rm -f $(DESTDIR)$(moduledir)/$$d/lib01_acl_plugin.so; \
	  $(LN_S) ../lib01_acl_plugin.so $(DESTDIR)$(moduledir)/$$d; \
	done

--- NEW FILE: acl-api-private.h ---
#ifndef __ACL_API_PRIVATE_H
#define __ACL_API_PRIVATE_H

#include "acl-api.h"

struct acl_backend_vfuncs {
	struct acl_backend *(*init)(const char *data);
	void (*deinit)(struct acl_backend *backend);

	struct acl_object *(*object_init)(struct acl_backend *backend,
					  const char *name,
					  const char *control_dir);
	void (*object_deinit)(struct acl_object *aclobj);

	int (*object_refresh_cache)(struct acl_object *aclobj);
	int (*object_update)(struct acl_object *aclobj,
			     const struct acl_rights *rights);

	struct acl_object_list_iter *
		(*object_list_init)(struct acl_object *aclobj);
	int (*object_list_next)(struct acl_object_list_iter *iter,
				struct acl_rights *rights_r);
	void (*object_list_deinit)(struct acl_object_list_iter *iter);
};

struct acl_backend {
	pool_t pool;
	const char *username, *owner_username;
	const char **groups;
	unsigned int group_count;

	struct mail_storage *storage;
	struct acl_cache *cache;
	struct acl_mask *default_rights;

	struct hash_table *aclobjs;
	struct acl_backend_vfuncs v;
};

struct acl_object {
	int refcount;

	struct acl_backend *backend;
	char *name;
};

struct acl_object_list_iter {
	struct acl_object *aclobj;
};

#endif

--- NEW FILE: acl-api.c ---
/* Copyright (C) 2006 Timo Sirainen */

#include "lib.h"
#include "hash.h"
#include "mail-storage.h"
#include "acl-cache.h"
#include "acl-api-private.h"

struct acl_object *acl_object_init_from_name(struct acl_backend *backend,
					     const char *name)
{
	struct acl_object *aclobj;
	const char *control_dir;

	aclobj = hash_lookup(backend->aclobjs, name);
	if (aclobj != NULL) {
		i_assert(aclobj->refcount >= 0);
		aclobj->refcount++;
		return aclobj;
	}

	control_dir =
		mail_storage_get_mailbox_control_dir(backend->storage, name);

	aclobj = backend->v.object_init(backend, name, control_dir);
	aclobj->refcount++;
	hash_insert(backend->aclobjs, aclobj->name, aclobj);
	return aclobj;
}

struct acl_object *acl_object_init_from_mailbox(struct acl_backend *backend,
						struct mailbox *box)
{
	i_assert(mailbox_get_storage(box) == backend->storage);

	return acl_object_init_from_name(backend, mailbox_get_name(box));
}

void acl_object_deinit(struct acl_object **_aclobj)
{
	struct acl_object *aclobj = *_aclobj;

	i_assert(aclobj->refcount > 0);

	*_aclobj = NULL;
	if (--aclobj->refcount > 0)
		return;

	/* currently ACL objects are really freed only at backend
	   deinitialization. */
}

int acl_object_have_right(struct acl_object *aclobj, unsigned int right_idx)
{
	const struct acl_mask *have_mask;
	unsigned int mask_idx;

	if (*aclobj->name == '\0') {
		/* we want to look up default rights */
		have_mask = aclobj->backend->default_rights;
	} else {
		if (aclobj->backend->v.object_refresh_cache(aclobj) < 0)
			return -1;

		have_mask = acl_cache_get_my_rights(aclobj->backend->cache,
						    aclobj->name);
		if (have_mask == NULL)
			have_mask = aclobj->backend->default_rights;
	}

	mask_idx = right_idx / CHAR_BIT;
	return mask_idx < have_mask->size &&
		(have_mask->mask[mask_idx] &
		 (1 << (right_idx % CHAR_BIT))) != 0;
}

int acl_object_get_my_rights(struct acl_object *aclobj, pool_t pool,
                             const char *const **rights_r)
{
	const struct acl_mask *mask;
	const char *const *names;
	const char **buf, **rights;
	unsigned int names_count, count, i, j, name_idx;

	if (*aclobj->name == '\0') {
		/* we want to look up default rights */
		mask = aclobj->backend->default_rights;
	} else {
		if (aclobj->backend->v.object_refresh_cache(aclobj) < 0)
			return -1;

		mask = acl_cache_get_my_rights(aclobj->backend->cache,
					       aclobj->name);
		if (mask == NULL)
			mask = aclobj->backend->default_rights;
	}

	if (!pool->datastack_pool)
		t_push();

	names = acl_cache_get_names(aclobj->backend->cache, &names_count);
	buf = t_new(const char *, (mask->size * CHAR_BIT) + 1);
	count = 0;
	for (i = 0, name_idx = 0; i < mask->size; i++) {
		if (mask->mask[i] == 0)
			name_idx += CHAR_BIT;
		else {
			for (j = 1; j < (1 << CHAR_BIT); j <<= 1, name_idx++) {
				if ((mask->mask[j] & j) == 0)
					continue;

				/* @UNSAFE */
				i_assert(name_idx < names_count);
				buf[count++] = p_strdup(pool, names[name_idx]);
			}
		}
	}

	/* @UNSAFE */
	rights = p_new(pool, const char *, count + 1);
	memcpy(rights, buf, count * sizeof(const char *));
	*rights_r = rights;

	if (!pool->datastack_pool)
		t_pop();
	return 0;
}

int acl_object_update(struct acl_object *aclobj,
		      const struct acl_rights *rights)
{
        return aclobj->backend->v.object_update(aclobj, rights);
}

struct acl_object_list_iter *acl_object_list_init(struct acl_object *aclobj)
{
        return aclobj->backend->v.object_list_init(aclobj);
}

int acl_object_list_next(struct acl_object_list_iter *iter,
                         struct acl_rights *rights_r)
{
        return iter->aclobj->backend->v.object_list_next(iter, rights_r);
}

void acl_object_list_deinit(struct acl_object_list_iter *iter)
{
        iter->aclobj->backend->v.object_list_deinit(iter);
}

--- NEW FILE: acl-api.h ---
#ifndef __ACL_API_H
#define __ACL_API_H

struct mail_storage;
struct mailbox;
struct acl_object;

/* Show mailbox in mailbox list. Allow subscribing to it. */
#define MAIL_ACL_LOOKUP		"lookup"
/* Allow opening mailbox for reading */
#define MAIL_ACL_READ		"read"
/* Allow permanent flag changes (except for seen/deleted).
   If not set, doesn't allow save/copy to set any flags either. */
#define MAIL_ACL_WRITE		"write"
/* Allow permanent seen-flag changes */
#define MAIL_ACL_WRITE_SEEN	"write-seen"
/* Allow permanent deleted-flag changes */
#define MAIL_ACL_WRITE_DELETED	"write-deleted"
/* Allow saving and copying mails into the mailbox */
#define MAIL_ACL_INSERT		"insert"
/* Allow expunging mails */
#define MAIL_ACL_EXPUNGE	"expunge"
/* Allow creating child mailboxes */
#define MAIL_ACL_CREATE		"create"
/* Allow deleting this mailbox */
#define MAIL_ACL_DELETE		"delete"
/* Allow changing ACL state in this mailbox */
#define MAIL_ACL_ADMIN		"admin"

enum acl_id_type {
	/* Anyone's rights, including anonymous's.
	   identifier name is ignored. */
	ACL_ID_ANYONE,
	/* Authenticate users' rights, overrides anyone's rights.
	   identifier name is ignored. */
	ACL_ID_AUTHENTICATED,
	/* Group's rights, overrides authenticated users' rights */
	ACL_ID_GROUP,
	/* User's rights, overrides group's rights */
	ACL_ID_USER,
	/* Same as group's rights, but also overrides user's rights */
	ACL_ID_GROUP_OVERRIDE,

	ACL_ID_TYPE_COUNT
};

enum acl_modify_mode {
	/* Add rights to existing ACL (or create a new one) */
	ACL_MODIFY_MODE_ADD = 0,
	/* Remove rights from existing ACL */
	ACL_MODIFY_MODE_REMOVE,
	/* Replace existing ACL with given rights */
	ACL_MODIFY_MODE_REPLACE
};

struct acl_rights {
	/* Type of the identifier, user/group */
	enum acl_id_type id_type;
	/* Identifier, eg. username / group name */
	const char *identifier;

	/* Rights assigned */
	const char *const *rights;
	/* Negative rights assigned */
	const char *const *neg_rights;

	/* For set: */
	enum acl_modify_mode modify_mode;
	enum acl_modify_mode neg_modify_mode;
};

/* data contains the information needed to initialize ACL backend. If username
   is NULL, it means the user is anonymous. Username and groups are matched
   case-sensitively. */
struct acl_backend *
acl_backend_init(const char *data, struct mail_storage *storage,
		 const char *acl_username, const char *const *groups,
		 const char *owner_username);
void acl_backend_deinit(struct acl_backend **user);
/* Returns TRUE if user isn't anonymous. */
bool acl_backend_user_is_authenticated(struct acl_backend *backend);
/* Returns TRUE if given name matches the ACL user name. */
bool acl_backend_user_name_equals(struct acl_backend *backend,
				  const char *username);
/* Returns TRUE if ACL user is in given group. */
bool acl_backend_user_is_in_group(struct acl_backend *backend,
				  const char *group_name);
/* Returns index for the right name. If it doesn't exist, it's created. */
unsigned int acl_backend_lookup_right(struct acl_backend *backend,
				      const char *right);

struct acl_object *acl_object_init_from_name(struct acl_backend *backend,
					     const char *name);
struct acl_object *acl_object_init_from_mailbox(struct acl_backend *backend,
						struct mailbox *box);
void acl_object_deinit(struct acl_object **aclobj);

/* Returns 1 if we have the requested rights, 0 if not, or -1 if internal
   error occurred. */
int acl_object_have_right(struct acl_object *aclobj, unsigned int right_idx);
/* Returns 0 = ok, -1 = internal error */
int acl_object_get_my_rights(struct acl_object *aclobj, pool_t pool,
			     const char *const **rights_r);

/* Update ACL of given object. */
int acl_object_update(struct acl_object *aclobj,
		      const struct acl_rights *rights);

/* List all identifiers. */
struct acl_object_list_iter *acl_object_list_init(struct acl_object *aclobj);
int acl_object_list_next(struct acl_object_list_iter *iter,
                         struct acl_rights *rights_r);
void acl_object_list_deinit(struct acl_object_list_iter *iter);

#endif

--- NEW FILE: acl-backend-vfile.c ---
/* Copyright (C) 2006 Timo Sirainen */

#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "istream.h"
#include "nfs-workarounds.h"
#include "mail-storage-private.h"
#include "acl-cache.h"
#include "acl-api-private.h"

#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

#define ACL_FILENAME "dovecot-acl"

#define ACL_SYNC_SECS 1
#define ACL_ESTALE_RETRY_COUNT NFS_ESTALE_RETRY_COUNT

struct acl_vfile {
	char *path;

	time_t last_read_time;
	time_t last_mtime;
	off_t last_size;
};

struct acl_backend_vfile {
	struct acl_backend backend;
	const char *global_dir;
};

struct acl_object_vfile {
	struct acl_object aclobj;

	struct acl_vfile global_file, local_file;
};

struct acl_letter_map {
	char letter;
	const char *name;
};

static const struct acl_letter_map acl_letter_map[] = {
	{ 'l', MAIL_ACL_LOOKUP },
	{ 'r', MAIL_ACL_READ },
	{ 'w', MAIL_ACL_WRITE },
	{ 's', MAIL_ACL_WRITE_SEEN },
	{ 'd', MAIL_ACL_WRITE_DELETED },
	{ 'i', MAIL_ACL_INSERT },
	{ 'e', MAIL_ACL_EXPUNGE },
	{ 'c', MAIL_ACL_CREATE },
	{ 'd', MAIL_ACL_DELETE },
	{ 'a', MAIL_ACL_ADMIN },
	{ '\0', NULL }
};

static struct acl_backend *acl_backend_vfile_init(const char *data)
{
	struct acl_backend_vfile *backend;
	pool_t pool;

	pool = pool_alloconly_create("ACL backend", nearest_power(512));
	backend = p_new(pool, struct acl_backend_vfile, 1);
	backend->global_dir = p_strdup(pool, data);
	backend->backend.pool = pool;
	return &backend->backend;
}

static void acl_backend_vfile_deinit(struct acl_backend *backend)
{
	pool_unref(backend->pool);
}

static struct acl_object *
acl_backend_vfile_object_init(struct acl_backend *_backend,
			      const char *name, const char *control_dir)
{
	struct acl_backend_vfile *backend =
		(struct acl_backend_vfile *)_backend;
	struct acl_object_vfile *aclobj;

	aclobj = i_new(struct acl_object_vfile, 1);
	aclobj->aclobj.backend = _backend;
	aclobj->aclobj.name = i_strdup(name);
	aclobj->global_file.path =
		i_strconcat(backend->global_dir, "/", name, NULL);
	aclobj->local_file.path =
		i_strconcat(control_dir, "/"ACL_FILENAME, NULL);
	return &aclobj->aclobj;
}

static void acl_backend_vfile_object_deinit(struct acl_object *_aclobj)
{
	struct acl_object_vfile *aclobj = (struct acl_object_vfile *)_aclobj;

	i_free(aclobj->local_file.path);
	i_free(aclobj->global_file.path);
	i_free(aclobj->aclobj.name);
	i_free(aclobj);
}

static const char *const *
acl_parse_rights(const char *acl, const char **error_r)
{
	array_t ARRAY_DEFINE(rights, const char *);
	const char *const *names;
	unsigned int i;

	/* parse IMAP ACL list */
	while (*acl == ' ' || *acl == '\t')
		acl++;

	ARRAY_CREATE(&rights, pool_datastack_create(), const char *, 64);
	for (; *acl != '\0' && *acl != ':'; acl++) {
		for (i = 0; acl_letter_map[i].letter != '\0'; i++) {
			if (acl_letter_map[i].letter == *acl)
				break;
		}

		if (acl_letter_map[i].letter == '\0') {
			*error_r = t_strdup_printf("Unknown ACL '%c'", *acl);
			return NULL;
		}

		array_append(&rights, &acl_letter_map[i].name, 1);
	}

	if (*acl == '\0')
		return array_idx(&rights, 0);

	/* parse our own extended ACLs */
	i_assert(*acl == ':');

	names = t_strsplit_spaces(acl, ", ");
	if (array_count(&rights) == 0)
		return names;
	
	for (; *names != NULL; names++)
		array_append(&rights, names, 1);
	return array_idx(&rights, 0);
}

static int
acl_object_vfile_parse_line(struct acl_object *aclobj, struct acl_vfile *file,
			    const char *line, unsigned int linenum)
{
	struct acl_rights rights;
	const char *p, *const *right_names, *error = NULL;

	if (*line == '\0' || *line == '#')
		return 0;

	/* <id> [<imap acls>] [:<named acls>] */
	t_push();
	p = strchr(line, ' ');
	if (p == NULL)
		p = "";
	else {
		line = t_strdup_until(line, p);
		p++;
	}

	memset(&rights, 0, sizeof(rights));

	right_names = acl_parse_rights(p, &error);
	if (*line != '-') {
		rights.modify_mode = ACL_MODIFY_MODE_REPLACE;
		rights.rights = right_names;
	} else {
		line++;
		rights.neg_modify_mode = ACL_MODIFY_MODE_REPLACE;
		rights.neg_rights = right_names;
	}

	if (strncmp(line, "user=", 5) == 0) {
		rights.id_type = ACL_ID_USER;
		rights.identifier = line + 5;
	} else if (strncmp(line, "group=", 6) == 0) {
		rights.id_type = ACL_ID_GROUP;
		rights.identifier = line + 6;
	} else if (strncmp(line, "group-override=", 15) == 0) {
		rights.id_type = ACL_ID_GROUP_OVERRIDE;
		rights.identifier = line + 15;
	} else if (strcmp(line, "owner") == 0) {
		rights.id_type = ACL_ID_USER;
		rights.identifier = aclobj->backend->owner_username;
	} else if (strcmp(line, "authenticated") == 0) {
		rights.id_type = ACL_ID_AUTHENTICATED;
	} else if (strcmp(line, "anyone") == 0 ||
		   strcmp(line, "anonymous") == 0) {
		rights.id_type = ACL_ID_ANYONE;
	} else {
		error = t_strdup_printf("Unknown ID '%s'", line);
	}

	if (error != NULL) {
		mail_storage_set_critical(aclobj->backend->storage,
					  "ACL file %s line %u: %s",
					  file->path, linenum, error);
		t_pop();
		return -1;
	}

	acl_cache_update(aclobj->backend->cache, aclobj->name, &rights);

	t_pop();
	return 0;
}

static int
acl_backend_vfile_read(struct acl_object *aclobj, struct acl_vfile *file,
		       bool try_retry)
{
	struct mail_storage *storage = aclobj->backend->storage;
	struct istream *input;
	struct stat st;
	const char *line;
	unsigned int linenum;
	int fd, ret = 1;

	fd = nfs_safe_open(file->path, O_RDONLY);
	if (fd == -1) {
		if (errno == ENOENT) {
			file->last_read_time = ioloop_time;
			return 1;
		}
		mail_storage_set_critical(storage,
					  "open(%s) failed: %m", file->path);
		return -1;
	}
	input = i_stream_create_file(fd, default_pool, 4096, FALSE);

	linenum = 1;
	while ((line = i_stream_read_next_line(input)) != NULL) {
		if (acl_object_vfile_parse_line(aclobj, file, line,
						linenum++) < 0) {
			ret = -1;
			break;
		}
	}

	if (input->stream_errno != 0) {
		if (input->stream_errno == ESTALE && try_retry)
			ret = 0;
		else {
			ret = -1;
			mail_storage_set_critical(storage,
				"read(%s) failed: %m", file->path);
		}
	}

	if (ret > 0) {
		if (fstat(fd, &st) < 0) {
			if (errno == ESTALE && try_retry)
				ret = 0;
			else {
				ret = -1;
				mail_storage_set_critical(storage,
					"read(%s) failed: %m", file->path);
			}
		} else {
			file->last_read_time = ioloop_time;
			file->last_mtime = st.st_mtime;
			file->last_size = st.st_size;
		}
	}

	i_stream_unref(&input);
	if (close(fd) < 0) {
		if (errno == ESTALE && try_retry)
			return 0;

		mail_storage_set_critical(storage, "close(%s) failed: %m",
					  file->path);
		return -1;
	}
	return ret;
}

static int
acl_backend_vfile_read_with_retry(struct acl_object *aclobj,
				  struct acl_vfile *file)
{
	unsigned int i;
	int ret;

	for (i = 0;; i++) {
		ret = acl_backend_vfile_read(aclobj, file,
					     i < ACL_ESTALE_RETRY_COUNT);
		if (ret != 0)
			break;

		/* ESTALE - try again */
	}

	return ret <= 0 ? -1 : 0;
}

static int
acl_backend_vfile_refresh(struct acl_object *aclobj, struct acl_vfile *file)
{
	struct stat st;

	if (file->last_read_time == 0)
		return 1;

	if (stat(file->path, &st) < 0) {
		if (errno == ENOENT) {
			/* No global ACL directory */
			return 0;
		}
		mail_storage_set_critical(aclobj->backend->storage,
					  "stat(%s) failed: %m", file->path);
		return -1;
	}

	if (st.st_mtime == file->last_mtime &&
	    st.st_size == file->last_size) {
		/* same timestamp, but if it was modified within the
		   same second we want to refresh it again later (but
		   do it only after a couple of seconds so we don't
		   keep re-reading it all the time within those
		   seconds) */
		if (st.st_mtime < file->last_read_time - ACL_SYNC_SECS ||
		    ioloop_time - file->last_read_time <= ACL_SYNC_SECS)
			return 0;
	}

	return 1;
}

static int acl_backend_vfile_object_refresh_cache(struct acl_object *_aclobj)
{
	struct acl_object_vfile *aclobj = (struct acl_object_vfile *)_aclobj;
	int ret;

	ret = acl_backend_vfile_refresh(_aclobj, &aclobj->global_file);
	if (ret == 0)
		ret = acl_backend_vfile_refresh(_aclobj, &aclobj->local_file);
	if (ret <= 0)
		return ret;

	/* either global or local ACLs changed, need to re-read both */
	acl_cache_flush(_aclobj->backend->cache, _aclobj->name);
	if (acl_backend_vfile_read_with_retry(_aclobj,
					      &aclobj->global_file) < 0)
		return -1;
	if (acl_backend_vfile_read_with_retry(_aclobj, &aclobj->local_file) < 0)
		return -1;
	return 0;
}

static int acl_backend_vfile_object_update(struct acl_object *aclobj,
					   const struct acl_rights *rights)
{
	/* FIXME */
	return -1;
}

static struct acl_object_list_iter *
acl_backend_vfile_object_list_init(struct acl_object *aclobj)
{
	struct acl_object_list_iter *iter;

	iter = i_new(struct acl_object_list_iter, 1);
	iter->aclobj = aclobj;
	return iter;
}

static int
acl_backend_vfile_object_list_next(struct acl_object_list_iter *iter,
				   struct acl_rights *rights_r)
{
	return -1;
}

static void
acl_backend_vfile_object_list_deinit(struct acl_object_list_iter *iter)
{
	i_free(iter);
}

struct acl_backend_vfuncs acl_backend_vfile = {
	acl_backend_vfile_init,
	acl_backend_vfile_deinit,
	acl_backend_vfile_object_init,
	acl_backend_vfile_object_deinit,
	acl_backend_vfile_object_refresh_cache,
	acl_backend_vfile_object_update,
	acl_backend_vfile_object_list_init,
	acl_backend_vfile_object_list_next,
	acl_backend_vfile_object_list_deinit
};

--- NEW FILE: acl-backend.c ---
/* Copyright (C) 2006 Timo Sirainen */

#include "lib.h"
#include "hash.h"
#include "acl-cache.h"
#include "acl-api-private.h"

#include <stdlib.h>

extern struct acl_backend_vfuncs acl_backend_vfile;

static const char *const owner_mailbox_rights[] = {
	MAIL_ACL_LOOKUP,
	MAIL_ACL_READ,
	MAIL_ACL_WRITE,
	MAIL_ACL_WRITE_SEEN,
	MAIL_ACL_WRITE_DELETED,
	MAIL_ACL_INSERT,
	MAIL_ACL_EXPUNGE,
	MAIL_ACL_CREATE,
	MAIL_ACL_DELETE,
	MAIL_ACL_ADMIN,
	NULL
};

static const char *const non_owner_mailbox_rights[] = { NULL };

struct acl_backend *
acl_backend_init(const char *data, struct mail_storage *storage,
		 const char *acl_username, const char *const *groups,
		 const char *owner_username)
{
	struct acl_backend *backend;
	unsigned int i, group_count;
	bool storage_owner;

	group_count = strarray_length(groups);

	if (strncmp(data, "vfile:", 6) != 0)
		i_fatal("Unknown ACL backend: %s", t_strcut(data, ':'));
	data += 6;

	backend = acl_backend_vfile.init(data);
	backend->v = acl_backend_vfile;
	backend->storage = storage;
	backend->username = p_strdup(backend->pool, acl_username);
	backend->owner_username = p_strdup(backend->pool, owner_username);
	backend->group_count = group_count;
	backend->cache = acl_cache_init(backend);
	backend->aclobjs = hash_create(default_pool, backend->pool, 0,
				       str_hash, (hash_cmp_callback_t *)strcmp);

	storage_owner = owner_username != NULL &&
		strcmp(acl_username, owner_username) == 0;
	backend->default_rights =
		acl_cache_mask_init(backend->cache, backend->pool,
				    storage_owner ? owner_mailbox_rights :
				    non_owner_mailbox_rights);

	if (group_count > 0) {
		backend->groups =
			p_new(backend->pool, const char *, group_count);
		for (i = 0; i < group_count; i++)
			backend->groups[i] = groups[i];
		qsort(backend->groups, group_count, sizeof(const char *),
		      strcmp_p);
	}
	return backend;
}

void acl_backend_deinit(struct acl_backend **_backend)
{
	struct acl_backend *backend = *_backend;
	struct hash_iterate_context *iter;
	void *key, *value;

	*_backend = NULL;

	iter = hash_iterate_init(backend->aclobjs);
	while (hash_iterate(iter, &key, &value)) {
		struct acl_object *aclobj = value;

		aclobj->backend->v.object_deinit(aclobj);
	}
	hash_iterate_deinit(iter);

	acl_cache_deinit(&backend->cache);
	hash_destroy(backend->aclobjs);
	backend->v.deinit(backend);
}

bool acl_backend_user_is_authenticated(struct acl_backend *backend)
{
	return backend->username != NULL;
}

bool acl_backend_user_name_equals(struct acl_backend *backend,
				  const char *username)
{
	if (backend->username == NULL) {
		/* anonymous user never matches */
		return FALSE;
	}

	return strcmp(backend->username, username) == 0;
}

bool acl_backend_user_is_in_group(struct acl_backend *backend,
				  const char *group_name)
{
	return bsearch(group_name, backend->groups, backend->group_count,
		       sizeof(const char *), bsearch_strcmp) != NULL;
}

unsigned int acl_backend_lookup_right(struct acl_backend *backend,
				      const char *right)
{
	return acl_cache_right_lookup(backend->cache, right);
}

--- NEW FILE: acl-cache.c ---
/* Copyright (C) 2006 Timo Sirainen */

#include "lib.h"
#include "array.h"
#include "hash.h"
#include "acl-cache.h"
#include "acl-api.h"

/* Give more than enough so that the arrays should never have to be grown.
   IMAP ACLs define only 10 standard rights and 10 user-defined rights. */
#define DEFAULT_ACL_RIGHTS_COUNT 64

struct acl_object_cache {
	char *name;

	struct acl_mask *my_rights[ACL_ID_TYPE_COUNT];
	struct acl_mask *my_neg_rights[ACL_ID_TYPE_COUNT];

	/* Needs to be calculated from my_*rights if NULL. */
	struct acl_mask *my_current_rights;
};

struct acl_cache {
	struct acl_backend *backend;
	struct hash_table *objects; /* name => struct acl_object_cache* */

	/* Right names mapping is used for faster rights checking. Note that
	   acl_mask bitmask relies on the order to never change, so only new
	   rights can be added to the mapping. */
	pool_t right_names_pool;
	/* idx => right name. */
	array_t ARRAY_DEFINE(right_idx_name_map, const char *);
	/* name => idx */
	struct hash_table *right_name_idx_map;
};

struct acl_cache *acl_cache_init(struct acl_backend *backend)
{
	struct acl_cache *cache;

	cache = i_new(struct acl_cache, 1);
	cache->backend = backend;
	cache->right_names_pool =
		pool_alloconly_create("ACL right names", 1024);
	cache->objects = hash_create(default_pool, default_pool, 0,
				     str_hash, (hash_cmp_callback_t *)strcmp);
	cache->right_name_idx_map =
		hash_create(default_pool, cache->right_names_pool, 0,
			    str_hash, (hash_cmp_callback_t *)strcmp);
	ARRAY_CREATE(&cache->right_idx_name_map, default_pool,
		     const char *, DEFAULT_ACL_RIGHTS_COUNT);
	return cache;
}

void acl_cache_deinit(struct acl_cache **_cache)
{
	struct acl_cache *cache = *_cache;

	*_cache = NULL;
	array_free(&cache->right_idx_name_map);
	hash_destroy(cache->right_name_idx_map);
	hash_destroy(cache->objects);
	pool_unref(cache->right_names_pool);
	i_free(cache);
}

static void acl_cache_free_object_cache(struct acl_object_cache *obj_cache)
{
	unsigned int i;

	for (i = 0; i < ACL_ID_TYPE_COUNT; i++) {
		if (obj_cache->my_rights[i] != NULL)
			acl_cache_mask_deinit(&obj_cache->my_rights[i]);
		if (obj_cache->my_neg_rights[i] != NULL)
			acl_cache_mask_deinit(&obj_cache->my_neg_rights[i]);
	}
	if (obj_cache->my_current_rights != NULL)
		acl_cache_mask_deinit(&obj_cache->my_current_rights);
	i_free(obj_cache->name);
	i_free(obj_cache);
}

struct acl_mask *acl_cache_mask_init(struct acl_cache *cache, pool_t pool,
				     const char *const *rights)
{
	struct acl_mask *mask;
	unsigned int rights_count, i, idx;
	unsigned char *p;
	buffer_t *bitmask;

	t_push();
	rights_count = strarray_length(rights);
	bitmask = buffer_create_dynamic(pool_datastack_create(),
					DEFAULT_ACL_RIGHTS_COUNT / CHAR_BIT);
	for (i = 0; i < rights_count; i++) {
		idx = acl_cache_right_lookup(cache, rights[i]);
		p = buffer_get_space_unsafe(bitmask, idx / CHAR_BIT, 1);
		*p |= 1 << (idx % CHAR_BIT);
	}

	/* @UNSAFE */
	mask = p_malloc(pool, SIZEOF_ACL_MASK(bitmask->used));
	memcpy(mask->mask, bitmask->data, bitmask->used);
	mask->pool = pool;
	mask->size = bitmask->used;
	t_pop();
	return mask;
}

void acl_cache_mask_deinit(struct acl_mask **_mask)
{
	struct acl_mask *mask = *_mask;

	*_mask = NULL;
	p_free(mask->pool, mask);
}

unsigned int acl_cache_right_lookup(struct acl_cache *cache, const char *right)
{
	unsigned int idx;
	void *idx_p;
	char *name;

	/* use +1 for right_name_idx_map values because we can't add NULL
	   values. */
	idx_p = hash_lookup(cache->right_name_idx_map, right);
	if (idx_p == NULL) {
		/* new right name, add it */
		name = p_strdup(cache->right_names_pool, right);

		idx = array_count(&cache->right_idx_name_map);
		array_append(&cache->right_idx_name_map, &name, 1);
		hash_insert(cache->right_name_idx_map, name,
			    POINTER_CAST(idx + 1));
	} else {
		idx = POINTER_CAST_TO(idx_p, unsigned int)-1;
	}
	return idx;
}

void acl_cache_flush(struct acl_cache *cache, const char *objname)
{
	struct acl_object_cache *obj_cache;

	obj_cache = hash_lookup(cache->objects, objname);
	if (obj_cache != NULL) {
		hash_remove(cache->objects, objname);
		acl_cache_free_object_cache(obj_cache);
	}
}

void acl_cache_flush_all(struct acl_cache *cache)
{
	struct hash_iterate_context *iter;
	void *key, *value;

	iter = hash_iterate_init(cache->objects);
	while (hash_iterate(iter, &key, &value)) {
		struct acl_object_cache *obj_cache = value;

		acl_cache_free_object_cache(obj_cache);
	}
	hash_iterate_deinit(iter);

	hash_clear(cache->objects, FALSE);
}

static void
acl_cache_update_rights_mask(struct acl_cache *cache,
			     struct acl_object_cache *obj_cache,
			     enum acl_modify_mode modify_mode,
			     const char *const *rights,
			     struct acl_mask **mask_p)
{
	struct acl_mask *change_mask, *old_mask, *new_mask;
	unsigned int i, size;
	bool changed = TRUE;

	change_mask = rights == NULL ? NULL :
		acl_cache_mask_init(cache, default_pool, rights);
	old_mask = *mask_p;
	new_mask = old_mask;

	switch (modify_mode) {
	case ACL_MODIFY_MODE_ADD:
		if (old_mask == NULL) {
			new_mask = change_mask;
			break;
		}

		if (change_mask == NULL) {
			/* no changes */
			changed = FALSE;
			break;
		}

		/* merge the masks */
		if (old_mask->size >= change_mask->size) {
			/* keep using the old mask */
			for (i = 0; i < change_mask->size; i++)
				old_mask->mask[i] |= change_mask->mask[i];
		} else {
			/* use the new mask, put old changes into it */
			for (i = 0; i < old_mask->size; i++)
				change_mask->mask[i] |= old_mask->mask[i];
			new_mask = change_mask;
		}
		break;
	case ACL_MODIFY_MODE_REMOVE:
		if (old_mask == NULL || change_mask == NULL) {
			changed = FALSE;
			break;
		}

		/* remove changed bits from old mask */
		size = I_MIN(old_mask->size, change_mask->size);
		for (i = 0; i < size; i++)
			old_mask->mask[i] &= ~change_mask->mask[i];
		break;
	case ACL_MODIFY_MODE_REPLACE:
		if (old_mask == NULL && change_mask == NULL)
			changed = FALSE;
		new_mask = change_mask;
		break;
	}

	if (new_mask != old_mask) {
		*mask_p = new_mask;
		if (old_mask != NULL)
			acl_cache_mask_deinit(&old_mask);
	}
	if (new_mask != change_mask && change_mask != NULL)
		acl_cache_mask_deinit(&change_mask);

	if (changed && obj_cache->my_current_rights != NULL) {
		/* current rights need to be recalculated */
		acl_cache_mask_deinit(&obj_cache->my_current_rights);
	}
}

static void
acl_cache_update_rights(struct acl_cache *cache,
			struct acl_object_cache *obj_cache,
			const struct acl_rights *rights)
{
	enum acl_id_type id_type = rights->id_type;

	acl_cache_update_rights_mask(cache, obj_cache, rights->modify_mode,
				     rights->rights,
				     &obj_cache->my_rights[id_type]);
	acl_cache_update_rights_mask(cache, obj_cache, rights->neg_modify_mode,
				     rights->neg_rights,
				     &obj_cache->my_neg_rights[id_type]);
}

void acl_cache_update(struct acl_cache *cache, const char *objname,
		      const struct acl_rights *rights)
{
	struct acl_object_cache *obj_cache;

	obj_cache = hash_lookup(cache->objects, objname);
	if (obj_cache == NULL) {
		obj_cache = i_new(struct acl_object_cache, 1);
		obj_cache->name = i_strdup(objname);
		hash_insert(cache->objects, obj_cache->name, obj_cache);
	}

	switch (rights->id_type) {
	case ACL_ID_ANYONE:
		acl_cache_update_rights(cache, obj_cache, rights);
		break;
	case ACL_ID_AUTHENTICATED:
		if (acl_backend_user_is_authenticated(cache->backend))
			acl_cache_update_rights(cache, obj_cache, rights);
		break;
	case ACL_ID_GROUP:
	case ACL_ID_GROUP_OVERRIDE:
		if (acl_backend_user_is_in_group(cache->backend,
						 rights->identifier))
			acl_cache_update_rights(cache, obj_cache, rights);
		break;
	case ACL_ID_USER:
		if (acl_backend_user_name_equals(cache->backend,
						 rights->identifier))
			acl_cache_update_rights(cache, obj_cache, rights);
		break;
	case ACL_ID_TYPE_COUNT:
		i_unreached();
	}
}

const char *const *acl_cache_get_names(struct acl_cache *cache,
				       unsigned int *count_r)
{
	*count_r = array_count(&cache->right_idx_name_map);
	return array_idx(&cache->right_idx_name_map, 0);
}

static void
acl_cache_my_current_rights_recalculate(struct acl_object_cache *obj_cache)
{
	struct acl_mask *mask;
	buffer_t *bitmask;
	unsigned char *p;
	unsigned int i, j, right_size;

	t_push();
	bitmask = buffer_create_dynamic(pool_datastack_create(),
					DEFAULT_ACL_RIGHTS_COUNT / CHAR_BIT);
	for (i = 0; i < ACL_ID_TYPE_COUNT; i++) {
		if (obj_cache->my_rights[i] != NULL) {
			/* apply the positive rights */
			right_size = obj_cache->my_rights[i]->size;
			p = buffer_get_space_unsafe(bitmask, 0, right_size);
			for (j = 0; j < right_size; j++)
				p[j] |= obj_cache->my_rights[i]->mask[j];
		}

		if (obj_cache->my_neg_rights[i] != NULL) {
			/* apply the negative rights. they override positive
			   rights. */
			right_size = obj_cache->my_neg_rights[i]->size;
			p = buffer_get_space_unsafe(bitmask, 0, right_size);
			for (j = 0; j < right_size; j++) {
				p[j] |=
					obj_cache->my_neg_rights[i]->mask[j];
			}
		}
	}

	/* @UNSAFE */
	obj_cache->my_current_rights = mask =
		i_malloc(SIZEOF_ACL_MASK(bitmask->used));
	memcpy(mask->mask, bitmask->data, bitmask->used);
	mask->pool = default_pool;
	mask->size = bitmask->used;
	t_pop();
}

const struct acl_mask *
acl_cache_get_my_rights(struct acl_cache *cache, const char *objname)
{
	struct acl_object_cache *obj_cache;

	obj_cache = hash_lookup(cache->objects, objname);
	if (obj_cache == NULL)
		return NULL;

	if (obj_cache->my_current_rights == NULL)
		acl_cache_my_current_rights_recalculate(obj_cache);
	return obj_cache->my_current_rights;
}

--- NEW FILE: acl-cache.h ---
#ifndef __ACL_CACHE_H
#define __ACL_CACHE_H

struct acl_backend;
struct acl_rights;

struct acl_mask {
	pool_t pool;

	/* mask[] size as bytes */
	unsigned int size;

	/* variable length bitmask */
	unsigned char mask[1];
};
#define SIZEOF_ACL_MASK(count) \
	(sizeof(pool_t) + sizeof(unsigned int) + \
	 (count + CHAR_BIT-1) / CHAR_BIT)

struct acl_cache *acl_cache_init(struct acl_backend *backend);
void acl_cache_deinit(struct acl_cache **cache);

struct acl_mask *acl_cache_mask_init(struct acl_cache *cache, pool_t pool,
				     const char *const *rights);
void acl_cache_mask_deinit(struct acl_mask **mask);
unsigned int acl_cache_right_lookup(struct acl_cache *cache,
				    const char *right);

/* Flush cache for given object name */
void acl_cache_flush(struct acl_cache *cache, const char *objname);
/* Flush cache for all objects */
void acl_cache_flush_all(struct acl_cache *cache);

/* Update object ACLs */
void acl_cache_update(struct acl_cache *cache, const char *objname,
		      const struct acl_rights *rights);

/* Returns all the right names currently created. The returned pointer may
   change after calling acl_cache_update(). */
const char *const *acl_cache_get_names(struct acl_cache *cache,
				       unsigned int *count_r);

/* Returns user's current rights, or NULL if no rights have been specified
   for this object. */
const struct acl_mask *
acl_cache_get_my_rights(struct acl_cache *cache, const char *objname);

#endif

--- NEW FILE: acl-mailbox.c ---
/* Copyright (C) 2006 Timo Sirainen */

/* FIXME: If we don't have permission to change flags/keywords, the changes
   should still be stored temporarily for this session. However most clients
   don't care and it's a huge job, so I currently this isn't done. The same
   problem actually exists when opening read-only mailboxes. */
#include "lib.h"
#include "array.h"
#include "istream.h"
#include "mail-storage-private.h"
#include "acl-api-private.h"
#include "acl-plugin.h"

#include <sys/stat.h>

#define ACL_CONTEXT(obj) \
	*((void **)array_idx_modifyable(&(obj)->module_contexts, \
					acl_storage_module_id))

struct acl_mailbox {
	struct mailbox_vfuncs super;
	struct acl_object *aclobj;

	unsigned int save_hack:1;
};

struct acl_mail {
	struct mail_vfuncs super;
};

static int acl_mailbox_close(struct mailbox *box)
{
	struct acl_mailbox *abox = ACL_CONTEXT(box);

	acl_object_deinit(&abox->aclobj);
	return abox->super.close(box);
}

static int mailbox_acl_right_lookup(struct mailbox *box, unsigned int right_idx)
{
	struct acl_mailbox *abox = ACL_CONTEXT(box);
	struct acl_mail_storage *astorage = ACL_CONTEXT(box->storage);
	int ret;

	ret = acl_object_have_right(abox->aclobj,
				    astorage->acl_storage_right_idx[right_idx]);
	if (ret > 0)
		return 1;
	if (ret < 0)
		return -1;

	mail_storage_set_error(box->storage, MAIL_STORAGE_ERR_NO_PERMISSION);
	return 0;
}

static int
acl_get_write_rights(struct mailbox *box,
		     bool *flags_r, bool *flag_seen_r, bool *flag_del_r)
{
	int ret;

	ret = mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE);
	if (ret < 0)
		return -1;
	*flags_r = ret > 0;

	ret = mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN);
	if (ret < 0)
		return -1;
	*flag_seen_r = ret > 0;

	ret = mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED);
	if (ret < 0)
		return -1;
	*flag_del_r = ret > 0;
	return 1;
}

static int
acl_mail_update_flags(struct mail *_mail, enum modify_type modify_type,
		      enum mail_flags flags)
{
	struct mail_private *mail = (struct mail_private *)_mail;
	struct acl_mail *amail = ACL_CONTEXT(mail);
	bool acl_flags, acl_flag_seen, acl_flag_del;
	int ret;

	ret = acl_get_write_rights(_mail->box, &acl_flags, &acl_flag_seen,
				   &acl_flag_del);
	if (ret < 0)
		return -1;

	if (modify_type != MODIFY_REPLACE) {
		/* adding/removing flags. just remove the disallowed
		   flags from the mask. */
		if (!acl_flags)
			flags &= MAIL_SEEN | MAIL_DELETED;
		if (!acl_flag_seen)
			flags &= ~MAIL_SEEN;
		if (!acl_flag_del)
			flags &= ~MAIL_DELETED;
	} else if (!acl_flags || acl_flag_seen || !acl_flag_del) {
		/* we don't have permission to replace all the flags. */
		if (!acl_flags && !acl_flag_seen && !acl_flag_del) {
			/* no flag changes allowed. ignore silently. */
			return 0;
		}

		/* handle this by first removing the allowed flags and
		   then adding the allowed flags */
		if (acl_mail_update_flags(_mail, MODIFY_REMOVE,
					  ~flags) < 0)
			return -1;
		return acl_mail_update_flags(_mail, MODIFY_ADD, flags);
	}

	return amail->super.update_flags(_mail, modify_type, flags);
}

static int
acl_mail_update_keywords(struct mail *_mail, enum modify_type modify_type,
			 struct mail_keywords *keywords)
{
	struct mail_private *mail = (struct mail_private *)_mail;
	struct acl_mail *amail = ACL_CONTEXT(mail);
	int ret;

	ret = mailbox_acl_right_lookup(_mail->box, ACL_STORAGE_RIGHT_WRITE);
	if (ret <= 0) {
		/* if we don't have permission, just silently return success. */
		return ret;
	}

	return amail->super.update_keywords(_mail, modify_type, keywords);
}

static int acl_mail_expunge(struct mail *_mail)
{
	struct mail_private *mail = (struct mail_private *)_mail;
	struct acl_mail *amail = ACL_CONTEXT(mail);
	int ret;

	ret = mailbox_acl_right_lookup(_mail->box, ACL_STORAGE_RIGHT_EXPUNGE);
	if (ret <= 0) {
		/* if we don't have permission, silently return success so
		   users won't see annoying error messages in case their
		   clients try automatic expunging. */
		return ret;
	}

	return amail->super.expunge(_mail);
}

static struct mail *
acl_mail_alloc(struct mailbox_transaction_context *t,
	       enum mail_fetch_field wanted_fields,
	       struct mailbox_header_lookup_ctx *wanted_headers)
{
	struct acl_mailbox *abox = ACL_CONTEXT(t->box);
	struct acl_mail *amail;
	struct mail *_mail;
	struct mail_private *mail;

	_mail = abox->super.mail_alloc(t, wanted_fields, wanted_headers);
	mail = (struct mail_private *)_mail;

	amail = p_new(mail->pool, struct acl_mail, 1);
	amail->super = mail->v;

	mail->v.update_flags = acl_mail_update_flags;
	mail->v.update_keywords = acl_mail_update_keywords;
	mail->v.expunge = acl_mail_expunge;
	array_idx_set(&mail->module_contexts, acl_storage_module_id, &amail);
	return _mail;
}

static int
acl_save_init(struct mailbox_transaction_context *t,
	      enum mail_flags flags, struct mail_keywords *keywords,
	      time_t received_date, int timezone_offset,
	      const char *from_envelope, struct istream *input,
	      bool want_mail, struct mail_save_context **ctx_r)
{
	struct acl_mailbox *abox = ACL_CONTEXT(t->box);

	if (mailbox_acl_right_lookup(t->box, ACL_STORAGE_RIGHT_INSERT) <= 0)
		return -1;

	return abox->super.save_init(t, flags, keywords, received_date,
				     timezone_offset, from_envelope,
				     input, want_mail, ctx_r);
}

static int
acl_copy(struct mailbox_transaction_context *t, struct mail *mail,
	 enum mail_flags flags, struct mail_keywords *keywords,
	 struct mail *dest_mail)
{
	struct acl_mailbox *abox = ACL_CONTEXT(t->box);

	if (mailbox_acl_right_lookup(t->box, ACL_STORAGE_RIGHT_INSERT) <= 0)
		return -1;

	return abox->super.copy(t, mail, flags, keywords, dest_mail);
}

struct mailbox *acl_mailbox_open_box(struct mailbox *box)
{
	struct acl_mail_storage *astorage = ACL_CONTEXT(box->storage);
	struct acl_mailbox *abox;

	abox = p_new(box->pool, struct acl_mailbox, 1);
	abox->super = box->v;
	abox->aclobj = acl_object_init_from_name(astorage->backend,
						 mailbox_get_name(box));
	
	box->v.close = acl_mailbox_close;
	box->v.mail_alloc = acl_mail_alloc;
	box->v.save_init = acl_save_init;
	box->v.copy = acl_copy;
	array_idx_set(&box->module_contexts, acl_storage_module_id, &abox);
	return box;
}

--- NEW FILE: acl-plugin.c ---
/* Copyright (C) 2005 Timo Sirainen */

#include "lib.h"
#include "mail-storage.h"
#include "acl-api.h"
#include "acl-plugin.h"

#include <stdlib.h>

/* defined by imap, pop3, lda */
extern void (*hook_mail_storage_created)(struct mail_storage *storage);

void (*acl_next_hook_mail_storage_created)(struct mail_storage *storage);

void acl_plugin_init(void)
{
	if (getenv("ACL") != NULL) {
		acl_next_hook_mail_storage_created =
			hook_mail_storage_created;
		hook_mail_storage_created = acl_mail_storage_created;
	}
}

void acl_plugin_deinit(void)
{
	if (acl_next_hook_mail_storage_created != NULL) {
		hook_mail_storage_created =
			acl_next_hook_mail_storage_created;
	}
}

--- NEW FILE: acl-plugin.h ---
#ifndef __ACL_PLUGIN_H
#define __ACL_PLUGIN_H

#include "mail-storage-private.h"

#define ACL_CONTEXT(obj) \
	*((void **)array_idx_modifyable(&(obj)->module_contexts, \
					acl_storage_module_id))

enum acl_storage_rights {
	ACL_STORAGE_RIGHT_LOOKUP,
	ACL_STORAGE_RIGHT_READ,
	ACL_STORAGE_RIGHT_WRITE,
	ACL_STORAGE_RIGHT_WRITE_SEEN,
	ACL_STORAGE_RIGHT_WRITE_DELETED,
	ACL_STORAGE_RIGHT_INSERT,
	ACL_STORAGE_RIGHT_EXPUNGE,
	ACL_STORAGE_RIGHT_CREATE,
	ACL_STORAGE_RIGHT_DELETE,
	ACL_STORAGE_RIGHT_ADMIN,

	ACL_STORAGE_RIGHT_COUNT
};

struct acl_mail_storage {
	struct mail_storage_vfuncs super;
	struct acl_backend *backend;
	unsigned int acl_storage_right_idx[ACL_STORAGE_RIGHT_COUNT];
};

extern void (*acl_next_hook_mail_storage_created)
	(struct mail_storage *storage);
extern unsigned int acl_storage_module_id;

void acl_mail_storage_created(struct mail_storage *storage);

struct mailbox *acl_mailbox_open_box(struct mailbox *box);

void acl_plugin_init(void);
void acl_plugin_deinit(void);

#endif

--- NEW FILE: acl-storage.c ---
/* Copyright (C) 2006 Timo Sirainen */

#include "lib.h"
#include "array.h"
#include "istream.h"
#include "acl-api-private.h"
#include "acl-plugin.h"

#include <stdlib.h>

unsigned int acl_storage_module_id = 0;

static bool acl_storage_module_id_set = FALSE;

static const char *acl_storage_right_names[ACL_STORAGE_RIGHT_COUNT] = {
	MAIL_ACL_LOOKUP,
	MAIL_ACL_READ,
	MAIL_ACL_WRITE,
	MAIL_ACL_WRITE_SEEN,
	MAIL_ACL_WRITE_DELETED,
	MAIL_ACL_INSERT,
	MAIL_ACL_EXPUNGE,
	MAIL_ACL_CREATE,
	MAIL_ACL_DELETE,
	MAIL_ACL_ADMIN
};

static int
acl_storage_have_right(struct mail_storage *storage, const char *name,
		       unsigned int acl_storage_right_idx, bool *can_see_r)
{
	struct acl_mail_storage *astorage = ACL_CONTEXT(storage);
	const unsigned int *idx_arr = astorage->acl_storage_right_idx;
	struct acl_object *aclobj;
	int ret, ret2;

	aclobj = acl_object_init_from_name(astorage->backend, name);
	ret = acl_object_have_right(aclobj, idx_arr[acl_storage_right_idx]);

	if (can_see_r != NULL) {
		ret2 = acl_object_have_right(aclobj,
					     idx_arr[ACL_STORAGE_RIGHT_LOOKUP]);
		if (ret2 < 0)
			ret = -1;
		*can_see_r = ret2 > 0;
	}
	acl_object_deinit(&aclobj);

	return ret;
}

static const char *
get_parent_mailbox_name(struct mail_storage *storage, const char *name)
{
	const char *p;
	char sep;

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

static void acl_storage_destroy(struct mail_storage *storage)
{
	struct acl_mail_storage *astorage = ACL_CONTEXT(storage);

	acl_backend_deinit(&astorage->backend);
}

static struct mailbox *
acl_mailbox_open(struct mail_storage *storage, const char *name,
		 struct istream *input, enum mailbox_open_flags flags)
{
	struct acl_mail_storage *astorage = ACL_CONTEXT(storage);
	struct mailbox *box;
	bool can_see;
	int ret;

	/* mailbox can be opened either for reading or appending new messages */
	if ((flags & MAILBOX_OPEN_SAVEONLY) != 0) {
		ret = acl_storage_have_right(storage, name,
					     ACL_STORAGE_RIGHT_INSERT,
					     &can_see);
	} else {
		ret = acl_storage_have_right(storage, name,
					     ACL_STORAGE_RIGHT_READ,
					     &can_see);
	}
	if (ret <= 0) {
		if (ret < 0)
			return NULL;
		if (can_see) {
			mail_storage_set_error(storage,
					       MAIL_STORAGE_ERR_NO_PERMISSION);
		} else {
			mail_storage_set_error(storage,
				MAIL_STORAGE_ERR_MAILBOX_NOT_FOUND, name);
		}
		return NULL;
	}

	box = astorage->super.mailbox_open(storage, name, input, flags);
	if (box == NULL)
		return NULL;

	return acl_mailbox_open_box(box);
}

static int acl_mailbox_create(struct mail_storage *storage, const char *name,
			      bool directory)
{
	struct acl_mail_storage *astorage = ACL_CONTEXT(storage);
	int ret;

	t_push();
	ret = acl_storage_have_right(storage,
				     get_parent_mailbox_name(storage, name),
				     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. */
			mail_storage_set_error(storage,
					       MAIL_STORAGE_ERR_NO_PERMISSION);
		}
		return -1;
	}

	return astorage->super.mailbox_create(storage, name, directory);
}

static int acl_mailbox_delete(struct mail_storage *storage, const char *name)
{
	struct acl_mail_storage *astorage = ACL_CONTEXT(storage);
	bool can_see;
	int ret;

	ret = acl_storage_have_right(storage, name, ACL_STORAGE_RIGHT_DELETE,
				     &can_see);
	if (ret <= 0) {
		if (ret < 0)
			return -1;
		if (can_see) {
			mail_storage_set_error(storage,
					       MAIL_STORAGE_ERR_NO_PERMISSION);
		} else {
			mail_storage_set_error(storage,
				MAIL_STORAGE_ERR_MAILBOX_NOT_FOUND, name);
		}
		return -1;
	}

	return astorage->super.mailbox_delete(storage, name);
}

static int acl_mailbox_rename(struct mail_storage *storage, const char *oldname,
			      const char *newname)
{
	struct acl_mail_storage *astorage = ACL_CONTEXT(storage);
	bool can_see;
	int ret;

	/* renaming requires rights to delete the old mailbox */
	ret = acl_storage_have_right(storage, oldname,
				     ACL_STORAGE_RIGHT_DELETE, &can_see);
	if (ret <= 0) {
		if (ret < 0)
			return -1;
		if (can_see) {
			mail_storage_set_error(storage,
					       MAIL_STORAGE_ERR_NO_PERMISSION);
		} else {
			mail_storage_set_error(storage,
				MAIL_STORAGE_ERR_MAILBOX_NOT_FOUND, oldname);
		}
		return 0;
	}

	/* and create the new one under the parent mailbox */
	t_push();
	ret = acl_storage_have_right(storage,
				     get_parent_mailbox_name(storage, 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. */
			mail_storage_set_error(storage,
					       MAIL_STORAGE_ERR_NO_PERMISSION);
		}
		return -1;
	}

	return astorage->super.mailbox_rename(storage, oldname, newname);
}

static struct mailbox_list *
acl_mailbox_list_next(struct mailbox_list_context *ctx)
{
	struct acl_mail_storage *astorage = ACL_CONTEXT(ctx->storage);
	struct mailbox_list *list;
	int ret;

	for (;;) {
		list = astorage->super.mailbox_list_next(ctx);
		if (list == NULL)
			return NULL;

		ret = acl_storage_have_right(ctx->storage, list->name,
					     ACL_STORAGE_RIGHT_LOOKUP, NULL);
		if (ret > 0)
			return list;
		if (ret < 0) {
			ctx->failed = TRUE;
			return NULL;
		}

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

		/* skip to next one */
	}
}

static int acl_get_mailbox_name_status(struct mail_storage *storage,
				       const char *name,
				       enum mailbox_name_status *status)
{
	struct acl_mail_storage *astorage = ACL_CONTEXT(storage);
	int ret;

	ret = acl_storage_have_right(storage, name,
				     ACL_STORAGE_RIGHT_LOOKUP, NULL);
	if (ret < 0)
		return -1;

	if (astorage->super.get_mailbox_name_status(storage, 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();
		ret = acl_storage_have_right(storage,
				get_parent_mailbox_name(storage, name),
				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;
}

void acl_mail_storage_created(struct mail_storage *storage)
{
	struct acl_mail_storage *astorage;
	struct acl_backend *backend;
	const char *acl_env, *user_env, *owner_username;
	unsigned int i;

	if (acl_next_hook_mail_storage_created != NULL)
		acl_next_hook_mail_storage_created(storage);

	acl_env = getenv("ACL");
	user_env = getenv("MASTER_USER");
	if (user_env == NULL)
		user_env = getenv("USER");
	i_assert(acl_env != NULL && user_env != NULL);

	/* FIXME: set groups. owner_username isn't also correct here it's a
	   per-mailbox thing. but we don't currently support shared mailboxes,
	   so this will do for now.. */
	owner_username =
		(storage->flags & MAIL_STORAGE_FLAG_SHARED_NAMESPACE) == 0 ?
		getenv("USER") : NULL;
	backend = acl_backend_init(acl_env, storage, user_env, NULL,
				  owner_username);
	if (backend == NULL)
		i_fatal("ACL backend initialization failed");

	if ((storage->flags & MAIL_STORAGE_FLAG_FULL_FS_ACCESS) != 0) {
		/* FIXME: not necessarily, but safer to do this for now.. */
		i_fatal("mail_full_filesystem_access=yes is "
			"incompatible with ACLs");
	}

	astorage = p_new(storage->pool, struct acl_mail_storage, 1);
	astorage->super = storage->v;
	astorage->backend = backend;
	storage->v.destroy = acl_storage_destroy;
	storage->v.mailbox_open = acl_mailbox_open;
	storage->v.mailbox_create = acl_mailbox_create;
	storage->v.mailbox_delete = acl_mailbox_delete;
	storage->v.mailbox_rename = acl_mailbox_rename;
	storage->v.mailbox_list_next = acl_mailbox_list_next;
	storage->v.get_mailbox_name_status = acl_get_mailbox_name_status;

	/* build ACL right lookup table */
	for (i = 0; i < ACL_STORAGE_RIGHT_COUNT; i++) {
		astorage->acl_storage_right_idx[i] =
			acl_backend_lookup_right(backend,
						 acl_storage_right_names[i]);
	}

	if (!acl_storage_module_id_set) {
		acl_storage_module_id = mail_storage_module_id++;
		acl_storage_module_id_set = TRUE;
	}

	array_idx_set(&storage->module_contexts,
		      acl_storage_module_id, &astorage);
}



More information about the dovecot-cvs mailing list