1 /* lcb_indexw.c -- replication-based backup api - index writing 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/xmalloc.h"
47 
48 #include "imap/dlist.h"
49 #include "imap/imap_err.h"
50 
51 #include "backup/backup.h"
52 
53 #define LIBCYRUS_BACKUP_SOURCE /* this file is part of libcyrus_backup */
54 #include "backup/lcb_internal.h"
55 #include "backup/lcb_sqlconsts.h"
56 
57 static int _index_expunge(struct backup *backup, struct dlist *dl,
58                           time_t ts, off_t dl_offset);
59 static int _index_mailbox(struct backup *backup, struct dlist *dl,
60                           time_t ts, off_t dl_offset);
61 static int _index_unmailbox(struct backup *backup, struct dlist *dl,
62                             time_t ts, off_t dl_offset);
63 static int _index_message(struct backup *backup, struct dlist *dl,
64                           time_t ts, off_t dl_offset, size_t dl_len);
65 static int _index_rename(struct backup *backup, struct dlist *dl,
66                          time_t ts, off_t dl_offset);
67 static int _index_seen(struct backup *backup, struct dlist *dl,
68                        time_t ts, off_t dl_offset);
69 static int _index_sub(struct backup *backup, struct dlist *dl,
70                       time_t ts, off_t dl_offset);
71 static int _index_sieve(struct backup *backup, struct dlist *dl,
72                         time_t ts, off_t dl_offset);
73 
backup_index(struct backup * backup,struct dlist * dlist,time_t ts,off_t start,size_t len)74 HIDDEN int backup_index(struct backup *backup, struct dlist *dlist,
75                         time_t ts, off_t start, size_t len)
76 {
77     int r = IMAP_PROTOCOL_ERROR;
78 
79     if (strcmp(dlist->name, "EXPUNGE") == 0)
80         r = _index_expunge(backup, dlist, ts, start);
81     else if (strcmp(dlist->name, "MAILBOX") == 0)
82         r = _index_mailbox(backup, dlist, ts, start);
83     else if (strcmp(dlist->name, "UNMAILBOX") == 0)
84         r = _index_unmailbox(backup, dlist, ts, start);
85     else if (strcmp(dlist->name, "MESSAGE") == 0)
86         r = _index_message(backup, dlist, ts, start, len);
87     else if (strcmp(dlist->name, "RENAME") == 0)
88         r = _index_rename(backup, dlist, ts, start);
89     else if (strcmp(dlist->name, "RESERVE") == 0)
90         r = 0; /* nothing to index for a reserve, just return success */
91     else if (strcmp(dlist->name, "SEEN") == 0)
92         r = _index_seen(backup, dlist, ts, start);
93     else if (strcmp(dlist->name, "SUB") == 0)
94         r = _index_sub(backup, dlist, ts, start);
95     else if (strcmp(dlist->name, "UNSUB") == 0)
96         r = _index_sub(backup, dlist, ts, start);
97     else if (strcmp(dlist->name, "SIEVE") == 0)
98         r = _index_sieve(backup, dlist, ts, start);
99     else if (strcmp(dlist->name, "UNSIEVE") == 0)
100         r = _index_sieve(backup, dlist, ts, start);
101     else if (config_debug) {
102         struct buf tmp = BUF_INITIALIZER;
103         dlist_printbuf(dlist, 1, &tmp);
104         syslog(LOG_DEBUG, "ignoring unrecognised dlist: %s\n", buf_cstring(&tmp));
105         buf_free(&tmp);
106     }
107 
108     return r;
109 }
110 
_index_expunge(struct backup * backup,struct dlist * dl,time_t ts,off_t dl_offset)111 static int _index_expunge(struct backup *backup, struct dlist *dl,
112                           time_t ts, off_t dl_offset)
113 {
114     syslog(LOG_DEBUG, "indexing EXPUNGE at " OFF_T_FMT "...\n", dl_offset);
115 
116     const char *mboxname;
117     const char *uniqueid;
118     struct dlist *uidl;
119     struct dlist *di;
120     struct backup_mailbox *mailbox = NULL;
121     int r = 0;
122 
123     if (!dlist_getatom(dl, "MBOXNAME", &mboxname))
124         return IMAP_PROTOCOL_BAD_PARAMETERS;
125     if (!dlist_getatom(dl, "UNIQUEID", &uniqueid))
126         return IMAP_PROTOCOL_BAD_PARAMETERS;
127     if (!dlist_getlist(dl, "UID", &uidl))
128         return IMAP_PROTOCOL_BAD_PARAMETERS;
129 
130     mbname_t *mbname = mbname_from_intname(mboxname);
131     mailbox = backup_get_mailbox_by_name(backup, mbname, 0);
132     mbname_free(&mbname);
133 
134     if (!mailbox)
135         return IMAP_MAILBOX_NONEXISTENT;
136 
137     /* verify that uniqueid matches */
138     if (strcmp(mailbox->uniqueid, uniqueid) != 0) {
139         syslog(LOG_ERR, "%s: uniqueid mismatch for %s: %s on wire, %s in index",
140                         __func__, mboxname, uniqueid, mailbox->uniqueid);
141         r = IMAP_PROTOCOL_BAD_PARAMETERS;
142     }
143 
144     for (di = uidl->head; di && !r; di = di->next) {
145         struct sqldb_bindval bval[] = {
146             { ":mailbox_id",    SQLITE_INTEGER, { .i = mailbox->id } },
147             { ":uid",           SQLITE_INTEGER, { .i = dlist_num(di) } },
148             { ":expunged",      SQLITE_INTEGER, { .i = ts } },
149         };
150 
151         r = sqldb_exec(backup->db, backup_index_mailbox_message_expunge_sql,
152                        bval, NULL, NULL);
153         if (r) r = IMAP_INTERNAL;
154     }
155 
156     backup_mailbox_free(&mailbox);
157     return r;
158 }
159 
_get_magic_flags(struct dlist * flags,int * is_expunged)160 static int _get_magic_flags(struct dlist *flags, int *is_expunged)
161 {
162     struct dlist *found_expunged = NULL;
163     struct dlist *di;
164     int found = 0;
165 
166     assert(strcmp(flags->name, "FLAGS") == 0);
167 
168     for (di = flags->head; di; di = di->next) {
169         if (strcmp(di->sval, "\\Expunged") == 0) {
170             if (is_expunged) *is_expunged = 1;
171             found_expunged = di;
172             found++;
173         }
174     }
175 
176     if (found_expunged) {
177         dlist_unstitch(flags, found_expunged);
178         dlist_free(&found_expunged);
179     }
180 
181     return found;
182 }
183 
_index_mailbox(struct backup * backup,struct dlist * dl,time_t ts,off_t dl_offset)184 static int _index_mailbox(struct backup *backup, struct dlist *dl,
185                           time_t ts, off_t dl_offset)
186 {
187     syslog(LOG_DEBUG, "indexing MAILBOX at " OFF_T_FMT "...\n", dl_offset);
188 
189     const char *uniqueid = NULL;
190     const char *mboxname = NULL;
191     const char *mboxtype = NULL;
192     uint32_t last_uid = 0;
193     modseq_t highestmodseq = 0;
194     uint32_t recentuid = 0;
195     time_t recenttime = 0;
196     time_t last_appenddate = 0;
197     time_t pop3_last_login = 0;
198     time_t pop3_show_after = 0;
199     uint32_t uidvalidity = 0;
200     const char *partition = NULL;
201     const char *acl = NULL;
202     const char *options = NULL;
203     struct synccrcs synccrcs = { 0, 0 };
204     const char *quotaroot = NULL;
205     modseq_t xconvmodseq = 0;
206     struct dlist *annotations = NULL;
207     struct buf annotations_buf = BUF_INITIALIZER;
208     struct dlist *record = NULL;
209     int r;
210 
211     if (!dlist_getatom(dl, "UNIQUEID", &uniqueid))
212         return IMAP_PROTOCOL_BAD_PARAMETERS;
213     if (!dlist_getatom(dl, "MBOXNAME", &mboxname))
214         return IMAP_PROTOCOL_BAD_PARAMETERS;
215     if (!dlist_getnum32(dl, "LAST_UID", &last_uid))
216         return IMAP_PROTOCOL_BAD_PARAMETERS;
217     if (!dlist_getnum64(dl, "HIGHESTMODSEQ", &highestmodseq))
218         return IMAP_PROTOCOL_BAD_PARAMETERS;
219     if (!dlist_getnum32(dl, "RECENTUID", &recentuid))
220         return IMAP_PROTOCOL_BAD_PARAMETERS;
221     if (!dlist_getdate(dl, "RECENTTIME", &recenttime))
222         return IMAP_PROTOCOL_BAD_PARAMETERS;
223     if (!dlist_getdate(dl, "LAST_APPENDDATE", &last_appenddate))
224         return IMAP_PROTOCOL_BAD_PARAMETERS;
225     if (!dlist_getdate(dl, "POP3_LAST_LOGIN", &pop3_last_login))
226         return IMAP_PROTOCOL_BAD_PARAMETERS;
227     if (!dlist_getnum32(dl, "UIDVALIDITY", &uidvalidity))
228         return IMAP_PROTOCOL_BAD_PARAMETERS;
229     if (!dlist_getatom(dl, "PARTITION", &partition))
230         return IMAP_PROTOCOL_BAD_PARAMETERS;
231     if (!dlist_getatom(dl, "ACL", &acl))
232         return IMAP_PROTOCOL_BAD_PARAMETERS;
233     if (!dlist_getatom(dl, "OPTIONS", &options))
234         return IMAP_PROTOCOL_BAD_PARAMETERS;
235     if (!dlist_getlist(dl, "RECORD", &record))
236         return IMAP_PROTOCOL_BAD_PARAMETERS;
237 
238     /* optional */
239     dlist_getlist(dl, "ANNOTATIONS", &annotations);
240     dlist_getdate(dl, "POP3_SHOW_AFTER", &pop3_show_after);
241     dlist_getatom(dl, "MBOXTYPE", &mboxtype);
242     dlist_getnum64(dl, "XCONVMODSEQ", &xconvmodseq);
243 
244     /* CRCs */
245     dlist_getnum32(dl, "SYNC_CRC", &synccrcs.basic);
246     dlist_getnum32(dl, "SYNC_CRC_ANNOT", &synccrcs.annot);
247 
248     /* if we can't start a transaction, bail out before allocating anything else */
249     r = sqldb_begin(backup->db, __func__);
250     if (r) return IMAP_INTERNAL;
251 
252     if (annotations) {
253         dlist_printbuf(annotations, 0, &annotations_buf);
254     }
255 
256     struct sqldb_bindval mbox_bval[] = {
257         { ":last_chunk_id",     SQLITE_INTEGER, { .i = backup->append_state->chunk_id } },
258         { ":uniqueid",          SQLITE_TEXT,    { .s = uniqueid } },
259         { ":mboxname",          SQLITE_TEXT,    { .s = mboxname } },
260         { ":mboxtype",          SQLITE_TEXT,    { .s = mboxtype } },
261         { ":last_uid",          SQLITE_INTEGER, { .i = last_uid } },
262         { ":highestmodseq",     SQLITE_INTEGER, { .i = highestmodseq } },
263         { ":recentuid",         SQLITE_INTEGER, { .i = recentuid } },
264         { ":recenttime",        SQLITE_INTEGER, { .i = recenttime } },
265         { ":last_appenddate",   SQLITE_INTEGER, { .i = last_appenddate } },
266         { ":pop3_last_login",   SQLITE_INTEGER, { .i = pop3_last_login } },
267         { ":pop3_show_after",   SQLITE_INTEGER, { .i = pop3_show_after } },
268         { ":uidvalidity",       SQLITE_INTEGER, { .i = uidvalidity } },
269         { ":partition",         SQLITE_TEXT,    { .s = partition } },
270         { ":acl",               SQLITE_TEXT,    { .s = acl } },
271         { ":options",           SQLITE_TEXT,    { .s = options } },
272         { ":sync_crc",          SQLITE_INTEGER, { .i = synccrcs.basic } },
273         { ":sync_crc_annot",    SQLITE_INTEGER, { .i = synccrcs.annot } },
274         { ":quotaroot",         SQLITE_TEXT,    { .s = quotaroot } },
275         { ":xconvmodseq",       SQLITE_INTEGER, { .i = xconvmodseq } },
276         { ":annotations",       SQLITE_TEXT,    { .s = buf_cstring(&annotations_buf) } },
277         { ":deleted",           SQLITE_NULL,    { .s = NULL } },
278         { NULL,                 SQLITE_NULL,    { .s = NULL      } },
279     };
280 
281     r = sqldb_exec(backup->db, backup_index_mailbox_update_sql,
282                    mbox_bval, NULL, NULL);
283 
284     if (!r && sqldb_changes(backup->db) == 0) {
285         r = sqldb_exec(backup->db, backup_index_mailbox_insert_sql,
286                        mbox_bval, NULL, NULL);
287         if (r) {
288             syslog(LOG_DEBUG, "%s: something went wrong: %i insert %s\n",
289                               __func__, r, mboxname);
290         }
291     }
292     else if (r) {
293         syslog(LOG_DEBUG, "%s: something went wrong: %i update %s\n",
294                           __func__, r, mboxname);
295     }
296 
297     buf_free(&annotations_buf);
298 
299     if (r) goto error;
300 
301     if (record->head) {
302         int mailbox_id = backup_get_mailbox_id(backup, uniqueid);
303         struct dlist *ki = NULL;
304 
305         for (ki = record->head; ki; ki = ki->next) {
306             uint32_t uid = 0;
307             modseq_t modseq = 0;
308             uint32_t last_updated = 0;
309             struct dlist *flags = NULL;
310             struct buf flags_buf = BUF_INITIALIZER;
311             uint32_t internaldate;
312             const char *guid;
313             struct dlist *annotations = NULL;
314             struct buf annotations_buf = BUF_INITIALIZER;
315             int message_id = -1;
316             time_t expunged = 0;
317 
318             if (!dlist_getnum32(ki, "UID", &uid))
319                 goto error;
320             if (!dlist_getnum64(ki, "MODSEQ", &modseq))
321                 goto error;
322             if (!dlist_getnum32(ki, "LAST_UPDATED", &last_updated))
323                 goto error;
324             if (!dlist_getnum32(ki, "INTERNALDATE", &internaldate))
325                 goto error;
326             if (!dlist_getatom(ki, "GUID", &guid))
327                 goto error;
328 
329             /* XXX should this search for guid+size rather than just guid? */
330             message_id = backup_get_message_id(backup, guid);
331             if (message_id == -1) {
332                 syslog(LOG_DEBUG, "%s: something went wrong: %i %s %s\n",
333                                   __func__, r, mboxname, guid);
334                 goto error;
335             }
336             if (message_id == 0) {
337                 /* can't link this record, we don't have a message */
338                 /* possibly we're in compact, and have deleted it? */
339                 /* XXX this should probably be an error too, but can't be until compact is smarter */
340                 syslog(LOG_DEBUG, "%s: skipping %s record for %s: message not found\n",
341                                   __func__, mboxname, guid);
342                 continue;
343             }
344 
345             dlist_getlist(ki, "FLAGS", &flags);
346             if (flags) {
347                 int is_expunged = 0;
348 
349                 _get_magic_flags(flags, &is_expunged);
350 
351                 if (is_expunged) {
352                     syslog(LOG_DEBUG, "%s: found expunge flag for message %s\n",
353                                       __func__, guid);
354                     expunged = ts;
355                 }
356 
357                 dlist_printbuf(flags, 0, &flags_buf);
358                 syslog(LOG_DEBUG, "%s: found flags for message %s: %s\n",
359                                   __func__, guid, buf_cstring(&flags_buf));
360             }
361 
362             dlist_getlist(ki, "ANNOTATIONS", &annotations);
363             if (annotations) {
364                 dlist_printbuf(annotations, 0, &annotations_buf);
365             }
366 
367             struct sqldb_bindval record_bval[] = {
368                 { ":mailbox_id",        SQLITE_INTEGER, { .i = mailbox_id } },
369                 { ":message_id",        SQLITE_INTEGER, { .i = message_id } },
370                 { ":last_chunk_id",     SQLITE_INTEGER, { .i = backup->append_state->chunk_id } },
371                 { ":uid",               SQLITE_INTEGER, { .i = uid } },
372                 { ":modseq",            SQLITE_INTEGER, { .i = modseq } },
373                 { ":last_updated",      SQLITE_INTEGER, { .i = last_updated } },
374                 { ":flags",             SQLITE_TEXT,    { .s = buf_cstring(&flags_buf) } },
375                 { ":internaldate",      SQLITE_INTEGER, { .i = internaldate } },
376                 { ":annotations",       SQLITE_TEXT,    { .s = buf_cstring(&annotations_buf) } },
377                 { ":expunged",          SQLITE_NULL,    { .s = NULL      } },
378                 { NULL,                 SQLITE_NULL,    { .s = NULL      } },
379             };
380 
381             /* provide an expunged value if we have one */
382             if (expunged) {
383                 struct sqldb_bindval *expunged_bval = &record_bval[9];
384                 assert(strcmp(expunged_bval->name, ":expunged") == 0);
385                 expunged_bval->type = SQLITE_INTEGER;
386                 expunged_bval->val.i = expunged;
387             }
388 
389             r = sqldb_exec(backup->db, backup_index_mailbox_message_update_sql,
390                            record_bval, NULL, NULL);
391 
392             if (!r && sqldb_changes(backup->db) == 0) {
393                 r = sqldb_exec(backup->db, backup_index_mailbox_message_insert_sql,
394                                record_bval, NULL, NULL);
395                 if (r) {
396                     syslog(LOG_DEBUG, "%s: something went wrong: %i insert %s %s\n",
397                                       __func__, r, mboxname, guid);
398                 }
399             }
400             else if (r) {
401                 syslog(LOG_DEBUG, "%s: something went wrong: %i update %s %s\n",
402                                   __func__, r, mboxname, guid);
403             }
404 
405             buf_free(&annotations_buf);
406             buf_free(&flags_buf);
407 
408             if (r) goto error;
409         }
410     }
411 
412     syslog(LOG_DEBUG, "%s: committing index change: %s\n", __func__, mboxname);
413     sqldb_commit(backup->db, __func__);
414     return 0;
415 
416 error:
417     syslog(LOG_DEBUG, "%s: rolling back index change: %s\n", __func__, mboxname);
418     sqldb_rollback(backup->db, __func__);
419 
420     return IMAP_INTERNAL;
421 }
422 
_index_unmailbox(struct backup * backup,struct dlist * dl,time_t ts,off_t dl_offset)423 static int _index_unmailbox(struct backup *backup, struct dlist *dl,
424                             time_t ts, off_t dl_offset)
425 {
426     syslog(LOG_DEBUG, "indexing UNMAILBOX at " OFF_T_FMT "...\n", dl_offset);
427 
428     const char *mboxname = dl->sval;
429 
430     struct sqldb_bindval bval[] = {
431         { ":mboxname",  SQLITE_TEXT,    { .s = mboxname  } },
432         { ":deleted",   SQLITE_INTEGER, { .i = ts        } },
433         { NULL,         SQLITE_NULL,    { .s = NULL      } },
434     };
435 
436     int r = sqldb_exec(backup->db, backup_index_mailbox_delete_sql, bval, NULL,
437                         NULL);
438     if (r) {
439         syslog(LOG_DEBUG, "%s: something went wrong: %i %s\n",
440                           __func__, r, mboxname);
441     }
442 
443     return r ? IMAP_INTERNAL : 0;
444 }
445 
_index_message(struct backup * backup,struct dlist * dl,time_t ts,off_t dl_offset,size_t dl_len)446 static int _index_message(struct backup *backup, struct dlist *dl,
447                           time_t ts, off_t dl_offset, size_t dl_len)
448 {
449     syslog(LOG_DEBUG, "indexing MESSAGE at " OFF_T_FMT " (" SIZE_T_FMT " bytes)...\n", dl_offset, dl_len);
450     (void) ts;
451 
452     struct dlist *di;
453     int r = 0;
454 
455     /* n.b. APPLY MESSAGE contains a list of messages, not just one */
456     for (di = dl->head; di && !r; di = di->next) {
457         struct message_guid *guid = NULL;
458         const char *partition = NULL;
459         unsigned long size = 0;
460 
461         if (!dlist_tofile(di, &partition, &guid, &size, NULL))
462             continue;
463 
464         struct sqldb_bindval bval[] = {
465             { ":guid",      SQLITE_TEXT,    { .s = message_guid_encode(guid) } },
466             { ":partition", SQLITE_TEXT,    { .s = partition } },
467             { ":chunk_id",  SQLITE_INTEGER, { .i = backup->append_state->chunk_id } },
468             { ":offset",    SQLITE_INTEGER, { .i = dl_offset } },
469             { ":size",      SQLITE_INTEGER, { .i = size      } },
470             { NULL,         SQLITE_NULL,    { .s = NULL      } },
471         };
472 
473         int r = sqldb_exec(backup->db, backup_index_message_insert_sql, bval, NULL,
474                            NULL);
475         if (r) {
476             syslog(LOG_DEBUG, "%s: something went wrong: %i %s\n",
477                    __func__, r, message_guid_encode(guid));
478         }
479     }
480 
481     return r ? IMAP_INTERNAL : 0;
482 }
483 
_index_rename(struct backup * backup,struct dlist * dl,time_t ts,off_t dl_offset)484 static int _index_rename(struct backup *backup, struct dlist *dl,
485                          time_t ts, off_t dl_offset)
486 {
487     syslog(LOG_DEBUG, "indexing RENAME at " OFF_T_FMT "\n", dl_offset);
488     (void) ts;
489 
490     const char *uniqueid = NULL;
491     const char *oldmboxname = NULL;
492     const char *newmboxname = NULL;
493     const char *partition = NULL;
494     uint32_t uidvalidity = 0;
495     int r = 0;
496 
497     /* XXX use uniqueid once sync proto includes it (D73) */
498     dlist_getatom(dl, "UNIQUEID", &uniqueid);
499     (void) uniqueid; /* silence unused warning */
500 
501     if (!dlist_getatom(dl, "OLDMBOXNAME", &oldmboxname))
502         return IMAP_PROTOCOL_BAD_PARAMETERS;
503     if (!dlist_getatom(dl, "NEWMBOXNAME", &newmboxname))
504         return IMAP_PROTOCOL_BAD_PARAMETERS;
505     if (!dlist_getatom(dl, "PARTITION", &partition))
506         return IMAP_PROTOCOL_BAD_PARAMETERS;
507     if (!dlist_getnum32(dl, "UIDVALIDITY", &uidvalidity))
508         return IMAP_PROTOCOL_BAD_PARAMETERS;
509 
510     struct sqldb_bindval mbox_bval[] = {
511 //        { ":uniqueid",          SQLITE_TEXT,    { .s = uniqueid } },
512         { ":oldmboxname",       SQLITE_TEXT,    { .s = oldmboxname } },
513         { ":newmboxname",       SQLITE_TEXT,    { .s = newmboxname } },
514         { ":partition",         SQLITE_TEXT,    { .s = partition } },
515         { ":uidvalidity",       SQLITE_INTEGER, { .i = uidvalidity } },
516         { NULL,                 SQLITE_NULL,    { .s = NULL      } },
517     };
518 
519     r = sqldb_exec(backup->db, backup_index_mailbox_rename_sql,
520                        mbox_bval, NULL, NULL);
521 
522     if (r) {
523         syslog(LOG_DEBUG, "%s: something went wrong: %i rename %s => %s\n",
524                __func__, r, oldmboxname, newmboxname);
525     }
526 
527     return r ? IMAP_INTERNAL : 0;
528 }
529 
_index_seen(struct backup * backup,struct dlist * dl,time_t ts,off_t dl_offset)530 static int _index_seen(struct backup *backup, struct dlist *dl,
531                        time_t ts, off_t dl_offset)
532 {
533     syslog(LOG_DEBUG, "indexing %s at " OFF_T_FMT "\n", dl->name, dl_offset);
534     (void) ts;
535 
536     const char *uniqueid;
537     time_t lastread;
538     uint32_t lastuid;
539     time_t lastchange;
540     const char *seenuids;
541     int r;
542 
543     if (!dlist_getatom(dl, "UNIQUEID", &uniqueid))
544         return IMAP_PROTOCOL_BAD_PARAMETERS;
545     if (!dlist_getdate(dl, "LASTREAD", &lastread))
546         return IMAP_PROTOCOL_BAD_PARAMETERS;
547     if (!dlist_getnum32(dl, "LASTUID", &lastuid))
548         return IMAP_PROTOCOL_BAD_PARAMETERS;
549     if (!dlist_getdate(dl, "LASTCHANGE", &lastchange))
550         return IMAP_PROTOCOL_BAD_PARAMETERS;
551     if (!dlist_getatom(dl, "SEENUIDS", &seenuids))
552         return IMAP_PROTOCOL_BAD_PARAMETERS;
553 
554     struct sqldb_bindval bval[] =  {
555         { ":last_chunk_id", SQLITE_INTEGER, { .i = backup->append_state->chunk_id } },
556         { ":uniqueid",      SQLITE_TEXT,    { .s = uniqueid } },
557         { ":lastread",      SQLITE_INTEGER, { .i = lastread } },
558         { ":lastuid",       SQLITE_INTEGER, { .i = lastuid } },
559         { ":lastchange",    SQLITE_INTEGER, { .i = lastchange } },
560         { ":seenuids",      SQLITE_TEXT,    { .s = seenuids } },
561         { NULL,             SQLITE_NULL,    { .s = NULL } },
562     };
563 
564     r = sqldb_exec(backup->db, backup_index_seen_update_sql, bval,
565                    NULL, NULL);
566 
567     if (!r && sqldb_changes(backup->db) == 0) {
568         r = sqldb_exec(backup->db, backup_index_seen_insert_sql, bval,
569                        NULL, NULL);
570         if (r) {
571             syslog(LOG_DEBUG, "%s: something went wrong: %i insert seen %s\n",
572                    __func__, r, uniqueid);
573         }
574     }
575     else if (r) {
576         syslog(LOG_DEBUG, "%s: something went wrong: %i update seen %s",
577                __func__, r, uniqueid);
578     }
579 
580     return r ? IMAP_INTERNAL : 0;
581 }
582 
_index_sub(struct backup * backup,struct dlist * dl,time_t ts,off_t dl_offset)583 static int _index_sub(struct backup *backup, struct dlist *dl,
584                       time_t ts, off_t dl_offset)
585 {
586     syslog(LOG_DEBUG, "indexing %s at " OFF_T_FMT "\n", dl->name, dl_offset);
587 
588     const char *mboxname = NULL;
589     int r;
590 
591     if (!dlist_getatom(dl, "MBOXNAME", &mboxname))
592         return IMAP_PROTOCOL_BAD_PARAMETERS;
593 
594     struct sqldb_bindval bval[] = {
595         { ":last_chunk_id", SQLITE_INTEGER, { .i = backup->append_state->chunk_id } },
596         { ":mboxname",      SQLITE_TEXT,    { .s = mboxname } },
597         { ":unsubscribed",  SQLITE_NULL,    { .s = NULL } },
598         { NULL,             SQLITE_NULL,    { .s = NULL } },
599     };
600 
601     /* set the unsubscribed time if this is an UNSUB */
602     if (!strcmp(dl->name, "UNSUB")) {
603         syslog(LOG_DEBUG, "setting unsubscribed to %ld for %s", ts, mboxname);
604         struct sqldb_bindval *unsubscribed_bval = &bval[2];
605         assert(strcmp(unsubscribed_bval->name, ":unsubscribed") == 0);
606         unsubscribed_bval->type = SQLITE_INTEGER;
607         unsubscribed_bval->val.i = ts;
608     }
609 
610     r = sqldb_exec(backup->db, backup_index_subscription_update_sql, bval,
611                    NULL, NULL);
612 
613     if (!r && sqldb_changes(backup->db) == 0) {
614         r = sqldb_exec(backup->db, backup_index_subscription_insert_sql, bval,
615                        NULL, NULL);
616         if (r) {
617             syslog(LOG_DEBUG, "%s: something went wrong: %i insert subscription %s\n",
618                    __func__, r, mboxname);
619         }
620     }
621     else if (r) {
622         syslog(LOG_DEBUG, "%s: something went wrong: %i update subscription %s",
623                __func__, r, mboxname);
624     }
625 
626     return r ? IMAP_INTERNAL : 0;
627 }
628 
_index_sieve(struct backup * backup,struct dlist * dl,time_t ts,off_t dl_offset)629 static int _index_sieve(struct backup *backup, struct dlist *dl,
630                         time_t ts, off_t dl_offset)
631 {
632     syslog(LOG_DEBUG, "indexing %s at " OFF_T_FMT "\n", dl->name, dl_offset);
633 
634     const char *filename;
635     int r;
636 
637     if (!dlist_getatom(dl, "FILENAME", &filename))
638         return IMAP_PROTOCOL_BAD_PARAMETERS;
639 
640     struct sqldb_bindval bval[] = {
641         { ":chunk_id",      SQLITE_INTEGER, { .i = backup->append_state->chunk_id } },
642         { ":last_update",   SQLITE_NULL,    { .s = NULL } },
643         { ":filename",      SQLITE_TEXT,    { .s = filename } },
644         { ":guid",          SQLITE_NULL,    { .s = NULL } },
645         { ":offset",        SQLITE_INTEGER, { .i = dl_offset } },
646         { ":deleted",       SQLITE_INTEGER, { .i = ts } },
647         { NULL,             SQLITE_NULL,    { .s = NULL } },
648     };
649 
650     /* mark previous record(s) for this filename as deleted */
651     r = sqldb_exec(backup->db, backup_index_sieve_delete_sql, bval,
652                    NULL, NULL);
653 
654     if (r) {
655         syslog(LOG_DEBUG, "%s: something went wrong: %i delete sieve %s",
656                __func__, r, filename);
657     }
658 
659     if (!r && strcmp(dl->name, "SIEVE") == 0) {
660         time_t last_update;
661         const char *content;
662         size_t content_len;
663         struct message_guid guid;
664 
665         if (!dlist_getdate(dl, "LAST_UPDATE", &last_update))
666             return IMAP_PROTOCOL_BAD_PARAMETERS;
667         if (!dlist_getmap(dl, "CONTENT", &content, &content_len))
668             return IMAP_PROTOCOL_BAD_PARAMETERS;
669 
670         message_guid_generate(&guid, content, content_len);
671 
672         struct sqldb_bindval *last_update_bval = &bval[1];
673         assert(strcmp(last_update_bval->name, ":last_update") == 0);
674         last_update_bval->type = SQLITE_INTEGER;
675         last_update_bval->val.i = last_update;
676 
677         struct sqldb_bindval *guid_bval = &bval[3];
678         assert(strcmp(guid_bval->name, ":guid") == 0);
679         guid_bval->type = SQLITE_TEXT;
680         guid_bval->val.s = message_guid_encode(&guid);
681 
682         /* insert doesn't use :deleted, but clear it still just in case */
683         struct sqldb_bindval *deleted_bval = &bval[5];
684         assert(strcmp(deleted_bval->name, ":deleted") == 0);
685         deleted_bval->type = SQLITE_NULL;
686         deleted_bval->val.s = NULL;
687 
688         /* insert the new record for this filename */
689         r = sqldb_exec(backup->db, backup_index_sieve_insert_sql, bval,
690                     NULL, NULL);
691 
692         if (r) {
693             syslog(LOG_DEBUG, "%s: something went wrong: %i insert sieve %s",
694                 __func__, r, filename);
695         }
696     }
697 
698     return r ? IMAP_INTERNAL : 0;
699 }
700