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(&current->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(&current->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(&current->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(&current->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, &copy, &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, &copy, 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, &copy);
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(&quotaroots);
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