dovecot-2.1: director: Handle all commands during handshake.
dovecot at dovecot.org
dovecot at dovecot.org
Tue Apr 3 00:50:23 EEST 2012
details: http://hg.dovecot.org/dovecot-2.1/rev/544a8c4705e5
changeset: 14372:544a8c4705e5
user: Timo Sirainen <tss at iki.fi>
date: Tue Apr 03 00:50:12 2012 +0300
description:
director: Handle all commands during handshake.
Previously the sender might have sent these commands, but the receiver
wouldn't have handled them and instead just disconnected.
diffstat:
src/director/director-connection.c | 166 +++++++++++++++++++++++-------------
src/director/director-connection.h | 1 +
src/director/director.c | 8 +-
3 files changed, 114 insertions(+), 61 deletions(-)
diffs (truncated from 317 to 300 lines):
diff -r d6cd93e32b37 -r 544a8c4705e5 src/director/director-connection.c
--- a/src/director/director-connection.c Tue Apr 03 00:23:02 2012 +0300
+++ b/src/director/director-connection.c Tue Apr 03 00:50:12 2012 +0300
@@ -1,5 +1,28 @@
/* Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file */
+/*
+ Handshaking:
+
+ Incoming director connections send:
+
+ VERSION
+ ME
+ <wait for remote handshake>
+ DONE
+
+ Outgoing director connections send:
+
+ VERSION
+ ME
+ [0..n] DIRECTOR
+ HOST-HAND-START
+ [0..n] HOST
+ HOST-HAND-END
+ [0..n] USER
+ <possibly other non-handshake commands between USERs>
+ DONE
+*/
+
#include "lib.h"
#include "ioloop.h"
#include "array.h"
@@ -33,6 +56,9 @@
mark the host as failed so we won't try to reconnect to it immediately */
#define DIRECTOR_SUCCESS_MIN_CONNECT_SECS 10
+#define CMD_IS_USER_HANDHAKE(args) \
+ (str_array_length(args) > 2)
+
struct director_connection {
struct director *dir;
char *name;
@@ -109,6 +135,10 @@
if (!director_args_parse_ip_port(conn, args, &ip, &port))
return FALSE;
+ if (conn->me_received) {
+ director_cmd_error(conn, "Duplicate ME");
+ return FALSE;
+ }
if (!conn->in && (!net_ip_compare(&conn->host->ip, &ip) ||
conn->host->port != port)) {
@@ -317,6 +347,8 @@
struct mail_host *host;
struct user *user;
+ /* NOTE: if more parameters are added, update also
+ CMD_IS_USER_HANDHAKE() macro */
if (str_array_length(args) != 2 ||
str_to_uint(args[0], &username_hash) < 0 ||
net_addr2ip(args[1], &ip) < 0) {
@@ -694,7 +726,7 @@
director_connection_set_ping_timeout(conn);
}
-static bool
+static int
director_connection_handle_handshake(struct director_connection *conn,
const char *cmd, const char *const *args)
{
@@ -708,29 +740,33 @@
i_error("director(%s): Wrong protocol in socket "
"(%s vs %s)",
conn->name, args[0], DIRECTOR_VERSION_NAME);
- return FALSE;
+ return -1;
} else if (atoi(args[1]) != DIRECTOR_VERSION_MAJOR) {
i_error("director(%s): Incompatible protocol version: "
"%u vs %u", conn->name, atoi(args[1]),
DIRECTOR_VERSION_MAJOR);
- return FALSE;
+ return -1;
}
conn->version_received = TRUE;
- return TRUE;
+ return 1;
}
if (!conn->version_received) {
director_cmd_error(conn, "Incompatible protocol");
- return FALSE;
+ return -1;
}
- if (strcmp(cmd, "ME") == 0 && !conn->me_received)
- return director_cmd_me(conn, args);
+ if (strcmp(cmd, "ME") == 0)
+ return director_cmd_me(conn, args) ? 1 : -1;
- /* only outgoing connections get a CONNECT reference */
- if (!conn->in && strcmp(cmd, "CONNECT") == 0) {
+ if (strcmp(cmd, "CONNECT") == 0) {
/* remote wants us to connect elsewhere */
if (!director_args_parse_ip_port(conn, args, &ip, &port))
- return FALSE;
+ return -1;
+ if (conn->in) {
+ director_cmd_error(conn,
+ "Incoming connections can't request CONNECT");
+ return -1;
+ }
conn->dir->right = NULL;
host = director_host_get(conn->dir, &ip, port);
@@ -740,53 +776,44 @@
if (conn->dir->debug)
i_debug("Received CONNECT reference to %s", host->name);
(void)director_connect_host(conn->dir, host);
- return FALSE;
+ return 1;
}
- /* only incoming connections get DIRECTOR and HOST lists */
- if (conn->in && strcmp(cmd, "DIRECTOR") == 0 && conn->me_received)
- return director_cmd_director(conn, args);
+ /* Only VERSION and CONNECT commands are allowed before ME */
+ if (!conn->me_received) {
+ director_cmd_error(conn, "Expecting ME command first");
+ return -1;
+ }
- if (strcmp(cmd, "HOST") == 0) {
- /* allow hosts from all connections always,
- this could be an host update */
- if (conn->handshake_sending_hosts)
- return director_cmd_host_handshake(conn, args);
- else
- return director_cmd_host(conn, args);
+ /* incoming connections get a HOST list */
+ if (conn->handshake_sending_hosts) {
+ if (strcmp(cmd, "HOST") == 0)
+ return director_cmd_host_handshake(conn, args) ? 1 : -1;
+ if (strcmp(cmd, "HOST-HAND-END") == 0) {
+ conn->ignore_host_events = FALSE;
+ conn->handshake_sending_hosts = FALSE;
+ return 1;
+ }
+ director_cmd_error(conn, "Unexpected command during host list");
+ return -1;
}
- if (conn->handshake_sending_hosts &&
- strcmp(cmd, "HOST-HAND-END") == 0) {
- conn->ignore_host_events = FALSE;
- conn->handshake_sending_hosts = FALSE;
- return TRUE;
+ if (strcmp(cmd, "HOST-HAND-START") == 0) {
+ if (!conn->in) {
+ director_cmd_error(conn,
+ "Host list is only for incoming connections");
+ return -1;
+ }
+ return director_cmd_host_hand_start(conn, args) ? 1 : -1;
}
- if (conn->in && strcmp(cmd, "HOST-HAND-START") == 0 &&
- conn->me_received)
- return director_cmd_host_hand_start(conn, args);
- /* only incoming connections get a full USER list, but outgoing
- connections can also receive USER updates during handshake and
- it wouldn't be safe to ignore them. */
- if (!conn->me_received) {
- /* no USER updates until ME */
- } else if (conn->in && strcmp(cmd, "USER") == 0) {
- return director_handshake_cmd_user(conn, args);
- } else if (!conn->in) {
- if (strcmp(cmd, "USER") == 0)
- return director_cmd_user(conn, args);
- if (strcmp(cmd, "USER-WEAK") == 0)
- return director_cmd_user_weak(conn, args);
+ if (conn->in && strcmp(cmd, "USER") == 0 && CMD_IS_USER_HANDHAKE(args))
+ return director_handshake_cmd_user(conn, args) ? 1 : -1;
+
+ /* both get DONE */
+ if (strcmp(cmd, "DONE") == 0) {
+ director_handshake_cmd_done(conn);
+ return 1;
}
- /* both get DONE */
- if (strcmp(cmd, "DONE") == 0 && !conn->handshake_received &&
- !conn->handshake_sending_hosts) {
- director_handshake_cmd_done(conn);
- return TRUE;
- }
- i_error("director(%s): Invalid handshake command: %s "
- "(in=%d me_received=%d)", conn->name, cmd,
- conn->in, conn->me_received);
- return FALSE;
+ return 0;
}
static void
@@ -806,6 +833,9 @@
/* stale SYNC event */
return;
}
+ /* sync_seq increases when we get disconnected, so we must be
+ successfully connected to both directions */
+ i_assert(dir->left != NULL && dir->right != NULL);
dir->ring_min_version = minor_version;
if (!dir->ring_handshaked) {
@@ -921,6 +951,8 @@
director_connection_handle_cmd(struct director_connection *conn,
const char *cmd, const char *const *args)
{
+ int ret;
+
/* ping/pong is always handled */
if (strcmp(cmd, "PING") == 0) {
director_connection_send(conn, "PONG\n");
@@ -930,7 +962,10 @@
return director_cmd_pong(conn);
if (!conn->handshake_received) {
- if (!director_connection_handle_handshake(conn, cmd, args)) {
+ ret = director_connection_handle_handshake(conn, cmd, args);
+ if (ret > 0)
+ return TRUE;
+ if (ret < 0) {
/* invalid commands during handshake,
we probably don't want to reconnect here */
if (conn->dir->debug) {
@@ -941,7 +976,7 @@
conn->host->last_failed = ioloop_time;
return FALSE;
}
- return TRUE;
+ /* allow also other commands during handshake */
}
if (strcmp(cmd, "USER") == 0)
@@ -1103,21 +1138,27 @@
static int director_connection_output(struct director_connection *conn)
{
- if (conn->user_iter != NULL)
+ if (conn->user_iter != NULL) {
+ /* still handshaking USER list */
return director_connection_send_users(conn);
- else
- return o_stream_flush(conn->output);
+ }
+ return o_stream_flush(conn->output);
}
static void
director_connection_init_timeout(struct director_connection *conn)
{
+ unsigned int secs = ioloop_time - conn->created;
+
if (conn->host != NULL)
conn->host->last_failed = ioloop_time;
- if (!conn->connected)
- i_error("director(%s): Connect timed out", conn->name);
- else
- i_error("director(%s): Handshaking timed out", conn->name);
+ if (!conn->connected) {
+ i_error("director(%s): Connect timed out (%u secs)",
+ conn->name, secs);
+ } else {
+ i_error("director(%s): Handshaking timed out (%u secs)",
+ conn->name, secs);
+ }
director_connection_disconnected(&conn);
}
@@ -1356,6 +1397,11 @@
return conn->host;
}
+bool director_connection_is_handshaked(struct director_connection *conn)
+{
+ return conn->handshake_received;
+}
+
bool director_connection_is_incoming(struct director_connection *conn)
{
return conn->in;
diff -r d6cd93e32b37 -r 544a8c4705e5 src/director/director-connection.h
--- a/src/director/director-connection.h Tue Apr 03 00:23:02 2012 +0300
+++ b/src/director/director-connection.h Tue Apr 03 00:50:12 2012 +0300
@@ -23,6 +23,7 @@
const char *director_connection_get_name(struct director_connection *conn);
struct director_host *
director_connection_get_host(struct director_connection *conn);
+bool director_connection_is_handshaked(struct director_connection *conn);
bool director_connection_is_incoming(struct director_connection *conn);
void director_connection_cork(struct director_connection *conn);
diff -r d6cd93e32b37 -r 544a8c4705e5 src/director/director.c
--- a/src/director/director.c Tue Apr 03 00:23:02 2012 +0300
More information about the dovecot-cvs
mailing list