summaryrefslogtreecommitdiffstats
path: root/src/imap/cmd-idle.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/imap/cmd-idle.c')
-rw-r--r--src/imap/cmd-idle.c308
1 files changed, 308 insertions, 0 deletions
diff --git a/src/imap/cmd-idle.c b/src/imap/cmd-idle.c
new file mode 100644
index 0000000..2b31dc7
--- /dev/null
+++ b/src/imap/cmd-idle.c
@@ -0,0 +1,308 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "istream.h"
+#include "ostream.h"
+#include "crc32.h"
+#include "mail-storage-settings.h"
+#include "imap-commands.h"
+#include "imap-keepalive.h"
+#include "imap-sync.h"
+
+struct cmd_idle_context {
+ struct client *client;
+ struct client_command_context *cmd;
+
+ struct imap_sync_context *sync_ctx;
+ struct timeout *keepalive_to, *to_hibernate;
+
+ bool manual_cork:1;
+ bool sync_pending:1;
+};
+
+static void idle_add_keepalive_timeout(struct cmd_idle_context *ctx);
+static bool cmd_idle_continue(struct client_command_context *cmd);
+
+static void
+idle_finish(struct cmd_idle_context *ctx, bool done_ok, bool free_cmd)
+{
+ struct client *client = ctx->client;
+
+ timeout_remove(&ctx->keepalive_to);
+ timeout_remove(&ctx->to_hibernate);
+
+ if (ctx->sync_ctx != NULL) {
+ /* we're here only in connection failure cases */
+ (void)imap_sync_deinit(ctx->sync_ctx, ctx->cmd);
+ }
+
+ o_stream_cork(client->output);
+ io_remove(&client->io);
+
+ if (client->mailbox != NULL)
+ mailbox_notify_changes_stop(client->mailbox);
+
+ if (done_ok)
+ client_send_tagline(ctx->cmd, "OK Idle completed.");
+ else
+ client_send_tagline(ctx->cmd, "BAD Expected DONE.");
+
+ o_stream_uncork(client->output);
+ if (free_cmd)
+ client_command_free(&ctx->cmd);
+}
+
+static bool
+idle_client_handle_input(struct cmd_idle_context *ctx, bool free_cmd)
+{
+ const char *line;
+
+ while ((line = i_stream_next_line(ctx->client->input)) != NULL) {
+ if (ctx->client->input_skip_line)
+ ctx->client->input_skip_line = FALSE;
+ else {
+ idle_finish(ctx, strcasecmp(line, "DONE") == 0,
+ free_cmd);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool idle_client_input_more(struct cmd_idle_context *ctx)
+{
+ struct client *client = ctx->client;
+
+ client->last_input = ioloop_time;
+ timeout_reset(client->to_idle);
+
+ switch (i_stream_read(client->input)) {
+ case -1:
+ /* disconnected */
+ client_disconnect(client, NULL);
+ return TRUE;
+ case -2:
+ client->input_skip_line = TRUE;
+ idle_finish(ctx, FALSE, TRUE);
+ return TRUE;
+ }
+
+ if (ctx->sync_ctx != NULL) {
+ /* we're still sending output to client. wait until it's all
+ sent so we don't lose any changes. */
+ io_remove(&client->io);
+ return FALSE;
+ }
+
+ return idle_client_handle_input(ctx, TRUE);
+}
+
+static void idle_client_input(struct cmd_idle_context *ctx)
+{
+ struct client *client = ctx->client;
+
+ if (idle_client_input_more(ctx))
+ client_continue_pending_input(client);
+}
+
+static void keepalive_timeout(struct cmd_idle_context *ctx)
+{
+ if (ctx->client->output_cmd_lock != NULL) {
+ /* it's busy sending output */
+ return;
+ }
+
+ if (o_stream_get_buffer_used_size(ctx->client->output) == 0) {
+ /* Sending this keeps NATs/stateful firewalls alive.
+ Sending this also catches dead connections. Don't send
+ anything if there is already data waiting in output
+ buffer. */
+ o_stream_cork(ctx->client->output);
+ client_send_line(ctx->client, "* OK Still here");
+ o_stream_uncork(ctx->client->output);
+ }
+ /* Make sure idling connections don't get disconnected. There are
+ several clients that really want to IDLE forever and there's not
+ much harm in letting them do so. */
+ timeout_reset(ctx->client->to_idle);
+ /* recalculate time for the next keepalive timeout */
+ idle_add_keepalive_timeout(ctx);
+}
+
+static bool idle_sync_now(struct mailbox *box, struct cmd_idle_context *ctx)
+{
+ i_assert(ctx->sync_ctx == NULL);
+
+ /* hibernation can't happen while sync is running.
+ the timeout is added back afterwards. */
+ timeout_remove(&ctx->to_hibernate);
+
+ ctx->sync_pending = FALSE;
+ ctx->sync_ctx = imap_sync_init(ctx->client, box, 0, 0);
+ return cmd_idle_continue(ctx->cmd);
+}
+
+static void idle_callback(struct mailbox *box, struct cmd_idle_context *ctx)
+{
+ struct client *client = ctx->client;
+
+ if (ctx->sync_ctx != NULL)
+ ctx->sync_pending = TRUE;
+ else {
+ ctx->manual_cork = TRUE;
+ (void)idle_sync_now(box, ctx);
+ if (client->disconnected)
+ client_destroy(client, NULL);
+ }
+}
+
+static void idle_add_keepalive_timeout(struct cmd_idle_context *ctx)
+{
+ struct client *client = ctx->client;
+ unsigned int interval = client->set->imap_idle_notify_interval;
+
+ if (interval == 0)
+ return;
+
+ interval = imap_keepalive_interval_msecs(client->user->username,
+ client->user->conn.remote_ip,
+ interval);
+
+ timeout_remove(&ctx->keepalive_to);
+ ctx->keepalive_to = timeout_add(interval, keepalive_timeout, ctx);
+}
+
+static void idle_hibernate_timeout(struct cmd_idle_context *ctx)
+{
+ struct client *client = ctx->client;
+ const char *reason;
+
+ i_assert(ctx->sync_ctx == NULL);
+ i_assert(!ctx->sync_pending);
+
+ if (imap_client_hibernate(&client, &reason)) {
+ /* client may be destroyed now */
+ } else {
+ /* failed - don't bother retrying */
+ timeout_remove(&ctx->to_hibernate);
+ }
+}
+
+static void idle_add_hibernate_timeout(struct cmd_idle_context *ctx)
+{
+ unsigned int secs = ctx->client->set->imap_hibernate_timeout;
+
+ i_assert(ctx->to_hibernate == NULL);
+
+ if (secs == 0)
+ return;
+
+ ctx->to_hibernate =
+ timeout_add(secs * 1000, idle_hibernate_timeout, ctx);
+}
+
+static bool cmd_idle_continue(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct cmd_idle_context *ctx = cmd->context;
+ uoff_t orig_offset = client->output->offset;
+
+ if (cmd->cancel) {
+ idle_finish(ctx, FALSE, FALSE);
+ return TRUE;
+ }
+
+ if (ctx->to_hibernate != NULL)
+ timeout_reset(ctx->to_hibernate);
+
+ if (ctx->manual_cork) {
+ /* we're coming from idle_callback instead of a normal
+ I/O handler, so we'll have to do corking manually */
+ o_stream_cork(client->output);
+ }
+
+ if (ctx->sync_ctx != NULL) {
+ if (imap_sync_more(ctx->sync_ctx) == 0) {
+ /* unfinished */
+ if (ctx->manual_cork) {
+ ctx->manual_cork = FALSE;
+ o_stream_uncork(client->output);
+ }
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+ return FALSE;
+ }
+
+ if (imap_sync_deinit(ctx->sync_ctx, ctx->cmd) < 0) {
+ client_send_untagged_storage_error(client,
+ mailbox_get_storage(client->mailbox));
+ mailbox_notify_changes_stop(client->mailbox);
+ }
+ ctx->sync_ctx = NULL;
+ }
+ if (client->output->offset != orig_offset &&
+ ctx->keepalive_to != NULL)
+ idle_add_keepalive_timeout(ctx);
+
+ if (ctx->sync_pending) {
+ /* more changes occurred while we were sending changes to
+ client.
+
+ NOTE: this recurses back to this function,
+ so we return here instead of doing everything twice. */
+ return idle_sync_now(client->mailbox, ctx);
+ }
+ if (ctx->to_hibernate == NULL)
+ idle_add_hibernate_timeout(ctx);
+ cmd->state = CLIENT_COMMAND_STATE_WAIT_INPUT;
+
+ if (ctx->manual_cork) {
+ ctx->manual_cork = FALSE;
+ o_stream_uncork(client->output);
+ }
+
+ if (client->output->closed) {
+ idle_finish(ctx, FALSE, FALSE);
+ return TRUE;
+ }
+ if (client->io == NULL) {
+ /* input is pending. add the io back and mark the input as
+ pending. we can't safely read more input immediately here. */
+ client->io = io_add_istream(client->input,
+ idle_client_input, ctx);
+ i_stream_set_input_pending(client->input, TRUE);
+ }
+ return FALSE;
+}
+
+bool cmd_idle(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct cmd_idle_context *ctx;
+
+ ctx = p_new(cmd->pool, struct cmd_idle_context, 1);
+ ctx->cmd = cmd;
+ ctx->client = client;
+ idle_add_keepalive_timeout(ctx);
+ idle_add_hibernate_timeout(ctx);
+
+ if (client->mailbox != NULL)
+ mailbox_notify_changes(client->mailbox, idle_callback, ctx);
+ if (!client->state_import_idle_continue)
+ client_send_line(client, "+ idling");
+ else {
+ /* continuing an IDLE after hibernation */
+ client->state_import_idle_continue = FALSE;
+ }
+
+ io_remove(&client->io);
+ client->io = io_add_istream(client->input, idle_client_input, ctx);
+
+ cmd->func = cmd_idle_continue;
+ cmd->context = ctx;
+
+ /* check immediately if there are changes. if they came before we
+ added mailbox-notifier, we wouldn't see them otherwise. */
+ if (client->mailbox != NULL)
+ idle_sync_now(client->mailbox, ctx);
+ return idle_client_handle_input(ctx, FALSE);
+}