Skip to content

Commit

Permalink
imapc: Use LOGOUT to cleanly disconnect from server.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sirainen committed Mar 21, 2017
1 parent 7c5caf3 commit 9fe890f
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/lib-imap-client/imapc-client-private.h
Expand Up @@ -24,6 +24,7 @@ struct imapc_client {
void *state_change_context;

ARRAY(struct imapc_client_connection *) conns;
bool logging_out;

struct ioloop *ioloop;
};
Expand Down
45 changes: 45 additions & 0 deletions src/lib-imap-client/imapc-client.c
Expand Up @@ -265,6 +265,51 @@ void imapc_client_login(struct imapc_client *client,
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)
Expand Down
6 changes: 5 additions & 1 deletion src/lib-imap-client/imapc-client.h
Expand Up @@ -45,7 +45,9 @@ enum imapc_command_flags {
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 {
Expand Down Expand Up @@ -169,6 +171,8 @@ void imapc_client_deinit(struct imapc_client **client);
/* 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,
Expand Down
27 changes: 25 additions & 2 deletions src/lib-imap-client/imapc-connection.c
Expand Up @@ -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,
Expand Down Expand Up @@ -453,6 +455,11 @@ void imapc_connection_disconnect_full(struct imapc_connection *conn,
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);
Expand All @@ -466,6 +473,8 @@ static void imapc_connection_set_disconnected(struct imapc_connection *conn)

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 {
Expand Down Expand Up @@ -1473,7 +1482,10 @@ static void imapc_connection_input(struct imapc_connection *conn)
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) {
Expand Down Expand Up @@ -2033,6 +2045,11 @@ static void imapc_command_send_more(struct imapc_connection *conn)
/* 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;
Expand Down Expand Up @@ -2064,7 +2081,13 @@ static void imapc_command_send_more(struct imapc_connection *conn)

/* 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);
}
Expand Down
1 change: 1 addition & 0 deletions src/lib-imap-client/imapc-connection.h
Expand Up @@ -32,6 +32,7 @@ void imapc_connection_deinit(struct imapc_connection **conn);
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);
Expand Down
2 changes: 1 addition & 1 deletion src/lib-storage/index/imapc/imapc-list.c
Expand Up @@ -91,7 +91,7 @@ static void imapc_list_deinit(struct mailbox_list *_list)
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)
Expand Down
4 changes: 2 additions & 2 deletions src/lib-storage/index/imapc/imapc-storage.c
Expand Up @@ -124,7 +124,7 @@ void imapc_simple_context_init(struct imapc_simple_context *sctx,
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)
Expand Down Expand Up @@ -402,7 +402,7 @@ static void imapc_storage_destroy(struct mail_storage *_storage)

/* 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);
Expand Down

0 comments on commit 9fe890f

Please sign in to comment.