Coverage Report

Created: 2026-06-09 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-storage/index/index-attribute.c
Line
Count
Source
1
/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "ioloop.h"
5
#include "settings.h"
6
#include "dict.h"
7
#include "index-storage.h"
8
9
struct index_storage_attribute_iter {
10
  struct mailbox_attribute_iter iter;
11
  struct dict_iterate_context *diter;
12
  char *prefix;
13
  size_t prefix_len;
14
  bool dict_disabled;
15
};
16
17
static struct mail_namespace *
18
mail_user_find_attribute_namespace(struct mail_user *user)
19
0
{
20
0
  struct mail_namespace *ns;
21
22
0
  ns = mail_namespace_find_inbox(user->namespaces);
23
0
  if (ns != NULL)
24
0
    return ns;
25
26
0
  for (ns = user->namespaces; ns != NULL; ns = ns->next) {
27
0
    if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE)
28
0
      return ns;
29
0
  }
30
0
  return NULL;
31
0
}
32
33
static int
34
index_storage_get_user_dict(struct mail_storage *err_storage,
35
          struct mail_user *user, struct dict **dict_r)
36
0
{
37
0
  struct mail_namespace *ns;
38
0
  const char *error;
39
0
  int ret;
40
41
0
  if (user->_attr_dict != NULL) {
42
0
    *dict_r = user->_attr_dict;
43
0
    return 0;
44
0
  }
45
0
  if (user->attr_dict_failed) {
46
0
    mail_storage_set_internal_error(err_storage);
47
0
    return -1;
48
0
  }
49
50
0
  ns = mail_user_find_attribute_namespace(user);
51
0
  if (ns == NULL) {
52
    /* probably never happens? */
53
0
    mail_storage_set_error(err_storage, MAIL_ERROR_NOTPOSSIBLE,
54
0
      "Mailbox attributes not available for this mailbox");
55
0
    return -1;
56
0
  }
57
58
0
  struct event *event = event_create(user->event);
59
0
  settings_event_add_filter_name(event, "mail_attribute");
60
0
  ret = dict_init_auto(event, &user->_attr_dict, &error);
61
0
  event_unref(&event);
62
63
0
  if (ret < 0) {
64
0
    mail_storage_set_critical(err_storage,
65
0
      "mail_attribute: dict_init_auto() failed: %s",
66
0
      error);
67
0
    user->attr_dict_failed = TRUE;
68
0
    return -1;
69
0
  }
70
0
  if (ret == 0) {
71
0
    mail_storage_set_error(err_storage, MAIL_ERROR_NOTPOSSIBLE,
72
0
               "Mailbox attributes not enabled");
73
0
    return -1;
74
0
  }
75
76
0
  *dict_r = user->_attr_dict;
77
0
  return 0;
78
0
}
79
80
static int
81
index_storage_get_dict(struct mailbox *box, enum mail_attribute_type type_flags,
82
           struct dict **dict_r, const char **mailbox_prefix_r)
83
0
{
84
0
  enum mail_attribute_type type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK;
85
0
  struct mail_storage *storage = box->storage;
86
0
  struct mail_namespace *ns;
87
0
  struct mailbox_metadata metadata;
88
0
  const char *error;
89
0
  int ret;
90
91
0
  if ((type_flags & MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED) != 0) {
92
    /* IMAP METADATA support isn't enabled, so don't allow using
93
       the mail_attribute's dict. */
94
0
    mail_storage_set_error(storage, MAIL_ERROR_NOTPOSSIBLE,
95
0
               "Generic mailbox attributes not enabled");
96
0
    return -1;
97
0
  }
98
99
0
  if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0)
100
0
    return -1;
101
0
  *mailbox_prefix_r = guid_128_to_string(metadata.guid);
102
103
0
  ns = mailbox_get_namespace(box);
104
0
  if (type == MAIL_ATTRIBUTE_TYPE_PRIVATE) {
105
    /* private attributes are stored in user's own dict */
106
0
    return index_storage_get_user_dict(storage, storage->user, dict_r);
107
0
  } else if (ns->user == ns->owner) {
108
    /* user owns the mailbox. shared attributes are stored in
109
       the same dict. */
110
0
    return index_storage_get_user_dict(storage, storage->user, dict_r);
111
0
  } else if (ns->owner != NULL) {
112
    /* accessing shared attribute of a shared mailbox.
113
       use the owner's dict. */
114
0
    return index_storage_get_user_dict(storage, ns->owner, dict_r);
115
0
  }
116
117
  /* accessing shared attributes of a public mailbox. no user owns it,
118
     so use the storage's dict. */
119
0
  if (storage->_shared_attr_dict != NULL) {
120
0
    *dict_r = storage->_shared_attr_dict;
121
0
    return 0;
122
0
  }
123
0
  if (storage->shared_attr_dict_failed) {
124
0
    mail_storage_set_internal_error(storage);
125
0
    return -1;
126
0
  }
127
128
0
  struct event *event = event_create(storage->event);
129
0
  settings_event_add_filter_name(event, "mail_attribute");
130
0
  ret = dict_init_auto(event, &storage->_shared_attr_dict, &error);
131
0
  event_unref(&event);
132
133
0
  if (ret < 0) {
134
0
    mail_storage_set_critical(storage,
135
0
      "mail_attribute: dict_init_auto() failed: %s",
136
0
      error);
137
0
    storage->shared_attr_dict_failed = TRUE;
138
0
    return -1;
139
0
  }
140
0
  if (ret == 0) {
141
0
    mail_storage_set_error(storage, MAIL_ERROR_NOTPOSSIBLE,
142
0
               "Mailbox attributes not enabled");
143
0
    return -1;
144
0
  }
145
146
0
  *dict_r = storage->_shared_attr_dict;
147
0
  return 0;
148
0
}
149
150
static const char *
151
key_get_prefixed(enum mail_attribute_type type_flags, const char *mailbox_prefix,
152
     const char *key)
153
0
{
154
0
  enum mail_attribute_type type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK;
155
156
0
  switch (type) {
157
0
  case MAIL_ATTRIBUTE_TYPE_PRIVATE:
158
0
    return t_strconcat(DICT_PATH_PRIVATE, mailbox_prefix, "/",
159
0
           key, NULL);
160
0
  case MAIL_ATTRIBUTE_TYPE_SHARED:
161
0
    return t_strconcat(DICT_PATH_SHARED, mailbox_prefix, "/",
162
0
           key, NULL);
163
0
  }
164
0
  i_unreached();
165
0
}
166
167
static int
168
index_storage_attribute_get_dict_trans(struct mailbox_transaction_context *t,
169
               enum mail_attribute_type type_flags,
170
               struct dict_transaction_context **dtrans_r,
171
               const char **mailbox_prefix_r)
172
0
{
173
0
  enum mail_attribute_type type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK;
174
0
  struct dict_transaction_context **dtransp = NULL;
175
0
  struct dict *dict;
176
0
  struct mailbox_metadata metadata;
177
178
0
  switch (type) {
179
0
  case MAIL_ATTRIBUTE_TYPE_PRIVATE:
180
0
    dtransp = &t->attr_pvt_trans;
181
0
    break;
182
0
  case MAIL_ATTRIBUTE_TYPE_SHARED:
183
0
    dtransp = &t->attr_shared_trans;
184
0
    break;
185
0
  }
186
0
  i_assert(dtransp != NULL);
187
188
0
  if (*dtransp != NULL &&
189
0
      (type_flags & MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED) == 0) {
190
    /* Transaction already created. Even if it was, don't use it
191
       if _FLAG_VALIDATED is being used. It'll be handled below by
192
       returning failure. */
193
0
    if (mailbox_get_metadata(t->box, MAILBOX_METADATA_GUID,
194
0
           &metadata) < 0)
195
0
      return -1;
196
0
    *mailbox_prefix_r = guid_128_to_string(metadata.guid);
197
0
    *dtrans_r = *dtransp;
198
0
    return 0;
199
0
  }
200
201
0
  if (index_storage_get_dict(t->box, type_flags, &dict, mailbox_prefix_r) < 0)
202
0
    return -1;
203
0
  i_assert(*dtransp == NULL);
204
205
0
  struct mail_user *user = mailbox_list_get_user(t->box->list);
206
0
  const struct dict_op_settings *set = mail_user_get_dict_op_settings(user);
207
0
  *dtransp = *dtrans_r = dict_transaction_begin(dict, set);
208
0
  return 0;
209
0
}
210
211
int index_storage_attribute_set(struct mailbox_transaction_context *t,
212
        enum mail_attribute_type type_flags,
213
        const char *key,
214
        const struct mail_attribute_value *value)
215
0
{
216
0
  enum mail_attribute_type type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK;
217
0
  struct dict_transaction_context *dtrans;
218
0
  const char *mailbox_prefix;
219
0
  bool pvt = type == MAIL_ATTRIBUTE_TYPE_PRIVATE;
220
0
  time_t ts = value->last_change != 0 ? value->last_change : ioloop_time;
221
0
  int ret = 0;
222
223
0
  if (index_storage_attribute_get_dict_trans(t, type_flags, &dtrans,
224
0
               &mailbox_prefix) < 0)
225
0
    return -1;
226
227
0
  T_BEGIN {
228
0
    const char *prefixed_key =
229
0
      key_get_prefixed(type_flags, mailbox_prefix, key);
230
0
    const char *value_str;
231
232
0
    if (mailbox_attribute_value_to_string(t->box->storage, value,
233
0
                  &value_str) < 0) {
234
0
      ret = -1;
235
0
    } else if (value_str != NULL) {
236
0
      dict_set(dtrans, prefixed_key, value_str);
237
0
      mail_index_attribute_set(t->itrans, pvt, key,
238
0
             ts, strlen(value_str));
239
0
    } else {
240
0
      dict_unset(dtrans, prefixed_key);
241
0
      mail_index_attribute_unset(t->itrans, pvt, key, ts);
242
0
    }
243
0
  } T_END;
244
0
  return ret;
245
0
}
246
247
int index_storage_attribute_get(struct mailbox *box,
248
        enum mail_attribute_type type_flags,
249
        const char *key,
250
        struct mail_attribute_value *value_r)
251
0
{
252
0
  struct dict *dict;
253
0
  const char *mailbox_prefix, *error;
254
0
  int ret;
255
256
0
  i_zero(value_r);
257
258
0
  if (index_storage_get_dict(box, type_flags, &dict, &mailbox_prefix) < 0)
259
0
    return -1;
260
261
0
  struct mail_user *user = mailbox_list_get_user(box->list);
262
0
  const struct dict_op_settings *set = mail_user_get_dict_op_settings(user);
263
0
  ret = dict_lookup(dict, set, pool_datastack_create(),
264
0
        key_get_prefixed(type_flags, mailbox_prefix, key),
265
0
        &value_r->value, &error);
266
0
  if (ret < 0) {
267
0
    mailbox_set_critical(box,
268
0
      "Failed to get attribute %s: %s", key, error);
269
0
    return -1;
270
0
  }
271
0
  return ret;
272
0
}
273
274
struct mailbox_attribute_iter *
275
index_storage_attribute_iter_init(struct mailbox *box,
276
          enum mail_attribute_type type_flags,
277
          const char *prefix)
278
0
{
279
0
  struct index_storage_attribute_iter *iter;
280
0
  struct dict *dict;
281
0
  const char *mailbox_prefix;
282
283
0
  iter = i_new(struct index_storage_attribute_iter, 1);
284
0
  iter->iter.box = box;
285
0
  if (index_storage_get_dict(box, type_flags, &dict, &mailbox_prefix) < 0) {
286
0
    if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTPOSSIBLE) {
287
0
      mail_storage_clear_error(box->storage);
288
0
      iter->dict_disabled = TRUE;
289
0
    }
290
0
  } else {
291
0
    iter->prefix = i_strdup(key_get_prefixed(type_flags, mailbox_prefix,
292
0
               prefix));
293
0
    iter->prefix_len = strlen(iter->prefix);
294
0
    struct mail_user *user = mailbox_list_get_user(box->list);
295
0
    const struct dict_op_settings *set = mail_user_get_dict_op_settings(user);
296
0
    iter->diter = dict_iterate_init(dict, set, iter->prefix,
297
0
            DICT_ITERATE_FLAG_RECURSE |
298
0
            DICT_ITERATE_FLAG_NO_VALUE);
299
0
  }
300
0
  return &iter->iter;
301
0
}
302
303
const char *
304
index_storage_attribute_iter_next(struct mailbox_attribute_iter *_iter)
305
0
{
306
0
  struct index_storage_attribute_iter *iter =
307
0
    (struct index_storage_attribute_iter *)_iter;
308
0
  const char *key, *value;
309
310
0
  if (iter->diter == NULL || !dict_iterate(iter->diter, &key, &value))
311
0
    return NULL;
312
313
0
  i_assert(strncmp(key, iter->prefix, iter->prefix_len) == 0);
314
0
  key += iter->prefix_len;
315
0
  return key;
316
0
}
317
318
int index_storage_attribute_iter_deinit(struct mailbox_attribute_iter *_iter)
319
0
{
320
0
  struct index_storage_attribute_iter *iter =
321
0
    (struct index_storage_attribute_iter *)_iter;
322
0
  const char *error;
323
0
  int ret;
324
325
0
  if (iter->diter == NULL) {
326
0
    ret = iter->dict_disabled ? 0 : -1;
327
0
  } else {
328
0
    if ((ret = dict_iterate_deinit(&iter->diter, &error)) < 0) {
329
0
      mailbox_set_critical(_iter->box,
330
0
        "dict_iterate(%s) failed: %s",
331
0
        iter->prefix, error);
332
0
    }
333
0
  }
334
0
  i_free(iter->prefix);
335
  i_free(iter);
336
0
  return ret;
337
0
}