changeset 21497:1f04b10661cc

imap: Add imap_fetch_failure setting This controls what happens when FETCH fails for some mails. The possible values are: disconnect-immediately: This is the original behavior. Whenever FETCH fails for a mail, the FETCH is aborted and client is disconnected. disconnect-after: The FETCH runs for all the requested mails, skipping any mails that returned failures, but at the end the client is still disconnected. no-after: The FETCH runs for all the requested mails, skipping any mails that returned failures. At the end tagged NO reply is returned. If the client attempts to FETCH the same failed mail more than once, the client is disconnected. This is to avoid clients from going into infinite loops trying to FETCH a broken mail.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Sun, 05 Feb 2017 16:49:05 +0200
parents 8cbee0fe11fd
children e74e1269959f
files src/imap/cmd-fetch.c src/imap/imap-client.h src/imap/imap-commands-util.c src/imap/imap-fetch.c src/imap/imap-fetch.h src/imap/imap-settings.c src/imap/imap-settings.h
diffstat 7 files changed, 86 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/src/imap/cmd-fetch.c	Mon May 02 13:26:05 2016 +0300
+++ b/src/imap/cmd-fetch.c	Sun Feb 05 16:49:05 2017 +0200
@@ -177,23 +177,42 @@
 	return TRUE;
 }
 
+static bool imap_fetch_is_failed_retry(struct imap_fetch_context *ctx)
+{
+	if (!array_is_created(&ctx->client->fetch_failed_uids) ||
+	    !array_is_created(&ctx->fetch_failed_uids))
+		return FALSE;
+	return seq_range_array_have_common(&ctx->client->fetch_failed_uids,
+					   &ctx->fetch_failed_uids);
+
+}
+
+static void imap_fetch_add_failed_uids(struct imap_fetch_context *ctx)
+{
+	if (!array_is_created(&ctx->fetch_failed_uids))
+		return;
+	if (!array_is_created(&ctx->client->fetch_failed_uids)) {
+		p_array_init(&ctx->client->fetch_failed_uids, ctx->client->pool,
+			     array_count(&ctx->fetch_failed_uids));
+	}
+	seq_range_array_merge(&ctx->client->fetch_failed_uids,
+			      &ctx->fetch_failed_uids);
+}
+
 static bool cmd_fetch_finish(struct imap_fetch_context *ctx,
 			     struct client_command_context *cmd)
 {
 	static const char *ok_message = "OK Fetch completed.";
 	const char *tagged_reply = ok_message;
 	enum mail_error error;
-	bool failed, seen_flags_changed = ctx->state.seen_flags_changed;
+	bool seen_flags_changed = ctx->state.seen_flags_changed;
 
 	if (ctx->state.skipped_expunged_msgs) {
 		tagged_reply = "OK ["IMAP_RESP_CODE_EXPUNGEISSUED"] "
 			"Some messages were already expunged.";
 	}
 
-	failed = imap_fetch_end(ctx) < 0;
-	imap_fetch_free(&ctx);
-
-	if (failed) {
+	if (imap_fetch_end(ctx) < 0) {
 		const char *errstr;
 
 		if (cmd->client->output->closed) {
@@ -205,6 +224,7 @@
 			   happen if we called client_disconnect() here
 			   directly). */
 			cmd->func = cmd_fetch_finished;
+			imap_fetch_free(&ctx);
 			return cmd->cancel;
 		}
 
@@ -217,13 +237,25 @@
 			/* Content was invalid */
 			tagged_reply = t_strdup_printf(
 				"NO ["IMAP_RESP_CODE_PARSE"] %s", errstr);
+		} else if (cmd->client->set->parsed_fetch_failure != IMAP_CLIENT_FETCH_FAILURE_NO_AFTER ||
+			   imap_fetch_is_failed_retry(ctx)) {
+			/* By default we never want to reply NO to FETCH
+			   requests, because many IMAP clients become confused
+			   about what they should on NO. A disconnection causes
+			   less confusion. */
+			client_disconnect_with_error(cmd->client, errstr);
+			imap_fetch_free(&ctx);
+			return TRUE;
 		} else {
-			/* We never want to reply NO to FETCH requests,
-			   BYE is preferrable (see imap-ml for reasons). */
-			client_disconnect_with_error(cmd->client, errstr);
-			return TRUE;
+			/* Use a tagged NO to FETCH failure, but only if client
+			   hasn't repeated the FETCH to the same email (so that
+			   we avoid infinitely retries from client.) */
+			imap_fetch_add_failed_uids(ctx);
+			tagged_reply = t_strdup_printf(
+				"NO ["IMAP_RESP_CODE_SERVERBUG"] %s", errstr);
 		}
 	}
+	imap_fetch_free(&ctx);
 
 	return cmd_sync(cmd,
 			(seen_flags_changed ? 0 : MAILBOX_SYNC_FLAG_FAST) |
--- a/src/imap/imap-client.h	Mon May 02 13:26:05 2016 +0300
+++ b/src/imap/imap-client.h	Sun Feb 05 16:49:05 2017 +0200
@@ -157,6 +157,7 @@
 
 	uint64_t sync_last_full_modseq;
 	uint64_t highest_fetch_modseq;
+	ARRAY_TYPE(seq_range) fetch_failed_uids;
 
 	/* For imap_logout_format statistics: */
 	unsigned int fetch_hdr_count, fetch_body_count;
--- a/src/imap/imap-commands-util.c	Mon May 02 13:26:05 2016 +0300
+++ b/src/imap/imap-commands-util.c	Sun Feb 05 16:49:05 2017 +0200
@@ -80,6 +80,8 @@
 
 	i_assert(client->mailbox != NULL);
 
+	if (array_is_created(&client->fetch_failed_uids))
+		array_clear(&client->fetch_failed_uids);
 	client_search_updates_free(client);
 
 	box = client->mailbox;
--- a/src/imap/imap-fetch.c	Mon May 02 13:26:05 2016 +0300
+++ b/src/imap/imap-fetch.c	Sun Feb 05 16:49:05 2017 +0200
@@ -429,6 +429,19 @@
 	return 0;
 }
 
+static bool imap_fetch_cur_failed(struct imap_fetch_context *ctx)
+{
+	ctx->failures = TRUE;
+	if (ctx->client->set->parsed_fetch_failure ==
+	    IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_IMMEDIATELY)
+		return FALSE;
+
+	if (!array_is_created(&ctx->fetch_failed_uids))
+		p_array_init(&ctx->fetch_failed_uids, ctx->ctx_pool, 8);
+	seq_range_array_add(&ctx->fetch_failed_uids, ctx->state.cur_mail->uid);
+	return TRUE;
+}
+
 static int imap_fetch_more_int(struct imap_fetch_context *ctx, bool cancel)
 {
 	struct imap_fetch_state *state = &ctx->state;
@@ -452,7 +465,8 @@
 				if (imap_fetch_send_nil_reply(ctx) < 0)
 					return -1;
 			} else {
-				return -1;
+				if (!imap_fetch_cur_failed(ctx))
+					return -1;
 			}
 		}
 
@@ -518,7 +532,8 @@
 				} else {
 					i_assert(ret < 0 ||
 						 state->cont_handler != NULL);
-					return -1;
+					if (!imap_fetch_cur_failed(ctx))
+						return -1;
 				}
 			}
 
@@ -551,7 +566,7 @@
 		state->cur_handler = 0;
 	}
 
-	return 1;
+	return ctx->failures ? -1 : 1;
 }
 
 int imap_fetch_more(struct imap_fetch_context *ctx,
--- a/src/imap/imap-fetch.h	Mon May 02 13:26:05 2016 +0300
+++ b/src/imap/imap-fetch.h	Sun Feb 05 16:49:05 2017 +0200
@@ -84,8 +84,10 @@
 	ARRAY_TYPE(keywords) tmp_keywords;
 
 	struct imap_fetch_state state;
+	ARRAY_TYPE(seq_range) fetch_failed_uids;
 
 	unsigned int initialized:1;
+	unsigned int failures:1;
 	unsigned int flags_have_handler:1;
 	unsigned int flags_update_seen:1;
 	unsigned int flags_show_only_seen_changes:1;
--- a/src/imap/imap-settings.c	Mon May 02 13:26:05 2016 +0300
+++ b/src/imap/imap-settings.c	Sun Feb 05 16:49:05 2017 +0200
@@ -72,6 +72,7 @@
 	DEF(SET_STR, imap_logout_format),
 	DEF(SET_STR, imap_id_send),
 	DEF(SET_STR, imap_id_log),
+	DEF(SET_ENUM, imap_fetch_failure),
 	DEF(SET_BOOL, imap_metadata),
 	DEF(SET_TIME, imap_hibernate_timeout),
 
@@ -95,6 +96,7 @@
 	.imap_logout_format = "in=%i out=%o",
 	.imap_id_send = "name *",
 	.imap_id_log = "",
+	.imap_fetch_failure = "disconnect-immediately:disconnect-after:no-after",
 	.imap_metadata = FALSE,
 	.imap_hibernate_timeout = 0,
 
@@ -170,6 +172,18 @@
 
 	if (imap_settings_parse_workarounds(set, error_r) < 0)
 		return FALSE;
+
+	if (strcmp(set->imap_fetch_failure, "disconnect-immediately") == 0)
+		set->parsed_fetch_failure = IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_IMMEDIATELY;
+	else if (strcmp(set->imap_fetch_failure, "disconnect-after") == 0)
+		set->parsed_fetch_failure = IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_AFTER;
+	else if (strcmp(set->imap_fetch_failure, "no-after") == 0)
+		set->parsed_fetch_failure = IMAP_CLIENT_FETCH_FAILURE_NO_AFTER;
+	else {
+		*error_r = t_strdup_printf("Unknown imap_fetch_failure: %s",
+					   set->imap_fetch_failure);
+		return FALSE;
+	}
 	return TRUE;
 }
 /* </settings checks> */
--- a/src/imap/imap-settings.h	Mon May 02 13:26:05 2016 +0300
+++ b/src/imap/imap-settings.h	Sun Feb 05 16:49:05 2017 +0200
@@ -11,6 +11,12 @@
 	WORKAROUND_TB_EXTRA_MAILBOX_SEP		= 0x08,
 	WORKAROUND_TB_LSUB_FLAGS		= 0x10
 };
+
+enum imap_client_fetch_failure {
+	IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_IMMEDIATELY,
+	IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_AFTER,
+	IMAP_CLIENT_FETCH_FAILURE_NO_AFTER,
+};
 /* </settings checks> */
 
 struct imap_settings {
@@ -25,6 +31,7 @@
 	const char *imap_logout_format;
 	const char *imap_id_send;
 	const char *imap_id_log;
+	const char *imap_fetch_failure;
 	bool imap_metadata;
 	unsigned int imap_hibernate_timeout;
 
@@ -33,6 +40,7 @@
 	in_port_t imap_urlauth_port;
 
 	enum imap_client_workarounds parsed_workarounds;
+	enum imap_client_fetch_failure parsed_fetch_failure;
 };
 
 extern const struct setting_parser_info imap_setting_parser_info;