summaryrefslogtreecommitdiffstats
path: root/src/doveadm/dsync/dsync-mailbox-tree-fill.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/doveadm/dsync/dsync-mailbox-tree-fill.c405
1 files changed, 405 insertions, 0 deletions
diff --git a/src/doveadm/dsync/dsync-mailbox-tree-fill.c b/src/doveadm/dsync/dsync-mailbox-tree-fill.c
new file mode 100644
index 0000000..c523e6b
--- /dev/null
+++ b/src/doveadm/dsync/dsync-mailbox-tree-fill.c
@@ -0,0 +1,405 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "guid.h"
+#include "str.h"
+#include "wildcard-match.h"
+#include "mailbox-log.h"
+#include "mail-namespace.h"
+#include "mail-storage.h"
+#include "mailbox-list-iter.h"
+#include "dsync-mailbox-tree-private.h"
+
+static int
+dsync_mailbox_tree_add_node(struct dsync_mailbox_tree *tree,
+ const struct mailbox_info *info,
+ struct dsync_mailbox_node **node_r)
+{
+ struct dsync_mailbox_node *node;
+
+ node = dsync_mailbox_tree_get(tree, info->vname);
+ if (node->ns == info->ns)
+ ;
+ else if (node->ns == NULL) {
+ i_assert(tree->root.ns == NULL);
+ node->ns = info->ns;
+ } else {
+ i_error("Mailbox '%s' exists in two namespaces: '%s' and '%s'",
+ info->vname, node->ns->prefix, info->ns->prefix);
+ return -1;
+ }
+ *node_r = node;
+ return 0;
+}
+
+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)
+{
+ if (dsync_mailbox_tree_add_node(tree, info, node_r) < 0) {
+ *error_r = MAIL_ERROR_TEMP;
+ return -1;
+ }
+ (*node_r)->existence = DSYNC_MAILBOX_NODE_EXISTS;
+ return 0;
+}
+
+static int
+dsync_mailbox_tree_get_selectable(struct mailbox *box,
+ struct mailbox_metadata *metadata_r,
+ struct mailbox_status *status_r)
+{
+ /* try the fast path */
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, metadata_r) < 0)
+ return -1;
+ if (mailbox_get_status(box, STATUS_UIDVALIDITY | STATUS_UIDNEXT, status_r) < 0)
+ return -1;
+
+ i_assert(!guid_128_is_empty(metadata_r->guid));
+ if (status_r->uidvalidity != 0)
+ return 0;
+
+ /* no UIDVALIDITY assigned yet. syncing a mailbox should add it. */
+ if (mailbox_sync(box, 0) < 0)
+ return -1;
+ if (mailbox_get_status(box, STATUS_UIDVALIDITY | STATUS_UIDNEXT, status_r) < 0)
+ return -1;
+ i_assert(status_r->uidvalidity != 0);
+ return 0;
+}
+
+static int 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)
+{
+ struct dsync_mailbox_node *node;
+ struct mailbox *box;
+ struct mailbox_metadata metadata;
+ struct mailbox_status status;
+ const char *errstr;
+ enum mail_error error;
+ int ret = 0;
+
+ if ((info->flags & MAILBOX_NONEXISTENT) != 0)
+ return 0;
+ if ((info->flags & MAILBOX_NOSELECT) != 0) {
+ return !guid_128_is_empty(box_guid) ? 0 :
+ dsync_mailbox_tree_add_exists_node(tree, info, &node, error_r);
+ }
+
+ /* get GUID and UIDVALIDITY for selectable mailbox */
+ box = mailbox_alloc(info->ns->list, info->vname, MAILBOX_FLAG_READONLY);
+ if (dsync_mailbox_tree_get_selectable(box, &metadata, &status) < 0) {
+ errstr = mailbox_get_last_internal_error(box, &error);
+ switch (error) {
+ case MAIL_ERROR_NOTFOUND:
+ /* mailbox was just deleted? */
+ break;
+ case MAIL_ERROR_NOTPOSSIBLE:
+ /* invalid mbox files? ignore */
+ break;
+ default:
+ i_error("Failed to access mailbox %s: %s",
+ info->vname, errstr);
+ *error_r = error;
+ ret = -1;
+ }
+ mailbox_free(&box);
+ return ret;
+ }
+ mailbox_free(&box);
+
+ if (!guid_128_is_empty(box_guid) &&
+ !guid_128_equals(box_guid, metadata.guid)) {
+ /* unwanted mailbox */
+ return 0;
+ }
+ if (dsync_mailbox_tree_add_exists_node(tree, info, &node, error_r) < 0)
+ return -1;
+ memcpy(node->mailbox_guid, metadata.guid,
+ sizeof(node->mailbox_guid));
+ node->uid_validity = status.uidvalidity;
+ node->uid_next = status.uidnext;
+ return 0;
+}
+
+static struct dsync_mailbox_node *
+dsync_mailbox_tree_find_sha(struct dsync_mailbox_tree *tree,
+ struct mail_namespace *ns, const guid_128_t sha128)
+{
+ struct dsync_mailbox_node *node;
+
+ if (!hash_table_is_created(tree->name128_hash))
+ dsync_mailbox_tree_build_name128_hash(tree);
+
+ node = hash_table_lookup(tree->name128_hash, sha128);
+ return node == NULL || node->ns != ns ? NULL : node;
+}
+
+static int
+dsync_mailbox_tree_add_change_timestamps(struct dsync_mailbox_tree *tree,
+ struct mail_namespace *ns)
+{
+ struct dsync_mailbox_node *node;
+ struct dsync_mailbox_delete *del;
+ struct mailbox_log *log;
+ struct mailbox_log_iter *iter;
+ const struct mailbox_log_record *rec;
+ const uint8_t *guid_p;
+ time_t timestamp;
+
+ log = mailbox_list_get_changelog(ns->list);
+ if (log == NULL)
+ return 0;
+
+ iter = mailbox_log_iter_init(log);
+ while ((rec = mailbox_log_iter_next(iter)) != NULL) {
+ /* For DELETE_MAILBOX the record_guid is the mailbox GUID.
+ Otherwise it's 128bit SHA1 of the mailbox vname. */
+ node = rec->type == MAILBOX_LOG_RECORD_DELETE_MAILBOX ? NULL :
+ dsync_mailbox_tree_find_sha(tree, ns, rec->mailbox_guid);
+
+ timestamp = mailbox_log_record_get_timestamp(rec);
+ switch (rec->type) {
+ case MAILBOX_LOG_RECORD_DELETE_MAILBOX:
+ guid_p = rec->mailbox_guid;
+ if (hash_table_lookup(tree->guid_hash, guid_p) != NULL) {
+ /* mailbox still exists. maybe it was restored
+ from backup or something. */
+ break;
+ }
+ del = array_append_space(&tree->deletes);
+ del->type = DSYNC_MAILBOX_DELETE_TYPE_MAILBOX;
+ del->timestamp = timestamp;
+ memcpy(del->guid, rec->mailbox_guid, sizeof(del->guid));
+ break;
+ case MAILBOX_LOG_RECORD_DELETE_DIR:
+ if (node != NULL &&
+ node->existence == DSYNC_MAILBOX_NODE_EXISTS) {
+ /* directory exists again, skip it */
+ break;
+ }
+ /* we don't know what directory name was deleted,
+ just its hash. if the name still exists on the other
+ dsync side, it can match this deletion to the
+ name. */
+ del = array_append_space(&tree->deletes);
+ del->type = DSYNC_MAILBOX_DELETE_TYPE_DIR;
+ del->timestamp = timestamp;
+ memcpy(del->guid, rec->mailbox_guid, sizeof(del->guid));
+ break;
+ case MAILBOX_LOG_RECORD_CREATE_DIR:
+ if (node == NULL) {
+ /* directory has been deleted again, skip it */
+ break;
+ }
+ /* notify the remote that we want to keep this
+ directory created (unless remote has a newer delete
+ timestamp) */
+ node->last_renamed_or_created = timestamp;
+ break;
+ case MAILBOX_LOG_RECORD_RENAME:
+ if (node != NULL)
+ node->last_renamed_or_created = timestamp;
+ break;
+ case MAILBOX_LOG_RECORD_SUBSCRIBE:
+ if (node != NULL)
+ node->last_subscription_change = timestamp;
+ break;
+ case MAILBOX_LOG_RECORD_UNSUBSCRIBE:
+ if (node != NULL) {
+ node->last_subscription_change = timestamp;
+ break;
+ }
+ /* The mailbox is already deleted, but it may still
+ exist on the other side (even the subscription
+ alone). */
+ del = array_append_space(&tree->deletes);
+ del->type = DSYNC_MAILBOX_DELETE_TYPE_UNSUBSCRIBE;
+ del->timestamp = timestamp;
+ memcpy(del->guid, rec->mailbox_guid, sizeof(del->guid));
+ break;
+ }
+ }
+ if (mailbox_log_iter_deinit(&iter) < 0) {
+ i_error("Mailbox log iteration for namespace '%s' failed",
+ ns->prefix);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+dsync_mailbox_tree_fix_guid_duplicate(struct dsync_mailbox_tree *tree,
+ struct dsync_mailbox_node *node1,
+ struct dsync_mailbox_node *node2)
+{
+ struct mailbox *box;
+ struct mailbox_update update;
+ struct dsync_mailbox_node *change_node;
+ const char *change_vname;
+ int ret = 0;
+
+ i_zero(&update);
+ guid_128_generate(update.mailbox_guid);
+
+ /* just in case the duplication exists in both sides,
+ make them choose the same node */
+ if (strcmp(dsync_mailbox_node_get_full_name(tree, node1),
+ dsync_mailbox_node_get_full_name(tree, node2)) <= 0)
+ change_node = node1;
+ else
+ change_node = node2;
+
+ change_vname = dsync_mailbox_node_get_full_name(tree, change_node);
+ i_error("Duplicate mailbox GUID %s for mailboxes %s and %s - "
+ "giving a new GUID %s to %s",
+ guid_128_to_string(node1->mailbox_guid),
+ dsync_mailbox_node_get_full_name(tree, node1),
+ dsync_mailbox_node_get_full_name(tree, node2),
+ guid_128_to_string(update.mailbox_guid), change_vname);
+
+ i_assert(node1->ns != NULL && node2->ns != NULL);
+ box = mailbox_alloc(change_node->ns->list, change_vname, 0);
+ if (mailbox_update(box, &update) < 0) {
+ i_error("Couldn't update mailbox %s GUID: %s",
+ change_vname, mailbox_get_last_internal_error(box, NULL));
+ ret = -1;
+ } else {
+ memcpy(change_node->mailbox_guid, update.mailbox_guid,
+ sizeof(change_node->mailbox_guid));
+ }
+ mailbox_free(&box);
+ return ret;
+}
+
+static bool
+dsync_mailbox_info_is_wanted(const struct mailbox_info *info,
+ const char *box_name,
+ const char *const *exclude_mailboxes)
+{
+ const char *const *info_specialuses;
+ unsigned int i;
+
+ if (exclude_mailboxes == NULL &&
+ (box_name == NULL || box_name[0] != '\\'))
+ return TRUE;
+
+ info_specialuses = info->special_use == NULL ? NULL :
+ t_strsplit(info->special_use, " ");
+ /* include */
+ if (box_name != NULL && box_name[0] == '\\') {
+ if (info_specialuses == NULL ||
+ !str_array_icase_find(info_specialuses, box_name))
+ return FALSE;
+ }
+ /* exclude */
+ if (exclude_mailboxes == NULL)
+ return TRUE;
+ for (i = 0; exclude_mailboxes[i] != NULL; i++) {
+ const char *exclude = exclude_mailboxes[i];
+
+ if (exclude[0] == '\\') {
+ /* special-use */
+ if (info_specialuses != NULL &&
+ str_array_icase_find(info_specialuses, exclude))
+ return FALSE;
+ } else {
+ /* mailbox with wildcards */
+ if (wildcard_match(info->vname, exclude))
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+int 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)
+{
+ const enum mailbox_list_iter_flags list_flags =
+ /* FIXME: we'll skip symlinks, because we can't handle them
+ currently. in future we could detect them and create them
+ by creating the symlink. */
+ MAILBOX_LIST_ITER_SKIP_ALIASES |
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES;
+ const enum mailbox_list_iter_flags subs_list_flags =
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES |
+ MAILBOX_LIST_ITER_SELECT_SUBSCRIBED |
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS;
+ struct mailbox_list_iterate_context *iter;
+ struct dsync_mailbox_node *node, *dup_node1, *dup_node2;
+ const struct mailbox_info *info;
+ const char *list_pattern =
+ box_name != NULL && box_name[0] != '\\' ? box_name : "*";
+ int ret = 0;
+
+ i_assert(mail_namespace_get_sep(ns) == tree->sep);
+
+ /* assign namespace to its root, so it gets copied to children */
+ if (ns->prefix_len > 0) {
+ const char *vname = t_strndup(ns->prefix, ns->prefix_len-1);
+ node = dsync_mailbox_tree_get(tree, vname);
+ node->ns = ns;
+
+ struct mailbox_info ns_info = {
+ .vname = vname,
+ .ns = ns,
+ };
+ if (dsync_mailbox_tree_add(tree, &ns_info, box_guid, error_r) < 0)
+ return -1;
+ } else {
+ tree->root.ns = ns;
+ }
+
+ /* first add all of the existing mailboxes */
+ iter = mailbox_list_iter_init(ns->list, list_pattern, list_flags);
+ while ((info = mailbox_list_iter_next(iter)) != NULL) T_BEGIN {
+ if (dsync_mailbox_info_is_wanted(info, box_name,
+ exclude_mailboxes)) {
+ if (dsync_mailbox_tree_add(tree, info, box_guid, error_r) < 0)
+ ret = -1;
+ }
+ } T_END;
+ if (mailbox_list_iter_deinit(&iter) < 0) {
+ i_error("Mailbox listing for namespace '%s' failed: %s",
+ ns->prefix, mailbox_list_get_last_internal_error(ns->list, error_r));
+ ret = -1;
+ }
+
+ /* add subscriptions */
+ iter = mailbox_list_iter_init(ns->list, list_pattern, subs_list_flags);
+ while ((info = mailbox_list_iter_next(iter)) != NULL) {
+ if (dsync_mailbox_tree_add_node(tree, info, &node) == 0)
+ node->subscribed = TRUE;
+ else {
+ *error_r = MAIL_ERROR_TEMP;
+ ret = -1;
+ }
+ }
+ if (mailbox_list_iter_deinit(&iter) < 0) {
+ i_error("Mailbox listing for namespace '%s' failed: %s",
+ ns->prefix, mailbox_list_get_last_internal_error(ns->list, error_r));
+ ret = -1;
+ }
+ if (ret < 0)
+ return -1;
+
+ while (dsync_mailbox_tree_build_guid_hash(tree, &dup_node1,
+ &dup_node2) < 0) {
+ if (dsync_mailbox_tree_fix_guid_duplicate(tree, dup_node1, dup_node2) < 0)
+ return -1;
+ }
+
+ /* add timestamps */
+ if (dsync_mailbox_tree_add_change_timestamps(tree, ns) < 0)
+ return -1;
+ return 0;
+}