Coverage Report

Created: 2026-08-13 06:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-storage/mailbox-attribute.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "array.h"
5
#include "str.h"
6
#include "istream.h"
7
#include "settings.h"
8
#include "dict.h"
9
#include "mail-storage-private.h"
10
#include "bsearch-insert-pos.h"
11
#include "mailbox-attribute-internal.h"
12
13
static ARRAY(struct mailbox_attribute_internal) mailbox_internal_attributes;
14
static pool_t mailbox_attribute_pool;
15
16
void mailbox_attributes_init(void)
17
0
{
18
0
  mailbox_attribute_pool =
19
0
    pool_alloconly_create("mailbox attributes", 2048);
20
0
  i_array_init(&mailbox_internal_attributes, 32);
21
22
  /* internal mailbox attributes */
23
0
  mailbox_attributes_internal_init();
24
0
}
25
26
void mailbox_attributes_deinit(void)
27
0
{
28
0
  pool_unref(&mailbox_attribute_pool);
29
0
  array_free(&mailbox_internal_attributes);
30
0
}
31
32
/*
33
 * Internal attributes
34
 */
35
36
static int
37
mailbox_attribute_internal_cmp(
38
  const struct mailbox_attribute_internal *reg1,
39
  const struct mailbox_attribute_internal *reg2)
40
0
{
41
0
  if (reg1->type != reg2->type)
42
0
    return (int)reg1->type - (int)reg2->type;
43
0
  return strcmp(reg1->key, reg2->key);
44
0
}
45
46
void mailbox_attribute_register_internal(
47
  const struct mailbox_attribute_internal *iattr)
48
0
{
49
0
  struct mailbox_attribute_internal ireg;
50
0
  unsigned int insert_idx;
51
52
  /* Validated attributes must have a set() callback that validates the
53
     provided values. Also read-only _RANK_AUTHORITY attributes don't
54
     need validation. */
55
0
  i_assert((iattr->flags & MAIL_ATTRIBUTE_INTERNAL_FLAG_VALIDATED) == 0 ||
56
0
     iattr->set != NULL ||
57
0
     iattr->rank == MAIL_ATTRIBUTE_INTERNAL_RANK_AUTHORITY);
58
59
0
  (void)array_bsearch_insert_pos(&mailbox_internal_attributes,
60
0
    iattr, mailbox_attribute_internal_cmp, &insert_idx);
61
62
0
  ireg = *iattr;
63
0
  ireg.key = p_strdup(mailbox_attribute_pool, iattr->key);
64
0
  array_insert(&mailbox_internal_attributes, insert_idx, &ireg, 1);
65
0
}
66
67
void mailbox_attribute_register_internals(
68
  const struct mailbox_attribute_internal *iattrs, unsigned int count)
69
0
{
70
0
  unsigned int i;
71
72
0
  for (i = 0; i < count; i++)
73
0
    mailbox_attribute_register_internal(&iattrs[i]);
74
0
}
75
76
void mailbox_attribute_unregister_internal(
77
  const struct mailbox_attribute_internal *iattr)
78
0
{
79
0
  unsigned int idx;
80
81
0
  if (!array_bsearch_insert_pos(&mailbox_internal_attributes,
82
0
              iattr, mailbox_attribute_internal_cmp, &idx)) {
83
0
    i_panic("mailbox_attribute_unregister_internal(%s): "
84
0
      "key not found", iattr->key);
85
0
  }
86
87
0
  array_delete(&mailbox_internal_attributes, idx, 1);
88
0
}
89
90
void mailbox_attribute_unregister_internals(
91
  const struct mailbox_attribute_internal *iattrs, unsigned int count)
92
0
{
93
0
  unsigned int i;
94
95
0
  for (i = 0; i < count; i++)
96
0
    mailbox_attribute_unregister_internal(&iattrs[i]);
97
0
}
98
99
static const struct mailbox_attribute_internal *
100
mailbox_internal_attribute_get_int(enum mail_attribute_type type_flags,
101
           const char *key)
102
0
{
103
0
  const struct mailbox_attribute_internal *iattr;
104
0
  struct mailbox_attribute_internal dreg;
105
0
  unsigned int insert_idx;
106
107
0
  i_zero(&dreg);
108
0
  dreg.type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK;
109
0
  dreg.key = key;
110
111
0
  if (array_bsearch_insert_pos(&mailbox_internal_attributes,
112
0
             &dreg, mailbox_attribute_internal_cmp,
113
0
             &insert_idx)) {
114
    /* exact match */
115
0
    return array_idx(&mailbox_internal_attributes, insert_idx);
116
0
  }
117
0
  if (insert_idx == 0) {
118
    /* not found at all */
119
0
    return NULL;
120
0
  }
121
0
  iattr = array_idx(&mailbox_internal_attributes, insert_idx-1);
122
0
  if (!str_begins_with(key, iattr->key)) {
123
    /* iattr isn't a prefix of key */
124
0
    return NULL;
125
0
  } else if ((iattr->flags & MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN) != 0) {
126
    /* iattr is a prefix of key and it wants to handle the key */
127
0
    return iattr;
128
0
  } else {
129
0
    return NULL;
130
0
  }
131
0
}
132
133
static const struct mailbox_attribute_internal *
134
mailbox_internal_attribute_get(enum mail_attribute_type type_flags,
135
             const char *key)
136
0
{
137
0
  const struct mailbox_attribute_internal *iattr;
138
139
0
  iattr = mailbox_internal_attribute_get_int(type_flags, key);
140
0
  if ((type_flags & MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED) != 0 &&
141
0
      iattr != NULL &&
142
0
      (iattr->flags & MAIL_ATTRIBUTE_INTERNAL_FLAG_VALIDATED) == 0) {
143
    /* only validated attributes can be accessed */
144
0
    iattr = NULL;
145
0
  }
146
0
  return iattr;
147
0
}
148
149
static void
150
mailbox_internal_attributes_add_prefixes(ARRAY_TYPE(const_string) *attrs,
151
           pool_t pool, unsigned int old_count,
152
           const char *key)
153
0
{
154
0
  unsigned int new_count;
155
156
0
  if (key[0] == '\0')
157
0
    return;
158
0
  new_count = array_count(attrs);
159
0
  for (unsigned int i = old_count; i < new_count; i++) {
160
0
    const char *old_key = array_idx_elem(attrs, i);
161
0
    const char *prefixed_key;
162
163
0
    if (old_key[0] == '\0')
164
0
      prefixed_key = p_strndup(pool, key, strlen(key)-1);
165
0
    else
166
0
      prefixed_key = p_strconcat(pool, key, old_key, NULL);
167
0
    array_idx_set(attrs, i, &prefixed_key);
168
0
  }
169
0
}
170
171
static int
172
mailbox_internal_attributes_get(struct mailbox *box,
173
  enum mail_attribute_type type_flags, const char *prefix,
174
  pool_t attr_pool, bool have_dict, ARRAY_TYPE(const_string) *attrs)
175
0
{
176
0
  const struct mailbox_attribute_internal *regs;
177
0
  struct mailbox_attribute_internal dreg;
178
0
  char *bare_prefix;
179
0
  size_t plen;
180
0
  unsigned int count, i, j;
181
0
  int ret = 0;
182
183
0
  bare_prefix = t_strdup_noconst(prefix);
184
0
  plen = strlen(bare_prefix);
185
0
  if (plen > 0 && bare_prefix[plen-1] == '/') {
186
0
    bare_prefix[plen-1] = '\0';
187
0
    plen--;
188
0
  }
189
190
0
  i_zero(&dreg);
191
0
  dreg.type = type_flags & MAIL_ATTRIBUTE_TYPE_MASK;
192
0
  dreg.key = bare_prefix;
193
194
0
  (void)array_bsearch_insert_pos(&mailbox_internal_attributes,
195
0
    &dreg, mailbox_attribute_internal_cmp, &i);
196
197
  /* iterate attributes that might have children whose keys begins with
198
     the prefix */
199
0
  regs = array_get(&mailbox_internal_attributes, &count);
200
0
  for (j = i; j > 0; j--) {
201
0
    const struct mailbox_attribute_internal *attr = &regs[j-1];
202
0
    const char *suffix;
203
204
0
    if ((attr->flags & MAIL_ATTRIBUTE_INTERNAL_FLAG_CHILDREN) == 0 ||
205
0
        !str_begins(bare_prefix, attr->key, &suffix))
206
0
      break;
207
208
    /* For example: bare_prefix="foo/bar" and attr->key="foo/", so
209
       iter() is called with key_prefix="bar". It could add to
210
       attrs: { "", "baz" }, which means with the full prefix:
211
       { "foo/bar", "foo/bar/baz" } */
212
0
    if (attr->iter != NULL &&
213
0
        attr->iter(box, suffix, attr_pool, attrs) < 0)
214
0
      ret = -1;
215
0
  }
216
217
  /* iterate attributes whose key begins with the prefix */
218
0
  for (; i < count; i++) {
219
0
    const char *key = regs[i].key;
220
221
0
    if (regs[i].type != dreg.type)
222
0
      return ret;
223
0
    if ((type_flags & MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED) != 0 &&
224
0
        (regs[i].flags & MAIL_ATTRIBUTE_INTERNAL_FLAG_VALIDATED) == 0)
225
0
      continue;
226
227
0
    if (plen > 0) {
228
0
      if (strncmp(key, bare_prefix, plen) != 0)
229
0
        return ret;
230
0
      if (key[plen] == '/') {
231
        /* remove prefix */
232
0
        key += plen + 1;
233
0
      } else if (key[plen] == '\0') {
234
        /* list the key itself, so this becomes an
235
           empty key string. it's the same as how the
236
           dict backend works too. */
237
0
        key += plen;
238
0
      } else {
239
0
        return ret;
240
0
      }
241
0
    }
242
0
    if (regs[i].iter != NULL) {
243
      /* For example: bare_prefix="foo" and
244
         attr->key="foo/bar/", so key="bar/". iter() is
245
         always called with key_prefix="", so we're also
246
         responsible for adding the "bar/" prefix to the
247
         attrs that iter() returns. */
248
0
      unsigned int old_count = array_count(attrs);
249
0
      if (regs[i].iter(box, "", attr_pool, attrs) < 0)
250
0
        ret = -1;
251
0
      mailbox_internal_attributes_add_prefixes(attrs, attr_pool,
252
0
                 old_count, key);
253
0
    } else if (have_dict || regs[i].rank == MAIL_ATTRIBUTE_INTERNAL_RANK_AUTHORITY)
254
0
      array_push_back(attrs, &key);
255
0
  }
256
0
  return ret;
257
0
}
258
259
/*
260
 * Attribute API
261
 */
262
263
static int
264
mailbox_attribute_set_common(struct mailbox_transaction_context *t,
265
           enum mail_attribute_type type_flags,
266
           const char *key,
267
           const struct mail_attribute_value *value)
268
0
{
269
0
  enum mail_attribute_type type =
270
0
    type_flags & MAIL_ATTRIBUTE_TYPE_MASK;
271
0
  const struct mailbox_attribute_internal *iattr;
272
0
  int ret;
273
274
0
  iattr = mailbox_internal_attribute_get(type_flags, key);
275
276
  /* allow internal server attribute only for inbox */
277
0
  if (iattr != NULL && !t->box->inbox_any &&
278
0
      str_begins_with(key, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER))
279
0
    iattr = NULL;
280
281
  /* handle internal attribute */
282
0
  if (iattr != NULL) {
283
0
    switch (iattr->rank) {
284
0
    case MAIL_ATTRIBUTE_INTERNAL_RANK_DEFAULT:
285
0
    case MAIL_ATTRIBUTE_INTERNAL_RANK_OVERRIDE:
286
      /* notify about assignment */
287
0
      if (iattr->set != NULL && iattr->set(t, key, value) < 0)
288
0
        return -1;
289
0
      break;
290
0
    case MAIL_ATTRIBUTE_INTERNAL_RANK_AUTHORITY:
291
0
      if (iattr->set == NULL) {
292
0
        mail_storage_set_error(t->box->storage, MAIL_ERROR_NOTPOSSIBLE, t_strdup_printf(
293
0
          "The /%s/%s attribute cannot be changed",
294
0
          (type == MAIL_ATTRIBUTE_TYPE_SHARED ? "shared" : "private"), key));
295
0
        return -1;
296
0
      }
297
      /* assign internal attribute */
298
0
      return iattr->set(t, key, value);
299
0
    default:
300
0
      i_unreached();
301
0
    }
302
    /* the value was validated. */
303
0
    type_flags &= ENUM_NEGATE(MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED);
304
0
  }
305
306
0
  ret = t->box->v.attribute_set(t, type_flags, key, value);
307
0
  return ret;
308
0
}
309
310
int mailbox_attribute_set(struct mailbox_transaction_context *t,
311
        enum mail_attribute_type type_flags, const char *key,
312
        const struct mail_attribute_value *value)
313
0
{
314
0
  return mailbox_attribute_set_common(t, type_flags, key, value);
315
0
}
316
317
int mailbox_attribute_unset(struct mailbox_transaction_context *t,
318
          enum mail_attribute_type type_flags, const char *key)
319
0
{
320
0
  struct mail_attribute_value value;
321
322
0
  i_zero(&value);
323
0
  return mailbox_attribute_set_common(t, type_flags, key, &value);
324
0
}
325
326
int mailbox_attribute_value_to_string(struct mail_storage *storage,
327
              const struct mail_attribute_value *value,
328
              const char **str_r)
329
0
{
330
0
  string_t *str;
331
0
  const unsigned char *data;
332
0
  size_t size;
333
334
0
  if (value->value_stream == NULL) {
335
0
    *str_r = value->value;
336
0
    return 0;
337
0
  }
338
0
  str = t_str_new(128);
339
0
  i_stream_seek(value->value_stream, 0);
340
0
  while (i_stream_read_more(value->value_stream, &data, &size) > 0) {
341
0
    if (memchr(data, '\0', size) != NULL) {
342
0
      mail_storage_set_error(storage, MAIL_ERROR_PARAMS,
343
0
        "Attribute string value has NULs");
344
0
      return -1;
345
0
    }
346
0
    str_append_data(str, data, size);
347
0
    i_stream_skip(value->value_stream, size);
348
0
  }
349
0
  if (value->value_stream->stream_errno != 0) {
350
0
    mail_storage_set_critical(storage, "read(%s) failed: %s",
351
0
      i_stream_get_name(value->value_stream),
352
0
      i_stream_get_error(value->value_stream));
353
0
    return -1;
354
0
  }
355
0
  i_assert(value->value_stream->eof);
356
0
  *str_r = str_c(str);
357
0
  return 0;
358
0
}
359
360
static int
361
mailbox_attribute_get_common(struct mailbox *box,
362
           enum mail_attribute_type type_flags,
363
           const char *key,
364
           struct mail_attribute_value *value_r)
365
0
{
366
0
  const struct mailbox_attribute_internal *iattr;
367
0
  int ret;
368
369
0
  if (key[0] == '\0') {
370
    /* never exists, and may cause dict lookup errors */
371
0
    return 0;
372
0
  }
373
374
0
  iattr = mailbox_internal_attribute_get(type_flags, key);
375
376
  /* allow internal server attributes only for the inbox */
377
0
  if (iattr != NULL && !box->inbox_user &&
378
0
      str_begins_with(key, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER))
379
0
    iattr = NULL;
380
381
  /* internal attribute */
382
0
  if (iattr != NULL) {
383
0
    switch (iattr->rank) {
384
0
    case MAIL_ATTRIBUTE_INTERNAL_RANK_OVERRIDE:
385
      /* we already checked that this attribute has
386
         validated-flag */
387
0
      type_flags &= ENUM_NEGATE(MAIL_ATTRIBUTE_TYPE_FLAG_VALIDATED);
388
389
0
      if (iattr->get == NULL)
390
0
        break;
391
0
      if ((ret = iattr->get(box, key, value_r)) != 0) {
392
0
        if (ret < 0)
393
0
          return -1;
394
0
        value_r->flags |= MAIL_ATTRIBUTE_VALUE_FLAG_READONLY;
395
0
        return 1;
396
0
      }
397
0
      break;
398
0
    case MAIL_ATTRIBUTE_INTERNAL_RANK_DEFAULT:
399
0
      break;
400
0
    case MAIL_ATTRIBUTE_INTERNAL_RANK_AUTHORITY:
401
0
      if ((ret = iattr->get(box, key, value_r)) <= 0)
402
0
        return ret;
403
0
      value_r->flags |= MAIL_ATTRIBUTE_VALUE_FLAG_READONLY;
404
0
      return 1;
405
0
    default:
406
0
      i_unreached();
407
0
    }
408
0
  }
409
410
0
  ret = box->v.attribute_get(box, type_flags, key, value_r);
411
0
  if (ret != 0)
412
0
    return ret;
413
414
  /* default entries */
415
0
  if (iattr != NULL) {
416
0
    switch (iattr->rank) {
417
0
    case MAIL_ATTRIBUTE_INTERNAL_RANK_DEFAULT:
418
0
      if (iattr->get == NULL)
419
0
        ret = 0;
420
0
      else {
421
0
        if ((ret = iattr->get(box, key, value_r)) < 0)
422
0
          return ret;
423
0
      }
424
0
      if (ret > 0) {
425
0
        value_r->flags |= MAIL_ATTRIBUTE_VALUE_FLAG_READONLY;
426
0
        return 1;
427
0
      }
428
0
      break;
429
0
    case MAIL_ATTRIBUTE_INTERNAL_RANK_OVERRIDE:
430
0
      break;
431
0
    default:
432
0
      i_unreached();
433
0
    }
434
0
  }
435
0
  return 0;
436
0
}
437
438
int mailbox_attribute_get(struct mailbox *box,
439
        enum mail_attribute_type type_flags, const char *key,
440
        struct mail_attribute_value *value_r)
441
0
{
442
0
  int ret;
443
0
  i_zero(value_r);
444
0
  if ((ret = mailbox_attribute_get_common(box, type_flags, key,
445
0
        value_r)) <= 0)
446
0
    return ret;
447
0
  i_assert(value_r->value != NULL);
448
0
  return 1;
449
0
}
450
451
int mailbox_attribute_get_stream(struct mailbox *box,
452
         enum mail_attribute_type type_flags,
453
         const char *key,
454
         struct mail_attribute_value *value_r)
455
0
{
456
0
  int ret;
457
458
0
  i_zero(value_r);
459
0
  value_r->flags |= MAIL_ATTRIBUTE_VALUE_FLAG_INT_STREAMS;
460
0
  if ((ret = mailbox_attribute_get_common(box, type_flags, key,
461
0
        value_r)) <= 0)
462
0
    return ret;
463
0
  i_assert(value_r->value != NULL || value_r->value_stream != NULL);
464
0
  return 1;
465
0
}
466
467
struct mailbox_attribute_internal_iter {
468
  struct mailbox_attribute_iter iter;
469
  pool_t pool;
470
471
  ARRAY_TYPE(const_string) extra_attrs;
472
  unsigned int extra_attr_idx;
473
474
  struct mailbox_attribute_iter *real_iter;
475
  bool iter_failed;
476
};
477
478
int mailbox_attribute_dict_is_enabled(struct mail_user *user,
479
              const char **error_r)
480
0
{
481
0
  struct dict_settings *dict_set;
482
0
  struct event *event = event_create(user->event);
483
0
  settings_event_add_filter_name(event, "mail_attribute");
484
0
  int ret = settings_get(event, &dict_setting_parser_info, 0,
485
0
             &dict_set, error_r);
486
0
  if (ret == 0 && array_not_empty(&dict_set->dicts))
487
0
    ret = 1;
488
0
  settings_free(dict_set);
489
0
  event_unref(&event);
490
0
  return ret;
491
0
}
492
493
struct mailbox_attribute_iter *
494
mailbox_attribute_iter_init(struct mailbox *box,
495
          enum mail_attribute_type type_flags,
496
          const char *prefix)
497
0
{
498
0
  struct mailbox_attribute_internal_iter *intiter;
499
0
  struct mailbox_attribute_iter *iter;
500
0
  ARRAY_TYPE(const_string) extra_attrs;
501
0
  const char *const *attr, *error;
502
0
  pool_t pool;
503
0
  bool have_dict, failed = FALSE;
504
505
0
  iter = box->v.attribute_iter_init(box, type_flags, prefix);
506
0
  i_assert(iter->box != NULL);
507
0
  box->attribute_iter_count++;
508
509
0
  int ret = mailbox_attribute_dict_is_enabled(box->storage->user, &error);
510
0
  have_dict = ret > 0;
511
0
  if (ret < 0) {
512
0
    mail_storage_set_critical(box->storage, "%s", error);
513
0
    failed = TRUE;
514
0
  }
515
516
  /* check which internal attributes may apply */
517
0
  t_array_init(&extra_attrs, 4);
518
0
  pool = pool_alloconly_create("mailbox internal attribute iter", 128);
519
0
  if (mailbox_internal_attributes_get(box, type_flags, prefix, pool,
520
0
              have_dict, &extra_attrs) < 0)
521
0
    failed = TRUE;
522
523
  /* any extra internal attributes to add? */
524
0
  if (array_count(&extra_attrs) == 0 && !failed) {
525
    /* no */
526
0
    pool_unref(&pool);
527
0
    return iter;
528
0
  }
529
530
  /* yes */
531
0
  intiter = p_new(pool, struct mailbox_attribute_internal_iter, 1);
532
0
  intiter->pool = pool;
533
0
  intiter->real_iter = iter;
534
0
  intiter->iter_failed = failed;
535
0
  p_array_init(&intiter->extra_attrs, pool, 4);
536
537
  /* copy relevant attributes */
538
0
  array_foreach(&extra_attrs, attr) {
539
    /* skip internal server attributes unless we're iterating inbox */
540
0
    if (!box->inbox_user &&
541
0
        strncmp(*attr, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER,
542
0
          strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER)) == 0)
543
0
      continue;
544
0
    array_push_back(&intiter->extra_attrs, attr);
545
0
  }
546
0
  return &intiter->iter;
547
0
}
548
549
const char *mailbox_attribute_iter_next(struct mailbox_attribute_iter *iter)
550
0
{
551
0
  struct mailbox_attribute_internal_iter *intiter;
552
0
  const char *const *attrs;
553
0
  unsigned int count, i;
554
0
  const char *result;
555
556
0
  if (iter->box != NULL) {
557
    /* no internal attributes to add */
558
0
    return iter->box->v.attribute_iter_next(iter);
559
0
  }
560
561
  /* filter out duplicate results */
562
0
  intiter = (struct mailbox_attribute_internal_iter *)iter;
563
0
  attrs = array_get(&intiter->extra_attrs, &count);
564
0
  while ((result = intiter->real_iter->box->
565
0
      v.attribute_iter_next(intiter->real_iter)) != NULL) {
566
0
    for (i = 0; i < count; i++) {
567
0
      if (strcasecmp(attrs[i], result) == 0)
568
0
        break;
569
0
    }
570
0
    if (i == count) {
571
      /* return normally */
572
0
      return result;
573
0
    }
574
    /* this attribute name is also to be returned as extra;
575
       skip now */
576
0
  }
577
578
  /* return extra attributes at the end */
579
0
  if (intiter->extra_attr_idx < count)
580
0
    return attrs[intiter->extra_attr_idx++];
581
0
  return NULL;
582
0
}
583
584
int mailbox_attribute_iter_deinit(struct mailbox_attribute_iter **_iter)
585
0
{
586
0
  struct mailbox_attribute_iter *iter = *_iter;
587
0
  struct mailbox_attribute_internal_iter *intiter;
588
0
  int ret;
589
590
0
  *_iter = NULL;
591
592
0
  if (iter->box != NULL) {
593
    /* not wrapped */
594
0
    i_assert(iter->box->attribute_iter_count > 0);
595
0
    iter->box->attribute_iter_count--;
596
0
    return iter->box->v.attribute_iter_deinit(iter);
597
0
  }
598
599
  /* wrapped */
600
0
  intiter = (struct mailbox_attribute_internal_iter *)iter;
601
602
0
  i_assert(intiter->real_iter->box->attribute_iter_count > 0);
603
0
  intiter->real_iter->box->attribute_iter_count--;
604
605
0
  ret = intiter->real_iter->box->v.attribute_iter_deinit(intiter->real_iter);
606
0
  if (intiter->iter_failed)
607
0
    ret = -1;
608
0
  pool_unref(&intiter->pool);
609
0
  return ret;
610
0
}