1
/* Copyright (c) 2008-2009 Dovecot authors, see the included COPYING file */
5
#include "imap-quote.h"
6
#include "imap-resp-code.h"
8
#include "mail-storage.h"
9
#include "mail-namespace.h"
11
#include "acl-storage.h"
12
#include "imap-acl-plugin.h"
16
#define ERROR_NOT_ADMIN "["IMAP_RESP_CODE_NOPERM"] " \
17
"You lack administrator privileges on this mailbox."
19
#define ACL_MAILBOX_OPEN_FLAGS \
20
(MAILBOX_OPEN_READONLY | MAILBOX_OPEN_FAST | MAILBOX_OPEN_KEEP_RECENT)
22
#define IMAP_ACL_ANYONE "anyone"
23
#define IMAP_ACL_AUTHENTICATED "authenticated"
24
#define IMAP_ACL_OWNER "owner"
25
#define IMAP_ACL_GROUP_PREFIX "$"
26
#define IMAP_ACL_GROUP_OVERRIDE_PREFIX "!$"
27
#define IMAP_ACL_GLOBAL_PREFIX "#"
29
struct imap_acl_letter_map {
34
static const struct imap_acl_letter_map imap_acl_letter_map[] = {
35
{ 'l', MAIL_ACL_LOOKUP },
36
{ 'r', MAIL_ACL_READ },
37
{ 'w', MAIL_ACL_WRITE },
38
{ 's', MAIL_ACL_WRITE_SEEN },
39
{ 't', MAIL_ACL_WRITE_DELETED },
40
{ 'i', MAIL_ACL_INSERT },
41
{ 'p', MAIL_ACL_POST },
42
{ 'e', MAIL_ACL_EXPUNGE },
43
{ 'k', MAIL_ACL_CREATE },
44
{ 'x', MAIL_ACL_DELETE },
45
{ 'a', MAIL_ACL_ADMIN },
49
const char *imap_acl_plugin_version = PACKAGE_VERSION;
51
static bool acl_anyone_allow = FALSE;
53
static struct mailbox *
54
acl_mailbox_open_as_admin(struct client_command_context *cmd, const char *name)
56
struct mail_storage *storage;
60
storage = client_find_storage(cmd, &name);
64
/* Force opening the mailbox so that we can give a nicer error message
65
if mailbox isn't selectable but is listable. */
66
box = mailbox_open(&storage, name, NULL, ACL_MAILBOX_OPEN_FLAGS |
67
MAILBOX_OPEN_IGNORE_ACLS);
69
client_send_storage_error(cmd, storage);
73
ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_ADMIN);
77
/* not an administrator. */
78
if (acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP) <= 0) {
79
client_send_tagline(cmd, t_strdup_printf(
80
"NO ["IMAP_RESP_CODE_NONEXISTENT"] "
81
MAIL_ERRSTR_MAILBOX_NOT_FOUND, name));
83
client_send_tagline(cmd, "NO "ERROR_NOT_ADMIN);
89
static const struct imap_acl_letter_map *
90
imap_acl_letter_map_find(const char *name)
94
for (i = 0; imap_acl_letter_map[i].name != NULL; i++) {
95
if (strcmp(imap_acl_letter_map[i].name, name) == 0)
96
return &imap_acl_letter_map[i];
102
imap_acl_write_rights_list(string_t *dest, const char *const *rights)
104
const struct imap_acl_letter_map *map;
105
unsigned int i, orig_len = str_len(dest);
106
bool append_c = FALSE, append_d = FALSE;
108
for (i = 0; rights[i] != NULL; i++) {
109
/* write only letters */
110
map = imap_acl_letter_map_find(rights[i]);
112
str_append_c(dest, map->letter);
113
if (map->letter == 'k' || map->letter == 'x')
115
if (map->letter == 't' || map->letter == 'e')
120
str_append_c(dest, 'c');
122
str_append_c(dest, 'd');
123
if (orig_len == str_len(dest))
124
str_append(dest, "\"\"");
128
imap_acl_write_right(string_t *dest, string_t *tmp,
129
const struct acl_rights *right, bool neg)
131
const char *const *rights = neg ? right->neg_rights : right->rights;
133
str_truncate(tmp, 0);
134
if (neg) str_append_c(tmp,'-');
136
str_append(tmp, IMAP_ACL_GLOBAL_PREFIX);
137
switch (right->id_type) {
139
str_append(tmp, IMAP_ACL_ANYONE);
141
case ACL_ID_AUTHENTICATED:
142
str_append(tmp, IMAP_ACL_AUTHENTICATED);
145
str_append(tmp, IMAP_ACL_OWNER);
148
str_append(tmp, right->identifier);
151
str_append(tmp, IMAP_ACL_GROUP_PREFIX);
152
str_append(tmp, right->identifier);
154
case ACL_ID_GROUP_OVERRIDE:
155
str_append(tmp, IMAP_ACL_GROUP_OVERRIDE_PREFIX);
156
str_append(tmp, right->identifier);
158
case ACL_ID_TYPE_COUNT:
162
imap_quote_append(dest, str_data(tmp), str_len(tmp), FALSE);
163
str_append_c(dest, ' ');
164
imap_acl_write_rights_list(dest, rights);
168
imap_acl_write_aclobj(string_t *dest, struct acl_backend *backend,
169
struct acl_object *aclobj, bool convert_owner,
172
struct acl_object_list_iter *iter;
173
struct acl_rights rights;
175
const char *username;
176
unsigned int orig_len = str_len(dest);
177
bool owner, seen_owner = FALSE, seen_positive_owner = FALSE;
180
username = acl_backend_get_acl_username(backend);
181
if (username == NULL)
182
convert_owner = FALSE;
184
tmp = t_str_new(128);
185
iter = acl_object_list_init(aclobj);
186
while ((ret = acl_object_list_next(iter, &rights)) > 0) {
187
if (rights.id_type == ACL_ID_USER &&
188
acl_backend_user_name_equals(backend, rights.identifier))
190
else if (rights.id_type == ACL_ID_OWNER) {
193
rights.id_type = ACL_ID_USER;
194
rights.identifier = username;
201
if (seen_owner && convert_owner) {
202
/* oops, we have both owner and user=myself.
203
can't do the conversion, so try again. */
204
str_truncate(dest, orig_len);
205
return imap_acl_write_aclobj(dest, backend,
210
if (rights.rights != NULL)
211
seen_positive_owner = TRUE;
214
if (rights.rights != NULL) {
215
str_append_c(dest, ' ');
216
imap_acl_write_right(dest, tmp, &rights, FALSE);
218
if (rights.neg_rights != NULL) {
219
str_append_c(dest, ' ');
220
imap_acl_write_right(dest, tmp, &rights, TRUE);
223
acl_object_list_deinit(&iter);
225
if (!seen_positive_owner && username != NULL && add_default) {
226
/* no positive owner rights returned, write default ACLs */
227
memset(&rights, 0, sizeof(rights));
228
if (!convert_owner) {
229
rights.id_type = ACL_ID_OWNER;
231
rights.id_type = ACL_ID_USER;
232
rights.identifier = username;
234
rights.rights = acl_object_get_default_rights(aclobj);
235
if (rights.rights != NULL) {
236
str_append_c(dest, ' ');
237
imap_acl_write_right(dest, tmp, &rights, FALSE);
243
static bool cmd_getacl(struct client_command_context *cmd)
245
struct acl_backend *backend;
246
struct mail_namespace *ns;
247
struct mail_storage *storage;
253
if (!client_read_string_args(cmd, 1, &mailbox))
256
box = acl_mailbox_open_as_admin(cmd, mailbox);
260
str = t_str_new(128);
261
str_append(str, "* ACL ");
262
imap_quote_append_string(str, mailbox, FALSE);
264
storage = mailbox_get_storage(box);
265
backend = acl_storage_get_backend(storage);
266
ns = mail_storage_get_namespace(storage);
267
ret = imap_acl_write_aclobj(str, backend,
268
acl_mailbox_get_aclobj(box), TRUE,
269
ns->type == NAMESPACE_PRIVATE);
271
client_send_line(cmd->client, str_c(str));
272
client_send_tagline(cmd, "OK Getacl completed.");
274
client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
280
static bool cmd_myrights(struct client_command_context *cmd)
282
struct mail_storage *storage;
284
const char *mailbox, *real_mailbox;
285
const char *const *rights;
288
if (!client_read_string_args(cmd, 1, &mailbox))
291
real_mailbox = mailbox;
292
storage = client_find_storage(cmd, &real_mailbox);
296
box = mailbox_open(&storage, real_mailbox, NULL,
297
ACL_MAILBOX_OPEN_FLAGS | MAILBOX_OPEN_IGNORE_ACLS);
299
client_send_storage_error(cmd, storage);
303
if (acl_object_get_my_rights(acl_mailbox_get_aclobj(box),
304
pool_datastack_create(), &rights) < 0) {
305
client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
309
/* Post right alone doesn't give permissions to see if the mailbox
310
exists or not. Only mail deliveries care about that. */
311
if (*rights == NULL ||
312
(strcmp(*rights, MAIL_ACL_POST) == 0 && rights[1] == NULL)) {
313
client_send_tagline(cmd, t_strdup_printf(
314
"NO ["IMAP_RESP_CODE_NONEXISTENT"] "
315
MAIL_ERRSTR_MAILBOX_NOT_FOUND, real_mailbox));
320
str = t_str_new(128);
321
str_append(str, "* MYRIGHTS ");
322
imap_quote_append_string(str, mailbox, FALSE);
323
str_append_c(str,' ');
324
imap_acl_write_rights_list(str, rights);
326
client_send_line(cmd->client, str_c(str));
327
client_send_tagline(cmd, "OK Myrights completed.");
332
static bool cmd_listrights(struct client_command_context *cmd)
335
const char *mailbox, *identifier;
338
if (!client_read_string_args(cmd, 2, &mailbox, &identifier))
341
box = acl_mailbox_open_as_admin(cmd, mailbox);
345
str = t_str_new(128);
346
str_append(str, "* LISTRIGHTS ");
347
imap_quote_append_string(str, mailbox, FALSE);
348
str_append_c(str, ' ');
349
imap_quote_append_string(str, identifier, FALSE);
350
str_append_c(str, ' ');
351
str_append(str, "\"\" l r w s t p i e k x a c d");
353
client_send_line(cmd->client, str_c(str));
354
client_send_tagline(cmd, "OK Listrights completed.");
360
imap_acl_letters_parse(const char *letters, const char *const **rights_r,
361
const char **error_r)
363
static const char *acl_k = MAIL_ACL_CREATE;
364
static const char *acl_x = MAIL_ACL_DELETE;
365
static const char *acl_e = MAIL_ACL_EXPUNGE;
366
static const char *acl_t = MAIL_ACL_WRITE_DELETED;
367
ARRAY_TYPE(const_string) rights;
370
t_array_init(&rights, 64);
371
for (; *letters != '\0'; letters++) {
372
for (i = 0; imap_acl_letter_map[i].name != NULL; i++) {
373
if (imap_acl_letter_map[i].letter == *letters) {
374
array_append(&rights,
375
&imap_acl_letter_map[i].name, 1);
379
if (imap_acl_letter_map[i].name == NULL) {
380
/* Handling of obsolete rights as virtual
381
rights according to RFC 4314 */
384
array_append(&rights, &acl_k, 1);
385
array_append(&rights, &acl_x, 1);
388
array_append(&rights, &acl_e, 1);
389
array_append(&rights, &acl_t, 1);
392
*error_r = t_strdup_printf(
393
"Invalid ACL right: %c", *letters);
398
(void)array_append_space(&rights);
399
*rights_r = array_idx(&rights, 0);
404
imap_acl_identifier_parse(const char *id, struct acl_rights *rights,
405
bool check_anyone, const char **error_r)
407
if (strncmp(id, IMAP_ACL_GLOBAL_PREFIX,
408
strlen(IMAP_ACL_GLOBAL_PREFIX)) == 0) {
409
*error_r = t_strdup_printf("Global ACLs can't be modified: %s",
414
if (strcmp(id, IMAP_ACL_ANYONE) == 0) {
415
if (!acl_anyone_allow && check_anyone) {
416
*error_r = "'anyone' identifier is disallowed";
419
rights->id_type = ACL_ID_ANYONE;
420
} else if (strcmp(id, IMAP_ACL_AUTHENTICATED) == 0) {
421
if (!acl_anyone_allow && check_anyone) {
422
*error_r = "'authenticated' identifier is disallowed";
425
rights->id_type = ACL_ID_AUTHENTICATED;
426
} else if (strcmp(id, IMAP_ACL_OWNER) == 0)
427
rights->id_type = ACL_ID_OWNER;
428
else if (strncmp(id, IMAP_ACL_GROUP_PREFIX,
429
strlen(IMAP_ACL_GROUP_PREFIX)) == 0) {
430
rights->id_type = ACL_ID_GROUP;
431
rights->identifier = id + strlen(IMAP_ACL_GROUP_PREFIX);
432
} else if (strncmp(id, IMAP_ACL_GROUP_OVERRIDE_PREFIX,
433
strlen(IMAP_ACL_GROUP_OVERRIDE_PREFIX)) == 0) {
434
rights->id_type = ACL_ID_GROUP_OVERRIDE;
435
rights->identifier = id +
436
strlen(IMAP_ACL_GROUP_OVERRIDE_PREFIX);
438
rights->id_type = ACL_ID_USER;
439
rights->identifier = id;
444
static void imap_acl_update_ensure_keep_admins(struct acl_rights_update *update)
446
static const char *acl_admin = MAIL_ACL_ADMIN;
447
const char *const *rights = update->rights.rights;
448
ARRAY_TYPE(const_string) new_rights;
451
t_array_init(&new_rights, 64);
452
for (i = 0; rights[i] != NULL; i++) {
453
if (strcmp(rights[i], MAIL_ACL_ADMIN) == 0)
455
array_append(&new_rights, &rights[i], 1);
458
switch (update->modify_mode) {
459
case ACL_MODIFY_MODE_REMOVE:
460
if (rights[i] == NULL)
463
/* skip over the ADMIN removal and add the rest */
464
for (i++; rights[i] != NULL; i++)
465
array_append(&new_rights, &rights[i], 1);
467
case ACL_MODIFY_MODE_REPLACE:
468
if (rights[i] != NULL)
471
/* add the missing ADMIN right */
472
array_append(&new_rights, &acl_admin, 1);
477
(void)array_append_space(&new_rights);
478
update->rights.rights = array_idx(&new_rights, 0);
481
static bool cmd_setacl(struct client_command_context *cmd)
483
struct mail_namespace *ns;
484
struct mail_storage *storage;
486
struct acl_backend *backend;
487
struct acl_rights_update update;
488
struct acl_rights *r;
489
const char *mailbox, *identifier, *rights, *error;
490
bool negative = FALSE;
492
if (!client_read_string_args(cmd, 3, &mailbox, &identifier, &rights))
495
if (*identifier == '\0') {
496
client_send_command_error(cmd, "Invalid arguments.");
500
memset(&update, 0, sizeof(update));
501
if (*identifier == '-') {
508
update.modify_mode = ACL_MODIFY_MODE_REMOVE;
512
update.modify_mode = ACL_MODIFY_MODE_ADD;
516
update.modify_mode = ACL_MODIFY_MODE_REPLACE;
520
if (imap_acl_identifier_parse(identifier, &update.rights,
522
client_send_command_error(cmd, error);
525
if (imap_acl_letters_parse(rights, &update.rights.rights, &error) < 0) {
526
client_send_command_error(cmd, error);
531
box = acl_mailbox_open_as_admin(cmd, mailbox);
535
storage = mailbox_get_storage(box);
536
backend = acl_storage_get_backend(storage);
537
ns = mail_storage_get_namespace(storage);
538
if (ns->type == NAMESPACE_PUBLIC && r->id_type == ACL_ID_OWNER) {
539
client_send_tagline(cmd, "NO Public namespaces have no owner");
545
update.neg_modify_mode = update.modify_mode;
546
update.modify_mode = ACL_MODIFY_MODE_REMOVE;
547
update.rights.neg_rights = update.rights.rights;
548
update.rights.rights = NULL;
549
} else if (ns->type == NAMESPACE_PRIVATE && r->rights != NULL &&
550
((r->id_type == ACL_ID_USER &&
551
acl_backend_user_name_equals(backend, r->identifier)) ||
552
(r->id_type == ACL_ID_OWNER &&
553
strcmp(acl_backend_get_acl_username(backend),
554
ns->user->username) == 0))) {
555
/* make sure client doesn't (accidentally) remove admin
556
privileges from its own mailboxes */
557
imap_acl_update_ensure_keep_admins(&update);
560
if (acl_object_update(acl_mailbox_get_aclobj(box), &update) < 0)
561
client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
563
client_send_tagline(cmd, "OK Setacl complete.");
568
static bool cmd_deleteacl(struct client_command_context *cmd)
571
struct acl_rights_update update;
572
const char *mailbox, *identifier, *error;
574
if (!client_read_string_args(cmd, 2, &mailbox, &identifier))
576
if (*identifier == '\0') {
577
client_send_command_error(cmd, "Invalid arguments.");
581
memset(&update, 0, sizeof(update));
582
if (*identifier != '-')
583
update.modify_mode = ACL_MODIFY_MODE_CLEAR;
585
update.neg_modify_mode = ACL_MODIFY_MODE_CLEAR;
589
if (imap_acl_identifier_parse(identifier, &update.rights,
590
FALSE, &error) < 0) {
591
client_send_command_error(cmd, error);
595
box = acl_mailbox_open_as_admin(cmd, mailbox);
599
if (acl_object_update(acl_mailbox_get_aclobj(box), &update) < 0)
600
client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
602
client_send_tagline(cmd, "OK Deleteacl complete.");
607
void imap_acl_plugin_init(void)
611
if (getenv("ACL") == NULL)
614
env = getenv("ACL_ANYONE");
616
acl_anyone_allow = strcmp(env, "allow") == 0;
618
str_append(capability_string, " ACL RIGHTS=texk");
620
command_register("LISTRIGHTS", cmd_listrights, 0);
621
command_register("GETACL", cmd_getacl, 0);
622
command_register("MYRIGHTS", cmd_myrights, 0);
623
command_register("SETACL", cmd_setacl, 0);
624
command_register("DELETEACL", cmd_deleteacl, 0);
627
void imap_acl_plugin_deinit(void)
629
if (getenv("ACL") == NULL)
632
command_unregister("GETACL");
633
command_unregister("MYRIGHTS");
634
command_unregister("SETACL");
635
command_unregister("DELETEACL");
636
command_unregister("LISTRIGHTS");