imap-sync.c revision 51327f2489a4e0e615eb9f7d921473cf8512bb79
5a580c3a38ced62d4bcc95b8ac7c4f2935b5d294Timo Sirainen/* Copyright (c) 2002-2010 Dovecot authors, see the included COPYING file */
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
08d6658a4e2ec8104cd1307f6baa75fdb07a24f8Mark Washenberger#include "imap-common.h"
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen#include "str.h"
ff487c974815bdaa2d05a3b834f4c2c841f4cc34Timo Sirainen#include "ostream.h"
66d2db642fe24d555d113ba463e446b038d476efTimo Sirainen#include "mail-storage.h"
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen#include "mail-user.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "imap-quote.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "imap-util.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "imap-sync.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "imap-commands.h"
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainen
4ee00532a265bdfb38539d811fcd12d51210ac35Timo Sirainenstruct client_sync_context {
b321df9603081896b70ec44635af96d674a9839aTimo Sirainen /* if multiple commands are in progress, we may need to wait for them
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen to finish before syncing mailbox. */
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen unsigned int counter;
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen enum mailbox_sync_flags flags;
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen enum imap_sync_flags imap_flags;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen const char *tagline;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen imap_sync_callback_t *callback;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen};
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainenstruct imap_sync_context {
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen struct client *client;
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen struct mailbox *box;
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen enum imap_sync_flags imap_flags;
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen struct mailbox_transaction_context *t;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen struct mailbox_sync_context *sync_ctx;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen struct mail *mail;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen struct mailbox_sync_rec sync_rec;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen ARRAY_TYPE(keywords) tmp_keywords;
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainen ARRAY_TYPE(seq_range) expunges;
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainen uint32_t seq;
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainen
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainen ARRAY_TYPE(seq_range) search_adds, search_removes;
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainen
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainen unsigned int messages_count;
5c99eaa4e3e07ee065580d163240b4ce95b66befTimo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen unsigned int failed:1;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen unsigned int no_newmail:1;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen};
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainenstatic void uids_to_seqs(struct mailbox *box, ARRAY_TYPE(seq_range) *uids)
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen{
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen T_BEGIN {
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen ARRAY_TYPE(seq_range) seqs;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen const struct seq_range *range;
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen uint32_t seq1, seq2;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen t_array_init(&seqs, array_count(uids));
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen array_foreach(uids, range) {
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen mailbox_get_seq_range(box, range->seq1, range->seq2,
7bd72e4deca3cbf757dd1ea298486d9f3bc24226Timo Sirainen &seq1, &seq2);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen /* since we have to notify about expunged messages,
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen we expect that all the referenced UIDs exist */
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen i_assert(seq1 != 0);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen i_assert(seq2 - seq1 == range->seq2 - range->seq1);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainen seq_range_array_add_range(&seqs, seq1, seq2);
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen }
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen /* replace uids with seqs */
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen array_clear(uids);
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen array_append_array(uids, &seqs);
a8e132559a7ebe54c8269d79ce29fa3338c76199Timo Sirainen
ce6c2809b8a1673372a683716566d973efd2f6eeTimo Sirainen } T_END;
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen}
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainenstatic void
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainenimap_sync_send_search_update(struct imap_sync_context *ctx,
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainen const struct imap_search_update *update)
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainen{
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainen string_t *cmd;
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainen
430c0b0c370bebeeceba2e206be76bc134742f41Timo Sirainen mailbox_search_result_sync(update->result, &ctx->search_removes,
ce6c2809b8a1673372a683716566d973efd2f6eeTimo Sirainen &ctx->search_adds);
ce6c2809b8a1673372a683716566d973efd2f6eeTimo Sirainen if (array_count(&ctx->search_adds) == 0 &&
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen array_count(&ctx->search_removes) == 0)
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen return;
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen
ce6c2809b8a1673372a683716566d973efd2f6eeTimo Sirainen cmd = t_str_new(256);
ce6c2809b8a1673372a683716566d973efd2f6eeTimo Sirainen str_append(cmd, "* ESEARCH (TAG ");
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen imap_quote_append_string(cmd, update->tag, FALSE);
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen str_append_c(cmd, ')');
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen if (update->return_uids)
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen str_append(cmd, " UID");
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen else {
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen /* convert to sequences */
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen uids_to_seqs(ctx->client->mailbox, &ctx->search_removes);
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainen uids_to_seqs(ctx->client->mailbox, &ctx->search_adds);
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainen }
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainen
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainen if (array_count(&ctx->search_removes) != 0) {
1479a685cdb1641783ac02ba135450929f5c2658Timo Sirainen str_printfa(cmd, " REMOVEFROM (0 ");
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainen imap_write_seq_range(cmd, &ctx->search_removes);
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainen str_append_c(cmd, ')');
b4f2560c29dacd066ba89e782d95ceed7ac473a3Timo Sirainen }
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen if (array_count(&ctx->search_adds) != 0) {
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen str_printfa(cmd, " ADDTO (0 ");
ac713658d206e8d001fef7c0e36945793f2eb942Timo Sirainen imap_write_seq_range(cmd, &ctx->search_adds);
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen str_append_c(cmd, ')');
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen }
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen str_append(cmd, "\r\n");
ac713658d206e8d001fef7c0e36945793f2eb942Timo Sirainen o_stream_send(ctx->client->output, str_data(cmd), str_len(cmd));
ac713658d206e8d001fef7c0e36945793f2eb942Timo Sirainen}
ac713658d206e8d001fef7c0e36945793f2eb942Timo Sirainen
ac713658d206e8d001fef7c0e36945793f2eb942Timo Sirainenstatic void imap_sync_send_search_updates(struct imap_sync_context *ctx)
ac713658d206e8d001fef7c0e36945793f2eb942Timo Sirainen{
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen const struct imap_search_update *update;
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen
517d1e7142d57299c733b30423e35e7e1f8d01d6Timo Sirainen if (!array_is_created(&ctx->client->search_updates))
517d1e7142d57299c733b30423e35e7e1f8d01d6Timo Sirainen return;
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen if (!array_is_created(&ctx->search_removes)) {
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen i_array_init(&ctx->search_removes, 64);
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen i_array_init(&ctx->search_adds, 128);
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen }
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen array_foreach(&ctx->client->search_updates, update) T_BEGIN {
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen imap_sync_send_search_update(ctx, update);
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen } T_END;
e5acc283bf030b0b5c79ca4e52d315c516a299faPascal Volk}
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainenstruct imap_sync_context *
d368bfd671ae6d04a69eb7f418521d49b8bbf77aTimo Sirainenimap_sync_init(struct client *client, struct mailbox *box,
446e518e4fe86ff40e33543445f4e99edf840a21Timo Sirainen enum imap_sync_flags imap_flags, enum mailbox_sync_flags flags)
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen{
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen struct imap_sync_context *ctx;
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen i_assert(client->mailbox == box);
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen ctx = i_new(struct imap_sync_context, 1);
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen ctx->client = client;
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen ctx->box = box;
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen ctx->imap_flags = imap_flags;
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen
f968e62caa52a8924bd05ebf76ff515b5c18e17bTimo Sirainen /* make sure user can't DoS the system by causing Dovecot to create
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen tons of useless namespaces. */
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen mail_user_drop_useless_namespaces(client->user);
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen ctx->sync_ctx = mailbox_sync_init(box, flags);
5367840b91df098e016f382960c391691c8d33ffTimo Sirainen ctx->t = mailbox_transaction_begin(box, 0);
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen ctx->mail = mail_alloc(ctx->t, MAIL_FETCH_FLAGS, 0);
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen ctx->messages_count = client->messages_count;
f968e62caa52a8924bd05ebf76ff515b5c18e17bTimo Sirainen i_array_init(&ctx->tmp_keywords, client->keywords.announce_count + 8);
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen
f968e62caa52a8924bd05ebf76ff515b5c18e17bTimo Sirainen if ((client->enabled_features & MAILBOX_FEATURE_QRESYNC) != 0) {
f968e62caa52a8924bd05ebf76ff515b5c18e17bTimo Sirainen i_array_init(&ctx->expunges, 128);
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainen /* always send UIDs in FETCH replies */
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen ctx->imap_flags |= IMAP_SYNC_FLAG_SEND_UID;
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen }
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen client_send_mailbox_flags(client, FALSE);
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen /* send search updates the first time after sync is initialized.
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen it now contains expunged messages that must be sent before
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen EXPUNGE replies. */
b24ffea8baa472d9b542e54ed3f9939eefd020adTimo Sirainen imap_sync_send_search_updates(ctx);
4ed1b49d815ec41a5e4b6a23d23e94b958da1923Timo Sirainen return ctx;
4ed1b49d815ec41a5e4b6a23d23e94b958da1923Timo Sirainen}
4ed1b49d815ec41a5e4b6a23d23e94b958da1923Timo Sirainen
a3dd97fb6d92a89c3de0597fed2d4b044c7aeb84Timo Sirainenstatic void
4ed1b49d815ec41a5e4b6a23d23e94b958da1923Timo Sirainenimap_sync_send_highestmodseq(struct imap_sync_context *ctx,
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen const struct mailbox_status *status,
b44650b0f48a4b5f0dc240ed836833a00b643b9fTimo Sirainen const struct mailbox_sync_status *sync_status,
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen struct client_command_context *sync_cmd)
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen{
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen struct client *client = ctx->client;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen uint64_t send_modseq = 0;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen if (sync_status->sync_delayed_expunges &&
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen client->highest_fetch_modseq > client->sync_last_full_modseq) {
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen /* if client updates highest-modseq using returned MODSEQs
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen it loses expunges. try to avoid this by sending it a lower
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen pre-expunge HIGHESTMODSEQ reply. */
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen send_modseq = client->sync_last_full_modseq;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen } else if (!sync_status->sync_delayed_expunges &&
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen status->highest_modseq > client->sync_last_full_modseq &&
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen status->highest_modseq > client->highest_fetch_modseq) {
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen /* we've probably sent some VANISHED or EXISTS replies which
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen increased the highest-modseq. notify the client about
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen this. */
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen send_modseq = status->highest_modseq;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen }
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen if (send_modseq == 0) {
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen /* no sending */
3cf67672fdc87583cb23ce088c95bb5dee60e74dTimo Sirainen } else if (sync_cmd->sync != NULL && /* IDLE doesn't have ->sync */
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen strncmp(sync_cmd->sync->tagline, "OK ", 3) == 0 &&
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen sync_cmd->sync->tagline[3] != '[') {
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen /* modify the tagged reply directly */
0727e38ac12efb8963a339daf56255e2be1f29fcTimo Sirainen sync_cmd->sync->tagline = p_strdup_printf(sync_cmd->pool,
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen "OK [HIGHESTMODSEQ %llu] %s",
8ff9812659728d4166df8e003a1dd3524ae8514eTimo Sirainen (unsigned long long)send_modseq,
966cb0c1aa58578339cea6f79b4a423a851ab074Timo Sirainen sync_cmd->sync->tagline + 3);
966cb0c1aa58578339cea6f79b4a423a851ab074Timo Sirainen } else {
966cb0c1aa58578339cea6f79b4a423a851ab074Timo Sirainen /* send an untagged OK reply */
966cb0c1aa58578339cea6f79b4a423a851ab074Timo Sirainen client_send_line(client, t_strdup_printf(
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen "* OK [HIGHESTMODSEQ %llu] Highest",
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen (unsigned long long)send_modseq));
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen }
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen if (!sync_status->sync_delayed_expunges) {
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen /* no delayed expunges, remember this for future */
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen client->sync_last_full_modseq = status->highest_modseq;
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen }
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen client->highest_fetch_modseq = 0;
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen}
747e77e3ab073a8e9e69c7a3e71b4593c5655d03Timo Sirainen
dd93aba1901a457346990f49c54a738947dc7128Timo Sirainenint imap_sync_deinit(struct imap_sync_context *ctx,
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen struct client_command_context *sync_cmd)
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen{
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen struct client *client = ctx->client;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen struct mailbox_status status;
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen struct mailbox_sync_status sync_status;
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen int ret;
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen mail_free(&ctx->mail);
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen if (array_is_created(&ctx->expunges))
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen array_free(&ctx->expunges);
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen if (mailbox_sync_deinit(&ctx->sync_ctx, &sync_status) < 0 ||
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen ctx->failed) {
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen mailbox_transaction_rollback(&ctx->t);
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen array_free(&ctx->tmp_keywords);
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen i_free(ctx);
04052d7cacaa866a3f00afb4e104fa46c04c1dd7Timo Sirainen return -1;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen }
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen mailbox_get_open_status(ctx->box, STATUS_UIDVALIDITY |
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen STATUS_MESSAGES | STATUS_RECENT |
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen STATUS_HIGHESTMODSEQ, &status);
f3d506e525a720f214020ca0f989a1966b30edaeTimo Sirainen
08aea01ef9a9d20703e0fcf8618e6195c0037a44Timo Sirainen ret = mailbox_transaction_commit(&ctx->t);
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen
f3d506e525a720f214020ca0f989a1966b30edaeTimo Sirainen if (status.uidvalidity != client->uidvalidity) {
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen /* most clients would get confused by this. disconnect them. */
e4d34f2fbee451219599d71505594df704093ce3Timo Sirainen client_disconnect_with_error(client,
849969f639a00eab26791db3cb1b66430420c0cdTimo Sirainen "Mailbox UIDVALIDITY changed");
849969f639a00eab26791db3cb1b66430420c0cdTimo Sirainen }
08aea01ef9a9d20703e0fcf8618e6195c0037a44Timo Sirainen if (!ctx->no_newmail) {
08aea01ef9a9d20703e0fcf8618e6195c0037a44Timo Sirainen if (status.messages < ctx->messages_count)
849969f639a00eab26791db3cb1b66430420c0cdTimo Sirainen i_panic("Message count decreased");
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen client->messages_count = status.messages;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen if (status.messages != ctx->messages_count) {
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen client_send_line(client,
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen t_strdup_printf("* %u EXISTS", status.messages));
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen }
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen if (status.recent != client->recent_count &&
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen !ctx->no_newmail) {
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen client->recent_count = status.recent;
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen client_send_line(client,
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen t_strdup_printf("* %u RECENT", status.recent));
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen }
e48d89622047bd8bbd0475b881ca9377d592f535Timo Sirainen }
849969f639a00eab26791db3cb1b66430420c0cdTimo Sirainen /* send search updates the second time after syncing in done.
849969f639a00eab26791db3cb1b66430420c0cdTimo Sirainen now it contains added/removed messages. */
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen imap_sync_send_search_updates(ctx);
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen
9d75363d3fbabc2fbc2d80f06672e3ed8965804aTimo Sirainen if ((client->enabled_features & MAILBOX_FEATURE_QRESYNC) != 0) {
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen imap_sync_send_highestmodseq(ctx, &status, &sync_status,
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen sync_cmd);
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen }
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen if (array_is_created(&ctx->search_removes)) {
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen array_free(&ctx->search_removes);
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen array_free(&ctx->search_adds);
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen }
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen array_free(&ctx->tmp_keywords);
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen i_free(ctx);
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen return ret;
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen}
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainenstatic void imap_sync_add_modseq(struct imap_sync_context *ctx, string_t *str)
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen{
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen uint64_t modseq;
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen
be5c76fabc7439fd33bc799bc3ab3f570799977bTimo Sirainen modseq = mail_get_modseq(ctx->mail);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen if (ctx->client->highest_fetch_modseq < modseq)
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen ctx->client->highest_fetch_modseq = modseq;
2028d80c2704bbf62b29b2c624b0ee3c3a03c462Timo Sirainen str_printfa(str, "MODSEQ (%llu)", (unsigned long long)modseq);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen}
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainenstatic int imap_sync_send_flags(struct imap_sync_context *ctx, string_t *str)
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen{
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen enum mail_flags flags;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen const char *const *keywords;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen mail_set_seq(ctx->mail, ctx->seq);
1a669829132a4b68aaba32400e28bb2a4e19bcaaTimo Sirainen flags = mail_get_flags(ctx->mail);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen keywords = client_get_keyword_names(ctx->client, &ctx->tmp_keywords,
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen mail_get_keyword_indexes(ctx->mail));
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen if ((flags & MAIL_DELETED) != 0)
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainen ctx->client->sync_seen_deletes = TRUE;
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen str_truncate(str, 0);
2028d80c2704bbf62b29b2c624b0ee3c3a03c462Timo Sirainen str_printfa(str, "* %u FETCH (", ctx->seq);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen if (ctx->imap_flags & IMAP_SYNC_FLAG_SEND_UID)
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen str_printfa(str, "UID %u ", ctx->mail->uid);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen if ((mailbox_get_enabled_features(ctx->box) &
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen MAILBOX_FEATURE_CONDSTORE) != 0) {
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen imap_sync_add_modseq(ctx, str);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen str_append_c(str, ' ');
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen }
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen str_append(str, "FLAGS (");
1a669829132a4b68aaba32400e28bb2a4e19bcaaTimo Sirainen imap_write_flags(str, flags, keywords);
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen str_append(str, "))");
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen return client_send_line(ctx->client, str_c(str));
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen}
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen
9ed2951bd0bb1878a27437d7c00611b2baadd614Timo Sirainenstatic int imap_sync_send_modseq(struct imap_sync_context *ctx, string_t *str)
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen{
43a66a0b16299bd4f7615acd85e98bd3832c54d5Timo Sirainen mail_set_seq(ctx->mail, ctx->seq);
str_truncate(str, 0);
str_printfa(str, "* %u FETCH (", ctx->seq);
if (ctx->imap_flags & IMAP_SYNC_FLAG_SEND_UID)
str_printfa(str, "UID %u ", ctx->mail->uid);
imap_sync_add_modseq(ctx, str);
str_append_c(str, ')');
return client_send_line(ctx->client, str_c(str));
}
static void imap_sync_vanished(struct imap_sync_context *ctx)
{
const struct seq_range *seqs;
unsigned int i, count;
string_t *line;
uint32_t seq, prev_uid, start_uid;
bool comma = FALSE;
/* Convert expunge sequences to UIDs and send them in VANISHED line. */
seqs = array_get(&ctx->expunges, &count);
if (count == 0)
return;
line = t_str_new(256);
str_append(line, "* VANISHED ");
for (i = 0; i < count; i++) {
start_uid = 0; prev_uid = (uint32_t)-1;
for (seq = seqs[i].seq1; seq <= seqs[i].seq2; seq++) {
mail_set_seq(ctx->mail, seq);
if (prev_uid + 1 != ctx->mail->uid) {
if (start_uid != 0) {
if (!comma)
comma = TRUE;
else
str_append_c(line, ',');
str_printfa(line, "%u", start_uid);
if (start_uid != prev_uid) {
str_printfa(line, ":%u",
prev_uid);
}
}
start_uid = ctx->mail->uid;
}
prev_uid = ctx->mail->uid;
}
if (!comma)
comma = TRUE;
else
str_append_c(line, ',');
str_printfa(line, "%u", start_uid);
if (start_uid != prev_uid)
str_printfa(line, ":%u", prev_uid);
}
str_append(line, "\r\n");
o_stream_send(ctx->client->output, str_data(line), str_len(line));
}
int imap_sync_more(struct imap_sync_context *ctx)
{
string_t *str;
int ret = 1;
/* finish syncing even when client has disconnected. otherwise our
internal state (ctx->messages_count) can get messed up and unless
we immediately stop handling all commands and syncs we could end up
assert-crashing. */
str = t_str_new(256);
for (;;) {
if (ctx->seq == 0) {
/* get next one */
if (!mailbox_sync_next(ctx->sync_ctx, &ctx->sync_rec)) {
/* finished */
ret = 1;
break;
}
}
if (ctx->sync_rec.seq2 > ctx->messages_count) {
/* don't send change notifications of messages we
haven't even announced to client yet */
if (ctx->sync_rec.seq1 > ctx->messages_count) {
ctx->seq = 0;
continue;
}
ctx->sync_rec.seq2 = ctx->messages_count;
}
/* EXPUNGEs must come last */
i_assert(!array_is_created(&ctx->expunges) ||
array_count(&ctx->expunges) == 0 ||
ctx->sync_rec.type == MAILBOX_SYNC_TYPE_EXPUNGE);
switch (ctx->sync_rec.type) {
case MAILBOX_SYNC_TYPE_FLAGS:
if (ctx->seq == 0)
ctx->seq = ctx->sync_rec.seq1;
ret = 1;
for (; ctx->seq <= ctx->sync_rec.seq2; ctx->seq++) {
if (ret == 0)
break;
ret = imap_sync_send_flags(ctx, str);
}
break;
case MAILBOX_SYNC_TYPE_EXPUNGE:
ctx->client->sync_seen_expunges = TRUE;
if (array_is_created(&ctx->expunges)) {
/* Use a single VANISHED line */
seq_range_array_add_range(&ctx->expunges,
ctx->sync_rec.seq1,
ctx->sync_rec.seq2);
ctx->messages_count -=
ctx->sync_rec.seq2 -
ctx->sync_rec.seq1 + 1;
break;
}
if (ctx->seq == 0)
ctx->seq = ctx->sync_rec.seq2;
ret = 1;
for (; ctx->seq >= ctx->sync_rec.seq1; ctx->seq--) {
if (ret == 0)
break;
str_truncate(str, 0);
str_printfa(str, "* %u EXPUNGE", ctx->seq);
ret = client_send_line(ctx->client, str_c(str));
}
if (ctx->seq < ctx->sync_rec.seq1) {
/* update only after we're finished, so that
the seq2 > messages_count check above
doesn't break */
ctx->messages_count -=
ctx->sync_rec.seq2 -
ctx->sync_rec.seq1 + 1;
}
break;
case MAILBOX_SYNC_TYPE_MODSEQ:
if ((ctx->client->enabled_features &
MAILBOX_FEATURE_CONDSTORE) == 0)
break;
if (ctx->seq == 0)
ctx->seq = ctx->sync_rec.seq1;
ret = 1;
for (; ctx->seq <= ctx->sync_rec.seq2; ctx->seq++) {
if (ret == 0)
break;
ret = imap_sync_send_modseq(ctx, str);
}
break;
}
if (ret == 0) {
/* buffer full */
break;
}
ctx->seq = 0;
}
if (array_is_created(&ctx->expunges))
imap_sync_vanished(ctx);
return ret;
}
bool imap_sync_is_allowed(struct client *client)
{
if (client->syncing)
return FALSE;
if (client->mailbox != NULL &&
mailbox_transaction_get_count(client->mailbox) > 0)
return FALSE;
return TRUE;
}
static bool cmd_finish_sync(struct client_command_context *cmd)
{
if (cmd->sync->callback != NULL)
return cmd->sync->callback(cmd);
else {
client_send_tagline(cmd, cmd->sync->tagline);
return TRUE;
}
}
static bool cmd_sync_continue(struct client_command_context *sync_cmd)
{
struct client_command_context *cmd, *prev;
struct client *client = sync_cmd->client;
struct imap_sync_context *ctx = sync_cmd->context;
int ret;
i_assert(ctx->client == client);
if ((ret = imap_sync_more(ctx)) == 0)
return FALSE;
if (ret < 0)
ctx->failed = TRUE;
client->syncing = FALSE;
if (imap_sync_deinit(ctx, sync_cmd) < 0) {
client_send_untagged_storage_error(client,
mailbox_get_storage(client->mailbox));
}
sync_cmd->context = NULL;
/* Finish all commands that waited for this sync. Go through the queue
backwards, so that tagged replies are sent in the same order as
they were received. This fixes problems with clients that rely on
this (Apple Mail 3.2) */
for (cmd = client->command_queue; cmd->next != NULL; cmd = cmd->next) ;
for (; cmd != NULL; cmd = prev) {
prev = cmd->prev;
if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC &&
cmd != sync_cmd &&
cmd->sync->counter+1 == client->sync_counter) {
if (cmd_finish_sync(cmd))
client_command_free(&cmd);
}
}
return cmd_finish_sync(sync_cmd);
}
static void get_common_sync_flags(struct client *client,
enum mailbox_sync_flags *flags_r,
enum imap_sync_flags *imap_flags_r)
{
struct client_command_context *cmd;
unsigned int count = 0, fast_count = 0, noexpunges_count = 0;
*flags_r = 0;
*imap_flags_r = 0;
for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
if (cmd->sync != NULL &&
cmd->sync->counter == client->sync_counter) {
if ((cmd->sync->flags & MAILBOX_SYNC_FLAG_FAST) != 0)
fast_count++;
if (cmd->sync->flags & MAILBOX_SYNC_FLAG_NO_EXPUNGES)
noexpunges_count++;
*flags_r |= cmd->sync->flags;
*imap_flags_r |= cmd->sync->imap_flags;
count++;
}
}
i_assert(noexpunges_count == 0 || noexpunges_count == count);
if (fast_count != count)
*flags_r &= ~MAILBOX_SYNC_FLAG_FAST;
i_assert((*flags_r & MAILBOX_SYNC_FLAG_FIX_INCONSISTENT) == 0);
}
static bool cmd_sync_client(struct client_command_context *sync_cmd)
{
struct client *client = sync_cmd->client;
struct imap_sync_context *ctx;
enum mailbox_sync_flags flags;
enum imap_sync_flags imap_flags;
bool no_newmail;
/* there may be multiple commands waiting. use their combined flags */
get_common_sync_flags(client, &flags, &imap_flags);
client->sync_counter++;
no_newmail = (client->set->parsed_workarounds & WORKAROUND_DELAY_NEWMAIL) != 0 &&
(imap_flags & IMAP_SYNC_FLAG_SAFE) == 0;
if (no_newmail) {
/* expunges might break the client just as badly as new mail
notifications. */
flags |= MAILBOX_SYNC_FLAG_NO_EXPUNGES;
}
client->syncing = TRUE;
ctx = imap_sync_init(client, client->mailbox, imap_flags, flags);
ctx->no_newmail = no_newmail;
/* handle the syncing using sync_cmd. it doesn't actually matter which
one of the pending commands it is. */
sync_cmd->func = cmd_sync_continue;
sync_cmd->context = ctx;
sync_cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
if (!cmd_sync_continue(sync_cmd)) {
o_stream_set_flush_pending(client->output, TRUE);
return FALSE;
}
client_command_free(&sync_cmd);
(void)cmd_sync_delayed(client);
return TRUE;
}
static bool
cmd_sync_full(struct client_command_context *cmd, enum mailbox_sync_flags flags,
enum imap_sync_flags imap_flags, const char *tagline,
imap_sync_callback_t *callback)
{
struct client *client = cmd->client;
i_assert(client->output_lock == cmd || client->output_lock == NULL);
if (cmd->cancel)
return TRUE;
if (client->mailbox == NULL) {
/* no mailbox selected, no point in delaying the sync */
i_assert(callback == NULL);
client_send_tagline(cmd, tagline);
return TRUE;
}
cmd->sync = p_new(cmd->pool, struct client_sync_context, 1);
cmd->sync->counter = client->sync_counter;
cmd->sync->flags = flags;
cmd->sync->imap_flags = imap_flags;
cmd->sync->tagline = p_strdup(cmd->pool, tagline);
cmd->sync->callback = callback;
cmd->state = CLIENT_COMMAND_STATE_WAIT_SYNC;
cmd->func = NULL;
cmd->context = NULL;
client->output_lock = NULL;
if (client->input_lock == cmd)
client->input_lock = NULL;
return FALSE;
}
bool cmd_sync(struct client_command_context *cmd, enum mailbox_sync_flags flags,
enum imap_sync_flags imap_flags, const char *tagline)
{
return cmd_sync_full(cmd, flags, imap_flags, tagline, NULL);
}
bool cmd_sync_callback(struct client_command_context *cmd,
enum mailbox_sync_flags flags,
enum imap_sync_flags imap_flags,
imap_sync_callback_t *callback)
{
return cmd_sync_full(cmd, flags, imap_flags, NULL, callback);
}
static bool cmd_sync_drop_fast(struct client *client)
{
struct client_command_context *cmd, *prev;
bool ret = FALSE;
if (client->command_queue == NULL)
return FALSE;
for (cmd = client->command_queue; cmd->next != NULL; cmd = cmd->next) ;
for (; cmd != NULL; cmd = prev) {
prev = cmd->next;
if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC &&
(cmd->sync->flags & MAILBOX_SYNC_FLAG_FAST) != 0) {
if (cmd_finish_sync(cmd)) {
client_command_free(&cmd);
ret = TRUE;
}
}
}
return ret;
}
bool cmd_sync_delayed(struct client *client)
{
struct client_command_context *cmd, *first_expunge, *first_nonexpunge;
if (client->output_lock != NULL) {
/* wait until we can send output to client */
return FALSE;
}
if (!imap_sync_is_allowed(client)) {
/* wait until mailbox can be synced */
return cmd_sync_drop_fast(client);
}
/* separate syncs that can send expunges from those that can't */
first_expunge = first_nonexpunge = NULL;
for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
if (cmd->sync != NULL &&
cmd->sync->counter == client->sync_counter) {
if (cmd->sync->flags & MAILBOX_SYNC_FLAG_NO_EXPUNGES) {
if (first_nonexpunge == NULL)
first_nonexpunge = cmd;
} else {
if (first_expunge == NULL)
first_expunge = cmd;
}
}
}
if (first_expunge != NULL && first_nonexpunge != NULL) {
/* sync expunges after nonexpunges */
for (cmd = first_expunge; cmd != NULL; cmd = cmd->next) {
if (cmd->sync != NULL &&
cmd->sync->counter == client->sync_counter &&
(cmd->sync->flags &
MAILBOX_SYNC_FLAG_NO_EXPUNGES) == 0)
cmd->sync->counter++;
}
first_expunge = NULL;
}
cmd = first_nonexpunge != NULL ? first_nonexpunge : first_expunge;
if (cmd == NULL)
return cmd_sync_drop_fast(client);
i_assert(client->mailbox != NULL);
return cmd_sync_client(cmd);
}