1 /* sync_support.c -- Cyrus synchronization support functions
2 *
3 * Copyright (c) 1994-2008 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 <config.h>
44
45 #ifdef HAVE_UNISTD_H
46 #include <unistd.h>
47 #endif
48 #include <stdlib.h>
49 #include <stdio.h>
50 #include <time.h>
51 #include <sys/stat.h>
52 #include <sys/types.h>
53 #include <fcntl.h>
54 #include <sysexits.h>
55 #include <syslog.h>
56 #include <string.h>
57 #include <sys/wait.h>
58 #include <errno.h>
59 #include <dirent.h>
60 #include <utime.h>
61 #include <limits.h>
62
63 #include "assert.h"
64 #include "global.h"
65 #include "imap_proxy.h"
66 #include "mboxlist.h"
67 #include "mailbox.h"
68 #include "quota.h"
69 #include "xmalloc.h"
70 #include "seen.h"
71 #include "mboxname.h"
72 #include "map.h"
73 #include "imapd.h"
74 #include "message.h"
75 #include "util.h"
76 #include "user.h"
77 #include "prot.h"
78 #include "dlist.h"
79 #include "xstrlcat.h"
80
81 #ifdef USE_CALALARMD
82 #include "caldav_alarm.h"
83 #endif
84
85 #ifdef USE_SIEVE
86 #include "sieve/sieve_interface.h"
87 #endif
88
89 /* generated headers are not necessarily in current directory */
90 #include "imap/imap_err.h"
91
92 #include "message_guid.h"
93 #include "sync_support.h"
94 #include "sync_log.h"
95
96 static int opt_force = 0; // FIXME
97
98 /* protocol definitions */
99 static char *imap_sasl_parsesuccess(char *str, const char **status);
100 static void imap_postcapability(struct backend *s);
101
102 struct protocol_t imap_csync_protocol =
103 { "imap", "imap", TYPE_STD,
104 { { { 1, NULL },
105 { "C01 CAPABILITY", NULL, "C01 ", imap_postcapability,
106 CAPAF_MANY_PER_LINE,
107 { { "AUTH", CAPA_AUTH },
108 { "STARTTLS", CAPA_STARTTLS },
109 // FIXME doesn't work with compress at the moment for some reason
110 // { "COMPRESS=DEFLATE", CAPA_COMPRESS },
111 // FIXME do we need these ones?
112 // { "IDLE", CAPA_IDLE },
113 // { "MUPDATE", CAPA_MUPDATE },
114 // { "MULTIAPPEND", CAPA_MULTIAPPEND },
115 // { "RIGHTS=kxte", CAPA_ACLRIGHTS },
116 // { "LIST-EXTENDED", CAPA_LISTEXTENDED },
117 { "SASL-IR", CAPA_SASL_IR },
118 { "X-REPLICATION", CAPA_REPLICATION },
119 { NULL, 0 } } },
120 { "S01 STARTTLS", "S01 OK", "S01 NO", 0 },
121 { "A01 AUTHENTICATE", 0, 0, "A01 OK", "A01 NO", "+ ", "*",
122 &imap_sasl_parsesuccess, AUTO_CAPA_AUTH_OK },
123 { "Z01 COMPRESS DEFLATE", "* ", "Z01 OK" },
124 { "N01 NOOP", "* ", "N01 OK" },
125 { "Q01 LOGOUT", "* ", "Q01 " } } }
126 };
127
128 struct protocol_t csync_protocol =
129 { "csync", "csync", TYPE_STD,
130 { { { 1, "* OK" },
131 { NULL, NULL, "* OK", NULL,
132 CAPAF_ONE_PER_LINE|CAPAF_SKIP_FIRST_WORD,
133 { { "SASL", CAPA_AUTH },
134 { "STARTTLS", CAPA_STARTTLS },
135 { "COMPRESS=DEFLATE", CAPA_COMPRESS },
136 { NULL, 0 } } },
137 { "STARTTLS", "OK", "NO", 1 },
138 { "AUTHENTICATE", USHRT_MAX, 0, "OK", "NO", "+ ", "*", NULL, 0 },
139 { "COMPRESS DEFLATE", NULL, "OK" },
140 { "NOOP", NULL, "OK" },
141 { "EXIT", NULL, "OK" } } }
142 };
143
144 /* parse_success api is undocumented but my current understanding
145 * is that the caller expects it to return a pointer to the position
146 * within str at which base64 encoded "success data" can be found.
147 * status is for passing back other status data (if required) to
148 * the original caller.
149 *
150 * in the case of what we're doing here, there is no base64 encoded
151 * 'success data', but there is a capability string that we want to
152 * save. so we grab the capability string (including the []s) and
153 * chuck that in status, and then we return NULL to indicate the
154 * lack of base64 data.
155 */
imap_sasl_parsesuccess(char * str,const char ** status)156 static char *imap_sasl_parsesuccess(char *str, const char **status)
157 {
158 syslog(LOG_DEBUG, "imap_sasl_parsesuccess(): input is: %s", str);
159 if (NULL == status) return NULL; /* nothing useful we can do */
160
161 const char *prelude = "A01 OK "; // FIXME don't hardcode this, get it from sasl_cmd->ok
162 const size_t prelude_len = strlen(prelude);
163
164 const char *capability = "[CAPABILITY ";
165 const size_t capability_len = strlen(capability);
166
167 char *start, *end;
168
169 if (strncmp(str, prelude, prelude_len)) {
170 /* this isn't the string we expected */
171 syslog(LOG_INFO, "imap_sasl_parsesuccess(): unexpected initial string contents: %s", str);
172 return NULL;
173 }
174
175 start = str + prelude_len;
176
177 if (strncmp(start, capability, capability_len)) {
178 /* this isn't a capability string */
179 syslog(LOG_INFO, "imap_sasl_parsesuccess(): str does not contain a capability string: %s", str);
180 return NULL;
181 }
182
183 end = start + capability_len;
184 while (*end != ']' && *end != '\0') {
185 end++;
186 }
187
188 if (*end == '\0') {
189 /* didn't find end of capability string */
190 syslog(LOG_INFO, "imap_sasl_parsesuccess(): did not find end of capability string: %s", str);
191 return NULL;
192 }
193
194 /* we want to keep the ], but crop the rest off */
195 *++end = '\0';
196
197 /* status gets the capability string */
198 syslog(LOG_DEBUG, "imap_sasl_parsesuccess(): found capability string: %s", start);
199 *status = start;
200
201 /* there's no base64 data, so return NULL */
202 return NULL;
203 }
204
imap_postcapability(struct backend * s)205 static void imap_postcapability(struct backend *s)
206 {
207 if (CAPA(s, CAPA_SASL_IR)) {
208 /* server supports initial response in AUTHENTICATE command */
209 s->prot->u.std.sasl_cmd.maxlen = USHRT_MAX;
210 }
211 }
212
213 /* channel-based configuration */
214
sync_get_config(const char * channel,const char * val)215 EXPORTED const char *sync_get_config(const char *channel, const char *val)
216 {
217 const char *response = NULL;
218
219 if (channel) {
220 char name[MAX_MAILBOX_NAME]; /* crazy long, but hey */
221 snprintf(name, MAX_MAILBOX_NAME, "%s_%s", channel, val);
222 response = config_getoverflowstring(name, NULL);
223 }
224
225 if (!response) {
226 /* get the core value */
227 if (!strcmp(val, "sync_host"))
228 response = config_getstring(IMAPOPT_SYNC_HOST);
229 else if (!strcmp(val, "sync_authname"))
230 response = config_getstring(IMAPOPT_SYNC_AUTHNAME);
231 else if (!strcmp(val, "sync_password"))
232 response = config_getstring(IMAPOPT_SYNC_PASSWORD);
233 else if (!strcmp(val, "sync_realm"))
234 response = config_getstring(IMAPOPT_SYNC_REALM);
235 else if (!strcmp(val, "sync_port"))
236 response = config_getstring(IMAPOPT_SYNC_PORT);
237 else if (!strcmp(val, "sync_shutdown_file"))
238 response = config_getstring(IMAPOPT_SYNC_SHUTDOWN_FILE);
239 else
240 fatal("unknown config variable requested", EX_SOFTWARE);
241 }
242
243 return response;
244 }
245
sync_get_durationconfig(const char * channel,const char * val,int defunit)246 EXPORTED int sync_get_durationconfig(const char *channel, const char *val, int defunit)
247 {
248 int response = -1;
249
250 if (channel) {
251 const char *result = NULL;
252 char name[MAX_MAILBOX_NAME]; /* crazy long, but hey */
253 snprintf(name, MAX_MAILBOX_NAME, "%s_%s", channel, val);
254 result = config_getoverflowstring(name, NULL);
255 if (result)
256 config_parseduration(result, defunit, &response);
257 }
258
259 if (response == -1) {
260 if (!strcmp(val, "sync_repeat_interval"))
261 response = config_getduration(IMAPOPT_SYNC_REPEAT_INTERVAL, defunit);
262 }
263
264 return response;
265 }
266
sync_get_switchconfig(const char * channel,const char * val)267 EXPORTED int sync_get_switchconfig(const char *channel, const char *val)
268 {
269 int response = -1;
270
271 if (channel) {
272 const char *result = NULL;
273 char name[MAX_MAILBOX_NAME]; /* crazy long, but hey */
274 snprintf(name, sizeof(name), "%s_%s", channel, val);
275 result = config_getoverflowstring(name, NULL);
276 if (result) response = atoi(result);
277 }
278
279 if (response == -1) {
280 if (!strcmp(val, "sync_try_imap"))
281 response = config_getswitch(IMAPOPT_SYNC_TRY_IMAP);
282 }
283
284 return response;
285 }
286
287 /* Parse routines */
288
sync_encode_options(int options)289 char *sync_encode_options(int options)
290 {
291 static char buf[4];
292 int i = 0;
293
294 if (options & OPT_POP3_NEW_UIDL)
295 buf[i++] = 'P';
296 if (options & OPT_IMAP_SHAREDSEEN)
297 buf[i++] = 'S';
298 if (options & OPT_IMAP_DUPDELIVER)
299 buf[i++] = 'D';
300 if (options & OPT_IMAP_HAS_ALARMS)
301 buf[i++] = 'A';
302 buf[i] = '\0';
303
304 return buf;
305 }
306
sync_parse_options(const char * options)307 int sync_parse_options(const char *options)
308 {
309 int res = 0;
310 const char *p = options;
311
312 if (!options) return 0;
313
314 while (*p) {
315 switch(*p) {
316 case 'P':
317 res |= OPT_POP3_NEW_UIDL;
318 break;
319 case 'S':
320 res |= OPT_IMAP_SHAREDSEEN;
321 break;
322 case 'D':
323 res |= OPT_IMAP_DUPDELIVER;
324 break;
325 case 'A':
326 res |= OPT_IMAP_HAS_ALARMS;
327 break;
328 }
329 p++;
330 }
331
332 return res;
333 }
334
335 /* Get a simple line (typically error text) */
sync_getline(struct protstream * in,struct buf * buf)336 static int sync_getline(struct protstream *in, struct buf *buf)
337 {
338 int c;
339
340 buf_reset(buf);
341
342 for (;;) {
343 c = prot_getc(in);
344
345 if (c == EOF || (c == '\r') || (c == '\n')) {
346 /* Munch optional LF after CR */
347 if (c == '\r' && ((c = prot_getc(in)) != EOF && c != '\n')) {
348 prot_ungetc(c, in);
349 c = '\r';
350 }
351 buf_cstring(buf);
352 return c;
353 }
354 if (buf->len > config_maxword)
355 fatal("word too long", EX_IOERR);
356 buf_putc(buf, c);
357 }
358 return c;
359 }
360
361
362 /* ====================================================================== */
363
sync_msgid_list_create(int hash_size)364 struct sync_msgid_list *sync_msgid_list_create(int hash_size)
365 {
366 struct sync_msgid_list *l = xzmalloc(sizeof (struct sync_msgid_list));
367
368 /* Pick a sensible default if no size given */
369 if (hash_size == 0)
370 hash_size = 256;
371
372 l->head = NULL;
373 l->tail = NULL;
374 l->hash_size = hash_size;
375 l->hash = xzmalloc(hash_size * sizeof(struct sync_msgid *));
376 l->count = 0;
377 l->toupload = 0;
378
379 return(l);
380 }
381
sync_msgid_insert(struct sync_msgid_list * l,const struct message_guid * guid)382 struct sync_msgid *sync_msgid_insert(struct sync_msgid_list *l,
383 const struct message_guid *guid)
384 {
385 struct sync_msgid *msgid;
386 int offset;
387
388 if (message_guid_isnull(guid))
389 return NULL;
390
391 offset = message_guid_hash(guid, l->hash_size);
392
393 /* do we already have it? Don't add it again */
394 for (msgid = l->hash[offset] ; msgid ; msgid = msgid->hash_next) {
395 if (message_guid_equal(&msgid->guid, guid))
396 return msgid;
397 }
398
399 msgid = xzmalloc(sizeof(struct sync_msgid));
400 msgid->need_upload = 1;
401 message_guid_copy(&msgid->guid, guid);
402
403 l->count++;
404 l->toupload++;
405
406 if (l->tail)
407 l->tail = l->tail->next = msgid;
408 else
409 l->head = l->tail = msgid;
410
411 /* Insert at start of list */
412 msgid->hash_next = l->hash[offset];
413 l->hash[offset] = msgid;
414
415 return msgid;
416 }
417
sync_msgid_remove(struct sync_msgid_list * l,const struct message_guid * guid)418 void sync_msgid_remove(struct sync_msgid_list *l,
419 const struct message_guid *guid)
420 {
421 int offset = message_guid_hash(guid, l->hash_size);
422 struct sync_msgid *msgid;
423
424 if (message_guid_isnull(guid)) return;
425
426 for (msgid = l->hash[offset] ; msgid ; msgid = msgid->hash_next) {
427 if (message_guid_equal(&msgid->guid, guid)) {
428 message_guid_set_null(&msgid->guid);
429 return;
430 }
431 }
432 }
433
sync_msgid_list_free(struct sync_msgid_list ** lp)434 void sync_msgid_list_free(struct sync_msgid_list **lp)
435 {
436 struct sync_msgid_list *l = *lp;
437 struct sync_msgid *current, *next;
438
439 current = l->head;
440 while (current) {
441 next = current->next;
442 free(current->fname);
443 free(current);
444 current = next;
445 }
446 free(l->hash);
447 free(l);
448
449 *lp = NULL;
450 }
451
sync_msgid_lookup(const struct sync_msgid_list * l,const struct message_guid * guid)452 struct sync_msgid *sync_msgid_lookup(const struct sync_msgid_list *l,
453 const struct message_guid *guid)
454 {
455 int offset = message_guid_hash(guid, l->hash_size);
456 struct sync_msgid *msgid;
457
458 if (message_guid_isnull(guid))
459 return(NULL);
460
461 for (msgid = l->hash[offset] ; msgid ; msgid = msgid->hash_next) {
462 if (message_guid_equal(&msgid->guid, guid))
463 return(msgid);
464 }
465
466 return(NULL);
467 }
468
sync_reserve_list_create(int hash_size)469 struct sync_reserve_list *sync_reserve_list_create(int hash_size)
470 {
471 struct sync_reserve_list *l = xmalloc(sizeof(struct sync_reserve_list));
472
473 l->head = NULL;
474 l->tail = NULL;
475 l->hash_size = hash_size;
476
477 return l;
478 }
479
sync_reserve_partlist(struct sync_reserve_list * l,const char * part)480 struct sync_msgid_list *sync_reserve_partlist(struct sync_reserve_list *l,
481 const char *part)
482 {
483 struct sync_reserve *item;
484
485 for (item = l->head; item; item = item->next) {
486 if (!strcmp(item->part, part))
487 return item->list;
488 }
489
490 /* not found, create it */
491 item = xmalloc(sizeof(struct sync_reserve));
492 item->part = xstrdup(part);
493 item->next = NULL;
494 item->list = sync_msgid_list_create(l->hash_size);
495
496 if (l->tail)
497 l->tail = l->tail->next = item;
498 else
499 l->tail = l->head = item;
500
501 return item->list;
502 }
503
sync_reserve_list_free(struct sync_reserve_list ** lp)504 void sync_reserve_list_free(struct sync_reserve_list **lp)
505 {
506 struct sync_reserve_list *l = *lp;
507 struct sync_reserve *current, *next;
508
509 current = l->head;
510 while (current) {
511 next = current->next;
512 sync_msgid_list_free(¤t->list);
513 free(current->part);
514 free(current);
515 current = next;
516 }
517 free(l);
518
519 *lp = NULL;
520 }
521
522 /* ====================================================================== */
523
sync_folder_list_create(void)524 struct sync_folder_list *sync_folder_list_create(void)
525 {
526 struct sync_folder_list *l = xzmalloc(sizeof (struct sync_folder_list));
527
528 l->head = NULL;
529 l->tail = NULL;
530 l->count = 0;
531
532 return(l);
533 }
534
sync_folder_list_add(struct sync_folder_list * l,const char * uniqueid,const char * name,uint32_t mbtype,const char * part,const char * acl,uint32_t options,uint32_t uidvalidity,uint32_t last_uid,modseq_t highestmodseq,struct synccrcs synccrcs,uint32_t recentuid,time_t recenttime,time_t pop3_last_login,time_t pop3_show_after,struct sync_annot_list * annots,modseq_t xconvmodseq,modseq_t raclmodseq,modseq_t foldermodseq,int ispartial)535 struct sync_folder *sync_folder_list_add(struct sync_folder_list *l,
536 const char *uniqueid, const char *name,
537 uint32_t mbtype,
538 const char *part, const char *acl,
539 uint32_t options,
540 uint32_t uidvalidity,
541 uint32_t last_uid,
542 modseq_t highestmodseq,
543 struct synccrcs synccrcs,
544 uint32_t recentuid,
545 time_t recenttime,
546 time_t pop3_last_login,
547 time_t pop3_show_after,
548 struct sync_annot_list *annots,
549 modseq_t xconvmodseq,
550 modseq_t raclmodseq,
551 modseq_t foldermodseq,
552 int ispartial)
553 {
554 struct sync_folder *result = xzmalloc(sizeof(struct sync_folder));
555
556 if (l->tail)
557 l->tail = l->tail->next = result;
558 else
559 l->head = l->tail = result;
560
561 l->count++;
562
563 result->next = NULL;
564 result->mailbox = NULL;
565
566 result->uniqueid = xstrdupnull(uniqueid);
567 result->name = xstrdupnull(name);
568 result->mbtype = mbtype;
569 result->part = xstrdupnull(part);
570 result->acl = xstrdupnull(acl);
571 result->uidvalidity = uidvalidity;
572 result->last_uid = last_uid;
573 result->highestmodseq = highestmodseq;
574 result->options = options;
575 result->synccrcs = synccrcs;
576 result->recentuid = recentuid;
577 result->recenttime = recenttime;
578 result->pop3_last_login = pop3_last_login;
579 result->pop3_show_after = pop3_show_after;
580 result->annots = annots; /* NOTE: not a copy! */
581 result->xconvmodseq = xconvmodseq;
582 result->raclmodseq = raclmodseq;
583 result->foldermodseq = foldermodseq;
584 result->ispartial = ispartial;
585
586 result->mark = 0;
587 result->reserve = 0;
588
589 return(result);
590 }
591
sync_folder_lookup(struct sync_folder_list * l,const char * uniqueid)592 struct sync_folder *sync_folder_lookup(struct sync_folder_list *l,
593 const char *uniqueid)
594 {
595 struct sync_folder *p;
596
597 for (p = l->head; p; p = p->next) {
598 if (!strcmp(p->uniqueid, uniqueid))
599 return p;
600 }
601 return NULL;
602 }
603
sync_folder_list_free(struct sync_folder_list ** lp)604 void sync_folder_list_free(struct sync_folder_list **lp)
605 {
606 struct sync_folder_list *l = *lp;
607 struct sync_folder *current, *next;
608
609 if (!l) return;
610
611 current = l->head;
612 while (current) {
613 next = current->next;
614 free(current->uniqueid);
615 free(current->name);
616 free(current->part);
617 free(current->acl);
618 sync_annot_list_free(¤t->annots);
619 free(current);
620 current = next;
621 }
622 free(l);
623 *lp = NULL;
624 }
625
626 /* ====================================================================== */
627
sync_rename_list_create(void)628 struct sync_rename_list *sync_rename_list_create(void)
629 {
630 struct sync_rename_list *l = xzmalloc(sizeof(struct sync_rename_list));
631
632 l->head = NULL;
633 l->tail = NULL;
634 l->count = 0;
635 l->done = 0;
636
637 return(l);
638 }
639
sync_rename_list_add(struct sync_rename_list * l,const char * uniqueid,const char * oldname,const char * newname,const char * partition,unsigned uidvalidity)640 struct sync_rename *sync_rename_list_add(struct sync_rename_list *l,
641 const char *uniqueid, const char *oldname,
642 const char *newname, const char *partition,
643 unsigned uidvalidity)
644 {
645 struct sync_rename *result
646 = xzmalloc(sizeof(struct sync_rename));
647
648 if (l->tail)
649 l->tail = l->tail->next = result;
650 else
651 l->head = l->tail = result;
652
653 l->count++;
654
655 result->next = NULL;
656 result->uniqueid = xstrdup(uniqueid);
657 result->oldname = xstrdup(oldname);
658 result->newname = xstrdup(newname);
659 result->part = xstrdup(partition);
660 result->uidvalidity = uidvalidity;
661 result->done = 0;
662
663 return result;
664 }
665
sync_rename_lookup(struct sync_rename_list * l,const char * oldname)666 struct sync_rename *sync_rename_lookup(struct sync_rename_list *l,
667 const char *oldname)
668 {
669 struct sync_rename *p;
670
671 for (p = l->head; p; p = p->next) {
672 if (!strcmp(p->oldname, oldname))
673 return p;
674 }
675
676 return NULL;
677 }
678
sync_rename_list_free(struct sync_rename_list ** lp)679 void sync_rename_list_free(struct sync_rename_list **lp)
680 {
681 struct sync_rename_list *l = *lp;
682 struct sync_rename *current, *next;
683
684 if (!l) return;
685
686 current = l->head;
687 while (current) {
688 next = current->next;
689 free(current->uniqueid);
690 free(current->oldname);
691 free(current->newname);
692 free(current->part);
693 free(current);
694 current = next;
695 }
696 free(l);
697 *lp = NULL;
698 }
699
700 /* ====================================================================== */
701
sync_quota_list_create(void)702 struct sync_quota_list *sync_quota_list_create(void)
703 {
704 struct sync_quota_list *l = xzmalloc(sizeof(struct sync_quota_list));
705
706 l->head = NULL;
707 l->tail = NULL;
708 l->count = 0;
709 l->done = 0;
710
711 return(l);
712 }
713
sync_quota_list_add(struct sync_quota_list * l,const char * root)714 struct sync_quota *sync_quota_list_add(struct sync_quota_list *l,
715 const char *root)
716 {
717 struct sync_quota *result
718 = xzmalloc(sizeof(struct sync_quota));
719 int res;
720
721 if (l->tail)
722 l->tail = l->tail->next = result;
723 else
724 l->head = l->tail = result;
725
726 l->count++;
727
728 result->next = NULL;
729 result->root = xstrdup(root);
730 for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++)
731 result->limits[res] = QUOTA_UNLIMITED;
732 result->done = 0;
733
734 return result;
735 }
736
sync_quota_lookup(struct sync_quota_list * l,const char * name)737 struct sync_quota *sync_quota_lookup(struct sync_quota_list *l,
738 const char *name)
739 {
740 struct sync_quota *p;
741
742 for (p = l->head; p; p = p->next) {
743 if (!strcmp(p->root, name))
744 return p;
745 }
746
747 return NULL;
748 }
749
sync_quota_list_free(struct sync_quota_list ** lp)750 void sync_quota_list_free(struct sync_quota_list **lp)
751 {
752 struct sync_quota_list *l = *lp;
753 struct sync_quota *current, *next;
754
755 if (!l) return;
756
757 current = l->head;
758 while (current) {
759 next = current->next;
760 free(current->root);
761 free(current);
762 current = next;
763 }
764 free(l);
765 *lp = NULL;
766 }
767
sync_encode_quota_limits(struct dlist * kl,const quota_t limits[QUOTA_NUMRESOURCES])768 void sync_encode_quota_limits(struct dlist *kl, const quota_t limits[QUOTA_NUMRESOURCES])
769 {
770 int res;
771
772 /*
773 * For backwards compatibility, we encode the STORAGE limit as LIMIT
774 * and we always report it even if it's QUOTA_UNLIMITED. This is
775 * kinda screwed up but should work. For QUOTA_UNLIMITED < 0, we
776 * send a very large unsigned number across the wire, and parse it
777 * back as QUOTA_UNLIMITED at the other end. Spit and string.
778 */
779 dlist_setnum32(kl, "LIMIT", limits[QUOTA_STORAGE]);
780
781 for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) {
782 if (limits[res] >= 0)
783 dlist_setnum32(kl, quota_names[res], limits[res]);
784 }
785 }
786
sync_decode_quota_limits(struct dlist * kl,quota_t limits[QUOTA_NUMRESOURCES])787 void sync_decode_quota_limits(/*const*/ struct dlist *kl, quota_t limits[QUOTA_NUMRESOURCES])
788 {
789 uint32_t limit = 0;
790 int res;
791
792 for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++)
793 limits[res] = QUOTA_UNLIMITED;
794
795 /* For backwards compatibility */
796 if (dlist_getnum32(kl, "LIMIT", &limit)) {
797 if (limit == UINT_MAX)
798 limits[QUOTA_STORAGE] = -1;
799 else
800 limits[QUOTA_STORAGE] = limit;
801 }
802
803 for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) {
804 if (dlist_getnum32(kl, quota_names[res], &limit))
805 limits[res] = limit;
806 }
807 }
808
809 /* ====================================================================== */
810
sync_sieve_list_create(void)811 struct sync_sieve_list *sync_sieve_list_create(void)
812 {
813 struct sync_sieve_list *l = xzmalloc(sizeof (struct sync_sieve_list));
814
815 l->head = NULL;
816 l->tail = NULL;
817 l->count = 0;
818
819 return l;
820 }
821
sync_sieve_list_add(struct sync_sieve_list * l,const char * name,time_t last_update,struct message_guid * guidp,int active)822 void sync_sieve_list_add(struct sync_sieve_list *l, const char *name,
823 time_t last_update, struct message_guid *guidp,
824 int active)
825 {
826 struct sync_sieve *item = xzmalloc(sizeof(struct sync_sieve));
827
828 item->name = xstrdup(name);
829 item->last_update = last_update;
830 item->active = active;
831 message_guid_copy(&item->guid, guidp);
832 item->mark = 0;
833
834 if (l->tail)
835 l->tail = l->tail->next = item;
836 else
837 l->head = l->tail = item;
838
839 l->count++;
840 }
841
sync_sieve_lookup(struct sync_sieve_list * l,const char * name)842 struct sync_sieve *sync_sieve_lookup(struct sync_sieve_list *l, const char *name)
843 {
844 struct sync_sieve *p;
845
846 for (p = l->head; p; p = p->next) {
847 if (!strcmp(p->name, name))
848 return p;
849 }
850
851 return NULL;
852 }
853
sync_sieve_list_set_active(struct sync_sieve_list * l,const char * name)854 void sync_sieve_list_set_active(struct sync_sieve_list *l, const char *name)
855 {
856 struct sync_sieve *item;
857
858 for (item = l->head; item; item = item->next) {
859 if (!strcmp(item->name, name)) {
860 item->active = 1;
861 break;
862 }
863 }
864 }
865
sync_sieve_list_free(struct sync_sieve_list ** lp)866 void sync_sieve_list_free(struct sync_sieve_list **lp)
867 {
868 struct sync_sieve_list *l = *lp;
869 struct sync_sieve *current, *next;
870
871 current = l->head;
872 while (current) {
873 next = current->next;
874 free(current->name);
875 free(current);
876 current = next;
877 }
878 free(l);
879 *lp = NULL;
880 }
881
sync_sieve_list_generate(const char * userid)882 struct sync_sieve_list *sync_sieve_list_generate(const char *userid)
883 {
884 struct sync_sieve_list *list = sync_sieve_list_create();
885 const char *sieve_path = user_sieve_path(userid);
886 char filename[2048];
887 char active[2048];
888 DIR *mbdir;
889 struct dirent *next = NULL;
890 struct stat sbuf;
891 int count;
892
893 mbdir = opendir(sieve_path);
894 if (!mbdir) return list;
895
896 active[0] = '\0';
897 while((next = readdir(mbdir)) != NULL) {
898 uint32_t size;
899 char *result;
900 struct message_guid guid;
901 if (!strcmp(next->d_name, ".") || !strcmp(next->d_name, ".."))
902 continue;
903
904 snprintf(filename, sizeof(filename), "%s/%s",
905 sieve_path, next->d_name);
906
907 if (stat(filename, &sbuf) < 0)
908 continue;
909
910 if (!strcmp(next->d_name, "defaultbc")) {
911 if (sbuf.st_mode & S_IFLNK) {
912 count = readlink(filename, active, 2047);
913
914 if (count >= 0) {
915 active[count] = '\0';
916 } else {
917 /* XXX Report problem? */
918 }
919 }
920 continue;
921 }
922
923 /* calculate the sha1 on the fly, relatively cheap */
924 result = sync_sieve_read(userid, next->d_name, &size);
925 if (!result) continue;
926 message_guid_generate(&guid, result, size);
927
928 sync_sieve_list_add(list, next->d_name, sbuf.st_mtime, &guid, 0);
929 free(result);
930 }
931 closedir(mbdir);
932
933 if (active[0])
934 sync_sieve_list_set_active(list, active);
935
936 return list;
937 }
938
sync_sieve_read(const char * userid,const char * name,uint32_t * sizep)939 char *sync_sieve_read(const char *userid, const char *name, uint32_t *sizep)
940 {
941 const char *sieve_path = user_sieve_path(userid);
942 char filename[2048];
943 FILE *file;
944 struct stat sbuf;
945 char *result, *s;
946 uint32_t count;
947 int c;
948
949 if (sizep)
950 *sizep = 0;
951
952 snprintf(filename, sizeof(filename), "%s/%s", sieve_path, name);
953
954 file = fopen(filename, "r");
955 if (!file) return NULL;
956
957 if (fstat(fileno(file), &sbuf) < 0) {
958 fclose(file);
959 return(NULL);
960 }
961
962 count = sbuf.st_size;
963 s = result = xmalloc(count+1);
964
965 if (sizep)
966 *sizep = count;
967
968 while (count > 0) {
969 if ((c=fgetc(file)) == EOF)
970 break;
971 *s++ = c;
972 count--;
973 }
974 fclose(file);
975 *s = '\0';
976
977 return(result);
978 }
979
sync_sieve_upload(const char * userid,const char * name,time_t last_update,const char * content,size_t len)980 int sync_sieve_upload(const char *userid, const char *name,
981 time_t last_update, const char *content,
982 size_t len)
983 {
984 const char *sieve_path = user_sieve_path(userid);
985 char tmpname[2048];
986 char newname[2048];
987 char *ext;
988 FILE *file;
989 int r = 0;
990 struct stat sbuf;
991 struct utimbuf utimbuf;
992
993 ext = strrchr(name, '.');
994 if (ext && !strcmp(ext, ".bc")) {
995 /* silently ignore attempts to upload compiled bytecode */
996 return 0;
997 }
998
999 if (stat(sieve_path, &sbuf) == -1 && errno == ENOENT) {
1000 if (cyrus_mkdir(sieve_path, 0755) == -1) return IMAP_IOERROR;
1001 if (mkdir(sieve_path, 0755) == -1 && errno != EEXIST) {
1002 syslog(LOG_ERR, "Failed to create %s:%m", sieve_path);
1003 return IMAP_IOERROR;
1004 }
1005 }
1006
1007 snprintf(tmpname, sizeof(tmpname), "%s/sync_tmp-%lu",
1008 sieve_path, (unsigned long)getpid());
1009 snprintf(newname, sizeof(newname), "%s/%s", sieve_path, name);
1010
1011 if ((file=fopen(tmpname, "w")) == NULL) {
1012 return IMAP_IOERROR;
1013 }
1014
1015 /* XXX - error handling */
1016 fwrite(content, 1, len, file);
1017
1018 if ((fflush(file) != 0) || (fsync(fileno(file)) < 0))
1019 r = IMAP_IOERROR;
1020
1021 fclose(file);
1022
1023 utimbuf.actime = time(NULL);
1024 utimbuf.modtime = last_update;
1025
1026 if (!r && (utime(tmpname, &utimbuf) < 0))
1027 r = IMAP_IOERROR;
1028
1029 if (!r && (rename(tmpname, newname) < 0))
1030 r = IMAP_IOERROR;
1031
1032 #ifdef USE_SIEVE
1033 if (!r) {
1034 r = sieve_rebuild(newname, NULL, /*force*/ 1, NULL);
1035 if (r == SIEVE_PARSE_ERROR || r == SIEVE_FAIL)
1036 r = IMAP_SYNC_BADSIEVE;
1037 }
1038 #endif
1039
1040 sync_log_sieve(userid);
1041
1042 return r;
1043 }
1044
sync_sieve_activate(const char * userid,const char * name)1045 int sync_sieve_activate(const char *userid, const char *name)
1046 {
1047 const char *sieve_path = user_sieve_path(userid);
1048 char target[2048];
1049 char active[2048];
1050 char tmp[2048+4]; /* +4 for ".NEW" */
1051
1052 snprintf(target, sizeof(target), "%s", name);
1053 snprintf(active, sizeof(active), "%s/%s", sieve_path, "defaultbc");
1054 snprintf(tmp, sizeof(tmp), "%s.NEW", active);
1055
1056 #ifdef USE_SIEVE
1057 char *bc_fname = strconcat(sieve_path, "/", target, NULL);
1058 sieve_rebuild(NULL, bc_fname, 0, NULL);
1059 free(bc_fname);
1060 #endif
1061
1062 /* N.B symlink() does NOT verify target for anything but string validity,
1063 * so activation of a nonexistent script will report success.
1064 */
1065 if (symlink(target, tmp) < 0) {
1066 syslog(LOG_ERR, "IOERROR: unable to symlink %s as %s: %m", target, tmp);
1067 return IMAP_IOERROR;
1068 }
1069
1070 if (rename(tmp, active) < 0) {
1071 syslog(LOG_ERR, "IOERROR: unable to rename %s to %s: %m", tmp, active);
1072 unlink(tmp);
1073 return IMAP_IOERROR;
1074 }
1075
1076 sync_log_sieve(userid);
1077
1078 return 0;
1079 }
1080
sync_sieve_deactivate(const char * userid)1081 int sync_sieve_deactivate(const char *userid)
1082 {
1083 const char *sieve_path = user_sieve_path(userid);
1084 char active[2048];
1085
1086 snprintf(active, sizeof(active), "%s/defaultbc", sieve_path);
1087 unlink(active);
1088
1089 sync_log_sieve(userid);
1090
1091 return(0);
1092 }
1093
sync_sieve_delete(const char * userid,const char * name)1094 int sync_sieve_delete(const char *userid, const char *name)
1095 {
1096 const char *sieve_path = user_sieve_path(userid);
1097 char filename[2048];
1098 char active[2048];
1099 DIR *mbdir;
1100 struct dirent *next = NULL;
1101 struct stat sbuf;
1102 int is_default = 0;
1103 int count;
1104
1105 if (!(mbdir = opendir(sieve_path)))
1106 return(IMAP_IOERROR);
1107
1108 while((next = readdir(mbdir)) != NULL) {
1109 if(!strcmp(next->d_name, ".") || !strcmp(next->d_name, ".."))
1110 continue;
1111
1112 snprintf(filename, sizeof(filename), "%s/%s",
1113 sieve_path, next->d_name);
1114
1115 if (stat(filename, &sbuf) < 0)
1116 continue;
1117
1118 if (!strcmp(next->d_name, "defaultbc")) {
1119 if (sbuf.st_mode & S_IFLNK) {
1120 count = readlink(filename, active, 2047);
1121
1122 if (count >= 0) {
1123 active[count] = '\0';
1124 if (!strcmp(active, name))
1125 is_default = 1;
1126 }
1127 }
1128 continue;
1129 }
1130 }
1131 closedir(mbdir);
1132
1133 if (is_default) {
1134 snprintf(filename, sizeof(filename), "%s/defaultbc", sieve_path);
1135 unlink(filename);
1136 }
1137
1138 snprintf(filename, sizeof(filename), "%s/%s", sieve_path, name);
1139 unlink(filename);
1140
1141 sync_log_sieve(userid);
1142
1143 return(0);
1144 }
1145
1146 /* ====================================================================== */
1147
sync_name_list_create(void)1148 struct sync_name_list *sync_name_list_create(void)
1149 {
1150 struct sync_name_list *l = xzmalloc(sizeof (struct sync_name_list));
1151 l->head = NULL;
1152 l->tail = NULL;
1153 l->count = 0;
1154 l->marked = 0;
1155 return l;
1156 }
1157
sync_name_list_add(struct sync_name_list * l,const char * name)1158 struct sync_name *sync_name_list_add(struct sync_name_list *l,
1159 const char *name)
1160 {
1161 struct sync_name *item = xzmalloc(sizeof(struct sync_name));
1162
1163 if (l->tail)
1164 l->tail = l->tail->next = item;
1165 else
1166 l->head = l->tail = item;
1167
1168 l->count++;
1169
1170 item->next = NULL;
1171 item->name = xstrdup(name);
1172 item->mark = 0;
1173
1174 return item;
1175 }
1176
sync_name_lookup(struct sync_name_list * l,const char * name)1177 struct sync_name *sync_name_lookup(struct sync_name_list *l,
1178 const char *name)
1179 {
1180 struct sync_name *p;
1181
1182 for (p = l->head; p; p = p->next)
1183 if (!strcmp(p->name, name))
1184 return p;
1185
1186 return NULL;
1187 }
1188
sync_name_list_free(struct sync_name_list ** lp)1189 void sync_name_list_free(struct sync_name_list **lp)
1190 {
1191 struct sync_name *current, *next;
1192
1193 current = (*lp)->head;
1194 while (current) {
1195 next = current->next;
1196 free(current->name);
1197 free(current);
1198 current = next;
1199 }
1200 free(*lp);
1201 *lp = NULL;
1202 }
1203
1204 /* ====================================================================== */
1205
sync_seen_list_create(void)1206 struct sync_seen_list *sync_seen_list_create(void)
1207 {
1208 struct sync_seen_list *l = xzmalloc(sizeof (struct sync_seen_list));
1209 l->head = NULL;
1210 l->tail = NULL;
1211 l->count = 0;
1212 return l;
1213 }
1214
sync_seen_list_add(struct sync_seen_list * l,const char * uniqueid,time_t lastread,unsigned lastuid,time_t lastchange,const char * seenuids)1215 struct sync_seen *sync_seen_list_add(struct sync_seen_list *l,
1216 const char *uniqueid, time_t lastread,
1217 unsigned lastuid, time_t lastchange,
1218 const char *seenuids)
1219 {
1220 struct sync_seen *item = xzmalloc(sizeof(struct sync_seen));
1221
1222 if (l->tail)
1223 l->tail = l->tail->next = item;
1224 else
1225 l->head = l->tail = item;
1226
1227 l->count++;
1228
1229 item->next = NULL;
1230 item->uniqueid = xstrdup(uniqueid);
1231 item->sd.lastread = lastread;
1232 item->sd.lastuid = lastuid;
1233 item->sd.lastchange = lastchange;
1234 item->sd.seenuids = xstrdup(seenuids);
1235 item->mark = 0;
1236
1237 return item;
1238 }
1239
sync_seen_list_lookup(struct sync_seen_list * l,const char * uniqueid)1240 struct sync_seen *sync_seen_list_lookup(struct sync_seen_list *l,
1241 const char *uniqueid)
1242 {
1243 struct sync_seen *p;
1244
1245 for (p = l->head; p; p = p->next)
1246 if (!strcmp(p->uniqueid, uniqueid))
1247 return p;
1248
1249 return NULL;
1250 }
1251
sync_seen_list_free(struct sync_seen_list ** lp)1252 void sync_seen_list_free(struct sync_seen_list **lp)
1253 {
1254 struct sync_seen *current, *next;
1255
1256 current = (*lp)->head;
1257 while (current) {
1258 next = current->next;
1259 free(current->uniqueid);
1260 seen_freedata(¤t->sd);
1261 free(current);
1262 current = next;
1263 }
1264 free(*lp);
1265 *lp = NULL;
1266 }
1267
1268 /* ====================================================================== */
1269
sync_annot_list_create(void)1270 struct sync_annot_list *sync_annot_list_create(void)
1271 {
1272 struct sync_annot_list *l = xzmalloc(sizeof (struct sync_annot_list));
1273
1274 l->head = NULL;
1275 l->tail = NULL;
1276 l->count = 0;
1277 return(l);
1278 }
1279
sync_annot_list_add(struct sync_annot_list * l,const char * entry,const char * userid,const struct buf * value,modseq_t modseq)1280 void sync_annot_list_add(struct sync_annot_list *l,
1281 const char *entry, const char *userid,
1282 const struct buf *value,
1283 modseq_t modseq)
1284 {
1285 struct sync_annot *item = xzmalloc(sizeof(struct sync_annot));
1286
1287 item->entry = xstrdupnull(entry);
1288 item->userid = xstrdupnull(userid);
1289 buf_copy(&item->value, value);
1290 item->mark = 0;
1291 item->modseq = modseq;
1292
1293 if (l->tail)
1294 l->tail = l->tail->next = item;
1295 else
1296 l->head = l->tail = item;
1297
1298 l->count++;
1299 }
1300
sync_annot_list_free(struct sync_annot_list ** lp)1301 void sync_annot_list_free(struct sync_annot_list **lp)
1302 {
1303 struct sync_annot_list *l = *lp;
1304 struct sync_annot *current, *next;
1305
1306 if (!l)
1307 return;
1308 current = l->head;
1309 while (current) {
1310 next = current->next;
1311 free(current->entry);
1312 free(current->userid);
1313 buf_free(¤t->value);
1314 free(current);
1315 current = next;
1316 }
1317 free(l);
1318 *lp = NULL;
1319 }
1320
1321 /* ====================================================================== */
1322
sync_action_list_create(void)1323 struct sync_action_list *sync_action_list_create(void)
1324 {
1325 struct sync_action_list *l = xzmalloc(sizeof (struct sync_action_list));
1326
1327 l->head = NULL;
1328 l->tail = NULL;
1329 l->count = 0;
1330
1331 return(l);
1332 }
1333
sync_action_list_add(struct sync_action_list * l,const char * name,const char * user)1334 void sync_action_list_add(struct sync_action_list *l,
1335 const char *name, const char *user)
1336 {
1337 struct sync_action *current;
1338
1339 if (!name && !user) return;
1340
1341 for (current = l->head ; current ; current = current->next) {
1342 if ((!name || (current->name && !strcmp(current->name, name))) &&
1343 (!user || (current->user && !strcmp(current->user, user)))) {
1344 current->active = 1; /* Make sure active */
1345 return;
1346 } else {
1347 /* name and/or user don't match current: no match possible */
1348 }
1349 }
1350
1351 current = xzmalloc(sizeof(struct sync_action));
1352 current->next = NULL;
1353 current->name = xstrdupnull(name);
1354 current->user = xstrdupnull(user);
1355 current->active = 1;
1356
1357 if (l->tail)
1358 l->tail = l->tail->next = current;
1359 else
1360 l->head = l->tail = current;
1361
1362 l->count++;
1363
1364 }
1365
sync_action_list_free(struct sync_action_list ** lp)1366 void sync_action_list_free(struct sync_action_list **lp)
1367 {
1368 struct sync_action_list *l = *lp;
1369 struct sync_action *current, *next;
1370
1371 current = l->head;
1372 while (current) {
1373 next = current->next;
1374
1375 if (current->name) free(current->name);
1376 if (current->user) free(current->user);
1377
1378 free(current);
1379 current = next;
1380 }
1381 free(l);
1382 *lp = NULL;
1383 }
1384
1385 /* ====================================================================== */
1386
read_one_annot(const char * mailbox,uint32_t uid,const char * entry,const char * userid,const struct buf * value,const struct annotate_metadata * mdata,void * rock)1387 static int read_one_annot(const char *mailbox __attribute__((unused)),
1388 uint32_t uid __attribute__((unused)),
1389 const char *entry,
1390 const char *userid,
1391 const struct buf *value,
1392 const struct annotate_metadata *mdata,
1393 void *rock)
1394 {
1395 struct sync_annot_list **salp = (struct sync_annot_list **)rock;
1396
1397 if (!*salp)
1398 *salp = sync_annot_list_create();
1399 sync_annot_list_add(*salp, entry, userid, value, mdata->modseq);
1400 return 0;
1401 }
1402
1403 /*
1404 * Read all the annotations in the local annotations database
1405 * for the message given by @mailbox and @record, returning them
1406 * as a new sync_annot_list. The caller should free the new
1407 * list with sync_annot_list_free().
1408 * If record is NULL, return the mailbox annotations
1409 * If since_modseq is greated than zero, return annotations
1410 * add or changed since modseq (exclusively since_modseq).
1411 * If flags is set to ANNOTATE_TOMBSTONES, also return
1412 * deleted annotations. Deleted annotations have a zero value.
1413 *
1414 * Returns: non-zero on error,
1415 * resulting sync_annot_list in *@resp
1416 */
read_annotations(const struct mailbox * mailbox,const struct index_record * record,struct sync_annot_list ** resp,modseq_t since_modseq,int flags)1417 int read_annotations(const struct mailbox *mailbox,
1418 const struct index_record *record,
1419 struct sync_annot_list **resp,
1420 modseq_t since_modseq __attribute__((unused)),
1421 int flags)
1422 {
1423 *resp = NULL;
1424 return annotatemore_findall(mailbox->name, record ? record->uid : 0,
1425 /* all entries*/"*", /*XXX since_modseq*/0,
1426 read_one_annot, (void *)resp, flags);
1427 }
1428
1429 /*
1430 * Encode the given list of annotations @sal as a dlist
1431 * structure with the given @parent.
1432 */
encode_annotations(struct dlist * parent,struct mailbox * mailbox,const struct index_record * record,const struct sync_annot_list * sal)1433 void encode_annotations(struct dlist *parent,
1434 struct mailbox *mailbox,
1435 const struct index_record *record,
1436 const struct sync_annot_list *sal)
1437 {
1438 const struct sync_annot *sa;
1439 struct dlist *annots = NULL;
1440 struct dlist *aa;
1441
1442 if (sal) {
1443 for (sa = sal->head ; sa ; sa = sa->next) {
1444 if (!annots)
1445 annots = dlist_newlist(parent, "ANNOTATIONS");
1446
1447 aa = dlist_newkvlist(annots, NULL);
1448 dlist_setatom(aa, "ENTRY", sa->entry);
1449 dlist_setatom(aa, "USERID", sa->userid);
1450 dlist_setnum64(aa, "MODSEQ", sa->modseq);
1451 dlist_setmap(aa, "VALUE", sa->value.s, sa->value.len);
1452 }
1453 }
1454
1455 if (record && record->cid && mailbox->i.minor_version >= 13) {
1456 if (!annots)
1457 annots = dlist_newlist(parent, "ANNOTATIONS");
1458 aa = dlist_newkvlist(annots, NULL);
1459 dlist_setatom(aa, "ENTRY", IMAP_ANNOT_NS "thrid");
1460 dlist_setatom(aa, "USERID", "");
1461 dlist_setnum64(aa, "MODSEQ", 0);
1462 dlist_sethex64(aa, "VALUE", record->cid);
1463 }
1464
1465 if (record && record->savedate && mailbox->i.minor_version >= 15) {
1466 if (!annots)
1467 annots = dlist_newlist(parent, "ANNOTATIONS");
1468 aa = dlist_newkvlist(annots, NULL);
1469 dlist_setatom(aa, "ENTRY", IMAP_ANNOT_NS "savedate");
1470 dlist_setatom(aa, "USERID", "");
1471 dlist_setnum64(aa, "MODSEQ", 0);
1472 dlist_setnum32(aa, "VALUE", record->savedate);
1473 }
1474
1475 if (record && record->createdmodseq && mailbox->i.minor_version >= 16) {
1476 if (!annots)
1477 annots = dlist_newlist(parent, "ANNOTATIONS");
1478 aa = dlist_newkvlist(annots, NULL);
1479 dlist_setatom(aa, "ENTRY", IMAP_ANNOT_NS "createdmodseq");
1480 dlist_setatom(aa, "USERID", "");
1481 dlist_setnum64(aa, "MODSEQ", 0);
1482 dlist_setnum64(aa, "VALUE", record->createdmodseq);
1483 }
1484 }
1485
1486 /*
1487 * Decode the given list of encoded annotations @annots and create
1488 * a new sync_annot_list in *@salp, which the caller should free
1489 * with sync_annot_list_free().
1490 *
1491 * Returns: zero on success or Cyrus error code.
1492 */
decode_annotations(struct dlist * annots,struct sync_annot_list ** salp,struct mailbox * mailbox,struct index_record * record)1493 int decode_annotations(/*const*/struct dlist *annots,
1494 struct sync_annot_list **salp,
1495 struct mailbox *mailbox,
1496 struct index_record *record)
1497 {
1498 struct dlist *aa;
1499 const char *entry;
1500 const char *userid;
1501 modseq_t modseq;
1502
1503 *salp = NULL;
1504 if (strcmp(annots->name, "ANNOTATIONS"))
1505 return IMAP_PROTOCOL_BAD_PARAMETERS;
1506
1507 for (aa = annots->head ; aa ; aa = aa->next) {
1508 struct buf value = BUF_INITIALIZER;
1509 if (!*salp)
1510 *salp = sync_annot_list_create();
1511 if (!dlist_getatom(aa, "ENTRY", &entry))
1512 return IMAP_PROTOCOL_BAD_PARAMETERS;
1513 if (!dlist_getatom(aa, "USERID", &userid))
1514 return IMAP_PROTOCOL_BAD_PARAMETERS;
1515 if (!dlist_getnum64(aa, "MODSEQ", &modseq))
1516 return IMAP_PROTOCOL_BAD_PARAMETERS;
1517 if (!dlist_getbuf(aa, "VALUE", &value))
1518 return IMAP_PROTOCOL_BAD_PARAMETERS;
1519 if (!strcmp(entry, IMAP_ANNOT_NS "thrid") &&
1520 record && mailbox->i.minor_version >= 13) {
1521 const char *p = buf_cstring(&value);
1522 parsehex(p, &p, 16, &record->cid);
1523 }
1524 else if (!strcmp(entry, IMAP_ANNOT_NS "savedate") &&
1525 record && mailbox->i.minor_version >= 15) {
1526 const char *p = buf_cstring(&value);
1527 bit64 newval;
1528 parsenum(p, &p, 0, &newval);
1529 record->savedate = newval;
1530 }
1531 else if (!strcmp(entry, IMAP_ANNOT_NS "createdmodseq") &&
1532 record && mailbox->i.minor_version >= 16) {
1533 const char *p = buf_cstring(&value);
1534 bit64 newval;
1535 parsenum(p, &p, 0, &newval);
1536 record->createdmodseq = newval;
1537 }
1538 else if (!strcmp(entry, IMAP_ANNOT_NS "basethrid") && record) {
1539 /* this might double-apply the annotation, but oh well. It does mean that
1540 * basethrid is paired in here when we do a comparison against new values
1541 * from the replica later! */
1542 const char *p = buf_cstring(&value);
1543 parsehex(p, &p, 16, &record->basecid);
1544 /* XXX - check on p? */
1545
1546 /* "basethrid" is special, since it is written during mailbox
1547 * appends and rewrites, using whatever modseq the index_record
1548 * has at this moment. This might differ from the modseq we
1549 * just parsed here, causing master and replica annotations
1550 * to get out of sync.
1551 * The fix is to set the basecid field both on the index
1552 * record *and* adding the annotation to the annotation list.
1553 * That way the local modseq of basethrid always gets over-
1554 * written by whoever wins to be master of this annotation */
1555 sync_annot_list_add(*salp, entry, userid, &value, modseq);
1556 }
1557 else {
1558 sync_annot_list_add(*salp, entry, userid, &value, modseq);
1559 }
1560 buf_free(&value);
1561 }
1562 return 0;
1563 }
1564
1565 /*
1566 * Merge a local and remote list of annotations, and apply the resulting
1567 * list of annotations to the local annotation database, storing new values
1568 * or deleting old values as necessary. Manages its own annotations
1569 * transaction.
1570 * Record may be null, to process mailbox annotations.
1571 */
1572
diff_annotation(const struct sync_annot * a,const struct sync_annot * b,int diff_value)1573 static int diff_annotation(const struct sync_annot *a,
1574 const struct sync_annot *b,
1575 int diff_value)
1576 {
1577 int diff = 0;
1578
1579 if (!a && !b) return 0;
1580
1581 if (a)
1582 diff--;
1583 if (b)
1584 diff++;
1585
1586 if (!diff)
1587 diff = strcmpnull(a->entry, b->entry);
1588 if (!diff)
1589 diff = strcmpnull(a->userid, b->userid);
1590 if (!diff && diff_value)
1591 diff = buf_cmp(&a->value, &b->value);
1592
1593 return diff;
1594 }
1595
diff_annotations(const struct sync_annot_list * local_annots,const struct sync_annot_list * remote_annots)1596 int diff_annotations(const struct sync_annot_list *local_annots,
1597 const struct sync_annot_list *remote_annots)
1598 {
1599 const struct sync_annot *local = (local_annots ? local_annots->head : NULL);
1600 const struct sync_annot *remote = (remote_annots ? remote_annots->head : NULL);
1601 while (local || remote) {
1602 int r = diff_annotation(local, remote, 1);
1603 if (r) return r;
1604 if (local) local = local->next;
1605 if (remote) remote = remote->next;
1606 }
1607
1608 return 0;
1609 }
1610
apply_annotations(struct mailbox * mailbox,const struct index_record * record,const struct sync_annot_list * local_annots,const struct sync_annot_list * remote_annots,int local_wins)1611 int apply_annotations(struct mailbox *mailbox,
1612 const struct index_record *record,
1613 const struct sync_annot_list *local_annots,
1614 const struct sync_annot_list *remote_annots,
1615 int local_wins)
1616 {
1617 const struct sync_annot *local = (local_annots ? local_annots->head : NULL);
1618 const struct sync_annot *remote = (remote_annots ? remote_annots->head : NULL);
1619 const struct sync_annot *chosen;
1620 static const struct buf novalue = BUF_INITIALIZER;
1621 const struct buf *value;
1622 int r = 0;
1623 int diff;
1624 annotate_state_t *astate = NULL;
1625
1626 if (record) {
1627 r = mailbox_get_annotate_state(mailbox, record->uid, &astate);
1628 }
1629 else {
1630 astate = annotate_state_new();
1631 r = annotate_state_set_mailbox(astate, mailbox);
1632 }
1633 if (r) goto out;
1634
1635 /*
1636 * We rely here on the database scan order resulting in lists
1637 * of annotations that are ordered lexically on entry then userid.
1638 * We walk over both lists at once, choosing an annotation from
1639 * either the local list only (diff < 0), the remote list only
1640 * (diff > 0), or both lists (diff == 0).
1641 */
1642 while (local || remote) {
1643 diff = diff_annotation(local, remote, 0);
1644 chosen = 0;
1645 if (diff < 0) {
1646 chosen = local;
1647 value = (local_wins ? &local->value : &novalue);
1648 local = local->next;
1649 }
1650 else if (diff > 0) {
1651 chosen = remote;
1652 value = (local_wins ? &novalue : &remote->value);
1653 remote = remote->next;
1654 }
1655 else {
1656 chosen = remote;
1657 value = (local_wins ? &local->value : &remote->value);
1658 diff = buf_cmp(&local->value, &remote->value);
1659 local = local->next;
1660 remote = remote->next;
1661 if (!diff)
1662 continue; /* same value, skip */
1663 }
1664
1665 /* Replicate the modseq of this record from master */
1666 struct annotate_metadata mdata = {
1667 chosen->modseq, /* modseq */
1668 0 /* flags - is determined by value */
1669 };
1670 r = annotate_state_writemdata(astate, chosen->entry,
1671 chosen->userid, value, &mdata);
1672 if (r)
1673 break;
1674 }
1675
1676 out:
1677
1678 if (record) {
1679 #ifdef USE_CALALARMD
1680 if (mailbox->mbtype & MBTYPE_CALENDAR) {
1681 // NOTE: this is because we don't pass the annotations through
1682 // with the record as we create it, so we can't update the alarm
1683 // database properly. Instead, we don't set anything when we append
1684 // by checking for .silent, and instead update the database by touching
1685 // the alarm AFTER writing the record.
1686 caldav_alarm_sync_nextcheck(mailbox, record);
1687 }
1688 #endif
1689 }
1690 else {
1691 /* need to manage our own txn for the global db */
1692 if (!r)
1693 r = annotate_state_commit(&astate);
1694 else
1695 annotate_state_abort(&astate);
1696 }
1697 /* else, the struct mailbox manages it for us */
1698
1699 return r;
1700 }
1701
1702 /* =========================================================================== */
1703
sync_print_flags(struct dlist * kl,struct mailbox * mailbox,const struct index_record * record)1704 void sync_print_flags(struct dlist *kl,
1705 struct mailbox *mailbox,
1706 const struct index_record *record)
1707 {
1708 int flag;
1709 struct dlist *fl = dlist_newlist(kl, "FLAGS");
1710
1711 if (record->system_flags & FLAG_DELETED)
1712 dlist_setflag(fl, "FLAG", "\\Deleted");
1713 if (record->system_flags & FLAG_ANSWERED)
1714 dlist_setflag(fl, "FLAG", "\\Answered");
1715 if (record->system_flags & FLAG_FLAGGED)
1716 dlist_setflag(fl, "FLAG", "\\Flagged");
1717 if (record->system_flags & FLAG_DRAFT)
1718 dlist_setflag(fl, "FLAG", "\\Draft");
1719 if (record->internal_flags & FLAG_INTERNAL_EXPUNGED)
1720 dlist_setflag(fl, "FLAG", "\\Expunged");
1721 if (record->system_flags & FLAG_SEEN)
1722 dlist_setflag(fl, "FLAG", "\\Seen");
1723
1724 /* print user flags in mailbox order */
1725 for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
1726 if (!mailbox->flagname[flag])
1727 continue;
1728 if (!(record->user_flags[flag/32] & (1<<(flag&31))))
1729 continue;
1730 dlist_setflag(fl, "FLAG", mailbox->flagname[flag]);
1731 }
1732 }
1733
sync_getflags(struct dlist * kl,struct mailbox * mailbox,struct index_record * record)1734 int sync_getflags(struct dlist *kl,
1735 struct mailbox *mailbox,
1736 struct index_record *record)
1737 {
1738 struct dlist *ki;
1739 int userflag;
1740
1741 for (ki = kl->head; ki; ki = ki->next) {
1742 char *s = xstrdup(ki->sval);
1743
1744 if (s[0] == '\\') {
1745 /* System flags */
1746 lcase(s);
1747 if (!strcmp(s, "\\seen")) {
1748 record->system_flags |= FLAG_SEEN;
1749 } else if (!strcmp(s, "\\expunged")) {
1750 record->internal_flags |= FLAG_INTERNAL_EXPUNGED;
1751 } else if (!strcmp(s, "\\answered")) {
1752 record->system_flags |= FLAG_ANSWERED;
1753 } else if (!strcmp(s, "\\flagged")) {
1754 record->system_flags |= FLAG_FLAGGED;
1755 } else if (!strcmp(s, "\\deleted")) {
1756 record->system_flags |= FLAG_DELETED;
1757 } else if (!strcmp(s, "\\draft")) {
1758 record->system_flags |= FLAG_DRAFT;
1759 } else {
1760 syslog(LOG_ERR, "Unknown system flag: %s", s);
1761 }
1762 }
1763 else {
1764 if (mailbox_user_flag(mailbox, s, &userflag, /*allow all*/2)) {
1765 syslog(LOG_ERR, "Unable to record user flag: %s", s);
1766 free(s);
1767 return IMAP_IOERROR;
1768 }
1769 record->user_flags[userflag/32] |= 1<<(userflag&31);
1770 }
1771
1772 free(s);
1773 }
1774
1775 return 0;
1776 }
1777
parse_upload(struct dlist * kr,struct mailbox * mailbox,struct index_record * record,struct sync_annot_list ** salp)1778 int parse_upload(struct dlist *kr, struct mailbox *mailbox,
1779 struct index_record *record,
1780 struct sync_annot_list **salp)
1781 {
1782 struct dlist *fl;
1783 struct message_guid *tmpguid;
1784 int r;
1785
1786 memset(record, 0, sizeof(struct index_record));
1787
1788 if (!dlist_getnum32(kr, "UID", &record->uid))
1789 return IMAP_PROTOCOL_BAD_PARAMETERS;
1790 if (!dlist_getnum64(kr, "MODSEQ", &record->modseq))
1791 return IMAP_PROTOCOL_BAD_PARAMETERS;
1792 if (!dlist_getdate(kr, "LAST_UPDATED", &record->last_updated))
1793 return IMAP_PROTOCOL_BAD_PARAMETERS;
1794 if (!dlist_getlist(kr, "FLAGS", &fl))
1795 return IMAP_PROTOCOL_BAD_PARAMETERS;
1796 if (!dlist_getdate(kr, "INTERNALDATE", &record->internaldate))
1797 return IMAP_PROTOCOL_BAD_PARAMETERS;
1798 if (!dlist_getnum32(kr, "SIZE", &record->size))
1799 return IMAP_PROTOCOL_BAD_PARAMETERS;
1800 if (!dlist_getguid(kr, "GUID", &tmpguid))
1801 return IMAP_PROTOCOL_BAD_PARAMETERS;
1802
1803 record->guid = *tmpguid;
1804
1805 /* parse the flags */
1806 r = sync_getflags(fl, mailbox, record);
1807 if (r) return r;
1808
1809 /* the ANNOTATIONS list is optional too */
1810 if (salp && dlist_getlist(kr, "ANNOTATIONS", &fl))
1811 r = decode_annotations(fl, salp, mailbox, record);
1812
1813 return r;
1814 }
1815
1816 /* NOTE - we don't prot_flush here, as we always send an OK at the
1817 * end of a response anyway */
sync_send_response(struct dlist * kl,struct protstream * out)1818 void sync_send_response(struct dlist *kl, struct protstream *out)
1819 {
1820 prot_printf(out, "* ");
1821 dlist_print(kl, 1, out);
1822 prot_printf(out, "\r\n");
1823 }
1824
sync_gentag(struct buf * tag)1825 static const char *sync_gentag(struct buf *tag)
1826 {
1827 static unsigned cmdcnt = 0;
1828
1829 buf_reset(tag);
1830 buf_printf(tag, "S%d", cmdcnt++);
1831 return buf_cstring(tag);
1832 }
1833
1834 /* these are one-shot commands for get and apply, so flush the stream
1835 * after sending */
sync_send_apply(struct dlist * kl,struct protstream * out)1836 void sync_send_apply(struct dlist *kl, struct protstream *out)
1837 {
1838 if (out->userdata) {
1839 /* IMAP flavor (w/ tag) */
1840 prot_printf(out, "%s SYNC", sync_gentag((struct buf *) out->userdata));
1841 }
1842 prot_printf(out, "APPLY ");
1843 dlist_print(kl, 1, out);
1844 prot_printf(out, "\r\n");
1845 prot_flush(out);
1846 }
1847
sync_send_lookup(struct dlist * kl,struct protstream * out)1848 void sync_send_lookup(struct dlist *kl, struct protstream *out)
1849 {
1850 if (out->userdata) {
1851 /* IMAP flavor (w/ tag) */
1852 prot_printf(out, "%s SYNC", sync_gentag((struct buf *) out->userdata));
1853 }
1854 prot_printf(out, "GET ");
1855 dlist_print(kl, 1, out);
1856 prot_printf(out, "\r\n");
1857 prot_flush(out);
1858 }
1859
sync_send_restart(struct protstream * out)1860 void sync_send_restart(struct protstream *out)
1861 {
1862 if (out->userdata) {
1863 /* IMAP flavor (w/ tag) */
1864 prot_printf(out, "%s SYNC", sync_gentag((struct buf *) out->userdata));
1865 }
1866 prot_printf(out, "RESTART\r\n");
1867 prot_flush(out);
1868 }
1869
sync_send_restore(struct dlist * kl,struct protstream * out)1870 void sync_send_restore(struct dlist *kl, struct protstream *out)
1871 {
1872 if (out->userdata) {
1873 /* IMAP flavor (w/ tag) */
1874 prot_printf(out, "%s SYNC", sync_gentag((struct buf *) out->userdata));
1875 }
1876 prot_printf(out, "RESTORE ");
1877 dlist_print(kl, 1, out);
1878 prot_printf(out, "\r\n");
1879 prot_flush(out);
1880 }
1881
sync_parseline(struct protstream * in)1882 struct dlist *sync_parseline(struct protstream *in)
1883 {
1884 struct dlist *dl = NULL;
1885 int c;
1886
1887 c = dlist_parse(&dl, 1, 0, in);
1888
1889 /* end line - or fail */
1890 if (c == '\r') c = prot_getc(in);
1891 if (c == '\n') return dl;
1892
1893 dlist_free(&dl);
1894 eatline(in, c);
1895 return NULL;
1896 }
1897
sync_send_file(struct mailbox * mailbox,const char * topart,const struct index_record * record,struct sync_msgid_list * part_list,struct dlist * kupload)1898 static int sync_send_file(struct mailbox *mailbox,
1899 const char *topart,
1900 const struct index_record *record,
1901 struct sync_msgid_list *part_list,
1902 struct dlist *kupload)
1903 {
1904 struct sync_msgid *msgid;
1905 const char *fname;
1906
1907 /* we'll trust that it exists - if not, we'll bail later,
1908 * but right now we're under locks, so be fast */
1909 fname = mailbox_record_fname(mailbox, record);
1910 if (!fname) return IMAP_MAILBOX_BADNAME;
1911
1912 msgid = sync_msgid_insert(part_list, &record->guid);
1913
1914 /* already uploaded, great */
1915 if (!msgid->need_upload)
1916 return 0;
1917
1918 dlist_setfile(kupload, "MESSAGE", topart, &record->guid, record->size, fname);
1919
1920 /* note that we will be sending it, so it doesn't need to be
1921 * sent again */
1922 msgid->size = record->size;
1923 if (!msgid->fname) msgid->fname = xstrdup(fname);
1924 msgid->need_upload = 0;
1925 msgid->is_archive = (record->internal_flags & FLAG_INTERNAL_ARCHIVED) ? 1 : 0;
1926 part_list->toupload--;
1927
1928 return 0;
1929 }
1930
sync_prepare_dlists(struct mailbox * mailbox,struct sync_folder * local,struct sync_folder * remote,const char * topart,struct sync_msgid_list * part_list,struct dlist * kl,struct dlist * kupload,int printrecords,int fullannots,int sendsince)1931 static int sync_prepare_dlists(struct mailbox *mailbox,
1932 struct sync_folder *local,
1933 struct sync_folder *remote,
1934 const char *topart,
1935 struct sync_msgid_list *part_list,
1936 struct dlist *kl, struct dlist *kupload,
1937 int printrecords, int fullannots, int sendsince)
1938 {
1939 struct sync_annot_list *annots = NULL;
1940 struct mailbox_iter *iter = NULL;
1941 modseq_t xconvmodseq = 0;
1942 int r = 0;
1943 int ispartial = local ? local->ispartial : 0;
1944
1945 if (!topart) topart = mailbox->part;
1946
1947 dlist_setatom(kl, "UNIQUEID", mailbox->uniqueid);
1948 dlist_setatom(kl, "MBOXNAME", mailbox->name);
1949 if (mailbox->mbtype)
1950 dlist_setatom(kl, "MBOXTYPE", mboxlist_mbtype_to_string(mailbox->mbtype));
1951 if (ispartial) {
1952 /* send a zero to make older Cyrus happy */
1953 dlist_setnum32(kl, "SYNC_CRC", 0);
1954 /* calculated partial values */
1955 dlist_setnum32(kl, "LAST_UID", local->last_uid);
1956 dlist_setnum64(kl, "HIGHESTMODSEQ", local->highestmodseq);
1957 /* create synthetic values for the other fields */
1958 dlist_setnum32(kl, "RECENTUID", remote ? remote->recentuid : 0);
1959 dlist_setdate(kl, "RECENTTIME", remote ? remote->recenttime : 0);
1960 dlist_setdate(kl, "LAST_APPENDDATE", 0);
1961 dlist_setdate(kl, "POP3_LAST_LOGIN", remote ? remote->pop3_last_login : 0);
1962 dlist_setdate(kl, "POP3_SHOW_AFTER", remote ? remote->pop3_show_after : 0);
1963 if (remote && remote->xconvmodseq)
1964 dlist_setnum64(kl, "XCONVMODSEQ", remote->xconvmodseq);
1965 if (remote && remote->raclmodseq)
1966 dlist_setnum64(kl, "RACLMODSEQ", remote->raclmodseq);
1967 }
1968 else {
1969 struct synccrcs synccrcs = mailbox_synccrcs(mailbox, /*force*/0);
1970 dlist_setnum32(kl, "SYNC_CRC", synccrcs.basic);
1971 dlist_setnum32(kl, "SYNC_CRC_ANNOT", synccrcs.annot);
1972 dlist_setnum32(kl, "LAST_UID", mailbox->i.last_uid);
1973 dlist_setnum64(kl, "HIGHESTMODSEQ", mailbox->i.highestmodseq);
1974 dlist_setnum32(kl, "RECENTUID", mailbox->i.recentuid);
1975 dlist_setdate(kl, "RECENTTIME", mailbox->i.recenttime);
1976 dlist_setdate(kl, "LAST_APPENDDATE", mailbox->i.last_appenddate);
1977 dlist_setdate(kl, "POP3_LAST_LOGIN", mailbox->i.pop3_last_login);
1978 dlist_setdate(kl, "POP3_SHOW_AFTER", mailbox->i.pop3_show_after);
1979 if (mailbox_has_conversations(mailbox)) {
1980 r = mailbox_get_xconvmodseq(mailbox, &xconvmodseq);
1981 if (!r && xconvmodseq)
1982 dlist_setnum64(kl, "XCONVMODSEQ", xconvmodseq);
1983 }
1984 modseq_t raclmodseq = mboxname_readraclmodseq(mailbox->name);
1985 if (raclmodseq)
1986 dlist_setnum64(kl, "RACLMODSEQ", raclmodseq);
1987 }
1988 dlist_setnum32(kl, "UIDVALIDITY", mailbox->i.uidvalidity);
1989 dlist_setatom(kl, "PARTITION", topart);
1990 dlist_setatom(kl, "ACL", mailbox->acl);
1991 dlist_setatom(kl, "OPTIONS", sync_encode_options(mailbox->i.options));
1992 if (mailbox->quotaroot)
1993 dlist_setatom(kl, "QUOTAROOT", mailbox->quotaroot);
1994
1995 if (mailbox->i.createdmodseq)
1996 dlist_setnum64(kl, "CREATEDMODSEQ", mailbox->i.createdmodseq);
1997
1998 if (mailbox->foldermodseq)
1999 dlist_setnum64(kl, "FOLDERMODSEQ", mailbox->foldermodseq);
2000
2001 /* always send mailbox annotations */
2002 r = read_annotations(mailbox, NULL, &annots, 0, 0);
2003 if (r) goto done;
2004
2005 encode_annotations(kl, mailbox, NULL, annots);
2006 sync_annot_list_free(&annots);
2007
2008 if (sendsince && remote && remote->highestmodseq) {
2009 dlist_setnum64(kl, "SINCE_MODSEQ", remote->highestmodseq);
2010 dlist_setnum32(kl, "SINCE_CRC", remote->synccrcs.basic);
2011 dlist_setnum32(kl, "SINCE_CRC_ANNOT", remote->synccrcs.annot);
2012 }
2013
2014 if (printrecords) {
2015 const message_t *msg;
2016 struct dlist *rl = dlist_newlist(kl, "RECORD");
2017 modseq_t modseq = remote ? remote->highestmodseq : 0;
2018
2019 iter = mailbox_iter_init(mailbox, modseq, 0);
2020 while ((msg = mailbox_iter_step(iter))) {
2021 const struct index_record *record = msg_record(msg);
2022 modseq_t since_modseq = fullannots ? 0 : modseq;
2023
2024 /* stop early for partial sync */
2025 modseq_t mymodseq = record->modseq;
2026 if (ispartial) {
2027 if (record->uid > local->last_uid)
2028 break;
2029 /* something from past the modseq that we're sending now */
2030 if (mymodseq > local->highestmodseq) {
2031 /* we will send this one later */
2032 if (remote && record->uid <= remote->last_uid)
2033 continue;
2034 /* falsify modseq for now, we will resync this message later */
2035 mymodseq = local->highestmodseq;
2036 }
2037 }
2038
2039
2040 /* start off thinking we're sending the file too */
2041 int send_file = 1;
2042
2043 /* does it exist at the other end? Don't send it */
2044 if (remote && record->uid <= remote->last_uid)
2045 send_file = 0;
2046
2047 /* if we're not uploading messages... don't send file */
2048 if (!part_list || !kupload)
2049 send_file = 0;
2050
2051 /* if we don't HAVE the file we can't send it */
2052 if (record->internal_flags & FLAG_INTERNAL_UNLINKED)
2053 send_file = 0;
2054
2055 if (send_file) {
2056 r = sync_send_file(mailbox, topart,
2057 record, part_list, kupload);
2058 if (r) goto done;
2059 }
2060
2061 struct dlist *il = dlist_newkvlist(rl, "RECORD");
2062 dlist_setnum32(il, "UID", record->uid);
2063 dlist_setnum64(il, "MODSEQ", mymodseq);
2064 dlist_setdate(il, "LAST_UPDATED", record->last_updated);
2065 sync_print_flags(il, mailbox, record);
2066 dlist_setdate(il, "INTERNALDATE", record->internaldate);
2067 dlist_setnum32(il, "SIZE", record->size);
2068 dlist_setatom(il, "GUID", message_guid_encode(&record->guid));
2069
2070 r = read_annotations(mailbox, record, &annots, since_modseq,
2071 /*XXX ANNOTATE_TOMBSTONES*/0);
2072 if (r) goto done;
2073
2074 encode_annotations(il, mailbox, record, annots);
2075 sync_annot_list_free(&annots);
2076 }
2077 }
2078
2079 done:
2080 mailbox_iter_done(&iter);
2081 return r;
2082 }
2083
sync_parse_response(const char * cmd,struct protstream * in,struct dlist ** klp)2084 int sync_parse_response(const char *cmd, struct protstream *in,
2085 struct dlist **klp)
2086 {
2087 static struct buf response; /* BSS */
2088 static struct buf errmsg;
2089 struct dlist *kl = NULL;
2090 int c;
2091
2092 if ((c = getword(in, &response)) == EOF) {
2093 syslog(LOG_ERR, "IOERROR: zero length response to %s (%s)",
2094 cmd, prot_error(in));
2095 return IMAP_PROTOCOL_ERROR;
2096 }
2097
2098 if (c != ' ') goto parse_err;
2099
2100 kl = dlist_newlist(NULL, cmd);
2101 while (!strcmp(response.s, "*")) {
2102 struct dlist *item = sync_parseline(in);
2103 if (!item) goto parse_err;
2104 dlist_stitch(kl, item);
2105 if ((c = getword(in, &response)) == EOF)
2106 goto parse_err;
2107 }
2108
2109 if (in->userdata) {
2110 /* check IMAP response tag */
2111 if (strcmp(response.s, buf_cstring((struct buf *) in->userdata)))
2112 goto parse_err;
2113
2114 /* first word was IMAP response tag - get response token */
2115 if ((c = getword(in, &response)) == EOF)
2116 return IMAP_PROTOCOL_ERROR;
2117
2118 if (c != ' ') goto parse_err;
2119 }
2120
2121 if (!strcmp(response.s, "OK")) {
2122 if (klp) *klp = kl;
2123 else dlist_free(&kl);
2124 eatline(in, c);
2125 return 0;
2126 }
2127 if (!strcmp(response.s, "NO")) {
2128 dlist_free(&kl);
2129 sync_getline(in, &errmsg);
2130 syslog(LOG_ERR, "%s received NO response: %s", cmd, errmsg.s);
2131
2132 /* Slight hack to transform certain error strings into equivalent
2133 * imap_err value so that caller has some idea of cause. Match
2134 * this to the logic at sync_response() */
2135 if (!strncmp(errmsg.s, "IMAP_INVALID_USER ",
2136 strlen("IMAP_INVALID_USER ")))
2137 return IMAP_INVALID_USER;
2138 else if (!strncmp(errmsg.s, "IMAP_MAILBOX_NONEXISTENT ",
2139 strlen("IMAP_MAILBOX_NONEXISTENT ")))
2140 return IMAP_MAILBOX_NONEXISTENT;
2141 else if (!strncmp(errmsg.s, "IMAP_MAILBOX_LOCKED ",
2142 strlen("IMAP_MAILBOX_LOCKED ")))
2143 return IMAP_MAILBOX_LOCKED;
2144 else if (!strncmp(errmsg.s, "IMAP_MAILBOX_MOVED ",
2145 strlen("IMAP_MAILBOX_MOVED ")))
2146 return IMAP_MAILBOX_MOVED;
2147 else if (!strncmp(errmsg.s, "IMAP_MAILBOX_NOTSUPPORTED ",
2148 strlen("IMAP_MAILBOX_NOTSUPPORTED ")))
2149 return IMAP_MAILBOX_NOTSUPPORTED;
2150 else if (!strncmp(errmsg.s, "IMAP_SYNC_CHECKSUM ",
2151 strlen("IMAP_SYNC_CHECKSUM ")))
2152 return IMAP_SYNC_CHECKSUM;
2153 else if (!strncmp(errmsg.s, "IMAP_SYNC_CHANGED ",
2154 strlen("IMAP_SYNC_CHANGED ")))
2155 return IMAP_SYNC_CHANGED;
2156 else if (!strncmp(errmsg.s, "IMAP_SYNC_BADSIEVE ",
2157 strlen("IMAP_SYNC_BADSIEVE ")))
2158 return IMAP_SYNC_BADSIEVE;
2159 else if (!strncmp(errmsg.s, "IMAP_PROTOCOL_ERROR ",
2160 strlen("IMAP_PROTOCOL_ERROR ")))
2161 return IMAP_PROTOCOL_ERROR;
2162 else if (!strncmp(errmsg.s, "IMAP_PROTOCOL_BAD_PARAMETERS ",
2163 strlen("IMAP_PROTOCOL_BAD_PARAMETERS ")))
2164 return IMAP_PROTOCOL_BAD_PARAMETERS;
2165 else
2166 return IMAP_REMOTE_DENIED;
2167 }
2168
2169 parse_err:
2170 dlist_free(&kl);
2171 sync_getline(in, &errmsg);
2172 syslog(LOG_ERR, "IOERROR: %s received %s response: %s",
2173 cmd, response.s, errmsg.s);
2174 return IMAP_PROTOCOL_ERROR;
2175 }
2176
sync_append_copyfile(struct mailbox * mailbox,struct index_record * record,const struct sync_annot_list * annots,const struct sync_msgid_list * part_list)2177 int sync_append_copyfile(struct mailbox *mailbox,
2178 struct index_record *record,
2179 const struct sync_annot_list *annots,
2180 const struct sync_msgid_list *part_list)
2181 {
2182 const char *destname;
2183 struct message_guid tmp_guid;
2184 struct sync_msgid *item;
2185 int r = 0;
2186
2187 message_guid_copy(&tmp_guid, &record->guid);
2188
2189 item = sync_msgid_lookup(part_list, &record->guid);
2190
2191 if (!item || !item->fname)
2192 r = IMAP_IOERROR;
2193 else
2194 r = message_parse(item->fname, record);
2195
2196 if (r) {
2197 /* deal with unlinked master records */
2198 if (record->internal_flags & FLAG_INTERNAL_EXPUNGED) {
2199 /* no need to set 'needs cleanup' here, it's already expunged */
2200 record->internal_flags |= FLAG_INTERNAL_UNLINKED;
2201 goto just_write;
2202 }
2203 syslog(LOG_ERR, "IOERROR: failed to parse %s: %s",
2204 message_guid_encode(&record->guid),
2205 error_message(r));
2206 return r;
2207 }
2208
2209 /* record->guid was rewritten in the parse, see if it changed */
2210 if (!message_guid_equal(&tmp_guid, &record->guid)) {
2211 syslog(LOG_ERR, "IOERROR: guid mismatch on parse %s (%s)",
2212 item->fname, message_guid_encode(&record->guid));
2213 return IMAP_IOERROR;
2214 }
2215
2216 /* put back to archive if original was archived, gain single instance store */
2217 if (item->is_archive)
2218 record->internal_flags |= FLAG_INTERNAL_ARCHIVED;
2219
2220 /* push it to archive if it should be archived now anyway */
2221 if (mailbox_should_archive(mailbox, record, NULL))
2222 record->internal_flags |= FLAG_INTERNAL_ARCHIVED;
2223
2224 destname = mailbox_record_fname(mailbox, record);
2225 cyrus_mkdir(destname, 0755);
2226 r = mailbox_copyfile(item->fname, destname, 0);
2227 if (r) {
2228 syslog(LOG_ERR, "IOERROR: Failed to copy %s to %s",
2229 item->fname, destname);
2230 return r;
2231 }
2232
2233 just_write:
2234 r = mailbox_append_index_record(mailbox, record);
2235 if (r) return r;
2236
2237 /* apply the remote annotations */
2238 r = apply_annotations(mailbox, record, NULL, annots, 0);
2239 if (r) {
2240 syslog(LOG_ERR, "Failed to apply annotations: %s",
2241 error_message(r));
2242 }
2243
2244 return r;
2245 }
2246
2247 /* ==================================================================== */
2248
sync_mailbox_version_check(struct mailbox ** mailboxp)2249 int sync_mailbox_version_check(struct mailbox **mailboxp)
2250 {
2251 int r = 0;
2252
2253 if ((*mailboxp)->i.minor_version < 10) {
2254 /* index records will definitely not have guids! */
2255 r = IMAP_MAILBOX_NOTSUPPORTED;
2256 goto done;
2257 }
2258
2259 /* scan index records to ensure they have guids. version 10 index records
2260 * have this field, but it might have never been initialised.
2261 * XXX this might be overkill for versions > 10, but let's be cautious */
2262 struct mailbox_iter *iter = mailbox_iter_init((*mailboxp), 0, 0);
2263 const message_t *msg;
2264 while ((msg = mailbox_iter_step(iter))) {
2265 const struct index_record *record = msg_record(msg);
2266 if (message_guid_isnull(&record->guid)) {
2267 syslog(LOG_WARNING, "%s: missing guid for record %u -- needs 'reconstruct -G'?",
2268 (*mailboxp)->name, record->recno);
2269 r = IMAP_MAILBOX_NOTSUPPORTED;
2270 break;
2271 }
2272 }
2273 mailbox_iter_done(&iter);
2274
2275 done:
2276 if (r) {
2277 syslog(LOG_DEBUG, "%s: %s failed version check: %s",
2278 __func__, (*mailboxp)->name, error_message(r));
2279 mailbox_close(mailboxp);
2280 }
2281 return r;
2282 }
2283
2284 /* ======================= server-side sync =========================== */
2285
reserve_folder(const char * part,const char * mboxname,struct sync_msgid_list * part_list)2286 static void reserve_folder(const char *part, const char *mboxname,
2287 struct sync_msgid_list *part_list)
2288 {
2289 struct mailbox *mailbox = NULL;
2290 int r;
2291 struct sync_msgid *item;
2292 const char *mailbox_msg_path, *stage_msg_path;
2293 int num_reserved;
2294
2295 redo:
2296
2297 num_reserved = 0;
2298
2299 /* Open and lock mailbox */
2300 r = mailbox_open_irl(mboxname, &mailbox);
2301 if (!r) r = sync_mailbox_version_check(&mailbox);
2302 if (r) return;
2303
2304 struct mailbox_iter *iter = mailbox_iter_init(mailbox, 0, ITER_SKIP_UNLINKED);
2305 const message_t *msg;
2306 while ((msg = mailbox_iter_step(iter))) {
2307 const struct index_record *record = msg_record(msg);
2308 /* do we need it? */
2309 item = sync_msgid_lookup(part_list, &record->guid);
2310 if (!item)
2311 continue;
2312
2313 /* have we already found it? */
2314 if (!item->need_upload)
2315 continue;
2316
2317 /* Attempt to reserve this message */
2318 mailbox_msg_path = mailbox_record_fname(mailbox, record);
2319 stage_msg_path = dlist_reserve_path(part, record->internal_flags & FLAG_INTERNAL_ARCHIVED,
2320 0, &record->guid);
2321
2322 /* check that the sha1 of the file on disk is correct */
2323 struct index_record record2;
2324 memset(&record2, 0, sizeof(struct index_record));
2325 r = message_parse(mailbox_msg_path, &record2);
2326 if (r) {
2327 syslog(LOG_ERR, "IOERROR: Unable to parse %s",
2328 mailbox_msg_path);
2329 continue;
2330 }
2331 if (!message_guid_equal(&record->guid, &record2.guid)) {
2332 syslog(LOG_ERR, "IOERROR: GUID mismatch on parse for %s",
2333 mailbox_msg_path);
2334 continue;
2335 }
2336
2337 if (mailbox_copyfile(mailbox_msg_path, stage_msg_path, 0) != 0) {
2338 syslog(LOG_ERR, "IOERROR: Unable to link %s -> %s: %m",
2339 mailbox_msg_path, stage_msg_path);
2340 continue;
2341 }
2342
2343 item->size = record->size;
2344 item->fname = xstrdup(stage_msg_path); /* track the correct location */
2345 item->is_archive = (record->internal_flags & FLAG_INTERNAL_ARCHIVED) ? 1 : 0;
2346 item->need_upload = 0;
2347 part_list->toupload--;
2348 num_reserved++;
2349
2350 /* already found everything, drop out */
2351 if (!part_list->toupload) break;
2352
2353 /* arbitrary batch size */
2354 if (num_reserved >= 1024) {
2355 mailbox_iter_done(&iter);
2356 mailbox_close(&mailbox);
2357 goto redo;
2358 }
2359 }
2360
2361 mailbox_iter_done(&iter);
2362
2363 mailbox_close(&mailbox);
2364 }
2365
sync_apply_reserve(struct dlist * kl,struct sync_reserve_list * reserve_list,struct sync_state * sstate)2366 int sync_apply_reserve(struct dlist *kl,
2367 struct sync_reserve_list *reserve_list,
2368 struct sync_state *sstate)
2369 {
2370 struct message_guid *tmpguid;
2371 struct sync_name_list *folder_names = sync_name_list_create();
2372 struct sync_msgid_list *part_list;
2373 struct sync_msgid *item;
2374 struct sync_name *folder;
2375 mbentry_t *mbentry = NULL;
2376 const char *partition = NULL;
2377 struct dlist *ml;
2378 struct dlist *gl;
2379 struct dlist *i;
2380 struct dlist *kout = NULL;
2381
2382 if (!dlist_getatom(kl, "PARTITION", &partition)) goto parse_err;
2383 if (!dlist_getlist(kl, "MBOXNAME", &ml)) goto parse_err;
2384 if (!dlist_getlist(kl, "GUID", &gl)) goto parse_err;
2385
2386 part_list = sync_reserve_partlist(reserve_list, partition);
2387 for (i = gl->head; i; i = i->next) {
2388 if (!dlist_toguid(i, &tmpguid))
2389 goto parse_err;
2390 sync_msgid_insert(part_list, tmpguid);
2391 }
2392
2393 /* need a list so we can mark items */
2394 for (i = ml->head; i; i = i->next) {
2395 sync_name_list_add(folder_names, i->sval);
2396 }
2397
2398 for (folder = folder_names->head; folder; folder = folder->next) {
2399 if (!part_list->toupload) break;
2400 if (mboxlist_lookup(folder->name, &mbentry, 0))
2401 continue;
2402 if (strcmp(mbentry->partition, partition)) {
2403 mboxlist_entry_free(&mbentry);
2404 continue; /* try folders on the same partition first! */
2405 }
2406 mboxlist_entry_free(&mbentry);
2407 reserve_folder(partition, folder->name, part_list);
2408 folder->mark = 1;
2409 }
2410
2411 /* if we have other folders, check them now */
2412 for (folder = folder_names->head; folder; folder = folder->next) {
2413 if (!part_list->toupload) break;
2414 if (folder->mark)
2415 continue;
2416 reserve_folder(partition, folder->name, part_list);
2417 folder->mark = 1;
2418 }
2419
2420 /* check if we missed any */
2421 kout = dlist_newlist(NULL, "MISSING");
2422 for (i = gl->head; i; i = i->next) {
2423 if (!dlist_toguid(i, &tmpguid))
2424 goto parse_err;
2425 item = sync_msgid_lookup(part_list, tmpguid);
2426 if (item->need_upload)
2427 dlist_setguid(kout, "GUID", tmpguid);
2428 }
2429
2430 if (kout->head)
2431 sync_send_response(kout, sstate->pout);
2432 dlist_free(&kout);
2433
2434 sync_name_list_free(&folder_names);
2435 mboxlist_entry_free(&mbentry);
2436
2437 return 0;
2438
2439 parse_err:
2440 dlist_free(&kout);
2441 sync_name_list_free(&folder_names);
2442 mboxlist_entry_free(&mbentry);
2443
2444 return IMAP_PROTOCOL_BAD_PARAMETERS;
2445 }
2446
2447 /* ====================================================================== */
2448
sync_apply_unquota(struct dlist * kin,struct sync_state * sstate)2449 int sync_apply_unquota(struct dlist *kin,
2450 struct sync_state *sstate __attribute__((unused)))
2451 {
2452 return mboxlist_unsetquota(kin->sval);
2453 }
2454
sync_apply_quota(struct dlist * kin,struct sync_state * sstate)2455 int sync_apply_quota(struct dlist *kin,
2456 struct sync_state *sstate __attribute__((unused)))
2457 {
2458 const char *root;
2459 quota_t limits[QUOTA_NUMRESOURCES];
2460
2461 if (!dlist_getatom(kin, "ROOT", &root))
2462 return IMAP_PROTOCOL_BAD_PARAMETERS;
2463 sync_decode_quota_limits(kin, limits);
2464
2465 modseq_t modseq = 0;
2466 dlist_getnum64(kin, "MODSEQ", &modseq);
2467
2468 return mboxlist_setquotas(root, limits, modseq, 1);
2469 }
2470
2471 /* ====================================================================== */
2472
sync_mailbox_compare_update(struct mailbox * mailbox,struct dlist * kr,int doupdate,struct sync_msgid_list * part_list)2473 static int sync_mailbox_compare_update(struct mailbox *mailbox,
2474 struct dlist *kr, int doupdate,
2475 struct sync_msgid_list *part_list)
2476 {
2477 struct index_record mrecord;
2478 struct dlist *ki;
2479 struct sync_annot_list *mannots = NULL;
2480 struct sync_annot_list *rannots = NULL;
2481 int r;
2482 int i;
2483 int has_append = 0;
2484
2485 struct mailbox_iter *iter = mailbox_iter_init(mailbox, 0, 0);
2486
2487 const message_t *msg = mailbox_iter_step(iter);
2488 const struct index_record *rrecord = msg ? msg_record(msg) : NULL;
2489
2490 for (ki = kr->head; ki; ki = ki->next) {
2491 sync_annot_list_free(&mannots);
2492 sync_annot_list_free(&rannots);
2493
2494 r = parse_upload(ki, mailbox, &mrecord, &mannots);
2495 if (r) {
2496 syslog(LOG_ERR, "SYNCERROR: failed to parse uploaded record");
2497 return IMAP_PROTOCOL_ERROR;
2498 }
2499
2500 /* n.b. we assume the records in kr are in ascending uid order.
2501 * stuff will probably fail in interesting ways if they're ever not.
2502 */
2503 while (rrecord && rrecord->uid < mrecord.uid) {
2504 /* read another record */
2505 msg = mailbox_iter_step(iter);
2506 rrecord = msg ? msg_record(msg) : NULL;
2507 }
2508
2509 /* found a match, check for updates */
2510 if (rrecord && rrecord->uid == mrecord.uid) {
2511 /* if they're both EXPUNGED then ignore everything else */
2512 if (mrecord.internal_flags & FLAG_INTERNAL_EXPUNGED &&
2513 rrecord->internal_flags & FLAG_INTERNAL_EXPUNGED)
2514 continue;
2515
2516 /* GUID mismatch is an error straight away, it only ever happens if we
2517 * had a split brain - and it will take a full sync to sort out the mess */
2518 if (!message_guid_equal(&mrecord.guid, &rrecord->guid)) {
2519 syslog(LOG_ERR, "SYNCNOTICE: guid mismatch %s %u",
2520 mailbox->name, mrecord.uid);
2521 r = IMAP_SYNC_CHECKSUM;
2522 goto out;
2523 }
2524
2525 /* higher modseq on the replica is an error */
2526 if (rrecord->modseq > mrecord.modseq) {
2527 if (opt_force) {
2528 syslog(LOG_NOTICE, "forcesync: higher modseq on replica %s %u (" MODSEQ_FMT " > " MODSEQ_FMT ")",
2529 mailbox->name, mrecord.uid, rrecord->modseq, mrecord.modseq);
2530 }
2531 else {
2532 syslog(LOG_ERR, "SYNCNOTICE: higher modseq on replica %s %u (" MODSEQ_FMT " > " MODSEQ_FMT ")",
2533 mailbox->name, mrecord.uid, rrecord->modseq, mrecord.modseq);
2534 r = IMAP_SYNC_CHECKSUM;
2535 goto out;
2536 }
2537 }
2538
2539 /* if it's already expunged on the replica, but alive on the master,
2540 * that's bad */
2541 if (!(mrecord.internal_flags & FLAG_INTERNAL_EXPUNGED) &&
2542 (rrecord->internal_flags & FLAG_INTERNAL_EXPUNGED)) {
2543 syslog(LOG_ERR, "SYNCNOTICE: expunged on replica %s %u",
2544 mailbox->name, mrecord.uid);
2545 r = IMAP_SYNC_CHECKSUM;
2546 goto out;
2547 }
2548
2549 /* skip out on the first pass */
2550 if (!doupdate) continue;
2551
2552 struct index_record copy = *rrecord;
2553 copy.cid = mrecord.cid;
2554 copy.basecid = mrecord.basecid;
2555 copy.modseq = mrecord.modseq;
2556 copy.last_updated = mrecord.last_updated;
2557 copy.internaldate = mrecord.internaldate;
2558 copy.savedate = mrecord.savedate;
2559 copy.createdmodseq = mrecord.createdmodseq;
2560 copy.system_flags = mrecord.system_flags;
2561 /* FLAG_INTERNAL_EXPUNGED is a syncable flag, but it's internal.
2562 * The `internal_flags` contain replica's internal_flags for
2563 * non-EXPUNGED, but master's internal_flags for EXPUNGED */
2564 copy.internal_flags = rrecord->internal_flags & ~FLAG_INTERNAL_EXPUNGED;
2565 copy.internal_flags |= mrecord.internal_flags & FLAG_INTERNAL_EXPUNGED;
2566
2567 for (i = 0; i < MAX_USER_FLAGS/32; i++)
2568 copy.user_flags[i] = mrecord.user_flags[i];
2569
2570 r = read_annotations(mailbox, ©, &rannots, rrecord->modseq,
2571 /*XXX ANNOTATE_TOMBSTONES*/0);
2572 if (r) {
2573 syslog(LOG_ERR, "Failed to read local annotations %s %u: %s",
2574 mailbox->name, rrecord->recno, error_message(r));
2575 goto out;
2576 }
2577
2578 r = apply_annotations(mailbox, ©, rannots, mannots, 0);
2579 if (r) {
2580 syslog(LOG_ERR, "Failed to write merged annotations %s %u: %s",
2581 mailbox->name, rrecord->recno, error_message(r));
2582 goto out;
2583 }
2584
2585 copy.silent = 1;
2586 r = mailbox_rewrite_index_record(mailbox, ©);
2587 if (r) {
2588 syslog(LOG_ERR, "IOERROR: failed to rewrite record %s %u",
2589 mailbox->name, rrecord->recno);
2590 goto out;
2591 }
2592 }
2593
2594 /* not found and less than LAST_UID, bogus */
2595 else if (mrecord.uid <= mailbox->i.last_uid) {
2596 /* Expunged, just skip it */
2597 if (!(mrecord.internal_flags & FLAG_INTERNAL_EXPUNGED)) {
2598 r = IMAP_SYNC_CHECKSUM;
2599 goto out;
2600 }
2601 }
2602
2603 /* after LAST_UID, it's an append, that's OK */
2604 else {
2605 /* skip out on the first pass */
2606 if (!doupdate) continue;
2607
2608 mrecord.silent = 1;
2609 r = sync_append_copyfile(mailbox, &mrecord, mannots, part_list);
2610 if (r) {
2611 syslog(LOG_ERR, "IOERROR: failed to append file %s %d",
2612 mailbox->name, mrecord.uid);
2613 goto out;
2614 }
2615
2616 has_append = 1;
2617 }
2618 }
2619
2620 if (has_append)
2621 sync_log_append(mailbox->name);
2622
2623 r = 0;
2624
2625 out:
2626 mailbox_iter_done(&iter);
2627 sync_annot_list_free(&mannots);
2628 sync_annot_list_free(&rannots);
2629 return r;
2630 }
2631
sync_apply_mailbox(struct dlist * kin,struct sync_reserve_list * reserve_list,struct sync_state * sstate)2632 int sync_apply_mailbox(struct dlist *kin,
2633 struct sync_reserve_list *reserve_list,
2634 struct sync_state *sstate)
2635 {
2636 struct sync_msgid_list *part_list;
2637 /* fields from the request */
2638 const char *uniqueid;
2639 const char *partition;
2640 const char *mboxname;
2641 const char *mboxtype = NULL; /* optional */
2642 uint32_t mbtype;
2643 uint32_t last_uid;
2644 modseq_t highestmodseq;
2645 uint32_t recentuid;
2646 time_t recenttime;
2647 time_t last_appenddate;
2648 time_t pop3_last_login;
2649 time_t pop3_show_after = 0; /* optional */
2650 uint32_t uidvalidity;
2651 modseq_t foldermodseq = 0;
2652 const char *acl;
2653 const char *options_str;
2654 struct synccrcs synccrcs = { 0, 0 };
2655
2656 uint32_t options;
2657
2658 /* optional fields */
2659 modseq_t xconvmodseq = 0;
2660 modseq_t raclmodseq = 0;
2661 modseq_t createdmodseq = 0;
2662
2663 /* previous state markers */
2664 modseq_t since_modseq = 0;
2665 struct synccrcs since_crcs = { 0, 0 };
2666
2667 struct mailbox *mailbox = NULL;
2668 struct dlist *kr;
2669 struct dlist *ka = NULL;
2670 int r;
2671
2672 struct sync_annot_list *mannots = NULL;
2673 struct sync_annot_list *rannots = NULL;
2674
2675 annotate_state_t *astate = NULL;
2676
2677 if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
2678 return IMAP_PROTOCOL_BAD_PARAMETERS;
2679 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
2680 return IMAP_PROTOCOL_BAD_PARAMETERS;
2681 if (!dlist_getnum64(kin, "HIGHESTMODSEQ", &highestmodseq))
2682 return IMAP_PROTOCOL_BAD_PARAMETERS;
2683
2684 dlist_getnum64(kin, "CREATEDMODSEQ", &createdmodseq);
2685
2686 dlist_getatom(kin, "MBOXTYPE", &mboxtype);
2687 mbtype = mboxlist_string_to_mbtype(mboxtype);
2688
2689 if (mbtype & (MBTYPE_INTERMEDIATE|MBTYPE_DELETED)) {
2690 // XXX - make sure what's already there is either nothing or compatible...
2691 mbentry_t *newmbentry = NULL;
2692
2693 newmbentry = mboxlist_entry_create();
2694 newmbentry->name = xstrdupnull(mboxname);
2695 newmbentry->mbtype = mbtype;
2696 newmbentry->uniqueid = xstrdupnull(uniqueid);
2697 newmbentry->foldermodseq = highestmodseq;
2698 newmbentry->createdmodseq = createdmodseq;
2699
2700 r = mboxlist_update(newmbentry, /*localonly*/1);
2701 mboxlist_entry_free(&newmbentry);
2702
2703 return r;
2704 }
2705
2706
2707 if (!dlist_getatom(kin, "PARTITION", &partition))
2708 return IMAP_PROTOCOL_BAD_PARAMETERS;
2709 if (!dlist_getnum32(kin, "LAST_UID", &last_uid))
2710 return IMAP_PROTOCOL_BAD_PARAMETERS;
2711 if (!dlist_getnum32(kin, "RECENTUID", &recentuid))
2712 return IMAP_PROTOCOL_BAD_PARAMETERS;
2713 if (!dlist_getdate(kin, "RECENTTIME", &recenttime))
2714 return IMAP_PROTOCOL_BAD_PARAMETERS;
2715 if (!dlist_getdate(kin, "LAST_APPENDDATE", &last_appenddate))
2716 return IMAP_PROTOCOL_BAD_PARAMETERS;
2717 if (!dlist_getdate(kin, "POP3_LAST_LOGIN", &pop3_last_login))
2718 return IMAP_PROTOCOL_BAD_PARAMETERS;
2719 if (!dlist_getnum32(kin, "UIDVALIDITY", &uidvalidity))
2720 return IMAP_PROTOCOL_BAD_PARAMETERS;
2721 if (!dlist_getatom(kin, "ACL", &acl))
2722 return IMAP_PROTOCOL_BAD_PARAMETERS;
2723 if (!dlist_getatom(kin, "OPTIONS", &options_str))
2724 return IMAP_PROTOCOL_BAD_PARAMETERS;
2725 if (!dlist_getlist(kin, "RECORD", &kr))
2726 return IMAP_PROTOCOL_BAD_PARAMETERS;
2727
2728 /* optional */
2729 dlist_getlist(kin, "ANNOTATIONS", &ka);
2730 dlist_getdate(kin, "POP3_SHOW_AFTER", &pop3_show_after);
2731 dlist_getnum64(kin, "XCONVMODSEQ", &xconvmodseq);
2732 dlist_getnum64(kin, "RACLMODSEQ", &raclmodseq);
2733 dlist_getnum64(kin, "FOLDERMODSEQ", &foldermodseq);
2734
2735 /* Get the CRCs */
2736 dlist_getnum32(kin, "SYNC_CRC", &synccrcs.basic);
2737 dlist_getnum32(kin, "SYNC_CRC_ANNOT", &synccrcs.annot);
2738
2739 /* Get the previous state for this delta */
2740 dlist_getnum64(kin, "SINCE_MODSEQ", &since_modseq);
2741 dlist_getnum32(kin, "SINCE_CRC", &since_crcs.basic);
2742 dlist_getnum32(kin, "SINCE_CRC_ANNOT", &since_crcs.annot);
2743
2744 options = sync_parse_options(options_str);
2745
2746 r = mailbox_open_iwl(mboxname, &mailbox);
2747 if (!r) r = sync_mailbox_version_check(&mailbox);
2748 if (r == IMAP_MAILBOX_NONEXISTENT) {
2749 struct mboxlock *namespacelock = mboxname_usernamespacelock(mboxname);
2750 // try again under lock
2751 r = mailbox_open_iwl(mboxname, &mailbox);
2752 if (!r) r = sync_mailbox_version_check(&mailbox);
2753 if (r == IMAP_MAILBOX_NONEXISTENT) { // did we win a race?
2754 char *oldname = mboxlist_find_uniqueid(uniqueid, NULL, NULL);
2755 // if they have the same name it's probably an intermediate being
2756 // promoted. Intermediates, the gift that keeps on giving.
2757 if (oldname && strcmp(oldname, mboxname)) {
2758 syslog(LOG_ERR, "SYNCNOTICE: failed to create mailbox %s with uniqueid %s (already used by %s)",
2759 mboxname, uniqueid, oldname);
2760 free(oldname);
2761 r = IMAP_MAILBOX_MOVED;
2762 }
2763 else {
2764 r = mboxlist_createsync(mboxname, mbtype, partition,
2765 sstate->userid, sstate->authstate,
2766 options, uidvalidity, createdmodseq,
2767 highestmodseq, foldermodseq, acl,
2768 uniqueid, sstate->local_only, 0, &mailbox);
2769 }
2770 /* set a highestmodseq of 0 so ALL changes are future
2771 * changes and get applied */
2772 if (!r) mailbox->i.highestmodseq = 0;
2773 }
2774 mboxname_release(&namespacelock);
2775 }
2776 if (r) {
2777 syslog(LOG_ERR, "Failed to open mailbox %s to update: %s",
2778 mboxname, error_message(r));
2779 goto done;
2780 }
2781
2782 // immediate bail if we have an old state to compare
2783 if (since_modseq) {
2784 struct synccrcs mycrcs = mailbox_synccrcs(mailbox, 0);
2785 if (since_modseq != mailbox->i.highestmodseq ||
2786 !mailbox_crceq(since_crcs, mycrcs)) {
2787 syslog(LOG_ERR, "SYNCNOTICE: mailbox sync mismatch %s "
2788 "hms (m=%llu,r=%llu) crcs (m=%u/%u,r=%u/%u)",
2789 mailbox->name,
2790 since_modseq, mailbox->i.highestmodseq,
2791 since_crcs.basic, since_crcs.annot,
2792 mycrcs.basic, mycrcs.annot);
2793 r = IMAP_SYNC_CHANGED;
2794 goto done;
2795 }
2796 }
2797
2798 if (mailbox->mbtype != mbtype) {
2799 syslog(LOG_ERR, "INVALID MAILBOX TYPE %s (%d, %d)", mailbox->name, mailbox->mbtype, mbtype);
2800 /* is this even possible? */
2801 r = IMAP_MAILBOX_BADTYPE;
2802 goto done;
2803 }
2804
2805 part_list = sync_reserve_partlist(reserve_list, mailbox->part);
2806
2807 /* hold the annotate state open */
2808 r = mailbox_get_annotate_state(mailbox, ANNOTATE_ANY_UID, &astate);
2809 if (r) goto done;
2810
2811 /* and make it hold a transaction open */
2812 annotate_state_begin(astate);
2813
2814 if (strcmp(mailbox->uniqueid, uniqueid)) {
2815 if (opt_force) {
2816 syslog(LOG_NOTICE, "forcesync: fixing uniqueid %s (%s => %s)",
2817 mboxname, mailbox->uniqueid, uniqueid);
2818 free(mailbox->uniqueid);
2819 mailbox->uniqueid = xstrdup(uniqueid);
2820 mailbox->header_dirty = 1;
2821 }
2822 else {
2823 syslog(LOG_ERR, "SYNCNOTICE: Mailbox uniqueid changed %s (%s => %s) - retry",
2824 mboxname, mailbox->uniqueid, uniqueid);
2825 r = IMAP_MAILBOX_MOVED;
2826 goto done;
2827 }
2828 }
2829
2830 /* skip out now, it's going to mismatch for sure! */
2831 if (highestmodseq < mailbox->i.highestmodseq) {
2832 if (opt_force) {
2833 syslog(LOG_NOTICE, "forcesync: higher modseq on replica %s - "
2834 MODSEQ_FMT " < " MODSEQ_FMT,
2835 mboxname, highestmodseq, mailbox->i.highestmodseq);
2836 }
2837 else {
2838 syslog(LOG_ERR, "higher modseq on replica %s - "
2839 MODSEQ_FMT " < " MODSEQ_FMT,
2840 mboxname, highestmodseq, mailbox->i.highestmodseq);
2841 r = IMAP_SYNC_CHECKSUM;
2842 goto done;
2843 }
2844 }
2845
2846 /* skip out now, it's going to mismatch for sure! */
2847 if (uidvalidity < mailbox->i.uidvalidity) {
2848 if (opt_force) {
2849 syslog(LOG_NOTICE, "forcesync: higher uidvalidity on replica %s - %u < %u",
2850 mboxname, uidvalidity, mailbox->i.uidvalidity);
2851 }
2852 else {
2853 syslog(LOG_ERR, "higher uidvalidity on replica %s - %u < %u",
2854 mboxname, uidvalidity, mailbox->i.uidvalidity);
2855 r = IMAP_SYNC_CHECKSUM;
2856 goto done;
2857 }
2858 }
2859
2860 /* skip out now, it's going to mismatch for sure! */
2861 if (last_uid < mailbox->i.last_uid) {
2862 if (opt_force) {
2863 syslog(LOG_NOTICE, "forcesync: higher last_uid on replica %s - %u < %u",
2864 mboxname, last_uid, mailbox->i.last_uid);
2865 }
2866 else {
2867 syslog(LOG_ERR, "higher last_uid on replica %s - %u < %u",
2868 mboxname, last_uid, mailbox->i.last_uid);
2869 r = IMAP_SYNC_CHECKSUM;
2870 goto done;
2871 }
2872 }
2873
2874 /* skip out now, it's going to mismatch for sure! */
2875 /* 0 is the default case, and should always be overwritten with the real value */
2876 if (createdmodseq > mailbox->i.createdmodseq && mailbox->i.createdmodseq != 0) {
2877 syslog(LOG_NOTICE, "SYNCNOTICE: lower createdmodseq on replica %s - %llu > %llu",
2878 mboxname, createdmodseq, mailbox->i.createdmodseq);
2879 }
2880
2881 /* NOTE - this is optional */
2882 if (mailbox_has_conversations(mailbox) && xconvmodseq) {
2883 modseq_t ourxconvmodseq = 0;
2884
2885 r = mailbox_get_xconvmodseq(mailbox, &ourxconvmodseq);
2886 if (r) {
2887 syslog(LOG_ERR, "Failed to read xconvmodseq for %s: %s",
2888 mboxname, error_message(r));
2889 goto done;
2890 }
2891
2892 /* skip out now, it's going to mismatch for sure! */
2893 if (xconvmodseq < ourxconvmodseq) {
2894 if (opt_force) {
2895 syslog(LOG_NOTICE, "forcesync: higher xconvmodseq on replica %s - %llu < %llu",
2896 mboxname, xconvmodseq, ourxconvmodseq);
2897 }
2898 else {
2899 syslog(LOG_ERR, "higher xconvmodseq on replica %s - %llu < %llu",
2900 mboxname, xconvmodseq, ourxconvmodseq);
2901 r = IMAP_SYNC_CHECKSUM;
2902 goto done;
2903 }
2904 }
2905 }
2906
2907 r = sync_mailbox_compare_update(mailbox, kr, 0, part_list);
2908 if (r) goto done;
2909
2910 /* now we're committed to writing something no matter what happens! */
2911
2912 mailbox_index_dirty(mailbox);
2913
2914 mailbox->silentchanges = 1;
2915
2916 /* always take the ACL from the master, it's not versioned */
2917 r = mboxlist_sync_setacls(mboxname, acl, foldermodseq ? foldermodseq : highestmodseq);
2918 if (!r) r = mailbox_set_acl(mailbox, acl);
2919 if (r) goto done;
2920
2921 /* take all mailbox (not message) annotations - aka metadata,
2922 * they're not versioned either */
2923 if (ka)
2924 decode_annotations(ka, &mannots, mailbox, NULL);
2925
2926 r = read_annotations(mailbox, NULL, &rannots, 0, 0);
2927 if (!r) r = apply_annotations(mailbox, NULL, rannots, mannots, 0);
2928
2929 if (r) {
2930 syslog(LOG_ERR, "syncerror: annotations failed to apply to %s",
2931 mailbox->name);
2932 goto done;
2933 }
2934
2935 r = sync_mailbox_compare_update(mailbox, kr, 1, part_list);
2936 if (r) {
2937 abort();
2938 return r;
2939 }
2940
2941 if (!opt_force) {
2942 assert(mailbox->i.last_uid <= last_uid);
2943 }
2944 mailbox->i.last_uid = last_uid;
2945 mailbox->i.recentuid = recentuid;
2946 mailbox->i.highestmodseq = highestmodseq;
2947 mailbox->i.recenttime = recenttime;
2948 mailbox->i.last_appenddate = last_appenddate;
2949 mailbox->i.pop3_last_login = pop3_last_login;
2950 mailbox->i.pop3_show_after = pop3_show_after;
2951 mailbox->i.createdmodseq = createdmodseq;
2952 /* only alter the syncable options */
2953 mailbox->i.options = (options & MAILBOX_OPTIONS_MASK) |
2954 (mailbox->i.options & ~MAILBOX_OPTIONS_MASK);
2955
2956 /* always set the highestmodseq */
2957 mboxname_setmodseq(mailbox->name, highestmodseq, mailbox->mbtype, /*dofolder*/0);
2958
2959 /* this happens rarely, so let us know */
2960 if (mailbox->i.uidvalidity != uidvalidity) {
2961 syslog(LOG_NOTICE, "%s uidvalidity changed, updating %u => %u",
2962 mailbox->name, mailbox->i.uidvalidity, uidvalidity);
2963 /* make sure nothing new gets created with a lower value */
2964 mailbox->i.uidvalidity = mboxname_setuidvalidity(mailbox->name, uidvalidity);
2965 }
2966
2967 if (mailbox_has_conversations(mailbox)) {
2968 r = mailbox_update_xconvmodseq(mailbox, xconvmodseq, opt_force);
2969 }
2970
2971 if (config_getswitch(IMAPOPT_REVERSEACLS) && raclmodseq) {
2972 mboxname_setraclmodseq(mailbox->name, raclmodseq);
2973 }
2974
2975 done:
2976 sync_annot_list_free(&mannots);
2977 sync_annot_list_free(&rannots);
2978
2979 /* check the CRC too */
2980 if (!r && !mailbox_crceq(synccrcs, mailbox_synccrcs(mailbox, 0))) {
2981 /* try forcing a recalculation */
2982 if (!mailbox_crceq(synccrcs, mailbox_synccrcs(mailbox, 1)))
2983 r = IMAP_SYNC_CHECKSUM;
2984 }
2985
2986 mailbox_close(&mailbox);
2987
2988 return r;
2989 }
2990
2991 /* ====================================================================== */
2992
getannotation_cb(const char * mailbox,uint32_t uid,const char * entry,const char * userid,const struct buf * value,const struct annotate_metadata * mdata,void * rock)2993 static int getannotation_cb(const char *mailbox,
2994 uint32_t uid __attribute__((unused)),
2995 const char *entry, const char *userid,
2996 const struct buf *value,
2997 const struct annotate_metadata *mdata __attribute__((unused)),
2998 void *rock)
2999 {
3000 struct protstream *pout = (struct protstream *)rock;
3001 struct dlist *kl;
3002
3003 kl = dlist_newkvlist(NULL, "ANNOTATION");
3004 dlist_setatom(kl, "MBOXNAME", mailbox);
3005 dlist_setatom(kl, "ENTRY", entry);
3006 dlist_setatom(kl, "USERID", userid);
3007 dlist_setmap(kl, "VALUE", value->s, value->len);
3008 sync_send_response(kl, pout);
3009 dlist_free(&kl);
3010
3011 return 0;
3012 }
3013
sync_get_annotation(struct dlist * kin,struct sync_state * sstate)3014 int sync_get_annotation(struct dlist *kin, struct sync_state *sstate)
3015 {
3016 const char *mboxname = kin->sval;
3017 return annotatemore_findall(mboxname, 0, "*", /*modseq*/0,
3018 &getannotation_cb, (void *) sstate->pout,
3019 /*flags*/0);
3020 }
3021
print_quota(struct quota * q,struct protstream * pout)3022 static void print_quota(struct quota *q, struct protstream *pout)
3023 {
3024 struct dlist *kl;
3025
3026 kl = dlist_newkvlist(NULL, "QUOTA");
3027 dlist_setatom(kl, "ROOT", q->root);
3028 sync_encode_quota_limits(kl, q->limits);
3029 dlist_setnum64(kl, "MODSEQ", q->modseq);
3030 sync_send_response(kl, pout);
3031 dlist_free(&kl);
3032 }
3033
quota_work(const char * root,struct protstream * pout)3034 static int quota_work(const char *root, struct protstream *pout)
3035 {
3036 struct quota q;
3037
3038 quota_init(&q, root);
3039 if (!quota_read(&q, NULL, 0))
3040 print_quota(&q, pout);
3041 quota_free(&q);
3042
3043 return 0;
3044 }
3045
sync_get_quota(struct dlist * kin,struct sync_state * sstate)3046 int sync_get_quota(struct dlist *kin, struct sync_state *sstate)
3047 {
3048 return quota_work(kin->sval, sstate->pout);
3049 }
3050
3051 struct mbox_rock {
3052 struct protstream *pout;
3053 struct sync_name_list *qrl;
3054 };
3055
sync_mailbox_byname(const char * name,void * rock)3056 static int sync_mailbox_byname(const char *name, void *rock)
3057 {
3058 struct mbox_rock *mrock = (struct mbox_rock *) rock;
3059 struct sync_name_list *qrl = mrock->qrl;
3060 struct mailbox *mailbox = NULL;
3061 struct dlist *kl = dlist_newkvlist(NULL, "MAILBOX");
3062 annotate_state_t *astate = NULL;
3063 int r;
3064
3065 r = mailbox_open_irl(name, &mailbox);
3066 if (!r) r = sync_mailbox_version_check(&mailbox);
3067 /* doesn't exist? Probably not finished creating or removing yet */
3068 if (r == IMAP_MAILBOX_NONEXISTENT ||
3069 r == IMAP_MAILBOX_RESERVED) {
3070 r = 0;
3071 goto out;
3072 }
3073 if (r) goto out;
3074
3075 /* hold the annotate state open */
3076 r = mailbox_get_annotate_state(mailbox, ANNOTATE_ANY_UID, &astate);
3077 if (r) goto out;
3078
3079 /* and make it hold a transaction open */
3080 annotate_state_begin(astate);
3081
3082 if (qrl && mailbox->quotaroot &&
3083 !sync_name_lookup(qrl, mailbox->quotaroot))
3084 sync_name_list_add(qrl, mailbox->quotaroot);
3085
3086 r = sync_prepare_dlists(mailbox, NULL, NULL, NULL, NULL, kl, NULL, 0,
3087 /*XXX fullannots*/1, 0);
3088
3089
3090 if (!r) sync_send_response(kl, mrock->pout);
3091
3092 out:
3093 mailbox_close(&mailbox);
3094 dlist_free(&kl);
3095
3096 return r;
3097 }
3098
sync_mailbox_byuniqueid(const char * uniqueid,void * rock)3099 static int sync_mailbox_byuniqueid(const char *uniqueid, void *rock)
3100 {
3101 char *name = mboxlist_find_uniqueid(uniqueid, NULL, NULL);
3102 int r = sync_mailbox_byname(name, rock);
3103 free(name);
3104 return r;
3105 }
3106
mailbox_cb(const mbentry_t * mbentry,void * rock)3107 static int mailbox_cb(const mbentry_t *mbentry, void *rock)
3108 {
3109 return sync_mailbox_byname(mbentry->name, rock);
3110 }
3111
sync_get_fullmailbox(struct dlist * kin,struct sync_state * sstate)3112 int sync_get_fullmailbox(struct dlist *kin, struct sync_state *sstate)
3113 {
3114 struct mailbox *mailbox = NULL;
3115 struct dlist *kl = dlist_newkvlist(NULL, "MAILBOX");
3116 int r;
3117
3118 r = mailbox_open_irl(kin->sval, &mailbox);
3119 if (!r) r = sync_mailbox_version_check(&mailbox);
3120 if (r) goto out;
3121
3122 r = sync_prepare_dlists(mailbox, NULL, NULL, NULL, NULL, kl, NULL, 1,
3123 /*XXX fullannots*/1, 0);
3124 if (r) goto out;
3125
3126 sync_send_response(kl, sstate->pout);
3127
3128 out:
3129 dlist_free(&kl);
3130 mailbox_close(&mailbox);
3131 return r;
3132 }
3133
sync_get_mailboxes(struct dlist * kin,struct sync_state * sstate)3134 int sync_get_mailboxes(struct dlist *kin, struct sync_state *sstate)
3135 {
3136 struct dlist *ki;
3137 struct mbox_rock mrock = { sstate->pout, NULL };
3138
3139 for (ki = kin->head; ki; ki = ki->next)
3140 sync_mailbox_byname(ki->sval, &mrock);
3141
3142 return 0;
3143 }
3144
sync_get_uniqueids(struct dlist * kin,struct sync_state * sstate)3145 int sync_get_uniqueids(struct dlist *kin, struct sync_state *sstate)
3146 {
3147 struct dlist *ki;
3148 struct mbox_rock mrock = { sstate->pout, NULL };
3149
3150 for (ki = kin->head; ki; ki = ki->next)
3151 sync_mailbox_byuniqueid(ki->sval, &mrock);
3152
3153 return 0;
3154 }
3155
3156 /* ====================================================================== */
3157
print_seen(const char * uniqueid,struct seendata * sd,void * rock)3158 static int print_seen(const char *uniqueid, struct seendata *sd, void *rock)
3159 {
3160 struct dlist *kl;
3161 struct protstream *pout = (struct protstream *) rock;
3162
3163 kl = dlist_newkvlist(NULL, "SEEN");
3164 dlist_setatom(kl, "UNIQUEID", uniqueid);
3165 dlist_setdate(kl, "LASTREAD", sd->lastread);
3166 dlist_setnum32(kl, "LASTUID", sd->lastuid);
3167 dlist_setdate(kl, "LASTCHANGE", sd->lastchange);
3168 dlist_setatom(kl, "SEENUIDS", sd->seenuids);
3169 sync_send_response(kl, pout);
3170 dlist_free(&kl);
3171
3172 return 0;
3173 }
3174
user_getseen(const char * userid,struct protstream * pout)3175 static int user_getseen(const char *userid, struct protstream *pout)
3176 {
3177 struct seen *seendb = NULL;
3178
3179 /* no SEEN DB is OK, just return */
3180 if (seen_open(userid, SEEN_SILENT, &seendb))
3181 return 0;
3182
3183 seen_foreach(seendb, print_seen, pout);
3184 seen_close(&seendb);
3185
3186 return 0;
3187 }
3188
3189
user_getsub(const char * userid,struct protstream * pout)3190 static int user_getsub(const char *userid, struct protstream *pout)
3191 {
3192 struct dlist *kl = dlist_newlist(NULL, "LSUB");
3193 strarray_t *sublist = mboxlist_sublist(userid);
3194 int i;
3195
3196 for (i = 0; i < sublist->count; i++) {
3197 const char *name = strarray_nth(sublist, i);
3198 dlist_setatom(kl, "MBOXNAME", name);
3199 }
3200
3201 if (kl->head)
3202 sync_send_response(kl, pout);
3203
3204 dlist_free(&kl);
3205 strarray_free(sublist);
3206
3207 return 0;
3208 }
3209
user_getsieve(const char * userid,struct protstream * pout)3210 static int user_getsieve(const char *userid, struct protstream *pout)
3211 {
3212 struct sync_sieve_list *sieve_list;
3213 struct sync_sieve *sieve;
3214 struct dlist *kl;
3215
3216 sieve_list = sync_sieve_list_generate(userid);
3217
3218 if (!sieve_list) return 0;
3219
3220 for (sieve = sieve_list->head; sieve; sieve = sieve->next) {
3221 kl = dlist_newkvlist(NULL, "SIEVE");
3222 dlist_setatom(kl, "FILENAME", sieve->name);
3223 dlist_setdate(kl, "LAST_UPDATE", sieve->last_update);
3224 dlist_setatom(kl, "GUID", message_guid_encode(&sieve->guid));
3225 dlist_setnum32(kl, "ISACTIVE", sieve->active ? 1 : 0);
3226 sync_send_response(kl, pout);
3227 dlist_free(&kl);
3228 }
3229
3230 sync_sieve_list_free(&sieve_list);
3231
3232 return 0;
3233 }
3234
user_meta(const char * userid,struct protstream * pout)3235 static int user_meta(const char *userid, struct protstream *pout)
3236 {
3237 user_getseen(userid, pout);
3238 user_getsub(userid, pout);
3239 user_getsieve(userid, pout);
3240 return 0;
3241 }
3242
sync_get_meta(struct dlist * kin,struct sync_state * sstate)3243 int sync_get_meta(struct dlist *kin, struct sync_state *sstate)
3244 {
3245 return user_meta(kin->sval, sstate->pout);
3246 }
3247
sync_get_user(struct dlist * kin,struct sync_state * sstate)3248 int sync_get_user(struct dlist *kin, struct sync_state *sstate)
3249 {
3250 int r;
3251 struct sync_name_list *quotaroots;
3252 struct sync_name *qr;
3253 const char *userid = kin->sval;
3254 struct mbox_rock mrock;
3255
3256 quotaroots = sync_name_list_create();
3257 mrock.qrl = quotaroots;
3258 mrock.pout = sstate->pout;
3259
3260 r = mboxlist_usermboxtree(userid, NULL, mailbox_cb, &mrock, MBOXTREE_DELETED);
3261 if (r) goto bail;
3262
3263 for (qr = quotaroots->head; qr; qr = qr->next) {
3264 r = quota_work(qr->name, sstate->pout);
3265 if (r) goto bail;
3266 }
3267
3268 r = user_meta(userid, sstate->pout);
3269 if (r) goto bail;
3270
3271 sync_log_user(userid);
3272
3273 bail:
3274 sync_name_list_free("aroots);
3275 return r;
3276 }
3277
3278 /* ====================================================================== */
3279
sync_apply_unmailbox(struct dlist * kin,struct sync_state * sstate)3280 int sync_apply_unmailbox(struct dlist *kin, struct sync_state *sstate)
3281 {
3282 const char *mboxname = kin->sval;
3283
3284 struct mboxlock *namespacelock = mboxname_usernamespacelock(mboxname);
3285
3286 /* Delete with admin privileges */
3287 int r = mboxlist_deletemailbox_full(mboxname, sstate->userisadmin,
3288 sstate->userid, sstate->authstate,
3289 NULL, 0, sstate->local_only, 1, 0,
3290 /*silent*/1);
3291
3292 mboxname_release(&namespacelock);
3293
3294 return r;
3295 }
3296
sync_apply_rename(struct dlist * kin,struct sync_state * sstate)3297 int sync_apply_rename(struct dlist *kin, struct sync_state *sstate)
3298 {
3299 const char *oldmboxname;
3300 const char *newmboxname;
3301 const char *partition;
3302 uint32_t uidvalidity = 0;
3303 mbentry_t *mbentry = NULL;
3304 int r;
3305
3306 if (!dlist_getatom(kin, "OLDMBOXNAME", &oldmboxname))
3307 return IMAP_PROTOCOL_BAD_PARAMETERS;
3308 if (!dlist_getatom(kin, "NEWMBOXNAME", &newmboxname))
3309 return IMAP_PROTOCOL_BAD_PARAMETERS;
3310 if (!dlist_getatom(kin, "PARTITION", &partition))
3311 return IMAP_PROTOCOL_BAD_PARAMETERS;
3312
3313 /* optional */
3314 dlist_getnum32(kin, "UIDVALIDITY", &uidvalidity);
3315
3316 struct mboxlock *oldlock = NULL;
3317 struct mboxlock *newlock = NULL;
3318
3319 /* make sure we grab these locks in a stable order! */
3320 if (strcmpsafe(oldmboxname, newmboxname) < 0) {
3321 oldlock = mboxname_usernamespacelock(oldmboxname);
3322 newlock = mboxname_usernamespacelock(newmboxname);
3323 }
3324 else {
3325 // doesn't hurt to double lock, it's refcounted
3326 newlock = mboxname_usernamespacelock(newmboxname);
3327 oldlock = mboxname_usernamespacelock(oldmboxname);
3328 }
3329
3330 r = mboxlist_lookup(oldmboxname, &mbentry, 0);
3331
3332 if (!r) r = mboxlist_renamemailbox(mbentry, newmboxname, partition,
3333 uidvalidity, 1, sstate->userid,
3334 sstate->authstate, NULL, sstate->local_only, 1, 1,
3335 0/*keep_intermediaries*/,
3336 0/*move_subscription*/,
3337 1/*silent*/);
3338
3339 mboxlist_entry_free(&mbentry);
3340 mboxname_release(&oldlock);
3341 mboxname_release(&newlock);
3342
3343 return r;
3344 }
3345
sync_apply_changesub(struct dlist * kin,struct sync_state * sstate)3346 int sync_apply_changesub(struct dlist *kin, struct sync_state *sstate)
3347 {
3348 const char *mboxname;
3349 const char *userid;
3350 int add;
3351
3352 /* SUB or UNSUB */
3353 add = strcmp(kin->name, "SUB") ? 0 : 1;
3354
3355 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
3356 return IMAP_PROTOCOL_BAD_PARAMETERS;
3357 if (!dlist_getatom(kin, "USERID", &userid))
3358 return IMAP_PROTOCOL_BAD_PARAMETERS;
3359
3360 return mboxlist_changesub(mboxname, userid, sstate->authstate, add, add, 0);
3361 }
3362
3363 /* ====================================================================== */
3364
sync_apply_annotation(struct dlist * kin,struct sync_state * sstate)3365 int sync_apply_annotation(struct dlist *kin, struct sync_state *sstate)
3366 {
3367 struct entryattlist *entryatts = NULL;
3368 struct attvaluelist *attvalues = NULL;
3369 const char *mboxname = NULL;
3370 const char *entry = NULL;
3371 const char *mapval = NULL;
3372 size_t maplen = 0;
3373 struct buf value = BUF_INITIALIZER;
3374 const char *userid = NULL;
3375 char *name = NULL;
3376 struct mailbox *mailbox = NULL;
3377 annotate_state_t *astate = NULL;
3378 int r;
3379
3380 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
3381 return IMAP_PROTOCOL_BAD_PARAMETERS;
3382 if (!dlist_getatom(kin, "ENTRY", &entry))
3383 return IMAP_PROTOCOL_BAD_PARAMETERS;
3384 if (!dlist_getatom(kin, "USERID", &userid))
3385 return IMAP_PROTOCOL_BAD_PARAMETERS;
3386 if (!dlist_getmap(kin, "VALUE", &mapval, &maplen))
3387 return IMAP_PROTOCOL_BAD_PARAMETERS;
3388 buf_init_ro(&value, mapval, maplen);
3389
3390 appendattvalue(&attvalues,
3391 *userid ? "value.priv" : "value.shared",
3392 &value);
3393 appendentryatt(&entryatts, entry, attvalues);
3394
3395 astate = annotate_state_new();
3396 if (*mboxname) {
3397 r = mailbox_open_iwl(mboxname, &mailbox);
3398 if (r) goto done;
3399 r = sync_mailbox_version_check(&mailbox);
3400 if (r) goto done;
3401 r = annotate_state_set_mailbox(astate, mailbox);
3402 if (r) goto done;
3403 }
3404 else {
3405 r = annotate_state_set_server(astate);
3406 if (r) goto done;
3407 }
3408 annotate_state_set_auth(astate,
3409 sstate->userisadmin, userid, sstate->authstate);
3410
3411 r = annotate_state_store(astate, entryatts);
3412
3413 done:
3414 if (!r)
3415 r = annotate_state_commit(&astate);
3416 else
3417 annotate_state_abort(&astate);
3418
3419 mailbox_close(&mailbox);
3420
3421 buf_free(&value);
3422 freeentryatts(entryatts);
3423 free(name);
3424
3425 return r;
3426 }
3427
sync_apply_unannotation(struct dlist * kin,struct sync_state * sstate)3428 int sync_apply_unannotation(struct dlist *kin, struct sync_state *sstate)
3429 {
3430 struct entryattlist *entryatts = NULL;
3431 struct attvaluelist *attvalues = NULL;
3432 const char *mboxname = NULL;
3433 const char *entry = NULL;
3434 const char *userid = NULL;
3435 struct buf empty = BUF_INITIALIZER;
3436 char *name = NULL;
3437 struct mailbox *mailbox = NULL;
3438 annotate_state_t *astate = NULL;
3439 int r;
3440
3441 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
3442 return IMAP_PROTOCOL_BAD_PARAMETERS;
3443 if (!dlist_getatom(kin, "ENTRY", &entry))
3444 return IMAP_PROTOCOL_BAD_PARAMETERS;
3445 if (!dlist_getatom(kin, "USERID", &userid))
3446 return IMAP_PROTOCOL_BAD_PARAMETERS;
3447
3448 appendattvalue(&attvalues,
3449 *userid ? "value.priv" : "value.shared",
3450 &empty);
3451 appendentryatt(&entryatts, entry, attvalues);
3452
3453 astate = annotate_state_new();
3454 if (*mboxname) {
3455 r = mailbox_open_iwl(mboxname, &mailbox);
3456 if (r) goto done;
3457 r = sync_mailbox_version_check(&mailbox);
3458 if (r) goto done;
3459 r = annotate_state_set_mailbox(astate, mailbox);
3460 if (r) goto done;
3461 }
3462 else {
3463 r = annotate_state_set_server(astate);
3464 if (r) goto done;
3465 }
3466 annotate_state_set_auth(astate,
3467 sstate->userisadmin, userid, sstate->authstate);
3468
3469 r = annotate_state_store(astate, entryatts);
3470
3471 done:
3472 if (!r)
3473 r = annotate_state_commit(&astate);
3474 else
3475 annotate_state_abort(&astate);
3476 mailbox_close(&mailbox);
3477 freeentryatts(entryatts);
3478 free(name);
3479
3480 return r;
3481 }
3482
sync_apply_sieve(struct dlist * kin,struct sync_state * sstate)3483 int sync_apply_sieve(struct dlist *kin,
3484 struct sync_state *sstate __attribute__((unused)))
3485 {
3486 const char *userid;
3487 const char *filename;
3488 time_t last_update;
3489 const char *content;
3490 size_t len;
3491
3492 if (!dlist_getatom(kin, "USERID", &userid))
3493 return IMAP_PROTOCOL_BAD_PARAMETERS;
3494 if (!dlist_getatom(kin, "FILENAME", &filename))
3495 return IMAP_PROTOCOL_BAD_PARAMETERS;
3496 if (!dlist_getdate(kin, "LAST_UPDATE", &last_update))
3497 return IMAP_PROTOCOL_BAD_PARAMETERS;
3498 if (!dlist_getmap(kin, "CONTENT", &content, &len))
3499 return IMAP_PROTOCOL_BAD_PARAMETERS;
3500
3501 return sync_sieve_upload(userid, filename, last_update, content, len);
3502 }
3503
sync_apply_unsieve(struct dlist * kin,struct sync_state * sstate)3504 int sync_apply_unsieve(struct dlist *kin,
3505 struct sync_state *sstate __attribute__((unused)))
3506 {
3507 const char *userid;
3508 const char *filename;
3509
3510 if (!dlist_getatom(kin, "USERID", &userid))
3511 return IMAP_PROTOCOL_BAD_PARAMETERS;
3512 if (!dlist_getatom(kin, "FILENAME", &filename))
3513 return IMAP_PROTOCOL_BAD_PARAMETERS;
3514
3515 return sync_sieve_delete(userid, filename);
3516 }
3517
sync_apply_activate_sieve(struct dlist * kin,struct sync_state * sstate __attribute ((unused)))3518 int sync_apply_activate_sieve(struct dlist *kin,
3519 struct sync_state *sstate __attribute((unused)))
3520 {
3521 const char *userid;
3522 const char *filename;
3523
3524 if (!dlist_getatom(kin, "USERID", &userid))
3525 return IMAP_PROTOCOL_BAD_PARAMETERS;
3526 if (!dlist_getatom(kin, "FILENAME", &filename))
3527 return IMAP_PROTOCOL_BAD_PARAMETERS;
3528
3529 return sync_sieve_activate(userid, filename);
3530 }
3531
sync_apply_unactivate_sieve(struct dlist * kin,struct sync_state * sstate)3532 int sync_apply_unactivate_sieve(struct dlist *kin,
3533 struct sync_state *sstate __attribute__((unused)))
3534 {
3535 const char *userid;
3536
3537 if (!dlist_getatom(kin, "USERID", &userid))
3538 return IMAP_PROTOCOL_BAD_PARAMETERS;
3539
3540 return sync_sieve_deactivate(userid);
3541 }
3542
sync_apply_seen(struct dlist * kin,struct sync_state * sstate)3543 int sync_apply_seen(struct dlist *kin,
3544 struct sync_state *sstate __attribute__((unused)))
3545 {
3546 int r;
3547 struct seen *seendb = NULL;
3548 struct seendata sd = SEENDATA_INITIALIZER;
3549 const char *seenuids;
3550 const char *userid;
3551 const char *uniqueid;
3552
3553 if (!dlist_getatom(kin, "USERID", &userid))
3554 return IMAP_PROTOCOL_BAD_PARAMETERS;
3555 if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
3556 return IMAP_PROTOCOL_BAD_PARAMETERS;
3557 if (!dlist_getdate(kin, "LASTREAD", &sd.lastread))
3558 return IMAP_PROTOCOL_BAD_PARAMETERS;
3559 if (!dlist_getnum32(kin, "LASTUID", &sd.lastuid))
3560 return IMAP_PROTOCOL_BAD_PARAMETERS;
3561 if (!dlist_getdate(kin, "LASTCHANGE", &sd.lastchange))
3562 return IMAP_PROTOCOL_BAD_PARAMETERS;
3563 if (!dlist_getatom(kin, "SEENUIDS", &seenuids))
3564 return IMAP_PROTOCOL_BAD_PARAMETERS;
3565 sd.seenuids = xstrdup(seenuids);
3566
3567 r = seen_open(userid, SEEN_CREATE, &seendb);
3568 if (r) return r;
3569
3570 r = seen_write(seendb, uniqueid, &sd);
3571 seen_close(&seendb);
3572
3573 seen_freedata(&sd);
3574
3575 return r;
3576 }
3577
addmbox_cb(const mbentry_t * mbentry,void * rock)3578 EXPORTED int addmbox_cb(const mbentry_t *mbentry, void *rock)
3579 {
3580 strarray_t *list = (strarray_t *)rock;
3581 strarray_append(list, mbentry->name);
3582 return 0;
3583 }
3584
sync_apply_unuser(struct dlist * kin,struct sync_state * sstate)3585 int sync_apply_unuser(struct dlist *kin, struct sync_state *sstate)
3586 {
3587 const char *userid = kin->sval;
3588 int r = 0;
3589 int i;
3590
3591 /* nothing to do if there's no userid */
3592 if (!userid || !userid[0]) {
3593 syslog(LOG_WARNING, "ignoring attempt to %s() without userid", __func__);
3594 return 0;
3595 }
3596
3597 struct mboxlock *namespacelock = user_namespacelock(userid);
3598
3599 /* Nuke subscriptions */
3600 /* ignore failures here - the subs file gets deleted soon anyway */
3601 strarray_t *list = mboxlist_sublist(userid);
3602 for (i = 0; i < list->count; i++) {
3603 const char *name = strarray_nth(list, i);
3604 mboxlist_changesub(name, userid, sstate->authstate, 0, 0, 0);
3605 }
3606
3607 strarray_truncate(list, 0);
3608 r = mboxlist_usermboxtree(userid, NULL, addmbox_cb, list, MBOXTREE_DELETED);
3609 if (r) goto done;
3610
3611 /* delete in reverse so INBOX is last */
3612 for (i = list->count; i; i--) {
3613 const char *name = strarray_nth(list, i-1);
3614 r = mboxlist_deletemailbox(name, sstate->userisadmin,
3615 sstate->userid, sstate->authstate,
3616 NULL, 0, sstate->local_only, 1, 0);
3617 if (r) goto done;
3618 }
3619
3620 r = user_deletedata(userid, 1);
3621
3622 done:
3623 mboxname_release(&namespacelock);
3624 strarray_free(list);
3625
3626 return r;
3627 }
3628
3629 /* ====================================================================== */
3630
sync_get_sieve(struct dlist * kin,struct sync_state * sstate)3631 int sync_get_sieve(struct dlist *kin, struct sync_state *sstate)
3632 {
3633 struct dlist *kl;
3634 const char *userid;
3635 const char *filename;
3636 uint32_t size;
3637 char *sieve;
3638
3639 if (!dlist_getatom(kin, "USERID", &userid))
3640 return IMAP_PROTOCOL_BAD_PARAMETERS;
3641 if (!dlist_getatom(kin, "FILENAME", &filename))
3642 return IMAP_PROTOCOL_BAD_PARAMETERS;
3643
3644 sieve = sync_sieve_read(userid, filename, &size);
3645 if (!sieve)
3646 return IMAP_MAILBOX_NONEXISTENT;
3647
3648 kl = dlist_newkvlist(NULL, "SIEVE");
3649 dlist_setatom(kl, "USERID", userid);
3650 dlist_setatom(kl, "FILENAME", filename);
3651 dlist_setmap(kl, "CONTENT", sieve, size);
3652 sync_send_response(kl, sstate->pout);
3653 dlist_free(&kl);
3654 free(sieve);
3655
3656 return 0;
3657 }
3658
3659 /* NOTE - can't lock a mailbox here, because it could deadlock,
3660 * so just pick the file out from under the hood */
sync_get_message(struct dlist * kin,struct sync_state * sstate)3661 int sync_get_message(struct dlist *kin, struct sync_state *sstate)
3662 {
3663 const char *mboxname;
3664 const char *partition;
3665 const char *uniqueid;
3666 const char *guid;
3667 uint32_t uid;
3668 const char *fname;
3669 struct dlist *kl;
3670 struct message_guid tmp_guid;
3671 struct stat sbuf;
3672
3673 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
3674 return IMAP_PROTOCOL_BAD_PARAMETERS;
3675 if (!dlist_getatom(kin, "PARTITION", &partition))
3676 return IMAP_PROTOCOL_BAD_PARAMETERS;
3677 if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
3678 return IMAP_PROTOCOL_BAD_PARAMETERS;
3679 if (!dlist_getatom(kin, "GUID", &guid))
3680 return IMAP_PROTOCOL_BAD_PARAMETERS;
3681 if (!dlist_getnum32(kin, "UID", &uid))
3682 return IMAP_PROTOCOL_BAD_PARAMETERS;
3683 if (!message_guid_decode(&tmp_guid, guid))
3684 return IMAP_PROTOCOL_BAD_PARAMETERS;
3685
3686 fname = mboxname_datapath(partition, mboxname, uniqueid, uid);
3687 if (stat(fname, &sbuf) == -1) {
3688 fname = mboxname_archivepath(partition, mboxname, uniqueid, uid);
3689 if (stat(fname, &sbuf) == -1)
3690 return IMAP_MAILBOX_NONEXISTENT;
3691 }
3692
3693 kl = dlist_setfile(NULL, "MESSAGE", partition, &tmp_guid, sbuf.st_size, fname);
3694 sync_send_response(kl, sstate->pout);
3695 dlist_free(&kl);
3696
3697 return 0;
3698 }
3699
sync_apply_expunge(struct dlist * kin,struct sync_state * sstate)3700 int sync_apply_expunge(struct dlist *kin,
3701 struct sync_state *sstate __attribute__((unused)))
3702 {
3703 const char *mboxname;
3704 const char *uniqueid;
3705 struct dlist *ul;
3706 struct dlist *ui;
3707 struct mailbox *mailbox = NULL;
3708 int r = 0;
3709
3710 if (!dlist_getatom(kin, "MBOXNAME", &mboxname))
3711 return IMAP_PROTOCOL_BAD_PARAMETERS;
3712 if (!dlist_getatom(kin, "UNIQUEID", &uniqueid))
3713 return IMAP_PROTOCOL_BAD_PARAMETERS;
3714 if (!dlist_getlist(kin, "UID", &ul))
3715 return IMAP_PROTOCOL_BAD_PARAMETERS;
3716
3717 r = mailbox_open_iwl(mboxname, &mailbox);
3718 if (!r) r = sync_mailbox_version_check(&mailbox);
3719 if (r) goto done;
3720
3721 /* don't want to expunge the wrong mailbox! */
3722 if (strcmp(mailbox->uniqueid, uniqueid)) {
3723 r = IMAP_MAILBOX_MOVED;
3724 goto done;
3725 }
3726
3727 for (ui = ul->head; ui; ui = ui->next) {
3728 struct index_record oldrecord;
3729 r = mailbox_find_index_record(mailbox, dlist_num(ui), &oldrecord);
3730 if (r) continue; /* skip */
3731 oldrecord.internal_flags |= FLAG_INTERNAL_EXPUNGED;
3732 oldrecord.silent = 1; /* so the next sync will succeed */
3733 r = mailbox_rewrite_index_record(mailbox, &oldrecord);
3734 if (r) goto done;
3735 }
3736
3737 done:
3738 mailbox_close(&mailbox);
3739 return r;
3740 }
3741
sync_apply_message(struct dlist * kin,struct sync_reserve_list * reserve_list,struct sync_state * sstate __attribute ((unused)))3742 int sync_apply_message(struct dlist *kin,
3743 struct sync_reserve_list *reserve_list,
3744 struct sync_state *sstate __attribute((unused)))
3745 {
3746 struct sync_msgid_list *part_list;
3747 struct dlist *ki;
3748 struct sync_msgid *msgid;
3749
3750 for (ki = kin->head; ki; ki = ki->next) {
3751 struct message_guid *guid;
3752 const char *part;
3753 size_t size;
3754 const char *fname;
3755
3756 /* XXX - complain more? */
3757 if (!dlist_tofile(ki, &part, &guid, (unsigned long *) &size, &fname))
3758 continue;
3759
3760 part_list = sync_reserve_partlist(reserve_list, part);
3761 msgid = sync_msgid_insert(part_list, guid);
3762 if (!msgid->need_upload)
3763 continue;
3764
3765 msgid->size = size;
3766 if (!msgid->fname) msgid->fname = xstrdup(fname);
3767 msgid->need_upload = 0;
3768 part_list->toupload--;
3769 }
3770
3771 return 0;
3772 }
3773
3774 /* ====================================================================== */
3775
sync_restore_mailbox(struct dlist * kin,struct sync_reserve_list * reserve_list,struct sync_state * sstate)3776 int sync_restore_mailbox(struct dlist *kin,
3777 struct sync_reserve_list *reserve_list,
3778 struct sync_state *sstate)
3779 {
3780 /* fields from the request, all but mboxname are optional */
3781 const char *mboxname;
3782 const char *uniqueid = NULL;
3783 const char *partition = NULL;
3784 const char *mboxtype = NULL;
3785 const char *acl = NULL;
3786 const char *options_str = NULL;
3787 modseq_t highestmodseq = 0;
3788 uint32_t uidvalidity = 0;
3789 struct dlist *kr = NULL;
3790 struct dlist *ka = NULL;
3791 modseq_t xconvmodseq = 0;
3792 modseq_t createdmodseq = 0;
3793 modseq_t foldermodseq = 0;
3794
3795 /* derived fields */
3796 uint32_t options = 0;
3797 uint32_t mbtype = 0;
3798
3799 struct mailbox *mailbox = NULL;
3800 struct sync_msgid_list *part_list;
3801 annotate_state_t *astate = NULL;
3802 struct dlist *ki;
3803 int has_append = 0;
3804 int is_new_mailbox = 0;
3805 int r;
3806
3807 if (!dlist_getatom(kin, "MBOXNAME", &mboxname)) {
3808 syslog(LOG_DEBUG, "%s: missing MBOXNAME", __func__);
3809 return IMAP_PROTOCOL_BAD_PARAMETERS;
3810 }
3811
3812 /* optional */
3813 dlist_getatom(kin, "PARTITION", &partition);
3814 dlist_getatom(kin, "ACL", &acl);
3815 dlist_getatom(kin, "OPTIONS", &options_str);
3816 dlist_getlist(kin, "RECORD", &kr);
3817 dlist_getlist(kin, "ANNOTATIONS", &ka);
3818 dlist_getatom(kin, "MBOXTYPE", &mboxtype);
3819 dlist_getnum64(kin, "XCONVMODSEQ", &xconvmodseq);
3820
3821 /* derived */
3822 options = sync_parse_options(options_str);
3823 mbtype = mboxlist_string_to_mbtype(mboxtype);
3824
3825 /* XXX sanely handle deletedprefix mboxnames */
3826
3827 /* If the mboxname being restored already exists, then restored messages
3828 * are appended to it. The UNIQUEID, HIGHESTMODSEQ, UIDVALIDITY and
3829 * MBOXTYPE fields in the dlist, and the UID, MODSEQ and LAST_UPDATED fields
3830 * in the restored message records, are ignored entirely.
3831 *
3832 * If the mboxname does not exist, we create it. If UNIQUEID, HIGHESTMODSEQ
3833 * and UIDVALIDITY were provided, we try to preserve them, and if we can, we
3834 * also try to preserve the UID, MODSEQ and LAST_UPDATED fields of the
3835 * restored messages. This is useful when e.g. rebuilding a server from a
3836 * backup, and wanting clients' IMAP states to match.
3837 *
3838 * If UNIQUEID, HIGHESTMODSEQ or UIDVALIDITY are not provided, we don't try
3839 * to preserve them. We create the mailbox, but then append the restored
3840 * messages to it as if it already existed (new UID et al).
3841 */
3842
3843 /* open/create mailbox */
3844 r = mailbox_open_iwl(mboxname, &mailbox);
3845 if (!r) r = sync_mailbox_version_check(&mailbox);
3846 syslog(LOG_DEBUG, "%s: mailbox_open_iwl %s: %s",
3847 __func__, mboxname, error_message(r));
3848 if (r == IMAP_MAILBOX_NONEXISTENT) {
3849 dlist_getatom(kin, "UNIQUEID", &uniqueid);
3850 dlist_getnum64(kin, "HIGHESTMODSEQ", &highestmodseq);
3851 dlist_getnum32(kin, "UIDVALIDITY", &uidvalidity);
3852 dlist_getnum64(kin, "CREATEDMODSEQ", &createdmodseq);
3853 dlist_getnum64(kin, "FOLDERMODSEQ", &foldermodseq);
3854
3855 /* if any of these three weren't set, disregard the others too */
3856 if (!uniqueid || !highestmodseq || !uidvalidity) {
3857 uniqueid = NULL;
3858 highestmodseq = 0;
3859 uidvalidity = 0;
3860 }
3861
3862 struct mboxlock *namespacelock = mboxname_usernamespacelock(mboxname);
3863 // try again under lock
3864 r = mailbox_open_iwl(mboxname, &mailbox);
3865 if (!r) r = sync_mailbox_version_check(&mailbox);
3866 if (r == IMAP_MAILBOX_NONEXISTENT) { // did we win a race?
3867 r = mboxlist_createsync(mboxname, mbtype, partition,
3868 sstate->userid, sstate->authstate,
3869 options, uidvalidity, createdmodseq,
3870 highestmodseq, foldermodseq, acl,
3871 uniqueid, sstate->local_only, 0, &mailbox);
3872 syslog(LOG_DEBUG, "%s: mboxlist_createsync %s: %s",
3873 __func__, mboxname, error_message(r));
3874 is_new_mailbox = 1;
3875 }
3876 mboxname_release(&namespacelock);
3877 }
3878 if (r) {
3879 syslog(LOG_ERR, "Failed to open mailbox %s to restore: %s",
3880 mboxname, error_message(r));
3881 return r;
3882 }
3883
3884 /* XXX what if we've opened a deleted mailbox? */
3885
3886 /* XXX verify mailbox is suitable? */
3887
3888 /* make sure mailbox types match */
3889 if (mailbox->mbtype != mbtype) {
3890 syslog(LOG_ERR, "restore mailbox %s: mbtype mismatch (%d, %d)",
3891 mailbox->name, mailbox->mbtype, mbtype);
3892 r = IMAP_MAILBOX_BADTYPE;
3893 goto bail;
3894 }
3895
3896 part_list = sync_reserve_partlist(reserve_list, mailbox->part);
3897
3898 /* hold the annotate state open */
3899 r = mailbox_get_annotate_state(mailbox, ANNOTATE_ANY_UID, &astate);
3900 syslog(LOG_DEBUG, "%s: mailbox_get_annotate_state %s: %s",
3901 __func__, mailbox->name, error_message(r));
3902 if (r) goto bail;
3903
3904 /* and make it hold a transaction open */
3905 annotate_state_begin(astate);
3906
3907 /* XXX do we need to hold the conversation state open? */
3908
3909 /* restore mailbox annotations */
3910 if (ka) {
3911 struct sync_annot_list *restore_annots = NULL;
3912 struct sync_annot_list *mailbox_annots = NULL;
3913
3914 r = decode_annotations(ka, &restore_annots, mailbox, NULL);
3915
3916 if (!r) r = read_annotations(mailbox, NULL, &mailbox_annots, 0, 0);
3917
3918 if (!r) r = apply_annotations(mailbox, NULL,
3919 mailbox_annots, restore_annots,
3920 !is_new_mailbox);
3921 if (r)
3922 syslog(LOG_WARNING,
3923 "restore mailbox %s: unable to apply mailbox annotations: %s",
3924 mailbox->name, error_message(r));
3925
3926 /* keep going on annotations failure*/
3927 r = 0;
3928
3929 sync_annot_list_free(&restore_annots);
3930 sync_annot_list_free(&mailbox_annots);
3931 }
3932
3933 /* n.b. undocumented assumption here and in sync_apply_mailbox
3934 * that records will be provided sorted by ascending uid */
3935 for (ki = kr->head; ki; ki = ki->next) {
3936 struct sync_annot_list *annots = NULL;
3937 struct index_record record = {0};
3938
3939 /* XXX skip if the guid is already in this folder? */
3940
3941 r = parse_upload(ki, mailbox, &record, &annots);
3942 syslog(LOG_DEBUG, "%s: parse_upload %s: %s",
3943 __func__, mailbox->name, error_message(r));
3944 if (r) goto bail;
3945
3946 /* generate a uid if we can't reuse a provided one */
3947 if (!uidvalidity || record.uid <= mailbox->i.last_uid)
3948 record.uid = mailbox->i.last_uid + 1;
3949
3950 /* reuse a provided modseq/last_updated if safe */
3951 if (highestmodseq && record.modseq && record.modseq <= mailbox->i.highestmodseq)
3952 record.silent = 1;
3953
3954 r = sync_append_copyfile(mailbox, &record, annots, part_list);
3955
3956 has_append = 1;
3957 sync_annot_list_free(&annots);
3958
3959 if (r) goto bail;
3960 }
3961
3962 r = mailbox_commit(mailbox);
3963 syslog(LOG_DEBUG, "%s: mailbox_commit %s: %s",
3964 __func__, mailbox->name, error_message(r));
3965 if (r) {
3966 syslog(LOG_ERR, "%s: mailbox_commit(%s): %s",
3967 __func__, mailbox->name, error_message(r));
3968 }
3969
3970 if (!r && has_append)
3971 sync_log_append(mailbox->name);
3972
3973 mailbox_close(&mailbox);
3974
3975 return r;
3976
3977 bail:
3978 mailbox_abort(mailbox);
3979 mailbox_close(&mailbox);
3980
3981 return r;
3982 }
3983
3984 /* ====================================================================== */
3985
sync_response(int r)3986 static const char *sync_response(int r)
3987 {
3988 const char *resp;
3989
3990 switch (r) {
3991 case 0:
3992 resp = "OK success";
3993 break;
3994 case IMAP_INVALID_USER:
3995 resp = "NO IMAP_INVALID_USER No Such User";
3996 break;
3997 case IMAP_MAILBOX_NONEXISTENT:
3998 resp = "NO IMAP_MAILBOX_NONEXISTENT No Such Mailbox";
3999 break;
4000 case IMAP_MAILBOX_LOCKED:
4001 resp = "NO IMAP_MAILBOX_LOCKED Mailbox locked";
4002 break;
4003 case IMAP_MAILBOX_MOVED:
4004 resp = "NO IMAP_MAILBOX_MOVED Mailbox exists with another name or uniqueid";
4005 break;
4006 case IMAP_MAILBOX_NOTSUPPORTED:
4007 resp = "NO IMAP_MAILBOX_NOTSUPPORTED Operation is not supported on mailbox";
4008 break;
4009 case IMAP_SYNC_CHECKSUM:
4010 resp = "NO IMAP_SYNC_CHECKSUM Checksum Failure";
4011 break;
4012 case IMAP_SYNC_CHANGED:
4013 resp = "NO IMAP_SYNC_CHANGED Changed since last sync";
4014 break;
4015 case IMAP_SYNC_BADSIEVE:
4016 resp = "NO IMAP_SYNC_BADSIEVE Sieve script compilation failure";
4017 break;
4018 case IMAP_PROTOCOL_ERROR:
4019 resp = "NO IMAP_PROTOCOL_ERROR Protocol error";
4020 break;
4021 case IMAP_PROTOCOL_BAD_PARAMETERS:
4022 resp = "NO IMAP_PROTOCOL_BAD_PARAMETERS";
4023 // XXX resp = "NO IMAP_PROTOCOL_BAD_PARAMETERS near %s\r\n", dlist_lastkey());
4024 break;
4025 default:
4026 resp = "NO Unknown error";
4027 }
4028
4029 return resp;
4030 }
4031
4032 /* ======================= client-side sync =========================== */
4033
4034 /* Routines relevant to reserve operation */
4035
4036 /* Find the messages that we will want to upload from this mailbox,
4037 * flag messages that are already available at the server end */
4038
sync_find_reserve_messages(struct mailbox * mailbox,uint32_t fromuid,uint32_t touid,struct sync_msgid_list * part_list)4039 int sync_find_reserve_messages(struct mailbox *mailbox,
4040 uint32_t fromuid,
4041 uint32_t touid,
4042 struct sync_msgid_list *part_list)
4043 {
4044
4045 struct mailbox_iter *iter = mailbox_iter_init(mailbox, 0, ITER_SKIP_UNLINKED);
4046 mailbox_iter_startuid(iter, fromuid+1); /* only read new records */
4047 const message_t *msg;
4048 while ((msg = mailbox_iter_step(iter))) {
4049 const struct index_record *record = msg_record(msg);
4050 sync_msgid_insert(part_list, &record->guid);
4051 if (record->uid >= touid) break;
4052 }
4053 mailbox_iter_done(&iter);
4054
4055 return 0;
4056 }
4057
calculate_intermediate_state(struct mailbox * mailbox,modseq_t frommodseq,uint32_t fromuid,uint32_t batchsize,uint32_t * touidp,modseq_t * tomodseqp)4058 static int calculate_intermediate_state(struct mailbox *mailbox,
4059 modseq_t frommodseq,
4060 uint32_t fromuid,
4061 uint32_t batchsize,
4062 uint32_t *touidp,
4063 modseq_t *tomodseqp)
4064 {
4065 modseq_t tomodseq = mailbox->i.highestmodseq;
4066 uint32_t touid = fromuid;
4067 uint32_t seen = 0;
4068
4069 struct mailbox_iter *iter = mailbox_iter_init(mailbox, 0, ITER_SKIP_UNLINKED);
4070 mailbox_iter_startuid(iter, fromuid+1); /* only read new records */
4071 const message_t *msg;
4072 while ((msg = mailbox_iter_step(iter))) {
4073 const struct index_record *record = msg_record(msg);
4074 if (seen < batchsize) {
4075 touid = record->uid;
4076 }
4077 else if (record->modseq <= tomodseq)
4078 tomodseq = record->modseq - 1;
4079 seen++;
4080 }
4081 mailbox_iter_done(&iter);
4082
4083 /* no need to batch if there are fewer than batchsize records */
4084 if (seen <= batchsize)
4085 return 0;
4086
4087 /* must have found at least one record past the end to do a partial batch,
4088 * and we need a highestmodseq at least one less than that records so that
4089 * it can successfully sync */
4090 if (tomodseq > frommodseq && tomodseq < mailbox->i.highestmodseq) {
4091 *tomodseqp = tomodseq;
4092 *touidp = touid;
4093 return 1;
4094 }
4095
4096 /* can't find an intermediate modseq */
4097 return 0;
4098 }
4099
find_reserve_all(struct sync_name_list * mboxname_list,const char * topart,struct sync_folder_list * master_folders,struct sync_folder_list * replica_folders,struct sync_reserve_list * reserve_list,uint32_t batchsize)4100 static int find_reserve_all(struct sync_name_list *mboxname_list,
4101 const char *topart,
4102 struct sync_folder_list *master_folders,
4103 struct sync_folder_list *replica_folders,
4104 struct sync_reserve_list *reserve_list,
4105 uint32_t batchsize)
4106 {
4107 struct sync_name *mbox;
4108 struct sync_folder *rfolder;
4109 struct sync_msgid_list *part_list;
4110 struct mailbox *mailbox = NULL;
4111 int r = 0;
4112
4113 /* Find messages we want to upload that are available on server */
4114 for (mbox = mboxname_list->head; mbox; mbox = mbox->next) {
4115 r = mailbox_open_irl(mbox->name, &mailbox);
4116 if (!r) r = sync_mailbox_version_check(&mailbox);
4117
4118 /* Quietly skip over folders which have been deleted since we
4119 started working (but record fact in case caller cares) */
4120 if (r == IMAP_MAILBOX_NONEXISTENT) {
4121 r = 0;
4122 continue;
4123 }
4124
4125 if (r) {
4126 syslog(LOG_ERR, "IOERROR: Failed to open %s: %s",
4127 mbox->name, error_message(r));
4128 goto bail;
4129 }
4130
4131 modseq_t xconvmodseq = 0;
4132 if (mailbox_has_conversations(mailbox)) {
4133 r = mailbox_get_xconvmodseq(mailbox, &xconvmodseq);
4134 if (r) {
4135 syslog(LOG_ERR, "IOERROR: Failed to get xconvmodseq %s: %s",
4136 mbox->name, error_message(r));
4137 goto bail;
4138 }
4139 }
4140 modseq_t raclmodseq = mboxname_readraclmodseq(mbox->name);
4141
4142 /* mailbox is open from here, no exiting without closing it! */
4143
4144 rfolder = sync_folder_lookup(replica_folders, mailbox->uniqueid);
4145 uint32_t fromuid = rfolder ? rfolder->last_uid : 0;
4146 uint32_t touid = mailbox->i.last_uid;
4147 modseq_t tomodseq = mailbox->i.highestmodseq;
4148 int ispartial = 0;
4149
4150 if (batchsize && touid - fromuid > batchsize) {
4151 /* see if we actually need to calculate an intermediate state */
4152 modseq_t frommodseq = rfolder ? rfolder->highestmodseq : 0;
4153 /* is there an intermediate modseq available and enough records to make a batch? */
4154 ispartial = calculate_intermediate_state(mailbox, frommodseq, fromuid,
4155 batchsize, &touid, &tomodseq);
4156 if (ispartial) {
4157 syslog(LOG_DEBUG, "doing partial sync: %s (%u/%u/%u) (%llu/%llu/%llu)",
4158 mailbox->name, fromuid, touid, mailbox->i.last_uid,
4159 frommodseq, tomodseq, mailbox->i.highestmodseq);
4160 }
4161 }
4162
4163 sync_folder_list_add(master_folders, mailbox->uniqueid, mailbox->name,
4164 mailbox->mbtype,
4165 mailbox->part, mailbox->acl, mailbox->i.options,
4166 mailbox->i.uidvalidity, touid,
4167 tomodseq, mailbox->i.synccrcs,
4168 mailbox->i.recentuid, mailbox->i.recenttime,
4169 mailbox->i.pop3_last_login,
4170 mailbox->i.pop3_show_after, NULL, xconvmodseq,
4171 raclmodseq, mailbox->foldermodseq, ispartial);
4172
4173
4174 part_list = sync_reserve_partlist(reserve_list, topart ? topart : mailbox->part);
4175 sync_find_reserve_messages(mailbox, fromuid, touid, part_list);
4176 mailbox_close(&mailbox);
4177 }
4178
4179 bail:
4180 mailbox_close(&mailbox);
4181 return r;
4182 }
4183
mark_missing(struct dlist * kin,struct sync_msgid_list * part_list)4184 static int mark_missing (struct dlist *kin,
4185 struct sync_msgid_list *part_list)
4186 {
4187 struct dlist *kl = kin->head;
4188 struct dlist *ki;
4189 struct message_guid tmp_guid;
4190 struct sync_msgid *msgid;
4191
4192 /* no missing at all, good */
4193 if (!kl) return 0;
4194
4195 if (strcmp(kl->name, "MISSING")) {
4196 syslog(LOG_ERR, "SYNCERROR: Illegal response to RESERVE: %s",
4197 kl->name);
4198 return IMAP_PROTOCOL_BAD_PARAMETERS;
4199 }
4200
4201 /* unmark each missing item */
4202 for (ki = kl->head; ki; ki = ki->next) {
4203 if (!message_guid_decode(&tmp_guid, ki->sval)) {
4204 syslog(LOG_ERR, "SYNCERROR: reserve: failed to parse GUID %s",
4205 ki->sval);
4206 return IMAP_PROTOCOL_BAD_PARAMETERS;
4207 }
4208
4209 /* afraid we will need this after all */
4210 msgid = sync_msgid_lookup(part_list, &tmp_guid);
4211 if (msgid && !msgid->need_upload) {
4212 msgid->need_upload = 1;
4213 part_list->toupload++;
4214 }
4215 }
4216
4217 return 0;
4218 }
4219
sync_reserve_partition(char * partition,struct sync_folder_list * replica_folders,struct sync_msgid_list * part_list,struct backend * sync_be)4220 int sync_reserve_partition(char *partition,
4221 struct sync_folder_list *replica_folders,
4222 struct sync_msgid_list *part_list,
4223 struct backend *sync_be)
4224 {
4225 const char *cmd = "RESERVE";
4226 struct sync_msgid *msgid = part_list->head;
4227 struct sync_folder *folder;
4228 struct dlist *kl = NULL;
4229 struct dlist *kin = NULL;
4230 struct dlist *ki;
4231 int r = 0;
4232
4233 if (!replica_folders->head)
4234 return 0; /* nowhere to reserve */
4235
4236 while (msgid) {
4237 int n = 0;
4238
4239 if (!part_list->toupload)
4240 goto done; /* got them all */
4241
4242 kl = dlist_newkvlist(NULL, cmd);
4243 dlist_setatom(kl, "PARTITION", partition);
4244
4245 ki = dlist_newlist(kl, "MBOXNAME");
4246 for (folder = replica_folders->head; folder; folder = folder->next)
4247 dlist_setatom(ki, "MBOXNAME", folder->name);
4248
4249 ki = dlist_newlist(kl, "GUID");
4250 for (; msgid; msgid = msgid->next) {
4251 if (!msgid->need_upload) continue;
4252 if (n > 8192) break;
4253 dlist_setatom(ki, "GUID", message_guid_encode(&msgid->guid));
4254 /* we will re-add the "need upload" if we get a MISSING response */
4255 msgid->need_upload = 0;
4256 part_list->toupload--;
4257 n++;
4258 }
4259
4260 sync_send_apply(kl, sync_be->out);
4261
4262 r = sync_parse_response(cmd, sync_be->in, &kin);
4263 if (r) goto done;
4264
4265 r = mark_missing(kin, part_list);
4266 if (r) goto done;
4267
4268 dlist_free(&kl);
4269 dlist_free(&kin);
4270 }
4271
4272 done:
4273 dlist_free(&kl);
4274 dlist_free(&kin);
4275 return r;
4276 }
4277
reserve_messages(struct sync_name_list * mboxname_list,const char * topart,struct sync_folder_list * master_folders,struct sync_folder_list * replica_folders,struct sync_reserve_list * reserve_list,struct backend * sync_be,uint32_t batchsize)4278 static int reserve_messages(struct sync_name_list *mboxname_list,
4279 const char *topart,
4280 struct sync_folder_list *master_folders,
4281 struct sync_folder_list *replica_folders,
4282 struct sync_reserve_list *reserve_list,
4283 struct backend *sync_be,
4284 uint32_t batchsize)
4285 {
4286 struct sync_reserve *reserve;
4287 int r;
4288
4289 r = find_reserve_all(mboxname_list, topart, master_folders,
4290 replica_folders, reserve_list, batchsize);
4291 if (r) return r;
4292
4293 for (reserve = reserve_list->head; reserve; reserve = reserve->next) {
4294 r = sync_reserve_partition(reserve->part, replica_folders,
4295 reserve->list, sync_be);
4296 if (r) return r;
4297 }
4298
4299 return 0;
4300 }
4301
sync_response_parse(struct protstream * sync_in,const char * cmd,struct sync_folder_list * folder_list,struct sync_name_list * sub_list,struct sync_sieve_list * sieve_list,struct sync_seen_list * seen_list,struct sync_quota_list * quota_list)4302 int sync_response_parse(struct protstream *sync_in, const char *cmd,
4303 struct sync_folder_list *folder_list,
4304 struct sync_name_list *sub_list,
4305 struct sync_sieve_list *sieve_list,
4306 struct sync_seen_list *seen_list,
4307 struct sync_quota_list *quota_list)
4308 {
4309 struct dlist *kin = NULL;
4310 struct dlist *kl;
4311 int r;
4312
4313 r = sync_parse_response(cmd, sync_in, &kin);
4314
4315 /* Unpleasant: translate remote access error into "please reset me" */
4316 if (r == IMAP_MAILBOX_NONEXISTENT)
4317 return 0;
4318
4319 if (r) return r;
4320
4321 for (kl = kin->head; kl; kl = kl->next) {
4322 if (!strcmp(kl->name, "SIEVE")) {
4323 struct message_guid guid;
4324 const char *filename = NULL;
4325 const char *guidstr = NULL;
4326 time_t modtime = 0;
4327 uint32_t active = 0;
4328 if (!sieve_list) goto parse_err;
4329 if (!dlist_getatom(kl, "FILENAME", &filename)) goto parse_err;
4330 if (!dlist_getdate(kl, "LAST_UPDATE", &modtime)) goto parse_err;
4331 dlist_getatom(kl, "GUID", &guidstr); /* optional */
4332 if (guidstr) {
4333 if (!message_guid_decode(&guid, guidstr)) goto parse_err;
4334 }
4335 else {
4336 message_guid_set_null(&guid);
4337 }
4338 dlist_getnum32(kl, "ISACTIVE", &active); /* optional */
4339 sync_sieve_list_add(sieve_list, filename, modtime, &guid, active);
4340 }
4341
4342 else if (!strcmp(kl->name, "QUOTA")) {
4343 const char *root = NULL;
4344 struct sync_quota *sq;
4345 if (!quota_list) goto parse_err;
4346 if (!dlist_getatom(kl, "ROOT", &root)) goto parse_err;
4347 sq = sync_quota_list_add(quota_list, root);
4348 sync_decode_quota_limits(kl, sq->limits);
4349 }
4350
4351 else if (!strcmp(kl->name, "LSUB")) {
4352 struct dlist *i;
4353 if (!sub_list) goto parse_err;
4354 for (i = kl->head; i; i = i->next) {
4355 sync_name_list_add(sub_list, i->sval);
4356 }
4357 }
4358
4359 else if (!strcmp(kl->name, "SEEN")) {
4360 const char *uniqueid = NULL;
4361 time_t lastread = 0;
4362 uint32_t lastuid = 0;
4363 time_t lastchange = 0;
4364 const char *seenuids = NULL;
4365 if (!seen_list) goto parse_err;
4366 if (!dlist_getatom(kl, "UNIQUEID", &uniqueid)) goto parse_err;
4367 if (!dlist_getdate(kl, "LASTREAD", &lastread)) goto parse_err;
4368 if (!dlist_getnum32(kl, "LASTUID", &lastuid)) goto parse_err;
4369 if (!dlist_getdate(kl, "LASTCHANGE", &lastchange)) goto parse_err;
4370 if (!dlist_getatom(kl, "SEENUIDS", &seenuids)) goto parse_err;
4371 sync_seen_list_add(seen_list, uniqueid, lastread,
4372 lastuid, lastchange, seenuids);
4373 }
4374
4375 else if (!strcmp(kl->name, "MAILBOX")) {
4376 const char *uniqueid = NULL;
4377 const char *mboxname = NULL;
4378 const char *mboxtype = NULL;
4379 const char *part = NULL;
4380 const char *acl = NULL;
4381 const char *options = NULL;
4382 modseq_t highestmodseq = 0;
4383 uint32_t uidvalidity = 0;
4384 uint32_t last_uid = 0;
4385 struct synccrcs synccrcs = { 0, 0 };
4386 uint32_t recentuid = 0;
4387 time_t recenttime = 0;
4388 time_t pop3_last_login = 0;
4389 time_t pop3_show_after = 0;
4390 struct dlist *al = NULL;
4391 struct sync_annot_list *annots = NULL;
4392 modseq_t xconvmodseq = 0;
4393 modseq_t raclmodseq = 0;
4394 modseq_t foldermodseq = 0;
4395
4396 if (!folder_list) goto parse_err;
4397 if (!dlist_getatom(kl, "UNIQUEID", &uniqueid)) goto parse_err;
4398 if (!dlist_getatom(kl, "MBOXNAME", &mboxname)) goto parse_err;
4399 if (!dlist_getatom(kl, "PARTITION", &part)) goto parse_err;
4400 if (!dlist_getatom(kl, "ACL", &acl)) goto parse_err;
4401 if (!dlist_getatom(kl, "OPTIONS", &options)) goto parse_err;
4402 if (!dlist_getnum64(kl, "HIGHESTMODSEQ", &highestmodseq)) goto parse_err;
4403 if (!dlist_getnum32(kl, "UIDVALIDITY", &uidvalidity)) goto parse_err;
4404 if (!dlist_getnum32(kl, "LAST_UID", &last_uid)) goto parse_err;
4405 if (!dlist_getnum32(kl, "RECENTUID", &recentuid)) goto parse_err;
4406 if (!dlist_getdate(kl, "RECENTTIME", &recenttime)) goto parse_err;
4407 if (!dlist_getdate(kl, "POP3_LAST_LOGIN", &pop3_last_login)) goto parse_err;
4408 /* optional */
4409 dlist_getdate(kl, "POP3_SHOW_AFTER", &pop3_show_after);
4410 dlist_getatom(kl, "MBOXTYPE", &mboxtype);
4411 dlist_getnum32(kl, "SYNC_CRC", &synccrcs.basic);
4412 dlist_getnum32(kl, "SYNC_CRC_ANNOT", &synccrcs.annot);
4413 dlist_getnum64(kl, "XCONVMODSEQ", &xconvmodseq);
4414 dlist_getnum64(kl, "RACLMODSEQ", &raclmodseq);
4415 dlist_getnum64(kl, "FOLDERMODSEQ", &foldermodseq);
4416
4417 if (dlist_getlist(kl, "ANNOTATIONS", &al))
4418 decode_annotations(al, &annots, NULL, NULL);
4419
4420
4421 sync_folder_list_add(folder_list, uniqueid, mboxname,
4422 mboxlist_string_to_mbtype(mboxtype),
4423 part, acl,
4424 sync_parse_options(options),
4425 uidvalidity, last_uid,
4426 highestmodseq, synccrcs,
4427 recentuid, recenttime,
4428 pop3_last_login,
4429 pop3_show_after, annots,
4430 xconvmodseq, raclmodseq,
4431 foldermodseq, /*ispartial*/0);
4432 }
4433 else
4434 goto parse_err;
4435 }
4436 dlist_free(&kin);
4437
4438 return r;
4439
4440 parse_err:
4441 dlist_free(&kin);
4442 syslog(LOG_ERR, "SYNCERROR: %s: Invalid response %s",
4443 cmd, dlist_lastkey());
4444 return IMAP_PROTOCOL_BAD_PARAMETERS;
4445 }
4446
folder_rename(const char * oldname,const char * newname,const char * partition,unsigned uidvalidity,struct backend * sync_be,unsigned flags)4447 static int folder_rename(const char *oldname, const char *newname,
4448 const char *partition, unsigned uidvalidity,
4449 struct backend *sync_be, unsigned flags)
4450 {
4451 const char *cmd = (flags & SYNC_FLAG_LOCALONLY) ? "LOCAL_RENAME" : "RENAME";
4452 struct dlist *kl;
4453
4454 if (flags & SYNC_FLAG_VERBOSE)
4455 printf("%s %s -> %s (%s)\n", cmd, oldname, newname, partition);
4456
4457 if (flags & SYNC_FLAG_LOGGING)
4458 syslog(LOG_INFO, "%s %s -> %s (%s)", cmd, oldname, newname, partition);
4459
4460 kl = dlist_newkvlist(NULL, cmd);
4461 dlist_setatom(kl, "OLDMBOXNAME", oldname);
4462 dlist_setatom(kl, "NEWMBOXNAME", newname);
4463 dlist_setatom(kl, "PARTITION", partition);
4464 dlist_setnum32(kl, "UIDVALIDITY", uidvalidity);
4465
4466 sync_send_apply(kl, sync_be->out);
4467 dlist_free(&kl);
4468
4469 return sync_parse_response(cmd, sync_be->in, NULL);
4470 }
4471
sync_folder_delete(const char * mboxname,struct backend * sync_be,unsigned flags)4472 int sync_folder_delete(const char *mboxname,
4473 struct backend *sync_be, unsigned flags)
4474 {
4475 const char *cmd =
4476 (flags & SYNC_FLAG_LOCALONLY) ? "LOCAL_UNMAILBOX" :"UNMAILBOX";
4477 struct dlist *kl;
4478 int r;
4479
4480 if (flags & SYNC_FLAG_VERBOSE)
4481 printf("%s %s\n", cmd, mboxname);
4482
4483 if (flags & SYNC_FLAG_LOGGING)
4484 syslog(LOG_INFO, "%s %s", cmd, mboxname);
4485
4486 kl = dlist_setatom(NULL, cmd, mboxname);
4487 sync_send_apply(kl, sync_be->out);
4488 dlist_free(&kl);
4489
4490 r = sync_parse_response(cmd, sync_be->in, NULL);
4491 if (r == IMAP_MAILBOX_NONEXISTENT)
4492 r = 0;
4493
4494 return r;
4495 }
4496
sync_set_sub(const char * userid,const char * mboxname,int add,struct backend * sync_be,unsigned flags)4497 int sync_set_sub(const char *userid, const char *mboxname, int add,
4498 struct backend *sync_be, unsigned flags)
4499 {
4500 const char *cmd = add ? "SUB" : "UNSUB";
4501 struct dlist *kl;
4502
4503 if (flags & SYNC_FLAG_VERBOSE)
4504 printf("%s %s %s\n", cmd, userid, mboxname);
4505
4506 if (flags & SYNC_FLAG_LOGGING)
4507 syslog(LOG_INFO, "%s %s %s", cmd, userid, mboxname);
4508
4509 kl = dlist_newkvlist(NULL, cmd);
4510 dlist_setatom(kl, "USERID", userid);
4511 dlist_setatom(kl, "MBOXNAME", mboxname);
4512 sync_send_apply(kl, sync_be->out);
4513 dlist_free(&kl);
4514
4515 return sync_parse_response(cmd, sync_be->in, NULL);
4516 }
4517
folder_setannotation(const char * mboxname,const char * entry,const char * userid,const struct buf * value,struct backend * sync_be,unsigned flags)4518 static int folder_setannotation(const char *mboxname, const char *entry,
4519 const char *userid, const struct buf *value,
4520 struct backend *sync_be, unsigned flags)
4521 {
4522 const char *cmd = "ANNOTATION";
4523 struct dlist *kl;
4524
4525 if (flags & SYNC_FLAG_VERBOSE)
4526 printf("%s %s %s %s\n", cmd, mboxname, entry, userid);
4527
4528 if (flags & SYNC_FLAG_LOGGING)
4529 syslog(LOG_INFO, "%s %s %s %s", cmd, mboxname, entry, userid);
4530
4531 kl = dlist_newkvlist(NULL, cmd);
4532 dlist_setatom(kl, "MBOXNAME", mboxname);
4533 dlist_setatom(kl, "ENTRY", entry);
4534 dlist_setatom(kl, "USERID", userid);
4535 dlist_setmap(kl, "VALUE", value->s, value->len);
4536 sync_send_apply(kl, sync_be->out);
4537 dlist_free(&kl);
4538
4539 return sync_parse_response(cmd, sync_be->in, NULL);
4540 }
4541
folder_unannotation(const char * mboxname,const char * entry,const char * userid,struct backend * sync_be,unsigned flags)4542 static int folder_unannotation(const char *mboxname, const char *entry,
4543 const char *userid, struct backend *sync_be,
4544 unsigned flags)
4545 {
4546 const char *cmd = "UNANNOTATION";
4547 struct dlist *kl;
4548
4549 if (flags & SYNC_FLAG_VERBOSE)
4550 printf("%s %s %s %s\n", cmd, mboxname, entry, userid);
4551
4552 if (flags & SYNC_FLAG_LOGGING)
4553 syslog(LOG_INFO, "%s %s %s %s", cmd, mboxname, entry, userid);
4554
4555 kl = dlist_newkvlist(NULL, cmd);
4556 dlist_setatom(kl, "MBOXNAME", mboxname);
4557 dlist_setatom(kl, "ENTRY", entry);
4558 dlist_setatom(kl, "USERID", userid);
4559 sync_send_apply(kl, sync_be->out);
4560 dlist_free(&kl);
4561
4562 return sync_parse_response(cmd, sync_be->in, NULL);
4563 }
4564
4565 /* ====================================================================== */
4566
sieve_upload(const char * userid,const char * filename,unsigned long last_update,struct backend * sync_be,unsigned flags)4567 static int sieve_upload(const char *userid, const char *filename,
4568 unsigned long last_update, struct backend *sync_be,
4569 unsigned flags)
4570 {
4571 const char *cmd = "SIEVE";
4572 struct dlist *kl;
4573 char *sieve;
4574 uint32_t size;
4575
4576 sieve = sync_sieve_read(userid, filename, &size);
4577 if (!sieve) return IMAP_IOERROR;
4578
4579 if (flags & SYNC_FLAG_VERBOSE)
4580 printf("%s %s %s\n", cmd, userid, filename);
4581
4582 if (flags & SYNC_FLAG_LOGGING)
4583 syslog(LOG_INFO, "%s %s %s", cmd, userid, filename);
4584
4585 kl = dlist_newkvlist(NULL, cmd);
4586 dlist_setatom(kl, "USERID", userid);
4587 dlist_setatom(kl, "FILENAME", filename);
4588 dlist_setdate(kl, "LAST_UPDATE", last_update);
4589 dlist_setmap(kl, "CONTENT", sieve, size);
4590 sync_send_apply(kl, sync_be->out);
4591 dlist_free(&kl);
4592 free(sieve);
4593
4594 return sync_parse_response(cmd, sync_be->in, NULL);
4595 }
4596
sieve_delete(const char * userid,const char * filename,struct backend * sync_be,unsigned flags)4597 static int sieve_delete(const char *userid, const char *filename,
4598 struct backend *sync_be, unsigned flags)
4599 {
4600 const char *cmd = "UNSIEVE";
4601 struct dlist *kl;
4602
4603 if (flags & SYNC_FLAG_VERBOSE)
4604 printf("%s %s %s\n", cmd, userid, filename);
4605
4606 if (flags & SYNC_FLAG_LOGGING)
4607 syslog(LOG_INFO, "%s %s %s", cmd, userid, filename);
4608
4609 kl = dlist_newkvlist(NULL, cmd);
4610 dlist_setatom(kl, "USERID", userid);
4611 dlist_setatom(kl, "FILENAME", filename);
4612 sync_send_apply(kl, sync_be->out);
4613 dlist_free(&kl);
4614
4615 return sync_parse_response(cmd, sync_be->in, NULL);
4616 }
4617
sieve_activate(const char * userid,const char * filename,struct backend * sync_be,unsigned flags)4618 static int sieve_activate(const char *userid, const char *filename,
4619 struct backend *sync_be, unsigned flags)
4620 {
4621 const char *cmd = "ACTIVATE_SIEVE";
4622 struct dlist *kl;
4623
4624 if (flags & SYNC_FLAG_VERBOSE)
4625 printf("%s %s %s\n", cmd, userid, filename);
4626
4627 if (flags & SYNC_FLAG_LOGGING)
4628 syslog(LOG_INFO, "%s %s %s", cmd, userid, filename);
4629
4630 kl = dlist_newkvlist(NULL, cmd);
4631 dlist_setatom(kl, "USERID", userid);
4632 dlist_setatom(kl, "FILENAME", filename);
4633 sync_send_apply(kl, sync_be->out);
4634 dlist_free(&kl);
4635
4636 return sync_parse_response(cmd, sync_be->in, NULL);
4637 }
4638
sieve_deactivate(const char * userid,struct backend * sync_be,unsigned flags)4639 static int sieve_deactivate(const char *userid,
4640 struct backend *sync_be, unsigned flags)
4641 {
4642 const char *cmd = "UNACTIVATE_SIEVE";
4643 struct dlist *kl;
4644
4645 if (flags & SYNC_FLAG_VERBOSE)
4646 printf("%s %s\n", cmd, userid);
4647
4648 if (flags & SYNC_FLAG_LOGGING)
4649 syslog(LOG_INFO, "%s %s", cmd, userid);
4650
4651 kl = dlist_newkvlist(NULL, cmd);
4652 dlist_setatom(kl, "USERID", userid);
4653 sync_send_apply(kl, sync_be->out);
4654 dlist_free(&kl);
4655
4656 return sync_parse_response(cmd, sync_be->in, NULL);
4657 }
4658
4659 /* ====================================================================== */
4660
delete_quota(const char * root,struct backend * sync_be,unsigned flags)4661 static int delete_quota(const char *root,
4662 struct backend *sync_be, unsigned flags)
4663 {
4664 const char *cmd = "UNQUOTA";
4665 struct dlist *kl;
4666
4667 if (flags & SYNC_FLAG_VERBOSE)
4668 printf("%s %s\n", cmd, root);
4669
4670 if (flags & SYNC_FLAG_LOGGING)
4671 syslog(LOG_INFO, "%s %s", cmd, root);
4672
4673 kl = dlist_setatom(NULL, cmd, root);
4674 sync_send_apply(kl, sync_be->out);
4675 dlist_free(&kl);
4676
4677 return sync_parse_response(cmd, sync_be->in, NULL);
4678 }
4679
update_quota_work(struct quota * client,struct sync_quota * server,struct backend * sync_be,unsigned flags)4680 static int update_quota_work(struct quota *client, struct sync_quota *server,
4681 struct backend *sync_be, unsigned flags)
4682 {
4683 const char *cmd = "QUOTA";
4684 struct dlist *kl;
4685 int r;
4686
4687 r = quota_read(client, NULL, 0);
4688
4689 /* disappeared? Delete it*/
4690 if (r == IMAP_QUOTAROOT_NONEXISTENT)
4691 return delete_quota(client->root, sync_be, flags);
4692
4693 if (r) {
4694 syslog(LOG_INFO, "Warning: failed to read quotaroot %s: %s",
4695 client->root, error_message(r));
4696 return r;
4697 }
4698
4699 if (server) {
4700 int changed = 0;
4701 int res;
4702 for (res = 0 ; res < QUOTA_NUMRESOURCES ; res++) {
4703 if (client->limits[res] != server->limits[res])
4704 changed++;
4705 }
4706 if (!changed)
4707 return 0;
4708 }
4709
4710 if (flags & SYNC_FLAG_VERBOSE)
4711 printf("%s %s\n", cmd, client->root);
4712
4713 if (flags & SYNC_FLAG_LOGGING)
4714 syslog(LOG_INFO, "%s %s", cmd, client->root);
4715
4716 kl = dlist_newkvlist(NULL, cmd);
4717 dlist_setatom(kl, "ROOT", client->root);
4718 sync_encode_quota_limits(kl, client->limits);
4719 dlist_setnum64(kl, "MODSEQ", client->modseq);
4720 sync_send_apply(kl, sync_be->out);
4721 dlist_free(&kl);
4722
4723 return sync_parse_response(cmd, sync_be->in, NULL);
4724 }
4725
copy_local(struct mailbox * mailbox,unsigned uid)4726 static int copy_local(struct mailbox *mailbox, unsigned uid)
4727 {
4728 char *oldfname, *newfname;
4729 struct index_record newrecord;
4730 struct index_record oldrecord;
4731 int r;
4732 annotate_state_t *astate = NULL;
4733
4734 if (mailbox_find_index_record(mailbox, uid, &oldrecord)) {
4735 /* not finding the record is an error! (should never happen) */
4736 syslog(LOG_ERR, "IOERROR: copy_local didn't find the record for %u", uid);
4737 return IMAP_MAILBOX_NONEXISTENT;
4738 }
4739
4740 /* create the new record as a clone of the old record */
4741 newrecord = oldrecord;
4742 newrecord.uid = mailbox->i.last_uid + 1;
4743
4744 /* copy the file in to place */
4745 oldfname = xstrdup(mailbox_record_fname(mailbox, &oldrecord));
4746 newfname = xstrdup(mailbox_record_fname(mailbox, &newrecord));
4747 r = mailbox_copyfile(oldfname, newfname, 0);
4748 free(oldfname);
4749 free(newfname);
4750 if (r) return r;
4751
4752 /* append the new record */
4753 r = mailbox_append_index_record(mailbox, &newrecord);
4754 if (r) return r;
4755
4756 /* ensure we have an astate connected to the destination
4757 * mailbox, so that the annotation txn will be committed
4758 * when we close the mailbox */
4759 r = mailbox_get_annotate_state(mailbox, newrecord.uid, &astate);
4760 if (r) return r;
4761
4762 /* Copy across any per-message annotations */
4763 r = annotate_msg_copy(mailbox, oldrecord.uid,
4764 mailbox, newrecord.uid,
4765 NULL);
4766 if (r) return r;
4767
4768 /* and expunge the old record */
4769 oldrecord.internal_flags |= FLAG_INTERNAL_EXPUNGED;
4770 r = mailbox_rewrite_index_record(mailbox, &oldrecord);
4771
4772 /* done - return */
4773 return r;
4774 }
4775
fetch_file(struct mailbox * mailbox,unsigned uid,const struct index_record * rp,struct sync_msgid_list * part_list,struct backend * sync_be)4776 static int fetch_file(struct mailbox *mailbox, unsigned uid,
4777 const struct index_record *rp, struct sync_msgid_list *part_list,
4778 struct backend *sync_be)
4779 {
4780 const char *cmd = "FETCH";
4781 struct dlist *kin = NULL;
4782 struct dlist *kl;
4783 int r = 0;
4784 struct sync_msgid *msgid;
4785 struct message_guid *guid = NULL;
4786 size_t size = 0;
4787 const char *fname = NULL;
4788
4789 msgid = sync_msgid_lookup(part_list, &rp->guid);
4790
4791 /* already reserved? great */
4792 if (msgid && msgid->fname) {
4793 syslog(LOG_NOTICE, "trying to get already uploaded %u: %s", uid, message_guid_encode(&rp->guid));
4794 return 0;
4795 }
4796
4797 kl = dlist_newkvlist(NULL, cmd);
4798 dlist_setatom(kl, "MBOXNAME", mailbox->name);
4799 dlist_setatom(kl, "PARTITION", mailbox->part);
4800 dlist_setatom(kl, "UNIQUEID", mailbox->uniqueid);
4801 dlist_setguid(kl, "GUID", &rp->guid);
4802 dlist_setnum32(kl, "UID", uid);
4803 sync_send_lookup(kl, sync_be->out);
4804 dlist_free(&kl);
4805
4806 r = sync_parse_response(cmd, sync_be->in, &kin);
4807 if (r) {
4808 syslog(LOG_ERR, "IOERROR: fetch_file failed %s", error_message(r));
4809 return r;
4810 }
4811
4812 if (!dlist_tofile(kin->head, NULL, &guid, (unsigned long *) &size, &fname)) {
4813 r = IMAP_MAILBOX_NONEXISTENT;
4814 syslog(LOG_ERR, "IOERROR: fetch_file failed tofile %s", error_message(r));
4815 goto done;
4816 }
4817
4818 /* well, we can copy it back or we can re-reserve... */
4819 if (message_guid_equal(guid, &rp->guid) && (size == rp->size)) {
4820 msgid = sync_msgid_insert(part_list, &rp->guid);
4821 msgid->need_upload = 1;
4822 msgid->size = size;
4823 if (!msgid->fname) msgid->fname = xstrdup(fname);
4824 }
4825 else {
4826 r = IMAP_MAILBOX_NONEXISTENT;
4827 syslog(LOG_ERR, "IOERROR: fetch_file GUID MISMATCH %s", error_message(r));
4828 r = IMAP_IOERROR;
4829 }
4830
4831 done:
4832 dlist_free(&kin);
4833 return r;
4834 }
4835
copy_remote(struct mailbox * mailbox,uint32_t uid,struct dlist * kr,struct sync_msgid_list * part_list)4836 static int copy_remote(struct mailbox *mailbox, uint32_t uid,
4837 struct dlist *kr, struct sync_msgid_list *part_list)
4838 {
4839 struct index_record record;
4840 struct dlist *ki;
4841 int r;
4842 struct sync_annot_list *annots = NULL;
4843
4844 for (ki = kr->head; ki; ki = ki->next) {
4845 r = parse_upload(ki, mailbox, &record, &annots);
4846 if (r) {
4847 syslog(LOG_ERR, "IOERROR: failed to parse upload for %u", uid);
4848 return r;
4849 }
4850 if (record.uid == uid) {
4851 /* choose the destination UID */
4852 record.uid = mailbox->i.last_uid + 1;
4853
4854 /* append the file */
4855 r = sync_append_copyfile(mailbox, &record, annots, part_list);
4856
4857 sync_annot_list_free(&annots);
4858
4859 return r;
4860 }
4861 sync_annot_list_free(&annots);
4862 }
4863 /* not finding the record is an error! (should never happen) */
4864 syslog(LOG_ERR, "IOERROR: copy_remote didn't find the record for %u", uid);
4865 return IMAP_MAILBOX_NONEXISTENT;
4866 }
4867
copyback_one_record(struct mailbox * mailbox,struct index_record * rp,const struct sync_annot_list * annots,struct dlist * kaction,struct sync_msgid_list * part_list,struct backend * sync_be)4868 static int copyback_one_record(struct mailbox *mailbox,
4869 struct index_record *rp,
4870 const struct sync_annot_list *annots,
4871 struct dlist *kaction,
4872 struct sync_msgid_list *part_list,
4873 struct backend *sync_be)
4874 {
4875 int r;
4876
4877 /* don't want to copy back expunged records! */
4878 if (rp->internal_flags & FLAG_INTERNAL_EXPUNGED)
4879 return 0;
4880
4881 /* if the UID is lower than master's last_uid,
4882 * we'll need to renumber */
4883 if (rp->uid <= mailbox->i.last_uid) {
4884 /* Ok, now we need to check if it's just really stale
4885 * (has been cleaned out locally) or an error.
4886 * In the error case we copy back, stale
4887 * we remove from the replica */
4888 if (rp->modseq < mailbox->i.deletedmodseq) {
4889 if (kaction)
4890 dlist_setnum32(kaction, "EXPUNGE", rp->uid);
4891 }
4892 else {
4893 r = fetch_file(mailbox, rp->uid, rp, part_list, sync_be);
4894 if (r) return r;
4895 if (kaction)
4896 dlist_setnum32(kaction, "COPYBACK", rp->uid);
4897 }
4898 }
4899
4900 /* otherwise we can pull it in with the same UID,
4901 * which saves causing renumbering on the replica
4902 * end, so is preferable */
4903 else {
4904 /* grab the file */
4905 r = fetch_file(mailbox, rp->uid, rp, part_list, sync_be);
4906 if (r) return r;
4907 /* make sure we're actually making changes now */
4908 if (!kaction) return 0;
4909 /* append the file */
4910 r = sync_append_copyfile(mailbox, rp, annots, part_list);
4911 if (r) return r;
4912 }
4913
4914 return 0;
4915 }
4916
renumber_one_record(const struct index_record * mp,struct dlist * kaction)4917 static int renumber_one_record(const struct index_record *mp,
4918 struct dlist *kaction)
4919 {
4920 /* don't want to renumber expunged records */
4921 if (mp->internal_flags & FLAG_INTERNAL_EXPUNGED)
4922 return 0;
4923
4924 if (kaction)
4925 dlist_setnum32(kaction, "RENUMBER", mp->uid);
4926
4927 return 0;
4928 }
4929
make_flags(struct mailbox * mailbox,struct index_record * record)4930 static const char *make_flags(struct mailbox *mailbox, struct index_record *record)
4931 {
4932 static char buf[4096];
4933 const char *sep = "";
4934 int flag;
4935
4936 if (record->system_flags & FLAG_DELETED) {
4937 snprintf(buf, 4096, "%s\\Deleted", sep);
4938 sep = " ";
4939 }
4940 if (record->system_flags & FLAG_ANSWERED) {
4941 snprintf(buf, 4096, "%s\\Answered", sep);
4942 sep = " ";
4943 }
4944 if (record->system_flags & FLAG_FLAGGED) {
4945 snprintf(buf, 4096, "%s\\Flagged", sep);
4946 sep = " ";
4947 }
4948 if (record->system_flags & FLAG_DRAFT) {
4949 snprintf(buf, 4096, "%s\\Draft", sep);
4950 sep = " ";
4951 }
4952 if (record->internal_flags & FLAG_INTERNAL_EXPUNGED) {
4953 snprintf(buf, 4096, "%s\\Expunged", sep);
4954 sep = " ";
4955 }
4956 if (record->system_flags & FLAG_SEEN) {
4957 snprintf(buf, 4096, "%s\\Seen", sep);
4958 sep = " ";
4959 }
4960
4961 /* print user flags in mailbox order */
4962 for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
4963 if (!mailbox->flagname[flag])
4964 continue;
4965 if (!(record->user_flags[flag/32] & (1<<(flag&31))))
4966 continue;
4967 snprintf(buf, 4096, "%s%s", sep, mailbox->flagname[flag]);
4968 sep = " ";
4969 }
4970
4971 return buf;
4972 }
4973
log_record(const char * name,struct mailbox * mailbox,struct index_record * record)4974 static void log_record(const char *name, struct mailbox *mailbox,
4975 struct index_record *record)
4976 {
4977 syslog(LOG_NOTICE, "SYNCNOTICE: %s uid:%u modseq:" MODSEQ_FMT " "
4978 "last_updated:" TIME_T_FMT " internaldate:" TIME_T_FMT " flags:(%s) cid:" CONV_FMT,
4979 name, record->uid, record->modseq,
4980 record->last_updated, record->internaldate,
4981 make_flags(mailbox, record), record->cid);
4982 }
4983
log_mismatch(const char * reason,struct mailbox * mailbox,struct index_record * mp,struct index_record * rp)4984 static void log_mismatch(const char *reason, struct mailbox *mailbox,
4985 struct index_record *mp,
4986 struct index_record *rp)
4987 {
4988 syslog(LOG_NOTICE, "SYNCNOTICE: record mismatch with replica: %s %s",
4989 mailbox->name, reason);
4990 log_record("master", mailbox, mp);
4991 log_record("replica", mailbox, rp);
4992 }
4993
compare_one_record(struct mailbox * mailbox,struct index_record * mp,struct index_record * rp,const struct sync_annot_list * mannots,const struct sync_annot_list * rannots,struct dlist * kaction,struct sync_msgid_list * part_list,struct backend * sync_be)4994 static int compare_one_record(struct mailbox *mailbox,
4995 struct index_record *mp,
4996 struct index_record *rp,
4997 const struct sync_annot_list *mannots,
4998 const struct sync_annot_list *rannots,
4999 struct dlist *kaction,
5000 struct sync_msgid_list *part_list,
5001 struct backend *sync_be)
5002 {
5003 int i;
5004 int r;
5005
5006 /* if both ends are expunged, then we do no more processing. This
5007 * allows a split brain cleanup to not break things forever. It
5008 * does mean that an expunged message might not replicate in that
5009 * case, but the only way to fix this is add ANOTHER special flag
5010 * for BROKEN and only ignore GUID mismatches in that case, after
5011 * moving the message up. I guess we could force UNLINK immediately
5012 * too... hmm. Not today. */
5013
5014 if ((mp->internal_flags & FLAG_INTERNAL_EXPUNGED) &&
5015 (rp->internal_flags & FLAG_INTERNAL_EXPUNGED))
5016 return 0;
5017
5018 /* first of all, check that GUID matches. If not, we have had a split
5019 * brain, so the messages both need to be fixed up to their new UIDs.
5020 * After this function succeeds, both the local and remote copies of this
5021 * current UID will be actually EXPUNGED, so the earlier return applies. */
5022 if (!message_guid_equal(&mp->guid, &rp->guid)) {
5023 char *mguid = xstrdup(message_guid_encode(&mp->guid));
5024 char *rguid = xstrdup(message_guid_encode(&rp->guid));
5025 syslog(LOG_ERR, "SYNCERROR: guid mismatch %s %u (%s %s)",
5026 mailbox->name, mp->uid, rguid, mguid);
5027 free(rguid);
5028 free(mguid);
5029 /* we will need to renumber both ends to get in sync */
5030
5031 /* ORDERING - always lower GUID first */
5032 if (message_guid_cmp(&mp->guid, &rp->guid) > 0) {
5033 r = copyback_one_record(mailbox, rp, rannots, kaction, part_list, sync_be);
5034 if (!r) r = renumber_one_record(mp, kaction);
5035 }
5036 else {
5037 r = renumber_one_record(mp, kaction);
5038 if (!r) r = copyback_one_record(mailbox, rp, rannots, kaction, part_list, sync_be);
5039 }
5040
5041 return r;
5042 }
5043
5044 /* are there any differences? */
5045 if (mp->modseq != rp->modseq)
5046 goto diff;
5047 if (mp->last_updated != rp->last_updated)
5048 goto diff;
5049 if (mp->internaldate != rp->internaldate)
5050 goto diff;
5051 if (mp->system_flags != rp->system_flags)
5052 goto diff;
5053 if ((mp->internal_flags & FLAG_INTERNAL_EXPUNGED) !=
5054 (rp->internal_flags & FLAG_INTERNAL_EXPUNGED))
5055 goto diff;
5056 if (mp->cid != rp->cid)
5057 goto diff;
5058 if (mp->basecid != rp->basecid)
5059 goto diff;
5060 if (mp->savedate != rp->savedate)
5061 goto diff;
5062 if (mp->createdmodseq != rp->createdmodseq)
5063 goto diff;
5064 if (diff_annotations(mannots, rannots))
5065 goto diff;
5066 for (i = 0; i < MAX_USER_FLAGS/32; i++) {
5067 if (mp->user_flags[i] != rp->user_flags[i])
5068 goto diff;
5069 }
5070
5071 /* no changes found, whoopee */
5072 return 0;
5073
5074 diff:
5075 /* if differences we'll have to rewrite to bump the modseq
5076 * so that regular replication will cause an update */
5077
5078 /* interesting case - expunged locally */
5079 if (mp->internal_flags & FLAG_INTERNAL_EXPUNGED) {
5080 /* if expunged, fall through - the rewrite will lift
5081 * the modseq to force the change to stick */
5082 }
5083 else if (rp->internal_flags & FLAG_INTERNAL_EXPUNGED) {
5084 /* mark expunged - rewrite will cause both sides to agree
5085 * again */
5086 mp->internal_flags |= FLAG_INTERNAL_EXPUNGED;
5087 }
5088
5089 /* otherwise, is the replica "newer"? Better grab those flags */
5090 else {
5091 if (rp->modseq > mp->modseq &&
5092 rp->last_updated >= mp->last_updated) {
5093 log_mismatch("more recent on replica", mailbox, mp, rp);
5094 /* then copy all the flag data over from the replica */
5095 mp->system_flags = rp->system_flags;
5096 mp->internal_flags &= ~FLAG_INTERNAL_EXPUNGED;
5097 mp->internal_flags |= rp->internal_flags & FLAG_INTERNAL_EXPUNGED;
5098
5099 mp->cid = rp->cid;
5100 for (i = 0; i < MAX_USER_FLAGS/32; i++)
5101 mp->user_flags[i] = rp->user_flags[i];
5102 }
5103 }
5104
5105 /* are we making changes yet? */
5106 if (!kaction) return 0;
5107
5108 /* even expunged messages get annotations synced */
5109 r = apply_annotations(mailbox, mp, mannots, rannots, 0);
5110 if (r) return r;
5111
5112 /* this will bump the modseq and force a resync either way :) */
5113 return mailbox_rewrite_index_record(mailbox, mp);
5114 }
5115
mailbox_update_loop(struct mailbox * mailbox,struct dlist * ki,uint32_t last_uid,modseq_t highestmodseq,struct dlist * kaction,struct sync_msgid_list * part_list,struct backend * sync_be)5116 static int mailbox_update_loop(struct mailbox *mailbox,
5117 struct dlist *ki,
5118 uint32_t last_uid,
5119 modseq_t highestmodseq,
5120 struct dlist *kaction,
5121 struct sync_msgid_list *part_list,
5122 struct backend *sync_be)
5123 {
5124 struct index_record rrecord;
5125 struct sync_annot_list *mannots = NULL;
5126 struct sync_annot_list *rannots = NULL;
5127 int r;
5128
5129 struct mailbox_iter *iter = mailbox_iter_init(mailbox, 0, 0);
5130 const message_t *msg = mailbox_iter_step(iter);
5131 const struct index_record *mrecord = msg ? msg_record(msg) : NULL;
5132
5133 /* while there are more records on either master OR replica,
5134 * work out what to do with them */
5135 while (ki || msg) {
5136
5137 sync_annot_list_free(&mannots);
5138 sync_annot_list_free(&rannots);
5139
5140 /* most common case - both a master AND a replica record exist */
5141 if (ki && mrecord) {
5142 r = read_annotations(mailbox, mrecord, &mannots, 0, 0);
5143 if (r) goto out;
5144 r = parse_upload(ki, mailbox, &rrecord, &rannots);
5145 if (r) goto out;
5146
5147 /* same UID - compare the records */
5148 if (rrecord.uid == mrecord->uid) {
5149 mailbox_read_basecid(mailbox, mrecord);
5150 r = compare_one_record(mailbox,
5151 (struct index_record *)mrecord, &rrecord,
5152 mannots, rannots,
5153 kaction, part_list,
5154 sync_be);
5155 if (r) goto out;
5156 /* increment both */
5157 msg = mailbox_iter_step(iter);
5158 mrecord = msg ? msg_record(msg) : NULL;
5159 ki = ki->next;
5160 }
5161 else if (rrecord.uid > mrecord->uid) {
5162 /* record only exists on the master */
5163 if (!(mrecord->internal_flags & FLAG_INTERNAL_EXPUNGED)) {
5164 syslog(LOG_ERR, "SYNCNOTICE: only exists on master %s %u (%s)",
5165 mailbox->name, mrecord->uid,
5166 message_guid_encode(&mrecord->guid));
5167 r = renumber_one_record(mrecord, kaction);
5168 if (r) goto out;
5169 }
5170 /* only increment master */
5171 msg = mailbox_iter_step(iter);
5172 mrecord = msg ? msg_record(msg) : NULL;
5173 }
5174 else {
5175 /* record only exists on the replica */
5176 if (!(rrecord.internal_flags & FLAG_INTERNAL_EXPUNGED)) {
5177 if (kaction)
5178 syslog(LOG_ERR, "SYNCNOTICE: only exists on replica %s %u (%s)",
5179 mailbox->name, rrecord.uid,
5180 message_guid_encode(&rrecord.guid));
5181 r = copyback_one_record(mailbox, &rrecord, rannots, kaction, part_list, sync_be);
5182 if (r) goto out;
5183 }
5184 /* only increment replica */
5185 ki = ki->next;
5186 }
5187 }
5188
5189 /* no more replica records, but still master records */
5190 else if (mrecord) {
5191 /* if the replica has seen this UID, we need to renumber.
5192 * Otherwise it will replicate fine as-is */
5193 if (mrecord->uid <= last_uid) {
5194 r = renumber_one_record(mrecord, kaction);
5195 if (r) goto out;
5196 }
5197 else if (mrecord->modseq <= highestmodseq) {
5198 if (kaction) {
5199 /* bump our modseq so we sync */
5200 syslog(LOG_NOTICE, "SYNCNOTICE: bumping modseq %s %u",
5201 mailbox->name, mrecord->uid);
5202 r = mailbox_rewrite_index_record(mailbox, (struct index_record *)mrecord);
5203 if (r) goto out;
5204 }
5205 }
5206 msg = mailbox_iter_step(iter);
5207 mrecord = msg ? msg_record(msg) : NULL;
5208 }
5209
5210 /* record only exists on the replica */
5211 else {
5212 r = parse_upload(ki, mailbox, &rrecord, &rannots);
5213 if (r) goto out;
5214
5215 if (kaction)
5216 syslog(LOG_NOTICE, "SYNCNOTICE: only on replica %s %u",
5217 mailbox->name, rrecord.uid);
5218
5219 /* going to need this one */
5220 r = copyback_one_record(mailbox, &rrecord, rannots, kaction, part_list, sync_be);
5221 if (r) goto out;
5222
5223 ki = ki->next;
5224 }
5225 }
5226 r = 0;
5227
5228 out:
5229 mailbox_iter_done(&iter);
5230 sync_annot_list_free(&mannots);
5231 sync_annot_list_free(&rannots);
5232 return r;
5233 }
5234
mailbox_full_update(struct sync_folder * local,struct sync_reserve_list * reserve_list,struct backend * sync_be,unsigned flags)5235 static int mailbox_full_update(struct sync_folder *local,
5236 struct sync_reserve_list *reserve_list,
5237 struct backend *sync_be,
5238 unsigned flags)
5239 {
5240 const char *cmd = "FULLMAILBOX";
5241 struct mailbox *mailbox = NULL;
5242 int r;
5243 struct dlist *kin = NULL;
5244 struct dlist *kr = NULL;
5245 struct dlist *ka = NULL;
5246 struct dlist *kuids = NULL;
5247 struct dlist *kl = NULL;
5248 struct dlist *kaction = NULL;
5249 struct dlist *kexpunge = NULL;
5250 modseq_t highestmodseq;
5251 modseq_t foldermodseq = 0;
5252 uint32_t uidvalidity;
5253 uint32_t last_uid;
5254 struct sync_annot_list *mannots = NULL;
5255 struct sync_annot_list *rannots = NULL;
5256 int remote_modseq_was_higher = 0;
5257 modseq_t xconvmodseq = 0;
5258 struct sync_msgid_list *part_list;
5259 annotate_state_t *astate = NULL;
5260
5261 if (flags & SYNC_FLAG_VERBOSE)
5262 printf("%s %s\n", cmd, local->name);
5263
5264 if (flags & SYNC_FLAG_LOGGING)
5265 syslog(LOG_INFO, "%s %s", cmd, local->name);
5266
5267 kl = dlist_setatom(NULL, cmd, local->name);
5268 sync_send_lookup(kl, sync_be->out);
5269 dlist_free(&kl);
5270
5271 r = sync_parse_response(cmd, sync_be->in, &kin);
5272 if (r) return r;
5273
5274 kl = kin->head;
5275
5276 if (!kl) {
5277 r = IMAP_MAILBOX_NONEXISTENT;
5278 goto done;
5279 }
5280
5281 /* XXX - handle the header. I want to do some ordering on timestamps
5282 * in particular here - if there's more recent data on the replica then
5283 * it should be copied back. This depends on having a nice way to
5284 * parse the mailbox structure back in to a struct index_header rather
5285 * than the by hand stuff though, because that sucks. NOTE - this
5286 * doesn't really matter too much, because we'll blat the replica's
5287 * values anyway! */
5288
5289 if (!dlist_getnum64(kl, "HIGHESTMODSEQ", &highestmodseq)) {
5290 r = IMAP_PROTOCOL_BAD_PARAMETERS;
5291 goto done;
5292 }
5293
5294 if (!dlist_getnum32(kl, "UIDVALIDITY", &uidvalidity)) {
5295 r = IMAP_PROTOCOL_BAD_PARAMETERS;
5296 goto done;
5297 }
5298
5299 if (!dlist_getnum32(kl, "LAST_UID", &last_uid)) {
5300 r = IMAP_PROTOCOL_BAD_PARAMETERS;
5301 goto done;
5302 }
5303
5304 if (!dlist_getlist(kl, "RECORD", &kr)) {
5305 r = IMAP_PROTOCOL_BAD_PARAMETERS;
5306 goto done;
5307 }
5308
5309 /* optional */
5310 dlist_getnum64(kl, "XCONVMODSEQ", &xconvmodseq);
5311 dlist_getnum64(kl, "FOLDERMODSEQ", &foldermodseq);
5312
5313 /* we'll be updating it! */
5314 if (local->mailbox) {
5315 mailbox = local->mailbox;
5316 }
5317 else {
5318 r = mailbox_open_iwl(local->name, &mailbox);
5319 if (!r) r = sync_mailbox_version_check(&mailbox);
5320 }
5321 if (r) goto done;
5322
5323 part_list = sync_reserve_partlist(reserve_list, mailbox->part);
5324
5325 /* if local UIDVALIDITY is lower, copy from remote, otherwise
5326 * remote will copy ours when we sync */
5327 if (mailbox->i.uidvalidity < uidvalidity) {
5328 syslog(LOG_NOTICE, "SYNCNOTICE: uidvalidity higher on replica %s"
5329 ", updating %u => %u",
5330 mailbox->name, mailbox->i.uidvalidity, uidvalidity);
5331 mailbox_index_dirty(mailbox);
5332 mailbox->i.uidvalidity = mboxname_setuidvalidity(mailbox->name, uidvalidity);
5333 }
5334
5335 if (mailbox->i.highestmodseq < highestmodseq) {
5336 /* highestmodseq on replica is dirty - we must copy and then dirty
5337 * so we go one higher! */
5338 syslog(LOG_NOTICE, "SYNCNOTICE: highestmodseq higher on replica %s"
5339 ", updating " MODSEQ_FMT " => " MODSEQ_FMT,
5340 mailbox->name, mailbox->i.highestmodseq, highestmodseq+1);
5341 mailbox->modseq_dirty = 0;
5342 mailbox->i.highestmodseq = highestmodseq;
5343 mailbox_modseq_dirty(mailbox);
5344 remote_modseq_was_higher = 1;
5345 }
5346
5347 /* hold the annotate state open */
5348 r = mailbox_get_annotate_state(mailbox, ANNOTATE_ANY_UID, &astate);
5349 if (r) goto done;
5350
5351 annotate_state_begin(astate);
5352
5353 r = mailbox_update_loop(mailbox, kr->head, last_uid,
5354 highestmodseq, NULL, part_list, sync_be);
5355 if (r) {
5356 syslog(LOG_ERR, "SYNCNOTICE: failed to prepare update for %s: %s",
5357 mailbox->name, error_message(r));
5358 goto done;
5359 }
5360
5361 /* OK - now we're committed to make changes! */
5362
5363 /* this is safe because "larger than" logic is embedded
5364 * inside update_xconvmodseq */
5365 if (mailbox_has_conversations(mailbox)) {
5366 r = mailbox_update_xconvmodseq(mailbox, xconvmodseq, /* force */0);
5367 if (r) goto done;
5368 }
5369
5370 if (foldermodseq) {
5371 // by writing the same ACL with the updated foldermodseq, this will bounce it
5372 // if needed
5373 r = mboxlist_sync_setacls(mailbox->name, mailbox->acl, foldermodseq);
5374 if (r) goto done;
5375 }
5376
5377 kaction = dlist_newlist(NULL, "ACTION");
5378 r = mailbox_update_loop(mailbox, kr->head, last_uid,
5379 highestmodseq, kaction, part_list, sync_be);
5380 if (r) goto cleanup;
5381
5382 /* if replica still has a higher last_uid, bump our local
5383 * number to match so future records don't clash */
5384 if (mailbox->i.last_uid < last_uid) {
5385 mailbox_index_dirty(mailbox);
5386 mailbox->i.last_uid = last_uid;
5387 }
5388
5389 /* ugly variable reuse */
5390 dlist_getlist(kl, "ANNOTATIONS", &ka);
5391
5392 if (ka) decode_annotations(ka, &rannots, mailbox, NULL);
5393 r = read_annotations(mailbox, NULL, &mannots, 0, 0);
5394 if (r) goto cleanup;
5395 r = apply_annotations(mailbox, NULL, mannots, rannots,
5396 !remote_modseq_was_higher);
5397 if (r) goto cleanup;
5398
5399 /* blatant reuse 'r' us */
5400 kexpunge = dlist_newkvlist(NULL, "EXPUNGE");
5401 dlist_setatom(kexpunge, "MBOXNAME", mailbox->name);
5402 dlist_setatom(kexpunge, "UNIQUEID", mailbox->uniqueid); /* just for safety */
5403 kuids = dlist_newlist(kexpunge, "UID");
5404 for (ka = kaction->head; ka; ka = ka->next) {
5405 if (!strcmp(ka->name, "EXPUNGE")) {
5406 dlist_setnum32(kuids, "UID", dlist_num(ka));
5407 }
5408 else if (!strcmp(ka->name, "COPYBACK")) {
5409 r = copy_remote(mailbox, dlist_num(ka), kr, part_list);
5410 if (r) goto cleanup;
5411 dlist_setnum32(kuids, "UID", dlist_num(ka));
5412 }
5413 else if (!strcmp(ka->name, "RENUMBER")) {
5414 r = copy_local(mailbox, dlist_num(ka));
5415 if (r) goto cleanup;
5416 }
5417 }
5418
5419 /* we still need to do the EXPUNGEs */
5420 cleanup:
5421
5422 sync_annot_list_free(&mannots);
5423 sync_annot_list_free(&rannots);
5424
5425 /* close the mailbox before sending any expunges
5426 * to avoid deadlocks */
5427 if (!local->mailbox) mailbox_close(&mailbox);
5428
5429 /* only send expunge if we have some UIDs to expunge */
5430 if (kuids && kuids->head) {
5431 int r2;
5432 sync_send_apply(kexpunge, sync_be->out);
5433 r2 = sync_parse_response("EXPUNGE", sync_be->in, NULL);
5434 if (r2) {
5435 syslog(LOG_ERR, "SYNCERROR: failed to expunge in cleanup %s",
5436 local->name);
5437 }
5438 }
5439
5440 done:
5441 if (r && mailbox)
5442 annotate_state_abort(&mailbox->annot_state);
5443
5444 if (mailbox && !local->mailbox) mailbox_close(&mailbox);
5445
5446 dlist_free(&kin);
5447 dlist_free(&kaction);
5448 dlist_free(&kexpunge);
5449 /* kuids points into the tree rooted at kexpunge
5450 * so we don't need to free it explicitly here */
5451
5452 return r;
5453 }
5454
is_unchanged(struct mailbox * mailbox,struct sync_folder * remote)5455 static int is_unchanged(struct mailbox *mailbox, struct sync_folder *remote)
5456 {
5457 /* look for any mismatches */
5458 unsigned options = mailbox->i.options & MAILBOX_OPTIONS_MASK;
5459 modseq_t xconvmodseq = 0;
5460
5461 if (!remote) return 0;
5462 if (remote->mbtype != mailbox->mbtype) return 0;
5463 if (remote->last_uid != mailbox->i.last_uid) return 0;
5464 if (remote->highestmodseq != mailbox->i.highestmodseq) return 0;
5465 if (remote->uidvalidity != mailbox->i.uidvalidity) return 0;
5466 if (remote->recentuid != mailbox->i.recentuid) return 0;
5467 if (remote->recenttime != mailbox->i.recenttime) return 0;
5468 if (remote->pop3_last_login != mailbox->i.pop3_last_login) return 0;
5469 if (remote->pop3_show_after != mailbox->i.pop3_show_after) return 0;
5470 if (remote->options != options) return 0;
5471 if (remote->foldermodseq && remote->foldermodseq != mailbox->foldermodseq) return 0;
5472 if (strcmp(remote->acl, mailbox->acl)) return 0;
5473
5474 if (config_getswitch(IMAPOPT_REVERSEACLS)) {
5475 modseq_t raclmodseq = mboxname_readraclmodseq(mailbox->name);
5476 // don't bail if either are zero, that could be version skew
5477 if (raclmodseq && remote->raclmodseq && remote->raclmodseq != raclmodseq) return 0;
5478 }
5479
5480 if (mailbox_has_conversations(mailbox)) {
5481 int r = mailbox_get_xconvmodseq(mailbox, &xconvmodseq);
5482 if (r) return 0;
5483
5484 if (remote->xconvmodseq != xconvmodseq) return 0;
5485 }
5486
5487 /* compare annotations */
5488 {
5489 struct sync_annot_list *mannots = NULL;
5490 int r = read_annotations(mailbox, NULL, &mannots, 0, 0);
5491 if (r) return 0;
5492
5493 if (diff_annotations(mannots, remote->annots)) {
5494 sync_annot_list_free(&mannots);
5495 return 0;
5496 }
5497 sync_annot_list_free(&mannots);
5498 }
5499
5500 /* if we got here then we should force check the CRCs */
5501 if (!mailbox_crceq(remote->synccrcs, mailbox_synccrcs(mailbox, /*force*/0)))
5502 if (!mailbox_crceq(remote->synccrcs, mailbox_synccrcs(mailbox, /*force*/1)))
5503 return 0;
5504
5505 /* otherwise it's unchanged! */
5506 return 1;
5507 }
5508
5509 /* XXX kind of nasty having this here, but i think it probably
5510 * shouldn't be in .h with the rest of them */
5511 #define SYNC_FLAG_ISREPEAT (1<<15)
5512 #define SYNC_FLAG_FULLANNOTS (1<<16)
5513
update_mailbox_once(struct sync_folder * local,struct sync_folder * remote,const char * topart,struct sync_reserve_list * reserve_list,struct backend * sync_be,unsigned flags)5514 static int update_mailbox_once(struct sync_folder *local,
5515 struct sync_folder *remote,
5516 const char *topart,
5517 struct sync_reserve_list *reserve_list,
5518 struct backend *sync_be,
5519 unsigned flags)
5520 {
5521 struct sync_msgid_list *part_list;
5522 struct mailbox *mailbox = NULL;
5523 int r = 0;
5524 const char *cmd =
5525 (flags & SYNC_FLAG_LOCALONLY) ? "LOCAL_MAILBOX" : "MAILBOX";
5526 struct dlist *kl = dlist_newkvlist(NULL, cmd);
5527 struct dlist *kupload = dlist_newlist(NULL, "MESSAGE");
5528 annotate_state_t *astate = NULL;
5529
5530 if (local->mailbox) {
5531 mailbox = local->mailbox;
5532 }
5533 else {
5534 r = mailbox_open_iwl(local->name, &mailbox);
5535 if (!r) r = sync_mailbox_version_check(&mailbox);
5536 }
5537
5538 if (r == IMAP_MAILBOX_NONEXISTENT) {
5539 /* been deleted in the meanwhile... it will get picked up by the
5540 * delete call later */
5541 r = 0;
5542 goto done;
5543 }
5544 else if (r)
5545 goto done;
5546
5547 /* hold the annotate state open */
5548 r = mailbox_get_annotate_state(mailbox, ANNOTATE_ANY_UID, &astate);
5549 if (r) goto done;
5550
5551 /* and force it to hold a transaction while it does stuff */
5552 annotate_state_begin(astate);
5553
5554 /* definitely bad if these don't match! */
5555 if (strcmp(mailbox->uniqueid, local->uniqueid) ||
5556 strcmp(mailbox->part, local->part)) {
5557 r = IMAP_MAILBOX_MOVED;
5558 goto done;
5559 }
5560
5561 /* check that replication stands a chance of succeeding */
5562 if (remote && !(flags & SYNC_FLAG_ISREPEAT)) {
5563 if (mailbox->i.deletedmodseq > remote->highestmodseq) {
5564 syslog(LOG_NOTICE, "inefficient replication ("
5565 MODSEQ_FMT " > " MODSEQ_FMT ") %s",
5566 mailbox->i.deletedmodseq, remote->highestmodseq,
5567 local->name);
5568 r = IMAP_AGAIN;
5569 goto done;
5570 }
5571 }
5572
5573 /* if local UIDVALIDITY is lower, copy from remote, otherwise
5574 * remote will copy ours when we sync */
5575 if (remote && mailbox->i.uidvalidity < remote->uidvalidity) {
5576 syslog(LOG_NOTICE, "SYNCNOTICE: uidvalidity higher on replica %s"
5577 ", updating %u => %u",
5578 mailbox->name, mailbox->i.uidvalidity, remote->uidvalidity);
5579 mailbox_index_dirty(mailbox);
5580 mailbox->i.uidvalidity = mboxname_setuidvalidity(mailbox->name, remote->uidvalidity);
5581 }
5582
5583 /* make sure CRC is updated if we're retrying */
5584 if (flags & SYNC_FLAG_ISREPEAT) {
5585 r = mailbox_index_recalc(mailbox);
5586 if (r) goto done;
5587 }
5588
5589 /* bump the raclmodseq if it's higher on the replica */
5590 if (remote && remote->raclmodseq) {
5591 mboxname_setraclmodseq(mailbox->name, remote->raclmodseq);
5592 }
5593
5594 /* bump the foldermodseq if it's higher on the replica */
5595 if (remote && remote->foldermodseq > mailbox->foldermodseq) {
5596 mboxlist_sync_setacls(mailbox->name, mailbox->acl, remote->foldermodseq);
5597 mailbox->foldermodseq = remote->foldermodseq;
5598 }
5599
5600 /* nothing changed - nothing to send */
5601 if (is_unchanged(mailbox, remote))
5602 goto done;
5603
5604 if (!topart) topart = mailbox->part;
5605 part_list = sync_reserve_partlist(reserve_list, topart);
5606 r = sync_prepare_dlists(mailbox, local, remote, topart, part_list, kl,
5607 kupload, 1, /*XXX flags & SYNC_FLAG_FULLANNOTS*/1, !(flags & SYNC_FLAG_ISREPEAT));
5608 if (r) goto done;
5609
5610 /* keep the mailbox locked for shorter time! Unlock the index now
5611 * but don't close it, because we need to guarantee that message
5612 * files don't get deleted until we're finished with them... */
5613 if (!local->mailbox) mailbox_unlock_index(mailbox, NULL);
5614
5615 if (flags & SYNC_FLAG_VERBOSE)
5616 printf("%s %s\n", cmd, local->name);
5617
5618 if (flags & SYNC_FLAG_LOGGING)
5619 syslog(LOG_INFO, "%s %s", cmd, local->name);
5620
5621 /* upload in small(ish) blocks to avoid timeouts */
5622 while (kupload->head) {
5623 struct dlist *kul1 = dlist_splice(kupload, 1024);
5624 sync_send_apply(kul1, sync_be->out);
5625 r = sync_parse_response("MESSAGE", sync_be->in, NULL);
5626 dlist_free(&kul1);
5627 if (r) goto done; /* abort earlier */
5628 }
5629
5630 /* close before sending the apply - all data is already read */
5631 if (!local->mailbox) mailbox_close(&mailbox);
5632
5633 /* update the mailbox */
5634 sync_send_apply(kl, sync_be->out);
5635 r = sync_parse_response("MAILBOX", sync_be->in, NULL);
5636
5637 done:
5638 if (mailbox && !local->mailbox) mailbox_close(&mailbox);
5639
5640 dlist_free(&kupload);
5641 dlist_free(&kl);
5642 return r;
5643 }
5644
sync_update_mailbox(struct sync_folder * local,struct sync_folder * remote,const char * topart,struct sync_reserve_list * reserve_list,struct backend * sync_be,unsigned flags)5645 int sync_update_mailbox(struct sync_folder *local,
5646 struct sync_folder *remote,
5647 const char *topart,
5648 struct sync_reserve_list *reserve_list,
5649 struct backend *sync_be,
5650 unsigned flags)
5651 {
5652 int r = update_mailbox_once(local, remote, topart,
5653 reserve_list, sync_be, flags);
5654
5655 flags |= SYNC_FLAG_ISREPEAT;
5656
5657 if (r == IMAP_SYNC_CHECKSUM) {
5658 syslog(LOG_NOTICE, "SYNC_NOTICE: CRC failure on sync %s, recalculating counts and trying again", local->name);
5659 r = update_mailbox_once(local, remote, topart,
5660 reserve_list, sync_be, flags);
5661 }
5662
5663 /* never retry - other end should always sync cleanly */
5664 if (flags & SYNC_FLAG_NO_COPYBACK) return r;
5665
5666 if (r == IMAP_AGAIN) {
5667 local->ispartial = 0; /* don't batch the re-update, means sync to 2.4 will still work after fullsync */
5668 r = mailbox_full_update(local, reserve_list, sync_be, flags);
5669 if (!r) r = update_mailbox_once(local, remote, topart,
5670 reserve_list, sync_be, flags);
5671 }
5672 else if (r == IMAP_SYNC_CHECKSUM) {
5673 syslog(LOG_ERR, "CRC failure on sync for %s, trying full update",
5674 local->name);
5675 r = mailbox_full_update(local, reserve_list, sync_be, flags);
5676 if (!r) r = update_mailbox_once(local, remote, topart,
5677 reserve_list, sync_be,
5678 flags|SYNC_FLAG_FULLANNOTS);
5679 }
5680
5681 return r;
5682 }
5683
5684 /* ====================================================================== */
5685
update_seen_work(const char * user,const char * uniqueid,struct seendata * sd,struct backend * sync_be,unsigned flags)5686 static int update_seen_work(const char *user, const char *uniqueid,
5687 struct seendata *sd, struct backend *sync_be,
5688 unsigned flags)
5689 {
5690 const char *cmd = "SEEN";
5691 struct dlist *kl;
5692
5693 if (flags & SYNC_FLAG_VERBOSE)
5694 printf("SEEN %s %s\n", user, uniqueid);
5695
5696 if (flags & SYNC_FLAG_LOGGING)
5697 syslog(LOG_INFO, "SEEN %s %s", user, uniqueid);
5698
5699 /* Update seen list */
5700 kl = dlist_newkvlist(NULL, cmd);
5701 dlist_setatom(kl, "USERID", user);
5702 dlist_setatom(kl, "UNIQUEID", uniqueid);
5703 dlist_setdate(kl, "LASTREAD", sd->lastread);
5704 dlist_setnum32(kl, "LASTUID", sd->lastuid);
5705 dlist_setdate(kl, "LASTCHANGE", sd->lastchange);
5706 dlist_setatom(kl, "SEENUIDS", sd->seenuids);
5707 sync_send_apply(kl, sync_be->out);
5708 dlist_free(&kl);
5709
5710 return sync_parse_response(cmd, sync_be->in, NULL);
5711 }
5712
sync_do_seen(const char * userid,char * uniqueid,struct backend * sync_be,unsigned flags)5713 int sync_do_seen(const char *userid, char *uniqueid, struct backend *sync_be,
5714 unsigned flags)
5715 {
5716 int r = 0;
5717 struct seen *seendb = NULL;
5718 struct seendata sd = SEENDATA_INITIALIZER;
5719
5720 /* ignore read failures */
5721 r = seen_open(userid, SEEN_SILENT, &seendb);
5722 if (r) return 0;
5723
5724 r = seen_read(seendb, uniqueid, &sd);
5725
5726 if (!r) r = update_seen_work(userid, uniqueid, &sd, sync_be, flags);
5727
5728 seen_close(&seendb);
5729 seen_freedata(&sd);
5730
5731 return r;
5732 }
5733
5734 /* ====================================================================== */
5735
sync_do_quota(const char * root,struct backend * sync_be,unsigned flags)5736 int sync_do_quota(const char *root, struct backend *sync_be,
5737 unsigned flags)
5738 {
5739 int r = 0;
5740 struct quota q;
5741
5742 quota_init(&q, root);
5743 r = update_quota_work(&q, NULL, sync_be, flags);
5744 quota_free(&q);
5745
5746 return r;
5747 }
5748
do_annotation_cb(const char * mailbox,uint32_t uid,const char * entry,const char * userid,const struct buf * value,const struct annotate_metadata * mdata,void * rock)5749 static int do_annotation_cb(const char *mailbox __attribute__((unused)),
5750 uint32_t uid __attribute__((unused)),
5751 const char *entry, const char *userid,
5752 const struct buf *value,
5753 const struct annotate_metadata *mdata,
5754 void *rock)
5755 {
5756 struct sync_annot_list *l = (struct sync_annot_list *) rock;
5757
5758 sync_annot_list_add(l, entry, userid, value, mdata->modseq);
5759
5760 return 0;
5761 }
5762
parse_annotation(struct dlist * kin,struct sync_annot_list * replica_annot)5763 static int parse_annotation(struct dlist *kin,
5764 struct sync_annot_list *replica_annot)
5765 {
5766 struct dlist *kl;
5767 const char *entry;
5768 const char *userid = "";
5769 const char *valmap = NULL;
5770 size_t vallen = 0;
5771 struct buf value = BUF_INITIALIZER;
5772 modseq_t modseq = 0;
5773
5774 for (kl = kin->head; kl; kl = kl->next) {
5775 if (!dlist_getatom(kl, "ENTRY", &entry))
5776 return IMAP_PROTOCOL_BAD_PARAMETERS;
5777 if (!dlist_getmap(kl, "VALUE", &valmap, &vallen))
5778 return IMAP_PROTOCOL_BAD_PARAMETERS;
5779
5780 dlist_getatom(kl, "USERID", &userid); /* optional */
5781 dlist_getnum64(kl, "MODSEQ", &modseq); /* optional */
5782
5783 buf_init_ro(&value, valmap, vallen);
5784 sync_annot_list_add(replica_annot, entry, userid, &value, modseq);
5785 buf_free(&value);
5786 }
5787
5788 return 0;
5789 }
5790
do_getannotation(const char * mboxname,struct sync_annot_list * replica_annot,struct backend * sync_be)5791 static int do_getannotation(const char *mboxname,
5792 struct sync_annot_list *replica_annot,
5793 struct backend *sync_be)
5794 {
5795 const char *cmd = "ANNOTATION";
5796 struct dlist *kl;
5797 struct dlist *kin = NULL;
5798 int r;
5799
5800 /* Update seen list */
5801 kl = dlist_setatom(NULL, cmd, mboxname);
5802 sync_send_lookup(kl, sync_be->out);
5803 dlist_free(&kl);
5804
5805 r = sync_parse_response(cmd, sync_be->in, &kin);
5806 if (r) return r;
5807
5808 r = parse_annotation(kin, replica_annot);
5809 dlist_free(&kin);
5810
5811 return r;
5812 }
5813
sync_do_annotation(char * mboxname,struct backend * sync_be,unsigned flags)5814 int sync_do_annotation(char *mboxname, struct backend *sync_be, unsigned flags)
5815 {
5816 int r;
5817 struct sync_annot_list *replica_annot = sync_annot_list_create();
5818 struct sync_annot_list *master_annot = sync_annot_list_create();
5819 struct sync_annot *ma, *ra;
5820 int n;
5821
5822 r = do_getannotation(mboxname, replica_annot, sync_be);
5823 if (r) goto bail;
5824
5825 r = annotatemore_findall(mboxname, 0, "*", /*modseq*/0, &do_annotation_cb,
5826 master_annot, /*flags*/0);
5827 if (r) {
5828 syslog(LOG_ERR, "IOERROR: fetching annotations for %s", mboxname);
5829 r = IMAP_IOERROR;
5830 goto bail;
5831 }
5832
5833 /* both lists are sorted, so we work our way through the lists
5834 top-to-bottom and determine what we need to do based on order */
5835 ma = master_annot->head;
5836 ra = replica_annot->head;
5837 while (ma || ra) {
5838 if (!ra) n = -1; /* add all master annotations */
5839 else if (!ma) n = 1; /* remove all replica annotations */
5840 else if ((n = strcmp(ma->entry, ra->entry)) == 0)
5841 n = strcmp(ma->userid, ra->userid);
5842
5843 if (n > 0) {
5844 /* remove replica annotation */
5845 r = folder_unannotation(mboxname, ra->entry, ra->userid,
5846 sync_be, flags);
5847 if (r) goto bail;
5848 ra = ra->next;
5849 continue;
5850 }
5851
5852 if (n == 0) {
5853 /* already have the annotation, but is the value different? */
5854 if (!buf_cmp(&ra->value, &ma->value)) {
5855 ra = ra->next;
5856 ma = ma->next;
5857 continue;
5858 }
5859 ra = ra->next;
5860 }
5861
5862 /* add the current client annotation */
5863 r = folder_setannotation(mboxname, ma->entry, ma->userid, &ma->value,
5864 sync_be, flags);
5865 if (r) goto bail;
5866
5867 ma = ma->next;
5868 }
5869
5870 bail:
5871 sync_annot_list_free(&master_annot);
5872 sync_annot_list_free(&replica_annot);
5873 return r;
5874 }
5875
5876 /* ====================================================================== */
5877
do_folders(struct sync_name_list * mboxname_list,const char * topart,struct sync_folder_list * replica_folders,struct backend * sync_be,const char ** channelp,unsigned flags)5878 static int do_folders(struct sync_name_list *mboxname_list, const char *topart,
5879 struct sync_folder_list *replica_folders,
5880 struct backend *sync_be,
5881 const char **channelp,
5882 unsigned flags)
5883 {
5884 int r = 0;
5885 struct sync_folder_list *master_folders = NULL;
5886 struct sync_rename_list *rename_folders = NULL;
5887 struct sync_reserve_list *reserve_list = NULL;
5888 struct sync_folder *mfolder, *rfolder;
5889 const char *part;
5890 uint32_t batchsize = 0;
5891 struct sync_name *mbox;
5892
5893 /* Look for intermediate mailboxes */
5894 for (mbox = mboxname_list->head; !r && mbox; mbox = mbox->next) {
5895 mbentry_t *mbentry = NULL;
5896
5897 if (mboxlist_lookup_allow_all(mbox->name, &mbentry, NULL))
5898 continue;
5899
5900 if (mbentry->mbtype & MBTYPE_INTERMEDIATE) {
5901 struct dlist *kl = dlist_newkvlist(NULL, "MAILBOX");
5902
5903 dlist_setatom(kl, "UNIQUEID", mbentry->uniqueid);
5904 dlist_setatom(kl, "MBOXNAME", mbentry->name);
5905 dlist_setatom(kl, "MBOXTYPE",
5906 mboxlist_mbtype_to_string(mbentry->mbtype));
5907 dlist_setnum64(kl, "HIGHESTMODSEQ", mbentry->foldermodseq);
5908 dlist_setnum64(kl, "CREATEDMODSEQ", mbentry->createdmodseq);
5909 dlist_setnum64(kl, "FOLDERMODSEQ", mbentry->foldermodseq);
5910
5911 sync_send_apply(kl, sync_be->out);
5912 r = sync_parse_response("MAILBOX", sync_be->in, NULL);
5913
5914 dlist_free(&kl);
5915 }
5916
5917 mboxlist_entry_free(&mbentry);
5918
5919 if (r) {
5920 syslog(LOG_ERR, "apply intermediates: failed: %s", error_message(r));
5921 goto bail;
5922 }
5923 }
5924
5925
5926 if (channelp) {
5927 batchsize = config_getint(IMAPOPT_SYNC_BATCHSIZE);
5928 }
5929
5930 master_folders = sync_folder_list_create();
5931 rename_folders = sync_rename_list_create();
5932 reserve_list = sync_reserve_list_create(SYNC_MSGID_LIST_HASH_SIZE);
5933
5934 r = reserve_messages(mboxname_list, topart, master_folders,
5935 replica_folders, reserve_list, sync_be, batchsize);
5936 if (r) {
5937 syslog(LOG_ERR, "reserve messages: failed: %s", error_message(r));
5938 goto bail;
5939 }
5940
5941 /* Tag folders on server which still exist on the client. Anything
5942 * on the server which remains untagged can be deleted immediately */
5943 for (mfolder = master_folders->head; mfolder; mfolder = mfolder->next) {
5944 if (mfolder->mark) continue;
5945 rfolder = sync_folder_lookup(replica_folders, mfolder->uniqueid);
5946 if (!rfolder) continue;
5947 if (rfolder->mark) continue;
5948 rfolder->mark = 1;
5949
5950 /* does it need a rename? partition change is a rename too */
5951 part = topart ? topart : mfolder->part;
5952 if (strcmp(mfolder->name, rfolder->name) || strcmp(part, rfolder->part)) {
5953 sync_rename_list_add(rename_folders, mfolder->uniqueid, rfolder->name,
5954 mfolder->name, part, mfolder->uidvalidity);
5955 }
5956 }
5957
5958 /* XXX - sync_log_channel_user on any issue here rather than trying to solve,
5959 * and remove all entries related to that user from both lists */
5960
5961 /* Delete folders on server which no longer exist on client */
5962 if (flags & SYNC_FLAG_DELETE_REMOTE) {
5963 for (rfolder = replica_folders->head; rfolder; rfolder = rfolder->next) {
5964 if (rfolder->mark) continue;
5965 r = sync_folder_delete(rfolder->name, sync_be, flags);
5966 if (r) {
5967 syslog(LOG_ERR, "sync_folder_delete(): failed: %s '%s'",
5968 rfolder->name, error_message(r));
5969 goto bail;
5970 }
5971 }
5972 }
5973
5974 /* Need to rename folders in an order which avoids dependancy conflicts
5975 * following isn't wildly efficient, but rename_folders will typically be
5976 * short and contain few dependancies. Algorithm is to simply pick a
5977 * rename operation which has no dependancy and repeat until done */
5978
5979 while (rename_folders->done < rename_folders->count) {
5980 int rename_success = 0;
5981 struct sync_rename *item, *item2 = NULL;
5982
5983 for (item = rename_folders->head; item; item = item->next) {
5984 if (item->done) continue;
5985
5986 /* don't skip rename to different partition */
5987 if (strcmp(item->oldname, item->newname)) {
5988 item2 = sync_rename_lookup(rename_folders, item->newname);
5989 if (item2 && !item2->done) continue;
5990 }
5991
5992 /* Found unprocessed item which should rename cleanly */
5993 r = folder_rename(item->oldname, item->newname, item->part,
5994 item->uidvalidity, sync_be, flags);
5995 if (r) {
5996 syslog(LOG_ERR, "do_folders(): failed to rename: %s -> %s ",
5997 item->oldname, item->newname);
5998 goto bail;
5999 }
6000
6001 rename_folders->done++;
6002 item->done = 1;
6003 rename_success = 1;
6004 }
6005
6006 if (!rename_success) {
6007 /* Scanned entire list without a match */
6008 const char *name = "unknown";
6009 if (item2) name = item2->oldname;
6010 syslog(LOG_ERR,
6011 "do_folders(): failed to order folders correctly at %s", name);
6012 r = IMAP_AGAIN;
6013 goto bail;
6014 }
6015 }
6016
6017 for (mfolder = master_folders->head; mfolder; mfolder = mfolder->next) {
6018 if (mfolder->mark) continue;
6019 /* NOTE: rfolder->name may now be wrong, but we're guaranteed that
6020 * it was successfully renamed above, so just use mfolder->name for
6021 * all commands */
6022 rfolder = sync_folder_lookup(replica_folders, mfolder->uniqueid);
6023 r = sync_update_mailbox(mfolder, rfolder, topart, reserve_list,
6024 sync_be, flags);
6025 if (r) {
6026 syslog(LOG_ERR, "do_folders(): update failed: %s '%s'",
6027 mfolder->name, error_message(r));
6028 goto bail;
6029 }
6030 if (channelp && mfolder->ispartial) {
6031 sync_log_channel_mailbox(*channelp, mfolder->name);
6032 }
6033 }
6034
6035 bail:
6036 sync_folder_list_free(&master_folders);
6037 sync_rename_list_free(&rename_folders);
6038 sync_reserve_list_free(&reserve_list);
6039 return r;
6040 }
6041
sync_do_mailboxes(struct sync_name_list * mboxname_list,const char * topart,struct backend * sync_be,const char ** channelp,unsigned flags)6042 int sync_do_mailboxes(struct sync_name_list *mboxname_list, const char *topart,
6043 struct backend *sync_be, const char **channelp, unsigned flags)
6044
6045 {
6046 struct sync_name *mbox;
6047 struct sync_folder_list *replica_folders = sync_folder_list_create();
6048 struct dlist *kl = NULL;
6049 struct buf buf = BUF_INITIALIZER;
6050 int r;
6051
6052 kl = dlist_newlist(NULL, "MAILBOXES");
6053
6054 for (mbox = mboxname_list->head; mbox; mbox = mbox->next) {
6055 dlist_setatom(kl, "MBOXNAME", mbox->name);
6056
6057 if ((flags & SYNC_FLAG_VERBOSE) || (flags & SYNC_FLAG_LOGGING))
6058 buf_printf(&buf, " %s", mbox->name);
6059 }
6060
6061 if (flags & SYNC_FLAG_VERBOSE)
6062 printf("MAILBOXES%s\n", buf_cstring(&buf));
6063
6064 if (flags & SYNC_FLAG_LOGGING)
6065 syslog(LOG_INFO, "MAILBOXES%s", buf_cstring(&buf));
6066
6067 buf_free(&buf);
6068
6069 sync_send_lookup(kl, sync_be->out);
6070
6071 dlist_free(&kl);
6072
6073 r = sync_response_parse(sync_be->in, "MAILBOXES", replica_folders,
6074 NULL, NULL, NULL, NULL);
6075
6076 /* we don't want to delete remote folders which weren't found locally,
6077 * because we may be racing with a rename, and we don't want to lose
6078 * the remote files. A real delete will always have inserted a
6079 * UNMAILBOX anyway */
6080 if (!r) {
6081 flags &= ~SYNC_FLAG_DELETE_REMOTE;
6082 r = do_folders(mboxname_list, topart,
6083 replica_folders, sync_be, channelp, flags);
6084 }
6085
6086 sync_folder_list_free(&replica_folders);
6087
6088 return r;
6089 }
6090
6091 /* ====================================================================== */
6092
6093 struct mboxinfo {
6094 struct sync_name_list *mboxlist;
6095 struct sync_name_list *quotalist;
6096 };
6097
do_mailbox_info(const mbentry_t * mbentry,void * rock)6098 static int do_mailbox_info(const mbentry_t *mbentry, void *rock)
6099 {
6100 struct mailbox *mailbox = NULL;
6101 struct mboxinfo *info = (struct mboxinfo *)rock;
6102 int r = 0;
6103
6104 /* XXX - check for deleted? */
6105
6106 if (mbentry->mbtype & MBTYPE_INTERMEDIATE) {
6107 sync_name_list_add(info->mboxlist, mbentry->name);
6108 return 0;
6109 }
6110
6111 r = mailbox_open_irl(mbentry->name, &mailbox);
6112 if (!r) r = sync_mailbox_version_check(&mailbox);
6113 /* doesn't exist? Probably not finished creating or removing yet */
6114 if (r == IMAP_MAILBOX_NONEXISTENT) {
6115 r = 0;
6116 goto done;
6117 }
6118 if (r == IMAP_MAILBOX_RESERVED) {
6119 r = 0;
6120 goto done;
6121 }
6122 if (r) goto done;
6123
6124 if (info->quotalist && mailbox->quotaroot) {
6125 if (!sync_name_lookup(info->quotalist, mailbox->quotaroot))
6126 sync_name_list_add(info->quotalist, mailbox->quotaroot);
6127 }
6128
6129 sync_name_list_add(info->mboxlist, mbentry->name);
6130
6131 done:
6132 mailbox_close(&mailbox);
6133 return r;
6134 }
6135
sync_do_user_quota(struct sync_name_list * master_quotaroots,struct sync_quota_list * replica_quota,struct backend * sync_be,unsigned flags)6136 int sync_do_user_quota(struct sync_name_list *master_quotaroots,
6137 struct sync_quota_list *replica_quota,
6138 struct backend *sync_be, unsigned flags)
6139 {
6140 int r;
6141 struct sync_name *mitem;
6142 struct sync_quota *rquota;
6143 struct quota q;
6144
6145 /* set any new or changed quotas */
6146 for (mitem = master_quotaroots->head; mitem; mitem = mitem->next) {
6147 rquota = sync_quota_lookup(replica_quota, mitem->name);
6148 if (rquota)
6149 rquota->done = 1;
6150 quota_init(&q, mitem->name);
6151 r = update_quota_work(&q, rquota, sync_be, flags);
6152 quota_free(&q);
6153 if (r) return r;
6154 }
6155
6156 /* delete any quotas no longer on the master */
6157 for (rquota = replica_quota->head; rquota; rquota = rquota->next) {
6158 if (rquota->done) continue;
6159 r = delete_quota(rquota->root, sync_be, flags);
6160 if (r) return r;
6161 }
6162
6163 return 0;
6164 }
6165
do_user_main(const char * user,const char * topart,struct sync_folder_list * replica_folders,struct sync_quota_list * replica_quota,struct backend * sync_be,const char ** channelp,unsigned flags)6166 static int do_user_main(const char *user, const char *topart,
6167 struct sync_folder_list *replica_folders,
6168 struct sync_quota_list *replica_quota,
6169 struct backend *sync_be,
6170 const char **channelp,
6171 unsigned flags)
6172 {
6173 int r = 0;
6174 struct mboxinfo info;
6175
6176 info.mboxlist = sync_name_list_create();
6177 info.quotalist = sync_name_list_create();
6178
6179 r = mboxlist_usermboxtree(user, NULL, do_mailbox_info, &info, MBOXTREE_DELETED);
6180
6181 /* we know all the folders present on the master, so it's safe to delete
6182 * anything not mentioned here on the replica - at least until we get
6183 * real tombstones */
6184 flags |= SYNC_FLAG_DELETE_REMOTE;
6185 if (!r) r = do_folders(info.mboxlist, topart,
6186 replica_folders, sync_be, channelp, flags);
6187 if (!r) r = sync_do_user_quota(info.quotalist, replica_quota,
6188 sync_be, flags);
6189
6190 sync_name_list_free(&info.mboxlist);
6191 sync_name_list_free(&info.quotalist);
6192
6193 if (r) syslog(LOG_ERR, "IOERROR: do_user_main: %s for %s to %s (%s)", error_message(r),
6194 user, (channelp && *channelp) ? *channelp : "[no channel]",
6195 sync_be->hostname);
6196
6197 return r;
6198 }
6199
sync_do_user_sub(const char * userid,struct sync_name_list * replica_subs,struct backend * sync_be,unsigned flags)6200 int sync_do_user_sub(const char *userid, struct sync_name_list *replica_subs,
6201 struct backend *sync_be, unsigned flags)
6202 {
6203 struct sync_name *rsubs;
6204 int r = 0;
6205 int i;
6206
6207 /* Includes subsidiary nodes automatically */
6208 strarray_t *msubs = mboxlist_sublist(userid);
6209 if (!msubs) {
6210 syslog(LOG_ERR, "IOERROR: fetching subscriptions for %s", userid);
6211 r = IMAP_IOERROR;
6212 goto bail;
6213 }
6214
6215 /* add any folders that need adding, and mark any which
6216 * still exist */
6217 for (i = 0; i < msubs->count; i++) {
6218 const char *name = strarray_nth(msubs, i);
6219 rsubs = sync_name_lookup(replica_subs, name);
6220 if (rsubs) {
6221 rsubs->mark = 1;
6222 continue;
6223 }
6224 r = sync_set_sub(userid, name, 1, sync_be, flags);
6225 if (r) goto bail;
6226 }
6227
6228 /* remove any no-longer-subscribed folders */
6229 for (rsubs = replica_subs->head; rsubs; rsubs = rsubs->next) {
6230 if (rsubs->mark)
6231 continue;
6232 r = sync_set_sub(userid, rsubs->name, 0, sync_be, flags);
6233 if (r) goto bail;
6234 }
6235
6236 bail:
6237 strarray_free(msubs);
6238 return r;
6239 }
6240
get_seen(const char * uniqueid,struct seendata * sd,void * rock)6241 static int get_seen(const char *uniqueid, struct seendata *sd, void *rock)
6242 {
6243 struct sync_seen_list *list = (struct sync_seen_list *)rock;
6244
6245 sync_seen_list_add(list, uniqueid, sd->lastread, sd->lastuid,
6246 sd->lastchange, sd->seenuids);
6247
6248 return 0;
6249 }
6250
sync_do_user_seen(const char * userid,struct sync_seen_list * replica_seen,struct backend * sync_be,unsigned flags)6251 int sync_do_user_seen(const char *userid, struct sync_seen_list *replica_seen,
6252 struct backend *sync_be, unsigned flags)
6253 {
6254 int r;
6255 struct sync_seen *mseen, *rseen;
6256 struct seen *seendb = NULL;
6257 struct sync_seen_list *list;
6258
6259 /* silently ignore errors */
6260 r = seen_open(userid, SEEN_SILENT, &seendb);
6261 if (r) return 0;
6262
6263 list = sync_seen_list_create();
6264
6265 seen_foreach(seendb, get_seen, list);
6266 seen_close(&seendb);
6267
6268 for (mseen = list->head; mseen; mseen = mseen->next) {
6269 rseen = sync_seen_list_lookup(replica_seen, mseen->uniqueid);
6270 if (rseen) {
6271 rseen->mark = 1;
6272 if (seen_compare(&rseen->sd, &mseen->sd))
6273 continue; /* nothing changed */
6274 }
6275 r = update_seen_work(userid, mseen->uniqueid, &mseen->sd,
6276 sync_be, flags);
6277 }
6278
6279 /* XXX - delete seen on the replica for records that don't exist? */
6280
6281 sync_seen_list_free(&list);
6282
6283 return 0;
6284 }
6285
sync_do_user_sieve(const char * userid,struct sync_sieve_list * replica_sieve,struct backend * sync_be,unsigned flags)6286 int sync_do_user_sieve(const char *userid, struct sync_sieve_list *replica_sieve,
6287 struct backend *sync_be, unsigned flags)
6288 {
6289 int r = 0;
6290 struct sync_sieve_list *master_sieve;
6291 struct sync_sieve *mitem, *ritem;
6292 int master_active = 0;
6293 int replica_active = 0;
6294 char *ext;
6295
6296 master_sieve = sync_sieve_list_generate(userid);
6297 if (!master_sieve) {
6298 syslog(LOG_ERR, "Unable to list sieve scripts for %s", userid);
6299 return IMAP_IOERROR;
6300 }
6301
6302 /* Upload missing and out of date or mismatching scripts */
6303 for (mitem = master_sieve->head; mitem; mitem = mitem->next) {
6304 ritem = sync_sieve_lookup(replica_sieve, mitem->name);
6305 if (ritem) {
6306 ritem->mark = 1;
6307 /* compare the GUID if known */
6308 if (!message_guid_isnull(&ritem->guid)) {
6309 if (message_guid_equal(&ritem->guid, &mitem->guid))
6310 continue;
6311 /* XXX: copyback support */
6312 }
6313 /* fallback to date comparison */
6314 else if (ritem->last_update >= mitem->last_update)
6315 continue; /* changed */
6316 }
6317
6318 /* Don't upload compiled bytecode */
6319 ext = strrchr(mitem->name, '.');
6320 if (ext && !strcmp(ext, ".bc"))
6321 continue;
6322
6323 r = sieve_upload(userid, mitem->name, mitem->last_update,
6324 sync_be, flags);
6325 if (r) goto bail;
6326 }
6327
6328 /* Delete scripts which no longer exist on the master */
6329 replica_active = 0;
6330 for (ritem = replica_sieve->head; ritem; ritem = ritem->next) {
6331 if (ritem->mark) {
6332 if (ritem->active)
6333 replica_active = 1;
6334 } else {
6335 r = sieve_delete(userid, ritem->name, sync_be, flags);
6336 if (r) goto bail;
6337 }
6338 }
6339
6340 /* Change active script if necessary */
6341 master_active = 0;
6342 for (mitem = master_sieve->head; mitem; mitem = mitem->next) {
6343 if (!mitem->active)
6344 continue;
6345
6346 master_active = 1;
6347 ritem = sync_sieve_lookup(replica_sieve, mitem->name);
6348 if (ritem && ritem->active)
6349 break;
6350
6351 r = sieve_activate(userid, mitem->name, sync_be, flags);
6352 if (r) goto bail;
6353
6354 replica_active = 1;
6355 break;
6356 }
6357
6358 if (!master_active && replica_active)
6359 r = sieve_deactivate(userid, sync_be, flags);
6360
6361 bail:
6362 sync_sieve_list_free(&master_sieve);
6363 return(r);
6364 }
6365
sync_do_user(const char * userid,const char * topart,struct backend * sync_be,const char ** channelp,unsigned flags)6366 int sync_do_user(const char *userid, const char *topart,
6367 struct backend *sync_be, const char **channelp, unsigned flags)
6368 {
6369 int r = 0;
6370 struct sync_folder_list *replica_folders = sync_folder_list_create();
6371 struct sync_name_list *replica_subs = sync_name_list_create();
6372 struct sync_sieve_list *replica_sieve = sync_sieve_list_create();
6373 struct sync_seen_list *replica_seen = sync_seen_list_create();
6374 struct sync_quota_list *replica_quota = sync_quota_list_create();
6375 struct dlist *kl = NULL;
6376 struct mailbox *mailbox = NULL;
6377
6378 if (flags & SYNC_FLAG_VERBOSE)
6379 printf("USER %s\n", userid);
6380
6381 if (flags & SYNC_FLAG_LOGGING)
6382 syslog(LOG_INFO, "USER %s", userid);
6383
6384 kl = dlist_setatom(NULL, "USER", userid);
6385 sync_send_lookup(kl, sync_be->out);
6386 dlist_free(&kl);
6387
6388 r = sync_response_parse(sync_be->in, "USER", replica_folders, replica_subs,
6389 replica_sieve, replica_seen, replica_quota);
6390 /* can happen! */
6391 if (r == IMAP_MAILBOX_NONEXISTENT) r = 0;
6392 if (r) goto done;
6393
6394 /* check that the inbox exists locally to be allowed to sync this user at all */
6395 char *inbox = mboxname_user_mbox(userid, NULL);
6396 r = mailbox_open_irl(inbox, &mailbox);
6397 if (!r) r = sync_mailbox_version_check(&mailbox);
6398 free(inbox);
6399 if (r == IMAP_MAILBOX_NONEXISTENT) {
6400 if (flags & SYNC_FLAG_VERBOSE)
6401 printf("Does not exist locally %s\n", userid);
6402 if (flags & SYNC_FLAG_LOGGING)
6403 syslog(LOG_INFO, "Does not exist locally %s", userid);
6404
6405 // just skip this user. XXX - tombstone for user -> sync_reset?
6406 r = 0;
6407 goto done;
6408 }
6409 if (r) goto done;
6410
6411 /* we don't hold locks while sending commands */
6412 mailbox_close(&mailbox);
6413 r = do_user_main(userid, topart, replica_folders, replica_quota,
6414 sync_be, channelp, flags);
6415 if (r) goto done;
6416 r = sync_do_user_sub(userid, replica_subs, sync_be, flags);
6417 if (r) goto done;
6418 r = sync_do_user_sieve(userid, replica_sieve, sync_be, flags);
6419 if (r) goto done;
6420 r = sync_do_user_seen(userid, replica_seen, sync_be, flags);
6421
6422 done:
6423 sync_folder_list_free(&replica_folders);
6424 sync_name_list_free(&replica_subs);
6425 sync_sieve_list_free(&replica_sieve);
6426 sync_seen_list_free(&replica_seen);
6427 sync_quota_list_free(&replica_quota);
6428
6429 return r;
6430 }
6431
6432 /* ====================================================================== */
6433
sync_do_meta(const char * userid,struct backend * sync_be,unsigned flags)6434 int sync_do_meta(const char *userid, struct backend *sync_be, unsigned flags)
6435 {
6436 struct sync_name_list *replica_subs = sync_name_list_create();
6437 struct sync_sieve_list *replica_sieve = sync_sieve_list_create();
6438 struct sync_seen_list *replica_seen = sync_seen_list_create();
6439 struct dlist *kl = NULL;
6440 int r = 0;
6441
6442 if (flags & SYNC_FLAG_VERBOSE)
6443 printf("META %s\n", userid);
6444
6445 if (flags & SYNC_FLAG_LOGGING)
6446 syslog(LOG_INFO, "META %s", userid);
6447
6448 kl = dlist_setatom(NULL, "META", userid);
6449 sync_send_lookup(kl, sync_be->out);
6450 dlist_free(&kl);
6451
6452 r = sync_response_parse(sync_be->in, "META", NULL,
6453 replica_subs, replica_sieve, replica_seen, NULL);
6454 if (!r) r = sync_do_user_seen(userid, replica_seen, sync_be, flags);
6455 if (!r) r = sync_do_user_sub(userid, replica_subs, sync_be, flags);
6456 if (!r) r = sync_do_user_sieve(userid, replica_sieve, sync_be, flags);
6457 sync_seen_list_free(&replica_seen);
6458 sync_name_list_free(&replica_subs);
6459 sync_sieve_list_free(&replica_sieve);
6460
6461 return r;
6462 }
6463
6464 /* ====================================================================== */
6465
sync_apply(struct dlist * kin,struct sync_reserve_list * reserve_list,struct sync_state * state)6466 EXPORTED const char *sync_apply(struct dlist *kin, struct sync_reserve_list *reserve_list, struct sync_state *state)
6467 {
6468 int r = IMAP_PROTOCOL_ERROR;
6469
6470 ucase(kin->name);
6471
6472 if (!strcmp(kin->name, "MESSAGE"))
6473 r = sync_apply_message(kin, reserve_list, state);
6474 else if (!strcmp(kin->name, "EXPUNGE"))
6475 r = sync_apply_expunge(kin, state);
6476
6477 /* dump protocol */
6478 else if (!strcmp(kin->name, "ACTIVATE_SIEVE"))
6479 r = sync_apply_activate_sieve(kin, state);
6480 else if (!strcmp(kin->name, "ANNOTATION"))
6481 r = sync_apply_annotation(kin, state);
6482 else if (!strcmp(kin->name, "MAILBOX"))
6483 r = sync_apply_mailbox(kin, reserve_list, state);
6484 else if (!strcmp(kin->name, "LOCAL_MAILBOX")) {
6485 state->local_only = 1;
6486 r = sync_apply_mailbox(kin, reserve_list, state);
6487 }
6488 else if (!strcmp(kin->name, "QUOTA"))
6489 r = sync_apply_quota(kin, state);
6490 else if (!strcmp(kin->name, "SEEN"))
6491 r = sync_apply_seen(kin, state);
6492 else if (!strcmp(kin->name, "RENAME"))
6493 r = sync_apply_rename(kin, state);
6494 else if (!strcmp(kin->name, "LOCAL_RENAME")) {
6495 state->local_only = 1;
6496 r = sync_apply_rename(kin, state);
6497 }
6498 else if (!strcmp(kin->name, "RESERVE"))
6499 r = sync_apply_reserve(kin, reserve_list, state);
6500 else if (!strcmp(kin->name, "SIEVE"))
6501 r = sync_apply_sieve(kin, state);
6502 else if (!strcmp(kin->name, "SUB"))
6503 r = sync_apply_changesub(kin, state);
6504
6505 /* "un"dump protocol ;) */
6506 else if (!strcmp(kin->name, "UNACTIVATE_SIEVE"))
6507 r = sync_apply_unactivate_sieve(kin, state);
6508 else if (!strcmp(kin->name, "UNANNOTATION"))
6509 r = sync_apply_unannotation(kin, state);
6510 else if (!strcmp(kin->name, "UNMAILBOX"))
6511 r = sync_apply_unmailbox(kin, state);
6512 else if (!strcmp(kin->name, "LOCAL_UNMAILBOX")) {
6513 state->local_only = 1;
6514 r = sync_apply_unmailbox(kin, state);
6515 }
6516 else if (!strcmp(kin->name, "UNQUOTA"))
6517 r = sync_apply_unquota(kin, state);
6518 else if (!strcmp(kin->name, "UNSIEVE"))
6519 r = sync_apply_unsieve(kin, state);
6520 else if (!strcmp(kin->name, "UNSUB"))
6521 r = sync_apply_changesub(kin, state);
6522
6523 /* user is a special case that's not paired, there's no "upload user"
6524 * as such - we just call the individual commands with their items */
6525 else if (!strcmp(kin->name, "UNUSER"))
6526 r = sync_apply_unuser(kin, state);
6527 else if (!strcmp(kin->name, "LOCAL_UNUSER")) {
6528 state->local_only = 1;
6529 r = sync_apply_unuser(kin, state);
6530 }
6531
6532 else {
6533 syslog(LOG_ERR, "SYNCERROR: unknown command %s", kin->name);
6534 r = IMAP_PROTOCOL_ERROR;
6535 }
6536
6537 return sync_response(r);
6538 }
6539
sync_get(struct dlist * kin,struct sync_state * state)6540 EXPORTED const char *sync_get(struct dlist *kin, struct sync_state *state)
6541 {
6542 int r = IMAP_PROTOCOL_ERROR;
6543
6544 ucase(kin->name);
6545
6546 if (!strcmp(kin->name, "ANNOTATION"))
6547 r = sync_get_annotation(kin, state);
6548 else if (!strcmp(kin->name, "FETCH"))
6549 r = sync_get_message(kin, state);
6550 else if (!strcmp(kin->name, "FETCH_SIEVE"))
6551 r = sync_get_sieve(kin, state);
6552 else if (!strcmp(kin->name, "FULLMAILBOX"))
6553 r = sync_get_fullmailbox(kin, state);
6554 else if (!strcmp(kin->name, "MAILBOXES"))
6555 r = sync_get_mailboxes(kin, state);
6556 else if (!strcmp(kin->name, "UNIQUEIDS"))
6557 r = sync_get_uniqueids(kin, state);
6558 else if (!strcmp(kin->name, "META"))
6559 r = sync_get_meta(kin, state);
6560 else if (!strcmp(kin->name, "QUOTA"))
6561 r = sync_get_quota(kin, state);
6562 else if (!strcmp(kin->name, "USER"))
6563 r = sync_get_user(kin, state);
6564 else
6565 r = IMAP_PROTOCOL_ERROR;
6566
6567 return sync_response(r);
6568 }
6569
sync_restore(struct dlist * kin,struct sync_reserve_list * reserve_list,struct sync_state * state)6570 EXPORTED const char *sync_restore(struct dlist *kin,
6571 struct sync_reserve_list *reserve_list,
6572 struct sync_state *state)
6573 {
6574 int r = IMAP_PROTOCOL_ERROR;
6575
6576 ucase(kin->name);
6577
6578 if (!strcmp(kin->name, "MAILBOX"))
6579 r = sync_restore_mailbox(kin, reserve_list, state);
6580 else if (!strcmp(kin->name, "LOCAL_MAILBOX")) {
6581 state->local_only = 1;
6582 r = sync_restore_mailbox(kin, reserve_list, state);
6583 }
6584
6585 else {
6586 syslog(LOG_ERR, "SYNCERROR: unknown command %s", kin->name);
6587 r = IMAP_PROTOCOL_ERROR;
6588 }
6589
6590 return sync_response(r);
6591 }
6592