changeset 21764:04edf83cff79

imapc: Use LOGOUT to cleanly disconnect from server. This makes it clearer in the remote server's logs whether the disconnection was intentional or not. Use a hardcoded 5 second timeout for LOGOUT. It should be enough time for the server to finish sending the tagged reply.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Sun, 29 Jan 2017 01:03:00 +0200
parents 5a5e046c42ec
children 91b94ecaa39d
files src/lib-imap-client/imapc-client-private.h src/lib-imap-client/imapc-client.c src/lib-imap-client/imapc-client.h src/lib-imap-client/imapc-connection.c src/lib-imap-client/imapc-connection.h src/lib-storage/index/imapc/imapc-list.c src/lib-storage/index/imapc/imapc-storage.c
diffstat 7 files changed, 80 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-imap-client/imapc-client-private.h	Thu Mar 16 18:46:28 2017 +0200
+++ b/src/lib-imap-client/imapc-client-private.h	Sun Jan 29 01:03:00 2017 +0200
@@ -24,6 +24,7 @@
 	void *state_change_context;
 
 	ARRAY(struct imapc_client_connection *) conns;
+	bool logging_out;
 
 	struct ioloop *ioloop;
 };
--- a/src/lib-imap-client/imapc-client.c	Thu Mar 16 18:46:28 2017 +0200
+++ b/src/lib-imap-client/imapc-client.c	Sun Jan 29 01:03:00 2017 +0200
@@ -265,6 +265,51 @@
 	imapc_connection_connect(conn->conn, callback, context);
 }
 
+struct imapc_logout_ctx {
+	struct imapc_client *client;
+	unsigned int logout_count;
+};
+
+static void
+imapc_client_logout_callback(const struct imapc_command_reply *reply ATTR_UNUSED,
+			     void *context)
+{
+	struct imapc_logout_ctx *ctx = context;
+
+	i_assert(ctx->logout_count > 0);
+
+	if (--ctx->logout_count == 0)
+		imapc_client_stop(ctx->client);
+}
+
+void imapc_client_logout(struct imapc_client *client)
+{
+	struct imapc_logout_ctx ctx = { .client = client };
+	struct imapc_client_connection *const *connp;
+	struct imapc_command *cmd;
+
+	client->logging_out = TRUE;
+
+	/* send LOGOUT to all connections */
+	array_foreach(&client->conns, connp) {
+		imapc_connection_set_no_reconnect((*connp)->conn);
+		ctx.logout_count++;
+		cmd = imapc_connection_cmd((*connp)->conn,
+			imapc_client_logout_callback, &ctx);
+		imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_PRELOGIN |
+					IMAPC_COMMAND_FLAG_LOGOUT);
+		imapc_command_send(cmd, "LOGOUT");
+	}
+
+	/* wait for LOGOUT to finish */
+	while (ctx.logout_count > 0)
+		imapc_client_run(client);
+
+	/* we should have disconnected all clients already, but if there were
+	   any timeouts there may be some clients left. */
+	imapc_client_disconnect(client);
+}
+
 struct imapc_client_mailbox *
 imapc_client_mailbox_open(struct imapc_client *client,
 			  void *untagged_box_context)
--- a/src/lib-imap-client/imapc-client.h	Thu Mar 16 18:46:28 2017 +0200
+++ b/src/lib-imap-client/imapc-client.h	Sun Jan 29 01:03:00 2017 +0200
@@ -45,7 +45,9 @@
 	IMAPC_COMMAND_FLAG_PRELOGIN	= 0x02,
 	/* Allow command to be automatically retried if disconnected before it
 	   finishes. */
-	IMAPC_COMMAND_FLAG_RETRIABLE	= 0x04
+	IMAPC_COMMAND_FLAG_RETRIABLE	= 0x04,
+	/* This is the LOGOUT command. Use a small timeout for it. */
+	IMAPC_COMMAND_FLAG_LOGOUT	= 0x08
 };
 
 enum imapc_client_ssl_mode {
@@ -169,6 +171,8 @@
 /* Explicitly login to server (also done automatically). */
 void imapc_client_login(struct imapc_client *client,
 			imapc_command_callback_t *callback, void *context);
+/* Send a LOGOUT and wait for disconnection. */
+void imapc_client_logout(struct imapc_client *client);
 
 struct imapc_command *
 imapc_client_cmd(struct imapc_client *client,
--- a/src/lib-imap-client/imapc-connection.c	Thu Mar 16 18:46:28 2017 +0200
+++ b/src/lib-imap-client/imapc-connection.c	Sun Jan 29 01:03:00 2017 +0200
@@ -25,6 +25,8 @@
 #define IMAPC_COMMAND_STATE_AUTHENTICATE_CONTINUE 10000
 #define IMAPC_MAX_INLINE_LITERAL_SIZE (1024*32)
 #define IMAPC_RECONNECT_MIN_RETRY_SECS 10
+/* If LOGOUT reply takes longer than this, disconnect. */
+#define IMAPC_LOGOUT_TIMEOUT_MSECS 5000
 
 enum imapc_input_state {
 	IMAPC_INPUT_STATE_NONE = 0,
@@ -453,6 +455,11 @@
 	imapc_connection_abort_commands(conn, NULL, reconnecting);
 }
 
+void imapc_connection_set_no_reconnect(struct imapc_connection *conn)
+{
+	conn->reconnect_ok = FALSE;
+}
+
 void imapc_connection_disconnect(struct imapc_connection *conn)
 {
 	imapc_connection_disconnect_full(conn, FALSE);
@@ -466,6 +473,8 @@
 
 static bool imapc_connection_can_reconnect(struct imapc_connection *conn)
 {
+	if (conn->client->logging_out)
+		return FALSE;
 	if (conn->selected_box != NULL)
 		return imapc_client_mailbox_can_reconnect(conn->selected_box);
 	else {
@@ -1473,7 +1482,10 @@
 	while (conn->input != NULL && (ret = i_stream_read(conn->input)) > 0)
 		imapc_connection_input_pending(conn);
 
-	if (ret < 0) {
+	if (ret < 0 && conn->client->logging_out &&
+	    conn->disconnect_reason != NULL) {
+		/* expected disconnection */
+	} else if (ret < 0) {
 		/* disconnected or buffer full */
 		str = t_str_new(128);
 		if (conn->disconnect_reason != NULL) {
@@ -2033,6 +2045,11 @@
 		/* wait until we're fully connected */
 		return;
 	}
+	if ((cmd->flags & IMAPC_COMMAND_FLAG_LOGOUT) != 0 &&
+	    array_count(&conn->cmd_wait_list) > 0) {
+		/* wait until existing commands have finished */
+		return;
+	}
 	if (cmd->wait_for_literal) {
 		/* wait until we received '+' */
 		return;
@@ -2064,7 +2081,13 @@
 
 	/* add timeout for commands if there's not one yet
 	   (pre-login has its own timeout) */
-	if (conn->to == NULL) {
+	if ((cmd->flags & IMAPC_COMMAND_FLAG_LOGOUT) != 0) {
+		/* LOGOUT has a shorter timeout */
+		if (conn->to != NULL)
+			timeout_remove(&conn->to);
+		conn->to = timeout_add(IMAPC_LOGOUT_TIMEOUT_MSECS,
+				       imapc_command_timeout, conn);
+	} else if (conn->to == NULL) {
 		conn->to = timeout_add(conn->client->set.cmd_timeout_msecs,
 				       imapc_command_timeout, conn);
 	}
--- a/src/lib-imap-client/imapc-connection.h	Thu Mar 16 18:46:28 2017 +0200
+++ b/src/lib-imap-client/imapc-connection.h	Sun Jan 29 01:03:00 2017 +0200
@@ -32,6 +32,7 @@
 void imapc_connection_connect(struct imapc_connection *conn,
 			      imapc_command_callback_t *login_callback,
 			      void *login_context) ATTR_NULL(2, 3);
+void imapc_connection_set_no_reconnect(struct imapc_connection *conn);
 void imapc_connection_disconnect(struct imapc_connection *conn);
 void imapc_connection_disconnect_full(struct imapc_connection *conn,
 				      bool reconnecting);
--- a/src/lib-storage/index/imapc/imapc-list.c	Thu Mar 16 18:46:28 2017 +0200
+++ b/src/lib-storage/index/imapc/imapc-list.c	Sun Jan 29 01:03:00 2017 +0200
@@ -91,7 +91,7 @@
 	   deinitialized */
 	if (list->client != NULL) {
 		list->client->destroying = TRUE;
-		imapc_client_disconnect(list->client->client);
+		imapc_client_logout(list->client->client);
 		imapc_storage_client_unref(&list->client);
 	}
 	if (list->index_list != NULL)
--- a/src/lib-storage/index/imapc/imapc-storage.c	Thu Mar 16 18:46:28 2017 +0200
+++ b/src/lib-storage/index/imapc/imapc-storage.c	Sun Jan 29 01:03:00 2017 +0200
@@ -124,7 +124,7 @@
 void imapc_simple_run(struct imapc_simple_context *sctx)
 {
 	if (sctx->client->auth_failed) {
-		imapc_client_disconnect(sctx->client->client);
+		imapc_client_logout(sctx->client->client);
 		sctx->ret = -1;
 	}
 	while (sctx->ret == -2)
@@ -402,7 +402,7 @@
 
 	/* make sure all pending commands are aborted before anything is
 	   deinitialized */
-	imapc_client_disconnect(storage->client->client);
+	imapc_client_logout(storage->client->client);
 
 	imapc_storage_client_unref(&storage->client);
 	index_storage_destroy(_storage);