Based on patch by Bernhard Herzog and Sascha Wilde.
--HG--
branch : HEAD
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
SUBDIRS = \
acl \
+ imap-acl \
autocreate \
convert \
expire \
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 \
#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);
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);
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);
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;
/* @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;
}
}
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)
struct mailbox_list;
struct mail_storage;
+struct mailbox;
struct acl_object;
/* Show mailbox in mailbox list. Allow subscribing to it. */
};
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,
+ ACL_MODIFY_MODE_REMOVE = 0,
+ /* Add rights to existing ACL (or create a new one) */
+ 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 {
/* 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);
#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"
{ '\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;
}
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;
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;
}
}
}
- return acl_rights_alloc(pool, &rights);
+ return acl_rights_alloc(pool, &rights, FALSE);
}
static int
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;
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;
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)
/* 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];
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_update_write(struct acl_object_vfile *aclobj,
+ int fd, const char *path)
+{
+ 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 ATTR_UNUSED,
- const struct acl_rights_update *rights
- ATTR_UNUSED)
+acl_backend_vfile_object_update(struct acl_object *_aclobj,
+ const struct acl_rights_update *update)
{
- /* FIXME */
- return -1;
+ 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);
+ }
+
+ /* 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)
+ if (_aclobj->backend->v.object_refresh_cache(_aclobj) < 0)
iter->failed = TRUE;
return iter;
}
(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;
}
changed = FALSE;
new_mask = change_mask;
break;
+ case ACL_MODIFY_MODE_CLEAR:
+ i_unreached();
}
if (new_mask != old_mask) {
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);
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;
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)
{
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;
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)
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
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;
{
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;
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;
#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
+#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
+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
+/* 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
+#ifndef IMAP_ACL_PLUGIN_H
+#define IMAP_ACL_PLUGIN_H
+
+void imap_acl_plugin_init(void);
+void imap_acl_plugin_deinit(void);
+
+#endif