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