1 /* lcb_verify.c -- replication-based backup api - verify functions
2  *
3  * Copyright (c) 1994-2015 Carnegie Mellon University.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * 3. The name "Carnegie Mellon University" must not be used to
18  *    endorse or promote products derived from this software without
19  *    prior written permission. For permission or any legal
20  *    details, please contact
21  *      Carnegie Mellon University
22  *      Center for Technology Transfer and Enterprise Creation
23  *      4615 Forbes Avenue
24  *      Suite 302
25  *      Pittsburgh, PA  15213
26  *      (412) 268-7393, fax: (412) 268-7395
27  *      innovation@andrew.cmu.edu
28  *
29  * 4. Redistributions of any form whatsoever must retain the following
30  *    acknowledgment:
31  *    "This product includes software developed by Computing Services
32  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33  *
34  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41  *
42  */
43 #include <assert.h>
44 #include <syslog.h>
45 
46 #include "lib/gzuncat.h"
47 #include "lib/hash.h"
48 #include "lib/map.h"
49 #include "lib/xmalloc.h"
50 #include "lib/xsha1.h"
51 
52 #include "backup/backup.h"
53 
54 #define LIBCYRUS_BACKUP_SOURCE /* this file is part of libcyrus_backup */
55 #include "backup/lcb_internal.h"
56 #include "backup/lcb_sqlconsts.h"
57 
58 static int verify_chunk_checksums(struct backup *backup, struct backup_chunk *chunk,
59                                   struct gzuncat *gzuc, int verbose,
60                                   FILE *out);
61 static int verify_chunk_messages(struct backup *backup, struct backup_chunk *chunk,
62                                  struct gzuncat *gzuc, unsigned level,
63                                  int verbose, FILE *out);
64 static int verify_chunk_mailbox_links(struct backup *backup, struct backup_chunk *chunk,
65                                       struct gzuncat *gzuc, int verbose,
66                                       FILE *out);
67 
backup_verify(struct backup * backup,unsigned level,int verbose,FILE * out)68 EXPORTED int backup_verify(struct backup *backup, unsigned level, int verbose, FILE *out)
69 {
70     struct backup_chunk_list *chunk_list = NULL;
71     struct gzuncat *gzuc = NULL;
72     int r = 0;
73 
74     /* don't double-verify last checksum when verifying all */
75     if ((level & BACKUP_VERIFY_ALL_CHECKSUMS))
76         level &= ~BACKUP_VERIFY_LAST_CHECKSUM;
77 
78     /* don't double-verify message links when verifying message guids */
79     if ((level & BACKUP_VERIFY_MESSAGE_GUIDS))
80         level &= ~BACKUP_VERIFY_MESSAGE_LINKS;
81 
82     chunk_list = backup_get_chunks(backup);
83     if (!chunk_list || !chunk_list->count) goto done;
84 
85     gzuc = gzuc_new(backup->fd);
86     if (!gzuc) {
87         r = -1;
88         goto done;
89     }
90 
91     if (!r && (level & BACKUP_VERIFY_LAST_CHECKSUM))
92         r = verify_chunk_checksums(backup, chunk_list->tail, gzuc, verbose, out);
93 
94     if (!r && level > BACKUP_VERIFY_LAST_CHECKSUM) {
95         struct backup_chunk *chunk = chunk_list->head;
96         while (!r && chunk) {
97             if (!r && (level & BACKUP_VERIFY_ALL_CHECKSUMS))
98                 r = verify_chunk_checksums(backup, chunk, gzuc, verbose, out);
99 
100             if (!r && (level & BACKUP_VERIFY_MESSAGES))
101                 r = verify_chunk_messages(backup, chunk, gzuc, level, verbose, out);
102 
103             if (!r && (level & BACKUP_VERIFY_MAILBOX_LINKS))
104                 r = verify_chunk_mailbox_links(backup, chunk, gzuc, verbose, out);
105 
106             chunk = chunk->next;
107         }
108     }
109 
110 done:
111     if (gzuc) gzuc_free(&gzuc);
112     if (chunk_list) backup_chunk_list_free(&chunk_list);
113     return r;
114 }
115 
verify_chunk_checksums(struct backup * backup,struct backup_chunk * chunk,struct gzuncat * gzuc,int verbose,FILE * out)116 static int verify_chunk_checksums(struct backup *backup, struct backup_chunk *chunk,
117                                   struct gzuncat *gzuc, int verbose, FILE *out)
118 {
119     int r;
120 
121     if (out && verbose)
122         fprintf(out, "checking chunk %d checksums...\n", chunk->id);
123 
124     /* validate file-prior-to-this-chunk checksum */
125     if (out && verbose > 1)
126         fprintf(out, "  checking file checksum...\n");
127     char file_sha1[2 * SHA1_DIGEST_LENGTH + 1];
128     sha1_file(backup->fd, backup->data_fname, chunk->offset, file_sha1);
129     r = strncmp(chunk->file_sha1, file_sha1, sizeof(file_sha1));
130     if (r) {
131         syslog(LOG_DEBUG, "%s: %s (chunk %d) file checksum mismatch: %s on disk, %s in index\n",
132                 __func__, backup->data_fname, chunk->id, file_sha1, chunk->file_sha1);
133         if (out)
134             fprintf(out, "file checksum mismatch for chunk %d: %s on disk, %s in index\n",
135                     chunk->id, file_sha1, chunk->file_sha1);
136         goto done;
137     }
138 
139     /* validate data-within-this-chunk checksum */
140     // FIXME length and data_sha1 are set at backup_append_end.
141     //       detect and correctly report case where this hasn't occurred.
142     if (out && verbose > 1)
143         fprintf(out, "  checking data length\n");
144     char buf[8192]; /* FIXME whatever */
145     size_t len = 0;
146     SHA_CTX sha_ctx;
147     SHA1_Init(&sha_ctx);
148     gzuc_member_start_from(gzuc, chunk->offset);
149     while (!gzuc_member_eof(gzuc)) {
150         ssize_t n = gzuc_read(gzuc, buf, sizeof(buf));
151         if (n >= 0) {
152             SHA1_Update(&sha_ctx, buf, n);
153             len += n;
154         }
155     }
156     gzuc_member_end(gzuc, NULL);
157     if (len != chunk->length) {
158         syslog(LOG_DEBUG, "%s: %s (chunk %d) data length mismatch: "
159                         SIZE_T_FMT " on disk,"
160                         SIZE_T_FMT " in index\n",
161                 __func__, backup->data_fname, chunk->id, len, chunk->length);
162         if (out)
163             fprintf(out, "data length mismatch for chunk %d: "
164                          SIZE_T_FMT " on disk,"
165                          SIZE_T_FMT " in index\n",
166                     chunk->id, len, chunk->length);
167         r = -1;
168         goto done;
169     }
170 
171     if (out && verbose > 1)
172         fprintf(out, "  checking data checksum...\n");
173     unsigned char sha1_raw[SHA1_DIGEST_LENGTH];
174     char data_sha1[2 * SHA1_DIGEST_LENGTH + 1];
175     SHA1_Final(sha1_raw, &sha_ctx);
176     r = bin_to_hex(sha1_raw, SHA1_DIGEST_LENGTH, data_sha1, BH_LOWER);
177     assert(r == 2 * SHA1_DIGEST_LENGTH);
178     r = strncmp(chunk->data_sha1, data_sha1, sizeof(data_sha1));
179     if (r) {
180         syslog(LOG_DEBUG, "%s: %s (chunk %d) data checksum mismatch: %s on disk, %s in index\n",
181                 __func__, backup->data_fname, chunk->id, data_sha1, chunk->data_sha1);
182         if (out)
183             fprintf(out, "data checksum mismatch for chunk %d: %s on disk, %s in index\n",
184                     chunk->id, data_sha1, chunk->data_sha1);
185         goto done;
186     }
187 
188 done:
189     syslog(LOG_DEBUG, "%s: checksum %s!\n", __func__, r ? "failed" : "passed");
190     if (out && verbose)
191         fprintf(out, "%s\n", r ? "error" : "ok");
192     return r;
193 }
194 
_prot_fill_cb(unsigned char * buf,size_t len,void * rock)195 static ssize_t _prot_fill_cb(unsigned char *buf, size_t len, void *rock)
196 {
197     struct gzuncat *gzuc = (struct gzuncat *) rock;
198     return gzuc_read(gzuc, buf, len);
199 }
200 
201 struct verify_message_rock {
202     struct gzuncat *gzuc;
203     int verify_guid;
204     struct dlist *cached_dlist;
205     off_t cached_offset;
206     int verbose;
207     FILE *out;
208 };
209 
_verify_message_cb(const struct backup_message * message,void * rock)210 static int _verify_message_cb(const struct backup_message *message, void *rock)
211 {
212     struct verify_message_rock *vmrock = (struct verify_message_rock *) rock;
213     struct dlist *dl = NULL;
214     struct dlist *di = NULL;
215     FILE *out = vmrock->out;
216     int r;
217 
218     /* cache the dlist so that multiple reads from the same offset don't
219      * cause expensive reverse seeks in decompression stream
220      */
221     if (!vmrock->cached_dlist || vmrock->cached_offset != message->offset) {
222         if (vmrock->cached_dlist) {
223             dlist_unlink_files(vmrock->cached_dlist);
224             dlist_free(&vmrock->cached_dlist);
225         }
226 
227         r = gzuc_seekto(vmrock->gzuc, message->offset);
228         if (r) return r;
229 
230         struct protstream *ps = prot_readcb(_prot_fill_cb, vmrock->gzuc);
231         prot_setisclient(ps, 1); /* don't sync literals */
232         r = parse_backup_line(ps, NULL, NULL, &dl);
233 
234         if (r == EOF) {
235             const char *error = prot_error(ps);
236             if (error && 0 != strcmp(error, PROT_EOF_STRING)) {
237                 syslog(LOG_ERR,
238                        "%s: error reading message %i at offset " OFF_T_FMT ", byte %i: %s",
239                        __func__, message->id, message->offset, prot_bytes_in(ps), error);
240                 if (out)
241                     fprintf(out, "error reading message %i at offset " OFF_T_FMT ", byte %i: %s",
242                             message->id, message->offset, prot_bytes_in(ps), error);
243             }
244             prot_free(ps);
245             return r;
246         }
247 
248         prot_free(ps);
249 
250         vmrock->cached_dlist = dl;
251         vmrock->cached_offset = message->offset;
252     }
253     else {
254         dl = vmrock->cached_dlist;
255     }
256 
257     r = strcmp(dl->name, "MESSAGE");
258     if (r) return r;
259 
260     r = -1;
261     for (di = dl->head; di; di = di->next) {
262         struct message_guid *guid = NULL;
263         const char *fname = NULL;
264 
265         if (!dlist_tofile(di, NULL, &guid, NULL, &fname))
266             continue;
267 
268         r = message_guid_cmp(guid, message->guid);
269         if (!r) {
270             if (vmrock->verify_guid) {
271                 const char *msg_base = NULL;
272                 size_t msg_len = 0;
273                 struct message_guid computed_guid;
274                 int fd;
275 
276                 fd = open(fname, O_RDWR);
277                 if (fd != -1) {
278                     map_refresh(fd, 1, &msg_base, &msg_len, MAP_UNKNOWN_LEN, fname, NULL);
279 
280                     message_guid_generate(&computed_guid, msg_base, msg_len);
281                     r = message_guid_cmp(&computed_guid, message->guid);
282                     if (r && out)
283                         fprintf(out, "guid mismatch for message %i\n", message->id);
284 
285                     map_free(&msg_base, &msg_len);
286                     close(fd);
287                 }
288                 else {
289                     syslog(LOG_ERR, "IOERROR: %s open %s: %m", __func__, fname);
290                     if (out)
291                         fprintf(out, "error reading staging file for message %i\n", message->id);
292                     r = -1;
293                 }
294             }
295             break;
296         }
297     }
298 
299     return r;
300 }
301 
302 /* verify that each message exists within the chunk the index claims */
verify_chunk_messages(struct backup * backup,struct backup_chunk * chunk,struct gzuncat * gzuc,unsigned level,int verbose,FILE * out)303 static int verify_chunk_messages(struct backup *backup, struct backup_chunk *chunk,
304                                  struct gzuncat *gzuc, unsigned level,
305                                  int verbose, FILE *out)
306 {
307     int r;
308 
309     struct verify_message_rock vmrock = {
310         gzuc,
311         (level & BACKUP_VERIFY_MESSAGE_GUIDS),
312         NULL,
313         0,
314         verbose,
315         out,
316     };
317 
318     if (out && verbose)
319         fprintf(out, "checking chunk %d messages...\n", chunk->id);
320 
321     r = gzuc_member_start_from(gzuc, chunk->offset);
322     if (!r) {
323         r = backup_message_foreach(backup, chunk->id, NULL,
324                                    _verify_message_cb, &vmrock);
325         gzuc_member_end(gzuc, NULL);
326     }
327 
328     if (vmrock.cached_dlist) {
329         dlist_unlink_files(vmrock.cached_dlist);
330         dlist_free(&vmrock.cached_dlist);
331     }
332 
333     syslog(LOG_DEBUG, "%s: chunk %d %s!\n", __func__, chunk->id,
334             r ? "failed" : "passed");
335     if (out && verbose)
336         fprintf(out, "%s\n", r ? "error" : "ok");
337 
338     return r;
339 }
340 
mailbox_matches(const struct backup_mailbox * mailbox,struct dlist * dlist)341 static int mailbox_matches(const struct backup_mailbox *mailbox,
342                            struct dlist *dlist)
343 {
344     const char *mboxname = NULL;
345     uint32_t last_uid = 0;
346     modseq_t highestmodseq = 0;
347     uint32_t recentuid = 0;
348     time_t recenttime = 0;
349     time_t last_appenddate = 0;
350     uint32_t uidvalidity = 0;
351     const char *partition = NULL;
352     const char *acl = NULL;
353     const char *options = NULL;
354     modseq_t xconvmodseq = 0;
355     struct synccrcs synccrcs = { 0, 0 };
356 
357     if (!dlist_getatom(dlist, "MBOXNAME", &mboxname)
358         || strcmp(mboxname, mailbox->mboxname) != 0)
359         return 0;
360 
361     if (!dlist_getnum32(dlist, "LAST_UID", &last_uid)
362         || last_uid != mailbox->last_uid)
363         return 0;
364 
365     if (!dlist_getnum64(dlist, "HIGHESTMODSEQ", &highestmodseq)
366         || highestmodseq != mailbox->highestmodseq)
367         return 0;
368 
369     if (!dlist_getnum32(dlist, "RECENTUID", &recentuid)
370         || recentuid != mailbox->recentuid)
371         return 0;
372 
373     if (!dlist_getdate(dlist, "RECENTTIME", &recenttime)
374         || recenttime != mailbox->recenttime)
375         return 0;
376 
377     if (!dlist_getdate(dlist, "LAST_APPENDDATE", &last_appenddate)
378         || last_appenddate != mailbox->last_appenddate)
379         return 0;
380 
381     if (!dlist_getnum32(dlist, "UIDVALIDITY", &uidvalidity)
382         || uidvalidity != mailbox->uidvalidity)
383         return 0;
384 
385     if (!dlist_getatom(dlist, "PARTITION", &partition)
386         || strcmp(partition, mailbox->partition) != 0)
387         return 0;
388 
389     if (!dlist_getatom(dlist, "ACL", &acl)
390         || strcmp(acl, mailbox->acl) != 0)
391         return 0;
392 
393     if (!dlist_getatom(dlist, "OPTIONS", &options)
394         || strcmp(options, mailbox->options) != 0)
395         return 0;
396 
397     /* optional */
398     dlist_getnum64(dlist, "XCONVMODSEQ", &xconvmodseq);
399     if (xconvmodseq != mailbox->xconvmodseq)
400         return 0;
401 
402     /* CRCs */
403     dlist_getnum32(dlist, "SYNC_CRC", &synccrcs.basic);
404     dlist_getnum32(dlist, "SYNC_CRC_ANNOT", &synccrcs.annot);
405     if (synccrcs.basic != mailbox->sync_crc)
406         return 0;
407     if (synccrcs.annot != mailbox->sync_crc_annot)
408         return 0;
409 
410     syslog(LOG_DEBUG, "%s: %s matches!\n", __func__, mailbox->uniqueid);
411     return 1;
412 }
413 
mailbox_message_matches(const struct backup_mailbox_message * mailbox_message,struct dlist * dlist)414 static int mailbox_message_matches(const struct backup_mailbox_message *mailbox_message,
415                                    struct dlist *dlist)
416 {
417     modseq_t modseq;
418     uint32_t last_updated;
419     uint32_t internaldate;
420     uint32_t size;
421     struct message_guid *guid;
422 
423     if (!dlist_getnum64(dlist, "MODSEQ", &modseq)
424         || modseq != mailbox_message->modseq)
425         return 0;
426 
427     if (!dlist_getnum32(dlist, "LAST_UPDATED", &last_updated)
428         || (time_t) last_updated != mailbox_message->last_updated)
429         return 0;
430 
431     if (!dlist_getnum32(dlist, "INTERNALDATE", &internaldate)
432         || (time_t) internaldate != mailbox_message->internaldate)
433         return 0;
434 
435     if (!dlist_getnum32(dlist, "SIZE", &size)
436         || size != mailbox_message->size)
437         return 0;
438 
439     if (!dlist_getguid(dlist, "GUID", &guid)
440         || !message_guid_equal(guid, &mailbox_message->guid))
441         return 0;
442 
443     syslog(LOG_DEBUG, "%s: %s:%u matches!\n", __func__,
444             mailbox_message->mailbox_uniqueid, mailbox_message->uid);
445     return 1;
446 }
447 
448 /* verify that the matching MAILBOX exists within the claimed chunk
449  * for each mailbox or mailbox_message in the index
450  */
verify_chunk_mailbox_links(struct backup * backup,struct backup_chunk * chunk,struct gzuncat * gzuc,int verbose,FILE * out)451 static int verify_chunk_mailbox_links(struct backup *backup, struct backup_chunk *chunk,
452                                       struct gzuncat *gzuc, int verbose, FILE *out)
453 {
454     /*
455      *   get list of mailboxes in chunk
456      *   get list of mailbox_messages in chunk
457      *   index mailboxes list by uniqueid
458      *   index mailbox_messages list by uniqueid:uid
459      *   open chunk
460      *   foreach line in chunk
461      *     read dlist
462      *     skip if it's not a mailbox
463      *     if details in dlist match details in mailbox
464      *       remove from mailbox list/index
465      *     foreach record in dlist
466      *       if details in dlist match details in mailbox_message
467      *       remove from mailbox_message list/index
468      *   failed if either list of mailboxes or list of mailbox_messages is not empty
469      */
470 
471     struct backup_mailbox_list *mailbox_list = NULL;
472     struct backup_mailbox_message_list *mailbox_message_list = NULL;
473     hash_table mailbox_list_index = HASH_TABLE_INITIALIZER;
474     hash_table mailbox_message_list_index = HASH_TABLE_INITIALIZER;
475     struct backup_mailbox *mailbox = NULL;
476     struct backup_mailbox_message *mailbox_message = NULL;
477     int r;
478 
479     if (out && verbose)
480         fprintf(out, "checking chunk %d mailbox links...\n", chunk->id);
481 
482     mailbox_list = backup_get_mailboxes(backup, chunk->id, BACKUP_MAILBOX_NO_RECORDS);
483     mailbox_message_list = backup_get_mailbox_messages(backup, chunk->id);
484 
485     if (mailbox_list->count == 0 && mailbox_message_list->count == 0) {
486         /* nothing we care about in this chunk */
487         free(mailbox_list);
488         free(mailbox_message_list);
489         if (out && verbose)
490             fprintf(out, "ok\n");
491         return 0;
492     }
493 
494     /* XXX consider whether the two hashes should use pools */
495 
496     if (mailbox_list->count) {
497         /* build an index of the mailbox list */
498         construct_hash_table(&mailbox_list_index, mailbox_list->count, 0);
499         mailbox = mailbox_list->head;
500         while (mailbox) {
501             hash_insert(mailbox->uniqueid, mailbox, &mailbox_list_index);
502             mailbox = mailbox->next;
503         }
504     }
505 
506     if (mailbox_message_list->count) {
507         /* build an index of the mailbox message list */
508         construct_hash_table(&mailbox_message_list_index,
509                              mailbox_message_list->count, 0);
510         mailbox_message = mailbox_message_list->head;
511         while (mailbox_message) {
512             char keybuf[1024]; // FIXME whatever
513             snprintf(keybuf, sizeof(keybuf), "%s:%d",
514                      mailbox_message->mailbox_uniqueid, mailbox_message->uid);
515             hash_insert(keybuf, mailbox_message, &mailbox_message_list_index);
516             mailbox_message = mailbox_message->next;
517         }
518     }
519 
520     r = gzuc_member_start_from(gzuc, chunk->offset);
521     if (r) {
522         syslog(LOG_ERR, "%s: error reading chunk %i at offset " OFF_T_FMT ": %s",
523                         __func__, chunk->id, chunk->offset, zError(r));
524         if (out)
525             fprintf(out, "error reading chunk %i at offset " OFF_T_FMT ": %s",
526                     chunk->id, chunk->offset, zError(r));
527         goto done;
528     }
529     struct protstream *ps = prot_readcb(_prot_fill_cb, gzuc);
530     prot_setisclient(ps, 1); /* don't sync literals */
531 
532     struct buf cmd = BUF_INITIALIZER;
533     while (1) {
534         struct dlist *dl = NULL;
535         struct dlist *record = NULL;
536         struct dlist *di = NULL;
537         const char *uniqueid = NULL;
538 
539         int c = parse_backup_line(ps, NULL, &cmd, &dl);
540         if (c == EOF) {
541             const char *error = prot_error(ps);
542             if (error && 0 != strcmp(error, PROT_EOF_STRING)) {
543                 syslog(LOG_ERR,
544                        "%s: error reading chunk %i data at offset " OFF_T_FMT ", byte %i: %s",
545                        __func__, chunk->id, chunk->offset, prot_bytes_in(ps), error);
546                 if (out)
547                     fprintf(out, "error reading chunk %i data at offset " OFF_T_FMT ", byte %i: %s",
548                             chunk->id, chunk->offset, prot_bytes_in(ps), error);
549                 r = EOF;
550             }
551             break;
552         }
553 
554         if (strcmp(buf_cstring(&cmd), "APPLY") != 0)
555             goto next_line;
556 
557         if (strcmp(dl->name, "MAILBOX") != 0)
558             goto next_line;
559 
560         if (!dlist_getatom(dl, "UNIQUEID", &uniqueid))
561             goto next_line;
562 
563         if (mailbox_list->count) {
564             mailbox = (struct backup_mailbox *) hash_lookup(uniqueid, &mailbox_list_index);
565 
566             if (mailbox && mailbox_matches(mailbox, dl)) {
567                 backup_mailbox_list_remove(mailbox_list, mailbox);
568                 hash_del(uniqueid, &mailbox_list_index);
569                 backup_mailbox_free(&mailbox);
570             }
571         }
572 
573         if (mailbox_message_list->count) {
574             if (!dlist_getlist(dl, "RECORD", &record))
575                 goto next_line;
576 
577             for (di = record->head; di; di = di->next) {
578                 char keybuf[1024]; // FIXME whatever
579                 uint32_t uid;
580 
581                 if (!dlist_getnum32(di, "UID", &uid))
582                     continue;
583 
584                 snprintf(keybuf, sizeof(keybuf), "%s:%d", uniqueid, uid);
585                 mailbox_message = (struct backup_mailbox_message *) hash_lookup(
586                     keybuf, &mailbox_message_list_index);
587 
588                 if (!mailbox_message)
589                     continue;
590 
591                 if (!mailbox_message_matches(mailbox_message, di))
592                     continue;
593 
594                 backup_mailbox_message_list_remove(mailbox_message_list, mailbox_message);
595                 hash_del(keybuf, &mailbox_message_list_index);
596                 backup_mailbox_message_free(&mailbox_message);
597             }
598         }
599 
600 next_line:
601         if (dl) {
602             dlist_unlink_files(dl);
603             dlist_free(&dl);
604         }
605     }
606     buf_free(&cmd);
607 
608     prot_free(ps);
609     gzuc_member_end(gzuc, NULL);
610 
611     /* anything left in either of the lists is missing from the chunk data. bad! */
612     mailbox = mailbox_list->head;
613     while (mailbox) {
614         syslog(LOG_DEBUG, "%s: chunk %d missing mailbox data for %s (%s)\n",
615                 __func__, chunk->id, mailbox->uniqueid, mailbox->mboxname);
616         if (out)
617             fprintf(out, "chunk %d missing mailbox data for %s (%s)\n",
618                     chunk->id, mailbox->uniqueid, mailbox->mboxname);
619         mailbox = mailbox->next;
620     }
621 
622     mailbox_message = mailbox_message_list->head;
623     while (mailbox_message) {
624         syslog(LOG_DEBUG, "%s: chunk %d missing mailbox_message data for %s uid %u\n",
625                 __func__, chunk->id, mailbox_message->mailbox_uniqueid,
626                 mailbox_message->uid);
627         if (out)
628             fprintf(out, "chunk %d missing mailbox_message data for %s uid %u\n",
629                     chunk->id, mailbox_message->mailbox_uniqueid,
630                     mailbox_message->uid);
631         mailbox_message = mailbox_message->next;
632     }
633 
634     if (!r) r = mailbox_list->count || mailbox_message_list->count ? -1 : 0;
635 
636 done:
637     free_hash_table(&mailbox_list_index, NULL);
638     free_hash_table(&mailbox_message_list_index, NULL);
639 
640     backup_mailbox_list_empty(mailbox_list);
641     free(mailbox_list);
642 
643     backup_mailbox_message_list_empty(mailbox_message_list);
644     free(mailbox_message_list);
645 
646     syslog(LOG_DEBUG, "%s: chunk %d %s!\n", __func__, chunk->id,
647             r ? "failed" : "passed");
648     if (out && verbose)
649         fprintf(out, "%s\n", r ? "error" : "ok");
650     return r;
651 }
652