summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/index/index-attribute.c
blob: 3d4c4158cedf2033853aba241cd0abc5564c99fc (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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "ioloop.h"
#include "dict.h"
#include "index-storage.h"

struct index_storage_attribute_iter {
	struct mailbox_attribute_iter iter;
	struct dict_iterate_context *diter;
	char *prefix;
	size_t prefix_len;
	bool dict_disabled;
};

static struct mail_namespace *
mail_user_find_attribute_namespace(struct mail_user *user)
{
	struct mail_namespace *ns;

	ns = mail_namespace_find_inbox(user->namespaces);
	if (ns != NULL)
		return ns;

	for (ns = user->namespaces; ns != NULL; ns = ns->next) {
		if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE)
			return ns;
	}
	return NULL;
}

static int
index_storage_get_user_dict(struct mail_storage *err_storage,
			    struct mail_user *user, struct dict **dict_r)
{
	struct dict_settings dict_set;
	struct mail_namespace *ns;
	struct mail_storage *attr_storage;
	const char *error;

	if (user->_attr_dict != NULL) {
		*dict_r = user->_attr_dict;
		return 0;
	}
	if (user->attr_dict_failed) {
		mail_storage_set_internal_error(err_storage);
		return -1;
	}

	ns = mail_user_find_attribute_namespace(user);
	if (ns == NULL) {
		/* probably never happens? */
		mail_storage_set_error(err_storage, MAIL_ERROR_NOTPOSSIBLE,
			"Mailbox attributes not available for this mailbox");
		return -1;
	}
	attr_storage = mail_namespace_get_default_storage(ns);

	if (*attr_storage->set->mail_attribute_dict == '\0') {
		mail_storage_set_error(err_storage, MAIL_ERROR_NOTPOSSIBLE,
				       "Mailbox attributes not enabled");
		return -1;
	}

	i_zero(&dict_set);
	dict_set.base_dir = user->set->base_dir;
	dict_set.event_parent = user->event;
	if (dict_init(attr_storage->set->mail_attribute_dict, &dict_set,
		      &user->_attr_dict, &error) < 0) {
		mail_storage_set_critical(err_storage,
			"mail_attribute_dict: dict_init(%s) failed: %s",
			attr_storage->set->mail_attribute_dict, error);
		user->attr_dict_failed = TRUE;
		return -1;
	}
	*dict_r = user->_attr_dict;
	return 0;
}

static int
index_storage_get_dict(struct mailbox *box, enum mail_attribute_type type_flags,
		       struct dict **dict_r, const char **mailbox_prefix_r)
{
	enum mail_attribute_type type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK;
	struct mail_storage *storage = box->storage;
	struct mail_namespace *ns;
	struct mailbox_metadata metadata;
	struct dict_settings set;
	const char *error;

	if ((type_flags & MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED) != 0) {
		/* IMAP METADATA support isn't enabled, so don't allow using
		   mail_attribute_dict. */
		mail_storage_set_error(storage, MAIL_ERROR_NOTPOSSIBLE,
				       "Generic mailbox attributes not enabled");
		return -1;
	}

	if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0)
		return -1;
	*mailbox_prefix_r = guid_128_to_string(metadata.guid);

	ns = mailbox_get_namespace(box);
	if (type == MAIL_ATTRIBUTE_TYPE_PRIVATE) {
		/* private attributes are stored in user's own dict */
		return index_storage_get_user_dict(storage, storage->user, dict_r);
	} else if (ns->user == ns->owner) {
		/* user owns the mailbox. shared attributes are stored in
		   the same dict. */
		return index_storage_get_user_dict(storage, storage->user, dict_r);
	} else if (ns->owner != NULL) {
		/* accessing shared attribute of a shared mailbox.
		   use the owner's dict. */
		return index_storage_get_user_dict(storage, ns->owner, dict_r);
	}

	/* accessing shared attributes of a public mailbox. no user owns it,
	   so use the storage's dict. */
	if (storage->_shared_attr_dict != NULL) {
		*dict_r = storage->_shared_attr_dict;
		return 0;
	}
	if (*storage->set->mail_attribute_dict == '\0') {
		mail_storage_set_error(storage, MAIL_ERROR_NOTPOSSIBLE,
				       "Mailbox attributes not enabled");
		return -1;
	}
	if (storage->shared_attr_dict_failed) {
		mail_storage_set_internal_error(storage);
		return -1;
	}

	i_zero(&set);
	set.base_dir = storage->user->set->base_dir;
	set.event_parent = storage->user->event;
	if (dict_init(storage->set->mail_attribute_dict, &set,
		      &storage->_shared_attr_dict, &error) < 0) {
		mail_storage_set_critical(storage,
			"mail_attribute_dict: dict_init(%s) failed: %s",
			storage->set->mail_attribute_dict, error);
		storage->shared_attr_dict_failed = TRUE;
		return -1;
	}
	*dict_r = storage->_shared_attr_dict;
	return 0;
}

static const char *
key_get_prefixed(enum mail_attribute_type type_flags, const char *mailbox_prefix,
		 const char *key)
{
	enum mail_attribute_type type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK;

	switch (type) {
	case MAIL_ATTRIBUTE_TYPE_PRIVATE:
		return t_strconcat(DICT_PATH_PRIVATE, mailbox_prefix, "/",
				   key, NULL);
	case MAIL_ATTRIBUTE_TYPE_SHARED:
		return t_strconcat(DICT_PATH_SHARED, mailbox_prefix, "/",
				   key, NULL);
	}
	i_unreached();
}

static int
index_storage_attribute_get_dict_trans(struct mailbox_transaction_context *t,
				       enum mail_attribute_type type_flags,
				       struct dict_transaction_context **dtrans_r,
				       const char **mailbox_prefix_r)
{
	enum mail_attribute_type type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK;
	struct dict_transaction_context **dtransp = NULL;
	struct dict *dict;
	struct mailbox_metadata metadata;

	switch (type) {
	case MAIL_ATTRIBUTE_TYPE_PRIVATE:
		dtransp = &t->attr_pvt_trans;
		break;
	case MAIL_ATTRIBUTE_TYPE_SHARED:
		dtransp = &t->attr_shared_trans;
		break;
	}
	i_assert(dtransp != NULL);

	if (*dtransp != NULL &&
	    (type_flags & MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED) == 0) {
		/* Transaction already created. Even if it was, don't use it
		   if _FLAG_VALIDATED is being used. It'll be handled below by
		   returning failure. */
		if (mailbox_get_metadata(t->box, MAILBOX_METADATA_GUID,
					 &metadata) < 0)
			return -1;
		*mailbox_prefix_r = guid_128_to_string(metadata.guid);
		*dtrans_r = *dtransp;
		return 0;
	}

	if (index_storage_get_dict(t->box, type_flags, &dict, mailbox_prefix_r) < 0)
		return -1;
	i_assert(*dtransp == NULL);

	struct mail_user *user = mailbox_list_get_user(t->box->list);
	const struct dict_op_settings *set = mail_user_get_dict_op_settings(user);
	*dtransp = *dtrans_r = dict_transaction_begin(dict, set);
	return 0;
}

int index_storage_attribute_set(struct mailbox_transaction_context *t,
				enum mail_attribute_type type_flags,
				const char *key,
				const struct mail_attribute_value *value)
{
	enum mail_attribute_type type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK;
	struct dict_transaction_context *dtrans;
	const char *mailbox_prefix;
	bool pvt = type == MAIL_ATTRIBUTE_TYPE_PRIVATE;
	time_t ts = value->last_change != 0 ? value->last_change : ioloop_time;
	int ret = 0;

	if (index_storage_attribute_get_dict_trans(t, type_flags, &dtrans,
						   &mailbox_prefix) < 0)
		return -1;

	T_BEGIN {
		const char *prefixed_key =
			key_get_prefixed(type_flags, mailbox_prefix, key);
		const char *value_str;

		if (mailbox_attribute_value_to_string(t->box->storage, value,
						      &value_str) < 0) {
			ret = -1;
		} else if (value_str != NULL) {
			dict_set(dtrans, prefixed_key, value_str);
			mail_index_attribute_set(t->itrans, pvt, key,
						 ts, strlen(value_str));
		} else {
			dict_unset(dtrans, prefixed_key);
			mail_index_attribute_unset(t->itrans, pvt, key, ts);
		}
	} T_END;
	return ret;
}

int index_storage_attribute_get(struct mailbox *box,
				enum mail_attribute_type type_flags,
				const char *key,
				struct mail_attribute_value *value_r)
{
	struct dict *dict;
	const char *mailbox_prefix, *error;
	int ret;

	i_zero(value_r);

	if (index_storage_get_dict(box, type_flags, &dict, &mailbox_prefix) < 0)
		return -1;

	struct mail_user *user = mailbox_list_get_user(box->list);
	const struct dict_op_settings *set = mail_user_get_dict_op_settings(user);
	ret = dict_lookup(dict, set, pool_datastack_create(),
			  key_get_prefixed(type_flags, mailbox_prefix, key),
			  &value_r->value, &error);
	if (ret < 0) {
		mailbox_set_critical(box,
			"Failed to get attribute %s: %s", key, error);
		return -1;
	}
	return ret;
}

struct mailbox_attribute_iter *
index_storage_attribute_iter_init(struct mailbox *box,
				  enum mail_attribute_type type_flags,
				  const char *prefix)
{
	struct index_storage_attribute_iter *iter;
	struct dict *dict;
	const char *mailbox_prefix;

	iter = i_new(struct index_storage_attribute_iter, 1);
	iter->iter.box = box;
	if (index_storage_get_dict(box, type_flags, &dict, &mailbox_prefix) < 0) {
		if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTPOSSIBLE)
			iter->dict_disabled = TRUE;
	} else {
		iter->prefix = i_strdup(key_get_prefixed(type_flags, mailbox_prefix,
							 prefix));
		iter->prefix_len = strlen(iter->prefix);
		struct mail_user *user = mailbox_list_get_user(box->list);
		const struct dict_op_settings *set = mail_user_get_dict_op_settings(user);
		iter->diter = dict_iterate_init(dict, set, iter->prefix,
						DICT_ITERATE_FLAG_RECURSE |
						DICT_ITERATE_FLAG_NO_VALUE);
	}
	return &iter->iter;
}

const char *
index_storage_attribute_iter_next(struct mailbox_attribute_iter *_iter)
{
	struct index_storage_attribute_iter *iter =
		(struct index_storage_attribute_iter *)_iter;
	const char *key, *value;

	if (iter->diter == NULL || !dict_iterate(iter->diter, &key, &value))
		return NULL;

	i_assert(strncmp(key, iter->prefix, iter->prefix_len) == 0);
	key += iter->prefix_len;
	return key;
}

int index_storage_attribute_iter_deinit(struct mailbox_attribute_iter *_iter)
{
	struct index_storage_attribute_iter *iter =
		(struct index_storage_attribute_iter *)_iter;
	const char *error;
	int ret;

	if (iter->diter == NULL) {
		ret = iter->dict_disabled ? 0 : -1;
	} else {
		if ((ret = dict_iterate_deinit(&iter->diter, &error)) < 0) {
			mailbox_set_critical(_iter->box,
				"dict_iterate(%s) failed: %s",
				iter->prefix, error);
		}
	}
	i_free(iter->prefix);
	i_free(iter);
	return ret;
}