1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "array.h"
5 #include "hash.h"
6 #include "guid.h"
7 #include "str.h"
8 #include "wildcard-match.h"
9 #include "mailbox-log.h"
10 #include "mail-namespace.h"
11 #include "mail-storage.h"
12 #include "mailbox-list-iter.h"
13 #include "dsync-mailbox-tree-private.h"
14 
15 static int
dsync_mailbox_tree_add_node(struct dsync_mailbox_tree * tree,const struct mailbox_info * info,struct dsync_mailbox_node ** node_r)16 dsync_mailbox_tree_add_node(struct dsync_mailbox_tree *tree,
17 			    const struct mailbox_info *info,
18 			    struct dsync_mailbox_node **node_r)
19 {
20 	struct dsync_mailbox_node *node;
21 
22 	node = dsync_mailbox_tree_get(tree, info->vname);
23 	if (node->ns == info->ns)
24 		;
25 	else if (node->ns == NULL) {
26 		i_assert(tree->root.ns == NULL);
27 		node->ns = info->ns;
28 	} else {
29 		i_error("Mailbox '%s' exists in two namespaces: '%s' and '%s'",
30 			info->vname, node->ns->prefix, info->ns->prefix);
31 		return -1;
32 	}
33 	*node_r = node;
34 	return 0;
35 }
36 
37 static int
dsync_mailbox_tree_add_exists_node(struct dsync_mailbox_tree * tree,const struct mailbox_info * info,struct dsync_mailbox_node ** node_r,enum mail_error * error_r)38 dsync_mailbox_tree_add_exists_node(struct dsync_mailbox_tree *tree,
39 				   const struct mailbox_info *info,
40 				   struct dsync_mailbox_node **node_r,
41 				   enum mail_error *error_r)
42 {
43 	if (dsync_mailbox_tree_add_node(tree, info, node_r) < 0) {
44 		*error_r = MAIL_ERROR_TEMP;
45 		return -1;
46 	}
47 	(*node_r)->existence = DSYNC_MAILBOX_NODE_EXISTS;
48 	return 0;
49 }
50 
51 static int
dsync_mailbox_tree_get_selectable(struct mailbox * box,struct mailbox_metadata * metadata_r,struct mailbox_status * status_r)52 dsync_mailbox_tree_get_selectable(struct mailbox *box,
53 				  struct mailbox_metadata *metadata_r,
54 				  struct mailbox_status *status_r)
55 {
56 	/* try the fast path */
57 	if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, metadata_r) < 0)
58 		return -1;
59 	if (mailbox_get_status(box, STATUS_UIDVALIDITY | STATUS_UIDNEXT, status_r) < 0)
60 		return -1;
61 
62 	i_assert(!guid_128_is_empty(metadata_r->guid));
63 	if (status_r->uidvalidity != 0)
64 		return 0;
65 
66 	/* no UIDVALIDITY assigned yet. syncing a mailbox should add it. */
67 	if (mailbox_sync(box, 0) < 0)
68 		return -1;
69 	if (mailbox_get_status(box, STATUS_UIDVALIDITY | STATUS_UIDNEXT, status_r) < 0)
70 		return -1;
71 	i_assert(status_r->uidvalidity != 0);
72 	return 0;
73 }
74 
dsync_mailbox_tree_add(struct dsync_mailbox_tree * tree,const struct mailbox_info * info,const guid_128_t box_guid,enum mail_error * error_r)75 static int dsync_mailbox_tree_add(struct dsync_mailbox_tree *tree,
76 				  const struct mailbox_info *info,
77 				  const guid_128_t box_guid,
78 				  enum mail_error *error_r)
79 {
80 	struct dsync_mailbox_node *node;
81 	struct mailbox *box;
82 	struct mailbox_metadata metadata;
83 	struct mailbox_status status;
84 	const char *errstr;
85 	enum mail_error error;
86 	int ret = 0;
87 
88 	if ((info->flags & MAILBOX_NONEXISTENT) != 0)
89 		return 0;
90 	if ((info->flags & MAILBOX_NOSELECT) != 0) {
91 		return !guid_128_is_empty(box_guid) ? 0 :
92 			dsync_mailbox_tree_add_exists_node(tree, info, &node, error_r);
93 	}
94 
95 	/* get GUID and UIDVALIDITY for selectable mailbox */
96 	box = mailbox_alloc(info->ns->list, info->vname, MAILBOX_FLAG_READONLY);
97 	if (dsync_mailbox_tree_get_selectable(box, &metadata, &status) < 0) {
98 		errstr = mailbox_get_last_internal_error(box, &error);
99 		switch (error) {
100 		case MAIL_ERROR_NOTFOUND:
101 			/* mailbox was just deleted? */
102 			break;
103 		case MAIL_ERROR_NOTPOSSIBLE:
104 			/* invalid mbox files? ignore */
105 			break;
106 		default:
107 			i_error("Failed to access mailbox %s: %s",
108 				info->vname, errstr);
109 			*error_r = error;
110 			ret = -1;
111 		}
112 		mailbox_free(&box);
113 		return ret;
114 	}
115 	mailbox_free(&box);
116 
117 	if (!guid_128_is_empty(box_guid) &&
118 	    !guid_128_equals(box_guid, metadata.guid)) {
119 		/* unwanted mailbox */
120 		return 0;
121 	}
122 	if (dsync_mailbox_tree_add_exists_node(tree, info, &node, error_r) < 0)
123 		return -1;
124 	memcpy(node->mailbox_guid, metadata.guid,
125 	       sizeof(node->mailbox_guid));
126 	node->uid_validity = status.uidvalidity;
127 	node->uid_next = status.uidnext;
128 	return 0;
129 }
130 
131 static struct dsync_mailbox_node *
dsync_mailbox_tree_find_sha(struct dsync_mailbox_tree * tree,struct mail_namespace * ns,const guid_128_t sha128)132 dsync_mailbox_tree_find_sha(struct dsync_mailbox_tree *tree,
133 			    struct mail_namespace *ns, const guid_128_t sha128)
134 {
135 	struct dsync_mailbox_node *node;
136 
137 	if (!hash_table_is_created(tree->name128_hash))
138 		dsync_mailbox_tree_build_name128_hash(tree);
139 
140 	node = hash_table_lookup(tree->name128_hash, sha128);
141 	return node == NULL || node->ns != ns ? NULL : node;
142 }
143 
144 static int
dsync_mailbox_tree_add_change_timestamps(struct dsync_mailbox_tree * tree,struct mail_namespace * ns)145 dsync_mailbox_tree_add_change_timestamps(struct dsync_mailbox_tree *tree,
146 					 struct mail_namespace *ns)
147 {
148 	struct dsync_mailbox_node *node;
149 	struct dsync_mailbox_delete *del;
150 	struct mailbox_log *log;
151 	struct mailbox_log_iter *iter;
152 	const struct mailbox_log_record *rec;
153 	const uint8_t *guid_p;
154 	time_t timestamp;
155 
156 	log = mailbox_list_get_changelog(ns->list);
157 	if (log == NULL)
158 		return 0;
159 
160 	iter = mailbox_log_iter_init(log);
161 	while ((rec = mailbox_log_iter_next(iter)) != NULL) {
162 		node = rec->type == MAILBOX_LOG_RECORD_DELETE_MAILBOX ? NULL :
163 			dsync_mailbox_tree_find_sha(tree, ns, rec->mailbox_guid);
164 
165 		timestamp = mailbox_log_record_get_timestamp(rec);
166 		switch (rec->type) {
167 		case MAILBOX_LOG_RECORD_DELETE_MAILBOX:
168 			guid_p = rec->mailbox_guid;
169 			if (hash_table_lookup(tree->guid_hash, guid_p) != NULL) {
170 				/* mailbox still exists. maybe it was restored
171 				   from backup or something. */
172 				break;
173 			}
174 			del = array_append_space(&tree->deletes);
175 			del->type = DSYNC_MAILBOX_DELETE_TYPE_MAILBOX;
176 			del->timestamp = timestamp;
177 			memcpy(del->guid, rec->mailbox_guid, sizeof(del->guid));
178 			break;
179 		case MAILBOX_LOG_RECORD_DELETE_DIR:
180 			if (node != NULL &&
181 			    node->existence == DSYNC_MAILBOX_NODE_EXISTS) {
182 				/* directory exists again, skip it */
183 				break;
184 			}
185 			/* we don't know what directory name was deleted,
186 			   just its hash. if the name still exists on the other
187 			   dsync side, it can match this deletion to the
188 			   name. */
189 			del = array_append_space(&tree->deletes);
190 			del->type = DSYNC_MAILBOX_DELETE_TYPE_DIR;
191 			del->timestamp = timestamp;
192 			memcpy(del->guid, rec->mailbox_guid, sizeof(del->guid));
193 			break;
194 		case MAILBOX_LOG_RECORD_CREATE_DIR:
195 			if (node == NULL) {
196 				/* directory has been deleted again, skip it */
197 				break;
198 			}
199 			/* notify the remote that we want to keep this
200 			   directory created (unless remote has a newer delete
201 			   timestamp) */
202 			node->last_renamed_or_created = timestamp;
203 			break;
204 		case MAILBOX_LOG_RECORD_RENAME:
205 			if (node != NULL)
206 				node->last_renamed_or_created = timestamp;
207 			break;
208 		case MAILBOX_LOG_RECORD_SUBSCRIBE:
209 			if (node != NULL)
210 				node->last_subscription_change = timestamp;
211 			break;
212 		case MAILBOX_LOG_RECORD_UNSUBSCRIBE:
213 			if (node != NULL) {
214 				node->last_subscription_change = timestamp;
215 				break;
216 			}
217 			/* The mailbox is already deleted, but it may still
218 			   exist on the other side (even the subscription
219 			   alone). */
220 			del = array_append_space(&tree->deletes);
221 			del->type = DSYNC_MAILBOX_DELETE_TYPE_UNSUBSCRIBE;
222 			del->timestamp = timestamp;
223 			memcpy(del->guid, rec->mailbox_guid, sizeof(del->guid));
224 			break;
225 		}
226 	}
227 	if (mailbox_log_iter_deinit(&iter) < 0) {
228 		i_error("Mailbox log iteration for namespace '%s' failed",
229 			ns->prefix);
230 		return -1;
231 	}
232 	return 0;
233 }
234 
235 static int
dsync_mailbox_tree_fix_guid_duplicate(struct dsync_mailbox_tree * tree,struct dsync_mailbox_node * node1,struct dsync_mailbox_node * node2)236 dsync_mailbox_tree_fix_guid_duplicate(struct dsync_mailbox_tree *tree,
237 				      struct dsync_mailbox_node *node1,
238 				      struct dsync_mailbox_node *node2)
239 {
240 	struct mailbox *box;
241 	struct mailbox_update update;
242 	struct dsync_mailbox_node *change_node;
243 	const char *change_vname;
244 	int ret = 0;
245 
246 	i_zero(&update);
247 	guid_128_generate(update.mailbox_guid);
248 
249 	/* just in case the duplication exists in both sides,
250 	   make them choose the same node */
251 	if (strcmp(dsync_mailbox_node_get_full_name(tree, node1),
252 		   dsync_mailbox_node_get_full_name(tree, node2)) <= 0)
253 		change_node = node1;
254 	else
255 		change_node = node2;
256 
257 	change_vname = dsync_mailbox_node_get_full_name(tree, change_node);
258 	i_error("Duplicate mailbox GUID %s for mailboxes %s and %s - "
259 		"giving a new GUID %s to %s",
260 		guid_128_to_string(node1->mailbox_guid),
261 		dsync_mailbox_node_get_full_name(tree, node1),
262 		dsync_mailbox_node_get_full_name(tree, node2),
263 		guid_128_to_string(update.mailbox_guid), change_vname);
264 
265 	i_assert(node1->ns != NULL && node2->ns != NULL);
266 	box = mailbox_alloc(change_node->ns->list, change_vname, 0);
267 	if (mailbox_update(box, &update) < 0) {
268 		i_error("Couldn't update mailbox %s GUID: %s",
269 			change_vname, mailbox_get_last_internal_error(box, NULL));
270 		ret = -1;
271 	} else {
272 		memcpy(change_node->mailbox_guid, update.mailbox_guid,
273 		       sizeof(change_node->mailbox_guid));
274 	}
275 	mailbox_free(&box);
276 	return ret;
277 }
278 
279 static bool
dsync_mailbox_info_is_wanted(const struct mailbox_info * info,const char * box_name,const char * const * exclude_mailboxes)280 dsync_mailbox_info_is_wanted(const struct mailbox_info *info,
281 			     const char *box_name,
282 			     const char *const *exclude_mailboxes)
283 {
284 	const char *const *info_specialuses;
285 	unsigned int i;
286 
287 	if (exclude_mailboxes == NULL &&
288 	    (box_name == NULL || box_name[0] != '\\'))
289 		return TRUE;
290 
291 	info_specialuses = info->special_use == NULL ? NULL :
292 		t_strsplit(info->special_use, " ");
293 	/* include */
294 	if (box_name != NULL && box_name[0] == '\\') {
295 		if (info_specialuses == NULL ||
296 		    !str_array_icase_find(info_specialuses, box_name))
297 			return FALSE;
298 	}
299 	/* exclude */
300 	if (exclude_mailboxes == NULL)
301 		return TRUE;
302 	for (i = 0; exclude_mailboxes[i] != NULL; i++) {
303 		const char *exclude = exclude_mailboxes[i];
304 
305 		if (exclude[0] == '\\') {
306 			/* special-use */
307 			if (info_specialuses != NULL &&
308 			    str_array_icase_find(info_specialuses, exclude))
309 				return FALSE;
310 		} else {
311 			/* mailbox with wildcards */
312 			if (wildcard_match(info->vname, exclude))
313 				return FALSE;
314 		}
315 	}
316 	return TRUE;
317 }
318 
dsync_mailbox_tree_fill(struct dsync_mailbox_tree * tree,struct mail_namespace * ns,const char * box_name,const guid_128_t box_guid,const char * const * exclude_mailboxes,enum mail_error * error_r)319 int dsync_mailbox_tree_fill(struct dsync_mailbox_tree *tree,
320 			    struct mail_namespace *ns, const char *box_name,
321 			    const guid_128_t box_guid,
322 			    const char *const *exclude_mailboxes,
323 			    enum mail_error *error_r)
324 {
325 	const enum mailbox_list_iter_flags list_flags =
326 		/* FIXME: we'll skip symlinks, because we can't handle them
327 		   currently. in future we could detect them and create them
328 		   by creating the symlink. */
329 		MAILBOX_LIST_ITER_SKIP_ALIASES |
330 		MAILBOX_LIST_ITER_NO_AUTO_BOXES;
331 	const enum mailbox_list_iter_flags subs_list_flags =
332 		MAILBOX_LIST_ITER_NO_AUTO_BOXES |
333 		MAILBOX_LIST_ITER_SELECT_SUBSCRIBED |
334 		MAILBOX_LIST_ITER_RETURN_NO_FLAGS;
335 	struct mailbox_list_iterate_context *iter;
336 	struct dsync_mailbox_node *node, *dup_node1, *dup_node2;
337 	const struct mailbox_info *info;
338 	const char *list_pattern =
339 		box_name != NULL && box_name[0] != '\\' ? box_name : "*";
340 	int ret = 0;
341 
342 	i_assert(mail_namespace_get_sep(ns) == tree->sep);
343 
344 	/* assign namespace to its root, so it gets copied to children */
345 	if (ns->prefix_len > 0) {
346 		const char *vname = t_strndup(ns->prefix, ns->prefix_len-1);
347 		node = dsync_mailbox_tree_get(tree, vname);
348 		node->ns = ns;
349 
350 		struct mailbox_info ns_info = {
351 			.vname = vname,
352 			.ns = ns,
353 		};
354 		if (dsync_mailbox_tree_add(tree, &ns_info, box_guid, error_r) < 0)
355 			return -1;
356 	} else {
357 		tree->root.ns = ns;
358 	}
359 
360 	/* first add all of the existing mailboxes */
361 	iter = mailbox_list_iter_init(ns->list, list_pattern, list_flags);
362 	while ((info = mailbox_list_iter_next(iter)) != NULL) T_BEGIN {
363 		if (dsync_mailbox_info_is_wanted(info, box_name,
364 						 exclude_mailboxes)) {
365 			if (dsync_mailbox_tree_add(tree, info, box_guid, error_r) < 0)
366 				ret = -1;
367 		}
368 	} T_END;
369 	if (mailbox_list_iter_deinit(&iter) < 0) {
370 		i_error("Mailbox listing for namespace '%s' failed: %s",
371 			ns->prefix, mailbox_list_get_last_internal_error(ns->list, error_r));
372 		ret = -1;
373 	}
374 
375 	/* add subscriptions */
376 	iter = mailbox_list_iter_init(ns->list, list_pattern, subs_list_flags);
377 	while ((info = mailbox_list_iter_next(iter)) != NULL) {
378 		if (dsync_mailbox_tree_add_node(tree, info, &node) == 0)
379 			node->subscribed = TRUE;
380 		else {
381 			*error_r = MAIL_ERROR_TEMP;
382 			ret = -1;
383 		}
384 	}
385 	if (mailbox_list_iter_deinit(&iter) < 0) {
386 		i_error("Mailbox listing for namespace '%s' failed: %s",
387 			ns->prefix, mailbox_list_get_last_internal_error(ns->list, error_r));
388 		ret = -1;
389 	}
390 	if (ret < 0)
391 		return -1;
392 
393 	while (dsync_mailbox_tree_build_guid_hash(tree, &dup_node1,
394 						  &dup_node2) < 0) {
395 		if (dsync_mailbox_tree_fix_guid_duplicate(tree, dup_node1, dup_node2) < 0)
396 			return -1;
397 	}
398 
399 	/* add timestamps */
400 	if (dsync_mailbox_tree_add_change_timestamps(tree, ns) < 0)
401 		return -1;
402 	return 0;
403 }
404