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