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/index/mbox/mbox-sync-parse.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
/* MD5 header summing logic was pretty much copy&pasted from popa3d by
4
   Solar Designer */
5
6
#include "lib.h"
7
#include "ioloop.h"
8
#include "array.h"
9
#include "buffer.h"
10
#include "istream.h"
11
#include "str.h"
12
#include "write-full.h"
13
#include "message-parser.h"
14
#include "mail-index.h"
15
#include "mbox-storage.h"
16
#include "mbox-md5.h"
17
#include "mbox-sync-private.h"
18
19
20
0
#define IS_LWSP_LF(c) (IS_LWSP(c) || (c) == '\n')
21
22
struct mbox_sync_header_func {
23
  const char *header;
24
  bool (*func)(struct mbox_sync_mail_context *ctx,
25
         struct message_header_line *hdr);
26
};
27
28
struct mbox_flag_type mbox_status_flags[] = {
29
  { 'R', MAIL_SEEN },
30
  { 'O', MBOX_NONRECENT_KLUDGE },
31
  { 0, 0 }
32
};
33
34
struct mbox_flag_type mbox_xstatus_flags[] = {
35
  { 'A', MAIL_ANSWERED },
36
  { 'F', MAIL_FLAGGED },
37
  { 'T', MAIL_DRAFT },
38
  { 'D', MAIL_DELETED },
39
  { 0, 0 }
40
};
41
42
static void parse_trailing_whitespace(struct mbox_sync_mail_context *ctx,
43
              struct message_header_line *hdr)
44
0
{
45
0
  size_t i, space = 0;
46
47
  /* the value may contain newlines. we can't count whitespace before
48
     and after it as a single contiguous whitespace block, as that may
49
     get us into situation where removing whitespace goes eg.
50
     " \n \n" -> " \n\n" which would then be treated as end of headers.
51
52
     that could probably be avoided by being careful, but as newlines
53
     should never be there (we don't generate them), it's not worth the
54
     trouble. */
55
56
0
  for (i = hdr->full_value_len; i > 0; i--) {
57
0
    if (!IS_LWSP(hdr->full_value[i-1]))
58
0
      break;
59
0
    space++;
60
0
  }
61
62
0
  if ((ssize_t)space > ctx->mail.space) {
63
0
    i_assert(space != 0);
64
0
    ctx->mail.offset = ctx->hdr_offset + str_len(ctx->header) + i;
65
0
    ctx->mail.space = space;
66
0
  }
67
0
}
68
69
static enum mail_flags mbox_flag_find(struct mbox_flag_type *flags, char chr)
70
0
{
71
0
  int i;
72
73
0
  for (i = 0; flags[i].chr != 0; i++) {
74
0
    if (flags[i].chr == chr)
75
0
      return flags[i].flag;
76
0
  }
77
78
0
  return 0;
79
0
}
80
81
static bool parse_status_flags(struct mbox_sync_mail_context *ctx,
82
             struct message_header_line *hdr,
83
             struct mbox_flag_type *flags_list)
84
0
{
85
0
  enum mail_flags flag;
86
0
  size_t i;
87
0
  bool duplicates = FALSE;
88
89
0
  ctx->mail.flags ^= MBOX_NONRECENT_KLUDGE;
90
0
  for (i = 0; i < hdr->full_value_len; i++) {
91
0
    flag = mbox_flag_find(flags_list, hdr->full_value[i]);
92
0
    if ((ctx->mail.flags & flag) != 0)
93
0
      duplicates = TRUE;
94
0
    else
95
0
      ctx->mail.flags |= flag;
96
0
  }
97
0
  ctx->mail.flags ^= MBOX_NONRECENT_KLUDGE;
98
0
  return duplicates;
99
0
}
100
101
static bool parse_status(struct mbox_sync_mail_context *ctx,
102
       struct message_header_line *hdr)
103
0
{
104
0
  if (parse_status_flags(ctx, hdr, mbox_status_flags))
105
0
    ctx->mail.status_broken = TRUE;
106
0
  ctx->hdr_pos[MBOX_HDR_STATUS] = str_len(ctx->header);
107
0
  return TRUE;
108
0
}
109
110
static bool parse_x_status(struct mbox_sync_mail_context *ctx,
111
         struct message_header_line *hdr)
112
0
{
113
0
  if (parse_status_flags(ctx, hdr, mbox_xstatus_flags))
114
0
    ctx->mail.xstatus_broken = TRUE;
115
0
  ctx->hdr_pos[MBOX_HDR_X_STATUS] = str_len(ctx->header);
116
0
  return TRUE;
117
0
}
118
119
static void
120
parse_imap_keywords_list(struct mbox_sync_mail_context *ctx,
121
                         struct message_header_line *hdr, size_t pos)
122
0
{
123
0
  struct mailbox *box = &ctx->sync_ctx->mbox->box;
124
0
  struct index_mailbox_context *ibox = INDEX_STORAGE_CONTEXT(box);
125
0
  const char *keyword, *error;
126
0
  size_t keyword_start;
127
0
  unsigned int idx, count;
128
129
0
  count = 0;
130
0
  while (pos < hdr->full_value_len) {
131
0
    if (IS_LWSP_LF(hdr->full_value[pos])) {
132
0
                        pos++;
133
0
      continue;
134
0
    }
135
136
    /* read the keyword */
137
0
    keyword_start = pos;
138
0
    for (; pos < hdr->full_value_len; pos++) {
139
0
      if (IS_LWSP_LF(hdr->full_value[pos]))
140
0
        break;
141
0
    }
142
143
    /* add it to index's keyword list if it's not there already */
144
0
    keyword = t_strndup(hdr->full_value + keyword_start,
145
0
            pos - keyword_start);
146
0
    if (mailbox_keyword_is_valid(&ctx->sync_ctx->mbox->box,
147
0
               keyword, &error)) {
148
0
      mail_index_keyword_lookup_or_create(box->index,
149
0
                  keyword, &idx);
150
0
    }
151
0
    count++;
152
0
  }
153
154
0
  if (count != array_count(ibox->keyword_names)) {
155
    /* need to update this list */
156
0
    ctx->imapbase_rewrite = TRUE;
157
0
    ctx->need_rewrite = TRUE;
158
0
  }
159
0
}
160
161
static bool parse_x_imap_base(struct mbox_sync_mail_context *ctx,
162
            struct message_header_line *hdr)
163
0
{
164
0
  size_t i, j, uid_last_pos;
165
0
  uint32_t uid_validity, uid_last;
166
167
0
  if (ctx->seq != 1 || ctx->seen_imapbase ||
168
0
      ctx->sync_ctx->renumber_uids) {
169
    /* Valid only in first message */
170
0
    return FALSE;
171
0
  }
172
173
  /* <uid-validity> 10x<uid-last> */
174
0
  for (i = 0, uid_validity = 0; i < hdr->full_value_len; i++) {
175
0
    if (hdr->full_value[i] < '0' || hdr->full_value[i] > '9') {
176
0
      if (hdr->full_value[i] != ' ')
177
0
        return FALSE;
178
0
      break;
179
0
    }
180
0
    uid_validity = uid_validity * 10 + (hdr->full_value[i] - '0');
181
0
  }
182
183
0
  if (uid_validity == 0) {
184
    /* broken */
185
0
    return FALSE;
186
0
  }
187
188
0
  for (; i < hdr->full_value_len; i++) {
189
0
    if (!IS_LWSP_LF(hdr->full_value[i]))
190
0
      break;
191
0
  }
192
0
  uid_last_pos = i;
193
194
0
  for (uid_last = 0, j = 0; i < hdr->full_value_len; i++, j++) {
195
0
    if (hdr->full_value[i] < '0' || hdr->full_value[i] > '9') {
196
0
      if (!IS_LWSP_LF(hdr->full_value[i]))
197
0
        return FALSE;
198
0
      break;
199
0
    }
200
0
    uid_last = uid_last * 10 + (hdr->full_value[i] - '0');
201
0
  }
202
203
0
  if (j != 10 ||
204
0
      hdr->full_value_offset != ctx->hdr_offset + str_len(ctx->header)) {
205
    /* uid-last field must be exactly 10 characters to make
206
       rewriting it easier. also don't try to do this if some
207
       headers have been removed */
208
0
    ctx->imapbase_rewrite = TRUE;
209
0
    ctx->need_rewrite = TRUE;
210
0
  } else {
211
0
    ctx->last_uid_value_start_pos = uid_last_pos;
212
0
    ctx->sync_ctx->base_uid_last_offset =
213
0
      hdr->full_value_offset + uid_last_pos;
214
0
  }
215
216
0
  if (ctx->sync_ctx->base_uid_validity == 0) {
217
    /* first time parsing this (ie. we're not rewriting).
218
       save the values. */
219
0
    ctx->sync_ctx->base_uid_validity = uid_validity;
220
0
    ctx->sync_ctx->base_uid_last = uid_last;
221
222
0
    if (ctx->sync_ctx->next_uid-1 <= uid_last) {
223
      /* new messages have been added since our last sync.
224
         just update our internal next_uid. */
225
0
      ctx->sync_ctx->next_uid = uid_last+1;
226
0
    } else {
227
      /* we need to rewrite the next-uid */
228
0
      ctx->need_rewrite = TRUE;
229
0
    }
230
0
    i_assert(ctx->sync_ctx->next_uid > ctx->sync_ctx->prev_msg_uid);
231
0
  }
232
233
0
  ctx->hdr_pos[MBOX_HDR_X_IMAPBASE] = str_len(ctx->header);
234
0
  ctx->seen_imapbase = TRUE;
235
236
0
  T_BEGIN {
237
0
    parse_imap_keywords_list(ctx, hdr, i);
238
0
  } T_END;
239
0
  parse_trailing_whitespace(ctx, hdr);
240
0
  return TRUE;
241
0
}
242
243
static bool parse_x_imap(struct mbox_sync_mail_context *ctx,
244
       struct message_header_line *hdr)
245
0
{
246
0
  if (!parse_x_imap_base(ctx, hdr))
247
0
    return FALSE;
248
249
  /* this is the c-client style "FOLDER INTERNAL DATA" message.
250
     skip it. */
251
0
  ctx->mail.pseudo = TRUE;
252
0
  return TRUE;
253
0
}
254
255
static bool parse_x_keywords_real(struct mbox_sync_mail_context *ctx,
256
          struct message_header_line *hdr)
257
0
{
258
0
  struct mailbox *box = &ctx->sync_ctx->mbox->box;
259
0
  ARRAY_TYPE(keyword_indexes) keyword_list;
260
0
  const unsigned int *list;
261
0
  string_t *keyword;
262
0
  size_t keyword_start;
263
0
  unsigned int i, idx, count;
264
0
  size_t pos;
265
266
0
  if (array_is_created(&ctx->mail.keywords))
267
0
    return FALSE; /* duplicate header, delete */
268
269
  /* read keyword indexes to temporary array first */
270
0
  keyword = t_str_new(128);
271
0
  t_array_init(&keyword_list, 16);
272
273
0
  for (pos = 0; pos < hdr->full_value_len; ) {
274
0
    if (IS_LWSP_LF(hdr->full_value[pos])) {
275
0
                        pos++;
276
0
      continue;
277
0
    }
278
279
    /* read the keyword string */
280
0
    keyword_start = pos;
281
0
    for (; pos < hdr->full_value_len; pos++) {
282
0
      if (IS_LWSP_LF(hdr->full_value[pos]))
283
0
        break;
284
0
    }
285
286
0
    str_truncate(keyword, 0);
287
0
    str_append_data(keyword, hdr->full_value + keyword_start,
288
0
        pos - keyword_start);
289
0
    if (!mail_index_keyword_lookup(box->index, str_c(keyword),
290
0
                 &idx)) {
291
      /* keyword wasn't found. that means the sent mail
292
         originally contained X-Keywords header. Delete it. */
293
0
      return FALSE;
294
0
    }
295
296
    /* check that the keyword isn't already added there.
297
       we don't want duplicates. */
298
0
    list = array_get(&keyword_list, &count);
299
0
    for (i = 0; i < count; i++) {
300
0
      if (list[i] == idx)
301
0
        break;
302
0
    }
303
304
0
    if (i == count)
305
0
      array_push_back(&keyword_list, &idx);
306
0
  }
307
308
  /* once we know how many keywords there are, we can allocate the array
309
     from mail_keyword_pool without wasting memory. */
310
0
  if (array_count(&keyword_list) > 0) {
311
0
    p_array_init(&ctx->mail.keywords,
312
0
           ctx->sync_ctx->mail_keyword_pool,
313
0
           array_count(&keyword_list));
314
0
    array_append_array(&ctx->mail.keywords, &keyword_list);
315
0
  }
316
317
0
  ctx->hdr_pos[MBOX_HDR_X_KEYWORDS] = str_len(ctx->header);
318
0
  parse_trailing_whitespace(ctx, hdr);
319
0
  return TRUE;
320
0
}
321
322
static bool parse_x_keywords(struct mbox_sync_mail_context *ctx,
323
           struct message_header_line *hdr)
324
0
{
325
0
  bool ret;
326
327
0
  T_BEGIN {
328
0
    ret = parse_x_keywords_real(ctx, hdr);
329
0
  } T_END;
330
0
  return ret;
331
0
}
332
333
static bool parse_x_uid(struct mbox_sync_mail_context *ctx,
334
      struct message_header_line *hdr)
335
0
{
336
0
  uint32_t value = 0;
337
0
  size_t i;
338
339
0
  if (ctx->mail.uid != 0) {
340
    /* duplicate */
341
0
    return FALSE;
342
0
  }
343
344
0
  for (i = 0; i < hdr->full_value_len; i++) {
345
0
    if (hdr->full_value[i] < '0' || hdr->full_value[i] > '9')
346
0
      break;
347
0
    value = value*10 + (hdr->full_value[i] - '0');
348
0
  }
349
350
0
  for (; i < hdr->full_value_len; i++) {
351
0
    if (!IS_LWSP_LF(hdr->full_value[i])) {
352
      /* broken value */
353
0
      return FALSE;
354
0
    }
355
0
  }
356
357
0
  if (ctx->sync_ctx == NULL) {
358
    /* we're in mbox_sync_parse_match_mail().
359
       don't do any extra checks. */
360
0
    ctx->mail.uid = value;
361
0
    return TRUE;
362
0
  }
363
364
0
  if (ctx->seq == 1 && !ctx->seen_imapbase) {
365
    /* Don't bother allowing X-UID before X-IMAPbase
366
       header. c-client doesn't allow it either, and this
367
       way the UID doesn't have to be reset if X-IMAPbase
368
       header isn't what we expect it to be. */
369
0
    return FALSE;
370
0
  }
371
372
0
  if (value == ctx->sync_ctx->next_uid) {
373
    /* X-UID is the next expected one. allow it because
374
       we'd just use this UID anyway. X-IMAPbase header
375
       still needs to be updated for this. */
376
0
    ctx->sync_ctx->next_uid++;
377
0
  } else if (value > ctx->sync_ctx->next_uid) {
378
    /* UID is larger than expected. Don't allow it because
379
       incoming mails can contain untrusted X-UID fields,
380
       causing possibly DoS if the UIDs get large enough. */
381
0
    ctx->mail.uid_broken = TRUE;
382
0
    return FALSE;
383
0
  }
384
385
0
  if (value <= ctx->sync_ctx->prev_msg_uid) {
386
    /* broken - UIDs must be growing */
387
0
    ctx->mail.uid_broken = TRUE;
388
0
    return FALSE;
389
0
  }
390
391
0
  ctx->mail.uid = value;
392
  /* if we had multiple X-UID headers, we could have
393
     uid_broken=TRUE here. */
394
0
  ctx->mail.uid_broken = FALSE;
395
396
0
  if (ctx->sync_ctx->dest_first_mail && ctx->seq != 1) {
397
    /* if we're expunging the first mail, delete this header since
398
       otherwise X-IMAPbase header would be added after this, which
399
       we don't like */
400
0
    return FALSE;
401
0
  }
402
403
0
  ctx->hdr_pos[MBOX_HDR_X_UID] = str_len(ctx->header);
404
0
  ctx->parsed_uid = value;
405
0
  parse_trailing_whitespace(ctx, hdr);
406
0
  return TRUE;
407
0
}
408
409
static bool parse_content_length(struct mbox_sync_mail_context *ctx,
410
         struct message_header_line *hdr)
411
0
{
412
0
  uoff_t value = 0;
413
0
  size_t i;
414
415
0
  if (ctx->content_length != UOFF_T_MAX) {
416
    /* duplicate */
417
0
    return FALSE;
418
0
  }
419
420
0
  for (i = 0; i < hdr->full_value_len; i++) {
421
0
    if (hdr->full_value[i] < '0' || hdr->full_value[i] > '9')
422
0
      break;
423
0
    value = value*10 + (hdr->full_value[i] - '0');
424
0
  }
425
426
0
  for (; i < hdr->full_value_len; i++) {
427
0
    if (!IS_LWSP_LF(hdr->full_value[i])) {
428
      /* broken value */
429
0
      return FALSE;
430
0
    }
431
0
  }
432
433
0
  ctx->content_length = value;
434
0
  return TRUE;
435
0
}
436
437
static struct mbox_sync_header_func header_funcs[] = {
438
  { "Content-Length", parse_content_length },
439
  { "Status", parse_status },
440
  { "X-IMAP", parse_x_imap },
441
  { "X-IMAPbase", parse_x_imap_base },
442
  { "X-Keywords", parse_x_keywords },
443
  { "X-Status", parse_x_status },
444
  { "X-UID", parse_x_uid }
445
};
446
447
static int mbox_sync_bsearch_header_func_cmp(const void *p1, const void *p2)
448
0
{
449
0
  const char *key = p1;
450
0
  const struct mbox_sync_header_func *func = p2;
451
452
0
  return strcasecmp(key, func->header);
453
0
}
454
455
int mbox_sync_parse_next_mail(struct istream *input,
456
            struct mbox_sync_mail_context *ctx)
457
0
{
458
0
  struct mbox_sync_context *sync_ctx = ctx->sync_ctx;
459
0
  struct message_header_parser_ctx *hdr_ctx;
460
0
  struct message_header_line *hdr;
461
0
  struct mbox_sync_header_func *func;
462
0
  struct mbox_md5_context *mbox_md5_ctx;
463
0
  size_t line_start_pos;
464
0
  int i, ret;
465
466
0
  ctx->hdr_offset = ctx->mail.offset;
467
0
  ctx->mail.flags = MAIL_RECENT; /* default to having recent flag */
468
469
0
        ctx->header_first_change = SIZE_MAX;
470
0
  ctx->header_last_change = 0;
471
472
0
  for (i = 0; i < MBOX_HDR_COUNT; i++)
473
0
    ctx->hdr_pos[i] = SIZE_MAX;
474
475
0
  ctx->content_length = UOFF_T_MAX;
476
0
  str_truncate(ctx->header, 0);
477
478
0
        mbox_md5_ctx = ctx->sync_ctx->mbox->md5_v.init();
479
480
0
        line_start_pos = 0;
481
0
  hdr_ctx = message_parse_header_init(input, NULL, 0);
482
0
  while ((ret = message_parse_header_next(hdr_ctx, &hdr)) > 0) {
483
0
    if (hdr->eoh) {
484
0
      ctx->have_eoh = TRUE;
485
0
      break;
486
0
    }
487
488
0
    if (!hdr->continued) {
489
0
      line_start_pos = str_len(ctx->header);
490
0
      str_append(ctx->header, hdr->name);
491
0
      str_append_data(ctx->header, hdr->middle, hdr->middle_len);
492
0
    }
493
494
0
    func = bsearch(hdr->name, header_funcs,
495
0
             N_ELEMENTS(header_funcs), sizeof(*header_funcs),
496
0
             mbox_sync_bsearch_header_func_cmp);
497
498
0
    if (func != NULL) {
499
0
      if (hdr->continues) {
500
0
        hdr->use_full_value = TRUE;
501
0
        continue;
502
0
      }
503
504
0
      if (!func->func(ctx, hdr)) {
505
        /* this header is broken, remove it */
506
0
        ctx->need_rewrite = TRUE;
507
0
        str_truncate(ctx->header, line_start_pos);
508
0
        if (ctx->header_first_change == SIZE_MAX) {
509
0
          ctx->header_first_change =
510
0
            line_start_pos;
511
0
        }
512
0
        continue;
513
0
      }
514
0
      buffer_append(ctx->header, hdr->full_value,
515
0
              hdr->full_value_len);
516
0
    } else {
517
0
      ctx->sync_ctx->mbox->md5_v.more(mbox_md5_ctx, hdr);
518
0
      buffer_append(ctx->header, hdr->value,
519
0
              hdr->value_len);
520
0
    }
521
0
    if (!hdr->no_newline) {
522
0
      if (hdr->crlf_newline)
523
0
        str_append_c(ctx->header, '\r');
524
0
      str_append_c(ctx->header, '\n');
525
0
    }
526
0
  }
527
0
  i_assert(ret != 0);
528
0
  message_parse_header_deinit(&hdr_ctx);
529
530
0
  ctx->sync_ctx->mbox->md5_v.finish(mbox_md5_ctx, ctx->hdr_md5_sum);
531
532
0
  if ((ctx->seq == 1 && !ctx->seen_imapbase) ||
533
0
      (ctx->seq > 1 && sync_ctx->dest_first_mail)) {
534
    /* missing X-IMAPbase */
535
0
    ctx->need_rewrite = TRUE;
536
0
    if (sync_ctx->base_uid_validity == 0) {
537
      /* figure out a new UIDVALIDITY for us. */
538
0
      sync_ctx->base_uid_validity =
539
0
        sync_ctx->hdr->uid_validity != 0 &&
540
0
        !sync_ctx->renumber_uids ?
541
0
        sync_ctx->hdr->uid_validity :
542
0
        I_MAX(ioloop_time32, 1);
543
0
    }
544
0
  }
545
546
0
  ctx->body_offset = input->v_offset;
547
0
  if (input->stream_errno != 0) {
548
0
    mbox_sync_set_critical(ctx->sync_ctx, "read(%s) failed: %s",
549
0
      i_stream_get_name(input), i_stream_get_error(input));
550
0
    return -1;
551
0
  }
552
0
  return 0;
553
0
}
554
555
bool mbox_sync_parse_match_mail(struct mbox_mailbox *mbox,
556
        struct mail_index_view *view, uint32_t seq)
557
0
{
558
0
        struct mbox_sync_mail_context ctx;
559
0
  struct message_header_parser_ctx *hdr_ctx;
560
0
  struct message_header_line *hdr;
561
0
  struct header_func *func;
562
0
  struct mbox_md5_context *mbox_md5_ctx;
563
0
  const void *data;
564
0
  bool expunged;
565
0
  uint32_t uid;
566
0
  int ret;
567
568
  /* we only wish to be sure that this mail actually is what we expect
569
     it to be. If there's X-UID header and it matches our UID, we use it.
570
     Otherwise it could mean that the X-UID header is invalid and it's
571
     just not yet been rewritten. In that case use MD5 sum, if it
572
     exists. */
573
574
0
  mail_index_lookup_uid(view, seq, &uid);
575
0
  i_zero(&ctx);
576
0
        mbox_md5_ctx = mbox->md5_v.init();
577
578
0
  hdr_ctx = message_parse_header_init(mbox->mbox_stream, NULL, 0);
579
0
  while ((ret = message_parse_header_next(hdr_ctx, &hdr)) > 0) {
580
0
    if (hdr->eoh)
581
0
      break;
582
583
0
    func = bsearch(hdr->name, header_funcs,
584
0
             N_ELEMENTS(header_funcs), sizeof(*header_funcs),
585
0
             mbox_sync_bsearch_header_func_cmp);
586
0
    if (func != NULL) {
587
0
      if (strcasecmp(hdr->name, "X-UID") == 0) {
588
0
        if (hdr->continues) {
589
0
          hdr->use_full_value = TRUE;
590
0
          continue;
591
0
        }
592
0
        (void)parse_x_uid(&ctx, hdr);
593
594
0
        if (ctx.mail.uid == uid)
595
0
          break;
596
0
      }
597
0
    } else {
598
0
      mbox->md5_v.more(mbox_md5_ctx, hdr);
599
0
    }
600
0
  }
601
0
  i_assert(ret != 0);
602
0
  message_parse_header_deinit(&hdr_ctx);
603
604
0
  mbox->md5_v.finish(mbox_md5_ctx, ctx.hdr_md5_sum);
605
606
0
  if (ctx.mail.uid == uid)
607
0
    return TRUE;
608
609
  /* match by MD5 sum */
610
0
  mbox->mbox_save_md5 = TRUE;
611
612
0
  mail_index_lookup_ext(view, seq, mbox->md5hdr_ext_idx,
613
0
            &data, &expunged);
614
0
  return data == NULL ? 0 :
615
0
    memcmp(data, ctx.hdr_md5_sum, 16) == 0;
616
0
}