Mercurial > irccd
changeset 534:2326a4dc39e6
Irccd: rewrite transports in Boost.Asio, closes #681
author | David Demelier <markand@malikania.fr> |
---|---|
date | Fri, 17 Nov 2017 20:56:10 +0100 |
parents | d5b6dd7c2311 |
children | 7e9bf74e0fd5 |
files | irccd/main.cpp libirccd-test/irccd/js_test.hpp libirccd-test/irccd/plugin_test.hpp libirccd/CMakeLists.txt libirccd/irccd/command.cpp libirccd/irccd/config.cpp libirccd/irccd/config.hpp libirccd/irccd/irccd.cpp libirccd/irccd/irccd.hpp libirccd/irccd/service.cpp libirccd/irccd/service.hpp libirccd/irccd/transport.cpp libirccd/irccd/transport.hpp libirccd/irccd/transport_client.cpp libirccd/irccd/transport_client.hpp libirccd/irccd/transport_server.cpp libirccd/irccd/transport_server.hpp tests/js-plugin/main.cpp |
diffstat | 18 files changed, 1098 insertions(+), 1007 deletions(-) [+] |
line wrap: on
line diff
--- a/irccd/main.cpp Fri Nov 17 19:12:32 2017 +0100 +++ b/irccd/main.cpp Fri Nov 17 20:56:10 2017 +0100 @@ -271,7 +271,7 @@ load_foreground(config.is_foreground(), options); // [transport] - for (const auto& transport : config.load_transports()) + for (const auto& transport : config.load_transports(*instance)) instance->transports().add(transport); // [server] section. @@ -296,9 +296,11 @@ init(argc, argv); - option::result options = parse(argc, argv); + boost::asio::io_service service; - instance = std::make_unique<class irccd>(); + auto options = parse(argc, argv); + + instance = std::make_unique<class irccd>(service); instance->commands().add(std::make_unique<plugin_config_command>()); instance->commands().add(std::make_unique<plugin_info_command>()); instance->commands().add(std::make_unique<plugin_list_command>());
--- a/libirccd-test/irccd/js_test.hpp Fri Nov 17 19:12:32 2017 +0100 +++ b/libirccd-test/irccd/js_test.hpp Fri Nov 17 20:56:10 2017 +0100 @@ -24,6 +24,8 @@ * \brief Test fixture helper for Javascript modules. */ +#include <boost/asio.hpp> + #include <irccd/irccd.hpp> #include <irccd/js/js_plugin.hpp> @@ -53,7 +55,8 @@ } public: - irccd irccd_; //!< Irccd instance. + boost::asio::io_service service_; + irccd irccd_{service_}; //!< Irccd instance. std::shared_ptr<js_plugin> plugin_; //!< Javascript plugin. std::shared_ptr<journal_server> server_; //!< A journal server.
--- a/libirccd-test/irccd/plugin_test.hpp Fri Nov 17 19:12:32 2017 +0100 +++ b/libirccd-test/irccd/plugin_test.hpp Fri Nov 17 20:56:10 2017 +0100 @@ -24,6 +24,8 @@ * \brief test fixture helper for Javascript plugins. */ +#include <boost/asio/io_service.hpp> + #include <js/js_plugin.hpp> #include "irccd.hpp" @@ -38,7 +40,8 @@ */ class plugin_test { protected: - irccd irccd_; + boost::asio::io_service service_; + irccd irccd_{service_}; std::shared_ptr<js_plugin> plugin_; std::shared_ptr<journal_server> server_;
--- a/libirccd/CMakeLists.txt Fri Nov 17 19:12:32 2017 +0100 +++ b/libirccd/CMakeLists.txt Fri Nov 17 20:56:10 2017 +0100 @@ -30,7 +30,8 @@ ${libirccd_SOURCE_DIR}/irccd/rule.hpp ${libirccd_SOURCE_DIR}/irccd/server.hpp ${libirccd_SOURCE_DIR}/irccd/service.hpp - ${libirccd_SOURCE_DIR}/irccd/transport.hpp + ${libirccd_SOURCE_DIR}/irccd/transport_client.hpp + ${libirccd_SOURCE_DIR}/irccd/transport_server.hpp ) set( @@ -43,7 +44,8 @@ ${libirccd_SOURCE_DIR}/irccd/rule.cpp ${libirccd_SOURCE_DIR}/irccd/server.cpp ${libirccd_SOURCE_DIR}/irccd/service.cpp - ${libirccd_SOURCE_DIR}/irccd/transport.cpp + ${libirccd_SOURCE_DIR}/irccd/transport_client.cpp + ${libirccd_SOURCE_DIR}/irccd/transport_server.cpp ) irccd_define_library(
--- a/libirccd/irccd/command.cpp Fri Nov 17 19:12:32 2017 +0100 +++ b/libirccd/irccd/command.cpp Fri Nov 17 20:56:10 2017 +0100 @@ -20,7 +20,7 @@ #include "irccd.hpp" #include "json_util.hpp" #include "service.hpp" -#include "transport.hpp" +#include "transport_client.hpp" #include "util.hpp" using namespace std::string_literals; @@ -66,8 +66,9 @@ * * It's easier for the client to iterate over all. */ - client.success("plugin-config", { - { "variables", variables } + client.send({ + { "command", "plugin-config" }, + { "variables", variables } }); } @@ -161,7 +162,8 @@ { auto plugin = irccd.plugins().require(json_util::require_identifier(args, "plugin")); - client.success("plugin-info", { + client.send({ + { "command", "plugin-info" }, { "author", plugin->author() }, { "license", plugin->license() }, { "summary", plugin->summary() }, @@ -181,8 +183,9 @@ for (const auto& plugin : irccd.plugins().list()) list += plugin->name(); - client.success("plugin-list", { - { "list", list } + client.send({ + { "command", "plugin-list" }, + { "list", list } }); } @@ -292,6 +295,7 @@ auto server = irccd.servers().require(json_util::require_identifier(args, "server")); // General stuff. + response.push_back({"command", "server-info"}); response.push_back({"name", server->name()}); response.push_back({"host", server->host()}); response.push_back({"port", server->port()}); @@ -308,7 +312,7 @@ if (server->flags() & server::ssl_verify) response.push_back({"sslVerify", true}); - client.success("server-info", response); + client.send(response); } server_invite_command::server_invite_command() @@ -367,8 +371,10 @@ for (const auto& server : irccd.servers().servers()) list.push_back(server->name()); - json.push_back({"list", std::move(list)}); - client.success("server-list", json); + client.send({ + { "command", "server-list" }, + { "list", std::move(json) } + }); } server_me_command::server_me_command() @@ -547,7 +553,10 @@ for (const auto& rule : irccd.rules().list()) array.push_back(to_json(rule)); - client.success("rule-list", {{ "list", std::move(array) }}); + client.send({ + { "command", "rule-list" }, + { "list", std::move(array) } + }); } rule_info_command::rule_info_command() @@ -557,7 +566,10 @@ void rule_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) { - client.success("rule-info", to_json(irccd.rules().require(json_util::require_uint(args, "index")))); + auto json = to_json(irccd.rules().require(json_util::require_uint(args, "index"))); + + json.push_back({"command", "rule-info"}); + client.send(std::move(json)); } rule_remove_command::rule_remove_command()
--- a/libirccd/irccd/config.cpp Fri Nov 17 19:12:32 2017 +0100 +++ b/libirccd/irccd/config.cpp Fri Nov 17 20:56:10 2017 +0100 @@ -30,7 +30,7 @@ #include "string_util.hpp" #include "sysconfig.hpp" #include "system.hpp" -#include "transport.hpp" +#include "transport_server.hpp" namespace irccd { @@ -119,7 +119,7 @@ #endif // !HAVE_SYSLOG } -std::shared_ptr<transport_server> load_transport_ip(const ini::section& sc) +std::shared_ptr<transport_server> load_transport_ip(boost::asio::io_service& service, const ini::section& sc) { assert(sc.key() == "transport"); @@ -144,7 +144,10 @@ if ((it = sc.find("address")) != sc.end()) address = it->value(); - std::uint8_t mode = transport_server_ip::v4; + // 0011 + // ^ define IPv4 + // ^ define IPv6 + auto mode = 1U; /* * Documentation stated family but code checked for 'domain' option. @@ -154,16 +157,21 @@ * See #637 */ if ((it = sc.find("domain")) != sc.end() || (it = sc.find("family")) != sc.end()) { - mode = 0; - for (const auto& v : *it) { if (v == "ipv4") - mode |= transport_server_ip::v4; + mode |= (1U << 0); if (v == "ipv6") - mode |= transport_server_ip::v6; + mode |= (1U << 1); } } + if (mode == 0U) + throw std::invalid_argument("transport: family must at least have ipv4 or ipv6"); + + auto protocol = (mode & 0x2U) + ? boost::asio::ip::tcp::v4() + : boost::asio::ip::tcp::v6(); + // Optional SSL. std::string pkey; std::string cert; @@ -180,21 +188,31 @@ pkey = it->value(); } - if (mode == 0) - throw std::invalid_argument("transport: family must at least have ipv4 or ipv6"); + auto endpoint = (address == "*") + ? boost::asio::ip::tcp::endpoint(protocol, port) + : boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port); + + boost::asio::ip::tcp::acceptor acceptor(service, endpoint, true); if (pkey.empty()) - return std::make_shared<transport_server_ip>(address, port, mode); + return std::make_shared<tcp_transport_server>(std::move(acceptor)); #if defined(HAVE_SSL) - return std::make_shared<transport_server_tls>(pkey, cert, address, port, mode); + boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23); + + ctx.use_private_key_file(pkey, boost::asio::ssl::context::pem); + ctx.use_certificate_file(cert, boost::asio::ssl::context::pem); + + return std::make_shared<tls_transport_server>(std::move(acceptor), std::move(ctx)); #else throw std::invalid_argument("transport: SSL disabled"); #endif } -std::shared_ptr<transport_server> load_transport_unix(const ini::section& sc) +std::shared_ptr<transport_server> load_transport_unix(boost::asio::io_service& service, const ini::section& sc) { + using boost::asio::local::stream_protocol; + assert(sc.key() == "transport"); #if !defined(IRCCD_SYSTEM_WINDOWS) @@ -203,7 +221,10 @@ if (it == sc.end()) throw std::invalid_argument("transport: missing 'path' parameter"); - return std::make_shared<transport_server_local>(it->value()); + stream_protocol::endpoint endpoint(it->value()); + stream_protocol::acceptor acceptor(service, std::move(endpoint)); + + return std::make_shared<local_transport_server>(std::move(acceptor)); #else (void)sc; @@ -211,7 +232,7 @@ #endif } -std::shared_ptr<transport_server> load_transport(const ini::section& sc) +std::shared_ptr<transport_server> load_transport(boost::asio::io_service& service, const ini::section& sc) { assert(sc.key() == "transport"); @@ -222,9 +243,9 @@ throw std::invalid_argument("transport: missing 'type' parameter"); if (it->value() == "ip") - transport = load_transport_ip(sc); + transport = load_transport_ip(service, sc); else if (it->value() == "unix") - transport = load_transport_unix(sc); + transport = load_transport_unix(service, sc); else throw std::invalid_argument(string_util::sprintf("transport: invalid type given: %s", it->value())); @@ -472,13 +493,13 @@ log::set_filter(std::move(filter)); } -std::vector<std::shared_ptr<transport_server>> config::load_transports() const +std::vector<std::shared_ptr<transport_server>> config::load_transports(irccd& irccd) const { std::vector<std::shared_ptr<transport_server>> transports; for (const auto& section : document_) if (section.key() == "transport") - transports.push_back(load_transport(section)); + transports.push_back(load_transport(irccd.service(), section)); return transports; }
--- a/libirccd/irccd/config.hpp Fri Nov 17 19:12:32 2017 +0100 +++ b/libirccd/irccd/config.hpp Fri Nov 17 20:56:10 2017 +0100 @@ -146,9 +146,10 @@ /** * Load transports. * + * \param irccd the irccd instance * \return the set of transports */ - std::vector<std::shared_ptr<transport_server>> load_transports() const; + std::vector<std::shared_ptr<transport_server>> load_transports(irccd& irccd) const; /** * Load rules.
--- a/libirccd/irccd/irccd.cpp Fri Nov 17 19:12:32 2017 +0100 +++ b/libirccd/irccd/irccd.cpp Fri Nov 17 20:56:10 2017 +0100 @@ -24,8 +24,9 @@ namespace irccd { -irccd::irccd(std::string config) +irccd::irccd(boost::asio::io_service& service, std::string config) : config_(std::move(config)) + , service_(service) , command_service_(std::make_unique<command_service>()) , itr_service_(std::make_unique<interrupt_service>()) , server_service_(std::make_unique<server_service>(*this)) @@ -47,13 +48,15 @@ void irccd::run() { - while (running_) - net_util::poll(250, *this); + while (running_) { + net_util::poll(100, *this); + service_.poll(); + } } void irccd::prepare(fd_set& in, fd_set& out, net::Handle& max) { - net_util::prepare(in, out, max, *itr_service_, *server_service_, *tpt_service_); + net_util::prepare(in, out, max, *itr_service_, *server_service_); } void irccd::sync(fd_set& in, fd_set& out) @@ -61,7 +64,7 @@ if (!running_) return; - net_util::sync(in, out, *itr_service_, *server_service_, *tpt_service_); + net_util::sync(in, out, *itr_service_, *server_service_); if (!running_) return;
--- a/libirccd/irccd/irccd.hpp Fri Nov 17 19:12:32 2017 +0100 +++ b/libirccd/irccd/irccd.hpp Fri Nov 17 20:56:10 2017 +0100 @@ -30,6 +30,8 @@ #include <mutex> #include <vector> +#include <boost/asio.hpp> + #include "config.hpp" #include "net.hpp" #include "sysconfig.hpp" @@ -54,6 +56,9 @@ // Configurations. class config config_; + // Main io service. + boost::asio::io_service& service_; + // Main loop stuff. std::atomic<bool> running_{true}; std::mutex mutex_; @@ -78,9 +83,10 @@ /** * Prepare standard services. * + * \param service the service * \param config the optional path to the configuration. */ - irccd(std::string config = ""); + irccd(boost::asio::io_service& service, std::string config = ""); /** * Default destructor. @@ -107,6 +113,16 @@ config_ = std::move(cfg); } + inline const boost::asio::io_service& service() const noexcept + { + return service_; + } + + inline boost::asio::io_service& service() noexcept + { + return service_; + } + /** * Access the command service. *
--- a/libirccd/irccd/service.cpp Fri Nov 17 19:12:32 2017 +0100 +++ b/libirccd/irccd/service.cpp Fri Nov 17 20:56:10 2017 +0100 @@ -27,7 +27,8 @@ #include "service.hpp" #include "string_util.hpp" #include "system.hpp" -#include "transport.hpp" +#include "transport_client.hpp" +#include "transport_server.hpp" using namespace std::string_literals; @@ -923,17 +924,11 @@ * ------------------------------------------------------------------ */ -void transport_service::handle_command(std::weak_ptr<transport_client> ptr, const nlohmann::json& object) +void transport_service::handle_command(std::shared_ptr<transport_client> tc, const nlohmann::json& object) { assert(object.is_object()); irccd_.post([=] (irccd&) { - // 0. Be sure the object still exists. - auto tc = ptr.lock(); - - if (!tc) - return; - auto name = object.find("command"); if (name == object.end() || !name->is_string()) { // TODO: send error. @@ -955,15 +950,21 @@ }); } -void transport_service::handle_die(std::weak_ptr<transport_client> ptr) +void transport_service::do_accept(std::shared_ptr<transport_server> ts) { - irccd_.post([=] (irccd &) { - log::info("transport: client disconnected"); + ts->accept([this, ts] (auto client, auto code) { + if (code) { + log::warning() << "transport: " << code.message() << std::endl; + } else { + client->recv([this, client] (auto json, auto code) { + if (code) + log::warning() << "transport: " << code.message() << std::endl; + else + handle_command(client, json); + }); + } - auto tc = ptr.lock(); - - if (tc) - clients_.erase(std::find(clients_.begin(), clients_.end(), tc)); + do_accept(ts); }); } @@ -972,58 +973,11 @@ { } -void transport_service::prepare(fd_set& in, fd_set& out, net::Handle& max) -{ - // Add transport servers. - for (const auto& transport : servers_) { - FD_SET(transport->handle(), &in); - - if (transport->handle() > max) - max = transport->handle(); - } - - // Transport clients. - for (const auto& client : clients_) - client->prepare(in, out, max); -} - -void transport_service::sync(fd_set& in, fd_set& out) -{ - // Transport clients. - for (const auto& client : clients_) { - try { - client->sync(in, out); - } catch (const std::exception& ex) { - log::info() << "transport: client disconnected: " << ex.what() << std::endl; - handle_die(client); - } - } - - // Transport servers. - for (const auto& transport : servers_) { - if (!FD_ISSET(transport->handle(), &in)) - continue; - - log::debug("transport: new client connected"); - - std::shared_ptr<transport_client> client = transport->accept(); - std::weak_ptr<transport_client> ptr(client); - - try { - // Connect signals. - client->on_command.connect(boost::bind(&transport_service::handle_command, this, ptr, _1)); - client->on_die.connect(boost::bind(&transport_service::handle_die, this, ptr)); - - // Register it. - clients_.push_back(std::move(client)); - } catch (const std::exception& ex) { - log::info() << "transport: client disconnected: " << ex.what() << std::endl; - } - } -} - void transport_service::add(std::shared_ptr<transport_server> ts) { + assert(ts); + + do_accept(ts); servers_.push_back(std::move(ts)); } @@ -1031,8 +985,8 @@ { assert(json.is_object()); - for (const auto& client : clients_) - if (client->state() == transport_client::state::ready) + for (const auto& servers : servers_) + for (const auto& client : servers->clients()) client->send(json); }
--- a/libirccd/irccd/service.hpp Fri Nov 17 19:12:32 2017 +0100 +++ b/libirccd/irccd/service.hpp Fri Nov 17 20:56:10 2017 +0100 @@ -486,14 +486,15 @@ * \ingroup services */ class transport_service { +public: + using servers_t = std::vector<std::shared_ptr<transport_server>>; + private: irccd& irccd_; + servers_t servers_; - std::vector<std::shared_ptr<transport_server>> servers_; - std::vector<std::shared_ptr<transport_client>> clients_; - - void handle_command(std::weak_ptr<transport_client>, const nlohmann::json&); - void handle_die(std::weak_ptr<transport_client>); + void handle_command(std::shared_ptr<transport_client>, const nlohmann::json&); + void do_accept(std::shared_ptr<transport_server>); public: /** @@ -504,16 +505,6 @@ transport_service(irccd& irccd) noexcept; /** - * \copydoc Service::prepare - */ - void prepare(fd_set& in, fd_set& out, net::Handle& max); - - /** - * \copydoc Service::sync - */ - void sync(fd_set& in, fd_set& out); - - /** * Add a transport server. * * \param ts the transport server
--- a/libirccd/irccd/transport.cpp Fri Nov 17 19:12:32 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,456 +0,0 @@ -/* - * transport.cpp -- irccd transports - * - * Copyright (c) 2013-2017 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. - */ - -#include <cassert> -#include <cstdio> - -#include "transport.hpp" - -namespace irccd { - -/* - * transport_client - * ------------------------------------------------------------------ - */ - -void transport_client::error(const std::string& msg) -{ - state_ = state::closing; - - send({{ "error", msg }}); -} - -void transport_client::flush() noexcept -{ - for (std::size_t pos; (pos = input_.find("\r\n\r\n")) != std::string::npos; ) { - auto message = input_.substr(0, pos); - - input_.erase(input_.begin(), input_.begin() + pos + 4); - - try { - auto document = nlohmann::json::parse(message); - - if (!document.is_object()) - error("invalid argument"); - else - on_command(document); - } catch (const std::exception& ex) { - error(ex.what()); - } - } -} - -void transport_client::authenticate() noexcept -{ - auto pos = input_.find("\r\n\r\n"); - - if (pos == std::string::npos) - return; - - auto msg = input_.substr(0, pos); - - input_.erase(input_.begin(), input_.begin() + pos + 4); - - try { - auto doc = nlohmann::json::parse(msg); - - if (!doc.is_object()) - error("invalid argument"); - - auto cmd = doc.find("command"); - - if (cmd == doc.end() || !cmd->is_string() || *cmd != "auth") - error("authentication required"); - - auto pw = doc.find("password"); - auto result = true; - - if (pw == doc.end() || !pw->is_string() || *pw != parent_.password()) { - state_ = state::closing; - result = false; - } else - state_ = state::ready; - - send({ - { "response", "auth" }, - { "result", result } - }); - } catch (const std::exception& ex) { - error(ex.what()); - } -} - -void transport_client::recv() noexcept -{ - try { - std::string buffer; - - buffer.resize(512); - buffer.resize(recv(&buffer[0], buffer.size())); - - if (buffer.empty()) - on_die(); - - input_ += std::move(buffer); - } catch (const std::exception &) { - on_die(); - } -} - -void transport_client::send() noexcept -{ - try { - auto ns = send(&output_[0], output_.size()); - - if (ns == 0) - on_die(); - - output_.erase(0, ns); - } catch (const std::exception&) { - on_die(); - } -} - -unsigned transport_client::recv(void* buffer, unsigned length) -{ - return socket_.recv(buffer, length); -} - -unsigned transport_client::send(const void* buffer, unsigned length) -{ - return socket_.send(buffer, length); -} - -transport_client::transport_client(transport_server& parent, net::TcpSocket socket) - : parent_(parent) - , socket_(std::move(socket)) -{ - assert(socket_.isOpen()); - - socket_.set(net::option::SockBlockMode(false)); - - // Send some information. - auto object = nlohmann::json::object({ - { "program", "irccd" }, - { "major", IRCCD_VERSION_MAJOR }, - { "minor", IRCCD_VERSION_MINOR }, - { "patch", IRCCD_VERSION_PATCH } - }); - -#if defined(HAVE_JS) - object.push_back({"javascript", true}); -#endif -#if defined(HAVE_SSL) - object.push_back({"ssl", true}); -#endif - - send(object); -} - -void transport_client::prepare(fd_set& in, fd_set& out, net::Handle& max) -{ - if (socket_.handle() > max) - max = socket_.handle(); - - switch (state_) { - case state::greeting: - FD_SET(socket_.handle(), &in); - FD_SET(socket_.handle(), &out); - break; - case state::authenticating: - FD_SET(socket_.handle(), &in); - break; - case state::ready: - FD_SET(socket_.handle(), &in); - - if (!output_.empty()) - FD_SET(socket_.handle(), &out); - break; - case state::closing: - if (!output_.empty()) - FD_SET(socket_.handle(), &out); - else - on_die(); - break; - default: - break; - } -} - -void transport_client::sync(fd_set& in, fd_set& out) -{ - switch (state_) { - case state::greeting: - if (FD_ISSET(socket_.handle(), &in)) - recv(); - else if (FD_ISSET(socket_.handle(), &out)) - send(); - - if (output_.empty()) - state_ = parent_.password().empty() ? state::ready : state::authenticating; - - break; - case state::authenticating: - if (FD_ISSET(socket_.handle(), &in)) - recv(); - - authenticate(); - break; - case state::ready: - if (FD_ISSET(socket_.handle(), &in)) - recv(); - if (FD_ISSET(socket_.handle(), &out)) - send(); - - flush(); - break; - case state::closing: - if (FD_ISSET(socket_.handle(), &out)) - send(); - break; - default: - break; - } -} - -void transport_client::send(const nlohmann::json& json) -{ - assert(json.is_object()); - - output_ += json.dump(); - output_ += "\r\n\r\n"; -} - -void transport_client::success(const std::string& cmd, nlohmann::json extra) -{ - assert(extra.is_object() || extra.is_null()); - - if (!extra.is_object()) - extra = nlohmann::json::object(); - - extra["command"] = cmd; - extra["status"] = true; - - output_ += extra.dump(); - output_ += "\r\n\r\n"; -} - -void transport_client::error(const std::string& cmd, const std::string& error, nlohmann::json extra) -{ - assert(extra.is_object() || extra.is_null()); - - if (!extra.is_object()) - extra = nlohmann::json::object(); - - extra["command"] = cmd; - extra["status"] = false; - extra["error"] = error; - - output_ += extra.dump(); - output_ += "\r\n\r\n"; -} - -/* - * transport_client_tls - * ------------------------------------------------------------------ - */ - -#if defined(HAVE_SSL) - -void transport_client_tls::handshake() -{ - try { - ssl_.handshake(); - handshake_ = handshake::ready; - } catch (const net::WantReadError&) { - handshake_ = handshake::read; - } catch (const net::WantWriteError&) { - handshake_ = handshake::write; - } catch (const std::exception&) { - on_die(); - } -} - -transport_client_tls::transport_client_tls(const std::string& pkey, - const std::string& cert, - transport_server& parent, - net::TcpSocket socket) - : transport_client(parent, std::move(socket)) - , ssl_(socket_) -{ - ssl_.setPrivateKey(pkey); - ssl_.setCertificate(cert); - - handshake(); -} - -unsigned transport_client_tls::recv(void* buffer, unsigned length) -{ - unsigned nread = 0; - - try { - nread = ssl_.recv(buffer, length); - } catch (const net::WantReadError&) { - handshake_ = handshake::read; - } catch (const net::WantWriteError&) { - handshake_ = handshake::write; - } catch (const std::exception&) { - on_die(); - } - - return nread; -} - -unsigned transport_client_tls::send(const void* buffer, unsigned length) -{ - unsigned nsent = 0; - - try { - nsent = ssl_.send(buffer, length); - } catch (const net::WantReadError&) { - handshake_ = handshake::read; - } catch (const net::WantWriteError &) { - handshake_ = handshake::write; - } catch (const std::exception&) { - on_die(); - } - - return nsent; -} - -void transport_client_tls::prepare(fd_set& in, fd_set& out, net::Handle& max) -{ - if (socket_.handle() > max) - max = socket_.handle(); - - switch (handshake_) { - case handshake::read: - FD_SET(socket_.handle(), &in); - break; - case handshake::write: - FD_SET(socket_.handle(), &out); - break; - default: - transport_client::prepare(in, out, max); - break; - } -} - -void transport_client_tls::sync(fd_set& in, fd_set& out) -{ - switch (handshake_) { - case handshake::read: - case handshake::write: - handshake(); - break; - default: - transport_client::sync(in, out); - } -} - -#endif // !HAVE_SSL - -/* - * transport_server_ip - * ------------------------------------------------------------------ - */ - -transport_server_ip::transport_server_ip(const std::string& address, - std::uint16_t port, - std::uint8_t mode) - : transport_server(net::TcpSocket((mode & v6) ? AF_INET6 : AF_INET, 0)) -{ - assert((mode & v6) || (mode & v4)); - - socket_.set(net::option::SockReuseAddress(true)); - - if (mode & v6) { - // Disable or enable IPv4 when using IPv6. - socket_.set(net::option::Ipv6Only(!(mode & v4))); - - if (address == "*") - socket_.bind(net::ipv6::any(port)); - else - socket_.bind(net::ipv6::pton(address, port)); - } else { - if (address == "*") - socket_.bind(net::ipv4::any(port)); - else - socket_.bind(net::ipv4::pton(address, port)); - } - - socket_.listen(); -} - -std::uint16_t transport_server_ip::port() const -{ - auto addr = socket_.getsockname(); - - return addr.domain() == AF_INET - ? ntohs(addr.as<sockaddr_in>().sin_port) - : ntohs(addr.as<sockaddr_in6>().sin6_port); -} - -/* - * transport_server_tls - * ------------------------------------------------------------------ - */ - -#if defined(HAVE_SSL) - -transport_server_tls::transport_server_tls(const std::string& pkey, - const std::string& cert, - const std::string& address, - std::uint16_t port, - std::uint8_t mode) - : transport_server_ip(address, port, mode) - , privatekey_(pkey) - , cert_(cert) -{ -} - -std::unique_ptr<transport_client> transport_server_tls::accept() -{ - return std::make_unique<transport_client_tls>(privatekey_, cert_, *this, socket_.accept()); -} - -#endif // !HAVE_SSL - -/* - * transport_server_local - * ------------------------------------------------------------------ - */ - -#if !defined(IRCCD_SYSTEM_WINDOWS) - -transport_server_local::transport_server_local(std::string path) - : transport_server(net::TcpSocket(AF_LOCAL, 0)) - , path_(std::move(path)) -{ - socket_.bind(net::local::create(path_, true)); - socket_.listen(); -} - -transport_server_local::~transport_server_local() -{ - ::remove(path_.c_str()); -} - -#endif - -} // !irccd
--- a/libirccd/irccd/transport.hpp Fri Nov 17 19:12:32 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,424 +0,0 @@ -/* - * transport.hpp -- irccd transports - * - * Copyright (c) 2013-2017 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. - */ - -#ifndef IRCCD_TRANSPORT_HPP -#define IRCCD_TRANSPORT_HPP - -/** - * \file transport.hpp - * \brief Irccd transports. - */ - -#include <cstdint> -#include <memory> -#include <string> - -#include <boost/signals2/signal.hpp> - -#include <json.hpp> - -#include "net.hpp" -#include "sysconfig.hpp" - -namespace irccd { - -class transport_server; - -/** - * \brief Client connected to irccd. - * - * This class emits a warning upon clients request through onCommand signal. - */ -class transport_client { -public: - /** - * \brief Client state - */ - enum class state { - greeting, //!< client is getting irccd info - authenticating, //!< client requires authentication - ready, //!< client is ready to use - closing //!< client must disconnect - }; - - /** - * Signal: on_command - * ---------------------------------------------------------- - * - * Arguments: - * - the command - */ - boost::signals2::signal<void (const nlohmann::json&)> on_command; - - /** - * Signal: on_die - * ---------------------------------------------------------- - * - * The client has disconnected. - */ - boost::signals2::signal<void ()> on_die; - -private: - void error(const std::string& msg); - void flush() noexcept; - void authenticate() noexcept; - -protected: - state state_{state::greeting}; //!< current client state - transport_server& parent_; //!< parent transport server - net::TcpSocket socket_; //!< socket - std::string input_; //!< input buffer - std::string output_; //!< output buffer - - /** - * Fill the input buffer with available data. - */ - void recv() noexcept; - - /** - * Flush the output buffer from available pending data. - */ - void send() noexcept; - - /** - * Try to receive some data into the given buffer. - * - * \param buffer the destination buffer - * \param length the buffer length - * \return the number of bytes received - */ - virtual unsigned recv(void* buffer, unsigned length); - - /** - * Try to send some data into the given buffer. - * - * \param buffer the source buffer - * \param length the buffer length - * \return the number of bytes sent - */ - virtual unsigned send(const void* buffer, unsigned length); - -public: - /** - * Create a transport client from the socket. - * - * \pre socket must be valid - * \param parent the parent server - * \param socket the new socket - */ - transport_client(transport_server& parent, net::TcpSocket socket); - - /** - * Virtual destructor defaulted. - */ - virtual ~transport_client() = default; - - /** - * Get the client state. - * - * \return the client state - */ - inline enum state state() const noexcept - { - return state_; - } - - /** - * Append some data to the output queue. - * - * \pre json.is_object() - * \param json the json object - */ - void send(const nlohmann::json& json); - - /** - * \copydoc Service::prepare - */ - virtual void prepare(fd_set& in, fd_set& out, net::Handle& max); - - /** - * \copydoc Service::sync - */ - virtual void sync(fd_set& in, fd_set& out); - - /** - * Send a successful command to the client with optional extra data - * - * \pre extra must be null or object - * \param cmd the command name - * \param extra the optional extra data - */ - void success(const std::string& cmd, nlohmann::json extra = nullptr); - - /** - * Send an error status to the client. - * - * \pre extra must be null or object - * \param cmd the command name - * \param error the error string - * \param extra the optional extra data - */ - void error(const std::string& cmd, - const std::string& error, - nlohmann::json extra = nullptr); -}; - -/* - * TransportClientTls - * ------------------------------------------------------------------ - */ - -#if defined(HAVE_SSL) - -/** - * \brief TLS version of transport client. - */ -class transport_client_tls : public transport_client { -private: - enum class handshake { - write, - read, - ready - } handshake_{handshake::ready}; - - net::TlsSocket ssl_; - - void handshake(); - -protected: - /** - * \copydoc TransportClient::recv - */ - unsigned recv(void* buffer, unsigned length) override; - - /** - * \copydoc TransportClient::send - */ - unsigned send(const void* buffer, unsigned length) override; - -public: - /** - * Create the transport client. - * - * \pre socket.isOpen() - * \param pkey the private key - * \param cert the certificate file - * \param socket the accepted socket - * \param parent the parent server - * \param socket the new socket - */ - transport_client_tls(const std::string& pkey, - const std::string& cert, - transport_server& server, - net::TcpSocket socket); - - /** - * \copydoc TransportClient::prepare - */ - void prepare(fd_set& in, fd_set& out, net::Handle& max) override; - - /** - * \copydoc TransportClient::sync - */ - void sync(fd_set& in, fd_set& out) override; -}; - -#endif // !HAVE_SSL - -/* - * TransportServer - * ------------------------------------------------------------------ - */ - -/** - * \brief Bring networking between irccd and irccdctl. - * - * This class contains a master sockets for listening to TCP connections, it is - * then processed by irccd. - * - * The transport class supports the following domains: - * - * | Domain | Class | - * |-----------------------|------------------------| - * | IPv4, IPv6 | transport_server_ip | - * | Unix (not on Windows) | transport_server_local | - * - * Note: IPv4 and IPv6 can be combined, using TransportServer::IPv6 and its - * option. - */ -class transport_server { -private: - transport_server(const transport_server&) = delete; - transport_server(transport_server&&) = delete; - - transport_server& operator=(const transport_server&) = delete; - transport_server& operator=(transport_server&&) = delete; - -protected: - net::TcpSocket socket_; - std::string password_; - -public: - /** - * Default constructor. - */ - inline transport_server(net::TcpSocket socket) - : socket_(std::move(socket)) - { - } - - /** - * Get the socket handle for this transport. - * - * \return the handle - */ - inline net::Handle handle() const noexcept - { - return socket_.handle(); - } - - /** - * Get the password. - * - * \return the password - */ - inline const std::string& password() const noexcept - { - return password_; - } - - /** - * Set an optional password. - * - * \return the password - */ - inline void set_password(std::string password) noexcept - { - password_ = std::move(password); - } - - /** - * Destructor defaulted. - */ - virtual ~transport_server() = default; - - /** - * Accept a new client depending on the domain. - * - * \return the new client - */ - virtual std::unique_ptr<transport_client> accept() - { - return std::make_unique<transport_client>(*this, socket_.accept()); - } -}; - -/** - * \brief Create IP transport. - */ -class transport_server_ip : public transport_server { -public: - /** - * \brief Domain to use. - */ - enum mode { - v4 = (1 << 0), //!< IPv6 - v6 = (1 << 1) //!< IPv4 - }; - - /** - * Constructor. - * \pre mode > 0 - * \param address the address (* for any) - * \param port the port number - * \param mode the domains to use (can be OR'ed) - */ - transport_server_ip(const std::string& address, - std::uint16_t port, - std::uint8_t mode = v4); - - /** - * Get the associated port. - * - * \return the port - */ - std::uint16_t port() const; -}; - -#if defined(HAVE_SSL) - -/** - * \brief TLS over IP transport. - */ -class transport_server_tls : public transport_server_ip { -private: - std::string privatekey_; - std::string cert_; - -public: - /** - * Constructor. - * \pre mode > 0 - * \param pkey the private key file - * \param cert the certificate file - * \param address the address (* for any) - * \param port the port number - * \param mode the domains to use (can be OR'ed) - */ - transport_server_tls(const std::string& pkey, - const std::string& cert, - const std::string& address, - std::uint16_t port, - std::uint8_t mode = mode::v4); - - /** - * \copydoc TransportServer::accept - */ - std::unique_ptr<transport_client> accept() override; -}; - -#endif // !HAVE_SSL - -#if !defined(IRCCD_SYSTEM_WINDOWS) - -/** - * \brief Implementation of transports for Unix sockets. - */ -class transport_server_local : public transport_server { -private: - std::string path_; - -public: - /** - * Create a Unix transport. - * - * \param path the path - */ - transport_server_local(std::string path); - - /** - * Destroy the transport and remove the file. - */ - ~transport_server_local(); -}; - -#endif // !IRCCD_SYSTEM_WINDOWS - -} // !irccd - -#endif // !IRCCD_TRANSPORT_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/transport_client.cpp Fri Nov 17 20:56:10 2017 +0100 @@ -0,0 +1,169 @@ +/* + * transport_client.cpp -- server side transport clients + * + * Copyright (c) 2013-2017 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. + */ + +#include <cassert> + +#include "transport_client.hpp" +#include "transport_server.hpp" + +namespace irccd { + +/* + * transport_client::close + * ------------------------------------------------------------------ + */ +void transport_client::close() +{ + state_ = state_t::closing; + output_.clear(); + parent_.clients().erase(shared_from_this()); +} + +/* + * transport_client::flush + * ------------------------------------------------------------------ + */ +void transport_client::flush() +{ + if (output_.empty()) + return; + + auto self = shared_from_this(); + auto size = output_[0].first.size(); + + do_send(output_[0].first, [this, self, size] (auto code, auto xfer) { + if (output_[0].second) + output_[0].second(code); + + output_.pop_front(); + + if (code || xfer != size || (output_.empty() && state_ == state_t::closing)) + close(); + else if (!output_.empty()) + flush(); + }); +} + +/* + * transport_client::recv + * ------------------------------------------------------------------ + */ +void transport_client::recv(recv_t handler) +{ + assert(handler); + + auto self = shared_from_this(); + + do_recv(input_, [this, self, handler] (auto code, auto xfer) { + if (code || xfer == 0) { + handler("", code); + close(); + return; + } + + std::string message( + boost::asio::buffers_begin(input_.data()), + boost::asio::buffers_begin(input_.data()) + xfer - 4 + ); + + // Remove early in case of errors. + input_.consume(xfer); + + try { + auto json = nlohmann::json::parse(message); + + if (!json.is_object()) + handler(nullptr, transport_error::invalid_message); + else + handler(json, code); + } catch (...) { + handler(nullptr, transport_error::invalid_message); + } + }); +} + +/* + * transport_client::send + * ------------------------------------------------------------------ + */ +void transport_client::send(const nlohmann::json& data, send_t handler) +{ + assert(data.is_object()); + + if (state_ == state_t::closing) + return; + + auto in_progress = !output_.empty(); + + output_.emplace_back(data.dump() + "\r\n\r\n", std::move(handler)); + + if (!in_progress) + flush(); +} + +/* + * transport_client::error + * ------------------------------------------------------------------ + */ +void transport_client::error(const nlohmann::json& data, send_t handler) +{ + send(std::move(data), std::move(handler)); + set_state(state_t::closing); +} + +/* + * transport_category + * ------------------------------------------------------------------ + */ +const boost::system::error_category& transport_category() noexcept +{ + static const class category : public boost::system::error_category { + public: + const char* name() const noexcept override + { + return "transport"; + } + + std::string message(int e) const override + { + switch (static_cast<transport_error>(e)) { + case transport_error::invalid_auth: + return "invalid authentication"; + case transport_error::invalid_message: + return "invalid message"; + case transport_error::incomplete_message: + return "incomplete message"; + } + + return "unknown error"; + } + } cat; + + return cat; +} + +/* + * make_error_code + * ------------------------------------------------------------------ + */ +boost::system::error_code make_error_code(transport_error e) noexcept +{ + return {static_cast<int>(e), transport_category()}; +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/transport_client.hpp Fri Nov 17 20:56:10 2017 +0100 @@ -0,0 +1,391 @@ +/* + * transport_client.hpp -- server side transport clients + * + * Copyright (c) 2013-2017 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. + */ + +#ifndef IRCCD_TRANSPORT_CLIENT_HPP +#define IRCCD_TRANSPORT_CLIENT_HPP + +#include <deque> +#include <memory> +#include <functional> +#include <string> +#include <utility> + +#include <boost/asio.hpp> +#include <boost/asio/ssl.hpp> + +#include "json.hpp" + +#include "errors.hpp" + +namespace irccd { + +class transport_server; + +/** + * \brief Error for transports. + */ +enum class transport_error : int { + invalid_auth = 1, //! invalid authentication + invalid_message, //! client has sent an invalid message + incomplete_message //!< message requires more parameter +}; + +/** + * \brief Abstract transport client class. + * + * This class is responsible of receiving/sending data. + */ +class transport_client : public std::enable_shared_from_this<transport_client> { +public: + /** + * Callback on receive operation. + */ + using recv_t = std::function<void (const nlohmann::json&, const boost::system::error_code&)>; + + /** + * Callback on send operation. + */ + using send_t = std::function<void (const boost::system::error_code&)>; + +protected: + /** + * Handler for do_recv. + * + * The implementation should read until \r\n\r\n is found. + */ + using do_recv_handler_t = std::function<void (const boost::system::error_code&, std::size_t)>; + + /** + * Handler for do_send. + * + * The implementation must send the whole message. + */ + using do_send_handler_t = std::function<void (const boost::system::error_code&, std::size_t)>; + + /** + * Input buffer. + */ + using input_t = boost::asio::streambuf; + +private: + using output_t = std::deque<std::pair<std::string, send_t>>; + + enum class state_t { + authenticating, + ready, + closing + } state_{state_t::authenticating}; + + input_t input_; + output_t output_; + transport_server& parent_; + + void close(); + void flush(); + +protected: + /** + * Start a read operation, the implementation must start reading without + * any checks. + * + * \pre handler is not null + * \param input the input buffer + * \param handler the completion handler + */ + virtual void do_recv(input_t& input, do_recv_handler_t handler) = 0; + + /** + * Start a send operation, the implementation has no checks to perform + * because it is already done in transport_client functions. + * + * The message buffer remains valid until completion is complete. + * + * \pre message is not empty + * \pre handler is not null + * \param message the data to send + * \param handler the completion handler + */ + virtual void do_send(const std::string& message, do_send_handler_t handler) = 0; + +public: + /** + * Default constructor. + */ + inline transport_client(transport_server& server) noexcept + : parent_(server) + { + } + + /** + * Virtual destructor defaulted. + */ + virtual ~transport_client() noexcept = default; + + /** + * Get the transport server parent. + * + * \return the parent + */ + inline const transport_server& parent() const noexcept + { + return parent_; + } + + /** + * Overloaded function. + * + * \return the parent + */ + inline transport_server& parent() noexcept + { + return parent_; + } + + /** + * Get the current client state. + * + * \return the state + */ + inline state_t state() const noexcept + { + return state_; + } + + /** + * Set the client state. + * + * \param state the new state + */ + inline void set_state(state_t state) noexcept + { + state_ = state; + } + + /** + * Start a receive operation. + * + * \param handler the handler + */ + void recv(recv_t handler); + + /** + * Send or postpone some data to the client. + * + * If there are pending data, the operation will be ran once all other + * messages has been sent. + * + * \note if state is closing, no data is sent + * \pre data.is_object() + * \param data the message to send + * \param handler the optional completion handler + */ + void send(const nlohmann::json& data, send_t handler = nullptr); + + /** + * Convenient success message. + * + * \param cname the command name + * \param handler the optional handler + */ + inline void success(const std::string& cname, send_t handler = nullptr) + { + assert(!cname.empty()); + + send({{ "command", cname }}, std::move(handler)); + } + + /** + * Send a error message, the state is set to closing. + * + * The invocation is similar to: + * + * ````cpp + * set_state(state_t::closing); + * send(message, handler); + * ```` + * + * \pre message is not null + * \pre data.is_object() + * \param message the error message + * \param handler the handler + */ + void error(const nlohmann::json& data, send_t handler = nullptr); + + /** + * Convenient error overload. + * + * \param cname the command name + * \param reason the reason string + * \param handler the optional handler + */ + inline void error(const std::string& cname, const std::string& reason, send_t handler = nullptr) + { + assert(!cname.empty()); + + error({ + { "command", cname }, + { "error", reason } + }, std::move(handler)); + } + + /** + * Convenient error overload. + * + * \param cname the command name + * \param reason the error code + * \param handler the optional handler + */ + inline void error(const std::string& cname, network_error reason, send_t handler) + { + assert(!cname.empty()); + + error({ + { "command", cname }, + { "error", static_cast<int>(reason) } + }, std::move(handler)); + } +}; + +/** + * \brief Basic implementation for IP/Tcp and local sockets + * + * This class implements an recv/send function for: + * + * - boost::asio::ip::tcp + * - boost::asio::local::stream_protocol + * - boost::asio::ssl::stream + */ +template <typename Socket> +class basic_transport_client : public transport_client { +protected: + Socket socket_; + +public: + /** + * Constructor. + * + * \param sock the socket + */ + template <typename... Args> + inline basic_transport_client(transport_server& parent, Args&&... args) noexcept + : transport_client(parent) + , socket_(std::forward<Args>(args)...) + { + } + + /** + * Get the underlying socket. + * + * \return the socket + */ + inline const Socket& socket() const noexcept + { + return socket_; + } + + /** + * Overloaded function. + * + * \return the socket + */ + inline Socket& socket() noexcept + { + return socket_; + } + + /** + * \copydoc transport_client::do_recv + */ + void do_recv(input_t& input, do_recv_handler_t handler) override; + + /** + * \copydoc transport_client::do_send + */ + void do_send(const std::string& data, do_send_handler_t handler) override; +}; + +template <typename Socket> +void basic_transport_client<Socket>::do_recv(input_t& input, do_recv_handler_t handler) +{ + auto self = shared_from_this(); + + boost::asio::async_read_until(socket_, input, "\r\n\r\n", [this, self, handler] (auto code, auto xfer) { + handler(code, xfer); + }); +} + +template <typename Socket> +void basic_transport_client<Socket>::do_send(const std::string& data, do_send_handler_t handler) +{ + auto self = shared_from_this(); + + boost::asio::async_write(socket_, boost::asio::buffer(data), [this, self, handler] (auto code, auto xfer) { + handler(code, xfer); + }); +} + +/** + * \brief Secure layer client. + */ +class tls_transport_client : public basic_transport_client<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> { +public: + /** + * Construct a secure layer client. + * + * \param parent the parent transport + * \param service the service to use + * \param context the context to reuse + */ + tls_transport_client(transport_server& parent, + boost::asio::io_service& service, + boost::asio::ssl::context& context) + : basic_transport_client(parent, service, context) + { + } +}; + +/** + * Get the transport category. + * + * \return the singleton category + */ +const boost::system::error_category& transport_category() noexcept; + +/** + * Wrap the creation of an error_code based on transport_server::error. + * + * \param e the transport_server error code + * \return a boost::system::error_code with transport_category + */ +boost::system::error_code make_error_code(transport_error e) noexcept; + +} // !irccd + +namespace boost { + +namespace system { + +template <> +struct is_error_code_enum<irccd::transport_error> : public std::true_type { +}; + +} // !system + +} // !boost + +#endif // !IRCCD_TRANSPORT_CLIENT_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/transport_server.cpp Fri Nov 17 20:56:10 2017 +0100 @@ -0,0 +1,156 @@ +/* + * transport_server.cpp -- server side transports + * + * Copyright (c) 2013-2017 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. + */ + +#include <cassert> + +#include "transport_server.hpp" + +namespace irccd { + +/* + * transport_server::do_auth_check + * ------------------------------------------------------------------ + */ +bool transport_server::do_auth_check(std::shared_ptr<transport_client> client, + nlohmann::json message, + accept_t handler) +{ + assert(client); + assert(handler); + + auto command = message["command"]; + auto password = message["password"]; + + if (!command.is_string() || !password.is_string()) { + handler(nullptr, transport_error::incomplete_message); + return false; + } + + if (command != "auth" || password.get<std::string>() != password_) { + handler(nullptr, transport_error::invalid_auth); + return false; + } + + return true; +} + +/* + * transport_server::do_auth + * ------------------------------------------------------------------ + */ +void transport_server::do_auth(std::shared_ptr<transport_client> client, accept_t handler) +{ + assert(client); + assert(handler); + + client->recv([this, client, handler] (auto message, auto code) { + if (code) + handler(client, code); + if (do_auth_check(client, message, handler)) { + clients_.insert(client); + client->set_state(transport_client::state_t::ready); + handler(client, code); + } + }); +} + +/* + * transport_server::do_greetings + * ------------------------------------------------------------------ + */ +void transport_server::do_greetings(std::shared_ptr<transport_client> client, accept_t handler) +{ + assert(client); + assert(handler); + + // TODO: update this in irccd. + auto greetings = nlohmann::json({ + { "irccd", "3.0.0" } + }); + + client->send(greetings, [this, client, handler] (auto code) { + if (code) + handler(client, code); + else if (!password_.empty()) + do_auth(std::move(client), std::move(handler)); + else { + clients_.insert(client); + handler(client, code); + } + }); +} + +/* + * transport_server::accept + * ------------------------------------------------------------------ + */ +void transport_server::accept(accept_t handler) +{ + assert(handler); + + do_accept([this, handler] (auto client, auto code) { + if (code) + handler(nullptr, code); + else + do_greetings(std::move(client), std::move(handler)); + }); +} + +/* + * tls_transport_server::do_handshake + * ------------------------------------------------------------------ + * + * Perform asynchronous SSL handshake. + */ +void tls_transport_server::do_handshake(std::shared_ptr<tls_transport_client> client, accept_t handler) +{ + client->socket().async_handshake(boost::asio::ssl::stream_base::server, [client, handler] (auto code) { + if (code) + handler(nullptr, code); + else + handler(std::move(client), std::move(code)); + }); +} + +/* + * tls_transport_server::tls_transport_server + * ------------------------------------------------------------------ + */ +tls_transport_server::tls_transport_server(acceptor_t acceptor, context_t context) + : tcp_transport_server(std::move(acceptor)) + , context_(std::move(context)) +{ +} + +/* + * tls_transport_server::do_accept + * ------------------------------------------------------------------ + */ +void tls_transport_server::do_accept(accept_t handler) +{ + auto client = std::make_shared<tls_transport_client>(*this, acceptor_.get_io_service(), context_); + + acceptor_.async_accept(client->socket().lowest_layer(), [this, client, handler] (auto code) { + if (code) + handler(nullptr, code); + else + do_handshake(std::move(client), std::move(handler)); + }); +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/transport_server.hpp Fri Nov 17 20:56:10 2017 +0100 @@ -0,0 +1,244 @@ +/* + * transport_server.hpp -- server side transports + * + * Copyright (c) 2013-2017 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. + */ + +#ifndef IRCCD_TRANSPORT_SERVER_HPP +#define IRCCD_TRANSPORT_SERVER_HPP + +#include <cassert> +#include <functional> +#include <memory> +#include <unordered_set> +#include <type_traits> + +#include <boost/asio.hpp> +#include <boost/asio/ssl.hpp> + +#include "transport_client.hpp" + +namespace irccd { + +/** + * \brief Abstract transport server class. + * + * This class create asynchronous operation to accept new clients. + */ +class transport_server { +protected: + /** + * Set of clients. + */ + using client_set_t = std::unordered_set<std::shared_ptr<transport_client>>; + + /** + * Callback when a new client should be accepted. + */ + using accept_t = std::function<void (std::shared_ptr<transport_client>, boost::system::error_code)>; + +private: + client_set_t clients_; + std::string password_; + + bool do_auth_check(std::shared_ptr<transport_client>, nlohmann::json, accept_t); + void do_auth(std::shared_ptr<transport_client>, accept_t); + void do_greetings(std::shared_ptr<transport_client>, accept_t); + +protected: + /** + * Start accept operation, the implementation should not block and call + * the handler function on error or completion. + * + * \pre handler must not be null + * \param handler the handler function + */ + virtual void do_accept(accept_t handler) = 0; + +public: + /** + * Default constructor. + */ + transport_server() noexcept = default; + + /** + * Virtual destructor defaulted. + */ + virtual ~transport_server() noexcept = default; + + /** + * Wrapper that automatically add the new client into the list. + * + * If handler is not null it is called on error or on successful accept + * operation. + * + * \param handler the handler + */ + void accept(accept_t handler); + + /** + * Get the clients. + * + * \return the clients + */ + inline const client_set_t& clients() const noexcept + { + return clients_; + } + + /** + * Overloaded function. + * + * \return the clients + */ + inline client_set_t& clients() noexcept + { + return clients_; + } + + /** + * Get the current password, empty string means no password. + * + * \return the password + */ + inline const std::string& password() const noexcept + { + return password_; + } + + /** + * Set an optional password, empty string means no password. + * + * \param password the password + */ + inline void set_password(std::string password) noexcept + { + password_ = std::move(password); + } +}; + +/** + * \brief Basic implementation for IP/TCP and local sockets + * + * This class implements an accept function for: + * + * - boost::asio::ip::tcp + * - boost::asio::local::stream_protocol + */ +template <typename Protocol> +class basic_transport_server : public transport_server { +public: + /** + * Type for underlying socket. + */ + using socket_t = typename Protocol::socket; + + /** + * Type for underlying acceptor. + */ + using acceptor_t = typename Protocol::acceptor; + +protected: + /** + * The acceptor object. + */ + acceptor_t acceptor_; + +protected: + /** + * \copydoc transport_server::accept + */ + void do_accept(accept_t handler) override; + +public: + /** + * Constructor with an acceptor in parameter. + * + * \pre acceptor.is_open() + * \param acceptor the already bound acceptor + */ + basic_transport_server(acceptor_t acceptor); +}; + +template <typename Protocol> +basic_transport_server<Protocol>::basic_transport_server(acceptor_t acceptor) + : acceptor_(std::move(acceptor)) +{ + assert(acceptor_.is_open()); +} + +template <typename Protocol> +void basic_transport_server<Protocol>::do_accept(accept_t handler) +{ + using client_type = basic_transport_client<socket_t>; + + auto client = std::make_shared<client_type>(*this, acceptor_.get_io_service()); + + acceptor_.async_accept(client->socket(), [client, handler] (auto code) { + if (!code) + handler(std::move(client), std::move(code)); + else + handler(nullptr, code); + }); +} + +/** + * Convenient type for IP/TCP + */ +using tcp_transport_server = basic_transport_server<boost::asio::ip::tcp>; + +#if !defined(_WIN32) + +/** + * Convenient type for UNIX local sockets. + */ +using local_transport_server = basic_transport_server<boost::asio::local::stream_protocol>; + +#endif // !_WIN32 + +/** + * \brief Secure layer implementation. + */ +class tls_transport_server : public tcp_transport_server { +public: + using context_t = boost::asio::ssl::context; + +private: + context_t context_; + + void do_handshake(std::shared_ptr<tls_transport_client>, accept_t); + +protected: + /** + * \copydoc tcp_transport_server::do_accept + * + * This function does the same as tcp_transport_server::do_accept but it + * also perform a SSL handshake after a successful accept operation. + */ + void do_accept(accept_t handler) override; + +public: + /** + * Construct a secure layer transport server. + * + * \param acceptor the acceptor + * \param context the SSL context + */ + tls_transport_server(acceptor_t acceptor, context_t context); +}; + +} // !irccd + +#endif // !IRCCD_TRANSPORT_SERVER_HPP
--- a/tests/js-plugin/main.cpp Fri Nov 17 19:12:32 2017 +0100 +++ b/tests/js-plugin/main.cpp Fri Nov 17 20:56:10 2017 +0100 @@ -17,6 +17,7 @@ */ #define BOOST_TEST_MODULE "Javascript plugin object" +#include <boost/asio.hpp> #include <boost/test/unit_test.hpp> #include <irccd/irccd.hpp> @@ -30,7 +31,8 @@ class js_plugin_test { protected: - irccd irccd_; + boost::asio::io_service service_; + irccd irccd_{service_}; std::shared_ptr<js_plugin> plugin_; void load(std::string name, std::string path) @@ -95,7 +97,8 @@ class js_plugin_loader_test { protected: - irccd irccd_; + boost::asio::io_service service_; + irccd irccd_{service_}; std::shared_ptr<plugin> plugin_; js_plugin_loader_test()