summaryrefslogtreecommitdiffstats
path: root/src/plugins/acl/acl-attributes.c
blob: 515ff422616fba3b40420fa0b003a469150b5bdc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */

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

struct acl_mailbox_attribute_iter {
	struct mailbox_attribute_iter iter;
	struct mailbox_attribute_iter *super;

	struct acl_object_list_iter *acl_iter;
	string_t *acl_name;

	bool failed;
};

static int
acl_attribute_update_acl(struct mailbox_transaction_context *t, const char *key,
			 const struct mail_attribute_value *value)
{
	const char *value_str, *id, *const *rights, *error;
	struct acl_rights_update update;

	/* for now allow only dsync to update ACLs this way.
	   if this check is removed, it should be replaced by a setting, since
	   some admins may still have configured Dovecot using dovecot-acl
	   files directly that they don't want users to update. and in any case
	   ACL_STORAGE_RIGHT_ADMIN must be checked then. */
	if (!t->box->storage->user->dsyncing) {
		mail_storage_set_error(t->box->storage, MAIL_ERROR_PERM,
				       MAIL_ERRSTR_NO_PERMISSION);
		return -1;
	}

	if (mailbox_attribute_value_to_string(t->box->storage, value,
					      &value_str) < 0)
		return -1;

	i_zero(&update);
	update.modify_mode = ACL_MODIFY_MODE_REPLACE;
	update.neg_modify_mode = ACL_MODIFY_MODE_REPLACE;
	update.last_change = value->last_change;
	id = key + strlen(MAILBOX_ATTRIBUTE_PREFIX_ACL);
	rights = value_str == NULL ? NULL : t_strsplit(value_str, " ");
	if (acl_rights_update_import(&update, id, rights, &error) < 0) {
		mail_storage_set_error(t->box->storage, MAIL_ERROR_PARAMS, error);
		return -1;
	}
	/* FIXME: this should actually be done only at commit().. */
	return acl_mailbox_update_acl(t, &update);
}

static int acl_attribute_get_acl(struct mailbox *box, const char *key,
				 struct mail_attribute_value *value_r)
{
	struct acl_object *aclobj = acl_mailbox_get_aclobj(box);
	struct acl_object_list_iter *iter;
	struct acl_rights rights, wanted_rights;
	const char *id;
	int ret = 0;

	i_zero(value_r);

	if (!box->storage->user->dsyncing) {
		mail_storage_set_error(box->storage, MAIL_ERROR_PERM,
				       MAIL_ERRSTR_NO_PERMISSION);
		return -1;
	}
	/* set last_change for all ACL objects, even if they don't exist
	   (because they could have been removed by the last change, and dsync
	   can use this information) */
	(void)acl_object_last_changed(aclobj, &value_r->last_change);

	i_zero(&wanted_rights);
	id = key + strlen(MAILBOX_ATTRIBUTE_PREFIX_ACL);
	if (acl_identifier_parse(id, &wanted_rights) < 0) {
		mail_storage_set_error(box->storage, MAIL_ERROR_PARAMS,
				       t_strdup_printf("Invalid ID: %s", id));
		return -1;
	}

	iter = acl_object_list_init(aclobj);
	while (acl_object_list_next(iter, &rights)) {
		if (!rights.global &&
		    rights.id_type == wanted_rights.id_type &&
		    null_strcmp(rights.identifier, wanted_rights.identifier) == 0) {
			value_r->value = acl_rights_export(&rights);
			ret = 1;
			break;
		}
	}
	/* the return value here cannot be used, because this function
	   needs to return whether it actually matched something
	   or not */
	if (acl_object_list_deinit(&iter) < 0) {
		mail_storage_set_internal_error(box->storage);
		ret = -1;
	}
	return ret;
}

static int acl_have_attribute_rights(struct mailbox *box)
{
	int ret;

	if (box->deleting) {
		/* deleting attributes during mailbox deletion */
		return 1;
	}

	ret = acl_mailbox_right_lookup(box, ACL_STORAGE_RIGHT_LOOKUP);
	if (ret <= 0) {
		if (ret < 0)
			return -1;
		mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
				T_MAIL_ERR_MAILBOX_NOT_FOUND(box->vname));
		return -1;
	}

	return acl_mailbox_have_extra_attribute_rights(box) ? 0 : -1;
}

int acl_attribute_set(struct mailbox_transaction_context *t,
		      enum mail_attribute_type type, const char *key,
		      const struct mail_attribute_value *value)
{
	struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(t->box);

	if (acl_have_attribute_rights(t->box) < 0)
		return -1;
	if (str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_ACL))
		return acl_attribute_update_acl(t, key, value);
	return abox->module_ctx.super.attribute_set(t, type, key, value);
}

int acl_attribute_get(struct mailbox *box,
		      enum mail_attribute_type type, const char *key,
		      struct mail_attribute_value *value_r)
{
	struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);

	if (acl_have_attribute_rights(box) < 0)
		return -1;
	if (str_begins(key, MAILBOX_ATTRIBUTE_PREFIX_ACL))
		return acl_attribute_get_acl(box, key, value_r);
	return abox->module_ctx.super.attribute_get(box, type, key, value_r);
}

struct mailbox_attribute_iter *
acl_attribute_iter_init(struct mailbox *box, enum mail_attribute_type type,
			const char *prefix)
{
	struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(box);
	struct acl_mailbox_attribute_iter *aiter;

	aiter = i_new(struct acl_mailbox_attribute_iter, 1);
	aiter->iter.box = box;
	if (acl_have_attribute_rights(box) < 0)
		aiter->failed = TRUE;
	else {
		aiter->super = abox->module_ctx.super.
			attribute_iter_init(box, type, prefix);
		if (box->storage->user->dsyncing &&
		    type == MAIL_ATTRIBUTE_TYPE_SHARED &&
		    str_begins(MAILBOX_ATTRIBUTE_PREFIX_ACL, prefix)) {
			aiter->acl_iter = acl_object_list_init(abox->aclobj);
			aiter->acl_name = str_new(default_pool, 128);
			str_append(aiter->acl_name, MAILBOX_ATTRIBUTE_PREFIX_ACL);
		}
	}
	return &aiter->iter;
}

static const char *
acl_attribute_iter_next_acl(struct acl_mailbox_attribute_iter *aiter)
{
	struct acl_rights rights;

	if (aiter->failed)
		return NULL;

	while (acl_object_list_next(aiter->acl_iter, &rights)) {
		if (rights.global)
			continue;
		str_truncate(aiter->acl_name, strlen(MAILBOX_ATTRIBUTE_PREFIX_ACL));
		acl_rights_write_id(aiter->acl_name, &rights);
		return str_c(aiter->acl_name);
	}
	if (acl_object_list_deinit(&aiter->acl_iter) < 0) {
		mail_storage_set_internal_error(aiter->iter.box->storage);
		aiter->failed = TRUE;
	}
	return NULL;
}

const char *acl_attribute_iter_next(struct mailbox_attribute_iter *iter)
{
	struct acl_mailbox_attribute_iter *aiter =
		(struct acl_mailbox_attribute_iter *)iter;
	struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(iter->box);
	const char *key;

	if (aiter->super == NULL)
		return NULL;
	if (aiter->acl_iter != NULL) {
		if ((key = acl_attribute_iter_next_acl(aiter)) != NULL)
			return key;
	}
	return abox->module_ctx.super.attribute_iter_next(aiter->super);
}

int acl_attribute_iter_deinit(struct mailbox_attribute_iter *iter)
{
	struct acl_mailbox_attribute_iter *aiter =
		(struct acl_mailbox_attribute_iter *)iter;
	struct acl_mailbox *abox = ACL_CONTEXT_REQUIRE(iter->box);
	int ret = aiter->failed ? -1 : 0;

	if (aiter->super != NULL) {
		if (abox->module_ctx.super.attribute_iter_deinit(aiter->super) < 0)
			ret = -1;
	}
	if (aiter->acl_iter != NULL && acl_object_list_deinit(&aiter->acl_iter) < 0) {
		mail_storage_set_internal_error(aiter->iter.box->storage);
		ret = -1;
	}
	str_free(&aiter->acl_name);
	i_free(aiter);
	return ret;
}