changeset 831:7b012c42660c

irccd: move authentication part from transport_server, closes #995 @2h If a server requires a password and a client does not provide, the server is unable to accept a new client. Move the authentication/greetings part from the server into the client to let the transport_service processing input.
author David Demelier <markand@malikania.fr>
date Sun, 24 Feb 2019 15:50:58 +0100
parents 4f7e46e593fe
children 1ddf628464a5
files libirccd-daemon/irccd/daemon/transport_client.cpp libirccd-daemon/irccd/daemon/transport_client.hpp libirccd-daemon/irccd/daemon/transport_server.cpp libirccd-daemon/irccd/daemon/transport_service.cpp libirccd-daemon/irccd/daemon/transport_service.hpp tests/src/libirccd-daemon/CMakeLists.txt tests/src/libirccd-daemon/transports/CMakeLists.txt tests/src/libirccd-daemon/transports/main.cpp
diffstat 8 files changed, 217 insertions(+), 94 deletions(-) [+]
line wrap: on
line diff
--- a/libirccd-daemon/irccd/daemon/transport_client.cpp	Mon Feb 11 20:30:00 2019 +0100
+++ b/libirccd-daemon/irccd/daemon/transport_client.cpp	Sun Feb 24 15:50:58 2019 +0100
@@ -20,11 +20,49 @@
 
 #include <cassert>
 
+#include <irccd/json_util.hpp>
+
+#include "bot.hpp"
 #include "transport_client.hpp"
 #include "transport_server.hpp"
 
 namespace irccd::daemon {
 
+void transport_client::auth(handshake_handler handler)
+{
+	assert(handler);
+
+	const auto self = shared_from_this();
+
+	read([this, self, handler] (auto code, auto message) {
+		auto parent = parent_.lock();
+
+		if (code) {
+			handler(std::move(code));
+			return;
+		}
+
+		const json_util::deserializer doc(message);
+		const auto command = doc.get<std::string>("command");
+		const auto password = doc.get<std::string>("password");
+
+		if (!command || *command != "auth") {
+			code = bot_error::auth_required;
+			error(bot_error::auth_required);
+		} else if (!password || *password != parent->get_password()) {
+			code = bot_error::invalid_auth;
+			error(bot_error::invalid_auth);
+		} else {
+			code = bot_error::no_error;
+			state_ = state::ready;
+			success("auth");
+			parent->get_clients().insert(self);
+		}
+
+		handler(std::move(code));
+	});
+}
+
 void transport_client::flush()
 {
 	if (queue_.empty())
@@ -71,6 +109,46 @@
 	state_ = state;
 }
 
+void transport_client::handshake(handshake_handler handler)
+{
+	assert(handler);
+
+	const auto greetings = nlohmann::json({
+		{ "program",    "irccd"                 },
+		{ "major",      IRCCD_VERSION_MAJOR     },
+		{ "minor",      IRCCD_VERSION_MINOR     },
+		{ "patch",      IRCCD_VERSION_PATCH     },
+#if defined(IRCCD_HAVE_JS)
+		{ "javascript", true                    },
+#endif
+#if defined(IRCCD_HAVE_SSL)
+		{ "ssl",        true                    },
+#endif
+	});
+
+	const auto self = shared_from_this();
+
+	write(greetings, [this, self, handler] (auto code) {
+		auto parent = parent_.lock();
+
+		if (!parent)
+			return;
+
+		if (code) {
+			handler(std::move(code));
+			return;
+		}
+
+		if (!parent->get_password().empty())
+			auth(std::move(handler));
+		else {
+			state_ = state::ready;
+			parent->get_clients().insert(self);
+			handler(std::move(code));
+		}
+	});
+}
+
 void transport_client::read(stream::recv_handler handler)
 {
 	assert(handler);
--- a/libirccd-daemon/irccd/daemon/transport_client.hpp	Mon Feb 11 20:30:00 2019 +0100
+++ b/libirccd-daemon/irccd/daemon/transport_client.hpp	Sun Feb 24 15:50:58 2019 +0100
@@ -45,6 +45,11 @@
 class transport_client : public std::enable_shared_from_this<transport_client> {
 public:
 	/**
+	 * Handler for handshaking.
+	 */
+	using handshake_handler = std::function<void (std::error_code)>;
+
+	/**
 	 * Client state.
 	 */
 	enum class state {
@@ -59,6 +64,7 @@
 	std::shared_ptr<stream> stream_;
 	std::deque<std::pair<nlohmann::json, stream::send_handler>> queue_;
 
+	void auth(handshake_handler);
 	void flush();
 	void erase();
 
@@ -88,6 +94,14 @@
 	void set_state(state state) noexcept;
 
 	/**
+	 * Do greetings and authentication if required.
+	 *
+	 * This function should be called after a new client has been accepted
+	 * by a transport_server.
+	 */
+	void handshake(handshake_handler);
+
+	/**
 	 * Start receiving if not closed.
 	 *
 	 * Possible error codes:
--- a/libirccd-daemon/irccd/daemon/transport_server.cpp	Mon Feb 11 20:30:00 2019 +0100
+++ b/libirccd-daemon/irccd/daemon/transport_server.cpp	Sun Feb 24 15:50:58 2019 +0100
@@ -21,80 +21,11 @@
 #include <cassert>
 #include <system_error>
 
-#include <irccd/json_util.hpp>
-
-#include "bot.hpp"
 #include "transport_client.hpp"
 #include "transport_server.hpp"
 
 namespace irccd::daemon {
 
-void transport_server::do_auth(std::shared_ptr<transport_client> client, accept_handler handler)
-{
-	assert(client);
-	assert(handler);
-
-	client->read([this, client, handler] (auto code, auto message) {
-		if (code) {
-			handler(std::move(code), std::move(client));
-			return;
-		}
-
-		const json_util::deserializer doc(message);
-		const auto command = doc.get<std::string>("command");
-		const auto password = doc.get<std::string>("password");
-
-		if (!command || *command != "auth") {
-			client->error(bot_error::auth_required);
-			code = bot_error::auth_required;
-		} else if (!password || *password != password_) {
-			client->error(bot_error::invalid_auth);
-			code = bot_error::invalid_auth;
-		} else {
-			clients_.insert(client);
-			client->set_state(transport_client::state::ready);
-			client->success("auth");
-			code = bot_error::no_error;
-		}
-
-		handler(std::move(code), std::move(client));
-	});
-}
-
-void transport_server::do_greetings(std::shared_ptr<transport_client> client, accept_handler handler)
-{
-	assert(client);
-	assert(handler);
-
-	const auto greetings = nlohmann::json({
-		{ "program",    "irccd"                 },
-		{ "major",      IRCCD_VERSION_MAJOR     },
-		{ "minor",      IRCCD_VERSION_MINOR     },
-		{ "patch",      IRCCD_VERSION_PATCH     },
-#if defined(IRCCD_HAVE_JS)
-		{ "javascript", true                    },
-#endif
-#if defined(IRCCD_HAVE_SSL)
-		{ "ssl",        true                    },
-#endif
-	});
-
-	client->write(greetings, [this, client, handler] (auto code) {
-		if (code) {
-			handler(std::move(code), std::move(client));
-			return;
-		}
-
-		if (!password_.empty())
-			do_auth(std::move(client), std::move(handler));
-		else {
-			clients_.insert(client);
-			client->set_state(transport_client::state::ready);
-			handler(std::move(code), std::move(client));
-		}
-	});
-}
-
 transport_server::transport_server(std::unique_ptr<acceptor> acceptor) noexcept
 	: acceptor_(std::move(acceptor))
 {
@@ -124,15 +55,10 @@
 void transport_server::accept(accept_handler handler)
 {
 	acceptor_->accept([this, handler] (auto code, auto stream) {
-		if (code) {
+		if (code)
 			handler(code, nullptr);
-			return;
-		}
-
-		do_greetings(
-			std::make_shared<transport_client>(shared_from_this(), std::move(stream)),
-			std::move(handler)
-		);
+		else
+			handler(code, std::make_shared<transport_client>(shared_from_this(), std::move(stream)));
 	});
 }
 
--- a/libirccd-daemon/irccd/daemon/transport_service.cpp	Mon Feb 11 20:30:00 2019 +0100
+++ b/libirccd-daemon/irccd/daemon/transport_service.cpp	Sun Feb 24 15:50:58 2019 +0100
@@ -32,7 +32,7 @@
 
 namespace irccd::daemon {
 
-void transport_service::handle_command(std::shared_ptr<transport_client> tc, const nlohmann::json& object)
+void transport_service::handle_command(std::shared_ptr<transport_client> client, const nlohmann::json& object)
 {
 	assert(object.is_object());
 
@@ -40,7 +40,7 @@
 	const auto name = doc.get<std::string>("command");
 
 	if (!name) {
-		tc->error(bot_error::invalid_message);
+		client->error(bot_error::invalid_message);
 		return;
 	}
 
@@ -49,12 +49,12 @@
 	});
 
 	if (cmd == commands_.end())
-		tc->error(bot_error::invalid_command, *name);
+		client->error(bot_error::invalid_command, *name);
 	else {
 		try {
-			(*cmd)->exec(bot_, *tc, doc);
+			(*cmd)->exec(bot_, *client, doc);
 		} catch (const std::system_error& ex) {
-			tc->error(ex.code(), (*cmd)->get_name());
+			client->error(ex.code(), (*cmd)->get_name());
 		} catch (const std::exception& ex) {
 			bot_.get_log().warning("transport", "")
 				<< "unknown error not reported: "
@@ -63,23 +63,23 @@
 	}
 }
 
-void transport_service::do_recv(std::shared_ptr<transport_client> tc)
+void transport_service::recv(std::shared_ptr<transport_client> client)
 {
-	tc->read([this, tc] (auto code, auto json) {
+	client->read([this, client] (auto code, auto json) {
 		switch (static_cast<std::errc>(code.value())) {
 		case std::errc::not_connected:
 			bot_.get_log().info("transport", "") << "client disconnected" << std::endl;
 			break;
 		case std::errc::invalid_argument:
-			tc->error(bot_error::invalid_message);
+			client->error(bot_error::invalid_message);
 			break;
 		default:
 			// Other errors.
 			if (!code) {
-				handle_command(tc, json);
+				handle_command(client, json);
 
-				if (tc->get_state() == transport_client::state::ready)
-					do_recv(std::move(tc));
+				if (client->get_state() == transport_client::state::ready)
+					recv(std::move(client));
 			}
 
 			break;
@@ -87,12 +87,25 @@
 	});
 }
 
-void transport_service::do_accept(transport_server& ts)
+void transport_service::handshake(std::shared_ptr<transport_client> client)
+{
+	client->handshake([this, client] (auto code) {
+		if (code)
+			bot_.get_log().warning("transport", "")
+				<< "error while handshaking: " << code.message() << std::endl;
+		else {
+			bot_.get_log().info("transport", "") << "client ready" << std::endl;
+			recv(std::move(client));
+		}
+	});
+}
+
+void transport_service::accept(transport_server& ts)
 {
 	ts.accept([this, &ts] (auto code, auto client) {
 		if (!code) {
-			do_accept(ts);
-			do_recv(std::move(client));
+			accept(ts);
+			handshake(std::move(client));
 
 			bot_.get_log().info("transport", "") << "new client connected" << std::endl;
 		}
@@ -120,7 +133,7 @@
 {
 	assert(ts);
 
-	do_accept(*ts);
+	accept(*ts);
 	servers_.push_back(std::move(ts));
 }
 
--- a/libirccd-daemon/irccd/daemon/transport_service.hpp	Mon Feb 11 20:30:00 2019 +0100
+++ b/libirccd-daemon/irccd/daemon/transport_service.hpp	Sun Feb 24 15:50:58 2019 +0100
@@ -65,8 +65,9 @@
 	servers servers_;
 
 	void handle_command(std::shared_ptr<transport_client>, const nlohmann::json&);
-	void do_recv(std::shared_ptr<transport_client>);
-	void do_accept(transport_server&);
+	void recv(std::shared_ptr<transport_client>);
+	void handshake(std::shared_ptr<transport_client>);
+	void accept(transport_server&);
 
 public:
 	/**
--- a/tests/src/libirccd-daemon/CMakeLists.txt	Mon Feb 11 20:30:00 2019 +0100
+++ b/tests/src/libirccd-daemon/CMakeLists.txt	Sun Feb 24 15:50:58 2019 +0100
@@ -51,3 +51,4 @@
 add_subdirectory(rule-util)
 add_subdirectory(server)
 add_subdirectory(server-util)
+add_subdirectory(transports)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-daemon/transports/CMakeLists.txt	Sun Feb 24 15:50:58 2019 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2019 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+	NAME transports
+	SOURCES main.cpp
+	LIBRARIES libirccd
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/libirccd-daemon/transports/main.cpp	Sun Feb 24 15:50:58 2019 +0100
@@ -0,0 +1,67 @@
+/*
+ * main.cpp -- test server object
+ *
+ * Copyright (c) 2013-2019 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "transports"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/daemon/transport_server.hpp>
+
+namespace asio = boost::asio;
+
+namespace irccd::daemon {
+
+namespace {
+
+BOOST_AUTO_TEST_CASE(fix_995)
+{
+	asio::io_context ctx;
+	asio::ip::tcp::socket cl1(ctx);
+	asio::ip::tcp::socket cl2(ctx);
+
+	/*
+	 * a server that waits for authentication, the client does not send
+	 * anything the handler will never be executed.
+	 */
+	auto acc = std::make_unique<ip_acceptor>(ctx, "*", 0, true, false);
+	auto ep = acc->get_acceptor().local_endpoint();
+	auto tpt = std::make_shared<transport_server>(std::move(acc));
+	auto accepted = false;
+	auto connected1 = false;
+	auto connected2 = false;
+
+	tpt->set_password("test");
+	tpt->accept([&accepted] (auto, auto) {
+		accepted = true;
+	});
+	cl1.async_connect(ep, [&connected1] (auto) {
+		connected1 = true;
+	});
+	cl2.async_connect(ep, [&connected2] (auto) {
+		connected2 = true;
+	});
+
+	ctx.run();
+
+	BOOST_TEST(accepted);
+	BOOST_TEST(connected1);
+	BOOST_TEST(connected2);
+}
+
+} // !namespace
+
+} // !irccd::daemon