imap-acl-plugin.c revision c1faff067b29fb48426cb84260adba563e93189a
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen/* Copyright (c) 2008-2012 Dovecot authors, see the included COPYING file */
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen#include "imap-common.h"
767431e5084a037c4dbefdf30ebfa03c84b1f449Timo Sirainen#include "str.h"
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen#include "imap-quote.h"
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include "imap-resp-code.h"
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen#include "imap-commands.h"
a8fe899601735459641edae975c0fa08be8482e2Timo Sirainen#include "mail-storage.h"
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen#include "mail-namespace.h"
5fb3f13537dffd15a31e997da133a721c0728af8Timo Sirainen#include "acl-api.h"
97437f768d1a3e6134fed1971202803fd250eef2Timo Sirainen#include "acl-storage.h"
bb25bed75eefd011138ebf1b8e033fc8ef55ca74Timo Sirainen#include "acl-plugin.h"
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen#include "imap-acl-plugin.h"
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainen#include <stdlib.h>
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen#define ERROR_NOT_ADMIN "["IMAP_RESP_CODE_NOPERM"] " \
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen "You lack administrator privileges on this mailbox."
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen#define IMAP_ACL_ANYONE "anyone"
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen#define IMAP_ACL_AUTHENTICATED "authenticated"
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen#define IMAP_ACL_OWNER "owner"
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen#define IMAP_ACL_GROUP_PREFIX "$"
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen#define IMAP_ACL_GROUP_OVERRIDE_PREFIX "!$"
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen#define IMAP_ACL_GLOBAL_PREFIX "#"
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainenstruct imap_acl_letter_map {
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen char letter;
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen const char *name;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen};
23878bd03d1de531e3261a25598beec621351910Timo Sirainen
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainenstatic const struct imap_acl_letter_map imap_acl_letter_map[] = {
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen { 'l', MAIL_ACL_LOOKUP },
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen { 'r', MAIL_ACL_READ },
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen { 'w', MAIL_ACL_WRITE },
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen { 's', MAIL_ACL_WRITE_SEEN },
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen { 't', MAIL_ACL_WRITE_DELETED },
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen { 'i', MAIL_ACL_INSERT },
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen { 'p', MAIL_ACL_POST },
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen { 'e', MAIL_ACL_EXPUNGE },
62d0db14d2c5008758983c28d242ec158baabf9eTimo Sirainen { 'k', MAIL_ACL_CREATE },
62d0db14d2c5008758983c28d242ec158baabf9eTimo Sirainen { 'x', MAIL_ACL_DELETE },
62d0db14d2c5008758983c28d242ec158baabf9eTimo Sirainen { 'a', MAIL_ACL_ADMIN },
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen { '\0', NULL }
42681892b206d13cb87a5f526d2bf4ff3f2f4af7Timo Sirainen};
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainenconst char *imap_acl_plugin_version = DOVECOT_VERSION;
98e8f95ffee4eacca72b1bcf082f2c735592301bTimo Sirainen
98e8f95ffee4eacca72b1bcf082f2c735592301bTimo Sirainenstatic struct module *imap_acl_module;
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainenstatic imap_client_created_func_t *next_hook_client_created;
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainen
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainenstatic struct mailbox *
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainenacl_mailbox_open_as_admin(struct client_command_context *cmd, const char *name)
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen{
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen struct mail_namespace *ns;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen struct mailbox *box;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen enum mailbox_existence existence = MAILBOX_EXISTENCE_NONE;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen int ret;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen if (ACL_USER_CONTEXT(cmd->client->user) == NULL) {
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen client_send_command_error(cmd, "ACLs disabled.");
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen return NULL;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen }
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen ns = client_find_namespace(cmd, &name);
10b8040903b1d1591f1d44552ff466c8789b8814Timo Sirainen if (ns == NULL)
10b8040903b1d1591f1d44552ff466c8789b8814Timo Sirainen return NULL;
0a9cb42cbb135e3200cbfbb657820304cca8ecb8Timo Sirainen
0a9cb42cbb135e3200cbfbb657820304cca8ecb8Timo Sirainen /* Force opening the mailbox so that we can give a nicer error message
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if mailbox isn't selectable but is listable. */
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen box = mailbox_alloc(ns->list, name, MAILBOX_FLAG_READONLY |
72bc08129fb0aaec8144cc183a998ccc426fef9eTimo Sirainen MAILBOX_FLAG_IGNORE_ACLS);
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen if (mailbox_exists(box, TRUE, &existence) == 0 &&
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen existence == MAILBOX_EXISTENCE_SELECT) {
cdfdb67422891a44fc7d9ace6bc1a00185fd3528Timo Sirainen ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_ADMIN);
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen if (ret > 0)
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen return box;
f7141101e27d766b695ef27726f755117332a58eTimo Sirainen }
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen
1d082a46e1676e7ec13928d588c4a25e062713ccTimo Sirainen /* mailbox doesn't exist / not an administrator. */
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen if (existence != MAILBOX_EXISTENCE_SELECT ||
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP) <= 0) {
7358272563d8ef77366447708ab0e58c0cff4151Timo Sirainen client_send_tagline(cmd, t_strdup_printf(
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen "NO ["IMAP_RESP_CODE_NONEXISTENT"] "
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen MAIL_ERRSTR_MAILBOX_NOT_FOUND, name));
32c779d5d0b3dabc697408e6b5d9d2e652180b33Timo Sirainen } else {
32c779d5d0b3dabc697408e6b5d9d2e652180b33Timo Sirainen client_send_tagline(cmd, "NO "ERROR_NOT_ADMIN);
32c779d5d0b3dabc697408e6b5d9d2e652180b33Timo Sirainen }
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen mailbox_free(&box);
7358272563d8ef77366447708ab0e58c0cff4151Timo Sirainen return NULL;
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen}
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainenstatic const struct imap_acl_letter_map *
a8281b7c770f4a9a842b19303083fc7f6859e756Timo Sirainenimap_acl_letter_map_find(const char *name)
a8281b7c770f4a9a842b19303083fc7f6859e756Timo Sirainen{
29f138b4b9bc037b21dfaa6b8e458943a99d5db2Timo Sirainen unsigned int i;
29f138b4b9bc037b21dfaa6b8e458943a99d5db2Timo Sirainen
29f138b4b9bc037b21dfaa6b8e458943a99d5db2Timo Sirainen for (i = 0; imap_acl_letter_map[i].name != NULL; i++) {
7358272563d8ef77366447708ab0e58c0cff4151Timo Sirainen if (strcmp(imap_acl_letter_map[i].name, name) == 0)
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen return &imap_acl_letter_map[i];
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen }
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen return NULL;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen}
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainenstatic void
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainenimap_acl_write_rights_list(string_t *dest, const char *const *rights)
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen{
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen const struct imap_acl_letter_map *map;
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen unsigned int i, orig_len = str_len(dest);
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen bool append_c = FALSE, append_d = FALSE;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen
ad58b50aef8125981ebdbc89513236558bcccf60Timo Sirainen for (i = 0; rights[i] != NULL; i++) {
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen /* write only letters */
344bb4abc3acb63d04131cb63f1503a6ca01fb40Timo Sirainen map = imap_acl_letter_map_find(rights[i]);
eff34528733a7893b2914a26023aac227ef4ae7fTimo Sirainen if (map != NULL) {
344bb4abc3acb63d04131cb63f1503a6ca01fb40Timo Sirainen str_append_c(dest, map->letter);
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen if (map->letter == 'k' || map->letter == 'x')
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen append_c = TRUE;
0779e926687b319fe1bcc0f1010ba7f88023e789Timo Sirainen if (map->letter == 't' || map->letter == 'e')
0f9a8663b0ff6fe30389d02284a2b002c40914ebTimo Sirainen append_d = TRUE;
bd417d416988d11a6b555b9aa57779e7ed976951Timo Sirainen }
a9efdb661eb7a8a33aacfdcc3486dcc675a21543Timo Sirainen }
a9efdb661eb7a8a33aacfdcc3486dcc675a21543Timo Sirainen if (append_c)
fab850a6aee4aaef4f4795bd7946807a3ba45041Timo Sirainen str_append_c(dest, 'c');
bd417d416988d11a6b555b9aa57779e7ed976951Timo Sirainen if (append_d)
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen str_append_c(dest, 'd');
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen if (orig_len == str_len(dest))
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen str_append(dest, "\"\"");
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen}
5685e60e62a8e0d368bd28a1526056f97bbba022Timo Sirainen
5685e60e62a8e0d368bd28a1526056f97bbba022Timo Sirainenstatic void
5685e60e62a8e0d368bd28a1526056f97bbba022Timo Sirainenimap_acl_write_right(string_t *dest, string_t *tmp,
0779e926687b319fe1bcc0f1010ba7f88023e789Timo Sirainen const struct acl_rights *right, bool neg)
72bc08129fb0aaec8144cc183a998ccc426fef9eTimo Sirainen{
72bc08129fb0aaec8144cc183a998ccc426fef9eTimo Sirainen const char *const *rights = neg ? right->neg_rights : right->rights;
c14c5561e85853d91280235a7611b6050feaebb2Timo Sirainen
c14c5561e85853d91280235a7611b6050feaebb2Timo Sirainen str_truncate(tmp, 0);
c14c5561e85853d91280235a7611b6050feaebb2Timo Sirainen if (neg) str_append_c(tmp,'-');
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen if (right->global)
72bc08129fb0aaec8144cc183a998ccc426fef9eTimo Sirainen str_append(tmp, IMAP_ACL_GLOBAL_PREFIX);
2cc88ff507e244faa63683f804833b321a62c665Timo Sirainen switch (right->id_type) {
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen case ACL_ID_ANYONE:
0779e926687b319fe1bcc0f1010ba7f88023e789Timo Sirainen str_append(tmp, IMAP_ACL_ANYONE);
32c779d5d0b3dabc697408e6b5d9d2e652180b33Timo Sirainen break;
32c779d5d0b3dabc697408e6b5d9d2e652180b33Timo Sirainen case ACL_ID_AUTHENTICATED:
32c779d5d0b3dabc697408e6b5d9d2e652180b33Timo Sirainen str_append(tmp, IMAP_ACL_AUTHENTICATED);
32c779d5d0b3dabc697408e6b5d9d2e652180b33Timo Sirainen break;
51327f2489a4e0e615eb9f7d921473cf8512bb79Timo Sirainen case ACL_ID_OWNER:
51327f2489a4e0e615eb9f7d921473cf8512bb79Timo Sirainen str_append(tmp, IMAP_ACL_OWNER);
97afa073e3e1e0301dc41173ec34beb08edcce50Timo Sirainen break;
97afa073e3e1e0301dc41173ec34beb08edcce50Timo Sirainen case ACL_ID_USER:
636f017be100bce67d66fd3ae1544a47681efd33Timo Sirainen str_append(tmp, right->identifier);
b8b085f7bc6f1c0367802a9f00062bbbd981690dTimo Sirainen break;
b8b085f7bc6f1c0367802a9f00062bbbd981690dTimo Sirainen case ACL_ID_GROUP:
94ba4820927b906b333e39445c1508a29387c3aaTimo Sirainen str_append(tmp, IMAP_ACL_GROUP_PREFIX);
b932ee7fbbec6e79b777dcc7ba613b9e99f8337bTimo Sirainen str_append(tmp, right->identifier);
b932ee7fbbec6e79b777dcc7ba613b9e99f8337bTimo Sirainen break;
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainen case ACL_ID_GROUP_OVERRIDE:
23878bd03d1de531e3261a25598beec621351910Timo Sirainen str_append(tmp, IMAP_ACL_GROUP_OVERRIDE_PREFIX);
23878bd03d1de531e3261a25598beec621351910Timo Sirainen str_append(tmp, right->identifier);
23878bd03d1de531e3261a25598beec621351910Timo Sirainen break;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen case ACL_ID_TYPE_COUNT:
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen i_unreached();
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen }
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen imap_quote_append(dest, str_data(tmp), str_len(tmp), FALSE);
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen str_append_c(dest, ' ');
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen imap_acl_write_rights_list(dest, rights);
bb25bed75eefd011138ebf1b8e033fc8ef55ca74Timo Sirainen}
bb25bed75eefd011138ebf1b8e033fc8ef55ca74Timo Sirainen
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainenstatic bool
2a15ce3abe14099b94535f6dfc2d4ee023a7c455Timo Sirainenacl_rights_is_owner(struct acl_backend *backend,
2a15ce3abe14099b94535f6dfc2d4ee023a7c455Timo Sirainen const struct acl_rights *rights)
2a15ce3abe14099b94535f6dfc2d4ee023a7c455Timo Sirainen{
2a15ce3abe14099b94535f6dfc2d4ee023a7c455Timo Sirainen switch (rights->id_type) {
eed1ec3ac96fddb8d9e4fa2af6e760ee42801fb8Timo Sirainen case ACL_ID_OWNER:
94ba4820927b906b333e39445c1508a29387c3aaTimo Sirainen return TRUE;
0368f3b0ae3fc1ea892da5c5ec02c05c0c3989afAki Tuomi case ACL_ID_USER:
0368f3b0ae3fc1ea892da5c5ec02c05c0c3989afAki Tuomi return acl_backend_user_name_equals(backend,
0368f3b0ae3fc1ea892da5c5ec02c05c0c3989afAki Tuomi rights->identifier);
0368f3b0ae3fc1ea892da5c5ec02c05c0c3989afAki Tuomi default:
0368f3b0ae3fc1ea892da5c5ec02c05c0c3989afAki Tuomi return FALSE;
0368f3b0ae3fc1ea892da5c5ec02c05c0c3989afAki Tuomi }
0368f3b0ae3fc1ea892da5c5ec02c05c0c3989afAki Tuomi}
0368f3b0ae3fc1ea892da5c5ec02c05c0c3989afAki Tuomi
0368f3b0ae3fc1ea892da5c5ec02c05c0c3989afAki Tuomistatic bool have_positive_owner_rights(struct acl_backend *backend,
0368f3b0ae3fc1ea892da5c5ec02c05c0c3989afAki Tuomi struct acl_object *aclobj)
0368f3b0ae3fc1ea892da5c5ec02c05c0c3989afAki Tuomi{
89d31290dab6e4bde08b8a118121f008154772e9Aki Tuomi struct acl_object_list_iter *iter;
5ba6009f4e5493c4e6be9ffb3134525004a7975cAki Tuomi struct acl_rights rights;
71f4549303dc1691382748a096c2ada9d2a1a9feAki Tuomi bool ret = FALSE;
844929a7bd6e9d21f0a8cdb3a19f4620a17cdecaAki Tuomi
e1d08b1c39c63de92f0e914064a508bbf6c6fcc5Aki Tuomi iter = acl_object_list_init(aclobj);
977f08d645b1779527c0938bbb848b61064839c3Aki Tuomi while ((ret = acl_object_list_next(iter, &rights)) > 0) {
a893aaa999856b1ba6e4541890016767aaa283c7Aki Tuomi if (acl_rights_is_owner(backend, &rights)) {
a893aaa999856b1ba6e4541890016767aaa283c7Aki Tuomi if (rights.rights != NULL) {
a893aaa999856b1ba6e4541890016767aaa283c7Aki Tuomi ret = TRUE;
ae8c89c81de5d867bd1359fb9c438dd8771210c7Aki Tuomi break;
0368f3b0ae3fc1ea892da5c5ec02c05c0c3989afAki Tuomi }
14af7be4aa26d55c341cd6efe32bb2add2c39830Aki Tuomi }
14102a0c5db8828ca8c7751ec96587fadc97a0bcTimo Sirainen }
14102a0c5db8828ca8c7751ec96587fadc97a0bcTimo Sirainen acl_object_list_deinit(&iter);
14102a0c5db8828ca8c7751ec96587fadc97a0bcTimo Sirainen return ret;
14102a0c5db8828ca8c7751ec96587fadc97a0bcTimo Sirainen}
14af7be4aa26d55c341cd6efe32bb2add2c39830Aki Tuomi
c45a841bee3f42ec6524b8f62c3fd457115c3f97Timo Sirainenstatic int
c45a841bee3f42ec6524b8f62c3fd457115c3f97Timo Sirainenimap_acl_write_aclobj(string_t *dest, struct acl_backend *backend,
c45a841bee3f42ec6524b8f62c3fd457115c3f97Timo Sirainen struct acl_object *aclobj, bool convert_owner,
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen bool add_default)
{
struct acl_object_list_iter *iter;
struct acl_rights rights;
string_t *tmp;
const char *username;
unsigned int orig_len = str_len(dest);
bool seen_owner = FALSE, seen_positive_owner = FALSE;
int ret;
username = acl_backend_get_acl_username(backend);
if (username == NULL)
convert_owner = FALSE;
tmp = t_str_new(128);
iter = acl_object_list_init(aclobj);
while ((ret = acl_object_list_next(iter, &rights)) > 0) {
if (acl_rights_is_owner(backend, &rights)) {
if (rights.id_type == ACL_ID_OWNER && convert_owner) {
rights.id_type = ACL_ID_USER;
rights.identifier = username;
}
if (seen_owner && convert_owner) {
/* oops, we have both owner and user=myself.
can't do the conversion, so try again. */
str_truncate(dest, orig_len);
return imap_acl_write_aclobj(dest, backend,
aclobj, FALSE,
add_default);
}
seen_owner = TRUE;
if (rights.rights != NULL)
seen_positive_owner = TRUE;
}
if (rights.rights != NULL) {
str_append_c(dest, ' ');
imap_acl_write_right(dest, tmp, &rights, FALSE);
}
if (rights.neg_rights != NULL) {
str_append_c(dest, ' ');
imap_acl_write_right(dest, tmp, &rights, TRUE);
}
}
acl_object_list_deinit(&iter);
if (!seen_positive_owner && username != NULL && add_default) {
/* no positive owner rights returned, write default ACLs */
memset(&rights, 0, sizeof(rights));
if (!convert_owner) {
rights.id_type = ACL_ID_OWNER;
} else {
rights.id_type = ACL_ID_USER;
rights.identifier = username;
}
rights.rights = acl_object_get_default_rights(aclobj);
if (rights.rights != NULL) {
str_append_c(dest, ' ');
imap_acl_write_right(dest, tmp, &rights, FALSE);
}
}
return ret;
}
static bool cmd_getacl(struct client_command_context *cmd)
{
struct acl_backend *backend;
struct mail_namespace *ns;
struct mailbox *box;
const char *mailbox;
string_t *str;
int ret;
if (!client_read_string_args(cmd, 1, &mailbox))
return FALSE;
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);
ns = mailbox_get_namespace(box);
backend = acl_mailbox_list_get_backend(ns->list);
ret = imap_acl_write_aclobj(str, backend,
acl_mailbox_get_aclobj(box), TRUE,
ns->type == NAMESPACE_PRIVATE);
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_free(&box);
return TRUE;
}
static bool cmd_myrights(struct client_command_context *cmd)
{
struct mail_namespace *ns;
struct mailbox *box;
const char *mailbox;
const char *const *rights;
string_t *str;
if (!client_read_string_args(cmd, 1, &mailbox))
return FALSE;
if (ACL_USER_CONTEXT(cmd->client->user) == NULL) {
client_send_command_error(cmd, "ACLs disabled.");
return TRUE;
}
ns = client_find_namespace(cmd, &mailbox);
if (ns == NULL)
return TRUE;
box = mailbox_alloc(ns->list, mailbox,
MAILBOX_FLAG_READONLY | MAILBOX_FLAG_IGNORE_ACLS);
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_free(&box);
return TRUE;
}
/* Post right alone doesn't give permissions to see if the mailbox
exists or not. Only mail deliveries care about that. */
if (*rights == NULL ||
(strcmp(*rights, MAIL_ACL_POST) == 0 && rights[1] == NULL)) {
client_send_tagline(cmd, t_strdup_printf(
"NO ["IMAP_RESP_CODE_NONEXISTENT"] "
MAIL_ERRSTR_MAILBOX_NOT_FOUND, mailbox));
mailbox_free(&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.");
mailbox_free(&box);
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))
return FALSE;
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.");
mailbox_free(&box);
return TRUE;
}
static int
imap_acl_letters_parse(const char *letters, const char *const **rights_r,
const char **error_r)
{
static const char *acl_k = MAIL_ACL_CREATE;
static const char *acl_x = MAIL_ACL_DELETE;
static const char *acl_e = MAIL_ACL_EXPUNGE;
static const char *acl_t = MAIL_ACL_WRITE_DELETED;
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) {
/* Handling of obsolete rights as virtual
rights according to RFC 4314 */
switch (*letters) {
case 'c':
array_append(&rights, &acl_k, 1);
array_append(&rights, &acl_x, 1);
break;
case 'd':
array_append(&rights, &acl_e, 1);
array_append(&rights, &acl_t, 1);
break;
default:
*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 bool acl_anyone_allow(struct mail_user *user)
{
const char *env;
env = mail_user_plugin_getenv(user, "acl_anyone");
return env != NULL && strcmp(env, "allow") == 0;
}
static int
imap_acl_identifier_parse(struct client_command_context *cmd,
const char *id, struct acl_rights *rights,
bool check_anyone, const char **error_r)
{
struct mail_user *user = cmd->client->user;
if (strncmp(id, IMAP_ACL_GLOBAL_PREFIX,
strlen(IMAP_ACL_GLOBAL_PREFIX)) == 0) {
*error_r = t_strdup_printf("Global ACLs can't be modified: %s",
id);
return -1;
}
if (strcmp(id, IMAP_ACL_ANYONE) == 0) {
if (check_anyone && !acl_anyone_allow(user)) {
*error_r = "'anyone' identifier is disallowed";
return -1;
}
rights->id_type = ACL_ID_ANYONE;
} else if (strcmp(id, IMAP_ACL_AUTHENTICATED) == 0) {
if (check_anyone && !acl_anyone_allow(user)) {
*error_r = "'authenticated' identifier is disallowed";
return -1;
}
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 void imap_acl_update_ensure_keep_admins(struct acl_backend *backend,
struct acl_object *aclobj,
struct acl_rights_update *update)
{
static const char *acl_admin = MAIL_ACL_ADMIN;
const char *const *rights = update->rights.rights;
const char *const *default_rights;
ARRAY_TYPE(const_string) new_rights;
unsigned int i;
t_array_init(&new_rights, 64);
for (i = 0; rights[i] != NULL; i++) {
if (strcmp(rights[i], MAIL_ACL_ADMIN) == 0)
break;
array_append(&new_rights, &rights[i], 1);
}
switch (update->modify_mode) {
case ACL_MODIFY_MODE_ADD:
if (have_positive_owner_rights(backend, aclobj))
return;
/* adding initial rights for a user. we need to add
the defaults also. don't worry about duplicates. */
for (; rights[i] != NULL; i++)
array_append(&new_rights, &rights[i], 1);
default_rights = acl_object_get_default_rights(aclobj);
for (i = 0; default_rights[i] != NULL; i++)
array_append(&new_rights, &default_rights[i], 1);
break;
case ACL_MODIFY_MODE_REMOVE:
if (rights[i] == NULL)
return;
/* skip over the ADMIN removal and add the rest */
for (i++; rights[i] != NULL; i++)
array_append(&new_rights, &rights[i], 1);
break;
case ACL_MODIFY_MODE_REPLACE:
if (rights[i] != NULL)
return;
/* add the missing ADMIN right */
array_append(&new_rights, &acl_admin, 1);
break;
default:
return;
}
(void)array_append_space(&new_rights);
update->rights.rights = array_idx(&new_rights, 0);
}
static bool cmd_setacl(struct client_command_context *cmd)
{
struct mail_namespace *ns;
struct mailbox *box;
struct acl_backend *backend;
struct acl_object *aclobj;
struct acl_rights_update update;
struct acl_rights *r;
const char *mailbox, *identifier, *rights, *error;
bool negative = FALSE;
if (!client_read_string_args(cmd, 3, &mailbox, &identifier, &rights))
return FALSE;
if (*identifier == '\0') {
client_send_command_error(cmd, "Invalid arguments.");
return TRUE;
}
memset(&update, 0, sizeof(update));
if (*identifier == '-') {
negative = TRUE;
identifier++;
}
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 (imap_acl_identifier_parse(cmd, identifier, &update.rights,
TRUE, &error) < 0) {
client_send_command_error(cmd, error);
return TRUE;
}
if (imap_acl_letters_parse(rights, &update.rights.rights, &error) < 0) {
client_send_command_error(cmd, error);
return TRUE;
}
r = &update.rights;
box = acl_mailbox_open_as_admin(cmd, mailbox);
if (box == NULL)
return TRUE;
ns = mailbox_get_namespace(box);
backend = acl_mailbox_list_get_backend(ns->list);
if (ns->type == NAMESPACE_PUBLIC && r->id_type == ACL_ID_OWNER) {
client_send_tagline(cmd, "NO Public namespaces have no owner");
mailbox_free(&box);
return TRUE;
}
aclobj = acl_mailbox_get_aclobj(box);
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;
} else if (ns->type == NAMESPACE_PRIVATE && r->rights != NULL &&
((r->id_type == ACL_ID_USER &&
acl_backend_user_name_equals(backend, r->identifier)) ||
(r->id_type == ACL_ID_OWNER &&
strcmp(acl_backend_get_acl_username(backend),
ns->user->username) == 0))) {
/* make sure client doesn't (accidentally) remove admin
privileges from its own mailboxes */
imap_acl_update_ensure_keep_admins(backend, aclobj, &update);
}
if (acl_object_update(aclobj, &update) < 0)
client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
else
client_send_tagline(cmd, "OK Setacl complete.");
mailbox_free(&box);
return TRUE;
}
static bool cmd_deleteacl(struct client_command_context *cmd)
{
struct mailbox *box;
struct acl_rights_update update;
const char *mailbox, *identifier, *error;
if (!client_read_string_args(cmd, 2, &mailbox, &identifier))
return FALSE;
if (*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(cmd, identifier, &update.rights,
FALSE, &error) < 0) {
client_send_command_error(cmd, error);
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_free(&box);
return TRUE;
}
static void imap_acl_client_created(struct client **client)
{
if (mail_user_is_plugin_loaded((*client)->user, imap_acl_module))
str_append((*client)->capability_string, " ACL RIGHTS=texk");
if (next_hook_client_created != NULL)
next_hook_client_created(client);
}
void imap_acl_plugin_init(struct module *module)
{
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);
imap_acl_module = module;
next_hook_client_created =
imap_client_created_hook_set(imap_acl_client_created);
}
void imap_acl_plugin_deinit(void)
{
command_unregister("GETACL");
command_unregister("MYRIGHTS");
command_unregister("SETACL");
command_unregister("DELETEACL");
command_unregister("LISTRIGHTS");
imap_client_created_hook_set(next_hook_client_created);
}
const char *imap_acl_plugin_dependencies[] = { "acl", NULL };
const char imap_acl_plugin_binary_dependency[] = "imap";