1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "str.h"
5 #include "mail-namespace.h"
6 #include "mailbox-list-private.h"
7 #include "dsync-ibc.h"
8 #include "dsync-mailbox-tree.h"
9 #include "dsync-brain-private.h"
10
11 #include <ctype.h>
12
dsync_brain_check_namespaces(struct dsync_brain * brain)13 static void dsync_brain_check_namespaces(struct dsync_brain *brain)
14 {
15 struct mail_namespace *ns, *first_ns = NULL;
16 char sep;
17
18 i_assert(brain->hierarchy_sep == '\0');
19
20 for (ns = brain->user->namespaces; ns != NULL; ns = ns->next) {
21 if (!dsync_brain_want_namespace(brain, ns))
22 continue;
23
24 sep = mail_namespace_get_sep(ns);
25 if (first_ns == NULL) {
26 brain->hierarchy_sep = sep;
27 first_ns = ns;
28 } else if (brain->hierarchy_sep != sep) {
29 i_fatal("Synced namespaces have conflicting separators "
30 "('%c' for prefix=\"%s\", '%c' for prefix=\"%s\")",
31 brain->hierarchy_sep, first_ns->prefix,
32 sep, ns->prefix);
33 }
34 }
35 if (brain->hierarchy_sep != '\0')
36 return;
37
38 i_fatal("All your namespaces have a location setting. "
39 "Only namespaces with empty location settings are converted. "
40 "(One namespace should default to mail_location setting)");
41 }
42
dsync_brain_mailbox_trees_init(struct dsync_brain * brain)43 void dsync_brain_mailbox_trees_init(struct dsync_brain *brain)
44 {
45 struct mail_namespace *ns;
46
47 dsync_brain_check_namespaces(brain);
48
49 brain->local_mailbox_tree =
50 dsync_mailbox_tree_init(brain->hierarchy_sep, brain->alt_char);
51 /* we'll convert remote mailbox names to use our own separator */
52 brain->remote_mailbox_tree =
53 dsync_mailbox_tree_init(brain->hierarchy_sep, brain->alt_char);
54
55 /* fill the local mailbox tree */
56 for (ns = brain->user->namespaces; ns != NULL; ns = ns->next) {
57 if (!dsync_brain_want_namespace(brain, ns))
58 continue;
59 if (brain->debug)
60 i_debug("brain %c: Namespace %s has location %s",
61 brain->master_brain ? 'M' : 'S',
62 ns->prefix, ns->set->location);
63 if (dsync_mailbox_tree_fill(brain->local_mailbox_tree, ns,
64 brain->sync_box,
65 brain->sync_box_guid,
66 brain->exclude_mailboxes,
67 &brain->mail_error) < 0) {
68 brain->failed = TRUE;
69 break;
70 }
71 }
72
73 brain->local_tree_iter =
74 dsync_mailbox_tree_iter_init(brain->local_mailbox_tree);
75 }
76
77
dsync_brain_send_mailbox_tree(struct dsync_brain * brain)78 void dsync_brain_send_mailbox_tree(struct dsync_brain *brain)
79 {
80 struct dsync_mailbox_node *node;
81 enum dsync_ibc_send_ret ret;
82 const char *full_name;
83 char sep[2];
84
85 sep[0] = brain->hierarchy_sep; sep[1] = '\0';
86 while (dsync_mailbox_tree_iter_next(brain->local_tree_iter,
87 &full_name, &node)) {
88 if (node->ns == NULL) {
89 /* This node was created when adding a namespace prefix
90 to the tree that has multiple hierarchical names,
91 but the parent names don't belong to any synced
92 namespace. For example when syncing "-n Shared/user/"
93 so "Shared/" is skipped. Or if there is e.g.
94 "Public/files/" namespace prefix, but no "Public/"
95 namespace at all. */
96 continue;
97 }
98
99 T_BEGIN {
100 const char *const *parts;
101
102 if (brain->debug) {
103 i_debug("brain %c: Local mailbox tree: %s %s",
104 brain->master_brain ? 'M' : 'S', full_name,
105 dsync_mailbox_node_to_string(node));
106 }
107
108 /* Avoid sending out mailbox names with escape
109 characters. Especially when dsync is used for
110 migration, we don't want to end up having invalid
111 mUTF7 mailbox names locally. Also, remote might not
112 even be configured to use the same escape
113 character. */
114 if (node->ns != NULL) {
115 i_assert(brain->alt_char != '\0');
116 full_name = t_str_replace(full_name,
117 node->ns->list->set.vname_escape_char,
118 brain->alt_char);
119 }
120 parts = t_strsplit(full_name, sep);
121 ret = dsync_ibc_send_mailbox_tree_node(brain->ibc,
122 parts, node);
123 } T_END;
124 if (ret == DSYNC_IBC_SEND_RET_FULL)
125 return;
126 }
127 dsync_mailbox_tree_iter_deinit(&brain->local_tree_iter);
128 dsync_ibc_send_end_of_list(brain->ibc, DSYNC_IBC_EOL_MAILBOX_TREE);
129
130 brain->state = DSYNC_STATE_SEND_MAILBOX_TREE_DELETES;
131 }
132
dsync_brain_send_mailbox_tree_deletes(struct dsync_brain * brain)133 void dsync_brain_send_mailbox_tree_deletes(struct dsync_brain *brain)
134 {
135 const struct dsync_mailbox_delete *deletes;
136 unsigned int count;
137
138 deletes = dsync_mailbox_tree_get_deletes(brain->local_mailbox_tree,
139 &count);
140 dsync_ibc_send_mailbox_deletes(brain->ibc, deletes, count,
141 brain->hierarchy_sep);
142
143 brain->state = DSYNC_STATE_RECV_MAILBOX_TREE;
144 }
145
146 static bool
dsync_namespace_match_parts(struct mail_namespace * ns,const char * const * name_parts)147 dsync_namespace_match_parts(struct mail_namespace *ns,
148 const char *const *name_parts)
149 {
150 const char *part, *prefix = ns->prefix;
151 size_t part_len;
152 char ns_sep = mail_namespace_get_sep(ns);
153
154 if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
155 strcmp(name_parts[0], "INBOX") == 0 && name_parts[1] == NULL)
156 return TRUE;
157
158 for (; *name_parts != NULL && *prefix != '\0'; name_parts++) {
159 part = *name_parts;
160 part_len = strlen(part);
161
162 if (!str_begins(prefix, part))
163 return FALSE;
164 if (prefix[part_len] != ns_sep)
165 return FALSE;
166 prefix += part_len + 1;
167 }
168 if (*name_parts != NULL) {
169 /* namespace prefix found with a mailbox */
170 return TRUE;
171 }
172 if (*prefix == '\0') {
173 /* namespace prefix itself matched */
174 return TRUE;
175 }
176 return FALSE;
177 }
178
179 static struct mail_namespace *
dsync_find_namespace(struct dsync_brain * brain,const char * const * name_parts)180 dsync_find_namespace(struct dsync_brain *brain, const char *const *name_parts)
181 {
182 struct mail_namespace *ns, *best_ns = NULL;
183
184 for (ns = brain->user->namespaces; ns != NULL; ns = ns->next) {
185 if (!dsync_brain_want_namespace(brain, ns))
186 continue;
187
188 if (ns->prefix_len == 0) {
189 /* prefix="" is the fallback namespace */
190 if (best_ns == NULL)
191 best_ns = ns;
192 } else if (dsync_namespace_match_parts(ns, name_parts)) {
193 if (best_ns == NULL ||
194 best_ns->prefix_len < ns->prefix_len)
195 best_ns = ns;
196 }
197 }
198 return best_ns;
199 }
200
201 static bool
dsync_is_valid_name(struct mail_namespace * ns,const char * vname)202 dsync_is_valid_name(struct mail_namespace *ns, const char *vname)
203 {
204 struct mailbox *box;
205 bool ret;
206
207 box = mailbox_alloc(ns->list, vname, MAILBOX_FLAG_READONLY);
208 ret = mailbox_verify_create_name(box) == 0;
209 mailbox_free(&box);
210 return ret;
211 }
212
213 static void
dsync_fix_mailbox_name(struct mail_namespace * ns,string_t * vname_str,char alt_char)214 dsync_fix_mailbox_name(struct mail_namespace *ns, string_t *vname_str,
215 char alt_char)
216 {
217 const char *old_vname;
218 char *vname, list_sep = mailbox_list_get_hierarchy_sep(ns->list);
219 guid_128_t guid;
220 unsigned int i, start_pos;
221
222 vname = str_c_modifiable(vname_str);
223 if (strncmp(vname, ns->prefix, ns->prefix_len) == 0)
224 start_pos = ns->prefix_len;
225 else
226 start_pos = 0;
227
228 /* replace control chars */
229 for (i = start_pos; vname[i] != '\0'; i++) {
230 if ((unsigned char)vname[i] < ' ')
231 vname[i] = alt_char;
232 }
233 /* make it valid UTF8 */
234 if (!uni_utf8_str_is_valid(vname)) {
235 old_vname = t_strdup(vname + start_pos);
236 str_truncate(vname_str, start_pos);
237 if (uni_utf8_get_valid_data((const void *)old_vname,
238 strlen(old_vname), vname_str))
239 i_unreached();
240 vname = str_c_modifiable(vname_str);
241 }
242 if (dsync_is_valid_name(ns, vname))
243 return;
244
245 /* 1) change any real separators to alt separators (this wouldn't
246 be necessary with listescape, but don't bother detecting it) */
247 if (list_sep != mail_namespace_get_sep(ns)) {
248 for (i = start_pos; vname[i] != '\0'; i++) {
249 if (vname[i] == list_sep)
250 vname[i] = alt_char;
251 }
252 if (dsync_is_valid_name(ns, vname))
253 return;
254 }
255 /* 2) '/' characters aren't valid without listescape */
256 if (mail_namespace_get_sep(ns) != '/' && list_sep != '/') {
257 for (i = start_pos; vname[i] != '\0'; i++) {
258 if (vname[i] == '/')
259 vname[i] = alt_char;
260 }
261 if (dsync_is_valid_name(ns, vname))
262 return;
263 }
264 /* 3) probably some reserved name (e.g. dbox-Mails) */
265 str_insert(vname_str, ns->prefix_len, "_");
266 if (dsync_is_valid_name(ns, str_c(vname_str)))
267 return;
268
269 /* 4) name is too long? just give up and generate a unique name */
270 guid_128_generate(guid);
271 str_truncate(vname_str, 0);
272 str_append(vname_str, ns->prefix);
273 str_append(vname_str, guid_128_to_string(guid));
274 i_assert(dsync_is_valid_name(ns, str_c(vname_str)));
275 }
276
277 static int
dsync_get_mailbox_name(struct dsync_brain * brain,const char * const * name_parts,const char ** name_r,struct mail_namespace ** ns_r)278 dsync_get_mailbox_name(struct dsync_brain *brain, const char *const *name_parts,
279 const char **name_r, struct mail_namespace **ns_r)
280 {
281 struct mail_namespace *ns;
282 const char *p;
283 string_t *vname;
284 char ns_sep;
285
286 i_assert(*name_parts != NULL);
287
288 ns = dsync_find_namespace(brain, name_parts);
289 if (ns == NULL)
290 return -1;
291 ns_sep = mail_namespace_get_sep(ns);
292
293 /* build the mailbox name */
294 vname = t_str_new(128);
295 for (; *name_parts != NULL; name_parts++) {
296 for (p = *name_parts; *p != '\0'; p++) {
297 if (*p != ns_sep)
298 str_append_c(vname, *p);
299 else
300 str_append_c(vname, brain->alt_char);
301 }
302 str_append_c(vname, ns_sep);
303 }
304 str_truncate(vname, str_len(vname)-1);
305
306 dsync_fix_mailbox_name(ns, vname, brain->alt_char);
307 *name_r = str_c(vname);
308 *ns_r = ns;
309 return 0;
310 }
311
dsync_brain_mailbox_trees_sync(struct dsync_brain * brain)312 static void dsync_brain_mailbox_trees_sync(struct dsync_brain *brain)
313 {
314 struct dsync_mailbox_tree_sync_ctx *ctx;
315 const struct dsync_mailbox_tree_sync_change *change;
316 enum dsync_mailbox_trees_sync_type sync_type;
317 enum dsync_mailbox_trees_sync_flags sync_flags =
318 (brain->debug ? DSYNC_MAILBOX_TREES_SYNC_FLAG_DEBUG : 0) |
319 (brain->master_brain ? DSYNC_MAILBOX_TREES_SYNC_FLAG_MASTER_BRAIN : 0) |
320 (brain->no_mailbox_renames ? DSYNC_MAILBOX_TREES_SYNC_FLAG_NO_RENAMES : 0);
321 int ret;
322
323 if (brain->no_backup_overwrite)
324 sync_type = DSYNC_MAILBOX_TREES_SYNC_TYPE_TWOWAY;
325 else if (brain->backup_send)
326 sync_type = DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_LOCAL;
327 else if (brain->backup_recv)
328 sync_type = DSYNC_MAILBOX_TREES_SYNC_TYPE_PRESERVE_REMOTE;
329 else
330 sync_type = DSYNC_MAILBOX_TREES_SYNC_TYPE_TWOWAY;
331
332 ctx = dsync_mailbox_trees_sync_init(brain->local_mailbox_tree,
333 brain->remote_mailbox_tree,
334 sync_type, sync_flags);
335 while ((change = dsync_mailbox_trees_sync_next(ctx)) != NULL) {
336 T_BEGIN {
337 ret = dsync_brain_mailbox_tree_sync_change(
338 brain, change, &brain->mail_error);
339 } T_END;
340 if (ret < 0) {
341 brain->failed = TRUE;
342 break;
343 }
344 }
345 if (dsync_mailbox_trees_sync_deinit(&ctx) < 0)
346 brain->failed = TRUE;
347 }
348
349 static int
dsync_brain_recv_mailbox_tree_add(struct dsync_brain * brain,const char * const * parts,const struct dsync_mailbox_node * remote_node,const char * sep)350 dsync_brain_recv_mailbox_tree_add(struct dsync_brain *brain,
351 const char *const *parts,
352 const struct dsync_mailbox_node *remote_node,
353 const char *sep)
354 {
355 struct dsync_mailbox_node *node;
356 struct mail_namespace *ns;
357 const char *name;
358
359 if (dsync_get_mailbox_name(brain, parts, &name, &ns) < 0)
360 return -1;
361 if (brain->debug) {
362 i_debug("brain %c: Remote mailbox tree: %s %s",
363 brain->master_brain ? 'M' : 'S',
364 t_strarray_join(parts, sep),
365 dsync_mailbox_node_to_string(remote_node));
366 }
367 node = dsync_mailbox_tree_get(brain->remote_mailbox_tree, name);
368 node->ns = ns;
369 dsync_mailbox_node_copy_data(node, remote_node);
370 return 0;
371 }
372
dsync_brain_recv_mailbox_tree(struct dsync_brain * brain)373 bool dsync_brain_recv_mailbox_tree(struct dsync_brain *brain)
374 {
375 const struct dsync_mailbox_node *remote_node;
376 struct dsync_mailbox_node *dup_node1, *dup_node2;
377 const char *const *parts;
378 enum dsync_ibc_recv_ret ret;
379 int ret2;
380 char sep[2];
381 bool changed = FALSE;
382
383 sep[0] = brain->hierarchy_sep; sep[1] = '\0';
384 while ((ret = dsync_ibc_recv_mailbox_tree_node(brain->ibc, &parts,
385 &remote_node)) > 0) {
386 T_BEGIN {
387 ret2 = dsync_brain_recv_mailbox_tree_add(
388 brain, parts, remote_node, sep);
389 } T_END;
390 if (ret2 < 0) {
391 i_error("Couldn't find namespace for mailbox %s",
392 t_strarray_join(parts, sep));
393 brain->failed = TRUE;
394 return TRUE;
395 }
396 }
397 if (ret != DSYNC_IBC_RECV_RET_FINISHED)
398 return changed;
399
400 if (dsync_mailbox_tree_build_guid_hash(brain->remote_mailbox_tree,
401 &dup_node1, &dup_node2) < 0) {
402 i_error("Remote sent duplicate mailbox GUID %s for mailboxes %s and %s",
403 guid_128_to_string(dup_node1->mailbox_guid),
404 dsync_mailbox_node_get_full_name(brain->remote_mailbox_tree,
405 dup_node1),
406 dsync_mailbox_node_get_full_name(brain->remote_mailbox_tree,
407 dup_node2));
408 brain->failed = TRUE;
409 }
410
411 brain->state = DSYNC_STATE_RECV_MAILBOX_TREE_DELETES;
412 return TRUE;
413 }
414
415 static void
dsync_brain_mailbox_tree_add_delete(struct dsync_mailbox_tree * tree,struct dsync_mailbox_tree * other_tree,const struct dsync_mailbox_delete * other_del,const struct dsync_mailbox_node ** node_r,const char ** status_r)416 dsync_brain_mailbox_tree_add_delete(struct dsync_mailbox_tree *tree,
417 struct dsync_mailbox_tree *other_tree,
418 const struct dsync_mailbox_delete *other_del,
419 const struct dsync_mailbox_node **node_r,
420 const char **status_r)
421 {
422 const struct dsync_mailbox_node *node;
423 struct dsync_mailbox_node *other_node, *old_node;
424 const char *name;
425
426 /* see if we can find the deletion based on mailbox tree that should
427 still have the mailbox */
428 node = *node_r = dsync_mailbox_tree_find_delete(tree, other_del);
429 if (node == NULL) {
430 *status_r = "not found";
431 return;
432 }
433
434 switch (other_del->type) {
435 case DSYNC_MAILBOX_DELETE_TYPE_MAILBOX:
436 /* mailbox is always deleted */
437 break;
438 case DSYNC_MAILBOX_DELETE_TYPE_DIR:
439 if (other_del->timestamp <= node->last_renamed_or_created) {
440 /* we don't want to delete this directory, we already
441 have a newer timestamp for it */
442 *status_r = "keep directory, we have a newer timestamp";
443 return;
444 }
445 break;
446 case DSYNC_MAILBOX_DELETE_TYPE_UNSUBSCRIBE:
447 if (other_del->timestamp <= node->last_subscription_change) {
448 /* we don't want to unsubscribe, since we already have
449 a newer subscription timestamp */
450 *status_r = "keep subscription, we have a newer timestamp";
451 return;
452 }
453 break;
454 }
455
456 /* make a node for it in the other mailbox tree */
457 name = dsync_mailbox_node_get_full_name(tree, node);
458 other_node = dsync_mailbox_tree_get(other_tree, name);
459
460 if (other_node->existence == DSYNC_MAILBOX_NODE_EXISTS &&
461 (!guid_128_is_empty(other_node->mailbox_guid) ||
462 other_del->type != DSYNC_MAILBOX_DELETE_TYPE_MAILBOX)) {
463 /* other side has already created a new mailbox or
464 directory with this name, we can't delete it */
465 *status_r = "name has already been recreated";
466 return;
467 }
468
469 /* ok, mark the other node deleted */
470 if (other_del->type == DSYNC_MAILBOX_DELETE_TYPE_MAILBOX) {
471 memcpy(other_node->mailbox_guid, node->mailbox_guid,
472 sizeof(other_node->mailbox_guid));
473 }
474 if (other_node->ns != node->ns && other_node->ns != NULL) {
475 /* namespace mismatch for this node. this shouldn't happen
476 normally, but especially during some misconfigurations it's
477 possible that one side has created mailboxes that conflict
478 with another namespace's prefix. since we're here because
479 one of the mailboxes was deleted, we'll just ignore this. */
480 *status_r = "namespace mismatch";
481 return;
482 }
483 other_node->ns = node->ns;
484 if (other_del->type != DSYNC_MAILBOX_DELETE_TYPE_UNSUBSCRIBE) {
485 other_node->existence = DSYNC_MAILBOX_NODE_DELETED;
486 *status_r = "marked as deleted";
487 } else {
488 other_node->last_subscription_change = other_del->timestamp;
489 other_node->subscribed = FALSE;
490 *status_r = "marked as unsubscribed";
491 }
492
493 if (dsync_mailbox_tree_guid_hash_add(other_tree, other_node,
494 &old_node) < 0)
495 i_unreached();
496 }
497
dsync_brain_recv_mailbox_tree_deletes(struct dsync_brain * brain)498 bool dsync_brain_recv_mailbox_tree_deletes(struct dsync_brain *brain)
499 {
500 const struct dsync_mailbox_node *node;
501 const char *status;
502 const struct dsync_mailbox_delete *deletes;
503 unsigned int i, count;
504 char sep;
505
506 if (dsync_ibc_recv_mailbox_deletes(brain->ibc, &deletes, &count,
507 &sep) == 0)
508 return FALSE;
509
510 /* apply remote's mailbox deletions based on our local tree */
511 dsync_mailbox_tree_set_remote_sep(brain->local_mailbox_tree, sep);
512 for (i = 0; i < count; i++) {
513 dsync_brain_mailbox_tree_add_delete(brain->local_mailbox_tree,
514 brain->remote_mailbox_tree,
515 &deletes[i], &node, &status);
516 if (brain->debug) {
517 const char *node_name = node == NULL ? "" :
518 dsync_mailbox_node_get_full_name(brain->local_mailbox_tree, node);
519 i_debug("brain %c: Remote mailbox tree deletion: guid=%s type=%s timestamp=%ld name=%s local update=%s",
520 brain->master_brain ? 'M' : 'S',
521 guid_128_to_string(deletes[i].guid),
522 dsync_mailbox_delete_type_to_string(deletes[i].type),
523 deletes[i].timestamp, node_name, status);
524 }
525 }
526
527 /* apply local mailbox deletions based on remote tree */
528 deletes = dsync_mailbox_tree_get_deletes(brain->local_mailbox_tree,
529 &count);
530 dsync_mailbox_tree_set_remote_sep(brain->remote_mailbox_tree,
531 brain->hierarchy_sep);
532 for (i = 0; i < count; i++) {
533 dsync_brain_mailbox_tree_add_delete(brain->remote_mailbox_tree,
534 brain->local_mailbox_tree,
535 &deletes[i], &node, &status);
536 }
537
538 dsync_brain_mailbox_trees_sync(brain);
539 brain->state = brain->master_brain ?
540 DSYNC_STATE_MASTER_SEND_MAILBOX :
541 DSYNC_STATE_SLAVE_RECV_MAILBOX;
542 i_assert(brain->local_tree_iter == NULL);
543 brain->local_tree_iter =
544 dsync_mailbox_tree_iter_init(brain->local_mailbox_tree);
545 return TRUE;
546 }
547