changeset 8418:29b1ec15880a HEAD

Added IMAP ACL commands with ability to modify ACLs. Based on patch by Bernhard Herzog and Sascha Wilde.
author Timo Sirainen <tss@iki.fi>
date Sun, 16 Nov 2008 04:46:14 +0200
parents 106ad33091ca
children b8a3b8d6c60c
files configure.in src/plugins/Makefile.am src/plugins/acl/Makefile.am src/plugins/acl/acl-api-private.h src/plugins/acl/acl-api.c src/plugins/acl/acl-api.h src/plugins/acl/acl-backend-vfile.c src/plugins/acl/acl-cache.c src/plugins/acl/acl-mailbox.c src/plugins/acl/acl-plugin.h src/plugins/acl/acl-storage.h src/plugins/imap-acl/Makefile.am src/plugins/imap-acl/imap-acl-plugin.c src/plugins/imap-acl/imap-acl-plugin.h
diffstat 14 files changed, 947 insertions(+), 88 deletions(-) [+]
line wrap: on
line diff
--- a/configure.in	Sun Nov 16 04:45:06 2008 +0200
+++ b/configure.in	Sun Nov 16 04:46:14 2008 +0200
@@ -2453,6 +2453,7 @@
 src/util/Makefile
 src/plugins/Makefile
 src/plugins/acl/Makefile
+src/plugins/imap-acl/Makefile
 src/plugins/autocreate/Makefile
 src/plugins/convert/Makefile
 src/plugins/expire/Makefile
--- a/src/plugins/Makefile.am	Sun Nov 16 04:45:06 2008 +0200
+++ b/src/plugins/Makefile.am	Sun Nov 16 04:46:14 2008 +0200
@@ -12,6 +12,7 @@
 
 SUBDIRS = \
 	acl \
+	imap-acl \
 	autocreate \
 	convert \
 	expire \
--- a/src/plugins/acl/Makefile.am	Sun Nov 16 04:45:06 2008 +0200
+++ b/src/plugins/acl/Makefile.am	Sun Nov 16 04:46:14 2008 +0200
@@ -26,7 +26,8 @@
 	acl-api-private.h \
 	acl-backend-vfile.h \
 	acl-cache.h \
-	acl-plugin.h
+	acl-plugin.h \
+	acl-storage.h
 
 install-exec-local:
 	for d in imap lda; do \
--- a/src/plugins/acl/acl-api-private.h	Sun Nov 16 04:45:06 2008 +0200
+++ b/src/plugins/acl/acl-api-private.h	Sun Nov 16 04:46:14 2008 +0200
@@ -3,6 +3,13 @@
 
 #include "acl-api.h"
 
+#define ACL_ID_NAME_ANYONE "anyone"
+#define ACL_ID_NAME_AUTHENTICATED "authenticated"
+#define ACL_ID_NAME_OWNER "owner"
+#define ACL_ID_NAME_USER_PREFIX "user="
+#define ACL_ID_NAME_GROUP_PREFIX "group="
+#define ACL_ID_NAME_GROUP_OVERRIDE_PREFIX "group-override="
+
 struct acl_backend_vfuncs {
 	struct acl_backend *(*alloc)(void);
 	int (*init)(struct acl_backend *backend, const char *data);
@@ -25,7 +32,7 @@
 
 	int (*object_refresh_cache)(struct acl_object *aclobj);
 	int (*object_update)(struct acl_object *aclobj,
-			     const struct acl_rights_update *rights);
+			     const struct acl_rights_update *update);
 
 	struct acl_object_list_iter *
 		(*object_list_init)(struct acl_object *aclobj);
@@ -65,9 +72,13 @@
 	struct acl_object *aclobj;
 
 	unsigned int idx;
+	unsigned int returned_owner:1;
 	unsigned int failed:1;
 };
 
+const char *const *
+acl_backend_mask_get_names(struct acl_backend *backend,
+			   const struct acl_mask *mask, pool_t pool);
 int acl_backend_get_default_rights(struct acl_backend *backend,
 				   const struct acl_mask **mask_r);
 
--- a/src/plugins/acl/acl-api.c	Sun Nov 16 04:45:06 2008 +0200
+++ b/src/plugins/acl/acl-api.c	Sun Nov 16 04:46:14 2008 +0200
@@ -52,31 +52,14 @@
 	return acl_cache_mask_isset(have_mask, right_idx);
 }
 
-static int acl_object_get_my_rights_real(struct acl_object *aclobj, pool_t pool,
-					 const char *const **rights_r)
+const char *const *
+acl_backend_mask_get_names(struct acl_backend *backend,
+			   const struct acl_mask *mask, pool_t pool)
 {
-	struct acl_backend *backend = aclobj->backend;
-	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 */
-		if (acl_backend_get_default_rights(backend, &mask) < 0)
-			return -1;
-	} else {
-		if (backend->v.object_refresh_cache(aclobj) < 0)
-			return -1;
-
-		mask = acl_cache_get_my_rights(backend->cache,
-					       aclobj->name);
-		if (mask == NULL) {
-			if (acl_backend_get_default_rights(backend, &mask) < 0)
-				return -1;
-		}
-	}
-
 	names = acl_cache_get_names(backend->cache, &names_count);
 	buf = t_new(const char *, (mask->size * CHAR_BIT) + 1);
 	count = 0;
@@ -98,7 +81,32 @@
 	/* @UNSAFE */
 	rights = p_new(pool, const char *, count + 1);
 	memcpy(rights, buf, count * sizeof(const char *));
-	*rights_r = rights;
+	return rights;
+}
+
+static int acl_object_get_my_rights_real(struct acl_object *aclobj, pool_t pool,
+					 const char *const **rights_r)
+{
+	struct acl_backend *backend = aclobj->backend;
+	const struct acl_mask *mask;
+
+	if (*aclobj->name == '\0') {
+		/* we want to look up default rights */
+		if (acl_backend_get_default_rights(backend, &mask) < 0)
+			return -1;
+	} else {
+		if (backend->v.object_refresh_cache(aclobj) < 0)
+			return -1;
+
+		mask = acl_cache_get_my_rights(backend->cache,
+					       aclobj->name);
+		if (mask == NULL) {
+			if (acl_backend_get_default_rights(backend, &mask) < 0)
+				return -1;
+		}
+	}
+
+	*rights_r = acl_backend_mask_get_names(backend, mask, pool);
 	return 0;
 }
 
@@ -116,9 +124,9 @@
 }
 
 int acl_object_update(struct acl_object *aclobj,
-		      const struct acl_rights_update *rights)
+		      const struct acl_rights_update *update)
 {
-        return aclobj->backend->v.object_update(aclobj, rights);
+        return aclobj->backend->v.object_update(aclobj, update);
 }
 
 struct acl_object_list_iter *acl_object_list_init(struct acl_object *aclobj)
--- a/src/plugins/acl/acl-api.h	Sun Nov 16 04:45:06 2008 +0200
+++ b/src/plugins/acl/acl-api.h	Sun Nov 16 04:46:14 2008 +0200
@@ -3,6 +3,7 @@
 
 struct mailbox_list;
 struct mail_storage;
+struct mailbox;
 struct acl_object;
 
 /* Show mailbox in mailbox list. Allow subscribing to it. */
@@ -48,12 +49,14 @@
 };
 
 enum acl_modify_mode {
+	/* Remove rights from existing ACL */
+	ACL_MODIFY_MODE_REMOVE = 0,
 	/* Add rights to existing ACL (or create a new one) */
-	ACL_MODIFY_MODE_ADD = 0,
-	/* Remove rights from existing ACL */
-	ACL_MODIFY_MODE_REMOVE,
+	ACL_MODIFY_MODE_ADD,
 	/* Replace existing ACL with given rights */
-	ACL_MODIFY_MODE_REPLACE
+	ACL_MODIFY_MODE_REPLACE,
+	/* Clear all the rights from an existing ACL */
+	ACL_MODIFY_MODE_CLEAR
 };
 
 struct acl_rights {
@@ -126,7 +129,7 @@
 
 /* Update ACL of given object. */
 int acl_object_update(struct acl_object *aclobj,
-		      const struct acl_rights_update *rights);
+		      const struct acl_rights_update *update);
 
 /* List all identifiers. */
 struct acl_object_list_iter *acl_object_list_init(struct acl_object *aclobj);
--- a/src/plugins/acl/acl-backend-vfile.c	Sun Nov 16 04:45:06 2008 +0200
+++ b/src/plugins/acl/acl-backend-vfile.c	Sun Nov 16 04:46:14 2008 +0200
@@ -3,9 +3,14 @@
 #include "lib.h"
 #include "ioloop.h"
 #include "array.h"
+#include "bsearch-insert-pos.h"
+#include "str.h"
 #include "istream.h"
+#include "ostream.h"
+#include "file-dotlock.h"
 #include "nfs-workarounds.h"
 #include "mail-storage-private.h"
+#include "mail-namespace.h"
 #include "acl-cache.h"
 #include "acl-backend-vfile.h"
 
@@ -52,6 +57,14 @@
 	{ '\0', NULL }
 };
 
+static struct dotlock_settings dotlock_set = {
+	MEMBER(temp_prefix) NULL,
+	MEMBER(lock_suffix) NULL,
+
+	MEMBER(timeout) 30,
+	MEMBER(stale_timeout) 120
+};
+
 static struct acl_backend *acl_backend_vfile_alloc(void)
 {
 	struct acl_backend_vfile *backend;
@@ -266,7 +279,8 @@
 }
 
 static const char *const *
-acl_rights_alloc(pool_t pool, ARRAY_TYPE(const_string) *rights_arr)
+acl_rights_alloc(pool_t pool, ARRAY_TYPE(const_string) *rights_arr,
+		 bool dup_strings)
 {
 	const char **ret, **rights;
 	unsigned int i, dest, count;
@@ -283,6 +297,11 @@
 			if (strcmp(rights[i-1], rights[i]) != 0)
 				ret[dest++] = rights[i];
 		}
+		ret[dest] = NULL;
+		if (dup_strings) {
+			for (i = 0; i < dest; i++)
+				ret[i] = p_strdup(pool, ret[i]);
+		}
 	}
 	return ret;
 }
@@ -326,7 +345,7 @@
 		}
 	}
 
-	return acl_rights_alloc(pool, &rights);
+	return acl_rights_alloc(pool, &rights, FALSE);
 }
 
 static int
@@ -362,31 +381,34 @@
 
 	switch (*line) {
 	case 'u':
-		if (strncmp(line, "user=", 5) == 0) {
+		if (strncmp(line, ACL_ID_NAME_USER_PREFIX,
+			    strlen(ACL_ID_NAME_USER_PREFIX)) == 0) {
 			rights.id_type = ACL_ID_USER;
 			rights.identifier = line + 5;
 			break;
 		}
 	case 'o':
-		if (strcmp(line, "owner") == 0) {
+		if (strcmp(line, ACL_ID_NAME_OWNER) == 0) {
 			rights.id_type = ACL_ID_OWNER;
 			break;
 		}
 	case 'g':
-		if (strncmp(line, "group=", 6) == 0) {
+		if (strncmp(line, ACL_ID_NAME_GROUP_PREFIX,
+			    strlen(ACL_ID_NAME_GROUP_PREFIX)) == 0) {
 			rights.id_type = ACL_ID_GROUP;
 			rights.identifier = line + 6;
 			break;
-		} else if (strncmp(line, "group-override=", 15) == 0) {
+		} else if (strncmp(line, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX,
+				   strlen(ACL_ID_NAME_GROUP_OVERRIDE_PREFIX)) == 0) {
 			rights.id_type = ACL_ID_GROUP_OVERRIDE;
 			rights.identifier = line + 15;
 			break;
 		}
 	case 'a':
-		if (strcmp(line, "authenticated") == 0) {
+		if (strcmp(line, ACL_ID_NAME_AUTHENTICATED) == 0) {
 			rights.id_type = ACL_ID_AUTHENTICATED;
 			break;
-		} else if (strcmp(line, "anyone") == 0 ||
+		} else if (strcmp(line, ACL_ID_NAME_ANYONE) == 0 ||
 			   strcmp(line, "anonymous") == 0) {
 			rights.id_type = ACL_ID_ANYONE;
 			break;
@@ -615,17 +637,23 @@
 static int acl_rights_cmp(const void *p1, const void *p2)
 {
 	const struct acl_rights *r1 = p1, *r2 = p2;
+	int ret;
 
 	if (r1->global != r2->global) {
 		/* globals have higher priority than locals */
 		return r1->global ? 1 : -1;
 	}
 
-	return r1->id_type - r2->id_type;
+	ret = r1->id_type - r2->id_type;
+	if (ret != 0)
+		return ret;
+
+	return null_strcmp(r1->identifier, r2->identifier);
 }
 
 static void
-acl_rights_merge(pool_t pool, const char *const **destp, const char *const *src)
+acl_rights_merge(pool_t pool, const char *const **destp, const char *const *src,
+		 bool dup_strings)
 {
 	const char *const *dest = *destp;
 	ARRAY_TYPE(const_string) rights;
@@ -641,7 +669,7 @@
 			array_append(&rights, &src[i], 1);
 	}
 
-	*destp = acl_rights_alloc(pool, &rights);
+	*destp = acl_rights_alloc(pool, &rights, dup_strings);
 }
 
 static void acl_backend_vfile_rights_sort(struct acl_object_vfile *aclobj)
@@ -657,17 +685,14 @@
 
 	/* merge identical identifiers */
 	for (dest = 0, i = 1; i < count; i++) {
-		if (rights[i].global == rights[dest].global &&
-		    rights[i].id_type == rights[dest].id_type &&
-		    null_strcmp(rights[i].identifier,
-				rights[dest].identifier) == 0) {
+		if (acl_rights_cmp(&rights[i], &rights[dest]) == 0) {
 			/* add i's rights to dest and delete i */
 			acl_rights_merge(aclobj->rights_pool,
 					 &rights[dest].rights,
-					 rights[i].rights);
+					 rights[i].rights, FALSE);
 			acl_rights_merge(aclobj->rights_pool,
 					 &rights[dest].neg_rights,
-					 rights[i].neg_rights);
+					 rights[i].neg_rights, FALSE);
 		} else {
 			if (++dest != i)
 				rights[dest] = rights[i];
@@ -763,24 +788,306 @@
 	return 0;
 }
 
+static int acl_backend_vfile_update_begin(struct acl_object_vfile *aclobj,
+					  struct dotlock **dotlock_r)
+{
+	struct acl_object *_aclobj = &aclobj->aclobj;
+	mode_t mode;
+	gid_t gid;
+	int fd;
+
+	/* first lock the ACL file */
+	mailbox_list_get_permissions(_aclobj->backend->list, &mode, &gid);
+	fd = file_dotlock_open_mode(&dotlock_set, aclobj->local_path, 0,
+				    mode, (uid_t)-1, gid, dotlock_r);
+	if (fd == -1) {
+		i_error("file_dotlock_open_mode(%s) failed: %m",
+			aclobj->local_path);
+		return -1;
+	}
+
+	/* locked successfully, re-read the existing file to make sure we
+	   don't lose any changes. */
+	acl_cache_flush(_aclobj->backend->cache, _aclobj->name);
+	if (acl_backend_vfile_object_refresh_cache(_aclobj) < 0) {
+		file_dotlock_delete(dotlock_r);
+		return -1;
+	}
+	return fd;
+}
+
+static bool modify_right_list(pool_t pool,
+			      const char *const **rightsp,
+			      const char *const *modify_rights,
+			      enum acl_modify_mode modify_mode)
+{
+	const char *const *old_rights = *rightsp;
+	const char *const *new_rights;
+	const char *null = NULL;
+	ARRAY_TYPE(const_string) rights;
+	unsigned int i, j;
+
+	if (modify_rights == NULL && modify_mode != ACL_MODIFY_MODE_CLEAR) {
+		/* nothing to do here */
+		return FALSE;
+	}
+
+	if (old_rights == NULL)
+		old_rights = &null;
+
+	switch (modify_mode) {
+	case ACL_MODIFY_MODE_REMOVE:
+		if (*old_rights == NULL) {
+			/* nothing to do */
+			return FALSE;
+		}
+		t_array_init(&rights, 64);
+		for (i = 0; old_rights[i] != NULL; i++) {
+			for (j = 0; modify_rights[j] != NULL; j++) {
+				if (strcmp(old_rights[i], modify_rights[j]) == 0)
+					break;
+			}
+			if (modify_rights[j] == NULL)
+				array_append(&rights, &old_rights[i], 1);
+		}
+		new_rights = &null;
+		modify_rights = array_idx(&rights, 0);
+		acl_rights_merge(pool, &new_rights, modify_rights, TRUE);
+		break;
+	case ACL_MODIFY_MODE_ADD:
+		new_rights = old_rights;
+		acl_rights_merge(pool, &new_rights, modify_rights, TRUE);
+		break;
+	case ACL_MODIFY_MODE_REPLACE:
+		new_rights = &null;
+		acl_rights_merge(pool, &new_rights, modify_rights, TRUE);
+		break;
+	case ACL_MODIFY_MODE_CLEAR:
+		if (*rightsp == NULL) {
+			/* ACL didn't exist before either */
+			return FALSE;
+		}
+		*rightsp = NULL;
+		return TRUE;
+	}
+	*rightsp = new_rights;
+
+	/* see if anything changed */
+	for (i = 0; old_rights[i] != NULL && new_rights[i] != NULL; i++) {
+		if (strcmp(old_rights[i], new_rights[i]) != 0)
+			return TRUE;
+	}
+	return old_rights[i] != NULL || new_rights[i] != NULL;
+}
+
+static bool
+vfile_object_modify_right(struct acl_object_vfile *aclobj, unsigned int idx,
+			  const struct acl_rights_update *update)
+{
+	struct acl_rights *right;
+	bool c1, c2;
+
+	right = array_idx_modifiable(&aclobj->rights, idx);
+	c1 = modify_right_list(aclobj->rights_pool, &right->rights,
+			       update->rights.rights, update->modify_mode);
+	c2 = modify_right_list(aclobj->rights_pool, &right->neg_rights,
+			       update->rights.neg_rights,
+			       update->neg_modify_mode);
+
+	if (right->rights == NULL && right->neg_rights == NULL) {
+		/* this identifier no longer exists */
+		array_delete(&aclobj->rights, idx, 1);
+		c1 = TRUE;
+	}
+	return c1 || c2;
+}
+
+static bool
+vfile_object_add_right(struct acl_object_vfile *aclobj, unsigned int idx,
+		       const struct acl_rights_update *update)
+{
+	struct acl_rights right;
+
+	if (update->modify_mode == ACL_MODIFY_MODE_REMOVE &&
+	    update->neg_modify_mode == ACL_MODIFY_MODE_REMOVE) {
+		/* nothing to do */
+		return FALSE;
+	}
+
+	memset(&right, 0, sizeof(right));
+	right.id_type = update->rights.id_type;
+	right.identifier = p_strdup(aclobj->rights_pool,
+				    update->rights.identifier);
+	array_insert(&aclobj->rights, idx, &right, 1);
+	return vfile_object_modify_right(aclobj, idx, update);
+}
+
+static void vfile_write_rights_list(string_t *dest, const char *const *rights)
+{
+	char c2[2];
+	unsigned int i, j, pos;
+
+	c2[1] = '\0';
+	pos = str_len(dest);
+	for (i = 0; rights[i] != NULL; i++) {
+		/* use letters if possible */
+		for (j = 0; acl_letter_map[j].name != NULL; j++) {
+			if (strcmp(rights[i], acl_letter_map[j].name) == 0) {
+				c2[0] = acl_letter_map[j].letter;
+				str_insert(dest, pos, c2);
+				pos++;
+				break;
+			}
+		}
+		if (acl_letter_map[j].name == NULL) {
+			/* fallback to full name */
+			str_append_c(dest, ' ');
+			str_append(dest, rights[j]);
+		}
+	}
+}
+
+static void
+vfile_write_right(string_t *dest, const struct acl_rights *right,
+		  bool neg)
+{
+	const char *const *rights = neg ? right->neg_rights : right->rights;
+
+	if (neg) str_append_c(dest,'-');
+
+	switch (right->id_type) {
+	case ACL_ID_ANYONE:
+		str_append(dest, ACL_ID_NAME_ANYONE);
+		break;
+	case ACL_ID_AUTHENTICATED:
+		str_append(dest, ACL_ID_NAME_AUTHENTICATED);
+		break;
+	case ACL_ID_OWNER:
+		str_append(dest, ACL_ID_NAME_OWNER);
+		break;
+	case ACL_ID_USER:
+		str_append(dest, ACL_ID_NAME_USER_PREFIX);
+		str_append(dest, right->identifier);
+		break;
+	case ACL_ID_GROUP:
+		str_append(dest, ACL_ID_NAME_GROUP_PREFIX);
+		str_append(dest, right->identifier);
+		break;
+	case ACL_ID_GROUP_OVERRIDE:
+		str_append(dest, ACL_ID_NAME_GROUP_OVERRIDE_PREFIX);
+		str_append(dest, right->identifier);
+		break;
+	case ACL_ID_TYPE_COUNT:
+		i_unreached();
+	}
+	str_append_c(dest, ' ');
+	vfile_write_rights_list(dest, rights);
+	str_append_c(dest, '\n');
+}
+
 static int
-acl_backend_vfile_object_update(struct acl_object *aclobj ATTR_UNUSED,
-				const struct acl_rights_update *rights
-					ATTR_UNUSED)
+acl_backend_vfile_update_write(struct acl_object_vfile *aclobj,
+			       int fd, const char *path)
 {
-	/* FIXME */
-	return -1;
+	struct ostream *output;
+	string_t *str;
+	const struct acl_rights *rights;
+	unsigned int i, count;
+	int ret = 0;
+
+	output = o_stream_create_fd_file(fd, 0, FALSE);
+	o_stream_cork(output);
+
+	str = t_str_new(256);
+	/* rights are sorted with globals at the end, so we can stop at the
+	   first global */
+	rights = array_get(&aclobj->rights, &count);
+	for (i = 0; i < count && !rights[i].global; i++) {
+		if (rights[i].rights != NULL)
+			vfile_write_right(str, &rights[i], FALSE);
+		if (rights[i].neg_rights != NULL)
+			vfile_write_right(str, &rights[i], TRUE);
+		o_stream_send(output, str_data(str), str_len(str));
+		str_truncate(str, 0);
+	}
+	if (o_stream_flush(output) < 0) {
+		i_error("write(%s) failed: %m", path);
+		ret = -1;
+	}
+	o_stream_destroy(&output);
+	/* we really don't want to lose ACL files' contents, so fsync() always
+	   before renaming */
+	if (fsync(fd) < 0) {
+		i_error("fsync(%s) failed: %m", path);
+		ret = -1;
+	}
+	return ret;
+}
+
+static int
+acl_backend_vfile_object_update(struct acl_object *_aclobj,
+				const struct acl_rights_update *update)
+{
+	struct acl_object_vfile *aclobj = (struct acl_object_vfile *)_aclobj;
+	const struct acl_rights *rights;
+	struct dotlock *dotlock;
+	const char *path;
+	unsigned int i, count;
+	int fd;
+	bool changed;
+
+	/* global ACLs can't be updated here */
+	i_assert(!update->rights.global);
+
+	fd = acl_backend_vfile_update_begin(aclobj, &dotlock);
+	if (fd == -1)
+		return -1;
+
+	rights = array_get(&aclobj->rights, &count);
+	if (!bsearch_insert_pos(&update->rights, rights, count, sizeof(*rights),
+				acl_rights_cmp, &i))
+		changed = vfile_object_add_right(aclobj, i, update);
+	else
+		changed = vfile_object_modify_right(aclobj, i, update);
+	if (!changed) {
+		file_dotlock_delete(&dotlock);
+		return 0;
+	} else {
+		acl_cache_flush(_aclobj->backend->cache, _aclobj->name);
+
+		path = file_dotlock_get_lock_path(dotlock);
+		if (acl_backend_vfile_update_write(aclobj, fd, path) < 0) {
+			file_dotlock_delete(&dotlock);
+			return -1;
+		}
+		return file_dotlock_replace(&dotlock, 0);
+	}
 }
 
 static struct acl_object_list_iter *
-acl_backend_vfile_object_list_init(struct acl_object *aclobj)
+acl_backend_vfile_object_list_init(struct acl_object *_aclobj)
 {
+	struct acl_object_vfile *aclobj =
+		(struct acl_object_vfile *)_aclobj;
 	struct acl_object_list_iter *iter;
+	struct mail_namespace *ns;
 
 	iter = i_new(struct acl_object_list_iter, 1);
-	iter->aclobj = aclobj;
+	iter->aclobj = _aclobj;
+
+	if (!array_is_created(&aclobj->rights)) {
+		/* we may have the object cached, but we don't have all the
+		   rights read into memory */
+		acl_cache_flush(_aclobj->backend->cache, _aclobj->name);
+	}
 
-	if (aclobj->backend->v.object_refresh_cache(aclobj) < 0)
+	/* be sure to return owner for private namespaces.
+	   (other namespaces don't have an owner) */
+	ns = mailbox_list_get_namespace(_aclobj->backend->list);
+	if (ns->type != NAMESPACE_PRIVATE)
+		iter->returned_owner = TRUE;
+
+	if (_aclobj->backend->v.object_refresh_cache(_aclobj) < 0)
 		iter->failed = TRUE;
 	return iter;
 }
@@ -793,11 +1100,26 @@
 		(struct acl_object_vfile *)iter->aclobj;
 	const struct acl_rights *rights;
 
-	if (!array_is_created(&aclobj->rights) ||
-	    iter->idx == array_count(&aclobj->rights))
-		return 0;
+	if (iter->idx == array_count(&aclobj->rights)) {
+		struct acl_backend *backend = iter->aclobj->backend;
+
+		if (iter->returned_owner)
+			return 0;
+
+		/* return missing owner based on the default ACLs */
+		iter->returned_owner = TRUE;
+		memset(rights_r, 0, sizeof(*rights_r));
+		rights_r->id_type = ACL_ID_OWNER;
+		rights_r->rights =
+			acl_backend_mask_get_names(backend,
+						   backend->default_aclmask,
+						   pool_datastack_create());
+		return 1;
+	}
 
 	rights = array_idx(&aclobj->rights, iter->idx++);
+	if (rights->id_type == ACL_ID_OWNER && rights->rights != NULL)
+		iter->returned_owner = TRUE;
 	*rights_r = *rights;
 	return 1;
 }
--- a/src/plugins/acl/acl-cache.c	Sun Nov 16 04:45:06 2008 +0200
+++ b/src/plugins/acl/acl-cache.c	Sun Nov 16 04:46:14 2008 +0200
@@ -244,6 +244,8 @@
 			changed = FALSE;
 		new_mask = change_mask;
 		break;
+	case ACL_MODIFY_MODE_CLEAR:
+		i_unreached();
 	}
 
 	if (new_mask != old_mask) {
--- a/src/plugins/acl/acl-mailbox.c	Sun Nov 16 04:45:06 2008 +0200
+++ b/src/plugins/acl/acl-mailbox.c	Sun Nov 16 04:46:14 2008 +0200
@@ -30,7 +30,21 @@
 static MODULE_CONTEXT_DEFINE_INIT(acl_mail_module, &mail_module_register);
 static struct acl_transaction_context acl_transaction_failure;
 
-static int mailbox_acl_right_lookup(struct mailbox *box, unsigned int right_idx)
+struct acl_object *acl_storage_get_default_aclobj(struct mail_storage *storage)
+{
+	struct acl_mail_storage *astorage = ACL_CONTEXT(storage);
+
+	return astorage->rights.backend->default_aclobj;
+}
+
+struct acl_object *acl_mailbox_get_aclobj(struct mailbox *box)
+{
+	struct acl_mailbox *abox = ACL_CONTEXT(box);
+
+	return abox->aclobj;
+}
+
+int acl_mailbox_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);
@@ -57,19 +71,19 @@
 	if (abox->module_ctx.super.is_readonly(box))
 		return TRUE;
 
-	if (mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_INSERT) > 0)
+	if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_INSERT) > 0)
 		return FALSE;
-	if (mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_EXPUNGE) > 0)
+	if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_EXPUNGE) > 0)
 		return FALSE;
 
 	/* Next up is the "shared flag rights" */
-	if (mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0)
+	if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0)
 		return FALSE;
 	if ((box->private_flags_mask & MAIL_DELETED) == 0 &&
-	    mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED) > 0)
+	    acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED) > 0)
 		return FALSE;
 	if ((box->private_flags_mask & MAIL_SEEN) == 0 &&
-	    mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN) > 0)
+	    acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_SEEN) > 0)
 		return FALSE;
 
 	return TRUE;
@@ -82,7 +96,7 @@
 	if (!abox->module_ctx.super.allow_new_keywords(box))
 		return FALSE;
 
-	return mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0;
+	return acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE) > 0;
 }
 
 static int acl_mailbox_close(struct mailbox *box)
@@ -99,17 +113,17 @@
 {
 	int ret;
 
-	ret = mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE);
+	ret = acl_mailbox_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);
+	ret = acl_mailbox_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);
+	ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE_DELETED);
 	if (ret < 0)
 		return -1;
 	*flag_del_r = ret > 0;
@@ -171,7 +185,7 @@
 	union mail_module_context *amail = ACL_MAIL_CONTEXT(mail);
 	int ret;
 
-	ret = mailbox_acl_right_lookup(_mail->box, ACL_STORAGE_RIGHT_WRITE);
+	ret = acl_mailbox_right_lookup(_mail->box, ACL_STORAGE_RIGHT_WRITE);
 	if (ret <= 0) {
 		/* if we don't have permission, just silently return success. */
 		if (ret < 0)
@@ -188,7 +202,7 @@
 	union mail_module_context *amail = ACL_MAIL_CONTEXT(mail);
 	int ret;
 
-	ret = mailbox_acl_right_lookup(_mail->box, ACL_STORAGE_RIGHT_EXPUNGE);
+	ret = acl_mailbox_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
@@ -251,7 +265,7 @@
 	struct mailbox *box = ctx->transaction->box;
 	struct acl_mailbox *abox = ACL_CONTEXT(box);
 
-	if (mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_INSERT) <= 0)
+	if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_INSERT) <= 0)
 		return -1;
 	if (acl_save_get_flags(box, &ctx->flags, &ctx->keywords) < 0)
 		return -1;
@@ -266,7 +280,7 @@
 {
 	struct acl_mailbox *abox = ACL_CONTEXT(t->box);
 
-	if (mailbox_acl_right_lookup(t->box, ACL_STORAGE_RIGHT_INSERT) <= 0)
+	if (acl_mailbox_right_lookup(t->box, ACL_STORAGE_RIGHT_INSERT) <= 0)
 		return -1;
 	if (acl_save_get_flags(t->box, &flags, &keywords) < 0)
 		return -1;
@@ -299,7 +313,7 @@
 	struct acl_mailbox *abox = ACL_CONTEXT(box);
 	int ret;
 
-	ret = mailbox_acl_right_lookup(box, ACL_STORAGE_RIGHT_WRITE);
+	ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_WRITE);
 	if (ret < 0) {
 		if (!skip_invalid)
 			return -1;
--- a/src/plugins/acl/acl-plugin.h	Sun Nov 16 04:45:06 2008 +0200
+++ b/src/plugins/acl/acl-plugin.h	Sun Nov 16 04:46:14 2008 +0200
@@ -2,25 +2,11 @@
 #define ACL_PLUGIN_H
 
 #include "mail-storage-private.h"
+#include "acl-storage.h"
 
 #define ACL_CONTEXT(obj) \
 	MODULE_CONTEXT(obj, acl_storage_module)
 
-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_storage_rights_context {
 	struct acl_backend *backend;
 	unsigned int acl_storage_right_idx[ACL_STORAGE_RIGHT_COUNT];
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/acl/acl-storage.h	Sun Nov 16 04:46:14 2008 +0200
@@ -0,0 +1,28 @@
+#ifndef ACL_STORAGE_H
+#define ACL_STORAGE_H
+
+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
+};
+
+/* Returns default acl_object for the given mail storage. */
+struct acl_object *acl_storage_get_default_aclobj(struct mail_storage *storage);
+/* Returns acl_object for the given mailbox. */
+struct acl_object *acl_mailbox_get_aclobj(struct mailbox *box);
+/* Returns 1 if we have the requested right. If not, returns 0 and sets storage
+   error to MAIL_ERROR_PERM. Returns -1 if internal error occurred and also
+   sets storage error. */
+int acl_mailbox_right_lookup(struct mailbox *box, unsigned int right_idx);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/imap-acl/Makefile.am	Sun Nov 16 04:46:14 2008 +0200
@@ -0,0 +1,20 @@
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/imap \
+	-I$(top_srcdir)/src/plugins/acl
+
+imap_moduledir = $(moduledir)/imap
+
+lib02_imap_acl_plugin_la_LDFLAGS = -module -avoid-version
+
+imap_module_LTLIBRARIES = \
+	lib02_imap_acl_plugin.la
+
+lib02_imap_acl_plugin_la_SOURCES = \
+	imap-acl-plugin.c
+
+noinst_HEADERS = \
+	imap-acl-plugin.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/imap-acl/imap-acl-plugin.c	Sun Nov 16 04:46:14 2008 +0200
@@ -0,0 +1,455 @@
+/* Copyright (c) 2008 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "str.h"
+#include "imap-quote.h"
+#include "imap-resp-code.h"
+#include "commands.h"
+#include "mail-storage.h"
+#include "mail-namespace.h"
+#include "acl-api.h"
+#include "acl-storage.h"
+#include "imap-acl-plugin.h"
+
+#include <stdlib.h>
+
+#define ERROR_NOT_ADMIN "["IMAP_RESP_CODE_ACL"] " \
+	"You lack administrator privileges on this mailbox."
+
+#define ACL_MAILBOX_OPEN_FLAGS \
+	(MAILBOX_OPEN_READONLY | MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT)
+
+#define IMAP_ACL_ANYONE "anyone"
+#define IMAP_ACL_AUTHENTICATED "authenticated"
+#define IMAP_ACL_OWNER "owner"
+#define IMAP_ACL_GROUP_PREFIX "$"
+#define IMAP_ACL_GROUP_OVERRIDE_PREFIX "!$"
+
+struct imap_acl_letter_map {
+	char letter;
+	const char *name;
+};
+
+static const struct imap_acl_letter_map imap_acl_letter_map[] = {
+	{ 'l', MAIL_ACL_LOOKUP },
+	{ 'r', MAIL_ACL_READ },
+	{ 'w', MAIL_ACL_WRITE },
+	{ 's', MAIL_ACL_WRITE_SEEN },
+	{ 't', MAIL_ACL_WRITE_DELETED },
+	{ 'i', MAIL_ACL_INSERT },
+	{ 'e', MAIL_ACL_EXPUNGE },
+	{ 'k', MAIL_ACL_CREATE },
+	{ 'x', MAIL_ACL_DELETE },
+	{ 'a', MAIL_ACL_ADMIN },
+	{ '\0', NULL }
+};
+
+static struct mailbox *
+acl_mailbox_open_as_admin(struct client_command_context *cmd, const char *name)
+{
+	struct mail_storage *storage;
+	struct mailbox *box;
+	int ret;
+
+	storage = client_find_storage(cmd, &name);
+	if (storage == NULL)
+		return NULL;
+
+	/* Force opening the mailbox so that we can give a nicer error message
+	   if mailbox isn't selectable but is listable. */
+	box = mailbox_open(storage, name, NULL, ACL_MAILBOX_OPEN_FLAGS |
+			   MAIL_STORAGE_FLAG_IGNORE_ACLS);
+	if (box == NULL) {
+		client_send_storage_error(cmd, storage);
+		return NULL;
+	}
+
+	ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_ADMIN);
+	if (ret > 0)
+		return box;
+
+	/* not an administrator. */
+	if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP) <= 0) {
+		client_send_tagline(cmd, t_strdup_printf(
+			"["IMAP_RESP_CODE_NONEXISTENT"] "
+			MAIL_ERRSTR_MAILBOX_NOT_FOUND, name));
+	} else {
+		client_send_tagline(cmd, "["IMAP_RESP_CODE_ACL"] "
+				    ERROR_NOT_ADMIN);
+	}
+	mailbox_close(&box);
+	return NULL;
+}
+
+static const struct imap_acl_letter_map *
+imap_acl_letter_map_find(const char *name)
+{
+	unsigned int i;
+
+	for (i = 0; imap_acl_letter_map[i].name != NULL; i++) {
+		if (strcmp(imap_acl_letter_map[i].name, name) == 0)
+			return &imap_acl_letter_map[i];
+	}
+	return NULL;
+}
+
+static void
+imap_acl_write_rights_list(string_t *dest, const char *const *rights)
+{
+	const struct imap_acl_letter_map *map;
+	unsigned int i;
+	bool append_c = FALSE, append_d = FALSE;
+
+	for (i = 0; rights[i] != NULL; i++) {
+		/* write only letters */
+		map = imap_acl_letter_map_find(rights[i]);
+		if (map != NULL) {
+			str_append_c(dest, map->letter);
+			if (map->letter == 'k' || map->letter == 'x')
+				append_c = TRUE;
+			if (map->letter == 't' || map->letter == 'e')
+				append_d = TRUE;
+		}
+	}
+	if (append_c)
+		str_append_c(dest, 'c');
+	if (append_d)
+		str_append_c(dest, 'd');
+}
+
+static void
+imap_acl_write_right(string_t *dest, string_t *tmp,
+		     const struct acl_rights *right, bool neg)
+{
+	const char *const *rights = neg ? right->neg_rights : right->rights;
+
+	if (neg) str_append_c(dest,'-');
+
+	str_truncate(tmp, 0);
+	switch (right->id_type) {
+	case ACL_ID_ANYONE:
+		str_append(tmp, IMAP_ACL_ANYONE);
+		break;
+	case ACL_ID_AUTHENTICATED:
+		str_append(tmp, IMAP_ACL_AUTHENTICATED);
+		break;
+	case ACL_ID_OWNER:
+		str_append(tmp, IMAP_ACL_OWNER);
+		break;
+	case ACL_ID_USER:
+		str_append(tmp, right->identifier);
+		break;
+	case ACL_ID_GROUP:
+		str_append(tmp, IMAP_ACL_GROUP_PREFIX);
+		str_append(tmp, right->identifier);
+		break;
+	case ACL_ID_GROUP_OVERRIDE:
+		str_append(tmp, IMAP_ACL_GROUP_OVERRIDE_PREFIX);
+		str_append(tmp, right->identifier);
+		break;
+	case ACL_ID_TYPE_COUNT:
+		i_unreached();
+	}
+
+	imap_quote_append(dest, str_data(tmp), str_len(tmp), FALSE);
+	str_append_c(dest, ' ');
+	imap_acl_write_rights_list(dest, rights);
+}
+
+static int imap_acl_write_aclobj(string_t *dest, struct acl_object *aclobj)
+{
+	struct acl_object_list_iter *iter;
+	struct acl_rights rights;
+	string_t *tmp;
+	int ret;
+
+	tmp = t_str_new(128);
+	iter = acl_object_list_init(aclobj);
+	while ((ret = acl_object_list_next(iter, &rights)) > 0) {
+		str_append_c(dest, ' ');
+		if (rights.rights != NULL)
+			imap_acl_write_right(dest, tmp, &rights, FALSE);
+		if (rights.neg_rights != NULL)
+			imap_acl_write_right(dest, tmp, &rights, TRUE);
+	}
+	acl_object_list_deinit(&iter);
+	return ret;
+}
+
+static bool cmd_getacl(struct client_command_context *cmd)
+{
+	struct mailbox *box;
+	const char *mailbox;
+	string_t *str;
+	unsigned int len;
+	int ret;
+
+	if (!client_read_string_args(cmd, 1, &mailbox)) {
+		client_send_command_error(cmd, "Invalid arguments.");
+		return TRUE;
+	}
+
+	box = acl_mailbox_open_as_admin(cmd, mailbox);
+	if (box == NULL)
+		return TRUE;
+
+	str = t_str_new(128);
+	str_append(str, "* ACL ");
+	imap_quote_append_string(str, mailbox, FALSE);
+	len = str_len(str);
+
+	ret = imap_acl_write_aclobj(str, acl_mailbox_get_aclobj(box));
+	if (ret == 0) {
+		client_send_line(cmd->client, str_c(str));
+		client_send_tagline(cmd, "OK Getacl completed.");
+	} else {
+		client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
+	}
+	mailbox_close(&box);
+	return TRUE;
+}
+
+static bool cmd_myrights(struct client_command_context *cmd)
+{
+	struct mail_storage *storage;
+	struct mailbox *box;
+	const char *mailbox, *real_mailbox;
+	const char *const *rights;
+	string_t *str;
+
+	if (!client_read_string_args(cmd, 1, &mailbox)) {
+		client_send_command_error(cmd, "Invalid arguments.");
+		return TRUE;
+	}
+
+	real_mailbox = mailbox;
+	storage = client_find_storage(cmd, &real_mailbox);
+	if (storage == NULL)
+		return TRUE;
+
+	box = mailbox_open(storage, real_mailbox, NULL, ACL_MAILBOX_OPEN_FLAGS);
+	if (box == NULL) {
+		client_send_storage_error(cmd, storage);
+		return TRUE;
+	}
+
+	if (acl_object_get_my_rights(acl_mailbox_get_aclobj(box),
+				     pool_datastack_create(), &rights) < 0) {
+		client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
+		mailbox_close(&box);
+		return TRUE;
+	}
+
+	str = t_str_new(128);
+	str_append(str, "* MYRIGHTS ");
+	imap_quote_append_string(str, mailbox, FALSE);
+	str_append_c(str,' ');
+	imap_acl_write_rights_list(str, rights);
+
+	client_send_line(cmd->client, str_c(str));
+	client_send_tagline(cmd, "OK Myrights completed.");
+	return TRUE;
+}
+
+static bool cmd_listrights(struct client_command_context *cmd)
+{
+	struct mailbox *box;
+	const char *mailbox, *identifier;
+	string_t *str;
+
+	if (!client_read_string_args(cmd, 2, &mailbox, &identifier)) {
+		client_send_command_error(cmd, "Invalid arguments.");
+		return TRUE;
+	}
+
+	box = acl_mailbox_open_as_admin(cmd, mailbox);
+	if (box == NULL)
+		return TRUE;
+
+	str = t_str_new(128);
+	str_append(str, "* LISTRIGHTS ");
+	imap_quote_append_string(str, mailbox, FALSE);
+	str_append_c(str, ' ');
+	imap_quote_append_string(str, identifier, FALSE);
+	str_append_c(str, ' ');
+	str_append(str, "\"\" l r w s t p i e k x a c d");
+
+	client_send_line(cmd->client, str_c(str));
+	client_send_tagline(cmd, "OK Listrights completed.");
+	return TRUE;
+}
+
+static int
+imap_acl_letters_parse(const char *letters, const char *const **rights_r,
+		       const char **error_r)
+{
+	ARRAY_TYPE(const_string) rights;
+	unsigned int i;
+
+	t_array_init(&rights, 64);
+	for (; *letters != '\0'; letters++) {
+		for (i = 0; imap_acl_letter_map[i].name != NULL; i++) {
+			if (imap_acl_letter_map[i].letter == *letters) {
+				array_append(&rights,
+					     &imap_acl_letter_map[i].name, 1);
+				break;
+			}
+		}
+		if (imap_acl_letter_map[i].name == NULL) {
+			*error_r = t_strdup_printf("Invalid ACL right: %c",
+						   *letters);
+			return -1;
+		}
+	}
+	(void)array_append_space(&rights);
+	*rights_r = array_idx(&rights, 0);
+	return 0;
+}
+
+static int
+imap_acl_identifier_parse(const char *id, struct acl_rights *rights)
+{
+	if (strcmp(id, IMAP_ACL_ANYONE) == 0)
+		rights->id_type = ACL_ID_ANYONE;
+	else if (strcmp(id, IMAP_ACL_AUTHENTICATED) == 0)
+		rights->id_type = ACL_ID_AUTHENTICATED;
+	else if (strcmp(id, IMAP_ACL_OWNER) == 0)
+		rights->id_type = ACL_ID_OWNER;
+	else if (strncmp(id, IMAP_ACL_GROUP_PREFIX,
+			 strlen(IMAP_ACL_GROUP_PREFIX)) == 0) {
+		rights->id_type = ACL_ID_GROUP;
+		rights->identifier = id + strlen(IMAP_ACL_GROUP_PREFIX);
+	} else if (strncmp(id, IMAP_ACL_GROUP_OVERRIDE_PREFIX,
+			   strlen(IMAP_ACL_GROUP_OVERRIDE_PREFIX)) == 0) {
+		rights->id_type = ACL_ID_GROUP_OVERRIDE;
+		rights->identifier = id +
+			strlen(IMAP_ACL_GROUP_OVERRIDE_PREFIX);
+	} else {
+		rights->id_type = ACL_ID_USER;
+		rights->identifier = id;
+	}
+	return 0;
+}
+
+static bool cmd_setacl(struct client_command_context *cmd)
+{
+	struct mailbox *box;
+	struct acl_rights_update update;
+	const char *mailbox, *identifier, *rights, *error;
+	bool negative = FALSE;
+
+	if (!client_read_string_args(cmd, 3, &mailbox, &identifier, &rights) ||
+	    *identifier == '\0' || *rights == '\0') {
+		client_send_command_error(cmd, "Invalid arguments.");
+		return TRUE;
+	}
+
+	memset(&update, 0, sizeof(update));
+	if (*identifier == '-') {
+		negative = TRUE;
+		identifier++;
+	}
+
+	if (imap_acl_identifier_parse(identifier, &update.rights) < 0) {
+		client_send_command_error(cmd,
+			t_strdup_printf("Invalid identifier: %s", identifier));
+		return TRUE;
+	}
+	if (imap_acl_letters_parse(rights, &update.rights.rights, &error) < 0) {
+		client_send_command_error(cmd, error);
+		return TRUE;
+	}
+
+	box = acl_mailbox_open_as_admin(cmd, mailbox);
+	if (box == NULL)
+		return TRUE;
+
+	switch (*rights) {
+	case '-':
+		update.modify_mode = ACL_MODIFY_MODE_REMOVE;
+		rights++;
+		break;
+	case '+':
+		update.modify_mode = ACL_MODIFY_MODE_ADD;
+		rights++;
+		break;
+	default:
+		update.modify_mode = ACL_MODIFY_MODE_REPLACE;
+		break;
+	}
+
+	if (negative) {
+		update.neg_modify_mode = update.modify_mode;
+		update.modify_mode = ACL_MODIFY_MODE_REMOVE;
+		update.rights.neg_rights = update.rights.rights;
+		update.rights.rights = NULL;
+	}
+
+	if (acl_object_update(acl_mailbox_get_aclobj(box), &update) < 0)
+		client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
+	else
+		client_send_tagline(cmd, "OK Setacl complete.");
+	mailbox_close(&box);
+	return TRUE;
+}
+
+static bool cmd_deleteacl(struct client_command_context *cmd)
+{
+	struct mailbox *box;
+	struct acl_rights_update update;
+	const char *mailbox, *identifier;
+
+	if (!client_read_string_args(cmd, 2, &mailbox, &identifier) ||
+	    *identifier == '\0') {
+		client_send_command_error(cmd, "Invalid arguments.");
+		return TRUE;
+	}
+
+	memset(&update, 0, sizeof(update));
+	if (*identifier != '-')
+		update.modify_mode = ACL_MODIFY_MODE_CLEAR;
+	else {
+		update.neg_modify_mode = ACL_MODIFY_MODE_CLEAR;
+		identifier++;
+	}
+
+	if (imap_acl_identifier_parse(identifier, &update.rights) < 0) {
+		client_send_command_error(cmd,
+			t_strdup_printf("Invalid identifier: %s", identifier));
+		return TRUE;
+	}
+
+	box = acl_mailbox_open_as_admin(cmd, mailbox);
+	if (box == NULL)
+		return TRUE;
+
+	if (acl_object_update(acl_mailbox_get_aclobj(box), &update) < 0)
+		client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
+	else
+		client_send_tagline(cmd, "OK Deleteacl complete.");
+	mailbox_close(&box);
+	return TRUE;
+}
+
+void imap_acl_plugin_init(void)
+{
+	if (getenv("ACL") == NULL)
+		return;
+
+	str_append(capability_string, " ACL RIGHTS=texk");
+
+	command_register("LISTRIGHTS", cmd_listrights, 0);
+	command_register("GETACL", cmd_getacl, 0);
+	command_register("MYRIGHTS", cmd_myrights, 0);
+	command_register("SETACL", cmd_setacl, 0);
+	command_register("DELETEACL", cmd_deleteacl, 0);
+}
+
+void imap_acl_plugin_deinit(void)
+{
+	command_unregister("GETACL");
+	command_unregister("MYRIGHTS");
+	command_unregister("SETACL");
+	command_unregister("DELETEACL");
+	command_unregister("LISTRIGHTS");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/imap-acl/imap-acl-plugin.h	Sun Nov 16 04:46:14 2008 +0200
@@ -0,0 +1,7 @@
+#ifndef IMAP_ACL_PLUGIN_H
+#define IMAP_ACL_PLUGIN_H
+
+void imap_acl_plugin_init(void);
+void imap_acl_plugin_deinit(void);
+
+#endif